欢迎关注“安全有理”微信公众号。
概述
系统在运行过程中的任何阶段,都有可能产生中断。在Armv8架构系统中,TEE-OS运行在安全世界的EL1,Rich-OS运行在非安全世界的EL1,而BL31则运行于EL3。想实现各种中断在三种状态下被处理的统一性和正确性,就需要确保各种状态下中断向量表以及GIC的正确配置。 在ATF的BL31阶段启用了中断,BL31主要完成了GIC驱动初始化,中断注册,中断处理,中断路由配置等功能。
GIC
中断控制器(General Interruption Controller,GIC) 模块是CPU的外设之一, 它的作用是接收来自其他外设的中断引脚输入, 然后根据中断触发模式、 中断类型优先级等设置来控制发送不同的中断信号到CPU。
GIC主要实现distributor、redistributor和cpu interface组件的初始化,包括驱动加载,中断路由,中断的参数配置等。ATF实现了GICv3和GICv2,这里只关注GICv3,源码位于drivers/arm/gic/v3
,头文件位于include/drivers/arm/gicv3.h
。
中断属性
在include/common/interrupt_props.h
定义了中断属性的描述。
/* Create an interrupt property descriptor from various interrupt properties */
#define INTR_PROP_DESC(num, pri, grp, cfg) \
{ \
.intr_num = (num), \
.intr_pri = (pri), \
.intr_grp = (grp), \
.intr_cfg = (cfg), \
}
typedef struct interrupt_prop {
unsigned int intr_num:10;
unsigned int intr_pri:8;
unsigned int intr_grp:2;
unsigned int intr_cfg:2;
} interrupt_prop_t;
- INTR_PROP_DESC:用于描述中断属性的宏
- intr_num:中断号
- intr_pri:中断优先级
- intr_grp:中断分组
- intr_cfg:触发方式,边沿触发还是电平触发
驱动结构
gicv3_driver_data_t
定义了GICv3 IP的一些属性,结构如下。
typedef unsigned int (*mpidr_hash_fn)(u_register_t mpidr);
typedef struct gicv3_driver_data {
uintptr_t gicd_base;
uintptr_t gicr_base;
const interrupt_prop_t *interrupt_props;
unsigned int interrupt_props_num;
unsigned int rdistif_num;
uintptr_t *rdistif_base_addrs;
mpidr_hash_fn mpidr_to_core_pos;
} gicv3_driver_data_t;
- gicd_base:Distributor接口的基地址
- gicr_base:Re-distributor接口的基地址
- interrupt_props:安全中断及其属性
- interrupt_props_num:
interrupt_props
的个数 - rdistif_num:GIC实施的Redistributor接口个数,其等于CPU的个数或者GIC实现的CPU接口个数
- rdistif_base_addrs:指向一个数组的指针,该数组存储了每个CPU的Redistributor接口的基地址,数组大小等于
rdistif_num
- mpidr_to_core_pos:指向一个哈希函数指针,驱动用来将MPIDR值转换为core 索引,这个索引用来访问
rdistif_base_addrs
数组
GIC驱动初始化
在EL3中,GICv3驱动初始化函数是gicv3_driver_init
,如下:
/*******************************************************************************
* This function initialises the ARM GICv3 driver in EL3 with provided platform
* inputs.
******************************************************************************/
void __init gicv3_driver_init(const gicv3_driver_data_t *plat_driver_data)
{
unsigned int gic_version;
unsigned int gicv2_compat;
assert(plat_driver_data != NULL);
assert(plat_driver_data->gicd_base != 0U);
assert(plat_driver_data->rdistif_num != 0U);
assert(plat_driver_data->rdistif_base_addrs != NULL);
assert(IS_IN_EL3());
assert((plat_driver_data->interrupt_props_num != 0U) ?
(plat_driver_data->interrupt_props != NULL) : 1);
/* Check for system register support */
#ifndef __aarch64__
assert((read_id_pfr1() &
(ID_PFR1_GIC_MASK << ID_PFR1_GIC_SHIFT)) != 0U);
#else
assert((read_id_aa64pfr0_el1() &
(ID_AA64PFR0_GIC_MASK << ID_AA64PFR0_GIC_SHIFT)) != 0U);
#endif /* !__aarch64__ */
gic_version = gicd_read_pidr2(plat_driver_data->gicd_base);
gic_version >>= PIDR2_ARCH_REV_SHIFT;
gic_version &= PIDR2_ARCH_REV_MASK;
/* Check GIC version */
#if !GIC_ENABLE_V4_EXTN
assert(gic_version == ARCH_REV_GICV3);
#endif
/*
* Find out whether the GIC supports the GICv2 compatibility mode.
* The ARE_S bit resets to 0 if supported
*/
gicv2_compat = gicd_read_ctlr(plat_driver_data->gicd_base);
gicv2_compat >>= CTLR_ARE_S_SHIFT;
gicv2_compat = gicv2_compat & CTLR_ARE_S_MASK;
if (plat_driver_data->gicr_base != 0U) {
/*
* Find the base address of each implemented Redistributor interface.
* The number of interfaces should be equal to the number of CPUs in the
* system. The memory for saving these addresses has to be allocated by
* the platform port
*/
gicv3_rdistif_base_addrs_probe(plat_driver_data->rdistif_base_addrs,
plat_driver_data->rdistif_num,
plat_driver_data->gicr_base,
plat_driver_data->mpidr_to_core_pos);
#if !HW_ASSISTED_COHERENCY
/*
* Flush the rdistif_base_addrs[] contents linked to the GICv3 driver.
*/
flush_dcache_range((uintptr_t)(plat_driver_data->rdistif_base_addrs),
plat_driver_data->rdistif_num *
sizeof(*(plat_driver_data->rdistif_base_addrs)));
#endif
}
gicv3_driver_data = plat_driver_data;
/*
* The GIC driver data is initialized by the primary CPU with caches
* enabled. When the secondary CPU boots up, it initializes the
* GICC/GICR interface with the caches disabled. Hence flush the
* driver data to ensure coherency. This is not required if the
* platform has HW_ASSISTED_COHERENCY enabled.
*/
#if !HW_ASSISTED_COHERENCY
flush_dcache_range((uintptr_t)&gicv3_driver_data,
sizeof(gicv3_driver_data));
flush_dcache_range((uintptr_t)gicv3_driver_data,
sizeof(*gicv3_driver_data));
#endif
gicv3_check_erratas_applies(plat_driver_data->gicd_base);
INFO("GICv%u with%s legacy support detected.\n", gic_version,
(gicv2_compat == 0U) ? "" : "out");
INFO("ARM GICv%u driver initialized in EL3\n", gic_version);
}
- gicd_read_pidr2:获取GIC的版本信息
- gicd_read_ctlr:判断GIC是否兼容GICv2
- gicv3_rdistif_base_addrs_probe:初始化redistributor的寄存器基地址
GIC初始化
Distributor Interface
函数gicv3_distif_init
主要初始化GIC Distributor接口,完成所有SPI中断的属性配置。
/*******************************************************************************
* This function initialises the GIC distributor interface based upon the data
* provided by the platform while initialising the driver.
******************************************************************************/
void __init gicv3_distif_init(void)
{
unsigned int bitmap;
assert(gicv3_driver_data != NULL);
assert(gicv3_driver_data->gicd_base != 0U);
assert(IS_IN_EL3());
/*
* Clear the "enable" bits for G0/G1S/G1NS interrupts before configuring
* the ARE_S bit. The Distributor might generate a system error
* otherwise.
*/
gicd_clr_ctlr(gicv3_driver_data->gicd_base,
CTLR_ENABLE_G0_BIT |
CTLR_ENABLE_G1S_BIT |
CTLR_ENABLE_G1NS_BIT,
RWP_TRUE);
/* Set the ARE_S and ARE_NS bit now that interrupts have been disabled */
gicd_set_ctlr(gicv3_driver_data->gicd_base,
CTLR_ARE_S_BIT | CTLR_ARE_NS_BIT, RWP_TRUE);
/* Set the default attribute of all (E)SPIs */
gicv3_spis_config_defaults(gicv3_driver_data->gicd_base);
bitmap = gicv3_secure_spis_config_props(
gicv3_driver_data->gicd_base,
gicv3_driver_data->interrupt_props,
gicv3_driver_data->interrupt_props_num);
/* Enable the secure (E)SPIs now that they have been configured */
gicd_set_ctlr(gicv3_driver_data->gicd_base, bitmap, RWP_TRUE);
}
- gicd_clr_ctlr:首先清除所有中断分组使能位,即清除Group 0,Secure Group 1,和 Non-secure Group 1
- gicd_set_ctlr:然后设置ARE_S和ARE_NS位,使能中断路由
- gicv3_spis_config_defaults:接着配置所有SPI中断属性为默认值,即将SPI中断分组配置为Non-secure Group 1,优先级默认最高,电平触发
- gicv3_secure_spis_config_props:接着配置平台提供的Secure SPI中断的属性,包括将每个SPI设置为Secure,中断分组为Group 0或Secure Group 1,触发方式,优先级,中断路由到primary cpu,最后使能该中断
- gicd_set_ctlr:最后使能Secure SPI中断
Redistributor Interface
函数gicv3_rdistif_init
初始化当前CPU(proc_num)的GIC Redistributor接口,完成所有PPI/SGI中断的属性配置。
/*******************************************************************************
* This function initialises the GIC Redistributor interface of the calling CPU
* (identified by the 'proc_num' parameter) based upon the data provided by the
* platform while initialising the driver.
******************************************************************************/
void gicv3_rdistif_init(unsigned int proc_num)
{
uintptr_t gicr_base;
unsigned int bitmap;
uint32_t ctlr;
assert(gicv3_driver_data != NULL);
assert(proc_num < gicv3_driver_data->rdistif_num);
assert(gicv3_driver_data->rdistif_base_addrs != NULL);
assert(gicv3_driver_data->gicd_base != 0U);
ctlr = gicd_read_ctlr(gicv3_driver_data->gicd_base);
assert((ctlr & CTLR_ARE_S_BIT) != 0U);
assert(IS_IN_EL3());
/* Power on redistributor */
gicv3_rdistif_on(proc_num);
gicr_base = gicv3_driver_data->rdistif_base_addrs[proc_num];
assert(gicr_base != 0U);
/* Set the default attribute of all SGIs and (E)PPIs */
gicv3_ppi_sgi_config_defaults(gicr_base);
bitmap = gicv3_secure_ppi_sgi_config_props(gicr_base,
gicv3_driver_data->interrupt_props,
gicv3_driver_data->interrupt_props_num);
/* Enable interrupt groups as required, if not already */
if ((ctlr & bitmap) != bitmap) {
gicd_set_ctlr(gicv3_driver_data->gicd_base, bitmap, RWP_TRUE);
}
}
- gicd_read_ctlr:首先读取ARE_S位是否使能,并判断当前是否在EL3异常等级
- gicv3_ppi_sgi_config_defaults:然后将所有SGI和PPI属性配置为默认值,即中断分组配置为Non-secure Group 1,中断优先级,电平触发
- gicv3_secure_ppi_sgi_config_props:接着配置平台提供的Secure PPI/SGI中断属性,包括设置每个SPI/SGI中断为Secure,中断分组为Group 0或Secure Group 1,优先级,触发方式,使能中断
- gicd_set_ctlr:最后使能中断分组
CPU Interface
同样,函数gicv3_cpuif_enable
当前CPU(proc_num)的GIC CPU接口。
/*******************************************************************************
* This function enables the GIC CPU interface of the calling CPU using only
* system register accesses.
******************************************************************************/
void gicv3_cpuif_enable(unsigned int proc_num)
{
uintptr_t gicr_base;
u_register_t scr_el3;
unsigned int icc_sre_el3;
assert(gicv3_driver_data != NULL);
assert(proc_num < gicv3_driver_data->rdistif_num);
assert(gicv3_driver_data->rdistif_base_addrs != NULL);
assert(IS_IN_EL3());
/* Mark the connected core as awake */
gicr_base = gicv3_driver_data->rdistif_base_addrs[proc_num];
gicv3_rdistif_mark_core_awake(gicr_base);
/* Disable the legacy interrupt bypass */
icc_sre_el3 = ICC_SRE_DIB_BIT | ICC_SRE_DFB_BIT;
/*
* Enable system register access for EL3 and allow lower exception
* levels to configure the same for themselves. If the legacy mode is
* not supported, the SRE bit is RAO/WI
*/
icc_sre_el3 |= (ICC_SRE_EN_BIT | ICC_SRE_SRE_BIT);
write_icc_sre_el3(read_icc_sre_el3() | icc_sre_el3);
scr_el3 = read_scr_el3();
/*
* Switch to NS state to write Non secure ICC_SRE_EL1 and
* ICC_SRE_EL2 registers.
*/
write_scr_el3(scr_el3 | SCR_NS_BIT);
isb();
write_icc_sre_el2(read_icc_sre_el2() | icc_sre_el3);
write_icc_sre_el1(ICC_SRE_SRE_BIT);
isb();
/* Switch to secure state. */
write_scr_el3(scr_el3 & (~SCR_NS_BIT));
isb();
/* Write the secure ICC_SRE_EL1 register */
write_icc_sre_el1(ICC_SRE_SRE_BIT);
isb();
/* Program the idle priority in the PMR */
write_icc_pmr_el1(GIC_PRI_MASK);
/* Enable Group0 interrupts */
write_icc_igrpen0_el1(IGRPEN1_EL1_ENABLE_G0_BIT);
/* Enable Group1 Secure interrupts */
write_icc_igrpen1_el3(read_icc_igrpen1_el3() |
IGRPEN1_EL3_ENABLE_G1S_BIT);
isb();
/* Add DSB to ensure visibility of System register writes */
dsb();
}
- gicv3_rdistif_mark_core_awake:首先将连接的Core标记为在线,即将
GICR_WAKER.ProcessorsSleep
清零,然后轮询GICR_WAKER.ChildrenASleep
,直至读到0 - write_icc_sre_el3:设置
ICC_SRE_EL3
寄存器中的 SRE 位,启用用对 CPU 接口寄存器的访问 - write_scr_el3 & write_icc_sre_el2 & write_icc_sre_el1:然后切换到非安全状态,同样设置
ICC_SRE_EL2
和寄存器ICC_SRE_EL1
中的 SRE 位 - write_scr_el3 & write_icc_sre_el1:接着切换回到安全状态,先设置寄存器
ICC_SRE_EL1
中的 SRE 位 - write_icc_pmr_el1:配置优先级mask,这里为允许所有的优先级中断
- write_icc_igrpen0_el1:使能Group 0中断
- write_icc_igrpen1_el3:最后使能Secure Group 1中断
GIC示例
以qemu平台为例,在BL31阶段启用GIC中断,GIC初始化函数为plat_qemu_gic_init
,包括GIC驱动初始化和GIC初始化,实现如下。
void plat_qemu_gic_init(void)
{
gicv3_driver_init(&qemu_gicv3_driver_data);
gicv3_distif_init();
gicv3_rdistif_init(plat_my_core_pos());
gicv3_cpuif_enable(plat_my_core_pos());
}
GIC驱动初始化
gicv3_driver_init
根据平台提供的数据初始化EL3中的GICv3驱动,qemu提供的gic驱动数据为:
static const gicv3_driver_data_t qemu_gicv3_driver_data = {
.gicd_base = GICD_BASE,
.gicr_base = GICR_BASE,
.interrupt_props = qemu_interrupt_props,
.interrupt_props_num = ARRAY_SIZE(qemu_interrupt_props),
.rdistif_num = PLATFORM_CORE_COUNT,
.rdistif_base_addrs = qemu_rdistif_base_addrs,
.mpidr_to_core_pos = qemu_mpidr_to_core_pos
};
其中qemu定义的中断分组包括Group 0和Secure Group 1,如下:
static const interrupt_prop_t qemu_interrupt_props[] = {
PLATFORM_G1S_PROPS(INTR_GROUP1S),
PLATFORM_G0_PROPS(INTR_GROUP0)
};
宏展开如下,所有的Secure Group 1都是SGI中断,最高优先级,边沿触发;未定义Group 0中断。
#define PLATFORM_G1S_PROPS(grp) \
INTR_PROP_DESC(QEMU_IRQ_SEC_SGI_0, GIC_HIGHEST_SEC_PRIORITY, \
grp, GIC_INTR_CFG_EDGE), \
INTR_PROP_DESC(QEMU_IRQ_SEC_SGI_1, GIC_HIGHEST_SEC_PRIORITY, \
grp, GIC_INTR_CFG_EDGE), \
INTR_PROP_DESC(QEMU_IRQ_SEC_SGI_2, GIC_HIGHEST_SEC_PRIORITY, \
grp, GIC_INTR_CFG_EDGE), \
INTR_PROP_DESC(QEMU_IRQ_SEC_SGI_3, GIC_HIGHEST_SEC_PRIORITY, \
grp, GIC_INTR_CFG_EDGE), \
INTR_PROP_DESC(QEMU_IRQ_SEC_SGI_4, GIC_HIGHEST_SEC_PRIORITY, \
grp, GIC_INTR_CFG_EDGE), \
INTR_PROP_DESC(QEMU_IRQ_SEC_SGI_5, GIC_HIGHEST_SEC_PRIORITY, \
grp, GIC_INTR_CFG_EDGE), \
INTR_PROP_DESC(QEMU_IRQ_SEC_SGI_6, GIC_HIGHEST_SEC_PRIORITY, \
grp, GIC_INTR_CFG_EDGE), \
INTR_PROP_DESC(QEMU_IRQ_SEC_SGI_7, GIC_HIGHEST_SEC_PRIORITY, \
grp, GIC_INTR_CFG_EDGE)
#define PLATFORM_G0_PROPS(grp)
然后定义了一个数组,用于存储Redistributor接口的基地址。
static uintptr_t qemu_rdistif_base_addrs[PLATFORM_CORE_COUNT];
最后定义一个将MPIDR转换为core索引的函数。
static unsigned int qemu_mpidr_to_core_pos(unsigned long mpidr)
{
return (unsigned int)plat_core_pos_by_mpidr(mpidr);
}
其实是调用plat_core_pos_by_mpidr
函数,实现如下。
/*******************************************************************************
* This function implements a part of the critical interface between the psci
* generic layer and the platform that allows the former to query the platform
* to convert an MPIDR to a unique linear index. An error code (-1) is returned
* in case the MPIDR is invalid.
******************************************************************************/
int plat_core_pos_by_mpidr(u_register_t mpidr)
{
unsigned int cluster_id, cpu_id;
mpidr &= MPIDR_AFFINITY_MASK;
if (mpidr & ~(MPIDR_CLUSTER_MASK | MPIDR_CPU_MASK))
return -1;
cluster_id = (mpidr >> MPIDR_AFF1_SHIFT) & MPIDR_AFFLVL_MASK;
cpu_id = (mpidr >> MPIDR_AFF0_SHIFT) & MPIDR_AFFLVL_MASK;
if (cluster_id >= PLATFORM_CLUSTER_COUNT)
return -1;
if (cpu_id >= PLATFORM_MAX_CPUS_PER_CLUSTER)
return -1;
return plat_qemu_calc_core_pos(mpidr);
}
core索引计算的方法为CorePos = (ClusterId * 4) + CoreId
。
/*
* unsigned int plat_qemu_calc_core_pos(u_register_t mpidr);
* With this function: CorePos = (ClusterId * 4) + CoreId
*/
func plat_qemu_calc_core_pos
and x1, x0, #MPIDR_CPU_MASK
and x0, x0, #MPIDR_CLUSTER_MASK
add x0, x1, x0, LSR #(MPIDR_AFFINITY_BITS -\
PLATFORM_CPU_PER_CLUSTER_SHIFT)
ret
endfunc plat_qemu_calc_core_pos
GIC初始化
Distributor Interface
调用函数gicv3_distif_init
初始化GIC Distributor接口,根据平台提供的中断信息数据,完成所有SPI中断的属性配置。
gicv3_distif_init();
Redistributor Interface
调用函数gicv3_rdistif_init
初始化GIC Redistributor接口,参数为plat_my_core_pos
,其返回当前CPU的core索引。
gicv3_rdistif_init(plat_my_core_pos());
最终根据MPIDR返回当前CPU的core索引。
func plat_my_core_pos
mrs x0, mpidr_el1
b plat_qemu_calc_core_pos
endfunc plat_my_core_pos
CPU Interface
调用函数gicv3_cpuif_enable
初始化GIC CPU接口,同样参数为plat_my_core_pos()
。
至此,qemu平台BL31阶段完成了GIC初始化和中断配置,后续中断发生后,GIC会向相应的CPU发送中断信号。
中断管理
中断管理框架负责管理路由到 EL3 的中断,它还允许 EL3 软件配置中断的路由行为,源码位于bl31/interrupt_mgmt.c
。中断管理框架定义了一个结构描述中断的相关信息:
typedef struct intr_type_desc {
interrupt_type_handler_t handler;
uint32_t flags;
uint32_t scr_el3[2];
} intr_type_desc_t;
- handler:中断处理程序
- flags:低两位存储中断的路由模型。Bit[0] 存储处于安全状态时的路由模型,Bit[1] 存储处于非安全状态时的路由模型, 值
0
意味着中断路由到 FEL ,值1
意味着中断路由到 EL3 - scr_el3:存储路由模型的映射,如从NS进入EL3,并在安全态的EL1进行处理等等
interrupt_type_handler_t
的原型声明如下:
typedef uint64_t (*interrupt_type_handler_t)(uint32_t id,
uint32_t flags,
void *handle,
void *cookie);
- id:保留
- flags:目前只用bits[0],表示中断生成时较低异常等级的安全状态。
1
表示处于非安全状态,0
表示它处于安全状态 - handle:指向当前安全状态的
cpu_context
上下文
在中断初始化时需要进行中断注册,用于中断处理,函数为register_interrupt_type_handler
,实现如下:
/*******************************************************************************
* This function registers a handler for the 'type' of interrupt specified. It
* also validates the routing model specified in the 'flags' for this type of
* interrupt.
******************************************************************************/
int32_t register_interrupt_type_handler(uint32_t type,
interrupt_type_handler_t handler,
uint32_t flags)
{
int32_t rc;
/* Validate the 'handler' parameter */
if (handler == NULL)
return -EINVAL;
/* Validate the 'flags' parameter */
if ((flags & INTR_TYPE_FLAGS_MASK) != 0U)
return -EINVAL;
/* Check if a handler has already been registered */
if (intr_type_descs[type].handler != NULL)
return -EALREADY;
rc = set_routing_model(type, flags);
if (rc != 0)
return rc;
/* Save the handler */
intr_type_descs[type].handler = handler;
return 0;
}
- 检查中断类型,及中断是否已经注册
- set_routing_model:配置路由模型,根据flags配置SCR_EL3.IRQ和SCR_EL3.FIQ的值,这里只是暂时将其存放到上下文cpu context中,当后面执行实际的上下文切换时,再配置到实际的SCR_EL3寄存器中去,以实现中断的路由
- 保存中断处理程序到
intr_type_descs
数组中
上面set_routing_model
函数实现如下:
/*******************************************************************************
* This function validates the routing model specified in the 'flags' and
* updates internal data structures to reflect the new routing model. It also
* updates the copy of SCR_EL3 for each security state with the new routing
* model in the 'cpu_context' structure for this cpu.
******************************************************************************/
int32_t set_routing_model(uint32_t type, uint32_t flags)
{
int32_t rc;
rc = validate_interrupt_type(type);
if (rc != 0)
return rc;
rc = validate_routing_model(type, flags);
if (rc != 0)
return rc;
/* Update the routing model in internal data structures */
intr_type_descs[type].flags = flags;
set_scr_el3_from_rm(type, flags, SECURE);
set_scr_el3_from_rm(type, flags, NON_SECURE);
return 0;
}
- validate_interrupt_type:首先校验中断类型
- validate_routing_model:接着校验中断模型
- intr_type_descs[type].flags = flags:变量intr_type_descs.flags用来描述安全/正常模式下SCR的设定
- set_scr_el3_from_rm(type, flags, SECURE):设置在CPU安全状态下(SCR.NS=0)的SCR.IRQ、SCR.FIQ位
- set_scr_el3_from_rm(type, flags, NON_SECURE):设置在CPU非安全状态下(SCR.NS=1)的SCR.IRQ、SCR.FIQ位
路由模型
Armv8架构和GIC一起设计了一套路由模型。GICv3将中断分为以下几组:
中断类型 | 使用示例 |
---|---|
Secure Group 0 | EL3中断(Secure Firmware) |
Secure Group 1 | Secure EL1中断(Trusted OS) |
Non-secure Group 1 | No-secure状态中断(OS或Hypervisor) |
Group 0 中断始终以 FIQ 形式发出信号,而 Group 1 中断以 IRQ 或 FIQ 形式,这取决于 PE 当前的安全状态和异常等级,如下:
PE的异常等级和安全状态 | Group 0 | Group 1 | Group 1 |
---|---|---|---|
Secure | Non-Secure | ||
Secure EL0/1 | FIQ | IRQ | FIQ |
Non-secure EL0/1/2 | FIQ | FIQ | IRQ |
EL3 | FIQ | FIQ | FIQ |
因此,总结起来看,不考虑EL2,在BL31中的中断路由模型要求如下:
- 如果当前PE正在EL3下执行,屏蔽所有IRQ和FIQ
- 对于Group 0(EL3中断),总是以FIQ触发,不论在哪个异常等级执行,最终均路由到EL3处理
- 对于Secure Group 1(Secure-EL1中断),如果PE正在Secure-EL1执行,以IRQ触发,直接在当前环境处理;如果PE正在Non-secure-EL1执行,以FIQ触发,路由到EL3,由EL3转发到Secure-EL1处理
- 对于Non-secure Group 1(Non-secure-EL1中断),如果PE正在Non-secure-EL1执行,以IRQ触发,直接在当前环境处理;如果PE正在Secure-EL1执行,以FIQ触发,路由到EL3,由EL3转发到Non-secure-EL1处理
根据当前PE的异常等级和安全状态,GIC实现中断以哪种方式触发(FIQ或IRQ形式)。而要配置中断路由到哪个异常等级处理,需要CPU配合参与,ARMv8是通过设置SCR_EL3寄存器中irq和fiq位实现,确定中断是被路由到当前异常等级还是EL3,其中SCR寄存器的定义如下:
- FIQ,bit[2]:0表示如果当前执行状态低于EL3,则FIQ不会路由到EL3,如果当前执行状态在EL3,则FIQ不会发生,即被屏蔽;1表示无论当前执行状态在哪个异常等级,FIQ均会路由到EL3
- IRQ,bit[2]:0表示如果当前执行状态低于EL3,则IRQ不会路由到EL3,如果当前执行状态在EL3,则IRQ不会发生,即被屏蔽;1表示无论当前执行状态在哪个异常等级,IRQ均会路由到EL3
中断处理
BL31异常处理位于bl31/aarch64/runtime_exceptions.S
文件中,其定义了bl31可以处理的异常向量表,如下
.globl runtime_exceptions
.globl sync_exception_sp_el0
.globl irq_sp_el0
.globl fiq_sp_el0
.globl serror_sp_el0
.globl sync_exception_sp_elx
.globl irq_sp_elx
.globl fiq_sp_elx
.globl serror_sp_elx
.globl sync_exception_aarch64
.globl irq_aarch64
.globl fiq_aarch64
.globl serror_aarch64
.globl sync_exception_aarch32
.globl irq_aarch32
.globl fiq_aarch32
.globl serror_aarch32
对于Armv8架构,异常向量有四张表,根据中断的不同状态,跳到相应的异常向量表中执行:
- 使用SP_EL0,异常来自当前异常等级
- 使用SP_ELx,异常来自当前异常等级
- 异常来自低异常等级,并且至少一个低异常等级是AArch64状态
- 异常来自低异常等级,并且所有低异常等级是AArch32状态
对于BL31,如果当前系统运行在EL3,则不允许再产生中断,即前两种类型异常BL31不会进行处理。ATF的BL31只实现了后面两种异常向量,以AArch64为例,又细分为四种异常类型:
- sync_exception_aarch64:同步异常,主要是SMC调用
- irq_aarch64:异步异常IRQ
- fiq_aarch64:异步异常FIQ
- serror_aarch64:系统错误SError
这里以fiq_aarch64
为例说明,当系统在低异常等级产生中断FIQ信号时,进入EL3异常向量表,调用handle_interrupt_exception
宏进行处理。
vector_entry fiq_aarch64
apply_at_speculative_wa
check_and_unmask_ea
handle_interrupt_exception fiq_aarch64
end_vector_entry fiq_aarch64
handle_interrupt_exception
主要实现系统寄存器保存,切换运行栈,检查中断是否合法,获取中断处理函数指针,跳转到中断处理函数,异常返回,其宏实现如下:
/* ---------------------------------------------------------------------
* This macro handles FIQ or IRQ interrupts i.e. EL3, S-EL1 and NS
* interrupts.
* ---------------------------------------------------------------------
*/
.macro handle_interrupt_exception label
/*
* Save general purpose and ARMv8.3-PAuth registers (if enabled).
* If Secure Cycle Counter is not disabled in MDCR_EL3 when
* ARMv8.5-PMU is implemented, save PMCR_EL0 and disable Cycle Counter.
* Also set the PSTATE to a known state.
*/
bl prepare_el3_entry
#if ENABLE_PAUTH
/* Load and program APIAKey firmware key */
bl pauth_load_bl31_apiakey
#endif
/* Save the EL3 system registers needed to return from this exception */
mrs x0, spsr_el3
mrs x1, elr_el3
stp x0, x1, [sp, #CTX_EL3STATE_OFFSET + CTX_SPSR_EL3]
/* Switch to the runtime stack i.e. SP_EL0 */
ldr x2, [sp, #CTX_EL3STATE_OFFSET + CTX_RUNTIME_SP]
mov x20, sp
msr spsel, #MODE_SP_EL0
mov sp, x2
/*
* Find out whether this is a valid interrupt type.
* If the interrupt controller reports a spurious interrupt then return
* to where we came from.
*/
bl plat_ic_get_pending_interrupt_type
cmp x0, #INTR_TYPE_INVAL
b.eq interrupt_exit_\label
/*
* Get the registered handler for this interrupt type.
* A NULL return value could be 'cause of the following conditions:
*
* a. An interrupt of a type was routed correctly but a handler for its
* type was not registered.
*
* b. An interrupt of a type was not routed correctly so a handler for
* its type was not registered.
*
* c. An interrupt of a type was routed correctly to EL3, but was
* deasserted before its pending state could be read. Another
* interrupt of a different type pended at the same time and its
* type was reported as pending instead. However, a handler for this
* type was not registered.
*
* a. and b. can only happen due to a programming error. The
* occurrence of c. could be beyond the control of Trusted Firmware.
* It makes sense to return from this exception instead of reporting an
* error.
*/
bl get_interrupt_type_handler
cbz x0, interrupt_exit_\label
mov x21, x0
mov x0, #INTR_ID_UNAVAILABLE
/* Set the current security state in the 'flags' parameter */
mrs x2, scr_el3
ubfx x1, x2, #0, #1
/* Restore the reference to the 'handle' i.e. SP_EL3 */
mov x2, x20
/* x3 will point to a cookie (not used now) */
mov x3, xzr
/* Call the interrupt type handler */
blr x21
interrupt_exit_\label:
/* Return from exception, possibly in a different security state */
b el3_exit
.endm
在BL31中,中断处理函数分为两种:Group 0中断,由异常处理框架ehf
注册和管理;Secure Group 1中断,一般是bl31为bl32接收并转发中断。
Group 0中断(EHF)
当编译使能EL3_EXCEPTION_HANDLING
宏时,就启用了EL3中的异常处理。在bl31_main
中会初始化EHF框架,如下:
#if EL3_EXCEPTION_HANDLING
INFO("BL31: Initialising Exception Handling Framework\n");
ehf_init();
#endif
ehf_init
会初始化EL3异常处理,执行中断检查,中断路由配置,注册EL3中断,如下:
/*
* Initialize the EL3 exception handling.
*/
void __init ehf_init(void)
{
unsigned int flags = 0;
int ret __unused;
/* Ensure EL3 interrupts are supported */
assert(plat_ic_has_interrupt_type(INTR_TYPE_EL3) != 0);
/*
* Make sure that priority water mark has enough bits to represent the
* whole priority array.
*/
assert(exception_data.num_priorities <= (sizeof(ehf_pri_bits_t) * 8U));
assert(exception_data.ehf_priorities != NULL);
/*
* Bit 7 of GIC priority must be 0 for secure interrupts. This means
* platforms must use at least 1 of the remaining 7 bits.
*/
assert((exception_data.pri_bits >= 1U) ||
(exception_data.pri_bits < 8U));
/* Route EL3 interrupts when in Non-secure. */
set_interrupt_rm_flag(flags, NON_SECURE);
/*
* Route EL3 interrupts when in secure, only when SPMC is not present
* in S-EL2.
*/
#if !(defined(SPD_spmd) && (SPMD_SPM_AT_SEL2 == 1))
set_interrupt_rm_flag(flags, SECURE);
#endif /* !(defined(SPD_spmd) && (SPMD_SPM_AT_SEL2 == 1)) */
/* Register handler for EL3 interrupts */
ret = register_interrupt_type_handler(INTR_TYPE_EL3,
ehf_el3_interrupt_handler, flags);
assert(ret == 0);
}
- assert:首先检查是否支持EL3中断,优先级位检查
- set_interrupt_rm_flag(flags, NON_SECURE):设置非安全状态执行的EL3中断路由标志
- set_interrupt_rm_flag(flags, SECURE):设置安全状态执行的EL3中断路由标志
- register_interrupt_type_handler:注册EL3中断,中断处理函数为ehf_el3_interrupt_handler
当EL3中断触发后,跳转到中断处理函数ehf_el3_interrupt_handler
,如下:
/*
* Top-level EL3 interrupt handler.
*/
static uint64_t ehf_el3_interrupt_handler(uint32_t id, uint32_t flags,
void *handle, void *cookie)
{
int ret = 0;
uint32_t intr_raw;
unsigned int intr, pri, idx;
ehf_handler_t handler;
/*
* Top-level interrupt type handler from Interrupt Management Framework
* doesn't acknowledge the interrupt; so the interrupt ID must be
* invalid.
*/
assert(id == INTR_ID_UNAVAILABLE);
/*
* Acknowledge interrupt. Proceed with handling only for valid interrupt
* IDs. This situation may arise because of Interrupt Management
* Framework identifying an EL3 interrupt, but before it's been
* acknowledged here, the interrupt was either deasserted, or there was
* a higher-priority interrupt of another type.
*/
intr_raw = plat_ic_acknowledge_interrupt();
intr = plat_ic_get_interrupt_id(intr_raw);
if (intr == INTR_ID_UNAVAILABLE)
return 0;
/* Having acknowledged the interrupt, get the running priority */
pri = plat_ic_get_running_priority();
/* Check EL3 interrupt priority is in secure range */
assert(IS_PRI_SECURE(pri));
/*
* Translate the priority to a descriptor index. We do this by masking
* and shifting the running priority value (platform-supplied).
*/
idx = pri_to_idx(pri);
/* Validate priority */
assert(pri == IDX_TO_PRI(idx));
handler = (ehf_handler_t) RAW_HANDLER(
exception_data.ehf_priorities[idx].ehf_handler);
if (handler == NULL) {
ERROR("No EL3 exception handler for priority 0x%x\n",
IDX_TO_PRI(idx));
panic();
}
/*
* Call registered handler. Pass the raw interrupt value to registered
* handlers.
*/
ret = handler(intr_raw, flags, handle, cookie);
return (uint64_t) ret;
}
- plat_ic_acknowledge_interrupt:首先是获取中断控制器(IC)中挂起的优先级最高的中断
- plat_ic_get_interrupt_id:获取最高优先级的中断ID,如果ID无效说明有其他优先级中断正在处理,直接返回
- plat_ic_get_running_priority:应答中断,获取运行优先级
- RAW_HANDLER:获取中断的处理程序
- handler:调用注册的处理程序,进行中断处理
上面的提到handler
是提前注册的处理程序,调用接口ehf_register_priority_handler
根据优先级注册到ehf中,如下:
/*
* Register a handler at the supplied priority. Registration is allowed only if
* a handler hasn't been registered before, or one wasn't provided at build
* time. The priority for which the handler is being registered must also accord
* with the platform-supplied data.
*/
void ehf_register_priority_handler(unsigned int pri, ehf_handler_t handler)
{
unsigned int idx;
/* Sanity check for handler */
assert(handler != NULL);
/* Handler ought to be 4-byte aligned */
assert((((uintptr_t) handler) & 3U) == 0U);
/* Ensure we register for valid priority */
idx = pri_to_idx(pri);
assert(idx < exception_data.num_priorities);
assert(IDX_TO_PRI(idx) == pri);
/* Return failure if a handler was already registered */
if (exception_data.ehf_priorities[idx].ehf_handler != EHF_NO_HANDLER_) {
ERROR("Handler already registered for priority 0x%x\n", pri);
panic();
}
/*
* Install handler, and retain the valid bit. We assume that the handler
* is 4-byte aligned, which is usually the case.
*/
exception_data.ehf_priorities[idx].ehf_handler =
(((uintptr_t) handler) | EHF_PRI_VALID_);
EHF_LOG("register pri=0x%x handler=%p\n", pri, handler);
}
当前SDEI在初始化时,会注册两种优先级的EHF中断,如下:
/* SDEI dispatcher initialisation */
void sdei_init(void)
{
plat_sdei_setup();
sdei_class_init(SDEI_CRITICAL);
sdei_class_init(SDEI_NORMAL);
/* Register priority level handlers */
ehf_register_priority_handler(PLAT_SDEI_CRITICAL_PRI,
sdei_intr_handler);
ehf_register_priority_handler(PLAT_SDEI_NORMAL_PRI,
sdei_intr_handler);
}
Secure Group 1中断
以optee为例,当optee初始化完成后,通过SMC(TEESMC_OPTEED_RETURN_ENTRY_DONE
)返回到BL31,会执行opteed_smc_handler
函数,然后注册了一个Secure-EL1中断处理函数,如下:
static uintptr_t opteed_smc_handler(uint32_t smc_fid,
u_register_t x1,
u_register_t x2,
u_register_t x3,
u_register_t x4,
void *cookie,
void *handle,
u_register_t flags)
{
cpu_context_t *ns_cpu_context;
uint32_t linear_id = plat_my_core_pos();
optee_context_t *optee_ctx = &opteed_sp_context[linear_id];
uint64_t rc;
/*.....*/
/*
* Returning from OPTEE
*/
switch (smc_fid) {
/*
* OPTEE has finished initialising itself after a cold boot
*/
case TEESMC_OPTEED_RETURN_ENTRY_DONE:
/*
* Stash the OPTEE entry points information. This is done
* only once on the primary cpu
*/
assert(optee_vector_table == NULL);
optee_vector_table = (optee_vectors_t *) x1;
if (optee_vector_table) {
set_optee_pstate(optee_ctx->state, OPTEE_PSTATE_ON);
/*
* OPTEE has been successfully initialized.
* Register power management hooks with PSCI
*/
psci_register_spd_pm_hook(&opteed_pm);
/*
* Register an interrupt handler for S-EL1 interrupts
* when generated during code executing in the
* non-secure state.
*/
flags = 0;
set_interrupt_rm_flag(flags, NON_SECURE);
rc = register_interrupt_type_handler(INTR_TYPE_S_EL1,
opteed_sel1_interrupt_handler,
flags);
if (rc)
panic();
}
/*
* OPTEE reports completion. The OPTEED must have initiated
* the original request through a synchronous entry into
* OPTEE. Jump back to the original C runtime context.
*/
opteed_synchronous_sp_exit(optee_ctx, x1);
break;
/*
* These function IDs is used only by OP-TEE to indicate it has
* finished:
* 1. turning itself on in response to an earlier psci
* cpu_on request
* 2. resuming itself after an earlier psci cpu_suspend
* request.
*/
case TEESMC_OPTEED_RETURN_ON_DONE:
case TEESMC_OPTEED_RETURN_RESUME_DONE:
/* .... */
default:
panic();
}
}
- set_interrupt_rm_flag(flags, NON_SECURE):配置在non-secure状态执行时的中断路由标志
- register_interrupt_type_handler:注册Secure-EL1中断处理程序,即当PE在non-secure状态执行时,触发了S-EL1中断的处理程序
opteed_sel1_interrupt_handler
是Secure-EL1的中断处理函数,实现如下:
/*******************************************************************************
* This function is the handler registered for S-EL1 interrupts by the
* OPTEED. It validates the interrupt and upon success arranges entry into
* the OPTEE at 'optee_fiq_entry()' for handling the interrupt.
******************************************************************************/
static uint64_t opteed_sel1_interrupt_handler(uint32_t id,
uint32_t flags,
void *handle,
void *cookie)
{
uint32_t linear_id;
optee_context_t *optee_ctx;
/* Check the security state when the exception was generated */
assert(get_interrupt_src_ss(flags) == NON_SECURE);
/* Sanity check the pointer to this cpu's context */
assert(handle == cm_get_context(NON_SECURE));
/* Save the non-secure context before entering the OPTEE */
cm_el1_sysregs_context_save(NON_SECURE);
/* Get a reference to this cpu's OPTEE context */
linear_id = plat_my_core_pos();
optee_ctx = &opteed_sp_context[linear_id];
assert(&optee_ctx->cpu_ctx == cm_get_context(SECURE));
cm_set_elr_el3(SECURE, (uint64_t)&optee_vector_table->fiq_entry);
cm_el1_sysregs_context_restore(SECURE);
cm_set_next_eret_context(SECURE);
/*
* Tell the OPTEE that it has to handle an FIQ (synchronously).
* Also the instruction in normal world where the interrupt was
* generated is passed for debugging purposes. It is safe to
* retrieve this address from ELR_EL3 as the secure context will
* not take effect until el3_exit().
*/
SMC_RET1(&optee_ctx->cpu_ctx, read_elr_el3());
}
- assert:首先检查中断生成时的安全状态是否是非安全,检查CPU上下文指针
- cm_el1_sysregs_context_save(NON_SECURE):保存非安全状态的系统寄存器上下文
- cm_get_context(SECURE)):获取optee的执行上下文
- cm_set_elr_el3:配置ELR_EL3寄存器,中断入口为optee_fiq_entry
- cm_el1_sysregs_context_restore:恢复Secure状态系统寄存器上下文
- cm_set_next_eret_context:设置异常返回的上下文为Secure
- SMC_RET1:执行SMC,进入optee的中断处理程序,处理FIQ
中断示例
以optee+atf+linux系统架构为例,即运行在Secure-EL1的optee,运行在Non-secure-ELl1的linux和运行在EL3的bl31,简单介绍一下两种需要路由转发的中断处理流程。
- PE在Non-Secure执行,来了S-EL1中断,以FIQ信号触发,路由到EL3,由EL3转发到Secure-EL1处理
- PE在Secure-EL1执行,来了Non-Secure中断,以FIQ信号触发,主动切换到EL3,由EL3进入Non-secure-EL1,中断以IRQ信号重新触发,进行处理
PE在Non-Secure执行,触发了S-EL1中断
- 当PE在非安全世界执行时,来了一个S-EL1中断,此中断在非安全世界以FIQ触发,将被路由到EL3
- PE读取VBAR_EL3寄存器获取EL3异常向量表基地址,并跳转到bl31的异常向量表
- 在bl31异常处理程序中,调用宏
handle_interrupt_exception
查询注册的中断处理函数,即opteed_sel1_interrupt_handler
,该函数将上下文切换成Secure-EL1,并配置上下文的入口函数optee_vector_table->fiq_entry
- 执行
el3_exit
退出EL3,使用获取到的CPU运行上下文进入optee,并从fiq_entry指定入口函数执行,开始对FIQ中断进行处理 - 当中断在optee中处理完成后,通过执行
SMC(TEESMC_OPTEED_RETURN_FIQ_DONE)
返回到bl31, 恢复non-secure上下文 - 执行
el3_exit
,返回非安全世界,恢复被中断的non-secure上下文,继续执行其他代码
PE在Secure-EL1执行,触发了Non-Secure中断
- 当PE在Secure-EL1执行,来了Non-Secure中断,此中断在安全世界以FIQ触发
- optee读取FIQ异常向量表,但是未对该中断执行应答操作,主动调用SMC(TEESMC_OPTEED_RETURN_CALL_DONE),切换到BL31,进入
opteed_smc_handler
函数,保存安全状态,并恢复non-secure上下文 - 执行
el3_exit
退出EL3,进入Non-Secure世界,此时中断重新以IRQ方式触发,进入Non-secure-EL中断向量表,执行相应的中断处理函数 - 当中断处理完成后,执行SMC(OPTEE_SMC_CALL_RETURN_FROM_RPC)调用,再次进入bl31,进入
opteed_smc_handler
函数,保存Non-Secure状态,并恢复Secure上下文 - 再次执行
el3_exit
退出EL3,进入Secure世界,恢复optee的执行
参考
- ATF bl31中断分析
- Arm通用中断控制器GICv3和GICv4
- 基于optee的可信操作系统(五) optee中断处理