Commit c5e3b741 authored by Alan Stern's avatar Alan Stern Committed by Greg Kroah-Hartman

[PATCH] UHCI: Improve FSBR-off timing

This patch (as707) improves the FSBR operation in uhci-hcd by turning it
off more quickly when it isn't needed.  FSBR puts a noticeable load on a
computer's PCI bus, so it should be disabled as soon as possible when it
isn't in use.  The patch leaves it running for only 10 ms after the last
URB stops using it, on the theory that this should be long enough for a
driver to submit another URB if it wants keep FSBR going.
Signed-off-by: default avatarAlan Stern <stern@rowland.harvard.edu>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent e323de46
...@@ -497,9 +497,9 @@ static int uhci_start(struct usb_hcd *hcd) ...@@ -497,9 +497,9 @@ static int uhci_start(struct usb_hcd *hcd)
hcd->uses_new_polling = 1; hcd->uses_new_polling = 1;
spin_lock_init(&uhci->lock); spin_lock_init(&uhci->lock);
setup_timer(&uhci->fsbr_timer, uhci_fsbr_timeout,
(unsigned long) uhci);
INIT_LIST_HEAD(&uhci->idle_qh_list); INIT_LIST_HEAD(&uhci->idle_qh_list);
init_waitqueue_head(&uhci->waitqh); init_waitqueue_head(&uhci->waitqh);
if (DEBUG_CONFIGURED) { if (DEBUG_CONFIGURED) {
...@@ -675,6 +675,7 @@ static void uhci_stop(struct usb_hcd *hcd) ...@@ -675,6 +675,7 @@ static void uhci_stop(struct usb_hcd *hcd)
uhci_scan_schedule(uhci, NULL); uhci_scan_schedule(uhci, NULL);
spin_unlock_irq(&uhci->lock); spin_unlock_irq(&uhci->lock);
del_timer_sync(&uhci->fsbr_timer);
release_uhci(uhci); release_uhci(uhci);
} }
......
...@@ -86,7 +86,7 @@ ...@@ -86,7 +86,7 @@
/* When no queues need Full-Speed Bandwidth Reclamation, /* When no queues need Full-Speed Bandwidth Reclamation,
* delay this long before turning FSBR off */ * delay this long before turning FSBR off */
#define FSBR_OFF_DELAY msecs_to_jiffies(400) #define FSBR_OFF_DELAY msecs_to_jiffies(10)
/* If a queue hasn't advanced after this much time, assume it is stuck */ /* If a queue hasn't advanced after this much time, assume it is stuck */
#define QH_WAIT_TIMEOUT msecs_to_jiffies(200) #define QH_WAIT_TIMEOUT msecs_to_jiffies(200)
...@@ -382,8 +382,6 @@ struct uhci_hcd { ...@@ -382,8 +382,6 @@ struct uhci_hcd {
__le32 *frame; __le32 *frame;
void **frame_cpu; /* CPU's frame list */ void **frame_cpu; /* CPU's frame list */
unsigned long fsbr_jiffies; /* Time when FSBR was last wanted */
enum uhci_rh_state rh_state; enum uhci_rh_state rh_state;
unsigned long auto_stop_time; /* When to AUTO_STOP */ unsigned long auto_stop_time; /* When to AUTO_STOP */
...@@ -400,6 +398,10 @@ struct uhci_hcd { ...@@ -400,6 +398,10 @@ struct uhci_hcd {
need to be polled */ need to be polled */
unsigned int is_initialized:1; /* Data structure is usable */ unsigned int is_initialized:1; /* Data structure is usable */
unsigned int fsbr_is_on:1; /* FSBR is turned on */ unsigned int fsbr_is_on:1; /* FSBR is turned on */
unsigned int fsbr_is_wanted:1; /* Does any URB want FSBR? */
unsigned int fsbr_expiring:1; /* FSBR is timing out */
struct timer_list fsbr_timer; /* For turning off FBSR */
/* Support for port suspend/resume/reset */ /* Support for port suspend/resume/reset */
unsigned long port_c_suspend; /* Bit-arrays of ports */ unsigned long port_c_suspend; /* Bit-arrays of ports */
......
...@@ -64,18 +64,32 @@ static void uhci_add_fsbr(struct uhci_hcd *uhci, struct urb *urb) ...@@ -64,18 +64,32 @@ static void uhci_add_fsbr(struct uhci_hcd *uhci, struct urb *urb)
urbp->fsbr = 1; urbp->fsbr = 1;
} }
static void uhci_qh_wants_fsbr(struct uhci_hcd *uhci, struct uhci_qh *qh) static void uhci_urbp_wants_fsbr(struct uhci_hcd *uhci, struct urb_priv *urbp)
{ {
struct urb_priv *urbp =
list_entry(qh->queue.next, struct urb_priv, node);
if (urbp->fsbr) { if (urbp->fsbr) {
uhci->fsbr_jiffies = jiffies; uhci->fsbr_is_wanted = 1;
if (!uhci->fsbr_is_on) if (!uhci->fsbr_is_on)
uhci_fsbr_on(uhci); uhci_fsbr_on(uhci);
else if (uhci->fsbr_expiring) {
uhci->fsbr_expiring = 0;
del_timer(&uhci->fsbr_timer);
}
} }
} }
static void uhci_fsbr_timeout(unsigned long _uhci)
{
struct uhci_hcd *uhci = (struct uhci_hcd *) _uhci;
unsigned long flags;
spin_lock_irqsave(&uhci->lock, flags);
if (uhci->fsbr_expiring) {
uhci->fsbr_expiring = 0;
uhci_fsbr_off(uhci);
}
spin_unlock_irqrestore(&uhci->lock, flags);
}
static struct uhci_td *uhci_alloc_td(struct uhci_hcd *uhci) static struct uhci_td *uhci_alloc_td(struct uhci_hcd *uhci)
{ {
...@@ -287,7 +301,7 @@ static int uhci_cleanup_queue(struct uhci_hcd *uhci, struct uhci_qh *qh, ...@@ -287,7 +301,7 @@ static int uhci_cleanup_queue(struct uhci_hcd *uhci, struct uhci_qh *qh,
if (qh->type == USB_ENDPOINT_XFER_ISOC) { if (qh->type == USB_ENDPOINT_XFER_ISOC) {
ret = (uhci->frame_number + uhci->is_stopped != ret = (uhci->frame_number + uhci->is_stopped !=
qh->unlink_frame); qh->unlink_frame);
return ret; goto done;
} }
/* If the URB isn't first on its queue, adjust the link pointer /* If the URB isn't first on its queue, adjust the link pointer
...@@ -304,24 +318,26 @@ static int uhci_cleanup_queue(struct uhci_hcd *uhci, struct uhci_qh *qh, ...@@ -304,24 +318,26 @@ static int uhci_cleanup_queue(struct uhci_hcd *uhci, struct uhci_qh *qh,
td = list_entry(urbp->td_list.prev, struct uhci_td, td = list_entry(urbp->td_list.prev, struct uhci_td,
list); list);
ptd->link = td->link; ptd->link = td->link;
return ret; goto done;
} }
/* If the QH element pointer is UHCI_PTR_TERM then then currently /* If the QH element pointer is UHCI_PTR_TERM then then currently
* executing URB has already been unlinked, so this one isn't it. */ * executing URB has already been unlinked, so this one isn't it. */
if (qh_element(qh) == UHCI_PTR_TERM) if (qh_element(qh) == UHCI_PTR_TERM)
return ret; goto done;
qh->element = UHCI_PTR_TERM; qh->element = UHCI_PTR_TERM;
/* Control pipes have to worry about toggles */ /* Control pipes have to worry about toggles */
if (qh->type == USB_ENDPOINT_XFER_CONTROL) if (qh->type == USB_ENDPOINT_XFER_CONTROL)
return ret; goto done;
/* Save the next toggle value */ /* Save the next toggle value */
WARN_ON(list_empty(&urbp->td_list)); WARN_ON(list_empty(&urbp->td_list));
td = list_entry(urbp->td_list.next, struct uhci_td, list); td = list_entry(urbp->td_list.next, struct uhci_td, list);
qh->needs_fixup = 1; qh->needs_fixup = 1;
qh->initial_toggle = uhci_toggle(td_token(td)); qh->initial_toggle = uhci_toggle(td_token(td));
done:
return ret; return ret;
} }
...@@ -1175,7 +1191,7 @@ static int uhci_urb_enqueue(struct usb_hcd *hcd, ...@@ -1175,7 +1191,7 @@ static int uhci_urb_enqueue(struct usb_hcd *hcd,
* queue isn't stopped. */ * queue isn't stopped. */
if (qh->queue.next == &urbp->node && !qh->is_stopped) { if (qh->queue.next == &urbp->node && !qh->is_stopped) {
uhci_activate_qh(uhci, qh); uhci_activate_qh(uhci, qh);
uhci_qh_wants_fsbr(uhci, qh); uhci_urbp_wants_fsbr(uhci, urbp);
} }
goto done; goto done;
...@@ -1404,7 +1420,7 @@ static int uhci_advance_check(struct uhci_hcd *uhci, struct uhci_qh *qh) ...@@ -1404,7 +1420,7 @@ static int uhci_advance_check(struct uhci_hcd *uhci, struct uhci_qh *qh)
unsigned status; unsigned status;
if (qh->type == USB_ENDPOINT_XFER_ISOC) if (qh->type == USB_ENDPOINT_XFER_ISOC)
return ret; goto done;
/* Treat an UNLINKING queue as though it hasn't advanced. /* Treat an UNLINKING queue as though it hasn't advanced.
* This is okay because reactivation will treat it as though * This is okay because reactivation will treat it as though
...@@ -1427,21 +1443,24 @@ static int uhci_advance_check(struct uhci_hcd *uhci, struct uhci_qh *qh) ...@@ -1427,21 +1443,24 @@ static int uhci_advance_check(struct uhci_hcd *uhci, struct uhci_qh *qh)
/* We're okay, the queue has advanced */ /* We're okay, the queue has advanced */
qh->wait_expired = 0; qh->wait_expired = 0;
qh->advance_jiffies = jiffies; qh->advance_jiffies = jiffies;
return ret; goto done;
} }
ret = 0; ret = 0;
} }
/* The queue hasn't advanced; check for timeout */ /* The queue hasn't advanced; check for timeout */
if (!qh->wait_expired && time_after(jiffies, if (qh->wait_expired)
qh->advance_jiffies + QH_WAIT_TIMEOUT)) { goto done;
if (time_after(jiffies, qh->advance_jiffies + QH_WAIT_TIMEOUT)) {
/* Detect the Intel bug and work around it */ /* Detect the Intel bug and work around it */
if (qh->post_td && qh_element(qh) == if (qh->post_td && qh_element(qh) ==
cpu_to_le32(qh->post_td->dma_handle)) { cpu_to_le32(qh->post_td->dma_handle)) {
qh->element = qh->post_td->link; qh->element = qh->post_td->link;
qh->advance_jiffies = jiffies; qh->advance_jiffies = jiffies;
return 1; ret = 1;
goto done;
} }
qh->wait_expired = 1; qh->wait_expired = 1;
...@@ -1452,7 +1471,14 @@ static int uhci_advance_check(struct uhci_hcd *uhci, struct uhci_qh *qh) ...@@ -1452,7 +1471,14 @@ static int uhci_advance_check(struct uhci_hcd *uhci, struct uhci_qh *qh)
* starts moving again. */ * starts moving again. */
if (urbp && urbp->fsbr && !(status & TD_CTRL_IOC)) if (urbp && urbp->fsbr && !(status & TD_CTRL_IOC))
uhci_unlink_qh(uhci, qh); uhci_unlink_qh(uhci, qh);
} else {
/* Unmoving but not-yet-expired queues keep FSBR alive */
if (urbp)
uhci_urbp_wants_fsbr(uhci, urbp);
} }
done:
return ret; return ret;
} }
...@@ -1472,6 +1498,7 @@ static void uhci_scan_schedule(struct uhci_hcd *uhci, struct pt_regs *regs) ...@@ -1472,6 +1498,7 @@ static void uhci_scan_schedule(struct uhci_hcd *uhci, struct pt_regs *regs)
uhci->scan_in_progress = 1; uhci->scan_in_progress = 1;
rescan: rescan:
uhci->need_rescan = 0; uhci->need_rescan = 0;
uhci->fsbr_is_wanted = 0;
uhci_clear_next_interrupt(uhci); uhci_clear_next_interrupt(uhci);
uhci_get_current_frame_number(uhci); uhci_get_current_frame_number(uhci);
...@@ -1487,8 +1514,10 @@ static void uhci_scan_schedule(struct uhci_hcd *uhci, struct pt_regs *regs) ...@@ -1487,8 +1514,10 @@ static void uhci_scan_schedule(struct uhci_hcd *uhci, struct pt_regs *regs)
if (uhci_advance_check(uhci, qh)) { if (uhci_advance_check(uhci, qh)) {
uhci_scan_qh(uhci, qh, regs); uhci_scan_qh(uhci, qh, regs);
if (qh->state == QH_STATE_ACTIVE) if (qh->state == QH_STATE_ACTIVE) {
uhci_qh_wants_fsbr(uhci, qh); uhci_urbp_wants_fsbr(uhci,
list_entry(qh->queue.next, struct urb_priv, node));
}
} }
} }
} }
...@@ -1498,9 +1527,11 @@ static void uhci_scan_schedule(struct uhci_hcd *uhci, struct pt_regs *regs) ...@@ -1498,9 +1527,11 @@ static void uhci_scan_schedule(struct uhci_hcd *uhci, struct pt_regs *regs)
goto rescan; goto rescan;
uhci->scan_in_progress = 0; uhci->scan_in_progress = 0;
if (uhci->fsbr_is_on && time_after(jiffies, if (uhci->fsbr_is_on && !uhci->fsbr_is_wanted &&
uhci->fsbr_jiffies + FSBR_OFF_DELAY)) !uhci->fsbr_expiring) {
uhci_fsbr_off(uhci); uhci->fsbr_expiring = 1;
mod_timer(&uhci->fsbr_timer, jiffies + FSBR_OFF_DELAY);
}
if (list_empty(&uhci->skel_unlink_qh->node)) if (list_empty(&uhci->skel_unlink_qh->node))
uhci_clear_next_interrupt(uhci); uhci_clear_next_interrupt(uhci);
......
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