diff options
Diffstat (limited to 'drivers/usb/host/ehci-sched.c')
| -rw-r--r-- | drivers/usb/host/ehci-sched.c | 136 | 
1 files changed, 61 insertions, 75 deletions
diff --git a/drivers/usb/host/ehci-sched.c b/drivers/usb/host/ehci-sched.c index 7cf3da7babf..69ebee73c0c 100644 --- a/drivers/usb/host/ehci-sched.c +++ b/drivers/usb/host/ehci-sched.c @@ -36,29 +36,6 @@  static int ehci_get_frame (struct usb_hcd *hcd); -#ifdef CONFIG_PCI - -static unsigned ehci_read_frame_index(struct ehci_hcd *ehci) -{ -	unsigned uf; - -	/* -	 * The MosChip MCS9990 controller updates its microframe counter -	 * a little before the frame counter, and occasionally we will read -	 * the invalid intermediate value.  Avoid problems by checking the -	 * microframe number (the low-order 3 bits); if they are 0 then -	 * re-read the register to get the correct value. -	 */ -	uf = ehci_readl(ehci, &ehci->regs->frame_index); -	if (unlikely(ehci->frame_index_bug && ((uf & 7) == 0))) -		uf = ehci_readl(ehci, &ehci->regs->frame_index); -	return uf; -} - -#endif - -/*-------------------------------------------------------------------------*/ -  /*   * periodic_next_shadow - return "next" pointer on shadow list   * @periodic: host pointer to qh/itd/sitd @@ -1361,7 +1338,7 @@ sitd_slot_ok (   * given EHCI_TUNE_FLS and the slop).  Or, write a smarter scheduler!   */ -#define SCHEDULE_SLOP	80	/* microframes */ +#define SCHEDULING_DELAY	40	/* microframes */  static int  iso_stream_schedule ( @@ -1370,7 +1347,7 @@ iso_stream_schedule (  	struct ehci_iso_stream	*stream  )  { -	u32			now, next, start, period, span; +	u32			now, base, next, start, period, span;  	int			status;  	unsigned		mod = ehci->periodic_size << 3;  	struct ehci_iso_sched	*sched = urb->hcpriv; @@ -1382,62 +1359,72 @@ iso_stream_schedule (  		span <<= 3;  	} -	if (span > mod - SCHEDULE_SLOP) { -		ehci_dbg (ehci, "iso request %p too long\n", urb); -		status = -EFBIG; -		goto fail; -	} -  	now = ehci_read_frame_index(ehci) & (mod - 1);  	/* Typical case: reuse current schedule, stream is still active.  	 * Hopefully there are no gaps from the host falling behind -	 * (irq delays etc), but if there are we'll take the next -	 * slot in the schedule, implicitly assuming URB_ISO_ASAP. +	 * (irq delays etc).  If there are, the behavior depends on +	 * whether URB_ISO_ASAP is set.  	 */  	if (likely (!list_empty (&stream->td_list))) { -		u32	excess; -		/* For high speed devices, allow scheduling within the -		 * isochronous scheduling threshold.  For full speed devices -		 * and Intel PCI-based controllers, don't (work around for -		 * Intel ICH9 bug). -		 */ -		if (!stream->highspeed && ehci->fs_i_thresh) -			next = now + ehci->i_thresh; +		/* Take the isochronous scheduling threshold into account */ +		if (ehci->i_thresh) +			next = now + ehci->i_thresh;	/* uframe cache */  		else -			next = now; +			next = (now + 2 + 7) & ~0x07;	/* full frame cache */ -		/* Fell behind (by up to twice the slop amount)? -		 * We decide based on the time of the last currently-scheduled -		 * slot, not the time of the next available slot. +		/* +		 * Use ehci->last_iso_frame as the base.  There can't be any +		 * TDs scheduled for earlier than that.  		 */ -		excess = (stream->next_uframe - period - next) & (mod - 1); -		if (excess >= mod - 2 * SCHEDULE_SLOP) -			start = next + excess - mod + period * -					DIV_ROUND_UP(mod - excess, period); -		else -			start = next + excess + period; -		if (start - now >= mod) { -			ehci_dbg(ehci, "request %p would overflow (%d+%d >= %d)\n", -					urb, start - now - period, period, -					mod); -			status = -EFBIG; +		base = ehci->last_iso_frame << 3; +		next = (next - base) & (mod - 1); +		start = (stream->next_uframe - base) & (mod - 1); + +		/* Is the schedule already full? */ +		if (unlikely(start < period)) { +			ehci_dbg(ehci, "iso sched full %p (%u-%u < %u mod %u)\n", +					urb, stream->next_uframe, base, +					period, mod); +			status = -ENOSPC;  			goto fail;  		} + +		/* Behind the scheduling threshold? */ +		if (unlikely(start < next)) { + +			/* USB_ISO_ASAP: Round up to the first available slot */ +			if (urb->transfer_flags & URB_ISO_ASAP) +				start += (next - start + period - 1) & -period; + +			/* +			 * Not ASAP: Use the next slot in the stream.  If +			 * the entire URB falls before the threshold, fail. +			 */ +			else if (start + span - period < next) { +				ehci_dbg(ehci, "iso urb late %p (%u+%u < %u)\n", +						urb, start + base, +						span - period, next + base); +				status = -EXDEV; +				goto fail; +			} +		} + +		start += base;  	}  	/* need to schedule; when's the next (u)frame we could start?  	 * this is bigger than ehci->i_thresh allows; scheduling itself -	 * isn't free, the slop should handle reasonably slow cpus.  it +	 * isn't free, the delay should handle reasonably slow cpus.  it  	 * can also help high bandwidth if the dma and irq loads don't  	 * jump until after the queue is primed.  	 */  	else {  		int done = 0; -		start = SCHEDULE_SLOP + (now & ~0x07); -		/* NOTE:  assumes URB_ISO_ASAP, to limit complexity/bugs */ +		base = now & ~0x07; +		start = base + SCHEDULING_DELAY;  		/* find a uframe slot with enough bandwidth.  		 * Early uframes are more precious because full-speed @@ -1464,19 +1451,16 @@ iso_stream_schedule (  		/* no room in the schedule */  		if (!done) { -			ehci_dbg(ehci, "iso resched full %p (now %d max %d)\n", -				urb, now, now + mod); +			ehci_dbg(ehci, "iso sched full %p", urb);  			status = -ENOSPC;  			goto fail;  		}  	}  	/* Tried to schedule too far into the future? */ -	if (unlikely(start - now + span - period -				>= mod - 2 * SCHEDULE_SLOP)) { -		ehci_dbg(ehci, "request %p would overflow (%d+%d >= %d)\n", -				urb, start - now, span - period, -				mod - 2 * SCHEDULE_SLOP); +	if (unlikely(start - base + span - period >= mod)) { +		ehci_dbg(ehci, "request %p would overflow (%u+%u >= %u)\n", +				urb, start - base, span - period, mod);  		status = -EFBIG;  		goto fail;  	} @@ -1490,7 +1474,7 @@ iso_stream_schedule (  	/* Make sure scan_isoc() sees these */  	if (ehci->isoc_count == 0) -		ehci->next_frame = now >> 3; +		ehci->last_iso_frame = now >> 3;  	return 0;   fail: @@ -1646,7 +1630,7 @@ static void itd_link_urb(  	/* don't need that schedule data any more */  	iso_sched_free (stream, iso_sched); -	urb->hcpriv = NULL; +	urb->hcpriv = stream;  	++ehci->isoc_count;  	enable_periodic(ehci); @@ -1708,7 +1692,7 @@ static bool itd_complete(struct ehci_hcd *ehci, struct ehci_itd *itd)  			urb->actual_length += desc->actual_length;  		} else {  			/* URB was too late */ -			desc->status = -EXDEV; +			urb->error_count++;  		}  	} @@ -2045,7 +2029,7 @@ static void sitd_link_urb(  	/* don't need that schedule data any more */  	iso_sched_free (stream, sched); -	urb->hcpriv = NULL; +	urb->hcpriv = stream;  	++ehci->isoc_count;  	enable_periodic(ehci); @@ -2081,7 +2065,7 @@ static bool sitd_complete(struct ehci_hcd *ehci, struct ehci_sitd *sitd)  	t = hc32_to_cpup(ehci, &sitd->hw_results);  	/* report transfer status */ -	if (t & SITD_ERRS) { +	if (unlikely(t & SITD_ERRS)) {  		urb->error_count++;  		if (t & SITD_STS_DBE)  			desc->status = usb_pipein (urb->pipe) @@ -2091,6 +2075,9 @@ static bool sitd_complete(struct ehci_hcd *ehci, struct ehci_sitd *sitd)  			desc->status = -EOVERFLOW;  		else /* XACT, MMF, etc */  			desc->status = -EPROTO; +	} else if (unlikely(t & SITD_STS_ACTIVE)) { +		/* URB was too late */ +		urb->error_count++;  	} else {  		desc->status = 0;  		desc->actual_length = desc->length - SITD_LENGTH(t); @@ -2220,16 +2207,16 @@ static void scan_isoc(struct ehci_hcd *ehci)  		now_frame = (uf >> 3) & fmask;  		live = true;  	} else  { -		now_frame = (ehci->next_frame - 1) & fmask; +		now_frame = (ehci->last_iso_frame - 1) & fmask;  		live = false;  	}  	ehci->now_frame = now_frame; -	frame = ehci->next_frame;  	for (;;) {  		union ehci_shadow	q, *q_p;  		__hc32			type, *hw_p; +		frame = ehci->last_iso_frame;  restart:  		/* scan each element in frame's queue for completions */  		q_p = &ehci->pshadow [frame]; @@ -2334,7 +2321,6 @@ restart:  		/* Stop when we have reached the current frame */  		if (frame == now_frame)  			break; -		frame = (frame + 1) & fmask; +		ehci->last_iso_frame = (frame + 1) & fmask;  	} -	ehci->next_frame = now_frame;  }  |