📄 usb-uhci.c
字号:
/* * Universal Host Controller Interface driver for USB (take II). * * (c) 1999-2000 Georg Acher, acher@in.tum.de (executive slave) (base guitar) * Deti Fliegl, deti@fliegl.de (executive slave) (lead voice) * Thomas Sailer, sailer@ife.ee.ethz.ch (chief consultant) (cheer leader) * Roman Weissgaerber, weissg@vienna.at (virt root hub) (studio porter) * (c) 2000 Yggdrasil Computing, Inc. (port of new PCI interface support * from usb-ohci.c by Adam Richter, adam@yggdrasil.com). * (C) 2000 David Brownell, david-b@pacbell.net (usb-ohci.c) * * HW-initalization based on material of * * (C) Copyright 1999 Linus Torvalds * (C) Copyright 1999 Johannes Erdfelt * (C) Copyright 1999 Randy Dunlap * (C) Copyright 1999 Gregory P. Smith * * $Id: usb-uhci.c,v 1.251 2000/11/30 09:47:54 acher Exp $ */#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 <linux/unistd.h>#include <linux/interrupt.h> /* for in_interrupt() */#include <linux/init.h>#include <linux/version.h>#include <linux/pm.h>#include <asm/uaccess.h>#include <asm/io.h>#include <asm/irq.h>#include <asm/system.h>/* This enables more detailed sanity checks in submit_iso *///#define ISO_SANITY_CHECK/* This enables debug printks */#define DEBUG/* This enables all symbols to be exported, to ease debugging oopses *///#define DEBUG_SYMBOLS/* This enables an extra UHCI slab for memory debugging */#define DEBUG_SLAB#define VERSTR "$Revision: 1.251 $ time " __TIME__ " " __DATE__#include <linux/usb.h>#include "usb-uhci.h"#include "usb-uhci-debug.h"#undef DEBUG#undef dbg#define dbg(format, arg...) do {} while (0)#define DEBUG_SYMBOLS#ifdef DEBUG_SYMBOLS #define _static #ifndef EXPORT_SYMTAB #define EXPORT_SYMTAB #endif#else #define _static static#endif#define queue_dbg dbg //err#define async_dbg dbg //err#ifdef DEBUG_SLAB static kmem_cache_t *uhci_desc_kmem; static kmem_cache_t *urb_priv_kmem;#endif#define SLAB_FLAG (in_interrupt ()? SLAB_ATOMIC : SLAB_KERNEL)#define KMALLOC_FLAG (in_interrupt ()? GFP_ATOMIC : GFP_KERNEL)#define CONFIG_USB_UHCI_HIGH_BANDWIDTH #define USE_CTRL_DEPTH_FIRST 0 // 0: Breadth first, 1: Depth first#define USE_BULK_DEPTH_FIRST 0 // 0: Breadth first, 1: Depth first// stop bandwidth reclamation after (roughly) 50ms#define IDLE_TIMEOUT (HZ/20)_static int rh_submit_urb (urb_t *urb);_static int rh_unlink_urb (urb_t *urb);_static int delete_qh (uhci_t *s, uhci_desc_t *qh);_static int process_transfer (uhci_t *s, urb_t *urb, int mode);_static int process_interrupt (uhci_t *s, urb_t *urb);_static int process_iso (uhci_t *s, urb_t *urb, int force);// How much URBs with ->next are walked#define MAX_NEXT_COUNT 2048static uhci_t *devs = NULL;/* used by userspace UHCI data structure dumper */uhci_t **uhci_devices = &devs;/*-------------------------------------------------------------------*/// Cleans up collected QHs, but not more than 100 in one govoid clean_descs(uhci_t *s, int force){ struct list_head *q; uhci_desc_t *qh; int now=UHCI_GET_CURRENT_FRAME(s), n=0; q=s->free_desc.prev; while (q != &s->free_desc && (force || n<100)) { qh = list_entry (q, uhci_desc_t, horizontal); q=qh->horizontal.prev; if ((qh->last_used!=now) || force) delete_qh(s,qh); n++; }}/*-------------------------------------------------------------------*/_static void uhci_switch_timer_int(uhci_t *s){ if (!list_empty(&s->urb_unlinked)) { s->td1ms->hw.td.status |= TD_CTRL_IOC; } else { s->td1ms->hw.td.status &= ~TD_CTRL_IOC; } if (s->timeout_urbs) { s->td32ms->hw.td.status |= TD_CTRL_IOC; } else { s->td32ms->hw.td.status &= ~TD_CTRL_IOC; } wmb();}/*-------------------------------------------------------------------*/#ifdef CONFIG_USB_UHCI_HIGH_BANDWIDTH_static void enable_desc_loop(uhci_t *s, urb_t *urb){ int flags; if (urb->transfer_flags & USB_NO_FSBR) return; spin_lock_irqsave (&s->qh_lock, flags); s->chain_end->hw.qh.head&=~UHCI_PTR_TERM; mb(); s->loop_usage++; ((urb_priv_t*)urb->hcpriv)->use_loop=1; spin_unlock_irqrestore (&s->qh_lock, flags);}/*-------------------------------------------------------------------*/_static void disable_desc_loop(uhci_t *s, urb_t *urb){ int flags; if (urb->transfer_flags & USB_NO_FSBR) return; spin_lock_irqsave (&s->qh_lock, flags); if (((urb_priv_t*)urb->hcpriv)->use_loop) { s->loop_usage--; if (!s->loop_usage) { s->chain_end->hw.qh.head|=UHCI_PTR_TERM; mb(); } ((urb_priv_t*)urb->hcpriv)->use_loop=0; } spin_unlock_irqrestore (&s->qh_lock, flags);}#endif/*-------------------------------------------------------------------*/_static void queue_urb_unlocked (uhci_t *s, urb_t *urb){ struct list_head *p=&urb->urb_list;#ifdef CONFIG_USB_UHCI_HIGH_BANDWIDTH { int type; type=usb_pipetype (urb->pipe); if ((type == PIPE_BULK) || (type == PIPE_CONTROL)) enable_desc_loop(s, urb); }#endif ((urb_priv_t*)urb->hcpriv)->started=jiffies; list_add (p, &s->urb_list); if (urb->timeout) s->timeout_urbs++; uhci_switch_timer_int(s);}/*-------------------------------------------------------------------*/_static void queue_urb (uhci_t *s, urb_t *urb){ unsigned long flags=0; spin_lock_irqsave (&s->urb_list_lock, flags); queue_urb_unlocked(s,urb); spin_unlock_irqrestore (&s->urb_list_lock, flags);}/*-------------------------------------------------------------------*/_static void dequeue_urb (uhci_t *s, urb_t *urb){#ifdef CONFIG_USB_UHCI_HIGH_BANDWIDTH int type; type=usb_pipetype (urb->pipe); if ((type == PIPE_BULK) || (type == PIPE_CONTROL)) disable_desc_loop(s, urb);#endif list_del (&urb->urb_list); if (urb->timeout && s->timeout_urbs) s->timeout_urbs--;}/*-------------------------------------------------------------------*/_static int alloc_td (uhci_desc_t ** new, int flags){#ifdef DEBUG_SLAB *new= kmem_cache_alloc(uhci_desc_kmem, SLAB_FLAG);#else *new = (uhci_desc_t *) kmalloc (sizeof (uhci_desc_t), KMALLOC_FLAG);#endif if (!*new) return -ENOMEM; memset (*new, 0, sizeof (uhci_desc_t)); (*new)->hw.td.link = UHCI_PTR_TERM | (flags & UHCI_PTR_BITS); // last by default (*new)->type = TD_TYPE; mb(); INIT_LIST_HEAD (&(*new)->vertical); INIT_LIST_HEAD (&(*new)->horizontal); return 0;}/*-------------------------------------------------------------------*/// append a qh to td.link physically, the SW linkage is not affected_static void append_qh(uhci_t *s, uhci_desc_t *td, uhci_desc_t* qh, int flags){ unsigned long xxx; spin_lock_irqsave (&s->td_lock, xxx); td->hw.td.link = virt_to_bus (qh) | (flags & UHCI_PTR_DEPTH) | UHCI_PTR_QH; mb(); spin_unlock_irqrestore (&s->td_lock, xxx);}/*-------------------------------------------------------------------*//* insert td at last position in td-list of qh (vertical) */_static int insert_td (uhci_t *s, uhci_desc_t *qh, uhci_desc_t* new, int flags){ uhci_desc_t *prev; unsigned long xxx; spin_lock_irqsave (&s->td_lock, xxx); list_add_tail (&new->vertical, &qh->vertical); prev = list_entry (new->vertical.prev, uhci_desc_t, vertical); if (qh == prev ) { // virgin qh without any tds qh->hw.qh.element = virt_to_bus (new) | UHCI_PTR_TERM; } else { // already tds inserted, implicitely remove TERM bit of prev prev->hw.td.link = virt_to_bus (new) | (flags & UHCI_PTR_DEPTH); } mb(); spin_unlock_irqrestore (&s->td_lock, xxx); return 0;}/*-------------------------------------------------------------------*//* insert new_td after td (horizontal) */_static int insert_td_horizontal (uhci_t *s, uhci_desc_t *td, uhci_desc_t* new){ uhci_desc_t *next; unsigned long flags; spin_lock_irqsave (&s->td_lock, flags); next = list_entry (td->horizontal.next, uhci_desc_t, horizontal); list_add (&new->horizontal, &td->horizontal); new->hw.td.link = td->hw.td.link; td->hw.td.link = virt_to_bus (new); mb(); spin_unlock_irqrestore (&s->td_lock, flags); return 0;}/*-------------------------------------------------------------------*/_static int unlink_td (uhci_t *s, uhci_desc_t *element, int phys_unlink){ uhci_desc_t *next, *prev; int dir = 0; unsigned long flags; spin_lock_irqsave (&s->td_lock, flags); next = list_entry (element->vertical.next, uhci_desc_t, vertical); if (next == element) { dir = 1; prev = list_entry (element->horizontal.prev, uhci_desc_t, horizontal); } else prev = list_entry (element->vertical.prev, uhci_desc_t, vertical); if (phys_unlink) { // really remove HW linking if (prev->type == TD_TYPE) prev->hw.td.link = element->hw.td.link; else prev->hw.qh.element = element->hw.td.link; } mb (); if (dir == 0) list_del (&element->vertical); else list_del (&element->horizontal); spin_unlock_irqrestore (&s->td_lock, flags); return 0;}/*-------------------------------------------------------------------*/_static int delete_desc (uhci_desc_t *element){#ifdef DEBUG_SLAB kmem_cache_free(uhci_desc_kmem, element);#else kfree (element);#endif return 0;}/*-------------------------------------------------------------------*/// Allocates qh element_static int alloc_qh (uhci_desc_t ** new){#ifdef DEBUG_SLAB *new= kmem_cache_alloc(uhci_desc_kmem, SLAB_FLAG);#else *new = (uhci_desc_t *) kmalloc (sizeof (uhci_desc_t), KMALLOC_FLAG);#endif if (!*new) return -ENOMEM; memset (*new, 0, sizeof (uhci_desc_t)); (*new)->hw.qh.head = UHCI_PTR_TERM; (*new)->hw.qh.element = UHCI_PTR_TERM; (*new)->type = QH_TYPE; mb(); INIT_LIST_HEAD (&(*new)->horizontal); INIT_LIST_HEAD (&(*new)->vertical); dbg("Allocated qh @ %p", *new); return 0;}/*-------------------------------------------------------------------*/// inserts new qh before/after the qh at pos// flags: 0: insert before pos, 1: insert after pos (for low speed transfers)_static int insert_qh (uhci_t *s, uhci_desc_t *pos, uhci_desc_t *new, int order){ uhci_desc_t *old; unsigned long flags; spin_lock_irqsave (&s->qh_lock, flags); if (!order) { // (OLD) (POS) -> (OLD) (NEW) (POS) old = list_entry (pos->horizontal.prev, uhci_desc_t, horizontal); list_add_tail (&new->horizontal, &pos->horizontal); new->hw.qh.head = MAKE_QH_ADDR (pos) ; if (!(old->hw.qh.head & UHCI_PTR_TERM)) old->hw.qh.head = MAKE_QH_ADDR (new) ; } else { // (POS) (OLD) -> (POS) (NEW) (OLD) old = list_entry (pos->horizontal.next, uhci_desc_t, horizontal); list_add (&new->horizontal, &pos->horizontal); new->hw.qh.head = MAKE_QH_ADDR (old); pos->hw.qh.head = MAKE_QH_ADDR (new) ; } mb (); spin_unlock_irqrestore (&s->qh_lock, flags); return 0;}/*-------------------------------------------------------------------*/_static int unlink_qh (uhci_t *s, uhci_desc_t *element){ uhci_desc_t *prev; unsigned long flags; spin_lock_irqsave (&s->qh_lock, flags); prev = list_entry (element->horizontal.prev, uhci_desc_t, horizontal); prev->hw.qh.head = element->hw.qh.head; dbg("unlink qh %p, pqh %p, nxqh %p, to %08x", element, prev, list_entry (element->horizontal.next, uhci_desc_t, horizontal),element->hw.qh.head &~15); list_del(&element->horizontal); mb (); spin_unlock_irqrestore (&s->qh_lock, flags); return 0;}/*-------------------------------------------------------------------*/_static int delete_qh (uhci_t *s, uhci_desc_t *qh){ uhci_desc_t *td; struct list_head *p; list_del (&qh->horizontal); while ((p = qh->vertical.next) != &qh->vertical) { td = list_entry (p, uhci_desc_t, vertical); dbg("unlink td @ %p",td); unlink_td (s, td, 0); // no physical unlink delete_desc (td); } delete_desc (qh); return 0;}/*-------------------------------------------------------------------*/_static void clean_td_chain (uhci_desc_t *td){ struct list_head *p; uhci_desc_t *td1; if (!td) return; while ((p = td->horizontal.next) != &td->horizontal) { td1 = list_entry (p, uhci_desc_t, horizontal); delete_desc (td1); } delete_desc (td);}/*-------------------------------------------------------------------*/_static void fill_td (uhci_desc_t *td, int status, int info, __u32 buffer){ td->hw.td.status = status; td->hw.td.info = info; td->hw.td.buffer = buffer;}/*-------------------------------------------------------------------*/// Removes ALL qhs in chain (paranoia!)_static void cleanup_skel (uhci_t *s){ unsigned int n; uhci_desc_t *td; dbg("cleanup_skel"); clean_descs(s,1); if (s->td32ms) {
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -