patch-2.3.34 linux/drivers/usb/uhci.c

Next file: linux/drivers/usb/uhci.h
Previous file: linux/drivers/usb/uhci-debug.h
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.3.33/linux/drivers/usb/uhci.c linux/drivers/usb/uhci.c
@@ -1,34 +1,24 @@
-/*
- * 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
+ * $Id: uhci.c,v 1.139 1999/12/17 17:50:59 fliegl Exp $
  */
 
-/* 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
- */
+
+#ifndef EXPORT_SYMTAB
+#define EXPORT_SYMTAB
+#endif
 
 #include <linux/config.h>
 #include <linux/module.h>
@@ -41,2286 +31,2219 @@
 #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>
+//#include <asm/spinlock.h>
 
+#include "usb.h"
 #include "uhci.h"
+#include "uhci-debug.h"
+
+//#define DEBUG
+#ifdef DEBUG
+#define dbg(format, args...) printk(format, ## args)
+#else
+#define dbg(format, args...)
+#endif
+
+#define _static static
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,3,0)
+#define __init 
+#define __exit
+#endif
+
+#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
 
 #ifdef CONFIG_APM
 #include <linux/apm_bios.h>
-static int handle_apm_event(apm_event_t event);
-static int apm_resume = 0;
+static int handle_apm_event (apm_event_t event);
 #endif
 
-static int uhci_debug = 1;
+/* 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.
+*/
 
-#define compile_assert(x) do { switch (0) { case 1: case !(x): } } while (0)
+// #define _UHCI_SLAB 
+#ifdef _UHCI_SLAB
+static kmem_cache_t *uhci_desc_kmem;
+static kmem_cache_t *urb_priv_kmem;
+#endif
 
-static DECLARE_WAIT_QUEUE_HEAD(uhci_configure);
+static int rh_submit_urb (purb_t purb);
+static int rh_unlink_urb (purb_t purb);
+static puhci_t devs = NULL;
 
-static kmem_cache_t *uhci_td_cachep;
-static kmem_cache_t *uhci_qh_cachep;
+/*-------------------------------------------------------------------*/
+_static void queue_urb (puhci_t s, struct list_head *p, int do_lock)
+{
+	unsigned long flags=0;
 
-static LIST_HEAD(uhci_list);
+	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);
+}
 
-#define UHCI_DEBUG
+/*-------------------------------------------------------------------*/
+_static void dequeue_urb (puhci_t s, struct list_head *p, int do_lock)
+{
+	unsigned long flags=0;
+	
+	if (do_lock) 
+		spin_lock_irqsave (&s->urb_list_lock, flags);
 
-/*
- * function prototypes
- */
+	list_del (p);
 
-static int uhci_get_current_frame_number(struct usb_device *usb_dev);
+	if (do_lock) 
+		spin_unlock_irqrestore (&s->urb_list_lock, flags);
+}
 
-static int uhci_init_isoc(struct usb_device *usb_dev,
-				unsigned int pipe,
-				int frame_count,
-				void *context,
-				struct usb_isoc_desc **isocdesc);
+/*-------------------------------------------------------------------*/
+_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
 
-static void uhci_free_isoc(struct usb_isoc_desc *isocdesc);
+	(*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);
 
-static int uhci_run_isoc(struct usb_isoc_desc *isocdesc,
-				struct usb_isoc_desc *pr_isocdesc);
+	list_add_tail (&new->vertical, &qh->vertical);
 
-static int uhci_kill_isoc(struct usb_isoc_desc *isocdesc);
+	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 */
+	}
+	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);
+	}
+	
+	spin_unlock_irqrestore (&s->td_lock, xxx);
+	
+	return 0;
+}
+/*-------------------------------------------------------------------*/
+/* insert new_td after td (horizontal) */
+_static int insert_td_horizontal (puhci_t s, puhci_desc_t td, puhci_desc_t new, int flags)
+{
+	uhci_desc_t *next;
+	unsigned long xxx;
+	
+	spin_lock_irqsave (&s->td_lock, xxx);
 
-/*
- * 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)
+	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 int unlink_td (puhci_t s, puhci_desc_t element)
 {
-	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;
+	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);
 	}
-	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;
-
-	return USB_ST_INTERNALERROR;
+	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 int delete_desc (puhci_desc_t element)
+{
+#ifdef _UHCI_SLAB
+	kmem_cache_free(uhci_desc_kmem, element);
+#else
+	kfree (element);
+#endif
+	return 0;
+}
+/*-------------------------------------------------------------------*/
+// Allocates qh element
+_static int alloc_qh (puhci_desc_t * new)
+{
+#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 (KERN_DEBUG MODSTR "Allocated qh @ %p\n", *new);
+	
+	return 0;
 }
+/*-------------------------------------------------------------------*/
+// inserts new qh before/after the qh at pos
+// flags: 0: insert before pos, 1: insert after pos (for low speed transfers)
+_static int insert_qh (puhci_t s, puhci_desc_t pos, puhci_desc_t new, int flags)
+{
+	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);
+	}
 
-/*
- * Return the result of a TD..
- */
-static int uhci_td_result(struct uhci_device *dev, struct uhci_td *td, unsigned long *rval)
+	wmb ();
+	
+	spin_unlock_irqrestore (&s->qh_lock, xxx);
+	
+	return 0;
+}
+/*-------------------------------------------------------------------*/
+_static int unlink_qh (puhci_t s, puhci_desc_t element)
 {
-	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;
+	puhci_desc_t next, prev;
+	unsigned long xxx;
 
-	if (!tmp)
-		return USB_ST_INTERNALERROR;
+	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 delete_qh (puhci_t s, puhci_desc_t qh)
+{
+	puhci_desc_t td;
+	struct list_head *p;
 
-	if (rval)
-		 *rval = 0;
+	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;
+}
+/*-------------------------------------------------------------------*/
+void clean_td_chain (puhci_desc_t td)
+{
+	struct list_head *p;
+	puhci_desc_t td1;
 
-	/* Locate the first failing td, if any */
-	do {
-		status = uhci_status_bits(tmp->status);
+	if (!td)
+		return;
+	
+	while ((p = td->horizontal.next) != &td->horizontal) {
+		td1 = list_entry (p, uhci_desc_t, horizontal);
+		delete_desc (td1);
+	}
+	
+	delete_desc (td);
+}
+/*-------------------------------------------------------------------*/
+// Removes ALL qhs in chain (paranoia!)
+_static void cleanup_skel (puhci_t s)
+{
+	unsigned int n;
+	puhci_desc_t td;
 
-		if (status)
-			break;
+	printk (KERN_DEBUG MODSTR "Cleanup_skel\n");
+	
+	for (n = 0; n < 8; n++) {
+		td = s->int_chain[n];
+		clean_td_chain (td);
+	}
 
-		/* 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 */
-			}
+	if (s->iso_td) {
+		for (n = 0; n < 1024; n++) {
+			td = s->iso_td[n];
+			clean_td_chain (td);
 		}
+		kfree (s->iso_td);
+	}
 
-		if ((tmp->link & UHCI_PTR_TERM) || (tmp->link & UHCI_PTR_QH))
-			break;
+	if (s->framelist)
+		free_page ((unsigned long) s->framelist);
 
-		tmp = uhci_ptr_to_virt(tmp->link);
-	} while (--count);
+	if (s->control_chain) {
+		// completed init_skel?
+		struct list_head *p;
+		puhci_desc_t qh, qh1;
 
-	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);
+		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);
+	}
+}
+/*-------------------------------------------------------------------*/
+// 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)
+{
+	int n, ret;
+	puhci_desc_t qh, td;
+	
+	dbg (KERN_DEBUG MODSTR "init_skel\n");
+	
+	s->framelist = (__u32 *) get_free_page (GFP_KERNEL);
+
+	if (!s->framelist)
+		return -ENOMEM;
+
+	memset (s->framelist, 0, 4096);
+
+	dbg (KERN_DEBUG MODSTR "allocating iso desc pointer list\n");
+	s->iso_td = (puhci_desc_t *) kmalloc (1024 * sizeof (puhci_desc_t), GFP_KERNEL);
+	
+	if (!s->iso_td)
+		goto init_skel_cleanup;
 
-	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;
+	s->control_chain = NULL;
+	s->bulk_chain = NULL;
+	s->chain_end = NULL;
+
+	dbg (KERN_DEBUG MODSTR "allocating iso descs\n");
+	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));
 	}
 
