ChangeSet 1.1557.49.24, 2004/02/18 13:17:51-08:00, [EMAIL PROTECTED]

[PATCH] USB: ehci-hcd, scheduler handles TT collisions (3/3)

[USB] ehci-hcd detects microframe transaction scheduling collisions

This detects TT schedule collisions for split periodic transactions
on USB 2.0 hubs, handling both isochronous and interrupt transfers
but not supporting the "wrap around to the next frame" modes for
either transfer type.

The ISO scheduler is modified to use that support for full speed
transfers ... though nothing will yet ask it to do that.

It also tracks schedule depth, to help avoid accepting ISO URBs
which need more time than currently remains unscheduled.


 drivers/usb/host/ehci-sched.c |  314 +++++++++++++++++++++++++++++++++++-------
 drivers/usb/host/ehci.h       |    6 
 2 files changed, 267 insertions(+), 53 deletions(-)


diff -Nru a/drivers/usb/host/ehci-sched.c b/drivers/usb/host/ehci-sched.c
--- a/drivers/usb/host/ehci-sched.c     Thu Feb 19 17:21:07 2004
+++ b/drivers/usb/host/ehci-sched.c     Thu Feb 19 17:21:07 2004
@@ -169,6 +169,93 @@
 
 /*-------------------------------------------------------------------------*/
 
+static int same_tt (struct usb_device *dev1, struct usb_device *dev2)
+{
+       if (!dev1->tt || !dev2->tt)
+               return 0;
+       if (dev1->tt != dev2->tt)
+               return 0;
+       if (dev1->tt->multi)
+               return dev1->ttport == dev2->ttport;
+       else
+               return 1;
+}
+
+/* return true iff the device's transaction translator is available
+ * for a periodic transfer starting at the specified frame, using
+ * all the uframes in the mask.
+ */
+static int tt_no_collision (
+       struct ehci_hcd         *ehci,
+       unsigned                period,
+       struct usb_device       *dev,
+       unsigned                frame,
+       u32                     uf_mask
+)
+{
+       if (period == 0)        /* error */
+               return 0;
+
+       /* note bandwidth wastage:  split never follows csplit
+        * (different dev or endpoint) until the next uframe.
+        * calling convention doesn't make that distinction.
+        */
+       for (; frame < ehci->periodic_size; frame += period) {
+               union ehci_shadow       here;
+               u32                     type;
+
+               here = ehci->pshadow [frame];
+               type = Q_NEXT_TYPE (ehci->periodic [frame]);
+               while (here.ptr) {
+                       switch (type) {
+                       case Q_TYPE_ITD:
+                               type = Q_NEXT_TYPE (here.itd->hw_next);
+                               here = here.itd->itd_next;
+                               continue;
+                       case Q_TYPE_QH:
+                               if (same_tt (dev, here.qh->dev)) {
+                                       u32             mask;
+
+                                       mask = le32_to_cpu (here.qh->hw_info2);
+                                       /* "knows" no gap is needed */
+                                       mask |= mask >> 8;
+                                       if (mask & uf_mask)
+                                               break;
+                               }
+                               type = Q_NEXT_TYPE (here.qh->hw_next);
+                               here = here.qh->qh_next;
+                               continue;
+                       case Q_TYPE_SITD:
+                               if (same_tt (dev, here.itd->urb->dev)) {
+                                       u16             mask;
+
+                                       mask = le32_to_cpu (here.sitd->hw_uframe);
+                                       /* FIXME assumes no gap for IN! */
+                                       mask |= mask >> 8;
+                                       if (mask & uf_mask)
+                                               break;
+                               }
+                               type = Q_NEXT_TYPE (here.qh->hw_next);
+                               here = here.sitd->sitd_next;
+                               break;
+                       // case Q_TYPE_FSTN:
+                       default:
+                               ehci_dbg (ehci,
+                                       "periodic frame %d bogus type %d\n",
+                                       frame, type);
+                       }
+
+                       /* collision or error */
+                       return 0;
+               }
+       }
+
+       /* no collision */
+       return 1;
+}
+
+/*-------------------------------------------------------------------------*/
+
 static int enable_periodic (struct ehci_hcd *ehci)
 {
        u32     cmd;
@@ -517,8 +604,10 @@
        unsigned                interval
 )
 {
+       static const u8 smask_out [] = { 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f };
+
        u32                     buf1;
-       unsigned                epnum, maxp, multi;
+       unsigned                epnum, maxp;
        int                     is_input;
        long                    bandwidth;
 
@@ -536,30 +625,62 @@
                buf1 = 0;
        }
 
