uboot启动第二阶段——C语言

Posted 代二毛

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了uboot启动第二阶段——C语言相关的知识,希望对你有一定的参考价值。

1、给全局变量gd分配内存

详情参考:《uboot中重要的全局变量——gd》

2、计算重定位的代码长度

_armboot_start:
	.word _start
	
monitor_flash_len = _bss_start - _armboot_start; //要重定位的长度

_bss_start 是bss段开始的地址,_armboot_start是整个程序重定位的起始地址,两者相减就得到代码段、数据段、自定义段等的长度,这些都是需要重定位的。_armboot_start等于_start的地址,而_start是uboot的入口,也就是uboot启动第一阶段的起始。不清楚的参考:《u-boot的链接脚本分析》《uboot启动第一阶段详解——汇编代码部分start.S》
补充:bss段不需要重定位。

3、通过函数指针数组的方式执行初始化函数

//函数指针类型
typedef int (init_fnc_t) (void);

//函数指针数组
init_fnc_t *init_sequence[] = 
	cpu_init,		/* basic cpu dependent setup */
#if defined(CONFIG_SKIP_RELOCATE_UBOOT)
	reloc_init,		/* Set the relocation done flag, must
				   do this AFTER cpu_init(), but as soon
				   as possible */
#endif
	board_init,		/* basic board dependent setup */
	interrupt_init,		/* set up exceptions 将timer4设置为10ms*/
	env_init,		/* initialize environment 将环境变量设置成默认的环境变量*/
	init_baudrate,		/* initialze baudrate settings */
	serial_init,		/* serial communications setup */
	console_init_f,		/* stage 1 init of console */
	display_banner,		/* say that we are here */
#if defined(CONFIG_DISPLAY_CPUINFO)
	print_cpuinfo,		/* display cpu info (and speed) */
#endif
#if defined(CONFIG_DISPLAY_BOARDINFO)
	checkboard,		/* display board info */
#endif
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C)
	init_func_i2c,
#endif
	dram_init,		/* configure available RAM banks */
	display_dram_config,
	NULL,
;

init_fnc_t **init_fnc_ptr;

//执行函数指针数组里的每一个函数指针
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) 
	if ((*init_fnc_ptr)() != 0) 
		hang ();
	

每个功能的初始化函数类型都是一样的,因此可以用一个函数指针数组将需要调用的初始化函数的指针保存在数组中,然后用循环的方式去依次初始化。

4、初始化堆管理器

static void mem_malloc_init (ulong dest_addr)

	mem_malloc_start = dest_addr;
	mem_malloc_end = dest_addr + CFG_MALLOC_LEN;//CFG_MALLOC_LEN是堆内存的大小
	mem_malloc_brk = mem_malloc_start;

	memset ((void *) mem_malloc_start, 0,
			mem_malloc_end - mem_malloc_start);


#ifdef CONFIG_MEMORY_UPPER_CODE /* by scsuh */
	mem_malloc_init (CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE);
#else
	mem_malloc_init (_armboot_start - CFG_MALLOC_LEN);
#endif

mem_malloc_init 是堆管理器的初始化函数,传入分配给堆管理器内存的起始地址,堆内存的大小在初始化函数内部用CFG_MALLOC_LEN宏指定。在初始化完成后就可以使用malloc来申请内存。

5、初始化外存

#if defined(CONFIG_X210)

	#if defined(CONFIG_GENERIC_MMC)
		puts ("SD/MMC:  ");
		mmc_exist = mmc_initialize(gd->bd);
		if (mmc_exist != 0)
		
			puts ("0 MB\\n");
		
	#endif

	#if defined(CONFIG_MTD_ONENAND)
		puts("OneNAND: ");
		onenand_init();
		/*setenv("bootcmd", "onenand read c0008000 80000 380000;bootm c0008000");*/
	#else
		//puts("OneNAND: (FSR layer enabled)\\n");
	#endif

	#if defined(CONFIG_CMD_NAND)
		puts("NAND:    ");
		nand_init();
	#endif
#endif /* CONFIG_X210 */

CONFIG_X210是表示开发板的宏定义,不同的开发板使用不同的外存,初始化也是不同的,这里只是摘抄了X210开发板的初始化部分。X210开发板可以支持MMC、oneNand、Nand外存,通过宏定义来控制,开发板实际焊接的那种外存就定义哪个宏。

6、重定位环境变量

	/* initialize environment */
	env_relocate (); //从flash中读出环境变量

此前因为flash没有初始化,环境变量是用的默认环境变量。这里去flash中读取环境变量,如果校验通过则使用从flash中读出来的环境变量;如果校验不通过,则还是使用默认的环境变量。

7、获取IP地址和MAC地址

	/* IP Address */
	gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");

	/* MAC Address */
	int i;
	ulong reg;
	char *s, *e;
	char tmp[64];

	i = getenv_r ("ethaddr", tmp, sizeof (tmp));
	s = (i > 0) ? tmp : NULL;

	for (reg = 0; reg < 6; ++reg)
	 
			gd->bd->bi_enetaddr[reg] = s ? simple_strtoul (s, &e, 16) : 0;
			if (s)
			
				s = (*e) ? e + 1 : e;
			
	

