Secure Interrupt Integration Guide
Introduction
This document describes how to enable an interrupt in TF-M. The target audiences are mainly platform integrators and Secure Partition developers.
This document assumes that you have read the PSA Firmware Framework (FF-M) v1.0 [1] and the FF-M v1.1 extensions [2] thus have knowlege on the terminologies such as Secure Partitions and manifests.
Interrupt Handling Model
TF-M supports the two interrupt handling model defined by FF-M:
First-Level Interrupt Handling (FLIH)
In this model, the interrupt handling is carried out immediately when the interrupt exception happens.
The interrupt handling can optionally set an interrupt signal for the Secure Partition Thread to have further data processing.
Second-Level Interrupt Handling (SLIH)
In this model, the interrupt handling is deferred after the interrupt exception. The handling occurs in the Secure Partition Thread thus is subject to scheduling.
The FLIH supports handling an interrupt in a bounded time, but very limited APIs are allowed in the FLIH handling because the handling occurs in an special exception context.
The SLIH is deferred and subject to scheduling but all Secure Partition APIs are allowed as the SLIH handling is in the Secure Partition Thread.
Both the FLIH and the SLIH can be used by Secure Partitions which conform to Firmware Framework v1.1.
While the SLIH is the only supported model for Secure Partitions which conform to Firmware Framework v1.0.
Please refer to chapter 6.2 of FF-M v1.1 [1] for more details on the interrupt handling models.
Enabling an Interrupt
To enable an interrupt, you need to do the following:
Binding the interrupt to a Secure Partition.
Granting the Secure Partition access permissions to the device of the interrupt.
Initializing the interrupt.
Integrating the interrupt handling function
TF-M has two Test Partitions as good examples for both FLIH [3] and SLIH [4]. See also Enabling the Interrupt Tests on how to integrate them to platforms.
Binding an Interrupt to a Secure Partition
To bind an interrupt to a Secure Partition, you need to add an item to the
irqs
attribute of the Secure Partition manifest.
irqs
is a list of Interrupt Request (IRQ) assigned to the Secure Partition.
Secure Partitions are not allowed to share IRQs with other Secure Partitions.
Different Firmware Framework versions have different definitions of manifest.
FF-M v1.0
Here is an example manifest of Secure Partitions conform to Firmware Framework version 1.0:
{
"irqs": [
{
"source": 5,
"signal": "DUAL_TIMER_SIGNAL"
},
{
"source": "TIMER_1_SOURCE",
"signal": "TIMER_1_SIGNAL"
}
]
}
source
Required, Unique.
The
source
is a string that identifies the interrupt source. It can be a valid exception number or a symbolic name defined in platform codes.signal
Required, Unique.
The
signal
attribute is a symbolic name used by TF-M to identify which interrupt is asserted. It is also used by the Secure Partition to receive the interrupt signal by callingpsa_wait
for interrupt handling.It is defined in the Secure Partition header file
<psa_manifest/manifestfilename.h>
generated by TF-M:#define signal VALUE
The interrupt handling model is SLIH by default as it is the only supported one for FF-M v1.0.
FF-M v1.1
Here is an example manifest of Secure Partitions conform to Firmware Framework version 1.1:
{
"irqs": [
{
"source" : "TIMER_1_SOURCE",
"name" : "TIMER_1",
"handling": "FLIH"
},
{
"source" : 5,
"name" : "DUAL_TIMER",
"handling": "SLIH"
}
]
}
source
The
source
is the same as the one in Firmware Framework Version 1.0.name
Required, Unique.
The
name
is used to construct the following two elements:the interrupt signal symbol:
{{name}}_SIGNAL
, the equivalent ofsignal
in FF-M v1.0the FLIH Function for handling
FLIH
IRQs provided by Secure Partition:psa_flih_result_t {{name}}_flih(void);
It is also declared in
<psa_manifest/manifestfilename.h>
.
handling
Required.
The
handling
attribute specifies the interrupt handling model and must have one of the following values:FLIH
- First-Level Interrupt HandlingSLIH
- Second-Level Interrupt Handling
Granting Permissions to Devices for Secure Partitions
A secure partition shall be granted two parts of permissions to access a device. One is the Memory Maped I/O (MMIO) region of the device. The other is the driver codes to access the device.
The MMIO Regions
You need to declare the MMIO region in the mmio_regions
attributes in the
Secure Partition manifest, to enable the Secure Partition to access it.
An MMIO region can be described as either numbered_region
or
named_region
.
A numbered region consists of a base
address and a size
.
A named region consists of a string name
to describe the region.
Here is an example of named region:
{
"mmio_regions": [
{
"name": "TFM_PERIPHERAL_TIMER0",
"permission": "READ-WRITE"
}
]
}
name
Required.
The
name
attribute is a symbolic name defined by platforms. It is a pointer to structure instance that usually includes the base address and size of the region and some other platform specific attributes that are required to set up permissions to the MMIO region.The structure is defined by platforms and the name must be
struct platform_data_t
.permission
Required.
The
permission
attribute must have one of the following values:READ-ONLY
READ-WRITE
The Device Drivers
To give permissions of devices drivers to Secure Partitions, it is recommended to put the driver codes to the Partition’s CMake library:
target_sources(some_partition_lib
PRIVATE
some_driver_code.c
)
Initializing the Interrupts
Platforms must define an interrupt initialization function for each Secure interrupt.
The prototype of the function is:
enum tfm_hal_status_t {source_symbol}_init(void *p_pt,
const struct irq_load_info_t *p_ildi)
The {source_symbol}
is:
irq_{source}
, if thesource
attribute of the IRQ in Partition manifest is a numberLowercase of
source
attribute, ifsource
is a symbolic name
For example if the manifest declares "source": 5
, then the function name
is irq_5_init
.
If the mannifest declares "source" : "TIMER_1_IRQ"
then the function
name is timer_1_irq_init
.
The function will be called by the Framework automatically during
initialization. The function can be put in any source file that belongs to SPM,
for example a tfm_interrupts.c
added to the tfm_spm
CMake target.
The initialization of an interrupt must include:
setting the priority
ensuring that the interrupt targets the Secure State.
saving the interrupt information
Setting Priority
The priority of external interrupts must be in the following range:
(0, N / 2)
, where N
is the number of configurable priorities.
Smaller values have higher priorities.
For example if the number of configurable priority of your interrupt controller
is 16, you must use the priorities in range (0, 8)
only, boundaries
excluded.
Note that these are not the values set into the interrupt controllers.
Different platforms may have different values for those priorities.
But if you use the NVIC_SetPriority
function provided by CMSIS to set
priorities, you can pass the values directly.
Platforms have the flexibilities on the assignment of priorities.
Targeting Interrupts to Secure
In single core systems, platform integrators must ensure that the Secure interrupts target to Secure State by setting the Interrupt Controller.
In multi-core systems, this might be optional.
Saving the Interrupt Information
The initialization function is called during Partition loading with the following information:
p_pt
- pointer to Partition runtime struct of the owner Partitionp_ildi
- pointer toirq_load_info_t
struct of the interrupt
Platforms must save the information for the future use. See Integrating the Interrupt Handling Function for the usage.
The easiest way is to save them in global variables for each interrupt. TF-M provides a struct for saving the information:
struct irq_t {
void *p_pt;
const struct irq_load_info_t *p_ildi;
};
Integrating the Interrupt Handling Function
TF-M provides an interrupt handling entry for Secure interrupts:
void spm_handle_interrupt(void *p_pt, const struct irq_load_info_t *p_ildi)
The p_pt
and p_ildi
are the information passed to interrupt
initialization functions and saved by platforms.
Platforms should call this entry function in the interrupt handlers held in Vector Table with the information saved by the interrupt initialization functions. If the information is saved as global variables, then the interrupt handlers can be put in the same source file that contains the initialization functions.
Here is an example:
void TFM_TIMER0_IRQ_Handler(void) /* The handler in Vector Table */
{
spm_handle_interrupt(p_timer0_pt, p_tfm_timer0_irq_ldinf);
}
Enabling the Interrupt Tests
TF-M provides test suites for FLIH and SLIH interrupts respectively. They are disabled by default.
Note
FLIH interrupt test and SLIH interrupt test share the same timer
TFM_TIMER0_IRQ
thus cannot be enabled at the same time.
To enable the tests, please follow steps in the previous sections. In addition, you need to implement the following APIs of timer control:
void tfm_plat_test_secure_timer_start(void)
void tfm_plat_test_secure_timer_clear_intr(void)
void tfm_plat_test_secure_timer_stop(void)
You shall also select the following flags in platform specific config.cmake
to indicate that FLIH and SLIH interrupt tests are supported respectively.
PLATFORM_FLIH_IRQ_TEST_SUPPORT
: platform implements support of FLIH interrupt testsPLATFORM_SLIH_IRQ_TEST_SUPPORT
: platform implements support of SLIH interrupt tests
The following configurations control SLIH and FLIH interrupt tests:
TEST_NS_FLIH_IRQ
TEST_NS_SLIH_IRQ
They can be enabled via build command line or via TEST_NS
.
Migrating to Firmware Framework v1.1
Please refer to Migrating Secure Partitions to version 1.1
of FF-M v1.1 [1]
.
References
Copyright (c) 2021-2023, Arm Limited. All rights reserved. Copyright (c) 2022 Cypress Semiconductor Corporation (an Infineon company) or an affiliate of Cypress Semiconductor Corporation. All rights reserved.