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.
In general, the Fuse API gives the SoC an easy way to read and write fuses with bit granularity via MCTP or mailbox commands.
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 the may only have 4 bits backed by actual fuses in hardware. Our additional fuse map contains a section to define how many bits are available in each field as this information is vendor-specific and hence 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}
],
// TBD how we allow additional fuses outside of these areas, if this is allowed by OTP
other_fuses: {},
// 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 script
We will provide a script, cargo xtask fuse-autogen
, that can be used to process an .hjson
file into:
- Firmware definitions and code for the fuses and bits (Rust)
- Documentation on the fuses and bits (Markdown)
The command will use either a --platform
flag to indicate the file is platforms/{platform}/fuse_bits.hjson
or a --file
to specify the file manually.
This script can optionally generate extra commands for programming specific fuses as well as Rust and C code to access them.
Fuse Provisioning Commands
The fuse provisioning commands can be used over our generic command interface through MCTP, mailboxes, etc., or through commands in firmware manifest (except read operations).
The commands are:
- Write
- Read
- Lock partition
Authorization
Only authorized SoC users shall call these commands. This can be authorized through the mechanism itself (i.e., a mailbox that only a trusted SoC component has access to) or cryptographically.
Some example cryptographic mechanisms that could be used:
- A certificate provided to MCU during boot, which will be used to validate signatures on each message.
- An HMAC key imported into Caliptra's cryptographic mailbox during boot, which MCU can use to validate a request. (The key could itself be stored as fuses.)
It will be integrator-defined how which authorization mechanism is required, but we will provide reference implementations with stubs for source-based and HMAC-based authorization.
These commands reference partition, fuse, and bit numbers that must use the same numbers generated from the Fuse definition script.
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.hjson
andvendor_fuses.hjson
files.
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.
Start bit is counting from the least significant bit.
Command Code: 0x4946_5057
("IFPW")
Table: MC_FUSE_WRITE
input arguments
Name | Type | Description |
---|---|---|
chksum | u32 | |
partition | u32 | Partition number to write to |
entry | u32 | Entry to write |
start bit | u32 | Starting bit to write to (least significant bit in entry is 0). |
length | u32 | in bits |
data | u8[...] | length/8 |
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. Existing data that is 0 but set to 1 will be burned to a 1.
- Writes to buffered partitions will not take effect until the next reset.
MC_FUSE_LOCK_PARTITON
Lock a partition.
Command Code: 0x4946_504B
("IFPK")
Table: MC_FUSE_WRITE
input arguments
Name | Type | Description |
---|---|---|
chksum | u32 | |
partition | u32 | Partition number to lock |
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 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.
Field Entropy Provisioning
Field entropy fuses can only be burned by Caliptra runtime, so the flow for provisioning them is different and any changes in them will invalidate any OCP Device Ownership Transfer blob stored on the device. See the MCU OCP DOT implementation spec.
This will be an MCU mailbox API command (or other signal to MCU runtime) to trigger that MCU should use the Caliptra mailbox PROGRAM_FIELD_ENTROPY command. If a mailbox command is used, it must be authorized in the same way as other fuse programming API commands (see authorization)
If OCP DOT is being used, then the MCU ROM must also update the DOT blob after Caliptra core burns field entropy. Failure to do so may "brick" a device or require a DOT recovery flow, as the ownership information could be corrupted after provisioning field entropy if the DOT root key is derived from the LDevID CDI.
The flow will be:
- MCU runtime receives a MCU_PROGRAM_FIELD_ENTROPY command through a mailbox or other signal.
- If DOT is enabled and the DOT root key is derived from LDevID, we need to store the DOT blob (and re-sign it after FE is updated):
- MCU runtime stores the current DOT blob in non-volatile storage, e.g., SRAM that won't be cleared on reset
- MCU runtime verifies this DOT blob using the current DOT root key.
- MCU runtime indicates that the DOT blob must be checked and re-derived by setting a register or other signal in non-volatile storage, for example, a generic output wire or SRAM.
- Sign the DOT blob copy with a new special DOT root key derived from IDevID.
- MCU runtime sends to Caliptra's runtime mailbox the PROGRAM_FIELD_ENTROPY command.
- MCU runtime waits for a successful response.
- MCU runtime initiates a reset.
- If DOT is enabled and the DOT root key is derived from LDevID
- MCU ROM checks if we are in a FE programming mode (i.e., from register, input wires, or SRAM contents)
- MCU derives both special DOT root key (from IDevID) and the normal DOT root key (from LDevID)
- MCU ROM verifies the DOT blob copy in non-volatile storage against the special IDevID DOT root key.
- MCU ROM copies the DOT blob copy into the standard DOT blob location
- MCU ROM asks Caliptra core signs the DOT blob with the current DOT root key.
- Continue with the normal boot flow
sequenceDiagram Participant SoC Note over MCU,Caliptra: Runtime SoC->>MCU: MCU_PROGRAM_FIELD_ENTROPY alt DOT enabled and LDevID DOT key used MCU->>Caliptra: Derive special DOT key MCU->>Caliptra: HMAC DOT blob with special DOT key end MCU->>Caliptra: PROGRAM_FIELD_ENTROPY Caliptra->>OTP: (Burn) Caliptra->>MCU: Done MCU->>SoC: Reset subsystem Note over MCU,Caliptra: ROM alt DOT enabled and LDevID DOT key used MCU->>Caliptra: Derive DOT normal and special key MCU->>Caliptra: HMAC DOT blob with special key MCU->>MCU: verify HMAC MCU->>Caliptra: HMAC DOT blob with normal key end