搜索
bottom↓
回复: 1

【正点原子FPGA连载】第十三章U-Boot启动流程详解--摘自【正点原子】领航者 ZYNQ 之linux驱动开发指南

[复制链接]

出0入234汤圆

发表于 2020-8-31 10:32:10 | 显示全部楼层 |阅读模式
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 点击加入:
QQ群头像.png          
5)关注正点原子公众号,获取最新资料


100846rel79a9p4uelap24.jpg

100846f1ce1fg14zbg0va4.png

第十三章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桌面中,如下图所示:
第13章564.png

图 24.1.1 zynq_petalinux.sdk目录

现在我们打开Ubuntu中的SDK软件,Workspace选择刚才拷贝的zynq_petalinux.sdk目录,如下图所示:
第13章703.png

图 24.1.2选择Workspace

点击“OK”,进入SDK工程界面,如下图所示:
第13章793.png

图 24.1.3 SDK工程界面

界面与Vivado中的SDK一样,操作也是一样的。现在我们创建一个名为“uboot”的空应用工程。工程创建后的界面如下:
第13章916.png

图 24.1.4“uboot”应用工程

可以看到src目录为空,现在我们往src目录添加uboot源码。此处我们使用第二十三章用到的uboot源码。拷贝uboot源码到上图中的src目录下。添加完uboot源码,然后在src目录下按键盘上的F5键刷新目录,刷新后的的src目录如下图所示:
第13章1133.png

图 24.1.5 src目录

可以看到,uboot源码已经添加进来了。现在我们来配置uboot。在SDK工具中单击下图中箭头所指的位置,打开bash shell。
第13章1260.png

图 24.1.6打开bash shell

弹出如下图所示的bash shell界面,输入“pwd”命令,得到当前的工作路径,如下图所示:
第13章1374.png

图 24.1.7当前的工作路径

可以看到当前路径处于zynq_petalinux.sdk目录下,我们切换路径到src目录下,输入命令“cd uboot/src”切换到src目录下,如下图所示:
第13章1516.png

图 24.1.8切换到src目录下

然后输入命令“make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zynq_altk_defconfig”配置uboot。配置完成后,鼠标右键单击应用工程uboot,在弹出的菜单中选择“C/C++ Build Settings”或者“Properties”,对应的快捷键是“Alt+Enter”,如下图所示:
第13章1757.png

图 24.1.9选择“C/C++ Build Settings”或者“Properties”

在弹出的属性界面中,单击“C/C++ Build”,如下图所示:
第13章1882.png

图 24.1.10配置“C/C++ Build”

取消勾选“Generate Makefiles automatically”,然后修改“Build directory:”,如上图所示。这些修改完成后,添加环境变量。在属性界面的左栏选择“Environment”,添加环境变量CROSS_COMPILE为“arm-linux-gnueabihf-”,如下图所示:
第13章2109.png

图 24.1.11添加环境变量CROSS_COMPILE

添加完环境变量CROSS_COMPILE后,单击上图箭头5所致的“OK”,然后单击箭头6所致的“Apply”,最后单击属性界面的“OK”退出该界面。然后编译整个应用工程(可使用快捷键Ctrl+B),从下图可以看出,编译没有出错:
第13章2298.png

图 24.1.12编译没有出错

现在调试uboot。将领航者开发板的启动模式设置为“JTAG”启动,连接JTAG、串口和电源,然后开发板上电。打开串口软件如SecureCRT或Putty,设置好领航者开发板所使用的串口并打开。
在Vmware软件的菜单栏点击“虚拟机(M)”菜单,在弹出的子菜单中移动到“可移动设备(D)”:会弹出相应的移动设备,里面带有“Digilent USB”的是JTAG的USB接口,连接该USB接口,如下图所示:
第13章2563.png

图 24.1.13 在Vmware中连接JTAG的USB接口到虚拟机内

在应用工程uboot目录上鼠标右键单击,在弹出的菜单中选择“Debug As”→“Debug Configurations”,如下图所示:

第13章2715.png

图 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所示。
第13章3011.png

图 24.1.15配置“Target Setup”栏

第13章3083.png

图 24.1.16配置“Application”栏

依次单击Debug Configurations界面右下方的“Apply”和“Debug”按钮,如下图所示:
第13章3209.png

图 24.1.17点击调试

会弹出提示打开调试界面的信息,点击“Yes”,如下图所示:
第13章3298.png

图 24.1.18提示打开调试界面

调试界面如图 24.1.19所示,方框1是常用的调试命令,方框2的Debug窗口中的方框3处显示的是当前PC指针的值和当前运行的命令对应的源码文件,方框4是源码文件,可以看到箭头指向_start: 标号的ARM_VECTORS处,方框5是常用的信息框,可以显示变量Variables、断点Breakpoint、寄存器Register信息等内容,方框6显示当前打开的文件所包括的头文件、包含的函数等信息,右下角的方框主要是显示内存Memory的内容。
第13章3610.png

图 24.1.19调试界面

对于方框1我们只要知道单步调试按键盘上的F5,遇到函数时不想进入就按F6,进入函数想返回时按F7即可。我们具体的来看下方框5。方框5中的Variables显示变量内容,尤其当进入函数时非常有用。当然了更有信息价值的是寄存器Register栏,所以我们看下寄存器Register栏的信息有哪些。
第13章3816.png

图 24.1.20寄存器Register栏

从上图可以看出,Register栏中包括Name、Hex、Decimal、Description和Mnemonic。我们常用的是Name用来显示寄存器名,Hex显示寄存器的16进制值和Desciption,描述寄存器内容。我们以cpsr寄存器为例。展开cpsr,如下图所示:
第13章4021.png

图 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文件,如下图所示:
第13章4626.png

图 24.3.1链接脚本

打开u-boot.lds,内容如下:
示例代码 u-boot.lds文件代码
  1. 1  OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
  2. 2  OUTPUT_ARCH(arm)
  3. 3  ENTRY(_start)
  4. 4  SECTIONS
  5. 5  {
  6. 6   . = 0x00000000;
  7. 7   . = ALIGN(4);
  8. 8   .text :
  9. 9   {
  10. 10   *(.__image_copy_start)
  11. 11   *(.vectors)
  12. 12   arch/arm/cpu/armv7/start.o (.text*)
  13. 13   *(.text*)
  14. 14  }
  15. 15  . = ALIGN(4);
  16. 16  .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }
  17. 17  . = ALIGN(4);
  18. 18  .data : {
  19. 19   *(.data*)
  20. 20  }
  21. 21  . = ALIGN(4);
  22. 22  . = .;
  23. 23  . = ALIGN(4);
  24. 24  .u_boot_list : {
  25. 25   KEEP(*(SORT(.u_boot_list*)));
  26. 26  }
  27. 27  . = ALIGN(4);
  28. 28  .__efi_runtime_start : {
  29. 29   *(.__efi_runtime_start)
  30. 30  }
  31. 31  .efi_runtime : {
  32. 32   *(efi_runtime_text)
  33. 33   *(efi_runtime_data)
  34. 34  }
  35. 35  .__efi_runtime_stop : {
  36. 36   *(.__efi_runtime_stop)
  37. 37  }
  38. 38  .efi_runtime_rel_start :
  39. 39  {
  40. 40   *(.__efi_runtime_rel_start)
  41. 41  }
  42. 42  .efi_runtime_rel : {
  43. 43   *(.relefi_runtime_text)
  44. 44   *(.relefi_runtime_data)
  45. 45  }
  46. 46  .efi_runtime_rel_stop :
  47. 47  {
  48. 48   *(.__efi_runtime_rel_stop)
  49. 49  }
  50. 50  . = ALIGN(4);
  51. 51  .image_copy_end :
  52. 52  {
  53. 53   *(.__image_copy_end)
  54. 54  }
  55. 55  .rel_dyn_start :
  56. 56  {
  57. 57   *(.__rel_dyn_start)
  58. 58  }
  59. 59  .rel.dyn : {
  60. 60   *(.rel*)
  61. 61  }
  62. 62  .rel_dyn_end :
  63. 63  {
  64. 64   *(.__rel_dyn_end)
  65. 65  }
  66. 66  .end :
  67. 67  {
  68. 68   *(.__end)
  69. 69  }
  70. 70  _image_binary_end = .;
  71. 71  .bss_start __rel_dyn_start (OVERLAY) : {
  72. 72   KEEP(*(.__bss_start));
  73. 73   __bss_base = .;
  74. 74  }
  75. 75  .bss __bss_base (OVERLAY) : {
  76. 76   *(.bss*)
  77. 77    . = ALIGN(4);
  78. 78    __bss_limit = .;
  79. 79  }
  80. 80  .bss_end __bss_limit (OVERLAY) : {
  81. 81   KEEP(*(.__bss_end));
  82. 82  }
  83. 83  /DISCARD/ : { *(.dynsym) }
  84. 84  /DISCARD/ : { *(.dynbss*) }
  85. 85  /DISCARD/ : { *(.dynstr*) }
  86. 86  /DISCARD/ : { *(.dynamic*) }
  87. 87  /DISCARD/ : { *(.plt*) }
  88. 88  /DISCARD/ : { *(.interp*) }
  89. 89  /DISCARD/ : { *(.gnu*) }
  90. 90  /DISCARD/ : { *(.ARM.exidx*) }
  91. 91  /DISCARD/ : { *(.gnu.linkonce.armexidx.*) }
  92. 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”:
  1. grep -nR "__image_copy_start"
