From 1be4445d3499f1cc6766a88a8cfabf2ffc2bbaa0 Mon Sep 17 00:00:00 2001 From: Adrian Lees Date: Thu, 5 Sep 2024 19:27:30 +0100 Subject: [PATCH] Simple bare metal CHERI check application for USB device. This does not require any special software on a physical USB host controller; `usbdev_check` will simply set up and connect the USB device to the USB host controller and await successful completion of the the normal device inspection and configuration sequence by the host. If the `do_disconnect` switch in usbdev_check.cc is set to false then serial comunications may also be tested by opening a terminal emulator on /dev/ttyUSB where the number 'n' may be ascertained by monitoring the output from dmesg when the USB device is being configured. --- sw/cheri/checks/CMakeLists.txt | 1 + sw/cheri/checks/uart_check.cc | 11 +- sw/cheri/checks/usbdev_check.cc | 152 +++++++++ sw/cheri/common/platform-usbdev.hh | 407 ++++++++++++++++++++++++ sw/cheri/common/usbdev-utils.hh | 493 +++++++++++++++++++++++++++++ sw/common/defs.h | 3 + 6 files changed, 1058 insertions(+), 9 deletions(-) create mode 100644 sw/cheri/checks/usbdev_check.cc create mode 100644 sw/cheri/common/platform-usbdev.hh create mode 100644 sw/cheri/common/usbdev-utils.hh diff --git a/sw/cheri/checks/CMakeLists.txt b/sw/cheri/checks/CMakeLists.txt index b5668eca..4b546600 100644 --- a/sw/cheri/checks/CMakeLists.txt +++ b/sw/cheri/checks/CMakeLists.txt @@ -8,6 +8,7 @@ set(CHECKS spi_test.cc revocation_test.cc rgbled_test.cc + usbdev_check.cc ) foreach(CHECK ${CHECKS}) diff --git a/sw/cheri/checks/uart_check.cc b/sw/cheri/checks/uart_check.cc index 99071cc8..2c8f49cc 100644 --- a/sw/cheri/checks/uart_check.cc +++ b/sw/cheri/checks/uart_check.cc @@ -8,19 +8,12 @@ #define CHERIOT_NO_NEW_DELETE #define CHERIOT_PLATFORM_CUSTOM_UART -#include "../../common/defs.h" #include -#include #include +#include "../common/uart-utils.hh" using namespace CHERI; -void write(volatile OpenTitanUart* uart, const char* str) { - for (; *str != '\0'; ++str) { - uart->blocking_write(*str); - } -} - /** * C++ entry point for the loader. This is called from assembly, with the * read-write root in the first argument. @@ -36,7 +29,7 @@ extern "C" void entry_point(void *rwRoot) uart.bounds() = UART_BOUNDS; uart->init(BAUD_RATE); - write(uart, "Hello There!\r\n"); + write_str(uart, "Hello There!\r\n"); char ch = '\n'; while (true) { diff --git a/sw/cheri/checks/usbdev_check.cc b/sw/cheri/checks/usbdev_check.cc new file mode 100644 index 00000000..1135f25b --- /dev/null +++ b/sw/cheri/checks/usbdev_check.cc @@ -0,0 +1,152 @@ +/** + * Copyright lowRISC contributors. + * Licensed under the Apache License, Version 2.0, see LICENSE for details. + * SPDX-License-Identifier: Apache-2.0 + */ + +#define CHERIOT_NO_AMBIENT_MALLOC +#define CHERIOT_NO_NEW_DELETE +#define CHERIOT_PLATFORM_CUSTOM_UART + +#include "../common/uart-utils.hh" +#include "../common/usbdev-utils.hh" + +#include + +#define LOG(...) write_str(uart, __VA_ARGS__) + +using namespace CHERI; + +// Shall we disconnect once we've been configured? +// +// - configuration indicates successful communication over the USB and the ability to send and +// receive data. For an FPGA, however, the host may be monitoring the USB serial connection +// (eg. /dev/ttyUSBx) and it may be useful to keep running and transmitting text. +// +// - if disconnection is not enabled then a terminator emulator may be attached to `/dev/ttyUSB` +// and a sign-on message should be observed. Any characters entered into the terminal will +// be echoed on the UART output. +static constexpr bool do_disconnect = true; + +static void write_strn(UartPtr uart, const char* str, size_t len) +{ + while (len-- > 0u) + { + uart->blocking_write(*str); + ++str; + } +} + +// Sign-on text after the USB device has been successfully configured. +// - this can be observed on a USB serial port of a physical host, eg. 'cat /dev/ttyUSB<>' +static const uint8_t _Alignas(uint32_t) signon[] = "Hello from CHERI USB!\r\n"; + +// Device Descriptor; see "Table 9-8. Standard Device Descriptor" +static const uint8_t _Alignas(uint32_t) dev_dscr[] = { + 0x12u, 1, 0, 2, 0, 0, 0, OpenTitanUsbdev::MaxPacketLen, + 0xd1, 0x18, 0x3a, 0x50, // Google lowRISC generic FS USB + 0, 1, 0, 0, 0, 1 +}; + +// Configuration Descriptor; see "Table 9-10. Standard Configuration Descriptor" +static const uint8_t _Alignas(uint32_t) cfg_dscr[] = { + // Present a single interface consisting of an IN EP and and OUT EP. + USB_CFG_DSCR_HEAD( + USB_CFG_DSCR_LEN + (USB_INTERFACE_DSCR_LEN + 2 * USB_EP_DSCR_LEN), + 1), + VEND_INTERFACE_DSCR(0, 2, 0x50, 1), + USB_BULK_EP_DSCR(0, 1, OpenTitanUsbdev::MaxPacketLen, 0), + USB_BULK_EP_DSCR(1, 1, OpenTitanUsbdev::MaxPacketLen, 4), +}; + +// Default test descriptor; required by the USB DPI model and retrieved using a Vendor Specific +// Control Transfer. Real USB host controllers will not retrieve this descriptor. +static const uint8_t _Alignas(uint32_t) test_dscr[] = { + USB_TESTUTILS_TEST_DSCR(0, 0, 0, 0, 0) +}; + +// Packet reception callback for endpoint 1 +static void rxCallback(void *rxHandle, uint8_t ep, bool setup, const uint8_t *data, uint16_t pktLen) +{ + Capability *uart; + uart = reinterpret_cast *>(rxHandle); + // Simply report any text to the UART output. + write_strn(*uart, reinterpret_cast(data), pktLen); +} + +[[noreturn]] +extern "C" void entry_point(void *rwRoot) +{ + // Buffer for data transfer to/from the USB device. +// uint8_t _Alignas(uint32_t) data[OpenTitanUsbdev::MaxPacketLen]; + + Capability root{rwRoot}; + + // Create a bounded capability to the UART + Capability uart = root.cast(); + uart.address() = UART_ADDRESS; + uart.bounds() = UART_BOUNDS; + + Capability usbdev = root.cast(); + usbdev.address() = USBDEV_ADDRESS; + usbdev.bounds() = USBDEV_BOUNDS; + + uart->init(BAUD_RATE); + + LOG("Initialising USB\r\n"); + + // Initialise the handling of the standard Control Transfer requests on the Default Control Pipe. + UsbdevUtils usb(usbdev, dev_dscr, sizeof(dev_dscr), cfg_dscr, sizeof(cfg_dscr), test_dscr, + sizeof(test_dscr)); + + // Configure IN/OUT endpoint pair 1 to act as a simple serial port. + bool ok = usb.setup_out_endpoint(1u, true, false, false, rxCallback, &uart); + if (ok) ok = usb.setup_in_endpoint(1u, true, false, nullptr, nullptr); + assert(ok); + + // Connect to the USB and indicate our presence to the USB host controller. + ok = usb.connect(); + assert(ok); + + // Note: be very careful about the use of UART-based logging after this point because neither the + // USB DPI model nor a physical USB host controller will wait around/retry transfers + // indefinitely and UART traffic can easily block the CPU waiting for FIFO space. + LOG("Connected\r\n"); // A brief progress indicator. + + bool sent = false; + while (true) + { + usb.service(); + + // Has the USB device been configured yet? + if (usb.configured()) + { + // Disconnect now that we've been configured successfully? + if (do_disconnect) + { + if (usbdev->connected()) + { + // Disconnect from the USB/host controller. + int rc = usbdev->disconnect(); + assert(!rc); + LOG("Test passed; disconnected from USB.\r\n"); + } + } + else if (!sent) + { + // Remain connected; send a sign-on message, and then echo any input from the simpleserial + // connection on the UART output. + if (usb.send_data(1U, reinterpret_cast(signon), sizeof(signon))) + { + LOG("Sent sign-on message over USB."); + sent = true; + } + else + { + // Packet could not be sent; notify via the UART. + LOG("Unable to send sign-on message over the USB."); + } + } + } + } +} diff --git a/sw/cheri/common/platform-usbdev.hh b/sw/cheri/common/platform-usbdev.hh new file mode 100644 index 00000000..a3115d8c --- /dev/null +++ b/sw/cheri/common/platform-usbdev.hh @@ -0,0 +1,407 @@ +/** + * Copyright lowRISC contributors. + * Licensed under the Apache License, Version 2.0, see LICENSE for details. + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once +#include +#include + +/** + * OpenTitan USB Device + * + * This peripheral's source and documentation can be found at: + * https://github.com/lowRISC/opentitan/tree/ab878b5d3578939a04db72d4ed966a56a869b2ed/hw/ip/usbdev + * + * Rendered register documentation is served at: + * https://opentitan.org/book/hw/ip/uart/doc/registers.html + */ +class OpenTitanUsbdev +{ + public: + /// USBDEV supports a maximum packet length of 64 bytes. + static constexpr uint8_t MaxPacketLen = 64U; + /// USBDEV provides 32 buffers. + static constexpr uint8_t NumBuffers = 32U; + /// USBDEV supports up to 12 endpoints, in each direction. + static constexpr uint8_t MaxEndpoints = 12U; + + /* Register definitions for the relevant parts of the OpenTitan USBDEV block; see above for + * documentation. + */ + + /** + * Interrupt State Register. + */ + uint32_t intrState; + /** + * Interrupt Enable Register. + */ + uint32_t intrEnable; + /** + * Interrupt Test Register. + */ + uint32_t intrTest; + /** + * Alert Test Register. + */ + uint32_t alertTest; + /** + * USB Control Register. + */ + uint32_t usbCtrl; + /** + * OUT Endpoint Enable Register. + */ + uint32_t epOutEnable; + /** + * IN Endpoint Enable Register. + */ + uint32_t epInEnable; + /** + * USB Status Register. + */ + uint32_t usbStat; + /** + * Available OUT Buffer FIFO. + */ + uint32_t avOutBuffer; + /** + * Available SETUP Buffer FIFO. + */ + uint32_t avSetupBuffer; + /** + * RX FIFO. + */ + uint32_t rxFIFO; + /** + * SETUP Reception Enable Register. + */ + uint32_t rxEnableSETUP; + /** + * OUT Reception Enable Register. + */ + uint32_t rxEnableOUT; + uint32_t pad0; + /** + * In Sent Register. + */ + uint32_t inSent; + /** + * Out STALL Register. + */ + uint32_t outStall; + /** + * In STALL Register. + */ + uint32_t inStall; + /** + * Config IN Registers. + */ + uint32_t configIn[MaxEndpoints]; + /** + * Out Iso Register. + */ + uint32_t outIso; + /** + * In Iso Register. + */ + uint32_t inIso; + /** + * Out Data Toggle Register. + */ + uint32_t outDataToggle; + /** + * In Data Toggle Register. + */ + uint32_t inDataToggle; + uint32_t pad1; + uint32_t pad2; + /** + * PHY Config Register. + */ + uint32_t phyConfig; + + /// USB Control Register Fields. + static constexpr uint32_t usbCtrlEnable = 1U; + static constexpr uint32_t usbCtrlDeviceAddr = 0x7F0000U; + static constexpr unsigned usbCtrlDeviceAddrShift = 16; + /// USB Status Register Fields. + static constexpr uint32_t usbStatAvOutFull = 0x800000U; + static constexpr uint32_t usbStatRxDepth = 0xF000000U; + static constexpr uint32_t usbStatAvSetupFull = 0x40000000U; + /// RX FIFO Register Fields. + static constexpr uint32_t rxFifoBuffer = 0x1FU; + static constexpr uint32_t rxFifoSize = 0x7F00U; + static constexpr uint32_t rxFifoSetup = 0x80000U; + static constexpr uint32_t rxFifoEp = 0xF00000U; + static constexpr unsigned rxFifoSizeShift = 8U; + static constexpr unsigned rxFifoEpShift = 20U; + /// Config In Register Fields. + static constexpr uint32_t configInBuffer = 0x1FU; + static constexpr uint32_t configInSending = 0x20000000U; + static constexpr uint32_t configInPend = 0x40000000U; + static constexpr uint32_t configInRdy = 0x80000000U; + static constexpr unsigned configInBufferShift = 0U; + static constexpr unsigned configInSizeShift = 8U; + /// PHY Config Register Fields. + static constexpr uint32_t phyConfigUseDiffRcvr = 1U; + + /** + * Ensure that the Available OUT Buffer and Available SETUP Buffers are kept supplied with + * buffers for packet reception. `buf_avail` specifies a bitmap of the buffers that are not + * currently committed and the return value is the updated bitmap. + */ + [[nodiscard]] uint64_t + supply_buffers(uint64_t buf_avail) volatile + { + for (uint8_t buf_num = 0U; buf_num < NumBuffers; buf_num++) + { + if (buf_avail & (1U << buf_num)) + { + if (usbStat & usbStatAvSetupFull) + { + if (usbStat & usbStatAvOutFull) + { + break; + } + avOutBuffer = buf_num; + } + else + { + avSetupBuffer = buf_num; + } + buf_avail &= ~(1U << buf_num); + } + } + return buf_avail; + } + + /** + * Initialise the USB device, ensuring that packet buffers are available for reception and that + * the PHY has been configured. Note that at this endpoints have not been configured and the + * device has not been connected to the USB. + */ + [[nodiscard]] int + init(uint64_t &buf_avail) volatile + { + buf_avail = supply_buffers(((uint64_t)1U << NumBuffers) - 1U); + phyConfig = phyConfigUseDiffRcvr; + return 0; + } + + /** + * Set up the configuration of an OUT endpoint. + */ + [[nodiscard]] int + configure_out_endpoint(uint8_t ep, bool enabled, bool setup, bool iso) volatile + { + if (ep < MaxEndpoints) + { + const uint32_t epMask = 1u << ep; + epOutEnable = (epOutEnable & ~epMask) | (enabled ? epMask : 0u); + rxEnableSETUP = (rxEnableSETUP & ~epMask) | (setup ? epMask : 0U); + rxEnableOUT = (rxEnableOUT & ~epMask) | (enabled ? epMask : 0u); + outIso = (outIso & ~epMask) | (iso ? epMask : 0u); + return 0; + } + return -1; + } + + /** + * Set up the configuration of an IN endpoint. + */ + [[nodiscard]] int + configure_in_endpoint(uint8_t ep, bool enabled, bool iso) volatile + { + if (ep < MaxEndpoints) + { + const uint32_t epMask = 1u << ep; + epInEnable = (epInEnable & ~epMask) | (enabled ? epMask : 0u); + inIso = (inIso & ~epMask) | (iso ? epMask : 0u); + return 0; + } + return -1; + } + + /** + * Set the STALL state of the specified endpoint pair (IN and OUT). + */ + [[nodiscard]] int + set_ep_stalling(uint8_t ep, bool stalling) volatile + { + if (ep < MaxEndpoints) + { + const uint32_t epMask = 1u << ep; + outStall = (outStall & ~epMask) | (stalling ? epMask : 0U); + inStall = (inStall & ~epMask) | (stalling ? epMask : 0U); + return 0; + } + return -1; + } + + /** + * Connect the device to the USB, indicating its presence to the USB host controller. + * Endpoints must already have been configured at this point because traffic may be received + * imminently. + */ + [[nodiscard]] int + connect() volatile + { + usbCtrl = usbCtrl | usbCtrlEnable; + return 0; + } + + /** + * Disconnect the device from the USB. + */ + [[nodiscard]] int + disconnect() volatile + { + usbCtrl = usbCtrl & ~usbCtrlEnable; + return 0; + } + + /** + * Indicate whether the USB device is connected (pullup enabled). + */ + [[nodiscard]] bool + connected() volatile + { + return (usbCtrl & usbCtrlEnable) != 0; + } + + /** + * Set the device address on the USB; this address will have been supplied by the USB host + * controller in the standard `SET_ADDRESS` Control Transfer. + */ + [[nodiscard]] int + set_device_address(uint8_t address) volatile + { + if (address < 0x80) + { + usbCtrl = (usbCtrl & ~usbCtrlDeviceAddr) | (address << usbCtrlDeviceAddrShift); + return 0; + } + return -1; + } + + /** + * Check for and return the endpoint number and buffer number of a recently-collected IN data + * packet. The caller is responsible for reusing or releasing the buffer. + */ + [[nodiscard]] int + packet_collected(uint8_t &ep, uint8_t &buf_num) volatile + { + uint32_t sent = inSent; + // Clear first packet sent indication. + for (ep = 0U; ep < MaxEndpoints; ep++) + { + uint32_t epMask = 1U << ep; + if (sent & epMask) + { + // Clear the `in_sent` bit for this specific endpoint. + inSent = epMask; + // Indicate which buffer has been released. + buf_num = (configIn[ep] & configInBuffer) >> configInBufferShift; + return 0; + } + } + return -1; + } + + /** + * Present a packet on the specified IN endpoint for collection by the USB host controller. + */ + [[nodiscard]] int + send_packet(uint8_t buf_num, uint8_t ep, const uint32_t *data, uint8_t size) volatile + { + // Transmission of Zero Length Packets is common over the USB. + if (size) + { + usbdev_transfer((uint32_t*)buf_base(0x800 + buf_num * MaxPacketLen), data, size, true); + } + configIn[ep] = (buf_num << configInBufferShift) | (size << configInSizeShift); + configIn[ep] = configIn[ep] | configInRdy; + return 0; + } + + /** + * Test for and collect the next received packet. + */ + [[nodiscard]] int + recv_packet(uint8_t &ep, uint8_t &buf_num, uint16_t &size, bool &is_setup, uint32_t *data) + volatile + { + if (usbStat & usbStatRxDepth) + { + uint32_t rx = rxFIFO; // FIFO, single word read required. + + ep = (rx & rxFifoEp) >> rxFifoEpShift; + size = (rx & rxFifoSize) >> rxFifoSizeShift; + is_setup = (rx & rxFifoSetup) != 0U; + buf_num = rx & rxFifoBuffer; + // Reception of Zero Length Packets occurs in the Status Stage of IN Control Transfers. + if (size) + { + usbdev_transfer(data, (uint32_t*)buf_base(0x800 + buf_num * MaxPacketLen), size, false); + } + return 0; + } + return -1; + } + + private: + /** + * Return a pointer to the given offset within the USB device register space; this is used to + * access the packet buffer memory. + */ + volatile uint32_t *buf_base(uint32_t offset) volatile + { + return (uint32_t*)((uintptr_t)this + offset); + } + + /** + * Faster, unrolled, word-based data transfer to/from the packet buffer memory. + */ + static void usbdev_transfer(uint32_t *dp, const uint32_t *sp, uint8_t len, bool to_dev) + { + const uint32_t *esp = (uint32_t*)((uintptr_t)sp + (len & ~15u)); + // Unrolled to mitigate the loop overheads. + while (sp < esp) { + dp[0] = sp[0]; + dp[1] = sp[1]; + dp[2] = sp[2]; + dp[3] = sp[3]; + dp += 4; + sp += 4; + } + len &= 15u; + // Copy the remaining whole words. + while (len >= 4u) { + *dp++ = *sp++; + len -= 4u; + } + // Tail bytes, handling the fact that USBDEV supports only 32-bit accesses. + if (len > 0u) { + if (to_dev) { + // Collect final bytes into a word. + const uint8_t *bsp = (uint8_t*)sp; + uint32_t d = bsp[0]; + if (len > 1u) d |= bsp[1] << 8; + if (len > 2u) d |= bsp[2] << 16; + // Write the final word to the device. + *dp = d; + } else { + uint8_t *bdp = (uint8_t*)dp; + // Collect the final word from the device. + uint32_t s = *sp; + // Unpack it into final bytes. + *bdp = (uint8_t)s; + if (len > 1u) bdp[1] = (uint8_t)(s >> 8); + if (len > 2u) bdp[2] = (uint8_t)(s >> 16); + } + } + } +}; diff --git a/sw/cheri/common/usbdev-utils.hh b/sw/cheri/common/usbdev-utils.hh new file mode 100644 index 00000000..abd6a3b8 --- /dev/null +++ b/sw/cheri/common/usbdev-utils.hh @@ -0,0 +1,493 @@ +/** + * Copyright lowRISC contributors. + * Licensed under the Apache License, Version 2.0, see LICENSE for details. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#include "platform-usbdev.hh" + +// 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; + +typedef enum usb_desc_type { // Descriptor type (wValue hi) + kUsbDescTypeDevice = 1, + kUsbDescTypeConfiguration, + kUsbDescTypeString, + kUsbDescTypeInterface, + kUsbDescTypeEndpoint, + kUsbDescTypeDeviceQualifier, + kUsbDescTypeOtherSpeedConfiguration, + kUsbDescTypeInterfacePower, +} usb_desc_type_t; + +// Vendor-specific requests defined by our device/test framework +typedef enum vendor_setup_req { + kVendorSetupReqTestConfig = 0x7C, + kVendorSetupReqTestStatus = 0x7E +} vendor_setup_req_t; + +/***********************************************************************/ +/* 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! + +/// Utility functions and state information about the USB device and the configuration sequence. +/// +/// This class provides the implementation of the Default Control Pipe and communicates with other +/// registered endpoints via callback functions. +class UsbdevUtils +{ + public: + UsbdevUtils(CHERI::Capability& dev, + const uint8_t *device, uint8_t device_len, // Device Descriptor. + const uint8_t *cfg, uint16_t cfg_len, // Configuration Descriptor. + const uint8_t *test, uint8_t test_len) : // Test Descriptor. + usbdev(dev), + devAddr(0u), + devState(Device_Powered), + ctrlState(Ctrl_Setup), + devDscr(device), + devLen(device_len), + cfgDscr(cfg), + cfgLen(cfg_len), + testDscr(test), + testLen(test_len), + bufAvail(((uint64_t)1u << OpenTitanUsbdev::NumBuffers) - 1u) + { + // Initialise the device and track which packet buffers are still available for use. + int rc = usbdev->init(bufAvail); + assert(!rc); + // Set up the Default Control Pipe. + // The USB host controller uses this endpoint to inspect and configure the device. + bool ok = setup_out_endpoint(0u, true, true, false, ep0_recv_cb, this); + if (ok) ok = setup_in_endpoint(0u, true, false, ep0_sent_cb, this); + assert(ok); + // Ensure that the remaining endpoints have defined state. + for (uint8_t ep = 1u; ep < OpenTitanUsbdev::MaxEndpoints; ++ep) + { + epOutCtx[ep].recvCallback = nullptr; + epInCtx[ep].txDoneCallback = nullptr; + } + // Ensure buffers are available for packet reception. + supply_buffers(); + } + + /// Connect the device to the USB. + bool connect() + { + int rc = usbdev->connect(); + if (rc) return false; + devState = Device_Powered; + return true; + } + + /// Disconnect the device from the USB. + bool disconnect() + { + int rc = usbdev->disconnect(); + if (rc) return false; + devState = Device_Powered; + return true; + } + + /// Indicate whether the device is connected to the USB (pullup enabled). + bool connected() + { + return usbdev->connected(); + } + + /// Allocate a buffer. + bool buf_alloc(uint8_t &bufNum) + { + if (bufAvail) { + for (bufNum = 0U; bufNum < OpenTitanUsbdev::NumBuffers; ++bufNum) { + if (1U & (bufAvail >> bufNum)) { + bufAvail &= ~((uint64_t)1U << bufNum); + return true; + } + } + } + return false; + } + + /// Release a buffer. + void buf_release(uint8_t bufNum) + { + // Try to make the buffer available for reception; the USB device can only have ownership of + // 20 buffers simultaneously on the reception side there are 32 buffers available. + bufAvail = usbdev->supply_buffers(bufAvail | ((uint64_t)1U << bufNum)); + } + + /// Ensure that the USB device remains supplied with buffers for packet reception. + void supply_buffers() + { + // Ensure that we keep buffers available for OUT packet reception. + bufAvail = usbdev->supply_buffers(bufAvail); + } + + // Packet reception callback handler. + typedef void (*UsbdevRecvCB)(void *handle, uint8_t ep, bool setup, const uint8_t *data, + uint16_t pktLen); + + // Transmission done callback handler. + typedef void (*UsbdevDoneCB)(void *handle, int rc); + + /// Configure an OUT endpoint within the USB device; packet reception invokes the supplied + /// callback function. + bool setup_out_endpoint(uint8_t ep, bool enabled, bool setup, bool iso, UsbdevRecvCB callback, + void *handle) + { + int rc = usbdev->configure_out_endpoint(ep, enabled, setup, iso); + if (rc) return false; + // Remember the callback function for this endpoint and its handle. + epOutCtx[ep].recvCallback = callback; + epOutCtx[ep].recvHandle = handle; + return true; + } + + /// Configure an IN endpoint within the USB device; packet collection from this endpoint results + /// in the supplied callback function being invoked. + bool setup_in_endpoint(uint8_t ep, bool enabled, bool iso, UsbdevDoneCB callback, void *handle) + { + int rc = usbdev->configure_in_endpoint(ep, enabled, iso); + if (rc) return false; + // Remember the callback function or this endpoint and its handle. + epInCtx[ep].txDoneCallback = callback; + epInCtx[ep].txDoneHandle = handle; + return true; + } + + /// Has the device been configured by the USB host controller? + bool configured() const { return devState == Device_Configured; } + + /// Send a packet of data over the USB to the host controller. + bool send_data(uint8_t ep, const uint32_t *data, uint16_t pktLen) + { + uint8_t bufNum; + assert(pktLen <= OpenTitanUsbdev::MaxPacketLen); + if (!buf_alloc(bufNum)) return false; + if (usbdev->send_packet(bufNum, ep, data, (uint8_t)pktLen)) + { + buf_release(bufNum); + return false; + } + return true; + } + + /// Service the USB device; this must be called regularly in order to minimise the latency of + /// responses to the USB host controller. + void service() + { + // Process the completion of any IN packets that have been collected by the USB host + // controller; this must be done promptly not just for performance reasons but also to ensure + // that IN Control Transfers conclude their Data Stage before the Status Stage commences. + uint8_t ep, bufNum; + int rc = usbdev->packet_collected(ep, bufNum); + while (!rc) + { + // Release the buffer for reuse. + buf_release(bufNum); + if (epInCtx[ep].txDoneCallback) + { + // Invoke the 'done' handler for this IN endpoint. + epInCtx[ep].txDoneCallback(epInCtx[ep].txDoneHandle, rc); + } + rc = usbdev->packet_collected(ep, bufNum); + } + + // Ensure that the packet reception FIFOs remains supplied with buffers. + supply_buffers(); + + // Process received packets. + uint16_t pktLen; + bool isSetup; + rc = usbdev->recv_packet(ep, bufNum, pktLen, isSetup, packetData); + while (!rc) + { + // Release the buffer for reuse. + buf_release(bufNum); + if (epOutCtx[ep].recvCallback) + { + const uint8_t *pktData = reinterpret_cast(packetData); + epOutCtx[ep].recvCallback(epOutCtx[ep].recvHandle, ep, isSetup, pktData, pktLen); + } + rc = usbdev->recv_packet(ep, bufNum, pktLen, isSetup, packetData); + } + } + +private: + /// Packet reception callback handler for endpoint zero. + static void ep0_recv_cb(void *handle, uint8_t ep, bool setup, const uint8_t *pktData, + uint16_t pktLen) + { + UsbdevUtils *utils = reinterpret_cast(handle); + utils->ep0_recv(ep, setup, pktData, pktLen); + } + /// Transmission done callback handler for endpoint zero. + static void ep0_sent_cb(void *handle, int rc) + { + UsbdevUtils *utils = reinterpret_cast(handle); + utils->ep0_sent(rc); + } + + /// Process the reception of a packet on the Default Control Pipe (Endpoint Zero). + void ep0_recv(uint8_t ep, bool setup, const uint8_t *data, uint16_t pktLen) + { + // Do we need to release the buffer? + bool release = true; + // Allocate a buffer for the reply. + uint8_t bufNum; + int rc; + bool ok = buf_alloc(bufNum); + assert(ok); + switch (ctrlState) + { + case Ctrl_Setup: + if (setup && pktLen == 8u) + { + // Received SETUP stage DATA packet. + uint16_t wValue = data[2] | (data[3] << 8); + uint16_t wLen = data[6] | (data[7] << 8); + switch (data[1]) + { + // GET_DESCRIPTOR requests. + case kUsbSetupReqGetDescriptor: + switch (data[3]) + { + case kUsbDescTypeDevice: + if (wLen > devLen) wLen = devLen; + rc = usbdev->send_packet(bufNum, 0u, (uint32_t*)devDscr, (uint8_t)wLen); + assert(!rc); + ctrlState = Ctrl_StatusGetDesc; + release = false; + break; + + case kUsbDescTypeConfiguration: + if (wLen > cfgLen) wLen = cfgLen; + rc = usbdev->send_packet(bufNum, 0u, (uint32_t*)cfgDscr, (uint8_t)wLen); + assert(!rc); + ctrlState = Ctrl_StatusGetDesc; + release = false; + break; + + default: + rc = usbdev->set_ep_stalling(0u, true); + assert(!rc); + break; + } + break; + + // SET_ADDRESS request. + case kUsbSetupReqSetAddress: + devAddr = (uint8_t)wValue; + // Send a Zero Length Packet as ACK. + rc = usbdev->send_packet(bufNum, 0u, nullptr, 0u); + assert(!rc); + ctrlState = Ctrl_StatusSetAddr; + release = false; + break; + + // SET_CONFIGURATION request. + case kUsbSetupReqSetConfiguration: + rc = usbdev->send_packet(bufNum, 0u, nullptr, 0u); // ZLP ACK. + assert(!rc); + ctrlState = Ctrl_StatusSetConfig; + release = false; + break; + + // Bespoke, vendor-defined Setup request to allow the USBDPI model to access the test + // configuration. + case kVendorSetupReqTestConfig: + if (wLen > testLen) wLen = testLen; + rc = usbdev->send_packet(bufNum, 0u, (uint32_t*)testDscr, (uint8_t)wLen); + assert(!rc); + ctrlState = Ctrl_StatusGetDesc; + release = false; + break; + + default: + ctrlState = Ctrl_Setup; + break; + } + } + break; + + case Ctrl_StatusGetDesc: + default: + ctrlState = Ctrl_Setup; + break; + } + // Release the buffer if it has not been used for a reply. + if (release) buf_release(bufNum); + } + + /// Process the collection of an IN packet on the Default Control Pipe (Endpoint Zero). + void ep0_sent(int rc) + { + switch (ctrlState) + { + case Ctrl_StatusSetAddr: + devState = Device_Addressed; + rc = usbdev->set_device_address(devAddr); + assert(!rc); + ctrlState = Ctrl_Setup; + break; + + case Ctrl_StatusSetConfig: + devState = Device_Configured; + ctrlState = Ctrl_Setup; + break; + + default: + ctrlState = Ctrl_Setup; + break; + } + } + + private: + // Access to the USB device driver. + CHERI::Capability usbdev; + + // Assigned device address on the USB. + uint8_t devAddr; + + // Device state + enum DeviceState { + Device_Powered, + Device_Addressed, + Device_Configured + } devState; + + // Control Transfer state + enum CtrlState { + Ctrl_Setup, + Ctrl_StatusSetAddr, + Ctrl_StatusGetDesc, + Ctrl_StatusSetConfig + } ctrlState; + + // Properties of Device Descriptor. + const uint8_t *devDscr; + uint8_t devLen; + // Properties of Configuration Descriptor. + const uint8_t *cfgDscr; + uint16_t cfgLen; + // Properties of Test Descriptor. + const uint8_t *testDscr; + uint8_t testLen; + + // Bitmap of available packet buffers. + uint64_t bufAvail; + + // Context for IN endpoints. + struct + { + // Callback handler function for packet transmissions that have completed. + UsbdevDoneCB txDoneCallback; + // Tx done handle. + void *txDoneHandle; + } epInCtx[OpenTitanUsbdev::MaxEndpoints]; + + // Context for OUT endpoints. + struct + { + // Callback handler function for received packets. + UsbdevRecvCB recvCallback; + // Callback handle. + void *recvHandle; + } epOutCtx[OpenTitanUsbdev::MaxEndpoints]; + + // Buffer for received packet data. + uint32_t packetData[OpenTitanUsbdev::MaxPacketLen >> 2]; +}; diff --git a/sw/common/defs.h b/sw/common/defs.h index cf7b10a0..80883b51 100644 --- a/sw/common/defs.h +++ b/sw/common/defs.h @@ -25,6 +25,9 @@ #define SPI_ADDRESS (0x8030'0000) #define SPI_BOUNDS (0x0000'0024) +#define USBDEV_ADDRESS (0x8040'0000) +#define USBDEV_BOUNDS (0x0000'1000) + #define HYPERRAM_ADDRESS (0x4000'0000) #define HYPERRAM_BOUNDS (0x0010'0000)