本文讲述uboot引导内核启动的全部过程,uboot版本为2010.06
连接文件,不同平台不一样。
OUTPUT_FORMAT("elf32-littlearm","elf32-littlearm", "elf32-littlearm") //elf格式 32位,小端
OUTPUT_ARCH(arm)//arm架构
ENTRY(_start)//代码进入点arch/arm/cpu/hi3536/start.S中定义,一切从这里开始
SECTIONS
{
.= 0x00000000; //可执行文件入口地址
.= ALIGN(4);
.text ://代码段
{
__text_start = .;
arch/arm/cpu/hi3536/start.o (.text)
drivers/ddr/ddr_training_impl.o (.text)
drivers/ddr/ddr_training_ctl.o (.text)
drivers/ddr/ddr_training_boot.o (.text)
drivers/ddr/ddr_training_custom.o (.text)
__init_end = .;
ASSERT(((__init_end - __text_start) < 0x16000), "init sectionstoo big!");
*(.text)
}
.= ALIGN(4);
.rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) } //只读数据段
.= ALIGN(4);
.data : { *(.data) } //数据段
.= ALIGN(4);
.got : { *(.got) }
__u_boot_cmd_start = .; //存放uboot命令
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;
.= ALIGN(4);
__bss_start = .; //bss段
.bss : { *(.bss) }
_end = .;
}
Arm在uboot下的入口文件,纯汇编,由于代码太多,这里描述基本过程:
(1)定义入口
.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
(2)进入reset第一步
A. set the cpu to SVC32 mode
B. Invalidate L1 I/D
C. Invalidate L1 D-cache
D. disable MMU stuff and caches
(3)第二步
判断boot方式,从bootrom / spi /nand中的一种启动,撤销地址空间映射。
假设启动方式为spi nor flash启动,这时起始地址空间映射到spi nor flash地址空间,但是spi nor flash不能直接执行代码,这里猜测是:映射后硬件自己从spi nor flash中将部分代码搬运到内部开始执行。
(4)第三步
进行pll /flash/ddr等的初始化
(5)第4步
拷贝flash数据到DDR空间。在这步之前只是搬运了初始化用的最基本的部分到内部,但是整个uboot并未搬运到DDR,这里则进行完整的搬运:r1-DDR地址0x40c00000(src/board/hi3536/config.mk中定义),r0-flash地址为spi地址,r2大小为_bss_start- _armboot_start(_start),memcpy(r1, r0, r2)。内存布局见第8节
根据uboot.map可以看到连接收地址发生了变化从0x40c00000开始,修改这个的地方由底层的config.mk完成:
LDFLAGS += -Bstatic -T $(obj)u-boot.lds$(PLATFORM_LDFLAGS)
ifneq ($(TEXT_BASE),)
LDFLAGS += -Ttext $(TEXT_BASE)
endif
连接地址替换成TEXT_BASE(src/board/hi3536/config.mk定义)
(6)第5步
跳转到DDR,进入第一个C语言入口
clbss_l:
str r2, [r0] @ clear BSSlocation
cmp r0, r1 @ are we atthe end yet
add r0, r0, #4 @ incrementclear index pointer
bne clbss_l @ keepclearing till at end
ldr pc, _start_armboot @ jump to C code
_start_armboot: .word start_armboot(0x40c04fdc start_armboot连接地址)
init_fnc_t *init_sequence[] = { //初始化函数列表
timer_init, /* initializetimer before usb init */
board_init, /* basic boarddependent setup */
env_init, /* initializeenvironment */
init_baudrate, /* initialzebaudrate settings */
serial_init, /* serialcommunications setup */
console_init_f, /* stage 1init of console */
display_banner, /* say that weare here */
dram_init, /* configureavailable RAM banks */
NULL,
};
void start_armboot (void)
{
/* Pointer iswritable since we allocated a register for it */
gd = (gd_t*)(_armboot_start - CONFIG_SYS_MALLOC_LEN - sizeof(gd_t));
_armboot_start = 0x40c00000(内存起始地址0x40000000,预留了12M)
CONFIG_SYS_MALLOC_LEN = 4 * 1024 * 1024(自己修改)
…
//运行所有初始化函数
for(init_fnc_ptr = init_sequence; *init_fnc_ptr;++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
…
#ifdefCONFIG_CMD_SF
spi_flash_probe(0, 0, 0, 0); //初始化spi flash
#endif
env_relocate ();//初始化uboot环境变量
…
#ifdefined(CONFIG_CMD_NET)
eth_initialize(gd->bd); //网络初始化
#endif
#ifdefSTART_UBOOT_NO_NET_OPERATION
int ret = 0;
ret = eth_init(gd->bd); //网络驱动初始化,该部分自己添加
if(!ret)
eth_halt();
#endif
…
for(;;) {
main_loop (); //进入main主函数
}
}
void main_loop (void)
{
…
#ifdefined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
s= getenv ("bootdelay"); //获取bootdelay次数(即秒数)
bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY;
debug ("### main_loop entered: bootdelay=%d\n\n", bootdelay);
#ifdef CONFIG_HI3536_A7
//modify by hlb
//s = getenv("slave_cmd");
s = getenv("bootcmd");
#else
s = getenv("bootcmd"); //获取bootcmd命令内容
/* bootcmd=usbupdate;tftpupdate; fsload; bootm
usbupdate: 扫描U盘,升级U盘文件,自己添加不进行说明
tftpupdate: tftp自动化升级,自己添加不进行说明
fsload:加载内核文件, 自己修改,后续章节说明
bootm:引导内核,后续章节说明
*/
#endif
debug ("### main_loop: bootcmd=\"%s\"\n", s ? s :"
//在bootdelay时间内连续输入3个字符*(自己修改为*),则不执行bootcmd命令,进入uboot命令行,否则执行bootcmd命令
if (bootdelay >= 0 && s && !abortboot (bootdelay)) {
# ifndef CONFIG_SYS_HUSH_PARSER
run_command (s, 0); //执行bootcmd命令
# else
parse_string_outer(s, FLAG_PARSE_SEMICOLON |
FLAG_EXIT_FROM_LOOP);
# endif
}
…
//进入uboot命令行,响应用户输入的命令
for (;;) {
len = readline (CONFIG_SYS_PROMPT);
flag = 0; /* assume no special flags for now */
if (len > 0)
strcpy (lastcommand, console_buffer);
else if (len == 0)
flag |= CMD_FLAG_REPEAT;
if (len == -1)
puts ("
else
run_command(lastcommand, flag);
/* invalid command or not repeatable,forget it */
lastcommand[0] = 0;
}
}
bootcmd环境变量中fsload命令,实际运行函数就为该文件的do_fsload函数。
int do_fsload(cmd_tbl_t *cmdtp, int flag,int argc, char *argv[])
{
char *fsname;
char *filename = "/boot/uImage"; //读取内核完整路径名
int size = 0;
struct part_info *part;
ulong offset = CONFIG_SYS_LOAD_ADDR; //=0x42000000,内核加载的内存地址
part = spi_cramfs_init(); //读取内核所在根文件系统信息
if (part ==0)
{
return 1;
}
printf("### squashfs loading '%s' to 0x%lx\n", filename, offset);
if(squashfs_check(part)) //这里使用自己添加的squashfs文件系统,默认是cramfs。
{
size = squashfs_fload((char *) offset, part, filename); //加载内核到上述内存地址
}
if (size > 0) { //加载成功
char buf[10];
printf("### %s load complete: %d bytes loaded to 0x%lx\n",
fsname, size, offset);
sprintf(buf, "%x", size);
setenv("filesize", buf);
}else { //加载失败
printf("### %s LOAD ERROR<%x> for %s!\n", fsname, size,filename);
}
return !(size > 0);
}
bootcmd环境变量中bootm命令,实际运行函数就为该文件的do_bootm函数。
int do_bootm (cmd_tbl_t *cmdtp, int flag,int argc, char *argv[])
{
…
if(bootm_start(cmdtp, flag, argc, argv)) //该函数解析内核image头,获取内核信息
return 1;
/*
images.os.image_start= 0x42000040 (0x40 64字节头大小)
images.os.image_len= 0x25fa88
images.os.type= IH_TYPE_KERNEL
images.os.comp= IH_COMP_NONE
images.os.os= IH_OS_LINUX
images.os.start= 0x42000000
images.os.end = 0x4225fac8
images.os.load= 0x40008000 真正的内核会加载到该地方
*/
…
ret = bootm_load_os(images.os,&load_end, 1);
/*
将内核从os.image_start搬运到os.load地址
load_end = 0x40267a88(0x40008000+0x25fa88)
*/
…
boot_fn= boot_os[images.os.os]; //根据操作系统类型获取对应操作系统入口函数,这里为do_bootm_linux
…
boot_fn(0, argc, argv, &images); //运行do_bootm_linux函数,成功则会进入内核
…
do_reset (cmdtp, flag, argc, argv); //如果失败则重启uboot
}
int do_bootm_linux(int flag, int argc, char*argv[], bootm_headers_t *images)
{
intmachid = bd->bi_arch_number; //传递给内核的机器码hi3536 0x8000
void (*theKernel)(int zero, intarch, uint params); //进入内核函数指针
theKernel= (void (*)(int, int, uint))images->ep; //ep =0x40008000
…
#ifdefined (CONFIG_SETUP_MEMORY_TAGS) || \
defined (CONFIG_CMDLINE_TAG) || \
defined (CONFIG_INITRD_TAG) || \
defined (CONFIG_SERIAL_TAG) || \
defined (CONFIG_REVISION_TAG)
setup_start_tag (bd);
#ifdefCONFIG_SETUP_MEMORY_TAGS
setup_memory_tags (bd);
#endif
#ifdefCONFIG_CMDLINE_TAG
setup_commandline_tag (bd,commandline);
#endif
….
setup_end_tag (bd);
#endif
/* 需要传递给内核的各个TAG,放入参数列表 */
…
theKernel (0, machid, bd->bi_boot_params); //进入内核,无返回
/* 第2个参数为机器码,第二个参数为传递给内核参数列表的首地址=0x40000100 */
}