diff options
author | Ralf Baechle <ralf@linux-mips.org> | 1999-06-17 13:25:08 +0000 |
---|---|---|
committer | Ralf Baechle <ralf@linux-mips.org> | 1999-06-17 13:25:08 +0000 |
commit | 59223edaa18759982db0a8aced0e77457d10c68e (patch) | |
tree | 89354903b01fa0a447bffeefe00df3044495db2e /drivers/usb/uhci.c | |
parent | db7d4daea91e105e3859cf461d7e53b9b77454b2 (diff) |
Merge with Linux 2.3.6. Sorry, this isn't tested on silicon, I don't
have a MIPS box at hand.
Diffstat (limited to 'drivers/usb/uhci.c')
-rw-r--r-- | drivers/usb/uhci.c | 741 |
1 files changed, 646 insertions, 95 deletions
diff --git a/drivers/usb/uhci.c b/drivers/usb/uhci.c index 3d8ccdb7d..73aab5fa1 100644 --- a/drivers/usb/uhci.c +++ b/drivers/usb/uhci.c @@ -19,6 +19,7 @@ */ /* 4/4/1999 added data toggle for interrupt pipes -keryan */ +/* 5/16/1999 added global toggles for bulk and control */ #include <linux/config.h> #include <linux/module.h> @@ -31,13 +32,19 @@ #include <linux/smp_lock.h> #include <linux/errno.h> +#include <linux/sched.h> +#include <linux/unistd.h> +#include <linux/smp_lock.h> + +#include <asm/uaccess.h> + + #include <asm/spinlock.h> #include <asm/io.h> #include <asm/irq.h> #include <asm/system.h> #include "uhci.h" -#include "inits.h" #ifdef CONFIG_APM #include <linux/apm_bios.h> @@ -45,33 +52,105 @@ static int handle_apm_event(apm_event_t event); static int apm_resume = 0; #endif +static int uhci_debug = 1; + #define compile_assert(x) do { switch (0) { case 1: case !(x): } } while (0) -static struct wait_queue *uhci_configure = NULL; +static DECLARE_WAIT_QUEUE_HEAD(uhci_configure); /* + * Map status to standard result codes + */ +static int uhci_map_status(int status, int dir_out) +{ + if (!status) + return USB_ST_NOERROR; + if (status & 0x02) /* Bitstuff error*/ + return USB_ST_BITSTUFF; + if (status & 0x04) { /* CRC/Timeout */ + if (dir_out) + return USB_ST_TIMEOUT; + else + return USB_ST_CRC; + } + if (status & 0x08) /* NAK */ + return USB_ST_TIMEOUT; + if (status & 0x10) /* Babble */ + return USB_ST_STALL; + if (status & 0x20) /* Buffer error */ + return USB_ST_BUFFERUNDERRUN; + if (status & 0x40) /* Stalled */ + return USB_ST_STALL; + if (status & 0x80) /* Active */ + return USB_ST_NOERROR; + return USB_ST_INTERNALERROR; +} +/* * Return the result of a TD.. */ -static int uhci_td_result(struct uhci_device *dev, struct uhci_td *td) +static int uhci_td_result(struct uhci_device *dev, struct uhci_td *td, unsigned long *rval) { unsigned int status; + struct uhci_td *tmp = td->first; + + if(rval) + *rval = 0; + + /* locate the first failing td, if any */ + + do { + status = (tmp->status >> 16) & 0xff; + if (status) { + /* must reset the toggle on first error */ + if (uhci_debug) { + printk("Set toggle from %x rval %d\n", (unsigned int)tmp, rval ? *rval : 0); + } + usb_settoggle(dev->usb, usb_pipeendpoint(tmp->info), (tmp->info >> 19) & 1); + break; + } else { + if(rval) + *rval += (tmp->status & 0x3ff) + 1; + } + if ((tmp->link & 1) || (tmp->link & 2)) + break; + tmp = bus_to_virt(tmp->link & ~0xF); + } while (1); + - status = (td->status >> 16) & 0xff; + if (!status) + return USB_ST_NOERROR; /* Some debugging code */ - if (status) { + if (uhci_debug /* && (!usb_pipeendpoint(tmp->info) || !(status & 0x08))*/ ) { int i = 10; - struct uhci_td *tmp = dev->control_td; - printk("uhci_td_result() failed with status %d\n", status); + + tmp = td->first; + printk("uhci_td_result() failed with status %x\n", status); show_status(dev->uhci); do { show_td(tmp); - tmp++; + if ((tmp->link & 1) || (tmp->link & 2)) + break; + tmp = bus_to_virt(tmp->link & ~0xF); if (!--i) break; - } while (tmp <= td); + } while (1); + } + + if (status & 0x40) { + /* endpoint has stalled - mark it halted */ + + usb_endpoint_halt(dev->usb, usb_pipeendpoint(tmp->info)); + return USB_ST_STALL; + } - return status; + + if (status == 0x80) { + /* still active */ + if (!rval) + return USB_ST_DATAUNDERRUN; + } + return uhci_map_status(status, usb_pipeout(tmp->info)); } /* @@ -205,8 +284,8 @@ static struct uhci_qh *uhci_qh_allocate(struct uhci_device *dev) static void uhci_qh_deallocate(struct uhci_qh *qh) { - if (qh->element != 1) - printk("qh %p leaving dangling entries? (%X)\n", qh, qh->element); +// if (qh->element != 1) +// printk("qh %p leaving dangling entries? (%X)\n", qh, qh->element); qh->element = 1; qh->link = 1; @@ -223,8 +302,10 @@ static struct uhci_td *uhci_td_allocate(struct uhci_device *dev) for (; (inuse = test_and_set_bit(0, &td->inuse)) != 0 && td < &dev->td[UHCI_MAXTD]; td++) ; - if (!inuse) + if (!inuse) { + td->inuse = 1; return(td); + } printk("ran out of td's for dev %p\n", dev); return(NULL); @@ -273,13 +354,14 @@ static void uhci_remove_irq_list(struct uhci_td *td) static int uhci_request_irq(struct usb_device *usb_dev, unsigned int pipe, usb_device_irq handler, int period, void *dev_id) { struct uhci_device *dev = usb_to_uhci(usb_dev); + struct uhci_device *root_hub=usb_to_uhci(dev->uhci->bus->root_hub); struct uhci_td *td = uhci_td_allocate(dev); struct uhci_qh *interrupt_qh = uhci_qh_allocate(dev); unsigned int destination, status; /* Destination: pipe destination with INPUT */ - destination = (pipe & 0x0007ff00) | 0x69; + destination = (pipe & 0x0007ff00) | 0x69; /* Status: slow/fast, Interrupt, Active, Short Packet Detect Infinite Errors */ status = (pipe & (1 << 26)) | (1 << 24) | (1 << 23) | (1 << 29) | (0 << 27); @@ -290,21 +372,270 @@ static int uhci_request_irq(struct usb_device *usb_dev, unsigned int pipe, usb_d td->link = 1; td->status = status; /* In */ - td->info = destination | (7 << 21); /* 8 bytes of data */ + td->info = destination | (7 << 21) | (usb_gettoggle(usb_dev, usb_pipeendpoint(pipe)) << 19); /* 8 bytes of data */ td->buffer = virt_to_bus(dev->data); + td->first = td; td->qh = interrupt_qh; - interrupt_qh->skel = &dev->uhci->root_hub->skel_int8_qh; + td->dev = usb_dev; + + /* if period 0, insert into fast q */ + + if (period == 0) { + td->inuse |= 2; + interrupt_qh->skel = &root_hub->skel_int2_qh; + } else + interrupt_qh->skel = &root_hub->skel_int8_qh; uhci_add_irq_list(dev->uhci, td, handler, dev_id); uhci_insert_td_in_qh(interrupt_qh, td); /* Add it into the skeleton */ - uhci_insert_qh(&dev->uhci->root_hub->skel_int8_qh, interrupt_qh); + uhci_insert_qh(interrupt_qh->skel, interrupt_qh); + return 0; +} + +/* + * Remove running irq td from queues + */ + +static int uhci_remove_irq(struct usb_device *usb_dev, unsigned int pipe, usb_device_irq handler, int period, void *dev_id) +{ + struct uhci_device *dev = usb_to_uhci(usb_dev); + struct uhci_device *root_hub=usb_to_uhci(dev->uhci->bus->root_hub); + struct uhci_td *td; + struct uhci_qh *interrupt_qh; + unsigned long flags; + struct list_head *head = &dev->uhci->interrupt_list; + struct list_head *tmp; + + spin_lock_irqsave(&irqlist_lock, flags); + + /* find the TD in the interrupt list */ + + tmp = head->next; + while (tmp != head) { + td = list_entry(tmp, struct uhci_td, irq_list); + if (td->dev_id == dev_id && td->completed == handler) { + + /* found the right one - let's remove it */ + + /* notify removal */ + + td->completed(USB_ST_REMOVED, NULL, td->dev_id); + + /* this is DANGEROUS - not sure whether this is right */ + + list_del(&td->irq_list); + uhci_remove_td(td); + interrupt_qh = td->qh; + uhci_remove_qh(interrupt_qh->skel, interrupt_qh); + uhci_td_deallocate(td); + uhci_qh_deallocate(interrupt_qh); + spin_unlock_irqrestore(&irqlist_lock, flags); + return USB_ST_NOERROR; + } + } + spin_unlock_irqrestore(&irqlist_lock, flags); + return USB_ST_INTERNALERROR; +} +/* + * Isochronous thread operations + */ + +int uhci_compress_isochronous(struct usb_device *usb_dev, void *_isodesc) +{ + struct uhci_iso_td *isodesc = (struct uhci_iso_td *)_isodesc; + char *data = isodesc->data; + int i, totlen = 0; + + for (i = 0; i < isodesc->num; i++) { + char *cdata = bus_to_virt(isodesc->td[i].buffer & ~0xF); + int n = (isodesc->td[i].status + 1) & 0x7FF; + + if ((cdata != data) && (n)) + memmove(data, cdata, n); + +#if 0 +if (n && n != 960) + printk("underrun: %d %d\n", i, n); +#endif +if ((isodesc->td[i].status >> 16) & 0xFF) + printk("error: %d %X\n", i, (isodesc->td[i].status >> 16)); + + data += n; + totlen += n; + } + + return totlen; +} + +int uhci_unsched_isochronous(struct usb_device *usb_dev, void *_isodesc) +{ + struct uhci_device *dev = usb_to_uhci(usb_dev); + struct uhci *uhci = dev->uhci; + struct uhci_iso_td *isodesc = (struct uhci_iso_td *)_isodesc; + int i; + + if ((isodesc->frame < 0) || (isodesc->frame > 1023)) + return 1; + + /* Remove from previous frames */ + for (i = 0; i < isodesc->num; i++) { + /* Turn off Active and IOC bits */ + isodesc->td[i].status &= ~(3 << 23); + uhci->fl->frame[(isodesc->frame + i) % 1024] = isodesc->td[i].link; + } + + isodesc->frame = -1; + + return 0; +} + +/* td points to the one td we allocated for isochronous transfers */ +int uhci_sched_isochronous(struct usb_device *usb_dev, void *_isodesc, void *_pisodesc) +{ + struct uhci_device *dev = usb_to_uhci(usb_dev); + struct uhci *uhci = dev->uhci; + struct uhci_iso_td *isodesc = (struct uhci_iso_td *)_isodesc; + struct uhci_iso_td *pisodesc = (struct uhci_iso_td *)_pisodesc; + int frame, i; + + if (isodesc->frame != -1) { + printk("isoc queue not removed\n"); + uhci_unsched_isochronous(usb_dev, isodesc); + } + + /* Insert TD into list */ + if (!pisodesc) { + frame = inw(uhci->io_addr + USBFRNUM) % 1024; + /* HACK: Start 2 frames from now */ + frame = (frame + 2) % 1024; + } else + frame = (pisodesc->endframe + 1) % 1024; + +#if 0 +printk("scheduling first at frame %d\n", frame); +#endif + + for (i = 0; i < isodesc->num; i++) { + /* Active */ + isodesc->td[i].status |= (1 << 23); + isodesc->td[i].backptr = &uhci->fl->frame[(frame + i) % 1024]; + isodesc->td[i].link = uhci->fl->frame[(frame + i) % 1024]; + uhci->fl->frame[(frame + i) % 1024] = virt_to_bus(&isodesc->td[i]); + } + +#if 0 +printk("last at frame %d\n", (frame + i - 1) % 1024); +#endif + + /* Interrupt */ + isodesc->td[i - 1].status |= (1 << 24); + + isodesc->frame = frame; + isodesc->endframe = (frame + isodesc->num - 1) % 1024; + +#if 0 + return uhci_td_result(dev, td[num - 1]); +#endif return 0; } /* + * Initialize isochronous queue + */ +void *uhci_alloc_isochronous(struct usb_device *usb_dev, unsigned int pipe, void *data, int len, int maxsze, usb_device_irq completed, void *dev_id) +{ + struct uhci_device *dev = usb_to_uhci(usb_dev); + unsigned long destination, status; + struct uhci_td *td; + struct uhci_iso_td *isodesc; + int i; + + isodesc = kmalloc(sizeof(*isodesc), GFP_KERNEL); + if (!isodesc) { + printk("Couldn't allocate isodesc!\n"); + return NULL; + } + memset(isodesc, 0, sizeof(*isodesc)); + + /* Carefully work around the non contiguous pages */ + isodesc->num = (len / PAGE_SIZE) * (PAGE_SIZE / maxsze); + isodesc->td = kmalloc(sizeof(struct uhci_td) * isodesc->num, GFP_KERNEL); + isodesc->frame = isodesc->endframe = -1; + isodesc->data = data; + isodesc->maxsze = maxsze; + + if (!isodesc->td) { + printk("Couldn't allocate td's\n"); + kfree(isodesc); + return NULL; + } + + isodesc->frame = isodesc->endframe = -1; + + /* + * Build the DATA TD's + */ + i = 0; + do { + /* Build the TD for control status */ + td = &isodesc->td[i]; + + /* The "pipe" thing contains the destination in bits 8--18 */ + destination = (pipe & 0x0007ff00); + + if (usb_pipeout(pipe)) + destination |= 0xE1; /* OUT */ + else + destination |= 0x69; /* IN */ + + /* Status: slow/fast, Active, Isochronous */ + status = (pipe & (1 << 26)) | (1 << 23) | (1 << 25); + + /* + * Build the TD for the control request + */ + td->status = status; + td->info = destination | ((maxsze - 1) << 21); + td->buffer = virt_to_bus(data); + td->first = td; + td->backptr = NULL; + + i++; + + data += maxsze; + + if (((int)data % PAGE_SIZE) + maxsze >= PAGE_SIZE) + data = (char *)(((int)data + maxsze) & ~(PAGE_SIZE - 1)); + + len -= maxsze; + } while (i < isodesc->num); + + /* IOC on the last TD */ + td->status |= (1 << 24); + uhci_add_irq_list(dev->uhci, td, completed, dev_id); + + return isodesc; +} + +void uhci_delete_isochronous(struct usb_device *usb_dev, void *_isodesc) +{ + struct uhci_iso_td *isodesc = (struct uhci_iso_td *)_isodesc; + + /* If it's still scheduled, unschedule them */ + if (isodesc->frame) + uhci_unsched_isochronous(usb_dev, isodesc); + + /* Remove it from the IRQ list */ + uhci_remove_irq_list(&isodesc->td[isodesc->num - 1]); + + kfree(isodesc->td); + kfree(isodesc); +} + +/* * Control thread operations: we just mark the last TD * in a control thread as an interrupt TD, and wake up * the front-end on completion. @@ -312,7 +643,7 @@ static int uhci_request_irq(struct usb_device *usb_dev, unsigned int pipe, usb_d * We need to remove the TD from the lists (both interrupt * list and TD lists) by hand if something bad happens! */ -static struct wait_queue *control_wakeup; +static DECLARE_WAIT_QUEUE_HEAD(control_wakeup); static int uhci_control_completed(int status, void *buffer, void *dev_id) { @@ -323,10 +654,10 @@ static int uhci_control_completed(int status, void *buffer, void *dev_id) /* td points to the last td in the list, which interrupts on completion */ static int uhci_run_control(struct uhci_device *dev, struct uhci_td *first, struct uhci_td *last) { - struct wait_queue wait = { current, NULL }; + DECLARE_WAITQUEUE(wait, current); struct uhci_qh *ctrl_qh = uhci_qh_allocate(dev); struct uhci_td *curtd; - + struct uhci_device *root_hub=usb_to_uhci(dev->uhci->bus->root_hub); current->state = TASK_UNINTERRUPTIBLE; add_wait_queue(&control_wakeup, &wait); @@ -354,10 +685,19 @@ static int uhci_run_control(struct uhci_device *dev, struct uhci_td *first, stru uhci_insert_tds_in_qh(ctrl_qh, first, last); /* Add it into the skeleton */ - uhci_insert_qh(&dev->uhci->root_hub->skel_control_qh, ctrl_qh); + uhci_insert_qh(&root_hub->skel_control_qh, ctrl_qh); + +// control should be full here... +// printk("control\n"); +// show_status(dev->uhci); +// show_queues(dev->uhci); schedule_timeout(HZ/10); +// control should be empty here... +// show_status(dev->uhci); +// show_queues(dev->uhci); + remove_wait_queue(&control_wakeup, &wait); /* Clean up in case it failed.. */ @@ -368,11 +708,11 @@ static int uhci_run_control(struct uhci_device *dev, struct uhci_td *first, stru #endif /* Remove it from the skeleton */ - uhci_remove_qh(&dev->uhci->root_hub->skel_control_qh, ctrl_qh); + uhci_remove_qh(&root_hub->skel_control_qh, ctrl_qh); uhci_qh_deallocate(ctrl_qh); - return uhci_td_result(dev, last); + return uhci_td_result(dev, last, NULL); } /* @@ -396,14 +736,16 @@ static int uhci_run_control(struct uhci_device *dev, struct uhci_td *first, stru * information, that's just ridiculously high. Most * control messages have just a few bytes of data. */ -static int uhci_control_msg(struct usb_device *usb_dev, unsigned int pipe, void *cmd, void *data, int len) +static int uhci_control_msg(struct usb_device *usb_dev, unsigned int pipe, + devrequest *cmd, void *data, int len) { struct uhci_device *dev = usb_to_uhci(usb_dev); struct uhci_td *first, *td, *prevtd; unsigned long destination, status; int ret; + int maxsze = usb_maxpacket(usb_dev, pipe); - if (len > usb_maxpacket(usb_dev->maxpacketsize) * 29) + if (len > maxsze * 29) printk("Warning, too much data for a control packet, crashing\n"); first = td = uhci_td_allocate(dev); @@ -420,6 +762,7 @@ static int uhci_control_msg(struct usb_device *usb_dev, unsigned int pipe, void td->status = status; /* Try forever */ td->info = destination | (7 << 21); /* 8 bytes of data */ td->buffer = virt_to_bus(cmd); + td->first = td; /* * If direction is "send", change the frame from SETUP (0x2D) @@ -439,7 +782,6 @@ static int uhci_control_msg(struct usb_device *usb_dev, unsigned int pipe, void while (len > 0) { /* Build the TD for control status */ int pktsze = len; - int maxsze = usb_maxpacket(pipe); if (pktsze > maxsze) pktsze = maxsze; @@ -450,14 +792,16 @@ static int uhci_control_msg(struct usb_device *usb_dev, unsigned int pipe, void td->status = status; /* Status */ td->info = destination | ((pktsze-1) << 21); /* pktsze bytes of data */ td->buffer = virt_to_bus(data); + td->first = first; td->backptr = &prevtd->link; + data += pktsze; + len -= pktsze; + prevtd = td; td = uhci_td_allocate(dev); prevtd->link = 4 | virt_to_bus(td); /* Update previous TD */ - data += maxsze; - len -= maxsze; } /* @@ -470,6 +814,7 @@ static int uhci_control_msg(struct usb_device *usb_dev, unsigned int pipe, void td->status = status | (1 << 24); /* IOC */ td->info = destination | (0x7ff << 21); /* 0 bytes of data */ td->buffer = 0; + td->first = first; td->backptr = &prevtd->link; /* Start it up.. */ @@ -495,6 +840,201 @@ static int uhci_control_msg(struct usb_device *usb_dev, unsigned int pipe, void } while (1); } + if (uhci_debug && ret) { + __u8 *p = (__u8 *) cmd; + + printk("Failed cmd - %02X %02X %02X %02X %02X %02X %02X %02X\n", + p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]); + } + return ret; +} + + +/* + * Bulk thread operations: we just mark the last TD + * in a bulk thread as an interrupt TD, and wake up + * the front-end on completion. + * + * We need to remove the TD from the lists (both interrupt + * list and TD lists) by hand if something bad happens! + */ +static DECLARE_WAIT_QUEUE_HEAD(bulk_wakeup); + +static int uhci_bulk_completed(int status, void *buffer, void *dev_id) +{ + wake_up(&bulk_wakeup); + return 0; /* Don't re-instate */ +} + +/* td points to the last td in the list, which interrupts on completion */ +static int uhci_run_bulk(struct uhci_device *dev, struct uhci_td *first, struct uhci_td *last, unsigned long *rval) +{ + DECLARE_WAITQUEUE(wait, current); + struct uhci_qh *bulk_qh = uhci_qh_allocate(dev); + struct uhci_td *curtd; + struct uhci_device *root_hub=usb_to_uhci(dev->uhci->bus->root_hub); + + current->state = TASK_UNINTERRUPTIBLE; + add_wait_queue(&bulk_wakeup, &wait); + + uhci_add_irq_list(dev->uhci, last, uhci_bulk_completed, NULL); + + /* FIXME: This is kinda kludged */ + /* Walk the TD list and update the QH pointer */ + { + int maxcount = 100; + + curtd = first; + do { + curtd->qh = bulk_qh; + if (curtd->link & 1) + break; + + curtd = bus_to_virt(curtd->link & ~0xF); + if (!--maxcount) { + printk("runaway tds!\n"); + break; + } + } while (1); + } + + uhci_insert_tds_in_qh(bulk_qh, first, last); + + /* Add it into the skeleton */ + uhci_insert_qh(&root_hub->skel_bulk0_qh, bulk_qh); + +// now we're in the queue... but don't ask WHAT is in there ;-( +// printk("bulk\n"); +// show_status(dev->uhci); +// show_queues(dev->uhci); + + schedule_timeout(HZ/10); +// show_status(dev->uhci); +// show_queues(dev->uhci); + + remove_wait_queue(&bulk_wakeup, &wait); + + /* Clean up in case it failed.. */ + uhci_remove_irq_list(last); + +#if 0 + printk("Looking for tds [%p, %p]\n", dev->control_td, td); +#endif + + /* Remove it from the skeleton */ + uhci_remove_qh(&root_hub->skel_bulk0_qh, bulk_qh); + + uhci_qh_deallocate(bulk_qh); + + return uhci_td_result(dev, last, rval); +} + +/* + * Send or receive a bulk message on a pipe. + * + * Note that the "pipe" structure is set up to map + * easily to the uhci destination fields. + * + * A bulk message is only built up from + * the data phase + * + * The data phase can be an arbitrary number of TD's + * although we currently had better not have more than + * 31 TD's here. + * + * 31 TD's is a minimum of 248 bytes worth of bulk + * information. + */ +static int uhci_bulk_msg(struct usb_device *usb_dev, unsigned int pipe, void *data, int len, unsigned long *rval) +{ + struct uhci_device *dev = usb_to_uhci(usb_dev); + struct uhci_td *first, *td, *prevtd; + unsigned long destination, status; + int ret; + int maxsze = usb_maxpacket(usb_dev, pipe); + + if (usb_endpoint_halted(usb_dev, usb_pipeendpoint(pipe)) && + usb_clear_halt(usb_dev, usb_pipeendpoint(pipe) | (pipe & 0x80))) + return USB_ST_STALL; + + if (len > maxsze * 31) + printk("Warning, too much data for a bulk packet, crashing (%d/%d)\n", len, maxsze); + + /* The "pipe" thing contains the destination in bits 8--18, 0x69 is IN */ + /* + IS THIS NECCESARY? PERHAPS WE CAN JUST USE THE PIPE + LOOK AT: usb_pipeout and the pipe bits + I FORGOT WHAT IT EXACTLY DOES + */ + if (usb_pipeout(pipe)) { + destination = (pipe & 0x0007ff00) | 0xE1; + } + else { + destination = (pipe & 0x0007ff00) | 0x69; + } + + /* Status: slow/fast, Active, Short Packet Detect Three Errors */ + status = (pipe & (1 << 26)) | (1 << 23) | (1 << 29) | (3 << 27); + + /* + * Build the TDs for the bulk request + */ + first = td = uhci_td_allocate(dev); + prevtd = first; //This is fake, but at least it's not NULL + while (len > 0) { + /* Build the TD for control status */ + int pktsze = len; + + if (pktsze > maxsze) + pktsze = maxsze; + + td->status = status; /* Status */ + td->info = destination | ((pktsze-1) << 21) | + (usb_gettoggle(usb_dev, usb_pipeendpoint(pipe)) << 19); /* pktsze bytes of data */ + td->buffer = virt_to_bus(data); + td->backptr = &prevtd->link; + td->first = first; + + data += maxsze; + len -= maxsze; + + if (len > 0) { + prevtd = td; + td = uhci_td_allocate(dev); + prevtd->link = 4 | virt_to_bus(td); /* Update previous TD */ + } + + /* Alternate Data0/1 (start with Data0) */ + usb_dotoggle(usb_dev, usb_pipeendpoint(pipe)); + } + td->link = 1; /* Terminate */ + td->status |= (1 << 24); /* IOC */ + + /* CHANGE DIRECTION HERE! SAVE IT SOMEWHERE IN THE ENDPOINT!!! */ + + /* Start it up.. */ + ret = uhci_run_bulk(dev, first, td, rval); + + { + int maxcount = 100; + struct uhci_td *curtd = first; + unsigned int nextlink; + + do { + nextlink = curtd->link; + uhci_remove_td(curtd); + uhci_td_deallocate(curtd); + if (nextlink & 1) /* Tail? */ + break; + + curtd = bus_to_virt(nextlink & ~0xF); + if (!--maxcount) { + printk("runaway td's!?\n"); + break; + } + } while (1); + } + return ret; } @@ -554,19 +1094,23 @@ static int uhci_usb_deallocate(struct usb_device *usb_dev) for (i = 0; i < UHCI_MAXTD; ++i) { struct uhci_td *td = dev->td + i; - /* And remove it from the irq list, if it's active */ - if (td->status & (1 << 23)) - uhci_remove_irq_list(td); - - if (td->inuse) + if (td->inuse & 1) { uhci_remove_td(td); + + /* And remove it from the irq list, if it's active */ + if (td->status & (1 << 23)) + td->status &= ~(1 << 23); +#if 0 + uhci_remove_irq_list(td); +#endif + } } /* Remove the td from any queues */ for (i = 0; i < UHCI_MAXQH; ++i) { struct uhci_qh *qh = dev->qh + i; - if (qh->inuse) + if (qh->inuse & 1) uhci_remove_qh(qh->skel, qh); } @@ -581,7 +1125,9 @@ struct usb_operations uhci_device_operations = { uhci_usb_allocate, uhci_usb_deallocate, uhci_control_msg, + uhci_bulk_msg, uhci_request_irq, + uhci_remove_irq, }; /* @@ -623,7 +1169,7 @@ static void uhci_connect_change(struct uhci *uhci, unsigned int port, unsigned i struct usb_device *usb_dev; struct uhci_device *dev; unsigned short status; - + struct uhci_device *root_hub=usb_to_uhci(uhci->bus->root_hub); printk("uhci_connect_change: called for %d\n", nr); /* @@ -633,7 +1179,7 @@ static void uhci_connect_change(struct uhci *uhci, unsigned int port, unsigned i * * So start off by getting rid of any old devices.. */ - usb_disconnect(&uhci->root_hub->usb->children[nr]); + usb_disconnect(&root_hub->usb->children[nr]); status = inw(port); @@ -648,14 +1194,14 @@ static void uhci_connect_change(struct uhci *uhci, unsigned int port, unsigned i * Ok, we got a new connection. Allocate a device to it, * and find out what it wants to do.. */ - usb_dev = uhci_usb_allocate(uhci->root_hub->usb); + usb_dev = uhci_usb_allocate(root_hub->usb); dev = usb_dev->hcpriv; dev->uhci = uhci; usb_connect(usb_dev); - uhci->root_hub->usb->children[nr] = usb_dev; + root_hub->usb->children[nr] = usb_dev; wait_ms(200); /* wait for powerup */ uhci_reset_port(port); @@ -678,8 +1224,9 @@ static void uhci_connect_change(struct uhci *uhci, unsigned int port, unsigned i */ static void uhci_check_configuration(struct uhci *uhci) { + struct uhci_device * root_hub=usb_to_uhci(uhci->bus->root_hub); unsigned int io_addr = uhci->io_addr + USBPORTSC1; - int maxchild = uhci->root_hub->usb->maxchild; + int maxchild = root_hub->usb->maxchild; int nr = 0; do { @@ -709,16 +1256,29 @@ static void uhci_interrupt_notify(struct uhci *uhci) /* remove from IRQ list */ __list_del(tmp->prev, next); INIT_LIST_HEAD(tmp); - if (td->completed(td->status, bus_to_virt(td->buffer), td->dev_id)) { - struct uhci_qh *interrupt_qh = td->qh; - + if (td->completed(uhci_map_status((td->status & 0xff)>> 16, 0), + bus_to_virt(td->buffer), td->dev_id)) { list_add(&td->irq_list, &uhci->interrupt_list); - td->info ^= 1 << 19; /* toggle between data0 and data1 */ - td->status = (td->status & 0x2f000000) | (1 << 23) | (1 << 24); /* active */ - /* Remove then readd? Is that necessary */ - uhci_remove_td(td); - uhci_insert_td_in_qh(interrupt_qh, td); + if (!(td->status & (1 << 25))) { + struct uhci_qh *interrupt_qh = td->qh; + + usb_dotoggle(td->dev, usb_pipeendpoint(td->info)); + td->info |= 1 << 19; /* toggle between data0 and data1 */ + td->status = (td->status & 0x2f000000) | (1 << 23) | (1 << 24); /* active */ + + /* Remove then readd? Is that necessary */ + uhci_remove_td(td); + uhci_insert_td_in_qh(interrupt_qh, td); + } + } else if (td->inuse & 2) { + struct uhci_qh *interrupt_qh = td->qh; + /* marked for removal */ + td->inuse &= ~2; + usb_dotoggle(td->dev, usb_pipeendpoint(td->info)); + uhci_remove_qh(interrupt_qh->skel, interrupt_qh); + uhci_qh_deallocate(interrupt_qh); + uhci_td_deallocate(td); } /* If completed wants to not reactivate, then it's */ /* responsible for free'ing the TD's and QH's */ @@ -740,7 +1300,8 @@ static void uhci_interrupt_notify(struct uhci *uhci) static void uhci_root_hub_events(struct uhci *uhci, unsigned int io_addr) { if (waitqueue_active(&uhci_configure)) { - int ports = uhci->root_hub->usb->maxchild; + struct uhci_device * root_hub=usb_to_uhci(uhci->bus->root_hub); + int ports = root_hub->usb->maxchild; io_addr += USBPORTSC1; do { if (inw(io_addr) & USBPORTSC_CSC) { @@ -764,6 +1325,9 @@ static void uhci_interrupt(int irq, void *__uhci, struct pt_regs *regs) status = inw(io_addr + USBSTS); outw(status, io_addr + USBSTS); +// if ((status & ~0x21) != 0) +// printk("interrupt: %X\n", status); + /* Walk the list of pending TD's to see which ones completed.. */ uhci_interrupt_notify(uhci); @@ -780,13 +1344,14 @@ static void uhci_interrupt(int irq, void *__uhci, struct pt_regs *regs) */ static void uhci_init_ticktd(struct uhci *uhci) { - struct uhci_device *dev = uhci->root_hub; + struct uhci_device *dev = usb_to_uhci(uhci->bus->root_hub); struct uhci_td *td = uhci_td_allocate(dev); td->link = 1; td->status = (1 << 24); /* interrupt on completion */ td->info = (15 << 21) | 0x7f69; /* (ignored) input packet, 16 bytes, device 127 */ td->buffer = 0; + td->first = td; td->qh = NULL; uhci->fl->frame[0] = virt_to_bus(td); @@ -824,12 +1389,13 @@ static void start_hc(struct uhci *uhci) } } + outw(USBINTR_TIMEOUT | USBINTR_RESUME | USBINTR_IOC | USBINTR_SP, io_addr + USBINTR); outw(0, io_addr + USBFRNUM); outl(virt_to_bus(uhci->fl), io_addr + USBFLBASEADD); /* Run and mark it configured with a 64-byte max packet */ - outw(USBCMD_RS | USBCMD_CF, io_addr + USBCMD); + outw(USBCMD_RS | USBCMD_CF | USBCMD_MAXP, io_addr + USBCMD); } /* @@ -895,10 +1461,9 @@ static struct uhci *alloc_uhci(unsigned int io_addr) if (!usb) return NULL; - dev = uhci->root_hub = usb_to_uhci(usb); - usb->bus = bus; - + dev = usb_to_uhci(usb); + uhci->bus->root_hub=uhci_to_usb(dev); /* Initialize the root hub */ /* 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 */ @@ -979,9 +1544,9 @@ static void release_uhci(struct uhci *uhci) } #if 0 - if (uhci->root_hub) { - uhci_usb_deallocate(uhci_to_usb(uhci->root_hub)); - uhci->root_hub = NULL; + if (uhci->bus->root_hub) { + uhci_usb_deallocate(uhci_to_usb(uhci->bus->root_hub)); + uhci->bus->root_hub = NULL; } #endif @@ -994,12 +1559,10 @@ static void release_uhci(struct uhci *uhci) kfree(uhci); } -void cleanup_drivers(void); - static int uhci_control_thread(void * __uhci) { struct uhci *uhci = (struct uhci *)__uhci; - + struct uhci_device * root_hub =usb_to_uhci(uhci->bus->root_hub); lock_kernel(); request_region(uhci->io_addr, 32, "usb-uhci"); @@ -1010,7 +1573,7 @@ static int uhci_control_thread(void * __uhci) printk("uhci_control_thread at %p\n", &uhci_control_thread); exit_mm(current); exit_files(current); - exit_fs(current); + //exit_fs(current); strcpy(current->comm, "uhci-control"); @@ -1018,6 +1581,7 @@ static int uhci_control_thread(void * __uhci) * Ok, all systems are go.. */ start_hc(uhci); + usb_register_bus(uhci->bus); for(;;) { siginfo_t info; int unsigned long signr; @@ -1041,19 +1605,23 @@ static int uhci_control_thread(void * __uhci) if(signr == SIGUSR1) { printk("UHCI queue dump:\n"); show_queues(uhci); + } else if (signr == SIGUSR2) { + printk("UHCI debug toggle\n"); + uhci_debug = !uhci_debug; } else { break; } } } -#if 0 - if(uhci->root_hub) - for(i = 0; i < uhci->root_hub->usb->maxchild; i++) - usb_disconnect(uhci->root_hub->usb->children + i); -#endif + { + int i; + if(root_hub) + for(i = 0; i < root_hub->usb->maxchild; i++) + usb_disconnect(root_hub->usb->children + i); + } - cleanup_drivers(); + usb_deregister_bus(uhci->bus); reset_hc(uhci); release_region(uhci->io_addr, 32); @@ -1084,10 +1652,10 @@ static int found_uhci(int irq, unsigned int io_addr) retval = -EBUSY; if (request_irq(irq, uhci_interrupt, SA_SHIRQ, "usb", uhci) == 0) { int pid; - MOD_INC_USE_COUNT; uhci->irq = irq; - pid = kernel_thread(uhci_control_thread, uhci, CLONE_FS | CLONE_FILES | CLONE_SIGHAND); + pid = kernel_thread(uhci_control_thread, uhci, + CLONE_FS | CLONE_FILES | CLONE_SIGHAND); if (pid >= 0) return 0; @@ -1152,18 +1720,6 @@ static int handle_apm_event(apm_event_t event) } #endif -#ifdef MODULE - -void cleanup_module(void) -{ -#ifdef CONFIG_APM - apm_unregister_callback(&handle_apm_event); -#endif -} - -#define uhci_init init_module - -#endif int uhci_init(void) { @@ -1185,29 +1741,24 @@ int uhci_init(void) if (retval < 0) continue; -#ifdef CONFIG_USB_MOUSE - usb_mouse_init(); -#endif -#ifdef CONFIG_USB_KBD - usb_kbd_init(); -#endif - hub_init(); -#ifdef CONFIG_USB_AUDIO - usb_audio_init(); -#endif #ifdef CONFIG_APM apm_register_callback(&handle_apm_event); #endif - return 0; } return retval; } -void cleanup_drivers(void) +#ifdef MODULE +int init_module(void) { - hub_cleanup(); -#ifdef CONFIG_USB_MOUSE - usb_mouse_cleanup(); + return uhci_init(); +} + +void cleanup_module(void) +{ +#ifdef CONFIG_APM + apm_unregister_callback(&handle_apm_event); #endif } +#endif //MODULE |