NFC: nci: Add HCI over NCI protocol support
According to the NCI specification, one can use HCI over NCI to talk with specific NFCEE. The HCI network is viewed as one logical NFCEE. This is needed to support secure element running HCI only firmwares embedded on an NCI capable chipset, like e.g. the st21nfcb. There is some duplication between this piece of code and the HCI core code, but the latter would need to be abstracted even more to be able to use NCI as a logical transport for HCP packets. Signed-off-by: Christophe Ricard <christophe-h.ricard@st.com> Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
This commit is contained in:
parent
736bb95774
commit
11f54f2286
5 changed files with 816 additions and 19 deletions
|
@ -78,6 +78,11 @@ struct nci_ops {
|
|||
int (*se_io)(struct nci_dev *ndev, u32 se_idx,
|
||||
u8 *apdu, size_t apdu_length,
|
||||
se_io_cb_t cb, void *cb_context);
|
||||
int (*hci_load_session)(struct nci_dev *ndev);
|
||||
void (*hci_event_received)(struct nci_dev *ndev, u8 pipe, u8 event,
|
||||
struct sk_buff *skb);
|
||||
void (*hci_cmd_received)(struct nci_dev *ndev, u8 pipe, u8 cmd,
|
||||
struct sk_buff *skb);
|
||||
};
|
||||
|
||||
#define NCI_MAX_SUPPORTED_RF_INTERFACES 4
|
||||
|
@ -102,10 +107,77 @@ struct nci_conn_info {
|
|||
|
||||
#define NCI_INVALID_CONN_ID 0x80
|
||||
|
||||
#define NCI_HCI_ANY_OPEN_PIPE 0x03
|
||||
|
||||
/* Gates */
|
||||
#define NCI_HCI_ADMIN_GATE 0x00
|
||||
#define NCI_HCI_LINK_MGMT_GATE 0x06
|
||||
|
||||
/* Pipes */
|
||||
#define NCI_HCI_LINK_MGMT_PIPE 0x00
|
||||
#define NCI_HCI_ADMIN_PIPE 0x01
|
||||
|
||||
/* Generic responses */
|
||||
#define NCI_HCI_ANY_OK 0x00
|
||||
#define NCI_HCI_ANY_E_NOT_CONNECTED 0x01
|
||||
#define NCI_HCI_ANY_E_CMD_PAR_UNKNOWN 0x02
|
||||
#define NCI_HCI_ANY_E_NOK 0x03
|
||||
#define NCI_HCI_ANY_E_PIPES_FULL 0x04
|
||||
#define NCI_HCI_ANY_E_REG_PAR_UNKNOWN 0x05
|
||||
#define NCI_HCI_ANY_E_PIPE_NOT_OPENED 0x06
|
||||
#define NCI_HCI_ANY_E_CMD_NOT_SUPPORTED 0x07
|
||||
#define NCI_HCI_ANY_E_INHIBITED 0x08
|
||||
#define NCI_HCI_ANY_E_TIMEOUT 0x09
|
||||
#define NCI_HCI_ANY_E_REG_ACCESS_DENIED 0x0a
|
||||
#define NCI_HCI_ANY_E_PIPE_ACCESS_DENIED 0x0b
|
||||
|
||||
#define NCI_HCI_DO_NOT_OPEN_PIPE 0x81
|
||||
#define NCI_HCI_INVALID_PIPE 0x80
|
||||
#define NCI_HCI_INVALID_GATE 0xFF
|
||||
#define NCI_HCI_INVALID_HOST 0x80
|
||||
|
||||
#define NCI_HCI_MAX_CUSTOM_GATES 50
|
||||
#define NCI_HCI_MAX_PIPES 127
|
||||
|
||||
struct nci_hci_gate {
|
||||
u8 gate;
|
||||
u8 pipe;
|
||||
u8 dest_host;
|
||||
} __packed;
|
||||
|
||||
struct nci_hci_pipe {
|
||||
u8 gate;
|
||||
u8 host;
|
||||
} __packed;
|
||||
|
||||
struct nci_hci_init_data {
|
||||
u8 gate_count;
|
||||
struct nci_hci_gate gates[NCI_HCI_MAX_CUSTOM_GATES];
|
||||
char session_id[9];
|
||||
};
|
||||
|
||||
#define NCI_HCI_MAX_GATES 256
|
||||
|
||||
struct nci_hci_dev {
|
||||
struct nci_dev *ndev;
|
||||
struct nci_conn_info *conn_info;
|
||||
|
||||
struct nci_hci_init_data init_data;
|
||||
struct nci_hci_pipe pipes[NCI_HCI_MAX_PIPES];
|
||||
u8 gate2pipe[NCI_HCI_MAX_GATES];
|
||||
int expected_pipes;
|
||||
int count_pipes;
|
||||
|
||||
struct sk_buff_head rx_hcp_frags;
|
||||
struct work_struct msg_rx_work;
|
||||
struct sk_buff_head msg_rx_queue;
|
||||
};
|
||||
|
||||
/* NCI Core structures */
|
||||
struct nci_dev {
|
||||
struct nfc_dev *nfc_dev;
|
||||
struct nci_ops *ops;
|
||||
struct nci_hci_dev *hci_dev;
|
||||
|
||||
int tx_headroom;
|
||||
int tx_tailroom;
|
||||
|
@ -181,6 +253,10 @@ struct nci_dev *nci_allocate_device(struct nci_ops *ops,
|
|||
void nci_free_device(struct nci_dev *ndev);
|
||||
int nci_register_device(struct nci_dev *ndev);
|
||||
void nci_unregister_device(struct nci_dev *ndev);
|
||||
int nci_request(struct nci_dev *ndev,
|
||||
void (*req)(struct nci_dev *ndev,
|
||||
unsigned long opt),
|
||||
unsigned long opt, __u32 timeout);
|
||||
int nci_recv_frame(struct nci_dev *ndev, struct sk_buff *skb);
|
||||
int nci_set_config(struct nci_dev *ndev, __u8 id, size_t len, __u8 *val);
|
||||
|
||||
|
@ -190,6 +266,21 @@ int nci_core_conn_create(struct nci_dev *ndev,
|
|||
struct core_conn_create_dest_spec_params *params);
|
||||
int nci_core_conn_close(struct nci_dev *ndev, u8 conn_id);
|
||||
|
||||
struct nci_hci_dev *nci_hci_allocate(struct nci_dev *ndev);
|
||||
int nci_hci_send_event(struct nci_dev *ndev, u8 gate, u8 event,
|
||||
const u8 *param, size_t param_len);
|
||||
int nci_hci_send_cmd(struct nci_dev *ndev, u8 gate,
|
||||
u8 cmd, const u8 *param, size_t param_len,
|
||||
struct sk_buff **skb);
|
||||
int nci_hci_open_pipe(struct nci_dev *ndev, u8 pipe);
|
||||
int nci_hci_connect_gate(struct nci_dev *ndev, u8 dest_host,
|
||||
u8 dest_gate, u8 pipe);
|
||||
int nci_hci_set_param(struct nci_dev *ndev, u8 gate, u8 idx,
|
||||
const u8 *param, size_t param_len);
|
||||
int nci_hci_get_param(struct nci_dev *ndev, u8 gate, u8 idx,
|
||||
struct sk_buff **skb);
|
||||
int nci_hci_dev_session_init(struct nci_dev *ndev);
|
||||
|
||||
static inline struct sk_buff *nci_skb_alloc(struct nci_dev *ndev,
|
||||
unsigned int len,
|
||||
gfp_t how)
|
||||
|
@ -225,6 +316,8 @@ int nci_send_cmd(struct nci_dev *ndev, __u16 opcode, __u8 plen, void *payload);
|
|||
int nci_send_data(struct nci_dev *ndev, __u8 conn_id, struct sk_buff *skb);
|
||||
void nci_data_exchange_complete(struct nci_dev *ndev, struct sk_buff *skb,
|
||||
__u8 conn_id, int err);
|
||||
void nci_hci_data_received_cb(void *context, struct sk_buff *skb, int err);
|
||||
|
||||
void nci_clear_target_list(struct nci_dev *ndev);
|
||||
|
||||
/* ----- NCI requests ----- */
|
||||
|
|
|
@ -4,6 +4,6 @@
|
|||
|
||||
obj-$(CONFIG_NFC_NCI) += nci.o
|
||||
|
||||
nci-objs := core.o data.o lib.o ntf.o rsp.o
|
||||
nci-objs := core.o data.o lib.o ntf.o rsp.o hci.o
|
||||
|
||||
nci-$(CONFIG_NFC_NCI_SPI) += spi.o
|
||||
|
|
|
@ -122,10 +122,10 @@ static int __nci_request(struct nci_dev *ndev,
|
|||
return rc;
|
||||
}
|
||||
|
||||
static inline int nci_request(struct nci_dev *ndev,
|
||||
void (*req)(struct nci_dev *ndev,
|
||||
unsigned long opt),
|
||||
unsigned long opt, __u32 timeout)
|
||||
inline int nci_request(struct nci_dev *ndev,
|
||||
void (*req)(struct nci_dev *ndev,
|
||||
unsigned long opt),
|
||||
unsigned long opt, __u32 timeout)
|
||||
{
|
||||
int rc;
|
||||
|
||||
|
@ -901,7 +901,6 @@ static struct nfc_ops nci_nfc_ops = {
|
|||
};
|
||||
|
||||
/* ---- Interface to NCI drivers ---- */
|
||||
|
||||
/**
|
||||
* nci_allocate_device - allocate a new nci device
|
||||
*
|
||||
|
@ -936,13 +935,20 @@ struct nci_dev *nci_allocate_device(struct nci_ops *ops,
|
|||
tx_headroom + NCI_DATA_HDR_SIZE,
|
||||
tx_tailroom);
|
||||
if (!ndev->nfc_dev)
|
||||
goto free_exit;
|
||||
goto free_nci;
|
||||
|
||||
ndev->hci_dev = nci_hci_allocate(ndev);
|
||||
if (!ndev->hci_dev)
|
||||
goto free_nfc;
|
||||
|
||||
nfc_set_drvdata(ndev->nfc_dev, ndev);
|
||||
|
||||
return ndev;
|
||||
|
||||
free_exit:
|
||||
free_nfc:
|
||||
kfree(ndev->nfc_dev);
|
||||
|
||||
free_nci:
|
||||
kfree(ndev);
|
||||
return NULL;
|
||||
}
|
||||
|
|
686
net/nfc/nci/hci.c
Normal file
686
net/nfc/nci/hci.c
Normal file
|
@ -0,0 +1,686 @@
|
|||
/*
|
||||
* The NFC Controller Interface is the communication protocol between an
|
||||
* NFC Controller (NFCC) and a Device Host (DH).
|
||||
* This is the HCI over NCI implementation, as specified in the 10.2
|
||||
* section of the NCI 1.1 specification.
|
||||
*
|
||||
* Copyright (C) 2014 STMicroelectronics SAS. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2
|
||||
* as published by the Free Software Foundation
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/skbuff.h>
|
||||
|
||||
#include "../nfc.h"
|
||||
#include <net/nfc/nci.h>
|
||||
#include <net/nfc/nci_core.h>
|
||||
#include <linux/nfc.h>
|
||||
|
||||
struct nci_data {
|
||||
u8 conn_id;
|
||||
u8 pipe;
|
||||
u8 cmd;
|
||||
const u8 *data;
|
||||
u32 data_len;
|
||||
} __packed;
|
||||
|
||||
struct nci_hci_create_pipe_params {
|
||||
u8 src_gate;
|
||||
u8 dest_host;
|
||||
u8 dest_gate;
|
||||
} __packed;
|
||||
|
||||
struct nci_hci_create_pipe_resp {
|
||||
u8 src_host;
|
||||
u8 src_gate;
|
||||
u8 dest_host;
|
||||
u8 dest_gate;
|
||||
u8 pipe;
|
||||
} __packed;
|
||||
|
||||
struct nci_hci_delete_pipe_noti {
|
||||
u8 pipe;
|
||||
} __packed;
|
||||
|
||||
struct nci_hci_all_pipe_cleared_noti {
|
||||
u8 host;
|
||||
} __packed;
|
||||
|
||||
struct nci_hcp_message {
|
||||
u8 header; /* type -cmd,evt,rsp- + instruction */
|
||||
u8 data[];
|
||||
} __packed;
|
||||
|
||||
struct nci_hcp_packet {
|
||||
u8 header; /* cbit+pipe */
|
||||
struct nci_hcp_message message;
|
||||
} __packed;
|
||||
|
||||
#define NCI_HCI_ANY_SET_PARAMETER 0x01
|
||||
#define NCI_HCI_ANY_GET_PARAMETER 0x02
|
||||
#define NCI_HCI_ANY_CLOSE_PIPE 0x04
|
||||
|
||||
#define NCI_HFP_NO_CHAINING 0x80
|
||||
|
||||
#define NCI_NFCEE_ID_HCI 0x80
|
||||
|
||||
#define NCI_EVT_HOT_PLUG 0x03
|
||||
|
||||
#define NCI_HCI_ADMIN_PARAM_SESSION_IDENTITY 0x01
|
||||
|
||||
/* HCP headers */
|
||||
#define NCI_HCI_HCP_PACKET_HEADER_LEN 1
|
||||
#define NCI_HCI_HCP_MESSAGE_HEADER_LEN 1
|
||||
#define NCI_HCI_HCP_HEADER_LEN 2
|
||||
|
||||
/* HCP types */
|
||||
#define NCI_HCI_HCP_COMMAND 0x00
|
||||
#define NCI_HCI_HCP_EVENT 0x01
|
||||
#define NCI_HCI_HCP_RESPONSE 0x02
|
||||
|
||||
#define NCI_HCI_ADM_NOTIFY_PIPE_CREATED 0x12
|
||||
#define NCI_HCI_ADM_NOTIFY_PIPE_DELETED 0x13
|
||||
#define NCI_HCI_ADM_NOTIFY_ALL_PIPE_CLEARED 0x15
|
||||
|
||||
#define NCI_HCI_FRAGMENT 0x7f
|
||||
#define NCI_HCP_HEADER(type, instr) ((((type) & 0x03) << 6) |\
|
||||
((instr) & 0x3f))
|
||||
|
||||
#define NCI_HCP_MSG_GET_TYPE(header) ((header & 0xc0) >> 6)
|
||||
#define NCI_HCP_MSG_GET_CMD(header) (header & 0x3f)
|
||||
#define NCI_HCP_MSG_GET_PIPE(header) (header & 0x7f)
|
||||
|
||||
/* HCI core */
|
||||
static void nci_hci_reset_pipes(struct nci_hci_dev *hdev)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < NCI_HCI_MAX_PIPES; i++) {
|
||||
hdev->pipes[i].gate = NCI_HCI_INVALID_GATE;
|
||||
hdev->pipes[i].host = NCI_HCI_INVALID_HOST;
|
||||
}
|
||||
memset(hdev->gate2pipe, NCI_HCI_INVALID_PIPE, sizeof(hdev->gate2pipe));
|
||||
}
|
||||
|
||||
static void nci_hci_reset_pipes_per_host(struct nci_dev *ndev, u8 host)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < NCI_HCI_MAX_PIPES; i++) {
|
||||
if (ndev->hci_dev->pipes[i].host == host) {
|
||||
ndev->hci_dev->pipes[i].gate = NCI_HCI_INVALID_GATE;
|
||||
ndev->hci_dev->pipes[i].host = NCI_HCI_INVALID_HOST;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Fragment HCI data over NCI packet.
|
||||
* NFC Forum NCI 10.2.2 Data Exchange:
|
||||
* The payload of the Data Packets sent on the Logical Connection SHALL be
|
||||
* valid HCP packets, as defined within [ETSI_102622]. Each Data Packet SHALL
|
||||
* contain a single HCP packet. NCI Segmentation and Reassembly SHALL NOT be
|
||||
* applied to Data Messages in either direction. The HCI fragmentation mechanism
|
||||
* is used if required.
|
||||
*/
|
||||
static int nci_hci_send_data(struct nci_dev *ndev, u8 pipe,
|
||||
const u8 data_type, const u8 *data,
|
||||
size_t data_len)
|
||||
{
|
||||
struct nci_conn_info *conn_info;
|
||||
struct sk_buff *skb;
|
||||
int len, i, r;
|
||||
u8 cb = pipe;
|
||||
|
||||
conn_info = ndev->hci_dev->conn_info;
|
||||
if (!conn_info)
|
||||
return -EPROTO;
|
||||
|
||||
skb = nci_skb_alloc(ndev, 2 + conn_info->max_pkt_payload_len +
|
||||
NCI_DATA_HDR_SIZE, GFP_KERNEL);
|
||||
if (!skb)
|
||||
return -ENOMEM;
|
||||
|
||||
skb_reserve(skb, 2 + NCI_DATA_HDR_SIZE);
|
||||
*skb_push(skb, 1) = data_type;
|
||||
|
||||
i = 0;
|
||||
len = conn_info->max_pkt_payload_len;
|
||||
|
||||
do {
|
||||
/* If last packet add NCI_HFP_NO_CHAINING */
|
||||
if (i + conn_info->max_pkt_payload_len -
|
||||
(skb->len + 1) >= data_len) {
|
||||
cb |= NCI_HFP_NO_CHAINING;
|
||||
len = data_len - i;
|
||||
} else {
|
||||
len = conn_info->max_pkt_payload_len - skb->len - 1;
|
||||
}
|
||||
|
||||
*skb_push(skb, 1) = cb;
|
||||
|
||||
if (len > 0)
|
||||
memcpy(skb_put(skb, len), data + i, len);
|
||||
|
||||
r = nci_send_data(ndev, conn_info->conn_id, skb);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
i += len;
|
||||
if (i < data_len) {
|
||||
skb_trim(skb, 0);
|
||||
skb_pull(skb, len);
|
||||
}
|
||||
} while (i < data_len);
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
static void nci_hci_send_data_req(struct nci_dev *ndev, unsigned long opt)
|
||||
{
|
||||
struct nci_data *data = (struct nci_data *)opt;
|
||||
|
||||
nci_hci_send_data(ndev, data->pipe, data->cmd,
|
||||
data->data, data->data_len);
|
||||
}
|
||||
|
||||
int nci_hci_send_event(struct nci_dev *ndev, u8 gate, u8 event,
|
||||
const u8 *param, size_t param_len)
|
||||
{
|
||||
u8 pipe = ndev->hci_dev->gate2pipe[gate];
|
||||
|
||||
if (pipe == NCI_HCI_INVALID_PIPE)
|
||||
return -EADDRNOTAVAIL;
|
||||
|
||||
return nci_hci_send_data(ndev, pipe,
|
||||
NCI_HCP_HEADER(NCI_HCI_HCP_EVENT, event),
|
||||
param, param_len);
|
||||
}
|
||||
EXPORT_SYMBOL(nci_hci_send_event);
|
||||
|
||||
int nci_hci_send_cmd(struct nci_dev *ndev, u8 gate, u8 cmd,
|
||||
const u8 *param, size_t param_len,
|
||||
struct sk_buff **skb)
|
||||
{
|
||||
struct nci_conn_info *conn_info;
|
||||
struct nci_data data;
|
||||
int r;
|
||||
u8 pipe = ndev->hci_dev->gate2pipe[gate];
|
||||
|
||||
if (pipe == NCI_HCI_INVALID_PIPE)
|
||||
return -EADDRNOTAVAIL;
|
||||
|
||||
conn_info = ndev->hci_dev->conn_info;
|
||||
if (!conn_info)
|
||||
return -EPROTO;
|
||||
|
||||
data.conn_id = conn_info->conn_id;
|
||||
data.pipe = pipe;
|
||||
data.cmd = NCI_HCP_HEADER(NCI_HCI_HCP_COMMAND, cmd);
|
||||
data.data = param;
|
||||
data.data_len = param_len;
|
||||
|
||||
r = nci_request(ndev, nci_hci_send_data_req, (unsigned long)&data,
|
||||
msecs_to_jiffies(NCI_DATA_TIMEOUT));
|
||||
|
||||
if (r == NCI_STATUS_OK)
|
||||
*skb = conn_info->rx_skb;
|
||||
|
||||
return r;
|
||||
}
|
||||
EXPORT_SYMBOL(nci_hci_send_cmd);
|
||||
|
||||
static void nci_hci_event_received(struct nci_dev *ndev, u8 pipe,
|
||||
u8 event, struct sk_buff *skb)
|
||||
{
|
||||
if (ndev->ops->hci_event_received)
|
||||
ndev->ops->hci_event_received(ndev, pipe, event, skb);
|
||||
}
|
||||
|
||||
static void nci_hci_cmd_received(struct nci_dev *ndev, u8 pipe,
|
||||
u8 cmd, struct sk_buff *skb)
|
||||
{
|
||||
u8 gate = ndev->hci_dev->pipes[pipe].gate;
|
||||
u8 status = NCI_HCI_ANY_OK | ~NCI_HCI_FRAGMENT;
|
||||
u8 dest_gate, new_pipe;
|
||||
struct nci_hci_create_pipe_resp *create_info;
|
||||
struct nci_hci_delete_pipe_noti *delete_info;
|
||||
struct nci_hci_all_pipe_cleared_noti *cleared_info;
|
||||
|
||||
pr_debug("from gate %x pipe %x cmd %x\n", gate, pipe, cmd);
|
||||
|
||||
switch (cmd) {
|
||||
case NCI_HCI_ADM_NOTIFY_PIPE_CREATED:
|
||||
if (skb->len != 5) {
|
||||
status = NCI_HCI_ANY_E_NOK;
|
||||
goto exit;
|
||||
}
|
||||
create_info = (struct nci_hci_create_pipe_resp *)skb->data;
|
||||
dest_gate = create_info->dest_gate;
|
||||
new_pipe = create_info->pipe;
|
||||
|
||||
/* Save the new created pipe and bind with local gate,
|
||||
* the description for skb->data[3] is destination gate id
|
||||
* but since we received this cmd from host controller, we
|
||||
* are the destination and it is our local gate
|
||||
*/
|
||||
ndev->hci_dev->gate2pipe[dest_gate] = new_pipe;
|
||||
ndev->hci_dev->pipes[new_pipe].gate = dest_gate;
|
||||
ndev->hci_dev->pipes[new_pipe].host =
|
||||
create_info->src_host;
|
||||
break;
|
||||
case NCI_HCI_ANY_OPEN_PIPE:
|
||||
/* If the pipe is not created report an error */
|
||||
if (gate == NCI_HCI_INVALID_GATE) {
|
||||
status = NCI_HCI_ANY_E_NOK;
|
||||
goto exit;
|
||||
}
|
||||
break;
|
||||
case NCI_HCI_ADM_NOTIFY_PIPE_DELETED:
|
||||
if (skb->len != 1) {
|
||||
status = NCI_HCI_ANY_E_NOK;
|
||||
goto exit;
|
||||
}
|
||||
delete_info = (struct nci_hci_delete_pipe_noti *)skb->data;
|
||||
|
||||
ndev->hci_dev->pipes[delete_info->pipe].gate =
|
||||
NCI_HCI_INVALID_GATE;
|
||||
ndev->hci_dev->pipes[delete_info->pipe].host =
|
||||
NCI_HCI_INVALID_HOST;
|
||||
break;
|
||||
case NCI_HCI_ADM_NOTIFY_ALL_PIPE_CLEARED:
|
||||
if (skb->len != 1) {
|
||||
status = NCI_HCI_ANY_E_NOK;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
cleared_info =
|
||||
(struct nci_hci_all_pipe_cleared_noti *)skb->data;
|
||||
nci_hci_reset_pipes_per_host(ndev, cleared_info->host);
|
||||
break;
|
||||
default:
|
||||
pr_debug("Discarded unknown cmd %x to gate %x\n", cmd, gate);
|
||||
break;
|
||||
}
|
||||
|
||||
if (ndev->ops->hci_cmd_received)
|
||||
ndev->ops->hci_cmd_received(ndev, pipe, cmd, skb);
|
||||
|
||||
exit:
|
||||
nci_hci_send_data(ndev, pipe, status, NULL, 0);
|
||||
|
||||
kfree_skb(skb);
|
||||
}
|
||||
|
||||
static void nci_hci_resp_received(struct nci_dev *ndev, u8 pipe,
|
||||
u8 result, struct sk_buff *skb)
|
||||
{
|
||||
struct nci_conn_info *conn_info;
|
||||
u8 status = result;
|
||||
|
||||
if (result != NCI_HCI_ANY_OK)
|
||||
goto exit;
|
||||
|
||||
conn_info = ndev->hci_dev->conn_info;
|
||||
if (!conn_info) {
|
||||
status = NCI_STATUS_REJECTED;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
conn_info->rx_skb = skb;
|
||||
|
||||
exit:
|
||||
nci_req_complete(ndev, status);
|
||||
}
|
||||
|
||||
/* Receive hcp message for pipe, with type and cmd.
|
||||
* skb contains optional message data only.
|
||||
*/
|
||||
static void nci_hci_hcp_message_rx(struct nci_dev *ndev, u8 pipe,
|
||||
u8 type, u8 instruction, struct sk_buff *skb)
|
||||
{
|
||||
switch (type) {
|
||||
case NCI_HCI_HCP_RESPONSE:
|
||||
nci_hci_resp_received(ndev, pipe, instruction, skb);
|
||||
break;
|
||||
case NCI_HCI_HCP_COMMAND:
|
||||
nci_hci_cmd_received(ndev, pipe, instruction, skb);
|
||||
break;
|
||||
case NCI_HCI_HCP_EVENT:
|
||||
nci_hci_event_received(ndev, pipe, instruction, skb);
|
||||
break;
|
||||
default:
|
||||
pr_err("UNKNOWN MSG Type %d, instruction=%d\n",
|
||||
type, instruction);
|
||||
kfree_skb(skb);
|
||||
break;
|
||||
}
|
||||
|
||||
nci_req_complete(ndev, 0);
|
||||
}
|
||||
|
||||
static void nci_hci_msg_rx_work(struct work_struct *work)
|
||||
{
|
||||
struct nci_hci_dev *hdev =
|
||||
container_of(work, struct nci_hci_dev, msg_rx_work);
|
||||
struct sk_buff *skb;
|
||||
struct nci_hcp_message *message;
|
||||
u8 pipe, type, instruction;
|
||||
|
||||
while ((skb = skb_dequeue(&hdev->msg_rx_queue)) != NULL) {
|
||||
pipe = skb->data[0];
|
||||
skb_pull(skb, NCI_HCI_HCP_PACKET_HEADER_LEN);
|
||||
message = (struct nci_hcp_message *)skb->data;
|
||||
type = NCI_HCP_MSG_GET_TYPE(message->header);
|
||||
instruction = NCI_HCP_MSG_GET_CMD(message->header);
|
||||
skb_pull(skb, NCI_HCI_HCP_MESSAGE_HEADER_LEN);
|
||||
|
||||
nci_hci_hcp_message_rx(hdev->ndev, pipe,
|
||||
type, instruction, skb);
|
||||
}
|
||||
}
|
||||
|
||||
void nci_hci_data_received_cb(void *context,
|
||||
struct sk_buff *skb, int err)
|
||||
{
|
||||
struct nci_dev *ndev = (struct nci_dev *)context;
|
||||
struct nci_hcp_packet *packet;
|
||||
u8 pipe, type, instruction;
|
||||
struct sk_buff *hcp_skb;
|
||||
struct sk_buff *frag_skb;
|
||||
int msg_len;
|
||||
|
||||
pr_debug("\n");
|
||||
|
||||
if (err) {
|
||||
nci_req_complete(ndev, err);
|
||||
return;
|
||||
}
|
||||
|
||||
packet = (struct nci_hcp_packet *)skb->data;
|
||||
if ((packet->header & ~NCI_HCI_FRAGMENT) == 0) {
|
||||
skb_queue_tail(&ndev->hci_dev->rx_hcp_frags, skb);
|
||||
return;
|
||||
}
|
||||
|
||||
/* it's the last fragment. Does it need re-aggregation? */
|
||||
if (skb_queue_len(&ndev->hci_dev->rx_hcp_frags)) {
|
||||
pipe = packet->header & NCI_HCI_FRAGMENT;
|
||||
skb_queue_tail(&ndev->hci_dev->rx_hcp_frags, skb);
|
||||
|
||||
msg_len = 0;
|
||||
skb_queue_walk(&ndev->hci_dev->rx_hcp_frags, frag_skb) {
|
||||
msg_len += (frag_skb->len -
|
||||
NCI_HCI_HCP_PACKET_HEADER_LEN);
|
||||
}
|
||||
|
||||
hcp_skb = nfc_alloc_recv_skb(NCI_HCI_HCP_PACKET_HEADER_LEN +
|
||||
msg_len, GFP_KERNEL);
|
||||
if (!hcp_skb) {
|
||||
nci_req_complete(ndev, -ENOMEM);
|
||||
return;
|
||||
}
|
||||
|
||||
*skb_put(hcp_skb, NCI_HCI_HCP_PACKET_HEADER_LEN) = pipe;
|
||||
|
||||
skb_queue_walk(&ndev->hci_dev->rx_hcp_frags, frag_skb) {
|
||||
msg_len = frag_skb->len - NCI_HCI_HCP_PACKET_HEADER_LEN;
|
||||
memcpy(skb_put(hcp_skb, msg_len), frag_skb->data +
|
||||
NCI_HCI_HCP_PACKET_HEADER_LEN, msg_len);
|
||||
}
|
||||
|
||||
skb_queue_purge(&ndev->hci_dev->rx_hcp_frags);
|
||||
} else {
|
||||
packet->header &= NCI_HCI_FRAGMENT;
|
||||
hcp_skb = skb;
|
||||
}
|
||||
|
||||
/* if this is a response, dispatch immediately to
|
||||
* unblock waiting cmd context. Otherwise, enqueue to dispatch
|
||||
* in separate context where handler can also execute command.
|
||||
*/
|
||||
packet = (struct nci_hcp_packet *)hcp_skb->data;
|
||||
type = NCI_HCP_MSG_GET_TYPE(packet->message.header);
|
||||
if (type == NCI_HCI_HCP_RESPONSE) {
|
||||
pipe = packet->header;
|
||||
instruction = NCI_HCP_MSG_GET_CMD(packet->message.header);
|
||||
skb_pull(hcp_skb, NCI_HCI_HCP_PACKET_HEADER_LEN +
|
||||
NCI_HCI_HCP_MESSAGE_HEADER_LEN);
|
||||
nci_hci_hcp_message_rx(ndev, pipe, type, instruction, hcp_skb);
|
||||
} else {
|
||||
skb_queue_tail(&ndev->hci_dev->msg_rx_queue, hcp_skb);
|
||||
schedule_work(&ndev->hci_dev->msg_rx_work);
|
||||
}
|
||||
}
|
||||
|
||||
int nci_hci_open_pipe(struct nci_dev *ndev, u8 pipe)
|
||||
{
|
||||
struct nci_data data;
|
||||
struct nci_conn_info *conn_info;
|
||||
|
||||
conn_info = ndev->hci_dev->conn_info;
|
||||
if (!conn_info)
|
||||
return -EPROTO;
|
||||
|
||||
data.conn_id = conn_info->conn_id;
|
||||
data.pipe = pipe;
|
||||
data.cmd = NCI_HCP_HEADER(NCI_HCI_HCP_COMMAND,
|
||||
NCI_HCI_ANY_OPEN_PIPE);
|
||||
data.data = NULL;
|
||||
data.data_len = 0;
|
||||
|
||||
return nci_request(ndev, nci_hci_send_data_req,
|
||||
(unsigned long)&data,
|
||||
msecs_to_jiffies(NCI_DATA_TIMEOUT));
|
||||
}
|
||||
EXPORT_SYMBOL(nci_hci_open_pipe);
|
||||
|
||||
int nci_hci_set_param(struct nci_dev *ndev, u8 gate, u8 idx,
|
||||
const u8 *param, size_t param_len)
|
||||
{
|
||||
struct nci_conn_info *conn_info;
|
||||
struct nci_data data;
|
||||
int r;
|
||||
u8 *tmp;
|
||||
u8 pipe = ndev->hci_dev->gate2pipe[gate];
|
||||
|
||||
pr_debug("idx=%d to gate %d\n", idx, gate);
|
||||
|
||||
if (pipe == NCI_HCI_INVALID_PIPE)
|
||||
return -EADDRNOTAVAIL;
|
||||
|
||||
conn_info = ndev->hci_dev->conn_info;
|
||||
if (!conn_info)
|
||||
return -EPROTO;
|
||||
|
||||
tmp = kmalloc(1 + param_len, GFP_KERNEL);
|
||||
if (!tmp)
|
||||
return -ENOMEM;
|
||||
|
||||
*tmp = idx;
|
||||
memcpy(tmp + 1, param, param_len);
|
||||
|
||||
data.conn_id = conn_info->conn_id;
|
||||
data.pipe = pipe;
|
||||
data.cmd = NCI_HCP_HEADER(NCI_HCI_HCP_COMMAND,
|
||||
NCI_HCI_ANY_SET_PARAMETER);
|
||||
data.data = tmp;
|
||||
data.data_len = param_len + 1;
|
||||
|
||||
r = nci_request(ndev, nci_hci_send_data_req,
|
||||
(unsigned long)&data,
|
||||
msecs_to_jiffies(NCI_DATA_TIMEOUT));
|
||||
|
||||
kfree(tmp);
|
||||
return r;
|
||||
}
|
||||
EXPORT_SYMBOL(nci_hci_set_param);
|
||||
|
||||
int nci_hci_get_param(struct nci_dev *ndev, u8 gate, u8 idx,
|
||||
struct sk_buff **skb)
|
||||
{
|
||||
struct nci_conn_info *conn_info;
|
||||
struct nci_data data;
|
||||
int r;
|
||||
u8 pipe = ndev->hci_dev->gate2pipe[gate];
|
||||
|
||||
pr_debug("idx=%d to gate %d\n", idx, gate);
|
||||
|
||||
if (pipe == NCI_HCI_INVALID_PIPE)
|
||||
return -EADDRNOTAVAIL;
|
||||
|
||||
conn_info = ndev->hci_dev->conn_info;
|
||||
if (!conn_info)
|
||||
return -EPROTO;
|
||||
|
||||
data.conn_id = conn_info->conn_id;
|
||||
data.pipe = pipe;
|
||||
data.cmd = NCI_HCP_HEADER(NCI_HCI_HCP_COMMAND,
|
||||
NCI_HCI_ANY_GET_PARAMETER);
|
||||
data.data = &idx;
|
||||
data.data_len = 1;
|
||||
|
||||
r = nci_request(ndev, nci_hci_send_data_req, (unsigned long)&data,
|
||||
msecs_to_jiffies(NCI_DATA_TIMEOUT));
|
||||
|
||||
if (r == NCI_STATUS_OK)
|
||||
*skb = conn_info->rx_skb;
|
||||
|
||||
return r;
|
||||
}
|
||||
EXPORT_SYMBOL(nci_hci_get_param);
|
||||
|
||||
int nci_hci_connect_gate(struct nci_dev *ndev,
|
||||
u8 dest_host, u8 dest_gate, u8 pipe)
|
||||
{
|
||||
int r;
|
||||
|
||||
if (pipe == NCI_HCI_DO_NOT_OPEN_PIPE)
|
||||
return 0;
|
||||
|
||||
if (ndev->hci_dev->gate2pipe[dest_gate] != NCI_HCI_INVALID_PIPE)
|
||||
return -EADDRINUSE;
|
||||
|
||||
if (pipe != NCI_HCI_INVALID_PIPE)
|
||||
goto open_pipe;
|
||||
|
||||
switch (dest_gate) {
|
||||
case NCI_HCI_LINK_MGMT_GATE:
|
||||
pipe = NCI_HCI_LINK_MGMT_PIPE;
|
||||
break;
|
||||
case NCI_HCI_ADMIN_GATE:
|
||||
pipe = NCI_HCI_ADMIN_PIPE;
|
||||
break;
|
||||
}
|
||||
|
||||
open_pipe:
|
||||
r = nci_hci_open_pipe(ndev, pipe);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
ndev->hci_dev->pipes[pipe].gate = dest_gate;
|
||||
ndev->hci_dev->pipes[pipe].host = dest_host;
|
||||
ndev->hci_dev->gate2pipe[dest_gate] = pipe;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(nci_hci_connect_gate);
|
||||
|
||||
static int nci_hci_dev_connect_gates(struct nci_dev *ndev,
|
||||
u8 gate_count,
|
||||
struct nci_hci_gate *gates)
|
||||
{
|
||||
int r;
|
||||
|
||||
while (gate_count--) {
|
||||
r = nci_hci_connect_gate(ndev, gates->dest_host,
|
||||
gates->gate, gates->pipe);
|
||||
if (r < 0)
|
||||
return r;
|
||||
gates++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int nci_hci_dev_session_init(struct nci_dev *ndev)
|
||||
{
|
||||
struct sk_buff *skb;
|
||||
int r;
|
||||
|
||||
ndev->hci_dev->count_pipes = 0;
|
||||
ndev->hci_dev->expected_pipes = 0;
|
||||
|
||||
nci_hci_reset_pipes(ndev->hci_dev);
|
||||
|
||||
if (ndev->hci_dev->init_data.gates[0].gate != NCI_HCI_ADMIN_GATE)
|
||||
return -EPROTO;
|
||||
|
||||
r = nci_hci_connect_gate(ndev,
|
||||
ndev->hci_dev->init_data.gates[0].dest_host,
|
||||
ndev->hci_dev->init_data.gates[0].gate,
|
||||
ndev->hci_dev->init_data.gates[0].pipe);
|
||||
if (r < 0)
|
||||
goto exit;
|
||||
|
||||
r = nci_hci_get_param(ndev, NCI_HCI_ADMIN_GATE,
|
||||
NCI_HCI_ADMIN_PARAM_SESSION_IDENTITY, &skb);
|
||||
if (r < 0)
|
||||
goto exit;
|
||||
|
||||
if (skb->len &&
|
||||
skb->len == strlen(ndev->hci_dev->init_data.session_id) &&
|
||||
memcmp(ndev->hci_dev->init_data.session_id,
|
||||
skb->data, skb->len) == 0 &&
|
||||
ndev->ops->hci_load_session) {
|
||||
/* Restore gate<->pipe table from some proprietary location. */
|
||||
r = ndev->ops->hci_load_session(ndev);
|
||||
if (r < 0)
|
||||
goto exit;
|
||||
} else {
|
||||
r = nci_hci_dev_connect_gates(ndev,
|
||||
ndev->hci_dev->init_data.gate_count,
|
||||
ndev->hci_dev->init_data.gates);
|
||||
if (r < 0)
|
||||
goto exit;
|
||||
|
||||
r = nci_hci_set_param(ndev, NCI_HCI_ADMIN_GATE,
|
||||
NCI_HCI_ADMIN_PARAM_SESSION_IDENTITY,
|
||||
ndev->hci_dev->init_data.session_id,
|
||||
strlen(ndev->hci_dev->init_data.session_id));
|
||||
}
|
||||
if (r == 0)
|
||||
goto exit;
|
||||
|
||||
exit:
|
||||
kfree_skb(skb);
|
||||
|
||||
return r;
|
||||
}
|
||||
EXPORT_SYMBOL(nci_hci_dev_session_init);
|
||||
|
||||
struct nci_hci_dev *nci_hci_allocate(struct nci_dev *ndev)
|
||||
{
|
||||
struct nci_hci_dev *hdev;
|
||||
|
||||
hdev = kzalloc(sizeof(*hdev), GFP_KERNEL);
|
||||
if (!hdev)
|
||||
return NULL;
|
||||
|
||||
skb_queue_head_init(&hdev->rx_hcp_frags);
|
||||
INIT_WORK(&hdev->msg_rx_work, nci_hci_msg_rx_work);
|
||||
skb_queue_head_init(&hdev->msg_rx_queue);
|
||||
hdev->ndev = ndev;
|
||||
|
||||
return hdev;
|
||||
}
|
|
@ -723,19 +723,31 @@ static void nci_nfcee_discover_ntf_packet(struct nci_dev *ndev,
|
|||
|
||||
pr_debug("\n");
|
||||
|
||||
conn_info = devm_kzalloc(&ndev->nfc_dev->dev,
|
||||
sizeof(struct nci_conn_info), GFP_KERNEL);
|
||||
if (!conn_info) {
|
||||
status = NCI_STATUS_REJECTED;
|
||||
goto exit;
|
||||
/* NFCForum NCI 9.2.1 HCI Network Specific Handling
|
||||
* If the NFCC supports the HCI Network, it SHALL return one,
|
||||
* and only one, NFCEE_DISCOVER_NTF with a Protocol type of
|
||||
* “HCI Access”, even if the HCI Network contains multiple NFCEEs.
|
||||
*/
|
||||
if (!ndev->hci_dev->conn_info) {
|
||||
conn_info = devm_kzalloc(&ndev->nfc_dev->dev,
|
||||
sizeof(*conn_info), GFP_KERNEL);
|
||||
if (!conn_info) {
|
||||
status = NCI_STATUS_REJECTED;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
conn_info->id = nfcee_ntf->nfcee_id;
|
||||
conn_info->conn_id = NCI_INVALID_CONN_ID;
|
||||
|
||||
conn_info->data_exchange_cb = nci_hci_data_received_cb;
|
||||
conn_info->data_exchange_cb_context = ndev;
|
||||
|
||||
INIT_LIST_HEAD(&conn_info->list);
|
||||
list_add(&conn_info->list, &ndev->conn_info_list);
|
||||
|
||||
ndev->hci_dev->conn_info = conn_info;
|
||||
}
|
||||
|
||||
conn_info->id = nfcee_ntf->nfcee_id;
|
||||
conn_info->conn_id = NCI_INVALID_CONN_ID;
|
||||
|
||||
INIT_LIST_HEAD(&conn_info->list);
|
||||
list_add(&conn_info->list, &ndev->conn_info_list);
|
||||
|
||||
exit:
|
||||
nci_req_complete(ndev, status);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue