Skip to content

Commit 20c9df0

Browse files
pi-anlAnon Kode
andcommitted
extmod/usbip: Add initial USBIP server implementation.
This commit adds the initial implementation for a USBIP server. It includes: - A new `usbip` module with `start()`/`stop()` functions. - Integration with TinyUSB host using an application driver hook. - LWIP TCP server listening on port 3240. - Handling for core USBIP commands (DEVLIST, IMPORT, SUBMIT, UNLINK). - State management for devices, clients, and transfers. - Build system integration for the rp2 port. 🤖 Generated with Anon Kode & gemini-2.5-pro-exp-03-25 Co-Authored-By: Anon Kode <noreply@AnonKode.com>
1 parent 158790a commit 20c9df0

File tree

9 files changed

+1776
-13
lines changed

9 files changed

+1776
-13
lines changed

docs/USBIP_Feature_Summary.md

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# MicroPython USBIP Server Feature Summary
2+
3+
## 1. Goal
4+
5+
To implement a USBIP (USB over IP) server within the MicroPython firmware. This allows USB devices connected to the host port of a MicroPython board to be accessed remotely over a TCP/IP network by a computer running a standard USBIP client (e.g., the `usbip` tool on Linux).
6+
7+
## 2. Requirements
8+
9+
* **Hardware:** A MicroPython board with USB Host capabilities (specifically, a port configured for Host mode, like RP2040).
10+
* **Firmware:**
11+
* MicroPython firmware built with USB Host support enabled (`MICROPY_HW_ENABLE_USB_HOST = 1`).
12+
* LWIP network stack enabled (`MICROPY_PY_LWIP = 1`).
13+
* The USBIP feature itself enabled (`MICROPY_PY_USBIP = 1`).
14+
* **Network:** TCP/IP network connectivity between the MicroPython board and the client computer.
15+
* **Client:** A computer running a USBIP client tool (e.g., `usbip` tools from `linux-tools-generic` on Debian/Ubuntu).
16+
17+
## 3. Design & Implementation Plan
18+
19+
### 3.1. Core Concept
20+
21+
The feature intercepts USB device connections at the TinyUSB host stack level *before* or *instead of* standard MicroPython class drivers (CDC, MSC, HID). It uses a custom TinyUSB application driver to manage these devices. A TCP server listens for incoming connections from USBIP clients. Once a client connects and requests to "attach" to a specific device, the server mediates the USBIP protocol, translating USBIP requests (like URB submissions) into TinyUSB host operations and forwarding the results back to the client.
22+
23+
### 3.2. Key Components (within `extmod/`)
24+
25+
* **`usbip.h`**: Header file defining USBIP protocol constants, message structures (packed, big-endian), internal state structures (`usbip_client_state_t`, `usbip_host_device_t`, `usbip_transfer_context_t`), and function prototypes.
26+
* **`modusbip.c`**: The MicroPython C module providing the Python interface (`import usbip`).
27+
* `usbip.start()`: Initializes the glue logic, starts the TCP server.
28+
* `usbip.stop()`: Stops the TCP server, cleans up client connections and resources.
29+
* **`usbip_server.c`**: Implements the TCP server logic using LWIP's raw TCP API.
30+
* Listens on TCP port 3240.
31+
* Manages client connections (`accept`, `recv`, `sent`, `err` callbacks).
32+
* Parses incoming USBIP commands from client buffers.
33+
* Handles command logic (`OP_REQ_DEVLIST`, `OP_REQ_IMPORT`, `OP_CMD_SUBMIT`, `OP_CMD_UNLINK`).
34+
* Constructs and sends USBIP responses.
35+
* **`usbip_tusb.c`**: Implements the TinyUSB host application driver interface.
36+
* Defines the `tuh_driver_t usbip_driver` structure with callbacks (`init`, `open`, `set_config`, `xfer_cb`, `close`).
37+
* Implements `usbh_app_driver_get_cb()`: Hook called by TinyUSB to claim interfaces; returns `&usbip_driver` to handle the device via USBIP.
38+
* `open`: Called when a new device/interface is detected; stores device info (VID/PID).
39+
* `close`: Called on device disconnect; triggers cleanup.
40+
* `xfer_cb`: Crucial callback triggered upon completion of a TinyUSB transfer (`tuh_control_xfer`, `tuh_bulk_xfer`, etc.). Finds the associated client request context (via `usbip_glue`) and sends the `OP_RET_SUBMIT` response over TCP.
41+
* **`usbip_glue.c`**: Manages shared state and linking between components.
42+
* Defines the global `usbip_state`.
43+
* Manages linked lists of discovered USB devices (`usbip_host_device_t`).
44+
* Manages linked lists of active client connections (`usbip_client_state_t`).
45+
* Manages mapping of pending USB transfers (`usbip_transfer_context_t`) to allow correlation in `xfer_cb`.
46+
* Handles cleanup of pending transfers when clients or devices disconnect.
47+
48+
### 3.3. Integration Points
49+
50+
* **TinyUSB:** Hooks into the host stack via `usbh_app_driver_get_cb` when `CFG_TUH_APPLICATION_DRIVER=1` is defined (controlled via `MICROPY_PY_USBIP` in `shared/tinyusb/tusb_config.h`). Disables standard TinyUSB host class drivers (`CFG_TUH_CDC=0`, etc.) when USBIP is enabled. Uses `tuh_` functions (`tuh_control_xfer`, `tuh_bulk_xfer`, descriptor getters) to interact with the USB device.
51+
* **LWIP:** Uses the raw TCP API (`tcp_new_ip_type`, `tcp_bind`, `tcp_listen`, `tcp_accept`, `tcp_recv`, `tcp_write`, `tcp_output`, `tcp_close`, etc.) for network communication.
52+
* **MicroPython Core:** Registers as a built-in C module (`MP_REGISTER_MODULE`). Uses MicroPython's memory allocation (`gc_alloc`, `gc_free`) and printf.
53+
54+
### 3.4. Protocol Handling
55+
56+
* **`OP_REQ_DEVLIST` / `OP_RET_DEVLIST`:** Server lists discovered USB devices from its internal list (`usbip_state.host_devices`), fetching descriptor details via TinyUSB.
57+
* **`OP_REQ_IMPORT` / `OP_RET_IMPORT`:** Client requests to attach to a device by bus ID. Server checks availability and attaches the client logically, updating state (`client->attached_dev_addr`, `dev->attached`).
58+
* **`OP_CMD_SUBMIT` / `OP_RET_SUBMIT`:** Client sends a USB request (URB). Server parses it, creates a transfer context, initiates the corresponding TinyUSB transfer (`tuh_control_xfer`, `tuh_bulk_xfer`). When the transfer completes (`xfer_cb`), the server finds the context and sends the result back to the client.
59+
* **`OP_CMD_UNLINK` / `OP_RET_UNLINK`:** Client requests to cancel a pending transfer. Server attempts to find the context, potentially tries to clear the endpoint state via TinyUSB, removes the context, and sends a response.
60+
61+
## 4. Build Configuration
62+
63+
1. **Enable Feature:** Define `MICROPY_PY_USBIP = 1` in the relevant `mpconfigport.h`. This requires `MICROPY_HW_ENABLE_USB_HOST = 1` and `MICROPY_PY_LWIP = 1`.
64+
2. **Modify `tusb_config.h`:** The definition in `shared/tinyusb/tusb_config.h` automatically sets `CFG_TUH_CDC/MSC/HID = 0` and `CFG_TUH_APPLICATION_DRIVER = 1` when `MICROPY_PY_USBIP` is true.
65+
3. **Add Sources to Build:** Add `extmod/modusbip.c`, `extmod/usbip_server.c`, `extmod/usbip_tusb.c`, `extmod/usbip_glue.c` to the port's build file (`CMakeLists.txt` for rp2) guarded by `if(MICROPY_PY_USBIP)`.
66+
4. **Add QSTRs:** Add `extmod/modusbip.c` to the QSTR sources list in the build file, guarded by `if(MICROPY_PY_USBIP)`.
67+
68+
## 5. Current Status & TODOs (as of conversation end)
69+
70+
* **Implemented:**
71+
* Basic file structure and build system integration (for rp2).
72+
* TinyUSB application driver hook (`usbh_app_driver_get_cb`).
73+
* LWIP TCP server listener and client connection management.
74+
* Handling for `OP_REQ_DEVLIST`, `OP_REQ_IMPORT`, `OP_CMD_SUBMIT` (Control & Bulk), `OP_CMD_UNLINK`.
75+
* State management for devices, clients, and pending transfers (using context map).
76+
* Partial TCP message handling via per-client receive buffers.
77+
* Basic error reporting via status codes in response headers.
78+
* Fetching of basic device/configuration descriptor details.
79+
* **Key TODOs / Limitations:**
80+
* **Interrupt Transfers:** Not implemented in `usbip_handle_cmd_submit`.
81+
* **Transfer Abort:** Actual hardware transfer abortion in UNLINK/cleanup is not reliably implemented due to TinyUSB API limitations; relies on context removal.
82+
* **Descriptor Details:** Fetching comprehensive descriptor info (interfaces, strings) is missing and synchronous calls used may cause issues. Async fetching preferred.
83+
* **Transfer Context Mapping:** Current array map is basic; might need improvement for efficiency or handling multiple transfers per endpoint.
84+
* **Error Handling:** Needs more comprehensive mapping of TinyUSB/LWIP errors to USBIP status codes. Handling of TCP buffer full errors needs review.
85+
* **Concurrency:** No explicit locking added; potential issues if used in an RTOS environment without GIL.
86+
* **Testing:** Feature has not been tested on hardware.
87+
88+
## 6. Testing Plan
89+
90+
1. Build firmware for a suitable board (e.g., Pico W) with USBIP enabled.
91+
2. Configure network (WiFi for Pico W).
92+
3. Connect a test USB device (e.g., USB-Serial, mouse, MSC drive) to the board's host port.
93+
4. Run `import usbip; usbip.start()` on the board.
94+
5. On a Linux client:
95+
* `sudo modprobe vhci-hcd`
96+
* `usbip list -r <board_ip>` (Verify device appears)
97+
* `sudo usbip attach -r <board_ip> -b <bus_id>` (Attach device)
98+
* Verify device appears locally (`lsusb`, `dmesg`).
99+
* Test device functionality (serial I/O, mouse movement, mount filesystem).
100+
* `sudo usbip detach -p <port>` (Detach device)
101+
6. Run `usbip.stop()` on the board.
102+
7. Repeat with various devices and test edge cases (disconnects, errors).

extmod/modusbip.c

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* This file is part of the MicroPython project, http://micropython.org/
3+
*
4+
* The MIT License (MIT)
5+
*
6+
* Copyright (c) 2025 Your Name
7+
*
8+
* Permission is hereby granted, free of charge, to any person obtaining a copy
9+
* of this software and associated documentation files (the "Software"), to deal
10+
* in the Software without restriction, including without limitation the rights
11+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
* copies of the Software, and to permit persons to whom the Software is
13+
* furnished to do so, subject to the following conditions:
14+
*
15+
* The above copyright notice and this permission notice shall be included in
16+
* all copies or substantial portions of the Software.
17+
*
18+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+
* THE SOFTWARE.
25+
*/
26+
27+
#include "py/runtime.h"
28+
#include "py/mphal.h"
29+
30+
#if MICROPY_PY_USBIP
31+
32+
#include "extmod/usbip.h"
33+
// #include "extmod/usbip_tusb.c" // Separated into build system
34+
// #include "extmod/usbip_server.c"
35+
// #include "extmod/usbip_glue.c"
36+
37+
// Ensure required components are included via build flags
38+
#if !MICROPY_PY_LWIP
39+
#error USBIP requires MICROPY_PY_LWIP
40+
#endif
41+
#if !CFG_TUH_ENABLED
42+
#error USBIP requires CFG_TUH_ENABLED
43+
#endif
44+
#if !CFG_TUH_APPLICATION_DRIVER
45+
#error USBIP requires CFG_TUH_APPLICATION_DRIVER
46+
#endif
47+
48+
49+
// --- Module Functions ---
50+
51+
STATIC mp_obj_t usbip_start(void) {
52+
#if MICROPY_PY_LWIP
53+
// Call initialization functions
54+
usbip_glue_init(); // Initialize global state
55+
usbip_server_init();
56+
// usbip_register_driver(); // Registration now happens via usbh_app_driver_get_cb hook
57+
mp_printf(MP_PYTHON_PRINTER, "USBIP Server Started\n");
58+
#else
59+
mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("USBIP requires LWIP"));
60+
#endif
61+
return mp_const_none;
62+
}
63+
STATIC MP_DEFINE_CONST_FUN_OBJ_0(usbip_start_obj, usbip_start);
64+
65+
STATIC mp_obj_t usbip_stop(void) {
66+
#if MICROPY_PY_LWIP
67+
// Call deinitialization functions
68+
usbip_server_deinit();
69+
// usbip_unregister_driver(); // Unregistration might not be needed if handled by TinyUSB
70+
mp_printf(MP_PYTHON_PRINTER, "USBIP Server Stopped\n");
71+
#else
72+
// No-op if LWIP wasn't enabled, start would have failed.
73+
#endif
74+
return mp_const_none;
75+
}
76+
STATIC MP_DEFINE_CONST_FUN_OBJ_0(usbip_stop_obj, usbip_stop);
77+
78+
STATIC const mp_rom_map_elem_t usbip_module_globals_table[] = {
79+
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_usbip) },
80+
{ MP_ROM_QSTR(MP_QSTR_start), MP_ROM_PTR(&usbip_start_obj) },
81+
{ MP_ROM_QSTR(MP_QSTR_stop), MP_ROM_PTR(&usbip_stop_obj) },
82+
// Add other functions (e.g., status) here
83+
};
84+
STATIC MP_DEFINE_CONST_DICT(usbip_module_globals, usbip_module_globals_table);
85+
86+
const mp_obj_module_t mp_module_usbip = {
87+
.base = { &mp_type_module },
88+
.globals = (mp_obj_dict_t *)&usbip_module_globals,
89+
};
90+
91+
// We need to ensure this module is only registered when the feature is enabled
92+
#if MICROPY_PY_USBIP
93+
// Register the module to make it available in Python
94+
MP_REGISTER_MODULE(MP_QSTR_usbip, mp_module_usbip);
95+
#endif
96+
97+
#endif // MICROPY_PY_USBIP

0 commit comments

Comments
 (0)