On Sat, 23 Jun 2007, ok wrote: > > If you like, I'll try revising your patch. You'll have to test it > > Yes, thanks. > > > though; I don't have the hardware.
Olav: Below is a patch based on your work. There's a number of differences, plus some things I left as-is but which look suspicious. I took out the references to urb->lock in preproc_atl_queue and isp116x_urb_enqueue. They didn't seem to serve any purpose; is that correct? All the other updates are in postproc_atl_queue: In the revised code, short_not_ok doesn't serve a clear purpose. I changed it to short_packet_received; now it works somewhat differently and it should be easier to understand. There's a new local variable called "status" which indicates whether the URB is completed and holds the final status value. The code at the end of the big loop uses the variable to set urb->status. There's a comment with associated code: /* According to usb spec, zero-length Int transfer signals finishing of the urb. Hey, does this apply only for IN endpoints? */ This doesn't make any sense to me. Firstly, zero-length packets signal the end of any Control-, Bulk-, or Int-IN transfer; there's nothing special about Int. Secondly, with OUT transfers the only way you can get a zero-length packet is if it is the last packet of the transfer anyway; all the packets before the last must be of maximum length. I left that stuff in but it probably should be removed. In this section: /* Relax after previously failed, but later succeeded or correctly NAK'ed retransmission attempt */ if (ep->error_count && (cc == TD_CC_NOERROR || cc == TD_NOTACCESSED)) ep->error_count = 0; there's no need to test ep->error_count; it's easier just to set it to 0. In the big "switch (ep->nextpid)" block, I think it would be better to factor this test: if (PTD_GET_ACTIVE(ptd) || (cc != TD_CC_NOERROR && cc < 0x0E)) break; out of the individual cases. On the other hand, I'm not entirely sure what the test is intended for. Does it have something to do with retrying a failed transaction? The USB_PID_IN/USB_PID OUT case got changed considerably. I think the logic is pretty much the same as it was before, but it makes more sense to me like this. This test if (urb->transfer_flags & URB_ZERO_PACKET && ep->nextpid == USB_PID_OUT && !(PTD_GET_COUNT(ptd) % ep->maxpacket)) { DBG("Zero packet requested\n"); seems wrong. Wouldn't it get stuck in an infinite loop? I think the last part of the test ought to be more like: && PTD_GET_COUNT(ptd) == ep->maxpacket except that wouldn't be quite right when the zero-length packet has to be retried. In the USB_PID_SETUP case, this test if (urb->transfer_buffer_length == urb->actual_length) ep->nextpid = USB_PID_ACK; might be a little clearer if it was changed to if (urb->transfer_buffer_length == 0) ep->nextpid = USB_PID_ACK; Overall, I'm not sure that the toggle setting is correct in all the possible pathways. There were so many changes, it's hard to track them. In particular, I don't see where the toggle gets set to 1 for the status stage of a control transfer. Anyway, I can't check this as carefully as you can. Let me know what you think. Alan Stern Index: usb-2.6/drivers/usb/host/isp116x-hcd.c =================================================================== --- usb-2.6.orig/drivers/usb/host/isp116x-hcd.c +++ usb-2.6/drivers/usb/host/isp116x-hcd.c @@ -228,7 +228,6 @@ static void preproc_atl_queue(struct isp struct urb, urb_list); ptd = &ep->ptd; len = ep->length; - spin_lock(&urb->lock); ep->data = (unsigned char *)urb->transfer_buffer + urb->actual_length; @@ -264,7 +263,6 @@ static void preproc_atl_queue(struct isp | PTD_EP(ep->epnum); ptd->len = PTD_LEN(len) | PTD_DIR(dir); ptd->faddr = PTD_FA(usb_pipedevice(urb->pipe)); - spin_unlock(&urb->lock); if (!ep->active) { ptd->mps |= PTD_LAST_MSK; isp116x->atl_last_dir = dir; @@ -275,6 +273,61 @@ static void preproc_atl_queue(struct isp } /* + Take done or failed requests out of schedule. Give back + processed urbs. +*/ +static void finish_request(struct isp116x *isp116x, struct isp116x_ep *ep, + struct urb *urb) +__releases(isp116x->lock) __acquires(isp116x->lock) +{ + unsigned i; + + urb->hcpriv = NULL; + ep->error_count = 0; + + if (usb_pipecontrol(urb->pipe)) + ep->nextpid = USB_PID_SETUP; + + urb_dbg(urb, "Finish"); + + spin_unlock(&isp116x->lock); + usb_hcd_giveback_urb(isp116x_to_hcd(isp116x), urb); + spin_lock(&isp116x->lock); + + /* take idle endpoints out of the schedule */ + if (!list_empty(&ep->hep->urb_list)) + return; + + /* async deschedule */ + if (!list_empty(&ep->schedule)) { + list_del_init(&ep->schedule); + return; + } + + /* periodic deschedule */ + DBG("deschedule qh%d/%p branch %d\n", ep->period, ep, ep->branch); + for (i = ep->branch; i < PERIODIC_SIZE; i += ep->period) { + struct isp116x_ep *temp; + struct isp116x_ep **prev = &isp116x->periodic[i]; + + while (*prev && ((temp = *prev) != ep)) + prev = &temp->next; + if (*prev) + *prev = ep->next; + isp116x->load[i] -= ep->load; + } + ep->branch = PERIODIC_SIZE; + isp116x_to_hcd(isp116x)->self.bandwidth_allocated -= + ep->load / ep->period; + + /* switch irq type? */ + if (!--isp116x->periodic_count) { + isp116x->irqenb &= ~HCuPINT_SOF; + isp116x->irqenb |= HCuPINT_ATL; + } +} + +/* Analyze transfer results, handle partial transfers and errors */ static void postproc_atl_queue(struct isp116x *isp116x) @@ -283,7 +336,8 @@ static void postproc_atl_queue(struct is struct urb *urb; struct usb_device *udev; struct ptd *ptd; - int short_not_ok; + int short_packet_received; + int status; u8 cc; for (ep = isp116x->atl_active; ep; ep = ep->active) { @@ -293,56 +347,36 @@ static void postproc_atl_queue(struct is udev = urb->dev; ptd = &ep->ptd; cc = PTD_GET_CC(ptd); - short_not_ok = 1; - spin_lock(&urb->lock); + short_packet_received = 0; + status = -EINPROGRESS; - /* Data underrun is special. For allowed underrun - we clear the error and continue as normal. For - forbidden underrun we finish the DATA stage - immediately while for control transfer, - we do a STATUS stage. */ if (cc == TD_DATAUNDERRUN) { if (!(urb->transfer_flags & URB_SHORT_NOT_OK)) { DBG("Allowed data underrun\n"); cc = TD_CC_NOERROR; - short_not_ok = 0; + short_packet_received = 1; } else { - ep->error_count = 1; - if (usb_pipecontrol(urb->pipe)) - ep->nextpid = USB_PID_ACK; - else - usb_settoggle(udev, ep->epnum, - ep->nextpid == - USB_PID_OUT, - PTD_GET_TOGGLE(ptd)); + usb_settoggle(udev, ep->epnum, + ep->nextpid == USB_PID_OUT, + PTD_GET_TOGGLE(ptd)); urb->actual_length += PTD_GET_COUNT(ptd); - urb->status = cc_to_error[TD_DATAUNDERRUN]; - spin_unlock(&urb->lock); - continue; + status = cc_to_error[TD_DATAUNDERRUN]; + goto done; } } - /* Keep underrun error through the STATUS stage */ - if (urb->status == cc_to_error[TD_DATAUNDERRUN]) - cc = TD_DATAUNDERRUN; if (cc != TD_CC_NOERROR && cc != TD_NOTACCESSED && (++ep->error_count >= 3 || cc == TD_CC_STALL || cc == TD_DATAOVERRUN)) { - if (urb->status == -EINPROGRESS) - urb->status = cc_to_error[cc]; - if (ep->nextpid == USB_PID_ACK) - ep->nextpid = 0; - spin_unlock(&urb->lock); - continue; + status = cc_to_error[cc]; + goto done; } /* According to usb spec, zero-length Int transfer signals finishing of the urb. Hey, does this apply only for IN endpoints? */ if (usb_pipeint(urb->pipe) && !PTD_GET_LEN(ptd)) { - if (urb->status == -EINPROGRESS) - urb->status = 0; - spin_unlock(&urb->lock); - continue; + status = 0; + goto done; } /* Relax after previously failed, but later succeeded @@ -367,22 +401,21 @@ static void postproc_atl_queue(struct is if (PTD_GET_ACTIVE(ptd) || (cc != TD_CC_NOERROR && cc < 0x0E)) break; - if (urb->transfer_buffer_length != urb->actual_length) { - if (short_not_ok) - break; - } else { - if (urb->transfer_flags & URB_ZERO_PACKET - && ep->nextpid == USB_PID_OUT - && !(PTD_GET_COUNT(ptd) % ep->maxpacket)) { - DBG("Zero packet requested\n"); - break; - } + if (urb->transfer_buffer_length != urb->actual_length + && !short_packet_received) + break; /* Still in progress */ + if (usb_pipecontrol(urb->pipe)) { + ep->nextpid = USB_PID_ACK; + break; + } + if (urb->transfer_flags & URB_ZERO_PACKET + && ep->nextpid == USB_PID_OUT + && !(PTD_GET_COUNT(ptd) % ep->maxpacket)) { + DBG("Zero packet requested\n"); + break; } /* All data for this URB is transferred, let's finish */ - if (usb_pipecontrol(urb->pipe)) - ep->nextpid = USB_PID_ACK; - else if (urb->status == -EINPROGRESS) - urb->status = 0; + status = 0; break; case USB_PID_SETUP: if (PTD_GET_ACTIVE(ptd) @@ -402,69 +435,22 @@ static void postproc_atl_queue(struct is if (PTD_GET_ACTIVE(ptd) || (cc != TD_CC_NOERROR && cc < 0x0E)) break; - if (urb->status == -EINPROGRESS) - urb->status = 0; + status = 0; ep->nextpid = 0; break; default: BUG(); } - spin_unlock(&urb->lock); - } -} - -/* - Take done or failed requests out of schedule. Give back - processed urbs. -*/ -static void finish_request(struct isp116x *isp116x, struct isp116x_ep *ep, - struct urb *urb) -__releases(isp116x->lock) __acquires(isp116x->lock) -{ - unsigned i; - - urb->hcpriv = NULL; - ep->error_count = 0; - if (usb_pipecontrol(urb->pipe)) - ep->nextpid = USB_PID_SETUP; - - urb_dbg(urb, "Finish"); - - spin_unlock(&isp116x->lock); - usb_hcd_giveback_urb(isp116x_to_hcd(isp116x), urb); - spin_lock(&isp116x->lock); - - /* take idle endpoints out of the schedule */ - if (!list_empty(&ep->hep->urb_list)) - return; - - /* async deschedule */ - if (!list_empty(&ep->schedule)) { - list_del_init(&ep->schedule); - return; - } - - /* periodic deschedule */ - DBG("deschedule qh%d/%p branch %d\n", ep->period, ep, ep->branch); - for (i = ep->branch; i < PERIODIC_SIZE; i += ep->period) { - struct isp116x_ep *temp; - struct isp116x_ep **prev = &isp116x->periodic[i]; - - while (*prev && ((temp = *prev) != ep)) - prev = &temp->next; - if (*prev) - *prev = ep->next; - isp116x->load[i] -= ep->load; - } - ep->branch = PERIODIC_SIZE; - isp116x_to_hcd(isp116x)->self.bandwidth_allocated -= - ep->load / ep->period; - - /* switch irq type? */ - if (!--isp116x->periodic_count) { - isp116x->irqenb &= ~HCuPINT_SOF; - isp116x->irqenb |= HCuPINT_ATL; + done: + if (status != -EINPROGRESS) { + spin_lock(&urb->lock); + if (urb->status == -EINPROGRESS) + urb->status = status; + spin_unlock(&urb->lock); + } + if (urb->status != -EINPROGRESS) + finish_request(isp116x, ep, urb); } } @@ -570,9 +556,6 @@ static void start_atl_transfers(struct i */ static void finish_atl_transfers(struct isp116x *isp116x) { - struct isp116x_ep *ep; - struct urb *urb; - if (!isp116x->atl_active) return; /* Fifo not ready? */ @@ -582,16 +565,6 @@ static void finish_atl_transfers(struct atomic_inc(&isp116x->atl_finishing); unpack_fifo(isp116x); postproc_atl_queue(isp116x); - for (ep = isp116x->atl_active; ep; ep = ep->active) { - urb = - container_of(ep->hep->urb_list.next, struct urb, urb_list); - /* USB_PID_ACK check here avoids finishing of - control transfers, for which TD_DATAUNDERRUN - occured, while URB_SHORT_NOT_OK was set */ - if (urb && urb->status != -EINPROGRESS - && ep->nextpid != USB_PID_ACK) - finish_request(isp116x, ep, urb); - } atomic_dec(&isp116x->atl_finishing); } @@ -821,15 +794,12 @@ static int isp116x_urb_enqueue(struct us } /* in case of unlink-during-submit */ - spin_lock(&urb->lock); if (urb->status != -EINPROGRESS) { - spin_unlock(&urb->lock); finish_request(isp116x, ep, urb); ret = 0; goto fail; } urb->hcpriv = hep; - spin_unlock(&urb->lock); start_atl_transfers(isp116x); fail: ------------------------------------------------------------------------- This SF.net email is sponsored by DB2 Express Download DB2 Express C - the FREE version of DB2 express and take control of your XML. No limits. Just data. Click to get it now. http://sourceforge.net/powerbar/db2/ _______________________________________________ linux-usb-devel@lists.sourceforge.net To unsubscribe, use the last form field at: https://lists.sourceforge.net/lists/listinfo/linux-usb-devel