Commit 9148b58e authored by David Brownell's avatar David Brownell Committed by Greg Kroah-Hartman

[PATCH] ehci split interrupt transactions

This patch lets more devices hook up to USB 2.0 hubs, stuff
like keyboards, mice, hubs that hasn't worked yet:

- schedules full/low speed interrupt transactions
- tracks CSPLIT bandwidth for full/low speed interrupt
  transactions
- moves some bus bandwidth calculation out of the EHCI code
- makes the bandwidth calculation primitive public, and
  adds kerneldoc for it

It still takes a scheduling shortcut, placing at most one
interrupt transaction in a frame (vs potentially over 100),
but it should do for now.
parent 71a15636
...@@ -745,12 +745,18 @@ EXPORT_SYMBOL (usb_register_root_hub); ...@@ -745,12 +745,18 @@ EXPORT_SYMBOL (usb_register_root_hub);
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
/* /**
* usb_calc_bus_time: * usb_calc_bus_time: approximate periodic transaction time in nanoseconds
* @speed: from dev->speed; USB_SPEED_{LOW,FULL,HIGH}
* @is_input: true iff the transaction sends data to the host
* @is_isoc: true for isochronous transactions, false for interrupt ones
* @bytecount: how many bytes in the transaction.
*
* Returns approximate bus time in nanoseconds for a periodic transaction. * Returns approximate bus time in nanoseconds for a periodic transaction.
* See USB 2.0 spec section 5.11.3 * See USB 2.0 spec section 5.11.3; only periodic transfers need to be
* scheduled in software, this function is only used for such scheduling.
*/ */
static long usb_calc_bus_time (int speed, int is_input, int isoc, int bytecount) long usb_calc_bus_time (int speed, int is_input, int isoc, int bytecount)
{ {
unsigned long tmp; unsigned long tmp;
...@@ -772,14 +778,18 @@ static long usb_calc_bus_time (int speed, int is_input, int isoc, int bytecount) ...@@ -772,14 +778,18 @@ static long usb_calc_bus_time (int speed, int is_input, int isoc, int bytecount)
return (9107L + BW_HOST_DELAY + tmp); return (9107L + BW_HOST_DELAY + tmp);
} }
case USB_SPEED_HIGH: /* ISOC or INTR */ case USB_SPEED_HIGH: /* ISOC or INTR */
// FIXME merge from EHCI code; caller will need to handle // FIXME adjust for input vs output
// each part of a split separately. if (isoc)
return 0; tmp = HS_USECS (bytecount);
else
tmp = HS_USECS_ISO (bytecount);
return tmp;
default: default:
dbg ("bogus device speed!"); dbg ("bogus device speed!");
return -1; return -1;
} }
} }
EXPORT_SYMBOL (usb_calc_bus_time);
/* /*
* usb_check_bandwidth(): * usb_check_bandwidth():
......
...@@ -263,6 +263,22 @@ extern void usb_release_bandwidth (struct usb_device *dev, struct urb *urb, ...@@ -263,6 +263,22 @@ extern void usb_release_bandwidth (struct usb_device *dev, struct urb *urb,
extern int usb_check_bandwidth (struct usb_device *dev, struct urb *urb); extern int usb_check_bandwidth (struct usb_device *dev, struct urb *urb);
/*
* Ceiling microseconds (typical) for that many bytes at high speed
* ISO is a bit less, no ACK ... from USB 2.0 spec, 5.11.3 (and needed
* to preallocate bandwidth)
*/
#define USB2_HOST_DELAY 5 /* nsec, guess */
#define HS_USECS(bytes) NS_TO_US ( ((55 * 8 * 2083)/1000) \
+ ((2083UL * (3167 + BitTime (bytes)))/1000) \
+ USB2_HOST_DELAY)
#define HS_USECS_ISO(bytes) NS_TO_US ( ((long)(38 * 8 * 2.083)) \
+ ((2083UL * (3167 + BitTime (bytes)))/1000) \
+ USB2_HOST_DELAY)
extern long usb_calc_bus_time (int speed, int is_input,
int isoc, int bytecount);
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
extern struct usb_bus *usb_alloc_bus (struct usb_operations *); extern struct usb_bus *usb_alloc_bus (struct usb_operations *);
......
...@@ -102,8 +102,8 @@ static inline void dbg_hcc_params (struct ehci_hcd *ehci, char *label) {} ...@@ -102,8 +102,8 @@ static inline void dbg_hcc_params (struct ehci_hcd *ehci, char *label) {}
#ifdef DEBUG #ifdef DEBUG
#if 0 static void __attribute__((__unused__))
static void dbg_qh (char *label, struct ehci_hcd *ehci, struct ehci_qh *qh) dbg_qh (char *label, struct ehci_hcd *ehci, struct ehci_qh *qh)
{ {
dbg ("%s %p info1 %x info2 %x hw_curr %x qtd_next %x", label, dbg ("%s %p info1 %x info2 %x hw_curr %x qtd_next %x", label,
qh, qh->hw_info1, qh->hw_info2, qh, qh->hw_info1, qh->hw_info2,
...@@ -117,15 +117,13 @@ static void dbg_qh (char *label, struct ehci_hcd *ehci, struct ehci_qh *qh) ...@@ -117,15 +117,13 @@ static void dbg_qh (char *label, struct ehci_hcd *ehci, struct ehci_qh *qh)
qh->hw_buf [4]); qh->hw_buf [4]);
} }
} }
#endif
static const char *const fls_strings [] = static const char *const fls_strings [] =
{ "1024", "512", "256", "??" }; { "1024", "512", "256", "??" };
#else #else
#if 0 static inline void __attribute__((__unused__))
static inline void dbg_qh (char *label, struct ehci_hcd *ehci, struct ehci_qh *qh) {} dbg_qh (char *label, struct ehci_hcd *ehci, struct ehci_qh *qh) {}
#endif
#endif /* DEBUG */ #endif /* DEBUG */
/* functions have the "wrong" filename when they're output... */ /* functions have the "wrong" filename when they're output... */
......
...@@ -65,6 +65,7 @@ ...@@ -65,6 +65,7 @@
* *
* HISTORY: * HISTORY:
* *
* 2002-05-24 Preliminary FS/LS interrupts, using scheduling shortcuts
* 2002-05-11 Clear TT errors for FS/LS ctrl/bulk. Fill in some other * 2002-05-11 Clear TT errors for FS/LS ctrl/bulk. Fill in some other
* missing pieces: enabling 64bit dma, handoff from BIOS/SMM. * missing pieces: enabling 64bit dma, handoff from BIOS/SMM.
* 2002-05-07 Some error path cleanups to report better errors; wmb(); * 2002-05-07 Some error path cleanups to report better errors; wmb();
...@@ -82,7 +83,7 @@ ...@@ -82,7 +83,7 @@
* 2001-June Works with usb-storage and NEC EHCI on 2.4 * 2001-June Works with usb-storage and NEC EHCI on 2.4
*/ */
#define DRIVER_VERSION "2002-May-11" #define DRIVER_VERSION "2002-May-24"
#define DRIVER_AUTHOR "David Brownell" #define DRIVER_AUTHOR "David Brownell"
#define DRIVER_DESC "USB 2.0 'Enhanced' Host Controller (EHCI) Driver" #define DRIVER_DESC "USB 2.0 'Enhanced' Host Controller (EHCI) Driver"
...@@ -622,7 +623,10 @@ dbg ("wait for dequeue: state %d, reclaim %p, hcd state %d", ...@@ -622,7 +623,10 @@ dbg ("wait for dequeue: state %d, reclaim %p, hcd state %d",
return 0; return 0;
case PIPE_INTERRUPT: case PIPE_INTERRUPT:
intr_deschedule (ehci, urb->start_frame, qh, urb->interval); intr_deschedule (ehci, urb->start_frame, qh,
(urb->dev->speed == USB_SPEED_HIGH)
? urb->interval
: (urb->interval << 3));
if (ehci->hcd.state == USB_STATE_HALT) if (ehci->hcd.state == USB_STATE_HALT)
urb->status = -ESHUTDOWN; urb->status = -ESHUTDOWN;
qh_completions (ehci, qh, 1); qh_completions (ehci, qh, 1);
......
...@@ -33,19 +33,6 @@ ...@@ -33,19 +33,6 @@
* or with "USB On The Go" additions to USB 2.0 ...) * or with "USB On The Go" additions to USB 2.0 ...)
*/ */
/*
* Ceiling microseconds (typical) for that many bytes at high speed
* ISO is a bit less, no ACK ... from USB 2.0 spec, 5.11.3 (and needed
* to preallocate bandwidth)
*/
#define EHCI_HOST_DELAY 5 /* nsec, guess */
#define HS_USECS(bytes) NS_TO_US ( ((55 * 8 * 2083)/1000) \
+ ((2083UL * (3167 + BitTime (bytes)))/1000) \
+ EHCI_HOST_DELAY)
#define HS_USECS_ISO(bytes) NS_TO_US ( ((long)(38 * 8 * 2.083)) \
+ ((2083UL * (3167 + BitTime (bytes)))/1000) \
+ EHCI_HOST_DELAY)
static int ehci_get_frame (struct usb_hcd *hcd); static int ehci_get_frame (struct usb_hcd *hcd);
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
...@@ -124,6 +111,9 @@ periodic_usecs (struct ehci_hcd *ehci, unsigned frame, unsigned uframe) ...@@ -124,6 +111,9 @@ periodic_usecs (struct ehci_hcd *ehci, unsigned frame, unsigned uframe)
/* is it in the S-mask? */ /* is it in the S-mask? */
if (q->qh->hw_info2 & cpu_to_le32 (1 << uframe)) if (q->qh->hw_info2 & cpu_to_le32 (1 << uframe))
usecs += q->qh->usecs; usecs += q->qh->usecs;
/* ... or C-mask? */
if (q->qh->hw_info2 & cpu_to_le32 (1 << (8 + uframe)))
usecs += q->qh->c_usecs;
q = &q->qh->qh_next; q = &q->qh->qh_next;
break; break;
case Q_TYPE_FSTN: case Q_TYPE_FSTN:
...@@ -273,6 +263,12 @@ static int check_period ( ...@@ -273,6 +263,12 @@ static int check_period (
unsigned period, unsigned period,
unsigned usecs unsigned usecs
) { ) {
/* complete split running into next frame?
* given FSTN support, we could sometimes check...
*/
if (uframe >= 8)
return 0;
/* /*
* 80% periodic == 100 usec/uframe available * 80% periodic == 100 usec/uframe available
* convert "usecs we need" to "max already claimed" * convert "usecs we need" to "max already claimed"
...@@ -284,6 +280,8 @@ static int check_period ( ...@@ -284,6 +280,8 @@ static int check_period (
// FIXME delete when intr_submit handles non-empty queues // FIXME delete when intr_submit handles non-empty queues
// this gives us a one intr/frame limit (vs N/uframe) // this gives us a one intr/frame limit (vs N/uframe)
// ... and also lets us avoid tracking split transactions
// that might collide at a given TT/hub.
if (ehci->pshadow [frame].ptr) if (ehci->pshadow [frame].ptr)
return 0; return 0;
...@@ -305,20 +303,54 @@ static int intr_submit ( ...@@ -305,20 +303,54 @@ static int intr_submit (
int mem_flags int mem_flags
) { ) {
unsigned epnum, period; unsigned epnum, period;
unsigned short usecs; unsigned short usecs, c_usecs, gap_uf;
unsigned long flags; unsigned long flags;
struct ehci_qh *qh; struct ehci_qh *qh;
struct hcd_dev *dev; struct hcd_dev *dev;
int is_input;
int status = 0; int status = 0;
/* get endpoint and transfer data */ /* get endpoint and transfer/schedule data */
epnum = usb_pipeendpoint (urb->pipe); epnum = usb_pipeendpoint (urb->pipe);
if (usb_pipein (urb->pipe)) is_input = usb_pipein (urb->pipe);
if (is_input)
epnum |= 0x10; epnum |= 0x10;
if (urb->dev->speed != USB_SPEED_HIGH) {
dbg ("no intr/tt scheduling yet"); /*
status = -ENOSYS; * HS interrupt transfers are simple -- only one microframe. FS/LS
goto done; * interrupt transfers involve a SPLIT in one microframe and CSPLIT
* sometime later. We need to know how much time each will be
* needed in each microframe and, for FS/LS, how many microframes
* separate the two in the best case.
*/
usecs = usb_calc_bus_time (USB_SPEED_HIGH, is_input, 0,
urb->transfer_buffer_length);
if (urb->dev->speed == USB_SPEED_HIGH) {
gap_uf = 0;
c_usecs = 0;
/* FIXME handle HS periods of less than 1 frame. */
period = urb->interval >> 3;
if (period < 1) {
dbg ("intr period %d uframes, NYET!", urb->interval);
status = -EINVAL;
goto done;
}
} else {
/* gap is a function of full/low speed transfer times */
gap_uf = 1 + usb_calc_bus_time (urb->dev->speed, is_input, 0,
urb->transfer_buffer_length) / (125 * 1000);
/* FIXME this just approximates SPLIT/CSPLIT times */
if (is_input) { // SPLIT, gap, CSPLIT+DATA
c_usecs = usecs + HS_USECS (0);
usecs = HS_USECS (1);
} else { // SPLIT+DATA, gap, CSPLIT
usecs = usecs + HS_USECS (1);
c_usecs = HS_USECS (0);
}
period = urb->interval;
} }
/* /*
...@@ -339,16 +371,6 @@ static int intr_submit ( ...@@ -339,16 +371,6 @@ static int intr_submit (
goto done; goto done;
} }
usecs = HS_USECS (urb->transfer_buffer_length);
/* FIXME handle HS periods of less than 1 frame. */
period = urb->interval >> 3;
if (period < 1) {
dbg ("intr period %d uframes, NYET!", urb->interval);
status = -EINVAL;
goto done;
}
spin_lock_irqsave (&ehci->lock, flags); spin_lock_irqsave (&ehci->lock, flags);
/* get the qh (must be empty and idle) */ /* get the qh (must be empty and idle) */
...@@ -392,6 +414,7 @@ static int intr_submit ( ...@@ -392,6 +414,7 @@ static int intr_submit (
qh->hw_next = EHCI_LIST_END; qh->hw_next = EHCI_LIST_END;
qh->usecs = usecs; qh->usecs = usecs;
qh->c_usecs = c_usecs;
urb->hcpriv = qh_get (qh); urb->hcpriv = qh_get (qh);
status = -ENOSPC; status = -ENOSPC;
...@@ -399,18 +422,47 @@ static int intr_submit ( ...@@ -399,18 +422,47 @@ static int intr_submit (
/* pick a set of schedule slots, link the QH into them */ /* pick a set of schedule slots, link the QH into them */
do { do {
int uframe; int uframe;
u32 c_mask = 0;
/* pick a set of slots such that all uframes have /* pick a set of slots such that all uframes have
* enough periodic bandwidth available. * enough periodic bandwidth available.
*
* FIXME for TT splits, need uframes for start and end.
* FSTNs can put end into next frame (uframes 0 or 1).
*/ */
frame--; frame--;
for (uframe = 0; uframe < 8; uframe++) { for (uframe = 0; uframe < 8; uframe++) {
if (check_period (ehci, frame, uframe, if (check_period (ehci, frame, uframe,
period, usecs) != 0) period, usecs) == 0)
break; continue;
/* If this is a split transaction, check the
* bandwidth available for the completion
* too. check both best and worst case gaps:
* worst case is SPLIT near uframe end, and
* CSPLIT near start ... best is vice versa.
* Difference can be almost two uframe times.
*
* FIXME don't even bother unless we know
* this TT is idle in that uframe ... right
* now we know only one interrupt transfer
* will be scheduled per frame, so we don't
* need to update/check TT state when we
* schedule a split (QH, SITD, or FSTN).
*
* FIXME ehci 0.96 and above can use FSTNs
*/
if (!c_usecs)
break;
if (check_period (ehci, frame,
uframe + gap_uf,
period, c_usecs) == 0)
continue;
if (check_period (ehci, frame,
uframe + gap_uf + 1,
period, c_usecs) == 0)
continue;
c_mask = 0x03 << (8 + uframe + gap_uf);
c_mask = cpu_to_le32 (c_mask);
break;
} }
if (uframe == 8) if (uframe == 8)
continue; continue;
...@@ -419,13 +471,14 @@ static int intr_submit ( ...@@ -419,13 +471,14 @@ static int intr_submit (
urb->start_frame = frame; urb->start_frame = frame;
status = 0; status = 0;
/* set S-frame mask */ /* reset S-frame and (maybe) C-frame masks */
qh->hw_info2 |= cpu_to_le32 (1 << uframe); qh->hw_info2 &= ~0xffff;
qh->hw_info2 |= cpu_to_le32 (1 << uframe) | c_mask;
// dbg_qh ("Schedule INTR qh", ehci, qh); // dbg_qh ("Schedule INTR qh", ehci, qh);
/* stuff into the periodic schedule */ /* stuff into the periodic schedule */
qh->qh_state = QH_STATE_LINKED; qh->qh_state = QH_STATE_LINKED;
vdbg ("qh %p usecs %d period %d starting %d.%d", vdbg ("qh %p usecs %d period %d.0 starting %d.%d",
qh, qh->usecs, period, frame, uframe); qh, qh->usecs, period, frame, uframe);
do { do {
if (unlikely (ehci->pshadow [frame].ptr != 0)) { if (unlikely (ehci->pshadow [frame].ptr != 0)) {
...@@ -443,7 +496,8 @@ static int intr_submit ( ...@@ -443,7 +496,8 @@ static int intr_submit (
} while (frame < ehci->periodic_size); } while (frame < ehci->periodic_size);
/* update bandwidth utilization records (for usbfs) */ /* update bandwidth utilization records (for usbfs) */
usb_claim_bandwidth (urb->dev, urb, usecs/period, 0); usb_claim_bandwidth (urb->dev, urb,
(usecs + c_usecs) / period, 0);
/* maybe enable periodic schedule processing */ /* maybe enable periodic schedule processing */
if (!ehci->periodic_urbs++) if (!ehci->periodic_urbs++)
...@@ -557,6 +611,7 @@ itd_fill ( ...@@ -557,6 +611,7 @@ itd_fill (
u32 buf1; u32 buf1;
unsigned i, epnum, maxp, multi; unsigned i, epnum, maxp, multi;
unsigned length; unsigned length;
int is_input;
itd->hw_next = EHCI_LIST_END; itd->hw_next = EHCI_LIST_END;
itd->urb = urb; itd->urb = urb;
...@@ -578,7 +633,8 @@ itd_fill ( ...@@ -578,7 +633,8 @@ itd_fill (
* as encoded in the ep descriptor's maxpacket field * as encoded in the ep descriptor's maxpacket field
*/ */
epnum = usb_pipeendpoint (urb->pipe); epnum = usb_pipeendpoint (urb->pipe);
if (usb_pipein (urb->pipe)) { is_input = usb_pipein (urb->pipe);
if (is_input) {
maxp = urb->dev->epmaxpacketin [epnum]; maxp = urb->dev->epmaxpacketin [epnum];
buf1 = (1 << 11); buf1 = (1 << 11);
} else { } else {
...@@ -598,7 +654,7 @@ itd_fill ( ...@@ -598,7 +654,7 @@ itd_fill (
urb->iso_frame_desc [index].length); urb->iso_frame_desc [index].length);
return -ENOSPC; return -ENOSPC;
} }
itd->usecs = HS_USECS_ISO (length); itd->usecs = usb_calc_bus_time (USB_SPEED_HIGH, is_input, 1, length);
/* "plus" info in low order bits of buffer pointers */ /* "plus" info in low order bits of buffer pointers */
itd->hw_bufp [0] |= cpu_to_le32 ((epnum << 8) | urb->dev->devnum); itd->hw_bufp [0] |= cpu_to_le32 ((epnum << 8) | urb->dev->devnum);
...@@ -919,17 +975,9 @@ itd_complete ( ...@@ -919,17 +975,9 @@ itd_complete (
return flags; return flags;
/* /*
* For now, always give the urb back to the driver ... expect it * Always give the urb back to the driver ... expect it to submit
* to submit a new urb (or resubmit this), and to have another * a new urb (or resubmit this), and to have another already queued
* already queued when un-interrupted transfers are needed. * when un-interrupted transfers are needed.
* No, that's not what OHCI or UHCI are now doing.
*
* FIXME Revisit the ISO URB model. It's cleaner not to have all
* the special case magic, but it'd be faster to reuse existing
* ITD/DMA setup and schedule state. Easy to dma_sync/complete(),
* then either reschedule or, if unlinking, free and giveback().
* But we can't overcommit like the full and low speed HCs do, and
* there's no clean way to report an error when rescheduling...
* *
* NOTE that for now we don't accelerate ISO unlinks; they just * NOTE that for now we don't accelerate ISO unlinks; they just
* happen according to the current schedule. Means a delay of * happen according to the current schedule. Means a delay of
......
...@@ -288,14 +288,11 @@ struct ehci_qh { ...@@ -288,14 +288,11 @@ struct ehci_qh {
atomic_t refcount; atomic_t refcount;
unsigned short usecs; /* intr bandwidth */ unsigned short usecs; /* intr bandwidth */
unsigned short c_usecs; /* ... split completion bw */
short qh_state; short qh_state;
#define QH_STATE_LINKED 1 /* HC sees this */ #define QH_STATE_LINKED 1 /* HC sees this */
#define QH_STATE_UNLINK 2 /* HC may still see this */ #define QH_STATE_UNLINK 2 /* HC may still see this */
#define QH_STATE_IDLE 3 /* HC doesn't see this */ #define QH_STATE_IDLE 3 /* HC doesn't see this */
#ifdef EHCI_SOFT_RETRIES
int retries;
#endif
} __attribute__ ((aligned (32))); } __attribute__ ((aligned (32)));
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
...@@ -360,6 +357,9 @@ struct ehci_sitd { ...@@ -360,6 +357,9 @@ struct ehci_sitd {
union ehci_shadow sitd_next; /* ptr to periodic q entry */ union ehci_shadow sitd_next; /* ptr to periodic q entry */
struct urb *urb; struct urb *urb;
dma_addr_t buf_dma; /* buffer address */ dma_addr_t buf_dma; /* buffer address */
unsigned short usecs; /* start bandwidth */
unsigned short c_usecs; /* completion bandwidth */
} __attribute__ ((aligned (32))); } __attribute__ ((aligned (32)));
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment