uboot启动第二阶段

1.前言

(1)uboot的第一阶段最后跳转到了start_armboot函数,也就是说start_armboot函数就是uboot的第二阶段。start_armboot函数的定义为位于lib_arm/board.c中的444 ~ 908行。

(2)uboot在第二阶段应该做什么?

在uboot的第一阶段主要完成了SoC内部的一些部件的初始化,例如看门狗、时钟等,以及DDR的初始化,最后完成了重定位。因此在uboot的第二阶段首先要做的就是初始化那些还没有被初始化的硬件,主要是SoC外部硬件(例如iNand、网卡芯片等),其次初始化uboot本身的一些东西(例如uboot的命令、环境变量等),最后初始化完必要的东西后进入uboot的命令行准备接受命令。

(3)uboot第二阶段完结于何处?

uboot启动后,在运行的过程中会打印出很多信息(这些信息就是uboot在第一和第二阶段不断进行初始化时,打印出来的信息),然后进入倒数bootdelay秒过程,如果此时用户不干涉,则当倒数bootdelay秒后,会执行bootcmd对应的启动命令来启动内核,此时uboot的生命周期就结束了;如果此时用户按下回车键干涉了,则打断倒数bootdelay秒过程,进入uboot的命令行下。

uboot的命令行就是一个死循环,不断重复:接收命令、解析命令、执行命令,这就是uboot最终的归宿。

2.init_fnc_t **init_fnc_ptr

init_fnc_t是一个函数类型,它的定义位于本文件的412行,原型为:

typedef int (init_fnc_t) (void);

在函数的第一行,也就是文件的446行就使用了这个类型,如下所示:

	init_fnc_t **init_fnc_ptr;

init_fnc_ptr是一个二重指针,在这里指向函数指针数组。

3.全局变量gd的由来

在代码的462 ~ 472行使用了一个全局变量gd,如下所示:

	/* Pointer is writable since we allocated a register for it */
#ifdef CONFIG_MEMORY_UPPER_CODE /* by scsuh */
	ulong gd_base;

	gd_base = CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE - sizeof(gd_t);		/* 计算gd的基地址 */
#ifdef CONFIG_USE_IRQ
	gd_base -= (CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ);
#endif
	gd = (gd_t*)gd_base;
#else
	gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));
#endif

它的定义位于本文件的70行,是一个宏定义,如下所示:

DECLARE_GLOBAL_DATA_PTR;

这个宏定义的原型是:

#define DECLARE_GLOBAL_DATA_PTR     register volatile gd_t *gd asm ("r8")

这个宏定义位于include/asm-arm/global_data.h的67行。

宏解析:定义了一个名字是gd的全局变量,这个全局变量是一个指针类型,占4字节。用volatile修饰表示这个变量是可变的,用register修饰表示这个变量尽量放到寄存器中,后面的asm("r8")是gcc支持的一种语法,意思就是要把这个变量放到寄存器r8中。综合以上分析可知,DECLARE_GLOBAL_DATA_PTR就是定义了一个要放在寄存器r8中全局变量,名字叫gd,类型是一个指向gd_t类型的指针。

为什么要定义register?因为这个全局变量gd(global data的简称)是uboot中很重要的一个全局变量(准确的说这个全局变量是一个结构体,里面有很多内容,这些内容加起来构成的结构体就是uboot中常用的所有的全局变量),这个gd在程序中经常被访问,为了提升效率,所以就把它放在了register中。因此这里仅仅运行效率方面的考虑,与功能要求无关,并不是必须的。

gd_t的定义位于include/asm-arm/global_data.h文件中,如下所示:

typedef	struct	global_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;
	phys_size_t	ram_size;	/* RAM size */
	unsigned long	reset_status;	/* reset status register at boot */
#endif
	void		**jt;		/* jump table */
} gd_t;

gd_t中定义了很多变量,大部分都有注释,所以不再过多的说明,这里需要特殊关注的仅仅只有一个:bd,这个变量是一个bd_t类型的指针。bd_t是开发板的板级信息的结构体,里面有不少硬件相关的参数,譬如波特率、IP地址、机器码、DDR内存分布等,它的定义位于include/asm-arm/uboot.h文件,如下所示:

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;

4.gd和bd的内存排布

(1)为什么要分配内存?DECLARE_GLOBAL_DATA_PTR宏只是定义了一个指针gd,并没有给gd所指向的结构体分配内存,因此我们在使用gd之前要先给它所指向的结构体分配内存,否则gd就是一个野指针。

(2)gd指向的结构体需要分配内存,但是当前的内存没有被管理(因为没有操作系统,所以无法统一管理内存),大片的内存散放着可以随意使用(只要使用内存地址直接去访问即可),但是因为uboot中后续很多操作还需要大片的连着的内存块,因此这里内存的使用要本着够用就好、紧凑排布的原则,所以我们在uboot中需要一个整体的规划。uboot的内存排布如下所示:

起始地址为0xC3E00000,长度为2MB-4KB

uboot区     长度为uboot的实际长度
间隔        
bd          长度为sizeof(bd_t),实际长度为36字节左右
gd          长度为sizoef(gd_t),实际长度为44B左右
堆区        长度为CFG_MALLOC_LEN,实际为912KB
栈区        长度为CFG_STACK_SIZE,实际为512KB

gd在内存排布中的位置的由来:

gd_base = CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE - sizeof(gd_t);	
gd = (gd_t*)gd_base;

bd在内存排布中的位置的由来:

gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));

5.init_sequence

代码:416 ~ 442行(init_sequence的定义)

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 */
	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,
};

代码:483 ~ 487行(init_sequence的使用)

	for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) { 
		if ((*init_fnc_ptr)() != 0) {
			hang ();
		}
	}

代码:910 ~ 914行(hang())

void hang (void)
{
	puts ("### ERROR ### Please RESET the board ###\n");
	for (;;);
}

(1)init_sequence是一个函数指针数组,数组中存储了很多个函数指针,这些指针指向的函数都是init_fnc_t类型(参数为void,返回值是int)

(2)init_sequence在定义时就初始化了,初始化的函数指针都是一些函数名。

(3)init_fnc_ptr是一个二重指针,可以指向init_sequenct这个函数指针数组。

(4)用for循环去遍历函数指针数组,遍历的目的是依次执行这个函数数组中的所有函数,那么该如何去遍历一个函数指针数组呢?

有2中方法:第一种,也是最常用的一种,就是用下标去遍历;第二种,不常用但是也可以,就是在数组的末尾添加一个标志,依次遍历到标志处。在这里使用的是第二种方法,由于数组中存放的全是函数指针,因此选用NULL来作为标志,遍历时从开头依次进行,直到NULL标志截止。这种方法的优势是不用事先统计数组中有多少个元素。

(5)init_fnc_t类型的函数的返回值都是一样的:执行成功返回0,错误返回非0。所以在遍历时,如果函数的返回值不等于0,则执行hang()(挂起)。分析hang()函数可知:uboot启动过程中,初始化板级硬件时不能出现任何错误,只要出现一个错误,整个过程就终止了,除了重启开发板外没有任何办法。

(6)init_sequence中的这些函数都是baord级别的各种硬件初始化。

6.init_sequence中的函数

1.cpu_init

代码:88 ~ 98行(cpu/s5pc11x/cpu.c)

int cpu_init (void)
{
	/*
	 * setup up stacks if necessary
	 */
#ifdef CONFIG_USE_IRQ
	IRQ_STACK_START = _armboot_start - CFG_MALLOC_LEN - CFG_GBL_DATA_SIZE - 4;
	FIQ_STACK_START = IRQ_STACK_START - CONFIG_STACKSIZE_IRQ;
#endif
	return 0;
}

(1)CPU已经初始化了,所以这里是空的。

2.board_init

代码:85 ~ 100行(board/samsung/x210/x210.c)

int board_init(void)
{
	DECLARE_GLOBAL_DATA_PTR;
#ifdef CONFIG_DRIVER_SMC911X
	smc9115_pre_init();
#endif

#ifdef CONFIG_DRIVER_DM9000
	dm9000_pre_init();
#endif

	gd->bd->bi_arch_number = MACH_TYPE;
	gd->bd->bi_boot_params = (PHYS_SDRAM_1+0x100);

	return 0;
}

(1)该函数的功能就是x210开发板相关的初始化

(2)DECLARE_GLOBAL_DATA_PTR:在这里声明是为了后面使用gd方便。

从这里就可以看出把gd声明成一个宏的原因是要到处使用gd,因此就要到处声明gd,定义成宏比较方便。

(3)网卡初始化:CONFIG_DRIVER_DM9000这个宏在x210_sd.h中是有定义的,这个宏是用来配置开发板的网卡的。dm9000_pre_init函数就是对应的DM9000网卡的初始化函数,在这个函数中,主要是网卡的GPIO和端口的配置,而不是驱动,因为网卡的驱动都是现成的、正确的,移植的时候是不需要修改的。开发板移植uboot时,如果要移植网卡,主要的工作就在这里。

(4)gd->bd->bi_arch_number:它的含义是开发板的机器码。所谓机器码其实就是uboot给这个开发板定义的唯一的编号。

机器码的主要作用就是在uboot和linux内核之间进行比对和适配。

嵌入式设备中每一个设备的硬件都是定制化的,不能通用。嵌入式设备的高度定制化导致硬件和软件不能随意适配使用,这就表明:这个开发板移植的内核镜像绝对不能下载到另一个开发板中,就算下载了,要不不能启动,要不不正常工作,有很多隐患。因此linux做了一个设置:给每个开发板定一个唯一编号(机器码),然后在uboot、linux内核中都有一个软件维护的机器码编号,然后开发板、uboot、linux三者去对比机器码,如果机器码对上了就启动,否则不启动(因为软件认为uboot或linux与这个硬件不适配)。

MACH_TYPE在x210_sd.h中有定义值时2456,并没有特殊含义,只是当前开发板对应的编号。这个编号就代表了x210这个开发板的机器码,将来这个开发板上面移植的linux内核中的机器码也必须是2456,否则就启动不起来。

uboot中配置的这个机器码会作为uboot给linux内核传参的一部分,传递给linux内核。内核启动过程中,会将接收到的这个机器码和自己本身的机器码作比对,如果相等就启动,不相等就不启动。

理论上来说,一个开发板的机器码不能随便定。理论上来说,有权利去发放这个机器码的只有uboot官方,所以我们做好一个开发板并且移植了uboot之后,理论上应该提交给uboot官方审核,并申请机器码(好像是免费的),但是国内的开发板基本都没有申请,主要是因为国内开发者英文都不行,和国外开源社区接触比较少,都是自己随便编号的。随便编号的问题就是有可能和别人的编号冲突,但是只要保证uboot和kernel中的编号是一致的,就不影响自己的开发板启动。

(5)gd->bd->bi_boot_params:表示uboot给linux内核启动传参时的内存地址。也就是说uboot给linux内核传参时是这么传的:uboot事先将准备好的传参(字符串,就是bootargs)放在内存的一个地址处(也就是bi_boot_args),然后uboot就启动了内核(uboot在启动内核时,真正是通过寄存器r0、r1、r2来直接传递参数的,其中有一个寄存器就是bi_boot_args)。内核启动后从寄存器中读取bi_boot_args,就知道了uboot传递的参数在内存的哪个位置,然后根据这个位置去内存中查找bootargs。

经过计算得知,x210中bi_boot_params的值为0x30000100,这个内存地址就被分配用来做内核传参了,所以在uboot的其他地方使用内存时要注意,千万不要把这里淹没了。

3.interrupt_init

代码:178 ~ 205行(cpu/s5pc11x/interrupts.c)

int interrupt_init(void)
{

	S5PC11X_TIMERS *const timers = S5PC11X_GetBase_TIMERS();

	/* use PWM Timer 4 because it has no output */
	/* prescaler for Timer 4 is 16 */
	timers->TCFG0 = 0x0f00;
	if (timer_load_val == 0) {
		/*
		 * for 10 ms clock period @ PCLK with 4 bit divider = 1/2
		 * (default) and prescaler = 16. Should be 10390
		 * @33.25MHz and  @ 66 MHz
		 */
		timer_load_val = get_PCLK() / (16 * 100);
	}

	/* load value for 10 ms timeout */
	lastdec = timers->TCNTB4 = timer_load_val;
	/* auto load, manual update of Timer 4 */
	timers->TCON = (timers->TCON & ~0x00700000) | TCON_4_AUTO | TCON_4_UPDATE;
	/* auto load, start Timer 4 */
	timers->TCON = (timers->TCON & ~0x00700000) | TCON_4_AUTO | COUNT_4_ON;
	timestamp = 0;


	return (0);
}

(1)看名字是和中断有关的函数,但实际不是,实际上这个函数是用来初始化定时器的(Timer4)。

(2)S5PV210中共有5个PWM Timer,其中Timer0 ~ Timer3都有一个对应的PWM输出引脚,而Timer4没有引脚,无法输出PWM波形。Timer4在设计的时候就不是用来输出PWM波形的(没有引脚,没有TCMPB寄存器),这个定时器被设计用来计时的。

(3)Timer4用来做计时时要使用到2个寄存器:TCNTB4、TCNTO4。TCNTB中存了一个数,这个数就是定时次数(每一次时间是由时钟决定的,其实就是由2级时钟分频器决定的)。我们定时时,只需要把定时时间/基准时间=数,将这个数放入TCNTB中即可;我们通过TCNTO寄存器即可读取时间有没有减到0,读取到0后,就知道定时的时间到了。

(4)使用Timer4定时,因为没有中断支持,所以CPU在定时时不能做其他的事情,CPU只能使用轮询方式不断查看TCNTO寄存器才能知道定时时间到了没。uboot中的定时就是通过Timer4来实现定时的,所以uboot在定时时不能做其他的事(uboot中的bootdelay的实现就是用轮询方式实现的)。

(5)总结:注意标准代码和裸机代码的区别,要学会通过定义结构体的方式来访问寄存器,通过函数来自动计算设置值已设置定时器。

4.env_init

代码:32 ~ 82行(common/env_movi.c)

int env_init(void)
{
#if defined(ENV_IS_EMBEDDED)
	ulong total;
	int crc1_ok = 0, crc2_ok = 0;
	env_t *tmp_env1, *tmp_env2;

	total = CFG_ENV_SIZE;

	tmp_env1 = env_ptr;
	tmp_env2 = (env_t *)((ulong)env_ptr + CFG_ENV_SIZE);

	crc1_ok = (crc32(0, tmp_env1->data, ENV_SIZE) == tmp_env1->crc);
	crc2_ok = (crc32(0, tmp_env2->data, ENV_SIZE) == tmp_env2->crc);

	if (!crc1_ok && !crc2_ok)
		gd->env_valid = 0;
	else if(crc1_ok && !crc2_ok)
		gd->env_valid = 1;
	else if(!crc1_ok && crc2_ok)
		gd->env_valid = 2;
	else {
		/* both ok - check serial */
		if(tmp_env1->flags == 255 && tmp_env2->flags == 0)
			gd->env_valid = 2;
		else if(tmp_env2->flags == 255 && tmp_env1->flags == 0)
			gd->env_valid = 1;
		else if(tmp_env1->flags > tmp_env2->flags)
			gd->env_valid = 1;
		else if(tmp_env2->flags > tmp_env1->flags)
			gd->env_valid = 2;
		else /* flags are equal - almost impossible */
			gd->env_valid = 1;
	}

	if (gd->env_valid == 1)
		env_ptr = tmp_env1;
	else if (gd->env_valid == 2)
		env_ptr = tmp_env2;
#else /* ENV_IS_EMBEDDED */
	gd->env_addr  = (ulong)&default_environment[0];
	gd->env_valid = 1;
#endif /* ENV_IS_EMBEDDED */

	return (0);
}

(1)从名字可以看出,该函数与环境变量的初始化有关

(2)在使用source insight时查到有很多的env_init函数,为什么呢?主要是因为uboot支持各种不同的介质(例如NorFlash、NandFlash、iNand、SD卡等),一般从哪里启动就会把环境变量env放到哪里。而各种介质存取操作env的方法是不一样的,因此uboot支持了各种不同介质中env的操作方法,所以有好多个env开头的.c文件。实际使用哪一个要根据自己的开发板使用的存储介质来决定(这些env_xxx.c中同时只有1个起作用,其他是不会起作用的,通过x210_sd.h中配置的宏来决定谁被包含),对于x210来说,使用的是env_movi.c中的函数。

(3)经过分析,这个函数只是对内存里维护的那一份uboot的env做了基本的初始化或者说是判定(判定里面有没有能用的环境便令),当前因为还没有进行环境变量从SD卡到DDR中的relocated,因此当前环境变量是不能用的。

(4)在start_armboot函数中(776行)调用了env_relocate才进行环境变量从SD卡到DDR中的重定位。重定位之后,需要环境变量时,才可以从DDR中去取,重定位之前,如果要使用环境变量,只能从SD卡中去取。

5.init_baudrate

代码:178 ~ 187行(lib_arm/board.c)

static int init_baudrate (void)
{
	char tmp[64];	/* long enough for environment variables */
	int i = getenv_r ("baudrate", tmp, sizeof (tmp));
	gd->bd->bi_baudrate = gd->baudrate = (i > 0)
			? (int) simple_strtoul (tmp, NULL, 10)
			: CONFIG_BAUDRATE;

	return (0);
}

(1)从名字来看,这个函数是用来初始化串口通信的波特率的。

(2)getenv_r用来读取环境变量的值。用getenv函数读取环境变量中"baudrate"的值(注意读取到的不是int,而是字符串),然后用simple_strtoul函数将字符串转换成数字格式的波特率。

(3)baudrate初始化时的规则:先去环境变量中读取"baudrate"这个环境变量的值。如果读取成功,则使用这个值作为环境变量,记录在gd_baudrate和bi_baudrate中;如果读取不成功,则使用x210_sd.h中的CONFIG_BAUDRATE的值作为波特率。从这里可以看出,环境变量的优先级是很高的。

6.serial_init

代码:54 ~ 59行(cpu/s5pc11x/serial.c)

/*
 * Initialise the serial port with the given baudrate. The settings
 * are always 8 data bits, no parity, 1 stop bit, no start bits.
 *
 */
int serial_init(void)
{
	serial_setbrg();

	return (0);
}

(1)该函数是用来初始化串口的

(2)在source insight中可以看到有很多serial_init函数,在这里我们使用的是cpu/s5pc11x/serial.c中的serial_init函数。

(3)这里的serial_init函数并没有做什么,因为在串口在汇编阶段已经初始化过了,所以这里就不再进行硬件的初始化工作了。

7.console_init_f

代码:362 ~372行(common/console.c)

/* Called before relocation - use serial functions */
int console_init_f (void)
{
	gd->have_console = 1;

#ifdef CONFIG_SILENT_CONSOLE
	if (getenv("silent") != NULL)
		gd->flags |= GD_FLG_SILENT;
#endif

	return (0);
}

(1)console_init_f是console(控制台)的第一阶段初始化。_f表示第一阶段初始化,_r表示第二阶段初始化。有时候初始化函数不能一次完成,中间必须要加载一些代码,因此将一个完整的模块的初始化分成了两个阶段。(在uboot的start_armboot的826行进行了console_init_r的初始化)

(2)该函数仅仅是对gc->have_console设置为1而已,其他事情都没做。

8.display_banner

代码:304 ~ 325行(lib_arm/board.c)

static int display_banner (void)
{
	printf ("\n\n%s\n\n", version_string);
	debug ("U-Boot code: %08lX -> %08lX  BSS: -> %08lX\n",
	       _armboot_start, _bss_start, _bss_end);
#ifdef CONFIG_MEMORY_UPPER_CODE /* by scsuh */
	debug("\t\bMalloc and Stack is above the U-Boot Code.\n");
#else
	debug("\t\bMalloc and Stack is below the U-Boot Code.\n");
#endif
#ifdef CONFIG_MODEM_SUPPORT
	debug ("Modem Support enabled\n");
#endif
#ifdef CONFIG_USE_IRQ
	debug ("IRQ Stack: %08lx\n", IRQ_STACK_START);
	debug ("FIQ Stack: %08lx\n", FIQ_STACK_START);
#endif
	open_backlight();//lqm.
	//open_gprs();

	return (0);
}

(1)该函数通过串口输出显示uboot的logo

(2)该函数使用printf函数向串口输出version_string这个字符串,但是从上面的分析中可以知道console_init_f并没有初始化好console,那么在这里怎么就可以使用printf呢?通过追踪printf的实现(printf函数的定义在common/console.c文件中),发现printf --> puts,在puts函数中会判断当前uboot中的console有没有被初始化好。如果初始化好了,则调用fputs完成串口发送(这条线才是控制台);如果console尚未初始化好,则会调用serial_puts(再调用serial_putc直接操作串口寄存器进行内容发送)。

(3)控制台是通过串口输出,非控制台也是通过串口输出,究竟什么是控制台?使用控制台和不使用控制台有什么区别?

分析代码发现,控制台就是用软件虚拟出来的设备,这个设备有一套专用的通信函数(发送、接收...),控制台的通信函数最终会映射到硬件的通信函数中来实现。uboot中控制台的通信函数是直接映射到硬件串口的通信函数中的,也就是说uboot用不用控制台没有本质区别,但是在别的体系中,控制台的通信函数映射到硬件通信函数时,用软件做了一些中间优化,例如缓冲机制。(操作系统中的控制台都使用了缓冲机制,所以有时候printf了,但是并没有在屏幕上看到输出信息,就是因为有了缓冲机制。输出的信息只是到了console的buffer中,buffer还没有被刷新到硬件输出设备上,尤其是在输出设备是LCD时)

(4)UBOOT_VERSION在uboot源码中找不到定义,这个变量是在makefile中定义的,然后在编译时生成的include/version_autogeneted.h中用一个宏定义实现的。

9.print_cpuinfo

代码:228 ~ 275行(cpu/s5pc11x/s5pv110/speed.c)

int print_cpuinfo(void)
{
	uint set_speed;
	uint tmp;
	uchar result_set;

#if defined(CONFIG_CLK_533_133_100_100)
	set_speed = 53300;
#elif defined(CONFIG_CLK_667_166_166_133)
	set_speed = 66700;
#elif defined(CONFIG_CLK_800_200_166_133)
	set_speed = 80000;
#elif defined(CONFIG_CLK_1000_200_166_133)
	set_speed = 100000;
#elif defined(CONFIG_CLK_1200_200_166_133)
	set_speed = 120000;
#else
	set_speed = 100000;
	printf("Any CONFIG_CLK_XXX is not enabled\n");
#endif
	tmp = (set_speed / (get_ARMCLK()/1000000));

	if((tmp < 105) && (tmp > 95)){
		result_set = 1;
	} else {
		result_set = 0;
	}

#ifdef CONFIG_MCP_SINGLE
	printf("\nCPU:  S5PV210@%ldMHz(%s)\n", get_ARMCLK()/1000000, ((result_set == 1) ? "OK" : "FAIL"));
#else
	printf("\nCPU:  S5PC110@%ldMHz(%s)\n", get_ARMCLK()/1000000, ((result_set == 1) ? "OK" : "FAIL"));
#endif
	printf("        APLL = %ldMHz, HclkMsys = %ldMHz, PclkMsys = %ldMHz\n",
			get_FCLK()/1000000, get_HCLK()/1000000, get_PCLK()/1000000);
#if 1
	printf("	MPLL = %ldMHz, EPLL = %ldMHz\n",
			get_MPLL_CLK()/1000000, get_PLLCLK(EPLL)/1000000);
	printf("		       HclkDsys = %ldMHz, PclkDsys = %ldMHz\n",
			get_HCLKD()/1000000, get_PCLKD()/1000000);
	printf("		       HclkPsys = %ldMHz, PclkPsys = %ldMHz\n",
			get_HCLKP()/1000000, get_PCLKP()/1000000);
	printf("		       SCLKA2M  = %ldMHz\n", get_SCLKA2M()/1000000);
#endif
	puts("Serial = CLKUART ");

	return 0;
}

(1)uboot启动过程中:

CPU:  S5PV210@1000MHz(OK)
        APLL = 1000MHz, HclkMsys = 200MHz, PclkMsys = 100MHz
        MPLL = 667MHz, EPLL = 96MHz
                       HclkDsys = 166MHz, PclkDsys = 83MHz
                       HclkPsys = 133MHz, PclkPsys = 66MHz
                       SCLKA2M  = 200MHz
Serial = CLKUART 

这些信息都是pirnt_cpuinfo打印出来的。

10.checkboard

代码:168 ~ 182行(board/samsung/x210/x210.c)

#ifdef CONFIG_DISPLAY_BOARDINFO
int checkboard(void)
{
#ifdef CONFIG_MCP_SINGLE
#if defined(CONFIG_VOGUES)
	printf("\nBoard:   VOGUESV210\n");
#else
	printf("\nBoard:   X210\n");
#endif //CONFIG_VOGUES
#else
	printf("\nBoard:   X210\n");
#endif
	return (0);
}
#endif

(1)这个函数的作用是检查当前开发板是哪个开发板,并打印开发板的名字

11.init_func_i2c

代码:367 ~ 375行(lib_arm/board.c)

#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C)
static int init_func_i2c (void)
{
	puts ("I2C:   ");
	i2c_init (CFG_I2C_SPEED, CFG_I2C_SLAVE);
	puts ("ready\n");
	return (0);
}
#endif

(1)该函数实际并没有被执行,因为x210的uboot中没有使用I2C,如果以后开发板要扩展I2C硬件设备的话,则在x210_sd.h中配置相应的宏即可开启。

12.dram_init

代码:102 ~ 120行(board/samsung/x210/x210.c)

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;

#if defined(PHYS_SDRAM_2)
	gd->bd->bi_dram[1].start = PHYS_SDRAM_2;
	gd->bd->bi_dram[1].size = PHYS_SDRAM_2_SIZE;
#endif

#if defined(PHYS_SDRAM_3)
	gd->bd->bi_dram[2].start = PHYS_SDRAM_3;
	gd->bd->bi_dram[2].size = PHYS_SDRAM_3_SIZE;
#endif

	return 0;
}

(1)从名字上看,该函数是关于DDR初始化的。疑问:在汇编阶段已经初始化过DDR了,否则也无法relocate到第二部分,怎么在这里又初始化DDR?

(2)该函数实际做的工作是在给gd->bd中关于DDR的配置部分的全局变量赋值。让gd->bd记录当前开发板的DDR的配置信息,以便在uboot中使用内存。、

(3)从代码来看,其实就是初始化gd->bd->bi_dram这个结构体数组。

13.display_dram_config

代码:334 ~ 357行(lib_arm/board.c)

/*
 * WARNING: this code looks "cleaner" than the PowerPC version, but
 * has the disadvantage that you either get nothing, or everything.
 * On PowerPC, you might see "DRAM: " before the system hangs - which
 * gives a simple yet clear indication which part of the
 * initialization if failing.
 */
static int display_dram_config (void)
{
	int i;

#ifdef DEBUG
	puts ("RAM Configuration:\n");

	for(i=0; i<CONFIG_NR_DRAM_BANKS; i++) {
		printf ("Bank #%d: %08lx ", i, gd->bd->bi_dram[i].start);
		print_size (gd->bd->bi_dram[i].size, "\n");
	}
#else
	ulong size = 0;

	for (i=0; i<CONFIG_NR_DRAM_BANKS; i++) {
		size += gd->bd->bi_dram[i].size;
	}

	puts("DRAM:    ");
	print_size(size, "\n");
#endif

	return (0);
}

(1)从名字上看,本函数是用来打印dram的配置信息的。

(2)启动信息中的:(DRAM:512M)就是在这个函数中打印出来的

(3)思考:如何在uboot运行中得知DDR的配置信息?uboot中有一个命令叫bdinfo,这个命令可以打印出gd->bd中记录的所有硬件相关的全局变量的值,因此可以DDR中的配置信息。

DRAM bank   = 0x00000000
-> start    = 0x30000000
-> size     = 0x10000000
DRAM bank   = 0x00000001
-> start    = 0x40000000
-> size     = 0x10000000

14.总结

都是板级硬件的初始化以及gd和gd->bd中数据结构的初始化,例如:网卡初始化、机器码(gd->bd->bi_arch_number)、内核传参DDR地址(gd->bd->bi_boot_params)、Timer4初始化为10ms一次、波特率设置(gd->bd->bi_baudrate和gd->baudrate)、console第一阶段初始化(gd->have_console设置为1)、打印uboot启动信息、打印cpu相关的设置信息、检查并打印当前开发板的名字、DDR配置信息初始化(gd->bd->bi_ram)、打印DDR总容量。

7.CFG_NO_FLASH

代码:489 ~ 493行(lib_arm/board.c)

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

(1)虽然NandFlash和NorFlash都是Flash,但是一般NandFlash会简称为Nand而不是Flash,一般讲Flash都是指NorFlash,这里第2行代码是有关NorFlash的。

(2)flash_init执行的是开发板中对应的NorFlash的初始化、display_flash_config打印的也是NorFlash的配置信息(Flash:8MB就是这里打印出来的),但是实际上x210是没有NorFlash的,所以这两行代码可以去掉(在这里为什么没有去掉?猜测有可能是去掉这两行代码,会导致别的地方工作不正常,需要花时间去移植调试,然后移植的人就懒得弄,实际上不去掉,除了显示有8MB的Flash之外,也没有其他的影响)。

(3)代码实践:去掉Flash会不会报错

结论:加上CFG_NO_FLASH宏之后编译出错,说明代码移植的不好,哪个文件的包含没有被这个宏控制,于是移植的人就直接放弃不管了。

8.CONFIG_VFD、CONFIG_LCD

代码:495 ~ 522行(lib_arm/board.c)

#ifdef CONFIG_VFD
#	ifndef PAGE_SIZE
#	  define PAGE_SIZE 4096
#	endif
	/*
	 * reserve memory for VFD display (always full pages)
	 */
	/* bss_end is defined in the board-specific linker script */
	addr = (_bss_end + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);
	size = vfd_setmem (addr);
	gd->fb_base = addr;
#endif /* CONFIG_VFD */

#ifdef CONFIG_LCD
	/* board init may have inited fb_base */
	if (!gd->fb_base) {
#		ifndef PAGE_SIZE
#		  define PAGE_SIZE 4096
#		endif
		/*
		 * reserve memory for LCD display (always full pages)
		 */
		/* bss_end is defined in the board-specific linker script */
		addr = (_bss_end + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);
		size = lcd_setmem (addr);
		gd->fb_base = addr;
	}
#endif /* CONFIG_LCD */

(1)CONFIG_VFD和CONFIG_LCD与显示相关,这个是uboot中自带的LCD显示的软件架构,但是在使用LCD时,并没有使用这套架构,而是在后面添加了一个LCD显示部分。

9.mem_malloc_init

代码:525 ~ 529行(lib_arm/board.c)

	/* armboot_start is defined in the board-specific linker script */
#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

(1)mem_malloc_init用来初始化uboot的堆管理器

(2)uboot中自己维护了一段堆程序,肯定自己就有一套代码来管理这个堆内存,有了这些东西,在uboot中也可以使用malloc、free这套机制来申请内存和释放内存,目前在DDR中给堆预留了892KB的内存。

10.mmc初始化(开发板独有的初始化)

代码:536 ~ 768 行(lib_arm/board.c)

//******************************//
// Board Specific
// #if defined(CONFIG_SMDKXXXX)
//******************************//

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

	if (INF_REG3_REG == 0)
		movi_ch = 0;
	else
		movi_ch = 1;

	movi_set_capacity();
	movi_init();
	movi_set_ofs(MOVI_TOTAL_BLKCNT);
	#endif
	#endif

	if (INF_REG3_REG == BOOT_ONENAND) {
	#if defined(CONFIG_CMD_ONENAND)
		puts("OneNAND: ");
		onenand_init();
	#endif
		/*setenv("bootcmd", "onenand read c0008000 80000 380000;bootm c0008000");*/
	} else {
		puts("NAND:    ");
		nand_init();

		if (INF_REG3_REG == 0 || INF_REG3_REG == 7)
			setenv("bootcmd", "movi read kernel c0008000;movi read rootfs c0800000;bootm c0008000");
		else
			setenv("bootcmd", "nand read c0008000 80000 380000;bootm c0008000");
	}
#endif	/* CONFIG_SMDK6410 */

#if defined(CONFIG_SMDKC100)

	#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_CMD_ONENAND)
		puts("OneNAND: ");
		onenand_init();
	#endif

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

#endif /* CONFIG_SMDKC100 */

#if defined(CONFIG_X210)														/* CONFIG_X210 */

	#if defined(CONFIG_GENERIC_MMC)
		puts ("SD/MMC:  ");
		mmc_exist = mmc_initialize(gd->bd);
		if (mmc_exist != 0)
		{
			puts ("0 MB\n");
#ifdef CONFIG_CHECK_X210CV3
			check_flash_flag=0;//check inand error!
#endif
		}
#ifdef CONFIG_CHECK_X210CV3
		else
		{
			check_flash_flag=1;//check inand ok! 
		}
#endif
	#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 */														/* end of CONFIG_X210 */

#if defined(CONFIG_SMDK6440)
	#if defined(CONFIG_GENERIC_MMC)
	puts ("SD/MMC:  ");
	mmc_exist = mmc_initialize(gd->bd);
	if (mmc_exist != 0)
	{
		puts ("0 MB\n");
	}
	#else
	#if defined(CONFIG_MMC)
	if (INF_REG3_REG == 1) {	/* eMMC_4.3 */
		puts("eMMC:    ");
		movi_ch = 1;
		movi_emmc = 1;

		movi_init();
		movi_set_ofs(0);
	} else if (INF_REG3_REG == 7 || INF_REG3_REG == 0) {	/* SD/MMC */
		if (INF_REG3_REG & 0x1)
			movi_ch = 1;
		else
			movi_ch = 0;

		puts("SD/MMC:  ");

		movi_set_capacity();
		movi_init();
		movi_set_ofs(MOVI_TOTAL_BLKCNT);

	} else {

	}
	#endif
	#endif

	if (INF_REG3_REG == 2) {
			/* N/A */
	} else {
		puts("NAND:    ");
		nand_init();
		//setenv("bootcmd", "nand read c0008000 80000 380000;bootm c0008000");
	}
#endif /* CONFIG_SMDK6440 */

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

	if (INF_REG3_REG == 0)
		movi_ch = 0;
	else
		movi_ch = 1;

	movi_set_capacity();
	movi_init();
	movi_set_ofs(MOVI_TOTAL_BLKCNT);
	#endif
	#endif

	if (INF_REG3_REG == BOOT_ONENAND) {
	#if defined(CONFIG_CMD_ONENAND)
		puts("OneNAND: ");
		onenand_init();
	#endif
		/*setenv("bootcmd", "onenand read c0008000 80000 380000;bootm c0008000");*/
	} else if (INF_REG3_REG == BOOT_NAND) {
		puts("NAND:    ");
		nand_init();
	} else {
	}

	if (INF_REG3_REG == 0 || INF_REG3_REG == 7)
		setenv("bootcmd", "movi read kernel c0008000;movi read rootfs c0800000;bootm c0008000");
	else
		setenv("bootcmd", "nand read c0008000 80000 380000;bootm c0008000");
#endif	/* CONFIG_SMDK6430 */

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

	movi_set_capacity();
	movi_init();
	movi_set_ofs(MOVI_TOTAL_BLKCNT);

	#endif
	#endif

	#if defined(CONFIG_CMD_ONENAND)
	if (INF_REG3_REG == BOOT_ONENAND) {
		puts("OneNAND: ");
		onenand_init();
		}
	#endif

#endif	/* CONFIG_SMDK6442 */

#if defined(CONFIG_SMDK2416) || defined(CONFIG_SMDK2450)
	#if defined(CONFIG_NAND)
	puts("NAND:    ");
	nand_init();
	#endif

	#if defined(CONFIG_ONENAND)
	puts("OneNAND: ");
	onenand_init();
	#endif

	#if defined(CONFIG_BOOT_MOVINAND)
	puts("SD/MMC:  ");

	if ((0x24564236 == magic[0]) && (0x20764316 == magic[1])) {
		printf("Boot up for burning\n");
	} else {
			movi_init();
			movi_set_ofs(MOVI_TOTAL_BLKCNT);
	}
	#endif
#endif	/* CONFIG_SMDK2416 CONFIG_SMDK2450 */

代码:1177 ~ 1208行(drivers/mmc/mmc.c)

int mmc_initialize(bd_t *bis)
{
	struct mmc *mmc;
	int err;

	INIT_LIST_HEAD(&mmc_devices);
	cur_dev_num = 0;

	if (board_mmc_init(bis) < 0)
		cpu_mmc_init(bis);

#if defined(DEBUG_S3C_HSMMC)
	print_mmc_devices(',');
#endif

#ifdef CONFIG_CHECK_X210CV3
	mmc = find_mmc_device(1);//lqm
#else
	mmc = find_mmc_device(0);
#endif
	if (mmc) {
		err = mmc_init(mmc);
		if (err)
			err = mmc_init(mmc);
		if (err) {
			printf("Card init fail!\n");
			return err;
		}
	}
	printf("%ldMB\n", (mmc->capacity/(1024*1024/(1<<9))));
	return 0;
}

(1)开发板独有的初始化,意思是三星用一套uboot同时满足了好多个系列的开发板,然后在这里把不同开发板自己独有的一些初始化写到了这里,用#if条件编译配合CONFIG_XXX宏来选定特定的开发板。

(2)x210相关的配置在599 ~ 630行(对应上面代码的69 ~ 102行)

(3)mmc_initialize,看名字应该是MMC相关的一些基本的初始化,其实就是用来初始化SoC内部的SD/MMC控制器的。该函数的定义在drivers/mmc/mmc.c文件中。

(4)uboot中对硬件的操作(网卡、SD卡...)都是借用linux内核中的驱动来实现的,uboot根目录下有个drivers文件夹,这里面放的全都是从linux内核中移植过来的各种驱动源文件。

(5)mmc_initialize是具体硬件架构无关的一个MMC初始化函数,所有的使用了这套架构的代码都调用这个函数来完成MMC的初始化。在mmc_initialize函数中调用board_mmc_init和cpu_mmc_init来完成具体硬件的MMC控制器的初始化工作。

(6)cpu_mmc_init的定义位于cpu/s5pc11x/cpu.c文件中,这里面又间接调用了drivers/mmc/s3c_mmcxxx.c中的驱动代码来初始化硬件MMC控制器。这里面分层很多,分层思想一定要有,否则就完全糊涂了。

11.env_relocate

代码:776行(lib_arm/board.c)

	/* initialize environment */
	env_relocate ();

(1)env_relocate是环境变量的重定位,完成从SD卡中将环境变量读取到DDR中的任务。

(2)环境变量到底从哪里来的?SD卡中有一些独立的扇区(8个)作为环境变量存储区域的。但是在烧录/部署系统时,只是烧录了uboot分区、kernel分区和rootfs分区,根本不层烧录env分区,所以当烧录完系统第一次启动时,env分区是空的,若此刻尝试去SD卡的env分区读取环境变量时会失败(读取回来后进行CRC校验时失败),所以uboot选择从其内部代码中定义好的一套默认的环境变量来使用(这就是默认环境变量),这套默认的环境变量在本次运行时,会被读取到DDR中的环境变量部分,然后被写入SD卡的env分区(有可能是输入saveenv指令时写入,有可能是uboot设计了第一次读取默认环境变量后就写入),然后下次再开机时,uboot就会从SD卡的env分区读取环境变量到DDR中,这次读取就不会失败了。

(3)真正的从SD卡到DDR中重定位env的代码是env_relocate_spec内部的movi_read_env完成的。

12. IP地址、MAC地址的确定

代码:787 ~ 816行(lib_arm/board.c)

	/* 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;
		}

#ifdef CONFIG_HAS_ETH1
		i = getenv_r ("eth1addr", tmp, sizeof (tmp));
		s = (i > 0) ? tmp : NULL;

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

(1)开发板的IP地址是在gd->bd中维护的,来源于环境变量ipaddr。getenv函数用来获取字符串格式的IP地址,然后用string_to_ip将字符串格式的IP地址转换为字符串格式的点分十进制格式。

(2)IP地址由4个0 ~ 255之间的数字组成,因此一个IP地址在程序中最简单的存储方式就是一个unsigned int,但是人类容易看懂的并不是这种类型,而是点分十进制类型(192.168.1.2),这两种类型可以相互转换。

13. device_init

代码:818行(lib_arm/board.c)

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

(1)device_init,看名字就是设备的初始化,这里的设备指的是开发板上的硬件设备。放在这里初始化的设备都是驱动设备,这个函数本来就是从驱动框架中衍生出来的。uboot中很多设备的驱动就是直接移植linux内核的(例如网卡、SD卡),linux内核中的驱动都有相应的设备初始化函数。linux内核在启动过程中就有一个device_init(名字不一定完全对,但是差不多),作用就是集中执行各种硬件驱动的init函数。

(2)uboot的这个函数其实就是从linux内核中移植过来的,它的作用也是去执行所有的从linux内核中继承过来的那些硬件驱动的初始化函数。

14. jumptable_init

代码:824行(lib_arm/board.c)

	jumptable_init ();

(1)jumptable,跳转表,本身是一个函数指针数组,里面记录了很多函数的函数名。看着阵势是要实现一个函数指针到具体函数的映射关系,将来通过跳转表中函数指针就可以执行具体的函数。这个其实就是在用C语言实现面向对象编程,在linux内核中有很多这种技巧。

(2)通过分析发现跳转表只是被赋值,从未被利用,因此跳转在uboot中根本就没使用。

15. console_init_r

代码:825 ~ 827行(lib_arm/board.c)

#if !defined(CONFIG_SMDK6442)
	console_init_r ();	/* fully init console as a device */
