电源/新能源
当我们接触电源管理的时候,最简单的流程就是 关机重启 ,但是仔细分析其涉及的所有源代码就会发现,关机重启虽然简单,但是“ 麻雀虽小,五脏俱全 ”,涉及到的软件模块非常的多,涉及的流程: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 << 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内核中的处理
内核中代码执行流程图:
关机主要过程总结:
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
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 < 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 ", (void *) service); panic(); //不正确 } if (service->init) { //该服务是否需要初始化 rc = service->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 < 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 . = 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_END
service->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, 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
•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 <
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. "); 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); |
审核编辑:刘清
全部0条评论
快来发表一下你的评论吧 !