前言我们在之前的arm系列课程中已经讲解了常用外设的架构、汇编指令、异常、控制器驱动,所以我们已经具备了开发arm系列产品的基本功.本文介绍一种比较常用的bootloader:uboot。通过对uboot的介绍和对源码的详细分析,让大家将之前所有与ARM相关的知识点整合起来。1.uboot1。ConceptU-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。二、uboot基本功能U-Boot能够支持的主要功能列表:系统启动支持NFS挂载、RAMDISK(压缩或未压缩)根文件系统;支持NFS挂载,从FLASH启动压缩或非压缩系统内核;基本的辅助功能和强大的操作系统接口功能;可灵活设置和传递多个关键参数给操作系统,适用于不同开发阶段的系统调试需求和产品发布,尤其是对Linux的支持最为强大;支持多种目标板环境参数FLASH、NVRAM、EEPROM等多种存储方式;CRC32校验可以检查FLASH、RAMDISK镜像文件中的内核是否完好;设备驱动串口、SDRAM、FLASH、Ethernet、LCD、NVRAM、EEPROM、键盘、USB、PCMCIA、PCI、RTC等驱动支持;开机自检功能SDRAM、FLASH大小自动检测;SDRAM故障检测;处理器型号。3、常用命令uboot命令很多,下面仅列出用于网络启动的命令:4、配置参数示例以下是从网络下载内核并挂载网络nfs的示例。1)ubuntu环境ubuntuip:192.168.6.186nfs配置:配置文件如下:/etc/exports配置信息如下:nfs2)开发板设置开发板ip:192.168.6.187配置命令:setenvipaddr192.168.6。187;板子的ipsetenvserverip192。168.6.186;虚拟机的ipsetenvgatewayip192.168.1.1;网关保存环境;保存配置,加载内核和设备树setenvbootcmdtftp41000000uImage\;tftp42000000exynos4412-fs4412.dtb\;bootm41000000-42000000bootcmd,首先执行如下启动参数:ubo命令。从tftp服务器下载内核镜像uImage到地址41000000,设备树文件exynos4412-fs4412.dtb到42000000,通过命令bootm加载启动内核。mountnfssetenvbootargsroot=/dev/nfsnfsroot=192.168.6.186:/rootfsrwconsole=ttySAC2,115200init=/linuxrcip=192.168.6.187mountnfsfilesystemroot=/dev/nfsnfsroot=192.168.6.186:/rootfs1692nfs目录是/rootfs,rw文件系统操作权限可改写console=ttySAC2,115200串口名称和波特率init=/linuxrc内核启动后运行的进程为linuxrcip=192.168.6.187开发板地址2.exynos-4412Socbootsequence要了解exynos-4412的启动顺序,首先要了解soc的内存布局。1.exynos-4412的内存布局通常是厂商在设计SoC内存时指定的。对于用户来说,我们无法改变它。我们只关心与启动相关的地址。iROM在soc里面,厂家在出厂的时候已经固化了一个特定的程序。iROM中的程序不能被用户更改,位于SOC内部,用于驱动RAM,大容量RAM需要接控制器2.BootingSequence不同厂家的bootsequence不一样,本文主要以三星的exynos-4412soc为例,说明一下基于本板的uboot启动顺序。根据上图,系统启动的大致顺序:iROM在SOC内部,是一个64KB的ROM,汇集了系统启动所必需的一些功能。例如:时钟、堆栈。iROM负责将BL1映像从特殊的引导外设加载到soc内部的256KBSRAM中。激活的外设由操作按钮决定。根据不同key的值,iROM会对bl1的image做不同的检查。BL1初始化系统时钟和DRAM控制器,然后将操作系统映像从引导外设加载到DRAM。根据开始按钮的值,BL1将对操作系统执行不同的检查。启动完成后,BL1跳转到操作系统(内核)。iROM会根据不同的OM管脚选择不同的启动设备,相应的OM寄存器需要提供相应的启动信息。三、内核启动过程概述1.内核启动过程概述uboot启动过程如上图所示:设备上电后,首先执行iROM中的工厂代码,然后初始化必要的硬件执行uboot。通常,内核和设备树文件是程序放入flash启动后,往往是先从flash启动,运行uboot。Step1:初始化硬件(svc模式栈,时钟,内存,串口)Step2:自搬:将uboot从flash复制到RAM中,跳转到RAM执行剩余的uboot代码Step3:将kernel复制到RAM,执行内核,并将控制权交给内核。2.内核启动的详细过程开发板从上电到内核启动的过程4.uboot启动过程的详细代码解释1.lds文件如果想了解整个uboot工程的代码流程,必须先了解链接脚本【链接脚本参考《7. 从0开始学ARM-GNU伪指令,lds使用》】。这个文件决定了uboot最终生成的镜像文件和各个段的布局。uboot链接脚本如下:u-boot-2013.01/arch/arm/cpu/u-boot.lds文件内容:26OUTPUT_FORMAT("elf32-littlearm","elf32-littlearm","elf32-littlearm")27OUTPUT_ARCH(arm)28ENTRY(_start)29SECTIONS30{31.=0x00000000;3233.=ALIGN(4);34.text:35{36__image_copy_start=.;37CPUDIR/start.o(.text*)38*(.text*)39}4041.=对齐(4);42.rodata:{*(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*)))}4344.=ALIGN(4);45.data:{46*(.data*)47}4849.=ALIGN(4);5051.=.;5253.=ALIGN(4);54.u_boot_list:{55#include56}5758.=ALIGN(4);5960__image_copy_end=.;6162.rel.dyn:{63__rel_dyn_start=.;64*(.rel*)65__rel_dyn_end=.;66}6768.dynsym:{69__dynsym_start=.;70*(.dynsym)71}7273_end=.;7475/*76*弃用:这个MMU部分被pxaatpresent使用但77*不应该被新板使用/CPUs.78*/79.=ALIGN(4096);80.mmutable:{81*(.mmutable)82}8384.bss__rel_dyn_start(OVERLAY):{85__bss_start=.;86*(.bss*)87.=ALIGN(4);88__bss_end__=.;89}9091/DISCARD/:{*(.dynstr*)}92/DISCARD/:{*(.dynamic*)}93/DISCARD/:{*(.plt*)}94/DISCARD/:{*(.interp*)}95/DISCARD/:{*(.gnu*)}96}97核心内容解释:27OUTPUT_ARCH(arm):镜像运行在arm架构上28ENTRY(_start)onthehardware:程序的入口是_start29SECTIONS30{31.=0x00000000;:程序的链接地址,不是运行地址【uboot必须是位置无关的代码】34.text:35{36__image_copy_start=.;:宏对应整个程序编译后的首地址,自移动代码的起始位置37CPUDIR/start.o(.text*):第一个目标文件CPUDIR/中的代码段start.o38*(.text*):剩余目标文件代码段39}60__image_copy_end=.;:自移动代码结束后BSS全局未初始化变量和初始化为0的变量所在段:84.bss__rel_dyn_start(OVERLAY):{85__bss_start=.;88__bss_end__=.;89}2.uboot启动代码流程概述代码只分析uboot命令行,函数main_loop()位置3.启动代码详细分析_start入口位于以下文件:u-boot-2013.01/arch/arm/cpu/armv7/start.S第一阶段:第二阶段第二阶段代码从_main开始:以上代码详细解释,请与B站视频同步学习。五.uboot启动的几个关键知识点1、如何判断第一条机器指令的位置?链接脚本决定了内存的布局。uboot链接脚本如下:u-boot-2013.01/arch/arm/cpu/u-boot.lds文件内容:28ENTRY(_start)29SECTIONS30{31.=0x00000000;32uboot的入口为_start链接地址为0x000000002。如何移动uboot代码?代码位于:u-boot-2013.01/arch/arm/cpu/armv7/start.S移动代码如下:ENTRY(relocate_code)movr4,r0/*saveaddr_sp*/movr5,r1/*saveaddrofgd*/movr6,r2/*saveaddrofdestination*/adrr0,_startcmpr0,r6moveqr9,#0/*norelocation.relocationoffset(r9)=0*/beqrelocate_done/*skiprelocation*/movr1,r6/*r1<-scratchforcopy_loop*/ldrr3,_image_copy_end_ofsaddr2,r0,r3/*r2<-sourceendaddress*/copy_loop:ldmiar0!,{r9-r10}/*copyfromsourceaddress[r0]*/stmiar1!,{r9-r10}/*copytotargetaddress[r1]*/cmpr0,r2/*untilsourceendaddress[r2]*/blocopy_loop详见第4章第3节。3、uboot中如何判断开机是关机状态还是睡眠状态?Theboard/samsung/fs4412/lowlevel_init.Scodeisasfollows:41lowlevel_init:54/*AFTRwakeupreset*/55ldrr2,=S5P_CHECK_DIDLE56cmpr1,r257beqexit_wakeup5859/*LPAwakeupreset*/60ldrr2,=S5P_CHECK_LPA61cmpr1,r262beqexit_wakeup6364/*Sleepwakeupreset*/65ldrr2,=S5P_CHECK_SLEEP66cmpr1,r267beqwakeup_reset112wakeup_reset:113blsystem_clock_init114blmem_ctrl_asm_init115bltzpc_init116117exit_wakeup:118/*Loadreturnaddressandjumptokernel*/119ldrr0,=(EXYNOS4_POWER_BASE+INFORM0_OFFSET)120121/*r1=physicaladdressofexynos4210_cpu_resumefunction*/122ldrr1,[r0]123124/*Jumptokernel*/125movp??c,r1Ascanbeseenfromtheabove,whenthe手机由于各种原因进入休眠,它会保护当前程序执行的上下文,并写入指定的数据到一些pmic寄存器中,以表明第二次是因为什么原因进入休眠。手机并没有完全关机,而是处于低功耗模式。这个时候bootRAM还有数据,所以这次boot之后,只要从专用寄存器中读取相应的值,就可以知道是什么原因休眠了,然后恢复休眠前的上下文。3、uboot代码移动到ram后,代码的运行地址发生了变化。如何保证程序跳转不会出错呢?.rel.dyn除了保证uboot代码是基于地址无关的,还帮我们解决了。其实主要是编译器帮我们做了很多工作。有关与位置无关的代码,请参阅《15. 从0学ARM-什么是位置无关码?》。4.设备启动时,可以直接从ram启动。如何知道当前是从flah还是ram启动的?文件:board/samsung/fs4412/lowlevel_init.S代码:lowlevel_init:85/*86*IfU-bootisalreadyrunninginram,noneedtorelocateU-Boot.87*MemorycontrollermustbeconfiguredbeforerelocatingU-Boot88*inram.89*/90ldrr0,=0x0ffffff/*r0<-MaskBits*/91bicr1,pc,r0/*pc<-currentaddrofcode*/92/*r1<-unmaskedbitsofpc*/93ldrr2,_TEXT_BASE/*r2<-originalbaseaddrinram*/94bicr2,r2,r0/*r2<-unmaskedbitsofr2*/95cmpr1,r2/*comparer1,r2*/96beq1f/*r0==r1thenskipsdraminit*/principle:RAM地址空间为:0x40000000-0xA00000000xA0000000-0x00000000iROM/iRAM地址的bits:28-31全为0,所以你只需要在执行lowlevel_init时读取pc的值,判断bits:28-31是否为0即可知道代码当前是否在RAM中运行。