[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;
-	}
-}