Skip to content

Commit

Permalink
---
Browse files Browse the repository at this point in the history
yaml
---
r: 317024
b: refs/heads/master
c: 3144661
h: refs/heads/master
v: v3
  • Loading branch information
Alan Stern authored and Greg Kroah-Hartman committed Jul 16, 2012
1 parent f38674a commit 95d41f8
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 50 deletions.
2 changes: 1 addition & 1 deletion [refs]
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
---
refs/heads/master: 9671cd7a91059bcd27665a884ee6568d31ef6857
refs/heads/master: 314466101c6ae14f6f5db8a86eda1509ba2c02a8
13 changes: 3 additions & 10 deletions trunk/drivers/usb/host/ehci-hcd.c
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,6 @@ static const char hcd_name [] = "ehci_hcd";

#define EHCI_IAA_MSECS 10 /* arbitrary */
#define EHCI_IO_JIFFIES (HZ/10) /* io watchdog > irq_thresh */
#define EHCI_ASYNC_JIFFIES (HZ/20) /* async idle timeout */
#define EHCI_SHRINK_JIFFIES (DIV_ROUND_UP(HZ, 200) + 1)
/* 5-ms async qh unlink delay */

Expand Down Expand Up @@ -137,7 +136,7 @@ timer_action(struct ehci_hcd *ehci, enum ehci_timer_action action)
* SHRINK were pending, OFF would never be requested.
*/
if (timer_pending(&ehci->watchdog)
&& ((BIT(TIMER_ASYNC_SHRINK) | BIT(TIMER_ASYNC_OFF))
&& (BIT(TIMER_ASYNC_SHRINK)
& ehci->actions))
return;

Expand All @@ -150,9 +149,6 @@ timer_action(struct ehci_hcd *ehci, enum ehci_timer_action action)
return;
t = EHCI_IO_JIFFIES;
break;
case TIMER_ASYNC_OFF:
t = EHCI_ASYNC_JIFFIES;
break;
/* case TIMER_ASYNC_SHRINK: */
default:
t = EHCI_SHRINK_JIFFIES;
Expand Down Expand Up @@ -376,10 +372,6 @@ static void ehci_watchdog(unsigned long param)

spin_lock_irqsave(&ehci->lock, flags);

/* stop async processing after it's idled a bit */
if (test_bit (TIMER_ASYNC_OFF, &ehci->actions))
start_unlink_async (ehci, ehci->async);

/* ehci could run by timer, without IRQs ... */
ehci_work (ehci);

Expand Down Expand Up @@ -470,7 +462,8 @@ static void ehci_work (struct ehci_hcd *ehci)
if (ehci->scanning)
return;
ehci->scanning = 1;
scan_async (ehci);
if (ehci->async_count)
scan_async(ehci);
if (ehci->next_uframe != -1)
scan_periodic (ehci);
ehci->scanning = 0;
Expand Down
68 changes: 30 additions & 38 deletions trunk/drivers/usb/host/ehci-q.c
Original file line number Diff line number Diff line change
Expand Up @@ -964,6 +964,30 @@ qh_make (

/*-------------------------------------------------------------------------*/

static void enable_async(struct ehci_hcd *ehci)
{
if (ehci->async_count++)
return;

/* Stop waiting to turn off the async schedule */
ehci->enabled_hrtimer_events &= ~BIT(EHCI_HRTIMER_DISABLE_ASYNC);

/* Don't start the schedule until ASS is 0 */
ehci_poll_ASS(ehci);
}

static void disable_async(struct ehci_hcd *ehci)
{
if (--ehci->async_count)
return;

/* The async schedule and async_unlink list are supposed to be empty */
WARN_ON(ehci->async->qh_next.qh || ehci->async_unlink);

/* Don't turn off the schedule until ASS is 1 */
ehci_poll_ASS(ehci);
}

/* move qh (and its qtds) onto async queue; maybe enable queue. */

static void qh_link_async (struct ehci_hcd *ehci, struct ehci_qh *qh)
Expand All @@ -977,24 +1001,11 @@ static void qh_link_async (struct ehci_hcd *ehci, struct ehci_qh *qh)

WARN_ON(qh->qh_state != QH_STATE_IDLE);

/* (re)start the async schedule? */
head = ehci->async;
timer_action_done (ehci, TIMER_ASYNC_OFF);
if (!head->qh_next.qh) {
if (!(ehci->command & CMD_ASE)) {
/* in case a clear of CMD_ASE didn't take yet */
(void)handshake(ehci, &ehci->regs->status,
STS_ASS, 0, 150);
ehci->command |= CMD_ASE;
ehci_writel(ehci, ehci->command, &ehci->regs->command);
/* posted write need not be known to HC yet ... */
}
}

/* clear halt and/or toggle; and maybe recover from silicon quirk */
qh_refresh(ehci, qh);

/* splice right after start */
head = ehci->async;
qh->qh_next = head->qh_next;
qh->hw->hw_next = head->hw->hw_next;
wmb ();
Expand All @@ -1005,6 +1016,8 @@ static void qh_link_async (struct ehci_hcd *ehci, struct ehci_qh *qh)
qh->xacterrs = 0;
qh->qh_state = QH_STATE_LINKED;
/* qtd completions reported later by interrupt */

enable_async(ehci);
}

/*-------------------------------------------------------------------------*/
Expand Down Expand Up @@ -1173,16 +1186,10 @@ static void end_unlink_async (struct ehci_hcd *ehci)

qh_completions (ehci, qh);

if (!list_empty(&qh->qtd_list) && ehci->rh_state == EHCI_RH_RUNNING) {
if (!list_empty(&qh->qtd_list) && ehci->rh_state == EHCI_RH_RUNNING)
qh_link_async (ehci, qh);
} else {
/* it's not free to turn the async schedule on/off; leave it
* active but idle for a while once it empties.
*/
if (ehci->rh_state == EHCI_RH_RUNNING
&& ehci->async->qh_next.qh == NULL)
timer_action (ehci, TIMER_ASYNC_OFF);
}

disable_async(ehci);

if (next) {
ehci->async_unlink = NULL;
Expand Down Expand Up @@ -1210,21 +1217,6 @@ static void start_unlink_async (struct ehci_hcd *ehci, struct ehci_qh *qh)
BUG ();
#endif

/* stop async schedule right now? */
if (unlikely (qh == ehci->async)) {
/* can't get here without STS_ASS set */
if (ehci->rh_state != EHCI_RH_HALTED
&& !ehci->async_unlink) {
/* ... and CMD_IAAD clear */
ehci->command &= ~CMD_ASE;
ehci_writel(ehci, ehci->command, &ehci->regs->command);
wmb ();
// handshake later, if we need to
timer_action_done (ehci, TIMER_ASYNC_OFF);
}
return;
}

qh->qh_state = QH_STATE_UNLINK;
ehci->async_unlink = qh;
if (!qh->unlink_next)
Expand Down
49 changes: 49 additions & 0 deletions trunk/drivers/usb/host/ehci-timer.c
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,10 @@ static void ehci_clear_command_bit(struct ehci_hcd *ehci, u32 bit)
* the event types indexed by enum ehci_hrtimer_event in ehci.h.
*/
static unsigned event_delays_ns[] = {
1 * NSEC_PER_MSEC, /* EHCI_HRTIMER_POLL_ASS */
1 * NSEC_PER_MSEC, /* EHCI_HRTIMER_POLL_PSS */
10 * NSEC_PER_MSEC, /* EHCI_HRTIMER_DISABLE_PERIODIC */
15 * NSEC_PER_MSEC, /* EHCI_HRTIMER_DISABLE_ASYNC */
};

/* Enable a pending hrtimer event */
Expand All @@ -91,6 +93,51 @@ static void ehci_enable_event(struct ehci_hcd *ehci, unsigned event,
}


/* Poll the STS_ASS status bit; see when it agrees with CMD_ASE */
static void ehci_poll_ASS(struct ehci_hcd *ehci)
{
unsigned actual, want;

/* Don't enable anything if the controller isn't running (e.g., died) */
if (ehci->rh_state != EHCI_RH_RUNNING)
return;

want = (ehci->command & CMD_ASE) ? STS_ASS : 0;
actual = ehci_readl(ehci, &ehci->regs->status) & STS_ASS;

if (want != actual) {

/* Poll again later, but give up after about 20 ms */
if (ehci->ASS_poll_count++ < 20) {
ehci_enable_event(ehci, EHCI_HRTIMER_POLL_ASS, true);
return;
}
ehci_warn(ehci, "Waited too long for the async schedule status, giving up\n");
}
ehci->ASS_poll_count = 0;

/* The status is up-to-date; restart or stop the schedule as needed */
if (want == 0) { /* Stopped */
if (ehci->async_count > 0)
ehci_set_command_bit(ehci, CMD_ASE);

} else { /* Running */
if (ehci->async_count == 0) {

/* Turn off the schedule after a while */
ehci_enable_event(ehci, EHCI_HRTIMER_DISABLE_ASYNC,
true);
}
}
}

/* Turn off the async schedule after a brief delay */
static void ehci_disable_ASE(struct ehci_hcd *ehci)
{
ehci_clear_command_bit(ehci, CMD_ASE);
}


/* Poll the STS_PSS status bit; see when it agrees with CMD_PSE */
static void ehci_poll_PSS(struct ehci_hcd *ehci)
{
Expand Down Expand Up @@ -151,8 +198,10 @@ static void ehci_disable_PSE(struct ehci_hcd *ehci)
* enum ehci_hrtimer_event in ehci.h.
*/
static void (*event_handlers[])(struct ehci_hcd *) = {
ehci_poll_ASS, /* EHCI_HRTIMER_POLL_ASS */
ehci_poll_PSS, /* EHCI_HRTIMER_POLL_PSS */
ehci_disable_PSE, /* EHCI_HRTIMER_DISABLE_PERIODIC */
ehci_disable_ASE, /* EHCI_HRTIMER_DISABLE_ASYNC */
};

static enum hrtimer_restart ehci_hrtimer_func(struct hrtimer *t)
Expand Down
5 changes: 4 additions & 1 deletion trunk/drivers/usb/host/ehci.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,10 @@ enum ehci_rh_state {
* ehci-timer.c) in parallel with this list.
*/
enum ehci_hrtimer_event {
EHCI_HRTIMER_POLL_ASS, /* Poll for async schedule off */
EHCI_HRTIMER_POLL_PSS, /* Poll for periodic schedule off */
EHCI_HRTIMER_DISABLE_PERIODIC, /* Wait to disable periodic sched */
EHCI_HRTIMER_DISABLE_ASYNC, /* Wait to disable async sched */
EHCI_HRTIMER_NUM_EVENTS /* Must come last */
};
#define EHCI_HRTIMER_NO_EVENT 99
Expand All @@ -93,6 +95,7 @@ struct ehci_hcd { /* one per controller */
struct hrtimer hrtimer;

int PSS_poll_count;
int ASS_poll_count;

/* glue to PCI and HCD framework */
struct ehci_caps __iomem *caps;
Expand All @@ -110,6 +113,7 @@ struct ehci_hcd { /* one per controller */
struct ehci_qh *async_unlink_last;
struct ehci_qh *qh_scan_next;
unsigned scanning : 1;
unsigned async_count; /* async activity count */

/* periodic schedule support */
#define DEFAULT_I_TDPS 1024 /* some HCs can do less */
Expand Down Expand Up @@ -229,7 +233,6 @@ static inline void iaa_watchdog_done(struct ehci_hcd *ehci)
enum ehci_timer_action {
TIMER_IO_WATCHDOG,
TIMER_ASYNC_SHRINK,
TIMER_ASYNC_OFF,
};

static inline void
Expand Down

0 comments on commit 95d41f8

Please sign in to comment.