]> Pileus Git - ~andy/linux/blobdiff - drivers/hid/usbhid/hid-core.c
HID: usbhid: hid-core: submit queued urbs before suspend
[~andy/linux] / drivers / hid / usbhid / hid-core.c
index 66061349be8781cb23ca2f50d3a64feb54197dc1..719f6b02fab195c82d25bd26b8328f21509874b9 100644 (file)
@@ -197,16 +197,24 @@ static int usbhid_restart_out_queue(struct usbhid_device *usbhid)
 {
        struct hid_device *hid = usb_get_intfdata(usbhid->intf);
        int kicked;
+       int r;
 
        if (!hid)
                return 0;
 
        if ((kicked = (usbhid->outhead != usbhid->outtail))) {
                dbg("Kicking head %d tail %d", usbhid->outhead, usbhid->outtail);
+
+               r = usb_autopm_get_interface_async(usbhid->intf);
+               if (r < 0)
+                       return r;
+               /* Asynchronously flush queue. */
+               set_bit(HID_OUT_RUNNING, &usbhid->iofl);
                if (hid_submit_out(hid)) {
                        clear_bit(HID_OUT_RUNNING, &usbhid->iofl);
-                       wake_up(&usbhid->wait);
+                       usb_autopm_put_interface_async(usbhid->intf);
                }
+               wake_up(&usbhid->wait);
        }
        return kicked;
 }
@@ -215,6 +223,7 @@ static int usbhid_restart_ctrl_queue(struct usbhid_device *usbhid)
 {
        struct hid_device *hid = usb_get_intfdata(usbhid->intf);
        int kicked;
+       int r;
 
        WARN_ON(hid == NULL);
        if (!hid)
@@ -222,10 +231,17 @@ static int usbhid_restart_ctrl_queue(struct usbhid_device *usbhid)
 
        if ((kicked = (usbhid->ctrlhead != usbhid->ctrltail))) {
                dbg("Kicking head %d tail %d", usbhid->ctrlhead, usbhid->ctrltail);
+
+               r = usb_autopm_get_interface_async(usbhid->intf);
+               if (r < 0)
+                       return r;
+               /* Asynchronously flush queue. */
+               set_bit(HID_CTRL_RUNNING, &usbhid->iofl);
                if (hid_submit_ctrl(hid)) {
                        clear_bit(HID_CTRL_RUNNING, &usbhid->iofl);
-                       wake_up(&usbhid->wait);
+                       usb_autopm_put_interface_async(usbhid->intf);
                }
+               wake_up(&usbhid->wait);
        }
        return kicked;
 }
@@ -304,30 +320,21 @@ static int hid_submit_out(struct hid_device *hid)
        report = usbhid->out[usbhid->outtail].report;
        raw_report = usbhid->out[usbhid->outtail].raw_report;
 
-       r = usb_autopm_get_interface_async(usbhid->intf);
-       if (r < 0)
-               return -1;
+       usbhid->urbout->transfer_buffer_length = ((report->size - 1) >> 3) +
+                                                1 + (report->id > 0);
+       usbhid->urbout->dev = hid_to_usb_dev(hid);
+       memcpy(usbhid->outbuf, raw_report,
+              usbhid->urbout->transfer_buffer_length);
+       kfree(raw_report);
 
-       /*
-        * if the device hasn't been woken, we leave the output
-        * to resume()
-        */
-       if (!test_bit(HID_REPORTED_IDLE, &usbhid->iofl)) {
-               usbhid->urbout->transfer_buffer_length = ((report->size - 1) >> 3) + 1 + (report->id > 0);
-               usbhid->urbout->dev = hid_to_usb_dev(hid);
-               memcpy(usbhid->outbuf, raw_report, usbhid->urbout->transfer_buffer_length);
-               kfree(raw_report);
-
-               dbg_hid("submitting out urb\n");
+       dbg_hid("submitting out urb\n");
 
-               if (usb_submit_urb(usbhid->urbout, GFP_ATOMIC)) {
-                       hid_err(hid, "usb_submit_urb(out) failed\n");
-                       usb_autopm_put_interface_async(usbhid->intf);
-                       return -1;
-               }
-               usbhid->last_out = jiffies;
+       r = usb_submit_urb(usbhid->urbout, GFP_ATOMIC);
+       if (r < 0) {
+               hid_err(hid, "usb_submit_urb(out) failed: %d\n", r);
+               return r;
        }
-
+       usbhid->last_out = jiffies;
        return 0;
 }
 
