| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| |
| #include <assert.h> |
| #include <string.h> |
| |
| #include "usb_utils.h" |
| #include "usbdpi.h" |
| |
| // Set up all of the available transfer descriptors |
| void usb_transfer_setup(usbdpi_ctx_t *ctx) { |
| usbdpi_transfer_t *next = NULL; |
| int idx = (int)USBDPI_MAX_TRANSFERS; |
| while (--idx >= 0) { |
| ctx->transfer_pool[idx].next = next; |
| next = &ctx->transfer_pool[idx]; |
| } |
| ctx->free = next; |
| } |
| |
| // Allocate and initialize a transfer descriptor |
| usbdpi_transfer_t *transfer_alloc(usbdpi_ctx_t *ctx) { |
| usbdpi_transfer_t *transfer = ctx->free; |
| if (transfer) { |
| ctx->free = transfer->next; |
| transfer_init(transfer); |
| } |
| return transfer; |
| } |
| |
| // Free a transfer descriptor |
| void transfer_release(usbdpi_ctx_t *ctx, usbdpi_transfer_t *transfer) { |
| // Prepend this transfer descriptor to the list of free descriptors |
| transfer->next = ctx->free; |
| ctx->free = transfer; |
| } |
| |
| // Initialize a transfer descriptor |
| void transfer_init(usbdpi_transfer_t *transfer) { |
| // Not within a linked list |
| transfer->next = NULL; |
| |
| // Initially content free |
| transfer->num_bytes = 0U; |
| transfer->data_start = USBDPI_NO_DATA_STAGE; |
| } |
| |
| // Prepare and send a 'Start Of Frame' transfer descriptor |
| void transfer_frame_start(usbdpi_ctx_t *ctx, usbdpi_transfer_t *transfer, |
| unsigned frame) { |
| // 11 bits of the frame number are used, and they are encoded in the same |
| // manner as the device address and endpoint number of other packets |
| transfer_token(transfer, USB_PID_SOF, frame & 0x7FU, (frame >> 7) & 15); |
| transfer_send(ctx, transfer); |
| } |
| |
| // Construct and send the setup stage of a control transfer |
| void transfer_setup(usbdpi_ctx_t *ctx, usbdpi_transfer_t *transfer, |
| uint8_t bmRequestType, uint8_t bRequest, uint16_t wValue, |
| uint16_t wIndex, uint16_t wLength) { |
| transfer_token(transfer, USB_PID_SETUP, ctx->dev_address, ENDPOINT_ZERO); |
| |
| uint8_t *dp = transfer_data_start(transfer, USB_PID_DATA0, 8U); |
| dp[0] = bmRequestType; |
| dp[1] = bRequest; |
| dp[2] = (uint8_t)wValue; |
| dp[3] = (uint8_t)(wValue >> 8); |
| dp[4] = (uint8_t)wIndex; |
| dp[5] = (uint8_t)(wIndex >> 8); |
| dp[6] = (uint8_t)wLength; |
| dp[7] = (uint8_t)(wLength >> 8); |
| transfer_data_end(transfer, dp + 8); |
| |
| // Send the transfer descriptor |
| transfer_send(ctx, transfer); |
| ctx->bus_state = kUsbControlSetup; |
| ctx->wait = USBDPI_TIMEOUT(ctx, USBDPI_INTERVAL_SETUP_STAGE); |
| ctx->hostSt = HS_WAITACK; |
| } |
| |
| // Append a token packet to a transfer descriptor that is under construction |
| void transfer_token(usbdpi_transfer_t *transfer, uint8_t pid, uint8_t device, |
| uint8_t endpoint) { |
| assert(device < 0x80U); |
| assert(endpoint < 0x10U); |
| |
| transfer_init(transfer); |
| |
| assert(transfer->num_bytes <= sizeof(transfer->data) - 3); |
| uint8_t *dp = &transfer->data[transfer->num_bytes]; |
| |
| dp[0] = pid; |
| dp[1] = (uint8_t)(device | (endpoint << 7)); |
| dp[2] = |
| (uint8_t)((endpoint >> 1) | (CRC5((endpoint << 7) | device, 11) << 3)); |
| |
| transfer->num_bytes += 3U; |
| } |
| |
| // Append a data stage to the given transfer descriptor, using the specified PID |
| // (DATA0|DATA1) and checking that there is sufficient space available |
| // (est_size) |
| uint8_t *transfer_data_start(usbdpi_transfer_t *transfer, uint8_t pid, |
| unsigned est_size) { |
| // TODO - we should at this point check that we are properly compliant with |
| // DATA0|DATA1 signalling too! |
| // unless of course we are deliberately being non-compliant |
| assert(pid == USB_PID_DATA0 || pid == USB_PID_DATA1); |
| |
| // If an estimated size has been speciifed, check whether there's sufficient |
| // space |
| unsigned data_start = transfer->num_bytes; |
| if (est_size > sizeof(transfer->data) - 1U - data_start) { |
| assert(!"transfer_data_start space check failed"); |
| return NULL; |
| } |
| |
| // Retain the offset of the PID (not the data field itself) for the |
| // calculation of the CRC16 and for the signalling transition (EOP) when we |
| // come to send this... |
| transfer->data_start = data_start; |
| uint8_t *dp = &transfer->data[data_start]; |
| *dp++ = pid; |
| transfer->num_bytes++; |
| |
| // We return a pointer to where the data stage shall be constructed, and the |
| // caller shall pass the advanced pointer to tranfer_data_end() to ensure |
| // correct completion of the data stage |
| return dp; |
| } |
| |
| // Conclude a data stage within a transfer, calculating and appending the CRC16 |
| // of the data field |
| void transfer_data_end(usbdpi_transfer_t *transfer, uint8_t *dp) { |
| assert(transfer->data_start != USBDPI_NO_DATA_STAGE); |
| assert(dp >= transfer->data); |
| |
| // Check that we still have space to append the CRC16 |
| assert(dp < transfer->data + sizeof(transfer->data) - 2); |
| |
| // Note: historically the datastart field has pointed to the PID rather than |
| // the data field itself |
| const uint8_t *data_field = &transfer->data[transfer->data_start + 1U]; |
| uint32_t crc = CRC16(data_field, dp - data_field); |
| dp[0] = (uint8_t)crc; |
| dp[1] = (uint8_t)(crc >> 8); |
| |
| // Update the count of bytes in the transfer |
| transfer->num_bytes = (dp + 2) - transfer->data; |
| } |
| |
| // Append some data to a transfer description |
| bool transfer_append(usbdpi_transfer_t *transfer, const uint8_t *dp, size_t n) { |
| unsigned num_bytes = transfer->num_bytes; |
| |
| // Check that we still have space to append this data |
| if (sizeof(transfer->data) - num_bytes < n) { |
| return false; |
| } |
| memcpy(&transfer->data[num_bytes], dp, n); |
| transfer->num_bytes = num_bytes + n; |
| return true; |
| } |
| |
| // Prepare a transfer for transmission to the device, and get ready to transmit |
| void transfer_send(usbdpi_ctx_t *ctx, usbdpi_transfer_t *transfer) { |
| // Validate the transfer properties |
| assert(transfer->num_bytes > 0U && |
| transfer->num_bytes <= sizeof(transfer->data)); |
| |
| // Set this as the current in-progress transfer |
| ctx->sending = transfer; |
| |
| // Prepare for transmission of the first byte, following the implicit SYNC |
| ctx->state = ST_SYNC; |
| ctx->byte = 0; |
| ctx->bit = 1; |
| } |
| |
| // Construct and prepare to send a Status response; |
| // Note: there is no requirement to call either transfer_init() or |
| // transfer_send() when using transfer_status() |
| void transfer_status(usbdpi_ctx_t *ctx, usbdpi_transfer_t *transfer, |
| uint8_t pid) { |
| // The single byte Status stage carries just the PID |
| transfer->num_bytes = 1U; |
| transfer->data[0] = pid; |
| |
| transfer->data_start = USBDPI_NO_DATA_STAGE; |
| |
| // Set this as the current in-progress transfer |
| ctx->sending = transfer; |
| |
| // Prepare for transmission of the first byte, following the implicit SYNC |
| ctx->state = ST_SYNC; |
| ctx->byte = 0; |
| ctx->bit = 1; |
| } |
| |
| // Diagnostic utility function to dump out the contents of a transfer descriptor |
| void transfer_dump(usbdpi_transfer_t *transfer, FILE *out) { |
| fprintf(out, "[usbdpi] Transfer descriptor at %p\n", transfer); |
| fprintf(out, "[usbdpi] num_bytes 0x%x data_start 0x%x\n", transfer->num_bytes, |
| transfer->data_start); |
| dump_bytes(out, "[usbdpi] ", transfer->data, transfer->num_bytes, 0U); |
| } |
| |
| // `extern` declarations to give the inline functions in the |
| // corresponding header a link location. |
| |
| extern uint8_t *transfer_data_field(usbdpi_transfer_t *transfer); |
| extern uint8_t transfer_data_pid(usbdpi_transfer_t *transfer); |
| extern uint32_t transfer_length(const usbdpi_transfer_t *transfer); |