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
This commit is contained in:
Ryan Malloy 2026-02-11 06:44:26 -07:00
parent 4447d2c0e7
commit 2e00a054e8
2 changed files with 757 additions and 0 deletions

View File

@ -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 |

View File

@ -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.