⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 ehci-q.c

📁 一个2.4.21版本的嵌入式linux内核
💻 C
📖 第 1 页 / 共 2 页
字号:
/* * Copyright (c) 2001-2002 by David Brownell *  * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License * for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. *//* this file is part of ehci-hcd.c *//*-------------------------------------------------------------------------*//* * EHCI hardware queue manipulation ... the core.  QH/QTD manipulation. * * Control, bulk, and interrupt traffic all use "qh" lists.  They list "qtd" * entries describing USB transactions, max 16-20kB/entry (with 4kB-aligned * buffers needed for the larger number).  We use one QH per endpoint, queue * multiple urbs (all three types) per endpoint.  URBs may need several qtds. * * ISO traffic uses "ISO TD" (itd, and sitd) records, and (along with * interrupts) needs careful scheduling.  Performance improvements can be * an ongoing challenge.  That's in "ehci-sched.c". *  * USB 1.1 devices are handled (a) by "companion" OHCI or UHCI root hubs, * or otherwise through transaction translators (TTs) in USB 2.0 hubs using * (b) special fields in qh entries or (c) split iso entries.  TTs will * buffer low/full speed data so the host collects it at high speed. *//*-------------------------------------------------------------------------*//* fill a qtd, returning how much of the buffer we were able to queue up */static intqtd_fill (struct ehci_qtd *qtd, dma_addr_t buf, size_t len,		int token, int maxpacket){	int	i, count;	u64	addr = buf;	/* one buffer entry per 4K ... first might be short or unaligned */	qtd->hw_buf [0] = cpu_to_le32 ((u32)addr);	qtd->hw_buf_hi [0] = cpu_to_le32 ((u32)(addr >> 32));	count = 0x1000 - (buf & 0x0fff);	/* rest of that page */	if (likely (len < count))		/* ... iff needed */		count = len;	else {		buf +=  0x1000;		buf &= ~0x0fff;		/* per-qtd limit: from 16K to 20K (best alignment) */		for (i = 1; count < len && i < 5; i++) {			addr = buf;			qtd->hw_buf [i] = cpu_to_le32 ((u32)addr);			qtd->hw_buf_hi [i] = cpu_to_le32 ((u32)(addr >> 32));			buf += 0x1000;			if ((count + 0x1000) < len)				count += 0x1000;			else				count = len;		}		/* short packets may only terminate transfers */		if (count != len)			count -= (count % maxpacket);	}	qtd->hw_token = cpu_to_le32 ((count << 16) | token);	qtd->length = count;	return count;}/*-------------------------------------------------------------------------*//* update halted (but potentially linked) qh */static inline voidqh_update (struct ehci_hcd *ehci, struct ehci_qh *qh, struct ehci_qtd *qtd){	qh->hw_current = 0;	qh->hw_qtd_next = QTD_NEXT (qtd->qtd_dma);	qh->hw_alt_next = EHCI_LIST_END;	/* HC must see latest qtd and qh data before we clear ACTIVE+HALT */	wmb ();	qh->hw_token &= __constant_cpu_to_le32 (QTD_TOGGLE | QTD_STS_PING);}/*-------------------------------------------------------------------------*/#define IS_SHORT_READ(token) (QTD_LENGTH (token) != 0 && QTD_PID (token) == 1)static void qtd_copy_status (	struct ehci_hcd *ehci,	struct urb *urb,	size_t length,	u32 token){	/* count IN/OUT bytes, not SETUP (even short packets) */	if (likely (QTD_PID (token) != 2))		urb->actual_length += length - QTD_LENGTH (token);	/* don't modify error codes */	if (unlikely (urb->status != -EINPROGRESS))		return;	/* force cleanup after short read; not always an error */	if (unlikely (IS_SHORT_READ (token)))		urb->status = -EREMOTEIO;	/* serious "can't proceed" faults reported by the hardware */	if (token & QTD_STS_HALT) {		if (token & QTD_STS_BABBLE) {			/* FIXME "must" disable babbling device's port too */			urb->status = -EOVERFLOW;		} else if (token & QTD_STS_MMF) {			/* fs/ls interrupt xfer missed the complete-split */			urb->status = -EPROTO;		} else if (token & QTD_STS_DBE) {			urb->status = (QTD_PID (token) == 1) /* IN ? */				? -ENOSR  /* hc couldn't read data */				: -ECOMM; /* hc couldn't write data */		} else if (token & QTD_STS_XACT) {			/* timeout, bad crc, wrong PID, etc; retried */			if (QTD_CERR (token))				urb->status = -EPIPE;			else {				ehci_dbg (ehci, "devpath %s ep%d%s 3strikes\n",					urb->dev->devpath,					usb_pipeendpoint (urb->pipe),					usb_pipein (urb->pipe) ? "in" : "out");				urb->status = -EPROTO;			}		/* CERR nonzero + no errors + halt --> stall */		} else if (QTD_CERR (token))			urb->status = -EPIPE;		else	/* unknown */			urb->status = -EPROTO;		ehci_vdbg (ehci,			"dev%d ep%d%s qtd token %08x --> status %d\n",			usb_pipedevice (urb->pipe),			usb_pipeendpoint (urb->pipe),			usb_pipein (urb->pipe) ? "in" : "out",			token, urb->status);		/* stall indicates some recovery action is needed */		if (urb->status == -EPIPE) {			int	pipe = urb->pipe;			if (!usb_pipecontrol (pipe))				usb_endpoint_halt (urb->dev,					usb_pipeendpoint (pipe),					usb_pipeout (pipe));			if (urb->dev->tt && !usb_pipeint (pipe)) {#ifdef DEBUG				struct usb_device *tt = urb->dev->tt->hub;				dbg ("clear tt %s-%s p%d buffer, a%d ep%d",					tt->bus->bus_name, tt->devpath,    					urb->dev->ttport, urb->dev->devnum,    					usb_pipeendpoint (pipe));#endif /* DEBUG */				usb_hub_tt_clear_buffer (urb->dev, pipe);			}		}	}}static voidehci_urb_done (struct ehci_hcd *ehci, struct urb *urb, struct pt_regs *regs){#ifdef	INTR_AUTOMAGIC	struct urb		*resubmit = 0;	struct usb_device	*dev = 0;	static int ehci_urb_enqueue (struct usb_hcd *, struct urb *, int);#endif	if (likely (urb->hcpriv != 0)) {		struct ehci_qh	*qh = (struct ehci_qh *) urb->hcpriv;		/* S-mask in a QH means it's an interrupt urb */		if ((qh->hw_info2 & cpu_to_le32 (0x00ff)) != 0) {			/* ... update hc-wide periodic stats (for usbfs) */			hcd_to_bus (&ehci->hcd)->bandwidth_int_reqs--;#ifdef	INTR_AUTOMAGIC			if (!((urb->status == -ENOENT)					|| (urb->status == -ECONNRESET))) {				resubmit = usb_get_urb (urb);				dev = urb->dev;			}#endif		}		qh_put (ehci, qh);	}	spin_lock (&urb->lock);	urb->hcpriv = 0;	switch (urb->status) {	case -EINPROGRESS:		/* success */		urb->status = 0;	default:			/* fault */		COUNT (ehci->stats.complete);		break;	case -EREMOTEIO:		/* fault or normal */		if (!(urb->transfer_flags & URB_SHORT_NOT_OK))			urb->status = 0;		COUNT (ehci->stats.complete);		break;	case -ECONNRESET:		/* canceled */	case -ENOENT:		COUNT (ehci->stats.unlink);		break;	}	spin_unlock (&urb->lock);	/* complete() can reenter this HCD */	spin_unlock (&ehci->lock);	usb_hcd_giveback_urb (&ehci->hcd, urb, regs);#ifdef	INTR_AUTOMAGIC	if (resubmit && ((urb->status == -ENOENT)				|| (urb->status == -ECONNRESET))) {		usb_put_urb (resubmit);		resubmit = 0;	}	// device drivers will soon be doing something like this	if (resubmit) {		int	status;		resubmit->dev = dev;		status = SUBMIT_URB (resubmit, SLAB_ATOMIC);		if (status != 0)			err ("can't resubmit interrupt urb %p: status %d",					resubmit, status);		usb_put_urb (resubmit);	}#endif	spin_lock (&ehci->lock);}/* * Process and free completed qtds for a qh, returning URBs to drivers. * Chases up to qh->hw_current.  Returns number of completions called, * indicating how much "real" work we did. */#define HALT_BIT cpu_to_le32(QTD_STS_HALT)static unsignedqh_completions (struct ehci_hcd *ehci, struct ehci_qh *qh, struct pt_regs *regs){	struct ehci_qtd		*last = 0, *end = qh->dummy;	struct list_head	*entry, *tmp;	int			stopped;	unsigned		count = 0;	int			do_status = 0;	u8			state;	if (unlikely (list_empty (&qh->qtd_list)))		return count;	/* completions (or tasks on other cpus) must never clobber HALT	 * till we've gone through and cleaned everything up, even when	 * they add urbs to this qh's queue or mark them for unlinking.	 *	 * NOTE:  unlinking expects to be done in queue order.	 */	state = qh->qh_state;	qh->qh_state = QH_STATE_COMPLETING;	stopped = (state == QH_STATE_IDLE);	/* remove de-activated QTDs from front of queue.	 * after faults (including short reads), cleanup this urb	 * then let the queue advance.	 * if queue is stopped, handles unlinks.	 */	list_for_each_safe (entry, tmp, &qh->qtd_list) {		struct ehci_qtd	*qtd;		struct urb	*urb;		u32		token = 0;		qtd = list_entry (entry, struct ehci_qtd, qtd_list);		urb = qtd->urb;		/* clean up any state from previous QTD ...*/		if (last) {			if (likely (last->urb != urb)) {				ehci_urb_done (ehci, last->urb, regs);				count++;			}			ehci_qtd_free (ehci, last);			last = 0;		}		/* ignore urbs submitted during completions we reported */		if (qtd == end)			break;		/* hardware copies qtd out of qh overlay */		rmb ();		token = le32_to_cpu (qtd->hw_token);		stopped = stopped			|| (HALT_BIT & qh->hw_token) != 0			|| (ehci->hcd.state == USB_STATE_HALT);		/* always clean up qtds the hc de-activated */		if ((token & QTD_STS_ACTIVE) == 0) {			/* magic dummy for short reads; won't advance */			if (IS_SHORT_READ (token)					&& !(token & QTD_STS_HALT)					&& (qh->hw_alt_next & QTD_MASK)						== ehci->async->hw_alt_next) {				stopped = 1;				goto halt;			}		/* stop scanning when we reach qtds the hc is using */		} else if (likely (!stopped)) {			break;		} else {			/* ignore active urbs unless some previous qtd			 * for the urb faulted (including short read) or			 * its urb was canceled.  we may patch qh or qtds.			 */			if (likely (urb->status == -EINPROGRESS))				continue;						/* issue status after short control reads */			if (unlikely (do_status != 0)					&& QTD_PID (token) == 0 /* OUT */) {				do_status = 0;				continue;			}			/* token in overlay may be most current */			if (state == QH_STATE_IDLE					&& cpu_to_le32 (qtd->qtd_dma)						== qh->hw_current)				token = le32_to_cpu (qh->hw_token);			/* force halt for unlinked or blocked qh, so we'll			 * patch the qh later and so that completions can't			 * activate it while we "know" it's stopped.			 */			if ((HALT_BIT & qh->hw_token) == 0) {halt:				qh->hw_token |= HALT_BIT;				wmb ();			}		} 		/* remove it from the queue */		spin_lock (&urb->lock);		qtd_copy_status (ehci, urb, qtd->length, token);		do_status = (urb->status == -EREMOTEIO)				&& usb_pipecontrol (urb->pipe);		spin_unlock (&urb->lock);		if (stopped && qtd->qtd_list.prev != &qh->qtd_list) {			last = list_entry (qtd->qtd_list.prev,					struct ehci_qtd, qtd_list);			last->hw_next = qtd->hw_next;		}		list_del (&qtd->qtd_list);		last = qtd;	}	/* last urb's completion might still need calling */	if (likely (last != 0)) {		ehci_urb_done (ehci, last->urb, regs);		count++;		ehci_qtd_free (ehci, last);	}	/* restore original state; caller must unlink or relink */	qh->qh_state = state;	/* update qh after fault cleanup */	if (unlikely ((HALT_BIT & qh->hw_token) != 0)) {		qh_update (ehci, qh,			list_empty (&qh->qtd_list)				? qh->dummy				: list_entry (qh->qtd_list.next,					struct ehci_qtd, qtd_list));	}	return count;}#undef HALT_BIT/*-------------------------------------------------------------------------*//* * reverse of qh_urb_transaction:  free a list of TDs. * used for cleanup after errors, before HC sees an URB's TDs. */static void qtd_list_free (	struct ehci_hcd		*ehci,	struct urb		*urb,	struct list_head	*qtd_list) {	struct list_head	*entry, *temp;	list_for_each_safe (entry, temp, qtd_list) {		struct ehci_qtd	*qtd;		qtd = list_entry (entry, struct ehci_qtd, qtd_list);		list_del (&qtd->qtd_list);		ehci_qtd_free (ehci, qtd);	}}/* * create a list of filled qtds for this URB; won't link into qh. */static struct list_head *qh_urb_transaction (	struct ehci_hcd		*ehci,	struct urb		*urb,	struct list_head	*head,	int			flags) {	struct ehci_qtd		*qtd, *qtd_prev;	dma_addr_t		buf;	int			len, maxpacket;	int			is_input;	u32			token;	/*	 * URBs map to sequences of QTDs:  one logical transaction	 */	qtd = ehci_qtd_alloc (ehci, flags);	if (unlikely (!qtd))		return 0;	list_add_tail (&qtd->qtd_list, head);	qtd->urb = urb;	token = QTD_STS_ACTIVE;	token |= (EHCI_TUNE_CERR << 10);	/* for split transactions, SplitXState initialized to zero */	len = urb->transfer_buffer_length;	is_input = usb_pipein (urb->pipe);	if (usb_pipecontrol (urb->pipe)) {		/* SETUP pid */		qtd_fill (qtd, urb->setup_dma, sizeof (struct usb_ctrlrequest),			token | (2 /* "setup" */ << 8), 8);		/* ... and always at least one more pid */		token ^= QTD_TOGGLE;		qtd_prev = qtd;		qtd = ehci_qtd_alloc (ehci, flags);		if (unlikely (!qtd))			goto cleanup;		qtd->urb = urb;		qtd_prev->hw_next = QTD_NEXT (qtd->qtd_dma);		list_add_tail (&qtd->qtd_list, head);	} 	/*	 * data transfer stage:  buffer setup	 */	if (likely (len > 0))		buf = urb->transfer_dma;	else		buf = 0;	// FIXME this 'buf' check break some zlps...	if (!buf || is_input)		token |= (1 /* "in" */ << 8);	/* else it's already initted to "out" pid (0 << 8) */	maxpacket = usb_maxpacket (urb->dev, urb->pipe, !is_input) & 0x03ff;	/*	 * buffer gets wrapped in one or more qtds;	 * last one may be "short" (including zero len)	 * and may serve as a control status ack	 */	for (;;) {		int this_qtd_len;		this_qtd_len = qtd_fill (qtd, buf, len, token, maxpacket);		len -= this_qtd_len;		buf += this_qtd_len;		if (is_input)			qtd->hw_alt_next = ehci->async->hw_alt_next;		/* qh makes control packets use qtd toggle; maybe switch it */		if ((maxpacket & (this_qtd_len + (maxpacket - 1))) == 0)			token ^= QTD_TOGGLE;		if (likely (len <= 0))			break;		qtd_prev = qtd;		qtd = ehci_qtd_alloc (ehci, flags);		if (unlikely (!qtd))			goto cleanup;		qtd->urb = urb;		qtd_prev->hw_next = QTD_NEXT (qtd->qtd_dma);		list_add_tail (&qtd->qtd_list, head);	}	/* unless the bulk/interrupt caller wants a chance to clean	 * up after short reads, hc should advance qh past this urb	 */	if (likely ((urb->transfer_flags & URB_SHORT_NOT_OK) == 0				|| usb_pipecontrol (urb->pipe)))		qtd->hw_alt_next = EHCI_LIST_END;	/*	 * control requests may need a terminating data "status" ack;	 * bulk ones may need a terminating short packet (zero length).	 */	if (likely (buf != 0)) {		int	one_more = 0;		if (usb_pipecontrol (urb->pipe)) {			one_more = 1;			token ^= 0x0100;	/* "in" <--> "out"  */			token |= QTD_TOGGLE;	/* force DATA1 */		} else if (usb_pipebulk (urb->pipe)				&& (urb->transfer_flags & URB_ZERO_PACKET)				&& !(urb->transfer_buffer_length % maxpacket)) {			one_more = 1;		}		if (one_more) {			qtd_prev = qtd;			qtd = ehci_qtd_alloc (ehci, flags);			if (unlikely (!qtd))				goto cleanup;			qtd->urb = urb;			qtd_prev->hw_next = QTD_NEXT (qtd->qtd_dma);			list_add_tail (&qtd->qtd_list, head);			/* never any data in such packets */

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -