]> git.kernelconcepts.de Git - karo-tx-linux.git/blobdiff - drivers/nfc/pn533.c
NFC: pn533: Add support for incoming fragmented frame in target mode
[karo-tx-linux.git] / drivers / nfc / pn533.c
index 0b7e928ceb3d1aff55e3acf7aeb00b50d18e80b2..cd20641e7443a3893d4e2a09d3aa1def14f824ba 100644 (file)
@@ -373,6 +373,7 @@ struct pn533 {
        struct delayed_work poll_work;
        struct work_struct mi_rx_work;
        struct work_struct mi_tx_work;
+       struct work_struct mi_tm_rx_work;
        struct work_struct tg_work;
        struct work_struct rf_work;
 
@@ -1624,27 +1625,81 @@ static struct sk_buff *pn533_alloc_poll_tg_frame(struct pn533 *dev)
 
 #define PN533_CMD_DATAEXCH_HEAD_LEN 1
 #define PN533_CMD_DATAEXCH_DATA_MAXLEN 262
+static void pn533_wq_tm_mi_recv(struct work_struct *work);
+static struct sk_buff *pn533_build_response(struct pn533 *dev);
+
 static int pn533_tm_get_data_complete(struct pn533 *dev, void *arg,
                                      struct sk_buff *resp)
 {
-       u8 status;
+       struct sk_buff *skb;
+       u8 status, ret, mi;
+       int rc;
 
        dev_dbg(&dev->interface->dev, "%s\n", __func__);
 
-       if (IS_ERR(resp))
+       if (IS_ERR(resp)) {
+               skb_queue_purge(&dev->resp_q);
                return PTR_ERR(resp);
+       }
 
        status = resp->data[0];
+
+       ret = status & PN533_CMD_RET_MASK;
+       mi = status & PN533_CMD_MI_MASK;
+
        skb_pull(resp, sizeof(status));
 
-       if (status != 0) {
-               nfc_tm_deactivated(dev->nfc_dev);
-               dev->tgt_mode = 0;
-               dev_kfree_skb(resp);
-               return 0;
+       if (ret != PN533_CMD_RET_SUCCESS) {
+               rc = -EIO;
+               goto error;
+       }
+
+       skb_queue_tail(&dev->resp_q, resp);
+
+       if (mi) {
+               queue_work(dev->wq, &dev->mi_tm_rx_work);
+               return -EINPROGRESS;
+       }
+
+       skb = pn533_build_response(dev);
+       if (!skb) {
+               rc = -EIO;
+               goto error;
        }
 
-       return nfc_tm_data_received(dev->nfc_dev, resp);
+       return nfc_tm_data_received(dev->nfc_dev, skb);
+
+error:
+       nfc_tm_deactivated(dev->nfc_dev);
+       dev->tgt_mode = 0;
+       skb_queue_purge(&dev->resp_q);
+       dev_kfree_skb(resp);
+
+       return rc;
+}
+
+static void pn533_wq_tm_mi_recv(struct work_struct *work)
+{
+       struct pn533 *dev = container_of(work, struct pn533, mi_tm_rx_work);
+       struct sk_buff *skb;
+       int rc;
+
+       dev_dbg(&dev->interface->dev, "%s\n", __func__);
+
+       skb = pn533_alloc_skb(dev, 0);
+       if (!skb)
+               return;
+
+       rc = pn533_send_cmd_direct_async(dev,
+                                       PN533_CMD_TG_GET_DATA,
+                                       skb,
+                                       pn533_tm_get_data_complete,
+                                       NULL);
+
+       if (rc < 0)
+               dev_kfree_skb(skb);
+
+       return;
 }
 
 static void pn533_wq_tg_get_data(struct work_struct *work)
@@ -3055,6 +3110,7 @@ static int pn533_probe(struct usb_interface *interface,
        INIT_WORK(&dev->mi_rx_work, pn533_wq_mi_recv);
        INIT_WORK(&dev->mi_tx_work, pn533_wq_mi_send);
        INIT_WORK(&dev->tg_work, pn533_wq_tg_get_data);
+       INIT_WORK(&dev->mi_tm_rx_work, pn533_wq_tm_mi_recv);
        INIT_DELAYED_WORK(&dev->poll_work, pn533_wq_poll);
        INIT_WORK(&dev->rf_work, pn533_wq_rf);
        dev->wq = alloc_ordered_workqueue("pn533", 0);