diff --git a/sw/device/lib/dif/dif_usbdev.c b/sw/device/lib/dif/dif_usbdev.c index 6e28dd812eb01..cd94705b5bcca 100644 --- a/sw/device/lib/dif/dif_usbdev.c +++ b/sw/device/lib/dif/dif_usbdev.c @@ -7,6 +7,7 @@ #include #include "sw/device/lib/base/bitfield.h" +#include "sw/device/lib/base/memory.h" #include "usbdev_regs.h" // Generated. @@ -1012,3 +1013,109 @@ dif_result_t dif_usbdev_set_phy_pins_state( reg_val); return kDifOk; } + +dif_result_t dif_usbdev_buffer_raw_write(const dif_usbdev_t *usbdev, uint8_t id, + const uint8_t *src, size_t src_len) { + if (usbdev == NULL || src == NULL || misalignment32_of((uintptr_t)src) || + src_len > USBDEV_BUFFER_ENTRY_SIZE_BYTES) { + return kDifBadArg; + } + + // We're writing to the start of the buffer. + ptrdiff_t buffer_offset = (ptrdiff_t)get_buffer_addr(id, 0U); + const uint32_t *restrict ews = (uint32_t *)(src + (src_len & ~15u)); + const uint32_t *restrict ws = (uint32_t *)src; + + // Transfer blocks of 4 x 32-bit words at a time; use the mmio_ routines for + // compliance and to operate correctly with the DIF mocks, although this + // results in transfers taking 50% longer because of the additional addressing + // arithmetic and increased loop overheads. + while (ws < ews) { + mmio_region_write32(usbdev->base_addr, buffer_offset, ws[0]); + mmio_region_write32(usbdev->base_addr, buffer_offset + 4, ws[1]); + mmio_region_write32(usbdev->base_addr, buffer_offset + 8, ws[2]); + mmio_region_write32(usbdev->base_addr, buffer_offset + 12, ws[3]); + buffer_offset += 16; + ws += 4; + } + src_len &= 15u; + + if (src_len) { + // Remaining whole words + ews = ws + (src_len >> 2); + while (ws < ews) { + mmio_region_write32(usbdev->base_addr, buffer_offset, *ws++); + buffer_offset += 4; + } + src_len &= 3u; + if (src_len) { + // Remaining individual bytes + const uint8_t *restrict bs = (uint8_t *)ws; + uint32_t d = bs[0]; + if (src_len > 1) { + d |= ((uint32_t)bs[1] << 8); + if (src_len > 2) { + d |= ((uint32_t)bs[2] << 16); + } + } + // Note: we can only perform full 32-bit writes to the packet buffer but + // any additional byte(s) will be ignored. Attempting byte-level writes + // would raise exceptions. + mmio_region_write32(usbdev->base_addr, buffer_offset, d); + } + } + + return kDifOk; +} + +dif_result_t dif_usbdev_buffer_raw_read(const dif_usbdev_t *usbdev, uint8_t id, + uint8_t *dst, size_t dst_len) { + if (usbdev == NULL || dst == NULL || misalignment32_of((uintptr_t)dst) || + dst_len > USBDEV_BUFFER_ENTRY_SIZE_BYTES) { + return kDifBadArg; + } + + // We're reading from the start of the packet buffer. + ptrdiff_t buffer_offset = (ptrdiff_t)get_buffer_addr(id, 0U); + const uint32_t *restrict ewd = (uint32_t *)(dst + (dst_len & ~15u)); + uint32_t *restrict wd = (uint32_t *)dst; + + // Transfer blocks of 4 x 32-bit words at a time; use the mmio_ routines for + // compliance and to operate correctly with the DIF mocks, although this + // results in transfers taking 50% longer because of the additional addressing + // arithmetic and increased loop overheads. + while (wd < ewd) { + wd[0] = mmio_region_read32(usbdev->base_addr, buffer_offset); + wd[1] = mmio_region_read32(usbdev->base_addr, buffer_offset + 4); + wd[2] = mmio_region_read32(usbdev->base_addr, buffer_offset + 8); + wd[3] = mmio_region_read32(usbdev->base_addr, buffer_offset + 12); + buffer_offset += 16; + wd += 4; + } + dst_len &= 15u; + + if (dst_len) { + // Remaining whole words + ewd = wd + (dst_len >> 2); + while (wd < ewd) { + *wd++ = mmio_region_read32(usbdev->base_addr, buffer_offset); + buffer_offset += 4; + } + dst_len &= 3u; + if (dst_len) { + // Remaining individual bytes + // Note: we can only perform full 32-bit reads from the packet buffer. + uint8_t *restrict bd = (uint8_t *)wd; + uint32_t d = mmio_region_read32(usbdev->base_addr, buffer_offset); + bd[0] = (uint8_t)d; + if (dst_len > 1) { + bd[1] = (uint8_t)(d >> 8); + if (dst_len > 2) { + bd[2] = (uint8_t)(d >> 16); + } + } + } + } + + return kDifOk; +} diff --git a/sw/device/lib/dif/dif_usbdev.h b/sw/device/lib/dif/dif_usbdev.h index 9bd81d86aa1b7..68a25e48e31cf 100644 --- a/sw/device/lib/dif/dif_usbdev.h +++ b/sw/device/lib/dif/dif_usbdev.h @@ -923,6 +923,36 @@ 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); +/** + * Raw data transfer directly to the packet buffer memory. This is a faster + * implementation of the generic `mmio_memcpy_to_mmio32` that is specialized for + * the USB device and gives a significant performance improvement. + * + * @param usbdev A USB device. + * @param id Buffer number. + * @param src Source data. + * @param src_len Number of bytes to transfer. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_buffer_raw_write(const dif_usbdev_t *usbdev, uint8_t id, + const uint8_t *src, size_t src_len); + +/** + * Raw data transfer directly from the packet buffer memory. This is a faster + * implementation of the generic `mmio_memcpy_from_mmio32` that is specialized + * for the USB device and gives a significant performance improvemenet. + * + * @param usbdev A USB device. + * @param id Buffer number. + * @param dst Destination buffer. + * @param dst_len Number of bytes to transfer. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_usbdev_buffer_raw_read(const dif_usbdev_t *usbdev, uint8_t id, + uint8_t *dst, size_t dst_len); + #ifdef __cplusplus } // extern "C" #endif // __cplusplus diff --git a/sw/device/lib/testing/usb_testutils_diags.h b/sw/device/lib/testing/usb_testutils_diags.h index 1a94b50217af0..ae8140174a047 100644 --- a/sw/device/lib/testing/usb_testutils_diags.h +++ b/sw/device/lib/testing/usb_testutils_diags.h @@ -21,6 +21,109 @@ #define USBUTILS_MEM_FASTER 1 #endif +// Implement low-impact tracing of software execution, permitting the software +// and hardware behavior to be married, hopefully without modifying the +// behavior/performance, particularly in top-level simulation +#define USBUTILS_FUNCTION_POINTS 0 + +// Record the function points to a memory buffer instead, for use where test +// hardware is unavailable, eg. FPGA builds +#define USBUTILS_FUNCPT_USE_BUFFER 0 + +#if USBUTILS_MEM_FASTER +#include "sw/device/lib/base/mmio.h" +#endif + +#if USBUTILS_FUNCTION_POINTS +// For access to ibex_mcycle_read() +#include "sw/device/lib/runtime/ibex.h" + +// Function point file numbers +// (used to index filename table in usb_testutils_diags.c) +#define USBUTILS_FUNCPT_FILE_DIF_USBDEV 0x01U +#define USBUTILS_FUNCPT_FILE_USB_TESTUTILS 0x02U +#define USBUTILS_FUNCPT_FILE_USB_CONTROLEP 0x03U +#define USBUTILS_FUNCPT_FILE_USB_SIMPLESER 0x04U +#define USBUTILS_FUNCPT_FILE_USBDEV_TEST 0x05U +#define USBUTILS_FUNCPT_FILE_USBDEV_STRM_TEST 0x06U + +#define USBUTILS_FUNCPT_LOG_ENTRIES 0x1000U +#define USBUTILS_FUNCPT_LOG_SIZE (USBUTILS_FUNCPT_LOG_ENTRIES * 4U) + +#define USBUTILS_FUNCPT_ENTRY_SIGNATURE 0xAA55FF99U +/** + * Entry in function point stream + */ +typedef struct { + uint32_t sig; + uint32_t time; + uint32_t file_point; + uint32_t data; +} funcpt_entry_t; + +#if USBUTILS_FUNCPT_USE_BUFFER +// Record function points to RAM buffer for deferred reporting, eg. FPGA +#define USBUTILS_FUNCPT(pt, d) \ + { \ + unsigned idx = usbutils_fpt_next; \ + usbutils_fpt_next = \ + (idx >= USBUTILS_FUNCPT_LOG_SIZE - 4U) ? 0U : (idx + 4U); \ + functpt_enttry_t *e = (functpt_enttry_t *)&usbutils_fpt_log[idx]; \ + e->sig = USBUTILS_FUNCPT_ENTRY_SIGNATURE; \ + e->time = (uint32_t)ibex_mcycle_read(); \ + e->file_point = (USBUTILS_FUNCPT_FILE << 16) | pt; \ + e->data = (d); \ + } + +extern volatile unsigned usbutils_fpt_next; +extern uint32_t usbutils_fpt_log[]; +#else +// Emit function points to special address for waveform viewing in simulation +// (this is the address used by DV simulation for logging output) +#define USBUTILS_FUNCPT(pt, d) \ + { \ + volatile uint32_t *log_hw = (uint32_t *)0x411f0084u; \ + uint32_t time = (uint32_t)ibex_mcycle_read(); \ + *log_hw = USBUTILS_FUNCPT_ENTRY_SIGNATURE; \ + *log_hw = time; \ + *log_hw = (USBUTILS_FUNCPT_FILE << 16) | (pt); \ + *log_hw = (d); \ + } +#endif + +/** + * Report the contents of the function point log + */ +void usbutils_funcpt_report(void); +#else +// Omit function point tracing +#define USBUTILS_FUNCPT(pt, d) +#endif + +// For investigation of usbdev performance characteristics +#if USBUTILS_MEM_FASTER +/** + * Performant copying routine from usbdev packet buffer (MMIO) + * + * @param base MMIO base address + * @param offset MMIO word offset + * @param dest Buffer to receive data + * @param len Number of bytes to be copied + */ +void usbutils_memcpy_from_mmio32(mmio_region_t base, uint32_t offset, + void *dest, size_t len); +/** + * Performant copying routine to usbdev packet buffer (MMIO) + * + * @param base MMIO base address + * @param offset MMIO word offset + * @param src Data to be copied + * @param len Number of bytes to be copied + */ +void usbutils_memcpy_to_mmio32(mmio_region_t base, uint32_t offset, + const void *src, size_t len); +#endif + // 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 @@ -35,12 +138,29 @@ } while (false) #if USBUTILS_ENABLE_TRC +#if 0 // 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 +// Very low impact, for use in t-l simulation +#define USBDIAGS_LOG_EMIT(d) (*((volatile uint32_t *)0x411f0084u) = (d)) + +#define TRC_S(s) usbutils_log_text(s) +#define TRC_I(i, b) USBDIAGS_LOG_EMIT(i) +#define TRC_C(c) USBDIAGS_LOG_EMIT(0xcc000000u | (uint16_t)c) + +// Faster string logging to minimise impact upon timing +inline void usbutils_log_text(const char *s) { + while (*s) { + USBDIAGS_LOG_EMIT(*s); + s++; + } +} +#endif +#else #define TRC_S(s) #define TRC_I(i, b) #define TRC_C(c) diff --git a/sw/device/tests/BUILD b/sw/device/tests/BUILD index 9216394294302..418b74062004f 100644 --- a/sw/device/tests/BUILD +++ b/sw/device/tests/BUILD @@ -4335,6 +4335,25 @@ opentitan_test( ], ) +opentitan_test( + name = "usbdev_mem_test", + srcs = ["usbdev_mem_test.c"], + exec_env = dicts.add( + EARLGREY_TEST_ENVS, + EARLGREY_SILICON_OWNER_ROM_EXT_ENVS, + ), + deps = [ + "//hw/top_earlgrey/sw/autogen:top_earlgrey", + "//sw/device/lib/dif:pinmux", + "//sw/device/lib/dif:usbdev", + "//sw/device/lib/runtime:log", + "//sw/device/lib/runtime:print", + "//sw/device/lib/testing:pinmux_testutils", + "//sw/device/lib/testing:usb_testutils", + "//sw/device/lib/testing/test_framework:ottf_main", + ], +) + opentitan_test( name = "usbdev_mixed_test", srcs = ["usbdev_mixed_test.c"], diff --git a/sw/device/tests/usbdev_mem_test.c b/sw/device/tests/usbdev_mem_test.c new file mode 100644 index 0000000000000..21b4586fdc188 --- /dev/null +++ b/sw/device/tests/usbdev_mem_test.c @@ -0,0 +1,161 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 +// +// USB packet memory test +// +// This test performs a simple read/write test of the entire USB packet memory +// from the CPU to check connectivity, addressing, integrity etc. A LFSR- +// generated byte stream is used as the test pattern. +// +// Two methods of access are tested in turn: +// (i) direct access using the mmio_ routines +// (ii) DIF-based buffer reading/writing + +#include "sw/device/lib/dif/dif_pinmux.h" +#include "sw/device/lib/dif/dif_usbdev.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 "hw/top_earlgrey/sw/autogen/top_earlgrey.h" // Generated. +#include "sw/device/lib/dif/autogen/dif_usbdev_autogen.h" + +#define USBDEV_BASE_ADDR TOP_EARLGREY_USBDEV_BASE_ADDR + +#ifndef USBDEV_BUFFER_REG_OFFSET +#define USBDEV_BUFFER_REG_OFFSET 0x800u +#endif + +// Simple LFSR for 8-bit sequences +/// Note: zero is an isolated state that shall be avoided +#define LFSR_ADVANCE(lfsr) \ + (uint8_t)( \ + (uint8_t)((lfsr) << 1) ^ \ + ((((lfsr) >> 1) ^ ((lfsr) >> 2) ^ ((lfsr) >> 3) ^ ((lfsr) >> 7)) & 1U)) + +// Total size of packet buffer memory in bytes +#define USBDEV_PACKET_MEM_SIZE (USBDEV_NUM_BUFFERS * USBDEV_MAX_PACKET_SIZE) + +// Basic test of packet memory function from the CPU side +static alignas(uint32_t) uint8_t testpatt[USBDEV_PACKET_MEM_SIZE]; +static alignas(uint32_t) uint8_t testcopy[USBDEV_PACKET_MEM_SIZE]; + +static dif_usbdev_t usbdev; + +OTTF_DEFINE_TEST_CONFIG(); + +/* The obvious advantage of using the DIF interface is that we are testing that + code too, but the disadvantage is that we have to resort to modifying the + buffer descriptors to perform reads because the buffers are not being used + in accordance with the expected flow model. +*/ + +static status_t mem_dif_write(const dif_usbdev_t *dev, const uint8_t *data, + size_t n) { + const unsigned nbufs = + (n + USBDEV_MAX_PACKET_SIZE - 1) / USBDEV_MAX_PACKET_SIZE; + TRY_CHECK(nbufs <= USBDEV_NUM_BUFFERS); + + for (unsigned id = 0u; id < nbufs; id++) { + TRY(dif_usbdev_buffer_raw_write(dev, (uint8_t)id, + &data[id * USBDEV_MAX_PACKET_SIZE], + USBDEV_MAX_PACKET_SIZE)); + } + + return OK_STATUS(); +} + +static status_t mem_dif_read(const dif_usbdev_t *dev, uint8_t *data, size_t n) { + const unsigned nbufs = + (n + USBDEV_MAX_PACKET_SIZE - 1) / USBDEV_MAX_PACKET_SIZE; + TRY_CHECK(nbufs <= USBDEV_NUM_BUFFERS); + + for (unsigned id = 0u; id < nbufs; id++) { + TRY(dif_usbdev_buffer_raw_read(dev, (uint8_t)id, + &data[id * USBDEV_MAX_PACKET_SIZE], + USBDEV_MAX_PACKET_SIZE)); + } + + return OK_STATUS(); +} + +// Perform a read from the usbdev packet memory, optionally using the DIF +// interface +static status_t mem_read(const dif_usbdev_t *dev, unsigned use_dif, + uint8_t *data, size_t n) { + if (use_dif) { + return mem_dif_read(dev, data, n); + } else { + mmio_region_memcpy_from_mmio32(dev->base_addr, USBDEV_BUFFER_REG_OFFSET, + data, n); + return OK_STATUS(); + } +} + +// Write a block of data to the usbdev packet memory, optionally using the DIF +// interface +static status_t mem_write(const dif_usbdev_t *dev, unsigned use_dif, + const uint8_t *data, size_t n) { + if (use_dif) { + return mem_dif_write(dev, data, n); + } else { + mmio_region_memcpy_to_mmio32(dev->base_addr, USBDEV_BUFFER_REG_OFFSET, data, + n); + return OK_STATUS(); + } +} + +bool test_main(void) { + LOG_INFO("Running USBDEV Mem Test"); + + // Initialize DIF-based access to the packet buffer memory + CHECK_DIF_OK( + dif_usbdev_init(mmio_region_from_addr(USBDEV_BASE_ADDR), &usbdev)); + + // Exercise mmio_ routines and then dif_usbdev_ routines in turn; it provides + // a bit more testing of the packet memory because the two routines exhibit + // different access patterns/timing. + for (unsigned use_dif = 0u; use_dif <= 1u; use_dif++) { + // Set up test pattern + uint8_t lfsr = 0x13u; + for (unsigned idx = 0u; idx < sizeof(testpatt); idx++) { + testpatt[idx] = lfsr; + lfsr = LFSR_ADVANCE(lfsr); + } + + // Write the generated test pattern to the usbdev packet memory + CHECK_STATUS_OK(mem_write(&usbdev, use_dif, testpatt, sizeof(testpatt))); + + // Invalidate the contents of the copy buffer + memset(testcopy, 0xffu, sizeof(testcopy)); + + // Read back a copy of the test pattern from the usbdev packet memory + CHECK_STATUS_OK( + mem_read(&usbdev, use_dif, testcopy, USBDEV_PACKET_MEM_SIZE)); + + // Check the copy against the original + CHECK_ARRAYS_EQ(testcopy, testpatt, sizeof(testcopy)); + + for (unsigned idx = 0u; idx < sizeof(testpatt); idx++) { + // Invert the test pattern to ensure every byte is modified + testcopy[idx] = ~testcopy[idx]; + } + + // Write the modified test pattern to the device memory + CHECK_STATUS_OK(mem_write(&usbdev, use_dif, testcopy, sizeof(testcopy))); + + // Invalidate the original test pattern buffer + memset(testpatt, 0xffu, sizeof(testpatt)); + + // Read back the modified test pattern from the device memory + CHECK_STATUS_OK(mem_read(&usbdev, use_dif, testpatt, sizeof(testpatt))); + + // Check against the modified test pattern + CHECK_ARRAYS_EQ(testpatt, testcopy, USBDEV_PACKET_MEM_SIZE); + } + + return true; +}