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

📄 uhci.c

📁 超小usb协议栈
💻 C
📖 第 1 页 / 共 2 页
字号:
/* * Universal Host Controller Interface driver for USB. * * (C) Copyright 1999 Linus Torvalds * * 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. * * Oh, well. The intel version is the more common by far. As such, * that's the one I care about right now. * * 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 *//* 4/4/1999 added data toggle for interrupt pipes -keryan */#include <linux/config.h>#include <linux/module.h>#include <linux/pci.h>#include <linux/kernel.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 <asm/spinlock.h>#include <asm/io.h>#include <asm/irq.h>#include <asm/system.h>#include "uhci.h"#include "inits.h"#ifdef CONFIG_APM#include <linux/apm_bios.h>static int handle_apm_event(apm_event_t event);static int apm_resume = 0;#endif#define compile_assert(x) do { switch (0) { case 1: case !(x): } } while (0)int usb_mouse_init(void);int hub_init(void);static struct wait_queue *uhci_configure = NULL;/* * Return the result of a TD.. */static int uhci_td_result(struct uhci_device *dev, struct uhci_td *td){	unsigned int status;	status = (td->status >> 16) & 0xff;	/* Some debugging code */	if (status) {		int i = 10;		struct uhci_td *tmp = dev->control_td;		printk("uhci_td_result() failed with status %d\n", status);		show_status(dev->uhci);		do {			show_td(tmp);			tmp++;			if (!--i)				break;		} while (tmp <= td);	}	return status;		}/* * Inserts a td into qh list at the top. * * Careful about atomicity: even on UP this * requires a locked access due to the concurrent * DMA engine. * * NOTE! This assumes that first->last is a valid * list of TD's with the proper backpointers set * up and all.. */static void uhci_insert_tds_in_qh(struct uhci_qh *qh, struct uhci_td *first, struct uhci_td *last){	unsigned int link = qh->element;	unsigned int new = 4 | virt_to_bus(first);	for (;;) {		unsigned char success;		last->link = link;		first->backptr = &qh->element;		asm volatile("lock ; cmpxchg %4,%2 ; sete %0"			:"=q" (success), "=a" (link)			:"m" (qh->element), "1" (link), "r" (new)			:"memory");		if (success) {			/* Was there a successor entry? Fix it's backpointer.. */			if ((link & 1) == 0) {				struct uhci_td *next = bus_to_virt(link & ~15);				next->backptr = &last->link;			}			break;		}	}}static inline void uhci_insert_td_in_qh(struct uhci_qh *qh, struct uhci_td *td){	uhci_insert_tds_in_qh(qh, td, td);}static void uhci_insert_qh(struct uhci_qh *qh, struct uhci_qh *newqh){	newqh->link = qh->link;	qh->link = virt_to_bus(newqh) | 2;}static void uhci_remove_qh(struct uhci_qh *qh, struct uhci_qh *remqh){	unsigned int remphys = virt_to_bus(remqh);	struct uhci_qh *lqh = qh;	while ((lqh->link & ~0xF) != remphys) {		if (lqh->link & 1)			break;		lqh = bus_to_virt(lqh->link & ~0xF);	}	if (lqh->link & 1) {		printk("couldn't find qh in chain!\n");		return;	}	lqh->link = remqh->link;}/* * Removes td from qh if present. * * NOTE! We keep track of both forward and back-pointers, * so this should be trivial, right? * * Wrong. While all TD insert/remove operations are synchronous * on the CPU, the UHCI controller can (and does) play with the * very first forward pointer. So we need to validate the backptr * before we change it, so that we don't by mistake reset the QH * head to something old. */static void uhci_remove_td(struct uhci_td *td){	unsigned int *backptr = td->backptr;	unsigned int link = td->link;	unsigned int me;	if (!backptr)		return;	td->backptr = NULL;	/*	 * This is the easy case: the UHCI will never change "td->link",	 * so we can always just look at that and fix up the backpointer	 * of any next element..	 */	if (!(link & 1)) {		struct uhci_td *next = bus_to_virt(link & ~15);		next->backptr = backptr;	}	/*	 * The nasty case is "backptr->next", which we need to	 * update to "link" _only_ if "backptr" still points	 * to us (it may not: maybe backptr is a QH->element	 * pointer and the UHCI has changed the value).	 */	me = virt_to_bus(td) | (0xe & *backptr);	asm volatile("lock ; cmpxchg %0,%1"		:		:"r" (link), "m" (*backptr), "a" (me)		:"memory");}static struct uhci_qh *uhci_qh_allocate(struct uhci_device *dev){	struct uhci_qh *qh;	int inuse;	qh = dev->qh;	for (; (inuse = test_and_set_bit(0, &qh->inuse)) != 0 && qh < &dev->qh[UHCI_MAXQH]; qh++)		;	if (!inuse)		return(qh);	printk("ran out of qh's for dev %p\n", dev);	return(NULL);}static void uhci_qh_deallocate(struct uhci_qh *qh){	if (qh->element != 1)		printk("qh %p leaving dangling entries? (%X)\n", qh, qh->element);	qh->element = 1;	qh->link = 1;	clear_bit(0, &qh->inuse);}static struct uhci_td *uhci_td_allocate(struct uhci_device *dev){	struct uhci_td *td;	int inuse;	td = dev->td;	for (; (inuse = test_and_set_bit(0, &td->inuse)) != 0 && td < &dev->td[UHCI_MAXTD]; td++)		;	if (!inuse)		return(td);	printk("ran out of td's for dev %p\n", dev);	return(NULL);}/* * This MUST only be called when it has been removed from a QH already (or * the QH has been removed from the skeleton */static void uhci_td_deallocate(struct uhci_td *td){	td->link = 1;	clear_bit(0, &td->inuse);}/* * UHCI interrupt list operations.. */static spinlock_t irqlist_lock = SPIN_LOCK_UNLOCKED;static void uhci_add_irq_list(struct uhci *uhci, struct uhci_td *td, usb_device_irq completed, void *dev_id){	unsigned long flags;	td->completed = completed;	td->dev_id = dev_id;	spin_lock_irqsave(&irqlist_lock, flags);	list_add(&td->irq_list, &uhci->interrupt_list);	spin_unlock_irqrestore(&irqlist_lock, flags);}static void uhci_remove_irq_list(struct uhci_td *td){	unsigned long flags;	spin_lock_irqsave(&irqlist_lock, flags);	list_del(&td->irq_list);	spin_unlock_irqrestore(&irqlist_lock, flags);}/* * Request a interrupt handler.. */static int uhci_request_irq(struct usb_device *usb_dev, unsigned int pipe, usb_device_irq handler, int period, void *dev_id){	struct uhci_device *dev = usb_to_uhci(usb_dev);	struct uhci_td *td = uhci_td_allocate(dev);	struct uhci_qh *interrupt_qh = uhci_qh_allocate(dev);	unsigned int destination, status;	/* Destination: pipe destination with INPUT */	destination = (pipe & 0x0007ff00)  |  0x69;	/* Status:    slow/fast,      Interrupt,   Active,    Short Packet Detect     Infinite Errors */	status = (pipe & (1 << 26)) | (1 << 24) | (1 << 23)   |   (1 << 29)       |    (0 << 27);	if(interrupt_qh->element != 1)		printk("interrupt_qh->element = 0x%x\n", 		       interrupt_qh->element);	td->link = 1;	td->status = status;			/* In */	td->info = destination | (7 << 21);	/* 8 bytes of data */	td->buffer = virt_to_bus(dev->data);	td->qh = interrupt_qh;	interrupt_qh->skel = &dev->uhci->root_hub->skel_int8_qh;	uhci_add_irq_list(dev->uhci, td, handler, dev_id);	uhci_insert_td_in_qh(interrupt_qh, td);	/* Add it into the skeleton */	uhci_insert_qh(&dev->uhci->root_hub->skel_int8_qh, interrupt_qh);	return 0;}/* * Control thread operations: we just mark the last TD * in a control thread as an interrupt TD, and wake up * the front-end on completion. * * We need to remove the TD from the lists (both interrupt * list and TD lists) by hand if something bad happens! */static struct wait_queue *control_wakeup;static int uhci_control_completed(int status, void *buffer, void *dev_id){	wake_up(&control_wakeup);	return 0;			/* Don't re-instate */}/* td points to the last td in the list, which interrupts on completion */static int uhci_run_control(struct uhci_device *dev, struct uhci_td *first, struct uhci_td *last){	struct wait_queue wait = { current, NULL };	struct uhci_qh *ctrl_qh = uhci_qh_allocate(dev);	struct uhci_td *curtd;	current->state = TASK_UNINTERRUPTIBLE;	add_wait_queue(&control_wakeup, &wait);	uhci_add_irq_list(dev->uhci, last, uhci_control_completed, NULL);		/* FIXME: This is kinda kludged */	/* Walk the TD list and update the QH pointer */	{	int maxcount = 100;	curtd = first;	do {		curtd->qh = ctrl_qh;		if (curtd->link & 1)			break;		curtd = bus_to_virt(curtd->link & ~0xF);		if (!--maxcount) {			printk("runaway tds!\n");			break;		}	} while (1);	}	uhci_insert_tds_in_qh(ctrl_qh, first, last);	/* Add it into the skeleton */	uhci_insert_qh(&dev->uhci->root_hub->skel_control_qh, ctrl_qh);	schedule_timeout(HZ/10);	remove_wait_queue(&control_wakeup, &wait);	/* Clean up in case it failed.. */	uhci_remove_irq_list(last);#if 0	printk("Looking for tds [%p, %p]\n", dev->control_td, td);#endif	/* Remove it from the skeleton */	uhci_remove_qh(&dev->uhci->root_hub->skel_control_qh, ctrl_qh);	uhci_qh_deallocate(ctrl_qh);	return uhci_td_result(dev, last);}/* * Send or receive a control message on a pipe. * * Note that the "pipe" structure is set up to map * easily to the uhci destination fields. * * A control message is built up from three parts: *  - The command itself *  - [ optional ] data phase *  - Status complete phase * * The data phase can be an arbitrary number of TD's * although we currently had better not have more than * 29 TD's here (we have 31 TD's allocated for control * operations, and two of them are used for command and * status). * * 29 TD's is a minimum of 232 bytes worth of control * information, that's just ridiculously high. Most * control messages have just a few bytes of data. */static int uhci_control_msg(struct usb_device *usb_dev, unsigned int pipe, void *cmd, void *data, int len){	struct uhci_device *dev = usb_to_uhci(usb_dev);	struct uhci_td *first, *td, *prevtd;	unsigned long destination, status;	int ret;	if (len > usb_maxpacket(usb_dev->maxpacketsize) * 29)		printk("Warning, too much data for a control packet, crashing\n");	first = td = uhci_td_allocate(dev);	/* The "pipe" thing contains the destination in bits 8--18, 0x2D is SETUP */	destination = (pipe & 0x0007ff00) | 0x2D;	/* Status:    slow/fast,       Active,    Short Packet Detect     Three Errors */	status = (pipe & (1 << 26)) | (1 << 23)   |   (1 << 29)       |    (3 << 27);	/*	 * Build the TD for the control request	 */	td->status = status;				/* Try forever */	td->info = destination | (7 << 21);		/* 8 bytes of data */	td->buffer = virt_to_bus(cmd);	/*	 * If direction is "send", change the frame from SETUP (0x2D)	 * to OUT (0xE1). Else change it from SETUP to IN (0x69)	 */	destination ^= (0x2D ^ 0x69);			/* SETUP -> IN */	if (usb_pipeout(pipe))		destination ^= (0xE1 ^ 0x69);		/* IN -> OUT */	prevtd = td;	td = uhci_td_allocate(dev);	prevtd->link = 4 | virt_to_bus(td);	/*	 * Build the DATA TD's	 */	while (len > 0) {		/* Build the TD for control status */		int pktsze = len;		int maxsze = usb_maxpacket(pipe);		if (pktsze > maxsze)			pktsze = maxsze;		/* Alternate Data0/1 (start with Data1) */		destination ^= 1 << 19;			td->status = status;					/* Status */		td->info = destination | ((pktsze-1) << 21);		/* pktsze bytes of data */		td->buffer = virt_to_bus(data);		td->backptr = &prevtd->link;		prevtd = td;		td = uhci_td_allocate(dev);		prevtd->link = 4 | virt_to_bus(td);			/* Update previous TD */		data += maxsze;		len -= maxsze;	}	/*	 * Build the final TD for control status	 */	destination ^= (0xE1 ^ 0x69);			/* OUT -> IN */	destination |= 1 << 19;				/* End in Data1 */	td->link = 1;					/* Terminate */	td->status = status | (1 << 24);		/* IOC */	td->info = destination | (0x7ff << 21);		/* 0 bytes of data */	td->buffer = 0;	td->backptr = &prevtd->link;	/* Start it up.. */	ret = uhci_run_control(dev, first, td);	{		int maxcount = 100;		struct uhci_td *curtd = first;		unsigned int nextlink;		do {			nextlink = curtd->link;			uhci_remove_td(curtd);			uhci_td_deallocate(curtd);			if (nextlink & 1)	/* Tail? */				break;			curtd = bus_to_virt(nextlink & ~0xF);			if (!--maxcount) {				printk("runaway td's!?\n");				break;			}		} while (1);	}	return ret;}static struct usb_device *uhci_usb_allocate(struct usb_device *parent){	struct usb_device *usb_dev;	struct uhci_device *dev;	int i;	usb_dev = kmalloc(sizeof(*usb_dev), GFP_KERNEL);	if (!usb_dev)		return NULL;	memset(usb_dev, 0, sizeof(*usb_dev));	dev = kmalloc(sizeof(*dev), GFP_KERNEL);	if (!dev) {		kfree(usb_dev);		return NULL;	}	/* Initialize "dev" */	memset(dev, 0, sizeof(*dev));	usb_dev->hcpriv = dev;	dev->usb = usb_dev;	usb_dev->parent = parent;	if (parent) {		usb_dev->bus = parent->bus;		dev->uhci = usb_to_uhci(parent)->uhci;	}	/* Reset the QH's and TD's */	for (i = 0; i < UHCI_MAXQH; i++) {		dev->qh[i].link = 1;		dev->qh[i].element = 1;		dev->qh[i].inuse = 0;	}	for (i = 0; i < UHCI_MAXTD; i++) {		dev->td[i].link = 1;		dev->td[i].inuse = 0;	}	return usb_dev;}static int uhci_usb_deallocate(struct usb_device *usb_dev){	struct uhci_device *dev = usb_to_uhci(usb_dev);	int i;	/* There are UHCI_MAXTD preallocated tds */	for (i = 0; i < UHCI_MAXTD; ++i) {		struct uhci_td *td = dev->td + i;		/* And remove it from the irq list, if it's active */		if (td->status & (1 << 23))			uhci_remove_irq_list(td);		if (td->inuse)			uhci_remove_td(td);	}	/* Remove the td from any queues */	for (i = 0; i < UHCI_MAXQH; ++i) {		struct uhci_qh *qh = dev->qh + i;		if (qh->inuse)			uhci_remove_qh(qh->skel, qh);	}	kfree(dev);	kfree(usb_dev);	return 0;}struct usb_operations uhci_device_operations = {	uhci_usb_allocate,	uhci_usb_deallocate,	uhci_control_msg,	uhci_request_irq,};/* * This is just incredibly fragile. The timings must be just * right, and they aren't really documented very well. * * Note the short delay between disabling reset and enabling * the port.. */static void uhci_reset_port(unsigned int port){

⌨️ 快捷键说明

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