转自:http://blog.csdn.net/baicaiaichibaicai/article/details/47980321
一、从上电到Uboot i.MX6 Solo从上电到Uboot
Boot Data包含:
整个内存结构如下图: 代码中的启动
... ENTRY(_start) //表明Uboot的入口地址... .text : { board/freescale/mx6q_riot/flash_header.o (.text.flasheader) cpu/arm_cortexa8/start.o ... } ... 上述的u-boot.lds代码片段,我们获取到两个信息: <p>....section ".text.flasheader", "x" b _start //跳转到Uboot入口地址 .org CONFIG_FLASH_HEADER_OFFSET //此处定义为IVT的偏移量0x400</p><p>ivt_header: .word 0x402000D1 /*Tag=0xD1, Len=0x0020, Ver=0x40*/app_code_jump_v: .word _start //Uboot入口地址reserv1: .word 0x0dcd_ptr: .word dcd_hdrboot_data_ptr: .word boot_dataself_ptr: .word ivt_headerapp_code_csf: .word 0x0reserv2: .word 0x0</p><p>boot_data: .word TEXT_BASE //Uboot加载到内存的地址image_len: .word _end_of_copy - TEXT_BASE + CONFIG_FLASH_HEADER_OFFSET //Uboot加载到内存的长度plugin: .word 0x0...dcd_hdr: .word 0x40E001D2 /* Tag=0xD2, Len=59*8 + 4 + 4, Ver=0x40 */write_dcd_cmd: .word 0x04DC01CC /* Tag=0xCC, Len=80*8 + 4, Param=0x04 */.../* DCD */</p> 可以看到IVT,Boot Data,DCD以及偏移量等关键信息,与上面的分析完全吻合.
0x00000000 的值是0xea000186,这是一条汇编指令,即flash_header.S中定义的b _start一致. 总结i.MX6 Solo芯片引入了Program Image的数据结构,用于指示片内ROM的启动代码从何处加载Uboot并且跳转到何处进入Uboot。在Uboot源码中定义了Program Image数据结构,并且链接的时候被链接到u-boot.bin的最开始的位置。烧写Uboot的时候也应该将Uboot烧写到存储设备的最低端方可正常启动。 参考1.i.MX6 Solo数据手册《IMX6SDLRM.pdf》
二、从Uboot到kernel 上 Uboot入口上一篇我们讲到Uboot的入口地址是定义在cpu/arm_cortexa8/start.S中的_start函数. <p>.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</p><p>_undefined_instruction: .word undefined_instruction_software_interrupt: .word software_interrupt_prefetch_abort: .word prefetch_abort_data_abort: .word data_abort_not_used: .word not_used_irq: .word irq_fiq: .word fiq_pad: .word 0x12345678 /* now 16*4=64 */</p> _start入口函数的第一条指令就是跳转到reset函数,所以reset函数是真正的入口地址,接下来的几条指令都是中断向量表,具体可参照ARM的体系结构. #if defined(CONFIG_CMD_NAND) puts ("NAND: "); nand_init(); /* go init the NAND */#endif 我们可以看出nand_init()函数是与CONFIG_CMD_NAND宏对应的,如果没有定义CONFIG_CMD_NAND,那么System.map里面也不会找到nand_init函数.
此处用于反汇编的是u-boot文件而不是u-boot.bin文件,两者的区别就是u-boot.bin是被strip之后的uboot,u-boot.bin里面没有包含函数名称,变量名称等信息. 例如此时的reset函数就可以通过直接看反汇编之后的文件: <p>27800670 <reset>:27800670: e10f0000 mrs r0, CPSR27800674: e3c0001f bic r0, r0, #3127800678: e38000d3 orr r0, r0, #211 ; 0xd32780067c: e129f000 msr CPSR_fc, r027800680: eb00000d bl 278006bc <cpu_init_crit></p><p>27800684 <stack_setup>:27800684: e51f002c ldr r0, [pc, #-44] ; 27800660 <_end_vect>27800688: e2400602 sub r0, r0, #2097152 ; 0x2000002780068c: e2400080 sub r0, r0, #128 ; 0x8027800690: e240d00c sub sp, r0, #1227800694: e3cdd007 bic sp, sp, #7</p><p>27800698 <clear_bss>:27800698: e51f0038 ldr r0, [pc, #-56] ; 27800668 <_bss_start>2780069c: e51f1038 ldr r1, [pc, #-56] ; 2780066c <_bss_end>278006a0: e3a02000 mov r2, #0</p><p>278006a4 <clbss_l>:278006a4: e5802000 str r2, [r0]278006a8: e1500001 cmp r0, r1278006ac: e2800004 add r0, r0, #4278006b0: 1afffffb bne 278006a4 <clbss_l>278006b4: e51ff004 ldr pc, [pc, #-4] ; 278006b8 <_start_armboot></p><p>278006b8 <_start_armboot>:278006b8: 27802224 strcs r2, [r0, r4, lsr #4]</p> 稍微看下,reset函数在调用完cpu_init_crit,执行完stack_setup和clear_bss后跳转到_start_armboot,下面我们将逐个分析. reset函数1.进入SVC32模式之后调用cpu_init_crit函数,该函数的定义也在start.S里面,主要的功能就是初始化CPU的工作环境,在通用的CPU设置(初始化L1 I/D,关闭MMU和缓存)之后跳转到板级相关的初始化lowlevel_init函数. /* Set up the stack */stack_setup: ldr r0, _TEXT_BASE @ upper 128 KiB: relocated uboot sub r0, r0, #CONFIG_SYS_MALLOC_LEN @ malloc area sub r0, r0, #CONFIG_SYS_GBL_DATA_SIZE @ bdinfo#ifdef CONFIG_USE_IRQ sub r0, r0, #(CONFIG_STACKSIZE_IRQ + CONFIG_STACKSIZE_FIQ)#endif sub sp, r0, #12 @ leave 3 words for abort-stack and sp, sp, #~7 @ 8 byte alinged for (ldr/str)d 在TEXT_BASE(即Uboot的加载地址)紧挨的地方申请CONFIG_SYS_MALLOC_LEN+CONFIG_SYS_GBL_DATA_SIZE长度的地址,然后sp指针指向该地址 /* Clear BSS (if any). Is below tx (watch load addr - need space) */clear_bss: ldr r0, _bss_start @ find start of bss segment ldr r1, _bss_end @ stop here mov r2, #0x00000000 @ clear valueclbss_l: str r2, [r0] @ clear BSS location cmp r0, r1 @ are we at the end yet add r0, r0, #4 @ increment clear index pointer bne clbss_l @ keep clearing till at end 上面的汇编语句相当于如下C语句 for(int * i = _bss_start; i < _bss_end; i += 1){ (*i) = 0x00000000;} _bss_start和_bss_end的定义都是在start.S文件中,分别是 .globl _bss_start_bss_start: .word __bss_start 和 .globl _bss_end_bss_end: .word _end 而__bss_start和_end的定义是在Uboot的链接脚本u-boot.lds中: __bss_start = .; .bss : { *(.bss) } _end = .; __bbs_start和_end之间的数据是所有的bss段,链接程序ld会根据这个链接脚本,将Uboot里面的所有静态变量,全局变量都放在这个段里面,并且在进入C函数之前将这些全局变量和静态变量的数据都清零. 总结Uboot的入口函数是有一段汇编代码来做相关的初始化,为进入C搭建运行环境,在研究Uboot的代码过程中,可以结合System.map和反汇编来研读代码,这样子花在判断宏是否定义的精力上就可以少一些. 参考暂无
三、从Uboot到kernel 中 Uboot的C函数入口上一篇讲述了Uboot的入口到C函数入口之间的一段汇编代码,执行完这段汇编代码之后Uboot就跳转到C函数的入口start_armboot. for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) { if ((*init_fnc_ptr)() != 0) { hang (); } } init_sequence的定义就在start_armboot的上方,程序顺序调用了这些函数接口,初始化了板子、时间、环境变量、波特率、串口等。 s = getenv ("bootdelay");bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY; 然后再取出默认执行的命令,如果在延时时长内没有任意键输入,则执行该命令 s = getenv ("bootcmd"); 然后等待用户的输入: if (bootdelay >= 0 && s && !abortboot (bootdelay)) {... run_command (s, 0);...} abortboot函数就是在bootdelay时间内判断用户是否有输入,如果没有就直接执行bootcmd的命令。如果用户有输入,则跳出该循环,进入下一个循环: for (;;) { ... len = readline (CONFIG_SYS_PROMPT); ... if (len == -1) puts ("<INTERRUPT>/n"); else rc = run_command (lastcommand, flag); ...} 即进入死循环,从串口读命令,然后执行命令. struct cmd_tbl_s { char *name; /* Command Name */ int maxargs; /* maximum number of arguments */ int repeatable; /* autorepeat allowed? */ /* Implementation function */ int (*cmd)(struct cmd_tbl_s *, int, int, char *[]); char *usage; /* Usage message (short) */#ifdef CONFIG_SYS_LONGHELP char *help; /* Help message (long) */#endif#ifdef CONFIG_AUTO_COMPLETE /* do auto completion on the arguments */ int (*complete)(int argc, char *argv[], char last_char, int maxv, char *cmdv[]);#endif}; 其中name是命令名称,不能有两个一样的命令名称. U_BOOT_CMD( version, 1, 1, do_version, "print monitor version", ""); 展开宏实际上就是 cmd_tbl_t __u_boot_cmd_version __attribute__ ((unused,section (".u_boot_cmd"))) = { "version",1,1,do_version,"print monitor version",""} 命令的名称是”version”,对应的接口函数是do_version,也就是说你在Uboot下面输入”version”,Uboot就会去调用do_version函数. run_command在对命令名称进行必要的处理之后,调用find_cmd函数来查找命令. cmd_tbl_t *find_cmd (const char *cmd){ int len = &__u_boot_cmd_end - &__u_boot_cmd_start; return find_cmd_tbl(cmd, &__u_boot_cmd_start, len);} __u_boot_cmd_end和__u_boot_cmd_start这两个变量,它们的定义在Uboot的链接脚本u-boot.lds中. __u_boot_cmd_start = .; .u_boot_cmd : { *(.u_boot_cmd) } __u_boot_cmd_end = .; 我们看到在__u_boot_cmd_start和__u_boot_cmd_end之间存放的是段属性为.u_boot_cmd的数据,而我们刚才上面说到每条命令的段属性都被定义成.u_boot_cmd,也就是说每一条命令都会被链接程序链接在__u_boot_cmd_start和__u_boot_cmd_end之间. /* OK - call function to do the command */ if ((cmdtp->cmd) (cmdtp, flag, argc, argv) != 0) { rc = -1; } 执行完命令之后,就又回到main_loop等待新的命令(如果用户进入命令交互界面)或者启动内核(命令就没有返回). 总结Uboot的启动流程其实不是很复杂,逻辑比较直,就是比较偏底层,需要对硬件有一定的了解,命令的处理方面有一个技巧,这个技巧在内核中也有使用到,后续我们研究到内核的时候再具体说明. 参考暂无
四、从Uboot到kernel 下 读取内核镜像上一篇我们讲到如果用户在启动Uboot过程中没有输入任意键,那么Uboot将执行默认命令bootcmd.
可以看出执行bootcmd过程中分别执行: bootm命令Uboot将内核镜像加载到${loadaddr}的位置上之后,就执行bootm命令,bootm命令的定义在uboot/common/cmd_bootm.c中,它的接口函数是do_bootm,也就是说Uboot调用了do_bootm函数.
/* relocate boot function table */ if (!relocated) { int i; for (i = 0; i < ARRAY_SIZE(boot_os); i++) if (boot_os[i] != NULL) boot_os[i] += gd->reloc_off; relocated = 1; } 启动函数表boot_os里面存放的是一些内核启动函数的函数指针.Uboot是一个通用的内核启动器,不仅支持Linux内核的启动,还支持其他例如Vxworks、netbsd等内核的启动,每一种内核启动函数各不一样,Uboot通过读取内核镜像文件头部的信息来判断该镜像为何种镜像以及在boot_os表中对应的加载函数. typedef struct image_header { uint32_t ih_magic; /* Image Header Magic Number */ uint32_t ih_hcrc; /* Image Header CRC Checksum */ uint32_t ih_time; /* Image Creation Timestamp */ uint32_t ih_size; /* Image Data Size */ uint32_t ih_load; /* Data Load Address */ uint32_t ih_ep; /* Entry Point Address */ uint32_t ih_dcrc; /* Image Data CRC Checksum */ uint8_t ih_os; /* Operating System */ uint8_t ih_arch; /* CPU architecture */ uint8_t ih_type; /* Image Type */ uint8_t ih_comp; /* Compression Type */ uint8_t ih_name[IH_NMLEN]; /* Image Name */} image_header_t; 其中ih_load指明内核最终被加载的地址,ih_ep指明内核的入口地址,ih_comp指明内核的压缩方式 ...boot_fn = boot_os[images.os.os];...boot_fn(0, argc, argv, &images);... linux内核对应的就是do_bootm_linux函数 do_bootm_linux函数do_bootm_linux函数先保存一下内核参数,然后跳转到内核入口地址. ...theKernel = (void (*)(int, int, uint))images->ep;...cleanup_before_linux ();theKernel (0, machid, bd->bi_boot_params); 传给内核的有三个参数,第一个参数为0,第二个参数为机器ID,第三个为内核参数的起始地址. <p>struct tag { struct tag_header hdr; union { struct tag_core core; struct tag_mem32 mem; struct tag_videotext videotext; struct tag_ramdisk ramdisk; struct tag_initrd initrd; struct tag_serialnr serialnr; struct tag_revision revision; struct tag_videolfb videolfb; struct tag_cmdline cmdline;</p><p> /* * Acorn specific */ struct tag_acorn acorn;</p><p> /* * DC21285 specific */ struct tag_memclk memclk; } u;};</p> struct tag_header hdr;指明里该结构体是什么内核参数以及参数长度,union u;存放参数的数据. 总结Uboot的代码中有许许多多的宏定义给阅读代码造成一些困难,我跳过许多东西,只是陈述一下启动流程.其余的代码读者有兴趣的可以去研究. 参考暂无
五、新建一条Uboot命令 原理在《IMX6Solo启动流程-从Uboot到kernel 上》中我们讲到了Uboot命令是通过宏U_BOOT_CMD来定义的.所以新建一条命令实际上就是定义一个宏的过程.
对于我这种懒人来说,复制粘帖这几行都是一件繁琐的事情.所以就想新建一条命令,能够一条命令执行以上四个步骤. <p>U_BOOT_CMD( uduboot, 2, 1, do_uduboot, "download uboot from tftp,then update it", "uduboot [uboot file name]");</p><p>U_BOOT_CMD( udkernel, 2, 1, do_udkernel, "download kernel from tftp,then update it", "udkernel [kernel file name]");</p> 然后实现函数do_uduboot和do_uduboot.接下来修改board/freescale/mx6q_riot/Makefile,将cmd_update.c编译进去. COBJS := $(BOARD).o cmd_update.o 在COBJS中增加 cmd_update.o 源码<p>#include <common.h>#include <command.h>#include <asm/io.h></p><p>#include <mmc.h>#include <net.h>#include <malloc.h></p><p>#define UBOOT_FILE_OFFSET 1024#define MMC_PAGE_SIZE 512</p><p>extern ulong TftpRRQTimeoutMSecs;extern int TftpRRQTimeoutCountMax;extern ulong load_addr;</p><p>extern enum boot_device get_boot_device(void);extern int get_mmc_env_devno(void);</p><p>/* follow function copy from $(TOPDIR)/common/update.c */static int update_load(char *filename, ulong msec_max, int cnt_max, ulong addr){ int size, rv; ulong saved_timeout_msecs; int saved_timeout_count; char *saved_netretry, *saved_bootfile;</p><p> rv = 0; /* save used globals and env variable */ saved_timeout_msecs = TftpRRQTimeoutMSecs; saved_timeout_count = TftpRRQTimeoutCountMax; saved_netretry = strdup(getenv("netretry")); saved_bootfile = strdup(BootFile);</p><p> /* set timeouts for auto-update */ TftpRRQTimeoutMSecs = msec_max; TftpRRQTimeoutCountMax = cnt_max;</p><p> /* we don't want to retry the connection if errors occur */ setenv("netretry", "no");</p><p> /* download the update file */ load_addr = addr; copy_filename(BootFile, filename, sizeof(BootFile)); size = NetLoop(TFTP);</p><p> if (size < 0) rv = 1; else if (size > 0) flush_cache(addr, size);</p><p> /* restore changed globals and env variable */ TftpRRQTimeoutMSecs = saved_timeout_msecs; TftpRRQTimeoutCountMax = saved_timeout_count;</p><p> setenv("netretry", saved_netretry); if (saved_netretry != NULL) free(saved_netretry);</p><p> if (saved_bootfile != NULL) { copy_filename(BootFile, saved_bootfile, sizeof(BootFile)); free(saved_bootfile); }</p><p> return rv;}</p><p>int DownloadAndUpdate(char * filename,unsigned int maxsize, unsigned int startblk,unsigned int fileoffset){</p><p> char * serverIP = getenv("serverip"); struct mmc * mmc = NULL; block_dev_desc_t * mmc_dev = NULL; char * s; ulong addr; uint boot_devno; unsigned int filesize; unsigned int block;</p><p> if (NULL == serverIP) { printf("error: not found tftp server IP/n"); return -1; } /* get load address of downloaded update file */ if ((s = getenv("loadaddr")) != NULL){ addr = simple_strtoul(s, NULL, 16); }else{ printf("error: not found $(loadaddr)/n"); return -1; } if (MMC_BOOT != get_boot_device()) { printf("error: only support MMC boot dev/n"); return -1; }</p><p> printf("download %s from %s/n",filename,serverIP); if(update_load(filename,100,10,addr)){ printf("error: download failure/n"); return -1; }else if (NULL == (s = getenv("filesize"))) { printf("error: download failure,not found $(filesize)/n"); return -1; }else{ filesize = simple_strtol(s,NULL,16); if (filesize < maxsize) { printf("download success,file length %d(0x%x)/n", filesize, filesize); }else{ printf("error: download failure,$(filesize) bigger then 1M/n"); return -1; } } block = (filesize + MMC_PAGE_SIZE - 1)/MMC_PAGE_SIZE;</p><p> boot_devno = readl(SRC_BASE_ADDR + 0x4); /* BOOT_CFG2[3] and BOOT_CFG2[4] */ boot_devno = (boot_devno & 0x00001800) >> 11; if (boot_devno >= CONFIG_SYS_FSL_USDHC_NUM) { printf("error: not such mmc dev %d/n",boot_devno); return -1; }else{ printf("boot dev is MMC%d/n",boot_devno); } if(NULL == (mmc = find_mmc_device(boot_devno))){ printf("Can't find mmc devno %d/n",boot_devno); return -1; } mmc_init(mmc); if (NULL == (mmc_dev = mmc_get_dev(boot_devno))) { printf("mmc init failure %d/n",boot_devno); return -1; }</p><p> if (block != mmc_dev->block_erase(boot_devno,startblk,block)) { printf("error: erase %d,%d failure/n",startblk,block); return -1; }else{ printf("erase %d,%d/n",startblk,block); } if (block != mmc_dev->block_write(boot_devno,startblk,block,(void *)(addr + fileoffset))) { printf("error: write %d,%d failure/n",startblk,block); return -1; }else{ printf("write %d,%d,0x%x/n",startblk,block,addr + fileoffset); } printf("update %s success/n",filename); return 0; }</p><p>int do_uduboot ( cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]){</p><p> char * filename = "u-boot.bin"; if (argc > 1) { filename = argv[1]; } return DownloadAndUpdate(filename,0x100000,UBOOT_FILE_OFFSET/MMC_PAGE_SIZE,UBOOT_FILE_OFFSET);};</p><p>int do_udkernel ( cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]){</p><p> char * filename = "uImage"; if (argc > 1) { filename = argv[1]; } return DownloadAndUpdate(filename,0x800000,0x800,0);};</p><p>U_BOOT_CMD( uduboot, 2, 1, do_uduboot, "download uboot from tftp,then update it", "uduboot [uboot file name]");</p><p>U_BOOT_CMD( udkernel, 2, 1, do_udkernel, "download kernel from tftp,then update it", "udkernel [kernel file name]");</p> 总结只要了解U_BOOT_CMD宏的原理之后,新建一条命令就十分简单。 参考暂无
转自:http://blog.csdn.net/baicaiaichibaicai/article/details/47980321
|
|