skywalker-1/tools/test_boot_debug.py
Ryan Malloy d9f51548e0 Fix BCM4500 boot: spurious I2C STOP corrupted FX2 controller
Removed I2CS bmSTOP "bus reset" from bcm4500_boot() and debug modes.
Sending STOP with no active transaction puts the FX2 I2C controller
into an inconsistent state where subsequent START+ACK detection fails.

Root cause identified through incremental debug modes (wValue 0x80-0x85)
on live hardware: mode 0x82 (with bmSTOP) fails, mode 0x85 (identical
but without bmSTOP) succeeds. Raw I2C reads confirm BCM4500 is alive
the entire time -- only the controller state is corrupted.

BCM4500 now boots successfully in ~90ms. Three I2C devices found on
bus: 0x08 (BCM4500), 0x10 (tuner/LNB), 0x51 (EEPROM).

Also in this commit:
- Timeout-protected I2C functions replacing fx2lib bare while loops
- I2C bus scan and debug mode infrastructure
- Kernel driver blacklist for dvb_usb_gp8psk
- Test tools for incremental boot debugging
- Technical findings documented in docs/boot-debug-findings.md
2026-02-12 10:34:15 -07:00

128 lines
3.8 KiB
Python

#!/usr/bin/env python3
"""Incremental BOOT_8PSK debug tester for SkyWalker-1.
Sends debug boot modes (wValue=0x80..0x83) one at a time to isolate
which stage of the BCM4500 boot sequence hangs the FX2 firmware.
Usage:
sudo python3 test_boot_debug.py # run all debug stages
sudo python3 test_boot_debug.py 0x82 # run only stage 0x82
"""
import usb.core
import usb.util
import sys
import time
BOOT_8PSK = 0x89
def find_device():
dev = usb.core.find(idVendor=0x09C0, idProduct=0x0203)
if not dev:
print("Device not found!")
sys.exit(1)
return dev
def setup_device(dev):
try:
if dev.is_kernel_driver_active(0):
dev.detach_kernel_driver(0)
except Exception:
pass
try:
dev.set_configuration()
except usb.core.USBError:
pass
def decode_stage(stage):
names = {
0x00: "NOT_STARTED",
0x01: "GPIO_SETUP",
0x02: "PWR_SETTLED",
0x03: "I2C_PROBE",
0x04: "INIT_BLK0",
0x05: "INIT_BLK1",
0x06: "INIT_BLK2",
0xA1: "DEBUG_GPIO_OK",
0xA2: "DEBUG_PROBE_OK",
0xA3: "DEBUG_BLK0_OK",
0xE3: "DEBUG_PROBE_FAIL",
0xE4: "DEBUG_BLK0_FAIL",
0xFF: "COMPLETE",
}
return names.get(stage, f"UNKNOWN(0x{stage:02X})")
def test_mode(dev, wval, label, timeout_ms=3000):
"""Send a debug boot mode and read 3-byte response."""
print(f"\n{'' * 50}")
print(f" Testing wValue=0x{wval:02X}: {label}")
print(f"{'' * 50}")
t0 = time.monotonic()
try:
ret = dev.ctrl_transfer(0xC0, BOOT_8PSK, wval, 0, 3, timeout=timeout_ms)
except usb.core.USBError as e:
elapsed = (time.monotonic() - t0) * 1000
print(f" FAILED after {elapsed:.0f}ms: {e}")
# Try to see if device is still alive
try:
dev.ctrl_transfer(0xC0, 0x92, 0, 0, 6, timeout=1000)
print(" Device still responds to GET_FW_VERS")
except:
print(" Device is HUNG (no response to GET_FW_VERS)")
return None
elapsed = (time.monotonic() - t0) * 1000
status = ret[0]
stage = ret[1] if len(ret) > 1 else 0
probe = ret[2] if len(ret) > 2 else 0
print(f" Response in {elapsed:.0f}ms:")
print(f" config_status: 0x{status:02X}")
print(f" boot_stage: 0x{stage:02X} [{decode_stage(stage)}]")
print(f" probe_byte: 0x{probe:02X}")
return ret
def main():
dev = find_device()
setup_device(dev)
# Verify firmware is responding
try:
ret = dev.ctrl_transfer(0xC0, 0x92, 0, 0, 6, timeout=2000)
major, minor, patch = ret[2], ret[1], ret[0]
print(f"Firmware: v{major}.{minor:02d}.{patch}")
except usb.core.USBError as e:
print(f"GET_FW_VERS failed: {e}")
print("Device may be hung. Try reloading firmware with fw_load.py.")
sys.exit(1)
ret = dev.ctrl_transfer(0xC0, 0x80, 0, 0, 1)
print(f"Config: 0x{ret[0]:02X}")
# Parse optional argument for single-stage testing
single_stage = None
if len(sys.argv) > 1:
single_stage = int(sys.argv[1], 0)
stages = [
(0x80, "No-op: return current state only"),
(0x81, "GPIO setup + power + delays (no I2C)"),
(0x82, "GPIO + I2C bus reset + BCM4500 probe read"),
(0x83, "GPIO + I2C probe + write init block 0"),
]
for wval, label in stages:
if single_stage is not None and wval != single_stage:
continue
result = test_mode(dev, wval, label)
if result is None:
print("\n*** STOPPING: device not responding ***")
break
print(f"\n{'=' * 50}")
print("Debug complete.")
if __name__ == "__main__":
main()