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

📄 ohci.c

📁 超小usb协议栈
💻 C
📖 第 1 页 / 共 2 页
字号:
/* * Open Host Controller Interface driver for USB. * * (C) Copyright 1999 Gregory P. Smith <greg@electricrain.com> * * This is the "other" host controller interface for USB.  You will * find this on many non-Intel based motherboards, and of course the * Mac.  As Linus hacked his UHCI driver together first, I modeled * this after his.. (it should be obvious) * * From the programming standpoint the OHCI interface seems a little * prettier and potentially less CPU intensive.  This remains to be * proven.  In reality, I don't believe it'll make one darn bit of * difference.  USB v1.1 is a slow bus by today's standards. * * OHCI hardware takes care of most of the scheduling of different * transfer types with the correct prioritization for us. * * To get started in USB, I used the "Universal Serial Bus System * Architecture" book by Mindshare, Inc.  It was a reasonable introduction * and overview of USB and the two dominant host controller interfaces * however you're better off just reading the real specs available * from www.usb.org as you'll need them to get enough detailt to * actually implement a HCD.  The book has many typos and omissions * Beware, the specs are the victim of a committee. * * This code was written with Guinness on the brain, xsnow on the desktop * and Orbital, Orb, Enya & Massive Attack on the CD player.  What a life!  ;)  * * No filesystems were harmed in the development of this code. * * $Id: ohci.c,v 1.11 1999/04/25 00:18:52 greg Exp $ */#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 "ohci.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;#endifstatic struct wait_queue *ohci_configure = NULL;static int ohci_td_result(struct ohci_device *dev, struct ohci_td *td){	unsigned int status;	status = td->info & OHCI_TD_CC;	/* TODO Debugging code for TD failures goes here */	return status;}static spinlock_t ohci_edtd_lock = SPIN_LOCK_UNLOCKED;/* * Add a TD to the end of the TD list on a given ED.  td->next_td is * assumed to be set correctly for the situation of no TDs already * being on the list (ie: pointing to NULL). */static void ohci_add_td_to_ed(struct ohci_td *td, struct ohci_ed *ed){	struct ohci_td *tail = bus_to_virt(ed->tail_td);	struct ohci_td *head = bus_to_virt(ed->head_td);	unsigned long flags;	spin_lock_irqsave(&ohci_edtd_lock, flags);	if (tail == head) {	/* empty list, put it on the head */		head = (struct ohci_td *) virt_to_bus(td);		tail = 0;	} else {		if (!tail) {	/* no tail, single element list */			td->next_td = head->next_td;			head->next_td = virt_to_bus(td);			tail = (struct ohci_td *) virt_to_bus(td);		} else {	/* append to the list */			td->next_td = tail->next_td;			tail->next_td = virt_to_bus(td);			tail = (struct ohci_td *) virt_to_bus(td);		}	}	/* save the reverse link */	td->ed_bus = virt_to_bus(ed);	spin_unlock_irqrestore(&ohci_edtd_lock, flags);} /* ohci_add_td_to_ed() *//* *  Remove a TD from the given EDs TD list */static void ohci_remove_td_from_ed(struct ohci_td *td, struct ohci_ed *ed){	struct ohci_td *head = bus_to_virt(ed->head_td);	struct ohci_td *tmp_td;	unsigned long flags;	spin_lock_irqsave(&ohci_edtd_lock, flags);	/* set the "skip me bit" in this ED */	writel_set(OHCI_ED_SKIP, ed->status);	/* XXX Assuming this list will never be circular */	if (td == head) {		/* unlink this TD; it was at the beginning */		ed->head_td = head->next_td;	}	tmp_td = head;	head = (struct ohci_td *) ed->head_td;		while (head != NULL) {		if (td == head) {			/* unlink this TD from the middle or end */			tmp_td->next_td = head->next_td;		}		tmp_td = head;		head = bus_to_virt(head->next_td);	}	td->next_td = virt_to_bus(NULL);  /* remove links to ED list */	/* XXX mark this TD for possible cleanup? */	/* unset the "skip me bit" in this ED */	writel_mask(~(__u32)OHCI_ED_SKIP, ed->status);	spin_unlock_irqrestore(&ohci_edtd_lock, flags);} /* ohci_remove_td_from_ed() *//* * Get a pointer (virtual) to an available TD from the given device's * pool. * * Return NULL if none are left. */static struct ohci_td *ohci_get_free_td(struct ohci_device *dev){	int idx;	for (idx=0; idx < NUM_TDS; idx++) {		if (td_done(dev->td[idx].info)) {			/* XXX should this also zero out the structure? */			/* mark all new TDs as unaccessed */			dev->td[idx].info = OHCI_TD_CC_NEW;			return &dev->td[idx];		}	}	return NULL;} /* ohci_get_free_td() *//********************************** * OHCI interrupt list operations * **********************************/static spinlock_t irqlist_lock = SPIN_LOCK_UNLOCKED;static void ohci_add_irq_list(struct ohci *ohci, struct ohci_td *td, usb_device_irq completed, void *dev_id){	unsigned long flags;	/* save the irq in our private portion of the TD */	td->completed = completed;	td->dev_id = dev_id;	spin_lock_irqsave(&irqlist_lock, flags);	list_add(&td->irq_list, &ohci->interrupt_list);	spin_unlock_irqrestore(&irqlist_lock, flags);} /* ohci_add_irq_list() */static void ohci_remove_irq_list(struct ohci_td *td){	unsigned long flags;	spin_lock_irqsave(&irqlist_lock, flags);	list_del(&td->irq_list);	spin_unlock_irqrestore(&irqlist_lock, flags);} /* ohci_remove_irq_list() *//* * Request an interrupt handler for one "pipe" of a USB device. * (this function is pretty minimal right now) * * At the moment this is only good for input interrupts. (ie: for a * mouse) * * period is desired polling interval in ms.  The closest, shorter * match will be used.  Powers of two from 1-32 are supported by OHCI. */static int ohci_request_irq(struct usb_device *usb, unsigned int pipe,	usb_device_irq handler, int period, void *dev_id){	struct ohci_device *dev = usb_to_ohci(usb);	struct ohci_td *td = dev->td;	/* */	struct ohci_ed *interrupt_ed;	/* endpoint descriptor for this irq */	/*	 * Pick a good frequency endpoint based on the requested period	 */	interrupt_ed = &dev->ohci->root_hub->ed[ms_to_ed_int(period)];	/*	 * Set the max packet size, device speed, endpoint number, usb	 * device number (function address), and type of TD.	 *	 * FIXME: Isochronous transfers need a pool of special 32 byte	 * TDs (32 byte aligned) in order to be supported.	 */	interrupt_ed->status = \		ed_set_maxpacket(usb_maxpacket(pipe)) |		ed_set_speed(usb_pipeslow(pipe)) |		usb_pipe_endpdev(pipe) |		OHCI_ED_F_NORM;	/*	 * Set the not accessed condition code, allow odd sized data,	 * and set the data transfer direction.	 */	td->info = OHCI_TD_CC_NEW | OHCI_TD_ROUND |		td_set_dir_out(usb_pipeout(pipe));	/* point it to our data buffer */	td->cur_buf = virt_to_bus(dev->data);	/* FIXME: we're only using 1 TD right now! */	td->next_td = virt_to_bus(&td);	/*	 * FIXME: be aware that OHCI won't advance out of the 4kb	 * page cur_buf started in.  It'll wrap around to the start	 * of the page...  annoying or useful? you decide.	 *	 * A pointer to the last *byte* in the buffer (ergh.. we get	 * to work around C's pointer arithmatic here with a typecast)	 */	td->buf_end = virt_to_bus(((u8*)(dev->data + DATA_BUF_LEN)) - 1);	/* does this make sense for ohci?.. time to think.. */	ohci_add_irq_list(dev->ohci, td, handler, dev_id);	wmb();     /* found in asm/system.h; scary concept... */	ohci_add_td_to_ed(td, interrupt_ed);	return 0;} /* ohci_request_irq() *//* * Control thread operations: */static struct wait_queue *control_wakeup;static int ohci_control_completed(int stats, void *buffer, void *dev_id){	wake_up(&control_wakeup);	return 0;} /* ohci_control_completed() *//* * Run a control transaction from the root hub's control endpoint. * The passed in TD is the control transfer's Status TD. */static int ohci_run_control(struct ohci_device *dev, struct ohci_td *status_td){	struct wait_queue wait = { current, NULL };	struct ohci_ed *control_ed = &dev->ohci->root_hub->ed[ED_CONTROL];	current->state = TASK_UNINTERRUPTIBLE;	add_wait_queue(&control_wakeup, &wait);	ohci_add_irq_list(dev->ohci, status_td, ohci_control_completed, NULL);	ohci_add_td_to_ed(status_td, control_ed);	/* FIXME? isn't this a little gross */	schedule_timeout(HZ/10);	ohci_remove_irq_list(status_td);	ohci_remove_td_from_ed(status_td, control_ed);	return ohci_td_result(dev, status_td);} /* ohci_run_control() *//* * Send or receive a control message on a "pipe" * * A control message contains: *   - The command itself *   - An optional data phase *   - Status complete phase * * The data phase can be an arbitrary number of TD's.  Currently since * we use statically allocated TDs if too many come in we'll just * start tossing them and printk() some warning goo...  Most control * messages won't have much data anyways. */static int ohci_control_msg(struct usb_device *usb, unsigned int pipe, void *cmd, void *data, int len){	struct ohci_device *dev = usb_to_ohci(usb);	/*	 * ideally dev->ed should be linked into the root hub's	 * control_ed list and used instead of just using it directly.	 * This could present a problem as is with more than one	 * device.  (but who wants to use a keyboard AND a mouse	 * anyways? ;)	 */	struct ohci_ed *control_ed = &dev->ohci->root_hub->ed[ED_CONTROL];	struct ohci_td *control_td;	struct ohci_td *data_td;	struct ohci_td *last_td;	__u32 data_td_info;	/*	 * Set the max packet size, device speed, endpoint number, usb	 * device number (function address), and type of TD.	 *	 */	control_ed->status = \		ed_set_maxpacket(usb_maxpacket(pipe)) |		ed_set_speed(usb_pipeslow(pipe)) |		usb_pipe_endpdev(pipe) |		OHCI_ED_F_NORM;	/*	 * Build the control TD	 */	/* get a TD to send this control message with */	control_td = ohci_get_free_td(dev);	/* TODO check for NULL */	/*	 * Set the not accessed condition code, allow odd sized data,	 * and set the data transfer type to SETUP.  Setup DATA always	 * uses a DATA0 packet.	 */	control_td->info = OHCI_TD_CC_NEW | OHCI_TD_ROUND |		OHCI_TD_D_SETUP | OHCI_TD_IOC_OFF | td_force_toggle(0);	/* point it to the command */	control_td->cur_buf = virt_to_bus(cmd);	/* link to a free TD for the control data input */	data_td = ohci_get_free_td(dev);  /* TODO check for NULL */	control_td->next_td = virt_to_bus(data_td);	/*	 * Build the DATA TDs	 */	data_td_info = OHCI_TD_CC_NEW | OHCI_TD_ROUND | OHCI_TD_IOC_OFF |		td_set_dir_out(usb_pipeout(pipe));	while (len > 0) {		int pktsize = len;		struct ohci_td *tmp_td;		if (pktsize > usb_maxpacket(pipe))			pktsize = usb_maxpacket(pipe);		/* set the data transaction type */		data_td->info = data_td_info;		/* point to the current spot in the data buffer */		data_td->cur_buf = virt_to_bus(data);		/* point to the end of this data */		data_td->buf_end = virt_to_bus(data+pktsize-1);		/* allocate the next TD */		tmp_td = ohci_get_free_td(dev);  /* TODO check for NULL */		data_td->next_td = virt_to_bus(tmp_td);		data_td = tmp_td;		/* move on.. */		data += pktsize;		len -= pktsize;	}	/* point it at the newly allocated TD from above */	last_td = data_td;	/* The control status packet always uses a DATA1 */	last_td->info = OHCI_TD_CC_NEW | OHCI_TD_ROUND | td_force_toggle(1);	last_td->next_td = 0; /* end of TDs */	last_td->cur_buf = 0; /* no data in this packet */	last_td->buf_end = 0;	/*	 * Start the control transaction.. give it the last TD so the	 * result can be returned.	 */	return ohci_run_control(dev, last_td);} /* ohci_control_msg() */static struct usb_device *ohci_usb_allocate(struct usb_device *parent){	struct usb_device *usb_dev;	struct ohci_device *dev;	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->ohci = usb_to_ohci(parent)->ohci;	}	return usb_dev;}static int ohci_usb_deallocate(struct usb_device *usb_dev){	kfree(usb_to_ohci(usb_dev));	kfree(usb_dev);	return 0;}/* * functions for the generic USB driver */struct usb_operations ohci_device_operations = {	ohci_usb_allocate,	ohci_usb_deallocate,	ohci_control_msg,	ohci_request_irq,};/* * Reset an OHCI controller */static void reset_hc(struct ohci *ohci){	writel((1<<31), &ohci->regs->intrdisable); /* Disable HC interrupts */	writel(1,  &ohci->regs->cmdstatus);	   /* HC Reset */	writel_mask(0x3f, &ohci->regs->control);   /* move to UsbReset state */} /* reset_hc() *//* * Reset and start an OHCI controller */static void start_hc(struct ohci *ohci){	int timeout = 1000;	/* used to prevent an infinite loop. */	reset_hc(ohci);	while ((readl(&ohci->regs->control) & 0xc0) == 0) {		if (!--timeout) {			printk("USB HC Reset timed out!\n");			break;		}	}	/* Choose the interrupts we care about */	writel( OHCI_INTR_MIE | OHCI_INTR_RHSC | OHCI_INTR_SF |		OHCI_INTR_WDH | OHCI_INTR_SO | OHCI_INTR_UE |		OHCI_INTR_FNO,		&ohci->regs->intrenable);	/* Enter the USB Operational state & start the frames a flowing.. */	writel_set(OHCI_USB_OPER, &ohci->regs->control);} /* start_hc() *//* * Reset a root hub port */static void ohci_reset_port(struct ohci *ohci, unsigned int port){	short ms;	int status;	/* Don't allow overflows. */	if (port >= MAX_ROOT_PORTS) {

⌨️ 快捷键说明

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