Skip to content

Conversation

@yueduz
Copy link

@yueduz yueduz commented Oct 30, 2025

Summary by CodeRabbit

  • New Features

    • Implemented ISOCHRONOUS USB endpoint support with dual-buffering capability for continuous, real-time data transfers
    • Added automatic buffer switching and intelligent management during endpoint operations
  • Refactor

    • Enhanced endpoint initialization, closure, and interrupt-driven transfer handling for complex USB scenarios
    • Optimized write operations and data path management for improved USB device compatibility

@coderabbitai
Copy link

coderabbitai bot commented Oct 30, 2025

Walkthrough

This pull request adds ISOCHRONOUS endpoint support with double-buffering to the FSDEV USB device controller. Changes include removing ISO assertion checks, implementing dual-buffer PMA setup during endpoint initialization, updating endpoint write operations to handle ISO buffers, and extending the IRQ handler to manage ISO transfers with proper data toggle handling.

Changes

Cohort / File(s) Summary
ISOCHRONOUS endpoint configuration
port/fsdev/usb_dc_fsdev.c
Removes strict ISO endpoint assertion; adds double-buffer PMA setup (dual addresses, DTOG bits, DBUF counts) on ISO endpoint open; extends close to reset data toggle for ISO endpoints; introduces internal is_in flag and pmabuffer management.
ISOCHRONOUS write and transfer handling
port/fsdev/usb_dc_fsdev.c
Refactors usbd_ep_start_write to use DBUF0/DBUF1 counters for ISO based on TX DTOG state; selects correct PMA address for writes. Extends IRQHandler for ISO OUT to read from correct dual buffer (DBUF0/DBUF1) per RX DTOG and emit completion events.
ISOCHRONOUS IN transfer completion
port/fsdev/usb_dc_fsdev.c
Updates IRQHandler IN path (CTR_TX) to select correct DBUF counter (DBUF0_CNT or DBUF1_CNT) based on TX DTOG for ISO endpoints; implements double-buffer write logic for typical transfers.
Buffer management macro
port/fsdev/usb_fsdev_reg.h
Introduces PCD_FREE_USER_BUFFER(USBx, bEpNum, bDir) macro to toggle DTOG based on transfer direction for double-buffered endpoints.

Sequence Diagram(s)

sequenceDiagram
    participant App as Application
    participant FSDEV as FSDEV USB<br/>(Endpoint)
    participant DMA as PMA<br/>(Dual Buffers)
    participant ISR as IRQHandler

    App->>FSDEV: Open ISO Endpoint
    FSDEV->>DMA: Setup DBUF0 & DBUF1<br/>addresses
    FSDEV->>FSDEV: Initialize DBUF counts<br/>& DTOG bits

    Note over App,ISR: OUT (CTR_RX) ISO Transfer
    App->>FSDEV: usbd_ep_start_read()
    FSDEV->>DMA: RX_VALID (awaiting data)
    DMA->>ISR: Data received in DBUF0
    ISR->>DMA: Read from active buffer<br/>(DBUF0 or DBUF1 per DTOG RX)
    ISR->>FSDEV: Free user buffer<br/>(toggle DTOG)
    ISR->>App: Transfer complete event

    Note over App,ISR: IN (CTR_TX) ISO Transfer
    App->>FSDEV: usbd_ep_start_write()
    FSDEV->>DMA: Write to active DBUF<br/>(per TX DTOG)
    FSDEV->>DMA: TX status = VALID
    DMA->>ISR: Transmission complete (CTR_TX)
    ISR->>FSDEV: Select next DBUF counter<br/>(DBUF0_CNT or DBUF1_CNT)
    ISR->>ISR: Advance transfer state
    alt Transfer complete
        ISR->>App: Completion event
    else More data
        ISR->>DMA: Write next buffer<br/>& set TX_VALID
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Logic density: Multiple interrelated changes across endpoint lifecycle (open → write → close → IRQ) with intricate double-buffer and data toggle management.
  • File heterogeneity: Changes span control logic in usb_dc_fsdev.c and register macro definitions, requiring separate reasoning per area.
  • ISO-specific logic: Double-buffer address calculation, DBUF counter selection based on DTOG state, and mirrored handling in both OUT and IN paths demand careful verification.
  • Areas requiring attention:
    • Double-buffer address offset calculations (endpoint open) and correctness of PMA base address usage
    • DTOG-based buffer selection logic in start_write and IRQ handler (verify DBUF0 vs DBUF1 selection)
    • Interaction between data toggle toggling and buffer switching in the IRQ handler
    • Boundary conditions in ISO transfers (e.g., final buffer handling, buffer exhaustion)

