📄 uhci.c
字号:
unsigned short status; status = inw(port); outw(status | USBPORTSC_PR, port); /* reset port */ wait_ms(10); outw(status & ~USBPORTSC_PR, port); udelay(5); status = inw(port); outw(status | USBPORTSC_PE, port); /* enable port */ wait_ms(10); status = inw(port); if(!(status & USBPORTSC_PE)) { outw(status | USBPORTSC_PE, port); /* one more try at enabling port */ wait_ms(50); }}/* * This gets called if the connect status on the root * hub (and the root hub only) changes. */static void uhci_connect_change(struct uhci *uhci, unsigned int port, unsigned int nr){ struct usb_device *usb_dev; struct uhci_device *dev; unsigned short status; printk("uhci_connect_change: called for %d\n", nr); /* * Even if the status says we're connected, * the fact that the status bits changed may * that we got disconnected and then reconnected. * * So start off by getting rid of any old devices.. */ usb_disconnect(&uhci->root_hub->usb->children[nr]); status = inw(port); /* If we have nothing connected, then clear change status and disable the port */ status = (status & ~USBPORTSC_PE) | USBPORTSC_PEC; if (!(status & USBPORTSC_CCS)) { outw(status, port); return; } /* * Ok, we got a new connection. Allocate a device to it, * and find out what it wants to do.. */ usb_dev = uhci_usb_allocate(uhci->root_hub->usb); dev = usb_dev->hcpriv; dev->uhci = uhci; usb_connect(usb_dev); uhci->root_hub->usb->children[nr] = usb_dev; wait_ms(200); /* wait for powerup */ uhci_reset_port(port); /* Get speed information */ usb_dev->slow = (inw(port) & USBPORTSC_LSDA) ? 1 : 0; /* * Ok, all the stuff specific to the root hub has been done. * The rest is generic for any new USB attach, regardless of * hub type. */ usb_new_device(usb_dev);}/* * This gets called when the root hub configuration * has changed. Just go through each port, seeing if * there is something interesting happening. */static void uhci_check_configuration(struct uhci *uhci){ unsigned int io_addr = uhci->io_addr + USBPORTSC1; int maxchild = uhci->root_hub->usb->maxchild; int nr = 0; do { unsigned short status = inw(io_addr); if (status & USBPORTSC_CSC) uhci_connect_change(uhci, io_addr, nr); nr++; io_addr += 2; } while (nr < maxchild);}static void uhci_interrupt_notify(struct uhci *uhci){ struct list_head *head = &uhci->interrupt_list; struct list_head *tmp; spin_lock(&irqlist_lock); tmp = head->next; while (tmp != head) { struct uhci_td *td = list_entry(tmp, struct uhci_td, irq_list); struct list_head *next; next = tmp->next; if (!(td->status & (1 << 23))) { /* No longer active? */ /* remove from IRQ list */ __list_del(tmp->prev, next); INIT_LIST_HEAD(tmp); if (td->completed(td->status, bus_to_virt(td->buffer), td->dev_id)) { struct uhci_qh *interrupt_qh = td->qh; list_add(&td->irq_list, &uhci->interrupt_list); td->info ^= 1 << 19; /* toggle between data0 and data1 */ td->status = (td->status & 0x2f000000) | (1 << 23) | (1 << 24); /* active */ /* Remove then readd? Is that necessary */ uhci_remove_td(td); uhci_insert_td_in_qh(interrupt_qh, td); } /* If completed wants to not reactivate, then it's */ /* responsible for free'ing the TD's and QH's */ /* or another function (such as run_control) */ } tmp = next; } spin_unlock(&irqlist_lock);}/* * Check port status - Connect Status Change - for * each of the attached ports (defaults to two ports, * but at least in theory there can be more of them). * * Wake up the configurator if something happened, we * can't really do much at interrupt time. */static void uhci_root_hub_events(struct uhci *uhci, unsigned int io_addr){ if (waitqueue_active(&uhci_configure)) { int ports = uhci->root_hub->usb->maxchild; io_addr += USBPORTSC1; do { if (inw(io_addr) & USBPORTSC_CSC) { wake_up(&uhci_configure); return; } io_addr += 2; } while (--ports > 0); }}static void uhci_interrupt(int irq, void *__uhci, struct pt_regs *regs){ struct uhci *uhci = __uhci; unsigned int io_addr = uhci->io_addr; unsigned short status; /* * Read the interrupt status, and write it back to clear the interrupt cause */ status = inw(io_addr + USBSTS); outw(status, io_addr + USBSTS); /* Walk the list of pending TD's to see which ones completed.. */ uhci_interrupt_notify(uhci); /* Check if there are any events on the root hub.. */ uhci_root_hub_events(uhci, io_addr);}/* * We init one packet, and mark it just IOC and _not_ * active. Which will result in no actual USB traffic, * but _will_ result in an interrupt every second. * * Which is exactly what we want. */static void uhci_init_ticktd(struct uhci *uhci){ struct uhci_device *dev = uhci->root_hub; struct uhci_td *td = uhci_td_allocate(dev); td->link = 1; td->status = (1 << 24); /* interrupt on completion */ td->info = (15 << 21) | 0x7f69; /* (ignored) input packet, 16 bytes, device 127 */ td->buffer = 0; td->qh = NULL; uhci->fl->frame[0] = virt_to_bus(td);}static void reset_hc(struct uhci *uhci){ unsigned int io_addr = uhci->io_addr; /* Global reset for 50ms */ outw(USBCMD_GRESET, io_addr+USBCMD); wait_ms(50); outw(0, io_addr+USBCMD); wait_ms(10);}static void start_hc(struct uhci *uhci){ unsigned int io_addr = uhci->io_addr; int timeout = 1000; uhci_init_ticktd(uhci); /* * Reset the HC - this will force us to get a * new notification of any already connected * ports due to the virtual disconnect that it * implies. */ outw(USBCMD_HCRESET, io_addr + USBCMD); while (inw(io_addr + USBCMD) & USBCMD_HCRESET) { if (!--timeout) { printk("USBCMD_HCRESET timed out!\n"); break; } } outw(USBINTR_TIMEOUT | USBINTR_RESUME | USBINTR_IOC | USBINTR_SP, io_addr + USBINTR); outw(0, io_addr + USBFRNUM); outl(virt_to_bus(uhci->fl), io_addr + USBFLBASEADD); /* Run and mark it configured with a 64-byte max packet */ outw(USBCMD_RS | USBCMD_CF, io_addr + USBCMD);}/* * Allocate a frame list, and four regular queues. * * The hardware doesn't really know any difference * in the queues, but the order does matter for the * protocols higher up. The order is: * * - any isochronous events handled before any * of the queues. We don't do that here, because * we'll create the actual TD entries on demand. * - The first queue is the "interrupt queue". * - The second queue is the "control queue". * - The third queue is "bulk data". * * We could certainly have multiple queues of the same * type, and maybe we should. We could have per-device * queues, for example. We begin small. */static struct uhci *alloc_uhci(unsigned int io_addr){ int i; struct uhci *uhci; struct usb_bus *bus; struct uhci_device *dev; struct usb_device *usb; uhci = kmalloc(sizeof(*uhci), GFP_KERNEL); if (!uhci) return NULL; memset(uhci, 0, sizeof(*uhci)); uhci->irq = -1; uhci->io_addr = io_addr; INIT_LIST_HEAD(&uhci->interrupt_list); /* We need exactly one page (per UHCI specs), how convenient */ uhci->fl = (void *)__get_free_page(GFP_KERNEL); bus = kmalloc(sizeof(*bus), GFP_KERNEL); if (!bus) return NULL; memset(bus, 0, sizeof(*bus)); uhci->bus = bus; bus->hcpriv = uhci; bus->op = &uhci_device_operations; /* * We allocate a 8kB area for the UHCI hub. The area * is described by the uhci_device structure, and basically * contains everything needed for normal operation. * * The first page is the actual device descriptor for the * hub. * * The second page is used for the frame list. */ usb = uhci_usb_allocate(NULL); if (!usb) return NULL; dev = uhci->root_hub = usb_to_uhci(usb); usb->bus = bus; /* Initialize the root hub */ /* UHCI specs says devices must have 2 ports, but goes on to say */ /* they may have more but give no way to determine how many they */ /* have, so default to 2 */ usb->maxchild = 2; usb_init_root_hub(usb); /* * Initialize the queues. They all start out empty, * linked to each other in the proper order. */ for (i = 1 ; i < 9; i++) { dev->qh[i].link = 2 | virt_to_bus(&dev->skel_control_qh); dev->qh[i].element = 1; } dev->skel_control_qh.link = 2 | virt_to_bus(&dev->skel_bulk0_qh); dev->skel_control_qh.element = 1; dev->skel_bulk0_qh.link = 2 | virt_to_bus(&dev->skel_bulk1_qh); dev->skel_bulk0_qh.element = 1; dev->skel_bulk1_qh.link = 2 | virt_to_bus(&dev->skel_bulk2_qh); dev->skel_bulk1_qh.element = 1; dev->skel_bulk2_qh.link = 2 | virt_to_bus(&dev->skel_bulk3_qh); dev->skel_bulk2_qh.element = 1; dev->skel_bulk3_qh.link = 1; dev->skel_bulk3_qh.element = 1; /* * Fill the frame list: make all entries point to * the proper interrupt queue. * * This is probably silly, but it's a simple way to * scatter the interrupt queues in a way that gives * us a reasonable dynamic range for irq latencies. */ for (i = 0; i < 1024; i++) { struct uhci_qh * irq = &dev->skel_int2_qh; if (i & 1) { irq++; if (i & 2) { irq++; if (i & 4) { irq++; if (i & 8) { irq++; if (i & 16) { irq++; if (i & 32) { irq++; if (i & 64) { irq++; } } } } } } } uhci->fl->frame[i] = 2 | virt_to_bus(irq); } return uhci;}/* * De-allocate all resources.. */static void release_uhci(struct uhci *uhci){ if (uhci->irq >= 0) { free_irq(uhci->irq, uhci); uhci->irq = -1; }#if 0 if (uhci->root_hub) { uhci_usb_deallocate(uhci_to_usb(uhci->root_hub)); uhci->root_hub = NULL; }#endif if (uhci->fl) { free_page((unsigned long)uhci->fl); uhci->fl = NULL; } kfree(uhci->bus); kfree(uhci);}static int uhci_control_thread(void * __uhci){ struct uhci *uhci = (struct uhci *)__uhci; lock_kernel(); request_region(uhci->io_addr, 32, "usb-uhci"); /* * This thread doesn't need any user-level access, * so get rid of all our resources.. */ printk("uhci_control_thread at %p\n", &uhci_control_thread); exit_mm(current); exit_files(current); exit_fs(current); strcpy(current->comm, "uhci-control"); /* * Ok, all systems are go.. */ start_hc(uhci); for(;;) { siginfo_t info; int unsigned long signr; interruptible_sleep_on(&uhci_configure);#ifdef CONFIG_APM if (apm_resume) { apm_resume = 0; start_hc(uhci); continue; }#endif uhci_check_configuration(uhci); if(signal_pending(current)) { /* sending SIGUSR1 makes us print out some info */ spin_lock_irq(¤t->sigmask_lock); signr = dequeue_signal(¤t->blocked, &info); spin_unlock_irq(¤t->sigmask_lock); if(signr == SIGUSR1) { printk("UHCI queue dump:\n"); show_queues(uhci); } else { break; } } }#if 0 if(uhci->root_hub) for(i = 0; i < uhci->root_hub->usb->maxchild; i++) usb_disconnect(uhci->root_hub->usb->children + i);#endif reset_hc(uhci); release_region(uhci->io_addr, 32); release_uhci(uhci); MOD_DEC_USE_COUNT; printk("uhci_control_thread exiting\n"); return 0;} /* * If we've successfully found a UHCI, now is the time to increment the * module usage count, start the control thread, and return success.. */static int found_uhci(int irq, unsigned int io_addr){ int retval; struct uhci *uhci; uhci = alloc_uhci(io_addr); if (!uhci) return -ENOMEM; reset_hc(uhci); retval = -EBUSY; if (request_irq(irq, uhci_interrupt, SA_SHIRQ, "usb", uhci) == 0) { int pid; MOD_INC_USE_COUNT; uhci->irq = irq; pid = kernel_thread(uhci_control_thread, uhci, CLONE_FS | CLONE_FILES | CLONE_SIGHAND); if (pid >= 0) return 0; MOD_DEC_USE_COUNT; retval = pid; } release_uhci(uhci); return retval;}static int init_uhci(struct pci_dev *dev){ int i; /* Search for the IO base address.. */ for (i = 0; i < 6; i++) { unsigned int io_addr = dev->base_address[i]; /* IO address? */ if (!(io_addr & 1)) continue; io_addr &= PCI_BASE_ADDRESS_IO_MASK; /* Is it already in use? */ if (check_region(io_addr, 32)) break; return found_uhci(dev->irq, io_addr); } return -1;}#ifdef CONFIG_APMstatic int handle_apm_event(apm_event_t event){ static int down = 0; switch (event) { case APM_SYS_SUSPEND: case APM_USER_SUSPEND: if (down) { printk(KERN_DEBUG "uhci: received extra suspend event\n"); break; } down = 1; break; case APM_NORMAL_RESUME: case APM_CRITICAL_RESUME: if (!down) { printk(KERN_DEBUG "uhci: received bogus resume event\n"); break; } down = 0; if (waitqueue_active(&uhci_configure)) { apm_resume = 1; wake_up(&uhci_configure); } break; } return 0;}#endifint init_module(void){ int retval; struct pci_dev *dev = NULL; u8 type; retval = -ENODEV; for (;;) { dev = pci_find_class(PCI_CLASS_SERIAL_USB<<8, dev); if (!dev) break; /* Is it UHCI */ pci_read_config_byte(dev, PCI_CLASS_PROG, &type); if(type != 0) continue; /* Ok set it up */ retval = init_uhci(dev); if (retval < 0) continue; usb_mouse_init(); usb_kbd_init(); hub_init();#ifdef CONFIG_APM apm_register_callback(&handle_apm_event);#endif return 0; } return retval;}void cleanup_module(void){#ifdef CONFIG_APM apm_unregister_callback(&handle_apm_event);#endif}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -