!C99Shell v. 1.0 pre-release build #13!

Software: Apache/2.0.54 (Unix) mod_perl/1.99_09 Perl/v5.8.0 mod_ssl/2.0.54 OpenSSL/0.9.7l DAV/2 FrontPage/5.0.2.2635 PHP/4.4.0 mod_gzip/2.0.26.1a 

uname -a: Linux snow.he.net 4.4.276-v2-mono-1 #1 SMP Wed Jul 21 11:21:17 PDT 2021 i686 

uid=99(nobody) gid=98(nobody) groups=98(nobody) 

Safe-mode: OFF (not secure)

/usr/src/linux-2.4.18-xfs-1.1/drivers/usb/   drwxr-xr-x
Free 318.38 GB of 458.09 GB (69.5%)
Home    Back    Forward    UPDIR    Refresh    Search    Buffer    Encoder    Tools    Proc.    FTP brute    Sec.    SQL    PHP-code    Update    Feedback    Self remove    Logout    


Viewing file:     usb-uhci.c (83.95 KB)      -rw-r--r--
Select action/file-type:
(+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
/* 
 * Universal Host Controller Interface driver for USB (take II).
 *
 * (c) 1999-2001 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)
 * (c) 2000      Yggdrasil Computing, Inc. (port of new PCI interface support
 *               from usb-ohci.c by Adam Richter, adam@yggdrasil.com).
 * (C) 2000      David Brownell, david-b@pacbell.net (usb-ohci.c)
 *          
 * HW-initalization based on material of
 *
 * (C) Copyright 1999 Linus Torvalds
 * (C) Copyright 1999 Johannes Erdfelt
 * (C) Copyright 1999 Randy Dunlap
 * (C) Copyright 1999 Gregory P. Smith
 *
 * $Id: usb-uhci.c,v 1.275 2002/01/19 20:57:33 acher Exp $
 */

#include <linux/config.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ioport.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/smp_lock.h>
#include <linux/errno.h>
#include <linux/unistd.h>
#include <linux/interrupt.h>    /* for in_interrupt() */
#include <linux/init.h>
#include <linux/version.h>
#include <linux/pm.h>
#include <linux/timer.h>

#include <asm/uaccess.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/system.h>

/* 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

#define VERSTR "$Revision: 1.275 $ time " __TIME__ " " __DATE__

#include <linux/usb.h>
#include "usb-uhci.h"
#include "usb-uhci-debug.h"

/*
 * Version Information
 */
#define DRIVER_VERSION "v1.275"
#define DRIVER_AUTHOR "Georg Acher, Deti Fliegl, Thomas Sailer, Roman Weissgaerber"
#define DRIVER_DESC "USB Universal Host Controller Interface driver"

#undef DEBUG
#undef dbg
#define dbg(format, arg...) do {} while (0)
#define DEBUG_SYMBOLS
#ifdef DEBUG_SYMBOLS
    #define _static
    #ifndef EXPORT_SYMTAB
        #define EXPORT_SYMTAB
    #endif
#else
    #define _static static
#endif

#define queue_dbg dbg //err
#define async_dbg dbg //err

#ifdef DEBUG_SLAB
    static kmem_cache_t *urb_priv_kmem;
#endif

#define SLAB_FLAG     (in_interrupt ()? SLAB_ATOMIC : SLAB_KERNEL)
#define KMALLOC_FLAG  (in_interrupt ()? GFP_ATOMIC : GFP_KERNEL)

/* CONFIG_USB_UHCI_HIGH_BANDWITH turns on Full Speed Bandwidth
 * Reclamation: feature that puts loop on descriptor loop when
 * there's some transfer going on. With FSBR, USB performance
 * is optimal, but PCI can be slowed down up-to 5 times, slowing down
 * system performance (eg. framebuffer devices).
 */
#define CONFIG_USB_UHCI_HIGH_BANDWIDTH 

/* *_DEPTH_FIRST puts descriptor in depth-first mode. This has
 * somehow similar effect to FSBR (higher speed), but does not
 * slow PCI down. OTOH USB performace is slightly slower than
 * in FSBR case and single device could hog whole USB, starving
 * other devices.
 */
#define USE_CTRL_DEPTH_FIRST 0  // 0: Breadth first, 1: Depth first
#define USE_BULK_DEPTH_FIRST 0  // 0: Breadth first, 1: Depth first

/* Turning off both CONFIG_USB_UHCI_HIGH_BANDWITH and *_DEPTH_FIRST
 * will lead to <64KB/sec performance over USB for bulk transfers targeting
 * one device's endpoint. You probably do not want to do that.
 */

// stop bandwidth reclamation after (roughly) 50ms
#define IDLE_TIMEOUT  (HZ/20)

// Suppress HC interrupt error messages for 5s
#define ERROR_SUPPRESSION_TIME (HZ*5)

_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);

// How much URBs with ->next are walked
#define MAX_NEXT_COUNT 2048

static uhci_t *devs = NULL;

/* used by userspace UHCI data structure dumper */
uhci_t **uhci_devices = &devs;

/*-------------------------------------------------------------------*/
// Cleans up collected QHs, but not more than 100 in one go
void clean_descs(uhci_t *s, int force)
{
    struct list_head *q;
    uhci_desc_t *qh;
    int now=UHCI_GET_CURRENT_FRAME(s), n=0;

    q=s->free_desc.prev;

    while (q != &s->free_desc && (force || n<100)) {
        qh = list_entry (q, uhci_desc_t, horizontal);        
        q=qh->horizontal.prev;

        if ((qh->last_used!=now) || force)
            delete_qh(s,qh);
        n++;
    }
}
/*-------------------------------------------------------------------*/
_static void uhci_switch_timer_int(uhci_t *s)
{

    if (!list_empty(&s->urb_unlinked))
        set_td_ioc(s->td1ms);
    else
        clr_td_ioc(s->td1ms);

    if (s->timeout_urbs)
        set_td_ioc(s->td32ms);
    else
        clr_td_ioc(s->td32ms);
    wmb();
}
/*-------------------------------------------------------------------*/
#ifdef CONFIG_USB_UHCI_HIGH_BANDWIDTH
_static void enable_desc_loop(uhci_t *s, urb_t *urb)
{
    int flags;

    if (urb->transfer_flags & USB_NO_FSBR)
        return;

    spin_lock_irqsave (&s->qh_lock, flags);
    s->chain_end->hw.qh.head&=cpu_to_le32(~UHCI_PTR_TERM);
    mb();
    s->loop_usage++;
    ((urb_priv_t*)urb->hcpriv)->use_loop=1;
    spin_unlock_irqrestore (&s->qh_lock, flags);
}
/*-------------------------------------------------------------------*/
_static void disable_desc_loop(uhci_t *s, urb_t *urb)
{
    int flags;

    if (urb->transfer_flags & USB_NO_FSBR)
        return;

    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|=cpu_to_le32(UHCI_PTR_TERM);
            mb();
        }
        ((urb_priv_t*)urb->hcpriv)->use_loop=0;
    }
    spin_unlock_irqrestore (&s->qh_lock, flags);
}
#endif
/*-------------------------------------------------------------------*/
_static void queue_urb_unlocked (uhci_t *s, urb_t *urb)
{
    struct list_head *p=&urb->urb_list;
#ifdef CONFIG_USB_UHCI_HIGH_BANDWIDTH
    {
        int type;
        type=usb_pipetype (urb->pipe);

        if ((type == PIPE_BULK) || (type == PIPE_CONTROL))
            enable_desc_loop(s, urb);
    }
#endif
    urb->status = -EINPROGRESS;
    ((urb_priv_t*)urb->hcpriv)->started=jiffies;
    list_add (p, &s->urb_list);
    if (urb->timeout)
        s->timeout_urbs++;
    uhci_switch_timer_int(s);
}
/*-------------------------------------------------------------------*/
_static void queue_urb (uhci_t *s, urb_t *urb)
{
    unsigned long flags=0;

    spin_lock_irqsave (&s->urb_list_lock, flags);
    queue_urb_unlocked(s,urb);
    spin_unlock_irqrestore (&s->urb_list_lock, flags);
}
/*-------------------------------------------------------------------*/
_static void dequeue_urb (uhci_t *s, urb_t *urb)
{
#ifdef CONFIG_USB_UHCI_HIGH_BANDWIDTH
    int type;

    type=usb_pipetype (urb->pipe);

    if ((type == PIPE_BULK) || (type == PIPE_CONTROL))
        disable_desc_loop(s, urb);
#endif

    list_del (&urb->urb_list);
    if (urb->timeout && s->timeout_urbs)
        s->timeout_urbs--;

}
/*-------------------------------------------------------------------*/
_static int alloc_td (uhci_t *s, uhci_desc_t ** new, int flags)
{
    dma_addr_t dma_handle;

    *new = pci_pool_alloc(s->desc_pool, GFP_DMA | GFP_ATOMIC, &dma_handle);
    if (!*new)
        return -ENOMEM;
    memset (*new, 0, sizeof (uhci_desc_t));
    (*new)->dma_addr = dma_handle;
    set_td_link((*new), UHCI_PTR_TERM | (flags & UHCI_PTR_BITS));    // last by default
    (*new)->type = TD_TYPE;
    mb();
    INIT_LIST_HEAD (&(*new)->vertical);
    INIT_LIST_HEAD (&(*new)->horizontal);
    
    return 0;
}
/*-------------------------------------------------------------------*/
// append a qh to td.link physically, the SW linkage is not affected
_static void append_qh(uhci_t *s, uhci_desc_t *td, uhci_desc_t* qh, int  flags)
{
    unsigned long xxx;
    
    spin_lock_irqsave (&s->td_lock, xxx);

    set_td_link(td, qh->dma_addr | (flags & UHCI_PTR_DEPTH) | UHCI_PTR_QH);
       
    mb();
    spin_unlock_irqrestore (&s->td_lock, xxx);
}
/*-------------------------------------------------------------------*/
/* insert td at last position in td-list of qh (vertical) */
_static int insert_td (uhci_t *s, uhci_desc_t *qh, uhci_desc_t* new, int flags)
{
    uhci_desc_t *prev;
    unsigned long xxx;
    
    spin_lock_irqsave (&s->td_lock, xxx);

    list_add_tail (&new->vertical, &qh->vertical);

    prev = list_entry (new->vertical.prev, uhci_desc_t, vertical);

    if (qh == prev ) {
        // virgin qh without any tds
        set_qh_element(qh, new->dma_addr | UHCI_PTR_TERM);
    }
    else {
        // already tds inserted, implicitely remove TERM bit of prev
        set_td_link(prev, new->dma_addr | (flags & UHCI_PTR_DEPTH));
    }
    mb();
    spin_unlock_irqrestore (&s->td_lock, xxx);
    
    return 0;
}
/*-------------------------------------------------------------------*/
/* insert new_td after td (horizontal) */
_static int insert_td_horizontal (uhci_t *s, uhci_desc_t *td, uhci_desc_t* new)
{
    uhci_desc_t *next;
    unsigned long flags;
    
    spin_lock_irqsave (&s->td_lock, flags);

    next = list_entry (td->horizontal.next, uhci_desc_t, horizontal);
    list_add (&new->horizontal, &td->horizontal);
    new->hw.td.link = td->hw.td.link;
    set_td_link(td, new->dma_addr);
    mb();
    spin_unlock_irqrestore (&s->td_lock, flags);    
    
    return 0;
}
/*-------------------------------------------------------------------*/
_static int unlink_td (uhci_t *s, uhci_desc_t *element, int phys_unlink)
{
    uhci_desc_t *next, *prev;
    int dir = 0;
    unsigned long flags;
    
    spin_lock_irqsave (&s->td_lock, flags);
    
    next = list_entry (element->vertical.next, uhci_desc_t, vertical);
    
    if (next == element) {
        dir = 1;
        prev = list_entry (element->horizontal.prev, uhci_desc_t, horizontal);
    }
    else 
        prev = list_entry (element->vertical.prev, uhci_desc_t, vertical);
    
    if (phys_unlink) {
        // really remove HW linking
        if (prev->type == TD_TYPE)
            prev->hw.td.link = element->hw.td.link;
        else
            prev->hw.qh.element = element->hw.td.link;
    }

    mb ();

    if (dir == 0)
        list_del (&element->vertical);
    else
        list_del (&element->horizontal);
    
    spin_unlock_irqrestore (&s->td_lock, flags);    
    
    return 0;
}

/*-------------------------------------------------------------------*/
_static int delete_desc (uhci_t *s, uhci_desc_t *element)
{
    pci_pool_free(s->desc_pool, element, element->dma_addr);
    return 0;
}
/*-------------------------------------------------------------------*/
// Allocates qh element
_static int alloc_qh (uhci_t *s, uhci_desc_t ** new)
{
    dma_addr_t dma_handle;

    *new = pci_pool_alloc(s->desc_pool, GFP_DMA | GFP_ATOMIC, &dma_handle);
    if (!*new)
        return -ENOMEM;
    memset (*new, 0, sizeof (uhci_desc_t));
    (*new)->dma_addr = dma_handle;
    set_qh_head(*new, UHCI_PTR_TERM);
    set_qh_element(*new, UHCI_PTR_TERM);
    (*new)->type = QH_TYPE;
    
    mb();
    INIT_LIST_HEAD (&(*new)->horizontal);
    INIT_LIST_HEAD (&(*new)->vertical);
    
    dbg("Allocated qh @ %p", *new);
    
    return 0;
}
/*-------------------------------------------------------------------*/
// inserts new qh before/after the qh at pos
// flags: 0: insert before pos, 1: insert after pos (for low speed transfers)
_static int insert_qh (uhci_t *s, uhci_desc_t *pos, uhci_desc_t *new, int order)
{
    uhci_desc_t *old;
    unsigned long flags;

    spin_lock_irqsave (&s->qh_lock, flags);

    if (!order) {
        // (OLD) (POS) -> (OLD) (NEW) (POS)
        old = list_entry (pos->horizontal.prev, uhci_desc_t, horizontal);
        list_add_tail (&new->horizontal, &pos->horizontal);
        set_qh_head(new, MAKE_QH_ADDR (pos)) ;
        if (!(old->hw.qh.head & cpu_to_le32(UHCI_PTR_TERM)))
            set_qh_head(old, 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);
        set_qh_head(new, MAKE_QH_ADDR (old));
        set_qh_head(pos, MAKE_QH_ADDR (new)) ;
    }

    mb ();
    
    spin_unlock_irqrestore (&s->qh_lock, flags);

    return 0;
}

/*-------------------------------------------------------------------*/
_static int unlink_qh (uhci_t *s, uhci_desc_t *element)
{
    uhci_desc_t  *prev;
    unsigned long flags;

    spin_lock_irqsave (&s->qh_lock, flags);
    
    prev = list_entry (element->horizontal.prev, uhci_desc_t, horizontal);
    prev->hw.qh.head = element->hw.qh.head;

    dbg("unlink qh %p, pqh %p, nxqh %p, to %08x", element, prev, 
        list_entry (element->horizontal.next, uhci_desc_t, horizontal),le32_to_cpu(element->hw.qh.head) &~15);
    
    list_del(&element->horizontal);

    mb ();
    spin_unlock_irqrestore (&s->qh_lock, flags);
    
    return 0;
}
/*-------------------------------------------------------------------*/
_static int delete_qh (uhci_t *s, uhci_desc_t *qh)
{
    uhci_desc_t *td;
    struct list_head *p;
    
    list_del (&qh->horizontal);

    while ((p = qh->vertical.next) != &qh->vertical) {
        td = list_entry (p, uhci_desc_t, vertical);
        dbg("unlink td @ %p",td);
        unlink_td (s, td, 0); // no physical unlink
        delete_desc (s, td);
    }

    delete_desc (s, qh);
    
    return 0;
}
/*-------------------------------------------------------------------*/
_static void clean_td_chain (uhci_t *s, uhci_desc_t *td)
{
    struct list_head *p;
    uhci_desc_t *td1;

    if (!td)
        return;
    
    while ((p = td->horizontal.next) != &td->horizontal) {
        td1 = list_entry (p, uhci_desc_t, horizontal);
        delete_desc (s, td1);
    }
    
    delete_desc (s, td);
}

