学习笔记--- U-BOOT从启动到引导内核过程分析

U-BOOT是操作系统启动之前的一段引导程序,所以他的终极目的就是要启动内核;所以他有三个重要的任务:

1 初始化硬件信息与环境

2 从NANDFLASH内核分区读取内核到SDRAM

3 跳转到内核入口地址执行内核

这三个是核心,其他的什么烧内核,文件系统等功能都是为了在开发阶段协助开发使用的功能,但最后都是为了引导内核,下面按照上面三个重要的任务分析,先看启动:

#include <config.h>       @实际上就是uboot在执行配置脚本的时候生成的"#include <configs/$BOARD.h>" 
#include <version.h>

/*
 *************************************************************************
 *
 * Jump vector table as in table 3.1 in [1]
 *
 *************************************************************************
 */

/*
_start是GNU汇编器的默认入口标签,.globl将_start声明为外部程序可访问的标签,.globl是GNU汇编的保留关键字,前面加点是GNU汇编的语法
*/
.globl _start
_start:	b       reset    /*为什么只能用b指令,参考学习笔记----S3C2440 从NANDFLASH启动的设计原理与过程*/
	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
/*
上面列了一张32字节的向量表,相当于定义8个函数指针,占用32字节;
硬件根据相对应的情况自动跳转进来查询表内的跳转地址,上电复位跳进来从_start:开始执行,跳到reset函数执行
*/
_undefined_instruction:	.word undefined_instruction
/*相当于C语言定义一个函数指针,再给其赋值;先从向量表跳转到_undefined_instruction,再通过这个跳转到ndefined_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

	.balignl 16,0xdeadbeef
/*
理解为如果当前PC不是16的倍数,那么填充deadbeef,而此时PC为15X4=60,所以填进去一块"死牛肉",之后变为64字节,为16的倍数;
这个牛肉只是一个标签,只是告诉你前面的东西软件禁止访问,其实是由硬件去访问的
*/

/*
 *************************************************************************
 *
 * Startup Code (reset vector)
 *
 * do important init only if we don't start from memory!
 * relocate armboot to ram
 * setup stack
 * jump to second stage
 *
 *************************************************************************
 */
 
/*
理解为定义一个_TEXT_BASE指针并赋值为TEXT_BASE,而TEXT_BASE的值在BOARD目录config.mk里面定义,代表启动代码在SDRAM中的加载地址
*/
_TEXT_BASE:
	.word	TEXT_BASE

.globl _armboot_start
_armboot_start:
	.word _start

/*
 * These are defined in the board-specific linker script.
 */
.globl _bss_start
_bss_start:
	.word __bss_start

.globl _bss_end
_bss_end:
	.word _end

.globl FREE_RAM_END
FREE_RAM_END:
	.word	0x0badc0de

.globl FREE_RAM_SIZE
FREE_RAM_SIZE:
	.word	0x0badc0de

.globl PreLoadedONRAM
PreLoadedONRAM:
	.word	0

#ifdef CONFIG_USE_IRQ
/* IRQ stack memory (calculated at run-time) */
.globl IRQ_STACK_START
IRQ_STACK_START:
	.word	0x0badc0de

/* IRQ stack memory (calculated at run-time) */
.globl FIQ_STACK_START
FIQ_STACK_START:
	.word 0x0badc0de
#endif


/*
 * the actual reset code
 */

reset:
	/*
	 * set the cpu to SVC32 mode
	 */
	mrs	r0,cpsr
	bic	r0,r0,#0x1f
	orr	r0,r0,#0xd3
	msr	cpsr,r0

/* turn off the watchdog */
#if defined(CONFIG_S3C2400)
# define pWTCON		0x15300000
# define INTMSK		0x14400008	/* Interupt-Controller base addresses */
# define CLKDIVN	0x14800014	/* clock divisor register */
#elif defined(CONFIG_S3C2410)
# define pWTCON		0x53000000
# define INTMOD     0X4A000004
# define INTMSK		0x4A000008	/* Interupt-Controller base addresses */
# define INTSUBMSK	0x4A00001C
# define CLKDIVN	0x4C000014	/* clock divisor register */
#endif

#if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410)
	ldr     r0, =pWTCON
	mov     r1, #0x0
	str     r1, [r0]

	/*
	 * mask all IRQs by setting all bits in the INTMR - default
	 */
	mov	r1, #0xffffffff
	ldr	r0, =INTMSK
	str	r1, [r0]
# if defined(CONFIG_S3C2410)
	ldr	r1, =0x3ff
	ldr	r0, =INTSUBMSK
	str	r1, [r0]
# endif

#if 0
	/* FCLK:HCLK:PCLK = 1:2:4 */
	/* default FCLK is 120 MHz ! */
	ldr	r0, =CLKDIVN
	mov	r1, #3
	str	r1, [r0]
#endif
#endif	/* CONFIG_S3C2400 || CONFIG_S3C2410 */

	/*
	 * we do sys-critical inits only at reboot,
	 * not when booting from ram!
	 */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
	adr	r0, _start		/* r0 <- current position of code  如果是从NANDFLASH启动,_start的当前运行地址肯定是0*/
	ldr	r1, _TEXT_BASE		/* test if we run from flash or RAM */
	cmp     r0, r1                  /* don't reloc during debug         */
	blne	cpu_init_crit
#endif

	/* Set up the stack						    */
stack_setup:
	ldr	r0, _TEXT_BASE		/* upper 128 KiB: relocated uboot   */
	sub	r0, r0, #CFG_MALLOC_LEN	/* malloc area                      */
	sub	r0, r0, #CFG_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    */

#ifndef CONFIG_SKIP_LOWLEVEL_INIT
    bl clock_init
#endif    

#ifndef CONFIG_SKIP_RELOCATE_UBOOT
relocate:				/* relocate U-Boot to RAM	    */
	adr	r0, _start		/* r0 <- current position of code   */
	ldr	r1, _TEXT_BASE		/* test if we run from flash or RAM */
	cmp     r0, r1                  /* don't reloc during debug         */
	beq     clear_bss
	
	ldr	r2, _armboot_start
	ldr	r3, _bss_start
	sub	r2, r3, r2		/* r2 <- size of armboot            */
        bl  CopyCode2Ram	/* r0: source, r1: dest, r2: size */
#endif	/* CONFIG_SKIP_RELOCATE_UBOOT */

clear_bss:
	ldr	r0, _bss_start		/* find start of bss segment        */
	ldr	r1, _bss_end		/* stop here                        */
	mov 	r2, #0x00000000		/* clear                            */


clbss_l:str	r2, [r0]		/* clear loop...                    */
	add	r0, r0, #4
	cmp	r0, r1
	ble	clbss_l


SetLoadFlag:
	/* Set a global flag, PreLoadedONRAM */
	adr	r0, _start		/* r0 <- current position of code   */
	ldr	r1, _TEXT_BASE		/* test if we run from flash or RAM */
	cmp     r0, r1                  /* don't reloc during debug         */
	ldr r2, =PreLoadedONRAM
	mov r3, #1
	streq r3, [r2]

	ldr	pc, _start_armboot
_start_armboot:	.word start_armboot

/*下面还有这么多代码,都是处理异常的,我们先不分析,以后再具体分析,现在只讲主干流程*/


第一阶段要做的事情:

1 设置中断向量表

2 切换到管理模式

3 关看门狗,屏蔽终端,刷新chach,静止MMU,初始化SDRAM,设置堆栈区,初始化时钟

4 复制代码到SDRAM

5 清BSS段,判断设置NORFLASH启动标志

6 调用start_armboot进入第二阶段

---------------------------------------------------------------------------------------------------------------------------------------------------------------

start_armboot 分析:

第二阶段初始化一些变量,信息,中断,串口,堆栈地址,nand,SDRAM,外设初始化,环境变量;主要目的就是为了设置下面这个结构体:(填充全局的一些参数)

这个结构体数据存放在:

gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));    

这里在第一阶段subr0, r0, #CFG_GBL_DATA_SIZE 设置好了区域;

结构体内容:

typedef structglobal_data {
bd_t *bd;
unsigned long flags;
unsigned long baudrate;
unsigned long have_console; /* serial_init() was called */
unsigned long reloc_off; /* Relocation Offset */
unsigned long env_addr; /* Address  of Environment struct */
unsigned long env_valid; /* Checksum of Environment valid? */
unsigned long fb_base; /* base address of frame buffer */
#ifdef CONFIG_VFD
unsigned char vfd_type; /* display type */
#endif
#if 0
unsigned long cpu_clk; /* CPU clock in Hz!*/
unsigned long bus_clk;
unsigned long ram_size; /* RAM size */
unsigned long reset_status; /* reset status register at boot */
#endif
void **jt;/* jump table */
} gd_t;
包括里面的结构体:
typedef struct bd_info {
    int bi_baudrate;/* serial console baudrate */
    unsigned long bi_ip_addr;/* IP Address */
    unsigned char bi_enetaddr[6]; /* Ethernet adress */
    struct environment_s       *bi_env;
    ulong        bi_arch_number;/* unique id for this board */
    ulong        bi_boot_params;/* where this board expects params */
    struct /* RAM configuration */
    {
ulong start;
ulong size;
    } bi_dram[CONFIG_NR_DRAM_BANKS];
#ifdef CONFIG_HAS_ETH1
    /* second onboard ethernet port */
    unsigned char   bi_enet1addr[6];
#endif
} bd_t;

填充的主要代码有:

gd->bd->bi_arch_number = MACH_TYPE_S3C2440;

 gd->bd->bi_boot_params = 0x30000100;

gd->bd->bi_baudrate = gd->baudrate = (i > 0)? (int) simple_strtoul (tmp, NULL, 10): CONFIG_BAUDRATE;

gd->have_console = 1;

gd->flags |= GD_FLG_SILENT;

gd->bd->bi_dram[0].start = PHYS_SDRAM_1;
gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;

gd->env_valid = 1;

gd->env_addr = (ulong)&(env_ptr->data);

gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");

gd->bd->bi_enetaddr[reg] = s ? simple_strtoul (s, &e, 16) : 0;

gd->jt[XF_getc] = serial_getc;

这里面的一些值是环境变量来的,环境变量默认的设置在下面,首次加载环境变量是默认值,当进行一次改写后,就会把这些环境变量存在NANDFLASH uboot参数区,

之后启动就会通过:

ret = nand_read(&nand_info[0], CFG_ENV_OFFSET, &total, (u_char*)env_ptr);

来读取环境变量。默认值通过下面变量设置:

uchar default_environment[]

修改环境变量通过下面函数实现的:

int saveenv(void)
{
	ulong total;
	int ret = 0;

	puts ("Erasing Nand...");
	if (nand_erase(&nand_info[0], CFG_ENV_OFFSET, CFG_ENV_SIZE))
		return 1;

	puts ("Writing to Nand... ");
	total = CFG_ENV_SIZE;
	ret = nand_write(&nand_info[0], CFG_ENV_OFFSET, &total, (u_char*)env_ptr);
	if (ret || total != CFG_ENV_SIZE)
		return 1;

	puts ("done\n");
	return ret;
}

这些东西准备好之后,再根据这些参数或者环境变量设置好串口,网卡等设备,所有初始化完成后,进入一个死循环:main_loop ();

这个函数核心就是:

如果开机延时时间(bootdelay环境变量)内按空格键,则执行:

run_command("menu", 0);    //进入维护模式

否则:

printf("Booting Linux ...\n");            

run_command (s, 0);     //进入引导模式

run_command 函数就是个uboot自带的shell内核,输入指令执行函数的shell模型;

他的指令与执行的函数有一个约定:输入help指令,就会对应执行do_help函数,就是指令对应的执行函数名要定义为:

do_指令;

本文开始就说了启动内核的这三步:

1 初始化硬件信息与环境

2 从NANDFLASH内核分区读取内核到SDRAM

3 跳转到内核入口地址执行内核

第一步完成,剩下2是通过nand 指令从nandflash读内核到sdram实现的,3是由bootm来启动内核的。所以bootcmd=这个环境变量必须设置这两项:

这里假设引导模式传入的指令 s=”bootcmd=nand read 30007fc0 3c0000 300000;bootm 00007fc0“

这里执行nand指令和bootm指令,查看源码就知道:

nand read 00007fc0  3c0000 300000  从nandflash 地址0x3c0000 读取0x300000长度的代码到SDRAM的0x00007fc0 里面;

bootm 00007fc0  为启动SDRAM地址;

bootm 的时候,有这样一段代码(这里指考虑未压缩):

if(ntohl(hdr->ih_load) == addr)// 相等则不需要移动
{
printf ("   XIP %s ... ", name);
} 
else
{
memmove ((void *) ntohl(hdr->ih_load),(uchar *)data, len);//把真正的内核移动到加载地址
}

1 如果nand read 下载到sdram的下载地址和bootm地址相同,那么这个地址对应的uImage的头部起始;

这里如果是未压缩内核则判断bootm地址是否和装载地址一致,如果一致则不移动内核数据到装载地址,如果不一致则移动,也就是说在这个条件下

如果bootm地址 = 装载地址 ,那么 入口地址=装载地址-0x40(64字节头部)

如果bootm地址!=装载地址,那么入口地址=装载地址

而上面分析Makefile可知道入口地址和装载地址是相等的,那么必须满足:

下载地址 = bootm地址 != 装载地址 = 入口地址

2 如果nand read 下载到sdram的地址和bootm地址不同;假设nand read 下载到sdram的地址为0x30007fc0,而bootm地址为0x30008000,那么这个bootm地址刚好对应的是内核起始;此时bootm地址 = 装载地址,那么内核数据不移动;那么必须满足:

下载地址 +0x40 = bootm地址 = 装载地址 入口地址

满足这样的关系,系统才能正常启动;

进入内核前的最后一步调用do_bootm_linux函数:

内部实现代码:

theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep); 

theKernel (0, bd->bi_arch_number, bd->bi_boot_params);

这三个参数分别存放在寄存器r0 r1 r2 ,内核启动代码里面读取这三个寄存器就表示从uboot传入参数到了内核了;这个函数调用成功之后就没uboot事了。到此uboot全部任务完成了。

这里再看下bd->bi_boot_params这个参数:

这个参数是在调用theKernel (0, bd->bi_arch_number, bd->bi_boot_params);之前设置好的,称为TAG设置:

#if defined (CONFIG_SETUP_MEMORY_TAGS) || \
    defined (CONFIG_CMDLINE_TAG) || \
    defined (CONFIG_INITRD_TAG) || \
    defined (CONFIG_SERIAL_TAG) || \
    defined (CONFIG_REVISION_TAG) || \
    defined (CONFIG_LCD) || \
    defined (CONFIG_VFD)
	setup_start_tag (bd);
#ifdef CONFIG_SERIAL_TAG
	setup_serial_tag (¶ms);
#endif
#ifdef CONFIG_REVISION_TAG
	setup_revision_tag (¶ms);
#endif
#ifdef CONFIG_SETUP_MEMORY_TAGS
	setup_memory_tags (bd);
#endif
#ifdef CONFIG_CMDLINE_TAG
	setup_commandline_tag (bd, commandline);
#endif
#ifdef CONFIG_INITRD_TAG
	if (initrd_start && initrd_end)
		setup_initrd_tag (bd, initrd_start, initrd_end);
#endif
#if defined (CONFIG_VFD) || defined (CONFIG_LCD)
	setup_videolfb_tag ((gd_t *) gd);
#endif
	setup_end_tag (bd);
#endif

	/* we assume that the kernel is in place */
	printf ("\nStarting kernel ...\n\n");

#ifdef CONFIG_USB_DEVICE
	{
		extern void udc_disconnect (void);
                //udc_disconnect (); // cancled by www.arm9.net
	}
#endif
先看下第一个设置函数:

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);
}
基本上没做什么事情,就是起个头,设置好params的实际操作地址,而之前在board_init里面设置好了

gd->bd->bi_boot_params = 0x30000100;

所以params的地址就是0x30000100,接下来的参数都是从这个开始陆续存下去的;这个参数设置需要和内核设置的匹配:

MACHINE_START(SMDK2410, "SMDK2410")
   
    .phys_ram    = S3C2410_SDRAM_PA,
    .phys_io    = S3C2410_PA_UART,
    .io_pg_offst    = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
    .boot_params    = S3C2410_SDRAM_PA + 0x100,//必须匹配gd->bd->bi_boot_params
    .map_io        = smdk2410_map_io,
    .init_irq    = smdk2410_init_irq,
    .timer        = &s3c24xx_timer,
MACHINE_END

填完一个之后,下一个:

void setup_serial_tag (struct tag **tmp)
{
	struct tag *params = *tmp;
	struct tag_serialnr serialnr;
	void get_board_serial(struct tag_serialnr *serialnr);

	get_board_serial(&serialnr);
	params->hdr.tag = ATAG_SERIAL;
	params->hdr.size = tag_size (tag_serialnr);
	params->u.serialnr.low = serialnr.low;
	params->u.serialnr.high= serialnr.high;
	params = tag_next (params);
	*tmp = params;
}
这个基本上都不用,再往下面放,设置版本参数:

void setup_revision_tag(struct tag **in_params)
{
	u32 rev = 0;
	u32 get_board_rev(void);

	rev = get_board_rev();
	params->hdr.tag = ATAG_REVISION;
	params->hdr.size = tag_size (tag_revision);
	params->u.revision.rev = rev;
	params = tag_next (params);
}
再往下是填写SDRAM的地址和大小:

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);
	}
}
再下一个就是存放bootargs环境变量,这个参数在内核启动的时候非常重要:

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);
}
下面几个都不是常用的,反正就是按照这个params的格式写一些参数进去,然后地址累加下去,一直加到下面这个:

static void setup_end_tag (bd_t *bd)
{
	params->hdr.tag = ATAG_NONE;
	params->hdr.size = 0;
}
表示参数段结束了,所以这些TAG参数就是uboot与内核的数据通道,通过把这个params的地址传递给内核,就表示这部分数据被内核共享了,共享地址为0x30000100



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值