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 源码结构:
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 后 , 我们会执行顶层目录的Makefile , 并执行mx6q_sabresd_config 规则:
有如下代码
1 mx6solo_sabresd_config \ 2 mx6solo_sabresd_mfg_config \ 3 mx6solo_sabresd_android_config \ 4 mx6dl_sabresd_config \ 5 mx6dl_sabresd_mfg_config \ 6 mx6dl_sabresd_android_config \ 7 mx6q_sabresd_config \ 8 mx6q_sabresd_android_config \ 9 mx6q_sabresd_mfg_config \ 10 mx6q_sabresd_iram_config : unconfig 11 @[ -z "$(findstring iram_,$@)" ] || \ 12 { echo "TEXT_BASE = 0x00907000" >$(obj)board/freescale/mx6q_sabresd/config.tmp ; \ 13 echo "... with iram configuration" ; \ 14 } 15 @$(MKCONFIG) $(@:_config=) arm arm_cortexa8 mx6q_sabresd freescale mx6
unconfig 这个规则其实就是将以前的生成的配置文件全删除掉: (@的作用就是不显示)
1 unconfig: 2 @rm -f $(obj)include/config.h $(obj)include/config.mk \ 3 $(obj)board/*/config.tmp $(obj)board/*/*/config.tmp \ 4 $(obj)include/autoconf.mk $(obj)include/autoconf.mk.dep
在这里,我们将调用$(MKCONFIG)
也就是:
1 MKCONFIG := $(SRCTREE)/mkconfig
其实就是执行了 ./mkconfig arm arm_cortexa8 mx6q_sabresd freescale mx6
就是顶层目录下的mkconfig 文件:
所以就有如下代码:
1 # 2 # Create include file for Make 3 # 4 echo "ARCH = $2" > config.mk 5 echo "CPU = $3" >> config.mk 6 echo "BOARD = $4" >> config.mk 7
8 [ "$5" ] && [ "$5" != "NULL" ] && echo "VENDOR = $5" >> config.mk 9
10 [ "$6" ] && [ "$6" != "NULL" ] && echo "SOC = $6" >> config.mk 11
12 #
我们会得到在 include/config.mk文件.
这个文件的内容是
1 ARCH = arm 2 CPU = arm_cortexa8 3 BOARD = mx6q_sabresd 4 VENDOR = freescale 5 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 这个平台下的主线所在.
下面将一下 make 命令的执行过程
首先说一下如果没有经过make mx6q_sabresd_config 会发生什么情况,
它会报一个:
System not configured - see README 的错误.
它生成的过程是这样的:
1 ifeq ($(obj)include/config.mk,$(wildcard $(obj)include/config.mk)) /* 判断这几个文件是否存在 */
2 ...... 3 all: 4 sinclude $(obj)include/autoconf.mk.dep 5 sinclude $(obj)include/autoconf.mk 6
7 ...... 8
9 else # !config.mk 10 all $(obj)u-boot.hex $(obj)u-boot.srec $(obj)u-boot.bin \ 11 $(obj)u-boot.img $(obj)u-boot.dis $(obj)u-boot \ 12 $(SUBDIRS) $(TIMESTAMP_FILE) $(VERSION_FILE) gdbtools updater env depend \ 13 dep tags ctags etags cscope $(obj)System.map: 14 @echo "System not configured - see README" >&2
15 @ exit 1
16 endif # config.mk
u-boot 启动流程分析
u-boot 的启动流程可以分为两个阶段 .
一 . 第一阶段 .
1 . 硬件设备初始化 (cpu)
2 . 加载u- boot 第二阶段代码到RAM空间
3 . 设置栈 , 为跳转c code 作准备.
4 . 跳转到第二阶段代码入口.
二 . 第二阶段
1 . 初始化本阶段使用的硬件设备 .
2 . 为内核设置参数.
3 .准备调用内核
在这里我忘了一个文件, 在顶层目录中的u-boot.lds
这是一个很重要的文件, 我么可以靠这个文件找到真正的入口
里面有内容:
1 . = 0x00000000; 2 . = ALIGN(4); 3 .text : 4 { 5 board/freescale/mx6q_sabresd/flash_header.o (.text.flasheader) 6 cpu/arm_cortexa8/start.o 7 board/freescale/mx6q_sabresd/libmx6q_sabresd.a (.text) 8 lib_arm/libarm.a (.text) 9 net/libnet.a (.text) 10 drivers/mtd/libmtd.a (.text) 11 drivers/mmc/libmmc.a (.text) 12 . = DEFINED(env_offset) ? env_offset : .; 13 common/env_embedded.o(.text) 14 *(.text)
可以看到我们的入口文件是在 board/freescale/mx6q_sabresd/flash_header.o (.text.flasheader)
参考: http://blog.csdn.net/kris_fei/article/details/50463018
这里面启动了rom 并跳转到了start.S
再介绍一个文件 , 在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 源码分析:
一开始:
1 .globl _start 2 _start: b reset 3 ldr pc, _undefined_instruction 4 ldr pc, _software_interrupt 5 ldr pc, _prefetch_abort 6 ldr pc, _data_abort 7 ldr pc, _not_used 8 ldr pc, _irq 9 ldr pc, _fiq 10
11 _undefined_instruction: .word undefined_instruction 12 _software_interrupt: .word software_interrupt 13 _prefetch_abort: .word prefetch_abort 14 _data_abort: .word data_abort 15 _not_used: .word not_used 16 _irq: .word irq 17 _fiq: .word fiq 18 _pad: .word 0x12345678 /* now 16*4=64 */
19 .global _end_vect
这一段代码其实从一开始的 跳转到 reset 函数 , 但是 , 这段代码设置了一个异常向量表 , 意思就是cpu 如果执行了异常的指令就会直接跳到这里 , 比如说除于0这种代码 .
在37行直接跳到 reset 函数
一开始 , u-boot 便把 cpu 设置为SVC模式(管理模式)
1 reset: 2 /*
3 * set the cpu to SVC32 mode 4 */
5 mrs r0, cpsr 6 bic r0, r0, #0x1f
7 orr r0, r0, #0xd3
8 msr cpsr,r0
跳到低水平的cpu_init :
1 bl cpu_init_crit //到里边看看
bl 指令是会回来的:
1 /************************************************************************* 2 * 3 * CPU_init_critical registers 4 * 5 * setup important registers 6 * setup memory timing 7 * @cpu 最开始的初始化 8 *************************************************************************/
9 cpu_init_crit: 10 /*
11 * Invalidate L1 I/D 12 */
13 mov r0, #0 @ set up for MCR 14 mcr p15, 0, r0, c8, c7, 0 @ invalidate TLBs 15 mcr p15, 0, r0, c7, c5, 0 @ invalidate icache 16
17 /*
18 * disable MMU stuff and caches //关闭mmu以及caches高速缓存器 19 */
20 mrc p15, 0, r0, c1, c0, 0
21 bic r0, r0, #0x00002000 @ clear bits 13 (--V-) 22 bic r0, r0, #0x00000007 @ clear bits 2:0 (-CAM) 23 orr r0, r0, #0x00000002 @ set bit 1 (--A-) Align 24 orr r0, r0, #0x00000800 @ set bit 12 (Z---) BTB 25 mcr p15, 0, r0, c1, c0, 0
26
27 /*
28 * Jump to board specific initialization... 29 * The Mask ROM will have already initialized 30 * basic memory. Go here to bump up clock rate and handle 31 * wake up conditions. 32 */
33 mov ip, lr @ persevere link reg across call 34 bl lowlevel_init @ go setup pll,mux,memory //跳到低水平的初始化
35 mov lr, ip @ restore link 36 mov pc, lr @ back to my caller
在34行 , 我们又跳入lowlevel_init
1 .globl lowlevel_init 2 lowlevel_init: 3
4 inv_dcache /*invalidate the D-CACHE */ 5
6 init_l2cc /* disable L2Cache */ 7
8 init_aips /* APIS setup */ 9
10 init_clock /*初始化时钟*/
11
12 mov pc, lr 13
跳回start.S
过后直接设置栈
1 /* Set up the stack */
2 stack_setup: 3 ldr r0, _TEXT_BASE @ upper 128 KiB: relocated uboot 4 sub r0, r0, #CONFIG_SYS_MALLOC_LEN @ malloc area 5 sub r0, r0, #CONFIG_SYS_GBL_DATA_SIZE @ bdinfo 6 #ifdef CONFIG_USE_IRQ /* 跳过 */ 7 sub r0, r0, #(CONFIG_STACKSIZE_IRQ + CONFIG_STACKSIZE_FIQ) 8 #endif
9 sub sp, r0, #12 @ leave 3 words for abort-stack 10 and sp, sp, #~7 @ 8 byte alinged for (ldr/str)d
清除bss段
1 clear_bss: 2 ldr r0, _bss_start @ find start of bss segment 3 ldr r1, _bss_end @ stop here 4 mov r2, #0x00000000 @ clear value 5 clbss_l: 6 str r2, [r0] @ clear BSS location 7 cmp r0, r1 @ are we at the end yet 8 add r0, r0, #4 @ increment clear index pointer 9 bne clbss_l @ keep clearing till at end
最后就便是跳到c 语言代码
1 ldr pc, _start_armboot @ jump to C code 2
3 _start_armboot: .word start_armboot @跳入c语言代码,lib_arm/board.c
lib_arm/board.c 源码分析:
首先介绍两个重要的结构体以及一个重要的数组.
gd_t 结构体 , 该结构体是用来储存全局数据区的数据 , 这个结构体在 include/asm-arm/global_data.h 中定义如下
1 /*
2 * The following data structure is placed in some memory wich is 3 * available very early after boot (like DPRAM on MPC8xx/MPC82xx, or 4 * some locked parts of the data cache) to allow for a minimum set of 5 * global variables during system initialization (until we have set 6 * up the memory controller so that we can use RAM). 7 * 8 * Keep it *SMALL* and remember to set CONFIG_SYS_GBL_DATA_SIZE > sizeof(gd_t) 9 */
10
11 typedef struct global_data { 12 bd_t *bd; 13 unsigned long flags; 14 unsigned long baudrate; 15 unsigned long have_console; /* serial_init() was called */ (被serial_init() 调用)
16 unsigned long reloc_off; /* Relocation Offset */
17 unsigned long env_addr; /* Address of Environment struct */
18 unsigned long env_valid; /* Checksum of Environment valid? */
19 unsigned long fb_base; /* base address of frame buffer */
20 void **jt; /* jump table */
21 } gd_t;
bd_t 结构体用于存放板级相关的全局数据 , 是gd_t 结构体指针成员 bd 的结构体类型 , 在 include/asm-arm/u-boot.h中定义如下:
typedef struct bd_info { int bi_baudrate; /* serial console baudrate */ /*串口波特率*/ unsigned long bi_ip_addr; /* IP Address */
struct environment_s *bi_env; ulong bi_arch_number; /* unique id for this board */ /*开发板机器码*/
ulong bi_boot_params; /* where this board expects params */ /*内核参数开始地址*/
struct /* RAM configuration */ /*内存配置信息结构体*/ { ulong start; ulong size; } bi_dram[CONFIG_NR_DRAM_BANKS]; } bd_t;
init_sequence 数组:
1 //一系列的初始化
2 init_fnc_t *init_sequence[] = { 3 #if defined(CONFIG_ARCH_CPU_INIT)
4 arch_cpu_init, /* basic arch cpu dependent setup */
5 #endif
6 board_init, /* basic board dependent setup */
7 #if defined(CONFIG_USE_IRQ)
8 interrupt_init, /* set up exceptions */ //不进入
9 #endif
10 timer_init, /* initialize timer */ //初始化定时器
11 env_init, /* initialize environment */
12 init_baudrate, /* initialze baudrate settings, 波特率由用户自己设定 */
13 serial_init, /* serial communications setup */
14 console_init_f, /* stage 1 init of console */
15 display_banner, /* say that we are here */
16 #if defined(CONFIG_DISPLAY_CPUINFO)
17 print_cpuinfo, /* display cpu info (and speed) */
18 #endif
19 #if defined(CONFIG_DISPLAY_BOARDINFO)
20 checkboard, /* display board info */
21 #endif
22 #if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C)
23 init_func_i2c, 24 #endif
25 dram_init, /* configure available RAM banks */
26 #if defined(CONFIG_CMD_PCI) || defined (CONFIG_PCI)
27 arm_pci_init, 28 #endif
29 display_dram_config, 30 NULL, 31 };
下面介绍start_armboot 这段代码的流程 :
首先直接进行一系列的初始化:
1 //这个是个小关键,这里面会运行一系列的初始化 ,就是上面那个数组内一个个函数的运行 , 有时间我会将一个个函数详细的讲解
2 for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) { 3 if ((*init_fnc_ptr)() != 0) { 4 hang (); 5 } 6 }
上面这段代码 , 会直接运行init_sequence 数组内的每一个函数, cpu , 时钟 , 串口 , 控制台 , 等一系列初始化.
1 //标准输入输出初始化
2 stdio_init (); /* get the devices list going. */
1 jumptable_init (); /* jump table init (chen) */
1 console_init_r (); /* fully init console as a device */
2 /* 对所有的控制台进行初始化并打印信息,主要是标准输入输出以及错误输出*/
1 /* enable exceptions */
2 enable_interrupts (); /*配置寄存器使中断使能 */
1 board_late_init (); /* mainly do one thing,setup i2c (chen) */
1 /* main_loop() can return to retry autoboot, if so just run it again. */
2 //最后的函数
3 for (;;) { 4 main_loop (); 5 }
今天由于时间关系 , 只能暂时写到这里 . 之后我会将该流程详细的讲解.