blob: b4ac7d5139c218f8598e2409693417c28a043498 [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 <stdint.h>
#include <string.h>
#include "usb_utils.h"
#include "usbdpi.h"
// Seed numbers for the LFSR generators in each transfer direction for
// the given stream number
#define USBTST_LFSR_SEED(s) (uint8_t)(0x10U + (s)*7U)
#define USBDPI_LFSR_SEED(s) (uint8_t)(0x9BU - (s)*7U)
// Seed number of packet retrying
#define RETRY_LFSR_SEED(s) (uint8_t)(0x24U + (s)*7U)
// Simple LFSR for 8-bit sequences
#define LFSR_ADVANCE(lfsr) \
(((lfsr) << 1) ^ \
((((lfsr) >> 1) ^ ((lfsr) >> 2) ^ ((lfsr) >> 3) ^ ((lfsr) >> 7)) & 1u))
// Stream signature words
#define STREAM_SIGNATURE_HEAD 0x579EA01AU
#define STREAM_SIGNATURE_TAIL 0x160AE975U
// Verbose logging/diagnostic reporting
// TODO - consider folding this into the existing log level
static const bool verbose = false;
static const bool expect_sig = true;
// Determine the next stream for which IN data packets shall be requested
static inline unsigned in_stream_next(usbdpi_ctx_t *ctx);
// Determine the next stream for which OUT data shall be sent
static inline unsigned out_stream_next(usbdpi_ctx_t *ctx);
// Check a data packet received from the test software (usbdev_stream_test)
static bool stream_data_check(usbdpi_ctx_t *ctx, usbdpi_stream_t *s,
const usbdpi_transfer_t *rx, bool accept);
// Generate a data packet as if it had been received from the device
static usbdpi_transfer_t *stream_data_gen(usbdpi_ctx_t *ctx, usbdpi_stream_t *s,
unsigned len);
// Check a data packet received from the test software (usbdev_stream_test)
// and collect the data, combined with our LFSR-generated random stream,
// for later transmission back to the device
static usbdpi_transfer_t *stream_data_process(usbdpi_ctx_t *ctx,
usbdpi_stream_t *s,
usbdpi_transfer_t *rx);
// Check the stream signature
static bool stream_sig_check(usbdpi_ctx_t *ctx, usbdpi_stream_t *s,
usbdpi_transfer_t *rx);
// Determine the next stream for which IN data packets shall be requested
inline unsigned in_stream_next(usbdpi_ctx_t *ctx) {
uint8_t id = ctx->stream_in;
if (++id >= ctx->nstreams) {
id = 0U;
}
ctx->stream_in = id;
return id;
}
// Determine the next stream for which OUT data shall be sent
inline unsigned out_stream_next(usbdpi_ctx_t *ctx) {
uint8_t id = ctx->stream_out;
if (++id >= ctx->nstreams) {
id = 0U;
}
ctx->stream_out = id;
return id;
}
// Initialize streaming state for the given number of streams
bool streams_init(usbdpi_ctx_t *ctx, unsigned nstreams, bool retrieve,
bool checking, bool retrying, bool send) {
assert(ctx);
// Can we support the requested number of streams?
if (!nstreams || nstreams > USBDPI_MAX_STREAMS) {
return false;
}
if (verbose) {
printf("[usbdpi] Stream test running with %u streams(s)\n", nstreams);
printf("[usbdpi] - retrieve %c checking %c retrying %c send %c\n",
retrieve ? 'Y' : 'N', checking ? 'Y' : 'N', retrying ? 'Y' : 'N',
send ? 'Y' : 'N');
}
// Remember the number of streams and initialize the arbitration of
// IN and OUT traffic
ctx->nstreams = nstreams;
ctx->stream_in = 0U;
ctx->stream_out = nstreams - 1U;
for (unsigned id = 0U; id < nstreams; id++) {
// Poll device for IN packets in streaming test?
ctx->stream[id].retrieve = retrieve;
// Attempt to sent OUT packets to device in streaming test?
ctx->stream[id].send = send;
// Checking of received data against expected LFSR output
ctx->stream[id].checking = checking;
// Request retrying of IN packets, feigning error
ctx->stream[id].retrying = retrying;
// Endpoints to be used by this stream
ctx->stream[id].ep_in = 1U + id;
ctx->stream[id].ep_out = 1U + id;
// LFSR state for this byte stream
ctx->stream[id].tst_lfsr = USBTST_LFSR_SEED(id);
ctx->stream[id].dpi_lfsr = USBDPI_LFSR_SEED(id);
// LFSR-controlled packet retrying state
ctx->stream[id].retry_lfsr = RETRY_LFSR_SEED(id);
ctx->stream[id].nretries = 0U;
// No received packets
ctx->stream[id].received = NULL;
}
return true;
}
// Check a data packet received from the test software (usbdev_stream_test)
bool stream_data_check(usbdpi_ctx_t *ctx, usbdpi_stream_t *s,
const usbdpi_transfer_t *rx, bool accept) {
assert(rx);
// The byte count _includes_ the DATAx PID and the two CRC bytes
//
// Note: we are expecting a LFSR-generated byte stream, but we do not
// make assumptions about the number or size of the data packets
// that make up the stream
unsigned num_bytes = transfer_length(rx);
unsigned idx = rx->data_start + 1u; // Skip past the DATAx PID
bool ok = false;
// Validate the received packet - data length valid and checksum present
if (num_bytes >= sizeof(rx->data) || idx + 2u > num_bytes) {
printf("[usbdpi] Unexpected/malformed data packet (0x%x 0x%x)\n", idx,
num_bytes);
} else {
// Data field within received packet
const uint8_t *sp = &rx->data[idx];
num_bytes -= 3u;
// Check that the CRC16 checksum of the data field is as expected
uint16_t rx_crc = sp[num_bytes] | (sp[num_bytes + 1u] << 8);
uint16_t crc = CRC16(sp, num_bytes);
if (rx_crc != crc) {
printf("[usbdpi] Mismatched CRC16 0x%04x received, expected 0x%04x\n",
rx_crc, crc);
} else {
// Data toggle synchronization
unsigned pid = ctx->ep_in[s->ep_in].next_data;
if (rx->data[0] == pid) {
// If we've decided to reject this packet then we still check its
// content but we do not advance the data toggle because we're
// pretending that we didn't receive it successfully
if (accept) {
ctx->ep_in[s->ep_in].next_data = DATA_TOGGLE_ADVANCE(pid);
}
// Tentatively acceptable, but we still have to check and report any and
// all mismatched bytes
ok = accept;
}
// Iff running a performance investigation, checking may be undesired
// because it causes us to reject and retry the transmission
if (s->checking) {
// Note: use a local copy of the LFSR so that we can check the data
// field even on those packets that we choose to reject
uint8_t tst_lfsr = s->tst_lfsr;
while (num_bytes-- > 0U) {
uint8_t recvd = *sp++;
if (recvd != tst_lfsr) {
printf(
"[usbdpi] Mismatched data from device 0x%02x, "
"expected 0x%02x\n",
recvd, tst_lfsr);
ok = false;
}
// Advance our local LFSR
tst_lfsr = LFSR_ADVANCE(tst_lfsr);
}
// Update the LFSR only if we've accepted valid data and will not
// be receiving this data again
if (accept && ok) {
s->tst_lfsr = tst_lfsr;
}
} else {
printf("[usbdpi] Warning: Stream data checking disabled\n");
}
}
}
return ok;
}
// Generate a data packet as if it had been received from the device
usbdpi_transfer_t *stream_data_gen(usbdpi_ctx_t *ctx, usbdpi_stream_t *s,
unsigned len) {
usbdpi_transfer_t *tr = transfer_alloc(ctx);
if (tr) {
// Pretend that we have successfully received the packet with the correct
// data toggling...
uint8_t data = ctx->ep_in[s->ep_in].next_data;
ctx->ep_in[s->ep_in].next_data = DATA_TOGGLE_ADVANCE(data);
// ...and that the data is as expected
uint8_t *dp = transfer_data_start(tr, data, len);
for (unsigned idx = 0U; idx < len; idx++) {
dp[idx] = s->tst_lfsr;
s->tst_lfsr = LFSR_ADVANCE(s->tst_lfsr);
}
transfer_data_end(tr, dp + len);
}
return tr;
}
// Process a received data packet to produce a corresponding reply packet
// by XORing our LFSR output with the received data
//
// Note: for now we do this even if the received data mismatches because
// only the CPU software has the capacity to decide upon and report
// test status
usbdpi_transfer_t *stream_data_process(usbdpi_ctx_t *ctx, usbdpi_stream_t *s,
usbdpi_transfer_t *rx) {
// Note: checkStreamData has already been called on this packet
assert(rx);
// The byte count _includes_ the DATAx PID and the two CRC bytes
unsigned num_bytes = rx->num_bytes;
unsigned idx = rx->data_start + 1u; // Skip past the DATAx PID
// Data field within received packet
const uint8_t *sp = &rx->data[idx];
num_bytes -= 3u;
// Allocate a new buffer for the reply
usbdpi_transfer_t *reply = transfer_alloc(ctx);
assert(reply);
// Construct OUT token packet to the target endpoint, using the
// appropriate DATAx PID
const uint8_t ep_out = s->ep_out;
transfer_token(reply, USB_PID_OUT, ctx->dev_address, ep_out);
uint8_t *dp =
transfer_data_start(reply, ctx->ep_out[ep_out].next_data, num_bytes);
assert(dp);
while (num_bytes-- > 0U) {
uint8_t recvd = *sp++;
// Simply XOR the two LFSR-generated streams together
*dp++ = recvd ^ s->dpi_lfsr;
if (verbose) {
printf("[usbdpi] 0x%02x <- 0x%02x ^ 0x%02x\n", *(dp - 1), recvd,
s->dpi_lfsr);
}
// Advance our LFSR
//
// TODO - decide whether we want to do this here; if the device
// responds with a NAK, requiring us to retry, or we decide to
// resend the packet, then we don't want to advance again
s->dpi_lfsr = LFSR_ADVANCE(s->dpi_lfsr);
}
transfer_data_end(reply, dp);
return reply;
}
// Check the stream signature
bool stream_sig_check(usbdpi_ctx_t *ctx, usbdpi_stream_t *s,
usbdpi_transfer_t *rx) {
// Packet should be PID, data field and CRC16
if (transfer_length(rx) == 3U + 0x10U) {
const uint8_t *sig = transfer_data_field(rx);
if (sig) {
// Signature format:
// Bits Description
// 32 head signature
// 8 initial vaue of LFSR
// 8 stream number
// 16 reserved, SBZ
// 32 number of bytes to be transferred
// 32 tail signature
// Note: all 32-bit quantities are in little endian order
uint32_t num_bytes = get_le32(&sig[8]);
if (verbose) {
printf("[usbdpi] Stream signature at %p head 0x%x tail 0x%x\n", sig,
get_le32(&sig[0]), get_le32(&sig[12]));
}
// Basic validation check; words are transmitted in little endian order
if (get_le32(&sig[0]) == STREAM_SIGNATURE_HEAD &&
get_le32(&sig[12]) == STREAM_SIGNATURE_TAIL &&
// sanity check on transfer length, though we rely upon the CPU
// software to send, receive and count the number of bytes
num_bytes > 0U && num_bytes < 0x10000000U && !sig[6] && !sig[7]) {
// Signature includes the initial value of the device-side LFSR
s->tst_lfsr = sig[4];
// Update data toggle
uint8_t pid = transfer_data_pid(rx);
ctx->ep_in[s->ep_in].next_data = DATA_TOGGLE_ADVANCE(pid);
return true;
}
}
}
return false;
}
// Service streaming data (usbdev_stream_test)
void streams_service(usbdpi_ctx_t *ctx) {
if (verbose) {
// printf("[usbdpi] streams_service hostSt %u in %u out %u\n",
// ctx->hostSt,
// ctx->stream_in, ctx->stream_out);
}
// Maximum time for transmission of a packet ought to be circa 80 bytes of
// data, 640 bits. Allowing for bitstuffing this means we need to leave ~800
const unsigned min_time_left = 800U;
switch (ctx->hostSt) {
// --------------------------------------------
// Try to transmit a data packet to the device
// --------------------------------------------
case HS_STARTFRAME:
case HS_STREAMOUT: {
// Decide whether we have enough time within this frame to attempt
// another transmission
uint32_t next_frame = ctx->frame_start + FRAME_INTERVAL;
if ((next_frame - ctx->tick_bits) > min_time_left) { // HACK
unsigned id = out_stream_next(ctx);
usbdpi_stream_t *s = &ctx->stream[id];
if (s->send) {
// Start by trying to transmit a data packet that we've received, if
// any
if (s->received) {
if (ctx->sending) {
transfer_release(ctx, ctx->sending);
}
// Scramble the oldest received packet with our LFSR-generated byte
// stream and send it to the device
usbdpi_transfer_t *reply = stream_data_process(ctx, s, s->received);
transfer_send(ctx, reply);
uint32_t max_bits =
transfer_length(ctx->sending) * 10 + 160; // HACK
ctx->wait = USBDPI_TIMEOUT(ctx, max_bits);
ctx->bus_state = kUsbBulkOut;
ctx->lastrxpid = 0;
ctx->hostSt = HS_WAITACK;
} else {
// Nothing to send, try receiving...
ctx->hostSt = HS_STREAMIN;
}
} else {
// We're not sending anything - discard any received data
while (s->received) {
usbdpi_transfer_t *tr = s->received;
s->received = tr->next;
transfer_release(ctx, tr);
}
ctx->hostSt = HS_STREAMIN;
}
} else {
// Wait until the next bus frame
ctx->hostSt = HS_NEXTFRAME;
}
} break;
// Await acknowledgement of the packet that we just transmitted
case HS_WAITACK:
if (ctx->sending) {
// Forget reference to the buffer we just tried to send; the received
// packet remains in the list of received buffers to try again later
transfer_release(ctx, ctx->sending);
ctx->sending = NULL;
}
if (ctx->bus_state == kUsbBulkOutAck) {
bool proceed = false;
if (verbose) {
printf("[usbdpi] OUT - response is PID 0x%02x from device (%s)\n",
ctx->lastrxpid, decode_pid(ctx->lastrxpid));
}
switch (ctx->lastrxpid) {
case USB_PID_ACK: {
// Transmitted packet was accepted, so we can retire it...
usbdpi_stream_t *s = &ctx->stream[ctx->stream_out];
usbdpi_transfer_t *rx = s->received;
assert(rx);
s->received = rx->next;
transfer_release(ctx, rx);
uint8_t ep_out = s->ep_out;
ctx->ep_out[ep_out].next_data =
DATA_TOGGLE_ADVANCE(ctx->ep_out[ep_out].next_data);
proceed = true;
} break;
// We may receive a NAK from the device if it is unable to receive the
// packet right now
case USB_PID_NAK:
printf(
"[usbdpi] frame 0x%x tick_bits 0x%x NAK received from device\n",
ctx->frame, ctx->tick_bits);
proceed = true;
break;
default:
printf("[usbdpi] Unexpected PID 0x%02x from device (%s)\n",
ctx->lastrxpid, decode_pid(ctx->lastrxpid));
ctx->hostSt = HS_NEXTFRAME;
break;
}
ctx->hostSt = proceed ? HS_STREAMIN : HS_NEXTFRAME;
} else if (ctx->tick_bits >= ctx->wait) {
printf("[usbdpi] Timed out waiting for OUT response\n");
ctx->hostSt = HS_NEXTFRAME;
}
break;
// ---------------------------------------------
// Try to collect a data packet from the device
// ---------------------------------------------
case HS_STREAMIN: {
// Decide whether we have enough time within this frame to attempt
// another fetch
//
// TODO - find out what the host behaviour should be at this point;
// the device must be required to respond within a certain
// time interval, and then the bus transmission speed
// determines the maximum delay
uint32_t next_frame = ctx->frame_start + FRAME_INTERVAL;
if ((next_frame - ctx->tick_bits) > min_time_left) { // HACK
unsigned id = in_stream_next(ctx);
usbdpi_stream_t *s = &ctx->stream[id];
if (s->retrieve) {
// Ensure that a buffer is available for constructing a transfer
usbdpi_transfer_t *tr = ctx->sending;
if (!tr) {
tr = transfer_alloc(ctx);
assert(tr);
ctx->sending = tr;
}
transfer_token(tr, USB_PID_IN, ctx->dev_address, s->ep_in);
transfer_send(ctx, tr);
ctx->bus_state = kUsbBulkInToken;
ctx->hostSt = HS_WAIT_PKT;
ctx->lastrxpid = 0;
} else {
// We're not required to poll for IN data, but if we're sending we
// must still fake the reception of valid packet data because
// the sw test will be expecting valid data
if (s->send && !s->received) {
// For simplicity we just create max length packets
const unsigned len = USBDEV_MAX_PACKET_SIZE;
s->received = stream_data_gen(ctx, s, len);
}
ctx->hostSt = HS_STREAMOUT;
}
} else {
// Wait until the next bus frame
ctx->hostSt = HS_NEXTFRAME;
}
} break;
case HS_WAIT_PKT:
// Wait max time for a response + packet
ctx->wait = ctx->tick_bits + 18 + 8 + 8 + 64 * 8 + 16;
ctx->hostSt = HS_ACKIFDATA;
break;
case HS_ACKIFDATA:
if (ctx->bus_state == kUsbBulkInData && ctx->recving) {
// We have a response from the device
switch (ctx->lastrxpid) {
case USB_PID_DATA0:
case USB_PID_DATA1: {
// Steal the received packet; it belongs to the stream
usbdpi_transfer_t *rx = ctx->recving;
ctx->recving = NULL;
// Decide whether we want to ACK or NAK this packet
unsigned id = ctx->stream_in;
usbdpi_stream_t *s = &ctx->stream[id];
bool accept = false;
if (s->retrying && s->nretries) {
s->nretries--;
} else {
// Decide the number of retries for the next data packet
// Note: by randomizing the number of retries, rather than
// independently deciding each accept/reject, we guarantee an
// upper bound on the run time
switch (s->retry_lfsr & 7U) {
case 7U:
s->nretries = 3U;
break;
case 6U:
case 5U:
s->nretries = 2U;
break;
case 4U:
s->nretries = 1U;
break;
default:
s->nretries = 0U;
break;
}
s->retry_lfsr = LFSR_ADVANCE(s->retry_lfsr);
accept = true;
}
if (!accept) {
printf("[usbdpi] Requesting resend of data\n");
usb_monitor_log(ctx->mon, "[usbdpi] Requesting resend of data\n");
}
if (expect_sig && !s->sig_recvd) {
// Note: the stream signature is primarily of use on a physical
// USB connection to a host since the endpoint to port mapping is
// variable. With t-l sim we can rely upon the first packet being
// the signature and nothing else
accept = stream_sig_check(ctx, s, rx);
transfer_release(ctx, rx);
// TODO - run time error, signal test failure to the software
assert(accept);
if (accept) {
s->sig_recvd = true;
}
} else {
if (stream_data_check(ctx, s, rx, accept)) {
// Collect the received packets in preparation for later
// transmission with modification back to the device
usbdpi_transfer_t *tr = s->received;
if (tr) {
while (tr->next)
tr = tr->next;
tr->next = rx;
} else {
s->received = rx;
}
} else {
transfer_release(ctx, rx);
accept = false;
}
}
usbdpi_transfer_t *tr = ctx->sending;
assert(tr);
transfer_status(ctx, tr, accept ? USB_PID_ACK : USB_PID_NAK);
ctx->hostSt = HS_STREAMOUT;
} break;
case USB_PID_NAK:
// No data available
ctx->hostSt = HS_STREAMOUT;
break;
default:
printf("[usbdpi] Unexpected PID 0x%02x from device (%s)\n",
ctx->lastrxpid, decode_pid(ctx->lastrxpid));
ctx->hostSt = HS_NEXTFRAME;
break;
}
} else if (ctx->tick_bits >= ctx->wait) {
printf("[usbdpi] Timed out waiting for IN response\n");
ctx->hostSt = HS_NEXTFRAME;
}
break;
default:
break;
}
}