Poem

🐰 Through buffers dual, the data flows,
ISO transfers—where the toggle goes!
Double-dance of DBUF0, DBUF1 spins,
As DTOG tracks which buffer wins.
Fast and furious, no wait, no delay—
FSDEV hops isochronously today! 📡✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "Add isochronous transfer support to fsdev by configuring double-buffering for isochronous endpoints" is directly and fully aligned with the changeset. The title accurately describes the main changes across both modified files: the significant refactoring of usb_dc_fsdev.c to implement double-buffering for isochronous endpoints in open/close/write/IRQ paths, and the addition of the supporting PCD_FREE_USER_BUFFER macro. The title is concise, specific, and clearly conveys the primary objective without vague terminology or unnecessary details.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dd46b8c and fcb2d9d.

📒 Files selected for processing (2)
  • port/fsdev/usb_dc_fsdev.c (9 hunks)
  • port/fsdev/usb_fsdev_reg.h (1 hunks)

Comment on lines +184 to +200
PCD_CLEAR_EP_KIND(USB, ep_idx);
/* Set buffer address for double buffered mode */
PCD_SET_EP_DBUF_ADDR(USB, ep_idx, g_fsdev_udc.out_ep[ep_idx].ep_pma_addr,
g_fsdev_udc.out_ep[ep_idx].ep_pma_addr + g_fsdev_udc.out_ep[ep_idx].ep_mps);

/* Clear the data toggle bits for the endpoint IN/OUT */
PCD_CLEAR_RX_DTOG(USB, ep_idx);
PCD_CLEAR_TX_DTOG(USB, ep_idx);

/* Set endpoint RX count */
PCD_SET_EP_DBUF_CNT(USB, ep_idx, 0, ep->wMaxPacketSize);

