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:
parent
d4422a074a
commit
7bfdd73818
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user