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

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.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.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段结束
}

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 运行时服务初始化处理

在遍历每一个注册的运行时服务的时候,会导致 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

设置平台的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.4 smc指令触发进入el3异常向量表

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

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

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;

处理函数根据 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)

函数注释:
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);  

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.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

启动从处理的时候最终调用到 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

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
# 学习对象在全民造车、造芯的大时代,在努力去解决卡脖子的时代,ASIC硬件、SOC底层软件、Linux Kernel等操作系统软件(内核/驱动)、软硬件方面的系统架构师等的岗位需求也越来越明显,社会一直都是非常缺人的,缺的是核心的那一小撮、领头的那一小撮,社会所缺的更是能够软硬件融合的那一小撮人……总之,要想在这个时代,站稳自己的脚跟,能够在大公司或行业上拥有一席之地,就必需深入学习底层技术原理,核心技术才是您的看家本领。本课程设计之初,主要针对SOC底层软件开发的者、系统开发者,或者励志成为这样的人。既适合资深/高级工程师来查缺补漏,又适合初级工程师入门。(理论上该课程和ASIC硬件电路设计无关,该课程偏软件,但事实购买该课程的做ASIC的同学已然超过了15%)适用人群1、芯片开发者(包括底层软件、或做ASIC硬件的)。不限行业,例如车、云、物联网、移动端等领域;2、汽车行业开发者(主机厂、tier1、SOC厂家、各级供应商);3、嵌入式开发者、kernel开发者、驱动、软件工程师;4、学生。既适合学生从入门到精通,也适合资深工程师查缺补漏;您的收益:1、全体系的掌握ARMv8/ARMv9的核心知识点(ARM基础、异常断GIC、MMU/Cache、architecture...);2、掌握ARM架构、掌握SOC架构、掌握常规IP(gic、smmu、timer、AXI/ACE/CHI、TZC400...);3、快速熟悉常规系统软件(bootrom、spl、ATF、TEE、bootloader、kernel...), Secureboot安全启动...4、技术水平提升N个level, 掌握快速的学习方法;# 学习什么在ARM蓬勃发展的年代,不仅仅涉及到物联网IOT、移动领域(如手机)、汽车电子领域,现在还涉及到PC、服务器的,简直就是各行各业。ARMv8出来已经有10年了,ARMv9也2年时间了。在技术不断更新迭代的背景下,此时再去学习十五年前的ARMv7、二十年前的ARMv5/v6显然不是明智的选择。本课程主要基于当前最新的架构,ARMv8的aarch64和ARMv9,如涉及具体的ARM Core IP主要还是以最新的ARM Core IP为主,软件架构也是以当前最主流的/未来所趋势的架构来讲解。以下也给大家列举初了一个ARM产品的timeline的总结(在本课程有着大量的这种总结),从这张图,您是可以清晰的看到本课程拥有独具一格的风格、拥有全网最新(且唯一)的资料总结或学习路线。# 本课程大纲和规划(课程持续更新,课程总量统计:2022/10/02  当前是 61节课, 22小时)第一章:主要是快速学习: ARM简介、指令集、寄存器总结等。第二章:本系列视频的一大亮点,系统全面地讲解了arm异常断gic等相关的软硬件知识,本人一直在倡导“学arm安全其实就是学arm架构,学arm架构其实就是学习arm的异常和断”,异常断是领着你进入架构的入门,是让你变成系统软硬件架构师的必走之路。第三章:安全专题,这也是本视频最核心的东西。因为你无论买书还是看博客等,你都很难找到讲解安全的教程,这里就是有和无的区别。本人系统的整理的安全的知识,带领你快速入门。第四章:mmu专题,透过事务看本质的讲解,白话式的演讲。在所有模块,mmu也算是相对较简单模块。相信人人听得懂,人人学得会。第五章:cache专题,一切追求实事求是,不人云亦云,一切知识点都有迹可循,推翻了网络的很多观念。在众多模块,cache算是一个比较难的模块。了解了cache后,才能算真正了解系统的软硬件架构。第六章:虚拟化,本人不擅长,会啥就随便讲点啥。(以后学会了再来补)第七章:architecture,就是零散和零碎的系统架构知识,如exclusive、arch timer、reset、系统启动、SOC设计、AMBA/AXI/ACE、DSU、WFE/WFI这样的。第八章: 新增的ARMv9 CCA/RME安全架构专题第九章:主要放置一些直播课。# 课程收益1、知道我学习什么,我要怎么去学习,从此之后有了一个明确的学习路线。2、认识一些共同目标的人,相互讨论问题,共同进步。勤学、共学、助学。3、ARM不再神秘,SOC不在神秘,让您短期内就能cover住全局4、熟悉ARM Architecture架构知识5、熟悉SOC架构知识6、熟悉主流的系统软件框架7、熟悉各项硬件原理和机制,如异常断、MMU、cache、TLB、VMSA、Trustzone6、深入了解当前的系统架构、软硬件架构,能够看懂这些大家,将来也能够自己设计。7、熟悉系统的启动流程、Secureboot等8、熟悉各类标准和规范9、能够进入芯片厂商干活、能够在非芯片产生成为技术担当。10、学习资料的获取方法,会看11500多页的ARM手册,会看数以百计的ARM各项参考手册。 本课程会持续更新。也希望通过本课程的学习,能够让大家的ARMv8/ARMv9开发技术能有质的飞越,能找到自己心仪的工作。在购买之前,也建议大家看一看第一章第一节的课程介绍。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

主公CodingCos

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值