-       stream->highspeed = 1;
+       /* knows about ITD vs SITD */
+       if (dev->speed == USB_SPEED_HIGH) {
+               unsigned multi = hb_mult(maxp);
+
+               stream->highspeed = 1;
+
+               maxp = max_packet(maxp);
+               buf1 |= maxp;
+               maxp *= multi;
+
+               stream->buf0 = cpu_to_le32 ((epnum << 8) | dev->devnum);
+               stream->buf1 = cpu_to_le32 (buf1);
+               stream->buf2 = cpu_to_le32 (multi);
+
+               /* usbfs wants to report the average usecs per frame tied up
+                * when transfers on this endpoint are scheduled ...
+                */
+               stream->usecs = HS_USECS_ISO (maxp);
+               bandwidth = stream->usecs * 8;
+               bandwidth /= 1 << (interval - 1);
+
+       } else {
+               u32             addr;
+
+               addr = dev->ttport << 24;
+               addr |= dev->tt->hub->devnum << 16;
+               addr |= epnum << 8;
+               addr |= dev->devnum;
+               stream->usecs = HS_USECS_ISO (maxp);
+               if (is_input) {
+                       u32     tmp;
+
+                       addr |= 1 << 31;
+                       stream->c_usecs = stream->usecs;
+                       stream->usecs = HS_USECS_ISO (1);
+                       stream->raw_mask = 1;
+
+                       /* pessimistic c-mask */
+                       tmp = usb_calc_bus_time (USB_SPEED_FULL, 1, 0, maxp)
+                                       / (125 * 1000);
+                       stream->raw_mask |= 3 << (tmp + 9);
+               } else
+                       stream->raw_mask = smask_out [maxp / 188];
+               bandwidth = stream->usecs + stream->c_usecs;
+               bandwidth /= 1 << (interval + 2);
 
-       multi = hb_mult(maxp);
-       maxp = max_packet(maxp);
-       buf1 |= maxp;
-       maxp *= multi;
+               /* stream->splits gets created from raw_mask later */
+               stream->address = cpu_to_le32 (addr);
+       }
+       stream->bandwidth = bandwidth;
 
        stream->udev = dev;
 
        stream->bEndpointAddress = is_input | epnum;
        stream->interval = interval;
        stream->maxp = maxp;
-
-       stream->buf0 = cpu_to_le32 ((epnum << 8) | dev->devnum);
-       stream->buf1 = cpu_to_le32 (buf1);
-       stream->buf2 = cpu_to_le32 (multi);
-
-       /* usbfs wants to report the average usecs per frame tied up
-        * when transfers on this endpoint are scheduled ...
-        */
-       stream->usecs = HS_USECS_ISO (maxp);
-       bandwidth = stream->usecs * 8;
-       bandwidth /= 1 << (interval - 1);
-       stream->bandwidth = bandwidth;
 }
 
 static void
@@ -781,6 +902,98 @@
        return 0;
 }
 
