summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHa Thach <[email protected]>2026-04-02 11:31:11 +0700
committerGitHub <[email protected]>2026-04-02 11:31:11 +0700
commit871b843245666786857f54b7ffc1454d4acad7a0 (patch)
treefa1d515702aab8de32a957f85c5b38bf1d6bf0dc
parenteae25b531dff32636aecb21ca4a8a3ac8e391fc3 (diff)
parentb6f04f8643e5ca1efe2b8f9754427f5e5a0303e7 (diff)
Merge pull request #3561 from hathach/rp2040-hcd-epx
rp2 hcd rework: epx for non-interrupt and ping-pong double buffered
-rw-r--r--.claude/commands/build-doc.md24
-rw-r--r--.claude/commands/hil.md30
-rw-r--r--examples/host/cdc_msc_hid/src/msc_app.c6
-rw-r--r--examples/host/cdc_msc_hid_freertos/src/msc_app.c6
-rw-r--r--examples/host/msc_file_explorer/README.md105
-rw-r--r--examples/host/msc_file_explorer/src/msc_app.c81
-rw-r--r--hw/bsp/ch32v20x/boards/ch32v203g_r0_1v0/board.cmake1
-rw-r--r--hw/bsp/rp2040/family.c6
-rw-r--r--src/portable/raspberrypi/rp2040/dcd_rp2040.c292
-rw-r--r--src/portable/raspberrypi/rp2040/hcd_rp2040.c829
-rw-r--r--src/portable/raspberrypi/rp2040/rp2040_usb.c478
-rw-r--r--src/portable/raspberrypi/rp2040/rp2040_usb.h159
-rwxr-xr-xtest/hil/hil_test.py159
-rw-r--r--test/hil/tinyusb.json20
-rw-r--r--tools/metrics.py5
15 files changed, 1352 insertions, 849 deletions
diff --git a/.claude/commands/build-doc.md b/.claude/commands/build-doc.md
new file mode 100644
index 000000000..c9ad9f539
--- /dev/null
+++ b/.claude/commands/build-doc.md
@@ -0,0 +1,24 @@
+# build-doc
+
+Scan all example READMEs and build the Sphinx documentation.
+
+## Instructions
+
+1. Install docs dependencies:
+ ```bash
+ pip install -r docs/requirements.txt
+ ```
+
+2. Build the docs from the repo root:
+ ```bash
+ sphinx-build -b html docs docs/_build
+ ```
+ `conf.py` automatically scans all `examples/{device,host,dual}/*/README.md`, copies them into `docs/examples/`, and regenerates `examples.rst` with the toctree.
+
+3. Use a timeout of at least 60 seconds.
+
+4. After the build completes:
+ - Show the build output to the user.
+ - Report total warnings and errors.
+ - List which example READMEs were discovered and included.
+ - If there are errors, suggest fixes.
diff --git a/.claude/commands/hil.md b/.claude/commands/hil.md
new file mode 100644
index 000000000..2ba35ec22
--- /dev/null
+++ b/.claude/commands/hil.md
@@ -0,0 +1,30 @@
+# hil
+
+Run Hardware-in-the-Loop (HIL) tests on physical boards.
+
+## Arguments
+- $ARGUMENTS: Optional flags (e.g. board name, extra args). If empty, runs all boards with default config.
+
+## Instructions
+
+1. Determine the HIL config file:
+ ```bash
+ HIL_CONFIG=$( (systemctl list-units --type=service --state=running 2>/dev/null; systemctl --user list-units --type=service --state=running 2>/dev/null) | grep -q 'actions\.runner' && echo tinyusb.json || echo local.json )
+ ```
+ Default is `local.json` for local development.
+
+2. Parse $ARGUMENTS:
+ - If $ARGUMENTS contains `-b BOARD_NAME`, run for that specific board only.
+ - If $ARGUMENTS is empty or has no `-b`, run for all boards in the config.
+ - Pass through any other flags (e.g. `-v` for verbose) directly to the command.
+
+3. Run the HIL test from the repo root directory:
+ - Specific board: `python test/hil/hil_test.py -b BOARD_NAME -B examples $HIL_CONFIG $EXTRA_ARGS`
+ - All boards: `python test/hil/hil_test.py -B examples $HIL_CONFIG $EXTRA_ARGS`
+
+4. Use a timeout of at least 20 minutes (600000ms). HIL tests take 2-5 minutes. NEVER cancel early.
+
+5. After the test completes:
+ - Show the test output to the user.
+ - Summarize pass/fail results per board.
+ - If there are failures, suggest re-running with `-v` flag for verbose output to help debug.
diff --git a/examples/host/cdc_msc_hid/src/msc_app.c b/examples/host/cdc_msc_hid/src/msc_app.c
index dd4e22d7f..8181bdcb9 100644
--- a/examples/host/cdc_msc_hid/src/msc_app.c
+++ b/examples/host/cdc_msc_hid/src/msc_app.c
@@ -41,14 +41,14 @@ static bool inquiry_complete_cb(uint8_t dev_addr, tuh_msc_complete_data_t const
}
// Print out Vendor ID, Product ID and Rev
- printf("%.8s %.16s rev %.4s\r\n", inquiry_resp.vendor_id, inquiry_resp.product_id, inquiry_resp.product_rev);
+ printf("%.8s %.16s %.4s\r\n", inquiry_resp.vendor_id, inquiry_resp.product_id, inquiry_resp.product_rev);
// Get capacity of device
uint32_t const block_count = tuh_msc_get_block_count(dev_addr, cbw->lun);
uint32_t const block_size = tuh_msc_get_block_size(dev_addr, cbw->lun);
- printf("Disk Size: %" PRIu32 " MB\r\n", block_count / ((1024*1024)/block_size));
- printf("Block Count = %" PRIu32 ", Block Size: %" PRIu32 "\r\n", block_count, block_size);
+ printf("Disk Size: %" PRIu32 " %" PRIu32 "-byte blocks: %" PRIu32 " MB\r\n",
+ block_count, block_size, block_count / ((1024 * 1024) / block_size));
return true;
}
diff --git a/examples/host/cdc_msc_hid_freertos/src/msc_app.c b/examples/host/cdc_msc_hid_freertos/src/msc_app.c
index fa864c364..17df11951 100644
--- a/examples/host/cdc_msc_hid_freertos/src/msc_app.c
+++ b/examples/host/cdc_msc_hid_freertos/src/msc_app.c
@@ -47,14 +47,14 @@ static bool inquiry_complete_cb(uint8_t dev_addr, tuh_msc_complete_data_t const
}
// Print out Vendor ID, Product ID and Rev
- printf("%.8s %.16s rev %.4s\r\n", scsi_resp.inquiry.vendor_id, scsi_resp.inquiry.product_id, scsi_resp.inquiry.product_rev);
+ printf("%.8s %.16s %.4s\r\n", scsi_resp.inquiry.vendor_id, scsi_resp.inquiry.product_id, scsi_resp.inquiry.product_rev);
// Get capacity of device
uint32_t const block_count = tuh_msc_get_block_count(dev_addr, cbw->lun);
uint32_t const block_size = tuh_msc_get_block_size(dev_addr, cbw->lun);
- printf("Disk Size: %" PRIu32 " MB\r\n", block_count / ((1024 * 1024) / block_size));
- printf("Block Count = %" PRIu32 ", Block Size: %" PRIu32 "\r\n", block_count, block_size);
+ printf("Disk Size: %" PRIu32 " %" PRIu32 "-byte blocks: %" PRIu32 " MB\r\n",
+ block_count, block_size, block_count / ((1024 * 1024) / block_size));
return true;
}
diff --git a/examples/host/msc_file_explorer/README.md b/examples/host/msc_file_explorer/README.md
new file mode 100644
index 000000000..e220bedea
--- /dev/null
+++ b/examples/host/msc_file_explorer/README.md
@@ -0,0 +1,105 @@
+# MSC File Explorer
+
+This host example implements an interactive command-line file browser for USB Mass Storage devices.
+When a USB flash drive is connected, the device is automatically mounted using FatFS and a shell-like
+CLI is presented over the board's serial console.
+
+## Features
+
+- Automatic mount/unmount of USB storage devices
+- FAT12/16/32 filesystem support via FatFS
+- Interactive CLI with command history
+- Read speed benchmarking with `dd`
+- Support for up to 4 simultaneous USB storage devices (via hub)
+
+## Supported Commands
+
+| Command | Usage | Description |
+|---------|--------------------|------------------------------------------------------|
+| help | `help` | Print list of available commands |
+| cat | `cat <file>` | Print file contents to the console |
+| cd | `cd <dir>` | Change current working directory |
+| cp | `cp <src> <dest>` | Copy a file |
+| dd | `dd [count]` | Read sectors and report speed (default 1024 sectors) |
+| ls | `ls [dir]` | List directory contents |
+| pwd | `pwd` | Print current working directory |
+| mkdir | `mkdir <dir>` | Create a directory |
+| mv | `mv <src> <dest>` | Rename/move a file or directory |
+| rm | `rm <file>` | Remove a file |
+
+## Build
+
+Build for a specific board using CMake (see [Getting Started](https://docs.tinyusb.org/en/latest/getting_started.html)):
+
+```bash
+# Example: build for Raspberry Pi Pico
+cmake -B build -DBOARD=raspberry_pi_pico -DFAMILY=rp2040 examples/host/msc_file_explorer
+cmake --build build
+```
+
+## Usage
+
+1. Flash the firmware to your board.
+2. Open a serial terminal (e.g. `minicom`, `screen`, `PuTTY`) at 115200 baud.
+3. Plug a USB flash drive into the board's USB host port.
+4. The device is auto-mounted and the prompt appears:
+
+```
+TinyUSB MSC File Explorer Example
+
+Device connected
+ Vendor : Kingston
+ Product : DataTraveler 2.0
+ Rev : 1.0
+ Capacity: 1.9 GB
+
+0:/> _
+```
+
+### Browsing Files
+
+```
+0:/> ls
+----a 1234 readme.txt
+d---- 0 photos
+d---- 0 docs
+
+0:/> cd photos
+0:/photos> ls
+----a 520432 vacation.jpg
+----a 312088 family.png
+
+0:/> cat readme.txt
+Hello from USB drive!
+```
+
+### Copying and Moving Files
+
+```
+0:/> cp readme.txt backup.txt
+0:/> mv backup.txt docs/backup.txt
+```
+
+### Measuring Read Speed
+
+```
+0:/> dd
+Reading 1024 sectors...
+ Data speed: 823 KB/s
+```
+
+### Multiple Devices
+
+When using a USB hub, multiple drives are mounted as `0:`, `1:`, etc. Use the drive prefix to
+navigate between them:
+
+```
+0:/> cd 1:
+1:/> ls
+```
+
+## Testing
+
+This example is part of the TinyUSB HIL (Hardware-in-the-Loop) test suite. The HIL test
+automatically flashes, runs the example, and verifies MSC enumeration and file operations
+against a known USB drive.
diff --git a/examples/host/msc_file_explorer/src/msc_app.c b/examples/host/msc_file_explorer/src/msc_app.c
index 238af5431..c7cc366b5 100644
--- a/examples/host/msc_file_explorer/src/msc_app.c
+++ b/examples/host/msc_file_explorer/src/msc_app.c
@@ -46,7 +46,7 @@
#define CLI_RX_BUFFER_SIZE 16
#define CLI_CMD_BUFFER_SIZE 64
#define CLI_HISTORY_SIZE 32
-#define CLI_BINDING_COUNT 8
+#define CLI_BINDING_COUNT 9
static EmbeddedCli *_cli;
static CLI_UINT cli_buffer[BYTES_TO_CLI_UINTS(CLI_BUFFER_SIZE)];
@@ -56,7 +56,11 @@ static CFG_TUH_MEM_SECTION FATFS fatfs[CFG_TUH_DEVICE_MAX]; // for simplicity on
static volatile bool _disk_busy[CFG_TUH_DEVICE_MAX];
static CFG_TUH_MEM_SECTION FIL file1, file2;
-static CFG_TUH_MEM_SECTION uint8_t rw_buf[512];
+
+#ifndef CFG_EXAMPLE_MSC_FILE_EXPLORER_RW_BUFSIZE
+#define CFG_EXAMPLE_MSC_FILE_EXPLORER_RW_BUFSIZE 4096
+#endif
+static CFG_TUH_MEM_SECTION uint8_t rw_buf[CFG_EXAMPLE_MSC_FILE_EXPLORER_RW_BUFSIZE];
// define the buffer to be place in USB/DMA memory with correct alignment/cache line size
CFG_TUH_MEM_SECTION static struct {
@@ -114,15 +118,15 @@ static bool inquiry_complete_cb(uint8_t dev_addr, const tuh_msc_complete_data_t
}
// Print out Vendor ID, Product ID and Rev
- printf("%.8s %.16s rev %.4s\r\n", scsi_resp.inquiry.vendor_id, scsi_resp.inquiry.product_id,
+ printf("%.8s %.16s %.4s\r\n", scsi_resp.inquiry.vendor_id, scsi_resp.inquiry.product_id,
scsi_resp.inquiry.product_rev);
// Get capacity of device
const uint32_t block_count = tuh_msc_get_block_count(dev_addr, cbw->lun);
const uint32_t block_size = tuh_msc_get_block_size(dev_addr, cbw->lun);
- printf("Disk Size: %" PRIu32 " MB\r\n", block_count / ((1024 * 1024) / block_size));
- // printf("Block Count = %lu, Block Size: %lu\r\n", block_count, block_size);
+ printf("Disk Size: %" PRIu32 " %" PRIu32 "-byte blocks: %" PRIu32 " MB\r\n",
+ block_count, block_size, block_count / ((1024 * 1024) / block_size));
// For simplicity: we only mount 1 LUN per device
const uint8_t drive_num = dev_addr - 1;
@@ -279,6 +283,7 @@ DRESULT disk_ioctl(BYTE pdrv, /* Physical drive nmuber (0..) */
void cli_cmd_cat(EmbeddedCli *cli, char *args, void *context);
void cli_cmd_cd(EmbeddedCli *cli, char *args, void *context);
void cli_cmd_cp(EmbeddedCli *cli, char *args, void *context);
+void cli_cmd_dd(EmbeddedCli *cli, char *args, void *context);
void cli_cmd_ls(EmbeddedCli *cli, char *args, void *context);
void cli_cmd_pwd(EmbeddedCli *cli, char *args, void *context);
void cli_cmd_mkdir(EmbeddedCli *cli, char *args, void *context);
@@ -316,6 +321,9 @@ bool cli_init(void) {
embeddedCliAddBinding(_cli, (CliCommandBinding){"cp", "Usage: cp SOURCE DEST\r\n\tCopy SOURCE to DEST.", true, NULL,
cli_cmd_cp});
+ embeddedCliAddBinding(_cli, (CliCommandBinding){"dd", "Usage: dd [COUNT]\r\n\t" "Read COUNT sectors (default 1024) and report speed.", true, NULL,
+ cli_cmd_dd});
+
embeddedCliAddBinding(_cli, (CliCommandBinding){"ls",
"Usage: ls [DIR]...\r\n\tList information about the FILEs (the "
"current directory by default).",
@@ -339,6 +347,69 @@ bool cli_init(void) {
return true;
}
+void cli_cmd_dd(EmbeddedCli *cli, char *args, void *context) {
+ (void)cli;
+ (void)context;
+
+ uint32_t count = 1024; // default sectors to read
+ if (embeddedCliGetTokenCount(args) >= 1) {
+ count = (uint32_t)atoi(embeddedCliGetToken(args, 1));
+ if (count == 0) {
+ count = 1024;
+ }
+ }
+
+ // find first mounted MSC device
+ uint8_t dev_addr = 0;
+ for (uint8_t i = 1; i <= CFG_TUH_DEVICE_MAX; i++) {
+ if (tuh_msc_mounted(i)) {
+ dev_addr = i;
+ break;
+ }
+ }
+ if (dev_addr == 0) {
+ printf("no MSC device mounted\r\n");
+ return;
+ }
+
+ const uint8_t lun = 0;
+ const uint32_t block_size = tuh_msc_get_block_size(dev_addr, lun);
+ const uint32_t block_count = tuh_msc_get_block_count(dev_addr, lun);
+ if (count > block_count) {
+ count = block_count;
+ }
+
+ const uint16_t sectors_per_xfer = (uint16_t)(sizeof(rw_buf) / block_size);
+ const uint32_t xfer_count = (count + sectors_per_xfer - 1) / sectors_per_xfer;
+
+ printf("dd: reading %" PRIu32 " sectors (%" PRIu32 " bytes), %u sectors/xfer ...\r\n",
+ count, count * block_size, sectors_per_xfer);
+
+ const uint32_t start_ms = tusb_time_millis_api();
+ const uint8_t pdrv = dev_addr - 1;
+
+ for (uint32_t i = 0; i < count; i += sectors_per_xfer) {
+ const uint16_t n = (uint16_t)((count - i < sectors_per_xfer) ? (count - i) : sectors_per_xfer);
+ _disk_busy[pdrv] = true;
+ tuh_msc_read10(dev_addr, lun, rw_buf, i, n, disk_io_complete, 0);
+ wait_for_disk_io(pdrv);
+ }
+
+ const uint32_t elapsed_ms = tusb_time_millis_api() - start_ms;
+ const uint32_t total_data = count * block_size;
+ // each SCSI transaction has 31-byte CBW + data + 13-byte CSW
+ const uint32_t total_bus = total_data + xfer_count * (31 + 13);
+
+ if (elapsed_ms > 0) {
+ const uint32_t data_kbs = total_data / elapsed_ms; // KB/s (bytes/ms = KB/s)
+ const uint32_t bus_kbs = total_bus / elapsed_ms;
+ printf("dd: %" PRIu32 " bytes in %" PRIu32 " ms = %" PRIu32 " KB/s (bus %" PRIu32 " KB/s)\r\n",
+ total_data, elapsed_ms, data_kbs, bus_kbs);
+ } else {
+ printf("dd: %" PRIu32 " bytes in <1 ms\r\n", total_data);
+ }
+}
+
void cli_cmd_cat(EmbeddedCli *cli, char *args, void *context) {
(void)cli;
(void)context;
diff --git a/hw/bsp/ch32v20x/boards/ch32v203g_r0_1v0/board.cmake b/hw/bsp/ch32v20x/boards/ch32v203g_r0_1v0/board.cmake
index ecb8b378f..819a1ba1e 100644
--- a/hw/bsp/ch32v20x/boards/ch32v203g_r0_1v0/board.cmake
+++ b/hw/bsp/ch32v20x/boards/ch32v203g_r0_1v0/board.cmake
@@ -9,5 +9,6 @@ function(update_board TARGET)
target_compile_definitions(${TARGET} PUBLIC
SYSCLK_FREQ_144MHz_HSI=144000000
CFG_EXAMPLE_MSC_DUAL_READONLY
+ CFG_EXAMPLE_MSC_FILE_EXPLORER_RW_BUFSIZE=1024
)
endfunction()
diff --git a/hw/bsp/rp2040/family.c b/hw/bsp/rp2040/family.c
index adb58449d..c64025036 100644
--- a/hw/bsp/rp2040/family.c
+++ b/hw/bsp/rp2040/family.c
@@ -165,7 +165,11 @@ void board_init(void)
{
#if (CFG_TUH_ENABLED && CFG_TUH_RPI_PIO_USB) || (CFG_TUD_ENABLED && CFG_TUD_RPI_PIO_USB)
// Set the system clock to a multiple of 12mhz for bit-banging USB with pico-usb
- set_sys_clock_khz(120000, true);
+ #if defined(PICO_RP2350) && PICO_RP2350 == 1
+ set_sys_clock_khz(156000, true); // rp2350 default is 150Mhz
+ #else
+ set_sys_clock_khz(120000, true); // rp2040 default is 125Mhz
+ #endif
// set_sys_clock_khz(180000, true);
// set_sys_clock_khz(192000, true);
// set_sys_clock_khz(240000, true);
diff --git a/src/portable/raspberrypi/rp2040/dcd_rp2040.c b/src/portable/raspberrypi/rp2040/dcd_rp2040.c
index ca0e24e96..2b6bbc43b 100644
--- a/src/portable/raspberrypi/rp2040/dcd_rp2040.c
+++ b/src/portable/raspberrypi/rp2040/dcd_rp2040.c
@@ -69,63 +69,69 @@ TU_ATTR_ALWAYS_INLINE static inline hw_endpoint_t *hw_endpoint_get_by_addr(uint8
return hw_endpoint_get(num, dir);
}
-// main processing for dcd_edpt_iso_activate
-static void hw_endpoint_init(hw_endpoint_t *ep, uint8_t ep_addr, uint16_t wMaxPacketSize, uint8_t transfer_type) {
- ep->ep_addr = ep_addr;
- ep->next_pid = 0u;
- ep->wMaxPacketSize = wMaxPacketSize;
+TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *get_ep_ctrl(uint8_t epnum, tusb_dir_t dir) {
+ if (epnum == 0) {
+ // EP0 has no endpoint control register because the buffer offsets are fixed and always enabled
+ return NULL;
+ }
+ struct usb_device_dpram_ep_ctrl *ep_ctrl = &usb_dpram->ep_ctrl[epnum - 1];
+ return (dir == TUSB_DIR_IN) ? &ep_ctrl->in : &ep_ctrl->out;
+}
+
+TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *get_buf_ctrl(uint8_t epnum, tusb_dir_t dir) {
+ struct usb_device_dpram_ep_buf_ctrl *buf_ctrl = &usb_dpram->ep_buf_ctrl[epnum];
+ return (dir == TUSB_DIR_IN) ? &buf_ctrl->in : &buf_ctrl->out;
+}
+
+// Init and enable endpoint
+static void hw_endpoint_open(uint8_t ep_addr, uint16_t wMaxPacketSize, uint8_t transfer_type, bool ep_enabled) {
+ const uint8_t epnum = tu_edpt_number(ep_addr);
+ const tusb_dir_t dir = tu_edpt_dir(ep_addr);
+
+ hw_endpoint_t *ep = hw_endpoint_get(epnum, dir);
+ ep->ep_addr = ep_addr;
+ ep->next_pid = 0u;
+ ep->max_packet_size = wMaxPacketSize;
// Clear existing buffer control state
- io_rw_32 *buf_ctrl_reg = hwbuf_ctrl_reg_device(ep);
- *buf_ctrl_reg = 0;
+ io_rw_32 *buf_reg = get_buf_ctrl(epnum, dir);
+ *buf_reg = 0;
// allocated hw buffer
- const uint8_t epnum = tu_edpt_number(ep_addr);
if (epnum == 0) {
- // Buffer offset is fixed (also double buffered)
- ep->hw_data_buf = (uint8_t*) &usb_dpram->ep0_buf_a[0];
+ // Buffer offset is fixed (2 buffer allocated).
+ // Note: Only single buffer for EP since Double buffered RX can be troublesome with future data.
+ ep->dpram_buf = (uint8_t *)&usb_dpram->ep0_buf_a[0];
} else {
+ uint32_t ep_ctrl = EP_CTRL_INTERRUPT_PER_BUFFER | ((uint32_t)transfer_type << EP_CTRL_BUFFER_TYPE_LSB);
+
// round up size to multiple of 64
uint16_t size = (uint16_t)tu_round_up(wMaxPacketSize, 64);
// double buffered Bulk endpoint
if (transfer_type == TUSB_XFER_BULK) {
size *= 2u;
-
- #if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX
- if (tu_edpt_dir(ep_addr) == TUSB_DIR_IN) {
+ #if CFG_TUSB_RP2_ERRATA_E15
+ if (dir == TUSB_DIR_IN) {
ep->e15_bulk_in = true;
}
#endif
}
// assign buffer
- ep->hw_data_buf = hw_buffer_ptr;
+ ep->dpram_buf = hw_buffer_ptr;
hw_buffer_ptr += size;
- hard_assert(hw_buffer_ptr < usb_dpram->epx_data + sizeof(usb_dpram->epx_data));
- pico_info(" Allocated %d bytes (0x%p)\r\n", size, ep->hw_data_buf);
- }
-}
-
-static void hw_endpoint_enable(hw_endpoint_t *ep, uint8_t transfer_type) {
- io_rw_32 *ctrl_reg = hwep_ctrl_reg_device(ep);
- // Set endpoint control register to enable (EP0 has no endpoint control register)
- if (ctrl_reg != NULL) {
- const uint32_t ctrl_value =
- EP_CTRL_ENABLE_BITS | ((uint32_t)transfer_type << EP_CTRL_BUFFER_TYPE_LSB) | hw_data_offset(ep->hw_data_buf);
- *ctrl_reg = ctrl_value;
- }
-}
+ ep_ctrl |= hw_data_offset(ep->dpram_buf);
+ if (ep_enabled) {
+ ep_ctrl |= EP_CTRL_ENABLE_BITS;
+ }
-// Init and enable endpoint
-static void hw_endpoint_open(uint8_t ep_addr, uint16_t wMaxPacketSize, uint8_t transfer_type) {
- const uint8_t epnum = tu_edpt_number(ep_addr);
- const tusb_dir_t dir = tu_edpt_dir(ep_addr);
- hw_endpoint_t *ep = hw_endpoint_get(epnum, dir);
+ *get_ep_ctrl(epnum, dir) = ep_ctrl;
- hw_endpoint_init(ep, ep_addr, wMaxPacketSize, transfer_type);
- hw_endpoint_enable(ep, transfer_type);
+ hard_assert(hw_buffer_ptr < usb_dpram->epx_data + sizeof(usb_dpram->epx_data));
+ pico_info(" Allocated %d bytes (0x%p)\r\n", size, ep->dpram_buf);
+ }
}
static void hw_endpoint_abort_xfer(struct hw_endpoint* ep) {
@@ -141,14 +147,9 @@ static void hw_endpoint_abort_xfer(struct hw_endpoint* ep) {
while ((usb_hw->abort_done & abort_mask) != abort_mask) {}
}
- uint32_t buf_ctrl = USB_BUF_CTRL_SEL; // reset to buffer 0
- if (ep->next_pid) {
- buf_ctrl |= USB_BUF_CTRL_DATA1_PID;
- }
-
- io_rw_32 *buf_ctrl_reg = hwbuf_ctrl_reg_device(ep);
- hwbuf_ctrl_set(buf_ctrl_reg, buf_ctrl);
- hw_endpoint_reset_transfer(ep);
+ io_rw_32 *buf_reg = get_buf_ctrl(epnum, dir);
+ *buf_reg = 0; // clear buffer control
+ rp2usb_reset_transfer(ep);
if (rp2040_chip_version() >= 2) {
usb_hw_clear->abort_done = abort_mask;
@@ -157,29 +158,33 @@ static void hw_endpoint_abort_xfer(struct hw_endpoint* ep) {
}
static void __tusb_irq_path_func(handle_hw_buff_status)(void) {
- uint32_t remaining_buffers = usb_hw->buf_status;
- pico_trace("buf_status = 0x%08lx\r\n", remaining_buffers);
- uint bit = 1u;
- for (uint8_t i = 0; remaining_buffers && i < USB_MAX_ENDPOINTS * 2; i++) {
- if (remaining_buffers & bit) {
- // clear this in advance
- usb_hw_clear->buf_status = bit;
+ uint32_t buf_status = usb_hw->buf_status;
+ pico_trace("buf_status = 0x%08lx\r\n", buf_status);
+ while (buf_status) {
+ // ctz/clz is faster than loop which has only a few bit set in general
+ const uint8_t i = (uint8_t) __builtin_ctz(buf_status);
+ const uint32_t bit = TU_BIT(i);
- // IN transfer for even i, OUT transfer for odd i
- const uint8_t epnum = i >> 1u;
- const tusb_dir_t dir = (i & 1u) ? TUSB_DIR_OUT : TUSB_DIR_IN;
- hw_endpoint_t *ep = hw_endpoint_get(epnum, dir);
+ // IN transfer for even i, OUT transfer for odd i
+ const uint8_t epnum = i >> 1u;
+ const tusb_dir_t dir = (i & 1u) ? TUSB_DIR_OUT : TUSB_DIR_IN;
+ hw_endpoint_t *ep = hw_endpoint_get(epnum, dir);
+ io_rw_32 *ep_reg = get_ep_ctrl(epnum, dir);
+ io_rw_32 *buf_reg = get_buf_ctrl(epnum, dir);
- const bool done = hw_endpoint_xfer_continue(ep);
- if (done) {
- // Notify usbd
+ // Double-buffered: if both buffers completed at once, buf_status re-sets
+ // immediately after clearing (datasheet Table 406). Process the second buffer too.
+ while (usb_hw->buf_status & bit) {
+ const uint8_t buf_id = (usb_hw->buf_cpu_should_handle & bit) ? 1 : 0; // before clear buf_status
+ usb_hw_clear->buf_status = bit;
+ buf_status &= ~bit;
+
+ if (rp2usb_xfer_continue(ep, ep_reg, buf_reg, buf_id, dir == TUSB_DIR_OUT)) {
const uint16_t xferred_len = ep->xferred_len;
- hw_endpoint_reset_transfer(ep);
+ rp2usb_reset_transfer(ep);
dcd_event_xfer_complete(0, ep->ep_addr, xferred_len, XFER_RESULT_SUCCESS, true);
}
- remaining_buffers &= ~bit;
}
- bit <<= 1u;
}
}
@@ -189,7 +194,7 @@ TU_ATTR_ALWAYS_INLINE static inline void reset_ep0(void) {
for (uint8_t dir = 0; dir < 2; dir++) {
struct hw_endpoint *ep = hw_endpoint_get(0, dir);
ep->next_pid = 1u;
- if (ep->active) {
+ if (ep->state == EPSTATE_ACTIVE) {
hw_endpoint_abort_xfer(ep); // Abort any pending transfer per USB specs
}
}
@@ -211,52 +216,25 @@ static void __tusb_irq_path_func(reset_non_control_endpoints)(void) {
static void __tusb_irq_path_func(dcd_rp2040_irq)(void) {
const uint32_t status = usb_hw->ints;
- uint32_t handled = 0;
if (status & USB_INTF_DEV_SOF_BITS) {
- bool keep_sof_alive = false;
-
- handled |= USB_INTF_DEV_SOF_BITS;
-
-#if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX
- // Errata 15 workaround for Device Bulk-In endpoint
- e15_last_sof = time_us_32();
-
- for (uint8_t i = 0; i < USB_MAX_ENDPOINTS; i++) {
- struct hw_endpoint *ep = hw_endpoint_get(i, TUSB_DIR_IN);
-
- // Active Bulk IN endpoint requires SOF
- if (ep->e15_bulk_in && ep->active) {
- keep_sof_alive = true;
-
- hw_endpoint_lock_update(ep, 1);
- if (ep->pending) {
- ep->pending = 0;
- hw_endpoint_start_next_buffer(ep);
- }
- hw_endpoint_lock_update(ep, -1);
- }
- }
-#endif
+ uint32_t sof_count = usb_hw->sof_rd & USB_SOF_RD_BITS; // clear interrupt by reading SOF_RD
- // disable SOF interrupt if it is used for RESUME in remote wakeup
- if (!keep_sof_alive && !_sof_enable) {
- usb_hw_clear->inte = USB_INTS_DEV_SOF_BITS;
- }
+ #if CFG_TUSB_RP2_ERRATA_E15
+ e15_last_sof = time_us_32(); // timing critical
+ #endif
- dcd_event_sof(0, usb_hw->sof_rd & USB_SOF_RD_BITS, true);
+ dcd_event_sof(0, sof_count, true);
}
// xfer events are handled before setup req. So if a transfer completes immediately
// before closing the EP, the events will be delivered in same order.
if (status & USB_INTS_BUFF_STATUS_BITS) {
- handled |= USB_INTS_BUFF_STATUS_BITS;
handle_hw_buff_status();
}
if (status & USB_INTS_SETUP_REQ_BITS) {
- handled |= USB_INTS_SETUP_REQ_BITS;
- uint8_t const* setup = remove_volatile_cast(uint8_t const*, &usb_dpram->setup_packet);
+ const uint8_t *setup = remove_volatile_cast(const uint8_t *, &usb_dpram->setup_packet);
// reset pid to both 1 (data and ack)
reset_ep0();
@@ -266,19 +244,68 @@ static void __tusb_irq_path_func(dcd_rp2040_irq)(void) {
usb_hw_clear->sie_status = USB_SIE_STATUS_SETUP_REC_BITS;
}
-#if FORCE_VBUS_DETECT == 0
+ // Errata 15 workaround for Device Bulk-In endpoint, must be after BUF_STATUS interrupt to sync buf control first
+ if (status & USB_INTF_DEV_SOF_BITS) {
+ bool keep_sof_alive = false;
+
+ #if CFG_TUSB_RP2_ERRATA_E15
+ for (uint8_t i = 0; i < USB_MAX_ENDPOINTS; i++) {
+ struct hw_endpoint *ep = hw_endpoint_get(i, TUSB_DIR_IN);
+
+ // Active Bulk IN endpoint requires SOF
+ if (ep->e15_bulk_in && ep->state >= EPSTATE_ACTIVE) {
+ keep_sof_alive = true;
+ hw_endpoint_lock_update(ep, 1);
+
+ if (ep->state == EPSTATE_PENDING) {
+ ep->state = EPSTATE_ACTIVE;
+
+ io_rw_32 *buf_reg32 = get_buf_ctrl(i, TUSB_DIR_IN);
+ io_rw_16 *buf_reg16 = (io_rw_16 *)buf_reg32;
+
+ // Check each buffer half: idle when both FULL and AVAIL are clear.
+ // Use 16-bit writes to avoid clobbering the other half (DPSRAM concurrent access).
+ enum {
+ BUSY_MASK = USB_BUF_CTRL_FULL | USB_BUF_CTRL_AVAIL
+ };
+
+ const bool buf0_idle = !(buf_reg16[0] & BUSY_MASK);
+ const bool buf1_idle = (ep->remaining_len > 0) && !(buf_reg16[1] & BUSY_MASK);
+
+ if (buf0_idle && buf1_idle) {
+ // both are idle, start fresh
+ io_rw_32 *ep_reg = get_ep_ctrl(i, TUSB_DIR_IN);
+ rp2usb_buffer_start(ep, ep_reg, buf_reg32, false);
+ } else if (buf0_idle) {
+ uint16_t buf0 = bufctrl_prepare16(ep, ep->dpram_buf, false);
+ bufctrl_write16(buf_reg16, buf0);
+ } else if (buf1_idle) {
+ uint16_t buf1 = bufctrl_prepare16(ep, ep->dpram_buf + 64, false);
+ bufctrl_write16(buf_reg16 + 1, buf1);
+ }
+ }
+
+ hw_endpoint_lock_update(ep, -1);
+ }
+ }
+ #endif
+
+ // disable SOF interrupt if it is used for RESUME in remote wakeup
+ if (!keep_sof_alive && !_sof_enable) {
+ usb_hw_clear->inte = USB_INTS_DEV_SOF_BITS;
+ }
+ }
+
+ #if FORCE_VBUS_DETECT == 0
// Since we force VBUS detect On, device will always think it is connected and
// couldn't distinguish between disconnect and suspend
if (status & USB_INTS_DEV_CONN_DIS_BITS) {
- handled |= USB_INTS_DEV_CONN_DIS_BITS;
-
if (usb_hw->sie_status & USB_SIE_STATUS_CONNECTED_BITS) {
// Connected: nothing to do
} else {
// Disconnected
dcd_event_bus_signal(0, DCD_EVENT_UNPLUGGED, true);
}
-
usb_hw_clear->sie_status = USB_SIE_STATUS_CONNECTED_BITS;
}
#endif
@@ -286,9 +313,6 @@ static void __tusb_irq_path_func(dcd_rp2040_irq)(void) {
// SE0 for 2.5 us or more (will last at least 10ms)
if (status & USB_INTS_BUS_RESET_BITS) {
pico_trace("BUS RESET\r\n");
-
- handled |= USB_INTS_BUS_RESET_BITS;
-
usb_hw->dev_addr_ctrl = 0;
reset_non_control_endpoints();
dcd_event_bus_reset(0, TUSB_SPEED_FULL, true);
@@ -311,20 +335,15 @@ static void __tusb_irq_path_func(dcd_rp2040_irq)(void) {
* being disconnected and suspended.
*/
if (status & USB_INTS_DEV_SUSPEND_BITS) {
- handled |= USB_INTS_DEV_SUSPEND_BITS;
dcd_event_bus_signal(0, DCD_EVENT_SUSPEND, true);
usb_hw_clear->sie_status = USB_SIE_STATUS_SUSPENDED_BITS;
}
if (status & USB_INTS_DEV_RESUME_FROM_HOST_BITS) {
- handled |= USB_INTS_DEV_RESUME_FROM_HOST_BITS;
dcd_event_bus_signal(0, DCD_EVENT_RESUME, true);
usb_hw_clear->sie_status = USB_SIE_STATUS_RESUME_BITS;
}
- if (status ^ handled) {
- panic("Unhandled IRQ 0x%x\n", (uint) (status ^ handled));
- }
}
/*------------------------------------------------------------------*/
@@ -340,7 +359,7 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
(void) rh_init;
assert(rhport == 0);
- TU_LOG(2, "Chip Version B%u\r\n", rp2040_chip_version());
+ // TU_LOG(1, "Chip Version B%u\r\n", rp2040_chip_version());
// Reset hardware to default state
rp2usb_init();
@@ -354,8 +373,8 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
// Init control endpoints
tu_memclr(hw_endpoints[0], 2 * sizeof(hw_endpoint_t));
- hw_endpoint_open(0x0, 64, TUSB_XFER_CONTROL);
- hw_endpoint_open(0x80, 64, TUSB_XFER_CONTROL);
+ hw_endpoint_open(0x0, 64, TUSB_XFER_CONTROL, false);
+ hw_endpoint_open(0x80, 64, TUSB_XFER_CONTROL, false);
// Init non-control endpoints
reset_non_control_endpoints();
@@ -436,7 +455,7 @@ void dcd_sof_enable(uint8_t rhport, bool en) {
if (en) {
usb_hw_set->inte = USB_INTS_DEV_SOF_BITS;
}
-#if !TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX
+ #if !CFG_TUSB_RP2_ERRATA_E15
else {
// Don't clear immediately if the SOF workaround is in use.
// The SOF handler will conditionally disable the interrupt.
@@ -461,7 +480,7 @@ void dcd_edpt0_status_complete(uint8_t rhport, tusb_control_request_t const* req
bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const* desc_edpt) {
(void) rhport;
const uint8_t xfer_type = desc_edpt->bmAttributes.xfer;
- hw_endpoint_open(desc_edpt->bEndpointAddress, tu_edpt_packet_size(desc_edpt), xfer_type);
+ hw_endpoint_open(desc_edpt->bEndpointAddress, tu_edpt_packet_size(desc_edpt), xfer_type, true);
return true;
}
@@ -469,8 +488,7 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const* desc_edpt) {
// Some MCU need manual packet buffer allocation, we allocate the largest size to avoid clustering
bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) {
(void)rhport;
- struct hw_endpoint *ep = hw_endpoint_get_by_addr(ep_addr);
- hw_endpoint_init(ep, ep_addr, largest_packet_size, TUSB_XFER_ISOCHRONOUS);
+ hw_endpoint_open(ep_addr, largest_packet_size, TUSB_XFER_ISOCHRONOUS, false);
return true;
}
@@ -480,14 +498,18 @@ bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *ep_desc)
const uint8_t epnum = tu_edpt_number(ep_desc->bEndpointAddress);
const tusb_dir_t dir = tu_edpt_dir(ep_desc->bEndpointAddress);
struct hw_endpoint *ep = hw_endpoint_get(epnum, dir);
- TU_ASSERT(ep->hw_data_buf != NULL); // must be inited and allocated previously
+ TU_ASSERT(ep->dpram_buf != NULL); // must be inited and allocated previously
- if (ep->active) {
+ if (ep->state == EPSTATE_ACTIVE) {
hw_endpoint_abort_xfer(ep); // abort any pending transfer
}
- ep->wMaxPacketSize = ep_desc->wMaxPacketSize;
+ ep->max_packet_size = ep_desc->wMaxPacketSize;
- hw_endpoint_enable(ep, TUSB_XFER_ISOCHRONOUS);
+ // enable endpoint
+ io_rw_32 *ep_reg = get_ep_ctrl(epnum, dir);
+ if (ep_reg != NULL) {
+ *ep_reg |= EP_CTRL_ENABLE_BITS;
+ }
return true;
}
@@ -500,8 +522,13 @@ void dcd_edpt_close_all(uint8_t rhport) {
bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes, bool is_isr) {
(void)rhport;
(void)is_isr;
- hw_endpoint_t *ep = hw_endpoint_get_by_addr(ep_addr);
- hw_endpoint_xfer_start(ep, buffer, NULL, total_bytes);
+ const uint8_t epnum = tu_edpt_number(ep_addr);
+ const tusb_dir_t dir = tu_edpt_dir(ep_addr);
+
+ hw_endpoint_t *ep = hw_endpoint_get(epnum, dir);
+ io_rw_32 *ep_reg = get_ep_ctrl(epnum, dir);
+ io_rw_32 *buf_reg = get_buf_ctrl(epnum, dir);
+ rp2usb_xfer_start(ep, ep_reg, buf_reg, buffer, NULL, total_bytes);
return true;
}
@@ -509,8 +536,10 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t to
bool dcd_edpt_xfer_fifo(uint8_t rhport, uint8_t ep_addr, tu_fifo_t *ff, uint16_t total_bytes, bool is_isr) {
(void)rhport;
(void)is_isr;
- hw_endpoint_t *ep = hw_endpoint_get_by_addr(ep_addr);
- hw_endpoint_xfer_start(ep, NULL, ff, total_bytes);
+ hw_endpoint_t *ep = hw_endpoint_get(epnum, dir);
+ io_rw_32 *ep_reg = get_ep_ctrl(epnum, dir);
+ io_rw_32 *buf_reg = get_buf_ctrl(epnum, dir);
+ rp2usb_xfer_start(ep, ep_reg, buf_reg, NULL, ff, total_bytes);
return true;
}
#endif
@@ -526,21 +555,22 @@ void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) {
usb_hw_set->ep_stall_arm = (dir == TUSB_DIR_IN) ? USB_EP_STALL_ARM_EP0_IN_BITS : USB_EP_STALL_ARM_EP0_OUT_BITS;
}
- // stall and clear current pending buffer, may need to use EP_ABORT
- io_rw_32 *buf_ctrl_reg = hwbuf_ctrl_reg_device(ep);
- hwbuf_ctrl_set(buf_ctrl_reg, USB_BUF_CTRL_STALL);
+ // abort first then stall and clear current pending buffer
+ hw_endpoint_abort_xfer(ep);
+ io_rw_32 *buf_reg = get_buf_ctrl(epnum, dir);
+ *buf_reg = USB_BUF_CTRL_STALL;
}
void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) {
(void) rhport;
+ const uint8_t epnum = tu_edpt_number(ep_addr);
+ const tusb_dir_t dir = tu_edpt_dir(ep_addr);
- if (tu_edpt_number(ep_addr)) {
- struct hw_endpoint* ep = hw_endpoint_get_by_addr(ep_addr);
-
- // clear stall also reset toggle to DATA0, ready for next transfer
- ep->next_pid = 0;
- io_rw_32 *buf_ctrl_reg = hwbuf_ctrl_reg_device(ep);
- hwbuf_ctrl_clear_mask(buf_ctrl_reg, USB_BUF_CTRL_STALL);
+ if (epnum != 0) {
+ struct hw_endpoint* ep = hw_endpoint_get(epnum, dir);
+ ep->next_pid = 0; // reset data toggle
+ io_rw_32 *buf_reg = get_buf_ctrl(epnum, dir);
+ *buf_reg = 0;
}
}
diff --git a/src/portable/raspberrypi/rp2040/hcd_rp2040.c b/src/portable/raspberrypi/rp2040/hcd_rp2040.c
index 06c0ce340..02a4e055e 100644
--- a/src/portable/raspberrypi/rp2040/hcd_rp2040.c
+++ b/src/portable/raspberrypi/rp2040/hcd_rp2040.c
@@ -1,3 +1,4 @@
+
/*
* The MIT License (MIT)
*
@@ -29,48 +30,65 @@
#if CFG_TUH_ENABLED && (CFG_TUSB_MCU == OPT_MCU_RP2040) && !CFG_TUH_RPI_PIO_USB && !CFG_TUH_MAX3421
-#include "pico.h"
-#include "rp2040_usb.h"
-
-//--------------------------------------------------------------------+
-// INCLUDE
-//--------------------------------------------------------------------+
-#include "osal/osal.h"
+ #include "pico.h"
-#include "host/hcd.h"
-#include "host/usbh.h"
+ #if defined(PICO_RP2350) && PICO_RP2350 == 1
+ #define HAS_STOP_EPX_ON_NAK
+ #endif
// port 0 is native USB port, other is counted as software PIO
-#define RHPORT_NATIVE 0
+ #define RHPORT_NATIVE 0
+
+ //--------------------------------------------------------------------+
+ // INCLUDE
+ //--------------------------------------------------------------------+
+ #include "rp2040_usb.h"
+ #include "osal/osal.h"
+
+ #include "host/hcd.h"
+ #include "host/usbh.h"
//--------------------------------------------------------------------+
-// Low level rp2040 controller functions
+//
//--------------------------------------------------------------------+
-#ifndef PICO_USB_HOST_INTERRUPT_ENDPOINTS
-#define PICO_USB_HOST_INTERRUPT_ENDPOINTS (USB_MAX_ENDPOINTS - 1)
-#endif
-static_assert(PICO_USB_HOST_INTERRUPT_ENDPOINTS <= USB_MAX_ENDPOINTS, "");
-
// Host mode uses one shared endpoint register for non-interrupt endpoint
-static struct hw_endpoint ep_pool[1 + PICO_USB_HOST_INTERRUPT_ENDPOINTS];
-#define epx (ep_pool[0])
+static hw_endpoint_t ep_pool[USB_MAX_ENDPOINTS];
+static hw_endpoint_t *epx = &ep_pool[0]; // current active endpoint
+
+ #ifndef HAS_STOP_EPX_ON_NAK
+static volatile bool epx_switch_request = false;
+ #endif
+
+enum {
+ SIE_CTRL_SPEED_DISCONNECT = 0,
+ SIE_CTRL_SPEED_LOW = 1,
+ SIE_CTRL_SPEED_FULL = 2,
+};
-// Flags we set by default in sie_ctrl (we add other bits on top)
enum {
- SIE_CTRL_BASE = USB_SIE_CTRL_SOF_EN_BITS | USB_SIE_CTRL_KEEP_ALIVE_EN_BITS |
- USB_SIE_CTRL_PULLDOWN_EN_BITS | USB_SIE_CTRL_EP0_INT_1BUF_BITS
+ EPX_CTRL_DEFAULT = EP_CTRL_ENABLE_BITS | EP_CTRL_INTERRUPT_PER_BUFFER | offsetof(usb_host_dpram_t, epx_data)
};
-static struct hw_endpoint *get_dev_ep(uint8_t dev_addr, uint8_t ep_addr) {
- uint8_t num = tu_edpt_number(ep_addr);
- if (num == 0) {
- return &epx;
+//--------------------------------------------------------------------+
+//
+//--------------------------------------------------------------------+
+
+static hw_endpoint_t *edpt_alloc(void) {
+ for (uint i = 1; i < TU_ARRAY_SIZE(ep_pool); i++) {
+ hw_endpoint_t *ep = &ep_pool[i];
+ if (ep->max_packet_size == 0) {
+ return ep;
+ }
}
+ return NULL;
+}
- for (uint32_t i = 1; i < TU_ARRAY_SIZE(ep_pool); i++) {
- struct hw_endpoint *ep = &ep_pool[i];
- if (ep->configured && (ep->dev_addr == dev_addr) && (ep->ep_addr == ep_addr)) {
+static hw_endpoint_t *edpt_find(uint8_t daddr, uint8_t ep_addr) {
+ for (uint32_t i = 0; i < TU_ARRAY_SIZE(ep_pool); i++) {
+ hw_endpoint_t *ep = &ep_pool[i];
+ if ((ep->dev_addr == daddr) && (ep->max_packet_size > 0) &&
+ (ep->ep_addr == ep_addr || (tu_edpt_number(ep_addr) == 0 && tu_edpt_number(ep->ep_addr) == 0))) {
return ep;
}
}
@@ -78,6 +96,18 @@ static struct hw_endpoint *get_dev_ep(uint8_t dev_addr, uint8_t ep_addr) {
return NULL;
}
+TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *dpram_int_ep_ctrl(uint8_t int_num) {
+ return &usbh_dpram->int_ep_ctrl[int_num - 1].ctrl;
+}
+
+TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *dpram_int_ep_buffer_ctrl(uint8_t int_num) {
+ return &usbh_dpram->int_ep_buffer_ctrl[int_num - 1].ctrl;
+}
+
+//--------------------------------------------------------------------+
+//
+//--------------------------------------------------------------------+
+
TU_ATTR_ALWAYS_INLINE static inline uint8_t dev_speed(void) {
return (usb_hw->sie_status & USB_SIE_STATUS_SPEED_BITS) >> USB_SIE_STATUS_SPEED_LSB;
}
@@ -88,253 +118,300 @@ TU_ATTR_ALWAYS_INLINE static inline bool need_pre(uint8_t dev_addr) {
return hcd_port_speed_get(0) != tuh_speed_get(dev_addr);
}
-static void __tusb_irq_path_func(hw_xfer_complete)(struct hw_endpoint *ep, xfer_result_t xfer_result) {
- // Mark transfer as done before we tell the tinyusb stack
- uint8_t dev_addr = ep->dev_addr;
- uint8_t ep_addr = ep->ep_addr;
- uint xferred_len = ep->xferred_len;
- hw_endpoint_reset_transfer(ep);
- hcd_event_xfer_complete(dev_addr, ep_addr, xferred_len, xfer_result, true);
+//--------------------------------------------------------------------+
+// EPX
+//--------------------------------------------------------------------+
+TU_ATTR_ALWAYS_INLINE static inline void sie_stop_xfer(void) {
+ uint32_t sie_ctrl = (usb_hw->sie_ctrl & SIE_CTRL_BASE_MASK) | USB_SIE_CTRL_STOP_TRANS_BITS;
+ usb_hw->sie_ctrl = sie_ctrl;
+ while (usb_hw->sie_ctrl & USB_SIE_CTRL_STOP_TRANS_BITS) {}
}
-static void __tusb_irq_path_func(handle_hwbuf_status_bit)(uint bit, struct hw_endpoint *ep) {
- usb_hw_clear->buf_status = bit;
- const bool done = hw_endpoint_xfer_continue(ep);
- if (done) {
- hw_xfer_complete(ep, XFER_RESULT_SUCCESS);
+static void __tusb_irq_path_func(sie_start_xfer)(bool send_setup, bool is_rx, bool need_pre) {
+ uint32_t sie_ctrl = usb_hw->sie_ctrl & SIE_CTRL_BASE_MASK; // preserve base bits
+ if (send_setup) {
+ sie_ctrl |= USB_SIE_CTRL_SEND_SETUP_BITS;
+ } else {
+ sie_ctrl |= (is_rx ? USB_SIE_CTRL_RECEIVE_DATA_BITS : USB_SIE_CTRL_SEND_DATA_BITS);
+ }
+ if (need_pre) {
+ sie_ctrl |= USB_SIE_CTRL_PREAMBLE_EN_BITS;
}
+
+ // START_TRANS bit on SIE_CTRL has the same behavior as the AVAILABLE bit
+ // described in RP2040 Datasheet, release 2.1, section "4.1.2.5.1. Concurrent access".!
+ // We write everything except the START_TRANS bit first, then wait some cycles.
+ usb_hw->sie_ctrl = sie_ctrl;
+ busy_wait_at_least_cycles(12);
+ usb_hw->sie_ctrl = sie_ctrl | USB_SIE_CTRL_START_TRANS_BITS;
}
-static void __tusb_irq_path_func(handle_hwbuf_status)(void) {
- uint32_t buf_status = usb_hw->buf_status;
- pico_trace("buf_status 0x%08lx\n", buf_status);
+// prepare epx_ctrl register for new endpoint
+TU_ATTR_ALWAYS_INLINE static inline void epx_ctrl_prepare(uint8_t transfer_type) {
+ usbh_dpram->epx_ctrl = EPX_CTRL_DEFAULT | ((uint32_t)transfer_type << EP_CTRL_BUFFER_TYPE_LSB);
+}
- // Check EPX first
- uint32_t bit = 1u;
- if (buf_status & bit) {
- buf_status &= ~bit;
- struct hw_endpoint * ep = &epx;
- handle_hwbuf_status_bit(bit, ep);
- }
+// Save buffer context for EPX preemption (called after STOP_TRANS).
+// Undo PID toggle and buffer accounting for buffers NOT completed on the wire.
+// A buffer completed on wire means: controller reached STATUS phase (ACK received).
+// OUT completed: FULL cleared to 0 in STATUS phase (was 1 when armed)
+// IN completed: FULL set to 1 in STATUS phase (was 0 when armed)
+// So undo when: AVAIL=1 (never started), or (OUT: FULL=1) or (IN: FULL=0)
+static void __tusb_irq_path_func(epx_save_context)(hw_endpoint_t *ep) {
+ uint32_t buf_ctrl = usbh_dpram->epx_buf_ctrl;
+ const bool is_out = (tu_edpt_dir(ep->ep_addr) == TUSB_DIR_OUT);
- // Check "interrupt" (asynchronous) endpoints for both IN and OUT
- for (uint i = 1; i <= USB_HOST_INTERRUPT_ENDPOINTS && buf_status; i++) {
- // EPX is bit 0 & 1
- // IEP1 IN is bit 2
- // IEP1 OUT is bit 3
- // IEP2 IN is bit 4
- // IEP2 OUT is bit 5
- // IEP3 IN is bit 6
- // IEP3 OUT is bit 7
- // etc
- for (uint j = 0; j < 2; j++) {
- bit = 1 << (i * 2 + j);
- if (buf_status & bit) {
- buf_status &= ~bit;
- handle_hwbuf_status_bit(bit, &ep_pool[i]);
+ do {
+ const uint16_t bc16 = (uint16_t)buf_ctrl;
+ if (bc16) {
+ const bool avail = (bc16 & USB_BUF_CTRL_AVAIL);
+ const bool full = (bc16 & USB_BUF_CTRL_FULL);
+ if (avail || (is_out ? full : !full)) {
+ const uint16_t buf_len = bc16 & USB_BUF_CTRL_LEN_MASK;
+ ep->remaining_len += buf_len;
+ ep->next_pid ^= 1u;
+ if (is_out) {
+ ep->user_buf -= buf_len;
+ }
}
}
- }
- if (buf_status) {
- panic("Unhandled buffer %d\n", buf_status);
- }
-}
+ if (usbh_dpram->epx_ctrl & EP_CTRL_DOUBLE_BUFFERED_BITS) {
+ buf_ctrl >>= 16;
+ } else {
+ buf_ctrl = 0;
+ }
+ } while (buf_ctrl > 0);
-static void __tusb_irq_path_func(hw_trans_complete)(void)
-{
- if (usb_hw->sie_ctrl & USB_SIE_CTRL_SEND_SETUP_BITS)
- {
- pico_trace("Sent setup packet\n");
- struct hw_endpoint *ep = &epx;
- assert(ep->active);
- // Set transferred length to 8 for a setup packet
- ep->xferred_len = 8;
- hw_xfer_complete(ep, XFER_RESULT_SUCCESS);
- }
- else
- {
- // Don't care. Will handle this in buff status
- return;
- }
+ usbh_dpram->epx_buf_ctrl = 0;
+
+ ep->state = EPSTATE_PENDING;
}
-static void __tusb_irq_path_func(hcd_rp2040_irq)(void)
-{
- uint32_t status = usb_hw->ints;
- uint32_t handled = 0;
+// switch epx to new endpoint and start the transfer
+static void __tusb_irq_path_func(epx_switch_ep)(hw_endpoint_t *ep) {
+ const bool is_setup = (ep->state == EPSTATE_PENDING_SETUP);
- if ( status & USB_INTS_HOST_CONN_DIS_BITS )
- {
- handled |= USB_INTS_HOST_CONN_DIS_BITS;
+ epx = ep; // switch pointer
+ ep->state = EPSTATE_ACTIVE;
- if ( dev_speed() )
- {
- hcd_event_device_attach(RHPORT_NATIVE, true);
- }
- else
- {
- hcd_event_device_remove(RHPORT_NATIVE, true);
- }
+ if (is_setup) {
+ // panic("new setup \n");
+ usb_hw->dev_addr_ctrl = ep->dev_addr;
+ sie_start_xfer(true, false, ep->need_pre);
+ } else {
+ const bool is_rx = (tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN);
+ io_rw_32 *ep_reg = &usbh_dpram->epx_ctrl;
+ io_rw_32 *buf_reg = &usbh_dpram->epx_buf_ctrl;
- // Clear speed change interrupt
- usb_hw_clear->sie_status = USB_SIE_STATUS_SPEED_BITS;
- }
+ epx_ctrl_prepare(ep->transfer_type);
+ rp2usb_buffer_start(ep, ep_reg, buf_reg, is_rx);
- if ( status & USB_INTS_STALL_BITS )
- {
- // We have rx'd a stall from the device
- // NOTE THIS SHOULD HAVE PRIORITY OVER BUFF_STATUS
- // AND TRANS_COMPLETE as the stall is an alternative response
- // to one of those events
- pico_trace("Stall REC\n");
- handled |= USB_INTS_STALL_BITS;
- usb_hw_clear->sie_status = USB_SIE_STATUS_STALL_REC_BITS;
- hw_xfer_complete(&epx, XFER_RESULT_STALLED);
+ usb_hw->dev_addr_ctrl = (uint32_t)(ep->dev_addr | (tu_edpt_number(ep->ep_addr) << USB_ADDR_ENDP_ENDPOINT_LSB));
+ sie_start_xfer(is_setup, is_rx, ep->need_pre);
}
+}
- if ( status & USB_INTS_BUFF_STATUS_BITS )
- {
- handled |= USB_INTS_BUFF_STATUS_BITS;
- TU_LOG(2, "Buffer complete\r\n");
- handle_hwbuf_status();
+// Round-robin find next pending ep after current epx
+static hw_endpoint_t *__tusb_irq_path_func(epx_next_pending)(hw_endpoint_t *cur_ep) {
+ const uint cur_idx = (uint)(cur_ep - &ep_pool[0]);
+ for (uint i = cur_idx + 1; i < TU_ARRAY_SIZE(ep_pool); i++) {
+ if (ep_pool[i].state >= EPSTATE_PENDING) {
+ return &ep_pool[i];
+ }
}
-
- if ( status & USB_INTS_TRANS_COMPLETE_BITS )
- {
- handled |= USB_INTS_TRANS_COMPLETE_BITS;
- usb_hw_clear->sie_status = USB_SIE_STATUS_TRANS_COMPLETE_BITS;
- TU_LOG(2, "Transfer complete\r\n");
- hw_trans_complete();
+ for (uint i = 0; i < cur_idx; i++) {
+ if (ep_pool[i].state >= EPSTATE_PENDING) {
+ return &ep_pool[i];
+ }
}
+ return NULL;
+}
- if ( status & USB_INTS_ERROR_RX_TIMEOUT_BITS )
- {
- handled |= USB_INTS_ERROR_RX_TIMEOUT_BITS;
- usb_hw_clear->sie_status = USB_SIE_STATUS_RX_TIMEOUT_BITS;
- }
- if ( status & USB_INTS_ERROR_DATA_SEQ_BITS )
- {
- usb_hw_clear->sie_status = USB_SIE_STATUS_DATA_SEQ_ERROR_BITS;
- TU_LOG(3, " Seq Error: [0] = 0x%04u [1] = 0x%04x\r\n", tu_u32_low16(*hwbuf_ctrl_reg_host(&epx)),
- tu_u32_high16(*hwbuf_ctrl_reg_host(&epx)));
- panic("Data Seq Error \n");
- }
+//--------------------------------------------------------------------+
+// Interrupt handlers
+//--------------------------------------------------------------------+
+static void __tusb_irq_path_func(xfer_complete_isr)(hw_endpoint_t *ep, xfer_result_t xfer_result, bool is_more) {
+ // Mark transfer as done before we tell the tinyusb stack
+ uint32_t xferred_len = ep->xferred_len;
+ rp2usb_reset_transfer(ep);
+ hcd_event_xfer_complete(ep->dev_addr, ep->ep_addr, xferred_len, xfer_result, true);
- if ( status ^ handled )
- {
- panic("Unhandled IRQ 0x%x\n", (uint) (status ^ handled));
+ // Carry more transfer on epx
+ if (is_more) {
+ hw_endpoint_t *next_ep = epx_next_pending(epx);
+ if (next_ep != NULL) {
+ epx_switch_ep(next_ep);
+ }
}
}
-void __tusb_irq_path_func(hcd_int_handler)(uint8_t rhport, bool in_isr) {
- (void) rhport;
- (void) in_isr;
- hcd_rp2040_irq();
-}
+static void __tusb_irq_path_func(handle_buf_status_isr)(void) {
+ pico_trace("buf_status 0x%08lx\n", buf_status);
+ enum {
+ BUF_STATUS_EPX = 1u
+ };
-static struct hw_endpoint *_next_free_interrupt_ep(void)
-{
- struct hw_endpoint * ep = NULL;
- for ( uint i = 1; i < TU_ARRAY_SIZE(ep_pool); i++ )
- {
- ep = &ep_pool[i];
- if ( !ep->configured )
- {
- // Will be configured by hw_endpoint_init / hw_endpoint_allocate
- ep->interrupt_num = (uint8_t) (i - 1);
- return ep;
+ // Check EPX first (bit 0).
+ // Double-buffered: if both buffers completed at once, buf_status re-sets
+ // immediately after clearing (datasheet Table 406). Process the second buffer too.
+ while (usb_hw->buf_status & BUF_STATUS_EPX) {
+ const uint8_t buf_id = (usb_hw->buf_cpu_should_handle & BUF_STATUS_EPX) ? 1 : 0;
+ usb_hw_clear->buf_status = 1u; // clear
+
+ io_rw_32 *ep_reg = &usbh_dpram->epx_ctrl;
+ io_rw_32 *buf_reg = &usbh_dpram->epx_buf_ctrl;
+ #ifndef HAS_STOP_EPX_ON_NAK
+ // Any packet completion (mid-transfer or final) means data is flowing.
+ // Clear switch request so the 2-SOF fallback only fires for NAK-retrying endpoints.
+ epx_switch_request = false;
+ #endif
+ if (rp2usb_xfer_continue(epx, ep_reg, buf_reg, buf_id, tu_edpt_dir(epx->ep_addr) == TUSB_DIR_IN)) {
+ xfer_complete_isr(epx, XFER_RESULT_SUCCESS, true);
}
}
- return ep;
-}
-static hw_endpoint_t *hw_endpoint_allocate(uint8_t transfer_type) {
- hw_endpoint_t *ep = NULL;
+ // Check "interrupt" (asynchronous) endpoints for both IN and OUT
+ uint32_t buf_status = usb_hw->buf_status & ~(uint32_t)BUF_STATUS_EPX;
+ while (buf_status) {
+ // ctz/clz is faster than loop which has only a few bit set in general
+ const uint8_t idx = (uint8_t)__builtin_ctz(buf_status);
+ const uint32_t bit = TU_BIT(idx);
+ usb_hw_clear->buf_status = bit;
+ buf_status &= ~bit;
- if (transfer_type == TUSB_XFER_CONTROL) {
- ep = &epx;
- ep->hw_data_buf = &usbh_dpram->epx_data[0];
- } else {
- // Note: even though datasheet name these "Interrupt" endpoints. These are actually
- // "Asynchronous" endpoints and can be used for other type such as: Bulk (ISO need confirmation)
- ep = _next_free_interrupt_ep();
- pico_info("Allocate %s ep %d\n", tu_edpt_type_str(transfer_type), ep->interrupt_num);
- assert(ep);
- // 0 for epx (double buffered): TODO increase to 1024 for ISO
- // 2x64 for intep0
- // 3x64 for intep1
- // etc
- ep->hw_data_buf = &usbh_dpram->epx_data[64 * (ep->interrupt_num + 2)];
+ // IN transfer for even i, OUT transfer for odd i
+ // EPX is bit 0. Bit 1 is not used
+ // IEP1 IN/OUT is bit 2, 3
+ // IEP2 IN/OUT is bit 4, 5 etc
+ const uint8_t epnum = idx >> 1u;
+ for (size_t e = 0; e < TU_ARRAY_SIZE(ep_pool); e++) {
+ hw_endpoint_t *ep = &ep_pool[e];
+ if (ep->interrupt_num == epnum) {
+ io_rw_32 *ep_reg = dpram_int_ep_ctrl(ep->interrupt_num);
+ io_rw_32 *buf_reg = dpram_int_ep_buffer_ctrl(ep->interrupt_num);
+ const bool done = rp2usb_xfer_continue(ep, ep_reg, buf_reg, 0, tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN);
+ if (done) {
+ xfer_complete_isr(ep, XFER_RESULT_SUCCESS, false);
+ }
+ break;
+ }
+ }
}
-
- return ep;
}
-static void hw_endpoint_init(struct hw_endpoint *ep, uint8_t dev_addr, uint8_t ep_addr, uint16_t wMaxPacketSize,
- uint8_t transfer_type, uint8_t bmInterval) {
- // Already has data buffer, endpoint control, and buffer control allocated at this point
- assert(ep->hw_data_buf);
+static void __tusb_irq_path_func(hcd_rp2040_irq)(void) {
+ const uint32_t status = usb_hw->ints;
- uint8_t const num = tu_edpt_number(ep_addr);
- tusb_dir_t const dir = tu_edpt_dir(ep_addr);
+ if (status & USB_INTS_HOST_CONN_DIS_BITS) {
+ uint8_t speed = dev_speed();
+ if (speed == SIE_CTRL_SPEED_DISCONNECT) {
+ hcd_event_device_remove(RHPORT_NATIVE, true);
+ } else {
+ if (speed == SIE_CTRL_SPEED_LOW) {
+ usb_hw->sie_ctrl = SIE_CTRL_BASE | USB_SIE_CTRL_KEEP_ALIVE_EN_BITS;
+ } else {
+ usb_hw->sie_ctrl = SIE_CTRL_BASE | USB_SIE_CTRL_SOF_EN_BITS;
+ }
+ hcd_event_device_attach(RHPORT_NATIVE, true);
+ }
+ usb_hw_clear->sie_status = USB_SIE_STATUS_SPEED_BITS;
+ }
- ep->ep_addr = ep_addr;
- ep->dev_addr = dev_addr;
+ if (status & USB_INTS_STALL_BITS) {
+ usb_hw_clear->sie_status = USB_SIE_STATUS_STALL_REC_BITS;
+ xfer_complete_isr(epx, XFER_RESULT_STALLED, true);
+ }
- // Response to a setup packet on EP0 starts with pid of 1
- ep->next_pid = (num == 0 ? 1u : 0u);
- ep->wMaxPacketSize = wMaxPacketSize;
+ if (status & USB_INTS_ERROR_RX_TIMEOUT_BITS) {
+ usb_hw_clear->sie_status = USB_SIE_STATUS_RX_TIMEOUT_BITS;
- pico_trace("hw_endpoint_init dev %d ep %02X xfer %d\n", ep->dev_addr, ep->ep_addr, transfer_type);
- pico_trace("dev %d ep %02X setup buffer @ 0x%p\n", ep->dev_addr, ep->ep_addr, ep->hw_data_buf);
- uint dpram_offset = hw_data_offset(ep->hw_data_buf);
- // Bits 0-5 should be 0
- assert(!(dpram_offset & 0b111111));
+ const uint32_t sie_ctrl = (usb_hw->sie_ctrl & SIE_CTRL_BASE_MASK) | USB_SIE_CTRL_STOP_TRANS_BITS;
+ usb_hw->sie_ctrl = sie_ctrl;
+ // while (usb_hw->sie_ctrl & USB_SIE_CTRL_STOP_TRANS_BITS) {}
- // Fill in endpoint control register with buffer offset
- uint32_t ctrl_value = EP_CTRL_ENABLE_BITS | EP_CTRL_INTERRUPT_PER_BUFFER |
- ((uint32_t)transfer_type << EP_CTRL_BUFFER_TYPE_LSB) | dpram_offset;
- if (bmInterval) {
- ctrl_value |= (uint32_t)((bmInterval - 1) << EP_CTRL_HOST_INTERRUPT_INTERVAL_LSB);
+ // Even if STOP_TRANS bit is clear, controller maybe in middle of retrying and may re-raise timeout once extra time
+ // Only handle if epx is active, don't carry more epx transfer since STOP_TRANS is raced and not safe.
+ if (epx->state == EPSTATE_ACTIVE) {
+ xfer_complete_isr(epx, XFER_RESULT_FAILED, false);
+ }
}
- io_rw_32 *ctrl_reg = hwep_ctrl_reg_host(ep);
- *ctrl_reg = ctrl_value;
- pico_trace("endpoint control (0x%p) <- 0x%lx\n", ctrl_reg, ctrl_value);
- ep->configured = true;
+ if (status & USB_INTS_TRANS_COMPLETE_BITS) {
+ // only applies for epx, interrupt endpoint does not seem to raise this
+ usb_hw_clear->sie_status = USB_SIE_STATUS_TRANS_COMPLETE_BITS;
+ if (usb_hw->sie_ctrl & USB_SIE_CTRL_SEND_SETUP_BITS) {
+ uint32_t sie_ctrl = usb_hw->sie_ctrl & SIE_CTRL_BASE_MASK;
+ usb_hw->sie_ctrl = sie_ctrl; // clear setup bit
+ epx->xferred_len = 8;
+ xfer_complete_isr(epx, XFER_RESULT_SUCCESS, true);
+ }
+ }
- if (ep != &epx) {
- // Endpoint has its own addr_endp and interrupt bits to be setup!
- // This is an interrupt/async endpoint. so need to set up ADDR_ENDP register with:
- // - device address
- // - endpoint number / direction
- // - preamble
- uint32_t reg = (uint32_t)(dev_addr | (num << USB_ADDR_ENDP1_ENDPOINT_LSB));
+ if (status & USB_INTS_BUFF_STATUS_BITS) {
+ handle_buf_status_isr();
+ }
- if (dir == TUSB_DIR_OUT) {
- reg |= USB_ADDR_ENDP1_INTEP_DIR_BITS;
+ // SOF-based round-robin MUST run BEFORE BUFF_STATUS to avoid processing
+ // buf_status on the wrong EPX after a completion+switch in handle_buf_status_isr.
+ #ifdef HAS_STOP_EPX_ON_NAK
+ if (status & USB_INTS_EPX_STOPPED_ON_NAK_BITS) {
+ usb_hw_clear->nak_poll = USB_NAK_POLL_EPX_STOPPED_ON_NAK_BITS;
+ hw_endpoint_t *next_ep = epx_next_pending(epx);
+ if (next_ep != NULL) {
+ epx_save_context(epx);
+ epx_switch_ep(next_ep);
+ } else {
+ usb_hw_clear->nak_poll = USB_NAK_POLL_STOP_EPX_ON_NAK_BITS;
+ sie_start_xfer(false, TUSB_DIR_IN == tu_edpt_dir(epx->ep_addr), epx->need_pre);
}
-
- if (need_pre(dev_addr)) {
- reg |= USB_ADDR_ENDP1_INTEP_PREAMBLE_BITS;
+ }
+ #else
+ // RP2040: on SOF, switch EPX if another endpoint is pending.
+ // First SOF sets epx_switch_request. If a transfer completes before next SOF, the flag is
+ // cleared (data is flowing, no need to force-switch). Second SOF with flag still set means
+ // no data exchanged (endpoint NAK-retrying): STOP_TRANS is safe and we switch.
+ // This avoids stopping mid-data-transfer which corrupts double-buffered PID tracking.
+ if (status & USB_INTS_HOST_SOF_BITS) {
+ (void)usb_hw->sof_rd; // clear SOF by reading SOF_RD
+ hw_endpoint_t *next_ep = epx_next_pending(epx);
+ if (next_ep == NULL) {
+ usb_hw_clear->inte = USB_INTE_HOST_SOF_BITS;
+ usb_hw->nak_poll = USB_NAK_POLL_RESET;
+ epx_switch_request = false;
+ } else if (epx->state == EPSTATE_ACTIVE) {
+ if (epx_switch_request) {
+ // Second SOF with no transfer completion: endpoint is NAK-retrying, safe to switch.
+ epx_switch_request = false;
+ sie_stop_xfer();
+ epx_save_context(epx);
+ epx_switch_ep(next_ep);
+ } else {
+ epx_switch_request = true;
+ }
}
- usb_hw->int_ep_addr_ctrl[ep->interrupt_num] = reg;
-
- // Finally, enable interrupt that endpoint
- usb_hw_set->int_ep_ctrl = 1 << (ep->interrupt_num + 1);
+ }
+ #endif
- // If it's an interrupt endpoint we need to set up the buffer control register
+ if (status & USB_INTS_ERROR_DATA_SEQ_BITS) {
+ usb_hw_clear->sie_status = USB_SIE_STATUS_DATA_SEQ_ERROR_BITS;
+ panic("Data Seq Error \n");
}
}
+void __tusb_irq_path_func(hcd_int_handler)(uint8_t rhport, bool in_isr) {
+ (void)rhport;
+ (void)in_isr;
+ hcd_rp2040_irq();
+}
+
//--------------------------------------------------------------------+
// HCD API
//--------------------------------------------------------------------+
-bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
- (void) rhport;
- (void) rh_init;
+bool hcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) {
+ (void)rhport;
+ (void)rh_init;
pico_trace("hcd_init %d\n", rhport);
assert(rhport == 0);
@@ -346,7 +423,6 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
// Remove shared irq if it was previously added so as not to fill up shared irq slots
irq_remove_handler(USBCTRL_IRQ, hcd_rp2040_irq);
-
irq_add_shared_handler(USBCTRL_IRQ, hcd_rp2040_irq, PICO_SHARED_IRQ_HANDLER_HIGHEST_ORDER_PRIORITY);
// clear epx and interrupt eps
@@ -354,97 +430,83 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
// Enable in host mode with SOF / Keep alive on
usb_hw->main_ctrl = USB_MAIN_CTRL_CONTROLLER_EN_BITS | USB_MAIN_CTRL_HOST_NDEVICE_BITS;
- usb_hw->sie_ctrl = SIE_CTRL_BASE;
- usb_hw->inte = USB_INTE_BUFF_STATUS_BITS |
- USB_INTE_HOST_CONN_DIS_BITS |
- USB_INTE_HOST_RESUME_BITS |
- USB_INTE_STALL_BITS |
- USB_INTE_TRANS_COMPLETE_BITS |
- USB_INTE_ERROR_RX_TIMEOUT_BITS |
- USB_INTE_ERROR_DATA_SEQ_BITS ;
+ usb_hw->sie_ctrl = SIE_CTRL_BASE;
+ usb_hw->inte = USB_INTE_BUFF_STATUS_BITS | USB_INTE_HOST_CONN_DIS_BITS | USB_INTE_HOST_RESUME_BITS |
+ USB_INTE_STALL_BITS | USB_INTE_TRANS_COMPLETE_BITS | USB_INTE_ERROR_RX_TIMEOUT_BITS |
+ USB_INTE_ERROR_DATA_SEQ_BITS;
+
+ #ifdef HAS_STOP_EPX_ON_NAK
+ usb_hw_set->inte = USB_INTE_EPX_STOPPED_ON_NAK_BITS;
+ #endif
return true;
}
bool hcd_deinit(uint8_t rhport) {
- (void) rhport;
-
+ (void)rhport;
irq_remove_handler(USBCTRL_IRQ, hcd_rp2040_irq);
reset_block(RESETS_RESET_USBCTRL_BITS);
unreset_block_wait(RESETS_RESET_USBCTRL_BITS);
-
return true;
}
-void hcd_port_reset(uint8_t rhport)
-{
- (void) rhport;
- pico_trace("hcd_port_reset\n");
- assert(rhport == 0);
+void hcd_port_reset(uint8_t rhport) {
+ (void)rhport;
// TODO: Nothing to do here yet. Perhaps need to reset some state?
}
-void hcd_port_reset_end(uint8_t rhport)
-{
- (void) rhport;
+void hcd_port_reset_end(uint8_t rhport) {
+ (void)rhport;
}
-bool hcd_port_connect_status(uint8_t rhport)
-{
- (void) rhport;
- pico_trace("hcd_port_connect_status\n");
- assert(rhport == 0);
+bool hcd_port_connect_status(uint8_t rhport) {
+ (void)rhport;
return usb_hw->sie_status & USB_SIE_STATUS_SPEED_BITS;
}
-tusb_speed_t hcd_port_speed_get(uint8_t rhport)
-{
- (void) rhport;
- assert(rhport == 0);
-
- // TODO: Should enumval this register
- switch ( dev_speed() )
- {
- case 1:
+tusb_speed_t hcd_port_speed_get(uint8_t rhport) {
+ (void)rhport;
+ switch (dev_speed()) {
+ case SIE_CTRL_SPEED_LOW:
return TUSB_SPEED_LOW;
- case 2:
+ case SIE_CTRL_SPEED_FULL:
return TUSB_SPEED_FULL;
default:
- panic("Invalid speed\n");
- // return TUSB_SPEED_INVALID;
+ return TUSB_SPEED_INVALID;
}
}
// Close all opened endpoint belong to this device
void hcd_device_close(uint8_t rhport, uint8_t dev_addr) {
- pico_trace("hcd_device_close %d\n", dev_addr);
- (void) rhport;
+ (void)rhport;
- // reset epx if it is currently active with unplugged device
- if (epx.configured && epx.active && epx.dev_addr == dev_addr) {
- epx.configured = false;
- *hwep_ctrl_reg_host(&epx) = 0;
- *hwbuf_ctrl_reg_host(&epx) = 0;
- hw_endpoint_reset_transfer(&epx);
+ if (dev_addr == 0) {
+ return; // address 0 is for device enumeration
}
- // dev0 only has ep0
- if (dev_addr != 0) {
- for (size_t i = 1; i < TU_ARRAY_SIZE(ep_pool); i++) {
- hw_endpoint_t *ep = &ep_pool[i];
- if (ep->dev_addr == dev_addr && ep->configured) {
- // in case it is an interrupt endpoint, disable it
- usb_hw_clear->int_ep_ctrl = (1 << (ep->interrupt_num + 1));
- usb_hw->int_ep_addr_ctrl[ep->interrupt_num] = 0;
+ rp2usb_critical_enter();
- // unconfigure the endpoint
- ep->configured = false;
- *hwep_ctrl_reg_host(ep) = 0;
- *hwbuf_ctrl_reg_host(ep) = 0;
- hw_endpoint_reset_transfer(ep);
+ for (size_t i = 0; i < TU_ARRAY_SIZE(ep_pool); i++) {
+ hw_endpoint_t *ep = &ep_pool[i];
+ if (ep->dev_addr == dev_addr && ep->max_packet_size > 0) {
+ ep->state = EPSTATE_IDLE; // clear any pending transfer
+
+ if (ep->interrupt_num > 0) {
+ // disable interrupt endpoint
+ usb_hw_clear->int_ep_ctrl = TU_BIT(ep->interrupt_num);
+ usb_hw->int_ep_addr_ctrl[ep->interrupt_num - 1] = 0;
+
+ io_rw_32 *ep_reg = dpram_int_ep_ctrl(ep->interrupt_num);
+ io_rw_32 *buf_reg = dpram_int_ep_buffer_ctrl(ep->interrupt_num);
+ *buf_reg = 0;
+ *ep_reg = 0;
}
+
+ ep->max_packet_size = 0; // mark as unused
}
}
+
+ rp2usb_critical_exit();
}
uint32_t hcd_frame_number(uint8_t rhport) {
@@ -469,122 +531,171 @@ void hcd_int_disable(uint8_t rhport) {
bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_endpoint_t *ep_desc) {
(void)rhport;
pico_trace("hcd_edpt_open dev_addr %d, ep_addr %d\n", dev_addr, ep_desc->bEndpointAddress);
- hw_endpoint_t *ep = hw_endpoint_allocate(ep_desc->bmAttributes.xfer);
+ hw_endpoint_t *ep;
+ if (dev_addr == 0) {
+ ep = &ep_pool[0];
+ } else {
+ ep = edpt_alloc();
+ }
TU_ASSERT(ep);
- hw_endpoint_init(ep, dev_addr, ep_desc->bEndpointAddress, tu_edpt_packet_size(ep_desc), ep_desc->bmAttributes.xfer,
- ep_desc->bInterval);
+ const uint8_t ep_addr = ep_desc->bEndpointAddress;
+ const uint16_t max_packet_size = tu_edpt_packet_size(ep_desc);
+
+ ep->max_packet_size = max_packet_size;
+ ep->ep_addr = ep_addr;
+ ep->dev_addr = dev_addr;
+ ep->transfer_type = ep_desc->bmAttributes.xfer;
+ ep->need_pre = need_pre(dev_addr);
+ ep->next_pid = 0u;
+
+ if (ep->transfer_type != TUSB_XFER_INTERRUPT) {
+ ep->dpram_buf = usbh_dpram->epx_data;
+ } else {
+ // from 15 interrupt endpoints pool
+ uint8_t int_idx;
+ for (int_idx = 0; int_idx < USB_HOST_INTERRUPT_ENDPOINTS; int_idx++) {
+ if (!tu_bit_test(usb_hw->int_ep_ctrl, 1 + int_idx)) {
+ ep->interrupt_num = int_idx + 1;
+ break;
+ }
+ }
+ assert(int_idx < USB_HOST_INTERRUPT_ENDPOINTS);
+ assert(ep_desc->bInterval > 0);
+
+ //------------- dpram buf -------------//
+ // 15x64 last bytes of DPRAM for interrupt endpoint buffers
+ ep->dpram_buf = (uint8_t *)(USBCTRL_DPRAM_BASE + USB_DPRAM_MAX - (int_idx + 1u) * 64u);
+ uint32_t ep_ctrl = EP_CTRL_ENABLE_BITS | EP_CTRL_INTERRUPT_PER_BUFFER |
+ (TUSB_XFER_INTERRUPT << EP_CTRL_BUFFER_TYPE_LSB) | hw_data_offset(ep->dpram_buf) |
+ ((uint32_t)(ep_desc->bInterval - 1) << EP_CTRL_HOST_INTERRUPT_INTERVAL_LSB);
+ usbh_dpram->int_ep_ctrl[int_idx].ctrl = ep_ctrl;
+
+ //------------- address control -------------//
+ const uint8_t epnum = tu_edpt_number(ep_addr);
+ uint32_t addr_ctrl = (uint32_t)(dev_addr | (epnum << USB_ADDR_ENDP1_ENDPOINT_LSB));
+ if (tu_edpt_dir(ep_addr) == TUSB_DIR_OUT) {
+ addr_ctrl |= USB_ADDR_ENDP1_INTEP_DIR_BITS;
+ }
+ if (ep->need_pre) {
+ addr_ctrl |= USB_ADDR_ENDP1_INTEP_PREAMBLE_BITS;
+ }
+ usb_hw->int_ep_addr_ctrl[int_idx] = addr_ctrl;
+
+ // Finally, activate interrupt endpoint
+ usb_hw_set->int_ep_ctrl = TU_BIT(ep->interrupt_num);
+ }
return true;
}
bool hcd_edpt_close(uint8_t rhport, uint8_t daddr, uint8_t ep_addr) {
- (void) rhport; (void) daddr; (void) ep_addr;
+ (void)rhport;
+ (void)daddr;
+ (void)ep_addr;
return false; // TODO not implemented yet
}
-bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *buffer, uint16_t buflen) {
- (void) rhport;
-
- pico_trace("hcd_edpt_xfer dev_addr %d, ep_addr 0x%x, len %d\n", dev_addr, ep_addr, buflen);
-
- const uint8_t ep_num = tu_edpt_number(ep_addr);
- tusb_dir_t const ep_dir = tu_edpt_dir(ep_addr);
+bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) {
+ (void)rhport;
+ (void)dev_addr;
+ (void)ep_addr;
+ // TODO not implemented yet
+ return false;
+}
- // Get appropriate ep. Either EPX or interrupt endpoint
- struct hw_endpoint *ep = get_dev_ep(dev_addr, ep_addr);
+bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *buffer, uint16_t buflen) {
+ (void)rhport;
+ hw_endpoint_t *ep = edpt_find(dev_addr, ep_addr);
TU_ASSERT(ep);
- // EP should be inactive
- assert(!ep->active);
-
- // Control endpoint can change direction 0x00 <-> 0x80
- if (ep_addr != ep->ep_addr) {
- assert(ep_num == 0);
+ if (ep->interrupt_num > 0) {
+ // For interrupt endpoint control and buffer is already configured
+ // Note: Interrupt is single buffered only
+ io_rw_32 *ep_reg = dpram_int_ep_ctrl(ep->interrupt_num);
+ io_rw_32 *buf_reg = dpram_int_ep_buffer_ctrl(ep->interrupt_num);
+ rp2usb_xfer_start(ep, ep_reg, buf_reg, buffer, NULL, buflen);
+ } else {
+ // Control endpoint can change direction 0x00 <-> 0x80 when changing stages
+ if (ep_addr != ep->ep_addr) {
+ ep->ep_addr = ep_addr;
+ ep->next_pid = 1; // data and status stage start with DATA1
+ }
- // Direction has flipped on endpoint control so re init it but with same properties
- hw_endpoint_init(ep, dev_addr, ep_addr, ep->wMaxPacketSize, TUSB_XFER_CONTROL, 0);
- }
+ // If EPX is busy with another transfer, mark as pending
+ rp2usb_critical_enter();
+ if (epx->state == EPSTATE_ACTIVE) {
+ ep->user_buf = buffer;
+ ep->remaining_len = buflen;
+ ep->state = EPSTATE_PENDING;
- // If a normal transfer (non-interrupt) then initiate using
- // sie ctrl registers. Otherwise, interrupt ep registers should
- // already be configured
- if (ep == &epx) {
- hw_endpoint_xfer_start(ep, buffer, NULL, buflen);
+ #ifdef HAS_STOP_EPX_ON_NAK
+ usb_hw_set->nak_poll = USB_NAK_POLL_STOP_EPX_ON_NAK_BITS;
+ #else
+ // Only enable SOF round-robin for non-control endpoints
+ usb_hw->nak_poll = (300 << USB_NAK_POLL_DELAY_FS_LSB) | (300 << USB_NAK_POLL_DELAY_LS_LSB);
+ usb_hw_set->inte = USB_INTE_HOST_SOF_BITS;
+ #endif
+ } else {
+ io_rw_32 *ep_reg = &usbh_dpram->epx_ctrl;
+ io_rw_32 *buf_reg = &usbh_dpram->epx_buf_ctrl;
- // That has set up buffer control, endpoint control etc
- // for host we have to initiate the transfer
- usb_hw->dev_addr_ctrl = (uint32_t) (dev_addr | (ep_num << USB_ADDR_ENDP_ENDPOINT_LSB));
+ epx = ep;
- uint32_t flags = USB_SIE_CTRL_START_TRANS_BITS | SIE_CTRL_BASE |
- (ep_dir ? USB_SIE_CTRL_RECEIVE_DATA_BITS : USB_SIE_CTRL_SEND_DATA_BITS) |
- (need_pre(dev_addr) ? USB_SIE_CTRL_PREAMBLE_EN_BITS : 0);
- // START_TRANS bit on SIE_CTRL seems to exhibit the same behavior as the AVAILABLE bit
- // described in RP2040 Datasheet, release 2.1, section "4.1.2.5.1. Concurrent access".
- // We write everything except the START_TRANS bit first, then wait some cycles.
- usb_hw->sie_ctrl = flags & ~USB_SIE_CTRL_START_TRANS_BITS;
- busy_wait_at_least_cycles(12);
- usb_hw->sie_ctrl = flags;
- } else {
- hw_endpoint_xfer_start(ep, buffer, NULL, buflen);
+ epx_ctrl_prepare(ep->transfer_type);
+ rp2usb_xfer_start(ep, ep_reg, buf_reg, buffer, NULL, buflen); // prepare bufctrl
+ usb_hw->dev_addr_ctrl = (uint32_t)(ep->dev_addr | (tu_edpt_number(ep->ep_addr) << USB_ADDR_ENDP_ENDPOINT_LSB));
+ sie_start_xfer(false, tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN, ep->need_pre);
+ }
+ rp2usb_critical_exit();
}
return true;
}
-bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) {
- (void) rhport;
- (void) dev_addr;
- (void) ep_addr;
- // TODO not implemented yet
- return false;
-}
+bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, const uint8_t setup_packet[8]) {
+ (void)rhport;
+
+ hw_endpoint_t *ep = edpt_find(dev_addr, 0x00);
+ TU_ASSERT(ep);
-bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, uint8_t const setup_packet[8])
-{
- (void) rhport;
+ rp2usb_critical_enter();
- // Copy data into setup packet buffer
+ // Copy data into setup packet buffer (usbh only schedules one setup at a time)
for (uint8_t i = 0; i < 8; i++) {
usbh_dpram->setup_packet[i] = setup_packet[i];
}
- // Configure EP0 struct with setup info for the trans complete
- hw_endpoint_t *ep = hw_endpoint_allocate((uint8_t)TUSB_XFER_CONTROL);
- TU_ASSERT(ep);
-
- // EPX should be inactive
- assert(!ep->active);
-
- // EP0 out
- hw_endpoint_init(ep, dev_addr, 0x00, ep->wMaxPacketSize, 0, 0);
- assert(ep->configured);
-
+ ep->ep_addr = 0; // setup is OUT
ep->remaining_len = 8;
- ep->active = true;
-
- // Set device address
- usb_hw->dev_addr_ctrl = dev_addr;
+ ep->xferred_len = 0;
- // Set pre if we are a low speed device on full speed hub
- uint32_t const flags = SIE_CTRL_BASE | USB_SIE_CTRL_SEND_SETUP_BITS | USB_SIE_CTRL_START_TRANS_BITS |
- (need_pre(dev_addr) ? USB_SIE_CTRL_PREAMBLE_EN_BITS : 0);
+ // If EPX is busy, mark as pending setup (DPRAM already has the packet)
+ if (epx->state == EPSTATE_ACTIVE) {
+ ep->state = EPSTATE_PENDING_SETUP;
+ #ifdef HAS_STOP_EPX_ON_NAK
+ usb_hw_set->nak_poll = USB_NAK_POLL_STOP_EPX_ON_NAK_BITS;
+ #else
+ usb_hw->nak_poll = (300 << USB_NAK_POLL_DELAY_FS_LSB) | (300 << USB_NAK_POLL_DELAY_LS_LSB);
+ usb_hw_set->inte = USB_INTE_HOST_SOF_BITS;
+ #endif
+ } else {
+ epx = ep;
+ ep->state = EPSTATE_ACTIVE;
- // START_TRANS bit on SIE_CTRL seems to exhibit the same behavior as the AVAILABLE bit
- // described in RP2040 Datasheet, release 2.1, section "4.1.2.5.1. Concurrent access".
- // We write everything except the START_TRANS bit first, then wait some cycles.
- usb_hw->sie_ctrl = flags & ~USB_SIE_CTRL_START_TRANS_BITS;
- busy_wait_at_least_cycles(12);
- usb_hw->sie_ctrl = flags;
+ usb_hw->dev_addr_ctrl = ep->dev_addr;
+ sie_start_xfer(true, tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN, ep->need_pre);
+ }
+ rp2usb_critical_exit();
return true;
}
bool hcd_edpt_clear_stall(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) {
- (void) rhport;
- (void) dev_addr;
- (void) ep_addr;
+ (void)rhport;
+ (void)dev_addr;
+ (void)ep_addr;
panic("hcd_clear_stall");
// return true;
diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.c b/src/portable/raspberrypi/rp2040/rp2040_usb.c
index ca83acc6b..1b13934d3 100644
--- a/src/portable/raspberrypi/rp2040/rp2040_usb.c
+++ b/src/portable/raspberrypi/rp2040/rp2040_usb.c
@@ -27,22 +27,27 @@
#include "tusb_option.h"
-#if CFG_TUSB_MCU == OPT_MCU_RP2040
+#if CFG_TUSB_MCU == OPT_MCU_RP2040 && (CFG_TUD_ENABLED || CFG_TUH_ENABLED)
-#include <stdlib.h>
-#include "rp2040_usb.h"
+ #include <stdlib.h>
+ #include "rp2040_usb.h"
+
+ #include "device/dcd.h"
+ #include "host/hcd.h"
//--------------------------------------------------------------------+
// MACRO CONSTANT TYPEDEF PROTOTYPE
//--------------------------------------------------------------------+
-static void sync_xfer(hw_endpoint_t *ep);
+ #if CFG_TUSB_RP2_ERRATA_E15
+static bool e15_is_critical_frame_period(void);
+ #endif
- #if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX
-static bool e15_is_critical_frame_period(struct hw_endpoint *ep);
- #else
- #define e15_is_critical_frame_period(x) (false)
+ #if CFG_TUSB_RP2_ERRATA_E2
+static uint8_t rp2040_chipversion = 2;
#endif
+critical_section_t rp2usb_lock;
+
//--------------------------------------------------------------------+
// Implementation
//--------------------------------------------------------------------+
@@ -53,7 +58,7 @@ static void unaligned_memcpy(uint8_t *dst, const uint8_t *src, size_t n) {
}
}
-#if CFG_TUD_EDPT_DEDICATED_HWFIFO
+ #if CFG_TUD_EDPT_DEDICATED_HWFIFO
void tu_hwfifo_write(volatile void *hwfifo, const uint8_t *src, uint16_t len, const tu_hwfifo_access_t *access_mode) {
(void)access_mode;
unaligned_memcpy((uint8_t *)(uintptr_t)hwfifo, src, len);
@@ -63,224 +68,222 @@ void tu_hwfifo_read(const volatile void *hwfifo, uint8_t *dest, uint16_t len, co
(void)access_mode;
unaligned_memcpy(dest, (const uint8_t *)(uintptr_t)hwfifo, len);
}
-#endif
+ #endif
void rp2usb_init(void) {
// Reset usb controller
reset_block(RESETS_RESET_USBCTRL_BITS);
unreset_block_wait(RESETS_RESET_USBCTRL_BITS);
-#ifdef __GNUC__
- // Clear any previous state just in case
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Warray-bounds"
-#if __GNUC__ > 6
-#pragma GCC diagnostic ignored "-Wstringop-overflow"
-#endif
-#endif
+ #ifdef __GNUC__
+ // Clear any previous state just in case
+ #pragma GCC diagnostic push
+ #pragma GCC diagnostic ignored "-Warray-bounds"
+ #if __GNUC__ > 6
+ #pragma GCC diagnostic ignored "-Wstringop-overflow"
+ #endif
+ #endif
memset(usb_dpram, 0, sizeof(*usb_dpram));
-#ifdef __GNUC__
-#pragma GCC diagnostic pop
-#endif
+ #ifdef __GNUC__
+ #pragma GCC diagnostic pop
+ #endif
// Mux the controller to the onboard usb phy
usb_hw->muxing = USB_USB_MUXING_TO_PHY_BITS | USB_USB_MUXING_SOFTCON_BITS;
+ #if CFG_TUSB_RP2_ERRATA_E2
+ rp2040_chipversion = rp2040_chip_version();
+ #endif
+
TU_LOG2_INT(sizeof(hw_endpoint_t));
+
+ critical_section_init(&rp2usb_lock);
}
-void __tusb_irq_path_func(hw_endpoint_reset_transfer)(struct hw_endpoint* ep) {
- ep->active = false;
+void __tusb_irq_path_func(rp2usb_reset_transfer)(hw_endpoint_t *ep) {
+ ep->state = EPSTATE_IDLE;
ep->remaining_len = 0;
- ep->xferred_len = 0;
- ep->user_buf = 0;
+ ep->xferred_len = 0;
+ ep->user_buf = 0;
+#if CFG_TUD_EDPT_DEDICATED_HWFIFO
+ ep->is_xfer_fifo = false;
+#endif
}
-void __tusb_irq_path_func(hwbuf_ctrl_update)(io_rw_32 *buf_ctrl_reg, uint32_t and_mask, uint32_t or_mask) {
- const bool is_host = rp2usb_is_host_mode();
- uint32_t value = 0;
- uint32_t buf_ctrl = *buf_ctrl_reg;
+void __tusb_irq_path_func(bufctrl_write32)(io_rw_32 *buf_reg, uint32_t value) {
+ const uint32_t current = *buf_reg;
+ const uint32_t avail_mask = USB_BUF_CTRL_AVAIL | (USB_BUF_CTRL_AVAIL << 16);
+ if (current & value & avail_mask) {
+ panic("buf_ctrl @ 0x%lX already available", (uintptr_t)buf_reg);
+ }
+ *buf_reg = value & ~(USB_BUF_CTRL_AVAIL | (USB_BUF_CTRL_AVAIL << 16)); // write other bits first
- if (and_mask) {
- value = buf_ctrl & and_mask;
+ // Section 4.1.2.7.1 (rp2040) / 12.7.3.7.1 (rp2350) Concurrent access: after write to buffer control,
+ // wait for USB controller to see the update before setting AVAILABLE.
+ // Don't need delay in host mode as host is in charge of when to start the transaction.
+ if (value & (USB_BUF_CTRL_AVAIL | (USB_BUF_CTRL_AVAIL << 16))) {
+ if (!rp2usb_is_host_mode()) {
+ busy_wait_at_least_cycles(12);
+ }
+ *buf_reg = value; // then set AVAILABLE bit last
}
+}
- if (or_mask) {
- value |= or_mask;
- if (or_mask & USB_BUF_CTRL_AVAIL) {
- if (buf_ctrl & USB_BUF_CTRL_AVAIL) {
- panic("buf_ctrl @ 0x%lX already available", (uintptr_t)buf_ctrl_reg);
- }
- *buf_ctrl_reg = value & ~USB_BUF_CTRL_AVAIL;
+void __tusb_irq_path_func(bufctrl_write16)(io_rw_16 *buf_reg16, uint16_t value) {
+ const uint16_t current = *buf_reg16;
+ if (current & value & USB_BUF_CTRL_AVAIL) {
+ panic("buf_ctrl @ 0x%lX already available", (uintptr_t)buf_reg16);
+ }
+ *buf_reg16 = value & (uint16_t)~USB_BUF_CTRL_AVAIL; // write other bits first
- // Section 4.1.2.7.1 (rp2040) / 12.7.3.7.1 (rp2350) Concurrent access: after write to buffer control, we need to
- // wait at least 1/48 mhz (usb clock), 12 cycles should be good for 48*12Mhz = 576Mhz.
- // Don't need delay in host mode as host is in charge
- if (!is_host) {
- busy_wait_at_least_cycles(12);
- }
+ // Section 4.1.2.7.1 (rp2040) / 12.7.3.7.1 (rp2350) Concurrent access
+ if (value & USB_BUF_CTRL_AVAIL) {
+ if (!rp2usb_is_host_mode()) {
+ busy_wait_at_least_cycles(12);
}
+ *buf_reg16 = value; // then set AVAILABLE bit last
}
-
- *buf_ctrl_reg = value;
}
// prepare buffer, move data if tx, return buffer control
-static uint32_t __tusb_irq_path_func(prepare_ep_buffer)(struct hw_endpoint *ep, uint8_t buf_id, bool is_rx) {
- const uint16_t buflen = tu_min16(ep->remaining_len, ep->wMaxPacketSize);
- ep->remaining_len = (uint16_t) (ep->remaining_len - buflen);
-
- uint32_t buf_ctrl = buflen | USB_BUF_CTRL_AVAIL;
+uint16_t __tusb_irq_path_func(bufctrl_prepare16)(hw_endpoint_t *ep, uint8_t *dpram_buf, bool is_rx) {
+ const uint16_t buflen = tu_min16(ep->remaining_len, ep->max_packet_size);
+ ep->remaining_len -= buflen;
- // PID
- buf_ctrl |= ep->next_pid ? USB_BUF_CTRL_DATA1_PID : USB_BUF_CTRL_DATA0_PID;
+ uint16_t buf_ctrl = buflen | USB_BUF_CTRL_AVAIL;
+ if (ep->next_pid) {
+ buf_ctrl |= USB_BUF_CTRL_DATA1_PID;
+ }
ep->next_pid ^= 1u;
if (!is_rx) {
if (buflen) {
- // Copy data from user buffer/fifo to hw buffer
- uint8_t *hw_buf = ep->hw_data_buf + buf_id * 64;
- #if CFG_TUD_EDPT_DEDICATED_HWFIFO
+ // Copy data from user buffer/fifo to hw buffer
+ #if CFG_TUD_EDPT_DEDICATED_HWFIFO
if (ep->is_xfer_fifo) {
// not in sram, may mess up timing with E15 workaround
- tu_hwfifo_write_from_fifo(hw_buf, ep->user_fifo, buflen, NULL);
+ tu_hwfifo_write_from_fifo(dpram_buf, ep->user_fifo, buflen, NULL);
} else
- #endif
+ #endif
{
- unaligned_memcpy(hw_buf, ep->user_buf, buflen);
+ unaligned_memcpy(dpram_buf, ep->user_buf, buflen);
ep->user_buf += buflen;
}
}
- // Mark as full
buf_ctrl |= USB_BUF_CTRL_FULL;
}
- // Is this the last buffer? Only really matters for host mode. Will trigger
- // the trans complete irq but also stop it polling. We only really care about
- // trans complete for setup packets being sent
+ // Is this the last buffer? Will trigger the trans complete irq but also stop it polling.
+ // This is used to detect setup packets being sent in host mode
if (ep->remaining_len == 0) {
buf_ctrl |= USB_BUF_CTRL_LAST;
}
- if (buf_id) {
- buf_ctrl = buf_ctrl << 16;
- }
-
return buf_ctrl;
}
-// Prepare buffer control register value
-void __tusb_irq_path_func(hw_endpoint_start_next_buffer)(struct hw_endpoint* ep) {
- const tusb_dir_t dir = tu_edpt_dir(ep->ep_addr);
- bool is_rx;
- bool is_host = false;
- io_rw_32 *ep_ctrl_reg;
- io_rw_32 *buf_ctrl_reg;
+// Start transaction on hw buffer
+void __tusb_irq_path_func(rp2usb_buffer_start)(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, bool is_rx) {
+ // always compute and start with buffer 0
+ uint32_t buf_ctrl = bufctrl_prepare16(ep, ep->dpram_buf, is_rx) | USB_BUF_CTRL_SEL;
+ // Note: device EP0 does not have an endpoint control register
+ if (ep_reg != NULL) {
+ uint32_t ep_ctrl = *ep_reg;
#if CFG_TUH_ENABLED
- is_host = rp2usb_is_host_mode();
- if (is_host) {
- buf_ctrl_reg = hwbuf_ctrl_reg_host(ep);
- ep_ctrl_reg = hwep_ctrl_reg_host(ep);
- is_rx = (dir == TUSB_DIR_IN);
- } else
+ const bool force_single = (rp2usb_is_host_mode() && ep->interrupt_num > 0);
+ #else
+ const bool force_single = false;
#endif
- {
- buf_ctrl_reg = hwbuf_ctrl_reg_device(ep);
- ep_ctrl_reg = hwep_ctrl_reg_device(ep);
- is_rx = (dir == TUSB_DIR_OUT);
- }
-
- // always compute and start with buffer 0
- uint32_t buf_ctrl = prepare_ep_buffer(ep, 0, is_rx) | USB_BUF_CTRL_SEL;
-
- // EP0 has no endpoint control register, also usbd only schedule 1 packet at a time (single buffer)
- if (ep_ctrl_reg != NULL) {
- uint32_t ep_ctrl = *ep_ctrl_reg;
-
- // For now: skip double buffered for RX e.g OUT endpoint in Device mode, since host could send < 64 bytes and cause
- // short packet on buffer0
- // NOTE: this could happen to Host mode IN endpoint Also, Host mode "interrupt" endpoint hardware is only single
- // buffered,
- // NOTE2: Currently Host bulk is implemented using "interrupt" endpoint
- const bool force_single = (!is_host && is_rx) || (is_host && tu_edpt_number(ep->ep_addr) != 0);
if (ep->remaining_len && !force_single) {
// Use buffer 1 (double buffered) if there is still data
- // TODO: Isochronous for buffer1 bit-field is different than CBI (control bulk, interrupt)
-
- buf_ctrl |= prepare_ep_buffer(ep, 1, is_rx);
-
- // Set endpoint control double buffered bit if needed
- ep_ctrl &= ~EP_CTRL_INTERRUPT_PER_BUFFER;
- ep_ctrl |= EP_CTRL_DOUBLE_BUFFERED_BITS | EP_CTRL_INTERRUPT_PER_DOUBLE_BUFFER;
+ buf_ctrl |= (uint32_t)bufctrl_prepare16(ep, ep->dpram_buf + 64, is_rx) << 16;
+ ep_ctrl |= EP_CTRL_DOUBLE_BUFFERED_BITS;
} else {
- // Single buffered since 1 is enough
- ep_ctrl &= ~(EP_CTRL_DOUBLE_BUFFERED_BITS | EP_CTRL_INTERRUPT_PER_DOUBLE_BUFFER);
- ep_ctrl |= EP_CTRL_INTERRUPT_PER_BUFFER;
+ // Only buf0 used: clear DOUBLE_BUFFERED so controller doesn't toggle buffer selector
+ ep_ctrl &= ~(uint32_t)EP_CTRL_DOUBLE_BUFFERED_BITS;
}
-
- *ep_ctrl_reg = ep_ctrl;
+ *ep_reg = ep_ctrl;
}
- TU_LOG(3, " Prepare BufCtrl: [0] = 0x%04x [1] = 0x%04x\r\n", tu_u32_low16(buf_ctrl), tu_u32_high16(buf_ctrl));
-
- // Finally, write to buffer_control which will trigger the transfer
- // the next time the controller polls this dpram address
- hwbuf_ctrl_set(buf_ctrl_reg, buf_ctrl);
+ // Finally, write to buffer control which will trigger the transfer the next time the controller polls this endpoint
+ bufctrl_write32(buf_reg, buf_ctrl);
}
-void hw_endpoint_xfer_start(struct hw_endpoint *ep, uint8_t *buffer, tu_fifo_t *ff, uint16_t total_len) {
- (void) ff;
+void rp2usb_xfer_start(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, uint8_t *buffer, tu_fifo_t *ff,
+ uint16_t total_len) {
+ (void)ff;
hw_endpoint_lock_update(ep, 1);
- if (ep->active) {
- // TODO: Is this acceptable for interrupt packets?
+ if (ep->state == EPSTATE_ACTIVE) {
TU_LOG(1, "WARN: starting new transfer on already active ep %02X\r\n", ep->ep_addr);
- hw_endpoint_reset_transfer(ep);
+ rp2usb_reset_transfer(ep);
}
// Fill in info now that we're kicking off the hw
ep->remaining_len = total_len;
- ep->xferred_len = 0;
- ep->active = true;
+ ep->xferred_len = 0;
+ ep->state = EPSTATE_ACTIVE;
-#if CFG_TUD_EDPT_DEDICATED_HWFIFO
+ #if CFG_TUD_EDPT_DEDICATED_HWFIFO
if (ff != NULL) {
ep->user_fifo = ff;
ep->is_xfer_fifo = true;
} else
-#endif
+ #endif
{
- ep->user_buf = buffer;
+ ep->user_buf = buffer;
+ #if CFG_TUD_EDPT_DEDICATED_HWFIFO
ep->is_xfer_fifo = false;
+ #endif
}
- #if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX
+ const bool is_host = rp2usb_is_host_mode();
+ const bool is_rx = (is_host == (tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN));
+
+ #if CFG_TUD_ENABLED
+ if (!is_host && ep->future_len > 0) {
+ // Device only: previous short-packet abort saved data from the other buffer
+ const uint8_t future_len = ep->future_len;
+ memcpy(ep->user_buf, ep->dpram_buf + (ep->future_bufid << 6), future_len);
+ ep->xferred_len += future_len;
+ ep->remaining_len -= future_len;
+ ep->user_buf += future_len;
+ ep->future_len = 0;
+ ep->future_bufid = 0;
+
+ if (ep->remaining_len == 0) {
+ const uint16_t xferred_len = ep->xferred_len;
+ rp2usb_reset_transfer(ep);
+ dcd_event_xfer_complete(0, ep->ep_addr, xferred_len, XFER_RESULT_SUCCESS, false);
+ hw_endpoint_lock_update(ep, -1);
+ return;
+ }
+ }
+
+ #if CFG_TUSB_RP2_ERRATA_E15
if (ep->e15_bulk_in) {
usb_hw_set->inte = USB_INTS_DEV_SOF_BITS;
- }
- if (e15_is_critical_frame_period(ep)) {
- ep->pending = 1; // skip transfer if we are in critical frame period
- } else
- #endif
- {
- hw_endpoint_start_next_buffer(ep);
+ // skip transfer if we are in critical frame period
+ if (e15_is_critical_frame_period()) {
+ ep->state = EPSTATE_PENDING;
+ hw_endpoint_lock_update(ep, -1);
+ return;
+ }
}
+ #endif // CFG_TUSB_RP2_ERRATA_E15
+ #endif // CFG_TUD_ENABLED
+ rp2usb_buffer_start(ep, ep_reg, buf_reg, is_rx);
hw_endpoint_lock_update(ep, -1);
}
// sync endpoint buffer and return transferred bytes
-static uint16_t __tusb_irq_path_func(sync_ep_buffer)(hw_endpoint_t *ep, io_rw_32 *buf_ctrl_reg, uint8_t buf_id,
- bool is_rx) {
- uint32_t buf_ctrl = *buf_ctrl_reg;
- if (buf_id) {
- buf_ctrl = buf_ctrl >> 16;
- }
-
+static uint16_t __tusb_irq_path_func(bufctrl_sync16)(hw_endpoint_t *ep, bool is_rx, uint16_t buf_ctrl,
+ uint8_t *dpram_buf) {
const uint16_t xferred_bytes = buf_ctrl & USB_BUF_CTRL_LEN_MASK;
if (!is_rx) {
@@ -291,23 +294,21 @@ static uint16_t __tusb_irq_path_func(sync_ep_buffer)(hw_endpoint_t *ep, io_rw_32
// If we have received some data, so can increase the length
// we have received AFTER we have copied it to the user buffer at the appropriate offset
assert(buf_ctrl & USB_BUF_CTRL_FULL);
-
- uint8_t *hw_buf = ep->hw_data_buf + buf_id * 64;
#if CFG_TUD_EDPT_DEDICATED_HWFIFO
if (ep->is_xfer_fifo) {
// not in sram, may mess up timing with E15 workaround
- tu_hwfifo_read_to_fifo(hw_buf, ep->user_fifo, xferred_bytes, NULL);
+ tu_hwfifo_read_to_fifo(dpram_buf, ep->user_fifo, xferred_bytes, NULL);
} else
#endif
{
- unaligned_memcpy(ep->user_buf, hw_buf, xferred_bytes);
+ unaligned_memcpy(ep->user_buf, dpram_buf, xferred_bytes);
ep->user_buf += xferred_bytes;
}
}
ep->xferred_len += xferred_bytes;
// Short packet
- if (xferred_bytes < ep->wMaxPacketSize) {
+ if (xferred_bytes < ep->max_packet_size) {
// Reduce total length as this is last packet
ep->remaining_len = 0;
}
@@ -315,109 +316,126 @@ static uint16_t __tusb_irq_path_func(sync_ep_buffer)(hw_endpoint_t *ep, io_rw_32
return xferred_bytes;
}
-// Update hw endpoint struct with info from hardware after a buff status interrupt
-static void __tusb_irq_path_func(sync_xfer)(hw_endpoint_t *ep) {
- // const uint8_t ep_num = tu_edpt_number(ep->ep_addr);
- const tusb_dir_t dir = tu_edpt_dir(ep->ep_addr);
-
- io_rw_32 *buf_ctrl_reg;
- io_rw_32 *ep_ctrl_reg;
- bool is_rx;
+// Returns true if transfer is complete.
+// buf_id: which buffer completed (from BUFF_CPU_SHOULD_HANDLE, only used for double-buffered).
+bool __tusb_irq_path_func(rp2usb_xfer_continue)(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, uint8_t buf_id,
+ bool is_rx) {
+ hw_endpoint_lock_update(ep, 1);
- #if CFG_TUH_ENABLED
- const bool is_host = rp2usb_is_host_mode();
- if (is_host) {
- buf_ctrl_reg = hwbuf_ctrl_reg_host(ep);
- ep_ctrl_reg = hwep_ctrl_reg_host(ep);
- is_rx = (dir == TUSB_DIR_IN);
- } else
- #endif
- {
- buf_ctrl_reg = hwbuf_ctrl_reg_device(ep);
- ep_ctrl_reg = hwep_ctrl_reg_device(ep);
- is_rx = (dir == TUSB_DIR_OUT);
+ if (ep->state == EPSTATE_IDLE) {
+ // probably land here due to short packet on rx with double buffered
+ hw_endpoint_lock_update(ep, -1);
+ return false;
}
- TU_LOG(3, " Sync BufCtrl: [0] = 0x%04x [1] = 0x%04x\r\n", tu_u32_low16(*buf_ctrl_reg),
- tu_u32_high16(*buf_ctrl_reg));
- uint16_t buf0_bytes = sync_ep_buffer(ep, buf_ctrl_reg, 0, is_rx); // always sync buffer 0
+ const bool is_host = rp2usb_is_host_mode();
+ const bool is_double = (ep_reg != NULL && ((*ep_reg) & EP_CTRL_DOUBLE_BUFFERED_BITS));
- // sync buffer 1 if double buffered
- if (ep_ctrl_reg != NULL && (*ep_ctrl_reg) & EP_CTRL_DOUBLE_BUFFERED_BITS) {
- if (buf0_bytes == ep->wMaxPacketSize) {
- // sync buffer 1 if not short packet
- sync_ep_buffer(ep, buf_ctrl_reg, 1, is_rx);
- } else {
- // short packet on buffer 0
- // TODO couldn't figure out how to handle this case which happen with net_lwip_webserver example
- // At this time (currently trigger per 2 buffer), the buffer1 is probably filled with data from
- // the next transfer (not current one). For now we disable double buffered for device OUT
- // NOTE this could happen to Host IN
-#if 0
- uint8_t const ep_num = tu_edpt_number(ep->ep_addr);
- uint8_t const dir = (uint8_t) tu_edpt_dir(ep->ep_addr);
- uint8_t const ep_id = 2*ep_num + (dir ? 0 : 1);
-
- // abort queued transfer on buffer 1
- usb_hw->abort |= TU_BIT(ep_id);
-
- while ( !(usb_hw->abort_done & TU_BIT(ep_id)) ) {}
+ // Double-buffered: buf_id from BUFF_CPU_SHOULD_HANDLE indicates which buffer completed.
+ // RP2040-E4 (host only): in single-buffered multi-packet transfers, the controller may write completion status to
+ // BUF1 half instead of BUF0. The side effect is that controller can execute an extra packet after writing to BUF1
+ // since it leaves BUF0 intact, which can be polled before buf_status interrupt is triggered.
+ uint8_t *dpram_buf = ep->dpram_buf;
+ if (buf_id) {
+ #if CFG_TUSB_RP2_ERRATA_E4
+ if (!(is_host && !is_double)) // E4 bug: incorrect buf_id, buffer data is still buf0
+ #endif
+ {
+ dpram_buf += 64; // buf1 offset
+ }
+ }
- uint32_t ep_ctrl = *ep->endpoint_control;
- ep_ctrl &= ~(EP_CTRL_DOUBLE_BUFFERED_BITS | EP_CTRL_INTERRUPT_PER_DOUBLE_BUFFER);
- ep_ctrl |= EP_CTRL_INTERRUPT_PER_BUFFER;
+ io_rw_16 *buf_reg16 = (io_rw_16 *)buf_reg;
+ uint16_t buf_ctrl16 = *(buf_reg16 + buf_id);
- io_rw_32 *buf_ctrl_reg = is_host ? hwbuf_ctrl_reg_host(ep) : hwbuf_ctrl_reg_device(ep);
- hwbuf_ctrl_set(buf_ctrl_reg, 0);
+ const uint16_t xact_bytes = bufctrl_sync16(ep, is_rx, buf_ctrl16, dpram_buf);
+ const bool is_last = buf_ctrl16 & USB_BUF_CTRL_LAST;
+ const bool is_short = xact_bytes < ep->max_packet_size;
+ const bool is_done = is_short || is_last;
- usb_hw->abort &= ~TU_BIT(ep_id);
+ // Short packet on rx with double buffer: abort the other half (if not last) and reset the buffer control.
+ // The other buffer may be: (a) still AVAIL, (b) in-progress (controller receiving), or (c) already completed.
+ // We must abort to safely reclaim it. If it has valid data (FULL), save as future for the next transfer.
+ // Note: Host mode current does not save next transfer data due to shared epx --> potential issue. However, RP2040-E4
+ // causes more or less of the same issue since it write to buf1 and next time it continues to transfer on buf0 (stale)
+ if (is_short && is_double && is_rx && !is_last) {
+ const uint32_t abort_bit = TU_BIT(tu_edpt_number(ep->ep_addr) << 1); // abort is device only -> IN endpoint
- TU_LOG(3, "----SHORT PACKET buffer0 on EP %02X:\r\n", ep->ep_addr);
- TU_LOG(3, " BufCtrl: [0] = 0x%04x [1] = 0x%04x\r\n", tu_u32_low16(buf_ctrl), tu_u32_high16(buf_ctrl));
-#endif
+ if (is_host) {
+ // host stop current transfer, not safe, can be racing
+ const uint32_t sie_ctrl = (usb_hw->sie_ctrl & SIE_CTRL_BASE_MASK) | USB_SIE_CTRL_STOP_TRANS_BITS;
+ usb_hw->sie_ctrl = sie_ctrl;
+ while (usb_hw->sie_ctrl & USB_SIE_CTRL_STOP_TRANS_BITS) {}
+ } else {
+ // device abort current transfer
+ #if CFG_TUSB_RP2_ERRATA_E2
+ if (rp2040_chipversion >= 2)
+ #endif
+ {
+ usb_hw_set->abort = abort_bit;
+ while ((usb_hw->abort_done & abort_bit) != abort_bit) {}
+ }
}
- }
-}
-// Returns true if transfer is complete
-bool __tusb_irq_path_func(hw_endpoint_xfer_continue)(struct hw_endpoint* ep) {
- hw_endpoint_lock_update(ep, 1);
+ // After abort, check if the other buffer received valid data
+ io_rw_16 *buf_reg16_other = buf_reg16 + (buf_id ^ 1);
+ const uint16_t buf_ctrl16_other = *buf_reg16_other;
+ if (buf_ctrl16_other & USB_BUF_CTRL_FULL) {
+ // Data already sent into this buffer. Save it for the next transfer.
+ // buff_status will be clear by the next run
+ #if CFG_TUD_ENABLED
+ if (!is_host) {
+ ep->future_len = (uint8_t)(buf_ctrl16_other & USB_BUF_CTRL_LEN_MASK);
+ ep->future_bufid = buf_id ^ 1;
+ }
+ #endif
+ } else {
+ ep->next_pid ^= 1u; // roll back pid if aborted
+ }
- // Part way through a transfer
- if (!ep->active) {
- panic("Can't continue xfer on inactive ep %02X", ep->ep_addr);
- }
+ *buf_reg = 0; // reset buffer control
- sync_xfer(ep); // Update EP struct from hardware state
+ if (!is_host) {
+ #if CFG_TUSB_RP2_ERRATA_E2
+ if (rp2040_chipversion >= 2)
+ #endif
+ {
+ usb_hw_clear->abort_done = abort_bit;
+ usb_hw_clear->abort = abort_bit;
+ }
+ }
- // Now we have synced our state with the hardware. Is there more data to transfer?
- // If we are done then notify tinyusb
- if (ep->remaining_len == 0) {
- pico_trace("Completed transfer of %d bytes on ep %02X\r\n", ep->xferred_len, ep->ep_addr);
- // Notify caller we are done so it can notify the tinyusb stack
hw_endpoint_lock_update(ep, -1);
return true;
- } else {
- #if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX
- if (e15_is_critical_frame_period(ep)) {
- ep->pending = 1;
+ }
+
+ if (!is_done && ep->remaining_len > 0) {
+ #if CFG_TUSB_RP2_ERRATA_E15
+ const bool need_e15 = ep->e15_bulk_in;
+ if (need_e15 && e15_is_critical_frame_period()) {
+ // mark as pending if matches E15 condition
+ ep->state = EPSTATE_PENDING;
+ } else if (need_e15 && ep->state == EPSTATE_PENDING) {
+ // if already pending, meaning the other buf completes first, don't arm buffer, let SOF handle it
+ // do nothing
} else
#endif
{
- hw_endpoint_start_next_buffer(ep);
+ // ping-pong: arm the completed buffer with new data
+ const uint16_t buf_ctrl16_new = bufctrl_prepare16(ep, dpram_buf, is_rx);
+ bufctrl_write16(buf_reg16 + buf_id, buf_ctrl16_new);
}
}
hw_endpoint_lock_update(ep, -1);
- // More work to do
- return false;
+ return is_done;
}
//--------------------------------------------------------------------+
// Errata 15
//--------------------------------------------------------------------+
-#if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX
+ #if CFG_TUSB_RP2_ERRATA_E15
// E15 is fixed with RP2350
/* Don't mark IN buffers as available during the last 200us of a full-speed
@@ -438,25 +456,19 @@ bool __tusb_irq_path_func(hw_endpoint_xfer_continue)(struct hw_endpoint* ep) {
volatile uint32_t e15_last_sof = 0;
-// check if we need to apply Errata 15 workaround : i.e
-// Endpoint is BULK IN and is currently in critical frame period i.e 20% of last usb frame
-static bool __tusb_irq_path_func(e15_is_critical_frame_period)(struct hw_endpoint* ep) {
- if (!ep->e15_bulk_in) {
- return false;
- }
-
+// check if it is currently in critical frame period i.e 20% of last usb frame
+static bool __tusb_irq_path_func(e15_is_critical_frame_period)(void) {
/* Avoid the last 200us (uframe 6.5-7) of a frame, up to the EOF2 point.
* The device state machine cannot recover from receiving an incorrect PID
- * when it is expecting an ACK.
- */
+ * when it is expecting an ACK. */
uint32_t delta = time_us_32() - e15_last_sof;
if (delta < 800 || delta > 998) {
return false;
}
- TU_LOG(3, "Avoiding sof %lu now %lu last %lu\r\n", (usb_hw->sof_rd + 1) & USB_SOF_RD_BITS, time_us_32(),
- e15_last_sof);
+ // TU_LOG(3, "Avoiding sof %lu now %lu last %lu\r\n", (usb_hw->sof_rd + 1) & USB_SOF_RD_BITS, time_us_32(),
+ // e15_last_sof);
return true;
}
-#endif // TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX
+ #endif
#endif
diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.h b/src/portable/raspberrypi/rp2040/rp2040_usb.h
index c03dc34b2..8ebc3e3fc 100644
--- a/src/portable/raspberrypi/rp2040/rp2040_usb.h
+++ b/src/portable/raspberrypi/rp2040/rp2040_usb.h
@@ -7,6 +7,8 @@
#include "hardware/resets.h"
#include "hardware/timer.h"
+#include "pico/critical_section.h"
+
#include "common/tusb_common.h"
#include "osal/osal.h"
#include "common/tusb_fifo.h"
@@ -15,42 +17,66 @@
#error TinyUSB device and host mode not supported at the same time
#endif
-// E5 and E15 only apply to RP2040
#if defined(PICO_RP2040) && PICO_RP2040 == 1
- // RP2040 E5: USB device fails to exit RESET state on busy USB bus.
+ // RP2040-E2 USB device endpoint abort is not cleared.
+ #define CFG_TUSB_RP2_ERRATA_E2 1
+
+ // RP2040-E4: USB host writes to upper half of buffer status in single buffered mode.
+ #define CFG_TUSB_RP2_ERRATA_E4 1
+
+ // RP2040-E5: USB device fails to exit RESET state on busy USB bus.
#if defined(PICO_RP2040_USB_DEVICE_ENUMERATION_FIX) && !defined(TUD_OPT_RP2040_USB_DEVICE_ENUMERATION_FIX)
#define TUD_OPT_RP2040_USB_DEVICE_ENUMERATION_FIX PICO_RP2040_USB_DEVICE_ENUMERATION_FIX
#endif
- // RP2040 E15: USB Device controller will hang if certain bus errors occur during an IN transfer.
- #if defined(PICO_RP2040_USB_DEVICE_UFRAME_FIX) && !defined(TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX)
- #define TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX PICO_RP2040_USB_DEVICE_UFRAME_FIX
+ // RP2040-E15: USB Device controller will hang if certain bus errors occur during an IN transfer.
+ #ifndef CFG_TUSB_RP2_ERRATA_E15
+ #if defined(PICO_RP2040_USB_DEVICE_UFRAME_FIX)
+ #define CFG_TUSB_RP2_ERRATA_E15 (CFG_TUD_ENABLED && PICO_RP2040_USB_DEVICE_UFRAME_FIX)
+ #elif defined(TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX)
+ #define CFG_TUSB_RP2_ERRATA_E15 (CFG_TUD_ENABLED && TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX)
+ #endif
#endif
#endif
+#ifndef CFG_TUSB_RP2_ERRATA_E2
+ #define CFG_TUSB_RP2_ERRATA_E2 0
+#endif
+
+#ifndef CFG_TUSB_RP2_ERRATA_E4
+ #define CFG_TUSB_RP2_ERRATA_E4 0
+#endif
+
#ifndef TUD_OPT_RP2040_USB_DEVICE_ENUMERATION_FIX
#define TUD_OPT_RP2040_USB_DEVICE_ENUMERATION_FIX 0
#endif
-#ifndef TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX
- #define TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX 0
+#ifndef CFG_TUSB_RP2_ERRATA_E15
+ #define CFG_TUSB_RP2_ERRATA_E15 0
#endif
-#if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX
+#if CFG_TUSB_RP2_ERRATA_E15
#undef PICO_RP2040_USB_FAST_IRQ
#define PICO_RP2040_USB_FAST_IRQ 1
#endif
#ifndef PICO_RP2040_USB_FAST_IRQ
-#define PICO_RP2040_USB_FAST_IRQ 0
+ #define PICO_RP2040_USB_FAST_IRQ 0
#endif
#if PICO_RP2040_USB_FAST_IRQ
-#define __tusb_irq_path_func(x) __no_inline_not_in_flash_func(x)
+ #define __tusb_irq_path_func(x) __no_inline_not_in_flash_func(x)
#else
-#define __tusb_irq_path_func(x) x
+ #define __tusb_irq_path_func(x) x
#endif
+// Flags we set by default in sie_ctrl (we add other bits on top)
+enum {
+ SIE_CTRL_BASE = USB_SIE_CTRL_PULLDOWN_EN_BITS | USB_SIE_CTRL_EP0_INT_1BUF_BITS,
+ SIE_CTRL_BASE_MASK = USB_SIE_CTRL_PULLDOWN_EN_BITS | USB_SIE_CTRL_EP0_INT_1BUF_BITS | USB_SIE_CTRL_SOF_EN_BITS |
+ USB_SIE_CTRL_KEEP_ALIVE_EN_BITS
+};
+
//--------------------------------------------------------------------+
//
//--------------------------------------------------------------------+
@@ -60,26 +86,43 @@
#define pico_info(...) TU_LOG(2, __VA_ARGS__)
#define pico_trace(...) TU_LOG(3, __VA_ARGS__)
+enum {
+ EPSTATE_IDLE = 0,
+ EPSTATE_ACTIVE,
+ EPSTATE_PENDING,
+ EPSTATE_PENDING_SETUP
+};
+
// Hardware information per endpoint
typedef struct hw_endpoint {
uint8_t ep_addr;
uint8_t next_pid;
- bool active; // transferring data
- bool is_xfer_fifo; // transfer using fifo
+ uint8_t state;
-#if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX
- bool e15_bulk_in; // Errata15 device bulk in
- uint8_t pending; // Transfer scheduled but not active
+#if CFG_TUD_EDPT_DEDICATED_HWFIFO
+ bool is_xfer_fifo; // transfer using fifo
+#endif
+
+#if CFG_TUD_ENABLED
+ uint8_t future_bufid; // which buffer holds next data
+ uint8_t future_len; // next data len
+#endif
+
+#if CFG_TUSB_RP2_ERRATA_E15
+ bool e15_bulk_in; // Errata15 device bulk in
#endif
#if CFG_TUH_ENABLED
- bool configured; // Is this a valid struct
uint8_t dev_addr;
- uint8_t interrupt_num; // for host interrupt endpoints
+ uint8_t interrupt_num; // 1-15 for interrupt endpoints
+ struct TU_ATTR_PACKED {
+ uint8_t transfer_type : 2;
+ uint8_t need_pre : 1; // preamble for low-speed device behind full speed hub
+ };
#endif
- uint16_t wMaxPacketSize;
- uint8_t *hw_data_buf; // Buffer pointer in usb dpram
+ uint16_t max_packet_size; // max packet size also indicates configured
+ uint8_t *dpram_buf; // Buffer pointer in usb dpram
// transfer info
union {
@@ -91,7 +134,7 @@ typedef struct hw_endpoint {
} hw_endpoint_t;
-#if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX
+#if CFG_TUSB_RP2_ERRATA_E15
extern volatile uint32_t e15_last_sof;
#endif
@@ -102,69 +145,39 @@ TU_ATTR_ALWAYS_INLINE static inline bool rp2usb_is_host_mode(void) {
return (usb_hw->main_ctrl & USB_MAIN_CTRL_HOST_NDEVICE_BITS) ? true : false;
}
-void hw_endpoint_xfer_start(struct hw_endpoint *ep, uint8_t *buffer, tu_fifo_t *ff, uint16_t total_len);
-bool hw_endpoint_xfer_continue(struct hw_endpoint *ep);
-void hw_endpoint_reset_transfer(struct hw_endpoint *ep);
-void hw_endpoint_start_next_buffer(struct hw_endpoint *ep);
-
-TU_ATTR_ALWAYS_INLINE static inline void hw_endpoint_lock_update(__unused struct hw_endpoint * ep, __unused int delta) {
- // todo add critsec as necessary to prevent issues between worker and IRQ...
- // note that this is perhaps as simple as disabling IRQs because it would make
- // sense to have worker and IRQ on same core, however I think using critsec is about equivalent.
-}
-
-// #if CFG_TUD_ENABLED
-TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *hwep_ctrl_reg_device(struct hw_endpoint *ep) {
- uint8_t const epnum = tu_edpt_number(ep->ep_addr);
- const uint8_t dir = (uint8_t)tu_edpt_dir(ep->ep_addr);
- if (epnum == 0) {
- // EP0 has no endpoint control register because the buffer offsets are fixed and always enabled
- return NULL;
- }
- return (dir == TUSB_DIR_IN) ? &usb_dpram->ep_ctrl[epnum - 1].in : &usb_dpram->ep_ctrl[epnum - 1].out;
-}
+extern critical_section_t rp2usb_lock;
-TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *hwbuf_ctrl_reg_device(struct hw_endpoint *ep) {
- const uint8_t epnum = tu_edpt_number(ep->ep_addr);
- const uint8_t dir = (uint8_t)tu_edpt_dir(ep->ep_addr);
- return (dir == TUSB_DIR_IN) ? &usb_dpram->ep_buf_ctrl[epnum].in : &usb_dpram->ep_buf_ctrl[epnum].out;
+TU_ATTR_ALWAYS_INLINE static inline void rp2usb_critical_enter(void) {
+ critical_section_enter_blocking(&rp2usb_lock);
}
-// #endif
-
-#if CFG_TUH_ENABLED
-TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *hwep_ctrl_reg_host(struct hw_endpoint *ep) {
- if (tu_edpt_number(ep->ep_addr) == 0) {
- return &usbh_dpram->epx_ctrl;
- }
- return &usbh_dpram->int_ep_ctrl[ep->interrupt_num].ctrl;
-}
-
-TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *hwbuf_ctrl_reg_host(struct hw_endpoint *ep) {
- if (tu_edpt_number(ep->ep_addr) == 0) {
- return &usbh_dpram->epx_buf_ctrl;
- }
- return &usbh_dpram->int_ep_buffer_ctrl[ep->interrupt_num].ctrl;
+TU_ATTR_ALWAYS_INLINE static inline void rp2usb_critical_exit(void) {
+ critical_section_exit(&rp2usb_lock);
}
-#endif
//--------------------------------------------------------------------+
-//
+// Hardware Endpoint
//--------------------------------------------------------------------+
-void hwbuf_ctrl_update(io_rw_32 *buf_ctrl_reg, uint32_t and_mask, uint32_t or_mask);
+void rp2usb_xfer_start(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, uint8_t *buffer, tu_fifo_t *ff,
+ uint16_t total_len);
+bool rp2usb_xfer_continue(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, uint8_t buf_id, bool is_rx);
+void rp2usb_buffer_start(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, bool is_rx);
+void rp2usb_reset_transfer(hw_endpoint_t *ep);
-TU_ATTR_ALWAYS_INLINE static inline void hwbuf_ctrl_set(io_rw_32 *buf_ctrl_reg, uint32_t value) {
- hwbuf_ctrl_update(buf_ctrl_reg, 0, value);
-}
-TU_ATTR_ALWAYS_INLINE static inline void hwbuf_ctrl_set_mask(io_rw_32 *buf_ctrl_reg, uint32_t value) {
- hwbuf_ctrl_update(buf_ctrl_reg, ~value, value);
+TU_ATTR_ALWAYS_INLINE static inline void hw_endpoint_lock_update(__unused struct hw_endpoint *ep, __unused int delta) {
+ // todo add critsec as necessary to prevent issues between worker and IRQ...
+ // note that this is perhaps as simple as disabling IRQs because it would make
+ // sense to have worker and IRQ on same core, however I think using critsec is about equivalent.
}
-TU_ATTR_ALWAYS_INLINE static inline void hwbuf_ctrl_clear_mask(io_rw_32 *buf_ctrl_reg, uint32_t value) {
- hwbuf_ctrl_update(buf_ctrl_reg, ~value, 0);
-}
+//--------------------------------------------------------------------+
+// Hardware Buffer
+//--------------------------------------------------------------------+
+void bufctrl_write32(io_rw_32 *buf_reg, uint32_t value);
+void bufctrl_write16(io_rw_16 *buf_reg16, uint16_t value);
+uint16_t bufctrl_prepare16(hw_endpoint_t *ep, uint8_t *dpram_buf, bool is_rx);
-static inline uintptr_t hw_data_offset(uint8_t *buf) {
+TU_ATTR_ALWAYS_INLINE static inline uintptr_t hw_data_offset(uint8_t *buf) {
// Remove usb base from buffer pointer
return (uintptr_t)buf ^ (uintptr_t)usb_dpram;
}
diff --git a/test/hil/hil_test.py b/test/hil/hil_test.py
index 41e9fad88..cf3cff1a3 100755
--- a/test/hil/hil_test.py
+++ b/test/hil/hil_test.py
@@ -50,7 +50,7 @@ import ctypes
from pymtp import MTP
import string
-ENUM_TIMEOUT = 30
+ENUM_TIMEOUT = 15
STATUS_OK = "\033[32mOK\033[0m"
STATUS_FAILED = "\033[31mFailed\033[0m"
@@ -155,8 +155,7 @@ def read_disk_file(uid, lun, fname):
def open_mtp_dev(uid):
mtp = MTP()
- # MTP seems to take a while to enumerate
- timeout = 2 * ENUM_TIMEOUT
+ timeout = ENUM_TIMEOUT
while timeout > 0:
# unmount gio/gvfs MTP mount which blocks libmtp from accessing the device
subprocess.run(f"gio mount -u mtp://TinyUsb_TinyUsb_Device_{uid}/",
@@ -456,17 +455,30 @@ def test_host_device_info(board):
return 0
-def print_msc_info(lines):
- """Print MSC inquiry and disk size on a single line"""
+def check_msc_info(lines, msc_devs):
+ """Print MSC info and verify block_count/block_size against config"""
inquiry = ''
disk_size = ''
for l in lines:
- if re.match(r'^[A-Za-z].*\s+rev\s+', l):
+ if re.match(r'^[A-Za-z].*\s+(rev\s+|[0-9])', l) and 'Disk Size' not in l:
inquiry = l.strip()
if 'Disk Size' in l:
disk_size = l.strip()
if inquiry or disk_size:
print(f'\r\n {inquiry} {disk_size} ', end='')
+ # Verify block_count and block_size from "Disk Size: COUNT SIZE-byte blocks: N MB"
+ if disk_size and msc_devs:
+ m = re.match(r'Disk Size:\s+(\d+)\s+(\d+)-byte blocks', disk_size)
+ if m:
+ actual_count = int(m.group(1))
+ actual_size = int(m.group(2))
+ for dev in msc_devs:
+ exp_count = dev.get('block_count')
+ exp_size = dev.get('block_size')
+ if exp_count and actual_count == exp_count:
+ assert actual_size == exp_size, (
+ f'MSC block_size mismatch: expected {exp_size}, got {actual_size}')
+ break
def test_host_cdc_msc_hid(board):
@@ -475,7 +487,7 @@ def test_host_cdc_msc_hid(board):
cdc_devs = [d for d in dev_attached if d.get('is_cdc')]
msc_devs = [d for d in dev_attached if d.get('is_msc')]
if not cdc_devs and not msc_devs:
- return
+ return 'skipped'
port = get_serial_dev(flasher["uid"], None, None, 0)
ser = open_serial_dev(port)
@@ -525,7 +537,7 @@ def test_host_cdc_msc_hid(board):
if msc_devs:
assert b'MassStorage device is mounted' in data, 'MSC device not mounted on host'
assert b'Disk Size' in data, 'MSC Disk Size not reported'
- print_msc_info(lines)
+ check_msc_info(lines, msc_devs)
# CDC echo test via flasher serial
if not cdc_devs:
@@ -533,33 +545,34 @@ def test_host_cdc_msc_hid(board):
return
time.sleep(2)
+ ser.read(ser.in_waiting)
ser.reset_input_buffer()
def rand_ascii(length):
return "".join(random.choices(string.ascii_letters + string.digits, k=length)).encode("ascii")
- sizes = [8, 32, 64, 128]
- for size in sizes:
- test_data = rand_ascii(size)
- ser.reset_input_buffer()
-
- # Write byte-by-byte with delay to avoid UART overrun
- for b in test_data:
- ser.write(bytes([b]))
- ser.flush()
- time.sleep(0.001)
+ packet_size = 64
- # Read echo back with timeout
+ # Echo test: write random 1-packet_size chunks, wait for echo before sending next
+ echo_len = 1024
+ echo_data = rand_ascii(echo_len)
+ ser.reset_input_buffer()
+ offset = 0
+ while offset < echo_len:
+ chunk_size = min(random.randint(1, packet_size), echo_len - offset)
+ ser.write(echo_data[offset:offset + chunk_size])
+ ser.flush()
+ # wait until this chunk is echoed back
echo = b''
- t = 5.0
- while t > 0 and len(echo) < size:
- rd = ser.read(max(1, ser.in_waiting))
+ t_end = time.monotonic() + 5.0
+ while time.monotonic() < t_end and len(echo) < chunk_size:
+ rd = ser.read(chunk_size - len(echo))
if rd:
echo += rd
- time.sleep(0.05)
- t -= 0.05
- assert echo == test_data, (f'CDC echo wrong data ({size} bytes):\n'
- f' expected: {test_data}\n received: {echo}')
+ expected = echo_data[offset:offset + chunk_size]
+ assert echo == expected, (f'CDC echo mismatch at offset {offset} ({chunk_size} bytes):\n'
+ f' expected: {expected}\n received: {echo}')
+ offset += chunk_size
ser.close()
@@ -568,7 +581,7 @@ def test_host_msc_file_explorer(board):
flasher = board['flasher']
msc_devs = [d for d in board['tests'].get('dev_attached', []) if d.get('is_msc')]
if not msc_devs:
- return
+ return 'skipped'
port = get_serial_dev(flasher["uid"], None, None, 0)
ser = open_serial_dev(port)
@@ -591,9 +604,9 @@ def test_host_msc_file_explorer(board):
timeout -= 0.1
assert b'Disk Size' in data, 'MSC device not mounted'
lines = data.decode('utf-8', errors='ignore').splitlines()
- print_msc_info(lines)
+ check_msc_info(lines, msc_devs)
- # Send "cat README.TXT" and read response
+ # Send "cat README.TXT" and check response (optional — file may not exist on all drives)
time.sleep(1)
ser.reset_input_buffer()
for ch in 'cat README.TXT\r':
@@ -601,24 +614,46 @@ def test_host_msc_file_explorer(board):
ser.flush()
time.sleep(0.002)
- # Read response
resp = b''
t = 10.0
while t > 0:
rd = ser.read(max(1, ser.in_waiting))
if rd:
resp += rd
- # wait for prompt after command output
if b'>' in resp and resp.rstrip().endswith(b'>'):
break
time.sleep(0.05)
t -= 0.05
- # Verify response contains README content
resp_text = resp.decode('utf-8', errors='ignore')
- assert MSC_README_TXT.decode() in resp_text, (f'MSC README.TXT not found in response:\n'
- f' received: {resp_text}')
- print('README.TXT matched ', end='')
+ if MSC_README_TXT.decode() in resp_text:
+ print('README.TXT matched ', end='')
+
+ # MSC throughput test: send dd command to read sectors
+ time.sleep(0.5)
+ ser.reset_input_buffer()
+ for ch in 'dd 1024\r':
+ ser.write(ch.encode())
+ ser.flush()
+ time.sleep(0.002)
+
+ # Read dd output until prompt
+ resp = b''
+ t = 30.0
+ while t > 0:
+ rd = ser.read(max(1, ser.in_waiting))
+ if rd:
+ resp += rd
+ if b'KB/s' in resp and b'>' in resp:
+ break
+ time.sleep(0.05)
+ t -= 0.05
+
+ resp_text = resp.decode('utf-8', errors='ignore')
+ for line in resp_text.splitlines():
+ if 'KB/s' in line:
+ print(f'{line.strip()} ', end='')
+ break
ser.close()
@@ -700,6 +735,53 @@ def test_device_cdc_msc(board):
data = read_disk_file(uid, 0, 'README.TXT')
assert data == MSC_README_TXT, f'MSC wrong data in README.TXT\n expected: {MSC_README_TXT.decode()}\n received: {data.decode()}'
+ # MSC dd throughput test: read all sectors then write back same data
+ dev = get_disk_dev(uid, 'TinyUSB', 0)
+ timeout = ENUM_TIMEOUT
+ while timeout > 0:
+ if os.path.exists(dev):
+ break
+ time.sleep(1)
+ timeout -= 1
+ assert timeout > 0, f'Disk {dev} not found for dd test'
+
+ block_count = 16
+ block_size = 512
+ tmp_file = f'/tmp/msc_dd_{uid}.bin'
+
+ # dd reports speed based on payload only. Each block also transfers 31-byte CBW + 13-byte CSW on USB.
+ scsi_ratio = (block_size + 31 + 13) / block_size
+
+ def parse_dd_speed(dd_output):
+ """Parse dd output, return USB-adjusted speed string"""
+ for line in dd_output.splitlines():
+ m = re.search(r'([\d.]+)\s+([kMG]?B/s)', line)
+ if m:
+ speed_val = float(m.group(1)) * scsi_ratio
+ return f'{speed_val:.1f} {m.group(2)}'
+ return ''
+
+ # Read: dd from device to file
+ ret = run_cmd(f'dd if={dev} of={tmp_file} bs={block_size} count={block_count} iflag=direct 2>&1')
+ assert ret.returncode == 0, f'dd read failed: {ret.stdout.decode()}'
+ read_speed = parse_dd_speed(ret.stdout.decode())
+
+ # Write back the same data to avoid corrupting the disk (skip if read-only)
+ ret = run_cmd(f'dd if={tmp_file} of={dev} bs={block_size} count={block_count} oflag=direct 2>&1')
+ if ret.returncode != 0 and 'Read-only' in ret.stdout.decode():
+ write_speed = 'skip (read-only)'
+ else:
+ assert ret.returncode == 0, f'dd write failed: {ret.stdout.decode()}'
+ write_speed = parse_dd_speed(ret.stdout.decode())
+
+ try:
+ os.remove(tmp_file)
+ except OSError:
+ pass
+
+ if read_speed and write_speed:
+ print(f' dd read: {read_speed}, write: {write_speed}', end='')
+
def test_device_cdc_msc_freertos(board):
test_device_cdc_msc(board)
@@ -1113,8 +1195,11 @@ def test_example(board, f1, example):
ret = globals()[f'flash_{board["flasher"]["name"].lower()}'](board, fw_name)
if ret.returncode == 0:
try:
- globals()[f'test_{example.replace("/", "_")}'](board)
- print(' OK', end='')
+ tret = globals()[f'test_{example.replace("/", "_")}'](board)
+ if tret == 'skipped':
+ print(f' {STATUS_SKIPPED}', end='')
+ else:
+ print(' OK', end='')
break
except Exception as e:
if i == max_rety - 1:
diff --git a/test/hil/tinyusb.json b/test/hil/tinyusb.json
index b4c6aaefe..86ac902ce 100644
--- a/test/hil/tinyusb.json
+++ b/test/hil/tinyusb.json
@@ -147,10 +147,24 @@
},
{
"name": "raspberry_pi_pico_w",
- "uid": "E6614C311B764A37",
+ "uid": "E6614864D35DAE36",
"tests": {
"device": false, "host": true, "dual": false,
- "dev_attached": [{"vid_pid": "1a86_55d4", "serial": "52D2023934", "is_cdc": true}]
+ "dev_attached": [
+ {
+ "vid_pid": "1a86_55d4",
+ "serial": "52D2023934",
+ "is_cdc": true
+ },
+ {
+ "vid_pid": "2008_2018",
+ "serial": "O20070925A002746",
+ "is_msc": true,
+ "block_size": 512,
+ "block_count": 4124152,
+ "msc_inquiry": "USB2.0 Flash Disk 2.10"
+ }
+ ]
},
"flasher": {
"name": "openocd",
@@ -182,7 +196,7 @@
"dev_attached": [
{"vid_pid": "0403_6001", "serial": "0", "is_cdc": true},
{"vid_pid": "058f_6387", "serial": "A8BEE062633D", "is_msc": true,
- "msc_disk_size": 3730, "msc_inquiry": "Generic Flash Disk rev 8.07"}
+ "block_size": 512, "block_count": 7639040, "msc_inquiry": "Generic Flash Disk 8.07"}
]
},
"flasher": {
diff --git a/tools/metrics.py b/tools/metrics.py
index 6b992c8f5..f624f382f 100644
--- a/tools/metrics.py
+++ b/tools/metrics.py
@@ -166,6 +166,9 @@ def compute_avg(all_json_data):
file_accumulator[fname]["symbols"][name].append(sym.get("size", 0))
sections_map = f.get("sections") or {}
for sname, ssize in sections_map.items():
+ # linkermap -v produces nested dicts {subsection: size}, flatten to total
+ if isinstance(ssize, dict):
+ ssize = sum(ssize.values())
file_accumulator[fname]["sections"][sname].append(ssize)
# Build json_average with averaged values
@@ -209,7 +212,7 @@ def compute_avg(all_json_data):
def compare_files(base_file, new_file, filters=None):
- """Compare two CSV or JSON inputs and generate difference report."""
+ """Compare two CSV or JSON inputs and generate a difference report."""
filters = filters or []
base_avg = compute_avg(combine_files([base_file], filters))