From 757da08987ce456aa1c41dc0652f50391e2139ae Mon Sep 17 00:00:00 2001 From: Ryan Malloy Date: Wed, 11 Feb 2026 04:51:34 -0700 Subject: [PATCH] Add FX2 firmware dumps and USB probe tool Dumped 8KB internal RAM and 64KB external RAM from SkyWalker-1 serial #00857 via Cypress FX2 vendor request 0xA0. Device reports FW v2.06.4 (build 2007-07-13). Tool also scans all vendor USB commands and probes device status registers. --- firmware-dump/skywalker1_fx2_external.bin | Bin 0 -> 65536 bytes firmware-dump/skywalker1_fx2_internal.bin | Bin 0 -> 8192 bytes tools/fw_dump.py | 292 ++++++++++++++++++++++ 3 files changed, 292 insertions(+) create mode 100644 firmware-dump/skywalker1_fx2_external.bin create mode 100644 firmware-dump/skywalker1_fx2_internal.bin create mode 100644 tools/fw_dump.py diff --git a/firmware-dump/skywalker1_fx2_external.bin b/firmware-dump/skywalker1_fx2_external.bin new file mode 100644 index 0000000000000000000000000000000000000000..9bacefb1cd2fb94ab4cdf977a5ce821fa70c91ad GIT binary patch literal 65536 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 literal 0 HcmV?d00001 diff --git a/firmware-dump/skywalker1_fx2_internal.bin b/firmware-dump/skywalker1_fx2_internal.bin new file mode 100644 index 0000000000000000000000000000000000000000..b8b51101d73c1ea5fadd092ff79ec11f83466af6 GIT binary patch literal 8192 zcmb_hJ#X7q6cwciLRY3)3T9!`BBSA<1v+`m*fC21b@M2|8UnPi1#9sZ)ohTt($+CQ zzzC2(;G#bu2}&oM6r?cVvG4Hm=)FfiK0Q+P0W4a8$@_WkxsR*tZYqc#MoE<5`ioal zCb*s7o(ul&1Gv68znBa1U-FZc>u0v|;O&~oGlfOI_;l7k>z@jGhrJj`eotNqo<=_^ zr0aA&c{X_}*xcX5f@vSfj%~n`@p`-wlqP1Yt*>{}qpOd=Z81IpG)HXB{7;24{nI%h(J6xUojfI>*%=v zKBb@1>-2iQ#rYk9__vF8?fzG|SCL2LGMmIia+O@k^)yl_vgFECk=r+^V3F4F^5Eq` zbT1kT;sFpHLxywhfkCshhM1{6VTo{q|zNp$Vs;Yet9lmDnm^1jJJo?L* zHSH0*5xrQg)sULXjvbxSqNlYb5>a#>W2^E$VCL=q%#Oh(=+LUo)&4?{E#EEQo46bi zv&XBY%UJcDhk;fzGSJ)g|IvT@bYKX_)w?daVtsUry5`099XI$KKPr&M3c{MK!i%}9 z4t+V5Oh^^sxA=qKVU?6_1mF=t$t7Tv8k?kxDxdIqVNcU_%Lo?Jp=39opR8Y2?&WCq z7yyqe57czCadhu!D9C@48R?#qu;0h8&PiyrjBsbubf|W>$(-_3=9_&q?T4@q=h%ZA z*dID8T)tFuT+MHFUf9(er&aCXddAAB97HD_zFvMSvv@}qmCk|Ev9fr-7ejC1nQe6! zd6OAY+3-x+X0K3#ix2dnp%x!C*P>`g+O=P~ct9fcSL>nBsPYjNR;D<_Gwb0Oh zb-wG(! z&zn%5=az&|b5knz-|{S=7J2MZ-t9NfRO}kL-P{vw)&O^)&nNM|`~mwoi{{t5F8DlC z5j*>H+ov`QVXq6>?1Tly7hD%QV&~STxvjeUn7P><{XFA_Shb$}p?;Fel*OW=IG8=X z(J^x3na-5kyRCLB_Xy4NCd+SkirPkjoU~&Md#38THX^6aPPpH(HkvAnw}w%bXH$~y!i*WcVs3^(aJs~yO3znc4~Cr^EaLEbQ6|wo9~3Vc^c(oI+wof z#j97<=(U?eesyJa5Asy~ndy^UyDmfbIKMjA)oru3W`>X*ms-JfqRReveTHxXc*n_< zRz)}GMC+fn%169$+0E}l?1v)Y))bwF?@i3izWRr@x;{hfhxX=afVmf$eJR)O?o{XP z<&N02A<|QP8XQ|{1Kid9W261&Y-p$2sK9gO|2BFXoQJ4W!pY|+JExR(XcpwuudiXHD@Y5@OPJU zL(WR+vF;Jjedg}3>iL>I{i5N%_Ltfs@NH3@slgpxc!Pd9t6$Zm*tPpqP4hpl+Rp&I z@8eFcPa-rY?(%Nm{ej90?=-ud0=jq~--U2Lwhc3Xx)$}-uBz0h9o@P}&jfH@sOr{L n%eTy>u(DKxR@u#O)J^_1`OB@gt7lEwak2ky--f#w-tW_Y<11g; literal 0 HcmV?d00001 diff --git a/tools/fw_dump.py b/tools/fw_dump.py new file mode 100644 index 0000000..05a45d7 --- /dev/null +++ b/tools/fw_dump.py @@ -0,0 +1,292 @@ +#!/usr/bin/env python3 +""" +Genpix SkyWalker-1 firmware probe and dump tool. + +The SkyWalker-1 uses a Cypress FX2 (EZ-USB) microcontroller. +FX2 devices support reading internal RAM (8KB at 0x0000-0x1FFF) +and external RAM via standard vendor requests: + - bRequest=0xA0 (FX2 firmware load/read) + - wValue=address, wIndex=0 + +This tool also queries Genpix-specific vendor commands to gather +device info before attempting a firmware dump. +""" + +import sys +import struct +import argparse +from datetime import datetime + +try: + import usb.core + import usb.util +except ImportError: + print("pyusb required: pip install pyusb") + sys.exit(1) + +VENDOR_ID = 0x09C0 +PRODUCT_ID = 0x0203 + +# Genpix vendor commands (from SkyWalker1Control.h) +CMD_GET_USB_SPEED = 0x07 +CMD_FW_VERSION_READ = 0x0B +CMD_VENDOR_STRING_READ = 0x0C +CMD_PRODUCT_STRING_READ = 0x0D +CMD_RESET_FX2 = 0x13 +CMD_FW_BCD_VERSION_READ = 0x14 +CMD_GET_8PSK_CONFIG = 0x80 +CMD_GET_SIGNAL_STRENGTH = 0x87 +CMD_GET_SIGNAL_LOCK = 0x90 +CMD_GET_SERIAL_NUMBER = 0x93 + +# FX2 standard vendor request for RAM access +FX2_RAM_REQUEST = 0xA0 + +# FX2 memory map +FX2_INTERNAL_RAM_SIZE = 0x2000 # 8KB internal RAM +FX2_EXTERNAL_RAM_SIZE = 0x10000 # Up to 64KB external + +# Config status bits +CONFIG_BITS = { + 0x01: "8PSK Started", + 0x02: "BCM4500 FW Loaded", + 0x04: "Intersil LNB On", + 0x08: "DVB Mode", + 0x10: "22kHz Tone", + 0x20: "18V Selected", + 0x40: "DC Tuned", + 0x80: "Armed (streaming)", +} + + +def find_device(): + dev = usb.core.find(idVendor=VENDOR_ID, idProduct=PRODUCT_ID) + if dev is None: + print("SkyWalker-1 not found. Is it plugged in?") + sys.exit(1) + return dev + + +def vendor_in(dev, request, value=0, index=0, length=64, timeout=2000): + """Send a vendor IN control transfer (device-to-host).""" + try: + return dev.ctrl_transfer( + usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_IN, + request, value, index, length, timeout + ) + except usb.core.USBError as e: + return None + + +def detach_kernel_driver(dev): + """Detach kernel driver if attached.""" + for cfg in dev: + for intf in cfg: + if dev.is_kernel_driver_active(intf.bInterfaceNumber): + try: + dev.detach_kernel_driver(intf.bInterfaceNumber) + print(f" Detached kernel driver from interface {intf.bInterfaceNumber}") + return intf.bInterfaceNumber + except usb.core.USBError as e: + print(f" Warning: Could not detach kernel driver: {e}") + print(" Try running with sudo, or: sudo modprobe -r dvb_usb_gp8psk") + sys.exit(1) + return None + + +def probe_device_info(dev): + """Query all known Genpix info commands.""" + print("\n=== Genpix SkyWalker-1 Device Info ===\n") + + # Firmware version (6 bytes) + data = vendor_in(dev, CMD_FW_VERSION_READ, length=6) + if data is not None and len(data) == 6: + fw_int = (data[2] << 16) | (data[1] << 8) | data[0] + build_date = f"20{data[5]:02d}-{data[4]:02d}-{data[3]:02d}" + print(f" FW Version: {data[2]}.{data[1]:02d}.{data[0]} (0x{fw_int:06x})") + print(f" FW Build: {build_date}") + else: + print(f" FW Version: (failed: {data})") + + # BCD version + data = vendor_in(dev, CMD_FW_BCD_VERSION_READ, length=2) + if data is not None: + print(f" BCD Version: {bytes(data).hex()}") + + # Vendor string + data = vendor_in(dev, CMD_VENDOR_STRING_READ, length=64) + if data is not None: + s = bytes(data).rstrip(b'\x00').decode('ascii', errors='replace') + print(f" Vendor: {s}") + + # Product string + data = vendor_in(dev, CMD_PRODUCT_STRING_READ, length=64) + if data is not None: + s = bytes(data).rstrip(b'\x00').decode('ascii', errors='replace') + print(f" Product: {s}") + + # USB speed + data = vendor_in(dev, CMD_GET_USB_SPEED, length=1) + if data is not None: + speeds = {0: "Low", 1: "Full (12Mbps)", 2: "High (480Mbps)"} + print(f" USB Speed: {speeds.get(data[0], f'Unknown ({data[0]})')}") + + # Serial number + data = vendor_in(dev, CMD_GET_SERIAL_NUMBER, length=8) + if data is not None: + print(f" Serial: {bytes(data).hex()} ({bytes(data).rstrip(b'\\x00').decode('ascii', errors='replace')})") + + # 8PSK config/status + data = vendor_in(dev, CMD_GET_8PSK_CONFIG, length=1) + if data is not None: + status = data[0] + print(f" Config: 0x{status:02x}") + for bit, desc in CONFIG_BITS.items(): + state = "ON" if status & bit else "off" + print(f" [{state:>3}] {desc}") + + print() + + +def dump_fx2_ram(dev, output_file, start=0x0000, size=FX2_INTERNAL_RAM_SIZE, chunk=64): + """ + Attempt to read FX2 internal RAM using the standard FX2 vendor request 0xA0. + + The Cypress FX2 bootloader/firmware typically supports: + - bRequest = 0xA0 + - wValue = start address + - wIndex = 0 + - Direction = IN (device to host) + """ + print(f"=== Attempting FX2 RAM dump: 0x{start:04X} - 0x{start+size-1:04X} ({size} bytes) ===\n") + + firmware = bytearray() + addr = start + errors = 0 + consecutive_errors = 0 + + while addr < start + size: + remaining = (start + size) - addr + read_len = min(chunk, remaining) + + data = vendor_in(dev, FX2_RAM_REQUEST, value=addr, index=0, length=read_len) + + if data is None: + errors += 1 + consecutive_errors += 1 + firmware.extend(b'\xff' * read_len) + if consecutive_errors >= 5: + print(f"\n Stopped: {consecutive_errors} consecutive read failures at 0x{addr:04X}") + print(" Device may not support FX2 RAM readback (EEPROM firmware)") + break + else: + consecutive_errors = 0 + firmware.extend(data) + + if (addr - start) % 0x400 == 0: + pct = ((addr - start) / size) * 100 + print(f" 0x{addr:04X} [{pct:5.1f}%] {'OK' if data is not None else 'FAIL'}", end='\r') + + addr += read_len + + print(f"\n\n Read {len(firmware)} bytes, {errors} chunk errors") + + if firmware and any(b != 0xFF for b in firmware): + with open(output_file, 'wb') as f: + f.write(firmware) + print(f" Saved to: {output_file}") + + # Quick analysis + non_ff = sum(1 for b in firmware if b != 0xFF) + non_zero = sum(1 for b in firmware if b != 0x00) + print(f" Non-0xFF bytes: {non_ff}/{len(firmware)}") + print(f" Non-0x00 bytes: {non_zero}/{len(firmware)}") + + # Check for FX2 reset vector + if len(firmware) >= 3: + print(f" First 16 bytes: {firmware[:16].hex(' ')}") + if firmware[0] == 0x02: + jump_addr = (firmware[1] << 8) | firmware[2] + print(f" Reset vector: LJMP 0x{jump_addr:04X} (typical FX2 firmware)") + else: + print(" No valid data read — dump appears empty") + + return firmware + + +def scan_vendor_commands(dev, start=0x00, end=0xFF): + """Brute-force scan all vendor IN commands to find undocumented ones.""" + print(f"=== Scanning vendor commands 0x{start:02X}-0x{end:02X} ===\n") + found = [] + for cmd in range(start, end + 1): + data = vendor_in(dev, cmd, length=64, timeout=500) + if data is not None and len(data) > 0: + preview = bytes(data[:16]).hex(' ') + is_known = cmd in ( + CMD_GET_USB_SPEED, CMD_FW_VERSION_READ, CMD_VENDOR_STRING_READ, + CMD_PRODUCT_STRING_READ, CMD_FW_BCD_VERSION_READ, CMD_GET_8PSK_CONFIG, + CMD_GET_SIGNAL_STRENGTH, CMD_GET_SIGNAL_LOCK, CMD_GET_SERIAL_NUMBER, + FX2_RAM_REQUEST, + ) + marker = " [KNOWN]" if is_known else " [NEW!]" + print(f" 0x{cmd:02X}: [{len(data):3d} bytes] {preview}...{marker}") + found.append((cmd, data)) + print(f"\n Found {len(found)} responding commands") + return found + + +def main(): + parser = argparse.ArgumentParser(description="Genpix SkyWalker-1 firmware probe/dump tool") + parser.add_argument('--info', action='store_true', help="Query device info") + parser.add_argument('--dump', metavar='FILE', help="Dump FX2 RAM to file") + parser.add_argument('--scan', action='store_true', help="Scan all vendor commands") + parser.add_argument('--start', type=lambda x: int(x, 0), default=0x0000, + help="RAM dump start address (default: 0x0000)") + parser.add_argument('--size', type=lambda x: int(x, 0), default=FX2_INTERNAL_RAM_SIZE, + help=f"RAM dump size (default: 0x{FX2_INTERNAL_RAM_SIZE:X})") + parser.add_argument('--external', action='store_true', + help="Try to dump external RAM (64KB)") + args = parser.parse_args() + + if not any([args.info, args.dump, args.scan]): + args.info = True + args.scan = True + + print(f"Genpix SkyWalker-1 Firmware Tool") + print(f"{'=' * 40}") + + dev = find_device() + print(f"\nFound device: Bus {dev.bus} Addr {dev.address}") + intf = detach_kernel_driver(dev) + + try: + dev.set_configuration() + except usb.core.USBError: + pass # May already be configured + + try: + if args.info: + probe_device_info(dev) + + if args.scan: + scan_vendor_commands(dev) + print() + + if args.dump: + if args.external: + dump_fx2_ram(dev, args.dump, args.start, FX2_EXTERNAL_RAM_SIZE) + else: + dump_fx2_ram(dev, args.dump, args.start, args.size) + + finally: + if intf is not None: + try: + usb.util.release_interface(dev, intf) + dev.attach_kernel_driver(intf) + print("\nRe-attached kernel driver") + except usb.core.USBError: + print("\nNote: Run 'sudo modprobe dvb_usb_gp8psk' to reload driver") + + +if __name__ == '__main__': + main()