Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+31
View File
@@ -0,0 +1,31 @@
# SPDX-License-Identifier: GPL-2.0-only
config NFC_ST_NCI
tristate
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.
config NFC_ST_NCI_I2C
tristate "STMicroelectronics ST NCI NFC driver (I2C)"
depends on NFC_NCI && I2C
select NFC_ST_NCI
help
This module adds support for an I2C interface to the
STMicroelectronics NFC NCI chips family.
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.
config NFC_ST_NCI_SPI
tristate "STMicroelectronics ST NCI NFC driver (SPI)"
depends on NFC_NCI && SPI
select NFC_ST_NCI
help
This module adds support for an SPI interface to the
STMicroelectronics NFC NCI chips family.
Select this if your platform is using the spi bus.
If you choose to build a module, it'll be called st-nci_spi.
Say N if unsure.
+13
View File
@@ -0,0 +1,13 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for ST_NCI NCI based NFC driver
#
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
obj-$(CONFIG_NFC_ST_NCI_I2C) += st-nci_i2c.o
st-nci_spi-objs = spi.o
obj-$(CONFIG_NFC_ST_NCI_SPI) += st-nci_spi.o
+175
View File
@@ -0,0 +1,175 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* NCI based Driver for STMicroelectronics NFC Chip
*
* Copyright (C) 2014-2015 STMicroelectronics SAS. All rights reserved.
*/
#include <linux/module.h>
#include <linux/nfc.h>
#include <net/nfc/nci.h>
#include <net/nfc/nci_core.h>
#include "st-nci.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 const struct nci_driver_ops st_nci_prop_ops[] = {
{
.opcode = nci_opcode_pack(NCI_GID_PROPRIETARY,
ST_NCI_CORE_PROP),
.rsp = st_nci_prop_rsp_packet,
},
};
static const 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_se_status *se_status)
{
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;
BUILD_BUG_ON(ARRAY_SIZE(st_nci_prop_ops) > NCI_MAX_PROPRIETARY_CMD);
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 = 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");
goto err_reg_dev;
}
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);
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);
+293
View File
@@ -0,0 +1,293 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* I2C Link Layer for ST NCI NFC controller familly based Driver
* Copyright (C) 2014-2015 STMicroelectronics SAS. All rights reserved.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/gpio/consumer.h>
#include <linux/acpi.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/nfc.h>
#include <linux/of.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_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_DRIVER_NAME "st_nci"
#define ST_NCI_I2C_DRIVER_NAME "st_nci_i2c"
struct st_nci_i2c_phy {
struct i2c_client *i2c_dev;
struct llt_ndlc *ndlc;
bool irq_active;
struct gpio_desc *gpiod_reset;
struct st_nci_se_status se_status;
};
static int st_nci_i2c_enable(void *phy_id)
{
struct st_nci_i2c_phy *phy = phy_id;
gpiod_set_value(phy->gpiod_reset, 0);
usleep_range(10000, 15000);
gpiod_set_value(phy->gpiod_reset, 1);
usleep_range(80000, 85000);
if (phy->ndlc->powered == 0 && phy->irq_active == 0) {
enable_irq(phy->i2c_dev->irq);
phy->irq_active = true;
}
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);
phy->irq_active = false;
}
/*
* 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;
struct st_nci_i2c_phy *phy = phy_id;
struct i2c_client *client = phy->i2c_dev;
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:
* 0 : if received frame is complete
* -EREMOTEIO : i2c read error (fatal)
* -EBADMSG : frame was incorrect and discarded
* -ENOMEM : cannot allocate skb, frame dropped
*/
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);
return 0;
}
/*
* Reads an ndlc frame from the chip.
*
* On ST_NCI, 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 sk_buff *skb = NULL;
int r;
if (!phy || !phy->ndlc || irq != phy->i2c_dev->irq) {
WARN_ON_ONCE(1);
return IRQ_NONE;
}
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 const struct nfc_phy_ops i2c_phy_ops = {
.write = st_nci_i2c_write,
.enable = st_nci_i2c_enable,
.disable = st_nci_i2c_disable,
};
static const struct acpi_gpio_params reset_gpios = { 1, 0, false };
static const struct acpi_gpio_mapping acpi_st_nci_gpios[] = {
{ "reset-gpios", &reset_gpios, 1 },
{},
};
static int st_nci_i2c_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct st_nci_i2c_phy *phy;
int r;
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
nfc_err(&client->dev, "Need I2C_FUNC_I2C\n");
return -ENODEV;
}
phy = devm_kzalloc(dev, sizeof(struct st_nci_i2c_phy), GFP_KERNEL);
if (!phy)
return -ENOMEM;
phy->i2c_dev = client;
i2c_set_clientdata(client, phy);
r = devm_acpi_dev_add_driver_gpios(dev, acpi_st_nci_gpios);
if (r)
dev_dbg(dev, "Unable to add GPIO mapping table\n");
/* Get RESET GPIO */
phy->gpiod_reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
if (IS_ERR(phy->gpiod_reset)) {
nfc_err(dev, "Unable to get RESET GPIO\n");
return -ENODEV;
}
phy->se_status.is_ese_present =
device_property_read_bool(dev, "ese-present");
phy->se_status.is_uicc_present =
device_property_read_bool(dev, "uicc-present");
r = ndlc_probe(phy, &i2c_phy_ops, &client->dev,
ST_NCI_FRAME_HEADROOM, ST_NCI_FRAME_TAILROOM,
&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,
IRQF_ONESHOT,
ST_NCI_DRIVER_NAME, phy);
if (r < 0)
nfc_err(&client->dev, "Unable to register IRQ handler\n");
return r;
}
static void st_nci_i2c_remove(struct i2c_client *client)
{
struct st_nci_i2c_phy *phy = i2c_get_clientdata(client);
ndlc_remove(phy->ndlc);
}
static const struct i2c_device_id st_nci_i2c_id_table[] = {
{ .name = ST_NCI_DRIVER_NAME },
{ }
};
MODULE_DEVICE_TABLE(i2c, st_nci_i2c_id_table);
static const struct acpi_device_id st_nci_i2c_acpi_match[] __maybe_unused = {
{"SMO2101"},
{"SMO2102"},
{}
};
MODULE_DEVICE_TABLE(acpi, st_nci_i2c_acpi_match);
static const struct of_device_id of_st_nci_i2c_match[] __maybe_unused = {
{ .compatible = "st,st21nfcb-i2c", },
{ .compatible = "st,st21nfcb_i2c", },
{ .compatible = "st,st21nfcc-i2c", },
{}
};
MODULE_DEVICE_TABLE(of, of_st_nci_i2c_match);
static struct i2c_driver st_nci_i2c_driver = {
.driver = {
.name = ST_NCI_I2C_DRIVER_NAME,
.of_match_table = of_match_ptr(of_st_nci_i2c_match),
.acpi_match_table = ACPI_PTR(st_nci_i2c_acpi_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);
+298
View File
@@ -0,0 +1,298 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Low Level Transport (NDLC) Driver for STMicroelectronics NFC Chip
*
* Copyright (C) 2014-2015 STMicroelectronics SAS. All rights reserved.
*/
#include <linux/sched.h>
#include <net/nfc/nci_core.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;
*(u8 *)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:
skb = skb_dequeue(&ndlc->ack_pending_q);
kfree_skb(skb);
timer_delete_sync(&ndlc->t1_timer);
timer_delete_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:
kfree_skb(skb);
break;
}
} else if ((pcb & PCB_TYPE_MASK) == PCB_TYPE_DATAFRAME) {
nci_recv_frame(ndlc->ndev, skb);
} else {
kfree_skb(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;
timer_delete_sync(&ndlc->t1_timer);
timer_delete_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(struct timer_list *t)
{
struct llt_ndlc *ndlc = timer_container_of(ndlc, t, t1_timer);
schedule_work(&ndlc->sm_work);
}
static void ndlc_t2_timeout(struct timer_list *t)
{
struct llt_ndlc *ndlc = timer_container_of(ndlc, t, t2_timer);
schedule_work(&ndlc->sm_work);
}
int ndlc_probe(void *phy_id, const struct nfc_phy_ops *phy_ops,
struct device *dev, int phy_headroom, int phy_tailroom,
struct llt_ndlc **ndlc_id, struct st_nci_se_status *se_status)
{
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 */
timer_setup(&ndlc->t1_timer, ndlc_t1_timeout, 0);
timer_setup(&ndlc->t2_timer, ndlc_t2_timeout, 0);
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, se_status);
}
EXPORT_SYMBOL(ndlc_probe);
void ndlc_remove(struct llt_ndlc *ndlc)
{
/* cancel timers */
timer_delete_sync(&ndlc->t1_timer);
timer_delete_sync(&ndlc->t2_timer);
ndlc->t2_active = false;
ndlc->t1_active = false;
/* cancel work */
cancel_work_sync(&ndlc->sm_work);
st_nci_remove(ndlc->ndev);
skb_queue_purge(&ndlc->rcv_q);
skb_queue_purge(&ndlc->send_q);
}
EXPORT_SYMBOL(ndlc_remove);
+52
View File
@@ -0,0 +1,52 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* NCI based Driver for STMicroelectronics NFC Chip
*
* Copyright (C) 2014-2015 STMicroelectronics SAS. All rights reserved.
*/
#ifndef __LOCAL_NDLC_H_
#define __LOCAL_NDLC_H_
#include <linux/skbuff.h>
#include <net/nfc/nfc.h>
struct st_nci_se_status;
/* Low Level Transport description */
struct llt_ndlc {
struct nci_dev *ndev;
const 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, const struct nfc_phy_ops *phy_ops,
struct device *dev, 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__ */
+763
View File
@@ -0,0 +1,763 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Secure Element driver for STMicroelectronics NFC NCI chip
*
* Copyright (C) 2014-2015 STMicroelectronics SAS. All rights reserved.
*/
#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"
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},
/* 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_UICC_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)) {
timer_delete_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)
{
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:
timer_delete_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 0;
}
/*
* 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;
u32 aid_len;
u8 params_len;
pr_debug("connectivity gate event: %x\n", event);
switch (event) {
case ST_NCI_EVT_CONNECTIVITY:
r = nfc_se_connectivity(ndev->nfc_dev, host);
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
*
* The key differences are aid storage length is variably sized
* in the packet, but fixed in nfc_evt_transaction, and that
* the aid_len is u8 in the packet, but u32 in the structure,
* and the tags in the packet are not included in
* nfc_evt_transaction.
*
* size(b): 1 1 5-16 1 1 0-255
* offset: 0 1 2 aid_len + 2 aid_len + 3 aid_len + 4
* mem name: aid_tag(M) aid_len aid params_tag(M) params_len params
* example: 0x81 5-16 X 0x82 0-255 X
*/
if (skb->len < 2 || skb->data[0] != NFC_EVT_TRANSACTION_AID_TAG)
return -EPROTO;
aid_len = skb->data[1];
if (skb->len < aid_len + 4 ||
aid_len > sizeof(transaction->aid))
return -EPROTO;
params_len = skb->data[aid_len + 3];
/* Verify PARAMETERS tag is (82), and final check that there is
* enough space in the packet to read everything.
*/
if (skb->data[aid_len + 2] != NFC_EVT_TRANSACTION_PARAMS_TAG ||
skb->len < aid_len + 4 + params_len)
return -EPROTO;
transaction = devm_kzalloc(dev, sizeof(*transaction) +
params_len, GFP_KERNEL);
if (!transaction)
return -ENOMEM;
transaction->aid_len = aid_len;
transaction->params_len = params_len;
memcpy(transaction->aid, &skb->data[2], aid_len);
memcpy(transaction->params, &skb->data[aid_len + 4],
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;
}
}
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) {
timer_delete_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;
/*
* 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;
/*
* 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_ESE_HOST_ID) {
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)
return -ENOMEM;
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->dest_params->id,
NCI_NFCEE_DISABLE);
else
r = nci_nfcee_mode_set(ndev,
ndev->hci_dev->conn_info->dest_params->id,
NCI_NFCEE_ENABLE);
free_dest_params:
kfree(dest_params);
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);
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_uicc_present)
white_list[wl_size++] = ST_NCI_UICC_HOST_ID;
if (info->se_info.se_status->is_ese_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);
switch (se_idx) {
case ST_NCI_ESE_HOST_ID:
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:
/* Need to free cb_context here as at the moment we can't
* clearly indicate to the caller if the callback function
* would be called (and free it) or not. In both cases a
* negative value may be returned to the caller.
*/
kfree(cb_context);
return -ENODEV;
}
}
EXPORT_SYMBOL(st_nci_se_io);
static void st_nci_se_wt_timeout(struct timer_list *t)
{
/*
* 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 = timer_container_of(info, t,
se_info.bwi_timer);
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, &param, 1);
}
info->se_info.cb(info->se_info.cb_context, NULL, 0, -ETIME);
}
static void st_nci_se_activation_timeout(struct timer_list *t)
{
struct st_nci_info *info = timer_container_of(info, t,
se_info.se_active_timer);
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 */
timer_setup(&info->se_info.bwi_timer, st_nci_se_wt_timeout, 0);
info->se_info.bwi_active = false;
timer_setup(&info->se_info.se_active_timer,
st_nci_se_activation_timeout, 0);
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)
timer_delete_sync(&info->se_info.bwi_timer);
if (info->se_info.se_active)
timer_delete_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);
+305
View File
@@ -0,0 +1,305 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* SPI Link Layer for ST NCI based Driver
* Copyright (C) 2014-2015 STMicroelectronics SAS. All rights reserved.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/gpio/consumer.h>
#include <linux/acpi.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/nfc.h>
#include <linux/of.h>
#include <net/nfc/nci.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_TAILROOM 0
#define ST_NCI_SPI_MIN_SIZE 4 /* PCB(1) + NCI Packet header(3) */
#define ST_NCI_SPI_MAX_SIZE 250 /* req 4.2.1 */
#define ST_NCI_DRIVER_NAME "st_nci"
#define ST_NCI_SPI_DRIVER_NAME "st_nci_spi"
struct st_nci_spi_phy {
struct spi_device *spi_dev;
struct llt_ndlc *ndlc;
bool irq_active;
struct gpio_desc *gpiod_reset;
struct st_nci_se_status se_status;
};
static int st_nci_spi_enable(void *phy_id)
{
struct st_nci_spi_phy *phy = phy_id;
gpiod_set_value(phy->gpiod_reset, 0);
usleep_range(10000, 15000);
gpiod_set_value(phy->gpiod_reset, 1);
usleep_range(80000, 85000);
if (phy->ndlc->powered == 0 && phy->irq_active == 0) {
enable_irq(phy->spi_dev->irq);
phy->irq_active = true;
}
return 0;
}
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;
}
/*
* 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_spi_write(void *phy_id, struct sk_buff *skb)
{
int r;
struct st_nci_spi_phy *phy = phy_id;
struct spi_device *dev = phy->spi_dev;
struct sk_buff *skb_rx;
u8 buf[ST_NCI_SPI_MAX_SIZE + NCI_DATA_HDR_SIZE +
ST_NCI_FRAME_HEADROOM + ST_NCI_FRAME_TAILROOM];
struct spi_transfer spi_xfer = {
.tx_buf = skb->data,
.rx_buf = buf,
.len = skb->len,
};
if (phy->ndlc->hard_fault != 0)
return phy->ndlc->hard_fault;
r = spi_sync_transfer(dev, &spi_xfer, 1);
/*
* We may have received some valuable data on miso line.
* Send them back in the ndlc state machine.
*/
if (!r) {
skb_rx = alloc_skb(skb->len, GFP_KERNEL);
if (!skb_rx)
return -ENOMEM;
skb_put(skb_rx, skb->len);
memcpy(skb_rx->data, buf, skb->len);
ndlc_recv(phy->ndlc, skb_rx);
}
return r;
}
/*
* Reads an ndlc frame and returns it in a newly allocated sk_buff.
* returns:
* 0 : if received frame is complete
* -EREMOTEIO : i2c read error (fatal)
* -EBADMSG : frame was incorrect and discarded
* -ENOMEM : cannot allocate skb, frame dropped
*/
static int st_nci_spi_read(struct st_nci_spi_phy *phy,
struct sk_buff **skb)
{
int r;
u8 len;
u8 buf[ST_NCI_SPI_MAX_SIZE];
struct spi_device *dev = phy->spi_dev;
struct spi_transfer spi_xfer = {
.rx_buf = buf,
.len = ST_NCI_SPI_MIN_SIZE,
};
r = spi_sync_transfer(dev, &spi_xfer, 1);
if (r < 0)
return -EREMOTEIO;
len = be16_to_cpu(*(__be16 *) (buf + 2));
if (len > ST_NCI_SPI_MAX_SIZE) {
nfc_err(&dev->dev, "invalid frame len\n");
phy->ndlc->hard_fault = 1;
return -EBADMSG;
}
*skb = alloc_skb(ST_NCI_SPI_MIN_SIZE + len, GFP_KERNEL);
if (*skb == NULL)
return -ENOMEM;
skb_reserve(*skb, ST_NCI_SPI_MIN_SIZE);
skb_put(*skb, ST_NCI_SPI_MIN_SIZE);
memcpy((*skb)->data, buf, ST_NCI_SPI_MIN_SIZE);
if (!len)
return 0;
spi_xfer.len = len;
r = spi_sync_transfer(dev, &spi_xfer, 1);
if (r < 0) {
kfree_skb(*skb);
return -EREMOTEIO;
}
skb_put(*skb, len);
memcpy((*skb)->data + ST_NCI_SPI_MIN_SIZE, buf, len);
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_spi_phy *phy = phy_id;
struct sk_buff *skb = NULL;
int r;
if (!phy || !phy->ndlc || irq != phy->spi_dev->irq) {
WARN_ON_ONCE(1);
return IRQ_NONE;
}
if (phy->ndlc->hard_fault)
return IRQ_HANDLED;
if (!phy->ndlc->powered) {
st_nci_spi_disable(phy);
return IRQ_HANDLED;
}
r = st_nci_spi_read(phy, &skb);
if (r == -EREMOTEIO || r == -ENOMEM || r == -EBADMSG)
return IRQ_HANDLED;
ndlc_recv(phy->ndlc, skb);
return IRQ_HANDLED;
}
static const struct nfc_phy_ops spi_phy_ops = {
.write = st_nci_spi_write,
.enable = st_nci_spi_enable,
.disable = st_nci_spi_disable,
};
static const struct acpi_gpio_params reset_gpios = { 1, 0, false };
static const struct acpi_gpio_mapping acpi_st_nci_gpios[] = {
{ "reset-gpios", &reset_gpios, 1 },
{},
};
static int st_nci_spi_probe(struct spi_device *dev)
{
struct st_nci_spi_phy *phy;
int r;
/* Check SPI platform functionnalities */
if (!dev) {
pr_debug("%s: dev is NULL. Device is not accessible.\n",
__func__);
return -ENODEV;
}
phy = devm_kzalloc(&dev->dev, sizeof(struct st_nci_spi_phy),
GFP_KERNEL);
if (!phy)
return -ENOMEM;
phy->spi_dev = dev;
spi_set_drvdata(dev, phy);
r = devm_acpi_dev_add_driver_gpios(&dev->dev, acpi_st_nci_gpios);
if (r)
dev_dbg(&dev->dev, "Unable to add GPIO mapping table\n");
/* Get RESET GPIO */
phy->gpiod_reset = devm_gpiod_get(&dev->dev, "reset", GPIOD_OUT_HIGH);
if (IS_ERR(phy->gpiod_reset)) {
nfc_err(&dev->dev, "Unable to get RESET GPIO\n");
return PTR_ERR(phy->gpiod_reset);
}
phy->se_status.is_ese_present =
device_property_read_bool(&dev->dev, "ese-present");
phy->se_status.is_uicc_present =
device_property_read_bool(&dev->dev, "uicc-present");
r = ndlc_probe(phy, &spi_phy_ops, &dev->dev,
ST_NCI_FRAME_HEADROOM, ST_NCI_FRAME_TAILROOM,
&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,
IRQF_ONESHOT,
ST_NCI_SPI_DRIVER_NAME, phy);
if (r < 0)
nfc_err(&dev->dev, "Unable to register IRQ handler\n");
return r;
}
static void st_nci_spi_remove(struct spi_device *dev)
{
struct st_nci_spi_phy *phy = spi_get_drvdata(dev);
ndlc_remove(phy->ndlc);
}
static struct spi_device_id st_nci_spi_id_table[] = {
{ST_NCI_SPI_DRIVER_NAME, 0},
{"st21nfcb-spi", 0},
{}
};
MODULE_DEVICE_TABLE(spi, st_nci_spi_id_table);
static const struct acpi_device_id st_nci_spi_acpi_match[] __maybe_unused = {
{"SMO2101", 0},
{}
};
MODULE_DEVICE_TABLE(acpi, st_nci_spi_acpi_match);
static const struct of_device_id of_st_nci_spi_match[] __maybe_unused = {
{ .compatible = "st,st21nfcb-spi", },
{}
};
MODULE_DEVICE_TABLE(of, of_st_nci_spi_match);
static struct spi_driver st_nci_spi_driver = {
.driver = {
.name = ST_NCI_SPI_DRIVER_NAME,
.of_match_table = of_match_ptr(of_st_nci_spi_match),
.acpi_match_table = ACPI_PTR(st_nci_spi_acpi_match),
},
.probe = st_nci_spi_probe,
.id_table = st_nci_spi_id_table,
.remove = st_nci_spi_remove,
};
module_spi_driver(st_nci_spi_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION(DRIVER_DESC);
+142
View File
@@ -0,0 +1,142 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* NCI based Driver for STMicroelectronics NFC Chip
*
* Copyright (C) 2014 STMicroelectronics SAS. All rights reserved.
*/
#ifndef __LOCAL_ST_NCI_H_
#define __LOCAL_ST_NCI_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
/*
* 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_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;
} __packed;
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.
* @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,
LOOPBACK,
HCI_DM_FWUPD_START,
HCI_DM_FWUPD_END,
HCI_DM_VDC_MEASUREMENT_VALUE,
HCI_DM_VDC_VALUE_COMPARISON,
MANUFACTURER_SPECIFIC,
};
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, 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);
int st_nci_vendor_cmds_init(struct nci_dev *ndev);
#endif /* __LOCAL_ST_NCI_H_ */
@@ -0,0 +1,462 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Proprietary commands extension for STMicroelectronics NFC NCI Chip
*
* Copyright (C) 2014-2015 STMicroelectronics SAS. All rights reserved.
*/
#include <net/genetlink.h>
#include <linux/module.h>
#include <linux/nfc.h>
#include <linux/delay.h>
#include <net/nfc/nci_core.h>
#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)
return r;
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);
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)
return r;
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);
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)
return r;
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);
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)
return r;
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);
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)
return r;
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);
return r;
}
static int st_nci_loopback(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 <= 0)
return -EPROTO;
r = nci_nfcc_loopback(ndev, data, data_len, &skb);
if (r < 0)
return r;
msg = nfc_vendor_cmd_alloc_reply_skb(dev, ST_NCI_VENDOR_OUI,
LOOPBACK, 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);
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 const 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 = LOOPBACK,
.doit = st_nci_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)
{
return nci_set_vendor_cmds(ndev, st_nci_vendor_cmds,
sizeof(st_nci_vendor_cmds));
}
EXPORT_SYMBOL(st_nci_vendor_cmds_init);