/*-------------------------------------------------------------------*/
_static void fill_td (uhci_desc_t *td, int status, int info, __u32 buffer)
{
    td->hw.td.status = cpu_to_le32(status);
    td->hw.td.info = cpu_to_le32(info);
    td->hw.td.buffer = cpu_to_le32(buffer);
}
/*-------------------------------------------------------------------*/
// Removes ALL qhs in chain (paranoia!)
_static void cleanup_skel (uhci_t *s)
{
    unsigned int n;
    uhci_desc_t *td;

    dbg("cleanup_skel");

    clean_descs(s,1);

    
    if (s->td32ms) {
    
        unlink_td(s,s->td32ms,1);
        delete_desc(s, s->td32ms);
    }

    for (n = 0; n < 8; n++) {
        td = s->int_chain[n];
        clean_td_chain (s, td);
    }

    if (s->iso_td) {
        for (n = 0; n < 1024; n++) {
            td = s->iso_td[n];
            clean_td_chain (s, td);
        }
        kfree (s->iso_td);
    }

    if (s->framelist)
        pci_free_consistent(s->uhci_pci, PAGE_SIZE,
                    s->framelist, s->framelist_dma);

    if (s->control_chain) {
        // completed init_skel?
        struct list_head *p;
        uhci_desc_t *qh, *qh1;

        qh = s->control_chain;
        while ((p = qh->horizontal.next) != &qh->horizontal) {
            qh1 = list_entry (p, uhci_desc_t, horizontal);
            delete_qh (s, qh1);
        }

        delete_qh (s, qh);
    }
    else {
        if (s->ls_control_chain)
            delete_desc (s, s->ls_control_chain);
        if (s->control_chain)
            delete_desc (s, s->control_chain);
        if (s->bulk_chain)
            delete_desc (s, s->bulk_chain);
        if (s->chain_end)
            delete_desc (s, s->chain_end);
    }

    if (s->desc_pool) {
        pci_pool_destroy(s->desc_pool);
        s->desc_pool = NULL;
    }

    dbg("cleanup_skel finished");    
}
/*-------------------------------------------------------------------*/
// allocates framelist and qh-skeletons
// only HW-links provide continous linking, SW-links stay in their domain (ISO/INT)
_static int init_skel (uhci_t *s)
{
    int n, ret;
    uhci_desc_t *qh, *td;
    
    dbg("init_skel");
    
    s->framelist = pci_alloc_consistent(s->uhci_pci, PAGE_SIZE,
                        &s->framelist_dma);

    if (!s->framelist)
        return -ENOMEM;

    memset (s->framelist, 0, 4096);

    dbg("creating descriptor pci_pool");

    s->desc_pool = pci_pool_create("uhci_desc", s->uhci_pci,
                       sizeof(uhci_desc_t), 16, 0,
                       GFP_DMA | GFP_ATOMIC);    
    if (!s->desc_pool)
        goto init_skel_cleanup;

    dbg("allocating iso desc pointer list");
    s->iso_td = (uhci_desc_t **) kmalloc (1024 * sizeof (uhci_desc_t*), GFP_KERNEL);
    
    if (!s->iso_td)
        goto init_skel_cleanup;

    s->ls_control_chain = NULL;
    s->control_chain = NULL;
    s->bulk_chain = NULL;
    s->chain_end = NULL;

    dbg("allocating iso descs");
    for (n = 0; n < 1024; n++) {
         // allocate skeleton iso/irq-tds
        if (alloc_td (s, &td, 0))
            goto init_skel_cleanup;

        s->iso_td[n] = td;
        s->framelist[n] = cpu_to_le32((__u32) td->dma_addr);
    }

    dbg("allocating qh: chain_end");
    if (alloc_qh (s, &qh))    
        goto init_skel_cleanup;
                
    s->chain_end = qh;

    if (alloc_td (s, &td, 0))
        goto init_skel_cleanup;
    
    fill_td (td, 0 * TD_CTRL_IOC, 0, 0); // generate 1ms interrupt (enabled on demand)
    insert_td (s, qh, td, 0);
    qh->hw.qh.element &= cpu_to_le32(~UHCI_PTR_TERM); // remove TERM bit
    s->td1ms=td;

    dbg("allocating qh: bulk_chain");
    if (alloc_qh (s, &qh))
        goto init_skel_cleanup;
    
    insert_qh (s, s->chain_end, qh, 0);
    s->bulk_chain = qh;

    dbg("allocating qh: control_chain");
    ret = alloc_qh (s, &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
    set_qh_head(s->chain_end, s->control_chain->dma_addr | UHCI_PTR_QH | UHCI_PTR_TERM);
#endif

    dbg("allocating qh: ls_control_chain");
    if (alloc_qh (s, &qh))
        goto init_skel_cleanup;
    
    insert_qh (s, s->control_chain, qh, 0);
    s->ls_control_chain = qh;

    for (n = 0; n < 8; n++)
        s->int_chain[n] = 0;

    dbg("allocating skeleton INT-TDs");
    
    for (n = 0; n < 8; n++) {
        uhci_desc_t *td;

        if (alloc_td (s, &td, 0))
            goto init_skel_cleanup;

        s->int_chain[n] = td;
        if (n == 0) {
            set_td_link(s->int_chain[0], s->ls_control_chain->dma_addr | UHCI_PTR_QH);
        }
        else {
            set_td_link(s->int_chain[n], s->int_chain[0]->dma_addr);
        }
    }

    dbg("Linking skeleton INT-TDs");
    
    for (n = 0; n < 1024; n++) {
        // link all iso-tds to the interrupt chains
        int m, o;
        dbg("framelist[%i]=%x",n,le32_to_cpu(s->framelist[n]));
        if ((n&127)==127) 
            ((uhci_desc_t*) s->iso_td[n])->hw.td.link = cpu_to_le32(s->int_chain[0]->dma_addr);
        else 
            for (o = 1, m = 2; m <= 128; o++, m += m)
                if ((n & (m - 1)) == ((m - 1) / 2))
                    set_td_link(((uhci_desc_t*) s->iso_td[n]), s->int_chain[o]->dma_addr);
    }

    if (alloc_td (s, &td, 0))
        goto init_skel_cleanup;
    
    fill_td (td, 0 * TD_CTRL_IOC, 0, 0); // generate 32ms interrupt (activated later)
    s->td32ms=td;

    insert_td_horizontal (s, s->int_chain[5], td);

    mb();
    //uhci_show_queue(s->control_chain);   
    dbg("init_skel exit");
    return 0;

      init_skel_cleanup:
    cleanup_skel (s);
    return -ENOMEM;
}

/*-------------------------------------------------------------------*/
//                         LOW LEVEL STUFF
//          assembles QHs und TDs for control, bulk and iso
/*-------------------------------------------------------------------*/
_static int uhci_submit_control_urb (urb_t *urb)
{
    uhci_desc_t *qh, *td;
    uhci_t *s = (uhci_t*) urb->dev->bus->hcpriv;
    urb_priv_t *urb_priv = urb->hcpriv;
    unsigned long destination, status;
    int maxsze = usb_maxpacket (urb->dev, urb->pipe, usb_pipeout (urb->pipe));
    unsigned long len;
    char *data;
    int depth_first=USE_CTRL_DEPTH_FIRST;  // UHCI descriptor chasing method

    dbg("uhci_submit_control start");
    if (alloc_qh (s, &qh))        // alloc qh for this request
        return -ENOMEM;

    if (alloc_td (s, &td, UHCI_PTR_DEPTH * depth_first))        // get td for setup stage
    {
        delete_qh (s, qh);
        return -ENOMEM;
    }

    /* The "pipe" thing contains the destination in bits 8--18 */
    destination = (urb->pipe & PIPE_DEVEP_MASK) | USB_PID_SETUP;

    /* 3 errors */
    status = (urb->pipe & TD_CTRL_LS) | TD_CTRL_ACTIVE |
        (urb->transfer_flags & USB_DISABLE_SPD ? 0 : TD_CTRL_SPD) | (3 << 27);

    /*  Build the TD for the control request, try forever, 8 bytes of data */
    fill_td (td, status, destination | (7 << 21), urb_priv->setup_packet_dma);

    insert_td (s, qh, td, 0);    // queue 'setup stage'-td in qh
#if 0
    {
        char *sp=urb->setup_packet;
        dbg("SETUP to pipe %x: %x %x %x %x %x %x %x %x", urb->pipe,
            sp[0],sp[1],sp[2],sp[3],sp[4],sp[5],sp[6],sp[7]);
    }
    //uhci_show_td(td);
#endif

    len = urb->transfer_buffer_length;
    data = urb->transfer_buffer;

    /* If direction is "send", change the frame from SETUP (0x2D)
       to OUT (0xE1). Else change it from SETUP to IN (0x69). */

    destination = (urb->pipe & PIPE_DEVEP_MASK) | (usb_pipeout (urb->pipe)?USB_PID_OUT:USB_PID_IN);

    while (len > 0) {
        int pktsze = len;

        if (alloc_td (s, &td, UHCI_PTR_DEPTH * depth_first))
            goto fail_unmap_enomem;

        if (pktsze > maxsze)
            pktsze = maxsze;

        destination ^= 1 << TD_TOKEN_TOGGLE;    // toggle DATA0/1

        // Status, pktsze bytes of data
        fill_td (td, status, destination | ((pktsze - 1) << 21),
             urb_priv->transfer_buffer_dma + (data - (char *)urb->transfer_buffer));

        insert_td (s, qh, td, UHCI_PTR_DEPTH * depth_first);    // queue 'data stage'-td in qh

        data += pktsze;
        len -= pktsze;
    }

    /* Build the final TD for control status */
    /* It's only IN if the pipe is out AND we aren't expecting data */

    destination &= ~UHCI_PID;

    if (usb_pipeout (urb->pipe) || (urb->transfer_buffer_length == 0))
        destination |= USB_PID_IN;
    else
        destination |= USB_PID_OUT;

    destination |= 1 << TD_TOKEN_TOGGLE;    /* End in Data1 */

    if (alloc_td (s, &td, UHCI_PTR_DEPTH))
        goto fail_unmap_enomem;

    status &=~TD_CTRL_SPD;

    /* no limit on errors on final packet , 0 bytes of data */
    fill_td (td, status | TD_CTRL_IOC, destination | (UHCI_NULL_DATA_SIZE << 21),
         0);

    insert_td (s, qh, td, UHCI_PTR_DEPTH * depth_first);    // queue status td

    list_add (&qh->desc_list, &urb_priv->desc_list);

    queue_urb (s, urb);    // queue before inserting in desc chain

    qh->hw.qh.element &= cpu_to_le32(~UHCI_PTR_TERM);

    //uhci_show_queue(qh);
    /* Start it up... put low speed first */
    if (urb->pipe & TD_CTRL_LS)
        insert_qh (s, s->control_chain, qh, 0);
    else
        insert_qh (s, s->bulk_chain, qh, 0);

    dbg("uhci_submit_control end");
    return 0;

fail_unmap_enomem:
    delete_qh(s, qh);
    return -ENOMEM;
}
/*-------------------------------------------------------------------*/
// 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, *upriv, *bpriv=NULL;
    uhci_desc_t *qh, *td, *nqh=NULL, *bqh=NULL, *first_td=NULL;
    unsigned long destination, status;
    char *data;
    unsigned int pipe = urb->pipe;
    int maxsze = usb_maxpacket (urb->dev, pipe, usb_pipeout (pipe));
    int info, len, last;
    int depth_first=USE_BULK_DEPTH_FIRST;  // UHCI descriptor chasing method

    if (usb_endpoint_halted (urb->dev, usb_pipeendpoint (pipe), usb_pipeout (pipe)))
        return -EPIPE;

    queue_dbg("uhci_submit_bulk_urb: urb %p, old %p, pipe %08x, len %i",
          urb,bulk_urb,urb->pipe,urb->transfer_buffer_length);

    upriv = (urb_priv_t*)urb->hcpriv;

    if (!bulk_urb) {
        if (alloc_qh (s, &qh))        // get qh for this request
            return -ENOMEM;

        if (urb->transfer_flags & USB_QUEUE_BULK) {
            if (alloc_qh(s, &nqh)) // placeholder for clean unlink
            {
                delete_desc (s, 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;    
        upriv->prev_queued_urb=bulk_urb;
    }

    if (urb->transfer_flags & USB_QUEUE_BULK) {
        if (alloc_qh (s, &bqh))  // "bottom" QH
        {
            if (!bulk_urb) { 
                delete_desc(s, qh);
                delete_desc(s, nqh);
            }
            return -ENOMEM;
        }
        set_qh_element(bqh, UHCI_PTR_TERM);
        set_qh_head(bqh, nqh->dma_addr | UHCI_PTR_QH); // element
        upriv->bottom_qh = bqh;
    }
    queue_dbg("uhci_submit_bulk: qh %p bqh %p nqh %p",qh, bqh, nqh);

    /* The "pipe" thing contains the destination in bits 8--18. */
    destination = (pipe & PIPE_DEVEP_MASK) | usb_packetid (pipe);

    /* 3 errors */
    status = (pipe & TD_CTRL_LS) | TD_CTRL_ACTIVE |
        ((urb->transfer_flags & USB_DISABLE_SPD) ? 0 : TD_CTRL_SPD) | (3 << 27);

    /* Build the TDs for the bulk request */
    len = urb->transfer_buffer_length;
    data = urb->transfer_buffer;
    
    do {                    // TBD: Really allow zero-length packets?
        int pktsze = len;

        if (alloc_td (s, &td, UHCI_PTR_DEPTH * depth_first))
        {
            delete_qh (s, qh);
            return -ENOMEM;
        }

        if (pktsze > maxsze)
            pktsze = maxsze;

        // pktsze bytes of data 
        info = destination | (((pktsze - 1)&UHCI_NULL_DATA_SIZE) << 21) |
            (usb_gettoggle (urb->dev, usb_pipeendpoint (pipe), usb_pipeout (pipe)) << TD_TOKEN_TOGGLE);

        fill_td (td, status, info,
             urb_priv->transfer_buffer_dma + (data - (char *)urb->transfer_buffer));

        data += pktsze;
        len -= pktsze;
        // Use USB_ZERO_PACKET to finish bulk OUTs always with a zero length packet
        last = (len == 0 && (usb_pipein(pipe) || pktsze < maxsze || !(urb->transfer_flags & USB_ZERO_PACKET)));

        if (last)
            set_td_ioc(td);    // last one generates INT

        insert_td (s, qh, td, UHCI_PTR_DEPTH * depth_first);
        if (!first_td)
            first_td=td;
        usb_dotoggle (urb->dev, usb_pipeendpoint (pipe), usb_pipeout (pipe));

    } while (!last);

    if (bulk_urb && bpriv)   // everything went OK, link with old bulk URB
        bpriv->next_queued_urb=urb;

    list_add (&qh->desc_list, &urb_priv->desc_list);

    if (urb->transfer_flags & USB_QUEUE_BULK)
        append_qh(s, td, bqh, UHCI_PTR_DEPTH * depth_first);

    queue_urb_unlocked (s, urb);
    
    if (urb->transfer_flags & USB_QUEUE_BULK)
        set_qh_element(qh, first_td->dma_addr);
    else
        qh->hw.qh.element &= cpu_to_le32(~UHCI_PTR_TERM);    // arm QH

    if (!bulk_urb) {                     // new bulk queue    
        if (urb->transfer_flags & USB_QUEUE_BULK) {
            spin_lock (&s->td_lock);        // both QHs in one go
            insert_qh (s, s->chain_end, qh, 0);    // Main QH
            insert_qh (s, s->chain_end, nqh, 0);    // Helper QH
            spin_unlock (&s->td_lock);
        }
        else
            insert_qh (s, s->chain_end, qh, 0);
    }
    
    //uhci_show_queue(s->bulk_chain);
    //dbg("uhci_submit_bulk_urb: exit\n");
    return 0;
}
/*-------------------------------------------------------------------*/
_static void uhci_clean_iso_step1(uhci_t *s, urb_priv_t *urb_priv)
{
    struct list_head *p;
    uhci_desc_t *td;

    for (p = urb_priv->desc_list.next; p != &urb_priv->desc_list; p = p->next) {
                td = list_entry (p, uhci_desc_t, desc_list);
                unlink_td (s, td, 1);
    }
}
/*-------------------------------------------------------------------*/
_static void uhci_clean_iso_step2(uhci_t *s, urb_priv_t *urb_priv)
{
    struct list_head *p;
    uhci_desc_t *td;

    while ((p = urb_priv->desc_list.next) != &urb_priv->desc_list) {
                td = list_entry (p, uhci_desc_t, desc_list);
                list_del (p);
                delete_desc (s, td);
    }
}
/*-------------------------------------------------------------------*/
/* mode: CLEAN_TRANSFER_NO_DELETION: unlink but no deletion mark (step 1 of async_unlink)
         CLEAN_TRANSFER_REGULAR: regular (unlink/delete-mark)
         CLEAN_TRANSFER_DELETION_MARK: deletion mark for QH (step 2 of async_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, *prevtd;
    int now;
    urb_priv_t *priv=(urb_priv_t*)urb->hcpriv;

    now=UHCI_GET_CURRENT_FRAME(s);

    bqh=priv->bottom_qh;    
    
    if (!priv->next_queued_urb)  { // no more appended bulk queues

        queue_dbg("uhci_clean_transfer: No more bulks for urb %p, qh %p, bqh %p, nqh %p", urb, qh, bqh, priv->next_qh);    
    
        if (priv->prev_queued_urb && mode != CLEAN_TRANSFER_DELETION_MARK) {  // qh not top of the queue
                unsigned long flags; 
                urb_priv_t* ppriv=(urb_priv_t*)priv->prev_queued_urb->hcpriv;

                spin_lock_irqsave (&s->qh_lock, flags);
                prevqh = list_entry (ppriv->desc_list.next, uhci_desc_t, desc_list);
                prevtd = list_entry (prevqh->vertical.prev, uhci_desc_t, vertical);
                set_td_link(prevtd, priv->bottom_qh->dma_addr | UHCI_PTR_QH); // skip current qh
                mb();
                queue_dbg("uhci_clean_transfer: relink pqh %p, ptd %p",prevqh, prevtd);
                spin_unlock_irqrestore (&s->qh_lock, flags);

                ppriv->bottom_qh = priv->bottom_qh;
                ppriv->next_queued_urb = NULL;
            }
        else {   // queue is dead, qh is top of the queue
            
            if (mode != CLEAN_TRANSFER_DELETION_MARK)                 
                unlink_qh(s, qh); // remove qh from horizontal chain

            if (bqh) {  // remove remainings of bulk queue
                nqh=priv->next_qh;

                if (mode != CLEAN_TRANSFER_DELETION_MARK) 
                    unlink_qh(s, nqh);  // remove nqh from horizontal chain
                
                if (mode != CLEAN_TRANSFER_NO_DELETION) {  // add helper QHs to free desc list
                    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
    
      queue_dbg("uhci_clean_transfer: urb %p, prevurb %p, nexturb %p, qh %p, bqh %p, nqh %p",
               urb, priv->prev_queued_urb,  priv->next_queued_urb, qh, bqh, priv->next_qh);    
           
        if (mode != CLEAN_TRANSFER_DELETION_MARK) {    // no work for cleanup at unlink-completion
            urb_t *nurb;
            unsigned long flags;

            nurb = priv->next_queued_urb;
            spin_lock_irqsave (&s->qh_lock, flags);        

            if (!priv->prev_queued_urb) { // top QH
                
                prevqh = list_entry (qh->horizontal.prev, uhci_desc_t, horizontal);
                set_qh_head(prevqh, bqh->dma_addr | UHCI_PTR_QH);
                list_del (&qh->horizontal);  // remove this qh form horizontal chain
                list_add (&bqh->horizontal, &prevqh->horizontal); // insert next bqh in horizontal chain
            }
            else {        // intermediate QH
                urb_priv_t* ppriv=(urb_priv_t*)priv->prev_queued_urb->hcpriv;
                urb_priv_t* npriv=(urb_priv_t*)nurb->hcpriv;
                uhci_desc_t * bnqh;
                
                bnqh = list_entry (npriv->desc_list.next, uhci_desc_t, desc_list);
                ppriv->bottom_qh = bnqh;
                ppriv->next_queued_urb = nurb;                
                prevqh = list_entry (ppriv->desc_list.next, uhci_desc_t, desc_list);
                set_qh_head(prevqh, bqh->dma_addr | UHCI_PTR_QH);
            }

            mb();
            ((urb_priv_t*)nurb->hcpriv)->prev_queued_urb=priv->prev_queued_urb;
            spin_unlock_irqrestore (&s->qh_lock, flags);
        }        
    }

    if (mode != CLEAN_TRANSFER_NO_DELETION) {
        qh->last_used = now;    
        list_add_tail (&qh->horizontal, &s->free_desc); // mark qh for later deletion/kfree
    }
}
/*-------------------------------------------------------------------*/
// Release bandwidth for Interrupt or Isoc. transfers 
_static void uhci_release_bandwidth(urb_t *urb)
{       
    if (urb->bandwidth) {
        switch (usb_pipetype(urb->pipe)) {
        case PIPE_INTERRUPT:
            usb_release_bandwidth (urb->dev, urb, 0);
            break;
        case PIPE_ISOCHRONOUS:
            usb_release_bandwidth (urb->dev, urb, 1);
            break;
        default:
            break;
        }
    }    
}

_static void uhci_urb_dma_sync(uhci_t *s, urb_t *urb, urb_priv_t *urb_priv)
{
    if (urb_priv->setup_packet_dma)
        pci_dma_sync_single(s->uhci_pci, urb_priv->setup_packet_dma,
                    sizeof(devrequest), PCI_DMA_TODEVICE);

    if (urb_priv->transfer_buffer_dma)
        pci_dma_sync_single(s->uhci_pci, urb_priv->transfer_buffer_dma,
                    urb->transfer_buffer_length,
                    usb_pipein(urb->pipe) ?
                    PCI_DMA_FROMDEVICE :
                    PCI_DMA_TODEVICE);
}

_static void uhci_urb_dma_unmap(uhci_t *s, urb_t *urb, urb_priv_t *urb_priv)
{
    if (urb_priv->setup_packet_dma) {
        pci_unmap_single(s->uhci_pci, urb_priv->setup_packet_dma,
                 sizeof(devrequest), PCI_DMA_TODEVICE);
        urb_priv->setup_packet_dma = 0;
    }
    if (urb_priv->transfer_buffer_dma) {
        pci_unmap_single(s->uhci_pci, urb_priv->transfer_buffer_dma,
                 urb->transfer_buffer_length,
                 usb_pipein(urb->pipe) ?
                 PCI_DMA_FROMDEVICE :
                 PCI_DMA_TODEVICE);
        urb_priv->transfer_buffer_dma = 0;
    }
}
/*-------------------------------------------------------------------*/
/* needs urb_list_lock!
   mode: UNLINK_ASYNC_STORE_URB: unlink and move URB into unlinked list
         UNLINK_ASYNC_DONT_STORE: unlink, don't move URB into unlinked list
*/
_static int uhci_unlink_urb_async (uhci_t *s,urb_t *urb, int mode)
{
    uhci_desc_t *qh;
    urb_priv_t *urb_priv;
    
    async_dbg("unlink_urb_async called %p",urb);

    if ((urb->status == -EINPROGRESS) ||
        ((usb_pipetype (urb->pipe) ==  PIPE_INTERRUPT) && ((urb_priv_t*)urb->hcpriv)->flags))
    {
        ((urb_priv_t*)urb->hcpriv)->started = ~0;  // mark
        dequeue_urb (s, urb);

        if (mode==UNLINK_ASYNC_STORE_URB)
            list_add_tail (&urb->urb_list, &s->urb_unlinked); // store urb

        uhci_switch_timer_int(s);
               s->unlink_urb_done = 1;
        uhci_release_bandwidth(urb);

        urb->status = -ECONNABORTED;    // mark urb as "waiting to be killed"    
        urb_priv = (urb_priv_t*)urb->hcpriv;

        switch (usb_pipetype (urb->pipe)) {
        case PIPE_INTERRUPT:
            usb_dotoggle (urb->dev, usb_pipeendpoint (urb->pipe), usb_pipeout (urb->pipe));

        case PIPE_ISOCHRONOUS:
            uhci_clean_iso_step1 (s, urb_priv);
            break;

        case PIPE_BULK:
        case PIPE_CONTROL:
            qh = list_entry (urb_priv->desc_list.next, uhci_desc_t, desc_list);
            uhci_clean_transfer (s, urb, qh, CLEAN_TRANSFER_NO_DELETION);
            break;
        }
        ((urb_priv_t*)urb->hcpriv)->started = UHCI_GET_CURRENT_FRAME(s);
        return -EINPROGRESS;  // completion will follow
    }        

    return 0;    // URB already dead
}
/*-------------------------------------------------------------------*/
// kills an urb by unlinking descriptors and waiting for at least one frame
_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;
    struct usb_device *usb_dev;

    spin_lock_irqsave (&s->urb_list_lock, flags);

    if (urb->status == -EINPROGRESS) {

        // move descriptors out the the running chains, dequeue urb
        uhci_unlink_urb_async(s, urb, UNLINK_ASYNC_DONT_STORE);

        urb_priv = urb->hcpriv;
        urb->status = -ENOENT;    // prevent from double deletion after unlock        
        spin_unlock_irqrestore (&s->urb_list_lock, flags);
        
        // cleanup the rest
        switch (usb_pipetype (urb->pipe)) {

        case PIPE_INTERRUPT:
        case PIPE_ISOCHRONOUS:
            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);
            uhci_clean_transfer(s, urb, qh, CLEAN_TRANSFER_DELETION_MARK);
            uhci_wait_ms(1);
        }
        urb->status = -ENOENT;    // mark urb as killed        
                    
        uhci_urb_dma_unmap(s, urb, urb->hcpriv);

#ifdef DEBUG_SLAB
        kmem_cache_free (urb_priv_kmem, urb->hcpriv);
#else
        kfree (urb->hcpriv);
#endif
        usb_dev = urb->dev;
        if (urb->complete) {
            dbg("unlink_urb: calling completion");
            urb->dev = NULL;
            urb->complete ((struct urb *) urb);
        }
        usb_dec_dev_use (usb_dev);
    }
    else
        spin_unlock_irqrestore (&s->urb_list_lock, flags);

    return 0;
}
/*-------------------------------------------------------------------*/
// async unlink_urb completion/cleanup work
// has to be protected by urb_list_lock!
// features: if set in transfer_flags, the resulting status of the killed
// transaction is not overwritten

_static void uhci_cleanup_unlink(uhci_t *s, int force)
{
    struct list_head *q;
    urb_t *urb;
    struct usb_device *dev;
    int now, type;
    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 (!urb_priv) // avoid crash when URB is corrupted
            break;
            
        if (force || ((urb_priv->started != ~0) && (urb_priv->started != now))) {
            async_dbg("async cleanup %p",urb);
            type=usb_pipetype (urb->pipe);

            switch (type) { // process descriptors
            case PIPE_CONTROL:
                process_transfer (s, urb, CLEAN_TRANSFER_DELETION_MARK);  // don't unlink (already done)
                break;
            case PIPE_BULK:
                if (!s->avoid_bulk.counter)
                    process_transfer (s, urb, CLEAN_TRANSFER_DELETION_MARK); // don't unlink (already done)
                else
                    continue;
                break;
            case PIPE_ISOCHRONOUS:
                process_iso (s, urb, PROCESS_ISO_FORCE); // 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

            dev = urb->dev;    // completion may destroy all...
            urb_priv = urb->hcpriv;
            list_del (&urb->urb_list);
            
            uhci_urb_dma_sync(s, urb, urb_priv);
            if (urb->complete) {
                spin_unlock(&s->urb_list_lock);
                urb->dev = NULL;
                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

            switch (type) {
            case PIPE_ISOCHRONOUS:
            case PIPE_INTERRUPT:
                uhci_clean_iso_step2(s, urb_priv);
                break;
            }
    
            uhci_urb_dma_unmap(s, urb, urb_priv);

            usb_dec_dev_use (dev);
#ifdef DEBUG_SLAB
            kmem_cache_free (urb_priv_kmem, urb_priv);
#else
            kfree (urb_priv);
#endif

        }
    }
}
 
/*-------------------------------------------------------------------*/
_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);
               
        uhci_release_bandwidth(urb);
        ret = uhci_unlink_urb_async(s, urb, UNLINK_ASYNC_STORE_URB);

        spin_unlock_irqrestore (&s->urb_list_lock, flags);    
        return ret;
    }
    else
        return uhci_unlink_urb_sync(s, urb);
}
/*-------------------------------------------------------------------*/
// In case of ASAP iso transfer, search the URB-list for already queued URBs
// for this EP and calculate the earliest start frame for the new
// URB (easy seamless URB continuation!)
_static int find_iso_limits (urb_t *urb, unsigned int *start, unsigned int *end)
{
    urb_t *u, *last_urb = NULL;
    uhci_t *s = (uhci_t*) urb->dev->bus->hcpriv;
    struct list_head *p;
    int ret=-1;
    unsigned long flags;
    
    spin_lock_irqsave (&s->urb_list_lock, flags);
    p=s->urb_list.prev;

    for (; p != &s->urb_list; p = p->prev) {
        u = list_entry (p, urb_t, urb_list);
        // look for pending URBs with identical pipe handle
        // works only because iso doesn't toggle the data bit!
        if ((urb->pipe == u->pipe) && (urb->dev == u->dev) && (u->status == -EINPROGRESS)) {
            if (!last_urb)
                *start = u->start_frame;
            last_urb = u;
        }
    }
    
    if (last_urb) {
        *end = (last_urb->start_frame + last_urb->number_of_packets) & 1023;
        ret=0;
    }
    
    spin_unlock_irqrestore(&s->urb_list_lock, flags);
    
    return ret;
}
/*-------------------------------------------------------------------*/
// adjust start_frame according to scheduling constraints (ASAP etc)

