diff options
author | Ralf Baechle <ralf@linux-mips.org> | 2000-02-05 06:47:02 +0000 |
---|---|---|
committer | Ralf Baechle <ralf@linux-mips.org> | 2000-02-05 06:47:02 +0000 |
commit | 99a7e12f34b3661a0d1354eef83a0eef4df5e34c (patch) | |
tree | 3560aca9ca86792f9ab7bd87861ea143a1b3c7a3 /drivers/usb/uhci.c | |
parent | e73a04659c0b8cdee4dd40e58630e2cf63afb316 (diff) |
Merge with Linux 2.3.38.
Diffstat (limited to 'drivers/usb/uhci.c')
-rw-r--r-- | drivers/usb/uhci.c | 3767 |
1 files changed, 1830 insertions, 1937 deletions
diff --git a/drivers/usb/uhci.c b/drivers/usb/uhci.c index 4c82d50b5..09dc04283 100644 --- a/drivers/usb/uhci.c +++ b/drivers/usb/uhci.c @@ -1,33 +1,18 @@ -/* - * Universal Host Controller Interface driver for USB. +/* + * 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 * - * 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. - * - * Oh, well. The intel version is the more common by far. As such, - * that's the one I care about right now. - * - * 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 - */ - -/* 4/4/1999 added data toggle for interrupt pipes -keryan */ -/* 5/16/1999 added global toggles for bulk and control */ -/* 6/25/1999 added fix for data toggles on bidirectional bulk endpoints */ -/* - * 1999-09-02: Thomas Sailer <sailer@ife.ee.ethz.ch> - * Added explicit frame list manipulation routines - * for inserting/removing iso td's to/from the frame list. - * START_ABSOLUTE fixes + * $Id: uhci.c,v 1.149 1999/12/26 20:57:14 acher Exp $ */ #include <linux/config.h> @@ -41,2286 +26,2209 @@ #include <linux/smp_lock.h> #include <linux/errno.h> #include <linux/unistd.h> -#include <linux/interrupt.h> -#include <linux/spinlock.h> +#include <linux/interrupt.h> /* for in_interrupt() */ +#include <linux/init.h> #include <asm/uaccess.h> #include <asm/io.h> #include <asm/irq.h> #include <asm/system.h> +#undef DEBUG + +#include "usb.h" #include "uhci.h" +#include "uhci-debug.h" -#ifdef CONFIG_APM -#include <linux/apm_bios.h> -static int handle_apm_event(apm_event_t event); -static int apm_resume = 0; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,3,0) +#define __init +#define __exit #endif -static int uhci_debug = 1; - -#define compile_assert(x) do { switch (0) { case 1: case !(x): } } while (0) - -static DECLARE_WAIT_QUEUE_HEAD(uhci_configure); - -static kmem_cache_t *uhci_td_cachep; -static kmem_cache_t *uhci_qh_cachep; - -static LIST_HEAD(uhci_list); - -#define UHCI_DEBUG - -/* - * function prototypes - */ - -static int uhci_get_current_frame_number(struct usb_device *usb_dev); +#ifdef __alpha +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,3,0) +extern long __kernel_thread (unsigned long, int (*)(void *), void *); +static inline long kernel_thread (int (*fn) (void *), void *arg, unsigned long flags) +{ + return __kernel_thread (flags | CLONE_VM, fn, arg); +} +#undef CONFIG_APM +#endif +#endif -static int uhci_init_isoc(struct usb_device *usb_dev, - unsigned int pipe, - int frame_count, - void *context, - struct usb_isoc_desc **isocdesc); +#ifdef CONFIG_APM +#include <linux/apm_bios.h> +static int handle_apm_event (apm_event_t event); +#endif -static void uhci_free_isoc(struct usb_isoc_desc *isocdesc); +/* We added an UHCI_SLAB slab support just for debugging purposes. In real + life this compile option is NOT recommended, because slab caches are not + suitable for modules. +*/ -static int uhci_run_isoc(struct usb_isoc_desc *isocdesc, - struct usb_isoc_desc *pr_isocdesc); +// #define _UHCI_SLAB +#ifdef _UHCI_SLAB +static kmem_cache_t *uhci_desc_kmem; +static kmem_cache_t *urb_priv_kmem; +#endif -static int uhci_kill_isoc(struct usb_isoc_desc *isocdesc); +static int rh_submit_urb (purb_t purb); +static int rh_unlink_urb (purb_t purb); +static puhci_t devs = NULL; -/* - * Map status to standard result codes - * - * <status> is (td->status & 0xFE0000) [a.k.a. uhci_status_bits(td->status)] - * <dir_out> is True for output TDs and False for input TDs. - */ -static int uhci_map_status(int status, int dir_out) +/*-------------------------------------------------------------------*/ +static void queue_urb (puhci_t s, struct list_head *p, int do_lock) { - if (!status) - return USB_ST_NOERROR; - if (status & TD_CTRL_BITSTUFF) /* Bitstuff error */ - return USB_ST_BITSTUFF; - if (status & TD_CTRL_CRCTIMEO) { /* CRC/Timeout */ - if (dir_out) - return USB_ST_NORESPONSE; - else - return USB_ST_CRC; - } - if (status & TD_CTRL_NAK) /* NAK */ - return USB_ST_TIMEOUT; - if (status & TD_CTRL_BABBLE) /* Babble */ - return USB_ST_STALL; - if (status & TD_CTRL_DBUFERR) /* Buffer error */ - return USB_ST_BUFFERUNDERRUN; - if (status & TD_CTRL_STALLED) /* Stalled */ - return USB_ST_STALL; - if (status & TD_CTRL_ACTIVE) /* Active */ - return USB_ST_NOERROR; + unsigned long flags=0; - return USB_ST_INTERNALERROR; + if (do_lock) + spin_lock_irqsave (&s->urb_list_lock, flags); + + list_add_tail (p, &s->urb_list); + + if (do_lock) + spin_unlock_irqrestore (&s->urb_list_lock, flags); } -/* - * Return the result of a TD.. - */ -static int uhci_td_result(struct uhci_device *dev, struct uhci_td *td, unsigned long *rval) +/*-------------------------------------------------------------------*/ +static void dequeue_urb (puhci_t s, struct list_head *p, int do_lock) { - unsigned int status; - struct uhci_td *tmp; - int count = 1000, actlength, explength; - - /* Start at the TD first in the chain, if possible */ - if (td->qh && td->qh->first) - tmp = td->qh->first; - else - tmp = td; - - if (!tmp) - return USB_ST_INTERNALERROR; + unsigned long flags=0; + + if (do_lock) + spin_lock_irqsave (&s->urb_list_lock, flags); - if (rval) - *rval = 0; + list_del (p); - /* Locate the first failing td, if any */ - do { - status = uhci_status_bits(tmp->status); + if (do_lock) + spin_unlock_irqrestore (&s->urb_list_lock, flags); +} - if (status) - break; +/*-------------------------------------------------------------------*/ +static int alloc_td (puhci_desc_t * new, int flags) +{ +#ifdef _UHCI_SLAB + *new= kmem_cache_alloc(uhci_desc_kmem, in_interrupt ()? SLAB_ATOMIC : SLAB_KERNEL); +#else + *new = (uhci_desc_t *) kmalloc (sizeof (uhci_desc_t), in_interrupt ()? GFP_ATOMIC : GFP_KERNEL); +#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 - /* The length field is only valid if the TD was completed */ - if (!(tmp->status & TD_CTRL_ACTIVE) && uhci_packetin(tmp->info)) { - explength = uhci_expected_length(tmp->info); - actlength = uhci_actual_length(tmp->status); - if (rval) - *rval += actlength; - - if (explength != actlength && - ((tmp->pipetype == PIPE_BULK) || (tmp->pipetype == PIPE_CONTROL))) { - /* If the packet is short, none of the */ - /* packets after this were processed, so */ - /* fix the DT accordingly */ - if (in_interrupt() || uhci_debug) - printk(KERN_DEBUG "Set toggle from %p rval %ld%c for status=%x to %d, exp=%d, act=%d\n", - tmp, rval ? *rval : 0, - rval ? '*' : '/', tmp->status, - uhci_toggle(tmp->info) ^ 1, - explength, actlength); - usb_settoggle(dev->usb, uhci_endpoint(tmp->info), - uhci_packetout(tmp->info), - uhci_toggle(tmp->info) ^ 1); - break; /* Short packet */ - } - } + (*new)->type = TD_TYPE; + INIT_LIST_HEAD (&(*new)->vertical); + INIT_LIST_HEAD (&(*new)->horizontal); + + return 0; +} +/*-------------------------------------------------------------------*/ +/* insert td at last position in td-list of qh (vertical) */ +static int insert_td (puhci_t s, puhci_desc_t qh, puhci_desc_t new, int flags) +{ + uhci_desc_t *prev; + unsigned long xxx; + + spin_lock_irqsave (&s->td_lock, xxx); - if ((tmp->link & UHCI_PTR_TERM) || (tmp->link & UHCI_PTR_QH)) - break; + list_add_tail (&new->vertical, &qh->vertical); - tmp = uhci_ptr_to_virt(tmp->link); - } while (--count); - - if (!count) { - printk(KERN_ERR "runaway td's in uhci_td_result!\n"); - } else { - /* If we got to the last TD */ - - /* No error */ - if (!status) - return USB_ST_NOERROR; - - /* APC BackUPS Pro kludge */ - /* It tries to send all of the descriptor instead of */ - /* the amount we requested */ - if (tmp->status & TD_CTRL_IOC && - tmp->status & TD_CTRL_ACTIVE && - tmp->status & TD_CTRL_NAK && - tmp->pipetype == PIPE_CONTROL) - return USB_ST_NOERROR; - - /* We got to an error, but the controller hasn't finished */ - /* with it yet */ - if (tmp->status & TD_CTRL_ACTIVE) - return USB_ST_NOCHANGE; - - /* If this wasn't the last TD and SPD is set, ACTIVE */ - /* is not and NAK isn't then we received a short */ - /* packet */ - if (tmp->status & TD_CTRL_SPD && !(tmp->status & TD_CTRL_NAK)) - return USB_ST_NOERROR; - } - - /* Some debugging code */ - if (!count || (!in_interrupt() && uhci_debug)) { - printk(KERN_DEBUG "uhci_td_result() failed with status %x\n", - status); - - /* Print the chain for debugging purposes */ - if (td->qh) - uhci_show_queue(td->qh); - else - uhci_show_td(td); + if (qh->hw.qh.element & UHCI_PTR_TERM) { + // virgin qh without any tds + qh->hw.qh.element = virt_to_bus (new); /* QH's cannot have the DEPTH bit set */ } - - if (status & TD_CTRL_STALLED) { - /* endpoint has stalled - mark it halted */ - usb_endpoint_halt(dev->usb, uhci_endpoint(tmp->info), - uhci_packetout(tmp->info)); - return USB_ST_STALL; + else { + // already tds inserted + prev = list_entry (new->vertical.prev, uhci_desc_t, vertical); + // implicitely remove TERM bit of prev + prev->hw.td.link = virt_to_bus (new) | (flags & UHCI_PTR_DEPTH); } - - if ((status == TD_CTRL_ACTIVE) && (!rval)) - return USB_ST_DATAUNDERRUN; - - return uhci_map_status(status, uhci_packetout(tmp->info)); + + spin_unlock_irqrestore (&s->td_lock, xxx); + + return 0; } - -/* - * Inserts a td into qh list at the top. - * - * Careful about atomicity: even on UP this - * requires a locked access due to the concurrent - * DMA engine. - * - * NOTE! This assumes that first->last is a valid - * list of TD's with the proper backpointers set - * up and all.. - */ -static void uhci_insert_tds_in_qh(struct uhci_qh *qh, struct uhci_td *first, struct uhci_td *last) +/*-------------------------------------------------------------------*/ +/* insert new_td after td (horizontal) */ +static int insert_td_horizontal (puhci_t s, puhci_desc_t td, puhci_desc_t new, int flags) { - unsigned int link = qh->element; - unsigned int new = virt_to_bus(first) | UHCI_PTR_DEPTH; - - for (;;) { - unsigned char success; - - last->link = link; - first->backptr = &qh->element; - asm volatile("lock ; cmpxchg %4,%2 ; sete %0" - :"=q" (success), "=a" (link) - :"m" (qh->element), "1" (link), "r" (new) - :"memory"); - - if (success) { - /* Was there a successor entry? Fix it's backpointer */ - if ((link & UHCI_PTR_TERM) == 0) { - struct uhci_td *next = uhci_ptr_to_virt(link); - next->backptr = &last->link; - } - break; - } - } + uhci_desc_t *next; + unsigned long xxx; + + spin_lock_irqsave (&s->td_lock, xxx); - qh->first = first; - first->qh = qh; - last->qh = qh; + next = list_entry (td->horizontal.next, uhci_desc_t, horizontal); + new->hw.td.link = td->hw.td.link; + list_add (&new->horizontal, &td->horizontal); + td->hw.td.link = virt_to_bus (new); + + spin_unlock_irqrestore (&s->td_lock, xxx); + + return 0; } - -static inline void uhci_insert_td_in_qh(struct uhci_qh *qh, struct uhci_td *td) +/*-------------------------------------------------------------------*/ +static int unlink_td (puhci_t s, puhci_desc_t element) { - uhci_insert_tds_in_qh(qh, td, td); + uhci_desc_t *next, *prev; + int dir = 0; + unsigned long xxx; + + spin_lock_irqsave (&s->td_lock, xxx); + + next = list_entry (element->vertical.next, uhci_desc_t, vertical); + + if (next == element) { + dir = 1; + next = list_entry (element->horizontal.next, uhci_desc_t, horizontal); + prev = list_entry (element->horizontal.prev, uhci_desc_t, horizontal); + } + else { + prev = list_entry (element->vertical.prev, uhci_desc_t, vertical); + } + + if (prev->type == TD_TYPE) + prev->hw.td.link = element->hw.td.link; + else + prev->hw.qh.element = element->hw.td.link; + + wmb (); + + if (dir == 0) + list_del (&element->vertical); + else + list_del (&element->horizontal); + + spin_unlock_irqrestore (&s->td_lock, xxx); + + return 0; } - -static void uhci_insert_qh(struct uhci_qh *qh, struct uhci_qh *newqh) +/*-------------------------------------------------------------------*/ +static int delete_desc (puhci_desc_t element) { - newqh->link = qh->link; - qh->link = virt_to_bus(newqh) | UHCI_PTR_QH; +#ifdef _UHCI_SLAB + kmem_cache_free(uhci_desc_kmem, element); +#else + kfree (element); +#endif + return 0; } - -static void uhci_remove_qh(struct uhci_qh *qh, struct uhci_qh *remqh) +/*-------------------------------------------------------------------*/ +// Allocates qh element +static int alloc_qh (puhci_desc_t * new) { - struct uhci_qh *lqh = qh; - - while (uhci_ptr_to_virt(lqh->link) != remqh) { - if (lqh->link & UHCI_PTR_TERM) - break; - - lqh = (struct uhci_qh *)uhci_ptr_to_virt(lqh->link); - } - - if (lqh->link & UHCI_PTR_TERM) { - printk(KERN_DEBUG "couldn't find qh in chain!\n"); - return; - } - - lqh->link = remqh->link; +#ifdef _UHCI_SLAB + *new= kmem_cache_alloc(uhci_desc_kmem, in_interrupt ()? SLAB_ATOMIC : SLAB_KERNEL); +#else + *new = (uhci_desc_t *) kmalloc (sizeof (uhci_desc_t), in_interrupt ()? GFP_ATOMIC : GFP_KERNEL); +#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; + INIT_LIST_HEAD (&(*new)->horizontal); + INIT_LIST_HEAD (&(*new)->vertical); + + dbg("Allocated qh @ %p", *new); + + return 0; } - -/* - * Removes td from qh if present. - * - * NOTE! We keep track of both forward and back-pointers, - * so this should be trivial, right? - * - * Wrong. While all TD insert/remove operations are synchronous - * on the CPU, the UHCI controller can (and does) play with the - * very first forward pointer. So we need to validate the backptr - * before we change it, so that we don't by mistake reset the QH - * head to something old. - */ -static void uhci_remove_td(struct uhci_td *td) +/*-------------------------------------------------------------------*/ +// 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 (puhci_t s, puhci_desc_t pos, puhci_desc_t new, int flags) { - unsigned int *backptr = td->backptr; - unsigned int link = td->link; - unsigned int me; - - if (!backptr) - return; - - td->backptr = NULL; - - /* - * This is the easy case: the UHCI will never change "td->link", - * so we can always just look at that and fix up the backpointer - * of any next element.. - */ - if (!(link & UHCI_PTR_TERM)) { - struct uhci_td *next = uhci_ptr_to_virt(link); - next->backptr = backptr; + puhci_desc_t old; + unsigned long xxx; + + spin_lock_irqsave (&s->qh_lock, xxx); + + if (!flags) { + // (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); + pos->hw.qh.head = MAKE_QH_ADDR (new) ; + new->hw.qh.head = MAKE_QH_ADDR (old); } - /* - * The nasty case is "backptr->next", which we need to - * update to "link" _only_ if "backptr" still points - * to us (it may not: maybe backptr is a QH->element - * pointer and the UHCI has changed the value). - */ - me = virt_to_bus(td) | (0xe & *backptr); - asm volatile("lock ; cmpxchg %0,%1" - : - :"r" (link), "m" (*backptr), "a" (me) - :"memory"); - - /* Reset it just in case */ - td->link = UHCI_PTR_TERM; + wmb (); + + spin_unlock_irqrestore (&s->qh_lock, xxx); + + return 0; } - -/* - * Only the USB core should call uhci_alloc_dev and uhci_free_dev - */ -static int uhci_alloc_dev(struct usb_device *usb_dev) +/*-------------------------------------------------------------------*/ +static int unlink_qh (puhci_t s, puhci_desc_t element) { - struct uhci_device *dev; - - /* Allocate the UHCI device private data */ - dev = kmalloc(sizeof(*dev), GFP_KERNEL); - if (!dev) - return -1; - - /* Initialize "dev" */ - memset(dev, 0, sizeof(*dev)); - - usb_dev->hcpriv = dev; - dev->usb = usb_dev; - atomic_set(&dev->refcnt, 1); - - if (usb_dev->parent) - dev->uhci = usb_to_uhci(usb_dev->parent)->uhci; + puhci_desc_t next, prev; + unsigned long xxx; + spin_lock_irqsave (&s->qh_lock, xxx); + + next = list_entry (element->horizontal.next, uhci_desc_t, horizontal); + prev = list_entry (element->horizontal.prev, uhci_desc_t, horizontal); + prev->hw.qh.head = element->hw.qh.head; + wmb (); + list_del (&element->horizontal); + + spin_unlock_irqrestore (&s->qh_lock, xxx); + return 0; } - -static int uhci_free_dev(struct usb_device *usb_dev) +/*-------------------------------------------------------------------*/ +static int delete_qh (puhci_t s, puhci_desc_t qh) { - struct uhci_device *dev = usb_to_uhci(usb_dev); - - if (atomic_dec_and_test(&dev->refcnt)) - kfree(dev); + puhci_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); + unlink_td (s, td); + delete_desc (td); + } + + delete_desc (qh); + return 0; } - -static void uhci_inc_dev_use(struct uhci_device *dev) +/*-------------------------------------------------------------------*/ +void clean_td_chain (puhci_desc_t td) { - atomic_inc(&dev->refcnt); -} + struct list_head *p; + puhci_desc_t td1; -static void uhci_dec_dev_use(struct uhci_device *dev) -{ - uhci_free_dev(dev->usb); + 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 struct uhci_td *uhci_td_alloc(struct uhci_device *dev) +/*-------------------------------------------------------------------*/ +// Removes ALL qhs in chain (paranoia!) +static void cleanup_skel (puhci_t s) { - struct uhci_td *td; - - td = kmem_cache_alloc(uhci_td_cachep, SLAB_KERNEL); - if (!td) - return NULL; - -#ifdef UHCI_DEBUG - if ((__u32)td & UHCI_PTR_BITS) - printk("qh not 16 byte aligned!\n"); -#endif - - td->link = UHCI_PTR_TERM; - td->buffer = 0; + unsigned int n; + puhci_desc_t td; - td->backptr = NULL; - td->qh = NULL; - td->dev_id = NULL; - td->dev = dev; - td->flags = 0; - INIT_LIST_HEAD(&td->irq_list); - atomic_set(&td->refcnt, 1); + dbg("cleanup_skel"); + + for (n = 0; n < 8; n++) { + td = s->int_chain[n]; + clean_td_chain (td); + } - uhci_inc_dev_use(dev); + if (s->iso_td) { + for (n = 0; n < 1024; n++) { + td = s->iso_td[n]; + clean_td_chain (td); + } + kfree (s->iso_td); + } - return td; -} + if (s->framelist) + free_page ((unsigned long) s->framelist); -static void uhci_td_free(struct uhci_td *td) -{ - if (atomic_dec_and_test(&td->refcnt)) { - kmem_cache_free(uhci_td_cachep, td); + if (s->control_chain) { + // completed init_skel? + struct list_head *p; + puhci_desc_t qh, qh1; - if (td->dev) - uhci_dec_dev_use(td->dev); + 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->control_chain) + kfree (s->control_chain); + if (s->bulk_chain) + kfree (s->bulk_chain); + if (s->chain_end) + kfree (s->chain_end); } } - -static struct uhci_qh *uhci_qh_alloc(struct uhci_device *dev) +/*-------------------------------------------------------------------*/ +// allocates framelist and qh-skeletons +// only HW-links provide continous linking, SW-links stay in their domain (ISO/INT) +static int init_skel (puhci_t s) { - struct uhci_qh *qh; - - qh = kmem_cache_alloc(uhci_qh_cachep, SLAB_KERNEL); - if (!qh) - return NULL; + int n, ret; + puhci_desc_t qh, td; + + dbg("init_skel"); + + s->framelist = (__u32 *) get_free_page (GFP_KERNEL); -#ifdef UHCI_DEBUG - if ((__u32)qh & UHCI_PTR_BITS) - printk("qh not 16 byte aligned!\n"); -#endif + if (!s->framelist) + return -ENOMEM; - qh->element = UHCI_PTR_TERM; - qh->link = UHCI_PTR_TERM; + memset (s->framelist, 0, 4096); - qh->dev = dev; - qh->skel = NULL; - qh->first = NULL; - init_waitqueue_head(&qh->wakeup); - atomic_set(&qh->refcnt, 1); + dbg("allocating iso desc pointer list"); + s->iso_td = (puhci_desc_t *) kmalloc (1024 * sizeof (puhci_desc_t), GFP_KERNEL); + + if (!s->iso_td) + goto init_skel_cleanup; + + 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)); + } - uhci_inc_dev_use(dev); + dbg("allocating qh: chain_end"); + ret = alloc_qh (&qh); + + if (ret) + goto init_skel_cleanup; + + s->chain_end = qh; - return qh; -} + 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; + for (n = 0; n < 8; n++) + s->int_chain[n] = 0; -static void uhci_qh_free(struct uhci_qh *qh) -{ - if (atomic_dec_and_test(&qh->refcnt)) { - kmem_cache_free(uhci_qh_cachep, qh); + dbg("allocating skeleton INT-TDs"); + + for (n = 0; n < 8; n++) { + puhci_desc_t td; - if (qh->dev) - uhci_dec_dev_use(qh->dev); + 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->control_chain); + } + else { + s->int_chain[n]->hw.td.link = virt_to_bus (s->int_chain[0]); + } } -} - -/* - * UHCI interrupt list operations.. - */ -static spinlock_t irqlist_lock = SPIN_LOCK_UNLOCKED; -static void uhci_add_irq_list(struct uhci *uhci, struct uhci_td *td, usb_device_irq completed, void *dev_id) -{ - unsigned long flags; + 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]); + for (o = 1, m = 2; m <= 128; o++, m += m) { + // n&(m-1) = n%m + if ((n & (m - 1)) == ((m - 1) / 2)) { + ((puhci_desc_t) s->iso_td[n])->hw.td.link = virt_to_bus (s->int_chain[o]); + } + } + } - td->completed = completed; - td->dev_id = dev_id; + //uhci_show_queue(s->control_chain); + dbg("init_skel exit"); + return 0; // OK - spin_lock_irqsave(&irqlist_lock, flags); - list_add(&td->irq_list, &uhci->interrupt_list); - spin_unlock_irqrestore(&irqlist_lock, flags); + init_skel_cleanup: + cleanup_skel (s); + return -ENOMEM; } -static void uhci_remove_irq_list(struct uhci_td *td) +/*-------------------------------------------------------------------*/ +static void fill_td (puhci_desc_t td, int status, int info, __u32 buffer) { - unsigned long flags; - - spin_lock_irqsave(&irqlist_lock, flags); - list_del(&td->irq_list); - spin_unlock_irqrestore(&irqlist_lock, flags); + td->hw.td.status = status; + td->hw.td.info = info; + td->hw.td.buffer = buffer; } -/* - * frame list manipulation. Used for Isochronous transfers. - * the list of (iso) TD's enqueued in a frame list entry - * is basically a doubly linked list with link being - * the forward pointer and backptr the backward ptr. - * the frame list entry itself doesn't have a back ptr - * (therefore the list is not circular), and the forward pointer - * stops at link entries having the UHCI_PTR_TERM or the UHCI_PTR_QH - * bit set. Maybe it could be extended to handle the QH's also, - * but it doesn't seem necessary right now. - * The layout looks as follows: - * frame list pointer -> iso td's (if any) -> - * periodic interrupt td (if framelist 0) -> irq qh -> control qh -> bulk qh - */ +/*-------------------------------------------------------------------*/ +// LOW LEVEL STUFF +// assembles QHs und TDs for control, bulk and iso +/*-------------------------------------------------------------------*/ +static int uhci_submit_control_urb (purb_t purb) +{ + puhci_desc_t qh, td; + puhci_t s = (puhci_t) purb->dev->bus->hcpriv; + purb_priv_t purb_priv = purb->hcpriv; + unsigned long destination, status; + int maxsze = usb_maxpacket (purb->dev, purb->pipe, usb_pipeout (purb->pipe)); + unsigned long len, bytesrequested; + char *data; -static spinlock_t framelist_lock = SPIN_LOCK_UNLOCKED; + dbg("uhci_submit_control start"); + alloc_qh (&qh); // alloc qh for this request -static void uhci_add_frame_list(struct uhci *uhci, struct uhci_td *td, unsigned framenum) -{ - unsigned long flags; - struct uhci_td *nexttd; - - framenum %= UHCI_NUMFRAMES; - spin_lock_irqsave(&framelist_lock, flags); - td->backptr = &uhci->fl->frame[framenum]; - td->link = uhci->fl->frame[framenum]; - if (!(td->link & (UHCI_PTR_TERM | UHCI_PTR_QH))) { - nexttd = (struct uhci_td *)uhci_ptr_to_virt(td->link); - nexttd->backptr = &td->link; - } - wmb(); - uhci->fl->frame[framenum] = virt_to_bus(td); - spin_unlock_irqrestore(&framelist_lock, flags); -} + if (!qh) + return -ENOMEM; -static void uhci_remove_frame_list(struct uhci *uhci, struct uhci_td *td) -{ - unsigned long flags; - struct uhci_td *nexttd; + alloc_td (&td, UHCI_PTR_DEPTH); // get td for setup stage - if (!td->backptr) - return; - spin_lock_irqsave(&framelist_lock, flags); - *(td->backptr) = td->link; - if (!(td->link & (UHCI_PTR_TERM | UHCI_PTR_QH))) { - nexttd = (struct uhci_td *)uhci_ptr_to_virt(td->link); - nexttd->backptr = td->backptr; - } - spin_unlock_irqrestore(&framelist_lock, flags); - td->backptr = NULL; - /* - * attention: td->link might still be in use by the - * hardware if the td is still active and the hardware - * was processing it. So td->link should be preserved - * until the frame number changes. Don't know what to do... - * udelay(1000) doesn't sound nice, and schedule() - * can't be used as this is called from within interrupt context. - */ - /* - * we should do the same thing as we do with the QH's - * see uhci_insert_tds_in_qh and uhci_remove_td --jerdfelt - */ - /* for now warn if there's a possible problem */ - if (td->status & TD_CTRL_ACTIVE) { - unsigned frn = inw(uhci->io_addr + USBFRNUM); - __u32 link = uhci->fl->frame[frn % UHCI_NUMFRAMES]; - if (!(link & (UHCI_PTR_TERM | UHCI_PTR_QH))) { - struct uhci_td *tdl = (struct uhci_td *)uhci_ptr_to_virt(link); - for (;tdl;) { - if (tdl == td) { - printk(KERN_WARNING "uhci_remove_frame_list: td possibly still in use!!\n"); - break; - } - if (tdl->link & (UHCI_PTR_TERM | UHCI_PTR_QH)) - break; - tdl = (struct uhci_td *)uhci_ptr_to_virt(tdl->link); - } - } + if (!td) { + delete_qh (s, qh); + return -ENOMEM; } -} + /* The "pipe" thing contains the destination in bits 8--18 */ + destination = (purb->pipe & PIPE_DEVEP_MASK) | USB_PID_SETUP; -/* - * This function removes and disallocates all structures set up for a transfer. - * It takes the qh out of the skeleton, removes the tq and the td's. - * It only removes the associated interrupt handler if removeirq is set. - * The *td argument is any td in the list of td's. - */ -static void uhci_remove_transfer(struct uhci_td *td, char removeirq) -{ - int count = 1000; - struct uhci_td *curtd; - unsigned int nextlink; + /* 3 errors */ + status = (purb->pipe & TD_CTRL_LS) | TD_CTRL_ACTIVE | + (purb->transfer_flags & USB_DISABLE_SPD ? 0 : TD_CTRL_SPD) | (3 << 27); - if (td->qh && td->qh->first) - curtd = td->qh->first; - else - curtd = td; + /* Build the TD for the control request, try forever, 8 bytes of data */ + fill_td (td, status, destination | (7 << 21), virt_to_bus (purb->setup_packet)); - /* Remove the QH from the skeleton and then free it */ - uhci_remove_qh(td->qh->skel, td->qh); - uhci_qh_free(td->qh); + /* If direction is "send", change the frame from SETUP (0x2D) + to OUT (0xE1). Else change it from SETUP to IN (0x69). */ - do { - nextlink = curtd->link; + destination ^= (USB_PID_SETUP ^ USB_PID_IN); /* SETUP -> IN */ + + if (usb_pipeout (purb->pipe)) + destination ^= (USB_PID_IN ^ USB_PID_OUT); /* IN -> OUT */ - /* IOC? => remove handler */ - /* HACK: Don't remove if already removed. Prevents deadlock */ - /* in uhci_interrupt_notify and callbacks */ - if (removeirq && (td->status & TD_CTRL_IOC) && - td->irq_list.next != &td->irq_list) - uhci_remove_irq_list(td); + insert_td (s, qh, td, 0); // queue 'setup stage'-td in qh +#if 0 + dbg("SETUP to pipe %x: %x %x %x %x %x %x %x %x", purb->pipe, + purb->setup_packet[0], purb->setup_packet[1], purb->setup_packet[2], purb->setup_packet[3], + purb->setup_packet[4], purb->setup_packet[5], purb->setup_packet[6], purb->setup_packet[7]); + //uhci_show_td(td); +#endif + /* Build the DATA TD's */ + len = purb->transfer_buffer_length; + bytesrequested = len; + data = purb->transfer_buffer; + + while (len > 0) { + int pktsze = len; - /* Remove the TD and then free it */ - uhci_remove_td(curtd); - uhci_td_free(curtd); + alloc_td (&td, UHCI_PTR_DEPTH); + if (!td) { + delete_qh (s, qh); + return -ENOMEM; + } - if (nextlink & UHCI_PTR_TERM) /* Tail? */ - break; + if (pktsze > maxsze) + pktsze = maxsze; - curtd = (struct uhci_td *)uhci_ptr_to_virt(nextlink & ~UHCI_PTR_BITS); - } while (count--); + destination ^= 1 << TD_TOKEN_TOGGLE; // toggle DATA0/1 - if (!count) - printk(KERN_ERR "runaway td's!?\n"); -} + fill_td (td, status, destination | ((pktsze - 1) << 21), + virt_to_bus (data)); // Status, pktsze bytes of data -/* - * Request an interrupt handler.. - * - * Returns 0 (success) or negative (failure). - * Also returns/sets a "handle pointer" that release_irq can use to stop this - * interrupt. (It's really a pointer to the TD.) - */ -static int uhci_request_irq(struct usb_device *usb_dev, unsigned int pipe, - usb_device_irq handler, int period, - void *dev_id, void **handle, long bustime) -{ - struct uhci_device *dev = usb_to_uhci(usb_dev); - struct uhci_td *td = uhci_td_alloc(dev); - struct uhci_qh *qh = uhci_qh_alloc(dev); - unsigned int destination, status; + insert_td (s, qh, td, UHCI_PTR_DEPTH); // queue 'data stage'-td in qh - if (!td || !qh) { - if (td) - uhci_td_free(td); - if (qh) - uhci_qh_free(qh); - return USB_ST_INTERNALERROR; + data += pktsze; + len -= pktsze; } - /* Destination: pipe destination with INPUT */ - destination = (pipe & PIPE_DEVEP_MASK) | usb_packetid(pipe); - - /* Infinite errors is 0, so no bits */ - status = (pipe & TD_CTRL_LS) | TD_CTRL_IOC | TD_CTRL_ACTIVE | - TD_CTRL_SPD; - - td->link = UHCI_PTR_TERM; /* Terminate */ - td->status = status; /* In */ - td->info = destination | ((usb_maxpacket(usb_dev, pipe, usb_pipeout(pipe)) - 1) << 21) | - (usb_gettoggle(usb_dev, usb_pipeendpoint(pipe), usb_pipeout(pipe)) << TD_TOKEN_TOGGLE); + /* Build the final TD for control status */ + /* It's only IN if the pipe is out AND we aren't expecting data */ + destination &= ~0xFF; + + if (usb_pipeout (purb->pipe) | (bytesrequested == 0)) + destination |= USB_PID_IN; + else + destination |= USB_PID_OUT; - td->buffer = virt_to_bus(dev->data); - td->qh = qh; - td->dev = dev; - td->pipetype = PIPE_INTERRUPT; - td->bandwidth_alloc = bustime; + destination |= 1 << TD_TOKEN_TOGGLE; /* End in Data1 */ - /* if period 0, set _REMOVE flag */ - if (!period) - td->flags |= UHCI_TD_REMOVE; + alloc_td (&td, UHCI_PTR_DEPTH); + + if (!td) { + delete_qh (s, qh); + return -ENOMEM; + } - qh->skel = &dev->uhci->skelqh[__interval_to_skel(period)]; + /* 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); - uhci_add_irq_list(dev->uhci, td, handler, dev_id); + insert_td (s, qh, td, UHCI_PTR_DEPTH); // queue status td - uhci_insert_td_in_qh(qh, td); - /* Add it into the skeleton */ - uhci_insert_qh(qh->skel, qh); + list_add (&qh->desc_list, &purb_priv->desc_list); - *handle = (void *)td; + /* Start it up... put low speed first */ + if (purb->pipe & TD_CTRL_LS) + insert_qh (s, s->control_chain, qh, 1); // insert after control chain + else + insert_qh (s, s->bulk_chain, qh, 0); // insert before bulk chain + //uhci_show_queue(s->control_chain); + dbg("uhci_submit_control end"); return 0; } - -/* - * Release an interrupt handler previously allocated using - * uhci_request_irq. This function does no validity checking, so make - * sure you're not releasing an already released handle as it may be - * in use by something else.. - * - * This function can NOT be called from an interrupt. - */ -static int uhci_release_irq(struct usb_device *usb, void *handle) +/*-------------------------------------------------------------------*/ +static int uhci_submit_bulk_urb (purb_t purb) { - struct uhci_td *td; - struct uhci_qh *qh; - - td = (struct uhci_td *)handle; - if (!td) - return USB_ST_INTERNALERROR; + puhci_t s = (puhci_t) purb->dev->bus->hcpriv; + purb_priv_t purb_priv = purb->hcpriv; + puhci_desc_t qh, td; + unsigned long destination, status; + char *data; + unsigned int pipe = purb->pipe; + int maxsze = usb_maxpacket (purb->dev, pipe, usb_pipeout (pipe)); + int info, len; - qh = td->qh; + /* shouldn't the clear_halt be done in the USB core or in the client driver? - Thomas */ + if (usb_endpoint_halted (purb->dev, usb_pipeendpoint (pipe), usb_pipeout (pipe)) && + usb_clear_halt (purb->dev, usb_pipeendpoint (pipe) | (pipe & USB_DIR_IN))) + return -EPIPE; - /* Remove it from the internal irq_list */ - uhci_remove_irq_list(td); + if (!maxsze) + return -EMSGSIZE; + /* FIXME: should tell the client that the endpoint is invalid, i.e. not in the descriptor */ - /* Remove the interrupt TD and QH */ - uhci_remove_td(td); - uhci_remove_qh(qh->skel, qh); + alloc_qh (&qh); // get qh for this request - if (td->completed != NULL) - td->completed(USB_ST_REMOVED, NULL, 0, td->dev_id); + if (!qh) + return -ENOMEM; - /* Free the TD and QH */ - uhci_td_free(td); - uhci_qh_free(qh); + /* The "pipe" thing contains the destination in bits 8--18. */ + destination = (pipe & PIPE_DEVEP_MASK) | usb_packetid (pipe); - return USB_ST_NOERROR; -} /* uhci_release_irq() */ + /* 3 errors */ + status = (pipe & TD_CTRL_LS) | TD_CTRL_ACTIVE | + ((purb->transfer_flags & USB_DISABLE_SPD) ? 0 : TD_CTRL_SPD) | (3 << 27); -/* - * 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 inw (usb_to_uhci(usb_dev)->uhci->io_addr + USBFRNUM); -} + /* Build the TDs for the bulk request */ + len = purb->transfer_buffer_length; + data = purb->transfer_buffer; + dbg("uhci_submit_bulk_urb: pipe %x, len %d", pipe, len); + + while (len > 0) { + int pktsze = len; + alloc_td (&td, UHCI_PTR_DEPTH); -/* - * uhci_init_isoc() - * - * Allocates some data structures. - * Initializes parts of them from the function parameters. - * - * It does not associate any data/buffer pointers or - * driver (caller) callback functions with the allocated - * data structures. Such associations are left until - * uhci_run_isoc(). - * - * Returns 0 for success or negative value for error. - * Sets isocdesc before successful return. - */ -static int uhci_init_isoc(struct usb_device *usb_dev, - unsigned int pipe, - int frame_count, /* bandwidth % = 100 * this / 1024 */ - void *context, - struct usb_isoc_desc **isocdesc) -{ - struct usb_isoc_desc *id; - int i; + if (!td) { + delete_qh (s, qh); + return -ENOMEM; + } - *isocdesc = NULL; + if (pktsze > maxsze) + pktsze = maxsze; - /* Check some parameters. */ - if ((frame_count <= 0) || (frame_count > UHCI_NUMFRAMES)) { -#ifdef CONFIG_USB_DEBUG_ISOC - printk (KERN_DEBUG "uhci_init_isoc: invalid frame_count (%d)\n", - frame_count); -#endif - return -EINVAL; - } + // pktsze bytes of data + info = destination | ((pktsze - 1) << 21) | + (usb_gettoggle (purb->dev, usb_pipeendpoint (pipe), usb_pipeout (pipe)) << TD_TOKEN_TOGGLE); - if (!usb_pipeisoc (pipe)) { -#ifdef CONFIG_USB_DEBUG_ISOC - printk (KERN_DEBUG "uhci_init_isoc: NOT an Isoc. pipe\n"); -#endif - return -EINVAL; - } + fill_td (td, status, info, virt_to_bus (data)); - id = kmalloc (sizeof (*id) + - (sizeof (struct isoc_frame_desc) * frame_count), GFP_KERNEL); - if (!id) - return -ENOMEM; + data += pktsze; + len -= pktsze; - memset (id, 0, sizeof (*id) + - (sizeof (struct isoc_frame_desc) * frame_count)); + if (!len) + td->hw.td.status |= TD_CTRL_IOC; // last one generates INT + //dbg("insert td %p, len %i",td,pktsze); - id->td = kmalloc (sizeof (struct uhci_td) * frame_count, GFP_KERNEL); - if (!id->td) { - kfree (id); - return -ENOMEM; + insert_td (s, qh, td, UHCI_PTR_DEPTH); + + /* Alternate Data0/1 (start with Data0) */ + usb_dotoggle (purb->dev, usb_pipeendpoint (pipe), usb_pipeout (pipe)); } - memset (id->td, 0, sizeof (struct uhci_td) * frame_count); + list_add (&qh->desc_list, &purb_priv->desc_list); - for (i = 0; i < frame_count; i++) - INIT_LIST_HEAD(&((struct uhci_td *)(id->td))[i].irq_list); + insert_qh (s, s->chain_end, qh, 0); // insert before end marker + //uhci_show_queue(s->bulk_chain); - id->frame_count = frame_count; - id->frame_size = usb_maxpacket (usb_dev, pipe, usb_pipeout(pipe)); - /* TBD: or make this a parameter to allow for frame_size - that is less than maxpacketsize */ - id->start_frame = -1; - id->end_frame = -1; - id->usb_dev = usb_dev; - id->pipe = pipe; - id->context = context; - - *isocdesc = id; + dbg("uhci_submit_bulk_urb: exit"); return 0; -} /* end uhci_init_isoc */ - -/* - * uhci_run_isoc() - * - * Associates data/buffer pointers/lengths and - * driver (caller) callback functions with the - * allocated Isoc. data structures and TDs. - * - * Then inserts the TDs into the USB controller frame list - * for its processing. - * And inserts the callback function into its TD. - * - * pr_isocdesc (previous Isoc. desc.) may be NULL. - * It is used only for chaining one list of TDs onto the - * end of the previous list of TDs. - * - * Returns 0 (success) or error code (negative value). - */ -static int uhci_run_isoc (struct usb_isoc_desc *isocdesc, - struct usb_isoc_desc *pr_isocdesc) +} +/*-------------------------------------------------------------------*/ +// unlinks an urb by dequeuing its qh, waits some frames and forgets it +// Problem: unlinking in interrupt requires waiting for one frame (udelay) +// to allow the whole structures to be safely removed +static int uhci_unlink_urb (purb_t purb) { - struct uhci_device *dev = usb_to_uhci (isocdesc->usb_dev); - struct uhci *uhci = dev->uhci; - unsigned long destination, status; - struct uhci_td *td; - int ix, cur_frame, pipeinput, frlen; - int cb_frames = 0; - struct isoc_frame_desc *fd; - unsigned char *bufptr; - - if (!isocdesc->callback_fn) { -#ifdef CONFIG_USB_DEBUG_ISOC - printk (KERN_DEBUG "uhci_run_isoc: caller must have a callback function\n"); -#endif - return -EINVAL; - } - - /* Check buffer size large enough for maxpacketsize * frame_count. */ - if (isocdesc->buf_size < (isocdesc->frame_count * isocdesc->frame_size)) { -#ifdef CONFIG_USB_DEBUG_ISOC - printk (KERN_DEBUG "uhci_init_isoc: buf_size too small (%d < %d)\n", - isocdesc->buf_size, isocdesc->frame_count * isocdesc->frame_size); -#endif - return -EINVAL; - } + puhci_t s; + puhci_desc_t qh; + puhci_desc_t td; + purb_priv_t purb_priv; + unsigned long flags=0; + struct list_head *p; + + if (!purb) // you never know... + return -1; - /* Check buffer ptr for Null. */ - if (!isocdesc->data) { -#ifdef CONFIG_USB_DEBUG_ISOC - printk (KERN_DEBUG "uhci_init_isoc: data ptr is null\n"); -#endif - return -EINVAL; - } + s = (puhci_t) purb->dev->bus->hcpriv; // get pointer to uhci struct -#ifdef NEED_ALIGNMENT - /* Check data page alignment. */ - if (((int)(isocdesc->data) & (PAGE_SIZE - 1)) != 0) { -#ifdef CONFIG_USB_DEBUG_ISOC - printk (KERN_DEBUG "uhci_init_isoc: buffer must be page-aligned (%p)\n", - isocdesc->data); -#endif - return -EINVAL; - } -#endif /* NEED_ALIGNMENT */ + if (usb_pipedevice (purb->pipe) == s->rh.devnum) + return rh_unlink_urb (purb); - /* - * Check start_type unless pr_isocdesc is used. - */ - if (!pr_isocdesc && (isocdesc->start_type > START_TYPE_MAX)) { -#ifdef CONFIG_USB_DEBUG_ISOC - printk (KERN_DEBUG "uhci_run_isoc: invalid start_type (%d)\n", - isocdesc->start_type); -#endif - return -EINVAL; + if(!in_interrupt()) { + // is the following really necessary? dequeue_urb has its own spinlock (GA) + spin_lock_irqsave (&s->unlink_urb_lock, flags); // do not allow interrupts } + + //dbg("unlink_urb called %p",purb); + if (purb->status == USB_ST_URB_PENDING) { + // URB probably still in work + purb_priv = purb->hcpriv; + dequeue_urb (s, &purb->urb_list,1); + purb->status = USB_ST_URB_KILLED; // mark urb as killed + + if(!in_interrupt()) { + spin_unlock_irqrestore (&s->unlink_urb_lock, flags); // allow interrupts from here + } - /* - * Check start_frame for inside a valid range. - * Only allow transfer requests to be made 1.000 second - * into the future. - */ - cur_frame = uhci_get_current_frame_number (isocdesc->usb_dev); - - /* if not START_ASAP (i.e., RELATIVE or ABSOLUTE): */ - if (!pr_isocdesc) { - if (isocdesc->start_type == START_RELATIVE) { - if ((isocdesc->start_frame < 0) || (isocdesc->start_frame > CAN_SCHEDULE_FRAMES)) { -#ifdef CONFIG_USB_DEBUG_ISOC - printk (KERN_DEBUG "uhci_init_isoc: bad start_frame value (%d)\n", - isocdesc->start_frame); -#endif - return -EINVAL; + switch (usb_pipetype (purb->pipe)) { + case PIPE_ISOCHRONOUS: + case PIPE_INTERRUPT: + for (p = purb_priv->desc_list.next; p != &purb_priv->desc_list; p = p->next) { + td = list_entry (p, uhci_desc_t, desc_list); + unlink_td (s, td); } - } /* end START_RELATIVE */ - else - if (isocdesc->start_type == START_ABSOLUTE) { /* within the scope of cur_frame */ - ix = USB_WRAP_FRAMENR(isocdesc->start_frame - cur_frame); - if (ix < START_FRAME_FUDGE || /* too small */ - ix > CAN_SCHEDULE_FRAMES) { /* too large */ -#ifdef CONFIG_USB_DEBUG_ISOC - printk (KERN_DEBUG "uhci_init_isoc: bad start_frame value (%d,%d)\n", - isocdesc->start_frame, cur_frame); -#endif - return -EINVAL; + // wait at least 1 Frame + if (in_interrupt ()) + udelay (1000); + else + schedule_timeout (1 + 1 * HZ / 1000); + while ((p = purb_priv->desc_list.next) != &purb_priv->desc_list) { + td = list_entry (p, uhci_desc_t, desc_list); + list_del (p); + delete_desc (td); } - } /* end START_ABSOLUTE */ - } /* end not pr_isocdesc */ - - /* - * Set the start/end frame numbers. - */ - if (pr_isocdesc) { - isocdesc->start_frame = pr_isocdesc->end_frame + 1; - } else if (isocdesc->start_type == START_RELATIVE) { - if (isocdesc->start_frame < START_FRAME_FUDGE) - isocdesc->start_frame = START_FRAME_FUDGE; - isocdesc->start_frame += cur_frame; - } else if (isocdesc->start_type == START_ASAP) { - isocdesc->start_frame = cur_frame + START_FRAME_FUDGE; - } - - /* and see if start_frame needs any correction */ - /* only wrap to USB frame numbers, the frame_list insertion routine - takes care of the wrapping to the frame_list size */ - isocdesc->start_frame = USB_WRAP_FRAMENR(isocdesc->start_frame); - - /* and fix the end_frame value */ - isocdesc->end_frame = USB_WRAP_FRAMENR(isocdesc->start_frame + isocdesc->frame_count - 1); - - isocdesc->prev_completed_frame = -1; - isocdesc->cur_completed_frame = -1; - - destination = (isocdesc->pipe & PIPE_DEVEP_MASK) | - usb_packetid (isocdesc->pipe); - status = TD_CTRL_ACTIVE | TD_CTRL_IOS; /* mark Isoc.; can't be low speed */ - pipeinput = usb_pipein (isocdesc->pipe); - cur_frame = isocdesc->start_frame; - bufptr = isocdesc->data; - - /* - * Build the Data TDs. - * TBD: Not using frame_spacing (Yet). Defaults to 1 (every frame). - * (frame_spacing is a way to request less bandwidth.) - * This can also be done by using frame_length = 0 in the - * frame_desc array, but this way won't take less bandwidth - * allocation into account. - */ - if (isocdesc->frame_spacing <= 0) - isocdesc->frame_spacing = 1; - - for (ix = 0, td = isocdesc->td, fd = isocdesc->frames; - ix < isocdesc->frame_count; ix++, td++, fd++) { - frlen = fd->frame_length; - if (frlen > isocdesc->frame_size) - frlen = isocdesc->frame_size; - -#ifdef NOTDEF - td->info = destination | /* use Actual len on OUT; max. on IN */ - (pipeinput ? ((isocdesc->frame_size - 1) << 21) - : ((frlen - 1) << 21)); -#endif + break; - td->dev_id = isocdesc; /* can get dev_id or context from isocdesc */ - td->status = status; - td->info = destination | ((frlen - 1) << 21); - td->buffer = virt_to_bus (bufptr); - td->dev = dev; - td->pipetype = PIPE_ISOCHRONOUS; - td->isoc_td_number = ix; /* 0-based; does not wrap/overflow back to 0 */ - - if (isocdesc->callback_frames && - (++cb_frames >= isocdesc->callback_frames)) { - td->status |= TD_CTRL_IOC; - td->completed = isocdesc->callback_fn; - cb_frames = 0; - uhci_add_irq_list (dev->uhci, td, isocdesc->callback_fn, isocdesc); - } + case PIPE_BULK: + case PIPE_CONTROL: + qh = list_entry (purb_priv->desc_list.next, uhci_desc_t, desc_list); - bufptr += fd->frame_length; /* or isocdesc->frame_size; */ + unlink_qh (s, qh); // remove this qh from qh-list + // wait at least 1 Frame - /* - * Insert the TD in the frame list. - */ - uhci_add_frame_list(uhci, td, cur_frame); + if (in_interrupt ()) + udelay (1000); + else + schedule_timeout (1 + 1 * HZ / 1000); + delete_qh (s, qh); // remove it physically - cur_frame = USB_WRAP_FRAMENR(cur_frame+1); - } /* end for ix */ - - /* - * Add IOC on the last TD. - */ - td--; - if (!(td->status & TD_CTRL_IOC)) { - td->status |= TD_CTRL_IOC; - td->completed = isocdesc->callback_fn; - uhci_add_irq_list(dev->uhci, td, isocdesc->callback_fn, isocdesc); /* TBD: D.K. ??? */ + } + +#ifdef _UHCI_SLAB + kmem_cache_free(urb_priv_kmem, purb->hcpriv); +#else + kfree (purb->hcpriv); +#endif + if (purb->complete) { + dbg("unlink_urb: calling completion"); + purb->complete ((struct urb *) purb); + usb_dec_dev_use (purb->dev); + } + return 0; + } + else { + if(!in_interrupt()) + spin_unlock_irqrestore (&s->unlink_urb_lock, flags); // allow interrupts from here } return 0; -} /* end uhci_run_isoc */ - -/* - * uhci_kill_isoc() - * - * Marks a TD list as Inactive and removes it from the Isoc. - * TD frame list. - * - * Does not free any memory resources. - * - * Returns 0 for success or negative value for error. - */ -static int uhci_kill_isoc (struct usb_isoc_desc *isocdesc) +} +/*-------------------------------------------------------------------*/ +// 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 (purb_t purb, unsigned int *start, unsigned int *end) { - struct uhci_device *dev = usb_to_uhci (isocdesc->usb_dev); - struct uhci *uhci = dev->uhci; - struct uhci_td *td; - int ix; - - if (USB_WRAP_FRAMENR(isocdesc->start_frame) != isocdesc->start_frame) { -#ifdef CONFIG_USB_DEBUG_ISOC - printk (KERN_DEBUG "uhci_kill_isoc: invalid start_frame (%d)\n", - isocdesc->start_frame); -#endif - return -EINVAL; + purb_t u, last_urb = NULL; + puhci_t s = (puhci_t) purb->dev->bus->hcpriv; + struct list_head *p = s->urb_list.next; + int ret=-1; + unsigned long flags; + + spin_lock_irqsave (&s->urb_list_lock, flags); + + for (; p != &s->urb_list; p = p->next) { + 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 ((purb->pipe == u->pipe) && (purb->dev == u->dev) && (u->status == USB_ST_URB_PENDING)) { + 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; // no previous urb found - for (ix = 0, td = isocdesc->td; ix < isocdesc->frame_count; ix++, td++) { - /* Deactivate and unlink */ - uhci_remove_frame_list(uhci, td); - td->status &= ~(TD_CTRL_ACTIVE | TD_CTRL_IOC); - } /* end for ix */ - - isocdesc->start_frame = -1; - return 0; -} /* end uhci_kill_isoc */ +} +/*-------------------------------------------------------------------*/ +// adjust start_frame according to scheduling constraints (ASAP etc) -static void uhci_free_isoc(struct usb_isoc_desc *isocdesc) +static void jnx_show_desc (puhci_desc_t d) { - int i; + switch (d->type) { + case TD_TYPE: + dbg("td @ 0x%08lx: link 0x%08x status 0x%08x info 0x%08x buffer 0x%08x", + (unsigned long) d, d->hw.td.link, d->hw.td.status, d->hw.td.info, d->hw.td.buffer); + if (!(d->hw.td.link & UHCI_PTR_TERM)) + jnx_show_desc ((puhci_desc_t) bus_to_virt (d->hw.td.link & ~UHCI_PTR_BITS)); + break; - /* If still Active, kill it. */ - if (isocdesc->start_frame >= 0) - uhci_kill_isoc(isocdesc); + case QH_TYPE: + dbg("qh @ 0x%08lx: head 0x%08x element 0x%08x", + (unsigned long) d, d->hw.qh.head, d->hw.qh.element); + if (!(d->hw.qh.element & UHCI_PTR_TERM)) + jnx_show_desc ((puhci_desc_t) bus_to_virt (d->hw.qh.element & ~UHCI_PTR_BITS)); + if (!(d->hw.qh.head & UHCI_PTR_TERM)) + jnx_show_desc ((puhci_desc_t) bus_to_virt (d->hw.qh.head & ~UHCI_PTR_BITS)); + break; - /* Remove all TD's from the IRQ list. */ - for (i = 0; i < isocdesc->frame_count; i++) - uhci_remove_irq_list(((struct uhci_td *)isocdesc->td) + i); + default: + dbg("desc @ 0x%08lx: invalid type %u", (unsigned long) d, d->type); + } +} - /* Free the associate memory. */ - if (isocdesc->td) - kfree(isocdesc->td); +/*-------------------------------------------------------------------*/ +static int iso_find_start (purb_t purb) +{ + puhci_t s = (puhci_t) purb->dev->bus->hcpriv; + unsigned int now; + unsigned int start_limit = 0, stop_limit = 0, queued_size; + int limits; - kfree(isocdesc); -} /* end uhci_free_isoc */ + now = UHCI_GET_CURRENT_FRAME (s) & 1023; -/* - * Control thread operations: we just mark the last TD - * in a control thread as an interrupt TD, and wake up - * the front-end on completion. - * - * We need to remove the TD from the lists (both interrupt - * list and TD lists) by hand if something bad happens! - */ + if ((unsigned) purb->number_of_packets > 900) + return -EFBIG; + + limits = find_iso_limits (purb, &start_limit, &stop_limit); + queued_size = (stop_limit - start_limit) & 1023; + + if (purb->transfer_flags & USB_ISO_ASAP) { + // first iso + if (limits) { + // 10ms setup should be enough //FIXME! + purb->start_frame = (now + 10) & 1023; + } + else { + purb->start_frame = stop_limit; //seamless linkage + + if (((now - purb->start_frame) & 1023) <= (unsigned) purb->number_of_packets) { + dbg("iso_find_start: warning, ASAP gap, should not happen"); + dbg("iso_find_start: now %u start_frame %u number_of_packets %u pipe 0x%08x", + now, purb->start_frame, purb->number_of_packets, purb->pipe); + { + puhci_t s = (puhci_t) purb->dev->bus->hcpriv; + struct list_head *p; + purb_t u; + int a = -1, b = -1; + unsigned long flags; + + spin_lock_irqsave (&s->urb_list_lock, flags); + p=s->urb_list.next; + + for (; p != &s->urb_list; p = p->next) { + u = list_entry (p, urb_t, urb_list); + if (purb->dev != u->dev) + continue; + dbg("urb: pipe 0x%08x status %d start_frame %u number_of_packets %u", + u->pipe, u->status, u->start_frame, u->number_of_packets); + if (!usb_pipeisoc (u->pipe)) + continue; + if (a == -1) + a = u->start_frame; + b = (u->start_frame + u->number_of_packets - 1) & 1023; + } + spin_unlock_irqrestore(&s->urb_list_lock, flags); +#if 0 + if (a != -1 && b != -1) { + do { + jnx_show_desc (s->iso_td[a]); + a = (a + 1) & 1023; + } + while (a != b); + } +#endif + } + purb->start_frame = (now + 5) & 1023; // 5ms setup should be enough //FIXME! + //return -EAGAIN; //FIXME -static int uhci_generic_completed(int status, void *buffer, int len, void *dev_id) -{ - wait_queue_head_t *wakeup = (wait_queue_head_t *)dev_id; + } + } + } + else { + purb->start_frame &= 1023; + if (((now - purb->start_frame) & 1023) < (unsigned) purb->number_of_packets) { + dbg("iso_find_start: now between start_frame and end"); + return -EAGAIN; + } + } - if (waitqueue_active(wakeup)) - wake_up(wakeup); - else - printk("waitqueue empty!\n"); + /* check if either start_frame or start_frame+number_of_packets-1 lies between start_limit and stop_limit */ + if (limits) + return 0; + if (((purb->start_frame - start_limit) & 1023) < queued_size || + ((purb->start_frame + purb->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", + purb->start_frame, purb->number_of_packets, start_limit, stop_limit); + return -EAGAIN; + } - return 0; /* Don't re-instate */ + return 0; } +/*-------------------------------------------------------------------*/ +// submits USB interrupt (ie. polling ;-) +// ASAP-flag set implicitely +// if period==0, the the transfer is only done once (usb_scsi need this...) -/* td points to the last td in the list, which interrupts on completion */ -static int uhci_run_control(struct uhci_device *dev, struct uhci_td *first, struct uhci_td *last, int timeout) +static int uhci_submit_int_urb (purb_t purb) { - DECLARE_WAITQUEUE(wait, current); - struct uhci_qh *qh = uhci_qh_alloc(dev); - unsigned long rval; - int ret; + puhci_t s = (puhci_t) purb->dev->bus->hcpriv; + purb_priv_t purb_priv = purb->hcpriv; + int nint, n, ret; + puhci_desc_t td; + int status, destination; + int now; + int info; + unsigned int pipe = purb->pipe; + + //dbg("SUBMIT INT"); + + if (purb->interval < 0 || purb->interval >= 256) + return -EINVAL; - if (!qh) - return USB_ST_INTERNALERROR; + if (purb->interval == 0) + nint = 0; + else { + for (nint = 0, n = 1; nint <= 8; nint++, n += n) // round interval down to 2^n + { + if (purb->interval < n) { + purb->interval = n / 2; + break; + } + } + nint--; + } + dbg("Rounded interval to %i, chain %i", purb->interval, nint); - current->state = TASK_UNINTERRUPTIBLE; - add_wait_queue(&qh->wakeup, &wait); + now = UHCI_GET_CURRENT_FRAME (s) & 1023; + purb->start_frame = now; // remember start frame, just in case... - uhci_add_irq_list(dev->uhci, last, uhci_generic_completed, &qh->wakeup); - - uhci_insert_tds_in_qh(qh, first, last); + purb->number_of_packets = 1; - /* Add it into the skeleton */ - uhci_insert_qh(&dev->uhci->skel_control_qh, qh); + // INT allows only one packet + if (purb->transfer_buffer_length > usb_maxpacket (purb->dev, pipe, usb_pipeout (pipe))) + return -EINVAL; - /* wait a user specified reasonable amount of time */ - schedule_timeout(timeout); + ret = alloc_td (&td, UHCI_PTR_DEPTH); - remove_wait_queue(&qh->wakeup, &wait); + if (ret) + return -ENOMEM; - /* Clean up in case it failed.. */ - uhci_remove_irq_list(last); + status = (pipe & TD_CTRL_LS) | TD_CTRL_ACTIVE | TD_CTRL_IOC | + (purb->transfer_flags & USB_DISABLE_SPD ? 0 : TD_CTRL_SPD) | (1 << 27); - /* Remove it from the skeleton */ - uhci_remove_qh(&dev->uhci->skel_control_qh, qh); + destination = (purb->pipe & PIPE_DEVEP_MASK) | usb_packetid (purb->pipe) | + (((purb->transfer_buffer_length - 1) & 0x7ff) << 21); - /* Need to check result before free'ing the qh */ - ret = uhci_td_result(dev, last, &rval); - uhci_qh_free(qh); + info = destination | (usb_gettoggle (purb->dev, usb_pipeendpoint (pipe), usb_pipeout (pipe)) << TD_TOKEN_TOGGLE); - return (ret < 0) ? ret : rval; -} + fill_td (td, status, info, virt_to_bus (purb->transfer_buffer)); + list_add_tail (&td->desc_list, &purb_priv->desc_list); + insert_td_horizontal (s, s->int_chain[nint], td, UHCI_PTR_DEPTH); // store in INT-TDs -/* - * Send or receive a control message on a pipe. - * - * Note that the "pipe" structure is set up to map - * easily to the uhci destination fields. - * - * A control message is built up from three parts: - * - The command itself - * - [ optional ] data phase - * - Status complete phase - * - * The data phase can be an arbitrary number of TD's - * although we currently had better not have more than - * 29 TD's here (we have 31 TD's allocated for control - * operations, and two of them are used for command and - * status). - * - * 29 TD's is a minimum of 232 bytes worth of control - * information, that's just ridiculously high. Most - * control messages have just a few bytes of data. - * - * 232 is not ridiculously high with many of the - * configurations on audio devices, etc. anyway, - * there is no restriction on length of transfers - * anymore - */ -static int uhci_control_msg(struct usb_device *usb_dev, unsigned int pipe, devrequest *cmd, - void *data, int len, int timeout) -{ - struct uhci_device *dev = usb_to_uhci(usb_dev); - struct uhci_td *first, *td, *prevtd; - unsigned long destination, status; - int ret, count; - int maxsze = usb_maxpacket(usb_dev, pipe, usb_pipeout(pipe)); - __u32 nextlink; - unsigned long bytesrequested = len; + usb_dotoggle (purb->dev, usb_pipeendpoint (pipe), usb_pipeout (pipe)); - first = td = uhci_td_alloc(dev); - if (!td) - return USB_ST_INTERNALERROR; +#if 0 + td = tdm[purb->number_of_packets]; + fill_td (td, TD_CTRL_IOC, 0, 0); + insert_td_horizontal (s, s->iso_td[(purb->start_frame + (purb->number_of_packets) * purb->interval + 1) & 1023], td, UHCI_PTR_DEPTH); + list_add_tail (&td->desc_list, &purb_priv->desc_list); +#endif - /* The "pipe" thing contains the destination in bits 8--18 */ - destination = (pipe & PIPE_DEVEP_MASK) | USB_PID_SETUP; + return 0; +} +/*-------------------------------------------------------------------*/ +static int uhci_submit_iso_urb (purb_t purb) +{ + puhci_t s = (puhci_t) purb->dev->bus->hcpriv; + purb_priv_t purb_priv = purb->hcpriv; + int n, ret; + puhci_desc_t td, *tdm; + int status, destination; + unsigned long flags; + spinlock_t lock; - /* 3 errors */ - status = (pipe & TD_CTRL_LS) | TD_CTRL_ACTIVE | TD_CTRL_SPD | (3 << 27); + spin_lock_init (&lock); + spin_lock_irqsave (&lock, flags); // Disable IRQs to schedule all ISO-TDs in time - /* - * Build the TD for the control request - */ - td->status = status; /* Try forever */ - td->info = destination | (7 << 21); /* 8 bytes of data */ - td->buffer = virt_to_bus(cmd); - td->pipetype = PIPE_CONTROL; + ret = iso_find_start (purb); // adjusts purb->start_frame for later use - /* - * If direction is "send", change the frame from SETUP (0x2D) - * to OUT (0xE1). Else change it from SETUP to IN (0x69). - */ - destination ^= (USB_PID_SETUP ^ USB_PID_IN); /* SETUP -> IN */ - if (usb_pipeout(pipe)) - destination ^= (USB_PID_IN ^ USB_PID_OUT); /* IN -> OUT */ + if (ret) + goto err; - prevtd = td; - td = uhci_td_alloc(dev); - if (!td) { - uhci_td_free(prevtd); - return USB_ST_INTERNALERROR; + tdm = (puhci_desc_t *) kmalloc (purb->number_of_packets * sizeof (puhci_desc_t), in_interrupt ()? GFP_ATOMIC : GFP_KERNEL); + + if (!tdm) { + ret = -ENOMEM; + goto err; } - prevtd->link = virt_to_bus(td) | UHCI_PTR_DEPTH; + // First try to get all TDs + for (n = 0; n < purb->number_of_packets; n++) { + dbg("n:%d purb->iso_frame_desc[n].length:%d", n, purb->iso_frame_desc[n].length); + if (!purb->iso_frame_desc[n].length) { + // allows ISO striping by setting length to zero in iso_descriptor + tdm[n] = 0; + continue; + } + ret = alloc_td (&td, UHCI_PTR_DEPTH); + if (ret) { + int i; // Cleanup allocated TDs + + for (i = 0; i < n; n++) + if (tdm[i]) + kfree (tdm[i]); + kfree (tdm); + ret = -ENOMEM; + goto err; + } + tdm[n] = td; + } - /* - * Build the DATA TD's - */ - while (len > 0) { - /* Build the TD for control status */ - int pktsze = len; + status = TD_CTRL_ACTIVE | TD_CTRL_IOS; //| (purb->transfer_flags&USB_DISABLE_SPD?0:TD_CTRL_SPD); - if (pktsze > maxsze) - pktsze = maxsze; + destination = (purb->pipe & PIPE_DEVEP_MASK) | usb_packetid (purb->pipe); - /* Alternate Data0/1 (start with Data1) */ - destination ^= 1 << TD_TOKEN_TOGGLE; - - td->status = status; /* Status */ - td->info = destination | ((pktsze - 1) << 21); /* pktsze bytes of data */ - td->buffer = virt_to_bus(data); - td->backptr = &prevtd->link; - td->pipetype = PIPE_CONTROL; + // Queue all allocated TDs + for (n = 0; n < purb->number_of_packets; n++) { + td = tdm[n]; + if (!td) + continue; + if (n + 1 >= purb->number_of_packets) + status |= TD_CTRL_IOC; - data += pktsze; - len -= pktsze; + fill_td (td, status, destination | (((purb->iso_frame_desc[n].length - 1) & 0x7ff) << 21), + virt_to_bus (purb->transfer_buffer + purb->iso_frame_desc[n].offset)); + list_add_tail (&td->desc_list, &purb_priv->desc_list); + insert_td_horizontal (s, s->iso_td[(purb->start_frame + n) & 1023], td, UHCI_PTR_DEPTH); // store in iso-tds + //uhci_show_td(td); - prevtd = td; - td = uhci_td_alloc(dev); - if (!td) - return USB_ST_INTERNALERROR; - prevtd->link = virt_to_bus(td) | UHCI_PTR_DEPTH; /* Update previous TD */ } - /* - * Build the final TD for control status - * - * It's IN if the pipe is an output pipe or we're not expecting - * data back. - */ - destination &= ~0xFF; - if (usb_pipeout(pipe) || !bytesrequested) - destination |= USB_PID_IN; - else - destination |= USB_PID_OUT; + kfree (tdm); + dbg("ISO-INT# %i, start %i, now %i", purb->number_of_packets, purb->start_frame, UHCI_GET_CURRENT_FRAME (s) & 1023); + ret = 0; - destination |= 1 << TD_TOKEN_TOGGLE; /* End in Data1 */ + err: + spin_unlock_irqrestore (&lock, flags); + return ret; - td->status = status | TD_CTRL_IOC; /* no limit on errors on final packet */ - td->info = destination | (UHCI_NULL_DATA_SIZE << 21); /* 0 bytes of data */ - td->buffer = 0; - td->backptr = &prevtd->link; - td->link = UHCI_PTR_TERM; /* Terminate */ - td->pipetype = PIPE_CONTROL; +} +/*-------------------------------------------------------------------*/ +static int search_dev_ep (puhci_t s, purb_t purb) +{ + unsigned long flags; + struct list_head *p = s->urb_list.next; + purb_t tmp; + + dbg("search_dev_ep:"); + spin_lock_irqsave (&s->urb_list_lock, flags); + + 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_pipetype (purb->pipe) != PIPE_ISOCHRONOUS && + tmp->dev == purb->dev && tmp->pipe == purb->pipe) || (purb == tmp)) { + spin_unlock_irqrestore (&s->urb_list_lock, flags); + return 1; // found another urb already queued for processing + } + } - /* Start it up.. */ - ret = uhci_run_control(dev, first, td, timeout); + spin_unlock_irqrestore (&s->urb_list_lock, flags); - count = 1000; - td = first; - do { - nextlink = td->link; - uhci_remove_td(td); - uhci_td_free(td); + return 0; +} +/*-------------------------------------------------------------------*/ +static int uhci_submit_urb (purb_t purb) +{ + puhci_t s; + purb_priv_t purb_priv; + int ret = 0; - if (nextlink & UHCI_PTR_TERM) /* Tail? */ - break; + if (!purb->dev || !purb->dev->bus) + return -ENODEV; - td = uhci_ptr_to_virt(nextlink); - } while (--count); + s = (puhci_t) purb->dev->bus->hcpriv; + //dbg("submit_urb: %p type %d",purb,usb_pipetype(purb->pipe)); - if (!count) - printk(KERN_ERR "runaway td's!?\n"); + if (usb_pipedevice (purb->pipe) == s->rh.devnum) + return rh_submit_urb (purb); /* virtual root hub */ -#ifdef UHCI_DEBUG - if (ret >= 0 && ret != bytesrequested && bytesrequested) - printk("requested %ld bytes, got %d\n", bytesrequested, ret); -#endif + usb_inc_dev_use (purb->dev); - return ret; -} + if (search_dev_ep (s, purb)) { + usb_dec_dev_use (purb->dev); + return -ENXIO; // urb already queued -/* - * Bulk thread operations: we just mark the last TD - * in a bulk thread as an interrupt TD, and wake up - * the front-end on completion. - * - * We need to remove the TD from the lists (both interrupt - * list and TD lists) by hand if something bad happens! - */ + } -/* td points to the last td in the list, which interrupts on completion */ -static int uhci_run_bulk(struct uhci_device *dev, struct uhci_td *first, struct uhci_td *last, unsigned long *rval, int timeout) -{ - DECLARE_WAITQUEUE(wait, current); - struct uhci_qh *qh = uhci_qh_alloc(dev); - int ret; +#ifdef _UHCI_SLAB + purb_priv = kmem_cache_alloc(urb_priv_kmem, in_interrupt ()? SLAB_ATOMIC : SLAB_KERNEL); +#else + purb_priv = kmalloc (sizeof (urb_priv_t), in_interrupt ()? GFP_ATOMIC : GFP_KERNEL); +#endif + if (!purb_priv) { + usb_dec_dev_use (purb->dev); + return -ENOMEM; + } - if (!qh) - return USB_ST_INTERNALERROR; + purb->hcpriv = purb_priv; + INIT_LIST_HEAD (&purb_priv->desc_list); + purb_priv->short_control_packet=0; + dbg("submit_urb: scheduling %p", purb); - current->state = TASK_UNINTERRUPTIBLE; - add_wait_queue(&qh->wakeup, &wait); + switch (usb_pipetype (purb->pipe)) { + case PIPE_ISOCHRONOUS: + ret = uhci_submit_iso_urb (purb); + break; + case PIPE_INTERRUPT: + ret = uhci_submit_int_urb (purb); + break; + case PIPE_CONTROL: + //dump_urb (purb); + ret = uhci_submit_control_urb (purb); + break; + case PIPE_BULK: + ret = uhci_submit_bulk_urb (purb); + break; + default: + ret = -EINVAL; + } - uhci_add_irq_list(dev->uhci, last, uhci_generic_completed, &qh->wakeup); - - uhci_insert_tds_in_qh(qh, first, last); + dbg("submit_urb: scheduled with ret: %d", ret); - /* Add it into the skeleton */ - uhci_insert_qh(&dev->uhci->skel_bulk_qh, qh); + if (ret != USB_ST_NOERROR) { + usb_dec_dev_use (purb->dev); +#ifdef _UHCI_SLAB + kmem_cache_free(urb_priv_kmem, purb_priv); +#else + kfree (purb_priv); +#endif + return ret; + } - /* wait a user specified reasonable amount of time */ - schedule_timeout(timeout); + purb->status = USB_ST_URB_PENDING; + queue_urb (s, &purb->urb_list,1); + dbg("submit_urb: exit"); - remove_wait_queue(&qh->wakeup, &wait); + return 0; +} +/*------------------------------------------------------------------- + Virtual Root Hub + -------------------------------------------------------------------*/ - /* Clean up in case it failed.. */ - uhci_remove_irq_list(last); +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; */ + 0x00, /* __u8 iProduct; */ + 0x00, /* __u8 iSerialNumber; */ + 0x01 /* __u8 bNumConfigurations; */ +}; - uhci_remove_qh(&dev->uhci->skel_bulk_qh, qh); - ret = uhci_td_result(dev, last, rval); +/* 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 */ +}; - uhci_qh_free(qh); - return ret; -} +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 *** */ +}; -/* - * Send or receive a bulk message on a pipe. - * - * Note that the "pipe" structure is set up to map - * easily to the uhci destination fields. - * - * A bulk message is only built up from - * the data phase - */ -static int uhci_bulk_msg(struct usb_device *usb_dev, unsigned int pipe, void *data, int len, unsigned long *rval, int timeout) +/*-------------------------------------------------------------------------*/ +/* prepare Interrupt pipe transaction data; HUB INTERRUPT ENDPOINT */ +static int rh_send_irq (purb_t purb) { - struct uhci_device *dev = usb_to_uhci(usb_dev); - struct uhci_td *first, *td, *prevtd, *curtd; - unsigned long destination, status; - unsigned int nextlink; - int ret, count; - int maxsze = usb_maxpacket(usb_dev, pipe, usb_pipeout(pipe)); - if (usb_endpoint_halted(usb_dev, usb_pipeendpoint(pipe), usb_pipeout(pipe)) && - usb_clear_halt(usb_dev, usb_pipeendpoint(pipe) | (pipe & USB_DIR_IN))) - return USB_ST_STALL; + int len = 1; + int i; + puhci_t uhci = purb->dev->bus->hcpriv; + unsigned int io_addr = uhci->io_addr; + __u16 data = 0; - /* The "pipe" thing contains the destination in bits 8--18. */ - destination = (pipe & PIPE_DEVEP_MASK) | usb_packetid (pipe); + 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; + } - /* 3 errors */ - status = (pipe & TD_CTRL_LS) | TD_CTRL_ACTIVE | TD_CTRL_SPD | (3 << 27); + *(__u16 *) purb->transfer_buffer = cpu_to_le16 (data); + purb->actual_length = len; + purb->status = USB_ST_NOERROR; - /* - * Build the TDs for the bulk request - */ - first = td = uhci_td_alloc(dev); - if (!td) - return USB_ST_INTERNALERROR; + 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); + purb->complete (purb); - prevtd = first; // This is fake, but at least it's not NULL - while (len > 0) { - /* Build the TD for control status */ - int pktsze = len; + } + return USB_ST_NOERROR; +} - if (pktsze > maxsze) - pktsze = maxsze; +/*-------------------------------------------------------------------------*/ +/* Virtual Root Hub INTs are polled by this timer every "intervall" ms */ +static int rh_init_int_timer (purb_t purb); - td->status = status; /* Status */ - td->info = destination | ((pktsze-1) << 21) | - (usb_gettoggle(usb_dev, usb_pipeendpoint(pipe), - usb_pipeout(pipe)) << TD_TOKEN_TOGGLE); /* pktsze bytes of data */ - td->buffer = virt_to_bus(data); - td->backptr = &prevtd->link; - td->pipetype = PIPE_BULK; +static void rh_int_timer_do (unsigned long ptr) +{ + int len; - data += maxsze; - len -= maxsze; + purb_t purb = (purb_t) ptr; + puhci_t uhci = purb->dev->bus->hcpriv; + if (uhci->rh.send) { + len = rh_send_irq (purb); if (len > 0) { - prevtd = td; - td = uhci_td_alloc(dev); - if (!td) - return USB_ST_INTERNALERROR; - - prevtd->link = virt_to_bus(td) | UHCI_PTR_DEPTH;/* Update previous TD */ + purb->actual_length = len; + if (purb->complete) + purb->complete (purb); } - - /* Alternate Data0/1 (start with Data0) */ - usb_dotoggle(usb_dev, usb_pipeendpoint(pipe), usb_pipeout(pipe)); } + rh_init_int_timer (purb); +} - td->link = UHCI_PTR_TERM; /* Terminate */ - td->status |= TD_CTRL_IOC; +/*-------------------------------------------------------------------------*/ +/* Root Hub INTs are polled by this timer */ +static int rh_init_int_timer (purb_t purb) +{ + puhci_t uhci = purb->dev->bus->hcpriv; - /* CHANGE DIRECTION HERE! SAVE IT SOMEWHERE IN THE ENDPOINT!!! */ + uhci->rh.interval = purb->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) purb; + uhci->rh.rh_int_timer.expires = jiffies + (HZ * (purb->interval < 30 ? 30 : purb->interval)) / 1000; + add_timer (&uhci->rh.rh_int_timer); - /* Start it up.. */ - ret = uhci_run_bulk(dev, first, td, rval, timeout); + return 0; +} - count = 1000; - curtd = first; +/*-------------------------------------------------------------------------*/ +#define OK(x) len = (x); break - do { - nextlink = curtd->link; - uhci_remove_td(curtd); - uhci_td_free(curtd); +#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)) - if (nextlink & UHCI_PTR_TERM) /* Tail? */ - break; +#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)) - curtd = uhci_ptr_to_virt(nextlink); - } while (--count); - if (!count) - printk(KERN_DEBUG "uhci: runaway td's!?\n"); +/*-------------------------------------------------------------------------*/ +/**** + ** Root Hub Control Pipe + *************************/ - return ret < 0; -} -static void *uhci_request_bulk(struct usb_device *usb_dev, unsigned int pipe, - usb_device_irq handler, void *data, int len, void *dev_id) +static int rh_submit_urb (purb_t purb) { - struct uhci_device *dev = usb_to_uhci(usb_dev); - struct uhci *uhci = dev->uhci; - struct uhci_td *first, *td, *prevtd; - struct uhci_qh *bulk_qh = uhci_qh_alloc(dev); - unsigned long destination, status; - int maxsze = usb_maxpacket(usb_dev, pipe, usb_pipeout(pipe)); + struct usb_device *usb_dev = purb->dev; + puhci_t uhci = usb_dev->bus->hcpriv; + unsigned int pipe = purb->pipe; + devrequest *cmd = (devrequest *) purb->setup_packet; + void *data = purb->transfer_buffer; + int leni = purb->transfer_buffer_length; + int len = 0; + int status = 0; + int stat = USB_ST_NOERROR; + int i; + unsigned int io_addr = uhci->io_addr; + __u16 cstatus; - /* The "pipe" thing contains the destination in bits 8--18. */ - destination = (pipe & PIPE_DEVEP_MASK) | usb_packetid(pipe); + __u16 bmRType_bReq; + __u16 wValue; + __u16 wIndex; + __u16 wLength; - /* Infinite errors is 0 */ - status = (pipe & TD_CTRL_LS) | TD_CTRL_ACTIVE | TD_CTRL_SPD; + if (usb_pipetype (pipe) == PIPE_INTERRUPT) { + dbg("Root-Hub submit IRQ: every %d ms", purb->interval); + uhci->rh.urb = purb; + uhci->rh.send = 1; + uhci->rh.interval = purb->interval; + rh_init_int_timer (purb); - /* Build the TDs for the bulk request */ - first = td = uhci_td_alloc(dev); - prevtd = td; - while (len > 0) { - /* Build the TD for control status */ - int pktsze = len; + return USB_ST_NOERROR; + } - if (pktsze > maxsze) - pktsze = maxsze; - td->status = status; /* Status */ - td->info = destination | ((pktsze-1) << 21) | - (usb_gettoggle(usb_dev, usb_pipeendpoint(pipe), usb_pipeout(pipe)) << TD_TOKEN_TOGGLE); /* pktsze bytes of data */ - td->buffer = virt_to_bus(data); - td->backptr = &prevtd->link; - td->qh = bulk_qh; - td->dev = dev; - td->pipetype = PIPE_BULK; + 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); - data += pktsze; - len -= pktsze; + for (i = 0; i < 8; i++) + uhci->rh.c_p_r[i] = 0; - if (len > 0) { - prevtd = td; - td = uhci_td_alloc(dev); - prevtd->link = virt_to_bus(td) | UHCI_PTR_DEPTH; + 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; - /* Alternate Data0/1 */ - usb_dotoggle(usb_dev, usb_pipeendpoint(pipe), usb_pipeout(pipe)); - } + case RH_CLEAR_FEATURE | RH_CLASS: + switch (wValue) { + case (RH_C_HUB_OVER_CURRENT): + OK (0); /* hub power over current ** */ + } + break; - first->backptr = NULL; - td->link = 1; /* Terminate */ - td->status = status | TD_CTRL_IOC; /* IOC */ + 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); + wait_ms (10); + uhci->rh.c_p_r[wIndex - 1] = 1; + CLR_RH_PORTSTAT (USBPORTSC_PR); + udelay (10); + SET_RH_PORTSTAT (USBPORTSC_PE); + 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; - uhci_add_irq_list(dev->uhci, td, handler, dev_id); + 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 */ + stat = -EPIPE; + } + break; - uhci_insert_tds_in_qh(bulk_qh, first, td); + 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); - bulk_qh->skel = &uhci->skel_bulk_qh; - uhci_insert_qh(&uhci->skel_bulk_qh, bulk_qh); + case RH_GET_CONFIGURATION: + *(__u8 *) data = 0x01; + OK (1); - /* Return last td for removal */ - return td; -} + case RH_SET_CONFIGURATION: + OK (0); + default: + stat = -EPIPE; + } -/* - * Remove a handler from a pipe. This terminates the transfer. - * We have some assumptions here: - * There is only one queue using this pipe. (the one we remove) - * Any data that is in the queue is useless for us, we throw it away. - */ -static int uhci_terminate_bulk(struct usb_device *dev, void *first) -{ - /* none found? there is nothing to remove! */ - if (!first) - return USB_ST_REMOVED; - uhci_remove_transfer(first, 1); + dbg("Root-Hub stat port1: %x port2: %x", + inw (io_addr + USBPORTSC1), inw (io_addr + USBPORTSC2)); + purb->actual_length = len; + purb->status = stat; + if (purb->complete) + purb->complete (purb); return USB_ST_NOERROR; } +/*-------------------------------------------------------------------------*/ -struct usb_operations uhci_device_operations = { - uhci_alloc_dev, - uhci_free_dev, - uhci_control_msg, - uhci_bulk_msg, - uhci_request_irq, - uhci_release_irq, - uhci_request_bulk, - uhci_terminate_bulk, - uhci_get_current_frame_number, - uhci_init_isoc, - uhci_free_isoc, - uhci_run_isoc, - uhci_kill_isoc -}; +static int rh_unlink_urb (purb_t purb) +{ + puhci_t uhci = purb->dev->bus->hcpriv; + + dbg("Root-Hub unlink IRQ"); + uhci->rh.send = 0; + del_timer (&uhci->rh.rh_int_timer); + return 0; +} +/*-------------------------------------------------------------------*/ + +#define UHCI_DEBUG /* - * This is just incredibly fragile. The timings must be just - * right, and they aren't really documented very well. + * Map status to standard result codes * - * Note the short delay between disabling reset and enabling - * the port.. + * <status> is (td->status & 0xFE0000) [a.k.a. uhci_status_bits(td->status) + * <dir_out> is True for output TDs and False for input TDs. */ -static void uhci_reset_port(unsigned int port) +static int uhci_map_status (int status, int dir_out) { - unsigned short status; - - status = inw(port); - outw(status | USBPORTSC_PR, port); /* reset port */ - wait_ms(10); - outw(status & ~USBPORTSC_PR, port); - udelay(5); - - status = inw(port); - outw(status | USBPORTSC_PE, port); /* enable port */ - wait_ms(10); - - status = inw(port); - if (!(status & USBPORTSC_PE)) { - outw(status | USBPORTSC_PE, port); /* one more try at enabling port */ - wait_ms(50); + if (!status) + return USB_ST_NOERROR; + if (status & TD_CTRL_BITSTUFF) /* Bitstuff error */ + return USB_ST_BITSTUFF; + if (status & TD_CTRL_CRCTIMEO) { /* CRC/Timeout */ + if (dir_out) + return USB_ST_NORESPONSE; + else + return USB_ST_CRC; } + if (status & TD_CTRL_NAK) /* NAK */ + return USB_ST_TIMEOUT; + if (status & TD_CTRL_BABBLE) /* Babble */ + return -EPIPE; + if (status & TD_CTRL_DBUFERR) /* Buffer error */ + return USB_ST_BUFFERUNDERRUN; + if (status & TD_CTRL_STALLED) /* Stalled */ + return -EPIPE; + if (status & TD_CTRL_ACTIVE) /* Active */ + return USB_ST_NOERROR; + return USB_ST_INTERNALERROR; } /* - * This gets called if the connect status on the root - * hub (and the root hub only) changes. + * Only the USB core should call uhci_alloc_dev and uhci_free_dev */ -static void uhci_connect_change(struct uhci *uhci, unsigned int port, unsigned int nr) +static int uhci_alloc_dev (struct usb_device *usb_dev) { - struct usb_device *usb_dev; - struct uhci_device *dev; - unsigned short status; - struct uhci_device *root_hub = usb_to_uhci(uhci->bus->root_hub); - -#ifdef UHCI_DEBUG - printk(KERN_INFO "uhci_connect_change: called for %d\n", nr); -#endif + return 0; +} - /* - * Even if the status says we're connected, - * the fact that the status bits changed may - * that we got disconnected and then reconnected. - * - * So start off by getting rid of any old devices.. - */ - usb_disconnect(&root_hub->usb->children[nr]); +static int uhci_free_dev (struct usb_device *usb_dev) +{ + return 0; +} - status = inw(port); +/* + * 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 ((puhci_t) usb_dev->bus->hcpriv); +} - /* If we have nothing connected, then clear change status and disable the port */ - status = (status & ~USBPORTSC_PE) | USBPORTSC_PEC; - if (!(status & USBPORTSC_CCS)) { - outw(status, port); - return; - } +struct usb_operations uhci_device_operations = +{ + uhci_alloc_dev, + uhci_free_dev, + uhci_get_current_frame_number, + uhci_submit_urb, + uhci_unlink_urb +}; - /* - * Ok, we got a new connection. Allocate a device to it, - * and find out what it wants to do.. - */ - usb_dev = usb_alloc_dev(root_hub->usb, root_hub->usb->bus); - if (!usb_dev) - return; - - dev = usb_dev->hcpriv; +/* + * 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. + * FIXME: Stall-condition may override 'nearly' successful CTRL-IN-transfer + * when the transfered length fits exactly in maxsze-packets. A bit + * more intelligence is needed to detect this and finish without error. + */ +static int process_transfer (puhci_t s, purb_t purb) +{ + int ret = USB_ST_NOERROR; + purb_priv_t purb_priv = purb->hcpriv; + struct list_head *qhl = purb_priv->desc_list.next; + puhci_desc_t qh = list_entry (qhl, uhci_desc_t, desc_list); + struct list_head *p = qh->vertical.next; + puhci_desc_t desc= list_entry (purb_priv->desc_list.prev, uhci_desc_t, desc_list); + puhci_desc_t last_desc = list_entry (desc->vertical.prev, uhci_desc_t, vertical); + int data_toggle = usb_gettoggle (purb->dev, usb_pipeendpoint (purb->pipe), usb_pipeout (purb->pipe)); // save initial data_toggle - usb_connect(usb_dev); - root_hub->usb->children[nr] = usb_dev; + // extracted and remapped info from TD + int maxlength; + int actual_length; + int status = USB_ST_NOERROR; - wait_ms(200); /* wait for powerup */ - uhci_reset_port(port); + dbg("process_transfer: urb contains bulk/control request"); - /* Get speed information */ - usb_dev->slow = (inw(port) & USBPORTSC_LSDA) ? 1 : 0; - /* - * Ok, all the stuff specific to the root hub has been done. - * The rest is generic for any new USB attach, regardless of - * hub type. + /* 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 (usb_new_device(usb_dev)) { - unsigned short status = inw(port); +#if 1 + if (purb_priv->short_control_packet && + ((qh->hw.qh.element == UHCI_PTR_TERM) ||(!(last_desc->hw.td.status & TD_CTRL_ACTIVE)))) + goto transfer_finished; +#endif + purb->actual_length=0; - printk(KERN_INFO "uhci: disabling port %d\n", - nr + 1); - outw(status & ~USBPORTSC_PE, port); - } -} + for (; p != &qh->vertical; p = p->next) { + desc = list_entry (p, uhci_desc_t, vertical); -/* - * This gets called when the root hub configuration - * has changed. Just go through each port, seeing if - * there is something interesting happening. - */ -static void uhci_check_configuration(struct uhci *uhci) -{ - struct uhci_device *root_hub = usb_to_uhci(uhci->bus->root_hub); - unsigned int io_addr = uhci->io_addr + USBPORTSC1; - int maxchild = root_hub->usb->maxchild; - int nr = 0; + if (desc->hw.td.status & TD_CTRL_ACTIVE) // do not process active TDs + return ret; - do { - unsigned short status = inw(io_addr); + // extract transfer parameters from TD + actual_length = (desc->hw.td.status + 1) & 0x7ff; + maxlength = (((desc->hw.td.info >> 21) & 0x7ff) + 1) & 0x7ff; + status = uhci_map_status (uhci_status_bits (desc->hw.td.status), usb_pipeout (purb->pipe)); - if (status & USBPORTSC_CSC) - uhci_connect_change(uhci, io_addr, nr); + // see if EP is stalled + if (status == -EPIPE) { + // set up stalled condition + usb_endpoint_halt (purb->dev, usb_pipeendpoint (purb->pipe), usb_pipeout (purb->pipe)); + } - nr++; io_addr += 2; - } while (nr < maxchild); -} + // if any error occured stop processing of further TDs + if (status != USB_ST_NOERROR) { + // only set ret if status returned an error + uhci_show_td (desc); + ret = status; + purb->error_count++; + break; + } + else if ((desc->hw.td.info & 0xff) != USB_PID_SETUP) + purb->actual_length += actual_length; -static int fixup_isoc_desc (struct uhci_td *td) -{ - struct usb_isoc_desc *isocdesc = td->dev_id; - struct uhci_td *prtd; - struct isoc_frame_desc *frm; - int first_comp = isocdesc->cur_completed_frame + 1; /* 0-based */ - int cur_comp = td->isoc_td_number; /* 0-based */ - int ix, fx; - int num_comp; - - if (first_comp >= isocdesc->frame_count) - first_comp = 0; - num_comp = cur_comp - first_comp + 1; - -#ifdef CONFIG_USB_DEBUG_ISOC - printk ("fixup_isoc_desc.1: td = %p, id = %p, first_comp = %d, cur_comp = %d, num_comp = %d\n", - td, isocdesc, first_comp, cur_comp, num_comp); +#if 0 + if (i++==0) + uhci_show_td (desc); // show first TD of each transfer #endif - for (ix = 0, fx = first_comp, prtd = ((struct uhci_td *)(isocdesc->td))+first_comp, frm = &isocdesc->frames [first_comp]; - ix < num_comp; ix++) { - frm->frame_length = uhci_actual_length (prtd->status); - isocdesc->total_length += frm->frame_length; + // got less data than requested + if ( (actual_length < maxlength)) { + if (purb->transfer_flags & USB_DISABLE_SPD) { + ret = USB_ST_SHORT_PACKET; // treat as real error + dbg("process_transfer: SPD!!"); + break; // exit after this TD because SP was detected + } - if ((frm->frame_status = uhci_map_status (uhci_status_bits (prtd->status), - uhci_packetout (prtd->info)))) - isocdesc->error_count++; + // short read during control-IN: re-start status stage + if ((usb_pipetype (purb->pipe) == PIPE_CONTROL)) { + if (uhci_packetid(last_desc->hw.td.info) == USB_PID_OUT) { + uhci_show_td (last_desc); + qh->hw.qh.element = virt_to_bus (last_desc); // re-trigger status stage + info("short packet during control transfer, retrigger status stage @ %p",last_desc); + purb_priv->short_control_packet=1; + return 0; + } + } + // all other cases: short read is OK + data_toggle = uhci_toggle (desc->hw.td.info); + break; + } - prtd++; - frm++; - if (++fx >= isocdesc->frame_count) { /* wrap fx, prtd, and frm */ - fx = 0; - prtd = isocdesc->td; - frm = isocdesc->frames; - } /* end wrap */ - } /* end for */ + data_toggle = uhci_toggle (desc->hw.td.info); + //dbg("process_transfer: len:%d status:%x mapped:%x toggle:%d", actual_length, desc->hw.td.status,status, data_toggle); - /* - * Update some other fields for drivers. - */ - isocdesc->prev_completed_frame = isocdesc->cur_completed_frame; - isocdesc->cur_completed_frame = cur_comp; - isocdesc->total_completed_frames += num_comp; /* 1-based */ + } + usb_settoggle (purb->dev, usb_pipeendpoint (purb->pipe), usb_pipeout (purb->pipe), !data_toggle); + transfer_finished: + + /* APC BackUPS Pro kludge */ + /* It tries to send all of the descriptor instead of */ + /* the amount we requested */ + if (desc->hw.td.status & TD_CTRL_IOC && + status & TD_CTRL_ACTIVE && + status & TD_CTRL_NAK ) + { + ret=0; + status=0; + } -#ifdef CONFIG_USB_DEBUG_ISOC - printk ("fixup_isoc_desc.2: total_comp_frames = %d, total_length = %d, error_count = %d\n", - isocdesc->total_completed_frames, isocdesc->total_length, isocdesc->error_count); -#endif /* CONFIG_USB_DEBUG_ISOC */ + unlink_qh (s, qh); + delete_qh (s, qh); - return 0; + purb->status = status; + + dbg("process_transfer: urb %p, wanted len %d, len %d status %x err %d", + purb,purb->transfer_buffer_length,purb->actual_length, purb->status, purb->error_count); + //dbg("process_transfer: exit"); + return ret; } -static int uhci_isoc_callback(struct uhci *uhci, struct uhci_td *td, int status, unsigned long rval) +static int process_interrupt (puhci_t s, purb_t purb) { - struct usb_isoc_desc *isocdesc = td->dev_id; - int ret; + int i, ret = USB_ST_URB_PENDING; + purb_priv_t purb_priv = purb->hcpriv; + struct list_head *p = purb_priv->desc_list.next; + puhci_desc_t desc = list_entry (purb_priv->desc_list.prev, uhci_desc_t, desc_list); + int data_toggle = usb_gettoggle (purb->dev, usb_pipeendpoint (purb->pipe), + usb_pipeout (purb->pipe)); // save initial data_toggle + // extracted and remapped info from TD + + int actual_length; + int status = USB_ST_NOERROR; + + //dbg("urb contains interrupt request"); + + for (i = 0; p != &purb_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; + } - /* - * Fixup the isocdesc for the driver: total_completed_frames, - * error_count, total_length, frames array. - * - * ret = callback_fn (int error_count, void *buffer, - * int len, void *isocdesc); - */ + if (!desc->hw.td.status & TD_CTRL_IOC) { + // do not process one-shot TDs, no recycling + break; + } + // extract transfer parameters from TD - fixup_isoc_desc (td); + actual_length = (desc->hw.td.status + 1) & 0x7ff; + status = uhci_map_status (uhci_status_bits (desc->hw.td.status), usb_pipeout (purb->pipe)); - ret = td->completed (isocdesc->error_count, bus_to_virt (td->buffer), - isocdesc->total_length, isocdesc); + // see if EP is stalled + if (status == -EPIPE) { + // set up stalled condition + usb_endpoint_halt (purb->dev, usb_pipeendpoint (purb->pipe), usb_pipeout (purb->pipe)); + } - /* - * Isoc. handling of return value from td->completed (callback function) - */ + // if any error occured: ignore this td, and continue + if (status != USB_ST_NOERROR) { + purb->error_count++; + goto recycle; + } + else + purb->actual_length = actual_length; - switch (ret) { - case CB_CONTINUE: /* similar to the REMOVE condition below */ - /* TBD */ - uhci_td_free (td); - break; + // FIXME: SPD? + + data_toggle = uhci_toggle (desc->hw.td.info); - case CB_REUSE: /* similar to the re-add condition below, - * but Not ACTIVE */ - /* TBD */ - /* usb_dev = td->dev->usb; */ + if (purb->complete && status != USB_ST_TIMEOUT) { + // for last td, no user completion is needed + dbg("process_interrupt: calling completion"); + purb->status = status; + purb->complete ((struct urb *) purb); + purb->status = USB_ST_URB_PENDING; + } + recycle: + // Recycle INT-TD if interval!=0, else mark TD as one-shot + if (purb->interval) { + desc->hw.td.status |= TD_CTRL_ACTIVE; + desc->hw.td.info &= ~(1 << TD_TOKEN_TOGGLE); + desc->hw.td.info |= (usb_gettoggle (purb->dev, usb_pipeendpoint (purb->pipe), + usb_pipeout (purb->pipe)) << TD_TOKEN_TOGGLE); + usb_dotoggle (purb->dev, usb_pipeendpoint (purb->pipe), usb_pipeout (purb->pipe)); + } + else { + desc->hw.td.status &= ~TD_CTRL_IOC; // inactivate TD + } + } - /* Safe since uhci_interrupt_notify holds the lock */ - list_add(&td->irq_list, &uhci->interrupt_list); + return ret; +} - td->status = (td->status & (TD_CTRL_SPD | TD_CTRL_C_ERR_MASK | - TD_CTRL_LS | TD_CTRL_IOS | TD_CTRL_IOC)) | - TD_CTRL_IOC; - /* The HC removes it, so re-add it */ - /* Insert into a QH? */ - uhci_insert_td_in_qh(td->qh, td); - break; +static int process_iso (puhci_t s, purb_t purb) +{ + int i; + int ret = USB_ST_NOERROR; + purb_priv_t purb_priv = purb->hcpriv; + struct list_head *p = purb_priv->desc_list.next; + puhci_desc_t desc = list_entry (purb_priv->desc_list.prev, uhci_desc_t, desc_list); + + dbg("urb contains iso request"); + if (desc->hw.td.status & TD_CTRL_ACTIVE) + return USB_ST_PARTIAL_ERROR; // last TD not finished + + purb->error_count = 0; + purb->actual_length = 0; + purb->status = USB_ST_NOERROR; + + for (i = 0; p != &purb_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 + dbg("TD still active (%x)- grrr. paranoia!", desc->hw.td.status); + ret = USB_ST_PARTIAL_ERROR; + purb->iso_frame_desc[i].status = ret; + unlink_td (s, desc); + goto err; + } - case CB_RESTART: /* similar to re-add, but mark ACTIVE */ - /* TBD */ - /* usb_dev = td->dev->usb; */ + unlink_td (s, desc); - list_add(&td->irq_list, &uhci->interrupt_list); + if (purb->number_of_packets <= i) { + dbg("purb->number_of_packets (%d)<=(%d)", purb->number_of_packets, i); + ret = USB_ST_URB_INVALID_ERROR; + goto err; + } - td->status = (td->status & (TD_CTRL_SPD | TD_CTRL_C_ERR_MASK | - TD_CTRL_LS | TD_CTRL_IOS | TD_CTRL_IOC)) | - TD_CTRL_ACTIVE | TD_CTRL_IOC; + if (purb->iso_frame_desc[i].offset + purb->transfer_buffer != bus_to_virt (desc->hw.td.buffer)) { + // Hm, something really weird is going on + dbg("Pointer Paranoia: %p!=%p", purb->iso_frame_desc[i].offset + purb->transfer_buffer, bus_to_virt (desc->hw.td.buffer)); + ret = USB_ST_URB_INVALID_ERROR; + purb->iso_frame_desc[i].status = ret; + goto err; + } + purb->iso_frame_desc[i].actual_length = (desc->hw.td.status + 1) & 0x7ff; + purb->iso_frame_desc[i].status = uhci_map_status (uhci_status_bits (desc->hw.td.status), usb_pipeout (purb->pipe)); + purb->actual_length += purb->iso_frame_desc[i].actual_length; - /* The HC removes it, so re-add it */ - uhci_insert_td_in_qh(td->qh, td); - break; + err: - case CB_ABORT: /* kill/abort */ - /* TBD */ - uhci_kill_isoc (isocdesc); - break; - } /* end isoc. TD switch */ + if (purb->iso_frame_desc[i].status != USB_ST_NOERROR) { + purb->error_count++; + purb->status = purb->iso_frame_desc[i].status; + } + dbg("process_iso: len:%d status:%x", + purb->iso_frame_desc[i].length, purb->iso_frame_desc[i].status); - return 0; + delete_desc (desc); + list_del (p); + } + dbg("process_iso: exit %i (%d)", i, ret); + return ret; } -static int uhci_bulk_callback(struct uhci *uhci, struct uhci_td *td, int status, unsigned long rval) -{ - if (td->completed(status, bus_to_virt(td->buffer), rval, td->dev_id)) { - struct usb_device *usb_dev = td->dev->usb; - - /* This is safe since uhci_interrupt_notify holds the lock */ - list_add(&td->irq_list, &uhci->interrupt_list); - /* Reset the status */ - td->status = (td->status & TD_CTRL_LS) | TD_CTRL_ACTIVE | TD_CTRL_IOC | TD_CTRL_SPD; +static int process_urb (puhci_t s, struct list_head *p) +{ + int ret = USB_ST_NOERROR; + purb_t purb; - /* Reset the info */ - td->info = (td->info & (PIPE_DEVEP_MASK | 0xFF | (TD_CTRL_ACTLEN_MASK << 21))) | - (usb_gettoggle(usb_dev, uhci_endpoint(td->info), uhci_packetout(td->info)) << TD_TOKEN_TOGGLE); /* pktsze bytes of data */ + spin_lock(&s->urb_list_lock); + purb=list_entry (p, urb_t, urb_list); + dbg("found queued urb: %p", purb); - usb_dotoggle(usb_dev, uhci_endpoint(td->info), uhci_packetout(td->info)); - /* The HC only removes it when it completed */ - /* successfully, so force remove and re-add it */ - uhci_remove_td(td); - uhci_insert_td_in_qh(td->qh, td); + switch (usb_pipetype (purb->pipe)) { + case PIPE_CONTROL: + case PIPE_BULK: + ret = process_transfer (s, purb); + break; + case PIPE_ISOCHRONOUS: + ret = process_iso (s, purb); + break; + case PIPE_INTERRUPT: + ret = process_interrupt (s, purb); + break; } - return 0; -} - -static int uhci_callback(struct uhci *uhci, struct uhci_td *td, int status, unsigned long rval) -{ - if (td->completed(status, bus_to_virt(td->buffer), rval, td->dev_id)) { - struct usb_device *usb_dev = td->dev->usb; - - if (td->pipetype != PIPE_INTERRUPT) - return 0; - - /* This is safe since uhci_interrupt_notify holds the lock */ - list_add(&td->irq_list, &uhci->interrupt_list); - - usb_dotoggle(usb_dev, uhci_endpoint(td->info), uhci_packetout(td->info)); - td->info &= ~(1 << TD_TOKEN_TOGGLE); /* clear data toggle */ - td->info |= usb_gettoggle(usb_dev, uhci_endpoint(td->info), - uhci_packetout(td->info)) << TD_TOKEN_TOGGLE; /* toggle between data0 and data1 */ - td->status = (td->status & 0x2F000000) | TD_CTRL_ACTIVE | TD_CTRL_IOC; - /* The HC only removes it when it completed */ - /* successfully, so force remove and re-add it */ - uhci_remove_td(td); - uhci_insert_td_in_qh(td->qh, td); - } else if (td->flags & UHCI_TD_REMOVE) { - struct usb_device *usb_dev = td->dev->usb; - - /* marked for removal */ - td->flags &= ~UHCI_TD_REMOVE; - usb_dotoggle(usb_dev, uhci_endpoint(td->info), uhci_packetout(td->info)); - uhci_remove_qh(td->qh->skel, td->qh); - uhci_qh_free(td->qh); - if (td->pipetype == PIPE_INTERRUPT) - usb_release_bandwidth(usb_dev, td->bandwidth_alloc); - uhci_td_free(td); - } + spin_unlock(&s->urb_list_lock); - return 0; -} + if (purb->status != USB_ST_URB_PENDING) { + int proceed = 0; + dbg("dequeued urb: %p", purb); + dequeue_urb (s, p, 1); -static void uhci_interrupt_notify(struct uhci *uhci) -{ - struct list_head *tmp, *head = &uhci->interrupt_list; - int status; +#ifdef _UHCI_SLAB + kmem_cache_free(urb_priv_kmem, purb->hcpriv); +#else + kfree (purb->hcpriv); +#endif - spin_lock(&irqlist_lock); - tmp = head->next; - while (tmp != head) { - struct uhci_td *td = list_entry(tmp, struct uhci_td, irq_list); - unsigned long rval; + if ((usb_pipetype (purb->pipe) != PIPE_INTERRUPT)) { + purb_t tmp = purb->next; // pointer to first urb + int is_ring = 0; + + if (purb->next) { + do { + if (tmp->status != USB_ST_URB_PENDING) { + proceed = 1; + break; + } + tmp = tmp->next; + } + while (tmp != NULL && tmp != purb->next); + if (tmp == purb->next) + is_ring = 1; + } - tmp = tmp->next; + // In case you need the current URB status for your completion handler + if (purb->complete && (!proceed || (purb->transfer_flags & USB_URB_EARLY_COMPLETE))) { + dbg("process_transfer: calling early completion"); + purb->complete ((struct urb *) purb); + if (!proceed && is_ring && (purb->status != USB_ST_URB_KILLED)) + uhci_submit_urb (purb); + } - /* We're interested if there was an error or if the chain of */ - /* TD's completed successfully */ - status = uhci_td_result(td->dev, td, &rval); - if (status == USB_ST_NOCHANGE) - continue; + if (proceed && purb->next) { + // if there are linked urbs - handle submitting of them right now. + tmp = purb->next; // pointer to first urb - /* remove from IRQ list */ - list_del(&td->irq_list); - INIT_LIST_HEAD(&td->irq_list); + do { + if ((tmp->status != USB_ST_URB_PENDING) && (tmp->status != USB_ST_URB_KILLED) && uhci_submit_urb (tmp) != USB_ST_NOERROR) + break; + tmp = tmp->next; + } + while (tmp != NULL && tmp != purb->next); // submit until we reach NULL or our own pointer or submit fails - switch (td->pipetype) { - case PIPE_ISOCHRONOUS: - uhci_isoc_callback(uhci, td, status, rval); - break; - case PIPE_BULK: - uhci_bulk_callback(uhci, td, status, rval); - break; - default: - uhci_callback(uhci, td, status, rval); + if (purb->complete && !(purb->transfer_flags & USB_URB_EARLY_COMPLETE)) { + dbg("process_transfer: calling completion"); + purb->complete ((struct urb *) purb); + } + } + usb_dec_dev_use (purb->dev); } - - /* If completed does not wants to reactivate, then */ - /* it's responsible for free'ing the TD's and QH's */ - /* or another function (such as run_control) */ } - spin_unlock(&irqlist_lock); -} -/* - * Check port status - Connect Status Change - for - * each of the attached ports (defaults to two ports, - * but at least in theory there can be more of them). - * - * Wake up the configurator if something happened, we - * can't really do much at interrupt time. - */ -static void uhci_root_hub_events(struct uhci *uhci, unsigned int io_addr) -{ - if (waitqueue_active(&uhci_configure)) { - struct uhci_device *root_hub = usb_to_uhci(uhci->bus->root_hub); - int ports = root_hub->usb->maxchild; - - io_addr += USBPORTSC1; - do { - if (inw(io_addr) & USBPORTSC_CSC) { - wake_up(&uhci_configure); - return; - } - io_addr += 2; - } while (--ports > 0); - } + return ret; } -static void uhci_interrupt(int irq, void *__uhci, struct pt_regs *regs) +static void uhci_interrupt (int irq, void *__uhci, struct pt_regs *regs) { - struct uhci *uhci = __uhci; - unsigned int io_addr = uhci->io_addr; + puhci_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; - outw(status, io_addr + USBSTS); + dbg("interrupt"); + status = inw (io_addr + USBSTS); - /* Walk the list of pending TD's to see which ones completed.. */ - uhci_interrupt_notify(uhci); + if (!status) /* shared interrupt, not mine */ + return; - /* Check if there are any events on the root hub.. */ - uhci_root_hub_events(uhci, io_addr); -} + if (status != 1) { + dbg("interrupt, status %x", status); + //uhci_show_status (s); + } + //beep(1000); + /* + * the following is very subtle and was blatantly wrong before + * 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 + * - Thomas Sailer + */ -/* - * We init one packet, and mark it just IOC and _not_ - * active. Which will result in no actual USB traffic, - * but _will_ result in an interrupt every second. - * - * Which is exactly what we want. - */ -static void uhci_init_ticktd(struct uhci *uhci) -{ - struct uhci_device *dev = usb_to_uhci(uhci->bus->root_hub); - struct uhci_td *td = uhci_td_alloc(dev); + spin_lock(&s->unlink_urb_lock); + spin_lock (&s->urb_list_lock); + p = s->urb_list.prev; + spin_unlock (&s->urb_list_lock); - if (!td) { - printk(KERN_ERR "unable to allocate ticktd\n"); - return; + while (p != &s->urb_list) { + p2 = p; + p = p->prev; + process_urb (s, p2); } - /* Don't clobber the frame */ - td->link = uhci->fl->frame[0]; - td->backptr = &uhci->fl->frame[0]; - td->status = TD_CTRL_IOC; - /* (ignored) input packet, 0 bytes, device 127 */ - td->info = (UHCI_NULL_DATA_SIZE << 21) | (0x7f << 8) | USB_PID_IN; - td->buffer = 0; - td->qh = NULL; - td->pipetype = -1; - - uhci->fl->frame[0] = virt_to_bus(td); + spin_unlock(&s->unlink_urb_lock); - uhci->ticktd = td; + outw (status, io_addr + USBSTS); +#ifdef __alpha + mb (); // ? +#endif + dbg("done"); } -static void reset_hc(struct uhci *uhci) +static void reset_hc (puhci_t s) { - unsigned int io_addr = uhci->io_addr; + unsigned int io_addr = s->io_addr; + s->apm_state = 0; /* Global reset for 50ms */ - outw(USBCMD_GRESET, io_addr + USBCMD); - wait_ms(50); - outw(0, io_addr + USBCMD); - wait_ms(10); + outw (USBCMD_GRESET, io_addr + USBCMD); + wait_ms (50); + outw (0, io_addr + USBCMD); + wait_ms (10); } -static void start_hc(struct uhci *uhci) +static void start_hc (puhci_t s) { - unsigned int io_addr = uhci->io_addr; + unsigned int io_addr = s->io_addr; int timeout = 1000; - uhci_init_ticktd(uhci); - /* * 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) { + outw (USBCMD_HCRESET, io_addr + USBCMD); + + while (inw (io_addr + USBCMD) & USBCMD_HCRESET) { if (!--timeout) { - printk(KERN_ERR "USBCMD_HCRESET timed out!\n"); + err("USBCMD_HCRESET timed out!"); break; } } /* Turn on all interrupts */ - outw(USBINTR_TIMEOUT | USBINTR_RESUME | USBINTR_IOC | USBINTR_SP, - io_addr + USBINTR); + 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(uhci->fl), io_addr + USBFLBASEADD); + 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); + outw (USBCMD_RS | USBCMD_CF | USBCMD_MAXP, io_addr + USBCMD); + s->apm_state = 1; } -/* - * Allocate a frame list, and four regular queues. - * - * The hardware doesn't really know any difference - * in the queues, but the order does matter for the - * protocols higher up. The order is: - * - * - any isochronous events handled before any - * of the queues. We don't do that here, because - * we'll create the actual TD entries on demand. - * - The first queue is the "interrupt queue". - * - The second queue is the "control queue". - * - The third queue is "bulk data". - * - * We could certainly have multiple queues of the same - * type, and maybe we should. We could have per-device - * queues, for example. We begin small. - * - * Queues are dynamically allocated for devices now, - * this code only sets up the skeleton queue - */ -static struct uhci *alloc_uhci(unsigned int io_addr, unsigned int io_size) +static void __exit uhci_cleanup_dev(puhci_t s) { - int i, port; - struct uhci *uhci; - struct usb_bus *bus; - struct uhci_device *dev; - struct usb_device *usb; + struct usb_device *root_hub = s->bus->root_hub; + if (root_hub) + usb_disconnect (&root_hub); - uhci = kmalloc(sizeof(*uhci), GFP_KERNEL); - if (!uhci) - return NULL; + usb_deregister_bus (s->bus); - memset(uhci, 0, sizeof(*uhci)); + reset_hc (s); + release_region (s->io_addr, s->io_size); + free_irq (s->irq, s); + usb_free_bus (s->bus); + cleanup_skel (s); + kfree (s); - uhci->irq = -1; - uhci->io_addr = io_addr; - uhci->io_size = io_size; - INIT_LIST_HEAD(&uhci->interrupt_list); +} - /* We need exactly one page (per UHCI specs), how convenient */ - uhci->fl = (void *)__get_free_page(GFP_KERNEL); - if (!uhci->fl) - goto au_free_uhci; +static int __init uhci_start_usb (puhci_t s) +{ /* start it up */ + /* connect the virtual root hub */ + struct usb_device *usb_dev; - bus = usb_alloc_bus(&uhci_device_operations); - if (!bus) - goto au_free_fl; + usb_dev = usb_alloc_dev (NULL, s->bus); + if (!usb_dev) + return -1; - uhci->bus = bus; - bus->hcpriv = uhci; + s->bus->root_hub = usb_dev; + usb_connect (usb_dev); - /* - * Allocate the root_hub - */ - usb = usb_alloc_dev(NULL, bus); - if (!usb) - goto au_free_bus; + if (usb_new_device (usb_dev) != 0) { + usb_free_dev (usb_dev); + return -1; + } - usb->bus = bus; + return 0; +} - dev = usb_to_uhci(usb); - dev->uhci = uhci; +static int __init alloc_uhci (int irq, unsigned int io_addr, unsigned int io_size) +{ + puhci_t s; + struct usb_bus *bus; - uhci->bus->root_hub = uhci_to_usb(dev); + s = kmalloc (sizeof (uhci_t), GFP_KERNEL); + if (!s) + return -1; - /* Initialize the root hub */ + memset (s, 0, sizeof (uhci_t)); + INIT_LIST_HEAD (&s->urb_list); + spin_lock_init (&s->urb_list_lock); + spin_lock_init (&s->qh_lock); + spin_lock_init (&s->td_lock); + spin_lock_init (&s->unlink_urb_lock); + s->irq = -1; + s->io_addr = io_addr; + s->io_size = io_size; + s->next = devs; //chain new uhci device into global list + + 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 (port = 0; port < (io_size - 0x10) / 2; port++) { + + for (s->maxports = 0; s->maxports < (io_size - 0x10) / 2; s->maxports++) { unsigned int portstatus; - portstatus = inw(io_addr + 0x10 + (port * 2)); + 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; } - printk(KERN_DEBUG "Detected %d ports\n", port); + dbg("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 (port < 2 || port > 8) { - printk(KERN_DEBUG "Port count misdetected, forcing to 2 ports\n"); - port = 2; - } - - usb->maxchild = port; - usb_init_root_hub(usb); - - /* - * 9 Interrupt queues; link int2 thru int256 to int1 first, - * then link int1 to control and control to bulk - */ - for (i = 1; i < 9; i++) { - struct uhci_qh *qh = &uhci->skelqh[i]; - - qh->link = virt_to_bus(&uhci->skel_int1_qh) | UHCI_PTR_QH; - qh->element = UHCI_PTR_TERM; + if (s->maxports < 2 || s->maxports > 8) { + dbg("Port count misdetected, forcing to 2 ports"); + s->maxports = 2; } - uhci->skel_int1_qh.link = virt_to_bus(&uhci->skel_control_qh) | UHCI_PTR_QH; - uhci->skel_int1_qh.element = UHCI_PTR_TERM; + s->rh.numports = s->maxports; - uhci->skel_control_qh.link = virt_to_bus(&uhci->skel_bulk_qh) | UHCI_PTR_QH; - uhci->skel_control_qh.element = UHCI_PTR_TERM; - - uhci->skel_bulk_qh.link = UHCI_PTR_TERM; - uhci->skel_bulk_qh.element = UHCI_PTR_TERM; - - /* - * Fill the frame list: make all entries point to - * the proper interrupt queue. - * - * This is probably silly, but it's a simple way to - * scatter the interrupt queues in a way that gives - * us a reasonable dynamic range for irq latencies. - */ - for (i = 0; i < 1024; i++) { - struct uhci_qh *irq = &uhci->skel_int2_qh; - if (i & 1) { - irq++; - if (i & 2) { - irq++; - if (i & 4) { - irq++; - if (i & 8) { - irq++; - if (i & 16) { - irq++; - if (i & 32) { - irq++; - if (i & 64) { - irq++; - } - } - } - } - } - } - } - uhci->fl->frame[i] = virt_to_bus(irq) | UHCI_PTR_QH; + if (init_skel (s)) { + usb_free_bus (bus); + kfree(s); + return -1; } - return uhci; + request_region (s->io_addr, io_size, MODNAME); + reset_hc (s); + usb_register_bus (s->bus); -/* - * error exits: - */ - -au_free_bus: - usb_free_bus(bus); -au_free_fl: - free_page((unsigned long)uhci->fl); -au_free_uhci: - kfree(uhci); - return NULL; -} + start_hc (s); -/* - * De-allocate all resources.. - */ -static void release_uhci(struct uhci *uhci) -{ - if (uhci->irq >= 0) { - free_irq(uhci->irq, uhci); - uhci->irq = -1; + 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; } - if (uhci->ticktd) { - uhci_td_free(uhci->ticktd); - uhci->ticktd = NULL; - } + s->irq = irq; - if (uhci->fl) { - free_page((unsigned long)uhci->fl); - uhci->fl = NULL; + if(uhci_start_usb (s) < 0) { + uhci_cleanup_dev(s); + return -1; } - - usb_free_bus(uhci->bus); - kfree(uhci); -} - -static int uhci_control_thread(void *__uhci) -{ - struct uhci *uhci = (struct uhci *)__uhci; - - uhci->control_running = 1; - - lock_kernel(); - - /* - * This thread doesn't need any user-level access, - * so get rid of all our resources.. - */ - exit_mm(current); - exit_files(current); - - strcpy(current->comm, "uhci-control"); - - /* - * Ok, all systems are go.. - */ - do { - siginfo_t info; - int unsigned long signr; - -#ifdef CONFIG_APM - if (apm_resume) { - apm_resume = 0; - start_hc(uhci); - continue; - } -#endif - uhci_check_configuration(uhci); - - interruptible_sleep_on(&uhci_configure); - - if (signal_pending(current)) { - /* sending SIGUSR1 makes us print out some info */ - spin_lock_irq(¤t->sigmask_lock); - signr = dequeue_signal(¤t->blocked, &info); - spin_unlock_irq(¤t->sigmask_lock); - - if (signr == SIGUSR1) { - printk(KERN_DEBUG "UHCI queue dump:\n"); - uhci_show_queues(uhci); - } else if (signr == SIGUSR2) { - uhci_debug = !uhci_debug; - printk(KERN_DEBUG "UHCI debug toggle = %x\n", - uhci_debug); - } else - break; - } - } while (uhci->control_continue); - -/* - MOD_DEC_USE_COUNT; -*/ - - uhci->control_running = 0; + + //chain new uhci device into global list + devs = s; return 0; -} - -/* - * If we've successfully found a UHCI, now is the time to increment the - * module usage count, start the control thread, and return success.. - */ -static int found_uhci(int irq, unsigned int io_addr, unsigned int io_size) -{ - int retval; - struct uhci *uhci; - - uhci = alloc_uhci(io_addr, io_size); - if (!uhci) - return -ENOMEM; - - INIT_LIST_HEAD(&uhci->uhci_list); - list_add(&uhci->uhci_list, &uhci_list); - - request_region(uhci->io_addr, io_size, "usb-uhci"); - - reset_hc(uhci); - - usb_register_bus(uhci->bus); - start_hc(uhci); - - uhci->control_continue = 1; - - retval = -EBUSY; - if (request_irq(irq, uhci_interrupt, SA_SHIRQ, "uhci", uhci) == 0) { - int pid; - - uhci->irq = irq; - pid = kernel_thread(uhci_control_thread, uhci, - CLONE_FS | CLONE_FILES | CLONE_SIGHAND); - if (pid >= 0) { - uhci->control_pid = pid; - - return(pid); - } - - retval = pid; - } - - reset_hc(uhci); - release_region(uhci->io_addr, uhci->io_size); - - release_uhci(uhci); - return retval; } -static int start_uhci(struct pci_dev *dev) +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; - - /* IO address? */ + 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)) + if (check_region (io_addr, io_size)) break; - /* disable legacy emulation */ - pci_write_config_word(dev, USBLEGSUP, USBLEGSUP_DEFAULT); + pci_write_config_word (dev, USBLEGSUP, USBLEGSUP_DEFAULT); - pci_enable_device(dev); - - return found_uhci(dev->irq, io_addr, io_size); + return alloc_uhci(dev->irq, io_addr, io_size); } return -1; } #ifdef CONFIG_APM -static int handle_apm_event(apm_event_t event) +static int handle_apm_event (apm_event_t event) { static int down = 0; - + puhci_t s = devs; + dbg("handle_apm_event(%d)", event); switch (event) { case APM_SYS_SUSPEND: case APM_USER_SUSPEND: if (down) { - printk(KERN_DEBUG "uhci: received extra suspend event\n"); + dbg("received extra suspend event"); break; } + while (s) { + reset_hc (s); + s = s->next; + } down = 1; break; case APM_NORMAL_RESUME: case APM_CRITICAL_RESUME: if (!down) { - printk(KERN_DEBUG "uhci: received bogus resume event\n"); + dbg("received bogus resume event"); break; } down = 0; - if (waitqueue_active(&uhci_configure)) { - apm_resume = 1; - wake_up(&uhci_configure); + while (s) { + start_hc (s); + s = s->next; } break; } @@ -2328,116 +2236,101 @@ static int handle_apm_event(apm_event_t event) } #endif -int uhci_init(void) +int __init uhci_init (void) { - int retval; + int retval = -ENODEV; struct pci_dev *dev = NULL; u8 type; + int i=0; - uhci_td_cachep = kmem_cache_create("uhci_td", - sizeof(struct uhci_td), 0, - SLAB_HWCACHE_ALIGN, NULL, NULL); +#ifdef _UHCI_SLAB + char *slabname=kmalloc(16, GFP_KERNEL); - if (!uhci_td_cachep) + if(!slabname) return -ENOMEM; - uhci_qh_cachep = kmem_cache_create("uhci_qh", - sizeof(struct uhci_qh), 0, - SLAB_HWCACHE_ALIGN, NULL, NULL); + strcpy(slabname, "uhci_desc"); + uhci_desc_kmem = kmem_cache_create(slabname, 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; + } + + slabname=kmalloc(16, GFP_KERNEL); - if (!uhci_qh_cachep) + if(!slabname) return -ENOMEM; - retval = -ENODEV; + strcpy(slabname, "urb_priv"); + urb_priv_kmem = kmem_cache_create(slabname, 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); + for (;;) { - dev = pci_find_class(PCI_CLASS_SERIAL_USB << 8, dev); + 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); + pci_read_config_byte (dev, PCI_CLASS_PROG, &type); if (type != 0) continue; - /* Ok set it up */ - retval = start_uhci(dev); - if (retval < 0) +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,3,8) + pci_enable_device (dev); +#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++; + + } #ifdef CONFIG_APM - apm_register_callback(&handle_apm_event); + if(i) + apm_register_callback (&handle_apm_event); #endif - return 0; - } return retval; } -void uhci_cleanup(void) +void __exit uhci_cleanup (void) { - struct list_head *next, *tmp, *head = &uhci_list; - int ret, i; - - tmp = head->next; - while (tmp != head) { - struct uhci *uhci = list_entry(tmp, struct uhci, uhci_list); - struct uhci_device *root_hub = usb_to_uhci(uhci->bus->root_hub); - - next = tmp->next; - - list_del(&uhci->uhci_list); - INIT_LIST_HEAD(&uhci->uhci_list); - - /* Check if the process is still running */ - ret = kill_proc(uhci->control_pid, 0, 1); - if (!ret) { - /* Try a maximum of 10 seconds */ - int count = 10 * 100; - - uhci->control_continue = 0; - wake_up(&uhci_configure); - - while (uhci->control_running && --count) { - current->state = TASK_INTERRUPTIBLE; - schedule_timeout(1); - } - - if (!count) - printk(KERN_ERR "uhci: giving up on killing uhci-control\n"); - } - - if (root_hub) - for (i = 0; i < root_hub->usb->maxchild; i++) - usb_disconnect(root_hub->usb->children + i); - - usb_deregister_bus(uhci->bus); - - reset_hc(uhci); - release_region(uhci->io_addr, uhci->io_size); - - release_uhci(uhci); - - tmp = next; + puhci_t s; + while ((s = devs)) { + devs = devs->next; + uhci_cleanup_dev(s); } - - if (kmem_cache_destroy(uhci_qh_cachep)) - printk(KERN_INFO "uhci: not all QH's were freed\n"); - - if (kmem_cache_destroy(uhci_td_cachep)) - printk(KERN_INFO "uhci: not all TD's were freed\n"); +#ifdef _UHCI_SLAB + kmem_cache_shrink(uhci_desc_kmem); + kmem_cache_shrink(urb_priv_kmem); +#endif } #ifdef MODULE -int init_module(void) +int init_module (void) { - return uhci_init(); + return uhci_init (); } -void cleanup_module(void) +void cleanup_module (void) { #ifdef CONFIG_APM - apm_unregister_callback(&handle_apm_event); + apm_unregister_callback (&handle_apm_event); #endif - uhci_cleanup(); + uhci_cleanup (); } -#endif //MODULE +#endif //MODULE |