您的位置:首页 > 运维架构 > Linux

U-Boot启动过程 分类: HI3531 arm-linux-Ubuntu 2013-12-18 09:36 587人阅读 评论(0) 收藏

2013-12-18 09:36 1186 查看
开发板上电后,执行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可以识别的符号。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