blob: 6025201c6ffd539062e09e01145986f7eb65e9f7 [file] [log] [blame]
// 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);