#endif

(1)console_init_f是控制台的第一阶段的初始化,console_init_r是第二阶段的初始化。实际上第一阶段初始化并没有实质性工作,第二阶段初始化才进行了实质性工作。

(2)uboot中有很多同名函数,使用source insight工具去搜索时经常搜索到不对的函数。

(3)console_init_r就是console纯软件结构方面的初始化(说白了就是去给console相关的数据结构中填充相应的值),所以属于纯软件配置类型的初始化函数。

(4)uboot的console实际上并没有干有意义的转化,它就是直接调用的串口通信的函数,所以用不用console实际并没有什么区别。(linux的console中就提供了缓冲机制等,不用console就不能实现的东西)

16. enable_interrupts

代码:835行(lib_arm/board.c)

	/* enable exceptions */
	enable_interrupts ();

(1)看名字应该是中断初始化代码,这里指的是CPSR中总中断标志位的使能。

(2)因为uboot中没有使用中断,因此没有定义CONFIG_USE_IRQ宏,因此这里的函数只是一个空壳子。

(3)uboot中经常出现一种情况就是根据一个宏是否定义了来条件编译决定是否调用一个函数内部的代码。uboot中有2中解决方案来处理这种情况:方案一,在调用函数处使用条件编译,然后函数体提供完整代码;方案二,在调用函数处直接调用,然后在函数体处提供两个函数体,一个是有实体的一个是空壳子,用宏定义条件编译来决定实际编译时编译哪个函数进去。

17. loadaddr、bootfile两个环境变量

代码:855 ~ 863行(lib_arm/board.c)

	/* Initialize from environment */
	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

(1)这两个环境变量都是内核启动有关的,在启动linux内核时,会参考这两个环境变量的值

18. board_late_init

代码:865 ~ 867行(lib_arm/board.c)

#ifdef BOARD_LATE_INIT
	board_late_init ();
#endif

(1)看名字这个函数就是开发板级别的一些初始化里比较晚的了,就是晚点初始化的。所以晚点就是前面该初始化的都已经初始化了,剩下的一些必须放在后面初始化的就在这里了。侧面说明开发板级别的硬件软件初始化告一段落了。

(2)对于x210来说,这个函数是空的。

19. eth_initialize

代码:872行(lib_arm/board.c)

	eth_initialize(gd->bd);

(1)看名字应该网卡相关的初始化。这里不是SoC与网卡芯片连接时SoC这边的初始化,而是网卡芯片本身的初始化。

(2)对于X210(DM9000)来说,这个函数是空的。X210的网卡初始化在board_init函数中,网卡芯片的初始化在驱动中。

20. x210_preboot_init(LCD和logo显示)

代码:885 ~ 888行

/****************lxg added**************/
#ifdef CONFIG_MPAD
	extern int x210_preboot_init(void);
	x210_preboot_init();
#endif

(1)x210开发板启动起来之前的一些初始化,以及LCD屏幕上的logo显示。

21. check menukey to update from sd

代码:892 ~ 900行(lib_arm/board.c)

	/* check menukey to update from sd */
	extern void update_all(void);
	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");

(1)uboot启动的最后一个阶段设计了一个自动更新的功能,其实就是:将要升级的镜像放到SD卡的固定目录中,然后开机时在uboot启动的最后阶段检查升级标志(是一个按键,按键中标志为"LEFT"的那个按键,这个按键如果按下则表示update mode,如果启动未按下,则表示boot mode)。如果进入update mode,uboot会自动从SD卡中读取镜像文件然后烧录到iNand中;如果进入boot mode,uboot不执行update,直接启动正常运行。

(2)这种机制能够帮助我们快速烧录系统,常用于量产时用SD卡进行系统烧录部署。

22. 死循环

代码:903 ~ 905行(lib_arm/board.c)

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

(1)解析器

(2)开机倒数自动执行

(3)命令补全

23. 总结

(1)第二阶段主要是对开发板级别的硬件、软件数据结构进行初始化

(2)函数调用顺序

init_sequence
    cpu_init                    空的
    board_init                  网卡、机器码、内存传参地址
        dm9000_pre_init         网卡
        gd->bd->bi_arch_number  机器码
        gd->bd->bi_boot_params  内存传参地址
    interrupt_init              定时器
    env_init
    init_baudrate               gd数据结构中波特率
    serial_init                 空的
    console_init_f              空的
    display_banner              打印启动信息
    print_cpuinfo               打印CPU时钟设置信息
    checkboard                  检验开发板名字
    dram_init                   gd数据结构中DDR信息
    display_dram_config         打印DDR配置信息表
mem_malloc_init                 初始化uboot自己维护的堆管理器的内存
mmc_initialize                  iNand/SD卡的SoC控制器和卡的初始化
env_relocate                    环境变量重定位
gd->bd->bi_ip_addr              gd数据结构赋值
gd->bd->bi_enetaddr             gd数据结构赋值
devices_init                    空的
jumptable_init                  不用关注的
console_init_r                  真正的控制台初始化
enable_interrupts               空的
loadaddr、bootfile              环境变量读出初始化全局变量
board_late_init                 空的
eth_initialize                  空的
x210_preboot_init               LCD初始化和显示logo
check_menu_update_from_sd       检查自动更新
main_loop                       主循环

猜你喜欢

转载自blog.csdn.net/linuxweiyh/article/details/82454815
今日推荐