diff --git a/arch/arm/src/lpc17xx/lpc17_usbhost.c b/arch/arm/src/lpc17xx/lpc17_usbhost.c index 6709e16cba2..bcc4eebe2ad 100644 --- a/arch/arm/src/lpc17xx/lpc17_usbhost.c +++ b/arch/arm/src/lpc17xx/lpc17_usbhost.c @@ -1568,7 +1568,7 @@ static int lpc17_ctrltd(struct lpc17_usbhost_s *priv, struct lpc17_ed_s *ed, } else { - uvdbg("Bad TD completion status: %d\n", xfrinfo->tdstatus); + udbg("ERROR: Bad TD completion status: %d\n", xfrinfo->tdstatus); ret = xfrinfo->tdstatus == TD_CC_STALL ? -EPERM : -EIO; } } @@ -2957,10 +2957,26 @@ static ssize_t lpc17_transfer(struct usbhost_driver_s *drvr, usbhost_ep_t ep, } else { - /* Return an I/O error */ + /* Map the bad completion status to something that a class driver + * might understand. + */ udbg("ERROR: Bad TD completion status: %d\n", xfrinfo->tdstatus); - nbytes = -EIO; + + switch (xfrinfo->tdstatus) + { + case TD_CC_STALL: + nbytes = -EPERM; + break; + + case TD_CC_USER: + nbytes = -ESHUTDOWN; + break; + + default: + nbytes = -EIO; + break; + } } errout_with_wdhwait: @@ -3031,10 +3047,26 @@ static void lpc17_asynch_completion(struct lpc17_usbhost_s *priv, } else { - /* Provide an I/O error indication */ + /* Map the bad completion status to something that a class driver + * might understand. + */ udbg("ERROR: Bad TD completion status: %d\n", xfrinfo->tdstatus); - nbytes = -EIO; + + switch (xfrinfo->tdstatus) + { + case TD_CC_STALL: + nbytes = -EPERM; + break; + + case TD_CC_USER: + nbytes = -ESHUTDOWN; + break; + + default: + nbytes = -EIO; + break; + } } #if LPC17_IOBUFFERS > 0 @@ -3209,14 +3241,14 @@ errout_with_sem: #ifdef CONFIG_USBHOST_ASYNCH static int lpc17_cancel(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep) { + struct lpc17_usbhost_s *priv = (struct lpc17_usbhost_s *)drvr; struct lpc17_ed_s *ed = (struct lpc17_ed_s *)ep; struct lpc17_gtd_s *td; struct lpc17_gtd_s *next; struct lpc17_xfrinfo_s *xfrinfo; irqstate_t flags; - int ret = OK; - DEBUGASSERT(ed != NULL); + DEBUGASSERT(priv != NULL && ed != NULL); /* These first steps must be atomic as possible */ @@ -3227,40 +3259,66 @@ static int lpc17_cancel(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep) xfrinfo = ed->xfrinfo; if (xfrinfo) { - /* It would be an usage error to use the interface to try to cancel a - * synchronous transfer (wdhwait == true). + /* It might be possible for no transfer to be in progress (callback == NULL + * and wdhwait == false) */ - DEBUGASSERT(xfrinfo->wdhwait == false); - - /* We really need some kind of atomic test and set to do this right */ - - td = (struct lpc17_gtd_s *)(ed->hw.headp & ED_HEADP_ADDR_MASK); - ed->hw.headp = LPC17_TDTAIL_ADDR; - ed->xfrinfo = NULL; - - /* Free all transfer descriptors that were connected to the ED */ - - DEBUGASSERT(td != (struct lpc17_gtd_s *)LPC17_TDTAIL_ADDR); - while (td != (struct lpc17_gtd_s *)LPC17_TDTAIL_ADDR) + if (xfrinfo->callback || xfrinfo->wdhwait) { - next = (struct lpc17_gtd_s *)td->hw.nexttd; - lpc17_tdfree(td); - td = next; + /* We really need some kind of atomic test and set to do this right */ + + td = (struct lpc17_gtd_s *)(ed->hw.headp & ED_HEADP_ADDR_MASK); + ed->hw.headp = LPC17_TDTAIL_ADDR; + ed->xfrinfo = NULL; + + /* Free all transfer descriptors that were connected to the ED */ + + DEBUGASSERT(td != (struct lpc17_gtd_s *)LPC17_TDTAIL_ADDR); + while (td != (struct lpc17_gtd_s *)LPC17_TDTAIL_ADDR) + { + next = (struct lpc17_gtd_s *)td->hw.nexttd; + lpc17_tdfree(td); + td = next; + } + + xfrinfo->tdstatus = TD_CC_USER; + + /* If there is a thread waiting for the transfer to complete, then + * wake up the thread. + */ + + if (xfrinfo->wdhwait) + { + /* Wake up the waiting thread */ + + lpc17_givesem(&ed->wdhsem); + xfrinfo->wdhwait = false; + + /* And free the transfer structure */ + + lpc17_free_xfrinfo(xfrinfo); + ed->xfrinfo = NULL; + } + else + { + /* Otherwise, perform the callback and free the transfer structure */ + + lpc17_asynch_completion(priv, ed); + } } + else + { + /* Just free the transfer structure */ - ret = xfrinfo->wdhwait ? -EINVAL : OK; - - /* Free the transfer structure */ - - lpc17_free_xfrinfo(xfrinfo); - ed->xfrinfo = NULL; + lpc17_free_xfrinfo(xfrinfo); + ed->xfrinfo = NULL; + } } /* Determine the return value */ irqrestore(flags); - return ret; + return OK; } #endif /* CONFIG_USBHOST_ASYNCH */ diff --git a/arch/arm/src/sama5/sam_ohci.c b/arch/arm/src/sama5/sam_ohci.c index e594c22d20a..d163ba0b209 100644 --- a/arch/arm/src/sama5/sam_ohci.c +++ b/arch/arm/src/sama5/sam_ohci.c @@ -1914,7 +1914,7 @@ static int sam_ctrltd(struct sam_rhport_s *rhport, struct sam_eplist_s *eplist, { usbhost_trace2(OHCI_TRACE2_BADTDSTATUS, RHPORT(rhport), edctrl->tdstatus); - ret = -EIO; + ret = edctrl->tdstatus == TD_CC_STALL ? -EPERM : -EIO; } } @@ -3389,7 +3389,20 @@ static ssize_t sam_transfer(struct usbhost_driver_s *drvr, usbhost_ep_t ep, /* A transfer error occurred */ usbhost_trace2(OHCI_TRACE2_BADTDSTATUS, RHPORT(rhport), ed->tdstatus); - ret = ed->tdstatus == TD_CC_STALL ? -EPERM : -EIO; + switch (ed->tdstatus) + { + case TD_CC_STALL: + ret = -EPERM; + break; + + case TD_CC_USER: + ret = -ESHUTDOWN; + break; + + default: + ret = -EIO; + break; + } errout: /* Make sure that there is no outstanding request on this endpoint */ @@ -3455,8 +3468,26 @@ static void sam_asynch_completion(struct sam_eplist_s *eplist) } else { + /* Map the bad completion status to something that a class driver + * might understand. + */ + usbhost_trace1(OHCI_TRACE1_BADTDSTATUS, ed->tdstatus); - nbytes = (ed->tdstatus == TD_CC_STALL) ? -EPERM : -EIO; + + switch (ed->tdstatus) + { + case TD_CC_STALL: + nbytes = -EPERM; + break; + + case TD_CC_USER: + nbytes = -ESHUTDOWN; + break; + + default: + nbytes = -EIO; + break; + } } /* Extract the callback information before freeing the buffer */ @@ -3605,18 +3636,11 @@ static int sam_cancel(struct usbhost_driver_s *drvr, usbhost_ep_t ep) flags = irqsave(); - /* It might be possible for no transfer to be in progress (callback == NULL), - * but it would be an usage error to use the interface to try to cancel a - * synchronous transfer (wdhwait == true). + /* It might be possible for no transfer to be in progress (callback == NULL + * and wdhwait == false) */ - DEBUGASSERT(eplist->wdhwait == false); - if (eplist->wdhwait) - { - return -EINVAL; - } - - if (eplist->callback) + if (eplist->callback || eplist->wdhwait) { /* We really need some kind of atomic test and set to do this right */ @@ -3636,6 +3660,24 @@ static int sam_cancel(struct usbhost_driver_s *drvr, usbhost_ep_t ep) sam_tdfree(td); td = next; } + + ed->tdstatus = TD_CC_USER; + + /* If there is a thread waiting for the transfer to complete, then + * wake up the thread. + */ + + if (eplist->wdhwait) + { + sam_givesem(&eplist->wdhsem); + eplist->wdhwait = false; + } + else + { + /* Otherwise, perform the callback */ + + sam_asynch_completion(eplist); + } } /* Reset any pending activity indications */ diff --git a/include/nuttx/usb/ohci.h b/include/nuttx/usb/ohci.h index c3250bd2c54..a548bdd4742 100644 --- a/include/nuttx/usb/ohci.h +++ b/include/nuttx/usb/ohci.h @@ -344,6 +344,8 @@ #define TD_CC_BUFFERUNDERRUN 0x0d #define TD_CC_NOTACCESSED 0x0f +#define TD_CC_USER 0x10 /* For use by OHCI drivers */ + /* Host Controller Communications Area Format (4.4.1) ***********************/ /* HccaInterrruptTable: 32x32-bit pointers to interrupt EDs */