ARM
上面说了pin-table的多核启动方式,看似很繁琐,实际上并不复杂,无外乎主处理器唤醒从处理器到指定地址上去执行指令,说他简单是相对于功能来说的,因为他只是实现了从处理器的启动,仅此而已,所以,现在社区几乎很少使用spin-table这种方式,取而代之的是psci,他不仅可以启动从处理器,还可以关闭,挂起等其他核操作,现在基本上arm64平台上使用多核启动方式都是psci。下面我们来揭开他神秘的面纱,其实理解了spin-table的启动方式,psci并不难( 说白了也是需要主处理器给从处理器一个启动地址,然后从处理器从这个地址执行指令 ,实际上比这要复杂的多)。
首先,我们先来看下设备树cpu节点对psci的支持:
arch/arm64/boot/dts/xxx.dtsi:
cpu0: cpu@0 {
device_type = "cpu";
compatible = "arm,armv8";
reg = < 0x0 >;
enable-method = "psci";
};
psci {
compatible = "arm,psci";
method = "smc";
cpu_suspend = < 0xC4000001 >;
cpu_off = < 0x84000002 >;
cpu_on = < 0xC4000003 >;
};
psci节点的详细说明可以参考内核文档:Documentation/devicetree/bindings/arm/psci.txt
可以看到现在enable-method 属性已经是psci,说明使用的多核启动方式是psci, 下面还有psci节点,用于psci驱动使用,method用于说明调用psci功能使用什么指令,可选有两个smc和hvc。其实smc, hvc和svc都是从低运行级别向高运行级别请求服务的指令,我们最常用的就是svc指令了,这是实现系统调用的指令。高级别的运行级别会根据传递过来的参数来决定提供什么样的服务。smc是用于陷入el3(安全), hvc用于陷入el2(虚拟化, 虚拟化场景中一般通过hvc指令陷入el2来请求唤醒vcpu), svc用于陷入el1(系统)。
注: 本文只讲解smc陷入el3启动多核的情况 。
下面开始分析源代码:
我们都知道armv8将异常等级分为el0 - el3,其中,el3为安全监控器,为了实现对它的支持,arm公司设计了一种firmware叫做ATF(ARM Trusted firmware),下面是atf源码readme.rst文件的一段介绍:
Trusted Firmware-A (TF-A) provides a reference implementation of secure world
software for `Armv7-A and Armv8-A`_, including a `Secure Monitor`_ executing
at Exception Level 3 (EL3). It implements various Arm interface standards,
such as:
- The `Power State Coordination Interface (PSCI)`_
- Trusted Board Boot Requirements (TBBR, Arm DEN0006C-1)
- `SMC Calling Convention`_
- `System Control and Management Interface (SCMI)`_
- `Software Delegated Exception Interface (SDEI)`_
ATF代码运行在EL3, 是实现安全相关的软件部分固件,其中会为其他特权级别提供服务,也就是说提供了在EL3中服务的手段,我们本文介绍的PSCI的实现就是在这里面,本文不会过多的讲解( 注:其实本文只会涉及到atf如何响应服务el1的smc发过来的psci的服务请求,仅此而已,有关ATF(Trustzone)请参考其他资料 )。
那么就开始我们的正题:
下面从源代码角度分析服务的注册处理流程:
atf/bl31/aarch64/bl31_entrypoint.S: //架构相关
bl31_entrypoint
- >el3_entrypoint_common
_exception_vectors=runtime_exceptions //设置el3的异常向量表
- >bl bl31_early_platform_setup //跳转到平台早期设置
- >bl bl31_plat_arch_setup //跳转到平台架构设置
- > bl bl31_main //跳转到bl31_main atf/bl31/aarch64/bl31_main.c:
- >NOTICE("BL31: %s\\n", version_string); //打印版本信息
- >NOTICE("BL31: %s\\n", build_message); //打印编译信息
- >bl31_platform_setup //执行平台设置
- > /* Initialize the runtime services e.g. psci. */ 初始化运行时服务 如psci
INFO("BL31: Initializing runtime services\\n") //打印log信息
- >runtime_svc_init //调用各种运行时服务历程
...
下面的宏是用于注册运行时服务的接口,每种服务通过它来注册:
/*
* Convenience macro to declare a service descriptor 定义运行时服务描述符结构的宏
*/
#define DECLARE_RT_SVC(_name, _start, _end, _type, _setup, _smch) \\
static const rt_svc_desc_t __svc_desc_ ## _name \\
__section("rt_svc_descs") __used = { \\ //结构放在rt_svc_descs段中
.start_oen = _start, \\
.end_oen = _end, \\
.call_type = _type, \\
.name = #_name, \\
.init = _setup, \\
.handle = _smch }
链接脚本中:
bl31/bl31.ld.S:
...
.rodata . : {
__RT_SVC_DESCS_START__ = .; rt_svc_descs段开始
KEEP(*(rt_svc_descs)) //rt_svc_descs段
__RT_SVC_DESCS_END__ = .; rt_svc_descs段结束
}
...
在标准的运行时服务中将服务初始化和处理函数放到rt_svc_descs段中,供调用。
services/std_svc/std_svc_setup.c:
DECLARE_RT_SVC(
std_svc,
OEN_STD_START,
OEN_STD_END,
SMC_TYPE_FAST,
std_svc_setup,//初始化
std_svc_smc_handler //处理
);
在runtime_svc_init函数中,调用每一个通过DECLARE_RT_SVC注册的服务,其中包括std_svc服务:
for (index = 0; index < RT_SVC_DECS_NUM; index++) {
rt_svc_desc_t *service = &rt_svc_descs[index];
...
rc = service- >init(); //调用每一个注册的运行时服务的设置函数
...
}
std_svc_setup (主要关注设置psci操作集)
std_svc_setup //services/std_svc/std_svc_setup.c
- >psci_setup //lib/psci/psci_setup.c
- >plat_setup_psci_ops //设置平台的psci操作 调用平台的plat_setup_psci_ops函数去设置psci操作 eg:qemu平台
- >*psci_ops = &plat_qemu_psci_pm_ops;
208 static const plat_psci_ops_t plat_qemu_psci_pm_ops = {
209 .cpu_standby = qemu_cpu_standby,
210 .pwr_domain_on = qemu_pwr_domain_on,
211 .pwr_domain_off = qemu_pwr_domain_off,
212 .pwr_domain_suspend = qemu_pwr_domain_suspend,
213 .pwr_domain_on_finish = qemu_pwr_domain_on_finish,
214 .pwr_domain_suspend_finish = qemu_pwr_domain_suspend_finish,
215 .system_off = qemu_system_off,
216 .system_reset = qemu_system_reset,
217 .validate_power_state = qemu_validate_power_state,
218 .validate_ns_entrypoint = qemu_validate_ns_entrypoint
219 };
可以看到,在遍历每一个注册的运行时服务的时候,会导致std_svc_setup调用,其中会做psci操作集的设置,操作集中我们可以看到对核电源的管理的接口如:核上电,下电,挂起等,我们主要关注上电 .pwr_domain_on = qemu_pwr_domain_on ,这个接口当我们主处理器boot从处理器的时候会用到。
smc指令触发进入el3异常向量表:
runtime_exceptions //el3的异常向量表
- >sync_exception_aarch64
- >handle_sync_exception
- >smc_handler64
- > ¦* Populate the parameters for the SMC handler.
¦* We already have x0-x4 in place. x5 will point to a cookie (not used
¦* now). x6 will point to the context structure (SP_EL3) and x7 will
¦* contain flags we need to pass to the handler Hence save x5-x7.
¦*
¦* Note: x4 only needs to be preserved for AArch32 callers but we do it
¦* for AArch64 callers as well for convenience
¦*/
stp x4, x5, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X4] //保存x4-x7到栈
stp x6, x7, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X6]
/* Save rest of the gpregs and sp_el0*/
save_x18_to_x29_sp_el0
mov x5, xzr //x5清零
mov x6, sp //sp保存在x6
/* Get the unique owning entity number */ //获得唯一的入口编号
ubfx x16, x0, #FUNCID_OEN_SHIFT, #FUNCID_OEN_WIDTH
ubfx x15, x0, #FUNCID_TYPE_SHIFT, #FUNCID_TYPE_WIDTH
orr x16, x16, x15, lsl #FUNCID_OEN_WIDTH
adr x11, (__RT_SVC_DESCS_START__ + RT_SVC_DESC_HANDLE)
/* Load descriptor index from array of indices */
adr x14, rt_svc_descs_indices //获得服务描述 标识数组
ldrb w15, [x14, x16] //根据唯一的入口编号 找到处理函数的 地址
/*
¦* Restore the saved C runtime stack value which will become the new
¦* SP_EL0 i.e. EL3 runtime stack. It was saved in the 'cpu_context'
¦* structure prior to the last ERET from EL3.
¦*/
ldr x12, [x6, #CTX_EL3STATE_OFFSET + CTX_RUNTIME_SP]
/*
¦* Any index greater than 127 is invalid. Check bit 7 for
¦* a valid index
¦*/
tbnz