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.
// 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.
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
0x7Eor0x7Dappears inside the packet, it is escaped to0x7D [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.
The tool sends
BSL_CMD_START_DATAwith the address (usually0x5000or0x4000) and size offdl1-sign.bin.Data is streamed in chunks via
BSL_CMD_MIDST_DATA.BSL_CMD_EXEC_DATAtells 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.
// 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.
