From b4b7ff4b08e691656c9d77c758fc355833128ac0 Mon Sep 17 00:00:00 2001 From: AndrĂ© Fabian Silva Delgado Date: Wed, 20 Jan 2016 14:01:31 -0300 Subject: Linux-libre 4.4-gnu --- drivers/nfc/st-nci/Makefile | 4 +- drivers/nfc/st-nci/core.c | 20 +- drivers/nfc/st-nci/i2c.c | 35 +- drivers/nfc/st-nci/ndlc.c | 7 +- drivers/nfc/st-nci/ndlc.h | 5 +- drivers/nfc/st-nci/se.c | 774 +++++++++++++++++++++++++++++++++++++++ drivers/nfc/st-nci/spi.c | 34 +- drivers/nfc/st-nci/st-nci.h | 117 +++++- drivers/nfc/st-nci/st-nci_se.c | 714 ------------------------------------ drivers/nfc/st-nci/st-nci_se.h | 61 --- drivers/nfc/st-nci/vendor_cmds.c | 516 ++++++++++++++++++++++++++ 11 files changed, 1468 insertions(+), 819 deletions(-) create mode 100644 drivers/nfc/st-nci/se.c delete mode 100644 drivers/nfc/st-nci/st-nci_se.c delete mode 100644 drivers/nfc/st-nci/st-nci_se.h create mode 100644 drivers/nfc/st-nci/vendor_cmds.c (limited to 'drivers/nfc/st-nci') diff --git a/drivers/nfc/st-nci/Makefile b/drivers/nfc/st-nci/Makefile index 348ce76f2..439b2fa86 100644 --- a/drivers/nfc/st-nci/Makefile +++ b/drivers/nfc/st-nci/Makefile @@ -1,8 +1,8 @@ # -# Makefile for ST21NFCB NCI based NFC driver +# Makefile for ST_NCI NCI based NFC driver # -st-nci-objs = ndlc.o core.o st-nci_se.o +st-nci-objs = ndlc.o core.o se.o vendor_cmds.o obj-$(CONFIG_NFC_ST_NCI) += st-nci.o st-nci_i2c-objs = i2c.o diff --git a/drivers/nfc/st-nci/core.c b/drivers/nfc/st-nci/core.c index c419d3943..c693128ee 100644 --- a/drivers/nfc/st-nci/core.c +++ b/drivers/nfc/st-nci/core.c @@ -24,7 +24,6 @@ #include #include "st-nci.h" -#include "st-nci_se.h" #define DRIVER_DESC "NCI NFC driver for ST_NCI" @@ -98,7 +97,7 @@ static int st_nci_prop_rsp_packet(struct nci_dev *ndev, return 0; } -static struct nci_prop_ops st_nci_prop_ops[] = { +static struct nci_driver_ops st_nci_prop_ops[] = { { .opcode = nci_opcode_pack(NCI_GID_PROPRIETARY, ST_NCI_CORE_PROP), @@ -124,7 +123,7 @@ static struct nci_ops st_nci_ops = { }; int st_nci_probe(struct llt_ndlc *ndlc, int phy_headroom, - int phy_tailroom) + int phy_tailroom, struct st_nci_se_status *se_status) { struct st_nci_info *info; int r; @@ -153,14 +152,23 @@ int st_nci_probe(struct llt_ndlc *ndlc, int phy_headroom, nci_set_drvdata(ndlc->ndev, info); + r = st_nci_vendor_cmds_init(ndlc->ndev); + if (r) { + pr_err("Cannot register proprietary vendor cmds\n"); + goto err_reg_dev; + } + 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; + goto err_reg_dev; } - return st_nci_se_init(ndlc->ndev); + return st_nci_se_init(ndlc->ndev, se_status); + +err_reg_dev: + nci_free_device(ndlc->ndev); + return r; } EXPORT_SYMBOL_GPL(st_nci_probe); diff --git a/drivers/nfc/st-nci/i2c.c b/drivers/nfc/st-nci/i2c.c index 707ed2eb5..15e3ce2d2 100644 --- a/drivers/nfc/st-nci/i2c.c +++ b/drivers/nfc/st-nci/i2c.c @@ -27,12 +27,12 @@ #include #include -#include "ndlc.h" +#include "st-nci.h" #define DRIVER_DESC "NCI NFC driver for ST_NCI" /* ndlc header */ -#define ST_NCI_FRAME_HEADROOM 1 +#define ST_NCI_FRAME_HEADROOM 1 #define ST_NCI_FRAME_TAILROOM 0 #define ST_NCI_I2C_MIN_SIZE 4 /* PCB(1) + NCI Packet header(3) */ @@ -50,16 +50,13 @@ struct st_nci_i2c_phy { struct i2c_client *i2c_dev; struct llt_ndlc *ndlc; + bool irq_active; + 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) + struct st_nci_se_status se_status; +}; static int st_nci_i2c_enable(void *phy_id) { @@ -70,8 +67,10 @@ static int st_nci_i2c_enable(void *phy_id) gpio_set_value(phy->gpio_reset, 1); usleep_range(80000, 85000); - if (phy->ndlc->powered == 0) + if (phy->ndlc->powered == 0 && phy->irq_active == 0) { enable_irq(phy->i2c_dev->irq); + phy->irq_active = true; + } return 0; } @@ -81,6 +80,7 @@ static void st_nci_i2c_disable(void *phy_id) struct st_nci_i2c_phy *phy = phy_id; disable_irq_nosync(phy->i2c_dev->irq); + phy->irq_active = false; } /* @@ -94,8 +94,6 @@ static int st_nci_i2c_write(void *phy_id, struct sk_buff *skb) 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; @@ -166,8 +164,6 @@ static int st_nci_i2c_read(struct st_nci_i2c_phy *phy, skb_put(*skb, len); memcpy((*skb)->data + ST_NCI_I2C_MIN_SIZE, buf, len); - I2C_DUMP_SKB("i2c frame read", *skb); - return 0; } @@ -245,6 +241,11 @@ static int st_nci_i2c_of_request_resources(struct i2c_client *client) phy->irq_polarity = irq_get_trigger_type(client->irq); + phy->se_status.is_ese_present = + of_property_read_bool(pp, "ese-present"); + phy->se_status.is_uicc_present = + of_property_read_bool(pp, "uicc-present"); + return 0; } #else @@ -277,6 +278,9 @@ static int st_nci_i2c_request_resources(struct i2c_client *client) return r; } + phy->se_status.is_ese_present = pdata->is_ese_present; + phy->se_status.is_uicc_present = pdata->is_uicc_present; + return 0; } @@ -326,12 +330,13 @@ static int st_nci_i2c_probe(struct i2c_client *client, r = ndlc_probe(phy, &i2c_phy_ops, &client->dev, ST_NCI_FRAME_HEADROOM, ST_NCI_FRAME_TAILROOM, - &phy->ndlc); + &phy->ndlc, &phy->se_status); if (r < 0) { nfc_err(&client->dev, "Unable to register ndlc layer\n"); return r; } + phy->irq_active = true; r = devm_request_threaded_irq(&client->dev, client->irq, NULL, st_nci_irq_thread_fn, phy->irq_polarity | IRQF_ONESHOT, diff --git a/drivers/nfc/st-nci/ndlc.c b/drivers/nfc/st-nci/ndlc.c index d2cf84e68..0884b1100 100644 --- a/drivers/nfc/st-nci/ndlc.c +++ b/drivers/nfc/st-nci/ndlc.c @@ -19,8 +19,8 @@ #include #include -#include "ndlc.h" #include "st-nci.h" +#include "ndlc.h" #define NDLC_TIMER_T1 100 #define NDLC_TIMER_T1_WAIT 400 @@ -266,7 +266,8 @@ static void ndlc_t2_timeout(unsigned long data) } 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) + int phy_headroom, int phy_tailroom, struct llt_ndlc **ndlc_id, + struct st_nci_se_status *se_status) { struct llt_ndlc *ndlc; @@ -296,7 +297,7 @@ int ndlc_probe(void *phy_id, struct nfc_phy_ops *phy_ops, struct device *dev, INIT_WORK(&ndlc->sm_work, llt_ndlc_sm_work); - return st_nci_probe(ndlc, phy_headroom, phy_tailroom); + return st_nci_probe(ndlc, phy_headroom, phy_tailroom, se_status); } EXPORT_SYMBOL(ndlc_probe); diff --git a/drivers/nfc/st-nci/ndlc.h b/drivers/nfc/st-nci/ndlc.h index 6361005ef..bdf78ffd5 100644 --- a/drivers/nfc/st-nci/ndlc.h +++ b/drivers/nfc/st-nci/ndlc.h @@ -22,6 +22,8 @@ #include #include +struct st_nci_se_status; + /* Low Level Transport description */ struct llt_ndlc { struct nci_dev *ndev; @@ -55,6 +57,7 @@ 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); + int phy_headroom, int phy_tailroom, struct llt_ndlc **ndlc_id, + struct st_nci_se_status *se_status); void ndlc_remove(struct llt_ndlc *ndlc); #endif /* __LOCAL_NDLC_H__ */ diff --git a/drivers/nfc/st-nci/se.c b/drivers/nfc/st-nci/se.c new file mode 100644 index 000000000..dbab722a0 --- /dev/null +++ b/drivers/nfc/st-nci/se.c @@ -0,0 +1,774 @@ +/* + * 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 . + */ + +#include +#include +#include +#include +#include + +#include "st-nci.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_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}, + + {NCI_HCI_IDENTITY_MGMT_GATE, NCI_HCI_INVALID_PIPE, + ST_NCI_HOST_CONTROLLER_ID}, + {NCI_HCI_LOOPBACK_GATE, NCI_HCI_INVALID_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) + return r; + + /* 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) + return r; + + /* 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); + kfree_skb(skb_pipe_info); + continue; + } + + for (j = 3; (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)) { + ndev->hci_dev->init_data.gates[j].pipe = pipe_info[2]; + + ndev->hci_dev->gate2pipe[st_nci_gates[j].gate] = + pipe_info[2]; + ndev->hci_dev->pipes[pipe_info[2]].gate = + st_nci_gates[j].gate; + ndev->hci_dev->pipes[pipe_info[2]].host = + dm_pipe_info->src_host_id; + } + kfree_skb(skb_pipe_info); + } + + /* + * 3 gates have a well known pipe ID. Only NCI_HCI_LINK_MGMT_GATE + * is not yet open at this stage. + */ + r = nci_hci_connect_gate(ndev, ST_NCI_HOST_CONTROLLER_ID, + NCI_HCI_LINK_MGMT_GATE, + NCI_HCI_LINK_MGMT_PIPE); + + 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; + default: + nfc_err(&ndev->nfc_dev->dev, "Unexpected event on admin gate\n"); + } +} + +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; + default: + nfc_err(&ndev->nfc_dev->dev, "Unexpected event on apdu reader gate\n"); + return 1; + } + + 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: + nfc_err(&ndev->nfc_dev->dev, "Unexpected event on connectivity gate\n"); + 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; + case NCI_HCI_LOOPBACK_GATE: + st_nci_hci_loopback_event_received(ndev, 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); + +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, i; + 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, state); + 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. + */ + if (info->se_info.se_status->is_ese_present && + info->se_info.se_status->is_uicc_present) + usleep_range(15000, 20000); + + 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; + + for (i = 0; i < sk_host_list->len && + sk_host_list->data[i] != se_idx; i++) + ; + host_id = sk_host_list->data[i]; + 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"); + + /* + * According to upper layer, se_idx == NFC_SE_UICC when + * info->se_info.se_status->is_uicc_enable is true should never happen + * Same for eSE. + */ + r = st_nci_control_se(ndev, se_idx, ST_NCI_SE_MODE_OFF); + if (r < 0) { + /* Do best effort to release SWP */ + 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); + } + 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"); + + /* + * According to upper layer, se_idx == NFC_SE_UICC when + * info->se_info.se_status->is_uicc_enable is true should never happen. + * Same for eSE. + */ + r = st_nci_control_se(ndev, se_idx, ST_NCI_SE_MODE_ON); + if (r == ST_NCI_HCI_HOST_ID_ESE) { + st_nci_se_get_atr(ndev); + r = nci_hci_send_event(ndev, ST_NCI_APDU_READER_GATE, + ST_NCI_EVT_SE_SOFT_RESET, NULL, 0); + } + + if (r < 0) { + /* + * The activation procedure failed, the secure element + * is not connected. Remove from the list. + */ + nfc_remove_se(ndev->nfc_dev, se_idx); + return r; + } + + return 0; +} +EXPORT_SYMBOL_GPL(st_nci_enable_se); + +static int st_nci_hci_network_init(struct nci_dev *ndev) +{ + struct st_nci_info *info = nci_get_drvdata(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; + + ndev->hci_dev->init_data.gate_count = ARRAY_SIZE(st_nci_gates); + 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; + + /* + * In factory mode, we prevent secure elements activation + * by disabling nfcee on the current HCI connection id. + * HCI will be used here only for proprietary commands. + */ + if (test_bit(ST_NCI_FACTORY_MODE, &info->flags)) + r = nci_nfcee_mode_set(ndev, ndev->hci_dev->conn_info->id, + NCI_NFCEE_DISABLE); + else + r = nci_nfcee_mode_set(ndev, ndev->hci_dev->conn_info->id, + NCI_NFCEE_ENABLE); + +free_dest_params: + kfree(dest_params); + +exit: + return r; +} + +int st_nci_discover_se(struct nci_dev *ndev) +{ + u8 white_list[2]; + int r, wl_size = 0; + int se_count = 0; + struct st_nci_info *info = nci_get_drvdata(ndev); + + pr_debug("st_nci_discover_se\n"); + + r = st_nci_hci_network_init(ndev); + if (r != 0) + return r; + + if (test_bit(ST_NCI_FACTORY_MODE, &info->flags)) + return 0; + + if (info->se_info.se_status->is_ese_present && + info->se_info.se_status->is_uicc_present) { + white_list[wl_size++] = ST_NCI_UICC_HOST_ID; + white_list[wl_size++] = ST_NCI_ESE_HOST_ID; + } else if (!info->se_info.se_status->is_ese_present && + info->se_info.se_status->is_uicc_present) { + white_list[wl_size++] = ST_NCI_UICC_HOST_ID; + } else if (info->se_info.se_status->is_ese_present && + !info->se_info.se_status->is_uicc_present) { + white_list[wl_size++] = ST_NCI_ESE_HOST_ID; + } + + if (wl_size) { + r = nci_hci_set_param(ndev, NCI_HCI_ADMIN_GATE, + NCI_HCI_ADMIN_PARAM_WHITELIST, + white_list, wl_size); + if (r != NCI_HCI_ANY_OK) + return r; + } + + if (info->se_info.se_status->is_uicc_present) { + nfc_add_se(ndev->nfc_dev, ST_NCI_UICC_HOST_ID, NFC_SE_UICC); + se_count++; + } + + if (info->se_info.se_status->is_ese_present) { + nfc_add_se(ndev->nfc_dev, ST_NCI_ESE_HOST_ID, NFC_SE_EMBEDDED); + se_count++; + } + + 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_se_status *se_status) +{ + 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); + + info->se_info.se_status = se_status; + + 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/spi.c b/drivers/nfc/st-nci/spi.c index 887d308c2..d6519bb9d 100644 --- a/drivers/nfc/st-nci/spi.c +++ b/drivers/nfc/st-nci/spi.c @@ -28,7 +28,7 @@ #include #include -#include "ndlc.h" +#include "st-nci.h" #define DRIVER_DESC "NCI NFC driver for ST_NCI" @@ -51,16 +51,13 @@ struct st_nci_spi_phy { struct spi_device *spi_dev; struct llt_ndlc *ndlc; + bool irq_active; + unsigned int gpio_reset; unsigned int irq_polarity; -}; -#define SPI_DUMP_SKB(info, skb) \ -do { \ - pr_debug("%s:\n", info); \ - print_hex_dump(KERN_DEBUG, "spi: ", DUMP_PREFIX_OFFSET, \ - 16, 1, (skb)->data, (skb)->len, 0); \ -} while (0) + struct st_nci_se_status se_status; +}; static int st_nci_spi_enable(void *phy_id) { @@ -71,8 +68,10 @@ static int st_nci_spi_enable(void *phy_id) gpio_set_value(phy->gpio_reset, 1); usleep_range(80000, 85000); - if (phy->ndlc->powered == 0) + if (phy->ndlc->powered == 0 && phy->irq_active == 0) { enable_irq(phy->spi_dev->irq); + phy->irq_active = true; + } return 0; } @@ -82,6 +81,7 @@ static void st_nci_spi_disable(void *phy_id) struct st_nci_spi_phy *phy = phy_id; disable_irq_nosync(phy->spi_dev->irq); + phy->irq_active = false; } /* @@ -103,8 +103,6 @@ static int st_nci_spi_write(void *phy_id, struct sk_buff *skb) .len = skb->len, }; - SPI_DUMP_SKB("st_nci_spi_write", skb); - if (phy->ndlc->hard_fault != 0) return phy->ndlc->hard_fault; @@ -181,8 +179,6 @@ static int st_nci_spi_read(struct st_nci_spi_phy *phy, skb_put(*skb, len); memcpy((*skb)->data + ST_NCI_SPI_MIN_SIZE, buf, len); - SPI_DUMP_SKB("spi frame read", *skb); - return 0; } @@ -260,6 +256,11 @@ static int st_nci_spi_of_request_resources(struct spi_device *dev) phy->irq_polarity = irq_get_trigger_type(dev->irq); + phy->se_status.is_ese_present = + of_property_read_bool(pp, "ese-present"); + phy->se_status.is_uicc_present = + of_property_read_bool(pp, "uicc-present"); + return 0; } #else @@ -292,6 +293,9 @@ static int st_nci_spi_request_resources(struct spi_device *dev) return r; } + phy->se_status.is_ese_present = pdata->is_ese_present; + phy->se_status.is_uicc_present = pdata->is_uicc_present; + return 0; } @@ -342,12 +346,13 @@ static int st_nci_spi_probe(struct spi_device *dev) r = ndlc_probe(phy, &spi_phy_ops, &dev->dev, ST_NCI_FRAME_HEADROOM, ST_NCI_FRAME_TAILROOM, - &phy->ndlc); + &phy->ndlc, &phy->se_status); if (r < 0) { nfc_err(&dev->dev, "Unable to register ndlc layer\n"); return r; } + phy->irq_active = true; r = devm_request_threaded_irq(&dev->dev, dev->irq, NULL, st_nci_irq_thread_fn, phy->irq_polarity | IRQF_ONESHOT, @@ -379,7 +384,6 @@ MODULE_DEVICE_TABLE(of, of_st_nci_spi_match); static struct spi_driver st_nci_spi_driver = { .driver = { - .owner = THIS_MODULE, .name = ST_NCI_SPI_DRIVER_NAME, .of_match_table = of_match_ptr(of_st_nci_spi_match), }, diff --git a/drivers/nfc/st-nci/st-nci.h b/drivers/nfc/st-nci/st-nci.h index 850a2395d..8b9f77b02 100644 --- a/drivers/nfc/st-nci/st-nci.h +++ b/drivers/nfc/st-nci/st-nci.h @@ -19,7 +19,6 @@ #ifndef __LOCAL_ST_NCI_H_ #define __LOCAL_ST_NCI_H_ -#include "st-nci_se.h" #include "ndlc.h" /* Define private flags: */ @@ -28,6 +27,18 @@ #define ST_NCI_CORE_PROP 0x01 #define ST_NCI_SET_NFC_MODE 0x02 +/* + * 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 + +#define ST_NCI_DEVICE_MGNT_GATE 0x01 + +#define ST_NCI_VENDOR_OUI 0x0080E1 /* STMicroelectronics */ +#define ST_NCI_FACTORY_MODE 2 + struct nci_mode_set_cmd { u8 cmd_type; u8 mode; @@ -37,14 +48,116 @@ struct nci_mode_set_rsp { u8 status; } __packed; +struct st_nci_se_status { + bool is_ese_present; + bool is_uicc_present; +}; + +struct st_nci_se_info { + struct st_nci_se_status *se_status; + 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; +}; + +/** + * enum nfc_vendor_cmds - supported nfc vendor commands + * + * @FACTORY_MODE: Allow to set the driver into a mode where no secure element + * are activated. It does not consider any NFC_ATTR_VENDOR_DATA. + * @HCI_CLEAR_ALL_PIPES: Allow to execute a HCI clear all pipes command. + * It does not consider any NFC_ATTR_VENDOR_DATA. + * @HCI_DM_PUT_DATA: Allow to configure specific CLF registry as for example + * RF trimmings or low level drivers configurations (I2C, SPI, SWP). + * @HCI_DM_UPDATE_AID: Allow to configure an AID routing into the CLF routing + * table following RF technology, CLF mode or protocol. + * @HCI_DM_GET_INFO: Allow to retrieve CLF information. + * @HCI_DM_GET_DATA: Allow to retrieve CLF configurable data such as low + * level drivers configurations or RF trimmings. + * @HCI_DM_DIRECT_LOAD: Allow to load a firmware into the CLF. A complete + * packet can be more than 8KB. + * @HCI_DM_RESET: Allow to run a CLF reset in order to "commit" CLF + * configuration changes without CLF power off. + * @HCI_GET_PARAM: Allow to retrieve an HCI CLF parameter (for example the + * white list). + * @HCI_DM_FIELD_GENERATOR: Allow to generate different kind of RF + * technology. When using this command to anti-collision is done. + * @HCI_LOOPBACK: Allow to echo a command and test the Dh to CLF + * connectivity. + * @HCI_DM_VDC_MEASUREMENT_VALUE: Allow to measure the field applied on the + * CLF antenna. A value between 0 and 0x0f is returned. 0 is maximum. + * @HCI_DM_FWUPD_START: Allow to put CLF into firmware update mode. It is a + * specific CLF command as there is no GPIO for this. + * @HCI_DM_FWUPD_END: Allow to complete firmware update. + * @HCI_DM_VDC_VALUE_COMPARISON: Allow to compare the field applied on the + * CLF antenna to a reference value. + * @MANUFACTURER_SPECIFIC: Allow to retrieve manufacturer specific data + * received during a NCI_CORE_INIT_CMD. + */ +enum nfc_vendor_cmds { + FACTORY_MODE, + HCI_CLEAR_ALL_PIPES, + HCI_DM_PUT_DATA, + HCI_DM_UPDATE_AID, + HCI_DM_GET_INFO, + HCI_DM_GET_DATA, + HCI_DM_DIRECT_LOAD, + HCI_DM_RESET, + HCI_GET_PARAM, + HCI_DM_FIELD_GENERATOR, + HCI_LOOPBACK, + HCI_DM_FWUPD_START, + HCI_DM_FWUPD_END, + HCI_DM_VDC_MEASUREMENT_VALUE, + HCI_DM_VDC_VALUE_COMPARISON, + MANUFACTURER_SPECIFIC, +}; + +struct st_nci_vendor_info { + struct completion req_completion; + struct sk_buff *rx_skb; +}; + struct st_nci_info { struct llt_ndlc *ndlc; unsigned long flags; + struct st_nci_se_info se_info; + struct st_nci_vendor_info vendor_info; }; void st_nci_remove(struct nci_dev *ndev); int st_nci_probe(struct llt_ndlc *ndlc, int phy_headroom, - int phy_tailroom); + int phy_tailroom, struct st_nci_se_status *se_status); + +int st_nci_se_init(struct nci_dev *ndev, struct st_nci_se_status *se_status); +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); + +void st_nci_hci_loopback_event_received(struct nci_dev *ndev, u8 event, + struct sk_buff *skb); +int st_nci_vendor_cmds_init(struct nci_dev *ndev); #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 deleted file mode 100644 index c742ef65a..000000000 --- a/drivers/nfc/st-nci/st-nci_se.c +++ /dev/null @@ -1,714 +0,0 @@ -/* - * 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 . - */ - -#include -#include -#include -#include -#include - -#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) - return r; - - /* 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) - return r; - - /* 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); - kfree_skb(skb_pipe_info); - 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; - } - kfree_skb(skb_pipe_info); - } - - memcpy(ndev->hci_dev->init_data.gates, st_nci_gates, - sizeof(st_nci_gates)); - - 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 deleted file mode 100644 index ea66e879d..000000000 --- a/drivers/nfc/st-nci/st-nci_se.h +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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 . - */ -#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_ */ diff --git a/drivers/nfc/st-nci/vendor_cmds.c b/drivers/nfc/st-nci/vendor_cmds.c new file mode 100644 index 000000000..b5debce4a --- /dev/null +++ b/drivers/nfc/st-nci/vendor_cmds.c @@ -0,0 +1,516 @@ +/* + * Proprietary commands extension 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 . + */ + +#include +#include +#include +#include +#include + +#include "st-nci.h" + +#define ST_NCI_HCI_DM_GETDATA 0x10 +#define ST_NCI_HCI_DM_PUTDATA 0x11 +#define ST_NCI_HCI_DM_LOAD 0x12 +#define ST_NCI_HCI_DM_GETINFO 0x13 +#define ST_NCI_HCI_DM_FWUPD_START 0x14 +#define ST_NCI_HCI_DM_FWUPD_STOP 0x15 +#define ST_NCI_HCI_DM_UPDATE_AID 0x20 +#define ST_NCI_HCI_DM_RESET 0x3e + +#define ST_NCI_HCI_DM_FIELD_GENERATOR 0x32 +#define ST_NCI_HCI_DM_VDC_MEASUREMENT_VALUE 0x33 +#define ST_NCI_HCI_DM_VDC_VALUE_COMPARISON 0x34 + +#define ST_NCI_FACTORY_MODE_ON 1 +#define ST_NCI_FACTORY_MODE_OFF 0 + +#define ST_NCI_EVT_POST_DATA 0x02 + +struct get_param_data { + u8 gate; + u8 data; +} __packed; + +static int st_nci_factory_mode(struct nfc_dev *dev, void *data, + size_t data_len) +{ + struct nci_dev *ndev = nfc_get_drvdata(dev); + struct st_nci_info *info = nci_get_drvdata(ndev); + + if (data_len != 1) + return -EINVAL; + + pr_debug("factory mode: %x\n", ((u8 *)data)[0]); + + switch (((u8 *)data)[0]) { + case ST_NCI_FACTORY_MODE_ON: + test_and_set_bit(ST_NCI_FACTORY_MODE, &info->flags); + break; + case ST_NCI_FACTORY_MODE_OFF: + clear_bit(ST_NCI_FACTORY_MODE, &info->flags); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int st_nci_hci_clear_all_pipes(struct nfc_dev *dev, void *data, + size_t data_len) +{ + struct nci_dev *ndev = nfc_get_drvdata(dev); + + return nci_hci_clear_all_pipes(ndev); +} + +static int st_nci_hci_dm_put_data(struct nfc_dev *dev, void *data, + size_t data_len) +{ + struct nci_dev *ndev = nfc_get_drvdata(dev); + + return nci_hci_send_cmd(ndev, ST_NCI_DEVICE_MGNT_GATE, + ST_NCI_HCI_DM_PUTDATA, data, + data_len, NULL); +} + +static int st_nci_hci_dm_update_aid(struct nfc_dev *dev, void *data, + size_t data_len) +{ + struct nci_dev *ndev = nfc_get_drvdata(dev); + + return nci_hci_send_cmd(ndev, ST_NCI_DEVICE_MGNT_GATE, + ST_NCI_HCI_DM_UPDATE_AID, data, data_len, NULL); +} + +static int st_nci_hci_dm_get_info(struct nfc_dev *dev, void *data, + size_t data_len) +{ + int r; + struct sk_buff *msg, *skb; + struct nci_dev *ndev = nfc_get_drvdata(dev); + + r = nci_hci_send_cmd(ndev, ST_NCI_DEVICE_MGNT_GATE, ST_NCI_HCI_DM_GETINFO, + data, data_len, &skb); + if (r) + goto exit; + + msg = nfc_vendor_cmd_alloc_reply_skb(dev, ST_NCI_VENDOR_OUI, + HCI_DM_GET_INFO, skb->len); + if (!msg) { + r = -ENOMEM; + goto free_skb; + } + + if (nla_put(msg, NFC_ATTR_VENDOR_DATA, skb->len, skb->data)) { + kfree_skb(msg); + r = -ENOBUFS; + goto free_skb; + } + + r = nfc_vendor_cmd_reply(msg); + +free_skb: + kfree_skb(skb); +exit: + return r; +} + +static int st_nci_hci_dm_get_data(struct nfc_dev *dev, void *data, + size_t data_len) +{ + int r; + struct sk_buff *msg, *skb; + struct nci_dev *ndev = nfc_get_drvdata(dev); + + r = nci_hci_send_cmd(ndev, ST_NCI_DEVICE_MGNT_GATE, ST_NCI_HCI_DM_GETDATA, + data, data_len, &skb); + if (r) + goto exit; + + msg = nfc_vendor_cmd_alloc_reply_skb(dev, ST_NCI_VENDOR_OUI, + HCI_DM_GET_DATA, skb->len); + if (!msg) { + r = -ENOMEM; + goto free_skb; + } + + if (nla_put(msg, NFC_ATTR_VENDOR_DATA, skb->len, skb->data)) { + kfree_skb(msg); + r = -ENOBUFS; + goto free_skb; + } + + r = nfc_vendor_cmd_reply(msg); + +free_skb: + kfree_skb(skb); +exit: + return r; +} + +static int st_nci_hci_dm_fwupd_start(struct nfc_dev *dev, void *data, + size_t data_len) +{ + int r; + struct nci_dev *ndev = nfc_get_drvdata(dev); + + dev->fw_download_in_progress = true; + r = nci_hci_send_cmd(ndev, ST_NCI_DEVICE_MGNT_GATE, + ST_NCI_HCI_DM_FWUPD_START, data, data_len, NULL); + if (r) + dev->fw_download_in_progress = false; + + return r; +} + +static int st_nci_hci_dm_fwupd_end(struct nfc_dev *dev, void *data, + size_t data_len) +{ + struct nci_dev *ndev = nfc_get_drvdata(dev); + + return nci_hci_send_cmd(ndev, ST_NCI_DEVICE_MGNT_GATE, + ST_NCI_HCI_DM_FWUPD_STOP, data, data_len, NULL); +} + +static int st_nci_hci_dm_direct_load(struct nfc_dev *dev, void *data, + size_t data_len) +{ + struct nci_dev *ndev = nfc_get_drvdata(dev); + + if (dev->fw_download_in_progress) { + dev->fw_download_in_progress = false; + return nci_hci_send_cmd(ndev, ST_NCI_DEVICE_MGNT_GATE, + ST_NCI_HCI_DM_LOAD, data, data_len, NULL); + } + return -EPROTO; +} + +static int st_nci_hci_dm_reset(struct nfc_dev *dev, void *data, + size_t data_len) +{ + struct nci_dev *ndev = nfc_get_drvdata(dev); + + nci_hci_send_cmd(ndev, ST_NCI_DEVICE_MGNT_GATE, + ST_NCI_HCI_DM_RESET, data, data_len, NULL); + msleep(200); + + return 0; +} + +static int st_nci_hci_get_param(struct nfc_dev *dev, void *data, + size_t data_len) +{ + int r; + struct sk_buff *msg, *skb; + struct nci_dev *ndev = nfc_get_drvdata(dev); + struct get_param_data *param = (struct get_param_data *)data; + + if (data_len < sizeof(struct get_param_data)) + return -EPROTO; + + r = nci_hci_get_param(ndev, param->gate, param->data, &skb); + if (r) + goto exit; + + msg = nfc_vendor_cmd_alloc_reply_skb(dev, ST_NCI_VENDOR_OUI, + HCI_GET_PARAM, skb->len); + if (!msg) { + r = -ENOMEM; + goto free_skb; + } + + if (nla_put(msg, NFC_ATTR_VENDOR_DATA, skb->len, skb->data)) { + kfree_skb(msg); + r = -ENOBUFS; + goto free_skb; + } + + r = nfc_vendor_cmd_reply(msg); + +free_skb: + kfree_skb(skb); +exit: + return r; +} + +static int st_nci_hci_dm_field_generator(struct nfc_dev *dev, void *data, + size_t data_len) +{ + struct nci_dev *ndev = nfc_get_drvdata(dev); + + return nci_hci_send_cmd(ndev, ST_NCI_DEVICE_MGNT_GATE, + ST_NCI_HCI_DM_FIELD_GENERATOR, data, data_len, NULL); +} + +static int st_nci_hci_dm_vdc_measurement_value(struct nfc_dev *dev, void *data, + size_t data_len) +{ + int r; + struct sk_buff *msg, *skb; + struct nci_dev *ndev = nfc_get_drvdata(dev); + + if (data_len != 4) + return -EPROTO; + + r = nci_hci_send_cmd(ndev, ST_NCI_DEVICE_MGNT_GATE, + ST_NCI_HCI_DM_VDC_MEASUREMENT_VALUE, + data, data_len, &skb); + if (r) + goto exit; + + msg = nfc_vendor_cmd_alloc_reply_skb(dev, ST_NCI_VENDOR_OUI, + HCI_DM_VDC_MEASUREMENT_VALUE, skb->len); + if (!msg) { + r = -ENOMEM; + goto free_skb; + } + + if (nla_put(msg, NFC_ATTR_VENDOR_DATA, skb->len, skb->data)) { + kfree_skb(msg); + r = -ENOBUFS; + goto free_skb; + } + + r = nfc_vendor_cmd_reply(msg); + +free_skb: + kfree_skb(skb); +exit: + return r; +} + +static int st_nci_hci_dm_vdc_value_comparison(struct nfc_dev *dev, void *data, + size_t data_len) +{ + int r; + struct sk_buff *msg, *skb; + struct nci_dev *ndev = nfc_get_drvdata(dev); + + if (data_len != 2) + return -EPROTO; + + r = nci_hci_send_cmd(ndev, ST_NCI_DEVICE_MGNT_GATE, + ST_NCI_HCI_DM_VDC_VALUE_COMPARISON, + data, data_len, &skb); + if (r) + goto exit; + + msg = nfc_vendor_cmd_alloc_reply_skb(dev, ST_NCI_VENDOR_OUI, + HCI_DM_VDC_VALUE_COMPARISON, skb->len); + if (!msg) { + r = -ENOMEM; + goto free_skb; + } + + if (nla_put(msg, NFC_ATTR_VENDOR_DATA, skb->len, skb->data)) { + kfree_skb(msg); + r = -ENOBUFS; + goto free_skb; + } + + r = nfc_vendor_cmd_reply(msg); + +free_skb: + kfree_skb(skb); +exit: + return r; +} + +void st_nci_hci_loopback_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_POST_DATA: + info->vendor_info.rx_skb = skb; + break; + default: + nfc_err(&ndev->nfc_dev->dev, "Unexpected event on loopback gate\n"); + } + complete(&info->vendor_info.req_completion); +} +EXPORT_SYMBOL(st_nci_hci_loopback_event_received); + +static int st_nci_hci_loopback(struct nfc_dev *dev, void *data, + size_t data_len) +{ + int r; + struct sk_buff *msg; + struct nci_dev *ndev = nfc_get_drvdata(dev); + struct st_nci_info *info = nci_get_drvdata(ndev); + + if (data_len <= 0) + return -EPROTO; + + reinit_completion(&info->vendor_info.req_completion); + info->vendor_info.rx_skb = NULL; + + r = nci_hci_send_event(ndev, NCI_HCI_LOOPBACK_GATE, + ST_NCI_EVT_POST_DATA, data, data_len); + if (r != data_len) { + r = -EPROTO; + goto exit; + } + + wait_for_completion_interruptible(&info->vendor_info.req_completion); + + if (!info->vendor_info.rx_skb || + info->vendor_info.rx_skb->len != data_len) { + r = -EPROTO; + goto exit; + } + + msg = nfc_vendor_cmd_alloc_reply_skb(ndev->nfc_dev, + ST_NCI_VENDOR_OUI, + HCI_LOOPBACK, + info->vendor_info.rx_skb->len); + if (!msg) { + r = -ENOMEM; + goto free_skb; + } + + if (nla_put(msg, NFC_ATTR_VENDOR_DATA, info->vendor_info.rx_skb->len, + info->vendor_info.rx_skb->data)) { + kfree_skb(msg); + r = -ENOBUFS; + goto free_skb; + } + + r = nfc_vendor_cmd_reply(msg); +free_skb: + kfree_skb(info->vendor_info.rx_skb); +exit: + return r; +} + +static int st_nci_manufacturer_specific(struct nfc_dev *dev, void *data, + size_t data_len) +{ + struct sk_buff *msg; + struct nci_dev *ndev = nfc_get_drvdata(dev); + + msg = nfc_vendor_cmd_alloc_reply_skb(dev, ST_NCI_VENDOR_OUI, + MANUFACTURER_SPECIFIC, + sizeof(ndev->manufact_specific_info)); + if (!msg) + return -ENOMEM; + + if (nla_put(msg, NFC_ATTR_VENDOR_DATA, sizeof(ndev->manufact_specific_info), + &ndev->manufact_specific_info)) { + kfree_skb(msg); + return -ENOBUFS; + } + + return nfc_vendor_cmd_reply(msg); +} + +static struct nfc_vendor_cmd st_nci_vendor_cmds[] = { + { + .vendor_id = ST_NCI_VENDOR_OUI, + .subcmd = FACTORY_MODE, + .doit = st_nci_factory_mode, + }, + { + .vendor_id = ST_NCI_VENDOR_OUI, + .subcmd = HCI_CLEAR_ALL_PIPES, + .doit = st_nci_hci_clear_all_pipes, + }, + { + .vendor_id = ST_NCI_VENDOR_OUI, + .subcmd = HCI_DM_PUT_DATA, + .doit = st_nci_hci_dm_put_data, + }, + { + .vendor_id = ST_NCI_VENDOR_OUI, + .subcmd = HCI_DM_UPDATE_AID, + .doit = st_nci_hci_dm_update_aid, + }, + { + .vendor_id = ST_NCI_VENDOR_OUI, + .subcmd = HCI_DM_GET_INFO, + .doit = st_nci_hci_dm_get_info, + }, + { + .vendor_id = ST_NCI_VENDOR_OUI, + .subcmd = HCI_DM_GET_DATA, + .doit = st_nci_hci_dm_get_data, + }, + { + .vendor_id = ST_NCI_VENDOR_OUI, + .subcmd = HCI_DM_DIRECT_LOAD, + .doit = st_nci_hci_dm_direct_load, + }, + { + .vendor_id = ST_NCI_VENDOR_OUI, + .subcmd = HCI_DM_RESET, + .doit = st_nci_hci_dm_reset, + }, + { + .vendor_id = ST_NCI_VENDOR_OUI, + .subcmd = HCI_GET_PARAM, + .doit = st_nci_hci_get_param, + }, + { + .vendor_id = ST_NCI_VENDOR_OUI, + .subcmd = HCI_DM_FIELD_GENERATOR, + .doit = st_nci_hci_dm_field_generator, + }, + { + .vendor_id = ST_NCI_VENDOR_OUI, + .subcmd = HCI_DM_FWUPD_START, + .doit = st_nci_hci_dm_fwupd_start, + }, + { + .vendor_id = ST_NCI_VENDOR_OUI, + .subcmd = HCI_DM_FWUPD_END, + .doit = st_nci_hci_dm_fwupd_end, + }, + { + .vendor_id = ST_NCI_VENDOR_OUI, + .subcmd = HCI_LOOPBACK, + .doit = st_nci_hci_loopback, + }, + { + .vendor_id = ST_NCI_VENDOR_OUI, + .subcmd = HCI_DM_VDC_MEASUREMENT_VALUE, + .doit = st_nci_hci_dm_vdc_measurement_value, + }, + { + .vendor_id = ST_NCI_VENDOR_OUI, + .subcmd = HCI_DM_VDC_VALUE_COMPARISON, + .doit = st_nci_hci_dm_vdc_value_comparison, + }, + { + .vendor_id = ST_NCI_VENDOR_OUI, + .subcmd = MANUFACTURER_SPECIFIC, + .doit = st_nci_manufacturer_specific, + }, +}; + +int st_nci_vendor_cmds_init(struct nci_dev *ndev) +{ + struct st_nci_info *info = nci_get_drvdata(ndev); + + init_completion(&info->vendor_info.req_completion); + return nfc_set_vendor_cmds(ndev->nfc_dev, st_nci_vendor_cmds, + sizeof(st_nci_vendor_cmds)); +} +EXPORT_SYMBOL(st_nci_vendor_cmds_init); -- cgit v1.2.3-54-g00ecf