skywalker-1/tools/stock_fw_compare.py
Ryan Malloy 0d6facb321 Add experimental I2C debugging and EEPROM analysis tools
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
2026-02-20 10:57:10 -07:00

211 lines
7.2 KiB
Python

#!/usr/bin/env python3
"""Compare BCM4500 register behavior: stock EEPROM firmware vs custom firmware.
USAGE:
1. Power cycle the SkyWalker-1 (unplug/replug USB)
2. Run this script IMMEDIATELY (before loading custom FW)
3. The script tests registers under stock firmware first,
then loads custom firmware and tests again.
If stock firmware shows different register behavior, the issue is
in our custom firmware's boot sequence.
"""
import sys
import time
import usb.core
sys.path.insert(0, 'tools')
VID = 0x09C0
PID = 0x0203
BCM4500_ADDR = 0x08
# ============================================================
# Raw USB helpers (work with any firmware)
# ============================================================
def vendor_in(dev, cmd, value=0, index=0, length=1):
"""Send a vendor IN request and return the response bytes."""
return dev.ctrl_transfer(
0xC0, # bmRequestType: vendor, device-to-host
cmd, # bRequest
value, # wValue
index, # wIndex
length # wLength
)
def vendor_out(dev, cmd, value=0, index=0, data=None):
"""Send a vendor OUT request."""
dev.ctrl_transfer(
0x40, # bmRequestType: vendor, host-to-device
cmd, # bRequest
value, # wValue
index, # wIndex
data if data else b''
)
def read_bcm_reg(dev, reg):
"""Read one BCM4500 register via stock-compatible I2C read (0xB5).
This might not exist on stock firmware, so we use the
stock READ_8PSK_REG (0x81) as a fallback."""
try:
data = vendor_in(dev, 0xB5, value=BCM4500_ADDR, index=reg, length=1)
return data[0]
except Exception:
return None
def read_bcm_reg_stock(dev, reg):
"""Read BCM4500 register via stock READ_8PSK_REG (0x81).
wValue = register address, returns 1 byte."""
try:
data = vendor_in(dev, 0x81, value=reg, index=0, length=1)
return data[0]
except Exception:
return None
def read_all_key_regs(dev, method='0xB5'):
"""Read key BCM4500 registers."""
regs = [0xA0, 0xA2, 0xA4, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB]
results = {}
for reg in regs:
if method == '0xB5':
val = read_bcm_reg(dev, reg)
else:
val = read_bcm_reg_stock(dev, reg)
results[reg] = val
v_str = f'0x{val:02X}' if val is not None else 'FAIL'
print(f' 0x{reg:02X}: {v_str}')
return results
# ============================================================
# MAIN
# ============================================================
print('=== Stock vs Custom Firmware BCM4500 Comparison ===')
print()
# Find the device
dev = usb.core.find(idVendor=VID, idProduct=PID)
if dev is None:
print('ERROR: SkyWalker-1 not found!')
print('Make sure to power cycle the device first (stock firmware must be running)')
sys.exit(1)
print(f'Found SkyWalker-1: Bus {dev.bus} Addr {dev.address}')
print(f' VID=0x{dev.idVendor:04X} PID=0x{dev.idProduct:04X}')
# Try to get firmware version (our custom command)
try:
fw = vendor_in(dev, 0x80, value=0, index=0, length=3)
print(f' Firmware response (0x80): {list(fw)}')
except Exception as e:
print(f' Firmware version (0x80): {e}')
# ============================================================
# Phase 1: Test under stock firmware (before boot command)
# ============================================================
print('\n' + '='*60)
print('PHASE 1: Stock firmware — BEFORE boot command')
print('='*60)
print('\n Registers via 0x81 (READ_8PSK_REG):')
regs_stock_pre_81 = {}
for reg in [0xA0, 0xA2, 0xA4, 0xA6, 0xA7, 0xA8]:
val = read_bcm_reg_stock(dev, reg)
regs_stock_pre_81[reg] = val
v_str = f'0x{val:02X}' if val is not None else 'FAIL'
print(f' 0x{reg:02X}: {v_str}')
print('\n Registers via 0xB5 (I2C_RAW_READ) — may fail on stock FW:')
regs_stock_pre_b5 = {}
for reg in [0xA0, 0xA2, 0xA4, 0xA6, 0xA7, 0xA8]:
val = read_bcm_reg(dev, reg)
regs_stock_pre_b5[reg] = val
v_str = f'0x{val:02X}' if val is not None else 'N/A (stock FW lacks 0xB5)'
print(f' 0x{reg:02X}: {v_str}')
# ============================================================
# Phase 2: Boot BCM4500 under stock firmware
# ============================================================
print('\n' + '='*60)
print('PHASE 2: Stock firmware — boot BCM4500 (0x89)')
print('='*60)
try:
result = vendor_in(dev, 0x89, value=1, index=0, length=3)
print(f' Boot result: [{result[0]:02X}, {result[1]:02X}, {result[2]:02X}]')
cfg = result[0]
bits = []
if cfg & 0x01: bits.append('Started')
if cfg & 0x02: bits.append('FW_Loaded')
if cfg & 0x04: bits.append('Intersil')
if cfg & 0x08: bits.append('DVBmode')
print(f' Config: 0x{cfg:02X} ({" | ".join(bits) if bits else "none"})')
print(f' Stage: 0x{result[1]:02X}')
except Exception as e:
print(f' Boot failed: {e}')
time.sleep(0.5)
print('\n Registers via 0x81 after boot:')
regs_stock_post_81 = {}
for reg in [0xA0, 0xA2, 0xA4, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB]:
val = read_bcm_reg_stock(dev, reg)
regs_stock_post_81[reg] = val
v_str = f'0x{val:02X}' if val is not None else 'FAIL'
marker = ''
if val is not None and reg in regs_stock_pre_81:
if regs_stock_pre_81.get(reg) != val:
marker = f' (was 0x{regs_stock_pre_81[reg]:02X})' if regs_stock_pre_81[reg] is not None else ''
print(f' 0x{reg:02X}: {v_str}{marker}')
# Try indirect read via stock firmware's 0x81 with indirect addressing
# (0x81 might support indirect reads differently)
print('\n Testing indirect register reads via 0x81:')
for page in [0x00, 0x06, 0x07, 0x0F]:
# Stock firmware might use a different convention for indirect reads
# Try reading page register values
val = read_bcm_reg_stock(dev, page)
v_str = f'0x{val:02X}' if val is not None else 'FAIL'
print(f' Page 0x{page:02X} via 0x81: {v_str}')
# Try signal monitor
print('\n Signal status:')
try:
sig = vendor_in(dev, 0x82, value=0, index=0, length=4)
print(f' GET_8PSK_SIGNAL (0x82): {list(sig)}')
except Exception as e:
print(f' 0x82: {e}')
try:
lock = vendor_in(dev, 0x83, value=0, index=0, length=1)
print(f' GET_8PSK_LOCK (0x83): 0x{lock[0]:02X}')
except Exception as e:
print(f' 0x83: {e}')
# ============================================================
# Summary
# ============================================================
print('\n' + '='*60)
print('SUMMARY')
print('='*60)
def compare_regs(label, regs):
vals = set(v for v in regs.values() if v is not None)
if len(vals) == 1:
print(f' {label}: ALL = 0x{list(vals)[0]:02X}')
elif len(vals) == 0:
print(f' {label}: ALL FAILED')
else:
print(f' {label}: Mixed: {", ".join(f"0x{v:02X}" for v in sorted(vals))}')
compare_regs('Stock FW before boot (0x81)', regs_stock_pre_81)
compare_regs('Stock FW after boot (0x81)', regs_stock_post_81)
print()
print('If stock FW shows DIFFERENT register values (not all 0x02),')
print('then the BCM4500 is truly functional under stock FW and our')
print('custom boot sequence is missing something.')
print()
print('If stock FW also shows all 0x02, then the register behavior')
print('is normal and the 0x02 IS the legitimate power-on value.')
print('\n=== Done ===')