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

📄 uhci.c

📁 Linux内核源代码 为压缩文件 是<<Linux内核>>一书中的源代码
💻 C
📖 第 1 页 / 共 4 页
字号:
/* * Universal Host Controller Interface driver for USB. * * (C) Copyright 1999 Linus Torvalds * (C) Copyright 1999-2000 Johannes Erdfelt, johannes@erdfelt.com * (C) Copyright 1999 Randy Dunlap * (C) Copyright 1999 Georg Acher, acher@in.tum.de * (C) Copyright 1999 Deti Fliegl, deti@fliegl.de * (C) Copyright 1999 Thomas Sailer, sailer@ife.ee.ethz.ch * (C) Copyright 1999 Roman Weissgaerber, weissg@vienna.at * (C) Copyright 2000 Yggdrasil Computing, Inc. (port of new PCI interface *               support from usb-ohci.c by Adam Richter, adam@yggdrasil.com). * (C) Copyright 1999 Gregory P. Smith (from usb-ohci.c) * * * Intel documents this fairly well, and as far as I know there * are no royalties or anything like that, but even so there are * people who decided that they want to do the same thing in a * completely different way. * * WARNING! The USB documentation is downright evil. Most of it * is just crap, written by a committee. You're better off ignoring * most of it, the important stuff is: *  - the low-level protocol (fairly simple but lots of small details) *  - working around the horridness of the rest */#include <linux/config.h>#include <linux/module.h>#include <linux/pci.h>#include <linux/kernel.h>#include <linux/init.h>#include <linux/delay.h>#include <linux/ioport.h>#include <linux/sched.h>#include <linux/malloc.h>#include <linux/smp_lock.h>#include <linux/errno.h>#include <linux/unistd.h>#include <linux/interrupt.h>#include <linux/spinlock.h>#define DEBUG#include <linux/usb.h>#include <asm/uaccess.h>#include <asm/io.h>#include <asm/irq.h>#include <asm/system.h>#include "uhci.h"#include "uhci-debug.h"#include <linux/pm.h>static int debug = 1;MODULE_PARM(debug, "i");MODULE_PARM_DESC(debug, "Debug level");static kmem_cache_t *uhci_td_cachep;static kmem_cache_t *uhci_qh_cachep;static kmem_cache_t *uhci_up_cachep;	/* urb_priv */static int rh_submit_urb(struct urb *urb);static int rh_unlink_urb(struct urb *urb);static int uhci_get_current_frame_number(struct usb_device *dev);static int uhci_unlink_generic(struct urb *urb);static int uhci_unlink_urb(struct urb *urb);#define min(a,b) (((a)<(b))?(a):(b))/* If a transfer is still active after this much time, turn off FSBR */#define IDLE_TIMEOUT	(HZ / 20)	/* 50 ms *//* * Only the USB core should call uhci_alloc_dev and uhci_free_dev */static int uhci_alloc_dev(struct usb_device *dev){	return 0;}static int uhci_free_dev(struct usb_device *dev){	struct uhci *uhci = (struct uhci *)dev->bus->hcpriv;	struct list_head *tmp, *head = &uhci->urb_list;	unsigned long flags;	/* Walk through the entire URB list and forcefully remove any */	/*  URBs that are still active for that device */	nested_lock(&uhci->urblist_lock, flags);	tmp = head->next;	while (tmp != head) {		struct urb *u = list_entry(tmp, struct urb, urb_list);		tmp = tmp->next;		if (u->dev == dev)			uhci_unlink_urb(u);	}	nested_unlock(&uhci->urblist_lock, flags);	return 0;}static void uhci_add_urb_list(struct uhci *uhci, struct urb *urb){	unsigned long flags;	nested_lock(&uhci->urblist_lock, flags);	list_add(&urb->urb_list, &uhci->urb_list);	nested_unlock(&uhci->urblist_lock, flags);}static void uhci_remove_urb_list(struct uhci *uhci, struct urb *urb){	unsigned long flags;	nested_lock(&uhci->urblist_lock, flags);	if (!list_empty(&urb->urb_list)) {		list_del(&urb->urb_list);		INIT_LIST_HEAD(&urb->urb_list);	}	nested_unlock(&uhci->urblist_lock, flags);}void uhci_set_next_interrupt(struct uhci *uhci){	unsigned long flags;	spin_lock_irqsave(&uhci->framelist_lock, flags);	uhci->skel_term_td.status |= TD_CTRL_IOC;	spin_unlock_irqrestore(&uhci->framelist_lock, flags);}void uhci_clear_next_interrupt(struct uhci *uhci){	unsigned long flags;	spin_lock_irqsave(&uhci->framelist_lock, flags);	uhci->skel_term_td.status &= ~TD_CTRL_IOC;	spin_unlock_irqrestore(&uhci->framelist_lock, flags);}static struct uhci_td *uhci_alloc_td(struct usb_device *dev){	struct uhci_td *td;	td = kmem_cache_alloc(uhci_td_cachep, in_interrupt() ? SLAB_ATOMIC : SLAB_KERNEL);	if (!td)		return NULL;	td->link = UHCI_PTR_TERM;	td->buffer = 0;	td->frameptr = NULL;	td->nexttd = td->prevtd = NULL;	td->dev = dev;	INIT_LIST_HEAD(&td->list);	usb_inc_dev_use(dev);	return td;}static void inline uhci_fill_td(struct uhci_td *td, __u32 status,		__u32 info, __u32 buffer){	td->status = status;	td->info = info;	td->buffer = buffer;}static void uhci_insert_td(struct uhci *uhci, struct uhci_td *skeltd, struct uhci_td *td){	unsigned long flags;	spin_lock_irqsave(&uhci->framelist_lock, flags);	/* Fix the linked list pointers */	td->nexttd = skeltd->nexttd;	td->prevtd = skeltd;	if (skeltd->nexttd)		skeltd->nexttd->prevtd = td;	skeltd->nexttd = td;	td->link = skeltd->link;	skeltd->link = virt_to_bus(td);	spin_unlock_irqrestore(&uhci->framelist_lock, flags);}/* * We insert Isochronous transfers directly into the frame list at the * beginning * The layout looks as follows: * frame list pointer -> iso td's (if any) -> * periodic interrupt td (if frame 0) -> irq td's -> control qh -> bulk qh */static void uhci_insert_td_frame_list(struct uhci *uhci, struct uhci_td *td, unsigned framenum){	unsigned long flags;	struct uhci_td *nexttd;	framenum %= UHCI_NUMFRAMES;	spin_lock_irqsave(&uhci->framelist_lock, flags);	td->frameptr = &uhci->fl->frame[framenum];	td->link = uhci->fl->frame[framenum];	if (!(td->link & (UHCI_PTR_TERM | UHCI_PTR_QH))) {		nexttd = (struct uhci_td *)uhci_ptr_to_virt(td->link);		td->nexttd = nexttd;		nexttd->prevtd = td;		nexttd->frameptr = NULL;	}	uhci->fl->frame[framenum] = virt_to_bus(td);	spin_unlock_irqrestore(&uhci->framelist_lock, flags);}static void uhci_remove_td(struct uhci *uhci, struct uhci_td *td){	unsigned long flags;	/* If it's not inserted, don't remove it */	if (!td->frameptr && !td->prevtd && !td->nexttd)		return;	spin_lock_irqsave(&uhci->framelist_lock, flags);	if (td->frameptr) {		*(td->frameptr) = td->link;		if (td->nexttd) {			td->nexttd->frameptr = td->frameptr;			td->nexttd->prevtd = NULL;			td->nexttd = NULL;		}		td->frameptr = NULL;	} else {		if (td->prevtd) {			td->prevtd->nexttd = td->nexttd;			td->prevtd->link = td->link;		}		if (td->nexttd)			td->nexttd->prevtd = td->prevtd;		td->prevtd = td->nexttd = NULL;	}	td->link = UHCI_PTR_TERM;	spin_unlock_irqrestore(&uhci->framelist_lock, flags);}/* * Inserts a td into qh list at the top. */static void uhci_insert_tds_in_qh(struct uhci_qh *qh, struct urb *urb, int breadth){	struct list_head *tmp, *head;	struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv;	struct uhci_td *td, *prevtd;	if (!urbp)		return;	head = &urbp->list;	tmp = head->next;	if (head == tmp)		return;	td = list_entry(tmp, struct uhci_td, list);	/* Add the first TD to the QH element pointer */	qh->element = virt_to_bus(td) | (breadth ? 0 : UHCI_PTR_DEPTH);	prevtd = td;	/* Then link the rest of the TD's */	tmp = tmp->next;	while (tmp != head) {		td = list_entry(tmp, struct uhci_td, list);		tmp = tmp->next;		prevtd->link = virt_to_bus(td) | (breadth ? 0 : UHCI_PTR_DEPTH);		prevtd = td;	}	prevtd->link = UHCI_PTR_TERM;}static void uhci_free_td(struct uhci_td *td){	if (!list_empty(&td->list))		dbg("td is still in URB list!");	if (td->dev)		usb_dec_dev_use(td->dev);	kmem_cache_free(uhci_td_cachep, td);}static struct uhci_qh *uhci_alloc_qh(struct usb_device *dev){	struct uhci_qh *qh;	qh = kmem_cache_alloc(uhci_qh_cachep, in_interrupt() ? SLAB_ATOMIC : SLAB_KERNEL);	if (!qh)		return NULL;	qh->element = UHCI_PTR_TERM;	qh->link = UHCI_PTR_TERM;	qh->dev = dev;	qh->prevqh = qh->nextqh = NULL;	INIT_LIST_HEAD(&qh->remove_list);	usb_inc_dev_use(dev);	return qh;}static void uhci_free_qh(struct uhci_qh *qh){	if (qh->dev)		usb_dec_dev_use(qh->dev);	kmem_cache_free(uhci_qh_cachep, qh);}static void uhci_insert_qh(struct uhci *uhci, struct uhci_qh *skelqh, struct uhci_qh *qh){	unsigned long flags;	spin_lock_irqsave(&uhci->framelist_lock, flags);	/* Fix the linked list pointers */	qh->nextqh = skelqh->nextqh;	qh->prevqh = skelqh;	if (skelqh->nextqh)		skelqh->nextqh->prevqh = qh;	skelqh->nextqh = qh;	qh->link = skelqh->link;	skelqh->link = virt_to_bus(qh) | UHCI_PTR_QH;	spin_unlock_irqrestore(&uhci->framelist_lock, flags);}static void uhci_remove_qh(struct uhci *uhci, struct uhci_qh *qh){	unsigned long flags;	int delayed;	/* If the QH isn't queued, then we don't need to delay unlink it */	delayed = (qh->prevqh || qh->nextqh);	spin_lock_irqsave(&uhci->framelist_lock, flags);	if (qh->prevqh) {		qh->prevqh->nextqh = qh->nextqh;		qh->prevqh->link = qh->link;	}	if (qh->nextqh)		qh->nextqh->prevqh = qh->prevqh;	qh->prevqh = qh->nextqh = NULL;	qh->element = qh->link = UHCI_PTR_TERM;	spin_unlock_irqrestore(&uhci->framelist_lock, flags);	if (delayed) {		spin_lock_irqsave(&uhci->qh_remove_lock, flags);		/* Check to see if the remove list is empty */		/* Set the IOC bit to force an interrupt so we can remove the QH */		if (list_empty(&uhci->qh_remove_list))			uhci_set_next_interrupt(uhci);		/* Add it */		list_add(&qh->remove_list, &uhci->qh_remove_list);		spin_unlock_irqrestore(&uhci->qh_remove_lock, flags);	} else		uhci_free_qh(qh);}static spinlock_t uhci_append_urb_lock = SPIN_LOCK_UNLOCKED;/* This function will append one URB's QH to another URB's QH. This is for *//*  USB_QUEUE_BULK support */static void uhci_append_queued_urb(struct uhci *uhci, struct urb *eurb, struct urb *urb){	struct urb_priv *eurbp, *urbp, *furbp, *lurbp;	struct list_head *tmp;	struct uhci_td *td, *ltd;	unsigned long flags;	eurbp = eurb->hcpriv;	urbp = urb->hcpriv;	spin_lock_irqsave(&uhci_append_urb_lock, flags);	/* Find the beginning URB in the queue */	if (eurbp->queued) {		struct list_head *head = &eurbp->urb_queue_list;		tmp = head->next;		while (tmp != head) {			struct urb_priv *turbp =				list_entry(tmp, struct urb_priv, urb_queue_list);			tmp = tmp->next;			if (!turbp->queued)				break;		}	} else		tmp = &eurbp->urb_queue_list;	furbp = list_entry(tmp, struct urb_priv, urb_queue_list);	tmp = furbp->urb_queue_list.prev;	lurbp = list_entry(tmp, struct urb_priv, urb_queue_list);	/* Add this one to the end */	list_add_tail(&urbp->urb_queue_list, &furbp->urb_queue_list);	/* Grab the last TD from the last URB */	ltd = list_entry(lurbp->list.prev, struct uhci_td, list);	/* Grab the first TD from the first URB */	td = list_entry(urbp->list.next, struct uhci_td, list);	/* No breadth since this will only be called for bulk transfers */	ltd->link = virt_to_bus(td);	spin_unlock_irqrestore(&uhci_append_urb_lock, flags);}static void uhci_delete_queued_urb(struct uhci *uhci, struct urb *urb){	struct urb_priv *urbp, *nurbp;	unsigned long flags;	urbp = urb->hcpriv;	spin_lock_irqsave(&uhci_append_urb_lock, flags);	nurbp = list_entry(urbp->urb_queue_list.next, struct urb_priv,			urb_queue_list);	if (!urbp->queued) {		/* We're the head, so just insert the QH for the next URB */		uhci_insert_qh(uhci, &uhci->skel_bulk_qh, nurbp->qh);		nurbp->queued = 0;	} else {		struct urb_priv *purbp;		struct uhci_td *ptd;		/* We're somewhere in the middle (or end). A bit trickier */		/*  than the head scenario */		purbp = list_entry(urbp->urb_queue_list.prev, struct urb_priv,				urb_queue_list);		ptd = list_entry(purbp->list.prev, struct uhci_td, list);		if (nurbp->queued)			/* Close the gap between the two */			ptd->link = virt_to_bus(list_entry(nurbp->list.next,					struct uhci_td, list));		else			/* The next URB happens to be the beggining, so */			/*  we're the last, end the chain */			ptd->link = UHCI_PTR_TERM;			}	list_del(&urbp->urb_queue_list);	spin_unlock_irqrestore(&uhci_append_urb_lock, flags);}struct urb_priv *uhci_alloc_urb_priv(struct urb *urb){	struct urb_priv *urbp;	urbp = kmem_cache_alloc(uhci_up_cachep, in_interrupt() ? SLAB_ATOMIC : SLAB_KERNEL);	if (!urbp)		return NULL;	memset((void *)urbp, 0, sizeof(*urbp));	urbp->inserttime = jiffies;	urbp->urb = urb;		INIT_LIST_HEAD(&urbp->list);	INIT_LIST_HEAD(&urbp->urb_queue_list);	urb->hcpriv = urbp;	return urbp;}static void uhci_add_td_to_urb(struct urb *urb, struct uhci_td *td){	struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv;	td->urb = urb;	list_add_tail(&td->list, &urbp->list);}static void uhci_remove_td_from_urb(struct urb *urb, struct uhci_td *td){	urb = NULL;	/* No warnings */	if (list_empty(&td->list))		return;	list_del(&td->list);	INIT_LIST_HEAD(&td->list);	td->urb = NULL;}static void uhci_destroy_urb_priv(struct urb *urb){	struct list_head *tmp, *head;	struct urb_priv *urbp;	struct uhci *uhci;	struct uhci_td *td;	unsigned long flags;	spin_lock_irqsave(&urb->lock, flags);	urbp = (struct urb_priv *)urb->hcpriv;	if (!urbp)		goto unlock;	if (!urb->dev || !urb->dev->bus || !urb->dev->bus->hcpriv)		goto unlock;	uhci = urb->dev->bus->hcpriv;	head = &urbp->list;	tmp = head->next;	while (tmp != head) {		td = list_entry(tmp, struct uhci_td, list);		tmp = tmp->next;		uhci_remove_td_from_urb(urb, td);		uhci_remove_td(uhci, td);		uhci_free_td(td);	}	urb->hcpriv = NULL;	kmem_cache_free(uhci_up_cachep, urbp);unlock:	spin_unlock_irqrestore(&urb->lock, flags);}static void uhci_inc_fsbr(struct uhci *uhci, struct urb *urb){	unsigned long flags;	struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv;	if (!urbp)		return;	spin_lock_irqsave(&uhci->framelist_lock, flags);	if ((!(urb->transfer_flags & USB_NO_FSBR)) && (!urbp->fsbr)) {		urbp->fsbr = 1;		if (!uhci->fsbr++)			uhci->skel_term_qh.link = virt_to_bus(&uhci->skel_hs_control_qh) | UHCI_PTR_QH;	}	spin_unlock_irqrestore(&uhci->framelist_lock, flags);}static void uhci_dec_fsbr(struct uhci *uhci, struct urb *urb){	unsigned long flags;	struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv;	if (!urbp)		return;	spin_lock_irqsave(&uhci->framelist_lock, flags);	if ((!(urb->transfer_flags & USB_NO_FSBR)) && urbp->fsbr) {		urbp->fsbr = 0;		if (!--uhci->fsbr)			uhci->skel_term_qh.link = UHCI_PTR_TERM;	}	spin_unlock_irqrestore(&uhci->framelist_lock, flags);}/* * Map status to standard result codes * * <status> is (td->status & 0xFE0000) [a.k.a. uhci_status_bits(td->status)] * <dir_out> is True for output TDs and False for input TDs. */static int uhci_map_status(int status, int dir_out){	if (!status)		return 0;	if (status & TD_CTRL_BITSTUFF)			/* Bitstuff error */		return -EPROTO;	if (status & TD_CTRL_CRCTIMEO) {		/* CRC/Timeout */		if (dir_out)			return -ETIMEDOUT;		else			return -EILSEQ;	}	if (status & TD_CTRL_NAK)			/* NAK */		return -ETIMEDOUT;	if (status & TD_CTRL_BABBLE)			/* Babble */		return -EPIPE;	if (status & TD_CTRL_DBUFERR)			/* Buffer error */		return -ENOSR;	if (status & TD_CTRL_STALLED)			/* Stalled */		return -EPIPE;	if (status & TD_CTRL_ACTIVE)			/* Active */		return 0;	return -EINVAL;}/* * Control transfers */static int uhci_submit_control(struct urb *urb)

⌨️ 快捷键说明

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