skywalker-1/tools/addr_gateway_test.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

129 lines
4.7 KiB
Python

#!/usr/bin/env python3
"""BCM4500 I2C address gateway verification test.
Compares register reads at address 0x08 (BCM4500 direct) vs 0x10
(BCM3440 tuner gateway) to confirm the stock firmware disassembly
finding: all BCM4500 register access goes through the tuner at 0x10.
HYPOTHESIS:
- Reads at 0x08 return the same status byte for all registers
- Reads at 0x10 return different, register-specific values
- Writes at 0x10 actually reach BCM4500 registers
- The indirect register protocol (A6/A7/A8) works at 0x10
Run with custom firmware v3.02+ (needs 0xB5 raw I2C read).
"""
import sys
sys.path.insert(0, 'tools')
from skywalker_lib import SkyWalker1
CMD_I2C_RAW_READ = 0xB5
ADDR_DIRECT = 0x08 # BCM4500 direct
ADDR_GATEWAY = 0x10 # BCM3440 tuner gateway
REGS = [0xA0, 0xA2, 0xA4, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB]
sw = SkyWalker1()
sw.open()
print('=== BCM4500 I2C Address Gateway Test ===')
print(f'Firmware: {sw.get_fw_version()}')
# Boot first
print('\n--- Booting BCM4500 ---')
result = sw._vendor_in(0x89, value=1, index=0, length=3)
cfg, stage = result[0], result[1]
print(f' Config: 0x{cfg:02X}, Stage: 0x{stage:02X}')
# Test 1: Read registers via direct address (0x08)
print(f'\n=== Test 1: Registers via direct address 0x{ADDR_DIRECT:02X} (BCM4500) ===')
direct_vals = {}
for reg in REGS:
try:
data = sw._vendor_in(CMD_I2C_RAW_READ, value=ADDR_DIRECT, index=reg, length=1)
direct_vals[reg] = data[0]
print(f' 0x{reg:02X}: 0x{data[0]:02X}')
except Exception as e:
direct_vals[reg] = None
print(f' 0x{reg:02X}: FAILED ({e})')
# Test 2: Read registers via gateway address (0x10)
print(f'\n=== Test 2: Registers via gateway address 0x{ADDR_GATEWAY:02X} (BCM3440) ===')
gw_vals = {}
for reg in REGS:
try:
data = sw._vendor_in(CMD_I2C_RAW_READ, value=ADDR_GATEWAY, index=reg, length=1)
gw_vals[reg] = data[0]
print(f' 0x{reg:02X}: 0x{data[0]:02X}')
except Exception as e:
gw_vals[reg] = None
print(f' 0x{reg:02X}: FAILED ({e})')
# Test 3: Read tuner-specific registers (low range) at 0x10
print(f'\n=== Test 3: BCM3440 tuner registers (0x00-0x0F) at 0x{ADDR_GATEWAY:02X} ===')
for reg in range(0x00, 0x10):
try:
data = sw._vendor_in(CMD_I2C_RAW_READ, value=ADDR_GATEWAY, index=reg, length=1)
print(f' 0x{reg:02X}: 0x{data[0]:02X}', end='')
except Exception:
print(f' 0x{reg:02X}: FAIL', end='')
if (reg + 1) % 8 == 0:
print()
# Test 4: Write to A6 via gateway, then read back
print(f'\n=== Test 4: Write A6=0x42 via gateway, read back ===')
try:
# Write using vendor OUT (need to construct this)
# Use bcm_direct_write equivalent through 0xB2 (RAW_DEMOD_WRITE)
# Actually, we need a raw I2C write command...
# Let's use the B6 diagnostic which writes A6 and reads back
diag = sw._vendor_in(0xB6, value=0x42, index=0x01, length=8)
print(f' B6 diag (via current FW addr):')
print(f' A6 write ok: {diag[0]}')
print(f' A6 readback: 0x{diag[1]:02X}')
print(f' A8 write ok: {diag[2]}')
print(f' A8 immediate: 0x{diag[3]:02X}')
print(f' A8 after 2ms: 0x{diag[4]:02X}')
print(f' A7 data: 0x{diag[5]:02X}')
print(f' A6 final: 0x{diag[6]:02X}')
print(f' Cmd sent: 0x{diag[7]:02X}')
except Exception as e:
print(f' FAILED: {e}')
# Analysis
print('\n' + '=' * 60)
print('ANALYSIS')
print('=' * 60)
direct_unique = set(v for v in direct_vals.values() if v is not None)
gw_unique = set(v for v in gw_vals.values() if v is not None)
print(f'\nDirect (0x{ADDR_DIRECT:02X}): {len(direct_unique)} unique values: '
f'{", ".join(f"0x{v:02X}" for v in sorted(direct_unique))}')
print(f'Gateway (0x{ADDR_GATEWAY:02X}): {len(gw_unique)} unique values: '
f'{", ".join(f"0x{v:02X}" for v in sorted(gw_unique))}')
if len(direct_unique) <= 2 and len(gw_unique) > 2:
print('\n >> HYPOTHESIS CONFIRMED!')
print(' >> Direct 0x08 returns same status for all regs')
print(' >> Gateway 0x10 returns register-specific values')
print(' >> FIX: BCM4500_ADDR should be 0x10 (tuner gateway)')
elif len(gw_unique) <= 2:
print('\n >> Gateway also returns uniform values -- may need different timing')
print(' >> or the tuner gateway requires initialization first')
else:
print('\n >> Unexpected results -- check values above')
# Show side-by-side comparison
print('\n Reg Direct(0x08) Gateway(0x10) Different?')
for reg in REGS:
d = direct_vals.get(reg)
g = gw_vals.get(reg)
d_str = f'0x{d:02X}' if d is not None else 'FAIL'
g_str = f'0x{g:02X}' if g is not None else 'FAIL'
diff = ' <--' if d != g else ''
print(f' 0x{reg:02X} {d_str:>12} {g_str:>13} {diff}')
sw.close()
print('\n=== Done ===')