uboot启动第二阶段

前言:

本帖是学习韦东山老师uboot教程,粗略总结的课堂笔记及自己感悟。如想深入了解uboot 启动过程,请绕路。

相关帖子:

uboot启动第一阶段

UBOOT初体验:编译、下载

初识uboot Makefile

概述

uboot第一阶段在汇编代码中进行了硬件初始化(看门狗、中断、堆栈)

第二阶段主要是C代码进行硬件初始化、设置内核启动参数、将内核加载到内存(SDRAM)并启动内核。

注意:uboot的目的就是启动内核。其他的初始化,参数设置都是辅助启动内核的。这就是唯物辩证法的认识现象和本质。

uboot启动第一阶段 中最后会调用start_armboot()接口进行第二阶段工作。

1-  start_armboot()

start_armboot主要将内核从flash读取到内存(SDRAM, 64M)中,并启动内核。

这里至少涉及flash(NOR/NAND FLASH)初始化,读取等操作,当然也包括一些参数获取。

之后进入死循环接口main_loop ()。

/*NOR FLASH初始化*/
#ifndef CFG_NO_FLASH
	/* configure available FLASH banks */
	size = flash_init ();
	display_flash_config (size);
#endif /* CFG_NO_FLASH */


/*Nand FLASH初始化*/
#if (CONFIG_COMMANDS & CFG_CMD_NAND)
	puts ("NAND:  ");
	nand_init();		/* go init the NAND */
#endif

/*死循环*/
/* main_loop() can return to retry autoboot, if so just run it again. */
for (;;) {
	main_loop ();
}

2- main_loop()

进行一些命令行的检测,或者直接进行启动内核。

场景1: 没有按下空格键,直接启动内核

对单板上电后,如果在倒数前没有按下空格键,则直接启动内核run_command (s, 0);

/*获取bootcmd参数 启动内核 */
s = getenv ("bootcmd");
if (bootdelay >= 0 && s && !abortboot (bootdelay)
{
    printf("Booting Linux ...\n");          
    run_command (s, 0);
}

abortboot()接口判断有无按键按下,并打印倒数计时

Hit any key to stop autoboot:  0

场景2: 倒数计时前按下空格键,运行厂商的菜单栏

如果我们在倒数计结束前前按下空格键,运行下面语句,进入厂商编写的菜单应用程序。

 run_command("menu", 0);

菜单程序的入口为:

void menu_shell(void):
    ...   
    case 'b':
    {
        printf("Booting Linux ...\n");
        strcpy(cmd_buf, "nand read.jffs2 0x30007FC0 kernel; bootm 0x30007FC0");
        run_command(cmd_buf, 0);
        break;
    }
    
    case 'k':
    {
        strcpy(cmd_buf, "usbslave 1 0x30000000; nand erase kernel; nand write.jffs2 0x30000000 kernel $(filesize)");
        run_command(cmd_buf, 0);
        break;
    }    
    ... 

菜单程序主要是为简化初学者的操作,将一系列命令集合在一个命令中。比如我们输入k,低层的uboot命令为:

usbslave 1 0x30000000; nand erase kernel; nand write.jffs2 0x30000000 kernel $(filesize)

场景3:uboot菜单模式中输入q,进入到uboot命令行模式

在uboot命令行中,输入help可以查看uboot支持的命令:

OpenJTAG> help
?       - alias for 'help'
autoscr - run script from memory
base    - print or set address offset
bdinfo  - print Board Info structure
boot    - boot default, i.e., run 'bootcmd'
...

假设我们在nand flash上已经下载了内核,我们可以在命令行中输入命令,手动将内核加载到SDRAM中。

nand read.jffs2 0x30007FC0 kernel 
bootm 0x30007FC0

或者,先查看nand flash的分区情况,使用   nand read.jffs2  SDRAM存放地址  KERNEL分区偏移地址  KERNEL分区大小方式,加载内核。

#查看分区情况。 可以看到kernel分区偏移地址为0x00060000,分区大小为0x00200000
#mtd
device nand0 <nandflash0>, # parts = 4
 #: name                        size            offset          mask_flags
 0: bootloader          0x00040000      0x00000000      0
 1: params              0x00020000      0x00040000      0
 2: kernel              0x00200000      0x00060000      0
 3: root                0x0fda0000      0x00260000      0
 
#将kernel分区内容写道内存0x30007FC0
#nand read.jffs2 0x30007FC0 0x00060000  0x00200000

#加载0x30007FC0地址数据
bootm 0x30007FC0

正常情况下,我们在倒数计时前没有按空格键,则读取bootcmd信息。

在uboot 命令行模式下输入print,可以看到bootcmd信息:

OpenJTAG> print
bootargs=noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0,115200
bootcmd=nand read.jffs2 0x30007FC0 kernel; bootm 0x30007FC0
bootdelay=2
baudrate=115200
ethaddr=08:00:3e:26:0a:5b
ipaddr=192.168.1.17
serverip=192.168.1.11
netmask=255.255.255.0
stdin=serial
stdout=serial
stderr=serial
mtdids=nand0=nandflash0
mtdparts=mtdparts=nandflash0:256k@0(bootloader),128k(params),2m(kernel),-(root)
partition=nand0,0
mtddevnum=0
mtddevname=bootloader

Environment size: 450/131068 bytes

#从flash 将kernel分区内容加载到SDRAM 0x30007FC0位置
bootcmd=nand read.jffs2 0x30007FC0 kernel;

#从0x30007FC0地址启动内核
bootm 0x30007FC0

3- do_bootm()

bootm命令对应的接口为:do_bootm().

我们执行bootm 0x30007FC0,给do_bootm 传入内核在SDRAM上的加载地址

我们使用的内核为UImage=头部+真正内核。

3.1- 解析头部信息

头部参数结构体为,大小为64M:

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;

我们在do_bootm中解析UImage头部信息,根据头部信息进行不同处理。

我们在bootm传入的地址为0x30007FC0,头部大小为64byte,所以计算出内核的加载地址为:

image_header->ih_load = 0x30007FC0 + 64 = 0x3000 8000

解析到我们的image类型为linux,执行do_bootm_linux()

/*获取头部信息 */
image_header_t *hdr = &header;
memmove (&header, (char *)addr, sizeof(image_header_t));

/*解析头部信息*/
if (ntohl(hdr->ih_magic) != IH_MAGIC)
...



/*压缩方式*/
switch (hdr->ih_comp) 
{
    case IH_COMP_NONE:
        /* 如果头部大小 + bootm传入地址  =  hdr->ih_load,则将内核移动到hdr->ih_load地址处*/
        if(ntohl(hdr->ih_load) == data) {
			/*不移动内核*/
		} else {
                /*移动内核到hdr->ih_load*/
        }

}
...

/*image类型*/
switch (hdr->ih_type)
{
    case IH_OS_LINUX:
        do_bootm_linux  (cmdtp, flag, argc, argv,
			     addr, len_ptr, verify);
}

3.2- do_bootm_linux()

theKernel (0, bd->bi_arch_number, bd->bi_boot_params);用于启动内核,启动内核时给内核传入了3个参数,第二个参数为arch_number,第三个参数为boot参数,参数设置在3.3中介绍。

/* 内核入口 */
theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);

