分享

6. Uboot启动分析

 mzsm 2015-03-10

6. Uboot启动分析

6.1. 硬件引导

通常在上电后,一旦CPU电源稳定期结束,它则开始从某个固定的地址(通常为FLASH的0地址)开始执行程序,为何方便代码的引导,一些CPU支持多种方式/地址处的引导,比如设置跳线,可以选择NOR Flash,或者NAND FLASH等。S3C6410就是这样一款功能强大的CPU。

S3C6410 具备了一个内部SRAM缓冲器,大小为8K,叫做“STEPPINGSTONE”,如果跳线设置为从Nand Flash启动,则当系统启动时,Nand Flash存储器的前4KB 将被自动载入到STEPPINGSTONE中,然后系统自动执行这些载入的引导代码。而Nand Flash的开始就是放置的U-boot的镜像。

对于S3C6410来说,起始代码位于:

cpu/s3c64xx/start.S
.globl _start 	 	/* uboot启动入口*/
_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		/* 中断向量 */
......	

第一跳指令就是复位:

/* 系统上电或reset后,cpu的PC一般都指向0x0地址,在0x0地址上的指令是 */
reset:
	/*
	 * set the cpu to SVC32 mode
	 */
	mrs	r0, cpsr
	/*
	 *把CPSR内容存入r0.使用了mrs指令:专用寄存器到通过寄存器的存取.
	 *CPSR当前程序状态寄存器格式如下:
	 *
	 * 31  30  29  28  27  26  25  24  ~ ~ ~ 8   7   6   5    4    3    2    1    0
	 *___ ___ ___ ___ ___ ___ ___ ___  _ _ _ _  ___ ___ ___ ____ ____ ____ ____ ____
	 *| N | Z | C | V | * | * | * | * | *  *  * | I | F | T | M4 | M3 | M2 | M1 | M0 |
	 *
	 */	
 	/* bic指令(bit clear): r0:= r0 and (not op2).上边的指令目的是把bit0~bit4清零.*/
	bic	r0,r0,#0x1f
	orr	r0,r0,#0xd3
	/* r0:= r0 or 0xd3 . 以上三条指令执行后r0值为:**** **** **** **** **** ***** 11*1 0011 */
  msr	cpsr,r0

msr把r0存于cpsr.注意:msr指令是专用的通用寄存器到特殊功能寄存器的指令与mrs对应说明:通过上边的指令可以看到,实现了两个功能.1,disable 外部中断(IRQ)与快速中断(FIR).2,把系统设为SVC32状态(超级保护)即M4~M1=10011。

接下来cpu初始化关键寄存器以及设置内存时钟。

cpu_init_crit:
	/*
	 * flush v4 I/D caches
	 */
	mov	r0, #0
	
	/* 使I/D cache失效:将寄存器r0的数据传送到协处理器p15的c7中。C7寄存器位对应cp15中的cache控制寄存器 */
	mcr	p15, 0, r0, c7, c7, 0	/* flush v3/v4 cache */
	
	/* 使TLB操作寄存器失效:将r0数据送到cp15的c8、c7中。C8对应TLB操作寄存器 */
	mcr	p15, 0, r0, c8, c7, 0	/* flush v4 TLB */

	/*
	 * disable MMU stuff and caches
	 */
	
	/* 先把c1和c0寄存器的各位置0(r0 = 0) */
	mrc	p15, 0, r0, c1, c0, 0
	bic	r0, r0, #0x00002300	@ clear bits 13, 9:8 (--V- --RS)
	bic	r0, r0, #0x00000087	@ clear bits 7, 2:0 (B--- -CAM)
	orr	r0, r0, #0x00000002	@ set bit 2 (A) Align
	orr	r0, r0, #0x00001000	@ set bit 12 (I) I-Cache
	mcr	p15, 0, r0, c1, c0, 0

	/* Peri port setup */
	/* 通过CP15 c15 设置外设基地址为0x70000000,地址空间为256M 
	 * 
	 *  Base Address   UPN/SBZ  SIZE
	 * |31----------12|11---5 | 4--0|
	 *
	 * 0x13 Means b10011 b10011 = 256MB
	 *  
	*/
	
	ldr	r0, =0x70000000
	orr	r0, r0, #0x13
    	mcr	p15,0,r0,c15,c2,4       @ 256M(0x70000000-0x7fffffff)

接着执行位于board/samsung/smdk6410/lowlevel_init.S内的lowlevel_init子程序,设置时钟相关的PLL,MUX和内存。

	bl	lowlevel_init	/* go setup pll,mux,memory */

	/* 判断当前uboot是否在内存RAM中运行,如果是那么运行after_copy,否则顺序执行
	 */
	ldr	r0, =0xff000fff
	bic	r1, pc, r0		/* r0 <- current base addr of code */
	ldr	r2, _TEXT_BASE		/* r1 <- original base addr in ram */
	bic	r2, r2, r0		/* r0 <- current base addr of code */
	cmp     r1, r2                  /* compare r0, r1                  */
	beq     after_copy		/* r0 == r1 then skip flash copy   */

一部分代码初始化堆栈,以及MMU,对于Nand Flash启动来说,一个重要的子程序是copy_from_nand,这将会把U-Boot镜像拷贝到内存中。

copy_from_nand:

	/* 设置返回地址 */
	mov	r10, lr		/* save return address */
	
  /* 注意r0此时为4096 */
	mov	r9, r0
	/* get ready to call C functions */
	/* 使用伪指令,设置栈顶sp指针为_TEXT_PHY_BASE ,fp为栈第指针*/
	ldr	sp, _TEXT_PHY_BASE	/* setup temp stack pointer */
	sub	sp, sp, #12 /* 分配12bytes */
	mov	fp, #0			/* no previous frame, so fp=0 */
	mov	r9, #0x1000
	bl	copy_uboot_to_ram // 位于 nand_cp.c it the first C functions
	// r0存储copy_uboot_to_ram的返回值,即从FLASH向内存0x57e00000拷贝的字节数

copy_uboot_to_ram位于nand_cp.c,这里需要注意NAND控制器支持每FLASH页读取的大小为512B/2K,所以当Flash页大小为4K的时候,它只能每块读取前2K,这里就需要在写Uboot的时候前面4页(实际上只要前两个2页面即可,因为STEPPINGSTONE只是用了4K,这里为4页)每个只能写入2K。而拷贝出的时候是反操作。

include/configs/smdk6410.h
#define MEMORY_BASE_ADDRESS     0x50000000
#define CFG_PHY_UBOOT_BASE	MEMORY_BASE_ADDRESS + 0x7e00000

cpu/s3c64xx/nand_cp.c
int copy_uboot_to_ram (void)
{
	......
	/* read NAND Block.
	 * 128KB ->240KB because of U-Boot size increase. by scsuh
	 * So, read 0x3c000 bytes not 0x20000(128KB).
	 */
	return nandll_read_blocks(CFG_PHY_UBOOT_BASE, 0x3c000, large_block);
}

copy_uboot_to_ram调用nandll_read_blocks读取了0x3c000(240K)数据,显然这是因为U-boot的体积在不断变大,当前版本的镜像大约为208K。这里的前4页只读取了前半页(page_shift - 1)。

static int nandll_read_blocks (ulong dst_addr, ulong size, int large_block)
{
......
		/* Read pages */
		for (i = 0; i < 4; i++, buf+=(1<<(page_shift-1))) {
		        nandll_read_page(buf, i, large_block);
		}

		/* Read pages */
		for (i = 4; i < (0x3c000>>page_shift); i++, buf+=(1<<page_shift)) {
		        nandll_read_page(buf, i, large_block);
		}
......
}

值得注意的是CFG_PHY_UBOOT_BASE,也即Uboot被写入的地址。这里为RAM物理地址MEMORY_BASE_ADDRESS加上0x7e00000的偏移,显然这相当于物理地址的第126M偏移处。

