u-boot的启动、编译过程和命令添加
MCU:s5pv210
开发板:unsp210
u-boot:1.3.4
一、简介
U-Boot是一种支持多架构,多操作系统的Bootloader(启动引导程序)
u-boot目前最新版本是:http://ftp.denx.de/pub/u-boot/
二、启动过程
嵌入式Bootloader的启动过程可以分为单阶段(Single-Stage)和多阶段(Multi-Stage)
通常多阶段的Bootloader能够提供更复杂的功能,及更好的可读性和移植性。
从外部存储设备上启动的Bootloader大多是两阶段的启动过程
第一阶段通常用汇编实现,完成一些依赖于CPU体系结构的初始化,并调用第二阶段代码
第二阶段通常用C语言实现,完成体系结构之外的功能,主要提供多种复杂命令并引导操作系统
u-boot两阶段入口代码位置为:
第一阶段位于:cpu/s5pc11x/start.S
第二阶段位于:lib_arm/board.c
三、启动流程分析
以s5pv210为例分析一下u-boot的启动流程:
上电之后,从iROM(BL0)里面执行出厂时固化的代码,获取6位OM状态,检测相应的控制器(controller),选择启动方式(ps:有的芯片也可以通过设置优先级的方式省去拨码开关,如s5p6818设置启动方式SD->USB->nand)。
通过控制器选择对应的启动设备(SD/USB/Nand/MMC/NOR),从中读取数据,将其中的BL1拷贝到芯片内部iRAM(BL1),开始从BL1运行,BL1运行结束后将BL2拷贝到SDRAM,在SDRAM里面运行BL2,BL2运行结束后将内核拷贝到SDRAM,运行内核,挂载根文件系统...
BL0:获取OM状态,选择控制器,拷贝外部BL1到内部BL1(iRAM)
BL1;把BL2拷贝到SDRAM,初始化SDRAM Controller
BL2;把OS拷贝到SDRAM
为什么把BL1拷贝到iRAM,BL2拷贝到SDRAM?
因为第一阶段要初始化SDRAM Controller才能使用外存SDRAM,所以第一阶段必须在内部进行,第二阶段如果芯片内部存储空间够大的话也可以在内部运行,但一般都是在外部运行,BL2运行结束后启动内核,内核直接从Bootloader的地址启动,覆盖Bootloader的地址空间,Bootloader被擦除
内核传参过程:
传递给内核的参数由多个结构体组成(面向对象),各结构体放在一段连续的内存空间,起始地址为0x3000_0100每一个结构体代表一条信息并首尾相连,内核引导起来以后,将从指定内存按照同样的数据结构将数据取出。
四、两个阶段的任务
u-boot第一阶段完成任务:
涉及两个文件:
u-boot/cpu/s5pc11x/start.S
u-boot/board/sumsung/unsp210/lowlevel_init.S
1、禁用看门狗
lowlevel_init.S(line:61)
/* Disable Watchdog */
ldr r0, =ELFIN_WATCHDOG_BASE /* 0xE2700000 */
mov r1, #0
str r1, [r0]
2、关中断
start.S(line:141)
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
3、初始化系统时钟
lowlevel_init.S(line:122)
/* init system clock */
bl system_clock_init
lowlevel_init.S(line:209)
/*
* system_clock_init: Initialize core clock and bus clock.
* void system_clock_init(void)
*/
system_clock_init:
ldr r0, =ELFIN_CLOCK_POWER_BASE @0xe0100000
/* Set Mux to FIN */
ldr r1, =0x0
str r1, [r0, #CLK_SRC0_OFFSET]
ldr r1, =APLL_LOCKTIME_VAL
str r1, [r0, #APLL_LOCK_OFFSET]
4、设置异常向量表(用到中断的情况下设置)
start.S(line:56)
.globl _start
_start: 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
5、内存控制器配置
lowlevel_init.S(line:125)
/* Memory initialize */
bl mem_ctrl_asm_init
/cpu/s5pc11x/s5pc110/cpu_init.S(line:5)
.globl mem_ctrl_asm_init
mem_ctrl_asm_init:
......
6、初始化调试指示灯(可选)
lowlevel_init.S(line:99)
/* PS_HOLD pin(GPH0_0) set to high */
ldr r0, =(ELFIN_CLOCK_POWER_BASE + PS_HOLD_CONTROL_OFFSET)
ldr r1, [r0]
orr r1, r1, #0x300
orr r1, r1, #0x1
str r1, [r0]
7、初始化UART,用于开发调试(可选)
lowlevel_init.S(line:129)
/* for UART */
bl uart_asm_init
lowlevel_init.S(line:355)
/*
* uart_asm_init: Initialize UART in asm mode, 115200bps fixed.
* void uart_asm_init(void)
*/
uart_asm_init:
/* set GPIO(GPA) to enable UART */
@ GPIO setting for UART
ldr r0, =ELFIN_GPIO_BASE
ldr r1, =0x22222222
str r1, [r0, #GPA0CON_OFFSET]
ldr r1, =0x2222
str r1, [r0, #GPA1CON_OFFSET]
8、从NAND、NOR或SD卡中复制代码到SDRAM
NAND:
start.S(line:337)
nand_boot:
mov r0, #0x1000
bl copy_from_nand
b after_copy
start.S(line:435)
/*
* copy U-Boot to SDRAM and jump to ram (from NAND or OneNAND)
* r0: size to be compared
* Load 1'st 2blocks to RAM because U-boot's size is larger than 1block(128k) size
*/
.globl copy_from_nand
copy_from_nand:
push {lr} /* save return address */
mov r9, r0
mov r9, #0x100 /* Compare about 8KB */
bl copy_uboot_to_ram
tst r0, #0x0
bne copy_failed
NOR:
start.S(line:355, 356)
nor_boot:
bl read_hword
b after_copy
/board/sumsung/unsp210/flash.c(line:590)
/*-----------------------------------------------------------------------
* Copy flash to memory
*/
void read_hword (void)
{
volatile u32 *buf = CFG_PHY_UBOOT_BASE;
volatile u32 *nor_base = CFG_FLASH_BASE;
memcpy ((void *)buf, (void *)nor_base, COPY_BL2_SIZE);
}
SD:
start.S(line:346~353)
mmcsd_boot:
#if DELETE
ldr sp, _TEXT_PHY_BASE
sub sp, sp, #12
mov fp, #0
#endif
bl movi_bl2_copy
b after_copy
/cpu/s5pc11x/movi.c(line:19)
void movi_bl2_copy(void)
{
......
......
}
9、 初始化堆栈:start.S(line:389)
stack_setup:
#if defined(CONFIG_MEMORY_UPPER_CODE)
ldr sp, =(CFG_UBOOT_BASE + CFG_UBOOT_SIZE - 0x1000)
#else
ldr r0, _TEXT_BASE /* upper 128 KiB: relocated uboot */
sub r0, r0, #CFG_MALLOC_LEN /* malloc area */
sub r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo */
#if defined(CONFIG_USE_IRQ)
sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif
sub sp, r0, #12 /* leave 3 words for abort-stack */
清除bss段:start.S(line:408)
clear_bss:
ldr r0, _bss_start /* find start of bss segment */
ldr r1, _bss_end /* stop here */
mov r2, #0x00000000 /* clear */
10、跳转到start_armboot,进入Bootloader第二阶段
start.S(line:419, 421, 422)
ldr pc, _start_armboot
_start_armboot:
.word start_armboot
u-boot第二阶段完成任务:
涉及一个文件:/u-boot/lib_arm/board.c
1.开发板相关的初始化:
u-boot/lib_arm/board.c(483行)
#if defined(CONFIG_SMDKC110)
2.加载环境变量:
u-boot/lib_arm/board.c(650行)
/* initialize environment */
env_relocate ();
3.使能中断:
u-boot/lib_arm/board.c(705行)
/* enable exceptions */
enable_interrupts ();
4.主循环:
main_loop(756行)
/* main_loop() can return to retry autoboot, if so just run it again. */
for (;;) {
main_loop ();
}
-> s = getenv ("bootcmd");
-> parse_string_outer(s, FLAG_PARSE_SEMICOLON | FLAG_EXIT_FROM_LOOP);
-> rcode = parse_stream_outer(&input, flag);
-> code = run_list(ctx.list_head);
-> rcode = run_list_real(pi);
-> rcode = run_pipe_real(pi);
-> if ((cmdtp = find_cmd(child->argv[i])) == NULL)
(common/hush.c 1679行)
-> rcode = (cmdtp->cmd)(cmdtp, flag,child->argc-i,&child->argv[i]);
(common/hush.c 1710行)
五、配置编译过程
以s5pv210开发板为例
u-boot的配置编译需要经过以下步骤:
1、在u-boot的根目录下执行:#make unsp210_config //对应开发板配置
Makefile 会构建编译结构,如:架构、cpu、开发板、厂商、芯片、目录等,为下一步真正编译链接做准备。
2、修改include/configs/unsp210.h配置文件
3、在根目录下执行:make
根据以上两步产生编译和连接所需文件的信息
最终make完成,在根目录下将生成:
u-boot.bin u-boot.dis u-boot.map...
编译过程如下:
u-boot的根目录下执行:#make unsp210_config 打开Makefile在2581行找到
unsp210_config : unconfig
@$(MKCONFIG) $(@:_config=) arm s5pc11x unsp210 samsung s5pc110
@echo "TEXT_BASE = 0xc3e00000" > $(obj)board/samsung/unsp210/config.mk
可以去掉@符号让信息输出到屏幕
其中第一句的$(MKCONFIG)表示引用第101行指定的mkconfig文件路径
$(@:_config=)表示目标去掉_config
后面的是编译参数
第二句表示将信息输出到文件board/samsung/unsp210/config.mk
打开mkconfig看到第23行如下:
[ "${BOARD_NAME}" ] || BOARD_NAME="$1" 给BOARD_NAME赋值$1
继续往下执行mkconfig发现都是一些mkdir, rm, cd, ln命令,删除旧的链接建立新的文件链接
mkconfig第123行
echo "ARCH = $2" > config.mk
echo "CPU = $3" >> config.mk
echo "BOARD = $4" >> config.mk
将信息输出到include/config.mk文件中,在config.mk文件114行引用环境变量
mkconfig第134行
if [ "$APPEND" = "yes" ] # Append to existing config file
then
echo >> config.h
else
> config.h # Create new config file
fi
echo "/* Automatically generated - do not edit */" >>config.h
echo "#include <configs/$1.h>" >>config.h
添加平台头文件(unsp210.h)创建config.h加入"#include <configs/$1.h>"
链接过程:
链接地址定义在board/samsung/unsp210/config.mk中
链接脚本board/samsung/unsp210/u-boot.lds
/board/samsung/unsp210/config.mk被顶层config.mk包含并设置链接选项LDFLAGS(arm-linux-ld)
config.mk第198行
LDFLAGS += -Text $(TEXT_BASE)
这个TEXT_BASE就是Makefile第2583行的这句里
@echo "TEXT_BASE = 0xc3e00000" > $(obj)board/samsung/unsp210/config.mk
LDFLAGS在makefile链接时发挥作用
最终用OBJCOPY去掉ELF格式信息得到可以直接在裸机上执行的u-boot.bin
$(OBJCOPY)${OBJCFLAGS} -O binary $< $@
六、命令添加
u-boot的每一个命令都是通过U_BOOT_CMD宏定义来实现的,这个宏在include/command.h头文件中
U_BOOT_CMD(name,maxargs,rep,cmd,usage,help)
每一个命令定义了一个cmd_tbl_t结构体,结构体包含的成员变量有:命令名称、最大参数个数、重复次数、命令执行函数、用法、帮助。
从控制台输入的命令都被送到common/command.c中的find_cmd()函数解释执行,根据匹配输入的命令,从列表中找出对应的命令结构体,并调用其回调处理函数完成命令处理,命令响应的过程,就是命令的查找与回调函数处理的过程
find_cmd()部分代码如下:
for(cmdtp=&__u_boot_cmd_start;cmdtp != &__u_boot_cmd_end;cmdtp++)
{
if(strncmp(cmd, cmdtp->name, len)==0)
{
if(len == strlen(cmdtp->name))
return cmdtp;
cmdtp_temp=cmdtp;
n_found++;
}
}
if(n_found==0){
return cmdtp_temp;
}
实现了输入部分命令可以代替全部命令的效果,如输入print和输入pri结果一样
原理:输入时用len保存命令长度,比较前len个长度的输入与命令,如果全部匹配直接返回命令结构体,如果部分匹配,存入临时结构体cmdtp_temp,并且n_found++,最后判断如果n_found==1则返回cmdtp_temp
怎样添加一个u-boot命令(三步)
假如要添加一个helloworld命令
1、在include/configs/unsp210.h中增加一项宏定义 这样通过改变宏定义的值(1打开或0关闭)进行选择性的编译
#define CONFIG_CMD_HELLOWORLD 1
2、在common/文件夹下建立cmd_helloworld.c文件
#include<common.h>
#include<command.h>
#ifdef CONFIG_CMD_HELLOWORLD
void helloworld(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
int i=0;
for(i=0;i<argc;i++)
{
printf("Hello World!\n");
printf("argv[%d]=$s\n", i, argv[i]);
}
}
//命令名称、最大参数个数、重复次数、命令执行函数、用法、帮助
U_BOOT_CMD(hello,3,2,helloworld,"hello command","add u-boot command!\n");
#endif
3、在common/Makefile中增加一项
COBJS-y += cmd_helloworld.o
make重新下载u-boot.bin在命令行输入help和hello命令查看结果