1049 lines
32 KiB
Markdown
1049 lines
32 KiB
Markdown
# Ameba-Z (RTL8710BX)
|
|
|
|
<!-- TOC -->
|
|
|
|
- [Ameba-Z RTL8710BX](#ameba-z-rtl8710bx)
|
|
- [Memory Map](#memory-map)
|
|
- [RAM Regions 256KB Total at 0x10000000](#ram-regions-256kb-total-at-0x10000000)
|
|
- [Flash Regions 1MB Total at 0x08000000](#flash-regions-1mb-total-at-0x08000000)
|
|
- [ROM Region](#rom-region)
|
|
- [Boot Process](#boot-process)
|
|
- [Reset_Handler](#reset_handler)
|
|
- [HalResetVsr](#halresetvsr)
|
|
- [HW states](#hw-states)
|
|
- [Setup](#setup)
|
|
- [Boot source select](#boot-source-select)
|
|
- [Early Boot Paths pre-populated SRAM](#early-boot-paths-pre-populated-sram)
|
|
- [Normal Boot Path](#normal-boot-path)
|
|
- [BOOT_ROM_Process](#boot_rom_process)
|
|
- [BOOT_ROM_FromFlash](#boot_rom_fromflash)
|
|
- [XIP bootloader flash layout](#xip-bootloader-flash-layout)
|
|
- [Image Location Calculation](#image-location-calculation)
|
|
- [Image Header @ 0x8000C88](#image-header--0x8000c88)
|
|
- [Function](#function)
|
|
- [TL;DR](#tldr)
|
|
- [XIPBOOT structure](#xipboot-structure)
|
|
- [Decompiled function](#decompiled-function)
|
|
- [FLASH_LoadAndBootImage](#flash_loadandbootimage)
|
|
- [control flow graph](#control-flow-graph)
|
|
- [check flash encryption in eFuse](#check-flash-encryption-in-efuse)
|
|
- [set up pointers to BD_RAM in BOOTLOADER_RAM](#set-up-pointers-to-bd_ram-in-bootloader_ram)
|
|
- [flash configuration](#flash-configuration)
|
|
- [OTA slot selection](#ota-slot-selection)
|
|
- [OTA_SelectBootImage decompiled](#ota_selectbootimage-decompiled)
|
|
- [check if OTA1 is forced by GPIO:](#check-if-ota1-is-forced-by-gpio)
|
|
- [final image selection and address set](#final-image-selection-and-address-set)
|
|
- [OTA1-TEXT header](#ota1-text-header)
|
|
- [OTA1-DATA](#ota1-data)
|
|
- [jump](#jump)
|
|
- [Decompiled function](#decompiled-function)
|
|
|
|
<!-- /TOC -->
|
|
|
|
## Memory Map
|
|
|
|
### RAM Regions (256KB Total at 0x10000000)
|
|
|
|
| Region | Start Address | End Address | Size | Flags | Description |
|
|
| --------- | ------------- | ----------- | ------- | ----- | ------------------ |
|
|
| ROM BSS | 0x10000000 | 0x10001FFF | 8KB | RW | ROM BSS Storage |
|
|
| IMG1 RAM | 0x10002000 | 0x10004FFF | 12KB | RWX | Boot Loader Code |
|
|
| IMG2 RAM | 0x10005000 | 0x1003DFFF | 172KB | RWX | Main Program RAM |
|
|
| MSP Stack | 0x1003E000 | 0x1003EFFF | 4KB | WX | Main Stack Pointer |
|
|
| RDP | 0x1003F000 | 0x1003FFEF | 4KB-16B | WX | RDP Operations |
|
|
|
|
### Flash Regions (1MB Total at 0x08000000)
|
|
|
|
| Region | Start Address | End Address | Size | Flags | Description |
|
|
| ----------- | ------------- | ----------- | ----- | ----- | --------------- |
|
|
| Boot | 0x08000020 | 0x08003FFF | ~16KB | RX | Boot Code |
|
|
| System Data | 0x08009000 | 0x08009FFF | 4KB | R | System Data |
|
|
| Reserved | 0x0800A000 | 0x0800AFFF | 4KB | R | Reserved Region |
|
|
| OTA1 | 0x0800B020 | 0x0807FFFF | 468KB | RX | OTA Region 1 |
|
|
| OTA2 | 0x08080020 | 0x080F4FFF | 468KB | RX | OTA Region 2 |
|
|
|
|
### ROM Region
|
|
|
|
| Region | Start Address | End Address | Size | Flags | Description |
|
|
| ------ | ------------- | ----------- | ----- | ----- | ----------- |
|
|
| ROM | 0x00000000 | 0x0007FFFF | 512KB | RX | Main ROM |
|
|
|
|
Notes:
|
|
|
|
- RDP region reserves 16 bytes at the end (0x1003FFF0-0x1003FFFF)
|
|
- Flash regions marked with 0x20 offset indicate header reservation
|
|
- Image2 RAM combines DATA, BSS, and HEAP sections
|
|
|
|

|
|
|
|
## Boot Process
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant CPU as CPU
|
|
participant ROM as ROM
|
|
participant FLASH as Flash
|
|
participant RAM as RAM
|
|
participant eFuse as eFuse
|
|
|
|
Note over CPU,RAM: Reset Sequence
|
|
CPU->>CPU: Reset_Handler
|
|
CPU->>CPU: Enable FPU
|
|
CPU->>CPU: Set Stack @ 0x1003EFFC
|
|
CPU->>ROM: Jump to HalResetVsr
|
|
|
|
Note over ROM,RAM: Early Boot Check
|
|
ROM->>ROM: Check SOC_FUNC_EN Register
|
|
alt Boot from JTAG
|
|
ROM->>RAM: IMG2_JTAGPreloadBoot
|
|
else Unlock from JTAG
|
|
ROM->>RAM: IMG2_QuickValidateAndBoot
|
|
else Direct Patch
|
|
ROM->>RAM: IMG2_DirectPatchBoot
|
|
else Normal Boot
|
|
ROM->>ROM: Continue Boot Process
|
|
end
|
|
|
|
Note over ROM,RAM: Normal Boot Init
|
|
ROM->>RAM: Clear Vector Table
|
|
ROM->>ROM: CPU_ClkSet
|
|
ROM->>ROM: Vector_TableInit
|
|
ROM->>ROM: OSC32K_Cmd
|
|
ROM->>ROM: Init UART/Debug
|
|
|
|
Note over ROM,eFuse: Boot Source Selection
|
|
ROM->>eFuse: Read Boot Type
|
|
|
|
alt Boot from Flash
|
|
ROM->>ROM: BOOT_ROM_FromFlash
|
|
ROM->>eFuse: Check Flash Encryption
|
|
|
|
Note over ROM,FLASH: Load XIP Bootloader
|
|
ROM->>FLASH: Read IMG1_HEADER @ 0x8000C88
|
|
ROM->>RAM: Copy to 0x10002000 (12KB)
|
|
ROM->>RAM: Verify Signature
|
|
ROM->>RAM: Jump to FLASH_LoadAndBootImage
|
|
|
|
Note over RAM,FLASH: Image Selection
|
|
RAM->>FLASH: Configure Flash Speed/Mode
|
|
RAM->>RAM: IMG2_SetupAddresses
|
|
RAM->>FLASH: OTA_SelectBootImage
|
|
|
|
alt Valid OTA2 & No GPIO Force
|
|
RAM->>FLASH: Load OTA2
|
|
else Otherwise
|
|
RAM->>FLASH: Load OTA1
|
|
end
|
|
|
|
RAM->>RAM: Decrypt if needed
|
|
RAM->>RAM: Jump to Entry @ 0x10005000
|
|
else Other Boot Sources
|
|
ROM->>ROM: Handle SDIO/USB/UART/SPI boot
|
|
end
|
|
|
|
Note over RAM: Final Execution
|
|
RAM->>RAM: Start Application
|
|
```
|
|
|
|
### `Reset_Handler`
|
|
|
|
```nasm
|
|
Reset_Handler:
|
|
LDR.W R0, =0xE000ED88 ; Enable FPU (CP10/11)
|
|
LDR R1, [R0]
|
|
ORR.W R1, R1, #0xF00000
|
|
STR R1, [R0]
|
|
LDR.W SP, =0x1003EFFC ; Set stack pointer
|
|
B.W HalResetVsr ; Jump to init
|
|
```
|
|
|
|
### `HalResetVsr`
|
|
|
|
#### HW states
|
|
|
|
```c
|
|
// REG_SOC_FUNC_EN @ 0x40000210
|
|
#define BIT_SOC_BOOT_FROM_JTAG (1 << 31)
|
|
#define BIT_SOC_UNLOCK_FROM_JTAG (1 << 30)
|
|
#define BIT_SOC_WAKE_FROM_PS (1 << 29)
|
|
#define BIT_SOC_PATCH_FUNC0 (1 << 28)
|
|
#define BIT_SOC_PATCH_FUNC1 (1 << 27)
|
|
#define BIT_SOC_PATCH_FUNC2 (1 << 26)
|
|
|
|
// REG_SYS_EFUSE_SYSCFG6 @ 0x40000038
|
|
#define BIT_MASK_SYS_BOOT_TYPE_SEL (3 << 28)
|
|
#define BIT_SYS_ICFG2_TEST (1 << 31)
|
|
|
|
// Debug configuration
|
|
#define BIT_DBG_MSG_EN (1 << 30) // ConfigDebugErr bit 30
|
|
```
|
|
|
|
#### 1. Setup
|
|
|
|
```nasm
|
|
PUSH {R4-R6,LR}
|
|
LDR R3, =__rom_bss_end__
|
|
LDR R5, =NewVectorTable
|
|
LDR R2, =(SYSCFG2_ROMINFO_Set+1)
|
|
LDR R4, =off_10002000 ; SRAM boot table ptr (may be empty)
|
|
SUB SP, SP, #8
|
|
SUBS R5, R3, R5 ; Calculate vector table size
|
|
```
|
|
|
|
#### 2. Boot source select
|
|
|
|
```c
|
|
// hw init first
|
|
SYSCFG2_ROMINFO_Set();
|
|
RCC_PeriphClockCmd(0, 0x40u, 1);
|
|
|
|
// early boot paths - SRAM must be pre-populated
|
|
if (REG_SOC_FUNC_EN & BIT_SOC_BOOT_FROM_JTAG) { // bit 31
|
|
IMG2_JTAGPreloadBoot(); // off_10002004
|
|
} else if (REG_SOC_FUNC_EN & BIT_SOC_UNLOCK_FROM_JTAG) { // bit 30
|
|
IMG2_QuickValidateAndBoot(); // off_10002000
|
|
} else if (REG_SOC_FUNC_EN & BIT_SOC_PATCH_FUNC0) { // bit 28
|
|
IMG2_DirectPatchBoot(); // off_1000200C
|
|
} else {
|
|
// normal boot path - init everything
|
|
|
|
// debug flags setup
|
|
ConfigDebugWarn = 0;
|
|
ConfigDebugErr = 0xFFFFFFFF;
|
|
ConfigDebugInfo = 0;
|
|
if (REG_SYS_EFUSE_SYSCFG6 & BIT_SYS_ICFG2_TEST) {
|
|
ConfigDebugErr = 0;
|
|
}
|
|
|
|
// clear vector table
|
|
memset(&NewVectorTable, 0, &_rom_bss_end__ - (_UNKNOWN *)&NewVectorTable);
|
|
|
|
// core system initialization
|
|
CPU_ClkSet(0);
|
|
VECTOR_TableInit(&unk_1003EFFC);
|
|
OSC32K_Cmd(1);
|
|
xtal_freq = XTAL_ClkGet();
|
|
NCO32K_Init(0x8000, xtal_freq, 9, 2);
|
|
DIAG_UartInit(1);
|
|
BOOT_ROM_ShowBuildInfo(0);
|
|
SYSTIMER_Init();
|
|
BOOT_ROM_InitFlash();
|
|
BOOT_ROM_Simulation();
|
|
|
|
if (REG_SOC_FUNC_EN & BIT_SOC_PATCH_FUNC1) { // bit 27
|
|
IMG2_QuickValidateAndBoot(); // off_10002010
|
|
}
|
|
|
|
// UART/Debug console handling
|
|
if (SYSCFG1_TRP_ICFG() == 2) {
|
|
if (SYSCFG1_AutoLoadDone() && (REG_SYS_EFUSE_SYSCFG6 & 0x80000000)) {
|
|
if (ConfigDebugErr & 0x40000000) {
|
|
DiagPrintf("\rROM BOOT: DEBUG mode \n");
|
|
}
|
|
RtlConsolRom(100000);
|
|
}
|
|
} else {
|
|
RtlConsolRom(20);
|
|
}
|
|
|
|
// check for UART image download
|
|
if (SYSCFG1_TRP_UARTImage() || (BKUP_Read() & 8)) {
|
|
BKUP_Clear(0, 8);
|
|
UARTIMG_Download(2);
|
|
|
|
if (!(REG_SOC_FUNC_EN & BIT_SOC_PATCH_FUNC2)) { // bit 26
|
|
goto NORMAL_BOOT;
|
|
}
|
|
} else if (!(REG_SOC_FUNC_EN & BIT_SOC_PATCH_FUNC2)) {
|
|
goto NORMAL_BOOT;
|
|
}
|
|
|
|
IMG2_QuickValidateAndBoot(); // off_10002008
|
|
|
|
NORMAL_BOOT:
|
|
// turn off SWD based on eFuse
|
|
uint8_t efuse_val;
|
|
EFUSE_OneByteReadROM(REG_SYS_EFUSE_SYSCFG6, 211, &efuse_val, 7);
|
|
if (!(efuse_val & 1)) {
|
|
PINMUX_SWD_OFF();
|
|
}
|
|
|
|
// enable mac and init USB
|
|
REG_SOC_HCI_COM_FUNC_EN |= BIT_SOC_HCI_WL_MACON_EN;
|
|
BOOT_ROM_InitUsb();
|
|
|
|
// process normal boot
|
|
BOOT_ROM_Process();
|
|
}
|
|
|
|
while (1);
|
|
```
|
|
|
|
#### Early Boot Paths (pre-populated SRAM)
|
|
|
|
1. **JTAG Boot** (BIT_SOC_BOOT_FROM_JTAG)
|
|
- boot via `IMG2_JTAGPreloadBoot` @ `0x10002004`
|
|
2. **JTAG Unlock** (BIT_SOC_UNLOCK_FROM_JTAG)
|
|
- boot via `IMG2_QuickValidateAndBoot` @ `0x10002000`
|
|
3. **Direct Patch** (BIT_SOC_PATCH_FUNC0)
|
|
- boot via `IMG2_DirectPatchBoot` @ `0x1000200C`
|
|
|
|
#### Normal Boot Path
|
|
|
|
- clear vector table
|
|
- **Patch Check** (`BIT_SOC_PATCH_FUNC1`): if set, boot via `IMG2_QuickValidateAndBoot` @ `0x10002000`
|
|
- **UART Image Handler**
|
|
- check for UART image or backup flag
|
|
- if found:
|
|
- clear backup flag
|
|
- download UART image
|
|
- if `BIT_SOC_PATCH_FUNC2` set: boot via`IMG2_QuickValidateAndBoot` @ `0x10002000`
|
|
- otherwise: goto normal boot
|
|
- disable SWD based if set in eFuse
|
|
- enable MAC
|
|
- init USB
|
|
- `BL BOOT_ROM_Process`
|
|
|
|
### `BOOT_ROM_Process`
|
|
|
|
Boot types:
|
|
|
|
```c
|
|
enum _BOOT_TYPE_ {
|
|
BOOT_FROM_FLASH = 0,
|
|
BOOT_FROM_SDIO = 1,
|
|
BOOT_FROM_USB = 2,
|
|
BOOT_FROM_UART0 = 3,
|
|
BOOT_FROM_UART1 = 4,
|
|
BOOT_FROM_SPI = 5,
|
|
BOOT_FROM_RSVD = 6,
|
|
};
|
|
```
|
|
|
|
```c
|
|
int __fastcall BOOT_ROM_Process(uint8_t *config, int param) {
|
|
// setup registers and read eFuse
|
|
uint32_t *efuse = (uint32_t *)0x40000038; // REG_SYS_EFUSE_SYSCFG6
|
|
uint32_t debug = ConfigDebugErr;
|
|
_BOOT_TYPE_ boot_type = (*efuse >> 28) & 7; // BIT_MASK_SYS_BOOT_TYPE_SEL
|
|
|
|
// If debug not enabled, direct jump to handler
|
|
if (!(debug & 0x80000000)) { // BMI check in asm
|
|
// TBB jump table implementation
|
|
switch (boot_type) {
|
|
case 0:
|
|
return BOOT_ROM_FromFlash((int)config, param);
|
|
case 1:
|
|
return SDIO_Boot_Up(config, param, debug << 1);
|
|
case 2:
|
|
return USB_Boot_ROM(config, param, debug << 1);
|
|
case 3:
|
|
return UARTIMG_Download(0, param, debug << 1);
|
|
case 4:
|
|
return UARTIMG_Download(1, param, debug << 1);
|
|
case 5:
|
|
return SPI_Boot_ROM(config, param, debug << 1);
|
|
default:
|
|
return RtlConsolRom(0x186A0, param, debug << 1);
|
|
}
|
|
}
|
|
|
|
// Debug path - print info first
|
|
int xtal = XTAL_ClkGet(config, param, debug << 1);
|
|
DiagPrintf("\rBOOT TYPE:%x XTAL:%d\n", boot_type, xtal);
|
|
|
|
switch (boot_type) {
|
|
case 0:
|
|
return BOOT_ROM_FromFlash((int)config, param);
|
|
case 1:
|
|
return SDIO_Boot_Up(config, param, debug << 1);
|
|
case 2:
|
|
return USB_Boot_ROM(config, param, debug << 1);
|
|
case 3:
|
|
return UARTIMG_Download(0, param, debug << 1);
|
|
case 4:
|
|
return UARTIMG_Download(1, param, debug << 1);
|
|
case 5:
|
|
return SPI_Boot_ROM(config, param, debug << 1);
|
|
default:
|
|
DiagPrintf("\rNo support boot type !!!!!!!\n");
|
|
return RtlConsolRom(0x186A0, param, debug << 1);
|
|
}
|
|
}
|
|
```
|
|
|
|
### `BOOT_ROM_FromFlash`
|
|
|
|
Loads the XIP bootloader RAM section from flash into SRAM
|
|
|
|
#### XIP bootloader flash layout
|
|
|
|
```
|
|
0x8000000 - Flash Base
|
|
0x8000008: IMG1_HEADER_OFFSET = 0xC68
|
|
```
|
|
|
|
#### Image Location Calculation
|
|
|
|
```
|
|
1. Base (0x8000000) + IMG1_HEADER_OFFSET (0xC68) = 0x8000C68
|
|
2. 0x8000C68 + 0x20 = 0x8000C88 (Final image location)
|
|
```
|
|
|
|
#### Image Header (@ 0x8000C88)
|
|
|
|
```
|
|
0x8000C88-0x8000C8F: Signature (99 99 96 96 3F CC 66 FC)
|
|
0x8000C90: Size (0x468)
|
|
0x8000C94: Destination (0x10002000)
|
|
```
|
|
|
|
#### Function
|
|
|
|
1. check encryption in eFuse
|
|
|
|
```nasm
|
|
LDR R3, =0x40000038 ; Check flash encryption status
|
|
LDR R3, [R3]
|
|
LSLS R4, R3, #0x12 ; Test bit 13
|
|
BMI init_flash_encryption
|
|
```
|
|
|
|
2. calc image location
|
|
|
|
```nasm
|
|
MOV.W R3, #0x8000000 ; Load flash base
|
|
LDR R5, [R3,#(IMG1_HEADER_OFFSET - 0x8000000)] ; Get 0xC68
|
|
ADD.W R5, R5, #0x8000000 ; R5 = 0x8000C68
|
|
ADDS R5, #0x20 ; R5 = 0x8000C88 (Image start)
|
|
```
|
|
|
|
3. load image params
|
|
|
|
```nasm
|
|
LDR.W R8, [R5,#(dword_8000C90 - 0x8000C88)] ; Load size (0x468)
|
|
LDR R7, [R5,#(dword_8000C94 - 0x8000C88)] ; Load dest (0x10002000)
|
|
```
|
|
|
|
4. `memcpy`
|
|
|
|
```c
|
|
memcpy(destination_addr, source_addr + 32, size);
|
|
// Copies from 0x8000C88 to 0x10002000
|
|
```
|
|
|
|
5. entrypoint selection
|
|
|
|
```nasm
|
|
LDR R3, [R4,#(off_10002014 - 0x10002000)] ; load FLASH_LoadAndBootImage
|
|
LDR R2, =off_10002000 ; base address for fallback
|
|
CBNZ R3, loc_5476 ; if primary entry valid, go to exit
|
|
LDR R3, [R2] ; load IMG2_QuickValidateAndBoot
|
|
loc_5476: ; exit sequence
|
|
POP.W {R4-R8,LR}
|
|
BX R3 ; jump to entry point
|
|
|
|
```
|
|
|
|
#### TL;DR
|
|
|
|
1. check flash encryption
|
|
2. calculate image location:
|
|
- start @ `0x8000000`
|
|
- add `IMG1_HEADER_OFFSET` (`0xC68` in factory boot_all.bin)
|
|
- add `0x20` to reach image
|
|
3. read image metadata from `0x8000C88`
|
|
4. copy `0x468` bytes to RAM @ `0x10002000`
|
|
5. validate copied data
|
|
6. jump to entry point (should be `FLASH_LoadAndBootImage`)
|
|
|
|
#### XIPBOOT structure
|
|
|
|
```
|
|
Flash:
|
|
0x8000000 [Base]
|
|
└── +0xC68 [IMG1_HEADER_OFFSET]
|
|
└── +0x20 [Header Skip]
|
|
└── 0x8000C88 [Image Start]
|
|
├── +0x00: Signature
|
|
├── +0x08: Size (0x468)
|
|
└── +0x0C: Destination (0x10002000)
|
|
```
|
|
|
|
#### Decompiled function
|
|
|
|
```c
|
|
__int64 __fastcall BOOT_ROM_FromFlash(int a1, int a2) {
|
|
int img_source; // r5
|
|
int img_size; // r8
|
|
int img_dest; // r7
|
|
_BYTE *v5; // r0
|
|
int v6; // r1
|
|
char *pattern_dest; // r3
|
|
void *pattern_src; // r2
|
|
_BYTE *v9; // t1
|
|
int pattern_byte; // t1
|
|
__int64(__fastcall * v11)(int, int, int, int); // r3
|
|
int v13; // r2
|
|
int v14; // r3
|
|
|
|
if ((MEMORY[0x40000038] & 0x2000) != 0) init_flash_encryption();
|
|
img_source = IMG1_HEADER_OFFSET + 0x8000020;
|
|
img_size = *(int *)((char *)&dword_8 + IMG1_HEADER_OFFSET + 0x8000020);
|
|
img_dest = *(int *)((char *)&dword_C + IMG1_HEADER_OFFSET + 0x8000020);
|
|
if ((ConfigDebugErr & 0x40000000) != 0)
|
|
DiagPrintf("\rIMG1 DATA[%d:%x]\n",
|
|
*(int *)((char *)&dword_8 + IMG1_HEADER_OFFSET + 0x8000020),
|
|
*(int *)((char *)&dword_C + IMG1_HEADER_OFFSET + 0x8000020));
|
|
if (img_size == -1 || (a2 = img_dest + 1, img_dest == -1)) {
|
|
while (1) {
|
|
v13 = 2 * ConfigDebugErr;
|
|
if ((ConfigDebugErr & 0x40000000) != 0)
|
|
DiagPrintf("\rFlash not Program \n", a2, v13);
|
|
RtlConsolRom(100000, a2, v13);
|
|
}
|
|
}
|
|
v5 = (_BYTE *)memcpy(img_dest, img_source + 32, img_size);
|
|
pattern_dest = &byte_10002018;
|
|
pattern_src = (void *)(unsigned __int8)byte_10002018;
|
|
if (byte_10002018 != 35) {
|
|
validation_failed:
|
|
v14 = ConfigDebugErr & 0x40000000;
|
|
if ((ConfigDebugErr & 0x40000000) != 0) {
|
|
DiagPrintf("\rImage1 Validation Incorrect !!!\n");
|
|
v14 = ConfigDebugErr & 0x40000000;
|
|
}
|
|
if (v14) goto LABEL_24;
|
|
while (1) {
|
|
do RtlConsolRom(100000, v6, pattern_src);
|
|
while ((ConfigDebugErr & 0x40000000) == 0);
|
|
LABEL_24:
|
|
DiagPrintf(
|
|
"\rPlease Re-boot and try again, or re-burn the flash image\n");
|
|
}
|
|
}
|
|
pattern_src = &ROM_IMG1_VALID_PATTEN;
|
|
while (1) {
|
|
pattern_byte = *((unsigned __int8 *)pattern_src + 1);
|
|
pattern_src = (char *)pattern_src + 1;
|
|
v6 = pattern_byte;
|
|
if (pattern_byte == 255) break;
|
|
v9 = (_BYTE *)(unsigned __int8)*++pattern_dest;
|
|
v5 = v9;
|
|
if (v9 != (_BYTE *)v6) goto validation_failed;
|
|
}
|
|
if ((ConfigDebugErr & 0x40000000) != 0)
|
|
v5 = DiagPrintf("\rIMG1 ENTRY[%x:%x]\n", off_10002014, off_10002000);
|
|
v11 = (__int64(__fastcall *)(int, int, int, int))off_10002014;
|
|
if (!off_10002014)
|
|
v11 = (__int64(__fastcall *)(int, int, int, int))off_10002000;
|
|
return v11((int)v5, v6, (int)&off_10002000, (int)v11);
|
|
}
|
|
```
|
|
|
|
### `FLASH_LoadAndBootImage`
|
|
|
|
#### control flow graph
|
|
|
|
```mermaid
|
|
graph TD
|
|
A[Start] --> B[GPIO Setup]
|
|
B --> C[Security Config]
|
|
C --> D[Memory Setup]
|
|
D --> E[Flash Init]
|
|
E --> F[OTA Select]
|
|
F --> G{Valid OTA2?}
|
|
G -->|Yes| H[Load OTA2]
|
|
G -->|No| I[Load OTA1]
|
|
H --> J[Verify Image]
|
|
I --> J
|
|
J -->|Valid| K[Boot Image]
|
|
J -->|Invalid| L[Error Loop]
|
|
```
|
|
|
|
inits gpio:
|
|
|
|
```nasm
|
|
; Configure SPI Flash pins
|
|
MOVS R1, #2 ; Pull control value
|
|
MOVS R0, #6 ; Pin 6
|
|
BL j_PAD_PullCtrl ; Set pin pull control
|
|
MOVS R1, #2
|
|
MOVS R0, #7 ; Pin 7
|
|
BL j_PAD_PullCtrl
|
|
; ... repeats for pins 8,9,10,11,29,30
|
|
```
|
|
|
|
#### check flash encryption in eFuse
|
|
|
|
```c
|
|
if ((*efuseReg & 0x2000) != 0) {
|
|
j_OTF_Mask(0, 0x8000, 2, 1);
|
|
set_flash_crypt_masks();
|
|
}
|
|
```
|
|
|
|
#### set up pointers to `BD_RAM` in `BOOTLOADER_RAM`
|
|
|
|
done by `IMG2_SetupAddresses()`
|
|
|
|
```nasm
|
|
; setup addresses for IMG2
|
|
LDR R0, =IMG2_VECTOR_TABLE
|
|
LDR R1, =0x10005000 ; Vector table address
|
|
STR R1, [R0] ; Store vector table addr
|
|
ADDS R1, #8 ; Entry point = vector table + 8
|
|
STR R1, [R0,#IMG2_ENTRY]
|
|
```
|
|
|
|
->
|
|
|
|
```c
|
|
// struct for ota load @ 0x10002430
|
|
typedef struct {
|
|
uint32_t *vector_table; // where IMG2 will be loaded in RAM
|
|
uint32_t *signature_loc; // points to IMG2 signature location (+8 offset)
|
|
uint32_t *ram_start; // working RAM area start
|
|
uint32_t *ram_end; // working RAM area end
|
|
// a lot of other stuff
|
|
} IMG2_Config;
|
|
|
|
void IMG2_SetupAddresses(void) {
|
|
IMG2_Config *config = (IMG2_Config *)0x10002430;
|
|
|
|
config->vector_table = (uint32_t *)0x10005000; // Base RAM address
|
|
config->signature_loc =
|
|
(uint32_t *)(0x10005000 + 8); // where signature check will be
|
|
config->ram_start = (uint32_t *)0x10002464; // Working RAM space
|
|
config->ram_end = (uint32_t *)0x10002464; // Working RAM end
|
|
|
|
// after fw is copied to RAM, we'd expect to find at 0x10005000:
|
|
// - entry_point (sub_8084DDA + 1) // +1 - Thumb
|
|
// - 0x803A95D // at 0x04 unrelated but some magic
|
|
// number? or address?
|
|
// - "RTKWin\0" // signature string
|
|
}
|
|
```
|
|
|
|
#### flash configuration
|
|
|
|
```c
|
|
// Configure flash speed and mode
|
|
BYTE4(v33[0]) = count_leading_zeros(XIP_SYSTEM_DATA.Spic_Mode);
|
|
v14 = count_leading_zeros(XIP_SYSTEM_DATA.Spic_Speed);
|
|
if (v14 >= 7) v14 = 6; // Cap max speed
|
|
|
|
FLASH_ConfigureClock((char *)v33 + 4);
|
|
FLASH_DetectAndInitType(BYTE4(v33[0]));
|
|
j_FLASH_ClockDiv(v14);
|
|
```
|
|
|
|
#### OTA slot selection
|
|
|
|
```mermaid
|
|
graph TD
|
|
A[Start OTA Selection] --> B{Check Valid_Image2 bitmap}
|
|
B -->|OTA2 bit set| C{Check OTA2_FlashAddr}
|
|
B -->|OTA1 bit set| D[Select OTA1]
|
|
C -->|Valid Address| E{Verify OTA2 Signature}
|
|
C -->|Invalid/-1| D
|
|
E -->|Valid| F{Check Force GPIO}
|
|
E -->|Invalid| D
|
|
F -->|GPIO Set| G{Check GPIO State}
|
|
F -->|No GPIO/255| H[Select OTA2]
|
|
G -->|Force Active| D
|
|
G -->|Not Forced| H
|
|
```
|
|
|
|
```c
|
|
OTA2_FlashAddr =
|
|
(_DWORD *)XIP_SYSTEM_DATA.OTA2_FlashAddr; // address of OTA2 in flash
|
|
otaSelect = XIP_SYSTEM_DATA.Valid_Image2; // bitmap of valid images
|
|
selectedSlot = 0;
|
|
|
|
// Find first valid slot
|
|
do {
|
|
if ((otaSelect >> selectedSlot) & 1) break;
|
|
++selectedSlot;
|
|
} while (selectedSlot < 0x20);
|
|
```
|
|
|
|
then verify signature:
|
|
|
|
```c
|
|
v9 = *(_DWORD *)OTA_SIGNATURE_1;
|
|
v10 = *OTA2_FlashAddr == *(_DWORD *)OTA_SIGNATURE_1;
|
|
if (*OTA2_FlashAddr == *(_DWORD *)OTA_SIGNATURE_1) {
|
|
v9 = OTA_SIGNATURE_2;
|
|
v10 = *(int *)((char *)&dword_4 + (_DWORD)OTA2_FlashAddr) == OTA_SIGNATURE_2;
|
|
}
|
|
```
|
|
|
|
#### `OTA_SelectBootImage` decompiled
|
|
|
|
```c
|
|
void __fastcall OTA_SelectBootImage(_DWORD *destAddr) {
|
|
int *v1; // r7
|
|
_DWORD *OTA2_FlashAddr; // r5
|
|
unsigned int otaSelect; // r10
|
|
unsigned int selectedSlot; // r4
|
|
int v6; // r0
|
|
unsigned int v7; // r1
|
|
int v8; // r0
|
|
int v9; // r1
|
|
bool v10; // zf
|
|
int v11; // r4
|
|
int v12; // r9
|
|
int v13; // r1
|
|
int v14; // [sp+0h] [bp-38h] BYREF
|
|
int v15; // [sp+4h] [bp-34h]
|
|
int v16; // [sp+14h] [bp-24h]
|
|
|
|
v1 = ConfigDebugErr_ptr;
|
|
OTA2_FlashAddr = (_DWORD *)XIP_SYSTEM_DATA.OTA2_FlashAddr;
|
|
otaSelect = XIP_SYSTEM_DATA.Valid_Image2;
|
|
selectedSlot = 0;
|
|
if ((*ConfigDebugErr_ptr & 0x40000000) != 0) {
|
|
j_DiagPrintf("\rOTA2 ADDR[%x]\n", XIP_SYSTEM_DATA.OTA2_FlashAddr);
|
|
if ((*v1 & 0x40000000) != 0) j_DiagPrintf("\rOTAx SELE[%x]\n", otaSelect);
|
|
}
|
|
do {
|
|
if (((otaSelect >> selectedSlot) & 1) != 0) break;
|
|
++selectedSlot;
|
|
} while (selectedSlot < 0x20);
|
|
v6 = *v1;
|
|
v7 = selectedSlot << 31;
|
|
if ((selectedSlot & 1) != 0) {
|
|
if ((v6 & 0x40000000) != 0) j_DiagPrintf(aOta2Use, v7);
|
|
v8 = *v1;
|
|
if (OTA2_FlashAddr == (_DWORD *)-1) {
|
|
if ((v8 & 0x40000000) != 0) j_DiagPrintf(aOta2NotInFlash, v7);
|
|
} else {
|
|
if ((v8 & 0x40000000) != 0)
|
|
j_DiagPrintf("\rOTA2 SIGN[%x:%x]\n", *OTA2_FlashAddr,
|
|
*(int *)((char *)&dword_4 + (_DWORD)OTA2_FlashAddr));
|
|
v9 = *(_DWORD *)OTA_SIGNATURE_1;
|
|
v10 = *OTA2_FlashAddr == *(_DWORD *)OTA_SIGNATURE_1;
|
|
if (*OTA2_FlashAddr == *(_DWORD *)OTA_SIGNATURE_1) {
|
|
v9 = OTA_SIGNATURE_2;
|
|
v10 = *(int *)((char *)&dword_4 + (_DWORD)OTA2_FlashAddr) ==
|
|
OTA_SIGNATURE_2;
|
|
}
|
|
if (v10) {
|
|
v11 = 0;
|
|
if (LOBYTE(XIP_SYSTEM_DATA.OTA1_ForceGpio) == 255) goto LABEL_31;
|
|
v16 = XIP_SYSTEM_DATA.OTA1_ForceGpio & 0x3F;
|
|
v14 = 0;
|
|
if ((XIP_SYSTEM_DATA.OTA1_ForceGpio & 0x80) != 0) {
|
|
v15 = 1;
|
|
v12 = 1;
|
|
} else {
|
|
v15 = 2;
|
|
v12 = 0;
|
|
}
|
|
j_GPIO_Init(&v14);
|
|
if (j_GPIO_ReadDataBit(v16) == v12) v11 = 1;
|
|
j_GPIO_DeInit(v16);
|
|
if (!v11) {
|
|
LABEL_31:
|
|
*destAddr = (char *)OTA2_FlashAddr +
|
|
*(int *)((char *)&dword_8 + (_DWORD)OTA2_FlashAddr) + 32;
|
|
} else if ((*v1 & 0x40000000) != 0) {
|
|
j_DiagPrintf("\rGPIO force OTA1 \n", v13);
|
|
}
|
|
} else if ((*v1 & 0x40000000) != 0) {
|
|
j_DiagPrintf(aOta2SignatureW, v9);
|
|
}
|
|
}
|
|
} else if ((v6 & 0x40000000) != 0) {
|
|
j_DiagPrintf(aOta1Use, v7);
|
|
}
|
|
JUMPOUT(0x8000236);
|
|
}
|
|
```
|
|
|
|
#### check if OTA1 is forced by GPIO:
|
|
|
|
```c
|
|
if (LOBYTE(XIP_SYSTEM_DATA.OTA1_ForceGpio) != 255) { // if GPIO force enabled
|
|
gpioPin = XIP_SYSTEM_DATA.OTA1_ForceGpio & 0x3F; // get pin number
|
|
activeLevel = (XIP_SYSTEM_DATA.OTA1_ForceGpio & 0x80) ? 1 : 0;
|
|
|
|
GPIO_Init(&gpioConfig);
|
|
if (GPIO_ReadDataBit(gpioPin) == activeLevel) {
|
|
forceOTA1 = 1; // force boot OTA1
|
|
}
|
|
GPIO_DeInit(gpioPin);
|
|
}
|
|
```
|
|
|
|
#### final image selection and address set
|
|
|
|
```c
|
|
if (!forceOTA1 && validOTA2) {
|
|
// calculate source address for OTA2 header
|
|
*headerAddr = (char *)OTA2_FlashAddr +
|
|
*(int *)((char *)&dword_8 + (_DWORD)OTA2_FlashAddr);
|
|
} else {
|
|
// calculate source address for OTA1 header
|
|
*headerAddr = (char *)&IMG2_OTA1_SIGNATURE_STR +
|
|
*(int *)((char *)&IMG2_OTA1_SIGNATURE_STR - 24);
|
|
}
|
|
|
|
// read from header:
|
|
destAddr = *(int *)(*headerAddr + 12); // destination from header+12
|
|
size = *(int *)(*headerAddr + 8); // size from header+8
|
|
|
|
// validation (1MB)
|
|
if (destAddr == -1 || size > 0x100000) {
|
|
// ...
|
|
return;
|
|
}
|
|
|
|
// copy image data that follows the header
|
|
memcpy(destAddr, // destination from header
|
|
*headerAddr + 32, // source = header + 32 byte header size
|
|
size); // size from header
|
|
```
|
|
|
|
Example:
|
|
|
|
1. Start at `IMG2_OTA1_SIGNATURE_STR` (`0x0800B020`)
|
|
2. Get offset at -24 bytes (`0x0800B008`): `0x823AC`
|
|
|
|
3. Calculate header location:
|
|
`0x0800B020` + `0x823AC` = `0x808D3CC`
|
|
|
|
4. From header at `0x808D3CC`:
|
|
|
|
- At offset 8 (`0x808D3D4`): Size info (`0x746C`)
|
|
- At offset 12 (`0x808D3D8`): Destination = 0x10005000 (RAM address)
|
|
|
|
5. Final memcpy:
|
|
|
|
```c
|
|
memcpy(0x10005000, // destination (from header+12 at 0x808D3D8)
|
|
0x808D3EC, // source (header + 32)
|
|
0x746C) // size 0x746C (from header+8)
|
|
```
|
|
|
|
#### OTA1-TEXT header
|
|
|
|
```
|
|
seg001:0800B000 OTA1_HEADER DCD 0x35393138, 0x31313738; signature
|
|
seg001:0800B000 ; DATA XREF: FLASH_LoadAndBootImage+1AA↑r
|
|
seg001:0800B000 ; sub_8076A94+48↓o ...
|
|
seg001:0800B008 DCD 0x823AC ; image_size
|
|
seg001:0800B00C DCD 0x800B020 ; image_addr
|
|
seg001:0800B010 DCD 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF; reserved
|
|
seg001:0800B020 IMG2_OTA1_SIGNATURE_STR DCB "Customer Signature-modelxxx",0
|
|
```
|
|
|
|
#### OTA1-DATA
|
|
|
|
```
|
|
seg001:0808D3CC DCD 0x35393138, 0x31313738
|
|
seg001:0808D3D4 dword_808D3D4 DCD 0x746C ; DATA XREF: sub_8076A94+54↑r
|
|
seg001:0808D3D4 ; sub_80B0534+50↓r
|
|
seg001:0808D3D8 DCD unk_10005000 ; RAM destination address!!!
|
|
seg001:0808D3DC DCB 0xFF
|
|
seg001:0808D3DD DCB 0xFF
|
|
seg001:0808D3DE DCB 0xFF
|
|
seg001:0808D3DF DCB 0xFF
|
|
seg001:0808D3E0 DCB 0xFF
|
|
seg001:0808D3E1 DCB 0xFF
|
|
seg001:0808D3E2 DCB 0xFF
|
|
seg001:0808D3E3 DCB 0xFF
|
|
seg001:0808D3E4 DCB 0xFF
|
|
seg001:0808D3E5 DCB 0xFF
|
|
seg001:0808D3E6 DCB 0xFF
|
|
seg001:0808D3E7 DCB 0xFF
|
|
seg001:0808D3E8 DCB 0xFF
|
|
seg001:0808D3E9 DCB 0xFF
|
|
seg001:0808D3EA DCB 0xFF
|
|
seg001:0808D3EB DCB 0xFF
|
|
seg001:0808D3EC DCD sub_8084DDA+1
|
|
seg001:0808D3F0 DCD 0x803A95D
|
|
seg001:0808D3F4 aRtkwin_2 DCB "RTKWin",0
|
|
seg001:0808D3FB DCB 0xFF
|
|
```
|
|
|
|
#### jump
|
|
|
|
```nasm
|
|
LDR.W R0, [R8] ; load address
|
|
BLX R0 ; jump to entry point
|
|
BL j_BOOT_RunInitializerArray ; this is dead code, since FreeRTOS will never return from the entry point
|
|
```
|
|
|
|
#### Decompiled function
|
|
|
|
```c
|
|
__int64 __fastcall FLASH_LoadAndBootImage(int a1, int a2, int a3, int a4) {
|
|
int v4; // r0
|
|
_DWORD *efuseReg; // r5
|
|
int v6; // r4
|
|
_DWORD *ramDest; // r7
|
|
unsigned int *vectorTable; // r8
|
|
int *debugErr; // r5
|
|
int ramSize; // r4
|
|
int v11; // r1
|
|
int v12; // r2
|
|
int v13; // r3
|
|
int v14; // r7
|
|
int v15; // r1
|
|
__int64 v16; // r2
|
|
int *v17; // r4
|
|
int v18; // r0
|
|
int v19; // r6
|
|
int v20; // r11
|
|
bool v21; // zf
|
|
bool v22; // zf
|
|
int v23; // r0
|
|
int v24; // r7
|
|
int v25; // r1
|
|
unsigned int v26; // r2
|
|
int v27; // r0
|
|
int v28; // r0
|
|
int *v29; // r4
|
|
unsigned int v30; // r1
|
|
int v31; // r0
|
|
_QWORD v33[5]; // [sp+0h] [bp-28h] BYREF
|
|
|
|
HIDWORD(v33[0]) = a4;
|
|
LODWORD(v33[0]) = 0;
|
|
j_PAD_PullCtrl(6, 2);
|
|
j_PAD_PullCtrl(7, 2);
|
|
j_PAD_PullCtrl(8, 2);
|
|
j_PAD_PullCtrl(9, 2);
|
|
j_PAD_PullCtrl(10, 2);
|
|
j_PAD_PullCtrl(11, 2);
|
|
j_PAD_PullCtrl(29, 2);
|
|
v4 = j_PAD_PullCtrl(30, 2);
|
|
efuseReg = REG_SYS_EFUSE_SYSCFG6;
|
|
v6 = *((_DWORD *)REG_SYS_EFUSE_SYSCFG6 + 27);
|
|
if (!j_SYSCFG1_TRP_LDOMode(v4)) efuseReg[27] = v6 & 0xFFFFFFFD;
|
|
efuseReg[27] &= ~4u;
|
|
if ((*efuseReg & 0x2000) != 0) {
|
|
j_OTF_Mask(0, 0x8000, 2, 1);
|
|
set_flash_crypt_masks();
|
|
}
|
|
BYTE4(v33[0]) = 0;
|
|
thunk_IMG2_SetupAddresses();
|
|
ramDest = (_DWORD *)IMG2_RAM_START_ptr[0];
|
|
vectorTable = (unsigned int *)*VECTOR_TABLE_ptr;
|
|
debugErr = ConfigDebugErr_ptr;
|
|
ramSize = *(_DWORD *)IMG2_RAM_END_ptr[0] - *(_DWORD *)IMG2_RAM_START_ptr[0];
|
|
*(_DWORD *)ConfigDebugWarn_ptr[0] = 0;
|
|
*(_DWORD *)ConfigDebugInfo_ptr = 0;
|
|
*debugErr = -1;
|
|
if (!XIP_SYSTEM_DATA.UlogDbgEn) *debugErr = 0;
|
|
if ((*debugErr & 0x40000000) != 0) j_DiagPrintf(aImg1Enter);
|
|
j__memset(*ramDest, 0, ramSize);
|
|
BYTE4(v33[0]) = count_leading_zeros(XIP_SYSTEM_DATA.Spic_Mode);
|
|
v14 = count_leading_zeros(XIP_SYSTEM_DATA.Spic_Speed);
|
|
if (v14 >= 7) v14 = 6;
|
|
FLASH_ConfigureClock((char *)v33 + 4, v11, v12, v13);
|
|
if ((*debugErr & 0x40000000) != 0)
|
|
j_DiagPrintf("\rread_mode idx:%d, flash_speed idx:%d\n", BYTE4(v33[0]),
|
|
v14);
|
|
FLASH_DetectAndInitType(BYTE4(v33[0]), v15, v16);
|
|
j_FLASH_ClockDiv(v14);
|
|
FLASH_SetExtendedConfig(XIP_SYSTEM_DATA.Flash_Status);
|
|
v17 = off_80007DC;
|
|
v18 = *(int *)((char *)&dword_4 + (_DWORD)(off_80007DC + 2));
|
|
if (v18) j_FLASH_SetStatusBits(v18, 1);
|
|
v19 = BYTE4(v33[0]);
|
|
byte_25[(_DWORD)v17 + 46] = 0;
|
|
v20 = 0;
|
|
v21 = v19 == 0;
|
|
if (v19) v21 = v19 == 1;
|
|
if (v21) {
|
|
v20 = 2;
|
|
} else {
|
|
v22 = v19 == 2;
|
|
if (v19 != 2) v22 = v19 == 3;
|
|
if (v22) v20 = 1;
|
|
}
|
|
if (FLASH_CalibrateTimings(v20, v14) == 1) {
|
|
if ((*debugErr & 0x40000000) != 0) j_DiagPrintf("\rFLASH CALIB[NEW OK]\n");
|
|
j_FLASH_CalibrationNewCmd(1);
|
|
j_FLASH_CalibrationPhaseIdx((unsigned __int8)byte_25[(_DWORD)v17 + 47]);
|
|
if (v19) {
|
|
switch (v19) {
|
|
case 1:
|
|
v23 = 107;
|
|
break;
|
|
case 2:
|
|
v23 = 187;
|
|
break;
|
|
case 3:
|
|
v23 = 59;
|
|
break;
|
|
default:
|
|
LABEL_36:
|
|
j_FLASH_Init(v20);
|
|
goto LABEL_37;
|
|
}
|
|
} else {
|
|
v23 = 235;
|
|
}
|
|
*(int *)((char *)&dword_8 + (_DWORD)v17) = v23;
|
|
goto LABEL_36;
|
|
}
|
|
LABEL_37:
|
|
j_Cache_Enable(1);
|
|
v24 = 1;
|
|
OTA_SelectBootImage(v33);
|
|
if (v27 != 1) {
|
|
v25 = *(_DWORD *)&IMG2_OTA1_SIGNATURE_STR_ptr[-24];
|
|
LODWORD(v33[0]) = &IMG2_OTA1_SIGNATURE_STR_ptr[v25];
|
|
v24 = 0;
|
|
}
|
|
if ((*debugErr & 0x40000000) != 0)
|
|
j_DiagPrintf("\rIMG2 DATA[0x%x:%d:0x%x]\n", LODWORD(v33[0]) + 32,
|
|
*(_DWORD *)(LODWORD(v33[0]) + 8),
|
|
*(_DWORD *)(LODWORD(v33[0]) + 12));
|
|
v28 = *(_DWORD *)(LODWORD(v33[0]) + 12);
|
|
if (v28 == -1 ||
|
|
(v25 = v33[0], v26 = *(_DWORD *)(LODWORD(v33[0]) + 8), v26 > 0x100000)) {
|
|
if ((*debugErr & 0x40000000) != 0) j_DiagPrintf("\rIMG2 ADDR Invalid\n");
|
|
goto LABEL_45;
|
|
}
|
|
j__memcpy(v28, LODWORD(v33[0]) + 32, v26);
|
|
v29 = off_8000808;
|
|
if ((*debugErr & 0x40000000) != 0) {
|
|
j_DiagPrintf("\rIMG2 SIGN[%s(%x)]\n", (const char *)*off_8000808,
|
|
*off_8000808);
|
|
if ((*debugErr & 0x40000000) != 0)
|
|
j_DiagPrintf("\rIMG2 ENTRY[0x%x:0x%x]\n", *VECTOR_TABLE_ptr,
|
|
*vectorTable);
|
|
}
|
|
if (j__strcmp(*v29, aRtkwin_0)) {
|
|
if ((*debugErr & 0x40000000) != 0) j_DiagPrintf(aImg2SignInvali);
|
|
while (1)
|
|
LABEL_45:
|
|
j_RtlConsolRom(1000, v25, v26);
|
|
}
|
|
if (v24) {
|
|
v30 = *vectorTable;
|
|
if (XIP_SYSTEM_DATA.OTA2_FlashAddr >= *vectorTable) {
|
|
while (1) {
|
|
if ((*debugErr & 0x40000000) != 0) {
|
|
j_DiagPrintf(aOta2ImageError);
|
|
if ((*debugErr & 0x40000000) != 0)
|
|
j_DiagPrintf(
|
|
"\rplease try to rebuild your project to generate OTA2 image "
|
|
"again...\n\n\n\n");
|
|
}
|
|
j_RtlConsolRom(50000, v30, v26);
|
|
}
|
|
}
|
|
}
|
|
FLASH_CheckAndDecryptRDP();
|
|
v31 = ((int (*)(void)) * vectorTable)();
|
|
j_BOOT_RunInitializerArray(v31);
|
|
return v33[0];
|
|
}
|
|
```
|