NFC: port100: Fix the command cancellation process
authorThierry Escande <thierry.escande@collabora.com>
Thu, 16 Jun 2016 18:25:21 +0000 (20:25 +0200)
committerSamuel Ortiz <sameo@linux.intel.com>
Wed, 6 Jul 2016 08:02:07 +0000 (10:02 +0200)
The USB out_urb used to send commands to the device can be submitted
through the standard command processing queue coming from the Digital
Protocol layer but it can also be submitted from port100_abort_cmd().

To not submit the URB while already active, a mutex is now used to
protect it and a cmd_cancel flag is used to not send command while
canceling the previous one.

Signed-off-by: Thierry Escande <thierry.escande@collabora.com>
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
drivers/nfc/port100.c

index 14a3cc2d0fd6cb2e27382abc4fd780b72052fb21..909e3df2c16a98e186877188739ac4f695380020 100644 (file)
@@ -456,6 +456,12 @@ struct port100 {
        struct urb *out_urb;
        struct urb *in_urb;
 
+       /* This mutex protects the out_urb and avoids to submit a new command
+        * through port100_send_frame_async() while the previous one is being
+        * canceled through port100_abort_cmd().
+        */
+       struct mutex out_urb_lock;
+
        struct work_struct cmd_complete_work;
 
        u8 cmd_type;
@@ -464,6 +470,8 @@ struct port100 {
         * for any queuing/locking mechanism at driver level.
         */
        struct port100_cmd *cmd;
+
+       bool cmd_cancel;
 };
 
 struct port100_cmd {
@@ -718,10 +726,22 @@ static int port100_send_ack(struct port100 *dev)
 {
        int rc;
 
+       mutex_lock(&dev->out_urb_lock);
+
+       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);
 
+       /* 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);
+
        return rc;
 }
 
@@ -730,6 +750,16 @@ static int port100_send_frame_async(struct port100 *dev, struct sk_buff *out,
 {
        int rc;
 
+       mutex_lock(&dev->out_urb_lock);
+
+       /* A command cancel frame as been sent through dev->out_urb. Don't try
+        * to submit a new one.
+        */
+       if (dev->cmd_cancel) {
+               rc = -EAGAIN;
+               goto exit;
+       }
+
        dev->out_urb->transfer_buffer = out->data;
        dev->out_urb->transfer_buffer_length = out->len;
 
@@ -741,16 +771,15 @@ static int port100_send_frame_async(struct port100 *dev, struct sk_buff *out,
 
        rc = usb_submit_urb(dev->out_urb, GFP_KERNEL);
        if (rc)
-               return rc;
+               goto exit;
 
        rc = port100_submit_urb_for_ack(dev, GFP_KERNEL);
        if (rc)
-               goto error;
+               usb_unlink_urb(dev->out_urb);
 
-       return 0;
+exit:
+       mutex_unlock(&dev->out_urb_lock);
 
-error:
-       usb_unlink_urb(dev->out_urb);
        return rc;
 }
 
@@ -892,6 +921,8 @@ static void port100_send_complete(struct urb *urb)
 {
        struct port100 *dev = urb->context;
 
+       dev->cmd_cancel = false;
+
        switch (urb->status) {
        case 0:
                break; /* success */
@@ -1455,6 +1486,7 @@ static int port100_probe(struct usb_interface *interface,
        if (!dev)
                return -ENOMEM;
 
+       mutex_init(&dev->out_urb_lock);
        dev->udev = usb_get_dev(interface_to_usbdev(interface));
        dev->interface = interface;
        usb_set_intfdata(interface, dev);
This page took 0.026559 seconds and 5 git commands to generate.