1)实验平台:正点原子领航者ZYNQ开发板
2)平台购买地址:https://item.taobao.com/item.htm?&id=606160108761
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz_linhanz.html
4)对正点原子FPGA感兴趣的同学可以加群讨论:876744900 点击加入:
5)关注正点原子公众号,获取最新资料
第十三章U-Boot启动流程详解
上一章我们详细的分析了uboot的顶层Makefile,理清了uboot的编译流程。本章我们来详细的分析一下uboot的启动流程,理清uboot是如何启动的。通过对uboot启动流程的梳理,我们就可以掌握一些外设是在哪里被初始化的,这样当我们需要修改这些外设驱动的时候就会心里有数。另外,通过分析uboot的启动流程可以了解Linux内核是如何被启动的。
13.1使用SDK工具编译和调试uboot
古语云:工欲善其事,必先利其器。要想对uboot启动流程进行准确而细致的分析,少不了调试尤其是单步调试,虽然vscode也能调试代码,但vscode偏向于纯软件的调试,而对于uboot这种与开发板硬件进行交互的vscode显然无能为力,所以我们需要祭出大杀器——SDK。下面我们讲解如何使用SDK工具编译和调试uboot。注:这里我们使用Ubuntu中的SDK(即XSDK)工具,而不是Windows下的SDK工具。
我们打开第十七章使用的Vivado工程,点击“Launch SDK”以打开SDK,然后关闭Vivado、关闭SDK,将得到的zynq_petalinux.sdk目录复制到Ubuntu中,笔者选择放在Ubuntu桌面中,如下图所示:
图 24.1.1 zynq_petalinux.sdk目录
现在我们打开Ubuntu中的SDK软件,Workspace选择刚才拷贝的zynq_petalinux.sdk目录,如下图所示:
图 24.1.2选择Workspace
点击“OK”,进入SDK工程界面,如下图所示:
图 24.1.3 SDK工程界面
界面与Vivado中的SDK一样,操作也是一样的。现在我们创建一个名为“uboot”的空应用工程。工程创建后的界面如下:
图 24.1.4“uboot”应用工程
可以看到src目录为空,现在我们往src目录添加uboot源码。此处我们使用第二十三章用到的uboot源码。拷贝uboot源码到上图中的src目录下。添加完uboot源码,然后在src目录下按键盘上的F5键刷新目录,刷新后的的src目录如下图所示:
图 24.1.5 src目录
可以看到,uboot源码已经添加进来了。现在我们来配置uboot。在SDK工具中单击下图中箭头所指的位置,打开bash shell。
图 24.1.6打开bash shell
弹出如下图所示的bash shell界面,输入“pwd”命令,得到当前的工作路径,如下图所示:
图 24.1.7当前的工作路径
可以看到当前路径处于zynq_petalinux.sdk目录下,我们切换路径到src目录下,输入命令“cd uboot/src”切换到src目录下,如下图所示:
图 24.1.8切换到src目录下
然后输入命令“make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zynq_altk_defconfig”配置uboot。配置完成后,鼠标右键单击应用工程uboot,在弹出的菜单中选择“C/C++ Build Settings”或者“Properties”,对应的快捷键是“Alt+Enter”,如下图所示:
图 24.1.9选择“C/C++ Build Settings”或者“Properties”
在弹出的属性界面中,单击“C/C++ Build”,如下图所示:
图 24.1.10配置“C/C++ Build”
取消勾选“Generate Makefiles automatically”,然后修改“Build directory:”,如上图所示。这些修改完成后,添加环境变量。在属性界面的左栏选择“Environment”,添加环境变量CROSS_COMPILE为“arm-linux-gnueabihf-”,如下图所示:
图 24.1.11添加环境变量CROSS_COMPILE
添加完环境变量CROSS_COMPILE后,单击上图箭头5所致的“OK”,然后单击箭头6所致的“Apply”,最后单击属性界面的“OK”退出该界面。然后编译整个应用工程(可使用快捷键Ctrl+B),从下图可以看出,编译没有出错:
图 24.1.12编译没有出错
现在调试uboot。将领航者开发板的启动模式设置为“JTAG”启动,连接JTAG、串口和电源,然后开发板上电。打开串口软件如SecureCRT或Putty,设置好领航者开发板所使用的串口并打开。
在Vmware软件的菜单栏点击“虚拟机(M)”菜单,在弹出的子菜单中移动到“可移动设备(D)”:会弹出相应的移动设备,里面带有“Digilent USB”的是JTAG的USB接口,连接该USB接口,如下图所示:
图 24.1.13 在Vmware中连接JTAG的USB接口到虚拟机内
在应用工程uboot目录上鼠标右键单击,在弹出的菜单中选择“Debug As”→“Debug Configurations”,如下图所示:
图 24.1.14 打开Debug Configurations界面
弹出Debug Configurations界面,如下图所示,双击“Xilinx C/C++ application(system Debugger)”,创建“(System Debugger on local”,然后在下图所示的方框2所指的“Target Setup”栏,将方框3所框的选项全部勾选,然后选择方框4所指的“Application”栏,配置如图 24.1.16所示。
图 24.1.15配置“Target Setup”栏
图 24.1.16配置“Application”栏
依次单击Debug Configurations界面右下方的“Apply”和“Debug”按钮,如下图所示:
图 24.1.17点击调试
会弹出提示打开调试界面的信息,点击“Yes”,如下图所示:
图 24.1.18提示打开调试界面
调试界面如图 24.1.19所示,方框1是常用的调试命令,方框2的Debug窗口中的方框3处显示的是当前PC指针的值和当前运行的命令对应的源码文件,方框4是源码文件,可以看到箭头指向_start: 标号的ARM_VECTORS处,方框5是常用的信息框,可以显示变量Variables、断点Breakpoint、寄存器Register信息等内容,方框6显示当前打开的文件所包括的头文件、包含的函数等信息,右下角的方框主要是显示内存Memory的内容。
图 24.1.19调试界面
对于方框1我们只要知道单步调试按键盘上的F5,遇到函数时不想进入就按F6,进入函数想返回时按F7即可。我们具体的来看下方框5。方框5中的Variables显示变量内容,尤其当进入函数时非常有用。当然了更有信息价值的是寄存器Register栏,所以我们看下寄存器Register栏的信息有哪些。
图 24.1.20寄存器Register栏
从上图可以看出,Register栏中包括Name、Hex、Decimal、Description和Mnemonic。我们常用的是Name用来显示寄存器名,Hex显示寄存器的16进制值和Desciption,描述寄存器内容。我们以cpsr寄存器为例。展开cpsr,如下图所示:
图 24.1.21 SDK调试界面中的cpsr寄存器
可以看到cpsr中各个标志位的内容,如果不清楚cpsr寄存器的n标志位代表什么意思,可以看下Desciption栏的内容。其他的寄存器如果不清楚的也可以照例参详。
可以看到SDK真的很方便,我们在调试uboot的时候,可以一目了然的知道寄存器的变化,不用自己计算,也不用翻手册查找寄存器中的标志位代表什么意思。另外调试界面是可以调整的,可以按照自己想要的方式布局调试界面,此处笔者限于篇幅就不介绍了。
需要说明的是在完成relocate_code函数实现代码动态重定位后,SDK就不能很好的调试了,这时只在反汇编文件中运行,不能对照源码,不过欣慰的是,这时已经完成动态重定位,最令人苦恼的汇编已基本结束了,后面基本上都是C函数了。
13.2链接脚本
13.3链接脚本u-boot.lds详解
要分析uboot的启动流程,首先要找到“入口”,找到第一行程序在哪里。程序的链接是由链接脚本来决定的,所以通过链接脚本可以找到程序的入口。如果没有编译过uboot的话链接脚本为arch/arm/cpu/u-boot.lds。但是这个不是最终使用的链接脚本,最终的链接脚本是在这个链接脚本的基础上生成的。编译一下uboot后就会在uboot根目录下生成u-boot.lds文件,如下图所示:
图 24.3.1链接脚本
打开u-boot.lds,内容如下:
示例代码 u-boot.lds文件代码
- 1 OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
- 2 OUTPUT_ARCH(arm)
- 3 ENTRY(_start)
- 4 SECTIONS
- 5 {
- 6 . = 0x00000000;
- 7 . = ALIGN(4);
- 8 .text :
- 9 {
- 10 *(.__image_copy_start)
- 11 *(.vectors)
- 12 arch/arm/cpu/armv7/start.o (.text*)
- 13 *(.text*)
- 14 }
- 15 . = ALIGN(4);
- 16 .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }
- 17 . = ALIGN(4);
- 18 .data : {
- 19 *(.data*)
- 20 }
- 21 . = ALIGN(4);
- 22 . = .;
- 23 . = ALIGN(4);
- 24 .u_boot_list : {
- 25 KEEP(*(SORT(.u_boot_list*)));
- 26 }
- 27 . = ALIGN(4);
- 28 .__efi_runtime_start : {
- 29 *(.__efi_runtime_start)
- 30 }
- 31 .efi_runtime : {
- 32 *(efi_runtime_text)
- 33 *(efi_runtime_data)
- 34 }
- 35 .__efi_runtime_stop : {
- 36 *(.__efi_runtime_stop)
- 37 }
- 38 .efi_runtime_rel_start :
- 39 {
- 40 *(.__efi_runtime_rel_start)
- 41 }
- 42 .efi_runtime_rel : {
- 43 *(.relefi_runtime_text)
- 44 *(.relefi_runtime_data)
- 45 }
- 46 .efi_runtime_rel_stop :
- 47 {
- 48 *(.__efi_runtime_rel_stop)
- 49 }
- 50 . = ALIGN(4);
- 51 .image_copy_end :
- 52 {
- 53 *(.__image_copy_end)
- 54 }
- 55 .rel_dyn_start :
- 56 {
- 57 *(.__rel_dyn_start)
- 58 }
- 59 .rel.dyn : {
- 60 *(.rel*)
- 61 }
- 62 .rel_dyn_end :
- 63 {
- 64 *(.__rel_dyn_end)
- 65 }
- 66 .end :
- 67 {
- 68 *(.__end)
- 69 }
- 70 _image_binary_end = .;
- 71 .bss_start __rel_dyn_start (OVERLAY) : {
- 72 KEEP(*(.__bss_start));
- 73 __bss_base = .;
- 74 }
- 75 .bss __bss_base (OVERLAY) : {
- 76 *(.bss*)
- 77 . = ALIGN(4);
- 78 __bss_limit = .;
- 79 }
- 80 .bss_end __bss_limit (OVERLAY) : {
- 81 KEEP(*(.__bss_end));
- 82 }
- 83 /DISCARD/ : { *(.dynsym) }
- 84 /DISCARD/ : { *(.dynbss*) }
- 85 /DISCARD/ : { *(.dynstr*) }
- 86 /DISCARD/ : { *(.dynamic*) }
- 87 /DISCARD/ : { *(.plt*) }
- 88 /DISCARD/ : { *(.interp*) }
- 89 /DISCARD/ : { *(.gnu*) }
- 90 /DISCARD/ : { *(.ARM.exidx*) }
- 91 /DISCARD/ : { *(.gnu.linkonce.armexidx.*) }
- 92 }
复制代码
链接脚本可以决定生成的u-boot镜像中所有.o文件和.a文件的链接地址。下面简单说明上述链接脚本中各行代码的含义。
第1行,指定输出可执行文件是32位ARM指令、小端模式的ELF格式。
第2行,指定输出可执行文件的平台为ARM
第3行为代码入口点:_start,_start在文件arch/arm/lib/vectors.S中有定义,具体代码见示例代码 24.4.1 vectors.S代码段。
第6行,指明目标代码的起始地址从0x0位置开始,“.”代表当前位置。
第7行,表示此处4字节对齐。
对于第10行,使用如下命令在uboot中查找“__image_copy_start”:
- grep -nR "__image_copy_start"
复制代码
搜索结果如下图所示:
图 24.3.2查找结果
打开u-boot.map,找到如下图所示位置:
图 24.3.3 u-boot.map
u-boot.map是uboot的映射文件,可以从该文件看到某个文件或者函数链接到了哪个地址,从上图的1228行可以看到__image_copy_start为0x400000,而.text的起始地址也是0x400000。
第11行是vectors段,vectors段保存中断向量表。从图 24.3.3可以看出,vectors段的起始地址也是0x400000,说明整个uboot的起始地址就是0x400000。
第12行将arch/arm/cpu/armv7/start.s编译出来的代码放到中断向量表后面。
第13行为text段,其他的代码段都放到这里。
第16行,只读数据段。
第18行,数据段。
第71行,bss_start标号指向bss段的开始位置。
第75行,bss段。BSS是英文Block Started by Symbol的缩写。BSS段通常是指用于存放程序中未初始化的全局变量的一块内存区域,该段中的变量在使用前由系统初始化为0。
在u-boot.lds中有一些跟地址有关的“变量”需要我们注意一下,后面分析u-boot源码的时候会用到,这些变量要最终编译完成才能确定的。比如笔者编译完成以后这些“变量”的值如下表所示:
表 23.3.15.1 uboot相关变量表
变量 数值 描述
__image_copy_start 0x400000 uboot拷贝的首地址
__image_copy_end 0x474798 uboot拷贝的结束地址
__rel_dyn_start 0x474798 .rel.dyn起始地址
__rel_dyn_end 0x480cb0 .rel.dyn结束地址
_image_binary_end 0x480cb0 二进制镜像结束地址
__bss_start 0x474798 bss段起始地址
__bss_end 0x4b58e8 bss段结束地址
上表中的“变量”值可以在u-boot.map文件中查找,表中除了__image_copy_start以外,其他的变量值每次编译的时候可能会变化,如果修改了uboot代码、修改了uboot配置、选用不同的优化等级等都会影响到这些值,所以一切以实际值为准.
13.4U-Boot启动流程详解
13.4.1reset函数源码详解
从u-boot.lds文件中我们已经知道了入口点是arch/arm/lib/vectors.S文件中的_start,代码如下:
示例代码 24.4.1 vectors.S代码段
- 18 /*
- 19 * A macro to allow insertion of an ARM exception vector either
- 20 * for the non-boot0 case or by a boot0-header.
- 21 */
- 22 .macro ARM_VECTORS
- 23 b reset
- 24 ldr pc, _undefined_instruction
- 25 ldr pc, _software_interrupt
- 26 ldr pc, _prefetch_abort
- 27 ldr pc, _data_abort
- 28 ldr pc, _not_used
- 29 ldr pc, _irq
- 30 ldr pc, _fiq
- 31 .endm
- 32
- 33
- 34 /*
- 35 *************************************************************************
- 36 *
- 37 * Symbol _start is referenced elsewhere, so make it global
- 38 *
- 39 *************************************************************************
- 40 */
- 41
- 42 .globl _start
- 43
- 44 /*
- 45 *************************************************************************
- 46 *
- 47 * Vectors have their own section so linker script can map them easily
- 48 *
- 49 *************************************************************************
- 50 */
- 51
- 52 .section ".vectors", "ax"
- 53
- 54 #if defined(CONFIG_ENABLE_ARM_SOC_BOOT0_HOOK)
- ……
- 69 #else
- 70
- 71 /*
- 72 *************************************************************************
- 73 *
- 74 * Exception vectors as described in ARM reference manuals
- 75 *
- 76 * Uses indirect branch to allow reaching handlers anywhere in memory.
- 77 *
- 78 *************************************************************************
- 79 */
- 80
- 81 _start:
- 82 #ifdef CONFIG_SYS_DV_NOR_BOOT_CFG
- 83 .word CONFIG_SYS_DV_NOR_BOOT_CFG
- 84 #endif
- 85 ARM_VECTORS
- 86 #endif /* !defined(CONFIG_ENABLE_ARM_SOC_BOOT0_HOOK) */
复制代码
第54行的宏CONFIG_ENABLE_ARM_SOC_BOOT0_HOOK,从下图可以看到其未定义。
图 24.4.1 CONFIG_ENABLE_ARM_SOC_BOOT0_HOOK
直接看“#else”部分。第81行_start开始的是中断向量表,CONFIG_SYS_DV_NOR_BOOT_CFG未定义,直接看第85行的ARM_VECTORS,对应第22~30行的中断向量表。当一个异常或中断发生时,CPU根据异常,在异常(中断)向量表中找到对应的异常向量,然后执行异常向量处的跳转指令,CPU跳转到对应的异常处理程序执行。其中第23行的复位异常向量的指令“b reset”决定u-boot启动后将自动跳转到标号reset处执行。标号reset在arch/arm/cpu/armv7/start.S中定义,代码如下:
示例代码 24.4.2 start.S代码段
- 22 /*************************************************************************
- 23 *
- 24 * Startup Code (reset vector)
- 25 *
- 26 * Do important init only if we don't start from memory!
- 27 * Setup memory and board specific bits prior to relocation.
- 28 * Relocate armboot to ram. Setup stack.
- 29 *
- 30 *************************************************************************/
- 31
- 32 .globl reset
- 33 .globl save_boot_params_ret
- 34 .type save_boot_params_ret,%function
- 35 #ifdef CONFIG_ARMV7_LPAE
- 36 .global switch_to_hypervisor_ret
- 37 #endif
- 38
- 39 reset:
- 40 /* Allow the board to save important registers */
- 41 b save_boot_params
复制代码
第39行就是标号reset。注:为了方便,后面对标号和ENTRY统一称为函数。
第41行从标号reset跳转到了save_boot_params函数,而save_boot_params函数同样定义在start.S中,定义如下:
示例代码 start.S代码段
- 107 /************************************************************************
- 108 *
- 109 * void save_boot_params(u32 r0, u32 r1, u32 r2, u32 r3)
- 110 * __attribute__((weak));
- 111 *
- 112 * Stack pointer is not yet initialized at this moment
- 113 * Don't save anything to stack even if compiled with -O0
- 114 *
- 115 ***********************************************************************/
- 116 ENTRY(save_boot_params)
- 117 b save_boot_params_ret @ back to my caller
复制代码
save_boot_params函数也是只有一句跳转语句,跳转到save_boot_params_ret函数,save_boot_params_ret函数代码如下:
示例代码 start.S代码段
- 42 save_boot_params_ret:
- 43 #ifdef CONFIG_ARMV7_LPAE
- 44 /*
- 45 * check for Hypervisor support
- 46 */
- 47 mrc p15, 0, r0, c0, c1, 1 @ read ID_PFR1
- 48 and r0, r0, #CPUID_ARM_VIRT_MASK @ mask virtualization bits
- 49 cmp r0, #(1 << CPUID_ARM_VIRT_SHIFT)
- 50 beq switch_to_hypervisor
- 51 switch_to_hypervisor_ret:
- 52 #endif
- 53 /*
- 54 * disable interrupts (FIQ and IRQ), also set the cpu to SVC32 mode,
- 55 * except if in HYP mode already
- 56 */
- 57 mrs r0, cpsr
- 58 and r1, r0, #0x1f @ mask mode bits
- 59 teq r1, #0x1a @ test for HYP mode
- 60 bicne r0, r0, #0x1f @ clear all mode bits
- 61 orrne r0, r0, #0x13 @ set SVC mode
- 62 orr r0, r0, #0xc0 @ disable FIQ and IRQ
- 63 msr cpsr,r0
复制代码
第43行表示是否定义宏CONFIG_ARMV7_LPAE。LPAE(Large Physical Address Extensions)是ARMv7系列的一种地址扩展技术,可以让32位的ARM最大能支持到1TB的内存空间,由于嵌入式ARM需求的内存空间一般不大,所以一般不使用LPAE技术。
第57行,读取寄存器cpsr中的值,并保存到r0寄存器中。
第58行,将寄存器r0中的值与0X1F进行与运算,结果保存到r1寄存器中,目的是提取cpsr的bit0~bit4这5位,这5位为M4 M3 M2 M1 M0,M[4:0]这五位用来设置处理器的工作模式,如下表所示:
表 24.4.1.1 Cortex-A9工作模式
第59行,判断r1寄存器的值是否等于0x1A(0b11010),也就是判断当前处理器模式是否处于Hyp模式。
第60行,如果r1和0x1A不相等,也就是CPU不处于Hyp模式的话就将r0寄存器的bit0~5进行清零,其实就是清除模式位。
第61行,如果处理器不处于Hyp模式的话就将r0的寄存器的值与0x13进行或运算,0x13=0b10011,也就是设置处理器进入SVC模式。
第62行,r0寄存器的值再与0xC0进行或运算,那么r0寄存器此时的值就是0xD3,cpsr的I为和F位分别控制IRQ和FIQ这两个中断的开关,设置为1就关闭了FIQ和IRQ。
第63行,将r0寄存器写回到cpsr寄存器中。完成设置CPU处于SVC32模式,并且关闭FIQ和IRQ这两个中断。
继续执行执行下面的代码:
示例代码 start.S代码段
- 65 /*
- 66 * Setup vector:
- 67 * (OMAP4 spl TEXT_BASE is not 32 byte aligned.
- 68 * Continue to use ROM code vector only in OMAP4 spl)
- 69 */
- 70 #if !(defined(CONFIG_OMAP44XX) && defined(CONFIG_SPL_BUILD))
- 71 /* Set V=0 in CP15 SCTLR register - for VBAR to point to vector */
- 72 mrc p15, 0, r0, c1, c0, 0 @ Read CP15 SCTLR Register
- 73 bic r0, #CR_V @ V = 0
- 74 mcr p15, 0, r0, c1, c0, 0 @ Write CP15 SCTLR Register
- 75
- 76 /* Set vector address in CP15 VBAR register */
- 77 ldr r0, =_start
- 78 mcr p15, 0, r0, c12, c0, 0 @Set VBAR
- 79 #endif
复制代码
第70行,如果没有定义CONFIG_OMAP44XX和CONFIG_SPL_BUILD则条件成立,此处条件成立。此处简单的说下SPL,SPL是Secondary Program Loader的简称,也就是第二阶段程序加载器,可用来初始化DDR内存并加载uboot到内存中,功能类似于FSBL,可使用CONFIG_SPL_BUILD来选择编译,一般我们不使用SPL。
第72行读取CP15中c1寄存器的值到r0寄存器中。
第73行,CR_V在arch/arm/include/asm/system.h中有如下所示定义:
- #define CR_V (1 << 13) /* Vectors relocated to 0xffff0000 */
复制代码
因此这一行的目的就是清除r0寄存器中的bit13,SCTLR寄存器结构如下图所示:
图 24.4.2 SCTLR寄存器结构图
从图可以看出,bit13为V位,此位是向量表控制位,当为0的时候向量表基地址为0X00000000,软件可以重定位向量表。为1的时候向量表基地址为0XFFFF0000,软件不能重定位向量表。这里将V清零,目的就是为了接下来的向量表重定位。
第74行将r0寄存器的值重写入到寄存器SCTLR中。
第77行设置r0寄存器的值为_start,_start就是整个uboot的入口地址,其值为0x4000000,相当于uboot的起始地址,因此0x4000000也是向量表的起始地址。
第78行将r0寄存器的值(向量表值)写入到CP15的c12寄存器中,也就是VBAR寄存器。因此第72~78行就是设置向量表重定位的。
代码继续往下执行:
示例代码 start.S代码段
- 81 /* the mask ROM code should have PLL and others stable */
- 82 #ifndef CONFIG_SKIP_LOWLEVEL_INIT
- 83 bl cpu_init_cp15
- 84 #ifndef CONFIG_SKIP_LOWLEVEL_INIT_ONLY
- 85 bl cpu_init_crit
- 86 #endif
- 87 #endif
- 88
- 89 bl _main
复制代码
第82行如果没有定义CONFIG_SKIP_LOWLEVEL_INIT的话条件成立。我们没有定义CONFIG_SKIP_LOWLEVEL_INIT,因此条件成立,执行下面的bl语句。
此处代码中的内容比较简单,就是分别调用函数cpu_init_cp15、cpu_init_crit和_main。
函数cpu_init_cp15用来设置CP15相关的内容,比如关闭MMU啥的,此函数同样在start.S文件中定义的,代码如下:
示例代码 start.S代码段
- 128 /*************************************************************************
- 129 *
- 130 * cpu_init_cp15
- 131 *
- 132 * Setup CP15 registers (cache, MMU, TLBs). The I-cache is turned on unless
- 133 * CONFIG_SYS_ICACHE_OFF is defined.
- 134 *
- 135 *************************************************************************/
- 136 ENTRY(cpu_init_cp15)
- 137 /*
- 138 * Invalidate L1 I/D
- 139 */
- 140 mov r0, #0 @ set up for MCR
- 141 mcr p15, 0, r0, c8, c7, 0 @ invalidate TLBs
- 142 mcr p15, 0, r0, c7, c5, 0 @ invalidate icache
- 143 mcr p15, 0, r0, c7, c5, 6 @ invalidate BP array
- 144 mcr p15, 0, r0, c7, c10, 4 @ DSB
- 145 mcr p15, 0, r0, c7, c5, 4 @ ISB
- 146
- 147 /*
- 148 * disable MMU stuff and caches
- 149 */
- 150 mrc p15, 0, r0, c1, c0, 0
- 151 bic r0, r0, #0x00002000 @ clear bits 13 (--V-)
- 152 bic r0, r0, #0x00000007 @ clear bits 2:0 (-CAM)
- 153 orr r0, r0, #0x00000002 @ set bit 1 (--A-) Align
- 154 orr r0, r0, #0x00000800 @ set bit 11 (Z---) BTB
- 155 #ifdef CONFIG_SYS_ICACHE_OFF
- 156 bic r0, r0, #0x00001000 @ clear bit 12 (I) I-cache
- 157 #else
- 158 orr r0, r0, #0x00001000 @ set bit 12 (I) I-cache
- 159 #endif
- 160 mcr p15, 0, r0, c1, c0, 0
- ......
- 305 mov pc, r5 @ back to my caller
- 306 ENDPROC(cpu_init_cp15)
复制代码
函数cpu_init_cp15都是一些和CP15有关的内容,我们不用关心,有兴趣的可以详细的看一下。
函数cpu_init_crit也在是定义在start.S文件中,函数内容如下:
示例代码 start.S代码段
- 310 /*************************************************************************
- 311 *
- 312 * CPU_init_critical registers
- 313 *
- 314 * setup important registers
- 315 * setup memory timing
- 316 *
- 317 *************************************************************************/
- 318 ENTRY(cpu_init_crit)
- 319 /*
- 320 * Jump to board specific initialization...
- 321 * The Mask ROM will have already initialized
- 322 * basic memory. Go here to bump up clock rate and handle
- 323 * wake up conditions.
- 324 */
- 325 b lowlevel_init @ go setup pll,mux,memory
- 326 ENDPROC(cpu_init_crit)
复制代码
可以看出函数cpu_init_crit内部仅仅是调用了函数lowlevel_init,接下来我们详细的分析一下lowlevel_init和_main这两个函数。
13.4.2lowlevel_init函数详解
函数lowlevel_init的目的一般是为了允许执行函数board_init_f所做的基本初始化,在文件arch/arm/mach-zynq/lowlevel_init.S中定义,内容如下:
示例代码 lowlevel_init.S代码段
- 7 #include <asm-offsets.h>
- 8 #include <config.h>
- 9 #include <linux/linkage.h>
- 10
- 11 ENTRY(lowlevel_init)
- 12
- 13 /* Enable the the VFP */
- 14 mrc p15, 0, r1, c1, c0, 2
- 15 orr r1, r1, #(0x3 << 20)
- 16 orr r1, r1, #(0x3 << 20)
- 17 mcr p15, 0, r1, c1, c0, 2
- 18 isb
- 19 fmrx r1, FPEXC
- 20 orr r1,r1, #(1<<30)
- 21 fmxr FPEXC, r1
- 22
- 23 /* Move back to caller */
- 24 mov pc, lr
- 25
- 26 ENDPROC(lowlevel_init)
复制代码
可以看到ZYNQ的lowlevel_init函数功能很简单。第14-21行使能VFP。第24行返回调用。
函数调用路径如下图所示:
图 24.4.3 uboot函数调用路径
从上图可知,接下来要执行的是save_boot_params_ret中的_main函数,接下来分析_main函数。
13.4.3_main函数详解
_main函数定义在文件arch/arm/lib/crt0.S中,函数内容如下:
示例代码 crt0.S代码段
- 63 /*
- 64 * entry point of crt0 sequence
- 65 */
- 66
- 67 ENTRY(_main)
- 68
- 69 /*
- 70 * Set up initial C runtime environment and call board_init_f(0).
- 71 */
- 72
- 73 #if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
- 74 ldr r0, =(CONFIG_SPL_STACK)
- 75 #else
- 76 ldr r0, =(CONFIG_SYS_INIT_SP_ADDR)
- 77 #endif
- 78 bic r0, r0, #7 /* 8-byte alignment for ABI compliance */
- 79 mov sp, r0
- 80 bl board_init_f_alloc_reserve
- 81 mov sp, r0
- 82 /* set up gd here, outside any C code */
- 83 mov r9, r0
- 84 bl board_init_f_init_reserve
- 85
- 86 mov r0, #0
- 87 bl board_init_f
- 88
- 89 #if ! defined(CONFIG_SPL_BUILD)
- 90
- 91 /*
- 92 * Set up intermediate environment (new sp and gd) and call
- 93 * relocate_code(addr_moni). Trick here is that we'll return
- 94 * 'here' but relocated.
- 95 */
- 96
- 97 ldr r0, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */
- 98 bic r0, r0, #7 /* 8-byte alignment for ABI compliance */
- 99 mov sp, r0
- 100 ldr r9, [r9, #GD_BD] /* r9 = gd->bd */
- 101 sub r9, r9, #GD_SIZE /* new GD is below bd */
- 102
- 103 adr lr, here
- 104 ldr r0, [r9, #GD_RELOC_OFF] /* r0 = gd->reloc_off */
- 105 add lr, lr, r0
- 106 #if defined(CONFIG_CPU_V7M)
- 107 orr lr, #1 /* As required by Thumb-only */
- 108 #endif
- 109 ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
- 110 b relocate_code
- 111 here:
- 112 /*
- 113 * now relocate vectors
- 114 */
- 115
- 116 bl relocate_vectors
- 117
- 118 /* Set up final (full) environment */
- 119
- 120 bl c_runtime_cpu_setup /* we still call old routine here */
- 121 #endif
- 122 #if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_FRAMEWORK)
- 123 # ifdef CONFIG_SPL_BUILD
- 124 /* Use a DRAM stack for the rest of SPL, if requested */
- 125 bl spl_relocate_stack_gd
- 126 cmp r0, #0
- 127 movne sp, r0
- 128 movne r9, r0
- 129 # endif
- 130 ldr r0, =__bss_start /* this is auto-relocated! */
- 131
- 132 #ifdef CONFIG_USE_ARCH_MEMSET
- 133 ldr r3, =__bss_end /* this is auto-relocated! */
- 134 mov r1, #0x00000000 /* prepare zero to clear BSS */
- 135
- 136 subs r2, r3, r0 /* r2 = memset len */
- 137 bl memset
- 138 #else
- 139 ldr r1, =__bss_end /* this is auto-relocated! */
- 140 mov r2, #0x00000000 /* prepare zero to clear BSS */
- 141
- 142 clbss_l:cmp r0, r1 /* while not at end of BSS */
- 143 #if defined(CONFIG_CPU_V7M)
- 144 itt lo
- 145 #endif
- 146 strlo r2, [r0] /* clear 32-bit BSS word */
- 147 addlo r0, r0, #4 /* move to next */
- 148 blo clbss_l
- 149 #endif
- 150
- 151 #if ! defined(CONFIG_SPL_BUILD)
- 152 bl coloured_LED_init
- 153 bl red_led_on
- 154 #endif
- 155 /* call board_init_r(gd_t *id, ulong dest_addr) */
- 156 mov r0, r9 /* gd_t */
- 157 ldr r1, [r9, #GD_RELOCADDR] /* dest_addr */
- 158 /* call board_init_r */
- 159 #if CONFIG_IS_ENABLED(SYS_THUMB_BUILD)
- 160 ldr lr, =board_init_r /* this is auto-relocated! */
- 161 bx lr
- 162 #else
- 163 ldr pc, =board_init_r /* this is auto-relocated! */
- 164 #endif
- 165 /* we should not return here. */
- 166 #endif
- 167
- 168 ENDPROC(_main)
复制代码
第76行,加载CONFIG_SYS_INIT_SP_ADDR到r0。CONFIG_SYS_INIT_SP_ADDR在include/configs/zynq-common.h文件中有如下所示定义:
示例代码 zynq-common.h代码段
- 341 #define CONFIG_SYS_INIT_RAM_ADDR 0xFFFF0000
- 342 #define CONFIG_SYS_INIT_RAM_SIZE 0x2000
- 343 #define CONFIG_SYS_INIT_SP_ADDR (CONFIG_SYS_INIT_RAM_ADDR + \
- 344 CONFIG_SYS_INIT_RAM_SIZE - \
- 345 GENERATED_GBL_DATA_SIZE)
复制代码
还需要知道GENERATED_GBL_DATA_SIZE的值,在文件include/generated/generic-asm-offsets.h中有定义如下:
示例代码 generic-asm-offsets.h代码段
- 1 #ifndef __GENERIC_ASM_OFFSETS_H__
- 2 #define __GENERIC_ASM_OFFSETS_H__
- 3 /*
- 4 * DO NOT MODIFY.
- 5 *
- 6 * This file was generated by Kbuild
- 7 */
- 8
- 9 #define GENERATED_GBL_DATA_SIZE 208 /* (sizeof(struct global_data) + 15) & ~15 @ */
- 10 #define GENERATED_BD_INFO_SIZE 80 /* (sizeof(struct bd_info) + 15) & ~15 @ */
- 11 #define GD_SIZE 200 /* sizeof(struct global_data) @ */
- 12 #define GD_BD 0 /* offsetof(struct global_data, bd) @ */
- 13 #define GD_MALLOC_BASE 148 /* offsetof(struct global_data, malloc_base) @ */
- 14 #define GD_RELOCADDR 44 /* offsetof(struct global_data, relocaddr) @ */
- 15 #define GD_RELOC_OFF 64 /* offsetof(struct global_data, reloc_off) @ */
- 16 #define GD_START_ADDR_SP 60 /* offsetof(struct global_data, start_addr_sp) @ */
- 17 #define GD_NEW_GD 68 /* offsetof(struct global_data, new_gd) @ */
- 18
- 19 #endif
复制代码
GENERATED_GBL_DATA_SIZE=208,GENERATED_GBL_DATA_SIZE的含义为(sizeof(struct global_data) + 15) & ~15。
综上所述,CONFIG_SYS_INIT_SP_ADDR值如下:
- CONFIG_SYS_INIT_SP_ADDR = 0xFFFF0000+ 0x2000 - 208= 0xFFFF1F30,
复制代码
此时r0的值为0xFFFF1F30。
第78行,r0做8字节对齐。
第79行,读取r0到寄存器sp里面,此时sp=0xFFFF1F30,属于ZYNQ的内部ram
第80行,调用函数board_init_f_alloc_reserve,此函数有一个参数,参数为r0中的值,也就是0xFFFF1F30,此函数定义在文件common/init/board_init.c中,内容如下:
示例代码 board_init.c代码段
- 46 ulong board_init_f_alloc_reserve(ulong top)
- 47 {
- 48 /* Reserve early malloc arena */
- 49 #if CONFIG_VAL(SYS_MALLOC_F_LEN)
- 50 top -= CONFIG_VAL(SYS_MALLOC_F_LEN);
- 51 #endif
- 52 /* LAST : reserve GD (rounded up to a multiple of 16 bytes) */
- 53 top = rounddown(top-sizeof(struct global_data), 16);
- 54
- 55 return top;
- 56 }
复制代码
函数board_init_f_alloc_reserve主要是从内存顶部地址分配保留空间以用作全局变量使用,返回分配空间的底部地址。其中SYS_MALLOC_F_LEN=0x800(在文件include/generated/autoconf.h中定义),sizeof(struct global_data)=200(GD_SIZE值)。
函数board_init_f_alloc_reserve是有返回值的,返回值为新的top值,此时top=0xffff1668。
继续回到_main函数,第81行,将r0写入到sp里面,r0保存着函数board_init_f_alloc_reserve的返回值,所以这一句也就是设置sp=0xffff1660(注因为需要16字节对齐,所以是0xffff1660)。
第83行,将r0寄存器的值写到寄存器r9里面,因为r9寄存器存放着全局变量gd的地址,在文件arch/arm/include/asm/global_data.h中有如下图所示宏定义:
图 24.4.4 DECLARE_GLOBAL_DATA_PTR宏定义
从上图可以看出,uboot中定义了一个指向gd_t的指针gd,gd存放在寄存器r9里面的,因此gd是个全局变量。gd_t是个结构体,在include/asm-generic/global_data.h里面有定义,gd_t定义如下:
示例代码 global_data.h代码段
- 27 typedef struct global_data {
- 28 bd_t *bd;
- 29 unsigned long flags;
- 30 unsigned int baudrate;
- 31 unsigned long cpu_clk; /* CPU clock in Hz! */
- 32 unsigned long bus_clk;
- 33 /* We cannot bracket this with CONFIG_PCI due to mpc5xxx */
- 34 unsigned long pci_clk;
- 35 unsigned long mem_clk;
- 36 #if defined(CONFIG_LCD) || defined(CONFIG_VIDEO)
- 37 unsigned long fb_base; /* Base address of framebuffer mem */
- 38 #endif
- ......
- 117 #ifdef CONFIG_LOG
- 118 int log_drop_count; /* Number of dropped log messages */
- 119 int default_log_level; /* For devices with no filters */
- 120 struct list_head log_head; /* List of struct log_device */
- 121 #endif
- 122 } gd_t;
复制代码
因此这一行代码就是设置gd所指向的位置,也就是gd指向0xffff1660。
回到_main函数,第84行调用函数board_init_f_init_reserve,此函数在文件common/init/board_init.c中有定义,函数内容如下:
示例代码 board_init.c代码段
- 100 void board_init_f_init_reserve(ulong base)
- 101 {
- 102 struct global_data *gd_ptr;
- 103
- 104 /*
- 105 * clear GD entirely and set it up.
- 106 * Use gd_ptr, as gd may not be properly set yet.
- 107 */
- 108
- 109 gd_ptr = (struct global_data *)base;
- 110 /* zero the area */
- 111 memset(gd_ptr, '\0', sizeof(*gd));
- 112 /* set GD unless architecture did it already */
- 113 #if !defined(CONFIG_ARM)
- 114 arch_setup_gd(gd_ptr);
- 115 #endif
- 116 /* next alloc will be higher by one GD plus 16-byte alignment */
- 117 base += roundup(sizeof(struct global_data), 16);
- 118
- 119 /*
- 120 * record early malloc arena start.
- 121 * Use gd as it is now properly set for all architectures.
- 122 */
- 123
- 124 #if CONFIG_VAL(SYS_MALLOC_F_LEN)
- 125 /* go down one 'early malloc arena' */
- 126 gd->malloc_base = base;
- 127 /* next alloc will be higher by one 'early malloc arena' size */
- 128 base += CONFIG_VAL(SYS_MALLOC_F_LEN);
- 129 #endif
- 130 }
复制代码
可以看出,此函数用于初始化已分配的全局变量(gd)的保留空间,其实就是清零处理。另外,此函数还设置了gd->malloc_base为gd基地址+gd大小=0xffff1660+200=0xffff1728,并做16字节对齐,最终gd->malloc_base=0xffff1730,这个也就是early malloc的起始地址。
继续回到_main函数,第86行设置R0为0。
第87行,调用board_init_f函数,此函数定义在文件common/board_f.c中。主要用来初始化DDR,定时器,完成代码拷贝等等,此函数我们后面进行详细的分析。
第97行,重新设置环境(sp和gd)、获取gd->start_addr_sp的值赋给r0,在函数board_init_f中会初始化gd的所有成员变量,其中gd->start_addr_sp=0x3eb19ec0,所以这里相当于设置r0=gd->start_addr_sp=0x3eb19ec0。0x3eb19ec0是DDR中的地址,说明新的sp和gd将会存放到DDR中,而不是内部的RAM了。GD_START_ADDR_SP=60,参考示例代码generic-asm-offsets.h。
第98-99行,r0做8字节对齐并赋值给sp。
第100行,获取gd->bd的地址赋给r9,此时r9存放的是老的gd,这里通过获取gd->bd的地址来计算出新的gd的位置。GD_BD=0,参考示例代码generic-asm-offsets.h。
第101行,新的gd在bd下面,所以r9减去gd的大小就是新的gd的位置,获取到新的gd的位置以后赋值给r9。
第103行,设置lr寄存器为here,这样后面执行其他函数返回的时候就返回到了第111行的here位置处。
第104,读取gd->reloc_off的值给r0寄存器,GD_RELOC_OFF=64,参考示例代码generic-asm-offsets.h。
第105行,lr寄存器的值加上r0寄存器的值,重新赋值给lr寄存器。因为接下来要重定位代码,也就是把代码拷贝到新的地方去(现在的uboot存放的起始地址为0x400000,下面要将uboot拷贝到DDR最后面的地址空间出,将0x400000开始的内存空出来),其中就包括here,因此lr中的here要使用重定位后的位置。
第109行,读取gd->relocaddr的值赋给r0寄存器,此时r0寄存器就保存着uboot要拷贝的目的地址,为0x3ff3a000。GD_RELOCADDR=44,参考示例代码generic-asm-offsets.h。
第110行,调用函数relocate_code,也就是代码重定位函数,此函数负责将uboot拷贝到新的地方去,此函数定义在文件arch/arm/lib/relocate.S中稍后会详细分析此函数。
第116行,调用函数relocate_vectors,对中断向量表做重定位,此函数定义在文件arch/arm/lib/relocate.S中,稍后会详细分析此函数。
第120行,调用函数c_runtime_cpu_setup,此函数定义在文件arch/arm/cpu/armv7/start.S中,函数作用为如果启用了I-cache,则使其无效。
第122~149行,清除BSS段。
第156~157行,设置函数board_init_r的两个参数,函数board_init_r声明如下:
board_init_r(gd_t *id, ulong dest_addr)
第一个参数是gd,因此读取r9保存到r0里面。
第二个参数是目的地址,因此r1= gd->relocaddr。
第163行,调用函数board_init_r,此函数定义在文件common/board_r.c中,稍后会详细的分析此函数。
这个就是_main函数的运行流程,在_main函数里面主要调用了board_init_f、relocate_code、relocate_vectors和board_init_r这4个函数,接下来依次来看下这4个函数的作用。
13.4.4board_init_f函数详解
_main中会board_init_f函数,board_init_f函数主要有两个工作:
①、初始化一系列外设,比如串口、定时器,或者打印一些消息等。
②、初始化gd的各个成员变量,uboot会将自己重定位到DRAM最后面的地址区域,也就是将自己拷贝到DRAM最后面的内存区域中。这么做的目的是给Linux腾出空间,防止Linux kernel覆盖掉uboot,将DRAM前面的区域完整的空出来。在拷贝之前肯定要给uboot各部分分配好内存位置和大小,比如gd应该存放到哪个位置,malloc内存池应该存放到哪个位置等等。这些信息都保存在gd的成员变量中,因此要对gd的这些成员变量做初始化。最终形成一个完整的内存“分配图”,在后面重定位uboot的时候就会用到这个内存“分配图”。
此函数定义在文件common/board_f.c中定义,代码如下:
示例代码 board_f.c代码段
- 886 void board_init_f(ulong boot_flags)
- 887 {
- 888 gd->flags = boot_flags;
- 889 gd->have_console = 0;
- 890
- 891 if (initcall_run_list(init_sequence_f))
- 892 hang();
- 893
- 894 #if !defined(CONFIG_ARM) && !defined(CONFIG_SANDBOX) && \
- 895 !defined(CONFIG_EFI_APP) && !CONFIG_IS_ENABLED(X86_64)
- 896 /* NOTREACHED - jump_to_copy() does not return */
- 897 hang();
- 898 #endif
- 899 }
复制代码
第888行,初始化gd->flags=boot_flags=0。
第889行,设置gd->have_console=0。
重点在第891行。通过函数initcall_run_list来运行初始化序列init_sequence_f里面的一系列函数。init_sequence_f里面包含了一系列的初始化函数,init_sequence_f也是定义在文件common/board_f.c中,由于init_sequence_f的内容比较长,里面有大量的条件编译代码,这里为了缩小篇幅,将条件编译部分删除掉了,去掉条件编译以后的init_sequence_f定义如下:
/*****************去掉条件编译语句后的init_sequence_f***************/
- 1 static const init_fnc_t init_sequence_f[] = {
- 2 setup_mon_len,
- 3 fdtdec_setup,
- 4 initf_malloc,
- 5 log_init,
- 6 initf_bootstage, /* uses its own timer, so does not need DM */
- 7 initf_console_record,
- 8 arch_cpu_init, /* basic arch cpu dependent setup */
- 9 mach_cpu_init, /* SoC/machine dependent CPU setup */
- 10 initf_dm,
- 11 arch_cpu_init_dm,
- 12 timer_init, /* initialize timer */
- 13 env_init, /* initialize environment */
- 14 init_baud_rate, /* initialze baudrate settings */
- 15 serial_init, /* serial communications setup */
- 16 console_init_f, /* stage 1 init of console */
- 17 display_options, /* say that we are here */
- 18 display_text_info, /* show debugging info if required */
- 19 show_board_info,
- 20 INIT_FUNC_WATCHDOG_INIT
- 21 INIT_FUNC_WATCHDOG_RESET
- 22 init_func_i2c,
- 23 announce_dram_init,
- 24 dram_init, /* configure available RAM banks */
- 25 INIT_FUNC_WATCHDOG_RESET
- 26 INIT_FUNC_WATCHDOG_RESET
- 27 INIT_FUNC_WATCHDOG_RESET
- 28 /*
- 29 * Now that we have DRAM mapped and working, we can
- 30 * relocate the code and continue running from DRAM.
- 31 *
- 32 * Reserve memory at end of RAM for (top down in that order):
- 33 * - area that won't get touched by U-Boot and Linux (optional)
- 34 * - kernel log buffer
- 35 * - protected RAM
- 36 * - LCD framebuffer
- 37 * - monitor code
- 38 * - board info struct
- 39 */
- 40 setup_dest_addr,
- 41 reserve_round_4k,
- 42 reserve_mmu,
- 43 reserve_video,
- 44 reserve_trace,
- 45 reserve_uboot,
- 46 reserve_malloc,
- 47 reserve_board,
- 48 setup_machine,
- 49 reserve_global_data,
- 50 reserve_fdt,
- 51 reserve_bootstage,
- 52 reserve_arch,
- 53 reserve_stacks,
- 54 dram_init_banksize,
- 55 show_dram_config,
- 56 display_new_sp,
- 57 INIT_FUNC_WATCHDOG_RESET
- 58 reloc_fdt,
- 59 reloc_bootstage,
- 60 setup_reloc,
- 61 NULL,
- 62 };
复制代码
接下来分析以上函数执行完以后的结果:
第2行,setup_mon_len函数设置gd的mon_len成员变量,变量值为__bss_end -_start,也就是整个代码的长度。代码长度为0x4b58e8-0x400000=0Xb58e8。
第3行,调用fdtdec_setup函数配置gd->fdt_blob指针(即device tree所在的存储位置)。对于ARM平台,uboot的Makefile会通过连接脚本,将dtb文件打包到u-boot image的“__dtb_dt_begin”位置处。
第4行,initf_malloc函数初始化gd中跟malloc有关的成员变量,比如malloc_limit,此函数会设置gd->malloc_limit = CONFIG_SYS_MALLOC_F_LEN=0x800。malloc_limit表示malloc内存池大小。
第5行,log_init函数将struct log_driver结构体加入到gd->log_head的循环链表中,并初始化gd->default_log_level。
第6行,initf_bootstage函数为gd->bootstage分配空间,并初始化gd->bootstage。
第7行,initf_console_record,如果定义了宏CONFIG_CONSOLE_RECORD和宏CONFIG_SYS_MALLOC_F_LEN,此函数就会调用函数console_record_init,但是领航者的uboot没有定义宏CONFIG_CONSOLE_RECORD,所以此函数直接返回0。
第8行,arch_cpu_init函数,初始化架构相关的内容,CPU级别的操作。
第9行,mach_cpu_init函数,初始化SOC相关的内容,CPU级别的操作。
第10行,initf_dm函数,驱动模型有关的初始化操作,如果定义了CONFIG_DM,则调用dm_init_and_scan 初始化并扫描系统所有的device。如果定义了CONFIG_TIMER_EARLY,调用dm_timer_init初始化driver model 所需的timer。
第11行,arch_cpu_init_dm函数未实现。
第12行,timer_init,初始化定时器,Cortex-A9内核有一个定时器,这里初始化的就是Cortex-A内核的定时器。通过这个定时器来为uboot提供时间。关于Cortex-A内部定时器的详细内容,请参考文档《ARM ArchitectureReference Manual ARMv7-A and ARMv7-R edition.pdf》的“Chapter B8 The Generic Timer”章节。
第13行,env_init函数是和环境变量有关的,设置gd的成员变量env_addr,也就是环境变量的保存地址。
第14行,init_baud_rate函数用于初始化波特率,根据CONFIG_BAUDRATE的值来初始化gd->baudrate。
第15行,serial_init,初始化串口。
第16行,console_init_f,设置gd->have_console 为1,表示有1个控制台,此函数也将前面暂存在缓冲区中的数据通过控制台打印出来。
第17行、display_options,通过串口输出信息,如下图所示:
图 24.4.5串口信息输出
第18行,display_text_info,打印一些文本信息,如果开启UBOOT的DEBUG功能的话就会输出text_base、bss_start、bss_end,形式如下:
- debug("U-Boot code: %08lX -> %08lX BSS: -> %08lX\n",text_base, bss_start, bss_end);
复制代码
结果如下图所示:
图 24.4.6文本信息
开启uboot的DEBUG功能的方法是在uboot的根目录下的include/common.h 中添加行
第19行,show_board_info函数用于打印开发板信息,会调用checkboard函数,结果如下图所示:
图 24.4.7开发板信息
第20行,INIT_FUNC_WATCHDOG_INIT,初始化看门狗,对于ZYNQ而言未定义。
第21行,INIT_FUNC_WATCHDOG_RESET,复位看门狗,对于ZYNQ而言未定义。
第22行,init_func_i2c函数用于初始化I2C,初始化完成以后会输出如下图所示信息:
图 24.4.8 I2C初始化信息输出
第23行,announce_dram_init,此函数很简单,就是输出字符串“DRAM:”
第24行,dram_init,并非真正的初始化DDR,只是设置gd->ram_size的值并检测ecc,对于正点原子ZYNQ开发板7020核心板来说,DDR大小为1GB,所以gd->ram_size的值为0x40000000。
第40行,setup_dest_addr函数,设置目的地址,设置gd->ram_top,gd->relocaddr的值。gd->ram_top = 0x40000000,gd->relocaddr = 0x40000000,也就是说重定位后的最高地址为0x40000000。
第41行,reserve_round_4k函数用于对 gd->relocaddr做4KB对齐,因为gd->relocaddr=0x40000000,已经是4K对齐了,调整后不变。
第42行,reserve_mmu,留出MMU的TLB表的位置,分配MMU的TLB表内存以后会对gd->relocaddr做64K字节对齐。完成以后gd->arch.tlb_size、gd->arch.tlb_addr和gd->relocaddr的值如下:
- gd->arch.tlb_size= 0X4000 //MMU的TLB表大小
- gd->arch.tlb_addr=0x3fff0000 //MMU的TLB表起始地址,64KB对齐以后
- gd->relocaddr=0x3fff0000 //relocaddr地址
复制代码
第43行,reserve_video函数为视频显示开辟帧空间,zynq未用到。
第44行,reserve_trace函数,留出跟踪调试的内存,zynq未用到。
第45行,reserve_uboot, 留出重定位后的uboot所占用的内存区域,uboot所占用大小由gd->mon_len所指定,留出uboot的空间以后还要对gd->relocaddr做4K字节对齐,并且重新设置gd->start_addr_sp,结果如下:
- gd->mon_len = 0x000b58e8(使能Debug时为0xcb1ec)
- gd->start_addr_sp = 0x3ff3a000(使能Debug时为0x3ff24000)
- gd->relocaddr = 0x3ff3a000(使能Debug时为0x3ff24000)
复制代码
第46行,reserve_malloc,留出malloc区域,调整gd->start_addr_sp位置,malloc区域由宏TOTAL_MALLOC_LEN定义,宏定义如下:
#define TOTAL_MALLOC_LEN (CONFIG_SYS_MALLOC_LEN + CONFIG_ENV_SIZE)
宏CONFIG_SYS_MALLOC_LEN值为20MB=0x1400000,宏CONFIG_ENV_SIZE值为128KB=0x20000,因此TOTAL_MALLOC_LEN=0x1420000。调整以后gd->start_addr_sp如下:
- TOTAL_MALLOC_LEN=0x1420000
复制代码
gd->start_addr_sp=0x3eb1a000 //0x3ff3a000-0x1420000=0x3eb1a000(使能Debug时为0x3eb04000)
第47行,reserve_board函数,留出板子bd所占的内存区,bd是结构体bd_t,bd_t大小为80字节,结果如下:
- gd->start_addr_sp=0x3eb19fb0(使能Debug为0x3eb03fb0)
- gd->bd=0x3eb19fb0(使能Debug为0x3eb03fb0)
复制代码
第48行,setup_machine,设置机器ID,linux启动的时候会和这个机器ID匹配,如果匹配的话linux就会启动正常。但是ZYNQ已经不采用这种方式了,这是以前老版本的uboot和linux使用的,新版本使用设备树了,因此此函数不起作用。
第49行,reserve_global_data函数,保留出gd_t的内存区域,gd_t结构体大小为200B,结果如下:
gd->start_addr_sp=0x3eb19ee8 //0x3eb19fb0-0xc8=0x3eb19ee8(使能Debug时为0x3eb03ee8)
gd->new_gd=3eb19ee8(使能Debug为0x3eb03ee8)
第50行,reserve_fdt,留出设备树相关的内存区域,领航者的uboot没有用到,因此此函数无效。
第51行,reserve_bootstage保留bootstage空间,领航者的uboot没有用到。
第52行,reserve_arch是个空函数。
第53行,reserve_stacks,留出栈空间,先对gd->start_addr_sp减去16,然后做16字节对齐。如果使能IRQ的话还要留出IRQ相应的内存,具体工作是由arch/arm/lib/stack.c文件中的函数arch_reserve_stacks完成。结果如下(使能Debug):
gd->start_addr_sp= 0x3eb19ec0
第54行,setup_dram_config函数设置dram信息,也就是设置gd->bd->bi_dram[0].start和gd->bd->bi_dram[0].size,后面会传递给linux内核,告诉linux DRAM的起始地址和大小。DRAM的起始地址为0x00000000,大小为0x40000000 (1GB)。
第55行,show_dram_config函数,用于显示DRAM的配置,如下图所示:
图 24.4.9信息输出
第56行,display_new_sp函数,显示新的sp位置,也就是gd->start_addr_sp,不过要定义宏DEBUG,结果如下图所示:
图 24.4.10信息输出
上图中的gd->start_addr_sp值和我们前面分析的最后一次修改的值一致。
第58行,reloc_fdt函数用于重定位fdt,没有用到。
第59行,reloc_bootstage函数用于重定位bootstage,没有用到。
第60行,setup_reloc,设置gd其他一些成员变量,供后面重定位的时候使用,并且将以前的gd拷贝到gd->new_gd处。需要使能DEBUG才能看到相应的信息输出,如下图所示:
图 24.4.11信息输出
从上图可以看出,uboot重定位后的偏移为0x3fb24000,重定位后的新地址为0x3ff24000,新的gd首地址为0x3eb03ee8,最终的sp为0x3eb19ec0。
至此,board_init_f函数就执行完成了,最终的内存分配如下图所示:
图 24.4.12最终的内存分配图
13.4.5relocate_code函数详解
relocate_code函数是用于代码拷贝的,此函数定义在文件arch/arm/lib/relocate.S中,代码如下:
示例代码relocate.S代码段
- 68 /*
- 69 * void relocate_code(addr_moni)
- 70 *
- 71 * This function relocates the monitor code.
- 72 *
- 73 * NOTE:
- 74 * To prevent the code below from containing references with an R_ARM_ABS32
- 75 * relocation record type, we never refer to linker-defined symbols directly.
- 76 * Instead, we declare literals which contain their relative location with
- 77 * respect to relocate_code, and at run time, add relocate_code back to them.
- 78 */
- 79
- 80 ENTRY(relocate_code)
- 81 ldr r1, =__image_copy_start /* r1 <- SRC &__image_copy_start */
- 82 subs r4, r0, r1 /* r4 <- relocation offset */
- 83 beq relocate_done /* skip relocation */
- 84 ldr r2, =__image_copy_end /* r2 <- SRC &__image_copy_end */
- 85
- 86 copy_loop:
- 87 ldmia r1!, {r10-r11} /* copy from source address [r1] */
- 88 stmia r0!, {r10-r11} /* copy to target address [r0] */
- 89 cmp r1, r2 /* until source end address [r2] */
- 90 blo copy_loop
- 91
- 92 /*
- 93 * fix .rel.dyn relocations
- 94 */
- 95 ldr r2, =__rel_dyn_start /* r2 <- SRC &__rel_dyn_start */
- 96 ldr r3, =__rel_dyn_end /* r3 <- SRC &__rel_dyn_end */
- 97 fixloop:
- 98 ldmia r2!, {r0-r1} /* (r0,r1) <- (SRC location,fixup) */
- 99 and r1, r1, #0xff
- 100 cmp r1, #R_ARM_RELATIVE
- 101 bne fixnext
- 102
- 103 /* relative fix: increase location by offset */
- 104 add r0, r0, r4
- 105 ldr r1, [r0]
- 106 add r1, r1, r4
- 107 str r1, [r0]
- 108 fixnext:
- 109 cmp r2, r3
- 110 blo fixloop
- 111
- 112 relocate_done:
- 113
- 114 #ifdef __XSCALE__
- 115 /*
- 116 * On xscale, icache must be invalidated and write buffers drained,
- 117 * even with cache disabled - 4.2.7 of xscale core developer's manual
- 118 */
- 119 mcr p15, 0, r0, c7, c7, 0 /* invalidate icache */
- 120 mcr p15, 0, r0, c7, c10, 4 /* drain write buffer */
- 121 #endif
- 122
- 123 /* ARMv4- don't know bx lr but the assembler fails to see that */
- 124
- 125 #ifdef __ARM_ARCH_4__
- 126 mov pc, lr
- 127 #else
- 128 bx lr
- 129 #endif
- 130
- 131 ENDPROC(relocate_code)
复制代码
第81行,r1=__image_copy_start,也就是r1寄存器保存源地址,由表 23.3.15.1可知,__image_copy_start=0x400000。
第82行,r0=0x3ff3a000,这个地址就是uboot拷贝的目标首地址。r4=r0-r1=0x3ff3a000-0x400000=0x3fb3a000,因此r4保存偏移量。
第83行,如果在第82中,r0-r1等于0,说明r0和r1相等,也就是源地址和目的地址是一样的,那肯定就不需要拷贝了,执行relocate_done函数
第84行,r2=__image_copy_end,r2中保存拷贝之前的代码结束地址,由表 23.3.15.1可知,__image_copy_end =0x00474798。
第86行,标号copy_loop完成代码拷贝工作。从r1,也就是__image_copy_start开始,读取uboot代码保存到r10和r11中,一次只拷贝2个32位的数据。拷贝完成以后r1的值会更新,保存下一个要拷贝的数据地址。
第88行,将r10和r11的数据写到r0开始的地方,也就是目的地址。写完以后r0的值会更新,更新为下一个要写入的数据地址。
第89行,比较r1是否和r2相等,也就是检查是否拷贝完成,如果不相等的话说明没有拷贝完成,没有拷贝完成的话就跳转到copy_loop接着拷贝,直至拷贝完成。
接下来的第95行~129行是.rel.dyn段。.rel.dyn是.relocation.dynamic的简写,也就是动态重定位的意思。.rel.dyn段是存放.text段中需要重定位地址的集合。动态重定位就是uboot在运行时将自身拷贝到DRAM的另一个地方去继续运行,需要解决运行地址和链接地址不同导致的寻址问题。在具体的实现时涉及到链接工具和相对寻址的内容。如在文件arch/arm/config.mk中有如下代码:
- 105 # needed for relocation
- 106 LDFLAGS_u-boot += -pie
复制代码
从注释可以看到第106行的“-pie”选项用于重定位。编译链接uboot的时候会使用到“-pie”选项,如下图所示:
图 24.4.13链接命令
使用“-pie”选项以后会生成一个.rel.dyn段,.rel.dyn段中存放着需要重定位的地址。uboot在运行时relocate_code函数遍历.rel.dyn段,根据.rel.dyn段中存储的地址进行重定位,从而完成对所有需要重定位的地址的修改。下面我们继续来看relocate_code函数下面地址重定位的实现。
第95行,r2=__rel_dyn_start,也就是.rel.dyn段的起始地址,执行结果见图 24.4.14。
第96行,r3=__rel_dyn_end,也就是.rel.dyn段的结束地址,执行结果见图 24.4.14。
第98行,从.rel.dyn段起始地址开始,每次读取两个4字节的数据存放到r0和r1寄存器中。从下图的执行结果可以看出r0存放的是代码拷贝前的地址;r1存放的是需要重定位的标志。
图 24.4.14 第98行执行结果
第99行,r1中给的值与0xff进行与运算,其实就是取r1的低8位,从图 24.4.14可以看到r1值不变。
第100行,判断r1中的值是否等于R_ARM_RELATIVE。R_ARM_RELATIVE定义在include/elf.h文件中,值为0X17(23),笔者推测是判断地址是否需要进行重定位的标志符号。
第101行,如果r1不等于23的话就说明不需要重定位,执行函数fixnext,否则的话继续执行下面的代码进行重定位。
第104行,r0保存代码拷贝前的地址,r4保存着代码拷贝的偏移量,r0+r4就得到了代码拷贝后的地址。执行结果见图 24.4.15的r0。
第105,读取r0所指向的代码拷贝后的地址处的值到r1中,从下图可以看到该值是地址,而且还是代码拷贝前的地址。
图 24.4.15 第105行执行结果
第106行,将r1中代码拷贝前的地址加上代码拷贝的偏移量r4即可得到代码拷贝后的地址,执行结果见图 24.4.16。
第107行,将得到的代码拷贝后的地址值r1写入到代码拷贝后的r0所指地址处。
图 24.4.16 第107行执行结果
第109行,比较r2和r3,查看.rel.dyn段重定位是否完成。
第110行,如果r2和r3不相等,说明.rel.dyn重定位还未完成,因此跳到fixloop继续重定位.rel.dyn段。
从上面的分析我们大概的知道了代码动态重定位是怎么一回事了。首先uboot在运行时将自身拷贝到DRAM的另一个地方去继续运行,有一个地址偏移,如从0拷贝到100,地址偏移为100。代码拷贝完成后,所有指向代码拷贝前的地址都是无效的,所以需要进行地址重定位,由于是在uboot运行中进行的,所以是动态重定位。那么如何进行重定位呢?譬如说代码拷贝前地址32处存放着地址值54,代码拷贝后的重定位就是将需要重定位的地址32加上地址偏移100等于132,然后读取地址132处的地址值54,使其也加上地址偏移100等于154,最后更新地址132处的地址值54为154,即实现了地址动态重定位。
13.4.6relocate_vectors函数详解
函数relocate_vectors用于重定位向量表,此函数定义在文件arch/arm/lib/relocate.S中,函数源码如下:
示例代码relocate.S代码段
- 28 ENTRY(relocate_vectors)
- 29
- 30 #ifdef CONFIG_CPU_V7M
- 31 /*
- 32 * On ARMv7-M we only have to write the new vector address
- 33 * to VTOR register.
- 34 */
- 35 ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
- 36 ldr r1, =V7M_SCB_BASE
- 37 str r0, [r1, V7M_SCB_VTOR]
- 38 #else
- 39 #ifdef CONFIG_HAS_VBAR
- 40 /*
- 41 * If the ARM processor has the security extensions,
- 42 * use VBAR to relocate the exception vectors.
- 43 */
- 44 ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
- 45 mcr p15, 0, r0, c12, c0, 0 /* Set VBAR */
- 46 #else
- 47 /*
- 48 * Copy the relocated exception vectors to the
- 49 * correct address
- 50 * CP15 c1 V bit gives us the location of the vectors:
- 51 * 0x00000000 or 0xFFFF0000.
- 52 */
- 53 ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
- 54 mrc p15, 0, r2, c1, c0, 0 /* V bit (bit[13]) in CP15 c1 */
- 55 ands r2, r2, #(1 << 13)
- 56 ldreq r1, =0x00000000 /* If V=0 */
- 57 ldrne r1, =0xFFFF0000 /* If V=1 */
- 58 ldmia r0!, {r2-r8,r10}
- 59 stmia r1!, {r2-r8,r10}
- 60 ldmia r0!, {r2-r8,r10}
- 61 stmia r1!, {r2-r8,r10}
- 62 #endif
- 63 #endif
- 64 bx lr
- 65
- 66 ENDPROC(relocate_vectors)
复制代码
第30行,如果定义了CONFIG_CPU_V7M的话就执行第35~37行的代码。从注释可以看出这是用于Cortex-M系列的ARM,因此对于ZYNQ来说是无效的。
第39行,如果定义了CONFIG_HAS_VBAR的话就执行下面的语句,这个是向量表偏移,Cortex-A9是支持向量表偏移的。而且,在.config里面定义了CONFIG_HAS_VBAR,因此会执行这个分支。
第44行,r0=gd->relocaddr,也就是重定位后uboot的首地址0x3ff3a000,向量表从这个地址开始存放。
第45行,将r0的值写入到CP15的VBAR寄存器中,也就是将新的向量表首地址写入到寄存器VBAR中,设置向量表偏移。
13.4.7board_init_r函数详解
24.4.4小节讲解了board_init_f函数,在此函数里面会调用一系列的函数来初始化一些外设和gd的成员变量。但是board_init_f并没有初始化所有的外设,还需要做一些后续工作,这些后续工作就是由函数board_init_r来完成的,board_init_r函数定义在文件common/board_r.c中,代码如下:
示例代码 board_r.c代码段
- 869 void board_init_r(gd_t *new_gd, ulong dest_addr)
- 870 {
- 871 /*
- 872 * Set up the new global data pointer. So far only x86 does this
- 873 * here.
- 874 * TODO(<a href="mailto:sjg@chromium.org">sjg@chromium.org</a>): Consider doing this for all archs, or
- 875 * dropping the new_gd parameter.
- 876 */
- 877 #if CONFIG_IS_ENABLED(X86_64)
- 878 arch_setup_gd(new_gd);
- 879 #endif
- 880
- 881 #ifdef CONFIG_NEEDS_MANUAL_RELOC
- 882 int i;
- 883 #endif
- 884
- 885 #if !defined(CONFIG_X86) && !defined(CONFIG_ARM) && !defined(CONFIG_ARM64)
- 886 gd = new_gd;
- 887 #endif
- 888 gd->flags &= ~GD_FLG_LOG_READY;
- 889
- 890 #ifdef CONFIG_NEEDS_MANUAL_RELOC
- 891 for (i = 0; i < ARRAY_SIZE(init_sequence_r); i++)
- 892 init_sequence_r += gd->reloc_off;
- 893 #endif
- 894
- 895 if (initcall_run_list(init_sequence_r))
- 896 hang();
- 897
- 898 /* NOTREACHED - run_main_loop() does not return */
- 899 hang();
- 900 }
复制代码
第895行调用initcall_run_list函数来执行初始化序列init_sequence_r,init_sequence_r是一个函数集合,init_sequence_r同样定义在文件common/board_r.c中,由于init_sequence_f的内容比较长,里面有大量的条件编译代码,这里为了缩小篇幅,将条件编译部分删除掉了,去掉条件编译以后的init_sequence_r定义如下:
示例代码board_r.c代码段
- 1 static init_fnc_t init_sequence_r[] = {
- 2 initr_trace,
- 3 initr_reloc,
- 4 initr_caches,
- 5 initr_reloc_global_data,
- 6 initr_barrier,
- 7 initr_malloc,
- 8 log_init,
- 9 initr_bootstage, /* Needs malloc() but has its own timer */
- 10 initr_console_record,
- 11 bootstage_relocate,
- 12 initr_dm,
- 13 board_init,
- 14 set_cpu_clk_info, /* Setup clock information */
- 15 efi_memory_init,
- 16 stdio_init_tables,
- 17 initr_serial,
- 18 initr_announce,
- 19 INIT_FUNC_WATCHDOG_RESET
- 20 INIT_FUNC_WATCHDOG_RESET
- 21 power_init_board,
- 22 INIT_FUNC_WATCHDOG_RESET
- 23 initr_mmc,
- 24 initr_env,
- 25 INIT_FUNC_WATCHDOG_RESET
- 26 initr_secondary_cpu,
- 27 INIT_FUNC_WATCHDOG_RESET
- 28 stdio_add_devices,
- 29 initr_jumptable,
- 30 console_init_r, /* fully init console as a device */
- 31 console_announce_r,
- 32 show_board_info,
- 33 INIT_FUNC_WATCHDOG_RESET
- 34 interrupt_init,
- 35 initr_enable_interrupts,
- 36 initr_ethaddr,
- 37 board_late_init,
- 38 INIT_FUNC_WATCHDOG_RESET
- 39 initr_net,
- 40 run_main_loop,
- 41 };
复制代码
第2行,initr_trace函数,如果定义了宏 CONFIG_TRACE的话就会调用函数trace_init,初始化和调试跟踪有关的内容。
第3行,initr_reloc函数用于设置gd->flags,标记重定位完成。
第4行,initr_caches函数用于初始化cache,使能cache。
第5行,initr_reloc_global_data函数,初始化重定位后gd的一些成员变量。
第6行,initr_barrier函数,ZYNQ未用到。
第7行,initr_malloc函数,初始化malloc。
第8行,log_init函数,将所有的struct log_driver结构体都加入到gd->log_head 的循环链表中,并初始化gd->default_log_level。
第9行,initr_bootstage函数,初始化bootstage标志ID。
第10行,initr_console_record函数,初始化控制台相关的内容,ZYNQ未用到,空函数。
第11行,bootstage_relocate函数,复制所有的字符串。因为字符串可能指向program.text的旧位置,最终可能被破坏。
第12行,initr_dm函数,保存先前的重定位驱动程序模型并开始新的驱动模型。
第13行,board_init函数,板级初始化。
第14行,set_cpu_clk_info函数,建立时钟信息。
第15行,efi_memory_init函数,初始化话efi memory。
第16行,stdio_init_tables函数,stdio相关初始化。
第17行,initr_serial函数,初始化串口。
第18行,initr_announce函数,与调试有关,通知已经在RAM中运行。
第21行,power_init_board函数,初始化电源芯片,正点原子的ZYNQ开发板没有用到。
第23行,initr_mmc函数,初始化EMMC。
第24行,initr_env函数,初始化环境变量。
第26行,initr_secondary_cpu函数,初始化其他CPU核。
第28行,stdio_add_devices函数,各种输入输出设备的初始化。
第29行,initr_jumptable函数,初始化跳转表。
第30行,console_init_r函数,控制台初始化,初始化完成以后此函数会调用stdio_print_current_devices函数来打印出当前的控制台设备,如下图所示:
图 24.4.17控制台信息
第31行,console_announce_r
第32行,show_board_info函数,显示开发板信息,空函数。
第34行,interrupt_init函数,初始化中断。
第35行,initr_enable_interrupts函数,使能中断。
第36行,initr_ethaddr函数,初始化网络地址,也就是获取MAC地址。读取环境变量“ethaddr”的值。
第37行,board_late_init函数,板子后续初始化,此函数定义在文件board/xilinx/zynq/board.c中。读取启动模式并设置环境变量。
第39行,initr_net函数,初始化网络设备,函数调用顺序为:initr_net->eth_initialize,串口输出如下图所示信息:
图 24.4.18网络信息输出
第40行,run_main_loop行,主循环,处理命令。
13.4.8run_main_loop函数详解
uboot启动以后会进入3秒倒计时,如果在3秒倒计时结束之前按下回车键,那么就会进入uboot的命令模式,如果倒计时结束以后都没有按下回车键,那么就会自动启动Linux内核,这个功能就是由run_main_loop函数来完成的。run_main_loop函数定义在文件common/board_r.c中,函数内容如下:
示例代码 board_r.c文件代码段
- 655 static int run_main_loop(void)
- 656 {
- 657 #ifdef CONFIG_SANDBOX
- 658 sandbox_main_loop_init();
- 659 #endif
- 660 /* main_loop() can return to retry autoboot, if so just run it again */
- 661 for (;;)
- 662 main_loop();
- 663 return 0;
- 664 }
复制代码
第664行是个死循环“for(;;)”,死循环里面就一个main_loop函数,main_loop函数定义在文件common/main.c里面,代码如下:
示例代码main_loop函数
- 43 /* We come here after U-Boot is initialised and ready to process commands */
- 44 void main_loop(void)
- 45 {
- 46 const char *s;
- 47
- 48 bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop");
- 49
- 50 #ifdef CONFIG_VERSION_VARIABLE
- 51 env_set("ver", version_string); /* set version variable */
- 52 #endif /* CONFIG_VERSION_VARIABLE */
- 53
- 54 cli_init();
- 55
- 56 run_preboot_environment_command();
- 57
- 58 #if defined(CONFIG_UPDATE_TFTP)
- 59 update_tftp(0UL, NULL, NULL);
- 60 #endif /* CONFIG_UPDATE_TFTP */
- 61
- 62 s = bootdelay_process();
- 63 if (cli_process_fdt(&s))
- 64 cli_secure_boot_cmd(s);
- 65
- 66 autoboot_command(s);
- 67
- 68 cli_loop();
- 69 panic("No CLI available");
- 70 }
复制代码
第48行,调用bootstage_mark_name函数,打印出启动进度。
第50行,如果定义了宏CONFIG_VERSION_VARIABLE的话就会执行函数setenv,设置变量ver的值为version_string,也就是设置版本号环境变量。领航者的zynq没有定义该宏。
第54行,cli_init函数,与命令初始化有关,初始化hush shell相关的变量。
第56行,run_preboot_environment_command函数,获取环境变量perboot的内容,perboot是一些预启动命令,一般不使用这个环境变量。
第58行,如果定义了宏CONFIG_UPDATE_TFTP,就更新tftp,zynq没有定义改宏。
第62行,bootdelay_process函数,此函数会读取环境变量bootdelay和bootcmd的内容,然后将bootdelay的值赋值给全局变量stored_bootdelay,返回值为环境变量bootcmd的值。
第63行,函数cli_process_fdt读取设备树中的bootcmd,覆盖之前读取的bootcmd值。
第66行,autoboot_command函数,此函数就是检查倒计时是否结束?倒计时结束之前有没有被打断?此函数定义在文件common/autoboot.c中。
第68行,cli_loop函数是uboot的命令行处理函数,我们在uboot中输入各种命令,进行各种操作就是有cli_loop来处理的,此函数定义在文件common/cli.c中,具体内容我们就不看了
13.5bootz启动Linux内核过程
13.5.1images全局变量
不管是bootz还是bootm命令,在启动Linux内核的时候都会用到一个重要的全局变量:images,images在文件cmd/bootm.c中有如下定义:
示例代码 images全局变量
- 1 bootm_headers_t images; /* pointers to os/initrd/fdt images */
复制代码
images是bootm_headers_t类型的全局变量,bootm_headers_t是个boot头结构体,在文件include/image.h中的定义如下(删除了一些条件编译代码):
示例代码 bootm_headers_t结构体
- 332 typedef struct bootm_headers {
- 333 /*
- 334 * Legacy os image header, if it is a multi component image
- 335 * then boot_get_ramdisk() and get_fdt() will attempt to get
- 336 * data from second and third component accordingly.
- 337 */
- 338 image_header_t *legacy_hdr_os; /* image header pointer */
- 339 image_header_t legacy_hdr_os_copy; /* header copy */
- 340 ulong legacy_hdr_valid;
- ……
- 362 #ifndef USE_HOSTCC
- 363 image_info_t os; /* os image info */
- 364 ulong ep; /* entry point of OS */
- 365
- 366 ulong rd_start, rd_end;/* ramdisk start/end */
- 367
- 368 char *ft_addr; /* flat dev tree address */
- 369 ulong ft_len; /* length of flat device tree */
- 370
- 371 ulong initrd_start;
- 372 ulong initrd_end;
- 373 ulong cmdline_start;
- 374 ulong cmdline_end;
- 375 bd_t *kbd;
- 376 #endif
- 377
- 378 int verify; /* env_get("verify")[0] != 'n' */
- 379
- 380 #define BOOTM_STATE_START (0x00000001)
- 381 #define BOOTM_STATE_FINDOS (0x00000002)
- 382 #define BOOTM_STATE_FINDOTHER (0x00000004)
- 383 #define BOOTM_STATE_LOADOS (0x00000008)
- 384 #define BOOTM_STATE_RAMDISK (0x00000010)
- 385 #define BOOTM_STATE_FDT (0x00000020)
- 386 #define BOOTM_STATE_OS_CMDLINE (0x00000040)
- 387 #define BOOTM_STATE_OS_BD_T (0x00000080)
- 388 #define BOOTM_STATE_OS_PREP (0x00000100)
- 389 #define BOOTM_STATE_OS_FAKE_GO (0x00000200) /* 'Almost' run the OS */
- 390 #define BOOTM_STATE_OS_GO (0x00000400)
- 391 int state;
- 392
- 393 #ifdef CONFIG_LMB
- 394 struct lmb lmb; /* for memory mgmt */
- 395 #endif
- 396 } bootm_headers_t;
复制代码
第363行的os成员变量是image_info_t类型的,为系统镜像信息。
第380~390行这11个宏定义表示BOOT的不同阶段。
接下来看一下结构体image_info_t,也就是系统镜像信息结构体,此结构体在文件include/image.h中的定义如下:
示例代码 image_info_t结构体
- 320 typedef struct image_info {
- 321 ulong start, end; /* start/end of blob */
- 322 ulong image_start, image_len; /* start of image within blob, len of image */
- 323 ulong load; /* load addr for the image */
- 324 uint8_t comp, type, os; /* compression, type of image, os type */
- 325 uint8_t arch; /* CPU architecture */
- 326 } image_info_t;
复制代码
全局变量images会在bootz命令的执行中频繁使用到,相当于Linux内核启动的“灵魂”。
13.5.2do_bootz函数
bootz命令的执行函数为do_bootz,在文件cmd/bootz.c中有如下定义:
示例代码 do_bootz函数
- 61 int do_bootz(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
- 62 {
- 63 int ret;
- 64
- 65 /* Consume 'bootz' */
- 66 argc--; argv++;
- 67
- 68 if (bootz_start(cmdtp, flag, argc, argv, &images))
- 69 return 1;
- 70
- 71 /*
- 72 * We are doing the BOOTM_STATE_LOADOS state ourselves, so must
- 73 * disable interrupts ourselves
- 74 */
- 75 bootm_disable_interrupts();
- 76
- 77 images.os.os = IH_OS_LINUX;
- 78 ret = do_bootm_states(cmdtp, flag, argc, argv,
- 79 #ifdef CONFIG_SYS_BOOT_RAMDISK_HIGH
- 80 BOOTM_STATE_RAMDISK |
- 81 #endif
- 82 BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO |
- 83 BOOTM_STATE_OS_GO,
- 84 &images, 1);
- 85
- 86 return ret;
- 87 }
复制代码
第68行,调用bootz_start函数,bootz_start函数执行过程参考下一小节。
第75行,调用函数bootm_disable_interrupts关闭中断。
第77行,设置images.os.os为IH_OS_LINUX,也就是设置系统镜像为Linux,表示我们要启动的是Linux系统。后面会用到images.os.os来选择具体的启动函数。
第78行,调用函数do_bootm_states来执行不同的BOOT阶段,这里要执行的BOOT阶段有:BOOTM_STATE_OS_PREP 、BOOTM_STATE_OS_FAKE_GO和BOOTM_STATE_OS_GO。
13.5.3bootz_start函数
bootz_start函数用于支持zImage镜像的启动,定义在文件cmd/bootz.c中,函数内容如下:
示例代码 bootz_start函数
- 25 static int bootz_start(cmd_tbl_t *cmdtp, int flag, int argc,
- 26 char * const argv[], bootm_headers_t *images)
- 27 {
- 28 int ret;
- 29 ulong zi_start, zi_end;
- 30
- 31 ret = do_bootm_states(cmdtp, flag, argc, argv, BOOTM_STATE_START,
- 32 images, 1);
- 33
- 34 /* Setup Linux kernel zImage entry point */
- 35 if (!argc) {
- 36 images->ep = load_addr;
- 37 debug("* kernel: default image load address = 0x%08lx\n",
- 38 load_addr);
- 39 } else {
- 40 images->ep = simple_strtoul(argv[0], NULL, 16);
- 41 debug("* kernel: cmdline image address = 0x%08lx\n",
- 42 images->ep);
- 43 }
- 44
- 45 ret = bootz_setup(images->ep, &zi_start, &zi_end);
- 46 if (ret != 0)
- 47 return 1;
- 48
- 49 lmb_reserve(&images->lmb, images->ep, zi_end - zi_start);
- 50
- 51 /*
- 52 * Handle the BOOTM_STATE_FINDOTHER state ourselves as we do not
- 53 * have a header that provide this informaiton.
- 54 */
- 55 if (bootm_find_images(flag, argc, argv))
- 56 return 1;
- 57
- 58 return 0;
- 59 }
复制代码
第31行,调用函数do_bootm_states,执行BOOTM_STATE_START阶段。
第36行,设置images的ep成员变量,也就是系统镜像的入口点,使用bootz命令启动系统的时候就会设置系统在DRAM中的存储位置,这个存储位置就是系统镜像的入口点,因此images->ep=0X8000。
第45行,调用bootz_setup函数,此函数会判断当前的系统镜像文件是否为Linux的镜像文件,并且会打印出镜像相关信息,bootz_setup函数稍后会讲解。
第55行,调用函数bootm_find_images查找ramdisk和设备树(dtb)文件,但是我们没有用到ramdisk,因此此函数在这里仅仅用于查找设备树(dtb)文件,此函数稍后也会讲解。
先来看一下bootz_setup函数,此函数定义在文件arch/arm/lib/zimage.c中,函数内容如下:
示例代码 bootz_setup函数
- 12 #define LINUX_ARM_ZIMAGE_MAGIC 0x016f2818
- 13
- 14 struct arm_z_header {
- 15 uint32_t code[9];
- 16 uint32_t zi_magic;
- 17 uint32_t zi_start;
- 18 uint32_t zi_end;
- 19 } __attribute__ ((__packed__));
- 20
- 21 int bootz_setup(ulong image, ulong *start, ulong *end)
- 22 {
- 23 struct arm_z_header *zi = (struct arm_z_header *)image;
- 24
- 25 if (zi->zi_magic != LINUX_ARM_ZIMAGE_MAGIC) {
- 26 #ifndef CONFIG_SPL_FRAMEWORK
- 27 puts("Bad Linux ARM zImage magic!\n");
- 28 #endif
- 29 return 1;
- 30 }
- 31
- 32 *start = zi->zi_start;
- 33 *end = zi->zi_end;
- 34 #ifndef CONFIG_SPL_FRAMEWORK
- 35 printf("Kernel image @ %#08lx [ %#08lx - %#08lx ]\n",
- 36 image, *start, *end);
- 37 #endif
- 38
- 39 return 0;
- 40 }
复制代码
第12行,宏LINUX_ARM_ZIMAGE_MAGIC是ARM Linux系统魔术数。
第23行,从传递进来的参数image(也就是系统镜像首地址)中获取zimage头。zImage头结构体为arm_z_header。
第25~30行,判断image是否为ARM的Linux系统镜像,如果不是的话就直接返回,并且打印出“Bad Linux ARM zImage magic!”,比如我们输入一个错误的启动命令:
因为我们并没有在0X8000处存放Linux镜像文件(zImage),因此上面的命令肯定会执行出错的,结果如下图所示:
图 24.5.1启动出错
第32、33行初始化函数bootz_setup的参数start和end。
第35行,打印启动信息,如果Linux系统镜像正常的话就会输出下图所示的信息:
图 24.5.2 Linux镜像信息
接下来看一下函数bootm_find_images。boot_find_images函数将尝试加载可用的ramdisk,设备树以及带有特殊标记的“可加载”映像,此函数定义在文件common/bootm.c中,函数内容如下:
示例代码 bootm_find_images函数
- 227 int bootm_find_images(int flag, int argc, char * const argv[])
- 228 {
- 229 int ret;
- 230
- 231 /* find ramdisk */
- 232 ret = boot_get_ramdisk(argc, argv, &images, IH_INITRD_ARCH,
- 233 &images.rd_start, &images.rd_end);
- 234 if (ret) {
- 235 puts("Ramdisk image is corrupt or invalid\n");
- 236 return 1;
- 237 }
- 238
- 239 #if IMAGE_ENABLE_OF_LIBFDT
- 240 /* find flattened device tree */
- 241 ret = boot_get_fdt(flag, argc, argv, IH_ARCH_DEFAULT, &images,
- 242 &images.ft_addr, &images.ft_len);
- 243 if (ret) {
- 244 puts("Could not find a valid device tree\n");
- 245 return 1;
- 246 }
- 247 set_working_fdt_addr((ulong)images.ft_addr);
- 248 #endif
- 249
- 250 #if IMAGE_ENABLE_FIT
- 251 #if defined(CONFIG_FPGA)
- 252 /* find bitstreams */
- 253 ret = boot_get_fpga(argc, argv, &images, IH_ARCH_DEFAULT,
- 254 NULL, NULL);
- 255 if (ret) {
- 256 printf("FPGA image is corrupted or invalid\n");
- 257 return 1;
- 258 }
- 259 #endif
- ……
- 270 return 0;
- 271 }
复制代码
第232~237行是跟查找ramdisk,但是我们没有用到ramdisk,因此这部分代码不用管。
第239~248行是查找设备树(dtb)文件,找到以后就将设备树的起始地址和长度分别写到images的ft_addr和ft_len成员变量中。我们使用bootz启动Linux的时候已经指明了设备树在DRAM中的存储地址,长度根据具体的设备树文件而定。
第250~259行是查找FPGA的bitstreams文件。
bootz_start函数就讲解到这里,bootz_start主要用于初始化images的相关成员变量。
13.5.4do_bootm_states函数
do_bootz最后调用的就是函数do_bootm_states,而且在bootz_start中也调用了do_bootm_states函数,看来do_bootm_states函数还是个香饽饽。此函数定义在文件common/bootm.c中,函数do_bootm_states根据不同的BOOT状态执行不同的代码段,通过如下代码来判断BOOT状态:
在do_bootz函数中会用到BOOTM_STATE_OS_PREP、BOOTM_STATE_OS_FAKE_GO和BOOTM_STATE_OS_GO这三个BOOT状态,bootz_start函数中会用到BOOTM_STATE_START状态。由于此函数代码较长,为了精简代码,方便分析,因此我们将函数do_bootm_states进行精简,只留下下面这4个BOOT状态对应的处理代码:
- BOOTM_STATE_OS_PREP
- BOOTM_STATE_OS_FAKE_GO
- BOOTM_STATE_OS_GO
- BOOTM_STATE_START
复制代码
精简以后的do_bootm_states函数如下所示:
示例代码 do_bootm_states函数
- 603 int do_bootm_states(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[],
- 604 int states, bootm_headers_t *images, int boot_progress)
- 605 {
- 606 boot_os_fn *boot_fn;
- 607 ulong iflag = 0;
- 608 int ret = 0, need_boot_fn;
- 609
- 610 images->state |= states;
- 611
- 612 /*
- 613 * Work through the states and see how far we get. We stop on
- 614 * any error.
- 615 */
- 616 if (states & BOOTM_STATE_START)
- 617 ret = bootm_start(cmdtp, flag, argc, argv);
- ……
- 661 /* From now on, we need the OS boot function */
- 662 if (ret)
- 663 return ret;
- 664 boot_fn = bootm_os_get_boot_func(images->os.os);
- ……
- 683 if (!ret && (states & BOOTM_STATE_OS_PREP)) {
- 684 #if defined(CONFIG_SILENT_CONSOLE) && !defined(CONFIG_SILENT_U_BOOT_ONLY)
- 685 if (images->os.os == IH_OS_LINUX)
- 686 fixup_silent_linux();
- 687 #endif
- 688 ret = boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images);
- 689 }
- 690
- 691 #ifdef CONFIG_TRACE
- 692 /* Pretend to run the OS, then run a user command */
- 693 if (!ret && (states & BOOTM_STATE_OS_FAKE_GO)) {
- 694 char *cmd_list = env_get("fakegocmd");
- 695
- 696 ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_FAKE_GO,
- 697 images, boot_fn);
- 698 if (!ret && cmd_list)
- 699 ret = run_command_list(cmd_list, -1, flag);
- 700 }
- 701 #endif
- 703 /* Check for unsupported subcommand. */
- 704 if (ret) {
- 705 puts("subcommand not supported\n");
- 706 return ret;
- 707 }
- 708
- 709 /* Now run the OS! We hope this doesn't return */
- 710 if (!ret && (states & BOOTM_STATE_OS_GO))
- 711 ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_GO,
- 712 images, boot_fn);
- ……
- 724 return ret;
- 725 }
复制代码
第616~617行,处理BOOTM_STATE_START阶段,bootz_start会执行这一段代码,这里调用函数bootm_start,用来清零images并重新设置images的state和verify。
第664行非常重要。通过函数bootm_os_get_boot_func来查找系统启动函数。参数images->os.os就是系统类型,根据这个系统类型来选择对应的启动函数,在do_bootz中设置images.os.os= IH_OS_LINUX。函数返回值就是找到的系统启动函数,这里找到的Linux系统启动函数为do_bootm_linux,关于此函数查找系统启动函数的过程请参考24.5.5小节。因此boot_fn=do_bootm_linux,后面执行boot_fn函数的地方实际上是执行的do_bootm_linux函数。
第683行,处理BOOTM_STATE_OS_PREP状态,调用函数do_bootm_linux。do_bootm_linux函数内部也是调用boot_prep_linux来完成具体的处理过程。boot_prep_linux主要用于处理环境变量bootargs,bootargs保存着传递给Linux kernel的参数。
第691~701行是处理BOOTM_STATE_OS_FAKE_GO状态的,但是要我们没用使能TRACE功能,因此宏CONFIG_TRACE也就没有定义,所以这段程序不会编译。
第711行,调用函数boot_selected_os启动Linux内核,此函数第4个参数为Linux系统镜像头,第5个参数就是Linux系统启动函数do_bootm_linux。boot_selected_os函数定义在文件common/bootm_os.c中,函数内容如下:
示例代码 boot_selected_os函数
- 477 int boot_selected_os(int argc, char * const argv[], int state,
- 478 bootm_headers_t *images, boot_os_fn *boot_fn)
- 479 {
- 480 arch_preboot_os();
- 481 boot_fn(state, argc, argv, images);
- ……
- 491 return BOOTM_ERR_RESET;
- 492 }
复制代码
第481行调用boot_fn函数,也就是do_bootm_linux函数来启动Linux内核。
13.5.5bootm_os_get_boot_func函数
do_bootm_states会调用bootm_os_get_boot_func来查找对应系统的启动函数,此函数定义在文件common/bootm_os.c中,函数内容如下:
示例代码 bootm_os_get_boot_func函数
- 494 boot_os_fn *bootm_os_get_boot_func(int os)
- 495 {
- 496 #ifdef CONFIG_NEEDS_MANUAL_RELOC
- 497 static bool relocated;
- 498
- 499 if (!relocated) {
- 500 int i;
- 501
- 502 /* relocate boot function table */
- 503 for (i = 0; i < ARRAY_SIZE(boot_os); i++)
- <span style="font-style: italic;"><span style="font-style: normal;">504 if (boot_os != NULL)
- 505 boot_os</span><span style="font-style: normal;"> += gd->reloc_off;
- 506
- 507 relocated = true;
- 508 }
- 509 #endif
- 510 return boot_os[os];
- 511 }</span></span>
复制代码
第496-509行是条件编译,在zynq的uboot中没有用到,因此这段代码无效,只有第510行有效。第510行中的boot_os是个数组,这个数组里面存放着不同的系统对应的启动函数。boot_os也定义在文件common/bootm_os.c中,如下所示:
示例代码 boot_os数组
- 436 static boot_os_fn *boot_os[] = {
- 437 [IH_OS_U_BOOT] = do_bootm_standalone,
- 438 #ifdef CONFIG_BOOTM_LINUX
- 439 [IH_OS_LINUX] = do_bootm_linux,
- 440 #endif
- ……
- 466 #ifdef CONFIG_BOOTM_OPENRTOS
- 467 [IH_OS_OPENRTOS] = do_bootm_openrtos,
- 468 #endif
- 469 };
复制代码
第439行就是Linux系统对应的启动函数:do_bootm_linux。
13.5.6do_bootm_linux函数
经过前面的分析,我们知道了do_bootm_linux就是最终启动Linux内核的函数,此函数定义在文件arch/arm/lib/bootm.c,函数内容如下:
示例代码 do_bootm_linux函数
- 402 int do_bootm_linux(int flag, int argc, char * const argv[],
- 403 bootm_headers_t *images)
- 404 {
- 405 /* No need for those on ARM */
- 406 if (flag & BOOTM_STATE_OS_BD_T || flag & BOOTM_STATE_OS_CMDLINE)
- 407 return -1;
- 408
- 409 if (flag & BOOTM_STATE_OS_PREP) {
- 410 boot_prep_linux(images);
- 411 return 0;
- 412 }
- 413
- 414 if (flag & (BOOTM_STATE_OS_GO | BOOTM_STATE_OS_FAKE_GO)) {
- 415 boot_jump_linux(images, flag);
- 416 return 0;
- 417 }
- 418
- 419 boot_prep_linux(images);
- 420 boot_jump_linux(images, flag);
- 421 return 0;
- 422 }
复制代码
第414行,如果参数flag等于BOOTM_STATE_OS_GO或者BOOTM_STATE_OS_FAKE_GO的话就执行boot_jump_linux函数。boot_selected_os函数在调用do_bootm_linux的时候会将flag设置为BOOTM_STATE_OS_GO。
第415行,执行函数boot_jump_linux,此函数定义在文件arch/arm/lib/bootm.c中,函数内容如下:
示例代码 boot_jump_linux函数
- 311 /* Subcommand: GO */
- 312 static void boot_jump_linux(bootm_headers_t *images, int flag)
- 313 {
- 314 #ifdef CONFIG_ARM64
- ……
- 352 #else
- 353 unsigned long machid = gd->bd->bi_arch_number;
- 354 char *s;
- 355 void (*kernel_entry)(int zero, int arch, uint params);
- 356 unsigned long r2;
- 357 int fake = (flag & BOOTM_STATE_OS_FAKE_GO);
- 358
- 359 kernel_entry = (void (*)(int, int, uint))images->ep;
- 360 #ifdef CONFIG_CPU_V7M
- 361 ulong addr = (ulong)kernel_entry | 1;
- 362 kernel_entry = (void *)addr;
- 363 #endif
- 364 s = env_get("machid");
- 365 if (s) {
- 366 if (strict_strtoul(s, 16, &machid) < 0) {
- 367 debug("strict_strtoul failed!\n");
- 368 return;
- 369 }
- 370 printf("Using machid 0x%lx from environment\n", machid);
- 371 }
- 372
- 373 debug("## Transferring control to Linux (at address %08lx)" \
- 374 "...\n", (ulong) kernel_entry);
- 375 bootstage_mark(BOOTSTAGE_ID_RUN_OS);
- 376 announce_and_cleanup(fake);
- 377
- 378 if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len)
- 379 r2 = (unsigned long)images->ft_addr;
- 380 else
- 381 r2 = gd->bd->bi_boot_params;
- ……
- 391 kernel_entry(0, machid, r2);
- 392 }
- 393 #endif
- 394 }
复制代码
第314~351行是64位ARM芯片对应的代码,Cortex-A9是32位芯片,因此用不到。
第353行,变量machid保存机器ID,如果不使用设备树的话这个机器ID会被传递给Linux内核,Linux内核会在自己的机器ID列表里面查找是否存在与uboot传递进来的machid匹配的项目,如果存在就表明Linux内核支持这个机器,那么Linux就会启动。如果使用设备树的话这个machid就无效了,设备树有一个“兼容性”属性,Linux内核会比较“兼容性”属性的值(字符串)来查看是否支持这个机器。
第355行,函数kernel_entry,看名字“内核_进入”,说明此函数是进入Linux内核的,也就是最终的大boos。此函数有三个参数:zero,arch,params,第一个参数zero同样为0;第二个参数为机器ID;第三个参数ATAGS或者设备树(DTB)首地址,ATAGS是传统的方法,用于传递一些命令行信息啥的,如果使用设备树的话就要传递设备树(DTB)。
第359行,获取kernel_entry函数,函数kernel_entry并不是uboot定义的,而是Linux内核定义的,Linux内核镜像文件的第一行代码就是函数kernel_entry,而images->ep保存着Linux内核镜像的起始地址,而起始地址保存的不正是Linux内核第一行代码么。
第376行,调用函数announce_and_cleanup来打印一些信息并做一些清理工作,此函数定义在文件arch/arm/lib/bootm.c中,函数内容如下:
示例代码 announce_and_cleanup函数
- 79 static void announce_and_cleanup(int fake)
- 80 {
- 81 printf("\nStarting kernel ...%s\n\n", fake ?
- 82 "(fake run for tracing)" : "");
- 83 bootstage_mark_name(BOOTSTAGE_ID_BOOTM_HANDOFF, "start_kernel");
- ……
- 95 board_quiesce_devices();
- 96
- 97 /*
- 98 * Call remove function of all devices with a removal flag set.
- 99 * This may be useful for last-stage operations, like cancelling
- 100 * of DMA operation or releasing device internal buffers.
- 101 */
- 102 dm_remove_devices_flags(DM_REMOVE_ACTIVE_ALL);
- 103
- 104 cleanup_before_linux();
- 105 }
复制代码
第81行,在启动Linux之前输出“Starting kernel ...”信息,如下图所示:
图 24.5.3启动内核提示信息
第104行调用cleanup_before_linux函数做一些清理工作。
继续回到示例代码boot_jump_linux函数,第378~381行是设置寄存器r2的值?为什么要设置r2的值呢?Linux内核一开始是汇编代码,因此函数kernel_entry就是个汇编函数。向汇编函数传递参数要使用r0、r1和r2(参数数量不超过3个的时候),所以r2寄存器就是函数kernel_entry的第三个参数。
第379行,如果使用设备树的话,r2应该是设备树的起始地址,而设备树地址保存在images的ftd_addr成员变量中。
第381行,如果不使用设备树的话,r2应该是uboot传递给Linux的参数起始地址,也就是环境变量bootargs的值,
第391行,调用kernel_entry函数进入Linux内核,此行将一去不复返,uboot的使命也就完成了,它可以安息了!
总结一下bootz命令的执行过程,如下图所示:
图 24.5.4 bootz命令执行过程
到这里uboot的启动流程我们就讲解完成了,加上uboot顶层Makefile的分析,洋洋洒洒近100页,还是不少的。这也仅仅是uboot启动流程分析,当缕清了uboot的启动流程以后,后面移植uboot就会轻松很多。其实在工作中我们基本不需要这么详细的去了解uboot,半导体厂商提供给我们的uboot一般是可以直接用的,只要能跑起来,可以使用就可以了。但是作为学习,我们是必须了解一下uboot的启动流程,否则如果在工作中遇到问题我们连解决的方法都没有,都不知道该从哪里看起。但是呢,如果第一次就想弄懂uboot的整个启动流程还是有点困难的,所以如果没有看懂的话,不要紧!不要气馁,大多数人第一次看uboot启动流程基本都有各种各样的问题。
|