OptiStick (S905X5M) eFuse burn and signed-firmware burn: runbook as run
Wilber Zheng · 2026-11-04 · based on the run of 2026-11-03 on one Shenzhen PVT stick (prd100046, prod root) · written so a person or an agent can repeat it
The authoritative procedure is Frank's team guide in yocto_os branch main-amlinux: claude-dev/frank-analysis/secure-boot/10-team-guide.md (§3 burn, §4 is it fused, §5 fuse, §6–§8 after, recovery, traps). This page is the order we actually ran, the exact commands, what we saw, and the deviations a station without root or a prd ROM without a shell forces on you.
0. The whole run on one page
| # | Step | Irreversible | Result on 11-03 |
|---|---|---|---|
| 1 | Get the inputs: prod-signed prd pack, fuse script + pattern at the same commit; check them offline | no | checker rc 0, 4 items at the prod root |
| 2 | Park the unit in u-boot ADNL ([TPL]) and burn the pack with -r 1 on the unfused unit; prove it boots | no | boots to pairing, optisigns.sb=open |
| 3 | Dry run stage A (reads only) | no | all fields 0, rc 0 |
| 4 | Park again with a -r 0 burn, pre-checks (rpmb_state=1, erase tee), stage A real | yes | both root hashes written + locked, rc 0 |
| 5 | Cold power-cycle, boot check, boot0/boot1 == pack bootloader | no | open, green, boot0 = boot1 = pack |
| 6 | Park, latch probe, dry run stage B | no | hashes locked, enables 00, rc 0 |
| 7 | Stage B real (the two enables back to back) | yes | both enables 01, read-back OK, rc 0 |
| 8 | First fused boot (warm + cold), post-fuse checks | no | fused, green, locked; refusals as designed |
1. Inputs and station
1.1 What you need
- ROM: a prd pack signed with the prod KMS keys, e.g.
optistick-x5m-prd100046-1g-5103dea85-ab-all.img(tagrom/prd100046=5103dea85). Record its md5 and sha256. - Fuse script and pattern from the same commit as the ROM. We used a detached worktree of
yocto_osat5103dea85, so the shared station checkout was not touched:git -C <yocto_os> worktree add --detach <wt> 5103dea85 sha256sum <wt>/amlogic/flash-host/sb-fuse-usb.sh # record it sha256sum <wt>/ota-trust/efuse/prod/pattern/pattern.txt # must equal pattern.sha256 cat <wt>/ota-trust/efuse/prod/pattern/pattern.sha256 - Offline pack check (guide §2.3) before the pack goes near a device:
sb_check_bootloader.pymust return rc 0 and report every bootloader item at the prod root hash (abe6fe5d…). - Burn tools:
~/flash/aml-flash-tool/tools/adnl/withadnl_burn_pkg(burn),adnl(single commands) andusb_flow/(libraries). Every call needsLD_LIBRARY_PATH=./usb_flowand must run from that directory.
1.2 Station deviations we had to make (no root on the station)
adnlwithout sudo. The udev rule gives Amlogic devices mode 0666 (SUBSYSTEM=="usb", ATTR{idVendor}=="1b8e", MODE="0666"), so single commands run as the user. A wrapper used asSB_ADNLfor the fuse script:#!/bin/sh cd "$HOME/flash/aml-flash-tool/tools/adnl" && LD_LIBRARY_PATH=./usb_flow exec ./adnl "$@"adnl_burn_pkgwith sudo, timeout outside sudo. Our sudoers allows onlyadnl_burn_pkgwith SETENV, notsudo env:cd ~/flash/aml-flash-tool/tools/adnl timeout 900 sudo -n LD_LIBRARY_PATH=./usb_flow ./adnl_burn_pkg -p <img> -s <dnl-serial> -e 1 -b 1 -r <0|1> -t 0-r 1boots Linux after the burn;-r 0leaves the unit parked in u-boot ADNL.-spins the burn to one unit (the DNL serial, 24 lowercase hex chars).- flash-daemon could not be stopped (needs root). It is safe if no slot is armed: an idle daemon never identifies a DUT. Check every
~/flash/state/next.*slot is empty before each step. - No
bay-fusedrow / fused-guard on an old station. Our station predates the fused guard. We ran the offline checker instead and burned only the one checked pack by hand. On a current station follow guide §5.2.
2. Park the unit (enter u-boot ADNL) without a shell
A prd ROM has no shell, so the guide's systemctl reboot update is not available. Use u-boot's power-on burn window instead (u-boot patch 178): on every power-on u-boot offers USB-A to a PC for about 3 s (enumerate) + 5 s (a burn tool must identify). The burn tool must already be waiting when the unit powers on.
- USB-A of the stick to the station with an A-to-A data cable. USB-C is power.
- Start the catch script before powering on. It waits for the unplug, then for the unit to appear as
1b8e:c004, then burns at once with-r 0so the unit stays parked:#!/bin/bash IMG=<prod-signed prd pack>; SER=<dnl-serial>; PORT=<usb sysfs port of the unit, e.g. 1-2>; L=$1 while [ -n "$(cat /sys/bus/usb/devices/$PORT/idVendor 2>/dev/null)" ]; do sleep 0.2; done # unplugged until [ "$(cat /sys/bus/usb/devices/$PORT/idVendor 2>/dev/null):$(cat /sys/bus/usb/devices/$PORT/idProduct 2>/dev/null)" = "1b8e:c004" ]; do sleep 0.1; done cd ~/flash/aml-flash-tool/tools/adnl || exit 9 timeout 900 sudo -n LD_LIBRARY_PATH=./usb_flow ./adnl_burn_pkg -p "$IMG" -s "$SER" -e 1 -b 1 -r 0 -t 0 >> "$L" 2>&1 echo "burn rc=$?" >> "$L" LD_LIBRARY_PATH=./usb_flow ./adnl -s "$SER" getvar identify >> "$L" 2>&1 - A person unplugs USB-C, waits ~3 s, plugs it back.
- Expect
burn successful, thengetvar identify→DNL mode [TPL]. That is the park.[BL1]is BootROM DFU (same USB id): not a park, replug and retry.
To park without burning (read-only checks), the same catch with adnl getvar identify in place of the burn holds the unit in the window. We used this for the boot0/boot1 check.
3. Burn the signed pack on the unfused unit and prove it
Same catch, with -r 1. Then check on the unit (needs a shell: USB debug toggled on screen + the device-agent cert, or SecureLink):
grep -o 'optisigns.sb=[a-z]*' /proc/cmdline # optisigns.sb=open
grep -o 'androidboot.verifiedbootstate=[a-z]*' /proc/cmdline # green
cat /etc/os-release | grep -E 'IMAGE_VERSION|BUILD_ID' # prd100046 …5103dea85
Guide §5.1 also wants a prd .swu OTA commit on the unfused unit; we did not have one (see §8).
4. Stage A: the two root hashes
4.1 Dry run (reads only)
cd <wt>
SB_ADNL=<adnl wrapper> bash amlogic/flash-host/sb-fuse-usb.sh --serial <dnl-serial> --stage A \
--expect-hash ota-trust/efuse/prod/keys/root/rsa/s905x5m/roothash/hash-device-rootcert-rsa.bin \
--pattern ota-trust/efuse/prod/pattern/stage-a.txt --dry-run --log-dir <dir> --foreground
Expect rc 0, every field zero / lock 00. AUDIO_VENDOR_ID lock 00 = PVT, 01 = EVT; both fine.
4.2 Park with a fresh -r 0 burn, then pre-checks
adnl -s <ser> oem "rpmb_state"
adnl -s <ser> oem "env_get rpmb_state" # response:1 (0 = STOP, never fuse)
adnl -s <ser> oem "amlmmc erase tee" # OKAY
4.3 Record the go, then the real run
Write one line before running: who gave the go, UTC, unit, ROM md5, script commit + sha256, dry-run rc. Then the same command without --dry-run --foreground. It detaches and prints its log path; wait for <log>.done (check at ≥ 60 s), read the log once. Expect rc=0; it ends with adnl reboot.
11-03: both hashes = abe6fe5d…, lock 01; every other field 0; both enables 00 (the unit is still open).
4.4 After stage A: cold boot + boot0/boot1 check
Cold power-cycle; Linux must show optisigns.sb=open, green, the right serial. Then check that eMMC boot0 and boot1 hold exactly the pack's bootloader. Our ROM's localuser cannot read /dev/mmcblk0boot*, so we did it from the ADNL park (no burn) with u-boot commands. u-boot has no sha256, so we used crc32:
# on the host: unpack the pack, take the bootloader item size and its crc32
~/flash/khadas-utils/aml_image_v2_packer -d <img> <dir> # bootloader.PARTITION
SECT = size/512 (hex) CRC = crc32(bootloader.PARTITION) # never assume the size
# on the parked unit (each line: adnl -s <ser> oem "…")
mmc dev 1 1 ; mmc read 0x1080000 1 <SECT> # boot0, from block 1
mmc dev 1 2 ; mmc read 0x1800000 1 <SECT> # boot1
mmc dev 1 0
crc32 0x1080000 <size> 0x3100000 # crc of boot0 stored at 0x3100000
crc32 0x1800000 <size> 0x3100010 # crc of boot1
mw.l 0x3200000 <CRC> ; mw.l 0x3200010 <CRC byte-swapped> ; mw.l 0x3200020 0x12345678 # control
cmp.l 0x3100000 0x3200000 1 ; cmp.l 0x3100000 0x3200010 1 ; cmp.l 0x3100000 0x3200020 1
cmp.l 0x3100010 0x3200000 1 ; cmp.l 0x3100010 0x3200010 1 ; cmp.l 0x3100010 0x3200020 1
cmp.b 0x1080000 0x1800000 <size> # boot0 == boot1, byte for byte
Pass = boot0 and boot1 each match one of the two byte orders (ours: the swapped form), the 0x12345678 control fails, and cmp.b is equal over the full size. Mismatch: stop, no stage B. Do this before stage B: after the enables, u-boot refuses mmc and printenv over ADNL.
5. Stage B: the two enables
- Re-park (§2, read-only park is enough; a fresh
-r 0burn is what the guide asks) and repeat the §4.2 pre-checks. - Latch probe:
adnl -s <ser> oem "setenv bootdelay -1"→OKAY.FAILED … H1bhere = stop, call Frank. - Dry run B:
rc 0; hashes = prod root, lockSB_ADNL=<adnl wrapper> bash amlogic/flash-host/sb-fuse-usb.sh --serial <ser> --stage B \ --expect-hash ota-trust/efuse/prod/keys/root/rsa/s905x5m/roothash/hash-device-rootcert-rsa.bin \ --pattern ota-trust/efuse/prod/pattern/pattern.txt --dry-run --log-dir <dir> --foreground01; enables00; it printswould write: FEAT_ENABLE_DEVICE_VENDOR_SIG FEAT_ENABLE_DEVICE_SCS_SIG. - Record the go, real run (no
--dry-run --foreground). It writesFEAT_ENABLE_DEVICE_VENDOR_SIG 01thenFEAT_ENABLE_DEVICE_SCS_SIG 01back to back, reads all back, ends withadnl reboot. Nobody touches cable or power until<log>.doneshowsrc=.
efuse_obj set by hand.6. After the fuse: checks we ran
| Check | How | Expected / seen 11-03 |
|---|---|---|
| First boot (warm) | after the script's adnl reboot | pairing screen in ~40 s |
| Cold boot | pull + replug USB-C; shell via USB debug | optisigns.sb=fused, verifiedbootstate=green, vbmeta.device_state=locked, slot _a, prd100046 |
| u-boot hardening (H1b) | park; oem "printenv bootcmd", oem "mmc dev 1 1" | FAILED … OptiSigns H1b: … not allowed when fused |
| eFuse read still allowed | oem "efuse_obj get FEAT_ENABLE_DEVICE_SCS_SIG" | 01 |
| Unsigned loader refused | adnl reboot romusb → [BL1]; tools/secure-boot/loader-only/sb-loader-only.sh --loader <DDR.USB of a dbg pack> --loader-sha256 <sha> --serial <ser> --expect refuse | PASS: Fail in first sect, unit stays [BL1] |
| Signed burn still works (positive control) | from [BL1]: adnl_burn_pkg -p <prod-signed pack> -e 1 -b 1 -r 1 -t 0 | burn successful, BL1 → BL2 → TPL, boots fused/green |
| Recovery on a fused unit | hold the button at power-on → recovery menu → Factory Reset | recovery image verified; system/boot/vbmeta restored from golden; new pairing code; boots fused/green |
On this station sb-loader-only.sh needed a burn wrapper as SB_LO_BURN because sudoers forbids sudo env; it passes the shim's variables to sudo explicitly:
#!/bin/bash
cd "$HOME/flash/aml-flash-tool/tools/adnl" || exit 2
V=(); for n in LUASHIM_REPLACE_DIR LUASHIM_DUMP_DIR LUASHIM_REQUIRE; do [ -n "${!n:-}" ] && V+=("$n=${!n}"); done
exec sudo -n "${V[@]}" LD_PRELOAD="$LUASHIM_SO" LD_LIBRARY_PATH=./usb_flow ./adnl_burn_pkg "$@"
7. Stop rules (from the guide, the ones that matter on the day)
- Fuse-script exit codes: 2 refused input · 3 pre-read refuses · 4 read-back mismatch · 5 link lost · 6 eFuse error reply · 7 session setup. Any non-zero: leave the unit parked, do not power-cycle, keep the log, decide with Frank.
getbefore everyset: zero → write; already the pattern value → skip (resume); anything else → stop.rpmb_statemust be 1 before stage A.- Only the prod root. The script refuses the dev, ODM-sample and SDK-template roots;
--expect-hashmust equal the pattern's hash. - Never
efuse_obj getthe DVGK / DVUK lines; the script only reads their locks.
8. Not covered on 11-03
- OTA on the fused unit (a prd
.swuthroughosupdate verify/apply): no prod-signed.swuwas available. - Golden read-back (guide §5.6.3: full stage-B dry run after the fuse): not run as a separate step; the stage-B log's own read-back is the record.
- BootROM FEAT line on UART: the stick had no UART wired during the run (wired later the same day).
9. Evidence to keep per unit
One record file per unit (template: guide §5.7) plus raw logs: catch/park logs, the pre-check log, each dry-run and real-run log with its .done, the boot0/boot1 crc log, post-fuse logs, and screenshots of each boot. Keep the unit's serials in that private record only, never in shared pages.