_static int iso_find_start (urb_t *urb)
{
    uhci_t *s = (uhci_t*) urb->dev->bus->hcpriv;
    unsigned int now;
    unsigned int start_limit = 0, stop_limit = 0, queued_size;
    int limits;

    now = UHCI_GET_CURRENT_FRAME (s) & 1023;

    if ((unsigned) urb->number_of_packets > 900)
        return -EFBIG;
    
    limits = find_iso_limits (urb, &start_limit, &stop_limit);
    queued_size = (stop_limit - start_limit) & 1023;

    if (urb->transfer_flags & USB_ISO_ASAP) {
        // first iso
        if (limits) {
            // 10ms setup should be enough //FIXME!
            urb->start_frame = (now + 10) & 1023;
        }
        else {
            urb->start_frame = stop_limit;        //seamless linkage

            if (((now - urb->start_frame) & 1023) <= (unsigned) urb->number_of_packets) {
                info("iso_find_start: gap in seamless isochronous scheduling");
                dbg("iso_find_start: now %u start_frame %u number_of_packets %u pipe 0x%08x",
                    now, urb->start_frame, urb->number_of_packets, urb->pipe);
                urb->start_frame = (now + 5) & 1023;    // 5ms setup should be enough //FIXME!
            }
        }
    }
    else {
        urb->start_frame &= 1023;
        if (((now - urb->start_frame) & 1023) < (unsigned) urb->number_of_packets) {
            dbg("iso_find_start: now between start_frame and end");
            return -EAGAIN;
        }
    }

    /* check if either start_frame or start_frame+number_of_packets-1 lies between start_limit and stop_limit */
    if (limits)
        return 0;

    if (((urb->start_frame - start_limit) & 1023) < queued_size ||
        ((urb->start_frame + urb->number_of_packets - 1 - start_limit) & 1023) < queued_size) {
        dbg("iso_find_start: start_frame %u number_of_packets %u start_limit %u stop_limit %u",
            urb->start_frame, urb->number_of_packets, start_limit, stop_limit);
        return -EAGAIN;
    }

    return 0;
}
/*-------------------------------------------------------------------*/
// submits USB interrupt (ie. polling ;-) 
// ASAP-flag set implicitely
// if period==0, the transfer is only done once

_static int uhci_submit_int_urb (urb_t *urb)
{
    uhci_t *s = (uhci_t*) urb->dev->bus->hcpriv;
    urb_priv_t *urb_priv = urb->hcpriv;
    int nint, n;
    uhci_desc_t *td;
    int status, destination;
    int info;
    unsigned int pipe = urb->pipe;

    if (urb->interval < 0 || urb->interval >= 256)
        return -EINVAL;

    if (urb->interval == 0)
        nint = 0;
    else {
        for (nint = 0, n = 1; nint <= 8; nint++, n += n)    // round interval down to 2^n
         {
            if (urb->interval < n) {
                urb->interval = n / 2;
                break;
            }
        }
        nint--;
    }

    dbg("Rounded interval to %i, chain  %i", urb->interval, nint);

    urb->start_frame = UHCI_GET_CURRENT_FRAME (s) & 1023;    // remember start frame, just in case...

    urb->number_of_packets = 1;

    // INT allows only one packet
    if (urb->transfer_buffer_length > usb_maxpacket (urb->dev, pipe, usb_pipeout (pipe)))
        return -EINVAL;

    if (alloc_td (s, &td, UHCI_PTR_DEPTH))
        return -ENOMEM;

    status = (pipe & TD_CTRL_LS) | TD_CTRL_ACTIVE | TD_CTRL_IOC |
        (urb->transfer_flags & USB_DISABLE_SPD ? 0 : TD_CTRL_SPD) | (3 << 27);

    destination = (urb->pipe & PIPE_DEVEP_MASK) | usb_packetid (urb->pipe) |
        (((urb->transfer_buffer_length - 1) & 0x7ff) << 21);


    info = destination | (usb_gettoggle (urb->dev, usb_pipeendpoint (pipe), usb_pipeout (pipe)) << TD_TOKEN_TOGGLE);

    fill_td (td, status, info, urb_priv->transfer_buffer_dma);
    list_add_tail (&td->desc_list, &urb_priv->desc_list);

    queue_urb (s, urb);

    insert_td_horizontal (s, s->int_chain[nint], td);    // store in INT-TDs

    usb_dotoggle (urb->dev, usb_pipeendpoint (pipe), usb_pipeout (pipe));

    return 0;
}
/*-------------------------------------------------------------------*/
_static int uhci_submit_iso_urb (urb_t *urb)
{
    uhci_t *s = (uhci_t*) urb->dev->bus->hcpriv;
    urb_priv_t *urb_priv = urb->hcpriv;
#ifdef ISO_SANITY_CHECK
    int pipe=urb->pipe;
    int maxsze = usb_maxpacket (urb->dev, pipe, usb_pipeout (pipe));
#endif
    int n, ret, last=0;
    uhci_desc_t *td, **tdm;
    int status, destination;
    unsigned long flags;

    __save_flags(flags);
    __cli();              // Disable IRQs to schedule all ISO-TDs in time
    ret = iso_find_start (urb);    // adjusts urb->start_frame for later use
    
    if (ret)
        goto err;

    tdm = (uhci_desc_t **) kmalloc (urb->number_of_packets * sizeof (uhci_desc_t*), KMALLOC_FLAG);

    if (!tdm) {
        ret = -ENOMEM;
        goto err;
    }

    memset(tdm, 0, urb->number_of_packets * sizeof (uhci_desc_t*));

    // First try to get all TDs. Cause: Removing already inserted TDs can only be done 
    // racefree in three steps: unlink TDs, wait one frame, delete TDs. 
    // So, this solutions seems simpler...

    for (n = 0; n < urb->number_of_packets; n++) {
        dbg("n:%d urb->iso_frame_desc[n].length:%d", n, urb->iso_frame_desc[n].length);
        if (!urb->iso_frame_desc[n].length)
            continue;  // allows ISO striping by setting length to zero in iso_descriptor


#ifdef ISO_SANITY_CHECK
        if(urb->iso_frame_desc[n].length > maxsze) {

            err("submit_iso: urb->iso_frame_desc[%d].length(%d)>%d",n , urb->iso_frame_desc[n].length, maxsze);
            ret=-EINVAL;        
        }
        else
#endif
        if (alloc_td (s, &td, UHCI_PTR_DEPTH)) {
            int i;    // Cleanup allocated TDs

            for (i = 0; i < n; n++)
                if (tdm[i])
                     delete_desc(s, tdm[i]);
            kfree (tdm);
            goto err;
        }
        last=n;
        tdm[n] = td;
    }

    status = TD_CTRL_ACTIVE | TD_CTRL_IOS;

    destination = (urb->pipe & PIPE_DEVEP_MASK) | usb_packetid (urb->pipe);

    // Queue all allocated TDs
    for (n = 0; n < urb->number_of_packets; n++) {
        td = tdm[n];
        if (!td)
            continue;
            
        if (n  == last) {
            status |= TD_CTRL_IOC;
            queue_urb (s, urb);
        }

        fill_td (td, status, destination | (((urb->iso_frame_desc[n].length - 1) & 0x7ff) << 21),
             urb_priv->transfer_buffer_dma + urb->iso_frame_desc[n].offset);
        list_add_tail (&td->desc_list, &urb_priv->desc_list);
    
        insert_td_horizontal (s, s->iso_td[(urb->start_frame + n) & 1023], td);    // store in iso-tds
    }

    kfree (tdm);
    dbg("ISO-INT# %i, start %i, now %i", urb->number_of_packets, urb->start_frame, UHCI_GET_CURRENT_FRAME (s) & 1023);
    ret = 0;

      err:
    __restore_flags(flags);
    return ret;
}
/*-------------------------------------------------------------------*/
// returns: 0 (no transfer queued), urb* (this urb already queued)
 
