LAN7800 EEPROM Programmer
Public Made by Adomby adom
MPLAB-Connect-parity EEPROM tool for the Microchip LAN7800 USB-3.1 to Gigabit-Ethernet controller (VID 0x0424 / PID 0x7800). Read an adapter's EEPROM to a .bin, edit MAC / VID / PID / bcdDevice / strings / serial and LED/GPIO with a byte-exact preview, and program hardware with verify-after-write. Reads are free; physical writes are gated behind --force-physical-write. Ships an AI-oriented CLI (single source of truth) plus a Hydrogen webapp that shells out to it.
LAN7800 EEPROM Programmer (Core v1)
MPLAB-Connect-parity EEPROM tool for the Microchip LAN7800 USB-3.1→GbE
controller (VID 0x0424 / PID 0x7800 only). Read the EEPROM to a .bin,
edit MAC / VID / PID / bcdDevice / strings / serial with a byte-exact preview,
and program a physical adapter with verify-after-write.


lan7800-programmer/
docs/ register map, .bin layout, MPLAB parity checklist (read first)
cli/ the engine — pluggable backends + AI-oriented CLI (single source of truth)
webapp/ Hydrogen app; shells out to the CLI (holds no EEPROM logic)
Access method (decided)
Primary backend is the in-tree lan78xx driver via ethtool, exactly what
Microchip's own "LAN78xx EEPROM/OTP Programming on Linux" article recommends:
- Read:
ethtool -e <iface> raw on - Write:
ethtool -E <iface> magic 0x78A5 offset <o> value <v>(one byte/call; the driver handles EWEN internally)
The backend is pluggable (cli/lan7800/backends/) so a driver-independent
libusb vendor-command path (for blank chips) can be added later without
touching field logic.
Safety model (important)
- Reads are free. Inspect, dump, preview, verify — none of these change hardware.
- Physical writes are gated. The only commands that touch an adapter are
programanderase, and both refuse unless--force-physical-writeis passed. In the Symphony mesh, the operator/agent sets that flag only after a clearedmesh escalatePROCEED. The webapp never sets it —POST /programreturns423with the exact command for a human to run. - Verify-after-write is mandatory (
programre-reads and diffs; fails loud). - Anti-brick: byte0 signature
0xA5is only written deliberately; a cleared signature is recoverable (device falls back to OTP/CSR defaults). Stay in 0–511. Keep a backup:lan7800prog read --iface <if> --out backup.bin.
CLI quickstart
cd cli
python3 lan7800prog scan # FREE — enumerate LAN7800s
python3 lan7800prog read-device --out dump.bin # FREE — connected device
# auto-picks the backend: a lan78xx netdev -> ethtool, else the driver-independent
# libusb path (needs a one-time udev rule; run `lan7800prog udev-rule` to print it)
python3 lan7800prog new --out base.bin # default LAN7800 template (or --blank)
python3 lan7800prog read --iface enx00800f780000 --out dump.bin # FREE (specific iface)
python3 lan7800prog info --in dump.bin # parse & print (fields, config flags, LED/GPIO)
python3 lan7800prog --json info --iface enp0s20u1 # machine-readable
# edit fields -> new .bin (no hardware), then preview
python3 lan7800prog edit --in base.bin --out out.bin \
--mac 00:80:0F:12:34:56 --vid 0x0424 --pid 0x7800 --serial SN0001 \
--set-string product="LAN7800 GbE" --ensure-signature
# LED / GPIO (offsets authoritative; per-bit encoding VERIFY vs datasheet §15)
python3 lan7800prog edit --in base.bin --out out.bin \
--set-led 0=10 --led-enable 0=1 --led-blink 2 \
--set-gpio 3=out,push-pull,1 --gpio-wake 3=1,1 \
--set-raw 0x48=01 # authoritative raw-byte fallback
# compare a device against an image (FREE)
python3 lan7800prog verify --iface enp0s20u1 --image out.bin
# GATED — only after a mesh-escalate PROCEED:
python3 lan7800prog program --iface enp0s20u1 --image out.bin --force-physical-write
Every command supports --json (machine output) and otherwise prints
OK: / ERROR: + Hint: lines.
Webapp
cd webapp && PORT=8747 python3 server.py # http://127.0.0.1:8747
Scan for devices enumerates attached LAN7800s (pick one to target). Load
is a native file picker (uploads the .bin to the server). Edit MAC/VID/PID/
strings/serial and the LED/GPIO panel, watch the live byte-exact preview, save.
Program to device is intentionally gated and returns the command to run under
escalation. State is on the server (GET /state, GET /console, GET /scan) so
it is AI-drivable.
Tests (offline, no hardware)
cd cli && python3 tests/test_eeprom.py # 10/10 field round-trips on a synthetic .bin
Field layout & VERIFY caveats
Layout follows docs/01-eeprom-mechanism.md (datasheet DS00001992G Table 10-2 +
lan78xx.c). Two assumptions are flagged for confirmation against a real dump
and centralized in cli/lan7800/eeprom.py so they flip in one place:
- MAC octet order (
MAC_LITTLE_ENDIAN) — stored little-endian per the doc. - VID/PID location — inside the SS/HS/FS USB device-descriptor blocks pointed
to by the word-unit pointers at
0x31/0x35/0x39; writes hit all present blocks.
Scope
Core v1 = read→dump, edit MAC/VID/PID/bcdDevice/strings/serial, signature 0xA5,
verify-after-write, load/save raw .bin, device scan, file-picker load,
and LED/GPIO editing (LED per-pin enable + 16 modes + blink; GPIO direction/
drive/output/wake). LED/GPIO byte offsets are authoritative (LED 0x0B–0x0D,
0x58–0x59; GPIO 0x07/0x0E/0x48–0x4F) but the per-bit encoding is
best-effort — confirm against datasheet §15 or a real dump; a --set-raw
byte-level fallback is always available. If a USB descriptor block is pointed
into one of these fixed register ranges, info reports the collision and named
LED/GPIO writes to the colliding byte are blocked (use --set-raw only if
you truly intend it) so a descriptor is never silently corrupted. Not in v1 (see
docs/02-mplab-connect-parity.md → ADVANCED): OTP, MAC ranges / mass production,
PME/wake-frame filters, string-heap relocation (v1 edits strings in place only),
INI/TOML source format.
# LAN7800 EEPROM Programmer (Core v1)
MPLAB-Connect-parity EEPROM tool for the **Microchip LAN7800** USB-3.1→GbE
controller (**VID 0x0424 / PID 0x7800 only**). Read the EEPROM to a `.bin`,
edit MAC / VID / PID / bcdDevice / strings / serial with a byte-exact preview,
and program a physical adapter with verify-after-write.


```
lan7800-programmer/
docs/ register map, .bin layout, MPLAB parity checklist (read first)
cli/ the engine — pluggable backends + AI-oriented CLI (single source of truth)
webapp/ Hydrogen app; shells out to the CLI (holds no EEPROM logic)
```
## Access method (decided)
Primary backend is the in-tree **`lan78xx` driver via `ethtool`**, exactly what
Microchip's own "LAN78xx EEPROM/OTP Programming on Linux" article recommends:
- Read: `ethtool -e <iface> raw on`
- Write: `ethtool -E <iface> magic 0x78A5 offset <o> value <v>` (one byte/call;
the driver handles EWEN internally)
The backend is **pluggable** (`cli/lan7800/backends/`) so a driver-independent
**libusb** vendor-command path (for blank chips) can be added later without
touching field logic.
## Safety model (important)
- **Reads are free.** Inspect, dump, preview, verify — none of these change hardware.
- **Physical writes are gated.** The only commands that touch an adapter are
`program` and `erase`, and both **refuse** unless `--force-physical-write` is
passed. In the Symphony mesh, the operator/agent sets that flag **only after a
cleared `mesh escalate` PROCEED**. The webapp never sets it — `POST /program`
returns `423` with the exact command for a human to run.
- **Verify-after-write is mandatory** (`program` re-reads and diffs; fails loud).
- **Anti-brick:** byte0 signature `0xA5` is only written deliberately; a cleared
signature is recoverable (device falls back to OTP/CSR defaults). Stay in
0–511. Keep a backup: `lan7800prog read --iface <if> --out backup.bin`.
## CLI quickstart
```bash
cd cli
python3 lan7800prog scan # FREE — enumerate LAN7800s
python3 lan7800prog read-device --out dump.bin # FREE — connected device
# auto-picks the backend: a lan78xx netdev -> ethtool, else the driver-independent
# libusb path (needs a one-time udev rule; run `lan7800prog udev-rule` to print it)
python3 lan7800prog new --out base.bin # default LAN7800 template (or --blank)
python3 lan7800prog read --iface enx00800f780000 --out dump.bin # FREE (specific iface)
python3 lan7800prog info --in dump.bin # parse & print (fields, config flags, LED/GPIO)
python3 lan7800prog --json info --iface enp0s20u1 # machine-readable
# edit fields -> new .bin (no hardware), then preview
python3 lan7800prog edit --in base.bin --out out.bin \
--mac 00:80:0F:12:34:56 --vid 0x0424 --pid 0x7800 --serial SN0001 \
--set-string product="LAN7800 GbE" --ensure-signature
# LED / GPIO (offsets authoritative; per-bit encoding VERIFY vs datasheet §15)
python3 lan7800prog edit --in base.bin --out out.bin \
--set-led 0=10 --led-enable 0=1 --led-blink 2 \
--set-gpio 3=out,push-pull,1 --gpio-wake 3=1,1 \
--set-raw 0x48=01 # authoritative raw-byte fallback
# compare a device against an image (FREE)
python3 lan7800prog verify --iface enp0s20u1 --image out.bin
# GATED — only after a mesh-escalate PROCEED:
python3 lan7800prog program --iface enp0s20u1 --image out.bin --force-physical-write
```
Every command supports `--json` (machine output) and otherwise prints
`OK:` / `ERROR:` + `Hint:` lines.
## Webapp
```bash
cd webapp && PORT=8747 python3 server.py # http://127.0.0.1:8747
```
**Scan for devices** enumerates attached LAN7800s (pick one to target). **Load**
is a native file picker (uploads the `.bin` to the server). Edit MAC/VID/PID/
strings/serial and the **LED/GPIO** panel, watch the live byte-exact preview, save.
`Program to device` is intentionally gated and returns the command to run under
escalation. State is on the server (`GET /state`, `GET /console`, `GET /scan`) so
it is AI-drivable.
## Tests (offline, no hardware)
```bash
cd cli && python3 tests/test_eeprom.py # 10/10 field round-trips on a synthetic .bin
```
## Field layout & VERIFY caveats
Layout follows `docs/01-eeprom-mechanism.md` (datasheet DS00001992G Table 10-2 +
`lan78xx.c`). Two assumptions are flagged for confirmation against a **real dump**
and centralized in `cli/lan7800/eeprom.py` so they flip in one place:
- **MAC octet order** (`MAC_LITTLE_ENDIAN`) — stored little-endian per the doc.
- **VID/PID location** — inside the SS/HS/FS USB device-descriptor blocks pointed
to by the word-unit pointers at `0x31/0x35/0x39`; writes hit **all present blocks**.
## Scope
Core v1 = read→dump, edit MAC/VID/PID/bcdDevice/strings/serial, signature `0xA5`,
verify-after-write, load/save raw `.bin`, **device scan**, **file-picker load**,
and **LED/GPIO editing** (LED per-pin enable + 16 modes + blink; GPIO direction/
drive/output/wake). LED/GPIO **byte offsets are authoritative** (LED `0x0B–0x0D`,
`0x58–0x59`; GPIO `0x07`/`0x0E`/`0x48–0x4F`) but the **per-bit encoding is
best-effort** — confirm against datasheet §15 or a real dump; a `--set-raw`
byte-level fallback is always available. If a USB descriptor block is pointed
into one of these fixed register ranges, `info` reports the collision and named
LED/GPIO writes to the colliding byte are **blocked** (use `--set-raw` only if
you truly intend it) so a descriptor is never silently corrupted. **Not** in v1 (see
`docs/02-mplab-connect-parity.md` → ADVANCED): OTP, MAC ranges / mass production,
PME/wake-frame filters, string-heap relocation (v1 edits strings in place only),
INI/TOML source format.