/* * This file is part of the unicore-mx project. * * Copyright (C) 2016 Kuldeep Singh Dhaka * * 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 . */ #include #include #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) } } }