电源管理入门-关机重启基础知识详解

当我们接触电源管理的时候,最简单的流程就是 关机重启 ,但是仔细分析其涉及的所有源代码就会发现,关机重启虽然简单,但是“ 麻雀虽小,五脏俱全 ”,涉及到的软件模块非常的多,涉及的流程:linux应用(busybox)-》linux内核-》bl31-》scp-》pmic/cru等硬件。所以是一个入门学习,特别是还没接触过linux内核代码的好机会,下面进入代码的海洋遨游, 超级干货 !
1. 关机重启软件流程框图
在linux系统上的处理分为**用户态**空间、**内核**空间、 **atf** 、**scp**四个阶段(atf是arm独有的,scp在复杂soc上才有应用)来处理:
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指令,关闭armcpu--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实现的,代码参考: https://busybox.net/downloads/
busybox启动的时候,会注册reboot的处理信号
initinit.c中init_main函数在初始化的时候调用
   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 <= 0)
         return;
/*  the signal sig was caught */
 #if enable_feature_use_inittab
     if (sig == sighup)
         reload_inittab();
 #endif
     if (sig == sigint)
         run_actions(ctrlaltdel);
     if (sig == sigquit) {
         exec_restart_action();
         /* returns only if no restart action defined */
     }
     if ((1 << sig) & (0
 #ifdef sigpwr
         | (1 << sigpwr)
 #endif
         | (1 << sigusr1)
         | (1 << sigusr2)
         | (1 invoke_psci_fn->
 arm_smccc_smc->smccc smccc_smc
3.1 系统调用实现
在kernel/reboot.c中声明了rboot系统调用的实现:
syscall_define4(reboot, int, magic1, int, magic2, unsigned int, cmd,void __user *, arg)
 {
     ......
     switch (cmd) {
         case linux_reboot_cmd_power_off:
                 kernel_power_off();
                 do_exit(0);
                 break;
其中cmd就是系统调用传进来的magic值,其他的值定义为:
#define        linux_reboot_cmd_restart   0x01234567
 #define         linux_reboot_cmd_halt     0xcdef0123
 #define        linux_reboot_cmd_cad_on    0x89abcdef
 #define         linux_reboot_cmd_cad_off  0x00000000
 #define        linux_reboot_cmd_power_off 0x4321fedc
 #define         linux_reboot_cmd_restart2 0xa1b2c3d4
 #define       linux_reboot_cmd_sw_suspend 0xd000fce2
 #define         linux_reboot_cmd_kexec    0x45584543
reboot系统调用实现过程:
1)判断调用者的用户权限,如果不是超级用户(superuser),则直接返回错误(这也是我们再用户空间执行reboot、halt、poweroff等命令时,必须是root用户的原因);
2)判断传入的magic number是否匹配,如果不匹配,直接返回错误。这样就可以尽可能的防止误动作发生;
3)调用reboot_pid_ns接口,检查是否需要由该接口处理reboot请求。这是一个有关pid namespaces的新特性,也是linux内核重要的知识点;
4)如果是power_off命令,且没有注册power off的machine处理函数(pmm_power_off),把该命令转换为halt命令;
5)根据具体的cmd命令,执行具体的处理,包括,
     如果是restart或者restart2命令,调用kernel_restart。
     如果是cad_on或cad_off命令,更新c_a_d的值,表示是否允许通过ctrl+alt+del组合键重启系统。
     如果是halt命令,调用kernel_halt。
     如果是power_off命令,调用kernel_power_off。
     如果是kexec命令,调用kernel_kexec接口。
     如果是sw_suspend,调用hibernate接口;
6)返回上述的处理结果,系统调用结束。
3.2 内核关机函数分析
这里我们以关机poweroff命令为例,进行代码分析,重启流程相似。
void  kernel_power_off(void)
 {
     kernel_shutdown_prepare(system_power_off);
       if (pmm_power_off_prepare)
               pmm_power_off_prepare();//pm相关的power off prepare函数
         migrate_to_reboot_cpu();//将当前的进程(task)移到一个cpu上
         syscore_shutdown(); //syscore的关闭流程,将系统核心器件关闭(例如中断等)
         pr_emerg(power  down );
          kmsg_dump(kmsg_dump_poweroff);//向这个世界发出最后的声音(打印日志)
         machine_power_off();//soc基本的关闭
 }
1)调用kernel_xxx_prepare函数,进行restart/halt/power_off前的准备工作,包括,
       调用blocking_notifier_call_chain接口,向关心reboot事件的进程,发送sys_restart、 sys_halt或者sys_power_off事件。对restart来说,还好将cmd参数一并发送出去。
       将系统状态设置为相应的状态(sys_restart、sys_halt或sys_power_off)。
       调用usermodehelper_disable接口,禁止user mode helper。
       调用device_shutdown,关闭所有的设备(具体内容会在下一节讲述);
2)如果是power_off,且存在pm相关的power off prepare函数(pm_power_off_prepare),则调用该回调函数;
3)调用migrate_to_reboot_cpu接口,将当前的进程(task)移到一个cpu上;
 注2:对于多cpu的机器,无论哪个cpu触发了当前的系统调用,代码都可以运行在任意的cpu上。这个接口将代码分派到一个特定的cpu上,并禁止调度器分派代码到其它cpu上。也就是说,这个接口被执行后,只有一个cpu在运行,用于完成后续的reboot动作。
