(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);
#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 0EL3中断(Secure Firmware)
Secure Group 1Secure EL1中断(Trusted OS)
Non-secure Group 1No-secure状态中断(OS或Hypervisor)

Group 0 中断始终以 FIQ 形式发出信号,而 Group 1 中断以 IRQ 或 FIQ 形式,这取决于 PE 当前的安全状态和异常等级,如下:

PE的异常等级和安全状态Group 0Group 1Group 1
SecureNon-Secure
Secure EL0/1FIQIRQFIQ
Non-secure EL0/1/2FIQFIQIRQ
EL3FIQFIQFIQ

因此,总结起来看,不考虑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寄存器的定义如下:

SCR_EL3寄存器
SCR_EL3的IRQ和FIQ位

  • 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中断

interrupt_handling1

  1. 当PE在非安全世界执行时,来了一个S-EL1中断,此中断在非安全世界以FIQ触发,将被路由到EL3
  2. PE读取VBAR_EL3寄存器获取EL3异常向量表基地址,并跳转到bl31的异常向量表
  3. 在bl31异常处理程序中,调用宏handle_interrupt_exception查询注册的中断处理函数,即opteed_sel1_interrupt_handler,该函数将上下文切换成Secure-EL1,并配置上下文的入口函数optee_vector_table->fiq_entry
  4. 执行el3_exit退出EL3,使用获取到的CPU运行上下文进入optee,并从fiq_entry指定入口函数执行,开始对FIQ中断进行处理
  5. 当中断在optee中处理完成后,通过执行SMC(TEESMC_OPTEED_RETURN_FIQ_DONE) 返回到bl31, 恢复non-secure上下文
  6. 执行el3_exit,返回非安全世界,恢复被中断的non-secure上下文,继续执行其他代码

PE在Secure-EL1执行,触发了Non-Secure中断

interrupt_handling2

  1. 当PE在Secure-EL1执行,来了Non-Secure中断,此中断在安全世界以FIQ触发
  2. optee读取FIQ异常向量表,但是未对该中断执行应答操作,主动调用SMC(TEESMC_OPTEED_RETURN_CALL_DONE),切换到BL31,进入opteed_smc_handler函数,保存安全状态,并恢复non-secure上下文
  3. 执行el3_exit退出EL3,进入Non-Secure世界,此时中断重新以IRQ方式触发,进入Non-secure-EL中断向量表,执行相应的中断处理函数
  4. 当中断处理完成后,执行SMC(OPTEE_SMC_CALL_RETURN_FROM_RPC)调用,再次进入bl31,进入opteed_smc_handler函数,保存Non-Secure状态,并恢复Secure上下文
  5. 再次执行el3_exit退出EL3,进入Secure世界,恢复optee的执行

参考

  1. ATF bl31中断分析
  2. Arm通用中断控制器GICv3和GICv4
  3. 基于optee的可信操作系统(五) optee中断处理

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/402145.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

QT day3 作业2.22

思维导图&#xff1a; 作业&#xff1a; 完善对话框&#xff0c;点击登录对话框&#xff0c;如果账号和密码匹配&#xff0c;则弹出信息对话框&#xff0c;给出提示”登录成功“&#xff0c;提供一个Ok按钮&#xff0c;用户点击Ok后&#xff0c;关闭登录界面&#xff0c;跳转到…

JS前端高频面试

JS数据类型有哪些&#xff0c;区别是什么 js数据类型分为原始数据类型和引用数据类型。 原始数据类型包括&#xff1a;number&#xff0c;string&#xff0c;boolean&#xff0c;null&#xff0c;undefined&#xff0c;和es6新增的两种类型&#xff1a;bigint 和 symbol。&am…

2.22作业

test.c #include "test.h" seq_p creat_list(){seq_p L(seq_p)malloc(sizeof(seq_list));if(LNULL){printf("申请空间失败\n");return 0;}L->len0;return L; } int seq_p_empt(seq_p L){if(LNULL){return -12;}return L->len0?1:0; } int seq_p_fu…

PostgreSQL教程(二):pg安装、架构基础、创建并访问数据库

安装 自然&#xff0c;在你能开始使用PostgreSQL之前&#xff0c; 你必须安装它。PostgreSQL很有可能已经安装到你的节点上了&#xff0c; 因为它可能包含在你的操作系统的发布里&#xff0c; 或者是系统管理员已经安装了它。如果是这样的话&#xff0c; 那么你应该从操作系统…

BabylonJS 6.0文档 Deep Dive 动画(一):动画介绍

1. 动画介绍 无论动画如何实现&#xff0c;它都必须考虑所需的动作、时间、产生所需流动性所需的帧数以及序列中的关键点。这个介绍应该有助于理解Babylon.js是如何进行动画的&#xff0c;以及它们是如何实现的。 动画由一系列图像、帧生成&#xff0c;这些图像、帧一个接一个地…

Google插件Sider: ChatGPT Sidebar + GPTs GPT-4 Turbo Sider

Sider: ChatGPT Sidebar 可以使得满屏都是机器人&#xff0c;左侧栏可以打开访问GPT-4. 配置跳板机地址 google 搜索的右侧也有打开

MATLAB环境下基于短时傅里叶变换和Rényi熵的脑电信号和语音信号分析

傅里叶变换是不能很好的反映信号在时域的某一个局部范围的频谱特点的&#xff0c;这一点很可惜。因为在许多实际工程中&#xff0c;人们对信号在局部区域的特征是比较关心的&#xff0c;这些特征包含着十分有用的信息。这类信号因为在时域(或者是空间域)上具有突变的非稳定性和…

C语言自定义类型:结构体的使用及其内存对齐【超详细建议点赞收藏】

目录 1. 结构体类型的声明1.1 结构的声明1.2 结构体变量的创建和初始化1.3 结构的特殊声明---匿名结构体1.4 结构的自引用 2.结构体内存对齐&#xff08;重点&#xff01;&#xff01;&#xff09;2.1 对齐规则2.2 例题讲解2.3 为什么存在内存对齐&#xff1f;2.4 修改默认对齐…

华为全新研发中心即将启用,投资超百亿 | 百能云芯

2月19日 &#xff0c;上海市发改委网站发布了《2024年上海市重大工程清单》&#xff0c;内容涉及科技产业、社会民生、生态文明建设、城市基础设施、城乡融合与乡村振兴等五大类&#xff0c;共191项重大工程。 191项重大工程中&#xff0c;科技产业类占比最多&#xff08;76项&…

Spring Boot打war包部署到Tomcat,访问页面404 !!!

水善利万物而不争&#xff0c;处众人之所恶&#xff0c;故几于道&#x1f4a6; 文章目录 Spring Boot打war包部署到Tomcat&#xff0c;访问页面404 &#xff01;&#xff01;&#xff01;解决办法&#xff1a;检查Tomcat版本和Jdk的对应关系&#xff0c;我的Tomcat是6.x&#x…

RK3568平台开发系列讲解(Linux系统篇)通过I2C总线访问客户端方法

🚀返回专栏总目录 文章目录 一、普通I2C通信二、系统管理总线(SMBus)兼容函数三、在开发板配置文件中实例化I2C设备(弃用的旧方式)沉淀、分享、成长,让自己和他人都能有所收获!😄 串行总线事务只是访问寄存器来设置/获取其内容。I2C遵循该规则。I2C内核提供两种API,…

(十二)【Jmeter】线程(Threads(Users))之setUp 线程组

简述 操作路径如下: 作用:在正式测试开始前执行预加载或预热操作,为测试做准备。配置:设置预加载或预热操作的采样器、循环次数等参数。使用场景:确保在正式测试开始前应用程序已经达到稳定状态,减少测试结果的偏差。优点:提供预加载或预热操作,确保测试的准确性。缺…

Code Control Process

代码提交流程&#xff08;Code Control Process&#xff09; VSS&#xff0c;早前定义的版本控制&#xff0c;没有谁对不对&#xff0c;但是要根本解决冲突&#xff0c;特别人多的时候&#xff0c;50个人的时候&#xff0c;处理冲突时非常的麻烦的&#xff0c;改半天还改错了&…

C++模板从入门到入土

1. 泛型编程 如果我们需要实现一个不同类型的交换函数&#xff0c;如果是学的C语言&#xff0c;你要交换哪些类型&#xff0c;不同的类型就需要重新写一个来实现&#xff0c;所以这是很麻烦的&#xff0c;虽然可以cv一下&#xff0c;有了模板就可以减轻负担。 下面写一个适…

重新安装VSCode后,按住Ctrl(or Command) 点击鼠标左键不跳转问题

重新安装VSCode后&#xff0c;按住Ctrl&#xff08;or Command&#xff09; 点击鼠标左键不跳转问题 原因&#xff1a;重新安装一般是因为相应编程语言的插件被删除了或还没有下载。 本次是由于Python相关的插件被删除了&#xff0c;因此导致Python无法跳转。 解决办法 在vs…

人工智能会是第四次工业革命吗?引领第四次工业革命的核心力量

许多专家和学者确实认为人工智能&#xff08;AI&#xff09;将是第四次工业革命的核心。第四次工业革命&#xff0c;也被称为"工业4.0"&#xff0c;是指正在发生的一场以高度数字化和互联网为基础的技术革新。 自18世纪的蒸汽机&#xff0c;到20世纪的电力和信息技术…

YOLO-World初体验:Ultralytics版本,可直接上手,离线运行

YOLOv8官方新增了对YOLO-World的支持&#xff0c;本文利用其提供的模型及接口进行了体验。 关于YOLO-World的详细介绍&#xff0c;见&#xff1a;YOLO-World&#xff1a;实时开放词汇目标检测-CSDN博客 目录 1. 前言 2. 安装&#xff08;更新&#xff09; Ultralytics安装&am…

低代码开发:拖拉拽自定义表单的创新之道

一、前言 在软工圣经《人月神话》一书中&#xff0c;作者Brooks指出了软件发展的一个僵局&#xff1a;在落后的项目中增加人手&#xff0c;只会使进度更加落后。 为了更快完成项目&#xff0c;开发团队会发展的极其庞大&#xff0c;以致于所有的时间都花费在沟通和变更决策上&a…

Apache服务

目录 引言 一、常见的http服务程序 &#xff08;一&#xff09;lls &#xff08;二&#xff09;nginx &#xff08;三&#xff09;Apache &#xff08;四&#xff09;Tomcat 二、Apache特点 三、Apache服务的安装 &#xff08;一&#xff09;yum安装及配置文件 1.配置…

Mybatis速成(二)

文章目录 1. Mybatis基础操作1.1 需求1.2 准备1.3 删除1.3.1 功能实现1.3.2 日志输入1.3.3 预编译SQL1.3.3.1 介绍1.3.3.2 SQL注入1.3.3.3 参数占位符 1.4 新增1.4.1 基本新增1.4.2 主键返回 1.5 更新1.6 查询1.6.1 根据ID查询1.6.2 数据封装1.6.3 条件查询1.6.4 参数名说明 2.…