/* 内核传递参数 */
setup_start_tag (bd);
setup_revision_tag (&params);
setup_memory_tags (bd);
setup_commandline_tag (bd, commandline);
setup_end_tag (bd);


/*启动内核*/
theKernel (0, bd->bi_arch_number, bd->bi_boot_params);

3.3- uboot给内核传参

uboot将内核加载到SDRAM后,在启动内核前,需要给内核传递一些参数。我们将参数按照结构体struct tag形式保存在SDRAM指定地址处,等开始加载内核后从SDRAM指定地址按照struct tag解析处参数。

参数存放地址:

/* \board\100ask24x0\100ask24x0.c */
gd->bd->bi_boot_params = 0x30000100;

/* \lib_arm\armlinux.c */
params = (struct tag *) bd->bi_boot_params;

参数存放结构体:

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;

		/*
		 * Acorn specific
		 */
		struct tag_acorn	acorn;

		/*
		 * DC21285 specific
		 */
		struct tag_memclk	memclk;
	} u;
};

设置的参数有:

static void setup_start_tag (bd_t *bd)
{
	params = (struct tag *) bd->bi_boot_params;

	params->hdr.tag = ATAG_CORE;
	params->hdr.size = tag_size (tag_core);

	params->u.core.flags = 0;
	params->u.core.pagesize = 0;
	params->u.core.rootdev = 0;

	params = tag_next (params);
}

static void setup_memory_tags (bd_t *bd)
{
	int i;

	for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) {
		params->hdr.tag = ATAG_MEM;
		params->hdr.size = tag_size (tag_mem32);

		params->u.mem.start = bd->bi_dram[i].start;
		params->u.mem.size = bd->bi_dram[i].size;

		params = tag_next (params);
	}
}

static void setup_commandline_tag (bd_t *bd, char *commandline)
{
	char *p;

	if (!commandline)
		return;

	/* eat leading white space */
	for (p = commandline; *p == ' '; p++);

	/* skip non-existent command lines so the kernel will still
	 * use its default command line.
	 */
	if (*p == '\0')
		return;

	params->hdr.tag = ATAG_CMDLINE;
	params->hdr.size =
		(sizeof (struct tag_header) + strlen (p) + 1 + 4) >> 2;

	strcpy (params->u.cmdline.cmdline, p);

	params = tag_next (params);
}

static void setup_end_tag (bd_t *bd)
{
	params->hdr.tag = ATAG_NONE;
	params->hdr.size = 0;
}

结合初识uboot Makefileuboot启动第一阶段  中对SDRAM的地址规划,我们可以画出SDRAM的存放内容如下:

 

书里画的如下:

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值