Import USB DIF/testutils from OpenTitan - Bring in the unmodified OpenTitan usbdev code, as of 04/03/2024. A follow-up will patch these to work for Matcha. Change-Id: I8a43b181cdb314b010fdf0f52fc299a81a333209
diff --git a/sw/device/lib/dif/autogen/dif_usbdev_autogen.c b/sw/device/lib/dif/autogen/dif_usbdev_autogen.c new file mode 100644 index 0000000..47b8e0a --- /dev/null +++ b/sw/device/lib/dif/autogen/dif_usbdev_autogen.c
@@ -0,0 +1,309 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// THIS FILE HAS BEEN GENERATED, DO NOT EDIT MANUALLY. COMMAND: +// util/make_new_dif.py --mode=regen --only=autogen + +#include "sw/device/lib/dif/autogen/dif_usbdev_autogen.h" + +#include <stdint.h> + +#include "sw/device/lib/dif/dif_base.h" + +#include "usbdev_regs.h" // Generated. + +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_init(mmio_region_t base_addr, dif_usbdev_t *usbdev) { + if (usbdev == NULL) { + return kDifBadArg; + } + + usbdev->base_addr = base_addr; + + return kDifOk; +} + +dif_result_t dif_usbdev_alert_force(const dif_usbdev_t *usbdev, + dif_usbdev_alert_t alert) { + if (usbdev == NULL) { + return kDifBadArg; + } + + bitfield_bit32_index_t alert_idx; + switch (alert) { + case kDifUsbdevAlertFatalFault: + alert_idx = USBDEV_ALERT_TEST_FATAL_FAULT_BIT; + break; + default: + return kDifBadArg; + } + + uint32_t alert_test_reg = bitfield_bit32_write(0, alert_idx, true); + mmio_region_write32(usbdev->base_addr, USBDEV_ALERT_TEST_REG_OFFSET, + alert_test_reg); + + return kDifOk; +} + +/** + * Get the corresponding interrupt register bit offset of the IRQ. + */ +static bool usbdev_get_irq_bit_index(dif_usbdev_irq_t irq, + bitfield_bit32_index_t *index_out) { + switch (irq) { + case kDifUsbdevIrqPktReceived: + *index_out = USBDEV_INTR_COMMON_PKT_RECEIVED_BIT; + break; + case kDifUsbdevIrqPktSent: + *index_out = USBDEV_INTR_COMMON_PKT_SENT_BIT; + break; + case kDifUsbdevIrqDisconnected: + *index_out = USBDEV_INTR_COMMON_DISCONNECTED_BIT; + break; + case kDifUsbdevIrqHostLost: + *index_out = USBDEV_INTR_COMMON_HOST_LOST_BIT; + break; + case kDifUsbdevIrqLinkReset: + *index_out = USBDEV_INTR_COMMON_LINK_RESET_BIT; + break; + case kDifUsbdevIrqLinkSuspend: + *index_out = USBDEV_INTR_COMMON_LINK_SUSPEND_BIT; + break; + case kDifUsbdevIrqLinkResume: + *index_out = USBDEV_INTR_COMMON_LINK_RESUME_BIT; + break; + case kDifUsbdevIrqAvEmpty: + *index_out = USBDEV_INTR_COMMON_AV_EMPTY_BIT; + break; + case kDifUsbdevIrqRxFull: + *index_out = USBDEV_INTR_COMMON_RX_FULL_BIT; + break; + case kDifUsbdevIrqAvOverflow: + *index_out = USBDEV_INTR_COMMON_AV_OVERFLOW_BIT; + break; + case kDifUsbdevIrqLinkInErr: + *index_out = USBDEV_INTR_COMMON_LINK_IN_ERR_BIT; + break; + case kDifUsbdevIrqRxCrcErr: + *index_out = USBDEV_INTR_COMMON_RX_CRC_ERR_BIT; + break; + case kDifUsbdevIrqRxPidErr: + *index_out = USBDEV_INTR_COMMON_RX_PID_ERR_BIT; + break; + case kDifUsbdevIrqRxBitstuffErr: + *index_out = USBDEV_INTR_COMMON_RX_BITSTUFF_ERR_BIT; + break; + case kDifUsbdevIrqFrame: + *index_out = USBDEV_INTR_COMMON_FRAME_BIT; + break; + case kDifUsbdevIrqPowered: + *index_out = USBDEV_INTR_COMMON_POWERED_BIT; + break; + case kDifUsbdevIrqLinkOutErr: + *index_out = USBDEV_INTR_COMMON_LINK_OUT_ERR_BIT; + break; + default: + return false; + } + + return true; +} + +static dif_irq_type_t irq_types[] = { + kDifIrqTypeEvent, kDifIrqTypeEvent, kDifIrqTypeEvent, kDifIrqTypeEvent, + kDifIrqTypeEvent, kDifIrqTypeEvent, kDifIrqTypeEvent, kDifIrqTypeEvent, + kDifIrqTypeEvent, kDifIrqTypeEvent, kDifIrqTypeEvent, kDifIrqTypeEvent, + kDifIrqTypeEvent, kDifIrqTypeEvent, kDifIrqTypeEvent, kDifIrqTypeEvent, + kDifIrqTypeEvent, +}; + +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_irq_get_type(const dif_usbdev_t *usbdev, + dif_usbdev_irq_t irq, + dif_irq_type_t *type) { + if (usbdev == NULL || type == NULL || irq == kDifUsbdevIrqLinkOutErr + 1) { + return kDifBadArg; + } + + *type = irq_types[irq]; + + return kDifOk; +} + +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_irq_get_state( + const dif_usbdev_t *usbdev, dif_usbdev_irq_state_snapshot_t *snapshot) { + if (usbdev == NULL || snapshot == NULL) { + return kDifBadArg; + } + + *snapshot = + mmio_region_read32(usbdev->base_addr, USBDEV_INTR_STATE_REG_OFFSET); + + return kDifOk; +} + +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_irq_acknowledge_state( + const dif_usbdev_t *usbdev, dif_usbdev_irq_state_snapshot_t snapshot) { + if (usbdev == NULL) { + return kDifBadArg; + } + + mmio_region_write32(usbdev->base_addr, USBDEV_INTR_STATE_REG_OFFSET, + snapshot); + + return kDifOk; +} + +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_irq_is_pending(const dif_usbdev_t *usbdev, + dif_usbdev_irq_t irq, bool *is_pending) { + if (usbdev == NULL || is_pending == NULL) { + return kDifBadArg; + } + + bitfield_bit32_index_t index; + if (!usbdev_get_irq_bit_index(irq, &index)) { + return kDifBadArg; + } + + uint32_t intr_state_reg = + mmio_region_read32(usbdev->base_addr, USBDEV_INTR_STATE_REG_OFFSET); + + *is_pending = bitfield_bit32_read(intr_state_reg, index); + + return kDifOk; +} + +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_irq_acknowledge_all(const dif_usbdev_t *usbdev) { + if (usbdev == NULL) { + return kDifBadArg; + } + + // Writing to the register clears the corresponding bits (Write-one clear). + mmio_region_write32(usbdev->base_addr, USBDEV_INTR_STATE_REG_OFFSET, + UINT32_MAX); + + return kDifOk; +} + +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_irq_acknowledge(const dif_usbdev_t *usbdev, + dif_usbdev_irq_t irq) { + if (usbdev == NULL) { + return kDifBadArg; + } + + bitfield_bit32_index_t index; + if (!usbdev_get_irq_bit_index(irq, &index)) { + return kDifBadArg; + } + + // Writing to the register clears the corresponding bits (Write-one clear). + uint32_t intr_state_reg = bitfield_bit32_write(0, index, true); + mmio_region_write32(usbdev->base_addr, USBDEV_INTR_STATE_REG_OFFSET, + intr_state_reg); + + return kDifOk; +} + +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_irq_force(const dif_usbdev_t *usbdev, + dif_usbdev_irq_t irq, const bool val) { + if (usbdev == NULL) { + return kDifBadArg; + } + + bitfield_bit32_index_t index; + if (!usbdev_get_irq_bit_index(irq, &index)) { + return kDifBadArg; + } + + uint32_t intr_test_reg = bitfield_bit32_write(0, index, val); + mmio_region_write32(usbdev->base_addr, USBDEV_INTR_TEST_REG_OFFSET, + intr_test_reg); + + return kDifOk; +} + +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_irq_get_enabled(const dif_usbdev_t *usbdev, + dif_usbdev_irq_t irq, + dif_toggle_t *state) { + if (usbdev == NULL || state == NULL) { + return kDifBadArg; + } + + bitfield_bit32_index_t index; + if (!usbdev_get_irq_bit_index(irq, &index)) { + return kDifBadArg; + } + + uint32_t intr_enable_reg = + mmio_region_read32(usbdev->base_addr, USBDEV_INTR_ENABLE_REG_OFFSET); + + bool is_enabled = bitfield_bit32_read(intr_enable_reg, index); + *state = is_enabled ? kDifToggleEnabled : kDifToggleDisabled; + + return kDifOk; +} + +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_irq_set_enabled(const dif_usbdev_t *usbdev, + dif_usbdev_irq_t irq, + dif_toggle_t state) { + if (usbdev == NULL) { + return kDifBadArg; + } + + bitfield_bit32_index_t index; + if (!usbdev_get_irq_bit_index(irq, &index)) { + return kDifBadArg; + } + + uint32_t intr_enable_reg = + mmio_region_read32(usbdev->base_addr, USBDEV_INTR_ENABLE_REG_OFFSET); + + bool enable_bit = (state == kDifToggleEnabled) ? true : false; + intr_enable_reg = bitfield_bit32_write(intr_enable_reg, index, enable_bit); + mmio_region_write32(usbdev->base_addr, USBDEV_INTR_ENABLE_REG_OFFSET, + intr_enable_reg); + + return kDifOk; +} + +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_irq_disable_all( + const dif_usbdev_t *usbdev, dif_usbdev_irq_enable_snapshot_t *snapshot) { + if (usbdev == NULL) { + return kDifBadArg; + } + + // Pass the current interrupt state to the caller, if requested. + if (snapshot != NULL) { + *snapshot = + mmio_region_read32(usbdev->base_addr, USBDEV_INTR_ENABLE_REG_OFFSET); + } + + // Disable all interrupts. + mmio_region_write32(usbdev->base_addr, USBDEV_INTR_ENABLE_REG_OFFSET, 0u); + + return kDifOk; +} + +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_irq_restore_all( + const dif_usbdev_t *usbdev, + const dif_usbdev_irq_enable_snapshot_t *snapshot) { + if (usbdev == NULL || snapshot == NULL) { + return kDifBadArg; + } + + mmio_region_write32(usbdev->base_addr, USBDEV_INTR_ENABLE_REG_OFFSET, + *snapshot); + + return kDifOk; +}
diff --git a/sw/device/lib/dif/autogen/dif_usbdev_autogen.h b/sw/device/lib/dif/autogen/dif_usbdev_autogen.h new file mode 100644 index 0000000..293587c --- /dev/null +++ b/sw/device/lib/dif/autogen/dif_usbdev_autogen.h
@@ -0,0 +1,323 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#ifndef OPENTITAN_SW_DEVICE_LIB_DIF_AUTOGEN_DIF_USBDEV_AUTOGEN_H_ +#define OPENTITAN_SW_DEVICE_LIB_DIF_AUTOGEN_DIF_USBDEV_AUTOGEN_H_ + +// THIS FILE HAS BEEN GENERATED, DO NOT EDIT MANUALLY. COMMAND: +// util/make_new_dif.py --mode=regen --only=autogen + +/** + * @file + * @brief <a href="/hw/ip/usbdev/doc/">USBDEV</a> Device Interface Functions + */ + +#include <stdbool.h> +#include <stdint.h> + +#include "sw/device/lib/base/macros.h" +#include "sw/device/lib/base/mmio.h" +#include "sw/device/lib/dif/dif_base.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** + * A handle to usbdev. + * + * This type should be treated as opaque by users. + */ +typedef struct dif_usbdev { + /** + * The base address for the usbdev hardware registers. + */ + mmio_region_t base_addr; +} dif_usbdev_t; + +/** + * Creates a new handle for a(n) usbdev peripheral. + * + * This function does not actuate the hardware. + * + * @param base_addr The MMIO base address of the usbdev peripheral. + * @param[out] usbdev Out param for the initialized handle. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_init(mmio_region_t base_addr, dif_usbdev_t *usbdev); + +/** + * A usbdev alert type. + */ +typedef enum dif_usbdev_alert { + /** + * This fatal alert is triggered when a fatal TL-UL bus integrity fault is + * detected. + */ + kDifUsbdevAlertFatalFault = 0, +} dif_usbdev_alert_t; + +/** + * Forces a particular alert, causing it to be escalated as if the hardware + * had raised it. + * + * @param usbdev A usbdev handle. + * @param alert The alert to force. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_alert_force(const dif_usbdev_t *usbdev, + dif_usbdev_alert_t alert); + +/** + * A usbdev interrupt request type. + */ +typedef enum dif_usbdev_irq { + /** + * Raised if a packet was received using an OUT or SETUP transaction. This + * interrupt is directly tied to whether the RX FIFO is empty, so it should be + * cleared only after handling the FIFO entry. + */ + kDifUsbdevIrqPktReceived = 0, + /** + * Raised if a packet was sent as part of an IN transaction. This interrupt is + * directly tied to whether a sent packet has not been acknowledged in the + * !!in_sent register. It should be cleared only after clearing all bits in + * the !!in_sent register. + */ + kDifUsbdevIrqPktSent = 1, + /** + * Raised if VBUS is lost thus the link is disconnected. + */ + kDifUsbdevIrqDisconnected = 2, + /** + * Raised if link is active but SOF was not received from host for 4.096 ms. + * The SOF should be every 1 ms. + */ + kDifUsbdevIrqHostLost = 3, + /** + * Raised if the link is at SE0 longer than 3 us indicating a link reset (host + * asserts for min 10 ms, device can react after 2.5 us). + */ + kDifUsbdevIrqLinkReset = 4, + /** + * Raised if the line has signaled J for longer than 3ms and is therefore in + * suspend state. + */ + kDifUsbdevIrqLinkSuspend = 5, + /** + * Raised when the link becomes active again after being suspended. + */ + kDifUsbdevIrqLinkResume = 6, + /** + * Raised when the AV FIFO is empty and the device interface is enabled. This + * interrupt is directly tied to the FIFO status, so the AV FIFO must be + * provided a free buffer before the interrupt is cleared. If the condition is + * not cleared, the interrupt can re-assert. + */ + kDifUsbdevIrqAvEmpty = 7, + /** + * Raised when the RX FIFO is full and the device interface is enabled. This + * interrupt is directly tied to the FIFO status, so the RX FIFO must have an + * entry removed before the interrupt is cleared. If the condition is not + * cleared, the interrupt can re-assert. + */ + kDifUsbdevIrqRxFull = 8, + /** + * Raised if a write was done to the Available Buffer FIFO when the FIFO was + * full. + */ + kDifUsbdevIrqAvOverflow = 9, + /** + * Raised if a packet to an IN endpoint started to be received but was then + * dropped due to an error. After transmitting the IN payload, the USB device + * expects a valid ACK handshake packet. This error is raised if either the + * packet or CRC is invalid or a different token was received. + */ + kDifUsbdevIrqLinkInErr = 10, + /** + * Raised if a CRC error occured. + */ + kDifUsbdevIrqRxCrcErr = 11, + /** + * Raised if an invalid packed identifier (PID) was received. + */ + kDifUsbdevIrqRxPidErr = 12, + /** + * Raised if an invalid bitstuffing was received. + */ + kDifUsbdevIrqRxBitstuffErr = 13, + /** + * Raised when the USB frame number is updated with a valid SOF. + */ + kDifUsbdevIrqFrame = 14, + /** + * Raised if VBUS is applied. + */ + kDifUsbdevIrqPowered = 15, + /** + * Raised if a packet to an OUT endpoint started to be received but was then + * dropped due to an error. This error is raised if either the data toggle, + * token, packet or CRC is invalid or if there is no buffer available in the + * Received Buffer FIFO. + */ + kDifUsbdevIrqLinkOutErr = 16, +} dif_usbdev_irq_t; + +/** + * A snapshot of the state of the interrupts for this IP. + * + * This is an opaque type, to be used with the `dif_usbdev_irq_get_state()` + * and `dif_usbdev_irq_acknowledge_state()` functions. + */ +typedef uint32_t dif_usbdev_irq_state_snapshot_t; + +/** + * Returns the type of a given interrupt (i.e., event or status) for this IP. + * + * @param usbdev A usbdev handle. + * @param irq An interrupt request. + * @param[out] type Out-param for the interrupt type. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_irq_get_type(const dif_usbdev_t *usbdev, + dif_usbdev_irq_t irq, + dif_irq_type_t *type); + +/** + * Returns the state of all interrupts (i.e., pending or not) for this IP. + * + * @param usbdev A usbdev handle. + * @param[out] snapshot Out-param for interrupt state snapshot. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_irq_get_state( + const dif_usbdev_t *usbdev, dif_usbdev_irq_state_snapshot_t *snapshot); + +/** + * Returns whether a particular interrupt is currently pending. + * + * @param usbdev A usbdev handle. + * @param irq An interrupt request. + * @param[out] is_pending Out-param for whether the interrupt is pending. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_irq_is_pending(const dif_usbdev_t *usbdev, + dif_usbdev_irq_t irq, bool *is_pending); + +/** + * Acknowledges all interrupts that were pending at the time of the state + * snapshot. + * + * @param usbdev A usbdev handle. + * @param snapshot Interrupt state snapshot. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_irq_acknowledge_state( + const dif_usbdev_t *usbdev, dif_usbdev_irq_state_snapshot_t snapshot); + +/** + * Acknowledges all interrupts, indicating to the hardware that all + * interrupts have been successfully serviced. + * + * @param usbdev A usbdev handle. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_irq_acknowledge_all(const dif_usbdev_t *usbdev); + +/** + * Acknowledges a particular interrupt, indicating to the hardware that it has + * been successfully serviced. + * + * @param usbdev A usbdev handle. + * @param irq An interrupt request. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_irq_acknowledge(const dif_usbdev_t *usbdev, + dif_usbdev_irq_t irq); + +/** + * Forces a particular interrupt, causing it to be serviced as if hardware had + * asserted it. + * + * @param usbdev A usbdev handle. + * @param irq An interrupt request. + * @param val Value to be set. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_irq_force(const dif_usbdev_t *usbdev, + dif_usbdev_irq_t irq, const bool val); + +/** + * A snapshot of the enablement state of the interrupts for this IP. + * + * This is an opaque type, to be used with the + * `dif_usbdev_irq_disable_all()` and `dif_usbdev_irq_restore_all()` + * functions. + */ +typedef uint32_t dif_usbdev_irq_enable_snapshot_t; + +/** + * Checks whether a particular interrupt is currently enabled or disabled. + * + * @param usbdev A usbdev handle. + * @param irq An interrupt request. + * @param[out] state Out-param toggle state of the interrupt. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_irq_get_enabled(const dif_usbdev_t *usbdev, + dif_usbdev_irq_t irq, + dif_toggle_t *state); + +/** + * Sets whether a particular interrupt is currently enabled or disabled. + * + * @param usbdev A usbdev handle. + * @param irq An interrupt request. + * @param state The new toggle state for the interrupt. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_irq_set_enabled(const dif_usbdev_t *usbdev, + dif_usbdev_irq_t irq, + dif_toggle_t state); + +/** + * Disables all interrupts, optionally snapshotting all enable states for later + * restoration. + * + * @param usbdev A usbdev handle. + * @param[out] snapshot Out-param for the snapshot; may be `NULL`. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_irq_disable_all( + const dif_usbdev_t *usbdev, dif_usbdev_irq_enable_snapshot_t *snapshot); + +/** + * Restores interrupts from the given (enable) snapshot. + * + * @param usbdev A usbdev handle. + * @param snapshot A snapshot to restore from. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_irq_restore_all( + const dif_usbdev_t *usbdev, + const dif_usbdev_irq_enable_snapshot_t *snapshot); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#endif // OPENTITAN_SW_DEVICE_LIB_DIF_AUTOGEN_DIF_USBDEV_AUTOGEN_H_
diff --git a/sw/device/lib/dif/autogen/dif_usbdev_autogen_unittest.cc b/sw/device/lib/dif/autogen/dif_usbdev_autogen_unittest.cc new file mode 100644 index 0000000..ad3e952 --- /dev/null +++ b/sw/device/lib/dif/autogen/dif_usbdev_autogen_unittest.cc
@@ -0,0 +1,388 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// THIS FILE HAS BEEN GENERATED, DO NOT EDIT MANUALLY. COMMAND: +// util/make_new_dif.py --mode=regen --only=autogen + +#include "sw/device/lib/dif/autogen/dif_usbdev_autogen.h" + +#include "gtest/gtest.h" +#include "sw/device/lib/base/mmio.h" +#include "sw/device/lib/base/mock_mmio.h" +#include "sw/device/lib/dif/dif_test_base.h" + +#include "usbdev_regs.h" // Generated. + +namespace dif_usbdev_autogen_unittest { +namespace { +using ::mock_mmio::MmioTest; +using ::mock_mmio::MockDevice; +using ::testing::Eq; +using ::testing::Test; + +class UsbdevTest : public Test, public MmioTest { + protected: + dif_usbdev_t usbdev_ = {.base_addr = dev().region()}; +}; + +class InitTest : public UsbdevTest {}; + +TEST_F(InitTest, NullArgs) { + EXPECT_DIF_BADARG(dif_usbdev_init(dev().region(), nullptr)); +} + +TEST_F(InitTest, Success) { + EXPECT_DIF_OK(dif_usbdev_init(dev().region(), &usbdev_)); +} + +class AlertForceTest : public UsbdevTest {}; + +TEST_F(AlertForceTest, NullArgs) { + EXPECT_DIF_BADARG(dif_usbdev_alert_force(nullptr, kDifUsbdevAlertFatalFault)); +} + +TEST_F(AlertForceTest, BadAlert) { + EXPECT_DIF_BADARG( + dif_usbdev_alert_force(nullptr, static_cast<dif_usbdev_alert_t>(32))); +} + +TEST_F(AlertForceTest, Success) { + // Force first alert. + EXPECT_WRITE32(USBDEV_ALERT_TEST_REG_OFFSET, + {{USBDEV_ALERT_TEST_FATAL_FAULT_BIT, true}}); + EXPECT_DIF_OK(dif_usbdev_alert_force(&usbdev_, kDifUsbdevAlertFatalFault)); +} + +class IrqGetTypeTest : public UsbdevTest {}; + +TEST_F(IrqGetTypeTest, NullArgs) { + dif_irq_type_t type; + + EXPECT_DIF_BADARG( + dif_usbdev_irq_get_type(nullptr, kDifUsbdevIrqPktReceived, &type)); + + EXPECT_DIF_BADARG( + dif_usbdev_irq_get_type(&usbdev_, kDifUsbdevIrqPktReceived, nullptr)); + + EXPECT_DIF_BADARG( + dif_usbdev_irq_get_type(nullptr, kDifUsbdevIrqPktReceived, nullptr)); +} + +TEST_F(IrqGetTypeTest, BadIrq) { + dif_irq_type_t type; + + EXPECT_DIF_BADARG(dif_usbdev_irq_get_type( + &usbdev_, static_cast<dif_usbdev_irq_t>(kDifUsbdevIrqLinkOutErr + 1), + &type)); +} + +TEST_F(IrqGetTypeTest, Success) { + dif_irq_type_t type; + + EXPECT_DIF_OK( + dif_usbdev_irq_get_type(&usbdev_, kDifUsbdevIrqPktReceived, &type)); + EXPECT_EQ(type, 0); +} + +class IrqGetStateTest : public UsbdevTest {}; + +TEST_F(IrqGetStateTest, NullArgs) { + dif_usbdev_irq_state_snapshot_t irq_snapshot = 0; + + EXPECT_DIF_BADARG(dif_usbdev_irq_get_state(nullptr, &irq_snapshot)); + + EXPECT_DIF_BADARG(dif_usbdev_irq_get_state(&usbdev_, nullptr)); + + EXPECT_DIF_BADARG(dif_usbdev_irq_get_state(nullptr, nullptr)); +} + +TEST_F(IrqGetStateTest, SuccessAllRaised) { + dif_usbdev_irq_state_snapshot_t irq_snapshot = 0; + + EXPECT_READ32(USBDEV_INTR_STATE_REG_OFFSET, + std::numeric_limits<uint32_t>::max()); + EXPECT_DIF_OK(dif_usbdev_irq_get_state(&usbdev_, &irq_snapshot)); + EXPECT_EQ(irq_snapshot, std::numeric_limits<uint32_t>::max()); +} + +TEST_F(IrqGetStateTest, SuccessNoneRaised) { + dif_usbdev_irq_state_snapshot_t irq_snapshot = 0; + + EXPECT_READ32(USBDEV_INTR_STATE_REG_OFFSET, 0); + EXPECT_DIF_OK(dif_usbdev_irq_get_state(&usbdev_, &irq_snapshot)); + EXPECT_EQ(irq_snapshot, 0); +} + +class IrqIsPendingTest : public UsbdevTest {}; + +TEST_F(IrqIsPendingTest, NullArgs) { + bool is_pending; + + EXPECT_DIF_BADARG(dif_usbdev_irq_is_pending(nullptr, kDifUsbdevIrqPktReceived, + &is_pending)); + + EXPECT_DIF_BADARG( + dif_usbdev_irq_is_pending(&usbdev_, kDifUsbdevIrqPktReceived, nullptr)); + + EXPECT_DIF_BADARG( + dif_usbdev_irq_is_pending(nullptr, kDifUsbdevIrqPktReceived, nullptr)); +} + +TEST_F(IrqIsPendingTest, BadIrq) { + bool is_pending; + // All interrupt CSRs are 32 bit so interrupt 32 will be invalid. + EXPECT_DIF_BADARG(dif_usbdev_irq_is_pending( + &usbdev_, static_cast<dif_usbdev_irq_t>(32), &is_pending)); +} + +TEST_F(IrqIsPendingTest, Success) { + bool irq_state; + + // Get the first IRQ state. + irq_state = false; + EXPECT_READ32(USBDEV_INTR_STATE_REG_OFFSET, + {{USBDEV_INTR_STATE_PKT_RECEIVED_BIT, true}}); + EXPECT_DIF_OK(dif_usbdev_irq_is_pending(&usbdev_, kDifUsbdevIrqPktReceived, + &irq_state)); + EXPECT_TRUE(irq_state); + + // Get the last IRQ state. + irq_state = true; + EXPECT_READ32(USBDEV_INTR_STATE_REG_OFFSET, + {{USBDEV_INTR_STATE_LINK_OUT_ERR_BIT, false}}); + EXPECT_DIF_OK( + dif_usbdev_irq_is_pending(&usbdev_, kDifUsbdevIrqLinkOutErr, &irq_state)); + EXPECT_FALSE(irq_state); +} + +class AcknowledgeStateTest : public UsbdevTest {}; + +TEST_F(AcknowledgeStateTest, NullArgs) { + dif_usbdev_irq_state_snapshot_t irq_snapshot = 0; + EXPECT_DIF_BADARG(dif_usbdev_irq_acknowledge_state(nullptr, irq_snapshot)); +} + +TEST_F(AcknowledgeStateTest, AckSnapshot) { + const uint32_t num_irqs = 17; + const uint32_t irq_mask = (1u << num_irqs) - 1; + dif_usbdev_irq_state_snapshot_t irq_snapshot = 1; + + // Test a few snapshots. + for (size_t i = 0; i < num_irqs; ++i) { + irq_snapshot = ~irq_snapshot & irq_mask; + irq_snapshot |= (1u << i); + EXPECT_WRITE32(USBDEV_INTR_STATE_REG_OFFSET, irq_snapshot); + EXPECT_DIF_OK(dif_usbdev_irq_acknowledge_state(&usbdev_, irq_snapshot)); + } +} + +TEST_F(AcknowledgeStateTest, SuccessNoneRaised) { + dif_usbdev_irq_state_snapshot_t irq_snapshot = 0; + + EXPECT_READ32(USBDEV_INTR_STATE_REG_OFFSET, 0); + EXPECT_DIF_OK(dif_usbdev_irq_get_state(&usbdev_, &irq_snapshot)); + EXPECT_EQ(irq_snapshot, 0); +} + +class AcknowledgeAllTest : public UsbdevTest {}; + +TEST_F(AcknowledgeAllTest, NullArgs) { + EXPECT_DIF_BADARG(dif_usbdev_irq_acknowledge_all(nullptr)); +} + +TEST_F(AcknowledgeAllTest, Success) { + EXPECT_WRITE32(USBDEV_INTR_STATE_REG_OFFSET, + std::numeric_limits<uint32_t>::max()); + + EXPECT_DIF_OK(dif_usbdev_irq_acknowledge_all(&usbdev_)); +} + +class IrqAcknowledgeTest : public UsbdevTest {}; + +TEST_F(IrqAcknowledgeTest, NullArgs) { + EXPECT_DIF_BADARG( + dif_usbdev_irq_acknowledge(nullptr, kDifUsbdevIrqPktReceived)); +} + +TEST_F(IrqAcknowledgeTest, BadIrq) { + EXPECT_DIF_BADARG( + dif_usbdev_irq_acknowledge(nullptr, static_cast<dif_usbdev_irq_t>(32))); +} + +TEST_F(IrqAcknowledgeTest, Success) { + // Clear the first IRQ state. + EXPECT_WRITE32(USBDEV_INTR_STATE_REG_OFFSET, + {{USBDEV_INTR_STATE_PKT_RECEIVED_BIT, true}}); + EXPECT_DIF_OK(dif_usbdev_irq_acknowledge(&usbdev_, kDifUsbdevIrqPktReceived)); + + // Clear the last IRQ state. + EXPECT_WRITE32(USBDEV_INTR_STATE_REG_OFFSET, + {{USBDEV_INTR_STATE_LINK_OUT_ERR_BIT, true}}); + EXPECT_DIF_OK(dif_usbdev_irq_acknowledge(&usbdev_, kDifUsbdevIrqLinkOutErr)); +} + +class IrqForceTest : public UsbdevTest {}; + +TEST_F(IrqForceTest, NullArgs) { + EXPECT_DIF_BADARG( + dif_usbdev_irq_force(nullptr, kDifUsbdevIrqPktReceived, true)); +} + +TEST_F(IrqForceTest, BadIrq) { + EXPECT_DIF_BADARG( + dif_usbdev_irq_force(nullptr, static_cast<dif_usbdev_irq_t>(32), true)); +} + +TEST_F(IrqForceTest, Success) { + // Force first IRQ. + EXPECT_WRITE32(USBDEV_INTR_TEST_REG_OFFSET, + {{USBDEV_INTR_TEST_PKT_RECEIVED_BIT, true}}); + EXPECT_DIF_OK(dif_usbdev_irq_force(&usbdev_, kDifUsbdevIrqPktReceived, true)); + + // Force last IRQ. + EXPECT_WRITE32(USBDEV_INTR_TEST_REG_OFFSET, + {{USBDEV_INTR_TEST_LINK_OUT_ERR_BIT, true}}); + EXPECT_DIF_OK(dif_usbdev_irq_force(&usbdev_, kDifUsbdevIrqLinkOutErr, true)); +} + +class IrqGetEnabledTest : public UsbdevTest {}; + +TEST_F(IrqGetEnabledTest, NullArgs) { + dif_toggle_t irq_state; + + EXPECT_DIF_BADARG(dif_usbdev_irq_get_enabled( + nullptr, kDifUsbdevIrqPktReceived, &irq_state)); + + EXPECT_DIF_BADARG( + dif_usbdev_irq_get_enabled(&usbdev_, kDifUsbdevIrqPktReceived, nullptr)); + + EXPECT_DIF_BADARG( + dif_usbdev_irq_get_enabled(nullptr, kDifUsbdevIrqPktReceived, nullptr)); +} + +TEST_F(IrqGetEnabledTest, BadIrq) { + dif_toggle_t irq_state; + + EXPECT_DIF_BADARG(dif_usbdev_irq_get_enabled( + &usbdev_, static_cast<dif_usbdev_irq_t>(32), &irq_state)); +} + +TEST_F(IrqGetEnabledTest, Success) { + dif_toggle_t irq_state; + + // First IRQ is enabled. + irq_state = kDifToggleDisabled; + EXPECT_READ32(USBDEV_INTR_ENABLE_REG_OFFSET, + {{USBDEV_INTR_ENABLE_PKT_RECEIVED_BIT, true}}); + EXPECT_DIF_OK(dif_usbdev_irq_get_enabled(&usbdev_, kDifUsbdevIrqPktReceived, + &irq_state)); + EXPECT_EQ(irq_state, kDifToggleEnabled); + + // Last IRQ is disabled. + irq_state = kDifToggleEnabled; + EXPECT_READ32(USBDEV_INTR_ENABLE_REG_OFFSET, + {{USBDEV_INTR_ENABLE_LINK_OUT_ERR_BIT, false}}); + EXPECT_DIF_OK(dif_usbdev_irq_get_enabled(&usbdev_, kDifUsbdevIrqLinkOutErr, + &irq_state)); + EXPECT_EQ(irq_state, kDifToggleDisabled); +} + +class IrqSetEnabledTest : public UsbdevTest {}; + +TEST_F(IrqSetEnabledTest, NullArgs) { + dif_toggle_t irq_state = kDifToggleEnabled; + + EXPECT_DIF_BADARG( + dif_usbdev_irq_set_enabled(nullptr, kDifUsbdevIrqPktReceived, irq_state)); +} + +TEST_F(IrqSetEnabledTest, BadIrq) { + dif_toggle_t irq_state = kDifToggleEnabled; + + EXPECT_DIF_BADARG(dif_usbdev_irq_set_enabled( + &usbdev_, static_cast<dif_usbdev_irq_t>(32), irq_state)); +} + +TEST_F(IrqSetEnabledTest, Success) { + dif_toggle_t irq_state; + + // Enable first IRQ. + irq_state = kDifToggleEnabled; + EXPECT_MASK32(USBDEV_INTR_ENABLE_REG_OFFSET, + {{USBDEV_INTR_ENABLE_PKT_RECEIVED_BIT, 0x1, true}}); + EXPECT_DIF_OK(dif_usbdev_irq_set_enabled(&usbdev_, kDifUsbdevIrqPktReceived, + irq_state)); + + // Disable last IRQ. + irq_state = kDifToggleDisabled; + EXPECT_MASK32(USBDEV_INTR_ENABLE_REG_OFFSET, + {{USBDEV_INTR_ENABLE_LINK_OUT_ERR_BIT, 0x1, false}}); + EXPECT_DIF_OK( + dif_usbdev_irq_set_enabled(&usbdev_, kDifUsbdevIrqLinkOutErr, irq_state)); +} + +class IrqDisableAllTest : public UsbdevTest {}; + +TEST_F(IrqDisableAllTest, NullArgs) { + dif_usbdev_irq_enable_snapshot_t irq_snapshot = 0; + + EXPECT_DIF_BADARG(dif_usbdev_irq_disable_all(nullptr, &irq_snapshot)); + + EXPECT_DIF_BADARG(dif_usbdev_irq_disable_all(nullptr, nullptr)); +} + +TEST_F(IrqDisableAllTest, SuccessNoSnapshot) { + EXPECT_WRITE32(USBDEV_INTR_ENABLE_REG_OFFSET, 0); + EXPECT_DIF_OK(dif_usbdev_irq_disable_all(&usbdev_, nullptr)); +} + +TEST_F(IrqDisableAllTest, SuccessSnapshotAllDisabled) { + dif_usbdev_irq_enable_snapshot_t irq_snapshot = 0; + + EXPECT_READ32(USBDEV_INTR_ENABLE_REG_OFFSET, 0); + EXPECT_WRITE32(USBDEV_INTR_ENABLE_REG_OFFSET, 0); + EXPECT_DIF_OK(dif_usbdev_irq_disable_all(&usbdev_, &irq_snapshot)); + EXPECT_EQ(irq_snapshot, 0); +} + +TEST_F(IrqDisableAllTest, SuccessSnapshotAllEnabled) { + dif_usbdev_irq_enable_snapshot_t irq_snapshot = 0; + + EXPECT_READ32(USBDEV_INTR_ENABLE_REG_OFFSET, + std::numeric_limits<uint32_t>::max()); + EXPECT_WRITE32(USBDEV_INTR_ENABLE_REG_OFFSET, 0); + EXPECT_DIF_OK(dif_usbdev_irq_disable_all(&usbdev_, &irq_snapshot)); + EXPECT_EQ(irq_snapshot, std::numeric_limits<uint32_t>::max()); +} + +class IrqRestoreAllTest : public UsbdevTest {}; + +TEST_F(IrqRestoreAllTest, NullArgs) { + dif_usbdev_irq_enable_snapshot_t irq_snapshot = 0; + + EXPECT_DIF_BADARG(dif_usbdev_irq_restore_all(nullptr, &irq_snapshot)); + + EXPECT_DIF_BADARG(dif_usbdev_irq_restore_all(&usbdev_, nullptr)); + + EXPECT_DIF_BADARG(dif_usbdev_irq_restore_all(nullptr, nullptr)); +} + +TEST_F(IrqRestoreAllTest, SuccessAllEnabled) { + dif_usbdev_irq_enable_snapshot_t irq_snapshot = + std::numeric_limits<uint32_t>::max(); + + EXPECT_WRITE32(USBDEV_INTR_ENABLE_REG_OFFSET, + std::numeric_limits<uint32_t>::max()); + EXPECT_DIF_OK(dif_usbdev_irq_restore_all(&usbdev_, &irq_snapshot)); +} + +TEST_F(IrqRestoreAllTest, SuccessAllDisabled) { + dif_usbdev_irq_enable_snapshot_t irq_snapshot = 0; + + EXPECT_WRITE32(USBDEV_INTR_ENABLE_REG_OFFSET, 0); + EXPECT_DIF_OK(dif_usbdev_irq_restore_all(&usbdev_, &irq_snapshot)); +} + +} // namespace +} // namespace dif_usbdev_autogen_unittest
diff --git a/sw/device/lib/dif/dif_usbdev.c b/sw/device/lib/dif/dif_usbdev.c new file mode 100644 index 0000000..3e5105f --- /dev/null +++ b/sw/device/lib/dif/dif_usbdev.c
@@ -0,0 +1,922 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#include "sw/device/lib/dif/dif_usbdev.h" + +#include <assert.h> + +#include "sw/device/lib/base/bitfield.h" + +#include "usbdev_regs.h" // Generated. + +/** + * Definition in the header file (and probably other places) must be updated if + * there is a hardware change. + */ +static_assert(USBDEV_NUM_ENDPOINTS == USBDEV_PARAM_N_ENDPOINTS, + "Mismatch in number of endpoints"); + +/** + * Max packet size is equal to the size of device buffers. + */ +#define USBDEV_BUFFER_ENTRY_SIZE_BYTES USBDEV_MAX_PACKET_SIZE + +/** + * Constants used to indicate that a buffer pool is full or empty. + */ +#define BUFFER_POOL_FULL (USBDEV_NUM_BUFFERS - 1) +#define BUFFER_POOL_EMPTY -1 + +/** + * Hardware information for endpoints. + */ +typedef struct endpoint_hw_info { + uint32_t config_in_reg_offset; + uint8_t bit_index; +} endpoint_hw_info_t; + +/** + * Helper macro to define an `endpoint_hw_info_t` entry for endpoint N. + * + * Note: This uses the bit indices of `USBDEV_IN_SENT` register for the sake + * of conciseness because other endpoint registers use the same layout. + */ +#define ENDPOINT_HW_INFO_ENTRY(N) \ + [N] = {.config_in_reg_offset = USBDEV_CONFIGIN_##N##_REG_OFFSET, \ + .bit_index = USBDEV_IN_SENT_SENT_##N##_BIT} + +static const endpoint_hw_info_t kEndpointHwInfos[USBDEV_NUM_ENDPOINTS] = { + ENDPOINT_HW_INFO_ENTRY(0), ENDPOINT_HW_INFO_ENTRY(1), + ENDPOINT_HW_INFO_ENTRY(2), ENDPOINT_HW_INFO_ENTRY(3), + ENDPOINT_HW_INFO_ENTRY(4), ENDPOINT_HW_INFO_ENTRY(5), + ENDPOINT_HW_INFO_ENTRY(6), ENDPOINT_HW_INFO_ENTRY(7), + ENDPOINT_HW_INFO_ENTRY(8), ENDPOINT_HW_INFO_ENTRY(9), + ENDPOINT_HW_INFO_ENTRY(10), ENDPOINT_HW_INFO_ENTRY(11), +}; + +#undef ENDPOINT_HW_INFO_ENTRY + +/** + * Static functions for the free buffer pool. + */ + +/** + * Checks if a buffer pool is full. + * + * A buffer pool is full if it contains `USBDEV_NUM_BUFFERS` buffers. + * + * @param pool A buffer pool. + * @return `true` if the buffer pool if full, `false` otherwise. + */ +OT_WARN_UNUSED_RESULT +static bool buffer_pool_is_full(dif_usbdev_buffer_pool_t *pool) { + return pool->top == BUFFER_POOL_FULL; +} + +/** + * Checks if a buffer pool is empty. + * + * @param pool A buffer pool. + * @return `true` if the buffer pool is empty, `false` otherwise. + */ +OT_WARN_UNUSED_RESULT +static bool buffer_pool_is_empty(dif_usbdev_buffer_pool_t *pool) { + return pool->top == BUFFER_POOL_EMPTY; +} + +/** + * Checks if a buffer id is valid. + * + * A buffer id is valid if it is less than `USBDEV_NUM_BUFFERS`. + * + * @param buffer_id A buffer id. + * @return `true` if `buffer_id` is valid, `false` otherwise. + */ +OT_WARN_UNUSED_RESULT +static bool buffer_pool_is_valid_buffer_id(uint8_t buffer_id) { + return buffer_id < USBDEV_NUM_BUFFERS; +} + +/** + * Adds a buffer to a buffer pool. + * + * @param pool A buffer pool. + * @param buffer_id A buffer id. + * @return `true` if the operation was successful, `false` otherwise. + */ +OT_WARN_UNUSED_RESULT +static bool buffer_pool_add(dif_usbdev_buffer_pool_t *pool, uint8_t buffer_id) { + if (buffer_pool_is_full(pool) || !buffer_pool_is_valid_buffer_id(buffer_id)) { + return false; + } + + ++pool->top; + pool->buffers[pool->top] = buffer_id; + + return true; +} + +/** + * Removes a buffer from a buffer pool. + * + * @param pool A buffer pool. + * @param buffer_id A buffer id. + * @return `true` if the operation was successful, `false` otherwise. + */ +OT_WARN_UNUSED_RESULT +static bool buffer_pool_remove(dif_usbdev_buffer_pool_t *pool, + uint8_t *buffer_id) { + if (buffer_pool_is_empty(pool) || buffer_id == NULL) { + return false; + } + + *buffer_id = pool->buffers[pool->top]; + --pool->top; + + return true; +} + +/** + * Initializes the buffer pool. + * + * At the end of this operation, the buffer pool contains `USBDEV_NUM_BUFFERS` + * buffers. + * + * @param pool A buffer pool. + * @return `true` if the operation was successful, `false` otherwise. + */ +OT_WARN_UNUSED_RESULT +static bool buffer_pool_init(dif_usbdev_buffer_pool_t *pool) { + // Start with an empty pool + pool->top = -1; + + // Add all buffers + for (uint8_t i = 0; i < USBDEV_NUM_BUFFERS; ++i) { + if (!buffer_pool_add(pool, i)) { + return false; + } + } + + return true; +} + +/** + * Utility functions + */ + +/** + * Checks if the given value is a valid endpoint number. + */ +OT_WARN_UNUSED_RESULT +static bool is_valid_endpoint(uint8_t endpoint_number) { + return endpoint_number < USBDEV_NUM_ENDPOINTS; +} + +/** + * Enables/disables the functionality controlled by the register at `reg_offset` + * for an endpoint. + */ +OT_WARN_UNUSED_RESULT +static dif_result_t endpoint_functionality_enable(const dif_usbdev_t *usbdev, + uint32_t reg_offset, + uint8_t endpoint, + dif_toggle_t new_state) { + if (usbdev == NULL || !is_valid_endpoint(endpoint) || + !dif_is_valid_toggle(new_state)) { + return kDifBadArg; + } + + uint32_t reg_val = mmio_region_read32(usbdev->base_addr, reg_offset); + reg_val = bitfield_bit32_write(reg_val, kEndpointHwInfos[endpoint].bit_index, + dif_toggle_to_bool(new_state)); + mmio_region_write32(usbdev->base_addr, reg_offset, reg_val); + return kDifOk; +} + +/** + * Returns the address that corresponds to the given buffer and offset + * into that buffer. + */ +OT_WARN_UNUSED_RESULT +static uint32_t get_buffer_addr(uint8_t buffer_id, size_t offset) { + return USBDEV_BUFFER_REG_OFFSET + + (buffer_id * USBDEV_BUFFER_ENTRY_SIZE_BYTES) + offset; +} + +/** + * USBDEV DIF library functions. + */ + +dif_result_t dif_usbdev_configure(const dif_usbdev_t *usbdev, + dif_usbdev_buffer_pool_t *buffer_pool, + dif_usbdev_config_t config) { + if (usbdev == NULL || buffer_pool == NULL) { + return kDifBadArg; + } + + // Configure the free buffer pool. + if (!buffer_pool_init(buffer_pool)) { + return kDifError; + } + + // Check enum fields. + if (!dif_is_valid_toggle(config.have_differential_receiver) || + !dif_is_valid_toggle(config.use_tx_d_se0) || + !dif_is_valid_toggle(config.single_bit_eop) || + !dif_is_valid_toggle(config.pin_flip) || + !dif_is_valid_toggle(config.clock_sync_signals)) { + return kDifBadArg; + } + + // Determine the value of the PHY_CONFIG register. + uint32_t phy_config_val = 0; + phy_config_val = bitfield_bit32_write( + phy_config_val, USBDEV_PHY_CONFIG_USE_DIFF_RCVR_BIT, + dif_toggle_to_bool(config.have_differential_receiver)); + phy_config_val = + bitfield_bit32_write(phy_config_val, USBDEV_PHY_CONFIG_TX_USE_D_SE0_BIT, + dif_toggle_to_bool(config.use_tx_d_se0)); + phy_config_val = + bitfield_bit32_write(phy_config_val, USBDEV_PHY_CONFIG_EOP_SINGLE_BIT_BIT, + dif_toggle_to_bool(config.single_bit_eop)); + phy_config_val = + bitfield_bit32_write(phy_config_val, USBDEV_PHY_CONFIG_PINFLIP_BIT, + dif_toggle_to_bool(config.pin_flip)); + phy_config_val = bitfield_bit32_write( + phy_config_val, USBDEV_PHY_CONFIG_USB_REF_DISABLE_BIT, + !dif_toggle_to_bool(config.clock_sync_signals)); + + // Write configuration to PHY_CONFIG register + mmio_region_write32(usbdev->base_addr, USBDEV_PHY_CONFIG_REG_OFFSET, + phy_config_val); + + return kDifOk; +} + +dif_result_t dif_usbdev_fill_available_fifo( + const dif_usbdev_t *usbdev, dif_usbdev_buffer_pool_t *buffer_pool) { + if (usbdev == NULL || buffer_pool == NULL) { + return kDifBadArg; + } + + // Remove buffers from the pool and write them to the AV FIFO until it is full + while (true) { + uint32_t status = + mmio_region_read32(usbdev->base_addr, USBDEV_USBSTAT_REG_OFFSET); + bool av_full = bitfield_bit32_read(status, USBDEV_USBSTAT_AV_FULL_BIT); + if (av_full || buffer_pool_is_empty(buffer_pool)) { + break; + } + uint8_t buffer_id; + if (!buffer_pool_remove(buffer_pool, &buffer_id)) { + return kDifError; + } + uint32_t reg_val = + bitfield_field32_write(0, USBDEV_AVBUFFER_BUFFER_FIELD, buffer_id); + mmio_region_write32(usbdev->base_addr, USBDEV_AVBUFFER_REG_OFFSET, reg_val); + } + + return kDifOk; +} + +dif_result_t dif_usbdev_endpoint_setup_enable(const dif_usbdev_t *usbdev, + uint8_t endpoint, + dif_toggle_t new_state) { + return endpoint_functionality_enable(usbdev, USBDEV_RXENABLE_SETUP_REG_OFFSET, + endpoint, new_state); +} + +dif_result_t dif_usbdev_endpoint_out_enable(const dif_usbdev_t *usbdev, + uint8_t endpoint, + dif_toggle_t new_state) { + return endpoint_functionality_enable(usbdev, USBDEV_RXENABLE_OUT_REG_OFFSET, + endpoint, new_state); +} + +dif_result_t dif_usbdev_endpoint_set_nak_out_enable(const dif_usbdev_t *usbdev, + uint8_t endpoint, + dif_toggle_t new_state) { + return endpoint_functionality_enable(usbdev, USBDEV_SET_NAK_OUT_REG_OFFSET, + endpoint, new_state); +} + +dif_result_t dif_usbdev_endpoint_stall_enable(const dif_usbdev_t *usbdev, + dif_usbdev_endpoint_id_t endpoint, + dif_toggle_t new_state) { + if (endpoint.direction == USBDEV_ENDPOINT_DIR_IN) { + return endpoint_functionality_enable(usbdev, USBDEV_IN_STALL_REG_OFFSET, + endpoint.number, new_state); + } else { + return endpoint_functionality_enable(usbdev, USBDEV_OUT_STALL_REG_OFFSET, + endpoint.number, new_state); + } +} + +dif_result_t dif_usbdev_endpoint_stall_get(const dif_usbdev_t *usbdev, + dif_usbdev_endpoint_id_t endpoint, + bool *state) { + if (usbdev == NULL || state == NULL || !is_valid_endpoint(endpoint.number)) { + return kDifBadArg; + } + + ptrdiff_t reg_offset = endpoint.direction == USBDEV_ENDPOINT_DIR_IN + ? USBDEV_IN_STALL_REG_OFFSET + : USBDEV_OUT_STALL_REG_OFFSET; + uint32_t reg_val = mmio_region_read32(usbdev->base_addr, reg_offset); + *state = + bitfield_bit32_read(reg_val, kEndpointHwInfos[endpoint.number].bit_index); + + return kDifOk; +} + +dif_result_t dif_usbdev_endpoint_iso_enable(const dif_usbdev_t *usbdev, + dif_usbdev_endpoint_id_t endpoint, + dif_toggle_t new_state) { + if (endpoint.direction == USBDEV_ENDPOINT_DIR_IN) { + return endpoint_functionality_enable(usbdev, USBDEV_IN_ISO_REG_OFFSET, + endpoint.number, new_state); + } else { + return endpoint_functionality_enable(usbdev, USBDEV_OUT_ISO_REG_OFFSET, + endpoint.number, new_state); + } +} + +dif_result_t dif_usbdev_endpoint_enable(const dif_usbdev_t *usbdev, + dif_usbdev_endpoint_id_t endpoint, + dif_toggle_t new_state) { + if (endpoint.direction == USBDEV_ENDPOINT_DIR_IN) { + return endpoint_functionality_enable(usbdev, USBDEV_EP_IN_ENABLE_REG_OFFSET, + endpoint.number, new_state); + } else { + return endpoint_functionality_enable( + usbdev, USBDEV_EP_OUT_ENABLE_REG_OFFSET, endpoint.number, new_state); + } + return kDifOk; +} + +dif_result_t dif_usbdev_interface_enable(const dif_usbdev_t *usbdev, + dif_toggle_t new_state) { + if (usbdev == NULL || !dif_is_valid_toggle(new_state)) { + return kDifBadArg; + } + + uint32_t reg_val = + mmio_region_read32(usbdev->base_addr, USBDEV_USBCTRL_REG_OFFSET); + reg_val = bitfield_bit32_write(reg_val, USBDEV_USBCTRL_ENABLE_BIT, + dif_toggle_to_bool(new_state)); + mmio_region_write32(usbdev->base_addr, USBDEV_USBCTRL_REG_OFFSET, reg_val); + + return kDifOk; +} + +dif_result_t dif_usbdev_recv(const dif_usbdev_t *usbdev, + dif_usbdev_rx_packet_info_t *info, + dif_usbdev_buffer_t *buffer) { + if (usbdev == NULL || info == NULL || buffer == NULL) { + return kDifBadArg; + } + + // Check if the RX FIFO is empty + uint32_t fifo_status = + mmio_region_read32(usbdev->base_addr, USBDEV_USBSTAT_REG_OFFSET); + if (bitfield_bit32_read(fifo_status, USBDEV_USBSTAT_RX_EMPTY_BIT)) { + return kDifUnavailable; + } + + // Read fifo entry + const uint32_t fifo_entry = + mmio_region_read32(usbdev->base_addr, USBDEV_RXFIFO_REG_OFFSET); + // Init packet info + *info = (dif_usbdev_rx_packet_info_t){ + .endpoint = bitfield_field32_read(fifo_entry, USBDEV_RXFIFO_EP_FIELD), + .is_setup = bitfield_bit32_read(fifo_entry, USBDEV_RXFIFO_SETUP_BIT), + .length = bitfield_field32_read(fifo_entry, USBDEV_RXFIFO_SIZE_FIELD), + }; + // Init buffer struct + *buffer = (dif_usbdev_buffer_t){ + .id = bitfield_field32_read(fifo_entry, USBDEV_RXFIFO_BUFFER_FIELD), + .offset = 0, + .remaining_bytes = info->length, + .type = kDifUsbdevBufferTypeRead, + }; + + return kDifOk; +} + +dif_result_t dif_usbdev_buffer_request(const dif_usbdev_t *usbdev, + dif_usbdev_buffer_pool_t *buffer_pool, + dif_usbdev_buffer_t *buffer) { + if (usbdev == NULL || buffer_pool == NULL || buffer == NULL) { + return kDifBadArg; + } + + if (buffer_pool_is_empty(buffer_pool)) { + return kDifUnavailable; + } + + uint8_t buffer_id; + if (!buffer_pool_remove(buffer_pool, &buffer_id)) { + return kDifError; + } + + *buffer = (dif_usbdev_buffer_t){ + .id = buffer_id, + .offset = 0, + .remaining_bytes = USBDEV_BUFFER_ENTRY_SIZE_BYTES, + .type = kDifUsbdevBufferTypeWrite, + }; + + return kDifOk; +} + +dif_result_t dif_usbdev_buffer_return(const dif_usbdev_t *usbdev, + dif_usbdev_buffer_pool_t *buffer_pool, + dif_usbdev_buffer_t *buffer) { + if (usbdev == NULL || buffer_pool == NULL || buffer == NULL) { + return kDifBadArg; + } + + switch (buffer->type) { + case kDifUsbdevBufferTypeRead: + case kDifUsbdevBufferTypeWrite: + // Return the buffer to the free buffer pool + if (!buffer_pool_add(buffer_pool, buffer->id)) { + return kDifError; + } + // Mark the buffer as stale + buffer->type = kDifUsbdevBufferTypeStale; + return kDifOk; + default: + return kDifBadArg; + } +} + +dif_result_t dif_usbdev_buffer_read(const dif_usbdev_t *usbdev, + dif_usbdev_buffer_pool_t *buffer_pool, + dif_usbdev_buffer_t *buffer, uint8_t *dst, + size_t dst_len, size_t *bytes_written) { + if (usbdev == NULL || buffer_pool == NULL || buffer == NULL || + buffer->type != kDifUsbdevBufferTypeRead || dst == NULL) { + return kDifBadArg; + } + + // bytes_to_copy is the minimum of remaining_bytes and dst_len + size_t bytes_to_copy = buffer->remaining_bytes; + if (bytes_to_copy > dst_len) { + bytes_to_copy = dst_len; + } + // Copy from buffer to dst + const uint32_t buffer_addr = get_buffer_addr(buffer->id, buffer->offset); + mmio_region_memcpy_from_mmio32(usbdev->base_addr, buffer_addr, dst, + bytes_to_copy); + // Update buffer state + buffer->offset += bytes_to_copy; + buffer->remaining_bytes -= bytes_to_copy; + + if (bytes_written != NULL) { + *bytes_written = bytes_to_copy; + } + + // Check if there are any remaining bytes + if (buffer->remaining_bytes > 0) { + return kDifOk; + } + + // Return the buffer to the free buffer pool + if (!buffer_pool_add(buffer_pool, buffer->id)) { + return kDifError; + } + + // Mark the buffer as stale + buffer->type = kDifUsbdevBufferTypeStale; + return kDifOk; +} + +dif_result_t dif_usbdev_buffer_write(const dif_usbdev_t *usbdev, + dif_usbdev_buffer_t *buffer, + const uint8_t *src, size_t src_len, + size_t *bytes_written) { + if (usbdev == NULL || buffer == NULL || + buffer->type != kDifUsbdevBufferTypeWrite || src == NULL) { + return kDifBadArg; + } + + // bytes_to_copy is the minimum of remaining_bytes and src_len. + size_t bytes_to_copy = buffer->remaining_bytes; + if (bytes_to_copy > src_len) { + bytes_to_copy = src_len; + } + + // Write bytes to the buffer + uint32_t buffer_addr = get_buffer_addr(buffer->id, buffer->offset); + mmio_region_memcpy_to_mmio32(usbdev->base_addr, buffer_addr, src, + bytes_to_copy); + + buffer->offset += bytes_to_copy; + buffer->remaining_bytes -= bytes_to_copy; + + if (bytes_written) { + *bytes_written = bytes_to_copy; + } + + if (buffer->remaining_bytes == 0 && bytes_to_copy < src_len) { + return kDifError; + } + + return kDifOk; +} + +dif_result_t dif_usbdev_send(const dif_usbdev_t *usbdev, uint8_t endpoint, + dif_usbdev_buffer_t *buffer) { + if (usbdev == NULL || !is_valid_endpoint(endpoint) || buffer == NULL || + buffer->type != kDifUsbdevBufferTypeWrite) { + return kDifBadArg; + } + + // Get the configin register offset of the endpoint. + const uint32_t config_in_reg_offset = + kEndpointHwInfos[endpoint].config_in_reg_offset; + + // Configure USBDEV_CONFIGINX register. + // Note: Using mask and offset values for the USBDEV_CONFIGIN0 register + // for all endpoints because all USBDEV_CONFIGINX registers have the same + // layout. + uint32_t config_in_val = 0; + config_in_val = bitfield_field32_write( + config_in_val, USBDEV_CONFIGIN_0_BUFFER_0_FIELD, buffer->id); + config_in_val = bitfield_field32_write( + config_in_val, USBDEV_CONFIGIN_0_SIZE_0_FIELD, buffer->offset); + mmio_region_write32(usbdev->base_addr, config_in_reg_offset, config_in_val); + + // Mark the packet as ready for transmission + config_in_val = + bitfield_bit32_write(config_in_val, USBDEV_CONFIGIN_0_RDY_0_BIT, true); + mmio_region_write32(usbdev->base_addr, config_in_reg_offset, config_in_val); + + // Mark the buffer as stale. It will be returned to the free buffer pool + // in dif_usbdev_get_tx_status once transmission is complete. + buffer->type = kDifUsbdevBufferTypeStale; + + return kDifOk; +} + +dif_result_t dif_usbdev_get_tx_sent(const dif_usbdev_t *usbdev, + uint16_t *sent) { + if (usbdev == NULL || sent == NULL) { + return kDifBadArg; + } + *sent = mmio_region_read32(usbdev->base_addr, USBDEV_IN_SENT_REG_OFFSET); + return kDifOk; +} + +dif_result_t dif_usbdev_clear_tx_status(const dif_usbdev_t *usbdev, + dif_usbdev_buffer_pool_t *buffer_pool, + uint8_t endpoint) { + if (usbdev == NULL || buffer_pool == NULL || !is_valid_endpoint(endpoint)) { + return kDifBadArg; + } + // Get the configin register offset and bit index of the endpoint. + uint32_t config_in_reg_offset = + kEndpointHwInfos[endpoint].config_in_reg_offset; + uint32_t config_in_reg_val = + mmio_region_read32(usbdev->base_addr, config_in_reg_offset); + uint8_t buffer = bitfield_field32_read(config_in_reg_val, + USBDEV_CONFIGIN_0_BUFFER_0_FIELD); + + mmio_region_write32(usbdev->base_addr, config_in_reg_offset, + 1u << USBDEV_CONFIGIN_0_PEND_0_BIT); + // Clear IN_SENT bit (rw1c). + mmio_region_write32(usbdev->base_addr, USBDEV_IN_SENT_REG_OFFSET, + 1u << endpoint); + // Return the buffer back to the free buffer pool. + if (!buffer_pool_add(buffer_pool, buffer)) { + return kDifError; + } + return kDifOk; +} + +dif_result_t dif_usbdev_get_tx_status(const dif_usbdev_t *usbdev, + uint8_t endpoint, + dif_usbdev_tx_status_t *status) { + if (usbdev == NULL || status == NULL || !is_valid_endpoint(endpoint)) { + return kDifBadArg; + } + + // Get the configin register offset and bit index of the endpoint. + uint32_t config_in_reg_offset = + kEndpointHwInfos[endpoint].config_in_reg_offset; + uint8_t endpoint_bit_index = kEndpointHwInfos[endpoint].bit_index; + + // Read the configin register. + uint32_t config_in_val = + mmio_region_read32(usbdev->base_addr, config_in_reg_offset); + + // Check the status of the packet. + if (bitfield_bit32_read(config_in_val, USBDEV_CONFIGIN_0_RDY_0_BIT)) { + // Packet is marked as ready to be sent and pending transmission. + *status = kDifUsbdevTxStatusPending; + } else if (bitfield_bit32_read(mmio_region_read32(usbdev->base_addr, + USBDEV_IN_SENT_REG_OFFSET), + endpoint_bit_index)) { + // Packet was sent successfully. + *status = kDifUsbdevTxStatusSent; + } else if (bitfield_bit32_read(config_in_val, USBDEV_CONFIGIN_0_PEND_0_BIT)) { + // Canceled due to an IN SETUP packet or link reset. + *status = kDifUsbdevTxStatusCancelled; + } else { + // No packet has been queued for this endpoint. + *status = kDifUsbdevTxStatusNoPacket; + } + + return kDifOk; +} + +dif_result_t dif_usbdev_address_set(const dif_usbdev_t *usbdev, uint8_t addr) { + if (usbdev == NULL) { + return kDifBadArg; + } + + uint32_t reg_val = + mmio_region_read32(usbdev->base_addr, USBDEV_USBCTRL_REG_OFFSET); + reg_val = bitfield_field32_write(reg_val, USBDEV_USBCTRL_DEVICE_ADDRESS_FIELD, + addr); + mmio_region_write32(usbdev->base_addr, USBDEV_USBCTRL_REG_OFFSET, reg_val); + + return kDifOk; +} + +dif_result_t dif_usbdev_address_get(const dif_usbdev_t *usbdev, uint8_t *addr) { + if (usbdev == NULL || addr == NULL) { + return kDifBadArg; + } + + uint32_t reg_val = + mmio_region_read32(usbdev->base_addr, USBDEV_USBCTRL_REG_OFFSET); + // Note: Size of address is 7 bits. + *addr = bitfield_field32_read(reg_val, USBDEV_USBCTRL_DEVICE_ADDRESS_FIELD); + + return kDifOk; +} + +dif_result_t dif_usbdev_clear_data_toggle(const dif_usbdev_t *usbdev, + uint8_t endpoint) { + if (usbdev == NULL) { + return kDifBadArg; + } + mmio_region_write32(usbdev->base_addr, USBDEV_DATA_TOGGLE_CLEAR_REG_OFFSET, + 1u << endpoint); + return kDifOk; +} + +dif_result_t dif_usbdev_status_get_frame(const dif_usbdev_t *usbdev, + uint16_t *frame_index) { + if (usbdev == NULL || frame_index == NULL) { + return kDifBadArg; + } + + uint32_t reg_val = + mmio_region_read32(usbdev->base_addr, USBDEV_USBSTAT_REG_OFFSET); + // Note: size of frame index is 11 bits. + *frame_index = bitfield_field32_read(reg_val, USBDEV_USBSTAT_FRAME_FIELD); + + return kDifOk; +} + +dif_result_t dif_usbdev_status_get_host_lost(const dif_usbdev_t *usbdev, + bool *host_lost) { + if (usbdev == NULL || host_lost == NULL) { + return kDifBadArg; + } + + *host_lost = + mmio_region_get_bit32(usbdev->base_addr, USBDEV_USBSTAT_REG_OFFSET, + USBDEV_USBSTAT_HOST_LOST_BIT); + + return kDifOk; +} + +dif_result_t dif_usbdev_status_get_link_state( + const dif_usbdev_t *usbdev, dif_usbdev_link_state_t *link_state) { + if (usbdev == NULL || link_state == NULL) { + return kDifBadArg; + } + + uint32_t val = + mmio_region_read32(usbdev->base_addr, USBDEV_USBSTAT_REG_OFFSET); + val = bitfield_field32_read(val, USBDEV_USBSTAT_LINK_STATE_FIELD); + + switch (val) { + case USBDEV_USBSTAT_LINK_STATE_VALUE_DISCONNECTED: + *link_state = kDifUsbdevLinkStateDisconnected; + break; + case USBDEV_USBSTAT_LINK_STATE_VALUE_POWERED: + *link_state = kDifUsbdevLinkStatePowered; + break; + case USBDEV_USBSTAT_LINK_STATE_VALUE_POWERED_SUSPENDED: + *link_state = kDifUsbdevLinkStatePoweredSuspended; + break; + case USBDEV_USBSTAT_LINK_STATE_VALUE_ACTIVE: + *link_state = kDifUsbdevLinkStateActive; + break; + case USBDEV_USBSTAT_LINK_STATE_VALUE_SUSPENDED: + *link_state = kDifUsbdevLinkStateSuspended; + break; + case USBDEV_USBSTAT_LINK_STATE_VALUE_ACTIVE_NOSOF: + *link_state = kDifUsbdevLinkStateActiveNoSof; + break; + case USBDEV_USBSTAT_LINK_STATE_VALUE_RESUMING: + *link_state = kDifUsbdevLinkStateResuming; + break; + default: + return kDifError; + } + + return kDifOk; +} + +dif_result_t dif_usbdev_status_get_sense(const dif_usbdev_t *usbdev, + bool *sense) { + if (usbdev == NULL || sense == NULL) { + return kDifBadArg; + } + + *sense = mmio_region_get_bit32(usbdev->base_addr, USBDEV_USBSTAT_REG_OFFSET, + USBDEV_USBSTAT_SENSE_BIT); + + return kDifOk; +} + +dif_result_t dif_usbdev_status_get_available_fifo_depth( + const dif_usbdev_t *usbdev, uint8_t *depth) { + if (usbdev == NULL || depth == NULL) { + return kDifBadArg; + } + + uint32_t reg_val = + mmio_region_read32(usbdev->base_addr, USBDEV_USBSTAT_REG_OFFSET); + // Note: Size of available FIFO depth is 3 bits. + *depth = bitfield_field32_read(reg_val, USBDEV_USBSTAT_AV_DEPTH_FIELD); + + return kDifOk; +} + +dif_result_t dif_usbdev_status_get_available_fifo_full( + const dif_usbdev_t *usbdev, bool *is_full) { + if (usbdev == NULL || is_full == NULL) { + return kDifBadArg; + } + + uint32_t reg_val = + mmio_region_read32(usbdev->base_addr, USBDEV_USBSTAT_REG_OFFSET); + *is_full = bitfield_bit32_read(reg_val, USBDEV_USBSTAT_AV_FULL_BIT); + + return kDifOk; +} + +dif_result_t dif_usbdev_status_get_rx_fifo_depth(const dif_usbdev_t *usbdev, + uint8_t *depth) { + if (usbdev == NULL || depth == NULL) { + return kDifBadArg; + } + + uint32_t reg_val = + mmio_region_read32(usbdev->base_addr, USBDEV_USBSTAT_REG_OFFSET); + // Note: Size of RX FIFO depth is 3 bits. + *depth = bitfield_field32_read(reg_val, USBDEV_USBSTAT_RX_DEPTH_FIELD); + + return kDifOk; +} + +dif_result_t dif_usbdev_status_get_rx_fifo_empty(const dif_usbdev_t *usbdev, + bool *is_empty) { + if (usbdev == NULL || is_empty == NULL) { + return kDifBadArg; + } + + uint32_t reg_val = + mmio_region_read32(usbdev->base_addr, USBDEV_USBSTAT_REG_OFFSET); + *is_empty = bitfield_bit32_read(reg_val, USBDEV_USBSTAT_RX_EMPTY_BIT); + + return kDifOk; +} + +dif_result_t dif_usbdev_set_osc_test_mode(const dif_usbdev_t *usbdev, + dif_toggle_t enable) { + if (usbdev == NULL || !dif_is_valid_toggle(enable)) { + return kDifBadArg; + } + bool set_tx_osc_mode = dif_toggle_to_bool(enable); + uint32_t reg_val = + mmio_region_read32(usbdev->base_addr, USBDEV_PHY_CONFIG_REG_OFFSET); + reg_val = bitfield_bit32_write( + reg_val, USBDEV_PHY_CONFIG_TX_OSC_TEST_MODE_BIT, set_tx_osc_mode); + mmio_region_write32(usbdev->base_addr, USBDEV_PHY_CONFIG_REG_OFFSET, reg_val); + return kDifOk; +} + +dif_result_t dif_usbdev_set_wake_enable(const dif_usbdev_t *usbdev, + dif_toggle_t enable) { + if (usbdev == NULL || !dif_is_valid_toggle(enable)) { + return kDifBadArg; + } + uint32_t reg_val; + if (dif_toggle_to_bool(enable)) { + reg_val = + bitfield_bit32_write(0, USBDEV_WAKE_CONTROL_SUSPEND_REQ_BIT, true); + } else { + reg_val = bitfield_bit32_write(0, USBDEV_WAKE_CONTROL_WAKE_ACK_BIT, true); + } + mmio_region_write32(usbdev->base_addr, USBDEV_WAKE_CONTROL_REG_OFFSET, + reg_val); + return kDifOk; +} + +dif_result_t dif_usbdev_get_wake_status(const dif_usbdev_t *usbdev, + dif_usbdev_wake_status_t *status) { + if (usbdev == NULL || status == NULL) { + return kDifBadArg; + } + uint32_t reg_val = + mmio_region_read32(usbdev->base_addr, USBDEV_WAKE_EVENTS_REG_OFFSET); + status->active = + bitfield_bit32_read(reg_val, USBDEV_WAKE_EVENTS_MODULE_ACTIVE_BIT); + status->disconnected = + bitfield_bit32_read(reg_val, USBDEV_WAKE_EVENTS_DISCONNECTED_BIT); + status->bus_reset = + bitfield_bit32_read(reg_val, USBDEV_WAKE_EVENTS_BUS_RESET_BIT); + return kDifOk; +} + +dif_result_t dif_usbdev_resume_link_to_active(const dif_usbdev_t *usbdev) { + if (usbdev == NULL) { + return kDifBadArg; + } + uint32_t reg_val = + mmio_region_read32(usbdev->base_addr, USBDEV_USBCTRL_REG_OFFSET); + reg_val = bitfield_bit32_write(reg_val, USBDEV_USBCTRL_RESUME_LINK_ACTIVE_BIT, + true); + mmio_region_write32(usbdev->base_addr, USBDEV_USBCTRL_REG_OFFSET, reg_val); + return kDifOk; +} + +dif_result_t dif_usbdev_get_phy_pins_status( + const dif_usbdev_t *usbdev, dif_usbdev_phy_pins_sense_t *status) { + if (usbdev == NULL || status == NULL) { + return kDifBadArg; + } + uint32_t reg_val = + mmio_region_read32(usbdev->base_addr, USBDEV_PHY_PINS_SENSE_REG_OFFSET); + status->rx_dp = + bitfield_bit32_read(reg_val, USBDEV_PHY_PINS_SENSE_RX_DP_I_BIT); + status->rx_dn = + bitfield_bit32_read(reg_val, USBDEV_PHY_PINS_SENSE_RX_DN_I_BIT); + status->rx_d = bitfield_bit32_read(reg_val, USBDEV_PHY_PINS_SENSE_RX_D_I_BIT); + status->tx_dp = + bitfield_bit32_read(reg_val, USBDEV_PHY_PINS_SENSE_TX_DP_O_BIT); + status->tx_dn = + bitfield_bit32_read(reg_val, USBDEV_PHY_PINS_SENSE_TX_DN_O_BIT); + status->tx_d = bitfield_bit32_read(reg_val, USBDEV_PHY_PINS_SENSE_TX_D_O_BIT); + status->tx_se0 = + bitfield_bit32_read(reg_val, USBDEV_PHY_PINS_SENSE_TX_SE0_O_BIT); + status->output_enable = + bitfield_bit32_read(reg_val, USBDEV_PHY_PINS_SENSE_TX_OE_O_BIT); + status->vbus_sense = + bitfield_bit32_read(reg_val, USBDEV_PHY_PINS_SENSE_PWR_SENSE_BIT); + return kDifOk; +} + +dif_result_t dif_usbdev_set_phy_pins_state( + const dif_usbdev_t *usbdev, dif_toggle_t override_enable, + dif_usbdev_phy_pins_drive_t overrides) { + if (usbdev == NULL || !dif_is_valid_toggle(override_enable)) { + return kDifBadArg; + } + bool drive_en = dif_toggle_to_bool(override_enable); + uint32_t reg_val = + bitfield_bit32_write(0, USBDEV_PHY_PINS_DRIVE_EN_BIT, drive_en); + if (drive_en) { + reg_val = bitfield_bit32_write(reg_val, USBDEV_PHY_PINS_DRIVE_DP_O_BIT, + overrides.dp); + reg_val = bitfield_bit32_write(reg_val, USBDEV_PHY_PINS_DRIVE_DN_O_BIT, + overrides.dn); + reg_val = bitfield_bit32_write(reg_val, USBDEV_PHY_PINS_DRIVE_D_O_BIT, + overrides.data); + reg_val = bitfield_bit32_write(reg_val, USBDEV_PHY_PINS_DRIVE_SE0_O_BIT, + overrides.se0); + reg_val = bitfield_bit32_write(reg_val, USBDEV_PHY_PINS_DRIVE_OE_O_BIT, + overrides.output_enable); + reg_val = + bitfield_bit32_write(reg_val, USBDEV_PHY_PINS_DRIVE_RX_ENABLE_O_BIT, + overrides.diff_receiver_enable); + reg_val = + bitfield_bit32_write(reg_val, USBDEV_PHY_PINS_DRIVE_DP_PULLUP_EN_O_BIT, + overrides.dp_pullup_en); + reg_val = + bitfield_bit32_write(reg_val, USBDEV_PHY_PINS_DRIVE_DN_PULLUP_EN_O_BIT, + overrides.dn_pullup_en); + } + mmio_region_write32(usbdev->base_addr, USBDEV_PHY_PINS_DRIVE_REG_OFFSET, + reg_val); + return kDifOk; +}
diff --git a/sw/device/lib/dif/dif_usbdev.h b/sw/device/lib/dif/dif_usbdev.h new file mode 100644 index 0000000..12c1a09 --- /dev/null +++ b/sw/device/lib/dif/dif_usbdev.h
@@ -0,0 +1,876 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#ifndef OPENTITAN_SW_DEVICE_LIB_DIF_DIF_USBDEV_H_ +#define OPENTITAN_SW_DEVICE_LIB_DIF_DIF_USBDEV_H_ + +/** + * @file + * @brief <a href="/hw/ip/usbdev/doc/">USB Device</a> Device Interface Functions + */ + +#include <stddef.h> +#include <stdint.h> + +#include "sw/device/lib/base/macros.h" +#include "sw/device/lib/base/mmio.h" +#include "sw/device/lib/dif/dif_base.h" + +#include "sw/device/lib/dif/autogen/dif_usbdev_autogen.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** + * Hardware constants. + */ +#define USBDEV_NUM_ENDPOINTS 12 +#define USBDEV_MAX_PACKET_SIZE 64 +// Internal constant that should not be used by clients. Defined here because +// it is used in the definition of `dif_usbdev_buffer_pool` below. +#define USBDEV_NUM_BUFFERS 32 + +// Constants used for the `dif_usbdev_endpoint_id` direction field. +#define USBDEV_ENDPOINT_DIR_IN 1 +#define USBDEV_ENDPOINT_DIR_OUT 0 + +typedef struct dif_usbdev_endpoint_id { + /** + * Endpoint number. + */ + unsigned int number : 4; + /** + * Reserved. Should be zero. + */ + unsigned int reserved : 3; + /** + * Endpoint direction. 1 = IN endpoint, 0 = OUT endpoint + */ + unsigned int direction : 1; +} dif_usbdev_endpoint_id_t; + +/** + * Free buffer pool. + * + * A USB device has a fixed number of buffers that are used for storing incoming + * and outgoing packets and the software is responsible for keeping track of + * free buffers. The pool is implemented as a stack for constant-time add and + * remove. `top` points to the last free buffer added to the pool. The pool is + * full when `top == USBDEV_NUM_BUFFERS - 1` and empty when `top == -1`. + */ +typedef struct dif_usbdev_buffer_pool { + uint8_t buffers[USBDEV_NUM_BUFFERS]; + int8_t top; +} dif_usbdev_buffer_pool_t; + +/** + * Buffer types. + */ +typedef enum dif_usbdev_buffer_type { + /** + * For reading payloads of incoming packets. + */ + kDifUsbdevBufferTypeRead, + /** + * For writing payloads of outgoing packets. + */ + kDifUsbdevBufferTypeWrite, + /** + * Clients must not use a buffer after it is handed over to hardware or + * returned to the free buffer pool. This type exists to protect against such + * cases. + */ + kDifUsbdevBufferTypeStale, +} dif_usbdev_buffer_type_t; + +/** + * A USB device buffer. + * + * This struct represents a USB device buffer that has been provided to a client + * in response to a buffer request. Clients should treat instances of this + * struct as opaque objects and should pass them to the appropriate functions of + * this library to read and write payloads of incoming and outgoing packets, + * respectively. + * + * See also: `dif_usbdev_recv`, `dif_usbdev_buffer_read`, + * `dif_usbdev_buffer_request`, `dif_usbdev_buffer_write`, + * `dif_usbdev_send`, `dif_usbdev_buffer_return`. + */ +typedef struct dif_usbdev_buffer { + /** + * Hardware buffer id. + */ + uint8_t id; + /** + * Byte offset for the next read or write operation. + */ + uint8_t offset; + /** + * For read buffers: remaining number of bytes to read. + * For write buffers: remaining number of bytes that can be written. + */ + uint8_t remaining_bytes; + /** + * Type of this buffer. + */ + dif_usbdev_buffer_type_t type; +} dif_usbdev_buffer_t; + +/** + * Configuration for initializing a USB device. + */ +typedef struct dif_usbdev_config { + /** + * Activate the single-ended D signal for detecting K and J symbols, for use + * with a differential receiver. + */ + dif_toggle_t have_differential_receiver; + /** + * Use the TX interface with D and SE0 signals instead of Dp/Dn, for use with + * certain transceivers. + */ + dif_toggle_t use_tx_d_se0; + /* + * Recognize a single SE0 bit as end of packet instead of requiring + * two bits. + */ + dif_toggle_t single_bit_eop; + /** + * Flip the D+/D- pins. + */ + dif_toggle_t pin_flip; + /** + * Reference signal generation for clock synchronization. + */ + dif_toggle_t clock_sync_signals; +} dif_usbdev_config_t; + +/** + * Configures a USB device with runtime information. + * + * This function should need to be called once for the lifetime of `handle`. + * + * @param usbdev A USB device. + * @param buffer_pool A USB device buffer pool. + * @param config Runtime configuration parameters for a USB device. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_configure(const dif_usbdev_t *usbdev, + dif_usbdev_buffer_pool_t *buffer_pool, + dif_usbdev_config_t config); + +/** + * Fill the available buffer FIFO of a USB device. + * + * The USB device has a small FIFO (AV FIFO) that stores free buffers for + * incoming packets. It is the responsibility of the software to ensure that the + * AV FIFO is never empty. If the host tries to send a packet when the AV FIFO + * is empty, the USB device will respond with a NAK. While this will typically + * cause the host to retry transmission for regular data packets, there are + * transactions in the USB protocol during which the USB device is not allowed + * to send a NAK. Thus, the software must make sure that the AV FIFO is never + * empty by calling this function periodically. + * + * @param usbdev A USB device. + * @param buffer_pool A USB device buffer pool. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_fill_available_fifo( + const dif_usbdev_t *usbdev, dif_usbdev_buffer_pool_t *buffer_pool); + +/** + * Enable or disable reception of SETUP packets for an endpoint. + * + * This controls whether the pair of IN and OUT endpoints with the specified + * endpoint number are control endpoints. + * + * @param usbdev A USB device. + * @param endpoint An endpoint number. + * @param new_state New SETUP packet reception state. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_endpoint_setup_enable(const dif_usbdev_t *usbdev, + uint8_t endpoint, + dif_toggle_t new_state); + +/** + * Enable or disable reception of OUT packets for an active endpoint. + * + * When disabling reception of OUT packets, what the endpoint will do depends + * on other factors. If the endpoint is currently configured as a control + * endpoint (receives SETUP packets) or it is configured as an isochronous + * endpoint, disabling reception of OUT packets will cause them to be ignored. + * + * If the endpoint is neither a control nor isochronous endpoint, then its + * behavior depends on whether it is configured to respond with STALL. If the + * STALL response is not active, then disabling reception will cause usbdev to + * NAK the packet. Otherwise, the STALL response takes priority, regardless of + * the setting here. + * + * @param usbdev A USB device. + * @param endpoint An OUT endpoint number. + * @param new_state New OUT packet reception state. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_endpoint_out_enable(const dif_usbdev_t *usbdev, + uint8_t endpoint, + dif_toggle_t new_state); + +/** + * Enable or disable clearing the out_enable bit after completion of an OUT + * transaction to an endpoint. + * + * If set_nak_out is enabled, an OUT endpoint will disable reception of OUT + * packets after each successful OUT transaction to that endpoint, requiring a + * call to `dif_usbdev_endpoint_out_enable()` to enable reception again. + * + * @param usbdev A USB device. + * @param endpoint An OUT endpoint number. + * @param new_state New set_nak_on_out state. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_endpoint_set_nak_out_enable(const dif_usbdev_t *usbdev, + uint8_t endpoint, + dif_toggle_t new_state); + +/** + * Enable or disable STALL for an endpoint. + * + * @param usbdev A USB device. + * @param endpoint An endpoint ID. + * @param new_state New STALL state. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_endpoint_stall_enable(const dif_usbdev_t *usbdev, + dif_usbdev_endpoint_id_t endpoint, + dif_toggle_t new_state); + +/** + * Get STALL state of an endpoint. + * + * @param usbdev A USB device. + * @param endpoint An endpoint ID. + * @param[out] state Current STALL state. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_endpoint_stall_get(const dif_usbdev_t *usbdev, + dif_usbdev_endpoint_id_t endpoint, + bool *state); + +/** + * Enable or disable isochronous mode for an endpoint. + * + * Isochronous endpoints transfer data periodically. Since isochronous transfers + * do not have a handshaking stage, isochronous endpoints cannot report errors + * or STALL conditions. + * + * @param usbdev A USB device. + * @param endpoint An endpoint. + * @param new_state New isochronous state. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_endpoint_iso_enable(const dif_usbdev_t *usbdev, + dif_usbdev_endpoint_id_t endpoint, + dif_toggle_t new_state); + +/** + * Enable or disable an endpoint. + * + * An enabled endpoint responds to packets from the host. A disabled endpoint + * ignores them. + * + * @param usbdev A USB device. + * @param endpoint An endpoint. + * @param new_state New endpoint state. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_endpoint_enable(const dif_usbdev_t *usbdev, + dif_usbdev_endpoint_id_t endpoint, + dif_toggle_t new_state); + +/** + * Enable the USB interface of a USB device. + * + * Calling this function causes the USB device to assert the full-speed pull-up + * signal to indicate its presence to the host. Ensure the default endpoint is + * set up before enabling the interface. + * + * @param usbdev A USB device. + * @param new_state New interface state. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_interface_enable(const dif_usbdev_t *usbdev, + dif_toggle_t new_state); + +/** + * Information about a received packet. + */ +typedef struct dif_usbdev_rx_packet_info { + /** + * Endpoint of the packet. + */ + uint8_t endpoint; + /** + * Payload length in bytes. + */ + uint8_t length; + /** + * Indicates if the packet is a SETUP packet. + */ + bool is_setup; +} dif_usbdev_rx_packet_info_t; + +/** + * Get the packet at the front of RX FIFO. + * + * The USB device has a small FIFO (RX FIFO) that stores received packets until + * the software has a chance to process them. It is the responsibility of the + * software to ensure that the RX FIFO is never full. If the host tries to send + * a packet when the RX FIFO is full, the USB device will respond with a NAK. + * While this will typically cause the host to retry transmission for regular + * data packets, there are transactions in the USB protocol during which the USB + * device is not allowed to send a NAK. Thus, the software must read received + * packets as soon as possible. + * + * Reading received packets involves two main steps: + * - Calling this function, i.e. `dif_usbdev_recv`, and + * - Calling `dif_usbdev_buffer_read` until the entire packet payload + * is read. + * + * In order to read an incoming packet, clients should first call this function + * to get information about the packet and the buffer that holds the packet + * payload. Then, clients should call `dif_usbdev_buffer_read` with this buffer + * one or more times (depending on the sizes of their internal buffers) until + * the entire packet payload is read. Once the entire payload is read, the + * buffer is returned to the free buffer pool. If the clients want to ignore the + * payload of a packet, e.g. for an unsupported or a zero-length packet, they + * can call `dif_usbdev_buffer_return` to immediately return the buffer to the + * free buffer pool. + * + * @param usbdev A USB device. + * @param[out] packet_info Packet information. + * @param[out] buffer Buffer that holds the packet payload. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_recv(const dif_usbdev_t *usbdev, + dif_usbdev_rx_packet_info_t *packet_info, + dif_usbdev_buffer_t *buffer); + +/** + * Read incoming packet payload. + * + * Clients should call this function with a buffer provided by `dif_usbdev_recv` + * to read the payload of an incoming packet. This function copies the smaller + * of `dst_len` and remaining number of bytes in the buffer to `dst`. The buffer + * that holds the packet payload is returned to the free buffer pool when the + * entire packet payload is read. + * + * See also: `dif_usbdev_recv`. + * + * @param usbdev A USB device. + * @param buffer_pool A USB device buffer pool. + * @param buffer A buffer provided by `dif_usbdev_recv`. + * @param[out] dst Destination buffer. + * @param dst_len Length of the destination buffer. + * @param[out] bytes_written Number of bytes written to destination buffer. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_buffer_read(const dif_usbdev_t *usbdev, + dif_usbdev_buffer_pool_t *buffer_pool, + dif_usbdev_buffer_t *buffer, uint8_t *dst, + size_t dst_len, size_t *bytes_written); + +/** + * Return a buffer to the free buffer pool. + * + * This function immediately returns the given buffer to the free buffer pool. + * Since `dif_usbdev_buffer_read` and `dif_usbdev_get_tx_status` return the + * buffers that they work on to the free buffer pool automatically, this + * function should only be called to discard the payload of a received + * packet or a packet that was being prepared for transmission before it is + * queued for transmission from an endpoint. + * + * See also: `dif_usbdev_recv`, `dif_usbdev_buffer_request`. + * + * @param usbdev A USB device. + * @param buffer_pool A USB device buffer pool. + * @param buffer A buffer provided by `dif_usbdev_recv` or + * `dif_usbdev_buffer_request`. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_buffer_return(const dif_usbdev_t *usbdev, + dif_usbdev_buffer_pool_t *buffer_pool, + dif_usbdev_buffer_t *buffer); + +/** + * Request a buffer for outgoing packet payload. + * + * Clients should call this function to request a buffer to write the payload of + * an outgoing packet. Sending a packet from a particular endpoint to the host + * involves four main steps: + * - Calling this function, i.e. `dif_usbdev_buffer_request`, + * - Calling `dif_usbdev_buffer_write`, + * - Calling `dif_usbdev_send`, and + * - Calling `dif_usbdev_get_tx_status`. + * + * In order to send a packet, clients should first call this function to obtain + * a buffer for the packet payload. Clients should then call + * `dif_usbdev_buffer_write` (one or more times depending on the sizes of their + * internal buffers) to write the packet payload to this buffer. After writing + * the packet payload, clients should call `dif_usbdev_send` to mark the packet + * as ready for transmission from a particular endpoint. Then, clients should + * call `dif_usbdev_get_tx_status` to check the status of the transmission. + * `dif_usbdev_get_tx_status` returns the buffer that holds the packet payload + * to the free buffer pool once the packet is either successfully transmitted or + * canceled due to an incoming SETUP packet or a link reset. If the packet + * should no longer be sent, clients can call `dif_usbdev_buffer_return` to + * return the buffer to the free buffer pool as long as `dif_usbdev_send` is not + * called yet. + * + * See also: `dif_usbdev_buffer_write`, `dif_usbdev_send`, + * `dif_usbdev_get_tx_status`, `dif_usbdev_buffer_return`. + * + * @param usbdev A USB device. + * @param buffer_pool A USB device buffer pool. + * @param[out] buffer A buffer for writing outgoing packet payload. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_buffer_request(const dif_usbdev_t *usbdev, + dif_usbdev_buffer_pool_t *buffer_pool, + dif_usbdev_buffer_t *buffer); + +/** + * Write outgoing packet payload. + * + * Clients should call this function with a buffer provided by + * `dif_usbdev_buffer_request` to write the payload of an outgoing packet. This + * function copies the smaller of `src_len` and remaining number of bytes in the + * buffer to the buffer. Clients should then call `dif_usbdev_send` to queue the + * packet for transmission from a particular endpoint. + * + * See also: `dif_usbdev_buffer_request`, `dif_usbdev_send`, + * `dif_usbdev_get_tx_status`, `dif_usbdev_buffer_return`. + * + * @param usbdev A USB device. + * @param buffer A buffer provided by `dif_usbdev_buffer_request`. + * @param src Source buffer. + * @param src_len Length of the source buffer. + * @param[out] bytes_written Number of bytes written to the USB device buffer. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_buffer_write(const dif_usbdev_t *usbdev, + dif_usbdev_buffer_t *buffer, + const uint8_t *src, size_t src_len, + size_t *bytes_written); + +/** + * Mark a packet ready for transmission from an endpoint. + * + * The USB device has 12 endpoints, each of which can be used to send packets to + * the host. Since a packet is not actually transmitted to the host until the + * host sends an IN token, clients must write the packet payload to a device + * buffer and mark it as ready for transmission from a particular endpoint. A + * packet queued for transmission from a particular endpoint is transmitted once + * the host sends an IN token for that endpoint. + * + * After a packet is queued for transmission, clients should check its status by + * calling `dif_usbdev_get_tx_status`. While the USB device handles transmission + * errors automatically by retrying transmission, transmission of a packet may + * be canceled if the endpoint receives a SETUP packet or the link is reset + * before the queued packet is transmitted. In these cases, clients should + * handle the SETUP packet or the link reset first and then optionally send the + * same packet again. Clients must also make sure that the given endpoint does + * not already have a packet pending for transmission before calling this + * function. + * + * See also: `dif_usbdev_buffer_request`, `dif_usbdev_buffer_write`, + * `dif_usbdev_get_tx_status`, `dif_usbdev_buffer_return`. + * + * @param usbdev A USB device. + * @param endpoint An OUT endpoint number. + * @param buffer A buffer provided by `dif_usbdev_buffer_request`. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_send(const dif_usbdev_t *usbdev, uint8_t endpoint, + dif_usbdev_buffer_t *buffer); + +/** + * Get which IN endpoints have sent packets. + * + * This function provides which endpoints have buffers that have successfully + * completed transmission to the host. It may be used to guide calls to + * `dif_usbdev_clear_tx_status` to return the used buffer to the pool and clear + * the state for the next transaction. + * + * @param usbdev A USB device. + * @param[out] sent A bitmap of which endpoints have sent packets. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_get_tx_sent(const dif_usbdev_t *usbdev, uint16_t *sent); + +/** + * Clear the TX state of the provided endpoint and restore its associated buffer + * to the pool. + * + * Note that this function should only be called when an endpoint has been + * provided a buffer. Without it, the buffer pool will become corrupted, as this + * function does not check the status. + * + * In addition, if the endpoint has not yet completed or canceled the + * transaction, the user must not call this function while the device is in an + * active state. Otherwise, the user risks corrupting an ongoing transaction. + * + * @param usbdev A USB device. + * @param buffer_pool A USB device buffer pool. + * @param endpoint An IN endpoint number. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_clear_tx_status(const dif_usbdev_t *usbdev, + dif_usbdev_buffer_pool_t *buffer_pool, + uint8_t endpoint); + +/** + * Status of an outgoing packet. + */ +typedef enum dif_usbdev_tx_status { + /** + * There is no packet for the given OUT endpoint. + */ + kDifUsbdevTxStatusNoPacket, + /** + * Packet is pending transmission. + */ + kDifUsbdevTxStatusPending, + /** + * Packet was sent successfully. + */ + kDifUsbdevTxStatusSent, + /** + * Transmission was canceled due to an incoming SETUP packet. + */ + kDifUsbdevTxStatusCancelled, +} dif_usbdev_tx_status_t; + +/** + * Get the status of a packet that has been queued to be sent from an endpoint. + * + * While the USB device handles transmission errors automatically by retrying + * transmission, transmission of a packet may be canceled if the endpoint + * receives a SETUP packet or the link is reset before the queued packet is + * transmitted. In these cases, clients should handle the SETUP packet or the + * link reset first and then optionally send the same packet again. + * + * This function does not modify any device state. `dif_usbdev_clear_tx_status` + * can be used to clear the status and return the buffer to the pool. + * + * @param usbdev A USB device. + * @param endpoint An IN endpoint number. + * @param[out] status Status of the packet. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_get_tx_status(const dif_usbdev_t *usbdev, + uint8_t endpoint, + dif_usbdev_tx_status_t *status); + +/** + * Set the address of a USB device. + * + * @param usbdev A USB device. + * @param addr New address. Only the last 7 bits are significant. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_address_set(const dif_usbdev_t *usbdev, uint8_t addr); + +/** + * Get the address of a USB device. + * + * @param usbdev A USB device. + * @param[out] addr Current address. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_address_get(const dif_usbdev_t *usbdev, uint8_t *addr); + +/** + * Clear the data toggle bits for the selected endpoint. + * + * @param usbdev A USB device. + * @param endpoint An endpoint number. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_clear_data_toggle(const dif_usbdev_t *usbdev, + uint8_t endpoint); + +/** + * Get USB frame index. + * + * @param usbdev A USB device. + * @param[out] frame_index USB frame index. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_status_get_frame(const dif_usbdev_t *usbdev, + uint16_t *frame_index); + +/** + * Check if the host is lost. + * + * The host is lost if the link is still active but a start of frame packet has + * not been received in the last 4.096ms. + * + * @param usbdev A USB device. + * @param[out] host_lost Status of the host. `true` if the host is lost, `false` + * otherwise. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_status_get_host_lost(const dif_usbdev_t *usbdev, + bool *host_lost); + +/** + * USB link state. + */ +typedef enum dif_usbdev_link_state { + kDifUsbdevLinkStateDisconnected, + kDifUsbdevLinkStatePowered, + kDifUsbdevLinkStatePoweredSuspended, + kDifUsbdevLinkStateActive, + kDifUsbdevLinkStateSuspended, + kDifUsbdevLinkStateActiveNoSof, + kDifUsbdevLinkStateResuming, +} dif_usbdev_link_state_t; + +/** + * Get USB link state. + * + * @param usbdev A USB device. + * @param[out] link_state USB link state. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_status_get_link_state( + const dif_usbdev_t *usbdev, dif_usbdev_link_state_t *link_state); + +/** + * Get the state of the sense pin. + * + * @param usbdev A USB device. + * @param[out] sense State of the sense pin. `true` if the host is providing + * VBUS, `false` otherwise. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_status_get_sense(const dif_usbdev_t *usbdev, + bool *sense); + +/** + * Get the depth of the AV FIFO. + * + * See also: `dif_usbdev_fill_available_fifo`. + * + * @param usbdev A USB device. + * @param[out] depth Depth of the AV FIFO. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_status_get_available_fifo_depth( + const dif_usbdev_t *usbdev, uint8_t *depth); +/** + * Check if AV FIFO is full. + * + * See also: `dif_usbdev_fill_available_fifo`. + * + * @param usbdev A USB device. + * @param[out] is_full State of the AV FIFO. `true` if full, false otherwise. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_status_get_available_fifo_full( + const dif_usbdev_t *usbdev, bool *is_full); +/** + * Get the depth of the RX FIFO. + * + * See also: `dif_usbdev_recv`. + * + * @param usbdev A USB device. + * @param[out] depth Depth of the RX FIFO. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_status_get_rx_fifo_depth(const dif_usbdev_t *usbdev, + uint8_t *depth); + +/** + * Check if the RX FIFO is empty. + * + * See also: `dif_usbdev_recv`. + * + * @param usbdev A USB device. + * @param[out] is_empty State of the RX FIFO. `true` if empty, `false` + * otherwise. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_status_get_rx_fifo_empty(const dif_usbdev_t *usbdev, + bool *is_empty); + +/** + * Control whether oscillator test mode is enabled. + * + * In oscillator test mode, usbdev transmits a continuous 0101 pattern for + * evaluating the reference clock's quality. + * + * @param usbdev A USB device. + * @param enable Whether the test mode should be enabled. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_set_osc_test_mode(const dif_usbdev_t *usbdev, + dif_toggle_t enable); + +/** + * Control whether the AON wake module is active. + * + * @param usbdev A USB device. + * @param enable Whether the AON wake module is enabled. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_set_wake_enable(const dif_usbdev_t *usbdev, + dif_toggle_t enable); + +typedef struct dif_usbdev_wake_status { + /** Whether the AON wake module is active. */ + bool active; + /** Whether the USB disconnected while the AON wake module was active. */ + bool disconnected; + /** Whether the USB was reset while the AON wake module was active. */ + bool bus_reset; +} dif_usbdev_wake_status_t; + +/** + * Get the status of the AON wake module. + * + * Note that the conditions triggering exit from suspended state must be read + * before disabling the AON wake module. Once the AON wake module is + * deactivated, that status information is lost. + * + * Also note that the ordinary resume condition does not report to the usbdev + * module. Instead, it should be obtained from the module monitoring wakeup + * sources. + * + * @param usbdev A USB device. + * @param[out] status The status of the module. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_get_wake_status(const dif_usbdev_t *usbdev, + dif_usbdev_wake_status_t *status); + +/** + * Force the link state machine to resume to an active state. + * + * This is used when waking from a low-power suspended state to resume to an + * active state. It moves the usbdev out of the Powered state (from the USB + * device state machine in the spec) without receiving a bus reset. Without help + * from software, the usbdev module cannot determine on its own when a bus reset + * is required. + * + * @param usbdev A USB device. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_resume_link_to_active(const dif_usbdev_t *usbdev); + +typedef struct dif_usbdev_phy_pins_sense { + /** USB D+ input. */ + bool rx_dp : 1; + /** USB D- input. */ + bool rx_dn : 1; + /** USB data input from an external differential receiver, if available. */ + bool rx_d : 1; + /** USB transmit D+ output. */ + bool tx_dp : 1; + /** USB transmit D- output. */ + bool tx_dn : 1; + /** USB transmit data value output. */ + bool tx_d : 1; + /** USB single-ended zero output. */ + bool tx_se0 : 1; + /** USB output enable for D+ / D-. */ + bool output_enable : 1; + /** USB VBUS sense pin. */ + bool vbus_sense : 1; +} dif_usbdev_phy_pins_sense_t; + +/** + * Get the current state of the USB PHY pins. + * + * @param usbdev A USB device. + * @param[out] status The current state of the pins. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_get_phy_pins_status( + const dif_usbdev_t *usbdev, dif_usbdev_phy_pins_sense_t *status); + +typedef struct dif_usbdev_phy_pins_drive { + /** USB D+ output, for use with dn. */ + bool dp : 1; + /** USB D- output. for use with dp. */ + bool dn : 1; + /** USB data output, encoding K and J when se0 is 0. */ + bool data : 1; + /** USB single-ended zero output. */ + bool se0 : 1; + /** USB output enable for D+ / D-. */ + bool output_enable : 1; + /** Enable control pin for the differential receiver. */ + bool diff_receiver_enable : 1; + /** Controls whether to pull up the D+ pin. */ + bool dp_pullup_en : 1; + /** Controls whether to pull up the D- pin. */ + bool dn_pullup_en : 1; +} dif_usbdev_phy_pins_drive_t; + +/** + * Control whether to override the USB PHY and drive pins as GPIOs. + * + * @param usbdev A USB device. + * @param override_enable Enable / disable the GPIO-like overrides. + * @param overrides The values to set the pins to. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_set_phy_pins_state( + const dif_usbdev_t *usbdev, dif_toggle_t override_enable, + dif_usbdev_phy_pins_drive_t overrides); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#endif // OPENTITAN_SW_DEVICE_LIB_DIF_DIF_USBDEV_H_
diff --git a/sw/device/lib/dif/dif_usbdev.md b/sw/device/lib/dif/dif_usbdev.md new file mode 100644 index 0000000..0e5f851 --- /dev/null +++ b/sw/device/lib/dif/dif_usbdev.md
@@ -0,0 +1,48 @@ +# USB Device DIF Checklist + +This checklist is for [Development Stage](../../../../doc/project_governance/development_stages.md) transitions for the [USB Device DIF](../../../../hw/ip/usbdev/README.md). +All checklist items refer to the content in the [Checklist](../../../../doc/project_governance/checklist/README.md). + +<h2>DIF Checklist</h2> + +<h3>S1</h3> + +Type | Item | Resolution | Note/Collaterals +---------------|------------------------|-------------|------------------ +Implementation | [DIF_EXISTS][] | Done | +Implementation | [DIF_USED_IN_TREE][] | Done | +Tests | [DIF_TEST_ON_DEVICE][] | Done | + +[DIF_EXISTS]: ../../../../doc/project_governance/checklist/README.md#dif_exists +[DIF_USED_IN_TREE]: ../../../../doc/project_governance/checklist/README.md#dif_used_in_tree +[DIF_TEST_ON_DEVICE]: ../../../../doc/project_governance/checklist/README.md#dif_test_on_device + +<h3>S2</h3> + +Type | Item | Resolution | Note/Collaterals +---------------|-----------------------------|-------------|------------------ +Coordination | [DIF_HW_FEATURE_COMPLETE][] | Done | [HW Dashboard](../../../../hw/README.md) +Implementation | [DIF_FEATURES][] | Done | + +[DIF_HW_FEATURE_COMPLETE]: ../../../../doc/project_governance/checklist/README.md#dif_hw_feature_complete +[DIF_FEATURES]: ../../../../doc/project_governance/checklist/README.md#dif_features + +<h3>S3</h3> + +Type | Item | Resolution | Note/Collaterals +---------------|----------------------------------|-------------|------------------ +Coordination | [DIF_HW_DESIGN_COMPLETE][] | Not Started | +Coordination | [DIF_HW_VERIFICATION_COMPLETE][] | Not Started | +Documentation | [DIF_DOC_HW][] | Not Started | +Code Quality | [DIF_CODE_STYLE][] | Not Started | +Tests | [DIF_TEST_UNIT][] | Done | +Review | [DIF_TODO_COMPLETE][] | Not Started | +Review | Reviewer(s) | Not Started | +Review | Signoff date | Not Started | + +[DIF_HW_DESIGN_COMPLETE]: ../../../../doc/project_governance/checklist/README.md#dif_hw_design_complete +[DIF_HW_VERIFICATION_COMPLETE]: ../../../../doc/project_governance/checklist/README.md#dif_hw_verification_complete +[DIF_DOC_HW]: ../../../../doc/project_governance/checklist/README.md#dif_doc_hw +[DIF_CODE_STYLE]: ../../../../doc/project_governance/checklist/README.md#dif_code_style +[DIF_TEST_UNIT]: ../../../../doc/project_governance/checklist/README.md#dif_test_unit +[DIF_TODO_COMPLETE]: ../../../../doc/project_governance/checklist/README.md#dif_todo_complete
diff --git a/sw/device/lib/dif/dif_usbdev_unittest.cc b/sw/device/lib/dif/dif_usbdev_unittest.cc new file mode 100644 index 0000000..404706a --- /dev/null +++ b/sw/device/lib/dif/dif_usbdev_unittest.cc
@@ -0,0 +1,1045 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#include "sw/device/lib/dif/dif_usbdev.h" + +#include "gtest/gtest.h" +#include "sw/device/lib/base/mmio.h" +#include "sw/device/lib/base/mock_mmio.h" +#include "sw/device/lib/base/multibits.h" +#include "sw/device/lib/dif/dif_base.h" +#include "sw/device/lib/dif/dif_test_base.h" + +#include "usbdev_regs.h" // Generated. + +namespace dif_usbdev_unittest { +namespace { +using mock_mmio::MmioTest; +using mock_mmio::MockDevice; +using testing::Test; + +class UsbdevTest : public Test, public MmioTest { + protected: + dif_usbdev_t usbdev_ = { + .base_addr = dev().region(), + }; +}; + +TEST_F(UsbdevTest, NullArgsTest) { + dif_usbdev_config_t config; + dif_usbdev_buffer_pool_t buffer_pool; + bool bool_arg; + dif_usbdev_rx_packet_info_t packet_info; + dif_usbdev_buffer_t buffer; + uint8_t uint8_arg; + size_t size_arg; + dif_usbdev_endpoint_id_t endpoint_id; + dif_usbdev_tx_status_t tx_status; + uint16_t uint16_arg; + dif_usbdev_link_state_t link_state; + dif_usbdev_wake_status_t wake_status; + dif_usbdev_phy_pins_sense_t phy_pins_status; + dif_usbdev_phy_pins_drive_t phy_pins_drive; + + EXPECT_DIF_BADARG(dif_usbdev_configure(nullptr, &buffer_pool, config)); + EXPECT_DIF_BADARG(dif_usbdev_configure(&usbdev_, nullptr, config)); + EXPECT_DIF_BADARG(dif_usbdev_fill_available_fifo(nullptr, &buffer_pool)); + EXPECT_DIF_BADARG(dif_usbdev_fill_available_fifo(&usbdev_, nullptr)); + EXPECT_DIF_BADARG(dif_usbdev_endpoint_setup_enable(nullptr, /*endpoint=*/0, + kDifToggleEnabled)); + EXPECT_DIF_BADARG(dif_usbdev_endpoint_out_enable(nullptr, /*endpoint=*/0, + kDifToggleEnabled)); + EXPECT_DIF_BADARG(dif_usbdev_endpoint_set_nak_out_enable( + nullptr, /*endpoint=*/0, kDifToggleEnabled)); + EXPECT_DIF_BADARG(dif_usbdev_endpoint_stall_enable(nullptr, endpoint_id, + kDifToggleEnabled)); + EXPECT_DIF_BADARG( + dif_usbdev_endpoint_stall_get(nullptr, endpoint_id, &bool_arg)); + EXPECT_DIF_BADARG( + dif_usbdev_endpoint_stall_get(&usbdev_, endpoint_id, nullptr)); + EXPECT_DIF_BADARG( + dif_usbdev_endpoint_iso_enable(nullptr, endpoint_id, kDifToggleEnabled)); + EXPECT_DIF_BADARG( + dif_usbdev_endpoint_enable(nullptr, endpoint_id, kDifToggleEnabled)); + EXPECT_DIF_BADARG(dif_usbdev_interface_enable(nullptr, kDifToggleEnabled)); + EXPECT_DIF_BADARG(dif_usbdev_recv(nullptr, &packet_info, &buffer)); + EXPECT_DIF_BADARG(dif_usbdev_recv(&usbdev_, nullptr, &buffer)); + EXPECT_DIF_BADARG(dif_usbdev_recv(&usbdev_, &packet_info, nullptr)); + EXPECT_DIF_BADARG(dif_usbdev_buffer_read( + nullptr, &buffer_pool, &buffer, &uint8_arg, /*dst_len=*/1, &size_arg)); + EXPECT_DIF_BADARG(dif_usbdev_buffer_read( + &usbdev_, nullptr, &buffer, &uint8_arg, /*dst_len=*/1, &size_arg)); + EXPECT_DIF_BADARG(dif_usbdev_buffer_read( + &usbdev_, &buffer_pool, nullptr, &uint8_arg, /*dst_len=*/1, &size_arg)); + EXPECT_DIF_BADARG(dif_usbdev_buffer_read(&usbdev_, &buffer_pool, &buffer, + nullptr, /*dst_len=*/1, &size_arg)); + EXPECT_DIF_BADARG(dif_usbdev_buffer_read(&usbdev_, &buffer_pool, &buffer, + &uint8_arg, /*dst_len=*/1, nullptr)); + EXPECT_DIF_BADARG(dif_usbdev_buffer_return(nullptr, &buffer_pool, &buffer)); + EXPECT_DIF_BADARG(dif_usbdev_buffer_return(&usbdev_, nullptr, &buffer)); + EXPECT_DIF_BADARG(dif_usbdev_buffer_return(&usbdev_, &buffer_pool, nullptr)); + EXPECT_DIF_BADARG(dif_usbdev_buffer_request(nullptr, &buffer_pool, &buffer)); + EXPECT_DIF_BADARG(dif_usbdev_buffer_request(&usbdev_, nullptr, &buffer)); + EXPECT_DIF_BADARG(dif_usbdev_buffer_request(&usbdev_, &buffer_pool, nullptr)); + EXPECT_DIF_BADARG(dif_usbdev_buffer_write(nullptr, &buffer, &uint8_arg, + /*src_len=*/1, &size_arg)); + EXPECT_DIF_BADARG(dif_usbdev_buffer_write(&usbdev_, nullptr, &uint8_arg, + /*src_len=*/1, &size_arg)); + EXPECT_DIF_BADARG(dif_usbdev_buffer_write(&usbdev_, &buffer, nullptr, + /*src_len=*/1, &size_arg)); + EXPECT_DIF_BADARG(dif_usbdev_buffer_write(&usbdev_, &buffer, &uint8_arg, + /*src_len=*/1, nullptr)); + EXPECT_DIF_BADARG(dif_usbdev_send(nullptr, /*endpoint=*/0, &buffer)); + EXPECT_DIF_BADARG(dif_usbdev_send(&usbdev_, /*endpoint=*/0, nullptr)); + EXPECT_DIF_BADARG(dif_usbdev_get_tx_sent(nullptr, &uint16_arg)); + EXPECT_DIF_BADARG(dif_usbdev_get_tx_sent(&usbdev_, nullptr)); + EXPECT_DIF_BADARG( + dif_usbdev_clear_tx_status(nullptr, &buffer_pool, /*endpoint=*/0)); + EXPECT_DIF_BADARG( + dif_usbdev_clear_tx_status(&usbdev_, nullptr, /*endpoint=*/0)); + EXPECT_DIF_BADARG( + dif_usbdev_get_tx_status(nullptr, /*endpoint=*/0, &tx_status)); + EXPECT_DIF_BADARG( + dif_usbdev_get_tx_status(&usbdev_, /*endpoint=*/0, nullptr)); + EXPECT_DIF_BADARG(dif_usbdev_address_set(nullptr, /*addr=*/1)); + EXPECT_DIF_BADARG(dif_usbdev_address_get(nullptr, &uint8_arg)); + EXPECT_DIF_BADARG(dif_usbdev_address_get(&usbdev_, nullptr)); + EXPECT_DIF_BADARG(dif_usbdev_clear_data_toggle(nullptr, /*endpoint=*/1)); + EXPECT_DIF_BADARG(dif_usbdev_status_get_frame(nullptr, &uint16_arg)); + EXPECT_DIF_BADARG(dif_usbdev_status_get_frame(&usbdev_, nullptr)); + EXPECT_DIF_BADARG(dif_usbdev_status_get_host_lost(nullptr, &bool_arg)); + EXPECT_DIF_BADARG(dif_usbdev_status_get_host_lost(&usbdev_, nullptr)); + EXPECT_DIF_BADARG(dif_usbdev_status_get_link_state(nullptr, &link_state)); + EXPECT_DIF_BADARG(dif_usbdev_status_get_link_state(&usbdev_, nullptr)); + EXPECT_DIF_BADARG(dif_usbdev_status_get_sense(nullptr, &bool_arg)); + EXPECT_DIF_BADARG(dif_usbdev_status_get_sense(&usbdev_, nullptr)); + EXPECT_DIF_BADARG( + dif_usbdev_status_get_available_fifo_depth(nullptr, &uint8_arg)); + EXPECT_DIF_BADARG( + dif_usbdev_status_get_available_fifo_depth(&usbdev_, nullptr)); + EXPECT_DIF_BADARG( + dif_usbdev_status_get_available_fifo_full(nullptr, &bool_arg)); + EXPECT_DIF_BADARG( + dif_usbdev_status_get_available_fifo_full(&usbdev_, nullptr)); + EXPECT_DIF_BADARG(dif_usbdev_status_get_rx_fifo_depth(nullptr, &uint8_arg)); + EXPECT_DIF_BADARG(dif_usbdev_status_get_rx_fifo_depth(&usbdev_, nullptr)); + EXPECT_DIF_BADARG(dif_usbdev_status_get_rx_fifo_empty(nullptr, &bool_arg)); + EXPECT_DIF_BADARG(dif_usbdev_status_get_rx_fifo_empty(&usbdev_, nullptr)); + EXPECT_DIF_BADARG(dif_usbdev_set_osc_test_mode(nullptr, kDifToggleEnabled)); + EXPECT_DIF_BADARG(dif_usbdev_set_wake_enable(nullptr, kDifToggleEnabled)); + EXPECT_DIF_BADARG(dif_usbdev_get_wake_status(nullptr, &wake_status)); + EXPECT_DIF_BADARG(dif_usbdev_get_wake_status(&usbdev_, nullptr)); + EXPECT_DIF_BADARG(dif_usbdev_resume_link_to_active(nullptr)); + EXPECT_DIF_BADARG(dif_usbdev_get_phy_pins_status(nullptr, &phy_pins_status)); + EXPECT_DIF_BADARG(dif_usbdev_get_phy_pins_status(&usbdev_, nullptr)); + EXPECT_DIF_BADARG(dif_usbdev_set_phy_pins_state(nullptr, kDifToggleEnabled, + phy_pins_drive)); +} + +TEST_F(UsbdevTest, PhyConfig) { + dif_usbdev_buffer_pool_t buffer_pool; + dif_usbdev_config_t phy_config = { + .have_differential_receiver = kDifToggleEnabled, + .use_tx_d_se0 = kDifToggleDisabled, + .single_bit_eop = kDifToggleDisabled, + .pin_flip = kDifToggleEnabled, + .clock_sync_signals = kDifToggleEnabled, + }; + EXPECT_WRITE32(USBDEV_PHY_CONFIG_REG_OFFSET, + { + {USBDEV_PHY_CONFIG_USE_DIFF_RCVR_BIT, 1}, + {USBDEV_PHY_CONFIG_TX_USE_D_SE0_BIT, 0}, + {USBDEV_PHY_CONFIG_EOP_SINGLE_BIT_BIT, 0}, + {USBDEV_PHY_CONFIG_PINFLIP_BIT, 1}, + {USBDEV_PHY_CONFIG_USB_REF_DISABLE_BIT, 0}, + }); + EXPECT_DIF_OK(dif_usbdev_configure(&usbdev_, &buffer_pool, phy_config)); + + dif_usbdev_phy_pins_sense_t phy_pins_status; + EXPECT_READ32(USBDEV_PHY_PINS_SENSE_REG_OFFSET, + { + {USBDEV_PHY_PINS_SENSE_RX_DP_I_BIT, 1}, + {USBDEV_PHY_PINS_SENSE_RX_DN_I_BIT, 0}, + {USBDEV_PHY_PINS_SENSE_RX_D_I_BIT, 1}, + {USBDEV_PHY_PINS_SENSE_TX_DP_O_BIT, 0}, + {USBDEV_PHY_PINS_SENSE_TX_DN_O_BIT, 1}, + {USBDEV_PHY_PINS_SENSE_TX_D_O_BIT, 0}, + {USBDEV_PHY_PINS_SENSE_TX_SE0_O_BIT, 0}, + {USBDEV_PHY_PINS_SENSE_TX_OE_O_BIT, 1}, + {USBDEV_PHY_PINS_SENSE_PWR_SENSE_BIT, 1}, + }); + EXPECT_DIF_OK(dif_usbdev_get_phy_pins_status(&usbdev_, &phy_pins_status)); + EXPECT_TRUE(phy_pins_status.rx_dp); + EXPECT_FALSE(phy_pins_status.rx_dn); + EXPECT_TRUE(phy_pins_status.rx_d); + EXPECT_FALSE(phy_pins_status.tx_dp); + EXPECT_TRUE(phy_pins_status.tx_dn); + EXPECT_FALSE(phy_pins_status.tx_d); + EXPECT_FALSE(phy_pins_status.tx_se0); + EXPECT_TRUE(phy_pins_status.output_enable); + EXPECT_TRUE(phy_pins_status.vbus_sense); + + dif_usbdev_phy_pins_drive_t overrides = { + .dp = 0, + .dn = 1, + .data = 0, + .se0 = 1, + .output_enable = 1, + .diff_receiver_enable = 0, + .dp_pullup_en = 1, + .dn_pullup_en = 0, + }; + EXPECT_WRITE32( + USBDEV_PHY_PINS_DRIVE_REG_OFFSET, + { + {USBDEV_PHY_PINS_DRIVE_DP_O_BIT, overrides.dp}, + {USBDEV_PHY_PINS_DRIVE_DN_O_BIT, overrides.dn}, + {USBDEV_PHY_PINS_DRIVE_D_O_BIT, overrides.data}, + {USBDEV_PHY_PINS_DRIVE_SE0_O_BIT, overrides.se0}, + {USBDEV_PHY_PINS_DRIVE_OE_O_BIT, overrides.output_enable}, + {USBDEV_PHY_PINS_DRIVE_RX_ENABLE_O_BIT, + overrides.diff_receiver_enable}, + {USBDEV_PHY_PINS_DRIVE_DP_PULLUP_EN_O_BIT, overrides.dp_pullup_en}, + {USBDEV_PHY_PINS_DRIVE_DN_PULLUP_EN_O_BIT, overrides.dn_pullup_en}, + {USBDEV_PHY_PINS_DRIVE_EN_BIT, 1}, + }); + EXPECT_DIF_OK( + dif_usbdev_set_phy_pins_state(&usbdev_, kDifToggleEnabled, overrides)); + + EXPECT_WRITE32(USBDEV_PHY_PINS_DRIVE_REG_OFFSET, 0); + EXPECT_DIF_OK( + dif_usbdev_set_phy_pins_state(&usbdev_, kDifToggleDisabled, overrides)); + + EXPECT_READ32(USBDEV_PHY_CONFIG_REG_OFFSET, + { + {USBDEV_PHY_CONFIG_USE_DIFF_RCVR_BIT, 1}, + {USBDEV_PHY_CONFIG_TX_USE_D_SE0_BIT, 0}, + {USBDEV_PHY_CONFIG_EOP_SINGLE_BIT_BIT, 0}, + {USBDEV_PHY_CONFIG_PINFLIP_BIT, 1}, + {USBDEV_PHY_CONFIG_USB_REF_DISABLE_BIT, 0}, + {USBDEV_PHY_CONFIG_TX_OSC_TEST_MODE_BIT, 0}, + }); + EXPECT_WRITE32(USBDEV_PHY_CONFIG_REG_OFFSET, + { + {USBDEV_PHY_CONFIG_USE_DIFF_RCVR_BIT, 1}, + {USBDEV_PHY_CONFIG_TX_USE_D_SE0_BIT, 0}, + {USBDEV_PHY_CONFIG_EOP_SINGLE_BIT_BIT, 0}, + {USBDEV_PHY_CONFIG_PINFLIP_BIT, 1}, + {USBDEV_PHY_CONFIG_USB_REF_DISABLE_BIT, 0}, + {USBDEV_PHY_CONFIG_TX_OSC_TEST_MODE_BIT, 1}, + }); + EXPECT_DIF_OK(dif_usbdev_set_osc_test_mode(&usbdev_, kDifToggleEnabled)); + + EXPECT_READ32(USBDEV_PHY_CONFIG_REG_OFFSET, + { + {USBDEV_PHY_CONFIG_USE_DIFF_RCVR_BIT, 1}, + {USBDEV_PHY_CONFIG_TX_USE_D_SE0_BIT, 0}, + {USBDEV_PHY_CONFIG_EOP_SINGLE_BIT_BIT, 0}, + {USBDEV_PHY_CONFIG_PINFLIP_BIT, 1}, + {USBDEV_PHY_CONFIG_USB_REF_DISABLE_BIT, 0}, + {USBDEV_PHY_CONFIG_TX_OSC_TEST_MODE_BIT, 1}, + }); + EXPECT_WRITE32(USBDEV_PHY_CONFIG_REG_OFFSET, + { + {USBDEV_PHY_CONFIG_USE_DIFF_RCVR_BIT, 1}, + {USBDEV_PHY_CONFIG_TX_USE_D_SE0_BIT, 0}, + {USBDEV_PHY_CONFIG_EOP_SINGLE_BIT_BIT, 0}, + {USBDEV_PHY_CONFIG_PINFLIP_BIT, 1}, + {USBDEV_PHY_CONFIG_USB_REF_DISABLE_BIT, 0}, + {USBDEV_PHY_CONFIG_TX_OSC_TEST_MODE_BIT, 0}, + }); + EXPECT_DIF_OK(dif_usbdev_set_osc_test_mode(&usbdev_, kDifToggleDisabled)); +} + +TEST_F(UsbdevTest, ConnectAndConfig) { + // Connect the interface. + EXPECT_READ32(USBDEV_USBCTRL_REG_OFFSET, 0); + EXPECT_WRITE32(USBDEV_USBCTRL_REG_OFFSET, {{USBDEV_USBCTRL_ENABLE_BIT, 1}}); + EXPECT_DIF_OK(dif_usbdev_interface_enable(&usbdev_, kDifToggleEnabled)); + + // Disconnect the interface. + EXPECT_READ32(USBDEV_USBCTRL_REG_OFFSET, + { + {USBDEV_USBCTRL_ENABLE_BIT, 1}, + {USBDEV_USBCTRL_DEVICE_ADDRESS_OFFSET, 127}, + }); + EXPECT_WRITE32(USBDEV_USBCTRL_REG_OFFSET, + { + {USBDEV_USBCTRL_ENABLE_BIT, 0}, + {USBDEV_USBCTRL_DEVICE_ADDRESS_OFFSET, 127}, + }); + EXPECT_DIF_OK(dif_usbdev_interface_enable(&usbdev_, kDifToggleDisabled)); + + dif_usbdev_endpoint_id_t endpoint = { + .number = 2, + .direction = 1, + }; + EXPECT_READ32(USBDEV_EP_IN_ENABLE_REG_OFFSET, + { + {USBDEV_EP_IN_ENABLE_ENABLE_0_BIT, 1}, + }); + EXPECT_WRITE32(USBDEV_EP_IN_ENABLE_REG_OFFSET, + { + {USBDEV_EP_IN_ENABLE_ENABLE_0_BIT, 1}, + {USBDEV_EP_IN_ENABLE_ENABLE_2_BIT, 1}, + }); + EXPECT_DIF_OK( + dif_usbdev_endpoint_enable(&usbdev_, endpoint, kDifToggleEnabled)); + + endpoint.number = 6; + endpoint.direction = 0; + EXPECT_READ32(USBDEV_EP_OUT_ENABLE_REG_OFFSET, + { + {USBDEV_EP_OUT_ENABLE_ENABLE_5_BIT, 1}, + {USBDEV_EP_OUT_ENABLE_ENABLE_6_BIT, 1}, + }); + EXPECT_WRITE32(USBDEV_EP_OUT_ENABLE_REG_OFFSET, + { + {USBDEV_EP_OUT_ENABLE_ENABLE_5_BIT, 1}, + }); + EXPECT_DIF_OK( + dif_usbdev_endpoint_enable(&usbdev_, endpoint, kDifToggleDisabled)); + + endpoint.number = 11; + endpoint.direction = 0; + EXPECT_READ32(USBDEV_OUT_ISO_REG_OFFSET, { + {USBDEV_OUT_ISO_ISO_5_BIT, 1}, + }); + EXPECT_WRITE32(USBDEV_OUT_ISO_REG_OFFSET, { + {USBDEV_OUT_ISO_ISO_5_BIT, 1}, + {USBDEV_OUT_ISO_ISO_11_BIT, 1}, + }); + EXPECT_DIF_OK( + dif_usbdev_endpoint_iso_enable(&usbdev_, endpoint, kDifToggleEnabled)); + + endpoint.number = 7; + endpoint.direction = 1; + EXPECT_READ32(USBDEV_IN_ISO_REG_OFFSET, { + {USBDEV_IN_ISO_ISO_1_BIT, 1}, + {USBDEV_IN_ISO_ISO_7_BIT, 1}, + }); + EXPECT_WRITE32(USBDEV_IN_ISO_REG_OFFSET, { + {USBDEV_IN_ISO_ISO_1_BIT, 1}, + }); + EXPECT_DIF_OK( + dif_usbdev_endpoint_iso_enable(&usbdev_, endpoint, kDifToggleDisabled)); +} + +TEST_F(UsbdevTest, OutEndpointConfig) { + EXPECT_READ32(USBDEV_RXENABLE_SETUP_REG_OFFSET, + { + {USBDEV_RXENABLE_SETUP_SETUP_0_BIT, 1}, + {USBDEV_RXENABLE_SETUP_SETUP_10_BIT, 1}, + {USBDEV_RXENABLE_SETUP_SETUP_11_BIT, 1}, + }); + EXPECT_WRITE32(USBDEV_RXENABLE_SETUP_REG_OFFSET, + { + {USBDEV_RXENABLE_SETUP_SETUP_0_BIT, 1}, + {USBDEV_RXENABLE_SETUP_SETUP_9_BIT, 1}, + {USBDEV_RXENABLE_SETUP_SETUP_10_BIT, 1}, + {USBDEV_RXENABLE_SETUP_SETUP_11_BIT, 1}, + }); + EXPECT_DIF_OK(dif_usbdev_endpoint_setup_enable(&usbdev_, /*endpoint=*/9, + kDifToggleEnabled)); + + EXPECT_READ32(USBDEV_RXENABLE_SETUP_REG_OFFSET, + { + {USBDEV_RXENABLE_SETUP_SETUP_8_BIT, 1}, + {USBDEV_RXENABLE_SETUP_SETUP_9_BIT, 1}, + {USBDEV_RXENABLE_SETUP_SETUP_10_BIT, 1}, + }); + EXPECT_WRITE32(USBDEV_RXENABLE_SETUP_REG_OFFSET, + { + {USBDEV_RXENABLE_SETUP_SETUP_8_BIT, 1}, + {USBDEV_RXENABLE_SETUP_SETUP_9_BIT, 1}, + }); + EXPECT_DIF_OK(dif_usbdev_endpoint_setup_enable(&usbdev_, /*endpoint=*/10, + kDifToggleDisabled)); + + EXPECT_READ32(USBDEV_RXENABLE_OUT_REG_OFFSET, + { + {USBDEV_RXENABLE_OUT_OUT_0_BIT, 1}, + {USBDEV_RXENABLE_OUT_OUT_2_BIT, 1}, + {USBDEV_RXENABLE_OUT_OUT_9_BIT, 1}, + }); + EXPECT_WRITE32(USBDEV_RXENABLE_OUT_REG_OFFSET, + { + {USBDEV_RXENABLE_OUT_OUT_0_BIT, 1}, + {USBDEV_RXENABLE_OUT_OUT_2_BIT, 1}, + {USBDEV_RXENABLE_OUT_OUT_5_BIT, 1}, + {USBDEV_RXENABLE_OUT_OUT_9_BIT, 1}, + }); + EXPECT_DIF_OK(dif_usbdev_endpoint_out_enable(&usbdev_, /*endpoint=*/5, + kDifToggleEnabled)); + + EXPECT_READ32(USBDEV_RXENABLE_OUT_REG_OFFSET, + { + {USBDEV_RXENABLE_OUT_OUT_1_BIT, 1}, + {USBDEV_RXENABLE_OUT_OUT_3_BIT, 1}, + {USBDEV_RXENABLE_OUT_OUT_7_BIT, 1}, + }); + EXPECT_WRITE32(USBDEV_RXENABLE_OUT_REG_OFFSET, + { + {USBDEV_RXENABLE_OUT_OUT_1_BIT, 1}, + {USBDEV_RXENABLE_OUT_OUT_7_BIT, 1}, + }); + EXPECT_DIF_OK(dif_usbdev_endpoint_out_enable(&usbdev_, /*endpoint=*/3, + kDifToggleDisabled)); + + EXPECT_READ32(USBDEV_SET_NAK_OUT_REG_OFFSET, + { + {USBDEV_SET_NAK_OUT_ENABLE_10_BIT, 1}, + }); + EXPECT_WRITE32(USBDEV_SET_NAK_OUT_REG_OFFSET, + { + {USBDEV_SET_NAK_OUT_ENABLE_9_BIT, 1}, + {USBDEV_SET_NAK_OUT_ENABLE_10_BIT, 1}, + }); + EXPECT_DIF_OK(dif_usbdev_endpoint_set_nak_out_enable(&usbdev_, /*endpoint=*/9, + kDifToggleEnabled)); + + EXPECT_READ32(USBDEV_SET_NAK_OUT_REG_OFFSET, + { + {USBDEV_SET_NAK_OUT_ENABLE_8_BIT, 1}, + {USBDEV_SET_NAK_OUT_ENABLE_9_BIT, 1}, + }); + EXPECT_WRITE32(USBDEV_SET_NAK_OUT_REG_OFFSET, + { + {USBDEV_SET_NAK_OUT_ENABLE_9_BIT, 1}, + }); + EXPECT_DIF_OK(dif_usbdev_endpoint_set_nak_out_enable(&usbdev_, /*endpoint=*/8, + kDifToggleDisabled)); +} + +TEST_F(UsbdevTest, StallConfig) { + dif_usbdev_endpoint_id_t endpoint = { + .number = 1, + .direction = 1, + }; + EXPECT_READ32(USBDEV_IN_STALL_REG_OFFSET, + { + {USBDEV_IN_STALL_ENDPOINT_0_BIT, 1}, + }); + EXPECT_WRITE32(USBDEV_IN_STALL_REG_OFFSET, + { + {USBDEV_IN_STALL_ENDPOINT_0_BIT, 1}, + {USBDEV_IN_STALL_ENDPOINT_1_BIT, 1}, + }); + EXPECT_DIF_OK( + dif_usbdev_endpoint_stall_enable(&usbdev_, endpoint, kDifToggleEnabled)); + + endpoint.number = 3; + endpoint.direction = 0; + EXPECT_READ32(USBDEV_OUT_STALL_REG_OFFSET, + { + {USBDEV_OUT_STALL_ENDPOINT_5_BIT, 1}, + }); + EXPECT_WRITE32(USBDEV_OUT_STALL_REG_OFFSET, + { + {USBDEV_OUT_STALL_ENDPOINT_3_BIT, 1}, + {USBDEV_OUT_STALL_ENDPOINT_5_BIT, 1}, + }); + EXPECT_DIF_OK( + dif_usbdev_endpoint_stall_enable(&usbdev_, endpoint, kDifToggleEnabled)); + + bool enabled; + endpoint.number = 5; + endpoint.direction = 1; + EXPECT_READ32(USBDEV_IN_STALL_REG_OFFSET, + { + {USBDEV_IN_STALL_ENDPOINT_5_BIT, 1}, + }); + EXPECT_DIF_OK(dif_usbdev_endpoint_stall_get(&usbdev_, endpoint, &enabled)); + EXPECT_TRUE(enabled); + + endpoint.number = 11; + endpoint.direction = 0; + EXPECT_READ32(USBDEV_OUT_STALL_REG_OFFSET, + { + {USBDEV_OUT_STALL_ENDPOINT_5_BIT, 1}, + {USBDEV_OUT_STALL_ENDPOINT_9_BIT, 1}, + {USBDEV_OUT_STALL_ENDPOINT_10_BIT, 1}, + }); + EXPECT_DIF_OK(dif_usbdev_endpoint_stall_get(&usbdev_, endpoint, &enabled)); + EXPECT_FALSE(enabled); +} + +TEST_F(UsbdevTest, OutPacket) { + constexpr uint32_t kMaxAvBuffers = 4; + dif_usbdev_buffer_pool_t buffer_pool; + dif_usbdev_config_t phy_config = { + .have_differential_receiver = kDifToggleEnabled, + .use_tx_d_se0 = kDifToggleDisabled, + .single_bit_eop = kDifToggleDisabled, + .pin_flip = kDifToggleDisabled, + .clock_sync_signals = kDifToggleEnabled, + }; + EXPECT_WRITE32(USBDEV_PHY_CONFIG_REG_OFFSET, + { + {USBDEV_PHY_CONFIG_USE_DIFF_RCVR_BIT, 1}, + {USBDEV_PHY_CONFIG_TX_USE_D_SE0_BIT, 0}, + {USBDEV_PHY_CONFIG_EOP_SINGLE_BIT_BIT, 0}, + {USBDEV_PHY_CONFIG_PINFLIP_BIT, 0}, + {USBDEV_PHY_CONFIG_USB_REF_DISABLE_BIT, 0}, + }); + EXPECT_DIF_OK(dif_usbdev_configure(&usbdev_, &buffer_pool, phy_config)); + + // Add buffers to the AV FIFO to receive. + for (uint32_t i = 0; i < kMaxAvBuffers; i++) { + int top = buffer_pool.top; + EXPECT_READ32(USBDEV_USBSTAT_REG_OFFSET, + { + {USBDEV_USBSTAT_FRAME_OFFSET, 10}, + {USBDEV_USBSTAT_LINK_STATE_OFFSET, + USBDEV_USBSTAT_LINK_STATE_VALUE_ACTIVE}, + {USBDEV_USBSTAT_SENSE_BIT, 1}, + {USBDEV_USBSTAT_AV_DEPTH_OFFSET, i}, + {USBDEV_USBSTAT_AV_FULL_BIT, 0}, + }); + EXPECT_WRITE32( + USBDEV_AVBUFFER_REG_OFFSET, + {{USBDEV_AVBUFFER_BUFFER_OFFSET, buffer_pool.buffers[top - i]}}); + } + EXPECT_READ32(USBDEV_USBSTAT_REG_OFFSET, + { + {USBDEV_USBSTAT_FRAME_OFFSET, 10}, + {USBDEV_USBSTAT_LINK_STATE_OFFSET, + USBDEV_USBSTAT_LINK_STATE_VALUE_ACTIVE}, + {USBDEV_USBSTAT_SENSE_BIT, 1}, + {USBDEV_USBSTAT_AV_DEPTH_OFFSET, kMaxAvBuffers}, + {USBDEV_USBSTAT_AV_FULL_BIT, 1}, + }); + EXPECT_DIF_OK(dif_usbdev_fill_available_fifo(&usbdev_, &buffer_pool)); + + // No read data available yet. + dif_usbdev_rx_packet_info_t rx_packet_info; + dif_usbdev_buffer_t buffer; + EXPECT_READ32(USBDEV_USBSTAT_REG_OFFSET, + { + {USBDEV_USBSTAT_FRAME_OFFSET, 15}, + {USBDEV_USBSTAT_LINK_STATE_OFFSET, + USBDEV_USBSTAT_LINK_STATE_VALUE_ACTIVE}, + {USBDEV_USBSTAT_SENSE_BIT, 1}, + {USBDEV_USBSTAT_AV_DEPTH_OFFSET, kMaxAvBuffers}, + {USBDEV_USBSTAT_AV_FULL_BIT, 1}, + {USBDEV_USBSTAT_RX_EMPTY_BIT, 1}, + }); + EXPECT_EQ(dif_usbdev_recv(&usbdev_, &rx_packet_info, &buffer), + kDifUnavailable); + + // Receive OUT packet all at once. + uint32_t expected_data[4], recvd_data[4]; + for (size_t i = 0; i < sizeof(expected_data) / sizeof(expected_data[0]); + i++) { + expected_data[i] = i * 1023; + } + EXPECT_READ32(USBDEV_USBSTAT_REG_OFFSET, + { + {USBDEV_USBSTAT_FRAME_OFFSET, 15}, + {USBDEV_USBSTAT_LINK_STATE_OFFSET, + USBDEV_USBSTAT_LINK_STATE_VALUE_ACTIVE}, + {USBDEV_USBSTAT_SENSE_BIT, 1}, + {USBDEV_USBSTAT_AV_DEPTH_OFFSET, kMaxAvBuffers - 1}, + {USBDEV_USBSTAT_AV_FULL_BIT, 0}, + {USBDEV_USBSTAT_RX_EMPTY_BIT, 0}, + }); + EXPECT_READ32(USBDEV_RXFIFO_REG_OFFSET, + { + {USBDEV_RXFIFO_EP_OFFSET, 1}, + {USBDEV_RXFIFO_SETUP_BIT, 0}, + {USBDEV_RXFIFO_SIZE_OFFSET, sizeof(expected_data)}, + {USBDEV_RXFIFO_BUFFER_OFFSET, 0}, + }); + EXPECT_DIF_OK(dif_usbdev_recv(&usbdev_, &rx_packet_info, &buffer)); + EXPECT_EQ(rx_packet_info.endpoint, 1); + EXPECT_EQ(rx_packet_info.length, sizeof(expected_data)); + EXPECT_FALSE(rx_packet_info.is_setup); + EXPECT_EQ(buffer.id, 0); + EXPECT_EQ(buffer.offset, 0); + EXPECT_EQ(buffer.remaining_bytes, sizeof(expected_data)); + EXPECT_EQ(buffer.type, kDifUsbdevBufferTypeRead); + + size_t bytes_written; + for (size_t i = 0; i < sizeof(expected_data) / sizeof(expected_data[0]); + i++) { + EXPECT_READ32(USBDEV_BUFFER_REG_OFFSET + buffer.id * 64 + 4 * i, + expected_data[i]); + } + EXPECT_DIF_OK(dif_usbdev_buffer_read(&usbdev_, &buffer_pool, &buffer, + reinterpret_cast<uint8_t *>(recvd_data), + sizeof(recvd_data), &bytes_written)); + EXPECT_EQ(bytes_written, sizeof(recvd_data)); + EXPECT_EQ(memcmp(expected_data, recvd_data, sizeof(expected_data)), 0); + + // One more received packet to test other offsets. + EXPECT_READ32(USBDEV_USBSTAT_REG_OFFSET, + { + {USBDEV_USBSTAT_FRAME_OFFSET, 25}, + {USBDEV_USBSTAT_LINK_STATE_OFFSET, + USBDEV_USBSTAT_LINK_STATE_VALUE_ACTIVE}, + {USBDEV_USBSTAT_SENSE_BIT, 1}, + {USBDEV_USBSTAT_AV_DEPTH_OFFSET, kMaxAvBuffers - 2}, + {USBDEV_USBSTAT_AV_FULL_BIT, 1}, + {USBDEV_USBSTAT_RX_EMPTY_BIT, 0}, + }); + EXPECT_READ32(USBDEV_RXFIFO_REG_OFFSET, + { + {USBDEV_RXFIFO_EP_OFFSET, 0}, + {USBDEV_RXFIFO_SETUP_BIT, 1}, + {USBDEV_RXFIFO_SIZE_OFFSET, sizeof(expected_data) - 1}, + {USBDEV_RXFIFO_BUFFER_OFFSET, 1}, + }); + EXPECT_DIF_OK(dif_usbdev_recv(&usbdev_, &rx_packet_info, &buffer)); + EXPECT_EQ(rx_packet_info.endpoint, 0); + EXPECT_EQ(rx_packet_info.length, sizeof(expected_data) - 1); + EXPECT_TRUE(rx_packet_info.is_setup); + EXPECT_EQ(buffer.id, 1); + EXPECT_EQ(buffer.offset, 0); + EXPECT_EQ(buffer.remaining_bytes, sizeof(expected_data) - 1); + EXPECT_EQ(buffer.type, kDifUsbdevBufferTypeRead); + + memset(recvd_data, 0, sizeof(recvd_data)); + for (size_t i = 0; i < sizeof(expected_data) / sizeof(expected_data[0]); + i++) { + EXPECT_READ32(USBDEV_BUFFER_REG_OFFSET + buffer.id * 64 + 4 * i, + expected_data[i]); + } + EXPECT_DIF_OK(dif_usbdev_buffer_read(&usbdev_, &buffer_pool, &buffer, + reinterpret_cast<uint8_t *>(recvd_data), + 4, &bytes_written)); + EXPECT_EQ(bytes_written, 4); + EXPECT_DIF_OK(dif_usbdev_buffer_read( + &usbdev_, &buffer_pool, &buffer, + reinterpret_cast<uint8_t *>(recvd_data) + bytes_written, + sizeof(recvd_data) - bytes_written, &bytes_written)); + EXPECT_EQ(bytes_written, sizeof(recvd_data) - 4 - 1); + EXPECT_EQ(memcmp(expected_data, recvd_data, sizeof(expected_data)), 0); +} + +TEST_F(UsbdevTest, InPacket) { + dif_usbdev_buffer_pool_t buffer_pool; + dif_usbdev_config_t phy_config = { + .have_differential_receiver = kDifToggleEnabled, + .use_tx_d_se0 = kDifToggleDisabled, + .single_bit_eop = kDifToggleDisabled, + .pin_flip = kDifToggleDisabled, + .clock_sync_signals = kDifToggleEnabled, + }; + EXPECT_WRITE32(USBDEV_PHY_CONFIG_REG_OFFSET, + { + {USBDEV_PHY_CONFIG_USE_DIFF_RCVR_BIT, 1}, + {USBDEV_PHY_CONFIG_TX_USE_D_SE0_BIT, 0}, + {USBDEV_PHY_CONFIG_EOP_SINGLE_BIT_BIT, 0}, + {USBDEV_PHY_CONFIG_PINFLIP_BIT, 0}, + {USBDEV_PHY_CONFIG_USB_REF_DISABLE_BIT, 0}, + }); + EXPECT_DIF_OK(dif_usbdev_configure(&usbdev_, &buffer_pool, phy_config)); + + dif_usbdev_buffer_t buffer; + EXPECT_DIF_OK(dif_usbdev_buffer_request(&usbdev_, &buffer_pool, &buffer)); + EXPECT_EQ(buffer.type, kDifUsbdevBufferTypeWrite); + + uint32_t data[16]; + uint8_t *bytes = reinterpret_cast<uint8_t *>(data); + size_t bytes_written; + EXPECT_DIF_OK(dif_usbdev_buffer_return(&usbdev_, &buffer_pool, &buffer)); + EXPECT_EQ(buffer.type, kDifUsbdevBufferTypeStale); + // Can't return a stale buffer. + EXPECT_DIF_BADARG(dif_usbdev_buffer_return(&usbdev_, &buffer_pool, &buffer)); + // Can't submit a stale buffer. + EXPECT_DIF_BADARG(dif_usbdev_buffer_write(&usbdev_, &buffer, bytes, + sizeof(data), &bytes_written)); + + // Request the buffer. + EXPECT_DIF_OK(dif_usbdev_buffer_request(&usbdev_, &buffer_pool, &buffer)); + for (size_t i = 0; i < sizeof(data); i++) { + bytes[i] = i; + if (i % 4 == 3) { + EXPECT_WRITE32(USBDEV_BUFFER_REG_OFFSET + buffer.id * 64 + i - 3, + data[i / 4]); + } + } + EXPECT_DIF_OK(dif_usbdev_buffer_write(&usbdev_, &buffer, bytes, sizeof(data), + &bytes_written)); + EXPECT_EQ(bytes_written, sizeof(data)); + + // Queue up the buffer for transmission. + EXPECT_WRITE32(USBDEV_CONFIGIN_5_REG_OFFSET, + { + {USBDEV_CONFIGIN_5_BUFFER_5_OFFSET, buffer.id}, + {USBDEV_CONFIGIN_5_SIZE_5_OFFSET, bytes_written}, + }); + EXPECT_WRITE32(USBDEV_CONFIGIN_5_REG_OFFSET, + { + {USBDEV_CONFIGIN_5_BUFFER_5_OFFSET, buffer.id}, + {USBDEV_CONFIGIN_5_SIZE_5_OFFSET, bytes_written}, + {USBDEV_CONFIGIN_5_RDY_5_BIT, 1}, + }); + EXPECT_DIF_OK(dif_usbdev_send(&usbdev_, /*endpoint=*/5, &buffer)); + + // Get TX status for a buffer with a transmission that had to be canceled. The + // buffer is returned to the free pool. + dif_usbdev_tx_status_t tx_status; + EXPECT_READ32(USBDEV_CONFIGIN_0_REG_OFFSET, + { + {USBDEV_CONFIGIN_0_BUFFER_0_OFFSET, buffer.id}, + {USBDEV_CONFIGIN_0_SIZE_0_OFFSET, sizeof(data)}, + {USBDEV_CONFIGIN_0_RDY_0_BIT, 0}, + {USBDEV_CONFIGIN_0_PEND_0_BIT, 1}, + }); + EXPECT_READ32(USBDEV_IN_SENT_REG_OFFSET, { + {USBDEV_IN_SENT_SENT_0_BIT, 0}, + }); + EXPECT_DIF_OK(dif_usbdev_get_tx_status(&usbdev_, /*endpoint=*/0, &tx_status)); + EXPECT_EQ(tx_status, kDifUsbdevTxStatusCancelled); + EXPECT_READ32(USBDEV_CONFIGIN_0_REG_OFFSET, + { + {USBDEV_CONFIGIN_0_BUFFER_0_OFFSET, buffer.id}, + {USBDEV_CONFIGIN_0_SIZE_0_OFFSET, sizeof(data)}, + {USBDEV_CONFIGIN_0_RDY_0_BIT, 0}, + {USBDEV_CONFIGIN_0_PEND_0_BIT, 1}, + }); + EXPECT_WRITE32(USBDEV_CONFIGIN_0_REG_OFFSET, + {{USBDEV_CONFIGIN_0_PEND_0_BIT, 1}}); + EXPECT_WRITE32(USBDEV_IN_SENT_REG_OFFSET, {{USBDEV_IN_SENT_SENT_0_BIT, 1}}); + EXPECT_DIF_OK( + dif_usbdev_clear_tx_status(&usbdev_, &buffer_pool, /*endpoint=*/0)); + + // Request a new buffer. + EXPECT_DIF_OK(dif_usbdev_buffer_request(&usbdev_, &buffer_pool, &buffer)); + for (size_t i = 0; i < sizeof(data); i++) { + bytes[i] = i; + if (i % 4 == 3) { + EXPECT_WRITE32(USBDEV_BUFFER_REG_OFFSET + buffer.id * 64 + i - 3, + data[i / 4]); + } + } + EXPECT_DIF_OK(dif_usbdev_buffer_write(&usbdev_, &buffer, bytes, sizeof(data), + &bytes_written)); + + // Queue the buffer for transmission. + EXPECT_WRITE32(USBDEV_CONFIGIN_4_REG_OFFSET, + { + {USBDEV_CONFIGIN_4_BUFFER_4_OFFSET, buffer.id}, + {USBDEV_CONFIGIN_4_SIZE_4_OFFSET, sizeof(data)}, + }); + EXPECT_WRITE32(USBDEV_CONFIGIN_4_REG_OFFSET, + { + {USBDEV_CONFIGIN_4_BUFFER_4_OFFSET, buffer.id}, + {USBDEV_CONFIGIN_4_SIZE_4_OFFSET, sizeof(data)}, + {USBDEV_CONFIGIN_4_RDY_4_BIT, 1}, + }); + EXPECT_DIF_OK(dif_usbdev_send(&usbdev_, /*endpoint=*/4, &buffer)); + + // Get status of an endpoint without a buffer queued for transmission. + EXPECT_READ32(USBDEV_CONFIGIN_7_REG_OFFSET, + { + {USBDEV_CONFIGIN_7_BUFFER_7_OFFSET, buffer.id}, + {USBDEV_CONFIGIN_7_SIZE_7_OFFSET, sizeof(data)}, + {USBDEV_CONFIGIN_7_RDY_7_BIT, 0}, + }); + EXPECT_READ32(USBDEV_IN_SENT_REG_OFFSET, { + {USBDEV_IN_SENT_SENT_7_BIT, 0}, + }); + EXPECT_DIF_OK(dif_usbdev_get_tx_status(&usbdev_, /*endpoint=*/7, &tx_status)); + EXPECT_EQ(tx_status, kDifUsbdevTxStatusNoPacket); + + // Get TX status for a queued, but not sent buffer. + EXPECT_READ32(USBDEV_CONFIGIN_8_REG_OFFSET, + { + {USBDEV_CONFIGIN_8_BUFFER_8_OFFSET, buffer.id}, + {USBDEV_CONFIGIN_8_SIZE_8_OFFSET, sizeof(data)}, + {USBDEV_CONFIGIN_8_RDY_8_BIT, 1}, + }); + EXPECT_DIF_OK(dif_usbdev_get_tx_status(&usbdev_, /*endpoint=*/8, &tx_status)); + EXPECT_EQ(tx_status, kDifUsbdevTxStatusPending); + + // Buffer was transmitted successfully. + uint16_t endpoints_done; + EXPECT_READ32(USBDEV_IN_SENT_REG_OFFSET, { + {USBDEV_IN_SENT_SENT_3_BIT, 1}, + {USBDEV_IN_SENT_SENT_5_BIT, 1}, + }); + EXPECT_DIF_OK(dif_usbdev_get_tx_sent(&usbdev_, &endpoints_done)); + EXPECT_EQ(endpoints_done, (1u << 3) | (1u << 5)); + + EXPECT_READ32(USBDEV_CONFIGIN_5_REG_OFFSET, + { + {USBDEV_CONFIGIN_5_BUFFER_5_OFFSET, buffer.id}, + {USBDEV_CONFIGIN_5_SIZE_5_OFFSET, sizeof(data)}, + {USBDEV_CONFIGIN_5_RDY_5_BIT, 0}, + }); + EXPECT_READ32(USBDEV_IN_SENT_REG_OFFSET, { + {USBDEV_IN_SENT_SENT_3_BIT, 1}, + {USBDEV_IN_SENT_SENT_5_BIT, 1}, + }); + EXPECT_DIF_OK(dif_usbdev_get_tx_status(&usbdev_, /*endpoint=*/5, &tx_status)); + EXPECT_EQ(tx_status, kDifUsbdevTxStatusSent); + EXPECT_EQ(buffer.type, kDifUsbdevBufferTypeStale); + + EXPECT_READ32(USBDEV_CONFIGIN_5_REG_OFFSET, + { + {USBDEV_CONFIGIN_5_BUFFER_5_OFFSET, buffer.id}, + {USBDEV_CONFIGIN_5_SIZE_5_OFFSET, sizeof(data)}, + {USBDEV_CONFIGIN_5_RDY_5_BIT, 0}, + }); + EXPECT_WRITE32(USBDEV_CONFIGIN_5_REG_OFFSET, + {{USBDEV_CONFIGIN_5_PEND_5_BIT, 1}}); + EXPECT_WRITE32(USBDEV_IN_SENT_REG_OFFSET, {{USBDEV_IN_SENT_SENT_5_BIT, 1}}); + EXPECT_DIF_OK( + dif_usbdev_clear_tx_status(&usbdev_, &buffer_pool, /*endpoint=*/5)); +} + +TEST_F(UsbdevTest, DeviceAddresses) { + uint8_t address = 101; + EXPECT_READ32(USBDEV_USBCTRL_REG_OFFSET, + { + {USBDEV_USBCTRL_ENABLE_BIT, 1}, + {USBDEV_USBCTRL_DEVICE_ADDRESS_OFFSET, 0}, + }); + EXPECT_WRITE32(USBDEV_USBCTRL_REG_OFFSET, + { + {USBDEV_USBCTRL_ENABLE_BIT, 1}, + {USBDEV_USBCTRL_DEVICE_ADDRESS_OFFSET, address}, + }); + EXPECT_DIF_OK(dif_usbdev_address_set(&usbdev_, address)); + + EXPECT_READ32(USBDEV_USBCTRL_REG_OFFSET, + { + {USBDEV_USBCTRL_ENABLE_BIT, 1}, + {USBDEV_USBCTRL_DEVICE_ADDRESS_OFFSET, 58}, + }); + EXPECT_DIF_OK(dif_usbdev_address_get(&usbdev_, &address)); + EXPECT_EQ(address, 58); +} + +TEST_F(UsbdevTest, Status) { + EXPECT_WRITE32(USBDEV_DATA_TOGGLE_CLEAR_REG_OFFSET, + {{USBDEV_DATA_TOGGLE_CLEAR_CLEAR_3_BIT, 1}}); + EXPECT_DIF_OK(dif_usbdev_clear_data_toggle(&usbdev_, /*endpoint=*/3)); + EXPECT_WRITE32(USBDEV_DATA_TOGGLE_CLEAR_REG_OFFSET, + {{USBDEV_DATA_TOGGLE_CLEAR_CLEAR_9_BIT, 1}}); + EXPECT_DIF_OK(dif_usbdev_clear_data_toggle(&usbdev_, /*endpoint=*/9)); + + uint16_t frame; + EXPECT_READ32(USBDEV_USBSTAT_REG_OFFSET, + { + {USBDEV_USBSTAT_FRAME_OFFSET, 92}, + {USBDEV_USBSTAT_SENSE_BIT, 1}, + {USBDEV_USBSTAT_LINK_STATE_OFFSET, + USBDEV_USBSTAT_LINK_STATE_VALUE_ACTIVE}, + {USBDEV_USBSTAT_AV_DEPTH_OFFSET, 2}, + }); + EXPECT_DIF_OK(dif_usbdev_status_get_frame(&usbdev_, &frame)); + EXPECT_EQ(frame, 92); + + bool host_lost; + EXPECT_READ32(USBDEV_USBSTAT_REG_OFFSET, + { + {USBDEV_USBSTAT_FRAME_OFFSET, 18}, + {USBDEV_USBSTAT_SENSE_BIT, 1}, + {USBDEV_USBSTAT_LINK_STATE_OFFSET, + USBDEV_USBSTAT_LINK_STATE_VALUE_ACTIVE_NOSOF}, + {USBDEV_USBSTAT_AV_DEPTH_OFFSET, 2}, + {USBDEV_USBSTAT_HOST_LOST_BIT, 1}, + }); + EXPECT_DIF_OK(dif_usbdev_status_get_host_lost(&usbdev_, &host_lost)); + EXPECT_TRUE(host_lost); + EXPECT_READ32(USBDEV_USBSTAT_REG_OFFSET, + { + {USBDEV_USBSTAT_FRAME_OFFSET, 18}, + {USBDEV_USBSTAT_LINK_STATE_OFFSET, + USBDEV_USBSTAT_LINK_STATE_VALUE_ACTIVE_NOSOF}, + {USBDEV_USBSTAT_SENSE_BIT, 1}, + {USBDEV_USBSTAT_AV_DEPTH_OFFSET, 2}, + {USBDEV_USBSTAT_HOST_LOST_BIT, 0}, + }); + EXPECT_DIF_OK(dif_usbdev_status_get_host_lost(&usbdev_, &host_lost)); + EXPECT_FALSE(host_lost); + + bool vbus_sense; + EXPECT_READ32(USBDEV_USBSTAT_REG_OFFSET, + { + {USBDEV_USBSTAT_FRAME_OFFSET, 31}, + {USBDEV_USBSTAT_LINK_STATE_OFFSET, + USBDEV_USBSTAT_LINK_STATE_VALUE_ACTIVE_NOSOF}, + {USBDEV_USBSTAT_SENSE_BIT, 1}, + }); + EXPECT_DIF_OK(dif_usbdev_status_get_sense(&usbdev_, &vbus_sense)); + EXPECT_TRUE(vbus_sense); + + EXPECT_READ32(USBDEV_USBSTAT_REG_OFFSET, + { + {USBDEV_USBSTAT_FRAME_OFFSET, 31}, + {USBDEV_USBSTAT_LINK_STATE_OFFSET, + USBDEV_USBSTAT_LINK_STATE_VALUE_DISCONNECTED}, + {USBDEV_USBSTAT_SENSE_BIT, 0}, + }); + EXPECT_DIF_OK(dif_usbdev_status_get_sense(&usbdev_, &vbus_sense)); + EXPECT_FALSE(vbus_sense); + + uint8_t av_fifo_depth; + EXPECT_READ32(USBDEV_USBSTAT_REG_OFFSET, + { + {USBDEV_USBSTAT_FRAME_OFFSET, 11}, + {USBDEV_USBSTAT_LINK_STATE_OFFSET, + USBDEV_USBSTAT_LINK_STATE_VALUE_ACTIVE}, + {USBDEV_USBSTAT_SENSE_BIT, 1}, + {USBDEV_USBSTAT_AV_DEPTH_OFFSET, 3}, + {USBDEV_USBSTAT_RX_EMPTY_BIT, 1}, + }); + EXPECT_DIF_OK( + dif_usbdev_status_get_available_fifo_depth(&usbdev_, &av_fifo_depth)); + EXPECT_EQ(av_fifo_depth, 3); + + bool av_fifo_full; + EXPECT_READ32(USBDEV_USBSTAT_REG_OFFSET, + { + {USBDEV_USBSTAT_FRAME_OFFSET, 12}, + {USBDEV_USBSTAT_LINK_STATE_OFFSET, + USBDEV_USBSTAT_LINK_STATE_VALUE_ACTIVE}, + {USBDEV_USBSTAT_SENSE_BIT, 1}, + {USBDEV_USBSTAT_AV_DEPTH_OFFSET, 4}, + {USBDEV_USBSTAT_AV_FULL_BIT, 1}, + {USBDEV_USBSTAT_RX_EMPTY_BIT, 1}, + }); + EXPECT_DIF_OK( + dif_usbdev_status_get_available_fifo_full(&usbdev_, &av_fifo_full)); + EXPECT_TRUE(av_fifo_full); + + uint8_t rx_fifo_depth; + EXPECT_READ32(USBDEV_USBSTAT_REG_OFFSET, + { + {USBDEV_USBSTAT_FRAME_OFFSET, 12}, + {USBDEV_USBSTAT_LINK_STATE_OFFSET, + USBDEV_USBSTAT_LINK_STATE_VALUE_ACTIVE}, + {USBDEV_USBSTAT_SENSE_BIT, 1}, + {USBDEV_USBSTAT_AV_DEPTH_OFFSET, 4}, + {USBDEV_USBSTAT_AV_FULL_BIT, 1}, + {USBDEV_USBSTAT_RX_EMPTY_BIT, 0}, + {USBDEV_USBSTAT_RX_DEPTH_OFFSET, 2}, + }); + EXPECT_DIF_OK(dif_usbdev_status_get_rx_fifo_depth(&usbdev_, &rx_fifo_depth)); + EXPECT_EQ(rx_fifo_depth, 2); + + bool rx_fifo_empty; + EXPECT_READ32(USBDEV_USBSTAT_REG_OFFSET, + { + {USBDEV_USBSTAT_FRAME_OFFSET, 12}, + {USBDEV_USBSTAT_LINK_STATE_OFFSET, + USBDEV_USBSTAT_LINK_STATE_VALUE_ACTIVE}, + {USBDEV_USBSTAT_SENSE_BIT, 1}, + {USBDEV_USBSTAT_AV_DEPTH_OFFSET, 4}, + {USBDEV_USBSTAT_AV_FULL_BIT, 1}, + {USBDEV_USBSTAT_RX_EMPTY_BIT, 1}, + }); + EXPECT_DIF_OK(dif_usbdev_status_get_rx_fifo_empty(&usbdev_, &rx_fifo_empty)); + EXPECT_TRUE(rx_fifo_empty); +} + +TEST_F(UsbdevTest, LinkState) { + dif_usbdev_link_state_t link_state; + EXPECT_READ32(USBDEV_USBSTAT_REG_OFFSET, + { + {USBDEV_USBSTAT_FRAME_OFFSET, 27}, + {USBDEV_USBSTAT_SENSE_BIT, 1}, + {USBDEV_USBSTAT_LINK_STATE_OFFSET, + USBDEV_USBSTAT_LINK_STATE_VALUE_ACTIVE}, + {USBDEV_USBSTAT_AV_DEPTH_OFFSET, 2}, + }); + EXPECT_DIF_OK(dif_usbdev_status_get_link_state(&usbdev_, &link_state)); + EXPECT_EQ(link_state, kDifUsbdevLinkStateActive); + + EXPECT_READ32(USBDEV_USBSTAT_REG_OFFSET, + { + {USBDEV_USBSTAT_LINK_STATE_OFFSET, + USBDEV_USBSTAT_LINK_STATE_VALUE_DISCONNECTED}, + }); + EXPECT_DIF_OK(dif_usbdev_status_get_link_state(&usbdev_, &link_state)); + EXPECT_EQ(link_state, kDifUsbdevLinkStateDisconnected); + + EXPECT_READ32(USBDEV_USBSTAT_REG_OFFSET, + { + {USBDEV_USBSTAT_SENSE_BIT, 1}, + {USBDEV_USBSTAT_LINK_STATE_OFFSET, + USBDEV_USBSTAT_LINK_STATE_VALUE_POWERED}, + }); + EXPECT_DIF_OK(dif_usbdev_status_get_link_state(&usbdev_, &link_state)); + EXPECT_EQ(link_state, kDifUsbdevLinkStatePowered); + + EXPECT_READ32(USBDEV_USBSTAT_REG_OFFSET, + { + {USBDEV_USBSTAT_SENSE_BIT, 1}, + {USBDEV_USBSTAT_LINK_STATE_OFFSET, + USBDEV_USBSTAT_LINK_STATE_VALUE_POWERED_SUSPENDED}, + }); + EXPECT_DIF_OK(dif_usbdev_status_get_link_state(&usbdev_, &link_state)); + EXPECT_EQ(link_state, kDifUsbdevLinkStatePoweredSuspended); + + EXPECT_READ32(USBDEV_USBSTAT_REG_OFFSET, + { + {USBDEV_USBSTAT_SENSE_BIT, 1}, + {USBDEV_USBSTAT_LINK_STATE_OFFSET, + USBDEV_USBSTAT_LINK_STATE_VALUE_SUSPENDED}, + }); + EXPECT_DIF_OK(dif_usbdev_status_get_link_state(&usbdev_, &link_state)); + EXPECT_EQ(link_state, kDifUsbdevLinkStateSuspended); + + EXPECT_READ32(USBDEV_USBSTAT_REG_OFFSET, + { + {USBDEV_USBSTAT_SENSE_BIT, 1}, + {USBDEV_USBSTAT_LINK_STATE_OFFSET, + USBDEV_USBSTAT_LINK_STATE_VALUE_ACTIVE_NOSOF}, + }); + EXPECT_DIF_OK(dif_usbdev_status_get_link_state(&usbdev_, &link_state)); + EXPECT_EQ(link_state, kDifUsbdevLinkStateActiveNoSof); + + EXPECT_READ32(USBDEV_USBSTAT_REG_OFFSET, + { + {USBDEV_USBSTAT_SENSE_BIT, 1}, + {USBDEV_USBSTAT_LINK_STATE_OFFSET, + USBDEV_USBSTAT_LINK_STATE_VALUE_RESUMING}, + }); + EXPECT_DIF_OK(dif_usbdev_status_get_link_state(&usbdev_, &link_state)); + EXPECT_EQ(link_state, kDifUsbdevLinkStateResuming); +} + +TEST_F(UsbdevTest, WakeFromSleep) { + EXPECT_WRITE32(USBDEV_WAKE_CONTROL_REG_OFFSET, + {{USBDEV_WAKE_CONTROL_SUSPEND_REQ_BIT, 1}}); + EXPECT_DIF_OK(dif_usbdev_set_wake_enable(&usbdev_, kDifToggleEnabled)); + + dif_usbdev_wake_status_t wake_status; + EXPECT_READ32(USBDEV_WAKE_EVENTS_REG_OFFSET, + { + {USBDEV_WAKE_EVENTS_MODULE_ACTIVE_BIT, 1}, + {USBDEV_WAKE_EVENTS_DISCONNECTED_BIT, 0}, + {USBDEV_WAKE_EVENTS_BUS_RESET_BIT, 1}, + }); + EXPECT_DIF_OK(dif_usbdev_get_wake_status(&usbdev_, &wake_status)); + EXPECT_TRUE(wake_status.active); + EXPECT_FALSE(wake_status.disconnected); + EXPECT_TRUE(wake_status.bus_reset); + + EXPECT_READ32(USBDEV_USBCTRL_REG_OFFSET, + { + {USBDEV_USBCTRL_ENABLE_BIT, 1}, + {USBDEV_USBCTRL_DEVICE_ADDRESS_OFFSET, 88}, + {USBDEV_USBCTRL_RESUME_LINK_ACTIVE_BIT, 0}, + }); + EXPECT_WRITE32(USBDEV_USBCTRL_REG_OFFSET, + { + {USBDEV_USBCTRL_ENABLE_BIT, 1}, + {USBDEV_USBCTRL_DEVICE_ADDRESS_OFFSET, 88}, + {USBDEV_USBCTRL_RESUME_LINK_ACTIVE_BIT, 1}, + }); + EXPECT_DIF_OK(dif_usbdev_resume_link_to_active(&usbdev_)); + + EXPECT_WRITE32(USBDEV_WAKE_CONTROL_REG_OFFSET, + {{USBDEV_WAKE_CONTROL_WAKE_ACK_BIT, 1}}); + EXPECT_DIF_OK(dif_usbdev_set_wake_enable(&usbdev_, kDifToggleDisabled)); +} + +} // namespace +} // namespace dif_usbdev_unittest
diff --git a/sw/device/lib/testing/usb_testutils.c b/sw/device/lib/testing/usb_testutils.c new file mode 100644 index 0000000..bacc95b --- /dev/null +++ b/sw/device/lib/testing/usb_testutils.c
@@ -0,0 +1,443 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#include "sw/device/lib/testing/usb_testutils.h" + +#include "sw/device/lib/dif/dif_usbdev.h" +#include "sw/device/lib/testing/test_framework/check.h" + +#include "hw/top_earlgrey/sw/autogen/top_earlgrey.h" + +#define USBDEV_BASE_ADDR TOP_EARLGREY_USBDEV_BASE_ADDR + +static dif_usbdev_t usbdev; +static dif_usbdev_buffer_pool_t buffer_pool; + +// Internal function to create the packet that will form the next part of a +// larger buffer transfer +static bool usb_testutils_part_prepare(usb_testutils_ctx_t *ctx, + usb_testutils_transfer_t *transfer, + dif_usbdev_buffer_t *next_part, + bool *last) { + CHECK(ctx && transfer && last); + + // Allocate and fill a packet buffer + dif_result_t result = + dif_usbdev_buffer_request(ctx->dev, ctx->buffer_pool, next_part); + if (result != kDifOk) { + return false; + } + + // Determine the maximum bytes/packet + unsigned max_packet = USBDEV_MAX_PACKET_SIZE; + if (transfer->flags & kUsbTestutilsXfrMaxPacketSupplied) { + max_packet = (unsigned)(transfer->flags & kUsbTestutilsXfrMaxPacketMask); + } + + // How much are we sending this time? + unsigned part_len = transfer->length - transfer->offset; + if (part_len > max_packet) { + part_len = max_packet; + } + size_t bytes_written = 0U; + if (part_len) { + CHECK_DIF_OK(dif_usbdev_buffer_write(ctx->dev, next_part, + &transfer->buffer[transfer->offset], + part_len, &bytes_written)); + } + // Is this the last packet? + uint32_t next_offset = transfer->offset + bytes_written; + *last = true; + if (bytes_written == max_packet) { + if (next_offset < transfer->length || + (transfer->flags & kUsbTestutilsXfrEmployZLP)) { + *last = false; + } + } else { + CHECK(bytes_written < max_packet); + } + + transfer->offset = next_offset; + return true; +} + +// Internal function to perform the next part of a larger buffer transfer +static bool usb_testutils_transfer_next_part( + usb_testutils_ctx_t *ctx, uint8_t ep, usb_testutils_transfer_t *transfer) { + // Do we need to prepare a packet? + if (!transfer->next_valid && + !usb_testutils_part_prepare(ctx, transfer, &transfer->next_part, + &transfer->last)) { + return false; + } + + // Send the existing prepared packet + CHECK_DIF_OK(dif_usbdev_send(ctx->dev, ep, &transfer->next_part)); + transfer->next_valid = false; + + // If we're double-buffering, request and fill another buffer immediately; + // we'll then be able to supply it much more promptly later... + if ((transfer->flags & kUsbTestutilsXfrDoubleBuffered) && !transfer->last) { + transfer->next_valid = usb_testutils_part_prepare( + ctx, transfer, &transfer->next_part, &transfer->last); + } + + return true; +} + +void usb_testutils_poll(usb_testutils_ctx_t *ctx) { + uint32_t istate; + + // Collect a set of interrupts + CHECK_DIF_OK(dif_usbdev_irq_get_state(ctx->dev, &istate)); + + if (!istate) { + return; + } + + // Process IN completions first so we get the fact that send completed + // before processing a response to that transmission + // This is also important for device IN performance + if (istate & (1u << kDifUsbdevIrqPktSent)) { + uint16_t sentep; + CHECK_DIF_OK(dif_usbdev_get_tx_sent(ctx->dev, &sentep)); + TRC_C('a' + sentep); + unsigned ep = 0u; + while (sentep && ep < USBDEV_NUM_ENDPOINTS) { + if (sentep & (1u << ep)) { + // Free up the buffer and optionally callback + CHECK_DIF_OK( + dif_usbdev_clear_tx_status(ctx->dev, ctx->buffer_pool, ep)); + + // If we have a larger transfer in progress, continue with that + usb_testutils_transfer_t *transfer = &ctx->in[ep].transfer; + usb_testutils_xfr_result_t res = kUsbTestutilsXfrResultOk; + bool done = true; + if (transfer->buffer) { + if (transfer->next_valid || !transfer->last) { + if (usb_testutils_transfer_next_part(ctx, ep, transfer)) { + done = false; + } else { + res = kUsbTestutilsXfrResultFailed; + } + } + if (done) { + // Larger buffer transfer now completed; forget the buffer + transfer->buffer = NULL; + } + } + // Notify that we've sent the single packet, or larger buffer transfer + // is now complete + if (done && ctx->in[ep].tx_done_callback) { + ctx->in[ep].tx_done_callback(ctx->in[ep].ep_ctx, res); + } + sentep &= ~(1u << ep); + } + ep++; + } + } + + // Keep buffers available for packet reception + CHECK_DIF_OK(dif_usbdev_fill_available_fifo(ctx->dev, ctx->buffer_pool)); + + if (istate & (1u << kDifUsbdevIrqPktReceived)) { + // TODO: we run the risk of starving the IN side here if the rx_callback(s) + // are time-consuming + while (true) { + bool is_empty; + CHECK_DIF_OK(dif_usbdev_status_get_rx_fifo_empty(ctx->dev, &is_empty)); + if (is_empty) { + break; + } + + dif_usbdev_rx_packet_info_t packet_info; + dif_usbdev_buffer_t buffer; + CHECK_DIF_OK(dif_usbdev_recv(ctx->dev, &packet_info, &buffer)); + + unsigned ep = packet_info.endpoint; + if (ctx->out[ep].rx_callback) { + ctx->out[ep].rx_callback(ctx->out[ep].ep_ctx, packet_info, buffer); + } else { + // Note: this could happen following endpoint removal + TRC_S("USB: unexpected RX "); + TRC_I(ep, 8); + CHECK_DIF_OK( + dif_usbdev_buffer_return(ctx->dev, ctx->buffer_pool, &buffer)); + } + } + } + if (istate & (1u << kDifUsbdevIrqLinkReset)) { + TRC_S("USB: Bus reset"); + // Link reset + for (int ep = 0; ep < USBDEV_NUM_ENDPOINTS; ep++) { + // Notify the IN endpoint first because transmission is more significantly + // impacted, and then the OUT endpoint before advancing to the next + // endpoint number in case the order is important to the client(s) + if (ctx->in[ep].reset) { + ctx->in[ep].reset(ctx->in[ep].ep_ctx); + } + if (ctx->out[ep].reset) { + ctx->out[ep].reset(ctx->out[ep].ep_ctx); + } + } + } + + // Clear the interrupts that we've received and handled + CHECK_DIF_OK(dif_usbdev_irq_acknowledge_state(ctx->dev, istate)); + + // Record bus frame + if ((istate & (1u << kDifUsbdevIrqFrame))) { + // The first bus frame is 1 + CHECK_DIF_OK(dif_usbdev_status_get_frame(ctx->dev, &ctx->frame)); + ctx->got_frame = true; + } + + // 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 + // usbdev_stream_test + // + // We can expect AVFIFO empty and RXFIFO full interrupts when using a real + // host and also 'LinkOut' errors because these can be triggered by a lack of + // space in the RXFIFO + + if (istate & + ~((1u << kDifUsbdevIrqLinkReset) | (1u << kDifUsbdevIrqPktReceived) | + (1u << kDifUsbdevIrqPktSent) | (1u << kDifUsbdevIrqFrame) | + (1u << kDifUsbdevIrqAvEmpty) | (1u << kDifUsbdevIrqRxFull) | + (1u << kDifUsbdevIrqLinkOutErr) | (1u << kDifUsbdevIrqLinkInErr))) { + // Report anything that really should not be happening during testing, + // at least for now + // + // TODO - introduce deliberate generation of each of these errors, and + // modify usb_testutils_ to return the information without faulting + // it? + if (istate & + ((1u << kDifUsbdevIrqRxFull) | (1u << kDifUsbdevIrqAvOverflow) | + (1u << kDifUsbdevIrqLinkInErr) | (1u << kDifUsbdevIrqRxCrcErr) | + (1u << kDifUsbdevIrqRxPidErr) | (1u << kDifUsbdevIrqRxBitstuffErr) | + (1u << kDifUsbdevIrqLinkOutErr))) { + LOG_INFO("USB: Unexpected interrupts: 0x%08x", istate); + } else { + // Other events are optionally reported + TRC_C('I'); + TRC_I(istate, 12); + TRC_C(' '); + } + } + + // TODO - clean this up + // Frame ticks every 1ms, use to flush data every 16ms + // (faster in DPI but this seems to work ok) + // + // Ensure that we do not flush until we have received a frame + if (ctx->got_frame && (ctx->frame & 0xf) == 1) { + if (ctx->flushed == 0) { + for (unsigned ep = 0; ep < USBDEV_NUM_ENDPOINTS; ep++) { + if (ctx->in[ep].flush) { + ctx->in[ep].flush(ctx->in[ep].ep_ctx); + } + } + ctx->flushed = 1; + } + } else { + ctx->flushed = 0; + } + // TODO Errors? What Errors? +} + +bool 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) { + CHECK(ep < USBDEV_NUM_ENDPOINTS); + + usb_testutils_transfer_t *transfer = &ctx->in[ep].transfer; + if (transfer->buffer) { + // If there is an in-progress transfer, then we cannot accept another + return false; + } + + // Describe this transfer + transfer->buffer = data; + transfer->offset = 0U; + transfer->length = length; + transfer->flags = flags; + transfer->next_valid = false; + + if (!usb_testutils_transfer_next_part(ctx, ep, transfer)) { + // Forget about the attempted transfer + transfer->buffer = NULL; + return false; + } + + // Buffer transfer is underway... + return true; +} + +void usb_testutils_in_endpoint_setup( + usb_testutils_ctx_t *ctx, uint8_t ep, void *ep_ctx, + void (*tx_done)(void *, usb_testutils_xfr_result_t), void (*flush)(void *), + void (*reset)(void *)) { + ctx->in[ep].ep_ctx = ep_ctx; + ctx->in[ep].tx_done_callback = tx_done; + ctx->in[ep].flush = flush; + ctx->in[ep].reset = reset; + + dif_usbdev_endpoint_id_t endpoint = { + .number = ep, + .direction = USBDEV_ENDPOINT_DIR_IN, + }; + + CHECK_DIF_OK( + dif_usbdev_endpoint_stall_enable(ctx->dev, endpoint, kDifToggleDisabled)); + + // Enable IN traffic from device to host + CHECK_DIF_OK( + dif_usbdev_endpoint_enable(ctx->dev, endpoint, kDifToggleEnabled)); +} + +void usb_testutils_out_endpoint_setup( + usb_testutils_ctx_t *ctx, uint8_t ep, + usb_testutils_out_transfer_mode_t out_mode, void *ep_ctx, + void (*rx)(void *, dif_usbdev_rx_packet_info_t, dif_usbdev_buffer_t), + void (*reset)(void *)) { + ctx->out[ep].ep_ctx = ep_ctx; + ctx->out[ep].rx_callback = rx; + ctx->out[ep].reset = reset; + + dif_usbdev_endpoint_id_t endpoint = { + .number = ep, + .direction = USBDEV_ENDPOINT_DIR_OUT, + }; + + CHECK_DIF_OK( + dif_usbdev_endpoint_stall_enable(ctx->dev, endpoint, kDifToggleDisabled)); + + // Enable/disable the endpoint and reception of OUT packets? + dif_toggle_t enabled = kDifToggleEnabled; + if (out_mode == kUsbdevOutDisabled) { + enabled = kDifToggleDisabled; + } + // Enable/disable generation of NAK responses once OUT packet has been + // received? + dif_toggle_t nak = kDifToggleDisabled; + if (out_mode == kUsbdevOutMessage) { + nak = kDifToggleEnabled; + } + + CHECK_DIF_OK(dif_usbdev_endpoint_enable(ctx->dev, endpoint, enabled)); + CHECK_DIF_OK(dif_usbdev_endpoint_out_enable(ctx->dev, ep, enabled)); + CHECK_DIF_OK(dif_usbdev_endpoint_set_nak_out_enable(ctx->dev, ep, nak)); +} + +void usb_testutils_endpoint_setup( + usb_testutils_ctx_t *ctx, uint8_t ep, + usb_testutils_out_transfer_mode_t out_mode, void *ep_ctx, + void (*tx_done)(void *, usb_testutils_xfr_result_t), + void (*rx)(void *, dif_usbdev_rx_packet_info_t, dif_usbdev_buffer_t), + void (*flush)(void *), void (*reset)(void *)) { + usb_testutils_in_endpoint_setup(ctx, ep, ep_ctx, tx_done, flush, reset); + + // Note: register the link reset handler only on the IN endpoint so that it + // does not get invoked twice + usb_testutils_out_endpoint_setup(ctx, ep, out_mode, ep_ctx, rx, NULL); +} + +void usb_testutils_in_endpoint_remove(usb_testutils_ctx_t *ctx, uint8_t ep) { + // Disable IN traffic + dif_usbdev_endpoint_id_t endpoint = { + .number = ep, + .direction = USBDEV_ENDPOINT_DIR_IN, + }; + CHECK_DIF_OK( + dif_usbdev_endpoint_enable(ctx->dev, endpoint, kDifToggleDisabled)); + + // Remove callback handlers + ctx->in[ep].tx_done_callback = NULL; + ctx->in[ep].flush = NULL; + ctx->in[ep].reset = NULL; +} + +void usb_testutils_out_endpoint_remove(usb_testutils_ctx_t *ctx, uint8_t ep) { + // Disable OUT traffic + dif_usbdev_endpoint_id_t endpoint = { + .number = ep, + .direction = USBDEV_ENDPOINT_DIR_OUT, + }; + CHECK_DIF_OK( + dif_usbdev_endpoint_enable(ctx->dev, endpoint, kDifToggleDisabled)); + + // Return the rest of the OUT endpoint configuration to its default state + CHECK_DIF_OK(dif_usbdev_endpoint_set_nak_out_enable(ctx->dev, endpoint.number, + kDifToggleDisabled)); + CHECK_DIF_OK(dif_usbdev_endpoint_out_enable(ctx->dev, endpoint.number, + kDifToggleDisabled)); + + // Remove callback handlers + ctx->out[ep].rx_callback = NULL; + ctx->out[ep].reset = NULL; +} + +void usb_testutils_endpoint_remove(usb_testutils_ctx_t *ctx, uint8_t ep) { + usb_testutils_in_endpoint_remove(ctx, ep); + usb_testutils_out_endpoint_remove(ctx, ep); +} + +void usb_testutils_init(usb_testutils_ctx_t *ctx, bool pinflip, + bool en_diff_rcvr, bool tx_use_d_se0) { + CHECK(ctx != NULL); + ctx->dev = &usbdev; + ctx->buffer_pool = &buffer_pool; + + // Ensure that we do not invoke the endpoint 'flush' functions before + // detection of the first bus frame + ctx->got_frame = false; + ctx->frame = 0u; + + CHECK_DIF_OK( + dif_usbdev_init(mmio_region_from_addr(USBDEV_BASE_ADDR), ctx->dev)); + + dif_usbdev_config_t config = { + .have_differential_receiver = dif_bool_to_toggle(en_diff_rcvr), + .use_tx_d_se0 = dif_bool_to_toggle(tx_use_d_se0), + .single_bit_eop = kDifToggleDisabled, + .pin_flip = dif_bool_to_toggle(pinflip), + .clock_sync_signals = kDifToggleEnabled, + }; + CHECK_DIF_OK(dif_usbdev_configure(ctx->dev, ctx->buffer_pool, config)); + + // Set up context + for (int i = 0; i < USBDEV_NUM_ENDPOINTS; i++) { + usb_testutils_endpoint_setup(ctx, i, kUsbdevOutDisabled, NULL, NULL, NULL, + NULL, NULL); + } + + // All about polling... + CHECK_DIF_OK(dif_usbdev_irq_disable_all(ctx->dev, NULL)); + + // Provide buffers for any packet reception + CHECK_DIF_OK(dif_usbdev_fill_available_fifo(ctx->dev, ctx->buffer_pool)); + + // Preemptively enable SETUP reception on endpoint zero for the + // Default Control Pipe; all other settings for that endpoint will be applied + // once the callback handlers are registered by a call to _endpoint_setup() + CHECK_DIF_OK( + dif_usbdev_endpoint_setup_enable(ctx->dev, 0, kDifToggleEnabled)); +} + +void usb_testutils_fin(usb_testutils_ctx_t *ctx) { + // Remove the endpoints in reverse order so that Endpoint Zero goes down last + for (int ep = USBDEV_NUM_ENDPOINTS - 1; ep >= 0; ep--) { + usb_testutils_endpoint_remove(ctx, ep); + } + + // Disconnect from the bus + CHECK_DIF_OK(dif_usbdev_interface_enable(ctx->dev, kDifToggleDisabled)); +} + +// `extern` declarations to give the inline functions in the +// corresponding header a link location. + +extern int usb_testutils_halted(usb_testutils_ctx_t *ctx, + dif_usbdev_endpoint_id_t endpoint);
diff --git a/sw/device/lib/testing/usb_testutils.h b/sw/device/lib/testing/usb_testutils.h new file mode 100644 index 0000000..6dcfc96 --- /dev/null +++ b/sw/device/lib/testing/usb_testutils.h
@@ -0,0 +1,317 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#ifndef OPENTITAN_SW_DEVICE_LIB_TESTING_USB_TESTUTILS_H_ +#define OPENTITAN_SW_DEVICE_LIB_TESTING_USB_TESTUTILS_H_ + +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> + +#include "sw/device/lib/dif/dif_usbdev.h" +#include "usb_testutils_diags.h" + +// Result codes to rx/tx callback handlers +typedef enum { + /** + * Successful completion. + */ + kUsbTestutilsXfrResultOk = 0u, + /** + * Failed to transfer because of internal error, + * eg. buffer exhaustion. + */ + kUsbTestutilsXfrResultFailed = 1u, + /** + * Link reset interrupted transfer. + */ + kUsbTestutilsXfrResultLinkReset = 2u, + /** + * Canceled by suspend, endpoint removal or finalization. + */ + kUsbTestutilsXfrResultCanceled = 3u, +} usb_testutils_xfr_result_t; + +// Flags affecting the transfer of larger data buffers +typedef enum { + kUsbTestutilsXfrMaxPacketMask = 0x7fu, // Max packet size [0,0x40U] + /** + * Explicitly specify the maximum packet size; otherwise the device default + * of USBDEV_MAX_PACKET_SIZE shall be assumed. + */ + kUsbTestutilsXfrMaxPacketSupplied = 0x100u, + /** + * Employ double-buffering to minimize the response time to notification of + * each packet transmission. This does require that two device packet buffers + * be available for use, but it increases the transfer rate. + */ + kUsbTestutilsXfrDoubleBuffered = 0x200u, + /** + * Emit/Expect Zero Length Packet as termination of data stage in the event + * that the final packet of the transfer is maximum length + */ + kUsbTestutilsXfrEmployZLP = 0x400u, +} usb_testutils_xfr_flags_t; + +// In-progress larger buffer transfer to/from host +typedef struct usb_testutils_transfer { + /** + * Start of buffer for transfer + */ + const uint8_t *buffer; + /** + * Total number of bytes to be transferred to/from buffer + */ + uint32_t length; + /** + * Byte offset of the _next_ packet to be transferred + */ + uint32_t offset; + /** + * Flags modifying the transfer + */ + usb_testutils_xfr_flags_t flags; + /** + * Indicates that the last packet of the transfer has been reached; + * if 'next_valid' is true, then 'next_part' holds the last packet, already + * prepared for sending; if next_valid is false, then the last packet has + * already been supplied to usbdev and we're just awaiting the 'pkt_sent' + * interrupt. + */ + bool last; + /** + * The next part has been prepared and is ready to send to usbdev + */ + bool next_valid; + /** + * When sending IN data to the host, we may employ double-buffering and keep + * an additional buffer ready to be sent as soon as we're notified of the + * transfer of its predecessor + */ + dif_usbdev_buffer_t next_part; +} usb_testutils_transfer_t; + +typedef struct usb_testutils_ctx usb_testutils_ctx_t; + +struct usb_testutils_ctx { + dif_usbdev_t *dev; + dif_usbdev_buffer_pool_t *buffer_pool; + int flushed; + /** + * Have we received an indication of USB activity? + */ + bool got_frame; + /** + * Most recent bus frame number received from host + */ + uint16_t frame; + + /** + * IN endpoints + */ + struct { + /** + * Opaque context handle for callback functions + */ + void *ep_ctx; + /** + * Callback for transmission of IN packet + */ + void (*tx_done_callback)(void *, usb_testutils_xfr_result_t); + /** + * Callback for periodically flushing IN data to host + */ + void (*flush)(void *); + /** + * Callback for link reset + */ + void (*reset)(void *); + /** + * Current in-progress transfer, if any + */ + usb_testutils_transfer_t transfer; + } in[USBDEV_NUM_ENDPOINTS]; + + /** + * OUT endpoints + */ + struct { + /** + * Opaque context handle for callback functions + */ + void *ep_ctx; + /** + * Callback for reception of IN packet + */ + void (*rx_callback)(void *, dif_usbdev_rx_packet_info_t, + dif_usbdev_buffer_t); + /** + * Callback for link reset + */ + void (*reset)(void *); + } out[USBDEV_NUM_ENDPOINTS]; +}; + +typedef enum usb_testutils_out_transfer_mode { + /** + * The endpoint does not support OUT transactions. + */ + kUsbdevOutDisabled = 0, + /** + * Software does NOT need to call usb_testutils_clear_out_nak() after every + * received transaction. If software takes no action, usbdev will allow an + * endpoint's transactions to proceed as long as a buffer is available. + */ + kUsbdevOutStream = 1, + /** + * Software must call usb_testutils_clear_out_nak() after every received + * transaction to re-enable packet reception. This gives software time to + * respond with the appropriate handshake when it's ready. + */ + kUsbdevOutMessage = 2, +} usb_testutils_out_transfer_mode_t; + +/** + * Call to set up IN endpoint. + * + * @param ctx usb test utils context pointer + * @param ep endpoint number + * @param ep_ctx context pointer for callee + * @param tx_done(void *ep_ctx) callback once send has been Acked + * @param flush(void *ep_ctx) called every 16ms based USB host timebase + * @param reset(void *ep_ctx) called when an USB link reset is detected + */ +void usb_testutils_in_endpoint_setup( + usb_testutils_ctx_t *ctx, uint8_t ep, void *ep_ctx, + void (*tx_done)(void *, usb_testutils_xfr_result_t), void (*flush)(void *), + void (*reset)(void *)); + +/** + * Call to set up OUT endpoint. + * + * @param ctx usb test utils context pointer + * @param ep endpoint number + * @param out_mode the transfer mode for OUT transactions + * @param ep_ctx context pointer for callee + * @param rx(void *ep_ctx, usbbufid_t buf, int size, int setup) + called when a packet is received + * @param reset(void *ep_ctx) called when an USB link reset is detected + */ +void usb_testutils_out_endpoint_setup( + usb_testutils_ctx_t *ctx, uint8_t ep, + usb_testutils_out_transfer_mode_t out_mode, void *ep_ctx, + void (*rx)(void *, dif_usbdev_rx_packet_info_t, dif_usbdev_buffer_t), + void (*reset)(void *)); + +/** + * Call to set up a pair of IN and OUT endpoints. + * + * @param ctx usb test utils context pointer + * @param ep endpoint number + * @param out_mode the transfer mode for OUT transactions + * @param ep_ctx context pointer for callee + * @param tx_done(void *ep_ctx) callback once send has been Acked + * @param rx(void *ep_ctx, usbbufid_t buf, int size, int setup) + called when a packet is received + * @param flush(void *ep_ctx) called every 16ms based USB host timebase + * @param reset(void *ep_ctx) called when an USB link reset is detected + */ +void usb_testutils_endpoint_setup( + usb_testutils_ctx_t *ctx, uint8_t ep, + usb_testutils_out_transfer_mode_t out_mode, void *ep_ctx, + void (*tx_done)(void *, usb_testutils_xfr_result_t), + void (*rx)(void *, dif_usbdev_rx_packet_info_t, dif_usbdev_buffer_t), + void (*flush)(void *), void (*reset)(void *)); + +/** + * Remove an IN endpoint. + * + * @param ctx usb test utils context pointer + * @param ep endpoint number + */ +void usb_testutils_in_endpoint_remove(usb_testutils_ctx_t *ctx, uint8_t ep); + +/** + * Remove an OUT endpoint. + * + * @param ctx usb test utils context pointer + * @param ep endpoint number + */ +void usb_testutils_out_endpoint_remove(usb_testutils_ctx_t *ctx, uint8_t ep); + +/** + * Remove a pair of IN and OUT endpoints + * + * @param ctx usb test utils context pointer + * @param ep endpoint number + */ +void usb_testutils_endpoint_remove(usb_testutils_ctx_t *ctx, uint8_t ep); + +/** + * Returns an indication of whether an endpoint is currently halted because + * of the occurrence of an error. + * + * @param ctx usb test utils context pointer + * @param ep endpoint number + * @return true iff the endpoint is halted as a result of an error condition + */ +inline bool usb_testutils_endpoint_halted(usb_testutils_ctx_t *ctx, + dif_usbdev_endpoint_id_t endpoint); + +/** + * Initialize the usbdev interface + * + * Does not connect the device, since the default endpoint is not yet enabled. + * See usb_testutils_connect(). + * + * @param ctx uninitialized usb test utils context pointer + * @param pinflip boolean to indicate if PHY should be configured for D+/D- flip + * @param en_diff_rcvr boolean to indicate if PHY should enable an external + * differential receiver, activating the single-ended D + * input + * @param tx_use_d_se0 boolean to indicate if PHY uses D/SE0 for TX instead of + * Dp/Dn + */ +void usb_testutils_init(usb_testutils_ctx_t *ctx, bool pinflip, + bool en_diff_rcvr, bool tx_use_d_se0); + +/** + * Send a larger data transfer from the given endpoint + * + * The usb_testutils layer will, if necessary, break this transfer into multiple + * packet buffers to be transferred in turn across the USB. The caller shall be + * notified via the tx_done_callback handler of successful completion of the + * entire transfer, or failure, and the caller must guarantee the availability + * of the supplied data throughout the operation. + * + * @param ctx usb test utils context pointer + * @param ep endpoint number + * @param data buffer of data to be transferred + * @param length number of bytes to be transferred + * @param flags flags modifying the transfer operation + * @return true iff the data has been accepted for transmission + */ +bool 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); + +/** + * Call regularly to poll the usbdev interface + * + * @param ctx usb test utils context pointer + */ +void usb_testutils_poll(usb_testutils_ctx_t *ctx); + +/** + * Finalize the usbdev interface + * + * Removes all endpoint handlers and disconnects the device from the USB. + * This should be used only if the USB device is no longer required, or if it is + * required to be restarted with, for example, a different bus configuration. + * + * @param ctx initialized usb test utils context pointer + */ +void usb_testutils_fin(usb_testutils_ctx_t *ctx); + +#endif // OPENTITAN_SW_DEVICE_LIB_TESTING_USB_TESTUTILS_H_
diff --git a/sw/device/lib/testing/usb_testutils_controlep.c b/sw/device/lib/testing/usb_testutils_controlep.c new file mode 100644 index 0000000..513c0da --- /dev/null +++ b/sw/device/lib/testing/usb_testutils_controlep.c
@@ -0,0 +1,413 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#include "sw/device/lib/testing/usb_testutils_controlep.h" + +#include "sw/device/lib/dif/dif_usbdev.h" +#include "sw/device/lib/testing/test_framework/check.h" +#include "sw/device/lib/testing/usb_testutils.h" + +// Device descriptor +static uint8_t dev_dscr[] = { + 18, // bLength + 1, // bDescriptorType + 0x00, // bcdUSB[0] + 0x02, // bcdUSB[1] + 0x00, // bDeviceClass (defined at interface level) + 0x00, // bDeviceSubClass + 0x00, // bDeviceProtocol + 64, // bMaxPacketSize0 + + 0xd1, // idVendor[0] 0x18d1 Google Inc. + 0x18, // idVendor[1] + 0x3a, // idProduct[0] lowRISC generic FS USB + 0x50, // idProduct[1] (allocated by Google) + + 0, // bcdDevice[0] + 0x1, // bcdDevice[1] + 0, // iManufacturer + 0, // iProduct + 0, // iSerialNumber + 1 // bNumConfigurations +}; + +// SETUP requests +typedef enum usb_setup_req { + kUsbSetupReqGetStatus = 0, + kUsbSetupReqClearFeature = 1, + kUsbSetupReqSetFeature = 3, + kUsbSetupReqSetAddress = 5, + kUsbSetupReqGetDescriptor = 6, + kUsbSetupReqSetDescriptor = 7, + kUsbSetupReqGetConfiguration = 8, + kUsbSetupReqSetConfiguration = 9, + kUsbSetupReqGetInterface = 10, + kUsbSetupReqSetInterface = 11, + kUsbSetupReqSynchFrame = 12 +} usb_setup_req_t; + +// Vendor-specific requests defined by our device/test framework +typedef enum vendor_setup_req { + kVendorSetupReqTestConfig = 0x7C, + kVendorSetupReqTestStatus = 0x7E +} vendor_setup_req_t; + +typedef enum usb_req_type { // bmRequestType + kUsbReqTypeRecipientMask = 0x1f, + kUsbReqTypeDevice = 0, + kUsbReqTypeInterface = 1, + kUsbReqTypeEndpoint = 2, + kUsbReqTypeOther = 3, + kUsbReqTypeTypeMask = 0x60, + kUsbReqTypeStandard = 0, + kUsbReqTypeClass = 0x20, + kUsbReqTypeVendor = 0x40, + kUsbReqTypeReserved = 0x60, + kUsbReqTypeDirMask = 0x80, + kUsbReqTypeDirH2D = 0x00, + kUsbReqTypeDirD2H = 0x80, +} usb_req_type_t; + +typedef enum usb_desc_type { // Descriptor type (wValue hi) + kUsbDescTypeDevice = 1, + kUsbDescTypeConfiguration, + kUsbDescTypeString, + kUsbDescTypeInterface, + kUsbDescTypeEndpoint, + kUsbDescTypeDeviceQualifier, + kUsbDescTypeOtherSpeedConfiguration, + kUsbDescTypeInterfacePower, +} usb_desc_type_t; + +typedef enum usb_feature_req { + kUsbFeatureEndpointHalt = 0, // recipient is endpoint + kUsbFeatureDeviceRemoteWakeup = 1, // recipient is device + kUsbFeatureTestMode = 2, // recipient is device + kUsbFeatureBHnpEnable = 3, // recipient is device only if OTG + kUsbFeatureAHnpSupport = 4, // recipient is device only if OTG + kUsbFeatureAAltHnpSupport = 5 // recipient is device only if OTG +} usb_feature_req_t; + +typedef enum usb_status { + kUsbStatusSelfPowered = 1, // Device status request + kUsbStatusRemWake = 2, // Device status request + kUsbStatusHalted = 1 // Endpoint status request +} usb_status_t; + +static usb_testutils_ctstate_t setup_req(usb_testutils_controlep_ctx_t *ctctx, + usb_testutils_ctx_t *ctx, + int bmRequestType, int bRequest, + int wValue, int wIndex, int wLength) { + size_t len; + uint32_t stat; + int zero, type; + size_t bytes_written; + // Endpoint for SetFeature/ClearFeature/GetStatus requests + dif_usbdev_endpoint_id_t endpoint = { + .number = (uint8_t)wIndex, + .direction = ((bmRequestType & 0x80U) != 0U), + }; + dif_usbdev_buffer_t buffer; + CHECK_DIF_OK(dif_usbdev_buffer_request(ctx->dev, ctx->buffer_pool, &buffer)); + switch (bRequest) { + case kUsbSetupReqGetDescriptor: + if ((wValue & 0xff00) == 0x100) { + // Device descriptor + len = sizeof(dev_dscr); + if (wLength < len) { + len = wLength; + } + CHECK_DIF_OK(dif_usbdev_buffer_write(ctx->dev, &buffer, dev_dscr, len, + &bytes_written)); + CHECK_DIF_OK(dif_usbdev_send(ctx->dev, ctctx->ep, &buffer)); + return kUsbTestutilsCtWaitIn; + } else if ((wValue & 0xff00) == 0x200) { + usb_testutils_xfr_flags_t flags = kUsbTestutilsXfrDoubleBuffered; + + // Configuration descriptor + len = ctctx->cfg_dscr_len; + if (wLength < len) { + len = wLength; + } else if (wLength > len) { + // Since we're not sending as much as requested, we may need to use + // a Zero Length Packet to mark the end of the data stage + flags |= kUsbTestutilsXfrEmployZLP; + } + + if (len >= USBDEV_MAX_PACKET_SIZE) { + CHECK_DIF_OK( + dif_usbdev_buffer_return(ctx->dev, ctx->buffer_pool, &buffer)); + + if (!usb_testutils_transfer_send(ctx, 0U, ctctx->cfg_dscr, len, + flags)) { + return kUsbTestutilsCtError; + } + } else { + CHECK_DIF_OK(dif_usbdev_buffer_write( + ctx->dev, &buffer, ctctx->cfg_dscr, len, &bytes_written)); + CHECK_DIF_OK(dif_usbdev_send(ctx->dev, ctctx->ep, &buffer)); + } + return kUsbTestutilsCtWaitIn; + } + return kUsbTestutilsCtError; // unknown + + case kUsbSetupReqSetAddress: + TRC_S("SA"); + ctctx->new_dev = wValue & 0x7f; + // send zero length packet for status phase + CHECK_DIF_OK(dif_usbdev_send(ctx->dev, ctctx->ep, &buffer)); + return kUsbTestutilsCtAddrStatIn; + + case kUsbSetupReqSetConfiguration: + TRC_S("SC"); + // only ever expect this to be 1 since there is one config descriptor + ctctx->usb_config = wValue; + // send zero length packet for status phase + CHECK_DIF_OK(dif_usbdev_send(ctx->dev, ctctx->ep, &buffer)); + if (wValue) { + ctctx->device_state = kUsbTestutilsDeviceConfigured; + } else { + // Device deconfigured + ctctx->device_state = kUsbTestutilsDeviceAddressed; + } + return kUsbTestutilsCtStatIn; + + case kUsbSetupReqGetConfiguration: + len = sizeof(ctctx->usb_config); + if (wLength < len) { + len = wLength; + } + // return the value that was set + CHECK_DIF_OK(dif_usbdev_buffer_write( + ctx->dev, &buffer, &ctctx->usb_config, len, &bytes_written)); + CHECK_DIF_OK(dif_usbdev_send(ctx->dev, ctctx->ep, &buffer)); + return kUsbTestutilsCtWaitIn; + + case kUsbSetupReqSetFeature: + if (wValue == kUsbFeatureEndpointHalt) { + CHECK_DIF_OK(dif_usbdev_endpoint_stall_enable(ctx->dev, endpoint, + kDifToggleEnabled)); + // send zero length packet for status phase + CHECK_DIF_OK(dif_usbdev_send(ctx->dev, ctctx->ep, &buffer)); + return kUsbTestutilsCtStatIn; + } + return kUsbTestutilsCtError; // unknown + + case kUsbSetupReqClearFeature: + if (wValue == kUsbFeatureEndpointHalt) { + CHECK_DIF_OK(dif_usbdev_endpoint_stall_enable(ctx->dev, endpoint, + kDifToggleDisabled)); + // send zero length packet for status phase + CHECK_DIF_OK(dif_usbdev_send(ctx->dev, ctctx->ep, &buffer)); + } + return kUsbTestutilsCtStatIn; + + case kUsbSetupReqGetStatus: + len = 2; + type = bmRequestType & kUsbReqTypeRecipientMask; + if (type == kUsbReqTypeDevice) { + stat = kUsbStatusSelfPowered; + } else if (type == kUsbReqTypeEndpoint) { + bool halted; + CHECK_DIF_OK( + dif_usbdev_endpoint_stall_get(ctx->dev, endpoint, &halted)); + stat = halted ? kUsbStatusHalted : 0; + } else { + stat = 0; + } + if (wLength < len) { + len = wLength; + } + // return the value that was set + CHECK_DIF_OK(dif_usbdev_buffer_write(ctx->dev, &buffer, (uint8_t *)&stat, + len, &bytes_written)); + CHECK_DIF_OK(dif_usbdev_send(ctx->dev, ctctx->ep, &buffer)); + return kUsbTestutilsCtWaitIn; + + case kUsbSetupReqSetInterface: + // Don't support alternate interfaces, so just ignore + // send zero length packet for status phase + CHECK_DIF_OK(dif_usbdev_send(ctx->dev, ctctx->ep, &buffer)); + return kUsbTestutilsCtStatIn; + + case kUsbSetupReqGetInterface: + zero = 0; + len = 1; + if (wLength < len) { + len = wLength; + } + // Don't support interface, so return zero + CHECK_DIF_OK(dif_usbdev_buffer_write(ctx->dev, &buffer, (uint8_t *)&zero, + len, &bytes_written)); + CHECK_DIF_OK(dif_usbdev_send(ctx->dev, ctctx->ep, &buffer)); + return kUsbTestutilsCtWaitIn; + + case kUsbSetupReqSynchFrame: + zero = 0; + len = 2; + if (wLength < len) { + len = wLength; + } + // Don't support synch_frame so return zero + CHECK_DIF_OK(dif_usbdev_buffer_write(ctx->dev, &buffer, (uint8_t *)&zero, + len, &bytes_written)); + CHECK_DIF_OK(dif_usbdev_send(ctx->dev, ctctx->ep, &buffer)); + return kUsbTestutilsCtWaitIn; + + default: + // We implement a couple of bespoke, vendor-defined Setup requests to + // allow the DPI model to access the test configuration (Control Read) and + // to report the test status (Control Write) + if ((bmRequestType & kUsbReqTypeTypeMask) == kUsbReqTypeVendor && + ctctx->test_dscr) { + switch ((vendor_setup_req_t)bRequest) { + case kVendorSetupReqTestConfig: { + TRC_S("TC"); + // Test config descriptor + len = ctctx->test_dscr_len; + if (wLength < len) { + len = wLength; + } + CHECK_DIF_OK(dif_usbdev_buffer_write( + ctx->dev, &buffer, ctctx->test_dscr, len, &bytes_written)); + CHECK_DIF_OK(dif_usbdev_send(ctx->dev, ctctx->ep, &buffer)); + return kUsbTestutilsCtWaitIn; + } break; + case kVendorSetupReqTestStatus: { + // TODO - pass the received test status to the OTTF directly? + } break; + } + } + return kUsbTestutilsCtError; + } + return kUsbTestutilsCtError; +} + +static void ctrl_tx_done(void *ctctx_v, usb_testutils_xfr_result_t result) { + usb_testutils_controlep_ctx_t *ctctx = + (usb_testutils_controlep_ctx_t *)ctctx_v; + usb_testutils_ctx_t *ctx = ctctx->ctx; + TRC_C('A' + ctctx->ctrlstate); + switch (ctctx->ctrlstate) { + case kUsbTestutilsCtAddrStatIn: + // Now the status was sent on device 0 can switch to new device ID + CHECK_DIF_OK(dif_usbdev_address_set(ctx->dev, ctctx->new_dev)); + TRC_I(ctctx->new_dev, 8); + ctctx->ctrlstate = kUsbTestutilsCtIdle; + // We now have a device address on the USB + ctctx->device_state = kUsbTestutilsDeviceAddressed; + return; + case kUsbTestutilsCtStatIn: + ctctx->ctrlstate = kUsbTestutilsCtIdle; + return; + case kUsbTestutilsCtWaitIn: + ctctx->ctrlstate = kUsbTestutilsCtStatOut; + return; + + default: + break; + } + TRC_S("USB: unexpected IN "); + TRC_I((ctctx->ctrlstate << 24), 32); +} + +static void ctrl_rx(void *ctctx_v, dif_usbdev_rx_packet_info_t packet_info, + dif_usbdev_buffer_t buffer) { + usb_testutils_controlep_ctx_t *ctctx = + (usb_testutils_controlep_ctx_t *)ctctx_v; + usb_testutils_ctx_t *ctx = ctctx->ctx; + CHECK_DIF_OK(dif_usbdev_endpoint_out_enable(ctx->dev, /*endpoint=*/0, + kDifToggleEnabled)); + + TRC_C('0' + ctctx->ctrlstate); + size_t bytes_written; + // TODO: Should check for canceled IN transactions due to receiving a SETUP + // packet. + switch (ctctx->ctrlstate) { + case kUsbTestutilsCtIdle: + // Waiting to be set up + if (packet_info.is_setup && (packet_info.length == 8)) { + alignas(uint32_t) uint8_t bp[8]; + CHECK_DIF_OK(dif_usbdev_buffer_read(ctx->dev, ctx->buffer_pool, &buffer, + bp, sizeof(bp), &bytes_written)); + int bmRequestType = bp[0]; + int bRequest = bp[1]; + int wValue = (bp[3] << 8) | bp[2]; + int wIndex = (bp[5] << 8) | bp[4]; + int wLength = (bp[7] << 8) | bp[6]; + TRC_C('0' + bRequest); + + ctctx->ctrlstate = setup_req(ctctx, ctx, bmRequestType, bRequest, + wValue, wIndex, wLength); + if (ctctx->ctrlstate != kUsbTestutilsCtError) { + return; + } + + TRC_C(':'); + for (int i = 0; i < packet_info.length; i++) { + TRC_I(bp[i], 8); + } + } + break; + + case kUsbTestutilsCtStatOut: + // Have sent some data, waiting STATUS stage + if (!packet_info.is_setup && (packet_info.length == 0)) { + CHECK_DIF_OK( + dif_usbdev_buffer_return(ctx->dev, ctx->buffer_pool, &buffer)); + ctctx->ctrlstate = kUsbTestutilsCtIdle; + return; + } + // anything else is unexpected + break; + + default: + // Error + break; + } + dif_usbdev_endpoint_id_t endpoint = { + .number = 0, + .direction = USBDEV_ENDPOINT_DIR_IN, + }; + // Enable responding with STALL. Will be cleared by the HW upon next SETUP. + CHECK_DIF_OK( + dif_usbdev_endpoint_stall_enable(ctx->dev, endpoint, kDifToggleEnabled)); + endpoint.direction = USBDEV_ENDPOINT_DIR_OUT; + CHECK_DIF_OK( + dif_usbdev_endpoint_stall_enable(ctx->dev, endpoint, kDifToggleEnabled)); + TRC_S("USB: unCT "); + TRC_I((ctctx->ctrlstate << 24) | ((int)packet_info.is_setup << 16) | + packet_info.length, + 32); + if (buffer.type != kDifUsbdevBufferTypeStale) { + // Return the unused buffer. + CHECK_DIF_OK(dif_usbdev_buffer_return(ctx->dev, ctx->buffer_pool, &buffer)); + } + ctctx->ctrlstate = kUsbTestutilsCtIdle; +} + +// Callback for the USB link reset +static void ctrl_reset(void *ctctx_v) { + usb_testutils_controlep_ctx_t *ctctx = + (usb_testutils_controlep_ctx_t *)ctctx_v; + ctctx->ctrlstate = kUsbTestutilsCtIdle; +} + +void usb_testutils_controlep_init(usb_testutils_controlep_ctx_t *ctctx, + usb_testutils_ctx_t *ctx, int ep, + const uint8_t *cfg_dscr, size_t cfg_dscr_len, + const uint8_t *test_dscr, + size_t test_dscr_len) { + ctctx->ctx = ctx; + usb_testutils_endpoint_setup(ctx, ep, kUsbdevOutMessage, ctctx, ctrl_tx_done, + ctrl_rx, NULL, ctrl_reset); + ctctx->ep = ep; + ctctx->ctrlstate = kUsbTestutilsCtIdle; + ctctx->cfg_dscr = cfg_dscr; + ctctx->cfg_dscr_len = cfg_dscr_len; + ctctx->test_dscr = test_dscr; + ctctx->test_dscr_len = test_dscr_len; + CHECK_DIF_OK(dif_usbdev_interface_enable(ctx->dev, kDifToggleEnabled)); + ctctx->device_state = kUsbTestutilsDeviceDefault; +}
diff --git a/sw/device/lib/testing/usb_testutils_controlep.h b/sw/device/lib/testing/usb_testutils_controlep.h new file mode 100644 index 0000000..9cbd378 --- /dev/null +++ b/sw/device/lib/testing/usb_testutils_controlep.h
@@ -0,0 +1,147 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#ifndef OPENTITAN_SW_DEVICE_LIB_TESTING_USB_TESTUTILS_CONTROLEP_H_ +#define OPENTITAN_SW_DEVICE_LIB_TESTING_USB_TESTUTILS_CONTROLEP_H_ + +#include <stddef.h> +#include <stdint.h> + +#include "sw/device/lib/testing/usb_testutils.h" + +typedef enum usb_testutils_ctstate { + kUsbTestutilsCtIdle, + kUsbTestutilsCtWaitIn, // Queued IN data stage, waiting ack + kUsbTestutilsCtStatOut, // Waiting for OUT status stage + kUsbTestutilsCtAddrStatIn, // Queued status stage, waiting ack. + // After which, set dev_addr + kUsbTestutilsCtStatIn, // Queued status stage, waiting ack + kUsbTestutilsCtError // Something bad +} usb_testutils_ctstate_t; + +typedef enum usb_testutils_device_state { + kUsbTestutilsDeviceAttached, + kUsbTestutilsDevicePowered, + kUsbTestutilsDeviceDefault, + kUsbTestutilsDeviceAddressed, + kUsbTestutilsDeviceConfigured, + kUsbTestutilsDeviceSuspended, +} usb_testutils_device_state_t; + +typedef struct usb_testutils_controlep_ctx { + usb_testutils_ctx_t *ctx; + int ep; + usb_testutils_ctstate_t ctrlstate; + usb_testutils_device_state_t device_state; + uint32_t new_dev; + uint8_t usb_config; + /** + * USB configuration descriptor + */ + const uint8_t *cfg_dscr; + size_t cfg_dscr_len; + /** + * Optional test descriptor, or NULL + */ + const uint8_t *test_dscr; + size_t test_dscr_len; +} usb_testutils_controlep_ctx_t; + +/** + * Initialize control endpoint + * + * @param ctctx uninitialized context for this instance + * @param ctx initialized context for usbdev driver + * @param ep endpoint (if this is other than 0 make sure you know why) + * @param cfg_dscr configuration descriptor for the device + * @param cfg_dscr_len length of cfg_dscr + * @param test_dscr optional test descriptor, or NULL + * @param test_dscr_len length of test_dscr + */ +void usb_testutils_controlep_init(usb_testutils_controlep_ctx_t *ctctx, + usb_testutils_ctx_t *ctx, int ep, + const uint8_t *cfg_dscr, size_t cfg_dscr_len, + const uint8_t *test_dscr, + size_t test_dscr_len); + +/***********************************************************************/ +/* Below this point are macros used to construct the USB configuration */ +/* descriptor. Use them to initialize a uint8_t array for cfg_dscr */ + +#define USB_CFG_DSCR_LEN 9 +#define USB_CFG_DSCR_HEAD(total_len, nint) \ + /* This is the actual configuration descriptor */ \ + USB_CFG_DSCR_LEN, /* bLength */ \ + 2, /* bDescriptorType */ \ + (total_len)&0xff, /* wTotalLength[0] */ \ + (total_len) >> 8, /* wTotalLength[1] */ \ + (nint), /* bNumInterfaces */ \ + 1, /* bConfigurationValue */ \ + 0, /* iConfiguration */ \ + 0xC0, /* bmAttributes: must-be-one, self-powered */ \ + 50 /* bMaxPower */ /* MUST be followed \ + by (nint) \ + Interface + \ + Endpoint \ + Descriptors */ + +// KEEP BLANK LINE ABOVE, it is in the macro! + +#define USB_INTERFACE_DSCR_LEN 9 +#define VEND_INTERFACE_DSCR(inum, nep, subclass, protocol) \ + /* interface descriptor, USB spec 9.6.5, page 267-269, Table 9-12 */ \ + USB_INTERFACE_DSCR_LEN, /* bLength */ \ + 4, /* bDescriptorType */ \ + (inum), /* bInterfaceNumber */ \ + 0, /* bAlternateSetting */ \ + (nep), /* bNumEndpoints */ \ + 0xff, /* bInterfaceClass (Vendor Specific) */ \ + (subclass), /* bInterfaceSubClass */ \ + (protocol), /* bInterfaceProtocol */ \ + 0 /* iInterface */ /* MUST be followed by \ + (nep) Endpoint \ + Descriptors */ + +// KEEP BLANK LINE ABOVE, it is in the macro! + +#define USB_EP_DSCR_LEN 7 +#define USB_EP_DSCR(in, ep, attr, maxsize, interval) \ + /* endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 */ \ + USB_EP_DSCR_LEN, /* bLength */ \ + 5, /* bDescriptorType */ \ + (ep) | (((in) << 7) & 0x80), /* bEndpointAddress, top bit set for IN */ \ + attr, /* bmAttributes */ \ + (maxsize)&0xff, /* wMaxPacketSize[0] */ \ + (maxsize) >> 8, /* wMaxPacketSize[1] */ \ + (interval) /* bInterval */ + +// KEEP BLANK LINE ABOVE, it is in the macro! +#define USB_BULK_EP_DSCR(in, ep, maxsize, interval) \ + /* endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 */ \ + USB_EP_DSCR_LEN, /* bLength */ \ + 5, /* bDescriptorType */ \ + (ep) | (((in) << 7) & 0x80), /* bEndpointAddress, top bit set for IN */ \ + 0x02, /* bmAttributes (0x02=bulk, data) */ \ + (maxsize)&0xff, /* wMaxPacketSize[0] */ \ + (maxsize) >> 8, /* wMaxPacketSize[1] */ \ + (interval) /* bInterval */ + +// KEEP BLANK LINE ABOVE, it is in the macro! + +/***********************************************************************/ +/* Below this point are macros used to construct the test descriptor */ +/* Use them to initialize a uint8_t array for test_dscr */ +#define USB_TESTUTILS_TEST_DSCR_LEN 0x10u +#define USB_TESTUTILS_TEST_DSCR(num, arg0, arg1, arg2, arg3) \ + 0x7e, 0x57, 0xc0, 0xf1u, /* Header signature */ \ + (USB_TESTUTILS_TEST_DSCR_LEN)&0xff, /* Descriptor length[0] */ \ + (USB_TESTUTILS_TEST_DSCR_LEN) >> 8, /* Descriptor length[1] */ \ + (num)&0xff, /* Test number[0] */ \ + (num) >> 8, /* Test number[1] */ \ + (arg0), (arg1), (arg2), (arg3), /* Test-specific arugments */ \ + 0x1fu, 0x0cu, 0x75, 0xe7u /* Tail signature */ + +// KEEP BLANK LINE ABOVE, it is in the macro! + +#endif // OPENTITAN_SW_DEVICE_LIB_TESTING_USB_TESTUTILS_CONTROLEP_H_
diff --git a/sw/device/lib/testing/usb_testutils_diags.h b/sw/device/lib/testing/usb_testutils_diags.h new file mode 100644 index 0000000..9d2e375 --- /dev/null +++ b/sw/device/lib/testing/usb_testutils_diags.h
@@ -0,0 +1,29 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#ifndef OPENTITAN_SW_DEVICE_LIB_TESTING_USB_TESTUTILS_DIAGS_H_ +#define OPENTITAN_SW_DEVICE_LIB_TESTING_USB_TESTUTILS_DIAGS_H_ +// Diagnostic, testing and performance measurements utilities for verification +// of usbdev and development of the usb_testutils support software; the +// requirements of this software are peculiar in that the USBDPI model used in +// top-level requires packet responses very promptly, so the introduction of +// logging/tracing code can substantially alter behavior and cause malfunction + +// Used for tracing what is going on. This may impact timing which is critical +// when simulating with the USB DPI module. +#define USBUTILS_ENABLE_TRC 0 + +#if USBUTILS_ENABLE_TRC +// May be useful on FPGA CW310 +#include "sw/device/lib/runtime/log.h" +#define TRC_S(s) LOG_INFO("%s", s) +#define TRC_I(i, b) LOG_INFO("0x%x", i) +#define TRC_C(c) LOG_INFO("%c", c) +#else +#define TRC_S(s) +#define TRC_I(i, b) +#define TRC_C(c) +#endif + +#endif // OPENTITAN_SW_DEVICE_LIB_TESTING_USB_TESTUTILS_DIAGS_H_
diff --git a/sw/device/lib/testing/usb_testutils_simpleserial.c b/sw/device/lib/testing/usb_testutils_simpleserial.c new file mode 100644 index 0000000..05a799d --- /dev/null +++ b/sw/device/lib/testing/usb_testutils_simpleserial.c
@@ -0,0 +1,76 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#include "sw/device/lib/testing/usb_testutils_simpleserial.h" + +#include "sw/device/lib/dif/dif_usbdev.h" +#include "sw/device/lib/testing/test_framework/check.h" +#include "sw/device/lib/testing/usb_testutils.h" + +#define MAX_GATHER 16 + +static void ss_rx(void *ssctx_v, dif_usbdev_rx_packet_info_t packet_info, + dif_usbdev_buffer_t buffer) { + usb_testutils_ss_ctx_t *ssctx = (usb_testutils_ss_ctx_t *)ssctx_v; + usb_testutils_ctx_t *ctx = ssctx->ctx; + + while (packet_info.length--) { + uint8_t data; + size_t bytes_written; + CHECK_DIF_OK(dif_usbdev_buffer_read(ctx->dev, ctx->buffer_pool, &buffer, + &data, sizeof(data), &bytes_written)); + ssctx->got_byte(data); + } +} + +// Called periodically by the main loop to ensure characters don't +// stick around too long +static void ss_flush(void *ssctx_v) { + usb_testutils_ss_ctx_t *ssctx = (usb_testutils_ss_ctx_t *)ssctx_v; + usb_testutils_ctx_t *ctx = ssctx->ctx; + if (ssctx->cur_cpos <= 0) { + return; + } + if ((ssctx->cur_cpos & 0x3) != 0) { + size_t bytes_written; + CHECK_DIF_OK(dif_usbdev_buffer_write(ctx->dev, &ssctx->cur_buf, + ssctx->chold.data_b, /*src_len=*/4, + &bytes_written)); + } + CHECK_DIF_OK(dif_usbdev_send(ctx->dev, ssctx->ep, &ssctx->cur_buf)); + ssctx->cur_cpos = -1; // given it to the hardware +} + +// Simple send byte will gather data for a while and send +void usb_testutils_simpleserial_send_byte(usb_testutils_ss_ctx_t *ssctx, + uint8_t c) { + usb_testutils_ctx_t *ctx = ssctx->ctx; + if (ssctx->cur_cpos == -1) { + CHECK_DIF_OK( + dif_usbdev_buffer_request(ctx->dev, ctx->buffer_pool, &ssctx->cur_buf)); + ssctx->cur_cpos = 0; + } + ssctx->chold.data_b[ssctx->cur_cpos++ & 0x3] = c; + if ((ssctx->cur_cpos & 0x3) == 0) { + size_t bytes_written; + CHECK_DIF_OK(dif_usbdev_buffer_write(ctx->dev, &ssctx->cur_buf, + ssctx->chold.data_b, /*src_len=*/4, + &bytes_written)); + if (ssctx->cur_cpos >= MAX_GATHER) { + CHECK_DIF_OK(dif_usbdev_send(ctx->dev, ssctx->ep, &ssctx->cur_buf)); + ssctx->cur_cpos = -1; // given it to the hardware + } + } +} + +void usb_testutils_simpleserial_init(usb_testutils_ss_ctx_t *ssctx, + usb_testutils_ctx_t *ctx, int ep, + void (*got_byte)(uint8_t)) { + usb_testutils_endpoint_setup(ctx, ep, kUsbdevOutStream, ssctx, NULL, ss_rx, + ss_flush, NULL); + ssctx->ctx = ctx; + ssctx->ep = ep; + ssctx->got_byte = got_byte; + ssctx->cur_cpos = -1; +}
diff --git a/sw/device/lib/testing/usb_testutils_simpleserial.h b/sw/device/lib/testing/usb_testutils_simpleserial.h new file mode 100644 index 0000000..3172f87 --- /dev/null +++ b/sw/device/lib/testing/usb_testutils_simpleserial.h
@@ -0,0 +1,48 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#ifndef OPENTITAN_SW_DEVICE_LIB_TESTING_USB_TESTUTILS_SIMPLESERIAL_H_ +#define OPENTITAN_SW_DEVICE_LIB_TESTING_USB_TESTUTILS_SIMPLESERIAL_H_ + +#include <stddef.h> +#include <stdint.h> + +#include "sw/device/lib/dif/dif_usbdev.h" +#include "sw/device/lib/testing/usb_testutils.h" + +// This is only here because caller of _init needs it +typedef struct usb_testutils_ss_ctx { + usb_testutils_ctx_t *ctx; + int ep; + dif_usbdev_buffer_t cur_buf; + int cur_cpos; + union usb_ss_b2w { + uint32_t data_w; + uint8_t data_b[4]; + } chold; + void (*got_byte)(uint8_t); +} usb_testutils_ss_ctx_t; + +/** + * Send a byte on a simpleserial endpoint + * + * @param ssctx instance context + * @param c byte to send + */ +void usb_testutils_simpleserial_send_byte(usb_testutils_ss_ctx_t *ssctx, + uint8_t c); + +/** + * Initialize a simpleserial endpoint + * + * @param ssctx unintialized simpleserial instance context + * @param ctx initialized usbdev context + * @param ep endpoint number for this instance + * @param got_byte callback function for when a byte is received + */ +void usb_testutils_simpleserial_init(usb_testutils_ss_ctx_t *ssctx, + usb_testutils_ctx_t *ctx, int ep, + void (*got_byte)(uint8_t)); + +#endif // OPENTITAN_SW_DEVICE_LIB_TESTING_USB_TESTUTILS_SIMPLESERIAL_H_
diff --git a/sw/device/tests/usbdev_stream_test.c b/sw/device/tests/usbdev_stream_test.c new file mode 100644 index 0000000..a119324 --- /dev/null +++ b/sw/device/tests/usbdev_stream_test.c
@@ -0,0 +1,729 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 +// +// USB streaming data test +// +// This test requires interaction with the USB DPI model or a test application +// on the USB host. The test initializes the USB device and configures a set of +// endpoints for data streaming using bulk transfers. +// +// The DPI model mimicks a USB host. After device initialization, it detects +// the assertion of the pullup and first assigns an address to the device. +// For this test it will then repeatedly fetch data via IN requests to +// each stream and propagate that data to the corresponding OUT endpoints. +// +// The data itself is pseudo-randomly generated by the sender and, +// independently, by the receiving code to check that the data has been +// propagated unmodified and without data loss, corruption, replication etc. + +#include "sw/device/lib/dif/dif_pinmux.h" +#include "sw/device/lib/runtime/log.h" +#include "sw/device/lib/runtime/print.h" +#include "sw/device/lib/testing/pinmux_testutils.h" +#include "sw/device/lib/testing/test_framework/check.h" +#include "sw/device/lib/testing/test_framework/ottf_main.h" +#include "sw/device/lib/testing/usb_testutils.h" +#include "sw/device/lib/testing/usb_testutils_controlep.h" + +#include "hw/top_earlgrey/sw/autogen/top_earlgrey.h" // Generated. + +// Maximum number of concurrent streams +#ifdef USBDEV_NUM_ENDPOINTS +// Endpoint zero implements the default control pipe +#define STREAMS_MAX (USBDEV_NUM_ENDPOINTS - 1U) +#else +#define STREAMS_MAX 11U +#endif + +// TODO - currently we are unable to send the configuration descriptor +// if we try to describe more than two bidirectional endpoints +#if STREAMS_MAX > 2U +#undef STREAMS_MAX +#define STREAMS_MAX 2U +#endif + +// Number of streams to be tested +#ifndef NUM_STREAMS +#define NUM_STREAMS STREAMS_MAX +#endif + +// Maximum number of buffer simultaneously awaiting transmission +// (we must leave some available for packet reception) +#ifndef MAX_TX_BUFFERS +#define MAX_TX_BUFFERS 24U +#endif + +// This takes about 256s presently with 10MHz CPU in CW310 FPGA and physical +// USB with randomized packet sizes and the default memcpy implementation; +// The _MEM_FASTER switch drops the run time to 187s +#define TRANSFER_BYTES_FPGA (0x10U << 20) + +// This is appropriate for a Verilator chip simulation with 15 min timeout +#define TRANSFER_BYTES_VERILATOR 0x2400U + +// This is about the amount that we can transfer within a 1 hour 'eternal' test +//#define TRANSFER_BYTES_LONG (0xD0U << 20) + +// Stream signature words +#define STREAM_SIGNATURE_HEAD 0x579EA01AU +#define STREAM_SIGNATURE_TAIL 0x160AE975U + +// Seed numbers for the LFSR generators in each transfer direction for +// the given stream number +#define USBTST_LFSR_SEED(s) (uint8_t)(0x10U + (s)*7U) +#define USBDPI_LFSR_SEED(s) (uint8_t)(0x9BU - (s)*7U) + +// Buffer size randomization +#define BUFSZ_LFSR_SEED(s) (uint8_t)(0x17U + (s)*7U) + +// Simple LFSR for 8-bit sequences +// Note: zero is an isolated state that shall be avoided +#define LFSR_ADVANCE(lfsr) \ + (((lfsr) << 1) ^ \ + ((((lfsr) >> 1) ^ ((lfsr) >> 2) ^ ((lfsr) >> 3) ^ ((lfsr) >> 7)) & 1U)) + +// Forward declaration to context state +typedef struct usbdev_stream_test_ctx usbdev_stream_test_ctx_t; + +/** + * Stream signature + * Note: this needs to be transferred over a byte stream + */ +typedef struct __attribute__((packed)) usbdev_stream_sig { + /** + * Head signature word + */ + uint32_t head_sig; + /** + * Initial value of LFSR + */ + uint8_t init_lfsr; + /** + * Stream number + */ + uint8_t stream; + /** + * Reserved fields; should be zero + */ + uint8_t reserved1; + uint8_t reserved2; + /** + * Number of bytes to be transferred + */ + uint32_t num_bytes; + /** + * Tail signature word + */ + uint32_t tail_sig; +} usbdev_stream_sig_t; + +// Sanity check because the host-side code relies upon the same structure +static_assert(sizeof(usbdev_stream_sig_t) == 0x10U, + "Host-side code relies upon signature structure"); + +/** + * Context state for a single stream + */ +typedef struct usbdev_stream { + /** + * Pointer to test context; callback functions receive only stream pointer + */ + usbdev_stream_test_ctx_t *ctx; + /** + * Stream IDentifier + */ + uint8_t id; + /** + * Has the stream signature been sent yet? + */ + bool sent_sig; + /** + * USB device endpoint being used for data transmission + */ + uint8_t tx_ep; + /** + * Transmission Linear Feedback Shift Register (for PRND data generation) + */ + uint8_t tx_lfsr; + /** + * Total number of bytes presented to the USB device for transmission + */ + uint32_t tx_bytes; + /** + * Transmission-side LFSR for selection of buffer size + */ + uint8_t tx_buf_size; + + /** + * USB device endpoint being used for data reception + */ + uint8_t rx_ep; + /** + * Reception-side LFSR state (mirrors USBDPI generation of PRND data) + */ + uint8_t rx_lfsr; + /** + * Reception-side shadow of transmission LFSR + */ + uint8_t rxtx_lfsr; + /** + * Total number of bytes received from the USB device + */ + uint32_t rx_bytes; + /** + * Size of transfer in bytes + */ + uint32_t transfer_bytes; +} usbdev_stream_t; + +/** + * Context state for streaming test + */ +struct usbdev_stream_test_ctx { + /** + * Context pointer + */ + usb_testutils_ctx_t *usbdev; + /** + * State information for each of the test streams + */ + usbdev_stream_t streams[STREAMS_MAX]; + /** + * Per-endpoint limits on the number of buffers that may be queued for + * transmission + */ + uint8_t tx_bufs_limit[USBDEV_NUM_ENDPOINTS]; + /** + * Per-endpoint counts of completed buffers queued for transmission + */ + uint8_t tx_bufs_queued[USBDEV_NUM_ENDPOINTS]; + /** + * Total number of completed buffers + */ + uint8_t tx_queued_total; + /** + * Buffers that have been filled but cannot yet be presented for transmission + * TODO - perhaps absorb the buffer queuing into usb_testutils because the dif + * API is explicitly not robust against back-to-back sending of multiple + * buffers to a single endpoint, and because the read performance is reliant + * upon having additional buffer(s) already available for immediate + * presentation + */ + // 12 X 24 X 4 (or 8?)( BYTES... could perhaps simplify this at some point + dif_usbdev_buffer_t tx_bufs[USBDEV_NUM_ENDPOINTS][MAX_TX_BUFFERS]; +}; + +/** + * Configuration values for USB. + * TODO - dynamically construct a config descriptor appropriate to the test; + * this would avoid creating unusable ports on the host and also provide + * a little more testing + */ +static const uint8_t config_descriptors[] = { + USB_CFG_DSCR_HEAD(USB_CFG_DSCR_LEN + STREAMS_MAX * (USB_INTERFACE_DSCR_LEN + + 2 * USB_EP_DSCR_LEN), + STREAMS_MAX), + + VEND_INTERFACE_DSCR(0, 2, 0x50, 1), + USB_BULK_EP_DSCR(0, 1U, USBDEV_MAX_PACKET_SIZE, 0), + USB_BULK_EP_DSCR(1, 1U, USBDEV_MAX_PACKET_SIZE, 0), + + VEND_INTERFACE_DSCR(1, 2, 0x50, 1), + USB_BULK_EP_DSCR(0, 2U, USBDEV_MAX_PACKET_SIZE, 0), + USB_BULK_EP_DSCR(1, 2U, USBDEV_MAX_PACKET_SIZE, 0), +}; + +/** + * Test descriptor + */ +static const uint8_t test_descriptor[] = { + USB_TESTUTILS_TEST_DSCR(1, NUM_STREAMS | 0xF0U, 0, 0, 0)}; + +/** + * USB device context types. + */ +static usb_testutils_ctx_t usbdev; +static usb_testutils_controlep_ctx_t usbdev_control; + +/** + * Pinmux handle + */ +static dif_pinmux_t pinmux; + +/** + * State information for streaming data test + */ +static usbdev_stream_test_ctx_t stream_test; + +/** + * Specify whether to perform verbose logging, for visibility + * (Note that this substantially alters the timing of interactions with the + * DPI model and will increase the simulation time) + */ +static bool verbose = false; + +/** + * Send only maximal length packets? + * (important for performance measurements on the USB, but obviously undesirable + * for testing reliability/function) + */ +static bool max_packets = false; + +/** + * Number of streams to be created + */ +static const unsigned nstreams = NUM_STREAMS; + +/** + * Diagnostic logging; expensive + */ +static bool log_traffic = false; + +// Dump a sequence of bytes as hexadecimal and ASCII for diagnostic purposes +static void buffer_dump(const uint8_t *data, size_t n) { + base_hexdump_fmt_t fmt = { + .bytes_per_word = 1, + .words_per_line = 0x20u, + .alphabet = &kBaseHexdumpDefaultFmtAlphabet, + }; + + base_hexdump_with(fmt, (char *)data, n); +} + +// Create a stream signature buffer +static uint32_t buffer_sig_create(usbdev_stream_t *s, + dif_usbdev_buffer_t *buf) { + usbdev_stream_sig_t sig; + + sig.head_sig = STREAM_SIGNATURE_HEAD; + sig.init_lfsr = s->tx_lfsr; + sig.stream = s->id; + sig.reserved1 = 0U; + sig.reserved2 = 0U; + sig.num_bytes = s->transfer_bytes; + sig.tail_sig = STREAM_SIGNATURE_TAIL; + + size_t bytes_written; + CHECK_DIF_OK(dif_usbdev_buffer_write(usbdev.dev, buf, (uint8_t *)&sig, + sizeof(sig), &bytes_written)); + CHECK(bytes_written == sizeof(sig)); + + // Note: stream signature is not included in the count of bytes transferred + + return bytes_written; +} + +// Fill a buffer with LFSR-generated data +static void buffer_fill(usbdev_stream_t *s, dif_usbdev_buffer_t *buf, + uint8_t num_bytes) { + alignas(uint32_t) uint8_t data[USBDEV_MAX_PACKET_SIZE]; + + CHECK(num_bytes <= buf->remaining_bytes); + CHECK(num_bytes <= sizeof(data)); + + if (true) { + // Emit LFSR-generated byte stream; keep this brief so that we can + // reduce our latency in responding to USB events (usb_testutils employs + // polling at present) + uint8_t lfsr = s->tx_lfsr; + + const uint8_t *edp = &data[num_bytes]; + uint8_t *dp = data; + while (dp < edp) { + *dp++ = lfsr; + lfsr = LFSR_ADVANCE(lfsr); + } + + // Update the LFSR for the next packet + s->tx_lfsr = lfsr; + } else { + // Undefined buffer contents; useful for profiling IN throughput on + // CW310, because the CPU load at 10MHz can be an appreciable slowdown + } + + if (verbose && log_traffic) { + buffer_dump(data, num_bytes); + } + + size_t bytes_written; + + CHECK_DIF_OK(dif_usbdev_buffer_write(usbdev.dev, buf, data, num_bytes, + &bytes_written)); + CHECK(bytes_written == num_bytes); + s->tx_bytes += bytes_written; +} + +// Check the contents of a received buffer +static void buffer_check(usbdev_stream_test_ctx_t *ctx, usbdev_stream_t *s, + dif_usbdev_rx_packet_info_t packet_info, + dif_usbdev_buffer_t buf) { + usb_testutils_ctx_t *usbdev = ctx->usbdev; + uint8_t len = packet_info.length; + + if (len > 0) { + alignas(uint32_t) uint8_t data[USBDEV_MAX_PACKET_SIZE]; + + CHECK(len <= sizeof(data)); + + size_t bytes_read; + + // Notes: the buffer being read here is USBDEV memory accessed as MMIO, so + // only the DIF accesses it directly. when we consume the final bytes + // from the read buffer, it is automatically returned to the buffer + // pool. + CHECK_DIF_OK(dif_usbdev_buffer_read(usbdev->dev, usbdev->buffer_pool, &buf, + data, len, &bytes_read)); + CHECK(bytes_read == len); + + if (log_traffic) { + buffer_dump(data, bytes_read); + } + + // Check received data against expected LFSR-generated byte stream; + // keep this brief so that we can reduce our latency in responding to + // USB events (usb_testutils employs polling at present) + uint8_t rxtx_lfsr = s->rxtx_lfsr; + uint8_t rx_lfsr = s->rx_lfsr; + + const uint8_t *esp = &data[bytes_read]; + const uint8_t *sp = data; + while (sp < esp) { + // Received data should be the XOR of two LFSR-generated PRND streams - + // ours on the + // transmission side, and that of the DPI model + uint8_t expected = rxtx_lfsr ^ rx_lfsr; + CHECK(expected == *sp, + "S%u: Unexpected received data 0x%02x : (LFSRs 0x%02x 0x%02x)", + s->id, *sp, rxtx_lfsr, rx_lfsr); + + rxtx_lfsr = LFSR_ADVANCE(rxtx_lfsr); + rx_lfsr = LFSR_ADVANCE(rx_lfsr); + sp++; + } + + // Update the LFSRs for the next packet + s->rxtx_lfsr = rxtx_lfsr; + s->rx_lfsr = rx_lfsr; + } else { + // In the event that we've received a zero-length data packet, we still + // must return the buffer to the pool + CHECK_DIF_OK( + dif_usbdev_buffer_return(usbdev->dev, usbdev->buffer_pool, &buf)); + } +} + +// Callback for successful buffer transmission +static void strm_tx_done(void *stream_v) { + usbdev_stream_t *s = (usbdev_stream_t *)stream_v; + usbdev_stream_test_ctx_t *ctx = s->ctx; + usb_testutils_ctx_t *usbdev = ctx->usbdev; + + // If we do not have at least one queued buffer then something has gone wrong + // and this callback is inappropriate + uint8_t tx_ep = s->tx_ep; + uint8_t nqueued = ctx->tx_bufs_queued[tx_ep]; + + if (verbose) { + LOG_INFO("strm_tx_done called. %u (%u total) buffers(s) are queued", + nqueued, ctx->tx_queued_total); + } + + CHECK(nqueued > 0); + + // Note: since buffer transmission and completion signalling both occur within + // the foreground code (polling, not interrupt-driven) there is no issue of + // potential races here + + if (nqueued > 0) { + // Shuffle the buffer descriptions, without using memmove + for (unsigned idx = 1u; idx < nqueued; idx++) { + ctx->tx_bufs[tx_ep][idx - 1u] = ctx->tx_bufs[tx_ep][idx]; + } + + // Is there another buffer ready to be transmitted? + ctx->tx_queued_total--; + ctx->tx_bufs_queued[tx_ep] = --nqueued; + + if (nqueued) { + CHECK_DIF_OK( + dif_usbdev_send(usbdev->dev, tx_ep, &ctx->tx_bufs[tx_ep][0u])); + } + } +} + +// Callback for buffer reception +static void strm_rx(void *stream_v, dif_usbdev_rx_packet_info_t packet_info, + dif_usbdev_buffer_t buf) { + usbdev_stream_t *s = (usbdev_stream_t *)stream_v; + usbdev_stream_test_ctx_t *ctx = s->ctx; + usb_testutils_ctx_t *usbdev = ctx->usbdev; + + CHECK(packet_info.endpoint == s->rx_ep); + + // We do not expect to receive SETUP packets to this endpoint + CHECK(!packet_info.is_setup); + + if (verbose) { + LOG_INFO("Stream %u: Received buffer of %u bytes(s)", s->id, + packet_info.length); + } + + if (true) { + buffer_check(ctx, s, packet_info, buf); + } else { + // Note: this is just test code for measuring the OUT throughput + usb_testutils_ctx_t *usbdev = ctx->usbdev; + CHECK_DIF_OK( + dif_usbdev_buffer_return(usbdev->dev, usbdev->buffer_pool, &buf)); + } + + s->rx_bytes += packet_info.length; +} + +// Callback for unexpected data reception (IN endpoint) +static void rx_show(void *stream_v, dif_usbdev_rx_packet_info_t packet_info, + dif_usbdev_buffer_t buf) { + usbdev_stream_t *s = (usbdev_stream_t *)stream_v; + usbdev_stream_test_ctx_t *ctx = s->ctx; + usb_testutils_ctx_t *usbdev = ctx->usbdev; + uint8_t data[0x100U]; + size_t bytes_read; + CHECK_DIF_OK(dif_usbdev_buffer_read(usbdev->dev, usbdev->buffer_pool, &buf, + data, packet_info.length, &bytes_read)); + LOG_INFO("rx_show packet of %u byte(s) - read %u", packet_info.length, + bytes_read); + buffer_dump(data, bytes_read); +} + +// Returns an indication of whether a stream has completed its data transfer +bool stream_completed(const usbdev_stream_t *s) { + return (s->tx_bytes >= s->transfer_bytes) && + (s->rx_bytes >= s->transfer_bytes); +} + +// Initialise a stream, preparing it for use +static void stream_init(usbdev_stream_test_ctx_t *ctx, usbdev_stream_t *s, + uint8_t id, uint8_t ep_in, uint8_t ep_out, + uint32_t transfer_bytes) { + // We need to be able to locate the test context given only the stream + // pointer within the strm_tx_done callback from usb_testutils + s->ctx = ctx; + + // Remember the stream IDentifier + s->id = id; + + // Not yet sent stream signature + s->sent_sig = false; + + // Initialise the transfer state + s->tx_bytes = 0u; + s->rx_bytes = 0u; + s->transfer_bytes = transfer_bytes; + + // Initialise the LFSR state for transmission and reception sides + // - we use a simple LFSR to generate a PRND stream to transmit to the USBPI + // - the USBDPI XORs the received data with another LFSR-generated stream of + // its own, and transmits the result back to us + // - to check the returned data, our reception code mimics both LFSRs + s->tx_lfsr = USBTST_LFSR_SEED(id); + s->rxtx_lfsr = s->tx_lfsr; + s->rx_lfsr = USBDPI_LFSR_SEED(id); + + // Packet size randomization + s->tx_buf_size = BUFSZ_LFSR_SEED(id); + + // Set up the endpoint for IN transfers (TO host) + // + // Note: We install the rx_show handler to catch any misdirected data + // transfers + void (*rx)(void *, dif_usbdev_rx_packet_info_t, dif_usbdev_buffer_t) = + (ep_in == ep_out) ? strm_rx : rx_show; + + s->tx_ep = ep_in; + usb_testutils_endpoint_setup(ctx->usbdev, ep_in, kUsbdevOutStream, s, + strm_tx_done, rx, NULL, NULL); + s->rx_ep = ep_out; + if (ep_out != ep_in) { + // Set up the endpoint for OUT transfers (FROM host) + usb_testutils_endpoint_setup(ctx->usbdev, ep_out, kUsbdevOutStream, s, NULL, + strm_rx, NULL, NULL); + } +} + +// Service the given stream, preparing and/or sending any data that we can; +// data reception is handled via callbacks and requires no attention here +static void stream_service(usbdev_stream_test_ctx_t *ctx, usbdev_stream_t *s) { + // Generate output data as soon as possible and make it available for + // collection by the host + + uint8_t tx_ep = s->tx_ep; + uint8_t nqueued = ctx->tx_bufs_queued[tx_ep]; + + if (s->tx_bytes < s->transfer_bytes && // More bytes to transfer? + nqueued < ctx->tx_bufs_limit[tx_ep] && // Endpoint allowed buffer? + ctx->tx_queued_total < MAX_TX_BUFFERS) { // Total buffers not exceeded? + dif_usbdev_buffer_t buf; + + // See whether we can populate another buffer yet + dif_result_t dif_result = + dif_usbdev_buffer_request(usbdev.dev, usbdev.buffer_pool, &buf); + if (dif_result == kDifOk) { + // This is just for reporting the number of buffers presented to the + // USB device, as a progress indicator + static unsigned bufs_sent = 0u; + uint32_t num_bytes; + + if (s->sent_sig) { + if (max_packets) { + num_bytes = USBDEV_MAX_PACKET_SIZE; + } else { + // Vary the amount of data sent per buffer + num_bytes = s->tx_buf_size % (USBDEV_MAX_PACKET_SIZE + 1u); + s->tx_buf_size = LFSR_ADVANCE(s->tx_buf_size); + } + uint32_t tx_left = s->transfer_bytes - s->tx_bytes; + if (num_bytes > tx_left) + num_bytes = tx_left; + + buffer_fill(s, &buf, num_bytes); + } else { + // Construct a signature to send to the host-side software, + // identifying the stream and its properties + num_bytes = buffer_sig_create(s, &buf); + s->sent_sig = true; + } + + // Remember the buffer until we're informed that it has been + // successfully transmitted + // + // Note: since the 'tx_done' callback occurs from foreground code that + // is polling, there is no issue of interrupt races here + ctx->tx_bufs[tx_ep][nqueued] = buf; + ctx->tx_bufs_queued[tx_ep] = ++nqueued; + ctx->tx_queued_total++; + + // Can we present this buffer for transmission yet? + if (nqueued <= 1U) { + CHECK_DIF_OK(dif_usbdev_send(usbdev.dev, tx_ep, &buf)); + } + + if (verbose) { + LOG_INFO( + "Stream %u: %uth buffer (of 0x%x byte(s)) awaiting transmission", + s->id, bufs_sent, num_bytes); + } + bufs_sent++; + } else { + // If we have no more buffers available right now, continue polling... + CHECK(dif_result == kDifUnavailable); + } + } +} + +OTTF_DEFINE_TEST_CONFIG(); + +bool test_main(void) { + // Context state for streaming test + usbdev_stream_test_ctx_t *ctx = &stream_test; + + CHECK(kDeviceType == kDeviceSimVerilator || kDeviceType == kDeviceFpgaCw310, + "This test is not expected to run on platforms other than the " + "Verilator simulation or CW310 FPGA. It needs logic on the host side " + "to retrieve, scramble and return the generated byte stream"); + + LOG_INFO("Running USBDEV Stream Test"); + + // Check we can support the requested number of streams + CHECK(nstreams && nstreams < USBDEV_NUM_ENDPOINTS); + + // Decide upon the number of bytes to be transferred for the entire test + uint32_t transfer_bytes = TRANSFER_BYTES_FPGA; + if (kDeviceType == kDeviceSimVerilator) { + transfer_bytes = TRANSFER_BYTES_VERILATOR; + } + transfer_bytes = (transfer_bytes + nstreams - 1) / nstreams; + LOG_INFO(" - %u stream(s), 0x%x bytes each", nstreams, transfer_bytes); + + CHECK_DIF_OK(dif_pinmux_init( + mmio_region_from_addr(TOP_EARLGREY_PINMUX_AON_BASE_ADDR), &pinmux)); + pinmux_testutils_init(&pinmux); + CHECK_DIF_OK(dif_pinmux_input_select( + &pinmux, kTopEarlgreyPinmuxPeripheralInUsbdevSense, + kTopEarlgreyPinmuxInselIoc7)); + + // Remember context state for usb_testutils context + ctx->usbdev = &usbdev; + + // Call `usbdev_init` here so that DPI will not start until the + // simulation has finished all of the printing, which takes a while + // if `--trace` was passed in. + usb_testutils_init(ctx->usbdev, /*pinflip=*/false, /*en_diff_rcvr=*/false, + /*tx_use_d_se0=*/false); + usb_testutils_controlep_init(&usbdev_control, ctx->usbdev, 0, + config_descriptors, sizeof(config_descriptors), + test_descriptor, sizeof(test_descriptor)); + while (usbdev_control.device_state != kUsbTestutilsDeviceConfigured) { + usb_testutils_poll(ctx->usbdev); + } + + // Initialise the state of each stream + for (unsigned id = 0U; id < nstreams; id++) { + // Which endpoint are we using for the IN transfers to the host? + const uint8_t ep_in = 1u + id; + // Which endpoint are we using for the OUT transfers from the host? + const uint8_t ep_out = 1u + id; + stream_init(ctx, &ctx->streams[id], id, ep_in, ep_out, transfer_bytes); + } + + // Decide how many buffers each endpoint may queue up for transmission; + // we must ensure that there are buffers available for reception, and we + // do not want any endpoint to starve another + for (unsigned s = 0U; s < nstreams; s++) { + // This is slightly overspending the available buffers, leaving the + // endpoints to vie for the final few buffers, so it's important that + // we limit the total number of buffers across all endpoints too + unsigned ep = ctx->streams[s].tx_ep; + ctx->tx_bufs_queued[ep] = 0U; + ctx->tx_bufs_limit[ep] = (MAX_TX_BUFFERS + nstreams - 1) / nstreams; + } + ctx->tx_queued_total = 0U; + + if (verbose) { + LOG_INFO("Commencing data transfer..."); + } + + bool done = false; + do { + for (unsigned s = 0U; s < nstreams; s++) { + stream_service(ctx, &ctx->streams[s]); + + // We must keep polling regularly in order to handle detection of packet + // transmission as well as perform packet reception and checking + usb_testutils_poll(ctx->usbdev); + } + + // See whether any streams still have more work to do + unsigned s = 0U; + while (s < nstreams && stream_completed(&ctx->streams[s])) { + s++; + } + done = (s >= nstreams); + } while (!done); + + // Determine the total counts of bytes sent and received + uint32_t tx_bytes = 0U; + uint32_t rx_bytes = 0U; + for (unsigned s = 0U; s < nstreams; s++) { + tx_bytes += ctx->streams[s].tx_bytes; + rx_bytes += ctx->streams[s].rx_bytes; + } + + LOG_INFO("USB sent 0x%x byte(s), received and checked 0x%x byte(s)", tx_bytes, + rx_bytes); + + CHECK(tx_bytes == nstreams * transfer_bytes, + "Unexpected count of byte(s) sent to USB host"); + + return true; +}
diff --git a/sw/device/tests/usbdev_test.c b/sw/device/tests/usbdev_test.c new file mode 100644 index 0000000..af52cf5 --- /dev/null +++ b/sw/device/tests/usbdev_test.c
@@ -0,0 +1,141 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 +// +// USB device test +// +// This test is a stripped down version of the hello_usbdev example application. +// It requires interaction with the USB DPI model mimicking the host and thus +// can only be run in the Verilator simulation. The test initializes the USB +// device and configures USB Endpoint 1 as a simpleserial endpoint. The test +// then starts polling the USB device for data sent by the host. Any data +// received on Endpoint 1 is stored in a buffer and printed via UART. +// +// The DPI model mimicks the USB host. After device initialization, it detects +// the assertion of the pullup and first assigns an address to the device. It +// then sends various USB transactions to the device including two OUT +// transactions with a data payload of "Hi!" to Endpoint 1. If these two OUT +// transactions are succesfully received by the device, the test passes. + +#include "sw/device/lib/dif/dif_pinmux.h" +#include "sw/device/lib/runtime/log.h" +#include "sw/device/lib/runtime/print.h" +#include "sw/device/lib/testing/pinmux_testutils.h" +#include "sw/device/lib/testing/test_framework/check.h" +#include "sw/device/lib/testing/test_framework/ottf_main.h" +#include "sw/device/lib/testing/usb_testutils.h" +#include "sw/device/lib/testing/usb_testutils_controlep.h" +#include "sw/device/lib/testing/usb_testutils_simpleserial.h" + +#include "hw/top_earlgrey/sw/autogen/top_earlgrey.h" // Generated. + +/** + * Configuration values for USB. + */ +static const uint8_t config_descriptors[] = { + 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), +}; + +/** + * Test descriptor + */ +static const uint8_t test_descriptor[] = { + USB_TESTUTILS_TEST_DSCR(0, 0, 0, 0, 0)}; + +/** + * USB device context types. + */ +static usb_testutils_ctx_t usbdev; +static usb_testutils_controlep_ctx_t usbdev_control; +static usb_testutils_ss_ctx_t simple_serial; + +/** + * Pinmux handle + */ +static dif_pinmux_t pinmux; + +/** + * Makes `c` into a printable character, replacing it with `replacement` + * as necessary. + */ +static char make_printable(char c, char replacement) { + if (c == 0xa || c == 0xd) { + return c; + } + + if (c < ' ' || c > '~') { + c = replacement; + } + return c; +} + +static const size_t kExpectedUsbCharsRecved = 6; +static const char kExpectedUsbRecved[7] = "Hi!Hi!"; +static size_t usb_chars_recved_total; +static char buffer[7]; + +/** + * Callback for processing USB reciept. + */ +static void usb_receipt_callback(uint8_t c) { + c = make_printable(c, '?'); + base_printf("%c", c); + if (usb_chars_recved_total < kExpectedUsbCharsRecved) { + buffer[usb_chars_recved_total] = c; + ++usb_chars_recved_total; + } +} + +OTTF_DEFINE_TEST_CONFIG(); + +bool test_main(void) { + CHECK(kDeviceType == kDeviceSimVerilator || kDeviceType == kDeviceFpgaCw310, + "This test is not expected to run on platforms other than the " + "Verilator simulation or CW310 FPGA. It needs the USB DPI model " + "or host application."); + + LOG_INFO("Running USBDEV test"); + + 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)); + + // Call `usbdev_init` here so that DPI will not start until the + // simulation has finished all of the printing, which takes a while + // if `--trace` was passed in. + usb_testutils_init(&usbdev, /*pinflip=*/false, /*en_diff_rcvr=*/false, + /*tx_use_d_se0=*/false); + usb_testutils_controlep_init(&usbdev_control, &usbdev, 0, config_descriptors, + sizeof(config_descriptors), test_descriptor, + sizeof(test_descriptor)); + while (usbdev_control.device_state != kUsbTestutilsDeviceConfigured) { + usb_testutils_poll(&usbdev); + } + usb_testutils_simpleserial_init(&simple_serial, &usbdev, 1, + usb_receipt_callback); + + while (usb_chars_recved_total < kExpectedUsbCharsRecved) { + usb_testutils_poll(&usbdev); + } + + base_printf("\r\n"); + for (int i = 0; i < kExpectedUsbCharsRecved; i++) { + CHECK(buffer[i] == kExpectedUsbRecved[i], + "Received char #%d mismatched: exp = %x, actual = %x", i, + kExpectedUsbRecved[i], buffer[i]); + } + LOG_INFO("USB received %d characters: %s", usb_chars_recved_total, buffer); + + return true; +}