_static urb_t* search_dev_ep (uhci_t *s, urb_t *urb)
{
    struct list_head *p;
    urb_t *tmp;
    unsigned int mask = usb_pipecontrol(urb->pipe) ? (~USB_DIR_IN) : (~0);

    dbg("search_dev_ep:");

    p=s->urb_list.next;

    for (; p != &s->urb_list; p = p->next) {
        tmp = list_entry (p, urb_t, urb_list);
        dbg("urb: %p", tmp);
        // we can accept this urb if it is not queued at this time 
        // or if non-iso transfer requests should be scheduled for the same device and pipe
        if ((!usb_pipeisoc(urb->pipe) && (tmp->dev == urb->dev) && !((tmp->pipe ^ urb->pipe) & mask)) ||
            (urb == tmp)) {
            return tmp;    // found another urb already queued for processing
        }
    }

    return 0;
}
/*-------------------------------------------------------------------*/
_static int uhci_submit_urb (urb_t *urb)
{
    uhci_t *s;
    urb_priv_t *urb_priv;
    int ret = 0, type;
    unsigned long flags;
    urb_t *queued_urb=NULL;
    int bustime;
        
    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;
    
    type = usb_pipetype (urb->pipe);

    if (usb_pipedevice (urb->pipe) == s->rh.devnum)
        return rh_submit_urb (urb);    /* virtual root hub */

    // Sanity checks
    if (usb_maxpacket (urb->dev, urb->pipe, usb_pipeout (urb->pipe)) <= 0) {        
        err("uhci_submit_urb: pipesize for pipe %x is zero", urb->pipe);
        return -EMSGSIZE;
    }

    if (urb->transfer_buffer_length < 0 && type != PIPE_ISOCHRONOUS) {
        err("uhci_submit_urb: Negative transfer length for urb %p", urb);
        return -EINVAL;
    }

    usb_inc_dev_use (urb->dev);

    spin_lock_irqsave (&s->urb_list_lock, flags);

    queued_urb = search_dev_ep (s, urb); // returns already queued urb for that pipe

    if (queued_urb) {

        queue_dbg("found bulk urb %p\n", queued_urb);

        if (( type != PIPE_BULK) ||
            ((type == PIPE_BULK) &&
             (!(urb->transfer_flags & USB_QUEUE_BULK) || !(queued_urb->transfer_flags & USB_QUEUE_BULK)))) {
            spin_unlock_irqrestore (&s->urb_list_lock, flags);
            usb_dec_dev_use (urb->dev);
            err("ENXIO %08x, flags %x, urb %p, burb %p",urb->pipe,urb->transfer_flags,urb,queued_urb);
            return -ENXIO;    // urb already queued
        }
    }

#ifdef DEBUG_SLAB
    urb_priv = kmem_cache_alloc(urb_priv_kmem, SLAB_FLAG);
#else
    urb_priv = kmalloc (sizeof (urb_priv_t), KMALLOC_FLAG);
#endif
    if (!urb_priv) {
        usb_dec_dev_use (urb->dev);
        spin_unlock_irqrestore (&s->urb_list_lock, flags);
        return -ENOMEM;
    }

    memset(urb_priv, 0, sizeof(urb_priv_t));
    urb->hcpriv = urb_priv;
    INIT_LIST_HEAD (&urb_priv->desc_list);

    dbg("submit_urb: scheduling %p", urb);
    
    if (type == PIPE_CONTROL)
        urb_priv->setup_packet_dma = pci_map_single(s->uhci_pci, urb->setup_packet,
                                sizeof(devrequest), PCI_DMA_TODEVICE);

    if (urb->transfer_buffer_length)
        urb_priv->transfer_buffer_dma = pci_map_single(s->uhci_pci,
                                   urb->transfer_buffer,
                                   urb->transfer_buffer_length,
                                   usb_pipein(urb->pipe) ?
                                   PCI_DMA_FROMDEVICE :
                                   PCI_DMA_TODEVICE);

    if (type == PIPE_BULK) {
    
        if (queued_urb) {
            while (((urb_priv_t*)queued_urb->hcpriv)->next_queued_urb)  // find last queued bulk
                queued_urb=((urb_priv_t*)queued_urb->hcpriv)->next_queued_urb;
            
            ((urb_priv_t*)queued_urb->hcpriv)->next_queued_urb=urb;
        }
        atomic_inc (&s->avoid_bulk);
        ret = uhci_submit_bulk_urb (urb, queued_urb);
        atomic_dec (&s->avoid_bulk);
        spin_unlock_irqrestore (&s->urb_list_lock, flags);
    }
    else {
        spin_unlock_irqrestore (&s->urb_list_lock, flags);
        switch (type) {
        case PIPE_ISOCHRONOUS:            
            if (urb->bandwidth == 0) {      /* not yet checked/allocated */
                if (urb->number_of_packets <= 0) {
                    ret = -EINVAL;
                    break;
                }

                bustime = usb_check_bandwidth (urb->dev, urb);
                if (bustime < 0) 
                    ret = bustime;
                else {
                    ret = uhci_submit_iso_urb(urb);
                    if (ret == 0)
                        usb_claim_bandwidth (urb->dev, urb, bustime, 1);
                }
            } else {        /* bandwidth is already set */
                ret = uhci_submit_iso_urb(urb);
            }
            break;
        case PIPE_INTERRUPT:
            if (urb->bandwidth == 0) {      /* not yet checked/allocated */
                bustime = usb_check_bandwidth (urb->dev, urb);
                if (bustime < 0)
                    ret = bustime;
                else {
                    ret = uhci_submit_int_urb(urb);
                    if (ret == 0)
                        usb_claim_bandwidth (urb->dev, urb, bustime, 0);
                }
            } else {        /* bandwidth is already set */
                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) {
        uhci_urb_dma_unmap(s, urb, urb_priv);
        usb_dec_dev_use (urb->dev);
#ifdef DEBUG_SLAB
        kmem_cache_free(urb_priv_kmem, urb_priv);
#else
        kfree (urb_priv);
#endif
        return ret;
    }

    return 0;
}

// Checks for URB timeout and removes bandwidth reclamation if URB idles too long
_static void uhci_check_timeouts(uhci_t *s)
{
    struct list_head *p,*p2;
    urb_t *urb;
    int type;    

    p = s->urb_list.prev;    

    while (p != &s->urb_list) {
        urb_priv_t *hcpriv;

        p2 = p;
        p = p->prev;
        urb = list_entry (p2, urb_t, urb_list);
        type = usb_pipetype (urb->pipe);

        hcpriv = (urb_priv_t*)urb->hcpriv;
        
        if ( urb->timeout && time_after(jiffies, hcpriv->started + urb->timeout)) {
            urb->transfer_flags |= USB_TIMEOUT_KILLED | USB_ASYNC_UNLINK;
            async_dbg("uhci_check_timeout: timeout for %p",urb);
            uhci_unlink_urb_async(s, urb, UNLINK_ASYNC_STORE_URB);
        }
#ifdef CONFIG_USB_UHCI_HIGH_BANDWIDTH
        else if (((type == PIPE_BULK) || (type == PIPE_CONTROL)) &&  
             (hcpriv->use_loop) && time_after(jiffies, hcpriv->started + IDLE_TIMEOUT))
            disable_desc_loop(s, urb);
#endif

    }
    s->timeout_check=jiffies;
}

/*-------------------------------------------------------------------
 Virtual Root Hub
 -------------------------------------------------------------------*/

_static __u8 root_hub_dev_des[] =
{
    0x12,            /*  __u8  bLength; */
    0x01,            /*  __u8  bDescriptorType; Device */
    0x00,            /*  __u16 bcdUSB; v1.0 */
    0x01,
    0x09,            /*  __u8  bDeviceClass; HUB_CLASSCODE */
    0x00,            /*  __u8  bDeviceSubClass; */
    0x00,            /*  __u8  bDeviceProtocol; */
    0x08,            /*  __u8  bMaxPacketSize0; 8 Bytes */
    0x00,            /*  __u16 idVendor; */
    0x00,
    0x00,            /*  __u16 idProduct; */
    0x00,
    0x00,            /*  __u16 bcdDevice; */
    0x00,
    0x00,            /*  __u8  iManufacturer; */
    0x02,            /*  __u8  iProduct; */
    0x01,            /*  __u8  iSerialNumber; */
    0x01            /*  __u8  bNumConfigurations; */
};


/* Configuration descriptor */
_static __u8 root_hub_config_des[] =
{
    0x09,            /*  __u8  bLength; */
    0x02,            /*  __u8  bDescriptorType; Configuration */
    0x19,            /*  __u16 wTotalLength; */
    0x00,
    0x01,            /*  __u8  bNumInterfaces; */
    0x01,            /*  __u8  bConfigurationValue; */
    0x00,            /*  __u8  iConfiguration; */
    0x40,            /*  __u8  bmAttributes; 
                   Bit 7: Bus-powered, 6: Self-powered, 5 Remote-wakwup, 4..0: resvd */
    0x00,            /*  __u8  MaxPower; */

     /* interface */
    0x09,            /*  __u8  if_bLength; */
    0x04,            /*  __u8  if_bDescriptorType; Interface */
    0x00,            /*  __u8  if_bInterfaceNumber; */
    0x00,            /*  __u8  if_bAlternateSetting; */
    0x01,            /*  __u8  if_bNumEndpoints; */
    0x09,            /*  __u8  if_bInterfaceClass; HUB_CLASSCODE */
    0x00,            /*  __u8  if_bInterfaceSubClass; */
    0x00,            /*  __u8  if_bInterfaceProtocol; */
    0x00,            /*  __u8  if_iInterface; */

     /* endpoint */
    0x07,            /*  __u8  ep_bLength; */
    0x05,            /*  __u8  ep_bDescriptorType; Endpoint */
    0x81,            /*  __u8  ep_bEndpointAddress; IN Endpoint 1 */
    0x03,            /*  __u8  ep_bmAttributes; Interrupt */
    0x08,            /*  __u16 ep_wMaxPacketSize; 8 Bytes */
    0x00,
    0xff            /*  __u8  ep_bInterval; 255 ms */
};


_static __u8 root_hub_hub_des[] =
{
    0x09,            /*  __u8  bLength; */
    0x29,            /*  __u8  bDescriptorType; Hub-descriptor */
    0x02,            /*  __u8  bNbrPorts; */
    0x00,            /* __u16  wHubCharacteristics; */
    0x00,
    0x01,            /*  __u8  bPwrOn2pwrGood; 2ms */
    0x00,            /*  __u8  bHubContrCurrent; 0 mA */
    0x00,            /*  __u8  DeviceRemovable; *** 7 Ports max *** */
    0xff            /*  __u8  PortPwrCtrlMask; *** 7 ports max *** */
};

/*-------------------------------------------------------------------------*/
/* prepare Interrupt pipe transaction data; HUB INTERRUPT ENDPOINT */
_static int rh_send_irq (urb_t *urb)
{
    int len = 1;
    int i;
    uhci_t *uhci = urb->dev->bus->hcpriv;
    unsigned int io_addr = uhci->io_addr;
    __u16 data = 0;

    for (i = 0; i < uhci->rh.numports; i++) {
        data |= ((inw (io_addr + USBPORTSC1 + i * 2) & 0xa) > 0 ? (1 << (i + 1)) : 0);
        len = (i + 1) / 8 + 1;
    }

    *(__u16 *) urb->transfer_buffer = cpu_to_le16 (data);
    urb->actual_length = len;
    urb->status = 0;
    
    if ((data > 0) && (uhci->rh.send != 0)) {
        dbg("Root-Hub INT complete: port1: %x port2: %x data: %x",
             inw (io_addr + USBPORTSC1), inw (io_addr + USBPORTSC2), data);
        urb->complete (urb);
    }
    return 0;
}

/*-------------------------------------------------------------------------*/
/* Virtual Root Hub INTs are polled by this timer every "intervall" ms */
_static int rh_init_int_timer (urb_t *urb);

_static void rh_int_timer_do (unsigned long ptr)
{
    int len;
    urb_t *urb = (urb_t*) ptr;
    uhci_t *uhci = urb->dev->bus->hcpriv;

    if (uhci->rh.send) {
        len = rh_send_irq (urb);
        if (len > 0) {
            urb->actual_length = len;
            if (urb->complete)
                urb->complete (urb);
        }
    }
    rh_init_int_timer (urb);
}

/*-------------------------------------------------------------------------*/
/* Root Hub INTs are polled by this timer, polling interval 20ms */

_static int rh_init_int_timer (urb_t *urb)
{
    uhci_t *uhci = urb->dev->bus->hcpriv;

    uhci->rh.interval = urb->interval;
    init_timer (&uhci->rh.rh_int_timer);
    uhci->rh.rh_int_timer.function = rh_int_timer_do;
    uhci->rh.rh_int_timer.data = (unsigned long) urb;
    uhci->rh.rh_int_timer.expires = jiffies + (HZ * 20) / 1000;
    add_timer (&uhci->rh.rh_int_timer);

    return 0;
}

/*-------------------------------------------------------------------------*/
#define OK(x)             len = (x); break

#define CLR_RH_PORTSTAT(x) \
        status = inw(io_addr+USBPORTSC1+2*(wIndex-1)); \
        status = (status & 0xfff5) & ~(x); \
        outw(status, io_addr+USBPORTSC1+2*(wIndex-1))

#define SET_RH_PORTSTAT(x) \
        status = inw(io_addr+USBPORTSC1+2*(wIndex-1)); \
        status = (status & 0xfff5) | (x); \
        outw(status, io_addr+USBPORTSC1+2*(wIndex-1))


/*-------------------------------------------------------------------------*/
/****
 ** Root Hub Control Pipe
 *************************/


_static int rh_submit_urb (urb_t *urb)
{
    struct usb_device *usb_dev = urb->dev;
    uhci_t *uhci = usb_dev->bus->hcpriv;
    unsigned int pipe = urb->pipe;
    devrequest *cmd = (devrequest *) urb->setup_packet;
    void *data = urb->transfer_buffer;
    int leni = urb->transfer_buffer_length;
    int len = 0;
    int status = 0;
    int stat = 0;
    int i;
    unsigned int io_addr = uhci->io_addr;
    __u16 cstatus;

    __u16 bmRType_bReq;
    __u16 wValue;
    __u16 wIndex;
    __u16 wLength;

    if (usb_pipetype (pipe) == PIPE_INTERRUPT) {
        dbg("Root-Hub submit IRQ: every %d ms", urb->interval);
        uhci->rh.urb = urb;
        uhci->rh.send = 1;
        uhci->rh.interval = urb->interval;
        rh_init_int_timer (urb);

        return 0;
    }


    bmRType_bReq = cmd->requesttype | cmd->request << 8;
    wValue = le16_to_cpu (cmd->value);
    wIndex = le16_to_cpu (cmd->index);
    wLength = le16_to_cpu (cmd->length);

    for (i = 0; i < 8; i++)
        uhci->rh.c_p_r[i] = 0;

    dbg("Root-Hub: adr: %2x cmd(%1x): %04x %04x %04x %04x",
         uhci->rh.devnum, 8, bmRType_bReq, wValue, wIndex, wLength);

    switch (bmRType_bReq) {
        /* Request Destination:
           without flags: Device, 
           RH_INTERFACE: interface, 
           RH_ENDPOINT: endpoint,
           RH_CLASS means HUB here, 
           RH_OTHER | RH_CLASS  almost ever means HUB_PORT here 
         */

    case RH_GET_STATUS:
        *(__u16 *) data = cpu_to_le16 (1);
        OK (2);
    case RH_GET_STATUS | RH_INTERFACE:
        *(__u16 *) data = cpu_to_le16 (0);
        OK (2);
    case RH_GET_STATUS | RH_ENDPOINT:
        *(__u16 *) data = cpu_to_le16 (0);
        OK (2);
    case RH_GET_STATUS | RH_CLASS:
        *(__u32 *) data = cpu_to_le32 (0);
        OK (4);        /* hub power ** */
    case RH_GET_STATUS | RH_OTHER | RH_CLASS:
        status = inw (io_addr + USBPORTSC1 + 2 * (wIndex - 1));
        cstatus = ((status & USBPORTSC_CSC) >> (1 - 0)) |
            ((status & USBPORTSC_PEC) >> (3 - 1)) |
            (uhci->rh.c_p_r[wIndex - 1] << (0 + 4));
        status = (status & USBPORTSC_CCS) |
            ((status & USBPORTSC_PE) >> (2 - 1)) |
            ((status & USBPORTSC_SUSP) >> (12 - 2)) |
            ((status & USBPORTSC_PR) >> (9 - 4)) |
            (1 << 8) |    /* power on ** */
            ((status & USBPORTSC_LSDA) << (-8 + 9));

        *(__u16 *) data = cpu_to_le16 (status);
        *(__u16 *) (data + 2) = cpu_to_le16 (cstatus);
        OK (4);

    case RH_CLEAR_FEATURE | RH_ENDPOINT:
        switch (wValue) {
        case (RH_ENDPOINT_STALL):
            OK (0);
        }
        break;

    case RH_CLEAR_FEATURE | RH_CLASS:
        switch (wValue) {
        case (RH_C_HUB_OVER_CURRENT):
            OK (0);    /* hub power over current ** */
        }
        break;

    case RH_CLEAR_FEATURE | RH_OTHER | RH_CLASS:
        switch (wValue) {
        case (RH_PORT_ENABLE):
            CLR_RH_PORTSTAT (USBPORTSC_PE);
            OK (0);
        case (RH_PORT_SUSPEND):
            CLR_RH_PORTSTAT (USBPORTSC_SUSP);
            OK (0);
        case (RH_PORT_POWER):
            OK (0);    /* port power ** */
        case (RH_C_PORT_CONNECTION):
            SET_RH_PORTSTAT (USBPORTSC_CSC);
            OK (0);
        case (RH_C_PORT_ENABLE):
            SET_RH_PORTSTAT (USBPORTSC_PEC);
            OK (0);
        case (RH_C_PORT_SUSPEND):
/*** WR_RH_PORTSTAT(RH_PS_PSSC); */
            OK (0);
        case (RH_C_PORT_OVER_CURRENT):
            OK (0);    /* port power over current ** */
        case (RH_C_PORT_RESET):
            uhci->rh.c_p_r[wIndex - 1] = 0;
            OK (0);
        }
        break;

    case RH_SET_FEATURE | RH_OTHER | RH_CLASS:
        switch (wValue) {
        case (RH_PORT_SUSPEND):
            SET_RH_PORTSTAT (USBPORTSC_SUSP);
            OK (0);
        case (RH_PORT_RESET):
            SET_RH_PORTSTAT (USBPORTSC_PR);
            uhci_wait_ms (10);
            uhci->rh.c_p_r[wIndex - 1] = 1;
            CLR_RH_PORTSTAT (USBPORTSC_PR);
            udelay (10);
            SET_RH_PORTSTAT (USBPORTSC_PE);
            uhci_wait_ms (10);
            SET_RH_PORTSTAT (0xa);
            OK (0);
        case (RH_PORT_POWER):
            OK (0);    /* port power ** */
        case (RH_PORT_ENABLE):
            SET_RH_PORTSTAT (USBPORTSC_PE);
            OK (0);
        }
        break;

    case RH_SET_ADDRESS:
        uhci->rh.devnum = wValue;
        OK (0);

    case RH_GET_DESCRIPTOR:
        switch ((wValue & 0xff00) >> 8) {
        case (0x01):    /* device descriptor */
            len = min_t(unsigned int, leni,
                  min_t(unsigned int,
                      sizeof (root_hub_dev_des), wLength));
            memcpy (data, root_hub_dev_des, len);
            OK (len);
        case (0x02):    /* configuration descriptor */
            len = min_t(unsigned int, leni,
                  min_t(unsigned int,
                      sizeof (root_hub_config_des), wLength));
            memcpy (data, root_hub_config_des, len);
            OK (len);
        case (0x03):    /* string descriptors */
            len = usb_root_hub_string (wValue & 0xff,
                    uhci->io_addr, "UHCI",
                data, wLength);
            if (len > 0) {
                OK(min_t(int, leni, len));
            } else 
                stat = -EPIPE;
        }
        break;

    case RH_GET_DESCRIPTOR | RH_CLASS:
        root_hub_hub_des[2] = uhci->rh.numports;
        len = min_t(unsigned int, leni,
              min_t(unsigned int, sizeof (root_hub_hub_des), wLength));
        memcpy (data, root_hub_hub_des, len);
        OK (len);

    case RH_GET_CONFIGURATION:
        *(__u8 *) data = 0x01;
        OK (1);

    case RH_SET_CONFIGURATION:
        OK (0);
    default:
        stat = -EPIPE;
    }

    dbg("Root-Hub stat port1: %x port2: %x",
         inw (io_addr + USBPORTSC1), inw (io_addr + USBPORTSC2));

    urb->actual_length = len;
    urb->status = stat;
    urb->dev=NULL;
    if (urb->complete)
        urb->complete (urb);
    return 0;
}
/*-------------------------------------------------------------------------*/

_static int rh_unlink_urb (urb_t *urb)
{
    uhci_t *uhci = urb->dev->bus->hcpriv;

    if (uhci->rh.urb==urb) {
        dbg("Root-Hub unlink IRQ");
        uhci->rh.send = 0;
        del_timer (&uhci->rh.rh_int_timer);
    }
    return 0;
}
/*-------------------------------------------------------------------*/

/*
 * Map status to standard result codes
 *
 * <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)
{
    if (!status)
        return 0;
    if (status & TD_CTRL_BITSTUFF)    /* Bitstuff error */
        return -EPROTO;
    if (status & TD_CTRL_CRCTIMEO) {    /* CRC/Timeout */
        if (dir_out)
            return -ETIMEDOUT;
        else
            return -EILSEQ;
    }
    if (status & TD_CTRL_NAK)    /* NAK */
        return -ETIMEDOUT;
    if (status & TD_CTRL_BABBLE)    /* Babble */
        return -EOVERFLOW;
    if (status & TD_CTRL_DBUFERR)    /* Buffer error */
        return -ENOSR;
    if (status & TD_CTRL_STALLED)    /* Stalled */
        return -EPIPE;
    if (status & TD_CTRL_ACTIVE)    /* Active */
        return 0;

    return -EPROTO;
}

/*
 * Only the USB core should call uhci_alloc_dev and uhci_free_dev
 */
_static int uhci_alloc_dev (struct usb_device *usb_dev)
{
    return 0;
}

_static void uhci_unlink_urbs(uhci_t *s, struct usb_device *usb_dev, int remove_all)
{
    unsigned long flags;
    struct list_head *p;
    struct list_head *p2;
    urb_t *urb;

    spin_lock_irqsave (&s->urb_list_lock, flags);
    p = s->urb_list.prev;    
    while (p != &s->urb_list) {
        p2 = p;
        p = p->prev ;
        urb = list_entry (p2, urb_t, urb_list);
        dbg("urb: %p, dev %p, %p", urb, usb_dev,urb->dev);
        
        //urb->transfer_flags |=USB_ASYNC_UNLINK; 
            
        if (remove_all || (usb_dev == urb->dev)) {
            spin_unlock_irqrestore (&s->urb_list_lock, flags);
            warn("forced removing of queued URB %p due to disconnect",urb);
            uhci_unlink_urb(urb);
            urb->dev = NULL; // avoid further processing of this URB
            spin_lock_irqsave (&s->urb_list_lock, flags);
            p = s->urb_list.prev;    
        }
    }
    spin_unlock_irqrestore (&s->urb_list_lock, flags);
}

_static int uhci_free_dev (struct usb_device *usb_dev)
{
    uhci_t *s;
    

    if(!usb_dev || !usb_dev->bus || !usb_dev->bus->hcpriv)
        return -EINVAL;
    
    s=(uhci_t*) usb_dev->bus->hcpriv;    
    uhci_unlink_urbs(s, usb_dev, 0);

    return 0;
}

/*
 * uhci_get_current_frame_number()
 *
 * returns the current frame number for a USB bus/controller.
 */
_static int uhci_get_current_frame_number (struct usb_device *usb_dev)
{
    return UHCI_GET_CURRENT_FRAME ((uhci_t*) usb_dev->bus->hcpriv);
}

struct usb_operations uhci_device_operations =
{
    uhci_alloc_dev,
    uhci_free_dev,
    uhci_get_current_frame_number,
    uhci_submit_urb,
    uhci_unlink_urb
};

_static void correct_data_toggles(urb_t *urb)
{
    usb_settoggle (urb->dev, usb_pipeendpoint (urb->pipe), usb_pipeout (urb->pipe), 
               !usb_gettoggle (urb->dev, usb_pipeendpoint (urb->pipe), usb_pipeout (urb->pipe)));

    while(urb) {
        urb_priv_t *priv=urb->hcpriv;        
        uhci_desc_t *qh = list_entry (priv->desc_list.next, uhci_desc_t, desc_list);
        struct list_head *p = qh->vertical.next;
        uhci_desc_t *td;
        dbg("URB to correct %p\n", urb);
    
        for (; p != &qh->vertical; p = p->next) {
            td = list_entry (p, uhci_desc_t, vertical);
            td->hw.td.info^=cpu_to_le32(1<<TD_TOKEN_TOGGLE);
        }
        urb=priv->next_queued_urb;
    }
}

/* 
 * For IN-control transfers, process_transfer gets a bit more complicated,
 * since there are devices that return less data (eg. strings) than they
 * have announced. This leads to a queue abort due to the short packet,
 * the status stage is not executed. If this happens, the status stage
 * is manually re-executed.
 * mode: PROCESS_TRANSFER_REGULAR: regular (unlink QH)
 *       PROCESS_TRANSFER_DONT_UNLINK: QHs already unlinked (for async unlink_urb)
 */

_static int process_transfer (uhci_t *s, urb_t *urb, int mode)
{
    int ret = 0;
    urb_priv_t *urb_priv = urb->hcpriv;
    struct list_head *qhl = urb_priv->desc_list.next;
    uhci_desc_t *qh = list_entry (qhl, uhci_desc_t, desc_list);
    struct list_head *p = qh->vertical.next;
    uhci_desc_t *desc= list_entry (urb_priv->desc_list.prev, uhci_desc_t, desc_list);
    uhci_desc_t *last_desc = list_entry (desc->vertical.prev, uhci_desc_t, vertical);
    int data_toggle = usb_gettoggle (urb->dev, usb_pipeendpoint (urb->pipe), usb_pipeout (urb->pipe));    // save initial data_toggle
    int maxlength;     // extracted and remapped info from TD
    int actual_length;
    int status = 0;

    //dbg("process_transfer: urb %p, urb_priv %p, qh %p last_desc %p\n",urb,urb_priv, qh, last_desc);

    /* if the status phase has been retriggered and the
       queue is empty or the last status-TD is inactive, the retriggered
       status stage is completed
     */

    if (urb_priv->flags && 
        ((qh->hw.qh.element == cpu_to_le32(UHCI_PTR_TERM)) || !is_td_active(desc)))
        goto transfer_finished;

    urb->actual_length=0;

    for (; p != &qh->vertical; p = p->next) {
        desc = list_entry (p, uhci_desc_t, vertical);

        if (is_td_active(desc)) {    // do not process active TDs
            if (mode == CLEAN_TRANSFER_DELETION_MARK) // if called from async_unlink
                uhci_clean_transfer(s, urb, qh, CLEAN_TRANSFER_DELETION_MARK);
            return ret;
        }
    
        actual_length = uhci_actual_length(le32_to_cpu(desc->hw.td.status));        // extract transfer parameters from TD
        maxlength = (((le32_to_cpu(desc->hw.td.info) >> 21) & 0x7ff) + 1) & 0x7ff;
        status = uhci_map_status (uhci_status_bits (le32_to_cpu(desc->hw.td.status)), usb_pipeout (urb->pipe));

        if (status == -EPIPE) {         // see if EP is stalled
            // set up stalled condition
            usb_endpoint_halt (urb->dev, usb_pipeendpoint (urb->pipe), usb_pipeout (urb->pipe));
        }

        if (status && (status != -EPIPE)) {    // if any error occurred stop processing of further TDs
            // only set ret if status returned an error
  is_error:
            ret = status;
            urb->error_count++;
            break;
        }
        else if ((le32_to_cpu(desc->hw.td.info) & 0xff) != USB_PID_SETUP)
            urb->actual_length += actual_length;

        // got less data than requested
        if ( (actual_length < maxlength)) {
            if (urb->transfer_flags & USB_DISABLE_SPD) {
                status = -EREMOTEIO;    // treat as real error
                dbg("process_transfer: SPD!!");
                break;    // exit after this TD because SP was detected
            }

            // short read during control-IN: re-start status stage
            if ((usb_pipetype (urb->pipe) == PIPE_CONTROL)) {
                if (uhci_packetid(le32_to_cpu(last_desc->hw.td.info)) == USB_PID_OUT) {
            
                    set_qh_element(qh, last_desc->dma_addr);  // re-trigger status stage
                    dbg("short packet during control transfer, retrigger status stage @ %p",last_desc);
                    urb_priv->flags = 1; // mark as short control packet
                    return 0;
                }
            }
            // all other cases: short read is OK
            data_toggle = uhci_toggle (le32_to_cpu(desc->hw.td.info));
            break;
        }
        else if (status)
            goto is_error;

        data_toggle = uhci_toggle (le32_to_cpu(desc->hw.td.info));
        queue_dbg("process_transfer: len:%d status:%x mapped:%x toggle:%d", actual_length, le32_to_cpu(desc->hw.td.status),status, data_toggle);      

    }

    if (usb_pipetype (urb->pipe) == PIPE_BULK ) {  /* toggle correction for short bulk transfers (nonqueued/queued) */

        urb_priv_t *priv=(urb_priv_t*)urb->hcpriv;
        urb_t *next_queued_urb=priv->next_queued_urb;

        if (next_queued_urb) {
            urb_priv_t *next_priv=(urb_priv_t*)next_queued_urb->hcpriv;
            uhci_desc_t *qh = list_entry (next_priv->desc_list.next, uhci_desc_t, desc_list);
            uhci_desc_t *first_td=list_entry (qh->vertical.next, uhci_desc_t, vertical);

            if (data_toggle == uhci_toggle (le32_to_cpu(first_td->hw.td.info))) {
                err("process_transfer: fixed toggle");
                correct_data_toggles(next_queued_urb);
            }                        
        }
        else
            usb_settoggle (urb->dev, usb_pipeendpoint (urb->pipe), usb_pipeout (urb->pipe), !data_toggle);        
    }

 transfer_finished:
    
    uhci_clean_transfer(s, urb, qh, mode);

    urb->status = status;

#ifdef CONFIG_USB_UHCI_HIGH_BANDWIDTH    
    disable_desc_loop(s,urb);
#endif    

    queue_dbg("process_transfer: (end) urb %p, wanted len %d, len %d status %x err %d",
        urb,urb->transfer_buffer_length,urb->actual_length, urb->status, urb->error_count);
    return ret;
}

_static int process_interrupt (uhci_t *s, urb_t *urb)
{
    int i, ret = -EINPROGRESS;
    urb_priv_t *urb_priv = urb->hcpriv;
    struct list_head *p = urb_priv->desc_list.next;
    uhci_desc_t *desc = list_entry (urb_priv->desc_list.prev, uhci_desc_t, desc_list);

    int actual_length;
    int status = 0;

    //dbg("urb contains interrupt request");

    for (i = 0; p != &urb_priv->desc_list; p = p->next, i++)    // Maybe we allow more than one TD later ;-)
    {
        desc = list_entry (p, uhci_desc_t, desc_list);

        if (is_td_active(desc)) {
            // do not process active TDs
            //dbg("TD ACT Status @%p %08x",desc,le32_to_cpu(desc->hw.td.status));
            break;
        }

        if (!(desc->hw.td.status & cpu_to_le32(TD_CTRL_IOC))) {
            // do not process one-shot TDs, no recycling
            break;
        }
        // extract transfer parameters from TD

        actual_length = uhci_actual_length(le32_to_cpu(desc->hw.td.status));
        status = uhci_map_status (uhci_status_bits (le32_to_cpu(desc->hw.td.status)), usb_pipeout (urb->pipe));

        // see if EP is stalled
        if (status == -EPIPE) {
            // set up stalled condition
            usb_endpoint_halt (urb->dev, usb_pipeendpoint (urb->pipe), usb_pipeout (urb->pipe));
        }

        // if any error occurred: ignore this td, and continue
        if (status != 0) {
            //uhci_show_td (desc);
            urb->error_count++;
            goto recycle;
        }
        else
            urb->actual_length = actual_length;

    recycle:
        uhci_urb_dma_sync(s, urb, urb->hcpriv);
        if (urb->complete) {
            //dbg("process_interrupt: calling completion, status %i",status);
            urb->status = status;
            ((urb_priv_t*)urb->hcpriv)->flags=1; // if unlink_urb is called during completion

            spin_unlock(&s->urb_list_lock);
            
            urb->complete ((struct urb *) urb);
            
            spin_lock(&s->urb_list_lock);

            ((urb_priv_t*)urb->hcpriv)->flags=0;                           
        }
        
        if ((urb->status != -ECONNABORTED) && (urb->status != ECONNRESET) &&
                (urb->status != -ENOENT)) {

            urb->status = -EINPROGRESS;

            // Recycle INT-TD if interval!=0, else mark TD as one-shot
            if (urb->interval) {
                
                desc->hw.td.info &= cpu_to_le32(~(1 << TD_TOKEN_TOGGLE));
                if (status==0) {
                    ((urb_priv_t*)urb->hcpriv)->started=jiffies;
                    desc->hw.td.info |= cpu_to_le32((usb_gettoggle (urb->dev, usb_pipeendpoint (urb->pipe),
                                        usb_pipeout (urb->pipe)) << TD_TOKEN_TOGGLE));
                    usb_dotoggle (urb->dev, usb_pipeendpoint (urb->pipe), usb_pipeout (urb->pipe));
                } else {
                    desc->hw.td.info |= cpu_to_le32((!usb_gettoggle (urb->dev, usb_pipeendpoint (urb->pipe),
                                         usb_pipeout (urb->pipe)) << TD_TOKEN_TOGGLE));
                }
                desc->hw.td.status= cpu_to_le32((urb->pipe & TD_CTRL_LS) | TD_CTRL_ACTIVE | TD_CTRL_IOC |
                    (urb->transfer_flags & USB_DISABLE_SPD ? 0 : TD_CTRL_SPD) | (3 << 27));
                mb();
            }
            else {
                uhci_unlink_urb_async(s, urb, UNLINK_ASYNC_STORE_URB);
                // correct toggle after unlink
                usb_dotoggle (urb->dev, usb_pipeendpoint (urb->pipe), usb_pipeout (urb->pipe));
                clr_td_ioc(desc); // inactivate TD
            }
        }
    }

    return ret;
}

// mode: PROCESS_ISO_REGULAR: processing only for done TDs, unlink TDs
// mode: PROCESS_ISO_FORCE: force processing, don't unlink TDs (already unlinked)

_static int process_iso (uhci_t *s, urb_t *urb, int mode)
{
    int i;
    int ret = 0;
    urb_priv_t *urb_priv = urb->hcpriv;
    struct list_head *p = urb_priv->desc_list.next, *p_tmp;
    uhci_desc_t *desc = list_entry (urb_priv->desc_list.prev, uhci_desc_t, desc_list);

    dbg("urb contains iso request");
    if (is_td_active(desc) && mode==PROCESS_ISO_REGULAR)
        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,le32_to_cpu(desc->hw.td.status));

    for (i = 0; p != &urb_priv->desc_list;  i++) {
        desc = list_entry (p, uhci_desc_t, desc_list);
        
        //uhci_show_td(desc);
        if (is_td_active(desc)) {
            // means we have completed the last TD, but not the TDs before
            desc->hw.td.status &= cpu_to_le32(~TD_CTRL_ACTIVE);
            dbg("TD still active (%x)- grrr. paranoia!", le32_to_cpu(desc->hw.td.status));
            ret = -EXDEV;
            urb->iso_frame_desc[i].status = ret;
            unlink_td (s, desc, 1);
            // FIXME: immediate deletion may be dangerous
            goto err;
        }

        if (mode == PROCESS_ISO_REGULAR)
            unlink_td (s, desc, 1);

        if (urb->number_of_packets <= i) {
            dbg("urb->number_of_packets (%d)<=(%d)", urb->number_of_packets, i);
            ret = -EINVAL;
            goto err;
        }

        urb->iso_frame_desc[i].actual_length = uhci_actual_length(le32_to_cpu(desc->hw.td.status));
        urb->iso_frame_desc[i].status = uhci_map_status (uhci_status_bits (le32_to_cpu(desc->hw.td.status)), usb_pipeout (urb->pipe));
        urb->actual_length += urb->iso_frame_desc[i].actual_length;

          err:

        if (urb->iso_frame_desc[i].status != 0) {
            urb->error_count++;
            urb->status = urb->iso_frame_desc[i].status;
        }
        dbg("process_iso: %i: len:%d %08x status:%x",
             i, urb->iso_frame_desc[i].actual_length, le32_to_cpu(desc->hw.td.status),urb->iso_frame_desc[i].status);

        p_tmp = p;
        p = p->next;
        list_del (p_tmp);
        delete_desc (s, desc);
    }
    
    dbg("process_iso: exit %i (%d), actual_len %i", i, ret,urb->actual_length);
    return ret;
}


