From 7deb82430767a2c269998e83e9978a41d2e5514f Mon Sep 17 00:00:00 2001 From: Adrian Lees Date: Tue, 14 May 2024 19:04:50 +0100 Subject: [PATCH 1/5] [host,usbdev] Host-side component of suspend-resume testing Add a simple host-side application to drive the suspend- sleep-resume top-level USBDEV tests. Signed-off-by: Adrian Lees --- sw/device/tests/BUILD | 1 + .../tests/usbdev/usbdev_stream/usb_device.cc | 328 +++++++++++++++--- .../tests/usbdev/usbdev_stream/usb_device.h | 107 +++++- 3 files changed, 376 insertions(+), 60 deletions(-) diff --git a/sw/device/tests/BUILD b/sw/device/tests/BUILD index 8c21d669ff27f..274306ee40a1d 100644 --- a/sw/device/tests/BUILD +++ b/sw/device/tests/BUILD @@ -4323,6 +4323,7 @@ opentitan_test( silicon = silicon_params( test_cmd = """ --bootstrap="{firmware}" + --timeout=30s --vbus-sense-en=VBUS_SENSE_EN --vbus-sense=VBUS_SENSE --no-wait-for-usb-device diff --git a/sw/host/tests/usbdev/usbdev_stream/usb_device.cc b/sw/host/tests/usbdev/usbdev_stream/usb_device.cc index 123c46c355518..4624eacfc4386 100644 --- a/sw/host/tests/usbdev/usbdev_stream/usb_device.cc +++ b/sw/host/tests/usbdev/usbdev_stream/usb_device.cc @@ -12,6 +12,11 @@ #include "usbdev_utils.h" +// Hub Class Feature Selectors (Table 11-17. USB 2.0 Protocol Specification) +static constexpr unsigned PORT_SUSPEND = 2u; +static constexpr unsigned PORT_RESET = 4u; +static constexpr unsigned PORT_POWER = 8u; + // 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) { @@ -38,6 +43,10 @@ bool USBDevice::Init(uint16_t vendorID, uint16_t productID, uint8_t devAddress, bool USBDevice::Fin() { (void)Close(); #if STREAMTEST_LIBUSB + if (parenth_) { + libusb_close(parenth_); + parenth_ = nullptr; + } libusb_exit(ctx_); #endif return true; @@ -104,6 +113,19 @@ bool USBDevice::Open() { // identical devices visible but access permissions may restrict // which device(s) may be opened. } else { + // Obtain the parent + libusb_device *parent = libusb_get_parent(dev); + if (parent) { + rc = libusb_open(parent, &parenth_); + if (rc < 0) { + std::cerr << "Failed to open parent device - " + << libusb_error_name(rc) << std::endl; + parenth_ = nullptr; + } else { + std::cout << "Opened parent\n"; + } + } + // Obtain the list of port numbers; required for suspend/resume. uint8_t bus = libusb_get_bus_number(dev); if (verbose_) { @@ -123,6 +145,8 @@ bool USBDevice::Open() { std::cout << '.'; devPath_ += '.'; } + // TODO: + portNumber_ = ports[idx]; } std::cout << std::endl; } else { @@ -214,6 +238,30 @@ bool USBDevice::Service() { return true; } +// Return the name of a test phase +const char *USBDevice::PhaseName(usbdev_suspend_phase_t phase) { + switch (phase) { + case kSuspendPhaseSuspend: + return "Suspend"; + case kSuspendPhaseSleepResume: + return "SleepResume"; + case kSuspendPhaseSleepReset: + return "SleepReset"; + case kSuspendPhaseSleepDisconnect: + return "SleepDisconnect"; + case kSuspendPhaseDeepResume: + return "DeepResume"; + case kSuspendPhaseDeepReset: + return "DeepReset"; + case kSuspendPhaseDeepDisconnect: + return "DeepDisconnect"; + case kSuspendPhaseShutdown: + return "Shutdown"; + default: + return ""; + } +} + bool USBDevice::ReadTestDesc() { std::cout << "Reading Test Descriptor" << std::endl; @@ -257,12 +305,19 @@ bool USBDevice::ReadTestDesc() { << (int)dp[11] << std::dec << std::endl; } + // Although we needn't retain the test number, having checked it, the test + // phase does vary and determines the actions we must perform. + testPhase_ = (usbdev_suspend_phase_t)dp[8]; + // 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]; + + std::cout << "Test number: " << testNum << " Test Phase: " + << PhaseName((usbdev_suspend_phase_t)testArg_[0]) << std::endl; return true; } @@ -281,46 +336,128 @@ bool USBDevice::ReadTestDesc() { #endif } -bool USBDevice::Suspend() { - std::cout << "Suspending Device " << devPath_ << std::endl; +bool USBDevice::Delay(uint32_t time_us, bool with_traffic) { + while (time_us > 0) { + uint32_t delay_us = time_us; + if (verbose_) { + std::cout << "Delaying " << time_us << "us " + << (with_traffic ? " - with traffic" : "no traffic") + << std::endl; + } + if (with_traffic) { + delay_us = 1000u; + // Service streams + } + usleep(delay_us); + time_us -= delay_us; + } + return true; +} - // We need to relinquish our access to the device otherwise the kernel - // will refuse to autosuspend the device! - Close(); +bool USBDevice::Reset() { + std::cout << "Resetting Device" << std::endl; - std::string powerPath = "/sys/bus/usb/devices/" + devPath_ + "/power/"; - std::string filename = powerPath + "autosuspend_delay_ms"; + // We need to forget the device address in the event of a Bus Reset + // because it will be assigned a new address. + addrSpec_ = 0u; - 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; - } + if (parenth_) { + // Use the hub to reset the device. + const uint8_t bmRequestType = LIBUSB_ENDPOINT_OUT | + LIBUSB_REQUEST_TYPE_CLASS | + LIBUSB_RECIPIENT_OTHER; + if (verbose_) { + std::cout << "Resetting port " << portNumber_ << 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; + int rc = libusb_control_transfer(parenth_, bmRequestType, + LIBUSB_REQUEST_SET_FEATURE, PORT_RESET, + portNumber_, NULL, 0u, 0u); + if (rc) { + std::cerr << "Failed to reset device - " << libusb_error_name(rc) + << std::endl; + return false; + } + if (verbose_) { + std::cout << "Done port reset" << std::endl; + } + } else { + if (!Open()) { + return false; + } + + int rc = libusb_reset_device(devh_); + if (rc < 0) { + return false; + } } - rc = close(fd); - if (rc < 0) { - std::cerr << "Close failed" << std::endl; + return true; +} + +bool USBDevice::Suspend() { + std::cout << "Suspending Device " << devPath_ << std::endl; + + if (parenth_) { + // Use the hub to suspend the device. + const uint8_t bmRequestType = LIBUSB_ENDPOINT_OUT | + LIBUSB_REQUEST_TYPE_CLASS | + LIBUSB_RECIPIENT_OTHER; + if (verbose_) { + std::cout << "Suspending port " << portNumber_ << std::endl; + } + + int rc = libusb_control_transfer(parenth_, bmRequestType, + LIBUSB_REQUEST_SET_FEATURE, PORT_SUSPEND, + portNumber_, NULL, 0u, 0u); + if (rc) { + std::cerr << "Failed to suspend device - " << libusb_error_name(rc) + << std::endl; + return false; + } + if (verbose_) { + std::cout << "Done suspend" << std::endl; + } + } else { + // 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); @@ -330,35 +467,116 @@ bool USBDevice::Suspend() { bool USBDevice::Resume() { std::cout << "Resuming Device" << std::endl; - std::string powerPath = "/sys/bus/usb/devices/" + devPath_ + "/power/"; - std::string filename = powerPath + "control"; + if (parenth_) { + // Use the hub to suspend the device. + const uint8_t bmRequestType = LIBUSB_ENDPOINT_OUT | + LIBUSB_REQUEST_TYPE_CLASS | + LIBUSB_RECIPIENT_OTHER; + if (verbose_) { + std::cout << "Resuming port " << portNumber_ << std::endl; + } - 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; + int rc = libusb_control_transfer(parenth_, bmRequestType, + LIBUSB_REQUEST_CLEAR_FEATURE, PORT_SUSPEND, + portNumber_, NULL, 0u, 0u); + if (rc) { + std::cerr << "Failed to resume device - " << libusb_error_name(rc) + << std::endl; + return false; + } + if (verbose_) { + std::cout << "Done resume" << std::endl; + } + } else { + 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; + } } - close(fd); - if (!Open()) { + SetState(StateResuming); + return true; +} + +// Note: The Rust harness uses VBUS_SENSE_EN on the HyperDebug board +// (non-portable) to achieve connect and disconnect functionality, which we +// cannot achieve with regular hubs. +// +// Instead what happens with the available hub(s) is that all USB traffic +// to/from the USB ceases but the bus remains Idle/Suspended and VBUS remains +// asserted. Reconnect and the result is Reset Signaling and reconfiguration. + +bool USBDevice::Connect() { + std::cout << "Connecting Device " << devPath_ << std::endl; + + if (parenth_) { + // Use the hub to suspend the device. + const uint8_t bmRequestType = LIBUSB_ENDPOINT_OUT | + LIBUSB_REQUEST_TYPE_CLASS | + LIBUSB_RECIPIENT_OTHER; + if (verbose_) { + std::cout << "Connecting port " << portNumber_ << std::endl; + } + + int rc = libusb_control_transfer(parenth_, bmRequestType, + LIBUSB_REQUEST_SET_FEATURE, PORT_POWER, + portNumber_, NULL, 0u, 0u); + if (rc) { + std::cerr << "Failed to connect device - " << libusb_error_name(rc) + << std::endl; + return false; + } + if (verbose_) { + std::cout << "Done connect" << std::endl; + } + } else { + std::cout << "Connect operation not available" << std::endl; 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 + std::cout << "Disconnecting Device " << devPath_ << std::endl; - return false; + if (parenth_) { + // Use the hub to suspend the device. + const uint8_t bmRequestType = LIBUSB_ENDPOINT_OUT | + LIBUSB_REQUEST_TYPE_CLASS | + LIBUSB_RECIPIENT_OTHER; + if (verbose_) { + std::cout << "Disconnecting port " << portNumber_ << std::endl; + } + + int rc = libusb_control_transfer(parenth_, bmRequestType, + LIBUSB_REQUEST_CLEAR_FEATURE, PORT_POWER, + portNumber_, NULL, 0u, 0u); + if (rc) { + std::cerr << "Failed to disconnect device - " << libusb_error_name(rc) + << std::endl; + return false; + } + if (verbose_) { + std::cout << "Done disconnect" << std::endl; + } + } else { + std::cout << "Disconnect operation not available" << std::endl; + return false; + } + + return true; } diff --git a/sw/host/tests/usbdev/usbdev_stream/usb_device.h b/sw/host/tests/usbdev/usbdev_stream/usb_device.h index 5cbb915ebe1f4..23b7ecb6c2370 100644 --- a/sw/host/tests/usbdev/usbdev_stream/usb_device.h +++ b/sw/host/tests/usbdev/usbdev_stream/usb_device.h @@ -15,14 +15,18 @@ class USBDevice { public: #if STREAMTEST_LIBUSB - USBDevice(bool verbose = false) + USBDevice(bool verbose = false, bool manual = false) : verbose_(verbose), + manual_(manual), state_(StateStreaming), ctx_(nullptr), devh_(nullptr) {} #else - USBDevice(bool verbose = false) - : verbose_(verbose), state_(StateStreaming), devh_(false) {} + USBDevice(bool verbose = false, bool manual = false) + : verbose_(verbose), + manual_(manual), + state_(StateStreaming), + devh_(false) {} #endif // Device states. @@ -33,6 +37,50 @@ class USBDevice { StateResuming }; + /** + * Test phases; named according to the event that we are expecting to occur. + */ + typedef enum { + /** + * First test phase just tests regular Suspend/Resume signaling; after we've + * resumed, we expect a Bus Reset from the DPI/Host. + */ + kSuspendPhaseSuspend = 0u, + /** + * This test phase instructs the DPI model to put the DUT into Suspend long + * enough that this software will attempt to put the device into its Normal + * Sleep state and exercise the AON/Wakeup module, stopping the clocks but + * not powering down. + */ + kSuspendPhaseSleepResume, + /* + * The AON/Wakeup module will cause us to awaken in response to a bus reset. + */ + kSuspendPhaseSleepReset, + /** + * As above, but this time we're expecting a VBUS/SENSE loss. + */ + kSuspendPhaseSleepDisconnect, + /** + * Mirrors Resume detection for normal sleep, but this time we enter Deep + * Sleep and the power is removed too. + */ + kSuspendPhaseDeepResume, + /** + * Mirrors Bus Reset detection for normal sleep, but this time we enter Deep + * Sleep and the power is removed too. + */ + kSuspendPhaseDeepReset, + /** + * As above, but this time we're expecting a VBUS/SENSE loss. + */ + kSuspendPhaseDeepDisconnect, + /** + * Final phase; shut down. + */ + kSuspendPhaseShutdown, + } usbdev_suspend_phase_t; + // DPI test numbers. typedef enum usb_testutils_test_number { kUsbTestNumberSmoke = 0, @@ -243,7 +291,33 @@ class USBDevice { return libusb_cancel_transfer(xfr); } #endif - + /** + * Is test progress being directed/controlled manually? + */ + inline bool ManualControl() const { return manual_; } + /** + * Returns the current phase of the test + */ + inline usbdev_suspend_phase_t TestPhase() const { return testPhase_; } + /** + * Returns microseconds corresponding to the given number of bus frame delays. + */ + static inline uint32_t TimeFrames(unsigned frames) { return frames * 1000u; } + /** + * Delay for at least the requested number of microseconds (wall time); + * this is important in ensuring the appropriate time intervals (eg. Resume + * Signaling) are met. + * + * For verbose reporting, the also indicates whether traffic is expected, and + * may be visually paired up with any device-side logging or logic analyzer + * traces. + * + * @param time_us Minimum time delay, in microseconds. + * @param with_traffic Indicates whether USB traffic is expected during this + * time. + * @return true iff delayed without error + */ + bool Delay(uint32_t time_us, bool with_traffic = true); /** * Read Test Descriptor from the DUT using a Vendor-Specific command. * @@ -283,11 +357,24 @@ class USBDevice { */ bool Resume(); /** - * Disconnect and reconnect device. + * Connect device to the USB. + * + * @param true iff the operation was succesful. + */ + bool Connect(); + /** + * Disconnect device from the USB. * * @param true iff the operation was succesful. */ bool Disconnect(); + /** + * Return the textual name of the specified test phase. + * + * @param phase Test phase. + * @return Textual name. + */ + static const char *PhaseName(usbdev_suspend_phase_t phase); /** * Returns the current state of the device. * @@ -305,6 +392,9 @@ class USBDevice { // Verbose logging/reporting. bool verbose_; + // Test phases/behavior are being directed/controlled manually. + bool manual_; + // Current device state. USBDevState state_; @@ -330,6 +420,10 @@ class USBDevice { // Device handle. libusb_device_handle *devh_; + // Parent device handler. + libusb_device_handle *parenth_; + int portNumber_; + // Device descriptor. libusb_device_descriptor devDesc_; #else @@ -340,6 +434,9 @@ class USBDevice { // Device path (bus number - ports numbers). std::string devPath_; + // Current phase within the Suspend-Sleep-Wakeup-Resume testing sequence. + usbdev_suspend_phase_t testPhase_; + usb_testutils_test_number_t testNumber_; uint8_t testArg_[4]; From 2276e3a907bd1b8783d5de906fbe8b5ffda9d2ae Mon Sep 17 00:00:00 2001 From: Adrian Lees Date: Tue, 14 May 2024 19:08:57 +0100 Subject: [PATCH 2/5] [usbdev] Changes to support suspend-resume testing Changes to the usb_testutils layer to report link events as required by the suspend-resume top-level tests. Extend the usb_testutils_streams code to construct a USB configuration descriptor from the list of transfer types, permitting a single test to be more configurable. Signed-off-by: Adrian Lees --- sw/device/lib/dif/dif_usbdev.c | 2 + sw/device/lib/dif/dif_usbdev.h | 2 + sw/device/lib/testing/usb_testutils.c | 40 +++++++++ sw/device/lib/testing/usb_testutils.h | 28 ++++++ .../lib/testing/usb_testutils_controlep.c | 3 + sw/device/lib/testing/usb_testutils_streams.c | 90 +++++++++++++++++-- sw/device/lib/testing/usb_testutils_streams.h | 33 +++++-- 7 files changed, 188 insertions(+), 10 deletions(-) diff --git a/sw/device/lib/dif/dif_usbdev.c b/sw/device/lib/dif/dif_usbdev.c index cd94705b5bcca..7e46e358b5fbc 100644 --- a/sw/device/lib/dif/dif_usbdev.c +++ b/sw/device/lib/dif/dif_usbdev.c @@ -938,6 +938,8 @@ dif_result_t dif_usbdev_get_wake_status(const dif_usbdev_t *usbdev, bitfield_bit32_read(reg_val, USBDEV_WAKE_EVENTS_DISCONNECTED_BIT); status->bus_reset = bitfield_bit32_read(reg_val, USBDEV_WAKE_EVENTS_BUS_RESET_BIT); + status->bus_not_idle = + bitfield_bit32_read(reg_val, USBDEV_WAKE_EVENTS_BUS_NOT_IDLE_BIT); return kDifOk; } diff --git a/sw/device/lib/dif/dif_usbdev.h b/sw/device/lib/dif/dif_usbdev.h index 68a25e48e31cf..4325e33c2d500 100644 --- a/sw/device/lib/dif/dif_usbdev.h +++ b/sw/device/lib/dif/dif_usbdev.h @@ -823,6 +823,8 @@ typedef struct dif_usbdev_wake_status { bool disconnected; /** Whether the USB was reset while the AON wake module was active. */ bool bus_reset; + /** Whether the USB became non-Idle whilst the AON wake module was active. */ + bool bus_not_idle; } dif_usbdev_wake_status_t; /** diff --git a/sw/device/lib/testing/usb_testutils.c b/sw/device/lib/testing/usb_testutils.c index 80f2b00e6e942..8a7488ed2de5c 100644 --- a/sw/device/lib/testing/usb_testutils.c +++ b/sw/device/lib/testing/usb_testutils.c @@ -194,6 +194,25 @@ status_t usb_testutils_poll(usb_testutils_ctx_t *ctx) { ctx->got_frame = true; } + // Report all link events to the registered callback handler, if any + if (ctx->link_callback) { + const dif_usbdev_irq_state_snapshot_t kIrqsLink = + (1u << kDifUsbdevIrqPowered) | (1u << kDifUsbdevIrqDisconnected) | + (1u << kDifUsbdevIrqHostLost) | (1u << kDifUsbdevIrqLinkReset) | + (1u << kDifUsbdevIrqLinkSuspend) | (1u << kDifUsbdevIrqLinkResume) | + (1u << kDifUsbdevIrqFrame); + if (istate & kIrqsLink) { + // Retrieve and report the current link state, since this should help + // resolve any confusion caused by delayed reporting of earlier link + // events (eg. disconnect/powered, suspend/resume) + dif_usbdev_link_state_t link_state; + TRY(dif_usbdev_status_get_link_state(ctx->dev, &link_state)); + + // Indicate only the link-related interrupts + TRY(ctx->link_callback(ctx->ctx_link, istate & kIrqsLink, link_state)); + } + } + // Note: LinkInErr will be raised in response to a packet being NAKed by the // host which is not expected behavior on a physical USB but this is something // that the DPI model does to exercise packet resending when running @@ -250,6 +269,20 @@ status_t usb_testutils_poll(usb_testutils_ctx_t *ctx) { return OK_STATUS(); } +status_t usb_testutils_link_callback_register(usb_testutils_ctx_t *ctx, + usb_testutils_link_handler_t link, + void *ctx_link) { + CHECK(ctx != NULL); + + // Retain the new link callback handler and its context pointer; either of + // these may be NULL, respectively to deregister a handler or because no + // context is required. + ctx->link_callback = link; + ctx->ctx_link = ctx_link; + + return OK_STATUS(); +} + status_t usb_testutils_transfer_send(usb_testutils_ctx_t *ctx, uint8_t ep, const uint8_t *data, uint32_t length, usb_testutils_xfr_flags_t flags) { @@ -423,6 +456,9 @@ status_t usb_testutils_init(usb_testutils_ctx_t *ctx, bool pinflip, ctx->got_frame = false; ctx->frame = 0u; + // No callback handler for link events + ctx->link_callback = NULL; + TRY(dif_usbdev_init(mmio_region_from_addr(USBDEV_BASE_ADDR), ctx->dev)); dif_usbdev_config_t config = { @@ -446,6 +482,10 @@ status_t usb_testutils_init(usb_testutils_ctx_t *ctx, bool pinflip, // All about polling... TRY(dif_usbdev_irq_disable_all(ctx->dev, NULL)); + // Clear any outstanding interrupts such as Powered/Disconnected interrupts + // from when the usbdev came out of reset + TRY(dif_usbdev_irq_acknowledge_all(ctx->dev)); + // Provide buffers for any packet reception TRY(dif_usbdev_fill_available_fifos(ctx->dev, ctx->buffer_pool)); diff --git a/sw/device/lib/testing/usb_testutils.h b/sw/device/lib/testing/usb_testutils.h index e79b8ec213ef5..20c39075cfa7a 100644 --- a/sw/device/lib/testing/usb_testutils.h +++ b/sw/device/lib/testing/usb_testutils.h @@ -14,6 +14,11 @@ #include "sw/device/lib/testing/test_framework/check.h" #include "usb_testutils_diags.h" +// Have Data Toggle Restore functionality (Prod)? +#define USBDEV_HAVE_TOGGLE_STATE 0 +// Have separated Available OUT and SETUP Buffer FIFOs (Prod)? +#define USBDEV_HAVE_SEPARATED_FIFOS 0 + // Result codes to rx/tx callback handlers typedef enum { /** @@ -87,6 +92,9 @@ typedef status_t (*usb_testutils_rx_handler_t)(void *, typedef status_t (*usb_testutils_tx_flush_handler_t)(void *); /* Called when a USB link reset is detected */ typedef status_t (*usb_testutils_reset_handler_t)(void *); +/* Called when a link event has been detected */ +typedef status_t (*usb_testutils_link_handler_t)( + void *, dif_usbdev_irq_state_snapshot_t, dif_usbdev_link_state_t); // In-progress larger buffer transfer to/from host typedef struct usb_testutils_transfer { @@ -140,6 +148,14 @@ struct usb_testutils_ctx { * Most recent bus frame number received from host */ uint16_t frame; + /** + * Link event callback + */ + usb_testutils_link_handler_t link_callback; + /** + * Context pointer for link event callkback + */ + void *ctx_link; /** * IN endpoints @@ -324,6 +340,18 @@ OT_WARN_UNUSED_RESULT status_t usb_testutils_init(usb_testutils_ctx_t *ctx, bool pinflip, bool en_diff_rcvr, bool tx_use_d_se0); +/** + * Register a callback function to be notified of link events + * + * @param ctx initialized usb test utils context pointer + * @param link link event callback handler + * @param ctx_link context pointer for link event callback + */ +OT_WARN_UNUSED_RESULT +status_t usb_testutils_link_callback_register(usb_testutils_ctx_t *ctx, + usb_testutils_link_handler_t link, + void *ctx_link); + /** * Send a larger data transfer from the given endpoint * diff --git a/sw/device/lib/testing/usb_testutils_controlep.c b/sw/device/lib/testing/usb_testutils_controlep.c index 53e71c596dd56..23f3efc37e9d9 100644 --- a/sw/device/lib/testing/usb_testutils_controlep.c +++ b/sw/device/lib/testing/usb_testutils_controlep.c @@ -198,6 +198,9 @@ static usb_testutils_ctstate_t setup_req(usb_testutils_controlep_ctx_t *ctctx, if (wValue == kUsbFeatureEndpointHalt) { CHECK_DIF_OK(dif_usbdev_endpoint_stall_enable(ctx->dev, endpoint, kDifToggleDisabled)); + // Clearing the Halt feature on an endpoint that is using Data Toggling + // also requires us to clear the Data Toggle for that endpoint + CHECK_DIF_OK(dif_usbdev_clear_data_toggle(ctx->dev, endpoint.number)); // send zero length packet for status phase CHECK_DIF_OK(dif_usbdev_send(ctx->dev, ctctx->ep, &buffer)); return kUsbTestutilsCtStatIn; diff --git a/sw/device/lib/testing/usb_testutils_streams.c b/sw/device/lib/testing/usb_testutils_streams.c index df6e6900e66e9..940df1666da2d 100644 --- a/sw/device/lib/testing/usb_testutils_streams.c +++ b/sw/device/lib/testing/usb_testutils_streams.c @@ -16,10 +16,16 @@ #include "sw/device/lib/runtime/print.h" #include "sw/device/lib/testing/test_framework/check.h" +#include "sw/device/lib/testing/usb_testutils_controlep.h" #include "sw/device/lib/testing/usb_testutils_diags.h" #define MODULE_ID MAKE_MODULE_ID('u', 't', 's') +// Maximum length of the configuration descriptor. +#define CFG_DSCR_LEN_MAX \ + (USB_CFG_DSCR_LEN + \ + USBUTILS_STREAMS_MAX * (USB_INTERFACE_DSCR_LEN + 2 * USB_EP_DSCR_LEN)) + /** * Read method to be employed */ @@ -673,11 +679,10 @@ status_t usb_testutils_stream_service(usb_testutils_streams_ctx_t *ctx, return OK_STATUS(); } -status_t usb_testutils_streams_init(usb_testutils_streams_ctx_t *ctx, - unsigned nstreams, - usb_testutils_transfer_type_t xfr_types[], - uint32_t num_bytes, - usbdev_stream_flags_t flags, bool verbose) { +status_t usb_testutils_streams_init( + usb_testutils_streams_ctx_t *ctx, unsigned nstreams, + const usb_testutils_transfer_type_t xfr_types[], uint32_t num_bytes, + usbdev_stream_flags_t flags, bool verbose) { TRY_CHECK(nstreams <= USBUTILS_STREAMS_MAX); TRY_CHECK(nstreams <= UINT8_MAX); @@ -697,6 +702,81 @@ status_t usb_testutils_streams_init(usb_testutils_streams_ctx_t *ctx, return OK_STATUS(); } +status_t usb_testutils_streams_typed_init( + usb_testutils_streams_ctx_t *ctx, uint8_t *cfg, uint16_t len, + unsigned nstreams, const usb_testutils_transfer_type_t xfr_types[], + uint32_t num_bytes, usbdev_stream_flags_t flags, bool verbose, + uint32_t *types) { + TRY_CHECK(nstreams <= USBUTILS_STREAMS_MAX); + + // Does the caller require a bitmap of the transfer type(s) collected? + if (types) { + *types = 0U; + } + + // Total length of the configuration descriptor; validate caller buffer. + size_t cfg_len = USB_CFG_DSCR_LEN + + nstreams * (USB_INTERFACE_DSCR_LEN + 2 * USB_EP_DSCR_LEN); + TRY_CHECK(cfg && cfg_len <= len); + + // Configuration Descriptor header. + uint8_t head[CFG_DSCR_LEN_MAX] = { + USB_CFG_DSCR_HEAD((uint16_t)cfg_len, (uint8_t)nstreams)}; + memcpy(cfg, head, USB_CFG_DSCR_LEN); + cfg += USB_CFG_DSCR_LEN; + + // Followed by programmatically-generated list of interface descriptors. + for (uint8_t id = 0U; id < nstreams; id++) { + usb_testutils_transfer_type_t xfr_type = xfr_types[id]; + // Return to the caller the transfer type of each stream in turn. + if (types) { + *types |= xfr_type << (id * 2U); + } + + uint8_t ep_in = (uint8_t)(id + 1U); + uint8_t ep_out = (uint8_t)(id + 1U); + TRY(usb_testutils_stream_init(ctx, id, xfr_type, ep_in, ep_out, num_bytes, + flags, verbose)); + + // Isochronous and Interrupt endpoints require a bInterval value of 1. + uint8_t bInterval = (xfr_type == kUsbTransferTypeIsochronous || + xfr_type == kUsbTransferTypeInterrupt); + + // Description of a single interface. + uint8_t int_dscr[USB_INTERFACE_DSCR_LEN + 2 * USB_EP_DSCR_LEN] = { + VEND_INTERFACE_DSCR(id, 2, 0x50, 1), + USB_EP_DSCR(0, ep_out, (uint8_t)xfr_type, USBDEV_MAX_PACKET_SIZE, + bInterval), + USB_EP_DSCR(1, ep_in, (uint8_t)xfr_type, USBDEV_MAX_PACKET_SIZE, + bInterval), + }; + + // Append interface descriptor to the configuration descriptor. + memcpy(cfg, int_dscr, sizeof(int_dscr)); + cfg += sizeof(int_dscr); + + if (verbose) { + /** + * Indexed by usb_testutils_transfer_type_t + */ + static const char *xfr_name[] = { + "Control", + "Isochronous", + "Bulk", + "Interrupt", + }; + TRY_CHECK(xfr_type <= ARRAYSIZE(xfr_name)); + LOG_INFO("S%u: IN %u:OUT %u : %s - 0x%x bytes flags 0x%x", id, ep_in, + ep_out, xfr_name[xfr_type], num_bytes, flags); + } + } + + // Remember the stream count and apportion the available tx buffers + TRY_CHECK(usb_testutils_streams_count_set(ctx, nstreams)); + + return OK_STATUS(); +} + status_t usb_testutils_streams_service(usb_testutils_streams_ctx_t *ctx) { TRY_CHECK(ctx->nstreams <= UINT8_MAX); diff --git a/sw/device/lib/testing/usb_testutils_streams.h b/sw/device/lib/testing/usb_testutils_streams.h index 915e88cc97663..a61a359d0ab0d 100644 --- a/sw/device/lib/testing/usb_testutils_streams.h +++ b/sw/device/lib/testing/usb_testutils_streams.h @@ -295,11 +295,10 @@ struct usb_testutils_streams_ctx { * @return The result status of the operation. */ OT_WARN_UNUSED_RESULT -status_t usb_testutils_streams_init(usb_testutils_streams_ctx_t *ctx, - unsigned nstreams, - usb_testutils_transfer_type_t xfr_types[], - uint32_t num_bytes, - usbdev_stream_flags_t flags, bool verbose); +status_t usb_testutils_streams_init( + usb_testutils_streams_ctx_t *ctx, unsigned nstreams, + const usb_testutils_transfer_type_t xfr_types[], uint32_t num_bytes, + usbdev_stream_flags_t flags, bool verbose); /** * Service all streams, preparing and/or sending any data that we can, as well @@ -343,6 +342,30 @@ status_t usb_testutils_stream_init(usb_testutils_streams_ctx_t *ctx, uint8_t id, uint32_t num_bytes, usbdev_stream_flags_t flags, bool verbose); +/** + * Initialize a set of streams of specified types, dynamically constructing a + * standard USB configuration descriptor for the caller. The transfer types of + * the streams may optionally be returned via `types` for passing to the DPI + * model/host. + * + * @param ctx Context state for streaming test. + * @param cfg Receives the configuration descriptor. + * @param len Size of buffer receiving the configuration descriptor. + * @param nstreams Number of streams to be initialized. + * @param xfr_types The transfer types of the streams. + * @param num_bytes Number of bytes to be transferred by stream + * @param flags Stream/test flags + * @param verbose Whether to perform verbose logging for this stream + * @param types Optionally receives the bitmap of transfer types. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +status_t usb_testutils_streams_typed_init( + usb_testutils_streams_ctx_t *ctx, uint8_t *cfg, uint16_t len, + unsigned nstreams, const usb_testutils_transfer_type_t xfr_types[], + uint32_t num_bytes, usbdev_stream_flags_t flags, bool verbose, + uint32_t *types); + /** * Specify the number of already-initialized streams, and apportion the * available tx buffers among them. To be called after usb_testutils_stream_init From b2d0a216ded9f069575e6c962b1bd3923c61fcb3 Mon Sep 17 00:00:00 2001 From: Adrian Lees Date: Tue, 14 May 2024 19:11:14 +0100 Subject: [PATCH 3/5] [tests,usbdev] Top-level suspend-resume tests Suspend-resume library code used in a number of top-level tests for exercising the various combinations of suspend/sleep states and USB stimuli. Individual top-level tests are very compact and just walk through shortened sequences of test phases to make chip-level simulation times feasible. Presently configured for ES USBDEV, but also supporting Prod. Signed-off-by: Adrian Lees --- sw/device/tests/BUILD | 349 +++ sw/device/tests/usbdev_deep_disconnect_test.c | 13 + sw/device/tests/usbdev_deep_reset_test.c | 13 + sw/device/tests/usbdev_deep_resume_test.c | 13 + .../tests/usbdev_sleep_disconnect_test.c | 13 + sw/device/tests/usbdev_sleep_reset_test.c | 13 + sw/device/tests/usbdev_sleep_resume_test.c | 13 + sw/device/tests/usbdev_suspend.c | 1970 +++++++++++++++++ sw/device/tests/usbdev_suspend.h | 84 + sw/device/tests/usbdev_suspend_full_test.c | 16 + sw/device/tests/usbdev_suspend_resume_test.c | 13 + 11 files changed, 2510 insertions(+) create mode 100644 sw/device/tests/usbdev_deep_disconnect_test.c create mode 100644 sw/device/tests/usbdev_deep_reset_test.c create mode 100644 sw/device/tests/usbdev_deep_resume_test.c create mode 100644 sw/device/tests/usbdev_sleep_disconnect_test.c create mode 100644 sw/device/tests/usbdev_sleep_reset_test.c create mode 100644 sw/device/tests/usbdev_sleep_resume_test.c create mode 100644 sw/device/tests/usbdev_suspend.c create mode 100644 sw/device/tests/usbdev_suspend.h create mode 100644 sw/device/tests/usbdev_suspend_full_test.c create mode 100644 sw/device/tests/usbdev_suspend_resume_test.c diff --git a/sw/device/tests/BUILD b/sw/device/tests/BUILD index 274306ee40a1d..c4269566fa2ee 100644 --- a/sw/device/tests/BUILD +++ b/sw/device/tests/BUILD @@ -4796,6 +4796,355 @@ opentitan_test( ], ) +cc_library( + name = "usbdev_suspend", + srcs = ["usbdev_suspend.c"], + hdrs = ["usbdev_suspend.h"], + target_compatible_with = [OPENTITAN_CPU], + deps = [ + "//hw/top_earlgrey/sw/autogen:top_earlgrey", + "//sw/device/lib/dif:pinmux", + "//sw/device/lib/dif:pwrmgr", + "//sw/device/lib/dif:rstmgr", + "//sw/device/lib/dif:rv_plic", + "//sw/device/lib/dif:usbdev", + "//sw/device/lib/runtime:log", + "//sw/device/lib/runtime:print", + "//sw/device/lib/testing:isr_testutils", + "//sw/device/lib/testing:pinmux_testutils", + "//sw/device/lib/testing:pwrmgr_testutils", + "//sw/device/lib/testing:rstmgr_testutils", + "//sw/device/lib/testing:rv_plic_testutils", + "//sw/device/lib/testing:sram_ctrl_testutils", + "//sw/device/lib/testing:usb_testutils", + "//sw/device/lib/testing:usb_testutils_streams", + "//sw/device/lib/testing/test_framework:ottf_main", + "//sw/device/silicon_creator/lib/drivers:retention_sram", + ], +) + +opentitan_test( + name = "usbdev_suspend_resume_test", + srcs = [ + "usbdev_suspend_resume_test.c", + ], + cw310 = cw310_params( + timeout = "eternal", + tags = ["manual"], + test_cmd = """ + --bootstrap="{firmware}" + --fin-phase=suspend + --init-phase=suspend + """, + test_harness = "//sw/host/tests/chip/usb:usbdev_suspend", + ), + exec_env = dicts.add( + EARLGREY_TEST_ENVS, + EARLGREY_SILICON_OWNER_ROM_EXT_ENVS, + { + "//hw/top_earlgrey:fpga_cw310_sival_rom_ext": None, + "//hw/top_earlgrey:fpga_cw310_test_rom": None, + "//hw/top_earlgrey:sim_dv": None, + }, + ), + silicon = silicon_params( + test_cmd = """ + --bootstrap="{firmware}" + --fin-phase=suspend + --init-phase=suspend + --vbus-sense-en=VBUS_SENSE_EN + --vbus-sense=VBUS_SENSE + """, + test_harness = "//sw/host/tests/chip/usb:usbdev_suspend", + ), + verilator = verilator_params(timeout = "long"), + deps = [ + "//sw/device/tests:usbdev_suspend", + ], +) + +opentitan_test( + name = "usbdev_sleep_resume_test", + srcs = [ + "usbdev_sleep_resume_test.c", + ], + cw310 = cw310_params( + timeout = "eternal", + tags = ["manual"], + test_cmd = """ + --bootstrap="{firmware}" + --fin-phase=sleep-resume + --init-phase=sleep-resume + """, + test_harness = "//sw/host/tests/chip/usb:usbdev_suspend", + ), + exec_env = dicts.add( + EARLGREY_TEST_ENVS, + EARLGREY_SILICON_OWNER_ROM_EXT_ENVS, + { + "//hw/top_earlgrey:fpga_cw310_sival_rom_ext": None, + "//hw/top_earlgrey:fpga_cw310_test_rom": None, + "//hw/top_earlgrey:sim_dv": None, + }, + ), + silicon = silicon_params( + test_cmd = """ + --bootstrap="{firmware}" + --fin-phase=sleep-resume + --init-phase=sleep-resume + --vbus-sense-en=VBUS_SENSE_EN + --vbus-sense=VBUS_SENSE + """, + test_harness = "//sw/host/tests/chip/usb:usbdev_suspend", + ), + verilator = verilator_params(timeout = "long"), + deps = [ + "//sw/device/lib/testing/test_framework:ottf_main", + "//sw/device/tests:usbdev_suspend", + ], +) + +opentitan_test( + name = "usbdev_sleep_reset_test", + srcs = [ + "usbdev_sleep_reset_test.c", + ], + cw310 = cw310_params( + timeout = "eternal", + tags = ["manual"], + test_cmd = """ + --bootstrap="{firmware}" + --fin-phase=sleep-reset + --init-phase=sleep-resume + """, + test_harness = "//sw/host/tests/chip/usb:usbdev_suspend", + ), + exec_env = dicts.add( + EARLGREY_TEST_ENVS, + EARLGREY_SILICON_OWNER_ROM_EXT_ENVS, + { + "//hw/top_earlgrey:fpga_cw310_sival_rom_ext": None, + "//hw/top_earlgrey:fpga_cw310_test_rom": None, + "//hw/top_earlgrey:sim_dv": None, + }, + ), + silicon = silicon_params( + test_cmd = """ + --bootstrap="{firmware}" + --fin-phase=sleep-reset + --init-phase=sleep-resume + --vbus-sense-en=VBUS_SENSE_EN + --vbus-sense=VBUS_SENSE + """, + test_harness = "//sw/host/tests/chip/usb:usbdev_suspend", + ), + verilator = verilator_params(timeout = "long"), + deps = [ + "//sw/device/lib/testing/test_framework:ottf_main", + "//sw/device/tests:usbdev_suspend", + ], +) + +opentitan_test( + name = "usbdev_sleep_disconnect_test", + srcs = [ + "usbdev_sleep_disconnect_test.c", + ], + cw310 = cw310_params( + timeout = "eternal", + tags = ["manual"], + test_cmd = """ + --bootstrap="{firmware}" + --fin-phase=deep-resume + --init-phase=sleep-disconnect + """, + test_harness = "//sw/host/tests/chip/usb:usbdev_suspend", + ), + exec_env = dicts.add( + EARLGREY_TEST_ENVS, + EARLGREY_SILICON_OWNER_ROM_EXT_ENVS, + { + "//hw/top_earlgrey:fpga_cw310_sival_rom_ext": None, + "//hw/top_earlgrey:fpga_cw310_test_rom": None, + "//hw/top_earlgrey:sim_dv": None, + }, + ), + silicon = silicon_params( + test_cmd = """ + --bootstrap="{firmware}" + --fin-phase=deep-resume + --init-phase=sleep-disconnect + --vbus-sense-en=VBUS_SENSE_EN + --vbus-sense=VBUS_SENSE + """, + test_harness = "//sw/host/tests/chip/usb:usbdev_suspend", + ), + verilator = verilator_params(timeout = "long"), + deps = [ + "//sw/device/lib/testing/test_framework:ottf_main", + "//sw/device/tests:usbdev_suspend", + ], +) + +opentitan_test( + name = "usbdev_deep_resume_test", + srcs = [ + "usbdev_deep_resume_test.c", + ], + cw310 = cw310_params( + timeout = "eternal", + tags = ["manual"], + test_cmd = """ + --bootstrap="{firmware}" + --fin-phase=deep-resume + --init-phase=deep-resume + """, + test_harness = "//sw/host/tests/chip/usb:usbdev_suspend", + ), + exec_env = dicts.add( + EARLGREY_TEST_ENVS, + EARLGREY_SILICON_OWNER_ROM_EXT_ENVS, + { + "//hw/top_earlgrey:fpga_cw310_sival_rom_ext": None, + "//hw/top_earlgrey:fpga_cw310_test_rom": None, + "//hw/top_earlgrey:sim_dv": None, + }, + ), + silicon = silicon_params( + test_cmd = """ + --bootstrap="{firmware}" + --fin-phase=deep-resume + --init-phase=deep-resume + --vbus-sense-en=VBUS_SENSE_EN + --vbus-sense=VBUS_SENSE + """, + test_harness = "//sw/host/tests/chip/usb:usbdev_suspend", + ), + verilator = verilator_params(timeout = "long"), + deps = [ + "//sw/device/lib/testing/test_framework:ottf_main", + "//sw/device/tests:usbdev_suspend", + ], +) + +opentitan_test( + name = "usbdev_deep_reset_test", + srcs = [ + "usbdev_deep_reset_test.c", + ], + cw310 = cw310_params( + timeout = "eternal", + tags = ["manual"], + test_cmd = """ + --bootstrap="{firmware}" + --fin-phase=deep-reset + --init-phase=deep-resume + """, + test_harness = "//sw/host/tests/chip/usb:usbdev_suspend", + ), + exec_env = dicts.add( + EARLGREY_TEST_ENVS, + EARLGREY_SILICON_OWNER_ROM_EXT_ENVS, + { + "//hw/top_earlgrey:fpga_cw310_sival_rom_ext": None, + "//hw/top_earlgrey:fpga_cw310_test_rom": None, + "//hw/top_earlgrey:sim_dv": None, + }, + ), + silicon = silicon_params( + test_cmd = """ + --bootstrap="{firmware}" + --fin-phase=deep-reset + --init-phase=deep-resume + --vbus-sense-en=VBUS_SENSE_EN + --vbus-sense=VBUS_SENSE + """, + test_harness = "//sw/host/tests/chip/usb:usbdev_suspend", + ), + verilator = verilator_params(timeout = "long"), + deps = [ + "//sw/device/lib/testing/test_framework:ottf_main", + "//sw/device/tests:usbdev_suspend", + ], +) + +opentitan_test( + name = "usbdev_deep_disconnect_test", + srcs = [ + "usbdev_deep_disconnect_test.c", + ], + cw310 = cw310_params( + timeout = "eternal", + tags = ["manual"], + test_cmd = """ + --bootstrap="{firmware}" + --fin-phase=shutdown + --init-phase=deep-disconnect + """, + test_harness = "//sw/host/tests/chip/usb:usbdev_suspend", + ), + exec_env = dicts.add( + EARLGREY_TEST_ENVS, + EARLGREY_SILICON_OWNER_ROM_EXT_ENVS, + { + "//hw/top_earlgrey:fpga_cw310_sival_rom_ext": None, + "//hw/top_earlgrey:fpga_cw310_test_rom": None, + "//hw/top_earlgrey:sim_dv": None, + }, + ), + silicon = silicon_params( + test_cmd = """ + --bootstrap="{firmware}" + --fin-phase=shutdown + --init-phase=deep-disconnect + --vbus-sense-en=VBUS_SENSE_EN + --vbus-sense=VBUS_SENSE + """, + test_harness = "//sw/host/tests/chip/usb:usbdev_suspend", + ), + verilator = verilator_params(timeout = "long"), + deps = [ + "//sw/device/lib/testing/test_framework:ottf_main", + "//sw/device/tests:usbdev_suspend", + ], +) + +opentitan_test( + name = "usbdev_suspend_full_test", + srcs = [ + "usbdev_suspend_full_test.c", + ], + cw310 = cw310_params( + timeout = "eternal", + tags = ["manual"], + test_cmd = """ + --bootstrap="{firmware}" + --init-phase=suspend + """, + exec_env = dicts.add( + EARLGREY_TEST_ENVS, + EARLGREY_SILICON_OWNER_ROM_EXT_ENVS, + { + "//hw/top_earlgrey:sim_dv": None, + }, + ), + silicon = silicon_params( + test_cmd = """ + --bootstrap="{firmware}" + --fin-phase=shutdown + --init-phase=suspend + --vbus-sense-en=VBUS_SENSE_EN + --vbus-sense=VBUS_SENSE + """, + test_harness = "//sw/host/tests/chip/usb:usbdev_suspend", + ), + verilator = verilator_params(timeout = "long"), + deps = [ + "//sw/device/lib/testing/test_framework:ottf_main", + "//sw/device/tests:usbdev_suspend", + ], +) + opentitan_test( name = "rstmgr_alert_info_test", srcs = ["rstmgr_alert_info_test.c"], diff --git a/sw/device/tests/usbdev_deep_disconnect_test.c b/sw/device/tests/usbdev_deep_disconnect_test.c new file mode 100644 index 0000000000000..43294d3f5b5c4 --- /dev/null +++ b/sw/device/tests/usbdev_deep_disconnect_test.c @@ -0,0 +1,13 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#include "sw/device/lib/testing/test_framework/ottf_main.h" +#include "sw/device/tests/usbdev_suspend.h" + +OTTF_DEFINE_TEST_CONFIG(); + +bool test_main(void) { + return usbdev_suspend_test(kSuspendPhaseDeepDisconnect, kSuspendPhaseShutdown, + 1u, false); +} diff --git a/sw/device/tests/usbdev_deep_reset_test.c b/sw/device/tests/usbdev_deep_reset_test.c new file mode 100644 index 0000000000000..aea5ec49143eb --- /dev/null +++ b/sw/device/tests/usbdev_deep_reset_test.c @@ -0,0 +1,13 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#include "sw/device/lib/testing/test_framework/ottf_main.h" +#include "sw/device/tests/usbdev_suspend.h" + +OTTF_DEFINE_TEST_CONFIG(); + +bool test_main(void) { + return usbdev_suspend_test(kSuspendPhaseDeepResume, kSuspendPhaseDeepReset, + 1u, false); +} diff --git a/sw/device/tests/usbdev_deep_resume_test.c b/sw/device/tests/usbdev_deep_resume_test.c new file mode 100644 index 0000000000000..444fd322f5490 --- /dev/null +++ b/sw/device/tests/usbdev_deep_resume_test.c @@ -0,0 +1,13 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#include "sw/device/lib/testing/test_framework/ottf_main.h" +#include "sw/device/tests/usbdev_suspend.h" + +OTTF_DEFINE_TEST_CONFIG(); + +bool test_main(void) { + return usbdev_suspend_test(kSuspendPhaseDeepResume, kSuspendPhaseDeepResume, + 1u, true); +} diff --git a/sw/device/tests/usbdev_sleep_disconnect_test.c b/sw/device/tests/usbdev_sleep_disconnect_test.c new file mode 100644 index 0000000000000..1b7d32a6b1436 --- /dev/null +++ b/sw/device/tests/usbdev_sleep_disconnect_test.c @@ -0,0 +1,13 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#include "sw/device/lib/testing/test_framework/ottf_main.h" +#include "sw/device/tests/usbdev_suspend.h" + +OTTF_DEFINE_TEST_CONFIG(); + +bool test_main(void) { + return usbdev_suspend_test(kSuspendPhaseSleepDisconnect, + kSuspendPhaseDeepResume, 1u, false); +} diff --git a/sw/device/tests/usbdev_sleep_reset_test.c b/sw/device/tests/usbdev_sleep_reset_test.c new file mode 100644 index 0000000000000..2138a65197317 --- /dev/null +++ b/sw/device/tests/usbdev_sleep_reset_test.c @@ -0,0 +1,13 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#include "sw/device/lib/testing/test_framework/ottf_main.h" +#include "sw/device/tests/usbdev_suspend.h" + +OTTF_DEFINE_TEST_CONFIG(); + +bool test_main(void) { + return usbdev_suspend_test(kSuspendPhaseSleepResume, kSuspendPhaseSleepReset, + 1u, false); +} diff --git a/sw/device/tests/usbdev_sleep_resume_test.c b/sw/device/tests/usbdev_sleep_resume_test.c new file mode 100644 index 0000000000000..27da11affb25d --- /dev/null +++ b/sw/device/tests/usbdev_sleep_resume_test.c @@ -0,0 +1,13 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#include "sw/device/lib/testing/test_framework/ottf_main.h" +#include "sw/device/tests/usbdev_suspend.h" + +OTTF_DEFINE_TEST_CONFIG(); + +bool test_main(void) { + return usbdev_suspend_test(kSuspendPhaseSleepResume, kSuspendPhaseSleepResume, + 1u, false); +} diff --git a/sw/device/tests/usbdev_suspend.c b/sw/device/tests/usbdev_suspend.c new file mode 100644 index 0000000000000..d82ee27ddbfd1 --- /dev/null +++ b/sw/device/tests/usbdev_suspend.c @@ -0,0 +1,1970 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// USB suspend/resume test +// +// Basic test of suspend/resume signaling and disconnect/reconnect behavior. +// The DPI model sets up the device and reads the test descriptor. Having +// ascertained the specific testing behavior that is required, it proceeds to +// run through the appropriate states, time delays and bus activity as +// illustrated below. +// +//----------------------------------------------------------------------------- +// Test state flow +//----------------------------------------------------------------------------- +// +// [PhaseSuspend] +// +// Power On Reset -> Configuration -> DPI drops SOF -> Suspend -> Resume -> +// -> DPI performs Bus Reset ... +// +// Three 'Normal Sleep' phases may be started from Power On Reset (to aid dev), +// or a Bus Reset if a Bus Reset or Disconnect was the most recent wake +// stimulus. If the wake stimulus was Resume Signaling, then configuration does +// not occur again and we arrive via NextPhase +// +// [PhaseSleepResume] +// [PhaseSleepReset] +// [PhaseSleepDisconnect] +// +// ... Bus Reset -> +// Power On Reset -> Configuration -> +// [from prev phase] ... -> DPI drops SOF -> Suspend -> Activate AON +// -> Normal Sleep -> DPI produces wake stimulus -> Wakeup +// -> Deactivate AON ... [to next] +// +// [PhaseDeepResume] +// [PhaseDeepReset] +// [PhaseDeepDisconnect] +// +// ... Bus Reset -> +// Power On Reset -> Configuration -> +// [from prev phase] ... -> DPI drops SOF -> Suspend -> Activate AON +// -> Deep Sleep -> DPI produces wake stimulus -> Wakeup +// -> Deactivate AON .. [to next] +// +// [PhaseShutdown] Test Complete +// +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Summary notes from USB 2.0 Specification to aid apprehension (see the spec. +// itself for details): +// +// Device starts transitioning to the Suspend state after observing a constant +// Idle state on bus for more than 3.0ms, and must be in Suspend state after no +// more than 10ms of bus inactivity. Pull-up resistor must remain asserted. +// +// Resuming from Suspend state is achieved by the host at any time by any non- +// signaling, for at leasat 20ms. This is followed by a standard, low-speed EOP +// (two low-speed bit times of SE0 followed by a J). Traffic, even if only SOF, +// must be resumed within 3ms after entering the Idle state (J), to prevent +// the device re-entering Suspend. +// Additionally, the host must provide a 10ms resume recovery time during which +// it will not attempt to access the device. +// +// Suspending/Resuming may occur from any Device State (Powered, Default, +// Address or Configured), and the device returns to its pre-Suspend state. +//----------------------------------------------------------------------------- + +#include "sw/device/tests/usbdev_suspend.h" + +#include +#include + +#include "sw/device/lib/dif/dif_pinmux.h" +#include "sw/device/lib/dif/dif_pwrmgr.h" +#include "sw/device/lib/dif/dif_rstmgr.h" +#include "sw/device/lib/dif/dif_rv_plic.h" +#include "sw/device/lib/runtime/ibex.h" +#include "sw/device/lib/runtime/irq.h" +#include "sw/device/lib/runtime/log.h" +#include "sw/device/lib/runtime/print.h" +#include "sw/device/lib/testing/pinmux_testutils.h" +#include "sw/device/lib/testing/pwrmgr_testutils.h" +#include "sw/device/lib/testing/rstmgr_testutils.h" +#include "sw/device/lib/testing/rv_plic_testutils.h" +#include "sw/device/lib/testing/sram_ctrl_testutils.h" +#include "sw/device/lib/testing/test_framework/check.h" +#include "sw/device/lib/testing/test_framework/ottf_main.h" +#include "sw/device/lib/testing/usb_testutils.h" +#include "sw/device/lib/testing/usb_testutils_controlep.h" +#include "sw/device/lib/testing/usb_testutils_streams.h" +#include "sw/device/silicon_creator/lib/drivers/retention_sram.h" + +#include "hw/top_earlgrey/sw/autogen/top_earlgrey.h" // Generated. +#include "sw/device/lib/testing/autogen/isr_testutils.h" + +#define MODULE_ID MAKE_MODULE_ID('u', 'd', 'u') + +// Set up the recording of function points for this module +#define USBUTILS_FUNCPT_FILE USBUTILS_FUNCPT_FILE_USBDEV_SUSP_TEST + +// Are we using purely Isochronous streams for testing? +#define USE_ISO_STREAMS 0 +// Are we using purely Bulk streams for testing? +#define USE_BULK_STREAMS 1 +// Otherwise, we'll use a mixture + +// The number of streams to be employed if testing with traffic +#if USB_ISO_STREAMS +#define NUM_STREAMS 4U +#else +#define NUM_STREAMS USBUTILS_STREAMS_MAX +#endif + +// Are we expecting a full frame interval? +// Note: this must match the setting in the DPI model, but we do not have the +// ability to share header files. +#ifndef USBDPI_FULL_FRAME +// By default we shorten the test duration. +#define USBDPI_FULL_FRAME 0 +#endif + +// TODO: these are now set appropriately for automated testing on FPGA/ES. +// We need three sets of parameters: +// - chip simulation +// - FPGA/Silicon with automated host code +// - FPGA/Silicon with manual test stimulus + +/** + * Timeout constants in microseconds; + */ +enum { + TimeoutResumeMissed = 40U * 1000U, + TimeoutResetMissed = 60U * 1000U, + TimeoutWakeupResume = 30000u, + TimeoutFinishMissed = 2000u, + TimeoutAonResponse = 5000u, + TimeoutSleepFailed = 5000u, + + // How long should we wait for configuration to occur if observing physical + // timings? (Real USB host or physically-accurate timings in simulation.) + TimeoutPhysConfig = 10U * 1000U * 1000U, + + /** + * Durations that are specified in terms of bus frames, however long those + * simulated bus frames happen to be (ie. these intervals are determined by + * the DPI host behavior rather than the USB protocol specification) + */ + FramesConfigDelay = 2000u, + FramesSuspendMissed = 2000u, + FramesInitiateResume = 2u, + + /** + * Number of frame delays to wait after device signals Suspend, before we + * initiate sleeping. + */ + FramesWaitEnterSleep = 10u, +}; + +/** + * Maximum size of the client state stored within the Retention SRAM; + * we leave the usb_testutils_streams/other code to specify and manage its own + * state. + */ +#define MAX_CLIENT_STATE 0x400U + +/** + * Test states + */ +typedef enum { + /** + * Power On Reset (start of first phase of test sequence). + */ + kSuspendStatePowerOnReset = 0u, + /** + * Bus Reset from DPI/Host has occurred. + */ + kSuspendStateBusReset, + /** + * Waiting for the DPI/Host to suspend the device, for normal Suspend/Resume + * behavior, not involving AON/Wakeup functionality. + */ + kSuspendStateWaitSuspend, + /** + * Waiting for the DPI/Host to suspend the device, expecting a longer suspend + * period, during which we put the device into Normal/Deep Sleep using the + * AON/Wakeup functionality. + */ + kSuspendStateWaitLongSuspend, + /** + * Waiting for the DPI/Host to initiate a Resume from Suspended. + */ + kSuspendStateWaitResume, + /** + * Waiting for the DPI/Host to provide the Bus Reset stimulus. + */ + kSuspendStateWaitBusReset, + /** + * Waiting whilst Suspended, until we decide to enter Normal/Deep Sleep. + * The DPI/Host is not expected to resume communication during this time. + */ + kSuspendStateWaitSuspendTimeout, + /** + * We have instructed the AON/Wakeup module to wake over control of the USB + * signals. It does not do so immediately because it lives in a slower clock + * domain, but the delay should be very short. + */ + kSuspendStateActivatedAON, + /** + * We are expecting to fall into a Normal Sleep. + */ + kSuspendStateNormalSleep, + /** + * We are expecting to fall into a Deep Sleep. + */ + kSuspendStateDeepSleep, + /** + * We have just returned from a Normal Sleep. + */ + kSuspendStateNormalWaking, + /** + * We have just returned from a Deep Sleep. + */ + kSuspendStateDeepWaking, + /** + * We've instructed the AON/Wakeup module to relinquish its control of the + * USB and deactivate. + */ + kSuspendStateAONWakeup, + /** + * TODO: Resume Signaling still needs completing... + */ + kSuspendStateWaitResumeTimeout, + /** + * Waiting for the DPI/Host to decide that the test phase is complete. + */ + kSuspendStateWaitFinish, + /** + * Transition to next test phase, with the device still connected and + * operational, ie. Resume Signaling has occurred. + */ + kSuspendStateNextPhase, + /** + * Disconnecting from the bus. + */ + kSuspendStateWaitDisconnect, + /** + * Test completed successfully. + */ + kSuspendStateComplete, + /** + * Test failed. + */ + kSuspendStateFailed, +} usbdev_suspend_state_t; + +/** + * Retained state; to be held in the Retention SRAM during Deep Sleep + */ +typedef struct { + /** + * Host-suppplied device address on the USB. + */ + uint8_t dev_address; + /** + * Selected device configuration number. + */ + uint8_t dev_config; + /** + * Test phase. + */ + uint8_t test_phase; + /** + * Unused padding. + */ + uint8_t pad0; + /** + * Number of remaining test iterations. + */ + uint32_t num_iters; + /** + * Data Toggle bits. + */ + uint32_t data_toggles; + /** + * Used bytes of client state. + */ + uint32_t client_used; + /** + * Client state; allow, for example, the usb_testutils_streams code to specify + * its own per-stream retention state rather than constraining it here. + */ + alignas(uint32_t) uint8_t client_state[MAX_CLIENT_STATE]; +} usbdev_retn_state_t; + +/** + * Test context + */ +typedef struct usbdev_suspend_ctx { + /** + * Access to usb_testutils context + */ + usb_testutils_ctx_t *usbdev; + /** + * Current test state + */ + usbdev_suspend_state_t test_state; + /** + * Current test phase + */ + usbdev_suspend_phase_t test_phase; + /** + * Initial test phase (inclusive) + */ + usbdev_suspend_phase_t init_phase; + /** + * Final test phase (inclusive) + */ + usbdev_suspend_phase_t fin_phase; + /** + * Number of iterations remaining (including the present iteration). + */ + uint32_t num_iters; + /** + * Streaming traffic throughout test? + */ + bool with_traffic; + /** + * Timeout catching any failure of test to advance as expected + */ + ibex_timeout_t timeout; + /** + * Most recent status of wakeup monitor + */ + dif_usbdev_wake_status_t wake_status; + /** + * Test descriptor for current test phase + */ + uint8_t test_dscr[USB_TESTUTILS_TEST_DSCR_LEN]; + /** + * Our retained state; transferred to and from Retention SRAM over Sleep + */ + usbdev_retn_state_t retn_state; +} usbdev_suspend_ctx_t; + +enum { + /** + * Retention SRAM start address + */ + kRetSramBaseAddr = TOP_EARLGREY_SRAM_CTRL_RET_AON_RAM_BASE_ADDR, + /** + * Retention SRAM address at which we may store some state. + */ + kRetSramOwnerAddr = kRetSramBaseAddr + offsetof(retention_sram_t, owner), +}; + +// Total length of the configuration descriptor. +#define CFG_DSCR_TOTAL_LEN \ + (USB_CFG_DSCR_LEN + \ + NUM_STREAMS * (USB_INTERFACE_DSCR_LEN + 2 * USB_EP_DSCR_LEN)) + +// This Configuration Descriptor must be capable of describing the maximal +// number of interfaces (= number of streams). It is overwritten dynamically +// when streaming with traffic. +static uint8_t config_descriptors[CFG_DSCR_TOTAL_LEN] = { + // Default configuration, for use if we're not testing with traffic too; + // we still need configuration for the host to recognize and configure us. + USB_CFG_DSCR_HEAD( + USB_CFG_DSCR_LEN + 2 * (USB_INTERFACE_DSCR_LEN + 2 * USB_EP_DSCR_LEN), + 2), + VEND_INTERFACE_DSCR(0, 2, 0x50, 1), + USB_BULK_EP_DSCR(0, 1, 32, 0), + USB_BULK_EP_DSCR(1, 1, 32, 4), + VEND_INTERFACE_DSCR(1, 2, 0x50, 1), + USB_BULK_EP_DSCR(0, 2, 32, 0), + USB_BULK_EP_DSCR(1, 2, 32, 4), +}; + +/** + * USB Bus Frame duration, in microseconds; may be reduced when communicating + * with the USBDPI module to reduce the simulation time. + */ +static uint32_t frame_interval = 1000u; + +/** + * USB device context types. + */ +static usb_testutils_ctx_t usbdev; +static usb_testutils_controlep_ctx_t usbdev_control; +static usb_testutils_streams_ctx_t usbdev_streams; + +/** + * Pinmux handle + */ +static dif_pinmux_t pinmux; +/** + * Rstmgr handle + */ +static dif_rstmgr_t rstmgr; +/** + * Pwrmgr handle + */ +static dif_pwrmgr_t pwrmgr; +/** + * Interrupt controller handle + */ +static dif_rv_plic_t rv_plic; +/** + * Do we expect this host to put the device into suspend? + */ +static bool host_suspends = true; +/** + * Do we expect this host to perform Resume Signaling and awaken the device from + * a Suspended state? + */ +static bool host_resumes = true; +/** + * Do we expect this host to perform Bus Resets to awaken the device from sleep? + */ +static bool host_resets = true; +/** + * Do we expect this host to Disconnect the device to awaken it from sleep? + */ +static bool host_disconnects = true; +/** + * Verbose logging? Mostly useful on FPGA; be warned that it can affect + * timing in simulation, and in particular will likely break Verilator sims. + * + * It can also cause timeouts on the host side with automated regression + * tests on FPGA/Silicon because the CPU is stalled waiting for UART FIFO + * space. + * + * TODO: perhaps we want a uart_flush() at carefully selected moments, eg. + * when setting a timeout? + */ +static bool verbose = true; +/** + * Verbose logging from the streaming code? + * + * Note: this makes the test a lot slower because the CPU is stalled awaiting + * UART FIFO space. + */ +static bool s_verbose = false; + +static plic_isr_ctx_t plic_ctx = {.rv_plic = &rv_plic, + .hart_id = kTopEarlgreyPlicTargetIbex0}; + +static pwrmgr_isr_ctx_t pwrmgr_isr_ctx = { + .pwrmgr = &pwrmgr, + .plic_pwrmgr_start_irq_id = kTopEarlgreyPlicIrqIdPwrmgrAonWakeup, + .expected_irq = kDifPwrmgrIrqWakeup, + .is_only_irq = true}; + +// Configuration for streaming layer. This specifies the transfer type of each +// of the streams being used and checked if we're testing with traffic enabled. +static const unsigned nstreams = NUM_STREAMS; +#if USE_ISO_STREAMS +static const usb_testutils_transfer_type_t xfr_types[] = { + kUsbTransferTypeIsochronous, kUsbTransferTypeIsochronous, + kUsbTransferTypeIsochronous, kUsbTransferTypeIsochronous, + kUsbTransferTypeIsochronous, kUsbTransferTypeIsochronous, + kUsbTransferTypeIsochronous, kUsbTransferTypeIsochronous, + kUsbTransferTypeIsochronous, kUsbTransferTypeIsochronous, + kUsbTransferTypeIsochronous}; +#elif USE_BULK_STREAMS +static const usb_testutils_transfer_type_t xfr_types[] = { + kUsbTransferTypeBulk, kUsbTransferTypeBulk, kUsbTransferTypeBulk, + kUsbTransferTypeBulk, kUsbTransferTypeBulk, kUsbTransferTypeBulk, + kUsbTransferTypeBulk, kUsbTransferTypeBulk, kUsbTransferTypeBulk, + kUsbTransferTypeBulk, kUsbTransferTypeBulk}; +#else +static const usb_testutils_transfer_type_t xfr_types[] = { + kUsbTransferTypeIsochronous, kUsbTransferTypeInterrupt, + kUsbTransferTypeBulk, kUsbTransferTypeBulk, + + kUsbTransferTypeIsochronous, kUsbTransferTypeInterrupt, + kUsbTransferTypeBulk, kUsbTransferTypeIsochronous, + + kUsbTransferTypeInterrupt, kUsbTransferTypeBulk, + kUsbTransferTypeBulk, +}; +#endif + +// Full traffic and checking +static const usbdev_stream_flags_t test_flags = + kUsbdevStreamFlagRetrieve | kUsbdevStreamFlagCheck | + kUsbdevStreamFlagRetry | kUsbdevStreamFlagSend; +// We don't expect it to complete; data transfer is exercised and checked in +// other tests. +static const uint32_t transfer_bytes = 0x80 << 20; + +/** + * Context information for suspend/resume test + */ +static usbdev_suspend_ctx_t suspend_ctx; + +/** + * Report progress; these messages are obligatory for automated testing in order + * that the host-side test harness may synchronize with this device-side code. + */ +// Implement as a varargs function if it becomes more complicated. +// static inline void report_progress(const char *msg); +#define report_progress(...) LOG_INFO(__VA_ARGS__) + +/** + * Are we observing physically-accurate timings? + */ +static inline bool physical_timings(void) { + switch (kDeviceType) { + case kDeviceFpgaCw310: + case kDeviceFpgaCw340: + case kDeviceSilicon: + break; + default: + return false; + } + return true; +} + +// Return a timeout in microseconds, scaled for the test target; longer timeout +// periods are more appropriate for FPGA tests and decidedly undesirable for +// Verilator top-level simulations +static uint32_t time_frames(unsigned n) { + uint32_t scale = 1u; + if (physical_timings()) { + // scale = 500u; + } + return scale * n * frame_interval; +} + +// Return the name of a test phase +static const char *phase_name(usbdev_suspend_phase_t phase) { + switch (phase) { + case kSuspendPhaseSuspend: + return "Suspend"; + case kSuspendPhaseSleepResume: + return "SleepResume"; + case kSuspendPhaseSleepReset: + return "SleepReset"; + case kSuspendPhaseSleepDisconnect: + return "SleepDisconnect"; + case kSuspendPhaseDeepResume: + return "DeepResume"; + case kSuspendPhaseDeepReset: + return "DeepReset"; + case kSuspendPhaseDeepDisconnect: + return "DeepDisconnect"; + case kSuspendPhaseShutdown: + return "Shutdown"; + default: + return ""; + } +} + +// Return the name of a test state +static const char *state_name(usbdev_suspend_state_t state) { + switch (state) { + case kSuspendStatePowerOnReset: + return "PowerOnReset"; + case kSuspendStateBusReset: + return "BusReset"; + case kSuspendStateWaitSuspend: + return "WaitSuspend"; + case kSuspendStateWaitResume: + return "WaitResume"; + case kSuspendStateWaitBusReset: + return "WaitBusReset"; + case kSuspendStateWaitLongSuspend: + return "WaitLongSuspend"; + case kSuspendStateWaitSuspendTimeout: + return "WaitSuspendTimeout"; + case kSuspendStateActivatedAON: + return "ActivatedAON"; + case kSuspendStateNormalSleep: + return "NormalSleep"; + case kSuspendStateDeepSleep: + return "DeepSleep"; + case kSuspendStateNormalWaking: + return "NormalWaking"; + case kSuspendStateDeepWaking: + return "DeepWaking"; + case kSuspendStateAONWakeup: + return "AONWakeup"; + case kSuspendStateWaitResumeTimeout: + return "WaitResumeTimeout"; + case kSuspendStateWaitDisconnect: + return "WaitDisconnect"; + case kSuspendStateWaitFinish: + return "WaitFinish"; + case kSuspendStateNextPhase: + return "NextPhase"; + case kSuspendStateComplete: + return "Complete"; + case kSuspendStateFailed: + return "Failed"; + default: + return ""; + } +} + +// Report any link event(s) +static void events_report(usbdev_suspend_ctx_t *ctx, uint32_t snapshot, + dif_usbdev_link_state_t link_state) { + // Report connection/reset events + if (snapshot & (1u << kDifUsbdevIrqPowered)) { + LOG_INFO("VBUS Connected"); + } + if (snapshot & (1u << kDifUsbdevIrqDisconnected)) { + LOG_INFO("VBUS Disconnected"); + } + if (snapshot & (1u << kDifUsbdevIrqLinkReset)) { + LOG_INFO("Link reset"); + } + if (snapshot & (1u << kDifUsbdevIrqHostLost)) { + LOG_INFO("Host lost"); + } + + // Report suspend/resume status changes + if (snapshot & + ((1u << kDifUsbdevIrqLinkSuspend) | (1u << kDifUsbdevIrqLinkResume))) { + switch (link_state) { + case kDifUsbdevLinkStatePoweredSuspended: + case kDifUsbdevLinkStateSuspended: + LOG_INFO("Suspended"); + break; + + case kDifUsbdevLinkStateResuming: + LOG_INFO("Resuming"); + break; + + case kDifUsbdevLinkStateActiveNoSof: + LOG_INFO("Resuming no SOF"); + break; + + case kDifUsbdevLinkStateActive: + LOG_INFO("Resumed"); + break; + + default: + break; + } + } +} + +// Transition to a (new) test state +static inline void state_enter(usbdev_suspend_ctx_t *ctx, + usbdev_suspend_state_t state) { + if (verbose) { + LOG_INFO("entering state %s", state_name(state)); + } + ctx->test_state = state; +} + +// Set a time out for the current test state, in microseconds +static inline void timeout_set(usbdev_suspend_ctx_t *ctx, + uint32_t interval_us) { + if (verbose) { + LOG_INFO("timeout_set %uus\n", interval_us); + if (false) { + uint64_t now = ibex_mcycle_read(); + uint64_t then = now + interval_us; + LOG_INFO(" setting timeout to 0x%x%x (at 0x%x%x)", + (uint32_t)(then >> 32), (uint32_t)then, (uint32_t)(now >> 32), + (uint32_t)now); + } + } + ctx->timeout = ibex_timeout_init(interval_us); +} + +// Set a time out, in frames, for the current test state +static void timeout_frames_set(usbdev_suspend_ctx_t *ctx, + uint32_t interval_frames) { + timeout_set(ctx, time_frames(interval_frames)); +} + +//////////////////////////////////////////////////////////////////////////////// +// Report the state of the phy pins +//////////////////////////////////////////////////////////////////////////////// +void sense_report(usbdev_suspend_ctx_t *ctx, uint32_t niters) { + while (niters-- > 0u) { + dif_usbdev_phy_pins_sense_t sense; + CHECK_DIF_OK(dif_usbdev_get_phy_pins_status(ctx->usbdev->dev, &sense)); +#if USBUTILS_FUNCTION_POINTS + uint32_t data = (sense.rx_dp ? 1U : 0) | (sense.rx_dn ? 2U : 0) | + (sense.rx_d ? 4U : 0) | (sense.output_enable ? 8U : 0) | + (sense.vbus_sense ? 0x100U : 0); + USBUTILS_FUNCPT(0x515, data); +#endif + LOG_INFO( + "sense %u : rx_dp %u rx_dn %u rx_d %u : tx_dp %u tx_dn %u tx_d %u " + "tx_se0 %u : oe %u", + sense.vbus_sense, sense.rx_dp, sense.rx_dn, sense.rx_d, sense.tx_dp, + sense.tx_dn, sense.tx_d, sense.tx_se0, sense.output_enable); + } +} + +/** + * Report the status information from the AON/Wake module. + */ +void report_wake_status(usbdev_suspend_ctx_t *ctx) { + LOG_INFO("wake status active %u disconnected %u bus_reset %u", + ctx->wake_status.active, ctx->wake_status.disconnected, + ctx->wake_status.bus_reset); + LOG_INFO(" bus_not_idle %u", ctx->wake_status.bus_not_idle); + LOG_INFO(" (host_resumes %u host_resets %u host_disconnects %u)", + host_resumes, host_resets, host_disconnects); +} + +/** + * Collect and optionally report the current 'wake status' reported by the + * AON/Wake module. This indicates why sleep was interrupted. + */ +status_t collect_wake_status(usbdev_suspend_ctx_t *ctx) { + TRY(dif_usbdev_get_wake_status(ctx->usbdev->dev, &ctx->wake_status)); + if (verbose) { + report_wake_status(ctx); + } + return OK_STATUS(); +} + +/** + * External interrupt handler. + */ +void ottf_external_isr(void) { + dif_pwrmgr_irq_t irq_id; + top_earlgrey_plic_peripheral_t peripheral; + + isr_testutils_pwrmgr_isr(plic_ctx, pwrmgr_isr_ctx, &peripheral, &irq_id); + + if (false) { + const bool debug = false; + usbdev_suspend_ctx_t *ctx = &suspend_ctx; + if (debug) { + sense_report(ctx, 2); + LOG_INFO("Received Wakeup IRQ in sleep"); + if (debug) { + // Report wake status immediately, to see what ES captured. + (void)collect_wake_status(ctx); + sense_report(ctx, 10u); + } + } else { + LOG_INFO("Received Wakeup IRQ in sleep"); + } + } + + // Check that both the peripheral and the irq id are correct. + CHECK(peripheral == kTopEarlgreyPlicPeripheralPwrmgrAon, + "IRQ peripheral: %d is incorrect", peripheral); + CHECK(irq_id == kDifPwrmgrIrqWakeup, "IRQ ID: %d is incorrect", irq_id); +} + +/** + * Simple Retention SRAM writing (from sram_ctrl_sleep_sram_ret_contents_test.c) + */ +static void retention_sram_store(const usbdev_retn_state_t *state) { + sram_ctrl_testutils_write(kRetSramOwnerAddr, (sram_ctrl_testutils_data_t){ + .words = (uint32_t *)state, + .len = sizeof(*state) / 4}); +} + +/** + * Simple Retention SRAM reading (from sram_ctrl_sleep_sram_ret_contents_test.c) + */ +static void retention_sram_load(usbdev_retn_state_t *state) { + memcpy(state, (uint8_t *)kRetSramOwnerAddr, sizeof(*state)); +} + +static void phase_set(usbdev_suspend_ctx_t *ctx, usbdev_suspend_phase_t phase) { + if (verbose) { + LOG_INFO("phase_set %u (%s)", phase, phase_name(phase)); + } + /** + * Test descriptor; indicates to the DPI model that we're interested in + * testing bus Suspend/Resume signaling, Reset signaling and Remote Wakeup + * behavior. + */ + uint8_t test_descriptor[] = { + USB_TESTUTILS_TEST_DSCR(kUsbTestNumberSuspend, + (uint8_t)phase, // Test phase + 0, 0, 0)}; + + memcpy(ctx->test_dscr, test_descriptor, sizeof(ctx->test_dscr)); + + // Remember the new test phase + ctx->test_phase = phase; +} + +// Callback handler for link events +static status_t link_callback(void *ctx_v, + dif_usbdev_irq_state_snapshot_t snapshot, + dif_usbdev_link_state_t link_state) { + usbdev_suspend_ctx_t *ctx = (usbdev_suspend_ctx_t *)ctx_v; + + if (snapshot & (1u << kDifUsbdevIrqFrame)) { + if (verbose) { + // For FPGA testing, we deliberately extend all of the timeout periods to + // make the activity viewable. This is therefore just an indication of + // activity/progress. + static int hb = 0; + if (++hb > 1000) { + LOG_INFO("SOF"); + hb = 0; + } + } + + // We are supplied with the SOF interrupts to help inform our decision + // but these are normally much too frequent to be reporting them + snapshot &= ~(1u << kDifUsbdevIrqFrame); + if (!snapshot) { + // If only a Frame interrupt has called, return without further reporting + return OK_STATUS(); + } + } + + if (false) { // verbose && snapshot) { + // LOG_INFO("State %u (%s) - Link events:", ctx->test_state, + // state_name(ctx->test_state)); + events_report(ctx, snapshot, link_state); + // LOG_INFO(" events: 0x%x link 0x%x", snapshot, link_state); + } + + // State machine anticipates the behavior of the host/DPI model, checking that + // the expected events are reported within the expected time intervals, and + // advances accordingly through the test states. + + switch (ctx->test_state) { + // We're expecting the host to drop the SOF heartbeat indicating that we + // should suspend... (STEP_IDLE_) + case kSuspendStateWaitSuspend: + if (snapshot & (1u << kDifUsbdevIrqLinkSuspend)) { + state_enter(ctx, kSuspendStateWaitResume); + timeout_set(ctx, TimeoutResumeMissed); + } + break; + + // After a short delay, the host should resume automatically... + // (STEP_ACTIVE_) + case kSuspendStateWaitResume: + if (snapshot & (1u << kDifUsbdevIrqLinkResume)) { + state_enter(ctx, kSuspendStateNextPhase); + } + break; + + // The first test phase (Suspend/Resume without AON/Wakeup involvement) + // is terminated by a deliberate Bus Reset, advancing us to the next + // phase. + case kSuspendStateWaitBusReset: + if (snapshot & (1u << kDifUsbdevIrqLinkReset)) { + state_enter(ctx, kSuspendStateBusReset); + } + break; + + // This time we're expecting a much longer Suspend... + case kSuspendStateWaitLongSuspend: + if (snapshot & (1u << kDifUsbdevIrqLinkSuspend)) { + state_enter(ctx, kSuspendStateWaitSuspendTimeout); + timeout_frames_set(ctx, FramesWaitEnterSleep); + } + break; + + // We're _waiting for a timeout_ to occur, so we're not expecting any + // events at this point... + case kSuspendStateWaitSuspendTimeout: + if (snapshot) { + state_enter(ctx, kSuspendStateFailed); + } + break; + + case kSuspendStateWaitDisconnect: + if (snapshot & (1u << kDifUsbdevIrqDisconnected)) { + state_enter(ctx, kSuspendStateComplete); + } + break; + + case kSuspendStateActivatedAON: + // TODO: should respond to a resume event, and seize back control! + break; + case kSuspendStateNormalSleep: + case kSuspendStateDeepSleep: + break; + case kSuspendStateAONWakeup: + break; + + case kSuspendStateWaitResumeTimeout: + break; + + case kSuspendStateWaitFinish: + // We've resumed, we're just waiting for the host to perform some simple + // traffic and then disconnect to signal test completion + if (snapshot & (1u << kDifUsbdevLinkStateDisconnected)) { + state_enter(ctx, kSuspendStateComplete); + } + break; + + case kSuspendStatePowerOnReset: + case kSuspendStateBusReset: + case kSuspendStateNextPhase: + break; + + case kSuspendStateNormalWaking: + case kSuspendStateDeepWaking: + break; + + // Ignore link events if we already have a verdict. + case kSuspendStateFailed: + case kSuspendStateComplete: + break; + + default: + LOG_INFO("Unknown/invalid test state %u (%s)", ctx->test_state, + state_name(ctx->test_state)); + state_enter(ctx, kSuspendStateFailed); + break; + } + + if (verbose && ctx->test_state == kSuspendStateFailed) { + LOG_INFO(" -> failed handling snapshot 0x%x with link state 0x%x\n", + snapshot, link_state); + } + + return OK_STATUS(); +} + +// TODO: redirect logging information in Verilator t-l sim because any attempt +// to use the UART will introduce long delays and break the test. +static size_t base_dev_uart(void *data, const char *buf, size_t len) { + if (kDeviceType == kDeviceSimVerilator) { + for (size_t i = 0; i < len; ++i) { + *(uint32_t *)0x411f0084 = ((uint8_t *)buf)[i]; + } + } + return len; +} +static buffer_sink_t base_stdout = { + .data = NULL, + // Note: Using `&base_dev_null` causes this variable to be placed in the + // .data section and triggers the assertion in rom.ld. + .sink = base_dev_uart, +}; + +//////////////////////////////////////////////////////////////////////////////// +// Timeout handling +// +// Timeouts generally occur if an expected event has not happened as expected, +// indicating a failure. Some timeouts, however, simply indicate that the test +// should advance - ie. a necessary delay interval has elapsed - and others are +// necessary with a physical host (FPGA runs) because the requisite behavior +// cannot be generated by the host. +//////////////////////////////////////////////////////////////////////////////// +static status_t timeout_handle(usbdev_suspend_ctx_t *ctx) { + // Timeouts typically indicate failure. + bool failed = true; + + switch (ctx->test_state) { + case kSuspendStateWaitSuspend: + if (!host_suspends) { + LOG_INFO("auto-suspending (FPGA)"); + state_enter(ctx, kSuspendStateWaitResume); + timeout_frames_set(ctx, FramesInitiateResume); + failed = false; + } + break; + + case kSuspendStateWaitLongSuspend: + if (!host_suspends) { + LOG_INFO("auto-long-suspending (FPGA)"); + state_enter(ctx, kSuspendStateWaitSuspendTimeout); + timeout_frames_set(ctx, FramesWaitEnterSleep); + failed = false; + } + break; + + // Timeout is required to advance from the longer suspend state + // because we're not expecting any host activity in this case, but + // must initiate sleep/powerdown + case kSuspendStateWaitSuspendTimeout: + if (verbose) { + LOG_INFO("set_wake_enable..."); + } + TRY(dif_usbdev_set_wake_enable(ctx->usbdev->dev, kDifToggleEnabled)); + + state_enter(ctx, kSuspendStateActivatedAON); + timeout_frames_set(ctx, TimeoutAonResponse); + failed = false; + break; + + case kSuspendStateWaitResume: + if (!host_resumes) { + LOG_INFO("auto-resuming (host does not support Resume Signaling)"); + state_enter(ctx, kSuspendStateWaitBusReset); + timeout_frames_set(ctx, 10000u); + failed = false; + } + break; + + // Timeout + case kSuspendStateWaitResumeTimeout: + // TODO: + // timeout_frames_set(ctx, TimeoutFinishMissed); + state_enter(ctx, kSuspendStateNextPhase); + failed = false; + break; + + // Timeout may also be required to advance from Wait(Long)Suspend if + // the host does not attempt to suspend the device, in which case we + // shall also need to transition from WaitResume automatically... + case kSuspendStateWaitBusReset: + if (!host_resets) { + LOG_INFO("auto-resetting (host does not support Bus Resets)"); + // Since we don't have an actual Bus Reset we must NOT wait around + // to be reconfigured. + state_enter(ctx, kSuspendStateNextPhase); + failed = false; + } + break; + + // Any other timeout implies that we did not receive the expected link + // event promptly and the test has failed + default: + break; + } + + if (failed) { + LOG_INFO("Timed out in test state %u (%s)", ctx->test_state, + state_name(ctx->test_state)); + state_enter(ctx, kSuspendStateFailed); + } + + return OK_STATUS(); +} + +//////////////////////////////////////////////////////////////////////////////// +// (Re)initialize the software stack +//////////////////////////////////////////////////////////////////////////////// + +static status_t software_init(usbdev_suspend_ctx_t *ctx) { + if (verbose) { + LOG_INFO("Init testutils layer in state %u (%s)", ctx->test_state, + state_name(ctx->test_state)); + } + + // Initialize the usb_testutils layer + // Note: when we exit a Deep Sleep via Resume Signaling we are relying + // upon being able to set up the state of the endpoints and device + // registers again, rather than retaining the full software state in SRAM + TRY(usb_testutils_init(ctx->usbdev, /*pinflip=*/false, + /*en_diff_rcvr=*/true, + /*tx_use_d_se0=*/false)); + + // Register our interest in link events + TRY(usb_testutils_link_callback_register(ctx->usbdev, link_callback, ctx)); + + if (ctx->with_traffic) { + // Supply usb_testutils context to streaming library + usbdev_streams.usbdev = ctx->usbdev; + +#if 0 + // Initialize the state of the streams; do this before resuming. + TRY(usb_testutils_streams_init(&usbdev_streams, nstreams, xfr_types, + transfer_bytes, test_flags, s_verbose)); +#else + // TODO: we do nothing with this at the moment, but perhaps in time we'll + // present it to the host via the test descriptor a la the regular + // streaming tests (usbdev_stream/iso/mixed_test). + uint32_t dpi_types; + + // Initialize the state of the streams; do this before resuming. + TRY(usb_testutils_streams_typed_init(&usbdev_streams, config_descriptors, + sizeof(config_descriptors), nstreams, + xfr_types, transfer_bytes, test_flags, + s_verbose, &dpi_types)); +#endif + } + + // Set up Endpoint Zero for Control Transfers, at which point the + // interface becomes enabled and we must be responsive to USB traffic. + TRY(usb_testutils_controlep_init( + &usbdev_control, ctx->usbdev, 0, config_descriptors, + sizeof(config_descriptors), ctx->test_dscr, sizeof(ctx->test_dscr))); + + return OK_STATUS(); +} + +//////////////////////////////////////////////////////////////////////////////// +// Handle the initial state of a new phase, and the first state after waking +// from Deep Sleep. +//////////////////////////////////////////////////////////////////////////////// + +static status_t phase_start_resume(usbdev_suspend_ctx_t *ctx) { + uint32_t timeout = FramesSuspendMissed; + + TRY_CHECK(ctx->test_state == kSuspendStateNextPhase || + ctx->test_state == kSuspendStateBusReset || + ctx->test_state == kSuspendStatePowerOnReset || + ctx->test_state == kSuspendStateDeepSleep); + + // If we can reuse the existing software state and device configuration for + // this phase then we avoid reinitialization... + if (ctx->test_state != kSuspendStateNextPhase) { + TRY(software_init(ctx)); + } + + switch (ctx->test_state) { + case kSuspendStatePowerOnReset: + case kSuspendStateBusReset: + // In this case, since we have no retention RAM, we must wait until the + // host has reconfigured us; do not wait indefinitely, in case something + // has gone wrong. + if (physical_timings()) { + timeout_set(ctx, TimeoutPhysConfig); + } else { + // It should take only a few bus frames even with the older, slower DPI + // behavior. + timeout_frames_set(ctx, 8u); + } + + if (verbose) { + LOG_INFO("waiting to be configured"); + } + + while (usbdev_control.device_state != kUsbTestutilsDeviceConfigured && + !ibex_timeout_check(&ctx->timeout)) { + TRY(usb_testutils_poll(ctx->usbdev)); + } + + // If we're out of step with the DPI model/host, stop the test. + TRY_CHECK(usbdev_control.device_state == kUsbTestutilsDeviceConfigured); + + // The DPI model still needs to complete the 'SET_DEVICE_CONFIG' bus frame + // and then devote another bus frame to reading the test configuration. + // (GET_TEST_CONFIG), so we must expect to wait longer before seeing the + // Suspend signaling. + timeout += FramesConfigDelay; + break; + + case kSuspendStateNextPhase: + // Software and device should already be up and running. + break; + + default: + TRY_CHECK(ctx->test_state == kSuspendStateDeepSleep); + + if (ctx->with_traffic) { + // Reinstate the stream information, now that we've set up the basic + // software structures for streaming. + TRY_CHECK(ctx->retn_state.client_used <= + sizeof(ctx->retn_state.client_state)); + TRY(usb_testutils_streams_resume(&usbdev_streams, + ctx->retn_state.client_state, + ctx->retn_state.client_used)); + } + + // Collect the device address and configuration previously used. + const uint8_t dev_address = ctx->retn_state.dev_address; + const uint8_t dev_config = ctx->retn_state.dev_config; + + // NOTE: We have run through the usb_testutils/controlep_init sequence as + // normal because this sets up our endpoints as they were before, whilst + // requiring less information to be stored in the retention RAM, but we + // must still reinstate the Data Toggle bits and the Device Address +#if USBDEV_HAVE_TOGGLE_STATE + uint16_t out_toggles = (uint16_t)ctx->retn_state.data_toggles; + uint16_t in_toggles = (uint16_t)(ctx->retn_state.data_toggles >> 16); + const uint16_t mask = (uint16_t)((1u << USBDEV_NUM_ENDPOINTS) - 1u); + CHECK_DIF_OK(dif_usbdev_data_toggle_out_write(ctx->usbdev->dev, mask, + out_toggles)); + CHECK_DIF_OK( + dif_usbdev_data_toggle_in_write(ctx->usbdev->dev, mask, in_toggles)); +#endif + CHECK_DIF_OK(dif_usbdev_address_set(ctx->usbdev->dev, dev_address)); + + // TODO: the controlep state needs to be forced to configured. + // TODO: introduce an API call to set/restore the state information for + // the control endpoint, to keep things a little cleaner/more contained? + usbdev_control.device_state = kUsbTestutilsDeviceConfigured; + usbdev_control.usb_config = dev_config; + usbdev_control.new_dev = dev_address; + + // At this point we expected the device to be in the Powered state, and + // since it won't see a Bus Reset, we nudge it into the ActiveNoSOF state. + dif_usbdev_link_state_t link_state; + TRY(dif_usbdev_status_get_link_state(ctx->usbdev->dev, &link_state)); + TRY_CHECK(link_state == kDifUsbdevLinkStatePowered || + link_state == kDifUsbdevLinkStateDisconnected); + + if (link_state == kDifUsbdevLinkStatePowered) { + TRY(dif_usbdev_resume_link_to_active(ctx->usbdev->dev)); + } + +#if USBDEV_HAVE_TOGGLE_STATE + if (verbose) { + LOG_INFO("in_toggles 0x%03x out_toggles 0x%03x", in_toggles, + out_toggles); + } +#else + if (ctx->with_traffic) { + LOG_INFO("Warning: Unable to reinstate Data Toggle bits"); + } +#endif + break; + } + + // Indication that we've started. + if (ctx->test_state != kSuspendStateNextPhase) { + if (ctx->with_traffic) { + if (ctx->test_state == kSuspendStateDeepSleep) { + if (verbose) { + LOG_INFO("Resuming streaming..."); + } + } else { + if (verbose) { + LOG_INFO("Configured; starting streaming..."); + } + } + } else { + if (verbose) { + LOG_INFO("Configured; not trying to stream..."); + } + } + } + + // Enter the appropriate starting state based upon the test phase + switch (ctx->test_phase) { + case kSuspendPhaseShutdown: + // Just disconnect from the bus and await notification via the link + // callback handler. + TRY(dif_usbdev_interface_enable(ctx->usbdev->dev, kDifToggleDisabled)); + state_enter(ctx, kSuspendStateWaitDisconnect); + break; + + case kSuspendPhaseSuspend: + state_enter(ctx, kSuspendStateWaitSuspend); + break; + + default: + CHECK(ctx->test_phase == kSuspendPhaseDeepDisconnect); + OT_FALLTHROUGH_INTENDED; + case kSuspendPhaseDeepReset: + case kSuspendPhaseDeepResume: + if (ctx->test_state == kSuspendStateDeepSleep) { + state_enter(ctx, kSuspendStateDeepWaking); + break; + } + // If we're starting one of the Deep phases rather than waking from + // DeepSleep then it's the same as starting a normal Sleep phase. + OT_FALLTHROUGH_INTENDED; + // + case kSuspendPhaseSleepDisconnect: + case kSuspendPhaseSleepReset: + case kSuspendPhaseSleepResume: + if (host_suspends) { + // TODO: human intervention required presently + // timeout *= 2000; + } + state_enter(ctx, kSuspendStateWaitLongSuspend); + break; + } + // Initialize timeout to catch any failure of the host to suspend the bus + timeout_frames_set(ctx, timeout); + + return OK_STATUS(); +} + +//////////////////////////////////////////////////////////////////////////////// +// Foreground event handling +// +// Some monitoring activities must occur in the foreground because the +// monitored event is neither immediate nor will it trigger a link +// event. +//////////////////////////////////////////////////////////////////////////////// + +static status_t state_service(usbdev_suspend_ctx_t *ctx) { + switch (ctx->test_state) { + case kSuspendStateActivatedAON: + // Since the AON/Wakeup operates on a low clock frequency, it may + // take some time for it to become active....await its signal + TRY(collect_wake_status(ctx)); + if (ctx->wake_status.active) { + // Retain our state information + TRY(dif_usbdev_address_get(ctx->usbdev->dev, + &ctx->retn_state.dev_address)); + ctx->retn_state.dev_config = usbdev_control.usb_config; + ctx->retn_state.test_phase = (uint8_t)ctx->test_phase; + // Remember the number of remaining iterations. + ctx->retn_state.num_iters = ctx->num_iters; +#if USBDEV_HAVE_TOGGLE_STATE + // Capture all current Data Toggle bits + uint16_t out_toggles, in_toggles; + TRY(dif_usbdev_data_toggle_out_read(ctx->usbdev->dev, &out_toggles)); + TRY(dif_usbdev_data_toggle_in_read(ctx->usbdev->dev, &in_toggles)); + ctx->retn_state.data_toggles = + ((uint32_t)in_toggles << 16) | out_toggles; +#else + ctx->retn_state.data_toggles = 0U; +#endif + if (verbose) { + LOG_INFO(" - retaining address %u config %u phase %u (%s)", + ctx->retn_state.dev_address, ctx->retn_state.dev_config, + ctx->retn_state.test_phase, + phase_name(ctx->retn_state.test_phase)); + } + + if (ctx->with_traffic) { + // Store any state information that is necessary to resume streaming. + unsigned used; + TRY(usb_testutils_streams_suspend( + &usbdev_streams, ctx->retn_state.client_state, + sizeof(ctx->retn_state.client_state), &used)); + TRY_CHECK(used <= sizeof(ctx->retn_state.client_state)); + ctx->retn_state.client_used = used; + } + + retention_sram_store(&ctx->retn_state); + + // TODO: migrate this into a subfunction + // Enter low power mode; note that on some targets we may be unable to + // produce the appropriate stimuli so we have to skip over the sleep + // state. + bool can_awaken = true; + switch (ctx->test_phase) { + case kSuspendPhaseSleepReset: + case kSuspendPhaseDeepReset: + if (!host_resets) { + if (verbose) { + LOG_INFO("auto-skipping WFI (host does not support Bus Resets"); + } + can_awaken = false; + } + break; + + case kSuspendPhaseSleepDisconnect: + case kSuspendPhaseDeepDisconnect: + if (!host_disconnects) { + if (verbose) { + LOG_INFO( + "auto-skipping WFI (host does not support Disconnects"); + } + can_awaken = false; + } + break; + + default: + TRY_CHECK(ctx->test_phase == kSuspendPhaseSleepResume || + ctx->test_phase == kSuspendPhaseDeepResume); + if (!host_resumes) { + if (verbose) { + LOG_INFO( + "auto-skipping WFI (host does not support Resume " + "Signaling"); + } + can_awaken = false; + } + break; + } + + if (can_awaken) { + if (ctx->test_phase == kSuspendPhaseDeepResume || + ctx->test_phase == kSuspendPhaseDeepReset || + ctx->test_phase == kSuspendPhaseDeepDisconnect) { + if (verbose) { + LOG_INFO("Requesting Deep sleep"); + } + + // Deep sleep. + // + // Note: we must keep the 'Active USB clock enable' bit set, + // because otherwise when we return to the active state, the + // usbdev clock will not be restored. + TRY(pwrmgr_testutils_enable_low_power( + &pwrmgr, kDifPwrmgrWakeupRequestSourceFour, + /*domain_config=*/ + kDifPwrmgrDomainOptionUsbClockInActivePower)); + + // Record that we've asked to power down; timeout should never + // occur. + state_enter(ctx, kSuspendStateDeepSleep); + timeout_frames_set(ctx, TimeoutSleepFailed); + } else { + LOG_INFO("Requesting Normal sleep"); + + // Normal sleep. + TRY(pwrmgr_testutils_enable_low_power( + &pwrmgr, /*wakeups=*/kDifPwrmgrWakeupRequestSourceFour, + /*domain_config=*/ + kDifPwrmgrDomainOptionCoreClockInLowPower | + kDifPwrmgrDomainOptionUsbClockInActivePower | + kDifPwrmgrDomainOptionMainPowerInLowPower)); + + // Record that we've asked to enter lower power mode; timeout + // should never occur. + state_enter(ctx, kSuspendStateNormalSleep); + timeout_frames_set(ctx, TimeoutSleepFailed); + } + + // With a physical host (FPGA) some manual intervention is required. + const char *action = ""; + if (physical_timings()) { + switch (ctx->test_phase) { + case kSuspendPhaseSleepReset: + case kSuspendPhaseDeepReset: + action = "; please Reset the device."; + break; + case kSuspendPhaseSleepDisconnect: + case kSuspendPhaseDeepDisconnect: + action = "; please disconnect and reconnect the USB cable."; + break; + default: + break; + } + } + // sense_report(ctx, 10); + if (verbose) { + LOG_INFO("Issuing WFI to enter sleep%s", action); + } + wait_for_interrupt(); + + // Check that a DeepSleep request did not somehow run past the + // WFI... + TRY_CHECK(ctx->test_state == kSuspendStateNormalSleep); + } + + //--------------------------------------------------------------- + // After a Normal sleep, we resume execution here; after a Deep + // sleep we start again as if from a Power On Reset, but the + // pwrmgr tells us otherwise. + //--------------------------------------------------------------- + + // TODO: check the IRQ source in the event of a Normal Sleep and + // proceeding past the WFI + + // ... and we should be in one of these test phases. + TRY_CHECK(ctx->retn_state.test_phase == kSuspendPhaseSleepResume || + ctx->retn_state.test_phase == kSuspendPhaseSleepReset || + ctx->retn_state.test_phase == kSuspendPhaseSleepDisconnect || + (!host_resumes && + ctx->retn_state.test_phase == kSuspendPhaseDeepResume) || + (!host_resets && + ctx->retn_state.test_phase == kSuspendPhaseDeepReset) || + (!host_disconnects && + ctx->retn_state.test_phase == kSuspendPhaseDeepDisconnect)); + + // Retrieve it and check; just additional testing. + usbdev_retn_state_t stored_state; + retention_sram_load(&stored_state); + if (verbose) { + LOG_INFO(" - retained address %u config %u phase %u (%s)", + stored_state.dev_address, stored_state.dev_config, + stored_state.test_phase, + phase_name(stored_state.test_phase)); + } + + // Check that the Retention SRAM did its job over Normal Sleep at least; + // the SRAM should remain powered and clocked so this should not be + // challenging. + TRY_CHECK(stored_state.dev_address == ctx->retn_state.dev_address && + stored_state.dev_config == ctx->retn_state.dev_config && + stored_state.test_phase == ctx->retn_state.test_phase && + stored_state.num_iters == ctx->retn_state.num_iters && + stored_state.data_toggles == ctx->retn_state.data_toggles); + + dif_pwrmgr_wakeup_reason_t wakeup_reason; + TRY(dif_pwrmgr_wakeup_reason_get(&pwrmgr, &wakeup_reason)); + + if (verbose) { + LOG_INFO("wakeup types 0x%x sources 0x%x", wakeup_reason.types, + wakeup_reason.request_sources); + } + + state_enter(ctx, kSuspendStateNormalWaking); + // We just need to previous spurious timeouts at this point. + timeout_frames_set(ctx, TimeoutSleepFailed); + } + break; + + case kSuspendStateNormalWaking: + case kSuspendStateDeepWaking: + // We've returned from sleeping; enquire of the USB AON Wake module + // what happened... + TRY(collect_wake_status(ctx)); + + // There are three ways that we may exit from Deep Sleep in which + // the AON/Wake module has been handling the bus: + // - Disconnecion (loss of VBUS/SENSE) + // - Bus Reset (from host) + // - Non-Idle state detected (Resume Signaling; this is inferred by + // neither of the other two conditions having occurred.) + // Resume signaling shall last at last 20ms, but the AON/Wake + // module alerts us long before that time has elapsed. + + // Check the report from the AON/Wakeup module + if (ctx->wake_status.active) { + bool got_signal = false; + + switch (ctx->test_phase) { + case kSuspendPhaseSleepResume: + case kSuspendPhaseDeepResume: + got_signal = !host_resumes || (ctx->wake_status.bus_not_idle != 0); + break; + + case kSuspendPhaseSleepReset: + case kSuspendPhaseDeepReset: + got_signal = !host_resets || (ctx->wake_status.bus_reset != 0); + break; + + default: + TRY_CHECK(ctx->test_phase == kSuspendPhaseDeepDisconnect); + OT_FALLTHROUGH_INTENDED; + case kSuspendPhaseSleepDisconnect: + got_signal = + !host_disconnects || (ctx->wake_status.disconnected != 0); + break; + } + + if (got_signal) { + // TODO: Issue #18562 VBUS Disconnection leaves pull ups asserted + // by the USB AON Wake module, so disconnect them here before + // potential confusion results. + if (ctx->wake_status.disconnected) { + bool sense; + + TRY(dif_usbdev_status_get_sense(ctx->usbdev->dev, &sense)); + if (verbose) { + LOG_INFO("Handling Disconnection when VBUS %sasserted", + sense ? "" : "de-"); + } + + // TODO: experimental test code! DO NOT MERGE + if (false) { + static uint8_t buf[4096]; + extern void usbutils_gather(dif_usbdev_t * dev, uint8_t * buf, + size_t n); + + while (!sense) { + TRY(dif_usbdev_status_get_sense(ctx->usbdev->dev, &sense)); + } + + usbutils_gather(ctx->usbdev->dev, buf, sizeof(buf)); + } + + // If VBUS/SENSE is not asserted, then the pull up will be removed + // as soon as the AON Wake module is deactivated, because usbdev + // qualifies its own pull up assertions with VBUS/SENSE presence. + if (sense) { + TRY(dif_usbdev_interface_enable(ctx->usbdev->dev, false)); + } + } + + // Signal to the AON wakeup module that it should deactivate and + // relinquish control of the bus + TRY(dif_usbdev_set_wake_enable(ctx->usbdev->dev, kDifToggleDisabled)); + + // Although it operates at only 200kHz, it should't take long + state_enter(ctx, kSuspendStateAONWakeup); + timeout_frames_set(ctx, TimeoutAonResponse); + } else { + LOG_INFO("Unexpected report from USB AON Wake module"); + report_wake_status(ctx); + state_enter(ctx, kSuspendStateFailed); + } + } else { + LOG_INFO("AON/Wake module not active when expected"); + state_enter(ctx, kSuspendStateFailed); + } + break; + + case kSuspendStateAONWakeup: + // Since the AON wakeup module operates on a much lower clock + // frequency it may take some time for it to stop monitoring and to + // report becoming inactive... + TRY(collect_wake_status(ctx)); + if (!ctx->wake_status.active) { + // If we've been awoken by a Disconnection event or by a Bus Reset + // event rather than by Resume Signaling, then we must advance to + // the next test phase and expect to be reconfigured. + // + // Note: at this point we may assume that we _did_ get the + // expected wakeup stimulus/report, because it was checked above. + switch (ctx->test_phase) { + case kSuspendPhaseSleepDisconnect: + case kSuspendPhaseDeepDisconnect: + state_enter(ctx, host_disconnects ? kSuspendStatePowerOnReset + : kSuspendStateNextPhase); + break; + + case kSuspendPhaseSleepReset: + case kSuspendPhaseDeepReset: + // TODO: Check! Reset Signaling is still ongoing at this point? + // state_enter(ctx, kSuspendStateWaitBusReset); + state_enter(ctx, kSuspendStateBusReset); + break; + + default: + TRY_CHECK(ctx->test_phase == kSuspendPhaseDeepResume); + OT_FALLTHROUGH_INTENDED; + case kSuspendPhaseSleepResume: + state_enter(ctx, kSuspendStateWaitResumeTimeout); + timeout_set(ctx, TimeoutWakeupResume); + break; + } + } else { + LOG_INFO("AON Wake module not active when expected"); + state_enter(ctx, kSuspendStateFailed); + } + break; + + // TODO: do we still want this state? + case kSuspendStateWaitFinish: + break; + + // Phase-initial/-final states in which we don't need to do anything here. + case kSuspendStatePowerOnReset: + case kSuspendStateBusReset: + case kSuspendStateNextPhase: + case kSuspendStateComplete: + case kSuspendStateFailed: + break; + + // Verdict already decided. + + // States in which we sit waiting - with a timeout - for something + // significant to happen... + default: + TRY_CHECK(ctx->test_state == kSuspendStateWaitResumeTimeout); + OT_FALLTHROUGH_INTENDED; + case kSuspendStateWaitSuspend: + case kSuspendStateWaitResume: + case kSuspendStateWaitBusReset: + case kSuspendStateWaitLongSuspend: + case kSuspendStateWaitSuspendTimeout: + case kSuspendStateWaitDisconnect: + break; + } + + return OK_STATUS(); +} + +/** + * Run a single test phase to completion + */ +static status_t phase_run(usbdev_suspend_ctx_t *ctx) { + bool send_progress = (ctx->test_state != kSuspendStateDeepSleep); + bool phase_done = false; + + // Handle the phase-initial state or resuming from Deep Sleep and continuing + // in the present phase. + TRY(phase_start_resume(ctx)); + + if (send_progress) { + report_progress("Phase awaiting stimulus (%s)", + phase_name(ctx->test_phase)); + } + + switch (ctx->test_state) { + case kSuspendStateBusReset: + case kSuspendStateNextPhase: + case kSuspendStatePowerOnReset: + case kSuspendStateComplete: + case kSuspendStateFailed: + LOG_INFO( + "Phase-initial state %u (%s) should have been handled in " + "phase_start()", + ctx->test_state, state_name(ctx->test_state)); + return FAILED_PRECONDITION(); + default: + break; + } + + // The DPI model and our callback handler for USB link events do most of the + // work of walking through the test states until completion + do { + if (ibex_timeout_check(&ctx->timeout)) { + TRY(timeout_handle(ctx)); + } else { + TRY(state_service(ctx)); + } + + switch (ctx->test_state) { + // These states terminate the phase, either advancing to the next phase + // or terminating the test sequence. + case kSuspendStateBusReset: + case kSuspendStateNextPhase: + case kSuspendStatePowerOnReset: + case kSuspendStateComplete: // from PhaseShutdown only + case kSuspendStateFailed: // from any phase + phase_done = true; + break; + + // Do not poll the USB device or perform traffic in these states. + case kSuspendStateActivatedAON: + case kSuspendStateAONWakeup: + case kSuspendStateDeepSleep: + case kSuspendStateNormalSleep: + break; + + // TODO: + case kSuspendStateWaitResume: + // No traffic, but we must still poll the usb_testutils layer to + // handle hardware events and callbacks. + TRY(usb_testutils_poll(ctx->usbdev)); + break; + + default: + if (ctx->with_traffic) { + // Servicing streams handles usbdev/testutils events for us. + // + // TODO: streaming code has been integrated, but it would probably be + // quite useful to be able to see the device streaming activity too, + // not just that of the host side? + TRY(usb_testutils_streams_service(&usbdev_streams)); + } else { + // No traffic, but we must still poll the usb_testutils layer to + // handle hardware events and callbacks. + TRY(usb_testutils_poll(ctx->usbdev)); + } + break; + } + } while (!phase_done); + + // Advance to next phase, unless we have just completed the final phase or + // the test has failed. + switch (ctx->test_state) { + case kSuspendStatePowerOnReset: + case kSuspendStateBusReset: + case kSuspendStateNextPhase: { + // Was this the final phase of the test? + usbdev_suspend_phase_t next_phase = + (usbdev_suspend_phase_t)(ctx->test_phase + 1u); + bool completed = false; + if (ctx->test_phase == ctx->fin_phase) { + if (ctx->num_iters == USBDEV_SUSPEND_ETERNAL || --ctx->num_iters > 0u) { + LOG_INFO("Rewinding to initial phase"); + if (ctx->num_iters > 0u) { + LOG_INFO(" - %u iteration(s) remaining", ctx->num_iters); + } + next_phase = ctx->init_phase; + } else { + completed = true; + } + } + // Have we completed the entire test? + if (completed) { + state_enter(ctx, kSuspendStateComplete); + } else { + // Advance to the next test phase, or rewind for the next iteration. + phase_set(ctx, next_phase); + } + } break; + + default: + TRY_CHECK(ctx->test_state == kSuspendStateComplete || + ctx->test_state == kSuspendStateFailed); + break; + } + + return OK_STATUS(); +} + +bool usbdev_suspend_test(usbdev_suspend_phase_t init_phase, + usbdev_suspend_phase_t fin_phase, uint32_t num_iters, + bool with_traffic) { + usbdev_suspend_ctx_t *ctx = &suspend_ctx; + + // Wipe out any memory from the previous test phase, just to be more confident + // that we really are resuming from Deep Sleep and using only the Retention + // SRAM contents to resume. + // + // This also means that the data we subsequently store in the Retention SRAM + // has defined values for the unused padding fields. + memset(ctx, 0, sizeof(*ctx)); + + // Enable global and external IRQ at Ibex. + irq_global_ctrl(true); + irq_external_ctrl(true); + + // Remember the phase in which we are to stop. + CHECK(fin_phase >= init_phase); + ctx->init_phase = init_phase; + ctx->fin_phase = fin_phase; + + // Default behavior - for simulation with the DPI - is that all types of + // signaling can be performed, in response to reading the test description. + host_suspends = true; + host_resumes = true; + host_resets = true; + host_disconnects = true; + + // DPI model can perform traffic and will deliberately avoid performing + // traffic during the periods when it stops sending the bus frames + + // Override any of the above switches according to the build target. + switch (kDeviceType) { + case kDeviceSimVerilator: + // steal the UART output and send it via a faster mechanism + base_set_stdout(base_stdout); + verbose = true; + +#if !USBDPI_FULL_FRAME + frame_interval = 500u; // Reduce to 0.5ms to shorten simulation time. +#endif + break; + + // Do NOT steal the UART output in this case because DVsim has a back door + // for rapid logging. + case kDeviceSimDV: + verbose = true; + break; + + case kDeviceSilicon: + // Silicon targets are presently tested with the use of the HyperDebug + // board for control over the VBUS connection. + // Suspend, Resume and Reset operations all rely upon control of the + // parent USB hub. + host_suspends = true; + host_resumes = true; + host_resets = true; + host_disconnects = true; + verbose = false; + break; + + default: + CHECK(kDeviceType == kDeviceFpgaCw310); + OT_FALLTHROUGH_INTENDED; + case kDeviceFpgaCw340: + // FPGA host can be used to perform Bus Resets and (with hoop-jumping) + // Suspend and Resume. With physical intervention or perhaps a capable + // hub it can perform VBUS Disconnects; such hubs are a rare minority, + // however. + host_suspends = true; + host_resumes = true; + host_resets = true; + // this must still be exercised manually with FPGA boards. + host_disconnects = false; + + // Presently, the FPGA build is expected to be observed/monitored by a + // developer, so verbose reporting is appropriate. + verbose = false; // true; + break; + } + + ctx->with_traffic = with_traffic; + + // Initialize pinmux. + CHECK_DIF_OK(dif_pinmux_init( + mmio_region_from_addr(TOP_EARLGREY_PINMUX_AON_BASE_ADDR), &pinmux)); + pinmux_testutils_init(&pinmux); + CHECK_DIF_OK(dif_pinmux_input_select( + &pinmux, kTopEarlgreyPinmuxPeripheralInUsbdevSense, + kTopEarlgreyPinmuxInselIoc7)); + + // Initialize pwrmgr. + CHECK_DIF_OK(dif_pwrmgr_init( + mmio_region_from_addr(TOP_EARLGREY_PWRMGR_AON_BASE_ADDR), &pwrmgr)); + + // Initialize the PLIC. + CHECK_DIF_OK(dif_rv_plic_init( + mmio_region_from_addr(TOP_EARLGREY_RV_PLIC_BASE_ADDR), &rv_plic)); + + // Initialize rstmgr + CHECK_DIF_OK(dif_rstmgr_init( + mmio_region_from_addr(TOP_EARLGREY_RSTMGR_AON_BASE_ADDR), &rstmgr)); + + // Enable all the AON interrupts used in this test. + rv_plic_testutils_irq_range_enable(&rv_plic, kTopEarlgreyPlicTargetIbex0, + kTopEarlgreyPlicIrqIdPwrmgrAonWakeup, + kTopEarlgreyPlicIrqIdPwrmgrAonWakeup); + + CHECK_DIF_OK(dif_pwrmgr_set_request_sources(&pwrmgr, kDifPwrmgrReqTypeWakeup, + kDifPwrmgrWakeupRequestSourceFour, + kDifToggleEnabled)); + + // Enable pwrmgr interrupt. + CHECK_DIF_OK(dif_pwrmgr_irq_set_enabled(&pwrmgr, 0, kDifToggleEnabled)); + + // Check if there was a HW reset caused by the wdog bite. + dif_rstmgr_reset_info_bitfield_t rst_info = rstmgr_testutils_reason_get(); + rstmgr_testutils_reason_clear(); + + // Initialize testing context and state machine + ctx->usbdev = &usbdev; + + if (rst_info == kDifRstmgrResetInfoPor) { + const char *iters = "eternally"; + static char buf[20]; + + report_progress("Running USBDEV_SUSPEND test"); + + // Remember the requested iteration count. + ctx->num_iters = num_iters; + + // Report the iteration count + if (num_iters != USBDEV_SUSPEND_ETERNAL) { + base_snprintf(buf, sizeof(buf), "%u times", num_iters); + iters = buf; + } + LOG_INFO(" (seq: %s to %s %s with%s traffic)", phase_name(init_phase), + phase_name(ctx->fin_phase), iters, ctx->with_traffic ? "" : "out"); + + // Power On Reset + LOG_INFO("Booting for the first time"); + + phase_set(ctx, init_phase); + state_enter(ctx, kSuspendStatePowerOnReset); + } else { + if (verbose) { + // Avoid UART-based logging as much as possible because producing UART + // traffic will stall the CPU waiting on FIFO space, and that can lead to + // USB timeouts when attempting to Resume from Deep Sleep. + LOG_INFO("Resuming from power down!"); + } + + // Recover state from the retention RAM + retention_sram_load(&ctx->retn_state); + if (verbose) { + LOG_INFO(" - retained address %u config %u phase %u (%s)", + ctx->retn_state.dev_address, ctx->retn_state.dev_config, + ctx->retn_state.test_phase, + phase_name(ctx->retn_state.test_phase)); + } + + // To have arrived we should be in one of these test phases and we should + // have been in kSuspendStateDeepSleep, so we have not retained that. + CHECK(ctx->retn_state.test_phase == kSuspendPhaseDeepResume || + ctx->retn_state.test_phase == kSuspendPhaseDeepReset || + ctx->retn_state.test_phase == kSuspendPhaseDeepDisconnect); + + // We can - presently - check the other parameters; revise/remove this code + // if the configuration becomes more variable. + CHECK(ctx->retn_state.dev_config == 1u); + + // Reinstate the remaining test iteration count from the retained state. + ctx->num_iters = ctx->retn_state.num_iters; + + // We must remain in the DeepSleep state in order to reinitialize the + // software, run through AON Wake deactivation and then check the reason + // for waking. + phase_set(ctx, ctx->retn_state.test_phase); + state_enter(ctx, kSuspendStateDeepSleep); + } + + do { + // Run this test phase + CHECK_STATUS_OK(phase_run(ctx)); + + // Keep going if we're advancing to the next phase. + // (NextPhase means that we advance whilst still active and can thus skip + // device setup and configuratinon) + } while (ctx->test_state == kSuspendStateNextPhase || // from Resume + ctx->test_state == kSuspendStateBusReset || // after Bus Reset + ctx->test_state == kSuspendStatePowerOnReset); // after Disconnect + + if (verbose) { + LOG_INFO("Test concluding (%s)", state_name(ctx->test_state)); + } + + // Wait enough for a SOF (>1ms) and tear down the software stack. + // Note: there is no finalization code for the streaming at present, because + // it has no resources to release. + busy_spin_micros(1000); + CHECK_STATUS_OK(usb_testutils_fin(ctx->usbdev)); + +#if USBUTILS_FUNCTION_POINTS && USBUTILS_FUNCPT_USE_BUFFER + if (false) { + usbutils_funcpt_report(); + } +#endif + + return (ctx->test_state == kSuspendStateComplete); +} diff --git a/sw/device/tests/usbdev_suspend.h b/sw/device/tests/usbdev_suspend.h new file mode 100644 index 0000000000000..7958728a77b15 --- /dev/null +++ b/sw/device/tests/usbdev_suspend.h @@ -0,0 +1,84 @@ +// 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_DEVICE_TESTS_USBDEV_SUSPEND_H_ +#define OPENTITAN_SW_DEVICE_TESTS_USBDEV_SUSPEND_H_ +#include +#include + +// USB suspend/resume test +// +// The testing of suspend, sleep, wakeup and resume logic can consume a _lot_ +// of simulation time because the timings of the USB protocol are very +// conservative, eg. Resume Signaling should occur for at least 20ms and Reset +// Signaling should be at least 10ms. These durations are impractical for +// chip simulation, so the DPI model intentionally shortens these delays. +// +// Additionally, to exercise both Normal and Deep Sleep modes and the three +// different reasons for waking from each sleep, the total number of test +// phases/states is prohibitive for a single simulation. Individual top-level +// tests therefore specify a range of test phases, and it is expected that the +// full sequence shall only ever be exercised on FPGA with a physical host. + +// Iteration count that denotes looping indefinitely +#define USBDEV_SUSPEND_ETERNAL 0U + +/** + * Test phases; named according to the event that we are expecting to occur. + */ +typedef enum { + /** + * First test phase just tests regular Suspend/Resume signaling; after we've + * resumed, we expect a Bus Reset from the DPI/Host. + */ + kSuspendPhaseSuspend = 0u, + /** + * This test phase instructs the DPI model to put the DUT into Suspend long + * enough that this software will attempt to put the device into its Normal + * Sleep state and exercise the AON/Wakeup module, stopping the clocks but not + * powering down. + */ + kSuspendPhaseSleepResume, + /* + * The AON/Wakeup module will cause us to awaken in response to a bus reset. + */ + kSuspendPhaseSleepReset, + /** + * As above, but this time we're expecting a VBUS/SENSE loss. + */ + kSuspendPhaseSleepDisconnect, + /** + * Mirrors Resume detection for normal sleep, but this time we enter Deep + * Sleep and the power is removed too. + */ + kSuspendPhaseDeepResume, + /** + * Mirrors Bus Reset detection for normal sleep, but this time we enter Deep + * Sleep and the power is removed too. + */ + kSuspendPhaseDeepReset, + /** + * As above, but this time we're expecting a VBUS/SENSE loss. + */ + kSuspendPhaseDeepDisconnect, + /** + * Final phase; shut down. + */ + kSuspendPhaseShutdown, +} usbdev_suspend_phase_t; + +/** + * USB Suspend/Sleep/Wakeup/Resume test body. + * + * @param init_phase Initial phase of test (inclusive). + * @param fin_phase Final phase of test (inclusive). + * @param num_iters Number of iterations of the phase sequence. (0 = eternal) + * @param with_traffic Perform streaming throughout test. + * @param return Successful completion of test. + */ +bool usbdev_suspend_test(usbdev_suspend_phase_t init_phase, + usbdev_suspend_phase_t fin_phase, uint32_t num_iters, + bool with_traffic); + +#endif // OPENTITAN_SW_DEVICE_TESTS_USBDEV_SUSPEND_H_ diff --git a/sw/device/tests/usbdev_suspend_full_test.c b/sw/device/tests/usbdev_suspend_full_test.c new file mode 100644 index 0000000000000..c7616d42e40c1 --- /dev/null +++ b/sw/device/tests/usbdev_suspend_full_test.c @@ -0,0 +1,16 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#include "sw/device/lib/testing/test_framework/ottf_main.h" +#include "sw/device/tests/usbdev_suspend.h" + +OTTF_DEFINE_TEST_CONFIG(); + +bool test_main(void) { + // Execute the entire test sequence; all test phases. + // Note: This is impractical in simulation, and should be used only on FPGA + // with a physical host. + return usbdev_suspend_test(kSuspendPhaseSuspend, kSuspendPhaseShutdown, 1u, + false); +} diff --git a/sw/device/tests/usbdev_suspend_resume_test.c b/sw/device/tests/usbdev_suspend_resume_test.c new file mode 100644 index 0000000000000..94b03c541a0a1 --- /dev/null +++ b/sw/device/tests/usbdev_suspend_resume_test.c @@ -0,0 +1,13 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#include "sw/device/lib/testing/test_framework/ottf_main.h" +#include "sw/device/tests/usbdev_suspend.h" + +OTTF_DEFINE_TEST_CONFIG(); + +bool test_main(void) { + return usbdev_suspend_test(kSuspendPhaseSuspend, kSuspendPhaseSuspend, 1u, + true); +} From 57798aea1a4336aa8b1d5d2a2081bad056b2a871 Mon Sep 17 00:00:00 2001 From: Adrian Lees Date: Fri, 17 May 2024 13:31:03 +0100 Subject: [PATCH 4/5] [host,usbdev] Harness for USBDEV suspend-resume testing Suspend-sleep-resume testing of USBDEV involves multiple test phases, states and stimuli, to check the DUT response to Resume Signaling, Bus Reset or VBUS loss during each of powered, Normal Sleep and Deep Sleep states. Signed-off-by: Adrian Lees --- sw/device/tests/BUILD | 36 +-- sw/host/tests/chip/usb/BUILD | 16 ++ sw/host/tests/chip/usb/usb.rs | 5 +- sw/host/tests/chip/usb/usbdev_suspend.rs | 336 +++++++++++++++++++++++ 4 files changed, 365 insertions(+), 28 deletions(-) create mode 100644 sw/host/tests/chip/usb/usbdev_suspend.rs diff --git a/sw/device/tests/BUILD b/sw/device/tests/BUILD index c4269566fa2ee..14fdc36d24673 100644 --- a/sw/device/tests/BUILD +++ b/sw/device/tests/BUILD @@ -4828,9 +4828,7 @@ opentitan_test( srcs = [ "usbdev_suspend_resume_test.c", ], - cw310 = cw310_params( - timeout = "eternal", - tags = ["manual"], + cw310 = fpga_params( test_cmd = """ --bootstrap="{firmware}" --fin-phase=suspend @@ -4868,9 +4866,7 @@ opentitan_test( srcs = [ "usbdev_sleep_resume_test.c", ], - cw310 = cw310_params( - timeout = "eternal", - tags = ["manual"], + cw310 = fpga_params( test_cmd = """ --bootstrap="{firmware}" --fin-phase=sleep-resume @@ -4909,9 +4905,7 @@ opentitan_test( srcs = [ "usbdev_sleep_reset_test.c", ], - cw310 = cw310_params( - timeout = "eternal", - tags = ["manual"], + cw310 = fpga_params( test_cmd = """ --bootstrap="{firmware}" --fin-phase=sleep-reset @@ -4950,7 +4944,7 @@ opentitan_test( srcs = [ "usbdev_sleep_disconnect_test.c", ], - cw310 = cw310_params( + cw310 = fpga_params( timeout = "eternal", tags = ["manual"], test_cmd = """ @@ -4991,9 +4985,7 @@ opentitan_test( srcs = [ "usbdev_deep_resume_test.c", ], - cw310 = cw310_params( - timeout = "eternal", - tags = ["manual"], + cw310 = fpga_params( test_cmd = """ --bootstrap="{firmware}" --fin-phase=deep-resume @@ -5032,9 +5024,7 @@ opentitan_test( srcs = [ "usbdev_deep_reset_test.c", ], - cw310 = cw310_params( - timeout = "eternal", - tags = ["manual"], + cw310 = fpga_params( test_cmd = """ --bootstrap="{firmware}" --fin-phase=deep-reset @@ -5073,9 +5063,7 @@ opentitan_test( srcs = [ "usbdev_deep_disconnect_test.c", ], - cw310 = cw310_params( - timeout = "eternal", - tags = ["manual"], + cw310 = fpga_params( test_cmd = """ --bootstrap="{firmware}" --fin-phase=shutdown @@ -5114,15 +5102,9 @@ opentitan_test( srcs = [ "usbdev_suspend_full_test.c", ], - cw310 = cw310_params( - timeout = "eternal", - tags = ["manual"], - test_cmd = """ - --bootstrap="{firmware}" - --init-phase=suspend - """, + # This test cannot be implemented on the CW310/CW340 because + # there is no VBUS control so we cannot disconnect the device. exec_env = dicts.add( - EARLGREY_TEST_ENVS, EARLGREY_SILICON_OWNER_ROM_EXT_ENVS, { "//hw/top_earlgrey:sim_dv": None, diff --git a/sw/host/tests/chip/usb/BUILD b/sw/host/tests/chip/usb/BUILD index bba76b2d34ea7..b3f268a25b271 100644 --- a/sw/host/tests/chip/usb/BUILD +++ b/sw/host/tests/chip/usb/BUILD @@ -37,6 +37,22 @@ rust_binary( ], ) +rust_binary( + name = "usbdev_suspend", + srcs = [ + "usbdev_suspend.rs", + ], + deps = [ + ":usb", + "//sw/host/opentitanlib", + "@crate_index//:anyhow", + "@crate_index//:clap", + "@crate_index//:humantime", + "@crate_index//:log", + "@crate_index//:rusb", + ], +) + rust_binary( name = "usb_harness", srcs = [ diff --git a/sw/host/tests/chip/usb/usb.rs b/sw/host/tests/chip/usb/usb.rs index 3243ae14933e4..0036780509369 100644 --- a/sw/host/tests/chip/usb/usb.rs +++ b/sw/host/tests/chip/usb/usb.rs @@ -169,7 +169,10 @@ impl UsbOpts { let Some(vbus_sense_en) = &self.vbus_sense_en else { bail!("cannot control VBUS, you must specify --vbus-sense-en"); }; - log::info!("{} VBUS sensing.", if en { "Enable" } else { "Disable" }); + log::info!( + "{} VBUS to OT USBDEV.", + if en { "Enable" } else { "Disable" } + ); let vbus_sense_en_pin = transport.gpio_pin(vbus_sense_en)?; vbus_sense_en_pin.write(en)?; // Give time to hardware buffer to stabilize. diff --git a/sw/host/tests/chip/usb/usbdev_suspend.rs b/sw/host/tests/chip/usb/usbdev_suspend.rs new file mode 100644 index 0000000000000..9b7045a13cd2c --- /dev/null +++ b/sw/host/tests/chip/usb/usbdev_suspend.rs @@ -0,0 +1,336 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 +// +// This harness controls a number of suspend-sleep-resume USBDEV tests: +// - usbdev_suspend_resume_test +// - usbdev_sleep_resume_test +// - usbdev_sleep_reset_test +// - usbdev_sleep_disconnect_test +// - usbdev_sleep_resume_test +// - usbdev_sleep_reset_test +// - usbdev_sleep_disconnect_test +// +// The above are sub-sections of the following full sequence test: +// - usbdev_suspend_full_test + +use anyhow::{anyhow, bail, ensure, Context, Result}; +use clap::{Parser, ValueEnum}; +use std::time::Duration; + +use opentitanlib::app::TransportWrapper; +use opentitanlib::execute_test; +use opentitanlib::io::uart::Uart; +use opentitanlib::test_utils::init::InitializeTest; +use opentitanlib::uart::console::UartConsole; + +use usb::{UsbHub, UsbHubOp, UsbOpts}; + +#[derive(Debug, Parser)] +struct Opts { + #[command(flatten)] + init: InitializeTest, + + // Console/USB timeout; test harness must allow time for device configuration by + // the host, as well as iterating through a number of test phases. + #[arg(long, value_parser = humantime::parse_duration, default_value = "60s")] + timeout: Duration, + + /// USB options. + #[command(flatten)] + usb: UsbOpts, + + // Iteration count. + #[arg(long, default_value = "1")] + num_iters: u32, + + // Test phases. + #[arg(long)] + init_phase: SuspendPhase, + #[arg(long)] + fin_phase: SuspendPhase, +} + +// Test phases +// - the tests controlled by this sequence presently run a contiguous sequence of phases +// within this full sequence. +#[derive(Clone, Debug, PartialEq, Eq, ValueEnum)] +enum SuspendPhase { + // Resume Signaling stimulus when suspended but not in a sleep state. + Suspend, + // Resume Signaling when the device is in Normal Sleep + // (clocks stopped but USBDEV not powered down). + SleepResume, + // Awaken from Normal Sleep in responses to a Bus Reset. + SleepReset, + // ... to a VBUS removal (disconnection). + SleepDisconnect, + // As for the above tests phases, but entering Deep Sleep rather in which power is removed also. + DeepResume, + DeepReset, + DeepDisconnect, + // Final test state; device disconnects and test completes. + Shutdown, +} + +impl ToString for SuspendPhase { + fn to_string(&self) -> String { + match self { + SuspendPhase::Suspend => String::from("Suspend"), + SuspendPhase::SleepResume => String::from("SleepResume"), + SuspendPhase::SleepReset => String::from("SleepReset"), + SuspendPhase::SleepDisconnect => String::from("SleepDisconnect"), + SuspendPhase::DeepResume => String::from("DeepResume"), + SuspendPhase::DeepReset => String::from("DeepReset"), + SuspendPhase::DeepDisconnect => String::from("DeepDisconnect"), + SuspendPhase::Shutdown => String::from("Shutdown"), + } + } +} + +// Wait for a device to appear and then return the parent device and port number. +fn wait_for_device_and_get_parent(opts: &Opts) -> Result<(rusb::Device, u8)> { + // Wait for USB device to appear. + log::info!("waiting for device..."); + let devices = opts.usb.wait_for_device(opts.timeout)?; + if devices.is_empty() { + bail!("no USB device found"); + } + if devices.len() > 1 { + bail!("several USB devices found"); + } + let device = &devices[0]; + log::info!( + "device found at bus={} address={}", + device.device().bus_number(), + device.device().address() + ); + + // Important note: here the handle will be dropped and the device handle + // will be closed. + Ok(( + device + .device() + .get_parent() + .context("device has no parent, you need to connect it via a hub for this test")?, + device.device().port_number(), + )) +} + +// Delay for the specified number of USB milliseconds (= bus frames). +fn delay_millis(frames: u64) { + std::thread::sleep(Duration::from_millis(frames)); +} + +// Time intervals, in milliseconds. +// - These are determined by the USB 2.0 Protocol specification. +const TIME_SUSPENDING: u64 = 4; +const TIME_RESUMING: u64 = 20; +const TIME_RESETTING: u64 = 10; + +fn suspend(hub: &UsbHub, port: u8) -> Result<()> { + log::info!("suspending port {}", port); + hub.op(UsbHubOp::Suspend, port, Duration::from_millis(100))?; + // Device shall suspend after 3ms of Idle state. + delay_millis(TIME_SUSPENDING); + log::info!("suspended"); + Ok(()) +} + +fn resume(hub: &UsbHub, port: u8) -> Result<()> { + log::info!("resuming device on port {}", port); + hub.op(UsbHubOp::Resume, port, Duration::from_millis(100))?; + // Resume Signaling shall be performed for at least 20ms. + delay_millis(TIME_RESUMING); + log::info!("resumed"); + Ok(()) +} + +fn reset(hub: &UsbHub, port: u8) -> Result<()> { + log::info!("resetting device on port {}", port); + // Reset Signaling shall be performed for at least 10ms. + hub.op(UsbHubOp::Reset, port, Duration::from_millis(100))?; + delay_millis(TIME_RESETTING); + log::info!("reset"); + Ok(()) +} + +fn connect(usb: &UsbOpts, transport: &TransportWrapper) -> Result<()> { + log::info!("connecting device"); + usb.enable_vbus(transport, true)?; + log::info!("connected"); + Ok(()) +} + +fn disconnect(usb: &UsbOpts, transport: &TransportWrapper) -> Result<()> { + log::info!("disconnecting device"); + usb.enable_vbus(transport, false)?; + log::info!("disconnected"); + Ok(()) +} + +// Implements the host-side component of usbdev_suspend_<>_test +fn usbdev_suspend( + opts: &Opts, + transport: &TransportWrapper, + uart: &dyn Uart, +) -> Result<(), anyhow::Error> { + // - These impact successful operation of the device-side software; vary them appreciably + // and test failures may occur through device-side timeout or the device not being ready. + let time_suspended_short: u64 = 3; + let time_suspended_long: u64 = 50; + + // - Fairly arbitrary, may be modified quite freely. + let time_disconnected: u64 = 1000; + + // Enable VBUS sense on the board if necessary. + if opts.usb.vbus_control_available() { + opts.usb.enable_vbus(transport, true)?; + } + // Sense VBUS if available. + if opts.usb.vbus_sense_available() { + ensure!( + opts.usb.vbus_present(transport)?, + "OT USB does not appear to be connected to a host (VBUS not detected)" + ); + } + + // Wait for device to appear. + let (parent, port) = wait_for_device_and_get_parent(opts)?; + log::info!( + "parent hub at bus={}, address={}, port numbers={:?}", + parent.bus_number(), + parent.address(), + parent.port_numbers()? + ); + log::info!("device under test is on port {}", port); + // At this point, we are not holding any device handle. If we really want to make sure, + // we could unbind the device from the driver but this requires a lot of privileges. + + let _devices = opts.usb.wait_for_device(opts.timeout)?; + + let hub = UsbHub::from_device(&parent).context("for this test, you need to make sure that the program has sufficient permissions to access the hub")?; + + // Collect test phases. + let init_phase = opts.init_phase.clone(); + let fin_phase = opts.fin_phase.clone(); + + // The full suspend-sleep-resume testing is decomposed into a number of shorter sequences to + // make chip-level simulation feasible. Most of the top-level tests that this harness supports + // expects to run through a short sub-sequence of these test phases. + log::info!( + "Phase sequence - {} to {} inclusive", + init_phase.to_string(), + fin_phase.to_string() + ); + + for iter in 1..=opts.num_iters { + log::info!("Iteration {} of {}", iter, opts.num_iters); + let mut phase = init_phase.clone(); + loop { + log::info!("Test phase {}", phase.to_string()); + + // Synchronize with the device-side code; it shall always emit this message at the point + // of being ready to receive the stimulus within a given test phase, because we have + // neither this harness nor the device-side code has any control over how long it takes + // the host to detect and configure the device. + UartConsole::wait_for(uart, r"Phase awaiting stimulus", opts.timeout)?; + + // All phase require a Suspend request and then wait for > 3 frames; some phases require + // a longer delay so that the device-side code decides to enter a sleep state. + suspend(&hub, port)?; + if phase == SuspendPhase::Suspend { + delay_millis(time_suspended_short); + } else { + // Longer Suspended state, a cue to enter the sleep state. + delay_millis(time_suspended_long); + } + + // Next test phase; initialize to final phase (safer default than the current phase). + let mut next_phase = fin_phase.clone(); + match phase { + // Basic test of Suspend-Resume functionality without entering a sleep state. + SuspendPhase::Suspend => { + resume(&hub, port)?; + delay_millis(10); + next_phase = SuspendPhase::SleepResume; + } + // Suspend, enter Normal Sleep, and then awaken in response to Resume signaling. + SuspendPhase::SleepResume => { + resume(&hub, port)?; + next_phase = SuspendPhase::SleepReset; + } + // Suspend, enter Normal Sleep, and then awaken in response to a Bus Reset. + SuspendPhase::SleepReset => { + reset(&hub, port)?; + next_phase = SuspendPhase::SleepDisconnect; + } + // Suspend, enter Normal Sleep, and then awaken in response to a VBUS Disconnection. + SuspendPhase::SleepDisconnect => { + if opts.usb.vbus_control_available() { + disconnect(&opts.usb, transport)?; + delay_millis(time_disconnected); + connect(&opts.usb, transport)?; + } else { + log::info!("Skipping VBUS Disconnection because support unavailable"); + resume(&hub, port)?; + } + next_phase = SuspendPhase::DeepResume; + } + // Suspend, enter Deep Sleep, and then awaken in response to Resume Signaling. + SuspendPhase::DeepResume => { + resume(&hub, port)?; + next_phase = SuspendPhase::DeepReset; + } + // Suspend, enter Deep Sleep, and then awaken in response to a Bus Reset. + SuspendPhase::DeepReset => { + reset(&hub, port)?; + next_phase = SuspendPhase::DeepDisconnect; + } + // Suspend, enter Deep Sleep, and then awaken in response to a VBUS Disconnection. + SuspendPhase::DeepDisconnect => { + if opts.usb.vbus_control_available() { + disconnect(&opts.usb, transport)?; + delay_millis(time_disconnected); + connect(&opts.usb, transport)?; + } else { + log::info!("Skipping VBUS Disconnection because support unavailable"); + resume(&hub, port)?; + } + next_phase = SuspendPhase::Shutdown; + } + // Shutdown + SuspendPhase::Shutdown => { + // Nothing or us to do, though we could spot the device disconnection. + } + } + // Have we completed the final phase of the sequence to be tested? + if phase == fin_phase { + break; + } + phase = next_phase; + } + } + + log::info!("Awaiting verdict from device software"); + + // Simply await the PASS/FAIL indication from the device-side software. + let vec = UartConsole::wait_for(uart, r"(PASS|FAIL)!", opts.timeout)?; + match vec[0].as_str() { + "PASS!" => Ok(()), + _ => Err(anyhow!("Failure result: {:?}", vec)), + } +} + +fn main() -> Result<()> { + let opts = Opts::parse(); + opts.init.init_logging(); + let transport = opts.init.init_target()?; + + // Wait until test is running. + let uart = transport.uart("console")?; + UartConsole::wait_for(&*uart, r"Running [^\r\n]*", opts.timeout)?; + + execute_test!(usbdev_suspend, &opts, &transport, &*uart); + Ok(()) +} From 46ec4120963f76063fd1146eab1f598aef0f4e4d Mon Sep 17 00:00:00 2001 From: Douglas Reis Date: Mon, 23 Dec 2024 14:52:02 +0000 Subject: [PATCH 5/5] [SiVal, usbdev] link suspend_resume_test in testplan Signed-off-by: Douglas Reis --- hw/top_earlgrey/data/ip/chip_usbdev_testplan.hjson | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hw/top_earlgrey/data/ip/chip_usbdev_testplan.hjson b/hw/top_earlgrey/data/ip/chip_usbdev_testplan.hjson index 0dfbec10f0185..366227149c688 100644 --- a/hw/top_earlgrey/data/ip/chip_usbdev_testplan.hjson +++ b/hw/top_earlgrey/data/ip/chip_usbdev_testplan.hjson @@ -339,7 +339,7 @@ si_stage: SV3 lc_states: ["PROD"] tests: [] - bazel: [] + bazel: ["//sw/device/tests:usbdev_suspend_resume_test"] } { name: chip_sw_usbdev_aon_wake_reset