Skip to content

Docker USB passthrough: post-mortem on running fbuild serial tests in a container with a real ESP32-C6 (Windows host) #899

Description

@zackees

Goal

Run fbuild's new serial-detection code (#895 / #897) against a real ESP32-C6 USB device that's plugged into the Windows host but attached into a Docker container for testing. This exercises the Linux path (/sys/class/tty/ttyACM0/device/driver sysfs read) end-to-end against actual hardware, not just synthetic temp-dir fixtures.

Result

Couldn't get the end-to-end test working in this session. Filing a post-mortem so the infrastructure work to enable it is tracked. Possible follow-up direction: if a future session does succeed, wire it up as a CI script + faster Docker Linux build image + agent-docs guidance ("use this docker harness for serial integration tests on real hardware in the future").

What's plugged in (host side)

Per Windows PnP enumeration, three Espressif native-USB-CDC devices are connected to the Windows host:

COM9   USB Serial Device   Svc=usbser   USB\VID_303A&PID_1001  (REV_0102, composite, JTAG IF on MI_02)
COM22  USB Serial Device   Svc=usbser   USB\VID_303A&PID_1001  (REV_0101)
COM25  USB Serial Device   Svc=usbser   USB\VID_303A&PID_1001  (REV_0102)

303A:1001 is the shared Espressif native-USB-CDC VID/PID used by ESP32-S3/C3/C6/H2/P4. The MAC-address tail of the DeviceID would let us distinguish them, but for this test any of the three works as a "real CDC device" target.

MI_02 is the USB JTAG/serial debug unit (WinUSB driver — not a serial port). MI_00 is the CDC interface (usbser driver -> COM port). Linux's cdc_acm.ko should bind the same way and produce /dev/ttyACM0.

Blockers, in order

1. usbipd-win is not installed

usbipd-win is the canonical Windows tool for sharing a host USB device into WSL2 (and from there into Docker). Without it, USB devices on the Windows host cannot be attached into any Docker Desktop container.

PS> where usbipd
INFO: Could not find files for the given pattern(s).
PS> winget list --query usbipd
winget : The term 'winget' is not recognized

Install path:

Cannot install from this session.

2. No general-purpose WSL2 distro

