• 632查看
  • 0回复

[芯片硬件] 电源管理入门-1关机重启详解

[复制链接]

  • TA的每日心情
    无聊
    1-7-2015 18:46
  • 签到天数: 1 天

    [LV.1]初来乍到

    发表于 3-3-2024 08:56:10 | 显示全部楼层 |阅读模式

    汽车零部件采购、销售通信录       填写你的培训需求,我们帮你找      招募汽车专业培训老师


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

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

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

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

    电源管理入门-1关机重启详解w2.jpg
    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_SMC1.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的处理信号init\init.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()函数,在init\halt.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 << SIGTERM)
          )) {
              halt_reboot_pwoff(sig);
          }
          /*  if (sig == SIGCHLD) do nothing */
      
    }
    在busybox内部解析后会执行halt_reboot_pwoff()函数
    #define  RB_AUTOBOOT        0x01234567
      #define RB_POWER_OFF       0x4321fedc

      static void halt_reboot_pwoff(int sig)
      {
              const char *m;
              unsigned rb;

               reset_sighandlers_and_unblock_sigs();
               run_shutdown_and_kill_processes();//进行关机通知其他进程处理

              m = "halt";
              rb = RB_HALT_SYSTEM;
              if (sig == SIGTERM) {
                      m = "reboot";
                      rb = RB_AUTOBOOT;
              } else if (sig == SIGUSR2) {
                      m =  "poweroff";
                      rb = RB_POWER_OFF;
              }
              message(L_CONSOLE,  "Requesting system %s", m);
              pause_and_low_level_reboot(rb);
      }
    halt_reboot_pwoff()分为三步:
      发送SIGTERM给所有进程,让进程正常退出发送SIGKILL给所有进程,将其杀掉让系统重启
    发送SIGTERM和SIGKILL信号给其他进程
    static  void run_shutdown_and_kill_processes(void)
      {
              run_actions(SHUTDOWN);

              message(L_CONSOLE | L_LOG,  "The system is going down NOW!");

              /* Send signals to every  process _except_ pid 1 */
              kill(-1,  SIGTERM);
              message(L_CONSOLE, "Sent  SIG%s to all processes", "TERM");
              sync();
              sleep1();

              kill(-1,  SIGKILL);
              message(L_CONSOLE, "Sent  SIG%s to all processes", "KILL");
              sync();
              /*sleep1(); - callers take care  about making a pause */
      }
    调用子进程进行reboot系统调用:
    Go
      static  void pause_and_low_level_reboot(unsigned magic)
      {
              pid_t pid;

              sleep1();
              pid = vfork();
              if (pid == 0) { /* child */
                      reboot(magic);
                      _exit(EXIT_SUCCESS);
              }

              waitpid(pid, NULL, 0);
              sleep1(); /* paranoia */
              _exit(EXIT_SUCCESS);
      }
    reboot(magic);进行了系统调用。
    3. Linux内核中的处理
    内核中代码执行流程图:

    电源管理入门-1关机重启详解w3.jpg
    关机主要过程总结:
      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_SMC3.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\n");
               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\n");
                  dev->class->shutdown_pre(dev);
                 }
                      if (dev->bus  && dev->bus->shutdown) {
                       if  (initcall_debug)
                             dev_info(dev, "shutdown\n");
                              dev->bus->shutdown(dev);
                      } else if  (dev->driver && dev->driver->shutdown) {
                         if  (initcall_debug)
                              dev_info(dev, "shutdown\n");
                              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 IDs\n");
              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手册:
    电源管理入门-1关机重启详解w4.jpg

    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 软件流程框图
    电源管理入门-1关机重启详解w5.jpg

    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异常触发执行流程:
    电源管理入门-1关机重启详解w6.jpg

    进入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\n",  version_string);
              NOTICE("BL31: %s\n",  build_message);
              bl31_platform_setup();        //通用和安全时钟初始化,其他芯片相关功能初始化
              bl31_lib_init();        //空函数
              INFO("BL31: Initializing  runtime services\n");
              runtime_svc_init();        //重点 下面展开分析
              if (bl32_init) {        
                      INFO("BL31:  Initializing BL32\n");
                      (*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 <  RT_SVC_DECS_NUM; index++) {
                      rt_svc_desc_t *service  = &rt_svc_descs[index];
                      rc = validate_rt_svc_desc(service);//判断每一个服务的各项参数是否正确
                      if (rc) {
                               ERROR("Invalid runtime service descriptor %p\n",
                                      (void  *) service);
                              panic();        //不正确
                      }
                      if (service->init)  {        //该服务是否需要初始化
                              rc =  service->init();        //进行初始化
                              if (rc) {        //初始化是否成功
                                       ERROR("Error initializing runtime service %s\n",
                                                       service->name);
                                       continue;
                              }
                      }
                      start_idx =  get_unique_oen(rt_svc_descs[index].start_oen,
                                      service->call_type);        //八位的id号
                      assert(start_idx <  MAX_RT_SVCS);
                      end_idx =  get_unique_oen(rt_svc_descs[index].end_oen,
                                       service->call_type);         //八位的id号
                      assert(end_idx <  MAX_RT_SVCS);
                      for (; start_idx <=  end_idx; start_idx++)
                               rt_svc_descs_indices[start_idx] = index;//证明可以根据rt_svc_descs_indices[?]的值找到其对应的rt_svc_descs[index]中index值
              }
      }
    RT_SVC_DECS_NUM表示svc数量
    #define  RT_SVC_DECS_NUM  ((RT_SVC_DESCS_END - RT_SVC_DESCS_START)\
         / sizeof(rt_svc_desc_t))
    RT_SVC_DESCS_START开始的位置已经存入了结构体数据数组,因为在ld文件中进行了内存布局说明在bl31/bl31.ld.S中:
             RODATA_COMMON

              /* Place pubsub sections for  events */
              . = ALIGN(8);
      #include <lib/el3_runtime/pubsub_events.h>

              . = ALIGN(PAGE_SIZE);
              __RODATA_END__ = .;
          } >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 << 6 + OEN_STD_ENDservice->init()会执行std_svc_setup()函数->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 << 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 << log2(size))
             
              blr        x15//跳转到handler

              b        el3_exit
    sync_handler64里找rt_svc_desc_t结构体类型里面的handle处理函数,而这些处理函数在rt_svc_descs节中一个问题:怎么找到index?例如发的一个smc消息id是0x84000000+8
    电源管理入门-1关机重启详解w7.jpg

    ?bit31决定是fast call,还是std call(yield对应的就是std call)?bit30表示是以32位传参,还是以64位传参,注意我们看了optee在linux的driver,都是以32位方式?bit29:24 决定服务的类型?bit23:16reserved?bit15:0每种call类型下,表示range这个地方值为4,
    /*  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)

      
    系统启动的时候会把index信息存入到rt_svc_descs_indices里面,根据4取出来就可以了。
        start_idx  = (uint8_t)get_unique_oen(service->start_oen,service->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 <<log2(size))翻译过来如下:w15 = (__RT_SVC_DESCS_START__ + RT_SVC_DESC_HANDLE) + w15<< log2(size)
    4.5 smc服务处理分析std_svc_smc_handler()中,可以看到会调用psci_smc_handler函数。
    /*
       * Top-level Standard Service SMC  handler. This handler will in turn dispatch
       * calls to PSCI SMC handler
       */
      static uintptr_t std_svc_smc_handler(uint32_t smc_fid,
                                    u_register_t x1,
                                    u_register_t x2,
                                    u_register_t x3,
                                    u_register_t x4,
                                   void  *cookie,
                                   void  *handle,
                                    u_register_t flags)
      {
                      ret =  psci_smc_handler(smc_fid, x1, x2, x3, x4,
                          cookie, handle,  flags);

      
    而在psci_smc_handler函数中,就可以看到上面传入的psci传入的PSCI_SYSTEM_OFF指令
    case PSCI_SYSTEM_OFF:
           psci_system_off();
          /* We should  never return from psci_system_off() */
             break;
    在psci_system_off进去就打印psci相关的打印,然后调用system_off回调。
    void  __dead2 psci_system_off(void)
      {
              psci_print_power_domain_map();//打印

               assert(psci_plat_pm_ops->system_off != NULL);

              /* 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.\n");
               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.\n");
      #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中的处理

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

    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);

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

    后记:    本篇文章代码有点多,其实撸代码的过程也挺有趣味的,特别是加上自己的log打印,就有了自己可以控制的感觉,即时反馈带来游戏的快感。另外从职业技能上说,掌握一个方向的技术成为专家,也能让自己有个饭碗,加油,同志们。“啥都懂一点,啥都不精通,干啥都能干,干啥啥不是,专业入门劝退,堪称程序员杂家”。    后续会继续更新,纯干货分析,无广告,不打赏,欢迎分享给朋友,欢迎评论交流!
    

    该用户从未签到

    发表于 14-3-2025 15:57:01 | 显示全部楼层
    关于电源管理入门与关机重启的详解

    对于初学者来说,关机重启作为电源管理的初步流程看似简单,实则涵盖众多软件和硬件模块。此过程涉及Linux应用层、Linux内核、BL31、SCP直至PMIC/CRU等硬件。以下是关键流程的简要说明:

    用户通过Linux应用层发起关机或重启指令,这些指令通过Linux内核进行响应和处理。在内核空间,关机重启的流程涉及多个模块和驱动,确保系统正常关闭或重新启动。对于ARM架构,ATF(信任固件)也参与其中,负责部分启动流程。SCP(安全控制处理器)在复杂SoC上更是扮演关键角色,负责安全监控和管理。最终,硬件层面的PMIC(电源管理集成电路)和CRU(时钟资源管理单元)负责实际执行关机或重启动作。

    对于入门学习者,理解并掌握关机重启的流程和涉及的模块是电源管理学习的基础。通过深入研究这一流程,可以逐步扩展到更复杂的电源管理场景和技术。希望此入门指导能助您在电源管理的道路上稳步前行。
    回复 支持 反对

    使用道具 举报

    快速发帖

    您需要登录后才可以回帖 登录 | 注册

    本版积分规则

    QQ|手机版|小黑屋|Archiver|汽车工程师之家 ( 渝ICP备18012993号-1 )

    GMT+8, 19-8-2025 17:17 , Processed in 0.310574 second(s), 37 queries .

    Powered by Discuz! X3.5

    © 2001-2013 Comsenz Inc.