diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9cd4a5ed1c..481925a262 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -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 ) @@ -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 diff --git a/examples/host/usbipd/CMakeLists.txt b/examples/host/usbipd/CMakeLists.txt new file mode 100644 index 0000000000..05546fb26e --- /dev/null +++ b/examples/host/usbipd/CMakeLists.txt @@ -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. -) +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.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) diff --git a/examples/host/usbipd/README.md b/examples/host/usbipd/README.md new file mode 100644 index 0000000000..0f32f10eea --- /dev/null +++ b/examples/host/usbipd/README.md @@ -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`, 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: --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` + (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` 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_eth.c`, +pulled in by `family_add_eth()` from the example's CMakeLists. diff --git a/examples/host/usbipd/only.txt b/examples/host/usbipd/only.txt new file mode 100644 index 0000000000..b18ab1b478 --- /dev/null +++ b/examples/host/usbipd/only.txt @@ -0,0 +1 @@ +board:stm32f439nucleo diff --git a/examples/host/usbipd/src/arch/cc.h b/examples/host/usbipd/src/arch/cc.h new file mode 100644 index 0000000000..c3fc12dda0 --- /dev/null +++ b/examples/host/usbipd/src/arch/cc.h @@ -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 + * + */ +#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__ */ diff --git a/examples/host/usbipd/src/lwipopts.h b/examples/host/usbipd/src/lwipopts.h new file mode 100644 index 0000000000..3345fb4f92 --- /dev/null +++ b/examples/host/usbipd/src/lwipopts.h @@ -0,0 +1,81 @@ +/* + * 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: Simon Goldschmidt + * + */ +#ifndef LWIPOPTS_H__ +#define LWIPOPTS_H__ + +#define NO_SYS 1 +#define SYS_LIGHTWEIGHT_PROT 0 +#define MEM_ALIGNMENT 4 +#define LWIP_RAW 0 +#define LWIP_NETCONN 0 +#define LWIP_SOCKET 0 +#define LWIP_DHCP 1 +#define LWIP_ICMP 1 +#define LWIP_UDP 1 +#define LWIP_TCP 1 +#define LWIP_IPV4 1 +#define LWIP_IPV6 0 +#define ETH_PAD_SIZE 0 + +/* Sized for the cdc-acm bulk-IN read-ahead pattern of a single + * attached USB/IP client. */ +#define MEM_SIZE (10 * 1024) +#define TCP_MSS (1500 /*mtu*/ - 20 /*iphdr*/ - 20 /*tcphdr*/) +#define TCP_SND_BUF (4 * TCP_MSS) +#define TCP_SND_QUEUELEN (2 * TCP_SND_BUF / TCP_MSS) +#define TCP_WND (2 * TCP_MSS) +#define TCP_QUEUE_OOSEQ 0 + +#define MEMP_NUM_TCP_PCB 10 +#define MEMP_NUM_TCP_PCB_LISTEN 5 +#define MEMP_NUM_TCP_SEG 8 +#define MEMP_NUM_SYS_TIMEOUT 10 + +#define PBUF_POOL_SIZE 8 +#define PBUF_POOL_BUFSIZE 512 + +#define LWIP_SINGLE_NETIF 1 +#define LWIP_NETIF_LINK_CALLBACK 1 +#define LWIP_STATS 0 + +/* The MAC driver supplied by family_add_eth offloads IP/UDP/TCP/ + * ICMP checksums in hardware, disable software computation on TX + * and verification on RX. */ +#define CHECKSUM_GEN_IP 0 +#define CHECKSUM_GEN_UDP 0 +#define CHECKSUM_GEN_TCP 0 +#define CHECKSUM_GEN_ICMP 0 +#define CHECKSUM_CHECK_IP 0 +#define CHECKSUM_CHECK_UDP 0 +#define CHECKSUM_CHECK_TCP 0 + +#endif /* LWIPOPTS_H__ */ diff --git a/examples/host/usbipd/src/main.c b/examples/host/usbipd/src/main.c new file mode 100644 index 0000000000..cde2b0c385 --- /dev/null +++ b/examples/host/usbipd/src/main.c @@ -0,0 +1,165 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2026 Andrew Leech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* + * USB/IP-over-Ethernet host bridge example. + * + * TinyUSB host stack + lwIP raw API + DHCP + a USB/IP server on + * port 3240. A USB device on the OTG_FS host port can be attached + * to a Linux box over the LAN with `usbip attach`; the kernel sees + * it as a local device (cdc-acm becomes /dev/ttyACM, etc). + * + * board UART log (representative): + * usbipd example: host + lwip + dhcp + usbip + * rhport=0 speed=FS + * tusb_init ok + * dhcp: got 192.168.1.42 + * usbip: listening on tcp/3240 + * MOUNT: daddr=1 rhport=0 speed=FS vid=0x2e8a pid=0x000c + * + * From the host: + * usbip list -r 192.168.1.42 + * sudo usbip attach -r 192.168.1.42 -b 1-1 + * + * Requires a TinyUSB host port plus an lwIP-capable Ethernet + * interface and a network with a DHCP server. See README for the + * supported boards. + */ + +#include +#include + +#include "bsp/board_api.h" +#include "tusb.h" + +#include "lwip/init.h" +#include "lwip/timeouts.h" +#include "lwip/dhcp.h" +#include "lwip/netif.h" +#include "netif/ethernet.h" + +#include "ethernetif.h" +#include "usbip_server.h" + +static struct netif gnetif; +static bool dhcp_started = false; +static ip_addr_t last_logged_ip; +static bool server_started = false; + +static void mount_log(uint8_t daddr); + +int main(void) { + board_init(); + printf("\nusbipd example: host + lwip + dhcp + usbip\n"); + printf(" rhport=%u speed=%s\n", + BOARD_TUH_RHPORT, + BOARD_TUH_MAX_SPEED == OPT_MODE_HIGH_SPEED ? "HS" : + BOARD_TUH_MAX_SPEED == OPT_MODE_FULL_SPEED ? "FS" : "default"); + + tusb_rhport_init_t host_init = { + .role = TUSB_ROLE_HOST, + .speed = TUSB_SPEED_AUTO, + }; + if (!tusb_init(BOARD_TUH_RHPORT, &host_init)) { + printf("tusb_init failed\n"); + return 1; + } + board_init_after_tusb(); + printf("tusb_init ok\n"); + + // lwIP, no DHCP yet (kicked off when the link comes up). + lwip_init(); + ip_addr_t ip = {0}, nm = {0}, gw = {0}; + netif_add(&gnetif, ip_2_ip4(&ip), ip_2_ip4(&nm), ip_2_ip4(&gw), + NULL, ethernetif_init, ethernet_input); + netif_set_default(&gnetif); + netif_set_up(&gnetif); + + ip_addr_set_zero(&last_logged_ip); + + while (1) { + tuh_task(); + ethernetif_input(&gnetif); + sys_check_timeouts(); + ethernet_link_check_state(&gnetif); + + if (netif_is_link_up(&gnetif) && !dhcp_started) { + printf("eth: up, starting dhcp\n"); + dhcp_start(&gnetif); + dhcp_started = true; + } + if (!netif_is_link_up(&gnetif) && dhcp_started) { + printf("eth: down\n"); + dhcp_stop(&gnetif); + dhcp_started = false; + server_started = false; + } + + if (dhcp_started && dhcp_supplied_address(&gnetif)) { + if (!ip_addr_cmp(&last_logged_ip, &gnetif.ip_addr)) { + ip_addr_copy(last_logged_ip, gnetif.ip_addr); + printf("dhcp: got %s\n", ip4addr_ntoa(netif_ip4_addr(&gnetif))); + } + if (!server_started) { + usbip_server_init(); + printf("usbip: listening on tcp/3240\n"); + server_started = true; + } + } + } + + return 0; +} + +//--------------------------------------------------------------------+ +// TinyUSB host callbacks +//--------------------------------------------------------------------+ + +void tuh_mount_cb(uint8_t daddr) { + mount_log(daddr); + usbip_server_on_mount(daddr); +} + +void tuh_umount_cb(uint8_t daddr) { + printf("UMOUNT: daddr=%u\n", daddr); + usbip_server_on_umount(daddr); +} + +static void mount_log(uint8_t daddr) { + tuh_bus_info_t bus_info; + if (!tuh_bus_info_get(daddr, &bus_info)) { + printf("MOUNT: daddr=%u (bus_info unavailable)\n", daddr); + return; + } + + uint16_t vid = 0, pid = 0; + tuh_vid_pid_get(daddr, &vid, &pid); + + printf("MOUNT: daddr=%u rhport=%u speed=%s vid=0x%04x pid=0x%04x\n", + daddr, + bus_info.rhport, + bus_info.speed == TUSB_SPEED_HIGH ? "HS" : + bus_info.speed == TUSB_SPEED_FULL ? "FS" : "LS", + vid, pid); +} diff --git a/examples/host/usbipd/src/tusb_config.h b/examples/host/usbipd/src/tusb_config.h new file mode 100644 index 0000000000..a03675c5f0 --- /dev/null +++ b/examples/host/usbipd/src/tusb_config.h @@ -0,0 +1,122 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#ifndef TUSB_CONFIG_H_ +#define TUSB_CONFIG_H_ + +#ifdef __cplusplus + extern "C" { +#endif + +//-------------------------------------------------------------------- +// Common Configuration +//-------------------------------------------------------------------- + +// defined by compiler flags for flexibility +#ifndef CFG_TUSB_MCU +#error CFG_TUSB_MCU must be defined +#endif + +#ifndef CFG_TUSB_OS +#define CFG_TUSB_OS OPT_OS_NONE +#endif + +#ifndef CFG_TUSB_DEBUG +#define CFG_TUSB_DEBUG 0 +#endif + +/* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment. + * Tinyusb use follows macros to declare transferring memory so that they can be put + * into those specific section. + * e.g + * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") )) + * - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4))) + */ +#ifndef CFG_TUH_MEM_SECTION +#define CFG_TUH_MEM_SECTION +#endif + +#ifndef CFG_TUH_MEM_ALIGN +#define CFG_TUH_MEM_ALIGN __attribute__ ((aligned(4))) +#endif + +//-------------------------------------------------------------------- +// Host Configuration +//-------------------------------------------------------------------- + +// Enable Host stack +#define CFG_TUH_ENABLED 1 + +// #define CFG_TUH_MAX3421 1 // use max3421 as host controller + +#if CFG_TUSB_MCU == OPT_MCU_RP2040 + // #define CFG_TUH_RPI_PIO_USB 1 // use pio-usb as host controller + + // host roothub port is 1 if using either pio-usb or max3421 + #if (defined(CFG_TUH_RPI_PIO_USB) && CFG_TUH_RPI_PIO_USB) || (defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421) + #define BOARD_TUH_RHPORT 1 + #endif +#endif + +// Default is max speed that hardware controller could support with on-chip PHY +#define CFG_TUH_MAX_SPEED BOARD_TUH_MAX_SPEED + +//------------------------- Board Specific -------------------------- + +// RHPort number used for host can be defined by board.mk, default to port 0 +#ifndef BOARD_TUH_RHPORT +#define BOARD_TUH_RHPORT 0 +#endif + +// RHPort max operational speed can defined by board.mk +#ifndef BOARD_TUH_MAX_SPEED +#define BOARD_TUH_MAX_SPEED OPT_MODE_DEFAULT_SPEED +#endif + +//-------------------------------------------------------------------- +// Driver Configuration +//-------------------------------------------------------------------- + +// Size of buffer to hold descriptors and other data used for enumeration +#define CFG_TUH_ENUMERATION_BUFSIZE 256 + +// only hub class is enabled +#define CFG_TUH_HUB 1 + +// max device support (excluding hub device) +// 1 hub typically has 4 ports +#define CFG_TUH_DEVICE_MAX (3*CFG_TUH_HUB + 1) + +// Max endpoint per device +#define CFG_TUH_ENDPOINT_MAX 8 + +// Enable tuh_edpt_xfer() API +#define CFG_TUH_API_EDPT_XFER 1 + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/examples/host/usbipd/src/usbip_devices.c b/examples/host/usbipd/src/usbip_devices.c new file mode 100644 index 0000000000..b40d2f11f7 --- /dev/null +++ b/examples/host/usbipd/src/usbip_devices.c @@ -0,0 +1,197 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2026 Andrew Leech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* + * Cached device table + DEVLIST/IMPORT op handlers. + * + * The table is populated from tuh_mount_cb / tuh_umount_cb hooks + * called by main.c. We cache the device descriptor synchronously in + * the mount callback (via tuh_descriptor_get_device_local), and the + * config descriptor opportunistically in usbip_inflight.c when we + * see the kernel's GET_DESCRIPTOR(CONFIG) reply flow through the + * bridge. + */ + +#include +#include + +#include "usbip_server.h" +#include "usbip_internal.h" + +cached_dev_t s_devs[MAX_DEV]; + +void usbip_server_on_mount(uint8_t daddr) { + if (daddr == 0 || daddr >= MAX_DEV) return; + cached_dev_t *d = &s_devs[daddr]; + memset(d, 0, sizeof(*d)); + d->daddr = daddr; + if (tuh_descriptor_get_device_local(daddr, &d->dev_desc)) { + d->speed = (uint8_t)tuh_speed_get(daddr); + snprintf(d->busid, sizeof(d->busid), "1-%u", (unsigned)daddr); + d->mounted = true; + } +} + +void usbip_server_on_umount(uint8_t daddr) { + if (daddr == 0 || daddr >= MAX_DEV) return; + cached_dev_t *d = &s_devs[daddr]; + d->mounted = false; + // Drop the EP-open bitmap and config descriptor cache so a re-mount + // (same daddr, possibly different config) starts from a clean slate. + d->ep_open = 0u; + d->config_desc_len = 0u; +} + +static uint32_t usbip_speed_from_tusb(uint8_t tspeed) { + switch (tspeed) { + case TUSB_SPEED_LOW: return 1u; + case TUSB_SPEED_FULL: return 2u; + case TUSB_SPEED_HIGH: return 3u; + default: return 0u; + } +} + +// Find the endpoint descriptor in the cached config descriptor that +// matches the requested ep_addr (full bEndpointAddress, including +// direction bit). Returns NULL if not found. +const tusb_desc_endpoint_t *find_ep_desc(const cached_dev_t *d, uint8_t ep_addr) { + if (d->config_desc_len < sizeof(tusb_desc_configuration_t)) return NULL; + const uint8_t *p = d->config_desc; + const uint8_t *end = p + d->config_desc_len; + while (p + 2 <= end) { + uint8_t bLength = p[0]; + uint8_t bType = p[1]; + if (bLength < 2 || p + bLength > end) return NULL; + if (bType == TUSB_DESC_ENDPOINT) { + const tusb_desc_endpoint_t *ep = (const tusb_desc_endpoint_t *)p; + if (ep->bEndpointAddress == ep_addr) return ep; + } + p += bLength; + } + return NULL; +} + +void fill_dev_desc(usbip_device_desc_t *out, const cached_dev_t *d) { + memset(out, 0, sizeof(*out)); + snprintf(out->path, sizeof(out->path), "/tinyusb/usb1/1-%u", (unsigned)d->daddr); + strncpy(out->busid, d->busid, sizeof(out->busid) - 1); + out->busnum = be32(1u); + out->devnum = be32(d->daddr); + out->speed = be32(usbip_speed_from_tusb(d->speed)); + out->id_vendor = be16(d->dev_desc.idVendor); + out->id_product = be16(d->dev_desc.idProduct); + out->bcd_device = be16(d->dev_desc.bcdDevice); + out->device_class = d->dev_desc.bDeviceClass; + out->device_subclass = d->dev_desc.bDeviceSubClass; + out->device_protocol = d->dev_desc.bDeviceProtocol; + out->configuration_value = 1u; + out->num_configurations = d->dev_desc.bNumConfigurations; + out->num_interfaces = d->num_interfaces; +} + +// Walk the cached config descriptor and emit one usbip_interface_desc_t +// per interface descriptor encountered, capped at d->num_interfaces. If +// the cache is missing or empty, emit zero interface records (fine - +// Linux usbip-utils tolerates 0). +static err_t emit_interface_descs(conn_t *c, const cached_dev_t *d) { + if (d->num_interfaces == 0 || d->config_desc_len == 0) return ERR_OK; + uint8_t emitted = 0; + const uint8_t *p = d->config_desc; + const uint8_t *end = p + d->config_desc_len; + while (p + 2 <= end && emitted < d->num_interfaces) { + uint8_t bLength = p[0]; + uint8_t bType = p[1]; + if (bLength < 2 || p + bLength > end) break; + if (bType == TUSB_DESC_INTERFACE && bLength >= 9) { + // Per the usbip wire format, only emit interfaces with + // bAlternateSetting == 0 (one entry per interface number). + uint8_t bAltSetting = p[3]; + if (bAltSetting == 0) { + usbip_interface_desc_t ifd = { + .interface_class = p[5], + .interface_subclass = p[6], + .interface_protocol = p[7], + .padding = 0, + }; + err_t e = conn_send(c, &ifd, sizeof(ifd)); + if (e != ERR_OK) return e; + emitted++; + } + } + p += bLength; + } + return ERR_OK; +} + +err_t handle_devlist(conn_t *c) { + uint32_t count = 0; + for (uint8_t i = 1; i < MAX_DEV; i++) if (s_devs[i].mounted) count++; + printf("usbip: DEVLIST -> %u device(s)\n", (unsigned)count); + + err_t e = send_op_common(c, USBIP_OP_REP_DEVLIST, 0); + if (e != ERR_OK) return e; + uint32_t count_be = be32(count); + e = conn_send(c, &count_be, sizeof(count_be)); + if (e != ERR_OK) return e; + for (uint8_t i = 1; i < MAX_DEV; i++) { + if (!s_devs[i].mounted) continue; + usbip_device_desc_t dd; + fill_dev_desc(&dd, &s_devs[i]); + e = conn_send(c, &dd, sizeof(dd)); + if (e != ERR_OK) return e; + e = emit_interface_descs(c, &s_devs[i]); + if (e != ERR_OK) return e; + } + return ERR_OK; +} + +err_t handle_import(conn_t *c, const char *busid) { + cached_dev_t *match = NULL; + for (uint8_t i = 1; i < MAX_DEV; i++) { + if (s_devs[i].mounted && + strncmp(s_devs[i].busid, busid, USBIP_BUSID_SIZE) == 0) { + match = &s_devs[i]; + break; + } + } + if (match == NULL) { + printf("usbip: IMPORT busid='%.*s' -> not found\n", + (int)USBIP_BUSID_SIZE, busid); + return send_op_common(c, USBIP_OP_REP_IMPORT, 1); + } + printf("usbip: IMPORT busid='%s' daddr=%u\n", match->busid, match->daddr); + err_t e = send_op_common(c, USBIP_OP_REP_IMPORT, 0); + if (e != ERR_OK) return e; + usbip_device_desc_t dd; + fill_dev_desc(&dd, match); + e = conn_send(c, &dd, sizeof(dd)); + if (e != ERR_OK) return e; + c->imported_daddr = match->daddr; + c->state = ST_IMPORTED; + c->urb_state = URB_RX_HEADER; + c->urb_hdr_have = 0; + // Reset the pre-import rx scratch; URB-phase reassembly uses urb_* + c->rxlen = 0; + return ERR_OK; +} diff --git a/examples/host/usbipd/src/usbip_inflight.c b/examples/host/usbipd/src/usbip_inflight.c new file mode 100644 index 0000000000..52554feb4c --- /dev/null +++ b/examples/host/usbipd/src/usbip_inflight.c @@ -0,0 +1,139 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2026 Andrew Leech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* + * Inflight URB pool and the completion handler that turns a TinyUSB + * giveback into a RET_SUBMIT on the wire. The completion path also + * sniffs GET_DESCRIPTOR(CONFIG) replies into the cached_dev_t so the + * submit path can open bulk/interrupt endpoints lazily. + */ + +#include +#include + +#include "usbip_internal.h" + +inflight_t s_inflight[MAX_INFLIGHT]; + +inflight_t *inflight_alloc(void) { + for (int i = 0; i < MAX_INFLIGHT; i++) { + if (!s_inflight[i].in_use) { + memset(&s_inflight[i], 0, sizeof(inflight_t)); + s_inflight[i].in_use = true; + return &s_inflight[i]; + } + } + return NULL; +} + +inflight_t *inflight_find_seqnum(uint32_t seqnum) { + for (int i = 0; i < MAX_INFLIGHT; i++) { + if (s_inflight[i].in_use && s_inflight[i].seqnum == seqnum) { + return &s_inflight[i]; + } + } + return NULL; +} + +// Sniff GET_DESCRIPTOR(CONFIGURATION) replies to populate the per- +// device config_desc cache, used later for tuh_edpt_open. +static void maybe_capture_config_desc(inflight_t *u) { + if (!u->is_control || u->direction != USBIP_DIR_IN) return; + uint8_t bm = u->setup.bmRequestType; + uint8_t br = u->setup.bRequest; + uint8_t hi = (uint8_t)(u->setup.wValue >> 8); + if (bm != 0x80 || br != 6 || hi != TUSB_DESC_CONFIGURATION) return; + + cached_dev_t *d = &s_devs[u->daddr]; + uint32_t actual = u->xfer.actual_len; + if (actual > MAX_CONFIG_DESC) actual = MAX_CONFIG_DESC; + if (actual > d->config_desc_len) { + memcpy(d->config_desc, u->buf, actual); + d->config_desc_len = (uint16_t)actual; + // Pull bNumInterfaces out of the configuration descriptor header + // so DEVLIST replies can advertise the per-interface records the + // Linux usbip-utils expects. The first 9 bytes are + // tusb_desc_configuration_t; bNumInterfaces is at offset 4. + if (actual >= sizeof(tusb_desc_configuration_t)) { + const tusb_desc_configuration_t *cfg = + (const tusb_desc_configuration_t *)d->config_desc; + d->num_interfaces = cfg->bNumInterfaces; + } + } +} + +void on_xfer_complete(tuh_xfer_t *xfer) { + uint32_t idx = (uint32_t)xfer->user_data; + if (idx >= MAX_INFLIGHT) return; + inflight_t *u = &s_inflight[idx]; + if (!u->in_use) return; + + // Sync result + actual_len back into our slot. tuh_control_xfer + // copies the xfer struct internally, so the callback receives a + // pointer to the stack's own copy - reading u->xfer fields after + // submit returns the values we filled in pre-submit, not the + // post-completion values. + u->xfer.result = xfer->result; + u->xfer.actual_len = xfer->actual_len; + + maybe_capture_config_desc(u); + + // Build RET_SUBMIT. + usbip_header_t out; + memset(&out, 0, sizeof(out)); + out.base.command = be32(USBIP_RET_SUBMIT); + out.base.seqnum = be32(u->seqnum); + + int32_t status_code = 0; + switch (xfer->result) { + case XFER_RESULT_SUCCESS: status_code = 0; break; + case XFER_RESULT_STALLED: status_code = -32; break; // -EPIPE + case XFER_RESULT_FAILED: status_code = -71; break; // -EPROTO + case XFER_RESULT_TIMEOUT: status_code = -110; break; // -ETIMEDOUT + default: status_code = -71; break; + } + out.u.ret_submit.status = be32((uint32_t)status_code); + out.u.ret_submit.actual_length = be32(xfer->actual_len); + out.u.ret_submit.start_frame = 0; + out.u.ret_submit.number_of_packets = be32(USBIP_NON_ISO_PACKETS); + + err_t e = conn_send(&s_conn, &out, sizeof(out)); + if (e == ERR_OK && u->direction == USBIP_DIR_IN && xfer->actual_len > 0) { + e = conn_send(&s_conn, u->buf, xfer->actual_len); + } + if (e != ERR_OK) { + printf("usbip: RET_SUBMIT seq=%u: tcp_write %d\n", + (unsigned)u->seqnum, (int)e); + } + uint8_t freed_daddr = u->daddr; + uint8_t freed_ep_addr = u->ep_addr; + bool freed_control = u->is_control != 0; + u->in_use = false; + + // Drain any queued URBs targeting the same EP. Control transfers + // never queue (we serialise on tuh_control_xfer's failure), so skip. + if (!freed_control) { + drain_queue_for_ep(freed_daddr, freed_ep_addr); + } +} diff --git a/examples/host/usbipd/src/usbip_internal.h b/examples/host/usbipd/src/usbip_internal.h new file mode 100644 index 0000000000..0c1e21e4f1 --- /dev/null +++ b/examples/host/usbipd/src/usbip_internal.h @@ -0,0 +1,195 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2026 Andrew Leech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* + * Shared types, state and helpers for the usbipd implementation. + * Private to the example - not part of the public usbip_server.h API. + * + * The implementation is split across: + * usbip_server.c connection state, TCP callbacks, pre-import + * framing, listener init. + * usbip_devices.c cached device table, mount/umount hooks, + * DEVLIST and IMPORT op handlers. + * usbip_inflight.c inflight URB pool, completion -> RET_SUBMIT. + * usbip_submit.c CMD_SUBMIT dispatch, per-EP queue, CMD_UNLINK. + */ + +#ifndef USBIP_INTERNAL_H_ +#define USBIP_INTERNAL_H_ + +#include +#include +#include + +#include "tusb.h" +#include "lwip/tcp.h" + +#include "usbip_protocol.h" + +//--------------------------------------------------------------------+ +// Constants +//--------------------------------------------------------------------+ + +#define MAX_DEV (CFG_TUH_DEVICE_MAX + 1) +#define MAX_CONFIG_DESC 512 +// Inflight slot pool. cdc-acm queues 16 read-ahead URBs on its bulk-IN; +// 32 slots covers the read queue plus a handful of outstanding controls +// and the IF1 interrupt notification URB. The pool is GLOBAL across all +// devices on the bridge; under hub topology with multiple cdc-acm +// devices, the pool can be exhausted - intended for single-device use. +// Buffer: 1536 bytes per slot covers FS bulk wMaxPacketSize chains plus +// the largest control descriptor we forward (config + interfaces). +#define MAX_INFLIGHT 32 +#define MAX_URB_BUF 1536 + +//--------------------------------------------------------------------+ +// Endian helpers +//--------------------------------------------------------------------+ + +static inline uint16_t be16(uint16_t v) { return (uint16_t)((v >> 8) | (v << 8)); } +static inline uint32_t be32(uint32_t v) { + return ((v & 0x000000FFu) << 24) | ((v & 0x0000FF00u) << 8) | + ((v & 0x00FF0000u) >> 8) | ((v & 0xFF000000u) >> 24); +} + +//--------------------------------------------------------------------+ +// Cached device table +//--------------------------------------------------------------------+ + +typedef struct { + bool mounted; + uint8_t daddr; + tusb_desc_device_t dev_desc; + uint8_t speed; + uint8_t num_interfaces; + char busid[USBIP_BUSID_SIZE]; + // Captured config descriptor (sniffed from GET_DESCRIPTOR(CONFIG) replies). + uint8_t config_desc[MAX_CONFIG_DESC]; + uint16_t config_desc_len; + // Per-EP open state: bit (ep_addr & 0x8F) packed; 0..15 IN, 16..31 OUT + uint32_t ep_open; +} cached_dev_t; + +extern cached_dev_t s_devs[MAX_DEV]; + +static inline uint32_t ep_open_bit(uint8_t ep_addr) { + uint8_t n = ep_addr & 0x0F; + return (ep_addr & 0x80) ? (1u << n) : (1u << (n + 16)); +} + +const tusb_desc_endpoint_t *find_ep_desc(const cached_dev_t *d, uint8_t ep_addr); + +//--------------------------------------------------------------------+ +// Inflight URB pool +//--------------------------------------------------------------------+ + +typedef struct { + bool in_use; + // queued: slot is allocated and ready to submit but the target EP was + // already claimed at submit time (TinyUSB enforces 1-URB-per-EP). The + // pump in on_xfer_complete drains queued slots when the EP frees. + bool queued; + uint64_t queue_seq; // FIFO order within the queue + uint32_t seqnum; + uint32_t devid; + uint32_t direction; // USBIP_DIR_IN / OUT + uint8_t daddr; + uint8_t ep; // raw EP number (0..15) + uint8_t ep_addr; // includes direction bit + uint8_t is_control; + // Buffer used for both setup-attached IN data and bulk/int data. + uint8_t buf[MAX_URB_BUF]; + uint32_t transfer_buffer_length; + // Setup packet kept here so tuh_xfer_t.setup remains valid until + // completion (tuh_xfer holds the pointer). + tusb_control_request_t setup; + tuh_xfer_t xfer; +} inflight_t; + +extern inflight_t s_inflight[MAX_INFLIGHT]; + +inflight_t *inflight_alloc(void); +inflight_t *inflight_find_seqnum(uint32_t seqnum); +void on_xfer_complete(tuh_xfer_t *xfer); + +//--------------------------------------------------------------------+ +// Per-connection state +//--------------------------------------------------------------------+ + +typedef enum { + ST_WAIT_OP = 0, + ST_WAIT_IMPORT_BUSID, + ST_IMPORTED, + ST_CLOSING, +} conn_state_t; + +typedef enum { + URB_RX_HEADER = 0, // expecting next 48-byte usbip_header_t + URB_RX_OUT_DATA, // header parsed, expecting OUT payload bytes +} urb_rx_state_t; + +typedef struct { + struct tcp_pcb *pcb; + conn_state_t state; + + // Reassembly: pre-import op_common parsing uses rxbuf inline. Once + // imported, we use a larger reassembly arena (urb_buf) for headers + // + OUT payloads. + uint8_t rxbuf[64]; + size_t rxlen; + size_t need; + uint8_t imported_daddr; + + urb_rx_state_t urb_state; + uint8_t urb_hdr_buf[sizeof(usbip_header_t)]; + size_t urb_hdr_have; + // Pending OUT URB: header is parsed, we are reading OUT data into + // an inflight slot's buf[]. + inflight_t *urb_out_inflight; + size_t urb_out_have; + size_t urb_out_need; +} conn_t; + +extern conn_t s_conn; + +//--------------------------------------------------------------------+ +// Wire helpers (defined in usbip_server.c) +//--------------------------------------------------------------------+ + +err_t conn_send(conn_t *c, const void *buf, size_t len); +err_t send_op_common(conn_t *c, uint16_t code, uint32_t status); +void fill_dev_desc(usbip_device_desc_t *out, const cached_dev_t *d); + +//--------------------------------------------------------------------+ +// Op handlers +//--------------------------------------------------------------------+ + +err_t handle_devlist(conn_t *c); +err_t handle_import(conn_t *c, const char *busid); +err_t submit_urb(conn_t *c, const usbip_header_t *hdr_be); +err_t submit_or_queue(conn_t *c, inflight_t *u); +bool drain_queue_for_ep(uint8_t daddr, uint8_t ep_addr); +err_t handle_unlink(conn_t *c, const usbip_header_t *hdr_be); + +#endif // USBIP_INTERNAL_H_ diff --git a/examples/host/usbipd/src/usbip_protocol.h b/examples/host/usbipd/src/usbip_protocol.h new file mode 100644 index 0000000000..172e8f86b2 --- /dev/null +++ b/examples/host/usbipd/src/usbip_protocol.h @@ -0,0 +1,137 @@ +/* USB/IP wire protocol structures and constants. + * + * Layout matches the Linux kernel USB/IP wire format documented at + * https://docs.kernel.org/usb/usbip_protocol.html. + * + * Wire fields are big-endian on the wire; consumers byte-swap on read + * and write. The packed structs reflect that encoding directly so that + * a byte-buffer cast lines up. The static_asserts at the bottom of + * this file pin sizes against the kernel layout - if any of them fire + * the parser is reading the wrong bytes. + */ + +#ifndef MPY_HITL_USBIP_PROTOCOL_H +#define MPY_HITL_USBIP_PROTOCOL_H + +#include + +#define USBIP_TCP_PORT 3240 +#define USBIP_VERSION 0x0111 + +#define USBIP_OP_REQ_IMPORT 0x8003 +#define USBIP_OP_REP_IMPORT 0x0003 +#define USBIP_OP_REQ_DEVLIST 0x8005 +#define USBIP_OP_REP_DEVLIST 0x0005 + +#define USBIP_CMD_SUBMIT 0x00000001u +#define USBIP_CMD_UNLINK 0x00000002u +#define USBIP_RET_SUBMIT 0x00000003u +#define USBIP_RET_UNLINK 0x00000004u + +#define USBIP_DIR_OUT 0u +#define USBIP_DIR_IN 1u + +/* Linux usbip kernel driver tags non-isochronous URBs with + * number_of_packets = 0xFFFFFFFF. Some clients send 0 instead. + * Both must be treated as non-iso. */ +#define USBIP_NON_ISO_PACKETS 0xFFFFFFFFu + +#define USBIP_MAX_INTERFACES 8u + +#define USBIP_BUSID_SIZE 32u +#define USBIP_PATH_SIZE 256u + +typedef struct __attribute__((packed)) { + uint16_t version; + uint16_t code; + uint32_t status; +} usbip_op_common_t; + +typedef struct __attribute__((packed)) { + uint32_t command; + uint32_t seqnum; + uint32_t devid; + uint32_t direction; + uint32_t ep; +} usbip_header_basic_t; + +typedef struct __attribute__((packed)) { + uint32_t transfer_flags; + int32_t transfer_buffer_length; + int32_t start_frame; + int32_t number_of_packets; + int32_t interval; + uint8_t setup[8]; +} usbip_cmd_submit_t; + +typedef struct __attribute__((packed)) { + int32_t status; + uint32_t actual_length; + int32_t start_frame; + int32_t number_of_packets; + int32_t error_count; + uint64_t padding; +} usbip_ret_submit_t; + +typedef struct __attribute__((packed)) { + uint32_t unlink_seqnum; + uint32_t padding[6]; +} usbip_cmd_unlink_t; + +typedef struct __attribute__((packed)) { + int32_t status; + uint8_t padding[24]; +} usbip_ret_unlink_t; + +typedef struct __attribute__((packed)) { + usbip_header_basic_t base; + union { + usbip_cmd_submit_t cmd_submit; + usbip_ret_submit_t ret_submit; + usbip_cmd_unlink_t cmd_unlink; + usbip_ret_unlink_t ret_unlink; + } u; +} usbip_header_t; + +typedef struct __attribute__((packed)) { + char path[USBIP_PATH_SIZE]; + char busid[USBIP_BUSID_SIZE]; + uint32_t busnum; + uint32_t devnum; + uint32_t speed; + uint16_t id_vendor; + uint16_t id_product; + uint16_t bcd_device; + uint8_t device_class; + uint8_t device_subclass; + uint8_t device_protocol; + uint8_t configuration_value; + uint8_t num_configurations; + uint8_t num_interfaces; +} usbip_device_desc_t; + +typedef struct __attribute__((packed)) { + uint8_t interface_class; + uint8_t interface_subclass; + uint8_t interface_protocol; + uint8_t padding; +} usbip_interface_desc_t; + +#ifdef __cplusplus +extern "C" { +#endif + +/* Compile-time guards: structures must match the kernel layout. */ +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L +_Static_assert(sizeof(usbip_header_t) == 48, "USB/IP URB header must be 48 bytes"); +_Static_assert(sizeof(usbip_op_common_t) == 8, "USB/IP op_common must be 8 bytes"); +_Static_assert(sizeof(usbip_device_desc_t) == 0x138, + "USB/IP device descriptor must be 0x138 bytes"); +_Static_assert(sizeof(usbip_interface_desc_t) == 4, "interface descriptor must be 4 bytes"); +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* MPY_HITL_USBIP_PROTOCOL_H */ diff --git a/examples/host/usbipd/src/usbip_server.c b/examples/host/usbipd/src/usbip_server.c new file mode 100644 index 0000000000..dacbb6f295 --- /dev/null +++ b/examples/host/usbipd/src/usbip_server.c @@ -0,0 +1,353 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2026 Andrew Leech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* + * USB/IP server: connection orchestration. + * + * Holds the single-client conn_t state and the lwIP TCP callbacks + * (accept / recv / err). Dispatches incoming bytes through two + * framing state machines: + * + * - pre-import: 8-byte op_common header then OP_REQ_DEVLIST or + * OP_REQ_IMPORT (busid). Implementations live in usbip_devices.c. + * + * - imported: 48-byte usbip_header_t + optional OUT payload, then + * CMD_SUBMIT or CMD_UNLINK. Implementations live in + * usbip_submit.c, completion in usbip_inflight.c. + * + * Single-client. lwIP raw API, NO_SYS=1: the lwIP recv path and + * tuh_task share the main thread so no locking is needed. + */ + +#include +#include + +#include "lwip/tcp.h" +#include "lwip/pbuf.h" + +#include "usbip_server.h" +#include "usbip_internal.h" + +conn_t s_conn; +static struct tcp_pcb *s_listen_pcb; + +static void conn_close(conn_t *c); + +//--------------------------------------------------------------------+ +// Wire helpers +//--------------------------------------------------------------------+ + +err_t conn_send(conn_t *c, const void *buf, size_t len) { + if (c->pcb == NULL) return ERR_CONN; + err_t e = tcp_write(c->pcb, buf, len, TCP_WRITE_FLAG_COPY); + if (e != ERR_OK) return e; + return tcp_output(c->pcb); +} + +err_t send_op_common(conn_t *c, uint16_t code, uint32_t status) { + usbip_op_common_t op = { + .version = be16(USBIP_VERSION), + .code = be16(code), + .status = be32(status), + }; + return conn_send(c, &op, sizeof(op)); +} + +//--------------------------------------------------------------------+ +// IMPORTED-state framing: 48-byte header, optional OUT payload +//--------------------------------------------------------------------+ + +static err_t process_imported_input(conn_t *c) { + while (1) { + if (c->urb_state == URB_RX_HEADER) { + // Drain rxbuf into urb_hdr_buf + while (c->urb_hdr_have < sizeof(usbip_header_t) && c->rxlen > 0) { + size_t want = sizeof(usbip_header_t) - c->urb_hdr_have; + size_t take = c->rxlen < want ? c->rxlen : want; + memcpy(c->urb_hdr_buf + c->urb_hdr_have, c->rxbuf, take); + c->urb_hdr_have += take; + memmove(c->rxbuf, c->rxbuf + take, c->rxlen - take); + c->rxlen -= take; + } + if (c->urb_hdr_have < sizeof(usbip_header_t)) return ERR_OK; + + const usbip_header_t *hdr_be = (const usbip_header_t *)c->urb_hdr_buf; + uint32_t cmd = be32(hdr_be->base.command); + + if (cmd == USBIP_CMD_SUBMIT) { + uint32_t dir = be32(hdr_be->base.direction); + uint32_t ep = be32(hdr_be->base.ep); + int32_t blen = (int32_t)be32((uint32_t)hdr_be->u.cmd_submit.transfer_buffer_length); + // Wire-field validation: reject obviously bad headers up front + // rather than letting them cast-truncate into bus calls. The + // attacker model is "anyone on the LAN can connect". + if (dir > 1u || ep > 15u || blen < 0 || blen > (int32_t)MAX_URB_BUF) { + printf("usbip: bad SUBMIT (dir=%u ep=%u blen=%d)\n", + (unsigned)dir, (unsigned)ep, (int)blen); + return ERR_VAL; + } + if (dir == USBIP_DIR_OUT && blen > 0) { + // Need to read OUT payload before dispatching. + inflight_t *u = inflight_alloc(); + if (u == NULL) { + printf("usbip: SUBMIT: no inflight slot for OUT (blen=%d)\n", + (int)blen); + return ERR_VAL; + } + // Stash header-derived metadata; we'll fill the rest in submit_urb. + u->seqnum = be32(hdr_be->base.seqnum); + u->devid = be32(hdr_be->base.devid); + u->direction = dir; + u->ep = ep; + u->daddr = c->imported_daddr; + u->ep_addr = (uint8_t)ep | 0x00; // OUT + u->is_control = (ep == 0); + u->transfer_buffer_length = (uint32_t)blen; + c->urb_out_inflight = u; + c->urb_out_have = 0; + c->urb_out_need = (size_t)blen; + c->urb_state = URB_RX_OUT_DATA; + continue; + } + // No OUT payload: dispatch right now using hdr_be. + c->urb_hdr_have = 0; + err_t e = submit_urb(c, hdr_be); + if (e != ERR_OK) return e; + } else if (cmd == USBIP_CMD_UNLINK) { + c->urb_hdr_have = 0; + err_t e = handle_unlink(c, hdr_be); + if (e != ERR_OK) return e; + } else { + // Drop the connection on unknown command. Continuing would + // happily consume any 48-byte aligned junk indefinitely. + printf("usbip: unknown cmd 0x%08x, closing conn\n", (unsigned)cmd); + return ERR_VAL; + } + } else { // URB_RX_OUT_DATA + if (c->urb_out_inflight == NULL) return ERR_VAL; + while (c->urb_out_have < c->urb_out_need && c->rxlen > 0) { + size_t want = c->urb_out_need - c->urb_out_have; + size_t take = c->rxlen < want ? c->rxlen : want; + memcpy(c->urb_out_inflight->buf + c->urb_out_have, c->rxbuf, take); + c->urb_out_have += take; + memmove(c->rxbuf, c->rxbuf + take, c->rxlen - take); + c->rxlen -= take; + } + if (c->urb_out_have < c->urb_out_need) return ERR_OK; + // Got full OUT data; dispatch. + const usbip_header_t *hdr_be = (const usbip_header_t *)c->urb_hdr_buf; + err_t e = submit_urb(c, hdr_be); + c->urb_hdr_have = 0; + c->urb_out_inflight = NULL; + c->urb_out_have = 0; + c->urb_out_need = 0; + c->urb_state = URB_RX_HEADER; + if (e != ERR_OK) return e; + } + } +} + +//--------------------------------------------------------------------+ +// Pre-import state machine (DEVLIST / IMPORT framing) +//--------------------------------------------------------------------+ + +static err_t conn_step_preimport(conn_t *c) { + while (1) { + switch (c->state) { + case ST_WAIT_OP: { + if (c->rxlen < sizeof(usbip_op_common_t)) return ERR_OK; + usbip_op_common_t op; + memcpy(&op, c->rxbuf, sizeof(op)); + uint16_t code = be16(op.code); + memmove(c->rxbuf, c->rxbuf + sizeof(op), c->rxlen - sizeof(op)); + c->rxlen -= sizeof(op); + if (code == USBIP_OP_REQ_DEVLIST) { + err_t e = handle_devlist(c); + if (e != ERR_OK) return e; + c->state = ST_WAIT_OP; + } else if (code == USBIP_OP_REQ_IMPORT) { + c->state = ST_WAIT_IMPORT_BUSID; + c->need = USBIP_BUSID_SIZE; + } else { + printf("usbip: unknown op 0x%04x\n", code); + return ERR_VAL; + } + break; + } + case ST_WAIT_IMPORT_BUSID: { + if (c->rxlen < c->need) return ERR_OK; + char busid_z[USBIP_BUSID_SIZE + 1]; + memcpy(busid_z, c->rxbuf, USBIP_BUSID_SIZE); + busid_z[USBIP_BUSID_SIZE] = 0; + memmove(c->rxbuf, c->rxbuf + c->need, c->rxlen - c->need); + c->rxlen -= c->need; + c->need = 0; + err_t e = handle_import(c, busid_z); + if (e != ERR_OK) return e; + if (c->state == ST_IMPORTED) return ERR_OK; + c->state = ST_WAIT_OP; + break; + } + default: + return ERR_OK; + } + } +} + +//--------------------------------------------------------------------+ +// lwIP TCP callbacks +//--------------------------------------------------------------------+ + +static err_t tcp_recv_cb(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err) { + conn_t *c = (conn_t *)arg; + if (err != ERR_OK) { + if (p) pbuf_free(p); + conn_close(c); + return ERR_OK; + } + if (p == NULL) { conn_close(c); return ERR_OK; } + + // Drain pbuf into rxbuf in chunks. After a chunk, run state machine. + // The pbuf can be larger than rxbuf, so loop until consumed. + size_t consumed = 0; + while (consumed < p->tot_len) { + size_t avail = sizeof(c->rxbuf) - c->rxlen; + size_t take = (size_t)(p->tot_len - consumed); + if (take > avail) take = avail; + pbuf_copy_partial(p, c->rxbuf + c->rxlen, take, (uint16_t)consumed); + c->rxlen += take; + consumed += take; + + err_t e = (c->state == ST_IMPORTED) ? process_imported_input(c) + : conn_step_preimport(c); + if (e != ERR_OK) { + tcp_recved(pcb, p->tot_len); + pbuf_free(p); + conn_close(c); + return ERR_OK; + } + if (avail == 0 && take == 0) { + // Cannot drain - state machine refused to consume. Drop the rest. + printf("usbip: rx stuck, dropping conn\n"); + tcp_recved(pcb, p->tot_len); + pbuf_free(p); + conn_close(c); + return ERR_OK; + } + } + tcp_recved(pcb, p->tot_len); + pbuf_free(p); + return ERR_OK; +} + +// Walk the inflight pool and abort any in-flight (non-queued, non-control) +// transfers via tuh_edpt_abort_xfer. With the abort_xfer fix the natural +// completion callback fires for each abort and runs through +// on_xfer_complete which would normally send RET_SUBMIT(FAILED) - but the +// connection is gone, so conn_send would fail. We zero in_use first so +// on_xfer_complete silently drops the result and just releases the EP +// claim. Control transfers don't have a per-EP claim concept here; the +// bus task's own retry/timeout handles them. +static void abort_all_inflight_and_clear(void) { + for (int i = 0; i < MAX_INFLIGHT; i++) { + inflight_t *u = &s_inflight[i]; + if (!u->in_use) continue; + bool was_in_flight = !u->queued && !u->is_control; + u->in_use = false; + if (was_in_flight) { + tuh_edpt_abort_xfer(u->daddr, u->ep_addr); + } + } +} + +static void tcp_err_cb(void *arg, err_t err) { + (void)err; + conn_t *c = (conn_t *)arg; + c->pcb = NULL; + c->state = ST_CLOSING; + c->rxlen = 0; + // Release EP claims via the abort path. Without this, in-flight + // bulk/int URBs would stay claimed in TinyUSB until next umount, + // blocking subsequent reattaches on the same EP. + abort_all_inflight_and_clear(); +} + +static void conn_close(conn_t *c) { + if (c->pcb != NULL) { + tcp_arg(c->pcb, NULL); + tcp_recv(c->pcb, NULL); + tcp_err(c->pcb, NULL); + if (tcp_close(c->pcb) != ERR_OK) tcp_abort(c->pcb); + c->pcb = NULL; + } + c->state = ST_CLOSING; + c->rxlen = 0; + c->imported_daddr = 0; + c->urb_state = URB_RX_HEADER; + c->urb_hdr_have = 0; + c->urb_out_inflight = NULL; + abort_all_inflight_and_clear(); +} + +static err_t tcp_accept_cb(void *arg, struct tcp_pcb *newpcb, err_t err) { + (void)arg; + if (err != ERR_OK || newpcb == NULL) return ERR_VAL; + if (s_conn.pcb != NULL) { + printf("usbip: rejecting second client\n"); + tcp_abort(newpcb); + return ERR_ABRT; + } + printf("usbip: accept\n"); + memset(&s_conn, 0, sizeof(s_conn)); + s_conn.pcb = newpcb; + s_conn.state = ST_WAIT_OP; + tcp_arg(newpcb, &s_conn); + tcp_recv(newpcb, tcp_recv_cb); + tcp_err(newpcb, tcp_err_cb); + return ERR_OK; +} + +// Override at build time with -DUSBIPD_BIND_ADDR=&my_addr to bind on a +// specific interface; default IP_ADDR_ANY exposes the bridge to anyone +// reachable at L2. +#ifndef USBIPD_BIND_ADDR +#define USBIPD_BIND_ADDR IP_ADDR_ANY +#endif + +void usbip_server_init(void) { + struct tcp_pcb *pcb = tcp_new(); + if (pcb == NULL) { printf("usbip: tcp_new failed\n"); return; } + if (tcp_bind(pcb, USBIPD_BIND_ADDR, USBIP_TCP_PORT) != ERR_OK) { + printf("usbip: tcp_bind(%u) failed\n", (unsigned)USBIP_TCP_PORT); + tcp_close(pcb); + return; + } + s_listen_pcb = tcp_listen(pcb); + if (s_listen_pcb == NULL) { + printf("usbip: tcp_listen failed\n"); + tcp_close(pcb); + return; + } + tcp_accept(s_listen_pcb, tcp_accept_cb); +} diff --git a/examples/host/usbipd/src/usbip_server.h b/examples/host/usbipd/src/usbip_server.h new file mode 100644 index 0000000000..3f58555018 --- /dev/null +++ b/examples/host/usbipd/src/usbip_server.h @@ -0,0 +1,30 @@ +/* + * USB/IP-over-TCP server: OP_REQ_DEVLIST, OP_REQ_IMPORT, full URB + * streaming (CMD_SUBMIT / CMD_UNLINK / RET_SUBMIT / RET_UNLINK). + * + * Single-connection: one Linux usbip client at a time. A second + * accept while one is attached gets dropped on accept. + */ +#ifndef USBIP_SERVER_H_ +#define USBIP_SERVER_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void usbip_server_init(void); + +// Called from the TinyUSB host mount/umount callbacks to keep the +// server's view of attached devices in sync. tuh_*() is not safe to +// call from the lwIP TCP recv callback (the bus task drives it), so +// we cache the device descriptor here. +void usbip_server_on_mount(uint8_t daddr); +void usbip_server_on_umount(uint8_t daddr); + +#ifdef __cplusplus +} +#endif + +#endif /* USBIP_SERVER_H_ */ diff --git a/examples/host/usbipd/src/usbip_submit.c b/examples/host/usbipd/src/usbip_submit.c new file mode 100644 index 0000000000..1e32860125 --- /dev/null +++ b/examples/host/usbipd/src/usbip_submit.c @@ -0,0 +1,260 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2026 Andrew Leech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* + * CMD_SUBMIT dispatch and CMD_UNLINK handling. submit_urb takes a + * parsed wire header (with OUT payload already in the inflight + * slot's buf[] for OUT direction) and either hands it to TinyUSB + * via tuh_control_xfer / tuh_edpt_xfer, or queues it behind any + * URB already in flight on the same endpoint. + * + * Per-EP queue (drain_queue_for_ep): 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. + */ + +#include +#include + +#include "usbip_internal.h" + +// Attempt to open a non-control endpoint from the cached config +// descriptor. Returns true if open or already opened. +static bool ensure_ep_open(uint8_t daddr, uint8_t ep_addr) { + cached_dev_t *d = &s_devs[daddr]; + uint32_t bit = ep_open_bit(ep_addr); + if (d->ep_open & bit) return true; + const tusb_desc_endpoint_t *ep_desc = find_ep_desc(d, ep_addr); + if (ep_desc == NULL) { + printf("usbip: ep_open: no descriptor for daddr=%u ep=0x%02x\n", + daddr, ep_addr); + return false; + } + if (!tuh_edpt_open(daddr, ep_desc)) { + printf("usbip: ep_open: tuh_edpt_open failed daddr=%u ep=0x%02x\n", + daddr, ep_addr); + return false; + } + d->ep_open |= bit; + return true; +} + +err_t submit_urb(conn_t *c, const usbip_header_t *hdr_be) { + uint32_t seqnum = be32(hdr_be->base.seqnum); + uint32_t devid = be32(hdr_be->base.devid); + uint32_t dir = be32(hdr_be->base.direction); + uint32_t ep = be32(hdr_be->base.ep); + int32_t blen = (int32_t)be32((uint32_t)hdr_be->u.cmd_submit.transfer_buffer_length); + + inflight_t *u; + if (dir == USBIP_DIR_OUT && blen > 0) { + // submit_urb is called after OUT payload is already in inflight->buf + u = c->urb_out_inflight; + } else { + u = inflight_alloc(); + if (u == NULL) { + printf("usbip: SUBMIT seq=%u: no inflight slot\n", (unsigned)seqnum); + return ERR_VAL; + } + u->seqnum = seqnum; + u->devid = devid; + u->direction = dir; + u->ep = ep; + u->daddr = c->imported_daddr; + u->is_control = (ep == 0); + // For control transfers tuh_control_xfer asserts ep_addr == 0 + // (the setup packet's bmRequestType.7 picks the data direction). + // For bulk/int we OR in the IN bit per the standard ep_addr layout. + u->ep_addr = u->is_control + ? 0u + : ((uint8_t)ep | (dir == USBIP_DIR_IN ? 0x80u : 0x00u)); + u->transfer_buffer_length = (blen > 0) ? (uint32_t)blen : 0u; + } + + if ((size_t)u->transfer_buffer_length > MAX_URB_BUF) { + printf("usbip: SUBMIT seq=%u: oversize (%u)\n", + (unsigned)seqnum, (unsigned)u->transfer_buffer_length); + u->in_use = false; + return ERR_VAL; + } + + // Setup packet copied for control transfers; buffer lifetime bound to slot. + if (u->is_control) { + memcpy(&u->setup, hdr_be->u.cmd_submit.setup, 8); + // Refuse SET_ADDRESS over the bridge: the kernel can't issue this + // legitimately (vhci_hcd handles its own address assignment), and + // a remote SET_ADDRESS would silently desync TinyUSB's view of + // the device's bus address. Reply RET_SUBMIT(EPIPE) and free. + if (u->setup.bRequest == TUSB_REQ_SET_ADDRESS && + (u->setup.bmRequestType & 0x60u) == 0u) { // standard request + printf("usbip: refusing SET_ADDRESS from wire (seq=%u)\n", + (unsigned)u->seqnum); + usbip_header_t out; + memset(&out, 0, sizeof(out)); + out.base.command = be32(USBIP_RET_SUBMIT); + out.base.seqnum = be32(u->seqnum); + out.u.ret_submit.status = be32((uint32_t)-32); // -EPIPE + err_t e = conn_send(c, &out, sizeof(out)); + u->in_use = false; + return e; + } + } + + // Open the endpoint up front so a queued slot doesn't need to retry it. + if (!u->is_control && !ensure_ep_open(u->daddr, u->ep_addr)) { + u->in_use = false; + return ERR_VAL; + } + + return submit_or_queue(c, u); +} + +// Try to hand the inflight slot to TinyUSB. If the EP is busy, mark the +// slot queued and return ERR_OK; the completion callback will drain it. +err_t submit_or_queue(conn_t *c, inflight_t *u) { + static uint64_t s_queue_counter = 1; + + // Connection gone (e.g. tcp_err_cb fired between rx and drain). + // Free the slot and drop the URB - there's no client to reply to. + if (c == NULL || c->pcb == NULL) { + u->in_use = false; + u->queued = false; + return ERR_OK; + } + + // Track whether this is a re-entry from drain_queue_for_ep so we + // preserve the original queue_seq on requeue. Without this, + // bouncing between busy and free shuffles slots to the back of + // the queue, breaking FIFO ordering vs the kernel's submit order. + const bool was_queued = u->queued; + + memset(&u->xfer, 0, sizeof(u->xfer)); + u->xfer.daddr = u->daddr; + u->xfer.ep_addr = u->ep_addr; + u->xfer.buffer = u->buf; + u->xfer.complete_cb = on_xfer_complete; + u->xfer.user_data = (uintptr_t)(u - s_inflight); + + bool ok = false; + if (u->is_control) { + u->xfer.setup = &u->setup; + ok = tuh_control_xfer(&u->xfer); + } else { + u->xfer.buflen = u->transfer_buffer_length; + ok = tuh_edpt_xfer(&u->xfer); + } + + if (ok) { + u->queued = false; + return ERR_OK; + } + + // Could not submit. For control transfers there's no queue concept + // (kernel serialises ep0 itself); for bulk/int the kernel queues + // ahead and we serialise on our side. + if (u->is_control) { + printf("usbip: ctl SUBMIT seq=%u rejected\n", (unsigned)u->seqnum); + usbip_header_t out; + memset(&out, 0, sizeof(out)); + out.base.command = be32(USBIP_RET_SUBMIT); + out.base.seqnum = be32(u->seqnum); + out.u.ret_submit.status = be32((uint32_t)-32); // -EPIPE + err_t e = conn_send(c, &out, sizeof(out)); + u->in_use = false; + return e; + } + + u->queued = true; + if (!was_queued) { + u->queue_seq = s_queue_counter++; + } + return ERR_OK; +} + +// Find the oldest queued slot for a given (daddr, ep_addr) and submit +// it. Returns true if something was submitted (or attempted). +bool drain_queue_for_ep(uint8_t daddr, uint8_t ep_addr) { + inflight_t *next = NULL; + for (int i = 0; i < MAX_INFLIGHT; i++) { + inflight_t *u = &s_inflight[i]; + if (!u->in_use || !u->queued) continue; + if (u->daddr != daddr || u->ep_addr != ep_addr) continue; + if (next == NULL || u->queue_seq < next->queue_seq) next = u; + } + if (next == NULL) return false; + (void)submit_or_queue(&s_conn, next); + return true; +} + +err_t handle_unlink(conn_t *c, const usbip_header_t *hdr_be) { + uint32_t seqnum = be32(hdr_be->base.seqnum); + uint32_t unlink_seqnum = be32(hdr_be->u.cmd_unlink.unlink_seqnum); + + inflight_t *target = inflight_find_seqnum(unlink_seqnum); + int32_t status = 0; + if (target != NULL) { + if (target->queued) { + // Slot is waiting in our internal queue, never reached the bus. + // Free it immediately and drain so the next queued URB gets a + // chance. No RET_SUBMIT - we never told the kernel it started. + printf("usbip: UNLINK seq=%u (cancels queued seq=%u ep=0x%02x)\n", + (unsigned)seqnum, (unsigned)unlink_seqnum, target->ep_addr); + uint8_t daddr = target->daddr, ep_addr = target->ep_addr; + target->in_use = false; + target->queued = false; + drain_queue_for_ep(daddr, ep_addr); + status = 0; + } else if (target->is_control) { + // Upstream has no API to cancel an in-flight control transfer. + // Flag the unlink and let the kernel see status != 0 in + // RET_UNLINK; the natural callback (PR4 timeout, if hit) will + // also retire the URB. + printf("usbip: UNLINK seq=%u (cancels in-flight control seq=%u)\n", + (unsigned)seqnum, (unsigned)unlink_seqnum); + status = -104; // -ECONNRESET + } else { + printf("usbip: UNLINK seq=%u (cancels in-flight seq=%u ep=0x%02x)\n", + (unsigned)seqnum, (unsigned)unlink_seqnum, target->ep_addr); + // PR3: tuh_edpt_abort_xfer fires the natural completion callback, + // which sends RET_SUBMIT(FAILED) for the in-flight URB. The + // kernel sees that as the giveback; RET_UNLINK with status=0 + // tells it the unlink itself succeeded (URB had not yet + // completed when unlink was processed). + tuh_edpt_abort_xfer(target->daddr, target->ep_addr); + status = 0; + } + } else { + printf("usbip: UNLINK seq=%u (target seq=%u already complete)\n", + (unsigned)seqnum, (unsigned)unlink_seqnum); + status = 0; + } + + usbip_header_t out; + memset(&out, 0, sizeof(out)); + out.base.command = be32(USBIP_RET_UNLINK); + out.base.seqnum = be32(seqnum); + out.u.ret_unlink.status = be32((uint32_t)status); + return conn_send(c, &out, sizeof(out)); +} diff --git a/hw/bsp/stm32f4/boards/stm32f439nucleo/board_eth.c b/hw/bsp/stm32f4/boards/stm32f439nucleo/board_eth.c new file mode 100644 index 0000000000..219a71d32e --- /dev/null +++ b/hw/bsp/stm32f4/boards/stm32f439nucleo/board_eth.c @@ -0,0 +1,86 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2026 Andrew Leech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* + * Board-specific Ethernet MspInit for the STM32F439ZI / F429ZI Nucleo-144. + * + * Compiled into the example only when family_add_eth() is called from + * the example's CMakeLists. HAL_ETH_Init() invokes HAL_ETH_MspInit() + * (a weak symbol) automatically; the example's lwIP netif glue does not + * need to know about the pin map. + * + * RMII pin map (LAN8742A, MII/RMII select via SYSCFG_PMC): + * RMII_REF_CLK - PA1 + * RMII_MDIO - PA2 + * RMII_MDC - PC1 + * RMII_CRS_DV - PA7 + * RMII_RXD0 - PC4 + * RMII_RXD1 - PC5 + * RMII_TX_EN - PG11 + * RMII_TXD0 - PG13 + * RMII_TXD1 - PB13 + */ + +#include "stm32f4xx_hal.h" + +#ifdef HAL_ETH_MODULE_ENABLED + +void HAL_ETH_MspInit(ETH_HandleTypeDef *heth) { + (void) heth; + GPIO_InitTypeDef gpio = {0}; + + __HAL_RCC_GPIOA_CLK_ENABLE(); + __HAL_RCC_GPIOB_CLK_ENABLE(); + __HAL_RCC_GPIOC_CLK_ENABLE(); + __HAL_RCC_GPIOG_CLK_ENABLE(); + + gpio.Speed = GPIO_SPEED_HIGH; + gpio.Mode = GPIO_MODE_AF_PP; + gpio.Pull = GPIO_NOPULL; + gpio.Alternate = GPIO_AF11_ETH; + + // Port A: PA1 (REF_CLK), PA2 (MDIO), PA7 (CRS_DV) + gpio.Pin = GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_7; + HAL_GPIO_Init(GPIOA, &gpio); + + // Port B: PB13 (TXD1) + gpio.Pin = GPIO_PIN_13; + HAL_GPIO_Init(GPIOB, &gpio); + + // Port C: PC1 (MDC), PC4 (RXD0), PC5 (RXD1) + gpio.Pin = GPIO_PIN_1 | GPIO_PIN_4 | GPIO_PIN_5; + HAL_GPIO_Init(GPIOC, &gpio); + + // Port G: PG11 (TX_EN), PG13 (TXD0) + gpio.Pin = GPIO_PIN_11 | GPIO_PIN_13; + HAL_GPIO_Init(GPIOG, &gpio); + + // SYSCFG (for SYSCFG->PMC MII/RMII select) and the ETH peripheral + // clock. HAL_ETH_Init's MII/RMII bit write into SYSCFG->PMC requires + // the SYSCFG clock to already be enabled. + __HAL_RCC_SYSCFG_CLK_ENABLE(); + __HAL_RCC_ETH_CLK_ENABLE(); +} + +#endif // HAL_ETH_MODULE_ENABLED diff --git a/hw/bsp/stm32f4/eth_lwip/LICENSE.md b/hw/bsp/stm32f4/eth_lwip/LICENSE.md new file mode 100644 index 0000000000..d1f6b02ed1 --- /dev/null +++ b/hw/bsp/stm32f4/eth_lwip/LICENSE.md @@ -0,0 +1,40 @@ +Copyright (c) 2017 STMicroelectronics. + +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. Neither the name of the copyright holder nor the names of its contributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT HOLDER OR CONTRIBUTORS 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. + +Files in this directory are vendored from STM32CubeF4 +(https://github.com/STMicroelectronics/STM32CubeF4), under the BSD-3-Clause +license. The originals carry an STMicroelectronics copyright header that +references "the LICENSE file in the root directory of this software +component"; this LICENSE.md captures the BSD-3-Clause text those headers +refer to. + + ethernetif.{c,h} - from Projects/STM32F429ZI-Nucleo/Applications/LwIP/ + LwIP_TCP_Echo_Server, with the DP83848 to LAN8742 + PHY swap (same _Object_t / _IOCtx_t / _Init / + _GetLinkState API). diff --git a/hw/bsp/stm32f4/eth_lwip/ethernetif.c b/hw/bsp/stm32f4/eth_lwip/ethernetif.c new file mode 100644 index 0000000000..5ba63cfcce --- /dev/null +++ b/hw/bsp/stm32f4/eth_lwip/ethernetif.c @@ -0,0 +1,541 @@ +/** + ****************************************************************************** + * @file LwIP/LwIP_TCP_Echo_Server/Src/ethernetif.c + * @author MCD Application Team + * @brief This file implements Ethernet network interface drivers for lwIP + ****************************************************************************** + * @attention + * + * Copyright (c) 2017 STMicroelectronics. + * All rights reserved. + * + * This software is licensed under terms that can be found in the LICENSE file + * in the root directory of this software component. + * If no LICENSE file comes with this software, it is provided AS-IS. + * + ****************************************************************************** + */ +/* Includes ------------------------------------------------------------------*/ +#include "stm32f4xx_hal.h" +#include "lwip/timeouts.h" +#include "netif/ethernet.h" +#include "netif/etharp.h" +#include "lwip/stats.h" +#include "lwip/snmp.h" +#include "lwip/tcpip.h" +#include "ethernetif.h" +#include "lan8742.h" +#include + +/* Private typedef -----------------------------------------------------------*/ +/* Private define ------------------------------------------------------------*/ +/* Network interface name */ +#define IFNAME0 's' +#define IFNAME1 't' + +#define ETH_DMA_TRANSMIT_TIMEOUT (20U) + +#define ETH_RX_BUFFER_CNT 10 /* This app buffers receive packets of its primary service + * protocol for processing later. */ +#define ETH_TX_BUFFER_MAX ((ETH_TX_DESC_CNT) * 2) /* HAL_ETH_Transmit(_IT) may attach two + * buffers per descriptor. */ + +/* Private macro -------------------------------------------------------------*/ +/* Private variables ---------------------------------------------------------*/ +/* +@Note: This interface is implemented to operate in zero-copy mode only: + - Rx Buffers will be allocated from LwIP stack Rx memory pool, + then passed to ETH HAL driver. + - Tx Buffers will be allocated from LwIP stack memory heap, + then passed to ETH HAL driver. + +@Notes: + 1.a. ETH DMA Rx descriptors must be contiguous, the default count is 4, + to customize it please redefine ETH_RX_DESC_CNT in ETH GUI (Rx Descriptor Length) + so that updated value will be generated in stm32xxxx_hal_conf.h + 1.b. ETH DMA Tx descriptors must be contiguous, the default count is 4, + to customize it please redefine ETH_TX_DESC_CNT in ETH GUI (Tx Descriptor Length) + so that updated value will be generated in stm32xxxx_hal_conf.h + + 2.a. Rx Buffers number: ETH_RX_BUFFER_CNT must be greater than ETH_RX_DESC_CNT. + 2.b. Rx Buffers must have the same size: ETH_RX_BUF_SIZE, this value must + passed to ETH DMA in the init field (heth.Init.RxBuffLen) +*/ +typedef enum +{ + RX_ALLOC_OK = 0x00, + RX_ALLOC_ERROR = 0x01 +} RxAllocStatusTypeDef; + +typedef struct +{ + struct pbuf_custom pbuf_custom; + uint8_t buff[(ETH_RX_BUF_SIZE + 31) & ~31] __ALIGNED(32); +} RxBuff_t; + +ETH_DMADescTypeDef DMARxDscrTab[ETH_RX_DESC_CNT]; /* Ethernet Rx DMA Descriptors */ + +ETH_DMADescTypeDef DMATxDscrTab[ETH_TX_DESC_CNT]; /* Ethernet Tx DMA Descriptors */ + +/* Memory Pool Declaration */ +LWIP_MEMPOOL_DECLARE(RX_POOL, ETH_RX_BUFFER_CNT, sizeof(RxBuff_t), "Zero-copy RX PBUF pool"); + +static uint8_t RxAllocStatus; + +/* Global Ethernet handle*/ +ETH_HandleTypeDef EthHandle; +ETH_TxPacketConfigTypeDef TxConfig; +lan8742_Object_t LAN8742; + +/* Private function prototypes -----------------------------------------------*/ +extern void Error_Handler(void); +int32_t ETH_PHY_IO_Init(void); +int32_t ETH_PHY_IO_DeInit (void); +int32_t ETH_PHY_IO_ReadReg(uint32_t DevAddr, uint32_t RegAddr, uint32_t *pRegVal); +int32_t ETH_PHY_IO_WriteReg(uint32_t DevAddr, uint32_t RegAddr, uint32_t RegVal); +int32_t ETH_PHY_IO_GetTick(void); +void pbuf_free_custom(struct pbuf *p); + +lan8742_IOCtx_t LAN8742_IOCtx = {ETH_PHY_IO_Init, + ETH_PHY_IO_DeInit, + ETH_PHY_IO_WriteReg, + ETH_PHY_IO_ReadReg, + ETH_PHY_IO_GetTick}; +/* Private functions ---------------------------------------------------------*/ +/******************************************************************************* + LL Driver Interface ( LwIP stack --> ETH) +*******************************************************************************/ +/** + * @brief In this function, the hardware should be initialized. + * Called from ethernetif_init(). + * + * @param netif the already initialized lwip network interface structure + * for this ethernetif + */ +static void low_level_init(struct netif *netif) +{ + /* Derive a stable, locally-administered MAC from the F4 96-bit unique + * device ID. Bit 1 of the first byte is set (locally administered), + * bit 0 is cleared (unicast). */ + static uint8_t macaddress[6]; + { + const uint32_t *uid = (const uint32_t *)UID_BASE; /* 0x1FFF7A10 on F4 */ + uint32_t a = uid[0] ^ uid[2]; + uint32_t b = uid[1]; + macaddress[0] = 0x02; + macaddress[1] = (uint8_t)(a >> 0); + macaddress[2] = (uint8_t)(a >> 8); + macaddress[3] = (uint8_t)(a >> 16); + macaddress[4] = (uint8_t)(b >> 0); + macaddress[5] = (uint8_t)(b >> 8); + } + + EthHandle.Instance = ETH; + EthHandle.Init.MACAddr = macaddress; + EthHandle.Init.MediaInterface = HAL_ETH_RMII_MODE; + EthHandle.Init.RxDesc = DMARxDscrTab; + EthHandle.Init.TxDesc = DMATxDscrTab; + EthHandle.Init.RxBuffLen = ETH_RX_BUF_SIZE; + + /* configure ethernet peripheral (GPIOs, clocks, MAC, DMA) */ + HAL_ETH_Init(&EthHandle); + + /* set MAC hardware address length */ + netif->hwaddr_len = ETH_HWADDR_LEN; + + /* set MAC hardware address */ + memcpy(netif->hwaddr, macaddress, ETH_HWADDR_LEN); + + /* maximum transfer unit */ + netif->mtu = ETH_MAX_PAYLOAD; + + /* device capabilities */ + /* don't set NETIF_FLAG_ETHARP if this device is not an ethernet one */ + netif->flags |= NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP; + + /* Initialize the RX POOL */ + LWIP_MEMPOOL_INIT(RX_POOL); + + /* Set Tx packet config common parameters */ + memset(&TxConfig, 0 , sizeof(ETH_TxPacketConfigTypeDef)); + TxConfig.Attributes = ETH_TX_PACKETS_FEATURES_CSUM | ETH_TX_PACKETS_FEATURES_CRCPAD; + TxConfig.ChecksumCtrl = ETH_CHECKSUM_IPHDR_PAYLOAD_INSERT_PHDR_CALC; + TxConfig.CRCPadCtrl = ETH_CRC_PAD_INSERT; + + /* Set PHY IO functions */ + LAN8742_RegisterBusIO(&LAN8742, &LAN8742_IOCtx); + + /* Initialize the LAN8742 ETH PHY */ + if(LAN8742_Init(&LAN8742) != LAN8742_STATUS_OK) + { + netif_set_link_down(netif); + netif_set_down(netif); + return; + } + + ethernet_link_check_state(netif); +} + +/** + * @brief This function should do the actual transmission of the packet. The packet is + * contained in the pbuf that is passed to the function. This pbuf + * might be chained. + * + * @param netif the lwip network interface structure for this ethernetif + * @param p the MAC packet to send (e.g. IP packet including MAC addresses and type) + * @return ERR_OK if the packet could be sent + * an err_t value if the packet couldn't be sent + * + * @note Returning ERR_MEM here if a DMA queue of your MAC is full can lead to + * strange results. You might consider waiting for space in the DMA queue + * to become available since the stack doesn't retry to send a packet + * dropped because of memory failure (except for the TCP timers). + */ +static err_t low_level_output(struct netif *netif, struct pbuf *p) +{ + uint32_t i = 0U; + struct pbuf *q = NULL; + err_t errval = ERR_OK; + ETH_BufferTypeDef Txbuffer[ETH_TX_DESC_CNT] = {0}; + + memset(Txbuffer, 0 , ETH_TX_DESC_CNT*sizeof(ETH_BufferTypeDef)); + + for(q = p; q != NULL; q = q->next) + { + if(i >= ETH_TX_DESC_CNT) + return ERR_IF; + + Txbuffer[i].buffer = q->payload; + Txbuffer[i].len = q->len; + + if(i>0) + { + Txbuffer[i-1].next = &Txbuffer[i]; + } + + if(q->next == NULL) + { + Txbuffer[i].next = NULL; + } + + i++; + } + + TxConfig.Length = p->tot_len; + TxConfig.TxBuffer = Txbuffer; + TxConfig.pData = p; + + HAL_ETH_Transmit(&EthHandle, &TxConfig, ETH_DMA_TRANSMIT_TIMEOUT); + + return errval; +} + + +/** + * @brief Should allocate a pbuf and transfer the bytes of the incoming + * packet from the interface into the pbuf. + * + * @param netif the lwip network interface structure for this ethernetif + * @return a pbuf filled with the received packet (including MAC header) + * NULL on memory error + */ +static struct pbuf * low_level_input(struct netif *netif) +{ + struct pbuf *p = NULL; + + if(RxAllocStatus == RX_ALLOC_OK) + { + HAL_ETH_ReadData(&EthHandle, (void **)&p); + } + return p; +} +/** + * @brief This function should be called when a packet is ready to be read + * from the interface. It uses the function low_level_input() that + * should handle the actual reception of bytes from the network + * interface. Then the type of the received packet is determined and + * the appropriate input function is called. + * + * @param netif the lwip network interface structure for this ethernetif + */ +void ethernetif_input(struct netif *netif) +{ + struct pbuf *p = NULL; + + do + { + p = low_level_input( netif ); + if (p != NULL) + { + if (netif->input( p, netif) != ERR_OK ) + { + pbuf_free(p); + } + } + + } while(p!=NULL); + +} + +/** + * @brief Should be called at the beginning of the program to set up the + * network interface. It calls the function low_level_init() to do the + * actual setup of the hardware. + * + * This function should be passed as a parameter to netif_add(). + * + * @param netif the lwip network interface structure for this ethernetif + * @return ERR_OK if the loopif is initialized + * ERR_MEM if private data couldn't be allocated + * any other err_t on error + */ +err_t ethernetif_init(struct netif *netif) +{ + LWIP_ASSERT("netif != NULL", (netif != NULL)); + +#if LWIP_NETIF_HOSTNAME + /* Initialize interface hostname */ + netif->hostname = "lwip"; +#endif /* LWIP_NETIF_HOSTNAME */ + + netif->name[0] = IFNAME0; + netif->name[1] = IFNAME1; + /* We directly use etharp_output() here to save a function call. + * You can instead declare your own function an call etharp_output() + * from it if you have to do some checks before sending (e.g. if link + * is available...) */ + netif->output = etharp_output; + netif->linkoutput = low_level_output; + + /* initialize the hardware */ + low_level_init(netif); + + return ERR_OK; +} + +/** + * @brief Custom Rx pbuf free callback + * @param pbuf: pbuf to be freed + * @retval None + */ +void pbuf_free_custom(struct pbuf *p) +{ + struct pbuf_custom* custom_pbuf = (struct pbuf_custom*)p; + LWIP_MEMPOOL_FREE(RX_POOL, custom_pbuf); + /* If the Rx Buffer Pool was exhausted, signal the ethernetif_input task to + * call HAL_ETH_GetRxDataBuffer to rebuild the Rx descriptors. */ + if (RxAllocStatus == RX_ALLOC_ERROR) + { + RxAllocStatus = RX_ALLOC_OK; + } +} + +/** + * @brief Returns the current time in milliseconds + * when LWIP_TIMERS == 1 and NO_SYS == 1 + * @param None + * @retval Current Time value + */ +u32_t sys_now(void) +{ + return HAL_GetTick(); +} +/* + * HAL_ETH_MspInit (RMII pin map, peripheral clocks) lives in the + * board's BSP at hw/bsp//boards//board_eth.c so the + * example stays board-agnostic. HAL_ETH_Init() invokes the weak + * HAL_ETH_MspInit symbol automatically; the linker pulls the BSP's + * definition when family_add_eth() is called from the example's + * CMakeLists. + */ + +/******************************************************************************* + PHI IO Functions +*******************************************************************************/ +/** + * @brief Initializes the MDIO interface GPIO and clocks. + * @param None + * @retval 0 if OK, -1 if ERROR + */ +int32_t ETH_PHY_IO_Init(void) +{ + /* We assume that MDIO GPIO configuration is already done + in the ETH_MspInit() else it should be done here + */ + + /* Configure the MDIO Clock */ + HAL_ETH_SetMDIOClockRange(&EthHandle); + + return 0; +} + +/** + * @brief De-Initializes the MDIO interface . + * @param None + * @retval 0 if OK, -1 if ERROR + */ +int32_t ETH_PHY_IO_DeInit (void) +{ + return 0; +} + +/** + * @brief Read a PHY register through the MDIO interface. + * @param DevAddr: PHY port address + * @param RegAddr: PHY register address + * @param pRegVal: pointer to hold the register value + * @retval 0 if OK -1 if Error + */ +int32_t ETH_PHY_IO_ReadReg(uint32_t DevAddr, uint32_t RegAddr, uint32_t *pRegVal) +{ + if(HAL_ETH_ReadPHYRegister(&EthHandle, DevAddr, RegAddr, pRegVal) != HAL_OK) + { + return -1; + } + + return 0; +} + +/** + * @brief Write a value to a PHY register through the MDIO interface. + * @param DevAddr: PHY port address + * @param RegAddr: PHY register address + * @param RegVal: Value to be written + * @retval 0 if OK -1 if Error + */ +int32_t ETH_PHY_IO_WriteReg(uint32_t DevAddr, uint32_t RegAddr, uint32_t RegVal) +{ + if(HAL_ETH_WritePHYRegister(&EthHandle, DevAddr, RegAddr, RegVal) != HAL_OK) + { + return -1; + } + + return 0; +} + +/** + * @brief Get the time in millisecons used for internal PHY driver process. + * @retval Time value + */ +int32_t ETH_PHY_IO_GetTick(void) +{ + return HAL_GetTick(); +} + +/** + * @brief + * @retval None + */ +void ethernet_link_check_state(struct netif *netif) +{ + ETH_MACConfigTypeDef MACConf = {0}; + int32_t PHYLinkState = 0U; + uint32_t linkchanged = 0U, speed = 0U, duplex =0U; + + PHYLinkState = LAN8742_GetLinkState(&LAN8742); + + if(netif_is_link_up(netif) && (PHYLinkState <= LAN8742_STATUS_LINK_DOWN)) + { + HAL_ETH_Stop(&EthHandle); + netif_set_down(netif); + netif_set_link_down(netif); + } + else if(!netif_is_link_up(netif) && (PHYLinkState > LAN8742_STATUS_LINK_DOWN)) + { + switch (PHYLinkState) + { + case LAN8742_STATUS_100MBITS_FULLDUPLEX: + duplex = ETH_FULLDUPLEX_MODE; + speed = ETH_SPEED_100M; + linkchanged = 1; + break; + case LAN8742_STATUS_100MBITS_HALFDUPLEX: + duplex = ETH_HALFDUPLEX_MODE; + speed = ETH_SPEED_100M; + linkchanged = 1; + break; + case LAN8742_STATUS_10MBITS_FULLDUPLEX: + duplex = ETH_FULLDUPLEX_MODE; + speed = ETH_SPEED_10M; + linkchanged = 1; + break; + case LAN8742_STATUS_10MBITS_HALFDUPLEX: + duplex = ETH_HALFDUPLEX_MODE; + speed = ETH_SPEED_10M; + linkchanged = 1; + break; + default: + break; + } + + if(linkchanged) + { + /* Get MAC Config MAC */ + HAL_ETH_GetMACConfig(&EthHandle, &MACConf); + MACConf.DuplexMode = duplex; + MACConf.Speed = speed; + HAL_ETH_SetMACConfig(&EthHandle, &MACConf); + HAL_ETH_Start(&EthHandle); + netif_set_up(netif); + netif_set_link_up(netif); + } + } +} + +void HAL_ETH_RxAllocateCallback(uint8_t **buff) +{ + struct pbuf_custom *p = LWIP_MEMPOOL_ALLOC(RX_POOL); + if (p) + { + /* Get the buff from the struct pbuf address. */ + *buff = (uint8_t *)p + offsetof(RxBuff_t, buff); + p->custom_free_function = pbuf_free_custom; + /* Initialize the struct pbuf. + * This must be performed whenever a buffer's allocated because it may be + * changed by lwIP or the app, e.g., pbuf_free decrements ref. */ + pbuf_alloced_custom(PBUF_RAW, 0, PBUF_REF, p, *buff, ETH_RX_BUF_SIZE); + } + else + { + RxAllocStatus = RX_ALLOC_ERROR; + *buff = NULL; + } +} + +void HAL_ETH_RxLinkCallback(void **pStart, void **pEnd, uint8_t *buff, uint16_t Length) +{ + struct pbuf **ppStart = (struct pbuf **)pStart; + struct pbuf **ppEnd = (struct pbuf **)pEnd; + struct pbuf *p = NULL; + + /* Get the struct pbuf from the buff address. */ + p = (struct pbuf *)(buff - offsetof(RxBuff_t, buff)); + p->next = NULL; + p->tot_len = 0; + p->len = Length; + + /* Chain the buffer. */ + if (!*ppStart) + { + /* The first buffer of the packet. */ + *ppStart = p; + } + else + { + /* Chain the buffer to the end of the packet. */ + (*ppEnd)->next = p; + } + *ppEnd = p; + + /* Update the total length of all the buffers of the chain. Each pbuf in the chain should have its tot_len + * set to its own length, plus the length of all the following pbufs in the chain. */ + for (p = *ppStart; p != NULL; p = p->next) + { + p->tot_len += Length; + } +} + +void HAL_ETH_TxFreeCallback(uint32_t * buff) +{ + pbuf_free((struct pbuf *)buff); +} diff --git a/hw/bsp/stm32f4/eth_lwip/ethernetif.h b/hw/bsp/stm32f4/eth_lwip/ethernetif.h new file mode 100644 index 0000000000..1fb9f9f289 --- /dev/null +++ b/hw/bsp/stm32f4/eth_lwip/ethernetif.h @@ -0,0 +1,30 @@ +/** + ****************************************************************************** + * @file ethernetif.h + * @author MCD Application Team + * @brief Ethernet interface header file. + ****************************************************************************** + * @attention + * + * Copyright (c) 2017 STMicroelectronics. + * All rights reserved. + * + * This software is licensed under terms that can be found in the LICENSE file + * in the root directory of this software component. + * If no LICENSE file comes with this software, it is provided AS-IS. + * + ****************************************************************************** + */ + +#ifndef __ETHERNETIF_H__ +#define __ETHERNETIF_H__ + + +#include "lwip/err.h" +#include "lwip/netif.h" + +/* Exported types ------------------------------------------------------------*/ +err_t ethernetif_init(struct netif *netif); +void ethernetif_input(struct netif *netif); +void ethernet_link_check_state(struct netif *netif); +#endif diff --git a/hw/bsp/stm32f4/family.cmake b/hw/bsp/stm32f4/family.cmake index 0d3d9ec930..3244823a28 100644 --- a/hw/bsp/stm32f4/family.cmake +++ b/hw/bsp/stm32f4/family.cmake @@ -129,3 +129,46 @@ function(family_configure_example TARGET RTOS) family_flash_stlink(${TARGET}) family_flash_jlink(${TARGET}) endfunction() + +#------------------------------------ +# Optional Ethernet support (HAL_ETH + LAN8742 PHY + lwIP netif) +#------------------------------------ +# Pull HAL_ETH, the LAN8742 PHY driver and the lwIP netif glue into +# TARGET, plus the board-specific MspInit (RMII pin map). Boards +# that wire an Ethernet PHY provide +# hw/bsp/stm32f4/boards/${BOARD}/board_eth.c defining +# HAL_ETH_MspInit; family_add_eth fails if that file is missing. +# +# The caller must already have lwIP set up on TARGET's include path +# (the netif glue includes lwip/netif.h and lwip/timeouts.h). +function(family_add_eth TARGET) + set(BOARD_ETH_SRC ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/boards/${BOARD}/board_eth.c) + if (NOT EXISTS ${BOARD_ETH_SRC}) + message(FATAL_ERROR + "family_add_eth: ${BOARD} has no board_eth.c. " + "Boards that wire an Ethernet PHY must provide one alongside " + "their board.h with the HAL_ETH MspInit pin map.") + endif () + + set(ETH_LWIP_DIR ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/eth_lwip) + + target_compile_definitions(${TARGET} PUBLIC HAL_ETH_MODULE_ENABLED) + target_sources(${TARGET} PUBLIC + ${BOARD_ETH_SRC} + ${ST_HAL_DRIVER}/Src/${ST_PREFIX}_hal_eth.c + ${TOP}/hw/mcu/st/stm32_lan8742/lan8742.c + ${ETH_LWIP_DIR}/ethernetif.c + ) + target_include_directories(${TARGET} PUBLIC + ${TOP}/hw/mcu/st/stm32_lan8742 + ${ETH_LWIP_DIR} + ) + # Vendored sources don't pass tinyusb's strict warning baseline. + set_source_files_properties( + ${ST_HAL_DRIVER}/Src/${ST_PREFIX}_hal_eth.c + ${TOP}/hw/mcu/st/stm32_lan8742/lan8742.c + ${ETH_LWIP_DIR}/ethernetif.c + ${BOARD_ETH_SRC} + 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") +endfunction() diff --git a/hw/mcu/st/stm32_lan8742/LICENSE.md b/hw/mcu/st/stm32_lan8742/LICENSE.md new file mode 100644 index 0000000000..5a3a0e75c0 --- /dev/null +++ b/hw/mcu/st/stm32_lan8742/LICENSE.md @@ -0,0 +1,32 @@ +Copyright 2017(-2019) STMicroelectronics. + +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. Neither the name of the copyright holder nor the names of its contributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT HOLDER OR CONTRIBUTORS 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. + +Verbatim copy of `LICENSE.md` from +https://github.com/STMicroelectronics/stm32-lan8742 (commit +`9f365d062b3cfae33859acd50e5df932e815b76a`, the version vendored here). diff --git a/hw/mcu/st/stm32_lan8742/lan8742.c b/hw/mcu/st/stm32_lan8742/lan8742.c new file mode 100644 index 0000000000..18a1309fc7 --- /dev/null +++ b/hw/mcu/st/stm32_lan8742/lan8742.c @@ -0,0 +1,613 @@ +/** + ****************************************************************************** + * @file lan8742.c + * @author MCD Application Team + * @brief This file provides a set of functions needed to manage the LAN742 + * PHY devices. + ****************************************************************************** + * @attention + * + * Copyright (c) 2017 STMicroelectronics. + * All rights reserved. + * + * This software is licensed under terms that can be found in the LICENSE file + * in the root directory of this software component. + * If no LICENSE file comes with this software, it is provided AS-IS. + * + ****************************************************************************** + */ + +/* Includes ------------------------------------------------------------------*/ +#include "lan8742.h" + +/** @addtogroup BSP + * @{ + */ + +/** @addtogroup Component + * @{ + */ + +/** @defgroup LAN8742 LAN8742 + * @{ + */ + +/* Private typedef -----------------------------------------------------------*/ +/* Private define ------------------------------------------------------------*/ +/** @defgroup LAN8742_Private_Defines LAN8742 Private Defines + * @{ + */ +#define LAN8742_MAX_DEV_ADDR ((uint32_t)31U) +/** + * @} + */ + +/* Private macro -------------------------------------------------------------*/ +/* Private variables ---------------------------------------------------------*/ +/* Private function prototypes -----------------------------------------------*/ +/* Private functions ---------------------------------------------------------*/ +/** @defgroup LAN8742_Private_Functions LAN8742 Private Functions + * @{ + */ + +/** + * @brief Register IO functions to component object + * @param pObj: device object of LAN8742_Object_t. + * @param ioctx: holds device IO functions. + * @retval LAN8742_STATUS_OK if OK + * LAN8742_STATUS_ERROR if missing mandatory function + */ +int32_t LAN8742_RegisterBusIO(lan8742_Object_t *pObj, lan8742_IOCtx_t *ioctx) +{ + if(!pObj || !ioctx->ReadReg || !ioctx->WriteReg || !ioctx->GetTick) + { + return LAN8742_STATUS_ERROR; + } + + pObj->IO.Init = ioctx->Init; + pObj->IO.DeInit = ioctx->DeInit; + pObj->IO.ReadReg = ioctx->ReadReg; + pObj->IO.WriteReg = ioctx->WriteReg; + pObj->IO.GetTick = ioctx->GetTick; + + return LAN8742_STATUS_OK; +} + +/** + * @brief Initialize the lan8742 and configure the needed hardware resources + * @param pObj: device object LAN8742_Object_t. + * @retval LAN8742_STATUS_OK if OK + * LAN8742_STATUS_ADDRESS_ERROR if cannot find device address + * LAN8742_STATUS_READ_ERROR if cannot read register + */ + int32_t LAN8742_Init(lan8742_Object_t *pObj) + { + uint32_t regvalue = 0, addr = 0; + int32_t status = LAN8742_STATUS_OK; + + if(pObj->Is_Initialized == 0) + { + if(pObj->IO.Init != 0) + { + /* GPIO and Clocks initialization */ + pObj->IO.Init(); + } + + /* for later check */ + pObj->DevAddr = LAN8742_MAX_DEV_ADDR + 1; + + /* Get the device address from special mode register */ + for(addr = 0; addr <= LAN8742_MAX_DEV_ADDR; addr ++) + { + if(pObj->IO.ReadReg(addr, LAN8742_SMR, ®value) < 0) + { + status = LAN8742_STATUS_READ_ERROR; + /* Can't read from this device address + continue with next address */ + continue; + } + + if((regvalue & LAN8742_SMR_PHY_ADDR) == addr) + { + pObj->DevAddr = addr; + status = LAN8742_STATUS_OK; + break; + } + } + + if(pObj->DevAddr > LAN8742_MAX_DEV_ADDR) + { + status = LAN8742_STATUS_ADDRESS_ERROR; + } + + /* if device address is matched */ + if(status == LAN8742_STATUS_OK) + { + pObj->Is_Initialized = 1; + } + } + + return status; + } + +/** + * @brief De-Initialize the lan8742 and it's hardware resources + * @param pObj: device object LAN8742_Object_t. + * @retval None + */ +int32_t LAN8742_DeInit(lan8742_Object_t *pObj) +{ + if(pObj->Is_Initialized) + { + if(pObj->IO.DeInit != 0) + { + if(pObj->IO.DeInit() < 0) + { + return LAN8742_STATUS_ERROR; + } + } + + pObj->Is_Initialized = 0; + } + + return LAN8742_STATUS_OK; +} + +/** + * @brief Disable the LAN8742 power down mode. + * @param pObj: device object LAN8742_Object_t. + * @retval LAN8742_STATUS_OK if OK + * LAN8742_STATUS_READ_ERROR if cannot read register + * LAN8742_STATUS_WRITE_ERROR if cannot write to register + */ +int32_t LAN8742_DisablePowerDownMode(lan8742_Object_t *pObj) +{ + uint32_t readval = 0; + int32_t status = LAN8742_STATUS_OK; + + if(pObj->IO.ReadReg(pObj->DevAddr, LAN8742_BCR, &readval) >= 0) + { + readval &= ~LAN8742_BCR_POWER_DOWN; + + /* Apply configuration */ + if(pObj->IO.WriteReg(pObj->DevAddr, LAN8742_BCR, readval) < 0) + { + status = LAN8742_STATUS_WRITE_ERROR; + } + } + else + { + status = LAN8742_STATUS_READ_ERROR; + } + + return status; +} + +/** + * @brief Enable the LAN8742 power down mode. + * @param pObj: device object LAN8742_Object_t. + * @retval LAN8742_STATUS_OK if OK + * LAN8742_STATUS_READ_ERROR if cannot read register + * LAN8742_STATUS_WRITE_ERROR if cannot write to register + */ +int32_t LAN8742_EnablePowerDownMode(lan8742_Object_t *pObj) +{ + uint32_t readval = 0; + int32_t status = LAN8742_STATUS_OK; + + if(pObj->IO.ReadReg(pObj->DevAddr, LAN8742_BCR, &readval) >= 0) + { + readval |= LAN8742_BCR_POWER_DOWN; + + /* Apply configuration */ + if(pObj->IO.WriteReg(pObj->DevAddr, LAN8742_BCR, readval) < 0) + { + status = LAN8742_STATUS_WRITE_ERROR; + } + } + else + { + status = LAN8742_STATUS_READ_ERROR; + } + + return status; +} + +/** + * @brief Start the auto negotiation process. + * @param pObj: device object LAN8742_Object_t. + * @retval LAN8742_STATUS_OK if OK + * LAN8742_STATUS_READ_ERROR if cannot read register + * LAN8742_STATUS_WRITE_ERROR if cannot write to register + */ +int32_t LAN8742_StartAutoNego(lan8742_Object_t *pObj) +{ + uint32_t readval = 0; + int32_t status = LAN8742_STATUS_OK; + + if(pObj->IO.ReadReg(pObj->DevAddr, LAN8742_BCR, &readval) >= 0) + { + readval |= LAN8742_BCR_AUTONEGO_EN; + + /* Apply configuration */ + if(pObj->IO.WriteReg(pObj->DevAddr, LAN8742_BCR, readval) < 0) + { + status = LAN8742_STATUS_WRITE_ERROR; + } + } + else + { + status = LAN8742_STATUS_READ_ERROR; + } + + return status; +} + +/** + * @brief Get the link state of LAN8742 device. + * @param pObj: Pointer to device object. + * @param pLinkState: Pointer to link state + * @retval LAN8742_STATUS_LINK_DOWN if link is down + * LAN8742_STATUS_AUTONEGO_NOTDONE if Auto nego not completed + * LAN8742_STATUS_100MBITS_FULLDUPLEX if 100Mb/s FD + * LAN8742_STATUS_100MBITS_HALFDUPLEX if 100Mb/s HD + * LAN8742_STATUS_10MBITS_FULLDUPLEX if 10Mb/s FD + * LAN8742_STATUS_10MBITS_HALFDUPLEX if 10Mb/s HD + * LAN8742_STATUS_READ_ERROR if cannot read register + * LAN8742_STATUS_WRITE_ERROR if cannot write to register + */ +int32_t LAN8742_GetLinkState(lan8742_Object_t *pObj) +{ + uint32_t readval = 0; + + /* Read Status register */ + if(pObj->IO.ReadReg(pObj->DevAddr, LAN8742_BSR, &readval) < 0) + { + return LAN8742_STATUS_READ_ERROR; + } + + /* Read Status register again */ + if(pObj->IO.ReadReg(pObj->DevAddr, LAN8742_BSR, &readval) < 0) + { + return LAN8742_STATUS_READ_ERROR; + } + + if((readval & LAN8742_BSR_LINK_STATUS) == 0) + { + /* Return Link Down status */ + return LAN8742_STATUS_LINK_DOWN; + } + + /* Check Auto negotiation */ + if(pObj->IO.ReadReg(pObj->DevAddr, LAN8742_BCR, &readval) < 0) + { + return LAN8742_STATUS_READ_ERROR; + } + + if((readval & LAN8742_BCR_AUTONEGO_EN) != LAN8742_BCR_AUTONEGO_EN) + { + if(((readval & LAN8742_BCR_SPEED_SELECT) == LAN8742_BCR_SPEED_SELECT) && ((readval & LAN8742_BCR_DUPLEX_MODE) == LAN8742_BCR_DUPLEX_MODE)) + { + return LAN8742_STATUS_100MBITS_FULLDUPLEX; + } + else if ((readval & LAN8742_BCR_SPEED_SELECT) == LAN8742_BCR_SPEED_SELECT) + { + return LAN8742_STATUS_100MBITS_HALFDUPLEX; + } + else if ((readval & LAN8742_BCR_DUPLEX_MODE) == LAN8742_BCR_DUPLEX_MODE) + { + return LAN8742_STATUS_10MBITS_FULLDUPLEX; + } + else + { + return LAN8742_STATUS_10MBITS_HALFDUPLEX; + } + } + else /* Auto Nego enabled */ + { + if(pObj->IO.ReadReg(pObj->DevAddr, LAN8742_PHYSCSR, &readval) < 0) + { + return LAN8742_STATUS_READ_ERROR; + } + + /* Check if auto nego not done */ + if((readval & LAN8742_PHYSCSR_AUTONEGO_DONE) == 0) + { + return LAN8742_STATUS_AUTONEGO_NOTDONE; + } + + if((readval & LAN8742_PHYSCSR_HCDSPEEDMASK) == LAN8742_PHYSCSR_100BTX_FD) + { + return LAN8742_STATUS_100MBITS_FULLDUPLEX; + } + else if ((readval & LAN8742_PHYSCSR_HCDSPEEDMASK) == LAN8742_PHYSCSR_100BTX_HD) + { + return LAN8742_STATUS_100MBITS_HALFDUPLEX; + } + else if ((readval & LAN8742_PHYSCSR_HCDSPEEDMASK) == LAN8742_PHYSCSR_10BT_FD) + { + return LAN8742_STATUS_10MBITS_FULLDUPLEX; + } + else + { + return LAN8742_STATUS_10MBITS_HALFDUPLEX; + } + } +} + +/** + * @brief Set the link state of LAN8742 device. + * @param pObj: Pointer to device object. + * @param pLinkState: link state can be one of the following + * LAN8742_STATUS_100MBITS_FULLDUPLEX if 100Mb/s FD + * LAN8742_STATUS_100MBITS_HALFDUPLEX if 100Mb/s HD + * LAN8742_STATUS_10MBITS_FULLDUPLEX if 10Mb/s FD + * LAN8742_STATUS_10MBITS_HALFDUPLEX if 10Mb/s HD + * @retval LAN8742_STATUS_OK if OK + * LAN8742_STATUS_ERROR if parameter error + * LAN8742_STATUS_READ_ERROR if cannot read register + * LAN8742_STATUS_WRITE_ERROR if cannot write to register + */ +int32_t LAN8742_SetLinkState(lan8742_Object_t *pObj, uint32_t LinkState) +{ + uint32_t bcrvalue = 0; + int32_t status = LAN8742_STATUS_OK; + + if(pObj->IO.ReadReg(pObj->DevAddr, LAN8742_BCR, &bcrvalue) >= 0) + { + /* Disable link config (Auto nego, speed and duplex) */ + bcrvalue &= ~(LAN8742_BCR_AUTONEGO_EN | LAN8742_BCR_SPEED_SELECT | LAN8742_BCR_DUPLEX_MODE); + + if(LinkState == LAN8742_STATUS_100MBITS_FULLDUPLEX) + { + bcrvalue |= (LAN8742_BCR_SPEED_SELECT | LAN8742_BCR_DUPLEX_MODE); + } + else if (LinkState == LAN8742_STATUS_100MBITS_HALFDUPLEX) + { + bcrvalue |= LAN8742_BCR_SPEED_SELECT; + } + else if (LinkState == LAN8742_STATUS_10MBITS_FULLDUPLEX) + { + bcrvalue |= LAN8742_BCR_DUPLEX_MODE; + } + else + { + /* Wrong link status parameter */ + status = LAN8742_STATUS_ERROR; + } + } + else + { + status = LAN8742_STATUS_READ_ERROR; + } + + if(status == LAN8742_STATUS_OK) + { + /* Apply configuration */ + if(pObj->IO.WriteReg(pObj->DevAddr, LAN8742_BCR, bcrvalue) < 0) + { + status = LAN8742_STATUS_WRITE_ERROR; + } + } + + return status; +} + +/** + * @brief Enable loopback mode. + * @param pObj: Pointer to device object. + * @retval LAN8742_STATUS_OK if OK + * LAN8742_STATUS_READ_ERROR if cannot read register + * LAN8742_STATUS_WRITE_ERROR if cannot write to register + */ +int32_t LAN8742_EnableLoopbackMode(lan8742_Object_t *pObj) +{ + uint32_t readval = 0; + int32_t status = LAN8742_STATUS_OK; + + if(pObj->IO.ReadReg(pObj->DevAddr, LAN8742_BCR, &readval) >= 0) + { + readval |= LAN8742_BCR_LOOPBACK; + + /* Apply configuration */ + if(pObj->IO.WriteReg(pObj->DevAddr, LAN8742_BCR, readval) < 0) + { + status = LAN8742_STATUS_WRITE_ERROR; + } + } + else + { + status = LAN8742_STATUS_READ_ERROR; + } + + return status; +} + +/** + * @brief Disable loopback mode. + * @param pObj: Pointer to device object. + * @retval LAN8742_STATUS_OK if OK + * LAN8742_STATUS_READ_ERROR if cannot read register + * LAN8742_STATUS_WRITE_ERROR if cannot write to register + */ +int32_t LAN8742_DisableLoopbackMode(lan8742_Object_t *pObj) +{ + uint32_t readval = 0; + int32_t status = LAN8742_STATUS_OK; + + if(pObj->IO.ReadReg(pObj->DevAddr, LAN8742_BCR, &readval) >= 0) + { + readval &= ~LAN8742_BCR_LOOPBACK; + + /* Apply configuration */ + if(pObj->IO.WriteReg(pObj->DevAddr, LAN8742_BCR, readval) < 0) + { + status = LAN8742_STATUS_WRITE_ERROR; + } + } + else + { + status = LAN8742_STATUS_READ_ERROR; + } + + return status; +} + +/** + * @brief Enable IT source. + * @param pObj: Pointer to device object. + * @param Interrupt: IT source to be enabled + * should be a value or a combination of the following: + * LAN8742_WOL_IT + * LAN8742_ENERGYON_IT + * LAN8742_AUTONEGO_COMPLETE_IT + * LAN8742_REMOTE_FAULT_IT + * LAN8742_LINK_DOWN_IT + * LAN8742_AUTONEGO_LP_ACK_IT + * LAN8742_PARALLEL_DETECTION_FAULT_IT + * LAN8742_AUTONEGO_PAGE_RECEIVED_IT + * @retval LAN8742_STATUS_OK if OK + * LAN8742_STATUS_READ_ERROR if cannot read register + * LAN8742_STATUS_WRITE_ERROR if cannot write to register + */ +int32_t LAN8742_EnableIT(lan8742_Object_t *pObj, uint32_t Interrupt) +{ + uint32_t readval = 0; + int32_t status = LAN8742_STATUS_OK; + + if(pObj->IO.ReadReg(pObj->DevAddr, LAN8742_IMR, &readval) >= 0) + { + readval |= Interrupt; + + /* Apply configuration */ + if(pObj->IO.WriteReg(pObj->DevAddr, LAN8742_IMR, readval) < 0) + { + status = LAN8742_STATUS_WRITE_ERROR; + } + } + else + { + status = LAN8742_STATUS_READ_ERROR; + } + + return status; +} + +/** + * @brief Disable IT source. + * @param pObj: Pointer to device object. + * @param Interrupt: IT source to be disabled + * should be a value or a combination of the following: + * LAN8742_WOL_IT + * LAN8742_ENERGYON_IT + * LAN8742_AUTONEGO_COMPLETE_IT + * LAN8742_REMOTE_FAULT_IT + * LAN8742_LINK_DOWN_IT + * LAN8742_AUTONEGO_LP_ACK_IT + * LAN8742_PARALLEL_DETECTION_FAULT_IT + * LAN8742_AUTONEGO_PAGE_RECEIVED_IT + * @retval LAN8742_STATUS_OK if OK + * LAN8742_STATUS_READ_ERROR if cannot read register + * LAN8742_STATUS_WRITE_ERROR if cannot write to register + */ +int32_t LAN8742_DisableIT(lan8742_Object_t *pObj, uint32_t Interrupt) +{ + uint32_t readval = 0; + int32_t status = LAN8742_STATUS_OK; + + if(pObj->IO.ReadReg(pObj->DevAddr, LAN8742_IMR, &readval) >= 0) + { + readval &= ~Interrupt; + + /* Apply configuration */ + if(pObj->IO.WriteReg(pObj->DevAddr, LAN8742_IMR, readval) < 0) + { + status = LAN8742_STATUS_WRITE_ERROR; + } + } + else + { + status = LAN8742_STATUS_READ_ERROR; + } + + return status; +} + +/** + * @brief Clear IT flag. + * @param pObj: Pointer to device object. + * @param Interrupt: IT flag to be cleared + * should be a value or a combination of the following: + * LAN8742_WOL_IT + * LAN8742_ENERGYON_IT + * LAN8742_AUTONEGO_COMPLETE_IT + * LAN8742_REMOTE_FAULT_IT + * LAN8742_LINK_DOWN_IT + * LAN8742_AUTONEGO_LP_ACK_IT + * LAN8742_PARALLEL_DETECTION_FAULT_IT + * LAN8742_AUTONEGO_PAGE_RECEIVED_IT + * @retval LAN8742_STATUS_OK if OK + * LAN8742_STATUS_READ_ERROR if cannot read register + */ +int32_t LAN8742_ClearIT(lan8742_Object_t *pObj, uint32_t Interrupt) +{ + uint32_t readval = 0; + int32_t status = LAN8742_STATUS_OK; + + if(pObj->IO.ReadReg(pObj->DevAddr, LAN8742_ISFR, &readval) < 0) + { + status = LAN8742_STATUS_READ_ERROR; + } + + return status; +} + +/** + * @brief Get IT Flag status. + * @param pObj: Pointer to device object. + * @param Interrupt: IT Flag to be checked, + * should be a value or a combination of the following: + * LAN8742_WOL_IT + * LAN8742_ENERGYON_IT + * LAN8742_AUTONEGO_COMPLETE_IT + * LAN8742_REMOTE_FAULT_IT + * LAN8742_LINK_DOWN_IT + * LAN8742_AUTONEGO_LP_ACK_IT + * LAN8742_PARALLEL_DETECTION_FAULT_IT + * LAN8742_AUTONEGO_PAGE_RECEIVED_IT + * @retval 1 IT flag is SET + * 0 IT flag is RESET + * LAN8742_STATUS_READ_ERROR if cannot read register + */ +int32_t LAN8742_GetITStatus(lan8742_Object_t *pObj, uint32_t Interrupt) +{ + uint32_t readval = 0; + int32_t status = 0; + + if(pObj->IO.ReadReg(pObj->DevAddr, LAN8742_ISFR, &readval) >= 0) + { + status = ((readval & Interrupt) == Interrupt); + } + else + { + status = LAN8742_STATUS_READ_ERROR; + } + + return status; +} + +/** + * @} + */ + +/** + * @} + */ + +/** + * @} + */ + +/** + * @} + */ diff --git a/hw/mcu/st/stm32_lan8742/lan8742.h b/hw/mcu/st/stm32_lan8742/lan8742.h new file mode 100644 index 0000000000..dc3da16d1f --- /dev/null +++ b/hw/mcu/st/stm32_lan8742/lan8742.h @@ -0,0 +1,446 @@ +/** + ****************************************************************************** + * @file lan8742.h + * @author MCD Application Team + * @brief This file contains all the functions prototypes for the + * lan8742.c PHY driver. + ****************************************************************************** + * @attention + * + * Copyright (c) 2017 STMicroelectronics. + * All rights reserved. + * + * This software is licensed under terms that can be found in the LICENSE file + * in the root directory of this software component. + * If no LICENSE file comes with this software, it is provided AS-IS. + * + ****************************************************************************** + */ + +/* Define to prevent recursive inclusion -------------------------------------*/ +#ifndef LAN8742_H +#define LAN8742_H + +#ifdef __cplusplus + extern "C" { +#endif + +/* Includes ------------------------------------------------------------------*/ +#include + +/** @addtogroup BSP + * @{ + */ + +/** @addtogroup Component + * @{ + */ + +/** @defgroup LAN8742 + * @{ + */ +/* Exported constants --------------------------------------------------------*/ +/** @defgroup LAN8742_Exported_Constants LAN8742 Exported Constants + * @{ + */ + +/** @defgroup LAN8742_Registers_Mapping LAN8742 Registers Mapping + * @{ + */ +#define LAN8742_BCR ((uint16_t)0x0000U) +#define LAN8742_BSR ((uint16_t)0x0001U) +#define LAN8742_PHYI1R ((uint16_t)0x0002U) +#define LAN8742_PHYI2R ((uint16_t)0x0003U) +#define LAN8742_ANAR ((uint16_t)0x0004U) +#define LAN8742_ANLPAR ((uint16_t)0x0005U) +#define LAN8742_ANER ((uint16_t)0x0006U) +#define LAN8742_ANNPTR ((uint16_t)0x0007U) +#define LAN8742_ANNPRR ((uint16_t)0x0008U) +#define LAN8742_MMDACR ((uint16_t)0x000DU) +#define LAN8742_MMDAADR ((uint16_t)0x000EU) +#define LAN8742_ENCTR ((uint16_t)0x0010U) +#define LAN8742_MCSR ((uint16_t)0x0011U) +#define LAN8742_SMR ((uint16_t)0x0012U) +#define LAN8742_TPDCR ((uint16_t)0x0018U) +#define LAN8742_TCSR ((uint16_t)0x0019U) +#define LAN8742_SECR ((uint16_t)0x001AU) +#define LAN8742_SCSIR ((uint16_t)0x001BU) +#define LAN8742_CLR ((uint16_t)0x001CU) +#define LAN8742_ISFR ((uint16_t)0x001DU) +#define LAN8742_IMR ((uint16_t)0x001EU) +#define LAN8742_PHYSCSR ((uint16_t)0x001FU) +/** + * @} + */ + +/** @defgroup LAN8742_BCR_Bit_Definition LAN8742 BCR Bit Definition + * @{ + */ +#define LAN8742_BCR_SOFT_RESET ((uint16_t)0x8000U) +#define LAN8742_BCR_LOOPBACK ((uint16_t)0x4000U) +#define LAN8742_BCR_SPEED_SELECT ((uint16_t)0x2000U) +#define LAN8742_BCR_AUTONEGO_EN ((uint16_t)0x1000U) +#define LAN8742_BCR_POWER_DOWN ((uint16_t)0x0800U) +#define LAN8742_BCR_ISOLATE ((uint16_t)0x0400U) +#define LAN8742_BCR_RESTART_AUTONEGO ((uint16_t)0x0200U) +#define LAN8742_BCR_DUPLEX_MODE ((uint16_t)0x0100U) +/** + * @} + */ + +/** @defgroup LAN8742_BSR_Bit_Definition LAN8742 BSR Bit Definition + * @{ + */ +#define LAN8742_BSR_100BASE_T4 ((uint16_t)0x8000U) +#define LAN8742_BSR_100BASE_TX_FD ((uint16_t)0x4000U) +#define LAN8742_BSR_100BASE_TX_HD ((uint16_t)0x2000U) +#define LAN8742_BSR_10BASE_T_FD ((uint16_t)0x1000U) +#define LAN8742_BSR_10BASE_T_HD ((uint16_t)0x0800U) +#define LAN8742_BSR_100BASE_T2_FD ((uint16_t)0x0400U) +#define LAN8742_BSR_100BASE_T2_HD ((uint16_t)0x0200U) +#define LAN8742_BSR_EXTENDED_STATUS ((uint16_t)0x0100U) +#define LAN8742_BSR_AUTONEGO_CPLT ((uint16_t)0x0020U) +#define LAN8742_BSR_REMOTE_FAULT ((uint16_t)0x0010U) +#define LAN8742_BSR_AUTONEGO_ABILITY ((uint16_t)0x0008U) +#define LAN8742_BSR_LINK_STATUS ((uint16_t)0x0004U) +#define LAN8742_BSR_JABBER_DETECT ((uint16_t)0x0002U) +#define LAN8742_BSR_EXTENDED_CAP ((uint16_t)0x0001U) +/** + * @} + */ + +/** @defgroup LAN8742_PHYI1R_Bit_Definition LAN8742 PHYI1R Bit Definition + * @{ + */ +#define LAN8742_PHYI1R_OUI_3_18 ((uint16_t)0xFFFFU) +/** + * @} + */ + +/** @defgroup LAN8742_PHYI2R_Bit_Definition LAN8742 PHYI2R Bit Definition + * @{ + */ +#define LAN8742_PHYI2R_OUI_19_24 ((uint16_t)0xFC00U) +#define LAN8742_PHYI2R_MODEL_NBR ((uint16_t)0x03F0U) +#define LAN8742_PHYI2R_REVISION_NBR ((uint16_t)0x000FU) +/** + * @} + */ + +/** @defgroup LAN8742_ANAR_Bit_Definition LAN8742 ANAR Bit Definition + * @{ + */ +#define LAN8742_ANAR_NEXT_PAGE ((uint16_t)0x8000U) +#define LAN8742_ANAR_REMOTE_FAULT ((uint16_t)0x2000U) +#define LAN8742_ANAR_PAUSE_OPERATION ((uint16_t)0x0C00U) +#define LAN8742_ANAR_PO_NOPAUSE ((uint16_t)0x0000U) +#define LAN8742_ANAR_PO_SYMMETRIC_PAUSE ((uint16_t)0x0400U) +#define LAN8742_ANAR_PO_ASYMMETRIC_PAUSE ((uint16_t)0x0800U) +#define LAN8742_ANAR_PO_ADVERTISE_SUPPORT ((uint16_t)0x0C00U) +#define LAN8742_ANAR_100BASE_TX_FD ((uint16_t)0x0100U) +#define LAN8742_ANAR_100BASE_TX ((uint16_t)0x0080U) +#define LAN8742_ANAR_10BASE_T_FD ((uint16_t)0x0040U) +#define LAN8742_ANAR_10BASE_T ((uint16_t)0x0020U) +#define LAN8742_ANAR_SELECTOR_FIELD ((uint16_t)0x000FU) +/** + * @} + */ + +/** @defgroup LAN8742_ANLPAR_Bit_Definition LAN8742 ANLPAR Bit Definition + * @{ + */ +#define LAN8742_ANLPAR_NEXT_PAGE ((uint16_t)0x8000U) +#define LAN8742_ANLPAR_REMOTE_FAULT ((uint16_t)0x2000U) +#define LAN8742_ANLPAR_PAUSE_OPERATION ((uint16_t)0x0C00U) +#define LAN8742_ANLPAR_PO_NOPAUSE ((uint16_t)0x0000U) +#define LAN8742_ANLPAR_PO_SYMMETRIC_PAUSE ((uint16_t)0x0400U) +#define LAN8742_ANLPAR_PO_ASYMMETRIC_PAUSE ((uint16_t)0x0800U) +#define LAN8742_ANLPAR_PO_ADVERTISE_SUPPORT ((uint16_t)0x0C00U) +#define LAN8742_ANLPAR_100BASE_TX_FD ((uint16_t)0x0100U) +#define LAN8742_ANLPAR_100BASE_TX ((uint16_t)0x0080U) +#define LAN8742_ANLPAR_10BASE_T_FD ((uint16_t)0x0040U) +#define LAN8742_ANLPAR_10BASE_T ((uint16_t)0x0020U) +#define LAN8742_ANLPAR_SELECTOR_FIELD ((uint16_t)0x000FU) +/** + * @} + */ + +/** @defgroup LAN8742_ANER_Bit_Definition LAN8742 ANER Bit Definition + * @{ + */ +#define LAN8742_ANER_RX_NP_LOCATION_ABLE ((uint16_t)0x0040U) +#define LAN8742_ANER_RX_NP_STORAGE_LOCATION ((uint16_t)0x0020U) +#define LAN8742_ANER_PARALLEL_DETECT_FAULT ((uint16_t)0x0010U) +#define LAN8742_ANER_LP_NP_ABLE ((uint16_t)0x0008U) +#define LAN8742_ANER_NP_ABLE ((uint16_t)0x0004U) +#define LAN8742_ANER_PAGE_RECEIVED ((uint16_t)0x0002U) +#define LAN8742_ANER_LP_AUTONEG_ABLE ((uint16_t)0x0001U) +/** + * @} + */ + +/** @defgroup LAN8742_ANNPTR_Bit_Definition LAN8742 ANNPTR Bit Definition + * @{ + */ +#define LAN8742_ANNPTR_NEXT_PAGE ((uint16_t)0x8000U) +#define LAN8742_ANNPTR_MESSAGE_PAGE ((uint16_t)0x2000U) +#define LAN8742_ANNPTR_ACK2 ((uint16_t)0x1000U) +#define LAN8742_ANNPTR_TOGGLE ((uint16_t)0x0800U) +#define LAN8742_ANNPTR_MESSAGGE_CODE ((uint16_t)0x07FFU) +/** + * @} + */ + +/** @defgroup LAN8742_ANNPRR_Bit_Definition LAN8742 ANNPRR Bit Definition + * @{ + */ +#define LAN8742_ANNPTR_NEXT_PAGE ((uint16_t)0x8000U) +#define LAN8742_ANNPRR_ACK ((uint16_t)0x4000U) +#define LAN8742_ANNPRR_MESSAGE_PAGE ((uint16_t)0x2000U) +#define LAN8742_ANNPRR_ACK2 ((uint16_t)0x1000U) +#define LAN8742_ANNPRR_TOGGLE ((uint16_t)0x0800U) +#define LAN8742_ANNPRR_MESSAGGE_CODE ((uint16_t)0x07FFU) +/** + * @} + */ + +/** @defgroup LAN8742_MMDACR_Bit_Definition LAN8742 MMDACR Bit Definition + * @{ + */ +#define LAN8742_MMDACR_MMD_FUNCTION ((uint16_t)0xC000U) +#define LAN8742_MMDACR_MMD_FUNCTION_ADDR ((uint16_t)0x0000U) +#define LAN8742_MMDACR_MMD_FUNCTION_DATA ((uint16_t)0x4000U) +#define LAN8742_MMDACR_MMD_DEV_ADDR ((uint16_t)0x001FU) +/** + * @} + */ + +/** @defgroup LAN8742_ENCTR_Bit_Definition LAN8742 ENCTR Bit Definition + * @{ + */ +#define LAN8742_ENCTR_TX_ENABLE ((uint16_t)0x8000U) +#define LAN8742_ENCTR_TX_TIMER ((uint16_t)0x6000U) +#define LAN8742_ENCTR_TX_TIMER_1S ((uint16_t)0x0000U) +#define LAN8742_ENCTR_TX_TIMER_768MS ((uint16_t)0x2000U) +#define LAN8742_ENCTR_TX_TIMER_512MS ((uint16_t)0x4000U) +#define LAN8742_ENCTR_TX_TIMER_265MS ((uint16_t)0x6000U) +#define LAN8742_ENCTR_RX_ENABLE ((uint16_t)0x1000U) +#define LAN8742_ENCTR_RX_MAX_INTERVAL ((uint16_t)0x0C00U) +#define LAN8742_ENCTR_RX_MAX_INTERVAL_64MS ((uint16_t)0x0000U) +#define LAN8742_ENCTR_RX_MAX_INTERVAL_256MS ((uint16_t)0x0400U) +#define LAN8742_ENCTR_RX_MAX_INTERVAL_512MS ((uint16_t)0x0800U) +#define LAN8742_ENCTR_RX_MAX_INTERVAL_1S ((uint16_t)0x0C00U) +#define LAN8742_ENCTR_EX_CROSS_OVER ((uint16_t)0x0002U) +#define LAN8742_ENCTR_EX_MANUAL_CROSS_OVER ((uint16_t)0x0001U) +/** + * @} + */ + +/** @defgroup LAN8742_MCSR_Bit_Definition LAN8742 MCSR Bit Definition + * @{ + */ +#define LAN8742_MCSR_EDPWRDOWN ((uint16_t)0x2000U) +#define LAN8742_MCSR_FARLOOPBACK ((uint16_t)0x0200U) +#define LAN8742_MCSR_ALTINT ((uint16_t)0x0040U) +#define LAN8742_MCSR_ENERGYON ((uint16_t)0x0002U) +/** + * @} + */ + +/** @defgroup LAN8742_SMR_Bit_Definition LAN8742 SMR Bit Definition + * @{ + */ +#define LAN8742_SMR_MODE ((uint16_t)0x00E0U) +#define LAN8742_SMR_PHY_ADDR ((uint16_t)0x001FU) +/** + * @} + */ + +/** @defgroup LAN8742_TPDCR_Bit_Definition LAN8742 TPDCR Bit Definition + * @{ + */ +#define LAN8742_TPDCR_DELAY_IN ((uint16_t)0x8000U) +#define LAN8742_TPDCR_LINE_BREAK_COUNTER ((uint16_t)0x7000U) +#define LAN8742_TPDCR_PATTERN_HIGH ((uint16_t)0x0FC0U) +#define LAN8742_TPDCR_PATTERN_LOW ((uint16_t)0x003FU) +/** + * @} + */ + +/** @defgroup LAN8742_TCSR_Bit_Definition LAN8742 TCSR Bit Definition + * @{ + */ +#define LAN8742_TCSR_TDR_ENABLE ((uint16_t)0x8000U) +#define LAN8742_TCSR_TDR_AD_FILTER_ENABLE ((uint16_t)0x4000U) +#define LAN8742_TCSR_TDR_CH_CABLE_TYPE ((uint16_t)0x0600U) +#define LAN8742_TCSR_TDR_CH_CABLE_DEFAULT ((uint16_t)0x0000U) +#define LAN8742_TCSR_TDR_CH_CABLE_SHORTED ((uint16_t)0x0200U) +#define LAN8742_TCSR_TDR_CH_CABLE_OPEN ((uint16_t)0x0400U) +#define LAN8742_TCSR_TDR_CH_CABLE_MATCH ((uint16_t)0x0600U) +#define LAN8742_TCSR_TDR_CH_STATUS ((uint16_t)0x0100U) +#define LAN8742_TCSR_TDR_CH_LENGTH ((uint16_t)0x00FFU) +/** + * @} + */ + +/** @defgroup LAN8742_SCSIR_Bit_Definition LAN8742 SCSIR Bit Definition + * @{ + */ +#define LAN8742_SCSIR_AUTO_MDIX_ENABLE ((uint16_t)0x8000U) +#define LAN8742_SCSIR_CHANNEL_SELECT ((uint16_t)0x2000U) +#define LAN8742_SCSIR_SQE_DISABLE ((uint16_t)0x0800U) +#define LAN8742_SCSIR_XPOLALITY ((uint16_t)0x0010U) +/** + * @} + */ + +/** @defgroup LAN8742_CLR_Bit_Definition LAN8742 CLR Bit Definition + * @{ + */ +#define LAN8742_CLR_CABLE_LENGTH ((uint16_t)0xF000U) +/** + * @} + */ + +/** @defgroup LAN8742_IMR_ISFR_Bit_Definition LAN8742 IMR ISFR Bit Definition + * @{ + */ +#define LAN8742_INT_8 ((uint16_t)0x0100U) +#define LAN8742_INT_7 ((uint16_t)0x0080U) +#define LAN8742_INT_6 ((uint16_t)0x0040U) +#define LAN8742_INT_5 ((uint16_t)0x0020U) +#define LAN8742_INT_4 ((uint16_t)0x0010U) +#define LAN8742_INT_3 ((uint16_t)0x0008U) +#define LAN8742_INT_2 ((uint16_t)0x0004U) +#define LAN8742_INT_1 ((uint16_t)0x0002U) +/** + * @} + */ + +/** @defgroup LAN8742_PHYSCSR_Bit_Definition LAN8742 PHYSCSR Bit Definition + * @{ + */ +#define LAN8742_PHYSCSR_AUTONEGO_DONE ((uint16_t)0x1000U) +#define LAN8742_PHYSCSR_HCDSPEEDMASK ((uint16_t)0x001CU) +#define LAN8742_PHYSCSR_10BT_HD ((uint16_t)0x0004U) +#define LAN8742_PHYSCSR_10BT_FD ((uint16_t)0x0014U) +#define LAN8742_PHYSCSR_100BTX_HD ((uint16_t)0x0008U) +#define LAN8742_PHYSCSR_100BTX_FD ((uint16_t)0x0018U) +/** + * @} + */ + +/** @defgroup LAN8742_Status LAN8742 Status + * @{ + */ + +#define LAN8742_STATUS_READ_ERROR ((int32_t)-5) +#define LAN8742_STATUS_WRITE_ERROR ((int32_t)-4) +#define LAN8742_STATUS_ADDRESS_ERROR ((int32_t)-3) +#define LAN8742_STATUS_RESET_TIMEOUT ((int32_t)-2) +#define LAN8742_STATUS_ERROR ((int32_t)-1) +#define LAN8742_STATUS_OK ((int32_t) 0) +#define LAN8742_STATUS_LINK_DOWN ((int32_t) 1) +#define LAN8742_STATUS_100MBITS_FULLDUPLEX ((int32_t) 2) +#define LAN8742_STATUS_100MBITS_HALFDUPLEX ((int32_t) 3) +#define LAN8742_STATUS_10MBITS_FULLDUPLEX ((int32_t) 4) +#define LAN8742_STATUS_10MBITS_HALFDUPLEX ((int32_t) 5) +#define LAN8742_STATUS_AUTONEGO_NOTDONE ((int32_t) 6) +/** + * @} + */ + +/** @defgroup LAN8742_IT_Flags LAN8742 IT Flags + * @{ + */ +#define LAN8742_WOL_IT LAN8742_INT_8 +#define LAN8742_ENERGYON_IT LAN8742_INT_7 +#define LAN8742_AUTONEGO_COMPLETE_IT LAN8742_INT_6 +#define LAN8742_REMOTE_FAULT_IT LAN8742_INT_5 +#define LAN8742_LINK_DOWN_IT LAN8742_INT_4 +#define LAN8742_AUTONEGO_LP_ACK_IT LAN8742_INT_3 +#define LAN8742_PARALLEL_DETECTION_FAULT_IT LAN8742_INT_2 +#define LAN8742_AUTONEGO_PAGE_RECEIVED_IT LAN8742_INT_1 +/** + * @} + */ + +/** + * @} + */ + +/* Exported types ------------------------------------------------------------*/ +/** @defgroup LAN8742_Exported_Types LAN8742 Exported Types + * @{ + */ +typedef int32_t (*lan8742_Init_Func) (void); +typedef int32_t (*lan8742_DeInit_Func) (void); +typedef int32_t (*lan8742_ReadReg_Func) (uint32_t, uint32_t, uint32_t *); +typedef int32_t (*lan8742_WriteReg_Func) (uint32_t, uint32_t, uint32_t); +typedef int32_t (*lan8742_GetTick_Func) (void); + +typedef struct +{ + lan8742_Init_Func Init; + lan8742_DeInit_Func DeInit; + lan8742_WriteReg_Func WriteReg; + lan8742_ReadReg_Func ReadReg; + lan8742_GetTick_Func GetTick; +} lan8742_IOCtx_t; + + +typedef struct +{ + uint32_t DevAddr; + uint32_t Is_Initialized; + lan8742_IOCtx_t IO; + void *pData; +}lan8742_Object_t; +/** + * @} + */ + +/* Exported macro ------------------------------------------------------------*/ +/* Exported functions --------------------------------------------------------*/ +/** @defgroup LAN8742_Exported_Functions LAN8742 Exported Functions + * @{ + */ +int32_t LAN8742_RegisterBusIO(lan8742_Object_t *pObj, lan8742_IOCtx_t *ioctx); +int32_t LAN8742_Init(lan8742_Object_t *pObj); +int32_t LAN8742_DeInit(lan8742_Object_t *pObj); +int32_t LAN8742_DisablePowerDownMode(lan8742_Object_t *pObj); +int32_t LAN8742_EnablePowerDownMode(lan8742_Object_t *pObj); +int32_t LAN8742_StartAutoNego(lan8742_Object_t *pObj); +int32_t LAN8742_GetLinkState(lan8742_Object_t *pObj); +int32_t LAN8742_SetLinkState(lan8742_Object_t *pObj, uint32_t LinkState); +int32_t LAN8742_EnableLoopbackMode(lan8742_Object_t *pObj); +int32_t LAN8742_DisableLoopbackMode(lan8742_Object_t *pObj); +int32_t LAN8742_EnableIT(lan8742_Object_t *pObj, uint32_t Interrupt); +int32_t LAN8742_DisableIT(lan8742_Object_t *pObj, uint32_t Interrupt); +int32_t LAN8742_ClearIT(lan8742_Object_t *pObj, uint32_t Interrupt); +int32_t LAN8742_GetITStatus(lan8742_Object_t *pObj, uint32_t Interrupt); +/** + * @} + */ + +#ifdef __cplusplus +} +#endif +#endif /* LAN8742_H */ + + +/** + * @} + */ + +/** + * @} + */ + +/** + * @} + */ + +/** + * @} + */ diff --git a/src/class/cdc/cdc_host.c b/src/class/cdc/cdc_host.c index 35717ddf61..53ed49f28a 100644 --- a/src/class/cdc/cdc_host.c +++ b/src/class/cdc/cdc_host.c @@ -781,12 +781,15 @@ bool cdch_set_config(uint8_t daddr, uint8_t itf_num) { TU_ASSERT(p_cdc && p_cdc->serial_drid < SERIAL_DRIVER_COUNT); TU_LOG_CDC(p_cdc, "set config"); - // fake transfer to kick-off process_set_config() - tuh_xfer_t xfer; - xfer.daddr = daddr; - xfer.result = XFER_RESULT_SUCCESS; - xfer.setup = &request; - xfer.user_data = 0; // initial state 0 + // fake transfer to kick-off process_set_config(). Designated init + // so future tuh_xfer_t fields (e.g. timeout_ms) are zero-initialised + // rather than holding stack canary garbage. + tuh_xfer_t xfer = { + .daddr = daddr, + .result = XFER_RESULT_SUCCESS, + .setup = &request, + .user_data = 0, // initial state 0 + }; cdch_process_set_config(&xfer); return true; diff --git a/src/class/hid/hid_host.c b/src/class/hid/hid_host.c index da776d04c0..eb6010e155 100644 --- a/src/class/hid/hid_host.c +++ b/src/class/hid/hid_host.c @@ -583,11 +583,14 @@ bool hidh_set_config(uint8_t daddr, uint8_t itf_num) { tusb_control_request_t request; request.wIndex = tu_htole16((uint16_t) itf_num); - tuh_xfer_t xfer; - xfer.daddr = daddr; - xfer.result = XFER_RESULT_SUCCESS; - xfer.setup = &request; - xfer.user_data = CONFG_SET_IDLE; + // Designated init so future tuh_xfer_t fields (e.g. timeout_ms) are + // zero-initialised rather than holding stack canary garbage. + tuh_xfer_t xfer = { + .daddr = daddr, + .result = XFER_RESULT_SUCCESS, + .setup = &request, + .user_data = CONFG_SET_IDLE, + }; // fake request to kick-off the set config process process_set_config(&xfer); diff --git a/src/host/hcd.h b/src/host/hcd.h index 36a7f5da5d..43bdf174c3 100644 --- a/src/host/hcd.h +++ b/src/host/hcd.h @@ -165,8 +165,32 @@ bool hcd_edpt_close(uint8_t rhport, uint8_t daddr, uint8_t ep_addr); // Submit a transfer, when complete hcd_event_xfer_complete() must be invoked bool hcd_edpt_xfer(uint8_t rhport, uint8_t daddr, uint8_t ep_addr, uint8_t * buffer, uint16_t buflen); -// Abort a queued transfer. Note: it can only abort transfer that has not been started -// Return true if a queued transfer is aborted, false if there is no transfer to abort +// Abort the in-flight transfer (if any) on the given endpoint. +// +// Return true if a transfer was aborted, false if there is no +// transfer to abort. +// +// Contract (host stack relies on this for clean teardown of timed-out +// or unlinked URBs): +// +// - On success the HCD MUST cause hcd_event_xfer_complete to fire +// for the aborted transfer (typically with XFER_RESULT_FAILED). +// Callers that need a giveback for the abort can rely on the +// natural completion path; without this guarantee they would have +// to synthesise a completion externally and risk double-free or +// stuck EP claims. +// +// - For EP0 the upper-layer control state machine +// (_usbh_data.ctrl_xfer_info.stage) must be parked by the caller +// BEFORE this is invoked; the synthetic completion will land in +// _control_xfer_complete which sets stage IDLE and fires the +// user callback. tuh_control_xfer's timeout path does this. +// +// - The synopsys/dwc2 implementation honours this contract as of +// https://github.com/hathach/tinyusb/pull/. Other ports +// (max3421e, rp2040, ehci, ohci, wch, musb, rusb2) may still +// behave per the older "queued transfer only, no callback" +// contract; cross-port audit pending. bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr); // Submit a special transfer to send 8-byte Setup Packet, when complete hcd_event_xfer_complete() must be invoked diff --git a/src/host/usbh.c b/src/host/usbh.c index 7340247719..bf32a48a46 100644 --- a/src/host/usbh.c +++ b/src/host/usbh.c @@ -721,7 +721,6 @@ static void _control_blocking_complete_cb(tuh_xfer_t* xfer) { *((xfer_result_t*) xfer->user_data) = xfer->result; } -// TODO timeout_ms is not supported yet bool tuh_control_xfer (tuh_xfer_t* xfer) { TU_VERIFY(xfer->ep_addr == 0 && xfer->setup); // EP0 with setup packet const uint8_t daddr = xfer->daddr; @@ -764,13 +763,57 @@ bool tuh_control_xfer (tuh_xfer_t* xfer) { TU_ASSERT(usbh_setup_send(daddr, (uint8_t const *) &_usbh_epbuf.request)); + // Honour xfer->timeout_ms. timeout_ms == 0 preserves the historical + // "wait forever" default for callers that do not initialise the + // field. A non-zero timeout returns false with xfer->result = + // XFER_RESULT_FAILED if the device does not complete the control + // transfer in time. The caller is responsible for any further + // recovery (e.g. hcd_edpt_abort_xfer) on the EP0 channel. + const uint32_t timeout_ms_local = xfer->timeout_ms; + const uint32_t start_ms = tusb_time_millis_api(); + while (result == XFER_RESULT_INVALID) { // Note: this can be called within an callback ie. part of tuh_task() // therefore event with RTOS tuh_task() still need to be invoked if (tuh_task_event_ready()) { tuh_task(); } - // TODO probably some timeout to prevent hanged + if (timeout_ms_local > 0 && (tusb_time_millis_api() - start_ms) >= timeout_ms_local) { + // Timeout. Tear down the in-flight control xfer so a subsequent + // control xfer on the same daddr can proceed. + // + // Lock policy: a 100 ms bounded wait. On NO_OS this lock is + // uncontended (the bus task and the recovery path share the + // same thread); under RTOS, a wedged bus task should not block + // the recovery itself. Skip the cleanup and continue tearing + // down on lock failure - the worst case is a stray late + // completion calling _control_xfer_complete with cb=NULL, + // which is a no-op. + const bool got_lock = osal_mutex_lock(_usbh_mutex, 100); + if (got_lock) { + if (ctrl_info->stage != CONTROL_STAGE_IDLE) { + ctrl_info->stage = CONTROL_STAGE_IDLE; + ctrl_info->complete_cb = NULL; // any late completion no-ops + ctrl_info->user_data = 0; + } + (void) osal_mutex_unlock(_usbh_mutex); + } + // Disable the EP0 channel so the hardware doesn't deliver a + // late completion in flight. With PR3 the natural completion + // callback fires after channel halt; it lands in + // _control_xfer_complete with cb=NULL (cleared above) and is + // dropped. If hcd_edpt_abort_xfer returns false (no in-flight + // channel) there is nothing to drop. We do not reach into + // tuh_edpt_abort_xfer because that function rejects calls + // when stage is already IDLE. + hcd_edpt_abort_xfer(usbh_get_rhport(daddr), daddr, 0); + if (xfer->user_data != 0) { + *((xfer_result_t*) xfer->user_data) = XFER_RESULT_FAILED; + } + xfer->result = XFER_RESULT_FAILED; + xfer->actual_len = 0; + return false; + } } // update transfer result, user_data is expected to point to xfer_result_t @@ -1460,11 +1503,14 @@ static bool enum_new_device(hcd_event_t* event) { dev0_bus->speed = hcd_port_speed_get(dev0_bus->rhport); TU_LOG_USBH("%s Speed\r\n", tu_str_speed[dev0_bus->speed]); - // fake transfer to kick-off the enumeration process - tuh_xfer_t xfer; - xfer.daddr = 0; - xfer.result = XFER_RESULT_SUCCESS; - xfer.user_data = ENUM_ADDR0_DEVICE_DESC; + // fake transfer to kick-off the enumeration process. Use designated + // init so any new field added to tuh_xfer_t (e.g. timeout_ms) is + // zero-initialised rather than holding stack canary garbage. + tuh_xfer_t xfer = { + .daddr = 0, + .result = XFER_RESULT_SUCCESS, + .user_data = ENUM_ADDR0_DEVICE_DESC, + }; process_enumeration(&xfer); } #if CFG_TUH_HUB diff --git a/src/host/usbh.h b/src/host/usbh.h index 4b67478488..1905dedd2e 100644 --- a/src/host/usbh.h +++ b/src/host/usbh.h @@ -70,7 +70,19 @@ struct tuh_xfer_s { tuh_xfer_cb_t complete_cb; uintptr_t user_data; - // uint32_t timeout_ms; // place holder, not supported yet + // Synchronous control transfer timeout in milliseconds. 0 means + // wait forever (preserves the historical default for callers that + // do not initialise this field). Currently honoured only by + // tuh_control_xfer with complete_cb == NULL. + // + // Callers using timeout_ms != 0 MUST inspect xfer->result rather + // than the function's bool return for a non-fatal timeout. The + // bool return is now overloaded with TU_VERIFY-style programming + // errors (precondition false) AND runtime timeouts; only the + // latter sets xfer->result = XFER_RESULT_FAILED. timeout_ms == 0 + // callers are unaffected because the timeout branch is unreachable + // for them. + uint32_t timeout_ms; }; // Subject to change diff --git a/src/portable/synopsys/dwc2/hcd_dwc2.c b/src/portable/synopsys/dwc2/hcd_dwc2.c index b924486856..e1d1024f21 100644 --- a/src/portable/synopsys/dwc2/hcd_dwc2.c +++ b/src/portable/synopsys/dwc2/hcd_dwc2.c @@ -98,6 +98,7 @@ typedef struct { uint8_t period_split_nyet_count : 3; uint8_t halted_nyet : 1; uint8_t closing : 1; // closing channel + uint8_t aborted : 1; // hcd_edpt_abort_xfer marked this channel for abort }; uint8_t result; @@ -721,8 +722,23 @@ bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t * return edpt_xfer_kickoff(dwc2, ep_id); } -// Abort a queued transfer. Note: it can only abort transfer that has not been started -// Return true if a queued transfer is aborted, false if there is no transfer to abort +// Abort the in-flight transfer (if any) on the given endpoint. The channel +// is disabled, which causes the channel-halted interrupt to fire; the IRQ +// handler observes xfer->aborted and reports completion via +// hcd_event_xfer_complete with XFER_RESULT_FAILED so the caller always +// receives a callback. Returns true if an in-flight transfer was aborted, +// false if no channel was active for this endpoint. +// +// EP0 special case: aborting EP0 mid-control-stage does not unwind +// _control_xfer_complete's retry/decrement logic on its own. Callers +// must park the upper-layer ctrl_info->stage to IDLE before invoking +// (tuh_control_xfer's timeout path does this). +// +// The flag-set + channel_disable sequence is bracketed with +// hcd_int_disable/enable so a real completion firing in the same +// instant cannot land in handle_channel_irq with the abort flag +// already set, which would let the IRQ overwrite the genuine result +// with XFER_RESULT_FAILED. bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) { dwc2_regs_t* dwc2 = DWC2_REG(rhport); const uint8_t ep_num = tu_edpt_number(ep_addr); @@ -730,17 +746,27 @@ bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) { const uint8_t ep_id = edpt_find_opened(dev_addr, ep_num, ep_dir); TU_VERIFY(ep_id < CFG_TUH_DWC2_ENDPOINT_MAX); - // hcd_int_disable(rhport); + hcd_int_disable(rhport); - // Find enabled channeled and disable it, channel will be de-allocated in the interrupt handler + // Find enabled channel and disable it. The channel will be de-allocated + // in the interrupt handler after the channel-halted interrupt fires. const uint8_t ch_id = channel_find_enabled(dwc2, dev_addr, ep_num, ep_dir); - if (ch_id < 16) { - dwc2_channel_t* channel = &dwc2->channel[ch_id]; - channel_disable(dwc2, channel); + if (ch_id >= 16) { + hcd_int_enable(rhport); + return false; // no in-flight transfer } - // hcd_int_enable(rhport); + hcd_xfer_t* xfer = &_hcd_data.xfer[ch_id]; + dwc2_channel_t* channel = &dwc2->channel[ch_id]; + + // Mark the xfer as aborted before disabling the channel so the IRQ + // handler's channel_irq path reports completion with XFER_RESULT_FAILED + // rather than treating the halt as a no-op. + xfer->aborted = 1; + xfer->result = XFER_RESULT_FAILED; + channel_disable(dwc2, channel); + hcd_int_enable(rhport); return true; } @@ -883,9 +909,6 @@ static void handle_rxflvl_irq(uint8_t rhport) { // return true if there is still pending data and need more ISR static bool handle_txfifo_empty(dwc2_regs_t* dwc2, bool is_periodic) { - // Use period txsts for both p/np to get request queue space available (1-bit difference, it is small enough) - const dwc2_hptxsts_t txsts = {.value = (is_periodic ? dwc2->hptxsts : dwc2->hnptxsts)}; - const uint8_t max_channel = dwc2_channel_count(dwc2); for (uint8_t ch_id = 0; ch_id < max_channel; ch_id++) { dwc2_channel_t* channel = &dwc2->channel[ch_id]; @@ -903,6 +926,8 @@ static bool handle_txfifo_empty(dwc2_regs_t* dwc2, bool is_periodic) { // skip if there is not enough space in FIFO and RequestQueue. // Packet's last word written to FIFO will trigger a request queue + // Use period txsts for both p/np to get request queue space available (1-bit difference, it is small enough) + const dwc2_hptxsts_t txsts = {.value = (is_periodic ? dwc2->hptxsts : dwc2->hnptxsts)}; if ((xact_bytes > (txsts.fifo_available << 2)) || (txsts.req_queue_available == 0)) { return true; } @@ -1121,6 +1146,25 @@ static bool handle_channel_in_dma(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t hci const uint16_t actual_len = edpt->buflen - remain_bytes; xfer->xferred_bytes += actual_len; + // Save the post-transfer PID from the channel size register so the + // next URB on this endpoint starts with the correct data toggle. + // The slave-mode IN handler does this at the matching point (see + // handle_channel_in_slave); the DMA-mode handler was missing the + // save, so on a short-packet completion the toggle pre-computed in + // channel_xfer_start (based on the requested packet count) was + // stale, causing DATATOGGLE_ERR on the next IN URB. The hardware + // either retried (dropping the device's first packet) or coalesced + // the duplicate (delivering corrupt bytes). Save the authoritative + // post-transfer PID so the next URB matches the device's toggle. + // + // OUT direction is exempt from this save: the host is the source + // of truth for the PID toggle on OUT, so cal_next_pid() in + // channel_xfer_start (run synchronously before the next submit) + // produces the right next PID without needing a hardware readback. + // The handle_channel_out_dma / handle_channel_out_slave paths + // therefore do not save hctsiz.pid. + edpt->next_pid = hctsiz.pid; + is_done = true; if (hcint & HCINT_STALL) { @@ -1301,6 +1345,38 @@ static void handle_channel_irq(uint8_t rhport, bool in_isr) { #endif } + // hcd_edpt_abort_xfer marked this channel for abort. Force the + // halted interrupt to be treated as a terminal completion so the + // host stack receives a callback. The dispatch handlers leave + // is_done=false on a NAK retry (channel re-armed), so guard + // against firing on a re-armed channel by also requiring that + // the channel is not currently enabled. The flag is single-shot; + // clear it after observing the halt. + // + // Override note: this OVERWRITES whatever xfer->result the + // dispatch handler set above (success path included) with + // XFER_RESULT_FAILED. That is the desired behaviour for an + // unlink: the caller asked us to abort, so the giveback should + // report failure even if the natural completion happened to + // race with the abort. + // + // TODO: confirm slave-mode IN NAK retry interaction. If the + // dispatch handler can re-arm the channel BEFORE this block + // runs, the hcchar_post.enable check would skip the abort + // completion and the kernel would be left waiting for a + // RET_SUBMIT(FAILED) that never arrives. The PR3 test bench + // (cdc-acm bulk IN) didn't trip this, but a stress test on + // slave-mode IN would be the right confirmation. + if (xfer->aborted && (hcint & HCINT_HALTED)) { + const dwc2_channel_char_t hcchar_post = {.value = channel->hcchar}; + if (!hcchar_post.enable) { + is_done = true; + xfer->result = XFER_RESULT_FAILED; + xfer->aborted = 0; + } + } + + if (is_done) { if (xfer->closing == 1) { hcd_endpoint_t *edpt = &_hcd_data.edpt[xfer->ep_id]; diff --git a/src/tusb.c b/src/tusb.c index 6fb5309ab4..ca928b5c93 100644 --- a/src/tusb.c +++ b/src/tusb.c @@ -45,6 +45,13 @@ tusb_role_t _tusb_rhport_role[TUP_USBIP_CONTROLLER_NUM] = { TUSB_ROLE_INVALID }; // Weak/Default API, can be overwritten by Application //-------------------------------------------------------------------- +TU_ATTR_WEAK uint32_t tusb_time_millis_api(void) { + // BSP must override for any feature that needs a real wall clock + // (e.g. tuh_xfer_t.timeout_ms). Returning 0 makes timeout-based paths + // behave as wait-forever, matching the historical default. + return 0; +} + TU_ATTR_WEAK void tusb_time_delay_ms_api(uint32_t ms) { #if CFG_TUSB_OS != OPT_OS_NONE osal_task_delay(ms);