Apply .gitattributes normalization to convert all CRLF line endings inherited from Windows-origin source files to Unix LF. 175 files, zero content changes.
32 KiB
Genpix SkyWalker-1 TUNE_8PSK (0x86) Protocol Deep-Dive
Executive Summary
This document presents a complete reverse engineering analysis of the TUNE_8PSK vendor command (0x86) as implemented in three Genpix SkyWalker-1 FX2 firmware versions. The analysis traces the full signal path from USB control transfer through EP0BUF parameter parsing, modulation-specific BCM4500 configuration, I2C register programming, and signal acquisition polling. Supporting commands (LNB voltage, 22 kHz tone, signal lock, signal strength) are fully documented.
Firmware versions analyzed:
- v2.06 (Ghidra port 8193) -- 61 functions, earliest revision
- v2.13 FW1 (Ghidra port 8194) -- 88 functions, latest revision
- Rev.2 v2.10.4 (Ghidra port 8197) -- 107 functions, intermediate revision
Primary analysis target: Rev.2 v2.10.4 (clearest decompilation, most granular function decomposition)
1. TUNE_8PSK Command Handler (0x86)
1.1 Vendor Dispatch Entry Points
All three firmware versions use identical vendor command dispatch logic at CODE:0056. The dispatcher checks bmRequestType bit 6 (vendor request), validates bRequest is in range, and performs an indexed jump via JMP @A+DPTR at the jump table starting at CODE:0076.
| Firmware | TUNE_8PSK Jump Target | Config Status Byte | Tune Function |
|---|---|---|---|
| v2.06 | 0x012E | IRAM 0x6D | 0x0800 (via FUN_CODE_09a7) |
| v2.13 | 0x012E | IRAM 0x4F | 0x09DF |
| Rev.2 v2.10.4 | 0x0118 | IRAM 0x4E | 0x0800 |
1.2 Handler Inline Code
The TUNE_8PSK handler at the jump table target performs two operations:
- Wait for EP0 data phase -- calls the EP0 flush function to ensure the 10-byte command payload has arrived in EP0BUF
- Check device status -- reads the config status byte and verifies bit 0 (bm8pskStarted) is set before proceeding
- Call tune function -- if the device is started, calls the main tune function
Rev.2 v2.10.4 (0x0118):
CODE:0118 LCALL 0x2167 ; EP0 flush -- wait for EP0BUF data ready
CODE:011B MOV A, 0x4E ; Read config status byte (IRAM 0x4E)
CODE:011D JNB ACC.0, +3 ; Skip tune if device not started
CODE:0120 LCALL 0x0800 ; Call main tune function (FUN_CODE_0800)
v2.06 (0x012E):
CODE:012E LCALL 0x23E0 ; EP0 flush function
CODE:0131 MOV A, 0x6D ; Config status byte (IRAM 0x6D)
CODE:0133 JNB ACC.0, +3 ; Skip if not started
CODE:0136 LCALL 0x0800 ; Main tune function
v2.13 (0x012E):
CODE:012E LCALL 0x231E ; EP0 flush function
CODE:0131 MOV A, 0x4F ; Config status byte (IRAM 0x4F)
CODE:0133 JNB ACC.0, +3 ; Skip if not started
CODE:0136 LCALL 0x09DF ; Main tune function (different address)
2. EP0BUF Parameter Parsing
2.1 10-Byte Command Format
The host (Windows BDA driver or Linux dvb-usb-gp8psk) sends a 10-byte payload via USB control transfer OUT:
USB Setup Packet:
bmRequestType = 0x40 (Vendor, Host-to-Device)
bRequest = 0x86 (TUNE_8PSK)
wValue = 0x0000
wIndex = 0x0000
wLength = 10
EP0BUF Layout (0xE740-0xE749):
Offset XRAM Addr Content Encoding
------ --------- ------------------- ------------------
[0] 0xE740 Symbol Rate byte 0 Little-endian LSB
[1] 0xE741 Symbol Rate byte 1
[2] 0xE742 Symbol Rate byte 2
[3] 0xE743 Symbol Rate byte 3 Little-endian MSB
[4] 0xE744 Frequency byte 0 Little-endian LSB
[5] 0xE745 Frequency byte 1
[6] 0xE746 Frequency byte 2
[7] 0xE747 Frequency byte 3 Little-endian MSB
[8] 0xE748 Modulation Type 0-9 (see section 3)
[9] 0xE749 Inner FEC Rate See FEC tables
Symbol Rate is in samples per second (sps). The Windows driver converts from ksps: ulTempSymbolRate = pDeviceParameter->ulSymbolRate * 1000
Frequency is in kHz, computed as (CarrierFrequency - LO_Frequency) * FrequencyMultiplier. Valid range: 800,000 - 2,250,000 kHz (after LNB downconversion: 950-2150 MHz IF).
2.2 Firmware EP0BUF Read Sequence (Rev.2 v2.10.4 Disassembly)
The tune function at 0x0800 begins with direct EP0BUF reads:
; Read modulation type and FEC rate
CODE:0802 MOV DPTR, #0xE748 ; EP0BUF[8] = modulation type
CODE:0805 MOVX A, @DPTR
CODE:0806 MOV 0x4D, A ; Store to IRAM 0x4D (DAT_INTMEM_4d)
CODE:0808 INC DPTR ; DPTR = 0xE749
CODE:0809 MOVX A, @DPTR ; EP0BUF[9] = FEC rate
CODE:080A MOV 0x4F, A ; Store to IRAM 0x4F (DAT_INTMEM_4f)
Frequency bytes (EP0BUF[4-7]) are byte-reversed from little-endian to big-endian for BCM4500:
; Loop: i = 0..3
; EP0BUF[4+i] -> XRAM[0xE0DE - i] (frequency, LE -> BE)
CODE:0816 MOV A, #0x44 ; 0x44 = EP0BUF offset for freq[0] (0xE740+4=0xE744)
CODE:0818 ADD A, 0x3B ; + loop index i
CODE:081A MOV DPL, A
CODE:081C CLR A
CODE:081D ADDC A, #0xE7 ; DPH = 0xE7
CODE:0821 MOVX A, @DPTR ; Read EP0BUF[4+i]
CODE:0822 MOV R7, A ; Save byte
; Compute destination: 0xE0D8 + (6 - i) = 0xE0DE - i
CODE:0827 MOV A, #0x06
CODE:0829 SUBB A, R5 ; 6 - i
CODE:082F MOV A, #0xD8 ; Base = 0xE0D8
CODE:0831 ADD A, R5
CODE:0832 MOV DPL, A
CODE:0834 MOV A, #0xE0
CODE:0837 MOV DPH, A
CODE:0839 MOV A, R7
CODE:083A MOVX @DPTR, A ; Store byte-reversed
Symbol rate bytes (EP0BUF[0-3]) follow the same pattern:
; EP0BUF[i] -> XRAM[0xE0C7 + (7 - i)] = XRAM[0xE0CE - i] (symbol rate, LE -> BE)
CODE:083B MOV A, #0x40 ; 0x40 = EP0BUF offset for SR[0] (0xE740+0=0xE740)
CODE:083D ADD A, 0x3B ; + loop index i
CODE:0846 MOVX A, @DPTR ; Read EP0BUF[i]
; Destination: 0xE0C7 + (7 - i) = 0xE0CE - i
CODE:084C MOV A, #0x07
CODE:084E SUBB A, R5 ; 7 - i
CODE:0854 MOV A, #0xC7 ; Base = 0xE0C7
CODE:0856 ADD A, R5
CODE:085F MOVX @DPTR, A ; Store byte-reversed
2.3 XRAM Storage Map After Parsing
IRAM Variables:
0x4D Modulation type (0-9) [from EP0BUF[8]]
0x4F Inner FEC rate index [from EP0BUF[9]]
XRAM Frequency (big-endian, 4 bytes):
0xE0DB Frequency byte 3 (MSB) [from EP0BUF[7]]
0xE0DC Frequency byte 2 [from EP0BUF[6]]
0xE0DD Frequency byte 1 [from EP0BUF[5]]
0xE0DE Frequency byte 0 (LSB) [from EP0BUF[4]]
XRAM Symbol Rate (big-endian, 4 bytes):
0xE0CB Symbol Rate byte 3 (MSB) [from EP0BUF[3]]
0xE0CC Symbol Rate byte 2 [from EP0BUF[2]]
0xE0CD Symbol Rate byte 1 [from EP0BUF[1]]
0xE0CE Symbol Rate byte 0 (LSB) [from EP0BUF[0]]
The byte reversal converts the host's little-endian format to the BCM4500's big-endian register format, so the values can be written directly to the demodulator via I2C without further conversion.
3. Modulation Mode Dispatch
3.1 Dispatch Table (Rev.2 at CODE:0873)
After parsing EP0BUF, the firmware validates the modulation type (IRAM 0x4D) is < 10, then dispatches via a jump table:
CODE:0864 MOV A, 0x4D ; Load modulation type
CODE:0866 CJNE A, #0x0A, +0 ; Compare with 10
CODE:0869 JC 0x086D ; If < 10, proceed to dispatch
CODE:086B AJMP 0x098E ; If >= 10, skip to post-tune (invalid mod)
CODE:086D MOV DPTR, #0x0873 ; Jump table base
CODE:0870 ADD A, A ; Double index (2 bytes per entry)
CODE:0872 JMP @A+DPTR ; Dispatch
Jump Table Memory (CODE:0873, 20 bytes):
Offset Bytes AJMP Target Modulation Type
------ ------ ----------- ---------------
+0x00 01 B7 0x08B7 0 = ADV_MOD_DVB_QPSK
+0x02 01 DF 0x08DF 1 = ADV_MOD_TURBO_QPSK
+0x04 01 FA 0x08FA 2 = ADV_MOD_TURBO_8PSK
+0x06 21 15 0x0915 3 = ADV_MOD_TURBO_16QAM
+0x08 21 47 0x0947 4 = ADV_MOD_DCII_C_QPSK (Combo)
+0x0A 21 4F 0x094F 5 = ADV_MOD_DCII_I_QPSK (I-stream)
+0x0C 21 57 0x0957 6 = ADV_MOD_DCII_Q_QPSK (Q-stream)
+0x0E 21 5F 0x095F 7 = ADV_MOD_DCII_C_OQPSK (Offset)
+0x10 01 87 0x0887 8 = ADV_MOD_DSS_QPSK
+0x12 01 87 0x0887 9 = ADV_MOD_DVB_BPSK
Note: Modulations 8 (DSS) and 9 (DVB BPSK) share the same handler at 0x0887.
3.2 Modulation Handler Details
Each handler reads the FEC rate from IRAM 0x4F, validates it against a maximum count, looks up a preconfigured value from an XRAM table, and writes it to XRAM 0xE0EB (the BCM4500 FEC configuration register). The handlers then set additional XRAM configuration registers.
Modulation 0: DVB-S QPSK (0x08B7)
CODE:08B7 MOV A, 0x4F ; FEC index
CODE:08BA SUBB A, #0x07 ; Max 7 FEC rates
CODE:08BC JNC 0x08D0 ; Out of range -> return error
CODE:08BE MOV A, #0xF9 ; XRAM table base = 0xE0F9
CODE:08C0 ADD A, 0x4F ; + FEC index
CODE:08C2 MOV DPL, A ; -> DPTR = 0xE0F9 + i
CODE:08C9 MOVX A, @DPTR ; Read FEC lookup value
CODE:08CA MOV DPTR, #0xE0EB ; BCM4500 FEC register
CODE:08CD MOVX @DPTR, A ; Write FEC value
; Set remaining config:
CODE:08D2 XRAM[0xE0EC] = 0x09 ; Modulation type register
CODE:08D9 XRAM[0xE0F6] = 0x00 ; Turbo mode flag = OFF
; Falls through to 0x093C:
CODE:093C XRAM[0xE0F5] = 0x10 ; Demod mode = standard
CODE:0942 IRAM[0x4E] &= 0xBF ; Clear bmDCtuned flag
Modulations 8/9: DSS QPSK / DVB BPSK (0x0887)
CODE:0887 CLR 0x04 ; Clear _0_4 flag (non-turbo indicator)
CODE:0889 MOV A, 0x4F ; FEC index
CODE:088C SUBB A, #0x07 ; Max 7 FEC rates
CODE:088E JNC 0x08A4 ; Out of range -> use default
; In range:
CODE:0890 XRAM[0xE0EB] = XRAM[0xE0F9 + FEC_index] | 0x80 ; FEC with bit 7 set
; Out of range:
CODE:08A4 XRAM[0xE0EB] = 0x8C ; Default FEC value
; Common:
CODE:08AA XRAM[0xE0EC] = 0x09 ; Mod type register
CODE:08B1 XRAM[0xE0F6] = 0x00 ; Turbo flag = OFF
; Falls through to 0x093C (same as DVB-S QPSK)
Modulation 1: Turbo QPSK (0x08DF)
CODE:08DF MOV A, 0x4F ; FEC index
CODE:08E2 SUBB A, #0x05 ; Max 5 Turbo QPSK FEC rates
CODE:08E4 JNC 0x08F8 ; Out of range -> error
CODE:08E6 XRAM[0xE0EB] = XRAM[0xE0B7 + FEC_index] ; Turbo QPSK FEC table
; Falls through to 0x0930:
CODE:0930 XRAM[0xE0EC] = 0x09 ; Mod type
CODE:0936 XRAM[0xE0F6] = 0x01 ; Turbo flag = ON
; Falls through to 0x093C:
CODE:093C XRAM[0xE0F5] = 0x10 ; Demod mode = Turbo
CODE:0942 IRAM[0x4E] &= 0xBF ; Clear bmDCtuned
Modulation 2: Turbo 8PSK (0x08FA)
CODE:08FA MOV A, 0x4F ; FEC index
CODE:08FD SUBB A, #0x05 ; Max 5 Turbo 8PSK FEC rates
CODE:08FF JNC 0x0913 ; Out of range -> error
CODE:0901 XRAM[0xE0EB] = XRAM[0xE0B1 + FEC_index] ; Turbo 8PSK FEC table
; Falls through to 0x0930 (same as Turbo QPSK)
Modulation 3: Turbo 16QAM (0x0915)
CODE:0915 MOV A, 0x4F ; FEC index
CODE:0918 SUBB A, #0x01 ; Max 1 Turbo 16QAM FEC rate
CODE:091A JNC 0x092E ; Out of range -> error
CODE:091C XRAM[0xE0EB] = XRAM[0xE0BC + FEC_index] ; Turbo 16QAM FEC table
; Falls through to 0x0930 (same as Turbo QPSK/8PSK)
Modulations 4-7: Digicipher II Variants (0x0947-0x095F)
All four DCII handlers share common post-processing but set different demod modes:
; Mod 4: DCII Combo (0x0947)
CODE:0947 XRAM[0xE0F5] = 0x10 ; Demod mode = DCII Combo
CODE:094D SJMP 0x0965 ; -> common DCII handler
; Mod 5: DCII I-stream (0x094F)
CODE:094F XRAM[0xE0F5] = 0x12 ; Demod mode = DCII I-stream (split)
CODE:0955 SJMP 0x0965
; Mod 6: DCII Q-stream (0x0957)
CODE:0957 XRAM[0xE0F5] = 0x16 ; Demod mode = DCII Q-stream (split)
CODE:095D SJMP 0x0965
; Mod 7: DCII Offset QPSK (0x095F)
CODE:095F XRAM[0xE0F5] = 0x11 ; Demod mode = DCII Offset QPSK
CODE:0964 ; Falls through to 0x0965
; Common DCII handler (0x0965):
CODE:0965 MOV A, 0x4F ; FEC index
CODE:0968 SUBB A, #0x09 ; Max 9 DCII FEC rates
CODE:096A JNC 0x097E ; Out of range -> error
CODE:096C XRAM[0xE0EC] = XRAM[0xE0BD + FEC_index] ; DCII FEC/mod table
CODE:0980 XRAM[0xE0EB] = 0xFC ; Fixed DCII FEC code
CODE:0987 XRAM[0xE0F6] = 0x00 ; Turbo flag = OFF
CODE:098B IRAM[0x4E] |= 0x40 ; SET bmDCtuned flag
3.3 BCM4500 XRAM Configuration Summary
After modulation dispatch completes, four XRAM registers hold the BCM4500 configuration:
| XRAM Address | Register Name | DVB-S | Turbo QPSK/8PSK/16QAM | DCII | DSS/BPSK |
|---|---|---|---|---|---|
| 0xE0EB | FEC Code Rate | Lookup from 0xE0F9+i | Lookup from tables | 0xFC (fixed) | Lookup |
| 0xE0EC | Modulation Type | 0x09 | 0x09 | From 0xE0BD+i table | 0x09 |
| 0xE0F5 | Demod Mode | 0x10 | 0x10 | 0x10/0x11/0x12/0x16 | 0x10 |
| 0xE0F6 | Turbo Flag | 0x00 | 0x01 | 0x00 | 0x00 |
3.4 FEC Rate Lookup Tables
The FEC lookup tables are populated at boot from the init data table in CODE space, copied to XRAM during startup:
| Table Base | Modulation | Max Index | FEC Rates |
|---|---|---|---|
| XRAM 0xE0F9 | DVB-S QPSK / DSS / BPSK | 7 | Standard Viterbi rates (1/2, 2/3, 3/4, 5/6, 7/8, auto, none) |
| XRAM 0xE0B7 | Turbo QPSK | 5 | Turbo QPSK code rates |
| XRAM 0xE0B1 | Turbo 8PSK | 5 | Turbo 8PSK code rates |
| XRAM 0xE0BC | Turbo 16QAM | 1 | Single 16QAM code rate |
| XRAM 0xE0BD | DCII (all variants) | 9 | DCII code rate + modulation combined |
4. BCM4500 I2C Write Sequence
4.1 Post-Dispatch: DVB Mode and I2C Programming
After the modulation dispatch sets XRAM configuration registers, the tune function enters the common post-processing path at CODE:098E:
; Copy _0_4 flag to _0_5 (DVB/non-DVB indicator)
CODE:098E MOV CY, 0x04 ; Read _0_4 flag
CODE:0990 MOV 0x05, CY ; Copy to _0_5
; Configure DVB mode pin
CODE:0992 LCALL 0x21D3 ; FUN_CODE_21d3: SET_DVB_MODE
; Sets P3.6 based on _0_4
; Attempt I2C programming (up to 3 tries)
CODE:0995 MOV 0x3B, #0x03 ; Retry counter = 3
CODE:0998 MOV A, 0x3B
CODE:099A JZ 0x09A7 ; If 0 retries left, return failure
CODE:099C LCALL 0x1DD0 ; FUN_CODE_1dd0: Demod I2C write sequence
CODE:099F JC 0x09A3 ; If carry set (success), return
CODE:09A1 SETB CY ; Set carry = success
CODE:09A2 RET
CODE:09A3 DEC 0x3B ; Decrement retry counter
CODE:09A5 SJMP 0x0998 ; Try again
CODE:09A7 CLR CY ; Clear carry = failure
CODE:09A8 RET
4.2 FUN_CODE_1DD0: Demod Scan (3 I2C Address Attempts)
The demod scan function tries to program the BCM4500 at up to three different I2C device addresses. This supports hardware variants where the BCM4500 may appear at different I2C addresses:
// Pseudocode for FUN_CODE_1dd0 (Rev.2 v2.10.4)
char demod_scan(void) {
for (DAT_INTMEM_3c = 0; DAT_INTMEM_3c < 3; DAT_INTMEM_3c++) {
// Compute I2C parameters from iteration index
// The multiplication by 0x11 and offset calculations produce:
// Iteration 0: device addr derived, register set A
// Iteration 1: device addr derived, register set B
// Iteration 2: device addr derived, register set C
result = FUN_CODE_1670(buf_addr, device_param, data_len);
if (result == success) {
return success; // Carry set
}
}
return failure; // Carry clear
}
4.3 FUN_CODE_1670: BCM4500 Indirect Register Write
This is the core I2C programming function. The BCM4500 uses an indirect register access protocol through three I2C-accessible registers:
BCM4500 I2C Registers (accessed at device address 0x10):
0xA6 = Page/Address register (indirect address high byte)
0xA7 = Data register (indirect data)
0xA8 = Command register (indirect command: 0x03 = write)
FUN_CODE_1670 decompiled (Rev.2 v2.10.4):
void bcm4500_indirect_write(byte buf_hi, byte device_param, byte data_count) {
// Step 1: Wait for BCM4500 ready (poll regs 0xA2, 0xA8, 0xA4)
FUN_CODE_1e73(); // 3-register bus wait
if (!carry) return; // BCM4500 not ready
// Step 2: Write page address (0x00) to register 0xA6
XRAM[0xE111] = 0x00; // Page = 0
IRAM[0x45] = 0xE1; // Buffer pointer high
IRAM[0x46] = 0x11; // Buffer pointer low -> XRAM 0xE111
FUN_CODE_136c(1, 0, 0xA6, 0x10); // I2C write 1 byte to device 0x10, reg 0xA6
// Step 3: Write data to register 0xA7
IRAM[0x45] = buf_hi; // Source buffer high byte
IRAM[0x46] = device_param; // Source buffer low byte
FUN_CODE_136c(data_count, 0, 0xA7, 0x10); // I2C write N bytes to reg 0xA7
// Step 4: Write command (0x03 = indirect write) to register 0xA8
XRAM[0xE111] = 0x03; // Command = write
IRAM[0x45] = 0xE1;
IRAM[0x46] = 0x11;
FUN_CODE_136c(1, 0, 0xA8, 0x10); // I2C write 1 byte to reg 0xA8
// Step 5: Wait for write completion (poll regs 0xA8, 0xA2)
FUN_CODE_1ea9();
// Step 6: Verify -- read back from 0xA7 and compare
XRAM[0xE111] = 0x00;
FUN_CODE_136c(1, 0, 0xA6, 0x10); // Re-select page 0
FUN_CODE_20cb(); // Read reg 0xA7
// Compare read-back with expected value
}
4.4 FUN_CODE_136C: I2C Multi-Byte Write Primitive
This is the lowest-level I2C write function that talks to the FX2's I2C controller:
// Pseudocode for FUN_CODE_136c (Rev.2 v2.10.4)
void i2c_write(byte byte_count, byte flags, byte register_addr, byte device_addr) {
// Setup I2C controller at XRAM 0xE678 (FX2 I2C register)
FUN_CODE_2000(); // Wait for I2C bus ready
FUN_CODE_2206(device_addr << 1); // Send I2C start + device address (write)
FUN_CODE_2224(register_addr); // Send register address
// Transfer data bytes from buffer at IRAM[0x45:0x46]
for (i = 0; i < byte_count; i++) {
byte data = XRAM[IRAM[0x45]:IRAM[0x46] + i];
FUN_CODE_2224(data); // Send each data byte
}
// Send I2C stop condition
FUN_CODE_1aa3(); // I2C stop + cleanup
}
4.5 Complete I2C Register Write Sequence for a Tune Operation
Combining all the above, a complete tune operation produces the following I2C bus transactions:
=== Phase 1: Pre-Tune LNB/Tone Configuration ===
(These happen before TUNE_8PSK, via separate vendor commands)
1. SET_LNB_VOLTAGE (0x8B): GPIO P0.4 high/low (no I2C)
2. SET_22KHZ_TONE (0x8C): GPIO P0.3 high/low (no I2C)
=== Phase 2: Tune Parameter Setup ===
(EP0BUF parsing -- no I2C, just XRAM writes)
3. EP0BUF[8] -> IRAM[0x4D] (modulation)
4. EP0BUF[9] -> IRAM[0x4F] (FEC rate)
5. EP0BUF[4-7] -> XRAM[0xE0DB-0xE0DE] (frequency, byte-reversed)
6. EP0BUF[0-3] -> XRAM[0xE0CB-0xE0CE] (symbol rate, byte-reversed)
=== Phase 3: Modulation Dispatch ===
(XRAM configuration writes -- no I2C)
7. XRAM[0xE0EB] = FEC lookup value (from modulation-specific table)
8. XRAM[0xE0EC] = modulation type (usually 0x09)
9. XRAM[0xE0F5] = demod mode (0x10/0x11/0x12/0x16)
10. XRAM[0xE0F6] = turbo flag (0x00 or 0x01)
=== Phase 4: DVB Mode GPIO ===
11. P3.6 set/clear based on _0_4 flag (DVB vs non-DVB mode)
=== Phase 5: BCM4500 I2C Programming (x3 retry, x3 addresses) ===
For each attempt (up to 3 retries, each trying up to 3 I2C addresses):
--- Wait for BCM4500 ready ---
12. I2C READ device 0x10, reg 0xA2 (poll status register)
13. I2C READ device 0x10, reg 0xA8 (poll command register)
14. I2C READ device 0x10, reg 0xA4 (poll lock/ready register)
--- Write page address ---
15. I2C WRITE device 0x10, reg 0xA6 <- 0x00 (select page 0)
--- Write configuration data ---
16. I2C WRITE device 0x10, reg 0xA7 <- [N config bytes from XRAM buffer]
Data includes: frequency, symbol rate, FEC, modulation, demod mode,
turbo flag -- assembled from XRAM 0xE0EB/EC/F5/F6 and 0xE0CB-CE/DB-DE
--- Issue indirect write command ---
17. I2C WRITE device 0x10, reg 0xA8 <- 0x03 (execute indirect write)
--- Wait for completion ---
18. I2C READ device 0x10, reg 0xA8 (poll for command done)
19. I2C READ device 0x10, reg 0xA2 (verify status)
--- Verify write ---
20. I2C WRITE device 0x10, reg 0xA6 <- 0x00 (re-select page)
21. I2C READ device 0x10, reg 0xA7 (read back data)
22. Compare read-back with expected value
4.6 I2C Device Address Note
The BCM4500 demodulator is accessed at I2C device address 0x10 (7-bit address, shifted to 0x20 for write, 0x21 for read on the wire). In some firmware versions, the device also responds at alternate addresses:
- 0x3F (used by v2.13 for status polling in INT0)
- 0x7F (used by v2.13 for status polling in INT0)
The use of 0x3F and 0x7F in v2.13's INT0 handler (demod availability probing) suggests these may be alternate I2C addresses on different hardware revisions, or they may access different internal register banks of the BCM4500.
5. Signal Acquisition
5.1 GET_SIGNAL_LOCK (Vendor Command 0x90)
Jump table targets:
- v2.06: 0x020B
- v2.13: 0x022D
- Rev.2: 0x0217
The signal lock handler reads BCM4500 register 0xA4 and returns the result to the host via EP0BUF.
Rev.2 implementation chain:
; Vendor command handler at 0x0217:
CODE:0217 MOV A, 0x4E ; Config status byte
CODE:0219 JNB ACC.0, ... ; Check device started
CODE:021C LCALL 0x2236 ; FUN_CODE_2236: Read lock register
; FUN_CODE_2236:
CODE:2236 MOV R7, #0xA4 ; Register = 0xA4
CODE:2238 LCALL 0x20CB ; FUN_CODE_20cb: I2C read
; FUN_CODE_20cb: I2C single register read
; Sets up buffer at XRAM 0xE114
; Calls FUN_CODE_0f00(device=0x0F, offset=0, reg=param, addr=0x10)
; Returns byte from XRAM[0xE114]
I2C transaction:
I2C READ device 0x10, register 0xA4, 1 byte -> XRAM[0xE114]
Register 0xA4 bit fields:
Bit 5 (0x20): Signal locked (demod has achieved lock)
The firmware returns the raw register byte to EP0BUF. The host driver (both Linux and Windows) interprets any non-zero value as "locked":
// Linux kernel driver (gp8psk-fe.c):
gp8psk_usb_in_op(d, GET_SIGNAL_LOCK, 0, 0, &lock, 1);
if (lock)
*status = FE_HAS_LOCK | FE_HAS_SYNC | FE_HAS_VITERBI |
FE_HAS_SIGNAL | FE_HAS_CARRIER;
// Windows BDA driver (SkyWalker1Control.cpp):
ControlUsbDevice(pKSDeviceObject, GET_SIGNAL_LOCK, 0, 0, &ucSignalStatus, 1, true);
if (ucSignalStatus)
*pbSignalLockStatus = TRUE;
5.2 GET_SIGNAL_STRENGTH (Vendor Command 0x87)
Jump table targets:
- v2.06: 0x0140
- v2.13: 0x0162
- Rev.2: 0x014C
This command returns 6 bytes of signal quality data. The first two bytes contain the SNR value.
Rev.2 implementation (FUN_CODE_15eb at 0x15EB):
The signal strength reader performs a more complex sequence than simple lock detection:
- Writes configuration to XRAM buffer via FUN_CODE_067e (multi-mode data access)
- Performs BCM4500 indirect register write via FUN_CODE_1670 (same protocol as tuning)
- Reads back BCM4500 register 0xA7 via FUN_CODE_0f00
- Compares write value with read-back for validity check
- If valid, copies 6 bytes of signal data to EP0BUF
I2C transactions during signal strength read:
1. I2C WRITE device 0x10, reg 0xA6 <- 0x00 (page select)
2. I2C WRITE device 0x10, reg 0xA7 <- [config] (request signal data)
3. I2C WRITE device 0x10, reg 0xA8 <- 0x03 (execute)
4. I2C READ device 0x10, reg 0xA7, 1 byte (verify/read)
5. Copy 6-byte result buffer to EP0BUF
EP0BUF response format (6 bytes):
Byte 0: SNR low byte (LSB)
Byte 1: SNR high byte (MSB)
Bytes 2-5: Reserved/diagnostic (BCM4500 internal registers)
SNR scaling (from Windows BDA driver):
ulSignalStrength = (int)(ucBuffer[1]) << 8 | ucBuffer[0];
// SNR is in dBu * 256 units
// SNR * 17 maps to 0-65535 range (100% at SNR >= 0x0F00)
if (ulSignalStrength <= 0x0F00)
*pulSigStrength = (ulSignalStrength << 4) + ulSignalStrength; // * 17
else
*pulSigStrength = 0xFFFF;
5.3 Differences Between Firmware Versions
v2.06 GET_SIGNAL_STRENGTH (0x0140):
- Checks IRAM[0x6D] bit 0 (demod active)
- Reads three I2C status registers (0xA2, 0xA8, 0xA4) to compute quality
- Loops up to 6 iterations polling for demod readiness
- Uses FUN_CODE_0c97 for I2C reads
v2.13 GET_SIGNAL_STRENGTH (0x0162):
- Checks IRAM[0x4F] bit 0 (demod active)
- Uses different function call chain (FUN_CODE_1278 vs v2.06's FUN_CODE_0c97)
- Same overall logic with relocated internal variables
- Simplified polling (uses consolidated BCM4500 status register)
Rev.2 GET_SIGNAL_STRENGTH (0x014C -> FUN_CODE_15eb):
- Checks IRAM[0x4E] bit 0
- Uses FUN_CODE_067e for multi-mode data access
- Validates read-back against written configuration
- Most granular implementation with explicit verify step
6. LNB and Tone Control
6.1 SET_LNB_VOLTAGE (Vendor Command 0x8B)
Jump table targets:
- v2.06: 0x01CB
- v2.13: 0x01ED
- Rev.2: 0x01D7
LNB voltage selection is pure GPIO -- no I2C transactions. The FX2 pin P0.4 directly controls a voltage regulator on the PCB.
Rev.2 FUN_CODE_21b1 decompiled:
void set_lnb_voltage(void) {
if (_0_4 != 0) {
// wValue = 1: Set 18V (horizontal / circular-left polarization)
P0 |= 0x10; // P0.4 = HIGH -> 18V
DAT_INTMEM_4e |= 0x20; // Set bmSEL18V in config status
} else {
// wValue = 0: Set 13V (vertical / circular-right polarization)
P0 &= 0xEF; // P0.4 = LOW -> 13V
DAT_INTMEM_4e &= 0xDF; // Clear bmSEL18V in config status
}
}
Pin mapping across versions:
| Version | 18V Pin | Config Status Bit |
|---|---|---|
| v2.06 | P0.4 | IRAM[0x6D] bit 5 |
| v2.13 | P0.4 | IRAM[0x4F] bit 5 |
| Rev.2 | P0.4 | IRAM[0x4E] bit 5 |
The wValue parameter from the USB SETUP packet is passed via the _0_4 bit flag (IRAM bit-addressable area). The Windows driver sends:
ControlUsbDevice(device, SET_LNB_VOLTAGE, (ucVoltage == SEC_VOLTAGE_18), 0, NULL, 0);
Polarization mapping:
- Horizontal / Circular-Left: 18V (wValue=1)
- Vertical / Circular-Right: 13V (wValue=0)
6.2 SET_22KHZ_TONE (Vendor Command 0x8C)
Jump table targets:
- v2.06: 0x01DD
- v2.13: 0x01FF
- Rev.2: 0x01E9
Like LNB voltage, the 22 kHz tone is pure GPIO. P0.3 enables/disables an external 22 kHz oscillator on the PCB.
Rev.2 FUN_CODE_21c2 decompiled:
void set_22khz_tone(void) {
if (_0_4 != 0) {
// wValue = 1: Tone ON (high band)
P0 |= 0x08; // P0.3 = HIGH -> 22 kHz oscillator enabled
DAT_INTMEM_4e |= 0x10; // Set bm22kHz in config status
} else {
// wValue = 0: Tone OFF (low band)
P0 &= 0xF7; // P0.3 = LOW -> 22 kHz oscillator disabled
DAT_INTMEM_4e &= 0xEF; // Clear bm22kHz in config status
}
}
Pin mapping across versions:
| Version | Tone Pin | Config Status Bit |
|---|---|---|
| v2.06 | P0.3 | IRAM[0x6D] bit 4 |
| v2.13 | P0.3 | IRAM[0x4F] bit 4 |
| Rev.2 | P0.3 | IRAM[0x4E] bit 4 |
The 22 kHz tone is used for satellite band selection:
- Tone ON: Select high-band LNB oscillator (universal LNB: 10.6 GHz LO)
- Tone OFF: Select low-band LNB oscillator (universal LNB: 9.75 GHz LO)
Windows driver tone logic:
// SEC_TONE_ON = 0, SEC_TONE_OFF = 1
ControlUsbDevice(device, SET_22KHZ_TONE, (ucTone == 0), 0, NULL, 0);
Note the inverted logic: SEC_TONE_ON = 0 results in wValue = 1 (tone active).
6.3 ConfigureTuner: Full Tuning Sequence (Windows BDA Driver)
The Windows driver's ConfigureTuner() function shows the complete host-side tuning sequence:
NTSTATUS ConfigureTuner(PKSDEVICE device, PBDATUNER_DEVICE_PARAMETER config) {
// Step 1: Set LNB voltage based on polarization
if (config->Polarity == BDA_POLARISATION_LINEAR_H ||
config->Polarity == BDA_POLARISATION_CIRCULAR_L) {
SetLnbVoltage(device, SEC_VOLTAGE_18); // 0x8B, wValue=1
} else {
SetLnbVoltage(device, SEC_VOLTAGE_13); // 0x8B, wValue=0
}
// Step 2: Disable tone during tune
SetTunerTone(device, SEC_TONE_OFF); // 0x8C, wValue=0
// Step 3: Send tune command
TuneDevice(device, config); // 0x86, 10 bytes
}
7. GPIO Pin Summary
All LNB, tone, and DVB mode control is performed through direct GPIO manipulation with no I2C involvement:
FX2 Port 0 Pin Assignments (Rev.2 v2.10.4):
P0.0 -- (unused in Rev.2; DiSEqC data in v2.13)
P0.1 -- (unused)
P0.2 -- (set during init, purpose TBD)
P0.3 -- 22 kHz tone oscillator enable (all versions)
P0.4 -- LNB 13V/18V voltage select (all versions)
Also DiSEqC data pin in Rev.2
P0.5 -- (unused)
P0.6 -- GPIO control (FUN_CODE_1fcf)
P0.7 -- DiSEqC data pin (v2.06 only)
FX2 Port 3 Pin Assignments (Rev.2 v2.10.4):
P3.4 -- GPIO control (FUN_CODE_1fcf)
P3.6 -- DVB mode select (SET_DVB_MODE, FUN_CODE_21d3)
8. Cross-Version Comparison
8.1 Tune Function Correspondence
| Component | v2.06 | v2.13 | Rev.2 v2.10.4 |
|---|---|---|---|
| Vendor dispatch | 0x0056 | 0x0056 | 0x0056 |
| TUNE_8PSK handler | 0x012E | 0x012E | 0x0118 |
| EP0 flush | 0x23E0 | 0x231E | 0x2167 |
| Main tune function | 0x0800 | 0x09DF | 0x0800 |
| Config status byte | IRAM 0x6D | IRAM 0x4F | IRAM 0x4E |
| Modulation storage | IRAM 0x4D* | IRAM 0x4D | IRAM 0x4D |
| FEC storage | IRAM 0x4F* | IRAM 0x4F | IRAM 0x4F |
| Mod dispatch table | 0x0873* | embedded in 0x09DF | 0x0873 |
| BCM4500 indirect write | similar | FUN_CODE_15b8 chain | FUN_CODE_1670 |
| I2C write primitive | FUN_CODE_1556 | FUN_CODE_15b8 | FUN_CODE_136c |
| Signal lock read | 0x020B | 0x022D | 0x0217 -> FUN_CODE_2236 |
| Signal strength | 0x0140 | 0x0162 | 0x014C -> FUN_CODE_15eb |
| LNB voltage | 0x01CB | 0x01ED | 0x01D7 -> FUN_CODE_21b1 |
| 22 kHz tone | 0x01DD | 0x01FF | 0x01E9 -> FUN_CODE_21c2 |
*v2.06 shares the same IRAM layout as Rev.2 for modulation/FEC but uses different XRAM offsets.
8.2 Key Architectural Differences
v2.06:
- Simplest implementation, single code path
- No demod verification or retry logic during tune
- BCM4500 status polling reads 3 registers (0xA2, 0xA8, 0xA4)
- Signal strength handler loops up to 6 times
Rev.2 v2.10.4:
- 3 I2C address attempts per tune (FUN_CODE_1dd0)
- 3 outer retries for the whole demod scan
- BCM4500 indirect write with read-back verification (FUN_CODE_1670)
- Most granular function decomposition
- Smallest binary despite highest function count
v2.13:
- Adds host-controlled DELAY_COMMAND (0x9C) for tuning timing
- Adds INIT_DEMOD (0x9A) for host-triggered re-initialization
- Adds GET_DEMOD_STATUS (0x99) for diagnostic register reads
- INT0 repurposed for demod availability polling
- Most robust error recovery (20-attempt retry loops for init)
9. Data Flow Diagram
Host Application (DVB app / szap / w_scan)
|
v
Linux dvb-usb-gp8psk / Windows BDA Driver
|
| USB Control Transfer (EP0)
| bmRequestType = 0x40 (Vendor OUT)
| bRequest = 0x86 (TUNE_8PSK)
| wValue = 0, wIndex = 0, wLength = 10
| Data: [SR0 SR1 SR2 SR3 F0 F1 F2 F3 MOD FEC]
|
v
FX2 Microcontroller (Cypress CY7C68013A)
|
| 1. EP0BUF (XRAM 0xE740-0xE749) receives 10 bytes
| 2. Parse: mod->IRAM[0x4D], fec->IRAM[0x4F]
| 3. Byte-reverse freq->XRAM[0xE0DB-DE], sr->XRAM[0xE0CB-CE]
| 4. Dispatch on mod type (jump table at CODE:0873)
| 5. Set XRAM[0xE0EB/EC/F5/F6] per modulation
| 6. GPIO: P3.6 for DVB mode
|
| I2C Bus (FX2 I2C controller at XRAM 0xE678)
| Device address: 0x10 (BCM4500)
|
v
BCM4500 Demodulator (Broadcom DVB-S/8PSK)
|
| Indirect register protocol:
| reg 0xA6 <- page (0x00)
| reg 0xA7 <- config data (freq, SR, FEC, mod params)
| reg 0xA8 <- command (0x03 = write)
|
| After programming:
| reg 0xA4 bit 5 = signal lock status
| reg 0xA7 = SNR / signal quality readback
|
v
RF Front End
|
| LNB Control (GPIO, no I2C):
| P0.4 = voltage (13V/18V)
| P0.3 = 22 kHz tone (band select)
|
v
Satellite LNB -> Dish -> Satellite
Sources
- Ghidra firmware disassembly: v2.06 (port 8193), v2.13 FW1 (port 8194), Rev.2 v2.10.4 (port 8197)
- Windows BDA driver source:
SkyWalker1_Final_Release/Source/SkyWalker1Control.cpp - Windows BDA driver headers:
SkyWalker1_Final_Release/Include/SkyWalker1Control.h,SkyWalker1CommonDef.h - Linux kernel driver:
drivers/media/usb/dvb-usb/gp8psk.c,gp8psk.h,gp8psk-fe.c - Companion documents:
gp8psk-driver-analysis.md,firmware-analysis-v206-vs-v213.md,rev2-deep-analysis.md