详解Cortex-A9 uboot启动代码

系统 Linux
本篇给大家介绍一款比较常用的bootloader:uboot,通过uboot的介绍以及源代码的详细分析,让大家把之前所有ARM相关的知识点融会贯通起来。

[[378325]]

 前言

我们在前面的arm系列课程,已经讲解了arm的架构、汇编指令、异常、常用外设的控制器驱动,那么我们已经具备开发arm系列产品的基本技能。

本篇给大家介绍一款比较常用的bootloader:uboot,通过uboot的介绍以及源代码的详细分析,让大家把之前所有ARM相关的知识点融会贯通起来。

一、uboot

1. 概念

U-Boot 是一个主要用于嵌入式系统的引导加载程序,可以支持多种不同的计算机系统结构,包括PPC、ARM、AVR32、MIPS、x86、68k、Nios与MicroBlaze。这也是一套在GNU通用公共许可证之下发布的自由软件。

U-Boot不仅仅支持嵌入式Linux系统的引导,它还支持NetBSD, VxWorks, QNX, RTEMS, ARTOS, LynxOS, android嵌入式操作系统。其目前要支持的目标操作系统是OpenBSD, NetBSD, FreeBSD,4.4BSD, Linux, SVR4, Esix, Solaris, Irix, SCO, Dell, NCR, VxWorks, LynxOS, pSOS, QNX, RTEMS, ARTOS, android。

2. uboot基本功能

U-Boot可支持的主要功能列表:

  • 系统引导支持NFS挂载、RAMDISK(压缩或非压缩)形式的根文件系统;支持NFS挂载、从FLASH中引导压缩或非压缩系统内核;
  • 基本辅助功能强大的操作系统接口功能;可灵活设置、传递多个关键参数给操作系统,适合系统在不同开发阶段的调试要求与产品发布,尤以Linux支持最为强劲;支持目标板环境参数多种存储方式,如FLASH、NVRAM、EEPROM;
  • CRC32校验可校验FLASH中内核、RAMDISK镜像文件是否完好;
  • 设备驱动串口、SDRAM、FLASH、以太网、LCD、NVRAM、EEPROM、键盘、USB、PCMCIA、PCI、RTC等驱动支持;
  • 上电自检功能SDRAM、FLASH大小自动检测;SDRAM故障检测;CPU型号。

3. 常用命令

uboot命令比较多,下面只列举网络启动要用到的命令:

4. 配置参数举例

以下以网络下载内核、网络挂载nfs为例。

1)ubuntu环境

ubuntu ip:192.168.6.186

nfs配置:

配置文件如下:

  1. /etc/exports 

配置信息如下:

 

nfs

2)开发板设置

开发板ip:192.168.6.187

配置命令:

  1. setenv ipaddr 192.168.6.187      ;板子的ip 
  2. setenv serverip 192.168.6.186    ;虚拟机的ip 
  3. setenv gatewayip 192.168.1.1     ;网关 
  4. saveenv                          ;保存配置 

加载内核和设备树

  1. setenv bootcmd tftp 41000000 uImage\;tftp 42000000 exynos4412-fs4412.dtb\;bootm 41000000 - 42000000 

bootcmd:uboot2启动之后,首先先执行找到这个参数,执行后面的命令。

从tftp服务器下载内核镜像uImage到地址41000000,设备树文件exynos4412-fs4412.dtb到42000000,并通过命令bootm加载启动内核。

  • 挂载nfs
  1. setenv bootargs root=/dev/nfs nfsroot=192.168.6.186:/rootfs rw console=ttySAC2,115200 init=/linuxrc ip=192.168.6.187 

挂载nfs文件系统

  • root=/dev/nfs
  • nfsroot=192.168.6.186:/rootfs nfs服务器地址192.168.6.186,目录为/rootfs,
  • rw 文件系统操作权限为可续写
  • console=ttySAC2,115200 串口名称和波特率
  • init=/linuxrc 内核启动后运行的进程为linuxrc
  • ip=192.168.6.187 开发板地址

二、exynos-4412 Soc 启动顺序

要想了解exynos-4412的启动顺序,我们首先需要了解该soc的内存布局。

1. exynos-4412内存布局

通常一款soc的内存在厂家设计的时候就已经规定死了,对于使用者来说,我们无法改变。


我们只关心和启动相关的一个地址,

  1. iROM 在soc内部,出厂时厂家固化了特定的程序,iROM中程序对应用户来说不可改变
  2. iRAM 在soc内部,速度较快,但空间不大
  3. DMC RAM控制器,位于SOC内部,用于驱动RAM,大容量的RAM都需要连接到该控制器

2. Booting Sequence

不同的厂家的启动顺序是不太一样的,本篇主要以三星的exynos-4412 soc为基础,讲解该基于该板子的uboot启动顺序。


根据上图,系统启动的大概顺序:

  • iROM在SOC内部,是一个64KB的ROM,他树池化一些系统启动必须的功能。比如:时钟、栈。
  • iROM负责从特殊的启动外设加载BL1的image到soc内部的256KB的SRAM中。启动的外设由操作按钮来决定的。根据不同按键的值,iROM将会对bl1 的image做不同的校验。
  • BL1初始化系统时钟和DRAM控制器,然后从启动外设加载OS image到DRAM中。根据启动按钮的值的不同,BL1会对OS做不同的校验。
  • 启动完成之后,BL1跳转到操作系统(kernel)。

iROM会根据OM 引脚的不同选择不同的启动设备,对应的OM寄存器需要提供对应的启动信息。

三、内核启动流程概述

1. 内核启动流程 概述

 

uboot启动流程

如上图所示:

  1. 设备上电之后,先执行iROM中的出厂代码,先进行必要硬件的初始化 去执行uboot,
  2. 通常把kernel、设备树文件放到flash中
  3. 程序启动之后,往往先从flash启动,运行uboot
  4. 第一步:先进行硬件的初始化(svc模式栈、clock、内存、串口) 第二步:自搬移:把uboot从flash中拷贝到RAM中,跳转到RAM中执行剩下的uboot代码
  5. 第三步:把内核拷贝到RAM中,执行内核,把控制权交给内核。

2. 内核启动详细流程

开发板从上电到启动内核的过程

四、uboot启动流程代码详解

1. lds文件

要想了解uboot整个项目的代码流程,必须首先了解链接脚本【链接脚本参考《7. 从0开始学ARM-GNU伪指令,lds使用》】。

该文件决定了uboot最终生成的镜像文件,各个段的布局。

uboot链接脚本如下:

  1. u-boot-2013.01/arch/arm/cpu/u-boot.lds 

文件内容:

  1. 26 OUTPUT_FORMAT("elf32-littlearm""elf32-littlearm""elf32-littlearm"
  2.  27 OUTPUT_ARCH(arm) 
  3.  28 ENTRY(_start) 
  4.  29 SECTIONS 
  5.  30 { 
  6.  31     . = 0x00000000; 
  7.  32  
  8.  33     . = ALIGN(4); 
  9.  34     .text : 
  10.  35     { 
  11.  36         __image_copy_start = .; 
  12.  37         CPUDIR/start.o (.text*) 
  13.  38         *(.text*) 
  14.  39     } 
  15.  40  
  16.  41     . = ALIGN(4); 
  17.  42     .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) } 
  18.  43  
  19.  44     . = ALIGN(4); 
  20.  45     .data : { 
  21.  46         *(.data*) 
  22.  47     } 
  23.  48  
  24.  49     . = ALIGN(4); 
  25.  50  
  26.  51     . = .; 
  27.  52  
  28.  53     . = ALIGN(4); 
  29.  54     .u_boot_list : { 
  30.  55     #include <u-boot.lst> 
  31.  56     } 
  32.  57  
  33.  58     . = ALIGN(4); 
  34.  59  
  35.  60     __image_copy_end = .; 
  36.  61  
  37.  62     .rel.dyn : { 
  38.  63         __rel_dyn_start = .; 
  39.  64         *(.rel*) 
  40.  65         __rel_dyn_end = .; 
  41.  66     } 
  42.  67  
  43.  68     .dynsym : { 
  44.  69         __dynsym_start = .; 
  45.  70         *(.dynsym) 
  46.  71     } 
  47.  72  
  48.  73     _end = .; 
  49.  74  
  50.  75     /* 
  51.  76      * Deprecated: this MMU section is used by pxa at present but 
  52.  77      * should not be used by new boards/CPUs. 
  53.  78      */ 
  54.  79     . = ALIGN(4096); 
  55.  80     .mmutable : { 
  56.  81         *(.mmutable) 
  57.  82     } 
  58.  83  
  59.  84     .bss __rel_dyn_start (OVERLAY) : { 
  60.  85         __bss_start = .; 
  61.  86         *(.bss*) 
  62.  87          . = ALIGN(4); 
  63.  88         __bss_end__ = .; 
  64.  89     } 
  65.  90  
  66.  91     /DISCARD/ : { *(.dynstr*) } 
  67.  92     /DISCARD/ : { *(.dynamic*) } 
  68.  93     /DISCARD/ : { *(.plt*) } 
  69.  94     /DISCARD/ : { *(.interp*) } 
  70.  95     /DISCARD/ : { *(.gnu*) } 
  71.  96 } 
  72.  97  

核心内容解释:

  1. 27 OUTPUT_ARCH(arm)       :    该镜像运行在arm架构的硬件上 
  2. 28 ENTRY(_start)          :    程序的入口是 _start 
  3. 29 SECTIONS 
  4. 30 { 
  5. 31  . = 0x00000000;      :   程序的链接地址,不是运行地址【uboot一定是位置无关码】 
  6. 34     .text : 
  7. 35     { 
  8. 36         __image_copy_start = .;    : 宏对应整个程序编译好后首地址,自搬移代码的初始位置 
  9. 37         CPUDIR/start.o (.text*)    : 第一个目标文件CPUDIR/start.o中的代码段 
  10. 38         *(.text*)                  : 剩下的目标文件的代码段 
  11. 39     } 
  12. 60     __image_copy_end = .;          : 自搬移代码的结束为止 

BSS全局未初始化变量、全局初始化为0的变量所在的段:

  1. 84     .bss __rel_dyn_start (OVERLAY) : { 
  2.  85         __bss_start = .; 
  3.  88         __bss_end__ = .; 
  4.  89     } 

2. uboot启动代码流程概要

代码只分析到uboot命令行,函数main_loop()位置。


3. 启动代码详细分析

_start入口位于以下文件:

  1. u-boot-2013.01/arch/arm/cpu/armv7/start.S 

第一阶段:

 

第二阶段

第二阶段代码从_main开始:


以上代码详细解释,请结合B站视频同步学习。

五、uboot启动的几个关键知识点

1.如何判断第一条机器指令的位置?

链接脚本决定了内存的布局。

uboot链接脚本如下:

  1. u-boot-2013.01/arch/arm/cpu/u-boot.lds 

文件内容:

  1. 28 ENTRY(_start) 
  2. 29 SECTIONS 
  3. 30 { 
  4. 31     . = 0x00000000; 
  5. 32  

uboot的入口是_start

链接地址是0x00000000

2.uboot如何搬运代码?

代码位于:

  1. u-boot-2013.01/arch/arm/cpu/armv7/start.S 

搬移代码如下:

  1. ENTRY(relocate_code) 
  2.  mov r4, r0 /* save addr_sp */ 
  3.  mov r5, r1 /* save addr of gd */ 
  4.  mov r6, r2 /* save addr of destination */ 
  5.  
  6.  adr r0, _start 
  7.  cmp r0, r6 
  8.  moveq r9, #0  /* no relocation. relocation offset(r9) = 0 */ 
  9.  beq relocate_done  /* skip relocation */ 
  10.  mov r1, r6   /* r1 <- scratch for copy_loop */ 
  11.  ldr r3, _image_copy_end_ofs 
  12.  add r2, r0, r3  /* r2 <- source end address     */ 
  13.  
  14. copy_loop: 
  15.  ldmia r0!, {r9-r10}  /* copy from source address [r0]    */ 
  16.  stmia r1!, {r9-r10}  /* copy to   target address [r1]    */ 
  17.  cmp r0, r2   /* until source end address [r2]    */ 
  18.  blo copy_loop 

详情参考第四章,第3节。

3.uboot中,如何判断此次开机是从断电状态开机还是从休眠状态启动的?

  1. board/samsung/fs4412/lowlevel_init.S 

代码如下:

  1. 41   lowlevel_init: 
  2. 54     /* AFTR wakeup reset */ 
  3. 55     ldr r2, =S5P_CHECK_DIDLE 
  4. 56     cmp r1, r2 
  5. 57     beq exit_wakeup 
  6. 58  
  7. 59     /* LPA wakeup reset */ 
  8. 60     ldr r2, =S5P_CHECK_LPA 
  9. 61     cmp r1, r2 
  10. 62     beq exit_wakeup 
  11. 63  
  12. 64     /* Sleep wakeup reset */ 
  13. 65     ldr r2, =S5P_CHECK_SLEEP 
  14. 66     cmp r1, r2 
  15. 67     beq wakeup_reset 
  16.  
  17. 112 wakeup_reset: 
  18. 113     bl system_clock_init 
  19. 114     bl mem_ctrl_asm_init 
  20. 115     bl tzpc_init 
  21. 116  
  22. 117 exit_wakeup: 
  23. 118     /* Load return address and jump to kernel */ 
  24. 119     ldr r0, =(EXYNOS4_POWER_BASE + INFORM0_OFFSET) 
  25. 120  
  26. 121     /* r1 = physical address of exynos4210_cpu_resume function */ 
  27. 122     ldr r1, [r0] 
  28. 123  
  29. 124     /* Jump to kernel*/ 
  30. 125     mov pc, r1 

由上可知,当手机因为各种原因进入休眠时,会将当前程序执行的上下文保护起来,并向一些pmic的寄存器中写入指定的数据,以表明此次是因为何种原因进入休眠。

而手机并没有完全断电,而是处于一个低功耗模式下,此时启动RAM仍然有数据,所以在此启动后,只需要从特殊的寄存器中读取相应的值,就可以知道之前是因为什么原因休眠,进而回复休眠之前的上下文即可。

3.uboot代码搬到ram之后,代码的运行地址发生了变化,如何保证程序跳转不会出错?

除了要保证uboot代码是基于地址无关的,此外.rel.dyn帮我们解决了,其实主要还是编译器帮我们做了很多工作。

位置无关码参考《15. 从0学ARM-什么是位置无关码?》

4.设备启动的时候,有可能直接从ram启动, 如何知道当前是从flah启动还是ram启动的?

文件:

  1. board/samsung/fs4412/lowlevel_init.S 

代码:

lowlevel_init:

  1. 85     /* 
  2.  86      * If U-boot is already running in ram, no need to relocate U-Boot. 
  3.  87      * Memory controller must be configured before relocating U-Boot 
  4.  88      * in ram. 
  5.  89      */ 
  6.  90     ldr r0, =0x0ffffff      /* r0 <- Mask Bits*/ 
  7.  91     bic r1, pc, r0      /* pc <- current addr of code */ 
  8.  92                     /* r1 <- unmasked bits of pc */ 
  9.  93     ldr r2, _TEXT_BASE      /* r2 <- original base addr in ram */ 
  10.  94     bic r2, r2, r0      /* r2 <- unmasked bits of r2*/ 
  11.  95     cmp r1, r2          /* compare r1, r2 */ 
  12.  96     beq 1f          /* r0 == r1 then skip sdram init */ 

原理:RAM地址空间是:0x40000000-0xA0000000 0xA0000000-0x00000000 而iROM/iRAM地址的bit:28-31均是0,所以只需要读取出执行到lowlevel_init时pc的值,判断其bit:28-31是否是0即可知道现在代码是否运行在RAM中。

 

责任编辑:姜华 来源: 一口Linux
相关推荐

2021-01-19 19:32:01

Cortex-A9 R嵌入式系统i2c 外设

2021-01-06 05:42:42

Cortex-A9 R嵌入式系统 RTC

2021-01-16 11:40:28

ARM嵌入式开发ADC应用

2020-12-22 11:54:42

C语言Cortex-A9LED汇编

2020-12-30 15:17:25

Cortex-A9UARTprintf函数

2021-01-08 12:06:59

WDT定时装置

2009-11-11 10:19:59

uboot内核

2021-04-20 09:26:40

Cortex M架构Cortex A架构STM32系列

2019-08-19 00:31:16

Pytorch神经网络深度学习

2012-02-21 23:05:17

Windows启动顺序

2011-02-23 15:26:01

FileZilla

2017-08-03 10:25:26

AndroidActivity

2010-06-09 13:52:55

OpenSUSE桌面

2010-06-13 15:58:20

GRUB启动命令

2009-12-22 17:46:56

Fedora 9嵌入式

2009-12-24 14:14:41

Fedora 9

2010-08-30 19:55:27

配置DHCP

2009-09-07 14:54:47

CCNA学习笔记

2014-05-27 15:36:01

AndroidActivity启动模式

2011-09-05 17:35:18

MTK启动过程RTOS
点赞
收藏

51CTO技术栈公众号