📄 uhci.c
字号:
/* * Universal Host Controller Interface driver for USB. * * Maintainer: Johannes Erdfelt <johannes@erdfelt.com> * * (C) Copyright 1999 Linus Torvalds * (C) Copyright 1999-2001 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/slab.h>#include <linux/smp_lock.h>#include <linux/errno.h>#include <linux/unistd.h>#include <linux/interrupt.h>#include <linux/spinlock.h>#include <linux/proc_fs.h>#ifdef CONFIG_USB_DEBUG#define DEBUG#else#undef DEBUG#endif#include <linux/usb.h>#include <asm/uaccess.h>#include <asm/io.h>#include <asm/irq.h>#include <asm/system.h>#include "uhci.h"#include <linux/pm.h>/* * Version Information */#define DRIVER_VERSION "v1.1"#define DRIVER_AUTHOR "Linus 'Frodo Rabbit' Torvalds, Johannes Erdfelt, Randy Dunlap, Georg Acher, Deti Fliegl, Thomas Sailer, Roman Weissgaerber"#define DRIVER_DESC "USB Universal Host Controller Interface driver"/* * debug = 0, no debugging messages * debug = 1, dump failed URB's except for stalls * debug = 2, dump all failed URB's (including stalls) * show all queues in /proc/uhci/hc* * debug = 3, show all TD's in URB's when dumping */#ifdef DEBUGstatic int debug = 1;#elsestatic int debug = 0;#endifMODULE_PARM(debug, "i");MODULE_PARM_DESC(debug, "Debug level");static char *errbuf;#define ERRBUF_LEN (PAGE_SIZE * 8)#include "uhci-debug.h"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_urb(struct urb *urb);static void uhci_unlink_generic(struct uhci *uhci, struct urb *urb);static void uhci_call_completion(struct urb *urb);static int ports_active(struct uhci *uhci);static void suspend_hc(struct uhci *uhci);static void wakeup_hc(struct uhci *uhci);/* If a transfer is still active after this much time, turn off FSBR */#define IDLE_TIMEOUT (HZ / 20) /* 50 ms */#define MAX_URB_LOOP 2048 /* Maximum number of linked URB's *//* * 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){ return 0;}static inline void uhci_set_next_interrupt(struct uhci *uhci){ unsigned long flags; spin_lock_irqsave(&uhci->frame_list_lock, flags); set_bit(TD_CTRL_IOC_BIT, &uhci->skel_term_td->status); spin_unlock_irqrestore(&uhci->frame_list_lock, flags);}static inline void uhci_clear_next_interrupt(struct uhci *uhci){ unsigned long flags; spin_lock_irqsave(&uhci->frame_list_lock, flags); clear_bit(TD_CTRL_IOC_BIT, &uhci->skel_term_td->status); spin_unlock_irqrestore(&uhci->frame_list_lock, flags);}static inline void uhci_add_complete(struct urb *urb){ struct uhci *uhci = (struct uhci *)urb->dev->bus->hcpriv; struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv; unsigned long flags; spin_lock_irqsave(&uhci->complete_list_lock, flags); list_add(&urbp->complete_list, &uhci->complete_list); spin_unlock_irqrestore(&uhci->complete_list_lock, flags);}static struct uhci_td *uhci_alloc_td(struct uhci *uhci, struct usb_device *dev){ dma_addr_t dma_handle; struct uhci_td *td; td = pci_pool_alloc(uhci->td_pool, GFP_DMA | GFP_ATOMIC, &dma_handle); if (!td) return NULL; td->dma_handle = dma_handle; td->link = UHCI_PTR_TERM; td->buffer = 0; td->frame = -1; td->dev = dev; INIT_LIST_HEAD(&td->list); INIT_LIST_HEAD(&td->fl_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; struct uhci_td *ltd; spin_lock_irqsave(&uhci->frame_list_lock, flags); ltd = list_entry(skeltd->fl_list.prev, struct uhci_td, fl_list); td->link = ltd->link; mb(); ltd->link = td->dma_handle; list_add_tail(&td->fl_list, &skeltd->fl_list); spin_unlock_irqrestore(&uhci->frame_list_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; framenum %= UHCI_NUMFRAMES; spin_lock_irqsave(&uhci->frame_list_lock, flags); td->frame = framenum; /* Is there a TD already mapped there? */ if (uhci->fl->frame_cpu[framenum]) { struct uhci_td *ftd, *ltd; ftd = uhci->fl->frame_cpu[framenum]; ltd = list_entry(ftd->fl_list.prev, struct uhci_td, fl_list); list_add_tail(&td->fl_list, &ftd->fl_list); td->link = ltd->link; mb(); ltd->link = td->dma_handle; } else { td->link = uhci->fl->frame[framenum]; mb(); uhci->fl->frame[framenum] = td->dma_handle; uhci->fl->frame_cpu[framenum] = td; } spin_unlock_irqrestore(&uhci->frame_list_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->frame == -1 && list_empty(&td->fl_list)) return; spin_lock_irqsave(&uhci->frame_list_lock, flags); if (td->frame != -1 && uhci->fl->frame_cpu[td->frame] == td) { if (list_empty(&td->fl_list)) { uhci->fl->frame[td->frame] = td->link; uhci->fl->frame_cpu[td->frame] = NULL; } else { struct uhci_td *ntd; ntd = list_entry(td->fl_list.next, struct uhci_td, fl_list); uhci->fl->frame[td->frame] = ntd->dma_handle; uhci->fl->frame_cpu[td->frame] = ntd; } } else { struct uhci_td *ptd; ptd = list_entry(td->fl_list.prev, struct uhci_td, fl_list); ptd->link = td->link; } mb(); td->link = UHCI_PTR_TERM; list_del_init(&td->fl_list); td->frame = -1; spin_unlock_irqrestore(&uhci->frame_list_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, *ptd; if (list_empty(&urbp->td_list)) return; head = &urbp->td_list; tmp = head->next; /* Ordering isn't important here yet since the QH hasn't been */ /* inserted into the schedule yet */ td = list_entry(tmp, struct uhci_td, list); /* Add the first TD to the QH element pointer */ qh->element = td->dma_handle | (breadth ? 0 : UHCI_PTR_DEPTH); ptd = 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; ptd->link = td->dma_handle | (breadth ? 0 : UHCI_PTR_DEPTH); ptd = td; } ptd->link = UHCI_PTR_TERM;}static void uhci_free_td(struct uhci *uhci, struct uhci_td *td){ if (!list_empty(&td->list) || !list_empty(&td->fl_list)) dbg("td is still in URB list!"); if (td->dev) usb_dec_dev_use(td->dev); pci_pool_free(uhci->td_pool, td, td->dma_handle);}static struct uhci_qh *uhci_alloc_qh(struct uhci *uhci, struct usb_device *dev){ dma_addr_t dma_handle; struct uhci_qh *qh; qh = pci_pool_alloc(uhci->qh_pool, GFP_DMA | GFP_ATOMIC, &dma_handle); if (!qh) return NULL; qh->dma_handle = dma_handle; qh->element = UHCI_PTR_TERM; qh->link = UHCI_PTR_TERM; qh->dev = dev; INIT_LIST_HEAD(&qh->list); INIT_LIST_HEAD(&qh->remove_list); usb_inc_dev_use(dev); return qh;}static void uhci_free_qh(struct uhci *uhci, struct uhci_qh *qh){ if (!list_empty(&qh->list)) dbg("qh list not empty!"); if (!list_empty(&qh->remove_list)) dbg("qh still in remove_list!"); if (qh->dev) usb_dec_dev_use(qh->dev); pci_pool_free(uhci->qh_pool, qh, qh->dma_handle);}static void _uhci_insert_qh(struct uhci *uhci, struct uhci_qh *skelqh, struct urb *urb){ struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv; struct list_head *head, *tmp; struct uhci_qh *lqh; /* Grab the last QH */ lqh = list_entry(skelqh->list.prev, struct uhci_qh, list); if (lqh->urbp) { head = &lqh->urbp->queue_list; tmp = head->next; while (head != tmp) { struct urb_priv *turbp = list_entry(tmp, struct urb_priv, queue_list); tmp = tmp->next; turbp->qh->link = urbp->qh->dma_handle | UHCI_PTR_QH; } } head = &urbp->queue_list; tmp = head->next; while (head != tmp) { struct urb_priv *turbp = list_entry(tmp, struct urb_priv, queue_list); tmp = tmp->next; turbp->qh->link = lqh->link; } urbp->qh->link = lqh->link; mb(); /* Ordering is important */ lqh->link = urbp->qh->dma_handle | UHCI_PTR_QH; list_add_tail(&urbp->qh->list, &skelqh->list);}static void uhci_insert_qh(struct uhci *uhci, struct uhci_qh *skelqh, struct urb *urb){ unsigned long flags; spin_lock_irqsave(&uhci->frame_list_lock, flags); _uhci_insert_qh(uhci, skelqh, urb); spin_unlock_irqrestore(&uhci->frame_list_lock, flags);}static void uhci_remove_qh(struct uhci *uhci, struct urb *urb){ struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv; unsigned long flags; struct uhci_qh *qh = urbp->qh, *pqh; if (!qh) return; /* Only go through the hoops if it's actually linked in */ if (!list_empty(&qh->list)) { qh->urbp = NULL; spin_lock_irqsave(&uhci->frame_list_lock, flags); pqh = list_entry(qh->list.prev, struct uhci_qh, list); if (pqh->urbp) { struct list_head *head, *tmp; head = &pqh->urbp->queue_list; tmp = head->next; while (head != tmp) { struct urb_priv *turbp = list_entry(tmp, struct urb_priv, queue_list); tmp = tmp->next; turbp->qh->link = qh->link; } } pqh->link = qh->link; mb(); qh->element = qh->link = UHCI_PTR_TERM; list_del_init(&qh->list); spin_unlock_irqrestore(&uhci->frame_list_lock, flags); } spin_lock_irqsave(&uhci->qh_remove_list_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); list_add(&qh->remove_list, &uhci->qh_remove_list); spin_unlock_irqrestore(&uhci->qh_remove_list_lock, flags);}static int uhci_fixup_toggle(struct urb *urb, unsigned int toggle){ struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv; struct list_head *head, *tmp; head = &urbp->td_list; tmp = head->next; while (head != tmp) { struct uhci_td *td = list_entry(tmp, struct uhci_td, list); tmp = tmp->next; if (toggle) set_bit(TD_TOKEN_TOGGLE, &td->info); else clear_bit(TD_TOKEN_TOGGLE, &td->info); toggle ^= 1; } return toggle;}/* This function will append one URB's QH to another URB's QH. This is for *//* USB_QUEUE_BULK support for bulk transfers and soon implicitily for *//* control transfers */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 *lltd; unsigned long flags; eurbp = eurb->hcpriv; urbp = urb->hcpriv; spin_lock_irqsave(&uhci->frame_list_lock, flags); /* Find the first URB in the queue */ if (eurbp->queued) { struct list_head *head = &eurbp->queue_list; tmp = head->next; while (tmp != head) { struct urb_priv *turbp = list_entry(tmp, struct urb_priv, queue_list); if (!turbp->queued) break; tmp = tmp->next; } } else tmp = &eurbp->queue_list; furbp = list_entry(tmp, struct urb_priv, queue_list); lurbp = list_entry(furbp->queue_list.prev, struct urb_priv, queue_list); lltd = list_entry(lurbp->td_list.prev, struct uhci_td, list); usb_settoggle(urb->dev, usb_pipeendpoint(urb->pipe), usb_pipeout(urb->pipe), uhci_fixup_toggle(urb, uhci_toggle(lltd->info) ^ 1)); /* All qh's in the queue need to link to the next queue */ urbp->qh->link = eurbp->qh->link; mb(); /* Make sure we flush everything */ /* Only support bulk right now, so no depth */ lltd->link = urbp->qh->dma_handle | UHCI_PTR_QH; list_add_tail(&urbp->queue_list, &furbp->queue_list); urbp->queued = 1;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -