Hi,

I tried to rewrite USB 2.0 isochronous transfer support and here is the patch against 2.6.0-test7.

I tested this code with our measuring device and I was able to do high-speed high-bandwidth transfer (IN transactions) continuously for very long time. In my opinion there are still some things to do on this code, but because I'm quite new to Linux kernel programming I would like to hear some comments.

There are some questions included in the source and I don't know the answers. These are mainly:

  1. How to claim and disclaim bandwidth pro iso transfer? Should I
     call usb_claimb_bandwidth for each urb?
  2. Performance of scan_periodic. I thing that the loop in
     scan_periodic may block interrupts for long time and it is better
     don't restart the loop when micro frame counter changes during
     processing the periodic list.
  3. Reentrancy of scan_periodic. When I started studying of ehci isoc
     code, I thought that scan_periodic should be reentrant because of
     dropping of ehci lock when giving urb back to the driver. Original
     version wasn’t reentrant, because of variable ehci->next_uframe
     which wasn’t updated before returning urb. When I added updating
     of this variable it behaves better. After this I started with
     deeper rewriting of code and now it seems, that is not necessary
     to update this variable.

Regards

Michal Sojka

diff -urN -X /root/emise/dontdiff linux-2.6.0-test7/drivers/usb/host.orig/ehci-dbg.c 
linux-2.6.0-test7/drivers/usb/host/ehci-dbg.c
--- linux-2.6.0-test7/drivers/usb/host.orig/ehci-dbg.c  2003-10-11 14:40:25.000000000 
+0200
+++ linux-2.6.0-test7/drivers/usb/host/ehci-dbg.c       2003-10-11 14:53:45.000000000 
+0200
@@ -29,12 +29,49 @@
 
 #ifdef EHCI_VERBOSE_DEBUG
 #      define vdbg dbg
+#      define xdbg dbg
 #      define ehci_vdbg ehci_dbg
 #else
 #      define vdbg(fmt,args...) do { } while (0)
+#      define xdbg(fmt,args...) do { } while (0)
 #      define ehci_vdbg(ehci, fmt, args...) do { } while (0)
 #endif
 
+#ifdef DEBUG
+void dump_itd(struct ehci_itd *itd) 
+{
+       dbg("dumping itd %p", itd);
+       dbg("hw_next: %08x", le32_to_cpu(itd->hw_next));
+       dbg("hw_transaction[0-7]: %08x %08x %08x %08x %08x %08x %08x %08x", 
+           le32_to_cpu(itd->hw_transaction[0]),
+           le32_to_cpu(itd->hw_transaction[1]),
+           le32_to_cpu(itd->hw_transaction[2]),
+           le32_to_cpu(itd->hw_transaction[3]),
+           le32_to_cpu(itd->hw_transaction[4]),
+           le32_to_cpu(itd->hw_transaction[5]),
+           le32_to_cpu(itd->hw_transaction[6]),
+           le32_to_cpu(itd->hw_transaction[7]));
+       dbg("hw_bufp[0-6]: %08x %08x %08x %08x %08x %08x %08x", 
+           le32_to_cpu(itd->hw_bufp[0]),
+           le32_to_cpu(itd->hw_bufp[1]),
+           le32_to_cpu(itd->hw_bufp[2]),
+           le32_to_cpu(itd->hw_bufp[3]),
+           le32_to_cpu(itd->hw_bufp[4]),
+           le32_to_cpu(itd->hw_bufp[5]),
+           le32_to_cpu(itd->hw_bufp[6]));
+       dbg("urb %p", itd->urb);
+       dbg("index[0-7]: %d %d %d %d %d %d %d %d",
+           itd->index[0],
+           itd->index[1],
+           itd->index[2],
+           itd->index[3],
+           itd->index[4],
+           itd->index[5],
+           itd->index[6],
+           itd->index[7]);
+}
+#endif /* DEBUG */
+
 #ifdef DEBUG
 
 /* check the values in the HCSPARAMS register
diff -urN -X /root/emise/dontdiff linux-2.6.0-test7/drivers/usb/host.orig/ehci.h 
linux-2.6.0-test7/drivers/usb/host/ehci.h
--- linux-2.6.0-test7/drivers/usb/host.orig/ehci.h      2003-07-14 05:37:27.000000000 
+0200
+++ linux-2.6.0-test7/drivers/usb/host/ehci.h   2003-10-11 14:53:45.000000000 +0200
@@ -383,6 +383,61 @@
 /*-------------------------------------------------------------------------*/
 
 /*
+ * Description of one iso micro-frame transaction
+ *
+ */
+
+struct ehci_iso_uframe {
+       /* These will be copied to iTD when scheduling */
+       u32                     transaction;
+       u64                     bufp;           /* page for beginning of this 
transaction */
+
+       /* other useful values */
+       dma_addr_t              buf_dma;        /* for this uframe */
+       u16                     usecs;          
+       u16                     uframe;         /* when scheduled */
+       struct  ehci_itd        *itd;           /* in which itd */
+};
+
+/*
+ * ehci_iso_priv - used as urb->hc_priv for iso transfer
+ */
+struct ehci_iso_priv {
+       struct ehci_iso_stream  *iso_stream;
+       struct list_head        itd_list;
+       int                     start;          /* uframe where schedule starts */
+       int                     span;           /* how many uframes uses this urb*/
+       int                     num_itds;       /* Num. iTDs allocated for this urb. */
+       u64                     last_bufp;      /* last transaction may overlap to 
this page */
+       unsigned                usecs;          /* time, this urb consumes */
+
+       struct ehci_iso_uframe  uframe[0];
+};
+
+/*
+ * ehci_iso_stream 
+ */
+struct ehci_iso_stream {
+       struct list_head        free_itd_list;  /* list of unused itds */
+
+       struct hcd_dev          *dev;
+       unsigned                epnum;
+
+       int                     urb_count;      /* active urbs for this endpoint */
+
+       int                     is_input;
+       int                     maxp;           /* max packet size */
+       /* This is used to initialize iTD's fields */
+       u32                     buf0;           
+       u32                     buf1;           
+       u32                     buf2;
+
+       unsigned                bandwidth;
+};
+
+/*-------------------------------------------------------------------------*/
+
+/*
  * EHCI Specification 0.95 Section 3.3
  * Fig 3-4 "Isochronous Transaction Descriptor (iTD)"
  *
@@ -407,14 +462,9 @@
        union ehci_shadow       itd_next;       /* ptr to periodic q entry */
 
        struct urb              *urb;
-       struct list_head        itd_list;       /* list of urb frames' itds */
-       dma_addr_t              buf_dma;        /* frame's buffer address */
-
-       /* for now, only one hw_transaction per itd */
-       u32                     transaction;
-       u16                     index;          /* in urb->iso_frame_desc */
-       u16                     uframe;         /* in periodic schedule */
-       u16                     usecs;
+       int                     usecs;
+       int                     index[8];       /* which urb->iso_frame_desc are here 
*/
+       struct list_head        itd_list;       /* list of urbs' itds */
 } __attribute__ ((aligned (32)));
 
 /*-------------------------------------------------------------------------*/
diff -urN -X /root/emise/dontdiff linux-2.6.0-test7/drivers/usb/host.orig/ehci-sched.c 
linux-2.6.0-test7/drivers/usb/host/ehci-sched.c
--- linux-2.6.0-test7/drivers/usb/host.orig/ehci-sched.c        2003-10-11 
14:40:25.000000000 +0200
+++ linux-2.6.0-test7/drivers/usb/host/ehci-sched.c     2003-10-11 16:41:20.046955232 
+0200
@@ -33,6 +33,11 @@
  * or with "USB On The Go" additions to USB 2.0 ...)
  */
 
+/*
+ * Changes:
+ *
+ * 2003 Michal Sojka <[EMAIL PROTECTED]> Rewritten iso transfer support.
+ */
 static int ehci_get_frame (struct usb_hcd *hcd);
 
 /*-------------------------------------------------------------------------*/
@@ -520,52 +525,73 @@
 
 /*-------------------------------------------------------------------------*/
 
+static int
+itd_fill (
+       struct ehci_itd *itd,
+       struct urb *urb
+) {
+       int i;
+     
+       itd->hw_next = EHCI_LIST_END;
+       itd->urb = urb;
+
+       for (i = 0; i < 8; i++) itd->index[i] = -1;
+
+       /* All other fields are filled when scheduling */
+
+       return 0;
+}
+ 
+static struct ehci_iso_stream *
+iso_stream_alloc (int mem_flags)
+{
+       struct ehci_iso_stream *iso_stream;
+
+       iso_stream = (struct ehci_iso_stream *)kmalloc(sizeof(struct ehci_iso_stream), 
mem_flags);
+
+       if (unlikely (iso_stream == 0 )) 
+               return iso_stream;
+
+       memset (iso_stream, 0, sizeof(*iso_stream));
+
+       return iso_stream;
+}
+
 static void
