当前位置:   article > 正文

【ARM64 ATF 系列 2.1 - ATF 与 kernel 中从处理器启动】

arm64 atf

1.1 kernel 中启动从处理器

对称多处理器(Symmetric Multi-Processor, SMP)系统包含多个处理器,如 4 核Cortex-A53。在启动过程中,处理器的地位不是平等的; core0 即 0 号处理器为引导处理器,负责执行引导程序和初始化内核;其他 core 处理器称为从处理器,等待引导处理器完成初始化。引导处理器完成初始化内核后,启动从处理器。

引导处理器启动从处理器的方法有 3 种。

  • 自旋表(spin-table);
  • 电源状态协调接口(Power State Coordination Interface, PSCI);
  • ACPI 停车协议(parking-protocol), ACPI 是高级配置与电源接口(Advanced Configuration and Power Interface)。

现在很少使用 spin-table 方式启动从核,取而代之的是 PSCIPSCI 不仅可以启动从处理器,还可以关闭,挂起等其他核操作,现在基本上 ARM64平台上使用多核启动方式都是 PSCIPSCI简单点来说也是需要主处理器给从处理器一个启动地址,然后从处理器从这个地址执行指令

主处理器通过 SMC 进入EL3 请求开核服务,ATF 中会响应这种请求,通过平台的开核操作来启动从处理器并且设置从处理的一些寄存器e.g. scr_el3、spsr_el3、elr_el3,然后主处理器,恢复现场,ERET 再次回到 EL1, 而处理器开核之后会从bl31_warm_entrypoint 开始执行,最后通过 el3_exit 返回到 EL1ELR_EL3 设置的地址。

实际上,所有的从处理器启动后都会从 bl31_warm_entrypoint 开始执行,每个平台都有自己的启动地址寄存器,通过写这个寄存器来获得上电后执行的指令地址。

ARM64  RMVBAR_ELx
  • 1

1.2 ATF 从处理器启动

Armv8 将异常等级分为 el0 - el3,其中,el3 为安全监控器,为了实现对它的支持,arm公司设计了一种 firmware 叫做 ATF(ARM Trusted firmware)。ATF代码运行在 EL3, 是实现安全相关的软件部分固件,其中会为其他特权级别提供服务,也就是说提供了在 EL3 中服务的手段。

1.2.1 EL31 处理总体流程(bl31)

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   //调用各种运行时服务历程,见下节内容
...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

1.2.2 DECLARE_RT_SVC 服务注册

在标准的运行时服务中将服务初始化和处理函数放到 rt_svc_descs 段中,供调用。
链接脚本中:
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段结束
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

runtime_svc_init 函数中,调用每一个通过 DECLARE_RT_SVC 注册的服务,其中包括 std_svc 服务:
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 //处理
  );
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

1.2.3 运行时服务初始化处理

在遍历每一个注册的运行时服务的时候,会导致 std_svc_setup 调用,其中会做 psci操作集的设置,操作集中我们可以看到对核电源的管理的接口如:核上电,下电,挂起等,我们主要关注上电 .pwr_domain_on = qemu_pwr_domain_on, 这个接口当我们主处理器 boot 从处理器的时候会用到。

std_svc_setup     //services/std_svc/std_svc_setup.c
  -->psci_setup   //lib/psci/psci_setup.c
  • 1
  • 2