3:	tst 	r0, #0x0
	bne	copy_failed

	ldr	r0, =0x0c000000
	ldr	r1, _TEXT_PHY_BASE // 即CFG_PHY_UBOOT_BASE,也即0x57e00000
1:	ldr	r3, [r0], #4
	ldr	r4, [r1], #4
	teq	r3, r4					
	bne	compare_failed	/* not matched */
	subs	r9, r9, #4
	bne	1b

4:	mov	lr, r10		/* all is OK */
	mov	pc, lr

如果存放在r0中的copy_uboot_to_ram的返回值不为0,则跳转到copy_failed。否则校验第一个4KB的代码是否和当前代码相同,如果不同跳转到compare_failed执行。否则跳转回bl copy_from_nand后的下一条指令继续执行。接下来执行after_copy子程序。

after_copy:
#ifdef CONFIG_ENABLE_MMU
enable_mmu:
	/* enable domain access */
	ldr	r5, =0x0000ffff
	mcr	p15, 0, r5, c3, c0, 0		@ load domain access register

	/* Set the TTB register */
	ldr	r0, _mmu_table_base
	ldr	r1, =CFG_PHY_UBOOT_BASE
	ldr	r2, =0xfff00000
	bic	r0, r0, r2
	orr	r1, r0, r1
	mcr	p15, 0, r1, c2, c0, 0

	/* Enable the MMU */
mmu_on:
	mrc	p15, 0, r0, c1, c0, 0
	orr	r0, r0, #1			/* Set CR_M to enable MMU */
	mcr	p15, 0, r0, c1, c0, 0
	nop
	nop
	nop
	nop
#endif

根据CONFIG_ENABLE_MMU的配置情况,通过mmu_on使能MMU单元。接下来执行stack_setup初始化栈,调用clear_bss清除BSS段等,最终调用start_armboot,它位于lib_arm/board.c。此时执行的代码已经是复制到126M处的Uboot的代码,为什么?请看_mmu_table_base引用的mmu_table在board/samsung/smdk6410/lowlevel_init.S中的定义。

	ldr	pc, _start_armboot

_start_armboot:
	.word start_armboot

图 32. Uboot在内存中的映像

Uboot在内存中的映像


6.2. bootm

bootm是Uboot启动内核的指令,它用来加载内核镜像,和go命令类似,但是支持r0,r1,r2和bootargs传递参数。一个通常的启动参数如下为:

bootcmd=nand read 0xc0008000 0x100000 0x300000;bootm 0xc0008000

其中nand read 0xc0008000 0x100000 0x300000表示从FLASH中的0x100000地址处读取长度为0x300000的数据放到内存的0xc0008000地址中,而bootm 0xc0008000则是调用do_bootm函数来启动内核镜像。 bootm的实现位于common/cmd_bootm.c中:

