1.2.4 函数run_main_loop
U-Boot程序初始化完成后,准备处理命令,这是通过数组init_sequence_r的最后一个函数run_main_loop实现的。
函数run_main_loop的执行流程如图1.1所示,把主要工作委托给函数main_loop,函数main_loop的执行过程如下。
图1.1 函数run main loop的执行流程
(1)调用bootdelay_process以读取环境变量bootdelay和bootcmd,环境变量bootdelay定义延迟时间,即等待用户按键的时间长度;环境变量bootcmd定义要执行的命令。
U-Boot程序到哪里读取环境变量?
通常我们把NOR闪存分成多个分区,其中第一个分区存放U-Boot程序,第二个分区存放环境变量。U-Boot程序里面的NOR闪存驱动程序对分区信息硬编码,指定每个分区的偏移和长度。U-Boot程序从环境变量分区读取环境变量。
(2)调用函数autoboot_command。函数autoboot_command先调用函数abortboot,等待用户按键。如果在等待时间内用户没有按键,就调用函数run_command_list,自动执行环境变量bootcmd定义的命令。假设环境变量bootcmd定义的命令是“bootm”,函数run_command_list查找命令表,发现命令“bootm”的处理函数是do_bootm。
函数do_bootm的执行流程如图1.2所示,把主要工作委托给函数do_bootm_states,函数do_bootm_states的执行过程如下。
图1.2 函数do bootm的执行流程
(1)函数bootm_start负责初始化全局变量“bootm_headers_t images”。
(2)函数bootm_find_os把内核镜像从存储设备读到内存。
(3)函数bootm_find_other读取其他信息,对于ARM64架构,通常是扁平设备树(Flattened Device Tree, FDT)二进制文件,该文件用来传递硬件信息给内核。
(4)函数bootm_load_os把内核加载到正确的位置,如果内核镜像是被压缩过的,需要解压缩。
(5)函数bootm_os_get_boot_func根据操作系统类型在数组boot_os中查找引导函数,Linux内核的引导函数是do_bootm_linux。
(6)第一次调用函数do_bootm_linux时,参数flag是BOOTM_STATE_OS_PREP,为执行Linux内核做准备工作。函数do_bootm_linux(flag=BOOTM_STATE_OS_PREP)把工作委托给函数boot_prep_linux,主要工作如下。
1)分配一块内存,把扁平设备树二进制文件复制过去。
2)修改扁平设备树二进制文件,例如:如果环境变量“bootargs”指定了内核参数,那么把节点“/chosen”的属性“bootargs”设置为内核参数字符串;如果多处理器系统使用自旋表启动方法,那么针对每个处理器对应的节点“cpu”,把属性“enable-method”设置为“spin-table”,把属性“cpu-release-addr”设置为全局变量spin_table_cpu_release_addr的地址。
(7)函数boot_selected_os调用函数do_bootm_linux,这是第二次调用函数do_bootm_linux,参数flag是BOOTM_STATE_OS_GO。函数do_bootm_linux(flag=BOOTM_STATE_OS_GO)调用函数boot_jump_linux,该函数跳转到内核的入口,第一个参数是扁平设备树二进制文件的起始地址,后面3个参数现在没有使用。
函数boot_jump_linux负责跳转到Linux内核,执行流程如图1.3所示。
图1.3 函数boot jump linux的执行流程
(1)调用函数smp_kick_all_cpus,如果打开了配置宏CONFIG_GICV2或者CONFIG_GICV3,即使用通用中断控制器版本2或者版本3,那么发送中断请求以唤醒所有从处理器。
(2)调用函数dcache_disable,禁用处理器的缓存和内存管理单元。
(3)如果开启配置宏CONFIG_ARMV8_SWITCH_TO_EL1,表示在异常级别1执行Linux内核,那么先从异常级别3切换到异常级别2,然后切换到异常级别1,最后跳转到内核入口。
(4)如果在异常级别2执行Linux内核,那么先从异常级别3切换到异常级别2,然后跳转到内核入口。