03 uboot命令的實現和內核啓動

uboot命令的實現

先分一下,uboot如何解析我們輸進去的命令,其中argv[x]就是用來保存每一條指令,uboot支持多條命令一起輸入,用分號;隔開

/* Extract arguments */
		if ((argc = parse_line (finaltoken, argv)) == 0) {
			rc = -1;	/* no command at all */
			continue;
		}

然後是在表中查找是否有輸入的命令,後續還要做一些命令合格性的檢查

/* Look up command in command table */
		if ((cmdtp = find_cmd(argv[0])) == NULL) {
			printf ("Unknown command '%s' - try 'help'\n", argv[0]);
			rc = -1;	/* give up after bad command */
			continue;
		}

		/* found - check max args */
		if (argc > cmdtp->maxargs) {
			printf ("Usage:\n%s\n", cmdtp->usage);
			rc = -1;
			continue;
		}

其中有個結構體需要注意,每個命令,都有一個名字,還有對應的參數,以及執行命令的函數,這些都被打包放在一起結構體變量中

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	CFG_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
};

find_cmd中,通過一個for循環去找命令是否存在

for (cmdtp = &__u_boot_cmd_start;
	     cmdtp != &__u_boot_cmd_end;
	     cmdtp++) {
		if (strncmp (cmd, cmdtp->name, len) == 0) {
			if (len == strlen (cmdtp->name))
				return cmdtp;	/* full match */

			cmdtp_temp = cmdtp;	/* abbreviated command ? */
			n_found++;
		}
	}

其中__u_boot_cmd_start__u_boot_cmd_end是在鏈接腳本中指定的

. = .;
	__u_boot_cmd_start = .;
	.u_boot_cmd : { *(.u_boot_cmd) }
	__u_boot_cmd_end = .;

再看一下u_boot_cmd這個段到底是幹嘛的,可以找到兩個宏定義,第一個定義了一個段,第二個在執行命令時進行宏展開

#define Struct_Section  __attribute__ ((unused,section (".u_boot_cmd")))
#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}

如果現在執行bootm 0x30007FC0,看看跟宏U_BOOT_CMD有什麼關係

U_BOOT_CMD(
 	bootm,	CFG_MAXARGS,	1,	do_bootm,
 	"bootm   - boot application image from memory\n",
 	"[addr [arg ...]]\n    - boot application image stored in memory\n"
 	"\tpassing arguments 'arg ...'; when booting a Linux kernel,\n"
 	"\t'arg' can be the address of an initrd image\n"
#ifdef CONFIG_OF_FLAT_TREE
	"\tWhen booting a Linux kernel which requires a flat device-tree\n"
	"\ta third argument is required which is the address of the of the\n"
	"\tdevice-tree blob. To boot that kernel without an initrd image,\n"
	"\tuse a '-' for the second argument. If you do not pass a third\n"
	"\ta bd_info struct will be passed instead\n"
#endif
);

展開後,發現是定義了一個cmd_tbl_t類型的結構體變量__u_boot_cmd_bootm,後面的Struct_Section也宏展開,發現段屬性被強制設爲u_boot_cmd

cmd_tbl_t __u_boot_cmd_bootm Struct_Section = {"bootm", CFG_MAXARGS, 1, do_bootm, usage, help}
Struct_Section  __attribute__ ((unused,section (".u_boot_cmd")))

這裏面的usage和help,是替換上面的字符串,其中

  • usage:“bootm - boot application image from memory\n”
  • help:剩下的所有字符串
    可以發現usage替換的後面是有逗號的,help的字符串雙引號之間沒有任何間隔符號

接下來自己寫一個命令試試看,在common目錄下創建文件cmd_hello.c

#include <common.h>
#include <watchdog.h>
#include <command.h>
#include <image.h>
#include <malloc.h>
#include <zlib.h>
#include <bzlib.h>
#include <environment.h>
#include <asm/byteorder.h>
int do_hello (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
	printf("hello world uboot sb things\n");
}
U_BOOT_CMD(
 	hello,	CFG_MAXARGS,	1,	do_hello,
 	"hello   - this is a first uboot cmd, hello world\n",
 	"this is a infomation for help, long string"
);

修改command目錄下的Makefile文件,增加一個源文件cmd_hello.o,編譯燒錄即可

uboot啓動內核

通過之前的分析,可以知道,內核是由bootm啓動,所以接下來分析整個bootcmd命令的參數

s = getenv ("bootcmd");
printf("Booting Linux ...\n");            
run_command (s, 0);

這條命令,第一句話,將內核從kernel分區讀出,並放到SDRAM的指定地址,然後從這個地址啓動內核

bootcmd=nand read.jffs2 0x30007FC0 kernel; bootm 0x30007FC0

在uboot中,一般會將分區大小寫死

#define MTDPARTS_DEFAULT "mtdparts=nandflash0:256k@0(bootloader)," \
                            "128k(params)," \
                            "2m(kernel)," \
                            "-(root)"

所以第一句話等價於下面的,從0x00060000讀取內核,大小爲0x00200000,存放到0x30007FC0

bootcmd=nand read.jffs2 0x30007FC0 kernel;
bootcmd=nand read.jffs2 0x30007FC0 0x00060000 0x00200000;

接下來看如何從nand中讀取內核,重點在於nand_read_opts

int do_nand(cmd_tbl_t * cmdtp, int flag, int argc, char *argv[])
{
...
    if (read) {
        /* read */
        nand_read_options_t opts;
        memset(&opts, 0, sizeof(opts));
        opts.buffer = (u_char*) addr;
        opts.length = size;
        opts.offset = off;
        opts.quiet      = quiet;
        ret = nand_read_opts(nand, &opts);
    } 
...
}

讀出了內核後,接下來如何啓動,分析bootm

bootm 0x30007FC0

首先分析一下uImage的結構,它是一個64字節的頭部+真正的內核組成,其中有兩項比較重要的參數
ih_load加載地址ih_ep入口地址

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;

bootm命令先去讀取uImage的頭部信息,如果發現當前內核並不位於它指定的加載地址,就將內核移到指定的加載地址,然後uboot需要告訴內核一些啓動參數,這些參數是存放到某些地址,然後內核去這些地址去讀取對應的參數,就是所謂的tag,參數傳完後,跳轉到入口地址執行

memmove (&header, (char *)addr, sizeof(image_header_t));
memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);
do_bootm_linux(...);
    setup_start_tag (bd);           //設置參數
    setup_serial_tag (&params);
    setup_revision_tag (&params);
    setup_memory_tags (bd);
    setup_commandline_tag (bd, commandline);
    等等
theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);
theKernel (0, bd->bi_arch_number, bd->bi_boot_params);  //啓動內核

啓動內核時傳遞了三個參數,其中bd->bi_arch_number是機器ID,bd->bi_boot_params是傳遞的參數地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章