| // 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; |
| } |