summaryrefslogtreecommitdiffstats
path: root/drivers/usb/uhci.c
diff options
context:
space:
mode:
authorRalf Baechle <ralf@linux-mips.org>2000-02-05 06:47:02 +0000
committerRalf Baechle <ralf@linux-mips.org>2000-02-05 06:47:02 +0000
commit99a7e12f34b3661a0d1354eef83a0eef4df5e34c (patch)
tree3560aca9ca86792f9ab7bd87861ea143a1b3c7a3 /drivers/usb/uhci.c
parente73a04659c0b8cdee4dd40e58630e2cf63afb316 (diff)
Merge with Linux 2.3.38.
Diffstat (limited to 'drivers/usb/uhci.c')
-rw-r--r--drivers/usb/uhci.c3767
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(&current->sigmask_lock);
- signr = dequeue_signal(&current->blocked, &info);
- spin_unlock_irq(&current->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