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:
+
+*/
