Files
rebbarb/hardware/re-bba-rb/flash_ftdi_eeprom.py
T
Roflin 6ba3447e58 Correct EEPROM soft-brick record: partial write on undersized U7 kills enumeration
Hardware bring-up correction (Dennis, 2026-08-22): writing the FT2232H product
string to the undersized 93LC46B (U7) does NOT harmlessly fail — the partial,
mirrored write lands a checksum-valid-but-garbage config, and the FT2232H then
FAILS USB enumeration entirely (dead silent, no dmesg attach). It does NOT fall
back to ROM defaults; only a blank/checksum-invalid EEPROM does. The earlier
claim that "a bad EEPROM always falls back and still enumerates" was WRONG.

Recovery (confirmed working): power on with U7 CLK shorted to GND so the FT2232H
can't read a valid config -> forces ROM defaults -> enumerates -> then erase U7.

Updated REVIEW.md, TODO.md, the flash-re-bba-rb skill (do-not-program warning +
recovery, native-Linux troubleshooting), and flash_ftdi_eeprom.py comments/help
to state the soft-brick reality. The script already refuses the write on
mirroring detection; --force now documented as "reproduce the soft-brick".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-22 18:57:52 +00:00

190 lines
8.5 KiB
Python

#!/usr/bin/env python3
"""Program the re-bba-rb FT2232H EEPROM so the unit enumerates as "re-BBA-rb".
Uses pyftdi (libusb) — the in-container tool, since debian's libftdi1-2 ships
only the runtime lib, not the `ftdi_eeprom` binary. The intended descriptor
values mirror hardware/re-bba-rb/ftdi_eeprom.conf.
DRY-RUN BY DEFAULT: prints the diff and writes a raw backup, but does NOT touch
the EEPROM unless you pass --commit. Keeps the stock VID/PID 0403:6010.
Usage (inside the devcontainer, FT2232H attached):
sudo /opt/venv/bin/python hardware/re-bba-rb/flash_ftdi_eeprom.py # dry-run + backup
sudo /opt/venv/bin/python hardware/re-bba-rb/flash_ftdi_eeprom.py --erase --commit # blank U7
⚠️⚠️ re-bba-rb V1: DO NOT PROGRAM THE STRINGS — IT SOFT-BRICKS THE FT2232H. ⚠️⚠️
U7 is a 93LC46B (128-byte / 1 Kbit) EEPROM, TOO SMALL for the FT2232H — the
H-series needs a 93LC56B (256 B) or 93LC66B (the 93LC46 is for the FT232R /
FT2232D). The FT2232H config mirrors in the 128-byte chip, so a string write
lands a PARTIAL, checksum-valid-but-garbage config. The FT2232H then reads it at
power-up and **FAILS USB ENUMERATION ENTIRELY** — dead silent, no dmesg attach.
It does NOT fall back to ROM defaults (only a BLANK / checksum-INVALID EEPROM
does that). This was confirmed on hardware 2026-08-22. The script therefore
REFUSES the write when it detects mirroring; --force exists only to reproduce the
soft-brick deliberately.
RECOVERY if U7 ever gets a bad config written (done on the V1 unit, works):
1. Power the board on with U7's CLK pin shorted to GND — the FT2232H then can't
read a valid EEPROM and enumerates on ROM defaults ("Dual RS232-HS",
0403:6010).
2. Release the short and immediately blank U7:
sudo /opt/venv/bin/python hardware/re-bba-rb/flash_ftdi_eeprom.py --erase --commit
(Classic FTDI EEPROM recovery.) With U7 blank the board is fully functional;
iceprog + the channel-B UART work normally. Fix for a future rev: 93LC56B at U7.
reset_device() is intentionally NOT called — it re-enumerates the FT2232H and can
drop a usbip attachment; re-plug / re-attach manually instead.
"""
import argparse
import sys
from pathlib import Path
VID = 0x0403
PID = 0x6010
URL = f"ftdi://0x{VID:04x}:0x{PID:04x}/1"
MANUFACTURER = "hashru"
PRODUCT = "re-BBA-rb"
SERIAL = "RBBARB001"
BACKUP = Path(__file__).with_name("eeprom-backup.bin")
def main() -> int:
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument("--commit", action="store_true",
help="actually write the EEPROM (default: dry-run only)")
ap.add_argument("--erase", action="store_true",
help="blank U7 to 0xFF (FT2232H falls back to ROM defaults)")
ap.add_argument("--force", action="store_true",
help="attempt the string write even if U7 reports mirroring "
"(undersized 93LC46B) — WILL SOFT-BRICK the FT2232H on "
"V1 (fails USB enumeration; recover via CLK-to-GND + erase)")
ap.add_argument("--url", default=URL, help=f"pyftdi device URL (default {URL})")
args = ap.parse_args()
try:
from pyftdi.eeprom import FtdiEeprom
except ImportError:
print("pyftdi not installed. In the devcontainer it is preinstalled; "
"otherwise: pip install pyftdi", file=sys.stderr)
return 2
eeprom = FtdiEeprom()
try:
eeprom.open(args.url)
except Exception as exc: # noqa: BLE001 - surface the real libusb error
print(f"Could not open {args.url}: {exc}", file=sys.stderr)
print("Is the FT2232H usbipd-attached, and are you running under sudo?",
file=sys.stderr)
return 1
# 1) Backup the current EEPROM image before any change.
try:
BACKUP.write_bytes(bytes(eeprom.data))
print(f"Backed up current EEPROM ({len(eeprom.data)} bytes) -> {BACKUP}")
except Exception as exc: # noqa: BLE001
print(f"WARNING: could not write backup ({exc}); aborting.", file=sys.stderr)
eeprom.close()
return 1
# --- Erase mode: blank U7 back to 0xFF (FT2232H then uses ROM defaults). ---
if args.erase:
return _erase(eeprom, args.commit)
# ------------------------------------------------------------------
# HARDWARE NOTE — U7 on re-bba-rb V1 is a 93LC46B (1 Kbit = 128 bytes).
# The FT2232H addresses a 256-byte EEPROM and its config does NOT fit in
# 128 bytes: pyftdi sees the 128-byte chip mirror into the upper half
# (has_mirroring=True), the config write mirror-clobbers word 0x80->0x00,
# and verify fails. FTDI specs a 93LC56B (256 B) / 93LC66B for the H-series
# (the 93LC46 is for the FT232R / FT2232D). Writing the strings leaves a
# PARTIAL checksum-valid-but-garbage config that SOFT-BRICKS the FT2232H: it
# then fails USB enumeration entirely (dead silent — does NOT fall back to
# ROM defaults). Recovery: power on with U7 CLK shorted to GND, then --erase.
# So REFUSE the write — do not half-brick the chip.
# ------------------------------------------------------------------
if eeprom.has_mirroring and not args.force:
print("\nREFUSING: U7 reports EEPROM mirroring — it is a 128-byte chip "
"(93LC46B), too small for the FT2232H's 256-byte config.\n"
"Writing the 're-BBA-rb' strings SOFT-BRICKS the FT2232H on this "
"board (partial config -> fails USB enumeration; recover by shorting "
"U7 CLK to GND at boot then --erase; see header / REVIEW.md).\n"
"Use --erase to blank U7; --force ONLY to reproduce the soft-brick.",
file=sys.stderr)
eeprom.close()
return 3
# Stage a COMPLETE FT2232H default config, then overlay our strings.
print(f"\nEEPROM currently blank: {eeprom.is_empty} — staging FT2232H defaults.")
eeprom.initialize()
cfg = eeprom._config # decoded config dict (no public accessor in pyftdi 0.57)
print("\nCurrent (staged-base) descriptors:")
print(f" vendor_id = 0x{cfg.get('vendor_id', 0):04x}")
print(f" product_id = 0x{cfg.get('product_id', 0):04x}")
# Stage the new strings (VID/PID/channel config untouched).
eeprom.set_manufacturer_name(MANUFACTURER)
eeprom.set_product_name(PRODUCT)
eeprom.set_serial_number(SERIAL)
print("\nStaged descriptors:")
print(f" manufacturer = {MANUFACTURER!r}")
print(f" product = {PRODUCT!r}")
print(f" serial = {SERIAL!r}")
if not args.commit:
print("\nDRY-RUN — nothing written. Re-run with --commit to program.")
eeprom.close()
return 0
try:
eeprom.commit(dry_run=False)
except Exception as exc: # noqa: BLE001
print(f"\nWRITE FAILED ({exc}). U7 may be partially written — run with "
"--erase to blank it back to ROM-default fallback.", file=sys.stderr)
eeprom.close()
return 1
eeprom.close()
# NOTE: do NOT call reset_device() — it re-enumerates the FT2232H, which
# drops the usbipd attachment on WSL2. Re-plug / re-attach manually instead.
print("\nEEPROM written. Re-attach USB (usbipd) so the host shows 're-BBA-rb'.")
return 0
def _erase(eeprom, commit: bool) -> int:
"""Blank U7 to 0xFF so the FT2232H falls back to ROM defaults.
On the mirrored 128-byte chip pyftdi's 256-byte verify trips even though the
0xFF writes land, so we loop and check the actual content instead of trusting
commit()'s verify.
"""
if not commit:
print("\n--erase DRY-RUN: would write 0xFF over U7. Add --commit to do it.")
eeprom.close()
return 0
for attempt in range(5):
data = bytes(eeprom.data)
nonff = sum(1 for b in data if b != 0xFF)
print(f"erase attempt {attempt}: non-0xFF bytes = {nonff}")
if nonff == 0:
print("U7 is fully blank — FT2232H will use ROM defaults.")
eeprom.close()
return 0
eeprom.erase(0xFF)
try:
eeprom.commit(dry_run=False)
except Exception as exc: # noqa: BLE001 - mirrored-chip verify trips; writes still land
print(f" (commit verify raised, expected on mirrored chip: {exc})")
# Re-read fresh next loop without reset_device() (which drops usbip).
eeprom.sync()
print("Could not fully blank U7 after retries — re-run --erase after re-attach.",
file=sys.stderr)
eeprom.close()
return 1
if __name__ == "__main__":
raise SystemExit(main())