skywalker-1/tools/stock_fw_test.py

343 lines
12 KiB
Python
Raw Permalink Normal View History

#!/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('<H', sig, 0)[0]
sig_all_zero = all(b == 0 for b in sig)
print(f" Signal strength (0x87): {sig.hex(' ')}")
print(f" SNR raw: 0x{snr_raw:04X} ({snr_raw}), all_zero: {sig_all_zero}")
except usb.core.USBError as e:
print(f" Signal strength read failed: {e}")
try:
lock = vendor_in(dev, GET_SIGNAL_LOCK, length=1)
print(f" Lock status (0x90): 0x{lock[0]:02X} {'LOCKED' if lock[0] else 'no lock'}")
except usb.core.USBError as e:
print(f" Lock status read failed: {e}")
# --- Step 6: FULL BCM4500 direct register dump (0xA0-0xBF) ---
# This captures the PLL/clock values written by FUN_CODE_10F2
print("\n--- Step 6: BCM4500 direct registers 0xA0-0xBF (post-boot) ---")
print(" (A0=config_mode, A9/AA/AB=PLL from EEPROM, A6/A7/A8=indirect)")
regs = dump_bcm4500_direct_regs(dev)
for reg in sorted(regs.keys()):
val = regs[reg]
if val is None:
print(f" 0x{reg:02X}: FAILED")
continue
# Check for echo pattern (dead core symptom)
echo_expected = min(reg & 0xFE, 0x7E) if reg > 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()