patch-2.3.17 linux/drivers/usb/uhci.c
Next file: linux/drivers/usb/usb-core.c
Previous file: linux/drivers/usb/ohci.c
Back to the patch index
Back to the overall index
- Lines: 299
- Date:
Thu Sep 2 10:22:08 1999
- Orig file:
v2.3.16/linux/drivers/usb/uhci.c
- Orig date:
Tue Aug 31 17:29:14 1999
diff -u --recursive --new-file v2.3.16/linux/drivers/usb/uhci.c linux/drivers/usb/uhci.c
@@ -23,6 +23,12 @@
/* 4/4/1999 added data toggle for interrupt pipes -keryan */
/* 5/16/1999 added global toggles for bulk and control */
/* 6/25/1999 added fix for data toggles on bidirectional bulk endpoints */
+/*
+ * 1999-09-02: Thomas Sailer <sailer@ife.ee.ethz.ch>
+ * Added explicit frame list manipulation routines
+ * for inserting/removing iso td's to/from the frame list.
+ * START_ABSOLUTE fixes
+ */
#include <linux/config.h>
#include <linux/module.h>
@@ -463,6 +469,84 @@
}
/*
+ * frame list manipulation. Used for Isochronous transfers.
+ * the list of (iso) TD's enqueued in a frame list entry
+ * is basically a doubly linked list with link being
+ * the forward pointer and backptr the backward ptr.
+ * the frame list entry itself doesn't have a back ptr
+ * (therefore the list is not circular), and the forward pointer
+ * stops at link entries having the UHCI_PTR_TERM or the UHCI_PTR_QH
+ * bit set. Maybe it could be extended to handle the QH's also,
+ * but it doesn't seem necessary right now.
+ * The layout looks as follows:
+ * frame list pointer -> iso td's (if any) ->
+ * periodic interrupt td (if framelist 0) -> irq qh -> control qh -> bulk qh
+ */
+
+static spinlock_t framelist_lock = SPIN_LOCK_UNLOCKED;
+
+static void uhci_add_frame_list(struct uhci *uhci, struct uhci_td *td, unsigned framenum)
+{
+ unsigned long flags;
+ struct uhci_td *nexttd;
+
+ framenum %= UHCI_NUMFRAMES;
+ spin_lock_irqsave(&framelist_lock, flags);
+ td->backptr = &uhci->fl->frame[framenum];
+ td->link = uhci->fl->frame[framenum];
+ if (!(td->link & (UHCI_PTR_TERM | UHCI_PTR_QH))) {
+ nexttd = (struct uhci_td *)bus_to_virt(td->link & ~15);
+ nexttd->backptr = &td->link;
+ }
+ wmb();
+ uhci->fl->frame[framenum] = virt_to_bus(td);
+ spin_unlock_irqrestore(&framelist_lock, flags);
+}
+
+static void uhci_remove_frame_list(struct uhci *uhci, struct uhci_td *td)
+{
+ unsigned long flags;
+ struct uhci_td *nexttd;
+
+ if (!td->backptr)
+ return;
+ spin_lock_irqsave(&framelist_lock, flags);
+ *(td->backptr) = td->link;
+ if (!(td->link & (UHCI_PTR_TERM | UHCI_PTR_QH))) {
+ nexttd = (struct uhci_td *)bus_to_virt(td->link & ~15);
+ nexttd->backptr = td->backptr;
+ }
+ spin_unlock_irqrestore(&framelist_lock, flags);
+ td->backptr = NULL;
+ /*
+ * attention: td->link might still be in use by the
+ * hardware if the td is still active and the hardware
+ * was processing it. So td->link should be preserved
+ * until the frame number changes. Don't know what to do...
+ * udelay(1000) doesn't sound nice, and schedule()
+ * can't be used as this is called from within interrupt context.
+ */
+ /* for now warn if there's a possible problem */
+ if (td->status & TD_CTRL_ACTIVE) {
+ unsigned frn = inw(uhci->io_addr + USBFRNUM);
+ __u32 link = uhci->fl->frame[frn % UHCI_NUMFRAMES];
+ if (!(link & (UHCI_PTR_TERM | UHCI_PTR_QH))) {
+ struct uhci_td *tdl = (struct uhci_td *)bus_to_virt(link & ~15);
+ for (;;) {
+ if (tdl == td) {
+ printk(KERN_WARNING "uhci_remove_frame_list: td possibly still in use!!\n");
+ break;
+ }
+ if (tdl->link & (UHCI_PTR_TERM | UHCI_PTR_QH))
+ break;
+ tdl = (struct uhci_td *)bus_to_virt(tdl->link & ~15);
+ }
+ }
+ }
+}
+
+
+/*
* This function removes and disallocates all structures set up for a transfer.
* It takes the qh out of the skeleton, removes the tq and the td's.
* It only removes the associated interrupt handler if removeirq is set.
@@ -619,6 +703,7 @@
struct usb_isoc_desc **isocdesc)
{
struct usb_isoc_desc *id;
+ int i;
#ifdef BANDWIDTH_ALLOCATION
/* TBD: add bandwidth allocation/checking/management HERE. */
@@ -628,7 +713,7 @@
*isocdesc = NULL;
/* Check some parameters. */
- if ((frame_count < 0) || (frame_count > UHCI_NUMFRAMES)) {
+ if ((frame_count <= 0) || (frame_count > UHCI_NUMFRAMES)) {
#ifdef CONFIG_USB_DEBUG_ISOC
printk (KERN_DEBUG "uhci_init_isoc: invalid frame_count (%d)\n",
frame_count);
@@ -648,16 +733,20 @@
if (!id)
return -ENOMEM;
+ memset (id, 0, sizeof (*id) +
+ (sizeof (struct isoc_frame_desc) * frame_count));
+
id->td = kmalloc (sizeof (struct uhci_td) * frame_count, GFP_KERNEL);
if (!id->td) {
kfree (id);
return -ENOMEM;
}
- memset (id, 0, sizeof (*id) +
- (sizeof (struct isoc_frame_desc) * frame_count));
memset (id->td, 0, sizeof (struct uhci_td) * frame_count);
+ for (i = 0; i < frame_count; i++)
+ INIT_LIST_HEAD(&((struct uhci_td *)(id->td))[i].irq_list);
+
id->frame_count = frame_count;
id->frame_size = usb_maxpacket (usb_dev, pipe, usb_pipeout(pipe));
/* TBD: or make this a parameter to allow for frame_size
@@ -755,7 +844,7 @@
cur_frame = uhci_get_current_frame_number (isocdesc->usb_dev);
/* if not START_ASAP (i.e., RELATIVE or ABSOLUTE): */
- if (!pr_isocdesc)
+ if (!pr_isocdesc) {
if (isocdesc->start_type == START_RELATIVE) {
if ((isocdesc->start_frame < 0) || (isocdesc->start_frame > CAN_SCHEDULE_FRAMES)) {
#ifdef CONFIG_USB_DEBUG_ISOC
@@ -766,9 +855,10 @@
}
} /* end START_RELATIVE */
else
- if (isocdesc->start_type == START_ABSOLUTE) {
- if (isocdesc->start_frame > cur_frame) {
- if ((isocdesc->start_frame - cur_frame) > CAN_SCHEDULE_FRAMES) {
+ if (isocdesc->start_type == START_ABSOLUTE) { /* within the scope of cur_frame */
+ ix = USB_WRAP_FRAMENR(isocdesc->start_frame - cur_frame);
+ if (ix < START_FRAME_FUDGE || /* too small */
+ ix > CAN_SCHEDULE_FRAMES) { /* too large */
#ifdef CONFIG_USB_DEBUG_ISOC
printk (KERN_DEBUG "uhci_init_isoc: bad start_frame value (%d)\n",
isocdesc->start_frame);
@@ -787,6 +877,7 @@
}
}
} /* end START_ABSOLUTE */
+ }
/*
* Set the start/end frame numbers.
@@ -800,16 +891,14 @@
} else if (isocdesc->start_type == START_ASAP) {
isocdesc->start_frame = cur_frame + START_FRAME_FUDGE;
}
- /* else for start_type == START_ABSOLUTE, use start_frame as is. */
/* and see if start_frame needs any correction */
- if (isocdesc->start_frame >= UHCI_NUMFRAMES)
- isocdesc->start_frame -= UHCI_NUMFRAMES;
+ /* only wrap to USB frame numbers, the frame_list insertion routine
+ takes care of the wrapping to the frame_list size */
+ isocdesc->start_frame = USB_WRAP_FRAMENR(isocdesc->start_frame);
/* and fix the end_frame value */
- isocdesc->end_frame = isocdesc->start_frame + isocdesc->frame_count - 1;
- if (isocdesc->end_frame >= UHCI_NUMFRAMES)
- isocdesc->end_frame -= UHCI_NUMFRAMES;
+ isocdesc->end_frame = USB_WRAP_FRAMENR(isocdesc->start_frame + isocdesc->frame_count - 1);
isocdesc->prev_completed_frame = -1;
isocdesc->cur_completed_frame = -1;
@@ -857,6 +946,7 @@
td->status |= TD_CTRL_IOC;
td->completed = isocdesc->callback_fn;
cb_frames = 0;
+ uhci_add_irq_list (dev->uhci, td, isocdesc->callback_fn, isocdesc->context);
}
bufptr += fd->frame_length; /* or isocdesc->frame_size; */
@@ -864,21 +954,20 @@
/*
* Insert the TD in the frame list.
*/
- td->backptr = &uhci->fl->frame [cur_frame];
- td->link = uhci->fl->frame [cur_frame];
- uhci->fl->frame [cur_frame] = virt_to_bus (td);
+ uhci_add_frame_list(uhci, td, cur_frame);
- if (++cur_frame >= UHCI_NUMFRAMES)
- cur_frame = 0;
+ cur_frame = USB_WRAP_FRAMENR(cur_frame+1);
} /* end for ix */
/*
* Add IOC on the last TD.
*/
td--;
- td->status |= TD_CTRL_IOC;
- uhci_add_irq_list (dev->uhci, td, isocdesc->callback_fn, isocdesc->context); /* TBD: D.K. ??? */
-
+ if (!(td->status & TD_CTRL_IOC)) {
+ td->status |= TD_CTRL_IOC;
+ td->completed = isocdesc->callback_fn;
+ uhci_add_irq_list(dev->uhci, td, isocdesc->callback_fn, isocdesc->context); /* TBD: D.K. ??? */
+ }
return 0;
} /* end uhci_run_isoc */
@@ -897,9 +986,9 @@
struct uhci_device *dev = usb_to_uhci (isocdesc->usb_dev);
struct uhci *uhci = dev->uhci;
struct uhci_td *td;
- int ix, cur_frame;
+ int ix;
- if ((isocdesc->start_frame < 0) || (isocdesc->start_frame >= UHCI_NUMFRAMES)) {
+ if (USB_WRAP_FRAMENR(isocdesc->start_frame) != isocdesc->start_frame) {
#ifdef CONFIG_USB_DEBUG_ISOC
printk (KERN_DEBUG "uhci_kill_isoc: invalid start_frame (%d)\n",
isocdesc->start_frame);
@@ -907,13 +996,9 @@
return -EINVAL;
}
- for (ix = 0, td = isocdesc->td, cur_frame = isocdesc->start_frame;
- ix < isocdesc->frame_count; ix++, td++) {
+ for (ix = 0, td = isocdesc->td; ix < isocdesc->frame_count; ix++, td++) {
+ uhci_remove_frame_list(uhci, td);
td->status &= ~(TD_CTRL_ACTIVE | TD_CTRL_IOC);
- uhci->fl->frame [cur_frame] = td->link;
-
- if (++cur_frame >= UHCI_NUMFRAMES)
- cur_frame = 0;
} /* end for ix */
isocdesc->start_frame = -1;
@@ -922,18 +1007,21 @@
static void uhci_free_isoc (struct usb_isoc_desc *isocdesc)
{
+ int i;
+
/* If still Active, kill it. */
if (isocdesc->start_frame >= 0)
- uhci_kill_isoc (isocdesc);
+ uhci_kill_isoc(isocdesc);
- /* Remove it from the IRQ list. */
- uhci_remove_irq_list ((struct uhci_td *)&(isocdesc->td [isocdesc->frame_count - 1]));
+ /* Remove all td's from the IRQ list. */
+ for(i = 0; i < isocdesc->frame_count; i++)
+ uhci_remove_irq_list(((struct uhci_td *)(isocdesc->td))+i);
/* Free the associate memory. */
if (isocdesc->td)
- kfree (isocdesc->td);
+ kfree(isocdesc->td);
- kfree (isocdesc);
+ kfree(isocdesc);
} /* end uhci_free_isoc */
/*
@@ -1512,7 +1600,7 @@
td, isocdesc, first_comp, cur_comp, num_comp);
#endif
- for (ix = 0, fx = first_comp, prtd = &isocdesc->td [first_comp], frm = &isocdesc->frames [first_comp];
+ for (ix = 0, fx = first_comp, prtd = ((struct uhci_td *)(isocdesc->td))+first_comp, frm = &isocdesc->frames [first_comp];
ix < num_comp; ix++) {
frm->frame_length = uhci_actual_length (prtd->status);
isocdesc->total_length += frm->frame_length;
@@ -1666,6 +1754,7 @@
/* Don't clobber the frame */
td->link = uhci->fl->frame[0];
+ td->backptr = &uhci->fl->frame[0];
td->status = TD_CTRL_IOC;
td->info = (15 << 21) | (0x7f << 8) | USB_PID_IN; /* (ignored) input packet, 16 bytes, device 127 */
td->buffer = 0;
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)