diff options
author | André Fabian Silva Delgado <emulatorman@parabola.nu> | 2015-09-08 01:01:14 -0300 |
---|---|---|
committer | André Fabian Silva Delgado <emulatorman@parabola.nu> | 2015-09-08 01:01:14 -0300 |
commit | e5fd91f1ef340da553f7a79da9540c3db711c937 (patch) | |
tree | b11842027dc6641da63f4bcc524f8678263304a3 /drivers/nfc/st-nci | |
parent | 2a9b0348e685a63d97486f6749622b61e9e3292f (diff) |
Linux-libre 4.2-gnu
Diffstat (limited to 'drivers/nfc/st-nci')
-rw-r--r-- | drivers/nfc/st-nci/Kconfig | 23 | ||||
-rw-r--r-- | drivers/nfc/st-nci/Makefile | 9 | ||||
-rw-r--r-- | drivers/nfc/st-nci/core.c | 179 | ||||
-rw-r--r-- | drivers/nfc/st-nci/i2c.c | 385 | ||||
-rw-r--r-- | drivers/nfc/st-nci/ndlc.c | 313 | ||||
-rw-r--r-- | drivers/nfc/st-nci/ndlc.h | 60 | ||||
-rw-r--r-- | drivers/nfc/st-nci/st-nci.h | 50 | ||||
-rw-r--r-- | drivers/nfc/st-nci/st-nci_se.c | 714 | ||||
-rw-r--r-- | drivers/nfc/st-nci/st-nci_se.h | 61 |
9 files changed, 1794 insertions, 0 deletions
diff --git a/drivers/nfc/st-nci/Kconfig b/drivers/nfc/st-nci/Kconfig new file mode 100644 index 000000000..fc3904c94 --- /dev/null +++ b/drivers/nfc/st-nci/Kconfig @@ -0,0 +1,23 @@ +config NFC_ST_NCI + tristate "STMicroelectronics ST NCI NFC driver" + depends on NFC_NCI + default n + ---help--- + STMicroelectronics NFC NCI chips core driver. It implements the chipset + NCI logic and hooks into the NFC kernel APIs. Physical layers will + register against it. + + To compile this driver as a module, choose m here. The module will + be called st-nci. + Say N if unsure. + +config NFC_ST_NCI_I2C + tristate "NFC ST NCI i2c support" + depends on NFC_ST_NCI && I2C + ---help--- + This module adds support for an I2C interface to the + STMicroelectronics NFC NCI chips familly. + Select this if your platform is using the i2c bus. + + If you choose to build a module, it'll be called st-nci_i2c. + Say N if unsure. diff --git a/drivers/nfc/st-nci/Makefile b/drivers/nfc/st-nci/Makefile new file mode 100644 index 000000000..0df157df3 --- /dev/null +++ b/drivers/nfc/st-nci/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for ST21NFCB NCI based NFC driver +# + +st-nci-objs = ndlc.o core.o st-nci_se.o +obj-$(CONFIG_NFC_ST_NCI) += st-nci.o + +st-nci_i2c-objs = i2c.o +obj-$(CONFIG_NFC_ST_NCI_I2C) += st-nci_i2c.o diff --git a/drivers/nfc/st-nci/core.c b/drivers/nfc/st-nci/core.c new file mode 100644 index 000000000..c419d3943 --- /dev/null +++ b/drivers/nfc/st-nci/core.c @@ -0,0 +1,179 @@ +/* + * NCI based Driver for STMicroelectronics NFC Chip + * + * Copyright (C) 2014-2015 STMicroelectronics SAS. 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, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/module.h> +#include <linux/nfc.h> +#include <net/nfc/nci.h> +#include <net/nfc/nci_core.h> +#include <linux/gpio.h> +#include <linux/delay.h> + +#include "st-nci.h" +#include "st-nci_se.h" + +#define DRIVER_DESC "NCI NFC driver for ST_NCI" + +#define ST_NCI1_X_PROPRIETARY_ISO15693 0x83 + +static int st_nci_init(struct nci_dev *ndev) +{ + struct nci_mode_set_cmd cmd; + + cmd.cmd_type = ST_NCI_SET_NFC_MODE; + cmd.mode = 1; + + return nci_prop_cmd(ndev, ST_NCI_CORE_PROP, + sizeof(struct nci_mode_set_cmd), (__u8 *)&cmd); +} + +static int st_nci_open(struct nci_dev *ndev) +{ + struct st_nci_info *info = nci_get_drvdata(ndev); + int r; + + if (test_and_set_bit(ST_NCI_RUNNING, &info->flags)) + return 0; + + r = ndlc_open(info->ndlc); + if (r) + clear_bit(ST_NCI_RUNNING, &info->flags); + + return r; +} + +static int st_nci_close(struct nci_dev *ndev) +{ + struct st_nci_info *info = nci_get_drvdata(ndev); + + if (!test_bit(ST_NCI_RUNNING, &info->flags)) + return 0; + + ndlc_close(info->ndlc); + + clear_bit(ST_NCI_RUNNING, &info->flags); + + return 0; +} + +static int st_nci_send(struct nci_dev *ndev, struct sk_buff *skb) +{ + struct st_nci_info *info = nci_get_drvdata(ndev); + + skb->dev = (void *)ndev; + + if (!test_bit(ST_NCI_RUNNING, &info->flags)) + return -EBUSY; + + return ndlc_send(info->ndlc, skb); +} + +static __u32 st_nci_get_rfprotocol(struct nci_dev *ndev, + __u8 rf_protocol) +{ + return rf_protocol == ST_NCI1_X_PROPRIETARY_ISO15693 ? + NFC_PROTO_ISO15693_MASK : 0; +} + +static int st_nci_prop_rsp_packet(struct nci_dev *ndev, + struct sk_buff *skb) +{ + __u8 status = skb->data[0]; + + nci_req_complete(ndev, status); + return 0; +} + +static struct nci_prop_ops st_nci_prop_ops[] = { + { + .opcode = nci_opcode_pack(NCI_GID_PROPRIETARY, + ST_NCI_CORE_PROP), + .rsp = st_nci_prop_rsp_packet, + }, +}; + +static struct nci_ops st_nci_ops = { + .init = st_nci_init, + .open = st_nci_open, + .close = st_nci_close, + .send = st_nci_send, + .get_rfprotocol = st_nci_get_rfprotocol, + .discover_se = st_nci_discover_se, + .enable_se = st_nci_enable_se, + .disable_se = st_nci_disable_se, + .se_io = st_nci_se_io, + .hci_load_session = st_nci_hci_load_session, + .hci_event_received = st_nci_hci_event_received, + .hci_cmd_received = st_nci_hci_cmd_received, + .prop_ops = st_nci_prop_ops, + .n_prop_ops = ARRAY_SIZE(st_nci_prop_ops), +}; + +int st_nci_probe(struct llt_ndlc *ndlc, int phy_headroom, + int phy_tailroom) +{ + struct st_nci_info *info; + int r; + u32 protocols; + + info = devm_kzalloc(ndlc->dev, + sizeof(struct st_nci_info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + protocols = NFC_PROTO_JEWEL_MASK + | NFC_PROTO_MIFARE_MASK + | NFC_PROTO_FELICA_MASK + | NFC_PROTO_ISO14443_MASK + | NFC_PROTO_ISO14443_B_MASK + | NFC_PROTO_ISO15693_MASK + | NFC_PROTO_NFC_DEP_MASK; + + ndlc->ndev = nci_allocate_device(&st_nci_ops, protocols, + phy_headroom, phy_tailroom); + if (!ndlc->ndev) { + pr_err("Cannot allocate nfc ndev\n"); + return -ENOMEM; + } + info->ndlc = ndlc; + + nci_set_drvdata(ndlc->ndev, info); + + r = nci_register_device(ndlc->ndev); + if (r) { + pr_err("Cannot register nfc device to nci core\n"); + nci_free_device(ndlc->ndev); + return r; + } + + return st_nci_se_init(ndlc->ndev); +} +EXPORT_SYMBOL_GPL(st_nci_probe); + +void st_nci_remove(struct nci_dev *ndev) +{ + struct st_nci_info *info = nci_get_drvdata(ndev); + + ndlc_close(info->ndlc); + + nci_unregister_device(ndev); + nci_free_device(ndev); +} +EXPORT_SYMBOL_GPL(st_nci_remove); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION(DRIVER_DESC); diff --git a/drivers/nfc/st-nci/i2c.c b/drivers/nfc/st-nci/i2c.c new file mode 100644 index 000000000..06175ce76 --- /dev/null +++ b/drivers/nfc/st-nci/i2c.c @@ -0,0 +1,385 @@ +/* + * I2C Link Layer for ST NCI NFC controller familly based Driver + * Copyright (C) 2014-2015 STMicroelectronics SAS. 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, see <http://www.gnu.org/licenses/>. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/gpio.h> +#include <linux/of_irq.h> +#include <linux/of_gpio.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/nfc.h> +#include <linux/platform_data/st_nci.h> + +#include "ndlc.h" + +#define DRIVER_DESC "NCI NFC driver for ST21NFCB" + +/* ndlc header */ +#define ST21NFCB_FRAME_HEADROOM 1 +#define ST21NFCB_FRAME_TAILROOM 0 + +#define ST_NCI_I2C_MIN_SIZE 4 /* PCB(1) + NCI Packet header(3) */ +#define ST_NCI_I2C_MAX_SIZE 250 /* req 4.2.1 */ + +#define ST_NCI_I2C_DRIVER_NAME "st_nci_i2c" + +static struct i2c_device_id st_nci_i2c_id_table[] = { + {ST_NCI_DRIVER_NAME, 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, st_nci_i2c_id_table); + +struct st_nci_i2c_phy { + struct i2c_client *i2c_dev; + struct llt_ndlc *ndlc; + + unsigned int gpio_reset; + unsigned int irq_polarity; +}; + +#define I2C_DUMP_SKB(info, skb) \ +do { \ + pr_debug("%s:\n", info); \ + print_hex_dump(KERN_DEBUG, "i2c: ", DUMP_PREFIX_OFFSET, \ + 16, 1, (skb)->data, (skb)->len, 0); \ +} while (0) + +static int st_nci_i2c_enable(void *phy_id) +{ + struct st_nci_i2c_phy *phy = phy_id; + + gpio_set_value(phy->gpio_reset, 0); + usleep_range(10000, 15000); + gpio_set_value(phy->gpio_reset, 1); + usleep_range(80000, 85000); + + if (phy->ndlc->powered == 0) + enable_irq(phy->i2c_dev->irq); + + return 0; +} + +static void st_nci_i2c_disable(void *phy_id) +{ + struct st_nci_i2c_phy *phy = phy_id; + + disable_irq_nosync(phy->i2c_dev->irq); +} + +/* + * Writing a frame must not return the number of written bytes. + * It must return either zero for success, or <0 for error. + * In addition, it must not alter the skb + */ +static int st_nci_i2c_write(void *phy_id, struct sk_buff *skb) +{ + int r = -1; + struct st_nci_i2c_phy *phy = phy_id; + struct i2c_client *client = phy->i2c_dev; + + I2C_DUMP_SKB("st_nci_i2c_write", skb); + + if (phy->ndlc->hard_fault != 0) + return phy->ndlc->hard_fault; + + r = i2c_master_send(client, skb->data, skb->len); + if (r < 0) { /* Retry, chip was in standby */ + usleep_range(1000, 4000); + r = i2c_master_send(client, skb->data, skb->len); + } + + if (r >= 0) { + if (r != skb->len) + r = -EREMOTEIO; + else + r = 0; + } + + return r; +} + +/* + * Reads an ndlc frame and returns it in a newly allocated sk_buff. + * returns: + * frame size : if received frame is complete (find ST21NFCB_SOF_EOF at + * end of read) + * -EAGAIN : if received frame is incomplete (not find ST21NFCB_SOF_EOF + * at end of read) + * -EREMOTEIO : i2c read error (fatal) + * -EBADMSG : frame was incorrect and discarded + * (value returned from st_nci_i2c_repack) + * -EIO : if no ST21NFCB_SOF_EOF is found after reaching + * the read length end sequence + */ +static int st_nci_i2c_read(struct st_nci_i2c_phy *phy, + struct sk_buff **skb) +{ + int r; + u8 len; + u8 buf[ST_NCI_I2C_MAX_SIZE]; + struct i2c_client *client = phy->i2c_dev; + + r = i2c_master_recv(client, buf, ST_NCI_I2C_MIN_SIZE); + if (r < 0) { /* Retry, chip was in standby */ + usleep_range(1000, 4000); + r = i2c_master_recv(client, buf, ST_NCI_I2C_MIN_SIZE); + } + + if (r != ST_NCI_I2C_MIN_SIZE) + return -EREMOTEIO; + + len = be16_to_cpu(*(__be16 *) (buf + 2)); + if (len > ST_NCI_I2C_MAX_SIZE) { + nfc_err(&client->dev, "invalid frame len\n"); + return -EBADMSG; + } + + *skb = alloc_skb(ST_NCI_I2C_MIN_SIZE + len, GFP_KERNEL); + if (*skb == NULL) + return -ENOMEM; + + skb_reserve(*skb, ST_NCI_I2C_MIN_SIZE); + skb_put(*skb, ST_NCI_I2C_MIN_SIZE); + memcpy((*skb)->data, buf, ST_NCI_I2C_MIN_SIZE); + + if (!len) + return 0; + + r = i2c_master_recv(client, buf, len); + if (r != len) { + kfree_skb(*skb); + return -EREMOTEIO; + } + + skb_put(*skb, len); + memcpy((*skb)->data + ST_NCI_I2C_MIN_SIZE, buf, len); + + I2C_DUMP_SKB("i2c frame read", *skb); + + return 0; +} + +/* + * Reads an ndlc frame from the chip. + * + * On ST21NFCB, IRQ goes in idle state when read starts. + */ +static irqreturn_t st_nci_irq_thread_fn(int irq, void *phy_id) +{ + struct st_nci_i2c_phy *phy = phy_id; + struct i2c_client *client; + struct sk_buff *skb = NULL; + int r; + + if (!phy || !phy->ndlc || irq != phy->i2c_dev->irq) { + WARN_ON_ONCE(1); + return IRQ_NONE; + } + + client = phy->i2c_dev; + dev_dbg(&client->dev, "IRQ\n"); + + if (phy->ndlc->hard_fault) + return IRQ_HANDLED; + + if (!phy->ndlc->powered) { + st_nci_i2c_disable(phy); + return IRQ_HANDLED; + } + + r = st_nci_i2c_read(phy, &skb); + if (r == -EREMOTEIO || r == -ENOMEM || r == -EBADMSG) + return IRQ_HANDLED; + + ndlc_recv(phy->ndlc, skb); + + return IRQ_HANDLED; +} + +static struct nfc_phy_ops i2c_phy_ops = { + .write = st_nci_i2c_write, + .enable = st_nci_i2c_enable, + .disable = st_nci_i2c_disable, +}; + +#ifdef CONFIG_OF +static int st_nci_i2c_of_request_resources(struct i2c_client *client) +{ + struct st_nci_i2c_phy *phy = i2c_get_clientdata(client); + struct device_node *pp; + int gpio; + int r; + + pp = client->dev.of_node; + if (!pp) + return -ENODEV; + + /* Get GPIO from device tree */ + gpio = of_get_named_gpio(pp, "reset-gpios", 0); + if (gpio < 0) { + nfc_err(&client->dev, + "Failed to retrieve reset-gpios from device tree\n"); + return gpio; + } + + /* GPIO request and configuration */ + r = devm_gpio_request_one(&client->dev, gpio, + GPIOF_OUT_INIT_HIGH, "clf_reset"); + if (r) { + nfc_err(&client->dev, "Failed to request reset pin\n"); + return r; + } + phy->gpio_reset = gpio; + + phy->irq_polarity = irq_get_trigger_type(client->irq); + + return 0; +} +#else +static int st_nci_i2c_of_request_resources(struct i2c_client *client) +{ + return -ENODEV; +} +#endif + +static int st_nci_i2c_request_resources(struct i2c_client *client) +{ + struct st_nci_nfc_platform_data *pdata; + struct st_nci_i2c_phy *phy = i2c_get_clientdata(client); + int r; + + pdata = client->dev.platform_data; + if (pdata == NULL) { + nfc_err(&client->dev, "No platform data\n"); + return -EINVAL; + } + + /* store for later use */ + phy->gpio_reset = pdata->gpio_reset; + phy->irq_polarity = pdata->irq_polarity; + + r = devm_gpio_request_one(&client->dev, + phy->gpio_reset, GPIOF_OUT_INIT_HIGH, "clf_reset"); + if (r) { + pr_err("%s : reset gpio_request failed\n", __FILE__); + return r; + } + + return 0; +} + +static int st_nci_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct st_nci_i2c_phy *phy; + struct st_nci_nfc_platform_data *pdata; + int r; + + 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)) { + nfc_err(&client->dev, "Need I2C_FUNC_I2C\n"); + return -ENODEV; + } + + phy = devm_kzalloc(&client->dev, sizeof(struct st_nci_i2c_phy), + GFP_KERNEL); + if (!phy) + return -ENOMEM; + + phy->i2c_dev = client; + + i2c_set_clientdata(client, phy); + + pdata = client->dev.platform_data; + if (!pdata && client->dev.of_node) { + r = st_nci_i2c_of_request_resources(client); + if (r) { + nfc_err(&client->dev, "No platform data\n"); + return r; + } + } else if (pdata) { + r = st_nci_i2c_request_resources(client); + if (r) { + nfc_err(&client->dev, + "Cannot get platform resources\n"); + return r; + } + } else { + nfc_err(&client->dev, + "st21nfcb platform resources not available\n"); + return -ENODEV; + } + + r = ndlc_probe(phy, &i2c_phy_ops, &client->dev, + ST21NFCB_FRAME_HEADROOM, ST21NFCB_FRAME_TAILROOM, + &phy->ndlc); + if (r < 0) { + nfc_err(&client->dev, "Unable to register ndlc layer\n"); + return r; + } + + r = devm_request_threaded_irq(&client->dev, client->irq, NULL, + st_nci_irq_thread_fn, + phy->irq_polarity | IRQF_ONESHOT, + ST_NCI_DRIVER_NAME, phy); + if (r < 0) + nfc_err(&client->dev, "Unable to register IRQ handler\n"); + + return r; +} + +static int st_nci_i2c_remove(struct i2c_client *client) +{ + struct st_nci_i2c_phy *phy = i2c_get_clientdata(client); + + dev_dbg(&client->dev, "%s\n", __func__); + + ndlc_remove(phy->ndlc); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id of_st_nci_i2c_match[] = { + { .compatible = "st,st21nfcb-i2c", }, + { .compatible = "st,st21nfcb_i2c", }, + { .compatible = "st,st21nfcc-i2c", }, + {} +}; +MODULE_DEVICE_TABLE(of, of_st_nci_i2c_match); +#endif + +static struct i2c_driver st_nci_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = ST_NCI_I2C_DRIVER_NAME, + .of_match_table = of_match_ptr(of_st_nci_i2c_match), + }, + .probe = st_nci_i2c_probe, + .id_table = st_nci_i2c_id_table, + .remove = st_nci_i2c_remove, +}; + +module_i2c_driver(st_nci_i2c_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION(DRIVER_DESC); diff --git a/drivers/nfc/st-nci/ndlc.c b/drivers/nfc/st-nci/ndlc.c new file mode 100644 index 000000000..56c6a4cb4 --- /dev/null +++ b/drivers/nfc/st-nci/ndlc.c @@ -0,0 +1,313 @@ +/* + * Low Level Transport (NDLC) Driver for STMicroelectronics NFC Chip + * + * Copyright (C) 2014-2015 STMicroelectronics SAS. 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, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/sched.h> +#include <net/nfc/nci_core.h> + +#include "ndlc.h" +#include "st-nci.h" + +#define NDLC_TIMER_T1 100 +#define NDLC_TIMER_T1_WAIT 400 +#define NDLC_TIMER_T2 1200 + +#define PCB_TYPE_DATAFRAME 0x80 +#define PCB_TYPE_SUPERVISOR 0xc0 +#define PCB_TYPE_MASK PCB_TYPE_SUPERVISOR + +#define PCB_SYNC_ACK 0x20 +#define PCB_SYNC_NACK 0x10 +#define PCB_SYNC_WAIT 0x30 +#define PCB_SYNC_NOINFO 0x00 +#define PCB_SYNC_MASK PCB_SYNC_WAIT + +#define PCB_DATAFRAME_RETRANSMIT_YES 0x00 +#define PCB_DATAFRAME_RETRANSMIT_NO 0x04 +#define PCB_DATAFRAME_RETRANSMIT_MASK PCB_DATAFRAME_RETRANSMIT_NO + +#define PCB_SUPERVISOR_RETRANSMIT_YES 0x00 +#define PCB_SUPERVISOR_RETRANSMIT_NO 0x02 +#define PCB_SUPERVISOR_RETRANSMIT_MASK PCB_SUPERVISOR_RETRANSMIT_NO + +#define PCB_FRAME_CRC_INFO_PRESENT 0x08 +#define PCB_FRAME_CRC_INFO_NOTPRESENT 0x00 +#define PCB_FRAME_CRC_INFO_MASK PCB_FRAME_CRC_INFO_PRESENT + +#define NDLC_DUMP_SKB(info, skb) \ +do { \ + pr_debug("%s:\n", info); \ + print_hex_dump(KERN_DEBUG, "ndlc: ", DUMP_PREFIX_OFFSET, \ + 16, 1, skb->data, skb->len, 0); \ +} while (0) + +int ndlc_open(struct llt_ndlc *ndlc) +{ + /* toggle reset pin */ + ndlc->ops->enable(ndlc->phy_id); + ndlc->powered = 1; + return 0; +} +EXPORT_SYMBOL(ndlc_open); + +void ndlc_close(struct llt_ndlc *ndlc) +{ + struct nci_mode_set_cmd cmd; + + cmd.cmd_type = ST_NCI_SET_NFC_MODE; + cmd.mode = 0; + + /* toggle reset pin */ + ndlc->ops->enable(ndlc->phy_id); + + nci_prop_cmd(ndlc->ndev, ST_NCI_CORE_PROP, + sizeof(struct nci_mode_set_cmd), (__u8 *)&cmd); + + ndlc->powered = 0; + ndlc->ops->disable(ndlc->phy_id); +} +EXPORT_SYMBOL(ndlc_close); + +int ndlc_send(struct llt_ndlc *ndlc, struct sk_buff *skb) +{ + /* add ndlc header */ + u8 pcb = PCB_TYPE_DATAFRAME | PCB_DATAFRAME_RETRANSMIT_NO | + PCB_FRAME_CRC_INFO_NOTPRESENT; + + *skb_push(skb, 1) = pcb; + skb_queue_tail(&ndlc->send_q, skb); + + schedule_work(&ndlc->sm_work); + + return 0; +} +EXPORT_SYMBOL(ndlc_send); + +static void llt_ndlc_send_queue(struct llt_ndlc *ndlc) +{ + struct sk_buff *skb; + int r; + unsigned long time_sent; + + if (ndlc->send_q.qlen) + pr_debug("sendQlen=%d unackQlen=%d\n", + ndlc->send_q.qlen, ndlc->ack_pending_q.qlen); + + while (ndlc->send_q.qlen) { + skb = skb_dequeue(&ndlc->send_q); + NDLC_DUMP_SKB("ndlc frame written", skb); + r = ndlc->ops->write(ndlc->phy_id, skb); + if (r < 0) { + ndlc->hard_fault = r; + break; + } + time_sent = jiffies; + *(unsigned long *)skb->cb = time_sent; + + skb_queue_tail(&ndlc->ack_pending_q, skb); + + /* start timer t1 for ndlc aknowledge */ + ndlc->t1_active = true; + mod_timer(&ndlc->t1_timer, time_sent + + msecs_to_jiffies(NDLC_TIMER_T1)); + /* start timer t2 for chip availability */ + ndlc->t2_active = true; + mod_timer(&ndlc->t2_timer, time_sent + + msecs_to_jiffies(NDLC_TIMER_T2)); + } +} + +static void llt_ndlc_requeue_data_pending(struct llt_ndlc *ndlc) +{ + struct sk_buff *skb; + u8 pcb; + + while ((skb = skb_dequeue_tail(&ndlc->ack_pending_q))) { + pcb = skb->data[0]; + switch (pcb & PCB_TYPE_MASK) { + case PCB_TYPE_SUPERVISOR: + skb->data[0] = (pcb & ~PCB_SUPERVISOR_RETRANSMIT_MASK) | + PCB_SUPERVISOR_RETRANSMIT_YES; + break; + case PCB_TYPE_DATAFRAME: + skb->data[0] = (pcb & ~PCB_DATAFRAME_RETRANSMIT_MASK) | + PCB_DATAFRAME_RETRANSMIT_YES; + break; + default: + pr_err("UNKNOWN Packet Control Byte=%d\n", pcb); + kfree_skb(skb); + continue; + } + skb_queue_head(&ndlc->send_q, skb); + } +} + +static void llt_ndlc_rcv_queue(struct llt_ndlc *ndlc) +{ + struct sk_buff *skb; + u8 pcb; + unsigned long time_sent; + + if (ndlc->rcv_q.qlen) + pr_debug("rcvQlen=%d\n", ndlc->rcv_q.qlen); + + while ((skb = skb_dequeue(&ndlc->rcv_q)) != NULL) { + pcb = skb->data[0]; + skb_pull(skb, 1); + if ((pcb & PCB_TYPE_MASK) == PCB_TYPE_SUPERVISOR) { + switch (pcb & PCB_SYNC_MASK) { + case PCB_SYNC_ACK: + del_timer_sync(&ndlc->t1_timer); + del_timer_sync(&ndlc->t2_timer); + ndlc->t2_active = false; + ndlc->t1_active = false; + break; + case PCB_SYNC_NACK: + llt_ndlc_requeue_data_pending(ndlc); + llt_ndlc_send_queue(ndlc); + /* start timer t1 for ndlc aknowledge */ + time_sent = jiffies; + ndlc->t1_active = true; + mod_timer(&ndlc->t1_timer, time_sent + + msecs_to_jiffies(NDLC_TIMER_T1)); + break; + case PCB_SYNC_WAIT: + time_sent = jiffies; + ndlc->t1_active = true; + mod_timer(&ndlc->t1_timer, time_sent + + msecs_to_jiffies(NDLC_TIMER_T1_WAIT)); + break; + default: + pr_err("UNKNOWN Packet Control Byte=%d\n", pcb); + kfree_skb(skb); + break; + } + } else { + nci_recv_frame(ndlc->ndev, skb); + } + } +} + +static void llt_ndlc_sm_work(struct work_struct *work) +{ + struct llt_ndlc *ndlc = container_of(work, struct llt_ndlc, sm_work); + + llt_ndlc_send_queue(ndlc); + llt_ndlc_rcv_queue(ndlc); + + if (ndlc->t1_active && timer_pending(&ndlc->t1_timer) == 0) { + pr_debug + ("Handle T1(recv SUPERVISOR) elapsed (T1 now inactive)\n"); + ndlc->t1_active = false; + + llt_ndlc_requeue_data_pending(ndlc); + llt_ndlc_send_queue(ndlc); + } + + if (ndlc->t2_active && timer_pending(&ndlc->t2_timer) == 0) { + pr_debug("Handle T2(recv DATA) elapsed (T2 now inactive)\n"); + ndlc->t2_active = false; + ndlc->t1_active = false; + del_timer_sync(&ndlc->t1_timer); + del_timer_sync(&ndlc->t2_timer); + ndlc_close(ndlc); + ndlc->hard_fault = -EREMOTEIO; + } +} + +void ndlc_recv(struct llt_ndlc *ndlc, struct sk_buff *skb) +{ + if (skb == NULL) { + pr_err("NULL Frame -> link is dead\n"); + ndlc->hard_fault = -EREMOTEIO; + ndlc_close(ndlc); + } else { + NDLC_DUMP_SKB("incoming frame", skb); + skb_queue_tail(&ndlc->rcv_q, skb); + } + + schedule_work(&ndlc->sm_work); +} +EXPORT_SYMBOL(ndlc_recv); + +static void ndlc_t1_timeout(unsigned long data) +{ + struct llt_ndlc *ndlc = (struct llt_ndlc *)data; + + pr_debug("\n"); + + schedule_work(&ndlc->sm_work); +} + +static void ndlc_t2_timeout(unsigned long data) +{ + struct llt_ndlc *ndlc = (struct llt_ndlc *)data; + + pr_debug("\n"); + + schedule_work(&ndlc->sm_work); +} + +int ndlc_probe(void *phy_id, struct nfc_phy_ops *phy_ops, struct device *dev, + int phy_headroom, int phy_tailroom, struct llt_ndlc **ndlc_id) +{ + struct llt_ndlc *ndlc; + + ndlc = devm_kzalloc(dev, sizeof(struct llt_ndlc), GFP_KERNEL); + if (!ndlc) + return -ENOMEM; + + ndlc->ops = phy_ops; + ndlc->phy_id = phy_id; + ndlc->dev = dev; + ndlc->powered = 0; + + *ndlc_id = ndlc; + + /* initialize timers */ + init_timer(&ndlc->t1_timer); + ndlc->t1_timer.data = (unsigned long)ndlc; + ndlc->t1_timer.function = ndlc_t1_timeout; + + init_timer(&ndlc->t2_timer); + ndlc->t2_timer.data = (unsigned long)ndlc; + ndlc->t2_timer.function = ndlc_t2_timeout; + + skb_queue_head_init(&ndlc->rcv_q); + skb_queue_head_init(&ndlc->send_q); + skb_queue_head_init(&ndlc->ack_pending_q); + + INIT_WORK(&ndlc->sm_work, llt_ndlc_sm_work); + + return st_nci_probe(ndlc, phy_headroom, phy_tailroom); +} +EXPORT_SYMBOL(ndlc_probe); + +void ndlc_remove(struct llt_ndlc *ndlc) +{ + st_nci_remove(ndlc->ndev); + + /* cancel timers */ + del_timer_sync(&ndlc->t1_timer); + del_timer_sync(&ndlc->t2_timer); + ndlc->t2_active = false; + ndlc->t1_active = false; + + skb_queue_purge(&ndlc->rcv_q); + skb_queue_purge(&ndlc->send_q); +} +EXPORT_SYMBOL(ndlc_remove); diff --git a/drivers/nfc/st-nci/ndlc.h b/drivers/nfc/st-nci/ndlc.h new file mode 100644 index 000000000..6361005ef --- /dev/null +++ b/drivers/nfc/st-nci/ndlc.h @@ -0,0 +1,60 @@ +/* + * NCI based Driver for STMicroelectronics NFC Chip + * + * Copyright (C) 2014-2015 STMicroelectronics SAS. 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __LOCAL_NDLC_H_ +#define __LOCAL_NDLC_H_ + +#include <linux/skbuff.h> +#include <net/nfc/nfc.h> + +/* Low Level Transport description */ +struct llt_ndlc { + struct nci_dev *ndev; + struct nfc_phy_ops *ops; + void *phy_id; + + struct timer_list t1_timer; + bool t1_active; + + struct timer_list t2_timer; + bool t2_active; + + struct sk_buff_head rcv_q; + struct sk_buff_head send_q; + struct sk_buff_head ack_pending_q; + + struct work_struct sm_work; + + struct device *dev; + + /* + * < 0 if hardware error occurred + * and prevents normal operation. + */ + int hard_fault; + int powered; +}; + +int ndlc_open(struct llt_ndlc *ndlc); +void ndlc_close(struct llt_ndlc *ndlc); +int ndlc_send(struct llt_ndlc *ndlc, struct sk_buff *skb); +void ndlc_recv(struct llt_ndlc *ndlc, struct sk_buff *skb); +int ndlc_probe(void *phy_id, struct nfc_phy_ops *phy_ops, struct device *dev, + int phy_headroom, int phy_tailroom, struct llt_ndlc **ndlc_id); +void ndlc_remove(struct llt_ndlc *ndlc); +#endif /* __LOCAL_NDLC_H__ */ diff --git a/drivers/nfc/st-nci/st-nci.h b/drivers/nfc/st-nci/st-nci.h new file mode 100644 index 000000000..850a2395d --- /dev/null +++ b/drivers/nfc/st-nci/st-nci.h @@ -0,0 +1,50 @@ +/* + * NCI based Driver for STMicroelectronics NFC Chip + * + * Copyright (C) 2014 STMicroelectronics SAS. 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __LOCAL_ST_NCI_H_ +#define __LOCAL_ST_NCI_H_ + +#include "st-nci_se.h" +#include "ndlc.h" + +/* Define private flags: */ +#define ST_NCI_RUNNING 1 + +#define ST_NCI_CORE_PROP 0x01 +#define ST_NCI_SET_NFC_MODE 0x02 + +struct nci_mode_set_cmd { + u8 cmd_type; + u8 mode; +} __packed; + +struct nci_mode_set_rsp { + u8 status; +} __packed; + +struct st_nci_info { + struct llt_ndlc *ndlc; + unsigned long flags; + struct st_nci_se_info se_info; +}; + +void st_nci_remove(struct nci_dev *ndev); +int st_nci_probe(struct llt_ndlc *ndlc, int phy_headroom, + int phy_tailroom); + +#endif /* __LOCAL_ST_NCI_H_ */ diff --git a/drivers/nfc/st-nci/st-nci_se.c b/drivers/nfc/st-nci/st-nci_se.c new file mode 100644 index 000000000..97addfa96 --- /dev/null +++ b/drivers/nfc/st-nci/st-nci_se.c @@ -0,0 +1,714 @@ +/* + * Secure Element driver for STMicroelectronics NFC NCI chip + * + * Copyright (C) 2014-2015 STMicroelectronics SAS. 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, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/module.h> +#include <linux/nfc.h> +#include <linux/delay.h> +#include <net/nfc/nci.h> +#include <net/nfc/nci_core.h> + +#include "st-nci.h" +#include "st-nci_se.h" + +struct st_nci_pipe_info { + u8 pipe_state; + u8 src_host_id; + u8 src_gate_id; + u8 dst_host_id; + u8 dst_gate_id; +} __packed; + +/* Hosts */ +#define ST_NCI_HOST_CONTROLLER_ID 0x00 +#define ST_NCI_TERMINAL_HOST_ID 0x01 +#define ST_NCI_UICC_HOST_ID 0x02 +#define ST_NCI_ESE_HOST_ID 0xc0 + +/* Gates */ +#define ST_NCI_DEVICE_MGNT_GATE 0x01 +#define ST_NCI_APDU_READER_GATE 0xf0 +#define ST_NCI_CONNECTIVITY_GATE 0x41 + +/* Pipes */ +#define ST_NCI_DEVICE_MGNT_PIPE 0x02 + +/* Connectivity pipe only */ +#define ST_NCI_SE_COUNT_PIPE_UICC 0x01 +/* Connectivity + APDU Reader pipe */ +#define ST_NCI_SE_COUNT_PIPE_EMBEDDED 0x02 + +#define ST_NCI_SE_TO_HOT_PLUG 1000 /* msecs */ +#define ST_NCI_SE_TO_PIPES 2000 + +#define ST_NCI_EVT_HOT_PLUG_IS_INHIBITED(x) (x->data[0] & 0x80) + +#define NCI_HCI_APDU_PARAM_ATR 0x01 +#define NCI_HCI_ADMIN_PARAM_SESSION_IDENTITY 0x01 +#define NCI_HCI_ADMIN_PARAM_WHITELIST 0x03 +#define NCI_HCI_ADMIN_PARAM_HOST_LIST 0x04 + +#define ST_NCI_EVT_SE_HARD_RESET 0x20 +#define ST_NCI_EVT_TRANSMIT_DATA 0x10 +#define ST_NCI_EVT_WTX_REQUEST 0x11 +#define ST_NCI_EVT_SE_SOFT_RESET 0x11 +#define ST_NCI_EVT_SE_END_OF_APDU_TRANSFER 0x21 +#define ST_NCI_EVT_HOT_PLUG 0x03 + +#define ST_NCI_SE_MODE_OFF 0x00 +#define ST_NCI_SE_MODE_ON 0x01 + +#define ST_NCI_EVT_CONNECTIVITY 0x10 +#define ST_NCI_EVT_TRANSACTION 0x12 + +#define ST_NCI_DM_GETINFO 0x13 +#define ST_NCI_DM_GETINFO_PIPE_LIST 0x02 +#define ST_NCI_DM_GETINFO_PIPE_INFO 0x01 +#define ST_NCI_DM_PIPE_CREATED 0x02 +#define ST_NCI_DM_PIPE_OPEN 0x04 +#define ST_NCI_DM_RF_ACTIVE 0x80 +#define ST_NCI_DM_DISCONNECT 0x30 + +#define ST_NCI_DM_IS_PIPE_OPEN(p) \ + ((p & 0x0f) == (ST_NCI_DM_PIPE_CREATED | ST_NCI_DM_PIPE_OPEN)) + +#define ST_NCI_ATR_DEFAULT_BWI 0x04 + +/* + * WT = 2^BWI/10[s], convert into msecs and add a secure + * room by increasing by 2 this timeout + */ +#define ST_NCI_BWI_TO_TIMEOUT(x) ((1 << x) * 200) +#define ST_NCI_ATR_GET_Y_FROM_TD(x) (x >> 4) + +/* If TA is present bit 0 is set */ +#define ST_NCI_ATR_TA_PRESENT(x) (x & 0x01) +/* If TB is present bit 1 is set */ +#define ST_NCI_ATR_TB_PRESENT(x) (x & 0x02) + +#define ST_NCI_NUM_DEVICES 256 + +static DECLARE_BITMAP(dev_mask, ST_NCI_NUM_DEVICES); + +/* Here are the mandatory pipe for st_nci */ +static struct nci_hci_gate st_nci_gates[] = { + {NCI_HCI_ADMIN_GATE, NCI_HCI_ADMIN_PIPE, + ST_NCI_HOST_CONTROLLER_ID}, + {NCI_HCI_LINK_MGMT_GATE, NCI_HCI_LINK_MGMT_PIPE, + ST_NCI_HOST_CONTROLLER_ID}, + {ST_NCI_DEVICE_MGNT_GATE, ST_NCI_DEVICE_MGNT_PIPE, + ST_NCI_HOST_CONTROLLER_ID}, + + /* Secure element pipes are created by secure element host */ + {ST_NCI_CONNECTIVITY_GATE, NCI_HCI_DO_NOT_OPEN_PIPE, + ST_NCI_HOST_CONTROLLER_ID}, + {ST_NCI_APDU_READER_GATE, NCI_HCI_DO_NOT_OPEN_PIPE, + ST_NCI_HOST_CONTROLLER_ID}, +}; + +static u8 st_nci_se_get_bwi(struct nci_dev *ndev) +{ + int i; + u8 td; + struct st_nci_info *info = nci_get_drvdata(ndev); + + /* Bits 8 to 5 of the first TB for T=1 encode BWI from zero to nine */ + for (i = 1; i < ST_NCI_ESE_MAX_LENGTH; i++) { + td = ST_NCI_ATR_GET_Y_FROM_TD(info->se_info.atr[i]); + if (ST_NCI_ATR_TA_PRESENT(td)) + i++; + if (ST_NCI_ATR_TB_PRESENT(td)) { + i++; + return info->se_info.atr[i] >> 4; + } + } + return ST_NCI_ATR_DEFAULT_BWI; +} + +static void st_nci_se_get_atr(struct nci_dev *ndev) +{ + struct st_nci_info *info = nci_get_drvdata(ndev); + int r; + struct sk_buff *skb; + + r = nci_hci_get_param(ndev, ST_NCI_APDU_READER_GATE, + NCI_HCI_APDU_PARAM_ATR, &skb); + if (r < 0) + return; + + if (skb->len <= ST_NCI_ESE_MAX_LENGTH) { + memcpy(info->se_info.atr, skb->data, skb->len); + + info->se_info.wt_timeout = + ST_NCI_BWI_TO_TIMEOUT(st_nci_se_get_bwi(ndev)); + } + kfree_skb(skb); +} + +int st_nci_hci_load_session(struct nci_dev *ndev) +{ + int i, j, r; + struct sk_buff *skb_pipe_list, *skb_pipe_info; + struct st_nci_pipe_info *dm_pipe_info; + u8 pipe_list[] = { ST_NCI_DM_GETINFO_PIPE_LIST, + ST_NCI_TERMINAL_HOST_ID}; + u8 pipe_info[] = { ST_NCI_DM_GETINFO_PIPE_INFO, + ST_NCI_TERMINAL_HOST_ID, 0}; + + /* On ST_NCI device pipes number are dynamics + * If pipes are already created, hci_dev_up will fail. + * Doing a clear all pipe is a bad idea because: + * - It does useless EEPROM cycling + * - It might cause issue for secure elements support + * (such as removing connectivity or APDU reader pipe) + * A better approach on ST_NCI is to: + * - get a pipe list for each host. + * (eg: ST_NCI_HOST_CONTROLLER_ID for now). + * (TODO Later on UICC HOST and eSE HOST) + * - get pipe information + * - match retrieved pipe list in st_nci_gates + * ST_NCI_DEVICE_MGNT_GATE is a proprietary gate + * with ST_NCI_DEVICE_MGNT_PIPE. + * Pipe can be closed and need to be open. + */ + r = nci_hci_connect_gate(ndev, ST_NCI_HOST_CONTROLLER_ID, + ST_NCI_DEVICE_MGNT_GATE, + ST_NCI_DEVICE_MGNT_PIPE); + if (r < 0) + goto free_info; + + /* Get pipe list */ + r = nci_hci_send_cmd(ndev, ST_NCI_DEVICE_MGNT_GATE, + ST_NCI_DM_GETINFO, pipe_list, sizeof(pipe_list), + &skb_pipe_list); + if (r < 0) + goto free_info; + + /* Complete the existing gate_pipe table */ + for (i = 0; i < skb_pipe_list->len; i++) { + pipe_info[2] = skb_pipe_list->data[i]; + r = nci_hci_send_cmd(ndev, ST_NCI_DEVICE_MGNT_GATE, + ST_NCI_DM_GETINFO, pipe_info, + sizeof(pipe_info), &skb_pipe_info); + + if (r) + continue; + + /* + * Match pipe ID and gate ID + * Output format from ST21NFC_DM_GETINFO is: + * - pipe state (1byte) + * - source hid (1byte) + * - source gid (1byte) + * - destination hid (1byte) + * - destination gid (1byte) + */ + dm_pipe_info = (struct st_nci_pipe_info *)skb_pipe_info->data; + if (dm_pipe_info->dst_gate_id == ST_NCI_APDU_READER_GATE && + dm_pipe_info->src_host_id != ST_NCI_ESE_HOST_ID) { + pr_err("Unexpected apdu_reader pipe on host %x\n", + dm_pipe_info->src_host_id); + continue; + } + + for (j = 0; (j < ARRAY_SIZE(st_nci_gates)) && + (st_nci_gates[j].gate != dm_pipe_info->dst_gate_id); j++) + ; + + if (j < ARRAY_SIZE(st_nci_gates) && + st_nci_gates[j].gate == dm_pipe_info->dst_gate_id && + ST_NCI_DM_IS_PIPE_OPEN(dm_pipe_info->pipe_state)) { + st_nci_gates[j].pipe = pipe_info[2]; + + ndev->hci_dev->gate2pipe[st_nci_gates[j].gate] = + st_nci_gates[j].pipe; + ndev->hci_dev->pipes[st_nci_gates[j].pipe].gate = + st_nci_gates[j].gate; + ndev->hci_dev->pipes[st_nci_gates[j].pipe].host = + dm_pipe_info->src_host_id; + } + } + + memcpy(ndev->hci_dev->init_data.gates, st_nci_gates, + sizeof(st_nci_gates)); + +free_info: + kfree_skb(skb_pipe_info); + kfree_skb(skb_pipe_list); + return r; +} +EXPORT_SYMBOL_GPL(st_nci_hci_load_session); + +static void st_nci_hci_admin_event_received(struct nci_dev *ndev, + u8 event, struct sk_buff *skb) +{ + struct st_nci_info *info = nci_get_drvdata(ndev); + + switch (event) { + case ST_NCI_EVT_HOT_PLUG: + if (info->se_info.se_active) { + if (!ST_NCI_EVT_HOT_PLUG_IS_INHIBITED(skb)) { + del_timer_sync(&info->se_info.se_active_timer); + info->se_info.se_active = false; + complete(&info->se_info.req_completion); + } else { + mod_timer(&info->se_info.se_active_timer, + jiffies + + msecs_to_jiffies(ST_NCI_SE_TO_PIPES)); + } + } + break; + } +} + +static int st_nci_hci_apdu_reader_event_received(struct nci_dev *ndev, + u8 event, + struct sk_buff *skb) +{ + int r = 0; + struct st_nci_info *info = nci_get_drvdata(ndev); + + pr_debug("apdu reader gate event: %x\n", event); + + switch (event) { + case ST_NCI_EVT_TRANSMIT_DATA: + del_timer_sync(&info->se_info.bwi_timer); + info->se_info.bwi_active = false; + info->se_info.cb(info->se_info.cb_context, + skb->data, skb->len, 0); + break; + case ST_NCI_EVT_WTX_REQUEST: + mod_timer(&info->se_info.bwi_timer, jiffies + + msecs_to_jiffies(info->se_info.wt_timeout)); + break; + } + + kfree_skb(skb); + return r; +} + +/* + * Returns: + * <= 0: driver handled the event, skb consumed + * 1: driver does not handle the event, please do standard processing + */ +static int st_nci_hci_connectivity_event_received(struct nci_dev *ndev, + u8 host, u8 event, + struct sk_buff *skb) +{ + int r = 0; + struct device *dev = &ndev->nfc_dev->dev; + struct nfc_evt_transaction *transaction; + + pr_debug("connectivity gate event: %x\n", event); + + switch (event) { + case ST_NCI_EVT_CONNECTIVITY: + + break; + case ST_NCI_EVT_TRANSACTION: + /* According to specification etsi 102 622 + * 11.2.2.4 EVT_TRANSACTION Table 52 + * Description Tag Length + * AID 81 5 to 16 + * PARAMETERS 82 0 to 255 + */ + if (skb->len < NFC_MIN_AID_LENGTH + 2 && + skb->data[0] != NFC_EVT_TRANSACTION_AID_TAG) + return -EPROTO; + + transaction = (struct nfc_evt_transaction *)devm_kzalloc(dev, + skb->len - 2, GFP_KERNEL); + + transaction->aid_len = skb->data[1]; + memcpy(transaction->aid, &skb->data[2], transaction->aid_len); + + /* Check next byte is PARAMETERS tag (82) */ + if (skb->data[transaction->aid_len + 2] != + NFC_EVT_TRANSACTION_PARAMS_TAG) + return -EPROTO; + + transaction->params_len = skb->data[transaction->aid_len + 3]; + memcpy(transaction->params, skb->data + + transaction->aid_len + 4, transaction->params_len); + + r = nfc_se_transaction(ndev->nfc_dev, host, transaction); + break; + default: + return 1; + } + kfree_skb(skb); + return r; +} + +void st_nci_hci_event_received(struct nci_dev *ndev, u8 pipe, + u8 event, struct sk_buff *skb) +{ + u8 gate = ndev->hci_dev->pipes[pipe].gate; + u8 host = ndev->hci_dev->pipes[pipe].host; + + switch (gate) { + case NCI_HCI_ADMIN_GATE: + st_nci_hci_admin_event_received(ndev, event, skb); + break; + case ST_NCI_APDU_READER_GATE: + st_nci_hci_apdu_reader_event_received(ndev, event, skb); + break; + case ST_NCI_CONNECTIVITY_GATE: + st_nci_hci_connectivity_event_received(ndev, host, event, + skb); + break; + } +} +EXPORT_SYMBOL_GPL(st_nci_hci_event_received); + + +void st_nci_hci_cmd_received(struct nci_dev *ndev, u8 pipe, u8 cmd, + struct sk_buff *skb) +{ + struct st_nci_info *info = nci_get_drvdata(ndev); + u8 gate = ndev->hci_dev->pipes[pipe].gate; + + pr_debug("cmd: %x\n", cmd); + + switch (cmd) { + case NCI_HCI_ANY_OPEN_PIPE: + if (gate != ST_NCI_APDU_READER_GATE && + ndev->hci_dev->pipes[pipe].host != ST_NCI_UICC_HOST_ID) + ndev->hci_dev->count_pipes++; + + if (ndev->hci_dev->count_pipes == + ndev->hci_dev->expected_pipes) { + del_timer_sync(&info->se_info.se_active_timer); + info->se_info.se_active = false; + ndev->hci_dev->count_pipes = 0; + complete(&info->se_info.req_completion); + } + break; + } +} +EXPORT_SYMBOL_GPL(st_nci_hci_cmd_received); + +/* + * Remarks: On some early st_nci firmware, nci_nfcee_mode_set(0) + * is rejected + */ +static int st_nci_control_se(struct nci_dev *ndev, u8 se_idx, + u8 state) +{ + struct st_nci_info *info = nci_get_drvdata(ndev); + int r; + struct sk_buff *sk_host_list; + u8 host_id; + + switch (se_idx) { + case ST_NCI_UICC_HOST_ID: + ndev->hci_dev->count_pipes = 0; + ndev->hci_dev->expected_pipes = ST_NCI_SE_COUNT_PIPE_UICC; + break; + case ST_NCI_ESE_HOST_ID: + ndev->hci_dev->count_pipes = 0; + ndev->hci_dev->expected_pipes = ST_NCI_SE_COUNT_PIPE_EMBEDDED; + break; + default: + return -EINVAL; + } + + /* + * Wait for an EVT_HOT_PLUG in order to + * retrieve a relevant host list. + */ + reinit_completion(&info->se_info.req_completion); + r = nci_nfcee_mode_set(ndev, se_idx, NCI_NFCEE_ENABLE); + if (r != NCI_STATUS_OK) + return r; + + mod_timer(&info->se_info.se_active_timer, jiffies + + msecs_to_jiffies(ST_NCI_SE_TO_HOT_PLUG)); + info->se_info.se_active = true; + + /* Ignore return value and check in any case the host_list */ + wait_for_completion_interruptible(&info->se_info.req_completion); + + /* There might be some "collision" after receiving a HOT_PLUG event + * This may cause the CLF to not answer to the next hci command. + * There is no possible synchronization to prevent this. + * Adding a small delay is the only way to solve the issue. + */ + usleep_range(3000, 5000); + + r = nci_hci_get_param(ndev, NCI_HCI_ADMIN_GATE, + NCI_HCI_ADMIN_PARAM_HOST_LIST, &sk_host_list); + if (r != NCI_HCI_ANY_OK) + return r; + + host_id = sk_host_list->data[sk_host_list->len - 1]; + kfree_skb(sk_host_list); + if (state == ST_NCI_SE_MODE_ON && host_id == se_idx) + return se_idx; + else if (state == ST_NCI_SE_MODE_OFF && host_id != se_idx) + return se_idx; + + return -1; +} + +int st_nci_disable_se(struct nci_dev *ndev, u32 se_idx) +{ + int r; + + pr_debug("st_nci_disable_se\n"); + + if (se_idx == NFC_SE_EMBEDDED) { + r = nci_hci_send_event(ndev, ST_NCI_APDU_READER_GATE, + ST_NCI_EVT_SE_END_OF_APDU_TRANSFER, NULL, 0); + if (r < 0) + return r; + } + + return 0; +} +EXPORT_SYMBOL_GPL(st_nci_disable_se); + +int st_nci_enable_se(struct nci_dev *ndev, u32 se_idx) +{ + int r; + + pr_debug("st_nci_enable_se\n"); + + if (se_idx == ST_NCI_HCI_HOST_ID_ESE) { + r = nci_hci_send_event(ndev, ST_NCI_APDU_READER_GATE, + ST_NCI_EVT_SE_SOFT_RESET, NULL, 0); + if (r < 0) + return r; + } + + return 0; +} +EXPORT_SYMBOL_GPL(st_nci_enable_se); + +static int st_nci_hci_network_init(struct nci_dev *ndev) +{ + struct core_conn_create_dest_spec_params *dest_params; + struct dest_spec_params spec_params; + struct nci_conn_info *conn_info; + int r, dev_num; + + dest_params = + kzalloc(sizeof(struct core_conn_create_dest_spec_params) + + sizeof(struct dest_spec_params), GFP_KERNEL); + if (dest_params == NULL) { + r = -ENOMEM; + goto exit; + } + + dest_params->type = NCI_DESTINATION_SPECIFIC_PARAM_NFCEE_TYPE; + dest_params->length = sizeof(struct dest_spec_params); + spec_params.id = ndev->hci_dev->nfcee_id; + spec_params.protocol = NCI_NFCEE_INTERFACE_HCI_ACCESS; + memcpy(dest_params->value, &spec_params, + sizeof(struct dest_spec_params)); + r = nci_core_conn_create(ndev, NCI_DESTINATION_NFCEE, 1, + sizeof(struct core_conn_create_dest_spec_params) + + sizeof(struct dest_spec_params), + dest_params); + if (r != NCI_STATUS_OK) + goto free_dest_params; + + conn_info = ndev->hci_dev->conn_info; + if (!conn_info) + goto free_dest_params; + + memcpy(ndev->hci_dev->init_data.gates, st_nci_gates, + sizeof(st_nci_gates)); + + /* + * Session id must include the driver name + i2c bus addr + * persistent info to discriminate 2 identical chips + */ + dev_num = find_first_zero_bit(dev_mask, ST_NCI_NUM_DEVICES); + if (dev_num >= ST_NCI_NUM_DEVICES) { + r = -ENODEV; + goto free_dest_params; + } + + scnprintf(ndev->hci_dev->init_data.session_id, + sizeof(ndev->hci_dev->init_data.session_id), + "%s%2x", "ST21BH", dev_num); + + r = nci_hci_dev_session_init(ndev); + if (r != NCI_HCI_ANY_OK) + goto free_dest_params; + + r = nci_nfcee_mode_set(ndev, ndev->hci_dev->conn_info->id, + NCI_NFCEE_ENABLE); + if (r != NCI_STATUS_OK) + goto free_dest_params; + +free_dest_params: + kfree(dest_params); + +exit: + return r; +} + +int st_nci_discover_se(struct nci_dev *ndev) +{ + u8 param[2]; + int r; + int se_count = 0; + + pr_debug("st_nci_discover_se\n"); + + r = st_nci_hci_network_init(ndev); + if (r != 0) + return r; + + param[0] = ST_NCI_UICC_HOST_ID; + param[1] = ST_NCI_HCI_HOST_ID_ESE; + r = nci_hci_set_param(ndev, NCI_HCI_ADMIN_GATE, + NCI_HCI_ADMIN_PARAM_WHITELIST, + param, sizeof(param)); + if (r != NCI_HCI_ANY_OK) + return r; + + r = st_nci_control_se(ndev, ST_NCI_UICC_HOST_ID, + ST_NCI_SE_MODE_ON); + if (r == ST_NCI_UICC_HOST_ID) { + nfc_add_se(ndev->nfc_dev, ST_NCI_UICC_HOST_ID, NFC_SE_UICC); + se_count++; + } + + /* Try to enable eSE in order to check availability */ + r = st_nci_control_se(ndev, ST_NCI_HCI_HOST_ID_ESE, + ST_NCI_SE_MODE_ON); + if (r == ST_NCI_HCI_HOST_ID_ESE) { + nfc_add_se(ndev->nfc_dev, ST_NCI_HCI_HOST_ID_ESE, + NFC_SE_EMBEDDED); + se_count++; + st_nci_se_get_atr(ndev); + } + + return !se_count; +} +EXPORT_SYMBOL_GPL(st_nci_discover_se); + +int st_nci_se_io(struct nci_dev *ndev, u32 se_idx, + u8 *apdu, size_t apdu_length, + se_io_cb_t cb, void *cb_context) +{ + struct st_nci_info *info = nci_get_drvdata(ndev); + + pr_debug("\n"); + + switch (se_idx) { + case ST_NCI_HCI_HOST_ID_ESE: + info->se_info.cb = cb; + info->se_info.cb_context = cb_context; + mod_timer(&info->se_info.bwi_timer, jiffies + + msecs_to_jiffies(info->se_info.wt_timeout)); + info->se_info.bwi_active = true; + return nci_hci_send_event(ndev, ST_NCI_APDU_READER_GATE, + ST_NCI_EVT_TRANSMIT_DATA, apdu, + apdu_length); + default: + return -ENODEV; + } +} +EXPORT_SYMBOL(st_nci_se_io); + +static void st_nci_se_wt_timeout(unsigned long data) +{ + /* + * No answer from the secure element + * within the defined timeout. + * Let's send a reset request as recovery procedure. + * According to the situation, we first try to send a software reset + * to the secure element. If the next command is still not + * answering in time, we send to the CLF a secure element hardware + * reset request. + */ + /* hardware reset managed through VCC_UICC_OUT power supply */ + u8 param = 0x01; + struct st_nci_info *info = (struct st_nci_info *) data; + + pr_debug("\n"); + + info->se_info.bwi_active = false; + + if (!info->se_info.xch_error) { + info->se_info.xch_error = true; + nci_hci_send_event(info->ndlc->ndev, ST_NCI_APDU_READER_GATE, + ST_NCI_EVT_SE_SOFT_RESET, NULL, 0); + } else { + info->se_info.xch_error = false; + nci_hci_send_event(info->ndlc->ndev, ST_NCI_DEVICE_MGNT_GATE, + ST_NCI_EVT_SE_HARD_RESET, ¶m, 1); + } + info->se_info.cb(info->se_info.cb_context, NULL, 0, -ETIME); +} + +static void st_nci_se_activation_timeout(unsigned long data) +{ + struct st_nci_info *info = (struct st_nci_info *) data; + + pr_debug("\n"); + + info->se_info.se_active = false; + + complete(&info->se_info.req_completion); +} + +int st_nci_se_init(struct nci_dev *ndev) +{ + struct st_nci_info *info = nci_get_drvdata(ndev); + + init_completion(&info->se_info.req_completion); + /* initialize timers */ + init_timer(&info->se_info.bwi_timer); + info->se_info.bwi_timer.data = (unsigned long)info; + info->se_info.bwi_timer.function = st_nci_se_wt_timeout; + info->se_info.bwi_active = false; + + init_timer(&info->se_info.se_active_timer); + info->se_info.se_active_timer.data = (unsigned long)info; + info->se_info.se_active_timer.function = + st_nci_se_activation_timeout; + info->se_info.se_active = false; + + info->se_info.xch_error = false; + + info->se_info.wt_timeout = + ST_NCI_BWI_TO_TIMEOUT(ST_NCI_ATR_DEFAULT_BWI); + + return 0; +} +EXPORT_SYMBOL(st_nci_se_init); + +void st_nci_se_deinit(struct nci_dev *ndev) +{ + struct st_nci_info *info = nci_get_drvdata(ndev); + + if (info->se_info.bwi_active) + del_timer_sync(&info->se_info.bwi_timer); + if (info->se_info.se_active) + del_timer_sync(&info->se_info.se_active_timer); + + info->se_info.se_active = false; + info->se_info.bwi_active = false; +} +EXPORT_SYMBOL(st_nci_se_deinit); + diff --git a/drivers/nfc/st-nci/st-nci_se.h b/drivers/nfc/st-nci/st-nci_se.h new file mode 100644 index 000000000..ea66e879d --- /dev/null +++ b/drivers/nfc/st-nci/st-nci_se.h @@ -0,0 +1,61 @@ +/* + * Secure Element Driver for STMicroelectronics NFC NCI Chip + * + * Copyright (C) 2014-2015 STMicroelectronics SAS. 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, see <http://www.gnu.org/licenses/>. + */ +#ifndef __LOCAL_ST_NCI_SE_H_ +#define __LOCAL_ST_NCI_SE_H_ + +/* + * ref ISO7816-3 chap 8.1. the initial character TS is followed by a + * sequence of at most 32 characters. + */ +#define ST_NCI_ESE_MAX_LENGTH 33 +#define ST_NCI_HCI_HOST_ID_ESE 0xc0 + +struct st_nci_se_info { + u8 atr[ST_NCI_ESE_MAX_LENGTH]; + struct completion req_completion; + + struct timer_list bwi_timer; + int wt_timeout; /* in msecs */ + bool bwi_active; + + struct timer_list se_active_timer; + bool se_active; + + bool xch_error; + + se_io_cb_t cb; + void *cb_context; +}; + +int st_nci_se_init(struct nci_dev *ndev); +void st_nci_se_deinit(struct nci_dev *ndev); + +int st_nci_discover_se(struct nci_dev *ndev); +int st_nci_enable_se(struct nci_dev *ndev, u32 se_idx); +int st_nci_disable_se(struct nci_dev *ndev, u32 se_idx); +int st_nci_se_io(struct nci_dev *ndev, u32 se_idx, + u8 *apdu, size_t apdu_length, + se_io_cb_t cb, void *cb_context); +int st_nci_hci_load_session(struct nci_dev *ndev); +void st_nci_hci_event_received(struct nci_dev *ndev, u8 pipe, + u8 event, struct sk_buff *skb); +void st_nci_hci_cmd_received(struct nci_dev *ndev, u8 pipe, u8 cmd, + struct sk_buff *skb); + + +#endif /* __LOCAL_ST_NCI_SE_H_ */ |