-	if ((status == TD_CTRL_ACTIVE) && (!rval))
-		return USB_ST_DATAUNDERRUN;
+	dbg (KERN_DEBUG MODSTR "allocating qh: chain_end\n");
+	ret = alloc_qh (&qh);
+	
+	if (ret)
+		goto init_skel_cleanup;
+	
+	s->chain_end = qh;
 
-	return uhci_map_status(status, uhci_packetout(tmp->info));
-}
+	dbg (KERN_DEBUG MODSTR "allocating qh: bulk_chain\n");
+	ret = alloc_qh (&qh);
+	
+	if (ret)
+		goto init_skel_cleanup;
+	
+	insert_qh (s, s->chain_end, qh, 0);
+	s->bulk_chain = qh;
+	dbg (KERN_DEBUG MODSTR "allocating qh: control_chain\n");
+	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;
 
-/*
- * 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)
-{
-	unsigned int link = qh->element;
-	unsigned int new = virt_to_bus(first) | UHCI_PTR_DEPTH;
+	dbg (KERN_DEBUG MODSTR "Allocating skeleton INT-TDs\n");
+	
+	for (n = 0; n < 8; n++) {
+		puhci_desc_t td;
 
-	for (;;) {
-		unsigned char success;
+		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]);
+		}
+	}
 
-		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;
+	dbg (KERN_DEBUG MODSTR "Linking skeleton INT-TDs\n");
+	
+	for (n = 0; n < 1024; n++) {
+		// link all iso-tds to the interrupt chains
+		int m, o;
+		//dbg("framelist[%i]=%x\n",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]);
 			}
-			break;
 		}
 	}
 
-	qh->first = first;
-	first->qh = qh;
-	last->qh = qh;
-}
+	//uhci_show_queue(s->control_chain);   
+	dbg (KERN_DEBUG MODSTR "init_skel exit\n");
+	return 0;		// OK
 
-static inline void uhci_insert_td_in_qh(struct uhci_qh *qh, struct uhci_td *td)
-{
-	uhci_insert_tds_in_qh(qh, td, td);
+      init_skel_cleanup:
+	cleanup_skel (s);
+	return -ENOMEM;
 }
 
-static void uhci_insert_qh(struct uhci_qh *qh, struct uhci_qh *newqh)
+/*-------------------------------------------------------------------*/
+_static void fill_td (puhci_desc_t td, int status, int info, __u32 buffer)
 {
-	newqh->link = qh->link;
-	qh->link = virt_to_bus(newqh) | UHCI_PTR_QH;
+	td->hw.td.status = status;
+	td->hw.td.info = info;
+	td->hw.td.buffer = buffer;
 }
 
-static void uhci_remove_qh(struct uhci_qh *qh, struct uhci_qh *remqh)
+/*-------------------------------------------------------------------*/
+//                         LOW LEVEL STUFF
+//          assembles QHs und TDs for control, bulk and iso
+/*-------------------------------------------------------------------*/
+_static int uhci_submit_control_urb (purb_t purb)
 {
-	struct uhci_qh *lqh = qh;
+	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;
 
-	while (uhci_ptr_to_virt(lqh->link) != remqh) {
-		if (lqh->link & UHCI_PTR_TERM)
-			break;
+	dbg (KERN_DEBUG MODSTR "uhci_submit_control start\n");
+	alloc_qh (&qh);		// alloc qh for this request
 
-		lqh = (struct uhci_qh *)uhci_ptr_to_virt(lqh->link);
-	}
+	if (!qh)
+		return -ENOMEM;
 
-	if (lqh->link & UHCI_PTR_TERM) {
-		printk(KERN_DEBUG "couldn't find qh in chain!\n");
-		return;
-	}
+	alloc_td (&td, UHCI_PTR_DEPTH);		// get td for setup stage
 
-	lqh->link = remqh->link;
-}
+	if (!td) {
+		delete_qh (s, qh);
+		return -ENOMEM;
+	}
 
-/*
- * 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)
-{
-	unsigned int *backptr = td->backptr;
-	unsigned int link = td->link;
-	unsigned int me;
+	/* The "pipe" thing contains the destination in bits 8--18 */
+	destination = (purb->pipe & PIPE_DEVEP_MASK) | USB_PID_SETUP;
 
-	if (!backptr)
-		return;
+	/* 3 errors */
+	status = (purb->pipe & TD_CTRL_LS) | TD_CTRL_ACTIVE |
+		(purb->transfer_flags & USB_DISABLE_SPD ? 0 : TD_CTRL_SPD) | (3 << 27);
 
-	td->backptr = NULL;
+	/*  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));
 
-	/*
-	 * 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;
-	}
+	/* If direction is "send", change the frame from SETUP (0x2D)
+	   to OUT (0xE1). Else change it from SETUP to IN (0x69). */
 
-	/*
-	 * 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");
+	destination ^= (USB_PID_SETUP ^ USB_PID_IN);	/* SETUP -> IN */
+	
+	if (usb_pipeout (purb->pipe))
+		destination ^= (USB_PID_IN ^ USB_PID_OUT);	/* IN -> OUT */
 
-	/* Reset it just in case */
-	td->link = UHCI_PTR_TERM;
-}
+	insert_td (s, qh, td, 0);	// queue 'setup stage'-td in qh
+#if 0
+	printk ("SETUP to pipe %x: %x %x %x %x %x %x %x %x\n", 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;
 
-/*
- * Only the USB core should call uhci_alloc_dev and uhci_free_dev
- */
-static int uhci_alloc_dev(struct usb_device *usb_dev)
-{
-	struct uhci_device *dev;
+		alloc_td (&td, UHCI_PTR_DEPTH);
+		if (!td) {
+			delete_qh (s, qh);
+			return -ENOMEM;
+		}
 
-	/* Allocate the UHCI device private data */
-	dev = kmalloc(sizeof(*dev), GFP_KERNEL);
-	if (!dev)
-		return -1;
+		if (pktsze > maxsze)
+			pktsze = maxsze;
 
-	/* Initialize "dev" */
-	memset(dev, 0, sizeof(*dev));
+		destination ^= 1 << TD_TOKEN_TOGGLE;	// toggle DATA0/1
 
-	usb_dev->hcpriv = dev;
-	dev->usb = usb_dev;
-	atomic_set(&dev->refcnt, 1);
+		fill_td (td, status, destination | ((pktsze - 1) << 21),
+			 virt_to_bus (data));	// Status, pktsze bytes of data
 
-	if (usb_dev->parent)
-		dev->uhci = usb_to_uhci(usb_dev->parent)->uhci;
+		insert_td (s, qh, td, UHCI_PTR_DEPTH);	// queue 'data stage'-td in qh
 
-	return 0;
-}
+		data += pktsze;
+		len -= pktsze;
+	}
 
-static int uhci_free_dev(struct usb_device *usb_dev)
-{
-	struct uhci_device *dev = usb_to_uhci(usb_dev);
+	/*  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;
 
-	if (atomic_dec_and_test(&dev->refcnt))
-		kfree(dev);
+	destination |= 1 << TD_TOKEN_TOGGLE;	/* End in Data1 */
 
-	return 0;
-}
+	alloc_td (&td, UHCI_PTR_DEPTH);
+	
+	if (!td) {
+		delete_qh (s, qh);
+		return -ENOMEM;
+	}
 
-static void uhci_inc_dev_use(struct uhci_device *dev)
-{
-	atomic_inc(&dev->refcnt);
-}
+	/* 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);
 
-static void uhci_dec_dev_use(struct uhci_device *dev)
-{
-	uhci_free_dev(dev->usb);
-}
+	insert_td (s, qh, td, UHCI_PTR_DEPTH);	// queue status td
 
-static struct uhci_td *uhci_td_alloc(struct uhci_device *dev)
-{
-	struct uhci_td *td;
 
-	td = kmem_cache_alloc(uhci_td_cachep, SLAB_KERNEL);
-	if (!td)
-		return NULL;
+	list_add (&qh->desc_list, &purb_priv->desc_list);
 
-#ifdef UHCI_DEBUG
-	if ((__u32)td & UHCI_PTR_BITS)
-		printk("qh not 16 byte aligned!\n");
-#endif
+	/* 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);
 
-	td->link = UHCI_PTR_TERM;
-	td->buffer = 0;
+	dbg (KERN_DEBUG MODSTR "uhci_submit_control end\n");
+	return 0;
+}
+/*-------------------------------------------------------------------*/
+_static int uhci_submit_bulk_urb (purb_t purb)
+{
+	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;
+
+	/* 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;
+
+	if (!maxsze)
+		return -EMSGSIZE;
+	/* FIXME: should tell the client that the endpoint is invalid, i.e. not in the descriptor */
 
-	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);
+	alloc_qh (&qh);		// get qh for this request
 
-	uhci_inc_dev_use(dev);
+	if (!qh)
+		return -ENOMEM;
 
-	return td;
-}
+	/* The "pipe" thing contains the destination in bits 8--18. */
+	destination = (pipe & PIPE_DEVEP_MASK) | usb_packetid (pipe);
 
-static void uhci_td_free(struct uhci_td *td)
-{
-	if (atomic_dec_and_test(&td->refcnt)) {
-		kmem_cache_free(uhci_td_cachep, td);
+	/* 3 errors */
+	status = (pipe & TD_CTRL_LS) | TD_CTRL_ACTIVE |
+		((purb->transfer_flags & USB_DISABLE_SPD) ? 0 : TD_CTRL_SPD) | (3 << 27);
 
-		if (td->dev)
-			uhci_dec_dev_use(td->dev);
-	}
-}
+	/* Build the TDs for the bulk request */
+	len = purb->transfer_buffer_length;
+	data = purb->transfer_buffer;
+	dbg (KERN_DEBUG MODSTR "uhci_submit_bulk_urb: len %d\n", len);
+	
+	while (len > 0) {
+		int pktsze = len;
 
-static struct uhci_qh *uhci_qh_alloc(struct uhci_device *dev)
-{
-	struct uhci_qh *qh;
+		alloc_td (&td, UHCI_PTR_DEPTH);
 
-	qh = kmem_cache_alloc(uhci_qh_cachep, SLAB_KERNEL);
-	if (!qh)
-		return NULL;
+		if (!td) {
+			delete_qh (s, qh);
+			return -ENOMEM;
+		}
 
-#ifdef UHCI_DEBUG
-	if ((__u32)qh & UHCI_PTR_BITS)
-		printk("qh not 16 byte aligned!\n");
-#endif
+		if (pktsze > maxsze)
+			pktsze = maxsze;
 
-	qh->element = UHCI_PTR_TERM;
-	qh->link = UHCI_PTR_TERM;
+		// pktsze bytes of data 
+		info = destination | ((pktsze - 1) << 21) |
+			(usb_gettoggle (purb->dev, usb_pipeendpoint (pipe), usb_pipeout (pipe)) << TD_TOKEN_TOGGLE);
 
-	qh->dev = dev;
-	qh->skel = NULL;
-	qh->first = NULL;
-	init_waitqueue_head(&qh->wakeup);
-	atomic_set(&qh->refcnt, 1);
+		fill_td (td, status, info, virt_to_bus (data));
 
-	uhci_inc_dev_use(dev);
+		data += pktsze;
+		len -= pktsze;
 
-	return qh;
-}
+		if (!len)
+			td->hw.td.status |= TD_CTRL_IOC;	// last one generates INT
+		//dbg("insert td %p, len %i\n",td,pktsze);
 
-static void uhci_qh_free(struct uhci_qh *qh)
-{
-	if (atomic_dec_and_test(&qh->refcnt)) {
-		kmem_cache_free(uhci_qh_cachep, qh);
+		insert_td (s, qh, td, UHCI_PTR_DEPTH);
 
-		if (qh->dev)
-			uhci_dec_dev_use(qh->dev);
+		/* Alternate Data0/1 (start with Data0) */
+		usb_dotoggle (purb->dev, usb_pipeendpoint (pipe), usb_pipeout (pipe));
 	}
-}
-
-/*
- * 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;
+	list_add (&qh->desc_list, &purb_priv->desc_list);
 
-	td->completed = completed;
-	td->dev_id = dev_id;
+	insert_qh (s, s->chain_end, qh, 0);	// insert before end marker
+	//uhci_show_queue(s->bulk_chain);
 
-	spin_lock_irqsave(&irqlist_lock, flags);
-	list_add(&td->irq_list, &uhci->interrupt_list);
-	spin_unlock_irqrestore(&irqlist_lock, flags);
+	dbg (KERN_DEBUG MODSTR "uhci_submit_bulk_urb: exit\n");
+	return 0;
 }
+/*-------------------------------------------------------------------*/
+// 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)
+{
+	puhci_t s;
+	puhci_desc_t qh;
+	puhci_desc_t td;
+	purb_priv_t purb_priv;
+	unsigned long flags=0;
+	struct list_head *p;
 
-static void uhci_remove_irq_list(struct uhci_td *td)
-{
-	unsigned long flags;
+	if (!purb)		// you never know... 
 
-	spin_lock_irqsave(&irqlist_lock, flags);
-	list_del(&td->irq_list);
-	spin_unlock_irqrestore(&irqlist_lock, flags);
-}
+		return -1;
 
-/*
- * 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
- */
-
-static spinlock_t framelist_lock = SPIN_LOCK_UNLOCKED;
-
-static void uhci_add_frame_list(struct uhci *uhci, struct uhci_td *td, unsigned framenum)
-{
-	unsigned long flags;
-	struct uhci_td *nexttd;
+	s = (puhci_t) purb->dev->bus->hcpriv;	// get pointer to uhci struct
 
-	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);
-}
-
-static void uhci_remove_frame_list(struct uhci *uhci, struct uhci_td *td)
-{
-	unsigned long flags;
-	struct uhci_td *nexttd;
+	if (usb_pipedevice (purb->pipe) == s->rh.devnum)
+		return rh_unlink_urb (purb);
 
-	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;
+	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
 	}
-	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);
-			}
+	
+	//dbg("unlink_urb called %p\n",purb);
+	if (purb->status == USB_ST_URB_PENDING) {
+		// URB probably still in work
+		purb_priv = purb->hcpriv;
+		dequeue_urb (s, &purb->urb_list,1);
+		if(!in_interrupt()) {
+			spin_unlock_irqrestore (&s->unlink_urb_lock, flags);	// allow interrupts from here
 		}
-	}
-}
 
+		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);
+			}
+			// 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);
+			}
+			break;
 
-/*
- * 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;
+		case PIPE_BULK:
+		case PIPE_CONTROL:
+			qh = list_entry (purb_priv->desc_list.next, uhci_desc_t, desc_list);
 
-	if (td->qh && td->qh->first)
-		curtd = td->qh->first;
-	else
-		curtd = td;
+			unlink_qh (s, qh);	// remove this qh from qh-list
+			// wait at least 1 Frame
 
-	/* Remove the QH from the skeleton and then free it */
-	uhci_remove_qh(td->qh->skel, td->qh);
-	uhci_qh_free(td->qh);
-
-	do {
-		nextlink = curtd->link;
-
-		/* 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);
-
-		/* Remove the TD and then free it */
-		uhci_remove_td(curtd);
-		uhci_td_free(curtd);
+			if (in_interrupt ())
+				udelay (1000);
+			else
+				schedule_timeout (1 + 1 * HZ / 1000);
+			delete_qh (s, qh);		// remove it physically
 
-		if (nextlink & UHCI_PTR_TERM)	/* Tail? */
-			break;
+		}
+		purb->status = USB_ST_URB_KILLED;	// mark urb as killed
 
-		curtd = (struct uhci_td *)uhci_ptr_to_virt(nextlink & ~UHCI_PTR_BITS);
-	} while (count--);
+#ifdef _UHCI_SLAB
+		kmem_cache_free(urb_priv_kmem, purb->hcpriv);
+#else
+		kfree (purb->hcpriv);
+#endif
+		if (purb->complete) {
+			dbg (KERN_DEBUG MODSTR "unlink_urb: calling completion\n");
+			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
+	}
 
-	if (!count)
-		printk(KERN_ERR "runaway td's!?\n");
+	return 0;
 }
+/*-------------------------------------------------------------------*/
+// 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)
+{
+	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);
 
-/*
- * 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;
-
-	if (!td || !qh) {
-		if (td)
-			uhci_td_free(td);
-		if (qh)
-			uhci_qh_free(qh);
-		return USB_ST_INTERNALERROR;
+	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
 
-	/* 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);
-
-	td->buffer = virt_to_bus(dev->data);
-	td->qh = qh;
-	td->dev = dev;
-	td->pipetype = PIPE_INTERRUPT;
-	td->bandwidth_alloc = bustime;
-
-	/* if period 0, set _REMOVE flag */
-	if (!period)
-		td->flags |= UHCI_TD_REMOVE;
-
-	qh->skel = &dev->uhci->skelqh[__interval_to_skel(period)];
-
-	uhci_add_irq_list(dev->uhci, td, handler, dev_id);
-
-	uhci_insert_td_in_qh(qh, td);
-
-	/* Add it into the skeleton */
-	uhci_insert_qh(qh->skel, qh);
-
-	*handle = (void *)td;
-
-	return 0;
 }
+/*-------------------------------------------------------------------*/
+// adjust start_frame according to scheduling constraints (ASAP etc)
 
-/*
- * 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 void jnx_show_desc (puhci_desc_t d)
 {
-	struct uhci_td *td;
-	struct uhci_qh *qh;
-
-	td = (struct uhci_td *)handle;
-	if (!td)
-		return USB_ST_INTERNALERROR;
-
-	qh = td->qh;
-
-	/* Remove it from the internal irq_list */
-	uhci_remove_irq_list(td);
-
-	/* Remove the interrupt TD and QH */
-	uhci_remove_td(td);
-	uhci_remove_qh(qh->skel, qh);
-
-	if (td->completed != NULL)
-		td->completed(USB_ST_REMOVED, NULL, 0, td->dev_id);
+	switch (d->type) {
+	case TD_TYPE:
+		printk (KERN_DEBUG MODSTR "td @ 0x%08lx: link 0x%08x status 0x%08x info 0x%08x buffer 0x%08x\n",
+			(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;
 
-	/* Free the TD and QH */
-	uhci_td_free(td);
-	uhci_qh_free(qh);
+	case QH_TYPE:
+		printk (KERN_DEBUG MODSTR "qh @ 0x%08lx: head 0x%08x element 0x%08x\n",
+			(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;
 
-	return USB_ST_NOERROR;
-} /* uhci_release_irq() */
+	default:
+		printk (KERN_DEBUG MODSTR "desc @ 0x%08lx: invalid type %u\n",
+			(unsigned long) d, d->type);
+	}
+}
 
-/*
- * 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)
+/*-------------------------------------------------------------------*/
+_static int iso_find_start (purb_t purb)
 {
-       return inw (usb_to_uhci(usb_dev)->uhci->io_addr + USBFRNUM);
-}
+	puhci_t s = (puhci_t) purb->dev->bus->hcpriv;
+	unsigned int now;
+	unsigned int start_limit = 0, stop_limit = 0, queued_size;
+	int limits;
 
+	now = UHCI_GET_CURRENT_FRAME (s) & 1023;
 
-/*
- * 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 ((unsigned) purb->number_of_packets > 900)
+		return -EFBIG;
+	
+	limits = find_iso_limits (purb, &start_limit, &stop_limit);
+	queued_size = (stop_limit - start_limit) & 1023;
 
-	*isocdesc = NULL;
+	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
 
-	/* 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);
+			if (((now - purb->start_frame) & 1023) <= (unsigned) purb->number_of_packets) {
+				printk (KERN_DEBUG MODSTR "iso_find_start: warning, ASAP gap, should not happen\n");
+				printk (KERN_DEBUG MODSTR "iso_find_start: now %u start_frame %u number_of_packets %u pipe 0x%08x\n",
+					now, purb->start_frame, purb->number_of_packets, purb->pipe);
+				{
+					puhci_t s = (puhci_t) purb->dev->bus->hcpriv;
+					struct list_head *p = s->urb_list.next;
+					purb_t u;
+					int a = -1, b = -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);
+						if (purb->dev != u->dev)
+							continue;
+						printk (KERN_DEBUG MODSTR "urb: pipe 0x%08x status %d start_frame %u number_of_packets %u\n",
+							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
-		return -EINVAL;
-	}
+				}
+				purb->start_frame = (now + 5) & 1023;	// 5ms setup should be enough //FIXME!
+				//return -EAGAIN; //FIXME
 
-	if (!usb_pipeisoc (pipe)) {
-#ifdef CONFIG_USB_DEBUG_ISOC
-		printk (KERN_DEBUG "uhci_init_isoc: NOT an Isoc. pipe\n");
-#endif
-		return -EINVAL;
+			}
+		}
 	}
-
-	id = kmalloc (sizeof (*id) +
-		(sizeof (struct isoc_frame_desc) * frame_count), GFP_KERNEL);
-	if (!id)
-		return -ENOMEM;
-
-	memset (id, 0, sizeof (*id) +
-		(sizeof (struct isoc_frame_desc) * frame_count));
-
-	id->td = kmalloc (sizeof (struct uhci_td) * frame_count, GFP_KERNEL);
-	if (!id->td) {
-		kfree (id);
-		return -ENOMEM;
+	else {
+		purb->start_frame &= 1023;
+		if (((now - purb->start_frame) & 1023) < (unsigned) purb->number_of_packets) {
+			printk (KERN_DEBUG MODSTR "iso_find_start: now between start_frame and end\n");
+			return -EAGAIN;
+		}
 	}
 
-	memset (id->td, 0, sizeof (struct uhci_td) * frame_count);
-
-	for (i = 0; i < frame_count; i++)
-		INIT_LIST_HEAD(&((struct uhci_td *)(id->td))[i].irq_list);
-
-	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;
+	/* 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) {
+		printk (KERN_DEBUG MODSTR "iso_find_start: start_frame %u number_of_packets %u start_limit %u stop_limit %u\n",
+			purb->start_frame, purb->number_of_packets, start_limit, stop_limit);
+		return -EAGAIN;
+	}
 
-	*isocdesc = id;
 	return 0;
-} /* end uhci_init_isoc */
+}
+/*-------------------------------------------------------------------*/
+// submits USB interrupt (ie. polling ;-) 
+// ASAP-flag set implicitely
+// if period==0, the the transfer is only done once (usb_scsi need this...)
+
+_static int uhci_submit_int_urb (purb_t purb)
+{
+	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;
 
-/*
- * 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)
-{
-	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;
-	}
+	//printk("SUBMIT INT\n");
 
-	/* 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
+	if (purb->interval < 0 || purb->interval >= 256)
 		return -EINVAL;
-	}
 
-	/* 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;
+	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(KERN_INFO "Rounded interval to %i, chain  %i\n", purb->interval, nint);
 
-#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 */
+	now = UHCI_GET_CURRENT_FRAME (s) & 1023;
+	purb->start_frame = now;	// remember start frame, just in case...
 
-	/*
-	 * 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;
-	}
+	purb->number_of_packets = 1;
 
-	/*
-	 * 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);
+	// INT allows only one packet
+	if (purb->transfer_buffer_length > usb_maxpacket (purb->dev, pipe, usb_pipeout (pipe)))
+		return -EINVAL;
 
-	/* 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;
-			}
-		} /* 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;
-			}
-		} /* end START_ABSOLUTE */
-	} /* end not pr_isocdesc */
+	ret = alloc_td (&td, UHCI_PTR_DEPTH);
 
-	/*
-	 * 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;
+	if (ret)
+		return -ENOMEM;
 
-	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
-
-		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);
-		}
+	status = (pipe & TD_CTRL_LS) | TD_CTRL_ACTIVE | TD_CTRL_IOC |
+		(purb->transfer_flags & USB_DISABLE_SPD ? 0 : TD_CTRL_SPD) | (1 << 27);
 
-		bufptr += fd->frame_length;  /* or isocdesc->frame_size; */
+	destination = (purb->pipe & PIPE_DEVEP_MASK) | usb_packetid (purb->pipe) |
+		(((purb->transfer_buffer_length - 1) & 0x7ff) << 21);
 
-		/*
-		 * Insert the TD in the frame list.
-		 */
-		uhci_add_frame_list(uhci, td, cur_frame);
 
-		cur_frame = USB_WRAP_FRAMENR(cur_frame+1);
-	} /* end for ix */
+	info = destination | (usb_gettoggle (purb->dev, usb_pipeendpoint (pipe), usb_pipeout (pipe)) << TD_TOKEN_TOGGLE);
 
-	/*
-	 * 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. ??? */
-	}
+	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
 
-	return 0;
-} /* end uhci_run_isoc */
+	usb_dotoggle (purb->dev, usb_pipeendpoint (pipe), usb_pipeout (pipe));
 
-/*
- * 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)
-{
-	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);
+#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
-		return -EINVAL;
-	}
-
-	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 */
-
-static void uhci_free_isoc(struct usb_isoc_desc *isocdesc)
-{
-	int i;
-
-	/* If still Active, kill it. */
-	if (isocdesc->start_frame >= 0)
-		uhci_kill_isoc(isocdesc);
-
-	/* 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);
-
-	/* Free the associate memory. */
-	if (isocdesc->td)
-		kfree(isocdesc->td);
-
-	kfree(isocdesc);
-} /* end uhci_free_isoc */
-
-/*
- * 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!
- */
-
-static int uhci_generic_completed(int status, void *buffer, int len, void *dev_id)
+}
+/*-------------------------------------------------------------------*/
+_static int uhci_submit_iso_urb (purb_t purb)
 {
-	wait_queue_head_t *wakeup = (wait_queue_head_t *)dev_id;
+	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;
 
-	if (waitqueue_active(wakeup))
-		wake_up(wakeup);
-	else
-		printk("waitqueue empty!\n");
+	spin_lock_init (&lock);
+	spin_lock_irqsave (&lock, flags);	// Disable IRQs to schedule all ISO-TDs in time
 
-	return 0;			/* Don't re-instate */
-}
+	ret = iso_find_start (purb);	// adjusts purb->start_frame for later use
 
-/* 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)
-{
-	DECLARE_WAITQUEUE(wait, current);
-	struct uhci_qh *qh = uhci_qh_alloc(dev);
-	unsigned long rval;
-	int ret;
+	if (ret)
+		goto err;
 
-	if (!qh)
-		return USB_ST_INTERNALERROR;
+	tdm = (puhci_desc_t *) kmalloc (purb->number_of_packets * sizeof (puhci_desc_t), in_interrupt ()? GFP_ATOMIC : GFP_KERNEL);
 
-	current->state = TASK_UNINTERRUPTIBLE;
-	add_wait_queue(&qh->wakeup, &wait);
+	if (!tdm) {
+		ret = -ENOMEM;
+		goto err;
+	}
 
-	uhci_add_irq_list(dev->uhci, last, uhci_generic_completed, &qh->wakeup);
-	
-	uhci_insert_tds_in_qh(qh, first, last);
+	// First try to get all TDs
+	for (n = 0; n < purb->number_of_packets; n++) {
+		dbg (KERN_DEBUG MODSTR "n:%d purb->iso_frame_desc[n].length:%d\n", 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;
+	}
 
-	/* Add it into the skeleton */
-	uhci_insert_qh(&dev->uhci->skel_control_qh, qh);
+	status = TD_CTRL_ACTIVE | TD_CTRL_IOS;	//| (purb->transfer_flags&USB_DISABLE_SPD?0:TD_CTRL_SPD);
 
-	/* wait a user specified reasonable amount of time */
-	schedule_timeout(timeout);
+	destination = (purb->pipe & PIPE_DEVEP_MASK) | usb_packetid (purb->pipe);
 
-	remove_wait_queue(&qh->wakeup, &wait);
+	// 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;
 
-	/* Clean up in case it failed.. */
-	uhci_remove_irq_list(last);
+		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);
 
-	/* Remove it from the skeleton */
-	uhci_remove_qh(&dev->uhci->skel_control_qh, qh);
+	}
 
-	/* Need to check result before free'ing the qh */
-	ret = uhci_td_result(dev, last, &rval);
+	kfree (tdm);
+	dbg ("ISO-INT# %i, start %i, now %i\n", purb->number_of_packets, purb->start_frame, UHCI_GET_CURRENT_FRAME (s) & 1023);
+	ret = 0;
 
-	uhci_qh_free(qh);
+      err:
+	spin_unlock_irqrestore (&lock, flags);
+	return ret;
 
-	return (ret < 0) ? ret : rval;
 }
-
-/*
- * 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)
+/*-------------------------------------------------------------------*/
+_static int search_dev_ep (puhci_t s, purb_t purb)
 {
-	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;
-
-	first = td = uhci_td_alloc(dev);
-	if (!td)
-		return USB_ST_INTERNALERROR;
-
-	/* The "pipe" thing contains the destination in bits 8--18 */
-	destination = (pipe & PIPE_DEVEP_MASK) | USB_PID_SETUP;
-
-	/* 3 errors */
-	status = (pipe & TD_CTRL_LS) | TD_CTRL_ACTIVE | TD_CTRL_SPD | (3 << 27);
+	unsigned long flags;
+	struct list_head *p = s->urb_list.next;
+	purb_t tmp;
 
-	/*
-	 * 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;
+	dbg (KERN_DEBUG MODSTR "search_dev_ep:\n");
+	spin_lock_irqsave (&s->urb_list_lock, flags);
 
-	/*
-	 * 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 */
+	for (; p != &s->urb_list; p = p->next) {
+		tmp = list_entry (p, urb_t, urb_list);
+		dbg (KERN_DEBUG MODSTR "urb: %p\n", 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))
+			return 1;	// found another urb already queued for processing
 
-	prevtd = td;
-	td = uhci_td_alloc(dev);
-	if (!td) {
-		uhci_td_free(prevtd);
-		return USB_ST_INTERNALERROR;
 	}
 
-	prevtd->link = virt_to_bus(td) | UHCI_PTR_DEPTH;
-
-	/*
-	 * Build the DATA TD's
-	 */
-	while (len > 0) {
-		/* Build the TD for control status */
-		int pktsze = len;
+	spin_unlock_irqrestore (&s->urb_list_lock, flags);
 
-		if (pktsze > maxsze)
-			pktsze = maxsze;
+	return 0;
+}
+/*-------------------------------------------------------------------*/
+_static int uhci_submit_urb (purb_t purb)
+{
+	puhci_t s;
+	purb_priv_t purb_priv;
+	int ret = 0;
 
-		/* 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;
+	if (!purb->dev || !purb->dev->bus)
+		return -ENODEV;
 
-		data += pktsze;
-		len -= pktsze;
+	s = (puhci_t) purb->dev->bus->hcpriv;
+	//printk( MODSTR"submit_urb: %p type %d\n",purb,usb_pipetype(purb->pipe));
 
-		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 */
-	}
+	if (usb_pipedevice (purb->pipe) == s->rh.devnum)
+		return rh_submit_urb (purb);	/* virtual root hub */
 
-	/*
-	 * 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;
+	usb_inc_dev_use (purb->dev);
 
-	destination |= 1 << TD_TOKEN_TOGGLE;		/* End in Data1 */
+	if (search_dev_ep (s, purb)) {
+		usb_dec_dev_use (purb->dev);
+		return -ENXIO;	// no such address
 
-	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;
-
-	/* Start it up.. */
-	ret = uhci_run_control(dev, first, td, timeout);
-
-	count = 1000;
-	td = first;
-	do {
-		nextlink = td->link;
-		uhci_remove_td(td);
-		uhci_td_free(td);
+	}
 
-		if (nextlink & UHCI_PTR_TERM)	/* Tail? */
-			break;
+#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;
+	}
 
-		td = uhci_ptr_to_virt(nextlink);
-	} while (--count);
+	purb->hcpriv = purb_priv;
+	INIT_LIST_HEAD (&purb_priv->desc_list);
+	purb_priv->short_control_packet=0;
+	dbg (KERN_DEBUG MODSTR "submit_urb: scheduling %p\n", purb);
+
+	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;
+	}
 
-	if (!count)
-		printk(KERN_ERR "runaway td's!?\n");
+	dbg (KERN_DEBUG MODSTR "submit_urb: scheduled with ret: %d\n", ret);
 
-#ifdef UHCI_DEBUG
-	if (ret >= 0 && ret != bytesrequested && bytesrequested)
-		printk("requested %ld bytes, got %d\n", bytesrequested, ret); 
+	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;
+	}
 
-	return ret;
+	purb->status = USB_ST_URB_PENDING;
+	queue_urb (s, &purb->urb_list,1);
+	dbg (KERN_DEBUG MODSTR "submit_urb: exit\n");
+
+	return 0;
 }
+/*-------------------------------------------------------------------
+ Virtual Root Hub
+ -------------------------------------------------------------------*/
+
+_static __u8 root_hub_dev_des[] =
+{
+	0x12,			/*  __u8  bLength; */
+	0x01,			/*  __u8  bDescriptorType; Device */
+	0x00,			/*  __u16 bcdUSB; v1.0 */
+	0x01,
+	0x09,			/*  __u8  bDeviceClass; HUB_CLASSCODE */
+	0x00,			/*  __u8  bDeviceSubClass; */
+	0x00,			/*  __u8  bDeviceProtocol; */
+	0x08,			/*  __u8  bMaxPacketSize0; 8 Bytes */
+	0x00,			/*  __u16 idVendor; */
+	0x00,
+	0x00,			/*  __u16 idProduct; */
+	0x00,
+	0x00,			/*  __u16 bcdDevice; */
+	0x00,
+	0x00,			/*  __u8  iManufacturer; */
+	0x00,			/*  __u8  iProduct; */
+	0x00,			/*  __u8  iSerialNumber; */
+	0x01			/*  __u8  bNumConfigurations; */
+};
 
-/*
- * 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)
+/* Configuration descriptor */
+_static __u8 root_hub_config_des[] =
 {
-	DECLARE_WAITQUEUE(wait, current);
-	struct uhci_qh *qh = uhci_qh_alloc(dev);
-	int ret;
-
-	if (!qh)
-		return USB_ST_INTERNALERROR;
-
-	current->state = TASK_UNINTERRUPTIBLE;
-	add_wait_queue(&qh->wakeup, &wait);
-
-	uhci_add_irq_list(dev->uhci, last, uhci_generic_completed, &qh->wakeup);
-	
-	uhci_insert_tds_in_qh(qh, first, last);
+	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 */
+};
 
-	/* Add it into the skeleton */
-	uhci_insert_qh(&dev->uhci->skel_bulk_qh, qh);
 
-	/* wait a user specified reasonable amount of time */
-	schedule_timeout(timeout);
+_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 *** */
+};
 
-	remove_wait_queue(&qh->wakeup, &wait);
+/*-------------------------------------------------------------------------*/
+/* prepare Interrupt pipe transaction data; HUB INTERRUPT ENDPOINT */
+_static int rh_send_irq (purb_t purb)
+{
 
-	/* Clean up in case it failed.. */
-	uhci_remove_irq_list(last);
+	int len = 1;
+	int i;
+	puhci_t uhci = purb->dev->bus->hcpriv;
+	unsigned int io_addr = uhci->io_addr;
+	__u16 data = 0;
 
-	uhci_remove_qh(&dev->uhci->skel_bulk_qh, qh);
+	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;
+	}
 
-	ret = uhci_td_result(dev, last, rval);
+	*(__u16 *) purb->transfer_buffer = cpu_to_le16 (data);
+	purb->actual_length = len;
+	purb->status = USB_ST_NOERROR;
 
-	uhci_qh_free(qh);
+	if ((data > 0) && (uhci->rh.send != 0)) {
+		dbg (KERN_DEBUG MODSTR "Root-Hub INT complete: port1: %x port2: %x data: %x\n",
+		     inw (io_addr + USBPORTSC1), inw (io_addr + USBPORTSC2), data);
+		purb->complete (purb);
 
-	return ret;
+	}
+	return USB_ST_NOERROR;
 }
 
-/*
- * 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)
-{
-	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;
-
-	/* The "pipe" thing contains the destination in bits 8--18. */
-	destination = (pipe & PIPE_DEVEP_MASK) | usb_packetid (pipe);
-
-	/* 3 errors */
-	status = (pipe & TD_CTRL_LS) | TD_CTRL_ACTIVE | TD_CTRL_SPD | (3 << 27);
-
-	/*
-	 * Build the TDs for the bulk request
-	 */
-        first = td = uhci_td_alloc(dev);
-	if (!td)
-		return USB_ST_INTERNALERROR;
-
-	prevtd = first;		// This is fake, but at least it's not NULL
-	while (len > 0) {
-		/* Build the TD for control status */
-		int pktsze = len;
-
-		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;
-
-	/* CHANGE DIRECTION HERE! SAVE IT SOMEWHERE IN THE ENDPOINT!!! */
-
-	/* Start it up.. */
-	ret = uhci_run_bulk(dev, first, td, rval, timeout);
-
-	count = 1000;
-	curtd = first;
-
-	do {
-		nextlink = curtd->link;
-		uhci_remove_td(curtd);
-		uhci_td_free(curtd);
-
-		if (nextlink & UHCI_PTR_TERM)	/* Tail? */
-			break;
-
-		curtd = uhci_ptr_to_virt(nextlink);
-	} while (--count);
+/*-------------------------------------------------------------------------*/
+/* Root Hub INTs are polled by this timer */
+_static int rh_init_int_timer (purb_t purb)
+{
+	puhci_t uhci = purb->dev->bus->hcpriv;
 
-	if (!count)
-		printk(KERN_DEBUG "uhci: runaway td's!?\n");
+	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);
 
-	return ret < 0;
+	return 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)
-{
-	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));
+/*-------------------------------------------------------------------------*/
+#define OK(x) 			len = (x); break
 
-	/* The "pipe" thing contains the destination in bits 8--18. */
-	destination = (pipe & PIPE_DEVEP_MASK) | usb_packetid(pipe);
+#define CLR_RH_PORTSTAT(x) \
+		status = inw(io_addr+USBPORTSC1+2*(wIndex-1)); \
+		status = (status & 0xfff5) & ~(x); \
+		outw(status, io_addr+USBPORTSC1+2*(wIndex-1))
+
+#define SET_RH_PORTSTAT(x) \
+		status = inw(io_addr+USBPORTSC1+2*(wIndex-1)); \
+		status = (status & 0xfff5) | (x); \
+		outw(status, io_addr+USBPORTSC1+2*(wIndex-1))
+
+
+/*-------------------------------------------------------------------------*/
+/****
+ ** Root Hub Control Pipe
+ *************************/
+
+
+_static int rh_submit_urb (purb_t purb)
+{
+	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;
 
-	/* Infinite errors is 0 */
-	status = (pipe & TD_CTRL_LS) | TD_CTRL_ACTIVE | TD_CTRL_SPD;
+	__u16 bmRType_bReq;
+	__u16 wValue;
+	__u16 wIndex;
+	__u16 wLength;
+
+	if (usb_pipetype (pipe) == PIPE_INTERRUPT) {
+		dbg (KERN_DEBUG MODSTR "Root-Hub submit IRQ: every %d ms\n", 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);
+
+	for (i = 0; i < 8; i++)
+		uhci->rh.c_p_r[i] = 0;
+
+	dbg (KERN_DEBUG MODSTR "Root-Hub: adr: %2x cmd(%1x): %04 %04 %04 %04\n",
+	     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 
+		 */
 
-		data += pktsze;
-		len -= pktsze;
+	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;
 
-		if (len > 0) {
-			prevtd = td;
-			td = uhci_td_alloc(dev);
-			prevtd->link = virt_to_bus(td) | UHCI_PTR_DEPTH;
+	case RH_CLEAR_FEATURE | RH_CLASS:
+		switch (wValue) {
+		case (RH_C_HUB_OVER_CURRENT):
+			OK (0);	/* hub power over current ** */
 		}
+		break;
 
-		/* Alternate Data0/1 */
-		usb_dotoggle(usb_dev, usb_pipeendpoint(pipe), usb_pipeout(pipe));
-	}
+	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;
 
-	first->backptr = NULL;
-	td->link = 1;                           /* Terminate */
-	td->status = status | TD_CTRL_IOC;	/* IOC */
+	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);
+
+	case RH_GET_CONFIGURATION:
+		*(__u8 *) data = 0x01;
+		OK (1);
+
+	case RH_SET_CONFIGURATION:
+		OK (0);
+	default:
+		stat = -EPIPE;
+	}
 
-	bulk_qh->skel = &uhci->skel_bulk_qh;
-	uhci_insert_qh(&uhci->skel_bulk_qh, bulk_qh);
 
-	/* Return last td for removal */
-	return td;
+	dbg (KERN_DEBUG MODSTR "Root-Hub stat port1: %x port2: %x \n",
+	     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;
 }
+/*-------------------------------------------------------------------------*/
 
-/*
- * 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)
+_static int rh_unlink_urb (purb_t purb)
 {
-	/* none found? there is nothing to remove! */
-	if (!first)
-		return USB_ST_REMOVED;
+	puhci_t uhci = purb->dev->bus->hcpriv;
 
-	uhci_remove_transfer(first, 1);
-
-	return USB_ST_NOERROR;
+	dbg (KERN_DEBUG MODSTR "Root-Hub unlink IRQ\n");
+	uhci->rh.send = 0;
+	del_timer (&uhci->rh.rh_int_timer);
+	return 0;
 }
+/*-------------------------------------------------------------------*/
 
-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
-};
+#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..
- */
-static void uhci_reset_port(unsigned int port)
-{
-	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);
-	}
-
-}
-
-/*
- * This gets called if the connect status on the root
- * hub (and the root hub only) changes.
+ * <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_connect_change(struct uhci *uhci, unsigned int port, unsigned int nr)
+_static int uhci_map_status (int status, int dir_out)
 {
-	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
-
-	/*
-	 * 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]);
-
-	status = inw(port);
-
-	/* 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;
+	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;
 
-	/*
-	 * 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;
-
-	usb_connect(usb_dev);
-
-	root_hub->usb->children[nr] = usb_dev;
-
-	wait_ms(200); /* wait for powerup */
-	uhci_reset_port(port);
-
-	/* 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 (usb_new_device(usb_dev)) {
-		unsigned short status = inw(port);
-
-		printk(KERN_INFO "uhci: disabling port %d\n",
-			nr + 1);
-		outw(status & ~USBPORTSC_PE, port);
-	}
+	return USB_ST_INTERNALERROR;
 }
 
 /*
- * This gets called when the root hub configuration
- * has changed. Just go through each port, seeing if
- * there is something interesting happening.
+ * Only the USB core should call uhci_alloc_dev and uhci_free_dev
  */
-static void uhci_check_configuration(struct uhci *uhci)
+_static int uhci_alloc_dev (struct usb_device *usb_dev)
 {
-	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;
-
-	do {
-		unsigned short status = inw(io_addr);
-
-		if (status & USBPORTSC_CSC)
-			uhci_connect_change(uhci, io_addr, nr);
-
-		nr++; io_addr += 2;
-	} while (nr < maxchild);
-}
-
-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);
-#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;
-
-		if ((frm->frame_status = uhci_map_status (uhci_status_bits (prtd->status),
-		    uhci_packetout (prtd->info))))
-			isocdesc->error_count++;
-
-		prtd++;
-		frm++;
-		if (++fx >= isocdesc->frame_count) {    /* wrap fx, prtd, and frm */
-			fx = 0;
-			prtd = isocdesc->td;
-			frm  = isocdesc->frames;
-		} /* end wrap */
-	} /* end for */
-
-	/*
- 	 * 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 */
-
-#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 */
+	return 0;
+}
 
+_static int uhci_free_dev (struct usb_device *usb_dev)
+{
 	return 0;
 }
 
-static int uhci_isoc_callback(struct uhci *uhci, struct uhci_td *td, int status, unsigned long rval)
+/*
+ * 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)
 {
-	struct usb_isoc_desc *isocdesc = td->dev_id;
-	int ret;
+	return UHCI_GET_CURRENT_FRAME ((puhci_t) usb_dev->bus->hcpriv);
+}
 
-	/*
-	 * 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);
-	 */
+struct usb_operations uhci_device_operations =
+{
+	uhci_alloc_dev,
+	uhci_free_dev,
+	uhci_get_current_frame_number,
+	uhci_submit_urb,
+	uhci_unlink_urb
+};
 
-	fixup_isoc_desc (td);
+/* 
+ * 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
+
+
+	// extracted and remapped info from TD
+	int maxlength;
+	int actual_length;
+	int status = USB_ST_NOERROR;
+
+	dbg (KERN_DEBUG MODSTR "process_transfer: urb contains bulk/control request\n");
+
+
+	/* 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 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;
+
+	for (; p != &qh->vertical; p = p->next) {
+		desc = list_entry (p, uhci_desc_t, vertical);
+
+		if (desc->hw.td.status & TD_CTRL_ACTIVE)	// do not process active TDs
+			return ret;
+
+		// 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));
+
+		// 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));
+		}
 
-	ret = td->completed (isocdesc->error_count, bus_to_virt (td->buffer),
-				isocdesc->total_length, isocdesc);
+		// 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;
 
-	/*
-	 * Isoc. handling of return value from td->completed (callback function)
-	 */
+#if 0
+		if (i++==0)
+		       	uhci_show_td (desc);	// show first TD of each transfer
+#endif
 
-	switch (ret) {
-	case CB_CONTINUE:       /* similar to the REMOVE condition below */
-		/* TBD */
-		uhci_td_free (td);
-		break;
+		// go into error condition to keep data_toggle unchanged if short packet occurs
+		if ((purb->transfer_flags & USB_DISABLE_SPD) && (actual_length < maxlength)) {
+			ret = USB_ST_SHORT_PACKET;
+			printk (KERN_DEBUG MODSTR "process_transfer: SPD!!\n");
+			break;	// exit after this TD because SP was detected
 
-	case CB_REUSE:          /* similar to the re-add condition below,
-				 * but Not ACTIVE */
-		/* TBD */
-		/* usb_dev = td->dev->usb; */
-
-		/* Safe since uhci_interrupt_notify holds the lock */
-		list_add(&td->irq_list, &uhci->interrupt_list);
-
-		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;
+		}
 
-	case CB_RESTART:        /* similar to re-add, but mark ACTIVE */
-		/* TBD */
-		/* usb_dev = td->dev->usb; */
-
-		list_add(&td->irq_list, &uhci->interrupt_list);
-
-		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;
+		data_toggle = uhci_toggle (desc->hw.td.info);
+		//printk(KERN_DEBUG MODSTR"process_transfer: len:%d status:%x mapped:%x toggle:%d\n", actual_length, desc->hw.td.status,status, data_toggle);      
+#if 1
+		if ((usb_pipetype (purb->pipe) == PIPE_CONTROL) && (actual_length < maxlength)) {
+			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
+				printk("uhci: short packet during control transfer, retrigger status stage\n");
+				purb_priv->short_control_packet=1;
+				return 0;
+			}
+		}
+#endif
+	}
+	usb_settoggle (purb->dev, usb_pipeendpoint (purb->pipe), usb_pipeout (purb->pipe), !data_toggle);
+	transfer_finished:
 
-		/* The HC removes it, so re-add it */
-		uhci_insert_td_in_qh(td->qh, td);
-		break;
+	/* 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;
+		purb->status=0;
+	}
 
-	case CB_ABORT:          /* kill/abort */
-		/* TBD */
-		uhci_kill_isoc (isocdesc);
-		break;
-	} /* end isoc. TD switch */
+	unlink_qh (s, qh);
+	delete_qh (s, qh);
 
-	return 0;
+	purb->status = status;
+	
+                                                                        	
+	dbg(KERN_DEBUG MODSTR"process_transfer: urb %p, wanted len %d, len %d status %x err %d\n",
+		purb,purb->transfer_buffer_length,purb->actual_length, purb->status, purb->error_count);
+	//dbg(KERN_DEBUG MODSTR"process_transfer: exit\n");
+	return ret;
 }
 
-static int uhci_bulk_callback(struct uhci *uhci, struct uhci_td *td, int status, unsigned long rval)
+_static int process_interrupt (puhci_t s, purb_t purb)
 {
-	if (td->completed(status, bus_to_virt(td->buffer), rval, td->dev_id)) {
-		struct usb_device *usb_dev = td->dev->usb;
+	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;
+
+	//printk(KERN_DEBUG MODSTR"urb contains interrupt request\n");
+
+	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_NAK) {
+			// NAKed transfer
+			//printk("TD NAK Status @%p %08x\n",desc,desc->hw.td.status);
+			goto err;
+		}
 
-		/* This is safe since uhci_interrupt_notify holds the lock */
-		list_add(&td->irq_list, &uhci->interrupt_list);
+		if (desc->hw.td.status & TD_CTRL_ACTIVE) {
+			// do not process active TDs
+			//printk("TD ACT Status @%p %08x\n",desc,desc->hw.td.status);
+			goto err;
+		}
 
-		/* Reset the status */
-		td->status = (td->status & TD_CTRL_LS) | TD_CTRL_ACTIVE | TD_CTRL_IOC | TD_CTRL_SPD;
+		if (!desc->hw.td.status & TD_CTRL_IOC) {
+			// do not process one-shot TDs
+			goto err;
+		}
+		// extract transfer parameters from TD
 
-		/* 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 */
+		actual_length = (desc->hw.td.status + 1) & 0x7ff;
+		status = uhci_map_status (uhci_status_bits (desc->hw.td.status), usb_pipeout (purb->pipe));
 
-		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);
-	}
+		// 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));
+		}
 
-	return 0;
-}
+		// if any error occured: ignore this td, and continue
+		if (status != USB_ST_NOERROR) {
+			purb->error_count++;
+			goto err;
+		}
+		else
+			purb->actual_length = actual_length;
 
-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;
+		// FIXME: SPD?
 
-		if (td->pipetype != PIPE_INTERRUPT)
-			return 0;
+		data_toggle = uhci_toggle (desc->hw.td.info);
 
-		/* This is safe since uhci_interrupt_notify holds the lock */
-		list_add(&td->irq_list, &uhci->interrupt_list);
+		if (purb->complete && status != USB_ST_TIMEOUT) {
+			// for last td, no user completion is needed
+			dbg (KERN_DEBUG MODSTR "process_interrupt: calling completion\n");
+			purb->status = status;
+			purb->complete ((struct urb *) purb);
+			purb->status = USB_ST_URB_PENDING;
+		}
 
