电源管理入门1关机重启详解

2023年 10月 11日 76.9k 0

电源管理入门-1关机重启详解

原创 thatway OS与AUTOSAR研究 2023-09-19 07:25 发表于北京

image.png

    当我们接触**电源管理**的时候,**最简单**的流程就是**关机重启**,但是仔细分析其涉及的所有源代码就会发现,关机重启虽然简单,但是“**麻雀虽小,五脏俱全**”,涉及到的软件模块非常的多,涉及的流程:Linux应用(busybox)-》Linux内核-》BL31-》SCP-》PMIC/CRU等硬件。所以是一个入门学习,特别是还没接触过Linux内核代码的好机会,下面进入代码的海洋遨游,**超级干货**!

1. 关机重启软件流程框图

    在Linux系统上的处理分为用户态空间、内核空间、ATF、SCP四个阶段(ATF是ARM独有的,SCP在复杂SoC上才有应用)来处理:

image.png

1.1 用户层****利用reboot、poweroff等命令进行关机,在应用层会执行:

  • 发送SIGTERM给所有进程,让进程正常退出
  • 发送SIGKILL给所有进程,将其杀掉,并等待一段时间
  • 调用reboot系统调用让系统关机/重启
  • 1.2 Linux 内核层reboot系统调用会进入内核,具体流程为:

  • reboot系统调用根据参数找到kernel_power_off/reset
  • 向关心reboot事件的进程发送消息--blocking_notifier_call_chain
  • 内核Kobject状态发生改变不通知用户空间--usermodehelper_disable
  • 关闭所有的设备--device_shutdown
  • 禁止CPU热插拔,设置当前CPU为第一个在线CPU,把新任务转移到当前CPU上--migrate_to_reboot_cpu
  • 关闭syscore设备--syscore_shutdown
  • 提示用户空间系统将要关闭--pr_emerg
  • 禁止cpu硬件中断--local_irq_disable
  • 其他cpu处于非工作状态--smp_send_stop
  • 调用psci接口,执行smc指令,关闭arm cpu--pmm_power_off/rese->psci_sys_poweroff/reset->invoke_psci_fn->arm_smccc_smc->SMCCC SMCCC_SMC
  • 1.3 ATF 层执行SMC指令后会触发异常,进入ATF的BL31中继续执行:

  • 进入异常向量处理的入口sync_exception_aarch64
  • 跳转执行rt_svc_desc_t结构体保存的服务std_svc_smc_handler
  • 执行psci相关处理。通用psci的处理函数psci_system_off和psci_system_reset,通过调用平台提供的system_off、 system_reset接口将psci消息转化为scmi消息发给SCP模块,实现最终的关机、重启。如果如果没有SCP固件的系统,会在ATF里面操作硬件寄存器进行关机重启处理。
  • 1.4 SCP 层

    ATF通过scim消息发送给MHU硬件并产生中断,SCP接受到中断后内部依次进行处理的模块为:mhu-->transport-->scmi-->scmi_system_power-->power_domain-->ppu/system_power-->i2c/cru,最后SCP固件通过控制PMIC/CRU的硬件寄存器实现对系统的关机重启设置。

    2. Busybox中的关机重启命令

    执行关机重启的系统命令,例如shutdown/poweroff/halt/reboot/init命令进程及服务如果提前会被正确的中止,我们就说其是安全的退出。通常关机重启命令需要管理员权限执行,所在系统目录为/sbin/*,如下为shutdown命令:

    命令格式  
    [root@localhost ~]# shutdown [选项] 时间 [警告信息]  选项: 
    -c:取消已经执行的 shutdown 命令;  
    -h:关机;  
    -r:重启; 
    

    init命令相关执行:

    |  [root@localhost~]#  init 0  
    #关机,也就是调用系统的 0 级别  
    [root@localhost ~】# init 6  
    #重启,也就是调用系统的 6 级别
    

    现在Linux里面这些命令基本都使用busybox实现的,代码参考:
    busybox.net/downloads/b…

       sigaddset(&G.delayed_sigset,  SIGUSR1); /* halt */
       sigaddset(&G.delayed_sigset, SIGTERM); /* reboot */
       sigaddset(&G.delayed_sigset, SIGUSR2); /* poweroff */
        
       /* Now run the looping stuff for the rest of forever */
         while (1) {
             /*  (Re)run the respawn/askfirst stuff */
             run_actions(RESPAWN |  ASKFIRST);
     
             /* Wait for any signal (typically it's SIGCHLD) */
             check_delayed_sigs(NULL); /* NULL timespec makes it wait */
             .....
         }
    

    check_delayed_sigs()函数会收到reboot的信号运行busybox reboot的时候,reboot —> halt_main,可知会执行 halt_main()函数,在inithalt.c中

    static  const smallint signals[] = { SIGUSR1, SIGUSR2, SIGTERM  };
     #  define RB_HALT_SYSTEM  0xcdef0123
     #  define RB_ENABLE_CAD   0x89abcdef
     #  define RB_DISABLE_CAD  0
     #  define RB_POWER_OFF    0x4321fedc
     #  define RB_AUTOBOOT     0x01234567
     
     flags = getopt32(argv,  "d:+nfwi", &delay);
     
     if (!(flags & 4)) { /* no -f */
         rc = kill(pidlist[0],  signals[which]);
     }
     else{
         rc = reboot(magic[which]);
     }
    

    这里可以看出来,分为两个流程:

  • 当reboot命令没有加**-f**的时候,直接使用kill发送信号到busybox执行halt_reboot_pwoff函数
  • 直接使用-f的话,直接使用reboot系统调用接口,通知内核,让内核执行重启操作,简单粗暴
  • 如果1中发送kill命令的SIGTERM 信号后,在busybox的轮询处理函数中会接收信号进行处理,如下:

    static  void check_delayed_sigs(struct timespec *ts)
     {
         int sig =  sigtimedwait(&G.delayed_sigset, /*  siginfo_t */ NULL, ts);
         if (sig list_lock);
             while  (!list_empty(&devices_kset->list)) {
                     dev =  list_entry(devices_kset->list.prev, struct device,
                                      kobj.entry);
     
             parent =  get_device(dev->parent);
             get_device(dev);
     
             list_del_init(&dev->kobj.entry);
               spin_unlock(&devices_kset->list_lock);
     
         /* hold lock to avoid  race with probe/release */
              if (parent)
                    device_lock(parent);
              device_lock(dev);
     
               /* Don't allow any more  runtime suspends */
                pm_runtime_get_noresume(dev);
                pm_runtime_barrier(dev);
     
                if (dev->class  && dev->class->shutdown_pre) {
                if  (initcall_debug)
                         dev_info(dev,  "shutdown_pren");
                  dev->class->shutdown_pre(dev);
                }
                     if (dev->bus  && dev->bus->shutdown) {
                       if  (initcall_debug)
                             dev_info(dev, "shutdownn");
                             dev->bus->shutdown(dev);
                     } else if  (dev->driver && dev->driver->shutdown) {
                         if  (initcall_debug)
                              dev_info(dev, "shutdownn");
                             dev->driver->shutdown(dev);
                     }
     
                  device_unlock(dev);
                    if (parent)
                          device_unlock(parent);
     
                  put_device(dev);
                  put_device(parent);
     
                  spin_lock(&devices_kset->list_lock);
             }
              spin_unlock(&devices_kset->list_lock);
     }
    

    | 1)遍历devices_kset的链表,取出所有的设备(struct device);2)将该设备从链表中删除;3)调用pmm_runtime_get_noresume和pmm_runtime_barrier接口,停止所有的Runtime相关的电源管理动作;4)如果该设备的bus提供了shutdown函数,优先调用bus的shutdown,关闭设备;5)如果bus没有提供shutdown函数,检测设备driver是否提供,如果提供,调用设备driver的shutdown,关闭设备;6)直至处理完毕所有的设备。

    系统中所有的设备都在“ /sys/devices/ ”目录下,这些设备是一个链表结构串起来的,devices_kset是链表头,里面都是struct device,然后找到对应的struct bus_type和struct device_driver等,然后按照优先级例如:class>bus>driver执行对应的shutdown回调函数。3.4 多 CPU 调度相关处理对于多CPU的机器,无论哪个CPU触发了当前的系统调用,代码都可以运行在任意的CPU上。这个接口将代码分派到一个特定的CPU上,并禁止调度器分派代码到其它CPU上。也就是说,这个接口被执行后,只有一个CPU在运行,用于完成后续的reboot动作。

    void  migrate_to_reboot_cpu(void)
     {
             /* The boot cpu is always  logical cpu 0 */
             int cpu = reboot_cpu;
     
             cpu_hotplug_disable();
     
             /* Make certain the cpu I'm  about to reboot on is online */
             if (!cpu_online(cpu))
                     cpu =  cpumask_first(cpu_online_mask);
     
             /* Prevent races with other tasks  migrating this task */
             current->flags |=  PF_NO_SETAFFINITY;
     
             /* Make certain I only run on  the appropriate processor */
             set_cpus_allowed_ptr(current,  cpumask_of(cpu));
     }
    

    | 1)CPU 0是默认重启使用的CPU2)禁止CPU热插拔3)如果CPU 0不在线,则设置当前CPU为第一个在线的CPU4)允许current进程在重启使用的CPU上运行 |

    3.5 内核核心关闭

    ****system core的shutdown和设备的shutdown类似,也是从一个链表中,遍历所有的system core,并调用它的shutdown接口。

    3.6 硬件平台的关闭*

    void  machine_power_off(void)
     {
             local_irq_disable();
             smp_send_stop();
             if (pmm_power_off)
                     pmm_power_off();
     }
    

    | 1)屏蔽当前CPU上的所有中断,通过操作arm核心中的寄存器来屏蔽到达CPU上的中断,此时中断控制器中所有送往该CPU上的中断信号都将被忽略。2)对于多CPU的机器来说,Restart之前必须保证其它的CPU处于非活动状态,由其中的一个主CPU负责Restart动作。调用smp_send_stop接口,确保其它CPU处于非活动状态;这里会等待1秒时间来停止其他CPU。3)调用PSCI相关接口实现相关关机操作 |

    3.7 内核

    PSCI 相关操作****PSCI(Power State Coordination Interface)电源状态协调接口,是ARM定义的电源管理接口规范。PSCI 初始化流程: 在kernel的setup_arch启动时,扫描设备树节点信息关于psci部分,根据compatible来匹配到psci_0_2_init()函数,然后进入psci_probe()函数,并在psci_0_2_set_functions()函数中设置相关的函数指针:start_kernel() -> setup_arch() -> psci_dt_init() -> psci_0_2_init() -> psci_probe() -> psci_0_2_set_functions()设备树里面的信息如下里标记的版本是psci-0.2,method是使用smc。

    | psci {        compatible = "arm,psci-0.2";          method = "smc"; }; |

    psci_0_2_set_functions会给处理函数赋值

    static  void __init psci_0_2_set_functions(void)
     {
             pr_info("Using standard  PSCI v0.2 function IDsn");
             psci_ops.get_version =  psci_get_version;
     
              psci_function_id[PSCI_FN_CPU_SUSPEND] =
                                              PSCI_FN_NATIVE(0_2, CPU_SUSPEND);
             psci_ops.cpu_suspend =  psci_cpu_suspend;
     
              psci_function_id[PSCI_FN_CPU_OFF] = PSCI_0_2_FN_CPU_OFF;
             psci_ops.cpu_off =  psci_cpu_off;
     
              psci_function_id[PSCI_FN_CPU_ON] = PSCI_FN_NATIVE(0_2, CPU_ON);
             psci_ops.cpu_on = psci_cpu_on;
     
             .....
             arm_pm_restart =  psci_sys_reset;
             pm_power_off = psci_sys_poweroff;
    

    PSCI 关机流程:

    #define  PSCI_0_2_FN_BASE                         0x84000000
     #define PSCI_0_2_FN(n)                          (PSCI_0_2_FN_BASE +  (n))
     #define PSCI_0_2_FN_SYSTEM_OFF                  PSCI_0_2_FN(8)
     
     static void psci_sys_poweroff(void)
     {
              invoke_psci_fn(PSCI_0_2_FN_SYSTEM_OFF, 0, 0, 0);
     }
    

    PSCI_0_2_FN_SYSTEM_OFF的值计算为:0x84000000+8,查看ARM PSCI手册:

    image.png

    invoke_psci_fn()在smc模式下对应 __invoke_psci_fn_smc()函数:

    static  unsigned long __invoke_psci_fn_smc(unsigned long function_id,
                             unsigned long  arg0, unsigned long arg1,
                             unsigned long  arg2)
     {
             struct arm_smccc_res res;
     
             arm_smccc_smc(function_id,  arg0, arg1, arg2, 0, 0, 0, 0, &res);
             return res.a0;
     }
    

    arm_smccc_smc()函数的实现为汇编代码,在arch/arm/kernel/smccc-call.S中

    .macro SMCCC_SMC
        __SMC(0)
          .endm
            
     /* 定义SMCCC宏,其参数为instr */
             .macro SMCCC instr
     /* 将normal world中的寄存器入栈,保存现场 */
     UNWIND(        .fnstart)
             mov        r12, sp  /* r12指向老的sp地址 */
             push        {r4-r7}  /* 推r4-r7入栈,则sp = sp - 4 * 4 */
     UNWIND(        .save        {r4-r7})
             ldm        r12, {r4-r7}  /* 把r12指向的内容的刷入r4-r7,其实就是把参数a4-a7存入r4-r7
             instr    /* 执行instr参数的内容,即执行smc切换 */
             pop        {r4-r7}   /* 出栈操作,恢复现场 */
             ldr        r12, [sp, #(4 * 4)]
             stm        r12, {r0-r3}
             bx        lr
     UNWIND(        .fnend)
             .endm
            
     ENTRY(__arm_smccc_smc)
             SMCCC SMCCC_SMC
     ENDPROC(__arm_smccc_smc)
    

    SMCCC宏如下,smc指令触发一个安全监视器异常后,将栈上的数据存到x0~x3上,回头看 __invoke_psci_fn_smc函数实际是返回x0的结果。由于smccc_smc函数的入参有9个参数,按照约定,前4个参数存在r0 - r3,其他参数从右向左入栈。r0=a0, r1=a1, r2=a2, r3=a3, r4=a4, r5=a5, r6=a6, r7=a7进入 ATF 中 EL3 模式执行: smc指令是arm-v8手册中定义的一个指令,这个安全监视器触发一个异常,然后进入到EL3。EL3:安全监控异常级别。异常级别,用于执行安全监视器代码,用于处理非安全状态和安全状态之间的转换。EL3始终处于Secure状态.

    **4. ATF Bl31中的处理

    4.1 ATF 软件流程框图**

    image.png

    BL31中smc异常触发流程图执行****SMC 指令****后会触发异常,进入 ATF 的BL31中继续执行:

  • 在Linux侧调用smc异常之后,会根据中断向量表触发cpu的同步异常sync_exception_aarch64/32
  • 然后跳转执行到handle_sync_exception->smc_handler64/32中
  • 根据_RT_SVC_DESCS_START_+RT_SVC_DESC_HANDLE的位置,跳转执行rt_svc_desc_t结构体保存的服务std_svc_smc_handler
  • 执行psci相关处理,找到psci_system_off和psci_system_rese处理函数。ATF直接处理如果是关机就执行halt指令,重启则通过设置gpio,或者转送给SCP处理。
  • 最后跳转到el3_exit返回Linux侧。
  • SMC 异常触发执行流程:

    image.png

    进入ATF的方式触发异常:同步异常SMC、异步异常(irq,fiq)

  • 如果是同步异常,那么一定是在linux或tee中发生了smc调用,此时进入跳转ATF中异常向量表中的同步异常程序smc_handler64或smc_handler32
    在该程序中,解析smc id,来选择跳转到具体哪一个rt-svc(runtime service)

  • 如果是异步异常,那么一定是触发了irq或fiq或serror中断等,此时进入跳转ATF中异常向量表中的异步异常程序,进而跳转到响应的中断处理函数.

  • 4.2 内存布局 bl31_entrypoint****编译使用的lds文件是arm-trusted-firmware/bl31/bl31.ld.S,开头就可以看到入口是bl31_entrypoint:

    ENTRY(bl31_entrypoint)

    bl31_entrypoint在bl31/aarch64/bl31_entrypoint.S中定义可以看到设置 _exception_vectors为runtime_exceptions函数的:

    /*  ---------------------------------------------------------------------
              * For !RESET_TO_BL31 systems,  only the primary CPU ever reaches
               * bl31_entrypoint() during the cold boot flow, so the cold/warm boot
              * and primary/secondary CPU  logic should not be executed in this case.
              *
              * Also, assume that the  previous bootloader has already initialised the
              * SCTLR_EL3, including the endianness, and  has initialised the memory.
              *  ---------------------------------------------------------------------
              */
             el3_entrypoint_common                                        
                     _init_sctlr=0                                        
                      _warm_boot_mailbox=0                                
                      _secondary_cold_boot=0                                
                     _init_memory=0                                        
                     _init_c_runtime=1                                
                     _exception_vectors=runtime_exceptions                
                      _pie_fixup_size=BL31_LIMIT - BL31_BASE
    

    在bl31/aarch64/runtime_exceptions.S中

         .globl        runtime_exceptions
     vector_base runtime_exceptions        //定义 .vectors
     vector_entry sync_exception_aarch64
             handle_sync_exception
             check_vector_size  sync_exception_aarch64
     vector_entry sync_exception_aarch32
             handle_sync_exception
             check_vector_size  sync_exception_aarch32
    

    vector_base 是一个宏,在include/arch/aarch64/asm_macros.S中定义:

             .macro vector_base  label, section_name=.vectors//label为标号以冒号结尾
             .section section_name,  "ax"//指定代码段必须存放在.vectors段里, “ax”表示该段可执行并且可‘a’读和可‘x’执行
             .align 11, 0//地址方式对齐11 其余字节用0填充
             label:
             .endm
    

    同样其他宏经过转化如下:

     .section  .vectors, "ax"        //指定代码段必须存放在.vectors段里, “ax”表示该段可执行并且可‘a’读和可‘x’执行
     .align 11, 0                        //地址方式对齐11 其余字节用0填充
     runtime_exceptions:
             .section .vectors,  "ax"//指定代码段必须存放在.vectors段里, “ax”表示该段可执行并且可‘a’读和可‘x’执行
             .align 7, 0                                //地址方式对齐7
             sync_exception_aarch64:
                     handle_sync_exception
                     .if (. -  serror_aarch64) > (32 * 4)        //这个.应该是当前位置 - 段的开头地址 如果大于 32条指令
                 .error "Vector exceeds  32 instructions"                //向量超过32条指令
                 .endif
             sync_exception_aarch32
                  handle_sync_exception
                     .if (. -  serror_aarch64) > (32 * 4)        //这个.应该是当前位置 - 段的开头地址 如果大于 32条指令
                 .error "Vector exceeds  32 instructions"                //向量超过32条指令
                 .endif
    

    4.3 runtime

    服务程序初始化****bl31_entrypoint入口向下执行首先是bl31_setup,然后是bl31_main

     void  bl31_setup(u_register_t arg0, u_register_t arg1, u_register_t arg2,
                     u_register_t arg3)
     {
             /* Perform early  platform-specific setup */
              bl31_early_platform_setup2(arg0, arg1, arg2, arg3);
     
             /* Perform late  platform-specific setup */
             bl31_plat_arch_setup();
    

    bl31_main()函数:

    void  bl31_main(void)
     {
             NOTICE("BL31: %sn",  version_string);
             NOTICE("BL31: %sn",  build_message);
             bl31_platform_setup();        //通用和安全时钟初始化,其他芯片相关功能初始化
             bl31_lib_init();        //空函数
             INFO("BL31: Initializing  runtime servicesn");
             runtime_svc_init();        //重点 下面展开分析
             if (bl32_init) {       
                     INFO("BL31:  Initializing BL32n");
                     (*bl32_init)();
             }
              bl31_prepare_next_image_entry();         //加载下一阶段的入口地址
             console_flush();        //控制台刷新
             bl31_plat_runtime_setup();        //空函数
     }
    

    runtime_svc_init()函数

    //注册smc指令相关的服务
     void runtime_svc_init(void)
     {
             int rc = 0;
             unsigned int index, start_idx,  end_idx;
     
             /* Assert the number of  descriptors detected are less than maximum indices */
             //这句话表明  RT_SVC_DECS_NUM时当前加载的服务数量
             assert((RT_SVC_DESCS_END >=  RT_SVC_DESCS_START) &&
                             (RT_SVC_DECS_NUM < MAX_RT_SVCS));       
                                              
     
             if (RT_SVC_DECS_NUM == 0)        //如果没有服务要注册
                     return;
             memset(rt_svc_descs_indices,  -1, sizeof(rt_svc_descs_indices));//初始化rt_svc_descs_indices
     
             rt_svc_descs = (rt_svc_desc_t  *)RT_SVC_DESCS_START;//建立一个注册表结构体
             for (index = 0; index init)  {        //该服务是否需要初始化
                             rc =  service->init();        //进行初始化
                             if (rc) {        //初始化是否成功
                                      ERROR("Error initializing runtime service %sn",
                                                      service->name);
                                      continue;
                             }
                     }
                     start_idx =  get_unique_oen(rt_svc_descs[index].start_oen,
                                     service->call_type);        //八位的id号
                     assert(start_idx call_type);         //八位的id号
                     assert(end_idx <  MAX_RT_SVCS);
                     for (; start_idx RAM
     #else
         ro . : {
             __RO_START__ = .;
             *bl31_entrypoint.o(.text*)
             *(SORT_BY_ALIGNMENT(.text*))
             *(SORT_BY_ALIGNMENT(.rodata*))
     
             RODATA_COMMON
    

    在include/common/bl_common.ld.h中

    #define  RODATA_COMMON                                        
             RT_SVC_DESCS                                        
              FCONF_POPULATOR                                        
             PMF_SVC_DESCS                                        
             PARSER_LIB_DESCS                                
             CPU_OPS                                                 
             GOT                                                 
             BASE_XLAT_TABLE_RO                                
             EL3_LP_DESCS
     
     #define RT_SVC_DESCS                                        
             . = ALIGN(STRUCT_ALIGN);                        
             __RT_SVC_DESCS_START__ =  .;                        
             KEEP(*(rt_svc_descs))                                
             __RT_SVC_DESCS_END__ = .;
    

    rt_svc_descs段存放的内容是通过DECLARE_RT_SVC宏来定义的://其中

     #define  DECLARE_RT_SVC(_name, _start, _end, _type,  _setup, _smch)        
             static const rt_svc_desc_t  __svc_desc_ ## _name                         
                     __section("rt_svc_descs") __used = {                        
                             .start_oen =  (_start),                                 
                             .end_oen =  (_end),                                 
                             .call_type =  (_type),                                 
                             .name = #_name,                                        
                             .init =  (_setup),                                 
                             .handle =  (_smch)                                 
                     }
    

    例如在services/std_svc/std_svc_setup.c中

    /*  Register Standard Service Calls as runtime service */
     DECLARE_RT_SVC(
                     std_svc,
     
                     OEN_STD_START,
                     OEN_STD_END,
                     SMC_TYPE_FAST,
                     std_svc_setup,
                     std_svc_smc_handler
     );
     #define OEN_STD_START                         U(4)        /* Standard Service  Calls */
     #define OEN_STD_END                         U(4)
     #define SMC_TYPE_FAST                         UL(1)
     #define SMC_TYPE_YIELD                        UL(0)
    

    static const rt_svc_desc_t  __svc_desc_std_svc服务。其服务id为SMC_TYPE_FAST psci_setup((const psci_lib_args_t *)svc_arg)(void) plat_setup_psci_ops((uintptr_t)lib_args->mailbox_ep,&psci_plat_pm_ops);plat_setup_psci_ops()的定义根据平台,我们使用的是qemu,对应plat/qemu/qemu_sbsa/sbsa_pm.c文件中:

    *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_pwr_down_wfi =  qemu_pwr_domain_pwr_down_wfi,
             .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,
             .system_off = qemu_system_off,
             .system_reset =  qemu_system_reset,
             .validate_power_state =  qemu_validate_power_state
     };
    

    4.4 SMC 异常处理入口分析

    SMC命令执行后,CPU会根据异常向量表找到sync_exception_aarch64的入口会执行handle_sync_exception,在bl31/aarch64/runtime_exceptions.S中

    *  ---------------------------------------------------------------------
              * This macro handles  Synchronous exceptions.
              * Only SMC exceptions are  supported.
              *  ---------------------------------------------------------------------
              */
             .macro        handle_sync_exception
     #if ENABLE_RUNTIME_INSTRUMENTATION
             /*
              * Read the timestamp value and  store it in per-cpu data. The value
              * will be extracted from  per-cpu data by the C level SMC handler and
              * saved to the PMF timestamp  region.
              *///存放时间戳
             mrs        x30, cntpct_el0
             str        x29, [sp, #CTX_GPREGS_OFFSET +  CTX_GPREG_X29]
             mrs        x29, tpidr_el3
             str        x30, [x29, #CPU_DATA_PMF_TS0_OFFSET]
             ldr        x29, [sp, #CTX_GPREGS_OFFSET +  CTX_GPREG_X29]
     #endif
     
             mrs        x30, esr_el3  //将esr_el3存入x30
             //#define ESR_EC_SHIFT U(26)  #define ESR_EC_LENGTH U(6)
             //相当于 保留 x30的bit[31-26]并将这几位提到bit[6-0]
             ubfx        x30, x30, #ESR_EC_SHIFT, #ESR_EC_LENGTH
     
             /* Handle SMC exceptions  separately from other synchronous exceptions */
             cmp        x30, #EC_AARCH32_SMC
             b.eq        smc_handler32
     
             cmp        x30, #EC_AARCH64_SMC
             b.eq        sync_handler64
     
             cmp        x30, #EC_AARCH64_SYS
             b.eq        sync_handler64
     
             /* Synchronous exceptions other  than the above are assumed to be EA */
             ldr        x30, [sp, #CTX_GPREGS_OFFSET +  CTX_GPREG_LR]
             b        enter_lower_el_sync_ea
             .endm
    

    三种跳转选项其中smc_handler32/64能够正确触发异常,report_unhandled_exception则是错误的流程

    #define  EC_AARCH32_SMC                         U(0x13)
     #define EC_AARCH64_SVC                         U(0x15)
     #define EC_AARCH64_HVC                         U(0x16)
     #define EC_AARCH64_SMC                        U(0x17)
    

    x30里面存储的是esr_el3 的26-32位,里面是什么判断了smc64当前平台架构是aarch64的,看一下sync_handler64这个处理,在bl31/aarch64/runtime_exceptions.S中

       /* Load descriptor index from array  of indices */
             //在runtime_svc_init()中会将所有的section rt_svc_descs段放入rt_svc_descs_indices数组,
             //这里获取该数组地址
             adrp        x14, rt_svc_descs_indices
             add        x14, x14, :lo12:rt_svc_descs_indices
             ldrb        w15, [x14, x16]//找到rt_svc在rt_svc_descs_indices数组中的index
            
              /*
              * Get the descriptor using the  index
              * x11 = (base + off), w15 =  index 这个index就是rt_svc_descs结构体数组下标
              *
              * handler = (base + off) +  (index end_oen,service->call_type);
        assert(start_idx api->signal_message(smt_channel->id);

    signal_message是smt模块里面提供的,对共享内存的数据进行处理

    status =  fwk_module_bind(smt_channel->id,
       FWK_ID_API(FWK_MODULE_IDX_SMT,  MOD_SMT_API_IDX_DRIVER_INPUT), &smt_channel->api);
    

        文章篇幅有点多了,具体代码就不分析了,可以参考:ARM SCP入门-AP与SCP通信,另外关于核间通信的细节里面没有说明,具体就是mhu或者PL320的驱动代码,以及共享内存的具体操作,后续专门写几篇核间通信的文章。

    后记

      本篇文章代码有点多,其实撸代码的过程也挺有趣味的,特别是加上自己的log打印,就有了自己可以控制的感觉,即时反馈带来游戏的快感。另外从职业技能上说,掌握一个方向的技术成为专家,也能让自己有个饭碗,加油,同志们。

    “啥都懂一点,啥都不精通,

    干啥都能干,干啥啥不是,

    专业入门劝退,堪称程序员杂家”。

    后续会继续更新,纯干货分析,欢迎分享给朋友,欢迎评论交流!

    微信扫一扫 ,关注该公众号

    相关文章

    服务器端口转发,带你了解服务器端口转发
    服务器开放端口,服务器开放端口的步骤
    产品推荐:7月受欢迎AI容器镜像来了,有Qwen系列大模型镜像
    如何使用 WinGet 下载 Microsoft Store 应用
    百度搜索:蓝易云 – 熟悉ubuntu apt-get命令详解
    百度搜索:蓝易云 – 域名解析成功但ping不通解决方案

    发布评论