Skip to content

mem(ncm): move USB-NCM tether RX copy to PSRAM (relieve internal SRAM) - #85

Closed
iliabaranov wants to merge 1 commit into
mainfrom
mem/ncm-rx-psram
Closed

mem(ncm): move USB-NCM tether RX copy to PSRAM (relieve internal SRAM)#85
iliabaranov wants to merge 1 commit into
mainfrom
mem/ncm-rx-psram

Conversation

@iliabaranov

Copy link
Copy Markdown
Contributor

What

on_usb_rx in components/ml_dev_tether/src/ml_dev_tether.c copies each inbound USB-NCM frame into a fresh buffer handed to lwIP. That copy was malloc'd from internal SRAM — the scarcest heap on the ESP32-S3, and the pool observed dipping to an ~8 KB low-watermark on the tethered device during the 12 h soak. This moves the copy to PSRAM via heap_caps_malloc(len, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT).

Why it's safe (flash-cache-disable hazard does NOT apply)

The buffer is filled by a plain CPU memcpy on the TinyUSB task (the code comment above the call confirms "not an ISR") and consumed on the TCPIP thread — it is never a DMA target and never touched in ISR context. So the usual "PSRAM buffer inaccessible while flash cache is disabled" hazard doesn't apply here. netif_l2_free()'s free() handles a PSRAM pointer unchanged.

Effect

Removes per-frame internal-heap alloc/free churn (fragmentation of the last few internal KB) from the device-side USB-NCM RX hot path — the exact path on the device that showed the low internal watermark.

Scope / what this does NOT do

The dominant USB-NCM internal consumer — the ~19.3 KB ncm_epbuf NTB block — is a live bus-master DMA target and the S3 USB-OTG DMA engine can only address internal SRAM, so it cannot move to PSRAM (verified in-tree: tusb_config.h DRAM_ATTR, dcd_dwc2.c internal-DMA arch gate; matches ESP-IDF external-RAM docs). Reclaiming that block by trimming NTB count/size is a separate, gated change (must re-run the USB-NCM stability regression that set IN-buffers to 4).

Verification

  • Builds clean (ESP-IDF 5.5, idf.py build exit 0); ml_dev_tether.c.obj confirmed to reference heap_caps_malloc (change compiled in, not a stale object).
  • Hardware watermark validation to follow: flash to the tethered device and confirm heap_min_int rises / pstop_sf_nomem stays 0 (deferred until the in-flight 12 h zero-disconnect soak completes so it isn't perturbed).

One-line change to a Polymath-owned file; no managed-component or safety-path edits.

🤖 Generated with Claude Code

The on_usb_rx per-frame copy handed to lwIP was malloc'd from internal
SRAM on the device-side USB-NCM path — the scarcest heap on the S3, and
the pool that dipped to an ~8 KB low-watermark on the tethered device.
The buffer is filled by a plain CPU memcpy on the TinyUSB task (never
DMA, never ISR) and consumed on the TCPIP thread, so the flash-cache-
disable hazard does not apply; move it to PSRAM via heap_caps_malloc.
This removes per-frame internal-heap alloc/free churn (fragmentation of
the last few internal KB) from the tether RX hot path. netif_l2_free()'s
free() handles a PSRAM pointer unchanged.

The dominant USB-NCM consumer (the ~19.3 KB NTB DMA block) is pinned to
internal SRAM by the S3 USB-OTG DMA engine and cannot move; reclaiming
it via NTB count/size trim is tracked separately, gated on the USB-NCM
stability regression.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UJz4ZB7WcPErDqKLWDq3ht
@iliabaranov

Copy link
Copy Markdown
Contributor Author

Superseded by #84 — the NCM RX→PSRAM commit was folded into the combined PR #84.

@iliabaranov iliabaranov closed this Aug 8, 2026
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