@@ -343,50 +350,48 @@ static int hid_submit_ctrl(struct hid_device *hid)
        raw_report = usbhid->ctrl[usbhid->ctrltail].raw_report;
        dir = usbhid->ctrl[usbhid->ctrltail].dir;
 
-       r = usb_autopm_get_interface_async(usbhid->intf);
-       if (r < 0)
-               return -1;
-       if (!test_bit(HID_REPORTED_IDLE, &usbhid->iofl)) {
-               len = ((report->size - 1) >> 3) + 1 + (report->id > 0);
-               if (dir == USB_DIR_OUT) {
-                       usbhid->urbctrl->pipe = usb_sndctrlpipe(hid_to_usb_dev(hid), 0);
-                       usbhid->urbctrl->transfer_buffer_length = len;
-                       memcpy(usbhid->ctrlbuf, raw_report, len);
-                       kfree(raw_report);
-               } else {
-                       int maxpacket, padlen;
-
-                       usbhid->urbctrl->pipe = usb_rcvctrlpipe(hid_to_usb_dev(hid), 0);
-                       maxpacket = usb_maxpacket(hid_to_usb_dev(hid), usbhid->urbctrl->pipe, 0);
-                       if (maxpacket > 0) {
-                               padlen = DIV_ROUND_UP(len, maxpacket);
-                               padlen *= maxpacket;
-                               if (padlen > usbhid->bufsize)
-                                       padlen = usbhid->bufsize;
-                       } else
-                               padlen = 0;
-                       usbhid->urbctrl->transfer_buffer_length = padlen;
-               }
-               usbhid->urbctrl->dev = hid_to_usb_dev(hid);
-
-               usbhid->cr->bRequestType = USB_TYPE_CLASS | USB_RECIP_INTERFACE | dir;
-               usbhid->cr->bRequest = (dir == USB_DIR_OUT) ? HID_REQ_SET_REPORT : HID_REQ_GET_REPORT;
-               usbhid->cr->wValue = cpu_to_le16(((report->type + 1) << 8) | report->id);
-               usbhid->cr->wIndex = cpu_to_le16(usbhid->ifnum);
-               usbhid->cr->wLength = cpu_to_le16(len);
-
-               dbg_hid("submitting ctrl urb: %s wValue=0x%04x wIndex=0x%04x wLength=%u\n",
-                       usbhid->cr->bRequest == HID_REQ_SET_REPORT ? "Set_Report" : "Get_Report",
-                       usbhid->cr->wValue, usbhid->cr->wIndex, usbhid->cr->wLength);
-
-               if (usb_submit_urb(usbhid->urbctrl, GFP_ATOMIC)) {
-                       usb_autopm_put_interface_async(usbhid->intf);
-                       hid_err(hid, "usb_submit_urb(ctrl) failed\n");
-                       return -1;
-               }
-               usbhid->last_ctrl = jiffies;
+       len = ((report->size - 1) >> 3) + 1 + (report->id > 0);
+       if (dir == USB_DIR_OUT) {
+               usbhid->urbctrl->pipe = usb_sndctrlpipe(hid_to_usb_dev(hid), 0);
+               usbhid->urbctrl->transfer_buffer_length = len;
+               memcpy(usbhid->ctrlbuf, raw_report, len);
+               kfree(raw_report);
+       } else {
+               int maxpacket, padlen;
+
+               usbhid->urbctrl->pipe = usb_rcvctrlpipe(hid_to_usb_dev(hid), 0);
+               maxpacket = usb_maxpacket(hid_to_usb_dev(hid),
+                                         usbhid->urbctrl->pipe, 0);
+               if (maxpacket > 0) {
+                       padlen = DIV_ROUND_UP(len, maxpacket);
+                       padlen *= maxpacket;
+                       if (padlen > usbhid->bufsize)
+                               padlen = usbhid->bufsize;
+               } else
+                       padlen = 0;
+               usbhid->urbctrl->transfer_buffer_length = padlen;
        }
-
+       usbhid->urbctrl->dev = hid_to_usb_dev(hid);
+
+       usbhid->cr->bRequestType = USB_TYPE_CLASS | USB_RECIP_INTERFACE | dir;
+       usbhid->cr->bRequest = (dir == USB_DIR_OUT) ? HID_REQ_SET_REPORT :
+                                                     HID_REQ_GET_REPORT;
+       usbhid->cr->wValue = cpu_to_le16(((report->type + 1) << 8) |
+                                        report->id);
+       usbhid->cr->wIndex = cpu_to_le16(usbhid->ifnum);
+       usbhid->cr->wLength = cpu_to_le16(len);
+
+       dbg_hid("submitting ctrl urb: %s wValue=0x%04x wIndex=0x%04x wLength=%u\n",
+               usbhid->cr->bRequest == HID_REQ_SET_REPORT ? "Set_Report" :
+                                                            "Get_Report",
+               usbhid->cr->wValue, usbhid->cr->wIndex, usbhid->cr->wLength);
+
+       r = usb_submit_urb(usbhid->urbctrl, GFP_ATOMIC);
+       if (r < 0) {
+               hid_err(hid, "usb_submit_urb(ctrl) failed: %d\n", r);
+               return r;
+       }
+       usbhid->last_ctrl = jiffies;
        return 0;
 }
 