-itd_free_list (struct ehci_hcd *ehci, struct urb *urb)
+iso_stream_free(struct ehci_hcd *ehci, struct ehci_iso_stream *iso_stream)
 {
-       struct ehci_itd *first_itd = urb->hcpriv;
+       xdbg("iso_stream_free: iso_stream %p", iso_stream);
+
+       /* TODO: If bandwidth is claimed for first urb only, then
+        * disclaim it here. Isn't disclaiming done automaticly in
+        * urb_unlink? */
 
-       while (!list_empty (&first_itd->itd_list)) {
+       while (!list_empty (&iso_stream->free_itd_list)) {
                struct ehci_itd *itd;
 
-               itd = list_entry (
-                       first_itd->itd_list.next,
+               itd = list_entry (iso_stream->free_itd_list.next,
                        struct ehci_itd, itd_list);
                list_del (&itd->itd_list);
                pci_pool_free (ehci->itd_pool, itd, itd->itd_dma);
+               
+               xdbg("itd freeed %p", itd);
        }
-       pci_pool_free (ehci->itd_pool, first_itd, first_itd->itd_dma);
-       urb->hcpriv = 0;
-}
 
-static int
-itd_fill (
-       struct ehci_hcd *ehci,
-       struct ehci_itd *itd,
-       struct urb      *urb,
-       unsigned        index,          // urb->iso_frame_desc [index]
-       dma_addr_t      dma             // mapped transfer buffer
-) {
-       u64             temp;
-       u32             buf1;
-       unsigned        i, epnum, maxp, multi;
-       unsigned        length;
-       int             is_input;
+       iso_stream->dev->ep[iso_stream->epnum] = 0;
 
-       itd->hw_next = EHCI_LIST_END;
-       itd->urb = urb;
-       itd->index = index;
+       kfree(iso_stream);
+       
+/*     urb->hcpriv = 0; */
+}
 
-       /* tell itd about its transfer buffer, max 2 pages */
-       length = urb->iso_frame_desc [index].length;
-       dma += urb->iso_frame_desc [index].offset;
-       temp = dma & ~0x0fff;
-       for (i = 0; i < 2; i++) {
-               itd->hw_bufp [i] = cpu_to_le32 ((u32) temp);
-               itd->hw_bufp_hi [i] = cpu_to_le32 ((u32)(temp >> 32));
-               temp += 0x1000;
-       }
-       itd->buf_dma = dma;
+void
+iso_stream_fill (
+       struct ehci_iso_stream  *iso_stream,
+       struct urb              *urb) 
+{
+       u32                     buf1;
+       unsigned                epnum, maxp, multi;
+       int                     is_input;
 
        /*
         * this might be a "high bandwidth" highspeed endpoint,
@@ -573,6 +599,7 @@
         */
        epnum = usb_pipeendpoint (urb->pipe);
        is_input = usb_pipein (urb->pipe);
+       iso_stream->is_input = is_input;
        if (is_input) {
                maxp = urb->dev->epmaxpacketin [epnum];
                buf1 = (1 << 11);
@@ -580,80 +607,214 @@
                maxp = urb->dev->epmaxpacketout [epnum];
                buf1 = 0;
        }
-       buf1 |= (maxp & 0x03ff);
+
+       buf1 |= (maxp & 0x07ff);
        multi = 1;
        multi += (maxp >> 11) & 0x03;
-       maxp &= 0x03ff;
+       maxp &= 0x07ff;
        maxp *= multi;
 
-       /* transfer can't fit in any uframe? */ 
-       if (length < 0 || maxp < length) {
-               dbg ("BAD iso packet: %d bytes, max %d, urb %p [%d] (of %d)",
-                       length, maxp, urb, index,
-                       urb->iso_frame_desc [index].length);
-               return -ENOSPC;
-       }
-       itd->usecs = usb_calc_bus_time (USB_SPEED_HIGH, is_input, 1, length);
-
-       /* "plus" info in low order bits of buffer pointers */
-       itd->hw_bufp [0] |= cpu_to_le32 ((epnum << 8) | urb->dev->devnum);
-       itd->hw_bufp [1] |= cpu_to_le32 (buf1);
-       itd->hw_bufp [2] |= cpu_to_le32 (multi);
-
-       /* figure hw_transaction[] value (it's scheduled later) */
-       itd->transaction = EHCI_ISOC_ACTIVE;
-       itd->transaction |= dma & 0x0fff;               /* offset; buffer=0 */
-       if ((index + 1) == urb->number_of_packets)
-               itd->transaction |= EHCI_ITD_IOC;       /* end-of-urb irq */
-       itd->transaction |= length << 16;
-       cpu_to_le32s (&itd->transaction);
+       INIT_LIST_HEAD(&iso_stream->free_itd_list);
+       iso_stream->dev = (struct hcd_dev *)urb->dev->hcpriv;
+       iso_stream->epnum = epnum;
+       iso_stream->buf0 = cpu_to_le32 ((epnum << 8) | urb->dev->devnum);
+       iso_stream->buf1 = cpu_to_le32 (buf1);
+       iso_stream->buf2 = cpu_to_le32 (multi);
+       iso_stream->maxp = maxp;
+}
+
+static struct ehci_iso_priv *
+iso_priv_alloc (int uframes, int mem_flags)
+{
+       struct ehci_iso_priv *iso_priv;
+       int size = sizeof (*iso_priv) + uframes * sizeof(struct ehci_iso_uframe);
+
+       iso_priv = kmalloc (size, mem_flags);
+
+       if (unlikely (iso_priv == 0)) 
+               return iso_priv;
+
+       memset(iso_priv, 0, size);
+
+       INIT_LIST_HEAD(&iso_priv->itd_list);
+
+       return iso_priv;
+}
+
+static int
+iso_priv_fill (
+       struct ehci_iso_priv *iso_priv,
+       struct ehci_iso_stream *iso_stream,
+       struct urb *urb
+       )
+{
+       int i;
+       u64 page = 0;
+       unsigned usecs = 0;
+       dma_addr_t dma = urb->transfer_dma;
+       int temp;
+
+       iso_priv->iso_stream = iso_stream;
+
+       /* how many uframes is needed for transfer */
+       iso_priv->span = urb->number_of_packets * urb->interval;
+
+       /* FIXME: What about endpoints with interval > 8? We can allocate less iTDs. */
+
+       temp = iso_priv->span - urb->interval + 1; /* Num. of uframes we need itds 
for. */
+
+       iso_priv->num_itds = (temp + 7) / 8 + 1;   /* schedule can start at any uframe 
in
+                                                   * a frame - consider overlaping to
+                                                   * next frames */
+
+       for (i = 0; i < urb->number_of_packets; i++) {
+               struct ehci_iso_uframe  *uframe = &iso_priv->uframe[i];
+               unsigned                length = urb->iso_frame_desc[i].length;
+               dma_addr_t              uframe_dma = dma + 
urb->iso_frame_desc[i].offset;
+
+               page = uframe_dma & ~0x0fff;
+       
+               /* transfer can't fit in any uframe? */
+               if (length < 0 || length > iso_stream->maxp) {
+                       /* FIXME: Isn't this checked elsewhere? */
+                       dbg ("BAD iso packet: %d bytes, max %d, urb %p [%d]",
+                            length, iso_stream->maxp, urb, i);
+                       return -ENOSPC;
+               }
+
+               /* Prepare values for iTD. FIXME: We can do this
+                * probably later, when sheduling iTDs. Here is it
+                * from historical reasons :-) */
+               uframe->transaction = EHCI_ISOC_ACTIVE;
+               uframe->transaction |= uframe_dma & 0x0fff;
+               if ((i + 1) == urb->number_of_packets)
+                       uframe->transaction |= EHCI_ITD_IOC; /* end-of-urb irq */
+               uframe->transaction |= length << 16;
+               /* conversion to LE is done when scheduling */
+
+               uframe->bufp = page;
+               uframe->buf_dma = uframe_dma;
+
+               uframe->usecs = usb_calc_bus_time (USB_SPEED_HIGH, 
iso_stream->is_input, 1, length);
+               usecs += uframe->usecs;
+       }
+
+       page += 0x1000;
+       iso_priv->last_bufp = page;
+       
+       iso_priv->usecs = usecs;
+       
+       return 0; 
+}
+
+static int
+iso_priv_free(struct ehci_hcd *ehci, struct ehci_iso_priv *iso_priv)
+{
+       struct ehci_iso_stream *iso_stream;
+
+       if (iso_priv == 0) return 0;
+
+       iso_stream = iso_priv->iso_stream;
+
+       list_splice(&iso_priv->itd_list, &iso_stream->free_itd_list);
+       
+       kfree(iso_priv);
+
+       if (--iso_stream->urb_count == 0)
+               iso_stream_free(ehci, iso_stream);
 
        return 0;
 }
 
+
 static int
 itd_urb_transaction (
+       struct ehci_iso_stream  *iso_stream,
        struct ehci_hcd         *ehci,
        struct urb              *urb,
        int                     mem_flags
-) {
-       int                     frame_index;
-       struct ehci_itd         *first_itd, *itd;
+       )
+{
+       struct ehci_itd         *itd;
        int                     status;
        dma_addr_t              itd_dma;
+       int                     i;
+       struct ehci_iso_priv    *iso_priv;
+
+       iso_priv = iso_priv_alloc(urb->number_of_packets, mem_flags);
+
+       if (unlikely (iso_priv == 0)) 
+               return -ENOMEM;
+
+       urb->hcpriv = iso_priv;
+
+       status = iso_priv_fill(iso_priv, iso_stream, urb);
+       if (status != 0) 
+               return status;
+
+
+       iso_stream->urb_count++;
+
+
+       if (iso_stream->bandwidth == 0) {
+               /* update bandwidth utilization records (for usbfs) */
+               unsigned usecs = iso_priv->usecs;
+
+               usecs /= urb->number_of_packets;
+               usecs /= urb->interval;
+               usecs >>= 3;
+               if (usecs < 1)
+                       usecs = 1;
+               iso_stream->bandwidth = usecs;
+               usb_claim_bandwidth (urb->dev, urb, usecs, 1); 
+                /* FIXME: Should we claim bandwidth for the first urb only or for all 
urbs
+                * (lines commented out bellow)? */
+       } 
+/*     else usecs = iso_stream->bandwidth; */
+/*     usb_claim_bandwidth (urb->dev, urb, usecs, 1); */
+
+       i = 0;
+
+       /* use previously allocated itds if possible */
+       while (! list_empty (&iso_stream->free_itd_list)) {
+               itd = list_entry(iso_stream->free_itd_list.next,
+                                struct ehci_itd, itd_list);
+               list_del(&itd->itd_list);
 
-       /* allocate/init ITDs */
-       for (frame_index = 0, first_itd = 0;
-                       frame_index < urb->number_of_packets;
-                       frame_index++) {
+               list_add_tail (&itd->itd_list, &iso_priv->itd_list);
+
+               itd_dma = itd->itd_dma;
+               memset (itd, 0, sizeof *itd);
+               itd->itd_dma = itd_dma;
+
+               status = itd_fill (itd, urb);
+               if (status != 0)
+                       return status;
+
+               i++;
+       }
+       
+
+       /* allocate/init new ITDs */
+       for (; i < iso_priv->num_itds; i++) {
                itd = pci_pool_alloc (ehci->itd_pool, mem_flags, &itd_dma);
-               if (!itd) {
-                       status = -ENOMEM;
-                       goto fail;
-               }
+               xdbg("itd allocated %p", itd);
+               if (!itd)
+                       return -ENOMEM;
+
                memset (itd, 0, sizeof *itd);
                itd->itd_dma = itd_dma;
 
-               status = itd_fill (ehci, itd, urb, frame_index,
-                               urb->transfer_dma);
+               list_add_tail (&itd->itd_list, &iso_priv->itd_list);
+
+               status = itd_fill (itd, urb);
                if (status != 0)
-                       goto fail;
+                       return status;
 
-               if (first_itd)
-                       list_add_tail (&itd->itd_list,
-                                       &first_itd->itd_list);
-               else {
-                       INIT_LIST_HEAD (&itd->itd_list);
-                       urb->hcpriv = first_itd = itd;
-               }
        }
+
        urb->error_count = 0;
        return 0;
-
-fail:
-       if (urb->hcpriv)
-               itd_free_list (ehci, urb);
-       return status;
 }
 
 /*-------------------------------------------------------------------------*/
@@ -664,6 +825,8 @@
        /* always prepend ITD/SITD ... only QH tree is order-sensitive */
        itd->itd_next = ehci->pshadow [frame];
        itd->hw_next = ehci->periodic [frame];
+       //xdbg("link itd %p @ fr %d, next %p", itd, frame, itd->itd_next.itd);
+       //dump_itd(itd);
        ehci->pshadow [frame].itd = itd;
        ehci->periodic [frame] = cpu_to_le32 (itd->itd_dma) | Q_TYPE_ITD;
 }
@@ -680,19 +843,19 @@
        unsigned                *start,
        unsigned                *max,
        unsigned                mod
-) {
+)
+{
        struct list_head        *lh;
        struct hcd_dev          *dev = urb->dev->hcpriv;
        int                     last = -1;
-       unsigned                now, span, end;
-
-       span = urb->interval * urb->number_of_packets;
+       unsigned                now, end;
+       struct ehci_iso_priv    *iso_priv = urb->hcpriv;
 
        /* first see if we know when the next transfer SHOULD happen */
        list_for_each (lh, &dev->urb_list) {
-               struct urb      *u;
-               struct ehci_itd *itd;
-               unsigned        s;
+               struct urb              *u;
+               struct ehci_iso_priv    *priv;
+               unsigned                s;
 
                u = list_entry (lh, struct urb, urb_list);
                if (u == urb || u->pipe != urb->pipe)
@@ -704,8 +867,8 @@
                }
                
                /* URB for this endpoint... covers through when?  */
-               itd = urb->hcpriv;
-               s = itd->uframe + u->interval * u->number_of_packets;
+               priv = u->hcpriv;
+               s = priv->start + priv->span;
                if (last < 0)
                        last = s;
                else {
@@ -727,13 +890,14 @@
        if (!ehci->periodic_sched)
                now += 8;                               /* startup delay */
        now %= mod;
-       end = now + mod;
-       if (last < 0) {
+       end = now + mod;                                /* end of periodic list */
+
+       if (last < 0) {                                 /* first urb */
                *start = now + ehci->i_thresh + /* paranoia */ 1;
-               *max = end - span;
-               if (*max < *start + 1)
+               *max = end - iso_priv->span;
+               if (*max < *start + 1)                  /* We return -EFBIG later. */
                        *max = *start + 1;
-       } else {
+       } else {                                        /* next urb */
                *start = last % mod;
                *max = (last + 1) % mod;
        }
@@ -760,18 +924,123 @@
        // FIXME minimize wraparound to "now" ... insist max+span
        // (and start+span) remains a few frames short of "end"
 
-       *max %= ehci->periodic_size;
-       if ((*start + span) < end)
+       *max %= mod;
+       if ((*start + iso_priv->span) < end)
                return 0;
+       dbg("Can't fit urb %p to ehci frame list", urb);
        return -EFBIG;
 }
 
+static inline int
+itd_fix(struct ehci_itd *itd, struct ehci_iso_stream *iso_stream, struct urb *urb,
+       int index, int bufp_index, u64 bufp)
+{ 
+       struct ehci_iso_priv *iso_priv = urb->hcpriv;
+
+       u64 next_bufp = (iso_priv->uframe[index].buf_dma + 
urb->iso_frame_desc[index].length) &
+               ~0x0fff;
+       
+       /* Last uframe may overlap to next page - if so, add it to iTD */
+       if (next_bufp != bufp) {
+               bufp = ((index + 1) < urb->number_of_packets) ? 
+                       iso_priv->uframe [index + 1].bufp : 
+                       iso_priv->last_bufp;
+               
+               if (++bufp_index <= 6) {
+                       itd->hw_bufp [bufp_index] = cpu_to_le32 ((u32) bufp);
+                       itd->hw_bufp_hi [bufp_index] = cpu_to_le32 ((u32)(bufp >> 32));
+               } else {
+                       err("Buffer page field not available - iso_frame_desc not 
valid?");
+                       return -ENOSPC;
+               }
+       }
+                                       
+       itd->hw_bufp [0] |= iso_stream->buf0;
+       itd->hw_bufp [1] |= iso_stream->buf1;
+       itd->hw_bufp [2] |= iso_stream->buf2;
+
+       return 0;
+}
+
+/**
+ * itd_link_urb - prepares all itds for one urb and links them to the
+ * schedule
+ */
+
+static void 
+itd_link_urb(struct ehci_hcd *ehci, struct urb *urb, int start, int mod, struct 
ehci_iso_stream *iso_stream)
+{
+       int                     index;
+       unsigned                uframe;
+       unsigned                usecs;
+       struct ehci_iso_priv    *iso_priv = urb->hcpriv;
+       int                     bufp_index = -1;
+       u64                     bufp = 0;
+       struct ehci_itd         *itd;
+       int                     status;
+
+       /* that's where we'll schedule this! */
+       urb->start_frame = start >> 3;
+       iso_priv->start = start;
+       vdbg ("ISO urb %p (%d pkts per %d) start %d.%d",
+             urb, urb->number_of_packets, urb->interval,
+             urb->start_frame, start & 0x7);
+       
+       itd = list_entry(iso_priv->itd_list.next, struct ehci_itd, itd_list);
+
+       /* fill in iTDs */
+       for (index = 0, uframe = start, usecs = 0;
+            index < urb->number_of_packets;
+            index++, uframe += urb->interval) {
+               struct ehci_iso_uframe *iso_uframe = &iso_priv->uframe[index];
+
+               uframe %= mod;
+
+               if (iso_uframe->bufp != bufp) {
+                       bufp_index++;
+                       bufp = iso_uframe->bufp;
+                       //xdbg("bufp[%d] = %08x", bufp_index, (u32) bufp);
+                       itd->hw_bufp [bufp_index] = cpu_to_le32 ((u32) bufp);
+                       itd->hw_bufp_hi [bufp_index] = cpu_to_le32 ((u32)(bufp >> 32));
+               }
+                       
+               iso_uframe->uframe = uframe;
+               iso_uframe->itd = itd;
+
+               iso_uframe->transaction |= (bufp_index & 0x07) << 12;
+
+               itd->hw_transaction [uframe & 0x07] = cpu_to_le32 
(iso_uframe->transaction);
+               itd->index [uframe & 0x07] = index;
+               itd->usecs += iso_uframe->usecs;
+
+               usecs += iso_uframe->usecs;
+
+               /* itd ready? Link it to schedule. */
+               if (((uframe & 0x07) + urb->interval) > 7 ||
+                   (index + 1) == urb->number_of_packets) { 
+
+                       status = itd_fix (itd, iso_stream, urb,
+                                         index, bufp_index, bufp);
+                       /* FIXME handle errors - deschedule */
+
+                       itd_link (ehci, (uframe >> 3) % ehci->periodic_size, itd);
+                       wmb ();
+
+                       itd = list_entry (itd->itd_list.next,
+                                         struct ehci_itd, itd_list);
+                       bufp_index = -1;
+                       bufp = 0;
+               }
+       }
+}
+
 static int
-itd_schedule (struct ehci_hcd *ehci, struct urb *urb)
+itd_schedule (struct ehci_hcd *ehci, struct urb *urb, struct ehci_iso_stream 
*iso_stream)
 {
-       unsigned        start, max, i;
-       int             status;
-       unsigned        mod = ehci->periodic_size << 3;
+       unsigned                start, max, i;
+       int                     status;
+       unsigned                mod = ehci->periodic_size << 3;
+       struct ehci_iso_priv    *iso_priv = urb->hcpriv;
 
        for (i = 0; i < urb->number_of_packets; i++) {
                urb->iso_frame_desc [i].status = -EINPROGRESS;
@@ -781,13 +1050,13 @@
        if ((status = get_iso_range (ehci, urb, &start, &max, mod)) != 0)
                return status;
 
+       //xdbg("get_iso_range returned start=%d, max=%d", start, max);
+
        do {
                unsigned        uframe;
-               unsigned        usecs;
-               struct ehci_itd *itd;
+               int             enough_space = 1;
 
                /* check schedule: enough space? */
-               itd = urb->hcpriv;
                uframe = start;
                for (i = 0, uframe = start;
                                i < urb->number_of_packets;
@@ -796,58 +1065,23 @@
 
                        /* can't commit more than 80% periodic == 100 usec */
                        if (periodic_usecs (ehci, uframe >> 3, uframe & 0x7)
-                                       > (100 - itd->usecs)) {
-                               itd = 0;
+                           > (100 - iso_priv->uframe[i].usecs)) {
+                               enough_space = 0;
                                break;
                        }
-                       itd = list_entry (itd->itd_list.next,
-                               struct ehci_itd, itd_list);
                }
-               if (!itd)
-                       continue;
+               if (!enough_space)
+                       continue; /* increase start by one */
                
-               /* that's where we'll schedule this! */
-               itd = urb->hcpriv;
-               urb->start_frame = start >> 3;
-               vdbg ("ISO urb %p (%d packets period %d) starting %d.%d",
-                       urb, urb->number_of_packets, urb->interval,
-                       urb->start_frame, start & 0x7);
-               for (i = 0, uframe = start, usecs = 0;
-                               i < urb->number_of_packets;
-                               i++, uframe += urb->interval) {
-                       uframe %= mod;
-
-                       itd->uframe = uframe;
-                       itd->hw_transaction [uframe & 0x07] = itd->transaction;
-                       itd_link (ehci, (uframe >> 3) % ehci->periodic_size,
-                               itd);
-                       wmb ();
-                       usecs += itd->usecs;
-
-                       itd = list_entry (itd->itd_list.next,
-                               struct ehci_itd, itd_list);
-               }
-
-               /* update bandwidth utilization records (for usbfs)
-                *
-                * FIXME This claims each URB queued to an endpoint, as if
-                * transfers were concurrent, not sequential.  So bandwidth
-                * typically gets double-billed ... comes from tying it to
-                * URBs rather than endpoints in the schedule.  Luckily we
-                * don't use this usbfs data for serious decision making.
-                */
-               usecs /= urb->number_of_packets;
-               usecs /= urb->interval;
-               usecs >>= 3;
-               if (usecs < 1)
-                       usecs = 1;
-               usb_claim_bandwidth (urb->dev, urb, usecs, 1);
+               /* Here is it OK to link itds. */
+               itd_link_urb(ehci, urb, start, mod, iso_stream);
 
                /* maybe enable periodic schedule processing */
                if (!ehci->periodic_sched++) {
                        if ((status =  enable_periodic (ehci)) != 0) {
                                // FIXME deschedule right away
                                err ("itd_schedule, enable = %d", status);
+                               return status;
                        }
                }
 
@@ -868,45 +1102,68 @@
 itd_complete (
        struct ehci_hcd *ehci,
        struct ehci_itd *itd,
-       unsigned        uframe,
        struct pt_regs  *regs
-) {
+)
+{
        struct urb                              *urb = itd->urb;
        struct usb_iso_packet_descriptor        *desc;
        u32                                     t;
+       int                                     uframe;
+       int                                     urb_index = -1;
+       unsigned long                           flags;
+
+/* #ifdef DEBUG */
+/*         static struct ehci_itd *last_itd = 0; */
+
+/*     if (itd == last_itd) { */
+/* /\*                 printk("***ERROR: Attempt to unlink same itd again."); *\/ */
+/* /\*                 while (1); *\/ */
+               
+/*             panic("Attempt to unlink same itd again."); */
+/*     } */
+/*     last_itd = itd; */
+/* #endif */
+
+       for (uframe = 0; uframe < 8; uframe++) {
+               if (itd->index[uframe] == -1) 
+                       continue; /* no active transaction */
 
-       /* update status for this uframe's transfers */
-       desc = &urb->iso_frame_desc [itd->index];
-
-       t = itd->hw_transaction [uframe];
-       itd->hw_transaction [uframe] = 0;
-       if (t & EHCI_ISOC_ACTIVE)
-               desc->status = -EXDEV;
-       else if (t & ISO_ERRS) {
-               urb->error_count++;
-               if (t & EHCI_ISOC_BUF_ERR)
-                       desc->status = usb_pipein (urb->pipe)
-                               ? -ENOSR  /* couldn't read */
-                               : -ECOMM; /* couldn't write */
-               else if (t & EHCI_ISOC_BABBLE)
-                       desc->status = -EOVERFLOW;
-               else /* (t & EHCI_ISOC_XACTERR) */
-                       desc->status = -EPROTO;
-
-               /* HC need not update length with this error */
-               if (!(t & EHCI_ISOC_BABBLE))
+               urb_index = itd->index[uframe];
+               
+               /* update status for this uframe's transfers */
+               desc = &urb->iso_frame_desc [urb_index];
+               //xdbg("itd %p, uframe %d, index %d", itd, uframe, urb_index);
+
+               t = itd->hw_transaction [uframe];
+               itd->hw_transaction [uframe] = 0;
+               if (t & EHCI_ISOC_ACTIVE)
+                       desc->status = -EXDEV;
+               else if (t & ISO_ERRS) {
+                       urb->error_count++;
+                       if (t & EHCI_ISOC_BUF_ERR)
+                               desc->status = usb_pipein (urb->pipe)
+                                       ? -ENOSR  /* couldn't read */
+                                       : -ECOMM; /* couldn't write */
+                       else if (t & EHCI_ISOC_BABBLE)
+                               desc->status = -EOVERFLOW;
+                       else /* (t & EHCI_ISOC_XACTERR) */
+                               desc->status = -EPROTO;
+
+                       /* HC need not update length with this error */
+                       if (!(t & EHCI_ISOC_BABBLE))
+                               desc->actual_length += EHCI_ITD_LENGTH (t);
+               } else {
+                       desc->status = 0;
                        desc->actual_length += EHCI_ITD_LENGTH (t);
-       } else {
-               desc->status = 0;
-               desc->actual_length += EHCI_ITD_LENGTH (t);
-       }
+               }
 
-       vdbg ("itd %p urb %p packet %d/%d trans %x status %d len %d",
-               itd, urb, itd->index + 1, urb->number_of_packets,
-               t, desc->status, desc->actual_length);
+/*             vdbg ("itd_complete: itd %p urb %p packet %d/%d trans %x status %d len 
%d", */
+/*                   itd, urb, urb_index + 1, urb->number_of_packets, */
+/*                   t, desc->status, desc->actual_length); */
+       }
 
        /* handle completion now? */
-       if ((itd->index + 1) != urb->number_of_packets)
+       if ((urb_index + 1) != urb->number_of_packets)
                return 0;
 
        /*
@@ -918,11 +1175,19 @@
         * happen according to the current schedule.  Means a delay of
         * up to about a second (max).
         */
-       itd_free_list (ehci, urb);
+
+       spin_lock_irqsave(&urb->lock, flags);
+
+       iso_priv_free (ehci, urb->hcpriv);
+       urb->hcpriv = 0;
+
        if (urb->status == -EINPROGRESS)
                urb->status = 0;
 
+       spin_unlock_irqrestore(&urb->lock, flags);
+
        /* complete() can reenter this HCD */
+/*     dbg("giveback urb %p status %d", urb, urb->status); //REMOVE ME */
        spin_unlock (&ehci->lock);
        usb_hcd_giveback_urb (&ehci->hcd, urb, regs);
        spin_lock (&ehci->lock);
@@ -937,24 +1202,71 @@
 
 /*-------------------------------------------------------------------------*/
 
-static int itd_submit (struct ehci_hcd *ehci, struct urb *urb, int mem_flags)
+static struct ehci_iso_stream *iso_stream_get(struct urb *urb, int mem_flags)
 {
-       int             status;
-       unsigned long   flags;
+       unsigned                epnum;
+       struct hcd_dev          *dev;
+       struct ehci_iso_stream  *iso_stream;
 
-       dbg ("itd_submit urb %p", urb);
+       epnum = usb_pipeendpoint (urb->pipe);
+       dev = (struct hcd_dev *)urb->dev->hcpriv;
 
-       /* allocate ITDs w/o locking anything */
-       status = itd_urb_transaction (ehci, urb, mem_flags);
-       if (status < 0)
-               return status;
+       iso_stream = dev->ep[epnum];
+       
+       if (unlikely (iso_stream == 0)) {
+               iso_stream = iso_stream_alloc(mem_flags);
+
+               if (unlikely (iso_stream == 0))
+                       goto done;
+
+               dev->ep[epnum] = iso_stream;
+               
+               iso_stream_fill(iso_stream, urb);
+       }
+
+done:
+       return iso_stream;
+}
+
+static int itd_submit (struct ehci_hcd *ehci, struct urb *urb, int mem_flags)
+{
+       int                     status = 0;
+       unsigned long           flags;
+       struct ehci_iso_stream  *iso_stream;
+       
+       dbg ("itd_submit urb %p", urb);
 
-       /* schedule ... need to lock */
        spin_lock_irqsave (&ehci->lock, flags);
-       status = itd_schedule (ehci, urb);
+
+       /* Get previously allocated or allocate new iso_stream struct */
+       iso_stream = iso_stream_get(urb, GFP_ATOMIC);
+       if (iso_stream == 0) {
+               dbg("Can't get iso_stream");
+               goto done;
+       }
+
+       /* allocate iTDs */
+       status = itd_urb_transaction (iso_stream, ehci, urb, GFP_ATOMIC);
+       if (status < 0) {
+               dbg("Can't prepare itds");
+               iso_priv_free (ehci, urb->hcpriv);
+               goto done;
+       }
+
+       /* schedule iTDs */
+       status = itd_schedule (ehci, urb, iso_stream);
+
+       if (status < 0) {
+               iso_priv_free (ehci, urb->hcpriv);
+               urb->hcpriv = 0;
+       }
+       
+       /* FIXME: Do propper bandwidth allocation. */
+       //hcd_to_bus (&ehci->hcd)->bandwidth_isoc_reqs++;
+
+done:
        spin_unlock_irqrestore (&ehci->lock, flags);
-       if (status < 0)
-               itd_free_list (ehci, urb);
+
 
        return status;
 }
@@ -974,12 +1286,34 @@
 
 /*-------------------------------------------------------------------------*/
 
+/*
+ * scan_periodic - scans periodic schedule and removes processed
+ * items. Calls urb's completion routines when needed.
+ *
+ * NOTE: This function should be reentrant. Although we have ehci
+ * lock, we drop it when completing urbs.
+ *
+ * FIXME: Is this note right???
+ * 
+ */
+
 static void
 scan_periodic (struct ehci_hcd *ehci, struct pt_regs *regs)
 {
        unsigned        frame, clock, now_uframe, mod;
        unsigned        count = 0;
 
+       static int test = 0;    /* FIXME: remove this test */
+       
+       if (test++ > 0)
+               info("scan_periodic rentered!");
+
+       /*
+        * now_uframe - actual ehci uframe position
+        * frame - frame, where last scan was finished
+        * clock - frame corresponding to now_uframe
+        */
+       
        mod = ehci->periodic_size << 3;
 
        /*
@@ -992,10 +1326,13 @@
        if (HCD_IS_RUNNING (ehci->hcd.state))
                now_uframe = readl (&ehci->regs->frame_index);
        else
-               now_uframe = (frame << 3) - 1;
+               now_uframe = (frame << 3) + mod - 1;
        now_uframe %= mod;
        clock = now_uframe >> 3;
 
+/*     xdbg("scan_periodic: now_uf=%d.%d fr=%d cl=%d int=%lx", */
+/*          now_uframe >> 3, now_uframe & 7, frame, clock, in_interrupt()); */
+
        for (;;) {
                union ehci_shadow       q, *q_p;
                u32                     type, *hw_p;
@@ -1008,11 +1345,16 @@
                else
                        uframes = 8;
 
+/*             xdbg("scan  restart: now_uf=%d.%d fr=%d cl=%d in_int=%lx", */
+/*                  now_uframe >> 3, now_uframe & 7, frame, clock, in_interrupt()); */
+
                q_p = &ehci->pshadow [frame];
                hw_p = &ehci->periodic [frame];
                q.ptr = q_p->ptr;
                type = Q_NEXT_TYPE (*hw_p);
 
+               //xdbg("scanning frame %d", frame);
+
                /* scan each element in frame's queue for completions */
                while (q.ptr != 0) {
                        int                     last;
@@ -1043,32 +1385,42 @@
                        case Q_TYPE_ITD:
                                last = (q.itd->hw_next == EHCI_LIST_END);
 
-                               /* Unlink each (S)ITD we see, since the ISO
-                                * URB model forces constant rescheduling.
-                                * That complicates sharing uframes in ITDs,
-                                * and means we need to skip uframes the HC
-                                * hasn't yet processed.
-                                */
-                               for (uf = 0; uf < uframes; uf++) {
-                                       if (q.itd->hw_transaction [uf] != 0) {
-                                               temp = q;
-                                               *q_p = q.itd->itd_next;
-                                               *hw_p = q.itd->hw_next;
-                                               type = Q_NEXT_TYPE (*hw_p);
-
-                                               /* might free q.itd ... */
-                                               count += itd_complete (ehci,
-                                                       temp.itd, uf, regs);
-                                               break;
-                                       }
+                               //xdbg("found itd %p at frame %d", q.itd, frame);
+
+                               /* can we unlink q.itd? */
+                               for (uf = uframes; uf < 8; uf++) { /* from now to end 
of whole frame */
+                                       if (q.itd->index[uf] != -1) 
+                                               break; /* no, we can't */
                                }
-                               /* we might skip this ITD's uframe ... */
-                               if (uf == uframes) {
+
+                               if (uf == 8) {
+                                       /* All itd's transactions are
+                                        * in the past. */
+                                       temp = q;
+                                       
+                                       *q_p = q.itd->itd_next;
+                                       *hw_p = q.itd->hw_next;
+
+                                       //xdbg("unlink itd %p, next %p, fr %d", q.itd, 
q_p->ptr, frame);
+
+                                       type = Q_NEXT_TYPE (*hw_p);
+
+                                       /* completion may reschedule
+                                        * itd and it needs valid
+                                        * next_uframe */
+
+                                       /* TODO: maybe not ;-) */
+                                       //ehci->next_uframe = now_uframe;       
+
+                                       /* might free q.itd ... */
+                                       count += itd_complete (ehci, temp.itd, regs);
+                               } else {
+                                       //xdbg("not unlink itd %p, fr %d", q.itd, 
frame);
                                        q_p = &q.itd->itd_next;
                                        hw_p = &q.itd->hw_next;
                                        type = Q_NEXT_TYPE (q.itd->hw_next);
                                }
-
+                               
                                q = *q_p;
                                break;
 #ifdef have_split_iso
@@ -1090,8 +1442,9 @@
                        }
 
                        /* did completion remove an interior q entry? */
-                       if (unlikely (q.ptr == 0 && !last))
+                       if (unlikely (q.ptr == 0 && !last)) {
                                goto restart;
+                       }
                }
 
                /* stop when we catch up to the HC */
@@ -1110,14 +1463,35 @@
                        if (!HCD_IS_RUNNING (ehci->hcd.state))
                                break;
                        ehci->next_uframe = now_uframe;
+
+                       break;  /* Don't restart scanning. On slow
+                                * computer it may last too long and
+                                * we hold ehci lock. */
+
                        now = readl (&ehci->regs->frame_index) % mod;
-                       if (now_uframe == now)
+                       if (now_uframe == now) {
+                               //xdbg("scan_periodic finished - frame %d.%d", now >> 
3, now & 7);
                                break;
+                       }
 
                        /* rescan the rest of this frame, then ... */
                        now_uframe = now;
                        clock = now_uframe >> 3;
-               } else
+               } else {
                        frame = (frame + 1) % ehci->periodic_size;
+
+                       /* We need valid now_uframe when scanning iTDs. */
+
+                       /* TODO: Really???? */
+                       //now_uframe = readl (&ehci->regs->frame_index) % mod;
+               }
        } 
+       test--;
 }
+
+/*
+Local Variables:
+compile-command:"make -C ../../.. V=1 SUBDIRS=drivers/usb/host modules 
modules_install"
+End:
+
+*/

Reply via email to