int do_bootm(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
	ulong	iflag;
	ulong	addr;
	ulong	data, len, checksum;
	ulong  *len_ptr = NULL; /* not to make warning. by scsuh */
	uint	unc_len = CFG_BOOTM_LEN;
	int	i, verify;
	char	*name, *s;
	int	(*appl)(int, char *[]);
	image_header_t *hdr = &header;

	s = getenv ("verify");
	verify = (s && (*s == 'n')) ? 0 : 1;

	if (argc < 2) {
		addr = load_addr;
	} else {
		addr = simple_strtoul(argv[1], NULL, 16);
	}

do_bootm的函数第一部分很简单,它首先查看环境变量"verify",如果不为"n",那么将对镜像进行Checksum的校验。do_bootm可以接受一个可选参数,即镜像文件在内存中的地址。如果没有指明addr,那么将使用默认的load_addr,它在早些时候被赋值为CFG_LOAD_ADDR。定义在include/configs/smdk6410.h。0x50000000即是SDRAM对应的物理地址:存储器端口2DDR/SDRAM Bank0。注意到hdr = &Theader,一个代表系统镜像头的结构体image_header_t header在本文件中定义为全局变量,它将在其它函数中被引用。

#define MEMORY_BASE_ADDRESS     0x50000000
......
#define CFG_LOAD_ADDR           MEMORY_BASE_ADDRESS     /* default load address */

#ifdef CONFIG_ZIMAGE_BOOT
#define LINUX_ZIMAGE_MAGIC	0x016f2818
	if (*(ulong *)(addr + 9*4) == LINUX_ZIMAGE_MAGIC) {
		printf("Boot with zImage\n");
		addr = virt_to_phys(addr);
		hdr->ih_os = IH_OS_LINUX;
		hdr->ih_ep = ntohl(addr);
		goto after_header_check;
	}
#endif

使用MAGIC来验证内核的ZIMAGE镜像的正确性。addr + 9 * 4将定位到镜像中的MAGIC数,它的定义位于内核的arch/arm/boot/compressed/head.S的开始处,与此同时,这也很好的解释了.rept 8的真正意图。

start:
	.type   start,#function
	.rept   8
	mov     r0, r0
	.endr
	
	b       1f
	.word   0x016f2818              @ Magic numbers to help the loader
	.word   start                   @ absolute load/run zImage address
	.word   _edata                  @ zImage end address
......

在看到Boot with zImage信息后,addr将从逻辑地址转换为物理地址。virt_to_phys函数根据不同的系统配置将不同并只在CONFIG_ENABLE_MMU开始起使能。它实际上是一个宏定义,位于include/configs/smdk6410.h。virt_to_phy_smdk6410定义在board/samsung/smdk6410/smdk6410.c。分析下列代码可以清晰的看到,逻辑地址向物理地址转换的过程,首先减去内核逻辑地址的偏移,通常为0xc0000000,然后加上物理地址的偏移,这里为0x50000000。

#define CONFIG_ENABLE_MMU
#ifdef CONFIG_ENABLE_MMU
#define virt_to_phys(x) virt_to_phy_smdk6410(x)
#else
#define virt_to_phys(x) (x)
#endif

#ifdef CONFIG_ENABLE_MMU
ulong virt_to_phy_smdk6410(ulong addr)
{
        if ((0xc0000000 <= addr) && (addr < 0xc8000000))
                return (addr - 0xc0000000 + 0x50000000);
        else
                printf("do not support this address : %08lx\n", addr);

        return addr;
}
#endif

描述镜像头信息的image_header_t结构体中的ih_os记录系统的类型,为IH_OS_LINUX,而其中的ih_ep则表示系统镜像的入口点(Entry Point Address),addr也即是入口点,但是需要转换为本机字节序。after_header_che ck将根据系统类型使用不同的函数对镜像进行加载。

	switch (hdr->ih_os) {
	default:			/* handled by (original) Linux case */
	case IH_OS_LINUX:
#ifdef CONFIG_SILENT_CONSOLE
	    fixup_silent_linux();
#endif
	    do_bootm_linux(cmdtp, flag, argc, argv, addr, len_ptr, verify);
	    break;
	......
}

6.3. do_bootm_linux

do_bootm_linux函数因不同的硬件架构而不同,对于arm来说,它位于lib_arm/armlinux.c,一个有些复杂的函数。这个函数的开始定义了一些变量:initrd_start和initrd_end顾名思义自然是一个虚拟的Ram Disk(initrd: boot loader initialized RAM disk );checksum用来校验之用,自然它由参数verify来决定;header从cmd_bootm.c extern而来,它在do_bootm函数中ih_os和ih_ep已被赋值。

void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],
		     ulong addr, ulong *len_ptr, int verify)
{
	ulong len = 0, checksum;
	ulong initrd_start, initrd_end;
	ulong data;
	void (*theKernel)(int zero, int arch, uint params);
	image_header_t *hdr = &header;
	bd_t *bd = gd->bd;

#ifdef CONFIG_CMDLINE_TAG
	char *commandline = getenv ("bootargs");
#endif
	theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);

bd_t类型的bd变量是一个用来表示Uboot中串口,板卡ID,IP等板卡相关信息配置接口。commandline则是Uboot传递给Linux系统的参数,一个实际使用的bootargs环境变量如下:

