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 reg value 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:

  1. Compile Secure Partition: Build OP-TEE into a raw binary file, tee-raw.bin.

  2. 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
    
  3. 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_address is 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>;
        };
    };
};