USB: whci-hcd: make endpoint_reset method async
authorDavid Vrabel <david.vrabel@csr.com>
Wed, 24 Jun 2009 17:26:40 +0000 (18:26 +0100)
committerGreg Kroah-Hartman <gregkh@suse.de>
Wed, 23 Sep 2009 13:46:21 +0000 (06:46 -0700)
usb_hcd_endpoint_reset() may be called in atomic context and must not
sleep.  So make whci-hcd's endpoint_reset() asynchronous.  URBs
submitted while the reset is in progress will be queued (on the std
list) and transfers will resume once the reset is complete.

Signed-off-by: David Vrabel <david.vrabel@csr.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
drivers/usb/host/whci/asl.c
drivers/usb/host/whci/hcd.c
drivers/usb/host/whci/pzl.c
drivers/usb/host/whci/qset.c
drivers/usb/host/whci/whci-hc.h

index c2050785a819f76b0157bc0490b66d31977999c8..c632437c7649db90d22219e0ad421caafb8e5db2 100644 (file)
@@ -227,11 +227,21 @@ void scan_async_work(struct work_struct *work)
        /*
         * Now that the ASL is updated, complete the removal of any
         * removed qsets.
+        *
+        * If the qset was to be reset, do so and reinsert it into the
+        * ASL if it has pending transfers.
         */
        spin_lock_irq(&whc->lock);
 
        list_for_each_entry_safe(qset, t, &whc->async_removed_list, list_node) {
                qset_remove_complete(whc, qset);
+               if (qset->reset) {
+                       qset_reset(whc, qset);
+                       if (!list_empty(&qset->stds)) {
+                               asl_qset_insert_begin(whc, qset);
+                               queue_work(whc->workqueue, &whc->async_work);
+                       }
+               }
        }
 
        spin_unlock_irq(&whc->lock);
@@ -267,7 +277,7 @@ int asl_urb_enqueue(struct whc *whc, struct urb *urb, gfp_t mem_flags)
        else
                err = qset_add_urb(whc, qset, urb, GFP_ATOMIC);
        if (!err) {
-               if (!qset->in_sw_list)
+               if (!qset->in_sw_list && !qset->remove)
                        asl_qset_insert_begin(whc, qset);
        } else
                usb_hcd_unlink_urb_from_ep(&whc->wusbhc.usb_hcd, urb);
index e019a5058ab86b355ac9106691d7a67fc20dd204..687b622a1612e49d024c9b2494c33cb3064bbe2d 100644 (file)
@@ -192,19 +192,23 @@ static void whc_endpoint_reset(struct usb_hcd *usb_hcd,
        struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
        struct whc *whc = wusbhc_to_whc(wusbhc);
        struct whc_qset *qset;
+       unsigned long flags;
+
+       spin_lock_irqsave(&whc->lock, flags);
 
        qset = ep->hcpriv;
        if (qset) {
                qset->remove = 1;
+               qset->reset = 1;
 
                if (usb_endpoint_xfer_bulk(&ep->desc)
                    || usb_endpoint_xfer_control(&ep->desc))
                        queue_work(whc->workqueue, &whc->async_work);
                else
                        queue_work(whc->workqueue, &whc->periodic_work);
-
-               qset_reset(whc, qset);
        }
+
+       spin_unlock_irqrestore(&whc->lock, flags);
 }
 
 
index ff4ef9e910d975ce5b6584cd66f420f1c58249cf..a9e05bac66463c919cc0943d9df25b0b6d142c3d 100644 (file)
@@ -255,11 +255,21 @@ void scan_periodic_work(struct work_struct *work)
        /*
         * Now that the PZL is updated, complete the removal of any
         * removed qsets.
+        *
+        * If the qset was to be reset, do so and reinsert it into the
+        * PZL if it has pending transfers.
         */
        spin_lock_irq(&whc->lock);
 
        list_for_each_entry_safe(qset, t, &whc->periodic_removed_list, list_node) {
                qset_remove_complete(whc, qset);
+               if (qset->reset) {
+                       qset_reset(whc, qset);
+                       if (!list_empty(&qset->stds)) {
+                               qset_insert_in_sw_list(whc, qset);
+                               queue_work(whc->workqueue, &whc->periodic_work);
+                       }
+               }
        }
 
        spin_unlock_irq(&whc->lock);
@@ -295,7 +305,7 @@ int pzl_urb_enqueue(struct whc *whc, struct urb *urb, gfp_t mem_flags)
        else
                err = qset_add_urb(whc, qset, urb, GFP_ATOMIC);
        if (!err) {
-               if (!qset->in_sw_list)
+               if (!qset->in_sw_list && !qset->remove)
                        qset_insert_in_sw_list(whc, qset);
        } else
                usb_hcd_unlink_urb_from_ep(&whc->wusbhc.usb_hcd, urb);
index 640b38fbd0518904f6102d2594f4991e1f8d6bfd..1b9dc15715703781922100186a7e354277969e73 100644 (file)
@@ -103,7 +103,6 @@ static void qset_fill_qh(struct whc_qset *qset, struct urb *urb)
 void qset_clear(struct whc *whc, struct whc_qset *qset)
 {
        qset->td_start = qset->td_end = qset->ntds = 0;
-       qset->remove = 0;
 
        qset->qh.link = cpu_to_le32(QH_LINK_NTDS(8) | QH_LINK_T);
        qset->qh.status = qset->qh.status & QH_STATUS_SEQ_MASK;
@@ -125,7 +124,7 @@ void qset_clear(struct whc *whc, struct whc_qset *qset)
  */
 void qset_reset(struct whc *whc, struct whc_qset *qset)
 {
-       wait_for_completion(&qset->remove_complete);
+       qset->reset = 0;
 
        qset->qh.status &= ~QH_STATUS_SEQ_MASK;
        qset->qh.cur_window = cpu_to_le32((1 << qset->max_burst) - 1);
@@ -156,6 +155,7 @@ struct whc_qset *get_qset(struct whc *whc, struct urb *urb,
 
 void qset_remove_complete(struct whc *whc, struct whc_qset *qset)
 {
+       qset->remove = 0;
        list_del_init(&qset->list_node);
        complete(&qset->remove_complete);
 }
index 794dba0d0f0a08440f78f899a4ad17b7af678575..e8d0001605be2c7192d2a01d0400877bbd2b234d 100644 (file)
@@ -264,6 +264,7 @@ struct whc_qset {
        unsigned in_sw_list:1;
        unsigned in_hw_list:1;
        unsigned remove:1;
+       unsigned reset:1;
        struct urb *pause_after_urb;
        struct completion remove_complete;
        int max_burst;