[usbdev] Host-side streaming code Host-side component of the usbdev streaming test (usbdev_stream_test) for use with a physical host. This commit provides implementations of Bulk Transfers using either serial port file descriptors with byte- level granularity, or libusb-managed packet-level Bulk Transfers. Change-Id: I52c110d6003e7b7edd366f078acdbf2496e7c423 Signed-off-by: Adrian Lees <a.lees@lowrisc.org>
diff --git a/sw/host/tests/usbdev/usbdev_stream/README.md b/sw/host/tests/usbdev/usbdev_stream/README.md new file mode 100644 index 0000000..5688720 --- /dev/null +++ b/sw/host/tests/usbdev/usbdev_stream/README.md
@@ -0,0 +1,175 @@ +# Host-side streaming software + +To verify the USB device (USBDEV) with a physical USB host requires special-purpose software to +communicate with the device, checking all of the data transferred. This `stream_test` program forms +the host-side component of the verification effort, complementing the following device-side tests: + +- usbdev_stream_test + - Device-side software that streams Bulk Transfer data to/from multiple endpoints, generating the + source data using a LFSR, and then checking the data returned by the USB host against the + device-side predictions. + +- usbdev_iso_test + - Derived from `usbdev_stream_test` and sharing a common body of code, this test employs + Isochronous transfers instead of Bulk Transfers. Isochronous Transfers have the important + distinction that the packet stream is inherently unreliable and there is no acknowledgement or + retrying of bad packets for Isochronous Transfers. Instead, this transfer type prioritizes the + timely delivery of new data over any retrying and error detection. + - Since the USB guarantees the bandwidth available to Isochronous endpoints, it is not possible + to implement the full complement of 12 supported endpoints in this test, and instead only + four concurrent streams are used. This allows the test to operate with a number of intervening + hubs between the USB host and device, without suffering from a failure to guarantee the + bandwidth requirements. + +- usbdev_mixed_test + - This test is more comprehensive than the above two tests in that it tests a combination of + Bulk Transfers, Interrupt Transfers and Isochronous Transfers concurrently. + +This host-side component typically uses `libusb` in order to verify all of the four Transfer Types +supported by the USB: + +- Bulk Transfers +- Isochronous Transfers +- Interrupt Transfers +- Control Transfers + +The `stream_test` program is also able to communicate over serial port connections using the +regular file I/O API of the OS. If `libusb` is not available on the host platform, then the +host-side software may be built with the macro 'STREAMTEST_LIBUSB' undefined or set to 0, allowing +direct serial port access to be used on other Linux or Linux-like hosts. + +## Device identification and the 'USBDevice' class + +When the 'stream_test' software has been built with `libusb` support, it is capable of locating and +identifying the USB device automatically by searching for a connected device having the expected +Vendor and Product IDs. If the device is connected and the device-side test software has configured +the USB device in response to the configuration requests from the USB host, it should be detected +by `stream_test`, and testing will commence. + +If there is a connection issue or the configuration sequence does not completely successfully, +there may be some useful diagnostic information available via `sudo dmesg -w` for a Linux host. + +``` +[101210.703949] usb 3-9.3: new full-speed USB device number 78 using xhci_hcd +[101210.854843] usb 3-9.3: New USB device found, idVendor=18d1, idProduct=503a, bcdDevice= 1.00 +[101210.854862] usb 3-9.3: New USB device strings: Mfr=0, Product=0, SerialNumber=0 +[101210.921072] usb_serial_simple 3-9.3:1.0: google converter detected +[101210.921480] usb 3-9.3: google converter now attached to ttyUSB0 +... +``` + +The start of the normal device identification and configuration is shown above. The +`usbdev_stream_test` test software presents the device in a manner that makes it suitable for +use by the 'usb-serial-simple' driver for Bulk Transfer communications. This makes an endpoint pair +behave two reliable, unidirectional, serial byte streams without any requirement for configuration +commands, flow control or handshaking etc. + +## USBDPI model + +Since verification of USBDEV is also performed using DV simulation (at both chip- and block-level) +and Verilator top-level simulation, there is a counterpart implementation of the 'stream_test' +functionality within the USBDPI model. This employs the same, umodified device-side tests and +offers greater control over the USB traffic than may be achieved using a physical host. + +## Test Descriptor + +Having located the USBDEV using `libusb` the host-side code will attempt to read a test descriptor +from the device-side test code using a 'Vendor Specific' Control Transfer. This is a bespoke +command that is interpreted by the device-side test software, and which returns a description of +the functionality required of the streaming code, including the number of streams to be tested +concurrently, and the transfer type of each of the streams. + +## Stream signatures + +The host-side code initiates communications with each of the device-side endpoints by claiming the +interface for exclusive use and performing an initial read from the IN endpoint. The start of the +received data stream is expected to consist of a stream signature that specifies the properties +of the stream, including the initial value of the LFSR used to generate the pseudo-random byte +stream. + +## LFSR-based data generation + +The device-side test software has one LFSR implementation for each of its streams, and uses these +to generate the byte streams. Since the host-side code is informed of the initial state of each LFSR +via the stream signatures, and employs the same algoriths, the host-side code may check the received +data for correctness and thus detect any transmission errors and signal integrity issues on the USB. + +Having verified the received bytes against expectations using its own model of the device-side LFSR, +the host-side code in `stream_test` then combines each byte with the output from its own LFSR for +that stream, before sending the results stream back to the device. This resulting byte stream is +simply the bytewise XOR of the device-side LFSR output and the host-side LFSR output. + +Upon receipt of data from the USB host, the device-side test software will check the received +data against its own prediction of the XOR of the two LFSR outputs, thus verifying the correct, +error-free transmission of data from host to device. + +## Circular Buffer Implementation + +Within the `USBDevStream` base class, from which the other stream types derive, there is an +implementation of a circular buffer using statically-allocated storage within the USBDevStream +instance itself. This is a relatively small buffer that keeps the byte stream flowing from and to +the device, without the need for additional data movement. + +For the serial port implementation in 'USBDevSerial' this is a generalized byte stream with the +amount of data being received/transmitted having byte-level granularity. For the other stream +implementations the transfers are packet-based, in accordance with the interface exposed by +`libusb` and it is therefore necessary to anticipate the recept of a maximum-length packet, +since is the device-side test software that determines the individual, randomized sizes of the +packets, rather than the USB requests from the host. + +To this end, in addition to the usual 'read' and 'write' indexes into the circular buffer +implementation, there is an 'end' index which tracks the current used size of the circular buffer, +allowing the buffer contents to wrap prematurely in the event that another maximum-length packet +cannot be accommodated contiguously. + +## Special considerations for Isochronous streams + +Since Isochronous Transfers prioritize the timely delivery or recent data over the reliable, +error-free delivery of all data, Isochronous packets are subject to be dropped from the byte +stream at any point, but particularly in the OUT direction (host to device) since the device-side +software is running on much slower hardware than the host-side code. Additionally, the USB host +controls the USB so packets that are requested will usually have sufficient space available +for storage. + +Packets transmitted to the USB device may be dropped silently when there is no storage space +available, or in the event of data corruption occurring on the USB. As such the streaming code +on the device side populates the first part of each Isochronous packet with a stream signature +that indicates both the sequence number of the packet and the initial value of the device-side +LFSR for that packet. This allows the USB host to detect the disappearance of packets on the IN +side (device to host), and to run both its mirror of the device-side LFSR and its own host-side LFSR +forwards to resynchronize. + +One further point to note regarding tests that employ Isochronous Transfers is that the host-side +`stream_test` code is unable to ascertain when all data has been transferred, because it receives +no guarantee that transmitted data has arrived at the USB device. + +Since the device-side tests software is ultimately responsible for deciding the pass/fail status +of the test, the test framework on the host shall be expected to terminate the `stream_test` when +a verdict has been reached. + +## Suspend/Resume testing + +In addition to the normal streaming and checking operations of the `stream_test` software, it may +be used to exercise the Suspend/Resume behavior of the USB device. The USB device has a companion +module called `usbdev_aon_wake` that may be used to keep the bus alive and monitor events occurring +on the USB whilst most of the chip enters a lower power 'sleep' state. + +In order for a Linux host to put a USB device to perform Suspend signaling, which indicates to the +device that it is presently not required to communicate and may enter a low power mode, all file +handles including those held by the `libusb` code must be closed. + +To initiate a Suspend operation, the `stream_test` software will therefore temporarily cease the +scheduling of new transfers on any stream, and await the completion of existing transfers, +before then 'closing' the streams. Note that the USBDevStream instances remain extant, and that +their internal state remains intact for use after resuming; in particular, communication must +continue from the point at which the stream was suspended without packet loss. + +After an appropriate interval in the Suspended state, `stream_test` will then invoke 'Resume()' on +each of the streams in turn, causing them to reestablish communication with the USBDEV. Each of +the streams continues from the point of suspension with all LFSR state and byte counts unmodified. + +Note that it may prove necessary to run the OpenTitanTool test harness and the `stream_test` +host-side code on separate hosts to test this behavior meaningfully, because something within the +standard USB driver stack on a Linux host causes USB devices to be reawakened from their Suspended +state every few seconds, making it impossible to put the USBDEV into a low power sleep state for +any prolonged period.
diff --git a/sw/host/tests/usbdev/usbdev_stream/stream_test.cc b/sw/host/tests/usbdev/usbdev_stream/stream_test.cc new file mode 100644 index 0000000..5f6d1c5 --- /dev/null +++ b/sw/host/tests/usbdev/usbdev_stream/stream_test.cc
@@ -0,0 +1,580 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 +// +// USB streaming data test +// +// Linux host-side application that receives a stream of LFSR-generated data +// from the USB device, checks the received bytestream and then XORs it with a +// host-side LFSR-generated byte stream to transmit back to the device. +// +// By default the streaming test expects a number of USB serial connections to +// the target device, one port per endpoint: +// +// /dev/ttyUSB0 - supplies and receives LFSR-generated byte stream for one/ +// the only endpoint +// /dev/ttyUSB1 - a secondary stream +// /dev/ttyUSB.. +// +// Note that the mapping from device endpoints to USB port number is not +// guaranteed, and when multiple streams are used, it is _not_ necessarily the +// case that ascending streams/endpoints in usbdev_stream_test are mapped to +// a contiguous range of ascending ttyUSBi port names. +// +// Either or both of the initial input port and the initial output port may be +// overridden using command line parameters. +// +// Usage: +// stream [-v<bool>][-c<bool>][-r<bool>][-s<bool>] +// [[-d<bus>:<address>] | [--device <bus>:<address>]] +// [<input port>[ <output port>]] +// +// --device programmatically specify a particular USB device by bus number +// and device address (see 'lsusb' output). +// +// -c check any retrieved data against expectations +// -d specify a particular USB device by bus number and device address +// -r retrieve data from device +// -s send data to device +// -t use serial ports (ttyUSBx) in preference to libusb Bulk Transfer +// streams for usbdev_stream_test +// -v verbose reporting +// -z perform suspend-resume signaling throughout the test +// +// <bool> values may be 0,1,n or y, and they default to 1. +// +// Build without libusb dependency: +// eg. g++ -Wall -Werror -o stream_test *.cc +#include "stream_test.h" + +#include <cassert> +#include <cctype> +#include <cerrno> +#include <cinttypes> +#include <cstdbool> +#include <cstddef> +#include <cstdint> +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <ctime> +#include <iostream> +#include <sys/time.h> + +#include "usb_device.h" +#if STREAMTEST_LIBUSB +#include "usbdev_int.h" +#endif +#include "usbdev_serial.h" +#include "usbdev_utils.h" + +// Test properties +// +// 16MiB takes about 40s presently with no appreciable CPU activity on the CW310 +// (ie. undefined transmitted data, and no checking of received data) but ca. +// 152s with LFSR generation and checking across all of the 11 streams possible. +// +// Note: in normal use such as regression tests, the stream signatures will +// override the specified transfer amount. +constexpr uint32_t kTransferBytes = (0x10U << 20); + +// Has any data yet been received from the device? +bool received = false; + +// Time of first data reception. +uint64_t start_time = 0U; + +// Configuration settings for the test. +TestConfig cfg(false, // Not verbose + true, // Retrieve data from the device + true, // Check the retrieved data + true); // Send modified data to the device + +static USBDevice dev; + +// State information for each of the streams. +static USBDevStream *streams[STREAMS_MAX]; + +// Parse a command line option and return boolean value. +static bool GetBool(const char *p); + +// Construct a modified port name for the next stream. +static void PortNext(char *next, size_t n, const char *curr); + +// Parse a command line option and return boolean value. +bool GetBool(const char *p) { + return (*p == '1') || (tolower(*p) == 'y') || (*p == '\r') || (*p == '\n') || + (*p == '\0'); +} + +// Parse a command line option, retrieving a byte and indicating +// success/failure. +bool GetByte(const char **pp, uint8_t &byte) { + const char *p = *pp; + if (isdigit(*p)) { + uint32_t n = 0u; + do { + n = (n * 10) + *p++ - '0'; + } while (n < 0x100u && isdigit(*p)); + if (n < 0x100u) { + byte = (uint8_t)n; + *pp = p; + return true; + } + } + return false; +} + +// Parse a command line option specifying the bus number and device address. +bool GetDevice(const char *p, uint8_t &busNumber, uint8_t &devAddress) { + return GetByte(&p, busNumber) && (*p++ == ':') && GetByte(&p, devAddress) && + *p == '\0'; +} + +// Construct a modified port name for the next stream. +void PortNext(char *next, size_t n, const char *curr) { + // We're expecting a port name of the form '/dev/ttyUSB<n>' + if (curr != next) { + strncpy(next, curr, n); + } + while (*next != '\0') { + if (isdigit(*next)) { + int port = atoi(next); + snprintf(next, n, "%u", (unsigned)port + 1U); + break; + } + next++; + n--; + } +} + +// Report command line syntax. +void ReportSyntax(void) { + fputs( + "Usage:\n" + " stream [-n<streams>][-v<bool>][-c<bool>][-r<bool>][-s<bool>][-t][-z]\n" + " [[-d<bus>:<address>] | [--device <bus>:<address>]]\n" + " [<input port>[ <output port>]]" + "\n\n" + " --device programmatically specify a particular USB device by bus\n" + " number and device address (see 'lsusb' output).\n" + "\n\n" + " -c check any retrieved data against expectations\n" + " -d specify a particular USB device by bus number" + " and device address\n" + " -r retrieve data from device\n" + " -s send data to device\n" + " -t use serial ports (ttyUSBx) in preference to libusb Bulk\n" + " Transfer streams for usbdev_stream_test\n" + " -v verbose reporting\n" + " -z perform suspend-resume signaling throughout the test" + "\n\n" + " <bool> values may be 0,1,n or y, and they default to 1\n", + stderr); +} + +static int RunTest(USBDevice *dev, const char *in_port, const char *out_port) { + // We need to modify the port names for each non-initial stream. + char out_name[FILENAME_MAX]; + char in_name[FILENAME_MAX]; + + // Collect the test number and the test arguments so that we may ascertain + // the transfer type of each of the streams. + uint8_t testNum = dev->TestNumber(); + uint8_t testArg[4]; + for (unsigned arg = 0U; arg < 4U; arg++) { + testArg[arg] = dev->TestArg(arg); + } + + // Determine the number of streams from the test descriptor; the device-side + // software supplies the stream count. + unsigned nstreams = 2U; + switch (testNum) { + case USBDevice::kUsbTestNumberStreams: + case USBDevice::kUsbTestNumberIso: + case USBDevice::kUsbTestNumberMixed: + // The lower nibble of the first test argument specifies the stream count + // in these test descriptions. + nstreams = testArg[0] & 0xfU; + break; + // Other tests default to 2 Bulk streams. + default: + nstreams = 2U; + break; + } + + // Decide upon the number of bytes to be transferred for the entire test. + uint32_t transfer_bytes = kTransferBytes; + transfer_bytes = (transfer_bytes + nstreams - 1) / nstreams; + if (cfg.verbose) { + std::cout << " - " << nstreams << " stream(s), 0x" << std::hex + << transfer_bytes << std::dec << " bytes each" << std::endl; + } + + // Initialize all streams. + for (unsigned idx = 0U; idx < nstreams; idx++) { + USBDevStream::StreamType streamType; + + switch (testNum) { + case USBDevice::kUsbTestNumberStreams: + // For the basic streaming test where all active endpoints are using + // Bulk Transfer types, we may either use the ttyUSBn serial port + // interface or we may use libusb. + // + // In the former case we cannot support suspend-resume testing because + // data will get buffered somewhere within the software layers and + // lost when the file descriptors are closed and opened. + if (cfg.serial && !cfg.suspending) { + streamType = USBDevStream::StreamType_Serial; + } else { + streamType = USBDevStream::StreamType_Bulk; + } + break; + case USBDevice::kUsbTestNumberIso: + streamType = USBDevStream::StreamType_Isochronous; + break; + case USBDevice::kUsbTestNumberMixed: { + uint32_t mixedTypes = + (testArg[3] << 16) | (testArg[2] << 8) | testArg[1]; + // Two bits per stream specify the stream/transfer type in terms of the + // USB standard endpoint types. + switch ((mixedTypes >> (idx * 2)) & 3U) { + case 0U: + streamType = USBDevStream::StreamType_Control; + break; + case 1U: + streamType = USBDevStream::StreamType_Isochronous; + break; + case 2U: + streamType = USBDevStream::StreamType_Bulk; + break; + default: + streamType = USBDevStream::StreamType_Interrupt; + break; + } + } break; + // Other tests default to 2 Bulk streams. + default: + streamType = USBDevStream::StreamType_Bulk; + break; + } + + std::cout << "S" << idx << ": " << USBDevStream::StreamTypeName(streamType) + << std::endl; + + bool opened(false); +#if STREAMTEST_LIBUSB + bool bulk(true); +#endif + switch (streamType) { + case USBDevStream::StreamType_Serial: { + USBDevSerial *s; + s = new USBDevSerial(idx, transfer_bytes, cfg.retrieve, cfg.check, + cfg.send, cfg.verbose); + if (s) { + opened = s->Open(in_port, out_port); + if (opened) { + streams[idx] = s; + + // Modify the port name for the next stream. + PortNext(out_name, sizeof(out_name), out_port); + PortNext(in_name, sizeof(in_name), in_port); + out_port = out_name; + in_port = in_name; + } + } + } break; + +#if STREAMTEST_LIBUSB + case USBDevStream::StreamType_Interrupt: + bulk = false; + // no break; Bulk Transfers are handled identically to Interrupt + // Transfers. + case USBDevStream::StreamType_Bulk: { + USBDevInt *interrupt; + interrupt = new USBDevInt(dev, bulk, idx, transfer_bytes, cfg.retrieve, + cfg.check, cfg.send, cfg.verbose); + if (interrupt) { + opened = interrupt->Open(idx); + if (opened) { + streams[idx] = interrupt; + } + } + } break; +#endif + default: + assert(!"Unrecognised/invalid stream type"); + break; + } + + if (!opened) { + std::cerr << "Failed to open stream" << std::endl; + if (idx > 0U) { + do { + idx--; + delete streams[idx]; + } while (idx > 0U); + } + return 1; + } + } + + std::cout << "Streaming...\r" << std::flush; + + // Times are in microseconds. + constexpr uint32_t kRunInterval = 5 * 1000000; // Running before suspending. + constexpr uint32_t kSuspendingInterval = 5 * 1000; // Suspending. + constexpr uint32_t kSuspendedInterval = 5 * 1000000; // Device is suspended. + // Resume Signaling shall occur for at least 20ms but we have no control. + // over its duration, so there's little point trying to communicate sooner. + constexpr uint32_t kResumeInterval = 30 * 1000; // Resuming before traffic. + uint64_t start_time = time_us(); + uint32_t prev_bytes = 0; + bool done = false; + do { + uint32_t total_bytes = 0U; + uint32_t total_recv = 0U; + uint32_t total_sent = 0U; + bool failed = false; + + done = false; + switch (dev->CurrentState()) { + case USBDevice::StateStreaming: + done = true; + for (unsigned idx = 0U; idx < nstreams; idx++) { + // Service this stream. + if (!streams[idx]->Service()) { + failed = true; + break; + } + + // Update the running totals. + total_bytes += streams[idx]->TransferBytes(); + total_recv += streams[idx]->BytesRecvd(); + total_sent += streams[idx]->BytesSent(); + + // Has the stream completed all its work yet? + if (!streams[idx]->Completed()) { + done = false; + } + } + + // Initiate transition to Suspended. + if (cfg.suspending && elapsed_time(start_time) >= kRunInterval) { + std::cout << "Waiting to suspend" << std::endl; + // Notify all of the streams that no more traffic shall be initiated. + for (unsigned idx = 0U; idx < nstreams; idx++) { + streams[idx]->Pause(); + } + if (true) { + // Initiate autosuspend. + dev->Suspend(); + // Start of Suspending interval. + start_time = time_us(); + } else { + // TODO: There remains an issue in which the host-side software + // apparently fails to complete one or more transfers, which + // prevents us from properly resuming the transfers after the device + // has cycled through suspend-resume; this code persists in order + // to demonstrate and further investigate that. + // + // At the time the issue appeared to be in the behavior of the + // driver stack/libusb. + + std::cout << "Attempting to resume" << std::endl; + // Notify all of the streams that no more traffic shall be + // initiated. + for (unsigned idx = 0U; idx < nstreams; idx++) { + streams[idx]->Resume(); + } + std::cout << "Resuming streaming..." << std::endl; + // Start of Running interval. + start_time = time_us(); + } + } + break; + + // Put the device into Suspended for a while. + case USBDevice::StateSuspending: + if (elapsed_time(start_time) >= kSuspendingInterval) { + dev->SetState(USBDevice::StateSuspended); + // Start of Suspended interval. + start_time = time_us(); + std::cout << "Suspended" << std::endl; + } + break; + + case USBDevice::StateSuspended: + if (elapsed_time(start_time) >= kSuspendedInterval) { + dev->Resume(); + // Start of Resuming interval. + start_time = time_us(); + } + break; + + case USBDevice::StateResuming: + if (elapsed_time(start_time) >= kResumeInterval) { + for (unsigned idx = 0U; idx < nstreams; idx++) { + streams[idx]->Resume(); + } + + dev->SetState(USBDevice::StateStreaming); + // Start of Running interval. + start_time = time_us(); + } + break; + } + + // Service the USBDevice to keep USB transfers flowing. + if (!failed) { + failed = !dev->Service(); + } + + // Tidy up if something went wrong. + if (failed) { + for (unsigned idx = 0U; idx < nstreams; idx++) { + (void)streams[idx]->Stop(); + } + return 3; + } + + // Down counting of the number of bytes remaining to be transferred. + if (std::abs((int32_t)total_sent - (int32_t)prev_bytes) >= 0x1000 || done) { + // Note: if there are Isochronous streams present then the bytes left + // count may hit zero some time before the test completes on the device + // side because packet delivery is not guaranteed. + uint32_t bytes_left = + (total_sent < total_bytes) ? (total_bytes - total_sent) : 0U; + std::cout << "Bytes received: 0x" << std::hex << total_recv + << " -- Left to send: 0x" << bytes_left << " \r" + << std::dec << std::flush; + prev_bytes = total_sent; + } + } while (!done); + + uint64_t elapsed_time = time_us() - start_time; + + // Report time elapsed from the start of data transfer. + for (unsigned idx = 0U; idx < nstreams; idx++) { + streams[idx]->Stop(); + } + + // TODO: introduce a crude estimate of the performance being achieved, + // for profiling the performance of IN and OUT traffic; totals and individual + // endpoints? + double elapsed_secs = elapsed_time / 1e6; + printf("Test completed in %.2lf seconds (%" PRIu64 "us)\n", elapsed_secs, + elapsed_time); + + return 0; +} + +int main(int argc, char *argv[]) { + const uint16_t kVendorID = 0x18d1u; + const uint16_t kProductID = 0x503au; + const char *out_port = nullptr; + const char *in_port = nullptr; + uint8_t devAddress = 0u; + uint8_t busNumber = 0u; + + cfg.override_flags = false; + + // Collect options and alternative port names. + for (int i = 1; i < argc; i++) { + if (argv[i][0] == '-') { + switch (tolower(argv[i][1])) { + case 'c': + cfg.check = GetBool(&argv[i][2]); + cfg.override_flags = true; + break; + case 'd': + if (!GetDevice(&argv[i][2], busNumber, devAddress)) { + std::cerr << "ERROR: Unrecognised option '" << argv[i] << "'" + << std::endl; + ReportSyntax(); + return 7; + } + break; + case 'r': + cfg.retrieve = GetBool(&argv[i][2]); + cfg.override_flags = true; + break; + case 's': + cfg.send = GetBool(&argv[i][2]); + cfg.override_flags = true; + break; + case 't': + cfg.serial = GetBool(&argv[i][2]); + break; + case 'v': + cfg.verbose = GetBool(&argv[i][2]); + break; + case 'z': + cfg.suspending = GetBool(&argv[i][2]); + break; + case '-': + // The bus/address may be specified programmatically as '--device' + // with confidence that this parameter/syntax will not change. + if (!strcmp(&argv[i][2], "device") && i < argc - 1) { + // The next argument should be 'bus:address' + if (GetDevice(argv[++i], busNumber, devAddress)) { + break; + } + } + // no break + default: + std::cerr << "ERROR: Unrecognised option '" << argv[i] << "'" + << std::endl; + ReportSyntax(); + return 6; + } + } else if (!out_port) { + out_port = argv[i]; + } else if (!in_port) { + in_port = argv[i]; + } else { + std::cerr << "ERROR: Parameter '" << argv[i] << "' unrecognised" + << std::endl; + ReportSyntax(); + return 7; + } + } + + // Furnish test with default port names. + if (!out_port) { + out_port = "/dev/ttyUSB0"; + } + if (!in_port) { + in_port = "/dev/ttyUSB0"; + } + + std::cout << "USB Streaming Test" << std::endl + << " (host-side implementation of usbdev streaming tests)" + << std::endl; + + // Locate the USB device using Vendor and Product IDs, and optionally a + // specific device address and bus number to handle the presence of multiple + // similar devices. + USBDevice dev(cfg.verbose); + if (!dev.Init(kVendorID, kProductID, devAddress, busNumber)) { + return 2; + } + + if (!dev.Open()) { + dev.Fin(); + return 3; + } + + // Read a vendor-specific test descriptor from the device-side software in + // order to ascertain the test configuration and required behavior. + if (!dev.ReadTestDesc()) { + dev.Fin(); + return 3; + } + + int rc = RunTest(&dev, in_port, out_port); + + dev.Fin(); + + return rc; +}
diff --git a/sw/host/tests/usbdev/usbdev_stream/stream_test.h b/sw/host/tests/usbdev/usbdev_stream/stream_test.h new file mode 100644 index 0000000..2337476 --- /dev/null +++ b/sw/host/tests/usbdev/usbdev_stream/stream_test.h
@@ -0,0 +1,79 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 +#ifndef OPENTITAN_SW_HOST_TESTS_USBDEV_USBDEV_STREAM_STREAM_TEST_H_ +#define OPENTITAN_SW_HOST_TESTS_USBDEV_USBDEV_STREAM_STREAM_TEST_H_ +#include <cstdint> + +// Maximum number of concurrent streams, leaving at least one endpoint for +// the Default Control Pipe. +#ifdef USBDEV_NUM_ENDPOINTS +#define STREAMS_MAX (USBDEV_NUM_ENDPOINTS - 1U) +#else +#define STREAMS_MAX 11U +#endif + +/** + * Simple container for test configuration. + */ +struct TestConfig { + /** + * Initialize test settings; see descriptions below. + */ + TestConfig(bool init_verb, bool init_retr, bool init_chk, bool init_send) + : verbose(init_verb), + retrieve(init_retr), + check(init_chk), + send(init_send), +#if STREAMTEST_LIBUSB + serial(false), +#else + serial(true), +#endif + suspending(false) { + } + /** + * Verbose logging/diagnostic reporting. + */ + bool verbose; + /** + * Override the stream flags using the command line arguments? + */ + bool override_flags; + /** + * Retrieve data from the device. + */ + bool retrieve; + /** + * Check the retrieved data against expectations. + */ + bool check; + /** + * Send data to the device. + */ + bool send; + /** + * Favor serial ports over libusb for Bulk Transfer streams? + */ + bool serial; + /** + * Are we performing suspend-resume testing whilst streaming? + */ + bool suspending; +}; + +// Has any data yet been received from the device? +extern bool received; + +// Time of first data reception. +extern uint64_t start_time; + +// Configuration settings for the test. +extern TestConfig cfg; + +/** + * Report command line syntax. + */ +void ReportSyntax(void); + +#endif // OPENTITAN_SW_HOST_TESTS_USBDEV_USBDEV_STREAM_STREAM_TEST_H_
diff --git a/sw/host/tests/usbdev/usbdev_stream/usb_device.cc b/sw/host/tests/usbdev/usbdev_stream/usb_device.cc new file mode 100644 index 0000000..123c46c --- /dev/null +++ b/sw/host/tests/usbdev/usbdev_stream/usb_device.cc
@@ -0,0 +1,364 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 +#include "usb_device.h" + +#include <cassert> +#include <cstring> +#include <fcntl.h> +#include <iostream> +#include <linux/usbdevice_fs.h> +#include <unistd.h> + +#include "usbdev_utils.h" + +// Initialize USB access, with intent to use a device with the given properties. +bool USBDevice::Init(uint16_t vendorID, uint16_t productID, uint8_t devAddress, + uint8_t busNumber) { +#if STREAMTEST_LIBUSB + // Initialize libusb + int rc = libusb_init(&ctx_); + if (rc < 0) { + return ErrorUSB("ERROR: Initializing libusb", rc); + } + + // Remember vendor and product IDs because the device may be opened and + // closed many times during the test. + vendorID_ = vendorID; + productID_ = productID; + + // Remember the specified bus number and device address. + addrSpec_ = devAddress; + busSpec_ = busNumber; +#endif + return true; +} + +// Finalize use of the device. +bool USBDevice::Fin() { + (void)Close(); +#if STREAMTEST_LIBUSB + libusb_exit(ctx_); +#endif + return true; +} + +// Open the device, if not already open. +bool USBDevice::Open() { + // Check whether we have already opened the device. + if (devh_) { + return true; + } + +#if STREAMTEST_LIBUSB + // Locate our USB device. + std::cout << "Locating USB device" << std::endl; + unsigned numTries = 30u; + bool found = false; + do { + // No device handle at present. + devh_ = nullptr; + + // We need to traverse a list of all devices before opening it; since + // we require the port numbers leading to our device, we cannot take the + // easier approach of using _open_device_with_vid_pid(). + libusb_device **dev_list; + ssize_t num_devs = libusb_get_device_list(ctx_, &dev_list); + int idx; + for (idx = 0; idx < num_devs; idx++) { + int rc = libusb_get_device_descriptor(dev_list[idx], &devDesc_); + if (rc >= 0) { + if (verbose_) { + std::cout << "Device: " + << "VendorID: " << std::hex << devDesc_.idVendor + << " ProductID: " << devDesc_.idProduct << std::dec + << std::endl; + } + if (devDesc_.idVendor == vendorID_ && + devDesc_.idProduct == productID_) { + // Read device identification; there could be multiple USB devices + // present with the same Vendor and Product IDs. + uint8_t addr = libusb_get_device_address(dev_list[idx]); + uint8_t bus = libusb_get_bus_number(dev_list[idx]); + // A device address of 0 is invalid for an addressed/configured + // device on the USB, and we use this to denote 'no specific physical + // device.' + assert(addr); + + // Filter by bus and address, if specific bus/device required; this + // may be because they were specified explicitly when the program + // started or simply because we're reopening the same device. + if (!addrSpec_ || (addrSpec_ == addr && busSpec_ == bus)) { + // We are interested in this device; remember its location. + busNumber_ = bus; + devAddress_ = addr; + + // Open a handle to our device. + libusb_device *dev = dev_list[idx]; + rc = libusb_open(dev, &devh_); + if (rc < 0) { + std::cerr << "Error opening device " << (int)bus << ":" + << (int)addr << " - " << libusb_error_name(rc) + << std::endl; + // Continue trying other devices; the system could have multiple + // identical devices visible but access permissions may restrict + // which device(s) may be opened. + } else { + // Obtain the list of port numbers; required for suspend/resume. + uint8_t bus = libusb_get_bus_number(dev); + if (verbose_) { + std::cout << "Device path: " << (unsigned)bus << "-"; + } + devPath_ = std::to_string(bus) + '-'; + uint8_t ports[8]; + int rc = libusb_get_port_numbers(dev, ports, sizeof(ports)); + if (rc >= 0) { + unsigned num_ports = (unsigned)rc; + for (unsigned idx = 0u; idx < num_ports; idx++) { + if (verbose_) { + std::cout << (unsigned)ports[idx]; + } + devPath_ += std::to_string(ports[idx]); + if (idx + 1 < num_ports) { + std::cout << '.'; + devPath_ += '.'; + } + } + std::cout << std::endl; + } else { + std::cerr << "Error getting port list: " + << libusb_error_name(rc) << std::endl; + return false; + } + break; + } + } + // else This is not the device you are looking for... + } + } + } + + // Unreference all devices and release device list. + libusb_free_device_list(dev_list, 1u); + + if (devh_) { + // Ensure that if we close and reopen this device, we shall return to the + // same device. + busSpec_ = busNumber_; + addrSpec_ = devAddress_; + found = true; + } else if (numTries-- > 0u) { + // Retry a number of times before reporting failure. + std::cout << '.' << std::flush; + sleep(1); + } else { + std::cerr << "Unable to locate USB device" << std::endl; + return false; + } + } while (!found); + + // Report that we have at least found the device. + std::cout << "Device found (Bus " << (int)busNumber_ << " Device " + << (int)devAddress_ << ")" << std::endl; + if (verbose_) { + std::cout << " - Path: " << devPath_ << std::endl; + } + + // We need to detach the kernel driver and claim the interface to have maximal + // control, eg. suspending device. + int rc = libusb_set_auto_detach_kernel_driver(devh_, 1u); + if (rc < 0) { + std::cerr << "Error detaching kernel driver: " << libusb_error_name(rc) + << std::endl; + return false; + } + + // Read and check the currently active configuration; this should just be 1 + // since our test software sets up only a single configuration. + int config; + rc = libusb_get_configuration(devh_, &config); + if (rc < 0) { + std::cerr << "Error getting configuration: " << libusb_error_name(rc) + << std::endl; + } + + std::cout << "Configuration: " << config << std::endl; +#else + std::cout << "Running without libusb" << std::endl; + devh_ = true; +#endif + return true; +} + +// Close the device, if open. +bool USBDevice::Close() { +#if STREAMTEST_LIBUSB + if (devh_) { + libusb_close(devh_); + devh_ = nullptr; + } +#else + devh_ = false; +#endif + return true; +} + +bool USBDevice::Service() { +#if STREAMTEST_LIBUSB + struct timeval tv = {0}; + int rc = libusb_handle_events_timeout(ctx_, &tv); + if (rc < 0) { + return ErrorUSB("ERROR: Handling events", rc); + } +#endif + return true; +} + +bool USBDevice::ReadTestDesc() { + std::cout << "Reading Test Descriptor" << std::endl; + + if (!Open()) { + return false; + } +#if STREAMTEST_LIBUSB + uint8_t bmRequestType = LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | + LIBUSB_RECIPIENT_ENDPOINT; + std::cout << "req type " << (int)bmRequestType << std::endl; + // Send a Vendor-Specific command to read the test descriptor. + uint8_t testDesc[0x10u]; + int rc = libusb_control_transfer(devh_, bmRequestType, kVendorTestConfig, 0u, + 0u, testDesc, sizeof(testDesc), + kControlTransferTimeout); + if (rc < 0) { + std::cerr << "Error reading test descriptor: " << libusb_error_name(rc) + << std::endl; + return false; + } + + if (verbose_) { + std::cout << "Test Descriptor:" << std::endl; + for (unsigned idx = 0u; idx < sizeof(testDesc); idx++) { + printf("%u: 0x%02x\n", idx, testDesc[idx]); + } + } + + // Validate the received test descriptor. + const uint8_t test_sig_head[] = {0x7eu, 0x57u, 0xc0u, 0xf1u}; + const uint8_t test_sig_tail[] = {0x1fu, 0x0cu, 0x75u, 0xe7u}; + const uint8_t *dp = testDesc; + if (!memcmp(dp, test_sig_head, 4) && 0x10u == get_le16(&dp[4]) && + !memcmp(&dp[12], test_sig_tail, 4)) { + usb_testutils_test_number_t testNum = + (usb_testutils_test_number_t)get_le16(&dp[6]); + + if (verbose_) { + std::cout << "Test number: " << testNum << " args " << std::hex + << (int)dp[8] << " " << (int)dp[9] << " " << (int)dp[10] << " " + << (int)dp[11] << std::dec << std::endl; + } + + // Retain the test number and the test arguments. + testNumber_ = testNum; + testArg_[0] = dp[8]; + testArg_[1] = dp[9]; + testArg_[2] = dp[10]; + testArg_[3] = dp[11]; + return true; + } + + return false; +#else + // Default test configuration, since we are unable to issue Vendor-Specific + // commands. + // - streaming test, 2 bulk streams. + testNumber_ = USBDevice::kUsbTestNumberStreams; + testArg_[0] = USBDevice::kUsbdevStreamFlagsDefault | 2U; + testArg_[1] = 0U; + testArg_[2] = 0U; + testArg_[3] = 0U; + + return true; +#endif +} + +bool USBDevice::Suspend() { + std::cout << "Suspending Device " << devPath_ << std::endl; + + // We need to relinquish our access to the device otherwise the kernel + // will refuse to autosuspend the device! + Close(); + + std::string powerPath = "/sys/bus/usb/devices/" + devPath_ + "/power/"; + std::string filename = powerPath + "autosuspend_delay_ms"; + + int fd = open(filename.c_str(), O_WRONLY); + if (fd < 0) { + std::cerr << "Failed to open '" << filename << "'" << std::endl; + std::cerr << " (Note: this requires super user permissions)" << std::endl; + return false; + } + int rc = write(fd, "0", 1); + if (rc < 0) { + std::cerr << "Write failed" << std::endl; + } + rc = close(fd); + if (rc < 0) { + std::cerr << "Close failed" << std::endl; + } + + // Enable auto-suspend behavior. + filename = powerPath + "control"; + fd = open(filename.c_str(), O_WRONLY); + if (fd < 0) { + std::cerr << "Failed to open '" << filename << "'" << std::endl; + std::cerr << " (Note: this requires super user permissions)" << std::endl; + return false; + } + rc = write(fd, "auto", 4); + if (rc < 0) { + std::cerr << "Write failed" << std::endl; + } + rc = close(fd); + if (rc < 0) { + std::cerr << "Close failed" << std::endl; + } + + SetState(StateSuspending); + return true; +} + +bool USBDevice::Resume() { + std::cout << "Resuming Device" << std::endl; + + std::string powerPath = "/sys/bus/usb/devices/" + devPath_ + "/power/"; + std::string filename = powerPath + "control"; + + int fd = open(filename.c_str(), O_WRONLY); + if (fd < 0) { + std::cerr << "Failed to open '" << filename << "'" << std::endl; + return false; + } + int rc = write(fd, "on", 2); + if (rc < 0) { + std::cerr << "Write failed" << std::endl; + } + close(fd); + + if (!Open()) { + return false; + } + + SetState(StateResuming); + return true; +} + +bool USBDevice::Disconnect() { + // TODO: Are we able to implement a Disconnect/Reconnect function here? + // Most hubs do not have the capacity to power cycle an individual + // port. + // + // Power Off + // Power On + + return false; +}
diff --git a/sw/host/tests/usbdev/usbdev_stream/usb_device.h b/sw/host/tests/usbdev/usbdev_stream/usb_device.h new file mode 100644 index 0000000..5cbb915 --- /dev/null +++ b/sw/host/tests/usbdev/usbdev_stream/usb_device.h
@@ -0,0 +1,355 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 +#ifndef OPENTITAN_SW_HOST_TESTS_USBDEV_USBDEV_STREAM_USB_DEVICE_H_ +#define OPENTITAN_SW_HOST_TESTS_USBDEV_USBDEV_STREAM_USB_DEVICE_H_ +#include <iostream> + +// The 'usbdev_serial' class may be used on platforms where libusb support is +// not available; libusb is required to exercise Isochronous streams, Interrupt +// streams and Control Transfers. +#if STREAMTEST_LIBUSB +#include <libusb-1.0/libusb.h> +#endif + +class USBDevice { + public: +#if STREAMTEST_LIBUSB + USBDevice(bool verbose = false) + : verbose_(verbose), + state_(StateStreaming), + ctx_(nullptr), + devh_(nullptr) {} +#else + USBDevice(bool verbose = false) + : verbose_(verbose), state_(StateStreaming), devh_(false) {} +#endif + + // Device states. + enum USBDevState { + StateStreaming, + StateSuspending, + StateSuspended, + StateResuming + }; + + // DPI test numbers. + typedef enum usb_testutils_test_number { + kUsbTestNumberSmoke = 0, + kUsbTestNumberStreams, + kUsbTestNumberIso, + kUsbTestNumberMixed, + kUsbTestNumberSuspend, + kUsbTestNumberExc, + } usb_testutils_test_number_t; + + // Test/stream flags; these are common with device-side software. + typedef enum { + /** + * Mask for extracting the stream ID number. + */ + kUsbdevStreamFlagID = 0x0FU, + /** + * Host shall retrieve IN data from the device for this stream. + */ + kUsbdevStreamFlagRetrieve = 0x10U, + /** + * Host shall check that IN data matches as expected. + */ + kUsbdevStreamFlagCheck = 0x20U, + /** + * DPI model (or Host) shall retry IN data fetches, where possible. + */ + kUsbdevStreamFlagRetry = 0x40U, + /** + * Host shall send OUT data to the device for this stream. + */ + kUsbdevStreamFlagSend = 0x80U, + /** + * Default stream flags; for non-libusb builds. + */ + kUsbdevStreamFlagsDefault = kUsbdevStreamFlagRetrieve | + kUsbdevStreamFlagCheck | + kUsbdevStreamFlagRetry | kUsbdevStreamFlagSend + } usbdev_stream_flags_t; + + // Vendor-Specific requests. + static constexpr uint8_t kVendorGetData = 0x7du; + static constexpr uint8_t kVendorPutData = 0x7fu; + + // Maximum packet size of 64 bytes is used for Control, Interrupt and Bulk + // Transfers. + static constexpr unsigned kDevDataMaxPacketSize = 0x40U; + + // Our USB device has a maximum packet size of just 64 bytes even for + // Isochronous transfers; this may one day be increased. + static constexpr unsigned kDevIsoMaxPacketSize = 0x40U; + + /** + * Initialize USB device before test; called once at startup. + * + * @param vendorID Vendor ID code of the USB device. + * @param productID Product ID code of the USB device. + * @param devAddress Device address (0 for first accessible device). + * @param busNumber Bus number (if devAddress non-zero). + * @return true iff initialization was successful. + */ + bool Init(uint16_t productID, uint16_t vendorID, uint8_t devAddress = 0u, + uint8_t busNumber = 0u); + /** + * Finalize USB device before completing test. + * + * @return true iff finalization was successful. + */ + bool Fin(); + /** + * (Re)open the device. If the device is already open, this function just + * returns immediately. + * + * @return true iff opening was successful, or the device was already open. + */ + bool Open(); + /** + * Close the device, if open. If the device is already closed, this function + * just returns immediately. + * + * @return true iff closing was successful, or the device was already closed. + */ + bool Close(); + /** + * Service the device; keep libusb transfers being processed. + * + * @return true iff the device is still operational. + */ + bool Service(); + +#if STREAMTEST_LIBUSB + /** + * Claim an interface on the device. + * + * @param interface The interface number of the interface to be claimed. + * @return The result of the operation. + */ + int ClaimInterface(unsigned interface) const { + return libusb_claim_interface(devh_, (int)interface); + } + /** + * Release an interface on the device. + * + * @param interface The interface number of the interface to be claimed. + * @return The result of the operation. + */ + int ReleaseInterface(unsigned interface) const { + return libusb_release_interface(devh_, (int)interface); + } + /** + * Emit a textual report of an error returned by libusb. + * + * @param rc Return code from libusb function. + * @return false as a convenience for indicating failure to caller. + */ + inline bool ErrorUSB(const char *prefix, int rc) { + std::cerr << prefix << " (" << rc << ", " << libusb_error_name(rc) << ")" + << std::endl; + return false; + } + /** + * Retrieve libusb device handle. + * + * @return The device handle, or nullptr if none eg. device not open. + */ + libusb_device_handle *DeviceHandle() const { return devh_; } + /** + * Fill Control Transfer descriptor. + */ + void FillControlTransfer(struct libusb_transfer *xfr, uint8_t ep, + uint8_t *buffer, libusb_transfer_cb_fn callback, + void *user_data, unsigned timeout_ms) { + libusb_fill_control_transfer(xfr, devh_, buffer, callback, user_data, + timeout_ms); + // Complete the endpoint number because we're _not_ always targeting + // Endpoint Zero. + xfr->endpoint = ep; + } + /** + * Fill Bulk Transfer descriptor. + */ + void FillBulkTransfer(struct libusb_transfer *xfr, uint8_t ep, + uint8_t *buffer, size_t len, + libusb_transfer_cb_fn callback, void *user_data, + unsigned timeout_ms) const { + libusb_fill_bulk_transfer(xfr, devh_, ep, buffer, len, callback, user_data, + timeout_ms); + } + /** + * Fill Interrupt Transfer descriptor. + */ + void FillIntTransfer(struct libusb_transfer *xfr, uint8_t ep, uint8_t *buffer, + size_t len, libusb_transfer_cb_fn callback, + void *user_data, unsigned timeout_ms) const { + libusb_fill_interrupt_transfer(xfr, devh_, ep, buffer, len, callback, + user_data, timeout_ms); + } + /** + * Fill Isochronous transfer descriptor, + */ + void FillIsoTransfer(struct libusb_transfer *xfr, uint8_t ep, uint8_t *buffer, + size_t len, unsigned num_iso_packets, + libusb_transfer_cb_fn callback, void *user_data, + unsigned timeout_ms) const { + libusb_fill_iso_transfer(xfr, devh_, ep, buffer, len, num_iso_packets, + callback, user_data, timeout_ms); + } + /** + * Complete the lengths of the packets in this Isochronous transfer, assuming + * that packets are of uniform size. + * + * @param xfr Isochronous transfer to be completed. + * @param len Total length of all packets within the transfer. + */ + void SetIsoPacketLengths(struct libusb_transfer *xfr, unsigned len) const { + libusb_set_iso_packet_lengths(xfr, len); + } + /** + * Allocate a transfer descriptor to be completed by the caller. + * + * @return Pointer to transfer transcript, or NULL if allocation failed. + */ + struct libusb_transfer *AllocTransfer(unsigned iso_packets) const { + return libusb_alloc_transfer((int)iso_packets); + } + /** + * Free an allocated transfer descriptor. + * + * @param xfr The transfer descriptor to be freed. + */ + void FreeTransfer(struct libusb_transfer *xfr) const { + return libusb_free_transfer(xfr); + } + /** + * Submit a transfer for processing. + * + * @param xfr The transfer to be submitted. + */ + int SubmitTransfer(struct libusb_transfer *xfr) const { + return libusb_submit_transfer(xfr); + } + /** + * Cancel a pending transfer. + * + * @param xfr The transfer to be cancelled. + */ + int CancelTransfer(struct libusb_transfer *xfr) const { + return libusb_cancel_transfer(xfr); + } +#endif + + /** + * Read Test Descriptor from the DUT using a Vendor-Specific command. + * + * @return true iff the operation was successful. + */ + bool ReadTestDesc(); + /** + * Return the test number from the test descriptor. + * + * @return test number + */ + uint8_t TestNumber() const { return testNumber_; } + /** + * Return the specified test argument from the test descriptor. + * + * @param arg Argument number. + * @return test argument + */ + uint8_t TestArg(unsigned arg) const { + // Presently all defined tests support up to 4 arguments. + return (arg < 4U) ? testArg_[arg] : 0U; + } + /** + * Reset and Reconfigure the DUT. + * + * @return true iff the operation was successful. + */ + bool Reset(); + /** + * Suspend device. + * + * @return true iff the operation was successful. + */ + bool Suspend(); + /** + * Resume operation of suspended device. + */ + bool Resume(); + /** + * Disconnect and reconnect device. + * + * @param true iff the operation was succesful. + */ + bool Disconnect(); + /** + * Returns the current state of the device. + * + * @return current state. + */ + USBDevState CurrentState() const { return state_; } + /** + * Sets the current state of the device in the Suspend/Resume signaling. + * + * @param state The current state of the device. + */ + void SetState(USBDevState state) { state_ = state; } + + private: + // Verbose logging/reporting. + bool verbose_; + + // Current device state. + USBDevState state_; + + // Vendor ID of USB device. + uint16_t vendorID_; + // Product ID of USB device. + uint16_t productID_; + + // Specified bus number. + uint8_t busSpec_; + // Specified device address. + uint8_t addrSpec_; + + // Bus number of the device we're using. + uint8_t busNumber_; + // (Bus-local) device address of the device we're using. + uint8_t devAddress_; + +#if STREAMTEST_LIBUSB + // Context information for libusb. + libusb_context *ctx_; + + // Device handle. + libusb_device_handle *devh_; + + // Device descriptor. + libusb_device_descriptor devDesc_; +#else + // Device handle; just retain whether open/closed. + bool devh_; +#endif + + // Device path (bus number - ports numbers). + std::string devPath_; + + usb_testutils_test_number_t testNumber_; + + uint8_t testArg_[4]; + + // TODO: We should introduce a timeout on libusb Control Transfers. + static const unsigned kControlTransferTimeout = 0u; + + // Vendor-Specific Commands. + static const uint8_t kVendorTestConfig = 0x7cu; + static const uint8_t kVendorTestStatus = 0x7eu; +}; + +#endif // OPENTITAN_SW_HOST_TESTS_USBDEV_USBDEV_STREAM_USB_DEVICE_H_
diff --git a/sw/host/tests/usbdev/usbdev_stream/usbdev_int.cc b/sw/host/tests/usbdev/usbdev_stream/usbdev_int.cc new file mode 100644 index 0000000..6851081 --- /dev/null +++ b/sw/host/tests/usbdev/usbdev_stream/usbdev_int.cc
@@ -0,0 +1,275 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 +#include "usbdev_int.h" + +#include <cassert> +#include <cstdio> + +#include "usbdev_utils.h" + +// Stub callback function supplied to libusb. +void LIBUSB_CALL USBDevInt::CbStubIN(struct libusb_transfer *xfr) { + USBDevInt *self = reinterpret_cast<USBDevInt *>(xfr->user_data); + self->CallbackIN(xfr); +} + +void LIBUSB_CALL USBDevInt::CbStubOUT(struct libusb_transfer *xfr) { + USBDevInt *self = reinterpret_cast<USBDevInt *>(xfr->user_data); + self->CallbackOUT(xfr); +} + +bool USBDevInt::Open(unsigned interface) { + int rc = dev_->ClaimInterface(interface); + if (rc < 0) { + return dev_->ErrorUSB("ERROR: Claiming interface", rc); + } + + // Retain the interface number. + interface_ = interface; + + // Remember the (assumed) endpoints which we're using. + epOut_ = interface + 1U; + epIn_ = 0x80U | epOut_; + + // No transfers in progress. + xfrIn_ = nullptr; + + xfrOut_ = nullptr; + + maxPacketSize_ = USBDevice::kDevDataMaxPacketSize; + + return true; +} + +void USBDevInt::Stop() { + SetClosing(true); + + int rc = dev_->ReleaseInterface(interface_); + if (rc < 0) { + std::cerr << "" << std::endl; + } +} + +void USBDevInt::Pause() { + SetClosing(true); + + if (verbose_) { + std::cout << PrefixID() << "waiting to close" << std::endl; + } + while (inActive_ || outActive_) { + dev_->Service(); + } + if (verbose_) { + std::cout << PrefixID() << " closed" << std::endl; + } + + int rc = dev_->ReleaseInterface(interface_); + if (rc < 0) { + std::cerr << "" << std::endl; + } +} + +bool USBDevInt::Resume() { + SetClosing(false); + + int rc = dev_->ClaimInterface(interface_); + if (rc < 0) { + return dev_->ErrorUSB("ERROR: Claiming interface", rc); + } + return true; +} + +// Return a summary report of the stream settings of status. +std::string USBDevInt::Report(bool status, bool verbose) const { return ""; } + +void USBDevInt::DumpIntTransfer(struct libusb_transfer *xfr) const { + const void *buf = reinterpret_cast<void *>(xfr->buffer); + std::cout << "Buffer " << buf << " length " << xfr->length + << " => actual length " << xfr->actual_length << std::endl; + buffer_dump(stdout, xfr->buffer, xfr->actual_length); +} + +// Retrieving of IN traffic from device. +bool USBDevInt::ServiceIN() { + // Ensure that we have enough space available for a full packet; the device + // software decides upon the length of each packet. + uint8_t *space; + bool ok = ProvisionSpace(&space, maxPacketSize_); + if (ok) { + uint32_t to_fetch = maxPacketSize_; + if (to_fetch > transfer_bytes_ - bytes_recvd_) { + to_fetch = transfer_bytes_ - bytes_recvd_; + } + + if (!xfrIn_) { + xfrIn_ = dev_->AllocTransfer(0U); + if (!xfrIn_) { + return false; + } + } + + if (bulk_) { + dev_->FillBulkTransfer(xfrIn_, epIn_, space, to_fetch, CbStubIN, this, + kDataTimeout); + } else { + dev_->FillIntTransfer(xfrIn_, epIn_, space, to_fetch, CbStubIN, this, + kDataTimeout); + } + + int rc = dev_->SubmitTransfer(xfrIn_); + if (rc < 0) { + return dev_->ErrorUSB("ERROR: Submitting IN transfer", rc); + } + inActive_ = true; + } else { + inActive_ = false; + } + + return true; +} + +// Sending of OUT traffic to device. +bool USBDevInt::ServiceOUT() { + // Do we have any data ready to send? + uint8_t *data; + uint32_t num_bytes = DataAvailable(&data); + if (num_bytes > 0U) { + // Supply details of the single OUT packet. + if (!xfrOut_) { + xfrOut_ = dev_->AllocTransfer(0U); + if (!xfrOut_) { + // Stream is not operational. + return false; + } + } + + if (bulk_) { + dev_->FillBulkTransfer(xfrOut_, epOut_, data, num_bytes, CbStubOUT, this, + kDataTimeout); + } else { + dev_->FillIntTransfer(xfrOut_, epOut_, data, num_bytes, CbStubOUT, this, + kDataTimeout); + } + + int rc = dev_->SubmitTransfer(xfrOut_); + if (rc < 0) { + return dev_->ErrorUSB("ERROR: Submitting OUT transfer", rc); + } + outActive_ = true; + } else { + // Nothing to propagate at this time. + outActive_ = false; + } + // Stream remains operational, even if it presently has no work on the OUT + // side. + return true; +} + +bool USBDevInt::Service() { + if (failed_) { + return false; + } + // (Re)start IN traffic if not already in progress. + if (!inActive_ && !ServiceIN()) { + return false; + } + // (Re)start OUT traffic if not already in progress and there is + // data available to be transmitted. + if (!outActive_ && !ServiceOUT()) { + return false; + } + return true; +} + +// Callback function supplied to libusb for IN transfers. +void USBDevInt::CallbackIN(struct libusb_transfer *xfr) { + if (xfr->status != LIBUSB_TRANSFER_COMPLETED) { + std::cerr << PrefixID() << " Invalid/unexpected IN transfer status " + << xfr->status << std::endl; + std::cerr << "length " << xfr->length << " actual " << xfr->actual_length + << std::endl; + failed_ = true; + return; + } + + if (verbose_) { + std::cout << PrefixID() << "CallbackIN xfr " << xfr << std::endl; + DumpIntTransfer(xfr); + } + + // Collect and parse signature bytes at the start of the IN stream. + uint8_t *dp = xfr->buffer; + int nrecvd = xfr->actual_length; + + // Update the circular buffer with the amount of data that we've received + CommitData(nrecvd); + + if (!SigReceived()) { + if (nrecvd > 0 && !SigReceived()) { + uint32_t dropped = SigDetect(&sig_, dp, (uint32_t)nrecvd); + + // Consume stream signature, rather than propagating it to the output + // side. + if (SigReceived()) { + SigProcess(sig_); + dropped += sizeof(usbdev_stream_sig_t); + } + + // Skip past any dropped bytes, including the signature, so that if there + // are additional bytes we may process them. + nrecvd = ((uint32_t)nrecvd > dropped) ? ((uint32_t)nrecvd - dropped) : 0; + dp += dropped; + + if (dropped) { + DiscardData(dropped); + } + } + } + + bool ok = true; + if (nrecvd > 0) { + // Check the received LFSR-generated byte(s) and combine them with the + // output of our host-side LFSR. + ok = ProcessData(dp, nrecvd); + } + + if (ok) { + if (CanSchedule()) { + // Attempt to set up another IN transfer. + failed_ = !ServiceIN(); + } else { + inActive_ = false; + } + } else { + failed_ = true; + } +} + +// Callback function supplied to libusb for OUT transfers. +void USBDevInt::CallbackOUT(struct libusb_transfer *xfr) { + if (xfr->status != LIBUSB_TRANSFER_COMPLETED) { + std::cerr << PrefixID() << " Invalid/unexpected OUT transfer status " + << xfr->status << std::endl; + std::cerr << "length " << xfr->length << " actual " << xfr->actual_length + << std::endl; + failed_ = true; + return; + } + + if (verbose_) { + std::cout << PrefixID() << "CallbackOUT xfr " << xfr << std::endl; + DumpIntTransfer(xfr); + } + + // Note: we're not expecting any truncation on OUT transfers. + assert(xfr->actual_length == xfr->length); + ConsumeData(xfr->actual_length); + + if (CanSchedule()) { + // Attempt to set up another OUT transfer. + failed_ = !ServiceOUT(); + } else { + outActive_ = false; + } +}
diff --git a/sw/host/tests/usbdev/usbdev_stream/usbdev_int.h b/sw/host/tests/usbdev/usbdev_stream/usbdev_int.h new file mode 100644 index 0000000..2e751c5 --- /dev/null +++ b/sw/host/tests/usbdev/usbdev_stream/usbdev_int.h
@@ -0,0 +1,150 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 +#ifndef OPENTITAN_SW_HOST_TESTS_USBDEV_USBDEV_STREAM_USBDEV_INT_H_ +#define OPENTITAN_SW_HOST_TESTS_USBDEV_USBDEV_STREAM_USBDEV_INT_H_ +#include <queue> + +#include "usb_device.h" +#include "usbdev_stream.h" + +// Interrupt and Bulk Transfer-based streams to usbdev; as far as the host- +// and device-side code at application level is concerned, these are the same. +// +// The differences are at the service/delivery level offered by the lower USB +// layers. +class USBDevInt : public USBDevStream { + public: + USBDevInt(USBDevice *dev, bool bulk, unsigned id, uint32_t transfer_bytes, + bool retrieve, bool check, bool send, bool verbose) + : USBDevStream(id, transfer_bytes, retrieve, check, send, verbose), + dev_(dev), + bulk_(bulk), + failed_(false), + inActive_(false), + outActive_(false), + xfrIn_(nullptr), + xfrOut_(nullptr) {} + /** + * Open an Interrupt connection to specified device interface. + * + * @param interface Interface number. + * @return The success of the operation. + */ + bool Open(unsigned interface); + /** + * Finalize the stream, prior to shutting down. + */ + virtual void Stop(); + /** + * Pause the stream, prior to suspending the device. + */ + virtual void Pause(); + /** + * Resume stremaing. + */ + virtual bool Resume(); + /** + * Return a summary report of the stream settings or status. + * + * @param status Indicates whether settings or status requested. + * @param verbose true iff a more verbose report is required. + * @return Status report + */ + virtual std::string Report(bool status = false, bool verbose = false) const; + /** + * Service this Interrupt stream. + * + * @return true iff the stream is still operational. + */ + virtual bool Service(); + + private: + /** + * Diagnostic utility function to display the content of libusb Bulk/Interrupt + * transfer. + * + * @param xfr The Interrupt transfer to be displayed. + */ + void DumpIntTransfer(struct libusb_transfer *xfr) const; + /** + * Retrieving of IN traffic from device. + * + * @return true iff the stream is still operational. + */ + bool ServiceIN(); + /** + * Sending of OUT traffic to device. + * + * @return true iff the stream is still operational. + */ + bool ServiceOUT(); + /** + * Callback function supplied to libusb for IN transfers; transfer has + * completed and requires attention. + * + * @param xfr The transfer that has completed. + */ + void CallbackIN(struct libusb_transfer *xfr); + /** + * Callback function supplied to libusb for OUT transfers; transfer has + * completed and requires attention. + * + * @param xfr The transfer that has completed. + */ + void CallbackOUT(struct libusb_transfer *xfr); + /** + * Stub callback function supplied to libusb for IN transfers. + * + * @param xfr The transfer that has completed. + */ + static void LIBUSB_CALL CbStubIN(struct libusb_transfer *xfr); + /** + * Stub callback function supplied to libusb for OUT transfers. + * + * @param xfr The transfer that has completed. + */ + static void LIBUSB_CALL CbStubOUT(struct libusb_transfer *xfr); + + // USB device. + USBDevice *dev_; + + // Bulk Transfer Type? + bool bulk_; + + // The number of the interface being used by this stream. + unsigned interface_; + + // Has this stream experienced a failure? + bool failed_; + + // Is an IN transfer in progress? + bool inActive_; + + // Is an OUT transfer in progress? + bool outActive_; + + // Do we currently have an IN transfer? + struct libusb_transfer *xfrIn_; + + // Do we currently have an OUT transfer? + struct libusb_transfer *xfrOut_; + + // Maximum packet size for this stream. + uint8_t maxPacketSize_; + + // Endpoint numbers used by this stream. + uint8_t epIn_; + uint8_t epOut_; + + // Stream signature. + // Note: this is constructed piecemeal as the bytes are received from the + // device. + usbdev_stream_sig_t sig_; + + // No timeout at present; the device-side code is responsible for signaling + // test completion/failure. This may need to change for CI tests. + static constexpr unsigned kDataTimeout = 0U; +}; + +#endif // OPENTITAN_SW_HOST_TESTS_USBDEV_USBDEV_STREAM_USBDEV_INT_H_
diff --git a/sw/host/tests/usbdev/usbdev_stream/usbdev_serial.cc b/sw/host/tests/usbdev/usbdev_stream/usbdev_serial.cc new file mode 100644 index 0000000..2877ad7 --- /dev/null +++ b/sw/host/tests/usbdev/usbdev_stream/usbdev_serial.cc
@@ -0,0 +1,175 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 +#include "usbdev_serial.h" + +#include <cstdio> +#include <iostream> +#include <unistd.h> + +#include "stream_test.h" +#include "usbdev_utils.h" + +USBDevSerial::~USBDevSerial() { Stop(); } + +// Iniitialise a stream between the specified input and output ports. +bool USBDevSerial::Open(const char *in_name, const char *out_name) { + // Take a copy of the port names. + inPort_ = in_name; + outPort_ = out_name; + return OpenPorts(); +} + +// Open the input and output ports to the board/device for this stream. +bool USBDevSerial::OpenPorts() { + in_ = port_open(inPort_.c_str(), false); + if (in_ < 0) { + return false; + } + out_ = port_open(outPort_.c_str(), true); + if (out_ < 0) { + close(in_); + return false; + } + + if (cfg.verbose) { + printf("S%u: input '%s' (%d) output '%s' (%d)\n", id_, inPort_.c_str(), in_, + outPort_.c_str(), out_); + } + + return true; +} + +void USBDevSerial::Stop() { + // Close any open port handles. + if (in_ >= 0) { + close(in_); + in_ = -1; + } + if (out_ >= 0) { + close(out_); + out_ = -1; + } +} + +void USBDevSerial::Pause() { + // This is the same behavior as stopping. + Stop(); +} + +bool USBDevSerial::Resume() { + // I don't think we can expect this to suspend/resume at the byte level + // anyway; we seem to resume at a different point in the LFSR, presumably + // because some internal buffer within the kernel/driver level dumps some + // bytes when we close the handle. + return OpenPorts(); +} + +// Return a summary report of the stream settings of status. +std::string USBDevSerial::Report(bool status, bool verbose) const { return ""; } + +// Sending of OUT traffic to device. +bool USBDevSerial::ServiceOUT() { + uint8_t *data; + size_t to_send = DataAvailable(&data); + if (to_send > 0U) { + ssize_t nsent; + if (send_) { + if (cfg.verbose) { + std::cout << PrefixID() << "Trying to send " << to_send << "byte(s)" + << std::endl; + } + // Propagate the modified bytes to the output port. + nsent = send_bytes(out_, &buf_.data[buf_.rd_idx], to_send); + if (nsent < 0) { + return false; + } + } else { + nsent = to_send; + } + + if (cfg.verbose) { + std::cout << PrefixID() << (send_ ? "Sent" : "Dropped") << nsent + << " byte(s)" << std::endl; + } + + ConsumeData(nsent); + } + + return true; +} + +// Retrieving of IN traffic from device. +bool USBDevSerial::ServiceIN() { + // Decide how many bytes to try to read into our buffer. + uint8_t *dp; + uint32_t space_bytes = SpaceAvailable(&dp); + + uint32_t to_fetch = space_bytes; + if (to_fetch > transfer_bytes_ - bytes_recvd_) { + to_fetch = transfer_bytes_ - bytes_recvd_; + } + + ssize_t nrecvd; + if (!SigReceived() || retrieve_) { + // Read as many bytes as we can from the input port. + nrecvd = recv_bytes(in_, dp, to_fetch); + if (nrecvd < 0) { + return false; + } + + // Update the circular buffer with the amount of data that we've written. + CommitData(nrecvd); + + if (nrecvd > 0 && !SigReceived()) { + uint32_t dropped = SigDetect(&sig_, dp, (uint32_t)nrecvd); + + // Consume stream signature, rather than propagating it to the output + // side. + if (SigReceived()) { + SigProcess(sig_); + dropped += sizeof(usbdev_stream_sig_t); + } + + // Skip past any dropped bytes, including the signature, so that if there + // are additional bytes we may process them. + nrecvd = ((uint32_t)nrecvd > dropped) ? ((uint32_t)nrecvd - dropped) : 0; + dp += dropped; + + if (dropped) { + DiscardData(dropped); + } + } + } else { + // Generate a stream of bytes _as if_ we'd received them correctly from + // the device. + GenerateData(dp, to_fetch); + nrecvd = to_fetch; + + // Update the circular buffer with the amount of data that we've written. + CommitData(nrecvd); + } + + bool ok = true; + if (nrecvd > 0) { + // Check the received LFSR-generated byte(s) and combine them with the + // output of our host-side LFSR. + ok = ProcessData(dp, nrecvd); + } + + return ok; +} + +// Service this stream. +bool USBDevSerial::Service() { + // The base class may perform some diagnostic reporting common to all streams. + bool ok = USBDevStream::Service(); + if (ok) { + // Handle OUT traffic first to try to create more buffer space. + ok = ServiceOUT(); + if (ok) { + ok = ServiceIN(); + } + } + return ok; +}
diff --git a/sw/host/tests/usbdev/usbdev_stream/usbdev_serial.h b/sw/host/tests/usbdev/usbdev_stream/usbdev_serial.h new file mode 100644 index 0000000..04ad93e --- /dev/null +++ b/sw/host/tests/usbdev/usbdev_stream/usbdev_serial.h
@@ -0,0 +1,91 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 +#ifndef OPENTITAN_SW_HOST_TESTS_USBDEV_USBDEV_STREAM_USBDEV_SERIAL_H_ +#define OPENTITAN_SW_HOST_TESTS_USBDEV_USBDEV_STREAM_USBDEV_SERIAL_H_ +#include "usbdev_stream.h" + +/** + * Bulk Transfer Stream over ttyUSBn serial connection using File Descriptors. + */ +class USBDevSerial : public USBDevStream { + public: + USBDevSerial(unsigned id, uint32_t transfer_bytes, bool retrieve, bool check, + bool send, bool verbose) + : USBDevStream(id, transfer_bytes, retrieve, check, send, verbose) {} + ~USBDevSerial(); + + /** + * Open the input and output ports to the board/device for this stream. + * + * @param in_name Device name of input serial port. + * @param out_name Device name of output serial port. + * @return true iff successful. + */ + bool Open(const char *in_name, const char *out_name); + /** + * Finalize the stream, prior to shutting down. + */ + virtual void Stop(); + /** + * Pause the stream, prior to suspending the device. + */ + virtual void Pause(); + /** + * Resume stremaing. + */ + virtual bool Resume(); + /** + * Return a summary report of the stream settings or status. + * + * @param status Indicates whether settings or status requested. + * @param verbose true iff a more verbose report is required. + * @return Status report + */ + virtual std::string Report(bool status = false, bool verbose = false) const; + /** + * Service this serial stream. + * + * @return true iff the stream is still operational. + */ + virtual bool Service(); + + private: + /** + * Retrieving of IN traffic from device. + * + * @return true iff the stream is still operational. + */ + bool ServiceIN(); + /** + * Sending of OUT traffic to device. + * + * @return true iff the stream is still operational. + */ + bool ServiceOUT(); + /** + * Open the input and output ports for this stream. + * + * @return true iff opened successfully. + */ + bool OpenPorts(); + + // Input port handle. + int in_; + + // Output port handle. + int out_; + + // Input port name. + std::string inPort_; + + // Output port name. + std::string outPort_; + + // Stream signature. + // Note: this is constructed piecemeal as the bytes are received from the + // device. + usbdev_stream_sig_t sig_; +}; + +#endif // OPENTITAN_SW_HOST_TESTS_USBDEV_USBDEV_STREAM_USBDEV_SERIAL_H_
diff --git a/sw/host/tests/usbdev/usbdev_stream/usbdev_stream.cc b/sw/host/tests/usbdev/usbdev_stream/usbdev_stream.cc new file mode 100644 index 0000000..c950c46 --- /dev/null +++ b/sw/host/tests/usbdev/usbdev_stream/usbdev_stream.cc
@@ -0,0 +1,391 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 +#include "usbdev_stream.h" + +#include <cassert> +#include <cstdio> +#include <cstring> +#include <iostream> + +#include "stream_test.h" +#include "usb_device.h" +#include "usbdev_utils.h" + +// 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) + +// Simple LFSR for 8-bit sequences. +#define LFSR_ADVANCE(lfsr) \ + (((lfsr) << 1) ^ \ + ((((lfsr) >> 1) ^ ((lfsr) >> 2) ^ ((lfsr) >> 3) ^ ((lfsr) >> 7)) & 1u)) + +USBDevStream::USBDevStream(unsigned id, uint32_t transfer_bytes, bool retrieve, + bool check, bool send, bool verbose) { + // Remember Stream IDentifier and flags. + SetProperties(id, retrieve, check, send); + verbose_ = verbose; + + // Stream is not in the process of being closed. + closing_ = false; + + // Not yet received stream signature. + sig_recvd_ = kSigStateStart; + + // Initialise LFSR state. + tst_lfsr_ = USBTST_LFSR_SEED(id); + dpi_lfsr_ = USBDPI_LFSR_SEED(id); + + // Initialize circular buffer. + buf_.wr_idx = 0U; + buf_.rd_idx = 0U; + buf_.end_idx = 0U; + + // Number of bytes to be transferred. + transfer_bytes_ = transfer_bytes; + + // Total counts of bytes received and sent. + bytes_recvd_ = 0U; + bytes_sent_ = 0U; +} + +// Detect a stream signature within the byte stream; +// simple restartable parser that drops all bytes until we find a header +// signature. +// +// The signature permits usbdev_stream_test to pass test parameters to this +// program as well as overcoming the issue of the mapping from device +// endpoints/streams to USB ports not being under our control. +uint32_t USBDevStream::SigDetect(usbdev_stream_sig_t *sig, const uint8_t *sp, + uint32_t nrecv) { + uint32_t dropped = 0U; + assert(nrecv > 0); + do { + if (sig_recvd_ == kSigStateStart) { + sig_recvd_ = kSigStateCheckHead; + sig_cnt_ = 0U; + } + + // Collect the signature into a structure with appropriate alignment + // for direct access to members. + uint8_t *sigp = reinterpret_cast<uint8_t *>(sig); + sigp[sig_cnt_] = *sp; + + // Advance the parser beyond this byte + // Note: valid signature bytes remain excluded from the returned count of + // dropped bytes. + const size_t tail_offset = offsetof(usbdev_stream_sig_t, tail_sig); + bool discard = false; + switch (sig_recvd_) { + // Check the bytes of the header signature. + case kSigStateCheckHead: { + const unsigned sh = sig_cnt_ << 3; + uint8_t match = (uint8_t)(STREAM_SIGNATURE_HEAD >> sh); + if (match == *sp) { + if (++sig_cnt_ >= 4U) { + sig_recvd_ = kSigStateSkipBody; + } + } else { + discard = true; + } + } break; + + // Just collect the signature body for later validation. + case kSigStateSkipBody: { + if (++sig_cnt_ >= tail_offset) { + sig_recvd_ = kSigStateCheckTail; + } + } break; + + // Check the bytes of the tail signature + case kSigStateCheckTail: { + const unsigned sh = (sig_cnt_ - tail_offset) << 3; + const uint8_t match = (uint8_t)(STREAM_SIGNATURE_TAIL >> sh); + if (match == *sp) { + if (sig_cnt_ >= sizeof(usbdev_stream_sig_t) - 1U) { + // Note: at this point we could use the signature words to identify + // the Endianness of the sender and thus reverse the other fields + // if necessary. + + // Basic sanity check of signature values before we accept it; + // some of the checking must be stream-type dependent, so this is + // not a complete guarantee of validity. + uint8_t stream = sig->stream & USBDevice::kUsbdevStreamFlagID; + if (sig->num_bytes > 0U && sig->num_bytes < 0x10000000U && + stream < STREAMS_MAX) { + if (verbose_) { + std::cout << PrefixID() << "Signature accepted" << std::endl; + } + sig_recvd_ = kSigStateReceived; + } else { + std::cout << PrefixID() << "Signature rejected" << std::endl; + // Report the signature to explain why it's been rejected. + SigReport(*sig); + discard = true; + } + } else { + sig_cnt_++; + } + } else { + discard = true; + } + } break; + + default: + // Note: should not be called once we have a valid stream signature. + assert(!"Invalid/undefined sig_recvd state"); + break; + } + + if (discard) { + // Mismatch; discard the checked data, and resume from the beginning. + dropped += sig_cnt_ + 1U; + sig_recvd_ = kSigStateStart; + sig_cnt_ = 0U; + } + // Advance to next byte. + sp++; + } while (--nrecv > 0U && sig_recvd_ != kSigStateReceived); + + return dropped; +} + +void USBDevStream::SigProcess(const usbdev_stream_sig_t &sig) { + // Stream IDentifier and flags. + uint8_t stream = sig.stream & USBDevice::kUsbdevStreamFlagID; + bool retrieve = ((sig.stream & USBDevice::kUsbdevStreamFlagRetrieve) != 0U); + bool check = ((sig.stream & USBDevice::kUsbdevStreamFlagCheck) != 0U); + bool send = ((sig.stream & USBDevice::kUsbdevStreamFlagSend) != 0U); + SetProperties(stream, retrieve, check, send); + + // If this is the start of the sequence then we also have the number of bytes + // to be transferred. + if (!(sig.seq_hi | sig.seq_lo)) { + transfer_bytes_ = sig.num_bytes; + } +} + +void USBDevStream::SigReport(const usbdev_stream_sig_t &sig) { + // Sequence number; important for unreliable packet-based streams + // (Isochronous streams). + uint16_t seq = sig_read8(offsetof(usbdev_stream_sig_t, seq_lo)) | + (sig_read8(offsetof(usbdev_stream_sig_t, seq_hi)) << 8); + + // Stream IDentifier and flags. + uint8_t stream = sig.stream & USBDevice::kUsbdevStreamFlagID; + bool retrieve = ((sig.stream & USBDevice::kUsbdevStreamFlagRetrieve) != 0U); + bool check = ((sig.stream & USBDevice::kUsbdevStreamFlagCheck) != 0U); + bool send = ((sig.stream & USBDevice::kUsbdevStreamFlagSend) != 0U); + + printf("Signature detected: stream #%u LFSR 0x%02x bytes 0x%x\n", stream, + sig.init_lfsr, sig.num_bytes); + printf(" - retrieve %c check %c send %c\n", retrieve ? 'Y' : 'N', + check ? 'Y' : 'N', send ? 'Y' : 'N'); + printf(" - seq #%u\n", seq); +} + +// Return an indication of whether this stream has completed its transfer. +bool USBDevStream::Completed() const { + return (bytes_recvd_ >= transfer_bytes_) && (bytes_sent_ >= transfer_bytes_); +} + +bool USBDevStream::ProvisionSpace(uint8_t **space, uint32_t len) { + uint32_t space_bytes = SpaceAvailable(space); + if (space_bytes >= len) { + return true; + } + + // If we're failing to allocate sufficient space right at the end of the + // buffer, wrap sooner. + if (buf_.rd_idx <= buf_.wr_idx && (kBufferSize - buf_.wr_idx) < len && + buf_.rd_idx > len) { + buf_.end_idx = buf_.wr_idx; + buf_.wr_idx = 0U; + // Return pointer to the start of the free space. + if (space) { + *space = buf_.data; + } + return true; + } + + return false; +} + +uint32_t USBDevStream::SpaceAvailable(uint8_t **space) { + uint32_t space_bytes = kBufferSize - buf_.wr_idx; + uint8_t *start = &buf_.data[buf_.wr_idx]; + + if (buf_.rd_idx > buf_.wr_idx) { + // All space is contiguous. + space_bytes = (buf_.rd_idx - 1U) - buf_.wr_idx; + } else if (!buf_.rd_idx) { + // Leave one unused byte at the end of the circular buffer. + space_bytes--; + } + + // Return pointer to the start of the free space. + if (space) { + *space = start; + } + return space_bytes; +} + +bool USBDevStream::AddData(const uint8_t *data, uint32_t len) { + // Ascertain the amount of space available. + uint32_t space_bytes = SpaceAvailable(nullptr); + if (space_bytes < len) { + return false; + } + + // Size of first contiguous chunk. + uint32_t chunk = kBufferSize - buf_.wr_idx; + if (len < chunk) { + chunk = len; + } + if (data) { + // Data is not already present in the buffer. + memcpy(&buf_.data[buf_.wr_idx], data, chunk); + data += chunk; + } + buf_.wr_idx += chunk; + if (buf_.wr_idx > buf_.end_idx) { + buf_.end_idx = buf_.wr_idx; + } + + // Write index wraparound. + if (buf_.wr_idx >= kBufferSize) { + buf_.wr_idx = 0U; + } + + return true; +} + +void USBDevStream::GenerateData(uint8_t *dp, uint32_t len) { + // Generate a stream of bytes _as if_ we'd received them correctly from + // the device + uint8_t next_lfsr = tst_lfsr_; + for (unsigned idx = 0U; idx < len; idx++) { + dp[idx] = next_lfsr; + next_lfsr = LFSR_ADVANCE(next_lfsr); + } +} + +bool USBDevStream::ProcessData(uint8_t *dp, uint32_t len) { + bool ok = true; + + if (len > 0U) { + // Record the time of the first data reception. + if (!received) { + start_time = time_us(); + received = true; + } + + if (verbose_) { + std::cout << "S" << ID() + << (cfg.retrieve ? ": Received " : ": Generated ") << len + << " byte(s)" << std::endl; + } + + // We can just check and overwrite the input data in-situ. + const uint8_t *sp = dp; + for (uint32_t idx = 0U; idx < len; idx++) { + uint8_t expected = tst_lfsr_; + uint8_t recvd = sp[idx]; + + // Check whether the received byte is as expected. + if (retrieve_ && check_) { + if (recvd != expected) { + printf("S%u: Mismatched data from device 0x%02x, expected 0x%02x\n", + id_, recvd, expected); + ok = false; + } + } + + // Simply XOR the two LFSR-generated streams together. + dp[idx] = recvd ^ dpi_lfsr_; + if (verbose_) { + printf("S%u: 0x%02x <- 0x%02x ^ 0x%02x\n", id_, dp[idx], recvd, + dpi_lfsr_); + } + + // Advance our LFSRs. + tst_lfsr_ = LFSR_ADVANCE(tst_lfsr_); + dpi_lfsr_ = LFSR_ADVANCE(dpi_lfsr_); + } + + // Update the buffer writing state. + bytes_recvd_ += len; + } + + return ok; +} + +uint32_t USBDevStream::DataAvailable(uint8_t **data) { + // Read index wraparound. + if (buf_.rd_idx >= buf_.end_idx && buf_.wr_idx > 0U && + buf_.wr_idx < buf_.rd_idx) { + buf_.rd_idx = 0U; + } + + uint32_t data_bytes = buf_.end_idx - buf_.rd_idx; + uint8_t *start = &buf_.data[buf_.rd_idx]; + + if (buf_.wr_idx >= buf_.rd_idx) { + data_bytes = buf_.wr_idx - buf_.rd_idx; + } + if (data) { + *data = start; + } + return data_bytes; +} + +bool USBDevStream::DiscardData(uint32_t len) { + // Ascertain the amount of data available. + uint32_t data_bytes = DataAvailable(nullptr); + if (data_bytes < len) { + return false; + } + + // Adjust buffer state to discard the specified number of bytes. + uint32_t chunk = buf_.end_idx - buf_.rd_idx; + assert(len <= chunk); + if (len < chunk || (len == chunk && buf_.wr_idx >= buf_.end_idx)) { + buf_.rd_idx += len; + } else { + buf_.rd_idx = len - chunk; + } + + return true; +} + +bool USBDevStream::ConsumeData(uint32_t len) { + if (!DiscardData(len)) { + return false; + } + // Update the count of transmitted bytes. + bytes_sent_ += len; + return true; +} + +void USBDevStream::ClearBuffer() { + // Check that the write index is valid before we advance the read index to + // clear the buffer. + assert(buf_.wr_idx < kBufferSize); + assert(buf_.wr_idx <= buf_.end_idx); + buf_.rd_idx = buf_.wr_idx; +} + +// Service the given data stream; this base class implementation just provides +// some generic progress reporting. +bool USBDevStream::Service() { + if (verbose_) { + printf("S%u : rd_idx 0x%x wr_idx 0x%x\n", id_, buf_.rd_idx, buf_.wr_idx); + } + return true; +}
diff --git a/sw/host/tests/usbdev/usbdev_stream/usbdev_stream.h b/sw/host/tests/usbdev/usbdev_stream/usbdev_stream.h new file mode 100644 index 0000000..9012422 --- /dev/null +++ b/sw/host/tests/usbdev/usbdev_stream/usbdev_stream.h
@@ -0,0 +1,426 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 +#ifndef OPENTITAN_SW_HOST_TESTS_USBDEV_USBDEV_STREAM_USBDEV_STREAM_H_ +#define OPENTITAN_SW_HOST_TESTS_USBDEV_USBDEV_STREAM_USBDEV_STREAM_H_ +#include <cstddef> +#include <cstdint> +#include <string> +#include <sys/types.h> + +/** + * 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 + * Note: for Isochronous Transfers, this is the initial value of the sender's + * LFSR for _this packet_. + */ + uint8_t init_lfsr; + /** + * Stream number and flags. + */ + uint8_t stream; + /** + * Sequence number, low part; for non-Isochronous streams this will always be + * zero because a signature is used only at the start of the data stream. + */ + uint8_t seq_lo; + /** + * Sequence number, high part; for non-Isochronous streams this will always be + * zero because a signature is used only at the start of the data stream. + */ + uint8_t seq_hi; + /** + * Number of bytes to be transferred. + */ + uint32_t num_bytes; + /** + * Tail signature word. + */ + uint32_t tail_sig; +} usbdev_stream_sig_t; + +// Data stream. +class USBDevStream { + public: + USBDevStream(unsigned id, uint32_t num_bytes, bool retrieve, bool check, + bool send, bool verbose); + virtual ~USBDevStream() {} + + /** + * Stream implementations. + */ + enum StreamType { + // File descriptor access to serial port. + StreamType_Serial, + // Standard USB Endpoint/Transfer Types all accessed via libusb. + StreamType_Bulk, + StreamType_Interrupt, + StreamType_Isochronous, + StreamType_Control + }; + /** + * Record whether or not the stream is in the process of closing + * (shutting down or suspending). + * + * @param closing Whether or not the stream is now closing. + */ + void SetClosing(bool closing) { closing_ = closing; } + /** + * Indicate whether an additional I/O transfer may safely be scheduled + * on this stream, eg. it is not suspending/shutting down. + * + * @return true iff another I/O transfer may be scheduled. + */ + bool CanSchedule() const { return !closing_; } + /** + * Finalize the stream, prior to shutting down. + */ + virtual void Stop() = 0; + /** + * Pause the stream, prior to suspending the device. + */ + virtual void Pause() = 0; + /** + * Resume streaming. + */ + virtual bool Resume() = 0; + /** + * Return the Stream IDentifier of this stream. + */ + unsigned ID() const { return id_; } + /** + * Return a Stream IDentifier prefix suitable for logging/reporting. + */ + std::string PrefixID() { + std::string s("S"); + s += std::to_string(id_); + s += ": "; + return s; + } + /** + * Return a summary report of the stream settings or status. + * + * @param status Indicates whether settings or status requested. + * @param verbose true iff a more verbose report is required. + * @return Status report. + */ + virtual std::string Report(bool status = false, + bool verbose = false) const = 0; + /** + * Set Stream IDentifier and flags. + */ + void SetProperties(unsigned id, bool retrieve, bool check, bool send) { + id_ = id; + retrieve_ = retrieve; + check_ = check; + send_ = send; + } + /** + * Service this stream. + * + * @return true iff test should continue, false indicates error. + */ + virtual bool Service(); + /** + * Indicates whether this stream has completed its transfer. + * + * @return true iff this stream has nothing more to do. + */ + virtual bool Completed() const; + /** + * Returns the total number of bytes to be transferred by this stream. + * + * @param Number of bytes to be transferred. + */ + uint32_t TransferBytes() const { return transfer_bytes_; } + /** + * Returns a count of the number of bytes received from the device + * + * @return Number of bytes received. + */ + uint32_t BytesRecvd() const { return bytes_recvd_; } + /** + * Returns a count of the number of bytes sent to the device. + * + * @return Number of bytes sent. + */ + uint32_t BytesSent() const { return bytes_sent_; } + /** + * Return the textual name of the given stream type. + * + * @param type Stream type. + * @return name of stream type. + */ + static const char *StreamTypeName(StreamType type) { + switch (type) { + case StreamType_Serial: + return "Serial"; + case StreamType_Bulk: + return "Bulk"; + case StreamType_Interrupt: + return "Interrupt"; + case StreamType_Isochronous: + return "Isochronous"; + case StreamType_Control: + return "Control"; + default: + return "<Unknown>"; + } + } + + protected: + /** + * Provision the given number of bytes of contiguous space, + * and optionally a pointer to the start of the free space. + * + * @param space Receives the pointer to the start of the free space, + * or NULL iff pointer not required. + * @param len Amount of space (in bytes) to provision. + * @return true iff the operation was successful. + */ + bool ProvisionSpace(uint8_t **space, uint32_t len); + /** + * Returns the amount of contiguous free space available in the buffer, + * and optionally a pointer to the start of the free space. + * + * @param space Receives the pointer to the start of the free space, + * or NULL iff pointer not required. + * @return The contiguous free space available (in bytes). + */ + uint32_t SpaceAvailable(uint8_t **space); + /** + * Add the specified number of bytes to the circular buffer; if `data` is NULL + * then the bytes shall already be present in the buffer and copying is not + * performed. + * + * @param data The data to be added to the buffer, or NULL + * @param len The number of bytes to be added. + * @return The success of the operation. + */ + bool AddData(const uint8_t *data, uint32_t len); + /** + * Record that the specified number of bytes have been added to the circular + * buffer. The space must already have provisioned and the data bytes shall + * already be in the buffer. + */ + bool CommitData(uint32_t len) { return AddData(nullptr, len); } + /** + * CLear the circular buffer by removing all of its contained data bytes. + * + * Note: this is achieved by advancing the read index to match the current + * write index, so all committed/added data is discarded, by any write data + * that has only been provisioned at this point does remain valid and may + * still be committed subsequently. + */ + void ClearBuffer(); + + /** + * States in reception of signature. + */ + typedef enum { + kSigStateStart = 0, + kSigStateCheckHead, + kSigStateSkipBody, + kSigStateCheckTail, + // Signature has been correctly received. + kSigStateReceived, + } sig_state_t; + + /** + * Reset the signature detection; Isochronous streams including a + * new signature at the start of each packet transferred. + */ + void SigReset() { + sig_recvd_ = kSigStateStart; + sig_cnt_ = 0U; + } + /** + * Has valid signature been received on this stream? Note that there may be + * some additional validity checks required for specific= transfer types. + * + * @return true iff a signature has been received and detected. + */ + bool SigReceived() const { return (sig_recvd_ == kSigStateReceived); } + /** + * Collect stream flags from the supplied signature. + * + * @param sig The latest stream signature received. + */ + void SigProcess(const usbdev_stream_sig_t &sig); + /** + * Detect and parse stream/packet signature, + * returning a count of the number of bytes to be discarded from the start + * of this data. Usually this is zero, but if a valid stream signature is + * required, bytes must be discarded until the signature is received. + */ + uint32_t SigDetect(usbdev_stream_sig_t *sig, const uint8_t *sp, + uint32_t nrecv); + /** + * Diagnostic utility function to report the contents of a stream/packet + * signature. + * + * @param sig Stream signature to be reported. + */ + void SigReport(const usbdev_stream_sig_t &sig); + /** + * Generate a sequence of bytes _as if_ we'd received them correctly from the + * device. + */ + void GenerateData(uint8_t *dp, uint32_t len); + /** + * Process the given sequence of bytes according to the current stream state. + */ + bool ProcessData(uint8_t *dp, uint32_t len); + /** + * Return the number of contiguous bytes of data available in the stream + * buffer, and a pointer to the first byte of data. This may be fewer than the + * total number of bytes in the buffer, if the data wraps at the end of the + * circular buffer. + * + * @param data Receives the pointer to the first data byte. + * @return The number of contiguous data bytes available. + */ + uint32_t DataAvailable(uint8_t **data); + /** + * Update the stream buffer to indicate that data has been discarded + * (removed from the buffer but not sent to the USB device). + * + * @param len Number of bytes of data consumed. + * @return true iff the buffer was successfully updated. + */ + bool DiscardData(uint32_t len); + /** + * Update the stream buffer to indicate that data has been consumed. + * + * @param len Number of bytes of data consumed. + * @return true iff the buffer was successfully updated. + */ + bool ConsumeData(uint32_t len); + + /** + * Size of circular buffer used for streaming. + */ + static constexpr uint32_t kBufferSize = 0x10000U; + + // Utility function for collecting a byte from the stream signature, handling + // wrap around at the end of the circular buffer. + inline uint8_t sig_read8(size_t offset) { + uint32_t rd_idx = buf_.rd_idx + offset; + if (rd_idx >= kBufferSize) { + rd_idx -= kBufferSize; + } + return buf_.data[rd_idx]; + } + + // Utility function for collecting a 16-bit word from the stream signature, + // handling wrap around at the end of the circular buffer. + inline uint16_t sig_read16(size_t offset) { + uint32_t rd_idx = buf_.rd_idx + offset; + if (rd_idx >= kBufferSize) { + rd_idx -= kBufferSize; + } + uint16_t d = buf_.data[rd_idx++]; + if (rd_idx >= kBufferSize) { + rd_idx -= kBufferSize; + } + return d | (buf_.data[rd_idx++] << 8); + } + + // Utility function for collecting a 32-bit word from the stream signature, + // handling wrap around at the end of the circular buffer. + inline uint32_t sig_read32(size_t offset) { + uint32_t rd_idx = buf_.rd_idx + offset; + unsigned n = 4U; + uint32_t d = 0U; + while (n-- > 0U) { + if (rd_idx >= kBufferSize) { + rd_idx -= kBufferSize; + } + // Transmission of multi-byte value is little endian. + d = (d >> 8) | (buf_.data[rd_idx++] << 24); + } + return d; + } + /** + * Stream IDentifier. + */ + unsigned id_; + /** + * Is the stream being closed? + */ + bool closing_; + /** + * Have we received the stream signature yet? + */ + sig_state_t sig_recvd_; + unsigned sig_cnt_; + /** + * Retrieve IN data for this stream? + */ + bool retrieve_; + /** + * Check the received data against expectations? + */ + bool check_; + /** + * Send OUT data for this stream? + */ + bool send_; + /** + * Verbose reporting? + */ + bool verbose_; + /** + * Total number of bytes received. + */ + uint32_t bytes_recvd_; + /** + * Total number of bytes sent. + */ + uint32_t bytes_sent_; + /** + * Device-side LFSR; byte stream expected from usbdev_stream_test. + */ + uint8_t tst_lfsr_; + /** + * Host/DPI-side LFSR. + */ + uint8_t dpi_lfsr_; + /** + * Number of bytes to be transferred. + */ + uint32_t transfer_bytes_; + /** + * Circular buffer of streamed data. + */ + struct { + /** + * Offset at which to write the next received data (IN from device). + */ + uint32_t wr_idx; + /** + * Offset of next byte to be read from the buffer (OUT to device). + */ + uint32_t rd_idx; + /** + * Offset beyond used portion of the buffer; for packet-based transmission + * we wrap before the end of the circular buffer if a maximum-length packet + * does not fit. + */ + uint32_t end_idx; + /** + * Circular buffer of data being transferred from input to output port. + */ + uint8_t data[kBufferSize]; + } buf_; +}; + +#endif // OPENTITAN_SW_HOST_TESTS_USBDEV_USBDEV_STREAM_USBDEV_STREAM_H_
diff --git a/sw/host/tests/usbdev/usbdev_stream/usbdev_utils.cc b/sw/host/tests/usbdev/usbdev_stream/usbdev_utils.cc new file mode 100644 index 0000000..cb1e5f2 --- /dev/null +++ b/sw/host/tests/usbdev/usbdev_stream/usbdev_utils.cc
@@ -0,0 +1,171 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 +#include "usbdev_utils.h" + +#include <cerrno> +#include <cstdio> +#include <cstring> +#include <ctime> +#include <fcntl.h> +#include <iostream> +#include <sys/time.h> +#include <termios.h> +#include <unistd.h> + +#include "stream_test.h" + +// Utility function to report an error returned from file/terminal-related +// functions. +static void report_error(const char *rsn) { + std::cerr << "ERROR: " << rsn << ": " << strerror(errno) << " (error " + << errno << ")" << std::endl; +} + +// Open and configure a serial port connection to/from the USB device. +int port_open(const char *dev_name, bool write) { + const char *port_type = write ? "output" : "input"; + int fd = open(dev_name, write ? O_WRONLY : O_RDONLY); + if (fd < 0) { + std::cerr << "ERROR: Could not open " << port_type << " port '" << dev_name + << "'" << std::endl; + ReportSyntax(); + return -1; + } + + // We need to ensure that we can send full 8-bit binary data with no character + // translations and no character echo etc. + struct termios tty; + if (tcgetattr(fd, &tty) != 0) { + report_error("Failed getting terminal attributes"); + close(fd); + return -1; + } + + // 8 bits, no parity, no hardware handshaking. + tty.c_cflag &= ~(PARENB | CSTOPB | CSIZE | CRTSCTS); + tty.c_cflag |= CS8 | CREAD | CLOCAL; + + // No character echo. + tty.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHONL | ISIG); + + // No software handshaking, no special characters. + tty.c_iflag &= ~(IXON | IXOFF | IXANY); + tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL); + + // Disable line feed conversions and special characters on output traffic. + tty.c_oflag &= ~(OPOST | ONLCR); + + // Non-blocking. + tty.c_cc[VTIME] = 0; + tty.c_cc[VMIN] = 0; + + // Set in/out baud rate to be as high as possible; just in case, but it has + // no impact upon the measured transfer speed. + cfsetispeed(&tty, B4000000); + cfsetospeed(&tty, B4000000); + + // Save tty settings, also checking for error. + if (tcsetattr(fd, TCSANOW, &tty) != 0) { + report_error("Failed setting terminal attributes"); + close(fd); + return -1; + } + + return fd; +} + +// Receive a sequence of bytes from the USB device, non-blocking. +ssize_t recv_bytes(int in, uint8_t *buf, size_t len) { + ssize_t nread = 0; + + // Read as many bytes as we can from the input port. + ssize_t n = read(in, buf, len); + if (cfg.verbose) { + printf("Received %zd byte(s)\n", n); + for (int idx = 0; idx < n; idx++) { + printf("0x%02x\n", buf[idx]); + } + fflush(stdout); + } + if (n < 0) { + report_error("Failed to read from input port"); + return -1; + } + + nread += n; + buf += n; + len -= (size_t)n; + + return nread; +} + +// Send a sequence of bytes to the USB device, non-blocking. +ssize_t send_bytes(int out, const uint8_t *data, size_t len) { + ssize_t nwritten = 0; + + if (len > 0u) { + ssize_t n = write(out, data, len); + if (n < 0) { + report_error("Failed to write to output port"); + return -1; + } + + nwritten += n; + data += n; + len -= n; + } + + return nwritten; +} + +// Current monotonic wall clock time in microseconds. +uint64_t time_us(void) { + struct timeval ts; + int ret = gettimeofday(&ts, NULL); + if (ret < 0) + return (uint64_t)0u; + return ((uint64_t)ts.tv_sec * 1000000u) + ts.tv_usec; +} + +// Dump a sequence of bytes as hexadecimal and ASCII for diagnostic purposes. +void buffer_dump(FILE *out, const uint8_t *data, size_t n) { + static const char hex_digits[] = "0123456789abcdef"; + const unsigned ncols = 0x20u; + char buf[ncols * 4u + 2u]; + + while (n > 0u) { + const unsigned chunk = (n > ncols) ? ncols : (unsigned)n; + const uint8_t *row = data; + unsigned idx = 0u; + char *dp = buf; + + // Columns of hexadecimal bytes. + while (idx < chunk) { + dp[0] = hex_digits[row[idx] >> 4]; + dp[1] = hex_digits[row[idx++] & 0xfu]; + dp[2] = ' '; + dp += 3; + } + while (idx++ < ncols) { + dp[2] = dp[1] = dp[0] = ' '; + dp += 3; + } + + // Printable ASCII characters. + for (idx = 0u; idx < chunk; idx++) { + uint8_t ch = row[idx]; + *dp++ = (ch < ' ' || ch >= 0x80u) ? '.' : ch; + } + *dp = '\0'; + fprintf(out, "%s\n", buf); + data += chunk; + n -= chunk; + } + + fflush(stdout); +} + +// Link locations for inline functions. +extern uint64_t elapsed_time(uint64_t start); +extern uint16_t get_le16(const uint8_t *p);
diff --git a/sw/host/tests/usbdev/usbdev_stream/usbdev_utils.h b/sw/host/tests/usbdev/usbdev_stream/usbdev_utils.h new file mode 100644 index 0000000..19232e6 --- /dev/null +++ b/sw/host/tests/usbdev/usbdev_stream/usbdev_utils.h
@@ -0,0 +1,73 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 +#ifndef OPENTITAN_SW_HOST_TESTS_USBDEV_USBDEV_STREAM_USBDEV_UTILS_H_ +#define OPENTITAN_SW_HOST_TESTS_USBDEV_USBDEV_STREAM_USBDEV_UTILS_H_ +#include <cstdint> +#include <cstdio> + +/** + * Open and configure a serial port connection to/from the USB device. + * The returned file descriptor may be passed directly to close(). + * + * @param dev_name Device path of serial port. + * @param write Indicates whether to open for writing or reading. + * @return File descriptor, or negative to indicate failure. + */ +int port_open(const char *dev_name, bool write); + +/** + * Receive a sequence of bytes from the USB device, non-blocking. + * + * @param in File descriptor. + * @param buf Buffer to receive data. + * @param len Buffer length (= maximum number of bytes to receive). + * @return Number of bytes received, or -ve to indicate failure. + */ +ssize_t recv_bytes(int in, uint8_t *buf, size_t len); + +/** + * Send a sequence of bytes to the USB device, non-blocking. + * + * @param out File descriptor. + * @param data Data to be transmitted. + * @param len Number of bytes to be transmitted. + * @return Number of bytes transmitted, or -ve to indicate failure. + */ + +ssize_t send_bytes(int out, const uint8_t *data, size_t len); + +/** + * Dump a sequence of bytes as hexadecimal and ASCII for diagnostic purposes. + * + * @param out Output stream. + * @param data Data buffer. + * @param n Number of bytes. + */ +void buffer_dump(FILE *out, const uint8_t *data, size_t n); + +/** + * Return current monotonic wall time in microseconds. + * + * @return Current time. + */ +uint64_t time_us(void); + +/** + * Return microseconds elapsed since the given monotonic wall time. + * + * @param start Monotonic wall time at the start of the time interval. + * @return Elapsed time in microseconds. + */ +inline uint64_t elapsed_time(uint64_t start) { return time_us() - start; } + +/** + * Collect a 16-bit quantity transmitted in USB byte ordering, without + * assuming alignment or host endianness. + * + * @param p Pointer to 2 bytes. + * @return The 16-bit quantity read. + */ +inline uint16_t get_le16(const uint8_t *p) { return p[0] | (p[1] << 8); } + +#endif // OPENTITAN_SW_HOST_TESTS_USBDEV_USBDEV_STREAM_USBDEV_UTILS_H_