mirror of
https://github.com/meshtastic/firmware.git
synced 2026-09-21 13:55:24 -04:00
feat(usbnet): make USB-NCM enablement a reusable fragment for any capable board
Move the env boilerplate (HAS_USB_NET macro, include/link setup, pinned esp_tinyusb/tinyusb components, and the NCM/DHCP sdkconfig) into a shared [usbnet] section in variants/esp32/usbnet.ini, so enabling the gadget on a board is a short <board>_usbnet env that splices the fragments in. The meshnology_w12_usbnet env becomes the reference consumer, keeping only its board-specific pieces (start delay, coredump-to-flash debugging config). Eligibility is enforced at compile time in USBNetEsp32.cpp: HAS_USB_NET on a SoC without native USB-OTG (C3/C6/H2, classic ESP32) or on a board without BOARD_HAS_PSRAM fails the build with an explanation, since the phone API session's connect-time allocations need PSRAM in the heap and USB-Serial-JTAG parts cannot present a USB device at all.
This commit is contained in:
1 parent
3a7871a1b8
commit
cb6a738cc6
4 files changed
+156
-75
No files matched your search
@@ -26,6 +26,17 @@ Verified on hardware:
|
||||
| Board stays flashable | ✅ many flash cycles |
|
||||
| **Real API session completes** | ✅ `TCPInterface` + `getMyNodeInfo()`, twice in a row, node stays up |
|
||||
|
||||
## Enabling on other boards
|
||||
|
||||
The enablement is a reusable fragment: `[usbnet]` in `variants/esp32/usbnet.ini`
|
||||
carries the build flags, managed components, and sdkconfig any adopter needs,
|
||||
and documents the adoption recipe (a `<board>_usbnet` env that splices the
|
||||
fragments in). `[env:meshnology_w12_usbnet]` is the reference consumer.
|
||||
Eligibility - a native USB-OTG SoC (ESP32-S2/S3/P4) and PSRAM
|
||||
(`-D BOARD_HAS_PSRAM`) - is enforced at compile time by `#error` guards in
|
||||
`USBNetEsp32.cpp`, so an ineligible variant fails the build with an
|
||||
explanation instead of misbehaving on hardware.
|
||||
|
||||
## The crash that blocked this branch - root cause and fix
|
||||
|
||||
**Symptom:** the node rebooted within seconds whenever a client opened a real
|
||||
|
||||
@@ -2,6 +2,21 @@
|
||||
|
||||
#if HAS_USB_NET && defined(ARCH_ESP32)
|
||||
|
||||
#include <soc/soc_caps.h>
|
||||
|
||||
// Hardware eligibility, enforced here so a variant that sets HAS_USB_NET=1
|
||||
// without the prerequisites fails at compile time with an explanation instead
|
||||
// of misbehaving on hardware (see variants/esp32/usbnet.ini for the adoption
|
||||
// recipe).
|
||||
#if !SOC_USB_OTG_SUPPORTED
|
||||
#error \
|
||||
"HAS_USB_NET requires a native USB-OTG SoC (ESP32-S2/S3/P4). C3/C6/H2 only have USB-Serial-JTAG and cannot present a USB device."
|
||||
#endif
|
||||
#ifndef BOARD_HAS_PSRAM
|
||||
#error \
|
||||
"HAS_USB_NET requires PSRAM (-D BOARD_HAS_PSRAM). Without PSRAM in the heap the first phone-API session's ~15KB contiguous allocation aborts the node with std::bad_alloc."
|
||||
#endif
|
||||
|
||||
#include "USBNetPolicy.h"
|
||||
#include "configuration.h"
|
||||
#include "mesh/api/WiFiServerAPI.h"
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
; ---------------------------------------------------------------------------
|
||||
; USB-Ethernet (CDC-NCM) gadget + phone API over USB ("usbnet")
|
||||
;
|
||||
; Presents the node to a USB host as a standard USB Ethernet adaptor so the
|
||||
; Meshtastic apps can reach the existing TCP phone API (port 4403) over a
|
||||
; plain USB-C cable - including on iOS/iPadOS in airplane mode, where USB
|
||||
; serial is not an option (Apple's own drivers win IOKit matching). Apple's
|
||||
; built-in NCM driver binds it; no dext, no MFi
|
||||
; (Apple DTS: developer.apple.com/forums/thread/772812).
|
||||
;
|
||||
; This section is a reusable fragment, not an env. To enable usbnet for a
|
||||
; board, add a `<board>_usbnet` env to the board's platformio.ini that
|
||||
; splices these fragments in. Reference consumer:
|
||||
; [env:meshnology_w12_usbnet] in variants/esp32s3/meshnology-w12/platformio.ini.
|
||||
;
|
||||
; [env:myboard_usbnet]
|
||||
; extends = env:myboard
|
||||
; board_level = extra
|
||||
; extra_scripts =
|
||||
; ${esp32_common.extra_scripts}
|
||||
; ${usbnet.extra_scripts}
|
||||
; build_flags =
|
||||
; ${env:myboard.build_flags}
|
||||
; ${usbnet.build_flags}
|
||||
; custom_component_add = ${usbnet.custom_component_add}
|
||||
; custom_sdkconfig =
|
||||
; ${esp32s3_base.custom_sdkconfig}
|
||||
; ${usbnet.custom_sdkconfig}
|
||||
;
|
||||
; Hardware requirements, enforced at compile time in USBNetEsp32.cpp:
|
||||
; * a native USB-OTG SoC (ESP32-S2/S3/P4) with D+/D- wired to the USB
|
||||
; connector. C3/C6/H2 only have USB-Serial-JTAG and physically cannot do
|
||||
; this; boards whose USB-C goes through a CP2102/CH9102 UART bridge
|
||||
; (e.g. Heltec V3) are equally out. Proxy check: ARDUINO_USB_CDC_ON_BOOT=1
|
||||
; in the board's build flags.
|
||||
; * PSRAM, declared with -D BOARD_HAS_PSRAM. The gadget stack (TinyUSB task,
|
||||
; NCM transfer buffers, esp_netif, DHCP server, API server) lives in
|
||||
; internal SRAM; without PSRAM in the heap the first phone-API session's
|
||||
; ~15KB contiguous allocation aborts the node with std::bad_alloc.
|
||||
;
|
||||
; Operational notes for adopters:
|
||||
; * Once the gadget claims the USB pads the serial console and esptool's
|
||||
; auto-reset disappear until the next power-on reset. USB_NET_START_DELAY_MS
|
||||
; (default 8000 in USBNetThread.cpp) is the reflash window after reset;
|
||||
; raise it per-env while iterating (the W12 pilot uses 30000).
|
||||
; * A panic reboot re-enters the gadget without ever enumerating
|
||||
; USB-Serial-JTAG, so panics are invisible on the console. Enable
|
||||
; coredump-to-flash in the env's custom_sdkconfig if your partition table
|
||||
; carries a coredump partition (see the W12 env), and mind that
|
||||
; CONFIG_ESP_COREDUMP_ENABLE_TO_NONE=n must be set explicitly.
|
||||
; * custom_component_add rewrites the SHARED
|
||||
; framework-arduinoespressif32/idf_component.yml (backed up once as .orig).
|
||||
; The platform restores it after the build; bin/restore-idf-component-yml.sh
|
||||
; is the manual fallback before building other ESP32 envs.
|
||||
; ---------------------------------------------------------------------------
|
||||
[usbnet]
|
||||
build_flags =
|
||||
-D HAS_USB_NET=1
|
||||
; esp_tinyusb is built as an IDF component, but PlatformIO compiles src/ with
|
||||
; its own CPPPATH which does not pick up the project's managed_components.
|
||||
; Ours must come first: a stale arduino_tinyusb copy on the path would supply
|
||||
; a tusb_config.h where CFG_TUD_NCM is 0, silently disagreeing with the
|
||||
; component the app is linked against. USBNetEsp32.cpp static-asserts
|
||||
; CFG_TUD_NCM to turn that into a build failure rather than a runtime mystery.
|
||||
-I managed_components/espressif__esp_tinyusb/include
|
||||
-I managed_components/espressif__tinyusb/src
|
||||
; HybridCompile builds libespressif__{esp_,}tinyusb.a but never adds them to
|
||||
; pioarduino-build.py's LIBS, so the app links without them. Pull them in
|
||||
; explicitly, inside a group because esp_tinyusb and tinyusb reference each
|
||||
; other. The -L search path for those archives is added by
|
||||
; extra_scripts/usbnet_link.py -- a relative -L here is silently dropped by
|
||||
; PlatformIO.
|
||||
-Wl,--start-group
|
||||
-lespressif__esp_tinyusb
|
||||
-lespressif__tinyusb
|
||||
-Wl,--end-group
|
||||
extra_scripts = extra_scripts/usbnet_link.py
|
||||
custom_component_add =
|
||||
espressif/esp_tinyusb@^2.2.0
|
||||
; Pin TinyUSB >= 0.21.0 explicitly. esp_tinyusb only requires ">=0.17.0~2",
|
||||
; but PR #3630 (first released in 0.21.0) is what makes NCM work on
|
||||
; iOS/iPadOS 26 -- without it, SET_ETHERNET_PACKET_FILTER (0x43) and
|
||||
; SET/GET_NTB_INPUT_SIZE (0x85/0x86) STALL, iOS deactivates the interface,
|
||||
; and DHCP succeeds only ~30% of the time (hathach/tinyusb#3505).
|
||||
espressif/tinyusb@^0.21.0
|
||||
custom_sdkconfig =
|
||||
CONFIG_TINYUSB_NET_MODE_NCM=y
|
||||
; NOTE: do not add CONFIG_TINYUSB_TASK_STACK_SIZE / CONFIG_TINYUSB_TASK_AFFINITY_CPU0
|
||||
; here. Those Kconfig symbols do not exist in esp_tinyusb 2.x - task size,
|
||||
; priority and core affinity moved into tinyusb_config_t.task, and anything
|
||||
; set here is silently ignored. USBNetEsp32.cpp sets them via
|
||||
; TINYUSB_TASK_CUSTOM().
|
||||
CONFIG_LWIP_DHCPS=y
|
||||
; Suppress DHCP option 6 (DNS). dhcpserver.c only emits it when the DNS offer
|
||||
; flag is set -- EXCEPT that with CONFIG_LWIP_DHCPS_ADD_DNS=y (the default)
|
||||
; the else-branch vends the server's own address as the DNS server. That
|
||||
; would point the host at 192.168.7.1 for name resolution, which answers
|
||||
; nothing. Apple's guidance for an accessory is to vend neither a router nor
|
||||
; a DNS - vending either makes iOS elect the link, fail its captive-network
|
||||
; probe, and kill the interface.
|
||||
CONFIG_LWIP_DHCPS_ADD_DNS=n
|
||||
; KCONFIG NAMESPACE COLLISION -- do not set these to 'y'.
|
||||
; Two different components share the CONFIG_TINYUSB_* prefix:
|
||||
; * arduino_tinyusb -- the Arduino core's own, NOT built by Meshtastic
|
||||
; (HybridCompile omits it), owns CONFIG_TINYUSB_ENABLED
|
||||
; * espressif/esp_tinyusb -- the managed component we add above
|
||||
; The core's USBCDC.cpp gates on CONFIG_TINYUSB_CDC_ENABLED and USBMSC.cpp on
|
||||
; CONFIG_TINYUSB_MSC_ENABLED. Enabling either esp_tinyusb symbol un-gates the
|
||||
; matching Arduino file, which then fails to compile because it needs
|
||||
; arduino_tinyusb's esp32-hal-tinyusb.h helpers that are not in this build.
|
||||
; esp_tinyusb defaults MSC to ON, so it must be turned off explicitly.
|
||||
; Consequence: NCM-only for now; a composite CDC-ACM console needs a
|
||||
; different mechanism.
|
||||
CONFIG_TINYUSB_CDC_ENABLED=n
|
||||
CONFIG_TINYUSB_MSC_ENABLED=n
|
||||
@@ -34,75 +34,36 @@ build_flags =
|
||||
-I variants/esp32s3/meshnology-w12
|
||||
|
||||
; ---------------------------------------------------------------------------
|
||||
; USB-Ethernet (CDC-NCM) gadget bring-up target.
|
||||
; USB-Ethernet (CDC-NCM) gadget target - reference consumer of the shared
|
||||
; [usbnet] fragment (variants/esp32/usbnet.ini; requirements and the adoption
|
||||
; recipe are documented there).
|
||||
;
|
||||
; Presents the node to a USB-C iPad/iPhone as a standard USB Ethernet adaptor
|
||||
; so the Meshtastic iOS app can reach the existing TCP phone API (port 4403)
|
||||
; over a wire, including in airplane mode. Apple's built-in NCM driver binds
|
||||
; it; no dext, no MFi (Apple DTS: developer.apple.com/forums/thread/772812).
|
||||
;
|
||||
; Requires native USB on the connector (S3 D+/D- = GPIO19/20). The W12
|
||||
; qualifies: ARDUINO_USB_CDC_ON_BOOT=1 above, and it is already
|
||||
; requires_dfu = true, so taking over USB-OTG regresses no auto-reset circuit.
|
||||
;
|
||||
; NOTE: custom_component_add rewrites the SHARED
|
||||
; framework-arduinoespressif32/idf_component.yml (backed up once as .orig) and
|
||||
; is NOT part of the HybridCompile cache key. Run bin/restore-idf-component-yml.sh
|
||||
; after building this env before building any other ESP32 env.
|
||||
; The W12 qualifies: native S3 USB on the connector (ARDUINO_USB_CDC_ON_BOOT=1
|
||||
; above), PSRAM (BOARD_HAS_PSRAM above), and it is already requires_dfu = true,
|
||||
; so taking over USB-OTG regresses no auto-reset circuit.
|
||||
; ---------------------------------------------------------------------------
|
||||
[env:meshnology_w12_usbnet]
|
||||
extends = env:meshnology_w12
|
||||
board_level = extra
|
||||
extra_scripts =
|
||||
${esp32_common.extra_scripts}
|
||||
extra_scripts/usbnet_link.py
|
||||
${usbnet.extra_scripts}
|
||||
custom_meshtastic_hw_model_slug = MESHNOLOGY_W12_USBNET
|
||||
custom_meshtastic_display_name = Meshnology W12 (USB-NCM)
|
||||
custom_meshtastic_actively_supported = false
|
||||
build_flags =
|
||||
${env:meshnology_w12.build_flags}
|
||||
-D HAS_USB_NET=1
|
||||
; 30s, not 8s: once the gadget claims the USB pads the serial port disappears
|
||||
; and esptool has no RTS line to auto-reset with, so every reflash would need a
|
||||
; manual BOOT+RST. A 30s window is long enough to start `pio run -t upload`
|
||||
; after a reset and keep the dev loop self-service. Lower it for a real build.
|
||||
${usbnet.build_flags}
|
||||
; 30s, not the 8s code default: once the gadget claims the USB pads the serial
|
||||
; port disappears and esptool has no RTS line to auto-reset with, so every
|
||||
; reflash would need a manual BOOT+RST. A 30s window is long enough to start
|
||||
; `pio run -t upload` after a reset and keep the dev loop self-service.
|
||||
; Lower it for a real build.
|
||||
-D USB_NET_START_DELAY_MS=30000
|
||||
; esp_tinyusb is built as an IDF component, but PlatformIO compiles src/ with
|
||||
; its own CPPPATH which does not pick up the project's managed_components.
|
||||
; Ours must come first: a stale arduino_tinyusb copy on the path would supply a
|
||||
; tusb_config.h where CFG_TUD_NCM is 0, silently disagreeing with the component
|
||||
; the app is linked against. USBNetEsp32.cpp static-asserts CFG_TUD_NCM to turn
|
||||
; that into a build failure rather than a runtime mystery.
|
||||
-I managed_components/espressif__esp_tinyusb/include
|
||||
-I managed_components/espressif__tinyusb/src
|
||||
; HybridCompile builds libespressif__{esp_,}tinyusb.a but never adds them to
|
||||
; pioarduino-build.py's LIBS, so the app links without them. Pull them in
|
||||
; explicitly, inside a group because esp_tinyusb and tinyusb reference each
|
||||
; other. Two -L paths because the archives land in different places depending
|
||||
; on whether the IDF libs were rebuilt for this env (project build dir) or
|
||||
; reused from the shared package (framework-libs lib dir, already on LIBPATH).
|
||||
; The search path for those archives is added by extra_scripts/usbnet_link.py
|
||||
; below -- a relative -L here is silently dropped by PlatformIO.
|
||||
-Wl,--start-group
|
||||
-lespressif__esp_tinyusb
|
||||
-lespressif__tinyusb
|
||||
-Wl,--end-group
|
||||
custom_component_add =
|
||||
espressif/esp_tinyusb@^2.2.0
|
||||
; Pin TinyUSB >= 0.21.0 explicitly. esp_tinyusb only requires ">=0.17.0~2",
|
||||
; but PR #3630 (first released in 0.21.0) is what makes NCM work on
|
||||
; iOS/iPadOS 26 -- without it, SET_ETHERNET_PACKET_FILTER (0x43) and
|
||||
; SET/GET_NTB_INPUT_SIZE (0x85/0x86) STALL, iOS deactivates the interface,
|
||||
; and DHCP succeeds only ~30% of the time (hathach/tinyusb#3505).
|
||||
espressif/tinyusb@^0.21.0
|
||||
custom_component_add = ${usbnet.custom_component_add}
|
||||
custom_sdkconfig =
|
||||
${esp32s3_base.custom_sdkconfig}
|
||||
CONFIG_TINYUSB_NET_MODE_NCM=y
|
||||
; NOTE: do not add CONFIG_TINYUSB_TASK_STACK_SIZE / CONFIG_TINYUSB_TASK_AFFINITY_CPU0
|
||||
; here. Those Kconfig symbols do not exist in esp_tinyusb 2.x - task size,
|
||||
; priority and core affinity moved into tinyusb_config_t.task, and anything set
|
||||
; here is silently ignored. USBNetEsp32.cpp sets them via TINYUSB_TASK_CUSTOM().
|
||||
CONFIG_LWIP_DHCPS=y
|
||||
${usbnet.custom_sdkconfig}
|
||||
; Panic backtraces are otherwise unrecoverable on this build: the console is
|
||||
; HWCDC on USB-Serial-JTAG, and the gadget takes those pads, so a crash after
|
||||
; the 30s mark prints into the void. default_16MB.csv already carries a 64KB
|
||||
@@ -118,24 +79,3 @@ custom_sdkconfig =
|
||||
CONFIG_ESP_COREDUMP_DATA_FORMAT_ELF=y
|
||||
CONFIG_ESP_COREDUMP_CHECKSUM_CRC32=y
|
||||
CONFIG_ESP_COREDUMP_MAX_TASKS_NUM=64
|
||||
; Suppress DHCP option 6 (DNS). dhcpserver.c only emits it when the DNS offer
|
||||
; flag is set -- EXCEPT that with CONFIG_LWIP_DHCPS_ADD_DNS=y (the default) the
|
||||
; else-branch vends the server's own address as the DNS server. That would
|
||||
; point the iPad at 192.168.7.1 for name resolution, which answers nothing.
|
||||
; Apple's guidance for an accessory is to vend neither a router nor a DNS.
|
||||
CONFIG_LWIP_DHCPS_ADD_DNS=n
|
||||
; KCONFIG NAMESPACE COLLISION -- do not set these to 'y'.
|
||||
; Two different components share the CONFIG_TINYUSB_* prefix:
|
||||
; * arduino_tinyusb -- the Arduino core's own, NOT built by Meshtastic
|
||||
; (HybridCompile omits it), owns CONFIG_TINYUSB_ENABLED
|
||||
; * espressif/esp_tinyusb -- the managed component we add above
|
||||
; The core's USBCDC.cpp gates on CONFIG_TINYUSB_CDC_ENABLED and USBMSC.cpp on
|
||||
; CONFIG_TINYUSB_MSC_ENABLED. Enabling either esp_tinyusb symbol un-gates the
|
||||
; matching Arduino file, which then fails to compile because it needs
|
||||
; arduino_tinyusb's esp32-hal-tinyusb.h helpers (tinyusb_add_string_descriptor,
|
||||
; TUD_CDC_DESCRIPTOR, TU_VERIFY, ...) that are not in this build.
|
||||
; esp_tinyusb defaults MSC to ON, so it must be turned off explicitly.
|
||||
; Consequence: NCM-only for now; the composite CDC-ACM console needs a
|
||||
; different mechanism (see P4). Console stays on UART0 until then.
|
||||
CONFIG_TINYUSB_CDC_ENABLED=n
|
||||
CONFIG_TINYUSB_MSC_ENABLED=n
|
||||
Reference in new issue
Block a user