📄 uhci.c
字号:
/* * Universal Host Controller Interface driver for USB. * * (C) Copyright 1999 Linus Torvalds * (C) Copyright 1999-2000 Johannes Erdfelt, johannes@erdfelt.com * (C) Copyright 1999 Randy Dunlap * (C) Copyright 1999 Georg Acher, acher@in.tum.de * (C) Copyright 1999 Deti Fliegl, deti@fliegl.de * (C) Copyright 1999 Thomas Sailer, sailer@ife.ee.ethz.ch * (C) Copyright 1999 Roman Weissgaerber, weissg@vienna.at * (C) Copyright 2000 Yggdrasil Computing, Inc. (port of new PCI interface * support from usb-ohci.c by Adam Richter, adam@yggdrasil.com). * (C) Copyright 1999 Gregory P. Smith (from usb-ohci.c) * * * 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. * * 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 */#include <linux/config.h>#include <linux/module.h>#include <linux/pci.h>#include <linux/kernel.h>#include <linux/init.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 <linux/unistd.h>#include <linux/interrupt.h>#include <linux/spinlock.h>#define DEBUG#include <linux/usb.h>#include <asm/uaccess.h>#include <asm/io.h>#include <asm/irq.h>#include <asm/system.h>#include "uhci.h"#include "uhci-debug.h"#include <linux/pm.h>static int debug = 1;MODULE_PARM(debug, "i");MODULE_PARM_DESC(debug, "Debug level");static kmem_cache_t *uhci_td_cachep;static kmem_cache_t *uhci_qh_cachep;static kmem_cache_t *uhci_up_cachep; /* urb_priv */static int rh_submit_urb(struct urb *urb);static int rh_unlink_urb(struct urb *urb);static int uhci_get_current_frame_number(struct usb_device *dev);static int uhci_unlink_generic(struct urb *urb);static int uhci_unlink_urb(struct urb *urb);#define min(a,b) (((a)<(b))?(a):(b))/* If a transfer is still active after this much time, turn off FSBR */#define IDLE_TIMEOUT (HZ / 20) /* 50 ms *//* * Only the USB core should call uhci_alloc_dev and uhci_free_dev */static int uhci_alloc_dev(struct usb_device *dev){ return 0;}static int uhci_free_dev(struct usb_device *dev){ struct uhci *uhci = (struct uhci *)dev->bus->hcpriv; struct list_head *tmp, *head = &uhci->urb_list; unsigned long flags; /* Walk through the entire URB list and forcefully remove any */ /* URBs that are still active for that device */ nested_lock(&uhci->urblist_lock, flags); tmp = head->next; while (tmp != head) { struct urb *u = list_entry(tmp, struct urb, urb_list); tmp = tmp->next; if (u->dev == dev) uhci_unlink_urb(u); } nested_unlock(&uhci->urblist_lock, flags); return 0;}static void uhci_add_urb_list(struct uhci *uhci, struct urb *urb){ unsigned long flags; nested_lock(&uhci->urblist_lock, flags); list_add(&urb->urb_list, &uhci->urb_list); nested_unlock(&uhci->urblist_lock, flags);}static void uhci_remove_urb_list(struct uhci *uhci, struct urb *urb){ unsigned long flags; nested_lock(&uhci->urblist_lock, flags); if (!list_empty(&urb->urb_list)) { list_del(&urb->urb_list); INIT_LIST_HEAD(&urb->urb_list); } nested_unlock(&uhci->urblist_lock, flags);}void uhci_set_next_interrupt(struct uhci *uhci){ unsigned long flags; spin_lock_irqsave(&uhci->framelist_lock, flags); uhci->skel_term_td.status |= TD_CTRL_IOC; spin_unlock_irqrestore(&uhci->framelist_lock, flags);}void uhci_clear_next_interrupt(struct uhci *uhci){ unsigned long flags; spin_lock_irqsave(&uhci->framelist_lock, flags); uhci->skel_term_td.status &= ~TD_CTRL_IOC; spin_unlock_irqrestore(&uhci->framelist_lock, flags);}static struct uhci_td *uhci_alloc_td(struct usb_device *dev){ struct uhci_td *td; td = kmem_cache_alloc(uhci_td_cachep, in_interrupt() ? SLAB_ATOMIC : SLAB_KERNEL); if (!td) return NULL; td->link = UHCI_PTR_TERM; td->buffer = 0; td->frameptr = NULL; td->nexttd = td->prevtd = NULL; td->dev = dev; INIT_LIST_HEAD(&td->list); usb_inc_dev_use(dev); return td;}static void inline uhci_fill_td(struct uhci_td *td, __u32 status, __u32 info, __u32 buffer){ td->status = status; td->info = info; td->buffer = buffer;}static void uhci_insert_td(struct uhci *uhci, struct uhci_td *skeltd, struct uhci_td *td){ unsigned long flags; spin_lock_irqsave(&uhci->framelist_lock, flags); /* Fix the linked list pointers */ td->nexttd = skeltd->nexttd; td->prevtd = skeltd; if (skeltd->nexttd) skeltd->nexttd->prevtd = td; skeltd->nexttd = td; td->link = skeltd->link; skeltd->link = virt_to_bus(td); spin_unlock_irqrestore(&uhci->framelist_lock, flags);}/* * We insert Isochronous transfers directly into the frame list at the * beginning * The layout looks as follows: * frame list pointer -> iso td's (if any) -> * periodic interrupt td (if frame 0) -> irq td's -> control qh -> bulk qh */static void uhci_insert_td_frame_list(struct uhci *uhci, struct uhci_td *td, unsigned framenum){ unsigned long flags; struct uhci_td *nexttd; framenum %= UHCI_NUMFRAMES; spin_lock_irqsave(&uhci->framelist_lock, flags); td->frameptr = &uhci->fl->frame[framenum]; td->link = uhci->fl->frame[framenum]; if (!(td->link & (UHCI_PTR_TERM | UHCI_PTR_QH))) { nexttd = (struct uhci_td *)uhci_ptr_to_virt(td->link); td->nexttd = nexttd; nexttd->prevtd = td; nexttd->frameptr = NULL; } uhci->fl->frame[framenum] = virt_to_bus(td); spin_unlock_irqrestore(&uhci->framelist_lock, flags);}static void uhci_remove_td(struct uhci *uhci, struct uhci_td *td){ unsigned long flags; /* If it's not inserted, don't remove it */ if (!td->frameptr && !td->prevtd && !td->nexttd) return; spin_lock_irqsave(&uhci->framelist_lock, flags); if (td->frameptr) { *(td->frameptr) = td->link; if (td->nexttd) { td->nexttd->frameptr = td->frameptr; td->nexttd->prevtd = NULL; td->nexttd = NULL; } td->frameptr = NULL; } else { if (td->prevtd) { td->prevtd->nexttd = td->nexttd; td->prevtd->link = td->link; } if (td->nexttd) td->nexttd->prevtd = td->prevtd; td->prevtd = td->nexttd = NULL; } td->link = UHCI_PTR_TERM; spin_unlock_irqrestore(&uhci->framelist_lock, flags);}/* * Inserts a td into qh list at the top. */static void uhci_insert_tds_in_qh(struct uhci_qh *qh, struct urb *urb, int breadth){ struct list_head *tmp, *head; struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv; struct uhci_td *td, *prevtd; if (!urbp) return; head = &urbp->list; tmp = head->next; if (head == tmp) return; td = list_entry(tmp, struct uhci_td, list); /* Add the first TD to the QH element pointer */ qh->element = virt_to_bus(td) | (breadth ? 0 : UHCI_PTR_DEPTH); prevtd = td; /* Then link the rest of the TD's */ tmp = tmp->next; while (tmp != head) { td = list_entry(tmp, struct uhci_td, list); tmp = tmp->next; prevtd->link = virt_to_bus(td) | (breadth ? 0 : UHCI_PTR_DEPTH); prevtd = td; } prevtd->link = UHCI_PTR_TERM;}static void uhci_free_td(struct uhci_td *td){ if (!list_empty(&td->list)) dbg("td is still in URB list!"); if (td->dev) usb_dec_dev_use(td->dev); kmem_cache_free(uhci_td_cachep, td);}static struct uhci_qh *uhci_alloc_qh(struct usb_device *dev){ struct uhci_qh *qh; qh = kmem_cache_alloc(uhci_qh_cachep, in_interrupt() ? SLAB_ATOMIC : SLAB_KERNEL); if (!qh) return NULL; qh->element = UHCI_PTR_TERM; qh->link = UHCI_PTR_TERM; qh->dev = dev; qh->prevqh = qh->nextqh = NULL; INIT_LIST_HEAD(&qh->remove_list); usb_inc_dev_use(dev); return qh;}static void uhci_free_qh(struct uhci_qh *qh){ if (qh->dev) usb_dec_dev_use(qh->dev); kmem_cache_free(uhci_qh_cachep, qh);}static void uhci_insert_qh(struct uhci *uhci, struct uhci_qh *skelqh, struct uhci_qh *qh){ unsigned long flags; spin_lock_irqsave(&uhci->framelist_lock, flags); /* Fix the linked list pointers */ qh->nextqh = skelqh->nextqh; qh->prevqh = skelqh; if (skelqh->nextqh) skelqh->nextqh->prevqh = qh; skelqh->nextqh = qh; qh->link = skelqh->link; skelqh->link = virt_to_bus(qh) | UHCI_PTR_QH; spin_unlock_irqrestore(&uhci->framelist_lock, flags);}static void uhci_remove_qh(struct uhci *uhci, struct uhci_qh *qh){ unsigned long flags; int delayed; /* If the QH isn't queued, then we don't need to delay unlink it */ delayed = (qh->prevqh || qh->nextqh); spin_lock_irqsave(&uhci->framelist_lock, flags); if (qh->prevqh) { qh->prevqh->nextqh = qh->nextqh; qh->prevqh->link = qh->link; } if (qh->nextqh) qh->nextqh->prevqh = qh->prevqh; qh->prevqh = qh->nextqh = NULL; qh->element = qh->link = UHCI_PTR_TERM; spin_unlock_irqrestore(&uhci->framelist_lock, flags); if (delayed) { spin_lock_irqsave(&uhci->qh_remove_lock, flags); /* Check to see if the remove list is empty */ /* Set the IOC bit to force an interrupt so we can remove the QH */ if (list_empty(&uhci->qh_remove_list)) uhci_set_next_interrupt(uhci); /* Add it */ list_add(&qh->remove_list, &uhci->qh_remove_list); spin_unlock_irqrestore(&uhci->qh_remove_lock, flags); } else uhci_free_qh(qh);}static spinlock_t uhci_append_urb_lock = SPIN_LOCK_UNLOCKED;/* This function will append one URB's QH to another URB's QH. This is for *//* USB_QUEUE_BULK support */static void uhci_append_queued_urb(struct uhci *uhci, struct urb *eurb, struct urb *urb){ struct urb_priv *eurbp, *urbp, *furbp, *lurbp; struct list_head *tmp; struct uhci_td *td, *ltd; unsigned long flags; eurbp = eurb->hcpriv; urbp = urb->hcpriv; spin_lock_irqsave(&uhci_append_urb_lock, flags); /* Find the beginning URB in the queue */ if (eurbp->queued) { struct list_head *head = &eurbp->urb_queue_list; tmp = head->next; while (tmp != head) { struct urb_priv *turbp = list_entry(tmp, struct urb_priv, urb_queue_list); tmp = tmp->next; if (!turbp->queued) break; } } else tmp = &eurbp->urb_queue_list; furbp = list_entry(tmp, struct urb_priv, urb_queue_list); tmp = furbp->urb_queue_list.prev; lurbp = list_entry(tmp, struct urb_priv, urb_queue_list); /* Add this one to the end */ list_add_tail(&urbp->urb_queue_list, &furbp->urb_queue_list); /* Grab the last TD from the last URB */ ltd = list_entry(lurbp->list.prev, struct uhci_td, list); /* Grab the first TD from the first URB */ td = list_entry(urbp->list.next, struct uhci_td, list); /* No breadth since this will only be called for bulk transfers */ ltd->link = virt_to_bus(td); spin_unlock_irqrestore(&uhci_append_urb_lock, flags);}static void uhci_delete_queued_urb(struct uhci *uhci, struct urb *urb){ struct urb_priv *urbp, *nurbp; unsigned long flags; urbp = urb->hcpriv; spin_lock_irqsave(&uhci_append_urb_lock, flags); nurbp = list_entry(urbp->urb_queue_list.next, struct urb_priv, urb_queue_list); if (!urbp->queued) { /* We're the head, so just insert the QH for the next URB */ uhci_insert_qh(uhci, &uhci->skel_bulk_qh, nurbp->qh); nurbp->queued = 0; } else { struct urb_priv *purbp; struct uhci_td *ptd; /* We're somewhere in the middle (or end). A bit trickier */ /* than the head scenario */ purbp = list_entry(urbp->urb_queue_list.prev, struct urb_priv, urb_queue_list); ptd = list_entry(purbp->list.prev, struct uhci_td, list); if (nurbp->queued) /* Close the gap between the two */ ptd->link = virt_to_bus(list_entry(nurbp->list.next, struct uhci_td, list)); else /* The next URB happens to be the beggining, so */ /* we're the last, end the chain */ ptd->link = UHCI_PTR_TERM; } list_del(&urbp->urb_queue_list); spin_unlock_irqrestore(&uhci_append_urb_lock, flags);}struct urb_priv *uhci_alloc_urb_priv(struct urb *urb){ struct urb_priv *urbp; urbp = kmem_cache_alloc(uhci_up_cachep, in_interrupt() ? SLAB_ATOMIC : SLAB_KERNEL); if (!urbp) return NULL; memset((void *)urbp, 0, sizeof(*urbp)); urbp->inserttime = jiffies; urbp->urb = urb; INIT_LIST_HEAD(&urbp->list); INIT_LIST_HEAD(&urbp->urb_queue_list); urb->hcpriv = urbp; return urbp;}static void uhci_add_td_to_urb(struct urb *urb, struct uhci_td *td){ struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv; td->urb = urb; list_add_tail(&td->list, &urbp->list);}static void uhci_remove_td_from_urb(struct urb *urb, struct uhci_td *td){ urb = NULL; /* No warnings */ if (list_empty(&td->list)) return; list_del(&td->list); INIT_LIST_HEAD(&td->list); td->urb = NULL;}static void uhci_destroy_urb_priv(struct urb *urb){ struct list_head *tmp, *head; struct urb_priv *urbp; struct uhci *uhci; struct uhci_td *td; unsigned long flags; spin_lock_irqsave(&urb->lock, flags); urbp = (struct urb_priv *)urb->hcpriv; if (!urbp) goto unlock; if (!urb->dev || !urb->dev->bus || !urb->dev->bus->hcpriv) goto unlock; uhci = urb->dev->bus->hcpriv; head = &urbp->list; tmp = head->next; while (tmp != head) { td = list_entry(tmp, struct uhci_td, list); tmp = tmp->next; uhci_remove_td_from_urb(urb, td); uhci_remove_td(uhci, td); uhci_free_td(td); } urb->hcpriv = NULL; kmem_cache_free(uhci_up_cachep, urbp);unlock: spin_unlock_irqrestore(&urb->lock, flags);}static void uhci_inc_fsbr(struct uhci *uhci, struct urb *urb){ unsigned long flags; struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv; if (!urbp) return; spin_lock_irqsave(&uhci->framelist_lock, flags); if ((!(urb->transfer_flags & USB_NO_FSBR)) && (!urbp->fsbr)) { urbp->fsbr = 1; if (!uhci->fsbr++) uhci->skel_term_qh.link = virt_to_bus(&uhci->skel_hs_control_qh) | UHCI_PTR_QH; } spin_unlock_irqrestore(&uhci->framelist_lock, flags);}static void uhci_dec_fsbr(struct uhci *uhci, struct urb *urb){ unsigned long flags; struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv; if (!urbp) return; spin_lock_irqsave(&uhci->framelist_lock, flags); if ((!(urb->transfer_flags & USB_NO_FSBR)) && urbp->fsbr) { urbp->fsbr = 0; if (!--uhci->fsbr) uhci->skel_term_qh.link = UHCI_PTR_TERM; } spin_unlock_irqrestore(&uhci->framelist_lock, flags);}/* * Map status to standard result codes * * <status> is (td->status & 0xFE0000) [a.k.a. uhci_status_bits(td->status)] * <dir_out> is True for output TDs and False for input TDs. */static int uhci_map_status(int status, int dir_out){ if (!status) return 0; if (status & TD_CTRL_BITSTUFF) /* Bitstuff error */ return -EPROTO; if (status & TD_CTRL_CRCTIMEO) { /* CRC/Timeout */ if (dir_out) return -ETIMEDOUT; else return -EILSEQ; } if (status & TD_CTRL_NAK) /* NAK */ return -ETIMEDOUT; if (status & TD_CTRL_BABBLE) /* Babble */ return -EPIPE; if (status & TD_CTRL_DBUFERR) /* Buffer error */ return -ENOSR; if (status & TD_CTRL_STALLED) /* Stalled */ return -EPIPE; if (status & TD_CTRL_ACTIVE) /* Active */ return 0; return -EINVAL;}/* * Control transfers */static int uhci_submit_control(struct urb *urb)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -