# 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.

![Webapp with an EEPROM loaded — byte-exact preview and field editor](webapp/screenshots/desktop-1-loaded.png)

![Device scan — enumerate attached LAN7800 adapters](webapp/screenshots/desktop-3-scan.png)

```
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.
