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命令查看结果