diff options
| -rw-r--r-- | drivers/usb/host/uhci-debug.c | 3 | ||||
| -rw-r--r-- | drivers/usb/host/uhci-hcd.c | 13 | ||||
| -rw-r--r-- | drivers/usb/host/uhci-hcd.h | 15 | ||||
| -rw-r--r-- | drivers/usb/host/uhci-hub.c | 1 | ||||
| -rw-r--r-- | drivers/usb/host/uhci-q.c | 168 | 
5 files changed, 143 insertions, 57 deletions
diff --git a/drivers/usb/host/uhci-debug.c b/drivers/usb/host/uhci-debug.c index 6bbd33db935..081c592fe8b 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 fb4c1a8cadf..395402eec5e 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 90ef7fbbf2f..04938e64799 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 c8451d9578f..f53c116e0df 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 12af6fb05a3..2be84b3b40f 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; -	} -}  |