复制代码

搜索结果如下图所示:
第13章6937.png
图 24.3.2查找结果
打开u-boot.map,找到如下图所示位置:
第13章7019.png

图 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代码段
  1. 18  /*
  2. 19   * A macro to allow insertion of an ARM exception vector either
  3. 20   * for the non-boot0 case or by a boot0-header.
  4. 21   */
  5. 22          .macro ARM_VECTORS
  6. 23      b   reset
  7. 24      ldr pc, _undefined_instruction
  8. 25      ldr pc, _software_interrupt
  9. 26      ldr pc, _prefetch_abort
  10. 27      ldr pc, _data_abort
  11. 28      ldr pc, _not_used
  12. 29      ldr pc, _irq
  13. 30      ldr pc, _fiq
  14. 31      .endm
  15. 32  
  16. 33  
  17. 34  /*
  18. 35   *************************************************************************
  19. 36   *
  20. 37   * Symbol _start is referenced elsewhere, so make it global
  21. 38   *
  22. 39   *************************************************************************
  23. 40   */
  24. 41  
  25. 42  .globl _start
  26. 43  
  27. 44  /*
  28. 45   *************************************************************************
  29. 46   *
  30. 47   * Vectors have their own section so linker script can map them easily
  31. 48   *
  32. 49   *************************************************************************
  33. 50   */
  34. 51  
  35. 52      .section ".vectors", "ax"
  36. 53  
  37. 54  #if defined(CONFIG_ENABLE_ARM_SOC_BOOT0_HOOK)
  38. ……
  39. 69  #else
  40. 70  
  41. 71  /*
  42. 72   *************************************************************************
  43. 73   *
  44. 74   * Exception vectors as described in ARM reference manuals
  45. 75   *
  46. 76   * Uses indirect branch to allow reaching handlers anywhere in memory.
  47. 77   *
  48. 78   *************************************************************************
  49. 79   */
  50. 80  
  51. 81  _start:
  52. 82  #ifdef CONFIG_SYS_DV_NOR_BOOT_CFG
  53. 83      .word   CONFIG_SYS_DV_NOR_BOOT_CFG
  54. 84  #endif
  55. 85      ARM_VECTORS
  56. 86  #endif /* !defined(CONFIG_ENABLE_ARM_SOC_BOOT0_HOOK) */
复制代码

第54行的宏CONFIG_ENABLE_ARM_SOC_BOOT0_HOOK,从下图可以看到其未定义。
第13章9882.png

图 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代码段
  1. 22  /*************************************************************************
  2. 23   *
  3. 24   * Startup Code (reset vector)
  4. 25   *
  5. 26   * Do important init only if we don't start from memory!
  6. 27   * Setup memory and board specific bits prior to relocation.
  7. 28   * Relocate armboot to ram. Setup stack.
  8. 29   *
  9. 30   *************************************************************************/
  10. 31  
  11. 32      .globl  reset
  12. 33      .globl  save_boot_params_ret
  13. 34      .type   save_boot_params_ret,%function
  14. 35  #ifdef CONFIG_ARMV7_LPAE
  15. 36      .global switch_to_hypervisor_ret
  16. 37  #endif
  17. 38  
  18. 39  reset:
  19. 40      /* Allow the board to save important registers */
  20. 41      b   save_boot_params
复制代码

第39行就是标号reset。注:为了方便,后面对标号和ENTRY统一称为函数。
第41行从标号reset跳转到了save_boot_params函数,而save_boot_params函数同样定义在start.S中,定义如下:
示例代码 start.S代码段
  1. 107 /************************************************************************
  2. 108  *
  3. 109  * void save_boot_params(u32 r0, u32 r1, u32 r2, u32 r3)
  4. 110  *  __attribute__((weak));
  5. 111  *
  6. 112  * Stack pointer is not yet initialized at this moment
  7. 113  * Don't save anything to stack even if compiled with -O0
  8. 114  *
  9. 115  ***********************************************************************/
  10. 116 ENTRY(save_boot_params)
  11. 117     b   save_boot_params_ret        @ back to my caller
复制代码

save_boot_params函数也是只有一句跳转语句,跳转到save_boot_params_ret函数,save_boot_params_ret函数代码如下:
示例代码 start.S代码段
  1. 42  save_boot_params_ret:
  2. 43  #ifdef CONFIG_ARMV7_LPAE
  3. 44  /*
  4. 45   * check for Hypervisor support
  5. 46   */
  6. 47      mrc p15, 0, r0, c0, c1, 1       @ read ID_PFR1
  7. 48      and r0, r0, #CPUID_ARM_VIRT_MASK    @ mask virtualization bits
  8. 49      cmp r0, #(1 << CPUID_ARM_VIRT_SHIFT)
  9. 50      beq switch_to_hypervisor
  10. 51  switch_to_hypervisor_ret:
  11. 52  #endif
  12. 53      /*
  13. 54       * disable interrupts (FIQ and IRQ), also set the cpu to SVC32 mode,
  14. 55       * except if in HYP mode already
  15. 56       */
  16. 57      mrs r0, cpsr
  17. 58      and r1, r0, #0x1f       @ mask mode bits
  18. 59      teq r1, #0x1a       @ test for HYP mode
  19. 60      bicne   r0, r0, #0x1f       @ clear all mode bits
  20. 61      orrne   r0, r0, #0x13       @ set SVC mode
  21. 62      orr r0, r0, #0xc0       @ disable FIQ and IRQ
  22. 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工作模式

24411.png

第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代码段
  1. 65  /*
  2. 66   * Setup vector:
  3. 67   * (OMAP4 spl TEXT_BASE is not 32 byte aligned.
  4. 68   * Continue to use ROM code vector only in OMAP4 spl)
  5. 69   */
  6. 70  #if !(defined(CONFIG_OMAP44XX) && defined(CONFIG_SPL_BUILD))
  7. 71      /* Set V=0 in CP15 SCTLR register - for VBAR to point to vector */
  8. 72      mrc p15, 0, r0, c1, c0, 0   @ Read CP15 SCTLR Register
  9. 73      bic r0, #CR_V       @ V = 0
  10. 74      mcr p15, 0, r0, c1, c0, 0   @ Write CP15 SCTLR Register
  11. 75  
  12. 76      /* Set vector address in CP15 VBAR register */
  13. 77      ldr r0, =_start
  14. 78      mcr p15, 0, r0, c12, c0, 0  @Set VBAR
  15. 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中有如下所示定义:
  1. #define CR_V        (1 << 13)        /* Vectors relocated to 0xffff0000        */
复制代码

因此这一行的目的就是清除r0寄存器中的bit13,SCTLR寄存器结构如下图所示:
第13章14337.png

