ajsb85/sprdflash-rs
GitHub: ajsb85/sprdflash-rs
纯 Rust 编写的跨平台 SPRD/UNISOC .pac 固件烧录工具,专为生产线多夹具并行高吞吐量产场景设计。
Stars: 3 | Forks: 0
# SPRD Flash Tool
[](https://github.com/ajsb85/sprdflash-rs/actions/workflows/ci.yml)
[](LICENSE)
[](https://doc.rust-lang.org/edition-guide/rust-2024/index.html)
[](https://blog.rust-lang.org/)
[](#build--test)
[](https://www.conventionalcommits.org)
[](crates/sprdflash-core)
纯 Rust 编写的跨平台(Windows 11 + Linux/WSL)SPRD/UNISOC `.pac` 固件烧录工具
——适用于 RDA8910 / UIS8910(Air724UG 及相关设备)。专为**生产线**构建:
快速、稳健且具备高度可观测性,足以在多个夹具上每天执行数千次烧录。
它是经过硬件验证的 Python 版
[`sprdflash`](https://github.com/ajsb85/sprdflash) 的移植版,原项目对完整的下载协议(PDL 第一阶段
→ BSL/FDL2 → 分区 → 跨 SDK 格式)进行了逆向工程,并从厂商工具中逐字节还原。
本项目将该协议保留在 **sans-I/O 核心库** 中,从而保持确定性并完全可进行单元测试,
并在其上层叠加了快速、可恢复的传输层和工位编排器。
## 为什么使用 Rust,以及速度的来源
瓶颈在于传输线,而不是 CPU。主要的优化手段(按效果从大到小排列)如下:
1. **将 `CHANGE_BAUD` 设置为 FDL 的最大值。** 参考烧录工具在 115200 波特率下
运行整个 6 MB 的传输(约 42 秒)。FDL2 支持更高的速率;将其提升至
921600–3 M 可实现 **8–25 倍的线路提速** —— 这是单项最大的优化。
2. **每个夹具分配一个工位。** 串行 I/O 是阻塞式且对延迟敏感的,因此
每个夹具都有自己独立的 OS 线程;吞吐量随夹具数量呈线性扩展。
3. **更大的 MIDST 数据块 + 在批量数据阶段使用 `DISABLE_TRANSCODE` (0x21)** ——
减少了往返次数,且没有 HDLC 转义扩展。
4. **仅对 PAC 进行一次 `mmap`**,通过 `Arc` 共享零拷贝的有效载荷切片给所有工位。
## 稳健性(工业级)
- **Sans-I/O 核心库** (`sprdflash-core`):声明了 `#![forbid(unsafe_code)]`,测试时无需
硬件 —— 协议是根据抓取的厂商数据流进行验证的。
- 分阶段的 **超时 + 有限次重试 + 自动设备恢复**(在 Windows 上
通过 `pnputil` 重新枚举;在 Linux/WSL 上通过 `usbip` 重新连接 / `uhubctl` 电源循环)。
- **烧录前进行 PAC CRC 校验** —— 绝不烧录损坏的镜像文件。
- 提供**针对单个设备的 JSON-lines 记录** 以供 MES 使用,每个工位配备 `tracing` 跨度,
以及 Prometheus 指标(良率、吞吐量、各阶段耗时)。
- **烧录后启动验证**(在 AT 端口执行 ATI/IMEI 校验),仅追加的审计日志。
- 提交了 `Cargo.lock`,在 release 构建中启用了 `panic = "abort"` + `overflow-checks = on`。
## 工作区
| crate | 状态 | 角色 |
|----------------------|--------|---------------------------------------------------------------|
| `sprdflash-core` | ✅ 完成 | sans-I/O 协议:PAC 解析、PDL + BSL 组帧、校验和、计划 |
| `sprdflash-cli` | ✅ `info`, `list-ports`, `flash` | `sprdflash` 可执行程序 |
| `sprdflash-transport`| ✅ 完成 | `serialport` 传输、端口发现、信标窗口连接、恢复 |
| `sprdflash-flash` | ✅ 完成 | 设备驱动:PDL→BSL→分区→格式化→重置,`CHANGE_BAUD` |
| `sprdflash-line` | ✅ 完成 | 并行工位、启动验证 (ATI/IMEI)、JSON-lines 记录、指标 |
核心库已针对真实的 V4035 PAC 和参考 Python 实现进行了逐字节验证。完整的驱动程序已
**在真实的 Air724UG (RDA8910) 上完成了硬件验证**,包括同 SDK 的重新烧录以及跨 SDK
的 `--format` 切换(LuatOS V4035 ⇄ CSDK V302340),在软重启后设备即可直接启动,且
IMEI 和 NV 数据保持完好。
### 实测速度(单设备,6 MB)
| 工具 / 配置 | 耗时 | 备注 |
|------------------------------|--------|---------------------------------------------|
| Python `sprdflash` (528 B) | ~42 s | 参考 |
| `sprdflash-rs` (2048 B) | ~33 s | **快 22%**;默认配置 |
该设备受限于**闪存写入速度**(~186 KiB/s):大于 ~2 KB 的 MIDST 数据块会导致
FDL2 接收缓冲区溢出,而且在 2 KB 时每帧的开销已经很小了 —— 因此单设备的耗时已
接近其下限。**流水线吞吐量的提升来自于并行运行每个夹具的一个工位**(即
`sprdflash-line` crate),而不是靠压榨单个设备的速度。
## 构建与测试
```
cargo test # 21 tests, all against captured ground truth
cargo build --release
# 单个设备,自动 mode-switch,启动进入新固件
sprdflash flash --enter-download firmware.pac
# cross-SDK 更改:格式化 FS + 刷新 NV(保留 IMEI)
sprdflash flash --enter-download --format firmware.pac
sprdflash info firmware.pac
sprdflash list-ports
```
目标平台:`x86_64-pc-windows-msvc` 和 `x86_64-unknown-linux-gnu` (WSL Ubuntu)。
## 通过 usbipd 从 WSL 进行烧录
在原生 Linux 主机上,下载小工具显示为普通的 `/dev/ttyACM0`,烧录过程与在
Windows 上完全相同。要想从 **WSL** 中访问连接在 Windows 上的设备,请使用
[usbipd-win](https://github.com/dorssel/usbipd-win) 进行转发:
```
usbipd bind --busid # once, elevated
usbipd attach --wsl --busid # module -> /dev/ttyUSB*, download -> /dev/ttyACM0
```
```
# 在 WSL 中,以 root 身份(或将你的用户添加到 dialout group)
sprdflash flash --port /dev/ttyACM0 --chunk 512 --format firmware.pac
```
**在使用 usbipd 时请使用 `--chunk 512`。** 默认的 2 KB MIDST 帧非常适合
直连环境,但 usbipd 背后通用的 `cdc_acm` 驱动程序在处理多 MB 传输末尾的
大帧数据时会停滞;使用 512 B 可以保证端到端的稳定传输(已验证:完整的
6 MB 烧录 + 格式化 + 启动,IMEI 完好无损)。原生的 Linux 生产线主机没有
usbipd 隧道,可以很好地运行默认的 2 KB 配置。
## 运行流水线
每个 `--station` 都在其独立的线程上运行;它们并行执行烧录和启动验证,
为每个设备流式传输一条 JSON-lines 记录,并报告良率和吞吐量。各工位之间
相互独立,因此一个损坏的设备绝不会阻塞整条流水线。卡死的下载代理会被
自动恢复(在 Windows 上通过 `pnputil` 重新枚举;在 Linux 上则通过夹具的
电源硬重启),并限制重试次数。
```
> sprdflash line firmware.pac \
--station fixture-1:COM12 --station fixture-2:COM22 --station fixture-3:COM32 \
--format --records /var/log/flash/units.jsonl
── results ──
[PASS] fixture-1 41.0s LuatOS-Air_V4035_... IMEI 8634880509...
[PASS] fixture-2 41.3s LuatOS-Air_V4035_... IMEI 8634880511...
[PASS] fixture-3 40.8s LuatOS-Air_V4035_... IMEI 8634880514...
3/3 passed in 41.3s → ~261 good units/hour at this concurrency
```
每个设备对应 MES / 审计日志中的一条 JSON 记录:
```
{"ts_ms":1700000000000,"station":"fixture-1","port":"COM12","product":"UIX8910_MODEM","result":"pass","attempts":1,"bytes":6071296,"flash_seconds":33.2,"total_seconds":41.0,"firmware":"LuatOS-Air_V4035_...","imei":"863488050987562","error":null}
```
单个设备受限于闪存写入速度,耗时约 33 秒,因此吞吐量随夹具数量增加而提升
(每个约 110个/小时) → **在规模适中的工位集群中即可实现每天数千台的产量**。
## 协议参考
完整的逆向工程说明请参阅 Python 项目的 README 和 `native.py`。以下是
编码在 `sprdflash-core` 中的关键事实:
- **PDL** (`0525:a4a7`,第一阶段):使用 `ae` 组帧,头部和有效载荷是
**分离写入**的;加载 `HOST_FDL`/`PDL1`,然后移交给 BSL。
- **BSL**:使用 HDLC `0x7e` 组帧,`type|size` 为大端序,在 RDA8910/UIS8910
上使用 **Spreadtrum sum** 校验和(而非 CRC)。
- **跨 SDK 的 `--format`**:擦除 `FMT_FSSYS "SYSF"` + `FLASH 0`(根据
地址去重),写入带有更新后 **CRC-16-ARC** 的 **NV 模板**以及经过 **sum32** 校验的
12 字节 `START_DATA`,然后执行 `PREPACK`。IMEI/RF 校准数据存在于一个
独立的 `factorynv` 区域中,格式化过程绝不会触及该区域。
标签:Rust, SPRD/UNISOC, 串口通信, 可视化界面, 固件烧录, 嵌入式系统, 网络流量审计, 自定义请求头, 通知系统