patch-2.2.8 linux/drivers/usb/ohci.c
Next file: linux/drivers/usb/ohci.h
Previous file: linux/drivers/usb/ohci-root-hub.h
Back to the patch index
Back to the overall index
- Lines: 1663
- Date:
Tue May 11 10:27:04 1999
- Orig file:
v2.2.7/linux/drivers/usb/ohci.c
- Orig date:
Wed Apr 28 11:37:30 1999
diff -u --recursive --new-file v2.2.7/linux/drivers/usb/ohci.c linux/drivers/usb/ohci.c
@@ -29,7 +29,7 @@
*
* No filesystems were harmed in the development of this code.
*
- * $Id: ohci.c,v 1.11 1999/04/25 00:18:52 greg Exp $
+ * $Id: ohci.c,v 1.26 1999/05/11 07:34:47 greg Exp $
*/
#include <linux/config.h>
@@ -59,6 +59,10 @@
static struct wait_queue *ohci_configure = NULL;
+#ifdef OHCI_TIMER
+static struct timer_list ohci_timer; /* timer for root hub polling */
+#endif
+
static int ohci_td_result(struct ohci_device *dev, struct ohci_td *td)
{
@@ -69,86 +73,258 @@
/* TODO Debugging code for TD failures goes here */
return status;
-}
+} /* ohci_td_result() */
static spinlock_t ohci_edtd_lock = SPIN_LOCK_UNLOCKED;
/*
- * Add a TD to the end of the TD list on a given ED. td->next_td is
- * assumed to be set correctly for the situation of no TDs already
- * being on the list (ie: pointing to NULL).
+ * Add a TD to the end of the TD list on a given ED. If td->next_td
+ * points to any more TDs, they will be added as well (naturally).
+ * Otherwise td->next_td must be 0.
+ *
+ * The SKIP flag will be cleared after this function.
+ *
+ * Important! This function needs locking and atomicity as it works
+ * in parallel with the HC's DMA. Locking ohci_edtd_lock while using
+ * the function is a must.
+ *
+ * This function can be called by the interrupt handler.
*/
static void ohci_add_td_to_ed(struct ohci_td *td, struct ohci_ed *ed)
{
- struct ohci_td *tail = bus_to_virt(ed->tail_td);
- struct ohci_td *head = bus_to_virt(ed->head_td);
- unsigned long flags;
+ /* don't let the HC pull anything from underneath us */
+ ed->status |= OHCI_ED_SKIP;
- spin_lock_irqsave(&ohci_edtd_lock, flags);
-
- if (tail == head) { /* empty list, put it on the head */
- head = (struct ohci_td *) virt_to_bus(td);
- tail = 0;
+ if (ed_head_td(ed) == 0) { /* empty list, put it on the head */
+ set_ed_head_td(ed, virt_to_bus(td));
+ ed->tail_td = 0;
} else {
+ struct ohci_td *tail, *head;
+ head = (ed_head_td(ed) == 0) ? NULL : bus_to_virt(ed_head_td(ed));
+ tail = (ed->tail_td == 0) ? NULL : bus_to_virt(ed->tail_td);
if (!tail) { /* no tail, single element list */
td->next_td = head->next_td;
head->next_td = virt_to_bus(td);
- tail = (struct ohci_td *) virt_to_bus(td);
+ ed->tail_td = virt_to_bus(td);
} else { /* append to the list */
td->next_td = tail->next_td;
tail->next_td = virt_to_bus(td);
- tail = (struct ohci_td *) virt_to_bus(td);
+ ed->tail_td = virt_to_bus(td);
}
}
- /* save the reverse link */
- td->ed_bus = virt_to_bus(ed);
- spin_unlock_irqrestore(&ohci_edtd_lock, flags);
+ /* save the ED link in each of the TDs added */
+ td->ed = ed;
+ while (td->next_td != 0) {
+ td = bus_to_virt(td->next_td);
+ td->ed = ed;
+ }
+
+ /* turn off the SKIP flag */
+ ed->status &= ~OHCI_ED_SKIP;
} /* ohci_add_td_to_ed() */
+inline void ohci_start_control(struct ohci *ohci)
+{
+ /* tell the HC to start processing the control list */
+ writel(OHCI_CMDSTAT_CLF, &ohci->regs->cmdstatus);
+}
+
+inline void ohci_start_bulk(struct ohci *ohci)
+{
+ /* tell the HC to start processing the bulk list */
+ writel(OHCI_CMDSTAT_BLF, &ohci->regs->cmdstatus);
+}
+
+inline void ohci_start_periodic(struct ohci *ohci)
+{
+ /* enable processing periodc transfers starting next frame */
+ writel_set(OHCI_USB_PLE, &ohci->regs->control);
+}
+
+inline void ohci_start_isoc(struct ohci *ohci)
+{
+ /* enable processing isoc. transfers starting next frame */
+ writel_set(OHCI_USB_IE, &ohci->regs->control);
+}
+
/*
- * Remove a TD from the given EDs TD list
+ * Add an ED to the hardware register ED list pointed to by hw_listhead_p
+ */
+static void ohci_add_ed_to_hw(struct ohci_ed *ed, void* hw_listhead_p)
+{
+ __u32 listhead;
+ unsigned long flags;
+
+ spin_lock_irqsave(&ohci_edtd_lock, flags);
+
+ listhead = readl(hw_listhead_p);
+
+ /* if the list is not empty, insert this ED at the front */
+ /* XXX should they go on the end? */
+ if (listhead) {
+ ed->next_ed = listhead;
+ }
+
+ /* update the hardware listhead pointer */
+ writel(virt_to_bus(ed), hw_listhead_p);
+
+ spin_unlock_irqrestore(&ohci_edtd_lock, flags);
+} /* ohci_add_ed() */
+
+
+/*
+ * Put another control ED on the controller's list
+ */
+void ohci_add_control_ed(struct ohci *ohci, struct ohci_ed *ed)
+{
+ ohci_add_ed_to_hw(ed, &ohci->regs->ed_controlhead);
+ ohci_start_control(ohci);
+} /* ohci_add_control_ed() */
+
+
+#if 0
+/*
+ * Put another control ED on the controller's list
+ */
+void ohci_add_periodic_ed(struct ohci *ohci, struct ohci_ed *ed, int period)
+{
+ ohci_add_ed_to_hw(ed, /* XXX */);
+ ohci_start_periodic(ohci);
+} /* ohci_add_control_ed() */
+#endif
+
+
+/*
+ * Remove an ED from the HC list whos bus headpointer is pointed to
+ * by hw_listhead_p
+ *
+ * Note that the SKIP bit is left on in the removed ED.
+ */
+void ohci_remove_ed_from_hw(struct ohci_ed *ed, __u32* hw_listhead_p)
+{
+ unsigned long flags;
+ struct ohci_ed *cur;
+ __u32 bus_ed = virt_to_bus(ed);
+ __u32 bus_cur;
+
+ if (ed == NULL || !bus_ed)
+ return;
+
+ /* tell the controller this skip ED */
+ ed->status |= OHCI_ED_SKIP;
+
+ bus_cur = readl(hw_listhead_p);
+
+ if (bus_cur == 0)
+ return; /* the list is already empty */
+
+ cur = bus_to_virt(bus_cur);
+
+ spin_lock_irqsave(&ohci_edtd_lock, flags);
+
+ /* if its the head ED, move the head */
+ if (bus_cur == bus_ed) {
+ writel(cur->next_ed, hw_listhead_p);
+ } else if (cur->next_ed != 0) {
+ struct ohci_ed *prev;
+
+ /* walk the list and unlink the ED if found */
+ for (;;) {
+ prev = cur;
+ cur = bus_to_virt(cur->next_ed);
+
+ if (virt_to_bus(cur) == bus_ed) {
+ /* unlink from the list */
+ prev->next_ed = cur->next_ed;
+ break;
+ }
+
+ if (cur->next_ed == 0)
+ break;
+ }
+ }
+
+ /* clear any links from the ED for safety */
+ ed->next_ed = 0;
+
+ spin_unlock_irqrestore(&ohci_edtd_lock, flags);
+} /* ohci_remove_ed_from_hw() */
+
+/*
+ * Remove an ED from the controller's control list. Note that the SKIP bit
+ * is left on in the removed ED.
+ */
+inline void ohci_remove_control_ed(struct ohci *ohci, struct ohci_ed *ed)
+{
+ ohci_remove_ed_from_hw(ed, &ohci->regs->ed_controlhead);
+}
+
+/*
+ * Remove an ED from the controller's bulk list. Note that the SKIP bit
+ * is left on in the removed ED.
+ */
+inline void ohci_remove_bulk_ed(struct ohci *ohci, struct ohci_ed *ed)
+{
+ ohci_remove_ed_from_hw(ed, &ohci->regs->ed_bulkhead);
+}
+
+
+/*
+ * Remove a TD from the given EDs TD list.
*/
static void ohci_remove_td_from_ed(struct ohci_td *td, struct ohci_ed *ed)
{
- struct ohci_td *head = bus_to_virt(ed->head_td);
- struct ohci_td *tmp_td;
unsigned long flags;
+ struct ohci_td *head_td;
+
+ if ((td == NULL) || (ed == NULL))
+ return;
spin_lock_irqsave(&ohci_edtd_lock, flags);
+ if (ed_head_td(ed) == 0)
+ return;
+
/* set the "skip me bit" in this ED */
- writel_set(OHCI_ED_SKIP, ed->status);
+ ed->status |= OHCI_ED_SKIP;
/* XXX Assuming this list will never be circular */
- if (td == head) {
- /* unlink this TD; it was at the beginning */
- ed->head_td = head->next_td;
- }
-
- tmp_td = head;
- head = (struct ohci_td *) ed->head_td;
-
- while (head != NULL) {
+ head_td = bus_to_virt(ed_head_td(ed));
+ if (virt_to_bus(td) == ed_head_td(ed)) {
+ /* It's the first TD, remove it. */
+ set_ed_head_td(ed, head_td->next_td);
+ } else {
+ struct ohci_td *prev_td, *cur_td;
- if (td == head) {
- /* unlink this TD from the middle or end */
- tmp_td->next_td = head->next_td;
+ /* FIXME: collapse this into a nice simple loop :) */
+ if (head_td->next_td != 0) {
+ prev_td = head_td;
+ cur_td = bus_to_virt(head_td->next_td);
+ for (;;) {
+ if (td == cur_td) {
+ /* remove it */
+ prev_td->next_td = cur_td->next_td;
+ break;
+ }
+ if (cur_td->next_td == 0)
+ break;
+ prev_td = cur_td;
+ cur_td = bus_to_virt(cur_td->next_td);
+ }
}
-
- tmp_td = head;
- head = bus_to_virt(head->next_td);
}
- td->next_td = virt_to_bus(NULL); /* remove links to ED list */
+ td->next_td = 0; /* remove the TDs links */
+ td->ed = NULL;
- /* XXX mark this TD for possible cleanup? */
+ /* TODO return this TD to the pool of free TDs */
/* unset the "skip me bit" in this ED */
- writel_mask(~(__u32)OHCI_ED_SKIP, ed->status);
+ ed->status &= ~OHCI_ED_SKIP;
spin_unlock_irqrestore(&ohci_edtd_lock, flags);
} /* ohci_remove_td_from_ed() */
@@ -165,60 +341,67 @@
int idx;
for (idx=0; idx < NUM_TDS; idx++) {
- if (td_done(dev->td[idx].info)) {
- /* XXX should this also zero out the structure? */
- /* mark all new TDs as unaccessed */
- dev->td[idx].info = OHCI_TD_CC_NEW;
- return &dev->td[idx];
+ if (!td_allocated(dev->td[idx])) {
+ struct ohci_td *new_td = &dev->td[idx];
+ /* zero out the TD */
+ memset(new_td, 0, sizeof(*new_td));
+ /* mark the new TDs as unaccessed */
+ new_td->info = OHCI_TD_CC_NEW;
+ /* mark it as allocated */
+ allocate_td(new_td);
+ return new_td;
}
}
+ printk("usb-ohci error: unable to allocate a TD\n");
return NULL;
} /* ohci_get_free_td() */
-/**********************************
- * OHCI interrupt list operations *
- **********************************/
-static spinlock_t irqlist_lock = SPIN_LOCK_UNLOCKED;
-
-static void ohci_add_irq_list(struct ohci *ohci, struct ohci_td *td, usb_device_irq completed, void *dev_id)
+/*
+ * Initialize a TD
+ *
+ * dir = OHCI_TD_D_IN, OHCI_TD_D_OUT, or OHCI_TD_D_SETUP
+ * toggle = TOGGLE_AUTO, TOGGLE_DATA0, TOGGLE_DATA1
+ */
+inline struct ohci_td *ohci_fill_new_td(struct ohci_td *td, int dir, int toggle, __u32 flags, void *data, __u32 len, void *dev_id, usb_device_irq completed)
{
- unsigned long flags;
+ /* hardware fields */
+ td->info = OHCI_TD_CC_NEW |
+ (dir & OHCI_TD_D) |
+ (toggle & OHCI_TD_DT) |
+ flags;
+ td->cur_buf = (data == NULL) ? 0 : virt_to_bus(data);
+ td->buf_end = (len == 0) ? 0 : td->cur_buf + len - 1;
- /* save the irq in our private portion of the TD */
- td->completed = completed;
+ /* driver fields */
+ td->data = data;
td->dev_id = dev_id;
+ td->completed = completed;
- spin_lock_irqsave(&irqlist_lock, flags);
- list_add(&td->irq_list, &ohci->interrupt_list);
- spin_unlock_irqrestore(&irqlist_lock, flags);
-} /* ohci_add_irq_list() */
+ return td;
+} /* ohci_fill_new_td() */
-static void ohci_remove_irq_list(struct ohci_td *td)
-{
- unsigned long flags;
- spin_lock_irqsave(&irqlist_lock, flags);
- list_del(&td->irq_list);
- spin_unlock_irqrestore(&irqlist_lock, flags);
-} /* ohci_remove_irq_list() */
+/**********************************
+ * OHCI interrupt list operations *
+ **********************************/
/*
* Request an interrupt handler for one "pipe" of a USB device.
* (this function is pretty minimal right now)
*
* At the moment this is only good for input interrupts. (ie: for a
- * mouse)
+ * mouse or keyboard)
*
- * period is desired polling interval in ms. The closest, shorter
+ * Period is desired polling interval in ms. The closest, shorter
* match will be used. Powers of two from 1-32 are supported by OHCI.
*/
static int ohci_request_irq(struct usb_device *usb, unsigned int pipe,
usb_device_irq handler, int period, void *dev_id)
{
struct ohci_device *dev = usb_to_ohci(usb);
- struct ohci_td *td = dev->td; /* */
+ struct ohci_td *td;
struct ohci_ed *interrupt_ed; /* endpoint descriptor for this irq */
/*
@@ -239,42 +422,68 @@
usb_pipe_endpdev(pipe) |
OHCI_ED_F_NORM;
+ td = ohci_get_free_td(dev);
+ /* FIXME: check for NULL */
+
+ /* Fill in the TD */
+ ohci_fill_new_td(td, td_set_dir_out(usb_pipeout(pipe)),
+ TOGGLE_AUTO,
+ OHCI_TD_ROUND,
+ dev->data, DATA_BUF_LEN,
+ dev_id, handler);
/*
- * Set the not accessed condition code, allow odd sized data,
- * and set the data transfer direction.
+ * TODO: be aware that OHCI won't advance out of the 4kb
+ * page cur_buf started in. It'll wrap around to the start
+ * of the page... annoying or useful? you decide.
+ *
+ * We should make sure dev->data doesn't cross a page...
*/
- td->info = OHCI_TD_CC_NEW | OHCI_TD_ROUND |
- td_set_dir_out(usb_pipeout(pipe));
- /* point it to our data buffer */
- td->cur_buf = virt_to_bus(dev->data);
+ /* FIXME: this just guarantees that its the end of the list */
+ td->next_td = 0;
- /* FIXME: we're only using 1 TD right now! */
- td->next_td = virt_to_bus(&td);
+ /* Linus did this. see asm/system.h; scary concept... I don't
+ * know if its needed here or not but it won't hurt. */
+ wmb();
/*
- * FIXME: be aware that OHCI won't advance out of the 4kb
- * page cur_buf started in. It'll wrap around to the start
- * of the page... annoying or useful? you decide.
- *
- * A pointer to the last *byte* in the buffer (ergh.. we get
- * to work around C's pointer arithmatic here with a typecast)
+ * Put the TD onto our ED
*/
- td->buf_end = virt_to_bus(((u8*)(dev->data + DATA_BUF_LEN)) - 1);
+ {
+ unsigned long flags;
+ spin_lock_irqsave(&ohci_edtd_lock, flags);
+ ohci_add_td_to_ed(td, interrupt_ed);
+ spin_unlock_irqrestore(&ohci_edtd_lock, flags);
+ }
- /* does this make sense for ohci?.. time to think.. */
- ohci_add_irq_list(dev->ohci, td, handler, dev_id);
- wmb(); /* found in asm/system.h; scary concept... */
- ohci_add_td_to_ed(td, interrupt_ed);
+#if 0
+ /* Assimilate the new ED into the collective */
+ /*
+ * When dynamic ED allocation is done, this call will be
+ * useful. For now, the correct ED already on the
+ * controller's proper periodic ED lists was chosen above.
+ */
+ ohci_add_periodic_ed(dev->ohci, interrupt_ed, period);
+#else
+ /* enable periodic (interrupt) transfers on the HC */
+ ohci_start_periodic(dev->ohci);
+#endif
return 0;
} /* ohci_request_irq() */
+
/*
* Control thread operations:
*/
static struct wait_queue *control_wakeup;
+/*
+ * This is the handler that gets called when a control transaction
+ * completes.
+ *
+ * This function is called from the interrupt handler.
+ */
static int ohci_control_completed(int stats, void *buffer, void *dev_id)
{
wake_up(&control_wakeup);
@@ -283,41 +492,16 @@
/*
- * Run a control transaction from the root hub's control endpoint.
- * The passed in TD is the control transfer's Status TD.
- */
-static int ohci_run_control(struct ohci_device *dev, struct ohci_td *status_td)
-{
- struct wait_queue wait = { current, NULL };
- struct ohci_ed *control_ed = &dev->ohci->root_hub->ed[ED_CONTROL];
-
- current->state = TASK_UNINTERRUPTIBLE;
- add_wait_queue(&control_wakeup, &wait);
-
- ohci_add_irq_list(dev->ohci, status_td, ohci_control_completed, NULL);
- ohci_add_td_to_ed(status_td, control_ed);
-
- /* FIXME? isn't this a little gross */
- schedule_timeout(HZ/10);
-
- ohci_remove_irq_list(status_td);
- ohci_remove_td_from_ed(status_td, control_ed);
-
- return ohci_td_result(dev, status_td);
-} /* ohci_run_control() */
-
-/*
* Send or receive a control message on a "pipe"
*
+ * The cmd parameter is a pointer to the 8 byte setup command to be
+ * sent. FIXME: This is a devrequest in usb.h. The function
+ * should be updated to accept a devrequest* instead of void*..
+ *
* A control message contains:
* - The command itself
- * - An optional data phase
+ * - An optional data phase (if len > 0)
* - Status complete phase
- *
- * The data phase can be an arbitrary number of TD's. Currently since
- * we use statically allocated TDs if too many come in we'll just
- * start tossing them and printk() some warning goo... Most control
- * messages won't have much data anyways.
*/
static int ohci_control_msg(struct usb_device *usb, unsigned int pipe, void *cmd, void *data, int len)
{
@@ -330,10 +514,12 @@
* anyways? ;)
*/
struct ohci_ed *control_ed = &dev->ohci->root_hub->ed[ED_CONTROL];
- struct ohci_td *control_td;
- struct ohci_td *data_td;
- struct ohci_td *last_td;
- __u32 data_td_info;
+ struct ohci_td *setup_td, *data_td, *status_td;
+ struct wait_queue wait = { current, NULL };
+
+#if 0
+ printk(KERN_DEBUG "entering ohci_control_msg %p (ohci_dev: %p) pipe 0x%x, cmd %p, data %p, len %d\n", usb, dev, pipe, cmd, data, len);
+#endif
/*
* Set the max packet size, device speed, endpoint number, usb
@@ -351,95 +537,174 @@
*/
/* get a TD to send this control message with */
- control_td = ohci_get_free_td(dev);
+ setup_td = ohci_get_free_td(dev);
/* TODO check for NULL */
/*
* Set the not accessed condition code, allow odd sized data,
* and set the data transfer type to SETUP. Setup DATA always
* uses a DATA0 packet.
- */
- control_td->info = OHCI_TD_CC_NEW | OHCI_TD_ROUND |
- OHCI_TD_D_SETUP | OHCI_TD_IOC_OFF | td_force_toggle(0);
-
- /* point it to the command */
- control_td->cur_buf = virt_to_bus(cmd);
+ *
+ * The setup packet contains a devrequest (usb.h) which
+ * will always be 8 bytes long. FIXME: the cmd parameter
+ * should be a pointer to one of these instead of a void* !!!
+ */
+ ohci_fill_new_td(setup_td, OHCI_TD_D_SETUP, TOGGLE_DATA0,
+ OHCI_TD_IOC_OFF,
+ cmd, 8, /* cmd is always 8 bytes long */
+ NULL, NULL);
- /* link to a free TD for the control data input */
+ /* allocate the next TD */
data_td = ohci_get_free_td(dev); /* TODO check for NULL */
- control_td->next_td = virt_to_bus(data_td);
+
+ /* link to the next TD */
+ setup_td->next_td = virt_to_bus(data_td);
+
+ if (len > 0) {
+
+ /* build the Control DATA TD, it starts with a DATA1. */
+ ohci_fill_new_td(data_td, td_set_dir_out(usb_pipeout(pipe)),
+ TOGGLE_DATA1,
+ OHCI_TD_ROUND | OHCI_TD_IOC_OFF,
+ data, len,
+ NULL, NULL);
+
+ /*
+ * XXX we should check that the data buffer doesn't
+ * cross a 4096 byte boundary. If so, it needs to be
+ * copied into a single 4096 byte aligned area for the
+ * OHCI's TD logic to see it all, or multiple TDs need
+ * to be made for each page.
+ *
+ * It's not likely a control transfer will run into
+ * this problem.. (famous last words)
+ */
+
+ status_td = ohci_get_free_td(dev); /* TODO check for NULL */
+ data_td->next_td = virt_to_bus(status_td);
+ } else {
+ status_td = data_td; /* no data_td, use it for status */
+ }
+
+ /* The control status packet always uses a DATA1 */
+ ohci_fill_new_td(status_td,
+ td_set_dir_in(usb_pipeout(pipe) | (len == 0)),
+ TOGGLE_DATA1,
+ 0,
+ NULL, 0,
+ NULL, ohci_control_completed);
+ status_td->next_td = 0; /* end of TDs */
/*
- * Build the DATA TDs
+ * Start the control transaction..
*/
+ current->state = TASK_UNINTERRUPTIBLE;
+ add_wait_queue(&control_wakeup, &wait);
- data_td_info = OHCI_TD_CC_NEW | OHCI_TD_ROUND | OHCI_TD_IOC_OFF |
- td_set_dir_out(usb_pipeout(pipe));
-
- while (len > 0) {
- int pktsize = len;
- struct ohci_td *tmp_td;
+ /*
+ * Add the chain of 2-3 control TDs to the control ED's TD list
+ */
+ {
+ unsigned long flags;
+ spin_lock_irqsave(&ohci_edtd_lock, flags);
+ ohci_add_td_to_ed(setup_td, control_ed);
+ spin_unlock_irqrestore(&ohci_edtd_lock, flags);
+ }
+
+#if 0
+ /* complete transaction debugging output (before) */
+ printk(KERN_DEBUG " Control ED %lx:\n", virt_to_bus(control_ed));
+ show_ohci_ed(control_ed);
+ printk(KERN_DEBUG " Setup TD %lx:\n", virt_to_bus(setup_td));
+ show_ohci_td(setup_td);
+ if (data_td != status_td) {
+ printk(KERN_DEBUG " Data TD %lx:\n", virt_to_bus(data_td));
+ show_ohci_td(data_td);
+ }
+ printk(KERN_DEBUG " Status TD %lx:\n", virt_to_bus(status_td));
+ show_ohci_td(status_td);
+#endif
- if (pktsize > usb_maxpacket(pipe))
- pktsize = usb_maxpacket(pipe);
+ /* Give the ED to the HC */
+ ohci_add_control_ed(dev->ohci, control_ed);
- /* set the data transaction type */
- data_td->info = data_td_info;
- /* point to the current spot in the data buffer */
- data_td->cur_buf = virt_to_bus(data);
- /* point to the end of this data */
- data_td->buf_end = virt_to_bus(data+pktsize-1);
+ /* FIXME:
+ * this should really check to see that the transaction completed.
+ */
+ schedule_timeout(HZ/10);
- /* allocate the next TD */
- tmp_td = ohci_get_free_td(dev); /* TODO check for NULL */
- data_td->next_td = virt_to_bus(tmp_td);
- data_td = tmp_td;
+ remove_wait_queue(&control_wakeup, &wait);
- /* move on.. */
- data += pktsize;
- len -= pktsize;
+#if 0
+ /* complete transaction debugging output (after) */
+ printk(KERN_DEBUG " (after) Control ED:\n");
+ show_ohci_ed(control_ed);
+ printk(KERN_DEBUG " (after) Setup TD:\n");
+ show_ohci_td(setup_td);
+ if (data_td != status_td) {
+ printk(KERN_DEBUG " (after) Data TD:\n");
+ show_ohci_td(data_td);
}
+ printk(KERN_DEBUG " (after) Status TD:\n");
+ show_ohci_td(status_td);
+#endif
- /* point it at the newly allocated TD from above */
- last_td = data_td;
+ /* clean up incase it failed */
+ /* XXX only do this if their ed pointer still points to control_ed
+ * incase they've been reclaimed and used by something else
+ * already. -greg */
+ ohci_remove_td_from_ed(setup_td, control_ed);
+ ohci_remove_td_from_ed(data_td, control_ed);
+ ohci_remove_td_from_ed(status_td, control_ed);
- /* The control status packet always uses a DATA1 */
- last_td->info = OHCI_TD_CC_NEW | OHCI_TD_ROUND | td_force_toggle(1);
- last_td->next_td = 0; /* end of TDs */
- last_td->cur_buf = 0; /* no data in this packet */
- last_td->buf_end = 0;
+ /* remove the control ED */
+ ohci_remove_control_ed(dev->ohci, control_ed);
- /*
- * Start the control transaction.. give it the last TD so the
- * result can be returned.
- */
- return ohci_run_control(dev, last_td);
+#if 0
+ printk(KERN_DEBUG "leaving ohci_control_msg\n");
+#endif
+
+ return ohci_td_result(dev, status_td);
} /* ohci_control_msg() */
+/*
+ * Allocate a new USB device to be attached to an OHCI controller
+ */
static struct usb_device *ohci_usb_allocate(struct usb_device *parent)
{
struct usb_device *usb_dev;
struct ohci_device *dev;
+ /*
+ * Allocate the generic USB device
+ */
usb_dev = kmalloc(sizeof(*usb_dev), GFP_KERNEL);
if (!usb_dev)
return NULL;
memset(usb_dev, 0, sizeof(*usb_dev));
+ /*
+ * Allocate an OHCI device (EDs and TDs for this device)
+ */
dev = kmalloc(sizeof(*dev), GFP_KERNEL);
if (!dev) {
kfree(usb_dev);
return NULL;
}
- /* Initialize "dev" */
memset(dev, 0, sizeof(*dev));
+ /*
+ * Link them together
+ */
usb_dev->hcpriv = dev;
dev->usb = usb_dev;
+ /*
+ * Link the device to its parent (hub, etc..) if any.
+ */
usb_dev->parent = parent;
if (parent) {
@@ -448,8 +713,14 @@
}
return usb_dev;
-}
+} /* ohci_usb_allocate() */
+
+/*
+ * Free a usb device.
+ *
+ * TODO This function needs to take better care of the EDs and TDs, etc.
+ */
static int ohci_usb_deallocate(struct usb_device *usb_dev)
{
kfree(usb_to_ohci(usb_dev));
@@ -457,6 +728,7 @@
return 0;
}
+
/*
* functions for the generic USB driver
*/
@@ -467,42 +739,92 @@
ohci_request_irq,
};
+
/*
- * Reset an OHCI controller
+ * Reset an OHCI controller. Returns >= 0 on success.
+ *
+ * Afterwards the HC will be in the "suspend" state which prevents you
+ * from writing to some registers. Bring it to the operational state
+ * ASAP.
*/
-static void reset_hc(struct ohci *ohci)
+static int reset_hc(struct ohci *ohci)
{
- writel((1<<31), &ohci->regs->intrdisable); /* Disable HC interrupts */
- writel(1, &ohci->regs->cmdstatus); /* HC Reset */
+ int timeout = 1000; /* prevent an infinite loop */
+
+#if 0
+ printk(KERN_DEBUG "usb-ohci: resetting HC %p\n", ohci);
+#endif
+
+ writel(~0x0, &ohci->regs->intrdisable); /* Disable HC interrupts */
+ writel(1, &ohci->regs->cmdstatus); /* HC Reset */
writel_mask(0x3f, &ohci->regs->control); /* move to UsbReset state */
+
+ while ((readl(&ohci->regs->cmdstatus) & OHCI_CMDSTAT_HCR) != 0) {
+ if (!--timeout) {
+ printk("usb-ohci: USB HC reset timed out!\n");
+ return -1;
+ }
+ udelay(1);
+ }
+
+ printk(KERN_DEBUG "usb-ohci: HC %p reset.\n", ohci);
+
+ return 0;
} /* reset_hc() */
/*
- * Reset and start an OHCI controller
+ * Reset and start an OHCI controller. Returns >= 0 on success.
*/
-static void start_hc(struct ohci *ohci)
+static int start_hc(struct ohci *ohci)
{
- int timeout = 1000; /* used to prevent an infinite loop. */
+ int ret = 0;
+ int fminterval;
- reset_hc(ohci);
+ fminterval = readl(&ohci->regs->fminterval) & 0x3fff;
+#if 0
+ printk(KERN_DEBUG "entering start_hc %p\n", ohci);
+#endif
- while ((readl(&ohci->regs->control) & 0xc0) == 0) {
- if (!--timeout) {
- printk("USB HC Reset timed out!\n");
- break;
- }
- }
+ if (reset_hc(ohci) < 0)
+ return -1;
+
+ /* restore registers cleared by the reset */
+ writel(virt_to_bus(ohci->root_hub->hcca), &ohci->regs->hcca);
+
+ /*
+ * XXX Should fminterval also be set here?
+ * The spec suggests 0x2edf [11,999]. (FIXME: make this a constant)
+ */
+ fminterval |= (0x2edf << 16);
+ writel(fminterval, &ohci->regs->fminterval);
+ /* Start periodic transfers at 90% of fminterval (fmremaining
+ * counts down; this will put them in the first 10% of the
+ * frame). */
+ writel((0x2edf*9)/10, &ohci->regs->periodicstart);
+ /*
+ * FNO (frame number overflow) could be enabled... they
+ * occur every 32768 frames (every 32-33 seconds). This is
+ * useful for debugging and as a bus heartbeat. -greg
+ */
/* Choose the interrupts we care about */
- writel( OHCI_INTR_MIE | OHCI_INTR_RHSC | OHCI_INTR_SF |
- OHCI_INTR_WDH | OHCI_INTR_SO | OHCI_INTR_UE |
- OHCI_INTR_FNO,
+ writel( OHCI_INTR_MIE | /* OHCI_INTR_RHSC | */
+ OHCI_INTR_WDH | OHCI_INTR_FNO,
&ohci->regs->intrenable);
/* Enter the USB Operational state & start the frames a flowing.. */
writel_set(OHCI_USB_OPER, &ohci->regs->control);
+
+ /* Enable control lists */
+ writel_set(OHCI_USB_IE | OHCI_USB_CLE | OHCI_USB_BLE, &ohci->regs->control);
+
+ /* Turn on power to the root hub ports (thanks Roman!) */
+ writel( OHCI_ROOT_LPSC, &ohci->regs->roothub.status );
+
+ printk("usb-ohci: host controller operational\n");
+ return ret;
} /* start_hc() */
@@ -511,23 +833,20 @@
*/
static void ohci_reset_port(struct ohci *ohci, unsigned int port)
{
- short ms;
int status;
/* Don't allow overflows. */
if (port >= MAX_ROOT_PORTS) {
- printk("Bad port # passed to ohci_reset_port\n");
+ printk("usb-ohci: bad port #%d in ohci_reset_port\n", port);
port = MAX_ROOT_PORTS-1;
}
writel(PORT_PRS, &ohci->regs->roothub.portstatus[port]); /* Reset */
/*
- * Get the time required for a root hub port to reset and wait
- * it out (adding 1ms for good measure).
+ * Wait for the reset to complete.
*/
- ms = (readl(&ohci->regs->roothub.a) >> 24) * 2 + 1;
- wait_ms(ms);
+ wait_ms(10);
/* check port status to see that the reset completed */
status = readl(&ohci->regs->roothub.portstatus[port]);
@@ -555,6 +874,8 @@
void *portaddr = &ohci->regs->roothub.portstatus[port];
int portstatus;
+ printk(KERN_DEBUG "ohci_connect_change(%p, %d)\n", ohci, port);
+
/*
* Because of the status change we have to forget
* everything we think we know about the device
@@ -593,6 +914,7 @@
* Do generic USB device tree processing on the new device.
*/
usb_new_device(usb_dev);
+
} /* ohci_connect_change() */
@@ -603,66 +925,241 @@
*/
static void ohci_check_configuration(struct ohci *ohci)
{
+ struct ohci_regs *regs = ohci->regs;
int num = 0;
int maxport = readl(&ohci->regs->roothub) & 0xff;
+#if 1
+ printk(KERN_DEBUG "entering ohci_check_configuration %p\n", ohci);
+#endif
+
do {
- if (readl(ohci->regs->roothub.portstatus[num]) & PORT_CSC)
+ if (readl(®s->roothub.portstatus[num]) & PORT_CSC) {
+ /* reset the connect status change bit */
+ writel(PORT_CSC, ®s->roothub.portstatus[num]);
+ /* check the port for a nifty device */
ohci_connect_change(ohci, num);
+ }
} while (++num < maxport);
+
+#if 0
+ printk(KERN_DEBUG "leaving ohci_check_configuration %p\n", ohci);
+#endif
} /* ohci_check_configuration() */
+
+/*
+ * Check root hub port status and wake the control thread up if
+ * anything has changed.
+ *
+ * This function is called from the interrupt handler.
+ */
+static void ohci_root_hub_events(struct ohci *ohci)
+{
+ if (waitqueue_active(&ohci_configure)) {
+ int num = 0;
+ int maxport = ohci->root_hub->usb->maxchild;
+
+ do {
+ if (readl(&ohci->regs->roothub.portstatus[num]) &
+ PORT_CSC) {
+ if (waitqueue_active(&ohci_configure))
+ wake_up(&ohci_configure);
+ return;
+ }
+ } while (++num < maxport);
+ }
+} /* ohci_root_hub_events() */
+
+
+/*
+ * The done list is in reverse order; we need to process TDs in the
+ * order they were finished (FIFO). This function builds the FIFO
+ * list using the next_dl_td pointer.
+ *
+ * This function originally by Roman Weissgaerber (weissg@vienna.at)
+ *
+ * This function is called from the interrupt handler.
+ */
+static struct ohci_td * ohci_reverse_donelist(struct ohci * ohci)
+{
+ __u32 td_list_hc;
+ struct ohci_hcca *hcca = ohci->root_hub->hcca;
+ struct ohci_td *td_list = NULL;
+ struct ohci_td *td_rev = NULL;
+
+ td_list_hc = hcca->donehead & 0xfffffff0;
+ hcca->donehead = 0;
+
+ while(td_list_hc) {
+ td_list = (struct ohci_td *) bus_to_virt(td_list_hc);
+ td_list->next_dl_td = td_rev;
+
+ td_rev = td_list;
+ td_list_hc = td_list->next_td & 0xfffffff0;
+ }
+
+ return td_list;
+} /* ohci_reverse_donelist() */
+
+
+/*
+ * Collect this interrupt's goodies off of the list of finished TDs
+ * that the OHCI controller is kind enough to setup for us.
+ *
+ * This function is called from the interrupt handler.
+ */
+static void ohci_reap_donelist(struct ohci *ohci)
+{
+ struct ohci_td *td; /* used for walking the list */
+
+ spin_lock(&ohci_edtd_lock);
+
+ /* create the FIFO ordered donelist */
+ td = ohci_reverse_donelist(ohci);
+
+ while (td != NULL) {
+ struct ohci_td *next_td = td->next_dl_td;
+
+ /* FIXME: munge td->info into a future standard status format */
+ /* Check if TD should be re-queued */
+ if ((td->completed != NULL) &&
+ (td->completed(OHCI_TD_CC_GET(td->info), td->data, td->dev_id)))
+ {
+ /* Mark the TD as active again:
+ * Set the not accessed condition code
+ * FIXME: should this reset OHCI_TD_ERRCNT?
+ */
+ td->info |= OHCI_TD_CC_NEW;
+
+ /* point it back to the start of the data buffer */
+ td->cur_buf = virt_to_bus(td->data);
+
+ /* XXX disabled for debugging reasons right now.. */
+ /* insert it back on its ED */
+ ohci_add_td_to_ed(td, td->ed);
+ } else {
+ /* return it to the pool of free TDs */
+ ohci_free_td(td);
+ }
+
+ td = next_td;
+ }
+
+ spin_unlock(&ohci_edtd_lock);
+} /* ohci_reap_donelist() */
+
+
+#if 0
+static int in_int = 0;
+#endif
/*
* Get annoyed at the controller for bothering us.
+ * This pretty much follows the OHCI v1.0a spec, section 5.3.
*/
static void ohci_interrupt(int irq, void *__ohci, struct pt_regs *r)
{
struct ohci *ohci = __ohci;
struct ohci_regs *regs = ohci->regs;
struct ohci_hcca *hcca = ohci->root_hub->hcca;
- __u32 donehead = hcca->donehead;
+ __u32 status, context;
- /*
- * Check the interrupt status register if needed
- */
- if (!donehead || (donehead & 1)) {
- __u32 intrstatus = readl(®s->intrstatus);
+#if 0
+ /* for debugging to keep IRQs from running away. */
+ if (in_int >= 2)
+ return;
+ ++in_int;
+ return;
+#endif
- /*
- * XXX eek! printk's in an interrupt handler. shoot me!
- */
- if (intrstatus & OHCI_INTR_SO) {
- printk(KERN_DEBUG "usb-ohci: scheduling overrun\n");
- }
- if (intrstatus & OHCI_INTR_RD) {
- printk(KERN_DEBUG "usb-ohci: resume detected\n");
- }
- if (intrstatus & OHCI_INTR_UE) {
- printk(KERN_DEBUG "usb-ohci: unrecoverable error\n");
+ /* Save the status of the interrupts that are enabled */
+ status = readl(®s->intrstatus);
+ status &= readl(®s->intrenable);
+
+
+ /* make context = the interrupt status bits that we care about */
+ if (hcca->donehead != 0) {
+ context = OHCI_INTR_WDH; /* hcca donehead needs processing */
+ if (hcca->donehead & 1) {
+ context |= status; /* other status change to check */
}
- if (intrstatus & OHCI_INTR_OC) {
- printk(KERN_DEBUG "usb-ohci: ownership change?\n");
+ } else {
+ context = status;
+ if (!context) {
+ /* TODO increment a useless interrupt counter here */
+ return;
}
+ }
- if (intrstatus & OHCI_INTR_RHSC) {
- /* TODO Process events on the root hub */
- }
+ /* Disable HC interrupts */
+ writel(OHCI_INTR_MIE, ®s->intrdisable);
+
+ /* Process the done list */
+ if (context & OHCI_INTR_WDH) {
+ /* See which TD's completed.. */
+ ohci_reap_donelist(ohci);
+
+ /* reset the done queue and tell the controller */
+ hcca->donehead = 0;
+ writel(OHCI_INTR_WDH, ®s->intrstatus);
+
+ context &= ~OHCI_INTR_WDH; /* mark this as checked */
}
- /*
- * Process the done list
- */
- if (donehead &= ~0x1) {
- /*
- * TODO See which TD's completed..
+ /* Process any root hub status changes */
+ if (context & OHCI_INTR_RHSC) {
+ /* Wake the thread to process root hub events */
+ if (waitqueue_active(&ohci_configure))
+ wake_up(&ohci_configure);
+
+ writel(OHCI_INTR_RHSC, ®s->intrstatus);
+ /*
+ * Don't unset RHSC in context; it should be disabled.
+ * The control thread will re-enable it after it has
+ * checked the root hub status.
*/
+ } else {
+ /* check the root hub status anyways. Some controllers
+ * might not generate the interrupt properly. (?) */
+ ohci_root_hub_events(ohci);
}
- /* Re-enable done queue interrupts and reset the donehead */
- hcca->donehead = 0;
- writel(OHCI_INTR_WDH, ®s->intrenable);
-
+ /* Check those "other" pesky bits */
+ if (context & (OHCI_INTR_FNO)) {
+ writel(OHCI_INTR_FNO, ®s->intrstatus);
+ context &= ~OHCI_INTR_FNO; /* mark this as checked */
+ }
+ if (context & OHCI_INTR_SO) {
+ writel(OHCI_INTR_SO, ®s->intrstatus);
+ context &= ~OHCI_INTR_SO; /* mark this as checked */
+ }
+ if (context & OHCI_INTR_RD) {
+ writel(OHCI_INTR_RD, ®s->intrstatus);
+ context &= ~OHCI_INTR_RD; /* mark this as checked */
+ }
+ if (context & OHCI_INTR_UE) {
+ /* FIXME: need to have the control thread reset the
+ * controller now and keep a count of unrecoverable
+ * errors. If there are too many, it should just shut
+ * the broken controller down entirely. */
+ writel(OHCI_INTR_UE, ®s->intrstatus);
+ context &= ~OHCI_INTR_UE; /* mark this as checked */
+ }
+ if (context & OHCI_INTR_OC) {
+ writel(OHCI_INTR_OC, ®s->intrstatus);
+ context &= ~OHCI_INTR_OC; /* mark this as checked */
+ }
+
+ /* Mask out any remaining unprocessed interrupts so we don't
+ * get any more of them. */
+ if (context & ~OHCI_INTR_MIE) {
+ writel(context, ®s->intrdisable);
+ }
+
+ /* Re-enable HC interrupts */
+ writel(OHCI_INTR_MIE, ®s->intrenable);
+
} /* ohci_interrupt() */
@@ -682,6 +1179,10 @@
struct ohci_device *dev;
struct usb_device *usb;
+#if 0
+ printk(KERN_DEBUG "entering alloc_ohci %p\n", mem_base);
+#endif
+
ohci = kmalloc(sizeof(*ohci), GFP_KERNEL);
if (!ohci)
return NULL;
@@ -703,6 +1204,8 @@
bus->op = &ohci_device_operations;
/*
+ * Allocate the USB device structure and root hub.
+ *
* Here we allocate our own root hub and TDs as well as the
* OHCI host controller communications area. The HCCA is just
* a nice pool of memory with pointers to endpoint descriptors
@@ -717,17 +1220,22 @@
usb->bus = bus;
/* Initialize the root hub */
- memset(dev, 0, sizeof(*dev));
dev->ohci = ohci; /* link back to the controller */
/*
- * Allocate the Host Controller Communications Area
+ * Allocate the Host Controller Communications Area on a 256
+ * byte boundary. XXX take the easy way out and just grab a
+ * page as that's guaranteed to have a nice boundary.
*/
- dev->hcca = (struct ohci_hcca *) kmalloc(sizeof(*dev->hcca), GFP_KERNEL);
+ dev->hcca = (struct ohci_hcca *) __get_free_page(GFP_KERNEL);
/* Tell the controller where the HCCA is */
writel(virt_to_bus(dev->hcca), &ohci->regs->hcca);
+#if 0
+ printk(KERN_DEBUG "usb-ohci: HCCA allocated at %p (bus %p)\n", dev->hcca, (void*)virt_to_bus(dev->hcca));
+#endif
+
/* Get the number of ports on the root hub */
usb->maxchild = readl(&ohci->regs->roothub.a) & 0xff;
if (usb->maxchild > MAX_ROOT_PORTS) {
@@ -740,11 +1248,6 @@
}
printk("usb-ohci: %d root hub ports found\n", usb->maxchild);
- printk("alloc_ohci() controller\n");
- show_ohci_status(ohci);
- printk("alloc_ohci() root_hub device\n");
- show_ohci_device(dev);
-
/*
* Initialize the ED polling "tree" (for simplicity's sake in
* this driver many nodes in the tree will be identical)
@@ -759,11 +1262,9 @@
* Initialize the polling table to call interrupts at the
* intended intervals.
*/
- for (i = 0; i < NUM_INTS; i++) {
- if (i == 0)
- dev->hcca->int_table[i] =
- virt_to_bus(&dev->ed[ED_INT_32]);
- else if (i & 1)
+ dev->hcca->int_table[0] = virt_to_bus(&dev->ed[ED_INT_32]);
+ for (i = 1; i < NUM_INTS; i++) {
+ if (i & 1)
dev->hcca->int_table[i] =
virt_to_bus(&dev->ed[ED_INT_16]);
else if (i & 2)
@@ -782,9 +1283,23 @@
/*
* Tell the controller where the control and bulk lists are
+ * The lists start out empty.
*/
+ writel(0, &ohci->regs->ed_controlhead);
+ writel(0, &ohci->regs->ed_bulkhead);
+ /*
writel(virt_to_bus(&dev->ed[ED_CONTROL]), &ohci->regs->ed_controlhead);
writel(virt_to_bus(&dev->ed[ED_BULK]), &ohci->regs->ed_bulkhead);
+ */
+
+#if 0
+ printk(KERN_DEBUG "alloc_ohci(): controller\n");
+ show_ohci_status(ohci);
+#endif
+
+#if 0
+ printk(KERN_DEBUG "leaving alloc_ohci %p\n", ohci);
+#endif
return ohci;
} /* alloc_ohci() */
@@ -795,16 +1310,25 @@
*/
static void release_ohci(struct ohci *ohci)
{
+ printk(KERN_DEBUG "entering release_ohci %p\n", ohci);
+
+#ifdef OHCI_TIMER
+ /* stop our timer */
+ del_timer(&ohci_timer);
+#endif
if (ohci->irq >= 0) {
free_irq(ohci->irq, ohci);
ohci->irq = -1;
}
+ /* stop all OHCI interrupts */
+ writel(~0x0, &ohci->regs->intrdisable);
+
if (ohci->root_hub) {
/* ensure that HC is stopped before releasing the HCCA */
writel(OHCI_USB_SUSPEND, &ohci->regs->control);
- free_pages((unsigned int) ohci->root_hub->hcca, 1);
- free_pages((unsigned int) ohci->root_hub, 1);
+ free_page((unsigned long) ohci->root_hub->hcca);
+ kfree(ohci->root_hub);
ohci->root_hub->hcca = NULL;
ohci->root_hub = NULL;
}
@@ -812,20 +1336,26 @@
/* unmap the IO address space */
iounmap(ohci->regs);
+ kfree(ohci);
+
+ MOD_DEC_USE_COUNT;
+
/* If the ohci itself were dynamic we'd free it here */
+ printk(KERN_DEBUG "usb-ohci: HC resources released.\n");
} /* release_ohci() */
+
/*
* USB OHCI control thread
*/
static int ohci_control_thread(void * __ohci)
{
struct ohci *ohci = (struct ohci *)__ohci;
-
+
/*
* I'm unfamiliar with the SMP kernel locking.. where should
- * this be released? -greg
+ * this be released and what does it do? -greg
*/
lock_kernel();
@@ -833,7 +1363,7 @@
* This thread doesn't need any user-level access,
* so get rid of all of our resources..
*/
- printk("ohci_control_thread at %p\n", &ohci_control_thread);
+ printk("ohci_control_thread code at %p\n", &ohci_control_thread);
exit_mm(current);
exit_files(current);
exit_fs(current);
@@ -843,23 +1373,64 @@
/*
* Damn the torpedoes, full speed ahead
*/
- start_hc(ohci);
- do {
+ if (start_hc(ohci) < 0) {
+ printk("usb-ohci: failed to start the controller\n");
+ release_ohci(ohci);
+ printk(KERN_DEBUG "leaving ohci_control_thread %p\n", __ohci);
+ return 0;
+ }
+
+ for(;;) {
+ siginfo_t info;
+ int unsigned long signr;
+
+ wait_ms(200);
+
+ /* check the root hub configuration for changes. */
+ ohci_check_configuration(ohci);
+
+ /* re-enable root hub status change interrupts. */
+#if 0
+ writel(OHCI_INTR_RHSC, &ohci->regs->intrenable);
+#endif
+
+ printk(KERN_DEBUG "ohci-control thread sleeping\n");
interruptible_sleep_on(&ohci_configure);
#ifdef CONFIG_APM
if (apm_resume) {
apm_resume = 0;
- start_hc(ohci);
+ if (start_hc(ohci) < 0)
+ break;
continue;
}
#endif
- ohci_check_configuration(ohci);
- } while (!signal_pending(current));
- reset_hc(ohci);
+ /*
+ * If we were woken up by a signal, see if its useful,
+ * otherwise exit.
+ */
+ if (signal_pending(current)) {
+ /* sending SIGUSR1 makes us print out some info */
+ spin_lock_irq(¤t->sigmask_lock);
+ signr = dequeue_signal(¤t->blocked, &info);
+ spin_unlock_irq(¤t->sigmask_lock);
+
+ if(signr == SIGUSR1) {
+ /* FIXME: have it do a full ed/td queue dump */
+ printk(KERN_DEBUG "OHCI status dump:\n");
+ show_ohci_status(ohci);
+ } else {
+ /* unknown signal, exit the thread */
+ break;
+ }
+ }
+ } /* for (;;) */
+ reset_hc(ohci);
release_ohci(ohci);
- MOD_DEC_USE_COUNT;
+
+ printk(KERN_DEBUG "leaving ohci_control_thread %p\n", __ohci);
+
return 0;
} /* ohci_control_thread() */
@@ -892,10 +1463,28 @@
break;
}
return 0;
-}
+} /* handle_apm_event() */
+#endif
+
+
+#ifdef OHCI_TIMER
+/*
+ * Inspired by Iñaky's driver. This function is a timer routine that
+ * is called OHCI_TIMER_FREQ times per second. It polls the root hub
+ * for status changes as on my system things are acting a bit odd at
+ * the moment..
+ */
+static void ohci_timer_func (unsigned long ohci_ptr)
+{
+ struct ohci *ohci = (struct ohci*)ohci_ptr;
+
+ ohci_root_hub_events(ohci);
+
+ /* press the snooze button... */
+ mod_timer(&ohci_timer, jiffies + (OHCI_TIMER_FREQ*HZ));
+} /* ohci_timer_func() */
#endif
-/* ... */
/*
* Increment the module usage count, start the control thread and
@@ -906,32 +1495,50 @@
int retval;
struct ohci *ohci;
+#if 0
+ printk(KERN_DEBUG "entering found_ohci %d %p\n", irq, mem_base);
+#endif
+
/* Allocate the running OHCI structures */
ohci = alloc_ohci(mem_base);
- if (!ohci)
+ if (!ohci) {
return -ENOMEM;
+ }
- printk("usb-ohci: alloc_ohci() = %p\n", ohci);
-
- reset_hc(ohci);
+#ifdef OHCI_TIMER
+ init_timer(&ohci_timer);
+ ohci_timer.expires = jiffies + (OHCI_TIMER_FREQ*HZ);
+ ohci_timer.data = (unsigned long)ohci;
+ ohci_timer.function = ohci_timer_func;
+#endif
retval = -EBUSY;
if (request_irq(irq, ohci_interrupt, SA_SHIRQ, "usb-ohci", ohci) == 0) {
int pid;
- MOD_INC_USE_COUNT;
ohci->irq = irq;
+#if 0
+ printk(KERN_DEBUG "usb-ohci: starting ohci-control thread\n");
+#endif
+
/* fork off the handler */
pid = kernel_thread(ohci_control_thread, ohci,
CLONE_FS | CLONE_FILES | CLONE_SIGHAND);
- if (pid >= 0)
+ if (pid >= 0) {
return 0;
+ }
- MOD_DEC_USE_COUNT;
retval = pid;
+ } else {
+ printk("usb-ohci: Couldn't allocate interrupt %d\n", irq);
}
release_ohci(ohci);
+
+#if 0
+ printk(KERN_DEBUG "leaving found_ohci %d %p\n", irq, mem_base);
+#endif
+
return retval;
} /* found_ohci() */
@@ -941,35 +1548,44 @@
*/
static int init_ohci(struct pci_dev *dev)
{
- unsigned int mem_base = dev->base_address[0];
+ unsigned long mem_base = dev->base_address[0];
- printk("usb-ohci: mem_base is %p\n", (void*)mem_base);
-
/* If its OHCI, its memory */
if (mem_base & PCI_BASE_ADDRESS_SPACE_IO)
return -ENODEV;
/* Get the memory address and map it for IO */
mem_base &= PCI_BASE_ADDRESS_MEM_MASK;
+
+ /* no interrupt won't work... */
+ if (dev->irq == 0) {
+ printk("usb-ohci: no irq assigned? check your BIOS settings.\n");
+ return -ENODEV;
+ }
+
/*
* FIXME ioremap_nocache isn't implemented on all CPUs (such
* as the Alpha) [?] What should I use instead...
*
* The iounmap() is done on in release_ohci.
*/
- mem_base = (unsigned int) ioremap_nocache(mem_base, 4096);
+ mem_base = (unsigned long) ioremap_nocache(mem_base, 4096);
if (!mem_base) {
printk("Error mapping OHCI memory\n");
return -EFAULT;
}
+ MOD_INC_USE_COUNT;
- return found_ohci(dev->irq, (void *) mem_base);
-} /* init_ohci() */
+ if (found_ohci(dev->irq, (void *) mem_base) < 0) {
+ MOD_DEC_USE_COUNT;
+ return -1;
+ }
+ return 0;
+} /* init_ohci() */
#ifdef MODULE
-
/*
* Clean up when unloading the module
*/
@@ -978,6 +1594,10 @@
#ifdef CONFIG_APM
apm_unregister_callback(&handle_apm_event);
#endif
+#ifdef CONFIG_USB_MOUSE
+ usb_mouse_cleanup();
+#endif
+ printk("usb-ohci: module unloaded\n");
}
#define ohci_init init_module
@@ -1005,6 +1625,8 @@
return -ENODEV;
}
+ printk("OHCI USB Driver loading\n");
+
retval = -ENODEV;
for (;;) {
/* Find an OHCI USB controller */
@@ -1024,9 +1646,16 @@
/* TODO check module params here to determine what to load */
-/* usb_mouse_init(); */
-/* usb_kbd_init();
- hub_init(); */
+#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
@@ -1034,7 +1663,7 @@
return 0; /* no error */
}
return retval;
-} /* init_module() */
+} /* ohci_init */
/* vim:sw=8
*/
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)