设置平台的PSCI操作调用平台的 plat_setup_psci_ops 函数去设置 PSCI 操作eg:qemu 平台:

 ->plat_setup_psci_ops   
  ->*psci_ops = &plat_qemu_psci_pm_ops;
  static const plat_psci_ops_t plat_qemu_psci_pm_ops = {
        .cpu_standby = qemu_cpu_standby,
        .pwr_domain_on = qemu_pwr_domain_on,
        .pwr_domain_off = qemu_pwr_domain_off, 
        .pwr_domain_suspend = qemu_pwr_domain_suspend,
        .pwr_domain_on_finish = qemu_pwr_domain_on_finish,
        .pwr_domain_suspend_finish = qemu_pwr_domain_suspend_finish,
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

1.2.4 smc指令触发进入el3异常向量表

runtime_exceptions  //el3的异常向量表>sync_exception_aarch64
->handle_sync_exception
->smc_handler64
...
blr     x15       //跳转到处理函数
b       el3_exit  //从el3 退出  会eret回到el1(后面会讲到)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

上面其实主要的是找到服务例程,然后跳转执行,下面是跳转的处理函数:

std_svc_smc_handler  //services/std_svc/std_svc_setup.c
->ret = psci_smc_handler(smc_fid, x1, x2, x3, x4,  cookie, handle, flags)
    /* 64-bit PSCI function */
    switch (smc_fid) {
        case PSCI_CPU_SUSPEND_AARCH64:
             ret = (u_register_t)
             psci_cpu_suspend((unsigned int)x1, x2, x3);
             break;
        case PSCI_CPU_ON_AARCH64:
            ret = (u_register_t)psci_cpu_on(x1, x2, x3);
            break;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

处理函数根据 smc function id 来决定服务, 可以看到 PSCI_CPU_ON_AARCH640xc4000003,这正是设备树中填写的 cpu_on属性的 ID,会委托 psci_cpu_on 来执行核上电任务。

1.2.5 ATF 开核工作

->psci_cpu_on()  //lib/psci/psci_main.c
 ->psci_validate_entry_point() //验证入口地址有效性并保存入口点到一个结构ep中
 ->psci_cpu_on_start(target_cpu, &ep) //ep入口地址
  ->psci_plat_pm_ops->pwr_domain_on(target_cpu)
   ->qemu_pwr_domain_on  //实现核上电(平台实现)
      ->cm_init_context_by_index()  
          ->cm_setup_context() //设置cpu上下文
              -> write_ctx_reg(state, CTX_SCR_EL3, scr_el3);
                  ->write_ctx_reg(state, CTX_ELR_EL3, ep->pc); 
                      ->write_ctx_reg(state, CTX_SPSR_EL3, ep->spsr)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

函数注释:
cm_init_context_by_index() 会通过 cpu 的编号找到 cpu 上下文(cpu_context_t),存在 cpu 寄存器的值,异常返回的时候写写到对应的寄存器中,然后 eret ,就返回到了 el1
cm_setup_context() //设置cpu上下文

lib/el3_runtime/aarch64/context_mgmt.c

write_ctx_reg(state, CTX_SCR_EL3, scr_el3);  
  • 1

write_ctx_reg(state, CTX_ELR_EL3, ep->pc);

异常返回时执行此地址 于是完成了cpu的启动

1.2.6 psci_cpu_on 小结

psci_cpu_on 主要完成开核的工作,然后会设置一些异常返回后寄存器的值(eg :从el1 -> el3 -> el1),重点关注 ep->pc 写到 cpu_context 结构的 CTX_ELR_EL3 偏移处(从处理器启动后会从这个地址取指执行)。
实际上,所有的从处理器启动后都会从 bl31_warm_entrypoint 开始执行,在 plat_setup_psci_ops 中会设置(每个平台都有自己的启动地址寄存器,通过写这个寄存器来获得上电后执行的指令地址)。

启动从处理的时候最终调用到 psci 的cpu操作集的 cpu_psci_cpu_boot 函数,会调用上面的 psci_cpu_on,最终调用 smc,传递第一个参数为 cpu 的 id 标识启动哪个cpu,第二个参数为从处理器启动后进入内核执行的地址 secondary_entry(这是个物理地址)。

所以综上,最后 smc 调用时传递的参数为 arm_smccc_smc(0xC4000003, cpuid, secondary_entry, arg2, 0, 0, 0, 0, &res)
这样陷入 el3 之后,就可以启动对应的从处理器,最终从处理器回到内核(el3->el1),执行 secondary_entry 处指令,从处理器启动完成。

1.3 从处理器进入内核后的动作

ATF 中主要是响应内核的smc的请求,然后做开核处理,也就是实际的开核动作,但是从处理器最后还是要回到内核中执行。
init/main.c

start_kernel
->boot_cpu_init // 引导cpu初始化设置引导cpu的位掩码 online active
			    // present possible都为true
->setup_arch                // arch/arm64/kernel/setup.c
->  if (acpi_disabled)      // 不支持acpi
            psci_dt_init(); // drivers/firmware/psci.c(psci主要文件)
            				// psci初始化 解析设备树寻找PSCI匹配的节点
       else
             psci_acpi_init();   // acpi中允许使用psci情况
->rest_init
->kernel_init
->kernel_init_freeable
->smp_prepare_cpus        // 准备cpu对于每个可能的cpu, 
	1. cpu_ops[cpu]->cpu_prepare(cpu)    
	2. set_cpu_present(cpu, true) cpu处于present状态
->do_pre_smp_initcalls   // 多核启动之前的调用initcall回调
->smp_init               // smp初始化kernel/smp.c会启动其他从处理器
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

1.3.1 psci_dt_init()

主要关注两个函数:psci_dt_initsmp_init, psci_dt_init 是解析设备树,设置操作函数,smp_init 用于启动从处理器。

->psci_dt_init() //drivers/firmware/psci.c:
 ->init_fn()
  ->psci_0_1_init()  //设备树中compatible = "arm,psci"为例
     				//根据设备树method 属性设置 invoke_psci_fn =
     				// __invoke_psci_fn_smc;  (method="smc")   
    ->get_set_conduit_method() 
       -> invoke_psci_fn = __invoke_psci_fn_smc
   ->   if (!of_property_read_u32(np, "cpu_on", &id))  {
                        psci_function_id[PSCI_FN_CPU_ON] = id;
                     psci_ops.cpu_on = psci_cpu_on;//设置psci操作的开核
             }
    ->psci_cpu_on()
     ->invoke_psci_fn()
      ->__invoke_psci_fn_smc()
        //x0=function_id  x1=arg0, x2=arg1, x3=arg2,...
        -> arm_smccc_smc(function_id, arg0, arg1, arg2, 0, 0, 0, 0, &res) 
         ->__arm_smccc_smc()
          ->SMCCC   smc //arch/arm64/kernel/smccc-call.S
            ->          .macro SMCCC instr
                         .cfi_startproc
                         \instr  #0   //即是SMC #0  陷入到el3
                         ldr     x4, [sp]
                         stp     x0, x1, [x4, #ARM_SMCCC_RES_X0_OFFS]
                         stp     x2, x3, [x4, #ARM_SMCCC_RES_X2_OFFS]
                         ldr     x4, [sp, #8]
                         cbz     x4, 1f /* no quirk structure */
                         ldr     x9, [x4, #ARM_SMCCC_QUIRK_ID_OFFS]
                         cmp     x9, #ARM_SMCCC_QUIRK_QCOM_A6
                         b.ne    1f
                         str     x6, [x4, ARM_SMCCC_QUIRK_STATE_OFFS]
                 1:      ret
                         .cfi_endproc
                         .endm
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

启动从处理的时候最终调用到 psci 的 cpu 操作集的 cpu_psci_cpu_boot 函数,会调用上面的 psci_cpu_on,最终调用 smc
传递第一个参数为 cpu 的 ID 标识启动哪个cpu,
第二个参数为从处理器启动后进入内核执行的地址 secondary_entry(这是个物理地址)。
所以综上,最后 smc 调用时传递的参数为 arm_smccc_smc(0xC4000003, cpuid, secondary_entry, arg2, 0, 0, 0, 0, &res)
这样陷入 el3 之后,就可以启动对应的从处理器,最终从处理器回到内核(el3->el1),执行 secondary_entry 处指令,从处理器启动完成。

1.3.2 从处理器启动进入内核世界之后

无论是 spin-table 还是 psci,从处理器启动进入内核之后都会执行secondary_startup
__cpu_setup 中设置了 secondary_data 结构中的一些成员:跳转到 secondary_start_kernel 这个C函数继续执行初始化:

当从处理器启动到内核的时候,他们也需要设置异常向量表,设置 mmu 等,然后执行各自的 idle 进程(这些都是一些处理器强相关的初始化代码,一些通用的初始化都已经被主处理器初始化完),当 cpu 负载均衡 的时候会放置一些进程到这些从处理器,然后进程就可以再这些从处理器上欢快的运行。

推荐阅读
https://blog.csdn.net/rockrockwu/article/details/103698045
https://blog.csdn.net/big2chris/article/details/53048880
https://zhuanlan.zhihu.com/p/373964690
https://zhuanlan.zhihu.com/p/373964690

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/129964
推荐阅读
相关标签
  

闽ICP备14008679号