# Genpix SkyWalker-1 Rev.2 v2.10.4 Deep Firmware Analysis
## Executive Summary
Rev.2 v2.10.4 has **107 functions** -- the most of any firmware version (vs 61 for v2.06, 88 for v2.13 FW1). The higher function count is driven by three factors:
1.**Granular function decomposition**: Rev.2 breaks large operations into many small helper functions (10-30 bytes each), where v2.06 inlines them and v2.13 recombines them differently.
2.**A massive 874-byte configuration dispatcher** (`FUN_CODE_0800`) that contains an embedded copy of the main loop, making Ghidra count additional entry points as separate functions.
3.**Extra I2C/demodulator helper chains**, GPIO control primitives, and hardware-polling wait loops that exist as individual callable units.
Rev.2 sits architecturally between v2.06 and v2.13 -- it has v2.06's INT0 USB re-enumeration behavior but already uses the same descriptor base offset (0x0E00) and stack pointer (SP=0x4F) pattern that v2.13 would adopt. It supports **27 vendor commands** (0x80-0x9A) vs 30 in v2.06/v2.13.
---
## 1. Complete Function Inventory (107 Functions)
### 1.1 Vector Table and ISR Region (0x0000-0x0055)
| Address | Name | Size | Role |
|---------|------|------|------|
| 0x0000 | `RESET_vector` | 3 | Jump to main (0x155F) |
By analyzing functional equivalence (matching by decompiled behavior, not address), the following **~40 functions exist in Rev.2 but have no direct counterpart in v2.13**:
### 3.1 INT0 ISR Split (2 functions)
Rev.2 splits its INT0 into two named functions that Ghidra recognizes separately:
| Rev.2 | Purpose | v2.13 Equivalent |
|-------|---------|------------------|
| `INT0_ISR` (0x0003) | Bit check + branch to 0x000F or 0x0016 | v2.13 has `INT0_vector` -- completely different (demod polling) |
| `INT0_ISR_bit_clear` (0x000F) | The CPUCS-only path of INT0 | No equivalent -- v2.13 moved this to `FUN_CODE_2031` |
### 3.2 Vector Stubs and Duplicate Handlers (5 functions)
| Rev.2 | Purpose | Why Unique |
|-------|---------|------------|
| `FUN_CODE_004e` (0x004E) | Empty RET at unused vector | v2.06 doesn't have this vector defined |
| `FUN_CODE_0051` (0x0051) | Empty RET at unused vector | Same as above |
### 4.1 Rev.2 INT0 (0x0003-0x0031): USB Re-enumeration
Rev.2's INT0 is a **47-byte inline ISR** that spans from address 0x0003 to 0x0031, consuming the INT0 vector slot plus the Timer0, INT1, Timer1, and UART0 vector slots (0x000B, 0x0013, 0x001B, 0x0023).
**Disassembly:**
```
CODE:0003 JNB 0x04, 0x000F ; If bit 0x04 (byte0.4) == 0, skip
CODE:0006 MOV DPTR, #0xE680 ; CPUCS register
CODE:0009 MOVX A, @DPTR
CODE:000A ORL A, #0x0A ; Set bits 3+1 (re-enumerate + 48MHz)
CODE:002E ANL A, #0xF7 ; Clear bit 3 (end re-enumeration)
CODE:0030 MOVX @DPTR, A
CODE:0031 RET
```
**Decompiled C equivalent:**
```c
void INT0_ISR(void) {
if (_0_4 == 0) {
CPUCS |= 0x08; // Re-enumerate only
} else {
CPUCS |= 0x0A; // Re-enumerate + set CPUCS.1
}
delay_routine(5, 0xDC); // ~1500 Timer2 ticks
EPIRQ = 0xFF; // Clear all endpoint interrupts
USBIRQ = 0xFF; // Clear all USB interrupts
EXIF &= 0xEF; // Clear external interrupt flag
CPUCS &= 0xF7; // Clear re-enumerate bit
}
```
### 4.2 Comparison: v2.06 INT0 (0x0003)
v2.06's INT0 is **functionally identical** to Rev.2's. The only differences:
- Checks bit `_0_7` instead of `_0_4` (different bit allocation in byte 0)
- Calls `FUN_CODE_1dfb` for delay (Rev.2 calls `delay_routine` at 0x1BDA)
- Same 47-byte inline ISR spanning the same vector slots
**Disassembly comparison:**
```
v2.06: JNB 0x07, 0x000F ; bit 7 of byte 0
Rev.2: JNB 0x04, 0x000F ; bit 4 of byte 0
```
All other instructions are byte-identical except for the delay function address (v2.06: 0x1DFB, Rev.2: 0x1BDA).
### 4.3 Comparison: v2.13 INT0 (0x0003)
v2.13 **completely replaces** INT0's purpose. Instead of USB re-enumeration, it performs demodulator availability polling:
```c
void INT0_vector(void) {
for (DAT_INTMEM_37 = 0x28; DAT_INTMEM_37 != 0; DAT_INTMEM_37--) {
result = FUN_CODE_2239(0x7F); // I2C read from demod at 0x7F
if (result != 0x01) {
result = FUN_CODE_2239(0x3F); // Try alternate address 0x3F
if (result != 0x01) break;
}
}
_1_4 = (DAT_INTMEM_37 == 0); // Flag if no demod found
}
```
v2.13 moved the USB re-enumeration logic to `FUN_CODE_2031`, called as a normal function before the main loop. This freed INT0 for periodic demodulator health checks.
### 4.4 Key Insight
Rev.2 represents the transitional state: it still uses INT0 for USB re-enumeration (like v2.06) but has already restructured the rest of the codebase in ways that v2.13 would inherit. The INT0 repurposing was the last major architectural change between Rev.2 and v2.13.
---
## 5. Vendor Command Coverage
### 5.1 Rev.2 Jump Table (27 commands: 0x80-0x9A)
The vendor command dispatcher at 0x0056 performs: `ADD A, #0x80` followed by `CJNE A, #0x1B`. This means the valid range is 0x80-0x9A (27 commands, range check `< 0x1B`).
Jump table at CODE:0076 (54 bytes = 27 entries x 2 bytes):
*Note: Addresses marked with * are computed from the jump table bytes and represent targets within or called by vendor_cmd_dispatch. Some may be inline code within the FUN_CODE_0319 mega-function.*
### 5.2 Missing Commands vs v2.06/v2.13
v2.06 and v2.13 both support 30 commands (0x80-0x9D, range check `< 0x1E`). Rev.2 supports only 27 (0x80-0x9A, range check `< 0x1B`).
1.**0x9B (reserved/STALL)**: Simply a placeholder in v2.06/v2.13. Rev.2 didn't need it since 0x9A was the highest command.
2.**0x9C (DELAY_COMMAND)**: This was added in v2.13 to allow host-controlled tuning acquisition delays. Rev.2's tuning logic handles delays internally without host control, so this command wasn't needed yet.
3.**0x9D (SET_MODE_FLAG)**: This is the most significant absence. In v2.06, it handles hardware revision-aware mode switching. In v2.13, it triggers conditional demodulator resets. Rev.2 handles these functions through different code paths -- the `FUN_CODE_0800` configuration dispatcher has embedded logic for mode switching that later versions extracted into a separate vendor command.
### 5.3 Commands Present but Differently Implemented
**0x99 and 0x9A exist in Rev.2** but appear to point to different targets than v2.13:
- Rev.2 0x99 -> 0x0101 (within the vendor dispatch region, likely a minimal read-back)
- Rev.2 0x9A -> 0x212A (in the utility function region)
**Key difference**: Rev.2 separates the RESET entry (`main`) from the init table processor (`main_init`), making them two distinct functions. v2.06 and v2.13 do both inline in a single function. This is one source of Rev.2's higher function count.
The init table processor is identical in algorithm across all three: it reads a compressed data stream from CODE space and writes initialization values to IRAM, XRAM, and SFR spaces. The table format uses a packed header byte where:
- Bits 7:6 select the target address space
- Bits 5:0 encode the data length
- Multi-page transfers use an additional page count byte
### 6.2 Main Loop
All three versions have structurally identical main loops:
```c
// Common main loop structure (all versions)
while (true) {
// Inner poll: check USB endpoints
do {
poll_usb(); // Rev.2: FUN_CODE_201e
if (vendor_request_pending) {
handle_vendor_request(); // Rev.2: FUN_CODE_0319
clear_flag();
}
} while (!data_ready);
// Process data
clear_data_flag();
while (check_ep2_status()) {
if (no_more_data) break;
}
reset_endpoints();
sync_gpif();
}
```
**Differences in main loop functions called:**
| Role | Rev.2 | v2.06 | v2.13 |
|------|-------|-------|-------|
| USB poll | FUN_CODE_201e | FUN_CODE_2297 | FUN_CODE_21ec |
Rev.2's descriptor setup matches the common pattern:
```c
void usb_descriptor_setup(void) {
USBCS &= 0xFD; // Clear disconnect
_0_0 = 0; _0_2 = 1; // Init flags
IFCONFIG = 0x10; // Internal clock, 48MHz
REVCTL = 0xCA;
GPIFIDLECTL = 0xFF;
PORTACFG = 0x07;
// I2C init
FUN_CODE_19f4(0); // I2C controller init
// GPIO configuration
P0 = 0x84; // P0.7=1, P0.2=1
IPL1 = 0x9E;
P3 = 0xE1;
// Timer2 for DiSEqC timing
T2CON = 0x04; // Auto-reload, running
RCAP2H = 0xF8;
RCAP2L = 0x2F; // 500us tick period
// LNB and demod init
FUN_CODE_1f5c(); // LNB voltage I2C select
FUN_CODE_12b8(); // Configuration structure init
FUN_CODE_0d7c(); // GPIF/slave FIFO config
FUN_CODE_21b1(); // LNB voltage select (P0.4)
FUN_CODE_21c2(); // 22kHz tone enable (P0.3)
}
```
**Compared to v2.06**: Nearly identical, but Rev.2 uses P0=0x84 where v2.06 uses different pin assignments (reflecting different PCB layout).
**Compared to v2.13**: v2.13 adds the `INT0_vector()` demod probe call during setup. Rev.2 does not probe the demodulator during init -- it assumes the hardware is present (like v2.06).
---
## 7. Function Address Shift Patterns
### 7.1 Code Organization Comparison
The firmware binary is organized into logical regions. Comparing equivalent functions across versions reveals systematic address shifts:
| Region | Rev.2 Range | v2.06 Range | v2.13 Range | Shift Pattern |
Rev.2's code starts earlier in several regions because:
1.**No init table processor in main()**: Rev.2 splits main() into a 6-instruction stub (10 bytes) + separate main_init(). v2.06/v2.13 inline the init table processor (92 instructions, ~130 bytes) into main(), pushing subsequent code later.
2.**Shorter vendor dispatch**: Rev.2 handles 27 commands (54-byte jump table) vs 30 commands (60-byte table), saving 6 bytes plus the handling code for 3 missing commands.
3.**More compact I2C layer**: Rev.2's granular I2C functions are individually small but collectively pack tighter in the binary than v2.06's larger monolithic I2C functions.
### 7.3 Why Rev.2 Ends at 0x2269 (Smaller Total)
Despite having 107 functions, Rev.2's code ends at 0x226B (total ~8.7 KB) while:
- v2.06 ends at 0x24DD (total ~9.4 KB)
- v2.13 ends at 0x244B (total ~9.3 KB)
Rev.2 is the **smallest binary** despite having the most functions. This is because:
- Many functions are tiny (2-8 bytes) -- empty stubs, simple register moves
- The granular decomposition creates more function entry points but reuses code through calls rather than duplication
- Missing vendor commands 0x9B-0x9D save ~100-200 bytes of handler code
---
## 8. Why Rev.2 Has 107 Functions
The 107 function count is driven by several factors:
### 8.1 Granular Function Decomposition (+25-30 vs v2.06)
Rev.2 breaks operations into many small callable units. Where v2.06 has a single I2C transfer function with inlined bus control, Rev.2 has:
-`i2c_exchange_byte` (5 bytes)
-`I2C_ISR` (8 bytes)
-`FUN_CODE_1af5` (9 bytes) -- address write
-`FUN_CODE_1afe` (3 bytes) -- register move
-`FUN_CODE_1b01` (67 bytes) -- byte transfer
-`FUN_CODE_1b44` (76 bytes) -- ACK/NAK
-`FUN_CODE_1b90` (74 bytes) -- bus reset
That's 7 functions for what v2.06 handles in 2-3 functions.
### 8.2 Duplicate Interrupt Handlers (+3)
Rev.2 has `INT4_FX2_vector`, `INT6_FX2_vector`, `GPIF_INT4_INT6_handler`, AND `INT4_INT6_handler` -- four functions for the same interrupt behavior. v2.06 and v2.13 consolidate these.
These are RET-only functions that serve as placeholders for features not yet implemented or intentionally disabled. v2.06 doesn't define explicit functions at these addresses.
### 8.4 INT0 Split (+1)
Ghidra recognizes two functions (`INT0_ISR` and `INT0_ISR_bit_clear`) for what is logically one handler, because the branch at 0x0003 creates two entry points.
### 8.5 The FUN_CODE_0800 Monster (+10-15 internal entry points)
The 874-byte `FUN_CODE_0800` configuration dispatcher is the most complex function in any firmware version. It contains a 128-entry switch statement that handles different demodulator types and signal modulations. Ghidra's analysis identifies multiple internal entry points within this function as separate functions, inflating the count. This function also contains an **embedded copy of the main loop** (the `while(true)` loop at the bottom), which Ghidra may count additional entry points for.
### 8.6 Summary Count
| Factor | Additional Functions |
|--------|---------------------|
| Granular I2C primitives | +12 |
| I2C wait/polling variants | +4 |
| GPIO control helpers | +6 |
| Duplicate interrupt handlers | +3 |
| Empty stubs/placeholders | +8 |
| INT0 split | +1 |
| Demod scan/version check | +3 |
| Math library (separate) | +3 |
| Config dispatcher internal entries | +5 |
| **Total unique additions** | **~45** |
Starting from v2.06's 61 functions, adding ~45 unique functions brings us close to 107. The remaining difference comes from Rev.2 having explicit function definitions where v2.06 has inline code or code that Ghidra doesn't recognize as separate functions.
---
## 9. Rev.2's Position in the Firmware Timeline
Rev.2 v2.10.4 is an intermediate development version that bridges v2.06 and v2.13:
| Feature | v2.06 | Rev.2 v2.10.4 | v2.13 FW1 |
|---------|-------|---------------|-----------|
| INT0 purpose | USB re-enum | USB re-enum | Demod polling |
| SP value | 0x72 | **0x4F** | 0x50 |
| Init table | 0x0B46 | **0x0B48** | 0x0B88 |
| Descriptor base | 0x1200 | **0x0E00** | 0x0E00 |
Rev.2 adopted the lower stack pointer (0x4F vs v2.06's 0x72), the descriptor base at 0x0E00, and the DiSEqC data pin at P0.4 -- changes that stuck through v2.13 (with minor adjustments). However, it retained v2.06's INT0 USB re-enumeration behavior and lacked v2.13's demodulator polling, retry loops, and 3 additional vendor commands.
The high function count reflects an engineering style favoring small, reusable functions over inline code -- a style that was partially consolidated back in v2.13 when the firmware was cleaned up for release.