|  | // Copyright lowRISC contributors. | 
|  | // Licensed under the Apache License, Version 2.0, see LICENSE for details. | 
|  | // SPDX-License-Identifier: Apache-2.0 | 
|  | // | 
|  | // USB streaming data test | 
|  | // | 
|  | // This test requires interaction with the USB DPI model or a test application | 
|  | // on the USB host. The test initializes the USB device and configures a set of | 
|  | // endpoints for data streaming using bulk transfers. | 
|  | // | 
|  | // The DPI model mimicks a USB host. After device initialization, it detects | 
|  | // the assertion of the pullup and first assigns an address to the device. | 
|  | // For this test it will then repeatedly fetch data via IN requests to | 
|  | // each stream and propagate that data to the corresponding OUT endpoints. | 
|  | // | 
|  | // The data itself is pseudo-randomly generated by the sender and, | 
|  | // independently, by the receiving code to check that the data has been | 
|  | // propagated unmodified and without data loss, corruption, replication etc. | 
|  |  | 
|  | #include "sw/device/lib/dif/dif_pinmux.h" | 
|  | #include "sw/device/lib/runtime/log.h" | 
|  | #include "sw/device/lib/runtime/print.h" | 
|  | #include "sw/device/lib/testing/pinmux_testutils.h" | 
|  | #include "sw/device/lib/testing/test_framework/check.h" | 
|  | #include "sw/device/lib/testing/test_framework/ottf_main.h" | 
|  | #include "sw/device/lib/testing/usb_testutils.h" | 
|  | #include "sw/device/lib/testing/usb_testutils_controlep.h" | 
|  |  | 
|  | #include "hw/top_earlgrey/sw/autogen/top_earlgrey.h"  // Generated. | 
|  |  | 
|  | // Maximum number of concurrent streams | 
|  | #ifdef USBDEV_NUM_ENDPOINTS | 
|  | // Endpoint zero implements the default control pipe | 
|  | #define STREAMS_MAX (USBDEV_NUM_ENDPOINTS - 1U) | 
|  | #else | 
|  | #define STREAMS_MAX 11U | 
|  | #endif | 
|  |  | 
|  | // TODO - currently we are unable to send the configuration descriptor | 
|  | // if we try to describe more than two bidirectional endpoints | 
|  | #if STREAMS_MAX > 2U | 
|  | #undef STREAMS_MAX | 
|  | #define STREAMS_MAX 2U | 
|  | #endif | 
|  |  | 
|  | // Number of streams to be tested | 
|  | #ifndef NUM_STREAMS | 
|  | #define NUM_STREAMS STREAMS_MAX | 
|  | #endif | 
|  |  | 
|  | // Maximum number of buffer simultaneously awaiting transmission | 
|  | // (we must leave some available for packet reception) | 
|  | #ifndef MAX_TX_BUFFERS | 
|  | #define MAX_TX_BUFFERS 24U | 
|  | #endif | 
|  |  | 
|  | // This takes about 256s presently with 10MHz CPU in CW310 FPGA and physical | 
|  | // USB with randomized packet sizes and the default memcpy implementation; | 
|  | // The _MEM_FASTER switch drops the run time to 187s | 
|  | #define TRANSFER_BYTES_FPGA (0x10U << 20) | 
|  |  | 
|  | // This is appropriate for a Verilator chip simulation with 15 min timeout | 
|  | #define TRANSFER_BYTES_VERILATOR 0x2400U | 
|  |  | 
|  | // This is about the amount that we can transfer within a 1 hour 'eternal' test | 
|  | //#define TRANSFER_BYTES_LONG (0xD0U << 20) | 
|  |  | 
|  | // Stream signature words | 
|  | #define STREAM_SIGNATURE_HEAD 0x579EA01AU | 
|  | #define STREAM_SIGNATURE_TAIL 0x160AE975U | 
|  |  | 
|  | // 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) | 
|  |  | 
|  | // Buffer size randomization | 
|  | #define BUFSZ_LFSR_SEED(s) (uint8_t)(0x17U + (s)*7U) | 
|  |  | 
|  | // Simple LFSR for 8-bit sequences | 
|  | // Note: zero is an isolated state that shall be avoided | 
|  | #define LFSR_ADVANCE(lfsr) \ | 
|  | (((lfsr) << 1) ^         \ | 
|  | ((((lfsr) >> 1) ^ ((lfsr) >> 2) ^ ((lfsr) >> 3) ^ ((lfsr) >> 7)) & 1U)) | 
|  |  | 
|  | // Forward declaration to context state | 
|  | typedef struct usbdev_stream_test_ctx usbdev_stream_test_ctx_t; | 
|  |  | 
|  | /** | 
|  | * Stream signature | 
|  | * Note: this needs to be transferred over a byte stream | 
|  | */ | 
|  | typedef struct __attribute__((packed)) usbdev_stream_sig { | 
|  | /** | 
|  | * Head signature word | 
|  | */ | 
|  | uint32_t head_sig; | 
|  | /** | 
|  | * Initial value of LFSR | 
|  | */ | 
|  | uint8_t init_lfsr; | 
|  | /** | 
|  | * Stream number | 
|  | */ | 
|  | uint8_t stream; | 
|  | /** | 
|  | * Reserved fields; should be zero | 
|  | */ | 
|  | uint8_t reserved1; | 
|  | uint8_t reserved2; | 
|  | /** | 
|  | * Number of bytes to be transferred | 
|  | */ | 
|  | uint32_t num_bytes; | 
|  | /** | 
|  | * Tail signature word | 
|  | */ | 
|  | uint32_t tail_sig; | 
|  | } usbdev_stream_sig_t; | 
|  |  | 
|  | // Sanity check because the host-side code relies upon the same structure | 
|  | static_assert(sizeof(usbdev_stream_sig_t) == 0x10U, | 
|  | "Host-side code relies upon signature structure"); | 
|  |  | 
|  | /** | 
|  | * Context state for a single stream | 
|  | */ | 
|  | typedef struct usbdev_stream { | 
|  | /** | 
|  | * Pointer to test context; callback functions receive only stream pointer | 
|  | */ | 
|  | usbdev_stream_test_ctx_t *ctx; | 
|  | /** | 
|  | * Stream IDentifier | 
|  | */ | 
|  | uint8_t id; | 
|  | /** | 
|  | * Has the stream signature been sent yet? | 
|  | */ | 
|  | bool sent_sig; | 
|  | /** | 
|  | * USB device endpoint being used for data transmission | 
|  | */ | 
|  | uint8_t tx_ep; | 
|  | /** | 
|  | * Transmission Linear Feedback Shift Register (for PRND data generation) | 
|  | */ | 
|  | uint8_t tx_lfsr; | 
|  | /** | 
|  | * Total number of bytes presented to the USB device for transmission | 
|  | */ | 
|  | uint32_t tx_bytes; | 
|  | /** | 
|  | * Transmission-side LFSR for selection of buffer size | 
|  | */ | 
|  | uint8_t tx_buf_size; | 
|  |  | 
|  | /** | 
|  | * USB device endpoint being used for data reception | 
|  | */ | 
|  | uint8_t rx_ep; | 
|  | /** | 
|  | * Reception-side LFSR state (mirrors USBDPI generation of PRND data) | 
|  | */ | 
|  | uint8_t rx_lfsr; | 
|  | /** | 
|  | * Reception-side shadow of transmission LFSR | 
|  | */ | 
|  | uint8_t rxtx_lfsr; | 
|  | /** | 
|  | * Total number of bytes received from the USB device | 
|  | */ | 
|  | uint32_t rx_bytes; | 
|  | /** | 
|  | * Size of transfer in bytes | 
|  | */ | 
|  | uint32_t transfer_bytes; | 
|  | } usbdev_stream_t; | 
|  |  | 
|  | /** | 
|  | * Context state for streaming test | 
|  | */ | 
|  | struct usbdev_stream_test_ctx { | 
|  | /** | 
|  | * Context pointer | 
|  | */ | 
|  | usb_testutils_ctx_t *usbdev; | 
|  | /** | 
|  | * State information for each of the test streams | 
|  | */ | 
|  | usbdev_stream_t streams[STREAMS_MAX]; | 
|  | /** | 
|  | * Per-endpoint limits on the number of buffers that may be queued for | 
|  | * transmission | 
|  | */ | 
|  | uint8_t tx_bufs_limit[USBDEV_NUM_ENDPOINTS]; | 
|  | /** | 
|  | * Per-endpoint counts of completed buffers queued for transmission | 
|  | */ | 
|  | uint8_t tx_bufs_queued[USBDEV_NUM_ENDPOINTS]; | 
|  | /** | 
|  | * Total number of completed buffers | 
|  | */ | 
|  | uint8_t tx_queued_total; | 
|  | /** | 
|  | * Buffers that have been filled but cannot yet be presented for transmission | 
|  | * TODO - perhaps absorb the buffer queuing into usb_testutils because the dif | 
|  | * API is explicitly not robust against back-to-back sending of multiple | 
|  | * buffers to a single endpoint, and because the read performance is reliant | 
|  | * upon having additional buffer(s) already available for immediate | 
|  | * presentation | 
|  | */ | 
|  | // 12 X 24 X 4 (or 8?)( BYTES... could perhaps simplify this at some point | 
|  | dif_usbdev_buffer_t tx_bufs[USBDEV_NUM_ENDPOINTS][MAX_TX_BUFFERS]; | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * Configuration values for USB. | 
|  | * TODO - dynamically construct a config descriptor appropriate to the test; | 
|  | *        this would avoid creating unusable ports on the host and also provide | 
|  | *        a little more testing | 
|  | */ | 
|  | static const uint8_t config_descriptors[] = { | 
|  | USB_CFG_DSCR_HEAD(USB_CFG_DSCR_LEN + STREAMS_MAX * (USB_INTERFACE_DSCR_LEN + | 
|  | 2 * USB_EP_DSCR_LEN), | 
|  | STREAMS_MAX), | 
|  |  | 
|  | VEND_INTERFACE_DSCR(0, 2, 0x50, 1), | 
|  | USB_BULK_EP_DSCR(0, 1U, USBDEV_MAX_PACKET_SIZE, 0), | 
|  | USB_BULK_EP_DSCR(1, 1U, USBDEV_MAX_PACKET_SIZE, 0), | 
|  |  | 
|  | VEND_INTERFACE_DSCR(1, 2, 0x50, 1), | 
|  | USB_BULK_EP_DSCR(0, 2U, USBDEV_MAX_PACKET_SIZE, 0), | 
|  | USB_BULK_EP_DSCR(1, 2U, USBDEV_MAX_PACKET_SIZE, 0), | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * Test descriptor | 
|  | */ | 
|  | static const uint8_t test_descriptor[] = { | 
|  | USB_TESTUTILS_TEST_DSCR(1, NUM_STREAMS | 0xF0U, 0, 0, 0)}; | 
|  |  | 
|  | /** | 
|  | * USB device context types. | 
|  | */ | 
|  | static usb_testutils_ctx_t usbdev; | 
|  | static usb_testutils_controlep_ctx_t usbdev_control; | 
|  |  | 
|  | /** | 
|  | * Pinmux handle | 
|  | */ | 
|  | static dif_pinmux_t pinmux; | 
|  |  | 
|  | /** | 
|  | * State information for streaming data test | 
|  | */ | 
|  | static usbdev_stream_test_ctx_t stream_test; | 
|  |  | 
|  | /** | 
|  | * Specify whether to perform verbose logging, for visibility | 
|  | *   (Note that this substantially alters the timing of interactions with the | 
|  | * DPI model and will increase the simulation time) | 
|  | */ | 
|  | static bool verbose = false; | 
|  |  | 
|  | /** | 
|  | * Send only maximal length packets? | 
|  | * (important for performance measurements on the USB, but obviously undesirable | 
|  | *  for testing reliability/function) | 
|  | */ | 
|  | static bool max_packets = false; | 
|  |  | 
|  | /** | 
|  | * Number of streams to be created | 
|  | */ | 
|  | static const unsigned nstreams = NUM_STREAMS; | 
|  |  | 
|  | /** | 
|  | * Diagnostic logging; expensive | 
|  | */ | 
|  | static bool log_traffic = false; | 
|  |  | 
|  | // Dump a sequence of bytes as hexadecimal and ASCII for diagnostic purposes | 
|  | static void buffer_dump(const uint8_t *data, size_t n) { | 
|  | base_hexdump_fmt_t fmt = { | 
|  | .bytes_per_word = 1, | 
|  | .words_per_line = 0x20u, | 
|  | .alphabet = &kBaseHexdumpDefaultFmtAlphabet, | 
|  | }; | 
|  |  | 
|  | base_hexdump_with(fmt, (char *)data, n); | 
|  | } | 
|  |  | 
|  | // Create a stream signature buffer | 
|  | static uint32_t buffer_sig_create(usbdev_stream_t *s, | 
|  | dif_usbdev_buffer_t *buf) { | 
|  | usbdev_stream_sig_t sig; | 
|  |  | 
|  | sig.head_sig = STREAM_SIGNATURE_HEAD; | 
|  | sig.init_lfsr = s->tx_lfsr; | 
|  | sig.stream = s->id; | 
|  | sig.reserved1 = 0U; | 
|  | sig.reserved2 = 0U; | 
|  | sig.num_bytes = s->transfer_bytes; | 
|  | sig.tail_sig = STREAM_SIGNATURE_TAIL; | 
|  |  | 
|  | size_t bytes_written; | 
|  | CHECK_DIF_OK(dif_usbdev_buffer_write(usbdev.dev, buf, (uint8_t *)&sig, | 
|  | sizeof(sig), &bytes_written)); | 
|  | CHECK(bytes_written == sizeof(sig)); | 
|  |  | 
|  | // Note: stream signature is not included in the count of bytes transferred | 
|  |  | 
|  | return bytes_written; | 
|  | } | 
|  |  | 
|  | // Fill a buffer with LFSR-generated data | 
|  | static void buffer_fill(usbdev_stream_t *s, dif_usbdev_buffer_t *buf, | 
|  | uint8_t num_bytes) { | 
|  | alignas(uint32_t) uint8_t data[USBDEV_MAX_PACKET_SIZE]; | 
|  |  | 
|  | CHECK(num_bytes <= buf->remaining_bytes); | 
|  | CHECK(num_bytes <= sizeof(data)); | 
|  |  | 
|  | if (true) { | 
|  | // Emit LFSR-generated byte stream; keep this brief so that we can | 
|  | // reduce our latency in responding to USB events (usb_testutils employs | 
|  | // polling at present) | 
|  | uint8_t lfsr = s->tx_lfsr; | 
|  |  | 
|  | const uint8_t *edp = &data[num_bytes]; | 
|  | uint8_t *dp = data; | 
|  | while (dp < edp) { | 
|  | *dp++ = lfsr; | 
|  | lfsr = LFSR_ADVANCE(lfsr); | 
|  | } | 
|  |  | 
|  | // Update the LFSR for the next packet | 
|  | s->tx_lfsr = lfsr; | 
|  | } else { | 
|  | // Undefined buffer contents; useful for profiling IN throughput on | 
|  | // CW310, because the CPU load at 10MHz can be an appreciable slowdown | 
|  | } | 
|  |  | 
|  | if (verbose && log_traffic) { | 
|  | buffer_dump(data, num_bytes); | 
|  | } | 
|  |  | 
|  | size_t bytes_written; | 
|  |  | 
|  | CHECK_DIF_OK(dif_usbdev_buffer_write(usbdev.dev, buf, data, num_bytes, | 
|  | &bytes_written)); | 
|  | CHECK(bytes_written == num_bytes); | 
|  | s->tx_bytes += bytes_written; | 
|  | } | 
|  |  | 
|  | // Check the contents of a received buffer | 
|  | static void buffer_check(usbdev_stream_test_ctx_t *ctx, usbdev_stream_t *s, | 
|  | dif_usbdev_rx_packet_info_t packet_info, | 
|  | dif_usbdev_buffer_t buf) { | 
|  | usb_testutils_ctx_t *usbdev = ctx->usbdev; | 
|  | uint8_t len = packet_info.length; | 
|  |  | 
|  | if (len > 0) { | 
|  | alignas(uint32_t) uint8_t data[USBDEV_MAX_PACKET_SIZE]; | 
|  |  | 
|  | CHECK(len <= sizeof(data)); | 
|  |  | 
|  | size_t bytes_read; | 
|  |  | 
|  | // Notes: the buffer being read here is USBDEV memory accessed as MMIO, so | 
|  | //        only the DIF accesses it directly. when we consume the final bytes | 
|  | //        from the read buffer, it is automatically returned to the buffer | 
|  | //        pool. | 
|  | CHECK_DIF_OK(dif_usbdev_buffer_read(usbdev->dev, usbdev->buffer_pool, &buf, | 
|  | data, len, &bytes_read)); | 
|  | CHECK(bytes_read == len); | 
|  |  | 
|  | if (log_traffic) { | 
|  | buffer_dump(data, bytes_read); | 
|  | } | 
|  |  | 
|  | // Check received data against expected LFSR-generated byte stream; | 
|  | // keep this brief so that we can reduce our latency in responding to | 
|  | // USB events (usb_testutils employs polling at present) | 
|  | uint8_t rxtx_lfsr = s->rxtx_lfsr; | 
|  | uint8_t rx_lfsr = s->rx_lfsr; | 
|  |  | 
|  | const uint8_t *esp = &data[bytes_read]; | 
|  | const uint8_t *sp = data; | 
|  | while (sp < esp) { | 
|  | // Received data should be the XOR of two LFSR-generated PRND streams - | 
|  | // ours on the | 
|  | //   transmission side, and that of the DPI model | 
|  | uint8_t expected = rxtx_lfsr ^ rx_lfsr; | 
|  | CHECK(expected == *sp, | 
|  | "S%u: Unexpected received data 0x%02x : (LFSRs 0x%02x 0x%02x)", | 
|  | s->id, *sp, rxtx_lfsr, rx_lfsr); | 
|  |  | 
|  | rxtx_lfsr = LFSR_ADVANCE(rxtx_lfsr); | 
|  | rx_lfsr = LFSR_ADVANCE(rx_lfsr); | 
|  | sp++; | 
|  | } | 
|  |  | 
|  | // Update the LFSRs for the next packet | 
|  | s->rxtx_lfsr = rxtx_lfsr; | 
|  | s->rx_lfsr = rx_lfsr; | 
|  | } else { | 
|  | // In the event that we've received a zero-length data packet, we still | 
|  | // must return the buffer to the pool | 
|  | CHECK_DIF_OK( | 
|  | dif_usbdev_buffer_return(usbdev->dev, usbdev->buffer_pool, &buf)); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Callback for successful buffer transmission | 
|  | static void strm_tx_done(void *stream_v) { | 
|  | usbdev_stream_t *s = (usbdev_stream_t *)stream_v; | 
|  | usbdev_stream_test_ctx_t *ctx = s->ctx; | 
|  | usb_testutils_ctx_t *usbdev = ctx->usbdev; | 
|  |  | 
|  | // If we do not have at least one queued buffer then something has gone wrong | 
|  | // and this callback is inappropriate | 
|  | uint8_t tx_ep = s->tx_ep; | 
|  | uint8_t nqueued = ctx->tx_bufs_queued[tx_ep]; | 
|  |  | 
|  | if (verbose) { | 
|  | LOG_INFO("strm_tx_done called. %u (%u total) buffers(s) are queued", | 
|  | nqueued, ctx->tx_queued_total); | 
|  | } | 
|  |  | 
|  | CHECK(nqueued > 0); | 
|  |  | 
|  | // Note: since buffer transmission and completion signalling both occur within | 
|  | // the foreground code (polling, not interrupt-driven) there is no issue of | 
|  | // potential races here | 
|  |  | 
|  | if (nqueued > 0) { | 
|  | // Shuffle the buffer descriptions, without using memmove | 
|  | for (unsigned idx = 1u; idx < nqueued; idx++) { | 
|  | ctx->tx_bufs[tx_ep][idx - 1u] = ctx->tx_bufs[tx_ep][idx]; | 
|  | } | 
|  |  | 
|  | // Is there another buffer ready to be transmitted? | 
|  | ctx->tx_queued_total--; | 
|  | ctx->tx_bufs_queued[tx_ep] = --nqueued; | 
|  |  | 
|  | if (nqueued) { | 
|  | CHECK_DIF_OK( | 
|  | dif_usbdev_send(usbdev->dev, tx_ep, &ctx->tx_bufs[tx_ep][0u])); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Callback for buffer reception | 
|  | static void strm_rx(void *stream_v, dif_usbdev_rx_packet_info_t packet_info, | 
|  | dif_usbdev_buffer_t buf) { | 
|  | usbdev_stream_t *s = (usbdev_stream_t *)stream_v; | 
|  | usbdev_stream_test_ctx_t *ctx = s->ctx; | 
|  | usb_testutils_ctx_t *usbdev = ctx->usbdev; | 
|  |  | 
|  | CHECK(packet_info.endpoint == s->rx_ep); | 
|  |  | 
|  | // We do not expect to receive SETUP packets to this endpoint | 
|  | CHECK(!packet_info.is_setup); | 
|  |  | 
|  | if (verbose) { | 
|  | LOG_INFO("Stream %u: Received buffer of %u bytes(s)", s->id, | 
|  | packet_info.length); | 
|  | } | 
|  |  | 
|  | if (true) { | 
|  | buffer_check(ctx, s, packet_info, buf); | 
|  | } else { | 
|  | // Note: this is just test code for measuring the OUT throughput | 
|  | usb_testutils_ctx_t *usbdev = ctx->usbdev; | 
|  | CHECK_DIF_OK( | 
|  | dif_usbdev_buffer_return(usbdev->dev, usbdev->buffer_pool, &buf)); | 
|  | } | 
|  |  | 
|  | s->rx_bytes += packet_info.length; | 
|  | } | 
|  |  | 
|  | // Callback for unexpected data reception (IN endpoint) | 
|  | static void rx_show(void *stream_v, dif_usbdev_rx_packet_info_t packet_info, | 
|  | dif_usbdev_buffer_t buf) { | 
|  | usbdev_stream_t *s = (usbdev_stream_t *)stream_v; | 
|  | usbdev_stream_test_ctx_t *ctx = s->ctx; | 
|  | usb_testutils_ctx_t *usbdev = ctx->usbdev; | 
|  | uint8_t data[0x100U]; | 
|  | size_t bytes_read; | 
|  | CHECK_DIF_OK(dif_usbdev_buffer_read(usbdev->dev, usbdev->buffer_pool, &buf, | 
|  | data, packet_info.length, &bytes_read)); | 
|  | LOG_INFO("rx_show packet of %u byte(s) - read %u", packet_info.length, | 
|  | bytes_read); | 
|  | buffer_dump(data, bytes_read); | 
|  | } | 
|  |  | 
|  | // Returns an indication of whether a stream has completed its data transfer | 
|  | bool stream_completed(const usbdev_stream_t *s) { | 
|  | return (s->tx_bytes >= s->transfer_bytes) && | 
|  | (s->rx_bytes >= s->transfer_bytes); | 
|  | } | 
|  |  | 
|  | // Initialise a stream, preparing it for use | 
|  | static void stream_init(usbdev_stream_test_ctx_t *ctx, usbdev_stream_t *s, | 
|  | uint8_t id, uint8_t ep_in, uint8_t ep_out, | 
|  | uint32_t transfer_bytes) { | 
|  | // We need to be able to locate the test context given only the stream | 
|  | // pointer within the strm_tx_done callback from usb_testutils | 
|  | s->ctx = ctx; | 
|  |  | 
|  | // Remember the stream IDentifier | 
|  | s->id = id; | 
|  |  | 
|  | // Not yet sent stream signature | 
|  | s->sent_sig = false; | 
|  |  | 
|  | // Initialise the transfer state | 
|  | s->tx_bytes = 0u; | 
|  | s->rx_bytes = 0u; | 
|  | s->transfer_bytes = transfer_bytes; | 
|  |  | 
|  | // Initialise the LFSR state for transmission and reception sides | 
|  | // - we use a simple LFSR to generate a PRND stream to transmit to the USBPI | 
|  | // - the USBDPI XORs the received data with another LFSR-generated stream of | 
|  | //   its own, and transmits the result back to us | 
|  | // - to check the returned data, our reception code mimics both LFSRs | 
|  | s->tx_lfsr = USBTST_LFSR_SEED(id); | 
|  | s->rxtx_lfsr = s->tx_lfsr; | 
|  | s->rx_lfsr = USBDPI_LFSR_SEED(id); | 
|  |  | 
|  | // Packet size randomization | 
|  | s->tx_buf_size = BUFSZ_LFSR_SEED(id); | 
|  |  | 
|  | // Set up the endpoint for IN transfers (TO host) | 
|  | // | 
|  | // Note: We install the rx_show handler to catch any misdirected data | 
|  | // transfers | 
|  | void (*rx)(void *, dif_usbdev_rx_packet_info_t, dif_usbdev_buffer_t) = | 
|  | (ep_in == ep_out) ? strm_rx : rx_show; | 
|  |  | 
|  | s->tx_ep = ep_in; | 
|  | usb_testutils_endpoint_setup(ctx->usbdev, ep_in, kUsbdevOutStream, s, | 
|  | strm_tx_done, rx, NULL, NULL); | 
|  | s->rx_ep = ep_out; | 
|  | if (ep_out != ep_in) { | 
|  | // Set up the endpoint for OUT transfers (FROM host) | 
|  | usb_testutils_endpoint_setup(ctx->usbdev, ep_out, kUsbdevOutStream, s, NULL, | 
|  | strm_rx, NULL, NULL); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Service the given stream, preparing and/or sending any data that we can; | 
|  | // data reception is handled via callbacks and requires no attention here | 
|  | static void stream_service(usbdev_stream_test_ctx_t *ctx, usbdev_stream_t *s) { | 
|  | // Generate output data as soon as possible and make it available for | 
|  | //   collection by the host | 
|  |  | 
|  | uint8_t tx_ep = s->tx_ep; | 
|  | uint8_t nqueued = ctx->tx_bufs_queued[tx_ep]; | 
|  |  | 
|  | if (s->tx_bytes < s->transfer_bytes &&        // More bytes to transfer? | 
|  | nqueued < ctx->tx_bufs_limit[tx_ep] &&    // Endpoint allowed buffer? | 
|  | ctx->tx_queued_total < MAX_TX_BUFFERS) {  // Total buffers not exceeded? | 
|  | dif_usbdev_buffer_t buf; | 
|  |  | 
|  | // See whether we can populate another buffer yet | 
|  | dif_result_t dif_result = | 
|  | dif_usbdev_buffer_request(usbdev.dev, usbdev.buffer_pool, &buf); | 
|  | if (dif_result == kDifOk) { | 
|  | // This is just for reporting the number of buffers presented to the | 
|  | // USB device, as a progress indicator | 
|  | static unsigned bufs_sent = 0u; | 
|  | uint32_t num_bytes; | 
|  |  | 
|  | if (s->sent_sig) { | 
|  | if (max_packets) { | 
|  | num_bytes = USBDEV_MAX_PACKET_SIZE; | 
|  | } else { | 
|  | // Vary the amount of data sent per buffer | 
|  | num_bytes = s->tx_buf_size % (USBDEV_MAX_PACKET_SIZE + 1u); | 
|  | s->tx_buf_size = LFSR_ADVANCE(s->tx_buf_size); | 
|  | } | 
|  | uint32_t tx_left = s->transfer_bytes - s->tx_bytes; | 
|  | if (num_bytes > tx_left) | 
|  | num_bytes = tx_left; | 
|  |  | 
|  | buffer_fill(s, &buf, num_bytes); | 
|  | } else { | 
|  | // Construct a signature to send to the host-side software, | 
|  | // identifying the stream and its properties | 
|  | num_bytes = buffer_sig_create(s, &buf); | 
|  | s->sent_sig = true; | 
|  | } | 
|  |  | 
|  | // Remember the buffer until we're informed that it has been | 
|  | // successfully transmitted | 
|  | // | 
|  | // Note: since the 'tx_done' callback occurs from foreground code that | 
|  | // is polling, there is no issue of interrupt races here | 
|  | ctx->tx_bufs[tx_ep][nqueued] = buf; | 
|  | ctx->tx_bufs_queued[tx_ep] = ++nqueued; | 
|  | ctx->tx_queued_total++; | 
|  |  | 
|  | // Can we present this buffer for transmission yet? | 
|  | if (nqueued <= 1U) { | 
|  | CHECK_DIF_OK(dif_usbdev_send(usbdev.dev, tx_ep, &buf)); | 
|  | } | 
|  |  | 
|  | if (verbose) { | 
|  | LOG_INFO( | 
|  | "Stream %u: %uth buffer (of 0x%x byte(s)) awaiting transmission", | 
|  | s->id, bufs_sent, num_bytes); | 
|  | } | 
|  | bufs_sent++; | 
|  | } else { | 
|  | // If we have no more buffers available right now, continue polling... | 
|  | CHECK(dif_result == kDifUnavailable); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | OTTF_DEFINE_TEST_CONFIG(); | 
|  |  | 
|  | bool test_main(void) { | 
|  | // Context state for streaming test | 
|  | usbdev_stream_test_ctx_t *ctx = &stream_test; | 
|  |  | 
|  | CHECK(kDeviceType == kDeviceSimVerilator || kDeviceType == kDeviceFpgaCw310, | 
|  | "This test is not expected to run on platforms other than the " | 
|  | "Verilator simulation or CW310 FPGA. It needs logic on the host side " | 
|  | "to retrieve, scramble and return the generated byte stream"); | 
|  |  | 
|  | LOG_INFO("Running USBDEV Stream Test"); | 
|  |  | 
|  | // Check we can support the requested number of streams | 
|  | CHECK(nstreams && nstreams < USBDEV_NUM_ENDPOINTS); | 
|  |  | 
|  | // Decide upon the number of bytes to be transferred for the entire test | 
|  | uint32_t transfer_bytes = TRANSFER_BYTES_FPGA; | 
|  | if (kDeviceType == kDeviceSimVerilator) { | 
|  | transfer_bytes = TRANSFER_BYTES_VERILATOR; | 
|  | } | 
|  | transfer_bytes = (transfer_bytes + nstreams - 1) / nstreams; | 
|  | LOG_INFO(" - %u stream(s), 0x%x bytes each", nstreams, transfer_bytes); | 
|  |  | 
|  | CHECK_DIF_OK(dif_pinmux_init( | 
|  | mmio_region_from_addr(TOP_EARLGREY_PINMUX_AON_BASE_ADDR), &pinmux)); | 
|  | pinmux_testutils_init(&pinmux); | 
|  | CHECK_DIF_OK(dif_pinmux_input_select( | 
|  | &pinmux, kTopEarlgreyPinmuxPeripheralInUsbdevSense, | 
|  | kTopEarlgreyPinmuxInselIoc7)); | 
|  |  | 
|  | // Remember context state for usb_testutils context | 
|  | ctx->usbdev = &usbdev; | 
|  |  | 
|  | // Call `usbdev_init` here so that DPI will not start until the | 
|  | // simulation has finished all of the printing, which takes a while | 
|  | // if `--trace` was passed in. | 
|  | usb_testutils_init(ctx->usbdev, /*pinflip=*/false, /*en_diff_rcvr=*/false, | 
|  | /*tx_use_d_se0=*/false); | 
|  | usb_testutils_controlep_init(&usbdev_control, ctx->usbdev, 0, | 
|  | config_descriptors, sizeof(config_descriptors), | 
|  | test_descriptor, sizeof(test_descriptor)); | 
|  | while (usbdev_control.device_state != kUsbTestutilsDeviceConfigured) { | 
|  | usb_testutils_poll(ctx->usbdev); | 
|  | } | 
|  |  | 
|  | // Initialise the state of each stream | 
|  | for (unsigned id = 0U; id < nstreams; id++) { | 
|  | // Which endpoint are we using for the IN transfers to the host? | 
|  | const uint8_t ep_in = 1u + id; | 
|  | // Which endpoint are we using for the OUT transfers from the host? | 
|  | const uint8_t ep_out = 1u + id; | 
|  | stream_init(ctx, &ctx->streams[id], id, ep_in, ep_out, transfer_bytes); | 
|  | } | 
|  |  | 
|  | // Decide how many buffers each endpoint may queue up for transmission; | 
|  | // we must ensure that there are buffers available for reception, and we | 
|  | // do not want any endpoint to starve another | 
|  | for (unsigned s = 0U; s < nstreams; s++) { | 
|  | // This is slightly overspending the available buffers, leaving the | 
|  | //   endpoints to vie for the final few buffers, so it's important that | 
|  | //   we limit the total number of buffers across all endpoints too | 
|  | unsigned ep = ctx->streams[s].tx_ep; | 
|  | ctx->tx_bufs_queued[ep] = 0U; | 
|  | ctx->tx_bufs_limit[ep] = (MAX_TX_BUFFERS + nstreams - 1) / nstreams; | 
|  | } | 
|  | ctx->tx_queued_total = 0U; | 
|  |  | 
|  | if (verbose) { | 
|  | LOG_INFO("Commencing data transfer..."); | 
|  | } | 
|  |  | 
|  | bool done = false; | 
|  | do { | 
|  | for (unsigned s = 0U; s < nstreams; s++) { | 
|  | stream_service(ctx, &ctx->streams[s]); | 
|  |  | 
|  | // We must keep polling regularly in order to handle detection of packet | 
|  | // transmission as well as perform packet reception and checking | 
|  | usb_testutils_poll(ctx->usbdev); | 
|  | } | 
|  |  | 
|  | // See whether any streams still have more work to do | 
|  | unsigned s = 0U; | 
|  | while (s < nstreams && stream_completed(&ctx->streams[s])) { | 
|  | s++; | 
|  | } | 
|  | done = (s >= nstreams); | 
|  | } while (!done); | 
|  |  | 
|  | // Determine the total counts of bytes sent and received | 
|  | uint32_t tx_bytes = 0U; | 
|  | uint32_t rx_bytes = 0U; | 
|  | for (unsigned s = 0U; s < nstreams; s++) { | 
|  | tx_bytes += ctx->streams[s].tx_bytes; | 
|  | rx_bytes += ctx->streams[s].rx_bytes; | 
|  | } | 
|  |  | 
|  | LOG_INFO("USB sent 0x%x byte(s), received and checked 0x%x byte(s)", tx_bytes, | 
|  | rx_bytes); | 
|  |  | 
|  | CHECK(tx_bytes == nstreams * transfer_bytes, | 
|  | "Unexpected count of byte(s) sent to USB host"); | 
|  |  | 
|  | return true; | 
|  | } |