u-boot分析与使用—源码分析
- 硬件平台:韦东山嵌入式Linxu开发板(S3C2440.v3)
- 软件平台:运行于VMware Workstation 12 Player下UbuntuLTS16.04_x64 系统
- 参考资料:《嵌入式Linux应用开发手册》、《嵌入式Linux应用开发手册第2版》
- 开发环境:Linux 2.6.22.6 内核、arm-linux-gcc-3.4.5-glibc-2.3.6工具链、u-boot-1.1.6
目录
u-boot分析与使用—源码分析 - 一、前言
- 二、第一阶段
- 1、设置CPU为管理模式
- 2、关看门狗
- 3、关中断
- 4、设置时钟
- 5、根据情况判断是否初始化SDARM
- 6、设置栈(只有设置栈之后才可以调用C函数)
- 7、根据情况判断是否提前清bss段
- 8、重定位
- 9、调用start_armboot(u-boot的第二阶段)
- 10、总结
- 三、第二阶段分析
- 1、进行一系列的初始化工作
- 2、初始化NOR FLASH
- 3、在内存中分配堆区
- 4、初始化NAND FLASH
- 5、设置环境变量
- 6、进入到`main_lopp()`死循环过程
- 6.1 资源的加载与版本号的设置
- 6.2 设置超时时间并打印一些信息
- 6.3 获取环境变量`bootcmd`,以便后面内核的启动
- 6.4 在超时时间内判断键盘是否有输入,无的话
- 6.5 在超时时间内判断键盘是否有输入,有的话,此时如果想启动内核的话,需要通过输入命令实现
- 7、总结
- 8、分析`run_command()`
- 四、总结
一、前言
在linux系统下,执行make
对u-boot
进行编译时,输出以下信息:
可以看到,除了关注脚本文件,也要关注cpu/arm920t/start.o
文件。
通过前面的分析可知,u-boot的主要目的如下:
- 从FLASH读出内核
- 启动内核
抱着这个目的,我们从cpu/arm920t/start.o
文件入手进行分析。
二、第一阶段
这个阶段分析的是cpu/arm920t/start.o
文件中的汇编代码实现的功能。
展示的代码并不是完整的代码,是根据前言中的目的来进行分析与展示。
1、设置CPU为管理模式
可以看到汇编文件一开始会跳到reset:
中执行
2、关看门狗
3、关中断
4、设置时钟
5、根据情况判断是否初始化SDARM
- 比较
r0+_start
的值与_TEXT_BASE
的值,不相等则跳去执行cpu_init_crit
- 在
cpu_init_crit
中进行:关闭flush、清除caches、关闭MMU、跳去执行lowlevel_init
- 在
lowlevel_init
中,对SDRAM进行相关初始化
6、设置栈(只有设置栈之后才可以调用C函数)
7、根据情况判断是否提前清bss段
- 比较
r0+_start
的值与_TEXT_BASE
的值,不相等则跳去执行clean_bss
- 在
clean_bss
中进行:清除操作
就算这里不清除,后面汇编代码也会清除。
8、重定位
- 在
CopyCode2Ram
中判断为NOR启动还是NAND启动 - 从NOR启动,
copy
工作简单 - 从NAND启动,先初始化NAND FLASH,后进行
copy
9、调用start_armboot(u-boot的第二阶段)
10、总结
到此为止,u-boot的第一阶段的分析已经结束。
总体的流程如下:
- 设置为管理模式
- 关看门狗
- 关中断
- 设置时钟
- 根据情况判断是否初始化SDARM
- 设置栈(只有设置栈之后才可以调用C函数)
- 根据情况判断是否提前清bss段
- 重定位
- 调用start_armboot(u-boot的第二阶段)
- 其中1~8步骤的设置称为硬件相关的设置,主要通过汇编代码完成。
- 9步骤后的设置才是启动内核的代码实现,比较复杂,需要通过C语言实现。
三、第二阶段分析
这里抱着启动内核的目标,分析start_armboot()
函数,所在的位置为u-boot-1.1.6/lib_arm/board.c
1、进行一系列的初始化工作
- 通过如下函数进行对应的初始化,如CPU初始化、板载资源的初始化、中断初始化等等
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
- 分析板载资源初始化
2_1. 进行把对应IO口拉高
2_2. 根据判断结果设置机器ID
2_3. 设置启动参数
2、初始化NOR FLASH
3、在内存中分配堆区
4、初始化NAND FLASH
5、设置环境变量
6、进入到main_lopp()
死循环过程
运行到这个函数这里,就可以
6.1 资源的加载与版本号的设置
6.2 设置超时时间并打印一些信息
6.3 获取环境变量bootcmd
,以便后面内核的启动
这个参数当我们启动bootloader时,执行print指令输出的信息有它的显示,这个参数与内核的启动有关,后面会进行介绍。
6.4 在超时时间内判断键盘是否有输入,无的话
如果此时键盘无输入,则会与如下信息:最明显的就是打印"Booting Linux ...\n"
-
实际运行bootlloader的界面
-
此时会运行
run_command(s, 0)
,根据上面的到的**bootcmd
环境变量来启动内核**
如何启动我这里只说结论,具体的分析过程可以看韦老师的分析博客【Uboot到底如何启动内核】 -
nand read.jffs2 0x30007FC0 kerne
l等价于:nand read.jffs20x30007FC0 0x00060000 0x00200000
-
bootm 0x30007FC0
关键函数do_bootm()
,具体做了如下:读取头部,将内核移动到加载地址,启动内核
6.5 在超时时间内判断键盘是否有输入,有的话,此时如果想启动内核的话,需要通过输入命令实现
6.5.1 进入菜单 run_command("menu", 0);
- 实际运行的显示
6.5.2 等待此时键盘输入的命令,若按下’q’
- 如果此时按下的是’q’,则程序会卡死在这个界面,并且等待命令的输入:
7、总结
第二阶段的作用大致如下:
- 进行CPU、中断、设置启动参数等初始化
- 初始化NOR FLASH、NAND FLASH,以便后面代码的重定位
- 设置好环境变量
- 进入bootloader启动界面,根据超时时间进行对应操作
4.1 超时时间内键盘不输入,过了超时时间则启动内核
4.2 超时时间内键盘输入,进入菜单界面,根据命令进行相关操作
其中,在4.1和4.2中都有执行了相同的函数run_command()
,下面来分析它。
8、分析run_command()
8.1 处理多条命令情况
8.2 拆分命令
如果此时命令为md.w 0
则拆分后结果如下:
8.3 分析find_cmd()
这个函数的作用是对拆分后得到的命令进行匹配
8.4 分析cmd_tbl_s
结构体
- 原型如下:
- 在链接脚本中有如下段
.u_boot_cmd : { *(.u_boot_cmd) }
通过搜索.u_boot_cmd
,并进行以下关键词搜索得到如下结果:
- 对
cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage, help}
进行拆分得到: