From ba37105e2a35035177e83ad1ec41d77135ffe65d Mon Sep 17 00:00:00 2001 From: Ryan Malloy Date: Wed, 11 Feb 2026 05:08:20 -0700 Subject: [PATCH] Extract real FX2 firmware from I2C EEPROM Previous RAM dumps via 0xA0 vendor request turned out to be live FIFO data, not firmware - the Genpix FX2 firmware overrides the standard 0xA0 handler. Discovered that I2C_READ (0x84) with wValue=0x51 and wIndex=offset reads the boot EEPROM directly. EEPROM contents (Cypress C2 format): - VID:PID 09C0:0203, config 0x40 (400kHz I2C) - 9,472 bytes of 8051 firmware in 10 load records - Code range 0x0000-0x24FF, entry at LJMP 0x188D - Ghidra auto-analysis finds 61 functions Tools: eeprom_dump.py (full dump), eeprom_probe.py (I2C protocol discovery) --- firmware-dump/skywalker1_eeprom.bin | Bin 0 -> 16384 bytes firmware-dump/skywalker1_eeprom_flat.bin | Bin 0 -> 9472 bytes firmware-dump/skywalker1_eeprom_full64k.bin | Bin 0 -> 65536 bytes tools/eeprom_dump.py | 251 ++++++++++++++++++++ tools/eeprom_probe.py | 101 ++++++++ 5 files changed, 352 insertions(+) create mode 100644 firmware-dump/skywalker1_eeprom.bin create mode 100644 firmware-dump/skywalker1_eeprom_flat.bin create mode 100644 firmware-dump/skywalker1_eeprom_full64k.bin create mode 100644 tools/eeprom_dump.py create mode 100644 tools/eeprom_probe.py diff --git a/firmware-dump/skywalker1_eeprom.bin b/firmware-dump/skywalker1_eeprom.bin new file mode 100644 index 0000000000000000000000000000000000000000..91bc611e840a4f0473e7ca95c280033b530ed241 GIT binary patch literal 16384 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 literal 0 HcmV?d00001 diff --git a/firmware-dump/skywalker1_eeprom_flat.bin b/firmware-dump/skywalker1_eeprom_flat.bin new file mode 100644 index 0000000000000000000000000000000000000000..fdc824761d8b5845febc06536f1b1666a79ef871 GIT binary patch literal 9472 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 literal 0 HcmV?d00001 diff --git a/tools/eeprom_dump.py b/tools/eeprom_dump.py new file mode 100644 index 0000000..169caef --- /dev/null +++ b/tools/eeprom_dump.py @@ -0,0 +1,251 @@ +#!/usr/bin/env python3 +""" +Genpix SkyWalker-1 EEPROM firmware dump tool. + +Reads the Cypress FX2 boot EEPROM via the I2C_READ vendor command. +Protocol: I2C_READ (0x84), wValue=0x51, wIndex=offset, length=chunk_size + +The EEPROM contains firmware in Cypress C2 IIC boot format: + - Header: C2 VID_L VID_H PID_L PID_H DID_L DID_H CONFIG + - Records: LEN_H LEN_L ADDR_H ADDR_L DATA[LEN] + - End: 80 01 ENTRY_H ENTRY_L (reset vector) +""" +import usb.core, usb.util, sys, struct + +VENDOR_ID = 0x09C0 +PRODUCT_ID = 0x0203 +I2C_READ = 0x84 +EEPROM_SLAVE = 0x51 + + +def find_device(): + dev = usb.core.find(idVendor=VENDOR_ID, idProduct=PRODUCT_ID) + if dev is None: + print("SkyWalker-1 not found") + sys.exit(1) + return dev + + +def detach_driver(dev): + intf_num = None + for cfg in dev: + for intf in cfg: + if dev.is_kernel_driver_active(intf.bInterfaceNumber): + try: + dev.detach_kernel_driver(intf.bInterfaceNumber) + intf_num = intf.bInterfaceNumber + except usb.core.USBError as e: + print(f"Cannot detach driver: {e}") + print("Try: sudo modprobe -r dvb_usb_gp8psk") + sys.exit(1) + try: + dev.set_configuration() + except: + pass + return intf_num + + +def eeprom_read(dev, offset, length=64): + """Read from EEPROM at given offset.""" + # wIndex holds the EEPROM byte offset (16-bit, so max 64KB) + return dev.ctrl_transfer( + usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_IN, + I2C_READ, EEPROM_SLAVE, offset, length, 2000) + + +def parse_c2_header(data): + """Parse Cypress C2 boot EEPROM header.""" + if data[0] != 0xC2: + print(f" Not a C2 EEPROM (first byte: 0x{data[0]:02X})") + return None + + vid = data[2] << 8 | data[1] + pid = data[4] << 8 | data[3] + did = data[6] << 8 | data[5] + config = data[7] + + print(f" Format: C2 (Large EEPROM, code loads to internal RAM)") + print(f" VID: 0x{vid:04X} {'(Genpix)' if vid == 0x09C0 else ''}") + print(f" PID: 0x{pid:04X} {'(SkyWalker-1)' if pid == 0x0203 else ''}") + print(f" DID: 0x{did:04X}") + print(f" Config: 0x{config:02X}", end="") + + config_flags = [] + if config & 0x40: + config_flags.append("400kHz I2C") + if config & 0x04: + config_flags.append("disconnect") + if config_flags: + print(f" ({', '.join(config_flags)})") + else: + print() + + return {"vid": vid, "pid": pid, "did": did, "config": config} + + +def parse_records(data, offset=8): + """Parse C2 load records from EEPROM data.""" + records = [] + while offset < len(data) - 4: + rec_len = (data[offset] << 8) | data[offset + 1] + rec_addr = (data[offset + 2] << 8) | data[offset + 3] + + if rec_len == 0x8001: + # End marker - rec_addr is the entry point (reset vector) + records.append({ + "type": "end", + "entry_point": rec_addr, + "offset": offset + }) + break + elif rec_len == 0 or rec_len > 0x4000: + records.append({ + "type": "invalid", + "raw_len": rec_len, + "offset": offset + }) + break + + rec_data = data[offset + 4:offset + 4 + rec_len] + records.append({ + "type": "data", + "length": rec_len, + "load_addr": rec_addr, + "data": bytes(rec_data), + "offset": offset + }) + offset += 4 + rec_len + + return records + + +def main(): + import argparse + parser = argparse.ArgumentParser(description="Dump SkyWalker-1 EEPROM firmware") + parser.add_argument('-o', '--output', default='skywalker1_eeprom.bin', + help='Output file for raw EEPROM dump') + parser.add_argument('--extract', action='store_true', + help='Also extract firmware as flat binary') + parser.add_argument('--max-size', type=int, default=16384, + help='Maximum EEPROM size to read (default: 16384)') + args = parser.parse_args() + + print("Genpix SkyWalker-1 EEPROM Dump") + print("=" * 40) + + dev = find_device() + print(f"Found device: Bus {dev.bus} Addr {dev.address}") + intf = detach_driver(dev) + + try: + # Read EEPROM + chunk_size = 64 # Max reliable USB control transfer + eeprom = bytearray() + consecutive_ff = 0 + + print(f"\nReading EEPROM (max {args.max_size} bytes)...") + + for offset in range(0, args.max_size, chunk_size): + # wIndex only goes up to 0xFFFF, which covers 64KB EEPROMs + data = eeprom_read(dev, offset, chunk_size) + + if data is None: + print(f"\n Read failed at offset 0x{offset:04X}") + break + + chunk = bytes(data) + eeprom.extend(chunk) + + # Check for end of data + if all(b == 0xFF for b in chunk): + consecutive_ff += 1 + if consecutive_ff >= 4: + print(f"\r End of data at 0x{len(eeprom):04X} (0xFF padding) ") + break + else: + consecutive_ff = 0 + + if offset % 1024 == 0: + print(f"\r 0x{offset:04X} / 0x{args.max_size:04X} ", end="", flush=True) + + print(f"\r Read {len(eeprom)} bytes total ") + + # Save raw EEPROM + with open(args.output, 'wb') as f: + f.write(eeprom) + print(f" Saved raw EEPROM to: {args.output}") + + # Parse header + print(f"\n{'=' * 40}") + print("EEPROM Header:") + header = parse_c2_header(eeprom) + + if header: + # Parse load records + print(f"\nLoad Records:") + records = parse_records(eeprom) + total_code = 0 + entry_point = None + + for i, rec in enumerate(records): + if rec["type"] == "data": + end_addr = rec["load_addr"] + rec["length"] - 1 + preview = rec["data"][:8].hex(' ') + print(f" [{i}] {rec['length']:5d} bytes -> " + f"0x{rec['load_addr']:04X}-0x{end_addr:04X} " + f"[{preview}...]") + total_code += rec["length"] + elif rec["type"] == "end": + entry_point = rec["entry_point"] + print(f" [{i}] END MARKER -> entry point: 0x{entry_point:04X}") + else: + print(f" [{i}] INVALID (raw_len=0x{rec['raw_len']:04X}) " + f"at EEPROM offset 0x{rec['offset']:04X}") + + print(f"\n Total firmware: {total_code} bytes in " + f"{sum(1 for r in records if r['type'] == 'data')} records") + if entry_point: + print(f" Entry point: 0x{entry_point:04X} (LJMP target after boot)") + + # Extract flat binary + if args.extract and records: + # Build memory image + mem = bytearray(0x10000) # 64KB address space + for b in range(len(mem)): + mem[b] = 0xFF + + for rec in records: + if rec["type"] == "data": + addr = rec["load_addr"] + mem[addr:addr + rec["length"]] = rec["data"] + + # Find actual used range + min_addr = min(r["load_addr"] for r in records if r["type"] == "data") + max_addr = max(r["load_addr"] + r["length"] + for r in records if r["type"] == "data") + + flat_file = args.output.replace('.bin', '_flat.bin') + with open(flat_file, 'wb') as f: + f.write(mem[min_addr:max_addr]) + print(f"\n Flat binary: {flat_file}") + print(f" Address range: 0x{min_addr:04X}-0x{max_addr:04X} " + f"({max_addr - min_addr} bytes)") + + # Also save full 64KB image for Ghidra + full_file = args.output.replace('.bin', '_full64k.bin') + with open(full_file, 'wb') as f: + f.write(mem) + print(f" Full 64K image: {full_file} (for Ghidra, load at 0x0000)") + + finally: + if intf is not None: + try: + usb.util.release_interface(dev, intf) + dev.attach_kernel_driver(intf) + print("\nRe-attached kernel driver") + except: + print("\nNote: run 'sudo modprobe dvb_usb_gp8psk' to reload") + + +if __name__ == '__main__': + main() diff --git a/tools/eeprom_probe.py b/tools/eeprom_probe.py new file mode 100644 index 0000000..5d7bcd2 --- /dev/null +++ b/tools/eeprom_probe.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 +"""Probe I2C EEPROM address setting on Genpix SkyWalker-1.""" +import usb.core, usb.util, time + +dev = usb.core.find(idVendor=0x09C0, idProduct=0x0203) +for cfg in dev: + for intf in cfg: + if dev.is_kernel_driver_active(intf.bInterfaceNumber): + dev.detach_kernel_driver(intf.bInterfaceNumber) + break +try: + dev.set_configuration() +except: + pass + +I2C_READ = 0x84 +I2C_WRITE = 0x83 + +def vin(req, value=0, index=0, length=64): + try: + return dev.ctrl_transfer( + usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_IN, + req, value, index, length, 2000) + except: + return None + +def vout(req, value=0, index=0, data=b''): + try: + return dev.ctrl_transfer( + usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_OUT, + req, value, index, data, 2000) + except Exception as e: + print(f" OUT error: {e}") + return None + +# Known first 8 bytes at offset 0: c2 c0 09 03 02 00 00 40 +# Known bytes at offset 8 (from our read): 03 ff 00 00 02 18 8d 30 +REF_0 = "c2 c0 09 03 02 00 00 40" +REF_8 = "03 ff 00 00 02 18 8d 30" + +def show(label, data): + if data is not None: + h = bytes(data[:8]).hex(' ') + match = "" + if h == REF_0: match = " <-- OFFSET 0" + elif h == REF_8: match = " <-- OFFSET 8" + print(f" {label}: {h}{match}") + else: + print(f" {label}: FAILED") + +print("=== Approach 1: I2C_WRITE data=[addr_h, addr_l], then I2C_READ ===") +vout(I2C_WRITE, 0x51, 0, bytes([0x00, 0x08])) +show("After set 0x0008", vin(I2C_READ, 0x51, 0, 8)) +vout(I2C_WRITE, 0x51, 0, bytes([0x00, 0x00])) +show("After set 0x0000", vin(I2C_READ, 0x51, 0, 8)) + +print("\n=== Approach 2: wValue=addr, wIndex=slave ===") +vout(I2C_WRITE, 0x0008, 0x51) +show("After set 0x0008", vin(I2C_READ, 0x51, 0, 8)) +vout(I2C_WRITE, 0x0000, 0x51) +show("After set 0x0000", vin(I2C_READ, 0x51, 0, 8)) + +print("\n=== Approach 3: wValue=slave, wIndex=addr ===") +vout(I2C_WRITE, 0x51, 0x0008) +show("After set 0x0008", vin(I2C_READ, 0x51, 0, 8)) +vout(I2C_WRITE, 0x51, 0x0000) +show("After set 0x0000", vin(I2C_READ, 0x51, 0, 8)) + +print("\n=== Approach 4: I2C_READ with wIndex=offset ===") +show("wIndex=0x0000", vin(I2C_READ, 0x51, 0x0000, 8)) +show("wIndex=0x0008", vin(I2C_READ, 0x51, 0x0008, 8)) +show("wIndex=0x0040", vin(I2C_READ, 0x51, 0x0040, 8)) + +print("\n=== Approach 5: I2C_READ with (slave<<8|offset) in wValue ===") +show("wValue=0x5100", vin(I2C_READ, 0x5100, 0, 8)) +show("wValue=0x5108", vin(I2C_READ, 0x5108, 0, 8)) + +print("\n=== Approach 6: I2C_READ with wValue=offset (no slave) ===") +show("wValue=0x0000", vin(I2C_READ, 0x0000, 0, 8)) +show("wValue=0x0008", vin(I2C_READ, 0x0008, 0, 8)) +show("wValue=0x0040", vin(I2C_READ, 0x0040, 0, 8)) + +print("\n=== Approach 7: Larger reads to check page boundaries ===") +data = vin(I2C_READ, 0x51, 0, 64) +if data: + show("First 8 of 64", data[:8]) + show("Bytes 8-15", data[8:16]) + show("Bytes 56-63", data[56:64]) + # Check if bytes 8-15 match REF_8 + if bytes(data[8:16]).hex(' ') == REF_8: + print(" ** Bytes 8-15 match expected offset 8 data!") + print(" ** The 64-byte read IS returning sequential EEPROM data!") + +# Reattach +for cfg in dev: + for intf in cfg: + try: + usb.util.release_interface(dev, intf.bInterfaceNumber) + dev.attach_kernel_driver(intf.bInterfaceNumber) + except: + pass