NFC: digital: Add NFC-DEP Send Chaining Support
When the NFC-DEP code is given a packet to send
that is larger than the peer's maximum payload,
its supposed to set the 'MI' bit in the 'I' PDU's
Protocol Frame Byte (PFB). Setting this bit
indicates that NFC-DEP chaining is to occur.
When NFC-DEP chaining is progress, sender 'I' PDUs
are acknowledged with 'ACK' PDUs until the last 'I'
PDU in the chain (which has the 'MI' bit cleared)
is responded to with a normal 'I' PDU. This can
occur while in Initiator mode or in Target mode.
Sender NFC-DEP chaining is currently not implemented
in the digital layer so add that support. Unfortunately,
since sending a frame may require writing the CRC to the
end of the data, the relevant data part of the original
skb must be copied for each intermediate frame.
Reviewed-by: Thierry Escande <thierry.escande@linux.intel.com>
Tested-by: Thierry Escande <thierry.escande@linux.intel.com>
Signed-off-by: Mark A. Greer <mgreer@animalcreek.com>
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
diff --git a/net/nfc/digital_dep.c b/net/nfc/digital_dep.c
index b787900..e613c29 100644
--- a/net/nfc/digital_dep.c
+++ b/net/nfc/digital_dep.c
@@ -42,14 +42,20 @@
#define DIGITAL_GB_BIT 0x02
+#define DIGITAL_NFC_DEP_REQ_RES_HEADROOM 2 /* SoD: [SB (NFC-A)] + LEN */
+#define DIGITAL_NFC_DEP_REQ_RES_TAILROOM 2 /* EoD: 2-byte CRC */
+
#define DIGITAL_NFC_DEP_PFB_TYPE(pfb) ((pfb) & 0xE0)
#define DIGITAL_NFC_DEP_PFB_TIMEOUT_BIT 0x10
+#define DIGITAL_NFC_DEP_PFB_MI_BIT 0x10
+#define DIGITAL_NFC_DEP_PFB_NACK_BIT 0x10
#define DIGITAL_NFC_DEP_PFB_DID_BIT 0x04
#define DIGITAL_NFC_DEP_PFB_IS_TIMEOUT(pfb) \
((pfb) & DIGITAL_NFC_DEP_PFB_TIMEOUT_BIT)
-#define DIGITAL_NFC_DEP_MI_BIT_SET(pfb) ((pfb) & 0x10)
+#define DIGITAL_NFC_DEP_MI_BIT_SET(pfb) ((pfb) & DIGITAL_NFC_DEP_PFB_MI_BIT)
+#define DIGITAL_NFC_DEP_NACK_BIT_SET(pfb) ((pfb) & DIGITAL_NFC_DEP_PFB_NACK_BIT)
#define DIGITAL_NFC_DEP_NAD_BIT_SET(pfb) ((pfb) & 0x08)
#define DIGITAL_NFC_DEP_DID_BIT_SET(pfb) ((pfb) & DIGITAL_NFC_DEP_PFB_DID_BIT)
#define DIGITAL_NFC_DEP_PFB_PNI(pfb) ((pfb) & 0x03)
@@ -161,6 +167,40 @@
return 0;
}
+static struct sk_buff *
+digital_send_dep_data_prep(struct nfc_digital_dev *ddev, struct sk_buff *skb,
+ struct digital_dep_req_res *dep_req_res,
+ struct digital_data_exch *data_exch)
+{
+ struct sk_buff *new_skb;
+
+ if (skb->len > ddev->remote_payload_max) {
+ dep_req_res->pfb |= DIGITAL_NFC_DEP_PFB_MI_BIT;
+
+ new_skb = digital_skb_alloc(ddev, ddev->remote_payload_max);
+ if (!new_skb) {
+ kfree_skb(ddev->chaining_skb);
+ ddev->chaining_skb = NULL;
+
+ return ERR_PTR(-ENOMEM);
+ }
+
+ skb_reserve(new_skb, ddev->tx_headroom + NFC_HEADER_SIZE +
+ DIGITAL_NFC_DEP_REQ_RES_HEADROOM);
+ memcpy(skb_put(new_skb, ddev->remote_payload_max), skb->data,
+ ddev->remote_payload_max);
+ skb_pull(skb, ddev->remote_payload_max);
+
+ ddev->chaining_skb = skb;
+ ddev->data_exch = data_exch;
+ } else {
+ ddev->chaining_skb = NULL;
+ new_skb = skb;
+ }
+
+ return new_skb;
+}
+
static void digital_in_recv_psl_res(struct nfc_digital_dev *ddev, void *arg,
struct sk_buff *resp)
{
@@ -498,8 +538,6 @@
break;
case DIGITAL_NFC_DEP_PFB_ACK_NACK_PDU:
- pr_err("Received a ACK/NACK PDU\n");
-
if (DIGITAL_NFC_DEP_PFB_PNI(pfb) != ddev->curr_nfc_dep_pni) {
PROTOCOL_ERR("14.12.3.3");
rc = -EIO;
@@ -509,6 +547,17 @@
ddev->curr_nfc_dep_pni =
DIGITAL_NFC_DEP_PFB_PNI(ddev->curr_nfc_dep_pni + 1);
+ if (ddev->chaining_skb && !DIGITAL_NFC_DEP_NACK_BIT_SET(pfb)) {
+ rc = digital_in_send_dep_req(ddev, NULL,
+ ddev->chaining_skb,
+ ddev->data_exch);
+ if (rc)
+ goto error;
+
+ return;
+ }
+
+ pr_err("Received a ACK/NACK PDU\n");
rc = -EINVAL;
goto exit;
@@ -538,6 +587,9 @@
error:
kfree(data_exch);
+ kfree_skb(ddev->chaining_skb);
+ ddev->chaining_skb = NULL;
+
if (rc)
kfree_skb(resp);
}
@@ -547,23 +599,38 @@
struct digital_data_exch *data_exch)
{
struct digital_dep_req_res *dep_req;
+ struct sk_buff *chaining_skb, *tmp_skb;
+ int rc;
skb_push(skb, sizeof(struct digital_dep_req_res));
- if (skb->len > ddev->remote_payload_max)
- return -EMSGSIZE;
-
dep_req = (struct digital_dep_req_res *)skb->data;
+
dep_req->dir = DIGITAL_NFC_DEP_FRAME_DIR_OUT;
dep_req->cmd = DIGITAL_CMD_DEP_REQ;
dep_req->pfb = ddev->curr_nfc_dep_pni;
- digital_skb_push_dep_sod(ddev, skb);
+ chaining_skb = ddev->chaining_skb;
- ddev->skb_add_crc(skb);
+ tmp_skb = digital_send_dep_data_prep(ddev, skb, dep_req, data_exch);
+ if (IS_ERR(tmp_skb))
+ return PTR_ERR(tmp_skb);
- return digital_in_send_cmd(ddev, skb, 1500, digital_in_recv_dep_res,
- data_exch);
+ digital_skb_push_dep_sod(ddev, tmp_skb);
+
+ ddev->skb_add_crc(tmp_skb);
+
+ rc = digital_in_send_cmd(ddev, tmp_skb, 1500, digital_in_recv_dep_res,
+ data_exch);
+ if (rc) {
+ if (tmp_skb != skb)
+ kfree_skb(tmp_skb);
+
+ kfree_skb(chaining_skb);
+ ddev->chaining_skb = NULL;
+ }
+
+ return rc;
}
static void digital_tg_set_rf_tech(struct nfc_digital_dev *ddev, u8 rf_tech)
@@ -678,6 +745,14 @@
goto exit;
}
+ if (ddev->chaining_skb && !DIGITAL_NFC_DEP_NACK_BIT_SET(pfb)) {
+ rc = digital_tg_send_dep_res(ddev, ddev->chaining_skb);
+ if (rc)
+ goto exit;
+
+ return;
+ }
+
pr_err("Received a ACK/NACK PDU\n");
rc = -EINVAL;
goto exit;
@@ -690,6 +765,9 @@
rc = nfc_tm_data_received(ddev->nfc_dev, resp);
exit:
+ kfree_skb(ddev->chaining_skb);
+ ddev->chaining_skb = NULL;
+
if (rc)
kfree_skb(resp);
}
@@ -697,12 +775,11 @@
int digital_tg_send_dep_res(struct nfc_digital_dev *ddev, struct sk_buff *skb)
{
struct digital_dep_req_res *dep_res;
+ struct sk_buff *chaining_skb, *tmp_skb;
+ int rc;
skb_push(skb, sizeof(struct digital_dep_req_res));
- if (skb->len > ddev->remote_payload_max)
- return -EMSGSIZE;
-
dep_res = (struct digital_dep_req_res *)skb->data;
dep_res->dir = DIGITAL_NFC_DEP_FRAME_DIR_IN;
@@ -719,12 +796,27 @@
ddev->curr_nfc_dep_pni =
DIGITAL_NFC_DEP_PFB_PNI(ddev->curr_nfc_dep_pni + 1);
- digital_skb_push_dep_sod(ddev, skb);
+ chaining_skb = ddev->chaining_skb;
- ddev->skb_add_crc(skb);
+ tmp_skb = digital_send_dep_data_prep(ddev, skb, dep_res, NULL);
+ if (IS_ERR(tmp_skb))
+ return PTR_ERR(tmp_skb);
- return digital_tg_send_cmd(ddev, skb, 1500, digital_tg_recv_dep_req,
- NULL);
+ digital_skb_push_dep_sod(ddev, tmp_skb);
+
+ ddev->skb_add_crc(tmp_skb);
+
+ rc = digital_tg_send_cmd(ddev, tmp_skb, 1500, digital_tg_recv_dep_req,
+ NULL);
+ if (rc) {
+ if (tmp_skb != skb)
+ kfree_skb(tmp_skb);
+
+ kfree_skb(chaining_skb);
+ ddev->chaining_skb = NULL;
+ }
+
+ return rc;
}
static void digital_tg_send_psl_res_complete(struct nfc_digital_dev *ddev,