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

📄 uhci.c

📁 ep9315平台下USB驱动的源码
💻 C
📖 第 1 页 / 共 5 页
字号:
/* * Universal Host Controller Interface driver for USB. * * Maintainer: Johannes Erdfelt <johannes@erdfelt.com> * * (C) Copyright 1999 Linus Torvalds * (C) Copyright 1999-2002 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/slab.h>#include <linux/smp_lock.h>#include <linux/errno.h>#include <linux/unistd.h>#include <linux/interrupt.h>#include <linux/spinlock.h>#include <linux/proc_fs.h>#ifdef CONFIG_USB_DEBUG#define DEBUG#else#undef DEBUG#endif#include <linux/usb.h>#include <asm/uaccess.h>#include <asm/io.h>#include <asm/irq.h>#include <asm/system.h>#include "uhci.h"#include <linux/pm.h>#include "../hcd.h"/* * Version Information */#define DRIVER_VERSION "v1.1"#define DRIVER_AUTHOR "Linus 'Frodo Rabbit' Torvalds, Johannes Erdfelt, Randy Dunlap, Georg Acher, Deti Fliegl, Thomas Sailer, Roman Weissgaerber"#define DRIVER_DESC "USB Universal Host Controller Interface driver"/* * debug = 0, no debugging messages * debug = 1, dump failed URB's except for stalls * debug = 2, dump all failed URB's (including stalls) *            show all queues in /proc/uhci/hc* * debug = 3, show all TD's in URB's when dumping */#ifdef DEBUGstatic int debug = 1;#elsestatic int debug = 0;#endifMODULE_PARM(debug, "i");MODULE_PARM_DESC(debug, "Debug level");static char *errbuf;#define ERRBUF_LEN    (PAGE_SIZE * 8)#include "uhci-debug.h"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_urb(struct urb *urb);static void uhci_unlink_generic(struct uhci *uhci, struct urb *urb);static void uhci_call_completion(struct urb *urb);static int  ports_active(struct uhci *uhci);static void suspend_hc(struct uhci *uhci);static void wakeup_hc(struct uhci *uhci);/* If a transfer is still active after this much time, turn off FSBR */#define IDLE_TIMEOUT	(HZ / 20)	/* 50 ms */#define FSBR_DELAY	(HZ / 20)	/* 50 ms *//* 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 TD's *//* to make sure it doesn't hog all of the bandwidth */#define DEPTH_INTERVAL	5#define MAX_URB_LOOP	2048		/* Maximum number of linked URB's *//* * 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){	return 0;}/* * Technically, updating td->status here is a race, but it's not really a * problem. The worst that can happen is that we set the IOC bit again * generating a spurios interrupt. We could fix this by creating another * QH and leaving the IOC bit always set, but then we would have to play * games with the FSBR code to make sure we get the correct order in all * the cases. I don't think it's worth the effort */static inline void uhci_set_next_interrupt(struct uhci *uhci){	unsigned long flags;	spin_lock_irqsave(&uhci->frame_list_lock, flags);	uhci->skel_term_td->status |= TD_CTRL_IOC;	spin_unlock_irqrestore(&uhci->frame_list_lock, flags);}static inline void uhci_clear_next_interrupt(struct uhci *uhci){	unsigned long flags;	spin_lock_irqsave(&uhci->frame_list_lock, flags);	uhci->skel_term_td->status &= ~TD_CTRL_IOC;	spin_unlock_irqrestore(&uhci->frame_list_lock, flags);}static inline void uhci_add_complete(struct urb *urb){	struct uhci *uhci = (struct uhci *)urb->dev->bus->hcpriv;	struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv;	unsigned long flags;	spin_lock_irqsave(&uhci->complete_list_lock, flags);	list_add_tail(&urbp->complete_list, &uhci->complete_list);	spin_unlock_irqrestore(&uhci->complete_list_lock, flags);}static struct uhci_td *uhci_alloc_td(struct uhci *uhci, struct usb_device *dev){	dma_addr_t dma_handle;	struct uhci_td *td;	td = pci_pool_alloc(uhci->td_pool, GFP_DMA | GFP_ATOMIC, &dma_handle);	if (!td)		return NULL;	td->dma_handle = dma_handle;	td->link = UHCI_PTR_TERM;	td->buffer = 0;	td->frame = -1;	td->dev = dev;	INIT_LIST_HEAD(&td->list);	INIT_LIST_HEAD(&td->fl_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;	struct uhci_td *ltd;	spin_lock_irqsave(&uhci->frame_list_lock, flags);	ltd = list_entry(skeltd->fl_list.prev, struct uhci_td, fl_list);	td->link = ltd->link;	mb();	ltd->link = td->dma_handle;	list_add_tail(&td->fl_list, &skeltd->fl_list);	spin_unlock_irqrestore(&uhci->frame_list_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;	framenum %= UHCI_NUMFRAMES;	spin_lock_irqsave(&uhci->frame_list_lock, flags);	td->frame = framenum;	/* Is there a TD already mapped there? */	if (uhci->fl->frame_cpu[framenum]) {		struct uhci_td *ftd, *ltd;		ftd = uhci->fl->frame_cpu[framenum];		ltd = list_entry(ftd->fl_list.prev, struct uhci_td, fl_list);		list_add_tail(&td->fl_list, &ftd->fl_list);		td->link = ltd->link;		mb();		ltd->link = td->dma_handle;	} else {		td->link = uhci->fl->frame[framenum];		mb();		uhci->fl->frame[framenum] = td->dma_handle;		uhci->fl->frame_cpu[framenum] = td;	}	spin_unlock_irqrestore(&uhci->frame_list_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 */	spin_lock_irqsave(&uhci->frame_list_lock, flags);	if (td->frame == -1 && list_empty(&td->fl_list))		goto out;	if (td->frame != -1 && uhci->fl->frame_cpu[td->frame] == td) {		if (list_empty(&td->fl_list)) {			uhci->fl->frame[td->frame] = td->link;			uhci->fl->frame_cpu[td->frame] = NULL;		} else {			struct uhci_td *ntd;			ntd = list_entry(td->fl_list.next, struct uhci_td, fl_list);			uhci->fl->frame[td->frame] = ntd->dma_handle;			uhci->fl->frame_cpu[td->frame] = ntd;		}	} else {		struct uhci_td *ptd;		ptd = list_entry(td->fl_list.prev, struct uhci_td, fl_list);		ptd->link = td->link;	}	mb();	td->link = UHCI_PTR_TERM;	list_del_init(&td->fl_list);	td->frame = -1;out:	spin_unlock_irqrestore(&uhci->frame_list_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, *ptd;	if (list_empty(&urbp->td_list))		return;	head = &urbp->td_list;	tmp = head->next;	/* Ordering isn't important here yet since the QH hasn't been */	/*  inserted into the schedule yet */	td = list_entry(tmp, struct uhci_td, list);	/* Add the first TD to the QH element pointer */	qh->element = td->dma_handle | (breadth ? 0 : UHCI_PTR_DEPTH);	ptd = 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;		ptd->link = td->dma_handle | (breadth ? 0 : UHCI_PTR_DEPTH);		ptd = td;	}	ptd->link = UHCI_PTR_TERM;}static void uhci_free_td(struct uhci *uhci, struct uhci_td *td){	if (!list_empty(&td->list) || !list_empty(&td->fl_list))		dbg("td is still in URB list!");	if (td->dev)		usb_dec_dev_use(td->dev);	pci_pool_free(uhci->td_pool, td, td->dma_handle);}static struct uhci_qh *uhci_alloc_qh(struct uhci *uhci, struct usb_device *dev){	dma_addr_t dma_handle;	struct uhci_qh *qh;	qh = pci_pool_alloc(uhci->qh_pool, GFP_DMA | GFP_ATOMIC, &dma_handle);	if (!qh)		return NULL;	qh->dma_handle = dma_handle;	qh->element = UHCI_PTR_TERM;	qh->link = UHCI_PTR_TERM;	qh->dev = dev;	qh->urbp = NULL;	INIT_LIST_HEAD(&qh->list);	INIT_LIST_HEAD(&qh->remove_list);	usb_inc_dev_use(dev);	return qh;}static void uhci_free_qh(struct uhci *uhci, struct uhci_qh *qh){	if (!list_empty(&qh->list))		dbg("qh list not empty!");	if (!list_empty(&qh->remove_list))		dbg("qh still in remove_list!");	if (qh->dev)		usb_dec_dev_use(qh->dev);	pci_pool_free(uhci->qh_pool, qh, qh->dma_handle);}/* * MUST be called with uhci->frame_list_lock acquired */static void _uhci_insert_qh(struct uhci *uhci, struct uhci_qh *skelqh, struct urb *urb){	struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv;	struct list_head *head, *tmp;	struct uhci_qh *lqh;	/* Grab the last QH */	lqh = list_entry(skelqh->list.prev, struct uhci_qh, list);	if (lqh->urbp) {		head = &lqh->urbp->queue_list;		tmp = head->next;		while (head != tmp) {			struct urb_priv *turbp =				list_entry(tmp, struct urb_priv, queue_list);			tmp = tmp->next;			turbp->qh->link = urbp->qh->dma_handle | UHCI_PTR_QH;		}	}	head = &urbp->queue_list;	tmp = head->next;	while (head != tmp) {		struct urb_priv *turbp =			list_entry(tmp, struct urb_priv, queue_list);		tmp = tmp->next;		turbp->qh->link = lqh->link;	}	urbp->qh->link = lqh->link;	mb();				/* Ordering is important */	lqh->link = urbp->qh->dma_handle | UHCI_PTR_QH;	list_add_tail(&urbp->qh->list, &skelqh->list);}static void uhci_insert_qh(struct uhci *uhci, struct uhci_qh *skelqh, struct urb *urb){	unsigned long flags;	spin_lock_irqsave(&uhci->frame_list_lock, flags);	_uhci_insert_qh(uhci, skelqh, urb);	spin_unlock_irqrestore(&uhci->frame_list_lock, flags);}static void uhci_remove_qh(struct uhci *uhci, struct uhci_qh *qh){	unsigned long flags;	struct uhci_qh *pqh;	if (!qh)		return;	qh->urbp = NULL;	/* Only go through the hoops if it's actually linked in */	spin_lock_irqsave(&uhci->frame_list_lock, flags);	if (!list_empty(&qh->list)) {		pqh = list_entry(qh->list.prev, struct uhci_qh, list);		if (pqh->urbp) {			struct list_head *head, *tmp;			head = &pqh->urbp->queue_list;			tmp = head->next;			while (head != tmp) {				struct urb_priv *turbp =					list_entry(tmp, struct urb_priv, queue_list);				tmp = tmp->next;				turbp->qh->link = qh->link;			}		}		pqh->link = qh->link;		mb();		qh->element = qh->link = UHCI_PTR_TERM;		list_del_init(&qh->list);	}	spin_unlock_irqrestore(&uhci->frame_list_lock, flags);	spin_lock_irqsave(&uhci->qh_remove_list_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);	list_add(&qh->remove_list, &uhci->qh_remove_list);	spin_unlock_irqrestore(&uhci->qh_remove_list_lock, flags);}static int uhci_fixup_toggle(struct urb *urb, unsigned int toggle){	struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv;	struct list_head *head, *tmp;	head = &urbp->td_list;	tmp = head->next;	while (head != tmp) {		struct uhci_td *td = list_entry(tmp, struct uhci_td, list);		tmp = tmp->next;		if (toggle)			td->info |= TD_TOKEN_TOGGLE;		else			td->info &= ~TD_TOKEN_TOGGLE;		toggle ^= 1;	}	return toggle;}/* This function will append one URB's QH to another URB's QH. This is for *//*  USB_QUEUE_BULK support for bulk transfers and soon implicitily for *//*  control transfers */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 *lltd;	unsigned long flags;	eurbp = eurb->hcpriv;	urbp = urb->hcpriv;	spin_lock_irqsave(&uhci->frame_list_lock, flags);	/* Find the first URB in the queue */	if (eurbp->queued) {		struct list_head *head = &eurbp->queue_list;		tmp = head->next;		while (tmp != head) {			struct urb_priv *turbp =				list_entry(tmp, struct urb_priv, queue_list);			if (!turbp->queued)				break;			tmp = tmp->next;		}	} else		tmp = &eurbp->queue_list;

⌨️ 快捷键说明

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