Enable in-container USB flashing + flash-re-bba-rb skill; find U7 EEPROM undersized
Devcontainer now grants libusb access to the FT2232H so iceprog (bitstream) and pyftdi (FT2232H EEPROM) run inside the container: - devcontainer.json: --privileged + /dev/bus/usb bind mount - Dockerfile: sudo (NOPASSWD for vscode; USB nodes are root-owned), libusb-1.0-0, libftdi1-2, pyftdi Add the flash-re-bba-rb skill documenting the procedure (bitstream via best swept seed, optional EEPROM string, troubleshooting, guardrails) and flash_ftdi_eeprom.py (pyftdi, backup-first, dry-run default) + ftdi_eeprom.conf value spec. Bring-up on the repaired V1 unit: bitstream flash VERIFY OK (iceprog build/seed4/top.bin, capture 58.36 MHz). EEPROM product string CANNOT be programmed on V1 — U7 is a 93LC46B (128 B), too small for the FT2232H, which needs a 93LC56B (256 B); the 128 B chip mirrors and the config write fails verify. This overturns the old REVIEW note "93LC46B = correct for FT2232H" (that only checked ORG, not size). Board still works blank -> ROM defaults; iceprog + UART unaffected. Documented in REVIEW.md + TODO.md (swap U7->93LC56B for V2); flash_ftdi_eeprom.py detects the mirroring and refuses the write. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
#!/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.
|
||||
|
||||
SAFE BY DESIGN:
|
||||
* Keeps the stock VID/PID 0403:6010 and the dual-channel config, so iceprog
|
||||
(MPSSE on channel A) and the channel-B UART keep working unchanged — only
|
||||
the descriptor STRINGS change.
|
||||
* DRY-RUN BY DEFAULT: prints the diff and writes a raw backup, but does NOT
|
||||
touch the EEPROM unless you pass --commit.
|
||||
* Always dumps the current EEPROM to a backup file first.
|
||||
|
||||
Usage (inside the rebuilt devcontainer, FT2232H usbipd-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 --commit # write strings
|
||||
sudo /opt/venv/bin/python hardware/re-bba-rb/flash_ftdi_eeprom.py --erase --commit # blank U7
|
||||
|
||||
⚠️ re-bba-rb V1 HARDWARE LIMITATION: U7 is a 93LC46B (128-byte / 1 Kbit) EEPROM,
|
||||
which is 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 and the string write fails verify, so the "re-BBA-rb" product
|
||||
string CANNOT be programmed on V1. This script therefore REFUSES the write when
|
||||
it detects mirroring (use --force to override, --erase to blank U7). The board
|
||||
is fully functional without it: it enumerates on ROM defaults ("Dual RS232-HS",
|
||||
0403:6010) and iceprog + the channel-B UART work normally. Fix in a future rev
|
||||
by fitting a 93LC56B at U7.
|
||||
|
||||
Flash the FPGA bitstream FIRST and confirm iceprog works — the string is purely
|
||||
cosmetic. Do NOT rely on reset_device(): it re-enumerates the FT2232H and drops
|
||||
the usbipd attachment on WSL2 — re-attach with usbipd 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 likely fail/corrupt")
|
||||
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). So the product string CANNOT be
|
||||
# stored on V1 — the board still works fine on ROM defaults ("Dual RS232-HS",
|
||||
# 0403:6010; iceprog + the UART are unaffected). Refuse rather than
|
||||
# half-write (which corrupts U7 and needs an --erase recovery).
|
||||
# ------------------------------------------------------------------
|
||||
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"
|
||||
"The 're-BBA-rb' product string cannot be stored on this board "
|
||||
"(see the header comment / REVIEW.md). The board works fine on ROM "
|
||||
"defaults. Use --erase to (re)blank U7; --force to attempt anyway.",
|
||||
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())
|
||||
Reference in New Issue
Block a user