diff options
author | OGAWA Hirofumi <hirofumi@mail.parknet.co.jp> | 2017-02-04 02:16:56 +0100 |
---|---|---|
committer | Samuel Ortiz <sameo@linux.intel.com> | 2017-04-01 23:04:30 +0200 |
commit | 2497128133f8169b24b928852ba6eae34fc495e5 (patch) | |
tree | 78d8aacc0bc93f1e3a426adcd8a470576c88801c /drivers/nfc | |
parent | nfc: Fix RC-S380* needs zero-length packet (diff) | |
download | linux-2497128133f8169b24b928852ba6eae34fc495e5.tar.xz linux-2497128133f8169b24b928852ba6eae34fc495e5.zip |
nfc: Fix hangup of RC-S380* in port100_send_ack()
If port100_send_ack() was called twice or more, it has race to hangup.
port100_send_ack() port100_send_ack()
init_completion()
[...]
dev->cmd_cancel = true
/* this removes previous from completion */
init_completion()
[...]
dev->cmd_cancel = true
wait_for_completion()
/* never be waked up */
wait_for_completion()
Like above race, this code is not assuming port100_send_ack() is
called twice or more.
To fix, this checks dev->cmd_cancel to know if prior cancel is
in-flight or not. And never be remove prior task from completion by
using reinit_completion(), so this guarantees to be waked up properly
soon or later.
Signed-off-by: OGAWA Hirofumi <hirofumi@mail.parknet.co.jp>
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
Diffstat (limited to 'drivers/nfc')
-rw-r--r-- | drivers/nfc/port100.c | 35 |
1 files changed, 23 insertions, 12 deletions
diff --git a/drivers/nfc/port100.c b/drivers/nfc/port100.c index 382985d3fee0..19be93e177fe 100644 --- a/drivers/nfc/port100.c +++ b/drivers/nfc/port100.c @@ -726,23 +726,33 @@ static int port100_submit_urb_for_ack(struct port100 *dev, gfp_t flags) static int port100_send_ack(struct port100 *dev) { - int rc; + int rc = 0; mutex_lock(&dev->out_urb_lock); - init_completion(&dev->cmd_cancel_done); + /* + * If prior cancel is in-flight (dev->cmd_cancel == true), we + * can skip to send cancel. Then this will wait the prior + * cancel, or merged into the next cancel rarely if next + * cancel was started before waiting done. In any case, this + * will be waked up soon or later. + */ + if (!dev->cmd_cancel) { + reinit_completion(&dev->cmd_cancel_done); - usb_kill_urb(dev->out_urb); + usb_kill_urb(dev->out_urb); - dev->out_urb->transfer_buffer = ack_frame; - dev->out_urb->transfer_buffer_length = sizeof(ack_frame); - rc = usb_submit_urb(dev->out_urb, GFP_KERNEL); + dev->out_urb->transfer_buffer = ack_frame; + dev->out_urb->transfer_buffer_length = sizeof(ack_frame); + rc = usb_submit_urb(dev->out_urb, GFP_KERNEL); - /* Set the cmd_cancel flag only if the URB has been successfully - * submitted. It will be reset by the out URB completion callback - * port100_send_complete(). - */ - dev->cmd_cancel = !rc; + /* + * Set the cmd_cancel flag only if the URB has been + * successfully submitted. It will be reset by the out + * URB completion callback port100_send_complete(). + */ + dev->cmd_cancel = !rc; + } mutex_unlock(&dev->out_urb_lock); @@ -929,8 +939,8 @@ static void port100_send_complete(struct urb *urb) struct port100 *dev = urb->context; if (dev->cmd_cancel) { + complete_all(&dev->cmd_cancel_done); dev->cmd_cancel = false; - complete(&dev->cmd_cancel_done); } switch (urb->status) { @@ -1546,6 +1556,7 @@ static int port100_probe(struct usb_interface *interface, PORT100_COMM_RF_HEAD_MAX_LEN; dev->skb_tailroom = PORT100_FRAME_TAIL_LEN; + init_completion(&dev->cmd_cancel_done); INIT_WORK(&dev->cmd_complete_work, port100_wq_cmd_complete); /* The first thing to do with the Port-100 is to set the command type |