Build HiSilicon application images and fwpkg firmware packages from a
compiled program (ELF or raw .bin), for chips in the WS63 / BS2X family.
probe-rs / hisiflash can write bytes to a WS63's flash, but flashboot does
not boot a bare ELF/bin: it expects the app partition (flash 0x230000 on
WS63) to hold a HiSilicon app image — a fixed 0x300-byte header followed by
the code. This crate produces that image and wraps it in the fwpkg container
that hisiflash flashes.
It is the producer counterpart to
hisiflash's fwpkg parser.
ELF / .bin ──▶ flatten (objcopy) ──▶ 0x300 image header + body ──▶ fwpkg (V1)
hisi_fwpkg::elf hisi_fwpkg::image hisi_fwpkg::fwpkg
cargo install --path crates/hisi-fwpkg-cli # installs `hisi-fwpkg`
# ELF/bin -> app image -> single-partition fwpkg (app only):
hisi-fwpkg pack blinky -o blinky.fwpkg --chip ws63
# Just the raw app image (0x300 header || body), e.g. to burn at 0x230000:
hisi-fwpkg image blinky -o blinky.img
# Override the app partition address / chip:
hisi-fwpkg pack app.elf -o app.fwpkg --chip bs21
hisi-fwpkg pack app.bin -o app.fwpkg --app-addr 0x230000Validate the result statically with hisiflash:
$ hisiflash info blinky.fwpkg
格式: V1 (32-byte names)
分区数: 1
CRC 有效: 是
[ 0] app 烧录地址: 0x00230000 长度: 5984 字节use hisi_fwpkg::{Chip, pack_app_fwpkg, PackOptions, build_app_image, ImageOptions};
// End-to-end: ELF/bin -> fwpkg
let elf = std::fs::read("blinky")?;
let fwpkg = pack_app_fwpkg(&elf, Chip::Ws63, &PackOptions::default())?;
std::fs::write("blinky.fwpkg", fwpkg)?;
// Or just the image header + body (for a full first-flash package you build
// yourself with build_fwpkg, prepending the vendor boot partitions):
let body = std::fs::read("blinky.bin")?;
let image = build_app_image(&body, &ImageOptions::default())?;The app image burned at 0x230000 is a fixed-size header followed by the code:
+--------------------------------------+ 0x000
| image_key_area_t (0x100 bytes) | image_id = 0x4B0F2D1E
+--------------------------------------+ 0x100
| image_code_info_t (0x200 bytes) | image_id = 0x4B0F2D2D
+--------------------------------------+ 0x300 = APP_IMAGE_HEADER_LEN
| code body (.text/.rodata/...) | linked to run at 0x230300
+--------------------------------------+
Key fields (little-endian), matching the vendor sign_tool output:
| area | off | field | value |
|---|---|---|---|
| key | 0x00 | image_id |
0x4B0F2D1E |
| key | 0x08 | structure_length |
0x100 |
| key | 0x18 | key_alg |
0x2A13C812 (ECC256/bp256r1) |
| code | 0x00 | image_id |
0x4B0F2D2D |
| code | 0x08 | structure_length |
0x200 |
| code | 0x24 | code_area_len |
body length |
| code | 0x28 | code_area_hash[32] |
SHA-256 of body |
| code | 0x48 | code_enc_flag |
0x3C7896E1 = not encrypted |
⚠️ code_enc_flagis a non-zero "no-encryption" sentinel (FLASH_NO_ENCRY_FLAG = 0x3C7896E1). A zero value makes flashboot try to configure on-the-fly flash decryption and fail to boot a plaintext image.
The ECC signature blobs (key-sig @ 0xC0, code-sig @ 0x280) are left zero
("dummy"). See "Is the signature real?" below.
flashboot's verify_image_* (bootloader/commonboot/src/secure_verify_boot.c)
each call check_verify_enable() first; when the efuse
SEC_VERIFY_ENABLE == 0 it returns ERRCODE_NOT_SUPPORT and the verify
function returns success immediately, before reading any signature or even
the body hash. Then start_fastboot() (bootloader/flashboot_ws63/startup/main.c)
ends with:
jump_to_execute_addr(image_addr + APP_IMAGE_HEADER_LEN); // 0x230000 + 0x300i.e. flashboot jumps unconditionally to app_partition + 0x300. So on a
secure-boot-disabled board (the ones that print secure verify disable!), the
only thing that matters is that the header is exactly 0x300 bytes and the body
that follows is real code linked at 0x230300. The signatures and even the
code_area_hash are never checked.
The vendor sign_tool_pltuni is a closed remote signing server (the
sign_client_cfbb.py script just sends the cfg + bin over a socket and gets the
signed bin back). It uses ECDSA-SHA256 over brainpoolP256r1 with a private key
we do not have. We therefore produce a structurally-identical, dummy-signed
image. Verified parity: rebuilding the header over the vendor's own unsigned
ws63-liteos-app.bin body yields a file byte-identical to the official
ws63-liteos-app-sign.bin in all 3 areas except 63 bytes — all inside the two
ECC signature regions. (See crates/hisi-fwpkg/tests/vendor_parity.rs.) On a
board with secure boot enabled this image would be rejected; with it disabled it
boots.
+----------------------------------+ 0x000
| FWPKG_HEAD (12 B) | flag=0xEFBEADDF crc16 cnt total_len
+----------------------------------+ 0x00C
| IMAGE_INFO[i] (52 B each) | name[32] offset len burn_addr burn_size type
+----------------------------------+
| payload[i] || 16 zero bytes | (separator padding, counted in total_len)
+----------------------------------+
crc is CRC16/XMODEM (poly 0x1021, init 0x0000) over bytes [6 .. end of descriptor table). Partition type: loader=0, normal=1, kv=2, efuse=3.
A full first-flash WS63 package has 7 partitions (loaderboot/params/ssb/
flashboot/flashboot_backup/nv/app); a pack here produces an app-only
package, sufficient to re-flash the app on a board that already has a working
boot chain.
No. A bare .bin/ELF at 0x230000 is missing the 0x300 image header, so
flashboot jumps to 0x230300 and lands 0x300 bytes into your code (or into
stale SRAM). The only missing piece is this header — which is exactly what
hisi-fwpkg image / pack adds. With it (and secure boot disabled), the app
boots; no real signature is required.
Dual-licensed under MIT or Apache-2.0.