wsl --list --verbose shows only docker-desktop (Docker Desktop's minimal backend distro). There's no Ubuntu / Debian / etc. where I could:

  • Apt-install usbutils to verify lsusb sees the attached device
  • Manually run ls /sys/class/tty/ttyACM0/device/driver to validate the sysfs path fbuild's Linux detector reads
  • Smoke-test fbuild's binary against the passed-through device
PS> wsl --list --verbose
NAME              STATE     VERSION
* docker-desktop   Running   2

Adding a real distro requires Microsoft Store interaction (wsl --install -d Ubuntu) which I can't drive from this CLI.

3. No fbuild Docker image

The user's running container is soldr-msvc-host-test:1.94 running cargo clippy -p soldr-cli — that's a soldr build, not fbuild. There's no fbuild image to test against:

$ docker images | grep fbuild
(no matches)
$ docker images | head
REPOSITORY                           TAG                       IMAGE ID       SIZE
soldr-msvc-host-test                 1.94                      ec7df6ed9936   2.89GB
soldr-darwin-cross                   latest                    bc9d10a75ab7   3.59GB
soldr-aarch64-windows-msvc-cross     v0.7.67-clangcl           cbd90dfe83d8   3.32GB

We'd need to either build an fbuild test image (a Dockerfile with cargo install --path crates/fbuild-cli and the serialport / udev-aware runtime libs) or use one of the soldr-cross images with fbuild built inside, mounted at runtime.

4. Running container has no USB devices mapped

$ docker inspect <running_container> --format '{{json .HostConfig.Devices}}'
[]

Even if the device were attached via usbipd, no --device flag was passed at container start, so it wouldn't be visible from inside.

What "working" would look like

# 1. (Windows host, admin) Install usbipd-win
winget install --interactive --exact dorssel.usbipd-win

# 2. (Windows host, admin) Identify the ESP32 bus ID
usbipd list
# Note BUSID for VID:PID 303A:1001 (e.g. 4-2)

# 3. (Windows host, admin) Bind + attach to WSL2
usbipd bind --busid 4-2
usbipd attach --wsl --busid 4-2

# 4. (WSL2, real Ubuntu) Verify the device shows up
ls -la /dev/ttyACM*       # expect /dev/ttyACM0
lsusb -d 303a:1001        # expect a Espressif line
readlink /sys/class/tty/ttyACM0/device/driver
# expect target basename = cdc_acm
# (this exercises the exact sysfs path fbuild's Linux detector reads)

# 5. (WSL2 -> Docker) Run a container with the device passed through
docker run --rm \
  --device=/dev/ttyACM0 \
  -v "$PWD:/work" -w /work \
  fbuild-test-image \
  bash -c '
    fbuild serial probe list | grep ttyACM0
    fbuild serial probe read /dev/ttyACM0 --seconds 5
  '

# 6. (host or container) Verify fbuild classifies the port correctly
#    Expected: family = Esp32NativeUsbCdc (VID/PID table hit; kernel-class
#    agrees -> no #897 disagreement warning).

Things this would have validated

  • The Linux sysfs read in crates/fbuild-serial/src/port_class.rs::linux::detect() works against a real cdc_acm-bound device, not just a tempfile symlink.
  • The driver-name basename (cdc_acm) matches what mainline Linux actually puts on the symlink target. (Mainline does — confirmed by reading drivers/usb/class/cdc-acm.c — but a real-device check is the belt-and-suspenders.)
  • The serial: warn when VID/PID table and OS kernel-class signal disagree #897 disagreement warning does NOT fire (both signals say "CDC" -> agree).
  • The serial open path in crates/fbuild-serial/src/manager.rs honors the (false, false) idle for the VID/PID-table-known device.

Follow-up plan

Items needed to enable this test in this repo:

  1. Build an fbuild-test Docker image (Linux-based, Debian / Ubuntu) under ci/docker-test-serial/Dockerfile. Should contain fbuild built with --features necessary for the runtime serial path, plus usbutils / udev for diagnostics. Prefer a multi-stage build with cached cargo dependencies for fast incremental rebuilds.
  2. Add a test-runner script (ci/test_serial_in_docker.sh or .py) that:
    • Verifies the host has usbipd-win installed (Windows) or that /dev/ttyACM* exists (Linux/macOS).
    • On Windows: walks the user through usbipd bind --busid X --wsl interactively (admin-required steps prompted, not auto-executed).
    • Spins up the test container with --device passed through.
    • Inside the container, runs fbuild serial probe list + verifies the new port_class::detect_port_kernel_class reports CdcAcm.
    • Outside the container, verifies the host VID/PID-table-known device produces the same family.
  3. Document the prerequisites in docs/testing-serial-in-docker.md so the install-usbipd + install-WSL-distro setup steps are recorded once, not relearned each time.
  4. Update the agent-docs (agents/docs/) under a new section like "Running serial integration tests against real hardware" so future agent sessions know to reach for this harness instead of re-investigating.

What I tried in this session

  • Inventoried host USB devices via WMI (3 ESP32 ports found, all 303A:1001)
  • Checked for usbipd-win — not installed, can't install without admin / winget
  • Listed WSL distros — only docker-desktop is registered, no general-purpose Linux
  • Inspected the running Docker container — it's a soldr-cli clippy run with no USB device mappings, not an fbuild test
  • Audited the Docker images present — all soldr-* cross-build images, no fbuild image

Acceptance for closing this issue

Either:

  • The test runs end-to-end through Docker against a real 303A:1001 device on the same Windows host that originally couldn't, with documented setup steps the next contributor can follow without re-learning the same blockers, AND wired up as a CI script with faster cached Docker Linux build, AND agent documentation updated to point at the harness for future sessions.
  • OR a documented decision is made that Docker-USB-passthrough testing isn't worth the per-contributor setup tax, and a different real-hardware test harness is chosen (e.g. native Windows binary + manual COM port; native Linux binary + host /dev/ttyACM0).

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status
    Triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions