gadget-kspconsole/unicore-mx/lib/usbd/usbd_transfer.c
Daniele Lacamera b8a7ffcd31 Initial import
2023-11-27 15:16:45 +01:00

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)
}
}
}