转自:http://blog.csdn.net/baicaiaichibaicai/article/details/47980321
一、从上电到Uboot
i.MX6 Solo从上电到Uboot
-
板子上电之后,先执行的是片内ROM的一段启动代码,具体可见imx6solo数据手册中< Chapter 8: System Boot >。启动代码根据寄存器配置及/或外部管脚的配置,决定进入下载模式或者从某处(Flash、emmc、SD卡等存储设备)启动Uboot。
如果是从emmc启动Uboot,根据数据手册所描述的,首先启动代码会将emmc前4K的数据拷贝到内部RAM,这些数据里面包含了Program image数据.
Program image是飞思卡尔定义的一个镜像数据结构,包括镜像向量表IVT,Boot Data,设备配置数据DCD和用户代码数据等信息,详见数据手册<8.6 Programe image>.
另:加载数据长度大小和IVT的偏移量根据启动设备类型的不同而不同,详见下表:
至于IVT为何要有一个偏移量,个人认为是空开位置用于存放MBR,目前RIotBoard是没有用到. -
IVT主要包含(详见< 8.6.1.1: Image Vector Table Structure >):
名称 | 含义 |
---|---|
header | IVT头部,标识IVT和IVT长度 |
entry | 第一条指令的入口地址,即Uboot的入口 |
dcd | DCD数据的地址,紧跟在Boot Data后面 |
Boot Data | Boot Data的地址,Boot Data紧跟在IVT后面 |
csf | CSF的地址 |
Boot Data包含:
名称 | 含义 |
---|---|
start | 启动数据加载到内存的地址 |
length | 启动数据加载到内存的长度 |
plugin |
整个内存结构如下图:
3. 启动代码根据Boot Data的指示,将emmc中的前Boot Data->length字节拷贝到Boot Data->start的内存位置上.拷贝之后的内存结构如下图:
左边的是片内RAM中的数据,右边的是内存中的数据.
可以看到IVT->entry指向的是内存中Application(即图中深蓝色的线)的位置,Application即是Uboot镜像,最后跳转到IVT->entry指定的地址,即进入UBoot.
代码中的启动
- 研究Uboot时,Uboot的链接脚本u-boot.lds是一个关键的文档,它指示里Uboot的入口地址以及各个段的分布:
...
ENTRY(_start) //表明Uboot的入口地址 ...
.text : { board/freescale/mx6q_riot/flash_header.o (.text.flasheader) cpu/arm_cortexa8/start.o ...
} ...
上述的u-boot.lds代码片段,我们获取到两个信息:
a.Uboot的入口地址是_start(它的定义是在cpu/arm_cortexa8/start.o中)
b.Uboot.bin第一个存放的是board/freescale/mx6q_riot/flash_header.o里面的代码,第二个存放的是cpu/arm_cortexa8/start.o里的代码.
根据上面的分析我们知道,在Uboot之前有一个Program image,所以我们不难猜到board/freescale/mx6q_riot/flash_header.o里定义的就是Program image.
2. 打开board/freescale/mx6q_riot/flash_header.S:
<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 0x0 dcd_ptr: .word dcd_hdr boot_data_ptr: .word boot_data self_ptr: .word ivt_header app_code_csf: .word 0x0 reserv2: .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以及偏移量等关键信息,与上面的分析完全吻合.
我们可以通过hexdump打印一下已编译好的Uboot.bin再次核对:
$ hexdump -n 2048 u-boot.bin
0000000 0186 ea00 0000 0000 0000 0000 0000 0000
0000010 0000 0000 0000 0000 0000 0000 0000 0000
*
0000400 00d1 4020 0620 2780 0000 0000 042c 2780
0000410 0420 2780 0400 2780 0000 0000 0000 0000
0000420 0000 2780 9f98 0006 0000 0000 01d2 40e0
0000430 01cc 04dc 0e02 7407 0c00 0000 0e02 5407
0000440 0000 0000 0e02 ac04 0000 3000 0e02 b004
0000450 0000 3000 0e02 6404 0000 3000 0e02 9004
0000460 0000 3000 0e02 4c07 0000 3000 0e02 9404
0x00000000 的值是0xea000186,这是一条汇编指令,即flash_header.S中定义的b _start一致.
0x00000004~0x000003FF都是0x00000000,与flash_header.S中的定义.org CONFIG_FLASH_HEADER_OFFSET一致.
0x00000400的值是0x402000D1,与flash_header.S中的定义ivt_header: .word 0x402000D1一致.
…
所以烧写的时候,一定要把u-boot.bin烧写到emmc的最前端,这样子片内启动代码才能正常启动Uboot.
总结
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函数.
start.S里放的是硬件相关的汇编代码,主要是为进入C函数之前搭建环境,例如初始化CPU,初始化堆栈,初始化BSS等.
首先看下_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的体系结构.
在reset函数中我们看到了许多Uboot里的宏定义,每个宏定义都会导致Uboot执行流程的不同,如果要去判断Uboot里是否开启或者关闭这些宏定义我们需要搜索这些宏定义的位置并且判断该开发板是否开启(许多宏定义在每个开发板中都有定义),这些宏不仅给研究代码带来一些麻烦,而且还容易出错.
我一般是没去搜索这些宏定义,而且结合以下两个途径来看代码:
1. 编译完Uboot之后,会导出Uboot的变量符号、函数符号文件System.map文件,在宏所包含的代码段里面,有一些函数名可以看出跟这个宏相对应的,我们可以根据在System.map里面搜索这个函数名来判断宏是否有定义,例如:
#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函数.
2. 一些比较简单的函数可以直接看汇编.先将编译好的Uboot反汇编:
arm-fsl-linux-gnueabi-objdump -D u-boot > u-boot.asm
此处用于反汇编的是u-boot文件而不是u-boot.bin文件,两者的区别就是u-boot.bin是被strip之后的uboot,u-boot.bin里面没有包含函数名称,变量名称等信息.
例如此时的reset函数就可以通过直接看反汇编之后的文件:
<p>27800670 <reset>: 27800670: e10f0000 mrs r0, CPSR 27800674: e3c0001f bic r0, r0, #31 27800678: e38000d3 orr r0, r0, #211 ; 0xd3 2780067c: e129f000 msr CPSR_fc, r0 27800680: 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 ; 0x200000 2780068c: e2400080 sub r0, r0, #128 ; 0x80 27800690: e240d00c sub sp, r0, #12 27800694: 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, r1 278006ac: e2800004 add r0, r0, #4 278006b0: 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函数.
lowlevel_init函数的定义在board/freescale/mx6q_riot/lowlevel_init.S,我就不展开了.
2.堆栈指针的设置:
/* 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指针指向该地址
3.清除bss
/* 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 value clbss_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函数之前将这些全局变量和静态变量的数据都清零.
4. 最后,程序跳转到_start_armboot函数,_start_armboot也是在start.S中定义,实际上就是跳转到start_armboot函数,定义在lib_arm/board.c.
总结
Uboot的入口函数是有一段汇编代码来做相关的初始化,为进入C搭建运行环境,在研究Uboot的代码过程中,可以结合System.map和反汇编来研读代码,这样子花在判断宏是否定义的精力上就可以少一些.
参考
暂无
三、从Uboot到kernel 中
Uboot的C函数入口
上一篇讲述了Uboot的入口到C函数入口之间的一段汇编代码,执行完这段汇编代码之后Uboot就跳转到C函数的入口start_armboot.
每个开发板的初始化流程都不一样,在这里我就不重点罗列出板子初始化的各个细节,我也没有研究的那么具体,我只记录以下我认为比较重要的点:
1. 基本的初始化:在start_armboot函数的开头有这么一段代码
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) { if ((*init_fnc_ptr)() != 0) { hang (); } }
init_sequence的定义就在start_armboot的上方,程序顺序调用了这些函数接口,初始化了板子、时间、环境变量、波特率、串口等。
执行完这些比较基本的初始化和其他初始化之后,Uboot进入main_loop函数
2. main_loop主要是接收和处理命令,如果在Uboot进入等待命令延时的时候按下任意键,Uboot就会进入命令交互模式,否则Uboot将启动内核。
main_loop先从环境变量bootdelay中取出延时启动时长:
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); ... }
即进入死循环,从串口读命令,然后执行命令.
3. 无论是用户输入的命令还是默认执行的命令,都是通过run_command来执行的.
每一条命令都是一个struct cmd_tbl_s的结构体,定义如下:
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是命令名称,不能有两个一样的命令名称.
int (*cmd)(struct cmd_tbl_s *, int, int, char *[]);是命令的接口函数,调用该接口执行命令对应的功能.
在Uboot下我们是通过U_BOOT_CMD宏来定义一条命令,例如:
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函数.
每个命令的段属性都是 “.u_boot_cmd”,这一点很重要,下面我们会提到
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之间.
理解里这一点,我们就不会难理解find_cmd_tbl函数,就是在__u_boot_cmd_start和__u_boot_cmd_end之间比较每条命令的名称,如果一致就返回命令的数据结构的指针.
在find_cmd有正确找到命令对应的数据结构指针后,run_command就会通过调用该命令的接口来执行命令:
/* OK - call function to do the command */ if ((cmdtp->cmd) (cmdtp, flag, argc, argv) != 0) { rc = -1; }
执行完命令之后,就又回到main_loop等待新的命令(如果用户进入命令交互界面)或者启动内核(命令就没有返回).
下一篇我们将研究一下默认执行bootcmd命令来启动内核的流程.
总结
Uboot的启动流程其实不是很复杂,逻辑比较直,就是比较偏底层,需要对硬件有一定的了解,命令的处理方面有一个技巧,这个技巧在内核中也有使用到,后续我们研究到内核的时候再具体说明.
参考
暂无
四、从Uboot到kernel 下
读取内核镜像
上一篇我们讲到如果用户在启动Uboot过程中没有输入任意键,那么Uboot将执行默认命令bootcmd.
在进入Uboot的命令交互模式后输入printenv:
…
bootargs=console=ttymxc1,115200 nosmp video=mxcfb0:dev=hdmi,1280x720M@60,bpp=32 video=mxcfb1:off
bootargs_mmc=setenv bootargs ${bootargs} root=/dev/mmcblk0p1 rootwait
bootcmd_mmc=run bootargs_mmc; mmc dev 3; mmc read ${loadaddr} 0x800 0x2000; bootm
bootcmd=run bootcmd_mmc
…
可以看出执行bootcmd过程中分别执行:
1. 设置bootargs(启动参数)到环境变量
2. mmc切换到dev 3(即emmc)中.
3. 从mmc dev 3(即emmc)的偏移量为0x800块的位置读取0x2000块长度的数据(一个数据块的大小是512B)到${loadaddr}
4. 执行bootm命令
根据RIotBoard板子上的配置,在emmc上,从0~1M的地址内存放的是Uboot(包括环境变量),从1~9M的地址内存放的是内核镜像,其他的存放文件系统.
所以第2、3步实际上就是将emmc上的1~9M上的数据拷贝到内存上${loadaddr}的位置.
bootm命令
Uboot将内核镜像加载到${loadaddr}的位置上之后,就执行bootm命令,bootm命令的定义在uboot/common/cmd_bootm.c中,它的接口函数是do_bootm,也就是说Uboot调用了do_bootm函数.
- 重新计算启动函数表地址boot_os
/* 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表中对应的加载函数.
在Uboot汇编入口的那段代码上有个函数是relocate函数,将Uboot的执行镜像重新定位到新地址,如果有重新定位(RIotBoard开发板没有重新定位),需要重新计算内核启动函数表的位置,这样子才能准确的调用.
2. 根据argc的值判断是否有子命令,如果有则执行子命令并返回.
3. 调用bootm_start函数,该函数主要就是读取镜像文件头部信息,然后到填充static bootm_headers_t images;这个数据结构.内核镜像文件就是uImage文件,它是在Image上面增加一个头部信息,后续讲到内核的时候再详细讲它的生成流程,在这边我稍微讲下头部信息,它包含如下信息:
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指明内核的压缩方式
另外一点就是如果执行bootm时没有带参数,则do_bootm默认从${loadaddr}处读取内核镜像文件,否则,从第一个参数指示的地址读取内核镜像文件.
4. 调用bootm_load_os函数,该函数就是根据已填充好的static bootm_headers_t images;数据结构来加载内核,其中包括:根据内核压缩方式对内核进行解压缩,将内核移动到指定的加载地址.
5. 跳转到对应的启动函数
... 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,第三个为内核参数的起始地址.
内核的各个参数是一个struct tag params结构体,该结构体的定义如下:
<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;存放参数的数据.
内核参数以ATAG_CORE开头,以ATAG_NONE结尾,依次存放在以bd->bi_boot_params为起始地址的内存中,然后传给内核进行解析.
最后,调用theKernel,进入内核…
总结
Uboot的代码中有许许多多的宏定义给阅读代码造成一些困难,我跳过许多东西,只是陈述一下启动流程.其余的代码读者有兴趣的可以去研究.
参考
暂无
五、新建一条Uboot命令
原理
在《IMX6Solo启动流程-从Uboot到kernel 上》中我们讲到了Uboot命令是通过宏U_BOOT_CMD来定义的.所以新建一条命令实际上就是定义一个宏的过程.
在新建一条命令之前,我们先要区分一下新建的命令是属于通用的还是专用的,所谓通用就是你的命令与具体的硬件无关,例如tftp命令,bootm命令等,专用的就是你的命令只能在你的硬件上面跑.
Uboot的命令一般在两个地方定义,一个是common目录下,在这里面定义的一般都是通用的命令,另外一个就是在board/xxx目录下,定义的就是专用的,当然这不是强制要求,只是一个建议,这样定义命令能够让逻辑分开.
我在每次更新Uboot或者内核的时候都要执行
tftp u-boot.bin
mmc dev 3
mmc erase 0 400
mmc write ${loadaddr} 0 400
对于我这种懒人来说,复制粘帖这几行都是一件繁琐的事情.所以就想新建一条命令,能够一条命令执行以上四个步骤.
在RIotBoard开发板上,存储设备是emmc,即port 3,下载烧写地址和长度都是固定的,所以这是一条专用的命令.当然你也可以将这些数据以参数的形式传给命令,将命令拓展成通用的.其实都一样.
首先在board/freescale/mx6q_riot/目录下新建cmd_update.c,定义命令
<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
重新编译Uboot并下载烧写,在Uboot的命令交互模式下就可以看到新加两条命令uduboot和udkernel.
另:使用tftp需要配置Uboot的环境变量ipaddr和serverip,每次烧写Uboot,之前配置的环境变量值都会被恢复默认值,所以可以通过修改ipaddr和serverip的默认值,后续烧写Uboot就无需修改IP地址.
源码
<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宏的原理之后,新建一条命令就十分简单。
另:上述的功能也可以通过将多条命令用分号隔开,保存到一个环境变量中,然后通过run命令来执行,达到一样的效果。类似bootcmd的做法,这里就不再赘述.
参考
暂无
转自:http://blog.csdn.net/baicaiaichibaicai/article/details/47980321