@@ -423,11 +428,8 @@ static void hid_irq_out(struct urb *urb)
        else
                usbhid->outtail = (usbhid->outtail + 1) & (HID_OUTPUT_FIFO_SIZE - 1);
 
-       if (usbhid->outhead != usbhid->outtail) {
-               if (hid_submit_out(hid)) {
-                       clear_bit(HID_OUT_RUNNING, &usbhid->iofl);
-                       wake_up(&usbhid->wait);
-               }
+       if (usbhid->outhead != usbhid->outtail && !hid_submit_out(hid)) {
+               /* Successfully submitted next urb in queue */
                spin_unlock_irqrestore(&usbhid->lock, flags);
                return;
        }
@@ -474,13 +476,9 @@ static void hid_ctrl(struct urb *urb)
        else
                usbhid->ctrltail = (usbhid->ctrltail + 1) & (HID_CONTROL_FIFO_SIZE - 1);
 
-       if (usbhid->ctrlhead != usbhid->ctrltail) {
-               if (hid_submit_ctrl(hid)) {
-                       clear_bit(HID_CTRL_RUNNING, &usbhid->iofl);
-                       wake_up(&usbhid->wait);
-               }
+       if (usbhid->ctrlhead != usbhid->ctrltail && !hid_submit_ctrl(hid)) {
+               /* Successfully submitted next urb in queue */
                spin_unlock(&usbhid->lock);
-               usb_autopm_put_interface_async(usbhid->intf);
                return;
        }
 
@@ -515,9 +513,23 @@ static void __usbhid_submit_report(struct hid_device *hid, struct hid_report *re
                usbhid->out[usbhid->outhead].report = report;
                usbhid->outhead = head;
 
+               /* Try to awake from autosuspend... */
+               if (usb_autopm_get_interface_async(usbhid->intf) < 0)
+                       return;
+
+               /*
+                * But if still suspended, leave urb enqueued, don't submit.
+                * Submission will occur if/when resume() drains the queue.
+                */
+               if (test_bit(HID_REPORTED_IDLE, &usbhid->iofl))
+                       return;
+
                if (!test_and_set_bit(HID_OUT_RUNNING, &usbhid->iofl)) {
-                       if (hid_submit_out(hid))
+                       if (hid_submit_out(hid)) {
                                clear_bit(HID_OUT_RUNNING, &usbhid->iofl);
+                               usb_autopm_put_interface_async(usbhid->intf);
+                       }
+                       wake_up(&usbhid->wait);
                } else {
                        /*
                         * the queue is known to run
@@ -549,9 +561,23 @@ static void __usbhid_submit_report(struct hid_device *hid, struct hid_report *re
        usbhid->ctrl[usbhid->ctrlhead].dir = dir;
        usbhid->ctrlhead = head;
 
+       /* Try to awake from autosuspend... */
+       if (usb_autopm_get_interface_async(usbhid->intf) < 0)
+               return;
+
+       /*
+        * If already suspended, leave urb enqueued, but don't submit.
+        * Submission will occur if/when resume() drains the queue.
+        */
+       if (test_bit(HID_REPORTED_IDLE, &usbhid->iofl))
+               return;
+
        if (!test_and_set_bit(HID_CTRL_RUNNING, &usbhid->iofl)) {
-               if (hid_submit_ctrl(hid))
+               if (hid_submit_ctrl(hid)) {
                        clear_bit(HID_CTRL_RUNNING, &usbhid->iofl);
+                       usb_autopm_put_interface_async(usbhid->intf);
+               }
+               wake_up(&usbhid->wait);
        } else {
                /*
                 * the queue is known to run