📄 ohci.c
字号:
/* * 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 + -