u-boot第二阶段分析(三)

本文详细分析了u-boot启动的第二阶段,包括interrupt_init函数中Timer4的10ms定时器初始化,env_init对环境变量的初始化,init_baudrate设置波特率,以及console_init_f和display_banner的相关内容。文章指出,即使console未完全初始化,printf也能通过serial_puts函数间接输出信息。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

start_armboot函数(三)

注:本次分析的u-boot是九鼎官方的u-boot代码
下载地址:链接:http://pan.baidu.com/s/1gfpDZqj 密码:7cqe


接着上一章的内容;

1.interrupt_init
这个函数看名字是中断初始化,其实是定时器的初始化,准确说是Timer4,代码如下:

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)x210中有5个PWM定时器,其中只有Timer4没有输出,所以这个定时器主要是用来做计时器的。
(2)Timer4用来做计时时要使用到2个寄存器:TCNTB4、TCNTO4。TCNTB中存了一个数,这个数就是定时次数(每一次时间是由时钟决定的,其实就是由2级时钟分频器决定的)。我们定时时只需要把定时时间/基准时间=数,将这个数放入TCNTB中即可;我们通过TCNTO寄存器即可读取时间有没有减到0,读取到0后就知道定的时间已经到了。
(3)使用Timer4来定时,因为没有中断支持,所以CPU不能做其他事情同时定时,CPU只能使用轮询方式来不断查看TCNTO寄存器才能知道定时时间到了没。因为Timer4的定时是不能实现微观上的并行。uboot中定时就是通过Timer4来实现定时的,所以uboot中定时时不能做其他事,比如bootdelay。
(4)interrupt_init函数将timer4设置为定时10ms。关键部位就是get_PCLK函数获取系统设置的PCLK_PSYS时钟频率,然后设置TCFG0和TCFG1进行分频,然后计算出设置为10ms时需要向TCNTB中写入的值,将其写入TCNTB,然后设置为auto reload模式,最后开定时器开始计时。

2.env_init

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

这个函数 的内容主要是对内存里维护的那一份uboot的环境变量(env)的一个初始化或者说是判定里面有没有能用的环境变量。当前因为我们还没进行环境变量从SD卡到DDR中的relocate,因此当前环境变量是不能用的。
在start_armboot函数中(776行)调用env_relocate才进行环境变量从SD卡中到DDR中的重定位。重定位之后需要环境变量时才可以从DDR中去取,重定位之前如果要使用环境变量只能从SD卡中去读取。

3.init_baudrate
这个函数是用来初始化波特率的,代码如下:

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

首先先用getenv_r读取环境变量中的baudrate,如果读取成功(i>0),则用simple_strtoul函数将读取的字符串转换为十进制(用getenv函数读取环境变量中“baudrate”的值不是int类型的,而是字符串类型);如果读取不成功则将CONFIG_BAUDRATE的值赋值过去,这个宏的大小115200。
从中我们可以看出,环境变量的优先级比较高。

4.serial_init
因为在start.S中已经初始化过了,所以这里函数为空。

5.console_init_f
(1)console_init_f是console(控制台)的第一阶段初始化。_f表示是第一阶段初始化,_r表示第二阶段初始化。有时候初始化函数不能一次一起完成,中间必须要夹杂一些代码,因此将完整的一个模块的初始化分成了2个阶段。(我们的uboot中start_armboot的826行进行了console_init_r的初始化)
(2)console_init_f在uboot/common/console.c中,仅仅是对gd->have_console设置为1而已,其他事情都没做。

6.display_banner
(1)diaplay_banner函数主要是用来打印u-boot的logo信息的,代码如下:

printf ("\n\n%s\n\n", version_string);

这里便出现了一个问题:display_banner中使用printf函数向串口输出了version_string这个字符串。那么上面的分析表示console_init_f并没有初始化好console怎么就可以printf了呢?

答:通过追踪printf函数是如何实现的可以解答这个问题。printf函数是通过puts函数实现的,而puts函数中会判断当前uboot中console有没有被初始化好。如果console初始化好了则调用fputs完成串口发送(这条线才是控制台);如果console尚未初始化好则会调用serial_puts函数,在serial_puts中再调用serial_putc直接操作串口寄存器进行内容发送;

(2)version_string定义如下:

const char version_string[] =
	U_BOOT_VERSION" (" __DATE__ " - " __TIME__ ")"CONFIG_IDENT_STRING;

其中,U_BOOT_VERSION是在makefile中定义的,DATE__与__TIME 表示u-boot编译的日期与时间,CONFIG_IDENT_STRING是一个宏定义,内容是“for x210”;

7.print_cpuinfo
函数作用是打印cpu相关信息,比如在u-boot启动过程中会出现:

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 

这些信息都是print_cpuinfo打印出来的。(注意:上面是u-boot启动时显示的内容,不是代码)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值