USB: whci-hcd: handle early deletion of endpoints
authorDavid Vrabel <david.vrabel@csr.com>
Mon, 12 Oct 2009 15:45:15 +0000 (15:45 +0000)
committerGreg Kroah-Hartman <gregkh@suse.de>
Wed, 14 Oct 2009 21:54:42 +0000 (14:54 -0700)
If an endpoint is deleted before it's been fully added to the hardware
list, the associated qset will not be fully initialized and an oops will
occur when complete(&qset->remove_complete) is called.  This can happen
if a queued URB is cancelled.

Fix this by only removing the qset from the hardware list if the
cancelled URB had qTDs.

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/pzl.c

index c632437..14ccbcf 100644 (file)
@@ -305,6 +305,7 @@ int asl_urb_dequeue(struct whc *whc, struct urb *urb, int status)
        struct whc_urb *wurb = urb->hcpriv;
        struct whc_qset *qset = wurb->qset;
        struct whc_std *std, *t;
        struct whc_urb *wurb = urb->hcpriv;
        struct whc_qset *qset = wurb->qset;
        struct whc_std *std, *t;
+       bool has_qtd = false;
        int ret;
        unsigned long flags;
 
        int ret;
        unsigned long flags;
 
@@ -315,17 +316,21 @@ int asl_urb_dequeue(struct whc *whc, struct urb *urb, int status)
                goto out;
 
        list_for_each_entry_safe(std, t, &qset->stds, list_node) {
                goto out;
 
        list_for_each_entry_safe(std, t, &qset->stds, list_node) {
-               if (std->urb == urb)
+               if (std->urb == urb) {
+                       if (std->qtd)
+                               has_qtd = true;
                        qset_free_std(whc, std);
                        qset_free_std(whc, std);
-               else
+               else
                        std->qtd = NULL; /* so this std is re-added when the qset is */
        }
 
                        std->qtd = NULL; /* so this std is re-added when the qset is */
        }
 
-       asl_qset_remove(whc, qset);
-       wurb->status = status;
-       wurb->is_async = true;
-       queue_work(whc->workqueue, &wurb->dequeue_work);
-
+       if (has_qtd) {
+               asl_qset_remove(whc, qset);
+               wurb->status = status;
+               wurb->is_async = true;
+               queue_work(whc->workqueue, &wurb->dequeue_work);
+       } else
+               qset_remove_urb(whc, qset, urb, status);
 out:
        spin_unlock_irqrestore(&whc->lock, flags);
 
 out:
        spin_unlock_irqrestore(&whc->lock, flags);
 
index a9e05ba..e923633 100644 (file)
@@ -333,6 +333,7 @@ int pzl_urb_dequeue(struct whc *whc, struct urb *urb, int status)
        struct whc_urb *wurb = urb->hcpriv;
        struct whc_qset *qset = wurb->qset;
        struct whc_std *std, *t;
        struct whc_urb *wurb = urb->hcpriv;
        struct whc_qset *qset = wurb->qset;
        struct whc_std *std, *t;
+       bool has_qtd = false;
        int ret;
        unsigned long flags;
 
        int ret;
        unsigned long flags;
 
@@ -343,17 +344,22 @@ int pzl_urb_dequeue(struct whc *whc, struct urb *urb, int status)
                goto out;
 
        list_for_each_entry_safe(std, t, &qset->stds, list_node) {
                goto out;
 
        list_for_each_entry_safe(std, t, &qset->stds, list_node) {
-               if (std->urb == urb)
+               if (std->urb == urb) {
+                       if (std->qtd)
+                               has_qtd = true;
                        qset_free_std(whc, std);
                        qset_free_std(whc, std);
-               else
+               else
                        std->qtd = NULL; /* so this std is re-added when the qset is */
        }
 
                        std->qtd = NULL; /* so this std is re-added when the qset is */
        }
 
-       pzl_qset_remove(whc, qset);
-       wurb->status = status;
-       wurb->is_async = false;
-       queue_work(whc->workqueue, &wurb->dequeue_work);
-
+       if (has_qtd) {
+               pzl_qset_remove(whc, qset);
+               update_pzl_hw_view(whc);
+               wurb->status = status;
+               wurb->is_async = false;
+               queue_work(whc->workqueue, &wurb->dequeue_work);
+       } else
+               qset_remove_urb(whc, qset, urb, status);
 out:
        spin_unlock_irqrestore(&whc->lock, flags);
 
 out:
        spin_unlock_irqrestore(&whc->lock, flags);