U-Boot啟動過程
開發板上電後,執行U-Boot的第一條指令,然後順序執行U-Boot啟動函數。看一下board/smdk2410/u-boot.lds這個鏈接腳本,可以知道目標程序的各部分鏈接順序。第一個要鏈接的是cpu/arm920t/start.o,那麼U-Boot的入口指令一定位於這個程序中。下麵分兩階段介紹啟動流程:
第一階段
1.cpu/arm920t/start.S
這個匯編程序是U-Boot的入口程序,開頭就是複位向量的代碼。
_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 //中斷向量
…
/* the actual reset code */
reset: //複位啟動子程序
/* 設置CPU為SVC32模式 */
mrs r0,cpsr
bic r0,r0,#0x1f
orr r0,r0,#0xd3
msr cpsr,r0
/* 關閉看門狗 */
…… ……
relocate: /* 把U-Boot重新定位到RAM */
adr r0, _start /* r0是代碼的當前位置 */
ldr r1, _TEXT_BASE /*_TEXT_BASE是RAM中的地址 */
cmp r0, r1 /* 比較r0和r1,判斷當前是從Flash啟動,還是RAM */
beq stack_setup /* 如果r0等於r1,跳過重定位代碼 */
/* 準備重新定位代碼 */
ldr r2, _armboot_start
ldr r3, _bss_start
sub r2, r3, r2 /* r2 得到armboot的大小 */
add r2, r0, r2 /* r2 得到要複製代碼的末尾地址 */
copy_loop: /* 重新定位代碼 */
ldmia r0!, {r3-r10} /*從源地址[r0]複製 */
stmia r1!, {r3-r10} /* 複製到目的地址[r1] */
cmp r0, r2 /* 複製數據塊直到源數據末尾地址[r2] */
ble copy_loop
/* 初始化堆棧等 */
stack_setup:
ldr r0, _TEXT_BASE /* 上麵是128 KiB重定位的u-boot */
sub r0, r0, #CFG_MALLOC_LEN /* 向下是內存分配空間 */
sub r0, r0, #CFG_GBL_DATA_SIZE /* 然後是bdinfo結構體地址空間 */
#ifdef CONFIG_USE_IRQ
sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif
sub sp, r0, #12 /* 為abort-stack預留3個字 */
clear_bss:
ldr r0, _bss_start /* 找到bss段起始地址 */
ldr r1, _bss_end /* bss段末尾地址 */
mov r2, #0x00000000 /* 清零 */
clbss_l:str r2, [r0]
/* bss段地址空間清零循環... */
add r0, r0, #4
cmp r0, r1
bne clbss_l
/* 跳轉到start_armboot函數入口,_start_armboot字保存函數入口指針 */
ldr pc, _start_armboot
_start_armboot: .word start_armboot //start_armboot函數在lib_arm/board.c中實現
第二階段
2.lib_arm/board.c
start_armboot是U-Boot執行的第一個C語言函數,完成係統初始化工作,進入主循環,處理用戶輸入的命令。
3.init_sequence[]
init_sequence[]數組保存著基本的初始化函數指針。
init_fnc_t *init_sequence[] = {
cpu_init, /* 基本的處理器相關配置 -- cpu/arm920t/cpu.c */
board_init, /* 基本的板級相關配置 -- board/smdk2410/smdk2410.c */
interrupt_init, /* 初始化中斷處理 -- cpu/arm920t/s3c24x0/interrupt.c */
env_init, /* 初始化環境變量 -- common/cmd_flash.c */
init_baudrate, /* 初始化波特率設置 -- lib_arm/board.c */
serial_init, /* 串口通訊設置 -- cpu/arm920t/s3c24x0/serial.c */
console_init_f, /* 控製台初始化階段1 -- common/console.c */
display_banner, /* 打印u-boot信息 -- lib_arm/board.c */
dram_init, /* 配置可用的RAM -- board/smdk2410/smdk2410.c */
display_dram_config, /* 顯示RAM的配置大小 -- lib_arm/board.c */
NULL,
};
void start_armboot (void)
{
/* 順序執行init_sequence數組中的初始化函數 */
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
/*配置可用的Flash */
size = flash_init ();
display_flash_config (size);
/* _armboot_start 在u-boot.lds鏈接腳本中定義 */
mem_malloc_init (_armboot_start - CFG_MALLOC_LEN);
/* 配置環境變量*/
env_relocate ();
/* 從環境變量中獲取IP地址 */
gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");
/* 以太網接口MAC 地址 */
……
devices_init (); /* 獲取列表中的設備 */
jumptable_init ();
console_init_r (); /* 完整地初始化控製台設備 */
enable_interrupts (); /* 使能中斷處理 */
/* 通過環境變量初始化 */
if ((s = getenv ("loadaddr")) != NULL) {
load_addr = simple_strtoul (s, NULL, 16);
}
/* main_loop()循環不斷執行 */
for (;;)
{
main_loop (); /* 主循環函數處理執行用戶命令 -- common/main.c */
}
命令實現
U-Boot作為Bootloader,具備多種引導內核啟動的方式。常用的go和bootm命令可以直接引導內核映像啟動。U-Boot與內核的關係主要是內核啟動過程中參數的傳遞。
1.go命令的實現
/* common/cmd_boot.c */
int do_go (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
ulong addr, rc;
int rcode = 0;
if (argc < 2) {
printf ("Usage:\n%s\n", cmdtp->usage);
return 1;
}
addr = simple_strtoul(argv[1], NULL, 16);
printf ("## Starting application at 0x%08lX ...\n", addr);
rc = ((ulong (*)(int, char []))addr) (--argc, &argv[1]); /* 運行程序 */
if (rc != 0) rcode = 1;
printf ("## Application terminated, rc = 0x%lX\n", rc); /*如果是運行linux,這條指令是否能運行?*/
return rcode;
}
go命令調用do_go()函數,跳轉到某個地址執行的。如果在這個地址準備好了自引導的內核映像,就可以啟動了。盡管go命令可以帶變參,實際使用時不用來傳遞參數。
2.bootm命令的實現
/* common/cmd_bootm.c */
int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
…… ……
/* 檢查頭部 */
if (crc32 (0, (uchar *)data, len) != checksum) {
puts ("Bad Header Checksum\n");
SHOW_BOOT_PROGRESS (-2);
return 1;
}
…… ……
/*解壓縮*/
switch (hdr->ih_comp) {
case IH_COMP_NONE:
if(ntohl(hdr->ih_load) == addr) {
printf (" XIP %s ... ", name);
} else {
#if defined(CONFIG_HW_WATCHDOG) || defined(CONFIG_WATCHDOG)
size_t l = len;
void *to = (void *)ntohl(hdr->ih_load);
void *from = (void *)data;
printf (" Loading %s ... ", name);
while (l > 0) {
size_t tail = (l > CHUNKSZ) ? CHUNKSZ : l;
WATCHDOG_RESET();
memmove (to, from, tail);
to += tail;
from += tail;
l -= tail;
}
#else /* !(CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG) */
memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);
#endif /* CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG */
}
break;
case IH_COMP_GZIP:
printf (" Uncompressing %s ... ", name);
if (gunzip ((void *)ntohl(hdr->ih_load), unc_len,
(uchar *)data, &len) != 0) {
puts ("GUNZIP ERROR - must RESET board to recover\n");
SHOW_BOOT_PROGRESS (-6);
do_reset (cmdtp, flag, argc, argv);
}
break;
#ifdef CONFIG_BZIP2
case IH_COMP_BZIP2:
printf (" Uncompressing %s ... ", name);
/*
* If we've got less than 4 MB of malloc() space,
* use slower decompression algorithm which requires
* at most 2300 KB of memory.
*/
i = BZ2_bzBuffToBuffDecompress ((char*)ntohl(hdr->ih_load),
&unc_len, (char *)data, len,
CFG_MALLOC_LEN < (4096 * 1024), 0);
if (i != BZ_OK) {
printf ("BUNZIP2 ERROR %d - must RESET board to recover\n", i);
SHOW_BOOT_PROGRESS (-6);
udelay(100000);
do_reset (cmdtp, flag, argc, argv);
}
break;
#endif /* CONFIG_BZIP2 */
default:
if (iflag)
enable_interrupts();
printf ("Unimplemented compression type %d\n", hdr->ih_comp);
SHOW_BOOT_PROGRESS (-7);
return 1;
}
}
…… …… ……
switch (hdr->ih_os) {
default: /* handled by (original) Linux case */
case IH_OS_LINUX:
do_bootm_linux (cmdtp, flag, argc, argv,
addr, len_ptr, verify);
break;
case IH_OS_NETBSD:
do_bootm_netbsd (cmdtp, flag, argc, argv,
addr, len_ptr, verify);
break;
case IH_OS_RTEMS:
do_bootm_rtems (cmdtp, flag, argc, argv,
addr, len_ptr, verify);
break;
case IH_OS_VXWORKS:
do_bootm_vxworks (cmdtp, flag, argc, argv,
addr, len_ptr, verify);
break;
case IH_OS_QNX:
do_bootm_qnxelf (cmdtp, flag, argc, argv,
addr, len_ptr, verify);
break;
}
bootm命令調用do_bootm函數。這個函數專門用來引導各種操作係統映像,可以支持引導Linux、vxWorks、QNX等操作係統。引導Linux的時候,調用do_bootm_linux()函數。
3.do_bootm_linux函數的實現
/* lib_arm/armlinux.c */
void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],
ulong addr, ulong *len_ptr, int verify)
{
theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);
… … … …
/* we assume that the kernel is in place */
printf ("\nStarting kernel ...\n\n");
… … … …
theKernel (0, bd->bi_arch_number, bd->bi_boot_params); /*啟動內核,傳遞啟動參數*/
}
do_bootm_linux()函數是專門引導Linux映像的函數,它還可以處理ramdisk文件係統的映像。這裏引導的內核映像和ramdisk映像,必須是U-Boot格式的。U-Boot格式的映像可以通過mkimage工具來轉換,其中包含了U-Boot可以識別的符號。
最後更新:2017-04-03 12:53:47