diff --git a/.claude/commands/build-doc.md b/.claude/commands/build-doc.md new file mode 100644 index 0000000000..c9ad9f5396 --- /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 0000000000..2ba35ec222 --- /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 dd4e22d7f5..8181bdcb91 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 fa864c364a..17df11951f 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 0000000000..e220bedea8 --- /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 ` | Print file contents to the console | +| cd | `cd ` | Change current working directory | +| cp | `cp ` | 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 ` | Create a directory | +| mv | `mv ` | Rename/move a file or directory | +| rm | `rm ` | 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 238af5431a..c7cc366b54 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 ecb8b378fb..819a1ba1e3 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 adb58449d3..c640250368 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 ca0e24e96c..2b6bbc43bf 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 + 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); + io_rw_32 *ep_reg = get_ep_ctrl(epnum, dir); + io_rw_32 *buf_reg = get_buf_ctrl(epnum, dir); + + // 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; - // 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); - - const bool done = hw_endpoint_xfer_continue(ep); - if (done) { - // Notify usbd + 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 06c0ce3403..02a4e055ee 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); -} - -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); - } +//--------------------------------------------------------------------+ +// 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)(void) { - uint32_t buf_status = usb_hw->buf_status; - pico_trace("buf_status 0x%08lx\n", buf_status); - - // 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); +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); } - - // 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]); - } - } + if (need_pre) { + sie_ctrl |= USB_SIE_CTRL_PREAMBLE_EN_BITS; } - if (buf_status) { - panic("Unhandled buffer %d\n", buf_status); - } + // 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(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; - } +// 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); } -static void __tusb_irq_path_func(hcd_rp2040_irq)(void) -{ - uint32_t status = usb_hw->ints; - uint32_t handled = 0; - - if ( status & USB_INTS_HOST_CONN_DIS_BITS ) - { - handled |= USB_INTS_HOST_CONN_DIS_BITS; - - if ( dev_speed() ) - { - hcd_event_device_attach(RHPORT_NATIVE, true); +// 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); + + 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; + } + } } - else - { - hcd_event_device_remove(RHPORT_NATIVE, true); + + if (usbh_dpram->epx_ctrl & EP_CTRL_DOUBLE_BUFFERED_BITS) { + buf_ctrl >>= 16; + } else { + buf_ctrl = 0; } + } while (buf_ctrl > 0); - // Clear speed change interrupt - usb_hw_clear->sie_status = USB_SIE_STATUS_SPEED_BITS; - } + usbh_dpram->epx_buf_ctrl = 0; - 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); - } + ep->state = EPSTATE_PENDING; +} - if ( status & USB_INTS_BUFF_STATUS_BITS ) - { - handled |= USB_INTS_BUFF_STATUS_BITS; - TU_LOG(2, "Buffer complete\r\n"); - handle_hwbuf_status(); - } +// 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_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(); - } + epx = ep; // switch pointer + ep->state = EPSTATE_ACTIVE; - 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 (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; - 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"); - } + epx_ctrl_prepare(ep->transfer_type); + rp2usb_buffer_start(ep, ep_reg, buf_reg, is_rx); - if ( status ^ handled ) - { - panic("Unhandled IRQ 0x%x\n", (uint) (status ^ handled)); + 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); } } -void __tusb_irq_path_func(hcd_int_handler)(uint8_t rhport, bool in_isr) { - (void) rhport; - (void) in_isr; - hcd_rp2040_irq(); +// 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]; + } + } + for (uint i = 0; i < cur_idx; i++) { + if (ep_pool[i].state >= EPSTATE_PENDING) { + return &ep_pool[i]; + } + } + return NULL; } -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; + +//--------------------------------------------------------------------+ +// 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); + + // 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); } } - return ep; } -static hw_endpoint_t *hw_endpoint_allocate(uint8_t transfer_type) { - hw_endpoint_t *ep = NULL; - - 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)]; +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 + }; + + // 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; + // 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; + + // 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; + } + } + } } -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 (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 (dir == TUSB_DIR_OUT) { - reg |= USB_ADDR_ENDP1_INTEP_DIR_BITS; + 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 (need_pre(dev_addr)) { - reg |= USB_ADDR_ENDP1_INTEP_PREAMBLE_BITS; - } - usb_hw->int_ep_addr_ctrl[ep->interrupt_num] = reg; + if (status & USB_INTS_BUFF_STATUS_BITS) { + handle_buf_status_isr(); + } - // Finally, enable interrupt that endpoint - usb_hw_set->int_ep_ctrl = 1 << (ep->interrupt_num + 1); + // 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); + } + } + #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; + } + } + } + #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; - - // 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); + (void)rhport; + + 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; - - // unconfigure the endpoint - ep->configured = false; - *hwep_ctrl_reg_host(ep) = 0; - *hwbuf_ctrl_reg_host(ep) = 0; - hw_endpoint_reset_transfer(ep); + rp2usb_critical_enter(); + + 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); - - // 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 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); - - // 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)); - - 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; + 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 { - hw_endpoint_xfer_start(ep, buffer, NULL, buflen); + // 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 + } + + // 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; + + #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; + + epx = ep; + + 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; - - // 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); + ep->xferred_len = 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 ca83acc6b5..1b13934d31 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 -#include "rp2040_usb.h" + #include + #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; - - if (and_mask) { - value = buf_ctrl & and_mask; +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 + + // 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); +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; - uint32_t buf_ctrl = buflen | USB_BUF_CTRL_AVAIL; - - // 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); +// 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 (ep->state == EPSTATE_IDLE) { + // probably land here due to short packet on rx with double buffered + hw_endpoint_lock_update(ep, -1); + return false; + } - io_rw_32 *buf_ctrl_reg; - io_rw_32 *ep_ctrl_reg; - bool is_rx; + const bool is_host = rp2usb_is_host_mode(); + const bool is_double = (ep_reg != NULL && ((*ep_reg) & EP_CTRL_DOUBLE_BUFFERED_BITS)); - #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 + // 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 - { - buf_ctrl_reg = hwbuf_ctrl_reg_device(ep); - ep_ctrl_reg = hwep_ctrl_reg_device(ep); - is_rx = (dir == TUSB_DIR_OUT); + { + dpram_buf += 64; // buf1 offset + } } - 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 - - // 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); + io_rw_16 *buf_reg16 = (io_rw_16 *)buf_reg; + uint16_t buf_ctrl16 = *(buf_reg16 + buf_id); + + 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; + + // 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 + + 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 { - // 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)) ) {} - - 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_32 *buf_ctrl_reg = is_host ? hwbuf_ctrl_reg_host(ep) : hwbuf_ctrl_reg_device(ep); - hwbuf_ctrl_set(buf_ctrl_reg, 0); - - usb_hw->abort &= ~TU_BIT(ep_id); + // 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) {} + } + } - 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 + // 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 } - } -} -// 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); + *buf_reg = 0; // reset buffer control - // Part way through a transfer - if (!ep->active) { - panic("Can't continue xfer on inactive ep %02X", ep->ep_addr); - } - - 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 c03dc34b23..8ebc3e3fc9 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 41e9fad88b..cf3cff1a32 100755 --- a/test/hil/hil_test.py +++ b/test/hil/hil_test.py @@ -50,7 +50,7 @@ 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() + packet_size = 64 - # 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) - - # 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 rand_ascii(length): 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 b4c6aaefef..86ac902ce8 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 6b992c8f52..f624f382f0 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))