#!/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(' 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()