Hafnium Secure Partition Manager#
Applies to the Jetson AGX Thor series.
Hafnium is an open-source secure partition manager (SPM) implementation based on the Arm Firmware Framework for Arm A-profile (FF-A) specification. It runs at S-EL2 and is responsible for managing secure partitions in the secure world.
Key features:
Supports FF-A v1.0 and v1.1 specifications.
Provides isolated execution environments for secure partitions.
Supports memory sharing and direct message passing.
Implements interrupt management and power management.
Supports S-EL1 and S-EL0 partitions.
Architecture Components#
A system with Hafnium generally contains the following components:
Secure Partition Manager Core (SPMC): Runs at S-EL2; manages secure partitions.
Secure Partition Manager Dispatcher (SPMD): Runs at EL3, usually as a component of Arm Trusted Firmware (also known as ATF, Trusted Firmware-A, and TF-A); handles world switching; relays FF-A calls to the SPMC.
Secure Partitions (SP): Secure partitions running at S-EL1 or S-EL0.
Normal World: Contains Hypervisor or OS in the normal world.
CPU Topology Configuration#
The SPMC manifest defines the system’s CPU topology so that Hafnium can properly manage processor resources and schedule virtual CPUs.
CPU Node Structure#
CPU topology is defined through the cpus node under the root node of the SPMC manifest:
cpus {
#address-cells = <0x2>;
#size-cells = <0x0>;
CPU0:cpu@0 {
device_type = "cpu";
compatible = "arm,armv8";
reg = <0x0 0x0>;
enable-method = "psci";
};
CPU1:cpu@000010000 {
device_type = "cpu";
compatible = "arm,armv8";
reg = <0x0 0x00010000>;
enable-method = "psci";
};
};
CPU Node Properties#
#address-cells: Number of address cells for CPU. Typically set to<0x2>for 64-bit addressing.#size-cells: Number of size cells for CPU. Typically set to<0x0>because CPU nodes don’t require size information.
Properties for each CPU child node:
device_type: Must be set to"cpu".compatible: CPU architecture compatibility string, such as"arm,armv8"or"arm,armv9".reg: CPU identifier in MPIDR (Multiprocessor Affinity Register) format.enable-method: CPU enable method. Typically"psci"(Power State Coordination Interface).
CPU Identifier Format#
The reg property defines the CPU’s MPIDR (Multiprocessor Affinity Register) value.
In the Jetson Thor series, the format of the MPIDR value is as follows:
MPIDR[23:16] = Cluster ID (0-14)
MPIDR[15:8] = Core ID (always 0)
MPIDR[7:0] = PE ID (always 0)
For example, the MPIDR value for cluster 1, core 0, PE 0 is 0x00010000.
CPU Configuration Considerations#
CPU count: Ensure that the number of defined CPUs matches the actual hardware.
MPIDR mapping: The
regvalue must match the hardware’s MPIDR register value.
Secure Partition Configuration#
Secure partitions are configured through manifest files in device-tree format. Each partition requires a corresponding manifest file to define its properties and resource requirements.
Basic Partition Configuration#
In the SPMC manifest, secure partitions are defined by vm child nodes under the hypervisor node:
hypervisor {
compatible = "hafnium,hafnium";
logging-level = "verbose";
vm1 {
uuid = <0xe0786148 0xe311f8e7 0x02005ebc 0x1bc5d5a5>;
is_ffa_partition;
debug_name = "optee";
mem_size = <0x02000000>;
load_address = <0x0 0x89c00000>;
vcpu_count = <14>;
};
vm2 {
uuid = <0x8afb129b 0x64ca4760 0x8618c888 0x4caa6c4a>;
is_ffa_partition;
debug_name = "standalonemm";
mem_size = <0x800000>;
load_address = <0x0 0x8BC00000>;
vcpu_count = <1>;
smc_whitelist = <0x80000002>;
};
};
Required Properties#
Each secure partition must include the following properties:
uuid: Unique identifier for the partition; used for FF-A communication.is_ffa_partition: Boolean value identifying an FF-A compatible partition.debug_name: Human-readable name for debugging.mem_size: Partition memory size (in bytes).load_address: Physical address where the partition is loaded. This value is ignored because MB2 overrides it with the actual memory address where the secure partition is loaded.vcpu_count: Number of virtual CPUs.
Add Secure Partitions#
To add a secure partition, add the definition of the new partition to the SPMC manifest (for example, t264_spmc.dts) and rebuild Hafnium:
hypervisor {
compatible = "hafnium,hafnium";
logging-level = "verbose";
/* Existing partitions... */
vm3 {
uuid = <0x12345678 0x9abc 0xdef0 0x123456789abc>;
is_ffa_partition;
debug_name = "my-secure-partition";
mem_size = <0x100000>;
load_address = <0x0 0xdeadbeef>;
vcpu_count = <1>;
boot-order = <1>;
};
};
Enable Secure Partitions#
To enable a secure partition, ensure that the partition definition is included in the SPMC manifest and the is_ffa_partition property is present:
vm1 {
uuid = <0xe0786148 0xe311f8e7 0x02005ebc 0x1bc5d5a5>;
is_ffa_partition;
debug_name = "optee";
mem_size = <0x02000000>;
load_address = <0x0 0x89c00000>;
vcpu_count = <14>;
};
Disable Secure Partitions#
To disable a secure partition, comment out or remove the partition definition:
/*
vm1 {
uuid = <0xe0786148 0xe311f8e7 0x02005ebc 0x1bc5d5a5>;
is_ffa_partition;
debug_name = "optee";
mem_size = <0x02000000>;
load_address = <0x0 0x89c00000>;
vcpu_count = <14>;
};
*/
Partition Configuration Best Practices#
We recommend the following best practices for secure partition configuration.
Memory Configuration#
Memory alignment: Ensure that memory size and addresses are page-aligned (typically 4 KB).
Memory overlap check: Avoid memory region overlaps between partitions.
Security boundaries: Allocate sufficient memory boundaries for each partition.
vm1 {
/* Memory size aligned to 4 KB */
mem_size = <0x100000>; /* 1 MB, 256 pages */
load_address = <0x0 0x90000000>; /* 4-KB aligned address */
};
MB2 overrides load_address with the actual memory address where the secure partition is loaded. Settings in the manifest are ignored.
CPU Configuration#
Allocate vCPUs reasonably based on partition requirements.
vm1 {
vcpu_count = <1>; /* Single-threaded partition */
/* or */
vcpu_count = <8>; /* Multi-threaded partition */
};
Build and Deployment#
Build Hafnium#
To build Hafnium itself, refer to hafnium_README.txt or Hafnium Documentation.
Build Secure Partitions#
Take OP-TEE as an example:
Compile Secure Partition: Build OP-TEE into a raw binary file,
tee-raw.bin.Pack Secure Partition Binary and Manifest: Pack the binary and manifest into a image file by using sptool from Arm Trusted Firmware.
${ATF_SRC}/tools/sptool/sptool.py -i tee-raw.bin:tegra264-optee.dtb -o tos.img
Create Secure Partition Package: Use fiptool from Arm Trusted Firmware to create a package of several secure partitions.
${ATF_SRC}/tools/fiptool/fiptool create \ --align 0x1000 \ --blob uuid=e0786148-e311f8e7-02005ebc-1bc5d5a5,file=tos.img \ sp_t264.fip
Debugging and Troubleshooting#
Common Issues#
Partition Loading Failure
Check memory size and address alignment.
Verify the
load_addressis within valid range.Check whether the partition binary file is correct.
Memory Access Errors
Verify memory region definitions.
Check memory attribute settings.
Ensure that the partition has sufficient memory permissions.
Communication Failure
Check whether the UUID is correct.
Verify that the partition has started properly.
Validate the FF-A interface implementation.
Logging Configuration#
t264_spmc.dts is a manifest file that describes the secure partitions on T264. Log level is available as a property.
Debug names: Use meaningful values for
debug_name.Log level: Set the appropriate log level at the SPMC level.
hypervisor {
compatible = "hafnium,hafnium";
logging-level = "verbose";
vm1 {
debug_name = "optee"; /* Meaningful name */
};
};
On T264, MB2 overrides the log level based on the MB1 boot config table (BCT). Users should modify tegra264-mb1-bct-debug-defaults.dtsi to set the log level.
mb1_bct {
debug {
/* 0: none, 1: critical, 2: error, 3: warnings, 4: info, 5: verbose */
log_level = <5>;
};
};
Reference Resources#
Example Configuration#
The following is an example of a complete SPMC manifest:
/dts-v1/;
/ {
#size-cells = <2>;
#address-cells = <2>;
compatible = "arm,ffa-core-manifest-1.0";
attribute {
maj_ver = <1>;
min_ver = <1>;
spmc_id = <0x8000>;
exec_state = <0>;
binary_size = <409600>;
load_address = <0x0 0x88400000>;
entrypoint = <0x0 0x88400000>;
};
cpus {
#address-cells = <0x2>;
#size-cells = <0x0>;
CPU0:cpu@0 {
device_type = "cpu";
compatible = "arm,armv8";
reg = <0x0 0x0>;
enable-method = "psci";
};
CPU1:cpu@000010000 {
device_type = "cpu";
compatible = "arm,armv8";
reg = <0x0 0x00010000>;
enable-method = "psci";
};
CPU2:cpu@000020000 {
device_type = "cpu";
compatible = "arm,armv8";
reg = <0x0 0x00020000>;
enable-method = "psci";
};
CPU3:cpu@000030000 {
device_type = "cpu";
compatible = "arm,armv8";
reg = <0x0 0x00030000>;
enable-method = "psci";
};
CPU4:cpu@000040000 {
device_type = "cpu";
compatible = "arm,armv8";
reg = <0x0 0x00040000>;
enable-method = "psci";
};
CPU5:cpu@000050000 {
device_type = "cpu";
compatible = "arm,armv8";
reg = <0x0 0x00050000>;
enable-method = "psci";
};
CPU6:cpu@000060000 {
device_type = "cpu";
compatible = "arm,armv8";
reg = <0x0 0x00060000>;
enable-method = "psci";
};
CPU7:cpu@000070000 {
device_type = "cpu";
compatible = "arm,armv8";
reg = <0x0 0x00070000>;
enable-method = "psci";
};
CPU8:cpu@000080000 {
device_type = "cpu";
compatible = "arm,armv8";
reg = <0x0 0x00080000>;
enable-method = "psci";
};
CPU9:cpu@000090000 {
device_type = "cpu";
compatible = "arm,armv8";
reg = <0x0 0x00090000>;
enable-method = "psci";
};
CPU10:cpu@0000a0000 {
device_type = "cpu";
compatible = "arm,armv8";
reg = <0x0 0x000a0000>;
enable-method = "psci";
};
CPU11:cpu@0000b0000 {
device_type = "cpu";
compatible = "arm,armv8";
reg = <0x0 0x000b0000>;
enable-method = "psci";
};
CPU12:cpu@0000c0000 {
device_type = "cpu";
compatible = "arm,armv8";
reg = <0x0 0x000c0000>;
enable-method = "psci";
};
CPU13:cpu@0000d0000 {
device_type = "cpu";
compatible = "arm,armv8";
reg = <0x0 0x000d0000>;
enable-method = "psci";
};
};
memory@80000000 {
device_type = "ns-memory";
reg = <0x0000 0x80000000 0x80 0x0>;
};
hypervisor {
compatible = "hafnium,hafnium";
logging-level = "verbose";
vm1 {
uuid = <0xe0786148 0xe311f8e7 0x02005ebc 0x1bc5d5a5>;
is_ffa_partition;
debug_name = "optee";
mem_size = <0x02000000>;
load_address = <0x0 0x89c00000>;
vcpu_count = <14>;
};
vm2 {
uuid = <0x8afb129b 0x64ca4760 0x8618c888 0x4caa6c4a>;
is_ffa_partition;
debug_name = "standalonemm";
mem_size = <0x800000>;
load_address = <0x0 0x8BC00000>;
vcpu_count = <1>;
smc_whitelist = <0x80000002>;
};
};
};