vRoT & secure boot
On this page
OpenSMA does not just boot an application processor (AP); it gates that boot on a cryptographic verdict. Two subsystems collaborate:
src/nv/vrot/— the virtual Root of Trust (vRoT). A thin, compile-time specialised interface that abstracts “which AP is this, and what physical operations hold, release, and flash it” behind a fixed set of platform operations.src/nv/secure_boot/— the secure-boot state machine. A dedicated task that drives one AP through hold reset -> authenticate -> release -> confirm boot, retries into a backup slot on failure, and records the outcome in non-volatile storage.
The actual signature and hash checking is performed by the SPDM crypto helper
(src/nv/spdm/); the secure-boot task orchestrates when it runs and what
happens to the verdict. See the Security & Root-of-Trust overview
for where this sits in the system, and Architecture
for the task model.
The vRoT interface
The public vRoT surface is a set of free functions in namespace nv::vrot,
declared in src/nv/vrot/interface/interface.h:50 (hold_reset,
pre_authenticate, post_authenticate, release_reset, check_booted).
Each takes an ApInfo describing the target AP and returns an ApOpErrCode
(src/nv/vrot/interface/types.h:40).
ApInfo (src/nv/vrot/interface/types.h:74) carries the AP identity and its
flash placement — id, type, component_id, fw_size, fw_offset, and
optional metadata_offset/metadata_size. ApType
(src/nv/vrot/interface/types.h:34) currently enumerates two backends:
Cpld and Lpu.
Compile-time dispatch by project
The set of APs a firmware image supports is a project constant,
nv::vrot::ApList. Each project’s config.h defines it; on the reference
projects it is an empty std::array<ApInfo, 0>
(src/projects/p3957_cxx/config.h:1327). The interface layer keys every
dispatch off that list at compile time:
constexpr bool HasAp = !ApList.empty();
constexpr bool HasCpld = has_ap_type(ApType::Cpld, ApList);
constexpr bool HasLpu = has_ap_type(ApType::Lpu, ApList);
(src/nv/vrot/interface/interface.cpp:29). Every op then switches on
ap.type guarded by if constexpr (HasXxx) and falls through to
ApOpErrCode::NotSupported — e.g. hold_reset at
src/nv/vrot/interface/interface.cpp:35. Interpretation: on an AP-less
build the whole vRoT/secure-boot chain compiles down to NotSupported
returns and the SecureBoot task never advances past initialisation, so the
feature carries no runtime cost when unused.
Platform Ops
Each ApType is backed by a *Ops struct. LpuOps
(src/nv/vrot/platform/lpu.h:118) and CpldOps
(src/nv/vrot/platform/cpld.h:28) implement the same static methods the
interface dispatches to. The bodies are deliberately small — they are the
physical half of secure boot. For the LPU backend:
hold_resetholds the LPU in reset (src/nv/vrot/platform/lpu.cpp:144).pre_authenticateclaims the shared SPI flash so the SMA can read the AP image (src/nv/vrot/platform/lpu.cpp:153).release_resetreleases the flash and then the reset line, letting the AP run (src/nv/vrot/platform/lpu.cpp:171).
The LPU flash is split into two equal slots, image first and a 4 KiB metadata
block at the end of each slot (src/nv/vrot/platform/lpu.h:98).
The SecureBoot state machine
The secure-boot logic lives in a FreeRTOS task. nv::secure_boot::Task
(src/nv/secure_boot/task.cpp:45) owns a single static SecureBoot instance
and its main() simply calls secure_boot.run()
(src/nv/secure_boot/task.cpp:76). A static_assert restricts the build to a
single AP with an id below MaxApCount
(src/nv/secure_boot/task.cpp:40, MaxApCount = 2 at
src/nv/secure_boot/secure_boot.h:31).
run() (src/nv/secure_boot/secure_boot.cpp:384) is an infinite loop over the
State enum (src/nv/secure_boot/secure_boot.h:33). The happy path and the
recovery path both flow through it:
stateDiagram-v2
direction TB
[*] --> Initialization
Initialization --> OnAuthenticate: hold_reset + pre_authenticate OK
Initialization --> AuthFailed: physical setup failed
OnAuthenticate --> AuthRequestPending: mark Auth_In_Progress
AuthRequestPending --> AuthPending: SPDM request sent
AuthRequestPending --> AuthFailed: send failed
AuthPending --> Release: CryptoStatus Success
AuthPending --> AuthFailed: bad signature or timeout
Release --> WaitBoot: post_authenticate + release_reset OK
Release --> BootFailed: physical release failed
WaitBoot --> BootComplete: check_booted OK
WaitBoot --> BootFailed: boot not observed
BootComplete --> Idle: record status, sync debug tokens
AuthFailed --> Recovery
BootFailed --> Recovery
Recovery --> Initialization: retry other slot
Recovery --> Fatal: retries exhausted
Fatal --> Idle
Idle --> Initialization: AP reset requested
Reading the states against the code:
- Initialization — calls
hold_resetthenpre_authenticate; either failure jumps straight toAuthFailedand, being pre-auth, leaves the persisted auth record untouched (src/nv/secure_boot/secure_boot.cpp:394). - AuthRequestPending — issues the authentication request to the SPDM
helper via
send_authenticate_firmware_request(ap_info, current_slot, ...)(src/nv/secure_boot/secure_boot.cpp:419). - AuthPending — waits on the
AuthResultReadyevent, matches the queued result to this attempt, and branches onCryptoStatus::Success(src/nv/secure_boot/secure_boot.cpp:431). A wall-clock guard,AuthTimeoutTicks= 120 s (src/nv/secure_boot/secure_boot.cpp:56), forces a failure if no verdict arrives (src/nv/secure_boot/secure_boot.cpp:454). - Release — only reached on success; runs
post_authenticatethenrelease_reset. A failure here is a boot failure, not an auth failure, and is tracked separately (src/nv/secure_boot/secure_boot.cpp:462). - BootComplete — records
Boot_Completestatus and callsnv::debugtoken::sync_debug_token_features_on_boot()exactly once (src/nv/secure_boot/secure_boot.cpp:488). - Recovery — flips
current_slotbetweenActiveSlotandUpdateSlotand retries; withMaxRecoveryRetries = 1(src/nv/secure_boot/secure_boot.h:101) each boot gets at most one retry into the other slot before goingFatal(src/nv/secure_boot/secure_boot.cpp:519).
The distinct Auth_Failed vs Boot_Failed statuses
(src/nv/fw_parser/fw_parser_ap.h:8) let a BMC or diagnostic tell a signature
rejection apart from a good image that simply failed to come up.
Result plumbing
Authentication runs on the SPDM task and reports back asynchronously. The SPDM
helper invokes SecureBoot::secure_boot_auth_callback
(src/nv/secure_boot/secure_boot.cpp:548), which — for a SecureBoot-originated
request — persists a successful verdict and then pushes a
SecureBootAuthResult onto a queue and sets the AuthResultReady event bit.
The FSM’s receive_auth_result (src/nv/secure_boot/secure_boot.cpp:254)
matches the queued ap_index, slot, and auth_request_id against the
in-flight attempt before accepting it, so a stale result cannot satisfy a new
request.
What is authenticated, and when
The heavy lifting is _authenticate_firmware_result in the SPDM crypto helper
(src/nv/spdm/spdm_crypto_helper.cpp:106), driven synchronously on the SPDM
task once SecureBoot requests it. For the selected slot it performs, in order:
- Read the 4 KiB metadata block for the slot via the vRoT
read_metadataop (src/nv/spdm/spdm_crypto_helper.cpp:122). - Pin the signing key. The metadata’s
verif_pub_keymust equal one of the two build-time public keysApFwPublicKeys[0/1]; otherwiseFailApPublicKeyMismatch(src/nv/spdm/spdm_crypto_helper.cpp:129). - Verify the metadata signature. SHA-384 over the to-be-signed portion
(
ApFwMetadata::TbsData), then ECDSA P-384 verify of the detachedr/ssignature (src/nv/spdm/spdm_crypto_helper.cpp:149). - Enforce anti-rollback. The on-device secure FW version (from CFPA) must
not exceed the image’s
sec_version, elseFailApRollbackProtection(src/nv/spdm/spdm_crypto_helper.cpp:166). - Enforce key revocation, with a debug-token bypass path: if prod-key
level is required but the image is debug-signed, a valid flash-debug token
for that AP is consulted (
src/nv/spdm/spdm_crypto_helper.cpp:198). - Verify every sub-image. For each of
ap_fw_images_countentries it streams the sub-image through SHA-384 and compares against the metadata hash table; any mismatch isFailApImageHashMismatch(src/nv/spdm/spdm_crypto_helper.cpp:224).
So the trust chain is: two pinned public keys -> a signed metadata block ->
per-sub-image SHA-384 hashes listed inside that signed metadata. Only after
all of these pass does the callback return Success and SecureBoot advance to
Release. The metadata layout that anchors this — ApFwMetadata (4096 bytes,
src/nv/fw_parser/fw_parser_ap.h:86), the MetadataHashTableEntry SHA-384
table (src/nv/fw_parser/fw_parser_ap.h:74), the verif_pub_key field
(src/nv/fw_parser/fw_parser_ap.h:118), and the ECDSA P-384 NvSignature
(src/nv/fw_parser/fw_parser_ap.h:39) — is described in
Crypto & firmware parsing.
Interpretation. The code establishes image integrity and authenticity against keys baked into the SMA firmware, plus rollback and key-revocation policy. It does not by itself establish confidentiality of the AP image, and the physical
check_bootedstep for the LPU backend currently just returnsSuccess(src/nv/vrot/platform/lpu.cpp:181) — “booted” is not independently attested there. Do not read more into it than the checks above.
Persisting the verdict
Results are written to non-volatile NPDS storage as an AuthenticateData
record (src/nv/secure_boot/authenticate_data.h:13): the metadata TBS data
plus the CryptoStatus, defaulted to FailUnknown so an un-provisioned slot
never reads as authenticated (src/nv/secure_boot/authenticate_data.h:19).
persist_authenticate_result (src/nv/secure_boot/secure_boot.cpp:216) routes
to the right flash key by slot. A subtle policy lives in
persist_authenticate_data (src/nv/secure_boot/secure_boot.cpp:179): when the
Active slot authenticates, its record is also seeded into the Update
slot — but only if Update has no prior successful record and PLDM is not
mid-stage, so a good staged update is never clobbered.
Downstream consumers read this record rather than re-hashing the image. The AP
firmware parser’s get_ap_metadata_data_from_flash
(src/nv/fw_parser/fw_parser_ap.cpp:17) returns metadata only when the stored
ap_auth_result is Success or ApAuthInProgress
(src/nv/fw_parser/fw_parser_ap.cpp:34) — the persisted verdict is the gate.
AP background copy
src/nv/vrot/ap_background_copy.cpp mirrors one flash slot into the other,
chunk by chunk, so an authenticated image can be duplicated to the backup slot
without blocking. It is a small state machine (Idle/Pending/InProgress/
Done/Failed, src/nv/vrot/ap_background_copy.cpp:38) serviced
incrementally:
startvalidates the source/dest pair and arms the copy (src/nv/vrot/ap_background_copy.cpp:275).serviceadvances oneChunkSize= 256-byte chunk per call (src/nv/vrot/ap_background_copy.cpp:34,:312) vianext_chunk(src/nv/vrot/ap_background_copy.cpp:206).- Each chunk reads through
read_partitionand writes throughwrite_partition, which transparently split a chunk that straddles the image/metadata boundary atap.metadata_offset(src/nv/vrot/ap_background_copy.cpp:155). - After every chunk it releases the underlying resource and yields for
ReleasedWindowUs(src/nv/vrot/ap_background_copy.cpp:261) so higher priority work is not starved.
The interface exposes the copy behind supports_ap_background_copy /
background_copy_begin / background_copy_end
(src/nv/vrot/interface/interface.h:108), again dispatched only for backends
that opt in — CPLD does not (src/nv/vrot/interface/interface.cpp:326).
How the boot chain establishes trust
Putting the pieces together (mostly fact, some interpretation where noted):
- The SMA firmware ships with two trusted AP public keys and boots first; it is the root that decides whether the AP may run.
- SecureBoot holds the AP in reset and claims its flash before any
verification, so the AP cannot execute unverified code
(
src/nv/secure_boot/secure_boot.cpp:394). - The SPDM helper verifies the signed metadata and every sub-image hash
against the pinned keys, applying rollback and revocation policy
(
src/nv/spdm/spdm_crypto_helper.cpp:106). - Only a
Successverdict releases reset (src/nv/secure_boot/secure_boot.cpp:462); a failure retries the other slot once, then latchesFatal. - The verdict is persisted and becomes the gate for every later metadata read
(
src/nv/fw_parser/fw_parser_ap.cpp:34).
Interpretation: trust is anchored in the SMA’s own signed firmware and its built-in key set; the AP inherits trust transitively only after a full metadata-signature + per-image-hash check. The scheme protects against tampered or rolled-back AP images, not against a compromised SMA root itself.
Where to go next
- Crypto & firmware parsing — the AES-GCM/key-wrap primitives, debug-token TLVs, and the image parsers that feed this chain.
- Security & Root-of-Trust overview — section map.
- The SPDM messaging layer that carries the authentication request is covered
under the protocols section (SPDM); the crypto
helper entry point is
send_authenticate_firmware_request(src/nv/spdm/spdm_crypto_helper.h:233). - Build system — how
ApListand per-projectconfig.hselect which APs (and therefore which vRoT backends) compile in.