Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files. =================================================================== RCS file: /ftp/cvs/cvsroot/src/sys/dev/usb/usbdi.c,v rcsdiff: /ftp/cvs/cvsroot/src/sys/dev/usb/usbdi.c,v: warning: Unknown phrases like `commitid ...;' are present. retrieving revision 1.186.2.1 retrieving revision 1.187 diff -u -p -r1.186.2.1 -r1.187 --- src/sys/dev/usb/usbdi.c 2020/02/29 20:19:17 1.186.2.1 +++ src/sys/dev/usb/usbdi.c 2020/02/08 07:38:17 1.187 @@ -1,4 +1,4 @@ -/* $NetBSD: usbdi.c,v 1.186.2.1 2020/02/29 20:19:17 ad Exp $ */ +/* $NetBSD: usbdi.c,v 1.187 2020/02/08 07:38:17 maxv Exp $ */ /* * Copyright (c) 1998, 2012, 2015 The NetBSD Foundation, Inc. @@ -32,7 +32,7 @@ */ #include -__KERNEL_RCSID(0, "$NetBSD: usbdi.c,v 1.186.2.1 2020/02/29 20:19:17 ad Exp $"); +__KERNEL_RCSID(0, "$NetBSD: usbdi.c,v 1.187 2020/02/08 07:38:17 maxv Exp $"); #ifdef _KERNEL_OPT #include "opt_usb.h" @@ -55,7 +55,6 @@ __KERNEL_RCSID(0, "$NetBSD: usbdi.c,v 1. #include #include #include -#include #include /* UTF-8 encoding stuff */ @@ -63,58 +62,6 @@ __KERNEL_RCSID(0, "$NetBSD: usbdi.c,v 1. extern int usbdebug; -SDT_PROBE_DEFINE5(usb, device, pipe, open, - "struct usbd_interface *"/*iface*/, - "uint8_t"/*address*/, - "uint8_t"/*flags*/, - "int"/*ival*/, - "struct usbd_pipe *"/*pipe*/); - -SDT_PROBE_DEFINE7(usb, device, pipe, open__intr, - "struct usbd_interface *"/*iface*/, - "uint8_t"/*address*/, - "uint8_t"/*flags*/, - "int"/*ival*/, - "usbd_callback"/*cb*/, - "void *"/*cookie*/, - "struct usbd_pipe *"/*pipe*/); - -SDT_PROBE_DEFINE2(usb, device, pipe, transfer__start, - "struct usbd_pipe *"/*pipe*/, - "struct usbd_xfer *"/*xfer*/); -SDT_PROBE_DEFINE3(usb, device, pipe, transfer__done, - "struct usbd_pipe *"/*pipe*/, - "struct usbd_xfer *"/*xfer*/, - "usbd_status"/*err*/); -SDT_PROBE_DEFINE2(usb, device, pipe, start, - "struct usbd_pipe *"/*pipe*/, - "struct usbd_xfer *"/*xfer*/); - -SDT_PROBE_DEFINE1(usb, device, pipe, close, "struct usbd_pipe *"/*pipe*/); -SDT_PROBE_DEFINE1(usb, device, pipe, abort__start, - "struct usbd_pipe *"/*pipe*/); -SDT_PROBE_DEFINE1(usb, device, pipe, abort__done, - "struct usbd_pipe *"/*pipe*/); -SDT_PROBE_DEFINE1(usb, device, pipe, clear__endpoint__stall, - "struct usbd_pipe *"/*pipe*/); -SDT_PROBE_DEFINE1(usb, device, pipe, clear__endpoint__toggle, - "struct usbd_pipe *"/*pipe*/); - -SDT_PROBE_DEFINE5(usb, device, xfer, create, - "struct usbd_xfer *"/*xfer*/, - "struct usbd_pipe *"/*pipe*/, - "size_t"/*len*/, - "unsigned int"/*flags*/, - "unsigned int"/*nframes*/); -SDT_PROBE_DEFINE1(usb, device, xfer, start, "struct usbd_xfer *"/*xfer*/); -SDT_PROBE_DEFINE1(usb, device, xfer, preabort, "struct usbd_xfer *"/*xfer*/); -SDT_PROBE_DEFINE1(usb, device, xfer, abort, "struct usbd_xfer *"/*xfer*/); -SDT_PROBE_DEFINE1(usb, device, xfer, timeout, "struct usbd_xfer *"/*xfer*/); -SDT_PROBE_DEFINE2(usb, device, xfer, done, - "struct usbd_xfer *"/*xfer*/, - "usbd_status"/*status*/); -SDT_PROBE_DEFINE1(usb, device, xfer, destroy, "struct usbd_xfer *"/*xfer*/); - Static usbd_status usbd_ar_pipe(struct usbd_pipe *); Static void usbd_start_next(struct usbd_pipe *); Static usbd_status usbd_open_pipe_ival @@ -124,10 +71,6 @@ static void usbd_free_buffer(struct usbd static struct usbd_xfer *usbd_alloc_xfer(struct usbd_device *, unsigned int); static usbd_status usbd_free_xfer(struct usbd_xfer *); static void usbd_request_async_cb(struct usbd_xfer *, void *, usbd_status); -static void usbd_xfer_timeout(void *); -static void usbd_xfer_timeout_task(void *); -static bool usbd_xfer_probe_timeout(struct usbd_xfer *); -static void usbd_xfer_cancel_timeout_async(struct usbd_xfer *); #if defined(USB_DEBUG) void @@ -248,8 +191,6 @@ usbd_open_pipe_ival(struct usbd_interfac return err; LIST_INSERT_HEAD(&iface->ui_pipes, p, up_next); *pipe = p; - SDT_PROBE5(usb, device, pipe, open, - iface, address, flags, ival, p); return USBD_NORMAL_COMPLETION; } @@ -283,8 +224,6 @@ usbd_open_pipe_intr(struct usbd_interfac *pipe = ipipe; if (err != USBD_IN_PROGRESS) goto bad3; - SDT_PROBE7(usb, device, pipe, open__intr, - iface, address, flags, ival, cb, priv, ipipe); return USBD_NORMAL_COMPLETION; bad3: @@ -305,7 +244,6 @@ usbd_close_pipe(struct usbd_pipe *pipe) KASSERT(pipe != NULL); usbd_lock_pipe(pipe); - SDT_PROBE1(usb, device, pipe, close, pipe); if (!SIMPLEQ_EMPTY(&pipe->up_queue)) { printf("WARNING: pipe closed with active xfers on addr %d\n", @@ -343,7 +281,6 @@ usbd_transfer(struct usbd_xfer *xfer) "xfer = %#jx, flags = %#jx, pipe = %#jx, running = %jd", (uintptr_t)xfer, xfer->ux_flags, (uintptr_t)pipe, pipe->up_running); KASSERT(xfer->ux_status == USBD_NOT_STARTED); - SDT_PROBE1(usb, device, xfer, start, xfer); #ifdef USB_DEBUG if (usbdebug > 5) @@ -354,7 +291,6 @@ usbd_transfer(struct usbd_xfer *xfer) if (pipe->up_aborting) { USBHIST_LOG(usbdebug, "<- done xfer %#jx, aborting", (uintptr_t)xfer, 0, 0, 0); - SDT_PROBE2(usb, device, xfer, done, xfer, USBD_CANCELLED); return USBD_CANCELLED; } @@ -386,9 +322,7 @@ usbd_transfer(struct usbd_xfer *xfer) } /* xfer is not valid after the transfer method unless synchronous */ - SDT_PROBE2(usb, device, pipe, transfer__start, pipe, xfer); err = pipe->up_methods->upm_transfer(xfer); - SDT_PROBE3(usb, device, pipe, transfer__done, pipe, xfer, err); if (err != USBD_IN_PROGRESS && err) { /* @@ -399,7 +333,6 @@ usbd_transfer(struct usbd_xfer *xfer) USBHIST_LOG(usbdebug, "xfer failed: %s, reinserting", err, 0, 0, 0); usbd_lock_pipe(pipe); - SDT_PROBE1(usb, device, xfer, preabort, xfer); SIMPLEQ_REMOVE_HEAD(&pipe->up_queue, ux_next); if (pipe->up_serialise) usbd_start_next(pipe); @@ -409,15 +342,12 @@ usbd_transfer(struct usbd_xfer *xfer) if (!(flags & USBD_SYNCHRONOUS)) { USBHIST_LOG(usbdebug, "<- done xfer %#jx, not sync (err %jd)", (uintptr_t)xfer, err, 0, 0); - if (err != USBD_IN_PROGRESS) /* XXX Possible? */ - SDT_PROBE2(usb, device, xfer, done, xfer, err); return err; } if (err != USBD_IN_PROGRESS) { USBHIST_LOG(usbdebug, "<- done xfer %#jx, sync (err %jd)", (uintptr_t)xfer, err, 0, 0); - SDT_PROBE2(usb, device, xfer, done, xfer, err); return err; } @@ -436,15 +366,11 @@ usbd_transfer(struct usbd_xfer *xfer) cv_wait(&xfer->ux_cv, pipe->up_dev->ud_bus->ub_lock); } if (err) { - if (!xfer->ux_done) { - SDT_PROBE1(usb, device, xfer, abort, xfer); + if (!xfer->ux_done) pipe->up_methods->upm_abort(xfer); - } break; } } - SDT_PROBE2(usb, device, xfer, done, xfer, xfer->ux_status); - /* XXX Race to read xfer->ux_status? */ usbd_unlock_pipe(pipe); return xfer->ux_status; } @@ -548,10 +474,7 @@ usbd_alloc_xfer(struct usbd_device *dev, goto out; xfer->ux_bus = dev->ud_bus; callout_init(&xfer->ux_callout, CALLOUT_MPSAFE); - callout_setfunc(&xfer->ux_callout, usbd_xfer_timeout, xfer); cv_init(&xfer->ux_cv, "usbxfer"); - usb_init_task(&xfer->ux_aborttask, usbd_xfer_timeout_task, xfer, - USB_TASKQ_MPSAFE); out: USBHIST_CALLARGS(usbdebug, "returns %#jx", (uintptr_t)xfer, 0, 0, 0); @@ -568,15 +491,12 @@ usbd_free_xfer(struct usbd_xfer *xfer) if (xfer->ux_buf) { usbd_free_buffer(xfer); } - - /* Wait for any straggling timeout to complete. */ - mutex_enter(xfer->ux_bus->ub_lock); - xfer->ux_timeout_reset = false; /* do not resuscitate */ - callout_halt(&xfer->ux_callout, xfer->ux_bus->ub_lock); - usb_rem_task_wait(xfer->ux_pipe->up_dev, &xfer->ux_aborttask, - USB_TASKQ_HC, xfer->ux_bus->ub_lock); - mutex_exit(xfer->ux_bus->ub_lock); - +#if defined(DIAGNOSTIC) + if (callout_pending(&xfer->ux_callout)) { + callout_stop(&xfer->ux_callout); + printf("usbd_free_xfer: timeout_handle pending\n"); + } +#endif cv_destroy(&xfer->ux_cv); xfer->ux_bus->ub_methods->ubm_freex(xfer->ux_bus, xfer); return USBD_NORMAL_COMPLETION; @@ -616,8 +536,6 @@ usbd_create_xfer(struct usbd_pipe *pipe, } *xp = xfer; - SDT_PROBE5(usb, device, xfer, create, - xfer, pipe, len, flags, nframes); return 0; } @@ -625,9 +543,9 @@ void usbd_destroy_xfer(struct usbd_xfer *xfer) { - SDT_PROBE1(usb, device, xfer, destroy, xfer); - if (xfer->ux_methods->upm_fini) + if (xfer->ux_methods->upm_fini) { xfer->ux_methods->upm_fini(xfer); + } usbd_free_xfer(xfer); } @@ -759,20 +677,23 @@ usbd_status usbd_clear_endpoint_stall(struct usbd_pipe *pipe) { struct usbd_device *dev = pipe->up_dev; + usb_device_request_t req; usbd_status err; USBHIST_FUNC(); USBHIST_CALLED(usbdebug); - SDT_PROBE1(usb, device, pipe, clear__endpoint__stall, pipe); /* * Clearing en endpoint stall resets the endpoint toggle, so * do the same to the HC toggle. */ - SDT_PROBE1(usb, device, pipe, clear__endpoint__toggle, pipe); pipe->up_methods->upm_cleartoggle(pipe); - err = usbd_clear_endpoint_feature(dev, - pipe->up_endpoint->ue_edesc->bEndpointAddress, UF_ENDPOINT_HALT); + req.bmRequestType = UT_WRITE_ENDPOINT; + req.bRequest = UR_CLEAR_FEATURE; + USETW(req.wValue, UF_ENDPOINT_HALT); + USETW(req.wIndex, pipe->up_endpoint->ue_edesc->bEndpointAddress); + USETW(req.wLength, 0); + err = usbd_do_request(dev, &req, 0); #if 0 XXX should we do this? if (!err) { @@ -788,13 +709,16 @@ usbd_clear_endpoint_stall_task(void *arg { struct usbd_pipe *pipe = arg; struct usbd_device *dev = pipe->up_dev; + usb_device_request_t req; - SDT_PROBE1(usb, device, pipe, clear__endpoint__stall, pipe); - SDT_PROBE1(usb, device, pipe, clear__endpoint__toggle, pipe); pipe->up_methods->upm_cleartoggle(pipe); - (void)usbd_clear_endpoint_feature(dev, - pipe->up_endpoint->ue_edesc->bEndpointAddress, UF_ENDPOINT_HALT); + req.bmRequestType = UT_WRITE_ENDPOINT; + req.bRequest = UR_CLEAR_FEATURE; + USETW(req.wValue, UF_ENDPOINT_HALT); + USETW(req.wIndex, pipe->up_endpoint->ue_edesc->bEndpointAddress); + USETW(req.wLength, 0); + (void)usbd_do_request(dev, &req, 0); } void @@ -807,7 +731,6 @@ void usbd_clear_endpoint_toggle(struct usbd_pipe *pipe) { - SDT_PROBE1(usb, device, pipe, clear__endpoint__toggle, pipe); pipe->up_methods->upm_cleartoggle(pipe); } @@ -945,7 +868,6 @@ usbd_ar_pipe(struct usbd_pipe *pipe) USBHIST_FUNC(); USBHIST_CALLARGS(usbdebug, "pipe = %#jx", (uintptr_t)pipe, 0, 0, 0); - SDT_PROBE1(usb, device, pipe, abort__start, pipe); KASSERT(mutex_owned(pipe->up_dev->ud_bus->ub_lock)); @@ -954,24 +876,20 @@ usbd_ar_pipe(struct usbd_pipe *pipe) usbd_dump_queue(pipe); #endif pipe->up_repeat = 0; - pipe->up_running = 0; pipe->up_aborting = 1; while ((xfer = SIMPLEQ_FIRST(&pipe->up_queue)) != NULL) { USBHIST_LOG(usbdebug, "pipe = %#jx xfer = %#jx " "(methods = %#jx)", (uintptr_t)pipe, (uintptr_t)xfer, (uintptr_t)pipe->up_methods, 0); if (xfer->ux_status == USBD_NOT_STARTED) { - SDT_PROBE1(usb, device, xfer, preabort, xfer); SIMPLEQ_REMOVE_HEAD(&pipe->up_queue, ux_next); } else { /* Make the HC abort it (and invoke the callback). */ - SDT_PROBE1(usb, device, xfer, abort, xfer); pipe->up_methods->upm_abort(xfer); /* XXX only for non-0 usbd_clear_endpoint_stall(pipe); */ } } pipe->up_aborting = 0; - SDT_PROBE1(usb, device, pipe, abort__done, pipe); return USBD_NORMAL_COMPLETION; } @@ -1050,7 +968,6 @@ usb_transfer_complete(struct usbd_xfer * USBHIST_LOG(usbdebug, "xfer %#jx doing done %#jx", (uintptr_t)xfer, (uintptr_t)pipe->up_methods->upm_done, 0, 0); - SDT_PROBE2(usb, device, xfer, done, xfer, xfer->ux_status); pipe->up_methods->upm_done(xfer); if (xfer->ux_length != 0 && xfer->ux_buffer != xfer->ux_buf) { @@ -1157,7 +1074,6 @@ usbd_start_next(struct usbd_pipe *pipe) } else { if (!polling) mutex_exit(pipe->up_dev->ud_bus->ub_lock); - SDT_PROBE2(usb, device, pipe, start, pipe, xfer); err = pipe->up_methods->upm_start(xfer); if (!polling) mutex_enter(pipe->up_dev->ud_bus->ub_lock); @@ -1355,6 +1271,93 @@ usb_match_device(const struct usb_devno return NULL; } + +void +usb_desc_iter_init(struct usbd_device *dev, usbd_desc_iter_t *iter) +{ + const usb_config_descriptor_t *cd = usbd_get_config_descriptor(dev); + + iter->cur = (const uByte *)cd; + iter->end = (const uByte *)cd + UGETW(cd->wTotalLength); +} + +const usb_descriptor_t * +usb_desc_iter_peek(usbd_desc_iter_t *iter) +{ + const usb_descriptor_t *desc; + + if (iter->cur + sizeof(usb_descriptor_t) >= iter->end) { + if (iter->cur != iter->end) + printf("usb_desc_iter_peek_next: bad descriptor\n"); + return NULL; + } + desc = (const usb_descriptor_t *)iter->cur; + if (desc->bLength == 0) { + printf("usb_desc_iter_peek_next: descriptor length = 0\n"); + return NULL; + } + if (iter->cur + desc->bLength > iter->end) { + printf("usb_desc_iter_peek_next: descriptor length too large\n"); + return NULL; + } + return desc; +} + +const usb_descriptor_t * +usb_desc_iter_next(usbd_desc_iter_t *iter) +{ + const usb_descriptor_t *desc; + + if (iter->cur + sizeof(usb_descriptor_t) >= iter->end) { + if (iter->cur != iter->end) + printf("usb_desc_iter_next: bad descriptor\n"); + return NULL; + } + desc = (const usb_descriptor_t *)iter->cur; + if (desc->bLength == 0) { + printf("usb_desc_iter_next: descriptor length = 0\n"); + return NULL; + } + iter->cur += desc->bLength; + if (iter->cur > iter->end) { + printf("usb_desc_iter_next: descriptor length too large\n"); + return NULL; + } + return desc; +} + +/* Return the next interface descriptor, skipping over any other + * descriptors. Returns NULL at the end or on error. */ +const usb_interface_descriptor_t * +usb_desc_iter_next_interface(usbd_desc_iter_t *iter) +{ + const usb_descriptor_t *desc; + + while ((desc = usb_desc_iter_peek(iter)) != NULL && + desc->bDescriptorType != UDESC_INTERFACE) + { + usb_desc_iter_next(iter); + } + + return (const usb_interface_descriptor_t *)usb_desc_iter_next(iter); +} + +/* Returns the next non-interface descriptor, returning NULL when the + * next descriptor would be an interface descriptor. */ +const usb_descriptor_t * +usb_desc_iter_next_non_interface(usbd_desc_iter_t *iter) +{ + const usb_descriptor_t *desc; + + if ((desc = usb_desc_iter_peek(iter)) != NULL && + desc->bDescriptorType != UDESC_INTERFACE) + { + return usb_desc_iter_next(iter); + } else { + return NULL; + } +} + usbd_status usbd_get_string(struct usbd_device *dev, int si, char *buf) { @@ -1419,381 +1422,3 @@ usbd_get_string0(struct usbd_device *dev #endif return USBD_NORMAL_COMPLETION; } - -/* - * usbd_xfer_trycomplete(xfer) - * - * Try to claim xfer for completion. Return true if successful, - * false if the xfer has been synchronously aborted or has timed - * out. - * - * If this returns true, caller is responsible for setting - * xfer->ux_status and calling usb_transfer_complete. To be used - * in a host controller interrupt handler. - * - * Caller must either hold the bus lock or have the bus in polling - * mode. - */ -bool -usbd_xfer_trycomplete(struct usbd_xfer *xfer) -{ - struct usbd_bus *bus __diagused = xfer->ux_bus; - - KASSERT(bus->ub_usepolling || mutex_owned(bus->ub_lock)); - - /* - * If software has completed it, either by synchronous abort or - * by timeout, too late. - */ - if (xfer->ux_status != USBD_IN_PROGRESS) - return false; - - /* - * We are completing the xfer. Cancel the timeout if we can, - * but only asynchronously. See usbd_xfer_cancel_timeout_async - * for why we need not wait for the callout or task here. - */ - usbd_xfer_cancel_timeout_async(xfer); - - /* Success! Note: Caller must set xfer->ux_status afterwar. */ - return true; -} - -/* - * usbd_xfer_abort(xfer) - * - * Try to claim xfer to abort. If successful, mark it completed - * with USBD_CANCELLED and call the bus-specific method to abort - * at the hardware level. - * - * To be called in thread context from struct - * usbd_pipe_methods::upm_abort. - * - * Caller must hold the bus lock. - */ -void -usbd_xfer_abort(struct usbd_xfer *xfer) -{ - struct usbd_bus *bus = xfer->ux_bus; - - KASSERT(mutex_owned(bus->ub_lock)); - - /* - * If host controller interrupt or timer interrupt has - * completed it, too late. But the xfer cannot be - * cancelled already -- only one caller can synchronously - * abort. - */ - KASSERT(xfer->ux_status != USBD_CANCELLED); - if (xfer->ux_status != USBD_IN_PROGRESS) - return; - - /* - * Cancel the timeout if we can, but only asynchronously; see - * usbd_xfer_cancel_timeout_async for why we need not wait for - * the callout or task here. - */ - usbd_xfer_cancel_timeout_async(xfer); - - /* - * We beat everyone else. Claim the status as cancelled and do - * the bus-specific dance to abort the hardware. - */ - xfer->ux_status = USBD_CANCELLED; - bus->ub_methods->ubm_abortx(xfer); -} - -/* - * usbd_xfer_timeout(xfer) - * - * Called at IPL_SOFTCLOCK when too much time has elapsed waiting - * for xfer to complete. Since we can't abort the xfer at - * IPL_SOFTCLOCK, defer to a usb_task to run it in thread context, - * unless the xfer has completed or aborted concurrently -- and if - * the xfer has also been resubmitted, take care of rescheduling - * the callout. - */ -static void -usbd_xfer_timeout(void *cookie) -{ - struct usbd_xfer *xfer = cookie; - struct usbd_bus *bus = xfer->ux_bus; - struct usbd_device *dev = xfer->ux_pipe->up_dev; - - /* Acquire the lock so we can transition the timeout state. */ - mutex_enter(bus->ub_lock); - - /* - * Use usbd_xfer_probe_timeout to check whether the timeout is - * still valid, or to reschedule the callout if necessary. If - * it is still valid, schedule the task. - */ - if (usbd_xfer_probe_timeout(xfer)) - usb_add_task(dev, &xfer->ux_aborttask, USB_TASKQ_HC); - - /* - * Notify usbd_xfer_cancel_timeout_async that we may have - * scheduled the task. This causes callout_invoking to return - * false in usbd_xfer_cancel_timeout_async so that it can tell - * which stage in the callout->task->abort process we're at. - */ - callout_ack(&xfer->ux_callout); - - /* All done -- release the lock. */ - mutex_exit(bus->ub_lock); -} - -/* - * usbd_xfer_timeout_task(xfer) - * - * Called in thread context when too much time has elapsed waiting - * for xfer to complete. Abort the xfer with USBD_TIMEOUT, unless - * it has completed or aborted concurrently -- and if the xfer has - * also been resubmitted, take care of rescheduling the callout. - */ -static void -usbd_xfer_timeout_task(void *cookie) -{ - struct usbd_xfer *xfer = cookie; - struct usbd_bus *bus = xfer->ux_bus; - - /* Acquire the lock so we can transition the timeout state. */ - mutex_enter(bus->ub_lock); - - /* - * Use usbd_xfer_probe_timeout to check whether the timeout is - * still valid, or to reschedule the callout if necessary. If - * it is not valid -- the timeout has been asynchronously - * cancelled, or the xfer has already been resubmitted -- then - * we're done here. - */ - if (!usbd_xfer_probe_timeout(xfer)) - goto out; - - /* - * May have completed or been aborted, but we're the only one - * who can time it out. If it has completed or been aborted, - * no need to timeout. - */ - KASSERT(xfer->ux_status != USBD_TIMEOUT); - if (xfer->ux_status != USBD_IN_PROGRESS) - goto out; - - /* - * We beat everyone else. Claim the status as timed out and do - * the bus-specific dance to abort the hardware. - */ - xfer->ux_status = USBD_TIMEOUT; - bus->ub_methods->ubm_abortx(xfer); - -out: /* All done -- release the lock. */ - mutex_exit(bus->ub_lock); -} - -/* - * usbd_xfer_probe_timeout(xfer) - * - * Probe the status of xfer's timeout. Acknowledge and process a - * request to reschedule. Return true if the timeout is still - * valid and the caller should take further action (queueing a - * task or aborting the xfer), false if it must stop here. - */ -static bool -usbd_xfer_probe_timeout(struct usbd_xfer *xfer) -{ - struct usbd_bus *bus = xfer->ux_bus; - bool valid; - - KASSERT(bus->ub_usepolling || mutex_owned(bus->ub_lock)); - - /* The timeout must be set. */ - KASSERT(xfer->ux_timeout_set); - - /* - * Neither callout nor task may be pending; they execute - * alternately in lock step. - */ - KASSERT(!callout_pending(&xfer->ux_callout)); - KASSERT(!usb_task_pending(xfer->ux_pipe->up_dev, &xfer->ux_aborttask)); - - /* There are a few cases... */ - if (bus->ub_methods->ubm_dying(bus)) { - /* Host controller dying. Drop it all on the floor. */ - xfer->ux_timeout_set = false; - xfer->ux_timeout_reset = false; - valid = false; - } else if (xfer->ux_timeout_reset) { - /* - * The xfer completed _and_ got resubmitted while we - * waited for the lock. Acknowledge the request to - * reschedule, and reschedule it if there is a timeout - * and the bus is not polling. - */ - xfer->ux_timeout_reset = false; - if (xfer->ux_timeout && !bus->ub_usepolling) { - KASSERT(xfer->ux_timeout_set); - callout_schedule(&xfer->ux_callout, - mstohz(xfer->ux_timeout)); - } else { - /* No more callout or task scheduled. */ - xfer->ux_timeout_set = false; - } - valid = false; - } else if (xfer->ux_status != USBD_IN_PROGRESS) { - /* - * The xfer has completed by hardware completion or by - * software abort, and has not been resubmitted, so the - * timeout must be unset, and is no longer valid for - * the caller. - */ - xfer->ux_timeout_set = false; - valid = false; - } else { - /* - * The xfer has not yet completed, so the timeout is - * valid. - */ - valid = true; - } - - /* Any reset must have been processed. */ - KASSERT(!xfer->ux_timeout_reset); - - /* - * Either we claim the timeout is set, or the callout is idle. - * If the timeout is still set, we may be handing off to the - * task instead, so this is an if but not an iff. - */ - KASSERT(xfer->ux_timeout_set || !callout_pending(&xfer->ux_callout)); - - /* - * The task must be idle now. - * - * - If the caller is the callout, _and_ the timeout is still - * valid, the caller will schedule it, but it hasn't been - * scheduled yet. (If the timeout is not valid, the task - * should not be scheduled.) - * - * - If the caller is the task, it cannot be scheduled again - * until the callout runs again, which won't happen until we - * next release the lock. - */ - KASSERT(!usb_task_pending(xfer->ux_pipe->up_dev, &xfer->ux_aborttask)); - - KASSERT(bus->ub_usepolling || mutex_owned(bus->ub_lock)); - - return valid; -} - -/* - * usbd_xfer_schedule_timeout(xfer) - * - * Ensure that xfer has a timeout. If the callout is already - * queued or the task is already running, request that they - * reschedule the callout. If not, and if we're not polling, - * schedule the callout anew. - * - * To be called in thread context from struct - * usbd_pipe_methods::upm_start. - */ -void -usbd_xfer_schedule_timeout(struct usbd_xfer *xfer) -{ - struct usbd_bus *bus = xfer->ux_bus; - - KASSERT(bus->ub_usepolling || mutex_owned(bus->ub_lock)); - - if (xfer->ux_timeout_set) { - /* - * Callout or task has fired from a prior completed - * xfer but has not yet noticed that the xfer is done. - * Ask it to reschedule itself to ux_timeout. - */ - xfer->ux_timeout_reset = true; - } else if (xfer->ux_timeout && !bus->ub_usepolling) { - /* Callout is not scheduled. Schedule it. */ - KASSERT(!callout_pending(&xfer->ux_callout)); - callout_schedule(&xfer->ux_callout, mstohz(xfer->ux_timeout)); - xfer->ux_timeout_set = true; - } - - KASSERT(bus->ub_usepolling || mutex_owned(bus->ub_lock)); -} - -/* - * usbd_xfer_cancel_timeout_async(xfer) - * - * Cancel the callout and the task of xfer, which have not yet run - * to completion, but don't wait for the callout or task to finish - * running. - * - * If they have already fired, at worst they are waiting for the - * bus lock. They will see that the xfer is no longer in progress - * and give up, or they will see that the xfer has been - * resubmitted with a new timeout and reschedule the callout. - * - * If a resubmitted request completed so fast that the callout - * didn't have time to process a timer reset, just cancel the - * timer reset. - */ -static void -usbd_xfer_cancel_timeout_async(struct usbd_xfer *xfer) -{ - struct usbd_bus *bus __diagused = xfer->ux_bus; - - KASSERT(bus->ub_usepolling || mutex_owned(bus->ub_lock)); - - /* - * If the timer wasn't running anyway, forget about it. This - * can happen if we are completing an isochronous transfer - * which doesn't use the same timeout logic. - */ - if (!xfer->ux_timeout_set) - return; - - xfer->ux_timeout_reset = false; - if (!callout_stop(&xfer->ux_callout)) { - /* - * We stopped the callout before it ran. The timeout - * is no longer set. - */ - xfer->ux_timeout_set = false; - } else if (callout_invoking(&xfer->ux_callout)) { - /* - * The callout has begun to run but it has not yet - * acquired the lock and called callout_ack. The task - * cannot be queued yet, and the callout cannot have - * been rescheduled yet. - * - * By the time the callout acquires the lock, we will - * have transitioned from USBD_IN_PROGRESS to a - * completed status, and possibly also resubmitted the - * xfer and set xfer->ux_timeout_reset = true. In both - * cases, the callout will DTRT, so no further action - * is needed here. - */ - } else if (usb_rem_task(xfer->ux_pipe->up_dev, &xfer->ux_aborttask)) { - /* - * The callout had fired and scheduled the task, but we - * stopped the task before it could run. The timeout - * is therefore no longer set -- the next resubmission - * of the xfer must schedule a new timeout. - * - * The callout should not be be pending at this point: - * it is scheduled only under the lock, and only when - * xfer->ux_timeout_set is false, or by the callout or - * task itself when xfer->ux_timeout_reset is true. - */ - xfer->ux_timeout_set = false; - } - - /* - * The callout cannot be scheduled and the task cannot be - * queued at this point. Either we cancelled them, or they are - * already running and waiting for the bus lock. - */ - KASSERT(!callout_pending(&xfer->ux_callout)); - KASSERT(!usb_task_pending(xfer->ux_pipe->up_dev, &xfer->ux_aborttask)); - - KASSERT(bus->ub_usepolling || mutex_owned(bus->ub_lock)); -}