4)调用syscore_shutdown接口,将系统核心器件关闭(例如中断等);
5)调用printk以及kmsg_dump,向这个世界发出最后的声音(打印日志);
6)最后,由machine-core的代码,接管后续的处理。
3.3 关闭所有设备处理
kernel_shutdown_prepare-->device_shutdown会关闭所有设备。设备关闭的过程是遍历全局变量devices_kset里面的所有设备,并从链表中删除,执行相关的shuntdown回调函数。函数处理过程如下:
void  device_shutdown(void)
 {
         struct device *dev, *parent;
wait_for_device_probe();
         device_block_probing();
spin_lock(&devices_kset->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_pre );
              dev->class->shutdown_pre(dev);
            }
                 if (dev->bus  && dev->bus->shutdown) {
                   if  (initcall_debug)
                         dev_info(dev, shutdown );
                         dev->bus->shutdown(dev);
                 } else if  (dev->driver && dev->driver->shutdown) {
                     if  (initcall_debug)
                          dev_info(dev, shutdown );
                         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是默认重启使用的cpu
2)禁止cpu热插拔
3)如果cpu 0不在线,则设置当前cpu为第一个在线的cpu
4)允许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 ids );
         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手册:
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)
#define arm_smccc_smc(...) __arm_smccc_smc(__va_args__, null)
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 软件流程框图
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异常触发执行流程:
进入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: %s ,  version_string);
         notice(bl31: %s ,  build_message);
         bl31_platform_setup();        //通用和安全时钟初始化,其他芯片相关功能初始化
         bl31_lib_init();        //空函数
         info(bl31: initializing  runtime services );
         runtime_svc_init();        //重点 下面展开分析
         if (bl32_init) {       
                 info(bl31:  initializing bl32 );
                 (*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 init();        //进行初始化
                         if (rc) {        //初始化是否成功
                                  error(error initializing runtime service %s ,
                                                  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宏来定义的:
//其中__setion(rt_svc_descs)的意思就是注册到rt_svc_descs段中
 #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<< 6 + oen_std_start,结束服务的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, 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 << log2(size))
          */
         adr        x11, (__rt_svc_descs_start__  + rt_svc_desc_handle) //base + off
         lsl        w10, w15, #rt_svc_size_log2 //(index << log2(size))
         ldr        x15, [x11, w10, uxtw]  //handler  = (base + off) + (index call_type);
    end_idx =  (uint8_t)get_unique_oen(service->end_oen,service->call_type);
    assert(start_idx <=  end_idx);
    assert(end_idx <  max_rt_svcs);
      for (; start_idx <=  end_idx; start_idx++)
        rt_svc_descs_indices[start_idx]  = index;
base+index << log2(size)找到结构体数组index对应的元素,然后off就是结构体内handle对应的函数。
handler = (base + off) + (index <
w15 = (__rt_svc_descs_start__ + rt_svc_desc_handle) + w15/* notify the secure payload  dispatcher */
         if ((psci_spd_pm != null)  && (psci_spd_pm->svc_system_off != null)) {
                  psci_spd_pm->svc_system_off();
         }
console_flush();
/* call the platform specific  hook */
          psci_plat_pm_ops->system_off();
/* this function does not  return. we should never get here */
 }
psci_print_power_domain_map()的打印再和设备重启时的日志进行对比,发现是一致的。
4.5 硬件平台相关处理
在qemu平台上的实现如下:
psci_plat_pm_ops系统初始化的时候会赋值.system_off = qemu_system_off,
static  void __dead2 qemu_system_off(void)
 {
 #ifdef secure_gpio_base
         error(qemu system power  off: with gpio. );
          gpio_set_direction(secure_gpio_poweroff, gpio_dir_out);
          gpio_set_value(secure_gpio_poweroff, gpio_level_low);
          gpio_set_value(secure_gpio_poweroff, gpio_level_high);
 #else
         semihosting_exit(adp_stopped_application_exit,  0);
         error(qemu system off:  semihosting call unexpectedly returned. );
 #endif
         panic();
 }
semihosting_exit:
func  semihosting_call
         hlt        #0xf000
         ret
 endfunc semihosting_call
对应重启,qemu_system_reset()函数设置gpio实现
gpio_set_direction(secure_gpio_reset,  gpio_dir_out);
          gpio_set_value(secure_gpio_reset, gpio_level_low);
          gpio_set_value(secure_gpio_reset, gpio_level_high);
如果不是atf里面自己处理,有scp,见下章节分析。
5. scp中的处理
mhu模块: mhu_isr收到中断   status = smt_channel->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);


马云:AI翻译成“人工智能”是人类把自己看得过大、过高
双十一电动牙刷怎么选?2020狂欢节必购电动牙刷推荐
用户定义数据类型的结构
捷豹F-PACE:据说买了这台SUV,就省下了买一台跑车的钱!
酷比koobeeH9L评测 通体的香槟金色配合前后双玻璃设计让人眼前一亮
电源管理入门-关机重启基础知识详解
如何预测比特币的价格
到2025年,全球人工智能软件市场收入将达到1260亿美元
CS5213 HDMI转VGA(带音频)、扩展坞线材等
港版PS4评测 值不值得买
微型电池重要突破:6纳米的病毒制造电池
分析一个电路因WPE效应挂掉的案例
和胜股份公布2018年上半年财报营收4.49亿元,同比增长13.95%
示波器那点事之发展史
物联网嵌入式软件设计的挑战及应对之道
基于MP3424的AA升压电路电源方案
电瓶修复—电动车电池失水硫化的原因
亚瑟莱特产品在上网本之电源应用方案
HoloLens 2:至今为止交互最自然的MR设备
如何才能实现让群众接受加密货币