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
211 lines
7.2 KiB
Python
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 ===')
|