1
0
Fork 0

NFC: pn533 Rx chaining support

When buffers on the receiption path exceed 262 bytes, the pn533 uses
a chaining mechanism where the initiator has to send NULL data frames
to fetch the remaining frames.
We do that from a workqueue context while holding the cmd lock. Once the
MI bit is gone, we aggregate the queued received skbs.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
hifive-unleashed-5.1
Samuel Ortiz 2012-04-10 19:43:18 +02:00 committed by John W. Linville
parent 4849f85ee3
commit 6ff73fd239
1 changed files with 129 additions and 17 deletions

View File

@ -266,8 +266,11 @@ struct pn533 {
int in_maxlen;
struct pn533_frame *in_frame;
struct sk_buff_head resp_q;
struct workqueue_struct *wq;
struct work_struct cmd_work;
struct work_struct mi_work;
struct pn533_frame *wq_in_frame;
int wq_in_error;
@ -1256,6 +1259,8 @@ static void pn533_deactivate_target(struct nfc_dev *nfc_dev, u32 target_idx)
dev->tgt_active_prot = 0;
skb_queue_purge(&dev->resp_q);
pn533_tx_frame_init(dev->out_frame, PN533_CMD_IN_RELEASE);
tg = 1;
@ -1454,11 +1459,49 @@ struct pn533_data_exchange_arg {
void *cb_context;
};
static struct sk_buff *pn533_build_response(struct pn533 *dev)
{
struct sk_buff *skb, *tmp, *t;
unsigned int skb_len = 0, tmp_len = 0;
nfc_dev_dbg(&dev->interface->dev, "%s\n", __func__);
if (skb_queue_empty(&dev->resp_q))
return NULL;
if (skb_queue_len(&dev->resp_q) == 1) {
skb = skb_dequeue(&dev->resp_q);
goto out;
}
skb_queue_walk_safe(&dev->resp_q, tmp, t)
skb_len += tmp->len;
nfc_dev_dbg(&dev->interface->dev, "%s total length %d\n",
__func__, skb_len);
skb = alloc_skb(skb_len, GFP_KERNEL);
if (skb == NULL)
goto out;
skb_put(skb, skb_len);
skb_queue_walk_safe(&dev->resp_q, tmp, t) {
memcpy(skb->data + tmp_len, tmp->data, tmp->len);
tmp_len += tmp->len;
}
out:
skb_queue_purge(&dev->resp_q);
return skb;
}
static int pn533_data_exchange_complete(struct pn533 *dev, void *_arg,
u8 *params, int params_len)
{
struct pn533_data_exchange_arg *arg = _arg;
struct sk_buff *skb_resp = arg->skb_resp;
struct sk_buff *skb = NULL, *skb_resp = arg->skb_resp;
struct pn533_frame *in_frame = (struct pn533_frame *) skb_resp->data;
int err = 0;
u8 status;
@ -1466,15 +1509,13 @@ static int pn533_data_exchange_complete(struct pn533 *dev, void *_arg,
nfc_dev_dbg(&dev->interface->dev, "%s", __func__);
dev_kfree_skb_irq(arg->skb_out);
dev_kfree_skb(arg->skb_out);
if (params_len < 0) { /* error */
err = params_len;
goto error;
}
skb_put(skb_resp, PN533_FRAME_SIZE(in_frame));
status = params[0];
cmd_ret = status & PN533_CMD_RET_MASK;
@ -1485,25 +1526,27 @@ static int pn533_data_exchange_complete(struct pn533 *dev, void *_arg,
goto error;
}
if (status & PN533_CMD_MI_MASK) {
/* TODO: Implement support to multi-part data exchange */
nfc_dev_err(&dev->interface->dev, "Multi-part message not yet"
" supported");
/* Prevent the other messages from controller */
pn533_send_ack(dev, GFP_ATOMIC);
err = -ENOSYS;
goto error;
}
skb_put(skb_resp, PN533_FRAME_SIZE(in_frame));
skb_pull(skb_resp, PN533_CMD_DATAEXCH_HEAD_LEN);
skb_trim(skb_resp, skb_resp->len - PN533_FRAME_TAIL_SIZE);
skb_queue_tail(&dev->resp_q, skb_resp);
arg->cb(arg->cb_context, skb_resp, 0);
if (status & PN533_CMD_MI_MASK) {
queue_work(dev->wq, &dev->mi_work);
return -EINPROGRESS;
}
skb = pn533_build_response(dev);
if (skb == NULL)
goto error;
arg->cb(arg->cb_context, skb, 0);
kfree(arg);
return 0;
error:
dev_kfree_skb_irq(skb_resp);
skb_queue_purge(&dev->resp_q);
dev_kfree_skb(skb_resp);
arg->cb(arg->cb_context, NULL, err);
kfree(arg);
return 0;
@ -1578,6 +1621,68 @@ error:
return rc;
}
static void pn533_wq_mi_recv(struct work_struct *work)
{
struct pn533 *dev = container_of(work, struct pn533, mi_work);
struct sk_buff *skb_cmd;
struct pn533_data_exchange_arg *arg = dev->cmd_complete_arg;
struct pn533_frame *out_frame, *in_frame;
struct sk_buff *skb_resp;
int skb_resp_len;
int rc;
nfc_dev_dbg(&dev->interface->dev, "%s", __func__);
/* This is a zero payload size skb */
skb_cmd = alloc_skb(PN533_CMD_DATAEXCH_HEAD_LEN + PN533_FRAME_TAIL_SIZE,
GFP_KERNEL);
if (skb_cmd == NULL)
goto error_cmd;
skb_reserve(skb_cmd, PN533_CMD_DATAEXCH_HEAD_LEN);
rc = pn533_data_exchange_tx_frame(dev, skb_cmd);
if (rc)
goto error_frame;
skb_resp_len = PN533_CMD_DATAEXCH_HEAD_LEN +
PN533_CMD_DATAEXCH_DATA_MAXLEN +
PN533_FRAME_TAIL_SIZE;
skb_resp = alloc_skb(skb_resp_len, GFP_KERNEL);
if (!skb_resp) {
rc = -ENOMEM;
goto error_frame;
}
in_frame = (struct pn533_frame *) skb_resp->data;
out_frame = (struct pn533_frame *) skb_cmd->data;
arg->skb_resp = skb_resp;
arg->skb_out = skb_cmd;
rc = __pn533_send_cmd_frame_async(dev, out_frame, in_frame,
skb_resp_len,
pn533_data_exchange_complete,
dev->cmd_complete_arg, GFP_KERNEL);
if (!rc)
return;
nfc_dev_err(&dev->interface->dev, "Error %d when trying to"
" perform data_exchange", rc);
kfree_skb(skb_resp);
error_frame:
kfree_skb(skb_cmd);
error_cmd:
pn533_send_ack(dev, GFP_KERNEL);
kfree(arg);
up(&dev->cmd_lock);
}
static int pn533_set_configuration(struct pn533 *dev, u8 cfgitem, u8 *cfgdata,
u8 cfgdata_len)
{
@ -1676,10 +1781,15 @@ static int pn533_probe(struct usb_interface *interface,
pn533_send_complete, dev);
INIT_WORK(&dev->cmd_work, pn533_wq_cmd_complete);
dev->wq = create_singlethread_workqueue("pn533");
INIT_WORK(&dev->mi_work, pn533_wq_mi_recv);
dev->wq = alloc_workqueue("pn533",
WQ_NON_REENTRANT | WQ_UNBOUND | WQ_MEM_RECLAIM,
1);
if (dev->wq == NULL)
goto error;
skb_queue_head_init(&dev->resp_q);
usb_set_intfdata(interface, dev);
pn533_tx_frame_init(dev->out_frame, PN533_CMD_GET_FIRMWARE_VERSION);
@ -1756,6 +1866,8 @@ static void pn533_disconnect(struct usb_interface *interface)
destroy_workqueue(dev->wq);
skb_queue_purge(&dev->resp_q);
kfree(dev->in_frame);
usb_free_urb(dev->in_urb);
kfree(dev->out_frame);