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