935 lines
24 KiB
C
935 lines
24 KiB
C
/*
|
|
* This file is part of the unicore-mx project.
|
|
*
|
|
* Copyright (C) 2016 Kuldeep Singh Dhaka <kuldeepdhaka9@gmail.com>
|
|
*
|
|
* This library is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with this library. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include <unicore-mx/usbd/usbd.h>
|
|
#include "usbd_private.h"
|
|
|
|
/*
|
|
* The Transfer design is such that application code submit transfer.
|
|
* Transfer is encapsulated in to an URB.
|
|
* Based on the availibility of the endpoint, the URB is submitted to backend.
|
|
* If endpoint available:
|
|
* - endpoint is marked as not-available (anymore)
|
|
* - append to active list
|
|
* - submit to backend
|
|
* if endpoint not available:
|
|
* - append to waiting list
|
|
*
|
|
* Later, when the endpoint is freed
|
|
* (transfer succesfully finished, cancellled, timeout etc..)
|
|
* Waiting URB are schedule and as per availibility of endpoint they are submitted.
|
|
*
|
|
* At reset, all transfer are cancelled
|
|
* because the life time of the transfer has ended.
|
|
*
|
|
* At SET_CONFIGURATION, all non-EP0 transfer are cancelled
|
|
* because the life time of the transfer has ended.
|
|
*
|
|
* In the design, while the endpoint is being prepared (in SET_CONFIGURATION)
|
|
* All transfer transfer are force to be added to Waiting list.
|
|
* and right after endpoint preperation has completed,
|
|
* scheduling is done in the order they were added.
|
|
*
|
|
*
|
|
* endpoint preperation is a method in which the backend is told about the
|
|
* resource requirement the endpoint.
|
|
* based on the description, it allocate resource specific to the periph.
|
|
* endpoint preperation is just an hint for the stack, they can be ignored!
|
|
*
|
|
* There are 3 queue [active, waiting, unused].
|
|
*
|
|
* When a URB is done (or at init or reset), the object is moved to "unused".
|
|
* When a new transfer is submitted, a "unused" URB object is poped and
|
|
* used for the transfer.
|
|
*
|
|
* In between usbd_ep_prepare_start() and usbd_ep_prepare_end() backend should
|
|
* clear DTOG bit for the endpoints. (except EP0)
|
|
*/
|
|
|
|
static void urb_callback(usbd_device *dev, usbd_urb *urb,
|
|
usbd_transfer_status status);
|
|
|
|
#if defined(USBD_DEBUG)
|
|
static const char *stringify_transfer_status(usbd_transfer_status status) {
|
|
switch (status) {
|
|
case USBD_ONE_PACKET_DATA: return "USBD_ONE_PACKET_DATA";
|
|
case USBD_SUCCESS: return "USBD_SUCCESS";
|
|
case USBD_ERR_TIMEOUT: return "USBD_ERR_TIMEOUT";
|
|
case USBD_ERR_IO: return "USBD_ERR_IO";
|
|
case USBD_ERR_SIZE: return "USBD_ERR_SIZE";
|
|
case USBD_ERR_CONFIG_CHANGE: return "USBD_ERR_CONFIG_CHANGE";
|
|
case USBD_ERR_RES_UNAVAIL: return "USBD_ERR_RES_UNAVAIL";
|
|
case USBD_ERR_CONN: return "USBD_ERR_CONN";
|
|
case USBD_ERR_BABBLE: return "USBD_ERR_BABBLE";
|
|
case USBD_ERR_DTOG: return "USBD_ERR_DTOG";
|
|
case USBD_ERR_SHORT_PACKET: return "USBD_ERR_SHORT_PACKET";
|
|
case USBD_ERR_INVALID: return "USBD_ERR_INVALID";
|
|
case USBD_ERR_CANCEL: return "USBD_ERR_CANCEL";
|
|
case USBD_ERR_OVERFLOW: return "USBD_ERR_OVERFLOW";
|
|
default: return "**UNKNOWN**";
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* Get for a free URB
|
|
* @param[in] dev USB Device
|
|
* @return pointer to URB (success)
|
|
* @return NULL (failure)
|
|
*/
|
|
static inline usbd_urb *unused_pop(usbd_device *dev)
|
|
{
|
|
if (dev->urbs.unused == NULL) {
|
|
LOG_LN("WARN: all urb in use");
|
|
return NULL;
|
|
}
|
|
|
|
usbd_urb *tmp = dev->urbs.unused;
|
|
dev->urbs.unused = tmp->next;
|
|
return tmp;
|
|
}
|
|
|
|
/**
|
|
* Add the URB to unused list
|
|
* @param[in] dev USB Device
|
|
* @param[in] urb USB Request Block
|
|
*/
|
|
static inline void unused_push(usbd_device *dev, usbd_urb *urb)
|
|
{
|
|
urb->next = dev->urbs.unused;
|
|
dev->urbs.unused = urb;
|
|
}
|
|
|
|
/**
|
|
* Free the endpoint from the URB.
|
|
* @param[in] dev USB Device
|
|
* @param[in] urb USB Request Block
|
|
*/
|
|
static void free_ep_from_urb(usbd_device *dev, usbd_urb *urb)
|
|
{
|
|
uint8_t addr = urb->transfer.ep_addr;
|
|
|
|
if (!is_ep_free(dev, addr)) {
|
|
/* Endpoint is not free. Tell backend to cancel it */
|
|
dev->backend->urb_cancel(dev, urb);
|
|
mark_ep_as_free(dev, addr, true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Detach the item from the Queue
|
|
* @param[in] queue Queue
|
|
* @param[in] prev Previous item to @a item
|
|
* @param[in] item Item to be detached
|
|
* @return the next item in the queue (NULL if not available)
|
|
*/
|
|
static usbd_urb *queue_item_detach(struct usbd_urb_queue *queue,
|
|
usbd_urb *prev, usbd_urb *item)
|
|
{
|
|
if (queue->head == queue->tail) {
|
|
/* two case:
|
|
* - the queue is fully empty (not possible)
|
|
* - single item (possible)
|
|
*/
|
|
queue->head = queue->tail = NULL;
|
|
return NULL;
|
|
}
|
|
|
|
/* Item at head? */
|
|
if (queue->head == item) {
|
|
queue->head = item->next;
|
|
return queue->head;
|
|
}
|
|
|
|
/* Item at tail? */
|
|
if (queue->tail == item) {
|
|
queue->tail = prev;
|
|
prev->next = NULL;
|
|
return NULL;
|
|
}
|
|
|
|
prev->next = item->next;
|
|
return prev->next;
|
|
}
|
|
|
|
static void queue_item_append(struct usbd_urb_queue *queue, usbd_urb *urb)
|
|
{
|
|
urb->next = NULL;
|
|
|
|
if (queue->head == NULL && queue->tail == NULL) {
|
|
queue->head = queue->tail = urb;
|
|
} else if (queue->head != NULL && queue->tail != NULL) {
|
|
queue->tail->next = urb;
|
|
queue->tail = urb;
|
|
} else {
|
|
/* Problem! */
|
|
LOGF_LN("URB Queue %p corrupt", queue);
|
|
queue->head = queue->tail = urb;
|
|
}
|
|
}
|
|
|
|
#if defined(USBD_ENABLE_TIMEOUT)
|
|
/**
|
|
* Has the URB timeout out compared to @a now
|
|
* @param[in] urb USB Request Block
|
|
* @param[in] now Current time reference
|
|
* @return false No
|
|
* @return true Yes
|
|
*/
|
|
static inline bool is_urb_timed_out(usbd_urb *urb, uint64_t now)
|
|
{
|
|
if (urb->transfer.timeout == USBD_TIMEOUT_NEVER) {
|
|
return false;
|
|
}
|
|
|
|
if (urb->timeout_on >= now) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Check for timeout of URB in @a queue.
|
|
* @param dev USB Device
|
|
* @param now Current time reference
|
|
* @param queue Queue
|
|
* @return true if any URB timeout
|
|
* @return false if no URB timeout
|
|
*/
|
|
static bool queue_timeout_check(usbd_device *dev, uint64_t now,
|
|
struct usbd_urb_queue *queue)
|
|
{
|
|
bool any_urb_timedout = false;
|
|
usbd_urb *urb = queue->head, *prev = NULL, *tmp;
|
|
|
|
while (urb != NULL) {
|
|
if (!is_urb_timed_out(urb, now)) {
|
|
prev = urb;
|
|
urb = urb->next;
|
|
continue;
|
|
}
|
|
|
|
tmp = urb;
|
|
urb = queue_item_detach(queue, prev, tmp);
|
|
free_ep_from_urb(dev, tmp);
|
|
urb_callback(dev, tmp, USBD_ERR_TIMEOUT);
|
|
unused_push(dev, tmp);
|
|
any_urb_timedout = true;
|
|
}
|
|
|
|
return any_urb_timedout;
|
|
}
|
|
|
|
/**
|
|
* Check if any URB has timeout out, it yes remove then with
|
|
* status = USBD_ERR_TIMEOUT
|
|
* @param[in] dev USB Device
|
|
* @param[in] now Current time reference
|
|
*/
|
|
void usbd_timeout_checkup(usbd_device *dev, uint64_t now)
|
|
{
|
|
/* Check the Waiting Queue */
|
|
queue_timeout_check(dev, now, &dev->urbs.waiting);
|
|
|
|
/* Check the Active Queue */
|
|
if (queue_timeout_check(dev, now, &dev->urbs.active)) {
|
|
usbd_urb_schedule(dev);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* Check if the Control endpoint @a size is acceptable or not
|
|
* @param[in] size Control endpoint size
|
|
* @return true on Yes
|
|
* @return false on No
|
|
*/
|
|
static inline bool ctrl_ep0_size_acceptable(uint16_t size)
|
|
{
|
|
switch (size) {
|
|
case 8:
|
|
case 16:
|
|
case 32:
|
|
case 64:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Try to assign allocate the endpoint for the URB
|
|
* @param[in] dev USB Device
|
|
* @param[in] urb USB Request Block
|
|
* @return true if the endpoint has been assigned to @a urb
|
|
* @return false if the endpoint cannot be assigned to @a urb
|
|
*/
|
|
static bool try_alloc_ep_for_urb(usbd_device *dev, usbd_urb *urb)
|
|
{
|
|
uint8_t addr = urb->transfer.ep_addr;
|
|
|
|
if (is_ep_free(dev, addr)) {
|
|
mark_ep_as_free(dev, addr, false);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
usbd_urb_id usbd_transfer_submit(usbd_device *dev,
|
|
const usbd_transfer *transfer)
|
|
{
|
|
/* Various Checks */
|
|
if (transfer->ep_type == USBD_EP_CONTROL) {
|
|
if (!ENDPOINT_NUMBER(transfer->ep_addr) &&
|
|
!ctrl_ep0_size_acceptable(transfer->ep_size)) {
|
|
LOGF_LN("Invalid control endpoint size %"PRIu16" for EP0",
|
|
transfer->ep_size);
|
|
TRANSFER_CALLBACK(dev, transfer, USBD_ERR_INVALID, USBD_INVALID_URB_ID)
|
|
return USBD_INVALID_URB_ID;
|
|
}
|
|
}
|
|
|
|
/* check if got any URB free */
|
|
usbd_urb *urb = unused_pop(dev);
|
|
if (urb == NULL) {
|
|
TRANSFER_CALLBACK(dev, transfer, USBD_ERR_RES_UNAVAIL, USBD_INVALID_URB_ID)
|
|
return USBD_INVALID_URB_ID;
|
|
}
|
|
|
|
/* store the information in URB */
|
|
urb->id = dev->urbs.next_id++;
|
|
urb->transfer = *transfer;
|
|
urb->transfer.transferred = 0;
|
|
#if defined(USBD_ENABLE_TIMEOUT)
|
|
urb->timeout_on = transfer->timeout ?
|
|
(dev->last_poll + MS2US(transfer->timeout)) : 0;
|
|
#endif
|
|
|
|
|
|
#if defined(USBD_DEBUG)
|
|
const char *ep_type_map_str[] = {
|
|
[USBD_EP_CONTROL] = "Control",
|
|
[USBD_EP_INTERRUPT] = "Interrupt",
|
|
[USBD_EP_ISOCHRONOUS] = "Isochronous",
|
|
[USBD_EP_BULK] = "Bulk",
|
|
};
|
|
|
|
LOGF_LN("Created URB %"PRIu64": %s %s %"PRIu8": data=%p, length=%u",
|
|
urb->id, ep_type_map_str[urb->transfer.ep_type],
|
|
IS_IN_ENDPOINT(urb->transfer.ep_addr) ? "IN" : "OUT",
|
|
ENDPOINT_NUMBER(urb->transfer.ep_addr),
|
|
urb->transfer.buffer, urb->transfer.length);
|
|
#endif
|
|
|
|
bool to_active = !dev->urbs.force_all_new_urb_to_waiting &&
|
|
try_alloc_ep_for_urb(dev, urb);
|
|
|
|
LOGF_LN("[new] URB id=%"PRIu64" is %s", urb->id,
|
|
to_active ? "active" : "waiting");
|
|
|
|
if (to_active) {
|
|
mark_ep_as_free(dev, urb->transfer.ep_addr, false);
|
|
queue_item_append(&dev->urbs.active, urb);
|
|
dev->backend->urb_submit(dev, urb);
|
|
} else {
|
|
queue_item_append(&dev->urbs.waiting, urb);
|
|
}
|
|
|
|
return urb->id;
|
|
}
|
|
|
|
/**
|
|
* Do the callback for the @a urb
|
|
* @param[in] dev USB Device
|
|
* @param[in] urb USB Request Block
|
|
* @param[in] status @a urb Status
|
|
*/
|
|
static void urb_callback(usbd_device *dev, usbd_urb *urb,
|
|
usbd_transfer_status status)
|
|
{
|
|
LOG_CALL
|
|
|
|
LOGF_LN("URB %"PRIu64" transfer status = %s", urb->id,
|
|
stringify_transfer_status(status));
|
|
|
|
/* callback provided */
|
|
if (urb->transfer.callback == NULL) {
|
|
return;
|
|
}
|
|
|
|
/* Explicitly mentioned not to do success callback */
|
|
if (urb->transfer.flags & USBD_FLAG_NO_SUCCESS_CALLBACK) {
|
|
if (status == USBD_SUCCESS) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
urb->transfer.callback(dev, &urb->transfer, status, urb->id);
|
|
}
|
|
|
|
bool usbd_transfer_cancel(usbd_device *dev, usbd_urb_id urb_id)
|
|
{
|
|
usbd_urb *urb, *prev;
|
|
|
|
if (urb_id == USBD_INVALID_URB_ID) {
|
|
LOG_LN("invalid urb id passed to transfer_cancel");
|
|
return false;
|
|
}
|
|
|
|
/* Check the Active Queue */
|
|
for (prev = NULL, urb = dev->urbs.active.head; urb != NULL; ) {
|
|
if (urb->id != urb_id) {
|
|
prev = urb;
|
|
urb = urb->next;
|
|
continue;
|
|
}
|
|
|
|
queue_item_detach(&dev->urbs.active, prev, urb);
|
|
free_ep_from_urb(dev, urb);
|
|
urb_callback(dev, urb, USBD_ERR_CANCEL);
|
|
unused_push(dev, urb);
|
|
return true;
|
|
}
|
|
|
|
/* Check the Waiting Queue */
|
|
for (prev = NULL, urb = dev->urbs.waiting.head; urb != NULL; ) {
|
|
if (urb->id != urb_id) {
|
|
prev = urb;
|
|
urb = urb->next;
|
|
continue;
|
|
}
|
|
|
|
queue_item_detach(&dev->urbs.waiting, prev, urb);
|
|
urb_callback(dev, urb, USBD_ERR_CANCEL);
|
|
unused_push(dev, urb);
|
|
return true;
|
|
}
|
|
|
|
LOGF_LN("WARN: urb with id = %"PRIu64" not found", urb_id);
|
|
return false;
|
|
}
|
|
|
|
unsigned usbd_transfer_cancel_ep(usbd_device *dev, uint8_t ep_addr)
|
|
{
|
|
unsigned result = 0;
|
|
usbd_urb *urb, *prev;
|
|
|
|
/* Check the Active Queue */
|
|
if (!is_ep_free(dev, ep_addr)) {
|
|
for (prev = NULL, urb = dev->urbs.active.head; urb != NULL; ) {
|
|
if (urb->transfer.ep_addr != ep_addr) {
|
|
prev = urb;
|
|
urb = urb->next;
|
|
continue;
|
|
}
|
|
|
|
queue_item_detach(&dev->urbs.active, prev, urb);
|
|
free_ep_from_urb(dev, urb);
|
|
urb_callback(dev, urb, USBD_ERR_CANCEL);
|
|
unused_push(dev, urb);
|
|
result++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Check the Waiting Queue */
|
|
for (prev = NULL, urb = dev->urbs.waiting.head; urb != NULL; ) {
|
|
if (urb->transfer.ep_addr != ep_addr) {
|
|
prev = urb;
|
|
urb = urb->next;
|
|
continue;
|
|
}
|
|
|
|
queue_item_detach(&dev->urbs.waiting, prev, urb);
|
|
urb_callback(dev, urb, USBD_ERR_CANCEL);
|
|
unused_push(dev, urb);
|
|
result++;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Schedule new URB from WAITING to ACTIVE if the endpoint is free
|
|
* @param[in] dev USB Device
|
|
*/
|
|
void usbd_urb_schedule(usbd_device *dev)
|
|
{
|
|
if (dev->urbs.force_all_new_urb_to_waiting) {
|
|
LOG_LN("Could not schedule (force_all_new_urb_to_waiting = true)");
|
|
return;
|
|
}
|
|
|
|
usbd_urb *urb = dev->urbs.waiting.head, *prev = NULL, *tmp;
|
|
|
|
while (urb != NULL) {
|
|
if (!try_alloc_ep_for_urb(dev, urb)) {
|
|
prev = urb;
|
|
urb = urb->next;
|
|
continue;
|
|
}
|
|
|
|
tmp = urb;
|
|
urb = queue_item_detach(&dev->urbs.waiting, prev, tmp);
|
|
queue_item_append(&dev->urbs.active, tmp);
|
|
|
|
LOGF_LN("[waiting] URB id=%"PRIu64" is now active", tmp->id);
|
|
dev->backend->urb_submit(dev, tmp);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Detach the URB from active list
|
|
* Usage: backend to remove a URB from active list.
|
|
* @param[in] dev USB Device
|
|
* @param[in] item Item to detach
|
|
*/
|
|
static void detach_from_active(usbd_device *dev, usbd_urb *item)
|
|
{
|
|
usbd_urb *urb = dev->urbs.active.head, *prev = NULL;
|
|
|
|
while (urb != NULL) {
|
|
if (urb != item) {
|
|
prev = urb;
|
|
urb = urb->next;
|
|
continue;
|
|
}
|
|
|
|
queue_item_detach(&dev->urbs.active, prev, urb);
|
|
free_ep_from_urb(dev, urb);
|
|
return;
|
|
}
|
|
|
|
LOGF_LN("WARNING: Found not find URB %"PRIu64" in active list to detach it",
|
|
urb->id);
|
|
}
|
|
|
|
/**
|
|
* Free the URB as it purpose has been served.
|
|
* Usage: Backend use it to mark the transfer
|
|
* @param[in] dev USB Device
|
|
* @param[in] urb USB Request Block
|
|
* @param[in] status Transfer status to provide to callback
|
|
*/
|
|
void usbd_urb_complete(usbd_device *dev, usbd_urb *urb,
|
|
usbd_transfer_status status)
|
|
{
|
|
detach_from_active(dev, urb);
|
|
urb_callback(dev, urb, status);
|
|
unused_push(dev, urb);
|
|
usbd_urb_schedule(dev);
|
|
}
|
|
|
|
/**
|
|
* Find the current processing URB
|
|
* @param[in] dev USB Device
|
|
* @param[in] ep_addr Endpoint (including direction)
|
|
* @return Found URB
|
|
* @return NULL (Not found - If this is the case, we are in problem)
|
|
*/
|
|
usbd_urb *usbd_find_active_urb(usbd_device *dev, uint8_t ep_addr)
|
|
{
|
|
usbd_urb *urb = NULL;
|
|
|
|
LOGF_LN("Searching for URB which is active and has "
|
|
"ep_addr=0x%"PRIx8, ep_addr);
|
|
|
|
for (urb = dev->urbs.active.head; urb != NULL; urb = urb->next) {
|
|
if (urb->transfer.ep_addr == ep_addr) {
|
|
return urb;
|
|
}
|
|
}
|
|
|
|
LOGF_LN("Unable to find the current processing URB for "
|
|
"endpoint 0x%"PRIx8, ep_addr);
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Clear up the whole queue
|
|
* @param[in] dev USB Device
|
|
* @param[in] queue Queue to clear up
|
|
* @param[in] status Status transfer to end with
|
|
*/
|
|
static void flush_queue(usbd_device *dev, struct usbd_urb_queue *queue,
|
|
usbd_transfer_status status)
|
|
{
|
|
usbd_urb *urb;
|
|
|
|
for (urb = queue->head; urb != NULL; urb = urb->next) {
|
|
urb_callback(dev, urb, status);
|
|
}
|
|
|
|
queue->head = queue->tail = NULL;
|
|
}
|
|
|
|
/**
|
|
* Intalize @a dev->urbs->unused.
|
|
* All arr entries are set to unused.
|
|
* @param[in] dev USB Device
|
|
*/
|
|
void usbd_put_all_urb_into_unused(usbd_device *dev)
|
|
{
|
|
unsigned i;
|
|
usbd_urb *prev;
|
|
|
|
dev->urbs.unused = prev = &dev->urbs.arr[0];
|
|
|
|
for (i = 1; i < USBD_URB_COUNT; i++) {
|
|
usbd_urb *urb = &dev->urbs.arr[i];
|
|
prev->next = urb;
|
|
prev = urb;
|
|
}
|
|
|
|
prev->next = NULL;
|
|
}
|
|
|
|
/**
|
|
* Move every URB to unused list (a reset has occured)
|
|
* @param[in] dev USB device
|
|
* @param[in] status Transfer status
|
|
*/
|
|
void usbd_purge_all_transfer(usbd_device *dev, usbd_transfer_status status)
|
|
{
|
|
flush_queue(dev, &dev->urbs.active, status);
|
|
flush_queue(dev, &dev->urbs.waiting, status);
|
|
usbd_put_all_urb_into_unused(dev);
|
|
dev->urbs.ep_free = ~0;
|
|
}
|
|
|
|
/**
|
|
* Purge any non zero endpoint transfer with @a status
|
|
* @param dev USB Device
|
|
* @param queue Queue
|
|
* @param status Callback status
|
|
*/
|
|
static void purge_non_ep0_tranfer_from_queue(usbd_device *dev,
|
|
struct usbd_urb_queue *queue, usbd_transfer_status status)
|
|
{
|
|
usbd_urb *urb = queue->head, *prev = NULL, *tmp;
|
|
|
|
while (urb != NULL) {
|
|
if (!ENDPOINT_NUMBER(urb->transfer.ep_addr)) {
|
|
prev = urb;
|
|
urb = urb->next;
|
|
continue;
|
|
}
|
|
|
|
tmp = urb;
|
|
urb = queue_item_detach(queue, prev, tmp);
|
|
urb_callback(dev, tmp, status);
|
|
unused_push(dev, tmp);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reset all non EP0 endpoint (and remove the transfers too)
|
|
* Cancel all non EP0 transfer.
|
|
* Usage: SET_CONFIGURATION
|
|
* @param[in] dev USB Device
|
|
* @param[in] status Transfer status
|
|
*/
|
|
void usbd_purge_all_non_ep0_transfer(usbd_device *dev,
|
|
usbd_transfer_status status)
|
|
{
|
|
purge_non_ep0_tranfer_from_queue(dev, &dev->urbs.active, status);
|
|
purge_non_ep0_tranfer_from_queue(dev, &dev->urbs.waiting, status);
|
|
|
|
/* Mark all endpoint as free (except EP0) */
|
|
dev->urbs.ep_free |= ~0x00010001;
|
|
}
|
|
|
|
/**
|
|
* Callback from the data stage transfer
|
|
*/
|
|
static void _control_status_callback(usbd_device *dev,
|
|
const usbd_transfer *transfer, usbd_transfer_status status,
|
|
usbd_urb_id urb_id)
|
|
{
|
|
(void) urb_id;
|
|
|
|
if (status != USBD_SUCCESS) {
|
|
LOGF_LN("[Control status stage] Transfer %"PRIu64" failed with "
|
|
"status=%s", urb_id, stringify_transfer_status(status));
|
|
return;
|
|
}
|
|
|
|
usbd_control_transfer_callback callback = transfer->user_data;
|
|
if (callback != NULL) {
|
|
callback(dev, NULL);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Perform status stage.
|
|
* @param[in] ep_addr Endpoint address (bit7: high = status in, low = status out)
|
|
* @param[in] ep_size Endpoint size
|
|
* @param[in] callback Callback to be done when successful
|
|
*/
|
|
static void _control_status_stage(usbd_device *dev, uint8_t ep_addr,
|
|
uint16_t ep_size, usbd_control_transfer_callback callback)
|
|
{
|
|
const usbd_transfer transfer = {
|
|
.ep_type = USBD_EP_CONTROL,
|
|
.ep_addr = ep_addr,
|
|
.ep_size = ep_size,
|
|
.ep_interval = USBD_INTERVAL_NA,
|
|
.buffer = NULL,
|
|
.length = 0,
|
|
.flags = USBD_FLAG_NONE,
|
|
.timeout = USBD_TIMEOUT_NEVER,
|
|
.callback = _control_status_callback,
|
|
.user_data = callback
|
|
};
|
|
|
|
if (usbd_transfer_submit(dev, &transfer) == USBD_INVALID_URB_ID) {
|
|
LOGF_LN("Failed to submit status transfer for control endpoint 0x%"PRIx8,
|
|
ep_addr);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Callback from the data stage transfer
|
|
*/
|
|
static void _control_data_callback(usbd_device *dev,
|
|
const usbd_transfer *transfer, usbd_transfer_status status,
|
|
usbd_urb_id urb_id)
|
|
{
|
|
(void) urb_id;
|
|
|
|
if (status != USBD_SUCCESS) {
|
|
LOGF_LN("[Control data stage] Transfer %"PRIu64" failed with "
|
|
"status=%s, not going into status stage", urb_id,
|
|
stringify_transfer_status(status));
|
|
return;
|
|
}
|
|
|
|
usbd_control_transfer_callback callback = transfer->user_data;
|
|
usbd_control_transfer_feedback feedback = USBD_CONTROL_TRANSFER_OK;
|
|
|
|
if (callback != NULL) {
|
|
usbd_control_transfer_callback_arg arg = {
|
|
.buffer = transfer->buffer,
|
|
.length = transfer->transferred
|
|
};
|
|
|
|
/* User provided callback, ask callback what to do next */
|
|
feedback = callback(dev, &arg);
|
|
}
|
|
|
|
/* User want to stall the transfer */
|
|
if (feedback & USBD_CONTROL_TRANSFER_STALL) {
|
|
usbd_set_ep_stall(dev, transfer->ep_addr, true);
|
|
usbd_set_ep_stall(dev, transfer->ep_addr ^ 0x80, true);
|
|
return;
|
|
}
|
|
|
|
/* User dont want anymore callback */
|
|
if (feedback & USBD_CONTROL_TRANSFER_NO_STATUS_CALLBACK) {
|
|
callback = NULL;
|
|
}
|
|
|
|
_control_status_stage(dev, transfer->ep_addr ^ 0x80, transfer->ep_size,
|
|
callback);
|
|
}
|
|
|
|
/**
|
|
* Perform DATA stage
|
|
* @param[in] dev USB Device
|
|
* @param[in] ep_addr Endpoint address (7bit: high = DATA IN, low = DATA OUT)
|
|
* @param[in] ep_size Endpoint size
|
|
* @param[in] wLength Number of actually data bytes requested by host
|
|
* @param[in] buf Pointer to buffer to use
|
|
* @param[in] len Number of bytes to actually send
|
|
* @param[in] callback Callback to done on successful completition of transaction.
|
|
*/
|
|
static void _control_data_stage(usbd_device *dev, uint8_t ep_addr,
|
|
uint8_t ep_size, uint16_t wLength, void *buf, size_t len,
|
|
usbd_control_transfer_callback callback)
|
|
{
|
|
if (IS_IN_ENDPOINT(ep_addr)) {
|
|
if (len > wLength) {
|
|
LOGF_LN("WARN: Try to send %u bytes when host only requested "
|
|
"%"PRIu16" bytes (more than requested) on control endpoint "
|
|
"0x%"PRIx8, len, wLength, ep_addr);
|
|
len = wLength;
|
|
}
|
|
} else {
|
|
if (len < wLength) {
|
|
LOGF_LN("WARNING: Host will be sending %"PRIu16" bytes but "
|
|
"buffer can only accomodated %u bytes on control endpoint "
|
|
"0x%"PRIx8, wLength, len, ep_addr);
|
|
}
|
|
}
|
|
|
|
usbd_transfer_flags flags = USBD_FLAG_NONE;
|
|
|
|
/*
|
|
* Transfer should always end with a short packet tell the host that
|
|
* because we have less data than the host is expecting.
|
|
* This flag come into play when the "len" is multiple of endpoint size.
|
|
*/
|
|
if (len != wLength) {
|
|
flags = USBD_FLAG_SHORT_PACKET;
|
|
}
|
|
|
|
const usbd_transfer transfer = {
|
|
.ep_type = USBD_EP_CONTROL,
|
|
.ep_addr = ep_addr,
|
|
.ep_size = ep_size,
|
|
.ep_interval = USBD_INTERVAL_NA,
|
|
.buffer = buf,
|
|
.length = len,
|
|
.flags = flags,
|
|
.timeout = USBD_TIMEOUT_NEVER,
|
|
.callback = _control_data_callback,
|
|
.user_data = callback
|
|
};
|
|
|
|
if (usbd_transfer_submit(dev, &transfer) == USBD_INVALID_URB_ID) {
|
|
LOGF_LN("Failed to submit data transfer for control endpoint 0x%"PRIx8,
|
|
ep_addr);
|
|
}
|
|
}
|
|
|
|
void usbd_control_transfer(usbd_device *dev, uint8_t ep_addr,
|
|
uint16_t ep_size, const struct usb_setup_data *setup_data,
|
|
void *buf, size_t len, usbd_control_transfer_callback callback)
|
|
{
|
|
/* Data stage and status stage both have DTOG = 1 */
|
|
dev->backend->set_ep_dtog(dev, ep_addr, true);
|
|
dev->backend->set_ep_dtog(dev, ep_addr ^ 0x80, true);
|
|
|
|
if (!setup_data->wLength) {
|
|
/* wLength is zero, directly proceed to status IN stage! */
|
|
_control_status_stage(dev, ep_addr | 0x80, ep_size, callback);
|
|
return;
|
|
}
|
|
|
|
if (!(setup_data->bmRequestType & 0x80)) {
|
|
if (setup_data->wLength > len) {
|
|
/* User is providing less buffer to store than the host is
|
|
* going to send for data stage.
|
|
* If our safest bet is to STALL the transaction.
|
|
*/
|
|
LOGF_LN("STALL: User provide less buffer than "
|
|
"the host is going to send");
|
|
usbd_set_ep_stall(dev, ep_addr, true);
|
|
usbd_set_ep_stall(dev, ep_addr ^ 0x80, true);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (setup_data->bmRequestType & 0x80) {
|
|
ep_addr |= 0x80;
|
|
} else {
|
|
ep_addr &= 0x7F;
|
|
}
|
|
|
|
_control_data_stage(dev, ep_addr, ep_size, setup_data->wLength, buf, len,
|
|
callback);
|
|
}
|
|
|
|
void usbd_ep0_transfer(usbd_device *dev,
|
|
const struct usb_setup_data *setup_data, void *buf, size_t len,
|
|
usbd_control_transfer_callback callback)
|
|
{
|
|
usbd_control_transfer(dev, 0x00, dev->info->device.desc->bMaxPacketSize0,
|
|
setup_data, buf, len, callback);
|
|
}
|
|
|
|
void usbd_ep0_stall(usbd_device *dev)
|
|
{
|
|
LOG_LN("Stalling EP0");
|
|
usbd_set_ep_stall(dev, 0x00, true);
|
|
usbd_set_ep_stall(dev, 0x80, true);
|
|
}
|
|
|
|
/**
|
|
* Called by backend to get a pointer to receive/transmit 1 packet
|
|
* @param[in] dev USB Device
|
|
* @param[in] urb USB Request Block
|
|
* @param len The number of bytes it should be able to hold/provide
|
|
* @return pointer to data
|
|
*/
|
|
void *usbd_urb_get_buffer_pointer(usbd_device *dev, usbd_urb *urb, size_t len)
|
|
{
|
|
usbd_transfer *transfer = &urb->transfer;
|
|
bool out = IS_OUT_ENDPOINT(transfer->ep_addr);
|
|
|
|
#if defined(USBD_DEBUG)
|
|
if ((transfer->transferred + len) > transfer->length) {
|
|
LOGF_LN("URB %"PRIu64" buffer overflow detected! "
|
|
"(backend want to %s %"PRIu16" bytes to buffer)", urb->id,
|
|
out ? "write" : "read", len);
|
|
LOGF_LN("transfer->length: %"PRIu16, transfer->length);
|
|
LOGF_LN("transfer->transferred: %"PRIu16, transfer->transferred);
|
|
}
|
|
#else
|
|
(void) len;
|
|
#endif
|
|
|
|
if (transfer->flags & USBD_FLAG_PER_PACKET_CALLBACK) {
|
|
if (!out) {
|
|
/* IN endpoint, get data from user */
|
|
TRANSFER_CALLBACK(dev, &urb->transfer, USBD_ONE_PACKET_DATA, urb->id)
|
|
}
|
|
}
|
|
|
|
if (transfer->flags & USBD_FLAG_NO_MEMORY_INCREMENT) {
|
|
/* User said to reuse same buffer location everytime. */
|
|
return transfer->buffer;
|
|
}
|
|
|
|
return transfer->buffer + transfer->transferred;
|
|
}
|
|
|
|
/**
|
|
* Called by backend when it get data
|
|
* @param[in] dev USB Device
|
|
* @param[in] urb USB Request Block
|
|
* @param[in] len
|
|
*/
|
|
void usbd_urb_inc_data_pointer(usbd_device *dev, usbd_urb *urb, size_t len)
|
|
{
|
|
usbd_transfer *transfer = &urb->transfer;
|
|
bool out = IS_OUT_ENDPOINT(transfer->ep_addr);
|
|
|
|
#if defined(USBD_DEBUG)
|
|
if ((transfer->transferred + len) > transfer->length) {
|
|
LOGF_LN("URB %"PRIu64" buffer overflow detected! "
|
|
"(backend is reporting that it has %s %"PRIu16" bytes)", urb->id,
|
|
out ? "written" : "readed", len);
|
|
LOGF_LN("transfer->length: %"PRIu16, transfer->length);
|
|
LOGF_LN("transfer->transferred: %"PRIu16, transfer->transferred);
|
|
}
|
|
#endif
|
|
|
|
transfer->transferred += len;
|
|
|
|
if (transfer->flags & USBD_FLAG_PER_PACKET_CALLBACK) {
|
|
if (out) {
|
|
/* OUT endpoint, give data to user */
|
|
TRANSFER_CALLBACK(dev, &urb->transfer, USBD_ONE_PACKET_DATA, urb->id)
|
|
}
|
|
}
|
|
}
|