tan flash's alif_mram_jlink backend ("Flow D": J-Link straight over SWD,
no SE-UART) burns a signed ATOC into an Alif Ensemble part's on-die MRAM.
Producing that signature is Alif's own job, done by the Alif Security Toolkit
(SETOOLS) app-gen-toc step — tan does not sign anything itself; it drives
app-gen-toc for you when it can find it, and refuses loudly, naming exactly
what it tried, when it cannot (tan-cli#365).
SETOOLS is license-gated and obtained directly from Alif. Neither tan
nor alp-sdk redistributes it. Get it from the Alif developer portal under
your own Alif account, then point tan at the directory you installed it
into — the sections below cover how.
Two shapes matter, depending on host OS:
- Linux bundle:
app-release-exec-linux-SE_FW_x.y.z— the one executabletanlooks for inside it isapp-gen-toc(west flash's ownalif_flashrunner, for the SE-UART path, looks forapp-write-mramseparately; Flow D here never does). Runningapp-gen-tocwritesapp-package-map.txt, its own build report — not another executable, and not somethingtansearches for the way it searches for the tool. - Windows: a genuine Windows SETOOLS install ships
app-gen-toc.exeinstead of the bare Linux name;tanlooks for both.
tan flash accepts three ways to say where SETOOLS lives. Highest
precedence wins outright — a lower source is never consulted once a higher
one resolves:
--setools-dir <path>— a flag ontan flashitself. The one durable, discoverable-from---helpway to pin this per invocation, regardless of shell session or manifest state.SETOOLS_DIR=<path>— an environment variable. Survives acrosstan buildruns (unlike the manifest field below), but is scoped to whatever shell/session set it.flash_args.setools_dirinbuild/system-manifest.yaml— lowest precedence, and not durable:tan buildregenerates this file on every run (python/tan/commands/build/manifest.py), and alp-sdk's own emit carries nosetools_dirkey at all. A hand-edit here is silently overwritten by your next build. Prefer the flag or the environment variable for anything you want to survive a rebuild; treat this field as build-owned, not a place to hand-author a durable setting.
If none of the three resolves, tan flash refuses with a message naming all
three sources, in this same order, and how to set each one — it never
searches the filesystem for a plausible SETOOLS install: a wrong SETOOLS
silently signing against the wrong part is worse than tan refusing outright.
When a Flow D entry has no atoc/atoc_address yet (an AEN801 slot0 slice's
manifest today typically carries only jlink_flash_device and
slot0_load_address — alp-sdk's emit does not sign anything itself),
tan flash drives one app-gen-toc sign step for you:
- copies the build's raw
.bininto<SETOOLS_DIR>/build/images/; - writes an app-only ATOC config to
<SETOOLS_DIR>/build/config/— no"DEVICE"key: the on-module factory device config is already correct for your part, and this step must not overwrite it; - runs
app-gen-toc, insideSETOOLS_DIR, against that config; - reads the resulting ATOC's MRAM placement back out of
<SETOOLS_DIR>/build/app-package-map.txt. This file is APPEND-mode — the accumulated sign record for the whole install, including hand-runs you did outsidetan— sotannever truncates or deletes it (tan-cli#373): it records the file's size and mtime beforehand and refuses if either is unchanged after a zero exit (a soft failure that would otherwise read back a stale, unrelated address as if it were fresh), and separately confirms<SETOOLS_DIR>/build/AppTocPackage.bin(which — unlike the map — IS overwritten whole every run, so there is no history in it to protect) was actually rewritten before trusting either.
A successful sign names which SETOOLS install did it (--setools-dir,
SETOOLS_DIR, or flash_args.setools_dir — see setools.source in tan flash's own output), not only a failed one.
Under --dry-run none of this touches your SETOOLS install or spawns
app-gen-toc at all — tan flash --dry-run prints what it would sign and
stops there.
If you already resolved a signature yourself — an explicit flash_args.atoc
flash_args.atoc_address, orflash_args.atoc_mappointing at your ownapp-package-map.txt— none of the above runs;tanuses what you gave it verbatim.
On a bench carrying more than one J-Link, flash_args.jlink_serial picks a
probe by serial only — JLinkExe has no USB-port selector. Some OEM J-Link
probes ship with a cloned serial number shared across more than one
physical unit, in which case jlink_serial alone cannot tell two probes
apart, even when set: a wrong-board write is now possible even with a serial
pinned. flash_args.expect_dpidr (paired with flash_args.jlink_device) is
the real per-silicon discriminator for this case — tan reads it back on
connect, before ever writing MRAM, and refuses when it doesn't match. Set
both when your bench has more than one probe, or when a shared/cloned serial
is a possibility; do not rely on jlink_serial alone to disambiguate.
The swd_probe backend (the GD32G553 supervisor bridge, not this doc's Flow
D) gets the identical read-only preflight, but flash_args.expect_dpidr
arms it alone there — swd_probe's own flash_args.jlink_device already
names the write's own -device profile, so it is not a second, preflight-only
field the way Flow D's is. Set flash_args.expect_dpidr on a swd_probe
entry whenever the same cloned-serial risk applies (tan-cli#520).
flash_args.expect_dpidr is optional, so a write with none set proceeds
unguarded. Since tan-cli#609 the flash.dpidr-preflight-unarmed warning covers
every method tan itself composes a J-Link Commander session for — today
swd_probe's J-Link arm AND Flow D (alif_mram_jlink) — rather than
swd_probe alone. The coverage is a table (DPIDR_GUARD_COVERAGE) pinned to
the backend registry by a gate, so a new backend has to declare which side it
is on instead of inheriting silence.
It reached only swd_probe before, and that was measured, not theoretical: a
real AEN MRAM write through tan flash on 2026-08-10 emitted ISSUES = [] —
no wrong-board guard and no signal that there was none — on a bench where one
J-Link serial is cloned across two probes.
What each path emits:
- Flow D (
alif_mram_jlink) — raises the warning. The remedy names BOTH keys, because Flow D pairsexpect_dpidrwithflash_args.jlink_device(the live-core attach profile, notjlink_flash_device). swd_probe, J-Link arm — raises the warning;expect_dpidralone is the remedy there.swd_probe, openocd/pyocd arm — raises nothing at all, by design: the SW-DP ID read is a JLinkExe-only primitive, so there is no guard to be unarmed and "setexpect_dpidr" would be advice that cannot be taken (plan_swd_proberefuses that key on this arm at plan time). This is the arm the shippedE1M-V2N101/V2N102/V2M101/V2M102flash_argsselect on a host with no J-Link, i.e. the default path a bricked-bridge recovery takes today. The switch below still refuses it.- Every other method (
zephyr_west_flash,baremetal_cmake_flash,yocto_wic*,xspi_flashwriter) — raises nothing, becausetancomposes no probe session there forexpect_dpidrto arm. That is not a safety claim about those methods;west flash's own runner, for one, may well drive a J-Link, andtanhas no view into how it selects a probe.
An unattended bench reads no warnings, and the openocd/pyocd arm emits none to
read, so tan flash also honours an env switch: with
ALP_FLASH_REQUIRE_DPIDR=1 exported, a real write whose DPIDR preflight
would not run fails the entry before anything is spawned
(flash.entry-failed) instead of proceeding. Unset — the default — nothing
changes.
Its scope is the same table as the advisory (tan-cli#609): swd_probe and
Flow D. It was swd_probe-only when tan-cli#589 shipped it, which left the AEN
MRAM path — the genuine customer flash path of the two, the GD32 bridge being
factory-programmed by Alp Lab — outside both halves of the guard. On Flow D the
refusal fires ahead of the SETOOLS auto-sign, not merely ahead of the write:
app-gen-toc rewrites build/app-package-map.txt rather than appending, and
tan-cli#512 measured a wrong-board abort that correctly left slot0
byte-identical and still left the SETOOLS install mutated.
The policy belongs to the host, not to the manifest. Export it on a factory or
bench machine, where a wrong-board write is expensive and nobody is watching;
leave it unset on a customer machine, where a bricked-bridge recovery must not
be blocked by a metadata field alp-sdk has not populated yet. It is read as the
exact string 1, the same as ALP_FLASH_FORCE.
Two things it does not do: it does not apply to --dry-run (a preview
writes nothing), and it does not make expect_dpidr mandatory in metadata. No
shipped alp-sdk preset carries a SW-DP ID today, and tan is forbidden from
deriving one — until metadata populates the field, exporting this variable
refuses these writes rather than guarding them.
A swd_probe entry taking the openocd/pyocd arm refuses under this switch
unconditionally — the SW-DP ID read is a JLinkExe-only primitive, so that arm
cannot be armed at all. openocd_usb_location is not a substitute: a USB path
selects a probe, it never confirms which board is on the other end of the
SWD cable (tan-cli#589).
docs/adr/— architecture decisions this backend follows (no new hardware fact invented intan; every identifier above comes fromflash_args, which alp-sdk'smetadata/**populates).- tan-cli#353, #365, #366, #367, #368, #369, #373 — the issues this doc and the surrounding fixes answer.
- tan-cli#520, #589, #609 — the wrong-board SW-DP ID guard: the preflight itself, the opt-in strict switch, and making both method-independent.