Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,17 @@ repos:
- id: trailing-whitespace
exclude: |
(?x)^(
hw/bsp/mcx/sdk/
hw/bsp/mcx/sdk/|
hw/mcu/st/stm32_lan8742/|
hw/bsp/stm32f4/eth_lwip/
)
- id: end-of-file-fixer
exclude: |
(?x)^(
.idea/|
hw/bsp/mcx/sdk/|
hw/mcu/st/stm32_lan8742/|
hw/bsp/stm32f4/eth_lwip/|
docs/contributing/code_of_conduct.rst|
docs/info/contributors.rst
)
Expand All @@ -30,7 +34,9 @@ repos:
exclude: |
(?x)^(
lib/|
hw/bsp/mcx/sdk/
hw/bsp/mcx/sdk/|
hw/mcu/st/stm32_lan8742/|
hw/bsp/stm32f4/eth_lwip/
)

- repo: local
Expand Down
110 changes: 110 additions & 0 deletions examples/host/usbipd/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
cmake_minimum_required(VERSION 3.20)

include(${CMAKE_CURRENT_SOURCE_DIR}/../../../hw/bsp/family_support.cmake)

# gets PROJECT name for the example (e.g. <BOARD>-<DIR_NAME>)
family_get_project_name(PROJECT ${CMAKE_CURRENT_LIST_DIR})

# lwIP comes from the bundled tinyusb submodule (matches the
# net_lwip_webserver device example pattern).
set(LWIP ${TOP}/lib/lwip)
if (NOT EXISTS ${LWIP}/src)
set(LWIP ${TINYUSB_LWIP_PATH})
endif()
if (NOT EXISTS ${LWIP}/src)
family_example_missing_dependency(${PROJECT} "lib/lwip")
return()
endif()

project(${PROJECT} C CXX ASM)

# Checks this example is valid for the family and initializes the project
family_initialize_project(${PROJECT} ${CMAKE_CURRENT_LIST_DIR})

# Espressif has its own cmake build system; not supported in this example.
if(FAMILY STREQUAL "espressif")
return()
endif()

add_executable(${PROJECT})

# Source files for the example. main.c drives tuh_task() +
# ethernetif_input() + sys_check_timeouts() in a single loop. The
# usbip_*.c files split the bridge into: server (TCP callbacks +
# framing), devices (cache + DEVLIST/IMPORT), inflight (URB pool +
# completion), submit (CMD_SUBMIT/UNLINK dispatch).
target_sources(${PROJECT} PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/src/main.c
${CMAKE_CURRENT_SOURCE_DIR}/src/usbip_server.c
${CMAKE_CURRENT_SOURCE_DIR}/src/usbip_devices.c
${CMAKE_CURRENT_SOURCE_DIR}/src/usbip_inflight.c
${CMAKE_CURRENT_SOURCE_DIR}/src/usbip_submit.c
)

target_include_directories(${PROJECT} PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/src
${LWIP}/src/include
)

# Pull in the family's Ethernet support: MAC driver, PHY driver and
# the lwIP netif glue, plus the board's PHY pin map. Defined per
# family in hw/bsp/<family>/family.cmake.
family_add_eth(${PROJECT})

# lwIP raw API + DHCP. Mirrors the net_lwip_webserver source list,
# minus the device-side networking helpers (dhserver, etc).
target_sources(${PROJECT} PUBLIC
${LWIP}/src/core/def.c
${LWIP}/src/core/inet_chksum.c
${LWIP}/src/core/init.c
${LWIP}/src/core/ip.c
${LWIP}/src/core/mem.c
${LWIP}/src/core/memp.c
${LWIP}/src/core/netif.c
${LWIP}/src/core/pbuf.c
${LWIP}/src/core/raw.c
${LWIP}/src/core/stats.c
${LWIP}/src/core/sys.c
${LWIP}/src/core/tcp.c
${LWIP}/src/core/tcp_in.c
${LWIP}/src/core/tcp_out.c
${LWIP}/src/core/timeouts.c
${LWIP}/src/core/udp.c
${LWIP}/src/core/ipv4/autoip.c
${LWIP}/src/core/ipv4/dhcp.c
${LWIP}/src/core/ipv4/etharp.c
${LWIP}/src/core/ipv4/icmp.c
${LWIP}/src/core/ipv4/igmp.c
${LWIP}/src/core/ipv4/ip4.c
${LWIP}/src/core/ipv4/ip4_addr.c
${LWIP}/src/core/ipv4/ip4_frag.c
${LWIP}/src/netif/ethernet.c
)

