Skip to content

shared/tinyusb, extmod: Add USB Network (NCM) with runtime class selection.#31

Draft
andrewleech wants to merge 12 commits intoreview/stm32_tinyusb_ncmfrom
stm32_tinyusb_ncm
Draft

shared/tinyusb, extmod: Add USB Network (NCM) with runtime class selection.#31
andrewleech wants to merge 12 commits intoreview/stm32_tinyusb_ncmfrom
stm32_tinyusb_ncm

Conversation

@andrewleech
Copy link
Copy Markdown
Owner

@andrewleech andrewleech commented Mar 22, 2026

Summary

This adds USB networking via NCM (Network Control Model) and a runtime USB class selection API. The work is in four phases that could be split into separate PRs, though phases 1-3 are dependent on prior work so would need to stay stacked.

Background network support - a couple of small prerequisites: a weak atoi() implementation for lwIP's netif_find(), consolidating TinyUSB source includes across port Makefiles to use upstream tinyusb.mk, a send_router flag on the DHCP server so point-to-point links don't advertise a default gateway (which hijacks the host's internet route), making LWIP_IPV6 configurable on stm32, and moving mod_network_init() earlier in stm32's boot sequence so the netif is ready before USB enumeration.

NCM implementation - the actual network.USB_NET driver. It provides a USB Ethernet adapter via NCM that the host sees immediately on plug-in. The device runs a DHCP server and optionally mDNS so the host gets an IP and can reach the device without any manual config. The netif is auto-initialised at boot (before tusb_init()) because TinyUSB's NCM driver starts receiving as soon as the host sends SET_INTERFACE, which happens during enumeration before any Python code runs. NCM is auto-enabled on any port with lwIP and TinyUSB.

Runtime USB class selection - integer flag constants (BUILTIN_FLAG_CDC, BUILTIN_FLAG_MSC, BUILTIN_FLAG_NCM) that can be OR'd together and assigned to machine.USBDevice.builtin_driver in boot.py. This builds a dynamic configuration descriptor containing only the selected classes with compact interface/endpoint numbering. The PID is varied per combination so the host re-enumerates correctly. BUILTIN_DEFAULT is now CDC-only, MSC and NCM are opt-in. Without RUNTIME_DEVICE (ie. no machine.USBDevice) all compiled-in classes are exposed by default.

TinyUSB fixes - VBUS sensing fix for newer STM32F4/F7 HAL versions that have the VBDEN register bit, and a mboot build fix for TinyUSB-enabled boards.

Testing

Tested on NUCLEO_WB55 (stm32 with TinyUSB + lwIP) with both RUNTIME_DEVICE=1 and RUNTIME_DEVICE=0. NCM interface enumerates on Linux host, DHCP assigns an address, ping works at ~1.8ms RTT. Flag-mask class selection tested with CDC-only and CDC+NCM combinations.

Not tested on mimxrt, renesas-ra, samd, rp2 (build-tested only via the auto-enable logic). The descriptor patch offset tables for the dynamic builder haven't been verified against TinyUSB versions other than the current submodule.

Trade-offs and Alternatives

The descriptor builder uses hardcoded byte-offset patch tables derived from TinyUSB's TUD_*_DESCRIPTOR macro output. This is fragile across TinyUSB updates but pretty much unavoidable without TinyUSB exposing a programmatic descriptor API. The offset tables include a comment noting this dependency.

The previous usbd_net branch used an opaque C type with an internal flags byte for class selection, requiring casting in the descriptor callback. The integer flag approach here is simpler at both the Python and C level.

Generative AI

I used generative AI tools when creating this PR, but a human has checked the
code and is responsible for the code and the description above.

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Mar 22, 2026

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.46%. Comparing base (5c00edc) to head (7d1a1ed).
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@                    Coverage Diff                    @@
##           review/stm32_tinyusb_ncm      #31   +/-   ##
=========================================================
  Coverage                     98.46%   98.46%           
=========================================================
  Files                           176      176           
  Lines                         22784    22784           
=========================================================
  Hits                          22435    22435           
  Misses                          349      349           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 22, 2026

Code size report:

Reference:  qemu/boards/MPS2_AN500: Configure and enable ROMFS. [ed463ed]
Comparison: stm32/usbd_conf: Fix USB VBUS sensing for newer STM32F4/F7 HAL versions. [merge of 04ef70d]
  mpy-cross:    +0 +0.000% 
   bare-arm:    +0 +0.000% 
minimal x86:    +0 +0.000% 
   unix x64:    +0 +0.000% standard
      stm32:    +0 +0.000% PYBV10
      esp32:    +0 +0.000% ESP32_GENERIC
     mimxrt:    +0 +0.000% TEENSY40
        rp2: +13596 +1.478% RPI_PICO_W[incl +10552(bss)]
       samd:  +504 +0.183% ADAFRUIT_ITSYBITSY_M4_EXPRESS
  qemu rv32:    +0 +0.000% VIRT_RV32

@andrewleech andrewleech force-pushed the stm32_tinyusb_ncm branch 4 times, most recently from f04861e to 7d1a1ed Compare March 24, 2026 01:58
pi-anl added 6 commits April 8, 2026 16:20
Provides atoi() function implementation required by lwIP's netif_find()
function when using string-based network interface lookup.

Based on (MIT) implementation from https://github.com/littlekernel/lk

Signed-off-by: Andrew Leech <andrew.leech@planetinnovation.com.au>
When false, the DHCP server omits the Router option from DHCP
responses. This prevents point-to-point links (like USB NCM) from
advertising a default gateway that hijacks the host's internet route.

Defaults to true for backward compatibility with CYW43 AP mode.

Signed-off-by: Andrew Leech <andrew.leech@planetinnovation.com.au>
Signed-off-by: Andrew Leech <andrew.leech@planetinnovation.com.au>
Signed-off-by: Andrew Leech <andrew.leech@planetinnovation.com.au>
Consolidates TinyUSB source includes across multiple ports (alif, mimxrt,
renesas-ra, samd) to use a common approach for TinyUSB integration.

This cleanup ensures consistent TinyUSB usage patterns across ports
and simplifies maintenance.

Signed-off-by: Andrew Leech <andrew.leech@planetinnovation.com.au>
Adds TinyUSB configuration and descriptor support for USB Network
Control Model (NCM) interface. This provides the low-level USB
infrastructure needed for USB networking.

Signed-off-by: Andrew Leech <andrew.leech@planetinnovation.com.au>
pi-anl added 6 commits April 9, 2026 07:54
Implements a complete USB Network Control Model (NCM) driver that
provides ethernet-over-USB functionality integrated with lwIP stack.

Features:
- Link-local IP address generation from MAC using CRC32
- DHCP server integration with connect callbacks
- Full lwIP network interface with IPv6 support
- USB NCM protocol handling via TinyUSB
- Python network.USB_NET class interface

Signed-off-by: Andrew Leech <andrew.leech@planetinnovation.com.au>
Automatically enable MICROPY_HW_NETWORK_USBNET when MICROPY_PY_LWIP
is enabled, following the same pattern as WEBSOCKET and WEBREPL.

This makes USB networking available by default on boards that support
both USB device mode and LWIP networking, while still allowing boards
to explicitly disable it by setting MICROPY_HW_NETWORK_USBNET=0.

Also fixes USB NCM build issues when LWIP is enabled for ethernet by
making MICROPY_HW_NETWORK_USBNET conditional on both LWIP and TinyUSB.

Signed-off-by: Andrew Leech <andrew@alelec.net>
Add builtin_driver flag constants (BUILTIN_FLAG_CDC, BUILTIN_FLAG_MSC,
BUILTIN_FLAG_NCM) that can be OR'd together to select USB classes at
runtime from boot.py. A dynamic configuration descriptor is built
containing only the selected classes with compact interface/endpoint
numbering.

Signed-off-by: Andrew Leech <andrew.leech@planetinnovation.com.au>
Signed-off-by: Andrew Leech <andrew.leech@planetinnovation.com.au>
usbd_conf.c unconditionally includes shared/tinyusb/mp_usbd.h which
pulls in tusb.h when MICROPY_HW_ENABLE_USBDEV is set. The
BUILDING_MBOOT guard that disables TinyUSB comes after the include,
so mboot builds fail with missing tusb.h. Guard the include with
!BUILDING_MBOOT.

Also add forward declaration of mp_usbd_ll_init() next to the
MICROPY_HW_TINYUSB_LL_INIT macro in mpconfigboard_common.h, since
the function is used in an inline function in mp_usbd.h but only
declared in the port-specific usbd_conf.h.

Signed-off-by: Andrew Leech <andrew.leech@planetinnovation.com.au>
Signed-off-by: Andrew Leech <andrew.leech@planetinnovation.com.au>
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.

3 participants