/* Set endpoint RX to valid state */
PCD_SET_EP_RX_STATUS(USB, ep_idx, USB_EP_RX_VALID);
PCD_SET_EP_TX_STATUS(USB, ep_idx, USB_EP_TX_DIS);
g_fsdev_udc.pma_offset += USB_GET_MAXPACKETSIZE(ep->wMaxPacketSize * 2);
} else {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Double buffer never enabled for ISO endpoints

PCD_CLEAR_EP_KIND disables the double-buffer flag. Without setting EP_KIND/PCD_SET_EP_DBUF, the hardware stays single-buffered, so all the subsequent DBUF address/count programming and DTOG handling never take effect. The new ISO paths will therefore malfunction. Please enable the DBUF bit (both OUT and IN branches) instead of clearing it.

-                PCD_CLEAR_EP_KIND(USB, ep_idx);
+                PCD_SET_EP_DBUF(USB, ep_idx);
...
-                PCD_CLEAR_EP_KIND(USB, ep_idx);
+                PCD_SET_EP_DBUF(USB, ep_idx);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
PCD_CLEAR_EP_KIND(USB, ep_idx);
/* Set buffer address for double buffered mode */
PCD_SET_EP_DBUF_ADDR(USB, ep_idx, g_fsdev_udc.out_ep[ep_idx].ep_pma_addr,
g_fsdev_udc.out_ep[ep_idx].ep_pma_addr + g_fsdev_udc.out_ep[ep_idx].ep_mps);
/* Clear the data toggle bits for the endpoint IN/OUT */
PCD_CLEAR_RX_DTOG(USB, ep_idx);
PCD_CLEAR_TX_DTOG(USB, ep_idx);
/* Set endpoint RX count */
PCD_SET_EP_DBUF_CNT(USB, ep_idx, 0, ep->wMaxPacketSize);
/* Set endpoint RX to valid state */
PCD_SET_EP_RX_STATUS(USB, ep_idx, USB_EP_RX_VALID);
PCD_SET_EP_TX_STATUS(USB, ep_idx, USB_EP_TX_DIS);
g_fsdev_udc.pma_offset += USB_GET_MAXPACKETSIZE(ep->wMaxPacketSize * 2);
} else {
PCD_SET_EP_DBUF(USB, ep_idx);
/* Set buffer address for double buffered mode */
PCD_SET_EP_DBUF_ADDR(USB, ep_idx, g_fsdev_udc.out_ep[ep_idx].ep_pma_addr,
g_fsdev_udc.out_ep[ep_idx].ep_pma_addr + g_fsdev_udc.out_ep[ep_idx].ep_mps);
/* Clear the data toggle bits for the endpoint IN/OUT */
PCD_CLEAR_RX_DTOG(USB, ep_idx);
PCD_CLEAR_TX_DTOG(USB, ep_idx);
/* Set endpoint RX count */
PCD_SET_EP_DBUF_CNT(USB, ep_idx, 0, ep->wMaxPacketSize);
/* Set endpoint RX to valid state */
PCD_SET_EP_RX_STATUS(USB, ep_idx, USB_EP_RX_VALID);
PCD_SET_EP_TX_STATUS(USB, ep_idx, USB_EP_TX_DIS);
g_fsdev_udc.pma_offset += USB_GET_MAXPACKETSIZE(ep->wMaxPacketSize * 2);
} else {
🤖 Prompt for AI Agents
In port/fsdev/usb_dc_fsdev.c around lines 184-200, the code calls
PCD_CLEAR_EP_KIND which disables double-buffering so the subsequent DBUF
address/count programming and DTOG handling are ignored for ISO endpoints;
replace the clear with the DBUF enable call(s) (e.g., PCD_SET_EP_KIND /
PCD_SET_EP_DBUF or the platform-specific macro) for both the OUT and IN branches
so the hardware is configured double-buffered, then leave the DBUF address, DBUF
count and DTOG clears as-is to complete proper double-buffer setup.

Comment on lines +488 to 505
/* manage double buffer bulk out */
/* free EP OUT Buffer */
PCD_FREE_USER_BUFFER(USB, ep_idx, 0U);

if ((PCD_GET_ENDPOINT(USB, ep_idx) & USB_EP_DTOG_RX) != 0U) {
/* read from endpoint BUF0Addr buffer */
read_count = (uint16_t)PCD_GET_EP_DBUF0_CNT(USB, ep_idx);
fsdev_read_pma(USB, g_fsdev_udc.out_ep[ep_idx].xfer_buf, g_fsdev_udc.out_ep[ep_idx].ep_pma_addr, (uint16_t)read_count);
} else {
/* read from endpoint BUF1Addr buffer */
read_count = (uint16_t)PCD_GET_EP_DBUF1_CNT(USB, ep_idx);
fsdev_read_pma(USB, g_fsdev_udc.out_ep[ep_idx].xfer_buf, g_fsdev_udc.out_ep[ep_idx].ep_pma_addr + g_fsdev_udc.out_ep[ep_idx].ep_mps, (uint16_t)read_count);
}
} else {
read_count = PCD_GET_EP_RX_CNT(USB, ep_idx);
fsdev_read_pma(USB, g_fsdev_udc.out_ep[ep_idx].xfer_buf, g_fsdev_udc.out_ep[ep_idx].ep_pma_addr, (uint16_t)read_count);
}
g_fsdev_udc.out_ep[ep_idx].xfer_buf += read_count;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Freeing ISO OUT buffer before copy corrupts data

Calling PCD_FREE_USER_BUFFER before copying toggles the SW_BUF bit, so the controller may reuse that PMA slot for the next ISO frame while we are still reading it. The PMA read must happen first, then free the user buffer. Please move the PCD_FREE_USER_BUFFER call to after the read completes.

