本文主要是介绍(12)ATF BL31中断,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
欢迎关注“安全有理”微信公众号。
概述
系统在运行过程中的任何阶段,都有可能产生中断。在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);
#elseassert((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_EXTNassert(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_COHERENCYflush_dcache_range((uintptr_t)&gicv3_driver_data,sizeof(gicv3_driver_data));flush_dcache_range((uintptr_t)gicv3_driver_data,sizeof(*gicv3_driver_data));
#endifgicv3_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_posand x1, x0, #MPIDR_CPU_MASKand x0, x0, #MPIDR_CLUSTER_MASKadd 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_posmrs x0, mpidr_el1b 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_aarch64apply_at_speculative_wacheck_and_unmask_eahandle_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_el3mrs x1, elr_el3stp 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, spmsr spsel, #MODE_SP_EL0mov 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_typecmp x0, #INTR_TYPE_INVALb.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_handlercbz x0, interrupt_exit_\labelmov x21, x0mov x0, #INTR_ID_UNAVAILABLE/* Set the current security state in the 'flags' parameter */mrs x2, scr_el3ubfx 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 x21interrupt_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_HANDLINGINFO("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中断处理
这篇关于(12)ATF BL31中断的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!