📄 uhci.c
字号:
/*
* Universal Host Controller Interface driver for USB.
*
* Maintainer: Johannes Erdfelt <johannes@erdfelt.com>
*
* (C) Copyright 1999 Linus Torvalds
* (C) Copyright 1999-2002 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 "../core/hcd.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 DEBUG
static int debug = 1;
#else
static int debug = 0;
#endif
MODULE_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 FSBR_DELAY (HZ / 20) /* 50 ms */
/* When we timeout an idle transfer for FSBR, we'll switch it over to */
/* depth first traversal. We'll do it in groups of this number of TD's */
/* to make sure it doesn't hog all of the bandwidth */
#define DEPTH_INTERVAL 5
/*
* Technically, updating td->status here is a race, but it's not really a
* problem. The worst that can happen is that we set the IOC bit again
* generating a spurios interrupt. We could fix this by creating another
* QH and leaving the IOC bit always set, but then we would have to play
* games with the FSBR code to make sure we get the correct order in all
* the cases. I don't think it's worth the effort
*/
static inline void uhci_set_next_interrupt(struct uhci *uhci)
{
unsigned long flags;
spin_lock_irqsave(&uhci->frame_list_lock, flags);
uhci->skel_term_td->status |= TD_CTRL_IOC;
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);
uhci->skel_term_td->status &= ~TD_CTRL_IOC;
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_get_dev(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 */
spin_lock_irqsave(&uhci->frame_list_lock, flags);
if (td->frame == -1 && list_empty(&td->fl_list))
goto out;
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;
out:
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_put_dev(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;
qh->urbp = NULL;
INIT_LIST_HEAD(&qh->list);
INIT_LIST_HEAD(&qh->remove_list);
usb_get_dev(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_put_dev(qh->dev);
pci_pool_free(uhci->qh_pool, qh, qh->dma_handle);
}
/*
* MUST be called with uhci->frame_list_lock acquired
*/
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 uhci_qh *qh)
{
unsigned long flags;
struct uhci_qh *pqh;
if (!qh)
return;
qh->urbp = NULL;
/* Only go through the hoops if it's actually linked in */
spin_lock_irqsave(&uhci->frame_list_lock, flags);
if (!list_empty(&qh->list)) {
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)
td->info |= TD_TOKEN_TOGGLE;
else
td->info &= ~TD_TOKEN_TOGGLE;
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 =
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -