patch-2.3.99-pre1 linux/drivers/usb/usb-uhci.c
Next file: linux/drivers/usb/usb-uhci.h
Previous file: linux/drivers/usb/usb-uhci-debug.h
Back to the patch index
Back to the overall index
- Lines: 1803
- Date:
Tue Mar 14 18:09:51 2000
- Orig file:
v2.3.51/linux/drivers/usb/usb-uhci.c
- Orig date:
Fri Mar 10 16:40:45 2000
diff -u --recursive --new-file v2.3.51/linux/drivers/usb/usb-uhci.c linux/drivers/usb/usb-uhci.c
@@ -12,7 +12,7 @@
* (C) Copyright 1999 Johannes Erdfelt
* (C) Copyright 1999 Randy Dunlap
*
- * $Id: usb-uhci.c,v 1.197 2000/02/15 17:44:22 acher Exp $
+ * $Id: usb-uhci.c,v 1.222 2000/03/13 21:18:02 fliegl Exp $
*/
#include <linux/config.h>
@@ -28,9 +28,9 @@
#include <linux/unistd.h>
#include <linux/interrupt.h> /* for in_interrupt() */
#include <linux/init.h>
-/* This enables debug printks */
-#define DEBUG
-#include <linux/usb.h>
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,3,44)
+#include <linux/pm.h>
+#endif
#include <asm/uaccess.h>
#include <asm/io.h>
@@ -40,51 +40,56 @@
/* This enables more detailed sanity checks in submit_iso */
//#define ISO_SANITY_CHECK
+/* This enables debug printks */
+#define DEBUG
+
/* This enables all symbols to be exported, to ease debugging oopses */
//#define DEBUG_SYMBOLS
/* This enables an extra UHCI slab for memory debugging */
#define DEBUG_SLAB
+#include <linux/usb.h>
#include "usb-uhci.h"
#include "usb-uhci-debug.h"
#undef DEBUG
#undef dbg
#define dbg(format, arg...) do {} while (0)
-
-#include <linux/pm.h>
-
+#define DEBUG_SYMBOLS
#ifdef DEBUG_SYMBOLS
#define _static
#ifndef EXPORT_SYMTAB
- #define EXPORT_SYMTAB
+ #define EXPORT_SYMTAB
#endif
#else
#define _static static
#endif
+#define queue_dbg dbg //err
+#define async_dbg dbg //err
+
#ifdef DEBUG_SLAB
static kmem_cache_t *uhci_desc_kmem;
static kmem_cache_t *urb_priv_kmem;
#endif
+#define SLAB_FLAG (in_interrupt ()? SLAB_ATOMIC : SLAB_KERNEL)
+#define KMALLOC_FLAG (in_interrupt ()? GFP_ATOMIC : GFP_KERNEL)
+
+#define CONFIG_USB_UHCI_HIGH_BANDWIDTH
#define USE_CTRL_DEPTH_FIRST 0 // 0: Breadth first, 1: Depth first
#define USE_BULK_DEPTH_FIRST 0 // 0: Breadth first, 1: Depth first
-#ifdef CONFIG_USB_UHCI_HIGH_BANDWIDTH
-#define USE_RECLAMATION_LOOP
-#else
-//#define USE_RECLAMATION_LOOP
-#endif
-
-// stop bandwidth reclamation after (roughly) 50ms (depends also on
-// hub polling interval)
+// stop bandwidth reclamation after (roughly) 50ms
#define IDLE_TIMEOUT (HZ/20)
_static int rh_submit_urb (urb_t *urb);
_static int rh_unlink_urb (urb_t *urb);
_static int delete_qh (uhci_t *s, uhci_desc_t *qh);
+_static int process_transfer (uhci_t *s, urb_t *urb, int mode);
+_static int process_interrupt (uhci_t *s, urb_t *urb);
+_static int process_iso (uhci_t *s, urb_t *urb, int force);
static uhci_t *devs = NULL;
@@ -105,58 +110,47 @@
qh = list_entry (q, uhci_desc_t, horizontal);
if ((qh->last_used!=now) || force)
delete_qh(s,qh);
+
q=qh->horizontal.prev;
}
}
/*-------------------------------------------------------------------*/
-#ifdef USE_RECLAMATION_LOOP
+#ifdef CONFIG_USB_UHCI_HIGH_BANDWIDTH
_static void enable_desc_loop(uhci_t *s, urb_t *urb)
{
int flags;
-
- dbg("enable_desc_loop: enter");
-
+
spin_lock_irqsave (&s->qh_lock, flags);
- s->chain_end->hw.qh.head=virt_to_bus(s->control_chain)|UHCI_PTR_QH;
+ s->chain_end->hw.qh.head&=~UHCI_PTR_TERM;
+ mb();
s->loop_usage++;
((urb_priv_t*)urb->hcpriv)->use_loop=1;
spin_unlock_irqrestore (&s->qh_lock, flags);
-
- dbg("enable_desc_loop: finished");
}
/*-------------------------------------------------------------------*/
_static void disable_desc_loop(uhci_t *s, urb_t *urb)
{
int flags;
-
- dbg("disable_desc_loop: enter\n");
-
+
spin_lock_irqsave (&s->qh_lock, flags);
if (((urb_priv_t*)urb->hcpriv)->use_loop) {
s->loop_usage--;
- if (!s->loop_usage)
- s->chain_end->hw.qh.head=UHCI_PTR_TERM;
-
+ if (!s->loop_usage) {
+ s->chain_end->hw.qh.head|=UHCI_PTR_TERM;
+ mb();
+ }
((urb_priv_t*)urb->hcpriv)->use_loop=0;
}
spin_unlock_irqrestore (&s->qh_lock, flags);
-
- dbg("disable_desc_loop: finished");
-
}
#endif
/*-------------------------------------------------------------------*/
-_static void queue_urb (uhci_t *s, urb_t *urb)
+_static void queue_urb_unlocked (uhci_t *s, urb_t *urb)
{
- unsigned long flags=0;
struct list_head *p=&urb->urb_list;
-
-
- spin_lock_irqsave (&s->urb_list_lock, flags);
-
-#ifdef USE_RECLAMATION_LOOP
+#ifdef CONFIG_USB_UHCI_HIGH_BANDWIDTH
{
int type;
type=usb_pipetype (urb->pipe);
@@ -166,15 +160,21 @@
}
#endif
((urb_priv_t*)urb->hcpriv)->started=jiffies;
- list_add_tail (p, &s->urb_list);
-
- spin_unlock_irqrestore (&s->urb_list_lock, flags);
+ list_add (p, &s->urb_list);
}
+/*-------------------------------------------------------------------*/
+_static void queue_urb (uhci_t *s, urb_t *urb)
+{
+ unsigned long flags=0;
+ spin_lock_irqsave (&s->urb_list_lock, flags);
+ queue_urb_unlocked(s,urb);
+ spin_unlock_irqrestore (&s->urb_list_lock, flags);
+}
/*-------------------------------------------------------------------*/
_static void dequeue_urb (uhci_t *s, urb_t *urb)
{
-#ifdef USE_RECLAMATION_LOOP
+#ifdef CONFIG_USB_UHCI_HIGH_BANDWIDTH
int type;
type=usb_pipetype (urb->pipe);
@@ -189,9 +189,9 @@
_static int alloc_td (uhci_desc_t ** new, int flags)
{
#ifdef DEBUG_SLAB
- *new= kmem_cache_alloc(uhci_desc_kmem, in_interrupt ()? SLAB_ATOMIC : SLAB_KERNEL);
+ *new= kmem_cache_alloc(uhci_desc_kmem, SLAB_FLAG);
#else
- *new = (uhci_desc_t *) kmalloc (sizeof (uhci_desc_t), in_interrupt ()? GFP_ATOMIC : GFP_KERNEL);
+ *new = (uhci_desc_t *) kmalloc (sizeof (uhci_desc_t), KMALLOC_FLAG);
#endif
if (!*new)
return -ENOMEM;
@@ -205,6 +205,19 @@
return 0;
}
/*-------------------------------------------------------------------*/
+// append a qh to td.link physically, the SW linkage is not affected
+_static void append_qh(uhci_t *s, uhci_desc_t *td, uhci_desc_t* qh, int flags)
+{
+ unsigned long xxx;
+
+ spin_lock_irqsave (&s->td_lock, xxx);
+
+ td->hw.td.link = virt_to_bus (qh) | (flags & UHCI_PTR_DEPTH) | UHCI_PTR_QH;
+
+ mb();
+ spin_unlock_irqrestore (&s->td_lock, xxx);
+}
+/*-------------------------------------------------------------------*/
/* insert td at last position in td-list of qh (vertical) */
_static int insert_td (uhci_t *s, uhci_desc_t *qh, uhci_desc_t* new, int flags)
{
@@ -271,10 +284,9 @@
if (prev->type == TD_TYPE)
prev->hw.td.link = element->hw.td.link;
else
- prev->hw.qh.element = element->hw.td.link;
+ prev->hw.qh.element = element->hw.td.link;
}
- element->hw.td.link=UHCI_PTR_TERM;
mb ();
if (dir == 0)
@@ -302,9 +314,9 @@
_static int alloc_qh (uhci_desc_t ** new)
{
#ifdef DEBUG_SLAB
- *new= kmem_cache_alloc(uhci_desc_kmem, in_interrupt ()? SLAB_ATOMIC : SLAB_KERNEL);
+ *new= kmem_cache_alloc(uhci_desc_kmem, SLAB_FLAG);
#else
- *new = (uhci_desc_t *) kmalloc (sizeof (uhci_desc_t), in_interrupt ()? GFP_ATOMIC : GFP_KERNEL);
+ *new = (uhci_desc_t *) kmalloc (sizeof (uhci_desc_t), KMALLOC_FLAG);
#endif
if (!*new)
return -ENOMEM;
@@ -350,9 +362,10 @@
mb ();
spin_unlock_irqrestore (&s->qh_lock, flags);
-
+
return 0;
}
+
/*-------------------------------------------------------------------*/
_static int unlink_qh (uhci_t *s, uhci_desc_t *element)
{
@@ -406,6 +419,14 @@
delete_desc (td);
}
+
+/*-------------------------------------------------------------------*/
+_static void fill_td (uhci_desc_t *td, int status, int info, __u32 buffer)
+{
+ td->hw.td.status = status;
+ td->hw.td.info = info;
+ td->hw.td.buffer = buffer;
+}
/*-------------------------------------------------------------------*/
// Removes ALL qhs in chain (paranoia!)
_static void cleanup_skel (uhci_t *s)
@@ -441,19 +462,20 @@
qh = s->control_chain;
while ((p = qh->horizontal.next) != &qh->horizontal) {
qh1 = list_entry (p, uhci_desc_t, horizontal);
- dbg("delete_qh @ %p",qh1);
delete_qh (s, qh1);
}
- dbg("delete_qh last @ %p",qh);
+
delete_qh (s, qh);
}
else {
+ if (s->ls_control_chain)
+ delete_desc (s->ls_control_chain);
if (s->control_chain)
- kfree (s->control_chain);
+ delete_desc(s->control_chain);
if (s->bulk_chain)
- kfree (s->bulk_chain);
+ delete_desc (s->bulk_chain);
if (s->chain_end)
- kfree (s->chain_end);
+ delete_desc (s->chain_end);
}
dbg("cleanup_skel finished");
}
@@ -480,6 +502,7 @@
if (!s->iso_td)
goto init_skel_cleanup;
+ s->ls_control_chain = NULL;
s->control_chain = NULL;
s->bulk_chain = NULL;
s->chain_end = NULL;
@@ -499,26 +522,46 @@
if (ret)
goto init_skel_cleanup;
-
+
s->chain_end = qh;
+ ret = alloc_td (&td, 0);
+
+ if (ret)
+ goto init_skel_cleanup;
+
+ fill_td (td, TD_CTRL_IOC, 0, 0); // generate 1ms interrupt
+ insert_td (s, qh, td, 0);
+
dbg("allocating qh: bulk_chain");
ret = alloc_qh (&qh);
-
if (ret)
goto init_skel_cleanup;
insert_qh (s, s->chain_end, qh, 0);
s->bulk_chain = qh;
+
dbg("allocating qh: control_chain");
ret = alloc_qh (&qh);
-
if (ret)
goto init_skel_cleanup;
insert_qh (s, s->bulk_chain, qh, 0);
s->control_chain = qh;
+#ifdef CONFIG_USB_UHCI_HIGH_BANDWIDTH
+ // disabled reclamation loop
+ s->chain_end->hw.qh.head=virt_to_bus(s->control_chain) | UHCI_PTR_QH | UHCI_PTR_TERM;
+#endif
+
+ dbg("allocating qh: ls_control_chain");
+ ret = alloc_qh (&qh);
+ if (ret)
+ goto init_skel_cleanup;
+
+ insert_qh (s, s->control_chain, qh, 0);
+ s->ls_control_chain = qh;
+
for (n = 0; n < 8; n++)
s->int_chain[n] = 0;
@@ -532,7 +575,7 @@
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) | UHCI_PTR_QH;
+ s->int_chain[0]->hw.td.link = virt_to_bus (s->ls_control_chain) | UHCI_PTR_QH;
}
else {
s->int_chain[n]->hw.td.link = virt_to_bus (s->int_chain[0]);
@@ -547,20 +590,16 @@
dbg("framelist[%i]=%x",n,s->framelist[n]);
if ((n&127)==127)
((uhci_desc_t*) s->iso_td[n])->hw.td.link = virt_to_bus(s->int_chain[0]);
- else {
- for (o = 1, m = 2; m <= 128; o++, m += m) {
- // n&(m-1) = n%m
- if ((n & (m - 1)) == ((m - 1) / 2)) {
+ else
+ for (o = 1, m = 2; m <= 128; o++, m += m)
+ if ((n & (m - 1)) == ((m - 1) / 2))
((uhci_desc_t*) s->iso_td[n])->hw.td.link = virt_to_bus (s->int_chain[o]);
- }
- }
- }
}
mb();
//uhci_show_queue(s->control_chain);
dbg("init_skel exit");
- return 0; // OK
+ return 0;
init_skel_cleanup:
cleanup_skel (s);
@@ -568,14 +607,6 @@
}
/*-------------------------------------------------------------------*/
-_static void fill_td (uhci_desc_t *td, int status, int info, __u32 buffer)
-{
- td->hw.td.status = status;
- td->hw.td.info = info;
- td->hw.td.buffer = buffer;
-}
-
-/*-------------------------------------------------------------------*/
// LOW LEVEL STUFF
// assembles QHs und TDs for control, bulk and iso
/*-------------------------------------------------------------------*/
@@ -586,10 +617,15 @@
urb_priv_t *urb_priv = urb->hcpriv;
unsigned long destination, status;
int maxsze = usb_maxpacket (urb->dev, urb->pipe, usb_pipeout (urb->pipe));
- unsigned long len, bytesrequested;
+ unsigned long len;
char *data;
int depth_first=USE_CTRL_DEPTH_FIRST; // UHCI descriptor chasing method
+ if (!maxsze) {
+ err("uhci_submit_control_urb: pipesize for pipe %x is zero", urb->pipe);
+ return -EINVAL;
+ }
+
dbg("uhci_submit_control start");
alloc_qh (&qh); // alloc qh for this request
@@ -615,26 +651,21 @@
insert_td (s, qh, td, 0); // queue 'setup stage'-td in qh
#if 0
- dbg("SETUP to pipe %x: %x %x %x %x %x %x %x %x", urb->pipe,
- urb->setup_packet[0], urb->setup_packet[1], urb->setup_packet[2], urb->setup_packet[3],
- urb->setup_packet[4], urb->setup_packet[5], urb->setup_packet[6], urb->setup_packet[7]);
+ {
+ char *sp=urb->setup_packet;
+ dbg("SETUP to pipe %x: %x %x %x %x %x %x %x %x", urb->pipe,
+ sp[0],sp[1],sp[2],sp[3],sp[4],sp[5],sp[6],sp[7]);
+ }
//uhci_show_td(td);
#endif
- /* Build the DATA TD's */
len = urb->transfer_buffer_length;
- bytesrequested = len;
data = urb->transfer_buffer;
/* If direction is "send", change the frame from SETUP (0x2D)
to OUT (0xE1). Else change it from SETUP to IN (0x69). */
- destination &= ~UHCI_PID;
-
- if (usb_pipeout (urb->pipe))
- destination |= USB_PID_OUT;
- else
- destination |= USB_PID_IN;
+ destination = (urb->pipe & PIPE_DEVEP_MASK) | (usb_pipeout (urb->pipe)?USB_PID_OUT:USB_PID_IN);
while (len > 0) {
int pktsze = len;
@@ -664,7 +695,7 @@
destination &= ~UHCI_PID;
- if (usb_pipeout (urb->pipe) || (bytesrequested == 0))
+ if (usb_pipeout (urb->pipe) || (urb->transfer_buffer_length == 0))
destination |= USB_PID_IN;
else
destination |= USB_PID_OUT;
@@ -690,32 +721,34 @@
urb->status = -EINPROGRESS;
queue_urb (s, urb); // queue before inserting in desc chain
- qh->hw.qh.element&=~UHCI_PTR_TERM;
+ qh->hw.qh.element &= ~UHCI_PTR_TERM;
//uhci_show_queue(qh);
/* Start it up... put low speed first */
if (urb->pipe & TD_CTRL_LS)
- insert_qh (s, s->control_chain, qh, 1); // insert after control chain
+ insert_qh (s, s->control_chain, qh, 0);
else
- insert_qh (s, s->bulk_chain, qh, 0); // insert before bulk chain
- //uhci_show_queue(qh);
+ insert_qh (s, s->bulk_chain, qh, 0);
dbg("uhci_submit_control end");
return 0;
}
/*-------------------------------------------------------------------*/
-_static int uhci_submit_bulk_urb (urb_t *urb)
+// For queued bulk transfers, two additional QH helpers are allocated (nqh, bqh)
+// Due to the linking with other bulk urbs, it has to be locked with urb_list_lock!
+
+_static int uhci_submit_bulk_urb (urb_t *urb, urb_t *bulk_urb)
{
uhci_t *s = (uhci_t*) urb->dev->bus->hcpriv;
urb_priv_t *urb_priv = urb->hcpriv;
- uhci_desc_t *qh, *td;
+ uhci_desc_t *qh, *td, *nqh, *bqh;
unsigned long destination, status;
char *data;
unsigned int pipe = urb->pipe;
int maxsze = usb_maxpacket (urb->dev, pipe, usb_pipeout (pipe));
int info, len;
int depth_first=USE_BULK_DEPTH_FIRST; // UHCI descriptor chasing method
-
+ urb_priv_t *upriv, *bpriv;
if (usb_endpoint_halted (urb->dev, usb_pipeendpoint (pipe), usb_pipeout (pipe)))
return -EPIPE;
@@ -727,12 +760,55 @@
if (!maxsze)
return -EMSGSIZE;
- /* FIXME: should tell the client that the endpoint is invalid, i.e. not in the descriptor */
+
+ queue_dbg("uhci_submit_bulk_urb: urb %p, old %p, pipe %08x, len %i",
+ urb,bulk_urb,urb->pipe,urb->transfer_buffer_length);
- alloc_qh (&qh); // get qh for this request
+ upriv=(urb_priv_t*)urb->hcpriv;
- if (!qh)
- return -ENOMEM;
+ if (!bulk_urb) {
+ alloc_qh (&qh); // get qh for this request
+
+ if (!qh)
+ return -ENOMEM;
+
+ if (urb->transfer_flags & USB_QUEUE_BULK) {
+ alloc_qh(&nqh); // placeholder for clean unlink
+ if (!nqh) {
+ delete_desc (qh);
+ return -ENOMEM;
+ }
+ upriv->next_qh = nqh;
+ queue_dbg("new next qh %p",nqh);
+ }
+ }
+ else {
+ bpriv = (urb_priv_t*)bulk_urb->hcpriv;
+ qh = bpriv->bottom_qh; // re-use bottom qh and next qh
+ nqh = bpriv->next_qh;
+ upriv->next_qh=nqh;
+ bpriv->next_queued_urb=urb;
+ upriv->prev_queued_urb=bulk_urb;
+ }
+
+ queue_dbg("uhci_submit_bulk: qh=%p, nqh=%p\n",bqh,nqh);
+
+ if (urb->transfer_flags & USB_QUEUE_BULK) {
+ alloc_qh (&bqh); // "bottom" QH,
+
+ if (!bqh) {
+ if (!bulk_urb) {
+ delete_desc(qh);
+ delete_desc(nqh);
+ }
+ return -ENOMEM;
+ }
+ bqh->hw.qh.element = UHCI_PTR_TERM;
+ bqh->hw.qh.element = virt_to_bus(nqh)|UHCI_PTR_QH;
+ upriv->bottom_qh = bqh;
+ queue_dbg("uhci_submit_bulk: new bqh %p\n",bqh);
+ }
+
/* The "pipe" thing contains the destination in bits 8--18. */
destination = (pipe & PIPE_DEVEP_MASK) | usb_packetid (pipe);
@@ -744,7 +820,6 @@
/* Build the TDs for the bulk request */
len = urb->transfer_buffer_length;
data = urb->transfer_buffer;
- dbg("uhci_submit_bulk_urb: pipe %x, len %d", pipe, len);
do { // TBD: Really allow zero-length packets?
int pktsze = len;
@@ -770,54 +845,149 @@
if (!len)
td->hw.td.status |= TD_CTRL_IOC; // last one generates INT
- //dbg("insert td %p, len %i",td,pktsze);
insert_td (s, qh, td, UHCI_PTR_DEPTH * depth_first);
-
- /* Alternate Data0/1 (start with Data0) */
usb_dotoggle (urb->dev, usb_pipeendpoint (pipe), usb_pipeout (pipe));
+
} while (len > 0);
list_add (&qh->desc_list, &urb_priv->desc_list);
+ if (urb->transfer_flags & USB_QUEUE_BULK) {
+ qh->hw.qh.element&=~UHCI_PTR_TERM;
+ append_qh(s, td, bqh, UHCI_PTR_DEPTH * depth_first);
+ }
+
urb->status = -EINPROGRESS;
- queue_urb (s, urb);
+ queue_urb_unlocked (s, urb);
- qh->hw.qh.element&=~UHCI_PTR_TERM;
+ qh->hw.qh.element &= ~UHCI_PTR_TERM;
- insert_qh (s, s->chain_end, qh, 0); // insert before end marker
+ if (!bulk_urb) {
+ if (urb->transfer_flags & USB_QUEUE_BULK) {
+ spin_lock (&s->td_lock); // both QHs in one go
+ insert_qh (s, s->chain_end, qh, 0); // Main QH
+ insert_qh (s, s->chain_end, nqh, 0); // Helper QH
+ spin_unlock (&s->td_lock);
+ }
+ else
+ insert_qh (s, s->chain_end, qh, 0);
+ }
+
//uhci_show_queue(s->bulk_chain);
-
- dbg("uhci_submit_bulk_urb: exit");
+ //dbg("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 (urb_t *urb)
+_static void uhci_clean_iso_step1(uhci_t *s, urb_priv_t *urb_priv)
{
- uhci_t *s;
- uhci_desc_t *qh;
+ struct list_head *p;
uhci_desc_t *td;
- urb_priv_t *urb_priv;
- unsigned long flags=0;
+ for (p = urb_priv->desc_list.next; p != &urb_priv->desc_list; p = p->next) {
+ td = list_entry (p, uhci_desc_t, desc_list);
+ unlink_td (s, td, 1);
+ }
+}
+/*-------------------------------------------------------------------*/
+_static void uhci_clean_iso_step2(uhci_t *s, urb_priv_t *urb_priv)
+{
struct list_head *p;
+ uhci_desc_t *td;
- if (!urb || !urb->dev) // you never know...
- return -EINVAL;
+ while ((p = urb_priv->desc_list.next) != &urb_priv->desc_list) {
+ td = list_entry (p, uhci_desc_t, desc_list);
+ list_del (p);
+ delete_desc (td);
+ }
+}
+/*-------------------------------------------------------------------*/
+// mode: 0: unlink + no deletion mark, 1: regular (unlink/delete-mark), 2: don't unlink
+// looks a bit complicated because of all the bulk queueing goodies
+
+_static void uhci_clean_transfer (uhci_t *s, urb_t *urb, uhci_desc_t *qh, int mode)
+{
+ uhci_desc_t *bqh, *nqh, *prevqh;
+ int now;
+ urb_priv_t *priv=(urb_priv_t*)urb->hcpriv;
- s = (uhci_t*) urb->dev->bus->hcpriv; // get pointer to uhci struct
+ now=UHCI_GET_CURRENT_FRAME(s);
- if (usb_pipedevice (urb->pipe) == s->rh.devnum)
- return rh_unlink_urb (urb);
+ dbg("clean transfer urb %p, qh %p, mode %i",urb,qh,mode);
+ bqh=priv->bottom_qh;
- if (!urb->hcpriv) // you never know...
- return -EINVAL;
+ if (!priv->next_queued_urb) { // no more appended bulk queues
- //dbg("unlink_urb called %p",urb);
+ if (mode != 2)
+ unlink_qh (s, qh);
+
+ if (priv->prev_queued_urb) {
+ urb_priv_t* ppriv=(urb_priv_t*)priv->prev_queued_urb->hcpriv;
+
+ ppriv->bottom_qh = priv->bottom_qh;
+ ppriv->next_queued_urb = NULL;
+ }
+ else if (bqh) { // queue dead
+ nqh=priv->next_qh;
+
+ if (mode != 2)
+ unlink_qh(s, nqh);
+
+ if (mode) {
+ nqh->last_used = bqh->last_used = now;
+ list_add_tail (&nqh->horizontal, &s->free_desc);
+ list_add_tail (&bqh->horizontal, &s->free_desc);
+ }
+ }
+ }
+ else { // there are queued urbs following
+ urb_t *nurb;
+ unsigned long flags;
+
+ nurb=priv->next_queued_urb;
+ spin_lock_irqsave (&s->qh_lock, flags);
+
+ if (!priv->prev_queued_urb) { // top
+ if (mode !=2) {
+ prevqh = list_entry (qh->horizontal.prev, uhci_desc_t, horizontal);
+ prevqh->hw.qh.head = virt_to_bus(bqh) | UHCI_PTR_QH;
+ queue_dbg ("TOP relink of %p to %p-%p",qh,prevqh,bqh);
+
+ list_del (&qh->horizontal);
+ list_add (&bqh->horizontal, &prevqh->horizontal);
+ }
+ }
+ else { //intermediate
+ urb_priv_t* ppriv=(urb_priv_t*)priv->prev_queued_urb->hcpriv;
+ uhci_desc_t * bnqh;
+
+ bnqh=list_entry (&((urb_priv_t*)(nurb->hcpriv))->desc_list.next, uhci_desc_t, desc_list);
+ ppriv->bottom_qh=bnqh;
+ ppriv->next_queued_urb=nurb;
+
+ if (mode!=2) {
+ prevqh = list_entry (ppriv->desc_list.next, uhci_desc_t, desc_list);
+ prevqh->hw.qh.head = virt_to_bus(bqh) | UHCI_PTR_QH;
+ queue_dbg ("IM relink of %p to %p-%p",qh,prevqh,bqh);
+ }
+ }
+ mb();
+ spin_unlock_irqrestore (&s->qh_lock, flags);
+ ((urb_priv_t*)nurb->hcpriv)->prev_queued_urb=priv->prev_queued_urb;
+ }
+
+ if (mode) {
+ qh->last_used = now;
+ list_add_tail (&qh->horizontal, &s->free_desc); // mark for later deletion
+ }
+}
+/*-------------------------------------------------------------------*/
+// unlinks an urb by dequeuing its qh, waits some frames and forgets it
+_static int uhci_unlink_urb_sync (uhci_t *s, urb_t *urb)
+{
+ uhci_desc_t *qh;
+ urb_priv_t *urb_priv;
+ unsigned long flags=0;
spin_lock_irqsave (&s->urb_list_lock, flags);
@@ -833,32 +1003,22 @@
switch (usb_pipetype (urb->pipe)) {
case PIPE_ISOCHRONOUS:
case PIPE_INTERRUPT:
- for (p = urb_priv->desc_list.next; p != &urb_priv->desc_list; p = p->next) {
- td = list_entry (p, uhci_desc_t, desc_list);
- unlink_td (s, td, 1);
- }
- // wait at least 1 Frame
- uhci_wait_ms(1);
- while ((p = urb_priv->desc_list.next) != &urb_priv->desc_list) {
- td = list_entry (p, uhci_desc_t, desc_list);
- list_del (p);
- delete_desc (td);
- }
+ uhci_clean_iso_step1(s, urb_priv);
+ uhci_wait_ms(1);
+ uhci_clean_iso_step2(s, urb_priv);
break;
case PIPE_BULK:
case PIPE_CONTROL:
qh = list_entry (urb_priv->desc_list.next, uhci_desc_t, desc_list);
-
- unlink_qh (s, qh); // remove this qh from qh-list
- qh->last_used=UHCI_GET_CURRENT_FRAME(s);
- list_add_tail (&qh->horizontal, &s->free_desc); // mark for later deletion
- // wait at least 1 Frame
+ spin_lock_irqsave (&s->urb_list_lock, flags);
+ uhci_clean_transfer(s, urb, qh, 1);
+ spin_unlock_irqrestore (&s->urb_list_lock, flags);
uhci_wait_ms(1);
}
#ifdef DEBUG_SLAB
- kmem_cache_free(urb_priv_kmem, urb->hcpriv);
+ kmem_cache_free (urb_priv_kmem, urb->hcpriv);
#else
kfree (urb->hcpriv);
#endif
@@ -874,6 +1034,147 @@
return 0;
}
/*-------------------------------------------------------------------*/
+// async unlink_urb completion/cleanup work
+// has to be protected by urb_list_lock!
+// features: if set in transfer_flags, the resulting status of the killed
+// transaction is not overwritten
+
+_static void uhci_cleanup_unlink(uhci_t *s, int force)
+{
+ struct list_head *q;
+ urb_t *urb;
+ struct usb_device *dev;
+ int pipe,now;
+ urb_priv_t *urb_priv;
+
+ q=s->urb_unlinked.next;
+ now=UHCI_GET_CURRENT_FRAME(s);
+
+ while (q != &s->urb_unlinked) {
+
+ urb = list_entry (q, urb_t, urb_list);
+
+ urb_priv = (urb_priv_t*)urb->hcpriv;
+ q = urb->urb_list.next;
+
+ if (force ||
+ ((urb_priv->started != 0xffffffff) && (urb_priv->started != now))) {
+ async_dbg("async cleanup %p",urb);
+ switch (usb_pipetype (urb->pipe)) { // process descriptors
+ case PIPE_CONTROL:
+ process_transfer (s, urb, 2);
+ break;
+ case PIPE_BULK:
+ if (!s->avoid_bulk.counter)
+ process_transfer (s, urb, 2); // don't unlink (already done)
+ else
+ continue;
+ break;
+ case PIPE_ISOCHRONOUS:
+ process_iso (s, urb, 1); // force, don't unlink
+ break;
+ case PIPE_INTERRUPT:
+ process_interrupt (s, urb);
+ break;
+ }
+
+ if (!(urb->transfer_flags & USB_TIMEOUT_KILLED))
+ urb->status = -ECONNRESET; // mark as asynchronously killed
+
+ pipe = urb->pipe; // completion may destroy all...
+ dev = urb->dev;
+ urb_priv = urb->hcpriv;
+
+ if (urb->complete) {
+ spin_unlock(&s->urb_list_lock);
+ urb->complete ((struct urb *) urb);
+ spin_lock(&s->urb_list_lock);
+ }
+
+ if (!(urb->transfer_flags & USB_TIMEOUT_KILLED))
+ urb->status = -ENOENT; // now the urb is really dead
+
+ usb_dec_dev_use (dev);
+#ifdef DEBUG_SLAB
+ kmem_cache_free (urb_priv_kmem, urb_priv);
+#else
+ kfree (urb_priv);
+#endif
+ switch (usb_pipetype (pipe)) {
+ case PIPE_ISOCHRONOUS:
+ case PIPE_INTERRUPT:
+ uhci_clean_iso_step2(s, urb_priv);
+ break;
+ }
+ list_del (&urb->urb_list);
+ }
+ }
+}
+
+/*-------------------------------------------------------------------*/
+_static int uhci_unlink_urb_async (uhci_t *s,urb_t *urb)
+{
+ uhci_desc_t *qh;
+ urb_priv_t *urb_priv;
+
+ async_dbg("unlink_urb_async called %p",urb);
+
+ if (urb->status == -EINPROGRESS) {
+ ((urb_priv_t*)urb->hcpriv)->started = ~0;
+ dequeue_urb (s, urb);
+ list_add_tail (&urb->urb_list, &s->urb_unlinked); // store urb
+
+ s->unlink_urb_done = 1;
+
+ urb->status = -ECONNABORTED; // mark urb as "waiting to be killed"
+ urb_priv = (urb_priv_t*)urb->hcpriv;
+
+ switch (usb_pipetype (urb->pipe)) {
+ case PIPE_ISOCHRONOUS:
+ case PIPE_INTERRUPT:
+ uhci_clean_iso_step1 (s, urb_priv);
+ break;
+
+ case PIPE_BULK:
+ case PIPE_CONTROL:
+ qh = list_entry (urb_priv->desc_list.next, uhci_desc_t, desc_list);
+ uhci_clean_transfer (s, urb, qh, 0);
+ break;
+ }
+ ((urb_priv_t*)urb->hcpriv)->started = UHCI_GET_CURRENT_FRAME(s);
+ }
+
+ return -EINPROGRESS;
+}
+/*-------------------------------------------------------------------*/
+_static int uhci_unlink_urb (urb_t *urb)
+{
+ uhci_t *s;
+ unsigned long flags=0;
+ dbg("uhci_unlink_urb called for %p",urb);
+ if (!urb || !urb->dev) // you never know...
+ return -EINVAL;
+
+ s = (uhci_t*) urb->dev->bus->hcpriv;
+
+ if (usb_pipedevice (urb->pipe) == s->rh.devnum)
+ return rh_unlink_urb (urb);
+
+ if (!urb->hcpriv)
+ return -EINVAL;
+
+ if (urb->transfer_flags & USB_ASYNC_UNLINK) {
+ int ret;
+
+ spin_lock_irqsave (&s->urb_list_lock, flags);
+ ret = uhci_unlink_urb_async(s, urb);
+ spin_unlock_irqrestore (&s->urb_list_lock, flags);
+ return ret;
+ }
+ else
+ return uhci_unlink_urb_sync(s, urb);
+}
+/*-------------------------------------------------------------------*/
// In case of ASAP iso transfer, search the URB-list for already queued URBs
// for this EP and calculate the earliest start frame for the new
// URB (easy seamless URB continuation!)
@@ -886,9 +1187,9 @@
unsigned long flags;
spin_lock_irqsave (&s->urb_list_lock, flags);
- p=s->urb_list.next;
+ p=s->urb_list.prev;
- for (; p != &s->urb_list; p = p->next) {
+ for (; p != &s->urb_list; p = p->prev) {
u = list_entry (p, urb_t, urb_list);
// look for pending URBs with identical pipe handle
// works only because iso doesn't toggle the data bit!
@@ -906,8 +1207,7 @@
spin_unlock_irqrestore(&s->urb_list_lock, flags);
- return ret; // no previous urb found
-
+ return ret;
}
/*-------------------------------------------------------------------*/
// adjust start_frame according to scheduling constraints (ASAP etc)
@@ -940,35 +1240,7 @@
info("iso_find_start: gap in seamless isochronous scheduling");
dbg("iso_find_start: now %u start_frame %u number_of_packets %u pipe 0x%08x",
now, urb->start_frame, urb->number_of_packets, urb->pipe);
-// The following code is only for debugging purposes...
-#if 0
- {
- uhci_t *s = (uhci_t*) urb->dev->bus->hcpriv;
- struct list_head *p;
- urb_t *u;
- int a = -1, b = -1;
- unsigned long flags;
-
- spin_lock_irqsave (&s->urb_list_lock, flags);
- p=s->urb_list.next;
-
- for (; p != &s->urb_list; p = p->next) {
- u = list_entry (p, urb_t, urb_list);
- if (urb->dev != u->dev)
- continue;
- dbg("urb: pipe 0x%08x status %d start_frame %u number_of_packets %u",
- u->pipe, u->status, u->start_frame, u->number_of_packets);
- if (!usb_pipeisoc (u->pipe))
- continue;
- if (a == -1)
- a = u->start_frame;
- b = (u->start_frame + u->number_of_packets - 1) & 1023;
- }
- spin_unlock_irqrestore(&s->urb_list_lock, flags);
- }
-#endif
urb->start_frame = (now + 5) & 1023; // 5ms setup should be enough //FIXME!
- //return -EAGAIN; //FIXME
}
}
}
@@ -996,7 +1268,7 @@
/*-------------------------------------------------------------------*/
// submits USB interrupt (ie. polling ;-)
// ASAP-flag set implicitely
-// if period==0, the the transfer is only done once (usb_scsi need this...)
+// if period==0, the the transfer is only done once
_static int uhci_submit_int_urb (urb_t *urb)
{
@@ -1005,12 +1277,9 @@
int nint, n, ret;
uhci_desc_t *td;
int status, destination;
- int now;
int info;
unsigned int pipe = urb->pipe;
- //dbg("SUBMIT INT");
-
if (urb->interval < 0 || urb->interval >= 256)
return -EINVAL;
@@ -1029,8 +1298,7 @@
dbg("Rounded interval to %i, chain %i", urb->interval, nint);
- now = UHCI_GET_CURRENT_FRAME (s) & 1023;
- urb->start_frame = now; // remember start frame, just in case...
+ urb->start_frame = UHCI_GET_CURRENT_FRAME (s) & 1023; // remember start frame, just in case...
urb->number_of_packets = 1;
@@ -1062,13 +1330,6 @@
usb_dotoggle (urb->dev, usb_pipeendpoint (pipe), usb_pipeout (pipe));
-#if 0
- td = tdm[urb->number_of_packets];
- fill_td (td, TD_CTRL_IOC, 0, 0);
- insert_td_horizontal (s, s->iso_td[(urb->start_frame + (urb->number_of_packets) * urb->interval + 1) & 1023], td);
- list_add_tail (&td->desc_list, &urb_priv->desc_list);
-#endif
-
return 0;
}
/*-------------------------------------------------------------------*/
@@ -1086,11 +1347,11 @@
__save_flags(flags);
__cli(); // Disable IRQs to schedule all ISO-TDs in time
ret = iso_find_start (urb); // adjusts urb->start_frame for later use
-
+
if (ret)
goto err;
- tdm = (uhci_desc_t **) kmalloc (urb->number_of_packets * sizeof (uhci_desc_t*), in_interrupt ()? GFP_ATOMIC : GFP_KERNEL);
+ tdm = (uhci_desc_t **) kmalloc (urb->number_of_packets * sizeof (uhci_desc_t*), KMALLOC_FLAG);
if (!tdm) {
ret = -ENOMEM;
@@ -1105,14 +1366,16 @@
tdm[n] = 0;
continue;
}
- #ifdef ISO_SANITY_CHECK
+
if(urb->iso_frame_desc[n].length > maxsze) {
+#ifdef ISO_SANITY_CHECK
err("submit_iso: urb->iso_frame_desc[%d].length(%d)>%d",n , urb->iso_frame_desc[n].length, maxsze);
tdm[n] = 0;
ret=-EINVAL;
goto inval;
+#endif
}
- #endif
+
ret = alloc_td (&td, UHCI_PTR_DEPTH);
inval:
if (ret) {
@@ -1120,7 +1383,7 @@
for (i = 0; i < n; n++)
if (tdm[i])
- kfree (tdm[i]);
+ delete_desc(tdm[i]);
kfree (tdm);
goto err;
}
@@ -1165,15 +1428,16 @@
}
/*-------------------------------------------------------------------*/
-_static int search_dev_ep (uhci_t *s, urb_t *urb)
+// returns: 0 (no transfer queued), urb* (this urb already queued)
+
+_static urb_t* search_dev_ep (uhci_t *s, urb_t *urb)
{
- unsigned long flags;
struct list_head *p;
urb_t *tmp;
unsigned int mask = usb_pipecontrol(urb->pipe) ? (~USB_DIR_IN) : (~0);
dbg("search_dev_ep:");
- spin_lock_irqsave (&s->urb_list_lock, flags);
+
p=s->urb_list.next;
for (; p != &s->urb_list; p = p->next) {
@@ -1181,13 +1445,12 @@
dbg("urb: %p", tmp);
// we can accept this urb if it is not queued at this time
// or if non-iso transfer requests should be scheduled for the same device and pipe
- if ((!usb_pipeisoc(urb->pipe) && tmp->dev == urb->dev && !((tmp->pipe ^ urb->pipe) & mask)) ||
+ if ((!usb_pipeisoc(urb->pipe) && (tmp->dev == urb->dev) && !((tmp->pipe ^ urb->pipe) & mask)) ||
(urb == tmp)) {
- spin_unlock_irqrestore (&s->urb_list_lock, flags);
- return 1; // found another urb already queued for processing
+ return tmp; // found another urb already queued for processing
}
}
- spin_unlock_irqrestore (&s->urb_list_lock, flags);
+
return 0;
}
/*-------------------------------------------------------------------*/
@@ -1196,60 +1459,93 @@
uhci_t *s;
urb_priv_t *urb_priv;
int ret = 0;
-
+ unsigned long flags;
+ urb_t *bulk_urb=NULL;
+
if (!urb->dev || !urb->dev->bus)
return -ENODEV;
s = (uhci_t*) urb->dev->bus->hcpriv;
//dbg("submit_urb: %p type %d",urb,usb_pipetype(urb->pipe));
-
+
+ if (!s->running)
+ return -ENODEV;
+
if (usb_pipedevice (urb->pipe) == s->rh.devnum)
return rh_submit_urb (urb); /* virtual root hub */
usb_inc_dev_use (urb->dev);
- if (search_dev_ep (s, urb)) {
- usb_dec_dev_use (urb->dev);
- return -ENXIO; // urb already queued
+ spin_lock_irqsave (&s->urb_list_lock, flags);
+
+ bulk_urb = search_dev_ep (s, urb);
+
+ if (bulk_urb) {
+ queue_dbg("found bulk urb %p\n",bulk_urb);
+
+ if ((usb_pipetype (urb->pipe) != PIPE_BULK) ||
+ ((usb_pipetype (urb->pipe) == PIPE_BULK) &&
+ (!(urb->transfer_flags & USB_QUEUE_BULK) || !(bulk_urb->transfer_flags & USB_QUEUE_BULK)))) {
+ spin_unlock_irqrestore (&s->urb_list_lock, flags);
+ usb_dec_dev_use (urb->dev);
+ err("ENXIO1 %08x, flags %x, urb %p, burb %p",urb->pipe,urb->transfer_flags,urb,bulk_urb);
+ return -ENXIO; // urb already queued
+ }
}
#ifdef DEBUG_SLAB
- urb_priv = kmem_cache_alloc(urb_priv_kmem, in_interrupt ()? SLAB_ATOMIC : SLAB_KERNEL);
+ urb_priv = kmem_cache_alloc(urb_priv_kmem, SLAB_FLAG);
#else
- urb_priv = kmalloc (sizeof (urb_priv_t), in_interrupt ()? GFP_ATOMIC : GFP_KERNEL);
+ urb_priv = kmalloc (sizeof (urb_priv_t), KMALLOC_FLAG);
#endif
if (!urb_priv) {
usb_dec_dev_use (urb->dev);
+ spin_unlock_irqrestore (&s->urb_list_lock, flags);
return -ENOMEM;
}
urb->hcpriv = urb_priv;
INIT_LIST_HEAD (&urb_priv->desc_list);
- urb_priv->short_control_packet=0;
+ urb_priv->short_control_packet = 0;
dbg("submit_urb: scheduling %p", urb);
-
- switch (usb_pipetype (urb->pipe)) {
- case PIPE_ISOCHRONOUS:
- ret = uhci_submit_iso_urb (urb);
- break;
- case PIPE_INTERRUPT:
- ret = uhci_submit_int_urb (urb);
- break;
- case PIPE_CONTROL:
- //dump_urb (urb);
- ret = uhci_submit_control_urb (urb);
- break;
- case PIPE_BULK:
- ret = uhci_submit_bulk_urb (urb);
- break;
- default:
- ret = -EINVAL;
+ urb_priv->next_queued_urb = NULL;
+ urb_priv->prev_queued_urb = NULL;
+ urb_priv->bottom_qh = NULL;
+ urb_priv->next_qh = NULL;
+
+ if (usb_pipetype (urb->pipe) == PIPE_BULK) {
+
+ if (bulk_urb) {
+ while (((urb_priv_t*)bulk_urb->hcpriv)->next_queued_urb) // find last queued bulk
+ bulk_urb=((urb_priv_t*)bulk_urb->hcpriv)->next_queued_urb;
+
+ ((urb_priv_t*)bulk_urb->hcpriv)->next_queued_urb=urb;
+ }
+ atomic_inc (&s->avoid_bulk);
+ ret = uhci_submit_bulk_urb (urb, bulk_urb);
+ atomic_dec (&s->avoid_bulk);
+ spin_unlock_irqrestore (&s->urb_list_lock, flags);
+ }
+ else {
+ spin_unlock_irqrestore (&s->urb_list_lock, flags);
+ switch (usb_pipetype (urb->pipe)) {
+ case PIPE_ISOCHRONOUS:
+ ret = uhci_submit_iso_urb (urb);
+ break;
+ case PIPE_INTERRUPT:
+ ret = uhci_submit_int_urb (urb);
+ break;
+ case PIPE_CONTROL:
+ ret = uhci_submit_control_urb (urb);
+ break;
+ default:
+ ret = -EINVAL;
+ }
}
dbg("submit_urb: scheduled with ret: %d", ret);
-
if (ret != 0) {
usb_dec_dev_use (urb->dev);
#ifdef DEBUG_SLAB
@@ -1262,41 +1558,43 @@
return 0;
}
-#ifdef USE_RECLAMATION_LOOP
-// Removes bandwidth reclamation if URB idles too long
-void check_idling_urbs(uhci_t *s)
+
+// Checks for URB timeout and removes bandwidth reclamation
+// if URB idles too long
+_static void uhci_check_timeouts(uhci_t *s)
{
struct list_head *p,*p2;
urb_t *urb;
int type;
- //dbg("check_idling_urbs: enter i:%d",in_interrupt());
-
- spin_lock (&s->urb_list_lock);
p = s->urb_list.prev;
while (p != &s->urb_list) {
+ urb_priv_t *hcpriv;
+
p2 = p;
p = p->prev;
- urb=list_entry (p2, urb_t, urb_list);
- type=usb_pipetype (urb->pipe);
+ urb = list_entry (p2, urb_t, urb_list);
+ type = usb_pipetype (urb->pipe);
-#if 0
- err("URB timers: %li now: %li %i\n",
- ((urb_priv_t*)urb->hcpriv)->started +IDLE_TIMEOUT, jiffies,
- type);
-#endif
- if (((type == PIPE_BULK) || (type == PIPE_CONTROL)) &&
- (((urb_priv_t*)urb->hcpriv)->use_loop) &&
- ((((urb_priv_t*)urb->hcpriv)->started +IDLE_TIMEOUT) < jiffies))
- disable_desc_loop(s,urb);
+ hcpriv = (urb_priv_t*)urb->hcpriv;
+
+ if ( urb->timeout &&
+ ((hcpriv->started + urb->timeout) < jiffies)) {
+ urb->transfer_flags |= USB_TIMEOUT_KILLED | USB_ASYNC_UNLINK;
+ async_dbg("uhci_check_timeout: timeout for %p",urb);
+ uhci_unlink_urb_async(s, urb);
+ }
+#ifdef CONFIG_USB_UHCI_HIGH_BANDWIDTH
+ else if (((type == PIPE_BULK) || (type == PIPE_CONTROL)) &&
+ (hcpriv->use_loop) &&
+ ((hcpriv->started + IDLE_TIMEOUT) < jiffies))
+ disable_desc_loop(s, urb);
+#endif
}
- spin_unlock (&s->urb_list_lock);
-
- //dbg("check_idling_urbs: finished");
}
-#endif
+
/*-------------------------------------------------------------------
Virtual Root Hub
-------------------------------------------------------------------*/
@@ -1396,7 +1694,6 @@
dbg("Root-Hub INT complete: port1: %x port2: %x data: %x",
inw (io_addr + USBPORTSC1), inw (io_addr + USBPORTSC2), data);
urb->complete (urb);
-
}
return 0;
}
@@ -1411,10 +1708,6 @@
urb_t *urb = (urb_t*) ptr;
uhci_t *uhci = urb->dev->bus->hcpriv;
-#ifdef USE_RECLAMATION_LOOP
- check_idling_urbs(uhci);
-#endif
-
if (uhci->rh.send) {
len = rh_send_irq (urb);
if (len > 0) {
@@ -1427,7 +1720,9 @@
}
/*-------------------------------------------------------------------------*/
-/* Root Hub INTs are polled by this timer */
+/* Root Hub INTs are polled by this timer, polling interval 20ms */
+/* This time is also used for URB-timeout checking */
+
_static int rh_init_int_timer (urb_t *urb)
{
uhci_t *uhci = urb->dev->bus->hcpriv;
@@ -1436,7 +1731,7 @@
init_timer (&uhci->rh.rh_int_timer);
uhci->rh.rh_int_timer.function = rh_int_timer_do;
uhci->rh.rh_int_timer.data = (unsigned long) urb;
- uhci->rh.rh_int_timer.expires = jiffies + (HZ * (urb->interval < 30 ? 30 : urb->interval)) / 1000;
+ uhci->rh.rh_int_timer.expires = jiffies + (HZ * 20) / 1000;
add_timer (&uhci->rh.rh_int_timer);
return 0;
@@ -1640,7 +1935,6 @@
stat = -EPIPE;
}
-
dbg("Root-Hub stat port1: %x port2: %x",
inw (io_addr + USBPORTSC1), inw (io_addr + USBPORTSC2));
@@ -1716,13 +2010,19 @@
p = s->urb_list.prev;
while (p != &s->urb_list) {
p2 = p;
- p = p->prev;
+ p = p->prev ;
urb = list_entry (p2, urb_t, urb_list);
- dbg("urb: %p", urb);
+ dbg("urb: %p, dev %p, %p", urb, usb_dev,urb->dev);
+
+ //urb->transfer_flags |=USB_ASYNC_UNLINK;
+
if (remove_all || (usb_dev == urb->dev)) {
+ spin_unlock_irqrestore (&s->urb_list_lock, flags);
warn("forced removing of queued URB %p due to disconnect",urb);
uhci_unlink_urb(urb);
- urb->dev = NULL; // avoid further processing of this URB
+ urb->dev = NULL; // avoid further processing of this UR
+ spin_lock_irqsave (&s->urb_list_lock, flags);
+ p = s->urb_list.prev;
}
}
spin_unlock_irqrestore (&s->urb_list_lock, flags);
@@ -1732,13 +2032,11 @@
{
uhci_t *s;
- dbg("uhci_free_dev");
if(!usb_dev || !usb_dev->bus || !usb_dev->bus->hcpriv)
return -EINVAL;
- s=(uhci_t*) usb_dev->bus->hcpriv;
-
+ s=(uhci_t*) usb_dev->bus->hcpriv;
uhci_unlink_urbs(s, usb_dev, 0);
return 0;
@@ -1769,12 +2067,10 @@
* 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.
+ * mode: 0: QHs already unlinked
*/
-_static int process_transfer (uhci_t *s, urb_t *urb)
+_static int process_transfer (uhci_t *s, urb_t *urb, int mode)
{
int ret = 0;
urb_priv_t *urb_priv = urb->hcpriv;
@@ -1784,25 +2080,21 @@
uhci_desc_t *desc= list_entry (urb_priv->desc_list.prev, uhci_desc_t, desc_list);
uhci_desc_t *last_desc = list_entry (desc->vertical.prev, uhci_desc_t, vertical);
int data_toggle = usb_gettoggle (urb->dev, usb_pipeendpoint (urb->pipe), usb_pipeout (urb->pipe)); // save initial data_toggle
-
-
- // extracted and remapped info from TD
- int maxlength;
+ int maxlength; // extracted and remapped info from TD
int actual_length;
int status = 0;
- dbg("process_transfer: urb contains bulk/control request");
-
+ //dbg("process_transfer: urb contains bulk/control request");
/* if the status phase has been retriggered and the
queue is empty or the last status-TD is inactive, the retriggered
status stage is completed
*/
-#if 1
+
if (urb_priv->short_control_packet &&
((qh->hw.qh.element == UHCI_PTR_TERM) ||(!(last_desc->hw.td.status & TD_CTRL_ACTIVE))))
goto transfer_finished;
-#endif
+
urb->actual_length=0;
for (; p != &qh->vertical; p = p->next) {
@@ -1810,22 +2102,20 @@
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;
+
+ actual_length = (desc->hw.td.status + 1) & 0x7ff; // extract transfer parameters from TD
maxlength = (((desc->hw.td.info >> 21) & 0x7ff) + 1) & 0x7ff;
status = uhci_map_status (uhci_status_bits (desc->hw.td.status), usb_pipeout (urb->pipe));
- // see if EP is stalled
- if (status == -EPIPE) {
+ if (status == -EPIPE) { // see if EP is stalled
// set up stalled condition
usb_endpoint_halt (urb->dev, usb_pipeendpoint (urb->pipe), usb_pipeout (urb->pipe));
}
- // if any error occured stop processing of further TDs
- if (status != 0) {
+ if (status != 0) { // if any error occured stop processing of further TDs
// only set ret if status returned an error
- uhci_show_td (desc);
+ if (status != -EPIPE)
+ uhci_show_td (desc);
ret = status;
urb->error_count++;
break;
@@ -1833,11 +2123,6 @@
else if ((desc->hw.td.info & 0xff) != USB_PID_SETUP)
urb->actual_length += actual_length;
-#if 0
- // if (i++==0)
- uhci_show_td (desc); // show first TD of each transfer
-#endif
-
// got less data than requested
if ( (actual_length < maxlength)) {
if (urb->transfer_flags & USB_DISABLE_SPD) {
@@ -1852,8 +2137,8 @@
qh->hw.qh.element = virt_to_bus (last_desc); // re-trigger status stage
dbg("short packet during control transfer, retrigger status stage @ %p",last_desc);
- uhci_show_td (desc);
- uhci_show_td (last_desc);
+ //uhci_show_td (desc);
+ //uhci_show_td (last_desc);
urb_priv->short_control_packet=1;
return 0;
}
@@ -1864,34 +2149,24 @@
}
data_toggle = uhci_toggle (desc->hw.td.info);
- //dbg("process_transfer: len:%d status:%x mapped:%x toggle:%d", actual_length, desc->hw.td.status,status, data_toggle);
+ queue_dbg("process_transfer: len:%d status:%x mapped:%x toggle:%d", actual_length, desc->hw.td.status,status, data_toggle);
}
+
usb_settoggle (urb->dev, usb_pipeendpoint (urb->pipe), usb_pipeout (urb->pipe), !data_toggle);
- transfer_finished:
- unlink_qh (s, qh);
- //delete_qh (s, qh);
- qh->last_used=UHCI_GET_CURRENT_FRAME(s);
- list_add_tail (&qh->horizontal, &s->free_desc); // mark for later deletion
+ transfer_finished:
+
+ uhci_clean_transfer(s, urb, qh, (mode==0?2:1));
urb->status = status;
-#ifdef USE_RECLAMATION_LOOP
+#ifdef CONFIG_USB_UHCI_HIGH_BANDWIDTH
disable_desc_loop(s,urb);
#endif
- dbg("process_transfer: urb %p, wanted len %d, len %d status %x err %d",
+ queue_dbg("process_transfer: (end) urb %p, wanted len %d, len %d status %x err %d",
urb,urb->transfer_buffer_length,urb->actual_length, urb->status, urb->error_count);
- //dbg("process_transfer: exit");
-#if 0
- if (urb->actual_length){
- char *uu;
- uu=urb->transfer_buffer;
- dbg("%x %x %x %x %x %x %x %x",
- *uu,*(uu+1),*(uu+2),*(uu+3),*(uu+4),*(uu+5),*(uu+6),*(uu+7));
- }
-#endif
return ret;
}
@@ -1934,15 +2209,13 @@
// if any error occured: ignore this td, and continue
if (status != 0) {
- uhci_show_td (desc);
+ //uhci_show_td (desc);
urb->error_count++;
goto recycle;
}
else
urb->actual_length = actual_length;
- // FIXME: SPD?
-
recycle:
if (urb->complete) {
//dbg("process_interrupt: calling completion, status %i",status);
@@ -1962,6 +2235,7 @@
desc->hw.td.info &= ~(1 << TD_TOKEN_TOGGLE);
if (status==0) {
+ ((urb_priv_t*)urb->hcpriv)->started=jiffies;
desc->hw.td.info |= (usb_gettoggle (urb->dev, usb_pipeendpoint (urb->pipe),
usb_pipeout (urb->pipe)) << TD_TOKEN_TOGGLE);
usb_dotoggle (urb->dev, usb_pipeendpoint (urb->pipe), usb_pipeout (urb->pipe));
@@ -1981,8 +2255,8 @@
return ret;
}
-
-_static int process_iso (uhci_t *s, urb_t *urb)
+// mode: 1: force processing, don't unlink tds (already unlinked)
+_static int process_iso (uhci_t *s, urb_t *urb, int mode)
{
int i;
int ret = 0;
@@ -1991,16 +2265,18 @@
uhci_desc_t *desc = list_entry (urb_priv->desc_list.prev, uhci_desc_t, desc_list);
dbg("urb contains iso request");
- if (desc->hw.td.status & TD_CTRL_ACTIVE)
+ if ((desc->hw.td.status & TD_CTRL_ACTIVE) && !mode)
return -EXDEV; // last TD not finished
urb->error_count = 0;
urb->actual_length = 0;
urb->status = 0;
+ dbg("process iso urb %p, %li, %i, %i, %i %08x",urb,jiffies,UHCI_GET_CURRENT_FRAME(s),
+ urb->number_of_packets,mode,desc->hw.td.status);
for (i = 0; p != &urb_priv->desc_list; p = p->next, i++) {
desc = list_entry (p, uhci_desc_t, desc_list);
-
+
//uhci_show_td(desc);
if (desc->hw.td.status & TD_CTRL_ACTIVE) {
// means we have completed the last TD, but not the TDs before
@@ -2013,7 +2289,8 @@
goto err;
}
- unlink_td (s, desc, 1);
+ if (!mode)
+ unlink_td (s, desc, 1);
if (urb->number_of_packets <= i) {
dbg("urb->number_of_packets (%d)<=(%d)", urb->number_of_packets, i);
@@ -2038,13 +2315,14 @@
urb->error_count++;
urb->status = urb->iso_frame_desc[i].status;
}
- dbg("process_iso: len:%d status:%x",
- urb->iso_frame_desc[i].length, urb->iso_frame_desc[i].status);
+ dbg("process_iso: %i: len:%d %08x status:%x",
+ i, urb->iso_frame_desc[i].actual_length, desc->hw.td.status,urb->iso_frame_desc[i].status);
delete_desc (desc);
list_del (p);
}
- dbg("process_iso: exit %i (%d)", i, ret);
+
+ dbg("process_iso: exit %i (%d), actual_len %i", i, ret,urb->actual_length);
return ret;
}
@@ -2056,15 +2334,20 @@
urb=list_entry (p, urb_t, urb_list);
- dbg("found queued urb: %p", urb);
+ //dbg("process_urb: found queued urb: %p", urb);
switch (usb_pipetype (urb->pipe)) {
case PIPE_CONTROL:
+ ret = process_transfer (s, urb, 1);
+ break;
case PIPE_BULK:
- ret = process_transfer (s, urb);
+ if (!s->avoid_bulk.counter)
+ ret = process_transfer (s, urb, 1);
+ else
+ return 0;
break;
case PIPE_ISOCHRONOUS:
- ret = process_iso (s, urb);
+ ret = process_iso (s, urb, 0);
break;
case PIPE_INTERRUPT:
ret = process_interrupt (s, urb);
@@ -2158,23 +2441,18 @@
if (status != 1) {
warn("interrupt, status %x, frame# %i", status,
UHCI_GET_CURRENT_FRAME(s));
- //uhci_show_queue(s->control_chain);
+
// remove host controller halted state
if ((status&0x20) && (s->running)) {
- // more to be done - check TDs for invalid entries
- // but TDs are only invalid if somewhere else is a (memory ?) problem
outw (USBCMD_RS | inw(io_addr + USBCMD), io_addr + USBCMD);
}
//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
*/
spin_lock (&s->urb_list_lock);
@@ -2186,18 +2464,23 @@
p2 = p;
p = p->prev;
process_urb (s, p2);
- if(s->unlink_urb_done)
- {
+ if (s->unlink_urb_done) {
s->unlink_urb_done=0;
goto restart;
}
}
- spin_unlock (&s->urb_list_lock);
- clean_descs(s,0);
+ if ((s->frame_counter & 63) == 0)
+ uhci_check_timeouts(s);
+ clean_descs(s,0);
+ uhci_cleanup_unlink(s, 0);
+
+ spin_unlock (&s->urb_list_lock);
+
+ s->frame_counter++;
outw (status, io_addr + USBSTS);
- dbg("done");
+ //dbg("uhci_interrupt: done");
}
_static void reset_hc (uhci_t *s)
@@ -2249,15 +2532,19 @@
{
struct usb_device *root_hub = s->bus->root_hub;
+ s->running = 0; // Don't allow submit_urb
+
if (root_hub)
usb_disconnect (&root_hub);
- uhci_unlink_urbs(s, 0, 1); // Forced unlink of remaining URBs
+ reset_hc (s);
+ wait_ms (1);
+ uhci_unlink_urbs (s, 0, 1); // Forced unlink of remaining URBs
+ uhci_cleanup_unlink (s, 1); // force cleanup of async killed URBs
+
usb_deregister_bus (s->bus);
- s->running = 0;
- reset_hc (s);
release_region (s->io_addr, s->io_size);
free_irq (s->irq, s);
usb_free_bus (s->bus);
@@ -2285,6 +2572,7 @@
return 0;
}
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,3,44)
_static int handle_pm_event (struct pm_dev *dev, pm_request_t rqst, void *data)
{
uhci_t *s = (uhci_t*) dev->data;
@@ -2301,6 +2589,7 @@
}
return 0;
}
+#endif
_static int __init alloc_uhci (struct pci_dev *dev, int irq, unsigned int io_addr, unsigned int io_size)
{
@@ -2315,14 +2604,17 @@
memset (s, 0, sizeof (uhci_t));
INIT_LIST_HEAD (&s->free_desc);
INIT_LIST_HEAD (&s->urb_list);
+ INIT_LIST_HEAD (&s->urb_unlinked);
spin_lock_init (&s->urb_list_lock);
spin_lock_init (&s->qh_lock);
spin_lock_init (&s->td_lock);
+ atomic_set(&s->avoid_bulk, 0);
s->irq = -1;
s->io_addr = io_addr;
s->io_size = io_size;
s->next = devs; //chain new uhci device into global list
-
+ s->frame_counter = 0;
+
bus = usb_alloc_bus (&uhci_device_operations);
if (!bus) {
kfree (s);
@@ -2389,11 +2681,11 @@
//chain new uhci device into global list
devs = s;
-
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,3,44)
pmdev = pm_register(PM_PCI_DEV, PM_PCI_ID(dev), handle_pm_event);
if (pmdev)
pmdev->data = s;
-
+#endif
return 0;
}
@@ -2407,7 +2699,7 @@
unsigned int io_addr = dev->resource[i].start;
unsigned int io_size =
dev->resource[i].end - dev->resource[i].start + 1;
- if (!(dev->resource[i].flags & IORESOURCE_IO))
+ if (!(dev->resource[i].flags & 1))
continue;
#else
unsigned int io_addr = dev->base_address[i];
@@ -2422,6 +2714,10 @@
break;
/* disable legacy emulation */
pci_write_config_word (dev, USBLEGSUP, USBLEGSUP_DEFAULT);
+ if(dev->vendor==0x8086) {
+ info("Intel USB controller: setting latency timer to %d", UHCI_LATENCY_TIMER);
+ pci_write_config_byte(dev, PCI_LATENCY_TIMER, UHCI_LATENCY_TIMER);
+ }
return alloc_uhci(dev, dev->irq, io_addr, io_size);
}
return -1;
@@ -2452,6 +2748,9 @@
#endif
info(VERSTR);
+#ifdef CONFIG_USB_UHCI_HIGH_BANDWIDTH
+ info("High bandwidth mode enabled");
+#endif
for (;;) {
dev = pci_find_class (PCI_CLASS_SERIAL_USB << 8, dev);
if (!dev)
@@ -2506,7 +2805,9 @@
void cleanup_module (void)
{
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,3,44)
pm_unregister_all (handle_pm_event);
+#endif
uhci_cleanup ();
}
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)