这是从环境变量里获取ip地址和MAC地址,其中ipaddr环境变量是IP地址,ethaddr是MAC地址。
注意:直接从环境变量获取的值是字符串格式的,要进行字符串转数字。

8、硬件设备初始化

devices_init ();	/* get the devices list going. */

devices_init函数就是设备初始化函数,开发板上的硬件设备(flash、网卡等)的初始化函数都可以放在这个函数里调用。Linux内核启动阶段里也有一个类似名字的函数,作用都是集中执行各种硬件设备的初始化函数。

9、跳转表初始化 & 控制台初始化

	jumptable_init ();
	console_init_r ();	/* fully init console as a device */

(1)跳转表就是一个函数指针数组,数组指针保存在全局变量gd->jt中,jt是二级指针。jumptable_init就是给函数指针数组的成员赋值。
(2)console_init_r是控制台的第二阶段初始化。

10、开启中断

/* enable IRQ interrupts */
void enable_interrupts(void)

	unsigned long temp;
	__asm__ __volatile__("mrs %0, cpsr\\n" "bic %0, %0, #0x80\\n" "msr cpsr_c, %0":"=r"(temp)
			     ::"memory");


/* enable exceptions */
enable_interrupts ();

enable_interrupts函数是开启中断,函数具体操作就是通过内嵌汇编实现操作cpsr寄存器。
(1)_asm_:表示后面是汇编代码;
(2)_volatile_:和C语言中的volatile关键字是一样的作用;
(3)CPSR寄存器:参考博客:《ARM的37个寄存器和异常处理机制详解》

11、loadaddr和bootfile

	if ((s = getenv ("loadaddr")) != NULL) 
		load_addr = simple_strtoul (s, NULL, 16);
	
#if defined(CONFIG_CMD_NET)
	if ((s = getenv ("bootfile")) != NULL) 
		copy_filename (BootFile, s, sizeof (BootFile));
	
#endif

loadaddr和bootfile是uboot中的两个环境变量,和内核启动有关。

12、board_late_init函数

int board_late_init (void)

	uint *magic = (uint*)(PHYS_SDRAM_1);
	char boot_cmd[100];

	if ((0x24564236 == magic[0]) && (0x20764316 == magic[1])) 
		sprintf(boot_cmd, "nand erase 0 40000;nand write %08x 0 40000", PHYS_SDRAM_1 + 0x8000);
		magic[0] = 0;
		magic[1] = 0;
		printf("\\nready for self-burning U-Boot image\\n\\n");
		setenv("bootdelay", "0");
		setenv("bootcmd", boot_cmd);
	

	return 0;


#ifdef BOARD_LATE_INIT
	board_late_init ();
#endif

从名字可以看出,board_late_init函数就是开发板初始化的末尾阶段了。在此函数中,PHYS_SDRAM_1是第一块内存的起始地址,如果if语句条件满足,则说明当前开发板是用的nandflash,则重新设置bootdelay和bootcmd。

13、网卡初始化

eth_initialize(gd->bd);

uboot可以同时支持多种网卡,但是对于开发板而言硬件一般只支持一种网卡。我们可以把网卡的初始化函数都放在eth_initialize函数里,具体调用哪个网卡初始化函数则由开发板的配置文件决定。

14、自动更新功能

/* GPH0_2: LEFT*/
static int check_menu_update_from_sd(void)

	unsigned int i;
	unsigned int reg;

	//GPH0_2
	reg = readl(GPH0CON);
	reg = reg & ~(0xf<<8) | (0x0<<8);
	writel(reg,GPH0CON);

	for(i=0;i<100;i++)
		udelay(500);

	reg = readl(GPH0DAT);
	reg = reg & (0x1<<2);

	if(reg)
		return 1;
	else //update mode
		return 0;


if(check_menu_update_from_sd()==0)//update mode

	puts ("[LEFT DOWN] update mode\\n");
	run_command("fdisk -c 0",0);
	update_all();

else
	puts ("[LEFT UP] boot mode\\n");

uboot在启动阶段设计的自动更新功能,如果当前是更新模式(update mode)则会去SD卡读取升级镜像并烧写到flash中;如果是启动模式(boot mode)则启动内核。
(1)check_menu_update_from_sd:通过检测GPH0_2引脚的状态来判断当前的模式,GPH0_2引脚实际是接的按键,当按键按下是更新模式,按键弹起是启动模式;
(2)run_command(“fdisk -c 0”,0):指定"fdisk -c 0"指令,这是分区的指令;
(3)update_all():该命令的功能是下载镜像并将镜像烧写到对应的分区,属于fastboot功能的一部分;

15、死循环

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

这个循环就是uboot的最后阶段,如果在bootdelay时间内没有检测到按键按下,则启动内核;如果有按键按下则进入到uboot的命令行。更具体的参见博客:《uboot中命令执行函数(main_loop函数)》

以上是关于uboot启动第二阶段——C语言的主要内容,如果未能解决你的问题,请参考以下文章

uboot启动第一阶段详解——汇编代码部分start.S

uboot启动过程

uboot启动过程

新版本uboot启动流程分析

Linux学习 :Uboot 移植

flash中怎么用代码实现初始化