host: USB/IP-over-Ethernet bridge example, plus DWC2 / control_xfer fixes#3637
Open
andrewleech wants to merge 6 commits into
Open
host: USB/IP-over-Ethernet bridge example, plus DWC2 / control_xfer fixes#3637andrewleech wants to merge 6 commits into
andrewleech wants to merge 6 commits into
Conversation
Signed-off-by: HiFiPhile <admin@hifiphile.com>
handle_channel_in_slave saves hctsiz.pid into edpt->next_pid after XFER_COMPLETE; the DMA-mode IN handler was missing the save. On a short packet ending a multi-packet IN early, the next URB started with the toggle pre-computed in channel_xfer_start rather than the authoritative post-transfer PID, triggering DATATOGGLE_ERR. OUT direction is exempt: the host drives the PID toggle on OUT. Signed-off-by: Andrew Leech <andrew.leech@planetinnovation.com.au>
hcd_edpt_abort_xfer disabled the channel without guaranteeing an hcd_event_xfer_complete for the in-flight transfer; the channel-halted IRQ landed in handle_channel_*_dma/slave with no XFER_COMPLETE / STALL / BABBLE / XACT_ERR set, so is_done stayed false and no callback fired. Callers needing a giveback after abort had to synthesise one. Mark the channel aborted before disabling, then in handle_channel_irq force is_done with XFER_RESULT_FAILED when the abort flag is set and the channel is no longer enabled (distinguishes a true abort halt from a NAK retry that re-armed the channel). The function now also returns false when there is no in-flight transfer. Document the cross-port callback contract in src/host/hcd.h. Signed-off-by: Andrew Leech <andrew.leech@planetinnovation.com.au>
The field was a placeholder ("not supported yet"); the synchronous
tuh_control_xfer path (complete_cb == NULL) was a busy loop with no
timeout, so a device that NAK'd a control transfer indefinitely wedged
the caller. timeout_ms == 0 keeps the historical wait-forever default;
non-zero returns false with xfer->result = XFER_RESULT_FAILED on
timeout and aborts the EP0 channel via hcd_edpt_abort_xfer so a
subsequent control transfer on the same daddr can proceed.
cdc_host / hid_host switch to designated init for their fake tuh_xfer_t
so the new field is zero-initialised rather than stack garbage.
Signed-off-by: Andrew Leech <andrew.leech@planetinnovation.com.au>
Vendors github.com/STMicroelectronics/stm32-lan8742 under hw/mcu/st/stm32_lan8742 (BSD-3-Clause, see LICENSE.md). Adds family_add_eth(TARGET) to hw/bsp/stm32f4/family.cmake which sets HAL_ETH_MODULE_ENABLED, pulls in stm32f4xx_hal_eth.c plus the LAN8742 driver, and the per-board MspInit (RMII pin map). Boards that wire an Ethernet PHY supply boards/<board>/board_eth.c with HAL_ETH_MspInit; family_add_eth fails if it's missing. Provides board_eth.c for stm32f439nucleo (the only F4 board in the tree with an on-board PHY). Other F4 boards remain unchanged - the helper is opt-in. The BSP's existing stm32f4xx_hal_conf.h already has the matching ETH constants (LAN8742A_PHY_ADDRESS, MAC_ADDR0..5, ETH_RX_BUF_SIZE, PHY_BCR ...); they were only missing a way to actually compile HAL_ETH and a driver to talk to the PHY. This is the consumer for those constants. Signed-off-by: Andrew Leech <andrew.leech@planetinnovation.com.au>
A TinyUSB host that forwards URBs from a USB device on its OTG host port over TCP/IP to a Linux usbip client. Once attached, the device appears in the kernel's USB bus the same way a locally plugged device would: cdc-acm becomes /dev/ttyACM<N>, HID becomes an evdev node, and so on. Targets STM32F439ZI Nucleo-144 (only F4 board in the tree with an on-board Ethernet PHY). Uses family_add_eth() from the F4 BSP for HAL_ETH and the LAN8742 driver; the example carries only the lwIP netif/PHY-IO glue and the USB/IP server. Per-EP submit queue serialises cdc-acm's 16-deep bulk-IN read-ahead against TinyUSB's one-URB-per-EP constraint. Signed-off-by: Andrew Leech <andrew.leech@planetinnovation.com.au>
Collaborator
|
Hi, it looks very nice ! Just for the example I think it's better to include a host/device dual port approach with NCM device + USBIP host, in this way it can be tested on more MCUs. F439 is a very old MCU which I don't have, I don't know if Ha Thach has it but it's not in the HIL test farm anyway. PS: I'm thinking about adding a |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
examples/host/usbipd, a USB/IP-over-Ethernet host bridge: a TinyUSB host that forwards URBs from an attached USB device over TCP/IP to a Linuxusbipclient. Once attached, the device shows up in the kernel's USB bus the same way a locally plugged one would (cdc-acm becomes/dev/ttyACM<N>, HID becomes an evdev node, etc).The bridge core (
usbip_server.c) is portable C against the publictuh_*and lwIPtcp_*APIs, so it should work on any TinyUSB host port with a TCP-capable lwIP stack. The board-specific glue is just the lwIPnetifdriver and PHY init; the includedethernetif.cplus the LAN8742 driver target the on-board RMII PHY of the STM32F429ZI / F439ZI Nucleo-144. Other boards bring their own.Tested on STM32F439ZI Nucleo-144 with the on-board LAN8742A: full mpremote round-trip through the bridge to a MicroPython Pico W, plus a debugprobe cdc-acm device.
The PR also carries four DWC2/host-stack fixes the example exposed end-to-end. Without them, multi-packet bulk-OUT corrupts data and cdc-acm cleanup wedges on first close. The fixes are scoped tightly and useful on their own; the example is just the regression reproducer.
The first fix is a cherry-pick of @HiFiPhile's commit
17d24b39from #3632. We landed on the exact same diff independently while bringing this up, so cherry-picking keeps attribution clean and meansgit rebasesilently skips it once #3632 merges. Including it directly keeps the PR self-contained for reviewers running the example.What's in here
6 commits, bottom-up:
hcd/dwc2: fix txfifo full check(cherry-picked from @HiFiPhile's hcd/dwc2: fix txfifo full check #3632)handle_txfifo_emptycachedfifo_available/req_queue_availableonce before the per-channel loop; after writing one packet the cached values were stale, so multi-packet bulk-OUT could be issued without enough FIFO space andXFER_COMPLETEnever fired. Re-read inside the loop. Without this,mpremote fs cpof a 3.7 KB file through the bridge fails with corrupted bulk-OUT data; with it, the samefs cpround-trips cleanly in ~3 s. Same hunk also fixes USB HOST MSC hangs on write10 #3623.hcd/dwc2: save post-transfer PID in DMA-mode IN handlerhandle_channel_in_slavesaveshctsiz.pidintoedpt->next_pidafterXFER_COMPLETE; the DMA-mode IN handler did not. On a short packet ending a multi-packet IN early, the next URB started with the toggle pre-computed inchannel_xfer_startfrom the requested packet count rather than the actual post-transfer PID, triggeringDATATOGGLE_ERRon the next IN. Hardware either retried (dropping the device's first packet) or coalesced the duplicate (delivering corrupt bytes). OUT is exempt because the host drives the toggle on OUT.hcd/dwc2: fire xfer_complete callback after hcd_edpt_abort_xferThe function disabled the active channel without guaranteeing anhcd_event_xfer_completefor the in-flight transfer. The channel-halted IRQ landed inhandle_channel_*_dma/slavewith noXFER_COMPLETE / STALL / BABBLE / XACT_ERRset, sois_donestayed false and the natural callback never fired. Callers needing a giveback after abort had to synthesise one externally.Mark the channel as aborted before disabling, then in
handle_channel_irqforceis_donewithXFER_RESULT_FAILEDwhen the abort flag is set and the channel is no longer enabled (distinguishes a real abort halt from a NAK retry that re-armed the channel). Also tightens the contract: returnsfalsewhen no in-flight transfer was found, was previously unconditionaltrue. The new cross-port contract is documented insrc/host/hcd.h. Other ports (max3421e, rp2040, ehci, ohci, wch, musb, rusb2) haven't been audited against it yet, flagged for follow-up; existing implementations are unchanged here.host: honour tuh_xfer_t.timeout_ms in tuh_control_xferThetimeout_msfield was a placeholder ("not supported yet"). The synchronoustuh_control_xferpath (complete_cb == NULL) was a busy loop with no timeout, so any device that NAK'd the control transfer indefinitely (e.g. cdc-acm onCLEAR_FEATURE(ENDPOINT_HALT)) wedged the caller.timeout_ms == 0keeps the historical wait-forever default for callers that don't initialise the field, so this is backwards-compatible. Non-zero returnsfalsewithxfer->result = XFER_RESULT_FAILEDand aborts the EP0 channel viahcd_edpt_abort_xferso the next control transfer on the same daddr can proceed. Relies on commit (3) for clean abort.cdc_hostandhid_hostswitch to designated init for their faketuh_xfer_tso the new field zero-initialises rather than holding stack garbage.hw/bsp/stm32f4: add optional Ethernet support and LAN8742 PHY driverOpt-infamily_add_eth(TARGET)helper for the F4 BSP, mirroring the existingfamily_add_tinyusb/family_add_rtos/family_add_uf2. When called from an example's CMakeLists it pulls instm32f4xx_hal_eth.c, the LAN8742 driver, and the per-boardboard_eth.c(RMII pin map forHAL_ETH_MspInit); without the call, F4 boards build identically to before. The LAN8742 driver lives athw/mcu/st/stm32_lan8742/(vendored fromSTMicroelectronics/stm32-lan8742, matches the existingcmsis_device_f4/stm32f4xx_hal_driverlayout).boards/stm32f439nucleo/board_eth.ccarries the F429ZI / F439ZI pin map. Verifiedexamples/host/cdc_msc_hidbuilds clean at this commit, no ETH symbols pulled in.examples/host/usbipd: add USB/IP-over-Ethernet host bridgeThe bridge itself. Single-client lwIP raw-API server on TCP/3240 implementing the kernel-compatible USB/IP wire format:OP_REQ_DEVLIST,OP_REQ_IMPORT,CMD_SUBMIT (control + bulk + interrupt, IN/OUT),CMD_UNLINK. Endpoints are opened lazily viatuh_edpt_openwith the descriptor sniffed from the kernel'sGET_DESCRIPTOR(CONFIG)reply that flows through us, avoiding a sync descriptor fetch from inside the lwIP recv path. Includes a per-EP submit queue: cdc-acm queues 16 read-ahead URBs on its bulk-IN but TinyUSB allows only one URB per EP in flight, so the rejected ones get queued and drained from the completion callback when the EP frees.Vendored alongside the example (BSD-3-Clause, captured in
src/LICENSE.stm32-vendored):ethernetif.{c,h}andlwipopts.hfrom STM32CubeF4LwIP_TCP_Echo_Serverwith the DP83848 to LAN8742 PHY swap (same_Object_t / _IOCtx_t / _Init / _GetLinkStateAPI);arch/cc.hreused from the existingnet_lwip_webserverexample.Hardware
hw/bsp/stm32f4/boards/).board_init_after_tusb().Build
54 KB FLASH, 93 KB RAM (the inflight buffer pool dominates: 32 slots x 1.5 KB).
Verification
Tested on STM32F439ZI Nucleo-144 (S/N 066CFF495177514867213407) with two devices on OTG_FS:
Raspberry Pi Pico Debugprobe firmware (vid 2e8a:000c):
usbip list -r <ip>returns the device,sudo usbip attach -r <ip> -b 1-1enumerates and creates/dev/ttyACM<N>, pyserial open + write + read + close round-trips clean. Detach viaecho 0 > /sys/devices/platform/vhci_hcd.0/detachis clean (threeRET_UNLINK status=0messages, no rejected cascade).Raspberry Pi Pico W with MicroPython firmware (vid 2e8a:0005): full mpremote round-trip in a single chained process:
$ mpremote connect /dev/ttyACM12 \ exec "import sys, os; print(sys.platform, sys.version); print(os.uname())" \ + fs cp /tmp/test_script.py :test_script.py \ + fs ls \ + exec "exec(open('test_script.py').read())" \ + fs rm :test_script.py rp2 3.4.0; MicroPython v1.28.0-preview.147 ... (sysname='rp2', ..., machine='Raspberry Pi Pico W with RP2040') cp /tmp/test_script.py :test_script.py ls : ... 147 test_script.py ... hello from a script over usbip rm :test_script.pyTo reproduce the failing baseline against stock upstream, revert the four fix commits and rebuild. The cdc-acm probe wedges instead of producing
/dev/ttyACM<N>:With the fixes applied, the same scenario gives one initial silent SUBMIT, drained queue entries, then a clean detach with three
RET_UNLINK status=0messages.Notes for reviewers
If the usbipd example feels too big or not a great fit for the examples tree, happy to publish it as a standalone project and drop commits 5 and 6 from this PR. The example was as much the reproducer that pinned down the host-stack issues in commits 1-4 as a contribution in its own right, so the four fixes can stand alone.
Each fix commit is self-contained and can be cherry-picked individually. The
timeout_mscommit relies on the abort_xfer commit for clean EP0 abort on timeout recovery; if the abort fix lands first the timeout fix is still a strict improvement on ports that don't yet honour the new abort contract (the timeout still fires, the recovery is just less complete).The Ethernet support is split into a reusable BSP helper (commit 5) and the example (commit 6) so other F4 examples can use the same
family_add_ethopt-in. Boards without on-board Ethernet are unaffected;family_add_ethfails loudly at CMake time if the board'sboard_eth.cis missing rather than silently producing a non-functional binary.The example is gated to
board:stm32f439nucleoviaonly.txtbecause runtime needs a board with both the LAN8742A RMII pin map and a working OTG_FS host port. Adding other F4 boards is just a newboard_eth.cplus extendingonly.txt, the bridge core is portable.The
abortedflag inhcd_xfer_tis a:1bitfield that fits in the existing flags byte, no growth in_hcd_data.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.