Conversation
|
@copilot please update the build workflow to build with tft, without tft, and with net |
|
@ramseymcgrath I've opened a new pull request, #4, to work on those changes. Once the pull request is ready, I'll request review from you. |
There was a problem hiding this comment.
Pull request overview
Adds a NET-enabled mode to the Teensy 4.1 USB proxy to accept KMBox Net commands over Ethernet (UDP), while also improving USB control-request passthrough behavior and expanding on-device stats/telemetry for the new mode.
Changes:
- Introduces a bare-metal ENET + minimal UDP/IP/ARP stack plus a
kmnetprotocol handler gated byNET_ENABLED. - Updates USB host control transfers to support caller-provided timeouts and adds an async “fire-and-forget” control OUT path used by deferred passthrough in the USB device stack.
- Extends TFT stats for network state and adjusts smoothing/humanization configuration/behavior.
Reviewed changes
Copilot reviewed 21 out of 22 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| tools/usb_passthrough_test.c | Adds a libusb-based host-side passthrough test utility. |
| tools/usb_passthrough_test | Adds a prebuilt binary artifact (should not be committed). |
| src/usb_host.h | Extends control transfer API with timeout + async control OUT API. |
| src/usb_host.c | Implements timeout-based control transfers and async control OUT + busy check. |
| src/usb_device.c | Adds EP0 RX buffer + deferred control OUT passthrough forwarding. |
| src/udp.h | Declares a minimal UDP stack API for NET mode. |
| src/udp.c | Implements minimal ARP/IPv4/UDP/ICMP handling on top of ENET. |
| src/enet.h | Declares bare-metal ENET + DP83825I PHY driver API. |
| src/enet.c | Implements polled ENET RX/TX and PHY init/link polling. |
| src/kmnet.h | Declares KMBox Net UDP protocol handler API. |
| src/kmnet.c | Implements KMBox Net command parsing/dispatch via UDP. |
| src/main.c | Wires NET mode init/polling and publishes network stats to TFT. |
| src/tft_display.h | Adds network-related fields to the TFT stats struct (NET mode). |
| src/tft_display.c | Renders NET stats (IP/port/link/UUID/RX/TX) and adjusts layout. |
| src/kmbox.h | Exposes public injection APIs for alternate transports (UART/NET). |
| src/kmbox.c | Adds NET-mode init/poll behavior and implements injection helpers. |
| src/smooth_config.h | Retunes smoothing constants and adds tremor/dither configuration. |
| src/smooth.c | Implements persistent micro-tremor + dithered rounding in humanize mode. |
| src/desc_capture.c | Updates all host control transfer calls for new timeout parameter. |
| core/startup.c | Expands non-cacheable DMA MPU overlay size when NET is enabled. |
| build.sh | Adds interactive build selection for UART vs Ethernet (NET) mode. |
| Makefile | Adds NET build flag, sources, and hot-path optimization list updates. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Version must be 4, IHL must be 5 (no options) | ||
| if ((ip[0] >> 4) != 4) return; | ||
| uint8_t ihl = (ip[0] & 0x0F) * 4; | ||
| if (ihl < 20) return; |
There was a problem hiding this comment.
The code comments say “IHL must be 5 (no options)”, and handle_icmp() assumes a fixed 20-byte IP header (ip_hdr + 20), but handle_ipv4() currently allows ihl > 20. Either enforce ihl == 20 (drop packets with options) or update downstream parsing to use ihl everywhere.
| if (ihl < 20) return; | |
| if (ihl != 20) return; |
| int32_t state = rd32le_s(data + 16); // 1=press, 0=release | ||
| // Get current buttons, set/clear this bit | ||
| uint8_t buttons = state ? button_bit : 0; | ||
| kmbox_inject_mouse(0, 0, buttons, 0, false); | ||
| send_response(hdr, NULL, 0); |
There was a problem hiding this comment.
Button handling doesn’t preserve existing button state: buttons = state ? button_bit : 0 clears all other buttons and releases all buttons on release. Track a current button-mask in kmnet (set/clear only button_bit) and pass the full mask into kmbox_inject_mouse() so simultaneous button presses work.
| void kmnet_init(void) | ||
| { | ||
| // Initialize Ethernet hardware | ||
| if (!enet_init()) { | ||
| // PHY not detected — this is fatal in NET mode | ||
| // Main will show error on TFT | ||
| return; | ||
| } | ||
|
|
||
| device_uuid = generate_uuid(); | ||
| client_connected = false; | ||
| rx_count = 0; | ||
| tx_count = 0; | ||
| link_status = false; | ||
| last_link_check = 0; | ||
|
|
||
| udp_init(KMNET_IP, KMNET_NETMASK, KMNET_GATEWAY); | ||
| } |
There was a problem hiding this comment.
kmnet_init() returns early if enet_init() fails, but main() will still call kmnet_poll() in NET mode. Since ENET/UDP state isn’t initialized in this failure path, kmnet_poll() can end up calling into udp_poll()/enet_rx() with uninitialized rings/pointers. Add a kmnet_ready flag (or make kmnet_init() return bool) and gate kmnet_poll()/stats on successful init.
|
|
||
| if (is_in) { | ||
| // Device-to-host: forward to real device, return response. | ||
| // Wait for any in-flight fire-and-forget to finish first. | ||
| while (usb_host_control_async_busy()) ; | ||
| int ret = usb_host_control_transfer(cap_desc->dev_addr, | ||
| cap_desc->ep0_maxpkt, setup, ep0_rx_buf, 200); |
There was a problem hiding this comment.
IN passthrough can overflow ep0_rx_buf: usb_host_control_transfer() may copy up to setup->wLength bytes into the provided buffer, but ep0_rx_buf is only 512 bytes and setup->wLength is not capped. Consider copying *setup to a local, clamp wLength to sizeof(ep0_rx_buf), and use that for the host transfer + device response.
| if (is_in) { | |
| // Device-to-host: forward to real device, return response. | |
| // Wait for any in-flight fire-and-forget to finish first. | |
| while (usb_host_control_async_busy()) ; | |
| int ret = usb_host_control_transfer(cap_desc->dev_addr, | |
| cap_desc->ep0_maxpkt, setup, ep0_rx_buf, 200); | |
| usb_setup_t local_setup = *setup; | |
| if (is_in) { | |
| // Device-to-host: forward to real device, return response. | |
| // Clamp requested length to our local EP0 RX buffer size. | |
| if (local_setup.wLength > sizeof(ep0_rx_buf)) { | |
| local_setup.wLength = (uint16_t)sizeof(ep0_rx_buf); | |
| } | |
| // Wait for any in-flight fire-and-forget to finish first. | |
| while (usb_host_control_async_busy()) ; | |
| int ret = usb_host_control_transfer(cap_desc->dev_addr, | |
| cap_desc->ep0_maxpkt, &local_setup, ep0_rx_buf, 200); |
| ep0_stall(); | ||
| return; | ||
| } | ||
| ep0_tx_data(ep0_rx_buf, (uint16_t)ret); |
There was a problem hiding this comment.
For IN passthrough with wLength == 0, ep0_tx_data(..., 0) won’t prime EP0 RX for the STATUS OUT stage (the RX priming in ep0_tx_data() only happens when len > 0). Vendor/device IN requests with zero-length data could therefore hang. Handle the wLength == 0 case explicitly by priming EP0 RX for the status phase (or adjust ep0_tx_data() to optionally prime RX based on request direction).
| ep0_tx_data(ep0_rx_buf, (uint16_t)ret); | |
| if (wLength == 0) { | |
| // IN request with no data phase: only STATUS OUT stage remains. | |
| // Prime EP0 RX explicitly for the status stage instead of calling | |
| // ep0_tx_data(..., 0), which does not prime RX when len == 0. | |
| (void)ep0_rx_data(0); | |
| } else { | |
| ep0_tx_data(ep0_rx_buf, (uint16_t)ret); | |
| } |
| # Pass NET=1 to enable Ethernet (KMBox Net UDP protocol, replaces UART commands) | ||
| NET ?= 0 | ||
|
|
||
| DEFINES = -DARDUINO_TEENSY41 -D__IMXRT1062__ -DF_CPU=816000000 -DUART_ENABLED=$(UART) \ | ||
| -DUART_BAUD=$(UART_BAUD) -DTFT_ENABLED=$(TFT) -DTFT_DRIVER=$(TFT_DRIVER) \ | ||
| -DTOUCH_ENABLED=$(TOUCH) -DUART_AUTOBAUD=$(UART_AUTOBAUD) | ||
| -DTOUCH_ENABLED=$(TOUCH) -DUART_AUTOBAUD=$(UART_AUTOBAUD) \ | ||
| -DNET_ENABLED=$(NET) |
There was a problem hiding this comment.
Enabling NET compiles and exposes the KMBox Net UDP protocol (src/enet.c, src/udp.c, src/kmnet.c), but that implementation currently has effectively no strong authentication: any host that knows the 32‑bit device_uuid can send KMBox Net commands. Because this UUID is derived from the MAC, displayed on the TFT, and reused as a static token, a network attacker who learns or guesses it can connect and then inject arbitrary keyboard/mouse events or trigger device reboots over UDP. Before enabling this mode by default, you should harden the KMBox Net protocol with real per-device or per-session secrets, bind commands to the original client IP/port, and add message authentication so only an authorized peer can control the device.
Co-authored-by: ramseymcgrath <2006510+ramseymcgrath@users.noreply.github.com>
Build matrix: with-tft, without-tft, with-net
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
No description provided.