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

[PATCH] USB: ehci-hcd updates

This should apply to 2.5.59 too.  It seems to get rid of some pesky
hangs, on at least some hardware, but I won't have time to test it
on either VIA version ... maybe someone else will make the time?  :)

      New QH state prevents a re-activation race
	- nobody can un-halt a qh before its cleanup is done
	- resubmit-from-completion had this race (some usbtest cases)
	  as could some normal submit paths on busy endpoints (storage)
	- faster controllers would trip on this more consistently

      Queues of qtds
	- work harder to avoid ever modifing any qh in software
	- short reads block queue advance much less often
	- be more cautious with large (>~19KB) unaligned buffers

      Unlinking urbs
	- if qtd unlinked is at queue head, use its latest status
	  (main effect is reporting bytes from partial transfers)
	- another new qh state:  defer qh unlink if IAA is busy
	  (eliminates a busy-wait loop in a rare scenario)

      Enable features to improve bus utilization
	- PCI MWI ... can produce better write throughput; and by
	  using right cacheline size, sometimes read throughput too
	- USB NAK throttle ... sometimes reduces PCI access rates

      Other
	- async dump shows more funky qh+qtd states, and NAK count
	- cope with with some of the sprintf wierdness
	- periodic dump is usually smaller (so is that schedule)
      	- minor cleanups
parent 9b224d53
...@@ -277,7 +277,26 @@ static inline void remove_debug_files (struct ehci_hcd *bus) { } ...@@ -277,7 +277,26 @@ static inline void remove_debug_files (struct ehci_hcd *bus) { }
default: tmp = '?'; break; \ default: tmp = '?'; break; \
}; tmp; }) }; tmp; })
static void qh_lines (struct ehci_qh *qh, char **nextp, unsigned *sizep) static inline char token_mark (u32 token)
{
token = le32_to_cpu (token);
if (token & QTD_STS_ACTIVE)
return '*';
if (token & QTD_STS_HALT)
return '-';
if (QTD_PID (token) != 1 /* not IN: OUT or SETUP */
|| QTD_LENGTH (token) == 0)
return ' ';
/* tries to advance through hw_alt_next */
return '/';
}
static void qh_lines (
struct ehci_hcd *ehci,
struct ehci_qh *qh,
char **nextp,
unsigned *sizep
)
{ {
u32 scratch; u32 scratch;
u32 hw_curr; u32 hw_curr;
...@@ -286,26 +305,49 @@ static void qh_lines (struct ehci_qh *qh, char **nextp, unsigned *sizep) ...@@ -286,26 +305,49 @@ static void qh_lines (struct ehci_qh *qh, char **nextp, unsigned *sizep)
unsigned temp; unsigned temp;
unsigned size = *sizep; unsigned size = *sizep;
char *next = *nextp; char *next = *nextp;
char mark;
mark = token_mark (qh->hw_token);
if (mark == '/') { /* qh_alt_next controls qh advance? */
if ((qh->hw_alt_next & QTD_MASK) == ehci->async->hw_alt_next)
mark = '#'; /* blocked */
else if (qh->hw_alt_next & cpu_to_le32 (0x01))
mark = '.'; /* use hw_qtd_next */
/* else alt_next points to some other qtd */
}
scratch = cpu_to_le32p (&qh->hw_info1); scratch = cpu_to_le32p (&qh->hw_info1);
hw_curr = cpu_to_le32p (&qh->hw_current); hw_curr = (mark == '*') ? cpu_to_le32p (&qh->hw_current) : 0;
temp = snprintf (next, size, temp = snprintf (next, size,
"qh/%p dev%d %cs ep%d %08x %08x (%08x %08x)", "qh/%p dev%d %cs ep%d %08x %08x (%08x%c %s nak%d)",
qh, scratch & 0x007f, qh, scratch & 0x007f,
speed_char (scratch), speed_char (scratch),
(scratch >> 8) & 0x000f, (scratch >> 8) & 0x000f,
scratch, cpu_to_le32p (&qh->hw_info2), scratch, cpu_to_le32p (&qh->hw_info2),
hw_curr, cpu_to_le32p (&qh->hw_token)); cpu_to_le32p (&qh->hw_token), mark,
(cpu_to_le32 (0x8000000) & qh->hw_token)
? "data0" : "data1",
(cpu_to_le32p (&qh->hw_alt_next) >> 1) & 0x0f);
size -= temp; size -= temp;
next += temp; next += temp;
/* hc may be modifying the list as we read it ... */
list_for_each (entry, &qh->qtd_list) { list_for_each (entry, &qh->qtd_list) {
td = list_entry (entry, struct ehci_qtd, qtd_list); td = list_entry (entry, struct ehci_qtd, qtd_list);
scratch = cpu_to_le32p (&td->hw_token); scratch = cpu_to_le32p (&td->hw_token);
mark = ' ';
if (hw_curr == td->qtd_dma)
mark = '*';
else if (qh->hw_qtd_next == td->qtd_dma)
mark = '+';
else if (QTD_LENGTH (scratch)) {
if (td->hw_alt_next == ehci->async->hw_alt_next)
mark = '#';
else if (td->hw_alt_next != EHCI_LIST_END)
mark = '/';
}
temp = snprintf (next, size, temp = snprintf (next, size,
"\n\t%std/%p %s len=%d %08x urb %p", "\n\t%p%c%s len=%d %08x urb %p",
(hw_curr == td->qtd_dma) ? "*" : "", td, mark, ({ char *tmp;
td, ({ char *tmp;
switch ((scratch>>8)&0x03) { switch ((scratch>>8)&0x03) {
case 0: tmp = "out"; break; case 0: tmp = "out"; break;
case 1: tmp = "in"; break; case 1: tmp = "in"; break;
...@@ -315,13 +357,27 @@ static void qh_lines (struct ehci_qh *qh, char **nextp, unsigned *sizep) ...@@ -315,13 +357,27 @@ static void qh_lines (struct ehci_qh *qh, char **nextp, unsigned *sizep)
(scratch >> 16) & 0x7fff, (scratch >> 16) & 0x7fff,
scratch, scratch,
td->urb); td->urb);
if (temp < 0)
temp = 0;
else if (size < temp)
temp = size;
size -= temp; size -= temp;
next += temp; next += temp;
if (temp == size)
goto done;
} }
temp = snprintf (next, size, "\n"); temp = snprintf (next, size, "\n");
*sizep = size - temp; if (temp < 0)
*nextp = next + temp; temp = 0;
else if (size < temp)
temp = size;
size -= temp;
next += temp;
done:
*sizep = size;
*nextp = next;
} }
static ssize_t static ssize_t
...@@ -344,14 +400,15 @@ show_async (struct device *dev, char *buf) ...@@ -344,14 +400,15 @@ show_async (struct device *dev, char *buf)
* one QH per line, and TDs we know about * one QH per line, and TDs we know about
*/ */
spin_lock_irqsave (&ehci->lock, flags); spin_lock_irqsave (&ehci->lock, flags);
for (qh = ehci->async->qh_next.qh; qh; qh = qh->qh_next.qh) for (qh = ehci->async->qh_next.qh; size > 0 && qh; qh = qh->qh_next.qh)
qh_lines (qh, &next, &size); qh_lines (ehci, qh, &next, &size);
if (ehci->reclaim) { if (ehci->reclaim && size > 0) {
temp = snprintf (next, size, "\nreclaim =\n"); temp = snprintf (next, size, "\nreclaim =\n");
size -= temp; size -= temp;
next += temp; next += temp;
qh_lines (ehci->reclaim, &next, &size); for (qh = ehci->reclaim; size > 0 && qh; qh = qh->reclaim)
qh_lines (ehci, qh, &next, &size);
} }
spin_unlock_irqrestore (&ehci->lock, flags); spin_unlock_irqrestore (&ehci->lock, flags);
...@@ -421,7 +478,7 @@ show_periodic (struct device *dev, char *buf) ...@@ -421,7 +478,7 @@ show_periodic (struct device *dev, char *buf)
scratch & 0x007f, scratch & 0x007f,
(scratch >> 8) & 0x000f, (scratch >> 8) & 0x000f,
p.qh->usecs, p.qh->c_usecs, p.qh->usecs, p.qh->c_usecs,
scratch >> 16); 0x7ff & (scratch >> 16));
/* FIXME TD info too */ /* FIXME TD info too */
...@@ -490,7 +547,8 @@ show_registers (struct device *dev, char *buf) ...@@ -490,7 +547,8 @@ show_registers (struct device *dev, char *buf)
/* Capability Registers */ /* Capability Registers */
i = readw (&ehci->caps->hci_version); i = readw (&ehci->caps->hci_version);
temp = snprintf (next, size, "EHCI %x.%02x, hcd state %d\n", temp = snprintf (next, size,
"EHCI %x.%02x, hcd state %d (version " DRIVER_VERSION ")\n",
i >> 8, i & 0x0ff, ehci->hcd.state); i >> 8, i & 0x0ff, ehci->hcd.state);
size -= temp; size -= temp;
next += temp; next += temp;
......
...@@ -94,7 +94,7 @@ ...@@ -94,7 +94,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-Nov-29" #define DRIVER_VERSION "2003-Jan-22"
#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"
...@@ -110,10 +110,11 @@ static const char hcd_name [] = "ehci-hcd"; ...@@ -110,10 +110,11 @@ static const char hcd_name [] = "ehci-hcd";
/* magic numbers that can affect system performance */ /* magic numbers that can affect system performance */
#define EHCI_TUNE_CERR 3 /* 0-3 qtd retries; 0 == don't stop */ #define EHCI_TUNE_CERR 3 /* 0-3 qtd retries; 0 == don't stop */
#define EHCI_TUNE_RL_HS 0 /* nak throttle; see 4.9 */ #define EHCI_TUNE_RL_HS 4 /* nak throttle; see 4.9 */
#define EHCI_TUNE_RL_TT 0 #define EHCI_TUNE_RL_TT 0
#define EHCI_TUNE_MULT_HS 1 /* 1-3 transactions/uframe; 4.10.3 */ #define EHCI_TUNE_MULT_HS 1 /* 1-3 transactions/uframe; 4.10.3 */
#define EHCI_TUNE_MULT_TT 1 #define EHCI_TUNE_MULT_TT 1
#define EHCI_TUNE_FLS 2 /* (small) 256 frame schedule */
#define EHCI_WATCHDOG_JIFFIES (HZ/100) /* arbitrary; ~10 msec */ #define EHCI_WATCHDOG_JIFFIES (HZ/100) /* arbitrary; ~10 msec */
#define EHCI_ASYNC_JIFFIES (HZ/20) /* async idle timeout */ #define EHCI_ASYNC_JIFFIES (HZ/20) /* async idle timeout */
...@@ -416,13 +417,26 @@ static int ehci_start (struct usb_hcd *hcd) ...@@ -416,13 +417,26 @@ static int ehci_start (struct usb_hcd *hcd)
ehci_info (ehci, "enabled 64bit PCI DMA\n"); ehci_info (ehci, "enabled 64bit PCI DMA\n");
} }
/* help hc dma work well with cachelines */
pci_set_mwi (ehci->hcd.pdev);
/* clear interrupt enables, set irq latency */ /* clear interrupt enables, set irq latency */
temp = readl (&ehci->regs->command) & 0xff; temp = readl (&ehci->regs->command) & 0xff;
if (log2_irq_thresh < 0 || log2_irq_thresh > 6) if (log2_irq_thresh < 0 || log2_irq_thresh > 6)
log2_irq_thresh = 0; log2_irq_thresh = 0;
temp |= 1 << (16 + log2_irq_thresh); temp |= 1 << (16 + log2_irq_thresh);
// if hc can park (ehci >= 0.96), default is 3 packets per async QH // if hc can park (ehci >= 0.96), default is 3 packets per async QH
// keeping default periodic framelist size if (HCC_PGM_FRAMELISTLEN (hcc_params)) {
/* periodic schedule size can be smaller than default */
temp &= ~(3 << 2);
temp |= (EHCI_TUNE_FLS << 2);
switch (EHCI_TUNE_FLS) {
case 0: ehci->periodic_size = 1024; break;
case 1: ehci->periodic_size = 512; break;
case 2: ehci->periodic_size = 256; break;
default: BUG ();
}
}
temp &= ~(CMD_IAAD | CMD_ASE | CMD_PSE), temp &= ~(CMD_IAAD | CMD_ASE | CMD_PSE),
// Philips, Intel, and maybe others need CMD_RUN before the // Philips, Intel, and maybe others need CMD_RUN before the
// root hub will detect new devices (why?); NEC doesn't // root hub will detect new devices (why?); NEC doesn't
...@@ -759,7 +773,6 @@ static int ehci_urb_dequeue (struct usb_hcd *hcd, struct urb *urb) ...@@ -759,7 +773,6 @@ static int ehci_urb_dequeue (struct usb_hcd *hcd, struct urb *urb)
struct ehci_hcd *ehci = hcd_to_ehci (hcd); struct ehci_hcd *ehci = hcd_to_ehci (hcd);
struct ehci_qh *qh; struct ehci_qh *qh;
unsigned long flags; unsigned long flags;
int maybe_irq = 1;
spin_lock_irqsave (&ehci->lock, flags); spin_lock_irqsave (&ehci->lock, flags);
switch (usb_pipetype (urb->pipe)) { switch (usb_pipetype (urb->pipe)) {
...@@ -769,23 +782,23 @@ static int ehci_urb_dequeue (struct usb_hcd *hcd, struct urb *urb) ...@@ -769,23 +782,23 @@ static int ehci_urb_dequeue (struct usb_hcd *hcd, struct urb *urb)
qh = (struct ehci_qh *) urb->hcpriv; qh = (struct ehci_qh *) urb->hcpriv;
if (!qh) if (!qh)
break; break;
while (qh->qh_state == QH_STATE_LINKED
/* if we need to use IAA and it's busy, defer */
if (qh->qh_state == QH_STATE_LINKED
&& ehci->reclaim && ehci->reclaim
&& HCD_IS_RUNNING (ehci->hcd.state) && HCD_IS_RUNNING (ehci->hcd.state)
) { ) {
spin_unlock_irqrestore (&ehci->lock, flags); struct ehci_qh *last;
if (maybe_irq) { for (last = ehci->reclaim;
if (in_interrupt ()) last->reclaim;
return -EAGAIN; last = last->reclaim)
maybe_irq = 0; continue;
} qh->qh_state = QH_STATE_UNLINK_WAIT;
/* let pending unlinks complete, so this can start */ last->reclaim = qh;
wait_ms (1);
spin_lock_irqsave (&ehci->lock, flags); /* bypass IAA if the hc can't care */
} } else if (!HCD_IS_RUNNING (ehci->hcd.state) && ehci->reclaim)
if (!HCD_IS_RUNNING (ehci->hcd.state) && ehci->reclaim)
end_unlink_async (ehci, NULL); end_unlink_async (ehci, NULL);
/* something else might have unlinked the qh by now */ /* something else might have unlinked the qh by now */
......
...@@ -75,8 +75,6 @@ static struct ehci_qtd *ehci_qtd_alloc (struct ehci_hcd *ehci, int flags) ...@@ -75,8 +75,6 @@ static struct ehci_qtd *ehci_qtd_alloc (struct ehci_hcd *ehci, int flags)
qtd = pci_pool_alloc (ehci->qtd_pool, flags, &dma); qtd = pci_pool_alloc (ehci->qtd_pool, flags, &dma);
if (qtd != 0) { if (qtd != 0) {
ehci_qtd_init (qtd, dma); ehci_qtd_init (qtd, dma);
if (ehci->async)
qtd->hw_alt_next = ehci->async->hw_alt_next;
} }
return qtd; return qtd;
} }
......
This diff is collapsed.
...@@ -236,12 +236,12 @@ struct ehci_qtd { ...@@ -236,12 +236,12 @@ struct ehci_qtd {
/* the rest is HCD-private */ /* the rest is HCD-private */
dma_addr_t qtd_dma; /* qtd address */ dma_addr_t qtd_dma; /* qtd address */
struct list_head qtd_list; /* sw qtd list */ struct list_head qtd_list; /* sw qtd list */
/* dma same in urb's qtds, except 1st control qtd (setup buffer) */
struct urb *urb; /* qtd's urb */ struct urb *urb; /* qtd's urb */
size_t length; /* length of buffer */ size_t length; /* length of buffer */
} __attribute__ ((aligned (32))); } __attribute__ ((aligned (32)));
#define QTD_MASK cpu_to_le32 (~0x1f) /* mask NakCnt+T in qh->hw_alt_next */
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
/* type tag from {qh,itd,sitd,fstn}->hw_next */ /* type tag from {qh,itd,sitd,fstn}->hw_next */
...@@ -305,6 +305,7 @@ struct ehci_qh { ...@@ -305,6 +305,7 @@ struct ehci_qh {
union ehci_shadow qh_next; /* ptr to qh; or periodic */ union ehci_shadow qh_next; /* ptr to qh; or periodic */
struct list_head qtd_list; /* sw qtd list */ struct list_head qtd_list; /* sw qtd list */
struct ehci_qtd *dummy; struct ehci_qtd *dummy;
struct ehci_qh *reclaim; /* next to reclaim */
atomic_t refcount; atomic_t refcount;
unsigned stamp; unsigned stamp;
...@@ -313,6 +314,8 @@ struct ehci_qh { ...@@ -313,6 +314,8 @@ struct ehci_qh {
#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 */
#define QH_STATE_UNLINK_WAIT 4 /* LINKED and on reclaim q */
#define QH_STATE_COMPLETING 5 /* don't touch token.HALT */
/* periodic schedule info */ /* periodic schedule info */
u8 usecs; /* intr bandwidth */ u8 usecs; /* intr bandwidth */
......
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