On Thu, May 03, 2001, Martin Diehl <[EMAIL PROTECTED]> wrote:
> > Anyway - chances are the observation that uhci_append_queued_urb() makes
> > significant contribution to the problem might be helpful. Probably the
> > eurb which was determined much earlier with status==EINPROGRESS might be
> > racy: all active TD of eurb might meanwhile been transmitted. This might 
> > even be true from the very beginning - eurb is determined inside
> > spin_lock_irqsave, so status might be outdated (i.e. all its TD's
> > already inactive at this point). So we end up connecting the new QH's TD
> > to a lltd->link, which is now out-of-reach for the HC?
> 
> just proved: exactly this HC/HCD race is killing my test!
> 
> After narrowing down the racy window the issue disappeared. Idea is to
> test if lltd->status is still set ACTIVE as late as possible before
> appending the new QH. Fallback to normal insertion if found inactive.
> With this change applied I was unable to break it with all the tests tried
> so far - including the remote floodping saturation which used to kill the
> thing instantaneously.
> 
> Two points two verify:
> - idea is based on the assumption, whenever we find *lltd (the last TD
>   from the last QH for the same EP) beeing inactive there are absolutely
>   no active TD's pending for this EP - do you see any reason this might be
>   wrong?
> - There is still a minor window for this race between testing lltd->status
>   and lltd->link update becomes visible to the HC. Lower limit for this
>   window is given by the uhci_fixup_toggle() call, i.e. depends on the
>   number of TD's for the QH to be appended. For example, assuming
>   something like 100nsec/TD to update toggle a 8KB=128*64 transfer would
>   give about 10usec window. Not so small compared to about 50 usec the TD
>   is on the wire. I'm not sure whether we can move the call in front of
>   the test - we would have to restore the toggles in case the test fails
>   and we fallback. If so, the window would be reduced to some 10 nsec,
>   i.e. shorter than USB-1.1 bittime - should be save I think.
>   Of course the right thing would be to synchronize with HC, but I don't
>   see how to do this.
>   Currently I've kept the toggle-call in the window and tried to detect if
>   we were overrun - which in turn has a mini-window for false positive...
> 
> Updated patch below. The additional more or less cosmetical changes
> (driver name, GFP_DMA) still include the /proc/driver/uhci typo fix
> you've already forwarded to Linus - patch is vs. 2.4.4.

Here's a different version which I think isn't susceptible to aformentioned
races. I'm going to go over it some more, but I've had good results with
it so far.

On Thu, May 03, 2001, Georg Acher <[EMAIL PROTECTED]> wrote:
> On Thu, May 03, 2001 at 05:37:09PM +0200, Martin Diehl wrote:
> <...>
> > > spin_lock_irqsave, so status might be outdated (i.e. all its TD's
> > > already inactive at this point). So we end up connecting the new QH's TD
> > > to a lltd->link, which is now out-of-reach for the HC?
> > 
> > just proved: exactly this HC/HCD race is killing my test!
> 
> This is exactly the cause, why usb-uhci is always using a "bottom QH" (bqh)
> as the last element in the vertical TD list when queuing bulks. With this
> last QH, UHCI enters a new context and doesn't update the element pointer of
> the old "top" QH. So there's no race when appending new TDs, the old bqh is
> recycled as the top QH of the new TD list. Of course, there's a slight
> memory cost, but absolutely no race window.
> 
> BTW: The helper QH just serves as unified "collection" point, where all bqhs
> are horizontally linked to. It eliminates the work of re-linking of all
> bqhs when a following QH is deleted.

Ahh, so that's what the code was doing. I looked at your driver to see
what you were doing but couldn't figure it out. That makes perfect sense
and is definately a better solution. Thanks for the explanation. My patch
makes uhci.c operate like that.

JE

--- linux-2.4.5-pre1.orig/drivers/usb/uhci.c    Wed May  2 12:25:47 2001
+++ linux-2.4.5-pre1/drivers/usb/uhci.c Thu May  3 14:32:41 2001
@@ -306,7 +306,7 @@
 /*
  * Inserts a td into qh list at the top.
  */
-static void uhci_insert_tds_in_qh(struct uhci_qh *qh, struct urb *urb, int breadth)
+static void uhci_insert_tds_in_qh(struct uhci_qh *qh, struct urb *urb, int breadth, 
+struct uhci_qh *bqh)
 {
        struct list_head *tmp, *head;
        struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv;
@@ -339,7 +339,10 @@
                ptd = td;
        }
 
-       ptd->link = UHCI_PTR_TERM;
+       if (bqh)
+               ptd->link = bqh->dma_handle | UHCI_PTR_QH;
+       else
+               ptd->link = UHCI_PTR_TERM;
 }
 
 static void uhci_free_td(struct uhci *uhci, struct uhci_td *td)
@@ -390,12 +393,9 @@
        pci_pool_free(uhci->qh_pool, qh, qh->dma_handle);
 }
 
-static void uhci_insert_qh(struct uhci *uhci, struct uhci_qh *skelqh, struct uhci_qh 
*qh)
+static void _uhci_insert_qh(struct uhci_qh *skelqh, struct uhci_qh *qh)
 {
        struct uhci_qh *lqh;
-       unsigned long flags;
-
-       spin_lock_irqsave(&uhci->frame_list_lock, flags);
 
        /* Grab the last QH */
        lqh = list_entry(skelqh->list.prev, struct uhci_qh, list);
@@ -405,6 +405,15 @@
        lqh->link = qh->dma_handle | UHCI_PTR_QH;
 
        list_add_tail(&qh->list, &skelqh->list);
+}
+
+static void uhci_insert_qh(struct uhci *uhci, struct uhci_qh *skelqh, struct uhci_qh 
+*qh)
+{
+       unsigned long flags;
+
+       spin_lock_irqsave(&uhci->frame_list_lock, flags);
+
+       _uhci_insert_qh(skelqh, qh);
 
        spin_unlock_irqrestore(&uhci->frame_list_lock, flags);
 }
@@ -511,7 +520,7 @@
 
        mb();                   /* Make sure we flush everything */
        /* Only support bulk right now, so no depth */
-       lltd->link = ftd->dma_handle;
+       lurbp->bqh->element = ftd->dma_handle;
 
        list_add_tail(&urbp->queue_list, &furbp->queue_list);
 
@@ -571,28 +580,13 @@
                usb_pipeout(urb->pipe), toggle);
 
        if (!urbp->queued) {
-               int status;
-
-               /*  The HC may continue using the current QH if it finished */
-               /* all of the TD's in this URB and may have started on the */
-               /* next URB's TD's already, so we'll take over ownership */
-               /* of this QH and use it instead. Don't forget to delete */
-               /* the old QH first */
+               /* Steal the bottom QH from the previous URB */
                uhci_free_qh(uhci, nurbp->qh);
+               nurbp->qh = urbp->bqh;
+               nurbp->qh->urbp = urbp;
+               urbp->bqh = NULL;
 
-               nurbp->qh = urbp->qh;
-               nurbp->qh->urbp = nurbp;
-               urbp->qh = NULL;
-
-               /* If the last TD from the first (this) urb didn't */
-               /*  complete, reset qh->element to the first TD in the */
-               /*  next urb */
-               pltd = list_entry(urbp->td_list.prev, struct uhci_td, list);
-               status = uhci_status_bits(pltd->status);
-               if ((status & TD_CTRL_ACTIVE) || uhci_actual_length(pltd->status) < 
uhci_expected_length(pltd->info)) {
-                       struct uhci_td *ftd = list_entry(nurbp->td_list.next, struct 
uhci_td, list);
-                       nurbp->qh->element = ftd->dma_handle;
-               }
+               _uhci_insert_qh(uhci->skel_bulk_qh, nurbp->qh);
 
                nurbp->queued = 0;
        } else {
@@ -899,10 +893,10 @@
 
        /* Low speed or small transfers gets a different queue and treatment */
        if (urb->pipe & TD_CTRL_LS) {
-               uhci_insert_tds_in_qh(qh, urb, 0);
+               uhci_insert_tds_in_qh(qh, urb, 0, NULL);
                uhci_insert_qh(uhci, uhci->skel_ls_control_qh, qh);
        } else {
-               uhci_insert_tds_in_qh(qh, urb, 1);
+               uhci_insert_tds_in_qh(qh, urb, 1, NULL);
                uhci_insert_qh(uhci, uhci->skel_hs_control_qh, qh);
                uhci_inc_fsbr(uhci, urb);
        }
@@ -1064,7 +1058,7 @@
        urbp->qh->urbp = urbp;
 
        /* One TD, who cares about Breadth first? */
-       uhci_insert_tds_in_qh(urbp->qh, urb, 0);
+       uhci_insert_tds_in_qh(urbp->qh, urb, 0, NULL);
 
        /* Low speed or small transfers gets a different queue and treatment */
        if (urb->pipe & TD_CTRL_LS)
@@ -1277,8 +1271,16 @@
        urbp->qh = qh;
        qh->urbp = urbp;
 
+       if (urb->transfer_flags & USB_QUEUE_BULK) {
+               urbp->bqh = uhci_alloc_qh(uhci, urb->dev);
+               if (!urbp->bqh)
+                       return -ENOMEM;
+
+               urbp->bqh->urbp = urbp;
+       }
+
        /* Always assume breadth first */
-       uhci_insert_tds_in_qh(qh, urb, 1);
+       uhci_insert_tds_in_qh(qh, urb, 1, urbp->bqh);
 
        if (urb->transfer_flags & USB_QUEUE_BULK && eurb)
                uhci_append_queued_urb(uhci, eurb, urb);
@@ -1674,9 +1676,12 @@
 
        uhci_delete_queued_urb(uhci, urb);
 
+       /* The interrupt loop will reclaim the QH's */
        if (urbp->qh)
-               /* The interrupt loop will reclaim the QH's */
                uhci_remove_qh(uhci, urbp->qh);
+
+       if (urbp->bqh)
+               uhci_remove_qh(uhci, urbp->bqh);
 }
 
 /* FIXME: If we forcefully unlink an urb, we should reset the toggle for */
--- linux-2.4.5-pre1.orig/drivers/usb/uhci.h    Fri Apr 27 15:49:56 2001
+++ linux-2.4.5-pre1/drivers/usb/uhci.h Thu May  3 11:31:28 2001
@@ -331,7 +331,8 @@
        dma_addr_t setup_packet_dma_handle;
        dma_addr_t transfer_buffer_dma_handle;
 
-       struct uhci_qh *qh;             /* QH for this URB */
+       struct uhci_qh *qh;
+       struct uhci_qh *bqh;            /* For queued URB's */
        struct list_head td_list;
 
        int fsbr : 1;                   /* URB turned on FSBR */

_______________________________________________
[EMAIL PROTECTED]
To unsubscribe, use the last form field at:
http://lists.sourceforge.net/lists/listinfo/linux-usb-devel

Reply via email to