U-Boot,全称 Universal Boot Loader,是遵循GPL条款的开放源码项目。
(1)uboot就是universal bootloader(通用启动代码),通用的意思就是在各种地方都可以用。所以说uboot具有可移植性。
(2)uboot具有可移植性并不是说uboot在哪个开发板都可以随便用,而是说uboot具有在源代码级别的移植能力,可以针对多个开发板进行移植,移植后就可以在这个开发板上使用。
首先要说明嵌入式Linux相关设备的启动流程:
嵌入式系统上电后先执行uboot,然后uboot负责初始化DDR,初始化Flash,然后将OS从Flash中读取到DDR中,然后启动OS(OS启动后uboot就无用了)
所以要使用uboot来完成OS启动前的相关准备。(在goole这个问题的时候,看到了知乎中有网友提问“问什么不把uboot和Linux系统写在一起”,结合网友回答,主要是为了提高Linux中的代码的代码可移植性,尽量将硬件相关的代码分开来,这样就可以在内核中屏蔽掉硬件的相关配置,从而提高内核的兼容性。)
1、自身可开机直接启动
2、能引导操作系统内核启动并给内核传参
3、能提供系统部署功能
4、能进行SoC级和板级硬件管理
(1)uboot中实现了一部分硬件的控制能力(uboot初始化了部分硬件),因为uboot为了实现一些任务必须让这些硬件工作,譬如uboot要实现刷机必须能驱动iNand,譬如uboot在刷机时要在LCD上显示进度条就必须能驱动LCD,譬如能通过串口提供操作界面就必须驱动串口。譬如uboot要实现网络功能,就必须驱动网卡芯片。
(2)SoC级就是SoC内部外设,板级就是SoC外开发板上的硬件(譬如网卡、iNand)
本代码来自朱老师物联网中嵌入式核心课程中提供的九鼎的x210开发板的uboot。
使用Source Insight查看代码。
文件夹分析
文件分析
.gitignore:github版本管理
arm_config.mk:后缀是.mk,是一个Makefile文件,将来在某个Makefile中会去调用它。
CHANGELOG
Changelog_Samsung
CHANGELOG-before-U-Boot-1.1.5
修改记录文件,该文件记录了这个uboot项目的版本变迁以及每个版本较上一个版本修改的记录。正式的项目都有这写记录。
config.mk:和arm_config.mk类似
COPYING:版权声明
CREDITS:鸣谢
image_split:是一个脚本,是用来分割uboot.bin
MAINTAINERS:维护者,当前在参与维护uboot源码的参与者
MAKEALL:一个脚本,帮助编译uboot
Makefile:是uboot源代码的主Makefile,将来整个uboot被编译时就是用这个Makefile管理编译的,所以在研究uboot配置时需要关注。
mk:快速编译的脚本,其实就是先清理然后配置然后编译而已。
mkconfig:uboot配置阶段的主要配置脚本,uboot的可以执行很大程度就是靠这个配置脚本。
mkmovi:一个脚本,和iNand/SD启动有关
README
rules.mk:是我们uboot的makefile使用的规则。
(代码位置:uboot_jiuding/cpu/s5pc11x/start.S)
主要思路:部分硬件初始化—>加载完整的uboot到DDR—>跳转到第二阶段入口开始执行
需要完成的工作:
(1)构建异常向量表
(2)设置CPU位SVC模式
(3)关看门狗
(4)开发板供电的置锁(维持供电)
(5)始终初始化、DDR初始化
(6)串口初始化,打印OK
(7)进行重定位
(8)建立映射表,并开启MMU
(9)设置栈,进行远跳转到第二阶段。
主要代码:
start.S汇编文件,涉及到特定硬件设备的读写寄存器操作以及特定体系结构的汇编语言
lowlevel_init.S底层硬件的初始化
#include
#include
#if defined(CONFIG_ENABLE_MMU)
#include
#endif
#include
#ifndef CONFIG_ENABLE_MMU
#ifndef CFG_PHY_UBOOT_BASE
#define CFG_PHY_UBOOT_BASE CFG_UBOOT_BASE
#endif
#endif
#include
config.h文件是在include目录下,这个文件不是源码中本身存在的文件,而是在配置中自动生成的文件。这个文件内容又包含了一个头文件:#include
#include
#include
uboot中本身是没有asm目录,asm是我们配置时创造的符号链接,实际指向的就是asm-arm。
#if defined(CONFIG_EVT1) && !defined(CONFIG_FUSED)
.word 0x2000
.word 0x0
.word 0x0
.word 0x0
#endif
(1)在SD卡/Nand启动等整个镜像开头需要16字节的校验头。(mkv210image.c就是为了计算这个校验头)。以前做裸机程序时根本没考虑这16字节的校验头,因为:如果是usb直接下载启动则不需要16字节的校验头:如果是SD卡启动mkv210image.c中会给原镜像前加16字节校验头。
(2)uboot这里start.S中在开头位置放了16字节的填充占位,这个占位的16字节只是保证正式的image的头部确实有16字节,但是这16字节的内容是不对的,还是需要后面去计算校验和然后重新填充的。
3.异常向量表的构建
.globl _start //定义一个全局标号——start
_start: b reset //_start处的内容为跳转到reset标号开始执行
//以下7条ldr pc,x加上b reset共八条指令组成了异常向量表
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq
//定义了一个word类型的变量undefined_instruction
_undefined_instruction:
.word undefined_instruction
//定义了一个word类型的变量software_interrupt
_software_interrupt:
.word software_interrupt
//定义了一个word类型的变量prefetch_abort
_prefetch_abort:
.word prefetch_abort
//定义了一个word类型的变量data_abort
_data_abort:
.word data_abort
//定义了一个word类型的变量not_used
_not_used:
.word not_used
//定义了一个word类型的变量irq
_irq:
.word irq
//定义了一个word类型的变量fiq
_fiq:
.word fiq
//now 16*4=64
_pad:
.word 0x12345678
.global _end_vect
_end_vect:
.balignl 16,0xdeadbeef
(1).balignl 16,0xdeadbeef
这句指令是让当前地址对其排布,如果当前地址不对齐,则地址自动向后走直到对齐,并且向后走的内存由0xdeadbeef填充。
(2)0xdeadbeef这是一个十六进制的数字。
(3)为什么要对齐访问,有时候是效率的要求,有时候是硬件的特殊要求。
5. TEXT_BASE
_TEXT_BASE:
.word TEXT_BASE
TEXT_BASE就是Makefile时中的那个配置阶段的TEXT_BASE,其实就是我们链接时制定的uboot的链接地址。(值就是c3e00000)
5.MMU相关变量的定义,在此uboot没有用到
//CFG _PHY_UBOOT_BASE 33e00000 uboot在DDR中的物理地址
_TEXT_PHY_BASE:
.word CFG_PHY_UBOOT_BASE
.globl _armboot_start
_armboot_start:
.word _start
6. 设置cpu为svc模式
/*
* the actual reset code
*/
reset:
/*
* set the cpu to SVC32 mode and IRQ & FIQ disable
*/
@;mrs r0,cpsr
@;bic r0,r0,#0x1f
@;orr r0,r0,#0xd3
@;msr cpsr,r0
msr cpsr_c, #0xd3 @ I & F disable, Mode: 0x13 - SVC
(1)msr cpsr_c, #0xd3将CPU设置为禁止FIQ IRQ,ARM状态,SVC模式。
(2)其实ARM CPU在复位时默认就会进入SVC模式,但是这里还是使用软件将其置为SVC模式。整个uboot工作时CPU一直处于SVC模式。
7. 设置L1、L2cache和MMU
bl set_l2cache_auxctrl
bl enable_l2cache
bl disable_l2cache
bl set_l2cache_auxctrl_cycle
bl enable_l2cache
(1)bl disable_l2cache //禁止L2cache
(2)bl set_l2cache_auxctrl_cycle //L2cache相关初始化
(3)bl enable_l2cache //使能L2cache
(4)刷新L1 cache的icache和dcache
(5)关闭MMU
总结:上面这5步都是和CPU的cache和MMU相关。
8. 识别并暂存启动介质选择
/* Read booting information */
ldr r0, =PRO_ID_BASE
ldr r1, [r0,#OMR_OFFSET]
bic r2, r1, #0xffffffc1
(1)从哪里启动时由SoC的OM5:OM0这6个引脚的高低电平决定的。
(2)实际上在210内部有个寄存器(地址时0xE0000004),这个寄存器中的值时硬件根据OM引脚的设置二自动设置值,反应的就是OM引脚的接法也就是真正的启动介质是谁
(3)我们代码中可以通过读取这个寄存器的值然后判断其值来确定当前选中的启动介质是Nand还是SD还是其它。
(4)
ldr r0, =PRO_ID_BASE
ldr r1, [r0,#OMR_OFFSET]
bic r2, r1, #0xffffffc1
将PRO_ID_BASE + OMR_OFFSET
(这是一个启动方式寄存器,从中可以得到上电时的启动状态)地址处的读取启动信息,值0x0~0x6分别对应几种不同的nandflash启动,0xc对应MMC/SD启动。当确定是某一种启动方式后,将其对应的十六进制存放于INFORM3寄存器中。
在R2寄存器中存储了一个数字,这个数字等于某个特定值就表示SD启动,等于另一个特定值时表示从Nand启动.
(5)第260行
moveq r3, #BOOT_MMCSD
这里给r3中赋值#BOOT_MMCSD(其值为0x03),
10. 第一次设置栈(SRAM中的栈)并调用lowlevel_init
/*
* Go setup Memory and board specific bits prior to relocation.
*/
ldr sp, =0xd0036000 /* end of sram dedicated to u-boot */
sub sp, sp, #12 /* set stack */
mov fp, #0
bl lowlevel_init /* go setup pll,mux,memory */
(1)这是第一次设置栈,这次设置栈是在SRAM中进行的,因为当前整个代码还是在SRAM中运行,此时DDR还未被初始化还不能用,栈地址0xd0036000是自己指定的,指定的原则就是这块空间只给栈用,不会给别人占用。
(2)在调用函数前初始化栈,主要原因是在被调用的函数内还有再次调用函数,而BL只会将返回地址存储到LR中,但是我们只有一个LR,所以在第二层调用函数前要将LR入栈,否则函数返回第一层地址就丢失了。
从这儿开始,进入了lowlevel_init函数:
11. 检查复位状态
/* check reset status */
ldr r0, =(ELFIN_CLOCK_POWER_BASE+RST_STAT_OFFSET)
ldr r1, [r0]
bic r1, r1, #0xfff6ffff
cmp r1, #0x10000
beq wakeup_reset_pre
cmp r1, #0x80000
beq wakeup_reset_from_didle
(1)复杂cpu允许多种复位情况,譬如直接冷上电、热启动、睡眠(低功耗)状态下的唤醒,这些情况都属于复位。我们在复位代码中要去检测复位状态,来判断到底是那种情况。
(2)判断哪种复位状态的意义在于:冷上电时DDR需要初始化才可以使用,热启动或者低功耗启动DDR可以直接使用。
12. IO状态恢复
/* IO Retention release */
ldr r0, =(ELFIN_CLOCK_POWER_BASE + OTHERS_OFFSET)
ldr r1, [r0]
ldr r2, =IO_RET_REL
orr r1, r1, r2
str r1, [r0]
这个和上一个和主线启动代码都无关,因此暂时不用去管他。
13. 看门狗
/* Disable Watchdog */
ldr r0, =ELFIN_WATCHDOG_BASE /* 0xE2700000 */
mov r1, #0
str r1, [r0]