Native flasher for SPRD/UNISOC .pac firmware — Air724UG, Air722UG and
other RDA8910 / UIS8910 modules — that speaks the Spreadtrum BootROM/FDL
download protocol directly over the serial port. No vendor download tool
(CmdDloader.exe / ResearchDownload.exe) is required.
Sibling project to pacflash, which drives the vendor tool.
sprdflashreimplements the wire protocol instead.
sprdflash info firmware.pac # parse + CRC-validate, classify FDL/flash/marker entries
sprdflash identify # connect to the BootROM and read its version (safe, read-only)
sprdflash flash firmware.pac # load FDL1/FDL2 and write every partition natively
The Spreadtrum BootROM speaks a framed protocol (BSL) over HDLC:
0x7e | escape( type[2] size[2] data[size] checksum[2] ) | 0x7e
- BootROM mode uses a CRC16 checksum (poly
0x11021); once FDL1 runs the device switches to a ones-complement sum checksum. - Flash flow: autobaud (
0x7e->VER) ->CONNECT-> send FDL1 andEXEC-> re-handshake in sum-checksum mode -> send FDL2 andEXEC->START_DATA/MIDST_DATA/END_DATAfor each real partition ->NORMAL_RESET.
The FDL stages and partition load addresses are read straight from the .pac
file table; logical erase/format markers are skipped.
On the RDA8910/UIS8910 the BootROM enumerates as a CDC-ACM USB gadget
(0525:A4A7, "SPRD U2S Diag"), but the BSL protocol does not ride the CDC
serial framing. It uses the raw bulk endpoints of the data interface
(OUT 0x02, IN 0x81), activated by a vendor control transfer
(bmRequestType=0x21, bRequest=0, wValue=1). A plain COM-port open only sends
the standard CDC line-state request, which the BootROM ignores — which is why a
pyserial-only approach stays silent. sprdflash therefore talks to the device
through libusb (--transport usb, the default). A --transport serial
mode is provided for targets whose BootROM is exposed on a real UART.
Protocol references (clean-room; no vendor code used): the open-source spreadtrum_flash / spd_dump and iscle/sprdclient projects, plus the device's own USB descriptor.
-
Python ≥ 3.10,
pip install "sprdflash[usb]"(the[usb]extra pulls in pyusb + a bundled libusb). -
A module in download mode. Two entry paths / USB identities are supported:
1782:4D00— the raw BootROM, reached by strapping theUSB_BOOTpin high (to VDD_EXT / VDD_1V8, directly or via a pull-up) during reset. This is the most reliable native target (plain BSL autobaud).0525:A4A7("SPRD U2S Diag") — the soft-download interface exposed afterAT*DOWNLOAD=1on the AT port (see pacflash for the automated switch).
Endpoints are auto-discovered from the descriptor, so either identity works.
-
A WinUSB/libusbK driver bound to the BootROM device. On Windows the device ships with the CDC/usbser (COM-port) driver, through which libusb cannot do I/O. Use Zadig once to replace the driver for USB
0525:A4A7with WinUSB. This is a deliberate, reversible choice: while WinUSB is bound, the vendor tool and pacflash cannot use the device (they need the COM driver) — swap back to restore them.
sprdflash flashed the full 6 MB firmware to an Air724UG (RDA8910) in ~42 s
with no vendor tool, and the module rebooted into the flashed firmware
(ATI reported LuatOS-Air_V4035_..., IMEI intact, SIM READY).
The reverse engineering that made it work: the RDA8910 download agent reached
via AT*DOWNLOAD (0525:a4a7) does not speak BSL in its first stage. It
speaks a proprietary PDL (Packet Download Loader) protocol — ae-framed,
over the vendor sprd_rdavcom COM port — to load and execute the first-stage
loader (PDL1 / HOST_FDL). Only after FDL1 runs does the device switch to the
BSL/HDLC protocol for FDL2 and the partitions. Both layers are implemented here
(pdl.py, protocol.py) and stitched together in native.py. The PDL wire
format was recovered by tracing the vendor ResearchDownload.exe with Frida.
You cannot brick the BootROM: a failed load just means re-entering download mode
and retrying. If a device gets stuck mid-attempt, a USB device restart
(pnputil /restart-device, elevated) or a power-cycle resets its agent.
A same-SDK reflash writes the payload partitions only. Changing firmware type
(e.g. LuatOS V4035 ⇄ CSDK V302340) additionally needs the PAC's logical
0xFE0000xx markers, which --format replays exactly as the vendor tool does —
reverse-engineered byte-for-byte from a Frida trace and confirmed on hardware
(the module boots the new SDK on the soft reset, IMEI and network intact):
FMT_FSSYS→ERASE_FLASH "SYSF"andFLASH→ERASE_FLASH 0format the stale filesystem (FMT_FSEXTshares the address and is not a second erase).NVis written from the PAC's nvitem template with a 12-byteSTART_DATA(addr | size | sum32) thatEND_DATAverifies. The template's leading CRC-16-ARC is recomputed first — the FDL2 validates it and rejects a stale one withOPERATION_FAILED. IMEI/RF-calibration live in a separatefactorynvregion the format never touches, so they survive the SDK change.PREPACK(the prepack cpio) is written plain after NV.- After
NORMAL_RESETthe frame is flushed and the port held briefly before closing; closing immediately cancels the in-flight reset and the module returns to download mode instead of booting.
> sprdflash flash --format LuatOS-Air_V302340_CSDK_BT_TTS_elua.pac
# module in download mode (AT*DOWNLOAD, or hold the boot key at power-on)
> sprdflash flash LuatOS-Air_V4035_RDA8910_TTS_NOVOLTE_FLOAT.pac
native-flashing ... via COM34 (PDL+BSL, no vendor tool)
FDL1 100%
FDL2 100%
BOOTLOADER 100%
AP 100%
PS 100%
LUA 100%
flash complete - module reboots into the new firmware
# one-time: Zadig -> USB 0525:A4A7 -> WinUSB -> Replace Driver
> sprdflash identify
connected via usb
BootROM version: SPRD3
> sprdflash flash C:\firmware\LuatOS-Air_V4035_RDA8910_TTS_NOVOLTE_FLOAT.pac
native-flashing ... via usb
BOOTLOADER 100%
AP 100%
PS 100%
...
flash complete
| code | meaning |
|---|---|
| 0 | success |
| 1 | file not found |
| 2 | PAC validation failed |
| 3 | no device / download port |
| 4 | flash / protocol error |
| 5 | USB transport unavailable (install [usb], or bind WinUSB via Zadig) |
MIT — see LICENSE. Protocol knowledge is from public open-source projects; no vendor binaries are included or required.