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 Set
tings”或者“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
- 8 #include
- 9 #include
- 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 i***
- 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, '