summaryrefslogtreecommitdiffstats
path: root/drivers/usb/uhci.c
diff options
context:
space:
mode:
authorRalf Baechle <ralf@linux-mips.org>1999-06-17 13:25:08 +0000
committerRalf Baechle <ralf@linux-mips.org>1999-06-17 13:25:08 +0000
commit59223edaa18759982db0a8aced0e77457d10c68e (patch)
tree89354903b01fa0a447bffeefe00df3044495db2e /drivers/usb/uhci.c
parentdb7d4daea91e105e3859cf461d7e53b9b77454b2 (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.c741
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