summaryrefslogtreecommitdiff
path: root/net/nfc/nci/core.c
diff options
context:
space:
mode:
Diffstat (limited to 'net/nfc/nci/core.c')
-rw-r--r--net/nfc/nci/core.c40
1 files changed, 33 insertions, 7 deletions
diff --git a/net/nfc/nci/core.c b/net/nfc/nci/core.c
index 6e9b76e2cc56..5f46c4b5720f 100644
--- a/net/nfc/nci/core.c
+++ b/net/nfc/nci/core.c
@@ -567,6 +567,10 @@ static int nci_close_device(struct nci_dev *ndev)
flush_workqueue(ndev->cmd_wq);
timer_delete_sync(&ndev->cmd_timer);
timer_delete_sync(&ndev->data_timer);
+ if (test_bit(NCI_DATA_EXCHANGE, &ndev->flags))
+ nci_data_exchange_complete(ndev, NULL,
+ ndev->cur_conn_id,
+ -ENODEV);
mutex_unlock(&ndev->req_lock);
return 0;
}
@@ -575,8 +579,7 @@ static int nci_close_device(struct nci_dev *ndev)
skb_queue_purge(&ndev->rx_q);
skb_queue_purge(&ndev->tx_q);
- /* Flush RX and TX wq */
- flush_workqueue(ndev->rx_wq);
+ /* Flush TX wq, RX wq flush can't be under the lock */
flush_workqueue(ndev->tx_wq);
/* Reset device */
@@ -588,22 +591,30 @@ static int nci_close_device(struct nci_dev *ndev)
msecs_to_jiffies(NCI_RESET_TIMEOUT));
/* After this point our queues are empty
- * and no works are scheduled.
+ * rx work may be running but will see that NCI_UP was cleared
*/
ndev->ops->close(ndev);
clear_bit(NCI_INIT, &ndev->flags);
- /* Flush cmd wq */
+ /* Flush cmd and tx wq */
flush_workqueue(ndev->cmd_wq);
timer_delete_sync(&ndev->cmd_timer);
+ timer_delete_sync(&ndev->data_timer);
+
+ if (test_bit(NCI_DATA_EXCHANGE, &ndev->flags))
+ nci_data_exchange_complete(ndev, NULL, ndev->cur_conn_id,
+ -ENODEV);
/* Clear flags except NCI_UNREG */
ndev->flags &= BIT(NCI_UNREG);
mutex_unlock(&ndev->req_lock);
+ /* rx_work may take req_lock via nci_deactivate_target */
+ flush_workqueue(ndev->rx_wq);
+
return 0;
}
@@ -1035,18 +1046,23 @@ static int nci_transceive(struct nfc_dev *nfc_dev, struct nfc_target *target,
struct nci_conn_info *conn_info;
conn_info = ndev->rf_conn_info;
- if (!conn_info)
+ if (!conn_info) {
+ kfree_skb(skb);
return -EPROTO;
+ }
pr_debug("target_idx %d, len %d\n", target->idx, skb->len);
if (!ndev->target_active_prot) {
pr_err("unable to exchange data, no active target\n");
+ kfree_skb(skb);
return -EINVAL;
}
- if (test_and_set_bit(NCI_DATA_EXCHANGE, &ndev->flags))
+ if (test_and_set_bit(NCI_DATA_EXCHANGE, &ndev->flags)) {
+ kfree_skb(skb);
return -EBUSY;
+ }
/* store cb and context to be used on receiving data */
conn_info->data_exchange_cb = cb;
@@ -1482,10 +1498,20 @@ static bool nci_valid_size(struct sk_buff *skb)
unsigned int hdr_size = NCI_CTRL_HDR_SIZE;
if (skb->len < hdr_size ||
- !nci_plen(skb->data) ||
skb->len < hdr_size + nci_plen(skb->data)) {
return false;
}
+
+ if (!nci_plen(skb->data)) {
+ /* Allow zero length in proprietary notifications (0x20 - 0x3F). */
+ if (nci_opcode_oid(nci_opcode(skb->data)) >= 0x20 &&
+ nci_mt(skb->data) == NCI_MT_NTF_PKT)
+ return true;
+
+ /* Disallow zero length otherwise. */
+ return false;
+ }
+
return true;
}