+/*-------------------------------------------------------------------------*/
+
+static inline int
+itd_slot_ok (
+       struct ehci_hcd         *ehci,
+       u32                     mod,
+       u32                     uframe,
+       u32                     end,
+       u8                      usecs,
+       u32                     period
+)
+{
+       do {
+               /* can't commit more than 80% periodic == 100 usec */
+               if (periodic_usecs (ehci, uframe >> 3, uframe & 0x7)
+                               > (100 - usecs))
+                       return 0;
+
+               /* we know urb->interval is 2^N uframes */
+               uframe += period;
+               uframe %= mod;
+       } while (uframe != end);
+       return 1;
+}
+
+static inline int
+sitd_slot_ok (
+       struct ehci_hcd         *ehci,
+       u32                     mod,
+       struct ehci_iso_stream  *stream,
+       u32                     uframe,
+       u32                     end,
+       struct ehci_iso_sched   *sched,
+       u32                     period_uframes
+)
+{
+       u32                     mask, tmp;
+       u32                     frame, uf;
+
+       mask = stream->raw_mask << (uframe & 7);
+
+       /* for IN, don't wrap CSPLIT into the next frame */
+       if (mask & ~0xffff)
+               return 0;
+
+       /* this multi-pass logic is simple, but performance may
+        * suffer when the schedule data isn't cached.
+        */
+
+       /* check bandwidth */
+       do {
+               u32             max_used;
+
+               frame = uframe >> 3;
+               uf = uframe & 7;
+
+               /* check starts (OUT uses more than one) */
+               max_used = 100 - stream->usecs;
+               for (tmp = stream->raw_mask & 0xff; tmp; tmp >>= 1, uf++) {
+                       if (periodic_usecs (ehci, frame, uf) > max_used)
+                               return 0;
+               }
+
+               /* for IN, check CSPLIT */
+               if (stream->c_usecs) {
+                       max_used = 100 - stream->c_usecs;
+                       do {
+                               /* tt is busy in the gap before CSPLIT */
+                               tmp = 1 << uf;
+                               mask |= tmp;
+                               tmp <<= 8;
+                               if (stream->raw_mask & tmp)
+                                       break;
+                       } while (++uf < 8);
+                       if (periodic_usecs (ehci, frame, uf) > max_used)
+                               return 0;
+               }
+
+               /* we know urb->interval is 2^N uframes */
+               uframe += period_uframes;
+               uframe %= mod;
+       } while (uframe != end);
+
+       /* tt must be idle for start(s), any gap, and csplit */
+       if (!tt_no_collision (ehci, period_uframes, stream->udev, frame, mask))
+               return 0;
+
+       stream->splits = stream->raw_mask << (uframe & 7);
+       cpu_to_le32s (&stream->splits);
+       return 1;
+}
+
 /*
  * This scheduler plans almost as far into the future as it has actual
  * periodic schedule slots.  (Affected by TUNE_FLS, which defaults to
@@ -795,23 +1008,30 @@
 #define SCHEDULE_SLOP  10      /* frames */
 
 static int
