From 84afddd7ac58adad00cb0e50d0af25fcf825668b Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Fri, 12 May 2006 11:35:45 -0400 Subject: [PATCH] UHCI: Reimplement FSBR This patch (as683) re-implements Full-Speed Bandwidth Reclamation (FSBR) properly. It keeps track of which endpoint queues have advanced, and when none have advanced for a sufficiently long time, FSBR is turned off. The next TD on each of the non-moving queues is modified to generate an interrupt on completion, so that FSBR can be re-enabled as soon as the hardware starts to make some progress. Signed-off-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/uhci-debug.c | 3 +- drivers/usb/host/uhci-hcd.c | 13 +--- drivers/usb/host/uhci-hcd.h | 15 +++- drivers/usb/host/uhci-hub.c | 1 - drivers/usb/host/uhci-q.c | 168 ++++++++++++++++++++++++++++++++---------- 5 files changed, 143 insertions(+), 57 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/host/uhci-debug.c b/drivers/usb/host/uhci-debug.c index 6bbd33d..081c592 100644 --- a/drivers/usb/host/uhci-debug.c +++ b/drivers/usb/host/uhci-debug.c @@ -274,7 +274,8 @@ static int uhci_show_root_hub_state(struct uhci_hcd *uhci, char *buf, int len) default: rh_state = "?"; break; } - out += sprintf(out, "Root-hub state: %s\n", rh_state); + out += sprintf(out, "Root-hub state: %s FSBR: %d\n", + rh_state, uhci->fsbr_is_on); return out - buf; } diff --git a/drivers/usb/host/uhci-hcd.c b/drivers/usb/host/uhci-hcd.c index fb4c1a8..395402e 100644 --- a/drivers/usb/host/uhci-hcd.c +++ b/drivers/usb/host/uhci-hcd.c @@ -88,15 +88,6 @@ static void suspend_rh(struct uhci_hcd *uhci, enum uhci_rh_state new_state); static void wakeup_rh(struct uhci_hcd *uhci); static void uhci_get_current_frame_number(struct uhci_hcd *uhci); -/* If a transfer is still active after this much time, turn off FSBR */ -#define IDLE_TIMEOUT msecs_to_jiffies(50) -#define FSBR_DELAY msecs_to_jiffies(50) - -/* When we timeout an idle transfer for FSBR, we'll switch it over to */ -/* depth first traversal. We'll do it in groups of this number of TDs */ -/* to make sure it doesn't hog all of the bandwidth */ -#define DEPTH_INTERVAL 5 - #include "uhci-debug.c" #include "uhci-q.c" #include "uhci-hub.c" @@ -255,6 +246,7 @@ __acquires(uhci->lock) uhci_to_hcd(uhci)->poll_rh = !int_enable; uhci_scan_schedule(uhci, NULL); + uhci_fsbr_off(uhci); } static void start_rh(struct uhci_hcd *uhci) @@ -487,9 +479,6 @@ static int uhci_start(struct usb_hcd *hcd) hcd->uses_new_polling = 1; - uhci->fsbr = 0; - uhci->fsbrtimeout = 0; - spin_lock_init(&uhci->lock); INIT_LIST_HEAD(&uhci->idle_qh_list); diff --git a/drivers/usb/host/uhci-hcd.h b/drivers/usb/host/uhci-hcd.h index 90ef7fb..04938e6 100644 --- a/drivers/usb/host/uhci-hcd.h +++ b/drivers/usb/host/uhci-hcd.h @@ -84,6 +84,13 @@ #define CAN_SCHEDULE_FRAMES 1000 /* how far in the future frames * can be scheduled */ +/* When no queues need Full-Speed Bandwidth Reclamation, + * delay this long before turning FSBR off */ +#define FSBR_OFF_DELAY msecs_to_jiffies(400) + +/* If a queue hasn't advanced after this much time, assume it is stuck */ +#define QH_WAIT_TIMEOUT msecs_to_jiffies(200) + /* * Queue Headers @@ -131,6 +138,7 @@ struct uhci_qh { struct uhci_td *dummy_td; /* Dummy TD to end the queue */ struct uhci_td *post_td; /* Last TD completed */ + unsigned long advance_jiffies; /* Time of last queue advance */ unsigned int unlink_frame; /* When the QH was unlinked */ int state; /* QH_STATE_xxx; see above */ int type; /* Queue type (control, bulk, etc) */ @@ -138,6 +146,7 @@ struct uhci_qh { unsigned int initial_toggle:1; /* Endpoint's current toggle value */ unsigned int needs_fixup:1; /* Must fix the TD toggle values */ unsigned int is_stopped:1; /* Queue was stopped by error/unlink */ + unsigned int wait_expired:1; /* QH_WAIT_TIMEOUT has expired */ } __attribute__((aligned(16))); /* @@ -397,8 +406,7 @@ struct uhci_hcd { __le32 *frame; void **frame_cpu; /* CPU's frame list */ - int fsbr; /* Full-speed bandwidth reclamation */ - unsigned long fsbrtimeout; /* FSBR delay */ + unsigned long fsbr_jiffies; /* Time when FSBR was last wanted */ enum uhci_rh_state rh_state; unsigned long auto_stop_time; /* When to AUTO_STOP */ @@ -413,6 +421,7 @@ struct uhci_hcd { unsigned int working_RD:1; /* Suspended root hub doesn't need to be polled */ unsigned int is_initialized:1; /* Data structure is usable */ + unsigned int fsbr_is_on:1; /* FSBR is turned on */ /* Support for port suspend/resume/reset */ unsigned long port_c_suspend; /* Bit-arrays of ports */ @@ -451,7 +460,7 @@ struct urb_priv { struct uhci_qh *qh; /* QH for this URB */ struct list_head td_list; - unsigned fsbr : 1; /* URB turned on FSBR */ + unsigned fsbr:1; /* URB wants FSBR */ }; diff --git a/drivers/usb/host/uhci-hub.c b/drivers/usb/host/uhci-hub.c index c8451d9..f53c116 100644 --- a/drivers/usb/host/uhci-hub.c +++ b/drivers/usb/host/uhci-hub.c @@ -173,7 +173,6 @@ static int uhci_hub_status_data(struct usb_hcd *hcd, char *buf) uhci_scan_schedule(uhci, NULL); if (uhci->hc_inaccessible) goto done; - check_fsbr(uhci); uhci_check_ports(uhci); status = get_hub_status_data(uhci, buf); diff --git a/drivers/usb/host/uhci-q.c b/drivers/usb/host/uhci-q.c index 12af6fb..2be84b3 100644 --- a/drivers/usb/host/uhci-q.c +++ b/drivers/usb/host/uhci-q.c @@ -37,6 +37,46 @@ static inline void uhci_clear_next_interrupt(struct uhci_hcd *uhci) uhci->term_td->status &= ~cpu_to_le32(TD_CTRL_IOC); } + +/* + * Full-Speed Bandwidth Reclamation (FSBR). + * We turn on FSBR whenever a queue that wants it is advancing, + * and leave it on for a short time thereafter. + */ +static void uhci_fsbr_on(struct uhci_hcd *uhci) +{ + uhci->fsbr_is_on = 1; + uhci->skel_term_qh->link = cpu_to_le32( + uhci->skel_fs_control_qh->dma_handle) | UHCI_PTR_QH; +} + +static void uhci_fsbr_off(struct uhci_hcd *uhci) +{ + uhci->fsbr_is_on = 0; + uhci->skel_term_qh->link = UHCI_PTR_TERM; +} + +static void uhci_add_fsbr(struct uhci_hcd *uhci, struct urb *urb) +{ + struct urb_priv *urbp = urb->hcpriv; + + if (!(urb->transfer_flags & URB_NO_FSBR)) + urbp->fsbr = 1; +} + +static void uhci_qh_wants_fsbr(struct uhci_hcd *uhci, struct uhci_qh *qh) +{ + struct urb_priv *urbp = + list_entry(qh->queue.next, struct urb_priv, node); + + if (urbp->fsbr) { + uhci->fsbr_jiffies = jiffies; + if (!uhci->fsbr_is_on) + uhci_fsbr_on(uhci); + } +} + + static struct uhci_td *uhci_alloc_td(struct uhci_hcd *uhci) { dma_addr_t dma_handle; @@ -331,6 +371,10 @@ static void uhci_activate_qh(struct uhci_hcd *uhci, struct uhci_qh *qh) qh->element = cpu_to_le32(td->dma_handle); } + /* Treat the queue as if it has just advanced */ + qh->wait_expired = 0; + qh->advance_jiffies = jiffies; + if (qh->state == QH_STATE_ACTIVE) return; qh->state = QH_STATE_ACTIVE; @@ -445,28 +489,6 @@ static void uhci_free_urb_priv(struct uhci_hcd *uhci, kmem_cache_free(uhci_up_cachep, urbp); } -static void uhci_inc_fsbr(struct uhci_hcd *uhci, struct urb *urb) -{ - struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv; - - if ((!(urb->transfer_flags & URB_NO_FSBR)) && !urbp->fsbr) { - urbp->fsbr = 1; - if (!uhci->fsbr++ && !uhci->fsbrtimeout) - uhci->skel_term_qh->link = cpu_to_le32(uhci->skel_fs_control_qh->dma_handle) | UHCI_PTR_QH; - } -} - -static void uhci_dec_fsbr(struct uhci_hcd *uhci, struct urb *urb) -{ - struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv; - - if ((!(urb->transfer_flags & URB_NO_FSBR)) && urbp->fsbr) { - urbp->fsbr = 0; - if (!--uhci->fsbr) - uhci->fsbrtimeout = jiffies + FSBR_DELAY; - } -} - /* * Map status to standard result codes * @@ -613,7 +635,7 @@ static int uhci_submit_control(struct uhci_hcd *uhci, struct urb *urb, qh->skel = uhci->skel_ls_control_qh; else { qh->skel = uhci->skel_fs_control_qh; - uhci_inc_fsbr(uhci, urb); + uhci_add_fsbr(uhci, urb); } urb->actual_length = -8; /* Account for the SETUP packet */ @@ -756,7 +778,7 @@ static inline int uhci_submit_bulk(struct uhci_hcd *uhci, struct urb *urb, qh->skel = uhci->skel_bulk_qh; ret = uhci_submit_common(uhci, urb, qh); if (ret == 0) - uhci_inc_fsbr(uhci, urb); + uhci_add_fsbr(uhci, urb); return ret; } @@ -1075,8 +1097,10 @@ static int uhci_urb_enqueue(struct usb_hcd *hcd, * the QH is new and idle or else it's unlinked and waiting to * become idle, so we can activate it right away. But only if the * 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_qh_wants_fsbr(uhci, qh); + } goto done; err_submit_failed: @@ -1135,7 +1159,6 @@ __acquires(uhci->lock) qh->needs_fixup = 0; } - uhci_dec_fsbr(uhci, urb); /* Safe since it checks */ uhci_free_urb_priv(uhci, urbp); switch (qh->type) { @@ -1239,6 +1262,18 @@ restart: if (!list_empty(&qh->queue)) { if (qh->needs_fixup) uhci_fixup_toggles(qh, 0); + + /* If the first URB on the queue wants FSBR but its time + * limit has expired, set the next TD to interrupt on + * completion before reactivating the QH. */ + urbp = list_entry(qh->queue.next, struct urb_priv, node); + if (urbp->fsbr && qh->wait_expired) { + struct uhci_td *td = list_entry(urbp->td_list.next, + struct uhci_td, list); + + td->status |= __cpu_to_le32(TD_CTRL_IOC); + } + uhci_activate_qh(uhci, qh); } @@ -1249,6 +1284,62 @@ restart: } /* + * Check for queues that have made some forward progress. + * Returns 0 if the queue is not Isochronous, is ACTIVE, and + * has not advanced since last examined; 1 otherwise. + */ +static int uhci_advance_check(struct uhci_hcd *uhci, struct uhci_qh *qh) +{ + struct urb_priv *urbp = NULL; + struct uhci_td *td; + int ret = 1; + unsigned status; + + if (qh->type == USB_ENDPOINT_XFER_ISOC) + return ret; + + /* Treat an UNLINKING queue as though it hasn't advanced. + * This is okay because reactivation will treat it as though + * it has advanced, and if it is going to become IDLE then + * this doesn't matter anyway. Furthermore it's possible + * for an UNLINKING queue not to have any URBs at all, or + * for its first URB not to have any TDs (if it was dequeued + * just as it completed). So it's not easy in any case to + * test whether such queues have advanced. */ + if (qh->state != QH_STATE_ACTIVE) { + urbp = NULL; + status = 0; + + } else { + urbp = list_entry(qh->queue.next, struct urb_priv, node); + td = list_entry(urbp->td_list.next, struct uhci_td, list); + status = td_status(td); + if (!(status & TD_CTRL_ACTIVE)) { + + /* We're okay, the queue has advanced */ + qh->wait_expired = 0; + qh->advance_jiffies = jiffies; + return ret; + } + ret = 0; + } + + /* The queue hasn't advanced; check for timeout */ + if (!qh->wait_expired && time_after(jiffies, + qh->advance_jiffies + QH_WAIT_TIMEOUT)) { + qh->wait_expired = 1; + + /* If the current URB wants FSBR, unlink it temporarily + * so that we can safely set the next TD to interrupt on + * completion. That way we'll know as soon as the queue + * starts moving again. */ + if (urbp && urbp->fsbr && !(status & TD_CTRL_IOC)) + uhci_unlink_qh(uhci, qh); + } + return ret; +} + +/* * Process events in the schedule, but only in one thread at a time */ static void uhci_scan_schedule(struct uhci_hcd *uhci, struct pt_regs *regs) @@ -1262,7 +1353,7 @@ static void uhci_scan_schedule(struct uhci_hcd *uhci, struct pt_regs *regs) return; } uhci->scan_in_progress = 1; - rescan: +rescan: uhci->need_rescan = 0; uhci_clear_next_interrupt(uhci); @@ -1275,7 +1366,12 @@ static void uhci_scan_schedule(struct uhci_hcd *uhci, struct pt_regs *regs) while ((qh = uhci->next_qh) != uhci->skelqh[i]) { uhci->next_qh = list_entry(qh->node.next, struct uhci_qh, node); - uhci_scan_qh(uhci, qh, regs); + + if (uhci_advance_check(uhci, qh)) { + uhci_scan_qh(uhci, qh, regs); + if (qh->state == QH_STATE_ACTIVE) + uhci_qh_wants_fsbr(uhci, qh); + } } } @@ -1283,20 +1379,12 @@ static void uhci_scan_schedule(struct uhci_hcd *uhci, struct pt_regs *regs) goto rescan; uhci->scan_in_progress = 0; + if (uhci->fsbr_is_on && time_after(jiffies, + uhci->fsbr_jiffies + FSBR_OFF_DELAY)) + uhci_fsbr_off(uhci); + if (list_empty(&uhci->skel_unlink_qh->node)) uhci_clear_next_interrupt(uhci); else uhci_set_next_interrupt(uhci); } - -static void check_fsbr(struct uhci_hcd *uhci) -{ - /* For now, don't scan URBs for FSBR timeouts. - * Add it back in later... */ - - /* Really disable FSBR */ - if (!uhci->fsbr && uhci->fsbrtimeout && time_after_eq(jiffies, uhci->fsbrtimeout)) { - uhci->fsbrtimeout = 0; - uhci->skel_term_qh->link = UHCI_PTR_TERM; - } -} -- cgit v1.1