From 2e00a054e8653610e65d4d28d913bc29febbd39b Mon Sep 17 00:00:00 2001 From: Ryan Malloy Date: Wed, 11 Feb 2026 06:44:26 -0700 Subject: [PATCH] Add comparative firmware analysis reports from Ghidra 8051 reverse engineering Two detailed reports from analyzing all firmware variants loaded into Ghidra: 1. v2.06 vs v2.13 FW1 comparative analysis: - Complete vendor command dispatch table mapping (0x80-0x9D) - 3 new commands in v2.13: GET_DEMOD_STATUS (0x99), INIT_DEMOD (0x9A), DELAY_COMMAND (0x9C) - DiSEqC architecture change: GPIO bit-bang -> I2C controller - INT0 repurposed from USB re-enumeration to demodulator polling - Hardware revision detection via descriptor byte 2. v2.13 sub-variant comparison (FW1/FW2/FW3): - FW1: I2C-connected demodulator (original SkyWalker-1 hardware) - FW2: Parallel-bus demodulator via P0/P1 GPIO - FW3: Enhanced parallel-bus with dual-phase read and OR accumulation - All three support identical modulation types, differ only in hardware interface --- firmware-analysis-v206-vs-v213.md | 378 ++++++++++++++++++++ firmware-dump/fw_v213_comparison_report.md | 379 +++++++++++++++++++++ 2 files changed, 757 insertions(+) create mode 100644 firmware-analysis-v206-vs-v213.md create mode 100644 firmware-dump/fw_v213_comparison_report.md diff --git a/firmware-analysis-v206-vs-v213.md b/firmware-analysis-v206-vs-v213.md new file mode 100644 index 0000000..54d3de6 --- /dev/null +++ b/firmware-analysis-v206-vs-v213.md @@ -0,0 +1,378 @@ +# Genpix SkyWalker-1 FX2 Firmware Comparative Analysis: v2.06 vs v2.13 FW1 + +## Executive Summary + +v2.13 is a significant evolution of the v2.06 firmware with 21 additional functions (82 vs 61). The key changes are: + +1. **Three new vendor commands** (0x99, 0x9A, 0x9C) for LNB/tuner control +2. **Restructured INT0 handler** with active satellite front-end polling +3. **I2C-based initialization** with retry logic for the satellite demodulator +4. **Version-aware code paths** (hardware revision detection via descriptor byte) +5. **Refactored DiSEqC implementation** using I2C-based bus control +6. **Simplified BCM4500 status polling** (consolidated from 3 register reads to 1) + +--- + +## 1. Vendor Command Dispatch Table Comparison + +Both versions use the same two-stage USB control request dispatch: + +- **Stage 1** (`FUN_CODE_032a` / `FUN_CODE_034e`): Handles standard USB requests (bRequest 0x00-0x0B) via a 12-entry jump table at CODE:033B / CODE:035F. These handle GET_STATUS, CLEAR_FEATURE, SET_FEATURE, SET_ADDRESS, GET_DESCRIPTOR, SET_DESCRIPTOR, GET_CONFIGURATION, SET_CONFIGURATION, GET_INTERFACE, SET_INTERFACE, SYNCH_FRAME, and FW_VERSION_READ (0x0B). + +- **Stage 2** (`FUN_CODE_0056`, identical in both versions): Handles vendor requests (bmRequestType bit 6 set, bRequest 0x80-0x9D) via a 30-entry jump table at CODE:0076. Range check: `(bRequest + 0x80) < 0x1E`, dispatching to `JMP @A+DPTR` at `0x76 + (bRequest - 0x80) * 2`. + +### Vendor Command Jump Table (0x80-0x9D) + +| bRequest | Name | v2.06 Target | v2.13 Target | Status | +|----------|------|-------------|-------------|--------| +| 0x80 | GET_8PSK_CONFIG | 0x00B2 | 0x00B2 | **Both**: Read config byte to EP0BUF (v2.06: IRAM 0x6D, v2.13: IRAM 0x4F) | +| 0x81 | SET_8PSK_CONFIG | 0x0326 (STALL) | 0x034A (STALL) | **Both STALL** - not implemented in either | +| 0x82 | (reserved) | 0x0326 (STALL) | 0x034A (STALL) | **Both STALL** | +| 0x83 | I2C_WRITE | 0x00F1 | 0x00F1 | **Both**: Same I2C write handler | +| 0x84 | I2C_READ | 0x0102 | 0x0102 | **Both**: Same I2C read handler | +| 0x85 | ARM_TRANSFER | 0x0110 | 0x0110 | **Both**: Same ARM transfer handler | +| 0x86 | TUNE_8PSK | 0x012E | 0x012E | **Both**: Same tuning handler | +| 0x87 | GET_SIGNAL_STRENGTH | 0x0140 | 0x0162 | **CHANGED** - see section below | +| 0x88 | LOAD_BCM4500 | 0x0326 (STALL) | 0x034A (STALL) | **Both STALL** - BCM4500 loading via different mechanism | +| 0x89 | BOOT_8PSK | 0x00C4 | 0x00C4 | **Both**: Same boot handler | +| 0x8A | START_INTERSIL | 0x019C | 0x01BE | **Relocated** but functionally similar | +| 0x8B | SET_LNB_VOLTAGE | 0x01CB | 0x01ED | **Relocated** but functionally similar | +| 0x8C | SET_22KHZ_TONE | 0x01DD | 0x01FF | **Relocated** but functionally similar | +| 0x8D | SEND_DISEQC_COMMAND | 0x01EF | 0x0211 | **CHANGED** - different DiSEqC implementation | +| 0x8E | SET_DVB_MODE | 0x0326 (STALL) | 0x034A (STALL) | **Both STALL** | +| 0x8F | (unknown) | 0x01FC | 0x021E | Both: Similar read-back function | +| 0x90 | GET_SIGNAL_LOCK | 0x020B | 0x022D | **Relocated** but functionally similar | +| 0x91 | (unknown) | 0x022C | 0x024E | Both: I2C read-back | +| 0x92 | (unknown) | 0x024A | 0x026C | Both: I2C read-back | +| 0x93 | GET_SERIAL_NUMBER | 0x026F | 0x0293 | **Relocated** but functionally similar | +| 0x94 | (unknown) | 0x01B9 | 0x01DB | Both: LNB-related | +| 0x95 | (unknown) | 0x02DF | 0x0303 | Both: Read-back function | +| 0x96 | (unknown) | 0x02B4 | 0x02D8 | Both: Similar handler | +| 0x97 | (unknown) | 0x02C1 | 0x02E5 | Both: Similar handler | +| 0x98 | (unknown) | 0x02CB | 0x02EF | Both: Similar handler | +| 0x99 | **NEW: GET_DEMOD_STATUS** | 0x0326 (STALL) | 0x0317 | **ADDED in v2.13** | +| 0x9A | **NEW: INIT_DEMOD** | 0x0326 (STALL) | 0x0140 | **ADDED in v2.13** | +| 0x9B | (reserved) | 0x0326 (STALL) | 0x034A (STALL) | **Both STALL** | +| 0x9C | **NEW: DELAY_COMMAND** | 0x0326 (STALL) | 0x032B | **ADDED in v2.13** | +| 0x9D | SET_MODE_FLAG | 0x02FA | 0x033A | **CHANGED** - different implementation | + +### Commands Added in v2.13 + +**0x99 - GET_DEMOD_STATUS (new read command)** +``` +LCALL FUN_CODE_2421 ; calls FUN_CODE_2239(0x3F, 0xF9) + ; -> I2C read from device 0x3F, register 0xF9 +MOV EP0BUF[0], R7 ; return I2C read result +EP0BCL = 1 ; send 1 byte back +``` +Reads demodulator register 0xF9 via I2C (device address 0x3F) and returns the value to the host. This is a status/diagnostics register read for the satellite demodulator IC. + +**0x9A - INIT_DEMOD (new control command)** +``` +LCALL 0x231E ; EP0 flush/prepare +if (config_flags.0 == 1) { ; check if demodulator is present + counter = 0; + while (counter < 3) { + LCALL FUN_CODE_1977 ; initialization step + if (success) break; + counter++; + } +} +EP0BCL = 0 ; no data returned +``` +Performs up to 3 attempts to initialize the demodulator, but only if the demodulator-present flag (bit 0 of DAT_INTMEM_4F) is set. This provides host-triggered re-initialization capability. + +**0x9C - DELAY_COMMAND (new control command)** +``` +R7 = wValue ; delay parameter from USB SETUP packet +LCALL FUN_CODE_1ac6 ; tuning/acquisition delay function +EP0BCL = 0 ; no data returned +``` +Calls FUN_CODE_1ac6 with the wValue parameter from the USB SETUP packet. FUN_CODE_1ac6 performs an I2C-based tuning acquisition sequence: it reads demod register 0xF9, writes control values to registers 0xF8 and 0xF9, then polls register 0xF9 up to 40 times (0x28) waiting for bit 0 to be set (lock acquired). If lock fails, it calls FUN_CODE_1e3c which performs a full demodulator reset sequence. + +### Commands Removed from v2.13 +None were removed -- all commands that were STALL in v2.06 remain STALL in v2.13. + +### Changed Command Implementations + +**0x87 - GET_SIGNAL_STRENGTH (modified)** +- v2.06 at 0x0140: Checks DAT_INTMEM_6D bit 0 (demod active), reads three I2C status registers (0xA2, 0xA8, 0xA4) to compute signal quality, loops up to 6 iterations polling for demod readiness +- v2.13 at 0x0162: Checks DAT_INTMEM_4F bit 0 (demod active), reads I2C but uses different function call chain (FUN_CODE_1278 vs FUN_CODE_0c97). Same overall logic with relocated internal variables. + +**0x8D - SEND_DISEQC_COMMAND (significantly changed)** +- v2.06: `LCALL 0x23e0; LCALL 0x1e41` -- calls a GPIO-based DiSEqC implementation (0x1e41) that directly manipulates P0 port pins and uses timer-based bit-banging with FUN_CODE_2098 for tone modulation +- v2.13: `LCALL 0x231e; LCALL FUN_CODE_0dbc` -- calls an I2C-based DiSEqC implementation that: + 1. Reads wLength (0xE6BE) as message byte count + 2. Pulls P0.3 low (power enable for DiSEqC bus) + 3. Delays 15 units via FUN_CODE_14b9 + 4. If message bytes present: iterates through EP0BUF data, sending each byte via `func_0x2060` (I2C-based DiSEqC bus write) + 5. If wValue == 0 and no bytes: calls `func_0x22b0` (tone burst command) + 6. If wValue != 0 and no bytes: calls `func_0x2060(0, 0xFF)` (continuous tone) + +This is a major architectural change: v2.06 uses GPIO bit-banging for DiSEqC, while v2.13 delegates DiSEqC to a dedicated I2C-connected controller chip. + +**0x9D - SET_MODE_FLAG (different logic)** +- v2.06: Reads byte at descriptor_base + 10, checks if value is 4, 5, or 6, and conditionally sets bit flag 0x06 based on wValue - 1 +- v2.13: Simply checks if wValue != 0, and if so calls FUN_CODE_21d1 which performs a conditional demodulator reset (calls FUN_CODE_1e3c if `_1_4` flag isn't already set, then writes I2C control registers 0xFC on both device 0x7F and 0x3F) + +--- + +## 2. Key Function Correspondence + +| v2.06 Function | v2.13 Function | Role | +|---------------|---------------|------| +| `main` (0x188D) | `main_entry` (0x170D) | RESET vector - clears IRAM, processes init table, jumps to init | +| `FUN_CODE_09a7` (0x09A7) | `FUN_CODE_0800` (0x0800) | Main init + main loop | +| `FUN_CODE_13c3` (0x13C3) | `FUN_CODE_11ab` (0x11AB) | USB/peripheral descriptor setup | +| `FUN_CODE_032a` (0x032A) | `FUN_CODE_034e` (0x034E) | Standard USB request handler | +| `FUN_CODE_0056` (0x0056) | `FUN_CODE_0056` (0x0056) | Vendor request dispatcher (identical code) | +| `FUN_CODE_2297` (0x2297) | `FUN_CODE_21ec` (0x21EC) | Main loop poll (USB IRQ processing) | +| `FUN_CODE_21ed` (0x21ED) | `FUN_CODE_2189` (0x2189) | EP2CS setup + PCON idle | +| `FUN_CODE_211d` (0x211D) | `FUN_CODE_20b9` (0x20B9) | CPUCS reset pulse (EP2 management) | +| `FUN_CODE_2174` (0x2174) | `FUN_CODE_2110` (0x2110) | USB descriptor type walker (identical code) | +| `FUN_CODE_1919` (0x1919) | `FUN_CODE_1800` (0x1800) | GPIF/FIFO management (identical logic) | +| `FUN_CODE_1d4f` (0x1D4F) | -- | v2.06 demod init (GPIO-based, complex) | +| -- | `FUN_CODE_1d4b` (0x1D4B) | v2.13 demod init (I2C write 4 bytes to 0x7F/0xF0) | +| `FUN_CODE_1da8` (0x1DA8) | -- | v2.06 I2C read with timeout (uses FUN_CODE_1556) | +| -- | `FUN_CODE_0eea` (0x0EEA) | v2.13 I2C read with retry (20 attempts) | +| `FUN_CODE_1dfb` (0x1DFB) | `FUN_CODE_14b9` (0x14B9) | Delay loop (clock-dependent timing) | +| `FUN_CODE_1cf3` (0x1CF3) | `FUN_CODE_1c44` (0x1C44) | Configuration update function | +| `FUN_CODE_12ea` (0x12EA) | `FUN_CODE_1000` (0x1000) | USB endpoint configuration | +| `FUN_CODE_0ddd` (0x0DDD) | `FUN_CODE_0ca4` (0x0CA4) | BCM4500 firmware loader | +| `FUN_CODE_2000` (0x2000) | `FUN_CODE_208d` (0x208D) | BCM4500 status polling | +| `FUN_CODE_1556` (0x1556) | `FUN_CODE_0eea` (0x0EEA) | I2C multi-byte transfer | +| `FUN_CODE_24d2` (0x24D2) | `FUN_CODE_243d` (0x243D) | SET_DVB_MODE config store | +| `FUN_CODE_2419` (0x2419) | `FUN_CODE_2357` (0x2357) | GET config byte to EP0BUF | +| `FUN_CODE_23cb` (0x23CB) | `FUN_CODE_2309` (0x2309) | Read descriptor byte to EP0BUF | +| `FUN_CODE_1a0e` (0x1A0E) | -- | v2.06 serial number reader (EEPROM) | +| `INT0_vec` (0x0003) | `INT0_vector` (0x0003) | INT0 interrupt handler (**significantly different**) | +| -- | `FUN_CODE_2239` (0x2239) | v2.13 I2C single-byte read helper | +| -- | `FUN_CODE_2031` (0x2031) | v2.13 USB reconnect function | +| -- | `FUN_CODE_1799` (0x1799) | v2.13 demod checksum/signature verify | +| -- | `FUN_CODE_1ca0` (0x1CA0) | v2.13 descriptor checksum verify | +| -- | `FUN_CODE_1ac6` (0x1AC6) | v2.13 tuning acquisition sequence | +| -- | `FUN_CODE_1e3c` (0x1E3C) | v2.13 demodulator full reset | +| -- | `FUN_CODE_10d9` (0x10D9) | v2.13 demod status polling/init | +| -- | `FUN_CODE_0dbc` (0x0DBC) | v2.13 I2C-based DiSEqC controller | + +### USB Descriptor Setup (FUN_CODE_13c3 vs FUN_CODE_11ab) + +Both functions are structurally identical: +1. Disable USB disconnect (0xE605 bit 1 clear) +2. Configure IFCONFIG (0xE600) for internal clock, 48MHz +3. Set REVCTL (0xE601) to 0xCA +4. Configure GPIFIDLECTL, PORTACFG +5. Set PORT registers (P0, P3, IPL1) +6. Configure GPIFCTLCFG, FIFORESET, FIFOPINPOLAR +7. Configure Timer2 (RCAP2H=0xF8, RCAP2L=0x2F -> ~2ms period at 48MHz/12) +8. Initialize subsystem modules + +Key difference: v2.13 calls `INT0_vector()` (the INT0 handler) during initialization as a probing step. This runs the demodulator availability check during USB setup, before enabling interrupts. v2.06 does not do this. + +Descriptor pointer offsets: +- v2.06: BANK1_R4:R5 = 0x1200 (descriptor base) +- v2.13: BANK1_R4:R5 = 0x0E00 (descriptor base, lower due to code restructuring) + +--- + +## 3. Structural Differences + +### 3.1 v2.13 Retry Loops (FUN_CODE_1799 and FUN_CODE_1ca0) + +In the main init function `FUN_CODE_0800`, v2.13 has: + +```c +// Retry loop 1: FUN_CODE_1799 - demodulator signature verification +DAT_INTMEM_36 = 0x14; // 20 attempts +while (DAT_INTMEM_36 != 0 && FUN_CODE_1799() fails) { + DAT_INTMEM_36--; +} +if (DAT_INTMEM_36 == 0) { + FUN_CODE_1ac6(100); // tuning acquisition with 100ms delay +} + +// Retry loop 2: FUN_CODE_1ca0 - descriptor checksum verification +DAT_INTMEM_36 = 0x14; // 20 attempts +while (DAT_INTMEM_36 != 0 && FUN_CODE_1ca0() fails) { + DAT_INTMEM_36--; +} +if (DAT_INTMEM_36 == 0) { + FUN_CODE_1ac6(100); // tuning acquisition with 100ms delay +} +``` + +**FUN_CODE_1799 - Demodulator Signature Verification:** +1. Calls FUN_CODE_1d4b() which writes 4 bytes via I2C to device 0x7F, register 0xF0 (demodulator control) +2. Saves parameters DAT_INTMEM_39:3A +3. Checks if parameters match 0x021C (known good value) - returns early if match +4. Reads 5 I2C bytes via FUN_CODE_0718, each at register 0x0A offset (stepping by 2) +5. Subtracts 0x30 ('0') from each byte (ASCII to binary conversion) +6. Sums the values and compares sum to the saved parameters +7. Returns success (carry set) if checksum matches + +This verifies the demodulator responds correctly with the expected identification pattern. The ASCII-to-binary conversion suggests the demod returns a readable version string at register 0x0A. + +**FUN_CODE_1ca0 - Descriptor Checksum Verification:** +1. Iterates bytes 6 through 0x29 (36 bytes) of the BANK2_R6:R7 descriptor block +2. Computes running sum, compares against expected value 0x0706 +3. If first block passes, iterates bytes 0x2C through 0x4F (36 bytes) of same block +4. Computes second running sum, compares against expected value 0x0686 +5. Returns success only if both checksums match + +This validates the integrity of a 2-block descriptor/configuration structure (possibly EEPROM-loaded calibration data). + +**v2.06 equivalent:** v2.06 does NOT have these retry loops. It calls `FUN_CODE_1a0e` (serial number/EEPROM reader) directly without verification, then proceeds immediately. There is no signature check or checksum validation. + +### 3.2 Version Byte Check (Hardware Revision Detection) + +After the retry loops, v2.13 performs: + +```c +// Read byte at descriptor_base + 10 +byte version_byte = *(BANK1_R4:R5 + 10); // 0x0E0A +if (version_byte == 0x03) { + bVar4 = 0x80; // flag = set +} else { + bVar4 = 0; // flag = clear +} +_1_3 = bVar4 >> 7; // store as bit flag _1_3 +``` + +This reads byte offset 10 from the USB descriptor base address. Offset 10 in a USB device descriptor is `bMaxPacketSize0` in standard USB, but since this is a custom descriptor area, it likely encodes a hardware revision. The value 0x03 sets bit flag `_1_3`, creating a hardware-revision-aware code path. + +**Impact:** The `_1_3` flag is used elsewhere in v2.13 to conditionally execute different initialization sequences, supporting multiple hardware revisions of the SkyWalker-1 board. + +### 3.3 FUN_CODE_2031 - USB Reconnect Before Main Loop + +```c +void FUN_CODE_2031(void) { + if (_0_0 == 0) { + CPUCS |= 0x08; // Set CPUCS.3 (8051 reset bit? Or re-enumerate) + } else { + CPUCS |= 0x0A; // Set CPUCS.3 + CPUCS.1 + } + FUN_CODE_14b9(5, 0xDC); // Delay ~1500 cycles + EPIRQ = 0xFF; // Clear all endpoint interrupts + USBIRQ = 0xFF; // Clear all USB interrupts + DAT_SFR_91 &= 0xEF; // Clear EXIF.4 (USB interrupt flag) + CPUCS &= 0xF7; // Clear CPUCS.3 +} +``` + +This performs a controlled USB re-enumeration by pulsing CPUCS.3, then clearing all pending USB/endpoint interrupts. The conditional on `_0_0` adds CPUCS.1 when the flag is set (possibly switching between 12MHz and 48MHz operation). + +**v2.06 equivalent:** In v2.06, this exact same logic exists as `INT0_vec` (the INT0 interrupt handler at 0x0003). The critical difference is that in v2.06 this code runs as an interrupt handler, while in v2.13 it's called as a normal function (`FUN_CODE_2031`) before the main loop starts, AND the INT0 vector is repurposed for demodulator polling (see section 4). + +--- + +## 4. INT0 Handler Difference + +### v2.06 INT0 (CODE:0003) - USB Re-enumeration + +```c +void INT0_vec(void) { + if (_0_7 == 0) { + CPUCS |= 0x08; // CPUCS bit 3 + } else { + CPUCS |= 0x0A; // CPUCS bits 3+1 + } + FUN_CODE_1dfb(5, 0xDC); // Delay + EPIRQ = 0xFF; // Clear endpoint IRQs + USBIRQ = 0xFF; // Clear USB IRQs + DAT_SFR_91 &= 0xEF; // Clear external interrupt flag + CPUCS &= 0xF7; // Clear CPUCS bit 3 +} +``` + +Simple USB reconnect/re-enumeration handler. Pulses CPUCS.3, clears all pending interrupts. + +### v2.13 INT0 (CODE:0003) - Demodulator Availability Polling + +```c +void INT0_vector(void) { + for (DAT_INTMEM_37 = 0x28; DAT_INTMEM_37 != 0; DAT_INTMEM_37--) { + // Read I2C device 0x7F (demod A), checking for response + byte result = FUN_CODE_2239(0x7F); // I2C read from 0x7F + if (result != 0x01) { + // Try I2C device 0x3F (demod B) + result = FUN_CODE_2239(0x3F); // I2C read from 0x3F + if (result != 0x01) break; // Neither responded normally + } + } + _1_4 = (DAT_INTMEM_37 == 0); // Set flag if loop exhausted (no demod found) +} +``` + +This is a complete replacement of INT0's purpose. Instead of USB re-enumeration, INT0 now polls two I2C devices (0x7F and 0x3F) up to 40 times (0x28). These are two possible addresses for the satellite demodulator IC. + +**FUN_CODE_2239 decompiled:** +```c +undefined1 FUN_CODE_2239(byte device_addr) { + DAT_INTMEM_48 = 0xE1; // buffer address high + DAT_INTMEM_49 = 0; // buffer address low + FUN_CODE_0eea(1, device_addr, 0x51); // I2C read 1 byte from device + return DAT_EXTMEM_e100; // return the read byte +} +``` + +The function performs an I2C single-byte read from the specified device address, using address 0x51 as a parameter (likely selecting a specific I2C bus or mode via the FX2's auxiliary I2C controller at 0xE678). It stores the result at 0xE100 (XRAM buffer). + +**Behavioral meaning:** The flag `_1_4` is set to 1 if neither demodulator responded after 40 attempts - indicating no demodulator hardware is present. This flag is later checked by: +- `FUN_CODE_21d1` (command 0x9D handler) - skips demodulator reset if `_1_4 != 1` +- Various initialization paths to avoid hanging on missing hardware + +**Why this matters:** v2.06 assumes the demodulator is always present. v2.13 can detect and gracefully handle boards where the demodulator is absent or unresponsive, making it more robust for manufacturing QC and field failures. + +--- + +## 5. What Can v2.13 Do That v2.06 Cannot? + +### 5.1 Demodulator Hardware Detection +v2.13 probes two I2C addresses (0x7F, 0x3F) at startup to determine which demodulator variant is installed, or if none is present. v2.06 blindly assumes the hardware configuration. + +### 5.2 Host-Initiated Demodulator Re-initialization (Command 0x9A) +The host can trigger a demodulator re-initialization via USB vendor command 0x9A, with up to 3 retry attempts. v2.06 has no mechanism for the host to request re-initialization. + +### 5.3 Demodulator Status Read (Command 0x99) +Direct I2C register read of demodulator register 0xF9, returned to host. This enables diagnostic/monitoring software to check demodulator status without going through the full signal quality pipeline. + +### 5.4 Host-Controlled Tuning Delay (Command 0x9C) +Allows the host to invoke the tuning acquisition sequence with a configurable delay parameter. In v2.06, the tuning timing is entirely firmware-controlled with no host influence. + +### 5.5 I2C-Based DiSEqC Control +v2.13 uses a dedicated I2C-connected DiSEqC controller chip rather than GPIO bit-banging: +- More precise DiSEqC timing (hardware-generated rather than software-timed) +- Support for reading DiSEqC reply messages (the I2C controller can buffer responses) +- Lower CPU overhead during DiSEqC transactions +- Better compatibility with DiSEqC 1.2 motor positioning commands that require precise timing + +### 5.6 Firmware/Descriptor Integrity Verification +v2.13 validates demodulator identification (ASCII version string checksum) and descriptor block integrity (two 36-byte checksums) before proceeding. If verification fails after 20 attempts, it falls back to a recovery sequence. v2.06 does no integrity checking. + +### 5.7 Hardware Revision Awareness +The version byte check (descriptor offset 10, value 0x03) creates conditional code paths allowing a single firmware image to support multiple SkyWalker-1 hardware revisions. v2.06 has a single code path for one hardware revision. + +### 5.8 Simplified BCM4500 Status Polling +v2.06's `FUN_CODE_2000` polls three separate BCM4500 registers (0xA2, 0xA8, 0xA4 via I2C) to determine demodulator readiness. v2.13's `FUN_CODE_208d` polls only one register (0xA4), suggesting either the demodulator firmware was updated to consolidate status, or the additional checks were found to be redundant. + +### 5.9 Conditional Demodulator Reset (Command 0x9D) +v2.13's 0x9D handler can trigger a full demodulator reset sequence (FUN_CODE_1e3c -> register writes to 0x18 bus) controlled by the host via wValue. This is useful for error recovery without full device re-enumeration. + +--- + +## 6. Architecture Summary + +| Aspect | v2.06 | v2.13 | +|--------|-------|-------| +| Total functions | 61 | 82 (+21) | +| RESET vector | 0x188D | 0x170D | +| Stack pointer | 0x72 | 0x50 | +| Init data table | CODE:0B46 | CODE:0B88 | +| Descriptor base | 0x1200 | 0x0E00 | +| Config byte (IRAM) | 0x6D | 0x4F | +| INT0 purpose | USB re-enumerate | Demod probe | +| DiSEqC method | GPIO bit-bang | I2C controller | +| Demod init | Direct, no retry | 20-attempt retry | +| Integrity checks | None | Checksum verification | +| HW revision support | Single | Multi-revision (flag _1_3) | +| New vendor cmds | -- | 0x99, 0x9A, 0x9C | diff --git a/firmware-dump/fw_v213_comparison_report.md b/firmware-dump/fw_v213_comparison_report.md new file mode 100644 index 0000000..ad042cf --- /dev/null +++ b/firmware-dump/fw_v213_comparison_report.md @@ -0,0 +1,379 @@ +# Genpix SkyWalker-1 Firmware v2.13.x Sub-Variant Comparison Report + +## Executive Summary + +Three firmware sub-variants were extracted from `SW1_update_2_13_x.exe` and analyzed as 8051 (Cypress FX2) binaries via Ghidra. The analysis reveals that **FW1 (v2.13.1) targets fundamentally different hardware** than FW2/FW3, while **FW2 (v2.13.2) and FW3 (v2.13.3) target different revisions of a newer hardware platform** with an external demodulator connected via a parallel data bus. + +--- + +## 1. Function List Comparison + +### Function Counts +| Variant | Port | Functions | Extra Function | +|---------|------|-----------|----------------| +| FW1 (v2.13.1) | 8194 | 82 | FUN_CODE_0fc7, FUN_CODE_1405, FUN_CODE_14b9 (unique) | +| FW2 (v2.13.2) | 8195 | 83 | FUN_CODE_1288 (unique), FUN_CODE_0ffc | +| FW3 (v2.13.3) | 8196 | 83 | FUN_CODE_1288, FUN_CODE_0706 (unique) | + +### Shared Core Functions (Same Address Across All Three) +The following functions exist at identical addresses in all variants: + +``` +CODE:0000 RESET_vector CODE:0003 INT0_vector +CODE:0033 INT2_USB_GPIF_vector CODE:0036 FUN_CODE_0036 +CODE:0043 INT4_FX2_vector CODE:004b INT5_FX2_vector +CODE:0050 FUN_CODE_0050 CODE:0053 INT6_FX2_vector +CODE:0056 FUN_CODE_0056 CODE:034e FUN_CODE_034e (vendor cmd handler) +CODE:06d9 FUN_CODE_06d9 CODE:0718 FUN_CODE_0718 +CODE:072a FUN_CODE_072a CODE:0779 FUN_CODE_0779 +CODE:079d FUN_CODE_079d CODE:0800 FUN_CODE_0800 (main loop) +CODE:0ca4 FUN_CODE_0ca4 CODE:0eea FUN_CODE_0eea +CODE:1000 FUN_CODE_1000 CODE:1500 thunk (target differs!) +CODE:15b8 FUN_CODE_15b8 CODE:170d main_entry +CODE:1799 FUN_CODE_1799 CODE:1800 FUN_CODE_1800 +CODE:19ed FUN_CODE_19ed CODE:1a5d FUN_CODE_1a5d +CODE:1ac6 FUN_CODE_1ac6 CODE:1b2a FUN_CODE_1b2a +``` + +### Functions That Shift Addresses (Same Logic, Different Location) +Many functions exist in all three variants but at shifted addresses: + +| Purpose | FW1 Address | FW2 Address | FW3 Address | +|---------|-------------|-------------|-------------| +| Hardware init | 0x11ab | 0x1288 | 0x1288 | +| Demod setup | 0x10d9 | 0x10dd | 0x10dd | +| I2C/parallel data transfer | 0x0eea (I2C) | 0x0eea (parallel) | 0x0eea (parallel) | +| Tuner detect | 0x1405 | 0x0eea | 0x0eea | +| Delay loop | 0x14b9 | 0x1e92 (FW2) | 0x1e88 (FW3) | +| EEPROM checksum | 0x1ca0 | 0x1cff | 0x1ca1 | +| USB descriptor setup | 0x2031 | 0x206c | 0x206c | +| Thunk target | 0x2252 | 0x228d | 0x228d | +| 2nd init call | 0x1be6 | 0x1c45 | 0x1cf7 | + +### Functions Unique to Each Variant + +**FW1 only:** +- `FUN_CODE_0fc7` -- An I2C write-with-retry function (tries 0x14 = 20 times via I2C bus using FUN_CODE_23ae/23ee) +- `FUN_CODE_1405` -- Tuner/demodulator identification via **I2C bus + P1 port reads** with signature matching +- `FUN_CODE_14b9` -- Calibrated delay function using CPUCS clock divider awareness + +**FW2 only:** +- `FUN_CODE_0ffc` -- Stores a parameter into BANK3_R1 (register save) +- `FUN_CODE_1288` -- New hardware initialization (loads from external memory at e080-e08e) + +**FW3 only:** +- `FUN_CODE_0706` -- Memory write dispatcher that handles 3 addressing modes (XDATA, IDATA, direct) +- `FUN_CODE_1288` -- Same as FW2 +- Uses `FUN_CODE_1ffc` (at a different address from FW2's 0x1ffd) + +--- + +## 2. Main Entry Comparison (CODE:170D) + +### Identical Across All Three: +- IRAM clear loop (0x7F down to 0x00) +- Init table parsing from CODE:0B88 +- Bit config table reference at CODE:1740 +- Final call to FUN_CODE_0800 + +### One Key Difference -- Stack Pointer: + +| Variant | SP Value | +|---------|----------| +| FW1 | SP = 0x50 | +| FW2 | SP = 0x50 | +| FW3 | SP = **0x52** | + +FW3 sets `SP = 0x52`, requiring 2 more bytes of IRAM for stack usage. This indicates FW3 uses additional internal RAM locations (0x51-0x52) for state variables that FW1/FW2 don't need, pushing the stack higher. + +**Confirmed**: FW3 uses `DAT_INTMEM_51` as a hardware status register throughout its code, while FW1/FW2 use `DAT_INTMEM_4f` for the same purpose. The 2-byte difference in SP exactly accounts for this. + +### Memory at CODE:170D: +``` +FW1: 787f e4f6 d8fd 7581 50 02 1754 020800 e4 +FW2: 787f e4f6 d8fd 7581 50 02 1754 020800 e4 (identical to FW1) +FW3: 787f e4f6 d8fd 7581 52 02 1754 020800 e4 (byte at 0x1714 = 0x52 vs 0x50) +``` + +--- + +## 3. Key Function Decompilation Comparison + +### 3.1 FUN_CODE_0800 (Main Loop) -- All at Same Address + +**Structure identical across all three:** +1. Clear INTMEM locations 0x22-0x2D, 0x32-0x35 +2. Clear bit flags _1_0 and _0_6 +3. Call hardware init (address differs) +4. Set up BANK register pairs (XDATA pointers): all use the same values (0x0E00, 0x0E12, 0x0E1C, 0x0E54, 0x0E8C, 0x0EE8) +5. Call FUN_CODE_072a with init params +6. Memory copy loop (0x80 bytes at offset 0x0E00) +7. Retry loop with FUN_CODE_1799 (20 attempts) +8. Retry loop with EEPROM checksum function (20 attempts) +9. Check demod type byte at offset +10 from BANK1_R4/R5 (== 0x03 -> set flag) +10. Enable interrupts, enter main event loop + +**Differences in called function addresses (relocated, not functionally different):** + +| Call Purpose | FW1 | FW2 | FW3 | +|-------------|------|------|------| +| Hardware init | FUN_CODE_11ab | FUN_CODE_1288 | FUN_CODE_1288 | +| EEPROM checksum | FUN_CODE_1ca0 | FUN_CODE_1cff | FUN_CODE_1ca1 | +| USB setup | FUN_CODE_2031 | FUN_CODE_206c | FUN_CODE_206c | +| Main loop poll | FUN_CODE_21ec | FUN_CODE_2227 | FUN_CODE_2227 | +| Interrupt check | FUN_CODE_2445 | FUN_CODE_247c | FUN_CODE_2473 | +| Status check | FUN_CODE_2189 | FUN_CODE_21c4 | FUN_CODE_21c4 | +| Buffer flush | FUN_CODE_20b9 | FUN_CODE_20f4 | FUN_CODE_20f4 | +| EP complete | FUN_CODE_2447 | FUN_CODE_247e | FUN_CODE_2475 | + +### 3.2 Hardware Init Function (FW1: 0x11ab, FW2/FW3: 0x1288) + +**Functionally identical** across all three except: + +| Parameter | FW1 | FW2 | FW3 | +|-----------|------|------|------| +| P0 init value | **0xa4** | **0xa4** | **0xa0** | +| Status register | DAT_INTMEM_4f | DAT_INTMEM_4f | **DAT_INTMEM_51** | +| Sub-init call 1 | FUN_CODE_1c44 | FUN_CODE_1ca3 | FUN_CODE_1c45 | +| Sub-init call 2 | FUN_CODE_1000 | FUN_CODE_10dd | FUN_CODE_10dd | +| I2C/bus init | FUN_CODE_213b | FUN_CODE_2176 | FUN_CODE_2176 | +| Tuner init | FUN_CODE_1be6 | FUN_CODE_1c45 | FUN_CODE_1cf7 | + +**P0 = 0xa4 vs 0xa0**: P0 on the FX2 controls GPIO port 0. The difference is bit 2: +- FW1/FW2: P0 bit 2 = 1 (0xa4 = 1010 0100) +- FW3: P0 bit 2 = 0 (0xa0 = 1010 0000) + +This suggests different default GPIO state for a control signal, likely related to the demodulator interface mode or reset polarity. + +### 3.3 FUN_CODE_0eea -- The Most Revealing Difference + +**FW1**: This is a standard **I2C master transfer** function: +- Uses `FUN_CODE_23ae` (I2C START), `FUN_CODE_23ee` (I2C byte write), `FUN_CODE_23d0` (I2C address) +- Reads back via `FUN_CODE_2164` +- Standard I2C retry with NACK detection + +**FW2**: This is a **parallel bus read with demodulator handshake**: +- Reads demod type from address table (BANK1_R4/R5 + offset) +- Uses `FUN_CODE_11b6` for demod selection +- Toggles P0 bits 6/7 for bus control (P0.6 = chip select, P0.7 = read strobe) +- Reads data from P1 port (parallel data bus) +- Checks P1 ^ 0x1D (signature) then reads P1 for device ID +- Matches device IDs: 0xC5/0xD5 (for type 3), 0x5A (type 4), 0x5B (type 5), 0x5C (type 6) +- Controls P3 bits for demod power/reset + +**FW3**: Similar parallel bus read as FW2 but with **different timing and bus protocol**: +- Sets P0 | 0x80 once at start (not per-iteration like FW2) +- Uses `DAT_INTMEM_3f` and `DAT_INTMEM_40` as OR-accumulators for P1 reads +- Two separate P1 reads per cycle: one with P0.6 high, one with P0.6 low +- Calls `FUN_CODE_1b2a` with 3 parameters (accumulated OR values) vs FW2's 2 parameters +- Uses `P0 | 0x44` and `P0 & 0xBF` toggle pattern (vs FW2's different bit dance) + +### 3.4 Vendor Command Handler (FUN_CODE_034e) + +**Structurally identical** across all three -- same switch/case table structure at CODE:035e. + +Key differences: + +| Feature | FW1 | FW2 | FW3 | +|---------|------|------|------| +| Case 0x35f/0x427 call | FUN_CODE_0ffe (nop) | FUN_CODE_1ffd | FUN_CODE_0ffe (nop) | +| Case 0x361 call | FUN_CODE_2441 | FUN_CODE_2478 | FUN_CODE_246f | +| Case 0x365 call | FUN_CODE_2443 | FUN_CODE_247a | FUN_CODE_2471 | +| Case 0x36f call | FUN_CODE_2357 | FUN_CODE_2392 | FUN_CODE_2392 | +| Case 0x371 call | FUN_CODE_243d | FUN_CODE_0ffc | FUN_CODE_1ffc | +| Case 0x373/0x3ff call | FUN_CODE_2309 | FUN_CODE_2344 | FUN_CODE_2344 | +| Case 0xf0 indirect call | func_0x231e | func_0x2359 | func_0x2359 | +| Case 0x39d return | func_0x06e4 | DAT=0x0 | DAT=0x0 | +| Case 0x3d1 call | FUN_CODE_2110 | FUN_CODE_214b | FUN_CODE_214b | +| Case 0x3d3 behavior | TR2 timer check | OR operation | OR operation | +| Case 0x405 behavior | Goto LAB_05db | Conditional branch | Conditional branch | +| Case 0x421 behavior | Simple check | Extra P2_1, RL A logic | Extra P2_1, RL A logic | + +**FW1's unique case 0x3d3**: Checks Timer 2 Run flag (TR2) -- this is used for I2C bus timeout recovery, consistent with FW1 being I2C-based. + +**FW2/FW3's unique case 0x421-0x423**: Includes a rotate-left and P2.1 write -- this is a parallel bus data direction control, consistent with the external demodulator interface. + +--- + +## 4. Memory Comparison at Key Offsets + +### CODE:0000-0x000F (Reset Vector) +``` +FW1: 02170d 753728 e53760 1b7ffc 7e7f12 22 +FW2: 02170d 753728 e53760 1b7ffc 7e7f12 22 +FW3: 02170d 753728 e53760 1b7ffc 7e7f12 22 +ALL IDENTICAL -- LJMP 0x170D, then INT0 vector handler +``` + +### CODE:0B88-0x0B9F (Init Table Start) +``` +FW1: 41e0b6 626033 e0c609 070939 4f0000 000000 000000 000000 +FW2: 41e0b6 626033 e0c609 070939 4f0000 000000 000000 000000 +FW3: 41e0b6 626033 e0c609 070939 4f0000 000000 000000 000000 +ALL IDENTICAL -- Same register/SFR initialization table +``` + +### CODE:1500 (Thunk/INT Vector Target) +``` +FW1: 02 2252 00 02 22dd 00 02 22c7 00 02 226a 00 +FW2: 02 228d 00 02 2318 00 02 2302 00 02 22a5 00 +FW3: 02 228d 00 02 2318 00 02 2302 00 02 22a5 00 +``` +FW1 jumps to different addresses than FW2/FW3. **FW2 and FW3 are IDENTICAL here** -- their interrupt handlers are at the same addresses. + +### CODE:1740-0x174F (Bit Config Table) +``` +FW1: 4004 f456 8001 46f6 dfe4 800b 0102 0408 +FW2: 4004 f456 8001 46f6 dfe4 800b 0102 0408 +FW3: 4004 f456 8001 46f6 dfe4 800b 0102 0408 +ALL IDENTICAL -- Same bit manipulation lookup table +``` + +### CODE:0800 (Main Loop Start) +``` +FW1: e4f52d...c208c206 12 11ab 750c0e... +FW2: e4f52d...c208c206 12 1288 750c0e... +FW3: e4f52d...c208c206 12 1288 750c0e... +``` +Only difference: the LCALL target (FW1: 0x11ab, FW2/FW3: 0x1288). + +### CODE:06D9 (Utility Functions) +``` +FW1: bb010c e58229 f582e5 833af5 83e022 5006e9 2582f8 e622 +FW2: bb010c e58229 f582e5 833af5 83e022 5006e9 2582f8 e622 +FW3: bb010c e58229 f582e5 833af5 83e022 5006e9 2582f8 e622 +ALL IDENTICAL -- Generic memory access utilities shared by all +``` + +### CODE:0EEA (Critical Divergence Point) +``` +FW1: 8f44 8c45 8d46 8b47 754a14 e544 b451... (I2C transfer params in registers) +FW2: 753e14 e50d 240a f582 e435 0cf5 83e0... (reads from DPTR+offset table) +FW3: 753e14 e4f5 3ff5 40 e50d 240a f582... (similar to FW2 + accumulator init) +``` +This confirms: FW1's FUN_CODE_0eea is a completely different function (I2C master) than FW2/FW3's (parallel bus demod interface). + +--- + +## 5. FW2 vs FW3 Specific Differences + +FW2 and FW3 are the most similar pair (1,525 bytes different). Key differences: + +| Feature | FW2 | FW3 | +|---------|------|------| +| Stack Pointer | SP = 0x50 | SP = 0x52 | +| Status register | DAT_INTMEM_4f | DAT_INTMEM_51 | +| P0 init | 0xa4 | 0xa0 (bit 2 different) | +| FUN_CODE_0eea bus protocol | Single-phase P1 read | Dual-phase P1 read with OR accumulation | +| I2C buffer addresses | DAT_INTMEM_48/49 | DAT_INTMEM_4a/4b | +| Unique function | FUN_CODE_0ffc (register store) | FUN_CODE_0706 (multi-mode write) | +| P0 bus timing | P0 &= ~0x40; P0 |= 0x80 per iteration | P0 |= 0x80 once; P0 |= 0x44 / P0 &= ~0x40 per phase | +| Delay function address | FUN_CODE_1e92 | FUN_CODE_1e88 | + +### The P1 Read Difference is Critical + +**FW2** reads P1 once per bus cycle: +```c +uVar1 = P1; // Read with one bus state +P0 |= 0x40; // Then change control line +uVar2 = P1; // Read again +FUN_CODE_1b2a(uVar2, uVar1); // Process both samples +``` + +**FW3** reads P1 in two phases and OR-accumulates: +```c +DAT_INTMEM_3f = 0; DAT_INTMEM_40 = 0; // Clear accumulators +// Phase 1: P0.6 high +P0 |= 0x44; +bVar2 = P1; +DAT_INTMEM_3f |= bVar2; // OR-accumulate +// Phase 2: P0.6 low +P0 &= ~0x40; +bVar2 = P1; +DAT_INTMEM_40 |= bVar2; // OR-accumulate +FUN_CODE_1b2a(0, DAT_INTMEM_3f, DAT_INTMEM_40); // Process accumulated +``` + +This OR-accumulation pattern in FW3 suggests dealing with a bus that may have metastable signals or requires multiple samples, characteristic of **a different demodulator chip with different bus timing**. + +--- + +## 6. Hypothesis: What Distinguishes Each Variant + +### FW1 (v2.13.1) -- Original I2C-Connected Demodulator Hardware + +**Target**: First-generation SkyWalker-1 PCB with an **I2C-connected demodulator** (likely a Conexant/Zarlink integrated tuner+demod). + +Evidence: +- Uses standard I2C protocol functions (START, STOP, ACK/NACK, byte read/write) +- FUN_CODE_0fc7: I2C write retry loop +- FUN_CODE_1405: Reads demodulator identification via I2C + P1 GPIO, checks device signatures: + - Type 3: P1 == 0xA5 or 0xB5 + - Type 4: P1 == 0x5A + - Type 5: P1 == 0x5B + - Type 6: P1 == 0x5C +- Has timer-based I2C timeout (TR2 check in vendor handler) +- SP=0x50, fewer IRAM state variables needed +- `func_0x06e4` called for unknown vendor commands (older error path) + +**Likely demodulator**: An I2C-bus demodulator supporting DVB-S/DCII/DSS, with the FX2 as USB bridge. The type codes 3-6 likely correspond to different supported modulation modes or demod silicon revisions. + +### FW2 (v2.13.2) -- Second-Generation Parallel-Bus Demodulator + +**Target**: Revised SkyWalker-1 PCB with a **parallel-bus connected demodulator** (likely a different demod chip or a custom FPGA/ASIC). + +Evidence: +- FUN_CODE_0eea: Parallel bus read using P0 GPIO for control (CS, RD strobe) and P1 for data +- FUN_CODE_10dd: Copies configuration from external memory (e080-e08e) into demod registers (e6c0-e6cd) -- reads 15 configuration bytes from what appears to be EEPROM/flash config area +- Reads same device signatures but via parallel bus (P1 ^ 0x1D check, then P1 reads for 0xC5/0xD5/0x5A/0x5B/0x5C) +- P0 = 0xa4 (bit 2 set = specific bus mode select) +- SP = 0x50 +- Extra vendor command paths for parallel data direction (P2.1 control in case 0x421/0x423) +- Uses FUN_CODE_14e2: Busy-wait on e678 bit 6 (demod ready flag) with 0xFFFF timeout counter + +**Likely demodulator**: A parallel-bus demodulator with 8-bit data port on P1, active-low chip select and read strobe on P0. The external config block (e080-e08e) stores per-unit calibration/tuning data. + +### FW3 (v2.13.3) -- Third-Generation with Enhanced Bus Protocol + +**Target**: Further revised PCB with the **same parallel-bus demodulator as FW2** but with a **different bus interface revision** or a variant chip that requires modified timing. + +Evidence: +- Same demod configuration loading as FW2 (FUN_CODE_10dd identical) +- Same parallel bus architecture but with dual-phase reading and OR-accumulation +- P0 = 0xa0 (bit 2 clear = different bus mode or reset polarity) +- SP = 0x52 (2 more IRAM bytes: status register moved from 0x4F to 0x51) +- FUN_CODE_0706 (unique): Multi-mode memory write supporting XDATA, IDATA, and direct addressing -- suggests the demod communicates through multiple address spaces +- The OR-accumulation of P1 reads suggests either: + - A demodulator with open-drain outputs requiring multiple read cycles + - Bus settling time issues on the newer PCB layout + - A chip variant that serializes data across multiple bus phases + +--- + +## 7. Summary Table + +| Aspect | FW1 (v2.13.1) | FW2 (v2.13.2) | FW3 (v2.13.3) | +|--------|---------------|---------------|---------------| +| **Demod interface** | I2C bus | Parallel bus (P0/P1) | Parallel bus (enhanced) | +| **Bus protocol** | I2C START/STOP/ACK | Single-phase P1 read | Dual-phase P1 read + OR accumulate | +| **Stack pointer** | 0x50 | 0x50 | 0x52 | +| **P0 init** | 0xa4 | 0xa4 | 0xa0 | +| **Status register** | INTMEM 0x4F | INTMEM 0x4F | INTMEM 0x51 | +| **Config source** | Hardcoded | External (e080-e08e) | External (e080-e08e) | +| **Demod types supported** | 3-6 (via I2C) | 3-6 (via parallel) | 3-6 (via parallel) | +| **Binary distance from FW1** | -- | 3,993 bytes | 3,789 bytes | +| **Binary distance from FW2** | 3,993 bytes | -- | 1,525 bytes | + +## 8. Conclusion + +The three v2.13 firmware sub-variants represent an evolutionary progression of the SkyWalker-1 hardware: + +1. **v2.13.1 (FW1)**: Original design with I2C-connected demodulator. The FX2 communicates with the demod entirely through I2C, using standard master-mode transactions. This is the simplest interface but limited in bandwidth. + +2. **v2.13.2 (FW2)**: Redesigned with a parallel-bus demodulator. The demod data port is connected directly to FX2's P1, with P0 bits used for bus control signals (chip select, read/write strobes). Configuration data is loaded from an external EEPROM area. This provides higher throughput for TS data transfer. + +3. **v2.13.3 (FW3)**: Refinement of the FW2 design, likely for a newer demod silicon revision or PCB layout. Uses dual-phase bus reads with signal accumulation, different GPIO defaults, and additional IRAM for state tracking. The OR-accumulation pattern suggests dealing with bus signal integrity improvements. + +The updater program's format string `"FW 2.13.%i"` and its selection logic presumably check the hardware revision (likely via a GPIO strap or I2C ID read) to determine which of the three firmware images to flash. + +All three variants support the same modulation types (DVB-S/QPSK, Turbo QPSK/8PSK, DCII, DSS) -- the demod type codes 3-6 appear in all variants. The differences are purely about the hardware interface, not the feature set.