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

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(&regs->roothub.portstatus[num]) & PORT_CSC) {
+			/* reset the connect status change bit */
+			writel(PORT_CSC, &regs->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(&regs->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(&regs->intrstatus);
+	status &= readl(&regs->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, &regs->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, &regs->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, &regs->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, &regs->intrenable);
-	
+	/* Check those "other" pesky bits */
+	if (context & (OHCI_INTR_FNO)) {
+		writel(OHCI_INTR_FNO, &regs->intrstatus);
+		context &= ~OHCI_INTR_FNO;  /* mark this as checked */
+	}
+	if (context & OHCI_INTR_SO) {
+		writel(OHCI_INTR_SO, &regs->intrstatus);
+		context &= ~OHCI_INTR_SO;  /* mark this as checked */
+	}
+	if (context & OHCI_INTR_RD) {
+		writel(OHCI_INTR_RD, &regs->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, &regs->intrstatus);
+		context &= ~OHCI_INTR_UE;  /* mark this as checked */
+	}
+	if (context & OHCI_INTR_OC) {
+		writel(OHCI_INTR_OC, &regs->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, &regs->intrdisable);
+	}
+
+	/* Re-enable HC interrupts */
+	writel(OHCI_INTR_MIE, &regs->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(&current->sigmask_lock);
+			signr = dequeue_signal(&current->blocked, &info);
+			spin_unlock_irq(&current->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)