skywalker-1/tools/eeprom_dump.py
Ryan Malloy 3d2cd477b2 Add EEPROM boot firmware (exp 0xDB) and supporting tools
Firmware: Rewrite skywalker1.c for EEPROM boot experiment — tests
whether I2C hardware controller works after FX2 boot ROM completes
EEPROM load (bypassing the CPUCS restart that triggers BERR).

Tools:
- fw_load.py: Add I2C cleanup stub, pre-halt register flush, improved
  error handling and segment loading
- eeprom_write.py: Add IHX→C2 EEPROM image converter (16KB format
  with length-prefixed segments, checksum)
- eeprom_dump.py: Refactor for cleaner output, better hex display
- skywalker_lib.py: Minor I2C register constant updates

Docs:
- EEPROM-RECOVERY.md: Four recovery options for soft-bricked device
  (SOIC clip, SDA pull-up, desolder, wait-for-timeout)
- Master reference: Updated with EEPROM boot findings

Status: EEPROM flash blocked — stock firmware I2C proxy returns pipe
errors, host-side 0xA0 writes proven unable to drive peripheral bus.
Device boot ROM intermittently hangs on EEPROM I2C read (~3-6% success).
2026-02-20 10:56:21 -07:00

158 lines
5.4 KiB
Python

#!/usr/bin/env python3
"""
Genpix SkyWalker-1 EEPROM exploration tool.
Reads the calibration EEPROM (AT24Cxxx at I2C addr 0x51) via the custom
firmware's EEPROM_READ (0xC0) vendor command. This uses 16-bit addressing
directly, bypassing the stock firmware's single-byte I2C_READ protocol.
Primary purpose: Find where PLL configuration data is stored so the
bcm4500_load_pll_config() function reads from the correct address.
"""
import sys
sys.path.insert(0, 'tools')
from skywalker_lib import SkyWalker1
CMD_EEPROM_READ = 0xC0
sw = SkyWalker1()
sw.open()
def eeprom_read(addr, length):
"""Read bytes from EEPROM at 16-bit address.
wValue = address, wIndex = length."""
return sw._vendor_in(CMD_EEPROM_READ, value=addr, index=length, length=length)
def hex_dump(addr, data):
"""Print hex dump with ASCII sidebar."""
for i in range(0, len(data), 16):
chunk = data[i:i + 16]
hex_str = ' '.join(f'{b:02X}' for b in chunk)
ascii_str = ''.join(chr(b) if 32 <= b < 127 else '.' for b in chunk)
print(f' {addr + i:04X}: {hex_str:<48s} |{ascii_str}|')
print('=== EEPROM Exploration ===')
print()
# Step 1: Determine EEPROM size by aliasing detection
print('--- Size Detection ---')
data_0000 = eeprom_read(0x0000, 16)
data_4000 = eeprom_read(0x4000, 16)
data_8000 = eeprom_read(0x8000, 16)
print(f' 0x0000: {data_0000.hex(" ")}')
print(f' 0x4000: {data_4000.hex(" ")}')
print(f' 0x8000: {data_8000.hex(" ")}')
if data_0000 == data_4000:
print(' Result: 0x4000 ALIASES to 0x0000 → AT24C128 (16KB)')
eeprom_size = 16384
elif data_0000 == data_8000:
print(' Result: 0x8000 aliases to 0x0000 → AT24C256 (32KB)')
eeprom_size = 32768
else:
print(' Result: All different → AT24C512+ (64KB+)')
eeprom_size = 65536
print()
# Step 2: Dump first 512 bytes (FX2 boot firmware header + data)
print('--- EEPROM 0x0000-0x01FF (C2 boot header region) ---')
for addr in range(0x0000, 0x0200, 64):
data = eeprom_read(addr, 64)
hex_dump(addr, data)
print()
# Step 3: Scan for PLL-like 20-byte blocks
# Format: [count(1-16), A9_val, AA_val, unused_byte, AB_data[count], padding...]
# Sentinel: count=0
print('--- Scanning for PLL config blocks ---')
print(' Format: [count, A9, AA, unused, AB_data[count]]')
print(' Sentinel: count=0')
print()
# Scan the entire EEPROM in 20-byte strides
pll_candidates = []
for addr in range(0, min(eeprom_size, 0x4000), 20):
data = eeprom_read(addr, 20)
count = data[0]
# Look for potential sentinel (count=0) preceded by valid blocks
if count == 0 and addr > 0:
# Check if previous 20 bytes looked like PLL data
prev = eeprom_read(addr - 20, 20)
if 1 <= prev[0] <= 16:
pll_candidates.append({
'sentinel_addr': addr,
'last_block_addr': addr - 20,
'last_count': prev[0],
'last_a9': prev[1],
'last_aa': prev[2],
})
if pll_candidates:
print(' Found sentinel(s):')
for c in pll_candidates:
print(f' Sentinel at 0x{c["sentinel_addr"]:04X}')
print(f' Last block at 0x{c["last_block_addr"]:04X}: '
f'count={c["last_count"]} A9=0x{c["last_a9"]:02X} AA=0x{c["last_aa"]:02X}')
# Walk backwards to find start of PLL data
start = c['last_block_addr']
while start >= 20:
prev = eeprom_read(start - 20, 20)
if 1 <= prev[0] <= 16:
start -= 20
else:
break
print(f' PLL data likely starts at: 0x{start:04X}')
# Dump the PLL blocks
print(f' PLL block dump:')
for baddr in range(start, c['sentinel_addr'] + 20, 20):
block = eeprom_read(baddr, 20)
cnt = block[0]
if cnt == 0:
print(f' 0x{baddr:04X}: [sentinel count=0]')
break
ab = block[4:4 + cnt]
print(f' 0x{baddr:04X}: count={cnt} A9=0x{block[1]:02X} '
f'AA=0x{block[2]:02X} unused=0x{block[3]:02X} '
f'AB=[{ab.hex(" ")}]')
else:
print(' No PLL sentinel found in first 16KB!')
print(' Dumping any 20-byte-aligned blocks with count 1-16:')
for addr in range(0, min(eeprom_size, 0x1000), 20):
data = eeprom_read(addr, 20)
count = data[0]
if 1 <= count <= 16:
ab = data[4:4 + count]
print(f' 0x{addr:04X}: count={count} A9=0x{data[1]:02X} '
f'AA=0x{data[2]:02X} unused=0x{data[3]:02X} '
f'AB=[{ab.hex(" ")}]')
print()
# Step 4: Dump around the 16KB boundary (where our code expects PLL data)
if eeprom_size > 16384:
print('--- EEPROM 0x3FE0-0x4060 (16KB boundary) ---')
for addr in range(0x3FE0, 0x4060, 64):
data = eeprom_read(addr, 64)
hex_dump(addr, data)
print()
# Step 5: Check for 0xFF regions (empty/erased)
print('--- Empty region scan ---')
last_was_ff = False
for addr in range(0, min(eeprom_size, 0x4000), 64):
data = eeprom_read(addr, 64)
is_ff = all(b == 0xFF for b in data)
if is_ff and not last_was_ff:
print(f' 0xFF starts at 0x{addr:04X}')
last_was_ff = True
elif not is_ff and last_was_ff:
print(f' Data resumes at 0x{addr:04X}')
last_was_ff = False
if last_was_ff:
print(f' 0xFF continues to end of scanned region')
sw.close()
print()
print('=== Done ===')