summaryrefslogtreecommitdiff
path: root/net/nfc
diff options
context:
space:
mode:
Diffstat (limited to 'net/nfc')
-rw-r--r--net/nfc/af_nfc.c2
-rw-r--r--net/nfc/llcp.h2
-rw-r--r--net/nfc/llcp_core.c2
-rw-r--r--net/nfc/llcp_sock.c8
-rw-r--r--net/nfc/nci/Kconfig7
-rw-r--r--net/nfc/nci/Makefile3
-rw-r--r--net/nfc/nci/core.c105
-rw-r--r--net/nfc/nci/hci.c11
-rw-r--r--net/nfc/nci/ntf.c10
-rw-r--r--net/nfc/nci/rsp.c10
-rw-r--r--net/nfc/nci/uart.c494
-rw-r--r--net/nfc/netlink.c55
-rw-r--r--net/nfc/nfc.h2
-rw-r--r--net/nfc/rawsock.c4
14 files changed, 688 insertions, 27 deletions
diff --git a/net/nfc/af_nfc.c b/net/nfc/af_nfc.c
index 2277276..54e40fa 100644
--- a/net/nfc/af_nfc.c
+++ b/net/nfc/af_nfc.c
@@ -40,7 +40,7 @@ static int nfc_sock_create(struct net *net, struct socket *sock, int proto,
read_lock(&proto_tab_lock);
if (proto_tab[proto] && try_module_get(proto_tab[proto]->owner)) {
- rc = proto_tab[proto]->create(net, sock, proto_tab[proto]);
+ rc = proto_tab[proto]->create(net, sock, proto_tab[proto], kern);
module_put(proto_tab[proto]->owner);
}
read_unlock(&proto_tab_lock);
diff --git a/net/nfc/llcp.h b/net/nfc/llcp.h
index de1789e..1f68724 100644
--- a/net/nfc/llcp.h
+++ b/net/nfc/llcp.h
@@ -225,7 +225,7 @@ void nfc_llcp_send_to_raw_sock(struct nfc_llcp_local *local,
struct sk_buff *skb, u8 direction);
/* Sock API */
-struct sock *nfc_llcp_sock_alloc(struct socket *sock, int type, gfp_t gfp);
+struct sock *nfc_llcp_sock_alloc(struct socket *sock, int type, gfp_t gfp, int kern);
void nfc_llcp_sock_free(struct nfc_llcp_sock *sock);
void nfc_llcp_accept_unlink(struct sock *sk);
void nfc_llcp_accept_enqueue(struct sock *parent, struct sock *sk);
diff --git a/net/nfc/llcp_core.c b/net/nfc/llcp_core.c
index b18f07c..9887627 100644
--- a/net/nfc/llcp_core.c
+++ b/net/nfc/llcp_core.c
@@ -934,7 +934,7 @@ static void nfc_llcp_recv_connect(struct nfc_llcp_local *local,
sock->ssap = ssap;
}
- new_sk = nfc_llcp_sock_alloc(NULL, parent->sk_type, GFP_ATOMIC);
+ new_sk = nfc_llcp_sock_alloc(NULL, parent->sk_type, GFP_ATOMIC, 0);
if (new_sk == NULL) {
reason = LLCP_DM_REJ;
release_sock(&sock->sk);
diff --git a/net/nfc/llcp_sock.c b/net/nfc/llcp_sock.c
index 9578bd6..b7de0da 100644
--- a/net/nfc/llcp_sock.c
+++ b/net/nfc/llcp_sock.c
@@ -942,12 +942,12 @@ static void llcp_sock_destruct(struct sock *sk)
}
}
-struct sock *nfc_llcp_sock_alloc(struct socket *sock, int type, gfp_t gfp)
+struct sock *nfc_llcp_sock_alloc(struct socket *sock, int type, gfp_t gfp, int kern)
{
struct sock *sk;
struct nfc_llcp_sock *llcp_sock;
- sk = sk_alloc(&init_net, PF_NFC, gfp, &llcp_sock_proto);
+ sk = sk_alloc(&init_net, PF_NFC, gfp, &llcp_sock_proto, kern);
if (!sk)
return NULL;
@@ -993,7 +993,7 @@ void nfc_llcp_sock_free(struct nfc_llcp_sock *sock)
}
static int llcp_sock_create(struct net *net, struct socket *sock,
- const struct nfc_protocol *nfc_proto)
+ const struct nfc_protocol *nfc_proto, int kern)
{
struct sock *sk;
@@ -1009,7 +1009,7 @@ static int llcp_sock_create(struct net *net, struct socket *sock,
else
sock->ops = &llcp_sock_ops;
- sk = nfc_llcp_sock_alloc(sock, sock->type, GFP_ATOMIC);
+ sk = nfc_llcp_sock_alloc(sock, sock->type, GFP_ATOMIC, kern);
if (sk == NULL)
return -ENOMEM;
diff --git a/net/nfc/nci/Kconfig b/net/nfc/nci/Kconfig
index a4f1e42..901c1dd 100644
--- a/net/nfc/nci/Kconfig
+++ b/net/nfc/nci/Kconfig
@@ -19,3 +19,10 @@ config NFC_NCI_SPI
an NFC Controller (NFCC) and a Device Host (DH).
Say yes if you use an NCI driver that requires SPI link layer.
+
+config NFC_NCI_UART
+ depends on NFC_NCI && TTY
+ tristate "NCI over UART protocol support"
+ default n
+ help
+ Say yes if you use an NCI driver that requires UART link layer.
diff --git a/net/nfc/nci/Makefile b/net/nfc/nci/Makefile
index 7ed8949..b4b85b8 100644
--- a/net/nfc/nci/Makefile
+++ b/net/nfc/nci/Makefile
@@ -7,3 +7,6 @@ obj-$(CONFIG_NFC_NCI) += nci.o
nci-objs := core.o data.o lib.o ntf.o rsp.o hci.o
nci-$(CONFIG_NFC_NCI_SPI) += spi.o
+
+nci_uart-y += uart.o
+obj-$(CONFIG_NFC_NCI_UART) += nci_uart.o
diff --git a/net/nfc/nci/core.c b/net/nfc/nci/core.c
index 49ff321..95af2d2 100644
--- a/net/nfc/nci/core.c
+++ b/net/nfc/nci/core.c
@@ -28,6 +28,7 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": %s: " fmt, __func__
#include <linux/module.h>
+#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/workqueue.h>
#include <linux/completion.h>
@@ -73,6 +74,7 @@ void nci_req_complete(struct nci_dev *ndev, int result)
complete(&ndev->req_completion);
}
}
+EXPORT_SYMBOL(nci_req_complete);
static void nci_req_cancel(struct nci_dev *ndev, int err)
{
@@ -323,6 +325,32 @@ static void nci_rf_deactivate_req(struct nci_dev *ndev, unsigned long opt)
sizeof(struct nci_rf_deactivate_cmd), &cmd);
}
+struct nci_prop_cmd_param {
+ __u16 opcode;
+ size_t len;
+ __u8 *payload;
+};
+
+static void nci_prop_cmd_req(struct nci_dev *ndev, unsigned long opt)
+{
+ struct nci_prop_cmd_param *param = (struct nci_prop_cmd_param *)opt;
+
+ nci_send_cmd(ndev, param->opcode, param->len, param->payload);
+}
+
+int nci_prop_cmd(struct nci_dev *ndev, __u8 oid, size_t len, __u8 *payload)
+{
+ struct nci_prop_cmd_param param;
+
+ param.opcode = nci_opcode_pack(NCI_GID_PROPRIETARY, oid);
+ param.len = len;
+ param.payload = payload;
+
+ return __nci_request(ndev, nci_prop_cmd_req, (unsigned long)&param,
+ msecs_to_jiffies(NCI_CMD_TIMEOUT));
+}
+EXPORT_SYMBOL(nci_prop_cmd);
+
static int nci_open_device(struct nci_dev *ndev)
{
int rc = 0;
@@ -343,11 +371,17 @@ static int nci_open_device(struct nci_dev *ndev)
set_bit(NCI_INIT, &ndev->flags);
- rc = __nci_request(ndev, nci_reset_req, 0,
- msecs_to_jiffies(NCI_RESET_TIMEOUT));
+ if (ndev->ops->init)
+ rc = ndev->ops->init(ndev);
- if (ndev->ops->setup)
- ndev->ops->setup(ndev);
+ if (!rc) {
+ rc = __nci_request(ndev, nci_reset_req, 0,
+ msecs_to_jiffies(NCI_RESET_TIMEOUT));
+ }
+
+ if (!rc && ndev->ops->setup) {
+ rc = ndev->ops->setup(ndev);
+ }
if (!rc) {
rc = __nci_request(ndev, nci_init_req, 0,
@@ -407,6 +441,12 @@ static int nci_close_device(struct nci_dev *ndev)
set_bit(NCI_INIT, &ndev->flags);
__nci_request(ndev, nci_reset_req, 0,
msecs_to_jiffies(NCI_RESET_TIMEOUT));
+
+ /* After this point our queues are empty
+ * and no works are scheduled.
+ */
+ ndev->ops->close(ndev);
+
clear_bit(NCI_INIT, &ndev->flags);
del_timer_sync(&ndev->cmd_timer);
@@ -414,10 +454,6 @@ static int nci_close_device(struct nci_dev *ndev)
/* Flush cmd wq */
flush_workqueue(ndev->cmd_wq);
- /* After this point our queues are empty
- * and no works are scheduled. */
- ndev->ops->close(ndev);
-
/* Clear flags */
ndev->flags = 0;
@@ -762,7 +798,7 @@ static void nci_deactivate_target(struct nfc_dev *nfc_dev,
if (atomic_read(&ndev->state) == NCI_POLL_ACTIVE) {
nci_request(ndev, nci_rf_deactivate_req,
- NCI_DEACTIVATE_TYPE_SLEEP_MODE,
+ NCI_DEACTIVATE_TYPE_IDLE_MODE,
msecs_to_jiffies(NCI_RF_DEACTIVATE_TIMEOUT));
}
}
@@ -961,6 +997,14 @@ struct nci_dev *nci_allocate_device(struct nci_ops *ops,
return NULL;
ndev->ops = ops;
+
+ if (ops->n_prop_ops > NCI_MAX_PROPRIETARY_CMD) {
+ pr_err("Too many proprietary commands: %zd\n",
+ ops->n_prop_ops);
+ ops->prop_ops = NULL;
+ ops->n_prop_ops = 0;
+ }
+
ndev->tx_headroom = tx_headroom;
ndev->tx_tailroom = tx_tailroom;
init_completion(&ndev->req_completion);
@@ -1165,6 +1209,49 @@ int nci_send_cmd(struct nci_dev *ndev, __u16 opcode, __u8 plen, void *payload)
return 0;
}
+/* Proprietary commands API */
+static struct nci_prop_ops *prop_cmd_lookup(struct nci_dev *ndev,
+ __u16 opcode)
+{
+ size_t i;
+ struct nci_prop_ops *prop_op;
+
+ if (!ndev->ops->prop_ops || !ndev->ops->n_prop_ops)
+ return NULL;
+
+ for (i = 0; i < ndev->ops->n_prop_ops; i++) {
+ prop_op = &ndev->ops->prop_ops[i];
+ if (prop_op->opcode == opcode)
+ return prop_op;
+ }
+
+ return NULL;
+}
+
+int nci_prop_rsp_packet(struct nci_dev *ndev, __u16 rsp_opcode,
+ struct sk_buff *skb)
+{
+ struct nci_prop_ops *prop_op;
+
+ prop_op = prop_cmd_lookup(ndev, rsp_opcode);
+ if (!prop_op || !prop_op->rsp)
+ return -ENOTSUPP;
+
+ return prop_op->rsp(ndev, skb);
+}
+
+int nci_prop_ntf_packet(struct nci_dev *ndev, __u16 ntf_opcode,
+ struct sk_buff *skb)
+{
+ struct nci_prop_ops *prop_op;
+
+ prop_op = prop_cmd_lookup(ndev, ntf_opcode);
+ if (!prop_op || !prop_op->ntf)
+ return -ENOTSUPP;
+
+ return prop_op->ntf(ndev, skb);
+}
+
/* ---- NCI TX Data worker thread ---- */
static void nci_tx_work(struct work_struct *work)
diff --git a/net/nfc/nci/hci.c b/net/nfc/nci/hci.c
index ed54ec5..af002df 100644
--- a/net/nfc/nci/hci.c
+++ b/net/nfc/nci/hci.c
@@ -639,22 +639,19 @@ int nci_hci_dev_session_init(struct nci_dev *ndev)
ndev->hci_dev->init_data.gates[0].gate,
ndev->hci_dev->init_data.gates[0].pipe);
if (r < 0)
- goto exit;
+ return r;
r = nci_hci_get_param(ndev, NCI_HCI_ADMIN_GATE,
NCI_HCI_ADMIN_PARAM_SESSION_IDENTITY, &skb);
if (r < 0)
- goto exit;
+ return r;
if (skb->len &&
skb->len == strlen(ndev->hci_dev->init_data.session_id) &&
- memcmp(ndev->hci_dev->init_data.session_id,
- skb->data, skb->len) == 0 &&
+ !memcmp(ndev->hci_dev->init_data.session_id, skb->data, skb->len) &&
ndev->ops->hci_load_session) {
/* Restore gate<->pipe table from some proprietary location. */
r = ndev->ops->hci_load_session(ndev);
- if (r < 0)
- goto exit;
} else {
r = nci_hci_dev_connect_gates(ndev,
ndev->hci_dev->init_data.gate_count,
@@ -667,8 +664,6 @@ int nci_hci_dev_session_init(struct nci_dev *ndev)
ndev->hci_dev->init_data.session_id,
strlen(ndev->hci_dev->init_data.session_id));
}
- if (r == 0)
- goto exit;
exit:
kfree_skb(skb);
diff --git a/net/nfc/nci/ntf.c b/net/nfc/nci/ntf.c
index 3218071..5d1c2e3 100644
--- a/net/nfc/nci/ntf.c
+++ b/net/nfc/nci/ntf.c
@@ -758,6 +758,15 @@ void nci_ntf_packet(struct nci_dev *ndev, struct sk_buff *skb)
/* strip the nci control header */
skb_pull(skb, NCI_CTRL_HDR_SIZE);
+ if (nci_opcode_gid(ntf_opcode) == NCI_GID_PROPRIETARY) {
+ if (nci_prop_ntf_packet(ndev, ntf_opcode, skb)) {
+ pr_err("unsupported ntf opcode 0x%x\n",
+ ntf_opcode);
+ }
+
+ goto end;
+ }
+
switch (ntf_opcode) {
case NCI_OP_CORE_CONN_CREDITS_NTF:
nci_core_conn_credits_ntf_packet(ndev, skb);
@@ -796,5 +805,6 @@ void nci_ntf_packet(struct nci_dev *ndev, struct sk_buff *skb)
break;
}
+end:
kfree_skb(skb);
}
diff --git a/net/nfc/nci/rsp.c b/net/nfc/nci/rsp.c
index 02486bc..408bd8f 100644
--- a/net/nfc/nci/rsp.c
+++ b/net/nfc/nci/rsp.c
@@ -296,6 +296,15 @@ void nci_rsp_packet(struct nci_dev *ndev, struct sk_buff *skb)
/* strip the nci control header */
skb_pull(skb, NCI_CTRL_HDR_SIZE);
+ if (nci_opcode_gid(rsp_opcode) == NCI_GID_PROPRIETARY) {
+ if (nci_prop_rsp_packet(ndev, rsp_opcode, skb) == -ENOTSUPP) {
+ pr_err("unsupported rsp opcode 0x%x\n",
+ rsp_opcode);
+ }
+
+ goto end;
+ }
+
switch (rsp_opcode) {
case NCI_OP_CORE_RESET_RSP:
nci_core_reset_rsp_packet(ndev, skb);
@@ -346,6 +355,7 @@ void nci_rsp_packet(struct nci_dev *ndev, struct sk_buff *skb)
break;
}
+end:
kfree_skb(skb);
/* trigger the next cmd */
diff --git a/net/nfc/nci/uart.c b/net/nfc/nci/uart.c
new file mode 100644
index 0000000..21d8875
--- /dev/null
+++ b/net/nfc/nci/uart.c
@@ -0,0 +1,494 @@
+/*
+ * Copyright (C) 2015, Marvell International Ltd.
+ *
+ * This software file (the "File") is distributed by Marvell International
+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991
+ * (the "License"). You may use, redistribute and/or modify this File in
+ * accordance with the terms and conditions of the License, a copy of which
+ * is available on the worldwide web at
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
+ *
+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE
+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about
+ * this warranty disclaimer.
+ */
+
+/* Inspired (hugely) by HCI LDISC implementation in Bluetooth.
+ *
+ * Copyright (C) 2000-2001 Qualcomm Incorporated
+ * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com>
+ * Copyright (C) 2004-2005 Marcel Holtmann <marcel@holtmann.org>
+ */
+
+#include <linux/module.h>
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/fcntl.h>
+#include <linux/interrupt.h>
+#include <linux/ptrace.h>
+#include <linux/poll.h>
+
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/signal.h>
+#include <linux/ioctl.h>
+#include <linux/skbuff.h>
+
+#include <net/nfc/nci.h>
+#include <net/nfc/nci_core.h>
+
+/* TX states */
+#define NCI_UART_SENDING 1
+#define NCI_UART_TX_WAKEUP 2
+
+static struct nci_uart *nci_uart_drivers[NCI_UART_DRIVER_MAX];
+
+static inline struct sk_buff *nci_uart_dequeue(struct nci_uart *nu)
+{
+ struct sk_buff *skb = nu->tx_skb;
+
+ if (!skb)
+ skb = skb_dequeue(&nu->tx_q);
+ else
+ nu->tx_skb = NULL;
+
+ return skb;
+}
+
+static inline int nci_uart_queue_empty(struct nci_uart *nu)
+{
+ if (nu->tx_skb)
+ return 0;
+
+ return skb_queue_empty(&nu->tx_q);
+}
+
+static int nci_uart_tx_wakeup(struct nci_uart *nu)
+{
+ if (test_and_set_bit(NCI_UART_SENDING, &nu->tx_state)) {
+ set_bit(NCI_UART_TX_WAKEUP, &nu->tx_state);
+ return 0;
+ }
+
+ schedule_work(&nu->write_work);
+
+ return 0;
+}
+
+static void nci_uart_write_work(struct work_struct *work)
+{
+ struct nci_uart *nu = container_of(work, struct nci_uart, write_work);
+ struct tty_struct *tty = nu->tty;
+ struct sk_buff *skb;
+
+restart:
+ clear_bit(NCI_UART_TX_WAKEUP, &nu->tx_state);
+
+ if (nu->ops.tx_start)
+ nu->ops.tx_start(nu);
+
+ while ((skb = nci_uart_dequeue(nu))) {
+ int len;
+
+ set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
+ len = tty->ops->write(tty, skb->data, skb->len);
+ skb_pull(skb, len);
+ if (skb->len) {
+ nu->tx_skb = skb;
+ break;
+ }
+ kfree_skb(skb);
+ }
+
+ if (test_bit(NCI_UART_TX_WAKEUP, &nu->tx_state))
+ goto restart;
+
+ if (nu->ops.tx_done && nci_uart_queue_empty(nu))
+ nu->ops.tx_done(nu);
+
+ clear_bit(NCI_UART_SENDING, &nu->tx_state);
+}
+
+static int nci_uart_set_driver(struct tty_struct *tty, unsigned int driver)
+{
+ struct nci_uart *nu = NULL;
+ int ret;
+
+ if (driver >= NCI_UART_DRIVER_MAX)
+ return -EINVAL;
+
+ if (!nci_uart_drivers[driver])
+ return -ENOENT;
+
+ nu = kzalloc(sizeof(*nu), GFP_KERNEL);
+ if (!nu)
+ return -ENOMEM;
+
+ memcpy(nu, nci_uart_drivers[driver], sizeof(struct nci_uart));
+ nu->tty = tty;
+ tty->disc_data = nu;
+ skb_queue_head_init(&nu->tx_q);
+ INIT_WORK(&nu->write_work, nci_uart_write_work);
+ spin_lock_init(&nu->rx_lock);
+
+ ret = nu->ops.open(nu);
+ if (ret) {
+ tty->disc_data = NULL;
+ kfree(nu);
+ } else if (!try_module_get(nu->owner)) {
+ nu->ops.close(nu);
+ tty->disc_data = NULL;
+ kfree(nu);
+ return -ENOENT;
+ }
+ return ret;
+}
+
+/* ------ LDISC part ------ */
+
+/* nci_uart_tty_open
+ *
+ * Called when line discipline changed to NCI_UART.
+ *
+ * Arguments:
+ * tty pointer to tty info structure
+ * Return Value:
+ * 0 if success, otherwise error code
+ */
+static int nci_uart_tty_open(struct tty_struct *tty)
+{
+ /* Error if the tty has no write op instead of leaving an exploitable
+ * hole
+ */
+ if (!tty->ops->write)
+ return -EOPNOTSUPP;
+
+ tty->disc_data = NULL;
+ tty->receive_room = 65536;
+
+ /* Flush any pending characters in the driver and line discipline. */
+
+ /* FIXME: why is this needed. Note don't use ldisc_ref here as the
+ * open path is before the ldisc is referencable.
+ */
+
+ if (tty->ldisc->ops->flush_buffer)
+ tty->ldisc->ops->flush_buffer(tty);
+ tty_driver_flush_buffer(tty);
+
+ return 0;
+}
+
+/* nci_uart_tty_close()
+ *
+ * Called when the line discipline is changed to something
+ * else, the tty is closed, or the tty detects a hangup.
+ */
+static void nci_uart_tty_close(struct tty_struct *tty)
+{
+ struct nci_uart *nu = (void *)tty->disc_data;
+
+ /* Detach from the tty */
+ tty->disc_data = NULL;
+
+ if (!nu)
+ return;
+
+ if (nu->tx_skb)
+ kfree_skb(nu->tx_skb);
+ if (nu->rx_skb)
+ kfree_skb(nu->rx_skb);
+
+ skb_queue_purge(&nu->tx_q);
+
+ nu->ops.close(nu);
+ nu->tty = NULL;
+ module_put(nu->owner);
+
+ cancel_work_sync(&nu->write_work);
+
+ kfree(nu);
+}
+
+/* nci_uart_tty_wakeup()
+ *
+ * Callback for transmit wakeup. Called when low level
+ * device driver can accept more send data.
+ *
+ * Arguments: tty pointer to associated tty instance data
+ * Return Value: None
+ */
+static void nci_uart_tty_wakeup(struct tty_struct *tty)
+{
+ struct nci_uart *nu = (void *)tty->disc_data;
+
+ if (!nu)
+ return;
+
+ clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
+
+ if (tty != nu->tty)
+ return;
+
+ nci_uart_tx_wakeup(nu);
+}
+
+/* nci_uart_tty_receive()
+ *
+ * Called by tty low level driver when receive data is
+ * available.
+ *
+ * Arguments: tty pointer to tty isntance data
+ * data pointer to received data
+ * flags pointer to flags for data
+ * count count of received data in bytes
+ *
+ * Return Value: None
+ */
+static void nci_uart_tty_receive(struct tty_struct *tty, const u8 *data,
+ char *flags, int count)
+{
+ struct nci_uart *nu = (void *)tty->disc_data;
+
+ if (!nu || tty != nu->tty)
+ return;
+
+ spin_lock(&nu->rx_lock);
+ nu->ops.recv_buf(nu, (void *)data, flags, count);
+ spin_unlock(&nu->rx_lock);
+
+ tty_unthrottle(tty);
+}
+
+/* nci_uart_tty_ioctl()
+ *
+ * Process IOCTL system call for the tty device.
+ *
+ * Arguments:
+ *
+ * tty pointer to tty instance data
+ * file pointer to open file object for device
+ * cmd IOCTL command code
+ * arg argument for IOCTL call (cmd dependent)
+ *
+ * Return Value: Command dependent
+ */
+static int nci_uart_tty_ioctl(struct tty_struct *tty, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ struct nci_uart *nu = (void *)tty->disc_data;
+ int err = 0;
+
+ switch (cmd) {
+ case NCIUARTSETDRIVER:
+ if (!nu)
+ return nci_uart_set_driver(tty, (unsigned int)arg);
+ else
+ return -EBUSY;
+ break;
+ default:
+ err = n_tty_ioctl_helper(tty, file, cmd, arg);
+ break;
+ }
+
+ return err;
+}
+
+/* We don't provide read/write/poll interface for user space. */
+static ssize_t nci_uart_tty_read(struct tty_struct *tty, struct file *file,
+ unsigned char __user *buf, size_t nr)
+{
+ return 0;
+}
+
+static ssize_t nci_uart_tty_write(struct tty_struct *tty, struct file *file,
+ const unsigned char *data, size_t count)
+{
+ return 0;
+}
+
+static unsigned int nci_uart_tty_poll(struct tty_struct *tty,
+ struct file *filp, poll_table *wait)
+{
+ return 0;
+}
+
+static int nci_uart_send(struct nci_uart *nu, struct sk_buff *skb)
+{
+ /* Queue TX packet */
+ skb_queue_tail(&nu->tx_q, skb);
+
+ /* Try to start TX (if possible) */
+ nci_uart_tx_wakeup(nu);
+
+ return 0;
+}
+
+/* -- Default recv_buf handler --
+ *
+ * This handler supposes that NCI frames are sent over UART link without any
+ * framing. It reads NCI header, retrieve the packet size and once all packet
+ * bytes are received it passes it to nci_uart driver for processing.
+ */
+static int nci_uart_default_recv_buf(struct nci_uart *nu, const u8 *data,
+ char *flags, int count)
+{
+ int chunk_len;
+
+ if (!nu->ndev) {
+ nfc_err(nu->tty->dev,
+ "receive data from tty but no NCI dev is attached yet, drop buffer\n");
+ return 0;
+ }
+
+ /* Decode all incoming data in packets
+ * and enqueue then for processing.
+ */
+ while (count > 0) {
+ /* If this is the first data of a packet, allocate a buffer */
+ if (!nu->rx_skb) {
+ nu->rx_packet_len = -1;
+ nu->rx_skb = nci_skb_alloc(nu->ndev,
+ NCI_MAX_PACKET_SIZE,
+ GFP_KERNEL);
+ if (!nu->rx_skb)
+ return -ENOMEM;
+ }
+
+ /* Eat byte after byte till full packet header is received */
+ if (nu->rx_skb->len < NCI_CTRL_HDR_SIZE) {
+ *skb_put(nu->rx_skb, 1) = *data++;
+ --count;
+ continue;
+ }
+
+ /* Header was received but packet len was not read */
+ if (nu->rx_packet_len < 0)
+ nu->rx_packet_len = NCI_CTRL_HDR_SIZE +
+ nci_plen(nu->rx_skb->data);
+
+ /* Compute how many bytes are missing and how many bytes can
+ * be consumed.
+ */
+ chunk_len = nu->rx_packet_len - nu->rx_skb->len;
+ if (count < chunk_len)
+ chunk_len = count;
+ memcpy(skb_put(nu->rx_skb, chunk_len), data, chunk_len);
+ data += chunk_len;
+ count -= chunk_len;
+
+ /* Chcek if packet is fully received */
+ if (nu->rx_packet_len == nu->rx_skb->len) {
+ /* Pass RX packet to driver */
+ if (nu->ops.recv(nu, nu->rx_skb) != 0)
+ nfc_err(nu->tty->dev, "corrupted RX packet\n");
+ /* Next packet will be a new one */
+ nu->rx_skb = NULL;
+ }
+ }
+
+ return 0;
+}
+
+/* -- Default recv handler -- */
+static int nci_uart_default_recv(struct nci_uart *nu, struct sk_buff *skb)
+{
+ return nci_recv_frame(nu->ndev, skb);
+}
+
+int nci_uart_register(struct nci_uart *nu)
+{
+ if (!nu || !nu->ops.open ||
+ !nu->ops.recv || !nu->ops.close)
+ return -EINVAL;
+
+ /* Set the send callback */
+ nu->ops.send = nci_uart_send;
+
+ /* Install default handlers if not overridden */
+ if (!nu->ops.recv_buf)
+ nu->ops.recv_buf = nci_uart_default_recv_buf;
+ if (!nu->ops.recv)
+ nu->ops.recv = nci_uart_default_recv;
+
+ /* Add this driver in the driver list */
+ if (nci_uart_drivers[nu->driver]) {
+ pr_err("driver %d is already registered\n", nu->driver);
+ return -EBUSY;
+ }
+ nci_uart_drivers[nu->driver] = nu;
+
+ pr_info("NCI uart driver '%s [%d]' registered\n", nu->name, nu->driver);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(nci_uart_register);
+
+void nci_uart_unregister(struct nci_uart *nu)
+{
+ pr_info("NCI uart driver '%s [%d]' unregistered\n", nu->name,
+ nu->driver);
+
+ /* Remove this driver from the driver list */
+ nci_uart_drivers[nu->driver] = NULL;
+}
+EXPORT_SYMBOL_GPL(nci_uart_unregister);
+
+void nci_uart_set_config(struct nci_uart *nu, int baudrate, int flow_ctrl)
+{
+ struct ktermios new_termios;
+
+ if (!nu->tty)
+ return;
+
+ down_read(&nu->tty->termios_rwsem);
+ new_termios = nu->tty->termios;
+ up_read(&nu->tty->termios_rwsem);
+ tty_termios_encode_baud_rate(&new_termios, baudrate, baudrate);
+
+ if (flow_ctrl)
+ new_termios.c_cflag |= CRTSCTS;
+ else
+ new_termios.c_cflag &= ~CRTSCTS;
+
+ tty_set_termios(nu->tty, &new_termios);
+}
+EXPORT_SYMBOL_GPL(nci_uart_set_config);
+
+static struct tty_ldisc_ops nci_uart_ldisc = {
+ .magic = TTY_LDISC_MAGIC,
+ .owner = THIS_MODULE,
+ .name = "n_nci",
+ .open = nci_uart_tty_open,
+ .close = nci_uart_tty_close,
+ .read = nci_uart_tty_read,
+ .write = nci_uart_tty_write,
+ .poll = nci_uart_tty_poll,
+ .receive_buf = nci_uart_tty_receive,
+ .write_wakeup = nci_uart_tty_wakeup,
+ .ioctl = nci_uart_tty_ioctl,
+};
+
+static int __init nci_uart_init(void)
+{
+ memset(nci_uart_drivers, 0, sizeof(nci_uart_drivers));
+ return tty_register_ldisc(N_NCI, &nci_uart_ldisc);
+}
+
+static void __exit nci_uart_exit(void)
+{
+ tty_unregister_ldisc(N_NCI);
+}
+
+module_init(nci_uart_init);
+module_exit(nci_uart_exit);
+
+MODULE_AUTHOR("Marvell International Ltd.");
+MODULE_DESCRIPTION("NFC NCI UART driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_LDISC(N_NCI);
diff --git a/net/nfc/netlink.c b/net/nfc/netlink.c
index 3763036..f85f37e 100644
--- a/net/nfc/netlink.c
+++ b/net/nfc/netlink.c
@@ -5,6 +5,12 @@
* Lauro Ramos Venancio <lauro.venancio@openbossa.org>
* Aloisio Almeida Jr <aloisio.almeida@openbossa.org>
*
+ * Vendor commands implementation based on net/wireless/nl80211.c
+ * which is:
+ *
+ * Copyright 2006-2010 Johannes Berg <johannes@sipsolutions.net>
+ * Copyright 2013-2014 Intel Mobile Communications GmbH
+ *
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
@@ -1489,6 +1495,50 @@ static int nfc_genl_se_io(struct sk_buff *skb, struct genl_info *info)
return nfc_se_io(dev, se_idx, apdu, apdu_len, se_io_cb, ctx);
}
+static int nfc_genl_vendor_cmd(struct sk_buff *skb,
+ struct genl_info *info)
+{
+ struct nfc_dev *dev;
+ struct nfc_vendor_cmd *cmd;
+ u32 dev_idx, vid, subcmd;
+ u8 *data;
+ size_t data_len;
+ int i;
+
+ if (!info->attrs[NFC_ATTR_DEVICE_INDEX] ||
+ !info->attrs[NFC_ATTR_VENDOR_ID] ||
+ !info->attrs[NFC_ATTR_VENDOR_SUBCMD])
+ return -EINVAL;
+
+ dev_idx = nla_get_u32(info->attrs[NFC_ATTR_DEVICE_INDEX]);
+ vid = nla_get_u32(info->attrs[NFC_ATTR_VENDOR_ID]);
+ subcmd = nla_get_u32(info->attrs[NFC_ATTR_VENDOR_SUBCMD]);
+
+ dev = nfc_get_device(dev_idx);
+ if (!dev || !dev->vendor_cmds || !dev->n_vendor_cmds)
+ return -ENODEV;
+
+ data = nla_data(info->attrs[NFC_ATTR_VENDOR_DATA]);
+ if (data) {
+ data_len = nla_len(info->attrs[NFC_ATTR_VENDOR_DATA]);
+ if (data_len == 0)
+ return -EINVAL;
+ } else {
+ data_len = 0;
+ }
+
+ for (i = 0; i < dev->n_vendor_cmds; i++) {
+ cmd = &dev->vendor_cmds[i];
+
+ if (cmd->vendor_id != vid || cmd->subcmd != subcmd)
+ continue;
+
+ return cmd->doit(dev, data, data_len);
+ }
+
+ return -EOPNOTSUPP;
+}
+
static const struct genl_ops nfc_genl_ops[] = {
{
.cmd = NFC_CMD_GET_DEVICE,
@@ -1579,6 +1629,11 @@ static const struct genl_ops nfc_genl_ops[] = {
.doit = nfc_genl_activate_target,
.policy = nfc_genl_policy,
},
+ {
+ .cmd = NFC_CMD_VENDOR,
+ .doit = nfc_genl_vendor_cmd,
+ .policy = nfc_genl_policy,
+ },
};
diff --git a/net/nfc/nfc.h b/net/nfc/nfc.h
index a8ce80b..5c93e84 100644
--- a/net/nfc/nfc.h
+++ b/net/nfc/nfc.h
@@ -30,7 +30,7 @@ struct nfc_protocol {
struct proto *proto;
struct module *owner;
int (*create)(struct net *net, struct socket *sock,
- const struct nfc_protocol *nfc_proto);
+ const struct nfc_protocol *nfc_proto, int kern);
};
struct nfc_rawsock {
diff --git a/net/nfc/rawsock.c b/net/nfc/rawsock.c
index 82b4e80..e9a9148 100644
--- a/net/nfc/rawsock.c
+++ b/net/nfc/rawsock.c
@@ -334,7 +334,7 @@ static void rawsock_destruct(struct sock *sk)
}
static int rawsock_create(struct net *net, struct socket *sock,
- const struct nfc_protocol *nfc_proto)
+ const struct nfc_protocol *nfc_proto, int kern)
{
struct sock *sk;
@@ -348,7 +348,7 @@ static int rawsock_create(struct net *net, struct socket *sock,
else
sock->ops = &rawsock_ops;
- sk = sk_alloc(net, PF_NFC, GFP_ATOMIC, nfc_proto->proto);
+ sk = sk_alloc(net, PF_NFC, GFP_ATOMIC, nfc_proto->proto, kern);
if (!sk)
return -ENOMEM;