One-off diagnostic scripts from experiments 0xD7-0xDB investigating the I2C BERR deadlock. Documents the systematic elimination of software-only recovery approaches: - i2c_host_test.py: Proved 0xA0 register writes cannot drive I2C bus - i2c_register_test.py: Tested I2C register writability from host - i2c_recovery_boot.py: Attempted I2C state machine recovery via boot - eeprom_flash_a0.py: Host-side EEPROM flash attempt (failed) - boot_ab_test.py / boot_test.py: EEPROM boot reliability testing - a8_autoclear_test.py: BCM4500 command register auto-clear behavior - addr_gateway_test.py: BCM3440 gateway address routing analysis - stock_fw_compare.py / stock_fw_test.py: Stock vs custom fw analysis
343 lines
12 KiB
Python
343 lines
12 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Stock firmware BCM4500 boot diagnostic.
|
|
|
|
Runs the stock v2.06 firmware boot sequence (same as kernel gp8psk driver),
|
|
then performs comprehensive register dumps to capture the BCM4500 state —
|
|
including the critical A9/AA/AB PLL registers written by FUN_CODE_10F2
|
|
(which our custom firmware currently skips).
|
|
|
|
Also scans the I2C bus to find device 0x51 (calibration EEPROM) that the
|
|
stock firmware reads PLL configuration data from.
|
|
|
|
Usage:
|
|
python tools/stock_fw_test.py [--dump-all] [--i2c-scan]
|
|
|
|
Requirements:
|
|
- SkyWalker-1 connected via USB (stock firmware loaded)
|
|
- pyusb installed
|
|
"""
|
|
|
|
import sys
|
|
import struct
|
|
|
|
try:
|
|
import usb.core
|
|
import usb.util
|
|
except ImportError:
|
|
print("pyusb required: pip install pyusb")
|
|
sys.exit(1)
|
|
|
|
|
|
# USB IDs
|
|
VID = 0x09C0
|
|
PID = 0x0203
|
|
|
|
# Stock firmware vendor commands (same as kernel driver)
|
|
GET_8PSK_CONFIG = 0x80
|
|
I2C_WRITE = 0x83
|
|
I2C_READ = 0x84
|
|
ARM_TRANSFER = 0x85
|
|
TUNE_8PSK = 0x86
|
|
GET_SIGNAL_STRENGTH = 0x87
|
|
BOOT_8PSK = 0x89
|
|
START_INTERSIL = 0x8A
|
|
SET_LNB_VOLTAGE = 0x8B
|
|
GET_SIGNAL_LOCK = 0x90
|
|
GET_FW_VERS = 0x92
|
|
|
|
# Config status bits
|
|
BM_STARTED = 0x01
|
|
BM_FW_LOADED = 0x02
|
|
BM_INTERSIL = 0x04
|
|
|
|
# I2C addresses (7-bit)
|
|
BCM4500_ADDR = 0x08
|
|
BCM3440_ADDR = 0x10
|
|
EEPROM_ADDR = 0x51 # Calibration EEPROM found in FUN_CODE_10F2 disassembly
|
|
|
|
|
|
def vendor_in(dev, cmd, value=0, index=0, length=1):
|
|
"""Send a vendor IN control transfer (device -> host)."""
|
|
return bytes(dev.ctrl_transfer(
|
|
usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_IN,
|
|
cmd, value, index, length, 2000))
|
|
|
|
|
|
def vendor_out(dev, cmd, value=0, index=0, data=None):
|
|
"""Send a vendor OUT control transfer (host -> device)."""
|
|
dev.ctrl_transfer(
|
|
usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_OUT,
|
|
cmd, value, index, data or b'', 2000)
|
|
|
|
|
|
def decode_config(cfg):
|
|
"""Decode config status byte to human-readable string."""
|
|
bits = []
|
|
names = [
|
|
(0x01, "Started"), (0x02, "FW_Loaded"), (0x04, "Intersil"),
|
|
(0x08, "DVBmode"), (0x10, "22kHz"), (0x20, "18V"),
|
|
(0x40, "DCtuned"), (0x80, "Armed"),
|
|
]
|
|
for mask, name in names:
|
|
if cfg & mask:
|
|
bits.append(name)
|
|
return " | ".join(bits) if bits else "(none)"
|
|
|
|
|
|
def i2c_read(dev, addr, reg, length=1):
|
|
"""Read from I2C device via stock firmware 0x84 command."""
|
|
return vendor_in(dev, I2C_READ, value=addr, index=reg, length=length)
|
|
|
|
|
|
def i2c_scan(dev, start=0x03, end=0x77):
|
|
"""Scan I2C bus for responding devices using stock firmware I2C_READ."""
|
|
found = []
|
|
for addr in range(start, end + 1):
|
|
try:
|
|
val = i2c_read(dev, addr, 0x00, length=1)
|
|
found.append((addr, val[0]))
|
|
except usb.core.USBError:
|
|
pass
|
|
return found
|
|
|
|
|
|
def dump_bcm4500_direct_regs(dev):
|
|
"""Read all BCM4500 direct registers 0xA0-0xBF after boot.
|
|
|
|
These are the PLL/config registers. FUN_CODE_10F2 writes to A0, A9, AA, AB
|
|
during boot from calibration EEPROM data. Our custom firmware skips this.
|
|
"""
|
|
regs = {}
|
|
for reg in range(0xA0, 0xC0):
|
|
try:
|
|
val = i2c_read(dev, BCM4500_ADDR, reg, length=1)
|
|
regs[reg] = val[0]
|
|
except usb.core.USBError:
|
|
regs[reg] = None
|
|
return regs
|
|
|
|
|
|
def main():
|
|
dump_all = "--dump-all" in sys.argv
|
|
do_scan = "--i2c-scan" in sys.argv or dump_all
|
|
|
|
print("=" * 60)
|
|
print("Stock Firmware BCM4500 Boot Diagnostic")
|
|
print("=" * 60)
|
|
|
|
# --- Step 0: Find device ---
|
|
dev = usb.core.find(idVendor=VID, idProduct=PID)
|
|
if dev is None:
|
|
print("\nERROR: SkyWalker-1 not found on USB")
|
|
sys.exit(1)
|
|
|
|
print(f"\nFound SkyWalker-1: Bus {dev.bus} Addr {dev.address}")
|
|
product = usb.util.get_string(dev, dev.iProduct) if dev.iProduct else "?"
|
|
serial = usb.util.get_string(dev, dev.iSerialNumber) if dev.iSerialNumber else "?"
|
|
print(f" Product: {product}")
|
|
print(f" Serial: {serial}")
|
|
|
|
# Detach kernel driver if attached
|
|
for cfg in dev:
|
|
for intf in cfg:
|
|
if dev.is_kernel_driver_active(intf.bInterfaceNumber):
|
|
dev.detach_kernel_driver(intf.bInterfaceNumber)
|
|
try:
|
|
dev.set_configuration()
|
|
except Exception:
|
|
pass
|
|
|
|
# --- Step 1: Pre-boot state ---
|
|
print("\n--- Step 1: Pre-boot state ---")
|
|
cfg = vendor_in(dev, GET_8PSK_CONFIG, length=1)
|
|
print(f" Config: 0x{cfg[0]:02X} = {decode_config(cfg[0])}")
|
|
|
|
try:
|
|
ver = vendor_in(dev, GET_FW_VERS, length=6)
|
|
print(f" FW Version: {ver[2]}.{ver[0]:02d}.{ver[1]}")
|
|
except Exception as e:
|
|
print(f" FW Version: read failed ({e})")
|
|
|
|
# --- Step 2: Boot BCM4500 (kernel driver sequence) ---
|
|
print("\n--- Step 2: BOOT_8PSK (0x89, wValue=1) ---")
|
|
try:
|
|
result = vendor_in(dev, BOOT_8PSK, value=1, length=1)
|
|
print(f" Boot response: 0x{result[0]:02X} = {decode_config(result[0])}")
|
|
except usb.core.USBError as e:
|
|
print(f" Boot FAILED: {e}")
|
|
try:
|
|
result = vendor_in(dev, BOOT_8PSK, value=1, length=3)
|
|
print(f" Boot response (3 bytes): {result.hex(' ')}")
|
|
except usb.core.USBError as e2:
|
|
print(f" Boot also failed with 3 bytes: {e2}")
|
|
sys.exit(1)
|
|
|
|
cfg = vendor_in(dev, GET_8PSK_CONFIG, length=1)
|
|
started = bool(cfg[0] & BM_STARTED)
|
|
fw_loaded = bool(cfg[0] & BM_FW_LOADED)
|
|
print(f" Config after boot: 0x{cfg[0]:02X} = {decode_config(cfg[0])}")
|
|
print(f" Started: {started}, FW Loaded: {fw_loaded}")
|
|
|
|
# --- Step 3: Enable LNB power supply (Intersil) ---
|
|
print("\n--- Step 3: START_INTERSIL (0x8A, wValue=1) ---")
|
|
try:
|
|
result = vendor_in(dev, START_INTERSIL, value=1, length=1)
|
|
print(f" Intersil response: 0x{result[0]:02X} = {decode_config(result[0])}")
|
|
except usb.core.USBError as e:
|
|
print(f" Intersil FAILED: {e}")
|
|
|
|
# --- Step 4: Cancel pending MPEG transfers ---
|
|
print("\n--- Step 4: ARM_TRANSFER (0x85, wValue=0) ---")
|
|
try:
|
|
vendor_out(dev, ARM_TRANSFER, value=0)
|
|
print(" OK")
|
|
except usb.core.USBError as e:
|
|
print(f" Failed: {e}")
|
|
|
|
cfg = vendor_in(dev, GET_8PSK_CONFIG, length=1)
|
|
print(f" Final config: 0x{cfg[0]:02X} = {decode_config(cfg[0])}")
|
|
|
|
# --- Step 5: Signal strength (indirect register reads) ---
|
|
print("\n--- Step 5: Signal reads ---")
|
|
sig_all_zero = True
|
|
try:
|
|
sig = vendor_in(dev, GET_SIGNAL_STRENGTH, length=6)
|
|
snr_raw = struct.unpack_from('<H', sig, 0)[0]
|
|
sig_all_zero = all(b == 0 for b in sig)
|
|
print(f" Signal strength (0x87): {sig.hex(' ')}")
|
|
print(f" SNR raw: 0x{snr_raw:04X} ({snr_raw}), all_zero: {sig_all_zero}")
|
|
except usb.core.USBError as e:
|
|
print(f" Signal strength read failed: {e}")
|
|
|
|
try:
|
|
lock = vendor_in(dev, GET_SIGNAL_LOCK, length=1)
|
|
print(f" Lock status (0x90): 0x{lock[0]:02X} {'LOCKED' if lock[0] else 'no lock'}")
|
|
except usb.core.USBError as e:
|
|
print(f" Lock status read failed: {e}")
|
|
|
|
# --- Step 6: FULL BCM4500 direct register dump (0xA0-0xBF) ---
|
|
# This captures the PLL/clock values written by FUN_CODE_10F2
|
|
print("\n--- Step 6: BCM4500 direct registers 0xA0-0xBF (post-boot) ---")
|
|
print(" (A0=config_mode, A9/AA/AB=PLL from EEPROM, A6/A7/A8=indirect)")
|
|
regs = dump_bcm4500_direct_regs(dev)
|
|
for reg in sorted(regs.keys()):
|
|
val = regs[reg]
|
|
if val is None:
|
|
print(f" 0x{reg:02X}: FAILED")
|
|
continue
|
|
# Check for echo pattern (dead core symptom)
|
|
echo_expected = min(reg & 0xFE, 0x7E) if reg > 0x7E else (reg & 0xFE)
|
|
markers = []
|
|
if val == echo_expected:
|
|
markers.append("ECHO")
|
|
if reg in (0xA9, 0xAA, 0xAB):
|
|
markers.append("★ PLL")
|
|
if reg == 0xA0:
|
|
markers.append("CONFIG_MODE")
|
|
tag = f" ({', '.join(markers)})" if markers else ""
|
|
print(f" 0x{reg:02X}: 0x{val:02X}{tag}")
|
|
|
|
# Highlight the critical PLL values
|
|
a9 = regs.get(0xA9)
|
|
aa = regs.get(0xAA)
|
|
ab = regs.get(0xAB)
|
|
a0 = regs.get(0xA0)
|
|
print(f"\n *** Critical PLL registers ***")
|
|
print(f" A0 (config mode): 0x{a0:02X}" if a0 is not None else " A0: FAILED")
|
|
print(f" A9 (PLL div?): 0x{a9:02X}" if a9 is not None else " A9: FAILED")
|
|
print(f" AA (PLL div?): 0x{aa:02X}" if aa is not None else " AA: FAILED")
|
|
print(f" AB (PLL cfg?): 0x{ab:02X}" if ab is not None else " AB: FAILED")
|
|
|
|
# Try multi-byte read of AB (FUN_CODE_10F2 writes variable-length data here)
|
|
print("\n AB multi-byte read (up to 8 bytes):")
|
|
try:
|
|
ab_multi = i2c_read(dev, BCM4500_ADDR, 0xAB, length=8)
|
|
print(f" {ab_multi.hex(' ')}")
|
|
except usb.core.USBError as e:
|
|
print(f" FAILED: {e}")
|
|
|
|
# --- Step 7: Other known registers ---
|
|
print("\n--- Step 7: Other BCM4500 registers ---")
|
|
other_regs = [(0xF0, "F0"), (0xF8, "F8"), (0xF9, "F9")]
|
|
for reg, name in other_regs:
|
|
try:
|
|
val = i2c_read(dev, BCM4500_ADDR, reg, length=1)
|
|
echo = min(reg & 0xFE, 0x7E)
|
|
marker = " (ECHO)" if val[0] == echo else ""
|
|
print(f" Reg 0x{name}: 0x{val[0]:02X}{marker}")
|
|
except usb.core.USBError as e:
|
|
print(f" Reg 0x{name}: FAILED ({e})")
|
|
|
|
# --- Step 8: BCM3440 tuner control read ---
|
|
print("\n--- Step 8: BCM3440 tuner control read (@ 0x10) ---")
|
|
tuner_ok = False
|
|
try:
|
|
val = i2c_read(dev, BCM3440_ADDR, 0x00, length=4)
|
|
tuner_ok = not all(b == 0 for b in val)
|
|
print(f" Tuner regs 0x00-0x03: {val.hex(' ')} {'(OK)' if tuner_ok else '(all zero!)'}")
|
|
except usb.core.USBError as e:
|
|
print(f" Tuner read failed: {e}")
|
|
|
|
# --- Step 9: I2C bus scan ---
|
|
if do_scan:
|
|
print("\n--- Step 9: I2C bus scan (0x03-0x77) ---")
|
|
print(" Scanning for all responding devices...")
|
|
devices = i2c_scan(dev)
|
|
if devices:
|
|
for addr, first_byte in devices:
|
|
label = ""
|
|
if addr == BCM4500_ADDR:
|
|
label = " ← BCM4500 demod"
|
|
elif addr == BCM3440_ADDR:
|
|
label = " ← BCM3440 tuner"
|
|
elif addr == 0x28:
|
|
label = " ← EEPROM? (0x51 wire >> 1)"
|
|
elif 0x50 <= addr <= 0x57:
|
|
label = " ← EEPROM range"
|
|
elif addr == EEPROM_ADDR:
|
|
label = " ← device 0x51 from FUN_CODE_10F2!"
|
|
print(f" 0x{addr:02X} (7-bit) = 0x{addr << 1:02X}/{addr << 1 | 1:02X} (wire) "
|
|
f"first byte: 0x{first_byte:02X}{label}")
|
|
else:
|
|
print(" No devices found!")
|
|
|
|
# Try reading from device 0x51 specifically (calibration EEPROM)
|
|
print("\n --- Device 0x51 probe (calibration EEPROM) ---")
|
|
for try_addr in [EEPROM_ADDR, 0x50, 0x28, 0x29]:
|
|
try:
|
|
val = i2c_read(dev, try_addr, 0x00, length=16)
|
|
print(f" Addr 0x{try_addr:02X} reg 0x00 (16 bytes): {val.hex(' ')}")
|
|
# If we get data, try reading more
|
|
if not all(b == 0xFF for b in val):
|
|
val2 = i2c_read(dev, try_addr, 0x00, length=64)
|
|
print(f" Addr 0x{try_addr:02X} reg 0x00 (64 bytes):")
|
|
for row in range(0, len(val2), 16):
|
|
chunk = val2[row:row+16]
|
|
print(f" +{row:02X}: {chunk.hex(' ')}")
|
|
except usb.core.USBError:
|
|
print(f" Addr 0x{try_addr:02X}: no response")
|
|
else:
|
|
print("\n (use --i2c-scan or --dump-all for I2C bus scan)")
|
|
|
|
# --- Summary ---
|
|
print("\n" + "=" * 60)
|
|
# Check for echo pattern on critical registers
|
|
pll_echo = (a9 is not None and a9 == 0xA8) or (aa is not None and aa == 0xAA)
|
|
if not sig_all_zero:
|
|
print("BCM4500 CORE IS ALIVE under stock firmware!")
|
|
print(" → Problem is in our custom firmware boot sequence")
|
|
if a9 is not None and aa is not None:
|
|
print(f" → PLL values to replicate: A9=0x{a9:02X} AA=0x{aa:02X} AB=0x{ab:02X}")
|
|
elif pll_echo:
|
|
print("BCM4500 CORE IS DEAD (echo pattern on PLL registers)")
|
|
print(" → FUN_CODE_10F2 may not have run, or EEPROM missing")
|
|
else:
|
|
print("BCM4500 CORE STATUS UNCERTAIN")
|
|
print(" → Check register values above for non-echo data")
|
|
print("=" * 60)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|