compact nfc 3.0.8
authorarron.wang <arron.wang@intel.com>
Thu, 26 Jul 2012 07:27:57 +0000 (15:27 +0800)
committerarron.wang <arron.wang@intel.com>
Thu, 26 Jul 2012 07:27:57 +0000 (15:27 +0800)
45 files changed:
Documentation/nfc/nfc-hci.txt [new file with mode: 0644]
drivers/Makefile
drivers/nfc/Kconfig
drivers/nfc/Makefile
drivers/nfc/nfcwilink.c [new file with mode: 0644]
drivers/nfc/pn533.c [new file with mode: 0644]
drivers/nfc/pn544.c
drivers/nfc/pn544_hci.c [new file with mode: 0644]
include/linux/nfc.h [new file with mode: 0644]
include/linux/nfc/pn544.h
include/linux/socket.h
include/net/nfc/hci.h [new file with mode: 0644]
include/net/nfc/nci.h [new file with mode: 0644]
include/net/nfc/nci_core.h [new file with mode: 0644]
include/net/nfc/nfc.h [new file with mode: 0644]
include/net/nfc/shdlc.h [new file with mode: 0644]
net/Kconfig
net/Makefile
net/core/sock.c
net/nfc/Kconfig [new file with mode: 0644]
net/nfc/Makefile [new file with mode: 0644]
net/nfc/af_nfc.c [new file with mode: 0644]
net/nfc/core.c [new file with mode: 0644]
net/nfc/hci/Kconfig [new file with mode: 0644]
net/nfc/hci/Makefile [new file with mode: 0644]
net/nfc/hci/command.c [new file with mode: 0644]
net/nfc/hci/core.c [new file with mode: 0644]
net/nfc/hci/hci.h [new file with mode: 0644]
net/nfc/hci/hcp.c [new file with mode: 0644]
net/nfc/hci/shdlc.c [new file with mode: 0644]
net/nfc/llcp/Kconfig [new file with mode: 0644]
net/nfc/llcp/commands.c [new file with mode: 0644]
net/nfc/llcp/llcp.c [new file with mode: 0644]
net/nfc/llcp/llcp.h [new file with mode: 0644]
net/nfc/llcp/sock.c [new file with mode: 0644]
net/nfc/nci/Kconfig [new file with mode: 0644]
net/nfc/nci/Makefile [new file with mode: 0644]
net/nfc/nci/core.c [new file with mode: 0644]
net/nfc/nci/data.c [new file with mode: 0644]
net/nfc/nci/lib.c [new file with mode: 0644]
net/nfc/nci/ntf.c [new file with mode: 0644]
net/nfc/nci/rsp.c [new file with mode: 0644]
net/nfc/netlink.c [new file with mode: 0644]
net/nfc/nfc.h [new file with mode: 0644]
net/nfc/rawsock.c [new file with mode: 0644]

diff --git a/Documentation/nfc/nfc-hci.txt b/Documentation/nfc/nfc-hci.txt
new file mode 100644 (file)
index 0000000..89a339c
--- /dev/null
@@ -0,0 +1,213 @@
+HCI backend for NFC Core
+
+Author: Eric Lapuyade, Samuel Ortiz
+Contact: eric.lapuyade@intel.com, samuel.ortiz@intel.com
+
+General
+-------
+
+The HCI layer implements much of the ETSI TS 102 622 V10.2.0 specification. It
+enables easy writing of HCI-based NFC drivers. The HCI layer runs as an NFC Core
+backend, implementing an abstract nfc device and translating NFC Core API
+to HCI commands and events.
+
+HCI
+---
+
+HCI registers as an nfc device with NFC Core. Requests coming from userspace are
+routed through netlink sockets to NFC Core and then to HCI. From this point,
+they are translated in a sequence of HCI commands sent to the HCI layer in the
+host controller (the chip). The sending context blocks while waiting for the
+response to arrive.
+HCI events can also be received from the host controller. They will be handled
+and a translation will be forwarded to NFC Core as needed.
+HCI uses 2 execution contexts:
+- one for executing commands : nfc_hci_msg_tx_work(). Only one command
+can be executing at any given moment.
+- one for dispatching received events and commands : nfc_hci_msg_rx_work().
+
+HCI Session initialization:
+---------------------------
+
+The Session initialization is an HCI standard which must unfortunately
+support proprietary gates. This is the reason why the driver will pass a list
+of proprietary gates that must be part of the session. HCI will ensure all
+those gates have pipes connected when the hci device is set up.
+
+HCI Gates and Pipes
+-------------------
+
+A gate defines the 'port' where some service can be found. In order to access
+a service, one must create a pipe to that gate and open it. In this
+implementation, pipes are totally hidden. The public API only knows gates.
+This is consistent with the driver need to send commands to proprietary gates
+without knowing the pipe connected to it.
+
+Driver interface
+----------------
+
+A driver would normally register itself with HCI and provide the following
+entry points:
+
+struct nfc_hci_ops {
+       int (*open)(struct nfc_hci_dev *hdev);
+       void (*close)(struct nfc_hci_dev *hdev);
+       int (*hci_ready) (struct nfc_hci_dev *hdev);
+       int (*xmit)(struct nfc_hci_dev *hdev, struct sk_buff *skb);
+       int (*start_poll)(struct nfc_hci_dev *hdev, u32 protocols);
+       int (*target_from_gate)(struct nfc_hci_dev *hdev, u8 gate,
+                               struct nfc_target *target);
+       int (*complete_target_discovered) (struct nfc_hci_dev *hdev, u8 gate,
+                                          struct nfc_target *target);
+       int (*data_exchange) (struct nfc_hci_dev *hdev,
+                             struct nfc_target *target,
+                             struct sk_buff *skb, struct sk_buff **res_skb);
+       int (*check_presence)(struct nfc_hci_dev *hdev,
+                             struct nfc_target *target);
+};
+
+- open() and close() shall turn the hardware on and off.
+- hci_ready() is an optional entry point that is called right after the hci
+session has been set up. The driver can use it to do additional initialization
+that must be performed using HCI commands.
+- xmit() shall simply write a frame to the chip.
+- start_poll() is an optional entrypoint that shall set the hardware in polling
+mode. This must be implemented only if the hardware uses proprietary gates or a
+mechanism slightly different from the HCI standard.
+- target_from_gate() is an optional entrypoint to return the nfc protocols
+corresponding to a proprietary gate.
+- complete_target_discovered() is an optional entry point to let the driver
+perform additional proprietary processing necessary to auto activate the
+discovered target.
+- data_exchange() must be implemented by the driver if proprietary HCI commands
+are required to send data to the tag. Some tag types will require custom
+commands, others can be written to using the standard HCI commands. The driver
+can check the tag type and either do proprietary processing, or return 1 to ask
+for standard processing.
+- check_presence() is an optional entry point that will be called regularly
+by the core to check that an activated tag is still in the field. If this is
+not implemented, the core will not be able to push tag_lost events to the user
+space
+
+On the rx path, the driver is responsible to push incoming HCP frames to HCI
+using nfc_hci_recv_frame(). HCI will take care of re-aggregation and handling
+This must be done from a context that can sleep.
+
+SHDLC
+-----
+
+Most chips use shdlc to ensure integrity and delivery ordering of the HCP
+frames between the host controller (the chip) and hosts (entities connected
+to the chip, like the cpu). In order to simplify writing the driver, an shdlc
+layer is available for use by the driver.
+When used, the driver actually registers with shdlc, and shdlc will register
+with HCI. HCI sees shdlc as the driver and thus send its HCP frames
+through shdlc->xmit.
+SHDLC adds a new execution context (nfc_shdlc_sm_work()) to run its state
+machine and handle both its rx and tx path.
+
+Included Drivers
+----------------
+
+An HCI based driver for an NXP PN544, connected through I2C bus, and using
+shdlc is included.
+
+Execution Contexts
+------------------
+
+The execution contexts are the following:
+- IRQ handler (IRQH):
+fast, cannot sleep. stores incoming frames into an shdlc rx queue
+
+- SHDLC State Machine worker (SMW)
+handles shdlc rx & tx queues. Dispatches HCI cmd responses.
+
+- HCI Tx Cmd worker (MSGTXWQ)
+Serializes execution of HCI commands. Completes execution in case of response
+timeout.
+
+- HCI Rx worker (MSGRXWQ)
+Dispatches incoming HCI commands or events.
+
+- Syscall context from a userspace call (SYSCALL)
+Any entrypoint in HCI called from NFC Core
+
+Workflow executing an HCI command (using shdlc)
+-----------------------------------------------
+
+Executing an HCI command can easily be performed synchronously using the
+following API:
+
+int nfc_hci_send_cmd (struct nfc_hci_dev *hdev, u8 gate, u8 cmd,
+                       const u8 *param, size_t param_len, struct sk_buff **skb)
+
+The API must be invoked from a context that can sleep. Most of the time, this
+will be the syscall context. skb will return the result that was received in
+the response.
+
+Internally, execution is asynchronous. So all this API does is to enqueue the
+HCI command, setup a local wait queue on stack, and wait_event() for completion.
+The wait is not interruptible because it is guaranteed that the command will
+complete after some short timeout anyway.
+
+MSGTXWQ context will then be scheduled and invoke nfc_hci_msg_tx_work().
+This function will dequeue the next pending command and send its HCP fragments
+to the lower layer which happens to be shdlc. It will then start a timer to be
+able to complete the command with a timeout error if no response arrive.
+
+SMW context gets scheduled and invokes nfc_shdlc_sm_work(). This function
+handles shdlc framing in and out. It uses the driver xmit to send frames and
+receives incoming frames in an skb queue filled from the driver IRQ handler.
+SHDLC I(nformation) frames payload are HCP fragments. They are aggregated to
+form complete HCI frames, which can be a response, command, or event.
+
+HCI Responses are dispatched immediately from this context to unblock
+waiting command execution. Response processing involves invoking the completion
+callback that was provided by nfc_hci_msg_tx_work() when it sent the command.
+The completion callback will then wake the syscall context.
+
+Workflow receiving an HCI event or command
+------------------------------------------
+
+HCI commands or events are not dispatched from SMW context. Instead, they are
+queued to HCI rx_queue and will be dispatched from HCI rx worker
+context (MSGRXWQ). This is done this way to allow a cmd or event handler
+to also execute other commands (for example, handling the
+NFC_HCI_EVT_TARGET_DISCOVERED event from PN544 requires to issue an
+ANY_GET_PARAMETER to the reader A gate to get information on the target
+that was discovered).
+
+Typically, such an event will be propagated to NFC Core from MSGRXWQ context.
+
+Error management
+----------------
+
+Errors that occur synchronously with the execution of an NFC Core request are
+simply returned as the execution result of the request. These are easy.
+
+Errors that occur asynchronously (e.g. in a background protocol handling thread)
+must be reported such that upper layers don't stay ignorant that something
+went wrong below and know that expected events will probably never happen.
+Handling of these errors is done as follows:
+
+- driver (pn544) fails to deliver an incoming frame: it stores the error such
+that any subsequent call to the driver will result in this error. Then it calls
+the standard nfc_shdlc_recv_frame() with a NULL argument to report the problem
+above. shdlc stores a EREMOTEIO sticky status, which will trigger SMW to
+report above in turn.
+
+- SMW is basically a background thread to handle incoming and outgoing shdlc
+frames. This thread will also check the shdlc sticky status and report to HCI
+when it discovers it is not able to run anymore because of an unrecoverable
+error that happened within shdlc or below. If the problem occurs during shdlc
+connection, the error is reported through the connect completion.
+
+- HCI: if an internal HCI error happens (frame is lost), or HCI is reported an
+error from a lower layer, HCI will either complete the currently executing
+command with that error, or notify NFC Core directly if no command is executing.
+
+- NFC Core: when NFC Core is notified of an error from below and polling is
+active, it will send a tag discovered event with an empty tag list to the user
+space to let it know that the poll operation will never be able to detect a tag.
+If polling is not active and the error was sticky, lower levels will return it
+at next invocation.
index 443d498..e9c0536 100644 (file)
@@ -125,3 +125,4 @@ obj-y                               += ieee802154/
 obj-y                          += clk/
 
 obj-$(CONFIG_HWSPINLOCK)       += hwspinlock/
+obj-$(CONFIG_NFC)              += nfc/
index ea15800..3b20b73 100644 (file)
@@ -2,17 +2,8 @@
 # Near Field Communication (NFC) devices
 #
 
-menuconfig NFC_DEVICES
-       bool "Near Field Communication (NFC) devices"
-       default n
-       ---help---
-         You'll have to say Y if your computer contains an NFC device that
-         you want to use under Linux.
-
-         You can say N here if you don't have any Near Field Communication
-         devices connected to your computer.
-
-if NFC_DEVICES
+menu "Near Field Communication (NFC) devices"
+       depends on NFC
 
 config PN544_NFC
        tristate "PN544 NFC driver"
@@ -26,5 +17,38 @@ config PN544_NFC
          To compile this driver as a module, choose m here. The module will
          be called pn544.
 
+config PN544_HCI_NFC
+       tristate "HCI PN544 NFC driver"
+       depends on I2C && NFC_SHDLC
+       select CRC_CCITT
+       default n
+       ---help---
+         NXP PN544 i2c driver.
+         This is a driver based on the SHDLC and HCI NFC kernel layers and
+         will thus not work with NXP libnfc library.
+
+         To compile this driver as a module, choose m here. The module will
+         be called pn544_hci.
+
+config NFC_PN533
+       tristate "NXP PN533 USB driver"
+       depends on USB
+       help
+         NXP PN533 USB driver.
+         This driver provides support for NFC NXP PN533 devices.
+
+         Say Y here to compile support for PN533 devices into the
+         kernel or say M to compile it as module (pn533).
+
+config NFC_WILINK
+       tristate "Texas Instruments NFC WiLink driver"
+       depends on TI_ST && NFC_NCI
+       help
+         This enables the NFC driver for Texas Instrument's BT/FM/GPS/NFC
+         combo devices. This makes use of shared transport line discipline
+         core driver to communicate with the NFC core of the combo chip.
+
+         Say Y here to compile support for Texas Instrument's NFC WiLink driver
+         into the kernel or say M to compile it as module.
 
-endif # NFC_DEVICES
+endmenu
index a4efb16..473e44c 100644 (file)
@@ -3,3 +3,8 @@
 #
 
 obj-$(CONFIG_PN544_NFC)                += pn544.o
+obj-$(CONFIG_PN544_HCI_NFC)    += pn544_hci.o
+obj-$(CONFIG_NFC_PN533)                += pn533.o
+obj-$(CONFIG_NFC_WILINK)       += nfcwilink.o
+
+ccflags-$(CONFIG_NFC_DEBUG) := -DDEBUG
diff --git a/drivers/nfc/nfcwilink.c b/drivers/nfc/nfcwilink.c
new file mode 100644 (file)
index 0000000..e7fd493
--- /dev/null
@@ -0,0 +1,627 @@
+/*
+ *  Texas Instrument's NFC Driver For Shared Transport.
+ *
+ *  NFC Driver acts as interface between NCI core and
+ *  TI Shared Transport Layer.
+ *
+ *  Copyright (C) 2011 Texas Instruments, Inc.
+ *
+ *  Written by Ilan Elias <ilane@ti.com>
+ *
+ *  Acknowledgements:
+ *  This file is based on btwilink.c, which was written
+ *  by Raja Mani and Pavan Savoy.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2 as
+ *  published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+#include <linux/platform_device.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/firmware.h>
+#include <linux/nfc.h>
+#include <net/nfc/nci.h>
+#include <net/nfc/nci_core.h>
+#include <linux/ti_wilink_st.h>
+
+#define NFCWILINK_CHNL                 12
+#define NFCWILINK_OPCODE               7
+#define NFCWILINK_MAX_FRAME_SIZE       300
+#define NFCWILINK_HDR_LEN              4
+#define NFCWILINK_OFFSET_LEN_IN_HDR    1
+#define NFCWILINK_LEN_SIZE             2
+#define NFCWILINK_REGISTER_TIMEOUT     8000    /* 8 sec */
+#define NFCWILINK_CMD_TIMEOUT          5000    /* 5 sec */
+
+#define BTS_FILE_NAME_MAX_SIZE         40
+#define BTS_FILE_HDR_MAGIC             0x42535442
+#define BTS_FILE_CMD_MAX_LEN           0xff
+#define BTS_FILE_ACTION_TYPE_SEND_CMD  1
+
+#define NCI_VS_NFCC_INFO_CMD_GID       0x2f
+#define NCI_VS_NFCC_INFO_CMD_OID       0x12
+#define NCI_VS_NFCC_INFO_RSP_GID       0x4f
+#define NCI_VS_NFCC_INFO_RSP_OID       0x12
+
+struct nfcwilink_hdr {
+       __u8 chnl;
+       __u8 opcode;
+       __le16 len;
+} __packed;
+
+struct nci_vs_nfcc_info_cmd {
+       __u8 gid;
+       __u8 oid;
+       __u8 plen;
+} __packed;
+
+struct nci_vs_nfcc_info_rsp {
+       __u8 gid;
+       __u8 oid;
+       __u8 plen;
+       __u8 status;
+       __u8 hw_id;
+       __u8 sw_ver_x;
+       __u8 sw_ver_z;
+       __u8 patch_id;
+} __packed;
+
+struct bts_file_hdr {
+       __le32 magic;
+       __le32 ver;
+       __u8 rfu[24];
+       __u8 actions[0];
+} __packed;
+
+struct bts_file_action {
+       __le16 type;
+       __le16 len;
+       __u8 data[0];
+} __packed;
+
+struct nfcwilink {
+       struct platform_device          *pdev;
+       struct nci_dev                  *ndev;
+       unsigned long                   flags;
+
+       char                            st_register_cb_status;
+       long                            (*st_write) (struct sk_buff *);
+
+       struct completion               completed;
+
+       struct nci_vs_nfcc_info_rsp     nfcc_info;
+};
+
+/* NFCWILINK driver flags */
+enum {
+       NFCWILINK_RUNNING,
+       NFCWILINK_FW_DOWNLOAD,
+};
+
+static int nfcwilink_send(struct sk_buff *skb);
+
+static inline struct sk_buff *nfcwilink_skb_alloc(unsigned int len, gfp_t how)
+{
+       struct sk_buff *skb;
+
+       skb = alloc_skb(len + NFCWILINK_HDR_LEN, how);
+       if (skb)
+               skb_reserve(skb, NFCWILINK_HDR_LEN);
+
+       return skb;
+}
+
+static void nfcwilink_fw_download_receive(struct nfcwilink *drv,
+                                               struct sk_buff *skb)
+{
+       struct nci_vs_nfcc_info_rsp *rsp = (void *)skb->data;
+
+       /* Detect NCI_VS_NFCC_INFO_RSP and store the result */
+       if ((skb->len > 3) && (rsp->gid == NCI_VS_NFCC_INFO_RSP_GID) &&
+               (rsp->oid == NCI_VS_NFCC_INFO_RSP_OID)) {
+               memcpy(&drv->nfcc_info, rsp,
+                       sizeof(struct nci_vs_nfcc_info_rsp));
+       }
+
+       kfree_skb(skb);
+
+       complete(&drv->completed);
+}
+
+static int nfcwilink_get_bts_file_name(struct nfcwilink *drv, char *file_name)
+{
+       struct nci_vs_nfcc_info_cmd *cmd;
+       struct sk_buff *skb;
+       unsigned long comp_ret;
+       int rc;
+
+       nfc_dev_dbg(&drv->pdev->dev, "get_bts_file_name entry");
+
+       skb = nfcwilink_skb_alloc(sizeof(struct nci_vs_nfcc_info_cmd),
+                                       GFP_KERNEL);
+       if (!skb) {
+               nfc_dev_err(&drv->pdev->dev,
+                               "no memory for nci_vs_nfcc_info_cmd");
+               return -ENOMEM;
+       }
+
+       skb->dev = (void *)drv->ndev;
+
+       cmd = (struct nci_vs_nfcc_info_cmd *)
+                       skb_put(skb, sizeof(struct nci_vs_nfcc_info_cmd));
+       cmd->gid = NCI_VS_NFCC_INFO_CMD_GID;
+       cmd->oid = NCI_VS_NFCC_INFO_CMD_OID;
+       cmd->plen = 0;
+
+       drv->nfcc_info.plen = 0;
+
+       rc = nfcwilink_send(skb);
+       if (rc)
+               return rc;
+
+       comp_ret = wait_for_completion_timeout(&drv->completed,
+                               msecs_to_jiffies(NFCWILINK_CMD_TIMEOUT));
+       nfc_dev_dbg(&drv->pdev->dev, "wait_for_completion_timeout returned %ld",
+                       comp_ret);
+       if (comp_ret == 0) {
+               nfc_dev_err(&drv->pdev->dev,
+                               "timeout on wait_for_completion_timeout");
+               return -ETIMEDOUT;
+       }
+
+       nfc_dev_dbg(&drv->pdev->dev, "nci_vs_nfcc_info_rsp: plen %d, status %d",
+                       drv->nfcc_info.plen,
+                       drv->nfcc_info.status);
+
+       if ((drv->nfcc_info.plen != 5) || (drv->nfcc_info.status != 0)) {
+               nfc_dev_err(&drv->pdev->dev,
+                               "invalid nci_vs_nfcc_info_rsp");
+               return -EINVAL;
+       }
+
+       snprintf(file_name, BTS_FILE_NAME_MAX_SIZE,
+                       "TINfcInit_%d.%d.%d.%d.bts",
+                       drv->nfcc_info.hw_id,
+                       drv->nfcc_info.sw_ver_x,
+                       drv->nfcc_info.sw_ver_z,
+                       drv->nfcc_info.patch_id);
+
+       nfc_dev_info(&drv->pdev->dev, "nfcwilink FW file name: %s", file_name);
+
+       return 0;
+}
+
+static int nfcwilink_send_bts_cmd(struct nfcwilink *drv, __u8 *data, int len)
+{
+       struct nfcwilink_hdr *hdr = (struct nfcwilink_hdr *)data;
+       struct sk_buff *skb;
+       unsigned long comp_ret;
+       int rc;
+
+       nfc_dev_dbg(&drv->pdev->dev, "send_bts_cmd entry");
+
+       /* verify valid cmd for the NFC channel */
+       if ((len <= sizeof(struct nfcwilink_hdr)) ||
+               (len > BTS_FILE_CMD_MAX_LEN) ||
+               (hdr->chnl != NFCWILINK_CHNL) ||
+               (hdr->opcode != NFCWILINK_OPCODE)) {
+               nfc_dev_err(&drv->pdev->dev,
+                       "ignoring invalid bts cmd, len %d, chnl %d, opcode %d",
+                       len, hdr->chnl, hdr->opcode);
+               return 0;
+       }
+
+       /* remove the ST header */
+       len -= sizeof(struct nfcwilink_hdr);
+       data += sizeof(struct nfcwilink_hdr);
+
+       skb = nfcwilink_skb_alloc(len, GFP_KERNEL);
+       if (!skb) {
+               nfc_dev_err(&drv->pdev->dev, "no memory for bts cmd");
+               return -ENOMEM;
+       }
+
+       skb->dev = (void *)drv->ndev;
+
+       memcpy(skb_put(skb, len), data, len);
+
+       rc = nfcwilink_send(skb);
+       if (rc)
+               return rc;
+
+       comp_ret = wait_for_completion_timeout(&drv->completed,
+                               msecs_to_jiffies(NFCWILINK_CMD_TIMEOUT));
+       nfc_dev_dbg(&drv->pdev->dev, "wait_for_completion_timeout returned %ld",
+                       comp_ret);
+       if (comp_ret == 0) {
+               nfc_dev_err(&drv->pdev->dev,
+                               "timeout on wait_for_completion_timeout");
+               return -ETIMEDOUT;
+       }
+
+       return 0;
+}
+
+static int nfcwilink_download_fw(struct nfcwilink *drv)
+{
+       unsigned char file_name[BTS_FILE_NAME_MAX_SIZE];
+       const struct firmware *fw;
+       __u16 action_type, action_len;
+       __u8 *ptr;
+       int len, rc;
+
+       nfc_dev_dbg(&drv->pdev->dev, "download_fw entry");
+
+       set_bit(NFCWILINK_FW_DOWNLOAD, &drv->flags);
+
+       rc = nfcwilink_get_bts_file_name(drv, file_name);
+       if (rc)
+               goto exit;
+
+       rc = request_firmware(&fw, file_name, &drv->pdev->dev);
+       if (rc) {
+               nfc_dev_err(&drv->pdev->dev, "request_firmware failed %d", rc);
+
+               /* if the file is not found, don't exit with failure */
+               if (rc == -ENOENT)
+                       rc = 0;
+
+               goto exit;
+       }
+
+       len = fw->size;
+       ptr = (__u8 *)fw->data;
+
+       if ((len == 0) || (ptr == NULL)) {
+               nfc_dev_dbg(&drv->pdev->dev,
+                               "request_firmware returned size %d", len);
+               goto release_fw;
+       }
+
+       if (__le32_to_cpu(((struct bts_file_hdr *)ptr)->magic) !=
+                       BTS_FILE_HDR_MAGIC) {
+               nfc_dev_err(&drv->pdev->dev, "wrong bts magic number");
+               rc = -EINVAL;
+               goto release_fw;
+       }
+
+       /* remove the BTS header */
+       len -= sizeof(struct bts_file_hdr);
+       ptr += sizeof(struct bts_file_hdr);
+
+       while (len > 0) {
+               action_type =
+                       __le16_to_cpu(((struct bts_file_action *)ptr)->type);
+               action_len =
+                       __le16_to_cpu(((struct bts_file_action *)ptr)->len);
+
+               nfc_dev_dbg(&drv->pdev->dev, "bts_file_action type %d, len %d",
+                               action_type, action_len);
+
+               switch (action_type) {
+               case BTS_FILE_ACTION_TYPE_SEND_CMD:
+                       rc = nfcwilink_send_bts_cmd(drv,
+                                       ((struct bts_file_action *)ptr)->data,
+                                       action_len);
+                       if (rc)
+                               goto release_fw;
+                       break;
+               }
+
+               /* advance to the next action */
+               len -= (sizeof(struct bts_file_action) + action_len);
+               ptr += (sizeof(struct bts_file_action) + action_len);
+       }
+
+release_fw:
+       release_firmware(fw);
+
+exit:
+       clear_bit(NFCWILINK_FW_DOWNLOAD, &drv->flags);
+       return rc;
+}
+
+/* Called by ST when registration is complete */
+static void nfcwilink_register_complete(void *priv_data, char data)
+{
+       struct nfcwilink *drv = priv_data;
+
+       nfc_dev_dbg(&drv->pdev->dev, "register_complete entry");
+
+       /* store ST registration status */
+       drv->st_register_cb_status = data;
+
+       /* complete the wait in nfc_st_open() */
+       complete(&drv->completed);
+}
+
+/* Called by ST when receive data is available */
+static long nfcwilink_receive(void *priv_data, struct sk_buff *skb)
+{
+       struct nfcwilink *drv = priv_data;
+       int rc;
+
+       nfc_dev_dbg(&drv->pdev->dev, "receive entry, len %d", skb->len);
+
+       if (!skb)
+               return -EFAULT;
+
+       if (!drv) {
+               kfree_skb(skb);
+               return -EFAULT;
+       }
+
+       /* strip the ST header
+       (apart for the chnl byte, which is not received in the hdr) */
+       skb_pull(skb, (NFCWILINK_HDR_LEN-1));
+
+       if (test_bit(NFCWILINK_FW_DOWNLOAD, &drv->flags)) {
+               nfcwilink_fw_download_receive(drv, skb);
+               return 0;
+       }
+
+       skb->dev = (void *) drv->ndev;
+
+       /* Forward skb to NCI core layer */
+       rc = nci_recv_frame(skb);
+       if (rc < 0) {
+               nfc_dev_err(&drv->pdev->dev, "nci_recv_frame failed %d", rc);
+               return rc;
+       }
+
+       return 0;
+}
+
+/* protocol structure registered with ST */
+static struct st_proto_s nfcwilink_proto = {
+       .chnl_id = NFCWILINK_CHNL,
+       .max_frame_size = NFCWILINK_MAX_FRAME_SIZE,
+       .hdr_len = (NFCWILINK_HDR_LEN-1),       /* not including chnl byte */
+       .offset_len_in_hdr = NFCWILINK_OFFSET_LEN_IN_HDR,
+       .len_size = NFCWILINK_LEN_SIZE,
+       .reserve = 0,
+       .recv = nfcwilink_receive,
+       .reg_complete_cb = nfcwilink_register_complete,
+       .write = NULL,
+};
+
+static int nfcwilink_open(struct nci_dev *ndev)
+{
+       struct nfcwilink *drv = nci_get_drvdata(ndev);
+       unsigned long comp_ret;
+       int rc;
+
+       nfc_dev_dbg(&drv->pdev->dev, "open entry");
+
+       if (test_and_set_bit(NFCWILINK_RUNNING, &drv->flags)) {
+               rc = -EBUSY;
+               goto exit;
+       }
+
+       nfcwilink_proto.priv_data = drv;
+
+       init_completion(&drv->completed);
+       drv->st_register_cb_status = -EINPROGRESS;
+
+       rc = st_register(&nfcwilink_proto);
+       if (rc < 0) {
+               if (rc == -EINPROGRESS) {
+                       comp_ret = wait_for_completion_timeout(
+                       &drv->completed,
+                       msecs_to_jiffies(NFCWILINK_REGISTER_TIMEOUT));
+
+                       nfc_dev_dbg(&drv->pdev->dev,
+                       "wait_for_completion_timeout returned %ld",
+                       comp_ret);
+
+                       if (comp_ret == 0) {
+                               /* timeout */
+                               rc = -ETIMEDOUT;
+                               goto clear_exit;
+                       } else if (drv->st_register_cb_status != 0) {
+                               rc = drv->st_register_cb_status;
+                               nfc_dev_err(&drv->pdev->dev,
+                               "st_register_cb failed %d", rc);
+                               goto clear_exit;
+                       }
+               } else {
+                       nfc_dev_err(&drv->pdev->dev,
+                               "st_register failed %d", rc);
+                       goto clear_exit;
+               }
+       }
+
+       /* st_register MUST fill the write callback */
+       BUG_ON(nfcwilink_proto.write == NULL);
+       drv->st_write = nfcwilink_proto.write;
+
+       if (nfcwilink_download_fw(drv)) {
+               nfc_dev_err(&drv->pdev->dev, "nfcwilink_download_fw failed %d",
+                               rc);
+               /* open should succeed, even if the FW download failed */
+       }
+
+       goto exit;
+
+clear_exit:
+       clear_bit(NFCWILINK_RUNNING, &drv->flags);
+
+exit:
+       return rc;
+}
+
+static int nfcwilink_close(struct nci_dev *ndev)
+{
+       struct nfcwilink *drv = nci_get_drvdata(ndev);
+       int rc;
+
+       nfc_dev_dbg(&drv->pdev->dev, "close entry");
+
+       if (!test_and_clear_bit(NFCWILINK_RUNNING, &drv->flags))
+               return 0;
+
+       rc = st_unregister(&nfcwilink_proto);
+       if (rc)
+               nfc_dev_err(&drv->pdev->dev, "st_unregister failed %d", rc);
+
+       drv->st_write = NULL;
+
+       return rc;
+}
+
+static int nfcwilink_send(struct sk_buff *skb)
+{
+       struct nci_dev *ndev = (struct nci_dev *)skb->dev;
+       struct nfcwilink *drv = nci_get_drvdata(ndev);
+       struct nfcwilink_hdr hdr = {NFCWILINK_CHNL, NFCWILINK_OPCODE, 0x0000};
+       long len;
+
+       nfc_dev_dbg(&drv->pdev->dev, "send entry, len %d", skb->len);
+
+       if (!test_bit(NFCWILINK_RUNNING, &drv->flags)) {
+               kfree_skb(skb);
+               return -EINVAL;
+       }
+
+       /* add the ST hdr to the start of the buffer */
+       hdr.len = cpu_to_le16(skb->len);
+       memcpy(skb_push(skb, NFCWILINK_HDR_LEN), &hdr, NFCWILINK_HDR_LEN);
+
+       /* Insert skb to shared transport layer's transmit queue.
+        * Freeing skb memory is taken care in shared transport layer,
+        * so don't free skb memory here.
+        */
+       len = drv->st_write(skb);
+       if (len < 0) {
+               kfree_skb(skb);
+               nfc_dev_err(&drv->pdev->dev, "st_write failed %ld", len);
+               return -EFAULT;
+       }
+
+       return 0;
+}
+
+static struct nci_ops nfcwilink_ops = {
+       .open = nfcwilink_open,
+       .close = nfcwilink_close,
+       .send = nfcwilink_send,
+};
+
+static int nfcwilink_probe(struct platform_device *pdev)
+{
+       static struct nfcwilink *drv;
+       int rc;
+       __u32 protocols;
+
+       nfc_dev_dbg(&pdev->dev, "probe entry");
+
+       drv = kzalloc(sizeof(struct nfcwilink), GFP_KERNEL);
+       if (!drv) {
+               rc = -ENOMEM;
+               goto exit;
+       }
+
+       drv->pdev = pdev;
+
+       protocols = NFC_PROTO_JEWEL_MASK
+               | NFC_PROTO_MIFARE_MASK | NFC_PROTO_FELICA_MASK
+               | NFC_PROTO_ISO14443_MASK
+               | NFC_PROTO_ISO14443_B_MASK
+               | NFC_PROTO_NFC_DEP_MASK;
+
+       drv->ndev = nci_allocate_device(&nfcwilink_ops,
+                                       protocols,
+                                       NFCWILINK_HDR_LEN,
+                                       0);
+       if (!drv->ndev) {
+               nfc_dev_err(&pdev->dev, "nci_allocate_device failed");
+               rc = -ENOMEM;
+               goto free_exit;
+       }
+
+       nci_set_parent_dev(drv->ndev, &pdev->dev);
+       nci_set_drvdata(drv->ndev, drv);
+
+       rc = nci_register_device(drv->ndev);
+       if (rc < 0) {
+               nfc_dev_err(&pdev->dev, "nci_register_device failed %d", rc);
+               goto free_dev_exit;
+       }
+
+       dev_set_drvdata(&pdev->dev, drv);
+
+       goto exit;
+
+free_dev_exit:
+       nci_free_device(drv->ndev);
+
+free_exit:
+       kfree(drv);
+
+exit:
+       return rc;
+}
+
+static int nfcwilink_remove(struct platform_device *pdev)
+{
+       struct nfcwilink *drv = dev_get_drvdata(&pdev->dev);
+       struct nci_dev *ndev;
+
+       nfc_dev_dbg(&pdev->dev, "remove entry");
+
+       if (!drv)
+               return -EFAULT;
+
+       ndev = drv->ndev;
+
+       nci_unregister_device(ndev);
+       nci_free_device(ndev);
+
+       kfree(drv);
+
+       dev_set_drvdata(&pdev->dev, NULL);
+
+       return 0;
+}
+
+static struct platform_driver nfcwilink_driver = {
+       .probe = nfcwilink_probe,
+       .remove = nfcwilink_remove,
+       .driver = {
+               .name = "nfcwilink",
+               .owner = THIS_MODULE,
+       },
+};
+
+/* ------- Module Init/Exit interfaces ------ */
+static int __init nfcwilink_init(void)
+{
+       printk(KERN_INFO "NFC Driver for TI WiLink");
+
+       return platform_driver_register(&nfcwilink_driver);
+}
+
+static void __exit nfcwilink_exit(void)
+{
+       platform_driver_unregister(&nfcwilink_driver);
+}
+
+module_init(nfcwilink_init);
+module_exit(nfcwilink_exit);
+
+/* ------ Module Info ------ */
+
+MODULE_AUTHOR("Ilan Elias <ilane@ti.com>");
+MODULE_DESCRIPTION("NFC Driver for TI Shared Transport");
+MODULE_LICENSE("GPL");
diff --git a/drivers/nfc/pn533.c b/drivers/nfc/pn533.c
new file mode 100644 (file)
index 0000000..d606f52
--- /dev/null
@@ -0,0 +1,2458 @@
+/*
+ * Copyright (C) 2011 Instituto Nokia de Tecnologia
+ *
+ * Authors:
+ *    Lauro Ramos Venancio <lauro.venancio@openbossa.org>
+ *    Aloisio Almeida Jr <aloisio.almeida@openbossa.org>
+ *
+ * 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
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/nfc.h>
+#include <linux/netdevice.h>
+#include <net/nfc/nfc.h>
+
+#define VERSION "0.1"
+
+#define PN533_VENDOR_ID 0x4CC
+#define PN533_PRODUCT_ID 0x2533
+
+#define SCM_VENDOR_ID 0x4E6
+#define SCL3711_PRODUCT_ID 0x5591
+
+#define SONY_VENDOR_ID         0x054c
+#define PASORI_PRODUCT_ID      0x02e1
+
+#define PN533_QUIRKS_TYPE_A          BIT(0)
+#define PN533_QUIRKS_TYPE_F          BIT(1)
+#define PN533_QUIRKS_DEP             BIT(2)
+#define PN533_QUIRKS_RAW_EXCHANGE    BIT(3)
+
+#define PN533_DEVICE_STD    0x1
+#define PN533_DEVICE_PASORI 0x2
+
+#define PN533_ALL_PROTOCOLS (NFC_PROTO_JEWEL_MASK | NFC_PROTO_MIFARE_MASK |\
+                            NFC_PROTO_FELICA_MASK | NFC_PROTO_ISO14443_MASK |\
+                            NFC_PROTO_NFC_DEP_MASK |\
+                            NFC_PROTO_ISO14443_B_MASK)
+
+#define PN533_NO_TYPE_B_PROTOCOLS (NFC_PROTO_JEWEL_MASK | \
+                                  NFC_PROTO_MIFARE_MASK | \
+                                  NFC_PROTO_FELICA_MASK | \
+                                  NFC_PROTO_ISO14443_MASK | \
+                                  NFC_PROTO_NFC_DEP_MASK)
+
+static const struct usb_device_id pn533_table[] = {
+       { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE,
+         .idVendor             = PN533_VENDOR_ID,
+         .idProduct            = PN533_PRODUCT_ID,
+         .driver_info          = PN533_DEVICE_STD,
+       },
+       { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE,
+         .idVendor             = SCM_VENDOR_ID,
+         .idProduct            = SCL3711_PRODUCT_ID,
+         .driver_info          = PN533_DEVICE_STD,
+       },
+       { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE,
+         .idVendor             = SONY_VENDOR_ID,
+         .idProduct            = PASORI_PRODUCT_ID,
+         .driver_info          = PN533_DEVICE_PASORI,
+       },
+       { }
+};
+MODULE_DEVICE_TABLE(usb, pn533_table);
+
+/* How much time we spend listening for initiators */
+#define PN533_LISTEN_TIME 2
+
+/* frame definitions */
+#define PN533_FRAME_TAIL_SIZE 2
+#define PN533_FRAME_SIZE(f) (sizeof(struct pn533_frame) + f->datalen + \
+                               PN533_FRAME_TAIL_SIZE)
+#define PN533_FRAME_ACK_SIZE (sizeof(struct pn533_frame) + 1)
+#define PN533_FRAME_CHECKSUM(f) (f->data[f->datalen])
+#define PN533_FRAME_POSTAMBLE(f) (f->data[f->datalen + 1])
+
+/* start of frame */
+#define PN533_SOF 0x00FF
+
+/* frame identifier: in/out/error */
+#define PN533_FRAME_IDENTIFIER(f) (f->data[0])
+#define PN533_DIR_OUT 0xD4
+#define PN533_DIR_IN 0xD5
+
+/* PN533 Commands */
+#define PN533_FRAME_CMD(f) (f->data[1])
+#define PN533_FRAME_CMD_PARAMS_PTR(f) (&f->data[2])
+#define PN533_FRAME_CMD_PARAMS_LEN(f) (f->datalen - 2)
+
+#define PN533_CMD_GET_FIRMWARE_VERSION 0x02
+#define PN533_CMD_RF_CONFIGURATION 0x32
+#define PN533_CMD_IN_DATA_EXCHANGE 0x40
+#define PN533_CMD_IN_COMM_THRU     0x42
+#define PN533_CMD_IN_LIST_PASSIVE_TARGET 0x4A
+#define PN533_CMD_IN_ATR 0x50
+#define PN533_CMD_IN_RELEASE 0x52
+#define PN533_CMD_IN_JUMP_FOR_DEP 0x56
+
+#define PN533_CMD_TG_INIT_AS_TARGET 0x8c
+#define PN533_CMD_TG_GET_DATA 0x86
+#define PN533_CMD_TG_SET_DATA 0x8e
+
+#define PN533_CMD_RESPONSE(cmd) (cmd + 1)
+
+/* PN533 Return codes */
+#define PN533_CMD_RET_MASK 0x3F
+#define PN533_CMD_MI_MASK 0x40
+#define PN533_CMD_RET_SUCCESS 0x00
+
+/* PN533 status codes */
+#define PN533_STATUS_TARGET_RELEASED 0x29
+
+struct pn533;
+
+typedef int (*pn533_cmd_complete_t) (struct pn533 *dev, void *arg,
+                                       u8 *params, int params_len);
+
+/* structs for pn533 commands */
+
+/* PN533_CMD_GET_FIRMWARE_VERSION */
+struct pn533_fw_version {
+       u8 ic;
+       u8 ver;
+       u8 rev;
+       u8 support;
+};
+
+/* PN533_CMD_RF_CONFIGURATION */
+#define PN533_CFGITEM_TIMING 0x02
+#define PN533_CFGITEM_MAX_RETRIES 0x05
+#define PN533_CFGITEM_PASORI 0x82
+
+#define PN533_CONFIG_TIMING_102 0xb
+#define PN533_CONFIG_TIMING_204 0xc
+#define PN533_CONFIG_TIMING_409 0xd
+#define PN533_CONFIG_TIMING_819 0xe
+
+#define PN533_CONFIG_MAX_RETRIES_NO_RETRY 0x00
+#define PN533_CONFIG_MAX_RETRIES_ENDLESS 0xFF
+
+struct pn533_config_max_retries {
+       u8 mx_rty_atr;
+       u8 mx_rty_psl;
+       u8 mx_rty_passive_act;
+} __packed;
+
+struct pn533_config_timing {
+       u8 rfu;
+       u8 atr_res_timeout;
+       u8 dep_timeout;
+} __packed;
+
+/* PN533_CMD_IN_LIST_PASSIVE_TARGET */
+
+/* felica commands opcode */
+#define PN533_FELICA_OPC_SENSF_REQ 0
+#define PN533_FELICA_OPC_SENSF_RES 1
+/* felica SENSF_REQ parameters */
+#define PN533_FELICA_SENSF_SC_ALL 0xFFFF
+#define PN533_FELICA_SENSF_RC_NO_SYSTEM_CODE 0
+#define PN533_FELICA_SENSF_RC_SYSTEM_CODE 1
+#define PN533_FELICA_SENSF_RC_ADVANCED_PROTOCOL 2
+
+/* type B initiator_data values */
+#define PN533_TYPE_B_AFI_ALL_FAMILIES 0
+#define PN533_TYPE_B_POLL_METHOD_TIMESLOT 0
+#define PN533_TYPE_B_POLL_METHOD_PROBABILISTIC 1
+
+union pn533_cmd_poll_initdata {
+       struct {
+               u8 afi;
+               u8 polling_method;
+       } __packed type_b;
+       struct {
+               u8 opcode;
+               __be16 sc;
+               u8 rc;
+               u8 tsn;
+       } __packed felica;
+};
+
+/* Poll modulations */
+enum {
+       PN533_POLL_MOD_106KBPS_A,
+       PN533_POLL_MOD_212KBPS_FELICA,
+       PN533_POLL_MOD_424KBPS_FELICA,
+       PN533_POLL_MOD_106KBPS_JEWEL,
+       PN533_POLL_MOD_847KBPS_B,
+       PN533_LISTEN_MOD,
+
+       __PN533_POLL_MOD_AFTER_LAST,
+};
+#define PN533_POLL_MOD_MAX (__PN533_POLL_MOD_AFTER_LAST - 1)
+
+struct pn533_poll_modulations {
+       struct {
+               u8 maxtg;
+               u8 brty;
+               union pn533_cmd_poll_initdata initiator_data;
+       } __packed data;
+       u8 len;
+};
+
+const struct pn533_poll_modulations poll_mod[] = {
+       [PN533_POLL_MOD_106KBPS_A] = {
+               .data = {
+                       .maxtg = 1,
+                       .brty = 0,
+               },
+               .len = 2,
+       },
+       [PN533_POLL_MOD_212KBPS_FELICA] = {
+               .data = {
+                       .maxtg = 1,
+                       .brty = 1,
+                       .initiator_data.felica = {
+                               .opcode = PN533_FELICA_OPC_SENSF_REQ,
+                               .sc = PN533_FELICA_SENSF_SC_ALL,
+                               .rc = PN533_FELICA_SENSF_RC_NO_SYSTEM_CODE,
+                               .tsn = 0,
+                       },
+               },
+               .len = 7,
+       },
+       [PN533_POLL_MOD_424KBPS_FELICA] = {
+               .data = {
+                       .maxtg = 1,
+                       .brty = 2,
+                       .initiator_data.felica = {
+                               .opcode = PN533_FELICA_OPC_SENSF_REQ,
+                               .sc = PN533_FELICA_SENSF_SC_ALL,
+                               .rc = PN533_FELICA_SENSF_RC_NO_SYSTEM_CODE,
+                               .tsn = 0,
+                       },
+                },
+               .len = 7,
+       },
+       [PN533_POLL_MOD_106KBPS_JEWEL] = {
+               .data = {
+                       .maxtg = 1,
+                       .brty = 4,
+               },
+               .len = 2,
+       },
+       [PN533_POLL_MOD_847KBPS_B] = {
+               .data = {
+                       .maxtg = 1,
+                       .brty = 8,
+                       .initiator_data.type_b = {
+                               .afi = PN533_TYPE_B_AFI_ALL_FAMILIES,
+                               .polling_method =
+                                       PN533_TYPE_B_POLL_METHOD_TIMESLOT,
+                       },
+               },
+               .len = 3,
+       },
+       [PN533_LISTEN_MOD] = {
+               .len = 0,
+       },
+};
+
+/* PN533_CMD_IN_ATR */
+
+struct pn533_cmd_activate_param {
+       u8 tg;
+       u8 next;
+} __packed;
+
+struct pn533_cmd_activate_response {
+       u8 status;
+       u8 nfcid3t[10];
+       u8 didt;
+       u8 bst;
+       u8 brt;
+       u8 to;
+       u8 ppt;
+       /* optional */
+       u8 gt[];
+} __packed;
+
+/* PN533_CMD_IN_JUMP_FOR_DEP */
+struct pn533_cmd_jump_dep {
+       u8 active;
+       u8 baud;
+       u8 next;
+       u8 data[];
+} __packed;
+
+struct pn533_cmd_jump_dep_response {
+       u8 status;
+       u8 tg;
+       u8 nfcid3t[10];
+       u8 didt;
+       u8 bst;
+       u8 brt;
+       u8 to;
+       u8 ppt;
+       /* optional */
+       u8 gt[];
+} __packed;
+
+
+/* PN533_TG_INIT_AS_TARGET */
+#define PN533_INIT_TARGET_PASSIVE 0x1
+#define PN533_INIT_TARGET_DEP 0x2
+
+#define PN533_INIT_TARGET_RESP_FRAME_MASK 0x3
+#define PN533_INIT_TARGET_RESP_ACTIVE     0x1
+#define PN533_INIT_TARGET_RESP_DEP        0x4
+
+struct pn533_cmd_init_target {
+       u8 mode;
+       u8 mifare[6];
+       u8 felica[18];
+       u8 nfcid3[10];
+       u8 gb_len;
+       u8 gb[];
+} __packed;
+
+struct pn533_cmd_init_target_response {
+       u8 mode;
+       u8 cmd[];
+} __packed;
+
+struct pn533 {
+       struct usb_device *udev;
+       struct usb_interface *interface;
+       struct nfc_dev *nfc_dev;
+
+       struct urb *out_urb;
+       int out_maxlen;
+       struct pn533_frame *out_frame;
+
+       struct urb *in_urb;
+       int in_maxlen;
+       struct pn533_frame *in_frame;
+
+       struct sk_buff_head resp_q;
+
+       struct workqueue_struct *wq;
+       struct work_struct cmd_work;
+       struct work_struct poll_work;
+       struct work_struct mi_work;
+       struct work_struct tg_work;
+       struct timer_list listen_timer;
+       struct pn533_frame *wq_in_frame;
+       int wq_in_error;
+       int cancel_listen;
+
+       pn533_cmd_complete_t cmd_complete;
+       void *cmd_complete_arg;
+       struct mutex cmd_lock;
+       u8 cmd;
+
+       struct pn533_poll_modulations *poll_mod_active[PN533_POLL_MOD_MAX + 1];
+       u8 poll_mod_count;
+       u8 poll_mod_curr;
+       u32 poll_protocols;
+       u32 listen_protocols;
+
+       u8 *gb;
+       size_t gb_len;
+
+       u8 tgt_available_prots;
+       u8 tgt_active_prot;
+       u8 tgt_mode;
+
+       u32 device_type;
+};
+
+struct pn533_frame {
+       u8 preamble;
+       __be16 start_frame;
+       u8 datalen;
+       u8 datalen_checksum;
+       u8 data[];
+} __packed;
+
+/* The rule: value + checksum = 0 */
+static inline u8 pn533_checksum(u8 value)
+{
+       return ~value + 1;
+}
+
+/* The rule: sum(data elements) + checksum = 0 */
+static u8 pn533_data_checksum(u8 *data, int datalen)
+{
+       u8 sum = 0;
+       int i;
+
+       for (i = 0; i < datalen; i++)
+               sum += data[i];
+
+       return pn533_checksum(sum);
+}
+
+/**
+ * pn533_tx_frame_ack - create a ack frame
+ * @frame:     The frame to be set as ack
+ *
+ * Ack is different type of standard frame. As a standard frame, it has
+ * preamble and start_frame. However the checksum of this frame must fail,
+ * i.e. datalen + datalen_checksum must NOT be zero. When the checksum test
+ * fails and datalen = 0 and datalen_checksum = 0xFF, the frame is a ack.
+ * After datalen_checksum field, the postamble is placed.
+ */
+static void pn533_tx_frame_ack(struct pn533_frame *frame)
+{
+       frame->preamble = 0;
+       frame->start_frame = cpu_to_be16(PN533_SOF);
+       frame->datalen = 0;
+       frame->datalen_checksum = 0xFF;
+       /* data[0] is used as postamble */
+       frame->data[0] = 0;
+}
+
+static void pn533_tx_frame_init(struct pn533_frame *frame, u8 cmd)
+{
+       frame->preamble = 0;
+       frame->start_frame = cpu_to_be16(PN533_SOF);
+       PN533_FRAME_IDENTIFIER(frame) = PN533_DIR_OUT;
+       PN533_FRAME_CMD(frame) = cmd;
+       frame->datalen = 2;
+}
+
+static void pn533_tx_frame_finish(struct pn533_frame *frame)
+{
+       frame->datalen_checksum = pn533_checksum(frame->datalen);
+
+       PN533_FRAME_CHECKSUM(frame) =
+               pn533_data_checksum(frame->data, frame->datalen);
+
+       PN533_FRAME_POSTAMBLE(frame) = 0;
+}
+
+static bool pn533_rx_frame_is_valid(struct pn533_frame *frame)
+{
+       u8 checksum;
+
+       if (frame->start_frame != cpu_to_be16(PN533_SOF))
+               return false;
+
+       checksum = pn533_checksum(frame->datalen);
+       if (checksum != frame->datalen_checksum)
+               return false;
+
+       checksum = pn533_data_checksum(frame->data, frame->datalen);
+       if (checksum != PN533_FRAME_CHECKSUM(frame))
+               return false;
+
+       return true;
+}
+
+static bool pn533_rx_frame_is_ack(struct pn533_frame *frame)
+{
+       if (frame->start_frame != cpu_to_be16(PN533_SOF))
+               return false;
+
+       if (frame->datalen != 0 || frame->datalen_checksum != 0xFF)
+               return false;
+
+       return true;
+}
+
+static bool pn533_rx_frame_is_cmd_response(struct pn533_frame *frame, u8 cmd)
+{
+       return (PN533_FRAME_CMD(frame) == PN533_CMD_RESPONSE(cmd));
+}
+
+
+static void pn533_wq_cmd_complete(struct work_struct *work)
+{
+       struct pn533 *dev = container_of(work, struct pn533, cmd_work);
+       struct pn533_frame *in_frame;
+       int rc;
+
+       in_frame = dev->wq_in_frame;
+
+       if (dev->wq_in_error)
+               rc = dev->cmd_complete(dev, dev->cmd_complete_arg, NULL,
+                                                       dev->wq_in_error);
+       else
+               rc = dev->cmd_complete(dev, dev->cmd_complete_arg,
+                                       PN533_FRAME_CMD_PARAMS_PTR(in_frame),
+                                       PN533_FRAME_CMD_PARAMS_LEN(in_frame));
+
+       if (rc != -EINPROGRESS)
+               mutex_unlock(&dev->cmd_lock);
+}
+
+static void pn533_recv_response(struct urb *urb)
+{
+       struct pn533 *dev = urb->context;
+       struct pn533_frame *in_frame;
+
+       dev->wq_in_frame = NULL;
+
+       switch (urb->status) {
+       case 0:
+               /* success */
+               break;
+       case -ECONNRESET:
+       case -ENOENT:
+       case -ESHUTDOWN:
+               nfc_dev_dbg(&dev->interface->dev, "Urb shutting down with"
+                                               " status: %d", urb->status);
+               dev->wq_in_error = urb->status;
+               goto sched_wq;
+       default:
+               nfc_dev_err(&dev->interface->dev, "Nonzero urb status received:"
+                                                       " %d", urb->status);
+               dev->wq_in_error = urb->status;
+               goto sched_wq;
+       }
+
+       in_frame = dev->in_urb->transfer_buffer;
+
+       if (!pn533_rx_frame_is_valid(in_frame)) {
+               nfc_dev_err(&dev->interface->dev, "Received an invalid frame");
+               dev->wq_in_error = -EIO;
+               goto sched_wq;
+       }
+
+       if (!pn533_rx_frame_is_cmd_response(in_frame, dev->cmd)) {
+               nfc_dev_err(&dev->interface->dev, "The received frame is not "
+                                               "response to the last command");
+               dev->wq_in_error = -EIO;
+               goto sched_wq;
+       }
+
+       nfc_dev_dbg(&dev->interface->dev, "Received a valid frame");
+       dev->wq_in_error = 0;
+       dev->wq_in_frame = in_frame;
+
+sched_wq:
+       queue_work(dev->wq, &dev->cmd_work);
+}
+
+static int pn533_submit_urb_for_response(struct pn533 *dev, gfp_t flags)
+{
+       dev->in_urb->complete = pn533_recv_response;
+
+       return usb_submit_urb(dev->in_urb, flags);
+}
+
+static void pn533_recv_ack(struct urb *urb)
+{
+       struct pn533 *dev = urb->context;
+       struct pn533_frame *in_frame;
+       int rc;
+
+       switch (urb->status) {
+       case 0:
+               /* success */
+               break;
+       case -ECONNRESET:
+       case -ENOENT:
+       case -ESHUTDOWN:
+               nfc_dev_dbg(&dev->interface->dev, "Urb shutting down with"
+                                               " status: %d", urb->status);
+               dev->wq_in_error = urb->status;
+               goto sched_wq;
+       default:
+               nfc_dev_err(&dev->interface->dev, "Nonzero urb status received:"
+                                                       " %d", urb->status);
+               dev->wq_in_error = urb->status;
+               goto sched_wq;
+       }
+
+       in_frame = dev->in_urb->transfer_buffer;
+
+       if (!pn533_rx_frame_is_ack(in_frame)) {
+               nfc_dev_err(&dev->interface->dev, "Received an invalid ack");
+               dev->wq_in_error = -EIO;
+               goto sched_wq;
+       }
+
+       nfc_dev_dbg(&dev->interface->dev, "Received a valid ack");
+
+       rc = pn533_submit_urb_for_response(dev, GFP_ATOMIC);
+       if (rc) {
+               nfc_dev_err(&dev->interface->dev, "usb_submit_urb failed with"
+                                                       " result %d", rc);
+               dev->wq_in_error = rc;
+               goto sched_wq;
+       }
+
+       return;
+
+sched_wq:
+       dev->wq_in_frame = NULL;
+       queue_work(dev->wq, &dev->cmd_work);
+}
+
+static int pn533_submit_urb_for_ack(struct pn533 *dev, gfp_t flags)
+{
+       dev->in_urb->complete = pn533_recv_ack;
+
+       return usb_submit_urb(dev->in_urb, flags);
+}
+
+static int pn533_send_ack(struct pn533 *dev, gfp_t flags)
+{
+       int rc;
+
+       nfc_dev_dbg(&dev->interface->dev, "%s", __func__);
+
+       pn533_tx_frame_ack(dev->out_frame);
+
+       dev->out_urb->transfer_buffer = dev->out_frame;
+       dev->out_urb->transfer_buffer_length = PN533_FRAME_ACK_SIZE;
+       rc = usb_submit_urb(dev->out_urb, flags);
+
+       return rc;
+}
+
+static int __pn533_send_cmd_frame_async(struct pn533 *dev,
+                                       struct pn533_frame *out_frame,
+                                       struct pn533_frame *in_frame,
+                                       int in_frame_len,
+                                       pn533_cmd_complete_t cmd_complete,
+                                       void *arg, gfp_t flags)
+{
+       int rc;
+
+       nfc_dev_dbg(&dev->interface->dev, "Sending command 0x%x",
+                                               PN533_FRAME_CMD(out_frame));
+
+       dev->cmd = PN533_FRAME_CMD(out_frame);
+       dev->cmd_complete = cmd_complete;
+       dev->cmd_complete_arg = arg;
+
+       dev->out_urb->transfer_buffer = out_frame;
+       dev->out_urb->transfer_buffer_length =
+                               PN533_FRAME_SIZE(out_frame);
+
+       dev->in_urb->transfer_buffer = in_frame;
+       dev->in_urb->transfer_buffer_length = in_frame_len;
+
+       rc = usb_submit_urb(dev->out_urb, flags);
+       if (rc)
+               return rc;
+
+       rc = pn533_submit_urb_for_ack(dev, flags);
+       if (rc)
+               goto error;
+
+       return 0;
+
+error:
+       usb_unlink_urb(dev->out_urb);
+       return rc;
+}
+
+static int pn533_send_cmd_frame_async(struct pn533 *dev,
+                                       struct pn533_frame *out_frame,
+                                       struct pn533_frame *in_frame,
+                                       int in_frame_len,
+                                       pn533_cmd_complete_t cmd_complete,
+                                       void *arg, gfp_t flags)
+{
+       int rc;
+
+       nfc_dev_dbg(&dev->interface->dev, "%s", __func__);
+
+       if (!mutex_trylock(&dev->cmd_lock))
+               return -EBUSY;
+
+       rc = __pn533_send_cmd_frame_async(dev, out_frame, in_frame,
+                                       in_frame_len, cmd_complete, arg, flags);
+       if (rc)
+               goto error;
+
+       return 0;
+error:
+       mutex_unlock(&dev->cmd_lock);
+       return rc;
+}
+
+struct pn533_sync_cmd_response {
+       int rc;
+       struct completion done;
+};
+
+static int pn533_sync_cmd_complete(struct pn533 *dev, void *_arg,
+                                       u8 *params, int params_len)
+{
+       struct pn533_sync_cmd_response *arg = _arg;
+
+       nfc_dev_dbg(&dev->interface->dev, "%s", __func__);
+
+       arg->rc = 0;
+
+       if (params_len < 0) /* error */
+               arg->rc = params_len;
+
+       complete(&arg->done);
+
+       return 0;
+}
+
+static int pn533_send_cmd_frame_sync(struct pn533 *dev,
+                                               struct pn533_frame *out_frame,
+                                               struct pn533_frame *in_frame,
+                                               int in_frame_len)
+{
+       int rc;
+       struct pn533_sync_cmd_response arg;
+
+       nfc_dev_dbg(&dev->interface->dev, "%s", __func__);
+
+       init_completion(&arg.done);
+
+       rc = pn533_send_cmd_frame_async(dev, out_frame, in_frame, in_frame_len,
+                               pn533_sync_cmd_complete, &arg, GFP_KERNEL);
+       if (rc)
+               return rc;
+
+       wait_for_completion(&arg.done);
+
+       return arg.rc;
+}
+
+static void pn533_send_complete(struct urb *urb)
+{
+       struct pn533 *dev = urb->context;
+
+       nfc_dev_dbg(&dev->interface->dev, "%s", __func__);
+
+       switch (urb->status) {
+       case 0:
+               /* success */
+               break;
+       case -ECONNRESET:
+       case -ENOENT:
+       case -ESHUTDOWN:
+               nfc_dev_dbg(&dev->interface->dev, "Urb shutting down with"
+                                               " status: %d", urb->status);
+               break;
+       default:
+               nfc_dev_dbg(&dev->interface->dev, "Nonzero urb status received:"
+                                                       " %d", urb->status);
+       }
+}
+
+struct pn533_target_type_a {
+       __be16 sens_res;
+       u8 sel_res;
+       u8 nfcid_len;
+       u8 nfcid_data[];
+} __packed;
+
+
+#define PN533_TYPE_A_SENS_RES_NFCID1(x) ((u8)((be16_to_cpu(x) & 0x00C0) >> 6))
+#define PN533_TYPE_A_SENS_RES_SSD(x) ((u8)((be16_to_cpu(x) & 0x001F) >> 0))
+#define PN533_TYPE_A_SENS_RES_PLATCONF(x) ((u8)((be16_to_cpu(x) & 0x0F00) >> 8))
+
+#define PN533_TYPE_A_SENS_RES_SSD_JEWEL 0x00
+#define PN533_TYPE_A_SENS_RES_PLATCONF_JEWEL 0x0C
+
+#define PN533_TYPE_A_SEL_PROT(x) (((x) & 0x60) >> 5)
+#define PN533_TYPE_A_SEL_CASCADE(x) (((x) & 0x04) >> 2)
+
+#define PN533_TYPE_A_SEL_PROT_MIFARE 0
+#define PN533_TYPE_A_SEL_PROT_ISO14443 1
+#define PN533_TYPE_A_SEL_PROT_DEP 2
+#define PN533_TYPE_A_SEL_PROT_ISO14443_DEP 3
+
+static bool pn533_target_type_a_is_valid(struct pn533_target_type_a *type_a,
+                                                       int target_data_len)
+{
+       u8 ssd;
+       u8 platconf;
+
+       if (target_data_len < sizeof(struct pn533_target_type_a))
+               return false;
+
+       /* The lenght check of nfcid[] and ats[] are not being performed because
+          the values are not being used */
+
+       /* Requirement 4.6.3.3 from NFC Forum Digital Spec */
+       ssd = PN533_TYPE_A_SENS_RES_SSD(type_a->sens_res);
+       platconf = PN533_TYPE_A_SENS_RES_PLATCONF(type_a->sens_res);
+
+       if ((ssd == PN533_TYPE_A_SENS_RES_SSD_JEWEL &&
+                       platconf != PN533_TYPE_A_SENS_RES_PLATCONF_JEWEL) ||
+                       (ssd != PN533_TYPE_A_SENS_RES_SSD_JEWEL &&
+                       platconf == PN533_TYPE_A_SENS_RES_PLATCONF_JEWEL))
+               return false;
+
+       /* Requirements 4.8.2.1, 4.8.2.3, 4.8.2.5 and 4.8.2.7 from NFC Forum */
+       if (PN533_TYPE_A_SEL_CASCADE(type_a->sel_res) != 0)
+               return false;
+
+       return true;
+}
+
+static int pn533_target_found_type_a(struct nfc_target *nfc_tgt, u8 *tgt_data,
+                                                       int tgt_data_len)
+{
+       struct pn533_target_type_a *tgt_type_a;
+
+       tgt_type_a = (struct pn533_target_type_a *) tgt_data;
+
+       if (!pn533_target_type_a_is_valid(tgt_type_a, tgt_data_len))
+               return -EPROTO;
+
+       switch (PN533_TYPE_A_SEL_PROT(tgt_type_a->sel_res)) {
+       case PN533_TYPE_A_SEL_PROT_MIFARE:
+               nfc_tgt->supported_protocols = NFC_PROTO_MIFARE_MASK;
+               break;
+       case PN533_TYPE_A_SEL_PROT_ISO14443:
+               nfc_tgt->supported_protocols = NFC_PROTO_ISO14443_MASK;
+               break;
+       case PN533_TYPE_A_SEL_PROT_DEP:
+               nfc_tgt->supported_protocols = NFC_PROTO_NFC_DEP_MASK;
+               break;
+       case PN533_TYPE_A_SEL_PROT_ISO14443_DEP:
+               nfc_tgt->supported_protocols = NFC_PROTO_ISO14443_MASK |
+                                                       NFC_PROTO_NFC_DEP_MASK;
+               break;
+       }
+
+       nfc_tgt->sens_res = be16_to_cpu(tgt_type_a->sens_res);
+       nfc_tgt->sel_res = tgt_type_a->sel_res;
+       nfc_tgt->nfcid1_len = tgt_type_a->nfcid_len;
+       memcpy(nfc_tgt->nfcid1, tgt_type_a->nfcid_data, nfc_tgt->nfcid1_len);
+
+       return 0;
+}
+
+struct pn533_target_felica {
+       u8 pol_res;
+       u8 opcode;
+       u8 nfcid2[8];
+       u8 pad[8];
+       /* optional */
+       u8 syst_code[];
+} __packed;
+
+#define PN533_FELICA_SENSF_NFCID2_DEP_B1 0x01
+#define PN533_FELICA_SENSF_NFCID2_DEP_B2 0xFE
+
+static bool pn533_target_felica_is_valid(struct pn533_target_felica *felica,
+                                                       int target_data_len)
+{
+       if (target_data_len < sizeof(struct pn533_target_felica))
+               return false;
+
+       if (felica->opcode != PN533_FELICA_OPC_SENSF_RES)
+               return false;
+
+       return true;
+}
+
+static int pn533_target_found_felica(struct nfc_target *nfc_tgt, u8 *tgt_data,
+                                                       int tgt_data_len)
+{
+       struct pn533_target_felica *tgt_felica;
+
+       tgt_felica = (struct pn533_target_felica *) tgt_data;
+
+       if (!pn533_target_felica_is_valid(tgt_felica, tgt_data_len))
+               return -EPROTO;
+
+       if (tgt_felica->nfcid2[0] == PN533_FELICA_SENSF_NFCID2_DEP_B1 &&
+                                       tgt_felica->nfcid2[1] ==
+                                       PN533_FELICA_SENSF_NFCID2_DEP_B2)
+               nfc_tgt->supported_protocols = NFC_PROTO_NFC_DEP_MASK;
+       else
+               nfc_tgt->supported_protocols = NFC_PROTO_FELICA_MASK;
+
+       memcpy(nfc_tgt->sensf_res, &tgt_felica->opcode, 9);
+       nfc_tgt->sensf_res_len = 9;
+
+       return 0;
+}
+
+struct pn533_target_jewel {
+       __be16 sens_res;
+       u8 jewelid[4];
+} __packed;
+
+static bool pn533_target_jewel_is_valid(struct pn533_target_jewel *jewel,
+                                                       int target_data_len)
+{
+       u8 ssd;
+       u8 platconf;
+
+       if (target_data_len < sizeof(struct pn533_target_jewel))
+               return false;
+
+       /* Requirement 4.6.3.3 from NFC Forum Digital Spec */
+       ssd = PN533_TYPE_A_SENS_RES_SSD(jewel->sens_res);
+       platconf = PN533_TYPE_A_SENS_RES_PLATCONF(jewel->sens_res);
+
+       if ((ssd == PN533_TYPE_A_SENS_RES_SSD_JEWEL &&
+                       platconf != PN533_TYPE_A_SENS_RES_PLATCONF_JEWEL) ||
+                       (ssd != PN533_TYPE_A_SENS_RES_SSD_JEWEL &&
+                       platconf == PN533_TYPE_A_SENS_RES_PLATCONF_JEWEL))
+               return false;
+
+       return true;
+}
+
+static int pn533_target_found_jewel(struct nfc_target *nfc_tgt, u8 *tgt_data,
+                                                       int tgt_data_len)
+{
+       struct pn533_target_jewel *tgt_jewel;
+
+       tgt_jewel = (struct pn533_target_jewel *) tgt_data;
+
+       if (!pn533_target_jewel_is_valid(tgt_jewel, tgt_data_len))
+               return -EPROTO;
+
+       nfc_tgt->supported_protocols = NFC_PROTO_JEWEL_MASK;
+       nfc_tgt->sens_res = be16_to_cpu(tgt_jewel->sens_res);
+       nfc_tgt->nfcid1_len = 4;
+       memcpy(nfc_tgt->nfcid1, tgt_jewel->jewelid, nfc_tgt->nfcid1_len);
+
+       return 0;
+}
+
+struct pn533_type_b_prot_info {
+       u8 bitrate;
+       u8 fsci_type;
+       u8 fwi_adc_fo;
+} __packed;
+
+#define PN533_TYPE_B_PROT_FCSI(x) (((x) & 0xF0) >> 4)
+#define PN533_TYPE_B_PROT_TYPE(x) (((x) & 0x0F) >> 0)
+#define PN533_TYPE_B_PROT_TYPE_RFU_MASK 0x8
+
+struct pn533_type_b_sens_res {
+       u8 opcode;
+       u8 nfcid[4];
+       u8 appdata[4];
+       struct pn533_type_b_prot_info prot_info;
+} __packed;
+
+#define PN533_TYPE_B_OPC_SENSB_RES 0x50
+
+struct pn533_target_type_b {
+       struct pn533_type_b_sens_res sensb_res;
+       u8 attrib_res_len;
+       u8 attrib_res[];
+} __packed;
+
+static bool pn533_target_type_b_is_valid(struct pn533_target_type_b *type_b,
+                                                       int target_data_len)
+{
+       if (target_data_len < sizeof(struct pn533_target_type_b))
+               return false;
+
+       if (type_b->sensb_res.opcode != PN533_TYPE_B_OPC_SENSB_RES)
+               return false;
+
+       if (PN533_TYPE_B_PROT_TYPE(type_b->sensb_res.prot_info.fsci_type) &
+                                               PN533_TYPE_B_PROT_TYPE_RFU_MASK)
+               return false;
+
+       return true;
+}
+
+static int pn533_target_found_type_b(struct nfc_target *nfc_tgt, u8 *tgt_data,
+                                                       int tgt_data_len)
+{
+       struct pn533_target_type_b *tgt_type_b;
+
+       tgt_type_b = (struct pn533_target_type_b *) tgt_data;
+
+       if (!pn533_target_type_b_is_valid(tgt_type_b, tgt_data_len))
+               return -EPROTO;
+
+       nfc_tgt->supported_protocols = NFC_PROTO_ISO14443_B_MASK;
+
+       return 0;
+}
+
+struct pn533_poll_response {
+       u8 nbtg;
+       u8 tg;
+       u8 target_data[];
+} __packed;
+
+static int pn533_target_found(struct pn533 *dev,
+                       struct pn533_poll_response *resp, int resp_len)
+{
+       int target_data_len;
+       struct nfc_target nfc_tgt;
+       int rc;
+
+       nfc_dev_dbg(&dev->interface->dev, "%s - modulation=%d", __func__,
+                                                       dev->poll_mod_curr);
+
+       if (resp->tg != 1)
+               return -EPROTO;
+
+       memset(&nfc_tgt, 0, sizeof(struct nfc_target));
+
+       target_data_len = resp_len - sizeof(struct pn533_poll_response);
+
+       switch (dev->poll_mod_curr) {
+       case PN533_POLL_MOD_106KBPS_A:
+               rc = pn533_target_found_type_a(&nfc_tgt, resp->target_data,
+                                                       target_data_len);
+               break;
+       case PN533_POLL_MOD_212KBPS_FELICA:
+       case PN533_POLL_MOD_424KBPS_FELICA:
+               rc = pn533_target_found_felica(&nfc_tgt, resp->target_data,
+                                                       target_data_len);
+               break;
+       case PN533_POLL_MOD_106KBPS_JEWEL:
+               rc = pn533_target_found_jewel(&nfc_tgt, resp->target_data,
+                                                       target_data_len);
+               break;
+       case PN533_POLL_MOD_847KBPS_B:
+               rc = pn533_target_found_type_b(&nfc_tgt, resp->target_data,
+                                                       target_data_len);
+               break;
+       default:
+               nfc_dev_err(&dev->interface->dev, "Unknown current poll"
+                                                               " modulation");
+               return -EPROTO;
+       }
+
+       if (rc)
+               return rc;
+
+       if (!(nfc_tgt.supported_protocols & dev->poll_protocols)) {
+               nfc_dev_dbg(&dev->interface->dev, "The target found does not"
+                                               " have the desired protocol");
+               return -EAGAIN;
+       }
+
+       nfc_dev_dbg(&dev->interface->dev, "Target found - supported protocols: "
+                                       "0x%x", nfc_tgt.supported_protocols);
+
+       dev->tgt_available_prots = nfc_tgt.supported_protocols;
+
+       nfc_targets_found(dev->nfc_dev, &nfc_tgt, 1);
+
+       return 0;
+}
+
+static inline void pn533_poll_next_mod(struct pn533 *dev)
+{
+       dev->poll_mod_curr = (dev->poll_mod_curr + 1) % dev->poll_mod_count;
+}
+
+static void pn533_poll_reset_mod_list(struct pn533 *dev)
+{
+       dev->poll_mod_count = 0;
+}
+
+static void pn533_poll_add_mod(struct pn533 *dev, u8 mod_index)
+{
+       dev->poll_mod_active[dev->poll_mod_count] =
+               (struct pn533_poll_modulations *) &poll_mod[mod_index];
+       dev->poll_mod_count++;
+}
+
+static void pn533_poll_create_mod_list(struct pn533 *dev,
+                                      u32 im_protocols, u32 tm_protocols)
+{
+       pn533_poll_reset_mod_list(dev);
+
+       if (im_protocols & NFC_PROTO_MIFARE_MASK
+           || im_protocols & NFC_PROTO_ISO14443_MASK
+           || im_protocols & NFC_PROTO_NFC_DEP_MASK)
+               pn533_poll_add_mod(dev, PN533_POLL_MOD_106KBPS_A);
+
+       if (im_protocols & NFC_PROTO_FELICA_MASK
+           || im_protocols & NFC_PROTO_NFC_DEP_MASK) {
+               pn533_poll_add_mod(dev, PN533_POLL_MOD_212KBPS_FELICA);
+               pn533_poll_add_mod(dev, PN533_POLL_MOD_424KBPS_FELICA);
+       }
+
+       if (im_protocols & NFC_PROTO_JEWEL_MASK)
+               pn533_poll_add_mod(dev, PN533_POLL_MOD_106KBPS_JEWEL);
+
+       if (im_protocols & NFC_PROTO_ISO14443_B_MASK)
+               pn533_poll_add_mod(dev, PN533_POLL_MOD_847KBPS_B);
+
+       if (tm_protocols)
+               pn533_poll_add_mod(dev, PN533_LISTEN_MOD);
+}
+
+static int pn533_start_poll_complete(struct pn533 *dev, void *arg,
+                                    u8 *params, int params_len)
+{
+       struct pn533_poll_response *resp;
+       int rc;
+
+       nfc_dev_dbg(&dev->interface->dev, "%s", __func__);
+
+       resp = (struct pn533_poll_response *) params;
+       if (resp->nbtg) {
+               rc = pn533_target_found(dev, resp, params_len);
+
+               /* We must stop the poll after a valid target found */
+               if (rc == 0) {
+                       pn533_poll_reset_mod_list(dev);
+                       return 0;
+               }
+       }
+
+       return -EAGAIN;
+}
+
+static int pn533_init_target_frame(struct pn533_frame *frame,
+                                  u8 *gb, size_t gb_len)
+{
+       struct pn533_cmd_init_target *cmd;
+       size_t cmd_len;
+       u8 felica_params[18] = {0x1, 0xfe, /* DEP */
+                               0x0, 0x0, 0x0, 0x0, 0x0, 0x0, /* random */
+                               0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+                               0xff, 0xff}; /* System code */
+       u8 mifare_params[6] = {0x1, 0x1, /* SENS_RES */
+                              0x0, 0x0, 0x0,
+                              0x40}; /* SEL_RES for DEP */
+
+       cmd_len = sizeof(struct pn533_cmd_init_target) + gb_len + 1;
+       cmd = kzalloc(cmd_len, GFP_KERNEL);
+       if (cmd == NULL)
+               return -ENOMEM;
+
+       pn533_tx_frame_init(frame, PN533_CMD_TG_INIT_AS_TARGET);
+
+       /* DEP support only */
+       cmd->mode |= PN533_INIT_TARGET_DEP;
+
+       /* Felica params */
+       memcpy(cmd->felica, felica_params, 18);
+       get_random_bytes(cmd->felica + 2, 6);
+
+       /* NFCID3 */
+       memset(cmd->nfcid3, 0, 10);
+       memcpy(cmd->nfcid3, cmd->felica, 8);
+
+       /* MIFARE params */
+       memcpy(cmd->mifare, mifare_params, 6);
+
+       /* General bytes */
+       cmd->gb_len = gb_len;
+       memcpy(cmd->gb, gb, gb_len);
+
+       /* Len Tk */
+       cmd->gb[gb_len] = 0;
+
+       memcpy(PN533_FRAME_CMD_PARAMS_PTR(frame), cmd, cmd_len);
+
+       frame->datalen += cmd_len;
+
+       pn533_tx_frame_finish(frame);
+
+       kfree(cmd);
+
+       return 0;
+}
+
+#define PN533_CMD_DATAEXCH_HEAD_LEN (sizeof(struct pn533_frame) + 3)
+#define PN533_CMD_DATAEXCH_DATA_MAXLEN 262
+static int pn533_tm_get_data_complete(struct pn533 *dev, void *arg,
+                                     u8 *params, int params_len)
+{
+       struct sk_buff *skb_resp = arg;
+       struct pn533_frame *in_frame = (struct pn533_frame *) skb_resp->data;
+
+       nfc_dev_dbg(&dev->interface->dev, "%s", __func__);
+
+       if (params_len < 0) {
+               nfc_dev_err(&dev->interface->dev,
+                           "Error %d when starting as a target",
+                           params_len);
+
+               return params_len;
+       }
+
+       if (params_len > 0 && params[0] != 0) {
+               nfc_tm_deactivated(dev->nfc_dev);
+
+               dev->tgt_mode = 0;
+
+               kfree_skb(skb_resp);
+               return 0;
+       }
+
+       skb_put(skb_resp, PN533_FRAME_SIZE(in_frame));
+       skb_pull(skb_resp, PN533_CMD_DATAEXCH_HEAD_LEN);
+       skb_trim(skb_resp, skb_resp->len - PN533_FRAME_TAIL_SIZE);
+
+       return nfc_tm_data_received(dev->nfc_dev, skb_resp);
+}
+
+static void pn533_wq_tg_get_data(struct work_struct *work)
+{
+       struct pn533 *dev = container_of(work, struct pn533, tg_work);
+       struct pn533_frame *in_frame;
+       struct sk_buff *skb_resp;
+       size_t skb_resp_len;
+
+       nfc_dev_dbg(&dev->interface->dev, "%s", __func__);
+
+       skb_resp_len = PN533_CMD_DATAEXCH_HEAD_LEN +
+               PN533_CMD_DATAEXCH_DATA_MAXLEN +
+               PN533_FRAME_TAIL_SIZE;
+
+       skb_resp = nfc_alloc_recv_skb(skb_resp_len, GFP_KERNEL);
+       if (!skb_resp)
+               return;
+
+       in_frame = (struct pn533_frame *)skb_resp->data;
+
+       pn533_tx_frame_init(dev->out_frame, PN533_CMD_TG_GET_DATA);
+       pn533_tx_frame_finish(dev->out_frame);
+
+       pn533_send_cmd_frame_async(dev, dev->out_frame, in_frame,
+                                  skb_resp_len,
+                                  pn533_tm_get_data_complete,
+                                  skb_resp, GFP_KERNEL);
+
+       return;
+}
+
+#define ATR_REQ_GB_OFFSET 17
+static int pn533_init_target_complete(struct pn533 *dev, void *arg,
+                                     u8 *params, int params_len)
+{
+       struct pn533_cmd_init_target_response *resp;
+       u8 frame, comm_mode = NFC_COMM_PASSIVE, *gb;
+       size_t gb_len;
+       int rc;
+
+       nfc_dev_dbg(&dev->interface->dev, "%s", __func__);
+
+       if (params_len < 0) {
+               nfc_dev_err(&dev->interface->dev,
+                           "Error %d when starting as a target",
+                           params_len);
+
+               return params_len;
+       }
+
+       if (params_len < ATR_REQ_GB_OFFSET + 1)
+               return -EINVAL;
+
+       resp = (struct pn533_cmd_init_target_response *) params;
+
+       nfc_dev_dbg(&dev->interface->dev, "Target mode 0x%x param len %d\n",
+                   resp->mode, params_len);
+
+       frame = resp->mode & PN533_INIT_TARGET_RESP_FRAME_MASK;
+       if (frame == PN533_INIT_TARGET_RESP_ACTIVE)
+               comm_mode = NFC_COMM_ACTIVE;
+
+       /* Again, only DEP */
+       if ((resp->mode & PN533_INIT_TARGET_RESP_DEP) == 0)
+               return -EOPNOTSUPP;
+
+       gb = resp->cmd + ATR_REQ_GB_OFFSET;
+       gb_len = params_len - (ATR_REQ_GB_OFFSET + 1);
+
+       rc = nfc_tm_activated(dev->nfc_dev, NFC_PROTO_NFC_DEP_MASK,
+                             comm_mode, gb, gb_len);
+       if (rc < 0) {
+               nfc_dev_err(&dev->interface->dev,
+                           "Error when signaling target activation");
+               return rc;
+       }
+
+       dev->tgt_mode = 1;
+
+       queue_work(dev->wq, &dev->tg_work);
+
+       return 0;
+}
+
+static void pn533_listen_mode_timer(unsigned long data)
+{
+       struct pn533 *dev = (struct pn533 *) data;
+
+       nfc_dev_dbg(&dev->interface->dev, "Listen mode timeout");
+
+       /* An ack will cancel the last issued command (poll) */
+       pn533_send_ack(dev, GFP_ATOMIC);
+
+       dev->cancel_listen = 1;
+
+       mutex_unlock(&dev->cmd_lock);
+
+       pn533_poll_next_mod(dev);
+
+       queue_work(dev->wq, &dev->poll_work);
+}
+
+static int pn533_poll_complete(struct pn533 *dev, void *arg,
+                              u8 *params, int params_len)
+{
+       struct pn533_poll_modulations *cur_mod;
+       int rc;
+
+       nfc_dev_dbg(&dev->interface->dev, "%s", __func__);
+
+       if (params_len == -ENOENT) {
+               if (dev->poll_mod_count != 0)
+                       return 0;
+
+               nfc_dev_err(&dev->interface->dev,
+                           "Polling operation has been stopped");
+
+               goto stop_poll;
+       }
+
+       if (params_len < 0) {
+               nfc_dev_err(&dev->interface->dev,
+                           "Error %d when running poll", params_len);
+
+               goto stop_poll;
+       }
+
+       cur_mod = dev->poll_mod_active[dev->poll_mod_curr];
+
+       if (cur_mod->len == 0) {
+               del_timer(&dev->listen_timer);
+
+               return pn533_init_target_complete(dev, arg, params, params_len);
+       } else {
+               rc = pn533_start_poll_complete(dev, arg, params, params_len);
+               if (!rc)
+                       return rc;
+       }
+
+       pn533_poll_next_mod(dev);
+
+       queue_work(dev->wq, &dev->poll_work);
+
+       return 0;
+
+stop_poll:
+       pn533_poll_reset_mod_list(dev);
+       dev->poll_protocols = 0;
+       return 0;
+}
+
+static void pn533_build_poll_frame(struct pn533 *dev,
+                                  struct pn533_frame *frame,
+                                  struct pn533_poll_modulations *mod)
+{
+       nfc_dev_dbg(&dev->interface->dev, "mod len %d\n", mod->len);
+
+       if (mod->len == 0) {
+               /* Listen mode */
+               pn533_init_target_frame(frame, dev->gb, dev->gb_len);
+       } else {
+               /* Polling mode */
+               pn533_tx_frame_init(frame, PN533_CMD_IN_LIST_PASSIVE_TARGET);
+
+               memcpy(PN533_FRAME_CMD_PARAMS_PTR(frame), &mod->data, mod->len);
+               frame->datalen += mod->len;
+
+               pn533_tx_frame_finish(frame);
+       }
+}
+
+static int pn533_send_poll_frame(struct pn533 *dev)
+{
+       struct pn533_poll_modulations *cur_mod;
+       int rc;
+
+       cur_mod = dev->poll_mod_active[dev->poll_mod_curr];
+
+       pn533_build_poll_frame(dev, dev->out_frame, cur_mod);
+
+       rc = pn533_send_cmd_frame_async(dev, dev->out_frame, dev->in_frame,
+                               dev->in_maxlen, pn533_poll_complete,
+                               NULL, GFP_KERNEL);
+       if (rc)
+               nfc_dev_err(&dev->interface->dev, "Polling loop error %d", rc);
+
+       return rc;
+}
+
+static void pn533_wq_poll(struct work_struct *work)
+{
+       struct pn533 *dev = container_of(work, struct pn533, poll_work);
+       struct pn533_poll_modulations *cur_mod;
+       int rc;
+
+       cur_mod = dev->poll_mod_active[dev->poll_mod_curr];
+
+       nfc_dev_dbg(&dev->interface->dev,
+                   "%s cancel_listen %d modulation len %d",
+                   __func__, dev->cancel_listen, cur_mod->len);
+
+       if (dev->cancel_listen == 1) {
+               dev->cancel_listen = 0;
+               usb_kill_urb(dev->in_urb);
+       }
+
+       rc = pn533_send_poll_frame(dev);
+       if (rc)
+               return;
+
+       if (cur_mod->len == 0 && dev->poll_mod_count > 1)
+               mod_timer(&dev->listen_timer, jiffies + PN533_LISTEN_TIME * HZ);
+
+       return;
+}
+
+static int pn533_start_poll(struct nfc_dev *nfc_dev,
+                           u32 im_protocols, u32 tm_protocols)
+{
+       struct pn533 *dev = nfc_get_drvdata(nfc_dev);
+
+       nfc_dev_dbg(&dev->interface->dev,
+                   "%s: im protocols 0x%x tm protocols 0x%x",
+                   __func__, im_protocols, tm_protocols);
+
+       if (dev->tgt_active_prot) {
+               nfc_dev_err(&dev->interface->dev,
+                           "Cannot poll with a target already activated");
+               return -EBUSY;
+       }
+
+       if (dev->tgt_mode) {
+               nfc_dev_err(&dev->interface->dev,
+                           "Cannot poll while already being activated");
+               return -EBUSY;
+       }
+
+       if (tm_protocols) {
+               dev->gb = nfc_get_local_general_bytes(nfc_dev, &dev->gb_len);
+               if (dev->gb == NULL)
+                       tm_protocols = 0;
+       }
+
+       dev->poll_mod_curr = 0;
+       pn533_poll_create_mod_list(dev, im_protocols, tm_protocols);
+       dev->poll_protocols = im_protocols;
+       dev->listen_protocols = tm_protocols;
+
+       return pn533_send_poll_frame(dev);
+}
+
+static void pn533_stop_poll(struct nfc_dev *nfc_dev)
+{
+       struct pn533 *dev = nfc_get_drvdata(nfc_dev);
+
+       nfc_dev_dbg(&dev->interface->dev, "%s", __func__);
+
+       del_timer(&dev->listen_timer);
+
+       if (!dev->poll_mod_count) {
+               nfc_dev_dbg(&dev->interface->dev, "Polling operation was not"
+                                                               " running");
+               return;
+       }
+
+       /* An ack will cancel the last issued command (poll) */
+       pn533_send_ack(dev, GFP_KERNEL);
+
+       /* prevent pn533_start_poll_complete to issue a new poll meanwhile */
+       usb_kill_urb(dev->in_urb);
+
+       pn533_poll_reset_mod_list(dev);
+}
+
+static int pn533_activate_target_nfcdep(struct pn533 *dev)
+{
+       struct pn533_cmd_activate_param param;
+       struct pn533_cmd_activate_response *resp;
+       u16 gt_len;
+       int rc;
+
+       nfc_dev_dbg(&dev->interface->dev, "%s", __func__);
+
+       pn533_tx_frame_init(dev->out_frame, PN533_CMD_IN_ATR);
+
+       param.tg = 1;
+       param.next = 0;
+       memcpy(PN533_FRAME_CMD_PARAMS_PTR(dev->out_frame), &param,
+                               sizeof(struct pn533_cmd_activate_param));
+       dev->out_frame->datalen += sizeof(struct pn533_cmd_activate_param);
+
+       pn533_tx_frame_finish(dev->out_frame);
+
+       rc = pn533_send_cmd_frame_sync(dev, dev->out_frame, dev->in_frame,
+                                                               dev->in_maxlen);
+       if (rc)
+               return rc;
+
+       resp = (struct pn533_cmd_activate_response *)
+                               PN533_FRAME_CMD_PARAMS_PTR(dev->in_frame);
+       rc = resp->status & PN533_CMD_RET_MASK;
+       if (rc != PN533_CMD_RET_SUCCESS)
+               return -EIO;
+
+       /* ATR_RES general bytes are located at offset 16 */
+       gt_len = PN533_FRAME_CMD_PARAMS_LEN(dev->in_frame) - 16;
+       rc = nfc_set_remote_general_bytes(dev->nfc_dev, resp->gt, gt_len);
+
+       return rc;
+}
+
+static int pn533_activate_target(struct nfc_dev *nfc_dev,
+                                struct nfc_target *target, u32 protocol)
+{
+       struct pn533 *dev = nfc_get_drvdata(nfc_dev);
+       int rc;
+
+       nfc_dev_dbg(&dev->interface->dev, "%s - protocol=%u", __func__,
+                                                               protocol);
+
+       if (dev->poll_mod_count) {
+               nfc_dev_err(&dev->interface->dev, "Cannot activate while"
+                                                               " polling");
+               return -EBUSY;
+       }
+
+       if (dev->tgt_active_prot) {
+               nfc_dev_err(&dev->interface->dev, "There is already an active"
+                                                               " target");
+               return -EBUSY;
+       }
+
+       if (!dev->tgt_available_prots) {
+               nfc_dev_err(&dev->interface->dev, "There is no available target"
+                                                               " to activate");
+               return -EINVAL;
+       }
+
+       if (!(dev->tgt_available_prots & (1 << protocol))) {
+               nfc_dev_err(&dev->interface->dev, "The target does not support"
+                                       " the requested protocol %u", protocol);
+               return -EINVAL;
+       }
+
+       if (protocol == NFC_PROTO_NFC_DEP) {
+               rc = pn533_activate_target_nfcdep(dev);
+               if (rc) {
+                       nfc_dev_err(&dev->interface->dev, "Error %d when"
+                                               " activating target with"
+                                               " NFC_DEP protocol", rc);
+                       return rc;
+               }
+       }
+
+       dev->tgt_active_prot = protocol;
+       dev->tgt_available_prots = 0;
+
+       return 0;
+}
+
+static void pn533_deactivate_target(struct nfc_dev *nfc_dev,
+                                   struct nfc_target *target)
+{
+       struct pn533 *dev = nfc_get_drvdata(nfc_dev);
+       u8 tg;
+       u8 status;
+       int rc;
+
+       nfc_dev_dbg(&dev->interface->dev, "%s", __func__);
+
+       if (!dev->tgt_active_prot) {
+               nfc_dev_err(&dev->interface->dev, "There is no active target");
+               return;
+       }
+
+       dev->tgt_active_prot = 0;
+
+       skb_queue_purge(&dev->resp_q);
+
+       pn533_tx_frame_init(dev->out_frame, PN533_CMD_IN_RELEASE);
+
+       tg = 1;
+       memcpy(PN533_FRAME_CMD_PARAMS_PTR(dev->out_frame), &tg, sizeof(u8));
+       dev->out_frame->datalen += sizeof(u8);
+
+       pn533_tx_frame_finish(dev->out_frame);
+
+       rc = pn533_send_cmd_frame_sync(dev, dev->out_frame, dev->in_frame,
+                                                               dev->in_maxlen);
+       if (rc) {
+               nfc_dev_err(&dev->interface->dev, "Error when sending release"
+                                               " command to the controller");
+               return;
+       }
+
+       status = PN533_FRAME_CMD_PARAMS_PTR(dev->in_frame)[0];
+       rc = status & PN533_CMD_RET_MASK;
+       if (rc != PN533_CMD_RET_SUCCESS)
+               nfc_dev_err(&dev->interface->dev, "Error 0x%x when releasing"
+                                                       " the target", rc);
+
+       return;
+}
+
+
+static int pn533_in_dep_link_up_complete(struct pn533 *dev, void *arg,
+                                               u8 *params, int params_len)
+{
+       struct pn533_cmd_jump_dep *cmd;
+       struct pn533_cmd_jump_dep_response *resp;
+       struct nfc_target nfc_target;
+       u8 target_gt_len;
+       int rc;
+
+       if (params_len == -ENOENT) {
+               nfc_dev_dbg(&dev->interface->dev, "");
+               return 0;
+       }
+
+       if (params_len < 0) {
+               nfc_dev_err(&dev->interface->dev,
+                               "Error %d when bringing DEP link up",
+                                                               params_len);
+               return 0;
+       }
+
+       if (dev->tgt_available_prots &&
+           !(dev->tgt_available_prots & (1 << NFC_PROTO_NFC_DEP))) {
+               nfc_dev_err(&dev->interface->dev,
+                       "The target does not support DEP");
+               return -EINVAL;
+       }
+
+       resp = (struct pn533_cmd_jump_dep_response *) params;
+       cmd = (struct pn533_cmd_jump_dep *) arg;
+       rc = resp->status & PN533_CMD_RET_MASK;
+       if (rc != PN533_CMD_RET_SUCCESS) {
+               nfc_dev_err(&dev->interface->dev,
+                               "Bringing DEP link up failed %d", rc);
+               return 0;
+       }
+
+       if (!dev->tgt_available_prots) {
+               nfc_dev_dbg(&dev->interface->dev, "Creating new target");
+
+               nfc_target.supported_protocols = NFC_PROTO_NFC_DEP_MASK;
+               nfc_target.nfcid1_len = 10;
+               memcpy(nfc_target.nfcid1, resp->nfcid3t, nfc_target.nfcid1_len);
+               rc = nfc_targets_found(dev->nfc_dev, &nfc_target, 1);
+               if (rc)
+                       return 0;
+
+               dev->tgt_available_prots = 0;
+       }
+
+       dev->tgt_active_prot = NFC_PROTO_NFC_DEP;
+
+       /* ATR_RES general bytes are located at offset 17 */
+       target_gt_len = PN533_FRAME_CMD_PARAMS_LEN(dev->in_frame) - 17;
+       rc = nfc_set_remote_general_bytes(dev->nfc_dev,
+                                               resp->gt, target_gt_len);
+       if (rc == 0)
+               rc = nfc_dep_link_is_up(dev->nfc_dev,
+                                               dev->nfc_dev->targets[0].idx,
+                                               !cmd->active, NFC_RF_INITIATOR);
+
+       return 0;
+}
+
+static int pn533_mod_to_baud(struct pn533 *dev)
+{
+       switch (dev->poll_mod_curr) {
+       case PN533_POLL_MOD_106KBPS_A:
+               return 0;
+       case PN533_POLL_MOD_212KBPS_FELICA:
+               return 1;
+       case PN533_POLL_MOD_424KBPS_FELICA:
+               return 2;
+       default:
+               return -EINVAL;
+       }
+}
+
+#define PASSIVE_DATA_LEN 5
+static int pn533_dep_link_up(struct nfc_dev *nfc_dev, struct nfc_target *target,
+                            u8 comm_mode, u8* gb, size_t gb_len)
+{
+       struct pn533 *dev = nfc_get_drvdata(nfc_dev);
+       struct pn533_cmd_jump_dep *cmd;
+       u8 cmd_len, *data_ptr;
+       u8 passive_data[PASSIVE_DATA_LEN] = {0x00, 0xff, 0xff, 0x00, 0x3};
+       int rc, baud;
+
+       nfc_dev_dbg(&dev->interface->dev, "%s", __func__);
+
+       if (dev->poll_mod_count) {
+               nfc_dev_err(&dev->interface->dev,
+                               "Cannot bring the DEP link up while polling");
+               return -EBUSY;
+       }
+
+       if (dev->tgt_active_prot) {
+               nfc_dev_err(&dev->interface->dev,
+                               "There is already an active target");
+               return -EBUSY;
+       }
+
+       baud = pn533_mod_to_baud(dev);
+       if (baud < 0) {
+               nfc_dev_err(&dev->interface->dev,
+                           "Invalid curr modulation %d", dev->poll_mod_curr);
+               return baud;
+       }
+
+       cmd_len = sizeof(struct pn533_cmd_jump_dep) + gb_len;
+       if (comm_mode == NFC_COMM_PASSIVE)
+               cmd_len += PASSIVE_DATA_LEN;
+
+       cmd = kzalloc(cmd_len, GFP_KERNEL);
+       if (cmd == NULL)
+               return -ENOMEM;
+
+       pn533_tx_frame_init(dev->out_frame, PN533_CMD_IN_JUMP_FOR_DEP);
+
+       cmd->active = !comm_mode;
+       cmd->next = 0;
+       cmd->baud = baud;
+       data_ptr = cmd->data;
+       if (comm_mode == NFC_COMM_PASSIVE && cmd->baud > 0) {
+               memcpy(data_ptr, passive_data, PASSIVE_DATA_LEN);
+               cmd->next |= 1;
+               data_ptr += PASSIVE_DATA_LEN;
+       }
+
+       if (gb != NULL && gb_len > 0) {
+               cmd->next |= 4; /* We have some Gi */
+               memcpy(data_ptr, gb, gb_len);
+       } else {
+               cmd->next = 0;
+       }
+
+       memcpy(PN533_FRAME_CMD_PARAMS_PTR(dev->out_frame), cmd, cmd_len);
+       dev->out_frame->datalen += cmd_len;
+
+       pn533_tx_frame_finish(dev->out_frame);
+
+       rc = pn533_send_cmd_frame_async(dev, dev->out_frame, dev->in_frame,
+                               dev->in_maxlen, pn533_in_dep_link_up_complete,
+                               cmd, GFP_KERNEL);
+       if (rc)
+               goto out;
+
+
+out:
+       kfree(cmd);
+
+       return rc;
+}
+
+static int pn533_dep_link_down(struct nfc_dev *nfc_dev)
+{
+       struct pn533 *dev = nfc_get_drvdata(nfc_dev);
+
+       pn533_poll_reset_mod_list(dev);
+
+       if (dev->tgt_mode || dev->tgt_active_prot) {
+               pn533_send_ack(dev, GFP_KERNEL);
+               usb_kill_urb(dev->in_urb);
+       }
+
+       dev->tgt_active_prot = 0;
+       dev->tgt_mode = 0;
+
+       skb_queue_purge(&dev->resp_q);
+
+       return 0;
+}
+
+static int pn533_build_tx_frame(struct pn533 *dev, struct sk_buff *skb,
+                               bool target)
+{
+       int payload_len = skb->len;
+       struct pn533_frame *out_frame;
+       u8 tg;
+
+       nfc_dev_dbg(&dev->interface->dev, "%s - Sending %d bytes", __func__,
+                                                               payload_len);
+
+       if (payload_len > PN533_CMD_DATAEXCH_DATA_MAXLEN) {
+               /* TODO: Implement support to multi-part data exchange */
+               nfc_dev_err(&dev->interface->dev, "Data length greater than the"
+                                               " max allowed: %d",
+                                               PN533_CMD_DATAEXCH_DATA_MAXLEN);
+               return -ENOSYS;
+       }
+
+       if (target == true) {
+               switch (dev->device_type) {
+               case PN533_DEVICE_PASORI:
+                       if (dev->tgt_active_prot == NFC_PROTO_FELICA) {
+                               skb_push(skb, PN533_CMD_DATAEXCH_HEAD_LEN - 1);
+                               out_frame = (struct pn533_frame *) skb->data;
+                               pn533_tx_frame_init(out_frame,
+                                                   PN533_CMD_IN_COMM_THRU);
+
+                               break;
+                       }
+
+               default:
+                       skb_push(skb, PN533_CMD_DATAEXCH_HEAD_LEN);
+                       out_frame = (struct pn533_frame *) skb->data;
+                       pn533_tx_frame_init(out_frame,
+                                           PN533_CMD_IN_DATA_EXCHANGE);
+                       tg = 1;
+                       memcpy(PN533_FRAME_CMD_PARAMS_PTR(out_frame),
+                              &tg, sizeof(u8));
+                       out_frame->datalen += sizeof(u8);
+
+                       break;
+               }
+
+       } else {
+               skb_push(skb, PN533_CMD_DATAEXCH_HEAD_LEN - 1);
+               out_frame = (struct pn533_frame *) skb->data;
+               pn533_tx_frame_init(out_frame, PN533_CMD_TG_SET_DATA);
+       }
+
+
+       /* The data is already in the out_frame, just update the datalen */
+       out_frame->datalen += payload_len;
+
+       pn533_tx_frame_finish(out_frame);
+       skb_put(skb, PN533_FRAME_TAIL_SIZE);
+
+       return 0;
+}
+
+struct pn533_data_exchange_arg {
+       struct sk_buff *skb_resp;
+       struct sk_buff *skb_out;
+       data_exchange_cb_t cb;
+       void *cb_context;
+};
+
+static struct sk_buff *pn533_build_response(struct pn533 *dev)
+{
+       struct sk_buff *skb, *tmp, *t;
+       unsigned int skb_len = 0, tmp_len = 0;
+
+       nfc_dev_dbg(&dev->interface->dev, "%s\n", __func__);
+
+       if (skb_queue_empty(&dev->resp_q))
+               return NULL;
+
+       if (skb_queue_len(&dev->resp_q) == 1) {
+               skb = skb_dequeue(&dev->resp_q);
+               goto out;
+       }
+
+       skb_queue_walk_safe(&dev->resp_q, tmp, t)
+               skb_len += tmp->len;
+
+       nfc_dev_dbg(&dev->interface->dev, "%s total length %d\n",
+                   __func__, skb_len);
+
+       skb = alloc_skb(skb_len, GFP_KERNEL);
+       if (skb == NULL)
+               goto out;
+
+       skb_put(skb, skb_len);
+
+       skb_queue_walk_safe(&dev->resp_q, tmp, t) {
+               memcpy(skb->data + tmp_len, tmp->data, tmp->len);
+               tmp_len += tmp->len;
+       }
+
+out:
+       skb_queue_purge(&dev->resp_q);
+
+       return skb;
+}
+
+static int pn533_data_exchange_complete(struct pn533 *dev, void *_arg,
+                                               u8 *params, int params_len)
+{
+       struct pn533_data_exchange_arg *arg = _arg;
+       struct sk_buff *skb = NULL, *skb_resp = arg->skb_resp;
+       struct pn533_frame *in_frame = (struct pn533_frame *) skb_resp->data;
+       int err = 0;
+       u8 status;
+       u8 cmd_ret;
+
+       nfc_dev_dbg(&dev->interface->dev, "%s", __func__);
+
+       dev_kfree_skb(arg->skb_out);
+
+       if (params_len < 0) { /* error */
+               err = params_len;
+               goto error;
+       }
+
+       status = params[0];
+
+       cmd_ret = status & PN533_CMD_RET_MASK;
+       if (cmd_ret != PN533_CMD_RET_SUCCESS) {
+               nfc_dev_err(&dev->interface->dev, "PN533 reported error %d when"
+                                               " exchanging data", cmd_ret);
+               err = -EIO;
+               goto error;
+       }
+
+       skb_put(skb_resp, PN533_FRAME_SIZE(in_frame));
+       skb_pull(skb_resp, PN533_CMD_DATAEXCH_HEAD_LEN);
+       skb_trim(skb_resp, skb_resp->len - PN533_FRAME_TAIL_SIZE);
+       skb_queue_tail(&dev->resp_q, skb_resp);
+
+       if (status & PN533_CMD_MI_MASK) {
+               queue_work(dev->wq, &dev->mi_work);
+               return -EINPROGRESS;
+       }
+
+       skb = pn533_build_response(dev);
+       if (skb == NULL)
+               goto error;
+
+       arg->cb(arg->cb_context, skb, 0);
+       kfree(arg);
+       return 0;
+
+error:
+       skb_queue_purge(&dev->resp_q);
+       dev_kfree_skb(skb_resp);
+       arg->cb(arg->cb_context, NULL, err);
+       kfree(arg);
+       return 0;
+}
+
+static int pn533_transceive(struct nfc_dev *nfc_dev,
+                           struct nfc_target *target, struct sk_buff *skb,
+                           data_exchange_cb_t cb, void *cb_context)
+{
+       struct pn533 *dev = nfc_get_drvdata(nfc_dev);
+       struct pn533_frame *out_frame, *in_frame;
+       struct pn533_data_exchange_arg *arg;
+       struct sk_buff *skb_resp;
+       int skb_resp_len;
+       int rc;
+
+       nfc_dev_dbg(&dev->interface->dev, "%s", __func__);
+
+       if (!dev->tgt_active_prot) {
+               nfc_dev_err(&dev->interface->dev, "Cannot exchange data if"
+                                               " there is no active target");
+               rc = -EINVAL;
+               goto error;
+       }
+
+       rc = pn533_build_tx_frame(dev, skb, true);
+       if (rc)
+               goto error;
+
+       skb_resp_len = PN533_CMD_DATAEXCH_HEAD_LEN +
+                       PN533_CMD_DATAEXCH_DATA_MAXLEN +
+                       PN533_FRAME_TAIL_SIZE;
+
+       skb_resp = nfc_alloc_recv_skb(skb_resp_len, GFP_KERNEL);
+       if (!skb_resp) {
+               rc = -ENOMEM;
+               goto error;
+       }
+
+       in_frame = (struct pn533_frame *) skb_resp->data;
+       out_frame = (struct pn533_frame *) skb->data;
+
+       arg = kmalloc(sizeof(struct pn533_data_exchange_arg), GFP_KERNEL);
+       if (!arg) {
+               rc = -ENOMEM;
+               goto free_skb_resp;
+       }
+
+       arg->skb_resp = skb_resp;
+       arg->skb_out = skb;
+       arg->cb = cb;
+       arg->cb_context = cb_context;
+
+       rc = pn533_send_cmd_frame_async(dev, out_frame, in_frame, skb_resp_len,
+                                       pn533_data_exchange_complete, arg,
+                                       GFP_KERNEL);
+       if (rc) {
+               nfc_dev_err(&dev->interface->dev, "Error %d when trying to"
+                                               " perform data_exchange", rc);
+               goto free_arg;
+       }
+
+       return 0;
+
+free_arg:
+       kfree(arg);
+free_skb_resp:
+       kfree_skb(skb_resp);
+error:
+       kfree_skb(skb);
+       return rc;
+}
+
+static int pn533_tm_send_complete(struct pn533 *dev, void *arg,
+                                 u8 *params, int params_len)
+{
+       nfc_dev_dbg(&dev->interface->dev, "%s", __func__);
+
+       if (params_len < 0) {
+               nfc_dev_err(&dev->interface->dev,
+                           "Error %d when sending data",
+                           params_len);
+
+               return params_len;
+       }
+
+       if (params_len > 0 && params[0] != 0) {
+               nfc_tm_deactivated(dev->nfc_dev);
+
+               dev->tgt_mode = 0;
+
+               return 0;
+       }
+
+       queue_work(dev->wq, &dev->tg_work);
+
+       return 0;
+}
+
+static int pn533_tm_send(struct nfc_dev *nfc_dev, struct sk_buff *skb)
+{
+       struct pn533 *dev = nfc_get_drvdata(nfc_dev);
+       struct pn533_frame *out_frame;
+       int rc;
+
+       nfc_dev_dbg(&dev->interface->dev, "%s", __func__);
+
+       rc = pn533_build_tx_frame(dev, skb, false);
+       if (rc)
+               goto error;
+
+       out_frame = (struct pn533_frame *) skb->data;
+
+       rc = pn533_send_cmd_frame_async(dev, out_frame, dev->in_frame,
+                                       dev->in_maxlen, pn533_tm_send_complete,
+                                       NULL, GFP_KERNEL);
+       if (rc) {
+               nfc_dev_err(&dev->interface->dev,
+                           "Error %d when trying to send data", rc);
+               goto error;
+       }
+
+       return 0;
+
+error:
+       kfree_skb(skb);
+
+       return rc;
+}
+
+static void pn533_wq_mi_recv(struct work_struct *work)
+{
+       struct pn533 *dev = container_of(work, struct pn533, mi_work);
+       struct sk_buff *skb_cmd;
+       struct pn533_data_exchange_arg *arg = dev->cmd_complete_arg;
+       struct pn533_frame *out_frame, *in_frame;
+       struct sk_buff *skb_resp;
+       int skb_resp_len;
+       int rc;
+
+       nfc_dev_dbg(&dev->interface->dev, "%s", __func__);
+
+       /* This is a zero payload size skb */
+       skb_cmd = alloc_skb(PN533_CMD_DATAEXCH_HEAD_LEN + PN533_FRAME_TAIL_SIZE,
+                           GFP_KERNEL);
+       if (skb_cmd == NULL)
+               goto error_cmd;
+
+       skb_reserve(skb_cmd, PN533_CMD_DATAEXCH_HEAD_LEN);
+
+       rc = pn533_build_tx_frame(dev, skb_cmd, true);
+       if (rc)
+               goto error_frame;
+
+       skb_resp_len = PN533_CMD_DATAEXCH_HEAD_LEN +
+                       PN533_CMD_DATAEXCH_DATA_MAXLEN +
+                       PN533_FRAME_TAIL_SIZE;
+       skb_resp = alloc_skb(skb_resp_len, GFP_KERNEL);
+       if (!skb_resp) {
+               rc = -ENOMEM;
+               goto error_frame;
+       }
+
+       in_frame = (struct pn533_frame *) skb_resp->data;
+       out_frame = (struct pn533_frame *) skb_cmd->data;
+
+       arg->skb_resp = skb_resp;
+       arg->skb_out = skb_cmd;
+
+       rc = __pn533_send_cmd_frame_async(dev, out_frame, in_frame,
+                                         skb_resp_len,
+                                         pn533_data_exchange_complete,
+                                         dev->cmd_complete_arg, GFP_KERNEL);
+       if (!rc)
+               return;
+
+       nfc_dev_err(&dev->interface->dev, "Error %d when trying to"
+                                               " perform data_exchange", rc);
+
+       kfree_skb(skb_resp);
+
+error_frame:
+       kfree_skb(skb_cmd);
+
+error_cmd:
+       pn533_send_ack(dev, GFP_KERNEL);
+
+       kfree(arg);
+
+       mutex_unlock(&dev->cmd_lock);
+}
+
+static int pn533_set_configuration(struct pn533 *dev, u8 cfgitem, u8 *cfgdata,
+                                                               u8 cfgdata_len)
+{
+       int rc;
+       u8 *params;
+
+       nfc_dev_dbg(&dev->interface->dev, "%s", __func__);
+
+       pn533_tx_frame_init(dev->out_frame, PN533_CMD_RF_CONFIGURATION);
+
+       params = PN533_FRAME_CMD_PARAMS_PTR(dev->out_frame);
+       params[0] = cfgitem;
+       memcpy(&params[1], cfgdata, cfgdata_len);
+       dev->out_frame->datalen += (1 + cfgdata_len);
+
+       pn533_tx_frame_finish(dev->out_frame);
+
+       rc = pn533_send_cmd_frame_sync(dev, dev->out_frame, dev->in_frame,
+                                                               dev->in_maxlen);
+
+       return rc;
+}
+
+static int pn533_fw_reset(struct pn533 *dev)
+{
+       int rc;
+       u8 *params;
+
+       nfc_dev_dbg(&dev->interface->dev, "%s", __func__);
+
+       pn533_tx_frame_init(dev->out_frame, 0x18);
+
+       params = PN533_FRAME_CMD_PARAMS_PTR(dev->out_frame);
+       params[0] = 0x1;
+       dev->out_frame->datalen += 1;
+
+       pn533_tx_frame_finish(dev->out_frame);
+
+       rc = pn533_send_cmd_frame_sync(dev, dev->out_frame, dev->in_frame,
+                                      dev->in_maxlen);
+
+       return rc;
+}
+
+static struct nfc_ops pn533_nfc_ops = {
+       .dev_up = NULL,
+       .dev_down = NULL,
+       .dep_link_up = pn533_dep_link_up,
+       .dep_link_down = pn533_dep_link_down,
+       .start_poll = pn533_start_poll,
+       .stop_poll = pn533_stop_poll,
+       .activate_target = pn533_activate_target,
+       .deactivate_target = pn533_deactivate_target,
+       .im_transceive = pn533_transceive,
+       .tm_send = pn533_tm_send,
+};
+
+static int pn533_setup(struct pn533 *dev)
+{
+       struct pn533_config_max_retries max_retries;
+       struct pn533_config_timing timing;
+       u8 pasori_cfg[3] = {0x08, 0x01, 0x08};
+       int rc;
+
+       switch (dev->device_type) {
+       case PN533_DEVICE_STD:
+               max_retries.mx_rty_atr = PN533_CONFIG_MAX_RETRIES_ENDLESS;
+               max_retries.mx_rty_psl = 2;
+               max_retries.mx_rty_passive_act =
+                       PN533_CONFIG_MAX_RETRIES_NO_RETRY;
+
+               timing.rfu = PN533_CONFIG_TIMING_102;
+               timing.atr_res_timeout = PN533_CONFIG_TIMING_204;
+               timing.dep_timeout = PN533_CONFIG_TIMING_409;
+
+               break;
+
+       case PN533_DEVICE_PASORI:
+               max_retries.mx_rty_atr = 0x2;
+               max_retries.mx_rty_psl = 0x1;
+               max_retries.mx_rty_passive_act =
+                       PN533_CONFIG_MAX_RETRIES_NO_RETRY;
+
+               timing.rfu = PN533_CONFIG_TIMING_102;
+               timing.atr_res_timeout = PN533_CONFIG_TIMING_102;
+               timing.dep_timeout = PN533_CONFIG_TIMING_204;
+
+               break;
+
+       default:
+               nfc_dev_err(&dev->interface->dev, "Unknown device type %d\n",
+                           dev->device_type);
+               return -EINVAL;
+       }
+
+       rc = pn533_set_configuration(dev, PN533_CFGITEM_MAX_RETRIES,
+                                    (u8 *)&max_retries, sizeof(max_retries));
+       if (rc) {
+               nfc_dev_err(&dev->interface->dev,
+                           "Error on setting MAX_RETRIES config");
+               return rc;
+       }
+
+
+       rc = pn533_set_configuration(dev, PN533_CFGITEM_TIMING,
+                                    (u8 *)&timing, sizeof(timing));
+       if (rc) {
+               nfc_dev_err(&dev->interface->dev,
+                           "Error on setting RF timings");
+               return rc;
+       }
+
+       switch (dev->device_type) {
+       case PN533_DEVICE_STD:
+               break;
+
+       case PN533_DEVICE_PASORI:
+               pn533_fw_reset(dev);
+
+               rc = pn533_set_configuration(dev, PN533_CFGITEM_PASORI,
+                                            pasori_cfg, 3);
+               if (rc) {
+                       nfc_dev_err(&dev->interface->dev,
+                                   "Error while settings PASORI config");
+                       return rc;
+               }
+
+               pn533_fw_reset(dev);
+
+               break;
+       }
+
+       return 0;
+}
+
+static int pn533_probe(struct usb_interface *interface,
+                       const struct usb_device_id *id)
+{
+       struct pn533_fw_version *fw_ver;
+       struct pn533 *dev;
+       struct usb_host_interface *iface_desc;
+       struct usb_endpoint_descriptor *endpoint;
+       int in_endpoint = 0;
+       int out_endpoint = 0;
+       int rc = -ENOMEM;
+       int i;
+       u32 protocols;
+
+       dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+       if (!dev)
+               return -ENOMEM;
+
+       dev->udev = usb_get_dev(interface_to_usbdev(interface));
+       dev->interface = interface;
+       mutex_init(&dev->cmd_lock);
+
+       iface_desc = interface->cur_altsetting;
+       for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
+               endpoint = &iface_desc->endpoint[i].desc;
+
+               if (!in_endpoint && usb_endpoint_is_bulk_in(endpoint)) {
+                       dev->in_maxlen = le16_to_cpu(endpoint->wMaxPacketSize);
+                       in_endpoint = endpoint->bEndpointAddress;
+               }
+
+               if (!out_endpoint && usb_endpoint_is_bulk_out(endpoint)) {
+                       dev->out_maxlen =
+                               le16_to_cpu(endpoint->wMaxPacketSize);
+                       out_endpoint = endpoint->bEndpointAddress;
+               }
+       }
+
+       if (!in_endpoint || !out_endpoint) {
+               nfc_dev_err(&interface->dev, "Could not find bulk-in or"
+                                                       " bulk-out endpoint");
+               rc = -ENODEV;
+               goto error;
+       }
+
+       dev->in_frame = kmalloc(dev->in_maxlen, GFP_KERNEL);
+       dev->in_urb = usb_alloc_urb(0, GFP_KERNEL);
+       dev->out_frame = kmalloc(dev->out_maxlen, GFP_KERNEL);
+       dev->out_urb = usb_alloc_urb(0, GFP_KERNEL);
+
+       if (!dev->in_frame || !dev->out_frame ||
+               !dev->in_urb || !dev->out_urb)
+               goto error;
+
+       usb_fill_bulk_urb(dev->in_urb, dev->udev,
+                       usb_rcvbulkpipe(dev->udev, in_endpoint),
+                       NULL, 0, NULL, dev);
+       usb_fill_bulk_urb(dev->out_urb, dev->udev,
+                       usb_sndbulkpipe(dev->udev, out_endpoint),
+                       NULL, 0,
+                       pn533_send_complete, dev);
+
+       INIT_WORK(&dev->cmd_work, pn533_wq_cmd_complete);
+       INIT_WORK(&dev->mi_work, pn533_wq_mi_recv);
+       INIT_WORK(&dev->tg_work, pn533_wq_tg_get_data);
+       INIT_WORK(&dev->poll_work, pn533_wq_poll);
+       dev->wq = alloc_workqueue("pn533",
+                                 WQ_NON_REENTRANT | WQ_UNBOUND | WQ_MEM_RECLAIM,
+                                 1);
+       if (dev->wq == NULL)
+               goto error;
+
+       init_timer(&dev->listen_timer);
+       dev->listen_timer.data = (unsigned long) dev;
+       dev->listen_timer.function = pn533_listen_mode_timer;
+
+       skb_queue_head_init(&dev->resp_q);
+
+       usb_set_intfdata(interface, dev);
+
+       pn533_tx_frame_init(dev->out_frame, PN533_CMD_GET_FIRMWARE_VERSION);
+       pn533_tx_frame_finish(dev->out_frame);
+
+       rc = pn533_send_cmd_frame_sync(dev, dev->out_frame, dev->in_frame,
+                                                               dev->in_maxlen);
+       if (rc)
+               goto destroy_wq;
+
+       fw_ver = (struct pn533_fw_version *)
+                               PN533_FRAME_CMD_PARAMS_PTR(dev->in_frame);
+       nfc_dev_info(&dev->interface->dev, "NXP PN533 firmware ver %d.%d now"
+                                       " attached", fw_ver->ver, fw_ver->rev);
+
+       dev->device_type = id->driver_info;
+       switch (dev->device_type) {
+       case PN533_DEVICE_STD:
+               protocols = PN533_ALL_PROTOCOLS;
+               break;
+
+       case PN533_DEVICE_PASORI:
+               protocols = PN533_NO_TYPE_B_PROTOCOLS;
+               break;
+
+       default:
+               nfc_dev_err(&dev->interface->dev, "Unknown device type %d\n",
+                           dev->device_type);
+               rc = -EINVAL;
+               goto destroy_wq;
+       }
+
+       dev->nfc_dev = nfc_allocate_device(&pn533_nfc_ops, protocols,
+                                          PN533_CMD_DATAEXCH_HEAD_LEN,
+                                          PN533_FRAME_TAIL_SIZE);
+       if (!dev->nfc_dev)
+               goto destroy_wq;
+
+       nfc_set_parent_dev(dev->nfc_dev, &interface->dev);
+       nfc_set_drvdata(dev->nfc_dev, dev);
+
+       rc = nfc_register_device(dev->nfc_dev);
+       if (rc)
+               goto free_nfc_dev;
+
+       rc = pn533_setup(dev);
+       if (rc)
+               goto unregister_nfc_dev;
+
+       return 0;
+
+unregister_nfc_dev:
+       nfc_unregister_device(dev->nfc_dev);
+
+free_nfc_dev:
+       nfc_free_device(dev->nfc_dev);
+
+destroy_wq:
+       destroy_workqueue(dev->wq);
+error:
+       kfree(dev->in_frame);
+       usb_free_urb(dev->in_urb);
+       kfree(dev->out_frame);
+       usb_free_urb(dev->out_urb);
+       kfree(dev);
+       return rc;
+}
+
+static void pn533_disconnect(struct usb_interface *interface)
+{
+       struct pn533 *dev;
+
+       dev = usb_get_intfdata(interface);
+       usb_set_intfdata(interface, NULL);
+
+       nfc_unregister_device(dev->nfc_dev);
+       nfc_free_device(dev->nfc_dev);
+
+       usb_kill_urb(dev->in_urb);
+       usb_kill_urb(dev->out_urb);
+
+       destroy_workqueue(dev->wq);
+
+       skb_queue_purge(&dev->resp_q);
+
+       del_timer(&dev->listen_timer);
+
+       kfree(dev->in_frame);
+       usb_free_urb(dev->in_urb);
+       kfree(dev->out_frame);
+       usb_free_urb(dev->out_urb);
+       kfree(dev);
+
+       nfc_dev_info(&interface->dev, "NXP PN533 NFC device disconnected");
+}
+
+static struct usb_driver pn533_driver = {
+       .name =         "pn533",
+       .probe =        pn533_probe,
+       .disconnect =   pn533_disconnect,
+       .id_table =     pn533_table,
+};
+
+module_usb_driver(pn533_driver);
+
+MODULE_AUTHOR("Lauro Ramos Venancio <lauro.venancio@openbossa.org>,"
+                       " Aloisio Almeida Jr <aloisio.almeida@openbossa.org>");
+MODULE_DESCRIPTION("PN533 usb driver ver " VERSION);
+MODULE_VERSION(VERSION);
+MODULE_LICENSE("GPL");
index 7177a98..724f65d 100644 (file)
 /*
- * Copyright (C) 2010 Trusted Logic S.A.
+ * Driver for the PN544 NFC chip.
  *
- * 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
- * (at your option) any later version.
+ * Copyright (C) Nokia Corporation
+ *
+ * Author: Jari Vanhala <ext-jari.vanhala@nokia.com>
+ * Contact: Matti Aaltonen <matti.j.aaltonen@nokia.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
  *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.        See the
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
  * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
- *
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  */
-#define DEBUG
 
-#include <linux/kernel.h>
-#include <linux/module.h>
-#include <linux/fs.h>
-#include <linux/slab.h>
-#include <linux/init.h>
-#include <linux/list.h>
-#include <linux/i2c.h>
-#include <linux/irq.h>
-#include <linux/jiffies.h>
-#include <linux/uaccess.h>
+#include <linux/completion.h>
+#include <linux/crc-ccitt.h>
 #include <linux/delay.h>
 #include <linux/interrupt.h>
-#include <linux/io.h>
-#include <linux/platform_device.h>
-#include <linux/gpio.h>
+#include <linux/kernel.h>
 #include <linux/miscdevice.h>
-#include <linux/spinlock.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
 #include <linux/nfc/pn544.h>
-#include <linux/suspend.h>
-#include <linux/wakelock.h>
+#include <linux/poll.h>
+#include <linux/regulator/consumer.h>
+#include <linux/serial_core.h> /* for TCGETS */
+#include <linux/slab.h>
 
-#define MAX_BUFFER_SIZE                512
+#define DRIVER_CARD    "PN544 NFC"
+#define DRIVER_DESC    "NFC driver for PN544"
 
-#if defined(CONFIG_I2C_DESIGNWARE_PCI_SPLIT_XFER)
-#define MAX_I2C_XFER_SIZE      31
-#endif
+static struct i2c_device_id pn544_id_table[] = {
+       { PN544_DRIVER_NAME, 0 },
+       { }
+};
+MODULE_DEVICE_TABLE(i2c, pn544_id_table);
 
-struct pn544_dev       {
-       wait_queue_head_t       read_wq;
-       struct i2c_client       *client;
-       struct miscdevice       pn544_device;
-       struct wake_lock        read_wake;
-       unsigned int            ven_gpio;
-       unsigned int            firm_gpio;
-       unsigned int            irq_gpio;
-       unsigned int            nfc_enable;
-       int                     busy;
+#define HCI_MODE       0
+#define FW_MODE                1
+
+enum pn544_state {
+       PN544_ST_COLD,
+       PN544_ST_FW_READY,
+       PN544_ST_READY,
 };
 
-static int pn544_platform_init(struct pn544_dev *pn544_dev)
+enum pn544_irq {
+       PN544_NONE,
+       PN544_INT,
+};
+
+struct pn544_info {
+       struct miscdevice miscdev;
+       struct i2c_client *i2c_dev;
+       struct regulator_bulk_data regs[3];
+
+       enum pn544_state state;
+       wait_queue_head_t read_wait;
+       loff_t read_offset;
+       enum pn544_irq read_irq;
+       struct mutex read_mutex; /* Serialize read_irq access */
+       struct mutex mutex; /* Serialize info struct access */
+       u8 *buf;
+       size_t buflen;
+};
+
+static const char reg_vdd_io[] = "Vdd_IO";
+static const char reg_vbat[]   = "VBat";
+static const char reg_vsim[]   = "VSim";
+
+/* sysfs interface */
+static ssize_t pn544_test(struct device *dev,
+                         struct device_attribute *attr, char *buf)
 {
-       int polarity, retry, ret;
-       char rset_cmd[] = {0x05, 0xF9, 0x04, 0x00, 0xC3, 0xE5};
-       int count = sizeof(rset_cmd);
-
-       pr_info("%s : detecting nfc_en polarity\n", __func__);
-
-       /* disable fw download */
-       gpio_set_value(pn544_dev->firm_gpio, 0);
-
-       for (polarity = 0; polarity < 2; polarity++) {
-               pn544_dev->nfc_enable = polarity;
-               retry = 3;
-               while (retry--) {
-                       /* power off */
-                       gpio_set_value(pn544_dev->ven_gpio,
-                                       !pn544_dev->nfc_enable);
-                       msleep(10);
-                       /* power on */
-                       gpio_set_value(pn544_dev->ven_gpio,
-                                       pn544_dev->nfc_enable);
-                       msleep(10);
-                       /* send reset */
-                       pr_debug("%s : sending reset cmd\n", __func__);
-                       ret = i2c_master_send(pn544_dev->client,
-                                       rset_cmd, count);
-                       if (ret == count) {
-                               pr_info("%s : nfc_en polarity : active %s\n",
-                                       __func__,
-                                       (polarity == 0 ? "low" : "high"));
-                               goto out;
-                       }
-               }
-       }
+       struct pn544_info *info = dev_get_drvdata(dev);
+       struct i2c_client *client = info->i2c_dev;
+       struct pn544_nfc_platform_data *pdata = client->dev.platform_data;
 
-       pr_err("%s : could not detect nfc_en polarity, fallback to active high\n",
-                       __func__);
-out:
-       /* power off */
-       gpio_set_value(pn544_dev->ven_gpio,
-                       !pn544_dev->nfc_enable);
-#ifndef HAVE_UNLOCKED_IOCTL
-       pr_err("%s: must have IOCTL", __func__);
-       return -ENODEV;
-#endif
-       return 0;
+       return snprintf(buf, PAGE_SIZE, "%d\n", pdata->test());
 }
 
-static irqreturn_t pn544_dev_irq_handler(int irq, void *dev_id)
+static int pn544_enable(struct pn544_info *info, int mode)
 {
-       struct pn544_dev *pn544_dev = dev_id;
+       struct pn544_nfc_platform_data *pdata;
+       struct i2c_client *client = info->i2c_dev;
 
-       pr_debug("%s : IRQ ENTER\n", __func__);
+       int r;
 
-#ifdef CONFIG_HAS_WAKELOCK
-       wake_lock_timeout(&pn544_dev->read_wake, 1*HZ);
-#else
-       pm_wakeup_event(&pn544_dev->client->dev, jiffies_to_msecs(1*HZ));
-#endif
+       r = regulator_bulk_enable(ARRAY_SIZE(info->regs), info->regs);
+       if (r < 0)
+               return r;
 
-       /* Wake up waiting readers */
-       wake_up(&pn544_dev->read_wq);
+       pdata = client->dev.platform_data;
+       info->read_irq = PN544_NONE;
+       if (pdata->enable)
+               pdata->enable(mode);
 
-       return IRQ_HANDLED;
+       if (mode) {
+               info->state = PN544_ST_FW_READY;
+               dev_dbg(&client->dev, "now in FW-mode\n");
+       } else {
+               info->state = PN544_ST_READY;
+               dev_dbg(&client->dev, "now in HCI-mode\n");
+       }
+
+       usleep_range(10000, 15000);
+
+       return 0;
 }
 
-static ssize_t pn544_dev_read(struct file *filp, char __user *buf,
-               size_t count, loff_t *offset)
+static void pn544_disable(struct pn544_info *info)
 {
-       struct pn544_dev *pn544_dev = filp->private_data;
-       char tmp[MAX_BUFFER_SIZE];
-       int ret;
-#if defined(CONFIG_I2C_DESIGNWARE_PCI_SPLIT_XFER)
-       char *tmp_p = tmp;
-       int i2c_xfer_size;
-       int i2c_xfer_ret;
-#endif
+       struct pn544_nfc_platform_data *pdata;
+       struct i2c_client *client = info->i2c_dev;
 
-       if (count > MAX_BUFFER_SIZE)
-               count = MAX_BUFFER_SIZE;
+       pdata = client->dev.platform_data;
+       if (pdata->disable)
+               pdata->disable();
 
-       pr_debug("%s : reading %zu bytes.\n", __func__, count);
+       info->state = PN544_ST_COLD;
 
-       if (!gpio_get_value(pn544_dev->irq_gpio)) {
-               if (filp->f_flags & O_NONBLOCK) {
-                       ret = -EAGAIN;
-                       goto fail;
-               }
+       dev_dbg(&client->dev, "Now in OFF-mode\n");
 
-               ret = wait_event_interruptible(pn544_dev->read_wq,
-                               gpio_get_value(pn544_dev->irq_gpio));
+       msleep(PN544_RESETVEN_TIME);
 
-               if (ret) {
-                       pr_err("%s : wait_event_interruptible: %d",
-                                       __func__, ret);
-                       goto fail;
-               }
+       info->read_irq = PN544_NONE;
+       regulator_bulk_disable(ARRAY_SIZE(info->regs), info->regs);
+}
+
+static int check_crc(u8 *buf, int buflen)
+{
+       u8 len;
+       u16 crc;
+
+       len = buf[0] + 1;
+       if (len < 4 || len != buflen || len > PN544_MSG_MAX_SIZE) {
+               pr_err(PN544_DRIVER_NAME
+                      ": CRC; corrupt packet len %u (%d)\n", len, buflen);
+               print_hex_dump(KERN_DEBUG, "crc: ", DUMP_PREFIX_NONE,
+                              16, 2, buf, buflen, false);
+               return -EPERM;
        }
+       crc = crc_ccitt(0xffff, buf, len - 2);
+       crc = ~crc;
 
-       /* Read data */
-#if !defined(CONFIG_I2C_DESIGNWARE_PCI_SPLIT_XFER)
-       ret = i2c_master_recv(pn544_dev->client, tmp, count);
+       if (buf[len-2] != (crc & 0xff) || buf[len-1] != (crc >> 8)) {
+               pr_err(PN544_DRIVER_NAME ": CRC error 0x%x != 0x%x 0x%x\n",
+                      crc, buf[len-1], buf[len-2]);
 
-       if (ret < 0) {
-               pr_err("%s: i2c_master_recv returned %d\n", __func__, ret);
-               return ret;
-       }
-       if (ret > count) {
-               pr_err("%s: received too many bytes from i2c (%d)\n",
-                       __func__, ret);
-               return -EIO;
+               print_hex_dump(KERN_DEBUG, "crc: ", DUMP_PREFIX_NONE,
+                              16, 2, buf, buflen, false);
+               return -EPERM;
        }
-#else
-       ret = count;
-
-       while (count) {
-               i2c_xfer_size = count;
-               if (i2c_xfer_size > MAX_I2C_XFER_SIZE)
-                       i2c_xfer_size = MAX_I2C_XFER_SIZE;
-
-               i2c_xfer_ret = i2c_master_recv(pn544_dev->client,
-                               tmp_p, i2c_xfer_size);
-               if (i2c_xfer_ret < 0) {
-                       pr_err("%s: i2c_master_recv returned %d\n",
-                                       __func__, i2c_xfer_ret);
-                       return i2c_xfer_ret;
-               }
-               if (i2c_xfer_ret > i2c_xfer_size) {
-                       pr_err("%s: received too many bytes from i2c (%d)\n",
-                                       __func__, i2c_xfer_ret);
-                       return -EIO;
-               }
+       return 0;
+}
 
-               count -= i2c_xfer_size;
-               tmp_p += i2c_xfer_size;
-       }
-#endif
-       if (copy_to_user(buf, tmp, ret)) {
-               pr_warning("%s : failed to copy to user space\n", __func__);
-               return -EFAULT;
+static int pn544_i2c_write(struct i2c_client *client, u8 *buf, int len)
+{
+       int r;
+
+       if (len < 4 || len != (buf[0] + 1)) {
+               dev_err(&client->dev, "%s: Illegal message length: %d\n",
+                       __func__, len);
+               return -EINVAL;
        }
 
-       /* Handle the corner case where read cycle is broken */
-       if (ret == 1 && pn544_dev->busy) {
-               pn544_dev->busy = 0;
-#ifdef CONFIG_HAS_WAKELOCK
-               wake_unlock(&pn544_dev->read_wake);
-#else
-               pm_relax(&pn544_dev->client->dev);
-#endif
-               return ret;
+       if (check_crc(buf, len))
+               return -EINVAL;
+
+       usleep_range(3000, 6000);
+
+       r = i2c_master_send(client, buf, len);
+       dev_dbg(&client->dev, "send: %d\n", r);
+
+       if (r == -EREMOTEIO) { /* Retry, chip was in standby */
+               usleep_range(6000, 10000);
+               r = i2c_master_send(client, buf, len);
+               dev_dbg(&client->dev, "send2: %d\n", r);
        }
 
+       if (r != len)
+               return -EREMOTEIO;
+
+       return r;
+}
+
+static int pn544_i2c_read(struct i2c_client *client, u8 *buf, int buflen)
+{
+       int r;
+       u8 len;
+
        /*
-        * PN544 read cycle consists of reading 1 byte containing the size of
-        * the data to be transferred then reading the requested data. During
-        * a read cycle, the platform shall not enter in s2ram, otherwise,
-        * the pn544 will wait forever for the data to be read and no interrupt
-        * is generated to the host even when a new field is detected. To avoid
-        * such situation, read_lock shall be held during a read_cycle and if
-        * the suspend happens during this period, then abort the suspend.
+        * You could read a packet in one go, but then you'd need to read
+        * max size and rest would be 0xff fill, so we do split reads.
         */
-       if (ret == 1) {
-#ifdef CONFIG_HAS_WAKELOCK
-               wake_lock(&pn544_dev->read_wake);
-#else
-               pm_stay_awake(&pn544_dev->client->dev);
-#endif
-               pn544_dev->busy = 1;
-       } else {
-#ifdef CONFIG_HAS_WAKELOCK
-               wake_unlock(&pn544_dev->read_wake);
-#else
-               pm_relax(&pn544_dev->client->dev);
-#endif
-               pn544_dev->busy = 0;
+       r = i2c_master_recv(client, &len, 1);
+       dev_dbg(&client->dev, "recv1: %d\n", r);
 
-               /* Prevent the suspend after each read cycle for 1 sec
-                * to allow propagation of the event to upper layers of NFC
-                * stack
-                */
-#ifdef CONFIG_HAS_WAKELOCK
-               wake_lock_timeout(&pn544_dev->read_wake, 1*HZ);
-#else
-               pm_wakeup_event(&pn544_dev->client->dev, jiffies_to_msecs(1*HZ));
-#endif
+       if (r != 1)
+               return -EREMOTEIO;
+
+       if (len < PN544_LLC_HCI_OVERHEAD)
+               len = PN544_LLC_HCI_OVERHEAD;
+       else if (len > (PN544_MSG_MAX_SIZE - 1))
+               len = PN544_MSG_MAX_SIZE - 1;
+
+       if (1 + len > buflen) /* len+(data+crc16) */
+               return -EMSGSIZE;
+
+       buf[0] = len;
+
+       r = i2c_master_recv(client, buf + 1, len);
+       dev_dbg(&client->dev, "recv2: %d\n", r);
+
+       if (r != len)
+               return -EREMOTEIO;
+
+       usleep_range(3000, 6000);
+
+       return r + 1;
+}
+
+static int pn544_fw_write(struct i2c_client *client, u8 *buf, int len)
+{
+       int r;
+
+       dev_dbg(&client->dev, "%s\n", __func__);
+
+       if (len < PN544_FW_HEADER_SIZE ||
+           (PN544_FW_HEADER_SIZE + (buf[1] << 8) + buf[2]) != len)
+               return -EINVAL;
+
+       r = i2c_master_send(client, buf, len);
+       dev_dbg(&client->dev, "fw send: %d\n", r);
+
+       if (r == -EREMOTEIO) { /* Retry, chip was in standby */
+               usleep_range(6000, 10000);
+               r = i2c_master_send(client, buf, len);
+               dev_dbg(&client->dev, "fw send2: %d\n", r);
        }
 
-       /* Return the number of bytes read */
-       pr_debug("%s : Bytes read = %d: ", __func__, ret);
-       return ret;
+       if (r != len)
+               return -EREMOTEIO;
 
-fail:
-       pr_debug("%s : wait_event is interrupted by a signal\n",
-               __func__);
-       return ret;
+       return r;
 }
 
-static ssize_t pn544_dev_write(struct file *filp, const char __user *buf,
-               size_t count, loff_t *offset)
+static int pn544_fw_read(struct i2c_client *client, u8 *buf, int buflen)
 {
-       struct pn544_dev  *pn544_dev;
-       char tmp[MAX_BUFFER_SIZE];
-       int ret;
-#if defined(CONFIG_I2C_DESIGNWARE_PCI_SPLIT_XFER)
-       char *tmp_p = tmp;
-       int i2c_xfer_size;
-       int i2c_xfer_ret;
-#endif
+       int r, len;
+
+       if (buflen < PN544_FW_HEADER_SIZE)
+               return -EINVAL;
+
+       r = i2c_master_recv(client, buf, PN544_FW_HEADER_SIZE);
+       dev_dbg(&client->dev, "FW recv1: %d\n", r);
+
+       if (r < 0)
+               return r;
+
+       if (r < PN544_FW_HEADER_SIZE)
+               return -EINVAL;
+
+       len = (buf[1] << 8) + buf[2];
+       if (len == 0) /* just header, no additional data */
+               return r;
+
+       if (len > buflen - PN544_FW_HEADER_SIZE)
+               return -EMSGSIZE;
+
+       r = i2c_master_recv(client, buf + PN544_FW_HEADER_SIZE, len);
+       dev_dbg(&client->dev, "fw recv2: %d\n", r);
+
+       if (r != len)
+               return -EINVAL;
+
+       return r + PN544_FW_HEADER_SIZE;
+}
+
+static irqreturn_t pn544_irq_thread_fn(int irq, void *dev_id)
+{
+       struct pn544_info *info = dev_id;
+       struct i2c_client *client = info->i2c_dev;
+
+       BUG_ON(!info);
+       BUG_ON(irq != info->i2c_dev->irq);
+
+       dev_dbg(&client->dev, "IRQ\n");
+
+       mutex_lock(&info->read_mutex);
+       info->read_irq = PN544_INT;
+       mutex_unlock(&info->read_mutex);
+
+       wake_up_interruptible(&info->read_wait);
+
+       return IRQ_HANDLED;
+}
+
+static enum pn544_irq pn544_irq_state(struct pn544_info *info)
+{
+       enum pn544_irq irq;
+
+       mutex_lock(&info->read_mutex);
+       irq = info->read_irq;
+       mutex_unlock(&info->read_mutex);
+       /*
+        * XXX: should we check GPIO-line status directly?
+        * return pdata->irq_status() ? PN544_INT : PN544_NONE;
+        */
 
-       pn544_dev = filp->private_data;
+       return irq;
+}
+
+static ssize_t pn544_read(struct file *file, char __user *buf,
+                         size_t count, loff_t *offset)
+{
+       struct pn544_info *info = container_of(file->private_data,
+                                              struct pn544_info, miscdev);
+       struct i2c_client *client = info->i2c_dev;
+       enum pn544_irq irq;
+       size_t len;
+       int r = 0;
 
-       if (count > MAX_BUFFER_SIZE)
-               count = MAX_BUFFER_SIZE;
+       dev_dbg(&client->dev, "%s: info: %p, count: %zu\n", __func__,
+               info, count);
 
-       if (copy_from_user(tmp, buf, count)) {
-               pr_err("%s : failed to copy from user space\n", __func__);
-               return -EFAULT;
+       mutex_lock(&info->mutex);
+
+       if (info->state == PN544_ST_COLD) {
+               r = -ENODEV;
+               goto out;
        }
 
-       pr_debug("%s : writing %zu bytes.\n", __func__, count);
+       irq = pn544_irq_state(info);
+       if (irq == PN544_NONE) {
+               if (file->f_flags & O_NONBLOCK) {
+                       r = -EAGAIN;
+                       goto out;
+               }
 
-       /* Write data */
-#if !defined(CONFIG_I2C_DESIGNWARE_PCI_SPLIT_XFER)
-       ret = i2c_master_send(pn544_dev->client, tmp, count);
-       if (ret != count) {
-               pr_err("%s : i2c_master_send returned %d\n",
-                       __func__, ret);
-               ret = -EIO;
+               if (wait_event_interruptible(info->read_wait,
+                                            (info->read_irq == PN544_INT))) {
+                       r = -ERESTARTSYS;
+                       goto out;
+               }
        }
-#else
-       ret = count;
-
-       while (count) {
-               i2c_xfer_size = count;
-               if (i2c_xfer_size > MAX_I2C_XFER_SIZE)
-                       i2c_xfer_size = MAX_I2C_XFER_SIZE;
-
-               i2c_xfer_ret = i2c_master_send(pn544_dev->client,
-                               tmp_p, i2c_xfer_size);
-               if (i2c_xfer_ret != i2c_xfer_size) {
-                       pr_err("%s : i2c_master_send returned %d\n",
-                                       __func__, i2c_xfer_ret);
-                       return -EIO;
+
+       if (info->state == PN544_ST_FW_READY) {
+               len = min(count, info->buflen);
+
+               mutex_lock(&info->read_mutex);
+               r = pn544_fw_read(info->i2c_dev, info->buf, len);
+               info->read_irq = PN544_NONE;
+               mutex_unlock(&info->read_mutex);
+
+               if (r < 0) {
+                       dev_err(&info->i2c_dev->dev, "FW read failed: %d\n", r);
+                       goto out;
+               }
+
+               print_hex_dump(KERN_DEBUG, "FW read: ", DUMP_PREFIX_NONE,
+                              16, 2, info->buf, r, false);
+
+               *offset += r;
+               if (copy_to_user(buf, info->buf, r)) {
+                       r = -EFAULT;
+                       goto out;
+               }
+       } else {
+               len = min(count, info->buflen);
+
+               mutex_lock(&info->read_mutex);
+               r = pn544_i2c_read(info->i2c_dev, info->buf, len);
+               info->read_irq = PN544_NONE;
+               mutex_unlock(&info->read_mutex);
+
+               if (r < 0) {
+                       dev_err(&info->i2c_dev->dev, "read failed (%d)\n", r);
+                       goto out;
                }
+               print_hex_dump(KERN_DEBUG, "read: ", DUMP_PREFIX_NONE,
+                              16, 2, info->buf, r, false);
 
-               count -= i2c_xfer_size;
-               tmp_p += i2c_xfer_size;
+               *offset += r;
+               if (copy_to_user(buf, info->buf, r)) {
+                       r = -EFAULT;
+                       goto out;
+               }
        }
-#endif
-       return ret;
+
+out:
+       mutex_unlock(&info->mutex);
+
+       return r;
 }
 
-static int pn544_dev_open(struct inode *inode, struct file *filp)
+static unsigned int pn544_poll(struct file *file, poll_table *wait)
 {
-       struct pn544_dev *pn544_dev = container_of(filp->private_data,
-                                               struct pn544_dev,
-                                               pn544_device);
+       struct pn544_info *info = container_of(file->private_data,
+                                              struct pn544_info, miscdev);
+       struct i2c_client *client = info->i2c_dev;
+       int r = 0;
 
-       filp->private_data = pn544_dev;
+       dev_dbg(&client->dev, "%s: info: %p\n", __func__, info);
 
-       pr_debug("%s : %d,%d\n", __func__, imajor(inode), iminor(inode));
+       mutex_lock(&info->mutex);
 
-       return 0;
+       if (info->state == PN544_ST_COLD) {
+               r = -ENODEV;
+               goto out;
+       }
+
+       poll_wait(file, &info->read_wait, wait);
+
+       if (pn544_irq_state(info) == PN544_INT) {
+               r = POLLIN | POLLRDNORM;
+               goto out;
+       }
+out:
+       mutex_unlock(&info->mutex);
+
+       return r;
 }
 
-static int pn544_dev_release(struct inode *inode, struct file *filp)
+static ssize_t pn544_write(struct file *file, const char __user *buf,
+                          size_t count, loff_t *ppos)
 {
-       struct pn544_dev *pn544_dev = filp->private_data;
+       struct pn544_info *info = container_of(file->private_data,
+                                              struct pn544_info, miscdev);
+       struct i2c_client *client = info->i2c_dev;
+       ssize_t len;
+       int r;
 
-       filp->private_data = NULL;
+       dev_dbg(&client->dev, "%s: info: %p, count %zu\n", __func__,
+               info, count);
 
-#ifdef CONFIG_HAS_WAKELOCK
-       if (wake_lock_active(&pn544_dev->read_wake))
-               wake_unlock(&pn544_dev->read_wake);
-#else
-       pm_relax(&pn544_dev->client->dev);
-#endif
+       mutex_lock(&info->mutex);
+
+       if (info->state == PN544_ST_COLD) {
+               r = -ENODEV;
+               goto out;
+       }
+
+       /*
+        * XXX: should we detect rset-writes and clean possible
+        * read_irq state
+        */
+       if (info->state == PN544_ST_FW_READY) {
+               size_t fw_len;
+
+               if (count < PN544_FW_HEADER_SIZE) {
+                       r = -EINVAL;
+                       goto out;
+               }
+
+               len = min(count, info->buflen);
+               if (copy_from_user(info->buf, buf, len)) {
+                       r = -EFAULT;
+                       goto out;
+               }
+
+               print_hex_dump(KERN_DEBUG, "FW write: ", DUMP_PREFIX_NONE,
+                              16, 2, info->buf, len, false);
+
+               fw_len = PN544_FW_HEADER_SIZE + (info->buf[1] << 8) +
+                       info->buf[2];
+
+               if (len > fw_len) /* 1 msg at a time */
+                       len = fw_len;
+
+               r = pn544_fw_write(info->i2c_dev, info->buf, len);
+       } else {
+               if (count < PN544_LLC_MIN_SIZE) {
+                       r = -EINVAL;
+                       goto out;
+               }
+
+               len = min(count, info->buflen);
+               if (copy_from_user(info->buf, buf, len)) {
+                       r = -EFAULT;
+                       goto out;
+               }
+
+               print_hex_dump(KERN_DEBUG, "write: ", DUMP_PREFIX_NONE,
+                              16, 2, info->buf, len, false);
+
+               if (len > (info->buf[0] + 1)) /* 1 msg at a time */
+                       len  = info->buf[0] + 1;
+
+               r = pn544_i2c_write(info->i2c_dev, info->buf, len);
+       }
+out:
+       mutex_unlock(&info->mutex);
+
+       return r;
 
-       return 0;
 }
 
-static int pn544_dev_ioctl(struct file *filp,
-               unsigned int cmd, unsigned long arg)
+static long pn544_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
 {
-       struct pn544_dev *pn544_dev = filp->private_data;
+       struct pn544_info *info = container_of(file->private_data,
+                                              struct pn544_info, miscdev);
+       struct i2c_client *client = info->i2c_dev;
+       struct pn544_nfc_platform_data *pdata;
+       unsigned int val;
+       int r = 0;
+
+       dev_dbg(&client->dev, "%s: info: %p, cmd: 0x%x\n", __func__, info, cmd);
+
+       mutex_lock(&info->mutex);
 
+       if (info->state == PN544_ST_COLD) {
+               r = -ENODEV;
+               goto out;
+       }
+
+       pdata = info->i2c_dev->dev.platform_data;
        switch (cmd) {
-       case PN544_SET_PWR:
-               if (arg == 2) {
-                       /* power on with firmware download (requires hw reset)
-                        */
-                       pr_info("%s power on with firmware\n", __func__);
-                       gpio_set_value(pn544_dev->ven_gpio,
-                                       pn544_dev->nfc_enable);
-                       gpio_set_value(pn544_dev->firm_gpio, 1);
-                       msleep(10);
-                       gpio_set_value(pn544_dev->ven_gpio,
-                                       !pn544_dev->nfc_enable);
-                       msleep(10);
-                       gpio_set_value(pn544_dev->ven_gpio,
-                                       pn544_dev->nfc_enable);
-                       msleep(10);
-               } else if (arg == 1) {
-                       /* power on */
-                       pr_info("%s power on\n", __func__);
-                       gpio_set_value(pn544_dev->firm_gpio, 0);
-                       gpio_set_value(pn544_dev->ven_gpio,
-                                       pn544_dev->nfc_enable);
-                       msleep(10);
-               } else  if (arg == 0) {
-                       /* power off */
-                       pr_info("%s power off\n", __func__);
-                       gpio_set_value(pn544_dev->firm_gpio, 0);
-                       gpio_set_value(pn544_dev->ven_gpio,
-                                       !pn544_dev->nfc_enable);
-                       msleep(10);
-                       gpio_set_value(pn544_dev->ven_gpio,
-                                       pn544_dev->nfc_enable);
-                       msleep(10);
-                       gpio_set_value(pn544_dev->ven_gpio,
-                                       !pn544_dev->nfc_enable);
-                       msleep(10);
-               } else {
-                       pr_err("%s bad arg %u\n", __func__, arg);
-                       return -EINVAL;
+       case PN544_GET_FW_MODE:
+               dev_dbg(&client->dev, "%s:  PN544_GET_FW_MODE\n", __func__);
+
+               val = (info->state == PN544_ST_FW_READY);
+               if (copy_to_user((void __user *)arg, &val, sizeof(val))) {
+                       r = -EFAULT;
+                       goto out;
                }
+
                break;
-       default:
-               pr_err("%s bad ioctl %u\n", __func__, cmd);
-               return -EINVAL;
-       }
 
-       return 0;
-}
+       case PN544_SET_FW_MODE:
+               dev_dbg(&client->dev, "%s:  PN544_SET_FW_MODE\n", __func__);
 
-static const struct file_operations pn544_dev_fops = {
-       .owner          = THIS_MODULE,
-       .llseek         = no_llseek,
-       .read           = pn544_dev_read,
-       .write          = pn544_dev_write,
-       .open           = pn544_dev_open,
-       .release        = pn544_dev_release,
-#ifdef HAVE_UNLOCKED_IOCTL
-       .unlocked_ioctl = pn544_dev_ioctl,
-#endif
-};
+               if (copy_from_user(&val, (void __user *)arg, sizeof(val))) {
+                       r = -EFAULT;
+                       goto out;
+               }
 
-static int pn544_probe(struct i2c_client *client,
-               const struct i2c_device_id *id)
-{
-       int ret;
-       struct pn544_i2c_platform_data *platform_data;
-       struct pn544_dev *pn544_dev;
+               if (val) {
+                       if (info->state == PN544_ST_FW_READY)
+                               break;
 
-       pr_debug("%s : entering probe\n", __func__);
+                       pn544_disable(info);
+                       r = pn544_enable(info, FW_MODE);
+                       if (r < 0)
+                               goto out;
+               } else {
+                       if (info->state == PN544_ST_READY)
+                               break;
+                       pn544_disable(info);
+                       r = pn544_enable(info, HCI_MODE);
+                       if (r < 0)
+                               goto out;
+               }
+               file->f_pos = info->read_offset;
+               break;
 
-       platform_data = client->dev.platform_data;
-       if (platform_data == NULL) {
-               pr_err("%s : nfc probe fail\n", __func__);
-               return  -ENODEV;
-       }
+       case TCGETS:
+               dev_dbg(&client->dev, "%s:  TCGETS\n", __func__);
 
-       if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
-               pr_err("%s : need I2C_FUNC_I2C\n", __func__);
-               return  -ENODEV;
-       }
+               r = -ENOIOCTLCMD;
+               break;
 
-       pn544_dev = kzalloc(sizeof(*pn544_dev), GFP_KERNEL);
-       if (pn544_dev == NULL) {
-               dev_err(&client->dev,
-                               "failed to allocate memory for module data\n");
-               ret = -ENOMEM;
-               goto err_exit;
+       default:
+               dev_err(&client->dev, "Unknown ioctl 0x%x\n", cmd);
+               r = -ENOIOCTLCMD;
+               break;
        }
 
-       ret = platform_data->request_resources(client);
-       if (ret)
-               goto err_exit;
-
-       pn544_dev->irq_gpio = platform_data->irq_gpio;
-       pn544_dev->ven_gpio  = platform_data->ven_gpio;
-       pn544_dev->firm_gpio  = platform_data->firm_gpio;
-       pn544_dev->client   = client;
-       pn544_dev->busy = 0;
-
-       ret = pn544_platform_init(pn544_dev);
-       if (ret)
-               goto err_exit;
-
-       /* init wakelock and queues */
-       init_waitqueue_head(&pn544_dev->read_wq);
-#ifdef CONFIG_HAS_WAKELOCK
-       wake_lock_init(&pn544_dev->read_wake, WAKE_LOCK_SUSPEND, "pn544_nfc");
-#else
-       device_init_wakeup(&client->dev, 1);
-#endif
+out:
+       mutex_unlock(&info->mutex);
 
-       pn544_dev->pn544_device.minor = MISC_DYNAMIC_MINOR;
-       pn544_dev->pn544_device.name = "pn544";
-       pn544_dev->pn544_device.fops = &pn544_dev_fops;
+       return r;
+}
 
-       ret = misc_register(&pn544_dev->pn544_device);
-       if (ret) {
-               pr_err("%s : misc_register failed\n", __FILE__);
-               goto err_misc_register;
-       }
+static int pn544_open(struct inode *inode, struct file *file)
+{
+       struct pn544_info *info = container_of(file->private_data,
+                                              struct pn544_info, miscdev);
+       struct i2c_client *client = info->i2c_dev;
+       int r = 0;
+
+       dev_dbg(&client->dev, "%s: info: %p, client %p\n", __func__,
+               info, info->i2c_dev);
+
+       mutex_lock(&info->mutex);
 
-       /* request irq.  the irq is set whenever the chip has data available
-        * for reading.  it is cleared when all data has been read.
+       /*
+        * Only 1 at a time.
+        * XXX: maybe user (counter) would work better
         */
-       pr_info("%s : requesting IRQ %d\n", __func__, client->irq);
-       ret = request_irq(client->irq, pn544_dev_irq_handler,
-                         IRQF_TRIGGER_RISING, client->name, pn544_dev);
-       if (ret) {
-               dev_err(&client->dev, "request_irq failed\n");
-               goto err_request_irq_failed;
+       if (info->state != PN544_ST_COLD) {
+               r = -EBUSY;
+               goto out;
        }
-       enable_irq_wake(client->irq);
-       i2c_set_clientdata(client, pn544_dev);
 
-       return 0;
+       file->f_pos = info->read_offset;
+       r = pn544_enable(info, HCI_MODE);
 
-err_request_irq_failed:
-       misc_deregister(&pn544_dev->pn544_device);
-err_misc_register:
-#ifdef CONFIG_HAS_WAKELOCK
-       wake_lock_destroy(&pn544_dev->read_wake);
-#else
-       device_set_wakeup_enable(&client->dev, 0);
-#endif
-       kfree(pn544_dev);
-err_exit:
-       gpio_free(platform_data->firm_gpio);
-       gpio_free(platform_data->ven_gpio);
-       gpio_free(platform_data->irq_gpio);
-       return ret;
+out:
+       mutex_unlock(&info->mutex);
+       return r;
 }
 
-static int pn544_remove(struct i2c_client *client)
+static int pn544_close(struct inode *inode, struct file *file)
 {
-       struct pn544_dev *pn544_dev;
-
-       pn544_dev = i2c_get_clientdata(client);
-#ifdef CONFIG_HAS_WAKELOCK
-       if (wake_lock_active(&pn544_dev->read_wake))
-               wake_unlock(&pn544_dev->read_wake);
-#else
-       pm_relax(&client->dev);
-#endif
+       struct pn544_info *info = container_of(file->private_data,
+                                              struct pn544_info, miscdev);
+       struct i2c_client *client = info->i2c_dev;
 
-       free_irq(client->irq, pn544_dev);
-#ifdef CONFIG_HAS_WAKELOCK
-       wake_lock_destroy(&pn544_dev->read_wake);
-#else
-       device_set_wakeup_enable(&client->dev, 0);
-#endif
-       misc_deregister(&pn544_dev->pn544_device);
-       gpio_free(pn544_dev->irq_gpio);
-       gpio_free(pn544_dev->ven_gpio);
-       gpio_free(pn544_dev->firm_gpio);
-       kfree(pn544_dev);
+       dev_dbg(&client->dev, "%s: info: %p, client %p\n",
+               __func__, info, info->i2c_dev);
+
+       mutex_lock(&info->mutex);
+       pn544_disable(info);
+       mutex_unlock(&info->mutex);
 
        return 0;
 }
 
-#ifdef CONFIG_SUSPEND
+static const struct file_operations pn544_fops = {
+       .owner          = THIS_MODULE,
+       .llseek         = no_llseek,
+       .read           = pn544_read,
+       .write          = pn544_write,
+       .poll           = pn544_poll,
+       .open           = pn544_open,
+       .release        = pn544_close,
+       .unlocked_ioctl = pn544_ioctl,
+};
 
+#ifdef CONFIG_PM
 static int pn544_suspend(struct device *dev)
 {
        struct i2c_client *client = to_i2c_client(dev);
-       struct pn544_dev *pn544_dev = i2c_get_clientdata(client);
+       struct pn544_info *info;
+       int r = 0;
 
-#ifdef CONFIG_HAS_WAKELOCK
-       WARN_ON(pn544_dev->busy);
-#endif
+       dev_info(&client->dev, "***\n%s: client %p\n***\n", __func__, client);
 
-       if (pn544_dev->busy)
-               return -EBUSY;
+       info = i2c_get_clientdata(client);
+       dev_info(&client->dev, "%s: info: %p, client %p\n", __func__,
+                info, client);
 
-       disable_irq(client->irq);
+       mutex_lock(&info->mutex);
 
-       return 0;
+       switch (info->state) {
+       case PN544_ST_FW_READY:
+               /* Do not suspend while upgrading FW, please! */
+               r = -EPERM;
+               break;
+
+       case PN544_ST_READY:
+               /*
+                * CHECK: Device should be in standby-mode. No way to check?
+                * Allowing low power mode for the regulator is potentially
+                * dangerous if pn544 does not go to suspension.
+                */
+               break;
+
+       case PN544_ST_COLD:
+               break;
+       };
+
+       mutex_unlock(&info->mutex);
+       return r;
 }
 
 static int pn544_resume(struct device *dev)
 {
        struct i2c_client *client = to_i2c_client(dev);
+       struct pn544_info *info = i2c_get_clientdata(client);
+       int r = 0;
 
-       enable_irq(client->irq);
+       dev_dbg(&client->dev, "%s: info: %p, client %p\n", __func__,
+               info, client);
 
-       return 0;
+       mutex_lock(&info->mutex);
+
+       switch (info->state) {
+       case PN544_ST_READY:
+               /*
+                * CHECK: If regulator low power mode is allowed in
+                * pn544_suspend, we should go back to normal mode
+                * here.
+                */
+               break;
+
+       case PN544_ST_COLD:
+               break;
+
+       case PN544_ST_FW_READY:
+               break;
+       };
+
+       mutex_unlock(&info->mutex);
+
+       return r;
 }
 
-static const struct dev_pm_ops pn544_pm_ops = {
-       SET_SYSTEM_SLEEP_PM_OPS(pn544_suspend,
-                               pn544_resume)
-};
+static SIMPLE_DEV_PM_OPS(pn544_pm_ops, pn544_suspend, pn544_resume);
 #endif
 
-static const struct i2c_device_id pn544_id[] = {
-       { "pn544", 0 },
-       { }
-};
+static struct device_attribute pn544_attr =
+       __ATTR(nfc_test, S_IRUGO, pn544_test, NULL);
+
+static int __devinit pn544_probe(struct i2c_client *client,
+                                const struct i2c_device_id *id)
+{
+       struct pn544_info *info;
+       struct pn544_nfc_platform_data *pdata;
+       int r = 0;
+
+       dev_dbg(&client->dev, "%s\n", __func__);
+       dev_dbg(&client->dev, "IRQ: %d\n", client->irq);
+
+       /* private data allocation */
+       info = kzalloc(sizeof(struct pn544_info), GFP_KERNEL);
+       if (!info) {
+               dev_err(&client->dev,
+                       "Cannot allocate memory for pn544_info.\n");
+               r = -ENOMEM;
+               goto err_info_alloc;
+       }
+
+       info->buflen = max(PN544_MSG_MAX_SIZE, PN544_MAX_I2C_TRANSFER);
+       info->buf = kzalloc(info->buflen, GFP_KERNEL);
+       if (!info->buf) {
+               dev_err(&client->dev,
+                       "Cannot allocate memory for pn544_info->buf.\n");
+               r = -ENOMEM;
+               goto err_buf_alloc;
+       }
+
+       info->regs[0].supply = reg_vdd_io;
+       info->regs[1].supply = reg_vbat;
+       info->regs[2].supply = reg_vsim;
+       r = regulator_bulk_get(&client->dev, ARRAY_SIZE(info->regs),
+                                info->regs);
+       if (r < 0)
+               goto err_kmalloc;
+
+       info->i2c_dev = client;
+       info->state = PN544_ST_COLD;
+       info->read_irq = PN544_NONE;
+       mutex_init(&info->read_mutex);
+       mutex_init(&info->mutex);
+       init_waitqueue_head(&info->read_wait);
+       i2c_set_clientdata(client, info);
+       pdata = client->dev.platform_data;
+       if (!pdata) {
+               dev_err(&client->dev, "No platform data\n");
+               r = -EINVAL;
+               goto err_reg;
+       }
+
+       if (!pdata->request_resources) {
+               dev_err(&client->dev, "request_resources() missing\n");
+               r = -EINVAL;
+               goto err_reg;
+       }
+
+       r = pdata->request_resources(client);
+       if (r) {
+               dev_err(&client->dev, "Cannot get platform resources\n");
+               goto err_reg;
+       }
+
+       r = request_threaded_irq(client->irq, NULL, pn544_irq_thread_fn,
+                                IRQF_TRIGGER_RISING, PN544_DRIVER_NAME,
+                                info);
+       if (r < 0) {
+               dev_err(&client->dev, "Unable to register IRQ handler\n");
+               goto err_res;
+       }
+
+       /* If we don't have the test we don't need the sysfs file */
+       if (pdata->test) {
+               r = device_create_file(&client->dev, &pn544_attr);
+               if (r) {
+                       dev_err(&client->dev,
+                               "sysfs registration failed, error %d\n", r);
+                       goto err_irq;
+               }
+       }
+
+       info->miscdev.minor = MISC_DYNAMIC_MINOR;
+       info->miscdev.name = PN544_DRIVER_NAME;
+       info->miscdev.fops = &pn544_fops;
+       info->miscdev.parent = &client->dev;
+       r = misc_register(&info->miscdev);
+       if (r < 0) {
+               dev_err(&client->dev, "Device registration failed\n");
+               goto err_sysfs;
+       }
+
+       dev_dbg(&client->dev, "%s: info: %p, pdata %p, client %p\n",
+               __func__, info, pdata, client);
+
+       return 0;
+
+err_sysfs:
+       if (pdata->test)
+               device_remove_file(&client->dev, &pn544_attr);
+err_irq:
+       free_irq(client->irq, info);
+err_res:
+       if (pdata->free_resources)
+               pdata->free_resources();
+err_reg:
+       regulator_bulk_free(ARRAY_SIZE(info->regs), info->regs);
+err_kmalloc:
+       kfree(info->buf);
+err_buf_alloc:
+       kfree(info);
+err_info_alloc:
+       return r;
+}
+
+static __devexit int pn544_remove(struct i2c_client *client)
+{
+       struct pn544_info *info = i2c_get_clientdata(client);
+       struct pn544_nfc_platform_data *pdata = client->dev.platform_data;
+
+       dev_dbg(&client->dev, "%s\n", __func__);
+
+       misc_deregister(&info->miscdev);
+       if (pdata->test)
+               device_remove_file(&client->dev, &pn544_attr);
+
+       if (info->state != PN544_ST_COLD) {
+               if (pdata->disable)
+                       pdata->disable();
+
+               info->read_irq = PN544_NONE;
+       }
+
+       free_irq(client->irq, info);
+       if (pdata->free_resources)
+               pdata->free_resources();
+
+       regulator_bulk_free(ARRAY_SIZE(info->regs), info->regs);
+       kfree(info->buf);
+       kfree(info);
+
+       return 0;
+}
 
 static struct i2c_driver pn544_driver = {
-       .id_table       = pn544_id,
-       .probe          = pn544_probe,
-       .remove         = pn544_remove,
-       .driver         = {
-               .owner  = THIS_MODULE,
-               .name   = "pn544",
-#ifdef CONFIG_SUSPEND
+       .driver = {
+               .name = PN544_DRIVER_NAME,
+#ifdef CONFIG_PM
                .pm = &pn544_pm_ops,
 #endif
        },
+       .probe = pn544_probe,
+       .id_table = pn544_id_table,
+       .remove = __devexit_p(pn544_remove),
 };
 
-/*
- * module load/unload record keeping
- */
-
-static int __init pn544_dev_init(void)
+static int __init pn544_init(void)
 {
-       pr_info("Loading pn544 driver\n");
-       return i2c_add_driver(&pn544_driver);
+       int r;
+
+       pr_debug(DRIVER_DESC ": %s\n", __func__);
+
+       r = i2c_add_driver(&pn544_driver);
+       if (r) {
+               pr_err(PN544_DRIVER_NAME ": driver registration failed\n");
+               return r;
+       }
+
+       return 0;
 }
-module_init(pn544_dev_init);
 
-static void __exit pn544_dev_exit(void)
+static void __exit pn544_exit(void)
 {
-       pr_info("Unloading pn544 driver\n");
        i2c_del_driver(&pn544_driver);
+       pr_info(DRIVER_DESC ", Exiting.\n");
 }
-module_exit(pn544_dev_exit);
 
-MODULE_AUTHOR("Sylvain Fonteneau");
-MODULE_DESCRIPTION("NFC PN544 driver");
+module_init(pn544_init);
+module_exit(pn544_exit);
+
 MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION(DRIVER_DESC);
diff --git a/drivers/nfc/pn544_hci.c b/drivers/nfc/pn544_hci.c
new file mode 100644 (file)
index 0000000..aa71807
--- /dev/null
@@ -0,0 +1,960 @@
+/*
+ * HCI based Driver for NXP PN544 NFC Chip
+ *
+ * Copyright (C) 2012  Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <linux/crc-ccitt.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/miscdevice.h>
+#include <linux/interrupt.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+
+#include <linux/nfc.h>
+#include <net/nfc/hci.h>
+#include <net/nfc/shdlc.h>
+
+#include <linux/nfc/pn544.h>
+
+#define DRIVER_DESC "HCI NFC driver for PN544"
+
+#define PN544_HCI_DRIVER_NAME "pn544_hci"
+
+/* Timing restrictions (ms) */
+#define PN544_HCI_RESETVEN_TIME                30
+
+static struct i2c_device_id pn544_hci_id_table[] = {
+       {"pn544", 0},
+       {}
+};
+
+MODULE_DEVICE_TABLE(i2c, pn544_hci_id_table);
+
+#define HCI_MODE 0
+#define FW_MODE 1
+
+/* framing in HCI mode */
+#define PN544_HCI_LLC_LEN              1
+#define PN544_HCI_LLC_CRC              2
+#define PN544_HCI_LLC_LEN_CRC          (PN544_HCI_LLC_LEN + PN544_HCI_LLC_CRC)
+#define PN544_HCI_LLC_MIN_SIZE         (1 + PN544_HCI_LLC_LEN_CRC)
+#define PN544_HCI_LLC_MAX_PAYLOAD      29
+#define PN544_HCI_LLC_MAX_SIZE         (PN544_HCI_LLC_LEN_CRC + 1 + \
+                                        PN544_HCI_LLC_MAX_PAYLOAD)
+
+enum pn544_state {
+       PN544_ST_COLD,
+       PN544_ST_FW_READY,
+       PN544_ST_READY,
+};
+
+#define FULL_VERSION_LEN 11
+
+/* Proprietary commands */
+#define PN544_WRITE            0x3f
+
+/* Proprietary gates, events, commands and registers */
+
+/* NFC_HCI_RF_READER_A_GATE additional registers and commands */
+#define PN544_RF_READER_A_AUTO_ACTIVATION                      0x10
+#define PN544_RF_READER_A_CMD_CONTINUE_ACTIVATION              0x12
+#define PN544_MIFARE_CMD                                       0x21
+
+/* Commands that apply to all RF readers */
+#define PN544_RF_READER_CMD_PRESENCE_CHECK     0x30
+#define PN544_RF_READER_CMD_ACTIVATE_NEXT      0x32
+
+/* NFC_HCI_ID_MGMT_GATE additional registers */
+#define PN544_ID_MGMT_FULL_VERSION_SW          0x10
+
+#define PN544_RF_READER_ISO15693_GATE          0x12
+
+#define PN544_RF_READER_F_GATE                 0x14
+#define PN544_FELICA_ID                                0x04
+#define PN544_FELICA_RAW                       0x20
+
+#define PN544_RF_READER_JEWEL_GATE             0x15
+#define PN544_JEWEL_RAW_CMD                    0x23
+
+#define PN544_RF_READER_NFCIP1_INITIATOR_GATE  0x30
+#define PN544_RF_READER_NFCIP1_TARGET_GATE     0x31
+
+#define PN544_SYS_MGMT_GATE                    0x90
+#define PN544_SYS_MGMT_INFO_NOTIFICATION       0x02
+
+#define PN544_POLLING_LOOP_MGMT_GATE           0x94
+#define PN544_PL_RDPHASES                      0x06
+#define PN544_PL_EMULATION                     0x07
+#define PN544_PL_NFCT_DEACTIVATED              0x09
+
+#define PN544_SWP_MGMT_GATE                    0xA0
+
+#define PN544_NFC_WI_MGMT_GATE                 0xA1
+
+static struct nfc_hci_gate pn544_gates[] = {
+       {NFC_HCI_ADMIN_GATE, NFC_HCI_INVALID_PIPE},
+       {NFC_HCI_LOOPBACK_GATE, NFC_HCI_INVALID_PIPE},
+       {NFC_HCI_ID_MGMT_GATE, NFC_HCI_INVALID_PIPE},
+       {NFC_HCI_LINK_MGMT_GATE, NFC_HCI_INVALID_PIPE},
+       {NFC_HCI_RF_READER_B_GATE, NFC_HCI_INVALID_PIPE},
+       {NFC_HCI_RF_READER_A_GATE, NFC_HCI_INVALID_PIPE},
+       {PN544_SYS_MGMT_GATE, NFC_HCI_INVALID_PIPE},
+       {PN544_SWP_MGMT_GATE, NFC_HCI_INVALID_PIPE},
+       {PN544_POLLING_LOOP_MGMT_GATE, NFC_HCI_INVALID_PIPE},
+       {PN544_NFC_WI_MGMT_GATE, NFC_HCI_INVALID_PIPE},
+       {PN544_RF_READER_F_GATE, NFC_HCI_INVALID_PIPE},
+       {PN544_RF_READER_JEWEL_GATE, NFC_HCI_INVALID_PIPE},
+       {PN544_RF_READER_ISO15693_GATE, NFC_HCI_INVALID_PIPE},
+       {PN544_RF_READER_NFCIP1_INITIATOR_GATE, NFC_HCI_INVALID_PIPE},
+       {PN544_RF_READER_NFCIP1_TARGET_GATE, NFC_HCI_INVALID_PIPE}
+};
+
+/* Largest headroom needed for outgoing custom commands */
+#define PN544_CMDS_HEADROOM    2
+
+struct pn544_hci_info {
+       struct i2c_client *i2c_dev;
+       struct nfc_shdlc *shdlc;
+
+       enum pn544_state state;
+
+       struct mutex info_lock;
+
+       unsigned int gpio_en;
+       unsigned int gpio_irq;
+       unsigned int gpio_fw;
+       unsigned int en_polarity;
+
+       int hard_fault;         /*
+                                * < 0 if hardware error occured (e.g. i2c err)
+                                * and prevents normal operation.
+                                */
+};
+
+static void pn544_hci_platform_init(struct pn544_hci_info *info)
+{
+       int polarity, retry, ret;
+       char rset_cmd[] = { 0x05, 0xF9, 0x04, 0x00, 0xC3, 0xE5 };
+       int count = sizeof(rset_cmd);
+
+       pr_info(DRIVER_DESC ": %s\n", __func__);
+       dev_info(&info->i2c_dev->dev, "Detecting nfc_en polarity\n");
+
+       /* Disable fw download */
+       gpio_set_value(info->gpio_fw, 0);
+
+       for (polarity = 0; polarity < 2; polarity++) {
+               info->en_polarity = polarity;
+               retry = 3;
+               while (retry--) {
+                       /* power off */
+                       gpio_set_value(info->gpio_en, !info->en_polarity);
+                       usleep_range(10000, 15000);
+
+                       /* power on */
+                       gpio_set_value(info->gpio_en, info->en_polarity);
+                       usleep_range(10000, 15000);
+
+                       /* send reset */
+                       dev_dbg(&info->i2c_dev->dev, "Sending reset cmd\n");
+                       ret = i2c_master_send(info->i2c_dev, rset_cmd, count);
+                       if (ret == count) {
+                               dev_info(&info->i2c_dev->dev,
+                                        "nfc_en polarity : active %s\n",
+                                        (polarity == 0 ? "low" : "high"));
+                               goto out;
+                       }
+               }
+       }
+
+       dev_err(&info->i2c_dev->dev,
+               "Could not detect nfc_en polarity, fallback to active high\n");
+
+out:
+       gpio_set_value(info->gpio_en, !info->en_polarity);
+}
+
+static int pn544_hci_enable(struct pn544_hci_info *info, int mode)
+{
+       pr_info(DRIVER_DESC ": %s\n", __func__);
+
+       gpio_set_value(info->gpio_fw, 0);
+       gpio_set_value(info->gpio_en, info->en_polarity);
+       usleep_range(10000, 15000);
+
+       return 0;
+}
+
+static void pn544_hci_disable(struct pn544_hci_info *info)
+{
+       pr_info(DRIVER_DESC ": %s\n", __func__);
+
+       gpio_set_value(info->gpio_fw, 0);
+       gpio_set_value(info->gpio_en, !info->en_polarity);
+       usleep_range(10000, 15000);
+
+       gpio_set_value(info->gpio_en, info->en_polarity);
+       usleep_range(10000, 15000);
+
+       gpio_set_value(info->gpio_en, !info->en_polarity);
+       usleep_range(10000, 15000);
+}
+
+static int pn544_hci_i2c_write(struct i2c_client *client, u8 *buf, int len)
+{
+       int r;
+
+       usleep_range(3000, 6000);
+
+       r = i2c_master_send(client, buf, len);
+
+       if (r == -EREMOTEIO) {  /* Retry, chip was in standby */
+               usleep_range(6000, 10000);
+               r = i2c_master_send(client, buf, len);
+       }
+
+       if (r >= 0 && r != len)
+               r = -EREMOTEIO;
+
+       return r;
+}
+
+static int check_crc(u8 *buf, int buflen)
+{
+       int len;
+       u16 crc;
+
+       len = buf[0] + 1;
+       crc = crc_ccitt(0xffff, buf, len - 2);
+       crc = ~crc;
+
+       if (buf[len - 2] != (crc & 0xff) || buf[len - 1] != (crc >> 8)) {
+               pr_err(PN544_HCI_DRIVER_NAME ": CRC error 0x%x != 0x%x 0x%x\n",
+                      crc, buf[len - 1], buf[len - 2]);
+
+               pr_info(DRIVER_DESC ": %s : BAD CRC\n", __func__);
+               print_hex_dump(KERN_DEBUG, "crc: ", DUMP_PREFIX_NONE,
+                              16, 2, buf, buflen, false);
+               return -EPERM;
+       }
+       return 0;
+}
+
+/*
+ * Reads an shdlc frame and returns it in a newly allocated sk_buff. Guarantees
+ * that i2c bus will be flushed and that next read will start on a new frame.
+ * returned skb contains only LLC header and payload.
+ * returns:
+ * -EREMOTEIO : i2c read error (fatal)
+ * -EBADMSG : frame was incorrect and discarded
+ * -ENOMEM : cannot allocate skb, frame dropped
+ */
+static int pn544_hci_i2c_read(struct i2c_client *client, struct sk_buff **skb)
+{
+       int r;
+       u8 len;
+       u8 tmp[PN544_HCI_LLC_MAX_SIZE - 1];
+
+       r = i2c_master_recv(client, &len, 1);
+       if (r != 1) {
+               dev_err(&client->dev, "cannot read len byte\n");
+               return -EREMOTEIO;
+       }
+
+       if ((len < (PN544_HCI_LLC_MIN_SIZE - 1)) ||
+           (len > (PN544_HCI_LLC_MAX_SIZE - 1))) {
+               dev_err(&client->dev, "invalid len byte\n");
+               r = -EBADMSG;
+               goto flush;
+       }
+
+       *skb = alloc_skb(1 + len, GFP_KERNEL);
+       if (*skb == NULL) {
+               r = -ENOMEM;
+               goto flush;
+       }
+
+       *skb_put(*skb, 1) = len;
+
+       r = i2c_master_recv(client, skb_put(*skb, len), len);
+       if (r != len) {
+               kfree_skb(*skb);
+               return -EREMOTEIO;
+       }
+
+       r = check_crc((*skb)->data, (*skb)->len);
+       if (r != 0) {
+               kfree_skb(*skb);
+               r = -EBADMSG;
+               goto flush;
+       }
+
+       skb_pull(*skb, 1);
+       skb_trim(*skb, (*skb)->len - 2);
+
+       usleep_range(3000, 6000);
+
+       return 0;
+
+flush:
+       if (i2c_master_recv(client, tmp, sizeof(tmp)) < 0)
+               r = -EREMOTEIO;
+
+       usleep_range(3000, 6000);
+
+       return r;
+}
+
+/*
+ * Reads an shdlc frame from the chip. This is not as straightforward as it
+ * seems. There are cases where we could loose the frame start synchronization.
+ * The frame format is len-data-crc, and corruption can occur anywhere while
+ * transiting on i2c bus, such that we could read an invalid len.
+ * In order to recover synchronization with the next frame, we must be sure
+ * to read the real amount of data without using the len byte. We do this by
+ * assuming the following:
+ * - the chip will always present only one single complete frame on the bus
+ *   before triggering the interrupt
+ * - the chip will not present a new frame until we have completely read
+ *   the previous one (or until we have handled the interrupt).
+ * The tricky case is when we read a corrupted len that is less than the real
+ * len. We must detect this here in order to determine that we need to flush
+ * the bus. This is the reason why we check the crc here.
+ */
+static irqreturn_t pn544_hci_irq_thread_fn(int irq, void *dev_id)
+{
+       struct pn544_hci_info *info = dev_id;
+       struct i2c_client *client = info->i2c_dev;
+       struct sk_buff *skb = NULL;
+       int r;
+
+       BUG_ON(!info);
+       BUG_ON(irq != info->i2c_dev->irq);
+
+       dev_dbg(&client->dev, "IRQ\n");
+
+       if (info->hard_fault != 0)
+               return IRQ_HANDLED;
+
+       r = pn544_hci_i2c_read(client, &skb);
+       if (r == -EREMOTEIO) {
+               info->hard_fault = r;
+
+               nfc_shdlc_recv_frame(info->shdlc, NULL);
+
+               return IRQ_HANDLED;
+       } else if ((r == -ENOMEM) || (r == -EBADMSG)) {
+               return IRQ_HANDLED;
+       }
+
+       nfc_shdlc_recv_frame(info->shdlc, skb);
+
+       return IRQ_HANDLED;
+}
+
+static int pn544_hci_open(struct nfc_shdlc *shdlc)
+{
+       struct pn544_hci_info *info = nfc_shdlc_get_clientdata(shdlc);
+       int r = 0;
+
+       mutex_lock(&info->info_lock);
+
+       if (info->state != PN544_ST_COLD) {
+               r = -EBUSY;
+               goto out;
+       }
+
+       r = pn544_hci_enable(info, HCI_MODE);
+
+       if (r == 0)
+               info->state = PN544_ST_READY;
+
+out:
+       mutex_unlock(&info->info_lock);
+       return r;
+}
+
+static void pn544_hci_close(struct nfc_shdlc *shdlc)
+{
+       struct pn544_hci_info *info = nfc_shdlc_get_clientdata(shdlc);
+
+       mutex_lock(&info->info_lock);
+
+       if (info->state == PN544_ST_COLD)
+               goto out;
+
+       pn544_hci_disable(info);
+
+       info->state = PN544_ST_COLD;
+
+out:
+       mutex_unlock(&info->info_lock);
+}
+
+static int pn544_hci_ready(struct nfc_shdlc *shdlc)
+{
+       struct nfc_hci_dev *hdev = nfc_shdlc_get_hci_dev(shdlc);
+       struct sk_buff *skb;
+       static struct hw_config {
+               u8 adr[2];
+               u8 value;
+       } hw_config[] = {
+               {{0x9f, 0x9a}, 0x00},
+
+               {{0x98, 0x10}, 0xbc},
+
+               {{0x9e, 0x71}, 0x00},
+
+               {{0x98, 0x09}, 0x00},
+
+               {{0x9e, 0xb4}, 0x00},
+
+               {{0x9e, 0xd9}, 0xff},
+               {{0x9e, 0xda}, 0xff},
+               {{0x9e, 0xdb}, 0x23},
+               {{0x9e, 0xdc}, 0x21},
+               {{0x9e, 0xdd}, 0x22},
+               {{0x9e, 0xde}, 0x24},
+
+               {{0x9c, 0x01}, 0x08},
+
+               {{0x9e, 0xaa}, 0x01},
+
+               {{0x9b, 0xd1}, 0x0d},
+               {{0x9b, 0xd2}, 0x24},
+               {{0x9b, 0xd3}, 0x0a},
+               {{0x9b, 0xd4}, 0x22},
+               {{0x9b, 0xd5}, 0x08},
+               {{0x9b, 0xd6}, 0x1e},
+               {{0x9b, 0xdd}, 0x1c},
+
+               {{0x9b, 0x84}, 0x13},
+               {{0x99, 0x81}, 0x7f},
+               {{0x99, 0x31}, 0x70},
+
+               {{0x98, 0x00}, 0x3f},
+
+               {{0x9f, 0x09}, 0x00},
+
+               {{0x9f, 0x0a}, 0x05},
+
+               {{0x9e, 0xd1}, 0xa1},
+               {{0x99, 0x23}, 0x00},
+
+               {{0x9e, 0x74}, 0x80},
+
+               {{0x9f, 0x28}, 0x10},
+
+               {{0x9f, 0x35}, 0x14},
+
+               {{0x9f, 0x36}, 0x60},
+
+               {{0x9c, 0x31}, 0x00},
+
+               {{0x9c, 0x32}, 0xc8},
+
+               {{0x9c, 0x19}, 0x40},
+
+               {{0x9c, 0x1a}, 0x40},
+
+               {{0x9c, 0x0c}, 0x00},
+
+               {{0x9c, 0x0d}, 0x00},
+
+               {{0x9c, 0x12}, 0x00},
+
+               {{0x9c, 0x13}, 0x00},
+
+               {{0x98, 0xa2}, 0x0e},
+
+               {{0x98, 0x93}, 0x40},
+
+               {{0x98, 0x7d}, 0x02},
+               {{0x98, 0x7e}, 0x00},
+               {{0x9f, 0xc8}, 0x01},
+       };
+       struct hw_config *p = hw_config;
+       int count = ARRAY_SIZE(hw_config);
+       struct sk_buff *res_skb;
+       u8 param[4];
+       int r;
+
+       param[0] = 0;
+       while (count--) {
+               param[1] = p->adr[0];
+               param[2] = p->adr[1];
+               param[3] = p->value;
+
+               r = nfc_hci_send_cmd(hdev, PN544_SYS_MGMT_GATE, PN544_WRITE,
+                                    param, 4, &res_skb);
+               if (r < 0)
+                       return r;
+
+               if (res_skb->len != 1) {
+                       kfree_skb(res_skb);
+                       return -EPROTO;
+               }
+
+               if (res_skb->data[0] != p->value) {
+                       kfree_skb(res_skb);
+                       return -EIO;
+               }
+
+               kfree_skb(res_skb);
+
+               p++;
+       }
+
+       param[0] = NFC_HCI_UICC_HOST_ID;
+       r = nfc_hci_set_param(hdev, NFC_HCI_ADMIN_GATE,
+                             NFC_HCI_ADMIN_WHITELIST, param, 1);
+       if (r < 0)
+               return r;
+
+       param[0] = 0x3d;
+       r = nfc_hci_set_param(hdev, PN544_SYS_MGMT_GATE,
+                             PN544_SYS_MGMT_INFO_NOTIFICATION, param, 1);
+       if (r < 0)
+               return r;
+
+       param[0] = 0x0;
+       r = nfc_hci_set_param(hdev, NFC_HCI_RF_READER_A_GATE,
+                             PN544_RF_READER_A_AUTO_ACTIVATION, param, 1);
+       if (r < 0)
+               return r;
+
+       r = nfc_hci_send_event(hdev, NFC_HCI_RF_READER_A_GATE,
+                              NFC_HCI_EVT_END_OPERATION, NULL, 0);
+       if (r < 0)
+               return r;
+
+       param[0] = 0x1;
+       r = nfc_hci_set_param(hdev, PN544_POLLING_LOOP_MGMT_GATE,
+                             PN544_PL_NFCT_DEACTIVATED, param, 1);
+       if (r < 0)
+               return r;
+
+       param[0] = 0x0;
+       r = nfc_hci_set_param(hdev, PN544_POLLING_LOOP_MGMT_GATE,
+                             PN544_PL_RDPHASES, param, 1);
+       if (r < 0)
+               return r;
+
+       r = nfc_hci_get_param(hdev, NFC_HCI_ID_MGMT_GATE,
+                             PN544_ID_MGMT_FULL_VERSION_SW, &skb);
+       if (r < 0)
+               return r;
+
+       if (skb->len != FULL_VERSION_LEN) {
+               kfree_skb(skb);
+               return -EINVAL;
+       }
+
+       print_hex_dump(KERN_DEBUG, "FULL VERSION SOFTWARE INFO: ",
+                      DUMP_PREFIX_NONE, 16, 1,
+                      skb->data, FULL_VERSION_LEN, false);
+
+       kfree_skb(skb);
+
+       return 0;
+}
+
+static int pn544_hci_xmit(struct nfc_shdlc *shdlc, struct sk_buff *skb)
+{
+       struct pn544_hci_info *info = nfc_shdlc_get_clientdata(shdlc);
+       struct i2c_client *client = info->i2c_dev;
+
+       if (info->hard_fault != 0)
+               return info->hard_fault;
+
+       return pn544_hci_i2c_write(client, skb->data, skb->len);
+}
+
+static int pn544_hci_start_poll(struct nfc_shdlc *shdlc,
+                               u32 im_protocols, u32 tm_protocols)
+{
+       struct nfc_hci_dev *hdev = nfc_shdlc_get_hci_dev(shdlc);
+       u8 phases = 0;
+       int r;
+       u8 duration[2];
+       u8 activated;
+
+       pr_info(DRIVER_DESC ": %s protocols 0x%x 0x%x\n",
+               __func__, im_protocols, tm_protocols);
+
+       r = nfc_hci_send_event(hdev, NFC_HCI_RF_READER_A_GATE,
+                              NFC_HCI_EVT_END_OPERATION, NULL, 0);
+       if (r < 0)
+               return r;
+
+       duration[0] = 0x18;
+       duration[1] = 0x6a;
+       r = nfc_hci_set_param(hdev, PN544_POLLING_LOOP_MGMT_GATE,
+                             PN544_PL_EMULATION, duration, 2);
+       if (r < 0)
+               return r;
+
+       activated = 0;
+       r = nfc_hci_set_param(hdev, PN544_POLLING_LOOP_MGMT_GATE,
+                             PN544_PL_NFCT_DEACTIVATED, &activated, 1);
+       if (r < 0)
+               return r;
+
+       if (im_protocols & (NFC_PROTO_ISO14443_MASK | NFC_PROTO_MIFARE_MASK |
+                        NFC_PROTO_JEWEL_MASK))
+               phases |= 1;            /* Type A */
+       if (im_protocols & NFC_PROTO_FELICA_MASK) {
+               phases |= (1 << 2);     /* Type F 212 */
+               phases |= (1 << 3);     /* Type F 424 */
+       }
+
+       phases |= (1 << 5);             /* NFC active */
+
+       r = nfc_hci_set_param(hdev, PN544_POLLING_LOOP_MGMT_GATE,
+                             PN544_PL_RDPHASES, &phases, 1);
+       if (r < 0)
+               return r;
+
+       r = nfc_hci_send_event(hdev, NFC_HCI_RF_READER_A_GATE,
+                              NFC_HCI_EVT_READER_REQUESTED, NULL, 0);
+       if (r < 0)
+               nfc_hci_send_event(hdev, NFC_HCI_RF_READER_A_GATE,
+                                  NFC_HCI_EVT_END_OPERATION, NULL, 0);
+
+       return r;
+}
+
+static int pn544_hci_target_from_gate(struct nfc_shdlc *shdlc, u8 gate,
+                                     struct nfc_target *target)
+{
+       switch (gate) {
+       case PN544_RF_READER_F_GATE:
+               target->supported_protocols = NFC_PROTO_FELICA_MASK;
+               break;
+       case PN544_RF_READER_JEWEL_GATE:
+               target->supported_protocols = NFC_PROTO_JEWEL_MASK;
+               target->sens_res = 0x0c00;
+               break;
+       default:
+               return -EPROTO;
+       }
+
+       return 0;
+}
+
+static int pn544_hci_complete_target_discovered(struct nfc_shdlc *shdlc,
+                                               u8 gate,
+                                               struct nfc_target *target)
+{
+       struct nfc_hci_dev *hdev = nfc_shdlc_get_hci_dev(shdlc);
+       struct sk_buff *uid_skb;
+       int r = 0;
+
+       if (target->supported_protocols & NFC_PROTO_MIFARE_MASK) {
+               if (target->nfcid1_len != 4 && target->nfcid1_len != 7 &&
+                   target->nfcid1_len != 10)
+                       return -EPROTO;
+
+               r = nfc_hci_send_cmd(hdev, NFC_HCI_RF_READER_A_GATE,
+                                    PN544_RF_READER_CMD_ACTIVATE_NEXT,
+                                    target->nfcid1, target->nfcid1_len, NULL);
+       } else if (target->supported_protocols & NFC_PROTO_FELICA_MASK) {
+               r = nfc_hci_get_param(hdev, PN544_RF_READER_F_GATE,
+                                     PN544_FELICA_ID, &uid_skb);
+               if (r < 0)
+                       return r;
+
+               if (uid_skb->len != 8) {
+                       kfree_skb(uid_skb);
+                       return -EPROTO;
+               }
+
+               r = nfc_hci_send_cmd(hdev, PN544_RF_READER_F_GATE,
+                                    PN544_RF_READER_CMD_ACTIVATE_NEXT,
+                                    uid_skb->data, uid_skb->len, NULL);
+               kfree_skb(uid_skb);
+       } else if (target->supported_protocols & NFC_PROTO_ISO14443_MASK) {
+               /*
+                * TODO: maybe other ISO 14443 require some kind of continue
+                * activation, but for now we've seen only this one below.
+                */
+               if (target->sens_res == 0x4403) /* Type 4 Mifare DESFire */
+                       r = nfc_hci_send_cmd(hdev, NFC_HCI_RF_READER_A_GATE,
+                             PN544_RF_READER_A_CMD_CONTINUE_ACTIVATION,
+                             NULL, 0, NULL);
+       }
+
+       return r;
+}
+
+#define MIFARE_CMD_AUTH_KEY_A  0x60
+#define MIFARE_CMD_AUTH_KEY_B  0x61
+#define MIFARE_CMD_HEADER      2
+#define MIFARE_UID_LEN         4
+#define MIFARE_KEY_LEN         6
+#define MIFARE_CMD_LEN         12
+/*
+ * Returns:
+ * <= 0: driver handled the data exchange
+ *    1: driver doesn't especially handle, please do standard processing
+ */
+static int pn544_hci_data_exchange(struct nfc_shdlc *shdlc,
+                                  struct nfc_target *target,
+                                  struct sk_buff *skb,
+                                  struct sk_buff **res_skb)
+{
+       struct nfc_hci_dev *hdev = nfc_shdlc_get_hci_dev(shdlc);
+       int r;
+
+       pr_info(DRIVER_DESC ": %s for gate=%d\n", __func__,
+               target->hci_reader_gate);
+
+       switch (target->hci_reader_gate) {
+       case NFC_HCI_RF_READER_A_GATE:
+               if (target->supported_protocols & NFC_PROTO_MIFARE_MASK) {
+                       /*
+                        * It seems that pn544 is inverting key and UID for
+                        * MIFARE authentication commands.
+                        */
+                       if (skb->len == MIFARE_CMD_LEN &&
+                           (skb->data[0] == MIFARE_CMD_AUTH_KEY_A ||
+                            skb->data[0] == MIFARE_CMD_AUTH_KEY_B)) {
+                               u8 uid[MIFARE_UID_LEN];
+                               u8 *data = skb->data + MIFARE_CMD_HEADER;
+
+                               memcpy(uid, data + MIFARE_KEY_LEN,
+                                      MIFARE_UID_LEN);
+                               memmove(data + MIFARE_UID_LEN, data,
+                                       MIFARE_KEY_LEN);
+                               memcpy(data, uid, MIFARE_UID_LEN);
+                       }
+
+                       return nfc_hci_send_cmd(hdev, target->hci_reader_gate,
+                                               PN544_MIFARE_CMD,
+                                               skb->data, skb->len, res_skb);
+               } else
+                       return 1;
+       case PN544_RF_READER_F_GATE:
+               *skb_push(skb, 1) = 0;
+               *skb_push(skb, 1) = 0;
+
+               r = nfc_hci_send_cmd(hdev, target->hci_reader_gate,
+                                    PN544_FELICA_RAW,
+                                    skb->data, skb->len, res_skb);
+               if (r == 0)
+                       skb_pull(*res_skb, 1);
+               return r;
+       case PN544_RF_READER_JEWEL_GATE:
+               return nfc_hci_send_cmd(hdev, target->hci_reader_gate,
+                                       PN544_JEWEL_RAW_CMD,
+                                       skb->data, skb->len, res_skb);
+       default:
+               return 1;
+       }
+}
+
+static int pn544_hci_check_presence(struct nfc_shdlc *shdlc,
+                                  struct nfc_target *target)
+{
+       struct nfc_hci_dev *hdev = nfc_shdlc_get_hci_dev(shdlc);
+
+       return nfc_hci_send_cmd(hdev, target->hci_reader_gate,
+                               PN544_RF_READER_CMD_PRESENCE_CHECK,
+                               NULL, 0, NULL);
+}
+
+static struct nfc_shdlc_ops pn544_shdlc_ops = {
+       .open = pn544_hci_open,
+       .close = pn544_hci_close,
+       .hci_ready = pn544_hci_ready,
+       .xmit = pn544_hci_xmit,
+       .start_poll = pn544_hci_start_poll,
+       .target_from_gate = pn544_hci_target_from_gate,
+       .complete_target_discovered = pn544_hci_complete_target_discovered,
+       .data_exchange = pn544_hci_data_exchange,
+       .check_presence = pn544_hci_check_presence,
+};
+
+static int __devinit pn544_hci_probe(struct i2c_client *client,
+                                    const struct i2c_device_id *id)
+{
+       struct pn544_hci_info *info;
+       struct pn544_nfc_platform_data *pdata;
+       int r = 0;
+       u32 protocols;
+       struct nfc_hci_init_data init_data;
+
+       dev_dbg(&client->dev, "%s\n", __func__);
+       dev_dbg(&client->dev, "IRQ: %d\n", client->irq);
+
+       if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+               dev_err(&client->dev, "Need I2C_FUNC_I2C\n");
+               return -ENODEV;
+       }
+
+       info = kzalloc(sizeof(struct pn544_hci_info), GFP_KERNEL);
+       if (!info) {
+               dev_err(&client->dev,
+                       "Cannot allocate memory for pn544_hci_info.\n");
+               r = -ENOMEM;
+               goto err_info_alloc;
+       }
+
+       info->i2c_dev = client;
+       info->state = PN544_ST_COLD;
+       mutex_init(&info->info_lock);
+       i2c_set_clientdata(client, info);
+
+       pdata = client->dev.platform_data;
+       if (pdata == NULL) {
+               dev_err(&client->dev, "No platform data\n");
+               r = -EINVAL;
+               goto err_pdata;
+       }
+
+       if (pdata->request_resources == NULL) {
+               dev_err(&client->dev, "request_resources() missing\n");
+               r = -EINVAL;
+               goto err_pdata;
+       }
+
+       r = pdata->request_resources(client);
+       if (r) {
+               dev_err(&client->dev, "Cannot get platform resources\n");
+               goto err_pdata;
+       }
+
+       info->gpio_en = pdata->get_gpio(NFC_GPIO_ENABLE);
+       info->gpio_fw = pdata->get_gpio(NFC_GPIO_FW_RESET);
+       info->gpio_irq = pdata->get_gpio(NFC_GPIO_IRQ);
+
+       pn544_hci_platform_init(info);
+
+       r = request_threaded_irq(client->irq, NULL, pn544_hci_irq_thread_fn,
+                                IRQF_TRIGGER_RISING, PN544_HCI_DRIVER_NAME,
+                                info);
+       if (r < 0) {
+               dev_err(&client->dev, "Unable to register IRQ handler\n");
+               goto err_rti;
+       }
+
+       init_data.gate_count = ARRAY_SIZE(pn544_gates);
+
+       memcpy(init_data.gates, pn544_gates, sizeof(pn544_gates));
+
+       /*
+        * TODO: Session id must include the driver name + some bus addr
+        * persistent info to discriminate 2 identical chips
+        */
+       strcpy(init_data.session_id, "ID544HCI");
+
+       protocols = NFC_PROTO_JEWEL_MASK |
+                   NFC_PROTO_MIFARE_MASK |
+                   NFC_PROTO_FELICA_MASK |
+                   NFC_PROTO_ISO14443_MASK |
+                   NFC_PROTO_ISO14443_B_MASK |
+                   NFC_PROTO_NFC_DEP_MASK;
+
+       info->shdlc = nfc_shdlc_allocate(&pn544_shdlc_ops,
+                                        &init_data, protocols,
+                                        PN544_CMDS_HEADROOM, 0,
+                                        PN544_HCI_LLC_MAX_PAYLOAD,
+                                        dev_name(&client->dev));
+       if (!info->shdlc) {
+               dev_err(&client->dev, "Cannot allocate nfc shdlc.\n");
+               r = -ENOMEM;
+               goto err_allocshdlc;
+       }
+
+       nfc_shdlc_set_clientdata(info->shdlc, info);
+
+       return 0;
+
+err_allocshdlc:
+       free_irq(client->irq, info);
+
+err_rti:
+       if (pdata->free_resources != NULL)
+               pdata->free_resources();
+
+err_pdata:
+       kfree(info);
+
+err_info_alloc:
+       return r;
+}
+
+static __devexit int pn544_hci_remove(struct i2c_client *client)
+{
+       struct pn544_hci_info *info = i2c_get_clientdata(client);
+       struct pn544_nfc_platform_data *pdata = client->dev.platform_data;
+
+       dev_dbg(&client->dev, "%s\n", __func__);
+
+       nfc_shdlc_free(info->shdlc);
+
+       if (info->state != PN544_ST_COLD) {
+               if (pdata->disable)
+                       pdata->disable();
+       }
+
+       free_irq(client->irq, info);
+       if (pdata->free_resources)
+               pdata->free_resources();
+
+       kfree(info);
+
+       return 0;
+}
+
+static struct i2c_driver pn544_hci_driver = {
+       .driver = {
+                  .name = PN544_HCI_DRIVER_NAME,
+                 },
+       .probe = pn544_hci_probe,
+       .id_table = pn544_hci_id_table,
+       .remove = __devexit_p(pn544_hci_remove),
+};
+
+static int __init pn544_hci_init(void)
+{
+       int r;
+
+       pr_debug(DRIVER_DESC ": %s\n", __func__);
+
+       r = i2c_add_driver(&pn544_hci_driver);
+       if (r) {
+               pr_err(PN544_HCI_DRIVER_NAME ": driver registration failed\n");
+               return r;
+       }
+
+       return 0;
+}
+
+static void __exit pn544_hci_exit(void)
+{
+       i2c_del_driver(&pn544_hci_driver);
+}
+
+module_init(pn544_hci_init);
+module_exit(pn544_hci_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION(DRIVER_DESC);
diff --git a/include/linux/nfc.h b/include/linux/nfc.h
new file mode 100644 (file)
index 0000000..6189f27
--- /dev/null
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2011 Instituto Nokia de Tecnologia
+ *
+ * Authors:
+ *    Lauro Ramos Venancio <lauro.venancio@openbossa.org>
+ *    Aloisio Almeida Jr <aloisio.almeida@openbossa.org>
+ *
+ * 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
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __LINUX_NFC_H
+#define __LINUX_NFC_H
+
+#include <linux/types.h>
+#include <linux/socket.h>
+
+#define NFC_GENL_NAME "nfc"
+#define NFC_GENL_VERSION 1
+
+#define NFC_GENL_MCAST_EVENT_NAME "events"
+
+/**
+ * enum nfc_commands - supported nfc commands
+ *
+ * @NFC_CMD_UNSPEC: unspecified command
+ *
+ * @NFC_CMD_GET_DEVICE: request information about a device (requires
+ *     %NFC_ATTR_DEVICE_INDEX) or dump request to get a list of all nfc devices
+ * @NFC_CMD_DEV_UP: turn on the nfc device
+ *     (requires %NFC_ATTR_DEVICE_INDEX)
+ * @NFC_CMD_DEV_DOWN: turn off the nfc device
+ *     (requires %NFC_ATTR_DEVICE_INDEX)
+ * @NFC_CMD_START_POLL: start polling for targets using the given protocols
+ *     (requires %NFC_ATTR_DEVICE_INDEX and %NFC_ATTR_PROTOCOLS)
+ * @NFC_CMD_STOP_POLL: stop polling for targets (requires
+ *     %NFC_ATTR_DEVICE_INDEX)
+ * @NFC_CMD_GET_TARGET: dump all targets found by the previous poll (requires
+ *     %NFC_ATTR_DEVICE_INDEX)
+ * @NFC_EVENT_TARGETS_FOUND: event emitted when a new target is found
+ *     (it sends %NFC_ATTR_DEVICE_INDEX)
+ * @NFC_EVENT_DEVICE_ADDED: event emitted when a new device is registred
+ *     (it sends %NFC_ATTR_DEVICE_NAME, %NFC_ATTR_DEVICE_INDEX and
+ *     %NFC_ATTR_PROTOCOLS)
+ * @NFC_EVENT_DEVICE_REMOVED: event emitted when a device is removed
+ *     (it sends %NFC_ATTR_DEVICE_INDEX)
+ * @NFC_EVENT_TM_ACTIVATED: event emitted when the adapter is activated in
+ *      target mode.
+ * @NFC_EVENT_DEVICE_DEACTIVATED: event emitted when the adapter is deactivated
+ *      from target mode.
+ */
+enum nfc_commands {
+       NFC_CMD_UNSPEC,
+       NFC_CMD_GET_DEVICE,
+       NFC_CMD_DEV_UP,
+       NFC_CMD_DEV_DOWN,
+       NFC_CMD_DEP_LINK_UP,
+       NFC_CMD_DEP_LINK_DOWN,
+       NFC_CMD_START_POLL,
+       NFC_CMD_STOP_POLL,
+       NFC_CMD_GET_TARGET,
+       NFC_EVENT_TARGETS_FOUND,
+       NFC_EVENT_DEVICE_ADDED,
+       NFC_EVENT_DEVICE_REMOVED,
+       NFC_EVENT_TARGET_LOST,
+       NFC_EVENT_TM_ACTIVATED,
+       NFC_EVENT_TM_DEACTIVATED,
+/* private: internal use only */
+       __NFC_CMD_AFTER_LAST
+};
+#define NFC_CMD_MAX (__NFC_CMD_AFTER_LAST - 1)
+
+/**
+ * enum nfc_attrs - supported nfc attributes
+ *
+ * @NFC_ATTR_UNSPEC: unspecified attribute
+ *
+ * @NFC_ATTR_DEVICE_INDEX: index of nfc device
+ * @NFC_ATTR_DEVICE_NAME: device name, max 8 chars
+ * @NFC_ATTR_PROTOCOLS: nfc protocols - bitwise or-ed combination from
+ *     NFC_PROTO_*_MASK constants
+ * @NFC_ATTR_TARGET_INDEX: index of the nfc target
+ * @NFC_ATTR_TARGET_SENS_RES: NFC-A targets extra information such as NFCID
+ * @NFC_ATTR_TARGET_SEL_RES: NFC-A targets extra information (useful if the
+ *     target is not NFC-Forum compliant)
+ * @NFC_ATTR_TARGET_NFCID1: NFC-A targets identifier, max 10 bytes
+ * @NFC_ATTR_TARGET_SENSB_RES: NFC-B targets extra information, max 12 bytes
+ * @NFC_ATTR_TARGET_SENSF_RES: NFC-F targets extra information, max 18 bytes
+ * @NFC_ATTR_COMM_MODE: Passive or active mode
+ * @NFC_ATTR_RF_MODE: Initiator or target
+ * @NFC_ATTR_IM_PROTOCOLS: Initiator mode protocols to poll for
+ * @NFC_ATTR_TM_PROTOCOLS: Target mode protocols to listen for
+ */
+enum nfc_attrs {
+       NFC_ATTR_UNSPEC,
+       NFC_ATTR_DEVICE_INDEX,
+       NFC_ATTR_DEVICE_NAME,
+       NFC_ATTR_PROTOCOLS,
+       NFC_ATTR_TARGET_INDEX,
+       NFC_ATTR_TARGET_SENS_RES,
+       NFC_ATTR_TARGET_SEL_RES,
+       NFC_ATTR_TARGET_NFCID1,
+       NFC_ATTR_TARGET_SENSB_RES,
+       NFC_ATTR_TARGET_SENSF_RES,
+       NFC_ATTR_COMM_MODE,
+       NFC_ATTR_RF_MODE,
+       NFC_ATTR_DEVICE_POWERED,
+       NFC_ATTR_IM_PROTOCOLS,
+       NFC_ATTR_TM_PROTOCOLS,
+/* private: internal use only */
+       __NFC_ATTR_AFTER_LAST
+};
+#define NFC_ATTR_MAX (__NFC_ATTR_AFTER_LAST - 1)
+
+#define NFC_DEVICE_NAME_MAXSIZE 8
+#define NFC_NFCID1_MAXSIZE 10
+#define NFC_SENSB_RES_MAXSIZE 12
+#define NFC_SENSF_RES_MAXSIZE 18
+#define NFC_GB_MAXSIZE        48
+
+/* NFC protocols */
+#define NFC_PROTO_JEWEL                1
+#define NFC_PROTO_MIFARE       2
+#define NFC_PROTO_FELICA       3
+#define NFC_PROTO_ISO14443     4
+#define NFC_PROTO_NFC_DEP      5
+#define NFC_PROTO_ISO14443_B   6
+
+#define NFC_PROTO_MAX          7
+
+/* NFC communication modes */
+#define NFC_COMM_ACTIVE  0
+#define NFC_COMM_PASSIVE 1
+
+/* NFC RF modes */
+#define NFC_RF_INITIATOR 0
+#define NFC_RF_TARGET    1
+#define NFC_RF_NONE      2
+
+/* NFC protocols masks used in bitsets */
+#define NFC_PROTO_JEWEL_MASK      (1 << NFC_PROTO_JEWEL)
+#define NFC_PROTO_MIFARE_MASK     (1 << NFC_PROTO_MIFARE)
+#define NFC_PROTO_FELICA_MASK    (1 << NFC_PROTO_FELICA)
+#define NFC_PROTO_ISO14443_MASK          (1 << NFC_PROTO_ISO14443)
+#define NFC_PROTO_NFC_DEP_MASK   (1 << NFC_PROTO_NFC_DEP)
+#define NFC_PROTO_ISO14443_B_MASK (1 << NFC_PROTO_ISO14443_B)
+
+struct sockaddr_nfc {
+       sa_family_t sa_family;
+       __u32 dev_idx;
+       __u32 target_idx;
+       __u32 nfc_protocol;
+};
+
+#define NFC_LLCP_MAX_SERVICE_NAME 63
+struct sockaddr_nfc_llcp {
+       sa_family_t sa_family;
+       __u32 dev_idx;
+       __u32 target_idx;
+       __u32 nfc_protocol;
+       __u8 dsap; /* Destination SAP, if known */
+       __u8 ssap; /* Source SAP to be bound to */
+       char service_name[NFC_LLCP_MAX_SERVICE_NAME]; /* Service name URI */;
+       size_t service_name_len;
+};
+
+/* NFC socket protocols */
+#define NFC_SOCKPROTO_RAW      0
+#define NFC_SOCKPROTO_LLCP     1
+#define NFC_SOCKPROTO_MAX      2
+
+#define NFC_HEADER_SIZE 1
+
+#endif /*__LINUX_NFC_H */
index 22419de..9890bba 100644 (file)
 /*
- * Copyright (C) 2010 Trusted Logic S.A.
+ * Driver include for the PN544 NFC chip.
  *
- * 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
- * (at your option) any later version.
+ * Copyright (C) Nokia Corporation
+ *
+ * Author: Jari Vanhala <ext-jari.vanhala@nokia.com>
+ * Contact: Matti Aaltoenn <matti.j.aaltonen@nokia.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
  *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.        See the
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
  * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  */
 
-#define PN544_MAGIC    0xE9
+#ifndef _PN544_H_
+#define _PN544_H_
 
-/*
- * PN544 power control via ioctl
- * PN544_SET_PWR(0): power off
- * PN544_SET_PWR(1): power on
- * PN544_SET_PWR(2): reset and power on with firmware download enabled
- */
-#define PN544_SET_PWR  _IOW(PN544_MAGIC, 0x01, unsigned int)
+#include <linux/i2c.h>
+
+#define PN544_DRIVER_NAME      "pn544"
+#define PN544_MAXWINDOW_SIZE   7
+#define PN544_WINDOW_SIZE      4
+#define PN544_RETRIES          10
+#define PN544_MAX_I2C_TRANSFER 0x0400
+#define PN544_MSG_MAX_SIZE     0x21 /* at normal HCI mode */
+
+/* ioctl */
+#define PN544_CHAR_BASE                'P'
+#define PN544_IOR(num, dtype)  _IOR(PN544_CHAR_BASE, num, dtype)
+#define PN544_IOW(num, dtype)  _IOW(PN544_CHAR_BASE, num, dtype)
+#define PN544_GET_FW_MODE      PN544_IOW(1, unsigned int)
+#define PN544_SET_FW_MODE      PN544_IOW(2, unsigned int)
+#define PN544_GET_DEBUG                PN544_IOW(3, unsigned int)
+#define PN544_SET_DEBUG                PN544_IOW(4, unsigned int)
+
+/* Timing restrictions (ms) */
+#define PN544_RESETVEN_TIME    30 /* 7 */
+#define PN544_PVDDVEN_TIME     0
+#define PN544_VBATVEN_TIME     0
+#define PN544_GPIO4VEN_TIME    0
+#define PN544_WAKEUP_ACK       5
+#define PN544_WAKEUP_GUARD     (PN544_WAKEUP_ACK + 1)
+#define PN544_INACTIVITY_TIME  1000
+#define PN544_INTERFRAME_DELAY 200 /* us */
+#define PN544_BAUDRATE_CHANGE  150 /* us */
+
+/* Debug bits */
+#define PN544_DEBUG_BUF                0x01
+#define PN544_DEBUG_READ       0x02
+#define PN544_DEBUG_WRITE      0x04
+#define PN544_DEBUG_IRQ                0x08
+#define PN544_DEBUG_CALLS      0x10
+#define PN544_DEBUG_MODE       0x20
+
+/* Normal (HCI) mode */
+#define PN544_LLC_HCI_OVERHEAD 3 /* header + crc (to length) */
+#define PN544_LLC_MIN_SIZE     (1 + PN544_LLC_HCI_OVERHEAD) /* length + */
+#define PN544_LLC_MAX_DATA     (PN544_MSG_MAX_SIZE - 2)
+#define PN544_LLC_MAX_HCI_SIZE (PN544_LLC_MAX_DATA - 2)
 
-struct pn544_i2c_platform_data {
+struct pn544_llc_packet {
+       unsigned char length; /* of rest of packet */
+       unsigned char header;
+       unsigned char data[PN544_LLC_MAX_DATA]; /* includes crc-ccitt */
+};
+
+/* Firmware upgrade mode */
+#define PN544_FW_HEADER_SIZE   3
+/* max fw transfer is 1024bytes, but I2C limits it to 0xC0 */
+#define PN544_MAX_FW_DATA      (PN544_MAX_I2C_TRANSFER - PN544_FW_HEADER_SIZE)
+
+struct pn544_fw_packet {
+       unsigned char command; /* status in answer */
+       unsigned char length[2]; /* big-endian order (msf) */
+       unsigned char data[PN544_MAX_FW_DATA];
+};
+
+#ifdef __KERNEL__
+enum {
+       NFC_GPIO_ENABLE,
+       NFC_GPIO_FW_RESET,
+       NFC_GPIO_IRQ
+};
+
+/* board config */
+struct pn544_nfc_platform_data {
        int (*request_resources) (struct i2c_client *client);
-       unsigned int irq_gpio;
-       unsigned int ven_gpio;
-       unsigned int firm_gpio;
+       void (*free_resources) (void);
+       void (*enable) (int fw);
+       int (*test) (void);
+       void (*disable) (void);
+       int (*get_gpio)(int type);
 };
+#endif /* __KERNEL__ */
+
+#endif /* _PN544_H_ */
index 4ef98e4..e17f822 100644 (file)
@@ -192,7 +192,8 @@ struct ucred {
 #define AF_IEEE802154  36      /* IEEE802154 sockets           */
 #define AF_CAIF                37      /* CAIF sockets                 */
 #define AF_ALG         38      /* Algorithm sockets            */
-#define AF_MAX         39      /* For now.. */
+#define AF_NFC         39      /* NFC sockets                  */
+#define AF_MAX         40      /* For now.. */
 
 /* Protocol families, same as address families. */
 #define PF_UNSPEC      AF_UNSPEC
@@ -234,6 +235,7 @@ struct ucred {
 #define PF_IEEE802154  AF_IEEE802154
 #define PF_CAIF                AF_CAIF
 #define PF_ALG         AF_ALG
+#define PF_NFC         AF_NFC
 #define PF_MAX         AF_MAX
 
 /* Maximum queue length specifiable by listen.  */
diff --git a/include/net/nfc/hci.h b/include/net/nfc/hci.h
new file mode 100644 (file)
index 0000000..f5169b0
--- /dev/null
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2011  Intel Corporation. All rights reserved.
+ *
+ * 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
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __NET_HCI_H
+#define __NET_HCI_H
+
+#include <linux/skbuff.h>
+
+#include <net/nfc/nfc.h>
+
+struct nfc_hci_dev;
+
+struct nfc_hci_ops {
+       int (*open) (struct nfc_hci_dev *hdev);
+       void (*close) (struct nfc_hci_dev *hdev);
+       int (*hci_ready) (struct nfc_hci_dev *hdev);
+       int (*xmit) (struct nfc_hci_dev *hdev, struct sk_buff *skb);
+       int (*start_poll) (struct nfc_hci_dev *hdev,
+                          u32 im_protocols, u32 tm_protocols);
+       int (*target_from_gate) (struct nfc_hci_dev *hdev, u8 gate,
+                                struct nfc_target *target);
+       int (*complete_target_discovered) (struct nfc_hci_dev *hdev, u8 gate,
+                                          struct nfc_target *target);
+       int (*data_exchange) (struct nfc_hci_dev *hdev,
+                             struct nfc_target *target,
+                             struct sk_buff *skb, struct sk_buff **res_skb);
+       int (*check_presence)(struct nfc_hci_dev *hdev,
+                             struct nfc_target *target);
+};
+
+/* Pipes */
+#define NFC_HCI_INVALID_PIPE   0x80
+#define NFC_HCI_LINK_MGMT_PIPE 0x00
+#define NFC_HCI_ADMIN_PIPE     0x01
+
+struct nfc_hci_gate {
+       u8 gate;
+       u8 pipe;
+};
+
+#define NFC_HCI_MAX_CUSTOM_GATES       50
+struct nfc_hci_init_data {
+       u8 gate_count;
+       struct nfc_hci_gate gates[NFC_HCI_MAX_CUSTOM_GATES];
+       char session_id[9];
+};
+
+typedef int (*xmit) (struct sk_buff *skb, void *cb_data);
+
+#define NFC_HCI_MAX_GATES              256
+
+struct nfc_hci_dev {
+       struct nfc_dev *ndev;
+
+       u32 max_data_link_payload;
+
+       struct mutex msg_tx_mutex;
+
+       struct list_head msg_tx_queue;
+
+       struct workqueue_struct *msg_tx_wq;
+       struct work_struct msg_tx_work;
+
+       struct timer_list cmd_timer;
+       struct hci_msg *cmd_pending_msg;
+
+       struct sk_buff_head rx_hcp_frags;
+
+       struct workqueue_struct *msg_rx_wq;
+       struct work_struct msg_rx_work;
+
+       struct sk_buff_head msg_rx_queue;
+
+       struct nfc_hci_ops *ops;
+
+       struct nfc_hci_init_data init_data;
+
+       void *clientdata;
+
+       u8 gate2pipe[NFC_HCI_MAX_GATES];
+
+       u8 sw_romlib;
+       u8 sw_patch;
+       u8 sw_flashlib_major;
+       u8 sw_flashlib_minor;
+
+       u8 hw_derivative;
+       u8 hw_version;
+       u8 hw_mpw;
+       u8 hw_software;
+       u8 hw_bsid;
+};
+
+/* hci device allocation */
+struct nfc_hci_dev *nfc_hci_allocate_device(struct nfc_hci_ops *ops,
+                                           struct nfc_hci_init_data *init_data,
+                                           u32 protocols,
+                                           int tx_headroom,
+                                           int tx_tailroom,
+                                           int max_link_payload);
+void nfc_hci_free_device(struct nfc_hci_dev *hdev);
+
+int nfc_hci_register_device(struct nfc_hci_dev *hdev);
+void nfc_hci_unregister_device(struct nfc_hci_dev *hdev);
+
+void nfc_hci_set_clientdata(struct nfc_hci_dev *hdev, void *clientdata);
+void *nfc_hci_get_clientdata(struct nfc_hci_dev *hdev);
+
+void nfc_hci_driver_failure(struct nfc_hci_dev *hdev, int err);
+
+/* Host IDs */
+#define NFC_HCI_HOST_CONTROLLER_ID     0x00
+#define NFC_HCI_TERMINAL_HOST_ID       0x01
+#define NFC_HCI_UICC_HOST_ID           0x02
+
+/* Host Controller Gates and registry indexes */
+#define NFC_HCI_ADMIN_GATE 0x00
+#define NFC_HCI_ADMIN_SESSION_IDENTITY 0x01
+#define NFC_HCI_ADMIN_MAX_PIPE         0x02
+#define NFC_HCI_ADMIN_WHITELIST                0x03
+#define NFC_HCI_ADMIN_HOST_LIST                0x04
+
+#define NFC_HCI_LOOPBACK_GATE          0x04
+
+#define NFC_HCI_ID_MGMT_GATE           0x05
+#define NFC_HCI_ID_MGMT_VERSION_SW     0x01
+#define NFC_HCI_ID_MGMT_VERSION_HW     0x03
+#define NFC_HCI_ID_MGMT_VENDOR_NAME    0x04
+#define NFC_HCI_ID_MGMT_MODEL_ID       0x05
+#define NFC_HCI_ID_MGMT_HCI_VERSION    0x02
+#define NFC_HCI_ID_MGMT_GATES_LIST     0x06
+
+#define NFC_HCI_LINK_MGMT_GATE         0x06
+#define NFC_HCI_LINK_MGMT_REC_ERROR    0x01
+
+#define NFC_HCI_RF_READER_B_GATE                       0x11
+#define NFC_HCI_RF_READER_B_PUPI                       0x03
+#define NFC_HCI_RF_READER_B_APPLICATION_DATA           0x04
+#define NFC_HCI_RF_READER_B_AFI                                0x02
+#define NFC_HCI_RF_READER_B_HIGHER_LAYER_RESPONSE      0x01
+#define NFC_HCI_RF_READER_B_HIGHER_LAYER_DATA          0x05
+
+#define NFC_HCI_RF_READER_A_GATE               0x13
+#define NFC_HCI_RF_READER_A_UID                        0x02
+#define NFC_HCI_RF_READER_A_ATQA               0x04
+#define NFC_HCI_RF_READER_A_APPLICATION_DATA   0x05
+#define NFC_HCI_RF_READER_A_SAK                        0x03
+#define NFC_HCI_RF_READER_A_FWI_SFGT           0x06
+#define NFC_HCI_RF_READER_A_DATARATE_MAX       0x01
+
+#define NFC_HCI_TYPE_A_SEL_PROT(x)             (((x) & 0x60) >> 5)
+#define NFC_HCI_TYPE_A_SEL_PROT_MIFARE         0
+#define NFC_HCI_TYPE_A_SEL_PROT_ISO14443       1
+#define NFC_HCI_TYPE_A_SEL_PROT_DEP            2
+#define NFC_HCI_TYPE_A_SEL_PROT_ISO14443_DEP   3
+
+/* Generic events */
+#define NFC_HCI_EVT_HCI_END_OF_OPERATION       0x01
+#define NFC_HCI_EVT_POST_DATA                  0x02
+#define NFC_HCI_EVT_HOT_PLUG                   0x03
+
+/* Reader RF gates events */
+#define NFC_HCI_EVT_READER_REQUESTED   0x10
+#define NFC_HCI_EVT_END_OPERATION      0x11
+
+/* Reader Application gate events */
+#define NFC_HCI_EVT_TARGET_DISCOVERED  0x10
+
+/* receiving messages from lower layer */
+void nfc_hci_resp_received(struct nfc_hci_dev *hdev, u8 result,
+                          struct sk_buff *skb);
+void nfc_hci_cmd_received(struct nfc_hci_dev *hdev, u8 pipe, u8 cmd,
+                         struct sk_buff *skb);
+void nfc_hci_event_received(struct nfc_hci_dev *hdev, u8 pipe, u8 event,
+                           struct sk_buff *skb);
+void nfc_hci_recv_frame(struct nfc_hci_dev *hdev, struct sk_buff *skb);
+
+/* connecting to gates and sending hci instructions */
+int nfc_hci_connect_gate(struct nfc_hci_dev *hdev, u8 dest_host, u8 dest_gate,
+                        u8 pipe);
+int nfc_hci_disconnect_gate(struct nfc_hci_dev *hdev, u8 gate);
+int nfc_hci_disconnect_all_gates(struct nfc_hci_dev *hdev);
+int nfc_hci_get_param(struct nfc_hci_dev *hdev, u8 gate, u8 idx,
+                     struct sk_buff **skb);
+int nfc_hci_set_param(struct nfc_hci_dev *hdev, u8 gate, u8 idx,
+                     const u8 *param, size_t param_len);
+int nfc_hci_send_cmd(struct nfc_hci_dev *hdev, u8 gate, u8 cmd,
+                    const u8 *param, size_t param_len, struct sk_buff **skb);
+int nfc_hci_send_response(struct nfc_hci_dev *hdev, u8 gate, u8 response,
+                         const u8 *param, size_t param_len);
+int nfc_hci_send_event(struct nfc_hci_dev *hdev, u8 gate, u8 event,
+                      const u8 *param, size_t param_len);
+
+#endif /* __NET_HCI_H */
diff --git a/include/net/nfc/nci.h b/include/net/nfc/nci.h
new file mode 100644 (file)
index 0000000..276094b
--- /dev/null
@@ -0,0 +1,364 @@
+/*
+ *  The NFC Controller Interface is the communication protocol between an
+ *  NFC Controller (NFCC) and a Device Host (DH).
+ *
+ *  Copyright (C) 2011 Texas Instruments, Inc.
+ *
+ *  Written by Ilan Elias <ilane@ti.com>
+ *
+ *  Acknowledgements:
+ *  This file is based on hci.h, which was written
+ *  by Maxim Krasnyansky.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2
+ *  as published by the Free Software Foundation
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#ifndef __NCI_H
+#define __NCI_H
+
+/* NCI constants */
+#define NCI_MAX_NUM_MAPPING_CONFIGS                            10
+#define NCI_MAX_NUM_RF_CONFIGS                                 10
+#define NCI_MAX_NUM_CONN                                       10
+
+/* NCI Status Codes */
+#define NCI_STATUS_OK                                          0x00
+#define NCI_STATUS_REJECTED                                    0x01
+#define NCI_STATUS_RF_FRAME_CORRUPTED                          0x02
+#define NCI_STATUS_FAILED                                      0x03
+#define NCI_STATUS_NOT_INITIALIZED                             0x04
+#define NCI_STATUS_SYNTAX_ERROR                                        0x05
+#define NCI_STATUS_SEMANTIC_ERROR                              0x06
+#define NCI_STATUS_UNKNOWN_GID                                 0x07
+#define NCI_STATUS_UNKNOWN_OID                                 0x08
+#define NCI_STATUS_INVALID_PARAM                               0x09
+#define NCI_STATUS_MESSAGE_SIZE_EXCEEDED                       0x0a
+/* Discovery Specific Status Codes */
+#define NCI_STATUS_DISCOVERY_ALREADY_STARTED                   0xa0
+#define NCI_STATUS_DISCOVERY_TARGET_ACTIVATION_FAILED          0xa1
+#define NCI_STATUS_DISCOVERY_TEAR_DOWN                         0xa2
+/* RF Interface Specific Status Codes */
+#define NCI_STATUS_RF_TRANSMISSION_ERROR                       0xb0
+#define NCI_STATUS_RF_PROTOCOL_ERROR                           0xb1
+#define NCI_STATUS_RF_TIMEOUT_ERROR                            0xb2
+/* NFCEE Interface Specific Status Codes */
+#define NCI_STATUS_NFCEE_INTERFACE_ACTIVATION_FAILED           0xc0
+#define NCI_STATUS_NFCEE_TRANSMISSION_ERROR                    0xc1
+#define NCI_STATUS_NFCEE_PROTOCOL_ERROR                                0xc2
+#define NCI_STATUS_NFCEE_TIMEOUT_ERROR                         0xc3
+
+/* NCI RF Technology and Mode */
+#define NCI_NFC_A_PASSIVE_POLL_MODE                            0x00
+#define NCI_NFC_B_PASSIVE_POLL_MODE                            0x01
+#define NCI_NFC_F_PASSIVE_POLL_MODE                            0x02
+#define NCI_NFC_A_ACTIVE_POLL_MODE                             0x03
+#define NCI_NFC_F_ACTIVE_POLL_MODE                             0x05
+#define NCI_NFC_15693_PASSIVE_POLL_MODE                                0x06
+#define NCI_NFC_A_PASSIVE_LISTEN_MODE                          0x80
+#define NCI_NFC_B_PASSIVE_LISTEN_MODE                          0x81
+#define NCI_NFC_F_PASSIVE_LISTEN_MODE                          0x82
+#define NCI_NFC_A_ACTIVE_LISTEN_MODE                           0x83
+#define NCI_NFC_F_ACTIVE_LISTEN_MODE                           0x85
+#define NCI_NFC_15693_PASSIVE_LISTEN_MODE                      0x86
+
+/* NCI RF Technologies */
+#define NCI_NFC_RF_TECHNOLOGY_A                                        0x00
+#define NCI_NFC_RF_TECHNOLOGY_B                                        0x01
+#define NCI_NFC_RF_TECHNOLOGY_F                                        0x02
+#define NCI_NFC_RF_TECHNOLOGY_15693                            0x03
+
+/* NCI Bit Rates */
+#define NCI_NFC_BIT_RATE_106                                   0x00
+#define NCI_NFC_BIT_RATE_212                                   0x01
+#define NCI_NFC_BIT_RATE_424                                   0x02
+#define NCI_NFC_BIT_RATE_848                                   0x03
+#define NCI_NFC_BIT_RATE_1695                                  0x04
+#define NCI_NFC_BIT_RATE_3390                                  0x05
+#define NCI_NFC_BIT_RATE_6780                                  0x06
+
+/* NCI RF Protocols */
+#define NCI_RF_PROTOCOL_UNKNOWN                                        0x00
+#define NCI_RF_PROTOCOL_T1T                                    0x01
+#define NCI_RF_PROTOCOL_T2T                                    0x02
+#define NCI_RF_PROTOCOL_T3T                                    0x03
+#define NCI_RF_PROTOCOL_ISO_DEP                                        0x04
+#define NCI_RF_PROTOCOL_NFC_DEP                                        0x05
+
+/* NCI RF Interfaces */
+#define NCI_RF_INTERFACE_NFCEE_DIRECT                          0x00
+#define NCI_RF_INTERFACE_FRAME                                 0x01
+#define NCI_RF_INTERFACE_ISO_DEP                               0x02
+#define NCI_RF_INTERFACE_NFC_DEP                               0x03
+
+/* NCI Reset types */
+#define NCI_RESET_TYPE_KEEP_CONFIG                             0x00
+#define NCI_RESET_TYPE_RESET_CONFIG                            0x01
+
+/* NCI Static RF connection ID */
+#define NCI_STATIC_RF_CONN_ID                                  0x00
+
+/* NCI Data Flow Control */
+#define NCI_DATA_FLOW_CONTROL_NOT_USED                         0xff
+
+/* NCI RF_DISCOVER_MAP_CMD modes */
+#define NCI_DISC_MAP_MODE_POLL                                 0x01
+#define NCI_DISC_MAP_MODE_LISTEN                               0x02
+
+/* NCI Discover Notification Type */
+#define NCI_DISCOVER_NTF_TYPE_LAST                             0x00
+#define NCI_DISCOVER_NTF_TYPE_LAST_NFCC                                0x01
+#define NCI_DISCOVER_NTF_TYPE_MORE                             0x02
+
+/* NCI Deactivation Type */
+#define NCI_DEACTIVATE_TYPE_IDLE_MODE                          0x00
+#define NCI_DEACTIVATE_TYPE_SLEEP_MODE                         0x01
+#define NCI_DEACTIVATE_TYPE_SLEEP_AF_MODE                      0x02
+#define NCI_DEACTIVATE_TYPE_DISCOVERY                          0x03
+
+/* Message Type (MT) */
+#define NCI_MT_DATA_PKT                                                0x00
+#define NCI_MT_CMD_PKT                                         0x01
+#define NCI_MT_RSP_PKT                                         0x02
+#define NCI_MT_NTF_PKT                                         0x03
+
+#define nci_mt(hdr)                    (((hdr)[0]>>5)&0x07)
+#define nci_mt_set(hdr, mt)            ((hdr)[0] |= (__u8)(((mt)&0x07)<<5))
+
+/* Packet Boundary Flag (PBF) */
+#define NCI_PBF_LAST                                           0x00
+#define NCI_PBF_CONT                                           0x01
+
+#define nci_pbf(hdr)                   (__u8)(((hdr)[0]>>4)&0x01)
+#define nci_pbf_set(hdr, pbf)          ((hdr)[0] |= (__u8)(((pbf)&0x01)<<4))
+
+/* Control Opcode manipulation */
+#define nci_opcode_pack(gid, oid)      (__u16)((((__u16)((gid)&0x0f))<<8)|\
+                                       ((__u16)((oid)&0x3f)))
+#define nci_opcode(hdr)                        nci_opcode_pack(hdr[0], hdr[1])
+#define nci_opcode_gid(op)             (__u8)(((op)&0x0f00)>>8)
+#define nci_opcode_oid(op)             (__u8)((op)&0x003f)
+
+/* Payload Length */
+#define nci_plen(hdr)                  (__u8)((hdr)[2])
+
+/* Connection ID */
+#define nci_conn_id(hdr)               (__u8)(((hdr)[0])&0x0f)
+
+/* GID values */
+#define NCI_GID_CORE                                           0x0
+#define NCI_GID_RF_MGMT                                                0x1
+#define NCI_GID_NFCEE_MGMT                                     0x2
+#define NCI_GID_PROPRIETARY                                    0xf
+
+/* ---- NCI Packet structures ---- */
+#define NCI_CTRL_HDR_SIZE                                      3
+#define NCI_DATA_HDR_SIZE                                      3
+
+struct nci_ctrl_hdr {
+       __u8    gid;            /* MT & PBF & GID */
+       __u8    oid;
+       __u8    plen;
+} __packed;
+
+struct nci_data_hdr {
+       __u8    conn_id;        /* MT & PBF & ConnID */
+       __u8    rfu;
+       __u8    plen;
+} __packed;
+
+/* ------------------------ */
+/* -----  NCI Commands ---- */
+/* ------------------------ */
+#define NCI_OP_CORE_RESET_CMD          nci_opcode_pack(NCI_GID_CORE, 0x00)
+struct nci_core_reset_cmd {
+       __u8    reset_type;
+} __packed;
+
+#define NCI_OP_CORE_INIT_CMD           nci_opcode_pack(NCI_GID_CORE, 0x01)
+
+#define NCI_OP_RF_DISCOVER_MAP_CMD     nci_opcode_pack(NCI_GID_RF_MGMT, 0x00)
+struct disc_map_config {
+       __u8    rf_protocol;
+       __u8    mode;
+       __u8    rf_interface;
+} __packed;
+
+struct nci_rf_disc_map_cmd {
+       __u8                            num_mapping_configs;
+       struct disc_map_config          mapping_configs
+                                       [NCI_MAX_NUM_MAPPING_CONFIGS];
+} __packed;
+
+#define NCI_OP_RF_DISCOVER_CMD         nci_opcode_pack(NCI_GID_RF_MGMT, 0x03)
+struct disc_config {
+       __u8    rf_tech_and_mode;
+       __u8    frequency;
+} __packed;
+
+struct nci_rf_disc_cmd {
+       __u8                            num_disc_configs;
+       struct disc_config              disc_configs[NCI_MAX_NUM_RF_CONFIGS];
+} __packed;
+
+#define NCI_OP_RF_DISCOVER_SELECT_CMD  nci_opcode_pack(NCI_GID_RF_MGMT, 0x04)
+struct nci_rf_discover_select_cmd {
+       __u8    rf_discovery_id;
+       __u8    rf_protocol;
+       __u8    rf_interface;
+} __packed;
+
+#define NCI_OP_RF_DEACTIVATE_CMD       nci_opcode_pack(NCI_GID_RF_MGMT, 0x06)
+struct nci_rf_deactivate_cmd {
+       __u8    type;
+} __packed;
+
+/* ----------------------- */
+/* ---- NCI Responses ---- */
+/* ----------------------- */
+#define NCI_OP_CORE_RESET_RSP          nci_opcode_pack(NCI_GID_CORE, 0x00)
+struct nci_core_reset_rsp {
+       __u8    status;
+       __u8    nci_ver;
+       __u8    config_status;
+} __packed;
+
+#define NCI_OP_CORE_INIT_RSP           nci_opcode_pack(NCI_GID_CORE, 0x01)
+struct nci_core_init_rsp_1 {
+       __u8    status;
+       __le32  nfcc_features;
+       __u8    num_supported_rf_interfaces;
+       __u8    supported_rf_interfaces[0];     /* variable size array */
+       /* continuted in nci_core_init_rsp_2 */
+} __packed;
+
+struct nci_core_init_rsp_2 {
+       __u8    max_logical_connections;
+       __le16  max_routing_table_size;
+       __u8    max_ctrl_pkt_payload_len;
+       __le16  max_size_for_large_params;
+       __u8    manufact_id;
+       __le32  manufact_specific_info;
+} __packed;
+
+#define NCI_OP_RF_DISCOVER_MAP_RSP     nci_opcode_pack(NCI_GID_RF_MGMT, 0x00)
+
+#define NCI_OP_RF_DISCOVER_RSP         nci_opcode_pack(NCI_GID_RF_MGMT, 0x03)
+
+#define NCI_OP_RF_DISCOVER_SELECT_RSP  nci_opcode_pack(NCI_GID_RF_MGMT, 0x04)
+
+#define NCI_OP_RF_DEACTIVATE_RSP       nci_opcode_pack(NCI_GID_RF_MGMT, 0x06)
+
+/* --------------------------- */
+/* ---- NCI Notifications ---- */
+/* --------------------------- */
+#define NCI_OP_CORE_CONN_CREDITS_NTF   nci_opcode_pack(NCI_GID_CORE, 0x06)
+struct conn_credit_entry {
+       __u8    conn_id;
+       __u8    credits;
+} __packed;
+
+struct nci_core_conn_credit_ntf {
+       __u8                            num_entries;
+       struct conn_credit_entry        conn_entries[NCI_MAX_NUM_CONN];
+} __packed;
+
+#define NCI_OP_CORE_GENERIC_ERROR_NTF  nci_opcode_pack(NCI_GID_CORE, 0x07)
+
+#define NCI_OP_CORE_INTF_ERROR_NTF     nci_opcode_pack(NCI_GID_CORE, 0x08)
+struct nci_core_intf_error_ntf {
+       __u8    status;
+       __u8    conn_id;
+} __packed;
+
+#define NCI_OP_RF_DISCOVER_NTF         nci_opcode_pack(NCI_GID_RF_MGMT, 0x03)
+struct rf_tech_specific_params_nfca_poll {
+       __u16   sens_res;
+       __u8    nfcid1_len;     /* 0, 4, 7, or 10 Bytes */
+       __u8    nfcid1[10];
+       __u8    sel_res_len;    /* 0 or 1 Bytes */
+       __u8    sel_res;
+} __packed;
+
+struct rf_tech_specific_params_nfcb_poll {
+       __u8    sensb_res_len;
+       __u8    sensb_res[12];  /* 11 or 12 Bytes */
+} __packed;
+
+struct rf_tech_specific_params_nfcf_poll {
+       __u8    bit_rate;
+       __u8    sensf_res_len;
+       __u8    sensf_res[18];  /* 16 or 18 Bytes */
+} __packed;
+
+struct nci_rf_discover_ntf {
+       __u8    rf_discovery_id;
+       __u8    rf_protocol;
+       __u8    rf_tech_and_mode;
+       __u8    rf_tech_specific_params_len;
+
+       union {
+               struct rf_tech_specific_params_nfca_poll nfca_poll;
+               struct rf_tech_specific_params_nfcb_poll nfcb_poll;
+               struct rf_tech_specific_params_nfcf_poll nfcf_poll;
+       } rf_tech_specific_params;
+
+       __u8    ntf_type;
+} __packed;
+
+#define NCI_OP_RF_INTF_ACTIVATED_NTF   nci_opcode_pack(NCI_GID_RF_MGMT, 0x05)
+struct activation_params_nfca_poll_iso_dep {
+       __u8    rats_res_len;
+       __u8    rats_res[20];
+};
+
+struct activation_params_nfcb_poll_iso_dep {
+       __u8    attrib_res_len;
+       __u8    attrib_res[50];
+};
+
+struct nci_rf_intf_activated_ntf {
+       __u8    rf_discovery_id;
+       __u8    rf_interface;
+       __u8    rf_protocol;
+       __u8    activation_rf_tech_and_mode;
+       __u8    max_data_pkt_payload_size;
+       __u8    initial_num_credits;
+       __u8    rf_tech_specific_params_len;
+
+       union {
+               struct rf_tech_specific_params_nfca_poll nfca_poll;
+               struct rf_tech_specific_params_nfcb_poll nfcb_poll;
+               struct rf_tech_specific_params_nfcf_poll nfcf_poll;
+       } rf_tech_specific_params;
+
+       __u8    data_exch_rf_tech_and_mode;
+       __u8    data_exch_tx_bit_rate;
+       __u8    data_exch_rx_bit_rate;
+       __u8    activation_params_len;
+
+       union {
+               struct activation_params_nfca_poll_iso_dep nfca_poll_iso_dep;
+               struct activation_params_nfcb_poll_iso_dep nfcb_poll_iso_dep;
+       } activation_params;
+
+} __packed;
+
+#define NCI_OP_RF_DEACTIVATE_NTF       nci_opcode_pack(NCI_GID_RF_MGMT, 0x06)
+struct nci_rf_deactivate_ntf {
+       __u8    type;
+       __u8    reason;
+} __packed;
+
+#endif /* __NCI_H */
diff --git a/include/net/nfc/nci_core.h b/include/net/nfc/nci_core.h
new file mode 100644 (file)
index 0000000..feba740
--- /dev/null
@@ -0,0 +1,199 @@
+/*
+ *  The NFC Controller Interface is the communication protocol between an
+ *  NFC Controller (NFCC) and a Device Host (DH).
+ *
+ *  Copyright (C) 2011 Texas Instruments, Inc.
+ *
+ *  Written by Ilan Elias <ilane@ti.com>
+ *
+ *  Acknowledgements:
+ *  This file is based on hci_core.h, which was written
+ *  by Maxim Krasnyansky.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2
+ *  as published by the Free Software Foundation
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#ifndef __NCI_CORE_H
+#define __NCI_CORE_H
+
+#include <linux/interrupt.h>
+#include <linux/skbuff.h>
+
+#include <net/nfc/nfc.h>
+#include <net/nfc/nci.h>
+
+/* NCI device flags */
+enum nci_flag {
+       NCI_INIT,
+       NCI_UP,
+       NCI_DATA_EXCHANGE,
+       NCI_DATA_EXCHANGE_TO,
+};
+
+/* NCI device states */
+enum nci_state {
+       NCI_IDLE,
+       NCI_DISCOVERY,
+       NCI_W4_ALL_DISCOVERIES,
+       NCI_W4_HOST_SELECT,
+       NCI_POLL_ACTIVE,
+};
+
+/* NCI timeouts */
+#define NCI_RESET_TIMEOUT                      5000
+#define NCI_INIT_TIMEOUT                       5000
+#define NCI_RF_DISC_TIMEOUT                    5000
+#define NCI_RF_DISC_SELECT_TIMEOUT             5000
+#define NCI_RF_DEACTIVATE_TIMEOUT              30000
+#define NCI_CMD_TIMEOUT                                5000
+#define NCI_DATA_TIMEOUT                       700
+
+struct nci_dev;
+
+struct nci_ops {
+       int (*open)(struct nci_dev *ndev);
+       int (*close)(struct nci_dev *ndev);
+       int (*send)(struct sk_buff *skb);
+};
+
+#define NCI_MAX_SUPPORTED_RF_INTERFACES                4
+#define NCI_MAX_DISCOVERED_TARGETS             10
+
+/* NCI Core structures */
+struct nci_dev {
+       struct nfc_dev          *nfc_dev;
+       struct nci_ops          *ops;
+
+       int                     tx_headroom;
+       int                     tx_tailroom;
+
+       atomic_t                state;
+       unsigned long           flags;
+
+       atomic_t                cmd_cnt;
+       atomic_t                credits_cnt;
+
+       struct timer_list       cmd_timer;
+       struct timer_list       data_timer;
+
+       struct workqueue_struct *cmd_wq;
+       struct work_struct      cmd_work;
+
+       struct workqueue_struct *rx_wq;
+       struct work_struct      rx_work;
+
+       struct workqueue_struct *tx_wq;
+       struct work_struct      tx_work;
+
+       struct sk_buff_head     cmd_q;
+       struct sk_buff_head     rx_q;
+       struct sk_buff_head     tx_q;
+
+       struct mutex            req_lock;
+       struct completion       req_completion;
+       __u32                   req_status;
+       __u32                   req_result;
+
+       void                    *driver_data;
+
+       __u32                   poll_prots;
+       __u32                   target_active_prot;
+
+       struct nfc_target       targets[NCI_MAX_DISCOVERED_TARGETS];
+       int                     n_targets;
+
+       /* received during NCI_OP_CORE_RESET_RSP */
+       __u8                    nci_ver;
+
+       /* received during NCI_OP_CORE_INIT_RSP */
+       __u32                   nfcc_features;
+       __u8                    num_supported_rf_interfaces;
+       __u8                    supported_rf_interfaces
+                               [NCI_MAX_SUPPORTED_RF_INTERFACES];
+       __u8                    max_logical_connections;
+       __u16                   max_routing_table_size;
+       __u8                    max_ctrl_pkt_payload_len;
+       __u16                   max_size_for_large_params;
+       __u8                    manufact_id;
+       __u32                   manufact_specific_info;
+
+       /* received during NCI_OP_RF_INTF_ACTIVATED_NTF */
+       __u8                    max_data_pkt_payload_size;
+       __u8                    initial_num_credits;
+
+       /* stored during nci_data_exchange */
+       data_exchange_cb_t      data_exchange_cb;
+       void                    *data_exchange_cb_context;
+       struct sk_buff          *rx_data_reassembly;
+};
+
+/* ----- NCI Devices ----- */
+struct nci_dev *nci_allocate_device(struct nci_ops *ops,
+                                   __u32 supported_protocols,
+                                   int tx_headroom,
+                                   int tx_tailroom);
+void nci_free_device(struct nci_dev *ndev);
+int nci_register_device(struct nci_dev *ndev);
+void nci_unregister_device(struct nci_dev *ndev);
+int nci_recv_frame(struct sk_buff *skb);
+
+static inline struct sk_buff *nci_skb_alloc(struct nci_dev *ndev,
+                                           unsigned int len,
+                                           gfp_t how)
+{
+       struct sk_buff *skb;
+
+       skb = alloc_skb(len + ndev->tx_headroom + ndev->tx_tailroom, how);
+       if (skb)
+               skb_reserve(skb, ndev->tx_headroom);
+
+       return skb;
+}
+
+static inline void nci_set_parent_dev(struct nci_dev *ndev, struct device *dev)
+{
+       nfc_set_parent_dev(ndev->nfc_dev, dev);
+}
+
+static inline void nci_set_drvdata(struct nci_dev *ndev, void *data)
+{
+       ndev->driver_data = data;
+}
+
+static inline void *nci_get_drvdata(struct nci_dev *ndev)
+{
+       return ndev->driver_data;
+}
+
+void nci_rsp_packet(struct nci_dev *ndev, struct sk_buff *skb);
+void nci_ntf_packet(struct nci_dev *ndev, struct sk_buff *skb);
+void nci_rx_data_packet(struct nci_dev *ndev, struct sk_buff *skb);
+int nci_send_cmd(struct nci_dev *ndev, __u16 opcode, __u8 plen, void *payload);
+int nci_send_data(struct nci_dev *ndev, __u8 conn_id, struct sk_buff *skb);
+void nci_data_exchange_complete(struct nci_dev *ndev, struct sk_buff *skb,
+                               int err);
+void nci_clear_target_list(struct nci_dev *ndev);
+
+/* ----- NCI requests ----- */
+#define NCI_REQ_DONE           0
+#define NCI_REQ_PEND           1
+#define NCI_REQ_CANCELED       2
+
+void nci_req_complete(struct nci_dev *ndev, int result);
+
+/* ----- NCI status code ----- */
+int nci_to_errno(__u8 code);
+
+#endif /* __NCI_CORE_H */
diff --git a/include/net/nfc/nfc.h b/include/net/nfc/nfc.h
new file mode 100644 (file)
index 0000000..6431f5e
--- /dev/null
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2011 Instituto Nokia de Tecnologia
+ *
+ * Authors:
+ *    Lauro Ramos Venancio <lauro.venancio@openbossa.org>
+ *    Aloisio Almeida Jr <aloisio.almeida@openbossa.org>
+ *
+ * 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
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __NET_NFC_H
+#define __NET_NFC_H
+
+#include <linux/nfc.h>
+#include <linux/device.h>
+#include <linux/skbuff.h>
+
+#define nfc_dev_info(dev, fmt, arg...) dev_info((dev), "NFC: " fmt "\n", ## arg)
+#define nfc_dev_err(dev, fmt, arg...) dev_err((dev), "NFC: " fmt "\n", ## arg)
+#define nfc_dev_dbg(dev, fmt, arg...) dev_dbg((dev), fmt "\n", ## arg)
+
+struct nfc_dev;
+
+/**
+ * data_exchange_cb_t - Definition of nfc_data_exchange callback
+ *
+ * @context: nfc_data_exchange cb_context parameter
+ * @skb: response data
+ * @err: If an error has occurred during data exchange, it is the
+ *     error number. Zero means no error.
+ *
+ * When a rx or tx package is lost or corrupted or the target gets out
+ * of the operating field, err is -EIO.
+ */
+typedef void (*data_exchange_cb_t)(void *context, struct sk_buff *skb,
+                                                               int err);
+
+struct nfc_target;
+
+struct nfc_ops {
+       int (*dev_up)(struct nfc_dev *dev);
+       int (*dev_down)(struct nfc_dev *dev);
+       int (*start_poll)(struct nfc_dev *dev,
+                         u32 im_protocols, u32 tm_protocols);
+       void (*stop_poll)(struct nfc_dev *dev);
+       int (*dep_link_up)(struct nfc_dev *dev, struct nfc_target *target,
+                          u8 comm_mode, u8 *gb, size_t gb_len);
+       int (*dep_link_down)(struct nfc_dev *dev);
+       int (*activate_target)(struct nfc_dev *dev, struct nfc_target *target,
+                              u32 protocol);
+       void (*deactivate_target)(struct nfc_dev *dev,
+                                 struct nfc_target *target);
+       int (*im_transceive)(struct nfc_dev *dev, struct nfc_target *target,
+                            struct sk_buff *skb, data_exchange_cb_t cb,
+                            void *cb_context);
+       int (*tm_send)(struct nfc_dev *dev, struct sk_buff *skb);
+       int (*check_presence)(struct nfc_dev *dev, struct nfc_target *target);
+};
+
+#define NFC_TARGET_IDX_ANY -1
+#define NFC_MAX_GT_LEN 48
+
+struct nfc_target {
+       u32 idx;
+       u32 supported_protocols;
+       u16 sens_res;
+       u8 sel_res;
+       u8 nfcid1_len;
+       u8 nfcid1[NFC_NFCID1_MAXSIZE];
+       u8 sensb_res_len;
+       u8 sensb_res[NFC_SENSB_RES_MAXSIZE];
+       u8 sensf_res_len;
+       u8 sensf_res[NFC_SENSF_RES_MAXSIZE];
+       u8 hci_reader_gate;
+       u8 logical_idx;
+};
+
+struct nfc_genl_data {
+       u32 poll_req_pid;
+       struct mutex genl_data_mutex;
+};
+
+struct nfc_dev {
+       unsigned int idx;
+       u32 target_next_idx;
+       struct nfc_target *targets;
+       int n_targets;
+       int targets_generation;
+       struct device dev;
+       bool dev_up;
+       u8 rf_mode;
+       bool polling;
+       struct nfc_target *active_target;
+       bool dep_link_up;
+       struct nfc_genl_data genl_data;
+       u32 supported_protocols;
+
+       int tx_headroom;
+       int tx_tailroom;
+
+       struct timer_list check_pres_timer;
+       struct workqueue_struct *check_pres_wq;
+       struct work_struct check_pres_work;
+
+       struct nfc_ops *ops;
+};
+#define to_nfc_dev(_dev) container_of(_dev, struct nfc_dev, dev)
+
+extern struct class nfc_class;
+
+struct nfc_dev *nfc_allocate_device(struct nfc_ops *ops,
+                                   u32 supported_protocols,
+                                   int tx_headroom,
+                                   int tx_tailroom);
+
+/**
+ * nfc_free_device - free nfc device
+ *
+ * @dev: The nfc device to free
+ */
+static inline void nfc_free_device(struct nfc_dev *dev)
+{
+       put_device(&dev->dev);
+}
+
+int nfc_register_device(struct nfc_dev *dev);
+
+void nfc_unregister_device(struct nfc_dev *dev);
+
+/**
+ * nfc_set_parent_dev - set the parent device
+ *
+ * @nfc_dev: The nfc device whose parent is being set
+ * @dev: The parent device
+ */
+static inline void nfc_set_parent_dev(struct nfc_dev *nfc_dev,
+                                     struct device *dev)
+{
+       nfc_dev->dev.parent = dev;
+}
+
+/**
+ * nfc_set_drvdata - set driver specifc data
+ *
+ * @dev: The nfc device
+ * @data: Pointer to driver specifc data
+ */
+static inline void nfc_set_drvdata(struct nfc_dev *dev, void *data)
+{
+       dev_set_drvdata(&dev->dev, data);
+}
+
+/**
+ * nfc_get_drvdata - get driver specifc data
+ *
+ * @dev: The nfc device
+ */
+static inline void *nfc_get_drvdata(struct nfc_dev *dev)
+{
+       return dev_get_drvdata(&dev->dev);
+}
+
+/**
+ * nfc_device_name - get the nfc device name
+ *
+ * @dev: The nfc device whose name to return
+ */
+static inline const char *nfc_device_name(struct nfc_dev *dev)
+{
+       return dev_name(&dev->dev);
+}
+
+struct sk_buff *nfc_alloc_send_skb(struct nfc_dev *dev, struct sock *sk,
+                                  unsigned int flags, unsigned int size,
+                                  unsigned int *err);
+struct sk_buff *nfc_alloc_recv_skb(unsigned int size, gfp_t gfp);
+
+int nfc_set_remote_general_bytes(struct nfc_dev *dev,
+                                u8 *gt, u8 gt_len);
+u8 *nfc_get_local_general_bytes(struct nfc_dev *dev, size_t *gb_len);
+
+int nfc_targets_found(struct nfc_dev *dev,
+                     struct nfc_target *targets, int ntargets);
+int nfc_target_lost(struct nfc_dev *dev, u32 target_idx);
+
+int nfc_dep_link_is_up(struct nfc_dev *dev, u32 target_idx,
+                      u8 comm_mode, u8 rf_mode);
+
+int nfc_tm_activated(struct nfc_dev *dev, u32 protocol, u8 comm_mode,
+                    u8 *gb, size_t gb_len);
+int nfc_tm_deactivated(struct nfc_dev *dev);
+int nfc_tm_data_received(struct nfc_dev *dev, struct sk_buff *skb);
+
+void nfc_driver_failure(struct nfc_dev *dev, int err);
+
+#endif /* __NET_NFC_H */
diff --git a/include/net/nfc/shdlc.h b/include/net/nfc/shdlc.h
new file mode 100644 (file)
index 0000000..35e930d
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2012  Intel Corporation. All rights reserved.
+ *
+ * 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
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __NFC_SHDLC_H
+#define __NFC_SHDLC_H
+
+struct nfc_shdlc;
+
+struct nfc_shdlc_ops {
+       int (*open) (struct nfc_shdlc *shdlc);
+       void (*close) (struct nfc_shdlc *shdlc);
+       int (*hci_ready) (struct nfc_shdlc *shdlc);
+       int (*xmit) (struct nfc_shdlc *shdlc, struct sk_buff *skb);
+       int (*start_poll) (struct nfc_shdlc *shdlc,
+                          u32 im_protocols, u32 tm_protocols);
+       int (*target_from_gate) (struct nfc_shdlc *shdlc, u8 gate,
+                                struct nfc_target *target);
+       int (*complete_target_discovered) (struct nfc_shdlc *shdlc, u8 gate,
+                                          struct nfc_target *target);
+       int (*data_exchange) (struct nfc_shdlc *shdlc,
+                             struct nfc_target *target,
+                             struct sk_buff *skb, struct sk_buff **res_skb);
+       int (*check_presence)(struct nfc_shdlc *shdlc,
+                             struct nfc_target *target);
+};
+
+enum shdlc_state {
+       SHDLC_DISCONNECTED = 0,
+       SHDLC_CONNECTING = 1,
+       SHDLC_NEGOCIATING = 2,
+       SHDLC_CONNECTED = 3
+};
+
+struct nfc_shdlc {
+       struct mutex state_mutex;
+       enum shdlc_state state;
+       int hard_fault;
+
+       struct nfc_hci_dev *hdev;
+
+       wait_queue_head_t *connect_wq;
+       int connect_tries;
+       int connect_result;
+       struct timer_list connect_timer;/* aka T3 in spec 10.6.1 */
+
+       u8 w;                           /* window size */
+       bool srej_support;
+
+       struct timer_list t1_timer;     /* send ack timeout */
+       bool t1_active;
+
+       struct timer_list t2_timer;     /* guard/retransmit timeout */
+       bool t2_active;
+
+       int ns;                         /* next seq num for send */
+       int nr;                         /* next expected seq num for receive */
+       int dnr;                        /* oldest sent unacked seq num */
+
+       struct sk_buff_head rcv_q;
+
+       struct sk_buff_head send_q;
+       bool rnr;                       /* other side is not ready to receive */
+
+       struct sk_buff_head ack_pending_q;
+
+       struct workqueue_struct *sm_wq;
+       struct work_struct sm_work;
+
+       struct nfc_shdlc_ops *ops;
+
+       int client_headroom;
+       int client_tailroom;
+
+       void *clientdata;
+};
+
+void nfc_shdlc_recv_frame(struct nfc_shdlc *shdlc, struct sk_buff *skb);
+
+struct nfc_shdlc *nfc_shdlc_allocate(struct nfc_shdlc_ops *ops,
+                                    struct nfc_hci_init_data *init_data,
+                                    u32 protocols,
+                                    int tx_headroom, int tx_tailroom,
+                                    int max_link_payload, const char *devname);
+
+void nfc_shdlc_free(struct nfc_shdlc *shdlc);
+
+void nfc_shdlc_set_clientdata(struct nfc_shdlc *shdlc, void *clientdata);
+void *nfc_shdlc_get_clientdata(struct nfc_shdlc *shdlc);
+struct nfc_hci_dev *nfc_shdlc_get_hci_dev(struct nfc_shdlc *shdlc);
+
+#endif /* __NFC_SHDLC_H */
index 919cf9a..a54855c 100644 (file)
@@ -336,6 +336,7 @@ source "net/rfkill/Kconfig"
 source "net/9p/Kconfig"
 source "net/caif/Kconfig"
 source "net/ceph/Kconfig"
+source "net/nfc/Kconfig"
 
 
 endif   # if NET
index 54808ab..075a0ce 100644 (file)
@@ -40,7 +40,6 @@ obj-$(CONFIG_AF_RXRPC)                += rxrpc/
 obj-$(CONFIG_ATM)              += atm/
 obj-$(CONFIG_L2TP)             += l2tp/
 obj-$(CONFIG_DECNET)           += decnet/
-obj-$(CONFIG_ECONET)           += econet/
 obj-$(CONFIG_PHONET)           += phonet/
 ifneq ($(CONFIG_VLAN_8021Q),)
 obj-y                          += 8021q/
@@ -60,6 +59,7 @@ ifneq ($(CONFIG_DCB),)
 obj-y                          += dcb/
 endif
 obj-$(CONFIG_IEEE802154)       += ieee802154/
+obj-$(CONFIG_MAC802154)                += mac802154/
 
 ifeq ($(CONFIG_NET),y)
 obj-$(CONFIG_SYSCTL)           += sysctl_net.o
@@ -68,4 +68,6 @@ obj-$(CONFIG_WIMAX)           += wimax/
 obj-$(CONFIG_DNS_RESOLVER)     += dns_resolver/
 obj-$(CONFIG_CEPH_LIB)         += ceph/
 obj-$(CONFIG_BATMAN_ADV)       += batman-adv/
-obj-$(CONFIG_NET_ACTIVITY_STATS)               += activity_stats.o
+obj-$(CONFIG_NFC)              += nfc/
+obj-$(CONFIG_OPENVSWITCH)      += openvswitch/
+obj-$(CONFIG_NET_ACTIVITY_STATS) += activity_stats.o
\ No newline at end of file
index 6e81978..84d6de8 100644 (file)
@@ -158,7 +158,7 @@ static const char *const af_family_key_strings[AF_MAX+1] = {
   "sk_lock-AF_TIPC"  , "sk_lock-AF_BLUETOOTH", "sk_lock-IUCV"        ,
   "sk_lock-AF_RXRPC" , "sk_lock-AF_ISDN"     , "sk_lock-AF_PHONET"   ,
   "sk_lock-AF_IEEE802154", "sk_lock-AF_CAIF" , "sk_lock-AF_ALG"      ,
-  "sk_lock-AF_MAX"
+  "sk_lock-AF_NFC"   , "sk_lock-AF_MAX"
 };
 static const char *const af_family_slock_key_strings[AF_MAX+1] = {
   "slock-AF_UNSPEC", "slock-AF_UNIX"     , "slock-AF_INET"     ,
@@ -174,7 +174,7 @@ static const char *const af_family_slock_key_strings[AF_MAX+1] = {
   "slock-AF_TIPC"  , "slock-AF_BLUETOOTH", "slock-AF_IUCV"     ,
   "slock-AF_RXRPC" , "slock-AF_ISDN"     , "slock-AF_PHONET"   ,
   "slock-AF_IEEE802154", "slock-AF_CAIF" , "slock-AF_ALG"      ,
-  "slock-AF_MAX"
+  "slock-AF_NFC"   , "slock-AF_MAX"
 };
 static const char *const af_family_clock_key_strings[AF_MAX+1] = {
   "clock-AF_UNSPEC", "clock-AF_UNIX"     , "clock-AF_INET"     ,
@@ -190,7 +190,7 @@ static const char *const af_family_clock_key_strings[AF_MAX+1] = {
   "clock-AF_TIPC"  , "clock-AF_BLUETOOTH", "clock-AF_IUCV"     ,
   "clock-AF_RXRPC" , "clock-AF_ISDN"     , "clock-AF_PHONET"   ,
   "clock-AF_IEEE802154", "clock-AF_CAIF" , "clock-AF_ALG"      ,
-  "clock-AF_MAX"
+  "clock-AF_NFC"   , "clock-AF_MAX"
 };
 
 /*
diff --git a/net/nfc/Kconfig b/net/nfc/Kconfig
new file mode 100644 (file)
index 0000000..8d8d9bc
--- /dev/null
@@ -0,0 +1,20 @@
+#
+# NFC sybsystem configuration
+#
+
+menuconfig NFC
+       depends on NET && EXPERIMENTAL
+       tristate "NFC subsystem support (EXPERIMENTAL)"
+       default n
+       help
+         Say Y here if you want to build support for NFC (Near field
+         communication) devices.
+
+         To compile this support as a module, choose M here: the module will
+         be called nfc.
+
+source "net/nfc/nci/Kconfig"
+source "net/nfc/hci/Kconfig"
+source "net/nfc/llcp/Kconfig"
+
+source "drivers/nfc/Kconfig"
diff --git a/net/nfc/Makefile b/net/nfc/Makefile
new file mode 100644 (file)
index 0000000..d1a117c
--- /dev/null
@@ -0,0 +1,10 @@
+#
+# Makefile for the Linux NFC subsystem.
+#
+
+obj-$(CONFIG_NFC) += nfc.o
+obj-$(CONFIG_NFC_NCI) += nci/
+obj-$(CONFIG_NFC_HCI) += hci/
+
+nfc-objs := core.o netlink.o af_nfc.o rawsock.o
+nfc-$(CONFIG_NFC_LLCP) += llcp/llcp.o llcp/commands.o llcp/sock.o
diff --git a/net/nfc/af_nfc.c b/net/nfc/af_nfc.c
new file mode 100644 (file)
index 0000000..9d68441
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2011 Instituto Nokia de Tecnologia
+ *
+ * Authors:
+ *    Aloisio Almeida Jr <aloisio.almeida@openbossa.org>
+ *    Lauro Ramos Venancio <lauro.venancio@openbossa.org>
+ *
+ * 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
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <linux/nfc.h>
+#include <linux/module.h>
+
+#include "nfc.h"
+
+static DEFINE_RWLOCK(proto_tab_lock);
+static const struct nfc_protocol *proto_tab[NFC_SOCKPROTO_MAX];
+
+static int nfc_sock_create(struct net *net, struct socket *sock, int proto,
+                          int kern)
+{
+       int rc = -EPROTONOSUPPORT;
+
+       if (net != &init_net)
+               return -EAFNOSUPPORT;
+
+       if (proto < 0 || proto >= NFC_SOCKPROTO_MAX)
+               return -EINVAL;
+
+       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]);
+               module_put(proto_tab[proto]->owner);
+       }
+       read_unlock(&proto_tab_lock);
+
+       return rc;
+}
+
+static struct net_proto_family nfc_sock_family_ops = {
+       .owner  = THIS_MODULE,
+       .family = PF_NFC,
+       .create = nfc_sock_create,
+};
+
+int nfc_proto_register(const struct nfc_protocol *nfc_proto)
+{
+       int rc;
+
+       if (nfc_proto->id < 0 || nfc_proto->id >= NFC_SOCKPROTO_MAX)
+               return -EINVAL;
+
+       rc = proto_register(nfc_proto->proto, 0);
+       if (rc)
+               return rc;
+
+       write_lock(&proto_tab_lock);
+       if (proto_tab[nfc_proto->id])
+               rc = -EBUSY;
+       else
+               proto_tab[nfc_proto->id] = nfc_proto;
+       write_unlock(&proto_tab_lock);
+
+       return rc;
+}
+EXPORT_SYMBOL(nfc_proto_register);
+
+void nfc_proto_unregister(const struct nfc_protocol *nfc_proto)
+{
+       write_lock(&proto_tab_lock);
+       proto_tab[nfc_proto->id] = NULL;
+       write_unlock(&proto_tab_lock);
+
+       proto_unregister(nfc_proto->proto);
+}
+EXPORT_SYMBOL(nfc_proto_unregister);
+
+int __init af_nfc_init(void)
+{
+       return sock_register(&nfc_sock_family_ops);
+}
+
+void af_nfc_exit(void)
+{
+       sock_unregister(PF_NFC);
+}
diff --git a/net/nfc/core.c b/net/nfc/core.c
new file mode 100644 (file)
index 0000000..be98ac5
--- /dev/null
@@ -0,0 +1,929 @@
+/*
+ * Copyright (C) 2011 Instituto Nokia de Tecnologia
+ *
+ * Authors:
+ *    Lauro Ramos Venancio <lauro.venancio@openbossa.org>
+ *    Aloisio Almeida Jr <aloisio.almeida@openbossa.org>
+ *
+ * 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
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": %s: " fmt, __func__
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/nfc.h>
+
+#include <net/genetlink.h>
+
+#include "nfc.h"
+
+#define VERSION "0.1"
+
+#define NFC_CHECK_PRES_FREQ_MS 2000
+
+int nfc_devlist_generation;
+DEFINE_MUTEX(nfc_devlist_mutex);
+
+/**
+ * nfc_dev_up - turn on the NFC device
+ *
+ * @dev: The nfc device to be turned on
+ *
+ * The device remains up until the nfc_dev_down function is called.
+ */
+int nfc_dev_up(struct nfc_dev *dev)
+{
+       int rc = 0;
+
+       pr_debug("dev_name=%s\n", dev_name(&dev->dev));
+
+       device_lock(&dev->dev);
+
+       if (!device_is_registered(&dev->dev)) {
+               rc = -ENODEV;
+               goto error;
+       }
+
+       if (dev->dev_up) {
+               rc = -EALREADY;
+               goto error;
+       }
+
+       if (dev->ops->dev_up)
+               rc = dev->ops->dev_up(dev);
+
+       if (!rc)
+               dev->dev_up = true;
+
+error:
+       device_unlock(&dev->dev);
+       return rc;
+}
+
+/**
+ * nfc_dev_down - turn off the NFC device
+ *
+ * @dev: The nfc device to be turned off
+ */
+int nfc_dev_down(struct nfc_dev *dev)
+{
+       int rc = 0;
+
+       pr_debug("dev_name=%s\n", dev_name(&dev->dev));
+
+       device_lock(&dev->dev);
+
+       if (!device_is_registered(&dev->dev)) {
+               rc = -ENODEV;
+               goto error;
+       }
+
+       if (!dev->dev_up) {
+               rc = -EALREADY;
+               goto error;
+       }
+
+       if (dev->polling || dev->active_target) {
+               rc = -EBUSY;
+               goto error;
+       }
+
+       if (dev->ops->dev_down)
+               dev->ops->dev_down(dev);
+
+       dev->dev_up = false;
+
+error:
+       device_unlock(&dev->dev);
+       return rc;
+}
+
+/**
+ * nfc_start_poll - start polling for nfc targets
+ *
+ * @dev: The nfc device that must start polling
+ * @protocols: bitset of nfc protocols that must be used for polling
+ *
+ * The device remains polling for targets until a target is found or
+ * the nfc_stop_poll function is called.
+ */
+int nfc_start_poll(struct nfc_dev *dev, u32 im_protocols, u32 tm_protocols)
+{
+       int rc;
+
+       pr_debug("dev_name %s initiator protocols 0x%x target protocols 0x%x\n",
+                dev_name(&dev->dev), im_protocols, tm_protocols);
+
+       if (!im_protocols && !tm_protocols)
+               return -EINVAL;
+
+       device_lock(&dev->dev);
+
+       if (!device_is_registered(&dev->dev)) {
+               rc = -ENODEV;
+               goto error;
+       }
+
+       if (dev->polling) {
+               rc = -EBUSY;
+               goto error;
+       }
+
+       rc = dev->ops->start_poll(dev, im_protocols, tm_protocols);
+       if (!rc) {
+               dev->polling = true;
+               dev->rf_mode = NFC_RF_NONE;
+       }
+
+error:
+       device_unlock(&dev->dev);
+       return rc;
+}
+
+/**
+ * nfc_stop_poll - stop polling for nfc targets
+ *
+ * @dev: The nfc device that must stop polling
+ */
+int nfc_stop_poll(struct nfc_dev *dev)
+{
+       int rc = 0;
+
+       pr_debug("dev_name=%s\n", dev_name(&dev->dev));
+
+       device_lock(&dev->dev);
+
+       if (!device_is_registered(&dev->dev)) {
+               rc = -ENODEV;
+               goto error;
+       }
+
+       if (!dev->polling) {
+               rc = -EINVAL;
+               goto error;
+       }
+
+       dev->ops->stop_poll(dev);
+       dev->polling = false;
+
+error:
+       device_unlock(&dev->dev);
+       return rc;
+}
+
+static struct nfc_target *nfc_find_target(struct nfc_dev *dev, u32 target_idx)
+{
+       int i;
+
+       if (dev->n_targets == 0)
+               return NULL;
+
+       for (i = 0; i < dev->n_targets ; i++) {
+               if (dev->targets[i].idx == target_idx)
+                       return &dev->targets[i];
+       }
+
+       return NULL;
+}
+
+int nfc_dep_link_up(struct nfc_dev *dev, int target_index, u8 comm_mode)
+{
+       int rc = 0;
+       u8 *gb;
+       size_t gb_len;
+       struct nfc_target *target;
+
+       pr_debug("dev_name=%s comm %d\n", dev_name(&dev->dev), comm_mode);
+
+       if (!dev->ops->dep_link_up)
+               return -EOPNOTSUPP;
+
+       device_lock(&dev->dev);
+
+       if (!device_is_registered(&dev->dev)) {
+               rc = -ENODEV;
+               goto error;
+       }
+
+       if (dev->dep_link_up == true) {
+               rc = -EALREADY;
+               goto error;
+       }
+
+       gb = nfc_llcp_general_bytes(dev, &gb_len);
+       if (gb_len > NFC_MAX_GT_LEN) {
+               rc = -EINVAL;
+               goto error;
+       }
+
+       target = nfc_find_target(dev, target_index);
+       if (target == NULL) {
+               rc = -ENOTCONN;
+               goto error;
+       }
+
+       rc = dev->ops->dep_link_up(dev, target, comm_mode, gb, gb_len);
+       if (!rc) {
+               dev->active_target = target;
+               dev->rf_mode = NFC_RF_INITIATOR;
+       }
+
+error:
+       device_unlock(&dev->dev);
+       return rc;
+}
+
+int nfc_dep_link_down(struct nfc_dev *dev)
+{
+       int rc = 0;
+
+       pr_debug("dev_name=%s\n", dev_name(&dev->dev));
+
+       if (!dev->ops->dep_link_down)
+               return -EOPNOTSUPP;
+
+       device_lock(&dev->dev);
+
+       if (!device_is_registered(&dev->dev)) {
+               rc = -ENODEV;
+               goto error;
+       }
+
+       if (dev->dep_link_up == false) {
+               rc = -EALREADY;
+               goto error;
+       }
+
+       rc = dev->ops->dep_link_down(dev);
+       if (!rc) {
+               dev->dep_link_up = false;
+               dev->active_target = NULL;
+               nfc_llcp_mac_is_down(dev);
+               nfc_genl_dep_link_down_event(dev);
+       }
+
+error:
+       device_unlock(&dev->dev);
+       return rc;
+}
+
+int nfc_dep_link_is_up(struct nfc_dev *dev, u32 target_idx,
+                      u8 comm_mode, u8 rf_mode)
+{
+       dev->dep_link_up = true;
+
+       nfc_llcp_mac_is_up(dev, target_idx, comm_mode, rf_mode);
+
+       return nfc_genl_dep_link_up_event(dev, target_idx, comm_mode, rf_mode);
+}
+EXPORT_SYMBOL(nfc_dep_link_is_up);
+
+/**
+ * nfc_activate_target - prepare the target for data exchange
+ *
+ * @dev: The nfc device that found the target
+ * @target_idx: index of the target that must be activated
+ * @protocol: nfc protocol that will be used for data exchange
+ */
+int nfc_activate_target(struct nfc_dev *dev, u32 target_idx, u32 protocol)
+{
+       int rc;
+       struct nfc_target *target;
+
+       pr_debug("dev_name=%s target_idx=%u protocol=%u\n",
+                dev_name(&dev->dev), target_idx, protocol);
+
+       device_lock(&dev->dev);
+
+       if (!device_is_registered(&dev->dev)) {
+               rc = -ENODEV;
+               goto error;
+       }
+
+       if (dev->active_target) {
+               rc = -EBUSY;
+               goto error;
+       }
+
+       target = nfc_find_target(dev, target_idx);
+       if (target == NULL) {
+               rc = -ENOTCONN;
+               goto error;
+       }
+
+       rc = dev->ops->activate_target(dev, target, protocol);
+       if (!rc) {
+               dev->active_target = target;
+               dev->rf_mode = NFC_RF_INITIATOR;
+
+               if (dev->ops->check_presence)
+                       mod_timer(&dev->check_pres_timer, jiffies +
+                                 msecs_to_jiffies(NFC_CHECK_PRES_FREQ_MS));
+       }
+
+error:
+       device_unlock(&dev->dev);
+       return rc;
+}
+
+/**
+ * nfc_deactivate_target - deactivate a nfc target
+ *
+ * @dev: The nfc device that found the target
+ * @target_idx: index of the target that must be deactivated
+ */
+int nfc_deactivate_target(struct nfc_dev *dev, u32 target_idx)
+{
+       int rc = 0;
+
+       pr_debug("dev_name=%s target_idx=%u\n",
+                dev_name(&dev->dev), target_idx);
+
+       device_lock(&dev->dev);
+
+       if (!device_is_registered(&dev->dev)) {
+               rc = -ENODEV;
+               goto error;
+       }
+
+       if (dev->active_target == NULL) {
+               rc = -ENOTCONN;
+               goto error;
+       }
+
+       if (dev->active_target->idx != target_idx) {
+               rc = -ENOTCONN;
+               goto error;
+       }
+
+       if (dev->ops->check_presence)
+               del_timer_sync(&dev->check_pres_timer);
+
+       dev->ops->deactivate_target(dev, dev->active_target);
+       dev->active_target = NULL;
+
+error:
+       device_unlock(&dev->dev);
+       return rc;
+}
+
+/**
+ * nfc_data_exchange - transceive data
+ *
+ * @dev: The nfc device that found the target
+ * @target_idx: index of the target
+ * @skb: data to be sent
+ * @cb: callback called when the response is received
+ * @cb_context: parameter for the callback function
+ *
+ * The user must wait for the callback before calling this function again.
+ */
+int nfc_data_exchange(struct nfc_dev *dev, u32 target_idx, struct sk_buff *skb,
+                     data_exchange_cb_t cb, void *cb_context)
+{
+       int rc;
+
+       pr_debug("dev_name=%s target_idx=%u skb->len=%u\n",
+                dev_name(&dev->dev), target_idx, skb->len);
+
+       device_lock(&dev->dev);
+
+       if (!device_is_registered(&dev->dev)) {
+               rc = -ENODEV;
+               kfree_skb(skb);
+               goto error;
+       }
+
+       if (dev->rf_mode == NFC_RF_INITIATOR && dev->active_target != NULL) {
+               if (dev->active_target->idx != target_idx) {
+                       rc = -EADDRNOTAVAIL;
+                       kfree_skb(skb);
+                       goto error;
+               }
+
+               if (dev->ops->check_presence)
+                       del_timer_sync(&dev->check_pres_timer);
+
+               rc = dev->ops->im_transceive(dev, dev->active_target, skb, cb,
+                                            cb_context);
+
+               if (!rc && dev->ops->check_presence)
+                       mod_timer(&dev->check_pres_timer, jiffies +
+                                 msecs_to_jiffies(NFC_CHECK_PRES_FREQ_MS));
+       } else if (dev->rf_mode == NFC_RF_TARGET && dev->ops->tm_send != NULL) {
+               rc = dev->ops->tm_send(dev, skb);
+       } else {
+               rc = -ENOTCONN;
+               kfree_skb(skb);
+               goto error;
+       }
+
+
+error:
+       device_unlock(&dev->dev);
+       return rc;
+}
+
+int nfc_set_remote_general_bytes(struct nfc_dev *dev, u8 *gb, u8 gb_len)
+{
+       pr_debug("dev_name=%s gb_len=%d\n", dev_name(&dev->dev), gb_len);
+
+       if (gb_len > NFC_MAX_GT_LEN)
+               return -EINVAL;
+
+       return nfc_llcp_set_remote_gb(dev, gb, gb_len);
+}
+EXPORT_SYMBOL(nfc_set_remote_general_bytes);
+
+u8 *nfc_get_local_general_bytes(struct nfc_dev *dev, size_t *gb_len)
+{
+       pr_debug("dev_name=%s\n", dev_name(&dev->dev));
+
+       return nfc_llcp_general_bytes(dev, gb_len);
+}
+EXPORT_SYMBOL(nfc_get_local_general_bytes);
+
+int nfc_tm_data_received(struct nfc_dev *dev, struct sk_buff *skb)
+{
+       /* Only LLCP target mode for now */
+       if (dev->dep_link_up == false) {
+               kfree_skb(skb);
+               return -ENOLINK;
+       }
+
+       return nfc_llcp_data_received(dev, skb);
+}
+EXPORT_SYMBOL(nfc_tm_data_received);
+
+int nfc_tm_activated(struct nfc_dev *dev, u32 protocol, u8 comm_mode,
+                    u8 *gb, size_t gb_len)
+{
+       int rc;
+
+       device_lock(&dev->dev);
+
+       dev->polling = false;
+
+       if (gb != NULL) {
+               rc = nfc_set_remote_general_bytes(dev, gb, gb_len);
+               if (rc < 0)
+                       goto out;
+       }
+
+       dev->rf_mode = NFC_RF_TARGET;
+
+       if (protocol == NFC_PROTO_NFC_DEP_MASK)
+               nfc_dep_link_is_up(dev, 0, comm_mode, NFC_RF_TARGET);
+
+       rc = nfc_genl_tm_activated(dev, protocol);
+
+out:
+       device_unlock(&dev->dev);
+
+       return rc;
+}
+EXPORT_SYMBOL(nfc_tm_activated);
+
+int nfc_tm_deactivated(struct nfc_dev *dev)
+{
+       dev->dep_link_up = false;
+
+       return nfc_genl_tm_deactivated(dev);
+}
+EXPORT_SYMBOL(nfc_tm_deactivated);
+
+/**
+ * nfc_alloc_send_skb - allocate a skb for data exchange responses
+ *
+ * @size: size to allocate
+ * @gfp: gfp flags
+ */
+struct sk_buff *nfc_alloc_send_skb(struct nfc_dev *dev, struct sock *sk,
+                                  unsigned int flags, unsigned int size,
+                                  unsigned int *err)
+{
+       struct sk_buff *skb;
+       unsigned int total_size;
+
+       total_size = size +
+               dev->tx_headroom + dev->tx_tailroom + NFC_HEADER_SIZE;
+
+       skb = sock_alloc_send_skb(sk, total_size, flags & MSG_DONTWAIT, err);
+       if (skb)
+               skb_reserve(skb, dev->tx_headroom + NFC_HEADER_SIZE);
+
+       return skb;
+}
+
+/**
+ * nfc_alloc_recv_skb - allocate a skb for data exchange responses
+ *
+ * @size: size to allocate
+ * @gfp: gfp flags
+ */
+struct sk_buff *nfc_alloc_recv_skb(unsigned int size, gfp_t gfp)
+{
+       struct sk_buff *skb;
+       unsigned int total_size;
+
+       total_size = size + 1;
+       skb = alloc_skb(total_size, gfp);
+
+       if (skb)
+               skb_reserve(skb, 1);
+
+       return skb;
+}
+EXPORT_SYMBOL(nfc_alloc_recv_skb);
+
+/**
+ * nfc_targets_found - inform that targets were found
+ *
+ * @dev: The nfc device that found the targets
+ * @targets: array of nfc targets found
+ * @ntargets: targets array size
+ *
+ * The device driver must call this function when one or many nfc targets
+ * are found. After calling this function, the device driver must stop
+ * polling for targets.
+ * NOTE: This function can be called with targets=NULL and n_targets=0 to
+ * notify a driver error, meaning that the polling operation cannot complete.
+ * IMPORTANT: this function must not be called from an atomic context.
+ * In addition, it must also not be called from a context that would prevent
+ * the NFC Core to call other nfc ops entry point concurrently.
+ */
+int nfc_targets_found(struct nfc_dev *dev,
+                     struct nfc_target *targets, int n_targets)
+{
+       int i;
+
+       pr_debug("dev_name=%s n_targets=%d\n", dev_name(&dev->dev), n_targets);
+
+       for (i = 0; i < n_targets; i++)
+               targets[i].idx = dev->target_next_idx++;
+
+       device_lock(&dev->dev);
+
+       if (dev->polling == false) {
+               device_unlock(&dev->dev);
+               return 0;
+       }
+
+       dev->polling = false;
+
+       dev->targets_generation++;
+
+       kfree(dev->targets);
+       dev->targets = NULL;
+
+       if (targets) {
+               dev->targets = kmemdup(targets,
+                                      n_targets * sizeof(struct nfc_target),
+                                      GFP_ATOMIC);
+
+               if (!dev->targets) {
+                       dev->n_targets = 0;
+                       device_unlock(&dev->dev);
+                       return -ENOMEM;
+               }
+       }
+
+       dev->n_targets = n_targets;
+       device_unlock(&dev->dev);
+
+       nfc_genl_targets_found(dev);
+
+       return 0;
+}
+EXPORT_SYMBOL(nfc_targets_found);
+
+/**
+ * nfc_target_lost - inform that an activated target went out of field
+ *
+ * @dev: The nfc device that had the activated target in field
+ * @target_idx: the nfc index of the target
+ *
+ * The device driver must call this function when the activated target
+ * goes out of the field.
+ * IMPORTANT: this function must not be called from an atomic context.
+ * In addition, it must also not be called from a context that would prevent
+ * the NFC Core to call other nfc ops entry point concurrently.
+ */
+int nfc_target_lost(struct nfc_dev *dev, u32 target_idx)
+{
+       struct nfc_target *tg;
+       int i;
+
+       pr_debug("dev_name %s n_target %d\n", dev_name(&dev->dev), target_idx);
+
+       device_lock(&dev->dev);
+
+       for (i = 0; i < dev->n_targets; i++) {
+               tg = &dev->targets[i];
+               if (tg->idx == target_idx)
+                       break;
+       }
+
+       if (i == dev->n_targets) {
+               device_unlock(&dev->dev);
+               return -EINVAL;
+       }
+
+       dev->targets_generation++;
+       dev->n_targets--;
+       dev->active_target = NULL;
+
+       if (dev->n_targets) {
+               memcpy(&dev->targets[i], &dev->targets[i + 1],
+                      (dev->n_targets - i) * sizeof(struct nfc_target));
+       } else {
+               kfree(dev->targets);
+               dev->targets = NULL;
+       }
+
+       device_unlock(&dev->dev);
+
+       nfc_genl_target_lost(dev, target_idx);
+
+       return 0;
+}
+EXPORT_SYMBOL(nfc_target_lost);
+
+inline void nfc_driver_failure(struct nfc_dev *dev, int err)
+{
+       nfc_targets_found(dev, NULL, 0);
+}
+EXPORT_SYMBOL(nfc_driver_failure);
+
+static void nfc_release(struct device *d)
+{
+       struct nfc_dev *dev = to_nfc_dev(d);
+
+       pr_debug("dev_name=%s\n", dev_name(&dev->dev));
+
+       if (dev->ops->check_presence) {
+               del_timer_sync(&dev->check_pres_timer);
+               destroy_workqueue(dev->check_pres_wq);
+       }
+
+       nfc_genl_data_exit(&dev->genl_data);
+       kfree(dev->targets);
+       kfree(dev);
+}
+
+static void nfc_check_pres_work(struct work_struct *work)
+{
+       struct nfc_dev *dev = container_of(work, struct nfc_dev,
+                                          check_pres_work);
+       int rc;
+
+       device_lock(&dev->dev);
+
+       if (dev->active_target && timer_pending(&dev->check_pres_timer) == 0) {
+               rc = dev->ops->check_presence(dev, dev->active_target);
+               if (!rc) {
+                       mod_timer(&dev->check_pres_timer, jiffies +
+                                 msecs_to_jiffies(NFC_CHECK_PRES_FREQ_MS));
+               } else {
+                       u32 active_target_idx = dev->active_target->idx;
+                       device_unlock(&dev->dev);
+                       nfc_target_lost(dev, active_target_idx);
+                       return;
+               }
+       }
+
+       device_unlock(&dev->dev);
+}
+
+static void nfc_check_pres_timeout(unsigned long data)
+{
+       struct nfc_dev *dev = (struct nfc_dev *)data;
+
+       queue_work(dev->check_pres_wq, &dev->check_pres_work);
+}
+
+struct class nfc_class = {
+       .name = "nfc",
+       .dev_release = nfc_release,
+};
+EXPORT_SYMBOL(nfc_class);
+
+static int match_idx(struct device *d, void *data)
+{
+       struct nfc_dev *dev = to_nfc_dev(d);
+       unsigned int *idx = data;
+
+       return dev->idx == *idx;
+}
+
+struct nfc_dev *nfc_get_device(unsigned int idx)
+{
+       struct device *d;
+
+       d = class_find_device(&nfc_class, NULL, &idx, match_idx);
+       if (!d)
+               return NULL;
+
+       return to_nfc_dev(d);
+}
+
+/**
+ * nfc_allocate_device - allocate a new nfc device
+ *
+ * @ops: device operations
+ * @supported_protocols: NFC protocols supported by the device
+ */
+struct nfc_dev *nfc_allocate_device(struct nfc_ops *ops,
+                                   u32 supported_protocols,
+                                   int tx_headroom, int tx_tailroom)
+{
+       static atomic_t dev_no = ATOMIC_INIT(0);
+       struct nfc_dev *dev;
+
+       if (!ops->start_poll || !ops->stop_poll || !ops->activate_target ||
+           !ops->deactivate_target || !ops->im_transceive)
+               return NULL;
+
+       if (!supported_protocols)
+               return NULL;
+
+       dev = kzalloc(sizeof(struct nfc_dev), GFP_KERNEL);
+       if (!dev)
+               return NULL;
+
+       dev->dev.class = &nfc_class;
+       dev->idx = atomic_inc_return(&dev_no) - 1;
+       dev_set_name(&dev->dev, "nfc%d", dev->idx);
+       device_initialize(&dev->dev);
+
+       dev->ops = ops;
+       dev->supported_protocols = supported_protocols;
+       dev->tx_headroom = tx_headroom;
+       dev->tx_tailroom = tx_tailroom;
+
+       nfc_genl_data_init(&dev->genl_data);
+
+
+       /* first generation must not be 0 */
+       dev->targets_generation = 1;
+
+       if (ops->check_presence) {
+               char name[32];
+               init_timer(&dev->check_pres_timer);
+               dev->check_pres_timer.data = (unsigned long)dev;
+               dev->check_pres_timer.function = nfc_check_pres_timeout;
+
+               INIT_WORK(&dev->check_pres_work, nfc_check_pres_work);
+               snprintf(name, sizeof(name), "nfc%d_check_pres_wq", dev->idx);
+               dev->check_pres_wq = alloc_workqueue(name, WQ_NON_REENTRANT |
+                                                    WQ_UNBOUND |
+                                                    WQ_MEM_RECLAIM, 1);
+               if (dev->check_pres_wq == NULL) {
+                       kfree(dev);
+                       return NULL;
+               }
+       }
+
+       return dev;
+}
+EXPORT_SYMBOL(nfc_allocate_device);
+
+/**
+ * nfc_register_device - register a nfc device in the nfc subsystem
+ *
+ * @dev: The nfc device to register
+ */
+int nfc_register_device(struct nfc_dev *dev)
+{
+       int rc;
+
+       pr_debug("dev_name=%s\n", dev_name(&dev->dev));
+
+       mutex_lock(&nfc_devlist_mutex);
+       nfc_devlist_generation++;
+       rc = device_add(&dev->dev);
+       mutex_unlock(&nfc_devlist_mutex);
+
+       if (rc < 0)
+               return rc;
+
+       rc = nfc_llcp_register_device(dev);
+       if (rc)
+               pr_err("Could not register llcp device\n");
+
+       rc = nfc_genl_device_added(dev);
+       if (rc)
+               pr_debug("The userspace won't be notified that the device %s was added\n",
+                        dev_name(&dev->dev));
+
+       return 0;
+}
+EXPORT_SYMBOL(nfc_register_device);
+
+/**
+ * nfc_unregister_device - unregister a nfc device in the nfc subsystem
+ *
+ * @dev: The nfc device to unregister
+ */
+void nfc_unregister_device(struct nfc_dev *dev)
+{
+       int rc;
+
+       pr_debug("dev_name=%s\n", dev_name(&dev->dev));
+
+       mutex_lock(&nfc_devlist_mutex);
+       nfc_devlist_generation++;
+
+       /* lock to avoid unregistering a device while an operation
+          is in progress */
+       device_lock(&dev->dev);
+       device_del(&dev->dev);
+       device_unlock(&dev->dev);
+
+       mutex_unlock(&nfc_devlist_mutex);
+
+       nfc_llcp_unregister_device(dev);
+
+       rc = nfc_genl_device_removed(dev);
+       if (rc)
+               pr_debug("The userspace won't be notified that the device %s was removed\n",
+                        dev_name(&dev->dev));
+
+}
+EXPORT_SYMBOL(nfc_unregister_device);
+
+static int __init nfc_init(void)
+{
+       int rc;
+
+       pr_info("NFC Core ver %s\n", VERSION);
+
+       rc = class_register(&nfc_class);
+       if (rc)
+               return rc;
+
+       rc = nfc_genl_init();
+       if (rc)
+               goto err_genl;
+
+       /* the first generation must not be 0 */
+       nfc_devlist_generation = 1;
+
+       rc = rawsock_init();
+       if (rc)
+               goto err_rawsock;
+
+       rc = nfc_llcp_init();
+       if (rc)
+               goto err_llcp_sock;
+
+       rc = af_nfc_init();
+       if (rc)
+               goto err_af_nfc;
+
+       return 0;
+
+err_af_nfc:
+       nfc_llcp_exit();
+err_llcp_sock:
+       rawsock_exit();
+err_rawsock:
+       nfc_genl_exit();
+err_genl:
+       class_unregister(&nfc_class);
+       return rc;
+}
+
+static void __exit nfc_exit(void)
+{
+       af_nfc_exit();
+       nfc_llcp_exit();
+       rawsock_exit();
+       nfc_genl_exit();
+       class_unregister(&nfc_class);
+}
+
+subsys_initcall(nfc_init);
+module_exit(nfc_exit);
+
+MODULE_AUTHOR("Lauro Ramos Venancio <lauro.venancio@openbossa.org>");
+MODULE_DESCRIPTION("NFC Core ver " VERSION);
+MODULE_VERSION(VERSION);
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_NETPROTO(PF_NFC);
diff --git a/net/nfc/hci/Kconfig b/net/nfc/hci/Kconfig
new file mode 100644 (file)
index 0000000..fd67f51
--- /dev/null
@@ -0,0 +1,17 @@
+config NFC_HCI
+       depends on NFC
+       tristate "NFC HCI implementation"
+       default n
+       help
+        Say Y here if you want to build support for a kernel NFC HCI
+        implementation. This is mostly needed for devices that only process
+        HCI frames, like for example the NXP pn544.
+
+config NFC_SHDLC
+       depends on NFC_HCI
+       select CRC_CCITT
+       bool "SHDLC link layer for HCI based NFC drivers"
+       default n
+       ---help---
+         Say yes if you use an NFC HCI driver that requires SHDLC link layer.
+         If unsure, say N here.
diff --git a/net/nfc/hci/Makefile b/net/nfc/hci/Makefile
new file mode 100644 (file)
index 0000000..f9c44b2
--- /dev/null
@@ -0,0 +1,8 @@
+#
+# Makefile for the Linux NFC HCI layer.
+#
+
+obj-$(CONFIG_NFC_HCI) += hci.o
+
+hci-y                  := core.o hcp.o command.o
+hci-$(CONFIG_NFC_SHDLC)        += shdlc.o
diff --git a/net/nfc/hci/command.c b/net/nfc/hci/command.c
new file mode 100644 (file)
index 0000000..46362ef
--- /dev/null
@@ -0,0 +1,346 @@
+/*
+ * Copyright (C) 2012  Intel Corporation. All rights reserved.
+ *
+ * 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
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#define pr_fmt(fmt) "hci: %s: " fmt, __func__
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/module.h>
+
+#include <net/nfc/hci.h>
+
+#include "hci.h"
+
+static void nfc_hci_execute_cb(struct nfc_hci_dev *hdev, int err,
+                              struct sk_buff *skb, void *cb_data)
+{
+       struct hcp_exec_waiter *hcp_ew = (struct hcp_exec_waiter *)cb_data;
+
+       pr_debug("HCI Cmd completed with result=%d\n", err);
+
+       hcp_ew->exec_result = err;
+       if (hcp_ew->exec_result == 0)
+               hcp_ew->result_skb = skb;
+       else
+               kfree_skb(skb);
+       hcp_ew->exec_complete = true;
+
+       wake_up(hcp_ew->wq);
+}
+
+static int nfc_hci_execute_cmd(struct nfc_hci_dev *hdev, u8 pipe, u8 cmd,
+                              const u8 *param, size_t param_len,
+                              struct sk_buff **skb)
+{
+       DECLARE_WAIT_QUEUE_HEAD_ONSTACK(ew_wq);
+       struct hcp_exec_waiter hcp_ew;
+       hcp_ew.wq = &ew_wq;
+       hcp_ew.exec_complete = false;
+       hcp_ew.result_skb = NULL;
+
+       pr_debug("through pipe=%d, cmd=%d, plen=%zd\n", pipe, cmd, param_len);
+
+       /* TODO: Define hci cmd execution delay. Should it be the same
+        * for all commands?
+        */
+       hcp_ew.exec_result = nfc_hci_hcp_message_tx(hdev, pipe,
+                                                   NFC_HCI_HCP_COMMAND, cmd,
+                                                   param, param_len,
+                                                   nfc_hci_execute_cb, &hcp_ew,
+                                                   3000);
+       if (hcp_ew.exec_result < 0)
+               return hcp_ew.exec_result;
+
+       wait_event(ew_wq, hcp_ew.exec_complete == true);
+
+       if (hcp_ew.exec_result == 0) {
+               if (skb)
+                       *skb = hcp_ew.result_skb;
+               else
+                       kfree_skb(hcp_ew.result_skb);
+       }
+
+       return hcp_ew.exec_result;
+}
+
+int nfc_hci_send_event(struct nfc_hci_dev *hdev, u8 gate, u8 event,
+                      const u8 *param, size_t param_len)
+{
+       u8 pipe;
+
+       pr_debug("%d to gate %d\n", event, gate);
+
+       pipe = hdev->gate2pipe[gate];
+       if (pipe == NFC_HCI_INVALID_PIPE)
+               return -EADDRNOTAVAIL;
+
+       return nfc_hci_hcp_message_tx(hdev, pipe, NFC_HCI_HCP_EVENT, event,
+                                     param, param_len, NULL, NULL, 0);
+}
+EXPORT_SYMBOL(nfc_hci_send_event);
+
+int nfc_hci_send_response(struct nfc_hci_dev *hdev, u8 gate, u8 response,
+                         const u8 *param, size_t param_len)
+{
+       u8 pipe;
+
+       pr_debug("\n");
+
+       pipe = hdev->gate2pipe[gate];
+       if (pipe == NFC_HCI_INVALID_PIPE)
+               return -EADDRNOTAVAIL;
+
+       return nfc_hci_hcp_message_tx(hdev, pipe, NFC_HCI_HCP_RESPONSE,
+                                     response, param, param_len, NULL, NULL,
+                                     0);
+}
+EXPORT_SYMBOL(nfc_hci_send_response);
+
+/*
+ * Execute an hci command sent to gate.
+ * skb will contain response data if success. skb can be NULL if you are not
+ * interested by the response.
+ */
+int nfc_hci_send_cmd(struct nfc_hci_dev *hdev, u8 gate, u8 cmd,
+                    const u8 *param, size_t param_len, struct sk_buff **skb)
+{
+       u8 pipe;
+
+       pr_debug("\n");
+
+       pipe = hdev->gate2pipe[gate];
+       if (pipe == NFC_HCI_INVALID_PIPE)
+               return -EADDRNOTAVAIL;
+
+       return nfc_hci_execute_cmd(hdev, pipe, cmd, param, param_len, skb);
+}
+EXPORT_SYMBOL(nfc_hci_send_cmd);
+
+int nfc_hci_set_param(struct nfc_hci_dev *hdev, u8 gate, u8 idx,
+                     const u8 *param, size_t param_len)
+{
+       int r;
+       u8 *tmp;
+
+       /* TODO ELa: reg idx must be inserted before param, but we don't want
+        * to ask the caller to do it to keep a simpler API.
+        * For now, just create a new temporary param buffer. This is far from
+        * optimal though, and the plan is to modify APIs to pass idx down to
+        * nfc_hci_hcp_message_tx where the frame is actually built, thereby
+        * eliminating the need for the temp allocation-copy here.
+        */
+
+       pr_debug("idx=%d to gate %d\n", idx, gate);
+
+       tmp = kmalloc(1 + param_len, GFP_KERNEL);
+       if (tmp == NULL)
+               return -ENOMEM;
+
+       *tmp = idx;
+       memcpy(tmp + 1, param, param_len);
+
+       r = nfc_hci_send_cmd(hdev, gate, NFC_HCI_ANY_SET_PARAMETER,
+                            tmp, param_len + 1, NULL);
+
+       kfree(tmp);
+
+       return r;
+}
+EXPORT_SYMBOL(nfc_hci_set_param);
+
+int nfc_hci_get_param(struct nfc_hci_dev *hdev, u8 gate, u8 idx,
+                     struct sk_buff **skb)
+{
+       pr_debug("gate=%d regidx=%d\n", gate, idx);
+
+       return nfc_hci_send_cmd(hdev, gate, NFC_HCI_ANY_GET_PARAMETER,
+                               &idx, 1, skb);
+}
+EXPORT_SYMBOL(nfc_hci_get_param);
+
+static int nfc_hci_open_pipe(struct nfc_hci_dev *hdev, u8 pipe)
+{
+       struct sk_buff *skb;
+       int r;
+
+       pr_debug("pipe=%d\n", pipe);
+
+       r = nfc_hci_execute_cmd(hdev, pipe, NFC_HCI_ANY_OPEN_PIPE,
+                               NULL, 0, &skb);
+       if (r == 0) {
+               /* dest host other than host controller will send
+                * number of pipes already open on this gate before
+                * execution. The number can be found in skb->data[0]
+                */
+               kfree_skb(skb);
+       }
+
+       return r;
+}
+
+static int nfc_hci_close_pipe(struct nfc_hci_dev *hdev, u8 pipe)
+{
+       pr_debug("\n");
+
+       return nfc_hci_execute_cmd(hdev, pipe, NFC_HCI_ANY_CLOSE_PIPE,
+                                  NULL, 0, NULL);
+}
+
+static u8 nfc_hci_create_pipe(struct nfc_hci_dev *hdev, u8 dest_host,
+                             u8 dest_gate, int *result)
+{
+       struct sk_buff *skb;
+       struct hci_create_pipe_params params;
+       struct hci_create_pipe_resp *resp;
+       u8 pipe;
+
+       pr_debug("gate=%d\n", dest_gate);
+
+       params.src_gate = NFC_HCI_ADMIN_GATE;
+       params.dest_host = dest_host;
+       params.dest_gate = dest_gate;
+
+       *result = nfc_hci_execute_cmd(hdev, NFC_HCI_ADMIN_PIPE,
+                                     NFC_HCI_ADM_CREATE_PIPE,
+                                     (u8 *) &params, sizeof(params), &skb);
+       if (*result == 0) {
+               resp = (struct hci_create_pipe_resp *)skb->data;
+               pipe = resp->pipe;
+               kfree_skb(skb);
+
+               pr_debug("pipe created=%d\n", pipe);
+
+               return pipe;
+       } else
+               return NFC_HCI_INVALID_PIPE;
+}
+
+static int nfc_hci_delete_pipe(struct nfc_hci_dev *hdev, u8 pipe)
+{
+       pr_debug("\n");
+
+       return nfc_hci_execute_cmd(hdev, NFC_HCI_ADMIN_PIPE,
+                                  NFC_HCI_ADM_DELETE_PIPE, &pipe, 1, NULL);
+}
+
+static int nfc_hci_clear_all_pipes(struct nfc_hci_dev *hdev)
+{
+       int r;
+
+       u8 param[2];
+
+       /* TODO: Find out what the identity reference data is
+        * and fill param with it. HCI spec 6.1.3.5 */
+
+       pr_debug("\n");
+
+       r = nfc_hci_execute_cmd(hdev, NFC_HCI_ADMIN_PIPE,
+                               NFC_HCI_ADM_CLEAR_ALL_PIPE, param, 2, NULL);
+
+       return 0;
+}
+
+int nfc_hci_disconnect_gate(struct nfc_hci_dev *hdev, u8 gate)
+{
+       int r;
+       u8 pipe = hdev->gate2pipe[gate];
+
+       pr_debug("\n");
+
+       if (pipe == NFC_HCI_INVALID_PIPE)
+               return -EADDRNOTAVAIL;
+
+       r = nfc_hci_close_pipe(hdev, pipe);
+       if (r < 0)
+               return r;
+
+       if (pipe != NFC_HCI_LINK_MGMT_PIPE && pipe != NFC_HCI_ADMIN_PIPE) {
+               r = nfc_hci_delete_pipe(hdev, pipe);
+               if (r < 0)
+                       return r;
+       }
+
+       hdev->gate2pipe[gate] = NFC_HCI_INVALID_PIPE;
+
+       return 0;
+}
+EXPORT_SYMBOL(nfc_hci_disconnect_gate);
+
+int nfc_hci_disconnect_all_gates(struct nfc_hci_dev *hdev)
+{
+       int r;
+
+       pr_debug("\n");
+
+       r = nfc_hci_clear_all_pipes(hdev);
+       if (r < 0)
+               return r;
+
+       memset(hdev->gate2pipe, NFC_HCI_INVALID_PIPE, sizeof(hdev->gate2pipe));
+
+       return 0;
+}
+EXPORT_SYMBOL(nfc_hci_disconnect_all_gates);
+
+int nfc_hci_connect_gate(struct nfc_hci_dev *hdev, u8 dest_host, u8 dest_gate,
+                        u8 pipe)
+{
+       bool pipe_created = false;
+       int r;
+
+       pr_debug("\n");
+
+       if (hdev->gate2pipe[dest_gate] != NFC_HCI_INVALID_PIPE)
+               return -EADDRINUSE;
+
+       if (pipe != NFC_HCI_INVALID_PIPE)
+               goto pipe_is_open;
+
+       switch (dest_gate) {
+       case NFC_HCI_LINK_MGMT_GATE:
+               pipe = NFC_HCI_LINK_MGMT_PIPE;
+               break;
+       case NFC_HCI_ADMIN_GATE:
+               pipe = NFC_HCI_ADMIN_PIPE;
+               break;
+       default:
+               pipe = nfc_hci_create_pipe(hdev, dest_host, dest_gate, &r);
+               if (pipe == NFC_HCI_INVALID_PIPE)
+                       return r;
+               pipe_created = true;
+               break;
+       }
+
+       r = nfc_hci_open_pipe(hdev, pipe);
+       if (r < 0) {
+               if (pipe_created)
+                       if (nfc_hci_delete_pipe(hdev, pipe) < 0) {
+                               /* TODO: Cannot clean by deleting pipe...
+                                * -> inconsistent state */
+                       }
+               return r;
+       }
+
+pipe_is_open:
+       hdev->gate2pipe[dest_gate] = pipe;
+
+       return 0;
+}
+EXPORT_SYMBOL(nfc_hci_connect_gate);
diff --git a/net/nfc/hci/core.c b/net/nfc/hci/core.c
new file mode 100644 (file)
index 0000000..e044801
--- /dev/null
@@ -0,0 +1,837 @@
+/*
+ * Copyright (C) 2012  Intel Corporation. All rights reserved.
+ *
+ * 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
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#define pr_fmt(fmt) "hci: %s: " fmt, __func__
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/nfc.h>
+
+#include <net/nfc/nfc.h>
+#include <net/nfc/hci.h>
+
+#include "hci.h"
+
+/* Largest headroom needed for outgoing HCI commands */
+#define HCI_CMDS_HEADROOM 1
+
+static int nfc_hci_result_to_errno(u8 result)
+{
+       switch (result) {
+       case NFC_HCI_ANY_OK:
+               return 0;
+       case NFC_HCI_ANY_E_TIMEOUT:
+               return -ETIME;
+       default:
+               return -1;
+       }
+}
+
+static void nfc_hci_msg_tx_work(struct work_struct *work)
+{
+       struct nfc_hci_dev *hdev = container_of(work, struct nfc_hci_dev,
+                                               msg_tx_work);
+       struct hci_msg *msg;
+       struct sk_buff *skb;
+       int r = 0;
+
+       mutex_lock(&hdev->msg_tx_mutex);
+
+       if (hdev->cmd_pending_msg) {
+               if (timer_pending(&hdev->cmd_timer) == 0) {
+                       if (hdev->cmd_pending_msg->cb)
+                               hdev->cmd_pending_msg->cb(hdev,
+                                                         -ETIME,
+                                                         NULL,
+                                                         hdev->
+                                                         cmd_pending_msg->
+                                                         cb_context);
+                       kfree(hdev->cmd_pending_msg);
+                       hdev->cmd_pending_msg = NULL;
+               } else
+                       goto exit;
+       }
+
+next_msg:
+       if (list_empty(&hdev->msg_tx_queue))
+               goto exit;
+
+       msg = list_first_entry(&hdev->msg_tx_queue, struct hci_msg, msg_l);
+       list_del(&msg->msg_l);
+
+       pr_debug("msg_tx_queue has a cmd to send\n");
+       while ((skb = skb_dequeue(&msg->msg_frags)) != NULL) {
+               r = hdev->ops->xmit(hdev, skb);
+               if (r < 0) {
+                       kfree_skb(skb);
+                       skb_queue_purge(&msg->msg_frags);
+                       if (msg->cb)
+                               msg->cb(hdev, r, NULL, msg->cb_context);
+                       kfree(msg);
+                       break;
+               }
+       }
+
+       if (r)
+               goto next_msg;
+
+       if (msg->wait_response == false) {
+               kfree(msg);
+               goto next_msg;
+       }
+
+       hdev->cmd_pending_msg = msg;
+       mod_timer(&hdev->cmd_timer, jiffies +
+                 msecs_to_jiffies(hdev->cmd_pending_msg->completion_delay));
+
+exit:
+       mutex_unlock(&hdev->msg_tx_mutex);
+}
+
+static void nfc_hci_msg_rx_work(struct work_struct *work)
+{
+       struct nfc_hci_dev *hdev = container_of(work, struct nfc_hci_dev,
+                                               msg_rx_work);
+       struct sk_buff *skb;
+       struct hcp_message *message;
+       u8 pipe;
+       u8 type;
+       u8 instruction;
+
+       while ((skb = skb_dequeue(&hdev->msg_rx_queue)) != NULL) {
+               pipe = skb->data[0];
+               skb_pull(skb, NFC_HCI_HCP_PACKET_HEADER_LEN);
+               message = (struct hcp_message *)skb->data;
+               type = HCP_MSG_GET_TYPE(message->header);
+               instruction = HCP_MSG_GET_CMD(message->header);
+               skb_pull(skb, NFC_HCI_HCP_MESSAGE_HEADER_LEN);
+
+               nfc_hci_hcp_message_rx(hdev, pipe, type, instruction, skb);
+       }
+}
+
+static void __nfc_hci_cmd_completion(struct nfc_hci_dev *hdev, int err,
+                                    struct sk_buff *skb)
+{
+       del_timer_sync(&hdev->cmd_timer);
+
+       if (hdev->cmd_pending_msg->cb)
+               hdev->cmd_pending_msg->cb(hdev, err, skb,
+                                         hdev->cmd_pending_msg->cb_context);
+       else
+               kfree_skb(skb);
+
+       kfree(hdev->cmd_pending_msg);
+       hdev->cmd_pending_msg = NULL;
+
+       queue_work(hdev->msg_tx_wq, &hdev->msg_tx_work);
+}
+
+void nfc_hci_resp_received(struct nfc_hci_dev *hdev, u8 result,
+                          struct sk_buff *skb)
+{
+       mutex_lock(&hdev->msg_tx_mutex);
+
+       if (hdev->cmd_pending_msg == NULL) {
+               kfree_skb(skb);
+               goto exit;
+       }
+
+       __nfc_hci_cmd_completion(hdev, nfc_hci_result_to_errno(result), skb);
+
+exit:
+       mutex_unlock(&hdev->msg_tx_mutex);
+}
+
+void nfc_hci_cmd_received(struct nfc_hci_dev *hdev, u8 pipe, u8 cmd,
+                         struct sk_buff *skb)
+{
+       kfree_skb(skb);
+}
+
+static u32 nfc_hci_sak_to_protocol(u8 sak)
+{
+       switch (NFC_HCI_TYPE_A_SEL_PROT(sak)) {
+       case NFC_HCI_TYPE_A_SEL_PROT_MIFARE:
+               return NFC_PROTO_MIFARE_MASK;
+       case NFC_HCI_TYPE_A_SEL_PROT_ISO14443:
+               return NFC_PROTO_ISO14443_MASK;
+       case NFC_HCI_TYPE_A_SEL_PROT_DEP:
+               return NFC_PROTO_NFC_DEP_MASK;
+       case NFC_HCI_TYPE_A_SEL_PROT_ISO14443_DEP:
+               return NFC_PROTO_ISO14443_MASK | NFC_PROTO_NFC_DEP_MASK;
+       default:
+               return 0xffffffff;
+       }
+}
+
+static int nfc_hci_target_discovered(struct nfc_hci_dev *hdev, u8 gate)
+{
+       struct nfc_target *targets;
+       struct sk_buff *atqa_skb = NULL;
+       struct sk_buff *sak_skb = NULL;
+       struct sk_buff *uid_skb = NULL;
+       int r;
+
+       pr_debug("from gate %d\n", gate);
+
+       targets = kzalloc(sizeof(struct nfc_target), GFP_KERNEL);
+       if (targets == NULL)
+               return -ENOMEM;
+
+       switch (gate) {
+       case NFC_HCI_RF_READER_A_GATE:
+               r = nfc_hci_get_param(hdev, NFC_HCI_RF_READER_A_GATE,
+                                     NFC_HCI_RF_READER_A_ATQA, &atqa_skb);
+               if (r < 0)
+                       goto exit;
+
+               r = nfc_hci_get_param(hdev, NFC_HCI_RF_READER_A_GATE,
+                                     NFC_HCI_RF_READER_A_SAK, &sak_skb);
+               if (r < 0)
+                       goto exit;
+
+               if (atqa_skb->len != 2 || sak_skb->len != 1) {
+                       r = -EPROTO;
+                       goto exit;
+               }
+
+               targets->supported_protocols =
+                               nfc_hci_sak_to_protocol(sak_skb->data[0]);
+               if (targets->supported_protocols == 0xffffffff) {
+                       r = -EPROTO;
+                       goto exit;
+               }
+
+               targets->sens_res = be16_to_cpu(*(u16 *)atqa_skb->data);
+               targets->sel_res = sak_skb->data[0];
+
+               r = nfc_hci_get_param(hdev, NFC_HCI_RF_READER_A_GATE,
+                                     NFC_HCI_RF_READER_A_UID, &uid_skb);
+               if (r < 0)
+                       goto exit;
+
+               if (uid_skb->len == 0 || uid_skb->len > NFC_NFCID1_MAXSIZE) {
+                       r = -EPROTO;
+                       goto exit;
+               }
+
+               memcpy (targets->nfcid1, uid_skb->data, uid_skb->len);
+               targets->nfcid1_len = uid_skb->len;
+
+               if (hdev->ops->complete_target_discovered) {
+                       r = hdev->ops->complete_target_discovered(hdev, gate,
+                                                                 targets);
+                       if (r < 0)
+                               goto exit;
+               }
+               break;
+       case NFC_HCI_RF_READER_B_GATE:
+               targets->supported_protocols = NFC_PROTO_ISO14443_B_MASK;
+               break;
+       default:
+               if (hdev->ops->target_from_gate)
+                       r = hdev->ops->target_from_gate(hdev, gate, targets);
+               else
+                       r = -EPROTO;
+               if (r < 0)
+                       goto exit;
+
+               if (hdev->ops->complete_target_discovered) {
+                       r = hdev->ops->complete_target_discovered(hdev, gate,
+                                                                 targets);
+                       if (r < 0)
+                               goto exit;
+               }
+               break;
+       }
+
+       targets->hci_reader_gate = gate;
+
+       r = nfc_targets_found(hdev->ndev, targets, 1);
+
+exit:
+       kfree(targets);
+       kfree_skb(atqa_skb);
+       kfree_skb(sak_skb);
+       kfree_skb(uid_skb);
+
+       return r;
+}
+
+void nfc_hci_event_received(struct nfc_hci_dev *hdev, u8 pipe, u8 event,
+                           struct sk_buff *skb)
+{
+       int r = 0;
+
+       switch (event) {
+       case NFC_HCI_EVT_TARGET_DISCOVERED:
+               if (skb->len < 1) {     /* no status data? */
+                       r = -EPROTO;
+                       goto exit;
+               }
+
+               if (skb->data[0] == 3) {
+                       /* TODO: Multiple targets in field, none activated
+                        * poll is supposedly stopped, but there is no
+                        * single target to activate, so nothing to report
+                        * up.
+                        * if we need to restart poll, we must save the
+                        * protocols from the initial poll and reuse here.
+                        */
+               }
+
+               if (skb->data[0] != 0) {
+                       r = -EPROTO;
+                       goto exit;
+               }
+
+               r = nfc_hci_target_discovered(hdev,
+                                             nfc_hci_pipe2gate(hdev, pipe));
+               break;
+       default:
+               /* TODO: Unknown events are hardware specific
+                * pass them to the driver (needs a new hci_ops) */
+               break;
+       }
+
+exit:
+       kfree_skb(skb);
+
+       if (r) {
+               /* TODO: There was an error dispatching the event,
+                * how to propagate up to nfc core?
+                */
+       }
+}
+
+static void nfc_hci_cmd_timeout(unsigned long data)
+{
+       struct nfc_hci_dev *hdev = (struct nfc_hci_dev *)data;
+
+       queue_work(hdev->msg_tx_wq, &hdev->msg_tx_work);
+}
+
+static int hci_dev_connect_gates(struct nfc_hci_dev *hdev, u8 gate_count,
+                                struct nfc_hci_gate *gates)
+{
+       int r;
+       while (gate_count--) {
+               r = nfc_hci_connect_gate(hdev, NFC_HCI_HOST_CONTROLLER_ID,
+                                        gates->gate, gates->pipe);
+               if (r < 0)
+                       return r;
+               gates++;
+       }
+
+       return 0;
+}
+
+static int hci_dev_session_init(struct nfc_hci_dev *hdev)
+{
+       struct sk_buff *skb = NULL;
+       int r;
+
+       if (hdev->init_data.gates[0].gate != NFC_HCI_ADMIN_GATE)
+               return -EPROTO;
+
+       r = nfc_hci_connect_gate(hdev, NFC_HCI_HOST_CONTROLLER_ID,
+                                hdev->init_data.gates[0].gate,
+                                hdev->init_data.gates[0].pipe);
+       if (r < 0)
+               goto exit;
+
+       r = nfc_hci_get_param(hdev, NFC_HCI_ADMIN_GATE,
+                             NFC_HCI_ADMIN_SESSION_IDENTITY, &skb);
+       if (r < 0)
+               goto disconnect_all;
+
+       if (skb->len && skb->len == strlen(hdev->init_data.session_id))
+               if (memcmp(hdev->init_data.session_id, skb->data,
+                          skb->len) == 0) {
+                       /* TODO ELa: restore gate<->pipe table from
+                        * some TBD location.
+                        * note: it doesn't seem possible to get the chip
+                        * currently open gate/pipe table.
+                        * It is only possible to obtain the supported
+                        * gate list.
+                        */
+
+                       /* goto exit
+                        * For now, always do a full initialization */
+               }
+
+       r = nfc_hci_disconnect_all_gates(hdev);
+       if (r < 0)
+               goto exit;
+
+       r = hci_dev_connect_gates(hdev, hdev->init_data.gate_count,
+                                 hdev->init_data.gates);
+       if (r < 0)
+               goto disconnect_all;
+
+       r = nfc_hci_set_param(hdev, NFC_HCI_ADMIN_GATE,
+                             NFC_HCI_ADMIN_SESSION_IDENTITY,
+                             hdev->init_data.session_id,
+                             strlen(hdev->init_data.session_id));
+       if (r == 0)
+               goto exit;
+
+disconnect_all:
+       nfc_hci_disconnect_all_gates(hdev);
+
+exit:
+       if (skb)
+               kfree_skb(skb);
+
+       return r;
+}
+
+static int hci_dev_version(struct nfc_hci_dev *hdev)
+{
+       int r;
+       struct sk_buff *skb;
+
+       r = nfc_hci_get_param(hdev, NFC_HCI_ID_MGMT_GATE,
+                             NFC_HCI_ID_MGMT_VERSION_SW, &skb);
+       if (r < 0)
+               return r;
+
+       if (skb->len != 3) {
+               kfree_skb(skb);
+               return -EINVAL;
+       }
+
+       hdev->sw_romlib = (skb->data[0] & 0xf0) >> 4;
+       hdev->sw_patch = skb->data[0] & 0x0f;
+       hdev->sw_flashlib_major = skb->data[1];
+       hdev->sw_flashlib_minor = skb->data[2];
+
+       kfree_skb(skb);
+
+       r = nfc_hci_get_param(hdev, NFC_HCI_ID_MGMT_GATE,
+                             NFC_HCI_ID_MGMT_VERSION_HW, &skb);
+       if (r < 0)
+               return r;
+
+       if (skb->len != 3) {
+               kfree_skb(skb);
+               return -EINVAL;
+       }
+
+       hdev->hw_derivative = (skb->data[0] & 0xe0) >> 5;
+       hdev->hw_version = skb->data[0] & 0x1f;
+       hdev->hw_mpw = (skb->data[1] & 0xc0) >> 6;
+       hdev->hw_software = skb->data[1] & 0x3f;
+       hdev->hw_bsid = skb->data[2];
+
+       kfree_skb(skb);
+
+       pr_info("SOFTWARE INFO:\n");
+       pr_info("RomLib         : %d\n", hdev->sw_romlib);
+       pr_info("Patch          : %d\n", hdev->sw_patch);
+       pr_info("FlashLib Major : %d\n", hdev->sw_flashlib_major);
+       pr_info("FlashLib Minor : %d\n", hdev->sw_flashlib_minor);
+       pr_info("HARDWARE INFO:\n");
+       pr_info("Derivative     : %d\n", hdev->hw_derivative);
+       pr_info("HW Version     : %d\n", hdev->hw_version);
+       pr_info("#MPW           : %d\n", hdev->hw_mpw);
+       pr_info("Software       : %d\n", hdev->hw_software);
+       pr_info("BSID Version   : %d\n", hdev->hw_bsid);
+
+       return 0;
+}
+
+static int hci_dev_up(struct nfc_dev *nfc_dev)
+{
+       struct nfc_hci_dev *hdev = nfc_get_drvdata(nfc_dev);
+       int r = 0;
+
+       if (hdev->ops->open) {
+               r = hdev->ops->open(hdev);
+               if (r < 0)
+                       return r;
+       }
+
+       r = hci_dev_session_init(hdev);
+       if (r < 0)
+               goto exit;
+
+       r = nfc_hci_send_event(hdev, NFC_HCI_RF_READER_A_GATE,
+                              NFC_HCI_EVT_END_OPERATION, NULL, 0);
+       if (r < 0)
+               goto exit;
+
+       if (hdev->ops->hci_ready) {
+               r = hdev->ops->hci_ready(hdev);
+               if (r < 0)
+                       goto exit;
+       }
+
+       r = hci_dev_version(hdev);
+       if (r < 0)
+               goto exit;
+
+exit:
+       if (r < 0)
+               if (hdev->ops->close)
+                       hdev->ops->close(hdev);
+       return r;
+}
+
+static int hci_dev_down(struct nfc_dev *nfc_dev)
+{
+       struct nfc_hci_dev *hdev = nfc_get_drvdata(nfc_dev);
+
+       if (hdev->ops->close)
+               hdev->ops->close(hdev);
+
+       memset(hdev->gate2pipe, NFC_HCI_INVALID_PIPE, sizeof(hdev->gate2pipe));
+
+       return 0;
+}
+
+static int hci_start_poll(struct nfc_dev *nfc_dev,
+                         u32 im_protocols, u32 tm_protocols)
+{
+       struct nfc_hci_dev *hdev = nfc_get_drvdata(nfc_dev);
+
+       if (hdev->ops->start_poll)
+               return hdev->ops->start_poll(hdev, im_protocols, tm_protocols);
+       else
+               return nfc_hci_send_event(hdev, NFC_HCI_RF_READER_A_GATE,
+                                      NFC_HCI_EVT_READER_REQUESTED, NULL, 0);
+}
+
+static void hci_stop_poll(struct nfc_dev *nfc_dev)
+{
+       struct nfc_hci_dev *hdev = nfc_get_drvdata(nfc_dev);
+
+       nfc_hci_send_event(hdev, NFC_HCI_RF_READER_A_GATE,
+                          NFC_HCI_EVT_END_OPERATION, NULL, 0);
+}
+
+static int hci_activate_target(struct nfc_dev *nfc_dev,
+                              struct nfc_target *target, u32 protocol)
+{
+       return 0;
+}
+
+static void hci_deactivate_target(struct nfc_dev *nfc_dev,
+                                 struct nfc_target *target)
+{
+}
+
+static int hci_transceive(struct nfc_dev *nfc_dev, struct nfc_target *target,
+                         struct sk_buff *skb, data_exchange_cb_t cb,
+                         void *cb_context)
+{
+       struct nfc_hci_dev *hdev = nfc_get_drvdata(nfc_dev);
+       int r;
+       struct sk_buff *res_skb = NULL;
+
+       pr_debug("target_idx=%d\n", target->idx);
+
+       switch (target->hci_reader_gate) {
+       case NFC_HCI_RF_READER_A_GATE:
+       case NFC_HCI_RF_READER_B_GATE:
+               if (hdev->ops->data_exchange) {
+                       r = hdev->ops->data_exchange(hdev, target, skb,
+                                                    &res_skb);
+                       if (r <= 0)     /* handled */
+                               break;
+               }
+
+               *skb_push(skb, 1) = 0;  /* CTR, see spec:10.2.2.1 */
+               r = nfc_hci_send_cmd(hdev, target->hci_reader_gate,
+                                    NFC_HCI_WR_XCHG_DATA,
+                                    skb->data, skb->len, &res_skb);
+               /*
+                * TODO: Check RF Error indicator to make sure data is valid.
+                * It seems that HCI cmd can complete without error, but data
+                * can be invalid if an RF error occured? Ignore for now.
+                */
+               if (r == 0)
+                       skb_trim(res_skb, res_skb->len - 1); /* RF Err ind */
+               break;
+       default:
+               if (hdev->ops->data_exchange) {
+                       r = hdev->ops->data_exchange(hdev, target, skb,
+                                                    &res_skb);
+                       if (r == 1)
+                               r = -ENOTSUPP;
+               }
+               else
+                       r = -ENOTSUPP;
+       }
+
+       kfree_skb(skb);
+
+       cb(cb_context, res_skb, r);
+
+       return 0;
+}
+
+static int hci_check_presence(struct nfc_dev *nfc_dev,
+                             struct nfc_target *target)
+{
+#if 0
+       struct nfc_hci_dev *hdev = nfc_get_drvdata(nfc_dev);
+
+       if (hdev->ops->check_presence)
+               return hdev->ops->check_presence(hdev, target);
+#endif
+
+       return 0;
+}
+
+static struct nfc_ops hci_nfc_ops = {
+       .dev_up = hci_dev_up,
+       .dev_down = hci_dev_down,
+       .start_poll = hci_start_poll,
+       .stop_poll = hci_stop_poll,
+       .activate_target = hci_activate_target,
+       .deactivate_target = hci_deactivate_target,
+       .im_transceive = hci_transceive,
+       .check_presence = hci_check_presence,
+};
+
+struct nfc_hci_dev *nfc_hci_allocate_device(struct nfc_hci_ops *ops,
+                                           struct nfc_hci_init_data *init_data,
+                                           u32 protocols,
+                                           int tx_headroom,
+                                           int tx_tailroom,
+                                           int max_link_payload)
+{
+       struct nfc_hci_dev *hdev;
+
+       if (ops->xmit == NULL)
+               return NULL;
+
+       if (protocols == 0)
+               return NULL;
+
+       hdev = kzalloc(sizeof(struct nfc_hci_dev), GFP_KERNEL);
+       if (hdev == NULL)
+               return NULL;
+
+       hdev->ndev = nfc_allocate_device(&hci_nfc_ops, protocols,
+                                        tx_headroom + HCI_CMDS_HEADROOM,
+                                        tx_tailroom);
+       if (!hdev->ndev) {
+               kfree(hdev);
+               return NULL;
+       }
+
+       hdev->ops = ops;
+       hdev->max_data_link_payload = max_link_payload;
+       hdev->init_data = *init_data;
+
+       nfc_set_drvdata(hdev->ndev, hdev);
+
+       memset(hdev->gate2pipe, NFC_HCI_INVALID_PIPE, sizeof(hdev->gate2pipe));
+
+       return hdev;
+}
+EXPORT_SYMBOL(nfc_hci_allocate_device);
+
+void nfc_hci_free_device(struct nfc_hci_dev *hdev)
+{
+       nfc_free_device(hdev->ndev);
+       kfree(hdev);
+}
+EXPORT_SYMBOL(nfc_hci_free_device);
+
+int nfc_hci_register_device(struct nfc_hci_dev *hdev)
+{
+       struct device *dev = &hdev->ndev->dev;
+       const char *devname = dev_name(dev);
+       char name[32];
+       int r = 0;
+
+       mutex_init(&hdev->msg_tx_mutex);
+
+       INIT_LIST_HEAD(&hdev->msg_tx_queue);
+
+       INIT_WORK(&hdev->msg_tx_work, nfc_hci_msg_tx_work);
+       snprintf(name, sizeof(name), "%s_hci_msg_tx_wq", devname);
+       hdev->msg_tx_wq = alloc_workqueue(name, WQ_NON_REENTRANT | WQ_UNBOUND |
+                                         WQ_MEM_RECLAIM, 1);
+       if (hdev->msg_tx_wq == NULL) {
+               r = -ENOMEM;
+               goto exit;
+       }
+
+       init_timer(&hdev->cmd_timer);
+       hdev->cmd_timer.data = (unsigned long)hdev;
+       hdev->cmd_timer.function = nfc_hci_cmd_timeout;
+
+       skb_queue_head_init(&hdev->rx_hcp_frags);
+
+       INIT_WORK(&hdev->msg_rx_work, nfc_hci_msg_rx_work);
+       snprintf(name, sizeof(name), "%s_hci_msg_rx_wq", devname);
+       hdev->msg_rx_wq = alloc_workqueue(name, WQ_NON_REENTRANT | WQ_UNBOUND |
+                                         WQ_MEM_RECLAIM, 1);
+       if (hdev->msg_rx_wq == NULL) {
+               r = -ENOMEM;
+               goto exit;
+       }
+
+       skb_queue_head_init(&hdev->msg_rx_queue);
+
+       r = nfc_register_device(hdev->ndev);
+
+exit:
+       if (r < 0) {
+               if (hdev->msg_tx_wq)
+                       destroy_workqueue(hdev->msg_tx_wq);
+               if (hdev->msg_rx_wq)
+                       destroy_workqueue(hdev->msg_rx_wq);
+       }
+
+       return r;
+}
+EXPORT_SYMBOL(nfc_hci_register_device);
+
+void nfc_hci_unregister_device(struct nfc_hci_dev *hdev)
+{
+       struct hci_msg *msg, *n;
+
+       skb_queue_purge(&hdev->rx_hcp_frags);
+       skb_queue_purge(&hdev->msg_rx_queue);
+
+       list_for_each_entry_safe(msg, n, &hdev->msg_tx_queue, msg_l) {
+               list_del(&msg->msg_l);
+               skb_queue_purge(&msg->msg_frags);
+               kfree(msg);
+       }
+
+       del_timer_sync(&hdev->cmd_timer);
+
+       nfc_unregister_device(hdev->ndev);
+
+       destroy_workqueue(hdev->msg_tx_wq);
+
+       destroy_workqueue(hdev->msg_rx_wq);
+}
+EXPORT_SYMBOL(nfc_hci_unregister_device);
+
+void nfc_hci_set_clientdata(struct nfc_hci_dev *hdev, void *clientdata)
+{
+       hdev->clientdata = clientdata;
+}
+EXPORT_SYMBOL(nfc_hci_set_clientdata);
+
+void *nfc_hci_get_clientdata(struct nfc_hci_dev *hdev)
+{
+       return hdev->clientdata;
+}
+EXPORT_SYMBOL(nfc_hci_get_clientdata);
+
+static void nfc_hci_failure(struct nfc_hci_dev *hdev, int err)
+{
+       mutex_lock(&hdev->msg_tx_mutex);
+
+       if (hdev->cmd_pending_msg == NULL) {
+               nfc_driver_failure(hdev->ndev, err);
+               goto exit;
+       }
+
+       __nfc_hci_cmd_completion(hdev, err, NULL);
+
+exit:
+       mutex_unlock(&hdev->msg_tx_mutex);
+}
+
+void nfc_hci_driver_failure(struct nfc_hci_dev *hdev, int err)
+{
+       nfc_hci_failure(hdev, err);
+}
+EXPORT_SYMBOL(nfc_hci_driver_failure);
+
+void nfc_hci_recv_frame(struct nfc_hci_dev *hdev, struct sk_buff *skb)
+{
+       struct hcp_packet *packet;
+       u8 type;
+       u8 instruction;
+       struct sk_buff *hcp_skb;
+       u8 pipe;
+       struct sk_buff *frag_skb;
+       int msg_len;
+
+       packet = (struct hcp_packet *)skb->data;
+       if ((packet->header & ~NFC_HCI_FRAGMENT) == 0) {
+               skb_queue_tail(&hdev->rx_hcp_frags, skb);
+               return;
+       }
+
+       /* it's the last fragment. Does it need re-aggregation? */
+       if (skb_queue_len(&hdev->rx_hcp_frags)) {
+               pipe = packet->header & NFC_HCI_FRAGMENT;
+               skb_queue_tail(&hdev->rx_hcp_frags, skb);
+
+               msg_len = 0;
+               skb_queue_walk(&hdev->rx_hcp_frags, frag_skb) {
+                       msg_len += (frag_skb->len -
+                                   NFC_HCI_HCP_PACKET_HEADER_LEN);
+               }
+
+               hcp_skb = nfc_alloc_recv_skb(NFC_HCI_HCP_PACKET_HEADER_LEN +
+                                            msg_len, GFP_KERNEL);
+               if (hcp_skb == NULL) {
+                       nfc_hci_failure(hdev, -ENOMEM);
+                       return;
+               }
+
+               *skb_put(hcp_skb, NFC_HCI_HCP_PACKET_HEADER_LEN) = pipe;
+
+               skb_queue_walk(&hdev->rx_hcp_frags, frag_skb) {
+                       msg_len = frag_skb->len - NFC_HCI_HCP_PACKET_HEADER_LEN;
+                       memcpy(skb_put(hcp_skb, msg_len),
+                              frag_skb->data + NFC_HCI_HCP_PACKET_HEADER_LEN,
+                              msg_len);
+               }
+
+               skb_queue_purge(&hdev->rx_hcp_frags);
+       } else {
+               packet->header &= NFC_HCI_FRAGMENT;
+               hcp_skb = skb;
+       }
+
+       /* if this is a response, dispatch immediately to
+        * unblock waiting cmd context. Otherwise, enqueue to dispatch
+        * in separate context where handler can also execute command.
+        */
+       packet = (struct hcp_packet *)hcp_skb->data;
+       type = HCP_MSG_GET_TYPE(packet->message.header);
+       if (type == NFC_HCI_HCP_RESPONSE) {
+               pipe = packet->header;
+               instruction = HCP_MSG_GET_CMD(packet->message.header);
+               skb_pull(hcp_skb, NFC_HCI_HCP_PACKET_HEADER_LEN +
+                        NFC_HCI_HCP_MESSAGE_HEADER_LEN);
+               nfc_hci_hcp_message_rx(hdev, pipe, type, instruction, hcp_skb);
+       } else {
+               skb_queue_tail(&hdev->msg_rx_queue, hcp_skb);
+               queue_work(hdev->msg_rx_wq, &hdev->msg_rx_work);
+       }
+}
+EXPORT_SYMBOL(nfc_hci_recv_frame);
+
+MODULE_LICENSE("GPL");
diff --git a/net/nfc/hci/hci.h b/net/nfc/hci/hci.h
new file mode 100644 (file)
index 0000000..fa9a21e
--- /dev/null
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2012  Intel Corporation. All rights reserved.
+ *
+ * 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
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __LOCAL_HCI_H
+#define __LOCAL_HCI_H
+
+struct gate_pipe_map {
+       u8 gate;
+       u8 pipe;
+};
+
+struct hcp_message {
+       u8 header;              /* type -cmd,evt,rsp- + instruction */
+       u8 data[];
+} __packed;
+
+struct hcp_packet {
+       u8 header;              /* cbit+pipe */
+       struct hcp_message message;
+} __packed;
+
+/*
+ * HCI command execution completion callback.
+ * result will be a standard linux error (may be converted from HCI response)
+ * skb contains the response data and must be disposed, or may be NULL if
+ * an error occured
+ */
+typedef void (*hci_cmd_cb_t) (struct nfc_hci_dev *hdev, int result,
+                             struct sk_buff *skb, void *cb_data);
+
+struct hcp_exec_waiter {
+       wait_queue_head_t *wq;
+       bool exec_complete;
+       int exec_result;
+       struct sk_buff *result_skb;
+};
+
+struct hci_msg {
+       struct list_head msg_l;
+       struct sk_buff_head msg_frags;
+       bool wait_response;
+       hci_cmd_cb_t cb;
+       void *cb_context;
+       unsigned long completion_delay;
+};
+
+struct hci_create_pipe_params {
+       u8 src_gate;
+       u8 dest_host;
+       u8 dest_gate;
+} __packed;
+
+struct hci_create_pipe_resp {
+       u8 src_host;
+       u8 src_gate;
+       u8 dest_host;
+       u8 dest_gate;
+       u8 pipe;
+} __packed;
+
+#define NFC_HCI_FRAGMENT       0x7f
+
+#define HCP_HEADER(type, instr) ((((type) & 0x03) << 6) | ((instr) & 0x3f))
+#define HCP_MSG_GET_TYPE(header) ((header & 0xc0) >> 6)
+#define HCP_MSG_GET_CMD(header) (header & 0x3f)
+
+int nfc_hci_hcp_message_tx(struct nfc_hci_dev *hdev, u8 pipe,
+                          u8 type, u8 instruction,
+                          const u8 *payload, size_t payload_len,
+                          hci_cmd_cb_t cb, void *cb_data,
+                          unsigned long completion_delay);
+
+u8 nfc_hci_pipe2gate(struct nfc_hci_dev *hdev, u8 pipe);
+
+void nfc_hci_hcp_message_rx(struct nfc_hci_dev *hdev, u8 pipe, u8 type,
+                           u8 instruction, struct sk_buff *skb);
+
+/* HCP headers */
+#define NFC_HCI_HCP_PACKET_HEADER_LEN  1
+#define NFC_HCI_HCP_MESSAGE_HEADER_LEN 1
+#define NFC_HCI_HCP_HEADER_LEN         2
+
+/* HCP types */
+#define NFC_HCI_HCP_COMMAND    0x00
+#define NFC_HCI_HCP_EVENT      0x01
+#define NFC_HCI_HCP_RESPONSE   0x02
+
+/* Generic commands */
+#define NFC_HCI_ANY_SET_PARAMETER      0x01
+#define NFC_HCI_ANY_GET_PARAMETER      0x02
+#define NFC_HCI_ANY_OPEN_PIPE          0x03
+#define NFC_HCI_ANY_CLOSE_PIPE         0x04
+
+/* Reader RF commands */
+#define NFC_HCI_WR_XCHG_DATA           0x10
+
+/* Admin commands */
+#define NFC_HCI_ADM_CREATE_PIPE                        0x10
+#define NFC_HCI_ADM_DELETE_PIPE                        0x11
+#define NFC_HCI_ADM_NOTIFY_PIPE_CREATED                0x12
+#define NFC_HCI_ADM_NOTIFY_PIPE_DELETED                0x13
+#define NFC_HCI_ADM_CLEAR_ALL_PIPE             0x14
+#define NFC_HCI_ADM_NOTIFY_ALL_PIPE_CLEARED    0x15
+
+/* Generic responses */
+#define NFC_HCI_ANY_OK                         0x00
+#define NFC_HCI_ANY_E_NOT_CONNECTED            0x01
+#define NFC_HCI_ANY_E_CMD_PAR_UNKNOWN          0x02
+#define NFC_HCI_ANY_E_NOK                      0x03
+#define NFC_HCI_ANY_E_PIPES_FULL               0x04
+#define NFC_HCI_ANY_E_REG_PAR_UNKNOWN          0x05
+#define NFC_HCI_ANY_E_PIPE_NOT_OPENED          0x06
+#define NFC_HCI_ANY_E_CMD_NOT_SUPPORTED                0x07
+#define NFC_HCI_ANY_E_INHIBITED                        0x08
+#define NFC_HCI_ANY_E_TIMEOUT                  0x09
+#define NFC_HCI_ANY_E_REG_ACCESS_DENIED                0x0a
+#define NFC_HCI_ANY_E_PIPE_ACCESS_DENIED       0x0b
+
+#endif /* __LOCAL_HCI_H */
diff --git a/net/nfc/hci/hcp.c b/net/nfc/hci/hcp.c
new file mode 100644 (file)
index 0000000..f4dad1a
--- /dev/null
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2012  Intel Corporation. All rights reserved.
+ *
+ * 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
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#define pr_fmt(fmt) "hci: %s: " fmt, __func__
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+#include <net/nfc/hci.h>
+
+#include "hci.h"
+
+/*
+ * Payload is the HCP message data only. Instruction will be prepended.
+ * Guarantees that cb will be called upon completion or timeout delay
+ * counted from the moment the cmd is sent to the transport.
+ */
+int nfc_hci_hcp_message_tx(struct nfc_hci_dev *hdev, u8 pipe,
+                          u8 type, u8 instruction,
+                          const u8 *payload, size_t payload_len,
+                          hci_cmd_cb_t cb, void *cb_data,
+                          unsigned long completion_delay)
+{
+       struct nfc_dev *ndev = hdev->ndev;
+       struct hci_msg *cmd;
+       const u8 *ptr = payload;
+       int hci_len, err;
+       bool firstfrag = true;
+
+       cmd = kzalloc(sizeof(struct hci_msg), GFP_KERNEL);
+       if (cmd == NULL)
+               return -ENOMEM;
+
+       INIT_LIST_HEAD(&cmd->msg_l);
+       skb_queue_head_init(&cmd->msg_frags);
+       cmd->wait_response = (type == NFC_HCI_HCP_COMMAND) ? true : false;
+       cmd->cb = cb;
+       cmd->cb_context = cb_data;
+       cmd->completion_delay = completion_delay;
+
+       hci_len = payload_len + 1;
+       while (hci_len > 0) {
+               struct sk_buff *skb;
+               int skb_len, data_link_len;
+               struct hcp_packet *packet;
+
+               if (NFC_HCI_HCP_PACKET_HEADER_LEN + hci_len <=
+                   hdev->max_data_link_payload)
+                       data_link_len = hci_len;
+               else
+                       data_link_len = hdev->max_data_link_payload -
+                                       NFC_HCI_HCP_PACKET_HEADER_LEN;
+
+               skb_len = ndev->tx_headroom + NFC_HCI_HCP_PACKET_HEADER_LEN +
+                         data_link_len + ndev->tx_tailroom;
+               hci_len -= data_link_len;
+
+               skb = alloc_skb(skb_len, GFP_KERNEL);
+               if (skb == NULL) {
+                       err = -ENOMEM;
+                       goto out_skb_err;
+               }
+               skb_reserve(skb, ndev->tx_headroom);
+
+               skb_put(skb, NFC_HCI_HCP_PACKET_HEADER_LEN + data_link_len);
+
+               /* Only the last fragment will have the cb bit set to 1 */
+               packet = (struct hcp_packet *)skb->data;
+               packet->header = pipe;
+               if (firstfrag) {
+                       firstfrag = false;
+                       packet->message.header = HCP_HEADER(type, instruction);
+                       if (ptr) {
+                               memcpy(packet->message.data, ptr,
+                                      data_link_len - 1);
+                               ptr += data_link_len - 1;
+                       }
+               } else {
+                       memcpy(&packet->message, ptr, data_link_len);
+                       ptr += data_link_len;
+               }
+
+               /* This is the last fragment, set the cb bit */
+               if (hci_len == 0)
+                       packet->header |= ~NFC_HCI_FRAGMENT;
+
+               skb_queue_tail(&cmd->msg_frags, skb);
+       }
+
+       mutex_lock(&hdev->msg_tx_mutex);
+       list_add_tail(&cmd->msg_l, &hdev->msg_tx_queue);
+       mutex_unlock(&hdev->msg_tx_mutex);
+
+       queue_work(hdev->msg_tx_wq, &hdev->msg_tx_work);
+
+       return 0;
+
+out_skb_err:
+       skb_queue_purge(&cmd->msg_frags);
+       kfree(cmd);
+
+       return err;
+}
+
+u8 nfc_hci_pipe2gate(struct nfc_hci_dev *hdev, u8 pipe)
+{
+       int gate;
+
+       for (gate = 0; gate < NFC_HCI_MAX_GATES; gate++)
+               if (hdev->gate2pipe[gate] == pipe)
+                       return gate;
+
+       return 0xff;
+}
+
+/*
+ * Receive hcp message for pipe, with type and cmd.
+ * skb contains optional message data only.
+ */
+void nfc_hci_hcp_message_rx(struct nfc_hci_dev *hdev, u8 pipe, u8 type,
+                           u8 instruction, struct sk_buff *skb)
+{
+       switch (type) {
+       case NFC_HCI_HCP_RESPONSE:
+               nfc_hci_resp_received(hdev, instruction, skb);
+               break;
+       case NFC_HCI_HCP_COMMAND:
+               nfc_hci_cmd_received(hdev, pipe, instruction, skb);
+               break;
+       case NFC_HCI_HCP_EVENT:
+               nfc_hci_event_received(hdev, pipe, instruction, skb);
+               break;
+       default:
+               pr_err("UNKNOWN MSG Type %d, instruction=%d\n",
+                      type, instruction);
+               kfree_skb(skb);
+               break;
+       }
+}
diff --git a/net/nfc/hci/shdlc.c b/net/nfc/hci/shdlc.c
new file mode 100644 (file)
index 0000000..256be0f
--- /dev/null
@@ -0,0 +1,950 @@
+/*
+ * Copyright (C) 2012  Intel Corporation. All rights reserved.
+ *
+ * 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
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#define pr_fmt(fmt) "shdlc: %s: " fmt, __func__
+
+#include <linux/sched.h>
+#include <linux/wait.h>
+#include <linux/crc-ccitt.h>
+#include <linux/slab.h>
+#include <linux/skbuff.h>
+
+#include <net/nfc/hci.h>
+#include <net/nfc/shdlc.h>
+
+#define SHDLC_LLC_HEAD_ROOM    2
+#define SHDLC_LLC_TAIL_ROOM    2
+
+#define SHDLC_MAX_WINDOW       4
+#define SHDLC_SREJ_SUPPORT     false
+
+#define SHDLC_CONTROL_HEAD_MASK        0xe0
+#define SHDLC_CONTROL_HEAD_I   0x80
+#define SHDLC_CONTROL_HEAD_I2  0xa0
+#define SHDLC_CONTROL_HEAD_S   0xc0
+#define SHDLC_CONTROL_HEAD_U   0xe0
+
+#define SHDLC_CONTROL_NS_MASK  0x38
+#define SHDLC_CONTROL_NR_MASK  0x07
+#define SHDLC_CONTROL_TYPE_MASK        0x18
+
+#define SHDLC_CONTROL_M_MASK   0x1f
+
+enum sframe_type {
+       S_FRAME_RR = 0x00,
+       S_FRAME_REJ = 0x01,
+       S_FRAME_RNR = 0x02,
+       S_FRAME_SREJ = 0x03
+};
+
+enum uframe_modifier {
+       U_FRAME_UA = 0x06,
+       U_FRAME_RSET = 0x19
+};
+
+#define SHDLC_CONNECT_VALUE_MS 5
+#define SHDLC_T1_VALUE_MS(w)   ((5 * w) / 4)
+#define SHDLC_T2_VALUE_MS      300
+
+#define SHDLC_DUMP_SKB(info, skb)                                \
+do {                                                             \
+       pr_debug("%s:\n", info);                                  \
+       print_hex_dump(KERN_DEBUG, "shdlc: ", DUMP_PREFIX_OFFSET, \
+                      16, 1, skb->data, skb->len, 0);            \
+} while (0)
+
+/* checks x < y <= z modulo 8 */
+static bool nfc_shdlc_x_lt_y_lteq_z(int x, int y, int z)
+{
+       if (x < z)
+               return ((x < y) && (y <= z)) ? true : false;
+       else
+               return ((y > x) || (y <= z)) ? true : false;
+}
+
+/* checks x <= y < z modulo 8 */
+static bool nfc_shdlc_x_lteq_y_lt_z(int x, int y, int z)
+{
+       if (x <= z)
+               return ((x <= y) && (y < z)) ? true : false;
+       else                    /* x > z -> z+8 > x */
+               return ((y >= x) || (y < z)) ? true : false;
+}
+
+static struct sk_buff *nfc_shdlc_alloc_skb(struct nfc_shdlc *shdlc,
+                                          int payload_len)
+{
+       struct sk_buff *skb;
+
+       skb = alloc_skb(shdlc->client_headroom + SHDLC_LLC_HEAD_ROOM +
+                       shdlc->client_tailroom + SHDLC_LLC_TAIL_ROOM +
+                       payload_len, GFP_KERNEL);
+       if (skb)
+               skb_reserve(skb, shdlc->client_headroom + SHDLC_LLC_HEAD_ROOM);
+
+       return skb;
+}
+
+static void nfc_shdlc_add_len_crc(struct sk_buff *skb)
+{
+       u16 crc;
+       int len;
+
+       len = skb->len + 2;
+       *skb_push(skb, 1) = len;
+
+       crc = crc_ccitt(0xffff, skb->data, skb->len);
+       crc = ~crc;
+       *skb_put(skb, 1) = crc & 0xff;
+       *skb_put(skb, 1) = crc >> 8;
+}
+
+/* immediately sends an S frame. */
+static int nfc_shdlc_send_s_frame(struct nfc_shdlc *shdlc,
+                                 enum sframe_type sframe_type, int nr)
+{
+       int r;
+       struct sk_buff *skb;
+
+       pr_debug("sframe_type=%d nr=%d\n", sframe_type, nr);
+
+       skb = nfc_shdlc_alloc_skb(shdlc, 0);
+       if (skb == NULL)
+               return -ENOMEM;
+
+       *skb_push(skb, 1) = SHDLC_CONTROL_HEAD_S | (sframe_type << 3) | nr;
+
+       nfc_shdlc_add_len_crc(skb);
+
+       r = shdlc->ops->xmit(shdlc, skb);
+
+       kfree_skb(skb);
+
+       return r;
+}
+
+/* immediately sends an U frame. skb may contain optional payload */
+static int nfc_shdlc_send_u_frame(struct nfc_shdlc *shdlc,
+                                 struct sk_buff *skb,
+                                 enum uframe_modifier uframe_modifier)
+{
+       int r;
+
+       pr_debug("uframe_modifier=%d\n", uframe_modifier);
+
+       *skb_push(skb, 1) = SHDLC_CONTROL_HEAD_U | uframe_modifier;
+
+       nfc_shdlc_add_len_crc(skb);
+
+       r = shdlc->ops->xmit(shdlc, skb);
+
+       kfree_skb(skb);
+
+       return r;
+}
+
+/*
+ * Free ack_pending frames until y_nr - 1, and reset t2 according to
+ * the remaining oldest ack_pending frame sent time
+ */
+static void nfc_shdlc_reset_t2(struct nfc_shdlc *shdlc, int y_nr)
+{
+       struct sk_buff *skb;
+       int dnr = shdlc->dnr;   /* MUST initially be < y_nr */
+
+       pr_debug("release ack pending up to frame %d excluded\n", y_nr);
+
+       while (dnr != y_nr) {
+               pr_debug("release ack pending frame %d\n", dnr);
+
+               skb = skb_dequeue(&shdlc->ack_pending_q);
+               kfree_skb(skb);
+
+               dnr = (dnr + 1) % 8;
+       }
+
+       if (skb_queue_empty(&shdlc->ack_pending_q)) {
+               if (shdlc->t2_active) {
+                       del_timer_sync(&shdlc->t2_timer);
+                       shdlc->t2_active = false;
+
+                       pr_debug
+                           ("All sent frames acked. Stopped T2(retransmit)\n");
+               }
+       } else {
+               skb = skb_peek(&shdlc->ack_pending_q);
+
+               mod_timer(&shdlc->t2_timer, *(unsigned long *)skb->cb +
+                         msecs_to_jiffies(SHDLC_T2_VALUE_MS));
+               shdlc->t2_active = true;
+
+               pr_debug
+                   ("Start T2(retransmit) for remaining unacked sent frames\n");
+       }
+}
+
+/*
+ * Receive validated frames from lower layer. skb contains HCI payload only.
+ * Handle according to algorithm at spec:10.8.2
+ */
+static void nfc_shdlc_rcv_i_frame(struct nfc_shdlc *shdlc,
+                                 struct sk_buff *skb, int ns, int nr)
+{
+       int x_ns = ns;
+       int y_nr = nr;
+
+       pr_debug("recvd I-frame %d, remote waiting frame %d\n", ns, nr);
+
+       if (shdlc->state != SHDLC_CONNECTED)
+               goto exit;
+
+       if (x_ns != shdlc->nr) {
+               nfc_shdlc_send_s_frame(shdlc, S_FRAME_REJ, shdlc->nr);
+               goto exit;
+       }
+
+       if (shdlc->t1_active == false) {
+               shdlc->t1_active = true;
+               mod_timer(&shdlc->t1_timer, jiffies +
+                         msecs_to_jiffies(SHDLC_T1_VALUE_MS(shdlc->w)));
+               pr_debug("(re)Start T1(send ack)\n");
+       }
+
+       if (skb->len) {
+               nfc_hci_recv_frame(shdlc->hdev, skb);
+               skb = NULL;
+       }
+
+       shdlc->nr = (shdlc->nr + 1) % 8;
+
+       if (nfc_shdlc_x_lt_y_lteq_z(shdlc->dnr, y_nr, shdlc->ns)) {
+               nfc_shdlc_reset_t2(shdlc, y_nr);
+
+               shdlc->dnr = y_nr;
+       }
+
+exit:
+       if (skb)
+               kfree_skb(skb);
+}
+
+static void nfc_shdlc_rcv_ack(struct nfc_shdlc *shdlc, int y_nr)
+{
+       pr_debug("remote acked up to frame %d excluded\n", y_nr);
+
+       if (nfc_shdlc_x_lt_y_lteq_z(shdlc->dnr, y_nr, shdlc->ns)) {
+               nfc_shdlc_reset_t2(shdlc, y_nr);
+               shdlc->dnr = y_nr;
+       }
+}
+
+static void nfc_shdlc_requeue_ack_pending(struct nfc_shdlc *shdlc)
+{
+       struct sk_buff *skb;
+
+       pr_debug("ns reset to %d\n", shdlc->dnr);
+
+       while ((skb = skb_dequeue_tail(&shdlc->ack_pending_q))) {
+               skb_pull(skb, 2);       /* remove len+control */
+               skb_trim(skb, skb->len - 2);    /* remove crc */
+               skb_queue_head(&shdlc->send_q, skb);
+       }
+       shdlc->ns = shdlc->dnr;
+}
+
+static void nfc_shdlc_rcv_rej(struct nfc_shdlc *shdlc, int y_nr)
+{
+       struct sk_buff *skb;
+
+       pr_debug("remote asks retransmition from frame %d\n", y_nr);
+
+       if (nfc_shdlc_x_lteq_y_lt_z(shdlc->dnr, y_nr, shdlc->ns)) {
+               if (shdlc->t2_active) {
+                       del_timer_sync(&shdlc->t2_timer);
+                       shdlc->t2_active = false;
+                       pr_debug("Stopped T2(retransmit)\n");
+               }
+
+               if (shdlc->dnr != y_nr) {
+                       while ((shdlc->dnr = ((shdlc->dnr + 1) % 8)) != y_nr) {
+                               skb = skb_dequeue(&shdlc->ack_pending_q);
+                               kfree_skb(skb);
+                       }
+               }
+
+               nfc_shdlc_requeue_ack_pending(shdlc);
+       }
+}
+
+/* See spec RR:10.8.3 REJ:10.8.4 */
+static void nfc_shdlc_rcv_s_frame(struct nfc_shdlc *shdlc,
+                                 enum sframe_type s_frame_type, int nr)
+{
+       struct sk_buff *skb;
+
+       if (shdlc->state != SHDLC_CONNECTED)
+               return;
+
+       switch (s_frame_type) {
+       case S_FRAME_RR:
+               nfc_shdlc_rcv_ack(shdlc, nr);
+               if (shdlc->rnr == true) {       /* see SHDLC 10.7.7 */
+                       shdlc->rnr = false;
+                       if (shdlc->send_q.qlen == 0) {
+                               skb = nfc_shdlc_alloc_skb(shdlc, 0);
+                               if (skb)
+                                       skb_queue_tail(&shdlc->send_q, skb);
+                       }
+               }
+               break;
+       case S_FRAME_REJ:
+               nfc_shdlc_rcv_rej(shdlc, nr);
+               break;
+       case S_FRAME_RNR:
+               nfc_shdlc_rcv_ack(shdlc, nr);
+               shdlc->rnr = true;
+               break;
+       default:
+               break;
+       }
+}
+
+static void nfc_shdlc_connect_complete(struct nfc_shdlc *shdlc, int r)
+{
+       pr_debug("result=%d\n", r);
+
+       del_timer_sync(&shdlc->connect_timer);
+
+       if (r == 0) {
+               shdlc->ns = 0;
+               shdlc->nr = 0;
+               shdlc->dnr = 0;
+
+               shdlc->state = SHDLC_CONNECTED;
+       } else {
+               shdlc->state = SHDLC_DISCONNECTED;
+       }
+
+       shdlc->connect_result = r;
+
+       wake_up(shdlc->connect_wq);
+}
+
+static int nfc_shdlc_connect_initiate(struct nfc_shdlc *shdlc)
+{
+       struct sk_buff *skb;
+
+       pr_debug("\n");
+
+       skb = nfc_shdlc_alloc_skb(shdlc, 2);
+       if (skb == NULL)
+               return -ENOMEM;
+
+       *skb_put(skb, 1) = SHDLC_MAX_WINDOW;
+       *skb_put(skb, 1) = SHDLC_SREJ_SUPPORT ? 1 : 0;
+
+       return nfc_shdlc_send_u_frame(shdlc, skb, U_FRAME_RSET);
+}
+
+static int nfc_shdlc_connect_send_ua(struct nfc_shdlc *shdlc)
+{
+       struct sk_buff *skb;
+
+       pr_debug("\n");
+
+       skb = nfc_shdlc_alloc_skb(shdlc, 0);
+       if (skb == NULL)
+               return -ENOMEM;
+
+       return nfc_shdlc_send_u_frame(shdlc, skb, U_FRAME_UA);
+}
+
+static void nfc_shdlc_rcv_u_frame(struct nfc_shdlc *shdlc,
+                                 struct sk_buff *skb,
+                                 enum uframe_modifier u_frame_modifier)
+{
+       u8 w = SHDLC_MAX_WINDOW;
+       bool srej_support = SHDLC_SREJ_SUPPORT;
+       int r;
+
+       pr_debug("u_frame_modifier=%d\n", u_frame_modifier);
+
+       switch (u_frame_modifier) {
+       case U_FRAME_RSET:
+               if (shdlc->state == SHDLC_NEGOCIATING) {
+                       /* we sent RSET, but chip wants to negociate */
+                       if (skb->len > 0)
+                               w = skb->data[0];
+
+                       if (skb->len > 1)
+                               srej_support = skb->data[1] & 0x01 ? true :
+                                              false;
+
+                       if ((w <= SHDLC_MAX_WINDOW) &&
+                           (SHDLC_SREJ_SUPPORT || (srej_support == false))) {
+                               shdlc->w = w;
+                               shdlc->srej_support = srej_support;
+                               r = nfc_shdlc_connect_send_ua(shdlc);
+                               nfc_shdlc_connect_complete(shdlc, r);
+                       }
+               } else if (shdlc->state == SHDLC_CONNECTED) {
+                       /*
+                        * Chip wants to reset link. This is unexpected and
+                        * unsupported.
+                        */
+                       shdlc->hard_fault = -ECONNRESET;
+               }
+               break;
+       case U_FRAME_UA:
+               if ((shdlc->state == SHDLC_CONNECTING &&
+                    shdlc->connect_tries > 0) ||
+                   (shdlc->state == SHDLC_NEGOCIATING))
+                       nfc_shdlc_connect_complete(shdlc, 0);
+               break;
+       default:
+               break;
+       }
+
+       kfree_skb(skb);
+}
+
+static void nfc_shdlc_handle_rcv_queue(struct nfc_shdlc *shdlc)
+{
+       struct sk_buff *skb;
+       u8 control;
+       int nr;
+       int ns;
+       enum sframe_type s_frame_type;
+       enum uframe_modifier u_frame_modifier;
+
+       if (shdlc->rcv_q.qlen)
+               pr_debug("rcvQlen=%d\n", shdlc->rcv_q.qlen);
+
+       while ((skb = skb_dequeue(&shdlc->rcv_q)) != NULL) {
+               control = skb->data[0];
+               skb_pull(skb, 1);
+               switch (control & SHDLC_CONTROL_HEAD_MASK) {
+               case SHDLC_CONTROL_HEAD_I:
+               case SHDLC_CONTROL_HEAD_I2:
+                       ns = (control & SHDLC_CONTROL_NS_MASK) >> 3;
+                       nr = control & SHDLC_CONTROL_NR_MASK;
+                       nfc_shdlc_rcv_i_frame(shdlc, skb, ns, nr);
+                       break;
+               case SHDLC_CONTROL_HEAD_S:
+                       s_frame_type = (control & SHDLC_CONTROL_TYPE_MASK) >> 3;
+                       nr = control & SHDLC_CONTROL_NR_MASK;
+                       nfc_shdlc_rcv_s_frame(shdlc, s_frame_type, nr);
+                       kfree_skb(skb);
+                       break;
+               case SHDLC_CONTROL_HEAD_U:
+                       u_frame_modifier = control & SHDLC_CONTROL_M_MASK;
+                       nfc_shdlc_rcv_u_frame(shdlc, skb, u_frame_modifier);
+                       break;
+               default:
+                       pr_err("UNKNOWN Control=%d\n", control);
+                       kfree_skb(skb);
+                       break;
+               }
+       }
+}
+
+static int nfc_shdlc_w_used(int ns, int dnr)
+{
+       int unack_count;
+
+       if (dnr <= ns)
+               unack_count = ns - dnr;
+       else
+               unack_count = 8 - dnr + ns;
+
+       return unack_count;
+}
+
+/* Send frames according to algorithm at spec:10.8.1 */
+static void nfc_shdlc_handle_send_queue(struct nfc_shdlc *shdlc)
+{
+       struct sk_buff *skb;
+       int r;
+       unsigned long time_sent;
+
+       if (shdlc->send_q.qlen)
+               pr_debug
+                   ("sendQlen=%d ns=%d dnr=%d rnr=%s w_room=%d unackQlen=%d\n",
+                    shdlc->send_q.qlen, shdlc->ns, shdlc->dnr,
+                    shdlc->rnr == false ? "false" : "true",
+                    shdlc->w - nfc_shdlc_w_used(shdlc->ns, shdlc->dnr),
+                    shdlc->ack_pending_q.qlen);
+
+       while (shdlc->send_q.qlen && shdlc->ack_pending_q.qlen < shdlc->w &&
+              (shdlc->rnr == false)) {
+
+               if (shdlc->t1_active) {
+                       del_timer_sync(&shdlc->t1_timer);
+                       shdlc->t1_active = false;
+                       pr_debug("Stopped T1(send ack)\n");
+               }
+
+               skb = skb_dequeue(&shdlc->send_q);
+
+               *skb_push(skb, 1) = SHDLC_CONTROL_HEAD_I | (shdlc->ns << 3) |
+                                   shdlc->nr;
+
+               pr_debug("Sending I-Frame %d, waiting to rcv %d\n", shdlc->ns,
+                        shdlc->nr);
+       /*      SHDLC_DUMP_SKB("shdlc frame written", skb); */
+
+               nfc_shdlc_add_len_crc(skb);
+
+               r = shdlc->ops->xmit(shdlc, skb);
+               if (r < 0) {
+                       shdlc->hard_fault = r;
+                       break;
+               }
+
+               shdlc->ns = (shdlc->ns + 1) % 8;
+
+               time_sent = jiffies;
+               *(unsigned long *)skb->cb = time_sent;
+
+               skb_queue_tail(&shdlc->ack_pending_q, skb);
+
+               if (shdlc->t2_active == false) {
+                       shdlc->t2_active = true;
+                       mod_timer(&shdlc->t2_timer, time_sent +
+                                 msecs_to_jiffies(SHDLC_T2_VALUE_MS));
+                       pr_debug("Started T2 (retransmit)\n");
+               }
+       }
+}
+
+static void nfc_shdlc_connect_timeout(unsigned long data)
+{
+       struct nfc_shdlc *shdlc = (struct nfc_shdlc *)data;
+
+       pr_debug("\n");
+
+       queue_work(shdlc->sm_wq, &shdlc->sm_work);
+}
+
+static void nfc_shdlc_t1_timeout(unsigned long data)
+{
+       struct nfc_shdlc *shdlc = (struct nfc_shdlc *)data;
+
+       pr_debug("SoftIRQ: need to send ack\n");
+
+       queue_work(shdlc->sm_wq, &shdlc->sm_work);
+}
+
+static void nfc_shdlc_t2_timeout(unsigned long data)
+{
+       struct nfc_shdlc *shdlc = (struct nfc_shdlc *)data;
+
+       pr_debug("SoftIRQ: need to retransmit\n");
+
+       queue_work(shdlc->sm_wq, &shdlc->sm_work);
+}
+
+static void nfc_shdlc_sm_work(struct work_struct *work)
+{
+       struct nfc_shdlc *shdlc = container_of(work, struct nfc_shdlc, sm_work);
+       int r;
+
+       pr_debug("\n");
+
+       mutex_lock(&shdlc->state_mutex);
+
+       switch (shdlc->state) {
+       case SHDLC_DISCONNECTED:
+               skb_queue_purge(&shdlc->rcv_q);
+               skb_queue_purge(&shdlc->send_q);
+               skb_queue_purge(&shdlc->ack_pending_q);
+               break;
+       case SHDLC_CONNECTING:
+               if (shdlc->hard_fault) {
+                       nfc_shdlc_connect_complete(shdlc, shdlc->hard_fault);
+                       break;
+               }
+
+               if (shdlc->connect_tries++ < 5)
+                       r = nfc_shdlc_connect_initiate(shdlc);
+               else
+                       r = -ETIME;
+               if (r < 0)
+                       nfc_shdlc_connect_complete(shdlc, r);
+               else {
+                       mod_timer(&shdlc->connect_timer, jiffies +
+                                 msecs_to_jiffies(SHDLC_CONNECT_VALUE_MS));
+
+                       shdlc->state = SHDLC_NEGOCIATING;
+               }
+               break;
+       case SHDLC_NEGOCIATING:
+               if (timer_pending(&shdlc->connect_timer) == 0) {
+                       shdlc->state = SHDLC_CONNECTING;
+                       queue_work(shdlc->sm_wq, &shdlc->sm_work);
+               }
+
+               nfc_shdlc_handle_rcv_queue(shdlc);
+
+               if (shdlc->hard_fault) {
+                       nfc_shdlc_connect_complete(shdlc, shdlc->hard_fault);
+                       break;
+               }
+               break;
+       case SHDLC_CONNECTED:
+               nfc_shdlc_handle_rcv_queue(shdlc);
+               nfc_shdlc_handle_send_queue(shdlc);
+
+               if (shdlc->t1_active && timer_pending(&shdlc->t1_timer) == 0) {
+                       pr_debug
+                           ("Handle T1(send ack) elapsed (T1 now inactive)\n");
+
+                       shdlc->t1_active = false;
+                       r = nfc_shdlc_send_s_frame(shdlc, S_FRAME_RR,
+                                                  shdlc->nr);
+                       if (r < 0)
+                               shdlc->hard_fault = r;
+               }
+
+               if (shdlc->t2_active && timer_pending(&shdlc->t2_timer) == 0) {
+                       pr_debug
+                           ("Handle T2(retransmit) elapsed (T2 inactive)\n");
+
+                       shdlc->t2_active = false;
+
+                       nfc_shdlc_requeue_ack_pending(shdlc);
+                       nfc_shdlc_handle_send_queue(shdlc);
+               }
+
+               if (shdlc->hard_fault) {
+                       nfc_hci_driver_failure(shdlc->hdev, shdlc->hard_fault);
+               }
+               break;
+       default:
+               break;
+       }
+       mutex_unlock(&shdlc->state_mutex);
+}
+
+/*
+ * Called from syscall context to establish shdlc link. Sleeps until
+ * link is ready or failure.
+ */
+static int nfc_shdlc_connect(struct nfc_shdlc *shdlc)
+{
+       DECLARE_WAIT_QUEUE_HEAD_ONSTACK(connect_wq);
+
+       pr_debug("\n");
+
+       mutex_lock(&shdlc->state_mutex);
+
+       shdlc->state = SHDLC_CONNECTING;
+       shdlc->connect_wq = &connect_wq;
+       shdlc->connect_tries = 0;
+       shdlc->connect_result = 1;
+
+       mutex_unlock(&shdlc->state_mutex);
+
+       queue_work(shdlc->sm_wq, &shdlc->sm_work);
+
+       wait_event(connect_wq, shdlc->connect_result != 1);
+
+       return shdlc->connect_result;
+}
+
+static void nfc_shdlc_disconnect(struct nfc_shdlc *shdlc)
+{
+       pr_debug("\n");
+
+       mutex_lock(&shdlc->state_mutex);
+
+       shdlc->state = SHDLC_DISCONNECTED;
+
+       mutex_unlock(&shdlc->state_mutex);
+
+       queue_work(shdlc->sm_wq, &shdlc->sm_work);
+}
+
+/*
+ * Receive an incoming shdlc frame. Frame has already been crc-validated.
+ * skb contains only LLC header and payload.
+ * If skb == NULL, it is a notification that the link below is dead.
+ */
+void nfc_shdlc_recv_frame(struct nfc_shdlc *shdlc, struct sk_buff *skb)
+{
+       if (skb == NULL) {
+               pr_err("NULL Frame -> link is dead\n");
+               shdlc->hard_fault = -EREMOTEIO;
+       } else {
+               SHDLC_DUMP_SKB("incoming frame", skb);
+               skb_queue_tail(&shdlc->rcv_q, skb);
+       }
+
+       queue_work(shdlc->sm_wq, &shdlc->sm_work);
+}
+EXPORT_SYMBOL(nfc_shdlc_recv_frame);
+
+static int nfc_shdlc_open(struct nfc_hci_dev *hdev)
+{
+       struct nfc_shdlc *shdlc = nfc_hci_get_clientdata(hdev);
+       int r;
+
+       pr_debug("\n");
+
+       if (shdlc->ops->open) {
+               r = shdlc->ops->open(shdlc);
+               if (r < 0)
+                       return r;
+       }
+
+       r = nfc_shdlc_connect(shdlc);
+       if (r < 0 && shdlc->ops->close)
+               shdlc->ops->close(shdlc);
+
+       return r;
+}
+
+static void nfc_shdlc_close(struct nfc_hci_dev *hdev)
+{
+       struct nfc_shdlc *shdlc = nfc_hci_get_clientdata(hdev);
+
+       pr_debug("\n");
+
+       nfc_shdlc_disconnect(shdlc);
+
+       if (shdlc->ops->close)
+               shdlc->ops->close(shdlc);
+}
+
+static int nfc_shdlc_hci_ready(struct nfc_hci_dev *hdev)
+{
+       struct nfc_shdlc *shdlc = nfc_hci_get_clientdata(hdev);
+       int r = 0;
+
+       pr_debug("\n");
+
+       if (shdlc->ops->hci_ready)
+               r = shdlc->ops->hci_ready(shdlc);
+
+       return r;
+}
+
+static int nfc_shdlc_xmit(struct nfc_hci_dev *hdev, struct sk_buff *skb)
+{
+       struct nfc_shdlc *shdlc = nfc_hci_get_clientdata(hdev);
+
+       SHDLC_DUMP_SKB("queuing HCP packet to shdlc", skb);
+
+       skb_queue_tail(&shdlc->send_q, skb);
+
+       queue_work(shdlc->sm_wq, &shdlc->sm_work);
+
+       return 0;
+}
+
+static int nfc_shdlc_start_poll(struct nfc_hci_dev *hdev,
+                               u32 im_protocols, u32 tm_protocols)
+{
+       struct nfc_shdlc *shdlc = nfc_hci_get_clientdata(hdev);
+
+       pr_debug("\n");
+
+       if (shdlc->ops->start_poll)
+               return shdlc->ops->start_poll(shdlc,
+                                             im_protocols, tm_protocols);
+
+       return 0;
+}
+
+static int nfc_shdlc_target_from_gate(struct nfc_hci_dev *hdev, u8 gate,
+                                     struct nfc_target *target)
+{
+       struct nfc_shdlc *shdlc = nfc_hci_get_clientdata(hdev);
+
+       if (shdlc->ops->target_from_gate)
+               return shdlc->ops->target_from_gate(shdlc, gate, target);
+
+       return -EPERM;
+}
+
+static int nfc_shdlc_complete_target_discovered(struct nfc_hci_dev *hdev,
+                                               u8 gate,
+                                               struct nfc_target *target)
+{
+       struct nfc_shdlc *shdlc = nfc_hci_get_clientdata(hdev);
+
+       pr_debug("\n");
+
+       if (shdlc->ops->complete_target_discovered)
+               return shdlc->ops->complete_target_discovered(shdlc, gate,
+                                                             target);
+
+       return 0;
+}
+
+static int nfc_shdlc_data_exchange(struct nfc_hci_dev *hdev,
+                                  struct nfc_target *target,
+                                  struct sk_buff *skb,
+                                  struct sk_buff **res_skb)
+{
+       struct nfc_shdlc *shdlc = nfc_hci_get_clientdata(hdev);
+
+       if (shdlc->ops->data_exchange)
+               return shdlc->ops->data_exchange(shdlc, target, skb, res_skb);
+
+       return -EPERM;
+}
+
+static int nfc_shdlc_check_presence(struct nfc_hci_dev *hdev,
+                                   struct nfc_target *target)
+{
+       struct nfc_shdlc *shdlc = nfc_hci_get_clientdata(hdev);
+
+       if (shdlc->ops->check_presence)
+               return shdlc->ops->check_presence(shdlc, target);
+
+       return 0;
+}
+
+static struct nfc_hci_ops shdlc_ops = {
+       .open = nfc_shdlc_open,
+       .close = nfc_shdlc_close,
+       .hci_ready = nfc_shdlc_hci_ready,
+       .xmit = nfc_shdlc_xmit,
+       .start_poll = nfc_shdlc_start_poll,
+       .target_from_gate = nfc_shdlc_target_from_gate,
+       .complete_target_discovered = nfc_shdlc_complete_target_discovered,
+       .data_exchange = nfc_shdlc_data_exchange,
+       .check_presence = nfc_shdlc_check_presence,
+};
+
+struct nfc_shdlc *nfc_shdlc_allocate(struct nfc_shdlc_ops *ops,
+                                    struct nfc_hci_init_data *init_data,
+                                    u32 protocols,
+                                    int tx_headroom, int tx_tailroom,
+                                    int max_link_payload, const char *devname)
+{
+       struct nfc_shdlc *shdlc;
+       int r;
+       char name[32];
+
+       if (ops->xmit == NULL)
+               return NULL;
+
+       shdlc = kzalloc(sizeof(struct nfc_shdlc), GFP_KERNEL);
+       if (shdlc == NULL)
+               return NULL;
+
+       mutex_init(&shdlc->state_mutex);
+       shdlc->ops = ops;
+       shdlc->state = SHDLC_DISCONNECTED;
+
+       init_timer(&shdlc->connect_timer);
+       shdlc->connect_timer.data = (unsigned long)shdlc;
+       shdlc->connect_timer.function = nfc_shdlc_connect_timeout;
+
+       init_timer(&shdlc->t1_timer);
+       shdlc->t1_timer.data = (unsigned long)shdlc;
+       shdlc->t1_timer.function = nfc_shdlc_t1_timeout;
+
+       init_timer(&shdlc->t2_timer);
+       shdlc->t2_timer.data = (unsigned long)shdlc;
+       shdlc->t2_timer.function = nfc_shdlc_t2_timeout;
+
+       shdlc->w = SHDLC_MAX_WINDOW;
+       shdlc->srej_support = SHDLC_SREJ_SUPPORT;
+
+       skb_queue_head_init(&shdlc->rcv_q);
+       skb_queue_head_init(&shdlc->send_q);
+       skb_queue_head_init(&shdlc->ack_pending_q);
+
+       INIT_WORK(&shdlc->sm_work, nfc_shdlc_sm_work);
+       snprintf(name, sizeof(name), "%s_shdlc_sm_wq", devname);
+       shdlc->sm_wq = alloc_workqueue(name, WQ_NON_REENTRANT | WQ_UNBOUND |
+                                      WQ_MEM_RECLAIM, 1);
+       if (shdlc->sm_wq == NULL)
+               goto err_allocwq;
+
+       shdlc->client_headroom = tx_headroom;
+       shdlc->client_tailroom = tx_tailroom;
+
+       shdlc->hdev = nfc_hci_allocate_device(&shdlc_ops, init_data, protocols,
+                                             tx_headroom + SHDLC_LLC_HEAD_ROOM,
+                                             tx_tailroom + SHDLC_LLC_TAIL_ROOM,
+                                             max_link_payload);
+       if (shdlc->hdev == NULL)
+               goto err_allocdev;
+
+       nfc_hci_set_clientdata(shdlc->hdev, shdlc);
+
+       r = nfc_hci_register_device(shdlc->hdev);
+       if (r < 0)
+               goto err_regdev;
+
+       return shdlc;
+
+err_regdev:
+       nfc_hci_free_device(shdlc->hdev);
+
+err_allocdev:
+       destroy_workqueue(shdlc->sm_wq);
+
+err_allocwq:
+       kfree(shdlc);
+
+       return NULL;
+}
+EXPORT_SYMBOL(nfc_shdlc_allocate);
+
+void nfc_shdlc_free(struct nfc_shdlc *shdlc)
+{
+       pr_debug("\n");
+
+       nfc_hci_unregister_device(shdlc->hdev);
+       nfc_hci_free_device(shdlc->hdev);
+
+       destroy_workqueue(shdlc->sm_wq);
+
+       skb_queue_purge(&shdlc->rcv_q);
+       skb_queue_purge(&shdlc->send_q);
+       skb_queue_purge(&shdlc->ack_pending_q);
+
+       kfree(shdlc);
+}
+EXPORT_SYMBOL(nfc_shdlc_free);
+
+void nfc_shdlc_set_clientdata(struct nfc_shdlc *shdlc, void *clientdata)
+{
+       pr_debug("\n");
+
+       shdlc->clientdata = clientdata;
+}
+EXPORT_SYMBOL(nfc_shdlc_set_clientdata);
+
+void *nfc_shdlc_get_clientdata(struct nfc_shdlc *shdlc)
+{
+       return shdlc->clientdata;
+}
+EXPORT_SYMBOL(nfc_shdlc_get_clientdata);
+
+struct nfc_hci_dev *nfc_shdlc_get_hci_dev(struct nfc_shdlc *shdlc)
+{
+       return shdlc->hdev;
+}
+EXPORT_SYMBOL(nfc_shdlc_get_hci_dev);
diff --git a/net/nfc/llcp/Kconfig b/net/nfc/llcp/Kconfig
new file mode 100644 (file)
index 0000000..fbf5e81
--- /dev/null
@@ -0,0 +1,7 @@
+config NFC_LLCP
+       depends on NFC && EXPERIMENTAL
+       bool "NFC LLCP support (EXPERIMENTAL)"
+       default n
+       help
+        Say Y here if you want to build support for a kernel NFC LLCP
+        implementation.
\ No newline at end of file
diff --git a/net/nfc/llcp/commands.c b/net/nfc/llcp/commands.c
new file mode 100644 (file)
index 0000000..b982b5b
--- /dev/null
@@ -0,0 +1,564 @@
+/*
+ * Copyright (C) 2011  Intel Corporation. All rights reserved.
+ *
+ * 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
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#define pr_fmt(fmt) "llcp: %s: " fmt, __func__
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/nfc.h>
+
+#include <net/nfc/nfc.h>
+
+#include "../nfc.h"
+#include "llcp.h"
+
+static u8 llcp_tlv_length[LLCP_TLV_MAX] = {
+       0,
+       1, /* VERSION */
+       2, /* MIUX */
+       2, /* WKS */
+       1, /* LTO */
+       1, /* RW */
+       0, /* SN */
+       1, /* OPT */
+       0, /* SDREQ */
+       2, /* SDRES */
+
+};
+
+static u8 llcp_tlv8(u8 *tlv, u8 type)
+{
+       if (tlv[0] != type || tlv[1] != llcp_tlv_length[tlv[0]])
+               return 0;
+
+       return tlv[2];
+}
+
+static u16 llcp_tlv16(u8 *tlv, u8 type)
+{
+       if (tlv[0] != type || tlv[1] != llcp_tlv_length[tlv[0]])
+               return 0;
+
+       return be16_to_cpu(*((__be16 *)(tlv + 2)));
+}
+
+
+static u8 llcp_tlv_version(u8 *tlv)
+{
+       return llcp_tlv8(tlv, LLCP_TLV_VERSION);
+}
+
+static u16 llcp_tlv_miux(u8 *tlv)
+{
+       return llcp_tlv16(tlv, LLCP_TLV_MIUX) & 0x7ff;
+}
+
+static u16 llcp_tlv_wks(u8 *tlv)
+{
+       return llcp_tlv16(tlv, LLCP_TLV_WKS);
+}
+
+static u16 llcp_tlv_lto(u8 *tlv)
+{
+       return llcp_tlv8(tlv, LLCP_TLV_LTO);
+}
+
+static u8 llcp_tlv_opt(u8 *tlv)
+{
+       return llcp_tlv8(tlv, LLCP_TLV_OPT);
+}
+
+static u8 llcp_tlv_rw(u8 *tlv)
+{
+       return llcp_tlv8(tlv, LLCP_TLV_RW) & 0xf;
+}
+
+u8 *nfc_llcp_build_tlv(u8 type, u8 *value, u8 value_length, u8 *tlv_length)
+{
+       u8 *tlv, length;
+
+       pr_debug("type %d\n", type);
+
+       if (type >= LLCP_TLV_MAX)
+               return NULL;
+
+       length = llcp_tlv_length[type];
+       if (length == 0 && value_length == 0)
+               return NULL;
+       else if (length == 0)
+               length = value_length;
+
+       *tlv_length = 2 + length;
+       tlv = kzalloc(2 + length, GFP_KERNEL);
+       if (tlv == NULL)
+               return tlv;
+
+       tlv[0] = type;
+       tlv[1] = length;
+       memcpy(tlv + 2, value, length);
+
+       return tlv;
+}
+
+int nfc_llcp_parse_gb_tlv(struct nfc_llcp_local *local,
+                         u8 *tlv_array, u16 tlv_array_len)
+{
+       u8 *tlv = tlv_array, type, length, offset = 0;
+
+       pr_debug("TLV array length %d\n", tlv_array_len);
+
+       if (local == NULL)
+               return -ENODEV;
+
+       while (offset < tlv_array_len) {
+               type = tlv[0];
+               length = tlv[1];
+
+               pr_debug("type 0x%x length %d\n", type, length);
+
+               switch (type) {
+               case LLCP_TLV_VERSION:
+                       local->remote_version = llcp_tlv_version(tlv);
+                       break;
+               case LLCP_TLV_MIUX:
+                       local->remote_miu = llcp_tlv_miux(tlv) + 128;
+                       break;
+               case LLCP_TLV_WKS:
+                       local->remote_wks = llcp_tlv_wks(tlv);
+                       break;
+               case LLCP_TLV_LTO:
+                       local->remote_lto = llcp_tlv_lto(tlv) * 10;
+                       break;
+               case LLCP_TLV_OPT:
+                       local->remote_opt = llcp_tlv_opt(tlv);
+                       break;
+               default:
+                       pr_err("Invalid gt tlv value 0x%x\n", type);
+                       break;
+               }
+
+               offset += length + 2;
+               tlv += length + 2;
+       }
+
+       pr_debug("version 0x%x miu %d lto %d opt 0x%x wks 0x%x\n",
+                local->remote_version, local->remote_miu,
+                local->remote_lto, local->remote_opt,
+                local->remote_wks);
+
+       return 0;
+}
+
+int nfc_llcp_parse_connection_tlv(struct nfc_llcp_sock *sock,
+                                 u8 *tlv_array, u16 tlv_array_len)
+{
+       u8 *tlv = tlv_array, type, length, offset = 0;
+
+       pr_debug("TLV array length %d\n", tlv_array_len);
+
+       if (sock == NULL)
+               return -ENOTCONN;
+
+       while (offset < tlv_array_len) {
+               type = tlv[0];
+               length = tlv[1];
+
+               pr_debug("type 0x%x length %d\n", type, length);
+
+               switch (type) {
+               case LLCP_TLV_MIUX:
+                       sock->miu = llcp_tlv_miux(tlv) + 128;
+                       break;
+               case LLCP_TLV_RW:
+                       sock->rw = llcp_tlv_rw(tlv);
+                       break;
+               case LLCP_TLV_SN:
+                       break;
+               default:
+                       pr_err("Invalid gt tlv value 0x%x\n", type);
+                       break;
+               }
+
+               offset += length + 2;
+               tlv += length + 2;
+       }
+
+       pr_debug("sock %p rw %d miu %d\n", sock, sock->rw, sock->miu);
+
+       return 0;
+}
+
+static struct sk_buff *llcp_add_header(struct sk_buff *pdu,
+                                      u8 dsap, u8 ssap, u8 ptype)
+{
+       u8 header[2];
+
+       pr_debug("ptype 0x%x dsap 0x%x ssap 0x%x\n", ptype, dsap, ssap);
+
+       header[0] = (u8)((dsap << 2) | (ptype >> 2));
+       header[1] = (u8)((ptype << 6) | ssap);
+
+       pr_debug("header 0x%x 0x%x\n", header[0], header[1]);
+
+       memcpy(skb_put(pdu, LLCP_HEADER_SIZE), header, LLCP_HEADER_SIZE);
+
+       return pdu;
+}
+
+static struct sk_buff *llcp_add_tlv(struct sk_buff *pdu, u8 *tlv,
+                                   u8 tlv_length)
+{
+       /* XXX Add an skb length check */
+
+       if (tlv == NULL)
+               return NULL;
+
+       memcpy(skb_put(pdu, tlv_length), tlv, tlv_length);
+
+       return pdu;
+}
+
+static struct sk_buff *llcp_allocate_pdu(struct nfc_llcp_sock *sock,
+                                        u8 cmd, u16 size)
+{
+       struct sk_buff *skb;
+       int err;
+
+       if (sock->ssap == 0)
+               return NULL;
+
+       skb = nfc_alloc_send_skb(sock->dev, &sock->sk, MSG_DONTWAIT,
+                                size + LLCP_HEADER_SIZE, &err);
+       if (skb == NULL) {
+               pr_err("Could not allocate PDU\n");
+               return NULL;
+       }
+
+       skb = llcp_add_header(skb, sock->dsap, sock->ssap, cmd);
+
+       return skb;
+}
+
+int nfc_llcp_disconnect(struct nfc_llcp_sock *sock)
+{
+       struct sk_buff *skb;
+       struct nfc_dev *dev;
+       struct nfc_llcp_local *local;
+       u16 size = 0;
+
+       pr_debug("Sending DISC\n");
+
+       local = sock->local;
+       if (local == NULL)
+               return -ENODEV;
+
+       dev = sock->dev;
+       if (dev == NULL)
+               return -ENODEV;
+
+       size += LLCP_HEADER_SIZE;
+       size += dev->tx_headroom + dev->tx_tailroom + NFC_HEADER_SIZE;
+
+       skb = alloc_skb(size, GFP_ATOMIC);
+       if (skb == NULL)
+               return -ENOMEM;
+
+       skb_reserve(skb, dev->tx_headroom + NFC_HEADER_SIZE);
+
+       skb = llcp_add_header(skb, sock->dsap, sock->ssap, LLCP_PDU_DISC);
+
+       skb_queue_tail(&local->tx_queue, skb);
+
+       return 0;
+}
+
+int nfc_llcp_send_symm(struct nfc_dev *dev)
+{
+       struct sk_buff *skb;
+       struct nfc_llcp_local *local;
+       u16 size = 0;
+
+       pr_debug("Sending SYMM\n");
+
+       local = nfc_llcp_find_local(dev);
+       if (local == NULL)
+               return -ENODEV;
+
+       size += LLCP_HEADER_SIZE;
+       size += dev->tx_headroom + dev->tx_tailroom + NFC_HEADER_SIZE;
+
+       skb = alloc_skb(size, GFP_KERNEL);
+       if (skb == NULL)
+               return -ENOMEM;
+
+       skb_reserve(skb, dev->tx_headroom + NFC_HEADER_SIZE);
+
+       skb = llcp_add_header(skb, 0, 0, LLCP_PDU_SYMM);
+
+       return nfc_data_exchange(dev, local->target_idx, skb,
+                                nfc_llcp_recv, local);
+}
+
+int nfc_llcp_send_connect(struct nfc_llcp_sock *sock)
+{
+       struct nfc_llcp_local *local;
+       struct sk_buff *skb;
+       u8 *service_name_tlv = NULL, service_name_tlv_length;
+       u8 *miux_tlv = NULL, miux_tlv_length;
+       u8 *rw_tlv = NULL, rw_tlv_length, rw;
+       __be16 miux;
+       int err;
+       u16 size = 0;
+
+       pr_debug("Sending CONNECT\n");
+
+       local = sock->local;
+       if (local == NULL)
+               return -ENODEV;
+
+       if (sock->service_name != NULL) {
+               service_name_tlv = nfc_llcp_build_tlv(LLCP_TLV_SN,
+                                                     sock->service_name,
+                                                     sock->service_name_len,
+                                                     &service_name_tlv_length);
+               size += service_name_tlv_length;
+       }
+
+       miux = cpu_to_be16(LLCP_MAX_MIUX);
+       miux_tlv = nfc_llcp_build_tlv(LLCP_TLV_MIUX, (u8 *)&miux, 0,
+                                     &miux_tlv_length);
+       size += miux_tlv_length;
+
+       rw = LLCP_MAX_RW;
+       rw_tlv = nfc_llcp_build_tlv(LLCP_TLV_RW, &rw, 0, &rw_tlv_length);
+       size += rw_tlv_length;
+
+       pr_debug("SKB size %d SN length %zu\n", size, sock->service_name_len);
+
+       skb = llcp_allocate_pdu(sock, LLCP_PDU_CONNECT, size);
+       if (skb == NULL) {
+               err = -ENOMEM;
+               goto error_tlv;
+       }
+
+       if (service_name_tlv != NULL)
+               skb = llcp_add_tlv(skb, service_name_tlv,
+                                  service_name_tlv_length);
+
+       skb = llcp_add_tlv(skb, miux_tlv, miux_tlv_length);
+       skb = llcp_add_tlv(skb, rw_tlv, rw_tlv_length);
+
+       skb_queue_tail(&local->tx_queue, skb);
+
+       return 0;
+
+error_tlv:
+       pr_err("error %d\n", err);
+
+       kfree(service_name_tlv);
+       kfree(miux_tlv);
+       kfree(rw_tlv);
+
+       return err;
+}
+
+int nfc_llcp_send_cc(struct nfc_llcp_sock *sock)
+{
+       struct nfc_llcp_local *local;
+       struct sk_buff *skb;
+       u8 *miux_tlv = NULL, miux_tlv_length;
+       u8 *rw_tlv = NULL, rw_tlv_length, rw;
+       __be16 miux;
+       int err;
+       u16 size = 0;
+
+       pr_debug("Sending CC\n");
+
+       local = sock->local;
+       if (local == NULL)
+               return -ENODEV;
+
+       miux = cpu_to_be16(LLCP_MAX_MIUX);
+       miux_tlv = nfc_llcp_build_tlv(LLCP_TLV_MIUX, (u8 *)&miux, 0,
+                                     &miux_tlv_length);
+       size += miux_tlv_length;
+
+       rw = LLCP_MAX_RW;
+       rw_tlv = nfc_llcp_build_tlv(LLCP_TLV_RW, &rw, 0, &rw_tlv_length);
+       size += rw_tlv_length;
+
+       skb = llcp_allocate_pdu(sock, LLCP_PDU_CC, size);
+       if (skb == NULL) {
+               err = -ENOMEM;
+               goto error_tlv;
+       }
+
+       skb = llcp_add_tlv(skb, miux_tlv, miux_tlv_length);
+       skb = llcp_add_tlv(skb, rw_tlv, rw_tlv_length);
+
+       skb_queue_tail(&local->tx_queue, skb);
+
+       return 0;
+
+error_tlv:
+       pr_err("error %d\n", err);
+
+       kfree(miux_tlv);
+       kfree(rw_tlv);
+
+       return err;
+}
+
+int nfc_llcp_send_dm(struct nfc_llcp_local *local, u8 ssap, u8 dsap, u8 reason)
+{
+       struct sk_buff *skb;
+       struct nfc_dev *dev;
+       u16 size = 1; /* Reason code */
+
+       pr_debug("Sending DM reason 0x%x\n", reason);
+
+       if (local == NULL)
+               return -ENODEV;
+
+       dev = local->dev;
+       if (dev == NULL)
+               return -ENODEV;
+
+       size += LLCP_HEADER_SIZE;
+       size += dev->tx_headroom + dev->tx_tailroom + NFC_HEADER_SIZE;
+
+       skb = alloc_skb(size, GFP_KERNEL);
+       if (skb == NULL)
+               return -ENOMEM;
+
+       skb_reserve(skb, dev->tx_headroom + NFC_HEADER_SIZE);
+
+       skb = llcp_add_header(skb, dsap, ssap, LLCP_PDU_DM);
+
+       memcpy(skb_put(skb, 1), &reason, 1);
+
+       skb_queue_head(&local->tx_queue, skb);
+
+       return 0;
+}
+
+int nfc_llcp_send_disconnect(struct nfc_llcp_sock *sock)
+{
+       struct sk_buff *skb;
+       struct nfc_llcp_local *local;
+
+       pr_debug("Send DISC\n");
+
+       local = sock->local;
+       if (local == NULL)
+               return -ENODEV;
+
+       skb = llcp_allocate_pdu(sock, LLCP_PDU_DISC, 0);
+       if (skb == NULL)
+               return -ENOMEM;
+
+       skb_queue_head(&local->tx_queue, skb);
+
+       return 0;
+}
+
+int nfc_llcp_send_i_frame(struct nfc_llcp_sock *sock,
+                         struct msghdr *msg, size_t len)
+{
+       struct sk_buff *pdu;
+       struct sock *sk = &sock->sk;
+       struct nfc_llcp_local *local;
+       size_t frag_len = 0, remaining_len;
+       u8 *msg_data, *msg_ptr;
+
+       pr_debug("Send I frame len %zd\n", len);
+
+       local = sock->local;
+       if (local == NULL)
+               return -ENODEV;
+
+       msg_data = kzalloc(len, GFP_KERNEL);
+       if (msg_data == NULL)
+               return -ENOMEM;
+
+       if (memcpy_fromiovec(msg_data, msg->msg_iov, len)) {
+               kfree(msg_data);
+               return -EFAULT;
+       }
+
+       remaining_len = len;
+       msg_ptr = msg_data;
+
+       while (remaining_len > 0) {
+
+               frag_len = min_t(size_t, sock->miu, remaining_len);
+
+               pr_debug("Fragment %zd bytes remaining %zd",
+                        frag_len, remaining_len);
+
+               pdu = llcp_allocate_pdu(sock, LLCP_PDU_I,
+                                       frag_len + LLCP_SEQUENCE_SIZE);
+               if (pdu == NULL)
+                       return -ENOMEM;
+
+               skb_put(pdu, LLCP_SEQUENCE_SIZE);
+
+               memcpy(skb_put(pdu, frag_len), msg_ptr, frag_len);
+
+               skb_queue_tail(&sock->tx_queue, pdu);
+
+               lock_sock(sk);
+
+               nfc_llcp_queue_i_frames(sock);
+
+               release_sock(sk);
+
+               remaining_len -= frag_len;
+               msg_ptr += frag_len;
+       }
+
+       kfree(msg_data);
+
+       return len;
+}
+
+int nfc_llcp_send_rr(struct nfc_llcp_sock *sock)
+{
+       struct sk_buff *skb;
+       struct nfc_llcp_local *local;
+
+       pr_debug("Send rr nr %d\n", sock->recv_n);
+
+       local = sock->local;
+       if (local == NULL)
+               return -ENODEV;
+
+       skb = llcp_allocate_pdu(sock, LLCP_PDU_RR, LLCP_SEQUENCE_SIZE);
+       if (skb == NULL)
+               return -ENOMEM;
+
+       skb_put(skb, LLCP_SEQUENCE_SIZE);
+
+       skb->data[2] = sock->recv_n;
+
+       skb_queue_head(&local->tx_queue, skb);
+
+       return 0;
+}
diff --git a/net/nfc/llcp/llcp.c b/net/nfc/llcp/llcp.c
new file mode 100644 (file)
index 0000000..82f0f75
--- /dev/null
@@ -0,0 +1,1231 @@
+/*
+ * Copyright (C) 2011  Intel Corporation. All rights reserved.
+ *
+ * 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
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#define pr_fmt(fmt) "llcp: %s: " fmt, __func__
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/nfc.h>
+
+#include "../nfc.h"
+#include "llcp.h"
+
+static u8 llcp_magic[3] = {0x46, 0x66, 0x6d};
+
+static struct list_head llcp_devices;
+
+void nfc_llcp_sock_link(struct llcp_sock_list *l, struct sock *sk)
+{
+       write_lock(&l->lock);
+       sk_add_node(sk, &l->head);
+       write_unlock(&l->lock);
+}
+
+void nfc_llcp_sock_unlink(struct llcp_sock_list *l, struct sock *sk)
+{
+       write_lock(&l->lock);
+       sk_del_node_init(sk);
+       write_unlock(&l->lock);
+}
+
+static void nfc_llcp_socket_release(struct nfc_llcp_local *local, bool listen)
+{
+       struct sock *sk;
+       struct hlist_node *node, *tmp;
+       struct nfc_llcp_sock *llcp_sock;
+
+       write_lock(&local->sockets.lock);
+
+       sk_for_each_safe(sk, node, tmp, &local->sockets.head) {
+               llcp_sock = nfc_llcp_sock(sk);
+
+               lock_sock(sk);
+
+               if (sk->sk_state == LLCP_CONNECTED)
+                       nfc_put_device(llcp_sock->dev);
+
+               if (sk->sk_state == LLCP_LISTEN) {
+                       struct nfc_llcp_sock *lsk, *n;
+                       struct sock *accept_sk;
+
+                       list_for_each_entry_safe(lsk, n, &llcp_sock->accept_queue,
+                                                accept_queue) {
+                               accept_sk = &lsk->sk;
+                               lock_sock(accept_sk);
+
+                               nfc_llcp_accept_unlink(accept_sk);
+
+                               accept_sk->sk_state = LLCP_CLOSED;
+
+                               release_sock(accept_sk);
+
+                               sock_orphan(accept_sk);
+                       }
+
+                       if (listen == true) {
+                               release_sock(sk);
+                               continue;
+                       }
+               }
+
+               sk->sk_state = LLCP_CLOSED;
+
+               release_sock(sk);
+
+               sock_orphan(sk);
+
+               sk_del_node_init(sk);
+       }
+
+       write_unlock(&local->sockets.lock);
+}
+
+struct nfc_llcp_local *nfc_llcp_local_get(struct nfc_llcp_local *local)
+{
+       kref_get(&local->ref);
+
+       return local;
+}
+
+static void local_release(struct kref *ref)
+{
+       struct nfc_llcp_local *local;
+
+       local = container_of(ref, struct nfc_llcp_local, ref);
+
+       list_del(&local->list);
+       nfc_llcp_socket_release(local, false);
+       del_timer_sync(&local->link_timer);
+       skb_queue_purge(&local->tx_queue);
+       destroy_workqueue(local->tx_wq);
+       destroy_workqueue(local->rx_wq);
+       destroy_workqueue(local->timeout_wq);
+       kfree_skb(local->rx_pending);
+       kfree(local);
+}
+
+int nfc_llcp_local_put(struct nfc_llcp_local *local)
+{
+       if (local == NULL)
+               return 0;
+
+       return kref_put(&local->ref, local_release);
+}
+
+static struct nfc_llcp_sock *nfc_llcp_sock_get(struct nfc_llcp_local *local,
+                                              u8 ssap, u8 dsap)
+{
+       struct sock *sk;
+       struct hlist_node *node;
+       struct nfc_llcp_sock *llcp_sock;
+
+       pr_debug("ssap dsap %d %d\n", ssap, dsap);
+
+       if (ssap == 0 && dsap == 0)
+               return NULL;
+
+       read_lock(&local->sockets.lock);
+
+       llcp_sock = NULL;
+
+       sk_for_each(sk, node, &local->sockets.head) {
+               llcp_sock = nfc_llcp_sock(sk);
+
+               if (llcp_sock->ssap == ssap && llcp_sock->dsap == dsap)
+                       break;
+       }
+
+       read_unlock(&local->sockets.lock);
+
+       if (llcp_sock == NULL)
+               return NULL;
+
+       sock_hold(&llcp_sock->sk);
+
+       return llcp_sock;
+}
+
+static void nfc_llcp_sock_put(struct nfc_llcp_sock *sock)
+{
+       sock_put(&sock->sk);
+}
+
+static void nfc_llcp_timeout_work(struct work_struct *work)
+{
+       struct nfc_llcp_local *local = container_of(work, struct nfc_llcp_local,
+                                                   timeout_work);
+
+       nfc_dep_link_down(local->dev);
+}
+
+static void nfc_llcp_symm_timer(unsigned long data)
+{
+       struct nfc_llcp_local *local = (struct nfc_llcp_local *) data;
+
+       pr_err("SYMM timeout\n");
+
+       queue_work(local->timeout_wq, &local->timeout_work);
+}
+
+struct nfc_llcp_local *nfc_llcp_find_local(struct nfc_dev *dev)
+{
+       struct nfc_llcp_local *local, *n;
+
+       list_for_each_entry_safe(local, n, &llcp_devices, list)
+               if (local->dev == dev)
+                       return local;
+
+       pr_debug("No device found\n");
+
+       return NULL;
+}
+
+static char *wks[] = {
+       NULL,
+       NULL, /* SDP */
+       "urn:nfc:sn:ip",
+       "urn:nfc:sn:obex",
+       "urn:nfc:sn:snep",
+};
+
+static int nfc_llcp_wks_sap(char *service_name, size_t service_name_len)
+{
+       int sap, num_wks;
+
+       pr_debug("%s\n", service_name);
+
+       if (service_name == NULL)
+               return -EINVAL;
+
+       num_wks = ARRAY_SIZE(wks);
+
+       for (sap = 0; sap < num_wks; sap++) {
+               if (wks[sap] == NULL)
+                       continue;
+
+               if (strncmp(wks[sap], service_name, service_name_len) == 0)
+                       return sap;
+       }
+
+       return -EINVAL;
+}
+
+static
+struct nfc_llcp_sock *nfc_llcp_sock_from_sn(struct nfc_llcp_local *local,
+                                           u8 *sn, size_t sn_len)
+{
+       struct sock *sk;
+       struct hlist_node *node;
+       struct nfc_llcp_sock *llcp_sock, *tmp_sock;
+
+       pr_debug("sn %zd %p\n", sn_len, sn);
+
+       if (sn == NULL || sn_len == 0)
+               return NULL;
+
+       read_lock(&local->sockets.lock);
+
+       llcp_sock = NULL;
+
+       sk_for_each(sk, node, &local->sockets.head) {
+               tmp_sock = nfc_llcp_sock(sk);
+
+               pr_debug("llcp sock %p\n", tmp_sock);
+
+               if (tmp_sock->sk.sk_state != LLCP_LISTEN)
+                       continue;
+
+               if (tmp_sock->service_name == NULL ||
+                   tmp_sock->service_name_len == 0)
+                       continue;
+
+               if (tmp_sock->service_name_len != sn_len)
+                       continue;
+
+               if (memcmp(sn, tmp_sock->service_name, sn_len) == 0) {
+                       llcp_sock = tmp_sock;
+                       break;
+               }
+       }
+
+       read_unlock(&local->sockets.lock);
+
+       pr_debug("Found llcp sock %p\n", llcp_sock);
+
+       return llcp_sock;
+}
+
+u8 nfc_llcp_get_sdp_ssap(struct nfc_llcp_local *local,
+                        struct nfc_llcp_sock *sock)
+{
+       mutex_lock(&local->sdp_lock);
+
+       if (sock->service_name != NULL && sock->service_name_len > 0) {
+               int ssap = nfc_llcp_wks_sap(sock->service_name,
+                                           sock->service_name_len);
+
+               if (ssap > 0) {
+                       pr_debug("WKS %d\n", ssap);
+
+                       /* This is a WKS, let's check if it's free */
+                       if (local->local_wks & BIT(ssap)) {
+                               mutex_unlock(&local->sdp_lock);
+
+                               return LLCP_SAP_MAX;
+                       }
+
+                       set_bit(ssap, &local->local_wks);
+                       mutex_unlock(&local->sdp_lock);
+
+                       return ssap;
+               }
+
+               /*
+                * Check if there already is a non WKS socket bound
+                * to this service name.
+                */
+               if (nfc_llcp_sock_from_sn(local, sock->service_name,
+                                         sock->service_name_len) != NULL) {
+                       mutex_unlock(&local->sdp_lock);
+
+                       return LLCP_SAP_MAX;
+               }
+
+               mutex_unlock(&local->sdp_lock);
+
+               return LLCP_SDP_UNBOUND;
+
+       } else if (sock->ssap != 0 && sock->ssap < LLCP_WKS_NUM_SAP) {
+               if (!test_bit(sock->ssap, &local->local_wks)) {
+                       set_bit(sock->ssap, &local->local_wks);
+                       mutex_unlock(&local->sdp_lock);
+
+                       return sock->ssap;
+               }
+       }
+
+       mutex_unlock(&local->sdp_lock);
+
+       return LLCP_SAP_MAX;
+}
+
+u8 nfc_llcp_get_local_ssap(struct nfc_llcp_local *local)
+{
+       u8 local_ssap;
+
+       mutex_lock(&local->sdp_lock);
+
+       local_ssap = find_first_zero_bit(&local->local_sap, LLCP_LOCAL_NUM_SAP);
+       if (local_ssap == LLCP_LOCAL_NUM_SAP) {
+               mutex_unlock(&local->sdp_lock);
+               return LLCP_SAP_MAX;
+       }
+
+       set_bit(local_ssap, &local->local_sap);
+
+       mutex_unlock(&local->sdp_lock);
+
+       return local_ssap + LLCP_LOCAL_SAP_OFFSET;
+}
+
+void nfc_llcp_put_ssap(struct nfc_llcp_local *local, u8 ssap)
+{
+       u8 local_ssap;
+       unsigned long *sdp;
+
+       if (ssap < LLCP_WKS_NUM_SAP) {
+               local_ssap = ssap;
+               sdp = &local->local_wks;
+       } else if (ssap < LLCP_LOCAL_NUM_SAP) {
+               atomic_t *client_cnt;
+
+               local_ssap = ssap - LLCP_WKS_NUM_SAP;
+               sdp = &local->local_sdp;
+               client_cnt = &local->local_sdp_cnt[local_ssap];
+
+               pr_debug("%d clients\n", atomic_read(client_cnt));
+
+               mutex_lock(&local->sdp_lock);
+
+               if (atomic_dec_and_test(client_cnt)) {
+                       struct nfc_llcp_sock *l_sock;
+
+                       pr_debug("No more clients for SAP %d\n", ssap);
+
+                       clear_bit(local_ssap, sdp);
+
+                       /* Find the listening sock and set it back to UNBOUND */
+                       l_sock = nfc_llcp_sock_get(local, ssap, LLCP_SAP_SDP);
+                       if (l_sock) {
+                               l_sock->ssap = LLCP_SDP_UNBOUND;
+                               nfc_llcp_sock_put(l_sock);
+                       }
+               }
+
+               mutex_unlock(&local->sdp_lock);
+
+               return;
+       } else if (ssap < LLCP_MAX_SAP) {
+               local_ssap = ssap - LLCP_LOCAL_NUM_SAP;
+               sdp = &local->local_sap;
+       } else {
+               return;
+       }
+
+       mutex_lock(&local->sdp_lock);
+
+       clear_bit(local_ssap, sdp);
+
+       mutex_unlock(&local->sdp_lock);
+}
+
+static u8 nfc_llcp_reserve_sdp_ssap(struct nfc_llcp_local *local)
+{
+       u8 ssap;
+
+       mutex_lock(&local->sdp_lock);
+
+       ssap = find_first_zero_bit(&local->local_sdp, LLCP_SDP_NUM_SAP);
+       if (ssap == LLCP_SDP_NUM_SAP) {
+               mutex_unlock(&local->sdp_lock);
+
+               return LLCP_SAP_MAX;
+       }
+
+       pr_debug("SDP ssap %d\n", LLCP_WKS_NUM_SAP + ssap);
+
+       set_bit(ssap, &local->local_sdp);
+
+       mutex_unlock(&local->sdp_lock);
+
+       return LLCP_WKS_NUM_SAP + ssap;
+}
+
+static int nfc_llcp_build_gb(struct nfc_llcp_local *local)
+{
+       u8 *gb_cur, *version_tlv, version, version_length;
+       u8 *lto_tlv, lto, lto_length;
+       u8 *wks_tlv, wks_length;
+       u8 *miux_tlv, miux_length;
+       __be16 miux;
+       u8 gb_len = 0;
+
+       version = LLCP_VERSION_11;
+       version_tlv = nfc_llcp_build_tlv(LLCP_TLV_VERSION, &version,
+                                        1, &version_length);
+       gb_len += version_length;
+
+       /* 1500 ms */
+       lto = 150;
+       lto_tlv = nfc_llcp_build_tlv(LLCP_TLV_LTO, &lto, 1, &lto_length);
+       gb_len += lto_length;
+
+       pr_debug("Local wks 0x%lx\n", local->local_wks);
+       wks_tlv = nfc_llcp_build_tlv(LLCP_TLV_WKS, (u8 *)&local->local_wks, 2,
+                                    &wks_length);
+       gb_len += wks_length;
+
+       miux = cpu_to_be16(LLCP_MAX_MIUX);
+       miux_tlv = nfc_llcp_build_tlv(LLCP_TLV_MIUX, (u8 *)&miux, 0,
+                                     &miux_length);
+       gb_len += miux_length;
+
+       gb_len += ARRAY_SIZE(llcp_magic);
+
+       if (gb_len > NFC_MAX_GT_LEN) {
+               kfree(version_tlv);
+               return -EINVAL;
+       }
+
+       gb_cur = local->gb;
+
+       memcpy(gb_cur, llcp_magic, ARRAY_SIZE(llcp_magic));
+       gb_cur += ARRAY_SIZE(llcp_magic);
+
+       memcpy(gb_cur, version_tlv, version_length);
+       gb_cur += version_length;
+
+       memcpy(gb_cur, lto_tlv, lto_length);
+       gb_cur += lto_length;
+
+       memcpy(gb_cur, wks_tlv, wks_length);
+       gb_cur += wks_length;
+
+       memcpy(gb_cur, miux_tlv, miux_length);
+       gb_cur += miux_length;
+
+       kfree(version_tlv);
+       kfree(lto_tlv);
+
+       local->gb_len = gb_len;
+
+       return 0;
+}
+
+u8 *nfc_llcp_general_bytes(struct nfc_dev *dev, size_t *general_bytes_len)
+{
+       struct nfc_llcp_local *local;
+
+       local = nfc_llcp_find_local(dev);
+       if (local == NULL) {
+               *general_bytes_len = 0;
+               return NULL;
+       }
+
+       nfc_llcp_build_gb(local);
+
+       *general_bytes_len = local->gb_len;
+
+       return local->gb;
+}
+
+int nfc_llcp_set_remote_gb(struct nfc_dev *dev, u8 *gb, u8 gb_len)
+{
+       struct nfc_llcp_local *local = nfc_llcp_find_local(dev);
+
+       if (local == NULL) {
+               pr_err("No LLCP device\n");
+               return -ENODEV;
+       }
+
+       memset(local->remote_gb, 0, NFC_MAX_GT_LEN);
+       memcpy(local->remote_gb, gb, gb_len);
+       local->remote_gb_len = gb_len;
+
+       if (local->remote_gb == NULL || local->remote_gb_len == 0)
+               return -ENODEV;
+
+       if (memcmp(local->remote_gb, llcp_magic, 3)) {
+               pr_err("MAC does not support LLCP\n");
+               return -EINVAL;
+       }
+
+       return nfc_llcp_parse_gb_tlv(local,
+                                    &local->remote_gb[3],
+                                    local->remote_gb_len - 3);
+}
+
+static u8 nfc_llcp_dsap(struct sk_buff *pdu)
+{
+       return (pdu->data[0] & 0xfc) >> 2;
+}
+
+static u8 nfc_llcp_ptype(struct sk_buff *pdu)
+{
+       return ((pdu->data[0] & 0x03) << 2) | ((pdu->data[1] & 0xc0) >> 6);
+}
+
+static u8 nfc_llcp_ssap(struct sk_buff *pdu)
+{
+       return pdu->data[1] & 0x3f;
+}
+
+static u8 nfc_llcp_ns(struct sk_buff *pdu)
+{
+       return pdu->data[2] >> 4;
+}
+
+static u8 nfc_llcp_nr(struct sk_buff *pdu)
+{
+       return pdu->data[2] & 0xf;
+}
+
+static void nfc_llcp_set_nrns(struct nfc_llcp_sock *sock, struct sk_buff *pdu)
+{
+       pdu->data[2] = (sock->send_n << 4) | (sock->recv_n);
+       sock->send_n = (sock->send_n + 1) % 16;
+       sock->recv_ack_n = (sock->recv_n - 1) % 16;
+}
+
+static void nfc_llcp_tx_work(struct work_struct *work)
+{
+       struct nfc_llcp_local *local = container_of(work, struct nfc_llcp_local,
+                                                   tx_work);
+       struct sk_buff *skb;
+       struct sock *sk;
+       struct nfc_llcp_sock *llcp_sock;
+
+       skb = skb_dequeue(&local->tx_queue);
+       if (skb != NULL) {
+               sk = skb->sk;
+               llcp_sock = nfc_llcp_sock(sk);
+               if (llcp_sock != NULL) {
+                       int ret;
+
+                       pr_debug("Sending pending skb\n");
+                       print_hex_dump(KERN_DEBUG, "LLCP Tx: ",
+                                      DUMP_PREFIX_OFFSET, 16, 1,
+                                      skb->data, skb->len, true);
+
+                       ret = nfc_data_exchange(local->dev, local->target_idx,
+                                               skb, nfc_llcp_recv, local);
+
+                       if (!ret && nfc_llcp_ptype(skb) == LLCP_PDU_I) {
+                               skb = skb_get(skb);
+                               skb_queue_tail(&llcp_sock->tx_pending_queue,
+                                              skb);
+                       }
+               } else {
+                       nfc_llcp_send_symm(local->dev);
+               }
+       } else {
+               nfc_llcp_send_symm(local->dev);
+       }
+
+       mod_timer(&local->link_timer,
+                 jiffies + msecs_to_jiffies(2 * local->remote_lto));
+}
+
+static struct nfc_llcp_sock *nfc_llcp_connecting_sock_get(struct nfc_llcp_local *local,
+                                                         u8 ssap)
+{
+       struct sock *sk;
+       struct nfc_llcp_sock *llcp_sock;
+       struct hlist_node *node;
+
+       read_lock(&local->connecting_sockets.lock);
+
+       sk_for_each(sk, node, &local->connecting_sockets.head) {
+               llcp_sock = nfc_llcp_sock(sk);
+
+               if (llcp_sock->ssap == ssap) {
+                       sock_hold(&llcp_sock->sk);
+                       goto out;
+               }
+       }
+
+       llcp_sock = NULL;
+
+out:
+       read_unlock(&local->connecting_sockets.lock);
+
+       return llcp_sock;
+}
+
+static struct nfc_llcp_sock *nfc_llcp_sock_get_sn(struct nfc_llcp_local *local,
+                                                 u8 *sn, size_t sn_len)
+{
+       struct nfc_llcp_sock *llcp_sock;
+
+       llcp_sock = nfc_llcp_sock_from_sn(local, sn, sn_len);
+
+       if (llcp_sock == NULL)
+               return NULL;
+
+       sock_hold(&llcp_sock->sk);
+
+       return llcp_sock;
+}
+
+static u8 *nfc_llcp_connect_sn(struct sk_buff *skb, size_t *sn_len)
+{
+       u8 *tlv = &skb->data[2], type, length;
+       size_t tlv_array_len = skb->len - LLCP_HEADER_SIZE, offset = 0;
+
+       while (offset < tlv_array_len) {
+               type = tlv[0];
+               length = tlv[1];
+
+               pr_debug("type 0x%x length %d\n", type, length);
+
+               if (type == LLCP_TLV_SN) {
+                       *sn_len = length;
+                       return &tlv[2];
+               }
+
+               offset += length + 2;
+               tlv += length + 2;
+       }
+
+       return NULL;
+}
+
+static void nfc_llcp_recv_connect(struct nfc_llcp_local *local,
+                                 struct sk_buff *skb)
+{
+       struct sock *new_sk, *parent;
+       struct nfc_llcp_sock *sock, *new_sock;
+       u8 dsap, ssap, reason;
+
+       dsap = nfc_llcp_dsap(skb);
+       ssap = nfc_llcp_ssap(skb);
+
+       pr_debug("%d %d\n", dsap, ssap);
+
+       if (dsap != LLCP_SAP_SDP) {
+               sock = nfc_llcp_sock_get(local, dsap, LLCP_SAP_SDP);
+               if (sock == NULL || sock->sk.sk_state != LLCP_LISTEN) {
+                       reason = LLCP_DM_NOBOUND;
+                       goto fail;
+               }
+       } else {
+               u8 *sn;
+               size_t sn_len;
+
+               sn = nfc_llcp_connect_sn(skb, &sn_len);
+               if (sn == NULL) {
+                       reason = LLCP_DM_NOBOUND;
+                       goto fail;
+               }
+
+               pr_debug("Service name length %zu\n", sn_len);
+
+               sock = nfc_llcp_sock_get_sn(local, sn, sn_len);
+               if (sock == NULL) {
+                       reason = LLCP_DM_NOBOUND;
+                       goto fail;
+               }
+       }
+
+       lock_sock(&sock->sk);
+
+       parent = &sock->sk;
+
+       if (sk_acceptq_is_full(parent)) {
+               reason = LLCP_DM_REJ;
+               release_sock(&sock->sk);
+               sock_put(&sock->sk);
+               goto fail;
+       }
+
+       if (sock->ssap == LLCP_SDP_UNBOUND) {
+               u8 ssap = nfc_llcp_reserve_sdp_ssap(local);
+
+               pr_debug("First client, reserving %d\n", ssap);
+
+               if (ssap == LLCP_SAP_MAX) {
+                       reason = LLCP_DM_REJ;
+                       release_sock(&sock->sk);
+                       sock_put(&sock->sk);
+                       goto fail;
+               }
+
+               sock->ssap = ssap;
+       }
+
+       new_sk = nfc_llcp_sock_alloc(NULL, parent->sk_type, GFP_ATOMIC);
+       if (new_sk == NULL) {
+               reason = LLCP_DM_REJ;
+               release_sock(&sock->sk);
+               sock_put(&sock->sk);
+               goto fail;
+       }
+
+       new_sock = nfc_llcp_sock(new_sk);
+       new_sock->dev = local->dev;
+       new_sock->local = nfc_llcp_local_get(local);
+       new_sock->miu = local->remote_miu;
+       new_sock->nfc_protocol = sock->nfc_protocol;
+       new_sock->dsap = ssap;
+       new_sock->target_idx = local->target_idx;
+       new_sock->parent = parent;
+       new_sock->ssap = sock->ssap;
+       if (sock->ssap < LLCP_LOCAL_NUM_SAP && sock->ssap >= LLCP_WKS_NUM_SAP) {
+               atomic_t *client_count;
+
+               pr_debug("reserved_ssap %d for %p\n", sock->ssap, new_sock);
+
+               client_count =
+                       &local->local_sdp_cnt[sock->ssap - LLCP_WKS_NUM_SAP];
+
+               atomic_inc(client_count);
+               new_sock->reserved_ssap = sock->ssap;
+       }
+
+       nfc_llcp_parse_connection_tlv(new_sock, &skb->data[LLCP_HEADER_SIZE],
+                                     skb->len - LLCP_HEADER_SIZE);
+
+       pr_debug("new sock %p sk %p\n", new_sock, &new_sock->sk);
+
+       nfc_llcp_sock_link(&local->sockets, new_sk);
+
+       nfc_llcp_accept_enqueue(&sock->sk, new_sk);
+
+       nfc_get_device(local->dev->idx);
+
+       new_sk->sk_state = LLCP_CONNECTED;
+
+       /* Wake the listening processes */
+       parent->sk_data_ready(parent, 0);
+
+       /* Send CC */
+       nfc_llcp_send_cc(new_sock);
+
+       release_sock(&sock->sk);
+       sock_put(&sock->sk);
+
+       return;
+
+fail:
+       /* Send DM */
+       nfc_llcp_send_dm(local, dsap, ssap, reason);
+
+       return;
+
+}
+
+int nfc_llcp_queue_i_frames(struct nfc_llcp_sock *sock)
+{
+       int nr_frames = 0;
+       struct nfc_llcp_local *local = sock->local;
+
+       pr_debug("Remote ready %d tx queue len %d remote rw %d",
+                sock->remote_ready, skb_queue_len(&sock->tx_pending_queue),
+                sock->rw);
+
+       /* Try to queue some I frames for transmission */
+       while (sock->remote_ready &&
+              skb_queue_len(&sock->tx_pending_queue) < sock->rw) {
+               struct sk_buff *pdu;
+
+               pdu = skb_dequeue(&sock->tx_queue);
+               if (pdu == NULL)
+                       break;
+
+               /* Update N(S)/N(R) */
+               nfc_llcp_set_nrns(sock, pdu);
+
+               skb_queue_tail(&local->tx_queue, pdu);
+               nr_frames++;
+       }
+
+       return nr_frames;
+}
+
+static void nfc_llcp_recv_hdlc(struct nfc_llcp_local *local,
+                              struct sk_buff *skb)
+{
+       struct nfc_llcp_sock *llcp_sock;
+       struct sock *sk;
+       u8 dsap, ssap, ptype, ns, nr;
+
+       ptype = nfc_llcp_ptype(skb);
+       dsap = nfc_llcp_dsap(skb);
+       ssap = nfc_llcp_ssap(skb);
+       ns = nfc_llcp_ns(skb);
+       nr = nfc_llcp_nr(skb);
+
+       pr_debug("%d %d R %d S %d\n", dsap, ssap, nr, ns);
+
+       llcp_sock = nfc_llcp_sock_get(local, dsap, ssap);
+       if (llcp_sock == NULL) {
+               nfc_llcp_send_dm(local, dsap, ssap, LLCP_DM_NOCONN);
+               return;
+       }
+
+       sk = &llcp_sock->sk;
+       lock_sock(sk);
+       if (sk->sk_state == LLCP_CLOSED) {
+               release_sock(sk);
+               nfc_llcp_sock_put(llcp_sock);
+       }
+
+       /* Pass the payload upstream */
+       if (ptype == LLCP_PDU_I) {
+               pr_debug("I frame, queueing on %p\n", &llcp_sock->sk);
+
+               if (ns == llcp_sock->recv_n)
+                       llcp_sock->recv_n = (llcp_sock->recv_n + 1) % 16;
+               else
+                       pr_err("Received out of sequence I PDU\n");
+
+               skb_pull(skb, LLCP_HEADER_SIZE + LLCP_SEQUENCE_SIZE);
+               if (sock_queue_rcv_skb(&llcp_sock->sk, skb)) {
+                       pr_err("receive queue is full\n");
+                       skb_queue_head(&llcp_sock->tx_backlog_queue, skb);
+               }
+       }
+
+       /* Remove skbs from the pending queue */
+       if (llcp_sock->send_ack_n != nr) {
+               struct sk_buff *s, *tmp;
+
+               llcp_sock->send_ack_n = nr;
+
+               /* Remove and free all skbs until ns == nr */
+               skb_queue_walk_safe(&llcp_sock->tx_pending_queue, s, tmp) {
+                       skb_unlink(s, &llcp_sock->tx_pending_queue);
+                       kfree_skb(s);
+
+                       if (nfc_llcp_ns(s) == nr)
+                               break;
+               }
+
+               /* Re-queue the remaining skbs for transmission */
+               skb_queue_reverse_walk_safe(&llcp_sock->tx_pending_queue,
+                                           s, tmp) {
+                       skb_unlink(s, &llcp_sock->tx_pending_queue);
+                       skb_queue_head(&local->tx_queue, s);
+               }
+       }
+
+       if (ptype == LLCP_PDU_RR)
+               llcp_sock->remote_ready = true;
+       else if (ptype == LLCP_PDU_RNR)
+               llcp_sock->remote_ready = false;
+
+       if (nfc_llcp_queue_i_frames(llcp_sock) == 0 && ptype == LLCP_PDU_I)
+               nfc_llcp_send_rr(llcp_sock);
+
+       release_sock(sk);
+       nfc_llcp_sock_put(llcp_sock);
+}
+
+static void nfc_llcp_recv_disc(struct nfc_llcp_local *local,
+                              struct sk_buff *skb)
+{
+       struct nfc_llcp_sock *llcp_sock;
+       struct sock *sk;
+       u8 dsap, ssap;
+
+       dsap = nfc_llcp_dsap(skb);
+       ssap = nfc_llcp_ssap(skb);
+
+       llcp_sock = nfc_llcp_sock_get(local, dsap, ssap);
+       if (llcp_sock == NULL) {
+               nfc_llcp_send_dm(local, dsap, ssap, LLCP_DM_NOCONN);
+               return;
+       }
+
+       sk = &llcp_sock->sk;
+       lock_sock(sk);
+       if (sk->sk_state == LLCP_CLOSED) {
+               release_sock(sk);
+               nfc_llcp_sock_put(llcp_sock);
+       }
+
+       if (sk->sk_state == LLCP_CONNECTED) {
+               nfc_put_device(local->dev);
+               sk->sk_state = LLCP_CLOSED;
+               sk->sk_state_change(sk);
+       }
+
+       nfc_llcp_send_dm(local, dsap, ssap, LLCP_DM_DISC);
+
+       release_sock(sk);
+       nfc_llcp_sock_put(llcp_sock);
+}
+
+static void nfc_llcp_recv_cc(struct nfc_llcp_local *local, struct sk_buff *skb)
+{
+       struct nfc_llcp_sock *llcp_sock;
+       struct sock *sk;
+       u8 dsap, ssap;
+
+       dsap = nfc_llcp_dsap(skb);
+       ssap = nfc_llcp_ssap(skb);
+
+       llcp_sock = nfc_llcp_connecting_sock_get(local, dsap);
+       if (llcp_sock == NULL) {
+               pr_err("Invalid CC\n");
+               nfc_llcp_send_dm(local, dsap, ssap, LLCP_DM_NOCONN);
+
+               return;
+       }
+
+       sk = &llcp_sock->sk;
+
+       /* Unlink from connecting and link to the client array */
+       nfc_llcp_sock_unlink(&local->connecting_sockets, sk);
+       nfc_llcp_sock_link(&local->sockets, sk);
+       llcp_sock->dsap = ssap;
+
+       nfc_llcp_parse_connection_tlv(llcp_sock, &skb->data[LLCP_HEADER_SIZE],
+                                     skb->len - LLCP_HEADER_SIZE);
+
+       sk->sk_state = LLCP_CONNECTED;
+       sk->sk_state_change(sk);
+
+       nfc_llcp_sock_put(llcp_sock);
+}
+
+static void nfc_llcp_recv_dm(struct nfc_llcp_local *local, struct sk_buff *skb)
+{
+       struct nfc_llcp_sock *llcp_sock;
+       struct sock *sk;
+       u8 dsap, ssap, reason;
+
+       dsap = nfc_llcp_dsap(skb);
+       ssap = nfc_llcp_ssap(skb);
+       reason = skb->data[2];
+
+       pr_debug("%d %d reason %d\n", ssap, dsap, reason);
+
+       switch (reason) {
+       case LLCP_DM_NOBOUND:
+       case LLCP_DM_REJ:
+               llcp_sock = nfc_llcp_connecting_sock_get(local, dsap);
+               break;
+
+       default:
+               llcp_sock = nfc_llcp_sock_get(local, dsap, ssap);
+               break;
+       }
+
+       if (llcp_sock == NULL) {
+               pr_err("Invalid DM\n");
+               return;
+       }
+
+       sk = &llcp_sock->sk;
+
+       sk->sk_err = ENXIO;
+       sk->sk_state = LLCP_CLOSED;
+       sk->sk_state_change(sk);
+
+       nfc_llcp_sock_put(llcp_sock);
+
+       return;
+}
+
+static void nfc_llcp_rx_work(struct work_struct *work)
+{
+       struct nfc_llcp_local *local = container_of(work, struct nfc_llcp_local,
+                                                   rx_work);
+       u8 dsap, ssap, ptype;
+       struct sk_buff *skb;
+
+       skb = local->rx_pending;
+       if (skb == NULL) {
+               pr_debug("No pending SKB\n");
+               return;
+       }
+
+       ptype = nfc_llcp_ptype(skb);
+       dsap = nfc_llcp_dsap(skb);
+       ssap = nfc_llcp_ssap(skb);
+
+       pr_debug("ptype 0x%x dsap 0x%x ssap 0x%x\n", ptype, dsap, ssap);
+
+       if (ptype != LLCP_PDU_SYMM)
+               print_hex_dump(KERN_DEBUG, "LLCP Rx: ", DUMP_PREFIX_OFFSET,
+                              16, 1, skb->data, skb->len, true);
+
+       switch (ptype) {
+       case LLCP_PDU_SYMM:
+               pr_debug("SYMM\n");
+               break;
+
+       case LLCP_PDU_CONNECT:
+               pr_debug("CONNECT\n");
+               nfc_llcp_recv_connect(local, skb);
+               break;
+
+       case LLCP_PDU_DISC:
+               pr_debug("DISC\n");
+               nfc_llcp_recv_disc(local, skb);
+               break;
+
+       case LLCP_PDU_CC:
+               pr_debug("CC\n");
+               nfc_llcp_recv_cc(local, skb);
+               break;
+
+       case LLCP_PDU_DM:
+               pr_debug("DM\n");
+               nfc_llcp_recv_dm(local, skb);
+               break;
+
+       case LLCP_PDU_I:
+       case LLCP_PDU_RR:
+       case LLCP_PDU_RNR:
+               pr_debug("I frame\n");
+               nfc_llcp_recv_hdlc(local, skb);
+               break;
+
+       }
+
+       queue_work(local->tx_wq, &local->tx_work);
+       kfree_skb(local->rx_pending);
+       local->rx_pending = NULL;
+
+       return;
+}
+
+void nfc_llcp_recv(void *data, struct sk_buff *skb, int err)
+{
+       struct nfc_llcp_local *local = (struct nfc_llcp_local *) data;
+
+       pr_debug("Received an LLCP PDU\n");
+       if (err < 0) {
+               pr_err("err %d\n", err);
+               return;
+       }
+
+       local->rx_pending = skb_get(skb);
+       del_timer(&local->link_timer);
+       queue_work(local->rx_wq, &local->rx_work);
+
+       return;
+}
+
+int nfc_llcp_data_received(struct nfc_dev *dev, struct sk_buff *skb)
+{
+       struct nfc_llcp_local *local;
+
+       local = nfc_llcp_find_local(dev);
+       if (local == NULL)
+               return -ENODEV;
+
+       local->rx_pending = skb_get(skb);
+       del_timer(&local->link_timer);
+       queue_work(local->rx_wq, &local->rx_work);
+
+       return 0;
+}
+
+void nfc_llcp_mac_is_down(struct nfc_dev *dev)
+{
+       struct nfc_llcp_local *local;
+
+       local = nfc_llcp_find_local(dev);
+       if (local == NULL)
+               return;
+
+       /* Close and purge all existing sockets */
+       nfc_llcp_socket_release(local, true);
+}
+
+void nfc_llcp_mac_is_up(struct nfc_dev *dev, u32 target_idx,
+                       u8 comm_mode, u8 rf_mode)
+{
+       struct nfc_llcp_local *local;
+
+       pr_debug("rf mode %d\n", rf_mode);
+
+       local = nfc_llcp_find_local(dev);
+       if (local == NULL)
+               return;
+
+       local->target_idx = target_idx;
+       local->comm_mode = comm_mode;
+       local->rf_mode = rf_mode;
+
+       if (rf_mode == NFC_RF_INITIATOR) {
+               pr_debug("Queueing Tx work\n");
+
+               queue_work(local->tx_wq, &local->tx_work);
+       } else {
+               mod_timer(&local->link_timer,
+                         jiffies + msecs_to_jiffies(local->remote_lto));
+       }
+}
+
+int nfc_llcp_register_device(struct nfc_dev *ndev)
+{
+       struct device *dev = &ndev->dev;
+       struct nfc_llcp_local *local;
+       char name[32];
+       int err;
+
+       local = kzalloc(sizeof(struct nfc_llcp_local), GFP_KERNEL);
+       if (local == NULL)
+               return -ENOMEM;
+
+       local->dev = ndev;
+       INIT_LIST_HEAD(&local->list);
+       kref_init(&local->ref);
+       mutex_init(&local->sdp_lock);
+       init_timer(&local->link_timer);
+       local->link_timer.data = (unsigned long) local;
+       local->link_timer.function = nfc_llcp_symm_timer;
+
+       skb_queue_head_init(&local->tx_queue);
+       INIT_WORK(&local->tx_work, nfc_llcp_tx_work);
+       snprintf(name, sizeof(name), "%s_llcp_tx_wq", dev_name(dev));
+       local->tx_wq =
+               alloc_workqueue(name,
+                               WQ_NON_REENTRANT | WQ_UNBOUND | WQ_MEM_RECLAIM,
+                               1);
+       if (local->tx_wq == NULL) {
+               err = -ENOMEM;
+               goto err_local;
+       }
+
+       local->rx_pending = NULL;
+       INIT_WORK(&local->rx_work, nfc_llcp_rx_work);
+       snprintf(name, sizeof(name), "%s_llcp_rx_wq", dev_name(dev));
+       local->rx_wq =
+               alloc_workqueue(name,
+                               WQ_NON_REENTRANT | WQ_UNBOUND | WQ_MEM_RECLAIM,
+                               1);
+       if (local->rx_wq == NULL) {
+               err = -ENOMEM;
+               goto err_tx_wq;
+       }
+
+       INIT_WORK(&local->timeout_work, nfc_llcp_timeout_work);
+       snprintf(name, sizeof(name), "%s_llcp_timeout_wq", dev_name(dev));
+       local->timeout_wq =
+               alloc_workqueue(name,
+                               WQ_NON_REENTRANT | WQ_UNBOUND | WQ_MEM_RECLAIM,
+                               1);
+       if (local->timeout_wq == NULL) {
+               err = -ENOMEM;
+               goto err_rx_wq;
+       }
+
+       local->sockets.lock = __RW_LOCK_UNLOCKED(local->sockets.lock);
+       local->connecting_sockets.lock = __RW_LOCK_UNLOCKED(local->connecting_sockets.lock);
+
+       nfc_llcp_build_gb(local);
+
+       local->remote_miu = LLCP_DEFAULT_MIU;
+       local->remote_lto = LLCP_DEFAULT_LTO;
+
+       list_add(&llcp_devices, &local->list);
+
+       return 0;
+
+err_rx_wq:
+       destroy_workqueue(local->rx_wq);
+
+err_tx_wq:
+       destroy_workqueue(local->tx_wq);
+
+err_local:
+       kfree(local);
+
+       return 0;
+}
+
+void nfc_llcp_unregister_device(struct nfc_dev *dev)
+{
+       struct nfc_llcp_local *local = nfc_llcp_find_local(dev);
+
+       if (local == NULL) {
+               pr_debug("No such device\n");
+               return;
+       }
+
+       nfc_llcp_local_put(local);
+}
+
+int __init nfc_llcp_init(void)
+{
+       INIT_LIST_HEAD(&llcp_devices);
+
+       return nfc_llcp_sock_init();
+}
+
+void nfc_llcp_exit(void)
+{
+       nfc_llcp_sock_exit();
+}
diff --git a/net/nfc/llcp/llcp.h b/net/nfc/llcp/llcp.h
new file mode 100644 (file)
index 0000000..83b8bba
--- /dev/null
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2011  Intel Corporation. All rights reserved.
+ *
+ * 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
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+enum llcp_state {
+       LLCP_CONNECTED = 1, /* wait_for_packet() wants that */
+       LLCP_CLOSED,
+       LLCP_BOUND,
+       LLCP_LISTEN,
+};
+
+#define LLCP_DEFAULT_LTO 100
+#define LLCP_DEFAULT_RW  1
+#define LLCP_DEFAULT_MIU 128
+
+#define LLCP_MAX_LTO  0xff
+#define LLCP_MAX_RW   15
+#define LLCP_MAX_MIUX 0x7ff
+
+#define LLCP_WKS_NUM_SAP   16
+#define LLCP_SDP_NUM_SAP   16
+#define LLCP_LOCAL_NUM_SAP 32
+#define LLCP_LOCAL_SAP_OFFSET (LLCP_WKS_NUM_SAP + LLCP_SDP_NUM_SAP)
+#define LLCP_MAX_SAP (LLCP_WKS_NUM_SAP + LLCP_SDP_NUM_SAP + LLCP_LOCAL_NUM_SAP)
+#define LLCP_SDP_UNBOUND   (LLCP_MAX_SAP + 1)
+
+struct nfc_llcp_sock;
+
+struct llcp_sock_list {
+       struct hlist_head head;
+       rwlock_t          lock;
+};
+
+struct nfc_llcp_local {
+       struct list_head list;
+       struct nfc_dev *dev;
+
+       struct kref ref;
+
+       struct mutex sdp_lock;
+
+       struct timer_list link_timer;
+       struct sk_buff_head tx_queue;
+       struct workqueue_struct *tx_wq;
+       struct work_struct       tx_work;
+       struct workqueue_struct *rx_wq;
+       struct work_struct       rx_work;
+       struct sk_buff *rx_pending;
+       struct workqueue_struct *timeout_wq;
+       struct work_struct       timeout_work;
+
+       u32 target_idx;
+       u8 rf_mode;
+       u8 comm_mode;
+       unsigned long local_wks;      /* Well known services */
+       unsigned long local_sdp;      /* Local services  */
+       unsigned long local_sap; /* Local SAPs, not available for discovery */
+       atomic_t local_sdp_cnt[LLCP_SDP_NUM_SAP];
+
+       /* local */
+       u8 gb[NFC_MAX_GT_LEN];
+       u8 gb_len;
+
+       /* remote */
+       u8 remote_gb[NFC_MAX_GT_LEN];
+       u8 remote_gb_len;
+
+       u8  remote_version;
+       u16 remote_miu;
+       u16 remote_lto;
+       u8  remote_opt;
+       u16 remote_wks;
+
+       /* sockets array */
+       struct llcp_sock_list sockets;
+       struct llcp_sock_list connecting_sockets;
+};
+
+struct nfc_llcp_sock {
+       struct sock sk;
+       struct nfc_dev *dev;
+       struct nfc_llcp_local *local;
+       u32 target_idx;
+       u32 nfc_protocol;
+
+       /* Link parameters */
+       u8 ssap;
+       u8 dsap;
+       char *service_name;
+       size_t service_name_len;
+       u8 rw;
+       u16 miu;
+
+       /* Link variables */
+       u8 send_n;
+       u8 send_ack_n;
+       u8 recv_n;
+       u8 recv_ack_n;
+
+       /* Is the remote peer ready to receive */
+       u8 remote_ready;
+
+       /* Reserved source SAP */
+       u8 reserved_ssap;
+
+       struct sk_buff_head tx_queue;
+       struct sk_buff_head tx_pending_queue;
+       struct sk_buff_head tx_backlog_queue;
+
+       struct list_head accept_queue;
+       struct sock *parent;
+};
+
+#define nfc_llcp_sock(sk) ((struct nfc_llcp_sock *) (sk))
+#define nfc_llcp_dev(sk)  (nfc_llcp_sock((sk))->dev)
+
+#define LLCP_HEADER_SIZE   2
+#define LLCP_SEQUENCE_SIZE 1
+
+/* LLCP versions: 1.1 is 1.0 plus SDP */
+#define LLCP_VERSION_10 0x10
+#define LLCP_VERSION_11 0x11
+
+/* LLCP PDU types */
+#define LLCP_PDU_SYMM     0x0
+#define LLCP_PDU_PAX      0x1
+#define LLCP_PDU_AGF      0x2
+#define LLCP_PDU_UI       0x3
+#define LLCP_PDU_CONNECT  0x4
+#define LLCP_PDU_DISC     0x5
+#define LLCP_PDU_CC       0x6
+#define LLCP_PDU_DM       0x7
+#define LLCP_PDU_FRMR     0x8
+#define LLCP_PDU_SNL      0x9
+#define LLCP_PDU_I        0xc
+#define LLCP_PDU_RR       0xd
+#define LLCP_PDU_RNR      0xe
+
+/* Parameters TLV types */
+#define LLCP_TLV_VERSION 0x1
+#define LLCP_TLV_MIUX    0x2
+#define LLCP_TLV_WKS     0x3
+#define LLCP_TLV_LTO     0x4
+#define LLCP_TLV_RW      0x5
+#define LLCP_TLV_SN      0x6
+#define LLCP_TLV_OPT     0x7
+#define LLCP_TLV_SDREQ   0x8
+#define LLCP_TLV_SDRES   0x9
+#define LLCP_TLV_MAX     0xa
+
+/* Well known LLCP SAP */
+#define LLCP_SAP_SDP   0x1
+#define LLCP_SAP_IP    0x2
+#define LLCP_SAP_OBEX  0x3
+#define LLCP_SAP_SNEP  0x4
+#define LLCP_SAP_MAX   0xff
+
+/* Disconnection reason code */
+#define LLCP_DM_DISC    0x00
+#define LLCP_DM_NOCONN  0x01
+#define LLCP_DM_NOBOUND 0x02
+#define LLCP_DM_REJ     0x03
+
+
+void nfc_llcp_sock_link(struct llcp_sock_list *l, struct sock *s);
+void nfc_llcp_sock_unlink(struct llcp_sock_list *l, struct sock *s);
+struct nfc_llcp_local *nfc_llcp_find_local(struct nfc_dev *dev);
+struct nfc_llcp_local *nfc_llcp_local_get(struct nfc_llcp_local *local);
+int nfc_llcp_local_put(struct nfc_llcp_local *local);
+u8 nfc_llcp_get_sdp_ssap(struct nfc_llcp_local *local,
+                        struct nfc_llcp_sock *sock);
+u8 nfc_llcp_get_local_ssap(struct nfc_llcp_local *local);
+void nfc_llcp_put_ssap(struct nfc_llcp_local *local, u8 ssap);
+int nfc_llcp_queue_i_frames(struct nfc_llcp_sock *sock);
+
+/* Sock API */
+struct sock *nfc_llcp_sock_alloc(struct socket *sock, int type, gfp_t gfp);
+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);
+struct sock *nfc_llcp_accept_dequeue(struct sock *sk, struct socket *newsock);
+
+/* TLV API */
+int nfc_llcp_parse_gb_tlv(struct nfc_llcp_local *local,
+                         u8 *tlv_array, u16 tlv_array_len);
+int nfc_llcp_parse_connection_tlv(struct nfc_llcp_sock *sock,
+                                 u8 *tlv_array, u16 tlv_array_len);
+
+/* Commands API */
+void nfc_llcp_recv(void *data, struct sk_buff *skb, int err);
+u8 *nfc_llcp_build_tlv(u8 type, u8 *value, u8 value_length, u8 *tlv_length);
+void nfc_llcp_recv(void *data, struct sk_buff *skb, int err);
+int nfc_llcp_disconnect(struct nfc_llcp_sock *sock);
+int nfc_llcp_send_symm(struct nfc_dev *dev);
+int nfc_llcp_send_connect(struct nfc_llcp_sock *sock);
+int nfc_llcp_send_cc(struct nfc_llcp_sock *sock);
+int nfc_llcp_send_dm(struct nfc_llcp_local *local, u8 ssap, u8 dsap, u8 reason);
+int nfc_llcp_send_disconnect(struct nfc_llcp_sock *sock);
+int nfc_llcp_send_i_frame(struct nfc_llcp_sock *sock,
+                         struct msghdr *msg, size_t len);
+int nfc_llcp_send_rr(struct nfc_llcp_sock *sock);
+
+/* Socket API */
+int __init nfc_llcp_sock_init(void);
+void nfc_llcp_sock_exit(void);
diff --git a/net/nfc/llcp/sock.c b/net/nfc/llcp/sock.c
new file mode 100644 (file)
index 0000000..03e608c
--- /dev/null
@@ -0,0 +1,761 @@
+/*
+ * Copyright (C) 2011  Intel Corporation. All rights reserved.
+ *
+ * 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
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#define pr_fmt(fmt) "llcp: %s: " fmt, __func__
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/nfc.h>
+
+#include "../nfc.h"
+#include "llcp.h"
+
+static int sock_wait_state(struct sock *sk, int state, unsigned long timeo)
+{
+       DECLARE_WAITQUEUE(wait, current);
+       int err = 0;
+
+       pr_debug("sk %p", sk);
+
+       add_wait_queue(sk_sleep(sk), &wait);
+       set_current_state(TASK_INTERRUPTIBLE);
+
+       while (sk->sk_state != state) {
+               if (!timeo) {
+                       err = -EINPROGRESS;
+                       break;
+               }
+
+               if (signal_pending(current)) {
+                       err = sock_intr_errno(timeo);
+                       break;
+               }
+
+               release_sock(sk);
+               timeo = schedule_timeout(timeo);
+               lock_sock(sk);
+               set_current_state(TASK_INTERRUPTIBLE);
+
+               err = sock_error(sk);
+               if (err)
+                       break;
+       }
+
+       __set_current_state(TASK_RUNNING);
+       remove_wait_queue(sk_sleep(sk), &wait);
+       return err;
+}
+
+static struct proto llcp_sock_proto = {
+       .name     = "NFC_LLCP",
+       .owner    = THIS_MODULE,
+       .obj_size = sizeof(struct nfc_llcp_sock),
+};
+
+static int llcp_sock_bind(struct socket *sock, struct sockaddr *addr, int alen)
+{
+       struct sock *sk = sock->sk;
+       struct nfc_llcp_sock *llcp_sock = nfc_llcp_sock(sk);
+       struct nfc_llcp_local *local;
+       struct nfc_dev *dev;
+       struct sockaddr_nfc_llcp llcp_addr;
+       int len, ret = 0;
+
+       if (!addr || addr->sa_family != AF_NFC)
+               return -EINVAL;
+
+       pr_debug("sk %p addr %p family %d\n", sk, addr, addr->sa_family);
+
+       memset(&llcp_addr, 0, sizeof(llcp_addr));
+       len = min_t(unsigned int, sizeof(llcp_addr), alen);
+       memcpy(&llcp_addr, addr, len);
+
+       /* This is going to be a listening socket, dsap must be 0 */
+       if (llcp_addr.dsap != 0)
+               return -EINVAL;
+
+       lock_sock(sk);
+
+       if (sk->sk_state != LLCP_CLOSED) {
+               ret = -EBADFD;
+               goto error;
+       }
+
+       dev = nfc_get_device(llcp_addr.dev_idx);
+       if (dev == NULL) {
+               ret = -ENODEV;
+               goto error;
+       }
+
+       local = nfc_llcp_find_local(dev);
+       if (local == NULL) {
+               ret = -ENODEV;
+               goto put_dev;
+       }
+
+       llcp_sock->dev = dev;
+       llcp_sock->local = nfc_llcp_local_get(local);
+       llcp_sock->nfc_protocol = llcp_addr.nfc_protocol;
+       llcp_sock->service_name_len = min_t(unsigned int,
+                                           llcp_addr.service_name_len,
+                                           NFC_LLCP_MAX_SERVICE_NAME);
+       llcp_sock->service_name = kmemdup(llcp_addr.service_name,
+                                         llcp_sock->service_name_len,
+                                         GFP_KERNEL);
+
+       llcp_sock->ssap = nfc_llcp_get_sdp_ssap(local, llcp_sock);
+       if (llcp_sock->ssap == LLCP_SAP_MAX) {
+               ret = -EADDRINUSE;
+               goto put_dev;
+       }
+
+       llcp_sock->reserved_ssap = llcp_sock->ssap;
+
+       nfc_llcp_sock_link(&local->sockets, sk);
+
+       pr_debug("Socket bound to SAP %d\n", llcp_sock->ssap);
+
+       sk->sk_state = LLCP_BOUND;
+
+put_dev:
+       nfc_put_device(dev);
+
+error:
+       release_sock(sk);
+       return ret;
+}
+
+static int llcp_sock_listen(struct socket *sock, int backlog)
+{
+       struct sock *sk = sock->sk;
+       int ret = 0;
+
+       pr_debug("sk %p backlog %d\n", sk, backlog);
+
+       lock_sock(sk);
+
+       if ((sock->type != SOCK_SEQPACKET && sock->type != SOCK_STREAM)
+           || sk->sk_state != LLCP_BOUND) {
+               ret = -EBADFD;
+               goto error;
+       }
+
+       sk->sk_max_ack_backlog = backlog;
+       sk->sk_ack_backlog = 0;
+
+       pr_debug("Socket listening\n");
+       sk->sk_state = LLCP_LISTEN;
+
+error:
+       release_sock(sk);
+
+       return ret;
+}
+
+void nfc_llcp_accept_unlink(struct sock *sk)
+{
+       struct nfc_llcp_sock *llcp_sock = nfc_llcp_sock(sk);
+
+       pr_debug("state %d\n", sk->sk_state);
+
+       list_del_init(&llcp_sock->accept_queue);
+       sk_acceptq_removed(llcp_sock->parent);
+       llcp_sock->parent = NULL;
+
+       sock_put(sk);
+}
+
+void nfc_llcp_accept_enqueue(struct sock *parent, struct sock *sk)
+{
+       struct nfc_llcp_sock *llcp_sock = nfc_llcp_sock(sk);
+       struct nfc_llcp_sock *llcp_sock_parent = nfc_llcp_sock(parent);
+
+       /* Lock will be free from unlink */
+       sock_hold(sk);
+
+       list_add_tail(&llcp_sock->accept_queue,
+                     &llcp_sock_parent->accept_queue);
+       llcp_sock->parent = parent;
+       sk_acceptq_added(parent);
+}
+
+struct sock *nfc_llcp_accept_dequeue(struct sock *parent,
+                                    struct socket *newsock)
+{
+       struct nfc_llcp_sock *lsk, *n, *llcp_parent;
+       struct sock *sk;
+
+       llcp_parent = nfc_llcp_sock(parent);
+
+       list_for_each_entry_safe(lsk, n, &llcp_parent->accept_queue,
+                                accept_queue) {
+               sk = &lsk->sk;
+               lock_sock(sk);
+
+               if (sk->sk_state == LLCP_CLOSED) {
+                       release_sock(sk);
+                       nfc_llcp_accept_unlink(sk);
+                       continue;
+               }
+
+               if (sk->sk_state == LLCP_CONNECTED || !newsock) {
+                       nfc_llcp_accept_unlink(sk);
+                       if (newsock)
+                               sock_graft(sk, newsock);
+
+                       release_sock(sk);
+
+                       pr_debug("Returning sk state %d\n", sk->sk_state);
+
+                       return sk;
+               }
+
+               release_sock(sk);
+       }
+
+       return NULL;
+}
+
+static int llcp_sock_accept(struct socket *sock, struct socket *newsock,
+                           int flags)
+{
+       DECLARE_WAITQUEUE(wait, current);
+       struct sock *sk = sock->sk, *new_sk;
+       long timeo;
+       int ret = 0;
+
+       pr_debug("parent %p\n", sk);
+
+       lock_sock_nested(sk, SINGLE_DEPTH_NESTING);
+
+       if (sk->sk_state != LLCP_LISTEN) {
+               ret = -EBADFD;
+               goto error;
+       }
+
+       timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK);
+
+       /* Wait for an incoming connection. */
+       add_wait_queue_exclusive(sk_sleep(sk), &wait);
+       while (!(new_sk = nfc_llcp_accept_dequeue(sk, newsock))) {
+               set_current_state(TASK_INTERRUPTIBLE);
+
+               if (!timeo) {
+                       ret = -EAGAIN;
+                       break;
+               }
+
+               if (signal_pending(current)) {
+                       ret = sock_intr_errno(timeo);
+                       break;
+               }
+
+               release_sock(sk);
+               timeo = schedule_timeout(timeo);
+               lock_sock_nested(sk, SINGLE_DEPTH_NESTING);
+       }
+       __set_current_state(TASK_RUNNING);
+       remove_wait_queue(sk_sleep(sk), &wait);
+
+       if (ret)
+               goto error;
+
+       newsock->state = SS_CONNECTED;
+
+       pr_debug("new socket %p\n", new_sk);
+
+error:
+       release_sock(sk);
+
+       return ret;
+}
+
+static int llcp_sock_getname(struct socket *sock, struct sockaddr *uaddr,
+                            int *len, int peer)
+{
+       struct sock *sk = sock->sk;
+       struct nfc_llcp_sock *llcp_sock = nfc_llcp_sock(sk);
+       DECLARE_SOCKADDR(struct sockaddr_nfc_llcp *, llcp_addr, uaddr);
+
+       if (llcp_sock == NULL || llcp_sock->dev == NULL)
+               return -EBADFD;
+
+       pr_debug("%p %d %d %d\n", sk, llcp_sock->target_idx,
+                llcp_sock->dsap, llcp_sock->ssap);
+
+       uaddr->sa_family = AF_NFC;
+       *len = sizeof(struct sockaddr_nfc_llcp);
+
+       llcp_addr->dev_idx = llcp_sock->dev->idx;
+       llcp_addr->target_idx = llcp_sock->target_idx;
+       llcp_addr->dsap = llcp_sock->dsap;
+       llcp_addr->ssap = llcp_sock->ssap;
+       llcp_addr->service_name_len = llcp_sock->service_name_len;
+       memcpy(llcp_addr->service_name, llcp_sock->service_name,
+              llcp_addr->service_name_len);
+
+       return 0;
+}
+
+static inline unsigned int llcp_accept_poll(struct sock *parent)
+{
+       struct nfc_llcp_sock *llcp_sock, *n, *parent_sock;
+       struct sock *sk;
+
+       parent_sock = nfc_llcp_sock(parent);
+
+       list_for_each_entry_safe(llcp_sock, n, &parent_sock->accept_queue,
+                                accept_queue) {
+               sk = &llcp_sock->sk;
+
+               if (sk->sk_state == LLCP_CONNECTED)
+                       return POLLIN | POLLRDNORM;
+       }
+
+       return 0;
+}
+
+static unsigned int llcp_sock_poll(struct file *file, struct socket *sock,
+                                  poll_table *wait)
+{
+       struct sock *sk = sock->sk;
+       unsigned int mask = 0;
+
+       pr_debug("%p\n", sk);
+
+       sock_poll_wait(file, sk_sleep(sk), wait);
+
+       if (sk->sk_state == LLCP_LISTEN)
+               return llcp_accept_poll(sk);
+
+       if (sk->sk_err || !skb_queue_empty(&sk->sk_error_queue))
+               mask |= POLLERR;
+
+       if (!skb_queue_empty(&sk->sk_receive_queue))
+               mask |= POLLIN | POLLRDNORM;
+
+       if (sk->sk_state == LLCP_CLOSED)
+               mask |= POLLHUP;
+
+       if (sk->sk_shutdown & RCV_SHUTDOWN)
+               mask |= POLLRDHUP | POLLIN | POLLRDNORM;
+
+       if (sk->sk_shutdown == SHUTDOWN_MASK)
+               mask |= POLLHUP;
+
+       if (sock_writeable(sk))
+               mask |= POLLOUT | POLLWRNORM | POLLWRBAND;
+       else
+               set_bit(SOCK_ASYNC_NOSPACE, &sk->sk_socket->flags);
+
+       pr_debug("mask 0x%x\n", mask);
+
+       return mask;
+}
+
+static int llcp_sock_release(struct socket *sock)
+{
+       struct sock *sk = sock->sk;
+       struct nfc_llcp_local *local;
+       struct nfc_llcp_sock *llcp_sock = nfc_llcp_sock(sk);
+       int err = 0;
+
+       if (!sk)
+               return 0;
+
+       pr_debug("%p\n", sk);
+
+       local = llcp_sock->local;
+       if (local == NULL) {
+               err = -ENODEV;
+               goto out;
+       }
+
+       lock_sock(sk);
+
+       /* Send a DISC */
+       if (sk->sk_state == LLCP_CONNECTED)
+               nfc_llcp_disconnect(llcp_sock);
+
+       if (sk->sk_state == LLCP_LISTEN) {
+               struct nfc_llcp_sock *lsk, *n;
+               struct sock *accept_sk;
+
+               list_for_each_entry_safe(lsk, n, &llcp_sock->accept_queue,
+                                        accept_queue) {
+                       accept_sk = &lsk->sk;
+                       lock_sock(accept_sk);
+
+                       nfc_llcp_disconnect(lsk);
+                       nfc_llcp_accept_unlink(accept_sk);
+
+                       release_sock(accept_sk);
+
+                       sock_orphan(accept_sk);
+               }
+       }
+
+       if (llcp_sock->reserved_ssap < LLCP_SAP_MAX)
+               nfc_llcp_put_ssap(llcp_sock->local, llcp_sock->ssap);
+
+       release_sock(sk);
+
+       nfc_llcp_sock_unlink(&local->sockets, sk);
+
+out:
+       sock_orphan(sk);
+       sock_put(sk);
+
+       return err;
+}
+
+static int llcp_sock_connect(struct socket *sock, struct sockaddr *_addr,
+                            int len, int flags)
+{
+       struct sock *sk = sock->sk;
+       struct nfc_llcp_sock *llcp_sock = nfc_llcp_sock(sk);
+       struct sockaddr_nfc_llcp *addr = (struct sockaddr_nfc_llcp *)_addr;
+       struct nfc_dev *dev;
+       struct nfc_llcp_local *local;
+       int ret = 0;
+
+       pr_debug("sock %p sk %p flags 0x%x\n", sock, sk, flags);
+
+       if (!addr || len < sizeof(struct sockaddr_nfc) ||
+           addr->sa_family != AF_NFC) {
+               pr_err("Invalid socket\n");
+               return -EINVAL;
+       }
+
+       if (addr->service_name_len == 0 && addr->dsap == 0) {
+               pr_err("Missing service name or dsap\n");
+               return -EINVAL;
+       }
+
+       pr_debug("addr dev_idx=%u target_idx=%u protocol=%u\n", addr->dev_idx,
+                addr->target_idx, addr->nfc_protocol);
+
+       lock_sock(sk);
+
+       if (sk->sk_state == LLCP_CONNECTED) {
+               ret = -EISCONN;
+               goto error;
+       }
+
+       dev = nfc_get_device(addr->dev_idx);
+       if (dev == NULL) {
+               ret = -ENODEV;
+               goto error;
+       }
+
+       local = nfc_llcp_find_local(dev);
+       if (local == NULL) {
+               ret = -ENODEV;
+               goto put_dev;
+       }
+
+       device_lock(&dev->dev);
+       if (dev->dep_link_up == false) {
+               ret = -ENOLINK;
+               device_unlock(&dev->dev);
+               goto put_dev;
+       }
+       device_unlock(&dev->dev);
+
+       if (local->rf_mode == NFC_RF_INITIATOR &&
+           addr->target_idx != local->target_idx) {
+               ret = -ENOLINK;
+               goto put_dev;
+       }
+
+       llcp_sock->dev = dev;
+       llcp_sock->local = nfc_llcp_local_get(local);
+       llcp_sock->miu = llcp_sock->local->remote_miu;
+       llcp_sock->ssap = nfc_llcp_get_local_ssap(local);
+       if (llcp_sock->ssap == LLCP_SAP_MAX) {
+               ret = -ENOMEM;
+               goto put_dev;
+       }
+
+       llcp_sock->reserved_ssap = llcp_sock->ssap;
+
+       if (addr->service_name_len == 0)
+               llcp_sock->dsap = addr->dsap;
+       else
+               llcp_sock->dsap = LLCP_SAP_SDP;
+       llcp_sock->nfc_protocol = addr->nfc_protocol;
+       llcp_sock->service_name_len = min_t(unsigned int,
+                                           addr->service_name_len,
+                                           NFC_LLCP_MAX_SERVICE_NAME);
+       llcp_sock->service_name = kmemdup(addr->service_name,
+                                         llcp_sock->service_name_len,
+                                         GFP_KERNEL);
+
+       nfc_llcp_sock_link(&local->connecting_sockets, sk);
+
+       ret = nfc_llcp_send_connect(llcp_sock);
+       if (ret)
+               goto sock_unlink;
+
+       ret = sock_wait_state(sk, LLCP_CONNECTED,
+                             sock_sndtimeo(sk, flags & O_NONBLOCK));
+       if (ret)
+               goto sock_unlink;
+
+       release_sock(sk);
+
+       return 0;
+
+sock_unlink:
+       nfc_llcp_put_ssap(local, llcp_sock->ssap);
+
+       nfc_llcp_sock_unlink(&local->connecting_sockets, sk);
+
+put_dev:
+       nfc_put_device(dev);
+
+error:
+       release_sock(sk);
+       return ret;
+}
+
+static int llcp_sock_sendmsg(struct kiocb *iocb, struct socket *sock,
+                            struct msghdr *msg, size_t len)
+{
+       struct sock *sk = sock->sk;
+       struct nfc_llcp_sock *llcp_sock = nfc_llcp_sock(sk);
+       int ret;
+
+       pr_debug("sock %p sk %p", sock, sk);
+
+       ret = sock_error(sk);
+       if (ret)
+               return ret;
+
+       if (msg->msg_flags & MSG_OOB)
+               return -EOPNOTSUPP;
+
+       lock_sock(sk);
+
+       if (sk->sk_state != LLCP_CONNECTED) {
+               release_sock(sk);
+               return -ENOTCONN;
+       }
+
+       release_sock(sk);
+
+       return nfc_llcp_send_i_frame(llcp_sock, msg, len);
+}
+
+static int llcp_sock_recvmsg(struct kiocb *iocb, struct socket *sock,
+                            struct msghdr *msg, size_t len, int flags)
+{
+       int noblock = flags & MSG_DONTWAIT;
+       struct sock *sk = sock->sk;
+       unsigned int copied, rlen;
+       struct sk_buff *skb, *cskb;
+       int err = 0;
+
+       pr_debug("%p %zu\n", sk, len);
+
+       lock_sock(sk);
+
+       if (sk->sk_state == LLCP_CLOSED &&
+           skb_queue_empty(&sk->sk_receive_queue)) {
+               release_sock(sk);
+               return 0;
+       }
+
+       release_sock(sk);
+
+       if (flags & (MSG_OOB))
+               return -EOPNOTSUPP;
+
+       skb = skb_recv_datagram(sk, flags, noblock, &err);
+       if (!skb) {
+               pr_err("Recv datagram failed state %d %d %d",
+                      sk->sk_state, err, sock_error(sk));
+
+               if (sk->sk_shutdown & RCV_SHUTDOWN)
+                       return 0;
+
+               return err;
+       }
+
+       rlen = skb->len;                /* real length of skb */
+       copied = min_t(unsigned int, rlen, len);
+
+       cskb = skb;
+       if (memcpy_toiovec(msg->msg_iov, cskb->data, copied)) {
+               if (!(flags & MSG_PEEK))
+                       skb_queue_head(&sk->sk_receive_queue, skb);
+               return -EFAULT;
+       }
+
+       /* Mark read part of skb as used */
+       if (!(flags & MSG_PEEK)) {
+
+               /* SOCK_STREAM: re-queue skb if it contains unreceived data */
+               if (sk->sk_type == SOCK_STREAM) {
+                       skb_pull(skb, copied);
+                       if (skb->len) {
+                               skb_queue_head(&sk->sk_receive_queue, skb);
+                               goto done;
+                       }
+               }
+
+               kfree_skb(skb);
+       }
+
+       /* XXX Queue backlogged skbs */
+
+done:
+       /* SOCK_SEQPACKET: return real length if MSG_TRUNC is set */
+       if (sk->sk_type == SOCK_SEQPACKET && (flags & MSG_TRUNC))
+               copied = rlen;
+
+       return copied;
+}
+
+static const struct proto_ops llcp_sock_ops = {
+       .family         = PF_NFC,
+       .owner          = THIS_MODULE,
+       .bind           = llcp_sock_bind,
+       .connect        = llcp_sock_connect,
+       .release        = llcp_sock_release,
+       .socketpair     = sock_no_socketpair,
+       .accept         = llcp_sock_accept,
+       .getname        = llcp_sock_getname,
+       .poll           = llcp_sock_poll,
+       .ioctl          = sock_no_ioctl,
+       .listen         = llcp_sock_listen,
+       .shutdown       = sock_no_shutdown,
+       .setsockopt     = sock_no_setsockopt,
+       .getsockopt     = sock_no_getsockopt,
+       .sendmsg        = llcp_sock_sendmsg,
+       .recvmsg        = llcp_sock_recvmsg,
+       .mmap           = sock_no_mmap,
+};
+
+static void llcp_sock_destruct(struct sock *sk)
+{
+       struct nfc_llcp_sock *llcp_sock = nfc_llcp_sock(sk);
+
+       pr_debug("%p\n", sk);
+
+       if (sk->sk_state == LLCP_CONNECTED)
+               nfc_put_device(llcp_sock->dev);
+
+       skb_queue_purge(&sk->sk_receive_queue);
+
+       nfc_llcp_sock_free(llcp_sock);
+
+       if (!sock_flag(sk, SOCK_DEAD)) {
+               pr_err("Freeing alive NFC LLCP socket %p\n", sk);
+               return;
+       }
+}
+
+struct sock *nfc_llcp_sock_alloc(struct socket *sock, int type, gfp_t gfp)
+{
+       struct sock *sk;
+       struct nfc_llcp_sock *llcp_sock;
+
+       sk = sk_alloc(&init_net, PF_NFC, gfp, &llcp_sock_proto);
+       if (!sk)
+               return NULL;
+
+       llcp_sock = nfc_llcp_sock(sk);
+
+       sock_init_data(sock, sk);
+       sk->sk_state = LLCP_CLOSED;
+       sk->sk_protocol = NFC_SOCKPROTO_LLCP;
+       sk->sk_type = type;
+       sk->sk_destruct = llcp_sock_destruct;
+
+       llcp_sock->ssap = 0;
+       llcp_sock->dsap = LLCP_SAP_SDP;
+       llcp_sock->rw = LLCP_DEFAULT_RW;
+       llcp_sock->miu = LLCP_DEFAULT_MIU;
+       llcp_sock->send_n = llcp_sock->send_ack_n = 0;
+       llcp_sock->recv_n = llcp_sock->recv_ack_n = 0;
+       llcp_sock->remote_ready = 1;
+       llcp_sock->reserved_ssap = LLCP_SAP_MAX;
+       skb_queue_head_init(&llcp_sock->tx_queue);
+       skb_queue_head_init(&llcp_sock->tx_pending_queue);
+       skb_queue_head_init(&llcp_sock->tx_backlog_queue);
+       INIT_LIST_HEAD(&llcp_sock->accept_queue);
+
+       if (sock != NULL)
+               sock->state = SS_UNCONNECTED;
+
+       return sk;
+}
+
+void nfc_llcp_sock_free(struct nfc_llcp_sock *sock)
+{
+       kfree(sock->service_name);
+
+       skb_queue_purge(&sock->tx_queue);
+       skb_queue_purge(&sock->tx_pending_queue);
+       skb_queue_purge(&sock->tx_backlog_queue);
+
+       list_del_init(&sock->accept_queue);
+
+       sock->parent = NULL;
+
+       nfc_llcp_local_put(sock->local);
+}
+
+static int llcp_sock_create(struct net *net, struct socket *sock,
+                           const struct nfc_protocol *nfc_proto)
+{
+       struct sock *sk;
+
+       pr_debug("%p\n", sock);
+
+       if (sock->type != SOCK_STREAM && sock->type != SOCK_DGRAM)
+               return -ESOCKTNOSUPPORT;
+
+       sock->ops = &llcp_sock_ops;
+
+       sk = nfc_llcp_sock_alloc(sock, sock->type, GFP_ATOMIC);
+       if (sk == NULL)
+               return -ENOMEM;
+
+       return 0;
+}
+
+static const struct nfc_protocol llcp_nfc_proto = {
+       .id       = NFC_SOCKPROTO_LLCP,
+       .proto    = &llcp_sock_proto,
+       .owner    = THIS_MODULE,
+       .create   = llcp_sock_create
+};
+
+int __init nfc_llcp_sock_init(void)
+{
+       return nfc_proto_register(&llcp_nfc_proto);
+}
+
+void nfc_llcp_sock_exit(void)
+{
+       nfc_proto_unregister(&llcp_nfc_proto);
+}
diff --git a/net/nfc/nci/Kconfig b/net/nfc/nci/Kconfig
new file mode 100644 (file)
index 0000000..decdc49
--- /dev/null
@@ -0,0 +1,10 @@
+config NFC_NCI
+       depends on NFC && EXPERIMENTAL
+       tristate "NCI protocol support (EXPERIMENTAL)"
+       default n
+       help
+         NCI (NFC Controller Interface) is a communication protocol between
+         an NFC Controller (NFCC) and a Device Host (DH).
+
+         Say Y here to compile NCI support into the kernel or say M to
+         compile it as module (nci).
diff --git a/net/nfc/nci/Makefile b/net/nfc/nci/Makefile
new file mode 100644 (file)
index 0000000..cdb3a2e
--- /dev/null
@@ -0,0 +1,7 @@
+#
+# Makefile for the Linux NFC NCI layer.
+#
+
+obj-$(CONFIG_NFC_NCI) += nci.o
+
+nci-objs := core.o data.o lib.o ntf.o rsp.o
\ No newline at end of file
diff --git a/net/nfc/nci/core.c b/net/nfc/nci/core.c
new file mode 100644 (file)
index 0000000..9195b3d
--- /dev/null
@@ -0,0 +1,884 @@
+/*
+ *  The NFC Controller Interface is the communication protocol between an
+ *  NFC Controller (NFCC) and a Device Host (DH).
+ *
+ *  Copyright (C) 2011 Texas Instruments, Inc.
+ *
+ *  Written by Ilan Elias <ilane@ti.com>
+ *
+ *  Acknowledgements:
+ *  This file is based on hci_core.c, which was written
+ *  by Maxim Krasnyansky.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2
+ *  as published by the Free Software Foundation
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": %s: " fmt, __func__
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/workqueue.h>
+#include <linux/completion.h>
+#include <linux/sched.h>
+#include <linux/bitops.h>
+#include <linux/skbuff.h>
+
+#include "../nfc.h"
+#include <net/nfc/nci.h>
+#include <net/nfc/nci_core.h>
+#include <linux/nfc.h>
+
+static void nci_cmd_work(struct work_struct *work);
+static void nci_rx_work(struct work_struct *work);
+static void nci_tx_work(struct work_struct *work);
+
+/* ---- NCI requests ---- */
+
+void nci_req_complete(struct nci_dev *ndev, int result)
+{
+       if (ndev->req_status == NCI_REQ_PEND) {
+               ndev->req_result = result;
+               ndev->req_status = NCI_REQ_DONE;
+               complete(&ndev->req_completion);
+       }
+}
+
+static void nci_req_cancel(struct nci_dev *ndev, int err)
+{
+       if (ndev->req_status == NCI_REQ_PEND) {
+               ndev->req_result = err;
+               ndev->req_status = NCI_REQ_CANCELED;
+               complete(&ndev->req_completion);
+       }
+}
+
+/* Execute request and wait for completion. */
+static int __nci_request(struct nci_dev *ndev,
+                        void (*req)(struct nci_dev *ndev, unsigned long opt),
+                        unsigned long opt, __u32 timeout)
+{
+       int rc = 0;
+       long completion_rc;
+
+       ndev->req_status = NCI_REQ_PEND;
+
+       init_completion(&ndev->req_completion);
+       req(ndev, opt);
+       completion_rc =
+               wait_for_completion_interruptible_timeout(&ndev->req_completion,
+                                                         timeout);
+
+       pr_debug("wait_for_completion return %ld\n", completion_rc);
+
+       if (completion_rc > 0) {
+               switch (ndev->req_status) {
+               case NCI_REQ_DONE:
+                       rc = nci_to_errno(ndev->req_result);
+                       break;
+
+               case NCI_REQ_CANCELED:
+                       rc = -ndev->req_result;
+                       break;
+
+               default:
+                       rc = -ETIMEDOUT;
+                       break;
+               }
+       } else {
+               pr_err("wait_for_completion_interruptible_timeout failed %ld\n",
+                      completion_rc);
+
+               rc = ((completion_rc == 0) ? (-ETIMEDOUT) : (completion_rc));
+       }
+
+       ndev->req_status = ndev->req_result = 0;
+
+       return rc;
+}
+
+static inline int nci_request(struct nci_dev *ndev,
+                             void (*req)(struct nci_dev *ndev,
+                                         unsigned long opt),
+                             unsigned long opt, __u32 timeout)
+{
+       int rc;
+
+       if (!test_bit(NCI_UP, &ndev->flags))
+               return -ENETDOWN;
+
+       /* Serialize all requests */
+       mutex_lock(&ndev->req_lock);
+       rc = __nci_request(ndev, req, opt, timeout);
+       mutex_unlock(&ndev->req_lock);
+
+       return rc;
+}
+
+static void nci_reset_req(struct nci_dev *ndev, unsigned long opt)
+{
+       struct nci_core_reset_cmd cmd;
+
+       cmd.reset_type = NCI_RESET_TYPE_RESET_CONFIG;
+       nci_send_cmd(ndev, NCI_OP_CORE_RESET_CMD, 1, &cmd);
+}
+
+static void nci_init_req(struct nci_dev *ndev, unsigned long opt)
+{
+       nci_send_cmd(ndev, NCI_OP_CORE_INIT_CMD, 0, NULL);
+}
+
+static void nci_init_complete_req(struct nci_dev *ndev, unsigned long opt)
+{
+       struct nci_rf_disc_map_cmd cmd;
+       struct disc_map_config *cfg = cmd.mapping_configs;
+       __u8 *num = &cmd.num_mapping_configs;
+       int i;
+
+       /* set rf mapping configurations */
+       *num = 0;
+
+       /* by default mapping is set to NCI_RF_INTERFACE_FRAME */
+       for (i = 0; i < ndev->num_supported_rf_interfaces; i++) {
+               if (ndev->supported_rf_interfaces[i] ==
+                   NCI_RF_INTERFACE_ISO_DEP) {
+                       cfg[*num].rf_protocol = NCI_RF_PROTOCOL_ISO_DEP;
+                       cfg[*num].mode = NCI_DISC_MAP_MODE_POLL |
+                               NCI_DISC_MAP_MODE_LISTEN;
+                       cfg[*num].rf_interface = NCI_RF_INTERFACE_ISO_DEP;
+                       (*num)++;
+               } else if (ndev->supported_rf_interfaces[i] ==
+                          NCI_RF_INTERFACE_NFC_DEP) {
+                       cfg[*num].rf_protocol = NCI_RF_PROTOCOL_NFC_DEP;
+                       cfg[*num].mode = NCI_DISC_MAP_MODE_POLL |
+                               NCI_DISC_MAP_MODE_LISTEN;
+                       cfg[*num].rf_interface = NCI_RF_INTERFACE_NFC_DEP;
+                       (*num)++;
+               }
+
+               if (*num == NCI_MAX_NUM_MAPPING_CONFIGS)
+                       break;
+       }
+
+       nci_send_cmd(ndev, NCI_OP_RF_DISCOVER_MAP_CMD,
+                    (1 + ((*num) * sizeof(struct disc_map_config))), &cmd);
+}
+
+static void nci_rf_discover_req(struct nci_dev *ndev, unsigned long opt)
+{
+       struct nci_rf_disc_cmd cmd;
+       __u32 protocols = opt;
+
+       cmd.num_disc_configs = 0;
+
+       if ((cmd.num_disc_configs < NCI_MAX_NUM_RF_CONFIGS) &&
+           (protocols & NFC_PROTO_JEWEL_MASK
+            || protocols & NFC_PROTO_MIFARE_MASK
+            || protocols & NFC_PROTO_ISO14443_MASK
+            || protocols & NFC_PROTO_NFC_DEP_MASK)) {
+               cmd.disc_configs[cmd.num_disc_configs].rf_tech_and_mode =
+                       NCI_NFC_A_PASSIVE_POLL_MODE;
+               cmd.disc_configs[cmd.num_disc_configs].frequency = 1;
+               cmd.num_disc_configs++;
+       }
+
+       if ((cmd.num_disc_configs < NCI_MAX_NUM_RF_CONFIGS) &&
+           (protocols & NFC_PROTO_ISO14443_B_MASK)) {
+               cmd.disc_configs[cmd.num_disc_configs].rf_tech_and_mode =
+                       NCI_NFC_B_PASSIVE_POLL_MODE;
+               cmd.disc_configs[cmd.num_disc_configs].frequency = 1;
+               cmd.num_disc_configs++;
+       }
+
+       if ((cmd.num_disc_configs < NCI_MAX_NUM_RF_CONFIGS) &&
+           (protocols & NFC_PROTO_FELICA_MASK
+            || protocols & NFC_PROTO_NFC_DEP_MASK)) {
+               cmd.disc_configs[cmd.num_disc_configs].rf_tech_and_mode =
+                       NCI_NFC_F_PASSIVE_POLL_MODE;
+               cmd.disc_configs[cmd.num_disc_configs].frequency = 1;
+               cmd.num_disc_configs++;
+       }
+
+       nci_send_cmd(ndev, NCI_OP_RF_DISCOVER_CMD,
+                    (1 + (cmd.num_disc_configs * sizeof(struct disc_config))),
+                    &cmd);
+}
+
+struct nci_rf_discover_select_param {
+       __u8    rf_discovery_id;
+       __u8    rf_protocol;
+};
+
+static void nci_rf_discover_select_req(struct nci_dev *ndev, unsigned long opt)
+{
+       struct nci_rf_discover_select_param *param =
+               (struct nci_rf_discover_select_param *)opt;
+       struct nci_rf_discover_select_cmd cmd;
+
+       cmd.rf_discovery_id = param->rf_discovery_id;
+       cmd.rf_protocol = param->rf_protocol;
+
+       switch (cmd.rf_protocol) {
+       case NCI_RF_PROTOCOL_ISO_DEP:
+               cmd.rf_interface = NCI_RF_INTERFACE_ISO_DEP;
+               break;
+
+       case NCI_RF_PROTOCOL_NFC_DEP:
+               cmd.rf_interface = NCI_RF_INTERFACE_NFC_DEP;
+               break;
+
+       default:
+               cmd.rf_interface = NCI_RF_INTERFACE_FRAME;
+               break;
+       }
+
+       nci_send_cmd(ndev, NCI_OP_RF_DISCOVER_SELECT_CMD,
+                    sizeof(struct nci_rf_discover_select_cmd), &cmd);
+}
+
+static void nci_rf_deactivate_req(struct nci_dev *ndev, unsigned long opt)
+{
+       struct nci_rf_deactivate_cmd cmd;
+
+       cmd.type = NCI_DEACTIVATE_TYPE_IDLE_MODE;
+
+       nci_send_cmd(ndev, NCI_OP_RF_DEACTIVATE_CMD,
+                    sizeof(struct nci_rf_deactivate_cmd), &cmd);
+}
+
+static int nci_open_device(struct nci_dev *ndev)
+{
+       int rc = 0;
+
+       mutex_lock(&ndev->req_lock);
+
+       if (test_bit(NCI_UP, &ndev->flags)) {
+               rc = -EALREADY;
+               goto done;
+       }
+
+       if (ndev->ops->open(ndev)) {
+               rc = -EIO;
+               goto done;
+       }
+
+       atomic_set(&ndev->cmd_cnt, 1);
+
+       set_bit(NCI_INIT, &ndev->flags);
+
+       rc = __nci_request(ndev, nci_reset_req, 0,
+                          msecs_to_jiffies(NCI_RESET_TIMEOUT));
+
+       if (!rc) {
+               rc = __nci_request(ndev, nci_init_req, 0,
+                                  msecs_to_jiffies(NCI_INIT_TIMEOUT));
+       }
+
+       if (!rc) {
+               rc = __nci_request(ndev, nci_init_complete_req, 0,
+                                  msecs_to_jiffies(NCI_INIT_TIMEOUT));
+       }
+
+       clear_bit(NCI_INIT, &ndev->flags);
+
+       if (!rc) {
+               set_bit(NCI_UP, &ndev->flags);
+               nci_clear_target_list(ndev);
+               atomic_set(&ndev->state, NCI_IDLE);
+       } else {
+               /* Init failed, cleanup */
+               skb_queue_purge(&ndev->cmd_q);
+               skb_queue_purge(&ndev->rx_q);
+               skb_queue_purge(&ndev->tx_q);
+
+               ndev->ops->close(ndev);
+               ndev->flags = 0;
+       }
+
+done:
+       mutex_unlock(&ndev->req_lock);
+       return rc;
+}
+
+static int nci_close_device(struct nci_dev *ndev)
+{
+       nci_req_cancel(ndev, ENODEV);
+       mutex_lock(&ndev->req_lock);
+
+       if (!test_and_clear_bit(NCI_UP, &ndev->flags)) {
+               del_timer_sync(&ndev->cmd_timer);
+               del_timer_sync(&ndev->data_timer);
+               mutex_unlock(&ndev->req_lock);
+               return 0;
+       }
+
+       /* Drop RX and TX queues */
+       skb_queue_purge(&ndev->rx_q);
+       skb_queue_purge(&ndev->tx_q);
+
+       /* Flush RX and TX wq */
+       flush_workqueue(ndev->rx_wq);
+       flush_workqueue(ndev->tx_wq);
+
+       /* Reset device */
+       skb_queue_purge(&ndev->cmd_q);
+       atomic_set(&ndev->cmd_cnt, 1);
+
+       set_bit(NCI_INIT, &ndev->flags);
+       __nci_request(ndev, nci_reset_req, 0,
+                     msecs_to_jiffies(NCI_RESET_TIMEOUT));
+       clear_bit(NCI_INIT, &ndev->flags);
+
+       /* 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;
+
+       mutex_unlock(&ndev->req_lock);
+
+       return 0;
+}
+
+/* NCI command timer function */
+static void nci_cmd_timer(unsigned long arg)
+{
+       struct nci_dev *ndev = (void *) arg;
+
+       atomic_set(&ndev->cmd_cnt, 1);
+       queue_work(ndev->cmd_wq, &ndev->cmd_work);
+}
+
+/* NCI data exchange timer function */
+static void nci_data_timer(unsigned long arg)
+{
+       struct nci_dev *ndev = (void *) arg;
+
+       set_bit(NCI_DATA_EXCHANGE_TO, &ndev->flags);
+       queue_work(ndev->rx_wq, &ndev->rx_work);
+}
+
+static int nci_dev_up(struct nfc_dev *nfc_dev)
+{
+       struct nci_dev *ndev = nfc_get_drvdata(nfc_dev);
+
+       return nci_open_device(ndev);
+}
+
+static int nci_dev_down(struct nfc_dev *nfc_dev)
+{
+       struct nci_dev *ndev = nfc_get_drvdata(nfc_dev);
+
+       return nci_close_device(ndev);
+}
+
+static int nci_start_poll(struct nfc_dev *nfc_dev,
+                         __u32 im_protocols, __u32 tm_protocols)
+{
+       struct nci_dev *ndev = nfc_get_drvdata(nfc_dev);
+       int rc;
+
+       if ((atomic_read(&ndev->state) == NCI_DISCOVERY) ||
+           (atomic_read(&ndev->state) == NCI_W4_ALL_DISCOVERIES)) {
+               pr_err("unable to start poll, since poll is already active\n");
+               return -EBUSY;
+       }
+
+       if (ndev->target_active_prot) {
+               pr_err("there is an active target\n");
+               return -EBUSY;
+       }
+
+       if ((atomic_read(&ndev->state) == NCI_W4_HOST_SELECT) ||
+           (atomic_read(&ndev->state) == NCI_POLL_ACTIVE)) {
+               pr_debug("target active or w4 select, implicitly deactivate\n");
+
+               rc = nci_request(ndev, nci_rf_deactivate_req, 0,
+                                msecs_to_jiffies(NCI_RF_DEACTIVATE_TIMEOUT));
+               if (rc)
+                       return -EBUSY;
+       }
+
+       rc = nci_request(ndev, nci_rf_discover_req, im_protocols,
+                        msecs_to_jiffies(NCI_RF_DISC_TIMEOUT));
+
+       if (!rc)
+               ndev->poll_prots = im_protocols;
+
+       return rc;
+}
+
+static void nci_stop_poll(struct nfc_dev *nfc_dev)
+{
+       struct nci_dev *ndev = nfc_get_drvdata(nfc_dev);
+
+       if ((atomic_read(&ndev->state) != NCI_DISCOVERY) &&
+           (atomic_read(&ndev->state) != NCI_W4_ALL_DISCOVERIES)) {
+               pr_err("unable to stop poll, since poll is not active\n");
+               return;
+       }
+
+       nci_request(ndev, nci_rf_deactivate_req, 0,
+                   msecs_to_jiffies(NCI_RF_DEACTIVATE_TIMEOUT));
+}
+
+static int nci_activate_target(struct nfc_dev *nfc_dev,
+                              struct nfc_target *target, __u32 protocol)
+{
+       struct nci_dev *ndev = nfc_get_drvdata(nfc_dev);
+       struct nci_rf_discover_select_param param;
+       struct nfc_target *nci_target = NULL;
+       int i;
+       int rc = 0;
+
+       pr_debug("target_idx %d, protocol 0x%x\n", target->idx, protocol);
+
+       if ((atomic_read(&ndev->state) != NCI_W4_HOST_SELECT) &&
+           (atomic_read(&ndev->state) != NCI_POLL_ACTIVE)) {
+               pr_err("there is no available target to activate\n");
+               return -EINVAL;
+       }
+
+       if (ndev->target_active_prot) {
+               pr_err("there is already an active target\n");
+               return -EBUSY;
+       }
+
+       for (i = 0; i < ndev->n_targets; i++) {
+               if (ndev->targets[i].idx == target->idx) {
+                       nci_target = &ndev->targets[i];
+                       break;
+               }
+       }
+
+       if (!nci_target) {
+               pr_err("unable to find the selected target\n");
+               return -EINVAL;
+       }
+
+       if (!(nci_target->supported_protocols & (1 << protocol))) {
+               pr_err("target does not support the requested protocol 0x%x\n",
+                      protocol);
+               return -EINVAL;
+       }
+
+       if (atomic_read(&ndev->state) == NCI_W4_HOST_SELECT) {
+               param.rf_discovery_id = nci_target->logical_idx;
+
+               if (protocol == NFC_PROTO_JEWEL)
+                       param.rf_protocol = NCI_RF_PROTOCOL_T1T;
+               else if (protocol == NFC_PROTO_MIFARE)
+                       param.rf_protocol = NCI_RF_PROTOCOL_T2T;
+               else if (protocol == NFC_PROTO_FELICA)
+                       param.rf_protocol = NCI_RF_PROTOCOL_T3T;
+               else if (protocol == NFC_PROTO_ISO14443 ||
+                        protocol == NFC_PROTO_ISO14443_B)
+                       param.rf_protocol = NCI_RF_PROTOCOL_ISO_DEP;
+               else
+                       param.rf_protocol = NCI_RF_PROTOCOL_NFC_DEP;
+
+               rc = nci_request(ndev, nci_rf_discover_select_req,
+                                (unsigned long)&param,
+                                msecs_to_jiffies(NCI_RF_DISC_SELECT_TIMEOUT));
+       }
+
+       if (!rc)
+               ndev->target_active_prot = protocol;
+
+       return rc;
+}
+
+static void nci_deactivate_target(struct nfc_dev *nfc_dev,
+                                 struct nfc_target *target)
+{
+       struct nci_dev *ndev = nfc_get_drvdata(nfc_dev);
+
+       pr_debug("target_idx %d\n", target->idx);
+
+       if (!ndev->target_active_prot) {
+               pr_err("unable to deactivate target, no active target\n");
+               return;
+       }
+
+       ndev->target_active_prot = 0;
+
+       if (atomic_read(&ndev->state) == NCI_POLL_ACTIVE) {
+               nci_request(ndev, nci_rf_deactivate_req, 0,
+                           msecs_to_jiffies(NCI_RF_DEACTIVATE_TIMEOUT));
+       }
+}
+
+static int nci_transceive(struct nfc_dev *nfc_dev, struct nfc_target *target,
+                         struct sk_buff *skb,
+                         data_exchange_cb_t cb, void *cb_context)
+{
+       struct nci_dev *ndev = nfc_get_drvdata(nfc_dev);
+       int rc;
+
+       pr_debug("target_idx %d, len %d\n", target->idx, skb->len);
+
+       if (!ndev->target_active_prot) {
+               pr_err("unable to exchange data, no active target\n");
+               return -EINVAL;
+       }
+
+       if (test_and_set_bit(NCI_DATA_EXCHANGE, &ndev->flags))
+               return -EBUSY;
+
+       /* store cb and context to be used on receiving data */
+       ndev->data_exchange_cb = cb;
+       ndev->data_exchange_cb_context = cb_context;
+
+       rc = nci_send_data(ndev, NCI_STATIC_RF_CONN_ID, skb);
+       if (rc)
+               clear_bit(NCI_DATA_EXCHANGE, &ndev->flags);
+
+       return rc;
+}
+
+static struct nfc_ops nci_nfc_ops = {
+       .dev_up = nci_dev_up,
+       .dev_down = nci_dev_down,
+       .start_poll = nci_start_poll,
+       .stop_poll = nci_stop_poll,
+       .activate_target = nci_activate_target,
+       .deactivate_target = nci_deactivate_target,
+       .im_transceive = nci_transceive,
+};
+
+/* ---- Interface to NCI drivers ---- */
+
+/**
+ * nci_allocate_device - allocate a new nci device
+ *
+ * @ops: device operations
+ * @supported_protocols: NFC protocols supported by the device
+ */
+struct nci_dev *nci_allocate_device(struct nci_ops *ops,
+                                   __u32 supported_protocols,
+                                   int tx_headroom, int tx_tailroom)
+{
+       struct nci_dev *ndev;
+
+       pr_debug("supported_protocols 0x%x\n", supported_protocols);
+
+       if (!ops->open || !ops->close || !ops->send)
+               return NULL;
+
+       if (!supported_protocols)
+               return NULL;
+
+       ndev = kzalloc(sizeof(struct nci_dev), GFP_KERNEL);
+       if (!ndev)
+               return NULL;
+
+       ndev->ops = ops;
+       ndev->tx_headroom = tx_headroom;
+       ndev->tx_tailroom = tx_tailroom;
+
+       ndev->nfc_dev = nfc_allocate_device(&nci_nfc_ops,
+                                           supported_protocols,
+                                           tx_headroom + NCI_DATA_HDR_SIZE,
+                                           tx_tailroom);
+       if (!ndev->nfc_dev)
+               goto free_exit;
+
+       nfc_set_drvdata(ndev->nfc_dev, ndev);
+
+       return ndev;
+
+free_exit:
+       kfree(ndev);
+       return NULL;
+}
+EXPORT_SYMBOL(nci_allocate_device);
+
+/**
+ * nci_free_device - deallocate nci device
+ *
+ * @ndev: The nci device to deallocate
+ */
+void nci_free_device(struct nci_dev *ndev)
+{
+       nfc_free_device(ndev->nfc_dev);
+       kfree(ndev);
+}
+EXPORT_SYMBOL(nci_free_device);
+
+/**
+ * nci_register_device - register a nci device in the nfc subsystem
+ *
+ * @dev: The nci device to register
+ */
+int nci_register_device(struct nci_dev *ndev)
+{
+       int rc;
+       struct device *dev = &ndev->nfc_dev->dev;
+       char name[32];
+
+       rc = nfc_register_device(ndev->nfc_dev);
+       if (rc)
+               goto exit;
+
+       ndev->flags = 0;
+
+       INIT_WORK(&ndev->cmd_work, nci_cmd_work);
+       snprintf(name, sizeof(name), "%s_nci_cmd_wq", dev_name(dev));
+       ndev->cmd_wq = create_singlethread_workqueue(name);
+       if (!ndev->cmd_wq) {
+               rc = -ENOMEM;
+               goto unreg_exit;
+       }
+
+       INIT_WORK(&ndev->rx_work, nci_rx_work);
+       snprintf(name, sizeof(name), "%s_nci_rx_wq", dev_name(dev));
+       ndev->rx_wq = create_singlethread_workqueue(name);
+       if (!ndev->rx_wq) {
+               rc = -ENOMEM;
+               goto destroy_cmd_wq_exit;
+       }
+
+       INIT_WORK(&ndev->tx_work, nci_tx_work);
+       snprintf(name, sizeof(name), "%s_nci_tx_wq", dev_name(dev));
+       ndev->tx_wq = create_singlethread_workqueue(name);
+       if (!ndev->tx_wq) {
+               rc = -ENOMEM;
+               goto destroy_rx_wq_exit;
+       }
+
+       skb_queue_head_init(&ndev->cmd_q);
+       skb_queue_head_init(&ndev->rx_q);
+       skb_queue_head_init(&ndev->tx_q);
+
+       setup_timer(&ndev->cmd_timer, nci_cmd_timer,
+                   (unsigned long) ndev);
+       setup_timer(&ndev->data_timer, nci_data_timer,
+                   (unsigned long) ndev);
+
+       mutex_init(&ndev->req_lock);
+
+       goto exit;
+
+destroy_rx_wq_exit:
+       destroy_workqueue(ndev->rx_wq);
+
+destroy_cmd_wq_exit:
+       destroy_workqueue(ndev->cmd_wq);
+
+unreg_exit:
+       nfc_unregister_device(ndev->nfc_dev);
+
+exit:
+       return rc;
+}
+EXPORT_SYMBOL(nci_register_device);
+
+/**
+ * nci_unregister_device - unregister a nci device in the nfc subsystem
+ *
+ * @dev: The nci device to unregister
+ */
+void nci_unregister_device(struct nci_dev *ndev)
+{
+       nci_close_device(ndev);
+
+       destroy_workqueue(ndev->cmd_wq);
+       destroy_workqueue(ndev->rx_wq);
+       destroy_workqueue(ndev->tx_wq);
+
+       nfc_unregister_device(ndev->nfc_dev);
+}
+EXPORT_SYMBOL(nci_unregister_device);
+
+/**
+ * nci_recv_frame - receive frame from NCI drivers
+ *
+ * @skb: The sk_buff to receive
+ */
+int nci_recv_frame(struct sk_buff *skb)
+{
+       struct nci_dev *ndev = (struct nci_dev *) skb->dev;
+
+       pr_debug("len %d\n", skb->len);
+
+       if (!ndev || (!test_bit(NCI_UP, &ndev->flags)
+                     && !test_bit(NCI_INIT, &ndev->flags))) {
+               kfree_skb(skb);
+               return -ENXIO;
+       }
+
+       /* Queue frame for rx worker thread */
+       skb_queue_tail(&ndev->rx_q, skb);
+       queue_work(ndev->rx_wq, &ndev->rx_work);
+
+       return 0;
+}
+EXPORT_SYMBOL(nci_recv_frame);
+
+static int nci_send_frame(struct sk_buff *skb)
+{
+       struct nci_dev *ndev = (struct nci_dev *) skb->dev;
+
+       pr_debug("len %d\n", skb->len);
+
+       if (!ndev) {
+               kfree_skb(skb);
+               return -ENODEV;
+       }
+
+       /* Get rid of skb owner, prior to sending to the driver. */
+       skb_orphan(skb);
+
+       return ndev->ops->send(skb);
+}
+
+/* Send NCI command */
+int nci_send_cmd(struct nci_dev *ndev, __u16 opcode, __u8 plen, void *payload)
+{
+       struct nci_ctrl_hdr *hdr;
+       struct sk_buff *skb;
+
+       pr_debug("opcode 0x%x, plen %d\n", opcode, plen);
+
+       skb = nci_skb_alloc(ndev, (NCI_CTRL_HDR_SIZE + plen), GFP_KERNEL);
+       if (!skb) {
+               pr_err("no memory for command\n");
+               return -ENOMEM;
+       }
+
+       hdr = (struct nci_ctrl_hdr *) skb_put(skb, NCI_CTRL_HDR_SIZE);
+       hdr->gid = nci_opcode_gid(opcode);
+       hdr->oid = nci_opcode_oid(opcode);
+       hdr->plen = plen;
+
+       nci_mt_set((__u8 *)hdr, NCI_MT_CMD_PKT);
+       nci_pbf_set((__u8 *)hdr, NCI_PBF_LAST);
+
+       if (plen)
+               memcpy(skb_put(skb, plen), payload, plen);
+
+       skb->dev = (void *) ndev;
+
+       skb_queue_tail(&ndev->cmd_q, skb);
+       queue_work(ndev->cmd_wq, &ndev->cmd_work);
+
+       return 0;
+}
+
+/* ---- NCI TX Data worker thread ---- */
+
+static void nci_tx_work(struct work_struct *work)
+{
+       struct nci_dev *ndev = container_of(work, struct nci_dev, tx_work);
+       struct sk_buff *skb;
+
+       pr_debug("credits_cnt %d\n", atomic_read(&ndev->credits_cnt));
+
+       /* Send queued tx data */
+       while (atomic_read(&ndev->credits_cnt)) {
+               skb = skb_dequeue(&ndev->tx_q);
+               if (!skb)
+                       return;
+
+               /* Check if data flow control is used */
+               if (atomic_read(&ndev->credits_cnt) !=
+                   NCI_DATA_FLOW_CONTROL_NOT_USED)
+                       atomic_dec(&ndev->credits_cnt);
+
+               pr_debug("NCI TX: MT=data, PBF=%d, conn_id=%d, plen=%d\n",
+                        nci_pbf(skb->data),
+                        nci_conn_id(skb->data),
+                        nci_plen(skb->data));
+
+               nci_send_frame(skb);
+
+               mod_timer(&ndev->data_timer,
+                         jiffies + msecs_to_jiffies(NCI_DATA_TIMEOUT));
+       }
+}
+
+/* ----- NCI RX worker thread (data & control) ----- */
+
+static void nci_rx_work(struct work_struct *work)
+{
+       struct nci_dev *ndev = container_of(work, struct nci_dev, rx_work);
+       struct sk_buff *skb;
+
+       while ((skb = skb_dequeue(&ndev->rx_q))) {
+               /* Process frame */
+               switch (nci_mt(skb->data)) {
+               case NCI_MT_RSP_PKT:
+                       nci_rsp_packet(ndev, skb);
+                       break;
+
+               case NCI_MT_NTF_PKT:
+                       nci_ntf_packet(ndev, skb);
+                       break;
+
+               case NCI_MT_DATA_PKT:
+                       nci_rx_data_packet(ndev, skb);
+                       break;
+
+               default:
+                       pr_err("unknown MT 0x%x\n", nci_mt(skb->data));
+                       kfree_skb(skb);
+                       break;
+               }
+       }
+
+       /* check if a data exchange timout has occurred */
+       if (test_bit(NCI_DATA_EXCHANGE_TO, &ndev->flags)) {
+               /* complete the data exchange transaction, if exists */
+               if (test_bit(NCI_DATA_EXCHANGE, &ndev->flags))
+                       nci_data_exchange_complete(ndev, NULL, -ETIMEDOUT);
+
+               clear_bit(NCI_DATA_EXCHANGE_TO, &ndev->flags);
+       }
+}
+
+/* ----- NCI TX CMD worker thread ----- */
+
+static void nci_cmd_work(struct work_struct *work)
+{
+       struct nci_dev *ndev = container_of(work, struct nci_dev, cmd_work);
+       struct sk_buff *skb;
+
+       pr_debug("cmd_cnt %d\n", atomic_read(&ndev->cmd_cnt));
+
+       /* Send queued command */
+       if (atomic_read(&ndev->cmd_cnt)) {
+               skb = skb_dequeue(&ndev->cmd_q);
+               if (!skb)
+                       return;
+
+               atomic_dec(&ndev->cmd_cnt);
+
+               pr_debug("NCI TX: MT=cmd, PBF=%d, GID=0x%x, OID=0x%x, plen=%d\n",
+                        nci_pbf(skb->data),
+                        nci_opcode_gid(nci_opcode(skb->data)),
+                        nci_opcode_oid(nci_opcode(skb->data)),
+                        nci_plen(skb->data));
+
+               nci_send_frame(skb);
+
+               mod_timer(&ndev->cmd_timer,
+                         jiffies + msecs_to_jiffies(NCI_CMD_TIMEOUT));
+       }
+}
+
+MODULE_LICENSE("GPL");
diff --git a/net/nfc/nci/data.c b/net/nfc/nci/data.c
new file mode 100644 (file)
index 0000000..76c48c5
--- /dev/null
@@ -0,0 +1,254 @@
+/*
+ *  The NFC Controller Interface is the communication protocol between an
+ *  NFC Controller (NFCC) and a Device Host (DH).
+ *
+ *  Copyright (C) 2011 Texas Instruments, Inc.
+ *
+ *  Written by Ilan Elias <ilane@ti.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2
+ *  as published by the Free Software Foundation
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": %s: " fmt, __func__
+
+#include <linux/types.h>
+#include <linux/interrupt.h>
+#include <linux/wait.h>
+#include <linux/bitops.h>
+#include <linux/skbuff.h>
+
+#include "../nfc.h"
+#include <net/nfc/nci.h>
+#include <net/nfc/nci_core.h>
+#include <linux/nfc.h>
+
+/* Complete data exchange transaction and forward skb to nfc core */
+void nci_data_exchange_complete(struct nci_dev *ndev, struct sk_buff *skb,
+                               int err)
+{
+       data_exchange_cb_t cb = ndev->data_exchange_cb;
+       void *cb_context = ndev->data_exchange_cb_context;
+
+       pr_debug("len %d, err %d\n", skb ? skb->len : 0, err);
+
+       /* data exchange is complete, stop the data timer */
+       del_timer_sync(&ndev->data_timer);
+       clear_bit(NCI_DATA_EXCHANGE_TO, &ndev->flags);
+
+       if (cb) {
+               ndev->data_exchange_cb = NULL;
+               ndev->data_exchange_cb_context = NULL;
+
+               /* forward skb to nfc core */
+               cb(cb_context, skb, err);
+       } else if (skb) {
+               pr_err("no rx callback, dropping rx data...\n");
+
+               /* no waiting callback, free skb */
+               kfree_skb(skb);
+       }
+
+       clear_bit(NCI_DATA_EXCHANGE, &ndev->flags);
+}
+
+/* ----------------- NCI TX Data ----------------- */
+
+static inline void nci_push_data_hdr(struct nci_dev *ndev,
+                                    __u8 conn_id,
+                                    struct sk_buff *skb,
+                                    __u8 pbf)
+{
+       struct nci_data_hdr *hdr;
+       int plen = skb->len;
+
+       hdr = (struct nci_data_hdr *) skb_push(skb, NCI_DATA_HDR_SIZE);
+       hdr->conn_id = conn_id;
+       hdr->rfu = 0;
+       hdr->plen = plen;
+
+       nci_mt_set((__u8 *)hdr, NCI_MT_DATA_PKT);
+       nci_pbf_set((__u8 *)hdr, pbf);
+
+       skb->dev = (void *) ndev;
+}
+
+static int nci_queue_tx_data_frags(struct nci_dev *ndev,
+                                  __u8 conn_id,
+                                  struct sk_buff *skb) {
+       int total_len = skb->len;
+       unsigned char *data = skb->data;
+       unsigned long flags;
+       struct sk_buff_head frags_q;
+       struct sk_buff *skb_frag;
+       int frag_len;
+       int rc = 0;
+
+       pr_debug("conn_id 0x%x, total_len %d\n", conn_id, total_len);
+
+       __skb_queue_head_init(&frags_q);
+
+       while (total_len) {
+               frag_len =
+                       min_t(int, total_len, ndev->max_data_pkt_payload_size);
+
+               skb_frag = nci_skb_alloc(ndev,
+                                        (NCI_DATA_HDR_SIZE + frag_len),
+                                        GFP_KERNEL);
+               if (skb_frag == NULL) {
+                       rc = -ENOMEM;
+                       goto free_exit;
+               }
+               skb_reserve(skb_frag, NCI_DATA_HDR_SIZE);
+
+               /* first, copy the data */
+               memcpy(skb_put(skb_frag, frag_len), data, frag_len);
+
+               /* second, set the header */
+               nci_push_data_hdr(ndev, conn_id, skb_frag,
+                                 ((total_len == frag_len) ?
+                                  (NCI_PBF_LAST) : (NCI_PBF_CONT)));
+
+               __skb_queue_tail(&frags_q, skb_frag);
+
+               data += frag_len;
+               total_len -= frag_len;
+
+               pr_debug("frag_len %d, remaining total_len %d\n",
+                        frag_len, total_len);
+       }
+
+       /* queue all fragments atomically */
+       spin_lock_irqsave(&ndev->tx_q.lock, flags);
+
+       while ((skb_frag = __skb_dequeue(&frags_q)) != NULL)
+               __skb_queue_tail(&ndev->tx_q, skb_frag);
+
+       spin_unlock_irqrestore(&ndev->tx_q.lock, flags);
+
+       /* free the original skb */
+       kfree_skb(skb);
+
+       goto exit;
+
+free_exit:
+       while ((skb_frag = __skb_dequeue(&frags_q)) != NULL)
+               kfree_skb(skb_frag);
+
+exit:
+       return rc;
+}
+
+/* Send NCI data */
+int nci_send_data(struct nci_dev *ndev, __u8 conn_id, struct sk_buff *skb)
+{
+       int rc = 0;
+
+       pr_debug("conn_id 0x%x, plen %d\n", conn_id, skb->len);
+
+       /* check if the packet need to be fragmented */
+       if (skb->len <= ndev->max_data_pkt_payload_size) {
+               /* no need to fragment packet */
+               nci_push_data_hdr(ndev, conn_id, skb, NCI_PBF_LAST);
+
+               skb_queue_tail(&ndev->tx_q, skb);
+       } else {
+               /* fragment packet and queue the fragments */
+               rc = nci_queue_tx_data_frags(ndev, conn_id, skb);
+               if (rc) {
+                       pr_err("failed to fragment tx data packet\n");
+                       goto free_exit;
+               }
+       }
+
+       queue_work(ndev->tx_wq, &ndev->tx_work);
+
+       goto exit;
+
+free_exit:
+       kfree_skb(skb);
+
+exit:
+       return rc;
+}
+
+/* ----------------- NCI RX Data ----------------- */
+
+static void nci_add_rx_data_frag(struct nci_dev *ndev,
+                                struct sk_buff *skb,
+                                __u8 pbf)
+{
+       int reassembly_len;
+       int err = 0;
+
+       if (ndev->rx_data_reassembly) {
+               reassembly_len = ndev->rx_data_reassembly->len;
+
+               /* first, make enough room for the already accumulated data */
+               if (skb_cow_head(skb, reassembly_len)) {
+                       pr_err("error adding room for accumulated rx data\n");
+
+                       kfree_skb(skb);
+                       skb = NULL;
+
+                       kfree_skb(ndev->rx_data_reassembly);
+                       ndev->rx_data_reassembly = NULL;
+
+                       err = -ENOMEM;
+                       goto exit;
+               }
+
+               /* second, combine the two fragments */
+               memcpy(skb_push(skb, reassembly_len),
+                      ndev->rx_data_reassembly->data,
+                      reassembly_len);
+
+               /* third, free old reassembly */
+               kfree_skb(ndev->rx_data_reassembly);
+               ndev->rx_data_reassembly = NULL;
+       }
+
+       if (pbf == NCI_PBF_CONT) {
+               /* need to wait for next fragment, store skb and exit */
+               ndev->rx_data_reassembly = skb;
+               return;
+       }
+
+exit:
+       nci_data_exchange_complete(ndev, skb, err);
+}
+
+/* Rx Data packet */
+void nci_rx_data_packet(struct nci_dev *ndev, struct sk_buff *skb)
+{
+       __u8 pbf = nci_pbf(skb->data);
+
+       pr_debug("len %d\n", skb->len);
+
+       pr_debug("NCI RX: MT=data, PBF=%d, conn_id=%d, plen=%d\n",
+                nci_pbf(skb->data),
+                nci_conn_id(skb->data),
+                nci_plen(skb->data));
+
+       /* strip the nci data header */
+       skb_pull(skb, NCI_DATA_HDR_SIZE);
+
+       if (ndev->target_active_prot == NFC_PROTO_MIFARE) {
+               /* frame I/F => remove the status byte */
+               pr_debug("NFC_PROTO_MIFARE => remove the status byte\n");
+               skb_trim(skb, (skb->len - 1));
+       }
+
+       nci_add_rx_data_frag(ndev, skb, pbf);
+}
diff --git a/net/nfc/nci/lib.c b/net/nfc/nci/lib.c
new file mode 100644 (file)
index 0000000..6b7fd26
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ *  The NFC Controller Interface is the communication protocol between an
+ *  NFC Controller (NFCC) and a Device Host (DH).
+ *
+ *  Copyright (C) 2011 Texas Instruments, Inc.
+ *
+ *  Written by Ilan Elias <ilane@ti.com>
+ *
+ *  Acknowledgements:
+ *  This file is based on lib.c, which was written
+ *  by Maxim Krasnyansky.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2
+ *  as published by the Free Software Foundation
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+
+#include <net/nfc/nci.h>
+#include <net/nfc/nci_core.h>
+
+/* NCI status codes to Unix errno mapping */
+int nci_to_errno(__u8 code)
+{
+       switch (code) {
+       case NCI_STATUS_OK:
+               return 0;
+
+       case NCI_STATUS_REJECTED:
+               return -EBUSY;
+
+       case NCI_STATUS_RF_FRAME_CORRUPTED:
+               return -EBADMSG;
+
+       case NCI_STATUS_NOT_INITIALIZED:
+               return -EHOSTDOWN;
+
+       case NCI_STATUS_SYNTAX_ERROR:
+       case NCI_STATUS_SEMANTIC_ERROR:
+       case NCI_STATUS_INVALID_PARAM:
+       case NCI_STATUS_RF_PROTOCOL_ERROR:
+       case NCI_STATUS_NFCEE_PROTOCOL_ERROR:
+               return -EPROTO;
+
+       case NCI_STATUS_UNKNOWN_GID:
+       case NCI_STATUS_UNKNOWN_OID:
+               return -EBADRQC;
+
+       case NCI_STATUS_MESSAGE_SIZE_EXCEEDED:
+               return -EMSGSIZE;
+
+       case NCI_STATUS_DISCOVERY_ALREADY_STARTED:
+               return -EALREADY;
+
+       case NCI_STATUS_DISCOVERY_TARGET_ACTIVATION_FAILED:
+       case NCI_STATUS_NFCEE_INTERFACE_ACTIVATION_FAILED:
+               return -ECONNREFUSED;
+
+       case NCI_STATUS_RF_TRANSMISSION_ERROR:
+       case NCI_STATUS_NFCEE_TRANSMISSION_ERROR:
+               return -ECOMM;
+
+       case NCI_STATUS_RF_TIMEOUT_ERROR:
+       case NCI_STATUS_NFCEE_TIMEOUT_ERROR:
+               return -ETIMEDOUT;
+
+       case NCI_STATUS_FAILED:
+       default:
+               return -ENOSYS;
+       }
+}
+EXPORT_SYMBOL(nci_to_errno);
diff --git a/net/nfc/nci/ntf.c b/net/nfc/nci/ntf.c
new file mode 100644 (file)
index 0000000..af7a93b
--- /dev/null
@@ -0,0 +1,559 @@
+/*
+ *  The NFC Controller Interface is the communication protocol between an
+ *  NFC Controller (NFCC) and a Device Host (DH).
+ *
+ *  Copyright (C) 2011 Texas Instruments, Inc.
+ *
+ *  Written by Ilan Elias <ilane@ti.com>
+ *
+ *  Acknowledgements:
+ *  This file is based on hci_event.c, which was written
+ *  by Maxim Krasnyansky.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2
+ *  as published by the Free Software Foundation
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": %s: " fmt, __func__
+
+#include <linux/types.h>
+#include <linux/interrupt.h>
+#include <linux/bitops.h>
+#include <linux/skbuff.h>
+
+#include "../nfc.h"
+#include <net/nfc/nci.h>
+#include <net/nfc/nci_core.h>
+#include <linux/nfc.h>
+
+/* Handle NCI Notification packets */
+
+static void nci_core_conn_credits_ntf_packet(struct nci_dev *ndev,
+                                            struct sk_buff *skb)
+{
+       struct nci_core_conn_credit_ntf *ntf = (void *) skb->data;
+       int i;
+
+       pr_debug("num_entries %d\n", ntf->num_entries);
+
+       if (ntf->num_entries > NCI_MAX_NUM_CONN)
+               ntf->num_entries = NCI_MAX_NUM_CONN;
+
+       /* update the credits */
+       for (i = 0; i < ntf->num_entries; i++) {
+               ntf->conn_entries[i].conn_id =
+                       nci_conn_id(&ntf->conn_entries[i].conn_id);
+
+               pr_debug("entry[%d]: conn_id %d, credits %d\n",
+                        i, ntf->conn_entries[i].conn_id,
+                        ntf->conn_entries[i].credits);
+
+               if (ntf->conn_entries[i].conn_id == NCI_STATIC_RF_CONN_ID) {
+                       /* found static rf connection */
+                       atomic_add(ntf->conn_entries[i].credits,
+                                  &ndev->credits_cnt);
+               }
+       }
+
+       /* trigger the next tx */
+       if (!skb_queue_empty(&ndev->tx_q))
+               queue_work(ndev->tx_wq, &ndev->tx_work);
+}
+
+static void nci_core_generic_error_ntf_packet(struct nci_dev *ndev,
+                                             struct sk_buff *skb)
+{
+       __u8 status = skb->data[0];
+
+       pr_debug("status 0x%x\n", status);
+
+       if (atomic_read(&ndev->state) == NCI_W4_HOST_SELECT) {
+               /* Activation failed, so complete the request
+                  (the state remains the same) */
+               nci_req_complete(ndev, status);
+       }
+}
+
+static void nci_core_conn_intf_error_ntf_packet(struct nci_dev *ndev,
+                                               struct sk_buff *skb)
+{
+       struct nci_core_intf_error_ntf *ntf = (void *) skb->data;
+
+       ntf->conn_id = nci_conn_id(&ntf->conn_id);
+
+       pr_debug("status 0x%x, conn_id %d\n", ntf->status, ntf->conn_id);
+
+       /* complete the data exchange transaction, if exists */
+       if (test_bit(NCI_DATA_EXCHANGE, &ndev->flags))
+               nci_data_exchange_complete(ndev, NULL, -EIO);
+}
+
+static __u8 *nci_extract_rf_params_nfca_passive_poll(struct nci_dev *ndev,
+                       struct rf_tech_specific_params_nfca_poll *nfca_poll,
+                                                    __u8 *data)
+{
+       nfca_poll->sens_res = __le16_to_cpu(*((__u16 *)data));
+       data += 2;
+
+       nfca_poll->nfcid1_len = min_t(__u8, *data++, NFC_NFCID1_MAXSIZE);
+
+       pr_debug("sens_res 0x%x, nfcid1_len %d\n",
+                nfca_poll->sens_res, nfca_poll->nfcid1_len);
+
+       memcpy(nfca_poll->nfcid1, data, nfca_poll->nfcid1_len);
+       data += nfca_poll->nfcid1_len;
+
+       nfca_poll->sel_res_len = *data++;
+
+       if (nfca_poll->sel_res_len != 0)
+               nfca_poll->sel_res = *data++;
+
+       pr_debug("sel_res_len %d, sel_res 0x%x\n",
+                nfca_poll->sel_res_len,
+                nfca_poll->sel_res);
+
+       return data;
+}
+
+static __u8 *nci_extract_rf_params_nfcb_passive_poll(struct nci_dev *ndev,
+                       struct rf_tech_specific_params_nfcb_poll *nfcb_poll,
+                                                    __u8 *data)
+{
+       nfcb_poll->sensb_res_len = min_t(__u8, *data++, NFC_SENSB_RES_MAXSIZE);
+
+       pr_debug("sensb_res_len %d\n", nfcb_poll->sensb_res_len);
+
+       memcpy(nfcb_poll->sensb_res, data, nfcb_poll->sensb_res_len);
+       data += nfcb_poll->sensb_res_len;
+
+       return data;
+}
+
+static __u8 *nci_extract_rf_params_nfcf_passive_poll(struct nci_dev *ndev,
+                       struct rf_tech_specific_params_nfcf_poll *nfcf_poll,
+                                                    __u8 *data)
+{
+       nfcf_poll->bit_rate = *data++;
+       nfcf_poll->sensf_res_len = min_t(__u8, *data++, NFC_SENSF_RES_MAXSIZE);
+
+       pr_debug("bit_rate %d, sensf_res_len %d\n",
+                nfcf_poll->bit_rate, nfcf_poll->sensf_res_len);
+
+       memcpy(nfcf_poll->sensf_res, data, nfcf_poll->sensf_res_len);
+       data += nfcf_poll->sensf_res_len;
+
+       return data;
+}
+
+static int nci_add_new_protocol(struct nci_dev *ndev,
+                               struct nfc_target *target,
+                               __u8 rf_protocol,
+                               __u8 rf_tech_and_mode,
+                               void *params)
+{
+       struct rf_tech_specific_params_nfca_poll *nfca_poll;
+       struct rf_tech_specific_params_nfcb_poll *nfcb_poll;
+       struct rf_tech_specific_params_nfcf_poll *nfcf_poll;
+       __u32 protocol;
+
+       if (rf_protocol == NCI_RF_PROTOCOL_T2T)
+               protocol = NFC_PROTO_MIFARE_MASK;
+       else if (rf_protocol == NCI_RF_PROTOCOL_ISO_DEP)
+               if (rf_tech_and_mode == NCI_NFC_A_PASSIVE_POLL_MODE)
+                       protocol = NFC_PROTO_ISO14443_MASK;
+               else
+                       protocol = NFC_PROTO_ISO14443_B_MASK;
+       else if (rf_protocol == NCI_RF_PROTOCOL_T3T)
+               protocol = NFC_PROTO_FELICA_MASK;
+       else
+               protocol = 0;
+
+       if (!(protocol & ndev->poll_prots)) {
+               pr_err("the target found does not have the desired protocol\n");
+               return -EPROTO;
+       }
+
+       if (rf_tech_and_mode == NCI_NFC_A_PASSIVE_POLL_MODE) {
+               nfca_poll = (struct rf_tech_specific_params_nfca_poll *)params;
+
+               target->sens_res = nfca_poll->sens_res;
+               target->sel_res = nfca_poll->sel_res;
+               target->nfcid1_len = nfca_poll->nfcid1_len;
+               if (target->nfcid1_len > 0) {
+                       memcpy(target->nfcid1, nfca_poll->nfcid1,
+                              target->nfcid1_len);
+               }
+       } else if (rf_tech_and_mode == NCI_NFC_B_PASSIVE_POLL_MODE) {
+               nfcb_poll = (struct rf_tech_specific_params_nfcb_poll *)params;
+
+               target->sensb_res_len = nfcb_poll->sensb_res_len;
+               if (target->sensb_res_len > 0) {
+                       memcpy(target->sensb_res, nfcb_poll->sensb_res,
+                              target->sensb_res_len);
+               }
+       } else if (rf_tech_and_mode == NCI_NFC_F_PASSIVE_POLL_MODE) {
+               nfcf_poll = (struct rf_tech_specific_params_nfcf_poll *)params;
+
+               target->sensf_res_len = nfcf_poll->sensf_res_len;
+               if (target->sensf_res_len > 0) {
+                       memcpy(target->sensf_res, nfcf_poll->sensf_res,
+                              target->sensf_res_len);
+               }
+       } else {
+               pr_err("unsupported rf_tech_and_mode 0x%x\n", rf_tech_and_mode);
+               return -EPROTO;
+       }
+
+       target->supported_protocols |= protocol;
+
+       pr_debug("protocol 0x%x\n", protocol);
+
+       return 0;
+}
+
+static void nci_add_new_target(struct nci_dev *ndev,
+                              struct nci_rf_discover_ntf *ntf)
+{
+       struct nfc_target *target;
+       int i, rc;
+
+       for (i = 0; i < ndev->n_targets; i++) {
+               target = &ndev->targets[i];
+               if (target->logical_idx == ntf->rf_discovery_id) {
+                       /* This target already exists, add the new protocol */
+                       nci_add_new_protocol(ndev, target, ntf->rf_protocol,
+                                            ntf->rf_tech_and_mode,
+                                            &ntf->rf_tech_specific_params);
+                       return;
+               }
+       }
+
+       /* This is a new target, check if we've enough room */
+       if (ndev->n_targets == NCI_MAX_DISCOVERED_TARGETS) {
+               pr_debug("not enough room, ignoring new target...\n");
+               return;
+       }
+
+       target = &ndev->targets[ndev->n_targets];
+
+       rc = nci_add_new_protocol(ndev, target, ntf->rf_protocol,
+                                 ntf->rf_tech_and_mode,
+                                 &ntf->rf_tech_specific_params);
+       if (!rc) {
+               target->logical_idx = ntf->rf_discovery_id;
+               ndev->n_targets++;
+
+               pr_debug("logical idx %d, n_targets %d\n", target->logical_idx,
+                        ndev->n_targets);
+       }
+}
+
+void nci_clear_target_list(struct nci_dev *ndev)
+{
+       memset(ndev->targets, 0,
+              (sizeof(struct nfc_target)*NCI_MAX_DISCOVERED_TARGETS));
+
+       ndev->n_targets = 0;
+}
+
+static void nci_rf_discover_ntf_packet(struct nci_dev *ndev,
+                                      struct sk_buff *skb)
+{
+       struct nci_rf_discover_ntf ntf;
+       __u8 *data = skb->data;
+       bool add_target = true;
+
+       ntf.rf_discovery_id = *data++;
+       ntf.rf_protocol = *data++;
+       ntf.rf_tech_and_mode = *data++;
+       ntf.rf_tech_specific_params_len = *data++;
+
+       pr_debug("rf_discovery_id %d\n", ntf.rf_discovery_id);
+       pr_debug("rf_protocol 0x%x\n", ntf.rf_protocol);
+       pr_debug("rf_tech_and_mode 0x%x\n", ntf.rf_tech_and_mode);
+       pr_debug("rf_tech_specific_params_len %d\n",
+                ntf.rf_tech_specific_params_len);
+
+       if (ntf.rf_tech_specific_params_len > 0) {
+               switch (ntf.rf_tech_and_mode) {
+               case NCI_NFC_A_PASSIVE_POLL_MODE:
+                       data = nci_extract_rf_params_nfca_passive_poll(ndev,
+                               &(ntf.rf_tech_specific_params.nfca_poll), data);
+                       break;
+
+               case NCI_NFC_B_PASSIVE_POLL_MODE:
+                       data = nci_extract_rf_params_nfcb_passive_poll(ndev,
+                               &(ntf.rf_tech_specific_params.nfcb_poll), data);
+                       break;
+
+               case NCI_NFC_F_PASSIVE_POLL_MODE:
+                       data = nci_extract_rf_params_nfcf_passive_poll(ndev,
+                               &(ntf.rf_tech_specific_params.nfcf_poll), data);
+                       break;
+
+               default:
+                       pr_err("unsupported rf_tech_and_mode 0x%x\n",
+                              ntf.rf_tech_and_mode);
+                       data += ntf.rf_tech_specific_params_len;
+                       add_target = false;
+               }
+       }
+
+       ntf.ntf_type = *data++;
+       pr_debug("ntf_type %d\n", ntf.ntf_type);
+
+       if (add_target == true)
+               nci_add_new_target(ndev, &ntf);
+
+       if (ntf.ntf_type == NCI_DISCOVER_NTF_TYPE_MORE) {
+               atomic_set(&ndev->state, NCI_W4_ALL_DISCOVERIES);
+       } else {
+               atomic_set(&ndev->state, NCI_W4_HOST_SELECT);
+               nfc_targets_found(ndev->nfc_dev, ndev->targets,
+                                 ndev->n_targets);
+       }
+}
+
+static int nci_extract_activation_params_iso_dep(struct nci_dev *ndev,
+                       struct nci_rf_intf_activated_ntf *ntf, __u8 *data)
+{
+       struct activation_params_nfca_poll_iso_dep *nfca_poll;
+       struct activation_params_nfcb_poll_iso_dep *nfcb_poll;
+
+       switch (ntf->activation_rf_tech_and_mode) {
+       case NCI_NFC_A_PASSIVE_POLL_MODE:
+               nfca_poll = &ntf->activation_params.nfca_poll_iso_dep;
+               nfca_poll->rats_res_len = min_t(__u8, *data++, 20);
+               pr_debug("rats_res_len %d\n", nfca_poll->rats_res_len);
+               if (nfca_poll->rats_res_len > 0) {
+                       memcpy(nfca_poll->rats_res,
+                              data, nfca_poll->rats_res_len);
+               }
+               break;
+
+       case NCI_NFC_B_PASSIVE_POLL_MODE:
+               nfcb_poll = &ntf->activation_params.nfcb_poll_iso_dep;
+               nfcb_poll->attrib_res_len = min_t(__u8, *data++, 50);
+               pr_debug("attrib_res_len %d\n", nfcb_poll->attrib_res_len);
+               if (nfcb_poll->attrib_res_len > 0) {
+                       memcpy(nfcb_poll->attrib_res,
+                              data, nfcb_poll->attrib_res_len);
+               }
+               break;
+
+       default:
+               pr_err("unsupported activation_rf_tech_and_mode 0x%x\n",
+                      ntf->activation_rf_tech_and_mode);
+               return NCI_STATUS_RF_PROTOCOL_ERROR;
+       }
+
+       return NCI_STATUS_OK;
+}
+
+static void nci_target_auto_activated(struct nci_dev *ndev,
+                                     struct nci_rf_intf_activated_ntf *ntf)
+{
+       struct nfc_target *target;
+       int rc;
+
+       target = &ndev->targets[ndev->n_targets];
+
+       rc = nci_add_new_protocol(ndev, target, ntf->rf_protocol,
+                                 ntf->activation_rf_tech_and_mode,
+                                 &ntf->rf_tech_specific_params);
+       if (rc)
+               return;
+
+       target->logical_idx = ntf->rf_discovery_id;
+       ndev->n_targets++;
+
+       pr_debug("logical idx %d, n_targets %d\n",
+                target->logical_idx, ndev->n_targets);
+
+       nfc_targets_found(ndev->nfc_dev, ndev->targets, ndev->n_targets);
+}
+
+static void nci_rf_intf_activated_ntf_packet(struct nci_dev *ndev,
+                                            struct sk_buff *skb)
+{
+       struct nci_rf_intf_activated_ntf ntf;
+       __u8 *data = skb->data;
+       int err = NCI_STATUS_OK;
+
+       ntf.rf_discovery_id = *data++;
+       ntf.rf_interface = *data++;
+       ntf.rf_protocol = *data++;
+       ntf.activation_rf_tech_and_mode = *data++;
+       ntf.max_data_pkt_payload_size = *data++;
+       ntf.initial_num_credits = *data++;
+       ntf.rf_tech_specific_params_len = *data++;
+
+       pr_debug("rf_discovery_id %d\n", ntf.rf_discovery_id);
+       pr_debug("rf_interface 0x%x\n", ntf.rf_interface);
+       pr_debug("rf_protocol 0x%x\n", ntf.rf_protocol);
+       pr_debug("activation_rf_tech_and_mode 0x%x\n",
+                ntf.activation_rf_tech_and_mode);
+       pr_debug("max_data_pkt_payload_size 0x%x\n",
+                ntf.max_data_pkt_payload_size);
+       pr_debug("initial_num_credits 0x%x\n",
+                ntf.initial_num_credits);
+       pr_debug("rf_tech_specific_params_len %d\n",
+                ntf.rf_tech_specific_params_len);
+
+       if (ntf.rf_tech_specific_params_len > 0) {
+               switch (ntf.activation_rf_tech_and_mode) {
+               case NCI_NFC_A_PASSIVE_POLL_MODE:
+                       data = nci_extract_rf_params_nfca_passive_poll(ndev,
+                               &(ntf.rf_tech_specific_params.nfca_poll), data);
+                       break;
+
+               case NCI_NFC_B_PASSIVE_POLL_MODE:
+                       data = nci_extract_rf_params_nfcb_passive_poll(ndev,
+                               &(ntf.rf_tech_specific_params.nfcb_poll), data);
+                       break;
+
+               case NCI_NFC_F_PASSIVE_POLL_MODE:
+                       data = nci_extract_rf_params_nfcf_passive_poll(ndev,
+                               &(ntf.rf_tech_specific_params.nfcf_poll), data);
+                       break;
+
+               default:
+                       pr_err("unsupported activation_rf_tech_and_mode 0x%x\n",
+                              ntf.activation_rf_tech_and_mode);
+                       err = NCI_STATUS_RF_PROTOCOL_ERROR;
+                       goto exit;
+               }
+       }
+
+       ntf.data_exch_rf_tech_and_mode = *data++;
+       ntf.data_exch_tx_bit_rate = *data++;
+       ntf.data_exch_rx_bit_rate = *data++;
+       ntf.activation_params_len = *data++;
+
+       pr_debug("data_exch_rf_tech_and_mode 0x%x\n",
+                ntf.data_exch_rf_tech_and_mode);
+       pr_debug("data_exch_tx_bit_rate 0x%x\n", ntf.data_exch_tx_bit_rate);
+       pr_debug("data_exch_rx_bit_rate 0x%x\n", ntf.data_exch_rx_bit_rate);
+       pr_debug("activation_params_len %d\n", ntf.activation_params_len);
+
+       if (ntf.activation_params_len > 0) {
+               switch (ntf.rf_interface) {
+               case NCI_RF_INTERFACE_ISO_DEP:
+                       err = nci_extract_activation_params_iso_dep(ndev,
+                                                                   &ntf, data);
+                       break;
+
+               case NCI_RF_INTERFACE_FRAME:
+                       /* no activation params */
+                       break;
+
+               default:
+                       pr_err("unsupported rf_interface 0x%x\n",
+                              ntf.rf_interface);
+                       err = NCI_STATUS_RF_PROTOCOL_ERROR;
+                       break;
+               }
+       }
+
+exit:
+       if (err == NCI_STATUS_OK) {
+               ndev->max_data_pkt_payload_size = ntf.max_data_pkt_payload_size;
+               ndev->initial_num_credits = ntf.initial_num_credits;
+
+               /* set the available credits to initial value */
+               atomic_set(&ndev->credits_cnt, ndev->initial_num_credits);
+       }
+
+       if (atomic_read(&ndev->state) == NCI_DISCOVERY) {
+               /* A single target was found and activated automatically */
+               atomic_set(&ndev->state, NCI_POLL_ACTIVE);
+               if (err == NCI_STATUS_OK)
+                       nci_target_auto_activated(ndev, &ntf);
+       } else {        /* ndev->state == NCI_W4_HOST_SELECT */
+               /* A selected target was activated, so complete the request */
+               atomic_set(&ndev->state, NCI_POLL_ACTIVE);
+               nci_req_complete(ndev, err);
+       }
+}
+
+static void nci_rf_deactivate_ntf_packet(struct nci_dev *ndev,
+                                        struct sk_buff *skb)
+{
+       struct nci_rf_deactivate_ntf *ntf = (void *) skb->data;
+
+       pr_debug("entry, type 0x%x, reason 0x%x\n", ntf->type, ntf->reason);
+
+       /* drop tx data queue */
+       skb_queue_purge(&ndev->tx_q);
+
+       /* drop partial rx data packet */
+       if (ndev->rx_data_reassembly) {
+               kfree_skb(ndev->rx_data_reassembly);
+               ndev->rx_data_reassembly = NULL;
+       }
+
+       /* complete the data exchange transaction, if exists */
+       if (test_bit(NCI_DATA_EXCHANGE, &ndev->flags))
+               nci_data_exchange_complete(ndev, NULL, -EIO);
+
+       nci_clear_target_list(ndev);
+       atomic_set(&ndev->state, NCI_IDLE);
+       nci_req_complete(ndev, NCI_STATUS_OK);
+}
+
+void nci_ntf_packet(struct nci_dev *ndev, struct sk_buff *skb)
+{
+       __u16 ntf_opcode = nci_opcode(skb->data);
+
+       pr_debug("NCI RX: MT=ntf, PBF=%d, GID=0x%x, OID=0x%x, plen=%d\n",
+                nci_pbf(skb->data),
+                nci_opcode_gid(ntf_opcode),
+                nci_opcode_oid(ntf_opcode),
+                nci_plen(skb->data));
+
+       /* strip the nci control header */
+       skb_pull(skb, NCI_CTRL_HDR_SIZE);
+
+       switch (ntf_opcode) {
+       case NCI_OP_CORE_CONN_CREDITS_NTF:
+               nci_core_conn_credits_ntf_packet(ndev, skb);
+               break;
+
+       case NCI_OP_CORE_GENERIC_ERROR_NTF:
+               nci_core_generic_error_ntf_packet(ndev, skb);
+               break;
+
+       case NCI_OP_CORE_INTF_ERROR_NTF:
+               nci_core_conn_intf_error_ntf_packet(ndev, skb);
+               break;
+
+       case NCI_OP_RF_DISCOVER_NTF:
+               nci_rf_discover_ntf_packet(ndev, skb);
+               break;
+
+       case NCI_OP_RF_INTF_ACTIVATED_NTF:
+               nci_rf_intf_activated_ntf_packet(ndev, skb);
+               break;
+
+       case NCI_OP_RF_DEACTIVATE_NTF:
+               nci_rf_deactivate_ntf_packet(ndev, skb);
+               break;
+
+       default:
+               pr_err("unknown ntf opcode 0x%x\n", ntf_opcode);
+               break;
+       }
+
+       kfree_skb(skb);
+}
diff --git a/net/nfc/nci/rsp.c b/net/nfc/nci/rsp.c
new file mode 100644 (file)
index 0000000..3003c33
--- /dev/null
@@ -0,0 +1,224 @@
+/*
+ *  The NFC Controller Interface is the communication protocol between an
+ *  NFC Controller (NFCC) and a Device Host (DH).
+ *
+ *  Copyright (C) 2011 Texas Instruments, Inc.
+ *
+ *  Written by Ilan Elias <ilane@ti.com>
+ *
+ *  Acknowledgements:
+ *  This file is based on hci_event.c, which was written
+ *  by Maxim Krasnyansky.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2
+ *  as published by the Free Software Foundation
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": %s: " fmt, __func__
+
+#include <linux/types.h>
+#include <linux/interrupt.h>
+#include <linux/bitops.h>
+#include <linux/skbuff.h>
+
+#include "../nfc.h"
+#include <net/nfc/nci.h>
+#include <net/nfc/nci_core.h>
+
+/* Handle NCI Response packets */
+
+static void nci_core_reset_rsp_packet(struct nci_dev *ndev, struct sk_buff *skb)
+{
+       struct nci_core_reset_rsp *rsp = (void *) skb->data;
+
+       pr_debug("status 0x%x\n", rsp->status);
+
+       if (rsp->status == NCI_STATUS_OK) {
+               ndev->nci_ver = rsp->nci_ver;
+               pr_debug("nci_ver 0x%x, config_status 0x%x\n",
+                        rsp->nci_ver, rsp->config_status);
+       }
+
+       nci_req_complete(ndev, rsp->status);
+}
+
+static void nci_core_init_rsp_packet(struct nci_dev *ndev, struct sk_buff *skb)
+{
+       struct nci_core_init_rsp_1 *rsp_1 = (void *) skb->data;
+       struct nci_core_init_rsp_2 *rsp_2;
+
+       pr_debug("status 0x%x\n", rsp_1->status);
+
+       if (rsp_1->status != NCI_STATUS_OK)
+               goto exit;
+
+       ndev->nfcc_features = __le32_to_cpu(rsp_1->nfcc_features);
+       ndev->num_supported_rf_interfaces = rsp_1->num_supported_rf_interfaces;
+
+       if (ndev->num_supported_rf_interfaces >
+           NCI_MAX_SUPPORTED_RF_INTERFACES) {
+               ndev->num_supported_rf_interfaces =
+                       NCI_MAX_SUPPORTED_RF_INTERFACES;
+       }
+
+       memcpy(ndev->supported_rf_interfaces,
+              rsp_1->supported_rf_interfaces,
+              ndev->num_supported_rf_interfaces);
+
+       rsp_2 = (void *) (skb->data + 6 + rsp_1->num_supported_rf_interfaces);
+
+       ndev->max_logical_connections = rsp_2->max_logical_connections;
+       ndev->max_routing_table_size =
+               __le16_to_cpu(rsp_2->max_routing_table_size);
+       ndev->max_ctrl_pkt_payload_len =
+               rsp_2->max_ctrl_pkt_payload_len;
+       ndev->max_size_for_large_params =
+               __le16_to_cpu(rsp_2->max_size_for_large_params);
+       ndev->manufact_id =
+               rsp_2->manufact_id;
+       ndev->manufact_specific_info =
+               __le32_to_cpu(rsp_2->manufact_specific_info);
+
+       pr_debug("nfcc_features 0x%x\n",
+                ndev->nfcc_features);
+       pr_debug("num_supported_rf_interfaces %d\n",
+                ndev->num_supported_rf_interfaces);
+       pr_debug("supported_rf_interfaces[0] 0x%x\n",
+                ndev->supported_rf_interfaces[0]);
+       pr_debug("supported_rf_interfaces[1] 0x%x\n",
+                ndev->supported_rf_interfaces[1]);
+       pr_debug("supported_rf_interfaces[2] 0x%x\n",
+                ndev->supported_rf_interfaces[2]);
+       pr_debug("supported_rf_interfaces[3] 0x%x\n",
+                ndev->supported_rf_interfaces[3]);
+       pr_debug("max_logical_connections %d\n",
+                ndev->max_logical_connections);
+       pr_debug("max_routing_table_size %d\n",
+                ndev->max_routing_table_size);
+       pr_debug("max_ctrl_pkt_payload_len %d\n",
+                ndev->max_ctrl_pkt_payload_len);
+       pr_debug("max_size_for_large_params %d\n",
+                ndev->max_size_for_large_params);
+       pr_debug("manufact_id 0x%x\n",
+                ndev->manufact_id);
+       pr_debug("manufact_specific_info 0x%x\n",
+                ndev->manufact_specific_info);
+
+exit:
+       nci_req_complete(ndev, rsp_1->status);
+}
+
+static void nci_rf_disc_map_rsp_packet(struct nci_dev *ndev,
+                                      struct sk_buff *skb)
+{
+       __u8 status = skb->data[0];
+
+       pr_debug("status 0x%x\n", status);
+
+       nci_req_complete(ndev, status);
+}
+
+static void nci_rf_disc_rsp_packet(struct nci_dev *ndev, struct sk_buff *skb)
+{
+       __u8 status = skb->data[0];
+
+       pr_debug("status 0x%x\n", status);
+
+       if (status == NCI_STATUS_OK)
+               atomic_set(&ndev->state, NCI_DISCOVERY);
+
+       nci_req_complete(ndev, status);
+}
+
+static void nci_rf_disc_select_rsp_packet(struct nci_dev *ndev,
+                                         struct sk_buff *skb)
+{
+       __u8 status = skb->data[0];
+
+       pr_debug("status 0x%x\n", status);
+
+       /* Complete the request on intf_activated_ntf or generic_error_ntf */
+       if (status != NCI_STATUS_OK)
+               nci_req_complete(ndev, status);
+}
+
+static void nci_rf_deactivate_rsp_packet(struct nci_dev *ndev,
+                                        struct sk_buff *skb)
+{
+       __u8 status = skb->data[0];
+
+       pr_debug("status 0x%x\n", status);
+
+       /* If target was active, complete the request only in deactivate_ntf */
+       if ((status != NCI_STATUS_OK) ||
+           (atomic_read(&ndev->state) != NCI_POLL_ACTIVE)) {
+               nci_clear_target_list(ndev);
+               atomic_set(&ndev->state, NCI_IDLE);
+               nci_req_complete(ndev, status);
+       }
+}
+
+void nci_rsp_packet(struct nci_dev *ndev, struct sk_buff *skb)
+{
+       __u16 rsp_opcode = nci_opcode(skb->data);
+
+       /* we got a rsp, stop the cmd timer */
+       del_timer(&ndev->cmd_timer);
+
+       pr_debug("NCI RX: MT=rsp, PBF=%d, GID=0x%x, OID=0x%x, plen=%d\n",
+                nci_pbf(skb->data),
+                nci_opcode_gid(rsp_opcode),
+                nci_opcode_oid(rsp_opcode),
+                nci_plen(skb->data));
+
+       /* strip the nci control header */
+       skb_pull(skb, NCI_CTRL_HDR_SIZE);
+
+       switch (rsp_opcode) {
+       case NCI_OP_CORE_RESET_RSP:
+               nci_core_reset_rsp_packet(ndev, skb);
+               break;
+
+       case NCI_OP_CORE_INIT_RSP:
+               nci_core_init_rsp_packet(ndev, skb);
+               break;
+
+       case NCI_OP_RF_DISCOVER_MAP_RSP:
+               nci_rf_disc_map_rsp_packet(ndev, skb);
+               break;
+
+       case NCI_OP_RF_DISCOVER_RSP:
+               nci_rf_disc_rsp_packet(ndev, skb);
+               break;
+
+       case NCI_OP_RF_DISCOVER_SELECT_RSP:
+               nci_rf_disc_select_rsp_packet(ndev, skb);
+               break;
+
+       case NCI_OP_RF_DEACTIVATE_RSP:
+               nci_rf_deactivate_rsp_packet(ndev, skb);
+               break;
+
+       default:
+               pr_err("unknown rsp opcode 0x%x\n", rsp_opcode);
+               break;
+       }
+
+       kfree_skb(skb);
+
+       /* trigger the next cmd */
+       atomic_set(&ndev->cmd_cnt, 1);
+       if (!skb_queue_empty(&ndev->cmd_q))
+               queue_work(ndev->cmd_wq, &ndev->cmd_work);
+}
diff --git a/net/nfc/netlink.c b/net/nfc/netlink.c
new file mode 100644 (file)
index 0000000..99bc6f7
--- /dev/null
@@ -0,0 +1,838 @@
+/*
+ * Copyright (C) 2011 Instituto Nokia de Tecnologia
+ *
+ * Authors:
+ *    Lauro Ramos Venancio <lauro.venancio@openbossa.org>
+ *    Aloisio Almeida Jr <aloisio.almeida@openbossa.org>
+ *
+ * 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
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": %s: " fmt, __func__
+
+#include <net/genetlink.h>
+#include <linux/nfc.h>
+#include <linux/slab.h>
+
+#include "nfc.h"
+
+static struct genl_multicast_group nfc_genl_event_mcgrp = {
+       .name = NFC_GENL_MCAST_EVENT_NAME,
+};
+
+static struct genl_family nfc_genl_family = {
+       .id = GENL_ID_GENERATE,
+       .hdrsize = 0,
+       .name = NFC_GENL_NAME,
+       .version = NFC_GENL_VERSION,
+       .maxattr = NFC_ATTR_MAX,
+};
+
+static const struct nla_policy nfc_genl_policy[NFC_ATTR_MAX + 1] = {
+       [NFC_ATTR_DEVICE_INDEX] = { .type = NLA_U32 },
+       [NFC_ATTR_DEVICE_NAME] = { .type = NLA_STRING,
+                               .len = NFC_DEVICE_NAME_MAXSIZE },
+       [NFC_ATTR_PROTOCOLS] = { .type = NLA_U32 },
+       [NFC_ATTR_COMM_MODE] = { .type = NLA_U8 },
+       [NFC_ATTR_RF_MODE] = { .type = NLA_U8 },
+       [NFC_ATTR_DEVICE_POWERED] = { .type = NLA_U8 },
+       [NFC_ATTR_IM_PROTOCOLS] = { .type = NLA_U32 },
+       [NFC_ATTR_TM_PROTOCOLS] = { .type = NLA_U32 },
+};
+
+static int nfc_genl_send_target(struct sk_buff *msg, struct nfc_target *target,
+                               struct netlink_callback *cb, int flags)
+{
+       void *hdr;
+
+       hdr = genlmsg_put(msg, NETLINK_CB(cb->skb).pid, cb->nlh->nlmsg_seq,
+                         &nfc_genl_family, flags, NFC_CMD_GET_TARGET);
+       if (!hdr)
+               return -EMSGSIZE;
+
+       genl_dump_check_consistent(cb, hdr, &nfc_genl_family);
+
+       if (nla_put_u32(msg, NFC_ATTR_TARGET_INDEX, target->idx) ||
+           nla_put_u32(msg, NFC_ATTR_PROTOCOLS, target->supported_protocols) ||
+           nla_put_u16(msg, NFC_ATTR_TARGET_SENS_RES, target->sens_res) ||
+           nla_put_u8(msg, NFC_ATTR_TARGET_SEL_RES, target->sel_res))
+               goto nla_put_failure;
+       if (target->nfcid1_len > 0 &&
+           nla_put(msg, NFC_ATTR_TARGET_NFCID1, target->nfcid1_len,
+                   target->nfcid1))
+               goto nla_put_failure;
+       if (target->sensb_res_len > 0 &&
+           nla_put(msg, NFC_ATTR_TARGET_SENSB_RES, target->sensb_res_len,
+                   target->sensb_res))
+               goto nla_put_failure;
+       if (target->sensf_res_len > 0 &&
+           nla_put(msg, NFC_ATTR_TARGET_SENSF_RES, target->sensf_res_len,
+                   target->sensf_res))
+               goto nla_put_failure;
+
+       return genlmsg_end(msg, hdr);
+
+nla_put_failure:
+       genlmsg_cancel(msg, hdr);
+       return -EMSGSIZE;
+}
+
+static struct nfc_dev *__get_device_from_cb(struct netlink_callback *cb)
+{
+       struct nfc_dev *dev;
+       int rc;
+       u32 idx;
+
+       rc = nlmsg_parse(cb->nlh, GENL_HDRLEN + nfc_genl_family.hdrsize,
+                        nfc_genl_family.attrbuf,
+                        nfc_genl_family.maxattr,
+                        nfc_genl_policy);
+       if (rc < 0)
+               return ERR_PTR(rc);
+
+       if (!nfc_genl_family.attrbuf[NFC_ATTR_DEVICE_INDEX])
+               return ERR_PTR(-EINVAL);
+
+       idx = nla_get_u32(nfc_genl_family.attrbuf[NFC_ATTR_DEVICE_INDEX]);
+
+       dev = nfc_get_device(idx);
+       if (!dev)
+               return ERR_PTR(-ENODEV);
+
+       return dev;
+}
+
+static int nfc_genl_dump_targets(struct sk_buff *skb,
+                                struct netlink_callback *cb)
+{
+       int i = cb->args[0];
+       struct nfc_dev *dev = (struct nfc_dev *) cb->args[1];
+       int rc;
+
+       if (!dev) {
+               dev = __get_device_from_cb(cb);
+               if (IS_ERR(dev))
+                       return PTR_ERR(dev);
+
+               cb->args[1] = (long) dev;
+       }
+
+       device_lock(&dev->dev);
+
+       cb->seq = dev->targets_generation;
+
+       while (i < dev->n_targets) {
+               rc = nfc_genl_send_target(skb, &dev->targets[i], cb,
+                                         NLM_F_MULTI);
+               if (rc < 0)
+                       break;
+
+               i++;
+       }
+
+       device_unlock(&dev->dev);
+
+       cb->args[0] = i;
+
+       return skb->len;
+}
+
+static int nfc_genl_dump_targets_done(struct netlink_callback *cb)
+{
+       struct nfc_dev *dev = (struct nfc_dev *) cb->args[1];
+
+       if (dev)
+               nfc_put_device(dev);
+
+       return 0;
+}
+
+int nfc_genl_targets_found(struct nfc_dev *dev)
+{
+       struct sk_buff *msg;
+       void *hdr;
+
+       dev->genl_data.poll_req_pid = 0;
+
+       msg = nlmsg_new(NLMSG_GOODSIZE, GFP_ATOMIC);
+       if (!msg)
+               return -ENOMEM;
+
+       hdr = genlmsg_put(msg, 0, 0, &nfc_genl_family, 0,
+                         NFC_EVENT_TARGETS_FOUND);
+       if (!hdr)
+               goto free_msg;
+
+       if (nla_put_u32(msg, NFC_ATTR_DEVICE_INDEX, dev->idx))
+               goto nla_put_failure;
+
+       genlmsg_end(msg, hdr);
+
+       return genlmsg_multicast(msg, 0, nfc_genl_event_mcgrp.id, GFP_ATOMIC);
+
+nla_put_failure:
+       genlmsg_cancel(msg, hdr);
+free_msg:
+       nlmsg_free(msg);
+       return -EMSGSIZE;
+}
+
+int nfc_genl_target_lost(struct nfc_dev *dev, u32 target_idx)
+{
+       struct sk_buff *msg;
+       void *hdr;
+
+       msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+       if (!msg)
+               return -ENOMEM;
+
+       hdr = genlmsg_put(msg, 0, 0, &nfc_genl_family, 0,
+                         NFC_EVENT_TARGET_LOST);
+       if (!hdr)
+               goto free_msg;
+
+       if (nla_put_string(msg, NFC_ATTR_DEVICE_NAME, nfc_device_name(dev)) ||
+           nla_put_u32(msg, NFC_ATTR_TARGET_INDEX, target_idx))
+               goto nla_put_failure;
+
+       genlmsg_end(msg, hdr);
+
+       genlmsg_multicast(msg, 0, nfc_genl_event_mcgrp.id, GFP_KERNEL);
+
+       return 0;
+
+nla_put_failure:
+       genlmsg_cancel(msg, hdr);
+free_msg:
+       nlmsg_free(msg);
+       return -EMSGSIZE;
+}
+
+int nfc_genl_tm_activated(struct nfc_dev *dev, u32 protocol)
+{
+       struct sk_buff *msg;
+       void *hdr;
+
+       msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+       if (!msg)
+               return -ENOMEM;
+
+       hdr = genlmsg_put(msg, 0, 0, &nfc_genl_family, 0,
+                         NFC_EVENT_TM_ACTIVATED);
+       if (!hdr)
+               goto free_msg;
+
+       if (nla_put_u32(msg, NFC_ATTR_DEVICE_INDEX, dev->idx))
+               goto nla_put_failure;
+       if (nla_put_u32(msg, NFC_ATTR_TM_PROTOCOLS, protocol))
+               goto nla_put_failure;
+
+       genlmsg_end(msg, hdr);
+
+       genlmsg_multicast(msg, 0, nfc_genl_event_mcgrp.id, GFP_KERNEL);
+
+       return 0;
+
+nla_put_failure:
+       genlmsg_cancel(msg, hdr);
+free_msg:
+       nlmsg_free(msg);
+       return -EMSGSIZE;
+}
+
+int nfc_genl_tm_deactivated(struct nfc_dev *dev)
+{
+       struct sk_buff *msg;
+       void *hdr;
+
+       msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+       if (!msg)
+               return -ENOMEM;
+
+       hdr = genlmsg_put(msg, 0, 0, &nfc_genl_family, 0,
+                         NFC_EVENT_TM_DEACTIVATED);
+       if (!hdr)
+               goto free_msg;
+
+       if (nla_put_u32(msg, NFC_ATTR_DEVICE_INDEX, dev->idx))
+               goto nla_put_failure;
+
+       genlmsg_end(msg, hdr);
+
+       genlmsg_multicast(msg, 0, nfc_genl_event_mcgrp.id, GFP_KERNEL);
+
+       return 0;
+
+nla_put_failure:
+       genlmsg_cancel(msg, hdr);
+free_msg:
+       nlmsg_free(msg);
+       return -EMSGSIZE;
+}
+
+int nfc_genl_device_added(struct nfc_dev *dev)
+{
+       struct sk_buff *msg;
+       void *hdr;
+
+       msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+       if (!msg)
+               return -ENOMEM;
+
+       hdr = genlmsg_put(msg, 0, 0, &nfc_genl_family, 0,
+                         NFC_EVENT_DEVICE_ADDED);
+       if (!hdr)
+               goto free_msg;
+
+       if (nla_put_string(msg, NFC_ATTR_DEVICE_NAME, nfc_device_name(dev)) ||
+           nla_put_u32(msg, NFC_ATTR_DEVICE_INDEX, dev->idx) ||
+           nla_put_u32(msg, NFC_ATTR_PROTOCOLS, dev->supported_protocols) ||
+           nla_put_u8(msg, NFC_ATTR_DEVICE_POWERED, dev->dev_up))
+               goto nla_put_failure;
+
+       genlmsg_end(msg, hdr);
+
+       genlmsg_multicast(msg, 0, nfc_genl_event_mcgrp.id, GFP_KERNEL);
+
+       return 0;
+
+nla_put_failure:
+       genlmsg_cancel(msg, hdr);
+free_msg:
+       nlmsg_free(msg);
+       return -EMSGSIZE;
+}
+
+int nfc_genl_device_removed(struct nfc_dev *dev)
+{
+       struct sk_buff *msg;
+       void *hdr;
+
+       msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+       if (!msg)
+               return -ENOMEM;
+
+       hdr = genlmsg_put(msg, 0, 0, &nfc_genl_family, 0,
+                         NFC_EVENT_DEVICE_REMOVED);
+       if (!hdr)
+               goto free_msg;
+
+       if (nla_put_u32(msg, NFC_ATTR_DEVICE_INDEX, dev->idx))
+               goto nla_put_failure;
+
+       genlmsg_end(msg, hdr);
+
+       genlmsg_multicast(msg, 0, nfc_genl_event_mcgrp.id, GFP_KERNEL);
+
+       return 0;
+
+nla_put_failure:
+       genlmsg_cancel(msg, hdr);
+free_msg:
+       nlmsg_free(msg);
+       return -EMSGSIZE;
+}
+
+static int nfc_genl_send_device(struct sk_buff *msg, struct nfc_dev *dev,
+                               u32 pid, u32 seq,
+                               struct netlink_callback *cb,
+                               int flags)
+{
+       void *hdr;
+
+       hdr = genlmsg_put(msg, pid, seq, &nfc_genl_family, flags,
+                         NFC_CMD_GET_DEVICE);
+       if (!hdr)
+               return -EMSGSIZE;
+
+       if (cb)
+               genl_dump_check_consistent(cb, hdr, &nfc_genl_family);
+
+       if (nla_put_string(msg, NFC_ATTR_DEVICE_NAME, nfc_device_name(dev)) ||
+           nla_put_u32(msg, NFC_ATTR_DEVICE_INDEX, dev->idx) ||
+           nla_put_u32(msg, NFC_ATTR_PROTOCOLS, dev->supported_protocols) ||
+           nla_put_u8(msg, NFC_ATTR_DEVICE_POWERED, dev->dev_up))
+               goto nla_put_failure;
+
+       return genlmsg_end(msg, hdr);
+
+nla_put_failure:
+       genlmsg_cancel(msg, hdr);
+       return -EMSGSIZE;
+}
+
+static int nfc_genl_dump_devices(struct sk_buff *skb,
+                                struct netlink_callback *cb)
+{
+       struct class_dev_iter *iter = (struct class_dev_iter *) cb->args[0];
+       struct nfc_dev *dev = (struct nfc_dev *) cb->args[1];
+       bool first_call = false;
+
+       if (!iter) {
+               first_call = true;
+               iter = kmalloc(sizeof(struct class_dev_iter), GFP_KERNEL);
+               if (!iter)
+                       return -ENOMEM;
+               cb->args[0] = (long) iter;
+       }
+
+       mutex_lock(&nfc_devlist_mutex);
+
+       cb->seq = nfc_devlist_generation;
+
+       if (first_call) {
+               nfc_device_iter_init(iter);
+               dev = nfc_device_iter_next(iter);
+       }
+
+       while (dev) {
+               int rc;
+
+               rc = nfc_genl_send_device(skb, dev, NETLINK_CB(cb->skb).pid,
+                                         cb->nlh->nlmsg_seq, cb, NLM_F_MULTI);
+               if (rc < 0)
+                       break;
+
+               dev = nfc_device_iter_next(iter);
+       }
+
+       mutex_unlock(&nfc_devlist_mutex);
+
+       cb->args[1] = (long) dev;
+
+       return skb->len;
+}
+
+static int nfc_genl_dump_devices_done(struct netlink_callback *cb)
+{
+       struct class_dev_iter *iter = (struct class_dev_iter *) cb->args[0];
+
+       nfc_device_iter_exit(iter);
+       kfree(iter);
+
+       return 0;
+}
+
+int nfc_genl_dep_link_up_event(struct nfc_dev *dev, u32 target_idx,
+                              u8 comm_mode, u8 rf_mode)
+{
+       struct sk_buff *msg;
+       void *hdr;
+
+       pr_debug("DEP link is up\n");
+
+       msg = nlmsg_new(NLMSG_GOODSIZE, GFP_ATOMIC);
+       if (!msg)
+               return -ENOMEM;
+
+       hdr = genlmsg_put(msg, 0, 0, &nfc_genl_family, 0, NFC_CMD_DEP_LINK_UP);
+       if (!hdr)
+               goto free_msg;
+
+       if (nla_put_u32(msg, NFC_ATTR_DEVICE_INDEX, dev->idx))
+               goto nla_put_failure;
+       if (rf_mode == NFC_RF_INITIATOR &&
+           nla_put_u32(msg, NFC_ATTR_TARGET_INDEX, target_idx))
+               goto nla_put_failure;
+       if (nla_put_u8(msg, NFC_ATTR_COMM_MODE, comm_mode) ||
+           nla_put_u8(msg, NFC_ATTR_RF_MODE, rf_mode))
+               goto nla_put_failure;
+
+       genlmsg_end(msg, hdr);
+
+       dev->dep_link_up = true;
+
+       genlmsg_multicast(msg, 0, nfc_genl_event_mcgrp.id, GFP_ATOMIC);
+
+       return 0;
+
+nla_put_failure:
+       genlmsg_cancel(msg, hdr);
+free_msg:
+       nlmsg_free(msg);
+       return -EMSGSIZE;
+}
+
+int nfc_genl_dep_link_down_event(struct nfc_dev *dev)
+{
+       struct sk_buff *msg;
+       void *hdr;
+
+       pr_debug("DEP link is down\n");
+
+       msg = nlmsg_new(NLMSG_GOODSIZE, GFP_ATOMIC);
+       if (!msg)
+               return -ENOMEM;
+
+       hdr = genlmsg_put(msg, 0, 0, &nfc_genl_family, 0,
+                         NFC_CMD_DEP_LINK_DOWN);
+       if (!hdr)
+               goto free_msg;
+
+       if (nla_put_u32(msg, NFC_ATTR_DEVICE_INDEX, dev->idx))
+               goto nla_put_failure;
+
+       genlmsg_end(msg, hdr);
+
+       genlmsg_multicast(msg, 0, nfc_genl_event_mcgrp.id, GFP_ATOMIC);
+
+       return 0;
+
+nla_put_failure:
+       genlmsg_cancel(msg, hdr);
+free_msg:
+       nlmsg_free(msg);
+       return -EMSGSIZE;
+}
+
+static int nfc_genl_get_device(struct sk_buff *skb, struct genl_info *info)
+{
+       struct sk_buff *msg;
+       struct nfc_dev *dev;
+       u32 idx;
+       int rc = -ENOBUFS;
+
+       if (!info->attrs[NFC_ATTR_DEVICE_INDEX])
+               return -EINVAL;
+
+       idx = nla_get_u32(info->attrs[NFC_ATTR_DEVICE_INDEX]);
+
+       dev = nfc_get_device(idx);
+       if (!dev)
+               return -ENODEV;
+
+       msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+       if (!msg) {
+               rc = -ENOMEM;
+               goto out_putdev;
+       }
+
+       rc = nfc_genl_send_device(msg, dev, info->snd_pid, info->snd_seq,
+                                 NULL, 0);
+       if (rc < 0)
+               goto out_free;
+
+       nfc_put_device(dev);
+
+       return genlmsg_reply(msg, info);
+
+out_free:
+       nlmsg_free(msg);
+out_putdev:
+       nfc_put_device(dev);
+       return rc;
+}
+
+static int nfc_genl_dev_up(struct sk_buff *skb, struct genl_info *info)
+{
+       struct nfc_dev *dev;
+       int rc;
+       u32 idx;
+
+       if (!info->attrs[NFC_ATTR_DEVICE_INDEX])
+               return -EINVAL;
+
+       idx = nla_get_u32(info->attrs[NFC_ATTR_DEVICE_INDEX]);
+
+       dev = nfc_get_device(idx);
+       if (!dev)
+               return -ENODEV;
+
+       rc = nfc_dev_up(dev);
+
+       nfc_put_device(dev);
+       return rc;
+}
+
+static int nfc_genl_dev_down(struct sk_buff *skb, struct genl_info *info)
+{
+       struct nfc_dev *dev;
+       int rc;
+       u32 idx;
+
+       if (!info->attrs[NFC_ATTR_DEVICE_INDEX])
+               return -EINVAL;
+
+       idx = nla_get_u32(info->attrs[NFC_ATTR_DEVICE_INDEX]);
+
+       dev = nfc_get_device(idx);
+       if (!dev)
+               return -ENODEV;
+
+       rc = nfc_dev_down(dev);
+
+       nfc_put_device(dev);
+       return rc;
+}
+
+static int nfc_genl_start_poll(struct sk_buff *skb, struct genl_info *info)
+{
+       struct nfc_dev *dev;
+       int rc;
+       u32 idx;
+       u32 im_protocols = 0, tm_protocols = 0;
+
+       pr_debug("Poll start\n");
+
+       if (!info->attrs[NFC_ATTR_DEVICE_INDEX] ||
+           ((!info->attrs[NFC_ATTR_IM_PROTOCOLS] &&
+             !info->attrs[NFC_ATTR_PROTOCOLS]) &&
+            !info->attrs[NFC_ATTR_TM_PROTOCOLS]))
+               return -EINVAL;
+
+       idx = nla_get_u32(info->attrs[NFC_ATTR_DEVICE_INDEX]);
+
+       if (info->attrs[NFC_ATTR_TM_PROTOCOLS])
+               tm_protocols = nla_get_u32(info->attrs[NFC_ATTR_TM_PROTOCOLS]);
+
+       if (info->attrs[NFC_ATTR_IM_PROTOCOLS])
+               im_protocols = nla_get_u32(info->attrs[NFC_ATTR_IM_PROTOCOLS]);
+       else if (info->attrs[NFC_ATTR_PROTOCOLS])
+               im_protocols = nla_get_u32(info->attrs[NFC_ATTR_PROTOCOLS]);
+
+       dev = nfc_get_device(idx);
+       if (!dev)
+               return -ENODEV;
+
+       mutex_lock(&dev->genl_data.genl_data_mutex);
+
+       rc = nfc_start_poll(dev, im_protocols, tm_protocols);
+       if (!rc)
+               dev->genl_data.poll_req_pid = info->snd_pid;
+
+       mutex_unlock(&dev->genl_data.genl_data_mutex);
+
+       nfc_put_device(dev);
+       return rc;
+}
+
+static int nfc_genl_stop_poll(struct sk_buff *skb, struct genl_info *info)
+{
+       struct nfc_dev *dev;
+       int rc;
+       u32 idx;
+
+       if (!info->attrs[NFC_ATTR_DEVICE_INDEX])
+               return -EINVAL;
+
+       idx = nla_get_u32(info->attrs[NFC_ATTR_DEVICE_INDEX]);
+
+       dev = nfc_get_device(idx);
+       if (!dev)
+               return -ENODEV;
+
+       device_lock(&dev->dev);
+
+       if (!dev->polling) {
+               device_unlock(&dev->dev);
+               return -EINVAL;
+       }
+
+       device_unlock(&dev->dev);
+
+       mutex_lock(&dev->genl_data.genl_data_mutex);
+
+       if (dev->genl_data.poll_req_pid != info->snd_pid) {
+               rc = -EBUSY;
+               goto out;
+       }
+
+       rc = nfc_stop_poll(dev);
+       dev->genl_data.poll_req_pid = 0;
+
+out:
+       mutex_unlock(&dev->genl_data.genl_data_mutex);
+       nfc_put_device(dev);
+       return rc;
+}
+
+static int nfc_genl_dep_link_up(struct sk_buff *skb, struct genl_info *info)
+{
+       struct nfc_dev *dev;
+       int rc, tgt_idx;
+       u32 idx;
+       u8 comm;
+
+       pr_debug("DEP link up\n");
+
+       if (!info->attrs[NFC_ATTR_DEVICE_INDEX] ||
+           !info->attrs[NFC_ATTR_COMM_MODE])
+               return -EINVAL;
+
+       idx = nla_get_u32(info->attrs[NFC_ATTR_DEVICE_INDEX]);
+       if (!info->attrs[NFC_ATTR_TARGET_INDEX])
+               tgt_idx = NFC_TARGET_IDX_ANY;
+       else
+               tgt_idx = nla_get_u32(info->attrs[NFC_ATTR_TARGET_INDEX]);
+
+       comm = nla_get_u8(info->attrs[NFC_ATTR_COMM_MODE]);
+
+       if (comm != NFC_COMM_ACTIVE && comm != NFC_COMM_PASSIVE)
+               return -EINVAL;
+
+       dev = nfc_get_device(idx);
+       if (!dev)
+               return -ENODEV;
+
+       rc = nfc_dep_link_up(dev, tgt_idx, comm);
+
+       nfc_put_device(dev);
+
+       return rc;
+}
+
+static int nfc_genl_dep_link_down(struct sk_buff *skb, struct genl_info *info)
+{
+       struct nfc_dev *dev;
+       int rc;
+       u32 idx;
+
+       if (!info->attrs[NFC_ATTR_DEVICE_INDEX])
+               return -EINVAL;
+
+       idx = nla_get_u32(info->attrs[NFC_ATTR_DEVICE_INDEX]);
+
+       dev = nfc_get_device(idx);
+       if (!dev)
+               return -ENODEV;
+
+       rc = nfc_dep_link_down(dev);
+
+       nfc_put_device(dev);
+       return rc;
+}
+
+static struct genl_ops nfc_genl_ops[] = {
+       {
+               .cmd = NFC_CMD_GET_DEVICE,
+               .doit = nfc_genl_get_device,
+               .dumpit = nfc_genl_dump_devices,
+               .done = nfc_genl_dump_devices_done,
+               .policy = nfc_genl_policy,
+       },
+       {
+               .cmd = NFC_CMD_DEV_UP,
+               .doit = nfc_genl_dev_up,
+               .policy = nfc_genl_policy,
+       },
+       {
+               .cmd = NFC_CMD_DEV_DOWN,
+               .doit = nfc_genl_dev_down,
+               .policy = nfc_genl_policy,
+       },
+       {
+               .cmd = NFC_CMD_START_POLL,
+               .doit = nfc_genl_start_poll,
+               .policy = nfc_genl_policy,
+       },
+       {
+               .cmd = NFC_CMD_STOP_POLL,
+               .doit = nfc_genl_stop_poll,
+               .policy = nfc_genl_policy,
+       },
+       {
+               .cmd = NFC_CMD_DEP_LINK_UP,
+               .doit = nfc_genl_dep_link_up,
+               .policy = nfc_genl_policy,
+       },
+       {
+               .cmd = NFC_CMD_DEP_LINK_DOWN,
+               .doit = nfc_genl_dep_link_down,
+               .policy = nfc_genl_policy,
+       },
+       {
+               .cmd = NFC_CMD_GET_TARGET,
+               .dumpit = nfc_genl_dump_targets,
+               .done = nfc_genl_dump_targets_done,
+               .policy = nfc_genl_policy,
+       },
+};
+
+static int nfc_genl_rcv_nl_event(struct notifier_block *this,
+                                unsigned long event, void *ptr)
+{
+       struct netlink_notify *n = ptr;
+       struct class_dev_iter iter;
+       struct nfc_dev *dev;
+
+       if (event != NETLINK_URELEASE || n->protocol != NETLINK_GENERIC)
+               goto out;
+
+       pr_debug("NETLINK_URELEASE event from id %d\n", n->pid);
+
+       nfc_device_iter_init(&iter);
+       dev = nfc_device_iter_next(&iter);
+
+       while (dev) {
+               if (dev->genl_data.poll_req_pid == n->pid) {
+                       nfc_stop_poll(dev);
+                       dev->genl_data.poll_req_pid = 0;
+               }
+               dev = nfc_device_iter_next(&iter);
+       }
+
+       nfc_device_iter_exit(&iter);
+
+out:
+       return NOTIFY_DONE;
+}
+
+void nfc_genl_data_init(struct nfc_genl_data *genl_data)
+{
+       genl_data->poll_req_pid = 0;
+       mutex_init(&genl_data->genl_data_mutex);
+}
+
+void nfc_genl_data_exit(struct nfc_genl_data *genl_data)
+{
+       mutex_destroy(&genl_data->genl_data_mutex);
+}
+
+static struct notifier_block nl_notifier = {
+       .notifier_call  = nfc_genl_rcv_nl_event,
+};
+
+/**
+ * nfc_genl_init() - Initialize netlink interface
+ *
+ * This initialization function registers the nfc netlink family.
+ */
+int __init nfc_genl_init(void)
+{
+       int rc;
+
+       rc = genl_register_family_with_ops(&nfc_genl_family, nfc_genl_ops,
+                                          ARRAY_SIZE(nfc_genl_ops));
+       if (rc)
+               return rc;
+
+       rc = genl_register_mc_group(&nfc_genl_family, &nfc_genl_event_mcgrp);
+
+       netlink_register_notifier(&nl_notifier);
+
+       return rc;
+}
+
+/**
+ * nfc_genl_exit() - Deinitialize netlink interface
+ *
+ * This exit function unregisters the nfc netlink family.
+ */
+void nfc_genl_exit(void)
+{
+       netlink_unregister_notifier(&nl_notifier);
+       genl_unregister_family(&nfc_genl_family);
+}
diff --git a/net/nfc/nfc.h b/net/nfc/nfc.h
new file mode 100644 (file)
index 0000000..c5e42b7
--- /dev/null
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2011 Instituto Nokia de Tecnologia
+ *
+ * Authors:
+ *    Lauro Ramos Venancio <lauro.venancio@openbossa.org>
+ *    Aloisio Almeida Jr <aloisio.almeida@openbossa.org>
+ *
+ * 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
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __LOCAL_NFC_H
+#define __LOCAL_NFC_H
+
+#include <net/nfc/nfc.h>
+#include <net/sock.h>
+
+struct nfc_protocol {
+       int id;
+       struct proto *proto;
+       struct module *owner;
+       int (*create)(struct net *net, struct socket *sock,
+                     const struct nfc_protocol *nfc_proto);
+};
+
+struct nfc_rawsock {
+       struct sock sk;
+       struct nfc_dev *dev;
+       u32 target_idx;
+       struct work_struct tx_work;
+       bool tx_work_scheduled;
+};
+#define nfc_rawsock(sk) ((struct nfc_rawsock *) sk)
+#define to_rawsock_sk(_tx_work) \
+       ((struct sock *) container_of(_tx_work, struct nfc_rawsock, tx_work))
+
+#ifdef CONFIG_NFC_LLCP
+
+void nfc_llcp_mac_is_down(struct nfc_dev *dev);
+void nfc_llcp_mac_is_up(struct nfc_dev *dev, u32 target_idx,
+                       u8 comm_mode, u8 rf_mode);
+int nfc_llcp_register_device(struct nfc_dev *dev);
+void nfc_llcp_unregister_device(struct nfc_dev *dev);
+int nfc_llcp_set_remote_gb(struct nfc_dev *dev, u8 *gb, u8 gb_len);
+u8 *nfc_llcp_general_bytes(struct nfc_dev *dev, size_t *general_bytes_len);
+int nfc_llcp_data_received(struct nfc_dev *dev, struct sk_buff *skb);
+int __init nfc_llcp_init(void);
+void nfc_llcp_exit(void);
+
+#else
+
+static inline void nfc_llcp_mac_is_down(struct nfc_dev *dev)
+{
+}
+
+static inline void nfc_llcp_mac_is_up(struct nfc_dev *dev, u32 target_idx,
+                                     u8 comm_mode, u8 rf_mode)
+{
+}
+
+static inline int nfc_llcp_register_device(struct nfc_dev *dev)
+{
+       return 0;
+}
+
+static inline void nfc_llcp_unregister_device(struct nfc_dev *dev)
+{
+}
+
+static inline int nfc_llcp_set_remote_gb(struct nfc_dev *dev,
+                                        u8 *gb, u8 gb_len)
+{
+       return 0;
+}
+
+static inline u8 *nfc_llcp_general_bytes(struct nfc_dev *dev, size_t *gb_len)
+{
+       *gb_len = 0;
+       return NULL;
+}
+
+static inline int nfc_llcp_data_received(struct nfc_dev *dev,
+                                        struct sk_buff *skb)
+{
+       return 0;
+}
+
+static inline int nfc_llcp_init(void)
+{
+       return 0;
+}
+
+static inline void nfc_llcp_exit(void)
+{
+}
+
+#endif
+
+int __init rawsock_init(void);
+void rawsock_exit(void);
+
+int __init af_nfc_init(void);
+void af_nfc_exit(void);
+int nfc_proto_register(const struct nfc_protocol *nfc_proto);
+void nfc_proto_unregister(const struct nfc_protocol *nfc_proto);
+
+extern int nfc_devlist_generation;
+extern struct mutex nfc_devlist_mutex;
+
+int __init nfc_genl_init(void);
+void nfc_genl_exit(void);
+
+void nfc_genl_data_init(struct nfc_genl_data *genl_data);
+void nfc_genl_data_exit(struct nfc_genl_data *genl_data);
+
+int nfc_genl_targets_found(struct nfc_dev *dev);
+int nfc_genl_target_lost(struct nfc_dev *dev, u32 target_idx);
+
+int nfc_genl_device_added(struct nfc_dev *dev);
+int nfc_genl_device_removed(struct nfc_dev *dev);
+
+int nfc_genl_dep_link_up_event(struct nfc_dev *dev, u32 target_idx,
+                              u8 comm_mode, u8 rf_mode);
+int nfc_genl_dep_link_down_event(struct nfc_dev *dev);
+
+int nfc_genl_tm_activated(struct nfc_dev *dev, u32 protocol);
+int nfc_genl_tm_deactivated(struct nfc_dev *dev);
+
+struct nfc_dev *nfc_get_device(unsigned int idx);
+
+static inline void nfc_put_device(struct nfc_dev *dev)
+{
+       put_device(&dev->dev);
+}
+
+static inline void nfc_device_iter_init(struct class_dev_iter *iter)
+{
+       class_dev_iter_init(iter, &nfc_class, NULL, NULL);
+}
+
+static inline struct nfc_dev *nfc_device_iter_next(struct class_dev_iter *iter)
+{
+       struct device *d = class_dev_iter_next(iter);
+       if (!d)
+               return NULL;
+
+       return to_nfc_dev(d);
+}
+
+static inline void nfc_device_iter_exit(struct class_dev_iter *iter)
+{
+       class_dev_iter_exit(iter);
+}
+
+int nfc_dev_up(struct nfc_dev *dev);
+
+int nfc_dev_down(struct nfc_dev *dev);
+
+int nfc_start_poll(struct nfc_dev *dev, u32 im_protocols, u32 tm_protocols);
+
+int nfc_stop_poll(struct nfc_dev *dev);
+
+int nfc_dep_link_up(struct nfc_dev *dev, int target_idx, u8 comm_mode);
+
+int nfc_dep_link_down(struct nfc_dev *dev);
+
+int nfc_activate_target(struct nfc_dev *dev, u32 target_idx, u32 protocol);
+
+int nfc_deactivate_target(struct nfc_dev *dev, u32 target_idx);
+
+int nfc_data_exchange(struct nfc_dev *dev, u32 target_idx, struct sk_buff *skb,
+                     data_exchange_cb_t cb, void *cb_context);
+
+#endif /* __LOCAL_NFC_H */
diff --git a/net/nfc/rawsock.c b/net/nfc/rawsock.c
new file mode 100644 (file)
index 0000000..9d5f928
--- /dev/null
@@ -0,0 +1,349 @@
+/*
+ * Copyright (C) 2011 Instituto Nokia de Tecnologia
+ *
+ * Authors:
+ *    Aloisio Almeida Jr <aloisio.almeida@openbossa.org>
+ *    Lauro Ramos Venancio <lauro.venancio@openbossa.org>
+ *
+ * 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
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": %s: " fmt, __func__
+
+#include <net/tcp_states.h>
+#include <linux/nfc.h>
+
+#include "nfc.h"
+
+static void rawsock_write_queue_purge(struct sock *sk)
+{
+       pr_debug("sk=%p\n", sk);
+
+       spin_lock_bh(&sk->sk_write_queue.lock);
+       __skb_queue_purge(&sk->sk_write_queue);
+       nfc_rawsock(sk)->tx_work_scheduled = false;
+       spin_unlock_bh(&sk->sk_write_queue.lock);
+}
+
+static void rawsock_report_error(struct sock *sk, int err)
+{
+       pr_debug("sk=%p err=%d\n", sk, err);
+
+       sk->sk_shutdown = SHUTDOWN_MASK;
+       sk->sk_err = -err;
+       sk->sk_error_report(sk);
+
+       rawsock_write_queue_purge(sk);
+}
+
+static int rawsock_release(struct socket *sock)
+{
+       struct sock *sk = sock->sk;
+
+       pr_debug("sock=%p sk=%p\n", sock, sk);
+
+       if (!sk)
+               return 0;
+
+       sock_orphan(sk);
+       sock_put(sk);
+
+       return 0;
+}
+
+static int rawsock_connect(struct socket *sock, struct sockaddr *_addr,
+                          int len, int flags)
+{
+       struct sock *sk = sock->sk;
+       struct sockaddr_nfc *addr = (struct sockaddr_nfc *)_addr;
+       struct nfc_dev *dev;
+       int rc = 0;
+
+       pr_debug("sock=%p sk=%p flags=%d\n", sock, sk, flags);
+
+       if (!addr || len < sizeof(struct sockaddr_nfc) ||
+           addr->sa_family != AF_NFC)
+               return -EINVAL;
+
+       pr_debug("addr dev_idx=%u target_idx=%u protocol=%u\n",
+                addr->dev_idx, addr->target_idx, addr->nfc_protocol);
+
+       lock_sock(sk);
+
+       if (sock->state == SS_CONNECTED) {
+               rc = -EISCONN;
+               goto error;
+       }
+
+       dev = nfc_get_device(addr->dev_idx);
+       if (!dev) {
+               rc = -ENODEV;
+               goto error;
+       }
+
+       if (addr->target_idx > dev->target_next_idx - 1 ||
+           addr->target_idx < dev->target_next_idx - dev->n_targets) {
+               rc = -EINVAL;
+               goto error;
+       }
+
+       rc = nfc_activate_target(dev, addr->target_idx, addr->nfc_protocol);
+       if (rc)
+               goto put_dev;
+
+       nfc_rawsock(sk)->dev = dev;
+       nfc_rawsock(sk)->target_idx = addr->target_idx;
+       sock->state = SS_CONNECTED;
+       sk->sk_state = TCP_ESTABLISHED;
+       sk->sk_state_change(sk);
+
+       release_sock(sk);
+       return 0;
+
+put_dev:
+       nfc_put_device(dev);
+error:
+       release_sock(sk);
+       return rc;
+}
+
+static int rawsock_add_header(struct sk_buff *skb)
+{
+       *skb_push(skb, NFC_HEADER_SIZE) = 0;
+
+       return 0;
+}
+
+static void rawsock_data_exchange_complete(void *context, struct sk_buff *skb,
+                                          int err)
+{
+       struct sock *sk = (struct sock *) context;
+
+       BUG_ON(in_irq());
+
+       pr_debug("sk=%p err=%d\n", sk, err);
+
+       if (err)
+               goto error;
+
+       err = rawsock_add_header(skb);
+       if (err)
+               goto error;
+
+       err = sock_queue_rcv_skb(sk, skb);
+       if (err)
+               goto error;
+
+       spin_lock_bh(&sk->sk_write_queue.lock);
+       if (!skb_queue_empty(&sk->sk_write_queue))
+               schedule_work(&nfc_rawsock(sk)->tx_work);
+       else
+               nfc_rawsock(sk)->tx_work_scheduled = false;
+       spin_unlock_bh(&sk->sk_write_queue.lock);
+
+       sock_put(sk);
+       return;
+
+error:
+       rawsock_report_error(sk, err);
+       sock_put(sk);
+}
+
+static void rawsock_tx_work(struct work_struct *work)
+{
+       struct sock *sk = to_rawsock_sk(work);
+       struct nfc_dev *dev = nfc_rawsock(sk)->dev;
+       u32 target_idx = nfc_rawsock(sk)->target_idx;
+       struct sk_buff *skb;
+       int rc;
+
+       pr_debug("sk=%p target_idx=%u\n", sk, target_idx);
+
+       if (sk->sk_shutdown & SEND_SHUTDOWN) {
+               rawsock_write_queue_purge(sk);
+               return;
+       }
+
+       skb = skb_dequeue(&sk->sk_write_queue);
+
+       sock_hold(sk);
+       rc = nfc_data_exchange(dev, target_idx, skb,
+                              rawsock_data_exchange_complete, sk);
+       if (rc) {
+               rawsock_report_error(sk, rc);
+               sock_put(sk);
+       }
+}
+
+static int rawsock_sendmsg(struct kiocb *iocb, struct socket *sock,
+                          struct msghdr *msg, size_t len)
+{
+       struct sock *sk = sock->sk;
+       struct nfc_dev *dev = nfc_rawsock(sk)->dev;
+       struct sk_buff *skb;
+       int rc;
+
+       pr_debug("sock=%p sk=%p len=%zu\n", sock, sk, len);
+
+       if (msg->msg_namelen)
+               return -EOPNOTSUPP;
+
+       if (sock->state != SS_CONNECTED)
+               return -ENOTCONN;
+
+       skb = nfc_alloc_send_skb(dev, sk, msg->msg_flags, len, &rc);
+       if (skb == NULL)
+               return rc;
+
+       rc = memcpy_fromiovec(skb_put(skb, len), msg->msg_iov, len);
+       if (rc < 0) {
+               kfree_skb(skb);
+               return rc;
+       }
+
+       spin_lock_bh(&sk->sk_write_queue.lock);
+       __skb_queue_tail(&sk->sk_write_queue, skb);
+       if (!nfc_rawsock(sk)->tx_work_scheduled) {
+               schedule_work(&nfc_rawsock(sk)->tx_work);
+               nfc_rawsock(sk)->tx_work_scheduled = true;
+       }
+       spin_unlock_bh(&sk->sk_write_queue.lock);
+
+       return len;
+}
+
+static int rawsock_recvmsg(struct kiocb *iocb, struct socket *sock,
+                          struct msghdr *msg, size_t len, int flags)
+{
+       int noblock = flags & MSG_DONTWAIT;
+       struct sock *sk = sock->sk;
+       struct sk_buff *skb;
+       int copied;
+       int rc;
+
+       pr_debug("sock=%p sk=%p len=%zu flags=%d\n", sock, sk, len, flags);
+
+       skb = skb_recv_datagram(sk, flags, noblock, &rc);
+       if (!skb)
+               return rc;
+
+       msg->msg_namelen = 0;
+
+       copied = skb->len;
+       if (len < copied) {
+               msg->msg_flags |= MSG_TRUNC;
+               copied = len;
+       }
+
+       rc = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied);
+
+       skb_free_datagram(sk, skb);
+
+       return rc ? : copied;
+}
+
+
+static const struct proto_ops rawsock_ops = {
+       .family         = PF_NFC,
+       .owner          = THIS_MODULE,
+       .release        = rawsock_release,
+       .bind           = sock_no_bind,
+       .connect        = rawsock_connect,
+       .socketpair     = sock_no_socketpair,
+       .accept         = sock_no_accept,
+       .getname        = sock_no_getname,
+       .poll           = datagram_poll,
+       .ioctl          = sock_no_ioctl,
+       .listen         = sock_no_listen,
+       .shutdown       = sock_no_shutdown,
+       .setsockopt     = sock_no_setsockopt,
+       .getsockopt     = sock_no_getsockopt,
+       .sendmsg        = rawsock_sendmsg,
+       .recvmsg        = rawsock_recvmsg,
+       .mmap           = sock_no_mmap,
+};
+
+static void rawsock_destruct(struct sock *sk)
+{
+       pr_debug("sk=%p\n", sk);
+
+       if (sk->sk_state == TCP_ESTABLISHED) {
+               nfc_deactivate_target(nfc_rawsock(sk)->dev,
+                                     nfc_rawsock(sk)->target_idx);
+               nfc_put_device(nfc_rawsock(sk)->dev);
+       }
+
+       skb_queue_purge(&sk->sk_receive_queue);
+
+       if (!sock_flag(sk, SOCK_DEAD)) {
+               pr_err("Freeing alive NFC raw socket %p\n", sk);
+               return;
+       }
+}
+
+static int rawsock_create(struct net *net, struct socket *sock,
+                         const struct nfc_protocol *nfc_proto)
+{
+       struct sock *sk;
+
+       pr_debug("sock=%p\n", sock);
+
+       if (sock->type != SOCK_SEQPACKET)
+               return -ESOCKTNOSUPPORT;
+
+       sock->ops = &rawsock_ops;
+
+       sk = sk_alloc(net, PF_NFC, GFP_ATOMIC, nfc_proto->proto);
+       if (!sk)
+               return -ENOMEM;
+
+       sock_init_data(sock, sk);
+       sk->sk_protocol = nfc_proto->id;
+       sk->sk_destruct = rawsock_destruct;
+       sock->state = SS_UNCONNECTED;
+
+       INIT_WORK(&nfc_rawsock(sk)->tx_work, rawsock_tx_work);
+       nfc_rawsock(sk)->tx_work_scheduled = false;
+
+       return 0;
+}
+
+static struct proto rawsock_proto = {
+       .name     = "NFC_RAW",
+       .owner    = THIS_MODULE,
+       .obj_size = sizeof(struct nfc_rawsock),
+};
+
+static const struct nfc_protocol rawsock_nfc_proto = {
+       .id       = NFC_SOCKPROTO_RAW,
+       .proto    = &rawsock_proto,
+       .owner    = THIS_MODULE,
+       .create   = rawsock_create
+};
+
+int __init rawsock_init(void)
+{
+       int rc;
+
+       rc = nfc_proto_register(&rawsock_nfc_proto);
+
+       return rc;
+}
+
+void rawsock_exit(void)
+{
+       nfc_proto_unregister(&rawsock_nfc_proto);
+}