图 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代码段
  1. 81      /* the mask ROM code should have PLL and others stable */
  2. 82  #ifndef CONFIG_SKIP_LOWLEVEL_INIT
  3. 83      bl  cpu_init_cp15
  4. 84  #ifndef CONFIG_SKIP_LOWLEVEL_INIT_ONLY
  5. 85      bl  cpu_init_crit
  6. 86  #endif
  7. 87  #endif
  8. 88  
  9. 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代码段
  1. 128 /*************************************************************************
  2. 129  *
  3. 130  * cpu_init_cp15
  4. 131  *
  5. 132  * Setup CP15 registers (cache, MMU, TLBs). The I-cache is turned on unless
  6. 133  * CONFIG_SYS_ICACHE_OFF is defined.
  7. 134  *
  8. 135  *************************************************************************/
  9. 136 ENTRY(cpu_init_cp15)
  10. 137     /*
  11. 138      * Invalidate L1 I/D
  12. 139      */
  13. 140     mov r0, #0          @ set up for MCR
  14. 141     mcr p15, 0, r0, c8, c7, 0   @ invalidate TLBs
  15. 142     mcr p15, 0, r0, c7, c5, 0   @ invalidate icache
  16. 143     mcr p15, 0, r0, c7, c5, 6   @ invalidate BP array
  17. 144     mcr     p15, 0, r0, c7, c10, 4  @ DSB
  18. 145     mcr     p15, 0, r0, c7, c5, 4   @ ISB
  19. 146
  20. 147     /*
  21. 148      * disable MMU stuff and caches
  22. 149      */
  23. 150     mrc p15, 0, r0, c1, c0, 0
  24. 151     bic r0, r0, #0x00002000 @ clear bits 13 (--V-)
  25. 152     bic r0, r0, #0x00000007 @ clear bits 2:0 (-CAM)
  26. 153     orr r0, r0, #0x00000002 @ set bit 1 (--A-) Align
  27. 154     orr r0, r0, #0x00000800 @ set bit 11 (Z---) BTB
  28. 155 #ifdef CONFIG_SYS_ICACHE_OFF
  29. 156     bic r0, r0, #0x00001000 @ clear bit 12 (I) I-cache
  30. 157 #else
  31. 158     orr r0, r0, #0x00001000 @ set bit 12 (I) I-cache
  32. 159 #endif
  33. 160     mcr p15, 0, r0, c1, c0, 0
  34. ......
  35. 305     mov pc, r5          @ back to my caller
  36. 306 ENDPROC(cpu_init_cp15)
复制代码

函数cpu_init_cp15都是一些和CP15有关的内容,我们不用关心,有兴趣的可以详细的看一下。
函数cpu_init_crit也在是定义在start.S文件中,函数内容如下:
示例代码 start.S代码段
  1. 310 /*************************************************************************
  2. 311  *
  3. 312  * CPU_init_critical registers
  4. 313  *
  5. 314  * setup important registers
  6. 315  * setup memory timing
  7. 316  *
  8. 317  *************************************************************************/
  9. 318 ENTRY(cpu_init_crit)
  10. 319     /*
  11. 320      * Jump to board specific initialization...
  12. 321      * The Mask ROM will have already initialized
  13. 322      * basic memory. Go here to bump up clock rate and handle
  14. 323      * wake up conditions.
  15. 324      */
  16. 325     b   lowlevel_init       @ go setup pll,mux,memory
  17. 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代码段
  1. 7  #include <asm-offsets.h>
  2. 8  #include <config.h>
  3. 9  #include <linux/linkage.h>
  4. 10
  5. 11 ENTRY(lowlevel_init)
  6. 12
  7. 13  /* Enable the the VFP */
  8. 14  mrc p15, 0, r1, c1, c0, 2
  9. 15  orr r1, r1, #(0x3 << 20)
  10. 16  orr r1, r1, #(0x3 << 20)
  11. 17  mcr p15, 0, r1, c1, c0, 2
  12. 18  isb
  13. 19  fmrx    r1, FPEXC
  14. 20  orr r1,r1, #(1<<30)
  15. 21  fmxr    FPEXC, r1
  16. 22
  17. 23  /* Move back to caller */
  18. 24  mov pc, lr
  19. 25
  20. 26 ENDPROC(lowlevel_init)
复制代码

可以看到ZYNQ的lowlevel_init函数功能很简单。第14-21行使能VFP。第24行返回调用。
函数调用路径如下图所示:
第13章17968.png

图 24.4.3 uboot函数调用路径

从上图可知,接下来要执行的是save_boot_params_ret中的_main函数,接下来分析_main函数。
13.4.3_main函数详解
_main函数定义在文件arch/arm/lib/crt0.S中,函数内容如下:
示例代码 crt0.S代码段
  1. 63  /*
  2. 64   * entry point of crt0 sequence
  3. 65   */
  4. 66  
  5. 67  ENTRY(_main)
  6. 68  
  7. 69  /*
  8. 70   * Set up initial C runtime environment and call board_init_f(0).
  9. 71   */
  10. 72  
  11. 73  #if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
  12. 74      ldr r0, =(CONFIG_SPL_STACK)
  13. 75  #else
  14. 76      ldr r0, =(CONFIG_SYS_INIT_SP_ADDR)
  15. 77  #endif
  16. 78      bic r0, r0, #7  /* 8-byte alignment for ABI compliance */
  17. 79      mov sp, r0
  18. 80      bl  board_init_f_alloc_reserve
  19. 81      mov sp, r0
  20. 82      /* set up gd here, outside any C code */
  21. 83      mov r9, r0
  22. 84      bl  board_init_f_init_reserve
  23. 85  
  24. 86      mov r0, #0
  25. 87      bl  board_init_f
  26. 88  
  27. 89  #if ! defined(CONFIG_SPL_BUILD)
  28. 90  
  29. 91  /*
  30. 92   * Set up intermediate environment (new sp and gd) and call
  31. 93   * relocate_code(addr_moni). Trick here is that we'll return
  32. 94   * 'here' but relocated.
  33. 95   */
  34. 96  
  35. 97      ldr r0, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */
  36. 98      bic r0, r0, #7  /* 8-byte alignment for ABI compliance */
  37. 99      mov sp, r0
  38. 100     ldr r9, [r9, #GD_BD]        /* r9 = gd->bd */
  39. 101     sub r9, r9, #GD_SIZE        /* new GD is below bd */
  40. 102
  41. 103     adr lr, here
  42. 104     ldr r0, [r9, #GD_RELOC_OFF]     /* r0 = gd->reloc_off */
  43. 105     add lr, lr, r0
  44. 106 #if defined(CONFIG_CPU_V7M)
  45. 107     orr lr, #1              /* As required by Thumb-only */
  46. 108 #endif
  47. 109     ldr r0, [r9, #GD_RELOCADDR]     /* r0 = gd->relocaddr */
  48. 110     b   relocate_code
  49. 111 here:
  50. 112 /*
  51. 113  * now relocate vectors
  52. 114  */
  53. 115
  54. 116     bl  relocate_vectors
  55. 117
  56. 118 /* Set up final (full) environment */
  57. 119
  58. 120     bl  c_runtime_cpu_setup /* we still call old routine here */
  59. 121 #endif
  60. 122 #if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_FRAMEWORK)
  61. 123 # ifdef CONFIG_SPL_BUILD
  62. 124     /* Use a DRAM stack for the rest of SPL, if requested */
  63. 125     bl  spl_relocate_stack_gd
  64. 126     cmp r0, #0
  65. 127     movne   sp, r0
  66. 128     movne   r9, r0
  67. 129 # endif
  68. 130     ldr r0, =__bss_start    /* this is auto-relocated! */
  69. 131
  70. 132 #ifdef CONFIG_USE_ARCH_MEMSET
  71. 133     ldr r3, =__bss_end      /* this is auto-relocated! */
  72. 134     mov r1, #0x00000000     /* prepare zero to clear BSS */
  73. 135
  74. 136     subs    r2, r3, r0      /* r2 = memset len */
  75. 137     bl  memset
  76. 138 #else
  77. 139     ldr r1, =__bss_end      /* this is auto-relocated! */
  78. 140     mov r2, #0x00000000     /* prepare zero to clear BSS */
  79. 141
  80. 142 clbss_l:cmp r0, r1          /* while not at end of BSS */
  81. 143 #if defined(CONFIG_CPU_V7M)
  82. 144     itt lo
  83. 145 #endif
  84. 146     strlo   r2, [r0]        /* clear 32-bit BSS word */
  85. 147     addlo   r0, r0, #4      /* move to next */
  86. 148     blo clbss_l
  87. 149 #endif
  88. 150
  89. 151 #if ! defined(CONFIG_SPL_BUILD)
  90. 152     bl coloured_LED_init
  91. 153     bl red_led_on
  92. 154 #endif
  93. 155     /* call board_init_r(gd_t *id, ulong dest_addr) */
  94. 156     mov     r0, r9                  /* gd_t */
  95. 157     ldr r1, [r9, #GD_RELOCADDR] /* dest_addr */
  96. 158     /* call board_init_r */
  97. 159 #if CONFIG_IS_ENABLED(SYS_THUMB_BUILD)
  98. 160     ldr lr, =board_init_r   /* this is auto-relocated! */
  99. 161     bx  lr
  100. 162 #else
  101. 163     ldr pc, =board_init_r   /* this is auto-relocated! */
  102. 164 #endif
  103. 165     /* we should not return here. */
  104. 166 #endif
  105. 167
  106. 168 ENDPROC(_main)
复制代码

第76行,加载CONFIG_SYS_INIT_SP_ADDR到r0。CONFIG_SYS_INIT_SP_ADDR在include/configs/zynq-common.h文件中有如下所示定义:
示例代码 zynq-common.h代码段
  1. 341 #define CONFIG_SYS_INIT_RAM_ADDR    0xFFFF0000
  2. 342 #define CONFIG_SYS_INIT_RAM_SIZE    0x2000
  3. 343 #define CONFIG_SYS_INIT_SP_ADDR     (CONFIG_SYS_INIT_RAM_ADDR + \
  4. 344                     CONFIG_SYS_INIT_RAM_SIZE - \
  5. 345                     GENERATED_GBL_DATA_SIZE)
复制代码

还需要知道GENERATED_GBL_DATA_SIZE的值,在文件include/generated/generic-asm-offsets.h中有定义如下:
示例代码 generic-asm-offsets.h代码段
  1. 1  #ifndef __GENERIC_ASM_OFFSETS_H__
  2. 2  #define __GENERIC_ASM_OFFSETS_H__
  3. 3  /*
  4. 4   * DO NOT MODIFY.
  5. 5   *
  6. 6   * This file was generated by Kbuild
  7. 7   */
  8. 8  
  9. 9  #define GENERATED_GBL_DATA_SIZE 208 /* (sizeof(struct global_data) + 15) & ~15   @ */
  10. 10 #define GENERATED_BD_INFO_SIZE 80 /* (sizeof(struct bd_info) + 15) & ~15 @ */
  11. 11 #define GD_SIZE 200 /* sizeof(struct global_data)    @ */
  12. 12 #define GD_BD 0 /* offsetof(struct global_data, bd)  @ */
  13. 13 #define GD_MALLOC_BASE 148 /* offsetof(struct global_data, malloc_base)  @ */
  14. 14 #define GD_RELOCADDR 44 /* offsetof(struct global_data, relocaddr)   @ */
  15. 15 #define GD_RELOC_OFF 64 /* offsetof(struct global_data, reloc_off)   @ */
  16. 16 #define GD_START_ADDR_SP 60 /* offsetof(struct global_data, start_addr_sp)   @ */
  17. 17 #define GD_NEW_GD 68 /* offsetof(struct global_data, new_gd) @ */
  18. 18
  19. 19 #endif
复制代码

GENERATED_GBL_DATA_SIZE=208,GENERATED_GBL_DATA_SIZE的含义为(sizeof(struct global_data) + 15) & ~15。
综上所述,CONFIG_SYS_INIT_SP_ADDR值如下:
  1. 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代码段
  1. 46  ulong board_init_f_alloc_reserve(ulong top)
  2. 47  {
  3. 48      /* Reserve early malloc arena */
  4. 49  #if CONFIG_VAL(SYS_MALLOC_F_LEN)
  5. 50      top -= CONFIG_VAL(SYS_MALLOC_F_LEN);
  6. 51  #endif
  7. 52      /* LAST : reserve GD (rounded up to a multiple of 16 bytes) */
  8. 53      top = rounddown(top-sizeof(struct global_data), 16);
  9. 54  
  10. 55      return top;
  11. 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中有如下图所示宏定义:
第13章23923.png

图 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代码段
  1. 27  typedef struct global_data {
  2. 28      bd_t *bd;
  3. 29      unsigned long flags;
  4. 30      unsigned int baudrate;
  5. 31      unsigned long cpu_clk;      /* CPU clock in Hz!     */
  6. 32      unsigned long bus_clk;
  7. 33      /* We cannot bracket this with CONFIG_PCI due to mpc5xxx */
  8. 34      unsigned long pci_clk;
  9. 35      unsigned long mem_clk;
  10. 36  #if defined(CONFIG_LCD) || defined(CONFIG_VIDEO)
  11. 37      unsigned long fb_base;      /* Base address of framebuffer mem */
  12. 38  #endif
  13. ......
  14. 117 #ifdef CONFIG_LOG
  15. 118     int log_drop_count;     /* Number of dropped log messages */
  16. 119     int default_log_level;      /* For devices with no filters */
  17. 120     struct list_head log_head;  /* List of struct log_device */
  18. 121 #endif
  19. 122 } gd_t;
复制代码

因此这一行代码就是设置gd所指向的位置,也就是gd指向0xffff1660。
回到_main函数,第84行调用函数board_init_f_init_reserve,此函数在文件common/init/board_init.c中有定义,函数内容如下:
示例代码 board_init.c代码段
  1. 100 void board_init_f_init_reserve(ulong base)
  2. 101 {
  3. 102     struct global_data *gd_ptr;
  4. 103
  5. 104     /*
  6. 105      * clear GD entirely and set it up.
  7. 106      * Use gd_ptr, as gd may not be properly set yet.
  8. 107      */
  9. 108
  10. 109     gd_ptr = (struct global_data *)base;
  11. 110     /* zero the area */
  12. 111     memset(gd_ptr, '\0', sizeof(*gd));
  13. 112     /* set GD unless architecture did it already */
  14. 113 #if !defined(CONFIG_ARM)
  15. 114     arch_setup_gd(gd_ptr);
  16. 115 #endif
  17. 116     /* next alloc will be higher by one GD plus 16-byte alignment */
  18. 117     base += roundup(sizeof(struct global_data), 16);
  19. 118
  20. 119     /*
  21. 120      * record early malloc arena start.
  22. 121      * Use gd as it is now properly set for all architectures.
  23. 122      */
  24. 123
  25. 124 #if CONFIG_VAL(SYS_MALLOC_F_LEN)
  26. 125     /* go down one 'early malloc arena' */
  27. 126     gd->malloc_base = base;
  28. 127     /* next alloc will be higher by one 'early malloc arena' size */
  29. 128     base += CONFIG_VAL(SYS_MALLOC_F_LEN);
  30. 129 #endif
  31. 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代码段
  1. 886 void board_init_f(ulong boot_flags)
  2. 887 {
  3. 888     gd->flags = boot_flags;
  4. 889     gd->have_console = 0;
  5. 890
  6. 891     if (initcall_run_list(init_sequence_f))
  7. 892         hang();
  8. 893
  9. 894 #if !defined(CONFIG_ARM) && !defined(CONFIG_SANDBOX) && \
  10. 895         !defined(CONFIG_EFI_APP) && !CONFIG_IS_ENABLED(X86_64)
  11. 896     /* NOTREACHED - jump_to_copy() does not return */
  12. 897     hang();
  13. 898 #endif
  14. 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. 1  static const init_fnc_t init_sequence_f[] = {
  2. 2   setup_mon_len,
  3. 3   fdtdec_setup,
  4. 4   initf_malloc,
  5. 5   log_init,
  6. 6   initf_bootstage,    /* uses its own timer, so does not need DM */
  7. 7   initf_console_record,
  8. 8   arch_cpu_init,      /* basic arch cpu dependent setup */
  9. 9   mach_cpu_init,      /* SoC/machine dependent CPU setup */
  10. 10  initf_dm,
  11. 11  arch_cpu_init_dm,
  12. 12  timer_init,     /* initialize timer */
  13. 13  env_init,       /* initialize environment */
  14. 14  init_baud_rate,     /* initialze baudrate settings */
  15. 15  serial_init,        /* serial communications setup */
  16. 16  console_init_f,     /* stage 1 init of console */
  17. 17  display_options,    /* say that we are here */
  18. 18  display_text_info,  /* show debugging info if required */
  19. 19  show_board_info,
  20. 20  INIT_FUNC_WATCHDOG_INIT
  21. 21  INIT_FUNC_WATCHDOG_RESET
  22. 22  init_func_i2c,
  23. 23  announce_dram_init,
  24. 24  dram_init,      /* configure available RAM banks */
  25. 25  INIT_FUNC_WATCHDOG_RESET
  26. 26  INIT_FUNC_WATCHDOG_RESET
  27. 27  INIT_FUNC_WATCHDOG_RESET
  28. 28  /*
  29. 29   * Now that we have DRAM mapped and working, we can
  30. 30   * relocate the code and continue running from DRAM.
  31. 31   *
  32. 32   * Reserve memory at end of RAM for (top down in that order):
  33. 33   *  - area that won't get touched by U-Boot and Linux (optional)
  34. 34   *  - kernel log buffer
  35. 35   *  - protected RAM
  36. 36   *  - LCD framebuffer
  37. 37   *  - monitor code
  38. 38   *  - board info struct
  39. 39   */
  40. 40  setup_dest_addr,
  41. 41  reserve_round_4k,
  42. 42  reserve_mmu,
  43. 43  reserve_video,
  44. 44  reserve_trace,
  45. 45  reserve_uboot,
  46. 46  reserve_malloc,
  47. 47  reserve_board,
  48. 48  setup_machine,
  49. 49  reserve_global_data,
  50. 50  reserve_fdt,
  51. 51  reserve_bootstage,
  52. 52  reserve_arch,
  53. 53  reserve_stacks,
  54. 54  dram_init_banksize,
  55. 55  show_dram_config,
  56. 56  display_new_sp,
  57. 57  INIT_FUNC_WATCHDOG_RESET
  58. 58  reloc_fdt,
  59. 59  reloc_bootstage,
  60. 60  setup_reloc,
  61. 61  NULL,
  62. 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,通过串口输出信息,如下图所示:
第13章32161.png

图 24.4.5串口信息输出

第18行,display_text_info,打印一些文本信息,如果开启UBOOT的DEBUG功能的话就会输出text_base、bss_start、bss_end,形式如下:
  1. debug("U-Boot code: %08lX -> %08lX  BSS: -> %08lX\n",text_base, bss_start, bss_end);
复制代码

结果如下图所示:
第13章32404.png

图 24.4.6文本信息

开启uboot的DEBUG功能的方法是在uboot的根目录下的include/common.h 中添加行
  1. #define DEBUG
复制代码

第19行,show_board_info函数用于打印开发板信息,会调用checkboard函数,结果如下图所示:
第13章32586.png

图 24.4.7开发板信息

第20行,INIT_FUNC_WATCHDOG_INIT,初始化看门狗,对于ZYNQ而言未定义。
第21行,INIT_FUNC_WATCHDOG_RESET,复位看门狗,对于ZYNQ而言未定义。
第22行,init_func_i2c函数用于初始化I2C,初始化完成以后会输出如下图所示信息:
第13章32791.png

图 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的值如下:
  1. gd->arch.tlb_size= 0X4000                        //MMU的TLB表大小
  2. gd->arch.tlb_addr=0x3fff0000                //MMU的TLB表起始地址,64KB对齐以后
  3. 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,结果如下:
  1. gd->mon_len = 0x000b58e8(使能Debug时为0xcb1ec)
  2. gd->start_addr_sp = 0x3ff3a000(使能Debug时为0x3ff24000)
  3. 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如下:
  1. TOTAL_MALLOC_LEN=0x1420000
复制代码

gd->start_addr_sp=0x3eb1a000  //0x3ff3a000-0x1420000=0x3eb1a000(使能Debug时为0x3eb04000)
第47行,reserve_board函数,留出板子bd所占的内存区,bd是结构体bd_t,bd_t大小为80字节,结果如下:
  1. gd->start_addr_sp=0x3eb19fb0(使能Debug为0x3eb03fb0)
  2. 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的配置,如下图所示:
第13章35230.png

图 24.4.9信息输出

第56行,display_new_sp函数,显示新的sp位置,也就是gd->start_addr_sp,不过要定义宏DEBUG,结果如下图所示:
第13章35361.png

图 24.4.10信息输出

上图中的gd->start_addr_sp值和我们前面分析的最后一次修改的值一致。
第58行,reloc_fdt函数用于重定位fdt,没有用到。
第59行,reloc_bootstage函数用于重定位bootstage,没有用到。
第60行,setup_reloc,设置gd其他一些成员变量,供后面重定位的时候使用,并且将以前的gd拷贝到gd->new_gd处。需要使能DEBUG才能看到相应的信息输出,如下图所示:
第13章35629.png
图 24.4.11信息输出
从上图可以看出,uboot重定位后的偏移为0x3fb24000,重定位后的新地址为0x3ff24000,新的gd首地址为0x3eb03ee8,最终的sp为0x3eb19ec0。
至此,board_init_f函数就执行完成了,最终的内存分配如下图所示:
第13章35815.png

图 24.4.12最终的内存分配图

13.4.5relocate_code函数详解
relocate_code函数是用于代码拷贝的,此函数定义在文件arch/arm/lib/relocate.S中,代码如下:
示例代码relocate.S代码段
  1. 68  /*
  2. 69   * void relocate_code(addr_moni)
  3. 70   *
  4. 71   * This function relocates the monitor code.
  5. 72   *
  6. 73   * NOTE:
  7. 74   * To prevent the code below from containing references with an R_ARM_ABS32
  8. 75   * relocation record type, we never refer to linker-defined symbols directly.
  9. 76   * Instead, we declare literals which contain their relative location with
  10. 77   * respect to relocate_code, and at run time, add relocate_code back to them.
  11. 78   */
  12. 79  
  13. 80  ENTRY(relocate_code)
  14. 81      ldr r1, =__image_copy_start /* r1 <- SRC &__image_copy_start */
  15. 82      subs    r4, r0, r1      /* r4 <- relocation offset */
  16. 83      beq relocate_done       /* skip relocation */
  17. 84      ldr r2, =__image_copy_end   /* r2 <- SRC &__image_copy_end */
  18. 85  
  19. 86  copy_loop:
  20. 87      ldmia   r1!, {r10-r11}      /* copy from source address [r1]    */
  21. 88      stmia   r0!, {r10-r11}      /* copy to   target address [r0]    */
  22. 89      cmp r1, r2          /* until source end address [r2]    */
  23. 90      blo copy_loop
  24. 91  
  25. 92      /*
  26. 93       * fix .rel.dyn relocations
  27. 94       */
  28. 95      ldr r2, =__rel_dyn_start    /* r2 <- SRC &__rel_dyn_start */
  29. 96      ldr r3, =__rel_dyn_end  /* r3 <- SRC &__rel_dyn_end */
  30. 97  fixloop:
  31. 98      ldmia   r2!, {r0-r1}        /* (r0,r1) <- (SRC location,fixup) */
  32. 99      and r1, r1, #0xff
  33. 100     cmp r1, #R_ARM_RELATIVE
  34. 101     bne fixnext
  35. 102
  36. 103     /* relative fix: increase location by offset */
  37. 104     add r0, r0, r4
  38. 105     ldr r1, [r0]
  39. 106     add r1, r1, r4
  40. 107     str r1, [r0]
  41. 108 fixnext:
  42. 109     cmp r2, r3
  43. 110     blo fixloop
  44. 111
  45. 112 relocate_done:
  46. 113
  47. 114 #ifdef __XSCALE__
  48. 115     /*
  49. 116      * On xscale, icache must be invalidated and write buffers drained,
  50. 117      * even with cache disabled - 4.2.7 of xscale core developer's manual
  51. 118      */
  52. 119     mcr p15, 0, r0, c7, c7, 0   /* invalidate icache */
  53. 120     mcr p15, 0, r0, c7, c10, 4  /* drain write buffer */
  54. 121 #endif
  55. 122
  56. 123     /* ARMv4- don't know bx lr but the assembler fails to see that */
  57. 124
  58. 125 #ifdef __ARM_ARCH_4__
  59. 126     mov pc, lr
  60. 127 #else
  61. 128     bx  lr
  62. 129 #endif
  63. 130
  64. 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中有如下代码:
  1. 105 # needed for relocation
  2. 106 LDFLAGS_u-boot  += -pie
复制代码

        从注释可以看到第106行的“-pie”选项用于重定位。编译链接uboot的时候会使用到“-pie”选项,如下图所示:
第13章39067.png

图 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存放的是需要重定位的标志。
第13章39541.png

图 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中,从下图可以看到该值是地址,而且还是代码拷贝前的地址。
第13章39995.png

图 24.4.15 第105行执行结果

第106行,将r1中代码拷贝前的地址加上代码拷贝的偏移量r4即可得到代码拷贝后的地址,执行结果见图 24.4.16。
第107行,将得到的代码拷贝后的地址值r1写入到代码拷贝后的r0所指地址处。
第13章40182.png

图 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代码段
  1. 28  ENTRY(relocate_vectors)
  2. 29  
  3. 30  #ifdef CONFIG_CPU_V7M
  4. 31      /*
  5. 32       * On ARMv7-M we only have to write the new vector address
  6. 33       * to VTOR register.
  7. 34       */
  8. 35      ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
  9. 36      ldr r1, =V7M_SCB_BASE
  10. 37      str r0, [r1, V7M_SCB_VTOR]
  11. 38  #else
  12. 39  #ifdef CONFIG_HAS_VBAR
  13. 40      /*
  14. 41       * If the ARM processor has the security extensions,
  15. 42       * use VBAR to relocate the exception vectors.
  16. 43       */
  17. 44      ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
  18. 45      mcr     p15, 0, r0, c12, c0, 0  /* Set VBAR */
  19. 46  #else
  20. 47      /*
  21. 48       * Copy the relocated exception vectors to the
  22. 49       * correct address
  23. 50       * CP15 c1 V bit gives us the location of the vectors:
  24. 51       * 0x00000000 or 0xFFFF0000.
  25. 52       */
  26. 53      ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
  27. 54      mrc p15, 0, r2, c1, c0, 0   /* V bit (bit[13]) in CP15 c1 */
  28. 55      ands    r2, r2, #(1 << 13)
  29. 56      ldreq   r1, =0x00000000     /* If V=0 */
  30. 57      ldrne   r1, =0xFFFF0000     /* If V=1 */
  31. 58      ldmia   r0!, {r2-r8,r10}
  32. 59      stmia   r1!, {r2-r8,r10}
  33. 60      ldmia   r0!, {r2-r8,r10}
  34. 61      stmia   r1!, {r2-r8,r10}
  35. 62  #endif
  36. 63  #endif
  37. 64      bx  lr
  38. 65  
  39. 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代码段
  1. 869 void board_init_r(gd_t *new_gd, ulong dest_addr)
  2. 870 {
  3. 871     /*
  4. 872      * Set up the new global data pointer. So far only x86 does this
  5. 873      * here.
  6. 874      * TODO(<a href="mailto:sjg@chromium.org">sjg@chromium.org</a>): Consider doing this for all archs, or
  7. 875      * dropping the new_gd parameter.
  8. 876      */
  9. 877 #if CONFIG_IS_ENABLED(X86_64)
  10. 878     arch_setup_gd(new_gd);
  11. 879 #endif
  12. 880
  13. 881 #ifdef CONFIG_NEEDS_MANUAL_RELOC
  14. 882     int i;
  15. 883 #endif
  16. 884
  17. 885 #if !defined(CONFIG_X86) && !defined(CONFIG_ARM) && !defined(CONFIG_ARM64)
  18. 886     gd = new_gd;
  19. 887 #endif
  20. 888     gd->flags &= ~GD_FLG_LOG_READY;
  21. 889
  22. 890 #ifdef CONFIG_NEEDS_MANUAL_RELOC
  23. 891     for (i = 0; i < ARRAY_SIZE(init_sequence_r); i++)
  24. 892         init_sequence_r += gd->reloc_off;
  25. 893 #endif
  26. 894
  27. 895     if (initcall_run_list(init_sequence_r))
  28. 896         hang();
  29. 897
  30. 898     /* NOTREACHED - run_main_loop() does not return */
  31. 899     hang();
  32. 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. 1  static init_fnc_t init_sequence_r[] = {
  2. 2   initr_trace,
  3. 3   initr_reloc,
  4. 4   initr_caches,
  5. 5   initr_reloc_global_data,
  6. 6   initr_barrier,
  7. 7   initr_malloc,
  8. 8   log_init,
  9. 9   initr_bootstage,    /* Needs malloc() but has its own timer */
  10. 10  initr_console_record,
  11. 11  bootstage_relocate,
  12. 12  initr_dm,
  13. 13     board_init,
  14. 14  set_cpu_clk_info, /* Setup clock information */
  15. 15  efi_memory_init,
  16. 16  stdio_init_tables,
  17. 17  initr_serial,
  18. 18  initr_announce,
  19. 19  INIT_FUNC_WATCHDOG_RESET
  20. 20  INIT_FUNC_WATCHDOG_RESET
  21. 21  power_init_board,
  22. 22  INIT_FUNC_WATCHDOG_RESET
  23. 23  initr_mmc,
  24. 24  initr_env,
  25. 25  INIT_FUNC_WATCHDOG_RESET
  26. 26  initr_secondary_cpu,
  27. 27  INIT_FUNC_WATCHDOG_RESET
  28. 28  stdio_add_devices,
  29. 29  initr_jumptable,
  30. 30  console_init_r,     /* fully init console as a device */
  31. 31  console_announce_r,
  32. 32  show_board_info,
  33. 33  INIT_FUNC_WATCHDOG_RESET
  34. 34  interrupt_init,
  35. 35  initr_enable_interrupts,
  36. 36  initr_ethaddr,
  37. 37  board_late_init,
  38. 38  INIT_FUNC_WATCHDOG_RESET
  39. 39  initr_net,
  40. 40  run_main_loop,
  41. 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函数来打印出当前的控制台设备,如下图所示:
第13章45754.png

图 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,串口输出如下图所示信息:
第13章46152.png
图 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文件代码段
  1. 655 static int run_main_loop(void)
  2. 656 {
  3. 657 #ifdef CONFIG_SANDBOX
  4. 658     sandbox_main_loop_init();
  5. 659 #endif
  6. 660     /* main_loop() can return to retry autoboot, if so just run it again */
  7. 661     for (;;)
  8. 662         main_loop();
  9. 663     return 0;
  10. 664 }
复制代码

        第664行是个死循环“for(;;)”,死循环里面就一个main_loop函数,main_loop函数定义在文件common/main.c里面,代码如下:
示例代码main_loop函数
  1. 43 /* We come here after U-Boot is initialised and ready to process commands */
  2. 44 void main_loop(void)
  3. 45 {
  4. 46  const char *s;
  5. 47
  6. 48  bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop");
  7. 49
  8. 50 #ifdef CONFIG_VERSION_VARIABLE
  9. 51  env_set("ver", version_string);  /* set version variable */
  10. 52 #endif /* CONFIG_VERSION_VARIABLE */
  11. 53
  12. 54  cli_init();
  13. 55
  14. 56  run_preboot_environment_command();
  15. 57
  16. 58 #if defined(CONFIG_UPDATE_TFTP)
  17. 59  update_tftp(0UL, NULL, NULL);
  18. 60 #endif /* CONFIG_UPDATE_TFTP */
  19. 61
  20. 62  s = bootdelay_process();
  21. 63  if (cli_process_fdt(&s))
  22. 64      cli_secure_boot_cmd(s);
  23. 65
  24. 66  autoboot_command(s);
  25. 67
  26. 68  cli_loop();
  27. 69  panic("No CLI available");
  28. 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. 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结构体
  1. 332 typedef struct bootm_headers {
  2. 333     /*
  3. 334      * Legacy os image header, if it is a multi component image
  4. 335      * then boot_get_ramdisk() and get_fdt() will attempt to get
  5. 336      * data from second and third component accordingly.
  6. 337      */
  7. 338     image_header_t  *legacy_hdr_os;     /* image header pointer */
  8. 339     image_header_t  legacy_hdr_os_copy; /* header copy */
  9. 340     ulong       legacy_hdr_valid;
  10. ……
  11. 362 #ifndef USE_HOSTCC
  12. 363     image_info_t    os;     /* os image info */
  13. 364     ulong       ep;     /* entry point of OS */
  14. 365
  15. 366     ulong       rd_start, rd_end;/* ramdisk start/end */
  16. 367
  17. 368     char        *ft_addr;   /* flat dev tree address */
  18. 369     ulong       ft_len;     /* length of flat device tree */
  19. 370
  20. 371     ulong       initrd_start;
  21. 372     ulong       initrd_end;
  22. 373     ulong       cmdline_start;
  23. 374     ulong       cmdline_end;
  24. 375     bd_t        *kbd;
  25. 376 #endif
  26. 377
  27. 378     int     verify;     /* env_get("verify")[0] != 'n' */
  28. 379
  29. 380 #define BOOTM_STATE_START   (0x00000001)
  30. 381 #define BOOTM_STATE_FINDOS  (0x00000002)
  31. 382 #define BOOTM_STATE_FINDOTHER   (0x00000004)
  32. 383 #define BOOTM_STATE_LOADOS  (0x00000008)
  33. 384 #define BOOTM_STATE_RAMDISK (0x00000010)
  34. 385 #define BOOTM_STATE_FDT     (0x00000020)
  35. 386 #define BOOTM_STATE_OS_CMDLINE  (0x00000040)
  36. 387 #define BOOTM_STATE_OS_BD_T (0x00000080)
  37. 388 #define BOOTM_STATE_OS_PREP (0x00000100)
  38. 389 #define BOOTM_STATE_OS_FAKE_GO  (0x00000200)    /* 'Almost' run the OS */
  39. 390 #define BOOTM_STATE_OS_GO   (0x00000400)
  40. 391     int     state;
  41. 392
  42. 393 #ifdef CONFIG_LMB
  43. 394     struct lmb  lmb;        /* for memory mgmt */
  44. 395 #endif
  45. 396 } bootm_headers_t;
复制代码

第363行的os成员变量是image_info_t类型的,为系统镜像信息。
第380~390行这11个宏定义表示BOOT的不同阶段。
接下来看一下结构体image_info_t,也就是系统镜像信息结构体,此结构体在文件include/image.h中的定义如下:
示例代码 image_info_t结构体
  1. 320 typedef struct image_info {
  2. 321     ulong       start, end;     /* start/end of blob */
  3. 322     ulong       image_start, image_len; /* start of image within blob, len of image */
  4. 323     ulong       load;           /* load addr for the image */
  5. 324     uint8_t     comp, type, os;     /* compression, type of image, os type */
  6. 325     uint8_t     arch;           /* CPU architecture */
  7. 326 } image_info_t;
复制代码

全局变量images会在bootz命令的执行中频繁使用到,相当于Linux内核启动的“灵魂”。
13.5.2do_bootz函数
bootz命令的执行函数为do_bootz,在文件cmd/bootz.c中有如下定义:
示例代码 do_bootz函数
  1. 61 int do_bootz(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
  2. 62 {
  3. 63  int ret;
  4. 64
  5. 65  /* Consume 'bootz' */
  6. 66  argc--; argv++;
  7. 67
  8. 68  if (bootz_start(cmdtp, flag, argc, argv, &images))
  9. 69      return 1;
  10. 70
  11. 71  /*
  12. 72   * We are doing the BOOTM_STATE_LOADOS state ourselves, so must
  13. 73   * disable interrupts ourselves
  14. 74   */
  15. 75  bootm_disable_interrupts();
  16. 76
  17. 77  images.os.os = IH_OS_LINUX;
  18. 78  ret = do_bootm_states(cmdtp, flag, argc, argv,
  19. 79 #ifdef CONFIG_SYS_BOOT_RAMDISK_HIGH
  20. 80                BOOTM_STATE_RAMDISK |
  21. 81 #endif
  22. 82                BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO |
  23. 83                BOOTM_STATE_OS_GO,
  24. 84                &images, 1);
  25. 85
  26. 86  return ret;
  27. 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函数
  1. 25 static int bootz_start(cmd_tbl_t *cmdtp, int flag, int argc,
  2. 26          char * const argv[], bootm_headers_t *images)
  3. 27 {
  4. 28  int ret;
  5. 29  ulong zi_start, zi_end;
  6. 30
  7. 31  ret = do_bootm_states(cmdtp, flag, argc, argv, BOOTM_STATE_START,
  8. 32                images, 1);
  9. 33
  10. 34  /* Setup Linux kernel zImage entry point */
  11. 35  if (!argc) {
  12. 36      images->ep = load_addr;
  13. 37      debug("*  kernel: default image load address = 0x%08lx\n",
  14. 38              load_addr);
  15. 39  } else {
  16. 40      images->ep = simple_strtoul(argv[0], NULL, 16);
  17. 41      debug("*  kernel: cmdline image address = 0x%08lx\n",
  18. 42          images->ep);
  19. 43  }
  20. 44
  21. 45  ret = bootz_setup(images->ep, &zi_start, &zi_end);
  22. 46  if (ret != 0)
  23. 47      return 1;
  24. 48
  25. 49  lmb_reserve(&images->lmb, images->ep, zi_end - zi_start);
  26. 50
  27. 51  /*
  28. 52   * Handle the BOOTM_STATE_FINDOTHER state ourselves as we do not
  29. 53   * have a header that provide this informaiton.
  30. 54   */
  31. 55  if (bootm_find_images(flag, argc, argv))
  32. 56      return 1;
  33. 57
  34. 58  return 0;
  35. 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函数
  1. 12 #define  LINUX_ARM_ZIMAGE_MAGIC  0x016f2818
  2. 13
  3. 14 struct arm_z_header {
  4. 15  uint32_t    code[9];
  5. 16  uint32_t    zi_magic;
  6. 17  uint32_t    zi_start;
  7. 18  uint32_t    zi_end;
  8. 19 } __attribute__ ((__packed__));
  9. 20
  10. 21 int bootz_setup(ulong image, ulong *start, ulong *end)
  11. 22 {
  12. 23  struct arm_z_header *zi = (struct arm_z_header *)image;
  13. 24
  14. 25  if (zi->zi_magic != LINUX_ARM_ZIMAGE_MAGIC) {
  15. 26 #ifndef CONFIG_SPL_FRAMEWORK
  16. 27      puts("Bad Linux ARM zImage magic!\n");
  17. 28 #endif
  18. 29      return 1;
  19. 30  }
  20. 31
  21. 32  *start = zi->zi_start;
  22. 33  *end = zi->zi_end;
  23. 34 #ifndef CONFIG_SPL_FRAMEWORK
  24. 35  printf("Kernel image @ %#08lx [ %#08lx - %#08lx ]\n",
  25. 36         image, *start, *end);
  26. 37 #endif
  27. 38
  28. 39  return 0;
  29. 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!”,比如我们输入一个错误的启动命令:
  1. bootz 8000 – 90000
复制代码

因为我们并没有在0X8000处存放Linux镜像文件(zImage),因此上面的命令肯定会执行出错的,结果如下图所示:

第13章54344.png

图 24.5.1启动出错

第32、33行初始化函数bootz_setup的参数start和end。
第35行,打印启动信息,如果Linux系统镜像正常的话就会输出下图所示的信息:
第13章54479.png

图 24.5.2 Linux镜像信息

接下来看一下函数bootm_find_images。boot_find_images函数将尝试加载可用的ramdisk,设备树以及带有特殊标记的“可加载”映像,此函数定义在文件common/bootm.c中,函数内容如下:
示例代码 bootm_find_images函数
  1. 227 int bootm_find_images(int flag, int argc, char * const argv[])
  2. 228 {
  3. 229     int ret;
  4. 230
  5. 231     /* find ramdisk */
  6. 232     ret = boot_get_ramdisk(argc, argv, &images, IH_INITRD_ARCH,
  7. 233                    &images.rd_start, &images.rd_end);
  8. 234     if (ret) {
  9. 235         puts("Ramdisk image is corrupt or invalid\n");
  10. 236         return 1;
  11. 237     }
  12. 238
  13. 239 #if IMAGE_ENABLE_OF_LIBFDT
  14. 240     /* find flattened device tree */
  15. 241     ret = boot_get_fdt(flag, argc, argv, IH_ARCH_DEFAULT, &images,
  16. 242                &images.ft_addr, &images.ft_len);
  17. 243     if (ret) {
  18. 244         puts("Could not find a valid device tree\n");
  19. 245         return 1;
  20. 246     }
  21. 247     set_working_fdt_addr((ulong)images.ft_addr);
  22. 248 #endif
  23. 249
  24. 250 #if IMAGE_ENABLE_FIT
  25. 251 #if defined(CONFIG_FPGA)
  26. 252     /* find bitstreams */
  27. 253     ret = boot_get_fpga(argc, argv, &images, IH_ARCH_DEFAULT,
  28. 254                 NULL, NULL);
  29. 255     if (ret) {
  30. 256         printf("FPGA image is corrupted or invalid\n");
  31. 257         return 1;
  32. 258     }
  33. 259 #endif
  34. ……
  35. 270     return 0;
  36. 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状态:
  1. states & BOOTM_STATE_XXX
复制代码

在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状态对应的处理代码:
  1. BOOTM_STATE_OS_PREP
  2. BOOTM_STATE_OS_FAKE_GO
  3. BOOTM_STATE_OS_GO
  4. BOOTM_STATE_START
复制代码

精简以后的do_bootm_states函数如下所示:
示例代码 do_bootm_states函数
  1. 603 int do_bootm_states(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[],
  2. 604             int states, bootm_headers_t *images, int boot_progress)
  3. 605 {
  4. 606     boot_os_fn *boot_fn;
  5. 607     ulong iflag = 0;
  6. 608     int ret = 0, need_boot_fn;
  7. 609
  8. 610     images->state |= states;
  9. 611
  10. 612     /*
  11. 613      * Work through the states and see how far we get. We stop on
  12. 614      * any error.
  13. 615      */
  14. 616     if (states & BOOTM_STATE_START)
  15. 617         ret = bootm_start(cmdtp, flag, argc, argv);
  16. ……
  17. 661     /* From now on, we need the OS boot function */
  18. 662     if (ret)
  19. 663         return ret;
  20. 664     boot_fn = bootm_os_get_boot_func(images->os.os);
  21. ……
  22. 683     if (!ret && (states & BOOTM_STATE_OS_PREP)) {
  23. 684 #if defined(CONFIG_SILENT_CONSOLE) && !defined(CONFIG_SILENT_U_BOOT_ONLY)
  24. 685         if (images->os.os == IH_OS_LINUX)
  25. 686             fixup_silent_linux();
  26. 687 #endif
  27. 688         ret = boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images);
  28. 689     }
  29. 690
  30. 691 #ifdef CONFIG_TRACE
  31. 692     /* Pretend to run the OS, then run a user command */
  32. 693     if (!ret && (states & BOOTM_STATE_OS_FAKE_GO)) {
  33. 694         char *cmd_list = env_get("fakegocmd");
  34. 695
  35. 696         ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_FAKE_GO,
  36. 697                 images, boot_fn);
  37. 698         if (!ret && cmd_list)
  38. 699             ret = run_command_list(cmd_list, -1, flag);
  39. 700     }
  40. 701 #endif
  41. 703     /* Check for unsupported subcommand. */
  42. 704     if (ret) {
  43. 705         puts("subcommand not supported\n");
  44. 706         return ret;
  45. 707     }
  46. 708
  47. 709     /* Now run the OS! We hope this doesn't return */
  48. 710     if (!ret && (states & BOOTM_STATE_OS_GO))
  49. 711         ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_GO,
  50. 712                 images, boot_fn);
  51. ……
  52. 724     return ret;
  53. 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函数
  1. 477 int boot_selected_os(int argc, char * const argv[], int state,
  2. 478              bootm_headers_t *images, boot_os_fn *boot_fn)
  3. 479 {
  4. 480     arch_preboot_os();
  5. 481     boot_fn(state, argc, argv, images);
  6. ……
  7. 491     return BOOTM_ERR_RESET;
  8. 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函数
  1. 494 boot_os_fn *bootm_os_get_boot_func(int os)
  2. 495 {
  3. 496 #ifdef CONFIG_NEEDS_MANUAL_RELOC
  4. 497     static bool relocated;
  5. 498
  6. 499     if (!relocated) {
  7. 500         int i;
  8. 501
  9. 502         /* relocate boot function table */
  10. 503         for (i = 0; i < ARRAY_SIZE(boot_os); i++)
  11. <span style="font-style: italic;"><span style="font-style: normal;">504             if (boot_os != NULL)
  12. 505                 boot_os</span><span style="font-style: normal;"> += gd->reloc_off;
  13. 506
  14. 507         relocated = true;
  15. 508     }
  16. 509 #endif
  17. 510     return boot_os[os];
  18. 511 }</span></span>
复制代码

第496-509行是条件编译,在zynq的uboot中没有用到,因此这段代码无效,只有第510行有效。第510行中的boot_os是个数组,这个数组里面存放着不同的系统对应的启动函数。boot_os也定义在文件common/bootm_os.c中,如下所示:
示例代码 boot_os数组
  1. 436 static boot_os_fn *boot_os[] = {
  2. 437     [IH_OS_U_BOOT] = do_bootm_standalone,
  3. 438 #ifdef CONFIG_BOOTM_LINUX
  4. 439     [IH_OS_LINUX] = do_bootm_linux,
  5. 440 #endif
  6. ……
  7. 466 #ifdef CONFIG_BOOTM_OPENRTOS
  8. 467     [IH_OS_OPENRTOS] = do_bootm_openrtos,
  9. 468 #endif
  10. 469 };
复制代码

第439行就是Linux系统对应的启动函数:do_bootm_linux。
13.5.6do_bootm_linux函数
经过前面的分析,我们知道了do_bootm_linux就是最终启动Linux内核的函数,此函数定义在文件arch/arm/lib/bootm.c,函数内容如下:
示例代码 do_bootm_linux函数
  1. 402 int do_bootm_linux(int flag, int argc, char * const argv[],
  2. 403            bootm_headers_t *images)
  3. 404 {
  4. 405     /* No need for those on ARM */
  5. 406     if (flag & BOOTM_STATE_OS_BD_T || flag & BOOTM_STATE_OS_CMDLINE)
  6. 407         return -1;
  7. 408
  8. 409     if (flag & BOOTM_STATE_OS_PREP) {
  9. 410         boot_prep_linux(images);
  10. 411         return 0;
  11. 412     }
  12. 413
  13. 414     if (flag & (BOOTM_STATE_OS_GO | BOOTM_STATE_OS_FAKE_GO)) {
  14. 415         boot_jump_linux(images, flag);
  15. 416         return 0;
  16. 417     }
  17. 418
  18. 419     boot_prep_linux(images);
  19. 420     boot_jump_linux(images, flag);
  20. 421     return 0;
  21. 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函数
  1. 311 /* Subcommand: GO */
  2. 312 static void boot_jump_linux(bootm_headers_t *images, int flag)
  3. 313 {
  4. 314 #ifdef CONFIG_ARM64
  5. ……
  6. 352 #else
  7. 353     unsigned long machid = gd->bd->bi_arch_number;
  8. 354     char *s;
  9. 355     void (*kernel_entry)(int zero, int arch, uint params);
  10. 356     unsigned long r2;
  11. 357     int fake = (flag & BOOTM_STATE_OS_FAKE_GO);
  12. 358
  13. 359     kernel_entry = (void (*)(int, int, uint))images->ep;
  14. 360 #ifdef CONFIG_CPU_V7M
  15. 361     ulong addr = (ulong)kernel_entry | 1;
  16. 362     kernel_entry = (void *)addr;
  17. 363 #endif
  18. 364     s = env_get("machid");
  19. 365     if (s) {
  20. 366         if (strict_strtoul(s, 16, &machid) < 0) {
  21. 367             debug("strict_strtoul failed!\n");
  22. 368             return;
  23. 369         }
  24. 370         printf("Using machid 0x%lx from environment\n", machid);
  25. 371     }
  26. 372
  27. 373     debug("## Transferring control to Linux (at address %08lx)" \
  28. 374         "...\n", (ulong) kernel_entry);
  29. 375     bootstage_mark(BOOTSTAGE_ID_RUN_OS);
  30. 376     announce_and_cleanup(fake);
  31. 377
  32. 378     if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len)
  33. 379         r2 = (unsigned long)images->ft_addr;
  34. 380     else
  35. 381         r2 = gd->bd->bi_boot_params;
  36. ……
  37. 391             kernel_entry(0, machid, r2);
  38. 392     }
  39. 393 #endif
  40. 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函数
  1. 79  static void announce_and_cleanup(int fake)
  2. 80  {
  3. 81      printf("\nStarting kernel ...%s\n\n", fake ?
  4. 82          "(fake run for tracing)" : "");
  5. 83      bootstage_mark_name(BOOTSTAGE_ID_BOOTM_HANDOFF, "start_kernel");
  6. ……
  7. 95      board_quiesce_devices();
  8. 96  
  9. 97      /*
  10. 98       * Call remove function of all devices with a removal flag set.
  11. 99       * This may be useful for last-stage operations, like cancelling
  12. 100      * of DMA operation or releasing device internal buffers.
  13. 101      */
  14. 102     dm_remove_devices_flags(DM_REMOVE_ACTIVE_ALL);
  15. 103
  16. 104     cleanup_before_linux();
  17. 105 }
复制代码

第81行,在启动Linux之前输出“Starting kernel ...”信息,如下图所示:
第13章64044.png

图 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命令的执行过程,如下图所示:
第13章64518.png

图 24.5.4 bootz命令执行过程

到这里uboot的启动流程我们就讲解完成了,加上uboot顶层Makefile的分析,洋洋洒洒近100页,还是不少的。这也仅仅是uboot启动流程分析,当缕清了uboot的启动流程以后,后面移植uboot就会轻松很多。其实在工作中我们基本不需要这么详细的去了解uboot,半导体厂商提供给我们的uboot一般是可以直接用的,只要能跑起来,可以使用就可以了。但是作为学习,我们是必须了解一下uboot的启动流程,否则如果在工作中遇到问题我们连解决的方法都没有,都不知道该从哪里看起。但是呢,如果第一次就想弄懂uboot的整个启动流程还是有点困难的,所以如果没有看懂的话,不要紧!不要气馁,大多数人第一次看uboot启动流程基本都有各种各样的问题。









阿莫论坛20周年了!感谢大家的支持与爱护!!

一只鸟敢站在脆弱的枝条上歇脚,它依仗的不是枝条不会断,而是自己有翅膀,会飞。

出100入18汤圆

发表于 2020-8-31 17:35:37 | 显示全部楼层
感谢分享,U-Boot启动流程详解真心不错!
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-3-29 14:27

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表