# All TinyUSB class drivers are disabled in tusb_config.h - this bridge
# forwards URBs at the endpoint level. BUILTIN_DRIVER_COUNT==0 trips
# upstream's -Wtype-limits in usbh.c (drv_id < 0 always-false). Silence
# locally; the example's own translation units stay strict.
target_compile_options(${PROJECT} PRIVATE -Wno-type-limits)

# lwIP carries its own warning hygiene that doesn't match TinyUSB's
# strict baseline. Scope the suppressions per source file so the
# example's own code (main.c, usbip_server.c) compiles cleanly under
# the family defaults. family_add_eth applies its own suppressions
# to the BSP-side vendored sources.
set(USBIPD_VENDOR_FILES "")
foreach(LWIPSRC IN ITEMS
core/def.c core/inet_chksum.c core/init.c core/ip.c core/mem.c
core/memp.c core/netif.c core/pbuf.c core/raw.c core/stats.c
core/sys.c core/tcp.c core/tcp_in.c core/tcp_out.c core/timeouts.c
core/udp.c core/ipv4/autoip.c core/ipv4/dhcp.c core/ipv4/etharp.c
core/ipv4/icmp.c core/ipv4/igmp.c core/ipv4/ip4.c
core/ipv4/ip4_addr.c core/ipv4/ip4_frag.c netif/ethernet.c)
list(APPEND USBIPD_VENDOR_FILES ${LWIP}/src/${LWIPSRC})
endforeach()
set_source_files_properties(${USBIPD_VENDOR_FILES} PROPERTIES
COMPILE_FLAGS
"-Wno-error=conversion -Wno-error=sign-conversion -Wno-error=sign-compare -Wno-error=unused-parameter -Wno-error=cast-align -Wno-error=redundant-decls -Wno-error=missing-prototypes")

# Configure compilation flags and libraries for the example, no-RTOS.
family_configure_host_example(${PROJECT} noos)
137 changes: 137 additions & 0 deletions examples/host/usbipd/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# USB/IP-over-Ethernet host bridge

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 Linux 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.

The example targets STM32F429ZI / F439ZI Nucleo-144 with the
on-board LAN8742A PHY. The TinyUSB host stack and the lwIP raw API
both run in the main loop (`tuh_task()` + `sys_check_timeouts()`),
no RTOS.

## Hardware

* STM32F429ZI Nucleo-144 (or F439ZI - same part bar the crypto
block, not used here).
* USB device under test plugged into the OTG_FS host port (CN13
USER USB on the Nucleo-144). VBUS enabled by the BSP via PG6 in
`board_init_after_tusb()`.
* Ethernet via the on-board LAN8742A PHY connected by RMII.
* STLink VCOM for the diagnostic UART (USART3 on PD8/PD9).

## Build

```sh
cd examples/host/usbipd
mkdir build && cd build
cmake -G Ninja -DBOARD=stm32f439nucleo ..
ninja
```

Flash with probe-rs:

```sh
probe-rs run --probe 0483:374b:<serial> --chip STM32F439ZITx \
usbipd.elf
```

On boot the UART prints the DHCP-assigned address:

```
usbipd example: host + lwip + dhcp + usbip
tusb_init ok
MOUNT: daddr=1 rhport=0 speed=FS vid=0x2e8a pid=0x000c
eth: up, starting dhcp
dhcp: got 192.168.0.182
usbip: listening on tcp/3240
```

## Attaching from a Linux host

```sh
$ usbip list -r 192.168.0.182
Exportable USB devices
======================
- 192.168.0.182
1-1: ... (2e8a:000c)

$ sudo modprobe vhci_hcd # if not already loaded
$ sudo usbip attach -r 192.168.0.182 -b 1-1
$ dmesg | tail
... usb 5-1: new full-speed USB device number 6 using vhci_hcd
... cdc_acm 5-1:1.1: ttyACM12: USB ACM device

$ sudo bash -c 'echo 0 > /sys/devices/platform/vhci_hcd.0/detach'
```

`usbip attach` exits silently on success, `dmesg` is the
authoritative confirmation. The classic `usbip detach -p 0` works
when the userspace utilities can find their state file; for
scripting the sysfs write above is robust.

To bind the listener to a specific interface instead of
`INADDR_ANY`, build with `-DUSBIPD_BIND_ADDR=...`. The macro is
passed to lwIP's `tcp_bind`, so any `ip_addr_t` initialiser works
(e.g. `IPADDR4_INIT_BYTES(192,168,0,182)`).

## How it works

The device runs three things concurrently on the main thread:
`tuh_task()` drives the TinyUSB host stack, `ethernetif_input()`
polls HAL_ETH and feeds frames to lwIP, `sys_check_timeouts()`
services lwIP's timers.

The USB/IP server in `usbip_server.c` is a single-client state
machine on TCP/3240:

1. `OP_REQ_DEVLIST` -> walk `tuh_descriptor_get_device_local`,
emit a `usbip_device_desc` per mounted device.
2. `OP_REQ_IMPORT` -> match busid, emit `OP_REP_IMPORT` + dev
desc. Kernel takes the socket from this point.
3. `CMD_SUBMIT` (control) -> `tuh_control_xfer`. Setup packet
copied into the inflight slot so its lifetime spans the async
completion.
4. `CMD_SUBMIT` (bulk/interrupt) -> `tuh_edpt_xfer`. Endpoints
open lazily via `tuh_edpt_open`, the descriptor sniffed from
the kernel's `GET_DESCRIPTOR(CONFIGURATION)` reply that flows
through us (avoids a sync descriptor fetch from inside the
lwIP recv path).
5. `CMD_UNLINK` -> `tuh_edpt_abort_xfer` fires the natural
completion callback which sends `RET_SUBMIT(FAILED)` for the
in-flight URB and frees the EP claim. `RET_UNLINK` reports
status=0 to acknowledge.

Per-EP submit queue: cdc-acm queues 16 read-ahead URBs on its
bulk-IN but TinyUSB only allows one URB per EP in flight. URBs
that come back rejected from `tuh_edpt_xfer` are kept in the
inflight pool flagged `queued` and submitted in FIFO order from
the completion callback when the EP frees.

## Notes / known limits

* lwIP's `LWIP_IPV4` only, the kernel's `usbip` client speaks
IPv4 for now too.
* Single client at a time. A second TCP connect while one is
attached gets dropped on accept.
* Endpoints are not closed on detach. Re-attaching the same
device tolerates this because `ensure_ep_open` is idempotent
against the open bit.
* This is a *host* example, the F4 itself does not present a USB
interface to the upstream port (no `usb_descriptors.c`).
* The example refuses USB `SET_ADDRESS` proxied from the wire
(would silently desync TinyUSB's view of the device).
* When `usbip attach` is interrupted before `vhci_hcd` finishes
setting up the device node, a stray write to `/dev/ttyACM<N>`
(e.g. `echo > /dev/ttyACM12`) creates a regular file at that
path. The next attach can't make the character device because
the inode is taken. Symptom: pyserial open returns
"Inappropriate ioctl for device". Cure: `sudo rm
/dev/ttyACM<N>` before re-attaching. Generic cdc_acm/devtmpfs
interaction, not specific to the bridge.

The lwIP netif driver, the LAN8742 PHY driver and the per-board
RMII pin map live in the F4 BSP under `hw/bsp/stm32f4/eth_lwip/`,
`hw/mcu/st/stm32_lan8742/` and `hw/bsp/stm32f4/boards/<board>/board_eth.c`,
pulled in by `family_add_eth()` from the example's CMakeLists.
1 change: 1 addition & 0 deletions examples/host/usbipd/only.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
board:stm32f439nucleo
75 changes: 75 additions & 0 deletions examples/host/usbipd/src/arch/cc.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Copyright (c) 2001-2003 Swedish Institute of Computer Science.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
* SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
* OF SUCH DAMAGE.
*
* This file is part of the lwIP TCP/IP stack.
*
* Author: Adam Dunkels <adam@sics.se>
*
*/
#ifndef CC_H__
#define CC_H__

//#include "cpu.h"

typedef int sys_prot_t;



/* define compiler specific symbols */
#if defined (__ICCARM__)

#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
#define PACK_STRUCT_USE_INCLUDES

#elif defined (__CC_ARM)

#define PACK_STRUCT_BEGIN __packed
#define PACK_STRUCT_STRUCT
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x

#elif defined (__GNUC__)

#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT __attribute__ ((__packed__))
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x

#elif defined (__TASKING__)

#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x

#endif

#define LWIP_PLATFORM_ASSERT(x) do { if(!(x)) while(1); } while(0)

#endif /* CC_H__ */
Loading
Loading