u-boot 介绍:
对于计算机来说 , 从一开始上机通电是无法直接启动操作系统的 , 这中间需要一个引导过程 , 嵌入式Linux系统同样离不开引导程序 , 这个启动程序就叫启动加载程序(Bootloader) ,Bootloader 主要是进行一些基础必要硬件的初始化 (cpu_init ,memory_init , UART_init ...) , 为最终调用 kernel 作准备 .
对于嵌入式系统而言 , Bootloader 是基于特定的硬件平台实现的 . 因此 , 几乎不可能有一个Bootloader 能适用所有的嵌入式系统 , 不同的处理器架构都有不同的Bootloader , 不同的硬件也需要不同的Bootloader , Bootloader 不仅仅依赖CPU的体系架构 , 而且依赖嵌入式开发板设备的配置 , 也就是说 , 就算是应用相同cpu 体系架构的 两块开发版 , 要想让运行在一块板子上的 Bootloader 运行在另一块板子上 , 一般都需要修改Bootloader 的源程序 .
但是 , 大部分的Bootloader 他们都有相似之处 , 而且相同cpu体系架构的Bootloader 很多源代码都有相似之处 , 所以 , 就产生了u - boot , u - boot 能支持PowerPC , ARM , MIPS ,和 X86 等体系结构 , 支持的板子也多达上百种 , 我们通过自己的修改也可以使用自己的板子 .
我所用的u-boot 的下载地址: http://pan.baidu.com/s/1qYktHTy
u-boot 源码结构:
u-boot 的 顶层目录下有30多个子目录 , 分别存放和管理不同的源程序 . 这些目录存放的文件也有其规则, 可以分为3类 .
1 . 第一类目录与处理器体系架构和开发板硬件直接相关 .
2 . 第二类目录是一些通用函数或驱动程序 .
3 . 第三类是u-boot的应用程序, 工具 , 文档 .
下面是相关目录的简介
board : 存放的开发板相关的目录 , 如 board/freescale 等目录.
cpu : 存放的cpu相关的目录 , 如 cpu/arm_cortexa8 等目录.
lib_xxx : 与体系架构相关的库文件 , 如与arm相关的库就存放在 lib_arm 目录当中 .
include : u-boot 使用的头文件 , 还有支持各种硬件平台的汇编文件 , 系统的相关配置文件和支持文件系统的文件 , 该目录下的configs 目录中有与开发板相关的配置文件 .
common : 实现u-boot 命令行下的支持的命令 , 每一条命令都对应一个文件 , 例如bootm 命令对应的就是cmd_bootm.c
lib_generic : 通用库函数的实现 .
net : 与网络协议相关的代码 , BOOTP , TFTP , RARP , 和NFS文件系统的实现 .
fs : 支持的文件系统 , 如 cramfs . fat . fdos . jffs2 和 registerfs.
drivers : u-boot 支持的设备驱动程序都放在该目录中 , 如串口驱动 , usb驱动 , 网卡驱动 .
disk : 对磁盘的支持.
doc : 文档目录 .
tools : 生成u-boot 的工具 .
examples : 一些独立运行的运用程序的例子 .
u-boot 配置编译 :
u-boot 的源码是通过makefile 来整体编译的 , 顶层目录下的Makefile 完成对开发板的整体配置 , 然后递归调用各级子目录下的Makefile , 最后把所有编译过的程序链接成u-boot 的映像 , 下面以arm_cortexa8处理器的 mx6q_sabresd 为例 , 介绍u - boot 的配置编译方法 , 并简单的分析其原理 .
1 . 基本的配置编译方法
配置编译u-boot 的方法十分简单 , 只需要在u-boot 顶层目录下执行如下两个命令.
# make <board_name>_config
# mkae
第一条命令为配置u-boot , 例如我们板子的的名字是mx6q_sabresd , 我们的命令就是 make mx6q_sabresd_config , 第二条命令是编译 , 一般我会在其后面加上 , make -j100 (请不要轻易尝试, 如果是处理器不是很好的情况下) .
2 . 在编译完成后 , 我们会得到以下文件
a . 在make mx6q_sabresd_config 后 , 我们会得到在 include/config.mk文件.
这个文件的内容是 ARCH = arm
CPU = arm_cortexa8
BOARD = mx6q_sabresd
VENDOR = freescale
SOC = mx6
根据上面的文件 , 我们可以得到在mx6q_sabresd 平台下 , 我们的相关目录如下
board/freescale/mx6q_sabresd/
cpu/arm_cortexa8/
cpu/arm_cortexa8/mx6/
lib_arm/
include/asm-arm/
include/configs/mx6q_sabresd.h
上面这几个目录 ,就是我们真个u-boot在 mx6q_sabresd 这个平台下的主线所在.
b . 在make -j100 之后 , 我们会得到以下四个文件
System.map u-boot映像的符号表 , 其实在我理解这是一个函数的地址表 , 我们可以充分的利用这个表在后面做很多事情.
u-boot.bin u-boot 映像原始的二进制格式
u-boot u-boot 映像的ELF格式
u-boot.srec u-boot映像的S-Record格式.
u-boot 启动流程分析
u-boot 的启动流程可以分为两个阶段 .
一 . 第一阶段 .
1 . 硬件设备初始化 (cpu)
2 . 加载u- boot 第二阶段代码到RAM空间
3 . 设置栈 , 为跳转c code 作准备.
4 . 跳转到第二阶段代码入口.
二 . 第二阶段
1 . 初始化本阶段使用的硬件设备 .
2 . 为内核设置参数.
3 .准备调用内核
首先 , 先介绍一个文件 , 在cpu/arm_cortexa8/u-boot.lds 文件中 , 该文件内容如下.
27 /* 指定输出可执行文件是32位的ARM指令,小端模式的ELF格式 */
28 OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
29 OUTPUT_ARCH(arm) /* 指定可执行文件平台为ARM */
30 ENTRY(_start) /* 指定程序的入口为_start */
31 SECTIONS
32 {
33 . = 0x00000000; /* 指明目标代码的起始代码地址为0x0位置开始,"."代表> 当前位置 */
34
35 . = ALIGN(4); /* 表示此处4字节对其 */
36 .text :
37 {
38 cpu/arm_cortexa8/start.o (.text) /* 表示start.o是代码段的第> 一个.o文件 */
39 *(.text) /* 表示这是代码段的其余部分 */
40 }
41
42 . = ALIGN(4);
43 .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) } /* 这是只读段数据 */
43 .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) } /* 这是只读段数据 */
44
45 . = ALIGN(4);
46 .data : { *(.data) } /* 这表示数据段 */
47
48 . = ALIGN(4);
49 .got : { *(.got) }
50
51 __u_boot_cmd_start = .;
52 .u_boot_cmd : { *(.u_boot_cmd) }
53 __u_boot_cmd_end = .;
54
55 . = ALIGN(4);
56 __bss_start = .;
57 .bss : { *(.bss) }
58 _end = .;
59 }
如上注释 , 第28行 , 指定输出可执行文件是32位的ARM指令,小端模式的ELF格式.
第29行 , 指定指定可执行文件平台为ARM.
第30行 , 指定程序的入口为_start .
第33行 , 指明目标代码的起始代码地址为0x0位置开始,"."代表> 当前位置
第38行 , 表示start.o是代码段的第> 一个.o文件 .
从而得出 , 我们的入口文件是 cpu/arm_cortexa8/start.o , 因此 ,u-boot 的入口代码是对应源文件cpu/arm_cortexa8/start.S
cpu/arm_cortexa8/start.S 源码分析:
一开始:
36 .globl _start
37 _start: b reset
38 ldr pc, _undefined_instruction
39 ldr pc, _software_interrupt
40 ldr pc, _prefetch_abort
41 ldr pc, _data_abort
42 ldr pc, _not_used
43 ldr pc, _irq
44 ldr pc, _fiq
45
46 _undefined_instruction: .word undefined_instruction
47 _software_interrupt: .word software_interrupt
48 _prefetch_abort: .word prefetch_abort
49 _data_abort: .word data_abort
50 _not_used: .word not_used
51 _irq: .word irq
52 _fiq: .word fiq
这一段代码其实从一开始的 跳转到 reset 函数 , 但是 , 这段代码设置了一个异常向量表 , 意思就是cpu 如果执行了异常的指令就会直接跳到这里 , 比如说除于0这种代码 .
在37行直接跳到 reset 函数
一开始 , u-boot 便把 cpu 设置为SVC模式(管理模式)
103 reset:
104 /*
105 * set the cpu to SVC32 mode
106 */
107 mrs r0, cpsr
108 bic r0, r0, #0x1f
109 orr r0, r0, #0xd3
110 msr cpsr,r0
跳到低水平的cpu_init :
136 #ifndef CONFIG_SKIP_LOWLEVEL_INIT
137 bl cpu_init_crit //到里边看看
138 #endif
bl 指令是会回来的:
189 /*************************************************************************
190 *
191 * CPU_init_critical registers
192 *
193 * setup important registers
194 * setup memory timing
195 * @cpu 最开始的初始化
196 *************************************************************************/
197 cpu_init_crit:
198 /*
199 * Invalidate L1 I/D
200 */
201 mov r0, #0 @ set up for MCR
202 mcr p15, 0, r0, c8, c7, 0 @ invalidate TLBs
203 mcr p15, 0, r0, c7, c5, 0 @ invalidate icache
204
205 /*
206 * disable MMU stuff and caches //关闭mmu以及caches高速缓存器
207 */
208 mrc p15, 0, r0, c1, c0, 0
209 bic r0, r0, #0x00002000 @ clear bits 13 (--V-)
210 bic r0, r0, #0x00000007 @ clear bits 2:0 (-CAM)
211 orr r0, r0, #0x00000002 @ set bit 1 (--A-) Align
212 orr r0, r0, #0x00000800 @ set bit 12 (Z---) BTB
213 mcr p15, 0, r0, c1, c0, 0
214
215 /*
216 * Jump to board specific initialization...
217 * The Mask ROM will have already initialized
218 * basic memory. Go here to bump up clock rate and handle
219 * wake up conditions.
220 */
221 mov ip, lr @ persevere link reg across call
222 bl lowlevel_init @ go setup pll,mux,memory //跳到低水平的初始化
223 mov lr, ip @ restore link
224 mov pc, lr @ back to my caller
225 /*
在222行 , 我们又跳入lowlevel_init
152 .globl lowlevel_init
153 lowlevel_init:
154
155 inv_dcache /* invalidate the D-CACHE */
156
157 init_l2cc /*Disable L2Cache 关闭L2Cache (高速缓存器)*/
158
159 init_aips /* AIPS setup - Only setup MPROTx registers The PACR default values are good.*/
160
161 init_clock //初始化时钟 */
162
163 mov pc, lr
过后直接设置栈
159 /* Set up the stack */
160 stack_setup:
161 ldr r0, _TEXT_BASE @ upper 128 KiB: relocated uboot
162 sub r0, r0, #CONFIG_SYS_MALLOC_LEN @ malloc area
163 sub r0, r0, #CONFIG_SYS_GBL_DATA_SIZE @ bdinfo
167 sub sp, r0, #12 @ leave 3 words for abort-stack
168 and sp, sp, #~7 @ 8 byte alinged for (ldr/str)d
清除bss段
170 /* Clear BSS (if any). Is below tx (watch load addr - need space) */
171 clear_bss:
172 ldr r0, _bss_start @ find start of bss segment
173 ldr r1, _bss_end @ stop here
174 mov r2, #0x00000000 @ clear value
175 clbss_l:1 76 str r2, [r0] @ clear BSS location
177 cmp r0, r1 @ are we at the end yet
178 add r0, r0, #4 @ increment clear index pointer
179 bne clbss_l @ keep clearing till at end
最后就便是跳到c 语言代码:
184 ldr pc, _start_armboot @ jump to C code
185
186 _start_armboot: .word start_armboot @跳入c语言代码,lib_arm/board.c
lib_arm/board.c 源码分析:
首先介绍两个重要的结构体以及一个重要的数组.
gd_t 结构体 , 该结构体是用来储存全局数据区的数据 , 这个结构体在 include/asm-arm/global_data.h 中定义如下
36 typedef struct global_data {
37 bd_t *bd;
38 unsigned long flags;
39 unsigned long baudrate;
40 unsigned long have_console; /* serial_init() was called */ (被serial_init()调用)
41 unsigned long reloc_off; /* Relocation Offset */
42 unsigned long env_addr; /* Address of Environment struct */
43 unsigned long env_valid; /* Checksum of Environment valid? */
44 unsigned long fb_base; /* base address of frame buffer */ /*framebuffer 基地址 */
54 void **jt; /* jump table */
55 } gd_t;
bd_t 结构体用于存放板级相关的全局数据 , 是gd_t 结构体指针成员 bd 的结构体类型 , 在 include/asm-arm/u-boot.h中定义如下:
39 typedef struct bd_info {
40 int bi_baudrate; /* serial console baudrate */ /*串口波特率*/
41 unsigned long bi_ip_addr; /* IP Address */ /* IP 地址 */
42 struct environment_s *bi_env;
43 ulong bi_arch_number; /* unique id for this board */ /*开发板机器码*/
44 ulong bi_boot_params; /* where this board expects params */ /*内核参数开始地址 */
45 struct /* RAM configuration */ /*内存配置信息结构体 */
46 {
47 ulong start;
48 ulong size;
49 } bi_dram[CONFIG_NR_DRAM_BANKS];
50 } bd_t;
init_sequence 数组:
278 //一系列的初始化
279 init_fnc_t *init_sequence[] = {
280 #if defined(CONFIG_ARCH_CPU_INIT)
281 arch_cpu_init, /* basic arch cpu dependent setup */
282 #endif
283 board_init, /* basic board dependent setup */
284 #if defined(CONFIG_USE_IRQ)
285 interrupt_init, /* set up exceptions */ //不进入
286 #endif
287 timer_init, /* initialize timer */ //初始化定时器
288 env_init, /* initialize environment */
289 init_baudrate, /* initialze baudrate settings, 波特率由用户自己设定
*/
290 serial_init, /* serial communications setup */
291 console_init_f, /* stage 1 init of console */
292 display_banner, /* say that we are here */
293 #if defined(CONFIG_DISPLAY_CPUINFO)
294 print_cpuinfo, /* display cpu info (and speed) */
295 #endif
296 #if defined(CONFIG_DISPLAY_BOARDINFO)
297 checkboard, /* display board info */
298 #endif
299 #if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C)
300 init_func_i2c,
301 #endif
302 dram_init, /* configure available RAM banks */
303 #if defined(CONFIG_CMD_PCI) || defined (CONFIG_PCI)
304 arm_pci_init,
305 #endif
306 display_dram_config,
307 NULL,
308 };
下面介绍start_armboot 这段代码的流程 :
首先直接进行一系列的初始化:
332 //这个是个小关键,这里面会运行一系列的初始化
333 for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
334 if ((*init_fnc_ptr)() != 0) {
335 hang ();
336 }
337 }
上面这段代码 , 会直接运行init_sequence 数组内的每一个函数, cpu , 时钟 , 串口 , 控制台 , 等一系列初始化.
413 //标准输入输出初始化
414 stdio_init (); /* get the devices list going. */
416 jumptable_init (); /* jump table init (chen) */ /* 这里边会设置一个函数地址跳转表 */
423 console_init_r (); /* fully init console as a device */
424 /* 对所有的控制台进行初始化并打印信息,主要是标准输入输出以及错误输出*/
436 enable_interrupts (); /*配置寄存器使中断使能 */
479 board_late_init (); /* mainly do one thing,setup i2c (chen) */
最后会到
501 for (;;) {
502 main_loop ();
503 }
今天由于时间关系 , 只能暂时写到这里 . 之后我会将该流程详细的讲解.