Skip to content

power.rack: pod-hosted TFTP staging client#92

Merged
widgetii merged 1 commit into
masterfrom
rack-tftp-host
May 11, 2026
Merged

power.rack: pod-hosted TFTP staging client#92
widgetii merged 1 commit into
masterfrom
rack-tftp-host

Conversation

@widgetii
Copy link
Copy Markdown
Member

Summary

Adds RackController.tftp_put / tftp_delete / tftp_clear / tftp_list — the host-side wrappers for the pod's /tftp/* endpoints. Lets defib stage firmware bytes into the pod's PSRAM, then have the camera's U-Boot fetch from the pod itself (192.168.1.1:69) over the local camera-side LAN.

Why it matters: defib's existing U-Boot+TFTP path requires a host-side TFTP server on port 69 — conflicts with dnsmasq, needs sudo, needs NIC IP plumbing on the camera's subnet. Pod-hosted TFTP gets rid of every one of those: zero host setup, the pod talks to the camera on its own local LAN.

End-to-end measurement

OpenIPC hi3516ev300-nor-neo install (kernel 2.0 MB + rootfs 4.2 MB):

# Method Total Stage Flash Host setup
1 Agent write_flash 92 s 77 s none
2 Host TFTP + sf write 132 s 100 s sudo + dnsmasq + NIC IP
3 Pod TFTP + sf write 193 s 50 s 96 s none

Pod TFTP is slower than host TFTP because the bytes traverse WiFi twice (host upload + pod serve) instead of once (camera-pulls-through-NAPT). It wins on operational simplicity vs Method 2 (no sudo, no port-69 conflict, no NIC plumbing) and is the natural fallback when the agent path isn't usable for a given SoC.

The PSRAM cap on the prototype N8R8 is 8 MB, comfortably holding both kernel + rootfs staged simultaneously.

Companion firmware change

Local-only branch on the rack repo (not on GitHub per project policy): adds tftpd.c — a tiny RFC 1350 TFTP server bound to the W5500 interface, plus POST /tftp/<name> HTTP staging endpoints. Stores files in PSRAM via zero-copy ownership transfer (tftpd_add_file_owned) to avoid the double-buffer OOM that the initial naïve impl hit on a 4 MB rootfs.

Tests

6 new TestTftpStaging cases:

  • tftp_put posts correct URL and body
  • tftp_put rejects path-traversal / empty names
  • tftp_delete one file
  • tftp_clear all
  • tftp_list returns the JSON dict
  • 503 OOM from the pod surfaces as PowerControllerError verbatim (callers can fall back)

Test plan

  • uv run pytest tests/ -x -v --ignore=tests/fuzz486 passed / 2 skipped
  • uv run ruff check tests/ src/defib/
  • uv run mypy src/defib/power/rack.py --ignore-missing-imports

🤖 Generated with Claude Code

Adds RackController.tftp_put / tftp_delete / tftp_clear / tftp_list —
the host-side wrappers for the pod's `/tftp/*` endpoints. Lets defib
stage firmware bytes into the pod's PSRAM, then have the camera's
U-Boot fetch from the pod itself (192.168.1.1:69) over the local
camera-side LAN.

Why this matters: defib install's existing U-Boot+TFTP path requires
a host-side TFTP server (port 69 — conflicts with dnsmasq, needs sudo,
needs NIC IP plumbing). Pod-hosted TFTP gets rid of every one of those.
The trade-off is that firmware bytes traverse WiFi once (host → pod
HTTP POST) before being served to the camera over Eth, vs the direct
camera-pulls-through-NAPT path which only crosses WiFi once but on
the slower path (camera → pod → host).

Companion to a rack firmware change (local-only branch) that adds the
`tftpd.c` server on the W5500 interface and the `POST /tftp/<name>`
HTTP staging endpoints.

End-to-end measurement on hi3516ev300 nor-neo install (kernel 2.0 MB
+ rootfs 4.2 MB):

  Method                  Total   Stage    Flash   Host setup needed
  -----------------------+-------+--------+-------+--------------------
  1. Agent write_flash    92 s    —        77 s   none (just /fastboot)
  2. Host TFTP + sf write 132 s   —       100 s   sudo + dnsmasq + NIC
  3. Pod TFTP + sf write  193 s   50 s     96 s   none
  -----------------------+-------+--------+-------+--------------------

Pod TFTP is slower than host TFTP because the bytes traverse WiFi
twice (host upload + pod serve) instead of once. It wins on
operational simplicity vs Method 2 (no sudo, no port-69 conflict,
no NIC plumbing) and is the natural fallback when the agent path
isn't usable for a given SoC.

6 new tests in TestTftpStaging:
  - tftp_put posts correct URL and body
  - tftp_put rejects path-traversal / empty names
  - tftp_delete one file
  - tftp_clear all
  - tftp_list returns the JSON dict
  - 503 OOM from the pod surfaces as PowerControllerError verbatim

Suite: 486 passed / 2 skipped; ruff + mypy clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@widgetii widgetii merged commit 2c5083b into master May 11, 2026
13 checks passed
@widgetii widgetii deleted the rack-tftp-host branch May 11, 2026 21:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant