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
129 lines
4.7 KiB
Python
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 ===')
|