e37dd9d
In-Field Fuse Programming (IFP) Specification
MCU has the OpenTitan fuse controller (OTP) and Lifecycle controller (LC), which sit on top of a process-specific fuse macro and manages them with a uniform interface.
The hardware has provisioned the OTP controller with an overall structure that has Caliptra- and general Subsystem-specific blocks, which are required, and vendor-defined blocks.
As part of the Fuse API, we support defining the vendor-specific fuses and assist in provisioning the Caliptra- and vendor-specific fuses.
The Runtime fuse API provides authorized MCU mailbox operations for reading and writing OTP and transport-neutral commands for supported provisioning and lock operations.
Fuse Defining
We provide a new fuse definition hjson file format to assist in further defining fuses.
There are two places where we expand on the standard OTP memory map:
- Defining how many bits are backed by actual fuses in each field
- Defining vendor-specific fields
For an example of (1), the CPTRA_SS_OWNER_ECC_REVOCATION is specified as 1 byte (8 bits) in the memory map, but may have only 4 bits backed by actual fuses in hardware. The additional fuse map defines how many bits are available in each field because this information is vendor-specific and is not present in the standard memory map.
For (2), there are two defined partitions, VENDOR_SECRET_PROD_PARTITION (520 bits) and VENDOR_NON_SECRET_PROD_PARTITION (984 bits) in the standard memory map. In addition, the vendor can choose to provide additional fuses. We provide a way to split these up into specific areas and automatically generate Rust code to manage them.
Fuse definition file
Here is an example fuse definition file, for example, vendor_fuses.hjson:
{
// vendor-specific secret fuses
secret_vendor: [
{"example_key1": 48}, // size in bytes
{"example_key2": 48}, // size in bytes
{"example_key3": 48}, // size in bytes
{"example_key4": 48}, // size in bytes
],
// vendor-specific non-secret-fuses
non_secret_vendor: [
{"example_key_revocation": 1}
],
// entries to define how many bits are in each field, and potentially other information
fields: [
// set specifics on Subsystem fuses
// By default, all bits in each field are assumed to be backed by actual fuse bits.
// Names should be globally unique
{name: "CPTRA_SS_OWNER_ECC_REVOCATION", bits: 4}, // size in bits
// set specifics on vendor-specific fuses
{name: "example_key_revocation", bits: 4},
]
}
By default, all bits in each field are assumed to be backed by actual fuse bits unless they have an entry in the fields array.
Fuse definition generation
The implemented generator runs as:
cargo xtask registers-autogen --fuses-hjson <path> --otp-mmap-hjson <path>
Both path options are optional and default to hw/fuses.hjson and the
Caliptra subsystem OTP memory map. The command generates:
- Rust fuse definitions in the selected generated-register output
- The corresponding
fuse_map.md
Fuse Provisioning Commands
MCU Runtime implements generic fuse read, write, and partition-lock operations through the MCI mailbox. Supported provisioning and lock operations are also exposed through the common SPDM VDM command interface.
The commands are:
- Write
- Read
- Lock partition
Authorization
The reference Runtime routes these operations through its generic authorized
command handler. The requester first obtains a one-use MACC challenge and
then supplies the configured ECC P-384 and ML-DSA-87 public keys plus hybrid
signatures over the command payload and challenge. See
Authorization-Gated Subcommand Wrapper.
These commands reference partition, fuse, and bit numbers that must use the same numbers generated by Fuse definition generation.
Limitations
- Reading is not allowed for secret partitions. The secret partitions can only be read via integrator-specific hardware (or by Caliptra for its secret partitions).
- Writing and locking are idempotent.
- Once a partition is locked, it cannot be written to again.
- u32s are represented in little-endian byte order
- Bytes are expected to be implemented in fuses in standard (big-endian) bit order, i.e., 76543210.
- If a byte is only partially filled with bits, then the high-order bits will be 0.
- Partitions, fields, and bits are specified in the particular platform's
otp_mmap.hjsonandvendor_fuses.hjsonfiles.
MC_FUSE_READ
Reads fuse values.
Command Code: 0x4946_5052 ("IFPR")
Table: MC_FUSE_READ input arguments
| Name | Type | Description |
|---|---|---|
| chksum | u32 | |
| partition | u32 | Partition number to read from |
| entry | u32 | Entry to read |
Table: MC_FUSE_READ output arguments
| Name | Type | Description |
|---|---|---|
| chksum | u32 | |
| fips_status | u32 | FIPS approved or an error |
| length (bits) | u32 | Number of bits that are valid |
| data | u8[...] | Fuse data (length/8) |
MC_FUSE_WRITE
Write fuse values.
Command Code: 0x4946_5057 ("IFPW")
Table: MC_FUSE_WRITE input arguments
| Name | Type | Description |
|---|---|---|
| chksum | u32 | |
| word_addr | u32 | Entry to write (word offset) |
| data | u32 | Word to write |
| mask | u32 | Bit-Mask to only write specified bits |
Table: MC_FUSE_WRITE output arguments
| Name | Type | Description |
|---|---|---|
| chksum | u32 | |
| fips_status | u32 | FIPS approved or an error |
Caveats:
- This command is idempotent, so that identical writes will have no effect.
- Will fail if any of the existing data is 1 but is set to 0 in the input data.
- Bits cleared in
maskare ignored - Writes to buffered partitions will not take effect until the next reset.
MC_FUSE_LOCK_PARTITION
Lock a partition.
Command Code: 0x4946_504B ("IFPK")
Table: MC_FUSE_LOCK_PARTITION input arguments
| Name | Type | Description |
|---|---|---|
| chksum | u32 | |
| partition | u32 | Partition number to lock |
Table: MC_FUSE_LOCK_PARTITION output arguments
| Name | Type | Description |
|---|---|---|
| chksum | u32 | |
| fips_status | u32 | FIPS approved or an error |
Caveats:
- This command is idempotent, so that locking a partition twice has no effect.
- Locking a partition causes subsequent writes to it to fail.
- Locking does not fully take effect until the next reset.
MC_PROVISION_VENDOR_PK_HASH
Provision a new vendor PK hash.
Command Code: 0x5056_504b ("PVPK")
Table: MC_PROVISION_VENDOR_PK_HASH input arguments
| Name | Type | Description |
|---|---|---|
| chksum | u32 | |
| slot | u32 | The vendor PK hash slot to use |
| hash | [u8; 48] | New vendor PK hash |
Table: MC_PROVISION_VENDOR_PK_HASH output arguments
| Name | Type | Description |
|---|---|---|
| chksum | u32 | |
| fips_status | u32 | FIPS approved or an error |
Caveats:
- Fails if the slot already contains data
MC_PROVISION_OWNER_PK_HASH
Provision CPTRA_SS_OWNER_PK_HASH using its 48-byte dword-reversed OTP
representation.
Command Code: 0x504F_504B ("POPK")
Table: MC_PROVISION_OWNER_PK_HASH input arguments
| Name | Type | Description |
|---|---|---|
| chksum | u32 | |
| hash | [u8; 48] | New owner public-key hash |
Table: MC_PROVISION_OWNER_PK_HASH output arguments
| Name | Type | Description |
|---|---|---|
| chksum | u32 | |
| fips_status | u32 | FIPS approved or an error |
Caveats:
- The all-zero hash is rejected.
- Reprovisioning the identical hash is idempotent.
- Provisioning a different hash after any owner-hash data has been burned is rejected.
- After verifying the hash, the command burns and verifies bit 0 of
CPTRA_SS_OWNER_PK_HASH_VALIDfor use as a commit marker by ROM versions that enforce it. - Current MCU ROM does not check
CPTRA_SS_OWNER_PK_HASH_VALIDbefore consuming the hash. Provisioning must not be interrupted by reset or power loss; an interruption can cause ROM to consume a partial hash and prevent retrying the intended hash. - The newly provisioned owner hash is consumed by MCU ROM on the next reset.
MC_FUSE_REVOKE_VENDOR_PUB_KEY
Revoke one vendor firmware verification key within a vendor PK hash slot.
Command Code: 0x4D52_564B ("MRVK")
Table: MC_FUSE_REVOKE_VENDOR_PUB_KEY input arguments
| Name | Type | Description |
|---|---|---|
| chksum | u32 | |
| reserved | u32 | Reserved; must be zero |
| vendor_pk_hash_slot | u32 | Vendor PK hash slot containing the key to revoke |
| key_type | u32 | 0 = ECDSA P-384, 1 = LMS, 2 = MLDSA-87 |
| key_index | u32 | Key index within the selected key type's revocation field |
Table: MC_FUSE_REVOKE_VENDOR_PUB_KEY output arguments
| Name | Type | Description |
|---|---|---|
| chksum | u32 | |
| fips_status | u32 | FIPS approved or an error |
Caveats:
- This command must be authorized.
- The selected PK hash slot must be provisioned and valid.
- The command fails if it targets the key used to boot the currently running firmware.
- The last key index for a key type cannot be revoked.
MC_FUSE_REVOKE_VENDOR_PK_HASH
Revoke a vendor PK hash. Marks a vendor PK hash as invalid, revoking all of the associated keys.
Command Code: 0x5256_4b48 ("RVKH")
Table: MC_FUSE_REVOKE_VENDOR_PK_HASH input arguments
| Name | Type | Description |
|---|---|---|
| chksum | u32 | |
| vendor_pk_hash_slot | u32 | Vendor PK hash slot to revoke |
Table: MC_FUSE_REVOKE_VENDOR_PK_HASH output arguments
| Name | Type | Description |
|---|---|---|
| chksum | u32 | |
| fips_status | u32 | FIPS approved or an error |
Caveats:
- This command must be authorized.
- This command is idempotent, so that revoking a slot twice has no effect.
- Trying to revoke an empty slot will result in an error
- Trying to revoke the PK hash slot used to boot the currently running firmware will result in an error
Field Entropy Provisioning
Field entropy can only be programmed by Caliptra. MCU Runtime exposes the
authorized MC_FE_PROG (MCFP) command, which accepts one partition index and
forwards Caliptra's FE_PROG mailbox command. MCU ROM can also program the
partitions selected by RomParameters::program_field_entropy during cold boot;
that path records Started and Finished state in FIELD_ENTROPY_STATE and
rejects partial or zeroized slots.
The reference DOT configuration derives its effective key from the stable
IDevID root, so Field Entropy programming does not invalidate the DOT_BLOB.
RomParameters::dot_stable_key_type can select LDevID for ROM paths, but the
reference Runtime always derives DOT keys from IDevID and no implementation
re-HMACs an existing DOT_BLOB when Field Entropy changes. The LDevID override is
therefore not compatible with the reference Runtime DOT commands or an existing
IDevID-sealed blob without corresponding platform code changes.
Fuse Layout Options
The firmware uses a FuseLayout enum to define how fuse values are stored and interpreted. This provides flexibility for different encoding schemes including redundancy and error tolerance. The layout policy can be customized per platform via McuFuseLayoutPolicy.
Note that u32s (dwords) are always packed in little-endian byte order and big-endian bit order.
Terminology note: the current OneHot* layout names are existing schema/API
names. They do not mean true one-hot encoding, where exactly one bit is set.
They implement monotonic bit-count, or thermometer-style, counters: the logical
value is the number of set bits, and writers encode value n as (1 << n) - 1.
Layout Types
Single
Values are stored literally without any encoding.
Values stored this way in fuses should be protected with ECC.
Example: A 4-bit value 0b1101 is stored as 0b1101
Bit-count counter (OneHot layout name)
The logical value is the count of bits set to 1 in the fuse field. This is commonly used for version numbers and counters where incrementing requires only burning one additional fuse bit.
The values can span multiple u32 words but the result is always a single u32 count value.
Example:
0b0000→ 00b0001→ 10b0011→ 20b0111→ 3
Caution: This method of storing fuses is dangerous, since generally ECC cannot be used if the value can be incremented after the initial value is written.
LinearMajorityVote
Each logical bit is duplicated multiple times within a single u32 (or across adjacent u32s). The final value uses majority voting for each bit position to provide error tolerance.
Duplication is limited to < 32x and should be odd.
Example: With 2-bit logical value and 3x duplication:
- Raw fuses:
0b100_110_111(bit 0 has votes111, bit 1 has votes110, bit 2 has votes100) - Result:
0b011
Generally this method is used for values that do not have ECC protection but are only written once, usually that only are a few bits in size or less.
OneHotLinearMajorityVote
Combines LinearMajorityVote with the OneHot bit-count counter layout.
First applies majority voting to each duplicated bit, then counts the number of
bits set.
This combines redundancy with bit-count counter semantics.
Example: With 3 logical bits and 3x duplication:
- Raw fuses:
0b100_110_111(bit 0 has votes111, bit 1 has votes110, bit 2 has votes100) - After majority vote on each bit:
0b011(2 bits set) - Result: 2
Generally this method is used for counters and version numbers that can be updated in production and therefore do not have ECC protection.
WordMajorityVote
Entire u32 words are duplicated. Each bit position across the duplicated words is decided by majority vote.
Example: With 3 duplicated words:
- Raw fuses:
[0b100, 0b110, 0b111] - Bit 0: votes are
0,0,1→ majority is0 - Bit 1: votes are
0,1,1→ majority is1 - Bit 2: votes are
1,1,1→ majority is1 - Result:
[0b110]
This is an alternative to LinearMajorityVote for larger values.
LinearOr
Like LinearMajorityVote, each logical bit is duplicated multiple times. However, the result bit is 1 if any copy is set (bitwise OR instead of majority vote). This is appropriate for OTP bits where a bit failing to flip to 1 is more likely than spontaneously flipping to 0.
Example: With 2-bit logical value and 3x duplication:
- Raw fuses:
0b000_001_111(bit 0 has copies111, bit 1 has copies001) - Result:
0b11(both bits have at least one copy set)
With majority vote the same raw value would yield 0b01 (bit 1 fails the ≥2 threshold).
OneHotLinearOr
Combines LinearOr with the OneHot bit-count counter layout. First applies
OR reduction to each duplicated bit, then counts the number of bits set.
Example: With 3 logical bits and 3x duplication:
- Raw fuses:
0b001_010_111(bit 0 has copies111, bit 1 has copies010, bit 2 has copies001) - After OR on each bit:
0b111(3 bits set) - Result: 3
This is the recommended way to store incrementable counters and version numbers in OTP without ECC.
WordOr
Entire u32 words are duplicated. Each bit position across the duplicated words is decided by OR (any word having the bit set → result bit is 1).
Example: With 3 duplicated words:
- Raw fuses:
[0b100, 0b010, 0b001] - Result:
[0b111]
Common Limitations
- Maximum result size for single value extraction is 32 bits
- For multi-word extraction, the result array size must match the expected output
- All layouts return
McuError::ROM_FUSE_LAYOUT_TOO_LARGEif constraints are violated - Unsupported or invalid configurations return
McuError::ROM_UNSUPPORTED_FUSE_LAYOUT - Majority vote calculations use ceiling division (e.g., need ≥2 votes for 3 duplicates)
- OR calculations treat any non-zero duplicate as a 1
Default Platform Configuration
The default McuFuseLayoutPolicy uses:
Single: For certificate data, identifiers, and validity flags that are assumed to be protected by ECCOneHotLinearOr(3x): For SVN fields and monotonic bit-count countersLinearOr(3x): for single revocation fields, flags, and small bitmasksWordMajorityVote(3x): for larger bitmasks
The SDK Owner SoC Manifest counter uses one OTP bit per SVN increment, without redundant copies. See Owner SoC Manifest SVN for storage and activation.
This provides a balance between redundancy for critical security fields and storage efficiency for larger data structures.