[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 <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
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 @@
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 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 @@
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 @@
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_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 @@
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 @@
__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 @@
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 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
* 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 @@
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 @@
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 @@
}
/*
+ * 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 @@
return;
}
uhci->scan_in_progress = 1;
- rescan:
+rescan:
uhci->need_rescan = 0;
uhci_clear_next_interrupt(uhci);
@@ -1275,7 +1366,12 @@
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 @@
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;
- }
-}