-		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;
+		// 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;
+		}
 
-		/* 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);
+	      err:
 	}
 
-	return 0;
+	return ret;
 }
 
-static void uhci_interrupt_notify(struct uhci *uhci)
-{
-	struct list_head *tmp, *head = &uhci->interrupt_list;
-	int status;
-
-	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;
 
-		tmp = tmp->next;
+_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 ( /*KERN_DEBUG */ MODSTR "urb contains iso request\n");
+	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
+			printk (KERN_DEBUG MODSTR "TD still active (%x)- grrr. paranoia!\n", desc->hw.td.status);
+			ret = USB_ST_PARTIAL_ERROR;
+			purb->iso_frame_desc[i].status = ret;
+			unlink_td (s, desc);
+			goto err;
+		}
 
-		/* 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;
+		unlink_td (s, desc);
 
-		/* remove from IRQ list */
-		list_del(&td->irq_list);
-		INIT_LIST_HEAD(&td->irq_list);
+		if (purb->number_of_packets <= i) {
+			dbg (KERN_DEBUG MODSTR "purb->number_of_packets (%d)<=(%d)\n", purb->number_of_packets, i);
+			ret = USB_ST_URB_INVALID_ERROR;
+			goto err;
+		}
 
-		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->iso_frame_desc[i].offset + purb->transfer_buffer != bus_to_virt (desc->hw.td.buffer)) {
+			// Hm, something really weird is going on
+			dbg (KERN_DEBUG MODSTR "Pointer Paranoia: %p!=%p\n", 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;
+
+	      err:
+
+		if (purb->iso_frame_desc[i].status != USB_ST_NOERROR) {
+			purb->error_count++;
+			purb->status = purb->iso_frame_desc[i].status;
+		}
+		dbg (KERN_DEBUG MODSTR "process_iso: len:%d status:%x\n",
+		     purb->iso_frame_desc[i].length, purb->iso_frame_desc[i].status);
 
-		/* 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) */
+		delete_desc (desc);
+		list_del (p);
 	}
-	spin_unlock(&irqlist_lock);
+	dbg ( /*KERN_DEBUG */ MODSTR "process_iso: exit %i (%d)\n", i, ret);
+	return ret;
 }
 
-/*
- * 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)
+
+_static int process_urb (puhci_t s, struct list_head *p)
 {
-	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;
+	int ret = USB_ST_NOERROR;
+	purb_t purb = list_entry (p, urb_t, urb_list);
+	spin_lock(&s->urb_list_lock);
+
+	dbg ( /*KERN_DEBUG */ MODSTR "found queued urb: %p\n", purb);
+	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;
+	}
+
+	spin_unlock(&s->urb_list_lock);
+
+	if (purb->status != USB_ST_URB_PENDING) {
+		int proceed = 0;
+		dbg ( /*KERN_DEBUG */ MODSTR "dequeued urb: %p\n", purb);
+		dequeue_urb (s, p, 1);
+
+#ifdef _UHCI_SLAB
+		kmem_cache_free(urb_priv_kmem, purb->hcpriv);
+#else
+		kfree (purb->hcpriv);
+#endif
+
+		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;
+			}
+
+			// In case you need the current URB status for your completion handler
+			if (purb->complete && (!proceed || (purb->transfer_flags & USB_URB_EARLY_COMPLETE))) {
+				dbg (KERN_DEBUG MODSTR "process_transfer: calling early completion\n");
+				purb->complete ((struct urb *) purb);
+				if (!proceed && is_ring)
+					uhci_submit_urb (purb);
+			}
+
+			if (proceed && purb->next) {
+				// if there are linked urbs - handle submitting of them right now.
+				tmp = purb->next;	// pointer to first urb
+
+				do {
+					if ((tmp->status != USB_ST_URB_PENDING) && 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
+
+				if (purb->complete && !(purb->transfer_flags & USB_URB_EARLY_COMPLETE)) {
+					dbg ( /*KERN_DEBUG */ MODSTR "process_transfer: calling completion\n");
+					purb->complete ((struct urb *) purb);
+				}
 			}
-			io_addr += 2;
-		} while (--ports > 0);
+			usb_dec_dev_use (purb->dev);
+		}
 	}
+
+	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\n");
+	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) {
+		printk (KERN_DEBUG MODSTR "interrupt, status %x\n", 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\n");
 }
 
-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");
+			printk (KERN_ERR MODSTR "USBCMD_HCRESET timed out!\n");
 			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;
-
-	memset(uhci, 0, sizeof(*uhci));
-
-	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;
-
-	bus = usb_alloc_bus(&uhci_device_operations);
-	if (!bus)
-		goto au_free_fl;
+	usb_deregister_bus (s->bus);
 
-	uhci->bus = bus;
-	bus->hcpriv = 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);
 
-	/*
- 	 * Allocate the root_hub
-	 */
-	usb = usb_alloc_dev(NULL, bus);
-	if (!usb)
-		goto au_free_bus;
+}
+
+_static int __init uhci_start_usb (puhci_t s)
+{				/* start it up */
+	/* connect the virtual root hub */
+	struct usb_device *usb_dev;
+
+	usb_dev = usb_alloc_dev (NULL, s->bus);
+	if (!usb_dev)
+		return -1;
 
-	usb->bus = bus;
+	s->bus->root_hub = usb_dev;
+	usb_connect (usb_dev);
 
-	dev = usb_to_uhci(usb);
-	dev->uhci = uhci;
+	if (usb_new_device (usb_dev) != 0) {
+		usb_free_dev (usb_dev);
+		return -1;
+	}
+
+	return 0;
+}
+
+_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;
+
+	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;
+	}
 
-	/* Initialize the root hub */
+	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));
+		printk ("port %i, adr %x status %x\n", s->maxports,
+			io_addr + 0x10 + (s->maxports * 2), portstatus);
 		if (!(portstatus & 0x0080))
 			break;
 	}
-	printk(KERN_DEBUG "Detected %d ports\n", port);
+	dbg (KERN_DEBUG MODSTR "Detected %d ports\n", 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 (KERN_DEBUG "Port count misdetected, forcing to 2 ports\n");
+		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;
-
-	uhci->skel_control_qh.link = virt_to_bus(&uhci->skel_bulk_qh) | UHCI_PTR_QH;
-	uhci->skel_control_qh.element = UHCI_PTR_TERM;
+	s->rh.numports = s->maxports;
 
-	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;
-
-/*
- * 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;
-}
-
-/*
- * De-allocate all resources..
- */
-static void release_uhci(struct uhci *uhci)
-{
-	if (uhci->irq >= 0) {
-		free_irq(uhci->irq, uhci);
-		uhci->irq = -1;
+	request_region (s->io_addr, io_size, MODNAME);
+	reset_hc (s);
+	usb_register_bus (s->bus);
+
+	start_hc (s);
+
+	if (request_irq (irq, uhci_interrupt, SA_SHIRQ, MODNAME, s)) {
+		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;
-
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,3,8)
+		pci_enable_device (dev);
+#endif
 		/* disable legacy emulation */
-		pci_write_config_word(dev, USBLEGSUP, USBLEGSUP_DEFAULT);
-
-		pci_enable_device(dev);
+		pci_write_config_word (dev, USBLEGSUP, USBLEGSUP_DEFAULT);
 
-		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;
+	printk ("handle_apm_event(%d)\n", event);
 	switch (event) {
 	case APM_SYS_SUSPEND:
 	case APM_USER_SUSPEND:
 		if (down) {
-			printk(KERN_DEBUG "uhci: received extra suspend event\n");
+			dbg (KERN_DEBUG MODSTR "received extra suspend event\n");
 			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 (KERN_DEBUG MODSTR "received bogus resume event\n");
 			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 +2251,90 @@
 }
 #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) {
+		printk(KERN_ERR MODSTR"kmem_cache_create for uhci_desc failed (out of memory)\n");
+		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) {
+		printk(KERN_ERR MODSTR"kmem_cache_create for urb_priv_t failed (out of memory)\n");
+		return -ENOMEM;
+	}
+#endif	
+	printk (KERN_INFO MODSTR VERSTR "\n");
+
 	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)
-			continue;
+		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;
-	}
-
-	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");
+	puhci_t s;
+	while ((s = devs)) {
+		devs = devs->next;
+		uhci_cleanup_dev(s);
+	}
+#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

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)