-itd_stream_schedule (
+iso_stream_schedule (
        struct ehci_hcd         *ehci,
        struct urb              *urb,
        struct ehci_iso_stream  *stream
 )
 {
-       u32                     now, start, end, max;
+       u32                     now, start, end, max, period;
        int                     status;
        unsigned                mod = ehci->periodic_size << 3;
        struct ehci_iso_sched   *sched = urb->hcpriv;
 
-       if (unlikely (sched->span > (mod - 8 * SCHEDULE_SLOP))) {
+       if (sched->span > (mod - 8 * SCHEDULE_SLOP)) {
                ehci_dbg (ehci, "iso request %p too long\n", urb);
                status = -EFBIG;
                goto fail;
        }
 
+       if ((stream->depth + sched->span) > mod) {
+               ehci_dbg (ehci, "request %p would overflow (%d+%d>%d)\n",
+                       urb, stream->depth, sched->span, mod);
+               status = -EFBIG;
+               goto fail;
+       }
+
        now = readl (&ehci->regs->frame_index) % mod;
 
        /* when's the last uframe this urb could start? */
@@ -823,18 +1043,12 @@
         * and no gaps from host falling behind (irq delays etc)
         */
        if (likely (!list_empty (&stream->td_list))) {
-
                start = stream->next_uframe;
                if (start < now)
                        start += mod;
                if (likely (start < max))
                        goto ready;
-
-               /* two cases:
-                * (a) we missed some uframes ... can reschedule
-                * (b) trying to overcommit the schedule
-                * FIXME (b) should be a hard failure
-                */
+               /* else fell behind; try to reschedule */
        }
 
        /* need to schedule; when's the next (u)frame we could start?
@@ -844,42 +1058,36 @@
         * jump until after the queue is primed.
         */
        start = SCHEDULE_SLOP * 8 + (now & ~0x07);
+       start %= mod;
        end = start;
 
-       ehci_vdbg (ehci, "%s schedule from %d (%d..%d), was %d\n",
-                       __FUNCTION__, now, start, max,
-                       stream->next_uframe);
-
        /* NOTE:  assumes URB_ISO_ASAP, to limit complexity/bugs */
 
-       if (likely (max > (start + urb->interval)))
-               max = start + urb->interval;
+       period = urb->interval;
+       if (!stream->highspeed)
+               period <<= 3;
+       if (max > (start + period))
+               max = start + period;
 
        /* hack:  account for itds already scheduled to this endpoint */
-       if (unlikely (list_empty (&stream->td_list)))
+       if (list_empty (&stream->td_list))
                end = max;
 
        /* within [start..max] find a uframe slot with enough bandwidth */
        end %= mod;
        do {
-               unsigned        uframe;
-               int             enough_space = 1;
+               int             enough_space;
 
                /* check schedule: enough space? */
-               uframe = start;
-               do {
-                       uframe %= mod;
-
-                       /* can't commit more than 80% periodic == 100 usec */
-                       if (periodic_usecs (ehci, uframe >> 3, uframe & 0x7)
-                                       > (100 - stream->usecs)) {
-                               enough_space = 0;
-                               break;
-                       }
-
-                       /* we know urb->interval is 2^N uframes */
-                       uframe += urb->interval;
-               } while (uframe != end);
+               if (stream->highspeed)
+                       enough_space = itd_slot_ok (ehci, mod, start, end,
+                                       stream->usecs, period);
+               else {
+                       if ((start % 8) >= 6)
+                               continue;
+                       enough_space = sitd_slot_ok (ehci, mod, stream,
+                                       start, end, sched, period);
+               }
 
                /* (re)schedule it here if there's enough bandwidth */
                if (enough_space) {
@@ -1035,6 +1243,7 @@
                first = 0;
 
                next_uframe += stream->interval;
+               stream->depth += stream->interval;
                next_uframe %= mod;
                packet++;
 
@@ -1081,6 +1290,7 @@
 
                t = le32_to_cpup (&itd->hw_transaction [uframe]);
                itd->hw_transaction [uframe] = 0;
+               stream->depth -= stream->interval;
 
                /* report transfer status */
                if (unlikely (t & ISO_ERRS)) {
@@ -1183,7 +1393,7 @@
 
        /* schedule ... need to lock */
        spin_lock_irqsave (&ehci->lock, flags);
-       status = itd_stream_schedule (ehci, urb, stream);
+       status = iso_stream_schedule (ehci, urb, stream);
        if (likely (status == 0))
                itd_link_urb (ehci, urb, ehci->periodic_size << 3, stream);
        spin_unlock_irqrestore (&ehci->lock, flags);
diff -Nru a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h
--- a/drivers/usb/host/ehci.h   Thu Feb 19 17:21:07 2004
+++ b/drivers/usb/host/ehci.h   Thu Feb 19 17:21:07 2004
@@ -418,6 +418,7 @@
        u32                     refcount;
        u8                      bEndpointAddress;
        u8                      highspeed;
+       u16                     depth;          /* depth in uframes */
        struct list_head        td_list;        /* queued itds/sitds */
        struct list_head        free_list;      /* list of unused itds/sitds */
        struct usb_device       *udev;
@@ -426,6 +427,7 @@
        unsigned long           start;          /* jiffies */
        unsigned long           rescheduled;
        int                     next_uframe;
+       u32                     splits;
 
        /* the rest is derived from the endpoint descriptor,
         * trusting urb->interval == f(epdesc->bInterval) and
@@ -434,6 +436,7 @@
        u8                      interval;
        u8                      usecs, c_usecs;
        u16                     maxp;
+       u16                     raw_mask;
        unsigned                bandwidth;
 
        /* This is used to initialize iTD's hw_bufp fields */
@@ -441,7 +444,8 @@
        u32                     buf1;           
        u32                     buf2;
 
-       /* ... sITD won't use buf[012], and needs TT access ... */
+       /* this is used to initialize sITD's tt info */
+       u32                     address;
 };
 
 /*-------------------------------------------------------------------------*/



-------------------------------------------------------
SF.Net is sponsored by: Speed Start Your Linux Apps Now.
Build and deploy apps & Web services for Linux with
a free DVD software kit from IBM. Click Now!
http://ads.osdn.com/?ad_id56&alloc_id438&op=click
_______________________________________________
[EMAIL PROTECTED]
To unsubscribe, use the last form field at:
https://lists.sourceforge.net/lists/listinfo/linux-usb-devel

Reply via email to