Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 68

Implementing Secure Monitor Calls for World Transitions

Recommended resources

Hello! Welcome back to our series on advanced embedded driver development.

In our last lesson, we established the "what" and "why" of ARM TrustZone, exploring how it creates hardware-enforced Secure and Non-secure worlds. We learned that the only legitimate way to cross from the Normal World to the Secure World is via a controlled gateway managed by the Secure Monitor at Exception Level 3 (EL3).

Today, we focus on the "how." Your learning goal is to implement a Secure Monitor Call (SMC) to transition between Non-Secure and Secure worlds. We will dissect the SMC instruction, the standardized calling convention that governs it, and the software in EL3 that handles the request and orchestrates the world switch. This is a fundamental skill for anyone working on the secure foundations of an ARM-based system.

ARM TrustZone Exception Levels and Secure Monitor Calls
This diagram provides a quick recap, showing that the Secure Monitor Call (SMC) instruction is the designated pathway for software in the Normal World (EL1/EL2) to request services from the Secure World, trapping into the Secure Monitor at EL3.

1. The Secure Monitor Call (SMC) as a Standardized API

An SMC is not just an arbitrary jump; it's a synchronous exception that triggers a mode switch to EL3. To ensure interoperability between different software components (e.g., a standard Linux kernel and a proprietary secure firmware), ARM defines the SMC Calling Convention (SMCCC). This convention standardizes how services are requested, how parameters are passed, and how results are returned.

The reference implementation for this architecture on A-class processors is the Trusted Firmware-A (TF-A) project, which provides the EL3 monitor firmware (known as BL31).

Technical Overview of the Trusted Firmware: A Class Open Source Project

To set the stage, let's watch a brief overview of the Trusted Firmware-A project. This will help you understand the context in which a real-world Secure Monitor operates.

Watch the segment from 01:14 to 04:15. Focus on how it describes the relationship between the Normal and Secure worlds and identifies the SMC layer as the communication mechanism. Note the mention of BL31 as the runtime component that handles these calls.

As the video explained, the SMC layer is a well-defined interface. Let's dig into the details of that interface.

EL3 Runtime Service Writer's Guide

The TF-A documentation provides a clear guide for developers who want to write runtime services that are invoked via SMC. We will use it to understand how SMCs are structured and categorized.

Please read the 'Introduction' and 'Owning Entities, Call Types and Function IDs' sections. Pay close attention to the structure of the SMC Function ID and the concept of an Owning Entity Number (OEN). This explains how the system knows whether an SMC is for a standard service like PSCI, a SiP (silicon provider) service, or a Trusted OS.

To summarize the key points from that reading:

  • An SMC is initiated by the smc instruction.
  • The W0 (or X0) register holds the SMC Function Identifier (smc_fid).
  • The smc_fid is a 32-bit value that encodes:
    • Call Type (bit 31): 0 for Fast Call, 1 for Yielding/Standard Call.
    • Calling Convention (bit 30): 0 for SMC32 (32-bit args), 1 for SMC64 (64-bit args).
    • Owning Entity Number (OEN, bits 23-16): Identifies the service owner (e.g., ARM Architecture, SiP, OEM, Trusted OS).
    • Function Number (bits 15-0): The specific function being called within that service.
  • Arguments are passed in registers X1 through X7.
  • Return values are passed back in X0 through X7.

This standardized structure allows the Secure Monitor to act as a router, directing incoming requests to the correct service handler.


2. Implementing the SMC Handler in EL3

When an smc instruction is executed in the Normal World (e.g., at EL1), the processor takes a synchronous exception to EL3. The Secure Monitor's exception handler must then perform the following steps:

  1. Identify the Exception: Read the Exception Syndrome Register (ESR_EL3) to confirm the cause was an SMC instruction.
  2. Decode the Request: Read the smc_fid from register X0.
  3. Dispatch to Service: Based on the OEN in the smc_fid, forward the call to the appropriate registered service handler.
  4. Execute and Return: The service handler performs its function and prepares the return values in registers X0-X7 before returning from the exception.

The following resource shows a simplified assembly implementation of this logic.

ARMv-8a Exception Levels

This blog post provides a clear, concise assembly code snippet demonstrating how an EL3 monitor handles an incoming SMC.

Read the section 'EL3: The Secure Monitor' and study the associated assembly code snippet. Notice how it reads esr_el3, checks for the SMC exception class (0x17), and then branches to an smc_handler.

In a full-featured firmware like TF-A, this raw assembly logic is wrapped in a C framework. Services register themselves with the monitor during initialization, providing a function pointer to their handler.

EL3 Runtime Service Writer's Guide

Let's revisit the TF-A Writer's Guide to see how this is abstracted in C. This connects the assembly concepts to a practical software framework.

