NFC: pn533: Fix socket deadlock

A deadlock can occur when the NFC raw socket is closed while
the driver is processing a command.

Following is the call graph of the affected situation:

send data via raw_sock:
-------------
rawsock_tx_work
  sock_hold => socket refcnt++
  nfc_data_exchange => cb = rawsock_data_exchange_complete

    ops->im_transceive = pn533_transceive => arg->cb = db
                               = rawsock_data_exchange_complete

      pn533_send_data_async => cb = pn533_data_exchange_complete

        __pn533_send_async => cmd->complete_cb = cb
                              = pn533_data_exchange_complete

          if_ops->send_frame_async

response:
--------
pn533_recv_response
  queue_work(priv->wq, &priv->cmd_complete_work)

pn533_wq_cmd_complete

  pn533_send_async_complete

    cmd->complete_cb() = pn533_data_exchange_complete()

      arg->cb() = rawsock_data_exchange_complete()

        sock_put => socket refcnt-- => If the corresponding
                    socket gets closed in the meantime socket
                    will be destructed

          sk_free

            __sk_free

              sk->sk_destruct = rawsock_destruct

                nfc_deactivate_target

                  ops->deactivate_target = pn533_deactivate_target

                    pn533_send_cmd_sync

                      pn533_send_cmd_async

                        __pn533_send_async

                          list_add_tail(&cmd->queue,&dev->cmd_queue)
                                  => add to command list because
                                     a command is currently
                                     processed

                        wait_for_completion
                                   => the workqueue thread waits
                                      here because it is the one
                                      processing the commands
                                         => deadlock

To fix the deadlock pn533_deactivate_target is changed to
issue the PN533_CMD_IN_RELEASE command in async mode. This
way nothing blocks and the release command is executed after
the current command.

Signed-off-by: Michael Thalmeier <michael.thalmeier@hale.at>
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
This commit is contained in:
Michael Thalmeier 2016-03-25 15:46:52 +01:00 committed by Samuel Ortiz
parent e997ebbe46
commit 37f895d7e8
1 changed files with 30 additions and 10 deletions

View File

@ -2263,12 +2263,35 @@ static int pn533_activate_target(struct nfc_dev *nfc_dev,
return 0; return 0;
} }
static int pn533_deactivate_target_complete(struct pn533 *dev, void *arg,
struct sk_buff *resp)
{
int rc = 0;
dev_dbg(&dev->interface->dev, "%s\n", __func__);
if (IS_ERR(resp)) {
rc = PTR_ERR(resp);
nfc_err(&dev->interface->dev, "Target release error %d\n", rc);
return rc;
}
rc = resp->data[0] & PN533_CMD_RET_MASK;
if (rc != PN533_CMD_RET_SUCCESS)
nfc_err(&dev->interface->dev,
"Error 0x%x when releasing the target\n", rc);
dev_kfree_skb(resp);
return rc;
}
static void pn533_deactivate_target(struct nfc_dev *nfc_dev, static void pn533_deactivate_target(struct nfc_dev *nfc_dev,
struct nfc_target *target, u8 mode) struct nfc_target *target, u8 mode)
{ {
struct pn533 *dev = nfc_get_drvdata(nfc_dev); struct pn533 *dev = nfc_get_drvdata(nfc_dev);
struct sk_buff *skb; struct sk_buff *skb;
struct sk_buff *resp;
int rc; int rc;
dev_dbg(&dev->interface->dev, "%s\n", __func__); dev_dbg(&dev->interface->dev, "%s\n", __func__);
@ -2287,16 +2310,13 @@ static void pn533_deactivate_target(struct nfc_dev *nfc_dev,
*skb_put(skb, 1) = 1; /* TG*/ *skb_put(skb, 1) = 1; /* TG*/
resp = pn533_send_cmd_sync(dev, PN533_CMD_IN_RELEASE, skb); rc = pn533_send_cmd_async(dev, PN533_CMD_IN_RELEASE, skb,
if (IS_ERR(resp)) pn533_deactivate_target_complete, NULL);
return; if (rc < 0) {
dev_kfree_skb(skb);
nfc_err(&dev->interface->dev, "Target release error %d\n", rc);
}
rc = resp->data[0] & PN533_CMD_RET_MASK;
if (rc != PN533_CMD_RET_SUCCESS)
nfc_err(&dev->interface->dev,
"Error 0x%x when releasing the target\n", rc);
dev_kfree_skb(resp);
return; return;
} }