bootargs=root=/dev/mtdblock2 rootfstype=cramfs console=ttySAC0,115200

一个值得注意的参数是theKernel,它有三个参数,并且用它来指向系统镜像的入口地址。继续回顾arch/arm/boot/compressed/head.S中的start入口,它通过.type start,#function被定义为了函数。根据ATPCS参数传递规则,int arch, uint params参数将分别对应r1和r2寄存器,这与注释相符合。

start:
	.type   start,#function
	.rept   8
	mov     r0, r0
	.endr
......
1:mov     r7, r1                  @ save architecture ID
  mov     r8, r2                  @ save atags pointer

接下来是一个三分支的判断,判断是否从initrd镜像,多系统文件镜像还是其他方式启动,这里将进入最后一个分支,它看起来相当简单:

	if (argc >= 3) {
	......
	}
	else if ((hdr->ih_type == IH_TYPE_MULTI) && (len_ptr[1])) {
	......}
	else{
		/* no initrd image */
		SHOW_BOOT_PROGRESS (14);

		len = data = 0;
	}

SHOW_BOOT_PROGRESS是一个宏,它通常定义在每个.c文件的开始,它通常使用LED灯的动作来指示Uboot的启动阶段。它由CONFIG_SHOW_BOOT_PROGRESS开关来控制。打开它将有助于对Uboot启动的分析。并不是所有板卡都定义了该函数,如果没有定义将出现编译错误。一个简单的实现方法是直接printf当前的arg值,尽管这会丢失一些串口初始化前的信息。另一个用来追踪信息的选项是DEBUG宏,它用来开关common.h中的debug函数。

#ifdef CONFIG_SHOW_BOOT_PROGRESS
# include <status_led.h>
# define SHOW_BOOT_PROGRESS(arg)	show_boot_progress(arg)
#else
# define SHOW_BOOT_PROGRESS(arg)
#endif

如果定义了DEBUG宏,那儿一个阶段信息将打印出来:## Transferring control to Linux (at address 50008000) ...,参考上面的地址转换函数很容易得到50008000的由来。

	SHOW_BOOT_PROGRESS (15);

	debug ("## Transferring control to Linux (at address %08lx) ...\n",
	       (ulong) theKernel);

#if defined (CONFIG_SETUP_MEMORY_TAGS) ||     defined (CONFIG_CMDLINE_TAG) || 	......
	setup_start_tag (bd);
	......
#ifdef CONFIG_SETUP_MEMORY_TAGS
	setup_memory_tags (bd);
#endif
#ifdef CONFIG_CMDLINE_TAG
	setup_commandline_tag (bd, commandline);
#endif
	......
	setup_end_tag (bd);
#endif

一些Uboot使用的环境变量将在这里得到解析,这些宏定义在include/configs/smdk6410.h中。当前的系统定义了CONFIG_SETUP_MEMORY_TAGS ,CONFIG_CMDLINE_TAG和CONFIG_INITRD_TAG。这些函数协作处理Uboot传递给内核的bi_boot_params参数。下一小节将对它们进行详细的分析。

	printf ("\nStarting kernel ...\n\n");	
	......
	cleanup_before_linux ();

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

如果定义了CONFIG_USE_IRQ,那么cleanup_before_linux用来关中断,另外它关闭并清除使用的I/D Cache。当看到"Starting kernel ..."信息的时候。Uboot的工作业已结束,Linux多彩缤纷的新纪元已经来临。

6.4. tag处理函数

注意到tag处理函数的主要数据结构params,它被定义为static struct tag *params。struct tag定义在include/asm-arm/setup.h,注意到成员u是联合体,core,mem等相关的参数将共用。
struct tag_header {
	u32 size;
	u32 tag;
};
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;
		......
	} u;
};
另外一个引人注目的参数是bd,它是Uboot配置信息的集合,bd_t类型定义在include/asm-arm/u-boot.h,bd指向被封装在一个全局的名为gd的struct global_data结构中,所有的操作均是对该gd->bd的操作。
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;