First, skim section 'Registering a runtime service' to see the DECLARE_RT_SVC() macro. Then, read section 'Handling runtime service requests' carefully. This details the responsibilities of the C handler function, such as validating the function ID, checking the caller's security state (is_caller_non_secure(flags)), and using the SMC_RETn() macros to return values.

Your experience with AUTOSAR BSW integration is relevant here. Just as you integrate different modules (e.g., Can, Com, PduR) that register their services with the RTE, here you have different secure services (PSCI, SiP, etc.) that register their SMC handlers with the EL3 runtime framework.


3. Implementing the World Switch

Not all SMCs result in a world switch. A simple request might be handled entirely within EL3. However, if the request is for a complex service provided by a Trusted OS (running at S-EL1), the Secure Monitor must perform a full context switch.

The key to the world switch is the Secure Configuration Register, SCR_EL3. Its NS (Non-Secure) bit determines the security state of the lower exception levels.

  • SCR_EL3.NS = 1: EL0/1/2 are in the Non-secure state.
  • SCR_EL3.NS = 0: EL0/1/2 are in the Secure state.

The process to switch from Normal to Secure world is:

  1. Receive the SMC in EL3.
  2. Save the complete context of the Normal World (GPRs, SP_ELx, SPSR_ELx, ELR_ELx).
  3. Set up the entry parameters for the Secure World (e.g., in registers X0-X4).
  4. Modify SCR_EL3 to clear the NS bit (bic x0, x0, #1).
  5. Set SPSR_EL3 to the desired state for entering the Secure World (e.g., target S-EL1).
  6. Set ELR_EL3 to the entry point address in the Secure World.
  7. Execute eret. The processor resumes at ELR_EL3 in the new security state.

ARMv-8a Exception Levels

The same blog post provides another excellent code snippet showing exactly how to modify SCR_EL3 to perform the world switch.

Now read the section 'Security States: Secure vs. Non-secure Worlds' and examine the final code snippet. This shows the critical msr scr_el3, x0 instruction that commits the change in security state.

This mechanism is used in many real-world automotive scenarios. For instance, writing to One-Time Programmable (OTP) fuses for configuration or key storage is a highly sensitive operation. A non-secure bootloader like U-Boot would use an SMC to request the ARM Trusted Firmware (ATF) to perform the write.

SMC Call for OTP Fuse Writing in ARM TrustZone
This diagram shows a practical example. U-Boot (Non-secure) issues an SMC to the ATF (Secure EL3). The ATF then communicates with a secure coprocessor (SCU) to perform the physical write to OTP memory, completely isolating the non-secure world from the hardware.
Test your understanding!

Let's put this together with a small coding exercise. Imagine you need to create a C function that can be called from a non-secure OS driver to request a service from the secure world. The service has an OEN of 50 (Trusted OS call) and a function number of 123. The call is SMC64 and is a fast call. It takes one 64-bit argument.

Write a C function uint64_t call_secure_service(uint64_t arg1) that uses inline assembly to execute the appropriate smc instruction.

Show answer

The smc_fid needs to be constructed first.

  • Call Type (Fast): 0
  • Calling Convention (SMC64): 1
  • OEN: 50 (0x32)
  • Function Number: 123 (0x7B)

So, smc_fid is 0x4000327B for SMC64, or 0x0000327B for SMC32. Since the spec says SMC64, we'll use that (0xC000327B if it were a yielding call, 0x8000327B for SMC32 yielding). Let's assume SMC64 Fast Call: 0x4000327B.

Here is the C function with GCC/Clang style inline assembly:

#include <stdint.h>

// Construct the SMC Function ID
// SMC64 Fast Call: Type=0 (bit31), CC=1 (bit30)
// OEN=50 (0x32), Fn_Num=123 (0x7B)
#define SECURE_SERVICE_SMC_FID 0x4000327B

uint64_t call_secure_service(uint64_t arg1) {
    register uint64_t r0 __asm__("x0") = SECURE_SERVICE_SMC_FID;
    register uint64_t r1 __asm__("x1") = arg1;

    // The 'smc #0' instruction is executed. The immediate value is ignored by the CPU
    // but can be used by the handler. Conventionally, it's 0.
    // The registers x0 and x1 are specified as both input and output ('+r').
    __asm__ volatile (
        "smc #0"
        : "+r"(r0), "+r"(r1) // Output operands
        : // Input operands (already specified in output)
        : "x2", "x3", "x4", "x5", "x6", "x7", "memory", "cc" // Clobbered registers
    );

    // The return value from the secure service will be in x0.
    return r0;
}

This demonstrates how high-level code prepares registers according to the SMCCC and then uses an inline smc instruction to trap into the Secure Monitor.


4. A Practical Look at World Transitions (Cortex-M)

While we've focused on Cortex-A's SMC instruction, it's insightful to see the equivalent process on a Cortex-M processor with TrustZone-M. The mechanism is different (SG instruction via "veneer" functions instead of SMC), but the principle of a controlled transition from non-secure to secure code is identical. Watching a debug session can make these abstract concepts very concrete.

6 TrustZone and secure hello world

This video demonstrates a 'hello world' project with separate secure and non-secure parts on a Cortex-M33. It shows how the non-secure code calls a function implemented in the secure world and lets you visualize the transition during a debug session.

Watch the two segments: (07:57 - 10:15): This part shows how the non-secure project calls a function (printf_non_secure_entry) that is defined in the secure project, using a 'veneer table' as the gateway. (10:15 - 13:15): This shows the debugging process. Observe how the program counter starts in the secure project, transitions to the non-secure project, and then steps back into the secure world to execute the print function when called from non-secure code.


5. Interview Corner

Q1: Walk me through the life of an SMC, from a non-secure Linux application to the execution of a secure service and back.
A:

  1. The Linux application makes a system call (e.g., via an ioctl on a specific device driver).
  2. The kernel driver, running in EL1, prepares the registers according to the SMCCC: X0 gets the smc_fid, and X1-Xn get the arguments.
  3. The driver executes the smc #0 instruction. This causes a synchronous exception to EL3.
  4. The Secure Monitor (e.g., TF-A's BL31) starts execution at its exception vector. It reads ESR_EL3 to confirm an SMC occurred.
  5. It decodes the smc_fid from X0 to identify the service owner (OEN) and function number.
  6. It dispatches the call to the registered handler for that OEN.
  7. The handler executes. If it needs a Trusted OS, the monitor saves the non-secure context, modifies SCR_EL3.NS to 0, sets up the S-EL1 entry context, and executes eret.
  8. The Trusted OS at S-EL1 performs the service. Upon completion, it makes an SMC to return to EL3.
  9. The monitor then restores the non-secure world context, sets SCR_EL3.NS back to 1, and executes eret to return to the Linux driver at EL1, right after the original smc instruction.
  10. The driver receives the return values in X0-Xn, and returns the result to the user-space application.

Q2: Why is the SMC Calling Convention (SMCCC) so important for the TrustZone ecosystem?
A: It provides a standardized, interoperable interface between software from different vendors. An SoC vendor can provide EL3 firmware (the monitor), a separate company can provide a Trusted OS, and a third party like Canonical can provide the Normal World OS (Ubuntu/Linux). The SMCCC ensures they can all communicate correctly without needing to be tightly coupled. It decouples the service requester from the service provider, which is essential for building complex, modular systems.

Q3: You're debugging a system where SMCs to a custom SiP service are failing with an "Unknown SMC Function ID" error (0xFFFFFFFF). What are the likely causes in the EL3 firmware?
A: The error code SMC_UNK means the monitor doesn't have a handler for that specific smc_fid. The likely causes are:

  • Registration Failure: The SiP service's _setup function failed during cold boot, so the framework never registered its handler. Check the boot logs for initialization errors.
  • Incorrect OEN Range: The DECLARE_RT_SVC macro for the SiP service might have the wrong _start or _end OEN values, so the incoming smc_fid doesn't fall within its claimed range.
  • Handler Logic Error: The service's _smch handler itself is incorrectly rejecting a valid function number within its range, perhaps due to a bug in its validation logic.
  • Call Type Mismatch: The call is being made as a Yielding call, but the service was only registered for SMC_TYPE_FAST.

Conclusion

Today we moved from the theory of TrustZone to the practical implementation of its core communication mechanism. You've seen how a simple instruction, when governed by a robust convention and handled by well-structured firmware, can create a secure gateway between two isolated worlds.

Key Takeaways:

  • World transitions are initiated via the smc instruction, which triggers a synchronous exception to EL3.
  • The SMC Calling Convention (SMCCC) standardizes how smc_fid, arguments, and return values are passed in registers.
  • The smc_fid contains the Owning Entity Number (OEN), which allows the Secure Monitor to route the call to the correct service (e.g., PSCI, SiP, Trusted OS).
  • The Secure Monitor handler in EL3 is responsible for decoding the SMC, dispatching it, and potentially orchestrating a world switch by modifying SCR_EL3.NS and performing a context switch.
  • Frameworks like TF-A provide a C-level abstraction for registering and handling SMC services.

Next Lesson Preview:
An SMC allows us to enter the Secure World, but how do we trust the code that's running there in the first place? In our next lesson, we will explore the principles of a secure boot process and the chain of trust, ensuring that the system starts up in a known, secure state from the very first instruction.