1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00

usbus: Add URB support

This commit adds support for URBs (USB Request/Response Blocks). These
allow for submitting multi-transfer sized buffers with USBUS handling
the individual usbdev xmits. Multiple URBs can be queued at once for a
single endpoint and USBUS will handle them in the order of submission.

OUT endpoint URBs must always consist of a whole number of full-sized
transfers (N x MaxEndpointSize). They will automatically finish after
the endpoint received a transfer less than the endpoint size.

IN endpoints can be arbitrary-sized and do not have to consist of a
whole number of full-sized transmissions. They support a flag to
indicate that the last transfer in the sequence must be less than a full
sized transfer (USBUS_URB_FLAG_AUTO_ZLP) and this adds a zero length
transfer at the end of the transmissions if the last transfer was equal
to the maximum transfer size.

URBs can be cancelled, but if the URB is already being processed it will
be cancelled after the current transmission within the URB is finished.
If it is still in the queue it will immediately be removed from the
queue.
This commit is contained in:
Koen Zandberg 2021-10-31 21:31:32 +01:00
parent d4422a074a
commit 7bfdd73818
No known key found for this signature in database
GPG Key ID: BA1718B37D79F51C
3 changed files with 260 additions and 4 deletions

View File

@ -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

View File

@ -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

View File

@ -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,