/* * Universal Host Controller Interface driver for USB (take II). * * (c) 1999 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) * * HW-initalization based on material of * * (C) Copyright 1999 Linus Torvalds * (C) Copyright 1999 Johannes Erdfelt * (C) Copyright 1999 Randy Dunlap * * $Id: usb-uhci.c,v 1.222 2000/03/13 21:18:02 fliegl Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #include /* for in_interrupt() */ #include #include #if LINUX_VERSION_CODE > KERNEL_VERSION(2,3,44) #include #endif #include #include #include #include /* 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 #include #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); static uhci_t *devs = NULL; /* used by userspace UHCI data structure dumper */ uhci_t **uhci_devices = &devs; /*-------------------------------------------------------------------*/ // Cleans up collected QHs void clean_descs(uhci_t *s, int force) { struct list_head *q; uhci_desc_t *qh; int now=UHCI_GET_CURRENT_FRAME(s); q=s->free_desc.prev; while (q != &s->free_desc) { qh = list_entry (q, uhci_desc_t, horizontal); if ((qh->last_used!=now) || force) delete_qh(s,qh); q=qh->horizontal.prev; } } /*-------------------------------------------------------------------*/ #ifdef CONFIG_USB_UHCI_HIGH_BANDWIDTH _static void enable_desc_loop(uhci_t *s, urb_t *urb) { int flags; 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; 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); } /*-------------------------------------------------------------------*/ _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); } /*-------------------------------------------------------------------*/ _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); } 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; 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); for (n = 0; n < 8; n++) { td = s->int_chain[n]; clean_td_chain (td); } if (s->iso_td) { for (n = 0; n < 1024; n++) { td = s->iso_td[n]; clean_td_chain (td); } kfree (s->iso_td); } if (s->framelist) free_page ((unsigned long) s->framelist); if (s->control_chain) { // completed init_skel? struct list_head *p; uhci_desc_t *qh, *qh1; qh = s->control_chain; while ((p = qh->horizontal.next) != &qh->horizontal) { qh1 = list_entry (p, uhci_desc_t, horizontal); delete_qh (s, qh1); } delete_qh (s, qh); } else { if (s->ls_control_chain) delete_desc (s->ls_control_chain); if (s->control_chain) delete_desc(s->control_chain); if (s->bulk_chain) delete_desc (s->bulk_chain); if (s->chain_end) delete_desc (s->chain_end); } dbg("cleanup_skel finished"); } /*-------------------------------------------------------------------*/ // allocates framelist and qh-skeletons // only HW-links provide continous linking, SW-links stay in their domain (ISO/INT) _static int init_skel (uhci_t *s) { int n, ret; uhci_desc_t *qh, *td; dbg("init_skel"); s->framelist = (__u32 *) get_free_page (GFP_KERNEL); if (!s->framelist) return -ENOMEM; memset (s->framelist, 0, 4096); dbg("allocating iso desc pointer list"); s->iso_td = (uhci_desc_t **) kmalloc (1024 * sizeof (uhci_desc_t*), GFP_KERNEL); if (!s->iso_td) goto init_skel_cleanup; s->ls_control_chain = NULL; s->control_chain = NULL; s->bulk_chain = NULL; s->chain_end = NULL; dbg("allocating iso descs"); for (n = 0; n < 1024; n++) { // allocate skeleton iso/irq-tds ret = alloc_td (&td, 0); if (ret) goto init_skel_cleanup; s->iso_td[n] = td; s->framelist[n] = ((__u32) virt_to_bus (td)); } dbg("allocating qh: chain_end"); ret = alloc_qh (&qh); if (ret) goto init_skel_cleanup; s->chain_end = qh; ret = alloc_td (&td, 0); if (ret) goto init_skel_cleanup; fill_td (td, TD_CTRL_IOC, 0, 0); // generate 1ms interrupt insert_td (s, qh, td, 0); dbg("allocating qh: bulk_chain"); ret = alloc_qh (&qh); if (ret) goto init_skel_cleanup; insert_qh (s, s->chain_end, qh, 0); s->bulk_chain = qh; dbg("allocating qh: control_chain"); ret = alloc_qh (&qh); if (ret) goto init_skel_cleanup; insert_qh (s, s->bulk_chain, qh, 0); s->control_chain = qh; #ifdef CONFIG_USB_UHCI_HIGH_BANDWIDTH // disabled reclamation loop s->chain_end->hw.qh.head=virt_to_bus(s->control_chain) | UHCI_PTR_QH | UHCI_PTR_TERM; #endif dbg("allocating qh: ls_control_chain"); ret = alloc_qh (&qh); if (ret) goto init_skel_cleanup; insert_qh (s, s->control_chain, qh, 0); s->ls_control_chain = qh; for (n = 0; n < 8; n++) s->int_chain[n] = 0; dbg("allocating skeleton INT-TDs"); for (n = 0; n < 8; n++) { uhci_desc_t *td; alloc_td (&td, 0); if (!td) goto init_skel_cleanup; s->int_chain[n] = td; if (n == 0) { s->int_chain[0]->hw.td.link = virt_to_bus (s->ls_control_chain) | UHCI_PTR_QH; } else { s->int_chain[n]->hw.td.link = virt_to_bus (s->int_chain[0]); } } dbg("Linking skeleton INT-TDs"); for (n = 0; n < 1024; n++) { // link all iso-tds to the interrupt chains int m, o; dbg("framelist[%i]=%x",n,s->framelist[n]); if ((n&127)==127) ((uhci_desc_t*) s->iso_td[n])->hw.td.link = virt_to_bus(s->int_chain[0]); else for (o = 1, m = 2; m <= 128; o++, m += m) if ((n & (m - 1)) == ((m - 1) / 2)) ((uhci_desc_t*) s->iso_td[n])->hw.td.link = virt_to_bus (s->int_chain[o]); } mb(); //uhci_show_queue(s->control_chain); dbg("init_skel exit"); return 0; init_skel_cleanup: cleanup_skel (s); return -ENOMEM; } /*-------------------------------------------------------------------*/ // LOW LEVEL STUFF // assembles QHs und TDs for control, bulk and iso /*-------------------------------------------------------------------*/ _static int uhci_submit_control_urb (urb_t *urb) { uhci_desc_t *qh, *td; uhci_t *s = (uhci_t*) urb->dev->bus->hcpriv; urb_priv_t *urb_priv = urb->hcpriv; unsigned long destination, status; int maxsze = usb_maxpacket (urb->dev, urb->pipe, usb_pipeout (urb->pipe)); unsigned long len; char *data; int depth_first=USE_CTRL_DEPTH_FIRST; // UHCI descriptor chasing method if (!maxsze) { err("uhci_submit_control_urb: pipesize for pipe %x is zero", urb->pipe); return -EINVAL; } dbg("uhci_submit_control start"); alloc_qh (&qh); // alloc qh for this request if (!qh) return -ENOMEM; alloc_td (&td, UHCI_PTR_DEPTH * depth_first); // get td for setup stage if (!td) { delete_qh (s, qh); return -ENOMEM; } /* The "pipe" thing contains the destination in bits 8--18 */ destination = (urb->pipe & PIPE_DEVEP_MASK) | USB_PID_SETUP; /* 3 errors */ status = (urb->pipe & TD_CTRL_LS) | TD_CTRL_ACTIVE | (urb->transfer_flags & USB_DISABLE_SPD ? 0 : TD_CTRL_SPD) | (3 << 27); /* Build the TD for the control request, try forever, 8 bytes of data */ fill_td (td, status, destination | (7 << 21), virt_to_bus (urb->setup_packet)); insert_td (s, qh, td, 0); // queue 'setup stage'-td in qh #if 0 { char *sp=urb->setup_packet; dbg("SETUP to pipe %x: %x %x %x %x %x %x %x %x", urb->pipe, sp[0],sp[1],sp[2],sp[3],sp[4],sp[5],sp[6],sp[7]); } //uhci_show_td(td); #endif len = urb->transfer_buffer_length; data = urb->transfer_buffer; /* If direction is "send", change the frame from SETUP (0x2D) to OUT (0xE1). Else change it from SETUP to IN (0x69). */ destination = (urb->pipe & PIPE_DEVEP_MASK) | (usb_pipeout (urb->pipe)?USB_PID_OUT:USB_PID_IN); while (len > 0) { int pktsze = len; alloc_td (&td, UHCI_PTR_DEPTH * depth_first); if (!td) { delete_qh (s, qh); return -ENOMEM; } if (pktsze > maxsze) pktsze = maxsze; destination ^= 1 << TD_TOKEN_TOGGLE; // toggle DATA0/1 fill_td (td, status, destination | ((pktsze - 1) << 21), virt_to_bus (data)); // Status, pktsze bytes of data insert_td (s, qh, td, UHCI_PTR_DEPTH * depth_first); // queue 'data stage'-td in qh data += pktsze; len -= pktsze; } /* Build the final TD for control status */ /* It's only IN if the pipe is out AND we aren't expecting data */ destination &= ~UHCI_PID; if (usb_pipeout (urb->pipe) || (urb->transfer_buffer_length == 0)) destination |= USB_PID_IN; else destination |= USB_PID_OUT; destination |= 1 << TD_TOKEN_TOGGLE; /* End in Data1 */ alloc_td (&td, UHCI_PTR_DEPTH); if (!td) { delete_qh (s, qh); return -ENOMEM; } status &=~TD_CTRL_SPD; /* no limit on errors on final packet , 0 bytes of data */ fill_td (td, status | TD_CTRL_IOC, destination | (UHCI_NULL_DATA_SIZE << 21), 0); insert_td (s, qh, td, UHCI_PTR_DEPTH * depth_first); // queue status td list_add (&qh->desc_list, &urb_priv->desc_list); urb->status = -EINPROGRESS; queue_urb (s, urb); // queue before inserting in desc chain qh->hw.qh.element &= ~UHCI_PTR_TERM; //uhci_show_queue(qh); /* Start it up... put low speed first */ if (urb->pipe & TD_CTRL_LS) insert_qh (s, s->control_chain, qh, 0); else insert_qh (s, s->bulk_chain, qh, 0); dbg("uhci_submit_control end"); return 0; } /*-------------------------------------------------------------------*/ // For queued bulk transfers, two additional QH helpers are allocated (nqh, bqh) // Due to the linking with other bulk urbs, it has to be locked with urb_list_lock! _static int uhci_submit_bulk_urb (urb_t *urb, urb_t *bulk_urb) { uhci_t *s = (uhci_t*) urb->dev->bus->hcpriv; urb_priv_t *urb_priv = urb->hcpriv; uhci_desc_t *qh, *td, *nqh, *bqh; unsigned long destination, status; char *data; unsigned int pipe = urb->pipe; int maxsze = usb_maxpacket (urb->dev, pipe, usb_pipeout (pipe)); int info, len; int depth_first=USE_BULK_DEPTH_FIRST; // UHCI descriptor chasing method urb_priv_t *upriv, *bpriv; if (usb_endpoint_halted (urb->dev, usb_pipeendpoint (pipe), usb_pipeout (pipe))) return -EPIPE; if (urb->transfer_buffer_length < 0) { err("Negative transfer length in submit_bulk"); return -EINVAL; } if (!maxsze) return -EMSGSIZE; queue_dbg("uhci_submit_bulk_urb: urb %p, old %p, pipe %08x, len %i", urb,bulk_urb,urb->pipe,urb->transfer_buffer_length); upriv=(urb_priv_t*)urb->hcpriv; if (!bulk_urb) { alloc_qh (&qh); // get qh for this request if (!qh) return -ENOMEM; if (urb->transfer_flags & USB_QUEUE_BULK) { alloc_qh(&nqh); // placeholder for clean unlink if (!nqh) { delete_desc (qh); return -ENOMEM; } upriv->next_qh = nqh; queue_dbg("new next qh %p",nqh); } } else { bpriv = (urb_priv_t*)bulk_urb->hcpriv; qh = bpriv->bottom_qh; // re-use bottom qh and next qh nqh = bpriv->next_qh; upriv->next_qh=nqh; bpriv->next_queued_urb=urb; upriv->prev_queued_urb=bulk_urb; } queue_dbg("uhci_submit_bulk: qh=%p, nqh=%p\n",bqh,nqh); if (urb->transfer_flags & USB_QUEUE_BULK) { alloc_qh (&bqh); // "bottom" QH, if (!bqh) { if (!bulk_urb) { delete_desc(qh); delete_desc(nqh); } return -ENOMEM; } bqh->hw.qh.element = UHCI_PTR_TERM; bqh->hw.qh.element = virt_to_bus(nqh)|UHCI_PTR_QH; upriv->bottom_qh = bqh; queue_dbg("uhci_submit_bulk: new bqh %p\n",bqh); } /* The "pipe" thing contains the destination in bits 8--18. */ destination = (pipe & PIPE_DEVEP_MASK) | usb_packetid (pipe); /* 3 errors */ status = (pipe & TD_CTRL_LS) | TD_CTRL_ACTIVE | ((urb->transfer_flags & USB_DISABLE_SPD) ? 0 : TD_CTRL_SPD) | (3 << 27); /* Build the TDs for the bulk request */ len = urb->transfer_buffer_length; data = urb->transfer_buffer; do { // TBD: Really allow zero-length packets? int pktsze = len; alloc_td (&td, UHCI_PTR_DEPTH * depth_first); if (!td) { delete_qh (s, qh); return -ENOMEM; } if (pktsze > maxsze) pktsze = maxsze; // pktsze bytes of data info = destination | (((pktsze - 1)&UHCI_NULL_DATA_SIZE) << 21) | (usb_gettoggle (urb->dev, usb_pipeendpoint (pipe), usb_pipeout (pipe)) << TD_TOKEN_TOGGLE); fill_td (td, status, info, virt_to_bus (data)); data += pktsze; len -= pktsze; if (!len) td->hw.td.status |= TD_CTRL_IOC; // last one generates INT insert_td (s, qh, td, UHCI_PTR_DEPTH * depth_first); usb_dotoggle (urb->dev, usb_pipeendpoint (pipe), usb_pipeout (pipe)); } while (len > 0); list_add (&qh->desc_list, &urb_priv->desc_list); if (urb->transfer_flags & USB_QUEUE_BULK) { qh->hw.qh.element&=~UHCI_PTR_TERM; append_qh(s, td, bqh, UHCI_PTR_DEPTH * depth_first); } urb->status = -EINPROGRESS; queue_urb_unlocked (s, urb); qh->hw.qh.element &= ~UHCI_PTR_TERM; if (!bulk_urb) { if (urb->transfer_flags & USB_QUEUE_BULK) { spin_lock (&s->td_lock); // both QHs in one go insert_qh (s, s->chain_end, qh, 0); // Main QH insert_qh (s, s->chain_end, nqh, 0); // Helper QH spin_unlock (&s->td_lock); } else insert_qh (s, s->chain_end, qh, 0); } //uhci_show_queue(s->bulk_chain); //dbg("uhci_submit_bulk_urb: exit\n"); return 0; } /*-------------------------------------------------------------------*/ _static void uhci_clean_iso_step1(uhci_t *s, urb_priv_t *urb_priv) { struct list_head *p; uhci_desc_t *td; for (p = urb_priv->desc_list.next; p != &urb_priv->desc_list; p = p->next) { td = list_entry (p, uhci_desc_t, desc_list); unlink_td (s, td, 1); } } /*-------------------------------------------------------------------*/ _static void uhci_clean_iso_step2(uhci_t *s, urb_priv_t *urb_priv) { struct list_head *p; uhci_desc_t *td; while ((p = urb_priv->desc_list.next) != &urb_priv->desc_list) { td = list_entry (p, uhci_desc_t, desc_list); list_del (p); delete_desc (td); } } /*-------------------------------------------------------------------*/ // mode: 0: unlink + no deletion mark, 1: regular (unlink/delete-mark), 2: don't unlink // looks a bit complicated because of all the bulk queueing goodies _static void uhci_clean_transfer (uhci_t *s, urb_t *urb, uhci_desc_t *qh, int mode) { uhci_desc_t *bqh, *nqh, *prevqh; int now; urb_priv_t *priv=(urb_priv_t*)urb->hcpriv; now=UHCI_GET_CURRENT_FRAME(s); dbg("clean transfer urb %p, qh %p, mode %i",urb,qh,mode); bqh=priv->bottom_qh; if (!priv->next_queued_urb) { // no more appended bulk queues if (mode != 2) unlink_qh (s, qh); if (priv->prev_queued_urb) { urb_priv_t* ppriv=(urb_priv_t*)priv->prev_queued_urb->hcpriv; ppriv->bottom_qh = priv->bottom_qh; ppriv->next_queued_urb = NULL; } else if (bqh) { // queue dead nqh=priv->next_qh; if (mode != 2) unlink_qh(s, nqh); if (mode) { nqh->last_used = bqh->last_used = now; list_add_tail (&nqh->horizontal, &s->free_desc); list_add_tail (&bqh->horizontal, &s->free_desc); } } } else { // there are queued urbs following urb_t *nurb; unsigned long flags; nurb=priv->next_queued_urb; spin_lock_irqsave (&s->qh_lock, flags); if (!priv->prev_queued_urb) { // top if (mode !=2) { prevqh = list_entry (qh->horizontal.prev, uhci_desc_t, horizontal); prevqh->hw.qh.head = virt_to_bus(bqh) | UHCI_PTR_QH; queue_dbg ("TOP relink of %p to %p-%p",qh,prevqh,bqh); list_del (&qh->horizontal); list_add (&bqh->horizontal, &prevqh->horizontal); } } else { //intermediate urb_priv_t* ppriv=(urb_priv_t*)priv->prev_queued_urb->hcpriv; uhci_desc_t * bnqh; bnqh=list_entry (&((urb_priv_t*)(nurb->hcpriv))->desc_list.next, uhci_desc_t, desc_list); ppriv->bottom_qh=bnqh; ppriv->next_queued_urb=nurb; if (mode!=2) { prevqh = list_entry (ppriv->desc_list.next, uhci_desc_t, desc_list); prevqh->hw.qh.head = virt_to_bus(bqh) | UHCI_PTR_QH; queue_dbg ("IM relink of %p to %p-%p",qh,prevqh,bqh); } } mb(); spin_unlock_irqrestore (&s->qh_lock, flags); ((urb_priv_t*)nurb->hcpriv)->prev_queued_urb=priv->prev_queued_urb; } if (mode) { qh->last_used = now; list_add_tail (&qh->horizontal, &s->free_desc); // mark for later deletion } } /*-------------------------------------------------------------------*/ // unlinks an urb by dequeuing its qh, waits some frames and forgets it _static int uhci_unlink_urb_sync (uhci_t *s, urb_t *urb) { uhci_desc_t *qh; urb_priv_t *urb_priv; unsigned long flags=0; spin_lock_irqsave (&s->urb_list_lock, flags); if (urb->status == -EINPROGRESS) { // URB probably still in work dequeue_urb (s, urb); s->unlink_urb_done=1; spin_unlock_irqrestore (&s->urb_list_lock, flags); urb->status = -ENOENT; // mark urb as killed urb_priv = urb->hcpriv; switch (usb_pipetype (urb->pipe)) { case PIPE_INTERRUPT: usb_dotoggle (urb->dev, usb_pipeendpoint (urb->pipe), usb_pipeout (urb->pipe)); case PIPE_ISOCHRONOUS: uhci_clean_iso_step1(s, urb_priv); uhci_wait_ms(1); uhci_clean_iso_step2(s, urb_priv); break; case PIPE_BULK: case PIPE_CONTROL: qh = list_entry (urb_priv->desc_list.next, uhci_desc_t, desc_list); spin_lock_irqsave (&s->urb_list_lock, flags); uhci_clean_transfer(s, urb, qh, 1); spin_unlock_irqrestore (&s->urb_list_lock, flags); uhci_wait_ms(1); } #ifdef DEBUG_SLAB kmem_cache_free (urb_priv_kmem, urb->hcpriv); #else kfree (urb->hcpriv); #endif if (urb->complete) { dbg("unlink_urb: calling completion"); urb->complete ((struct urb *) urb); } usb_dec_dev_use (urb->dev); return 0; } else spin_unlock_irqrestore (&s->urb_list_lock, flags); return 0; } /*-------------------------------------------------------------------*/ // async unlink_urb completion/cleanup work // has to be protected by urb_list_lock! // features: if set in transfer_flags, the resulting status of the killed // transaction is not overwritten _static void uhci_cleanup_unlink(uhci_t *s, int force) { struct list_head *q; urb_t *urb; struct usb_device *dev; int pipe,now; urb_priv_t *urb_priv; q=s->urb_unlinked.next; now=UHCI_GET_CURRENT_FRAME(s); while (q != &s->urb_unlinked) { urb = list_entry (q, urb_t, urb_list); urb_priv = (urb_priv_t*)urb->hcpriv; q = urb->urb_list.next; if (force || ((urb_priv->started != 0xffffffff) && (urb_priv->started != now))) { async_dbg("async cleanup %p",urb); switch (usb_pipetype (urb->pipe)) { // process descriptors case PIPE_CONTROL: process_transfer (s, urb, 2); break; case PIPE_BULK: if (!s->avoid_bulk.counter) process_transfer (s, urb, 2); // don't unlink (already done) else continue; break; case PIPE_ISOCHRONOUS: process_iso (s, urb, 1); // force, don't unlink break; case PIPE_INTERRUPT: process_interrupt (s, urb); break; } if (!(urb->transfer_flags & USB_TIMEOUT_KILLED)) urb->status = -ECONNRESET; // mark as asynchronously killed pipe = urb->pipe; // completion may destroy all... dev = urb->dev; urb_priv = urb->hcpriv; if (urb->complete) { spin_unlock(&s->urb_list_lock); urb->complete ((struct urb *) urb); spin_lock(&s->urb_list_lock); } if (!(urb->transfer_flags & USB_TIMEOUT_KILLED)) urb->status = -ENOENT; // now the urb is really dead usb_dec_dev_use (dev); #ifdef DEBUG_SLAB kmem_cache_free (urb_priv_kmem, urb_priv); #else kfree (urb_priv); #endif switch (usb_pipetype (pipe)) { case PIPE_ISOCHRONOUS: case PIPE_INTERRUPT: uhci_clean_iso_step2(s, urb_priv); break; } list_del (&urb->urb_list); } } } /*-------------------------------------------------------------------*/ _static int uhci_unlink_urb_async (uhci_t *s,urb_t *urb) { uhci_desc_t *qh; urb_priv_t *urb_priv; async_dbg("unlink_urb_async called %p",urb); if (urb->status == -EINPROGRESS) { ((urb_priv_t*)urb->hcpriv)->started = ~0; dequeue_urb (s, urb); list_add_tail (&urb->urb_list, &s->urb_unlinked); // store urb s->unlink_urb_done = 1; urb->status = -ECONNABORTED; // mark urb as "waiting to be killed" urb_priv = (urb_priv_t*)urb->hcpriv; switch (usb_pipetype (urb->pipe)) { case PIPE_INTERRUPT: usb_dotoggle (urb->dev, usb_pipeendpoint (urb->pipe), usb_pipeout (urb->pipe)); case PIPE_ISOCHRONOUS: uhci_clean_iso_step1 (s, urb_priv); break; case PIPE_BULK: case PIPE_CONTROL: qh = list_entry (urb_priv->desc_list.next, uhci_desc_t, desc_list); uhci_clean_transfer (s, urb, qh, 0); break; } ((urb_priv_t*)urb->hcpriv)->started = UHCI_GET_CURRENT_FRAME(s); } return -EINPROGRESS; } /*-------------------------------------------------------------------*/ _static int uhci_unlink_urb (urb_t *urb) { uhci_t *s; unsigned long flags=0; dbg("uhci_unlink_urb called for %p",urb); if (!urb || !urb->dev) // you never know... return -EINVAL; s = (uhci_t*) urb->dev->bus->hcpriv; if (usb_pipedevice (urb->pipe) == s->rh.devnum) return rh_unlink_urb (urb); if (!urb->hcpriv) return -EINVAL; if (urb->transfer_flags & USB_ASYNC_UNLINK) { int ret; spin_lock_irqsave (&s->urb_list_lock, flags); ret = uhci_unlink_urb_async(s, urb); spin_unlock_irqrestore (&s->urb_list_lock, flags); return ret; } else return uhci_unlink_urb_sync(s, urb); } /*-------------------------------------------------------------------*/ // In case of ASAP iso transfer, search the URB-list for already queued URBs // for this EP and calculate the earliest start frame for the new // URB (easy seamless URB continuation!) _static int find_iso_limits (urb_t *urb, unsigned int *start, unsigned int *end) { urb_t *u, *last_urb = NULL; uhci_t *s = (uhci_t*) urb->dev->bus->hcpriv; struct list_head *p; int ret=-1; unsigned long flags; spin_lock_irqsave (&s->urb_list_lock, flags); p=s->urb_list.prev; for (; p != &s->urb_list; p = p->prev) { u = list_entry (p, urb_t, urb_list); // look for pending URBs with identical pipe handle // works only because iso doesn't toggle the data bit! if ((urb->pipe == u->pipe) && (urb->dev == u->dev) && (u->status == -EINPROGRESS)) { if (!last_urb) *start = u->start_frame; last_urb = u; } } if (last_urb) { *end = (last_urb->start_frame + last_urb->number_of_packets) & 1023; ret=0; } spin_unlock_irqrestore(&s->urb_list_lock, flags); return ret; } /*-------------------------------------------------------------------*/ // adjust start_frame according to scheduling constraints (ASAP etc) _static int iso_find_start (urb_t *urb) { uhci_t *s = (uhci_t*) urb->dev->bus->hcpriv; unsigned int now; unsigned int start_limit = 0, stop_limit = 0, queued_size; int limits; now = UHCI_GET_CURRENT_FRAME (s) & 1023; if ((unsigned) urb->number_of_packets > 900) return -EFBIG; limits = find_iso_limits (urb, &start_limit, &stop_limit); queued_size = (stop_limit - start_limit) & 1023; if (urb->transfer_flags & USB_ISO_ASAP) { // first iso if (limits) { // 10ms setup should be enough //FIXME! urb->start_frame = (now + 10) & 1023; } else { urb->start_frame = stop_limit; //seamless linkage if (((now - urb->start_frame) & 1023) <= (unsigned) urb->number_of_packets) { info("iso_find_start: gap in seamless isochronous scheduling"); dbg("iso_find_start: now %u start_frame %u number_of_packets %u pipe 0x%08x", now, urb->start_frame, urb->number_of_packets, urb->pipe); urb->start_frame = (now + 5) & 1023; // 5ms setup should be enough //FIXME! } } } else { urb->start_frame &= 1023; if (((now - urb->start_frame) & 1023) < (unsigned) urb->number_of_packets) { dbg("iso_find_start: now between start_frame and end"); return -EAGAIN; } } /* check if either start_frame or start_frame+number_of_packets-1 lies between start_limit and stop_limit */ if (limits) return 0; if (((urb->start_frame - start_limit) & 1023) < queued_size || ((urb->start_frame + urb->number_of_packets - 1 - start_limit) & 1023) < queued_size) { dbg("iso_find_start: start_frame %u number_of_packets %u start_limit %u stop_limit %u", urb->start_frame, urb->number_of_packets, start_limit, stop_limit); return -EAGAIN; } return 0; } /*-------------------------------------------------------------------*/ // submits USB interrupt (ie. polling ;-) // ASAP-flag set implicitely // if period==0, the the transfer is only done once _static int uhci_submit_int_urb (urb_t *urb) { uhci_t *s = (uhci_t*) urb->dev->bus->hcpriv; urb_priv_t *urb_priv = urb->hcpriv; int nint, n, ret; uhci_desc_t *td; int status, destination; int info; unsigned int pipe = urb->pipe; if (urb->interval < 0 || urb->interval >= 256) return -EINVAL; if (urb->interval == 0) nint = 0; else { for (nint = 0, n = 1; nint <= 8; nint++, n += n) // round interval down to 2^n { if (urb->interval < n) { urb->interval = n / 2; break; } } nint--; } dbg("Rounded interval to %i, chain %i", urb->interval, nint); urb->start_frame = UHCI_GET_CURRENT_FRAME (s) & 1023; // remember start frame, just in case... urb->number_of_packets = 1; // INT allows only one packet if (urb->transfer_buffer_length > usb_maxpacket (urb->dev, pipe, usb_pipeout (pipe))) return -EINVAL; ret = alloc_td (&td, UHCI_PTR_DEPTH); if (ret) return -ENOMEM; status = (pipe & TD_CTRL_LS) | TD_CTRL_ACTIVE | TD_CTRL_IOC | (urb->transfer_flags & USB_DISABLE_SPD ? 0 : TD_CTRL_SPD) | (3 << 27); destination = (urb->pipe & PIPE_DEVEP_MASK) | usb_packetid (urb->pipe) | (((urb->transfer_buffer_length - 1) & 0x7ff) << 21); info = destination | (usb_gettoggle (urb->dev, usb_pipeendpoint (pipe), usb_pipeout (pipe)) << TD_TOKEN_TOGGLE); fill_td (td, status, info, virt_to_bus (urb->transfer_buffer)); list_add_tail (&td->desc_list, &urb_priv->desc_list); urb->status = -EINPROGRESS; queue_urb (s, urb); insert_td_horizontal (s, s->int_chain[nint], td); // store in INT-TDs usb_dotoggle (urb->dev, usb_pipeendpoint (pipe), usb_pipeout (pipe)); return 0; } /*-------------------------------------------------------------------*/ _static int uhci_submit_iso_urb (urb_t *urb) { uhci_t *s = (uhci_t*) urb->dev->bus->hcpriv; urb_priv_t *urb_priv = urb->hcpriv; int pipe=urb->pipe; int maxsze = usb_maxpacket (urb->dev, pipe, usb_pipeout (pipe)); int n, ret, last=0; uhci_desc_t *td, **tdm; int status, destination; unsigned long flags; __save_flags(flags); __cli(); // Disable IRQs to schedule all ISO-TDs in time ret = iso_find_start (urb); // adjusts urb->start_frame for later use if (ret) goto err; tdm = (uhci_desc_t **) kmalloc (urb->number_of_packets * sizeof (uhci_desc_t*), KMALLOC_FLAG); if (!tdm) { ret = -ENOMEM; goto err; } // First try to get all TDs for (n = 0; n < urb->number_of_packets; n++) { dbg("n:%d urb->iso_frame_desc[n].length:%d", n, urb->iso_frame_desc[n].length); if (!urb->iso_frame_desc[n].length) { // allows ISO striping by setting length to zero in iso_descriptor tdm[n] = 0; continue; } if(urb->iso_frame_desc[n].length > maxsze) { #ifdef ISO_SANITY_CHECK err("submit_iso: urb->iso_frame_desc[%d].length(%d)>%d",n , urb->iso_frame_desc[n].length, maxsze); tdm[n] = 0; ret=-EINVAL; goto inval; #endif } ret = alloc_td (&td, UHCI_PTR_DEPTH); inval: if (ret) { int i; // Cleanup allocated TDs for (i = 0; i < n; n++) if (tdm[i]) delete_desc(tdm[i]); kfree (tdm); goto err; } last=n; tdm[n] = td; } status = TD_CTRL_ACTIVE | TD_CTRL_IOS; //| (urb->transfer_flags&USB_DISABLE_SPD?0:TD_CTRL_SPD); destination = (urb->pipe & PIPE_DEVEP_MASK) | usb_packetid (urb->pipe); // Queue all allocated TDs for (n = 0; n < urb->number_of_packets; n++) { td = tdm[n]; if (!td) continue; if (n == last) status |= TD_CTRL_IOC; fill_td (td, status, destination | (((urb->iso_frame_desc[n].length - 1) & 0x7ff) << 21), virt_to_bus (urb->transfer_buffer + urb->iso_frame_desc[n].offset)); list_add_tail (&td->desc_list, &urb_priv->desc_list); if (n == last) { urb->status = -EINPROGRESS; queue_urb (s, urb); } insert_td_horizontal (s, s->iso_td[(urb->start_frame + n) & 1023], td); // store in iso-tds //uhci_show_td(td); } kfree (tdm); dbg("ISO-INT# %i, start %i, now %i", urb->number_of_packets, urb->start_frame, UHCI_GET_CURRENT_FRAME (s) & 1023); ret = 0; err: __restore_flags(flags); return ret; } /*-------------------------------------------------------------------*/ // returns: 0 (no transfer queued), urb* (this urb already queued) _static urb_t* search_dev_ep (uhci_t *s, urb_t *urb) { struct list_head *p; urb_t *tmp; unsigned int mask = usb_pipecontrol(urb->pipe) ? (~USB_DIR_IN) : (~0); dbg("search_dev_ep:"); p=s->urb_list.next; for (; p != &s->urb_list; p = p->next) { tmp = list_entry (p, urb_t, urb_list); dbg("urb: %p", tmp); // we can accept this urb if it is not queued at this time // or if non-iso transfer requests should be scheduled for the same device and pipe if ((!usb_pipeisoc(urb->pipe) && (tmp->dev == urb->dev) && !((tmp->pipe ^ urb->pipe) & mask)) || (urb == tmp)) { return tmp; // found another urb already queued for processing } } return 0; } /*-------------------------------------------------------------------*/ _static int uhci_submit_urb (urb_t *urb) { uhci_t *s; urb_priv_t *urb_priv; int ret = 0; unsigned long flags; urb_t *bulk_urb=NULL; if (!urb->dev || !urb->dev->bus) return -ENODEV; s = (uhci_t*) urb->dev->bus->hcpriv; //dbg("submit_urb: %p type %d",urb,usb_pipetype(urb->pipe)); if (!s->running) return -ENODEV; if (usb_pipedevice (urb->pipe) == s->rh.devnum) return rh_submit_urb (urb); /* virtual root hub */ usb_inc_dev_use (urb->dev); spin_lock_irqsave (&s->urb_list_lock, flags); bulk_urb = search_dev_ep (s, urb); if (bulk_urb) { queue_dbg("found bulk urb %p\n",bulk_urb); if ((usb_pipetype (urb->pipe) != PIPE_BULK) || ((usb_pipetype (urb->pipe) == PIPE_BULK) && (!(urb->transfer_flags & USB_QUEUE_BULK) || !(bulk_urb->transfer_flags & USB_QUEUE_BULK)))) { spin_unlock_irqrestore (&s->urb_list_lock, flags); usb_dec_dev_use (urb->dev); err("ENXIO1 %08x, flags %x, urb %p, burb %p",urb->pipe,urb->transfer_flags,urb,bulk_urb); return -ENXIO; // urb already queued } } #ifdef DEBUG_SLAB urb_priv = kmem_cache_alloc(urb_priv_kmem, SLAB_FLAG); #else urb_priv = kmalloc (sizeof (urb_priv_t), KMALLOC_FLAG); #endif if (!urb_priv) { usb_dec_dev_use (urb->dev); spin_unlock_irqrestore (&s->urb_list_lock, flags); return -ENOMEM; } urb->hcpriv = urb_priv; INIT_LIST_HEAD (&urb_priv->desc_list); urb_priv->short_control_packet = 0; dbg("submit_urb: scheduling %p", urb); urb_priv->next_queued_urb = NULL; urb_priv->prev_queued_urb = NULL; urb_priv->bottom_qh = NULL; urb_priv->next_qh = NULL; if (usb_pipetype (urb->pipe) == PIPE_BULK) { if (bulk_urb) { while (((urb_priv_t*)bulk_urb->hcpriv)->next_queued_urb) // find last queued bulk bulk_urb=((urb_priv_t*)bulk_urb->hcpriv)->next_queued_urb; ((urb_priv_t*)bulk_urb->hcpriv)->next_queued_urb=urb; } atomic_inc (&s->avoid_bulk); ret = uhci_submit_bulk_urb (urb, bulk_urb); atomic_dec (&s->avoid_bulk); spin_unlock_irqrestore (&s->urb_list_lock, flags); } else { spin_unlock_irqrestore (&s->urb_list_lock, flags); switch (usb_pipetype (urb->pipe)) { case PIPE_ISOCHRONOUS: ret = uhci_submit_iso_urb (urb); break; case PIPE_INTERRUPT: ret = uhci_submit_int_urb (urb); break; case PIPE_CONTROL: ret = uhci_submit_control_urb (urb); break; default: ret = -EINVAL; } } dbg("submit_urb: scheduled with ret: %d", ret); if (ret != 0) { usb_dec_dev_use (urb->dev); #ifdef DEBUG_SLAB kmem_cache_free(urb_priv_kmem, urb_priv); #else kfree (urb_priv); #endif return ret; } return 0; } // Checks for URB timeout and removes bandwidth reclamation // if URB idles too long _static void uhci_check_timeouts(uhci_t *s) { struct list_head *p,*p2; urb_t *urb; int type; p = s->urb_list.prev; while (p != &s->urb_list) { urb_priv_t *hcpriv; p2 = p; p = p->prev; urb = list_entry (p2, urb_t, urb_list); type = usb_pipetype (urb->pipe); hcpriv = (urb_priv_t*)urb->hcpriv; if ( urb->timeout && ((hcpriv->started + urb->timeout) < jiffies)) { urb->transfer_flags |= USB_TIMEOUT_KILLED | USB_ASYNC_UNLINK; async_dbg("uhci_check_timeout: timeout for %p",urb); uhci_unlink_urb_async(s, urb); } #ifdef CONFIG_USB_UHCI_HIGH_BANDWIDTH else if (((type == PIPE_BULK) || (type == PIPE_CONTROL)) && (hcpriv->use_loop) && ((hcpriv->started + IDLE_TIMEOUT) < jiffies)) disable_desc_loop(s, urb); #endif } } /*------------------------------------------------------------------- Virtual Root Hub -------------------------------------------------------------------*/ _static __u8 root_hub_dev_des[] = { 0x12, /* __u8 bLength; */ 0x01, /* __u8 bDescriptorType; Device */ 0x00, /* __u16 bcdUSB; v1.0 */ 0x01, 0x09, /* __u8 bDeviceClass; HUB_CLASSCODE */ 0x00, /* __u8 bDeviceSubClass; */ 0x00, /* __u8 bDeviceProtocol; */ 0x08, /* __u8 bMaxPacketSize0; 8 Bytes */ 0x00, /* __u16 idVendor; */ 0x00, 0x00, /* __u16 idProduct; */ 0x00, 0x00, /* __u16 bcdDevice; */ 0x00, 0x00, /* __u8 iManufacturer; */ 0x02, /* __u8 iProduct; */ 0x01, /* __u8 iSerialNumber; */ 0x01 /* __u8 bNumConfigurations; */ }; /* Configuration descriptor */ _static __u8 root_hub_config_des[] = { 0x09, /* __u8 bLength; */ 0x02, /* __u8 bDescriptorType; Configuration */ 0x19, /* __u16 wTotalLength; */ 0x00, 0x01, /* __u8 bNumInterfaces; */ 0x01, /* __u8 bConfigurationValue; */ 0x00, /* __u8 iConfiguration; */ 0x40, /* __u8 bmAttributes; Bit 7: Bus-powered, 6: Self-powered, 5 Remote-wakwup, 4..0: resvd */ 0x00, /* __u8 MaxPower; */ /* interface */ 0x09, /* __u8 if_bLength; */ 0x04, /* __u8 if_bDescriptorType; Interface */ 0x00, /* __u8 if_bInterfaceNumber; */ 0x00, /* __u8 if_bAlternateSetting; */ 0x01, /* __u8 if_bNumEndpoints; */ 0x09, /* __u8 if_bInterfaceClass; HUB_CLASSCODE */ 0x00, /* __u8 if_bInterfaceSubClass; */ 0x00, /* __u8 if_bInterfaceProtocol; */ 0x00, /* __u8 if_iInterface; */ /* endpoint */ 0x07, /* __u8 ep_bLength; */ 0x05, /* __u8 ep_bDescriptorType; Endpoint */ 0x81, /* __u8 ep_bEndpointAddress; IN Endpoint 1 */ 0x03, /* __u8 ep_bmAttributes; Interrupt */ 0x08, /* __u16 ep_wMaxPacketSize; 8 Bytes */ 0x00, 0xff /* __u8 ep_bInterval; 255 ms */ }; _static __u8 root_hub_hub_des[] = { 0x09, /* __u8 bLength; */ 0x29, /* __u8 bDescriptorType; Hub-descriptor */ 0x02, /* __u8 bNbrPorts; */ 0x00, /* __u16 wHubCharacteristics; */ 0x00, 0x01, /* __u8 bPwrOn2pwrGood; 2ms */ 0x00, /* __u8 bHubContrCurrent; 0 mA */ 0x00, /* __u8 DeviceRemovable; *** 7 Ports max *** */ 0xff /* __u8 PortPwrCtrlMask; *** 7 ports max *** */ }; /*-------------------------------------------------------------------------*/ /* prepare Interrupt pipe transaction data; HUB INTERRUPT ENDPOINT */ _static int rh_send_irq (urb_t *urb) { int len = 1; int i; uhci_t *uhci = urb->dev->bus->hcpriv; unsigned int io_addr = uhci->io_addr; __u16 data = 0; for (i = 0; i < uhci->rh.numports; i++) { data |= ((inw (io_addr + USBPORTSC1 + i * 2) & 0xa) > 0 ? (1 << (i + 1)) : 0); len = (i + 1) / 8 + 1; } *(__u16 *) urb->transfer_buffer = cpu_to_le16 (data); urb->actual_length = len; urb->status = 0; if ((data > 0) && (uhci->rh.send != 0)) { dbg("Root-Hub INT complete: port1: %x port2: %x data: %x", inw (io_addr + USBPORTSC1), inw (io_addr + USBPORTSC2), data); urb->complete (urb); } return 0; } /*-------------------------------------------------------------------------*/ /* Virtual Root Hub INTs are polled by this timer every "intervall" ms */ _static int rh_init_int_timer (urb_t *urb); _static void rh_int_timer_do (unsigned long ptr) { int len; urb_t *urb = (urb_t*) ptr; uhci_t *uhci = urb->dev->bus->hcpriv; if (uhci->rh.send) { len = rh_send_irq (urb); if (len > 0) { urb->actual_length = len; if (urb->complete) urb->complete (urb); } } rh_init_int_timer (urb); } /*-------------------------------------------------------------------------*/ /* Root Hub INTs are polled by this timer, polling interval 20ms */ /* This time is also used for URB-timeout checking */ _static int rh_init_int_timer (urb_t *urb) { uhci_t *uhci = urb->dev->bus->hcpriv; uhci->rh.interval = urb->interval; init_timer (&uhci->rh.rh_int_timer); uhci->rh.rh_int_timer.function = rh_int_timer_do; uhci->rh.rh_int_timer.data = (unsigned long) urb; uhci->rh.rh_int_timer.expires = jiffies + (HZ * 20) / 1000; add_timer (&uhci->rh.rh_int_timer); return 0; } /*-------------------------------------------------------------------------*/ #define OK(x) len = (x); break #define CLR_RH_PORTSTAT(x) \ status = inw(io_addr+USBPORTSC1+2*(wIndex-1)); \ status = (status & 0xfff5) & ~(x); \ outw(status, io_addr+USBPORTSC1+2*(wIndex-1)) #define SET_RH_PORTSTAT(x) \ status = inw(io_addr+USBPORTSC1+2*(wIndex-1)); \ status = (status & 0xfff5) | (x); \ outw(status, io_addr+USBPORTSC1+2*(wIndex-1)) /*-------------------------------------------------------------------------*/ /**** ** Root Hub Control Pipe *************************/ _static int rh_submit_urb (urb_t *urb) { struct usb_device *usb_dev = urb->dev; uhci_t *uhci = usb_dev->bus->hcpriv; unsigned int pipe = urb->pipe; devrequest *cmd = (devrequest *) urb->setup_packet; void *data = urb->transfer_buffer; int leni = urb->transfer_buffer_length; int len = 0; int status = 0; int stat = 0; int i; unsigned int io_addr = uhci->io_addr; __u16 cstatus; __u16 bmRType_bReq; __u16 wValue; __u16 wIndex; __u16 wLength; if (usb_pipetype (pipe) == PIPE_INTERRUPT) { dbg("Root-Hub submit IRQ: every %d ms", urb->interval); uhci->rh.urb = urb; uhci->rh.send = 1; uhci->rh.interval = urb->interval; rh_init_int_timer (urb); return 0; } bmRType_bReq = cmd->requesttype | cmd->request << 8; wValue = le16_to_cpu (cmd->value); wIndex = le16_to_cpu (cmd->index); wLength = le16_to_cpu (cmd->length); for (i = 0; i < 8; i++) uhci->rh.c_p_r[i] = 0; dbg("Root-Hub: adr: %2x cmd(%1x): %04x %04x %04x %04x", uhci->rh.devnum, 8, bmRType_bReq, wValue, wIndex, wLength); switch (bmRType_bReq) { /* Request Destination: without flags: Device, RH_INTERFACE: interface, RH_ENDPOINT: endpoint, RH_CLASS means HUB here, RH_OTHER | RH_CLASS almost ever means HUB_PORT here */ case RH_GET_STATUS: *(__u16 *) data = cpu_to_le16 (1); OK (2); case RH_GET_STATUS | RH_INTERFACE: *(__u16 *) data = cpu_to_le16 (0); OK (2); case RH_GET_STATUS | RH_ENDPOINT: *(__u16 *) data = cpu_to_le16 (0); OK (2); case RH_GET_STATUS | RH_CLASS: *(__u32 *) data = cpu_to_le32 (0); OK (4); /* hub power ** */ case RH_GET_STATUS | RH_OTHER | RH_CLASS: status = inw (io_addr + USBPORTSC1 + 2 * (wIndex - 1)); cstatus = ((status & USBPORTSC_CSC) >> (1 - 0)) | ((status & USBPORTSC_PEC) >> (3 - 1)) | (uhci->rh.c_p_r[wIndex - 1] << (0 + 4)); status = (status & USBPORTSC_CCS) | ((status & USBPORTSC_PE) >> (2 - 1)) | ((status & USBPORTSC_SUSP) >> (12 - 2)) | ((status & USBPORTSC_PR) >> (9 - 4)) | (1 << 8) | /* power on ** */ ((status & USBPORTSC_LSDA) << (-8 + 9)); *(__u16 *) data = cpu_to_le16 (status); *(__u16 *) (data + 2) = cpu_to_le16 (cstatus); OK (4); case RH_CLEAR_FEATURE | RH_ENDPOINT: switch (wValue) { case (RH_ENDPOINT_STALL): OK (0); } break; case RH_CLEAR_FEATURE | RH_CLASS: switch (wValue) { case (RH_C_HUB_OVER_CURRENT): OK (0); /* hub power over current ** */ } break; case RH_CLEAR_FEATURE | RH_OTHER | RH_CLASS: switch (wValue) { case (RH_PORT_ENABLE): CLR_RH_PORTSTAT (USBPORTSC_PE); OK (0); case (RH_PORT_SUSPEND): CLR_RH_PORTSTAT (USBPORTSC_SUSP); OK (0); case (RH_PORT_POWER): OK (0); /* port power ** */ case (RH_C_PORT_CONNECTION): SET_RH_PORTSTAT (USBPORTSC_CSC); OK (0); case (RH_C_PORT_ENABLE): SET_RH_PORTSTAT (USBPORTSC_PEC); OK (0); case (RH_C_PORT_SUSPEND): /*** WR_RH_PORTSTAT(RH_PS_PSSC); */ OK (0); case (RH_C_PORT_OVER_CURRENT): OK (0); /* port power over current ** */ case (RH_C_PORT_RESET): uhci->rh.c_p_r[wIndex - 1] = 0; OK (0); } break; case RH_SET_FEATURE | RH_OTHER | RH_CLASS: switch (wValue) { case (RH_PORT_SUSPEND): SET_RH_PORTSTAT (USBPORTSC_SUSP); OK (0); case (RH_PORT_RESET): SET_RH_PORTSTAT (USBPORTSC_PR); uhci_wait_ms (10); uhci->rh.c_p_r[wIndex - 1] = 1; CLR_RH_PORTSTAT (USBPORTSC_PR); udelay (10); SET_RH_PORTSTAT (USBPORTSC_PE); uhci_wait_ms (10); SET_RH_PORTSTAT (0xa); OK (0); case (RH_PORT_POWER): OK (0); /* port power ** */ case (RH_PORT_ENABLE): SET_RH_PORTSTAT (USBPORTSC_PE); OK (0); } break; case RH_SET_ADDRESS: uhci->rh.devnum = wValue; OK (0); case RH_GET_DESCRIPTOR: switch ((wValue & 0xff00) >> 8) { case (0x01): /* device descriptor */ len = min (leni, min (sizeof (root_hub_dev_des), wLength)); memcpy (data, root_hub_dev_des, len); OK (len); case (0x02): /* configuration descriptor */ len = min (leni, min (sizeof (root_hub_config_des), wLength)); memcpy (data, root_hub_config_des, len); OK (len); case (0x03): /* string descriptors */ len = usb_root_hub_string (wValue & 0xff, uhci->io_addr, "UHCI", data, wLength); if (len > 0) { OK (min (leni, len)); } else stat = -EPIPE; } break; case RH_GET_DESCRIPTOR | RH_CLASS: root_hub_hub_des[2] = uhci->rh.numports; len = min (leni, min (sizeof (root_hub_hub_des), wLength)); memcpy (data, root_hub_hub_des, len); OK (len); case RH_GET_CONFIGURATION: *(__u8 *) data = 0x01; OK (1); case RH_SET_CONFIGURATION: OK (0); default: stat = -EPIPE; } dbg("Root-Hub stat port1: %x port2: %x", inw (io_addr + USBPORTSC1), inw (io_addr + USBPORTSC2)); urb->actual_length = len; urb->status = stat; if (urb->complete) urb->complete (urb); return 0; } /*-------------------------------------------------------------------------*/ _static int rh_unlink_urb (urb_t *urb) { uhci_t *uhci = urb->dev->bus->hcpriv; if (uhci->rh.urb==urb) { dbg("Root-Hub unlink IRQ"); uhci->rh.send = 0; del_timer (&uhci->rh.rh_int_timer); } return 0; } /*-------------------------------------------------------------------*/ /* * Map status to standard result codes * * is (td->status & 0xFE0000) [a.k.a. uhci_status_bits(td->status) * 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 -EPROTO; } /* * Only the USB core should call uhci_alloc_dev and uhci_free_dev */ _static int uhci_alloc_dev (struct usb_device *usb_dev) { return 0; } _static void uhci_unlink_urbs(uhci_t *s, struct usb_device *usb_dev, int remove_all) { unsigned long flags; struct list_head *p; struct list_head *p2; urb_t *urb; spin_lock_irqsave (&s->urb_list_lock, flags); p = s->urb_list.prev; while (p != &s->urb_list) { p2 = p; p = p->prev ; urb = list_entry (p2, urb_t, urb_list); dbg("urb: %p, dev %p, %p", urb, usb_dev,urb->dev); //urb->transfer_flags |=USB_ASYNC_UNLINK; if (remove_all || (usb_dev == urb->dev)) { spin_unlock_irqrestore (&s->urb_list_lock, flags); warn("forced removing of queued URB %p due to disconnect",urb); uhci_unlink_urb(urb); urb->dev = NULL; // avoid further processing of this UR spin_lock_irqsave (&s->urb_list_lock, flags); p = s->urb_list.prev; } } spin_unlock_irqrestore (&s->urb_list_lock, flags); } _static int uhci_free_dev (struct usb_device *usb_dev) { uhci_t *s; if(!usb_dev || !usb_dev->bus || !usb_dev->bus->hcpriv) return -EINVAL; s=(uhci_t*) usb_dev->bus->hcpriv; uhci_unlink_urbs(s, usb_dev, 0); return 0; } /* * uhci_get_current_frame_number() * * returns the current frame number for a USB bus/controller. */ _static int uhci_get_current_frame_number (struct usb_device *usb_dev) { return UHCI_GET_CURRENT_FRAME ((uhci_t*) usb_dev->bus->hcpriv); } struct usb_operations uhci_device_operations = { uhci_alloc_dev, uhci_free_dev, uhci_get_current_frame_number, uhci_submit_urb, uhci_unlink_urb }; /* * For IN-control transfers, process_transfer gets a bit more complicated, * since there are devices that return less data (eg. strings) than they * have announced. This leads to a queue abort due to the short packet, * the status stage is not executed. If this happens, the status stage * is manually re-executed. * mode: 0: QHs already unlinked */ _static int process_transfer (uhci_t *s, urb_t *urb, int mode) { int ret = 0; urb_priv_t *urb_priv = urb->hcpriv; struct list_head *qhl = urb_priv->desc_list.next; uhci_desc_t *qh = list_entry (qhl, uhci_desc_t, desc_list); struct list_head *p = qh->vertical.next; uhci_desc_t *desc= list_entry (urb_priv->desc_list.prev, uhci_desc_t, desc_list); uhci_desc_t *last_desc = list_entry (desc->vertical.prev, uhci_desc_t, vertical); int data_toggle = usb_gettoggle (urb->dev, usb_pipeendpoint (urb->pipe), usb_pipeout (urb->pipe)); // save initial data_toggle int maxlength; // extracted and remapped info from TD int actual_length; int status = 0; //dbg("process_transfer: urb contains bulk/control request"); /* if the status phase has been retriggered and the queue is empty or the last status-TD is inactive, the retriggered status stage is completed */ if (urb_priv->short_control_packet && ((qh->hw.qh.element == UHCI_PTR_TERM) ||(!(last_desc->hw.td.status & TD_CTRL_ACTIVE)))) goto transfer_finished; urb->actual_length=0; for (; p != &qh->vertical; p = p->next) { desc = list_entry (p, uhci_desc_t, vertical); if (desc->hw.td.status & TD_CTRL_ACTIVE) // do not process active TDs return ret; actual_length = (desc->hw.td.status + 1) & 0x7ff; // extract transfer parameters from TD maxlength = (((desc->hw.td.info >> 21) & 0x7ff) + 1) & 0x7ff; status = uhci_map_status (uhci_status_bits (desc->hw.td.status), usb_pipeout (urb->pipe)); if (status == -EPIPE) { // see if EP is stalled // set up stalled condition usb_endpoint_halt (urb->dev, usb_pipeendpoint (urb->pipe), usb_pipeout (urb->pipe)); } if (status != 0) { // if any error occured stop processing of further TDs // only set ret if status returned an error if (status != -EPIPE) uhci_show_td (desc); ret = status; urb->error_count++; break; } else if ((desc->hw.td.info & 0xff) != USB_PID_SETUP) urb->actual_length += actual_length; // got less data than requested if ( (actual_length < maxlength)) { if (urb->transfer_flags & USB_DISABLE_SPD) { status = -EREMOTEIO; // treat as real error dbg("process_transfer: SPD!!"); break; // exit after this TD because SP was detected } // short read during control-IN: re-start status stage if ((usb_pipetype (urb->pipe) == PIPE_CONTROL)) { if (uhci_packetid(last_desc->hw.td.info) == USB_PID_OUT) { qh->hw.qh.element = virt_to_bus (last_desc); // re-trigger status stage dbg("short packet during control transfer, retrigger status stage @ %p",last_desc); //uhci_show_td (desc); //uhci_show_td (last_desc); urb_priv->short_control_packet=1; return 0; } } // all other cases: short read is OK data_toggle = uhci_toggle (desc->hw.td.info); break; } data_toggle = uhci_toggle (desc->hw.td.info); queue_dbg("process_transfer: len:%d status:%x mapped:%x toggle:%d", actual_length, desc->hw.td.status,status, data_toggle); } usb_settoggle (urb->dev, usb_pipeendpoint (urb->pipe), usb_pipeout (urb->pipe), !data_toggle); transfer_finished: uhci_clean_transfer(s, urb, qh, (mode==0?2:1)); urb->status = status; #ifdef CONFIG_USB_UHCI_HIGH_BANDWIDTH disable_desc_loop(s,urb); #endif queue_dbg("process_transfer: (end) urb %p, wanted len %d, len %d status %x err %d", urb,urb->transfer_buffer_length,urb->actual_length, urb->status, urb->error_count); return ret; } _static int process_interrupt (uhci_t *s, urb_t *urb) { int i, ret = -EINPROGRESS; urb_priv_t *urb_priv = urb->hcpriv; struct list_head *p = urb_priv->desc_list.next; uhci_desc_t *desc = list_entry (urb_priv->desc_list.prev, uhci_desc_t, desc_list); int actual_length; int status = 0; //dbg("urb contains interrupt request"); for (i = 0; p != &urb_priv->desc_list; p = p->next, i++) // Maybe we allow more than one TD later ;-) { desc = list_entry (p, uhci_desc_t, desc_list); if (desc->hw.td.status & TD_CTRL_ACTIVE) { // do not process active TDs //dbg("TD ACT Status @%p %08x",desc,desc->hw.td.status); break; } if (!desc->hw.td.status & TD_CTRL_IOC) { // do not process one-shot TDs, no recycling break; } // extract transfer parameters from TD actual_length = (desc->hw.td.status + 1) & 0x7ff; status = uhci_map_status (uhci_status_bits (desc->hw.td.status), usb_pipeout (urb->pipe)); // see if EP is stalled if (status == -EPIPE) { // set up stalled condition usb_endpoint_halt (urb->dev, usb_pipeendpoint (urb->pipe), usb_pipeout (urb->pipe)); } // if any error occured: ignore this td, and continue if (status != 0) { //uhci_show_td (desc); urb->error_count++; goto recycle; } else urb->actual_length = actual_length; recycle: if (urb->complete) { //dbg("process_interrupt: calling completion, status %i",status); urb->status = status; spin_unlock(&s->urb_list_lock); urb->complete ((struct urb *) urb); spin_lock(&s->urb_list_lock); urb->status = -EINPROGRESS; } // Recycle INT-TD if interval!=0, else mark TD as one-shot if (urb->interval) { desc->hw.td.info &= ~(1 << TD_TOKEN_TOGGLE); if (status==0) { ((urb_priv_t*)urb->hcpriv)->started=jiffies; desc->hw.td.info |= (usb_gettoggle (urb->dev, usb_pipeendpoint (urb->pipe), usb_pipeout (urb->pipe)) << TD_TOKEN_TOGGLE); usb_dotoggle (urb->dev, usb_pipeendpoint (urb->pipe), usb_pipeout (urb->pipe)); } else { desc->hw.td.info |= (!usb_gettoggle (urb->dev, usb_pipeendpoint (urb->pipe), usb_pipeout (urb->pipe)) << TD_TOKEN_TOGGLE); } desc->hw.td.status= (urb->pipe & TD_CTRL_LS) | TD_CTRL_ACTIVE | TD_CTRL_IOC | (urb->transfer_flags & USB_DISABLE_SPD ? 0 : TD_CTRL_SPD) | (3 << 27); mb(); } else { desc->hw.td.status &= ~TD_CTRL_IOC; // inactivate TD } } return ret; } // mode: 1: force processing, don't unlink tds (already unlinked) _static int process_iso (uhci_t *s, urb_t *urb, int mode) { int i; int ret = 0; urb_priv_t *urb_priv = urb->hcpriv; struct list_head *p = urb_priv->desc_list.next; uhci_desc_t *desc = list_entry (urb_priv->desc_list.prev, uhci_desc_t, desc_list); dbg("urb contains iso request"); if ((desc->hw.td.status & TD_CTRL_ACTIVE) && !mode) return -EXDEV; // last TD not finished urb->error_count = 0; urb->actual_length = 0; urb->status = 0; dbg("process iso urb %p, %li, %i, %i, %i %08x",urb,jiffies,UHCI_GET_CURRENT_FRAME(s), urb->number_of_packets,mode,desc->hw.td.status); for (i = 0; p != &urb_priv->desc_list; p = p->next, i++) { desc = list_entry (p, uhci_desc_t, desc_list); //uhci_show_td(desc); if (desc->hw.td.status & TD_CTRL_ACTIVE) { // means we have completed the last TD, but not the TDs before desc->hw.td.status &= ~TD_CTRL_ACTIVE; dbg("TD still active (%x)- grrr. paranoia!", desc->hw.td.status); ret = -EXDEV; urb->iso_frame_desc[i].status = ret; unlink_td (s, desc, 1); // FIXME: immediate deletion may be dangerous goto err; } if (!mode) unlink_td (s, desc, 1); if (urb->number_of_packets <= i) { dbg("urb->number_of_packets (%d)<=(%d)", urb->number_of_packets, i); ret = -EINVAL; goto err; } if (urb->iso_frame_desc[i].offset + urb->transfer_buffer != bus_to_virt (desc->hw.td.buffer)) { // Hm, something really weird is going on dbg("Pointer Paranoia: %p!=%p", urb->iso_frame_desc[i].offset + urb->transfer_buffer, bus_to_virt (desc->hw.td.buffer)); ret = -EINVAL; urb->iso_frame_desc[i].status = ret; goto err; } urb->iso_frame_desc[i].actual_length = (desc->hw.td.status + 1) & 0x7ff; urb->iso_frame_desc[i].status = uhci_map_status (uhci_status_bits (desc->hw.td.status), usb_pipeout (urb->pipe)); urb->actual_length += urb->iso_frame_desc[i].actual_length; err: if (urb->iso_frame_desc[i].status != 0) { urb->error_count++; urb->status = urb->iso_frame_desc[i].status; } dbg("process_iso: %i: len:%d %08x status:%x", i, urb->iso_frame_desc[i].actual_length, desc->hw.td.status,urb->iso_frame_desc[i].status); delete_desc (desc); list_del (p); } dbg("process_iso: exit %i (%d), actual_len %i", i, ret,urb->actual_length); return ret; } _static int process_urb (uhci_t *s, struct list_head *p) { int ret = 0; urb_t *urb; urb=list_entry (p, urb_t, urb_list); //dbg("process_urb: found queued urb: %p", urb); switch (usb_pipetype (urb->pipe)) { case PIPE_CONTROL: ret = process_transfer (s, urb, 1); break; case PIPE_BULK: if (!s->avoid_bulk.counter) ret = process_transfer (s, urb, 1); else return 0; break; case PIPE_ISOCHRONOUS: ret = process_iso (s, urb, 0); break; case PIPE_INTERRUPT: ret = process_interrupt (s, urb); break; } if (urb->status != -EINPROGRESS) { int proceed = 0; dbg("dequeued urb: %p", urb); dequeue_urb (s, urb); #ifdef DEBUG_SLAB kmem_cache_free(urb_priv_kmem, urb->hcpriv); #else kfree (urb->hcpriv); #endif if ((usb_pipetype (urb->pipe) != PIPE_INTERRUPT)) { urb_t *tmp = urb->next; // pointer to first urb int is_ring = 0; if (urb->next) { do { if (tmp->status != -EINPROGRESS) { proceed = 1; break; } tmp = tmp->next; } while (tmp != NULL && tmp != urb->next); if (tmp == urb->next) is_ring = 1; } spin_unlock(&s->urb_list_lock); // In case you need the current URB status for your completion handler if (urb->complete && (!proceed || (urb->transfer_flags & USB_URB_EARLY_COMPLETE))) { dbg("process_transfer: calling early completion"); urb->complete ((struct urb *) urb); if (!proceed && is_ring && (urb->status != -ENOENT)) uhci_submit_urb (urb); } if (proceed && urb->next) { // if there are linked urbs - handle submitting of them right now. tmp = urb->next; // pointer to first urb do { if ((tmp->status != -EINPROGRESS) && (tmp->status != -ENOENT) && uhci_submit_urb (tmp) != 0) break; tmp = tmp->next; } while (tmp != NULL && tmp != urb->next); // submit until we reach NULL or our own pointer or submit fails if (urb->complete && !(urb->transfer_flags & USB_URB_EARLY_COMPLETE)) { dbg("process_transfer: calling completion"); urb->complete ((struct urb *) urb); } } spin_lock(&s->urb_list_lock); usb_dec_dev_use (urb->dev); } } return ret; } _static void uhci_interrupt (int irq, void *__uhci, struct pt_regs *regs) { uhci_t *s = __uhci; unsigned int io_addr = s->io_addr; unsigned short status; struct list_head *p, *p2; /* * Read the interrupt status, and write it back to clear the * interrupt cause */ status = inw (io_addr + USBSTS); if (!status) /* shared interrupt, not mine */ return; dbg("interrupt"); if (status != 1) { warn("interrupt, status %x, frame# %i", status, UHCI_GET_CURRENT_FRAME(s)); // remove host controller halted state if ((status&0x20) && (s->running)) { outw (USBCMD_RS | inw(io_addr + USBCMD), io_addr + USBCMD); } //uhci_show_status (s); } /* * traverse the list in *reverse* direction, because new entries * may be added at the end. * also, because process_urb may unlink the current urb, * we need to advance the list before */ spin_lock (&s->urb_list_lock); restart: s->unlink_urb_done=0; p = s->urb_list.prev; while (p != &s->urb_list) { p2 = p; p = p->prev; process_urb (s, p2); if (s->unlink_urb_done) { s->unlink_urb_done=0; goto restart; } } if ((s->frame_counter & 63) == 0) uhci_check_timeouts(s); clean_descs(s,0); uhci_cleanup_unlink(s, 0); spin_unlock (&s->urb_list_lock); s->frame_counter++; outw (status, io_addr + USBSTS); //dbg("uhci_interrupt: done"); } _static void reset_hc (uhci_t *s) { unsigned int io_addr = s->io_addr; s->apm_state = 0; /* Global reset for 50ms */ outw (USBCMD_GRESET, io_addr + USBCMD); uhci_wait_ms (50); outw (0, io_addr + USBCMD); uhci_wait_ms (10); } _static void start_hc (uhci_t *s) { unsigned int io_addr = s->io_addr; int timeout = 1000; /* * Reset the HC - this will force us to get a * new notification of any already connected * ports due to the virtual disconnect that it * implies. */ outw (USBCMD_HCRESET, io_addr + USBCMD); while (inw (io_addr + USBCMD) & USBCMD_HCRESET) { if (!--timeout) { err("USBCMD_HCRESET timed out!"); break; } } /* Turn on all interrupts */ outw (USBINTR_TIMEOUT | USBINTR_RESUME | USBINTR_IOC | USBINTR_SP, io_addr + USBINTR); /* Start at frame 0 */ outw (0, io_addr + USBFRNUM); outl (virt_to_bus (s->framelist), io_addr + USBFLBASEADD); /* Run and mark it configured with a 64-byte max packet */ outw (USBCMD_RS | USBCMD_CF | USBCMD_MAXP, io_addr + USBCMD); s->apm_state = 1; s->running = 1; } _static void __exit uhci_cleanup_dev(uhci_t *s) { struct usb_device *root_hub = s->bus->root_hub; s->running = 0; // Don't allow submit_urb if (root_hub) usb_disconnect (&root_hub); reset_hc (s); wait_ms (1); uhci_unlink_urbs (s, 0, 1); // Forced unlink of remaining URBs uhci_cleanup_unlink (s, 1); // force cleanup of async killed URBs usb_deregister_bus (s->bus); release_region (s->io_addr, s->io_size); free_irq (s->irq, s); usb_free_bus (s->bus); cleanup_skel (s); kfree (s); } _static int __init uhci_start_usb (uhci_t *s) { /* start it up */ /* connect the virtual root hub */ struct usb_device *usb_dev; usb_dev = usb_alloc_dev (NULL, s->bus); if (!usb_dev) return -1; s->bus->root_hub = usb_dev; usb_connect (usb_dev); if (usb_new_device (usb_dev) != 0) { usb_free_dev (usb_dev); return -1; } return 0; } #if LINUX_VERSION_CODE > KERNEL_VERSION(2,3,44) _static int handle_pm_event (struct pm_dev *dev, pm_request_t rqst, void *data) { uhci_t *s = (uhci_t*) dev->data; dbg("handle_apm_event(%d)", rqst); if (s) { switch (rqst) { case PM_SUSPEND: reset_hc (s); break; case PM_RESUME: start_hc (s); break; } } return 0; } #endif _static int __init alloc_uhci (struct pci_dev *dev, int irq, unsigned int io_addr, unsigned int io_size) { uhci_t *s; struct usb_bus *bus; struct pm_dev *pmdev; char buf[8], *bufp = buf; #ifndef __sparc__ sprintf(buf, "%d", irq); #else bufp = __irq_itoa(irq); #endif printk(KERN_INFO __FILE__ ": USB UHCI at I/O 0x%x, IRQ %s\n", io_addr, bufp); s = kmalloc (sizeof (uhci_t), GFP_KERNEL); if (!s) return -1; memset (s, 0, sizeof (uhci_t)); INIT_LIST_HEAD (&s->free_desc); INIT_LIST_HEAD (&s->urb_list); INIT_LIST_HEAD (&s->urb_unlinked); spin_lock_init (&s->urb_list_lock); spin_lock_init (&s->qh_lock); spin_lock_init (&s->td_lock); atomic_set(&s->avoid_bulk, 0); s->irq = -1; s->io_addr = io_addr; s->io_size = io_size; s->next = devs; //chain new uhci device into global list s->frame_counter = 0; bus = usb_alloc_bus (&uhci_device_operations); if (!bus) { kfree (s); return -1; } s->bus = bus; bus->hcpriv = s; /* UHCI specs says devices must have 2 ports, but goes on to say */ /* they may have more but give no way to determine how many they */ /* have, so default to 2 */ /* According to the UHCI spec, Bit 7 is always set to 1. So we try */ /* to use this to our advantage */ for (s->maxports = 0; s->maxports < (io_size - 0x10) / 2; s->maxports++) { unsigned int portstatus; portstatus = inw (io_addr + 0x10 + (s->maxports * 2)); dbg("port %i, adr %x status %x", s->maxports, io_addr + 0x10 + (s->maxports * 2), portstatus); if (!(portstatus & 0x0080)) break; } warn("Detected %d ports", s->maxports); /* This is experimental so anything less than 2 or greater than 8 is */ /* something weird and we'll ignore it */ if (s->maxports < 2 || s->maxports > 8) { dbg("Port count misdetected, forcing to 2 ports"); s->maxports = 2; } s->rh.numports = s->maxports; s->loop_usage=0; if (init_skel (s)) { usb_free_bus (bus); kfree(s); return -1; } request_region (s->io_addr, io_size, MODNAME); reset_hc (s); usb_register_bus (s->bus); start_hc (s); if (request_irq (irq, uhci_interrupt, SA_SHIRQ, MODNAME, s)) { err("request_irq %d failed!",irq); usb_free_bus (bus); reset_hc (s); release_region (s->io_addr, s->io_size); cleanup_skel(s); kfree(s); return -1; } s->irq = irq; if(uhci_start_usb (s) < 0) { uhci_cleanup_dev(s); return -1; } //chain new uhci device into global list devs = s; #if LINUX_VERSION_CODE > KERNEL_VERSION(2,3,44) pmdev = pm_register(PM_PCI_DEV, PM_PCI_ID(dev), handle_pm_event); if (pmdev) pmdev->data = s; #endif return 0; } _static int __init start_uhci (struct pci_dev *dev) { int i; /* Search for the IO base address.. */ for (i = 0; i < 6; i++) { #if LINUX_VERSION_CODE > KERNEL_VERSION(2,3,8) unsigned int io_addr = dev->resource[i].start; unsigned int io_size = dev->resource[i].end - dev->resource[i].start + 1; if (!(dev->resource[i].flags & 1)) continue; #else unsigned int io_addr = dev->base_address[i]; unsigned int io_size = 0x14; if (!(io_addr & 1)) continue; io_addr &= ~1; #endif /* Is it already in use? */ if (check_region (io_addr, io_size)) break; /* disable legacy emulation */ pci_write_config_word (dev, USBLEGSUP, USBLEGSUP_DEFAULT); if(dev->vendor==0x8086) { info("Intel USB controller: setting latency timer to %d", UHCI_LATENCY_TIMER); pci_write_config_byte(dev, PCI_LATENCY_TIMER, UHCI_LATENCY_TIMER); } return alloc_uhci(dev, dev->irq, io_addr, io_size); } return -1; } int __init uhci_init (void) { int retval = -ENODEV; struct pci_dev *dev = NULL; u8 type; int i=0; #ifdef DEBUG_SLAB uhci_desc_kmem = kmem_cache_create("uhci_desc", sizeof(uhci_desc_t), 0, SLAB_HWCACHE_ALIGN, NULL, NULL); if(!uhci_desc_kmem) { err("kmem_cache_create for uhci_desc failed (out of memory)"); return -ENOMEM; } urb_priv_kmem = kmem_cache_create("urb_priv", sizeof(urb_priv_t), 0, SLAB_HWCACHE_ALIGN, NULL, NULL); if(!urb_priv_kmem) { err("kmem_cache_create for urb_priv_t failed (out of memory)"); return -ENOMEM; } #endif info(VERSTR); #ifdef CONFIG_USB_UHCI_HIGH_BANDWIDTH info("High bandwidth mode enabled"); #endif for (;;) { dev = pci_find_class (PCI_CLASS_SERIAL_USB << 8, dev); if (!dev) break; /* Is it UHCI */ pci_read_config_byte (dev, PCI_CLASS_PROG, &type); if (type != 0) continue; #if LINUX_VERSION_CODE > KERNEL_VERSION(2,3,8) if (pci_enable_device (dev) < 0) continue; #endif if(!dev->irq) { err("Found UHCI device with no IRQ assigned. Check BIOS settings!"); continue; } /* Ok set it up */ retval = start_uhci (dev); if (!retval) i++; } return retval; } void __exit uhci_cleanup (void) { uhci_t *s; while ((s = devs)) { devs = devs->next; uhci_cleanup_dev(s); } #ifdef DEBUG_SLAB if(kmem_cache_destroy(uhci_desc_kmem)) err("uhci_desc_kmem remained"); if(kmem_cache_destroy(urb_priv_kmem)) err("urb_priv_kmem remained"); #endif } #ifdef MODULE int init_module (void) { return uhci_init (); } void cleanup_module (void) { #if LINUX_VERSION_CODE > KERNEL_VERSION(2,3,44) pm_unregister_all (handle_pm_event); #endif uhci_cleanup (); } #endif //MODULE