Add stock firmware dump, 8051 disassembler, and analysis notes
- stock_firmware.bin: 15KB dump from working device (v2.13) - disasm8051.py / v2: Custom 8051 disassemblers for FX2LP firmware analysis, used to trace init block loading and I2C sequences - STARTUP_DISASSEMBLY.md: Annotated startup sequence disassembly - TODO: Notes on stock vs custom firmware BCM4500 init differences
This commit is contained in:
parent
a12a394099
commit
97c1000d8b
8
TODO
Normal file
8
TODO
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
Both stock and our custom firmware report the same status
|
||||||
|
progression (0x00 → 0x03 → 0x07), but the stock firmware's
|
||||||
|
BCM4500 initialization actually works (SNR raw ~64000) while
|
||||||
|
the custom firmware's returns all zeros. The custom
|
||||||
|
firmware's bcm4500_boot() silently fails somewhere in the
|
||||||
|
I2C init block writes — it sets BM_STARTED | BM_FW_LOADED
|
||||||
|
but the hardware isn't actually running. This is a firmware
|
||||||
|
bug that needs investigation.
|
||||||
604
firmware-dump/STARTUP_DISASSEMBLY.md
Normal file
604
firmware-dump/STARTUP_DISASSEMBLY.md
Normal file
@ -0,0 +1,604 @@
|
|||||||
|
# Genpix SkyWalker-1 Firmware Startup Disassembly
|
||||||
|
|
||||||
|
**Binary**: `skywalker1_eeprom_full64k.bin` (65536 bytes, RAM image after EEPROM boot)
|
||||||
|
**CPU**: FX2LP (CY7C68013A) — 8051 core @ 48MHz
|
||||||
|
**Firmware region**: 0x0000-0x24FF (9472 bytes)
|
||||||
|
**Compiler**: Keil C51
|
||||||
|
**USB**: VID=0x09C0 (Cypress), PID=0x0203
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 1: Reset Vector (0x0000)
|
||||||
|
|
||||||
|
```
|
||||||
|
0000: 02 18 8D LJMP 0x188D ; -> Keil C51 startup
|
||||||
|
```
|
||||||
|
|
||||||
|
## Phase 2: Keil C51 Startup (0x188D)
|
||||||
|
|
||||||
|
### IDATA Clear
|
||||||
|
```
|
||||||
|
188D: 78 7F MOV R0,#7Fh ; Start at IDATA address 0x7F
|
||||||
|
188F: E4 CLR A ; A = 0
|
||||||
|
1890: F6 MOV @R0,A ; Clear IDATA[R0]
|
||||||
|
1891: D8 FD DJNZ R0,1890h ; Decrement R0, loop until 0
|
||||||
|
; Clears IDATA 0x7F..0x01 (127 bytes)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Stack Pointer
|
||||||
|
```
|
||||||
|
1893: 75 81 72 MOV SP,#72h ; Stack pointer = 0x72
|
||||||
|
; Stack grows up: 0x73..0x7F = 13 bytes
|
||||||
|
```
|
||||||
|
|
||||||
|
### XDATA Initialization
|
||||||
|
```
|
||||||
|
1896: 02 18 D4 LJMP 18D4h ; Jump to init table interpreter
|
||||||
|
|
||||||
|
; Init table interpreter at 0x18D4 (Keil standard STARTUP.A51):
|
||||||
|
; Reads structured init records from CODE space
|
||||||
|
; Each record has:
|
||||||
|
; - Length byte (bits 5:0 = count, bits 7:6 = mode)
|
||||||
|
; - Mode 00: IDATA/XDATA fill (address + data pairs)
|
||||||
|
; - Mode 01: Bit initialization
|
||||||
|
; - Mode 10: XDATA block copy (source addr + destination + data)
|
||||||
|
; - Zero byte terminates the table
|
||||||
|
;
|
||||||
|
; When table exhausted:
|
||||||
|
1899: 02 09 A7 LJMP 09A7h ; -> main()
|
||||||
|
```
|
||||||
|
|
||||||
|
## Phase 3: main() (0x09A7)
|
||||||
|
|
||||||
|
### Clear Global Variables
|
||||||
|
```
|
||||||
|
09A7: E4 CLR A ; A = 0
|
||||||
|
09A8: F5 2D MOV 2Dh,A ; Clear counter/state variables
|
||||||
|
09AA: F5 2C MOV 2Ch,A ; 4-byte counter at 0x2A-0x2D
|
||||||
|
09AC: F5 2B MOV 2Bh,A
|
||||||
|
09AE: F5 2A MOV 2Ah,A
|
||||||
|
09B0: F5 35 MOV 35h,A ; 4-byte counter at 0x32-0x35
|
||||||
|
09B2: F5 34 MOV 34h,A
|
||||||
|
09B4: F5 33 MOV 33h,A
|
||||||
|
09B6: F5 32 MOV 32h,A
|
||||||
|
09B8: C2 03 CLR bit_03h ; Clear SUSPEND-pending flag
|
||||||
|
09BA: C2 01 CLR bit_01h ; Clear SETUP-pending flag
|
||||||
|
```
|
||||||
|
|
||||||
|
### Call hw_init()
|
||||||
|
```
|
||||||
|
09BC: 12 13 C3 LCALL 13C3h ; -> hw_init()
|
||||||
|
```
|
||||||
|
|
||||||
|
## Phase 4: hw_init() (0x13C3)
|
||||||
|
|
||||||
|
### CPU Configuration
|
||||||
|
```
|
||||||
|
13C3: MOV DPTR,#E605h ; REVCTL (FX2LP revision control)
|
||||||
|
13C6: MOVX A,@DPTR ; Read current value
|
||||||
|
13C7: ANL A,#FDh ; Clear bit 1 (NOAUTOARM)
|
||||||
|
13C9: MOVX @DPTR,A ; REVCTL &= ~0x02
|
||||||
|
|
||||||
|
13CE: MOV DPTR,#E600h ; CPUCS
|
||||||
|
13D1: MOVX A,@DPTR
|
||||||
|
13D2: ANL A,#E5h ; Clear bits 4,3,1 (clock bits)
|
||||||
|
13D4: ORL A,#10h ; Set bit 4
|
||||||
|
13D6: MOVX @DPTR,A ; CPUCS = 48MHz clock mode
|
||||||
|
; NOP x3 (sync delay)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Interface Configuration
|
||||||
|
```
|
||||||
|
13DA: MOV DPTR,#E601h ; IFCONFIG
|
||||||
|
13DD: MOV A,#CAh ; 0xCA = 1100_1010:
|
||||||
|
; IFCLKSRC=1 (internal)
|
||||||
|
; 3048MHZ=1 (48MHz)
|
||||||
|
; IFCLKOE=0 (don't output IFCLK)
|
||||||
|
; IFCLKPOL=0
|
||||||
|
; ASYNC=1 (async GPIF)
|
||||||
|
; GSTATE=0
|
||||||
|
; IFCFG=10 (GPIF mode)
|
||||||
|
13DF: MOVX @DPTR,A
|
||||||
|
```
|
||||||
|
|
||||||
|
### GPIF Waveform Init
|
||||||
|
```
|
||||||
|
13E3: MOV DPTR,#E6F5h ; Undocumented register
|
||||||
|
13E6: MOV A,#FFh
|
||||||
|
13E8: MOVX @DPTR,A
|
||||||
|
|
||||||
|
13E9: MOV 0xAF,#07h ; Unknown SFR (FX2-specific)
|
||||||
|
|
||||||
|
13F1: LCALL 12EAh ; GPIF waveform configuration:
|
||||||
|
; Reads waveform data from XDATA 0xE000-0xE08E
|
||||||
|
; Copies 128 bytes to GPIF waveform RAM via autopointers
|
||||||
|
; Configures: GPIFCTLCFG, GPIFIDLECS, GPIFIDLECTL,
|
||||||
|
; GPIFWFSELECT, GPIFADR, CTL states
|
||||||
|
; PORTCCFG=0xFF (all alt function for GPIF)
|
||||||
|
; PORTECFG.7=1 (GPIF ready signal)
|
||||||
|
```
|
||||||
|
|
||||||
|
### State Initialization
|
||||||
|
```
|
||||||
|
13F4: CLR A
|
||||||
|
13F5: MOV 6Dh,A ; Status flags byte = 0
|
||||||
|
; bit 0: init in progress
|
||||||
|
; bit 1: 8PSK firmware loaded
|
||||||
|
; bit 3: signature check mode
|
||||||
|
; bit 6: GPIF configured
|
||||||
|
; bit 7: streaming active
|
||||||
|
13F7: CLR bit_05h
|
||||||
|
13F9: CLR bit_04h
|
||||||
|
13FB: MOV 68h,A ; Clear more state
|
||||||
|
13FD: MOV 69h,A
|
||||||
|
13FF: MOV 66h,A
|
||||||
|
1401: CLR bit_06h ; GPIF-configured flag = 0
|
||||||
|
```
|
||||||
|
|
||||||
|
### GPIO Configuration
|
||||||
|
```
|
||||||
|
1403: MOV DPTR,#E670h ; PORTACFG
|
||||||
|
1406: MOVX @DPTR,A ; = 0x00 (all GPIO, no alt functions)
|
||||||
|
|
||||||
|
1407: MOV IOA,#A4h ; Port A initial: 1010_0100
|
||||||
|
; PA7=1 (GPIF/transport select?)
|
||||||
|
; PA5=1 (8PSK_RESET deasserted)
|
||||||
|
; PA2=1 (LNB voltage select?)
|
||||||
|
; PA1=0, PA0=input
|
||||||
|
140A: MOV OEA,#FEh ; Port A direction: 1111_1110
|
||||||
|
; PA7-PA1 = output
|
||||||
|
; PA0 = input (lock detect?)
|
||||||
|
|
||||||
|
140D: MOV IOD,#F0h ; Port D initial: 1111_0000
|
||||||
|
; PD7=1 (8PSK bus D7?)
|
||||||
|
; PD6=1 (8PSK bus D6?)
|
||||||
|
; PD5=1 (8PSK bus D5?)
|
||||||
|
; PD4=1 (8PSK bus D4?)
|
||||||
|
1410: MOV OED,#F0h ; Port D direction: upper 4 output
|
||||||
|
```
|
||||||
|
|
||||||
|
### FIFO Reset Sequence
|
||||||
|
```
|
||||||
|
141C: MOV DPTR,#E604h ; FIFORESET
|
||||||
|
141F: MOV A,#80h ; NAK-ALL = 1 (hold off USB during reset)
|
||||||
|
1421: MOVX @DPTR,A
|
||||||
|
1425: MOV A,#02h ; Reset EP2
|
||||||
|
1427: MOVX @DPTR,A
|
||||||
|
142B: MOV A,#04h ; Reset EP4
|
||||||
|
142D: MOVX @DPTR,A
|
||||||
|
1431: MOV A,#06h ; Reset EP6
|
||||||
|
1433: MOVX @DPTR,A
|
||||||
|
1437: MOV A,#08h ; Reset EP8
|
||||||
|
1439: MOVX @DPTR,A
|
||||||
|
143D: CLR A ; NAK-ALL = 0 (release)
|
||||||
|
143E: MOVX @DPTR,A
|
||||||
|
```
|
||||||
|
|
||||||
|
### GPIF Timing
|
||||||
|
```
|
||||||
|
1442: MOV DPTR,#E618h ; GPIFHOLDAMOUNT
|
||||||
|
1445: MOV A,#0Ch ; 12 IFCLK cycles hold
|
||||||
|
1447: MOVX @DPTR,A
|
||||||
|
|
||||||
|
; E619-E61B (FLOWSTATE regs) = 0x00
|
||||||
|
```
|
||||||
|
|
||||||
|
### I2C Configuration
|
||||||
|
```
|
||||||
|
1461: MOV DPTR,#E67Ah ; I2CTL
|
||||||
|
1464: MOVX A,@DPTR
|
||||||
|
1465: ORL A,#01h ; Set bit 0: 400kHz I2C clock
|
||||||
|
1467: MOVX @DPTR,A
|
||||||
|
```
|
||||||
|
|
||||||
|
### Timer 2 Setup
|
||||||
|
```
|
||||||
|
1468: ANL CKCON,#DFh ; Clear Timer2 clock source bit
|
||||||
|
146B: MOV T2CON,#04h ; Timer 2: auto-reload mode
|
||||||
|
146E: MOV RCAP2H,#F8h ; Reload value = 0xF8xx (fast tick)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Phase 5: Descriptor Setup (back in main, 0x09BF)
|
||||||
|
|
||||||
|
```
|
||||||
|
09BF: MOV 0Ch,#12h ; Descriptor table high byte
|
||||||
|
09C2: MOV 0Dh,#00h ; Descriptor table low byte = 0x1200
|
||||||
|
; -> USB Device Descriptor
|
||||||
|
|
||||||
|
; Multiple descriptor pointer pairs stored in direct RAM:
|
||||||
|
; [0C:0D] = 0x1200 Device Descriptor
|
||||||
|
; [14:15] = 0x1212 Device Qualifier
|
||||||
|
; [0A:0B] = 0x121C Configuration Descriptor
|
||||||
|
; [12:13] = 0x1254 Other Speed Config
|
||||||
|
; [16:17] = 0x128C String Descriptors
|
||||||
|
; [08:09] = 0x12E8 (high-speed descriptor variant?)
|
||||||
|
|
||||||
|
; 09E3-0AE0: Copy all descriptors from CODE to XDATA
|
||||||
|
; Calculates actual XDATA base address
|
||||||
|
; Adjusts all pointers for runtime location
|
||||||
|
; Prepares EP configuration from config descriptor
|
||||||
|
```
|
||||||
|
|
||||||
|
## Phase 6: USB Enable (0x0AE1)
|
||||||
|
|
||||||
|
```
|
||||||
|
0AE1: MOV R7,09h / R6,08h ; Pass descriptor base pointer
|
||||||
|
0AE5: LCALL 1A0Eh ; Configure USB descriptors in FX2
|
||||||
|
; Reads XDATA desc, calculates checksums
|
||||||
|
; Sets up descriptor table for USB core
|
||||||
|
; Checks descriptor type -> sets bit_06h
|
||||||
|
|
||||||
|
; Enable USB interrupts
|
||||||
|
0AE8: SETB EIE.0 ; Enable INT2 (USB interrupt)
|
||||||
|
0AEA: ORL EICON,#20h ; INT2 edge trigger
|
||||||
|
|
||||||
|
0AED: MOV DPTR,#E668h ; INTSETUP
|
||||||
|
0AF0: MOVX A,@DPTR
|
||||||
|
0AF1: ORL A,#09h ; Enable autovectoring for INT2 and INT4
|
||||||
|
0AF3: MOVX @DPTR,A
|
||||||
|
|
||||||
|
0AF4: MOV DPTR,#E65Ch ; USBIE (USB Interrupt Enable)
|
||||||
|
0AF7: MOVX A,@DPTR
|
||||||
|
0AF8: ORL A,#3Dh ; Enable: SUDAV|SOF|SUTOK|SUSPEND|USBRESET
|
||||||
|
; 0x3D = 0011_1101
|
||||||
|
0AFA: MOVX @DPTR,A
|
||||||
|
|
||||||
|
0AFB: SETB EA ; Global interrupt enable (IE.7)
|
||||||
|
```
|
||||||
|
|
||||||
|
### USB Re-enumeration
|
||||||
|
```
|
||||||
|
0AFD: SETB bit_07h ; Mark "disconnected" state
|
||||||
|
0AFF: LCALL 0003h ; Call re-enumeration handler:
|
||||||
|
; Sets USBCS.DISCON=1, USBCS.RENUM=1
|
||||||
|
; Delay ~375,000 cycles (~7.8ms @ 48MHz)
|
||||||
|
; Clears USBIRQ (write 0xFF)
|
||||||
|
; Clears EPIRQ (write 0xFF)
|
||||||
|
; Clears EXIF.4
|
||||||
|
; Clears USBCS.DISCON (reconnect)
|
||||||
|
|
||||||
|
; After re-enum completes:
|
||||||
|
0B02: MOV DPTR,#E680h ; USBCS
|
||||||
|
0B05: MOVX A,@DPTR
|
||||||
|
0B06: ANL A,#F7h ; Clear DISCON bit (bit 3)
|
||||||
|
0B08: MOVX @DPTR,A ; *** USB device now visible to host ***
|
||||||
|
|
||||||
|
0B09: ANL CKCON,#F8h ; Final timer prescaler setup
|
||||||
|
```
|
||||||
|
|
||||||
|
## Phase 7: Main Loop (0x0B0C)
|
||||||
|
|
||||||
|
```
|
||||||
|
; ---- MAIN POLLING LOOP ----
|
||||||
|
0B0C: LCALL 2297h ; ep1_poll():
|
||||||
|
; Check EP1IN ready, arm if needed
|
||||||
|
; Check FLOW state registers
|
||||||
|
; Return C=1 if EP1 armed
|
||||||
|
|
||||||
|
0B0F: JNB bit_01h,0B17h ; SETUP pending (from SUDAV ISR)?
|
||||||
|
0B12: LCALL 032Ah ; Yes -> handle_setup()
|
||||||
|
0B15: CLR bit_01h ; Clear pending flag
|
||||||
|
|
||||||
|
0B17: JNB bit_03h,0B0Ch ; SUSPEND pending (from SUSPEND ISR)?
|
||||||
|
0B1A: LCALL 24DAh ; Check suspend condition
|
||||||
|
0B1D: JNC 0B0Ch ; False alarm -> continue loop
|
||||||
|
0B1F: CLR bit_03h
|
||||||
|
0B21: LCALL 21EDh ; Enter suspend (WAKEUPCS handling)
|
||||||
|
; ... wakeup recovery code ...
|
||||||
|
0B3D: LCALL 211Dh ; I2C device polling (demod status?)
|
||||||
|
; ... loop back to 0B0C ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Phase 8: SETUP Packet Handler (0x032A)
|
||||||
|
|
||||||
|
### Standard Request Dispatch
|
||||||
|
```
|
||||||
|
032A: MOV DPTR,#E6B9h ; SETUPDAT[1] = bRequest (alt mapping)
|
||||||
|
032D: MOVX A,@DPTR ; Read bRequest
|
||||||
|
032E: CJNE A,#0Ch,0331h ; Compare with 12
|
||||||
|
0331: JC 0335h ; If bRequest < 12 -> standard USB request
|
||||||
|
0333: AJMP 05ABh ; Else -> vendor request handler
|
||||||
|
|
||||||
|
; Standard USB request jump table (bRequest 0-11):
|
||||||
|
0335: MOV DPTR,#033Bh
|
||||||
|
0338: ADD A,ACC ; index * 2 (each AJMP is 2 bytes)
|
||||||
|
033A: JMP @A+DPTR ; Computed jump
|
||||||
|
|
||||||
|
; Table at 033B (12 entries):
|
||||||
|
; [0] GET_STATUS -> 0403h
|
||||||
|
; [1] CLEAR_FEATURE -> 04A6h
|
||||||
|
; [2] (reserved) -> 05ABh (stall)
|
||||||
|
; [3] SET_FEATURE -> 0536h
|
||||||
|
; [4] (reserved) -> 05ABh
|
||||||
|
; [5] SET_ADDRESS -> 05ABh (handled by hardware)
|
||||||
|
; [6] GET_DESCRIPTOR -> 0353h
|
||||||
|
; [7] (reserved) -> 05ABh
|
||||||
|
; [8] GET_CONFIGURATION -> 03FEh
|
||||||
|
; [9] SET_CONFIGURATION -> 03F4h
|
||||||
|
; [10] GET_INTERFACE -> 03DBh
|
||||||
|
; [11] SET_INTERFACE -> 03E5h
|
||||||
|
```
|
||||||
|
|
||||||
|
### Vendor Request Dispatch (0x05AB -> 0x0056)
|
||||||
|
```
|
||||||
|
05AB: LCALL 0056h ; Call vendor request dispatcher
|
||||||
|
05AE: JC 05B7h ; If handled (C=1) -> skip stall
|
||||||
|
05B0: MOV DPTR,#E6A0h ; EP0CS
|
||||||
|
05B3: MOVX A,@DPTR
|
||||||
|
05B4: ORL A,#01h ; Set STALL bit (unhandled request)
|
||||||
|
05B6: MOVX @DPTR,A
|
||||||
|
|
||||||
|
05B7: MOV DPTR,#E6A0h ; EP0CS
|
||||||
|
05BA: MOVX A,@DPTR
|
||||||
|
05BB: ORL A,#80h ; Set HSNAK bit (handshake)
|
||||||
|
05BD: MOVX @DPTR,A
|
||||||
|
05BE: RET
|
||||||
|
```
|
||||||
|
|
||||||
|
### Vendor Dispatch at 0x0056
|
||||||
|
```
|
||||||
|
; NOTE: E6B8-E6BF maps to SETUPDAT[0]-SETUPDAT[7] (alternate XDATA addresses)
|
||||||
|
; This is an undocumented but valid FX2LP mapping.
|
||||||
|
|
||||||
|
0056: MOV DPTR,#E6B8h ; SETUPDAT[0] = bmRequestType
|
||||||
|
0059: MOVX A,@DPTR
|
||||||
|
005A: JB ACC.6,005Fh ; Test bit 6: vendor request flag
|
||||||
|
005D: CLR C ; Not vendor -> return unhandled
|
||||||
|
005E: RET
|
||||||
|
|
||||||
|
005F: MOV DPTR,#E6B9h ; SETUPDAT[1] = bRequest
|
||||||
|
0062: MOVX A,@DPTR
|
||||||
|
0063: ADD A,#80h ; Remap: bRequest 0x80 -> index 0x00
|
||||||
|
; bRequest 0x89 -> index 0x09
|
||||||
|
; bRequest 0x9D -> index 0x1D
|
||||||
|
0065: CJNE A,#1Eh,0068h ; Check if index < 30
|
||||||
|
0068: JC 006Ch ; In range -> use jump table
|
||||||
|
006A: AJMP 0326h ; Out of range -> return unhandled (C=0)
|
||||||
|
|
||||||
|
006C: MOV DPTR,#0076h ; Jump table base
|
||||||
|
006F: ADD A,ACC ; index * 2 (AJMP = 2 bytes each)
|
||||||
|
0071: JNC 0075h ; Handle page crossing
|
||||||
|
0073: INC DPH
|
||||||
|
0075: JMP @A+DPTR ; Computed jump into vendor table
|
||||||
|
```
|
||||||
|
|
||||||
|
### Vendor Command Jump Table (0x0076)
|
||||||
|
|
||||||
|
| bRequest | Index | Target | Function |
|
||||||
|
|----------|-------|--------|----------|
|
||||||
|
| 0x80 | 0 | 00B2h | GET_STATUS — returns status byte `6Dh` via EP0 |
|
||||||
|
| 0x81 | 1 | 0326h | (unhandled) |
|
||||||
|
| 0x82 | 2 | 0326h | (unhandled) |
|
||||||
|
| 0x83 | 3 | 00F1h | EP0_SETUP_READ — sets up descriptor read via SUDPTR |
|
||||||
|
| 0x84 | 4 | 0102h | EP0_DATA_WRITE — host writes data to EP0 |
|
||||||
|
| 0x85 | 5 | 0110h | EP0_DATA_READ — host reads data from EP0 |
|
||||||
|
| 0x86 | 6 | 012Eh | I2C_WRITE — write I2C data block |
|
||||||
|
| 0x87 | 7 | 0140h | EP0_MULTI_READ — multi-byte EP0 read |
|
||||||
|
| 0x88 | 8 | 0326h | (unhandled) |
|
||||||
|
| **0x89** | **9** | **00C4h** | **BOOT_8PSK** — boot the 8PSK demodulator |
|
||||||
|
| 0x8A | 10 | 019Ch | I2C_READ (with status check) |
|
||||||
|
| 0x8B | 11 | 01CBh | I2C_WRITE (variant) |
|
||||||
|
| 0x8C | 12 | 01DDh | I2C_READ (variant) |
|
||||||
|
| 0x8D | 13 | 01EFh | DiSEqC / LNB control? |
|
||||||
|
| 0x8E | 14 | 0326h | (unhandled) |
|
||||||
|
| 0x8F | 15 | 01FCh | GPIF flow control / read register |
|
||||||
|
| 0x90 | 16 | 020Bh | TUNE — set frequency / symbol rate |
|
||||||
|
| 0x91 | 17 | 022Ch | TUNE_STATUS — check lock |
|
||||||
|
| 0x92 | 18 | 024Ah | SET_TRANSPORT — configure TS stream |
|
||||||
|
| 0x93 | 19 | 026Fh | GET_SIGNAL_QUALITY — read SNR/BER |
|
||||||
|
| 0x94 | 20 | 01B9h | I2C bus control |
|
||||||
|
| 0x95 | 21 | 02DFh | GET_DEMOD_STATUS |
|
||||||
|
| 0x96 | 22 | 02B4h | I2C_ADDR_SELECT |
|
||||||
|
| 0x97 | 23 | 02C1h | RAW_REGISTER_READ |
|
||||||
|
| 0x98 | 24 | 02CBh | GET_EP0_BUFFER |
|
||||||
|
| 0x99-9C | 25-28 | 0326h | (unhandled) |
|
||||||
|
| 0x9D | 29 | 02FAh | FIRMWARE_VERSION? |
|
||||||
|
|
||||||
|
## BOOT_8PSK (0x89) Handler: Complete Trace
|
||||||
|
|
||||||
|
### Entry Point (0x00C4)
|
||||||
|
```
|
||||||
|
00C4: JNB bit_06h,00DAh ; Is GPIF configured?
|
||||||
|
; [If GPIF configured]:
|
||||||
|
00C7: MOV DPTR,#E6BAh ; SETUPDAT[2] = wValueL
|
||||||
|
00CA: MOVX A,@DPTR
|
||||||
|
00CB: ADD A,#FFh ; Test if wValue == 0 (ADD #FF sets C if A>0)
|
||||||
|
00CD: MOV bit_07h,C ; bit_07h = (wValue != 0) = "load-from-host" flag
|
||||||
|
00CF: LCALL 1D4Fh ; -> boot_8psk_dispatch()
|
||||||
|
|
||||||
|
; [If NOT configured]:
|
||||||
|
00DA: CLR bit_07h ; Force "no load" mode
|
||||||
|
00DC: LCALL 1D4Fh ; -> boot_8psk_dispatch()
|
||||||
|
|
||||||
|
; [Common exit]:
|
||||||
|
00E5: CLR A
|
||||||
|
00E6: MOV DPTR,#E68Ah ; EP0BCH = 0
|
||||||
|
00E9: MOVX @DPTR,A
|
||||||
|
00EA: MOV DPTR,#E68Bh ; EP0BCL = 1
|
||||||
|
00ED: INC A
|
||||||
|
00EE: MOVX @DPTR,A ; Send 1-byte response (success/fail in EP0BUF[0])
|
||||||
|
00EF: AJMP 0328h ; SETB C; RET (command handled)
|
||||||
|
```
|
||||||
|
|
||||||
|
### boot_8psk_dispatch (0x1D4F)
|
||||||
|
```
|
||||||
|
1D4F: MOV A,6Dh ; Read status flags
|
||||||
|
1D51: RRC A ; bit 0 (init flag) -> C -> bit_08h
|
||||||
|
1D52: MOV bit_08h,C ; bit_08h = "currently booted" state
|
||||||
|
1D54: ORL IOD,#E0h ; PD7:PD5 = 1 (8PSK bus enable)
|
||||||
|
|
||||||
|
1D57: JNB bit_07h,1D93h ; If NOT "load from host" -> error path
|
||||||
|
1D5A: JB bit_08h,1DA5h ; If already booted -> return success
|
||||||
|
|
||||||
|
; --- Signature Check ---
|
||||||
|
1D5D: LCALL 16B8h ; Check 8PSK demod chip signature:
|
||||||
|
; Read descriptor type byte from XDATA
|
||||||
|
; Switch IFCONFIG to port mode (0xC0)
|
||||||
|
; Set OEB=0 (Port B = input)
|
||||||
|
; Set PA7=1, PA6=1 (select chip)
|
||||||
|
; Read Port B:
|
||||||
|
; Type 3: expect IOB=0xA5
|
||||||
|
; Type 4: expect IOB=0x5A
|
||||||
|
; Type 5: expect IOB=0x5B
|
||||||
|
; Type 6: expect IOB=0x5C
|
||||||
|
; Restore IFCONFIG
|
||||||
|
; Return C=1 if signature match
|
||||||
|
1D60: JNC 1DA5h ; Signature fail -> return with current state
|
||||||
|
|
||||||
|
; --- Begin 8PSK Boot ---
|
||||||
|
1D62: ANL IOA,#DFh ; PA5=0 (assert 8PSK reset)
|
||||||
|
1D65: ANL 6Dh,#BFh ; Clear "streaming" flag in 6Dh
|
||||||
|
1D68: CLR bit_09h ; Clear streaming-active flag
|
||||||
|
1D6A: LCALL 1919h ; GPIF abort + FIFO cleanup:
|
||||||
|
; EP2FIFOPFH, GPIFABORT, GPIFCTRL
|
||||||
|
; Wait for GPIF idle
|
||||||
|
; Reset EP2 FIFO
|
||||||
|
|
||||||
|
; --- Configure for programming ---
|
||||||
|
1D6D: MOV A,IOA
|
||||||
|
1D6F: ANL A,#FBh ; PA2=0
|
||||||
|
1D71: ORL A,#02h ; PA1=1 (select programming mode)
|
||||||
|
1D73: MOV IOA,A
|
||||||
|
1D75: MOV R7,#1Eh / R6,#00h
|
||||||
|
1D79: LCALL 1DFBh ; Delay ~30 * (CPUCS-based divisor) cycles
|
||||||
|
|
||||||
|
; --- Deassert reset ---
|
||||||
|
1D7C: ORL IOA,#20h ; PA5=1 (deassert 8PSK reset)
|
||||||
|
1D7F: ORL 6Dh,#01h ; Set "init in progress" flag
|
||||||
|
|
||||||
|
; --- Load 8PSK firmware ---
|
||||||
|
1D82: LCALL 10F2h ; load_8psk_firmware():
|
||||||
|
; I2C read from demod (addr 0x51) to get segment table
|
||||||
|
; Loop: read segment header (addr, length)
|
||||||
|
; I2C write firmware data blocks to demod
|
||||||
|
; Retry up to 5x on write failures
|
||||||
|
; Return C=0 on success, C=1 on failure
|
||||||
|
1D85: JC 1D93h ; If load failed -> error
|
||||||
|
|
||||||
|
; --- Boot init blocks ---
|
||||||
|
1D87: LCALL 0DDDh ; boot_init_blocks():
|
||||||
|
; 3 iterations (init blocks 0,1,2)
|
||||||
|
; Each: wait_for_ready() then I2C write
|
||||||
|
; Sends register configuration to demod
|
||||||
|
1D8A: JC 1D93h ; If init failed -> error
|
||||||
|
1D8C: SETB bit_08h ; Mark "8PSK booted"
|
||||||
|
1D8E: ORL IOD,#E0h ; PD7:PD5 = 1 (8PSK bus active)
|
||||||
|
1D91: SJMP 1DA5h ; -> success exit
|
||||||
|
|
||||||
|
; --- Error path ---
|
||||||
|
1D93: ANL 6Dh,#BCh ; Clear bits 0,1,6 in status
|
||||||
|
1D96: CLR bit_09h ; Clear streaming flag
|
||||||
|
1D98: LCALL 1919h ; GPIF abort + cleanup
|
||||||
|
1D9B: MOV A,IOA
|
||||||
|
1D9D: ANL A,#FDh ; PA1=0
|
||||||
|
1D9F: ORL A,#04h ; PA2=1 (restore normal GPIO)
|
||||||
|
1DA1: MOV IOA,A
|
||||||
|
1DA3: CLR bit_08h ; Mark "not booted"
|
||||||
|
|
||||||
|
; --- Return ---
|
||||||
|
1DA5: MOV C,bit_08h ; Return C = boot success
|
||||||
|
1DA7: RET
|
||||||
|
```
|
||||||
|
|
||||||
|
## USB Interrupt Vector Table (0x1600)
|
||||||
|
|
||||||
|
The FX2LP USB autovector table at 0x1600. Each entry is a 3-byte LJMP + 1 NOP (4 bytes per slot). The hardware indexes into this table via INT2IVEC.
|
||||||
|
|
||||||
|
| Offset | IRQ Source | Target | Status |
|
||||||
|
|--------|-----------------|--------|--------|
|
||||||
|
| +0x00 | SUDAV | 22E4h | **Active** — sets bit_01h, clears USBIRQ |
|
||||||
|
| +0x04 | SOF | 239Fh | **Active** |
|
||||||
|
| +0x08 | SUTOK | 2389h | **Active** |
|
||||||
|
| +0x0C | SUSPEND | 22FCh | **Active** — sets bit_03h |
|
||||||
|
| +0x10 | USBRESET | 1FB6h | **Active** |
|
||||||
|
| +0x14 | HISPEED | 1EC7h | **Active** |
|
||||||
|
| +0x18 | EP0ACK | 0032h | Stub (RETI) |
|
||||||
|
| +0x24 | EP0OUT | 0FFFh | **Active** |
|
||||||
|
| +0x28+ | EP1-8, IBN, etc | 24E0h+ | All stubs |
|
||||||
|
|
||||||
|
## USB Descriptors (0x1200)
|
||||||
|
|
||||||
|
```
|
||||||
|
Device Descriptor (18 bytes):
|
||||||
|
bLength: 18
|
||||||
|
bDescriptorType: 1 (DEVICE)
|
||||||
|
bcdUSB: 02.00
|
||||||
|
bDeviceClass: 0xFF (vendor-specific)
|
||||||
|
bDeviceSubClass: 0xFF
|
||||||
|
bDeviceProtocol: 0x00
|
||||||
|
bMaxPacketSize0: 64
|
||||||
|
idVendor: 0x09C0 (Cypress Semiconductor)
|
||||||
|
idProduct: 0x0203
|
||||||
|
bcdDevice: 0x0001
|
||||||
|
iManufacturer: 1
|
||||||
|
iProduct: 2
|
||||||
|
iSerialNumber: 3
|
||||||
|
bNumConfigurations: 1
|
||||||
|
```
|
||||||
|
|
||||||
|
## Bit-Addressable Flag Map
|
||||||
|
|
||||||
|
| Bit | Name/Purpose |
|
||||||
|
|-------|-------------|
|
||||||
|
| bit_00h | Wakeup/resume active |
|
||||||
|
| bit_01h | SETUP packet pending (from SUDAV ISR) |
|
||||||
|
| bit_02h | System initialized |
|
||||||
|
| bit_03h | SUSPEND pending (from SUSPEND ISR) |
|
||||||
|
| bit_04h | (unused/reserved) |
|
||||||
|
| bit_05h | (unused/reserved) |
|
||||||
|
| bit_06h | GPIF descriptor present |
|
||||||
|
| bit_07h | USB disconnect / "load-from-host" flag (dual use) |
|
||||||
|
| bit_08h | 8PSK demod booted successfully |
|
||||||
|
| bit_09h | Streaming active (GPIF/TS transfer in progress) |
|
||||||
|
| bit_0Ah | Autopointer direction flag |
|
||||||
|
|
||||||
|
## Direct RAM Variable Map
|
||||||
|
|
||||||
|
| Address | Name/Purpose |
|
||||||
|
|---------|-------------|
|
||||||
|
| 08h:09h | Descriptor pointer 7 (high-speed variant?) |
|
||||||
|
| 0Ah:0Bh | Configuration descriptor pointer |
|
||||||
|
| 0Ch:0Dh | Device descriptor pointer (0x1200) |
|
||||||
|
| 0Eh:0Fh | (adjusted descriptor offset) |
|
||||||
|
| 10h:11h | (adjusted descriptor offset) |
|
||||||
|
| 12h:13h | Other-speed config descriptor pointer |
|
||||||
|
| 14h:15h | Device qualifier descriptor pointer |
|
||||||
|
| 16h:17h | String descriptor pointer |
|
||||||
|
| 22h-25h | 32-bit counter/offset (XDATA copy) |
|
||||||
|
| 26h-29h | 32-bit counter (descriptor calculation) |
|
||||||
|
| 2Ah-2Dh | 32-bit counter (main state) |
|
||||||
|
| 32h-35h | 32-bit counter (main state) |
|
||||||
|
| 36h:37h | USB descriptor base (passed to 1A0E) |
|
||||||
|
| 38h | Descriptor iteration counter |
|
||||||
|
| 3Bh | Boot init block index / temp |
|
||||||
|
| 3Ch | I2C temp / ready flag |
|
||||||
|
| 3Dh:3Eh | wait_for_ready countdown |
|
||||||
|
| 3Fh:40h | Delay counter / retry counter |
|
||||||
|
| 45h:46h | I2C buffer pointer high:low |
|
||||||
|
| 49h-4Bh | Autopointer save area |
|
||||||
|
| 4Ch:4Dh | Saved IRQ state |
|
||||||
|
| 4Eh-50h | EP buffer config |
|
||||||
|
| 66h | (state variable) |
|
||||||
|
| 68h:69h | (state variables) |
|
||||||
|
| 6Dh | Master status flags byte |
|
||||||
|
| 6Fh:70h | Secondary wait counter |
|
||||||
|
|
||||||
|
## Key Observations for RAM-Load Boot Failure
|
||||||
|
|
||||||
|
1. **IDATA cleared on reset**: The startup clears IDATA 0x01-0x7F. All bit-addressable flags and direct RAM variables start at 0. This is critical — if you're loading into RAM without resetting the CPU, these won't be cleared.
|
||||||
|
|
||||||
|
2. **XDATA init from CODE space**: The Keil startup copies initialization data from a table in CODE space to XDATA. If your RAM load doesn't include this init data at the correct CODE-relative offset, XDATA won't be initialized properly.
|
||||||
|
|
||||||
|
3. **CPUCS clock switch**: The firmware switches to 48MHz immediately in hw_init(). If CPUCS is already set (from a previous boot), the second write may cause timing issues.
|
||||||
|
|
||||||
|
4. **USB re-enumeration**: The firmware explicitly disconnects (USBCS.DISCON=1), waits ~8ms, then reconnects. If you're loading via USB, this disconnect cycle will kill your USB connection.
|
||||||
|
|
||||||
|
5. **E6B8-E6BF SETUPDAT mapping**: The firmware uses an alternate XDATA mapping for SETUPDAT at 0xE6B8 instead of the standard 0xE6A5. Both mappings are valid, but your host-side code must account for this if doing SETUP data inspection.
|
||||||
|
|
||||||
|
6. **Interrupt vector overwrite risk**: The reset vector at 0x0000 and all interrupt vectors (0x0003-0x006B) contain actual code, not just jump stubs. The INT0 vector at 0x0003 contains the USB re-enumeration handler. Loading code that overwrites these vectors will break USB.
|
||||||
|
|
||||||
|
7. **GPIF waveform data**: The GPIF config at 0x12EA reads waveform data from XDATA 0xE000-0xE08E. This data must be present in XDATA before hw_init() runs. If it's missing (because the EEPROM-to-XDATA copy didn't happen), GPIF will be misconfigured.
|
||||||
|
|
||||||
|
---
|
||||||
|
*Generated from firmware analysis of skywalker1_eeprom_full64k.bin*
|
||||||
|
*Disassembly tool: disasm8051.py (custom 8051 disassembler)*
|
||||||
660
firmware-dump/disasm8051.py
Normal file
660
firmware-dump/disasm8051.py
Normal file
@ -0,0 +1,660 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""8051 disassembler for Genpix SkyWalker-1 firmware analysis.
|
||||||
|
|
||||||
|
Produces annotated disassembly of the FX2LP (8051-based) firmware,
|
||||||
|
focusing on the Keil C51 startup sequence and USB initialization.
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
import re
|
||||||
|
|
||||||
|
OPCODES = {}
|
||||||
|
|
||||||
|
def op(code, mnem, length, desc=""):
|
||||||
|
OPCODES[code] = (mnem, length, desc)
|
||||||
|
|
||||||
|
# NOP
|
||||||
|
op(0x00, "NOP", 1)
|
||||||
|
# AJMP/ACALL (page 0-7)
|
||||||
|
for page in range(8):
|
||||||
|
op(0x01 + page*0x20, f"AJMP {{a11:{page}}}", 2)
|
||||||
|
op(0x11 + page*0x20, f"ACALL {{a11:{page}}}", 2)
|
||||||
|
|
||||||
|
op(0x02, "LJMP {a16}", 3)
|
||||||
|
op(0x12, "LCALL {a16}", 3)
|
||||||
|
op(0x03, "RR A", 1)
|
||||||
|
op(0x13, "RRC A", 1)
|
||||||
|
op(0x23, "RL A", 1)
|
||||||
|
op(0x33, "RLC A", 1)
|
||||||
|
|
||||||
|
# INC
|
||||||
|
op(0x04, "INC A", 1)
|
||||||
|
op(0x05, "INC {d}", 2)
|
||||||
|
op(0x06, "INC @R0", 1)
|
||||||
|
op(0x07, "INC @R1", 1)
|
||||||
|
for i in range(8): op(0x08+i, f"INC R{i}", 1)
|
||||||
|
|
||||||
|
# JBC, JB, JNB, JC, JNC, JZ, JNZ
|
||||||
|
op(0x10, "JBC {bit},{r8}", 3)
|
||||||
|
op(0x20, "JB {bit},{r8}", 3)
|
||||||
|
op(0x30, "JNB {bit},{r8}", 3)
|
||||||
|
op(0x40, "JC {r8}", 2)
|
||||||
|
op(0x50, "JNC {r8}", 2)
|
||||||
|
op(0x60, "JZ {r8}", 2)
|
||||||
|
op(0x70, "JNZ {r8}", 2)
|
||||||
|
|
||||||
|
# DEC
|
||||||
|
op(0x14, "DEC A", 1)
|
||||||
|
op(0x15, "DEC {d}", 2)
|
||||||
|
op(0x16, "DEC @R0", 1)
|
||||||
|
op(0x17, "DEC @R1", 1)
|
||||||
|
for i in range(8): op(0x18+i, f"DEC R{i}", 1)
|
||||||
|
|
||||||
|
# ADD
|
||||||
|
op(0x24, "ADD A,#{imm}", 2)
|
||||||
|
op(0x25, "ADD A,{d}", 2)
|
||||||
|
op(0x26, "ADD A,@R0", 1)
|
||||||
|
op(0x27, "ADD A,@R1", 1)
|
||||||
|
for i in range(8): op(0x28+i, f"ADD A,R{i}", 1)
|
||||||
|
|
||||||
|
# ADDC
|
||||||
|
op(0x34, "ADDC A,#{imm}", 2)
|
||||||
|
op(0x35, "ADDC A,{d}", 2)
|
||||||
|
op(0x36, "ADDC A,@R0", 1)
|
||||||
|
op(0x37, "ADDC A,@R1", 1)
|
||||||
|
for i in range(8): op(0x38+i, f"ADDC A,R{i}", 1)
|
||||||
|
|
||||||
|
# ORL
|
||||||
|
op(0x42, "ORL {d},A", 2)
|
||||||
|
op(0x43, "ORL {d},#{imm}", 3)
|
||||||
|
op(0x44, "ORL A,#{imm}", 2)
|
||||||
|
op(0x45, "ORL A,{d}", 2)
|
||||||
|
op(0x46, "ORL A,@R0", 1)
|
||||||
|
op(0x47, "ORL A,@R1", 1)
|
||||||
|
for i in range(8): op(0x48+i, f"ORL A,R{i}", 1)
|
||||||
|
|
||||||
|
# ANL
|
||||||
|
op(0x52, "ANL {d},A", 2)
|
||||||
|
op(0x53, "ANL {d},#{imm}", 3)
|
||||||
|
op(0x54, "ANL A,#{imm}", 2)
|
||||||
|
op(0x55, "ANL A,{d}", 2)
|
||||||
|
op(0x56, "ANL A,@R0", 1)
|
||||||
|
op(0x57, "ANL A,@R1", 1)
|
||||||
|
for i in range(8): op(0x58+i, f"ANL A,R{i}", 1)
|
||||||
|
|
||||||
|
# XRL
|
||||||
|
op(0x62, "XRL {d},A", 2)
|
||||||
|
op(0x63, "XRL {d},#{imm}", 3)
|
||||||
|
op(0x64, "XRL A,#{imm}", 2)
|
||||||
|
op(0x65, "XRL A,{d}", 2)
|
||||||
|
op(0x66, "XRL A,@R0", 1)
|
||||||
|
op(0x67, "XRL A,@R1", 1)
|
||||||
|
for i in range(8): op(0x68+i, f"XRL A,R{i}", 1)
|
||||||
|
|
||||||
|
# ORL C, ANL C
|
||||||
|
op(0x72, "ORL C,{bit}", 2)
|
||||||
|
op(0xA0, "ORL C,/{bit}", 2)
|
||||||
|
op(0x82, "ANL C,{bit}", 2)
|
||||||
|
op(0xB0, "ANL C,/{bit}", 2)
|
||||||
|
|
||||||
|
# MOV bit
|
||||||
|
op(0x92, "MOV {bit},C", 2)
|
||||||
|
op(0xA2, "MOV C,{bit}", 2)
|
||||||
|
|
||||||
|
# MOV direct
|
||||||
|
op(0x75, "MOV {d},#{imm}", 3)
|
||||||
|
op(0x85, "MOV {d2},{d1}", 3)
|
||||||
|
op(0xE5, "MOV A,{d}", 2)
|
||||||
|
op(0xF5, "MOV {d},A", 2)
|
||||||
|
op(0xA5, "DB A5h", 1)
|
||||||
|
|
||||||
|
# MOV A,#imm
|
||||||
|
op(0x74, "MOV A,#{imm}", 2)
|
||||||
|
|
||||||
|
# MOV A,@Ri / MOV @Ri,A
|
||||||
|
op(0xE6, "MOV A,@R0", 1)
|
||||||
|
op(0xE7, "MOV A,@R1", 1)
|
||||||
|
op(0xF6, "MOV @R0,A", 1)
|
||||||
|
op(0xF7, "MOV @R1,A", 1)
|
||||||
|
|
||||||
|
# MOV A,Rn / MOV Rn,A
|
||||||
|
for i in range(8):
|
||||||
|
op(0xE8+i, f"MOV A,R{i}", 1)
|
||||||
|
op(0xF8+i, f"MOV R{i},A", 1)
|
||||||
|
|
||||||
|
# MOV Rn,#imm
|
||||||
|
for i in range(8): op(0x78+i, f"MOV R{i},#{{imm}}", 2)
|
||||||
|
# MOV Rn,direct
|
||||||
|
for i in range(8): op(0xA8+i, f"MOV R{i},{{d}}", 2)
|
||||||
|
# MOV direct,Rn
|
||||||
|
for i in range(8): op(0x88+i, f"MOV {{d}},R{i}", 2)
|
||||||
|
|
||||||
|
op(0x86, "MOV {d},@R0", 2)
|
||||||
|
op(0x87, "MOV {d},@R1", 2)
|
||||||
|
op(0xA6, "MOV @R0,{d}", 2)
|
||||||
|
op(0xA7, "MOV @R1,{d}", 2)
|
||||||
|
op(0x76, "MOV @R0,#{imm}", 2)
|
||||||
|
op(0x77, "MOV @R1,#{imm}", 2)
|
||||||
|
|
||||||
|
# MOV DPTR,#imm16
|
||||||
|
op(0x90, "MOV DPTR,#{imm16}", 3)
|
||||||
|
|
||||||
|
# MOVC
|
||||||
|
op(0x83, "MOVC A,@A+PC", 1)
|
||||||
|
op(0x93, "MOVC A,@A+DPTR", 1)
|
||||||
|
|
||||||
|
# MOVX
|
||||||
|
op(0xE0, "MOVX A,@DPTR", 1)
|
||||||
|
op(0xE2, "MOVX A,@R0", 1)
|
||||||
|
op(0xE3, "MOVX A,@R1", 1)
|
||||||
|
op(0xF0, "MOVX @DPTR,A", 1)
|
||||||
|
op(0xF2, "MOVX @R0,A", 1)
|
||||||
|
op(0xF3, "MOVX @R1,A", 1)
|
||||||
|
|
||||||
|
# PUSH/POP
|
||||||
|
op(0xC0, "PUSH {d}", 2)
|
||||||
|
op(0xD0, "POP {d}", 2)
|
||||||
|
|
||||||
|
# XCH
|
||||||
|
op(0xC5, "XCH A,{d}", 2)
|
||||||
|
op(0xC6, "XCH A,@R0", 1)
|
||||||
|
op(0xC7, "XCH A,@R1", 1)
|
||||||
|
for i in range(8): op(0xC8+i, f"XCH A,R{i}", 1)
|
||||||
|
|
||||||
|
# XCHD
|
||||||
|
op(0xD6, "XCHD A,@R0", 1)
|
||||||
|
op(0xD7, "XCHD A,@R1", 1)
|
||||||
|
|
||||||
|
# DJNZ
|
||||||
|
op(0xD5, "DJNZ {d},{r8}", 3)
|
||||||
|
for i in range(8): op(0xD8+i, f"DJNZ R{i},{{r8}}", 2)
|
||||||
|
|
||||||
|
# CJNE
|
||||||
|
op(0xB4, "CJNE A,#{imm},{r8}", 3)
|
||||||
|
op(0xB5, "CJNE A,{d},{r8}", 3)
|
||||||
|
op(0xB6, "CJNE @R0,#{imm},{r8}", 3)
|
||||||
|
op(0xB7, "CJNE @R1,#{imm},{r8}", 3)
|
||||||
|
for i in range(8): op(0xB8+i, f"CJNE R{i},#{{imm}},{{r8}}", 3)
|
||||||
|
|
||||||
|
# SJMP
|
||||||
|
op(0x80, "SJMP {r8}", 2)
|
||||||
|
# JMP @A+DPTR
|
||||||
|
op(0x73, "JMP @A+DPTR", 1)
|
||||||
|
# RET, RETI
|
||||||
|
op(0x22, "RET", 1)
|
||||||
|
op(0x32, "RETI", 1)
|
||||||
|
|
||||||
|
# CLR, SETB, CPL
|
||||||
|
op(0xC2, "CLR {bit}", 2)
|
||||||
|
op(0xC3, "CLR C", 1)
|
||||||
|
op(0xD2, "SETB {bit}", 2)
|
||||||
|
op(0xD3, "SETB C", 1)
|
||||||
|
op(0xB2, "CPL {bit}", 2)
|
||||||
|
op(0xB3, "CPL C", 1)
|
||||||
|
op(0xE4, "CLR A", 1)
|
||||||
|
op(0xF4, "CPL A", 1)
|
||||||
|
|
||||||
|
# MUL, DIV, DA, SWAP
|
||||||
|
op(0xA4, "MUL AB", 1)
|
||||||
|
op(0x84, "DIV AB", 1)
|
||||||
|
op(0xD4, "DA A", 1)
|
||||||
|
op(0xC4, "SWAP A", 1)
|
||||||
|
|
||||||
|
# SUBB
|
||||||
|
op(0x94, "SUBB A,#{imm}", 2)
|
||||||
|
op(0x95, "SUBB A,{d}", 2)
|
||||||
|
op(0x96, "SUBB A,@R0", 1)
|
||||||
|
op(0x97, "SUBB A,@R1", 1)
|
||||||
|
for i in range(8): op(0x98+i, f"SUBB A,R{i}", 1)
|
||||||
|
|
||||||
|
# INC DPTR
|
||||||
|
op(0xA3, "INC DPTR", 1)
|
||||||
|
|
||||||
|
|
||||||
|
# ============ SFR / XDATA / BIT names ============
|
||||||
|
|
||||||
|
SFR_NAMES = {
|
||||||
|
0x80: "IOA", 0x81: "SP", 0x82: "DPL", 0x83: "DPH",
|
||||||
|
0x84: "DPL1", 0x85: "DPH1", 0x86: "DPS", 0x87: "PCON",
|
||||||
|
0x88: "TCON", 0x89: "TMOD", 0x8A: "TL0", 0x8B: "TL1",
|
||||||
|
0x8C: "TH0", 0x8D: "TH1", 0x8E: "CKCON",
|
||||||
|
0x90: "IOB", 0x91: "EXIF",
|
||||||
|
0x98: "SCON0", 0x99: "SBUF0",
|
||||||
|
0x9A: "AUTOPTRH1", 0x9B: "AUTOPTRL1",
|
||||||
|
0x9C: "AUTOPTRH2", 0x9D: "AUTOPTRL2", 0x9E: "AUTOPTRSETUP",
|
||||||
|
0xA0: "IOC", 0xA8: "IE",
|
||||||
|
0xAA: "EP2468STAT", 0xAB: "EP24FIFOFLGS", 0xAC: "EP68FIFOFLGS",
|
||||||
|
0xB0: "IOD", 0xB1: "IOE",
|
||||||
|
0xB2: "OEA", 0xB3: "OEB", 0xB4: "OEC", 0xB5: "OED", 0xB6: "OEE",
|
||||||
|
0xC0: "SCON1", 0xC1: "SBUF1",
|
||||||
|
0xC8: "T2CON", 0xCA: "RCAP2L", 0xCB: "RCAP2H", 0xCC: "TL2", 0xCD: "TH2",
|
||||||
|
0xD0: "PSW", 0xD8: "EICON",
|
||||||
|
0xE0: "ACC", 0xE8: "EIE", 0xF0: "B", 0xF8: "EIP",
|
||||||
|
}
|
||||||
|
|
||||||
|
XREG_NAMES = {
|
||||||
|
0xE600: "CPUCS", 0xE601: "IFCONFIG", 0xE602: "PINFLAGSAB",
|
||||||
|
0xE603: "PINFLAGSCD", 0xE604: "FIFORESET", 0xE609: "REVCTL",
|
||||||
|
0xE60A: "GPIFTRIG", 0xE60B: "GPIFSGLDATH", 0xE60C: "GPIFSGLDATLX",
|
||||||
|
0xE610: "FLOWSTATE", 0xE611: "FLOWLOGIC",
|
||||||
|
0xE618: "GPIFHOLDAMOUNT",
|
||||||
|
0xE620: "EP2CFG", 0xE621: "EP4CFG", 0xE622: "EP6CFG", 0xE623: "EP8CFG",
|
||||||
|
0xE624: "EP2FIFOCFG", 0xE625: "EP4FIFOCFG",
|
||||||
|
0xE626: "EP6FIFOCFG", 0xE627: "EP8FIFOCFG",
|
||||||
|
0xE628: "EP2AUTOINLENH", 0xE629: "EP2AUTOINLENL",
|
||||||
|
0xE630: "EP2FIFOPFH", 0xE631: "EP2FIFOPFL",
|
||||||
|
0xE640: "EP2ISOINPKTS",
|
||||||
|
0xE648: "INPKTEND", 0xE649: "OUTPKTEND",
|
||||||
|
0xE650: "EP2FIFOIE", 0xE651: "EP2FIFOIRQ",
|
||||||
|
0xE65C: "USBIE", 0xE65D: "USBIRQ",
|
||||||
|
0xE65E: "EPIE", 0xE65F: "EPIRQ",
|
||||||
|
0xE660: "GPIFIE", 0xE661: "GPIFIRQ",
|
||||||
|
0xE662: "USBERRIE", 0xE663: "USBERRIRQ",
|
||||||
|
0xE666: "INT2IVEC", 0xE667: "INT4IVEC", 0xE668: "INTSETUP",
|
||||||
|
0xE670: "PORTACFG", 0xE671: "PORTCCFG", 0xE672: "PORTECFG",
|
||||||
|
0xE678: "I2CS", 0xE679: "I2DAT", 0xE67A: "I2CTL",
|
||||||
|
0xE67B: "XAUTODAT1", 0xE67C: "XAUTODAT2",
|
||||||
|
0xE680: "USBCS", 0xE681: "SUSPEND", 0xE682: "WAKEUPCS",
|
||||||
|
0xE683: "TOGCTL",
|
||||||
|
0xE684: "USBFRAMEH", 0xE685: "USBFRAMEL", 0xE686: "MICROFRAME",
|
||||||
|
0xE687: "FNADDR",
|
||||||
|
0xE68A: "EP0BCH", 0xE68B: "EP0BCL",
|
||||||
|
0xE68C: "EP1OUTBC", 0xE68D: "EP1INBC",
|
||||||
|
0xE68F: "EP1INCS",
|
||||||
|
0xE690: "EP2CS", 0xE691: "EP4CS", 0xE692: "EP6CS", 0xE693: "EP8CS",
|
||||||
|
0xE694: "EP2FIFOFLGS", 0xE695: "EP4FIFOFLGS",
|
||||||
|
0xE696: "EP6FIFOFLGS", 0xE697: "EP8FIFOFLGS",
|
||||||
|
0xE698: "EP2BCH", 0xE699: "EP2BCL",
|
||||||
|
0xE6A0: "EP0CS", 0xE6A1: "EP0STAT", 0xE6A2: "EP0STALLBITS",
|
||||||
|
0xE6A3: "CLRTOGS",
|
||||||
|
0xE6A5: "SETUPDAT[0]", 0xE6A6: "SETUPDAT[1]",
|
||||||
|
0xE6A7: "SETUPDAT[2]", 0xE6A8: "SETUPDAT[3]",
|
||||||
|
0xE6A9: "SETUPDAT[4]", 0xE6AA: "SETUPDAT[5]",
|
||||||
|
0xE6AB: "SETUPDAT[6]", 0xE6AC: "SETUPDAT[7]",
|
||||||
|
0xE6C0: "GPIFWFSELECT", 0xE6C1: "GPIFIDLECS",
|
||||||
|
0xE6C2: "GPIFIDLECTL", 0xE6C3: "GPIFCTLCFG",
|
||||||
|
0xE6C4: "GPIFADRH", 0xE6C5: "GPIFADRL",
|
||||||
|
0xE6CE: "GPIFREADYCFG", 0xE6CF: "GPIFREADYSTAT",
|
||||||
|
0xE6D0: "GPIFABORT",
|
||||||
|
0xE6F8: "SUDPTRH", 0xE6F9: "SUDPTRL", 0xE6FA: "SUDPTRCTL",
|
||||||
|
0xE740: "EP0BUF[0]",
|
||||||
|
0xE780: "EP1OUTBUF[0]",
|
||||||
|
0xE7C0: "EP1INBUF[0]",
|
||||||
|
}
|
||||||
|
|
||||||
|
BIT_NAMES = {
|
||||||
|
0xD0: "PSW.P", 0xD1: "PSW.1", 0xD2: "PSW.OV", 0xD3: "PSW.RS0",
|
||||||
|
0xD4: "PSW.RS1", 0xD5: "PSW.F0", 0xD6: "PSW.AC", 0xD7: "PSW.CY",
|
||||||
|
0xA8: "EX0", 0xA9: "ET0", 0xAA: "EX1", 0xAB: "ET1",
|
||||||
|
0xAC: "ES0", 0xAD: "ET2", 0xAE: "ES1", 0xAF: "EA",
|
||||||
|
0x80: "IOA.0", 0x81: "IOA.1", 0x82: "IOA.2", 0x83: "IOA.3",
|
||||||
|
0x84: "IOA.4", 0x85: "IOA.5", 0x86: "IOA.6", 0x87: "IOA.7",
|
||||||
|
0x90: "IOB.0", 0x91: "IOB.1", 0x92: "IOB.2", 0x93: "IOB.3",
|
||||||
|
0x94: "IOB.4", 0x95: "IOB.5", 0x96: "IOB.6", 0x97: "IOB.7",
|
||||||
|
0x88: "IT0", 0x89: "IE0", 0x8A: "IT1", 0x8B: "IE1",
|
||||||
|
0x8C: "TR0", 0x8D: "TF0", 0x8E: "TR1", 0x8F: "TF1",
|
||||||
|
0x98: "RI_0", 0x99: "TI_0",
|
||||||
|
0xD8: "EICON.0", 0xDB: "INT6", 0xDC: "RESI", 0xDD: "ERESI",
|
||||||
|
}
|
||||||
|
|
||||||
|
KNOWN_LABELS = {
|
||||||
|
0x0000: "reset_vector",
|
||||||
|
0x0003: "int0_vector",
|
||||||
|
0x000B: "timer0_vector",
|
||||||
|
0x0013: "int1_vector",
|
||||||
|
0x001B: "timer1_vector",
|
||||||
|
0x0023: "serial0_vector",
|
||||||
|
0x002B: "timer2_vector",
|
||||||
|
0x0033: "resume_vector",
|
||||||
|
0x003B: "serial1_vector",
|
||||||
|
0x0043: "usb_int2_vector",
|
||||||
|
0x004B: "i2c_int3_vector",
|
||||||
|
0x0053: "gpif_int4_vector",
|
||||||
|
0x005B: "int5_vector",
|
||||||
|
0x0063: "int6_vector",
|
||||||
|
0x099A: "tune_function",
|
||||||
|
0x0DDD: "boot_init_blocks",
|
||||||
|
0x0EE9: "tune_init_blocks",
|
||||||
|
0x1200: "usb_device_desc",
|
||||||
|
0x1556: "i2c_combined_read",
|
||||||
|
0x188D: "keil_startup",
|
||||||
|
0x1A81: "i2c_write",
|
||||||
|
0x1D87: "boot_8psk",
|
||||||
|
0x2000: "wait_for_ready",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def sfr_name(addr):
|
||||||
|
if addr in SFR_NAMES:
|
||||||
|
return SFR_NAMES[addr]
|
||||||
|
if addr >= 0x80:
|
||||||
|
return f"SFR_{addr:02X}h"
|
||||||
|
return f"{addr:02X}h"
|
||||||
|
|
||||||
|
def bit_name(addr):
|
||||||
|
if addr in BIT_NAMES:
|
||||||
|
return BIT_NAMES[addr]
|
||||||
|
if addr >= 0x80:
|
||||||
|
base = addr & 0xF8
|
||||||
|
bit = addr & 0x07
|
||||||
|
if base in SFR_NAMES:
|
||||||
|
return f"{SFR_NAMES[base]}.{bit}"
|
||||||
|
return f"bit_{addr:02X}h"
|
||||||
|
|
||||||
|
def xreg_name(addr):
|
||||||
|
if addr in XREG_NAMES:
|
||||||
|
return XREG_NAMES[addr]
|
||||||
|
if 0xE740 <= addr <= 0xE77F:
|
||||||
|
return f"EP0BUF[{addr-0xE740}]"
|
||||||
|
if 0xE780 <= addr <= 0xE7BF:
|
||||||
|
return f"EP1OUTBUF[{addr-0xE780}]"
|
||||||
|
if 0xE7C0 <= addr <= 0xE7FF:
|
||||||
|
return f"EP1INBUF[{addr-0xE7C0}]"
|
||||||
|
if 0xE600 <= addr <= 0xE6FF:
|
||||||
|
return f"XSFR_{addr:04X}h"
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def disasm_one(data, pc):
|
||||||
|
"""Returns (mnem, hex_str, length, comment, branch_target)."""
|
||||||
|
if pc >= len(data):
|
||||||
|
return ("???", "", 1, "", None)
|
||||||
|
|
||||||
|
opc = data[pc]
|
||||||
|
entry = OPCODES.get(opc)
|
||||||
|
|
||||||
|
if entry is None:
|
||||||
|
return (f"DB {opc:02X}h", f"{opc:02X}", 1, "; UNKNOWN", None)
|
||||||
|
|
||||||
|
mnem_fmt, length, desc = entry
|
||||||
|
|
||||||
|
if pc + length > len(data):
|
||||||
|
return (f"DB {opc:02X}h", f"{opc:02X}", 1, "; TRUNCATED", None)
|
||||||
|
|
||||||
|
raw_bytes = data[pc:pc+length]
|
||||||
|
hex_str = " ".join(f"{b:02X}" for b in raw_bytes)
|
||||||
|
comment = ""
|
||||||
|
branch_target = None
|
||||||
|
mnem = mnem_fmt
|
||||||
|
|
||||||
|
# Resolve address fields
|
||||||
|
if "{a16}" in mnem:
|
||||||
|
addr16 = (data[pc+1] << 8) | data[pc+2]
|
||||||
|
label = KNOWN_LABELS.get(addr16)
|
||||||
|
mnem = mnem.replace("{a16}", label if label else f"{addr16:04X}h")
|
||||||
|
branch_target = addr16
|
||||||
|
|
||||||
|
m = re.search(r'\{a11:(\d+)\}', mnem)
|
||||||
|
if m:
|
||||||
|
page = int(m.group(1))
|
||||||
|
addr11 = (page << 8) | data[pc+1] | ((pc + 2) & 0xF800)
|
||||||
|
label = KNOWN_LABELS.get(addr11)
|
||||||
|
mnem = re.sub(r'\{a11:\d+\}', label if label else f"{addr11:04X}h", mnem)
|
||||||
|
branch_target = addr11
|
||||||
|
|
||||||
|
if "{r8}" in mnem:
|
||||||
|
rel_byte = data[pc + length - 1]
|
||||||
|
rel = rel_byte if rel_byte < 0x80 else rel_byte - 256
|
||||||
|
target = (pc + length + rel) & 0xFFFF
|
||||||
|
label = KNOWN_LABELS.get(target)
|
||||||
|
mnem = mnem.replace("{r8}", label if label else f"{target:04X}h")
|
||||||
|
branch_target = target
|
||||||
|
|
||||||
|
# Direct addressing - handle the special MOV d2,d1 case
|
||||||
|
if "{d2}" in mnem and "{d1}" in mnem:
|
||||||
|
src = data[pc+1]
|
||||||
|
dst = data[pc+2]
|
||||||
|
mnem = mnem.replace("{d1}", sfr_name(src) if src >= 0x20 else f"{src:02X}h")
|
||||||
|
mnem = mnem.replace("{d2}", sfr_name(dst) if dst >= 0x20 else f"{dst:02X}h")
|
||||||
|
elif "{d}" in mnem:
|
||||||
|
d = data[pc+1]
|
||||||
|
mnem = mnem.replace("{d}", sfr_name(d) if d >= 0x20 else f"{d:02X}h")
|
||||||
|
|
||||||
|
if "{bit}" in mnem:
|
||||||
|
b = data[pc+1]
|
||||||
|
mnem = mnem.replace("{bit}", bit_name(b))
|
||||||
|
|
||||||
|
if "{imm16}" in mnem:
|
||||||
|
imm16 = (data[pc+1] << 8) | data[pc+2]
|
||||||
|
xname = xreg_name(imm16)
|
||||||
|
if xname:
|
||||||
|
mnem = mnem.replace("{imm16}", f"{imm16:04X}h")
|
||||||
|
comment = f"; = {xname}"
|
||||||
|
elif imm16 in KNOWN_LABELS:
|
||||||
|
mnem = mnem.replace("{imm16}", f"{imm16:04X}h")
|
||||||
|
comment = f"; -> {KNOWN_LABELS[imm16]}"
|
||||||
|
else:
|
||||||
|
mnem = mnem.replace("{imm16}", f"{imm16:04X}h")
|
||||||
|
|
||||||
|
if "{imm}" in mnem:
|
||||||
|
# Determine which byte is the immediate
|
||||||
|
if length == 3 and opc in (0x43, 0x53, 0x63):
|
||||||
|
imm = data[pc+2] # ORL/ANL/XRL d,#imm
|
||||||
|
elif length == 3 and opc == 0x75:
|
||||||
|
imm = data[pc+2] # MOV d,#imm
|
||||||
|
elif length == 3 and opc == 0xB4:
|
||||||
|
imm = data[pc+1] # CJNE A,#imm,r8
|
||||||
|
elif length == 3 and 0xB6 <= opc <= 0xBF:
|
||||||
|
imm = data[pc+1] # CJNE @Ri/#imm or CJNE Rn,#imm
|
||||||
|
elif length == 3 and opc == 0xD5:
|
||||||
|
# DJNZ d,r8 - no imm, but {d} already handled
|
||||||
|
imm = 0 # shouldn't happen
|
||||||
|
else:
|
||||||
|
imm = data[pc + length - 1]
|
||||||
|
mnem = mnem.replace("{imm}", f"{imm:02X}h")
|
||||||
|
|
||||||
|
return (mnem, hex_str, length, comment, branch_target)
|
||||||
|
|
||||||
|
|
||||||
|
def disasm_range(data, start, end, title=""):
|
||||||
|
lines = []
|
||||||
|
if title:
|
||||||
|
lines.append(f"\n{'='*80}")
|
||||||
|
lines.append(f" {title}")
|
||||||
|
lines.append(f"{'='*80}")
|
||||||
|
|
||||||
|
pc = start
|
||||||
|
while pc < end and pc < len(data):
|
||||||
|
if pc in KNOWN_LABELS:
|
||||||
|
lines.append(f"\n {KNOWN_LABELS[pc]}:")
|
||||||
|
|
||||||
|
mnem, hex_str, length, comment, target = disasm_one(data, pc)
|
||||||
|
addr_str = f"{pc:04X}"
|
||||||
|
hex_padded = f"{hex_str:<12s}"
|
||||||
|
line = f" {addr_str}: {hex_padded} {mnem}"
|
||||||
|
if comment:
|
||||||
|
line = f"{line:<58s} {comment}"
|
||||||
|
lines.append(line)
|
||||||
|
pc += length
|
||||||
|
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
def track_dptr_accesses(data, start, end):
|
||||||
|
"""Track all MOVX accesses with DPTR context."""
|
||||||
|
results = []
|
||||||
|
pc = start
|
||||||
|
dptr = None
|
||||||
|
while pc < end and pc < len(data):
|
||||||
|
opc = data[pc]
|
||||||
|
entry = OPCODES.get(opc)
|
||||||
|
if entry is None:
|
||||||
|
pc += 1
|
||||||
|
continue
|
||||||
|
_, length, _ = entry
|
||||||
|
if pc + length > len(data):
|
||||||
|
break
|
||||||
|
if opc == 0x90: # MOV DPTR,#imm16
|
||||||
|
dptr = (data[pc+1] << 8) | data[pc+2]
|
||||||
|
elif opc in (0xE0, 0xF0) and dptr is not None:
|
||||||
|
xname = xreg_name(dptr)
|
||||||
|
direction = "READ" if opc == 0xE0 else "WRITE"
|
||||||
|
results.append((pc, dptr, xname, direction))
|
||||||
|
elif opc == 0xA3: # INC DPTR
|
||||||
|
if dptr is not None:
|
||||||
|
dptr += 1
|
||||||
|
pc += length
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
fw_path = "/home/rpm/claude/ham/satellite/genpix/skywalker-1/firmware-dump/skywalker1_eeprom_full64k.bin"
|
||||||
|
with open(fw_path, "rb") as f:
|
||||||
|
data = f.read()
|
||||||
|
|
||||||
|
out = []
|
||||||
|
out.append("=" * 80)
|
||||||
|
out.append(" GENPIX SKYWALKER-1 FIRMWARE DISASSEMBLY")
|
||||||
|
out.append(" FX2LP (CY7C68013A) 8051 Core - Keil C51 Compiled")
|
||||||
|
out.append(" Firmware size: 9472 bytes (0x0000-0x24FF)")
|
||||||
|
out.append(" Binary: 65536 bytes total (RAM image)")
|
||||||
|
out.append("=" * 80)
|
||||||
|
|
||||||
|
# ==== SECTION 1: Reset + Interrupt Vectors ====
|
||||||
|
out.append(disasm_range(data, 0x0000, 0x006B,
|
||||||
|
"SECTION 1: Reset Vector & Interrupt Vector Table (0x0000-0x006A)"))
|
||||||
|
|
||||||
|
# ==== SECTION 2: Keil C51 Startup ====
|
||||||
|
out.append(disasm_range(data, 0x188D, 0x1950,
|
||||||
|
"SECTION 2: Keil C51 Startup (STARTUP.A51) @ 0x188D"))
|
||||||
|
|
||||||
|
# Find main() by tracing startup
|
||||||
|
pc = 0x188D
|
||||||
|
main_addr = None
|
||||||
|
for _ in range(500):
|
||||||
|
if pc >= len(data) - 2:
|
||||||
|
break
|
||||||
|
opc = data[pc]
|
||||||
|
entry = OPCODES.get(opc)
|
||||||
|
if entry is None:
|
||||||
|
break
|
||||||
|
_, length, _ = entry
|
||||||
|
if opc == 0x02: # LJMP
|
||||||
|
addr = (data[pc+1] << 8) | data[pc+2]
|
||||||
|
# The startup has two LJMPs: one internal loop, one to main
|
||||||
|
# main() is the last LJMP before we hit data/different code
|
||||||
|
if addr < 0x188D: # Jump backward = likely to main()
|
||||||
|
main_addr = addr
|
||||||
|
pc += length
|
||||||
|
|
||||||
|
if main_addr:
|
||||||
|
KNOWN_LABELS[main_addr] = "main"
|
||||||
|
out.append(f"\n >>> Keil startup jumps to main() at {main_addr:04X}h")
|
||||||
|
|
||||||
|
# ==== SECTION 3: main() ====
|
||||||
|
if main_addr:
|
||||||
|
# Disassemble main() - generous range to cover init
|
||||||
|
out.append(disasm_range(data, main_addr, min(main_addr + 400, 0x2500),
|
||||||
|
f"SECTION 3: main() @ {main_addr:04X}h (first ~400 bytes)"))
|
||||||
|
|
||||||
|
# ==== SECTION 4: Vendor command dispatch ====
|
||||||
|
out.append("\n" + "=" * 80)
|
||||||
|
out.append(" SECTION 4: USB Vendor Command Dispatch")
|
||||||
|
out.append("=" * 80)
|
||||||
|
|
||||||
|
# Find all reads of SETUPDAT[1] (bRequest)
|
||||||
|
for pc in range(0x2500):
|
||||||
|
if pc + 2 < len(data) and data[pc] == 0x90 and data[pc+1] == 0xE6 and data[pc+2] == 0xA6:
|
||||||
|
out.append(f"\n SETUPDAT[1] read at {pc:04X}h:")
|
||||||
|
ctx_start = max(0, pc - 16)
|
||||||
|
ctx_end = min(pc + 120, 0x2500)
|
||||||
|
out.append(disasm_range(data, ctx_start, ctx_end,
|
||||||
|
f" Dispatch context @ {pc:04X}h"))
|
||||||
|
|
||||||
|
# ==== SECTION 5: Find all CJNE A,#xx comparisons (command dispatch) ====
|
||||||
|
out.append("\n" + "=" * 80)
|
||||||
|
out.append(" SECTION 5: All CJNE A,#xx Instructions (Command Dispatch Table)")
|
||||||
|
out.append("=" * 80)
|
||||||
|
|
||||||
|
for pc in range(0x2500):
|
||||||
|
if pc + 2 < len(data) and data[pc] == 0xB4:
|
||||||
|
imm = data[pc+1]
|
||||||
|
rel = data[pc+2]
|
||||||
|
if rel >= 0x80:
|
||||||
|
rel -= 256
|
||||||
|
target = (pc + 3 + rel) & 0xFFFF
|
||||||
|
out.append(f" {pc:04X}: CJNE A,#{imm:02X}h,{target:04X}h ; bRequest?=0x{imm:02X} skip->{target:04X}h")
|
||||||
|
|
||||||
|
# ==== SECTION 6: boot_8psk ====
|
||||||
|
out.append(disasm_range(data, 0x1D87, min(0x1D87 + 250, 0x2500),
|
||||||
|
"SECTION 6: boot_8psk() @ 0x1D87"))
|
||||||
|
|
||||||
|
# ==== SECTION 7: SFR access map ====
|
||||||
|
out.append("\n" + "=" * 80)
|
||||||
|
out.append(" SECTION 7: All XDATA SFR Accesses (DPTR-tracked)")
|
||||||
|
out.append("=" * 80)
|
||||||
|
|
||||||
|
accesses = track_dptr_accesses(data, 0x0000, 0x2500)
|
||||||
|
for pc, addr, name, direction in accesses:
|
||||||
|
name_str = name if name else f"XDATA_{addr:04X}h"
|
||||||
|
out.append(f" {pc:04X}: {direction:5s} [{addr:04X}h] {name_str}")
|
||||||
|
|
||||||
|
# ==== SECTION 8: Key functions ====
|
||||||
|
out.append(disasm_range(data, 0x0DDD, 0x0DDD + 120,
|
||||||
|
"SECTION 8: boot_init_blocks() @ 0x0DDD"))
|
||||||
|
|
||||||
|
out.append(disasm_range(data, 0x1A81, 0x1A81 + 150,
|
||||||
|
"SECTION 9: i2c_write() @ 0x1A81"))
|
||||||
|
|
||||||
|
out.append(disasm_range(data, 0x1556, 0x1556 + 150,
|
||||||
|
"SECTION 10: i2c_combined_read() @ 0x1556"))
|
||||||
|
|
||||||
|
out.append(disasm_range(data, 0x2000, 0x2000 + 120,
|
||||||
|
"SECTION 11: wait_for_ready() @ 0x2000"))
|
||||||
|
|
||||||
|
out.append(disasm_range(data, 0x099A, 0x099A + 200,
|
||||||
|
"SECTION 12: tune_function() @ 0x099A"))
|
||||||
|
|
||||||
|
# ==== SECTION 13: USB Descriptors ====
|
||||||
|
out.append("\n" + "=" * 80)
|
||||||
|
out.append(" SECTION 13: USB Descriptors @ 0x1200")
|
||||||
|
out.append("=" * 80)
|
||||||
|
|
||||||
|
desc = data[0x1200:0x1212]
|
||||||
|
out.append(f" Device Descriptor (18 bytes):")
|
||||||
|
out.append(f" bLength: {desc[0]} (0x{desc[0]:02X})")
|
||||||
|
out.append(f" bDescriptorType: {desc[1]} (DEVICE)")
|
||||||
|
out.append(f" bcdUSB: {desc[3]:02X}.{desc[2]:02X}")
|
||||||
|
out.append(f" bDeviceClass: 0x{desc[4]:02X} (vendor-specific)")
|
||||||
|
out.append(f" bDeviceSubClass: 0x{desc[5]:02X}")
|
||||||
|
out.append(f" bDeviceProtocol: 0x{desc[6]:02X}")
|
||||||
|
out.append(f" bMaxPacketSize0: {desc[7]} bytes")
|
||||||
|
vid = desc[8] | (desc[9] << 8)
|
||||||
|
pid = desc[10] | (desc[11] << 8)
|
||||||
|
out.append(f" idVendor: 0x{vid:04X} (Cypress)")
|
||||||
|
out.append(f" idProduct: 0x{pid:04X}")
|
||||||
|
bcd = desc[12] | (desc[13] << 8)
|
||||||
|
out.append(f" bcdDevice: {bcd:04X}")
|
||||||
|
out.append(f" iManufacturer: {desc[14]}")
|
||||||
|
out.append(f" iProduct: {desc[15]}")
|
||||||
|
out.append(f" iSerialNumber: {desc[16]}")
|
||||||
|
out.append(f" bNumConfigurations: {desc[17]}")
|
||||||
|
|
||||||
|
# Raw descriptor dump
|
||||||
|
out.append(f"\n Raw descriptor area 0x1200-0x1300:")
|
||||||
|
for off in range(0, 0x100, 16):
|
||||||
|
addr = 0x1200 + off
|
||||||
|
chunk = data[addr:addr+16]
|
||||||
|
hexl = " ".join(f"{b:02X}" for b in chunk)
|
||||||
|
asciil = "".join(chr(b) if 32 <= b < 127 else "." for b in chunk)
|
||||||
|
out.append(f" {addr:04X}: {hexl} {asciil}")
|
||||||
|
|
||||||
|
# ==== SECTION 14: Hex dump of interesting code regions ====
|
||||||
|
out.append("\n" + "=" * 80)
|
||||||
|
out.append(" SECTION 14: Raw Hex - Code Space Near USB ISR Targets")
|
||||||
|
out.append("=" * 80)
|
||||||
|
|
||||||
|
# Show hex around the USB interrupt vector dispatch
|
||||||
|
# The USB ISR at 0x0043 likely jumps somewhere
|
||||||
|
for start_addr in [0x0043, 0x004B]:
|
||||||
|
out.append(f"\n Hex dump at {start_addr:04X}h:")
|
||||||
|
for off in range(0, 48, 16):
|
||||||
|
addr = start_addr + off
|
||||||
|
chunk = data[addr:addr+16]
|
||||||
|
hexl = " ".join(f"{b:02X}" for b in chunk)
|
||||||
|
out.append(f" {addr:04X}: {hexl}")
|
||||||
|
|
||||||
|
print("\n".join(out))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
660
firmware-dump/disasm8051_v2.py
Normal file
660
firmware-dump/disasm8051_v2.py
Normal file
@ -0,0 +1,660 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""8051 disassembler for Genpix SkyWalker-1 firmware analysis.
|
||||||
|
|
||||||
|
Produces annotated disassembly of the FX2LP (8051-based) firmware,
|
||||||
|
focusing on the Keil C51 startup sequence and USB initialization.
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
import re
|
||||||
|
|
||||||
|
OPCODES = {}
|
||||||
|
|
||||||
|
def op(code, mnem, length, desc=""):
|
||||||
|
OPCODES[code] = (mnem, length, desc)
|
||||||
|
|
||||||
|
# NOP
|
||||||
|
op(0x00, "NOP", 1)
|
||||||
|
# AJMP/ACALL (page 0-7)
|
||||||
|
for page in range(8):
|
||||||
|
op(0x01 + page*0x20, f"AJMP {{a11:{page}}}", 2)
|
||||||
|
op(0x11 + page*0x20, f"ACALL {{a11:{page}}}", 2)
|
||||||
|
|
||||||
|
op(0x02, "LJMP {a16}", 3)
|
||||||
|
op(0x12, "LCALL {a16}", 3)
|
||||||
|
op(0x03, "RR A", 1)
|
||||||
|
op(0x13, "RRC A", 1)
|
||||||
|
op(0x23, "RL A", 1)
|
||||||
|
op(0x33, "RLC A", 1)
|
||||||
|
|
||||||
|
# INC
|
||||||
|
op(0x04, "INC A", 1)
|
||||||
|
op(0x05, "INC {d}", 2)
|
||||||
|
op(0x06, "INC @R0", 1)
|
||||||
|
op(0x07, "INC @R1", 1)
|
||||||
|
for i in range(8): op(0x08+i, f"INC R{i}", 1)
|
||||||
|
|
||||||
|
# JBC, JB, JNB, JC, JNC, JZ, JNZ
|
||||||
|
op(0x10, "JBC {bit},{r8}", 3)
|
||||||
|
op(0x20, "JB {bit},{r8}", 3)
|
||||||
|
op(0x30, "JNB {bit},{r8}", 3)
|
||||||
|
op(0x40, "JC {r8}", 2)
|
||||||
|
op(0x50, "JNC {r8}", 2)
|
||||||
|
op(0x60, "JZ {r8}", 2)
|
||||||
|
op(0x70, "JNZ {r8}", 2)
|
||||||
|
|
||||||
|
# DEC
|
||||||
|
op(0x14, "DEC A", 1)
|
||||||
|
op(0x15, "DEC {d}", 2)
|
||||||
|
op(0x16, "DEC @R0", 1)
|
||||||
|
op(0x17, "DEC @R1", 1)
|
||||||
|
for i in range(8): op(0x18+i, f"DEC R{i}", 1)
|
||||||
|
|
||||||
|
# ADD
|
||||||
|
op(0x24, "ADD A,#{imm}", 2)
|
||||||
|
op(0x25, "ADD A,{d}", 2)
|
||||||
|
op(0x26, "ADD A,@R0", 1)
|
||||||
|
op(0x27, "ADD A,@R1", 1)
|
||||||
|
for i in range(8): op(0x28+i, f"ADD A,R{i}", 1)
|
||||||
|
|
||||||
|
# ADDC
|
||||||
|
op(0x34, "ADDC A,#{imm}", 2)
|
||||||
|
op(0x35, "ADDC A,{d}", 2)
|
||||||
|
op(0x36, "ADDC A,@R0", 1)
|
||||||
|
op(0x37, "ADDC A,@R1", 1)
|
||||||
|
for i in range(8): op(0x38+i, f"ADDC A,R{i}", 1)
|
||||||
|
|
||||||
|
# ORL
|
||||||
|
op(0x42, "ORL {d},A", 2)
|
||||||
|
op(0x43, "ORL {d},#{imm}", 3)
|
||||||
|
op(0x44, "ORL A,#{imm}", 2)
|
||||||
|
op(0x45, "ORL A,{d}", 2)
|
||||||
|
op(0x46, "ORL A,@R0", 1)
|
||||||
|
op(0x47, "ORL A,@R1", 1)
|
||||||
|
for i in range(8): op(0x48+i, f"ORL A,R{i}", 1)
|
||||||
|
|
||||||
|
# ANL
|
||||||
|
op(0x52, "ANL {d},A", 2)
|
||||||
|
op(0x53, "ANL {d},#{imm}", 3)
|
||||||
|
op(0x54, "ANL A,#{imm}", 2)
|
||||||
|
op(0x55, "ANL A,{d}", 2)
|
||||||
|
op(0x56, "ANL A,@R0", 1)
|
||||||
|
op(0x57, "ANL A,@R1", 1)
|
||||||
|
for i in range(8): op(0x58+i, f"ANL A,R{i}", 1)
|
||||||
|
|
||||||
|
# XRL
|
||||||
|
op(0x62, "XRL {d},A", 2)
|
||||||
|
op(0x63, "XRL {d},#{imm}", 3)
|
||||||
|
op(0x64, "XRL A,#{imm}", 2)
|
||||||
|
op(0x65, "XRL A,{d}", 2)
|
||||||
|
op(0x66, "XRL A,@R0", 1)
|
||||||
|
op(0x67, "XRL A,@R1", 1)
|
||||||
|
for i in range(8): op(0x68+i, f"XRL A,R{i}", 1)
|
||||||
|
|
||||||
|
# ORL C, ANL C
|
||||||
|
op(0x72, "ORL C,{bit}", 2)
|
||||||
|
op(0xA0, "ORL C,/{bit}", 2)
|
||||||
|
op(0x82, "ANL C,{bit}", 2)
|
||||||
|
op(0xB0, "ANL C,/{bit}", 2)
|
||||||
|
|
||||||
|
# MOV bit
|
||||||
|
op(0x92, "MOV {bit},C", 2)
|
||||||
|
op(0xA2, "MOV C,{bit}", 2)
|
||||||
|
|
||||||
|
# MOV direct
|
||||||
|
op(0x75, "MOV {d},#{imm}", 3)
|
||||||
|
op(0x85, "MOV {d2},{d1}", 3)
|
||||||
|
op(0xE5, "MOV A,{d}", 2)
|
||||||
|
op(0xF5, "MOV {d},A", 2)
|
||||||
|
op(0xA5, "DB A5h", 1)
|
||||||
|
|
||||||
|
# MOV A,#imm
|
||||||
|
op(0x74, "MOV A,#{imm}", 2)
|
||||||
|
|
||||||
|
# MOV A,@Ri / MOV @Ri,A
|
||||||
|
op(0xE6, "MOV A,@R0", 1)
|
||||||
|
op(0xE7, "MOV A,@R1", 1)
|
||||||
|
op(0xF6, "MOV @R0,A", 1)
|
||||||
|
op(0xF7, "MOV @R1,A", 1)
|
||||||
|
|
||||||
|
# MOV A,Rn / MOV Rn,A
|
||||||
|
for i in range(8):
|
||||||
|
op(0xE8+i, f"MOV A,R{i}", 1)
|
||||||
|
op(0xF8+i, f"MOV R{i},A", 1)
|
||||||
|
|
||||||
|
# MOV Rn,#imm
|
||||||
|
for i in range(8): op(0x78+i, f"MOV R{i},#{{imm}}", 2)
|
||||||
|
# MOV Rn,direct
|
||||||
|
for i in range(8): op(0xA8+i, f"MOV R{i},{{d}}", 2)
|
||||||
|
# MOV direct,Rn
|
||||||
|
for i in range(8): op(0x88+i, f"MOV {{d}},R{i}", 2)
|
||||||
|
|
||||||
|
op(0x86, "MOV {d},@R0", 2)
|
||||||
|
op(0x87, "MOV {d},@R1", 2)
|
||||||
|
op(0xA6, "MOV @R0,{d}", 2)
|
||||||
|
op(0xA7, "MOV @R1,{d}", 2)
|
||||||
|
op(0x76, "MOV @R0,#{imm}", 2)
|
||||||
|
op(0x77, "MOV @R1,#{imm}", 2)
|
||||||
|
|
||||||
|
# MOV DPTR,#imm16
|
||||||
|
op(0x90, "MOV DPTR,#{imm16}", 3)
|
||||||
|
|
||||||
|
# MOVC
|
||||||
|
op(0x83, "MOVC A,@A+PC", 1)
|
||||||
|
op(0x93, "MOVC A,@A+DPTR", 1)
|
||||||
|
|
||||||
|
# MOVX
|
||||||
|
op(0xE0, "MOVX A,@DPTR", 1)
|
||||||
|
op(0xE2, "MOVX A,@R0", 1)
|
||||||
|
op(0xE3, "MOVX A,@R1", 1)
|
||||||
|
op(0xF0, "MOVX @DPTR,A", 1)
|
||||||
|
op(0xF2, "MOVX @R0,A", 1)
|
||||||
|
op(0xF3, "MOVX @R1,A", 1)
|
||||||
|
|
||||||
|
# PUSH/POP
|
||||||
|
op(0xC0, "PUSH {d}", 2)
|
||||||
|
op(0xD0, "POP {d}", 2)
|
||||||
|
|
||||||
|
# XCH
|
||||||
|
op(0xC5, "XCH A,{d}", 2)
|
||||||
|
op(0xC6, "XCH A,@R0", 1)
|
||||||
|
op(0xC7, "XCH A,@R1", 1)
|
||||||
|
for i in range(8): op(0xC8+i, f"XCH A,R{i}", 1)
|
||||||
|
|
||||||
|
# XCHD
|
||||||
|
op(0xD6, "XCHD A,@R0", 1)
|
||||||
|
op(0xD7, "XCHD A,@R1", 1)
|
||||||
|
|
||||||
|
# DJNZ
|
||||||
|
op(0xD5, "DJNZ {d},{r8}", 3)
|
||||||
|
for i in range(8): op(0xD8+i, f"DJNZ R{i},{{r8}}", 2)
|
||||||
|
|
||||||
|
# CJNE
|
||||||
|
op(0xB4, "CJNE A,#{imm},{r8}", 3)
|
||||||
|
op(0xB5, "CJNE A,{d},{r8}", 3)
|
||||||
|
op(0xB6, "CJNE @R0,#{imm},{r8}", 3)
|
||||||
|
op(0xB7, "CJNE @R1,#{imm},{r8}", 3)
|
||||||
|
for i in range(8): op(0xB8+i, f"CJNE R{i},#{{imm}},{{r8}}", 3)
|
||||||
|
|
||||||
|
# SJMP
|
||||||
|
op(0x80, "SJMP {r8}", 2)
|
||||||
|
# JMP @A+DPTR
|
||||||
|
op(0x73, "JMP @A+DPTR", 1)
|
||||||
|
# RET, RETI
|
||||||
|
op(0x22, "RET", 1)
|
||||||
|
op(0x32, "RETI", 1)
|
||||||
|
|
||||||
|
# CLR, SETB, CPL
|
||||||
|
op(0xC2, "CLR {bit}", 2)
|
||||||
|
op(0xC3, "CLR C", 1)
|
||||||
|
op(0xD2, "SETB {bit}", 2)
|
||||||
|
op(0xD3, "SETB C", 1)
|
||||||
|
op(0xB2, "CPL {bit}", 2)
|
||||||
|
op(0xB3, "CPL C", 1)
|
||||||
|
op(0xE4, "CLR A", 1)
|
||||||
|
op(0xF4, "CPL A", 1)
|
||||||
|
|
||||||
|
# MUL, DIV, DA, SWAP
|
||||||
|
op(0xA4, "MUL AB", 1)
|
||||||
|
op(0x84, "DIV AB", 1)
|
||||||
|
op(0xD4, "DA A", 1)
|
||||||
|
op(0xC4, "SWAP A", 1)
|
||||||
|
|
||||||
|
# SUBB
|
||||||
|
op(0x94, "SUBB A,#{imm}", 2)
|
||||||
|
op(0x95, "SUBB A,{d}", 2)
|
||||||
|
op(0x96, "SUBB A,@R0", 1)
|
||||||
|
op(0x97, "SUBB A,@R1", 1)
|
||||||
|
for i in range(8): op(0x98+i, f"SUBB A,R{i}", 1)
|
||||||
|
|
||||||
|
# INC DPTR
|
||||||
|
op(0xA3, "INC DPTR", 1)
|
||||||
|
|
||||||
|
|
||||||
|
# ============ SFR / XDATA / BIT names ============
|
||||||
|
|
||||||
|
SFR_NAMES = {
|
||||||
|
0x80: "IOA", 0x81: "SP", 0x82: "DPL", 0x83: "DPH",
|
||||||
|
0x84: "DPL1", 0x85: "DPH1", 0x86: "DPS", 0x87: "PCON",
|
||||||
|
0x88: "TCON", 0x89: "TMOD", 0x8A: "TL0", 0x8B: "TL1",
|
||||||
|
0x8C: "TH0", 0x8D: "TH1", 0x8E: "CKCON",
|
||||||
|
0x90: "IOB", 0x91: "EXIF",
|
||||||
|
0x98: "SCON0", 0x99: "SBUF0",
|
||||||
|
0x9A: "AUTOPTRH1", 0x9B: "AUTOPTRL1",
|
||||||
|
0x9C: "AUTOPTRH2", 0x9D: "AUTOPTRL2", 0x9E: "AUTOPTRSETUP",
|
||||||
|
0xA0: "IOC", 0xA8: "IE",
|
||||||
|
0xAA: "EP2468STAT", 0xAB: "EP24FIFOFLGS", 0xAC: "EP68FIFOFLGS",
|
||||||
|
0xB0: "IOD", 0xB1: "IOE",
|
||||||
|
0xB2: "OEA", 0xB3: "OEB", 0xB4: "OEC", 0xB5: "OED", 0xB6: "OEE",
|
||||||
|
0xC0: "SCON1", 0xC1: "SBUF1",
|
||||||
|
0xC8: "T2CON", 0xCA: "RCAP2L", 0xCB: "RCAP2H", 0xCC: "TL2", 0xCD: "TH2",
|
||||||
|
0xD0: "PSW", 0xD8: "EICON",
|
||||||
|
0xE0: "ACC", 0xE8: "EIE", 0xF0: "B", 0xF8: "EIP",
|
||||||
|
}
|
||||||
|
|
||||||
|
XREG_NAMES = {
|
||||||
|
0xE600: "CPUCS", 0xE601: "IFCONFIG", 0xE602: "PINFLAGSAB",
|
||||||
|
0xE603: "PINFLAGSCD", 0xE604: "FIFORESET", 0xE609: "REVCTL",
|
||||||
|
0xE60A: "GPIFTRIG", 0xE60B: "GPIFSGLDATH", 0xE60C: "GPIFSGLDATLX",
|
||||||
|
0xE610: "FLOWSTATE", 0xE611: "FLOWLOGIC",
|
||||||
|
0xE618: "GPIFHOLDAMOUNT",
|
||||||
|
0xE620: "EP2CFG", 0xE621: "EP4CFG", 0xE622: "EP6CFG", 0xE623: "EP8CFG",
|
||||||
|
0xE624: "EP2FIFOCFG", 0xE625: "EP4FIFOCFG",
|
||||||
|
0xE626: "EP6FIFOCFG", 0xE627: "EP8FIFOCFG",
|
||||||
|
0xE628: "EP2AUTOINLENH", 0xE629: "EP2AUTOINLENL",
|
||||||
|
0xE630: "EP2FIFOPFH", 0xE631: "EP2FIFOPFL",
|
||||||
|
0xE640: "EP2ISOINPKTS",
|
||||||
|
0xE648: "INPKTEND", 0xE649: "OUTPKTEND",
|
||||||
|
0xE650: "EP2FIFOIE", 0xE651: "EP2FIFOIRQ",
|
||||||
|
0xE65C: "USBIE", 0xE65D: "USBIRQ",
|
||||||
|
0xE65E: "EPIE", 0xE65F: "EPIRQ",
|
||||||
|
0xE660: "GPIFIE", 0xE661: "GPIFIRQ",
|
||||||
|
0xE662: "USBERRIE", 0xE663: "USBERRIRQ",
|
||||||
|
0xE666: "INT2IVEC", 0xE667: "INT4IVEC", 0xE668: "INTSETUP",
|
||||||
|
0xE670: "PORTACFG", 0xE671: "PORTCCFG", 0xE672: "PORTECFG",
|
||||||
|
0xE678: "I2CS", 0xE679: "I2DAT", 0xE67A: "I2CTL",
|
||||||
|
0xE67B: "XAUTODAT1", 0xE67C: "XAUTODAT2",
|
||||||
|
0xE680: "USBCS", 0xE681: "SUSPEND", 0xE682: "WAKEUPCS",
|
||||||
|
0xE683: "TOGCTL",
|
||||||
|
0xE684: "USBFRAMEH", 0xE685: "USBFRAMEL", 0xE686: "MICROFRAME",
|
||||||
|
0xE687: "FNADDR",
|
||||||
|
0xE68A: "EP0BCH", 0xE68B: "EP0BCL",
|
||||||
|
0xE68C: "EP1OUTBC", 0xE68D: "EP1INBC",
|
||||||
|
0xE68F: "EP1INCS",
|
||||||
|
0xE690: "EP2CS", 0xE691: "EP4CS", 0xE692: "EP6CS", 0xE693: "EP8CS",
|
||||||
|
0xE694: "EP2FIFOFLGS", 0xE695: "EP4FIFOFLGS",
|
||||||
|
0xE696: "EP6FIFOFLGS", 0xE697: "EP8FIFOFLGS",
|
||||||
|
0xE698: "EP2BCH", 0xE699: "EP2BCL",
|
||||||
|
0xE6A0: "EP0CS", 0xE6A1: "EP0STAT", 0xE6A2: "EP0STALLBITS",
|
||||||
|
0xE6A3: "CLRTOGS",
|
||||||
|
0xE6A5: "SETUPDAT[0]", 0xE6A6: "SETUPDAT[1]",
|
||||||
|
0xE6A7: "SETUPDAT[2]", 0xE6A8: "SETUPDAT[3]",
|
||||||
|
0xE6A9: "SETUPDAT[4]", 0xE6AA: "SETUPDAT[5]",
|
||||||
|
0xE6AB: "SETUPDAT[6]", 0xE6AC: "SETUPDAT[7]",
|
||||||
|
0xE6C0: "GPIFWFSELECT", 0xE6C1: "GPIFIDLECS",
|
||||||
|
0xE6C2: "GPIFIDLECTL", 0xE6C3: "GPIFCTLCFG",
|
||||||
|
0xE6C4: "GPIFADRH", 0xE6C5: "GPIFADRL",
|
||||||
|
0xE6CE: "GPIFREADYCFG", 0xE6CF: "GPIFREADYSTAT",
|
||||||
|
0xE6D0: "GPIFABORT",
|
||||||
|
0xE6F8: "SUDPTRH", 0xE6F9: "SUDPTRL", 0xE6FA: "SUDPTRCTL",
|
||||||
|
0xE740: "EP0BUF[0]",
|
||||||
|
0xE780: "EP1OUTBUF[0]",
|
||||||
|
0xE7C0: "EP1INBUF[0]",
|
||||||
|
}
|
||||||
|
|
||||||
|
BIT_NAMES = {
|
||||||
|
0xD0: "PSW.P", 0xD1: "PSW.1", 0xD2: "PSW.OV", 0xD3: "PSW.RS0",
|
||||||
|
0xD4: "PSW.RS1", 0xD5: "PSW.F0", 0xD6: "PSW.AC", 0xD7: "PSW.CY",
|
||||||
|
0xA8: "EX0", 0xA9: "ET0", 0xAA: "EX1", 0xAB: "ET1",
|
||||||
|
0xAC: "ES0", 0xAD: "ET2", 0xAE: "ES1", 0xAF: "EA",
|
||||||
|
0x80: "IOA.0", 0x81: "IOA.1", 0x82: "IOA.2", 0x83: "IOA.3",
|
||||||
|
0x84: "IOA.4", 0x85: "IOA.5", 0x86: "IOA.6", 0x87: "IOA.7",
|
||||||
|
0x90: "IOB.0", 0x91: "IOB.1", 0x92: "IOB.2", 0x93: "IOB.3",
|
||||||
|
0x94: "IOB.4", 0x95: "IOB.5", 0x96: "IOB.6", 0x97: "IOB.7",
|
||||||
|
0x88: "IT0", 0x89: "IE0", 0x8A: "IT1", 0x8B: "IE1",
|
||||||
|
0x8C: "TR0", 0x8D: "TF0", 0x8E: "TR1", 0x8F: "TF1",
|
||||||
|
0x98: "RI_0", 0x99: "TI_0",
|
||||||
|
0xD8: "EICON.0", 0xDB: "INT6", 0xDC: "RESI", 0xDD: "ERESI",
|
||||||
|
}
|
||||||
|
|
||||||
|
KNOWN_LABELS = {
|
||||||
|
0x0000: "reset_vector",
|
||||||
|
0x0003: "int0_vector",
|
||||||
|
0x000B: "timer0_vector",
|
||||||
|
0x0013: "int1_vector",
|
||||||
|
0x001B: "timer1_vector",
|
||||||
|
0x0023: "serial0_vector",
|
||||||
|
0x002B: "timer2_vector",
|
||||||
|
0x0033: "resume_vector",
|
||||||
|
0x003B: "serial1_vector",
|
||||||
|
0x0043: "usb_int2_vector",
|
||||||
|
0x004B: "i2c_int3_vector",
|
||||||
|
0x0053: "gpif_int4_vector",
|
||||||
|
0x005B: "int5_vector",
|
||||||
|
0x0063: "int6_vector",
|
||||||
|
0x099A: "tune_function",
|
||||||
|
0x0DDD: "boot_init_blocks",
|
||||||
|
0x0EE9: "tune_init_blocks",
|
||||||
|
0x1200: "usb_device_desc",
|
||||||
|
0x1556: "i2c_combined_read",
|
||||||
|
0x188D: "keil_startup",
|
||||||
|
0x1A81: "i2c_write",
|
||||||
|
0x1D87: "boot_8psk",
|
||||||
|
0x2000: "wait_for_ready",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def sfr_name(addr):
|
||||||
|
if addr in SFR_NAMES:
|
||||||
|
return SFR_NAMES[addr]
|
||||||
|
if addr >= 0x80:
|
||||||
|
return f"SFR_{addr:02X}h"
|
||||||
|
return f"{addr:02X}h"
|
||||||
|
|
||||||
|
def bit_name(addr):
|
||||||
|
if addr in BIT_NAMES:
|
||||||
|
return BIT_NAMES[addr]
|
||||||
|
if addr >= 0x80:
|
||||||
|
base = addr & 0xF8
|
||||||
|
bit = addr & 0x07
|
||||||
|
if base in SFR_NAMES:
|
||||||
|
return f"{SFR_NAMES[base]}.{bit}"
|
||||||
|
return f"bit_{addr:02X}h"
|
||||||
|
|
||||||
|
def xreg_name(addr):
|
||||||
|
if addr in XREG_NAMES:
|
||||||
|
return XREG_NAMES[addr]
|
||||||
|
if 0xE740 <= addr <= 0xE77F:
|
||||||
|
return f"EP0BUF[{addr-0xE740}]"
|
||||||
|
if 0xE780 <= addr <= 0xE7BF:
|
||||||
|
return f"EP1OUTBUF[{addr-0xE780}]"
|
||||||
|
if 0xE7C0 <= addr <= 0xE7FF:
|
||||||
|
return f"EP1INBUF[{addr-0xE7C0}]"
|
||||||
|
if 0xE600 <= addr <= 0xE6FF:
|
||||||
|
return f"XSFR_{addr:04X}h"
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def disasm_one(data, pc):
|
||||||
|
"""Returns (mnem, hex_str, length, comment, branch_target)."""
|
||||||
|
if pc >= len(data):
|
||||||
|
return ("???", "", 1, "", None)
|
||||||
|
|
||||||
|
opc = data[pc]
|
||||||
|
entry = OPCODES.get(opc)
|
||||||
|
|
||||||
|
if entry is None:
|
||||||
|
return (f"DB {opc:02X}h", f"{opc:02X}", 1, "; UNKNOWN", None)
|
||||||
|
|
||||||
|
mnem_fmt, length, desc = entry
|
||||||
|
|
||||||
|
if pc + length > len(data):
|
||||||
|
return (f"DB {opc:02X}h", f"{opc:02X}", 1, "; TRUNCATED", None)
|
||||||
|
|
||||||
|
raw_bytes = data[pc:pc+length]
|
||||||
|
hex_str = " ".join(f"{b:02X}" for b in raw_bytes)
|
||||||
|
comment = ""
|
||||||
|
branch_target = None
|
||||||
|
mnem = mnem_fmt
|
||||||
|
|
||||||
|
# Resolve address fields
|
||||||
|
if "{a16}" in mnem:
|
||||||
|
addr16 = (data[pc+1] << 8) | data[pc+2]
|
||||||
|
label = KNOWN_LABELS.get(addr16)
|
||||||
|
mnem = mnem.replace("{a16}", label if label else f"{addr16:04X}h")
|
||||||
|
branch_target = addr16
|
||||||
|
|
||||||
|
m = re.search(r'\{a11:(\d+)\}', mnem)
|
||||||
|
if m:
|
||||||
|
page = int(m.group(1))
|
||||||
|
addr11 = (page << 8) | data[pc+1] | ((pc + 2) & 0xF800)
|
||||||
|
label = KNOWN_LABELS.get(addr11)
|
||||||
|
mnem = re.sub(r'\{a11:\d+\}', label if label else f"{addr11:04X}h", mnem)
|
||||||
|
branch_target = addr11
|
||||||
|
|
||||||
|
if "{r8}" in mnem:
|
||||||
|
rel_byte = data[pc + length - 1]
|
||||||
|
rel = rel_byte if rel_byte < 0x80 else rel_byte - 256
|
||||||
|
target = (pc + length + rel) & 0xFFFF
|
||||||
|
label = KNOWN_LABELS.get(target)
|
||||||
|
mnem = mnem.replace("{r8}", label if label else f"{target:04X}h")
|
||||||
|
branch_target = target
|
||||||
|
|
||||||
|
# Direct addressing - handle the special MOV d2,d1 case
|
||||||
|
if "{d2}" in mnem and "{d1}" in mnem:
|
||||||
|
src = data[pc+1]
|
||||||
|
dst = data[pc+2]
|
||||||
|
mnem = mnem.replace("{d1}", sfr_name(src) if src >= 0x20 else f"{src:02X}h")
|
||||||
|
mnem = mnem.replace("{d2}", sfr_name(dst) if dst >= 0x20 else f"{dst:02X}h")
|
||||||
|
elif "{d}" in mnem:
|
||||||
|
d = data[pc+1]
|
||||||
|
mnem = mnem.replace("{d}", sfr_name(d) if d >= 0x20 else f"{d:02X}h")
|
||||||
|
|
||||||
|
if "{bit}" in mnem:
|
||||||
|
b = data[pc+1]
|
||||||
|
mnem = mnem.replace("{bit}", bit_name(b))
|
||||||
|
|
||||||
|
if "{imm16}" in mnem:
|
||||||
|
imm16 = (data[pc+1] << 8) | data[pc+2]
|
||||||
|
xname = xreg_name(imm16)
|
||||||
|
if xname:
|
||||||
|
mnem = mnem.replace("{imm16}", f"{imm16:04X}h")
|
||||||
|
comment = f"; = {xname}"
|
||||||
|
elif imm16 in KNOWN_LABELS:
|
||||||
|
mnem = mnem.replace("{imm16}", f"{imm16:04X}h")
|
||||||
|
comment = f"; -> {KNOWN_LABELS[imm16]}"
|
||||||
|
else:
|
||||||
|
mnem = mnem.replace("{imm16}", f"{imm16:04X}h")
|
||||||
|
|
||||||
|
if "{imm}" in mnem:
|
||||||
|
# Determine which byte is the immediate
|
||||||
|
if length == 3 and opc in (0x43, 0x53, 0x63):
|
||||||
|
imm = data[pc+2] # ORL/ANL/XRL d,#imm
|
||||||
|
elif length == 3 and opc == 0x75:
|
||||||
|
imm = data[pc+2] # MOV d,#imm
|
||||||
|
elif length == 3 and opc == 0xB4:
|
||||||
|
imm = data[pc+1] # CJNE A,#imm,r8
|
||||||
|
elif length == 3 and 0xB6 <= opc <= 0xBF:
|
||||||
|
imm = data[pc+1] # CJNE @Ri/#imm or CJNE Rn,#imm
|
||||||
|
elif length == 3 and opc == 0xD5:
|
||||||
|
# DJNZ d,r8 - no imm, but {d} already handled
|
||||||
|
imm = 0 # shouldn't happen
|
||||||
|
else:
|
||||||
|
imm = data[pc + length - 1]
|
||||||
|
mnem = mnem.replace("{imm}", f"{imm:02X}h")
|
||||||
|
|
||||||
|
return (mnem, hex_str, length, comment, branch_target)
|
||||||
|
|
||||||
|
|
||||||
|
def disasm_range(data, start, end, title=""):
|
||||||
|
lines = []
|
||||||
|
if title:
|
||||||
|
lines.append(f"\n{'='*80}")
|
||||||
|
lines.append(f" {title}")
|
||||||
|
lines.append(f"{'='*80}")
|
||||||
|
|
||||||
|
pc = start
|
||||||
|
while pc < end and pc < len(data):
|
||||||
|
if pc in KNOWN_LABELS:
|
||||||
|
lines.append(f"\n {KNOWN_LABELS[pc]}:")
|
||||||
|
|
||||||
|
mnem, hex_str, length, comment, target = disasm_one(data, pc)
|
||||||
|
addr_str = f"{pc:04X}"
|
||||||
|
hex_padded = f"{hex_str:<12s}"
|
||||||
|
line = f" {addr_str}: {hex_padded} {mnem}"
|
||||||
|
if comment:
|
||||||
|
line = f"{line:<58s} {comment}"
|
||||||
|
lines.append(line)
|
||||||
|
pc += length
|
||||||
|
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
def track_dptr_accesses(data, start, end):
|
||||||
|
"""Track all MOVX accesses with DPTR context."""
|
||||||
|
results = []
|
||||||
|
pc = start
|
||||||
|
dptr = None
|
||||||
|
while pc < end and pc < len(data):
|
||||||
|
opc = data[pc]
|
||||||
|
entry = OPCODES.get(opc)
|
||||||
|
if entry is None:
|
||||||
|
pc += 1
|
||||||
|
continue
|
||||||
|
_, length, _ = entry
|
||||||
|
if pc + length > len(data):
|
||||||
|
break
|
||||||
|
if opc == 0x90: # MOV DPTR,#imm16
|
||||||
|
dptr = (data[pc+1] << 8) | data[pc+2]
|
||||||
|
elif opc in (0xE0, 0xF0) and dptr is not None:
|
||||||
|
xname = xreg_name(dptr)
|
||||||
|
direction = "READ" if opc == 0xE0 else "WRITE"
|
||||||
|
results.append((pc, dptr, xname, direction))
|
||||||
|
elif opc == 0xA3: # INC DPTR
|
||||||
|
if dptr is not None:
|
||||||
|
dptr += 1
|
||||||
|
pc += length
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
fw_path = "/home/rpm/claude/ham/satellite/genpix/skywalker-1/firmware-dump/skywalker1_eeprom_full64k.bin"
|
||||||
|
with open(fw_path, "rb") as f:
|
||||||
|
data = f.read()
|
||||||
|
|
||||||
|
out = []
|
||||||
|
out.append("=" * 80)
|
||||||
|
out.append(" GENPIX SKYWALKER-1 FIRMWARE DISASSEMBLY")
|
||||||
|
out.append(" FX2LP (CY7C68013A) 8051 Core - Keil C51 Compiled")
|
||||||
|
out.append(" Firmware size: 9472 bytes (0x0000-0x24FF)")
|
||||||
|
out.append(" Binary: 65536 bytes total (RAM image)")
|
||||||
|
out.append("=" * 80)
|
||||||
|
|
||||||
|
# ==== SECTION 1: Reset + Interrupt Vectors ====
|
||||||
|
out.append(disasm_range(data, 0x0000, 0x006B,
|
||||||
|
"SECTION 1: Reset Vector & Interrupt Vector Table (0x0000-0x006A)"))
|
||||||
|
|
||||||
|
# ==== SECTION 2: Keil C51 Startup ====
|
||||||
|
out.append(disasm_range(data, 0x188D, 0x1950,
|
||||||
|
"SECTION 2: Keil C51 Startup (STARTUP.A51) @ 0x188D"))
|
||||||
|
|
||||||
|
# Find main() by tracing startup
|
||||||
|
pc = 0x188D
|
||||||
|
main_addr = None
|
||||||
|
for _ in range(500):
|
||||||
|
if pc >= len(data) - 2:
|
||||||
|
break
|
||||||
|
opc = data[pc]
|
||||||
|
entry = OPCODES.get(opc)
|
||||||
|
if entry is None:
|
||||||
|
break
|
||||||
|
_, length, _ = entry
|
||||||
|
if opc == 0x02: # LJMP
|
||||||
|
addr = (data[pc+1] << 8) | data[pc+2]
|
||||||
|
# The startup has two LJMPs: one internal loop, one to main
|
||||||
|
# main() is the last LJMP before we hit data/different code
|
||||||
|
if addr < 0x188D: # Jump backward = likely to main()
|
||||||
|
main_addr = addr
|
||||||
|
pc += length
|
||||||
|
|
||||||
|
if main_addr:
|
||||||
|
KNOWN_LABELS[main_addr] = "main"
|
||||||
|
out.append(f"\n >>> Keil startup jumps to main() at {main_addr:04X}h")
|
||||||
|
|
||||||
|
# ==== SECTION 3: main() ====
|
||||||
|
if main_addr:
|
||||||
|
# Disassemble main() - generous range to cover init
|
||||||
|
out.append(disasm_range(data, main_addr, min(main_addr + 400, 0x2500),
|
||||||
|
f"SECTION 3: main() @ {main_addr:04X}h (first ~400 bytes)"))
|
||||||
|
|
||||||
|
# ==== SECTION 4: Vendor command dispatch ====
|
||||||
|
out.append("\n" + "=" * 80)
|
||||||
|
out.append(" SECTION 4: USB Vendor Command Dispatch")
|
||||||
|
out.append("=" * 80)
|
||||||
|
|
||||||
|
# Find all reads of SETUPDAT[1] (bRequest)
|
||||||
|
for pc in range(0x2500):
|
||||||
|
if pc + 2 < len(data) and data[pc] == 0x90 and data[pc+1] == 0xE6 and data[pc+2] == 0xA6:
|
||||||
|
out.append(f"\n SETUPDAT[1] read at {pc:04X}h:")
|
||||||
|
ctx_start = max(0, pc - 16)
|
||||||
|
ctx_end = min(pc + 120, 0x2500)
|
||||||
|
out.append(disasm_range(data, ctx_start, ctx_end,
|
||||||
|
f" Dispatch context @ {pc:04X}h"))
|
||||||
|
|
||||||
|
# ==== SECTION 5: Find all CJNE A,#xx comparisons (command dispatch) ====
|
||||||
|
out.append("\n" + "=" * 80)
|
||||||
|
out.append(" SECTION 5: All CJNE A,#xx Instructions (Command Dispatch Table)")
|
||||||
|
out.append("=" * 80)
|
||||||
|
|
||||||
|
for pc in range(0x2500):
|
||||||
|
if pc + 2 < len(data) and data[pc] == 0xB4:
|
||||||
|
imm = data[pc+1]
|
||||||
|
rel = data[pc+2]
|
||||||
|
if rel >= 0x80:
|
||||||
|
rel -= 256
|
||||||
|
target = (pc + 3 + rel) & 0xFFFF
|
||||||
|
out.append(f" {pc:04X}: CJNE A,#{imm:02X}h,{target:04X}h ; bRequest?=0x{imm:02X} skip->{target:04X}h")
|
||||||
|
|
||||||
|
# ==== SECTION 6: boot_8psk ====
|
||||||
|
out.append(disasm_range(data, 0x1D87, min(0x1D87 + 250, 0x2500),
|
||||||
|
"SECTION 6: boot_8psk() @ 0x1D87"))
|
||||||
|
|
||||||
|
# ==== SECTION 7: SFR access map ====
|
||||||
|
out.append("\n" + "=" * 80)
|
||||||
|
out.append(" SECTION 7: All XDATA SFR Accesses (DPTR-tracked)")
|
||||||
|
out.append("=" * 80)
|
||||||
|
|
||||||
|
accesses = track_dptr_accesses(data, 0x0000, 0x2500)
|
||||||
|
for pc, addr, name, direction in accesses:
|
||||||
|
name_str = name if name else f"XDATA_{addr:04X}h"
|
||||||
|
out.append(f" {pc:04X}: {direction:5s} [{addr:04X}h] {name_str}")
|
||||||
|
|
||||||
|
# ==== SECTION 8: Key functions ====
|
||||||
|
out.append(disasm_range(data, 0x0DDD, 0x0DDD + 120,
|
||||||
|
"SECTION 8: boot_init_blocks() @ 0x0DDD"))
|
||||||
|
|
||||||
|
out.append(disasm_range(data, 0x1A81, 0x1A81 + 150,
|
||||||
|
"SECTION 9: i2c_write() @ 0x1A81"))
|
||||||
|
|
||||||
|
out.append(disasm_range(data, 0x1556, 0x1556 + 150,
|
||||||
|
"SECTION 10: i2c_combined_read() @ 0x1556"))
|
||||||
|
|
||||||
|
out.append(disasm_range(data, 0x2000, 0x2000 + 120,
|
||||||
|
"SECTION 11: wait_for_ready() @ 0x2000"))
|
||||||
|
|
||||||
|
out.append(disasm_range(data, 0x099A, 0x099A + 200,
|
||||||
|
"SECTION 12: tune_function() @ 0x099A"))
|
||||||
|
|
||||||
|
# ==== SECTION 13: USB Descriptors ====
|
||||||
|
out.append("\n" + "=" * 80)
|
||||||
|
out.append(" SECTION 13: USB Descriptors @ 0x1200")
|
||||||
|
out.append("=" * 80)
|
||||||
|
|
||||||
|
desc = data[0x1200:0x1212]
|
||||||
|
out.append(f" Device Descriptor (18 bytes):")
|
||||||
|
out.append(f" bLength: {desc[0]} (0x{desc[0]:02X})")
|
||||||
|
out.append(f" bDescriptorType: {desc[1]} (DEVICE)")
|
||||||
|
out.append(f" bcdUSB: {desc[3]:02X}.{desc[2]:02X}")
|
||||||
|
out.append(f" bDeviceClass: 0x{desc[4]:02X} (vendor-specific)")
|
||||||
|
out.append(f" bDeviceSubClass: 0x{desc[5]:02X}")
|
||||||
|
out.append(f" bDeviceProtocol: 0x{desc[6]:02X}")
|
||||||
|
out.append(f" bMaxPacketSize0: {desc[7]} bytes")
|
||||||
|
vid = desc[8] | (desc[9] << 8)
|
||||||
|
pid = desc[10] | (desc[11] << 8)
|
||||||
|
out.append(f" idVendor: 0x{vid:04X} (Cypress)")
|
||||||
|
out.append(f" idProduct: 0x{pid:04X}")
|
||||||
|
bcd = desc[12] | (desc[13] << 8)
|
||||||
|
out.append(f" bcdDevice: {bcd:04X}")
|
||||||
|
out.append(f" iManufacturer: {desc[14]}")
|
||||||
|
out.append(f" iProduct: {desc[15]}")
|
||||||
|
out.append(f" iSerialNumber: {desc[16]}")
|
||||||
|
out.append(f" bNumConfigurations: {desc[17]}")
|
||||||
|
|
||||||
|
# Raw descriptor dump
|
||||||
|
out.append(f"\n Raw descriptor area 0x1200-0x1300:")
|
||||||
|
for off in range(0, 0x100, 16):
|
||||||
|
addr = 0x1200 + off
|
||||||
|
chunk = data[addr:addr+16]
|
||||||
|
hexl = " ".join(f"{b:02X}" for b in chunk)
|
||||||
|
asciil = "".join(chr(b) if 32 <= b < 127 else "." for b in chunk)
|
||||||
|
out.append(f" {addr:04X}: {hexl} {asciil}")
|
||||||
|
|
||||||
|
# ==== SECTION 14: Hex dump of interesting code regions ====
|
||||||
|
out.append("\n" + "=" * 80)
|
||||||
|
out.append(" SECTION 14: Raw Hex - Code Space Near USB ISR Targets")
|
||||||
|
out.append("=" * 80)
|
||||||
|
|
||||||
|
# Show hex around the USB interrupt vector dispatch
|
||||||
|
# The USB ISR at 0x0043 likely jumps somewhere
|
||||||
|
for start_addr in [0x0043, 0x004B]:
|
||||||
|
out.append(f"\n Hex dump at {start_addr:04X}h:")
|
||||||
|
for off in range(0, 48, 16):
|
||||||
|
addr = start_addr + off
|
||||||
|
chunk = data[addr:addr+16]
|
||||||
|
hexl = " ".join(f"{b:02X}" for b in chunk)
|
||||||
|
out.append(f" {addr:04X}: {hexl}")
|
||||||
|
|
||||||
|
print("\n".join(out))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
BIN
firmware-dump/stock_firmware.bin
Normal file
BIN
firmware-dump/stock_firmware.bin
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user