u-boot 内核引导与添加指令
1.Uboot:Start.s -> start_armboot ()<./lib_arm/board.c> -> main_loop ()<./common/main.c>
main_loop()会调用abortboot (bootdelay)判断在delay time内有没有键按下,并给出prompt“Hit any key to stop autoboot”,若没有键按下则run_command (s, 0)(s为默认的bootcmd)。这里默认的bootcmd需要根据内核的位置设置,若bootcmd未赋值,Uboot就会一直等待。如果敲入bootm,也会启动kernel,并且传入参数。如果使用tftp从pc中下载kernel并启动也是可以的。
2.添加u-boot命令。也就是在进入main_loop()函数后,在等待的时间里可以识别的命令。
需要修改5个地方:
- /include/config_cmd_all.h 添加CONFIG_CMD_NANDBOOT宏
- /include/config_cmd_default.h 同上
- /include/configs/SEP0611.h 添加宏 (以上头文件的修改,都是为了条件编译做准备的)
- /common/cmd_nandboot.c 自己定义的命令 具体实现命令的函数 (命令的实现与命令的注册都在这儿)
- /common/Makefile 把自己写的实现命令文件添加到Makefile中,(参加编译需要配置一下)
最主要的还是自己写的命令实现文件。
那么为什么要修改这几个文件呢?下面先分析一下正常的执行过程:
对应的函数执行过程是:
其中main_loop() run_command() 都在comman/main.c中,而find_cmd(),find_cmd_tbl()是在common/command.c中的。
1)实现命令的具体功能,在comman文件夹中建立对应的.c文件。
2)如果要添加指令,首先为了能让系统找到该指令,所以要在命令表中注册一下。
#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \
cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage, help}
“##”与“#”都是预编译操作符,“##”有字符串连接的功能,“#”表示后面紧接着的是一个字符串。
#define Struct_Section __attribute__ ((unused,section (".u_boot_cmd")))
其中,unused表示该函数或变量可能不使用,这个属性可以避免编译器产生警告信息。
凡通过U_BOOT_CMD定义的cmd_tbl_t变量会全部被放在.u_boot_cmd段当中。这也是在你写的.c文件的末尾必须要写的,为了完成注册这个动作。
比如说:U_BOOT_CMD(nandboot,0,0,do_nandboot,"boot from nand","--help") 通过宏展开就是:
cmd_tbl_t __u_boot_cmd_nandboot __attribute__((unused, section(".u_boot_cmd"))) = {"nandboot", 0, 0, do_nandboot, "boot from nand","--help"}
struct cmd_tbl_s { char *name; /* 命令名*/ int maxargs; /* 最大参数个数*/ int repeatable; /* 是否自动重复*/ int (*cmd)(struct cmd_tbl_s *, int, int, char *[]); /* 响应函数*/ char *usage; /* 简短的帮助信息*/ #ifdef CONFIG_SYS_LONGHELP char *help; /* 较详细的帮助信息*/ #endif #ifdef CONFIG_AUTO_COMPLETE /* 自动补全参数*/ int (*complete)(int argc, char *argv[], char last_char, int maxv, char *cmdv[]); #endif };
所谓注册就是把一个特定命令的信息填在这个结构体中,然后把这个结构体放到一个表中,用于查找和跳转。
#include <common.h> #include <command.h> #if defined(CONFIG_CMD_NANDBOOT) int do_bootfromnand (cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) { printf ("## you are using command nandboot\n"); run_command("nand read 0x40008000 0x400000 0x300000",0); run_command("bootm 0x40008000",0); return 0; } #endif /* -------------------------------------------------------------------- */ U_BOOT_CMD( nandboot, CONFIG_SYS_MAXARGS, 1, do_bootfromnand, "boot from nand", "boot from nand" );
bootm命令执行过程解析:
当你输入bootm命令的时候,最终执行的就是do_bootm()函数。
执行过程大体是这样的:先到ram内存中kernel所在位置(uboot从nandflash中拷贝过来的),找到uImage的header,header中保存了很多关于镜像的信息,用一个结构体来保存的,这个header一共占64个字节,还保存了一些bootargs参数。那么怎么找到这个地址呢,那就是从bootm后面跟的参数得知的,或者使用默认地址,但这些地址必须与uboot拷贝kernel到ram中的地址一样,否则是找不到的。找到header之后,就把header中的信息保存起来。后面就是关于要不要在loadkernel的问题,那么需要比较两个地址,如果mkimage时候定的load地址与bootm后面跟随的地址不一样,那么就需要再load一次。但是load之后load地址与entrypoint地址肯定是一样的。如果前两个地址一样,那么就不需要再次load,但是这时候的entrypoint就与load地址相差0x40,其实相差的就是一个header的大小,第一次是省去了header,所以相等。
load之后就可以进入do_bootm_linux()函数了。在这个函数中呢,会把bootargs参数做成TAG链表形式。然后void (*theKernel)(int zero, int arch,uint params); 其中thekernel赋值为ep,就是entrypoint即入口地址。这是mkimage制作镜像时定义的。
问题说说:
1.一直在想在bootm中,加入nand read ,完成kernel由nandflash到ram的拷贝,只需要bootm一条指令就可以完成autoboot的工作,其实这样不好,想想autoboot就知道了。所谓的autoboot,是执行的bootcmd,bootcmd=nand read 0x40008000 0x400000 0x300000 ;bootm 0x40008000;两条指令完成的。还是不要改boom的实现吧。
2.tftp 0x40008000 uImage;然后就启动内核了,把文件系统从nandflash拷贝到ram空间不是必须得,只需要把参数bootargs传给内核,内核在引导文件系统的时候会自动加载。
3.这次学习添加了2条指令,感觉还不错。关于0x40008000这个地址的理解,花了不少时间的。首先,这是个ram地址,第二kernel从nandflash拷贝到ram就是到这儿,第三bootm 后面的地址参数或者默认的地址参数,都必须与此地址相同,否则找不到header 第四 mkimage指定的load地址与之是否一样决定了kernel的解压地址与entrypoint。比如mkimage指定的load地址是0x41800000,与0x40008000不一样,那么kernel将解压到0x41800000,此时已经不含header了,所以entrypoint就必须是0x41800000。