# Technical Deep Dive: Unisoc BSL Protocol, FDL1 and FDL2

## **The Boot Sequence: Power-On to FDL2**

When a Unisoc device (like the SL8541E) powers on, the primary CPU executes code from the primitive **ROM (BootROM)**.

### **Stage 0: BootROM (BROM)**

At power-on, a Unisoc SoC is an "empty shell" running primitive code from a tiny on-chip ROM. To interact with it, we must catch it in **Download Mode**.

If the device detects a specific key combo (usually Vol-Down) or if the eMMC is blank/corrupted, it enters **Download Mode**. In this state, it enumerates on USB as a Serial Port (VID `0x1782`, PID `0x4D00`).

### **Stage 1: The Handshake**

The tool must "wake up" the BROM. There are two primary methods:

**Legacy BSL (0x7E):** The tool sends a burst of `0x7E` bytes. The BROM responds with `~SPRD3W~` or similar. Used by older chips.

```cpp
// From uflash/src/bsl_protocol.cpp 
bool BslProtocol::handshake(int timeout_ms) { 
    std::cout << "Sending 0x7E handshake (32-byte burst)...\n";
    std::vector<uint8_t> burst(32, 0x7E); // BSL_FRAME_TAG
    dev_.write(burst.data(), burst.size()); // ... wait for "~SPRD3W~" response ... 
}
```

**Modern Host Protocol (0xAE):** The tool sends `0xAE 0xAE 0xAE 0xAE`. The BROM responds with its version string. This is common in Android 10+ chipsets.

```cpp
bool BslProtocol::host_handshake(int timeout_ms) { 
    uint8_t cmd_ae[] = { 0xAE, 0xAE, 0xAE, 0xAE }; 
    dev_.write(cmd_ae, 4); // ... device responds with internal version string ... 
}
```

### Boot Service Layer (BSL) Protocol

Once the handshake is complete, we use the **BSL Protocol** for all further commands. Every command is "framed" with `0x7E` tags.

Every packet is framed to ensure integrity over noisy serial/USB lines: `[7E] [Type: 2B] [Len: 2B] [Payload: N Bytes] [Checksum/CRC: 2B] [7E]`

*   **Endianness:** Big-Endian for headers, Little-Endian for payload contents.
    
*   **Escaping:** If `0x7E` or `0x7D` appears inside the packet, it is escaped to `0x7D [Byte ^ 0x20]`.
    

### Key Enums & Opcodes

| Command | Hex | Description |
| --- | --- | --- |
| `BSL_CMD_CONNECT` | `0x00` | Basic handshake |
| `BSL_CMD_START_DATA` | `0x01` | Prepare for transfer (Name, Address, Size) |
| `BSL_CMD_MIDST_DATA` | `0x02` | Steam data chunks |
| `BSL_CMD_END_DATA` | `0x03` | Finalize transfer & Verify Checksum |
| `BSL_CMD_EXEC_DATA` | `0x04` | Execute code at address |
| `BSL_CMD_READ_FLASH` | `0x10` | Read raw block from flash |
| `BSL_CMD_REPARTITION` | `0x0B` | Format and rebuild eMMC GPT |

### Response Codes

*   `BSL_REP_ACK (0x80)`: Success.
    
*   `BSL_REP_VER (0x81)`: Version response.
    
*   `BSL_REP_OPERATION_FAILED (0x84)`: Internal device error.
    
*   `BSL_REP_INVALID_PARTITION (0xFE)`: Partition name/type mismatch.
    

### **Stage 2: FDL1 (Stage 1 Loader)**

The BROM is extremely limited—it can only read data into a tiny area of SRAM.

FDL1 is small (~40KB) and resides in internal SRAM. Its primary job is to **initialize External DDR RAM**.

1.  The tool sends `BSL_CMD_START_DATA` with the address (usually `0x5000` or `0x4000`) and size of `fdl1-sign.bin`.
    
2.  Data is streamed in chunks via `BSL_CMD_MIDST_DATA`. `BSL_CMD_EXEC_DATA` tells the CPU to jump to the FDL1 address.
    

After FDL1 executes, the device **re-enumerates** as a different USB device. The host must re-find the handle.

```cpp
// Flow in main.cpp
bsl.start_data(0x5000, fdl1_size, fdl1_checksum); // Target SRAM address
bsl.midst_data(fdl1_buffer, chunk_size);
bsl.exec_data(0x5000); // Trigger Jump to 0x5000
```

### Stage 2: FDL2 (DDR Loader)

FDL2 is the full "Flash Kernel". We load `fdl2-sign.bin` into DDR (usually high addresses like `0x80000000`). FDL2 contains the **Full BSL implementation**, including:

*   eMMC/UFS Drivers.
    
*   File System support (EXT4/Sparse).
    
*   Modern Repartitioning logic.
