summaryrefslogtreecommitdiffstats
path: root/drivers/usb/uhci.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/uhci.c')
-rw-r--r--drivers/usb/uhci.c269
1 files changed, 187 insertions, 82 deletions
diff --git a/drivers/usb/uhci.c b/drivers/usb/uhci.c
index ae2b4ec73..20be3faab 100644
--- a/drivers/usb/uhci.c
+++ b/drivers/usb/uhci.c
@@ -91,7 +91,7 @@ static int uhci_kill_isoc (struct usb_isoc_desc *isocdesc);
/*
* Map status to standard result codes
*
- * <status> is (td->status & 0xFE0000) [a.k.a. uhci_status_bits(td->status)
+ * <status> is (td->status & 0xFE0000) [a.k.a. uhci_status_bits(td->status)]
* <dir_out> is True for output TDs and False for input TDs.
*/
static int uhci_map_status(int status, int dir_out)
@@ -127,7 +127,7 @@ static int uhci_td_result(struct uhci_device *dev, struct uhci_td *td, unsigned
{
unsigned int status;
struct uhci_td *tmp;
- int count = 1000, bytesreceived = 0;
+ int count = 1000, actlength, explength;
if (rval)
*rval = 0;
@@ -147,29 +147,26 @@ static int uhci_td_result(struct uhci_device *dev, struct uhci_td *td, unsigned
if (status)
break;
-#if 0
- if (debug) {
- /* Must reset the toggle on first error */
- if (uhci_debug) {
- printk(KERN_DEBUG "Set toggle from %x rval %ld\n",
- (unsigned int)tmp, rval ? *rval : 0);
- }
- usb_settoggle(dev->usb, uhci_endpoint(tmp->info),
- uhci_packetout(tmp->info) ^ 1,
- uhci_toggle(tmp->info));
- break;
- }
- } else {
- if (rval && ((tmp->info & 0xFF) == USB_PID_IN))
- *rval += uhci_actual_length(tmp->status);
- }
-#endif
/* The length field is only valid if the TD was completed */
if (!(tmp->status & TD_CTRL_ACTIVE) && uhci_packetin(tmp->info)) {
- bytesreceived += uhci_actual_length(tmp->status);
+ explength = uhci_expected_length(tmp->info);
+ actlength = uhci_actual_length(tmp->status);
if (rval)
- *rval += uhci_actual_length(tmp->status);
+ *rval += actlength;
+ if (explength != actlength) {
+ /* Reset the data toggle on error. */
+ if (debug || uhci_debug)
+ printk(KERN_DEBUG "Set toggle from %p rval %ld%c for status=%x to %d, exp=%d, act=%d\n",
+ tmp, rval ? *rval : 0,
+ rval ? '*' : '/', tmp->status,
+ uhci_toggle(tmp->info) ^ 1,
+ explength, actlength);
+ usb_settoggle(dev->usb, uhci_endpoint(tmp->info),
+ uhci_packetout(tmp->info),
+ uhci_toggle(tmp->info) ^ 1);
+ break; // Short packet
+ }
}
if ((tmp->link & UHCI_PTR_TERM) ||
@@ -190,26 +187,24 @@ static int uhci_td_result(struct uhci_device *dev, struct uhci_td *td, unsigned
if (!status)
return USB_ST_NOERROR;
- /* XXX FIXME APC BackUPS Pro kludge */
+ /* APC BackUPS Pro kludge */
/* It tries to send all of the descriptor instead of */
/* the amount we requested */
if (tmp->status & TD_CTRL_IOC &&
tmp->status & TD_CTRL_ACTIVE &&
- tmp->status & TD_CTRL_NAK /* && its a control TD */)
+ tmp->status & TD_CTRL_NAK &&
+ tmp->pipetype == PIPE_CONTROL)
return USB_ST_NOERROR;
-#if 0
/* We got to an error, but the controller hasn't finished */
- /* with it, yet */
+ /* with it yet. */
if (tmp->status & TD_CTRL_ACTIVE)
return USB_ST_NOCHANGE;
-#endif
/* If this wasn't the last TD and SPD is set, ACTIVE */
/* is not and NAK isn't then we received a short */
/* packet */
- if (tmp->status & TD_CTRL_SPD &&
- !(tmp->status & TD_CTRL_NAK))
+ if (tmp->status & TD_CTRL_SPD && !(tmp->status & TD_CTRL_NAK))
return USB_ST_NOERROR;
}
@@ -516,7 +511,7 @@ static void uhci_remove_irq_list(struct uhci_td *td)
* 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) ->
+ * frame list pointer -> iso td's (if any) ->
* periodic interrupt td (if framelist 0) -> irq qh -> control qh -> bulk qh
*/
@@ -532,7 +527,7 @@ static void uhci_add_frame_list(struct uhci *uhci, struct uhci_td *td, unsigned
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 = (struct uhci_td *)uhci_ptr_to_virt(td->link);
nexttd->backptr = &td->link;
}
wmb();
@@ -550,7 +545,7 @@ static void uhci_remove_frame_list(struct uhci *uhci, struct uhci_td *td)
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 = (struct uhci_td *)uhci_ptr_to_virt(td->link);
nexttd->backptr = td->backptr;
}
spin_unlock_irqrestore(&framelist_lock, flags);
@@ -568,7 +563,7 @@ static void uhci_remove_frame_list(struct uhci *uhci, struct uhci_td *td)
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);
+ struct uhci_td *tdl = (struct uhci_td *)uhci_ptr_to_virt(link);
for (;;) {
if (tdl == td) {
printk(KERN_WARNING "uhci_remove_frame_list: td possibly still in use!!\n");
@@ -576,9 +571,9 @@ static void uhci_remove_frame_list(struct uhci *uhci, struct uhci_td *td)
}
if (tdl->link & (UHCI_PTR_TERM | UHCI_PTR_QH))
break;
- tdl = (struct uhci_td *)bus_to_virt(tdl->link & ~15);
+ tdl = (struct uhci_td *)uhci_ptr_to_virt(tdl->link);
}
- }
+ }
}
}
@@ -603,6 +598,7 @@ static void uhci_remove_transfer(struct uhci_td *td, char removeirq)
/* Remove it from the skeleton */
uhci_remove_qh(td->qh->skel, td->qh);
uhci_qh_free(td->qh);
+
do {
nextlink = curtd->link;
@@ -612,10 +608,11 @@ static void uhci_remove_transfer(struct uhci_td *td, char removeirq)
uhci_remove_td(curtd);
uhci_td_free(curtd);
+
if (nextlink & UHCI_PTR_TERM) /* Tail? */
break;
- curtd = bus_to_virt(nextlink & ~UHCI_PTR_BITS);
+ curtd = (struct uhci_td *)uhci_ptr_to_virt(nextlink);
if (!--maxcount) {
printk(KERN_ERR "runaway td's!?\n");
break;
@@ -624,7 +621,7 @@ static void uhci_remove_transfer(struct uhci_td *td, char removeirq)
}
/*
- * Request a interrupt handler..
+ * Request an interrupt handler..
*
* Returns 0 (success) or negative (failure).
* Also returns/sets a "handle pointer" that release_irq can use to stop this
@@ -665,9 +662,8 @@ static int uhci_request_irq(struct usb_device *usb_dev, unsigned int pipe,
td->bandwidth_alloc = bustime;
/* if period 0, set _REMOVE flag */
- if (period == 0) {
+ if (!period)
td->flags |= UHCI_TD_REMOVE;
- }
qh->skel = &dev->uhci->skelqh[__interval_to_skel(period)];
@@ -690,7 +686,7 @@ static int uhci_request_irq(struct usb_device *usb_dev, unsigned int pipe,
*
* This function can NOT be called from an interrupt.
*/
-int uhci_release_irq(struct usb_device *usb, void *handle)
+static int uhci_release_irq(struct usb_device *usb, void *handle)
{
struct uhci_td *td;
struct uhci_qh *qh;
@@ -699,12 +695,13 @@ int uhci_release_irq(struct usb_device *usb, void *handle)
if (!td)
return USB_ST_INTERNALERROR;
+ qh = td->qh;
+
/* Remove it from the internal irq_list */
uhci_remove_irq_list(td);
/* Remove the interrupt TD and QH */
uhci_remove_td(td);
- qh = td->qh;
uhci_remove_qh(qh->skel, qh);
if (td->completed != NULL)
@@ -744,7 +741,7 @@ static int uhci_get_current_frame_number(struct usb_device *usb_dev)
*/
static int uhci_init_isoc (struct usb_device *usb_dev,
unsigned int pipe,
- int frame_count, /* bandwidth % = 100 * this / 1000 */
+ int frame_count, /* bandwidth % = 100 * this / 1024 */
void *context,
struct usb_isoc_desc **isocdesc)
{
@@ -896,7 +893,7 @@ static int uhci_run_isoc (struct usb_isoc_desc *isocdesc,
}
} /* end START_RELATIVE */
else
- if (isocdesc->start_type == START_ABSOLUTE) { /* within the scope of cur_frame */
+ 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 */
@@ -999,6 +996,7 @@ static int uhci_run_isoc (struct usb_isoc_desc *isocdesc,
td->completed = isocdesc->callback_fn;
uhci_add_irq_list(dev->uhci, td, isocdesc->callback_fn, isocdesc); /* TBD: D.K. ??? */
}
+
return 0;
} /* end uhci_run_isoc */
@@ -1028,6 +1026,7 @@ static int uhci_kill_isoc (struct usb_isoc_desc *isocdesc)
}
for (ix = 0, td = isocdesc->td; ix < isocdesc->frame_count; ix++, td++) {
+ /* Deactivate and unlink */
uhci_remove_frame_list(uhci, td);
td->status &= ~(TD_CTRL_ACTIVE | TD_CTRL_IOC);
} /* end for ix */
@@ -1044,9 +1043,9 @@ static void uhci_free_isoc (struct usb_isoc_desc *isocdesc)
if (isocdesc->start_frame >= 0)
uhci_kill_isoc(isocdesc);
- /* 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);
+ /* 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)
@@ -1137,7 +1136,8 @@ static int uhci_run_control(struct uhci_device *dev, struct uhci_td *first, stru
* there is no restriction on length of transfers
* anymore
*/
-static int uhci_control_msg(struct usb_device *usb_dev, unsigned int pipe, devrequest *cmd, void *data, int len, int timeout)
+static int uhci_control_msg(struct usb_device *usb_dev, unsigned int pipe, devrequest *cmd,
+ void *data, int len, int timeout)
{
struct uhci_device *dev = usb_to_uhci(usb_dev);
struct uhci_td *first, *td, *prevtd;
@@ -1147,6 +1147,9 @@ static int uhci_control_msg(struct usb_device *usb_dev, unsigned int pipe, devre
__u32 nextlink;
unsigned long bytesrequested = len;
unsigned long bytesread = 0;
+#ifdef DUMP_RAW
+ unsigned char *orig_data = (unsigned char *) data;
+#endif
first = td = uhci_td_alloc(dev);
if (!td)
@@ -1176,8 +1179,10 @@ static int uhci_control_msg(struct usb_device *usb_dev, unsigned int pipe, devre
prevtd = td;
td = uhci_td_alloc(dev);
- if (!td)
- return -ENOMEM;
+ if (!td) {
+ uhci_td_free(prevtd);
+ return -ENOMEM;
+ }
prevtd->link = virt_to_bus(td) | UHCI_PTR_DEPTH;
@@ -1261,9 +1266,29 @@ static int uhci_control_msg(struct usb_device *usb_dev, unsigned int pipe, devre
if (uhci_debug && ret) {
__u8 *p = (__u8 *)cmd;
+ printk("dev %d, pipe %X requested %ld bytes, got %ld, status=%d:\n",
+ usb_dev->devnum, pipe, bytesrequested, bytesread, ret);
printk(KERN_DEBUG "Failed cmd - %02X %02X %02X %02X %02X %02X %02X %02X\n",
p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]);
}
+
+#ifdef DUMP_RAW
+ if (!ret && usb_pipein(pipe)) { /* good Input control msg */
+ int i;
+
+ printk (KERN_CRIT "ctrl msg [%02x %02x %04x %04x %04x] on pipe %x returned:\n",
+ cmd->requesttype, cmd->request,
+ cmd->value, cmd->index, cmd->length, pipe);
+ for (i = 0; i < bytesrequested; ) {
+ printk(" %02x", orig_data[i]);
+ if (++i % 16 == 0)
+ printk("\n");
+ }
+ if (i % 16 != 0)
+ printk("\n");
+ }
+#endif
+
return ret;
}
@@ -1344,7 +1369,7 @@ static int uhci_bulk_msg(struct usb_device *usb_dev, unsigned int pipe, void *da
if (!td)
return -ENOMEM;
- prevtd = first; //This is fake, but at least it's not NULL
+ prevtd = first; // This is fake, but at least it's not NULL
while (len > 0) {
/* Build the TD for control status */
int pktsze = len;
@@ -1376,7 +1401,7 @@ static int uhci_bulk_msg(struct usb_device *usb_dev, unsigned int pipe, void *da
usb_dotoggle(usb_dev, usb_pipeendpoint(pipe), usb_pipeout(pipe));
}
- td->link = 1; /* Terminate */
+ td->link = UHCI_PTR_TERM; /* Terminate */
td->status |= TD_CTRL_IOC;
/* CHANGE DIRECTION HERE! SAVE IT SOMEWHERE IN THE ENDPOINT!!! */
@@ -1619,7 +1644,6 @@ static void uhci_check_configuration(struct uhci *uhci)
} while (nr < maxchild);
}
-#if 0
static int fixup_isoc_desc (struct uhci_td *td)
{
struct usb_isoc_desc *isocdesc = td->dev_id;
@@ -1647,7 +1671,7 @@ static int fixup_isoc_desc (struct uhci_td *td)
if ((frm->frame_status = uhci_map_status (uhci_status_bits (prtd->status),
uhci_packetout (prtd->info))))
isocdesc->error_count++;
-
+
prtd++;
frm++;
if (++fx >= isocdesc->frame_count) { /* wrap fx, prtd, and frm */
@@ -1671,8 +1695,106 @@ static int fixup_isoc_desc (struct uhci_td *td)
return 0;
}
-#endif /* 0 */
+int uhci_isoc_callback(struct uhci *uhci, struct uhci_td *td, int status, unsigned long rval)
+{
+ struct usb_isoc_desc *isocdesc = td->dev_id;
+ int ret;
+
+ /*
+ * Fixup the isocdesc for the driver: total_completed_frames,
+ * error_count, total_length, frames array.
+ *
+ * ret = callback_fn (int error_count, void *buffer,
+ * int len, void *isocdesc);
+ */
+
+ fixup_isoc_desc (td);
+
+ ret = td->completed (isocdesc->error_count, bus_to_virt (td->buffer),
+ isocdesc->total_length, isocdesc);
+
+ /*
+ * Isoc. handling of return value from td->completed (callback function)
+ */
+
+ switch (ret) {
+ case CB_CONTINUE: /* similar to the REMOVE condition below */
+ /* TBD */
+ uhci_td_free (td);
+ break;
+
+ case CB_REUSE: /* similar to the re-add condition below,
+ * but Not ACTIVE */
+ /* TBD */
+ /* usb_dev = td->dev->usb; */
+
+ list_add(&td->irq_list, &uhci->interrupt_list);
+
+ td->status = (td->status & (TD_CTRL_SPD | TD_CTRL_C_ERR_MASK |
+ TD_CTRL_LS | TD_CTRL_IOS | TD_CTRL_IOC)) |
+ TD_CTRL_IOC;
+
+ /* The HC removes it, so re-add it */
+ /* Insert into a QH? */
+ uhci_insert_td_in_qh(td->qh, td);
+ break;
+
+ case CB_RESTART: /* similar to re-add, but mark ACTIVE */
+ /* TBD */
+ /* usb_dev = td->dev->usb; */
+
+ list_add(&td->irq_list, &uhci->interrupt_list);
+
+ td->status = (td->status & (TD_CTRL_SPD | TD_CTRL_C_ERR_MASK |
+ TD_CTRL_LS | TD_CTRL_IOS | TD_CTRL_IOC)) |
+ TD_CTRL_ACTIVE | TD_CTRL_IOC;
+
+ /* The HC removes it, so re-add it */
+ uhci_insert_td_in_qh(td->qh, td);
+ break;
+
+ case CB_ABORT: /* kill/abort */
+ /* TBD */
+ uhci_kill_isoc (isocdesc);
+ break;
+ } /* end isoc. TD switch */
+
+ return 0;
+}
+
+int uhci_callback(struct uhci *uhci, struct uhci_td *td, int status, unsigned long rval)
+{
+ if (td->completed(status, bus_to_virt(td->buffer), rval, td->dev_id)) {
+ struct usb_device *usb_dev = td->dev->usb;
+
+ list_add(&td->irq_list, &uhci->interrupt_list);
+
+ usb_dotoggle(usb_dev, uhci_endpoint(td->info), uhci_packetout(td->info));
+ td->info &= ~(1 << TD_TOKEN_TOGGLE); /* clear data toggle */
+ td->info |= usb_gettoggle(usb_dev, uhci_endpoint(td->info),
+ uhci_packetout(td->info)) << TD_TOKEN_TOGGLE; /* toggle between data0 and data1 */
+ td->status = (td->status & 0x2F000000) | TD_CTRL_ACTIVE | TD_CTRL_IOC;
+ /* The HC only removes it when it completed */
+ /* successfully, so force remove and re-add it. */
+ uhci_remove_td(td);
+ uhci_insert_td_in_qh(td->qh, td);
+ } else if (td->flags & UHCI_TD_REMOVE) {
+ struct usb_device *usb_dev = td->dev->usb;
+
+ /* marked for removal */
+ td->flags &= ~UHCI_TD_REMOVE;
+ usb_dotoggle(usb_dev, uhci_endpoint(td->info), uhci_packetout(td->info));
+ uhci_remove_qh(td->qh->skel, td->qh);
+ uhci_qh_free(td->qh);
+ if (td->pipetype == PIPE_INTERRUPT)
+ usb_release_bandwidth(usb_dev, td->bandwidth_alloc);
+ uhci_td_free(td);
+ }
+
+ return 0;
+}
+
static void uhci_interrupt_notify(struct uhci *uhci)
{
struct list_head *tmp, *head = &uhci->interrupt_list;
@@ -1690,39 +1812,18 @@ static void uhci_interrupt_notify(struct uhci *uhci)
/* TD's completed successfully */
status = uhci_td_result(td->dev, td, &rval, 0);
- if ((status == USB_ST_NOERROR) && (td->status & TD_CTRL_ACTIVE))
+ if (status == USB_ST_NOCHANGE)
continue;
/* remove from IRQ list */
list_del(&td->irq_list);
INIT_LIST_HEAD(&td->irq_list);
- if (td->completed(status, bus_to_virt(td->buffer), rval,
- td->dev_id)) {
- struct usb_device *usb_dev = td->dev->usb;
-
- list_add(&td->irq_list, &uhci->interrupt_list);
-
- usb_dotoggle(usb_dev, usb_pipeendpoint(td->info), usb_pipeout(td->info) ^ 1);
- td->info &= ~(1 << 19); /* clear data toggle */
- td->info |= usb_gettoggle(usb_dev, usb_pipeendpoint(td->info),
- uhci_packetout(td->info)) << 19; /* toggle between data0 and data1 */
- td->status = (td->status & 0x2F000000) | TD_CTRL_ACTIVE | TD_CTRL_IOC;
- /* The HC only removes it when it completed */
- /* successfully, so force remove and re-add it */
- uhci_remove_td(td);
- uhci_insert_td_in_qh(td->qh, td);
- } else if (td->flags & UHCI_TD_REMOVE) {
- struct usb_device *usb_dev = td->dev->usb;
-
- /* marked for removal */
- td->flags &= ~UHCI_TD_REMOVE;
- usb_dotoggle(usb_dev, usb_pipeendpoint(td->info), uhci_packetout(td->info));
- uhci_remove_qh(td->qh->skel, td->qh);
- uhci_qh_free(td->qh);
- uhci_td_free(td);
- if (td->pipetype == PIPE_INTERRUPT)
- usb_release_bandwidth(usb_dev, td->bandwidth_alloc);
+ if (td->pipetype == PIPE_ISOCHRONOUS) {
+ uhci_isoc_callback(uhci, td, status, rval);
+ }
+ else {
+ uhci_callback(uhci, td, status, rval);
}
/* If completed does not wants to reactivate, then */
@@ -1800,7 +1901,8 @@ static void uhci_init_ticktd(struct uhci *uhci)
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->info = (15 << 21) | (0x7f << 8) | USB_PID_IN;
+ /* (ignored) input packet, 16 bytes, device 127 */
td->buffer = 0;
td->qh = NULL;
td->pipetype = -1;
@@ -1843,7 +1945,8 @@ static void start_hc(struct uhci *uhci)
}
/* Turn on all interrupts */
- outw(USBINTR_TIMEOUT | USBINTR_RESUME | USBINTR_IOC | USBINTR_SP, io_addr + USBINTR);
+ outw(USBINTR_TIMEOUT | USBINTR_RESUME | USBINTR_IOC | USBINTR_SP,
+ io_addr + USBINTR);
/* Start at frame 0 */
outw(0, io_addr + USBFRNUM);
@@ -2169,6 +2272,8 @@ static int start_uhci(struct pci_dev *dev)
/* disable legacy emulation */
pci_write_config_word(dev, USBLEGSUP, USBLEGSUP_DEFAULT);
+ pci_enable_device(dev);
+
return found_uhci(dev->irq, io_addr, io_size);
}
return -1;