diff options
Diffstat (limited to 'drivers/usb/host/ehci-q.c')
| -rw-r--r-- | drivers/usb/host/ehci-q.c | 307 | 
1 files changed, 175 insertions, 132 deletions
diff --git a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c index 4378bf72bba..9bc39ca460c 100644 --- a/drivers/usb/host/ehci-q.c +++ b/drivers/usb/host/ehci-q.c @@ -100,7 +100,7 @@ qh_update (struct ehci_hcd *ehci, struct ehci_qh *qh, struct ehci_qtd *qtd)  	 * and set the pseudo-toggle in udev. Only usb_clear_halt() will  	 * ever clear it.  	 */ -	if (!(hw->hw_info1 & cpu_to_hc32(ehci, 1 << 14))) { +	if (!(hw->hw_info1 & cpu_to_hc32(ehci, QH_TOGGLE_CTL))) {  		unsigned	is_out, epnum;  		is_out = qh->is_out; @@ -265,7 +265,6 @@ __acquires(ehci->lock)  			/* ... update hc-wide periodic stats (for usbfs) */  			ehci_to_hcd(ehci)->self.bandwidth_int_reqs--;  		} -		qh_put (qh);  	}  	if (unlikely(urb->unlinked)) { @@ -294,9 +293,6 @@ __acquires(ehci->lock)  	spin_lock (&ehci->lock);  } -static void start_unlink_async (struct ehci_hcd *ehci, struct ehci_qh *qh); -static void unlink_async (struct ehci_hcd *ehci, struct ehci_qh *qh); -  static int qh_schedule (struct ehci_hcd *ehci, struct ehci_qh *qh);  /* @@ -326,7 +322,7 @@ qh_completions (struct ehci_hcd *ehci, struct ehci_qh *qh)  	 *  	 * It's a bug for qh->qh_state to be anything other than  	 * QH_STATE_IDLE, unless our caller is scan_async() or -	 * scan_periodic(). +	 * scan_intr().  	 */  	state = qh->qh_state;  	qh->qh_state = QH_STATE_COMPLETING; @@ -434,7 +430,7 @@ qh_completions (struct ehci_hcd *ehci, struct ehci_qh *qh)  		/* stop scanning when we reach qtds the hc is using */  		} else if (likely (!stopped -				&& ehci->rh_state == EHCI_RH_RUNNING)) { +				&& ehci->rh_state >= EHCI_RH_RUNNING)) {  			break;  		/* scan the whole queue for unlinks whenever it stops */ @@ -442,7 +438,7 @@ qh_completions (struct ehci_hcd *ehci, struct ehci_qh *qh)  			stopped = 1;  			/* cancel everything if we halt, suspend, etc */ -			if (ehci->rh_state != EHCI_RH_RUNNING) +			if (ehci->rh_state < EHCI_RH_RUNNING)  				last_status = -ESHUTDOWN;  			/* this qtd is active; skip it unless a previous qtd @@ -836,7 +832,6 @@ qh_make (  				is_input, 0,  				hb_mult(maxp) * max_packet(maxp)));  		qh->start = NO_FRAME; -		qh->stamp = ehci->periodic_stamp;  		if (urb->dev->speed == USB_SPEED_HIGH) {  			qh->c_usecs = 0; @@ -887,7 +882,7 @@ qh_make (  	/* using TT? */  	switch (urb->dev->speed) {  	case USB_SPEED_LOW: -		info1 |= (1 << 12);	/* EPS "low" */ +		info1 |= QH_LOW_SPEED;  		/* FALL THROUGH */  	case USB_SPEED_FULL: @@ -895,8 +890,8 @@ qh_make (  		if (type != PIPE_INTERRUPT)  			info1 |= (EHCI_TUNE_RL_TT << 28);  		if (type == PIPE_CONTROL) { -			info1 |= (1 << 27);	/* for TT */ -			info1 |= 1 << 14;	/* toggle from qtd */ +			info1 |= QH_CONTROL_EP;		/* for TT */ +			info1 |= QH_TOGGLE_CTL;		/* toggle from qtd */  		}  		info1 |= maxp << 16; @@ -921,11 +916,11 @@ qh_make (  		break;  	case USB_SPEED_HIGH:		/* no TT involved */ -		info1 |= (2 << 12);	/* EPS "high" */ +		info1 |= QH_HIGH_SPEED;  		if (type == PIPE_CONTROL) {  			info1 |= (EHCI_TUNE_RL_HS << 28);  			info1 |= 64 << 16;	/* usb2 fixed maxpacket */ -			info1 |= 1 << 14;	/* toggle from qtd */ +			info1 |= QH_TOGGLE_CTL;	/* toggle from qtd */  			info2 |= (EHCI_TUNE_MULT_HS << 30);  		} else if (type == PIPE_BULK) {  			info1 |= (EHCI_TUNE_RL_HS << 28); @@ -946,7 +941,7 @@ qh_make (  		ehci_dbg(ehci, "bogus dev %p speed %d\n", urb->dev,  			urb->dev->speed);  done: -		qh_put (qh); +		qh_destroy(ehci, qh);  		return NULL;  	} @@ -965,6 +960,31 @@ done:  /*-------------------------------------------------------------------------*/ +static void enable_async(struct ehci_hcd *ehci) +{ +	if (ehci->async_count++) +		return; + +	/* Stop waiting to turn off the async schedule */ +	ehci->enabled_hrtimer_events &= ~BIT(EHCI_HRTIMER_DISABLE_ASYNC); + +	/* Don't start the schedule until ASS is 0 */ +	ehci_poll_ASS(ehci); +	turn_on_io_watchdog(ehci); +} + +static void disable_async(struct ehci_hcd *ehci) +{ +	if (--ehci->async_count) +		return; + +	/* The async schedule and async_unlink list are supposed to be empty */ +	WARN_ON(ehci->async->qh_next.qh || ehci->async_unlink); + +	/* Don't turn off the schedule until ASS is 1 */ +	ehci_poll_ASS(ehci); +} +  /* move qh (and its qtds) onto async queue; maybe enable queue.  */  static void qh_link_async (struct ehci_hcd *ehci, struct ehci_qh *qh) @@ -978,24 +998,11 @@ static void qh_link_async (struct ehci_hcd *ehci, struct ehci_qh *qh)  	WARN_ON(qh->qh_state != QH_STATE_IDLE); -	/* (re)start the async schedule? */ -	head = ehci->async; -	timer_action_done (ehci, TIMER_ASYNC_OFF); -	if (!head->qh_next.qh) { -		if (!(ehci->command & CMD_ASE)) { -			/* in case a clear of CMD_ASE didn't take yet */ -			(void)handshake(ehci, &ehci->regs->status, -					STS_ASS, 0, 150); -			ehci->command |= CMD_ASE; -			ehci_writel(ehci, ehci->command, &ehci->regs->command); -			/* posted write need not be known to HC yet ... */ -		} -	} -  	/* clear halt and/or toggle; and maybe recover from silicon quirk */  	qh_refresh(ehci, qh);  	/* splice right after start */ +	head = ehci->async;  	qh->qh_next = head->qh_next;  	qh->hw->hw_next = head->hw->hw_next;  	wmb (); @@ -1003,10 +1010,11 @@ static void qh_link_async (struct ehci_hcd *ehci, struct ehci_qh *qh)  	head->qh_next.qh = qh;  	head->hw->hw_next = dma; -	qh_get(qh);  	qh->xacterrs = 0;  	qh->qh_state = QH_STATE_LINKED;  	/* qtd completions reported later by interrupt */ + +	enable_async(ehci);  }  /*-------------------------------------------------------------------------*/ @@ -1090,7 +1098,7 @@ static struct ehci_qh *qh_append_tds (  			wmb ();  			dummy->hw_token = token; -			urb->hcpriv = qh_get (qh); +			urb->hcpriv = qh;  		}  	}  	return qh; @@ -1155,117 +1163,155 @@ submit_async (  /*-------------------------------------------------------------------------*/ -/* the async qh for the qtds being reclaimed are now unlinked from the HC */ - -static void end_unlink_async (struct ehci_hcd *ehci) +static void single_unlink_async(struct ehci_hcd *ehci, struct ehci_qh *qh)  { -	struct ehci_qh		*qh = ehci->reclaim; -	struct ehci_qh		*next; +	struct ehci_qh		*prev; -	iaa_watchdog_done(ehci); +	/* Add to the end of the list of QHs waiting for the next IAAD */ +	qh->qh_state = QH_STATE_UNLINK; +	if (ehci->async_unlink) +		ehci->async_unlink_last->unlink_next = qh; +	else +		ehci->async_unlink = qh; +	ehci->async_unlink_last = qh; -	// qh->hw_next = cpu_to_hc32(qh->qh_dma); -	qh->qh_state = QH_STATE_IDLE; -	qh->qh_next.qh = NULL; -	qh_put (qh);			// refcount from reclaim +	/* Unlink it from the schedule */ +	prev = ehci->async; +	while (prev->qh_next.qh != qh) +		prev = prev->qh_next.qh; + +	prev->hw->hw_next = qh->hw->hw_next; +	prev->qh_next = qh->qh_next; +	if (ehci->qh_scan_next == qh) +		ehci->qh_scan_next = qh->qh_next.qh; +} + +static void start_iaa_cycle(struct ehci_hcd *ehci, bool nested) +{ +	/* +	 * Do nothing if an IAA cycle is already running or +	 * if one will be started shortly. +	 */ +	if (ehci->async_iaa || ehci->async_unlinking) +		return; -	/* other unlink(s) may be pending (in QH_STATE_UNLINK_WAIT) */ -	next = qh->reclaim; -	ehci->reclaim = next; -	qh->reclaim = NULL; +	/* Do all the waiting QHs at once */ +	ehci->async_iaa = ehci->async_unlink; +	ehci->async_unlink = NULL; -	qh_completions (ehci, qh); +	/* If the controller isn't running, we don't have to wait for it */ +	if (unlikely(ehci->rh_state < EHCI_RH_RUNNING)) { +		if (!nested)		/* Avoid recursion */ +			end_unlink_async(ehci); -	if (!list_empty(&qh->qtd_list) && ehci->rh_state == EHCI_RH_RUNNING) { -		qh_link_async (ehci, qh); -	} else { -		/* it's not free to turn the async schedule on/off; leave it -		 * active but idle for a while once it empties. -		 */ -		if (ehci->rh_state == EHCI_RH_RUNNING -				&& ehci->async->qh_next.qh == NULL) -			timer_action (ehci, TIMER_ASYNC_OFF); -	} -	qh_put(qh);			/* refcount from async list */ +	/* Otherwise start a new IAA cycle */ +	} else if (likely(ehci->rh_state == EHCI_RH_RUNNING)) { +		/* Make sure the unlinks are all visible to the hardware */ +		wmb(); -	if (next) { -		ehci->reclaim = NULL; -		start_unlink_async (ehci, next); +		ehci_writel(ehci, ehci->command | CMD_IAAD, +				&ehci->regs->command); +		ehci_readl(ehci, &ehci->regs->command); +		ehci_enable_event(ehci, EHCI_HRTIMER_IAA_WATCHDOG, true);  	} +} + +/* the async qh for the qtds being unlinked are now gone from the HC */ + +static void end_unlink_async(struct ehci_hcd *ehci) +{ +	struct ehci_qh		*qh;  	if (ehci->has_synopsys_hc_bug)  		ehci_writel(ehci, (u32) ehci->async->qh_dma,  			    &ehci->regs->async_next); -} -/* makes sure the async qh will become idle */ -/* caller must own ehci->lock */ +	/* Process the idle QHs */ + restart: +	ehci->async_unlinking = true; +	while (ehci->async_iaa) { +		qh = ehci->async_iaa; +		ehci->async_iaa = qh->unlink_next; +		qh->unlink_next = NULL; + +		qh->qh_state = QH_STATE_IDLE; +		qh->qh_next.qh = NULL; -static void start_unlink_async (struct ehci_hcd *ehci, struct ehci_qh *qh) +		qh_completions(ehci, qh); +		if (!list_empty(&qh->qtd_list) && +				ehci->rh_state == EHCI_RH_RUNNING) +			qh_link_async(ehci, qh); +		disable_async(ehci); +	} +	ehci->async_unlinking = false; + +	/* Start a new IAA cycle if any QHs are waiting for it */ +	if (ehci->async_unlink) { +		start_iaa_cycle(ehci, true); +		if (unlikely(ehci->rh_state < EHCI_RH_RUNNING)) +			goto restart; +	} +} + +static void unlink_empty_async(struct ehci_hcd *ehci)  { -	struct ehci_qh	*prev; +	struct ehci_qh		*qh, *next; +	bool			stopped = (ehci->rh_state < EHCI_RH_RUNNING); +	bool			check_unlinks_later = false; -#ifdef DEBUG -	assert_spin_locked(&ehci->lock); -	if (ehci->reclaim -			|| (qh->qh_state != QH_STATE_LINKED -				&& qh->qh_state != QH_STATE_UNLINK_WAIT) -			) -		BUG (); -#endif +	/* Unlink all the async QHs that have been empty for a timer cycle */ +	next = ehci->async->qh_next.qh; +	while (next) { +		qh = next; +		next = qh->qh_next.qh; -	/* stop async schedule right now? */ -	if (unlikely (qh == ehci->async)) { -		/* can't get here without STS_ASS set */ -		if (ehci->rh_state != EHCI_RH_HALTED -				&& !ehci->reclaim) { -			/* ... and CMD_IAAD clear */ -			ehci->command &= ~CMD_ASE; -			ehci_writel(ehci, ehci->command, &ehci->regs->command); -			wmb (); -			// handshake later, if we need to -			timer_action_done (ehci, TIMER_ASYNC_OFF); +		if (list_empty(&qh->qtd_list) && +				qh->qh_state == QH_STATE_LINKED) { +			if (!stopped && qh->unlink_cycle == +					ehci->async_unlink_cycle) +				check_unlinks_later = true; +			else +				single_unlink_async(ehci, qh);  		} -		return;  	} -	qh->qh_state = QH_STATE_UNLINK; -	ehci->reclaim = qh = qh_get (qh); +	/* Start a new IAA cycle if any QHs are waiting for it */ +	if (ehci->async_unlink) +		start_iaa_cycle(ehci, false); -	prev = ehci->async; -	while (prev->qh_next.qh != qh) -		prev = prev->qh_next.qh; +	/* QHs that haven't been empty for long enough will be handled later */ +	if (check_unlinks_later) { +		ehci_enable_event(ehci, EHCI_HRTIMER_ASYNC_UNLINKS, true); +		++ehci->async_unlink_cycle; +	} +} -	prev->hw->hw_next = qh->hw->hw_next; -	prev->qh_next = qh->qh_next; -	if (ehci->qh_scan_next == qh) -		ehci->qh_scan_next = qh->qh_next.qh; -	wmb (); +/* makes sure the async qh will become idle */ +/* caller must own ehci->lock */ -	/* If the controller isn't running, we don't have to wait for it */ -	if (unlikely(ehci->rh_state != EHCI_RH_RUNNING)) { -		/* if (unlikely (qh->reclaim != 0)) -		 *	this will recurse, probably not much -		 */ -		end_unlink_async (ehci); +static void start_unlink_async(struct ehci_hcd *ehci, struct ehci_qh *qh) +{ +	/* +	 * If the QH isn't linked then there's nothing we can do +	 * unless we were called during a giveback, in which case +	 * qh_completions() has to deal with it. +	 */ +	if (qh->qh_state != QH_STATE_LINKED) { +		if (qh->qh_state == QH_STATE_COMPLETING) +			qh->needs_rescan = 1;  		return;  	} -	ehci_writel(ehci, ehci->command | CMD_IAAD, &ehci->regs->command); -	(void)ehci_readl(ehci, &ehci->regs->command); -	iaa_watchdog_start(ehci); +	single_unlink_async(ehci, qh); +	start_iaa_cycle(ehci, false);  }  /*-------------------------------------------------------------------------*/  static void scan_async (struct ehci_hcd *ehci)  { -	bool			stopped;  	struct ehci_qh		*qh; -	enum ehci_timer_action	action = TIMER_IO_WATCHDOG; - -	timer_action_done (ehci, TIMER_ASYNC_SHRINK); -	stopped = (ehci->rh_state != EHCI_RH_RUNNING); +	bool			check_unlinks_later = false;  	ehci->qh_scan_next = ehci->async->qh_next.qh;  	while (ehci->qh_scan_next) { @@ -1281,33 +1327,30 @@ static void scan_async (struct ehci_hcd *ehci)  			 * drops the lock.  That's why ehci->qh_scan_next  			 * always holds the next qh to scan; if the next qh  			 * gets unlinked then ehci->qh_scan_next is adjusted -			 * in start_unlink_async(). +			 * in single_unlink_async().  			 */ -			qh = qh_get(qh);  			temp = qh_completions(ehci, qh); -			if (qh->needs_rescan) -				unlink_async(ehci, qh); -			qh->unlink_time = jiffies + EHCI_SHRINK_JIFFIES; -			qh_put(qh); -			if (temp != 0) +			if (qh->needs_rescan) { +				start_unlink_async(ehci, qh); +			} else if (list_empty(&qh->qtd_list) +					&& qh->qh_state == QH_STATE_LINKED) { +				qh->unlink_cycle = ehci->async_unlink_cycle; +				check_unlinks_later = true; +			} else if (temp != 0)  				goto rescan;  		} +	} -		/* unlink idle entries, reducing DMA usage as well -		 * as HCD schedule-scanning costs.  delay for any qh -		 * we just scanned, there's a not-unusual case that it -		 * doesn't stay idle for long. -		 * (plus, avoids some kind of re-activation race.) -		 */ -		if (list_empty(&qh->qtd_list) -				&& qh->qh_state == QH_STATE_LINKED) { -			if (!ehci->reclaim && (stopped || -					time_after_eq(jiffies, qh->unlink_time))) -				start_unlink_async(ehci, qh); -			else -				action = TIMER_ASYNC_SHRINK; -		} +	/* +	 * Unlink empty entries, reducing DMA usage as well +	 * as HCD schedule-scanning costs.  Delay for any qh +	 * we just scanned, there's a not-unusual case that it +	 * doesn't stay idle for long. +	 */ +	if (check_unlinks_later && ehci->rh_state == EHCI_RH_RUNNING && +			!(ehci->enabled_hrtimer_events & +				BIT(EHCI_HRTIMER_ASYNC_UNLINKS))) { +		ehci_enable_event(ehci, EHCI_HRTIMER_ASYNC_UNLINKS, true); +		++ehci->async_unlink_cycle;  	} -	if (action == TIMER_ASYNC_SHRINK) -		timer_action (ehci, TIMER_ASYNC_SHRINK);  }  |