diff options
| author | hathach <[email protected]> | 2026-04-01 15:51:31 +0700 |
|---|---|---|
| committer | hathach <[email protected]> | 2026-04-01 17:47:01 +0700 |
| commit | 78d34d5b6488d7129530461ce1865a33a946e698 (patch) | |
| tree | 6c81065a6f0624e265bf581c1c7a8f650a72b2b4 | |
| parent | 9ac343a0471b03b1a76166c72347e7fdbf2c4800 (diff) | |
reduce code size, use state to replace active + pending
| -rw-r--r-- | .claude/commands/build-doc.md | 24 | ||||
| -rw-r--r-- | .claude/commands/hil.md | 30 | ||||
| -rw-r--r-- | examples/host/msc_file_explorer/README.md | 105 | ||||
| -rw-r--r-- | src/portable/raspberrypi/rp2040/dcd_rp2040.c | 13 | ||||
| -rw-r--r-- | src/portable/raspberrypi/rp2040/hcd_rp2040.c | 106 | ||||
| -rw-r--r-- | src/portable/raspberrypi/rp2040/rp2040_usb.c | 78 | ||||
| -rw-r--r-- | src/portable/raspberrypi/rp2040/rp2040_usb.h | 27 | ||||
| -rw-r--r-- | tools/metrics.py | 5 |
8 files changed, 266 insertions, 122 deletions
diff --git a/.claude/commands/build-doc.md b/.claude/commands/build-doc.md new file mode 100644 index 000000000..c9ad9f539 --- /dev/null +++ b/.claude/commands/build-doc.md @@ -0,0 +1,24 @@ +# build-doc + +Scan all example READMEs and build the Sphinx documentation. + +## Instructions + +1. Install docs dependencies: + ```bash + pip install -r docs/requirements.txt + ``` + +2. Build the docs from the repo root: + ```bash + sphinx-build -b html docs docs/_build + ``` + `conf.py` automatically scans all `examples/{device,host,dual}/*/README.md`, copies them into `docs/examples/`, and regenerates `examples.rst` with the toctree. + +3. Use a timeout of at least 60 seconds. + +4. After the build completes: + - Show the build output to the user. + - Report total warnings and errors. + - List which example READMEs were discovered and included. + - If there are errors, suggest fixes. diff --git a/.claude/commands/hil.md b/.claude/commands/hil.md new file mode 100644 index 000000000..2ba35ec22 --- /dev/null +++ b/.claude/commands/hil.md @@ -0,0 +1,30 @@ +# hil + +Run Hardware-in-the-Loop (HIL) tests on physical boards. + +## Arguments +- $ARGUMENTS: Optional flags (e.g. board name, extra args). If empty, runs all boards with default config. + +## Instructions + +1. Determine the HIL config file: + ```bash + HIL_CONFIG=$( (systemctl list-units --type=service --state=running 2>/dev/null; systemctl --user list-units --type=service --state=running 2>/dev/null) | grep -q 'actions\.runner' && echo tinyusb.json || echo local.json ) + ``` + Default is `local.json` for local development. + +2. Parse $ARGUMENTS: + - If $ARGUMENTS contains `-b BOARD_NAME`, run for that specific board only. + - If $ARGUMENTS is empty or has no `-b`, run for all boards in the config. + - Pass through any other flags (e.g. `-v` for verbose) directly to the command. + +3. Run the HIL test from the repo root directory: + - Specific board: `python test/hil/hil_test.py -b BOARD_NAME -B examples $HIL_CONFIG $EXTRA_ARGS` + - All boards: `python test/hil/hil_test.py -B examples $HIL_CONFIG $EXTRA_ARGS` + +4. Use a timeout of at least 20 minutes (600000ms). HIL tests take 2-5 minutes. NEVER cancel early. + +5. After the test completes: + - Show the test output to the user. + - Summarize pass/fail results per board. + - If there are failures, suggest re-running with `-v` flag for verbose output to help debug. diff --git a/examples/host/msc_file_explorer/README.md b/examples/host/msc_file_explorer/README.md new file mode 100644 index 000000000..e220bedea --- /dev/null +++ b/examples/host/msc_file_explorer/README.md @@ -0,0 +1,105 @@ +# MSC File Explorer + +This host example implements an interactive command-line file browser for USB Mass Storage devices. +When a USB flash drive is connected, the device is automatically mounted using FatFS and a shell-like +CLI is presented over the board's serial console. + +## Features + +- Automatic mount/unmount of USB storage devices +- FAT12/16/32 filesystem support via FatFS +- Interactive CLI with command history +- Read speed benchmarking with `dd` +- Support for up to 4 simultaneous USB storage devices (via hub) + +## Supported Commands + +| Command | Usage | Description | +|---------|--------------------|------------------------------------------------------| +| help | `help` | Print list of available commands | +| cat | `cat <file>` | Print file contents to the console | +| cd | `cd <dir>` | Change current working directory | +| cp | `cp <src> <dest>` | Copy a file | +| dd | `dd [count]` | Read sectors and report speed (default 1024 sectors) | +| ls | `ls [dir]` | List directory contents | +| pwd | `pwd` | Print current working directory | +| mkdir | `mkdir <dir>` | Create a directory | +| mv | `mv <src> <dest>` | Rename/move a file or directory | +| rm | `rm <file>` | Remove a file | + +## Build + +Build for a specific board using CMake (see [Getting Started](https://docs.tinyusb.org/en/latest/getting_started.html)): + +```bash +# Example: build for Raspberry Pi Pico +cmake -B build -DBOARD=raspberry_pi_pico -DFAMILY=rp2040 examples/host/msc_file_explorer +cmake --build build +``` + +## Usage + +1. Flash the firmware to your board. +2. Open a serial terminal (e.g. `minicom`, `screen`, `PuTTY`) at 115200 baud. +3. Plug a USB flash drive into the board's USB host port. +4. The device is auto-mounted and the prompt appears: + +``` +TinyUSB MSC File Explorer Example + +Device connected + Vendor : Kingston + Product : DataTraveler 2.0 + Rev : 1.0 + Capacity: 1.9 GB + +0:/> _ +``` + +### Browsing Files + +``` +0:/> ls +----a 1234 readme.txt +d---- 0 photos +d---- 0 docs + +0:/> cd photos +0:/photos> ls +----a 520432 vacation.jpg +----a 312088 family.png + +0:/> cat readme.txt +Hello from USB drive! +``` + +### Copying and Moving Files + +``` +0:/> cp readme.txt backup.txt +0:/> mv backup.txt docs/backup.txt +``` + +### Measuring Read Speed + +``` +0:/> dd +Reading 1024 sectors... + Data speed: 823 KB/s +``` + +### Multiple Devices + +When using a USB hub, multiple drives are mounted as `0:`, `1:`, etc. Use the drive prefix to +navigate between them: + +``` +0:/> cd 1: +1:/> ls +``` + +## Testing + +This example is part of the TinyUSB HIL (Hardware-in-the-Loop) test suite. The HIL test +automatically flashes, runs the example, and verifies MSC enumeration and file operations +against a known USB drive. diff --git a/src/portable/raspberrypi/rp2040/dcd_rp2040.c b/src/portable/raspberrypi/rp2040/dcd_rp2040.c index 8814f95d1..ca03ebf8a 100644 --- a/src/portable/raspberrypi/rp2040/dcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/dcd_rp2040.c @@ -111,7 +111,6 @@ static void hw_endpoint_open(uint8_t ep_addr, uint16_t wMaxPacketSize, uint8_t t // double buffered Bulk endpoint if (transfer_type == TUSB_XFER_BULK) { size *= 2u; - #if CFG_TUSB_RP2_ERRATA_E15 if (dir == TUSB_DIR_IN) { ep->e15_bulk_in = true; @@ -195,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 } } @@ -254,12 +253,12 @@ static void __tusb_irq_path_func(dcd_rp2040_irq)(void) { struct hw_endpoint *ep = hw_endpoint_get(i, TUSB_DIR_IN); // Active Bulk IN endpoint requires SOF - if (ep->e15_bulk_in && ep->active) { + if (ep->e15_bulk_in && ep->state == EPSTATE_ACTIVE) { keep_sof_alive = true; hw_endpoint_lock_update(ep, 1); - if (ep->pending) { - ep->pending = 0; + 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; @@ -276,7 +275,7 @@ static void __tusb_irq_path_func(dcd_rp2040_irq)(void) { 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, false); + 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); @@ -501,7 +500,7 @@ bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *ep_desc) struct hw_endpoint *ep = hw_endpoint_get(epnum, dir); 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->max_packet_size = ep_desc->wMaxPacketSize; diff --git a/src/portable/raspberrypi/rp2040/hcd_rp2040.c b/src/portable/raspberrypi/rp2040/hcd_rp2040.c index fb1676f50..02a4e055e 100644 --- a/src/portable/raspberrypi/rp2040/hcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/hcd_rp2040.c @@ -66,6 +66,10 @@ enum { SIE_CTRL_SPEED_FULL = 2, }; +enum { + EPX_CTRL_DEFAULT = EP_CTRL_ENABLE_BITS | EP_CTRL_INTERRUPT_PER_BUFFER | offsetof(usb_host_dpram_t, epx_data) +}; + //--------------------------------------------------------------------+ // //--------------------------------------------------------------------+ @@ -123,7 +127,7 @@ TU_ATTR_ALWAYS_INLINE static inline void sie_stop_xfer(void) { while (usb_hw->sie_ctrl & USB_SIE_CTRL_STOP_TRANS_BITS) {} } -TU_ATTR_ALWAYS_INLINE static inline void sie_start_xfer(bool send_setup, bool is_rx, bool need_pre) { +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; @@ -135,31 +139,16 @@ TU_ATTR_ALWAYS_INLINE static inline void sie_start_xfer(bool send_setup, bool is } // 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". + // 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; } -TU_ATTR_ALWAYS_INLINE static inline void epx_start_xfer(hw_endpoint_t *ep, bool is_setup) { - 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, tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN, ep->need_pre); -} - // prepare epx_ctrl register for new endpoint -TU_ATTR_ALWAYS_INLINE static inline void epx_ctrl_prepare(hw_endpoint_t *ep) { - // RP2040-E4: USB host writes status to the upper half of buffer control in single buffered mode. - // The buffer selector toggles even in single-buffered mode, so the previous transfer's status - // may have been written to BUF1 half, leaving BUF0 with a stale AVAILABLE bit. Clear it here. - #if defined(PICO_RP2040) && PICO_RP2040 == 1 - usbh_dpram->epx_buf_ctrl = 0; - #endif - - // ep control - const uint32_t ep_ctrl = EP_CTRL_ENABLE_BITS | EP_CTRL_INTERRUPT_PER_BUFFER | - ((uint32_t)ep->transfer_type << EP_CTRL_BUFFER_TYPE_LSB) | hw_data_offset(ep->dpram_buf); - usbh_dpram->epx_ctrl = ep_ctrl; +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); } // Save buffer context for EPX preemption (called after STOP_TRANS). @@ -196,31 +185,30 @@ static void __tusb_irq_path_func(epx_save_context)(hw_endpoint_t *ep) { usbh_dpram->epx_buf_ctrl = 0; - ep->pending = 1; - ep->active = false; + ep->state = EPSTATE_PENDING; } -// All non-interrupt endpoints use shared EPX. -// Save the current EPX context, mark pending, switch to ep +// 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->pending == 2); + const bool is_setup = (ep->state == EPSTATE_PENDING_SETUP); - epx = ep; // switch pointer - ep->pending = 0; - ep->active = true; + epx = ep; // switch pointer + ep->state = EPSTATE_ACTIVE; if (is_setup) { // panic("new setup \n"); - epx_start_xfer(ep, true); + usb_hw->dev_addr_ctrl = ep->dev_addr; + sie_start_xfer(true, false, ep->need_pre); } else { - io_rw_32 *ep_reg = &usbh_dpram->epx_ctrl; + 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; - epx_ctrl_prepare(ep); - rp2usb_buffer_start(ep, ep_reg, buf_reg, tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN, - ep->transfer_type == TUSB_XFER_INTERRUPT); + epx_ctrl_prepare(ep->transfer_type); + rp2usb_buffer_start(ep, ep_reg, buf_reg, is_rx); - epx_start_xfer(ep, false); + 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); } } @@ -228,12 +216,12 @@ static void __tusb_irq_path_func(epx_switch_ep)(hw_endpoint_t *ep) { 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].pending) { + if (ep_pool[i].state >= EPSTATE_PENDING) { return &ep_pool[i]; } } for (uint i = 0; i < cur_idx; i++) { - if (ep_pool[i].pending) { + if (ep_pool[i].state >= EPSTATE_PENDING) { return &ep_pool[i]; } } @@ -246,7 +234,7 @@ static hw_endpoint_t *__tusb_irq_path_func(epx_next_pending)(hw_endpoint_t *cur_ //--------------------------------------------------------------------+ 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 - uint xferred_len = ep->xferred_len; + 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); @@ -345,7 +333,7 @@ static void __tusb_irq_path_func(hcd_rp2040_irq)(void) { // 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->active) { + if (epx->state == EPSTATE_ACTIVE) { xfer_complete_isr(epx, XFER_RESULT_FAILED, false); } } @@ -392,7 +380,7 @@ static void __tusb_irq_path_func(hcd_rp2040_irq)(void) { usb_hw_clear->inte = USB_INTE_HOST_SOF_BITS; usb_hw->nak_poll = USB_NAK_POLL_RESET; epx_switch_request = false; - } else if (epx->active) { + } 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; @@ -496,20 +484,14 @@ void hcd_device_close(uint8_t rhport, uint8_t dev_addr) { return; // address 0 is for device enumeration } - // reset epx if it is currently active with unplugged device - if (epx->max_packet_size > 0 && epx->dev_addr == dev_addr) { - // if (epx->active) { - // // need to abort transfer - // } - epx->max_packet_size = 0; - } + 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->pending = 0; // clear any pending transfer + ep->state = EPSTATE_IDLE; // clear any pending transfer - if (ep->interrupt_num) { + 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; @@ -523,6 +505,8 @@ void hcd_device_close(uint8_t rhport, uint8_t dev_addr) { ep->max_packet_size = 0; // mark as unused } } + + rp2usb_critical_exit(); } uint32_t hcd_frame_number(uint8_t rhport) { @@ -557,16 +541,15 @@ bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_endpoint_t const uint8_t ep_addr = ep_desc->bEndpointAddress; const uint16_t max_packet_size = tu_edpt_packet_size(ep_desc); - const uint8_t transfer_type = ep_desc->bmAttributes.xfer; ep->max_packet_size = max_packet_size; ep->ep_addr = ep_addr; ep->dev_addr = dev_addr; - ep->transfer_type = transfer_type; + ep->transfer_type = ep_desc->bmAttributes.xfer; ep->need_pre = need_pre(dev_addr); ep->next_pid = 0u; - if (transfer_type != TUSB_XFER_INTERRUPT) { + if (ep->transfer_type != TUSB_XFER_INTERRUPT) { ep->dpram_buf = usbh_dpram->epx_data; } else { // from 15 interrupt endpoints pool @@ -627,7 +610,7 @@ bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *b hw_endpoint_t *ep = edpt_find(dev_addr, ep_addr); TU_ASSERT(ep); - if (ep->transfer_type == TUSB_XFER_INTERRUPT) { + 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); @@ -642,10 +625,10 @@ bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *b // If EPX is busy with another transfer, mark as pending rp2usb_critical_enter(); - if (epx->active) { + if (epx->state == EPSTATE_ACTIVE) { ep->user_buf = buffer; ep->remaining_len = buflen; - ep->pending = 1; + ep->state = EPSTATE_PENDING; #ifdef HAS_STOP_EPX_ON_NAK usb_hw_set->nak_poll = USB_NAK_POLL_STOP_EPX_ON_NAK_BITS; @@ -660,9 +643,10 @@ bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *b epx = ep; - epx_ctrl_prepare(ep); + epx_ctrl_prepare(ep->transfer_type); rp2usb_xfer_start(ep, ep_reg, buf_reg, buffer, NULL, buflen); // prepare bufctrl - epx_start_xfer(ep, false); + 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(); } @@ -688,8 +672,8 @@ bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, const uint8_t setup_packet ep->xferred_len = 0; // If EPX is busy, mark as pending setup (DPRAM already has the packet) - if (epx->active) { - ep->pending = 2; // setup + 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 @@ -697,9 +681,11 @@ bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, const uint8_t setup_packet usb_hw_set->inte = USB_INTE_HOST_SOF_BITS; #endif } else { - epx = ep; - ep->active = true; - epx_start_xfer(ep, true); + epx = ep; + ep->state = EPSTATE_ACTIVE; + + 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(); diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.c b/src/portable/raspberrypi/rp2040/rp2040_usb.c index 156be62e4..206da041b 100644 --- a/src/portable/raspberrypi/rp2040/rp2040_usb.c +++ b/src/portable/raspberrypi/rp2040/rp2040_usb.c @@ -101,12 +101,13 @@ void rp2usb_init(void) { } void __tusb_irq_path_func(rp2usb_reset_transfer)(hw_endpoint_t *ep) { - ep->active = false; - ep->pending = 0; + ep->state = EPSTATE_IDLE; ep->remaining_len = 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(bufctrl_write32)(io_rw_32 *buf_reg, uint32_t value) { @@ -183,14 +184,19 @@ uint16_t __tusb_irq_path_func(bufctrl_prepare16)(hw_endpoint_t *ep, uint8_t *dpr } // 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, - bool force_single) { +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 + const bool force_single = (rp2usb_is_host_mode() && ep->interrupt_num > 0); + #else + const bool force_single = false; + #endif + if (ep->remaining_len && !force_single) { // Use buffer 1 (double buffered) if there is still data buf_ctrl |= (uint32_t)bufctrl_prepare16(ep, ep->dpram_buf + 64, is_rx) << 16; @@ -211,8 +217,7 @@ void rp2usb_xfer_start(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, u (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); rp2usb_reset_transfer(ep); } @@ -220,7 +225,7 @@ void rp2usb_xfer_start(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, u // Fill in info now that we're kicking off the hw ep->remaining_len = total_len; ep->xferred_len = 0; - ep->active = true; + ep->state = EPSTATE_ACTIVE; #if CFG_TUD_EDPT_DEDICATED_HWFIFO if (ff != NULL) { @@ -229,66 +234,50 @@ void rp2usb_xfer_start(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, u } else #endif { - ep->user_buf = buffer; + ep->user_buf = buffer; + #if CFG_TUD_EDPT_DEDICATED_HWFIFO ep->is_xfer_fifo = false; + #endif } const bool is_host = rp2usb_is_host_mode(); + const bool is_rx = (is_host == (tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN)); - if (ep->future_len > 0) { - // only on rx endpoint + #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) { - // all data has been received, no need to start hw transfer - ep->active = false; const uint16_t xferred_len = ep->xferred_len; rp2usb_reset_transfer(ep); - - #if CFG_TUH_ENABLED - if (is_host) { - hcd_event_xfer_complete(0, ep->ep_addr, xferred_len, XFER_RESULT_SUCCESS, false); - } - #endif - #if CFG_TUD_ENABLED - if (!is_host) { - dcd_event_xfer_complete(0, ep->ep_addr, xferred_len, XFER_RESULT_SUCCESS, false); - } - #endif - + 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 CFG_TUSB_RP2_ERRATA_E15 if (ep->e15_bulk_in) { usb_hw_set->inte = USB_INTS_DEV_SOF_BITS; // skip transfer if we are in critical frame period if (e15_is_critical_frame_period()) { - ep->pending = 1; + ep->state = EPSTATE_PENDING; hw_endpoint_lock_update(ep, -1); return; } } - #endif + #endif // CFG_TUSB_RP2_ERRATA_E15 + #endif // CFG_TUD_ENABLED - const bool is_rx = (is_host == (tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN)); - #if CFG_TUH_ENABLED - const bool force_single = (is_host && ep->transfer_type == TUSB_XFER_INTERRUPT); - #else - const bool force_single = false; - #endif - - rp2usb_buffer_start(ep, ep_reg, buf_reg, is_rx, force_single); + rp2usb_buffer_start(ep, ep_reg, buf_reg, is_rx); hw_endpoint_lock_update(ep, -1); } @@ -333,7 +322,7 @@ bool __tusb_irq_path_func(rp2usb_xfer_continue)(hw_endpoint_t *ep, io_rw_32 *ep_ bool is_rx) { hw_endpoint_lock_update(ep, 1); - if (!ep->active) { + if (ep->state != EPSTATE_ACTIVE) { // probably land here due to short packet on rx with double buffered hw_endpoint_lock_update(ep, -1); return false; @@ -394,12 +383,12 @@ bool __tusb_irq_path_func(rp2usb_xfer_continue)(hw_endpoint_t *ep, io_rw_32 *ep_ 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 (is_host) { - // host put future_len pointer at end of epx_data - } else { + #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; } - ep->future_bufid = buf_id ^ 1; + #endif } else { ep->next_pid ^= 1u; // roll back pid if aborted } @@ -422,10 +411,11 @@ bool __tusb_irq_path_func(rp2usb_xfer_continue)(hw_endpoint_t *ep, io_rw_32 *ep_ if (!is_done && ep->remaining_len > 0) { #if CFG_TUSB_RP2_ERRATA_E15 - if (ep->e15_bulk_in && e15_is_critical_frame_period()) { + const bool need_e15 = ep->e15_bulk_in; + if (need_e15 && e15_is_critical_frame_period()) { // mark as pending if matches E15 condition - ep->pending = 1; - } else if (ep->e15_bulk_in && ep->pending) { + 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 diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.h b/src/portable/raspberrypi/rp2040/rp2040_usb.h index 2ba9d018e..8ebc3e3fc 100644 --- a/src/portable/raspberrypi/rp2040/rp2040_usb.h +++ b/src/portable/raspberrypi/rp2040/rp2040_usb.h @@ -32,9 +32,9 @@ // 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 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 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 @@ -90,18 +90,23 @@ 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 - uint8_t pending; // Transfer scheduled but not active - bool is_xfer_fifo; // transfer using fifo + uint8_t state; - uint8_t future_bufid; - uint8_t future_len; +#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 @@ -110,8 +115,10 @@ typedef struct hw_endpoint { #if CFG_TUH_ENABLED uint8_t dev_addr; uint8_t interrupt_num; // 1-15 for interrupt endpoints - uint8_t transfer_type; - bool need_pre; // need preamble for low speed device behind full speed hub + 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 max_packet_size; // max packet size also indicates configured @@ -153,7 +160,7 @@ TU_ATTR_ALWAYS_INLINE static inline void rp2usb_critical_exit(void) { 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, bool force_single); +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); diff --git a/tools/metrics.py b/tools/metrics.py index 6b992c8f5..f624f382f 100644 --- a/tools/metrics.py +++ b/tools/metrics.py @@ -166,6 +166,9 @@ def compute_avg(all_json_data): file_accumulator[fname]["symbols"][name].append(sym.get("size", 0)) sections_map = f.get("sections") or {} for sname, ssize in sections_map.items(): + # linkermap -v produces nested dicts {subsection: size}, flatten to total + if isinstance(ssize, dict): + ssize = sum(ssize.values()) file_accumulator[fname]["sections"][sname].append(ssize) # Build json_average with averaged values @@ -209,7 +212,7 @@ def compute_avg(all_json_data): def compare_files(base_file, new_file, filters=None): - """Compare two CSV or JSON inputs and generate difference report.""" + """Compare two CSV or JSON inputs and generate a difference report.""" filters = filters or [] base_avg = compute_avg(combine_files([base_file], filters)) |