-                        /* free EP OUT Buffer */
-                        PCD_FREE_USER_BUFFER(USB, ep_idx, 0U);
-
                         if ((PCD_GET_ENDPOINT(USB, ep_idx) & USB_EP_DTOG_RX) != 0U) {
                             /* read from endpoint BUF0Addr buffer */
                             read_count = (uint16_t)PCD_GET_EP_DBUF0_CNT(USB, ep_idx);
                             fsdev_read_pma(USB, g_fsdev_udc.out_ep[ep_idx].xfer_buf, g_fsdev_udc.out_ep[ep_idx].ep_pma_addr, (uint16_t)read_count);
                         } else {
                             /* read from endpoint BUF1Addr buffer */
                             read_count = (uint16_t)PCD_GET_EP_DBUF1_CNT(USB, ep_idx);
                             fsdev_read_pma(USB, g_fsdev_udc.out_ep[ep_idx].xfer_buf, g_fsdev_udc.out_ep[ep_idx].ep_pma_addr + g_fsdev_udc.out_ep[ep_idx].ep_mps, (uint16_t)read_count);
                         }
+
+                        /* free EP OUT Buffer */
+                        PCD_FREE_USER_BUFFER(USB, ep_idx, 0U);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/* manage double buffer bulk out */
/* free EP OUT Buffer */
PCD_FREE_USER_BUFFER(USB, ep_idx, 0U);
if ((PCD_GET_ENDPOINT(USB, ep_idx) & USB_EP_DTOG_RX) != 0U) {
/* read from endpoint BUF0Addr buffer */
read_count = (uint16_t)PCD_GET_EP_DBUF0_CNT(USB, ep_idx);
fsdev_read_pma(USB, g_fsdev_udc.out_ep[ep_idx].xfer_buf, g_fsdev_udc.out_ep[ep_idx].ep_pma_addr, (uint16_t)read_count);
} else {
/* read from endpoint BUF1Addr buffer */
read_count = (uint16_t)PCD_GET_EP_DBUF1_CNT(USB, ep_idx);
fsdev_read_pma(USB, g_fsdev_udc.out_ep[ep_idx].xfer_buf, g_fsdev_udc.out_ep[ep_idx].ep_pma_addr + g_fsdev_udc.out_ep[ep_idx].ep_mps, (uint16_t)read_count);
}
} else {
read_count = PCD_GET_EP_RX_CNT(USB, ep_idx);
fsdev_read_pma(USB, g_fsdev_udc.out_ep[ep_idx].xfer_buf, g_fsdev_udc.out_ep[ep_idx].ep_pma_addr, (uint16_t)read_count);
}
g_fsdev_udc.out_ep[ep_idx].xfer_buf += read_count;
/* manage double buffer bulk out */
if ((PCD_GET_ENDPOINT(USB, ep_idx) & USB_EP_DTOG_RX) != 0U) {
/* read from endpoint BUF0Addr buffer */
read_count = (uint16_t)PCD_GET_EP_DBUF0_CNT(USB, ep_idx);
fsdev_read_pma(USB, g_fsdev_udc.out_ep[ep_idx].xfer_buf, g_fsdev_udc.out_ep[ep_idx].ep_pma_addr, (uint16_t)read_count);
} else {
/* read from endpoint BUF1Addr buffer */
read_count = (uint16_t)PCD_GET_EP_DBUF1_CNT(USB, ep_idx);
fsdev_read_pma(USB, g_fsdev_udc.out_ep[ep_idx].xfer_buf, g_fsdev_udc.out_ep[ep_idx].ep_pma_addr + g_fsdev_udc.out_ep[ep_idx].ep_mps, (uint16_t)read_count);
}
/* free EP OUT Buffer */
PCD_FREE_USER_BUFFER(USB, ep_idx, 0U);
} else {
read_count = PCD_GET_EP_RX_CNT(USB, ep_idx);
fsdev_read_pma(USB, g_fsdev_udc.out_ep[ep_idx].xfer_buf, g_fsdev_udc.out_ep[ep_idx].ep_pma_addr, (uint16_t)read_count);
}
g_fsdev_udc.out_ep[ep_idx].xfer_buf += read_count;
🤖 Prompt for AI Agents
In port/fsdev/usb_dc_fsdev.c around lines 488 to 505, the PCD_FREE_USER_BUFFER
call is currently executed before reading the PMA which can toggle SW_BUF and
let the controller reuse the PMA while we're still copying; move the
PCD_FREE_USER_BUFFER(USB, ep_idx, 0U) invocation to immediately after the
fsdev_read_pma(...) completes for both the double-buffer branches and the
single-buffer branch so the PMA is read first and then the buffer is freed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant