本文章首发地址:https://www.lgfccl.xyz 随时down 机,转载注明出处谢谢
U-Boot,全称 Universal Boot Loader,是遵循 GPL 条款的开放源码项目。从 FADSROM、8xxROM、PPCBOOT 逐步发展演化而来。其源码目录、编译形式 与 Linux 内核很相似,事实上,不少 U-Boot 源码就是相应的 Linux 内核源程序的简化,尤其是一些设备的驱动程序,如网络设备、flash、ddr等,这从 U-Boot 源码的注释中能体现这一点。
U-Boot 不仅仅支持嵌入式 Linux 系统的引导,它还支持如NetBSD, VxWorks, QNX, RTEMS, ARTOS, LynxOS 嵌入式OS。硬件支持包括 MIPS、 x86、ARM、Powerpc、Zynq 等诸多常用处理器。 这两个特点正是 U-Boot 项目的开发目标,即支持尽可能多的嵌入式处理器和嵌 入式操作系统。
这个项目起源于 Magnus Damm.[1] 在 8xx PowerPC 架构下写的引导加载程序: 8xxROM。1999 年十月,Wolfgang Denk 将项目移转到 SourceForge.net,但 SourceForge.net 不允许数字开头的项目名称,所以改名为 PPCBoot。PPCBoot 在 2000年 7 月 19 日第一次公开发布 0.4.1 版。也正是因此,U-Boot对PPC支持最为丰富。
开放源码
支持多种嵌入式OS,如 Linux、NetBSD、VxWorks、QNX等
支持多种处理器架构,如PowerPC、ARM、x86、MIPS、XScale、Zynq(ARM);
较高的可靠性和稳定性;
丰富的设备驱动源码,如串口、以太网、SDRAM、FLASH等;
较为丰富的开发调试文档与强大的网络技术支持;
(
以上关于目录结构的说明只适应u-boot-2010.06之前版本。u-boot-2010.06之后目录结构改变
cpu与lib_arch合二为一,命名arch
增加include folder
分离出通用库文件夹lib
)
U-Boot 可支持的主要功能如下:
- 系统引导,Linux 支持最为强劲;
- 支持NFS挂载、RAMDISK(压缩或非压缩)形 式的根文件系统;
- 支持NFS挂载、从FLASH中引导压缩或非压缩系统内核;
- 支持目标板环境参数多种存储方式,如FLASH、NVRAM、EEPROM;
- CRC32 校验,可校验 FLASH 中内核、RAMDISK 镜像文件是否完好;
- 设备驱动 串口、SDRAM、FLASH、以太网 、USB、PCMCIA、PCI、RTC 等驱动支持;
- 上电自检功能 SDRAM、FLASH 大小自动检测;SDRAM 故障检测;CPU型号;
- 特殊功能 XIP 内核引导;
大多数BootLoader都分为stage1和stage2两大部分,U-boot也不例外。依赖于cpu体系结构的代码(如设备初始化代码等)通常都放在stage1且可以用汇编语言来实现,而stage2则通常用C语言来实现,这样可以实现复杂的功能,而且有更好的可读性和移植性。
1、 stage1(start.s代码结构)
U-boot的stage1代码通常放在start.s文件中,它用汇编语言写成,其主要代码部分如下:
(1) 定义入口。由于一个可执行的image必须有一个入口点,并且只能有一个全局入口,通常这个入口放在rom(Flash)的0x0地址,因此,必须通知编译器以使其知道这个入口,该工作可通过修改连接器脚本来完成。
(2)设置异常向量(exception vector)。
(3)设置CPU的速度、时钟频率及中断控制寄存器。
(4)初始化内存控制器 。
(5)将rom中的程序复制到ram中。
(6)初始化堆栈 。
(7)转到ram中执行,该工作可使用指令ldrpc来完成。
2、 stage2(C语言代码部分)
lib_arm/board.c中的start armboot是C语言开始的函数,也是整个启动代码中C语言的主函数,同时还是整个u-boot(armboot)的主函数,该函数主要完成如下操作:
(1)调用一系列的初始化函数。
(2)初始化flash设备。
(3)初始化系统内存分配函数。
(4)如果目标系统拥有nand设备,则初始化nand设备。
(5)如果目标系统有显示设备,则初始化该类设备。
(6)初始化相关网络设备,填写ip,c地址等。
(7)进入命令循环(即整个boot的工作循环),接受用户从串口输入的命令,然后进行相应的工作。
如下举例,由于此服务器基本处于爹妈不维护状态,所以建议直接浏览器下载压缩包,或者wget下载,如果用git clone下载很慢。
> wget http://git.freescale.com/git/cgit.cgi/auto/u-boot.git/snapshot/u-boot-2016.01_bsp14.0.tar.gz
> tar xvf u-boot-2016.01_bsp14.0.tar.gz
> cd u-boot-2016.01_bsp14.0
> ls -alh
- 如上获取了官方支持的最新的U-Boot源代码,接下来获取交叉编译工具(不通内核架构工具不一样)方式也有3种:
source ./fsl-setup-poky -h 查看支持的参考Demo板卡
source ./fsl-setup-poky -m 选择你的处理器如P2020RDB
bitbake fsl-toolchain
cd build_<machine>_release/tmp/deploy/sdk
./fsl-networking-eglibc-<host-system>-<core>-toolchain-<release>.sh
默认安装/opt/下直接允许即可
寻求技术支持索要:最直接,最快速,推荐。
我分享在github的SDK编译出的文件,避免下载Yocto SDK工具。
wget https://raw.githubusercontent.com/fafactx/powerpc-corss-toolchain-all/master/fsl-networking-eglibc-x86_64-ppce500v2-toolchain-QorIQ-SDK-V1.6.sh
P2020芯片原Freescale出过两个Demo板卡,我们取其中P2020RDB-PC 进行参考即可
> cd u-boot-2016.01_bsp14.0
> source /opt/fsl-networking/QorIQ-SDK-V1.6/environment-setup-ppce500v2-fsl-linux-gnuspe;
> unset LDFLAGS;
> make distclean;make P2020RDB-PC_config;make -j8;
> powerpc-fsl-linux-gnuspe-objdump -j .text -l -C -S u-boot >uboot.map;
到此,你应该可以获得bin,大小根据头文件include/configs/p1_p2_rdb_pc.h里的 #define CONFIG_SYS_TEXT_BASE 0xeff40000决定,这个地址到0xefffffff正好是768K大小。这里你看你会有疑问,e500V2架构Core第一条指令不是去0xFFFFFFFC去取指令吗,且一般是跳转到前4K Page Size处,没错这里就是由于两个default决定的了。
请参考:P2020RM的4.3.3章节:Boot page tranlation,E500CORERM 1.5.1 章节 Initial Instruction Fetch
我们这里不展开讨论TLB以及MMU,LAW的概念,后续我会根据时间继续进行介绍。
如上,你已经获取了u-boot.bin,通过code warrior jtag或者usb code warrior jtag 烧写程序到Nor的0xfff40000地址即可启动,可能是乱码,根据至于code warrior 的永久license和如何根据自己Nor Flash生成tcl,生成Nor的自定义型号,这里不展开讨论,后续看时间专门出一片code warrior 工具使用介绍(自己看help也够用了),不过这个cw工具官方已经不在维护,建议不要过度依赖。cw的后台server是ccs服务程序,这个程序又可以写一片博客了,看时间进行总结吧。
Include/configs/p1_p2_rdb_pc.h —>修改sys clk,ddr clk,nor law reg,ddr2(3) 参数等。
本部分涉及的代码非常多,所以只能对关键代码进行解释,还有更多部分涉 及到 POWERPC 体系结构、MMU 工作原理等,这里对这些内容不再叙述,但是 使用U-Boot过程中,上述内容是必备的,可以参考《POWERPC 体系结构》与 《MMU 工作原理》等手册相关内容。
打开文件,vim arch/powerpc/cpu/mpc85xx/start.S 文件开始定义了:
CPU 复位后做了三件事情,1清寄存器、2设置异常向量表、3设置更多更大TLB供CPU看见地址:
/*
* e500 Startup -- after reset only the last 4KB of the effective
* address space is mapped in the MMU L2 TLB1 Entry0. The .bootpg
* section is located at THIS LAST page and basically does three
* things: clear some registers, set up exception tables and
* add more TLB entries for 'larger spaces'(e.g. the boot rom) to
* continue the boot procedure.
* Once the boot rom is mapped by TLB entries we can proceed
* with normal startup.
*
*/
.section .bootpg,"ax"
.globl _start_e500
_start_e500:
/* Enable debug exception */
li r1,MSR_DE
mtmsr r1
/*
* If we got an ePAPR device tree pointer passed in as r3, we need that
* later in cpu_init_early_f(). Save it to a safe register before we
* clobber it so that we can fetch it from there later.
*/
mr r24, r3
接下来就是根据不同芯片的ERRATUM 进行操作,和关闭L2,L1禁止cache的操作,源码很简单这里不粘贴,只捡最关键几点,接下来是初始化异常向量表,告诉CPU出现下列异常应该怎么办:
/* Interrupt vectors do not fit in minimal SPL. */
#if !defined(MINIMAL_SPL)
/* Setup interrupt vectors */
lis r1,CONFIG_SYS_MONITOR_BASE@h
mtspr IVPR,r1
li r4,CriticalInput@l
mtspr IVOR0,r4 /* 0: Critical input */
li r4,MachineCheck@l
mtspr IVOR1,r4 /* 1: Machine check */
li r4,DataStorage@l
mtspr IVOR2,r4 /* 2: Data storage */
li r4,InstStorage@l
mtspr IVOR3,r4 /* 3: Instruction storage */
li r4,ExtInterrupt@l
mtspr IVOR4,r4 /* 4: External interrupt */
li r4,Alignment@l
mtspr IVOR5,r4 /* 5: Alignment */
li r4,ProgramCheck@l
mtspr IVOR6,r4 /* 6: Program check */
li r4,FPUnavailable@l
mtspr IVOR7,r4 /* 7: floating point unavailable */
li r4,SystemCall@l
mtspr IVOR8,r4 /* 8: System call */
/* 9: Auxiliary processor unavailable(unsupported) */
li r4,Decrementer@l
mtspr IVOR10,r4 /* 10: Decrementer */
li r4,IntervalTimer@l
mtspr IVOR11,r4 /* 11: Interval timer */
li r4,WatchdogTimer@l
mtspr IVOR12,r4 /* 12: Watchdog timer */
li r4,DataTLBError@l 等
直至后就是cache 当临时stack供C语言代码运行,新tlb 映射,u-boot C代码出事后ddr,基本设置,copy自身代码到ram(ddr)等等。这部分代码基本不会出什么问题,后续就到了最关键的relocate 技术了。
所谓代码重定位技术就是对于链接时地址已经固定的数据段与代码段,在进行代码搬移后,需要对这些数据段与代码段的地址进行修改,使程序能够正确寻 址的技术。在 bootloader 中,通常有 GOT与 PLT两种实现方法。其基本原理就是利 用数据段与代码段的相对位置不变实现的。代码重定位技术也应用于 OS 中,OS 的加载器也实现了 PIC,这里不做算法研究。
我们知道对于全局变量的访问,首先要获取该变量的全局地址,而这个地址是编译链接时生成的一个地址,对于没有-fpic 选项编译的程序,此值是一个静态的地址,当程序从 flash 重新定位到 ram 后,访问的地址仍然在 Flash 中,那 么如何读写呢?
这就需要一种机制来保证获取的全局变量的地址是一个动态的值,也是说当 程序重定位到 RAM 中后,这个地址也需要随之修正。而 U-boot 正是将所有全局 符号的地址保存在一个表中 GOT,程序访问变量时先从这个表中动态获取该符 号的地址,而这个地址在重定位时已经修正过了,从而保证能从重定位后的地址 处正确访问全局变量。
总的来讲,U-Boot 依靠维护 GOT 表来实现,在 GOT 表中存放一些全局 label表,这些表项记录重要的地址。运行在 Flash 时,GOT 表中存放的是编译时 全局 label 的值(地址);当 U-Boot 运行时检测 RAM 大小进行代码搬运之后,利用代码搬运前后产生的地址偏移对(相对偏移)GOT表中的各个表项值进行更新,使其记录 RAM 中的相应的地址。 这样代码运行时不会出现代码/变量地址出错的问题。
.globl in_ram
in_ram:
/*
* Relocation Function, r12 point to got2+0x8000
*
* Adjust got2 pointers, no need to check for 0, this code
* already puts a few entries in the table.
*/
li r0,__got2_entries@sectoff@l
la r3,GOT(_GOT2_TABLE_)
lwz r11,GOT(_GOT2_TABLE_)
mtctr r0
sub r11,r3,r11
addi r3,r3,-4
1: lwzu r0,4(r3)
cmpwi r0,0
beq- 2f
add r0,r0,r11
stw r0,0(r3)
2: bdnz 1b
其实可以这么理解,一旦有全局变量的地方都需要进行地址换算,这个U-boot原始位置与最新位置,在调用relocate函数之前已经算好,并传参数给汇编代码。其实就是公式:
N e w G O T P T R − O l d G O T P T R = D e s t i n a t i o n A d d r e s s + ( O f f s e t ) ; New GOTPTR-Old GOTPTR=Destination Address+(Offset); NewGOTPTR−OldGOTPTR=DestinationAddress+(Offset);
N e w G O T P T R − O l d G O T P T R = U B o o t 目 的 地 址 − U B o o t 源 地 址 ; New GOTPTR-Old GOTPTR=UBoot目的地址-UBoot源地址; NewGOTPTR−OldGOTPTR=UBoot目的地址−UBoot源地址;
这里Offset可以是负数,具体参考Uboot源代码。
/*
* void relocate_code (addr_sp, gd, addr_moni)
*
* This "function" does not return, instead it continues in RAM
* after relocating the monitor code.
*
* r3 = dest
* r4 = src
* r5 = length in bytes
* r6 = cachelinesize
*/
.globl relocate_code
relocate_code:
mr r1,r3 /* Set new stack pointer */
mr r9,r4 /* Save copy of Init Data pointer */
mr r10,r5 /* Save copy of Destination Address */
GET_GOT
#ifndef CONFIG_SPL_SKIP_RELOCATE
mr r3,r5 /* Destination Address */
lis r4,CONFIG_SYS_MONITOR_BASE@h /* Source Address */
ori r4,r4,CONFIG_SYS_MONITOR_BASE@l
lwz r5,GOT(__init_end)
sub r5,r5,r4
li r6,CONFIG_SYS_CACHELINE_SIZE /* Cache Line Size */
/*
* Fix GOT pointer:
*
* New GOT-PTR = (old GOT-PTR - CONFIG_SYS_MONITOR_BASE) + Destination Address
*
* Offset:
*/
sub r15,r10,r4
/* First our own GOT */
add r12,r12,r15
*/
sub r15,r10,r4
/* First our own GOT */
add r12,r12,r15
/* the the one used by the C code */
add r30,r30,r15
U-Boot发展到现在,已经接近 linux 系统。它具备了丰富的命令,可以帮助 用户实现调试与系统环境设置等。下面我仅介绍下几个常用的命令。1、HELP 命令
在终端输入 help 命令,将会显示系统支持的命令及简要说明。
help == ?
? - alias for ‘help’
base - print or set address offset
bdinfo - print Board Info structure
boot - boot default, i.e., run ‘bootcmd’ - boot default, i.e., run ‘bootcmd’
flinfo. - print flash bank and erase infotftp - download image from serverip via net
rx - receive by xmodel
setenv editenv savenv and so on
U-BOOT 是一个非常好用的嵌入式 bootloader。它已经成为目前嵌入式linux的御用boot,内容非常丰富,但结构混乱,看下common里面的board_r和board_f就能可见一斑,本文只是从整体上对其启动部分原理做了简要陈述。关于 u-boot 程序开发,已经超出了 u-boot 启动部分内容,并且需要很多外围硬件的相关知识,所以没有进行讲解。
本文参 考了网络相关文章,并进行了实践验证,以及Powerpc 相关手册,但由于本人技术水平有限,难免存在 理解错误,望大家指正。