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