_static int process_urb (uhci_t *s, struct list_head *p)
{
    int ret = 0;
    urb_t *urb;

    urb=list_entry (p, urb_t, urb_list);
    //dbg("process_urb: found queued urb: %p", urb);

    switch (usb_pipetype (urb->pipe)) {
    case PIPE_CONTROL:
        ret = process_transfer (s, urb, CLEAN_TRANSFER_REGULAR);
        break;
    case PIPE_BULK:
        if (!s->avoid_bulk.counter)
            ret = process_transfer (s, urb, CLEAN_TRANSFER_REGULAR);
        else
            return 0;
        break;
    case PIPE_ISOCHRONOUS:
        ret = process_iso (s, urb, PROCESS_ISO_REGULAR);
        break;
    case PIPE_INTERRUPT:
        ret = process_interrupt (s, urb);
        break;
    }

    if (urb->status != -EINPROGRESS) {
        urb_priv_t *urb_priv;
        struct usb_device *usb_dev;
        
        usb_dev=urb->dev;

        /* Release bandwidth for Interrupt or Iso transfers */
        if (urb->bandwidth) {
            if (usb_pipetype(urb->pipe)==PIPE_ISOCHRONOUS)
                usb_release_bandwidth (urb->dev, urb, 1);
            else if (usb_pipetype(urb->pipe)==PIPE_INTERRUPT && urb->interval)
                usb_release_bandwidth (urb->dev, urb, 0);
        }

        dbg("dequeued urb: %p", urb);
        dequeue_urb (s, urb);

        urb_priv = urb->hcpriv;

        uhci_urb_dma_unmap(s, urb, urb_priv);

#ifdef DEBUG_SLAB
        kmem_cache_free(urb_priv_kmem, urb_priv);
#else
        kfree (urb_priv);
#endif

        if ((usb_pipetype (urb->pipe) != PIPE_INTERRUPT)) {  // process_interrupt does completion on its own        
            urb_t *next_urb = urb->next;
            int is_ring = 0;
            int contains_killed = 0;
            int loop_count=0;
            
            if (next_urb) {
                // Find out if the URBs are linked to a ring
                while  (next_urb != NULL && next_urb != urb && loop_count < MAX_NEXT_COUNT) {
                    if (next_urb->status == -ENOENT) {// killed URBs break ring structure & resubmission
                        contains_killed = 1;
                        break;
                    }    
                    next_urb = next_urb->next;
                    loop_count++;
                }
                
                if (loop_count == MAX_NEXT_COUNT)
                    err("process_urb: Too much linked URBs in ring detection!");

                if (next_urb == urb)
                    is_ring=1;
            }            

            // Submit idle/non-killed URBs linked with urb->next
            // Stop before the current URB                
            
            next_urb = urb->next;    
            if (next_urb && !contains_killed) {
                int ret_submit;
                next_urb = urb->next;    
                
                loop_count=0;
                while (next_urb != NULL && next_urb != urb && loop_count < MAX_NEXT_COUNT) {
                    if (next_urb->status != -EINPROGRESS) {
                    
                        if (next_urb->status == -ENOENT) 
                            break;

                        spin_unlock(&s->urb_list_lock);

                        ret_submit=uhci_submit_urb(next_urb);
                        spin_lock(&s->urb_list_lock);
                        
                        if (ret_submit)
                            break;                        
                    }
                    loop_count++;
                    next_urb = next_urb->next;
                }
                if (loop_count == MAX_NEXT_COUNT)
                    err("process_urb: Too much linked URBs in resubmission!");
            }

            // Completion
            if (urb->complete) {
                int was_unlinked = (urb->status == -ENOENT);
                urb->dev = NULL;
                spin_unlock(&s->urb_list_lock);

                urb->complete ((struct urb *) urb);

                // Re-submit the URB if ring-linked
                if (is_ring && !was_unlinked && !contains_killed) {
                    urb->dev=usb_dev;
                    uhci_submit_urb (urb);
                }
                spin_lock(&s->urb_list_lock);
            }
            
            usb_dec_dev_use (usb_dev);
        }
    }

    return ret;
}

