diff --git a/makefiles/pseudomodules.inc.mk b/makefiles/pseudomodules.inc.mk index b7e89b064679..10729a50928f 100644 --- a/makefiles/pseudomodules.inc.mk +++ b/makefiles/pseudomodules.inc.mk @@ -510,6 +510,7 @@ PSEUDOMODULES += suit_storage_% PSEUDOMODULES += sys_bus_% PSEUDOMODULES += tiny_strerror_as_strerror PSEUDOMODULES += tiny_strerror_minimal +PSEUDOMODULES += usbus_urb PSEUDOMODULES += vdd_lc_filter_% ## @defgroup pseudomodule_vfs_auto_format vfs_auto_format ## @brief Format mount points at startup unless they can be mounted diff --git a/sys/Makefile.dep b/sys/Makefile.dep index 0928893a5c63..1a781b7eba0a 100644 --- a/sys/Makefile.dep +++ b/sys/Makefile.dep @@ -831,6 +831,7 @@ ifneq (,$(filter usbus_cdc_ecm,$(USEMODULE))) USEMODULE += iolist USEMODULE += fmt USEMODULE += usbus + USEMODULE += usbus_urb USEMODULE += netdev_eth USEMODULE += luid endif diff --git a/sys/include/usb/usbus.h b/sys/include/usb/usbus.h index 51b2043221d5..a5d4d80ac933 100644 --- a/sys/include/usb/usbus.h +++ b/sys/include/usb/usbus.h @@ -133,6 +133,31 @@ extern "C" { #define USBUS_HANDLER_FLAG_TR_STALL (0x0020) /**< Report transfer stall complete */ /** @} */ +/** + * @name USBUS URB flags + * + * @{ + */ + +/** + * @brief End the URB with a zero length packet if the URB is a full number of + * USB transfers in length + */ +#define USBUS_URB_FLAG_AUTO_ZLP (0x0001) + +/** + * @brief URB needs a zero length packet and it is requested + * @internal + */ +#define USBUS_URB_FLAG_NEEDS_ZLP (0x1000) + +/** + * @brief URB must be cancelled after the next finished xmit + * @internal + */ +#define USBUS_URB_FLAG_CANCELLED (0x2000) +/** @} */ + /** * @brief USB handler events */ @@ -285,12 +310,26 @@ typedef struct usbus_endpoint { usbus_descr_gen_t *descr_gen; /**< Linked list of optional additional descriptor generators */ usbdev_ep_t *ep; /**< ptr to the matching usbdev endpoint */ +#ifdef MODULE_USBUS_URB + clist_node_t urb_list; /**< clist of urbs */ +#endif uint16_t maxpacketsize; /**< Max packet size of this endpoint */ uint8_t interval; /**< Poll interval for interrupt endpoints */ bool active; /**< If the endpoint should be activated after reset */ } usbus_endpoint_t; +/** + * @brief USBUS USB request/response block + */ +typedef struct usbus_urb { + clist_node_t list; /**< clist block in the queue */ + uint32_t flags; /**< Transfer flags */ + uint8_t *buf; /**< Pointer to the (aligned) buffer */ + size_t len; /**< Length of the data */ + size_t transferred; /**< amount transferred, only valid for OUT */ +} usbus_urb_t; + /** * @brief USBUS interface alternative setting * @@ -552,6 +591,65 @@ void usbus_init(usbus_t *usbus, usbdev_t *usbdev); void usbus_create(char *stack, int stacksize, char priority, const char *name, usbus_t *usbus); +/** + * @brief Initialize a new URB. + * + * Must be called before submitting an URB to an endpoint. + * + * @note When using this for OUT endpoints, the buffer size and the @p len + * argument must allow for a whole number of max length transfers. + * + * @note Requires the `usbus_urb` module. + * + * @param[in] urb URB to submit + * @param[in] buf Buffer to store or transmit the data from + * @param[in] len Length of @p buf in bytes + * @param[in] flags Flags to set for the URB such as @ref USBUS_URB_FLAG_AUTO_ZLP + */ +static inline void usbus_urb_init(usbus_urb_t *urb, + uint8_t *buf, + size_t len, + uint32_t flags) +{ + urb->buf = buf; + urb->len = len; + urb->flags = flags; + urb->transferred = 0; +} + +/** + * @brief Submit an URB to an endpoint. + * + * @note Requires the `usbus_urb` module. + * + * @param[in] usbus USBUS context + * @param[in] endpoint USBUS endpoint the URB is queued to + * @param[in] urb URB to submit + */ +void usbus_urb_submit(usbus_t *usbus, usbus_endpoint_t *endpoint, usbus_urb_t *urb); + +/** + * @brief Cancel and already queued URB. + * + * The URB will be cancelled after the next transmission if the URB is already + * partially completed. It is up to the handler code to gracefully handle the + * partially aborted transfer on the endpoint pipe. + * + * The callback will be called if the URB was already started and cancelled + * while (partially) transferred + * + * @note Requires the `usbus_urb` module. + * + * @param[in] usbus USBUS context + * @param[in] endpoint USBUS endpoint the URB is queued to + * @param[in] urb URB to cancel + * + * @returns 0 if the URB is partially completed + * 1 if the URB was not yet started + * -1 if the URB was not found in the endpoint queue + */ +int usbus_urb_cancel(usbus_t *usbus, usbus_endpoint_t *endpoint, usbus_urb_t *urb); + /** * @brief Enable an endpoint * @@ -614,6 +712,44 @@ static inline bool usbus_handler_isset_flag(usbus_handler_t *handler, return handler->flags & flag; } +/** + * @brief enable an URB flag + * + * @param[in] urb URB to enable the flag for + * @param[in] flag flag to enable + */ +static inline void usbus_urb_set_flag(usbus_urb_t *urb, + uint32_t flag) +{ + urb->flags |= flag; +} + +/** + * @brief disable an URB flag + * + * @param[in] urb URB to disable the flag for + * @param[in] flag flag to disable + */ +static inline void usbus_urb_remove_flag(usbus_urb_t *urb, + uint32_t flag) +{ + urb->flags &= ~flag; +} + +/** + * @brief check if an URB flag is set + * + * @param[in] urb URB to check for flag + * @param[in] flag flag to check + * + * @return true if the flag is set for this URB + */ +static inline bool usbus_urb_isset_flag(usbus_urb_t *urb, + uint32_t flag) +{ + return urb->flags & flag; +} + #ifdef __cplusplus } #endif diff --git a/sys/include/usb/usbus/cdc/ecm.h b/sys/include/usb/usbus/cdc/ecm.h index f117a32271d0..7ceb9293972c 100644 --- a/sys/include/usb/usbus/cdc/ecm.h +++ b/sys/include/usb/usbus/cdc/ecm.h @@ -29,6 +29,7 @@ #include "usb/descriptor.h" #include "usb/usbus.h" #include "usb/usbus/control.h" +#include "macros/math.h" #include "net/netdev.h" #include "mutex.h" @@ -77,6 +78,11 @@ extern "C" { */ #define USBUS_CDCECM_EP_DATA_SIZE 64 +/** + * @brief Full ethernet frame rounded up to a whole number of transfers + */ +#define USBUS_ETHERNET_FRAME_BUF MATH_ALIGN(ETHERNET_FRAME_LEN, USBUS_CDCECM_EP_DATA_SIZE) + /** * @brief notification state, used to track which information must be send to * the host @@ -108,14 +114,13 @@ typedef struct usbus_cdcecm_device { usbus_t *usbus; /**< Ptr to the USBUS context */ mutex_t out_lock; /**< mutex used for locking netif/USBUS send */ size_t tx_len; /**< Length of the current tx frame */ - size_t len; /**< Length of the current rx frame */ usbus_cdcecm_notif_t notif; /**< Startup message notification tracker */ unsigned active_iface; /**< Current active data interface */ /** * @brief Buffer for received frames from the host */ - usbdev_ep_buf_t data_out[ETHERNET_FRAME_LEN]; + usbdev_ep_buf_t data_out[USBUS_ETHERNET_FRAME_BUF]; /** * @brief Host in device out data buffer @@ -126,6 +131,10 @@ typedef struct usbus_cdcecm_device { * @brief Host out device in control buffer */ usbdev_ep_buf_t control_in[USBUS_CDCECM_EP_CTRL_SIZE]; + /** + * @brief Host out device in reception URB + */ + usbus_urb_t out_urb; } usbus_cdcecm_device_t; /** diff --git a/sys/usb/usbus/cdc/ecm/cdc_ecm.c b/sys/usb/usbus/cdc/ecm/cdc_ecm.c index d9504ba27a31..e9374add8017 100644 --- a/sys/usb/usbus/cdc/ecm/cdc_ecm.c +++ b/sys/usb/usbus/cdc/ecm/cdc_ecm.c @@ -162,6 +162,14 @@ static void _fill_ethernet(usbus_cdcecm_device_t *cdcecm) } +void _start_urb(usbus_cdcecm_device_t *cdcecm) +{ + usbus_urb_init(&cdcecm->out_urb, + cdcecm->data_out, + USBUS_ETHERNET_FRAME_BUF, 0); + usbus_urb_submit(cdcecm->usbus, cdcecm->ep_out, &cdcecm->out_urb); +} + void usbus_cdcecm_init(usbus_t *usbus, usbus_cdcecm_device_t *handler) { assert(usbus); @@ -251,9 +259,9 @@ static int _control_handler(usbus_t *usbus, usbus_handler_t *handler, setup->value); cdcecm->active_iface = (uint8_t)setup->value; if (cdcecm->active_iface == 1) { - usbdev_ep_xmit(cdcecm->ep_out->ep, cdcecm->data_out, - USBUS_CDCECM_EP_DATA_SIZE); _notify_link_up(cdcecm); + /* Start URB */ + _start_urb(cdcecm); } break; @@ -298,8 +306,8 @@ static void _handle_rx_flush_ev(event_t *ev) { usbus_cdcecm_device_t *cdcecm = container_of(ev, usbus_cdcecm_device_t, rx_flush); - cdcecm->len = 0; /* Flush packet */ - usbdev_ep_xmit(cdcecm->ep_out->ep, cdcecm->data_out, USBUS_CDCECM_EP_DATA_SIZE); + /* Start URB */ + _start_urb(cdcecm); } static void _transfer_handler(usbus_t *usbus, usbus_handler_t *handler, @@ -310,20 +318,7 @@ static void _transfer_handler(usbus_t *usbus, usbus_handler_t *handler, usbus_cdcecm_device_t *cdcecm = (usbus_cdcecm_device_t *)handler; if (ep == cdcecm->ep_out->ep) { /* Retrieve incoming data */ - if (cdcecm->notif == USBUS_CDCECM_NOTIF_NONE) { - _notify_link_up(cdcecm); - } - size_t len = 0; - usbdev_ep_get(ep, USBOPT_EP_AVAILABLE, &len, sizeof(size_t)); - cdcecm->len += len; - if (len == USBUS_CDCECM_EP_DATA_SIZE) { - /* ready next chunk */ - usbdev_ep_xmit(ep, cdcecm->data_out + cdcecm->len, - USBUS_CDCECM_EP_DATA_SIZE); - } - else { - netdev_trigger_event_isr(&cdcecm->netdev); - } + netdev_trigger_event_isr(&cdcecm->netdev); } else if (ep == cdcecm->ep_in->ep) { _handle_in_complete(usbus, handler); @@ -341,7 +336,6 @@ static void _handle_reset(usbus_t *usbus, usbus_handler_t *handler) DEBUG("CDC ECM: Reset\n"); _handle_in_complete(usbus, handler); cdcecm->notif = USBUS_CDCECM_NOTIF_NONE; - cdcecm->len = 0; /* Flush received data */ cdcecm->active_iface = 0; mutex_unlock(&cdcecm->out_lock); } diff --git a/sys/usb/usbus/cdc/ecm/cdc_ecm_netdev.c b/sys/usb/usbus/cdc/ecm/cdc_ecm_netdev.c index 906ab9f2d034..5cdfdc2c40c2 100644 --- a/sys/usb/usbus/cdc/ecm/cdc_ecm_netdev.c +++ b/sys/usb/usbus/cdc/ecm/cdc_ecm_netdev.c @@ -132,7 +132,7 @@ static int _recv(netdev_t *netdev, void *buf, size_t max_len, void *info) (void)info; usbus_cdcecm_device_t *cdcecm = _netdev_to_cdcecm(netdev); - size_t pktlen = cdcecm->len; + size_t pktlen = cdcecm->out_urb.transferred; if (max_len == 0 && buf == NULL) { return pktlen; @@ -198,7 +198,7 @@ static void _isr(netdev_t *dev) { usbus_cdcecm_device_t *cdcecm = _netdev_to_cdcecm(dev); - if (cdcecm->len) { + if (cdcecm->out_urb.transferred) { cdcecm->netdev.event_callback(&cdcecm->netdev, NETDEV_EVENT_RX_COMPLETE); } diff --git a/sys/usb/usbus/usbus.c b/sys/usb/usbus/usbus.c index a58453aab2db..6e2800f82653 100644 --- a/sys/usb/usbus/usbus.c +++ b/sys/usb/usbus/usbus.c @@ -108,6 +108,12 @@ static usbus_handler_t *_ep_to_handler(usbus_t *usbus, usbdev_ep_t *ep) return NULL; } +static inline usbus_endpoint_t *_usbus_ep_from_usbdev(usbus_t *usbus, usbdev_ep_t* ep) +{ + return ep->dir == USB_EP_DIR_IN ? &usbus->ep_in[ep->num] + : &usbus->ep_out[ep->num]; +} + uint16_t usbus_add_interface(usbus_t *usbus, usbus_interface_t *iface) { /* While it is possible to us clist.h here, this results in less flash @@ -164,8 +170,7 @@ usbus_endpoint_t *usbus_add_endpoint(usbus_t *usbus, usbus_interface_t *iface, usbdev_ep_t *usbdev_ep = usbdev_new_ep(usbus->dev, type, dir, len); if (usbdev_ep) { - ep = dir == USB_EP_DIR_IN ? &usbus->ep_in[usbdev_ep->num] - : &usbus->ep_out[usbdev_ep->num]; + ep = _usbus_ep_from_usbdev(usbus, usbdev_ep); ep->maxpacketsize = len; ep->ep = usbdev_ep; if (iface) { @@ -217,6 +222,121 @@ static void _usbus_init_handlers(usbus_t *usbus) } } +#ifdef MODULE_USBUS_URB +static void _usbus_transfer_urb_submit(usbus_endpoint_t *usbus_ep, + usbus_urb_t *urb) +{ + /* Maximum between the urb length and the endpoint maximum size */ + size_t len = urb->len > usbus_ep->maxpacketsize ? + usbus_ep->maxpacketsize : + urb->len; + usbdev_ep_xmit(usbus_ep->ep, urb->buf, len); + urb->buf += len; + urb->len -= len; +} + +void usbus_urb_submit(usbus_t *usbus, usbus_endpoint_t *endpoint, usbus_urb_t *urb) +{ + (void)usbus; + assert(!(clist_find(&endpoint->urb_list, &urb->list))); + if (endpoint->ep->dir == USB_EP_DIR_IN && + ((urb->len % endpoint->maxpacketsize) == 0) && + usbus_urb_isset_flag(urb, USBUS_URB_FLAG_AUTO_ZLP)) { + /* If it is an IN endpoint, the urb length is a whole number of + * transfers and the ZLP is requested, then set flag that it needs the + * ZLP + */ + urb->flags |= USBUS_URB_FLAG_NEEDS_ZLP; + } + clist_rpush(&endpoint->urb_list, &urb->list); + /* Initiate transfer immediately if the list is empty */ + if (clist_exactly_one(&endpoint->urb_list)) { + _usbus_transfer_urb_submit(endpoint, urb); + } +} + +int usbus_urb_cancel(usbus_t *usbus, usbus_endpoint_t *endpoint, usbus_urb_t *urb) +{ + (void)usbus; + usbus_urb_t *active_urb = (usbus_urb_t*)clist_lpeek(&endpoint->urb_list); + if (active_urb == urb) { + usbus_urb_set_flag(active_urb, USBUS_URB_FLAG_CANCELLED); + usbus_urb_remove_flag(active_urb, USBUS_URB_FLAG_NEEDS_ZLP); + return 0; + } + if (clist_remove(&endpoint->urb_list, &urb->list)) { + return 1; + } + return -1; /* URB not found */ +} + +static bool _urb_transfer_complete(usbus_t *usbus, usbdev_ep_t *ep, + usbus_handler_t *handler) +{ + usbus_endpoint_t *usbus_ep = _usbus_ep_from_usbdev(usbus, ep); + if (clist_is_empty(&usbus_ep->urb_list)) { + return false; + } + + /* Ongoing transfer */ + usbus_urb_t *active_urb = (usbus_urb_t*)clist_lpeek(&usbus_ep->urb_list); + + size_t len = usbus_ep->maxpacketsize; + if (ep->dir == USB_EP_DIR_OUT) { + usbdev_ep_get(ep, USBOPT_EP_AVAILABLE, &len, sizeof(size_t)); + active_urb->transferred += len; + } + + if ((active_urb->len == 0) || (len < usbus_ep->maxpacketsize) || + (usbus_urb_isset_flag(active_urb, USBUS_URB_FLAG_CANCELLED))) { + /* Only set for IN endpoints */ + if (usbus_urb_isset_flag(active_urb, USBUS_URB_FLAG_NEEDS_ZLP)) { + usbus_urb_remove_flag(active_urb, USBUS_URB_FLAG_NEEDS_ZLP); + _usbus_transfer_urb_submit(usbus_ep, active_urb); + } + else { + /* transfer of URB complete */ + clist_lpop(&usbus_ep->urb_list); + + /* Schedule next URB first, then notify the handler */ + usbus_urb_t *next_urb = (usbus_urb_t*)clist_lpeek(&usbus_ep->urb_list); + if (next_urb) { + _usbus_transfer_urb_submit(usbus_ep, next_urb); + } + + DEBUG("Done with the transfer, available: %u, len: %u\n", + (unsigned)active_urb->transferred, (unsigned)active_urb->len); + handler->driver->transfer_handler(usbus, handler, ep, + USBUS_EVENT_TRANSFER_COMPLETE); + } + } + else { + _usbus_transfer_urb_submit(usbus_ep, active_urb); + } + + return true; +} +#else +static bool _urb_transfer_complete(usbus_t *usbus, usbdev_ep_t *ep, + usbus_handler_t *handler) +{ + (void)usbus; + (void)ep; + (void)handler; + return false; +} +#endif /* MODULE_USBUS_URB */ + +static void _usbus_transfer_complete(usbus_t *usbus, usbdev_ep_t *ep, usbus_handler_t *handler) +{ + if (_urb_transfer_complete(usbus, ep, handler)) { + return; + } + + /* Raw usbdev transfers by the handler */ + handler->driver->transfer_handler(usbus, handler, ep, USBUS_EVENT_TRANSFER_COMPLETE); +} + static void *_usbus_thread(void *args) { usbus_t *usbus = (usbus_t *)args; @@ -361,8 +481,7 @@ static void _event_ep_cb(usbdev_ep_t *ep, usbdev_event_t event) if (handler) { switch (event) { case USBDEV_EVENT_TR_COMPLETE: - handler->driver->transfer_handler(usbus, handler, ep, - USBUS_EVENT_TRANSFER_COMPLETE); + _usbus_transfer_complete(usbus, ep, handler); break; case USBDEV_EVENT_TR_FAIL: if (usbus_handler_isset_flag(handler,