6.4.1. setup_start_tag

setup_start_tag是一个静态函数,它对bd中的bi_boot_params进行操作,bi_boot_params被定义为一个ulong类型,这里被作为一个指针进行处理,它存储了一个包含多个tag的动态数组。这个数组的第一个元素中的hdr.tag必需是ATAG_CORE。tag_next的作用是使params指向下一个RAM区,以备后来的TAG元素使用。
	
/* The list must start with an ATAG_CORE node */
#define ATAG_CORE	0x54410001
#define tag_next(t)	((struct tag *)((u32 *)(t) + (t)->hdr.size))
#define tag_size(type)	((sizeof(struct tag_header) + sizeof(struct type)) >> 2)

struct tag_core {
	u32 flags;		/* bit 0 = read-only */
	u32 pagesize;
	u32 rootdev;
};

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);
}
一个疑问是Uboot中并没有malloc类似的内存管理函数,而bi_boot_params也仅仅是一个指针而已,那么这里直接对它指向的地址进行操作会不会出现问题呢?位于board/samsung/smdk6410/smdk6410.c的board_init对其进行了初始化,MEMORY_BASE_ADDRESS即RAM的物理起始地址0x50000000。这里把参数起始地址定义到0x100的偏移处,也即256Byte处,这段地址是需要保证未被使用的。
#define PHYS_SDRAM_1            MEMORY_BASE_ADDRESS /* SDRAM Bank #1 */
#define DECLARE_GLOBAL_DATA_PTR     register volatile gd_t *gd asm ("r8")
#define MACH_TYPE               1626

int board_init(void)
{
	DECLARE_GLOBAL_DATA_PTR;

	cs8900_pre_init();

	gd->bd->bi_arch_number = MACH_TYPE;
	gd->bd->bi_boot_params = (PHYS_SDRAM_1 + 0x100);
	return 0;
}
另外值得注意的参数是bi_arch_number,它记录当前板卡的ID号,并作为内核启动时的第二个参数。

6.4.2. setup_memory_tags

#define CONFIG_NR_DRAM_BANKS    1  /* we used 1 bank of DRAM */
/* it is allowed to have multiple ATAG_MEM nodes */
#define ATAG_MEM	0x54410002
struct tag_mem32 {
	u32	size;
	u32	start;	/* physical start address */
};

#ifdef CONFIG_SETUP_MEMORY_TAGS
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);
	}
}
#endif /* CONFIG_SETUP_MEMORY_TAGS */

u.mem.start和u.mem.size均是从bd->bi_dram获取,它们在dram_init中赋值。显然这里的开始地址依然是0x50000000,而大小是256Mb。

#define PHYS_SDRAM_1            MEMORY_BASE_ADDRESS /* SDRAM Bank #1 */
#define PHYS_SDRAM_1_SIZE       0x10000000		/* 256M */
int dram_init(void)
{
	DECLARE_GLOBAL_DATA_PTR;

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

	return 0;
}

6.4.3. setup_commandline_tag

setup_start_tag是一个非常简单明了的函数,它将传入的commandline参数复制到struct tag_cmdline中cmdline中,并以'\0'结束。这里的cmdline实际就是bootargs环境变量的值。

	
/* command line: \0 terminated string */
#define ATAG_CMDLINE	0x54410009

struct tag_cmdline {
	char	cmdline[1];	/* this is the minimum size */
};	

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

	if (!commandline)
		return;

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

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

6.4.4. setup_end_tag

tag数组的组后一个元素必须是ATAG_NONE类型,它表示当前数组的结束。

	
/* The list ends with an ATAG_NONE node. */
#define ATAG_NONE	0x00000000

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

图 33. Uboot传递给内核的tag组织形式

Uboot传递给内核的tag组织形式

6.5. Sandbox

表 12. Memory Hierarchy

存储器类型 位于哪里[a]
CPU寄存器 位于CPU执行单元中。

[a] 到底位于哪里呢?


  • 列表项内容,可以使用para、formalpara等
  • 列表项内容......

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多