_static void uhci_interrupt (int irq, void *__uhci, struct pt_regs *regs)
{
    uhci_t *s = __uhci;
    unsigned int io_addr = s->io_addr;
    unsigned short status;
    struct list_head *p, *p2;
    int restarts, work_done;
    
    /*
     * Read the interrupt status, and write it back to clear the
     * interrupt cause
     */

    status = inw (io_addr + USBSTS);

    if (!status)        /* shared interrupt, not mine */
        return;

    dbg("interrupt");

    if (status != 1) {
        // Avoid too much error messages at a time
        if (time_after(jiffies, s->last_error_time + ERROR_SUPPRESSION_TIME)) {
            warn("interrupt, status %x, frame# %i", status, 
                 UHCI_GET_CURRENT_FRAME(s));
            s->last_error_time = jiffies;
        }
        
        // remove host controller halted state
        if ((status&0x20) && (s->running)) {
            err("Host controller halted, trying to restart.");
            outw (USBCMD_RS | inw(io_addr + USBCMD), io_addr + USBCMD);
        }
        //uhci_show_status (s);
    }
    /*
     * traverse the list in *reverse* direction, because new entries
     * may be added at the end.
     * also, because process_urb may unlink the current urb,
     * we need to advance the list before
     * New: check for max. workload and restart count
     */

    spin_lock (&s->urb_list_lock);

    restarts=0;
    work_done=0;

restart:
    s->unlink_urb_done=0;
    p = s->urb_list.prev;    

    while (p != &s->urb_list && (work_done < 1024)) {
        p2 = p;
        p = p->prev;

        process_urb (s, p2);
        
        work_done++;

        if (s->unlink_urb_done) {
            s->unlink_urb_done=0;
            restarts++;
            
            if (restarts<16)    // avoid endless restarts
                goto restart;
            else 
                break;
        }
    }
    if (time_after(jiffies, s->timeout_check + (HZ/30)))
        uhci_check_timeouts(s);

    clean_descs(s, CLEAN_NOT_FORCED);
    uhci_cleanup_unlink(s, CLEAN_NOT_FORCED);
    uhci_switch_timer_int(s);
                            
    spin_unlock (&s->urb_list_lock);
    
    outw (status, io_addr + USBSTS);

    //dbg("uhci_interrupt: done");
}

