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