_static void reset_hc (uhci_t *s)
{
    unsigned int io_addr = s->io_addr;

    s->apm_state = 0;
    /* Global reset for 50ms */
    outw (USBCMD_GRESET, io_addr + USBCMD);
    uhci_wait_ms (50);
    outw (0, io_addr + USBCMD);
    uhci_wait_ms (10);
}

_static void start_hc (uhci_t *s)
{
    unsigned int io_addr = s->io_addr;
    int timeout = 10;

    /*
     * Reset the HC - this will force us to get a
     * new notification of any already connected
     * ports due to the virtual disconnect that it
     * implies.
     */
    outw (USBCMD_HCRESET, io_addr + USBCMD);

    while (inw (io_addr + USBCMD) & USBCMD_HCRESET) {
        if (!--timeout) {
            err("USBCMD_HCRESET timed out!");
            break;
        }
        udelay(1);
    }

    /* Turn on all interrupts */
    outw (USBINTR_TIMEOUT | USBINTR_RESUME | USBINTR_IOC | USBINTR_SP, io_addr + USBINTR);

    /* Start at frame 0 */
    outw (0, io_addr + USBFRNUM);
    outl (s->framelist_dma, io_addr + USBFLBASEADD);

    /* Run and mark it configured with a 64-byte max packet */
    outw (USBCMD_RS | USBCMD_CF | USBCMD_MAXP, io_addr + USBCMD);
    s->apm_state = 1;
    s->running = 1;
}

/* No  __devexit, since it maybe called from alloc_uhci() */
_static void
uhci_pci_remove (struct pci_dev *dev)
{
    uhci_t *s = pci_get_drvdata(dev);
    struct usb_device *root_hub = s->bus->root_hub;

    s->running = 0;            // Don't allow submit_urb

    if (root_hub)
        usb_disconnect (&root_hub);

    reset_hc (s);
    wait_ms (1);

    uhci_unlink_urbs (s, 0, CLEAN_FORCED);  // Forced unlink of remaining URBs
    uhci_cleanup_unlink (s, CLEAN_FORCED);  // force cleanup of async killed URBs
    
    usb_deregister_bus (s->bus);

    release_region (s->io_addr, s->io_size);
    free_irq (s->irq, s);
    usb_free_bus (s->bus);
    cleanup_skel (s);
    kfree (s);
}

_static int __init uhci_start_usb (uhci_t *s)
{                /* start it up */
    /* connect the virtual root hub */
    struct usb_device *usb_dev;

    usb_dev = usb_alloc_dev (NULL, s->bus);
    if (!usb_dev)
        return -1;

    s->bus->root_hub = usb_dev;
    usb_connect (usb_dev);

    if (usb_new_device (usb_dev) != 0) {
        usb_free_dev (usb_dev);
        return -1;
    }

    return 0;
}

#ifdef CONFIG_PM
_static int
uhci_pci_suspend (struct pci_dev *dev, u32 state)
{
    reset_hc((uhci_t *) pci_get_drvdata(dev));
    return 0;
}

_static int
uhci_pci_resume (struct pci_dev *dev)
{
    start_hc((uhci_t *) pci_get_drvdata(dev));
    return 0;
}
#endif

_static int __devinit alloc_uhci (struct pci_dev *dev, int irq, unsigned int io_addr, unsigned int io_size)
{
    uhci_t *s;
    struct usb_bus *bus;
    char buf[8], *bufp = buf;

#ifndef __sparc__
    sprintf(buf, "%d", irq);
#else
    bufp = __irq_itoa(irq);
#endif
    printk(KERN_INFO __FILE__ ": USB UHCI at I/O 0x%x, IRQ %s\n",
        io_addr, bufp);

    s = kmalloc (sizeof (uhci_t), GFP_KERNEL);
    if (!s)
        return -1;

    memset (s, 0, sizeof (uhci_t));
    INIT_LIST_HEAD (&s->free_desc);
    INIT_LIST_HEAD (&s->urb_list);
    INIT_LIST_HEAD (&s->urb_unlinked);
    spin_lock_init (&s->urb_list_lock);
    spin_lock_init (&s->qh_lock);
    spin_lock_init (&s->td_lock);
    atomic_set(&s->avoid_bulk, 0);
    s->irq = -1;
    s->io_addr = io_addr;
    s->io_size = io_size;
    s->uhci_pci=dev;

    bus = usb_alloc_bus (&uhci_device_operations);
    if (!bus) {
        kfree (s);
        return -1;
    }

    s->bus = bus;
    bus->hcpriv = s;

    /* UHCI specs says devices must have 2 ports, but goes on to say */
    /* they may have more but give no way to determine how many they */
    /* have, so default to 2 */
    /* According to the UHCI spec, Bit 7 is always set to 1. So we try */
    /* to use this to our advantage */

    for (s->maxports = 0; s->maxports < (io_size - 0x10) / 2; s->maxports++) {
        unsigned int portstatus;

        portstatus = inw (io_addr + 0x10 + (s->maxports * 2));
        dbg("port %i, adr %x status %x", s->maxports,
            io_addr + 0x10 + (s->maxports * 2), portstatus);
        if (!(portstatus & 0x0080))
            break;
    }
    warn("Detected %d ports", s->maxports);

    /* This is experimental so anything less than 2 or greater than 8 is */
    /*  something weird and we'll ignore it */
    if (s->maxports < 2 || s->maxports > 8) {
        dbg("Port count misdetected, forcing to 2 ports");
        s->maxports = 2;
    }

    s->rh.numports = s->maxports;
    s->loop_usage=0;
    if (init_skel (s)) {
        usb_free_bus (bus);
        kfree(s);
        return -1;
    }

    request_region (s->io_addr, io_size, MODNAME);
    reset_hc (s);
    usb_register_bus (s->bus);

    start_hc (s);

    if (request_irq (irq, uhci_interrupt, SA_SHIRQ, MODNAME, s)) {
        err("request_irq %d failed!",irq);
        usb_free_bus (bus);
        reset_hc (s);
        release_region (s->io_addr, s->io_size);
        cleanup_skel(s);
        kfree(s);
        return -1;
    }

    /* Enable PIRQ */
    pci_write_config_word (dev, USBLEGSUP, USBLEGSUP_DEFAULT);

    s->irq = irq;

    if(uhci_start_usb (s) < 0) {
        uhci_pci_remove(dev);
        return -1;
    }

    //chain new uhci device into global list
    pci_set_drvdata(dev, s);
    devs=s;

    return 0;
}

_static int __devinit
uhci_pci_probe (struct pci_dev *dev, const struct pci_device_id *id)
{
    int i;

    if (pci_enable_device(dev) < 0)
        return -ENODEV;
        
    if (!dev->irq) {
        err("found UHCI device with no IRQ assigned. check BIOS settings!");
        return -ENODEV;
    }
    
    pci_set_master(dev);

    /* Search for the IO base address.. */
    for (i = 0; i < 6; i++) {

        unsigned int io_addr = pci_resource_start(dev, i);
        unsigned int io_size = pci_resource_len(dev, i);
        if (!(pci_resource_flags(dev,i) & IORESOURCE_IO))
            continue;

        /* Is it already in use? */
        if (check_region (io_addr, io_size))
            break;
        /* disable legacy emulation */
        pci_write_config_word (dev, USBLEGSUP, 0);
    
        return alloc_uhci(dev, dev->irq, io_addr, io_size);
    }
    return -ENODEV;
}

/*-------------------------------------------------------------------------*/

static const struct pci_device_id __devinitdata uhci_pci_ids [] = { {

    /* handle any USB UHCI controller */
    class:         ((PCI_CLASS_SERIAL_USB << 8) | 0x00),
    class_mask:     ~0,

    /* no matter who makes it */
    vendor:        PCI_ANY_ID,
    device:        PCI_ANY_ID,
    subvendor:    PCI_ANY_ID,
    subdevice:    PCI_ANY_ID,

    }, { /* end: all zeroes */ }
};

MODULE_DEVICE_TABLE (pci, uhci_pci_ids);

static struct pci_driver uhci_pci_driver = {
    name:        "usb-uhci",
    id_table:    &uhci_pci_ids [0],

    probe:        uhci_pci_probe,
    remove:        uhci_pci_remove,

#ifdef    CONFIG_PM
    suspend:    uhci_pci_suspend,
    resume:        uhci_pci_resume,
#endif    /* PM */

};

/*-------------------------------------------------------------------------*/

static int __init uhci_hcd_init (void) 
{
    int retval;

#ifdef DEBUG_SLAB
    urb_priv_kmem = kmem_cache_create("urb_priv", sizeof(urb_priv_t), 0, SLAB_HWCACHE_ALIGN, NULL, NULL);
    
    if(!urb_priv_kmem) {
        err("kmem_cache_create for urb_priv_t failed (out of memory)");
        return -ENOMEM;
    }
#endif    
    info(VERSTR);

#ifdef CONFIG_USB_UHCI_HIGH_BANDWIDTH
    info("High bandwidth mode enabled");    
#endif

    retval = pci_module_init (&uhci_pci_driver);

#ifdef DEBUG_SLAB
    if (retval < 0 ) {
        if (kmem_cache_destroy(urb_priv_kmem))
            err("urb_priv_kmem remained");
    }
#endif
    
    info(DRIVER_VERSION ":" DRIVER_DESC);

    return retval;
}

static void __exit uhci_hcd_cleanup (void) 
{      
    pci_unregister_driver (&uhci_pci_driver);
    
#ifdef DEBUG_SLAB
    if(kmem_cache_destroy(urb_priv_kmem))
        err("urb_priv_kmem remained");
#endif
}

module_init (uhci_hcd_init);
module_exit (uhci_hcd_cleanup);


MODULE_AUTHOR( DRIVER_AUTHOR );
MODULE_DESCRIPTION( DRIVER_DESC );
MODULE_LICENSE("GPL");

:: Command execute ::

Enter:
 
Select:
 

:: Search ::
  - regexp 

:: Upload ::
 
[ Read-Only ]

:: Make Dir ::
 
[ Read-Only ]
:: Make File ::
 
[ Read-Only ]

:: Go Dir ::
 
:: Go File ::
 

--[ c99shell v. 1.0 pre-release build #13 powered by Captain Crunch Security Team | http://ccteam.ru | Generation time: 0.0382 ]--