uboot启动第二阶段--start_armboot函数分析(1)


path = …/uboot/lib_arm/board.c


一. init_fnc_t类型及相关使用介绍

  1. init_fnc_t 类型的定义为typedef int (init_fnc_t) (void); ,显然,这是一个返回类型为int的函数类型。在函数的第一行定义了一个二重指针init_fnc_ptr ,这个指针用于之后指向一个函数指针数组。
  2. board.c 文件中有一个init_sequence 函数指针数组,类型为init_fnc_t ,定义如下:
init_fnc_t *init_sequence[] = {
	cpu_init,		/* basic cpu dependent setup */
	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 */
	print_cpuinfo,		/* display cpu info (and speed) */
	checkboard,		/* display board info */
	dram_init,		/* configure available RAM banks */
	display_dram_config,
	NULL,
};

里面按顺序存放了各个部件初始化程序函数地址,返回类型为int,成功返回0,否则不为0。二重指针init_fnc_ptr 指向这个数组。

  1. 第37行有一for循环如下:
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
	if ((*init_fnc_ptr)() != 0) {
		hang ();
	}
}

二重指针init_fnc_ptr 先指向这个init_sequence 数组第一个元素,然后依次调用数组中的函数,直到调用到NULL地址,循环结束。如果调用的初始化程序未初始化成功,就会调用hang 函数,将程序挂起,即进入死循环直到重启开发板。hang函数如下:

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

二. 全局变量gd及其巧妙的内存分配

  1. 关于gd的定义其实是一个宏DECLARE_GLOBAL_DATA_PTR,该宏的内容为:
#define DECLARE_GLOBAL_DATA_PTR     register volatile gd_t *gd asm ("r8")

由于gd作为全局变量,在整个uboot第二阶段用到的地方特别多,所以定义为register,volatile,这样可以提升访问效率。

  1. 全局变量gd的类型为gd_t,该类型的定义为:
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 */
	void		**jt;		/* jump table */
} gd_t;

该结构体中定义了板信息(结构体指针bd),标志信息,波特率,控制台是否建立(布尔),重定位偏移量,环境变量地址,环境变量是否能够访问(布尔),frame基地址(关于LCD),跳转表。其中,结构体指针bd的类型为bd_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];
} bd_t;

bd_t类型包含了开发板波特率,开发板ip地址,开发板网卡地址,开发板环境变量,机器码,以及开发板启动参数与内存分配地址。

  1. 由于gd是一个指针变量,所以必须为gd分配内存,uboot为gd分配内存是在该函数第15行到33行。首先,uboot先定义了一个unsigned long 型的gd_base变量,然后将值CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE - sizeof(gd_t) 赋给gd_base变量,CFG_UBOOT_BASE 为uboot物理起始地址的宏,等于0x33E00000,CFG_UBOOT_SIZE 是uboot的“假设”大小,等于2M,CFG_MALLOC_LEN是堆的大小,等于912K,CFG_STACK_SIZE是栈的大小,等于512K,总的分配如下图:
    在这里插入图片描述
    gd_base被赋值后相当于获得了内存中的一个地址,也就是获得了空间,然后利用memset 函数将内存清0,至此申请内存工作完成。但可以注意到的一点是,在gd变量中,还有一个指针bd,所以同样的需要按照上面的方法为bd申请内存,关于其他详细内存分配请阅读uboot第一阶段分析。f分配具体过程如下:
	ulong gd_base;

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

	/* compiler optimization barrier needed for GCC >= 3.4 */
	__asm__ __volatile__("": : :"memory");

	memset ((void*)gd, 0, sizeof (gd_t));
	gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));
	memset (gd->bd, 0, sizeof (bd_t));

另外,计算出的实际地址在为指针赋值时需要强制类型转换,倒数第二句中(char*)是将gd强制类型转换为char型,以便在做运算时实际减去sizeof(bd_t)的真实大小。至于中间有一句内嵌汇编,这句话是为了防止版本大于3.4的gcc优化程序导致的错误。

三. init_sequence 函数指针数组元素分析

  1. cpu_init 函数在…/uboot/cpu/s5pc11x/Cpu.c 文件中,是CPU相关硬件的初始化,但由于在uboot的第一阶段已经初始化过了,所以在目前的cpu_init 函数什么也没做,直接返回0,表示初始化成功。
  2. board_init 函数在…/uboot/board/samsung/x210/X210.c 文件中,是开发板相关硬件的初始化,函数如下:
int board_init(void)
{
	DECLARE_GLOBAL_DATA_PTR;
	dm9000_pre_init();

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

	return 0;
}

第一句先声明了之后要使用的gd全局变量,然后初始化了开发板的网卡dm9000,初始化的工作主要是引脚与端口的配置。最后,第3,4句设置了uboot很重要的两个全局变量。第一个是uboot的机器码,只有uboot的机器码与linux内核的机器码相同,内核才会正常启动,否则,linux会认为与uboot不适配,导致启动失败。另外,这个机器码理论说来应是每个开发板配一个唯一固定的机器码,而且需要从官方申请,但如果没有特定要求,只是自己学习,随便写一个即可,只要linux与uboot的一致就行,这个机器码会在启动时作为参数传递给linux内核,在这,我们赋值2456。第二个是uboot启动所需参数的地址,在这PHYS_SDRAM_1 这个宏值为0x30000000,并且定义在…uboot/include/configs/X210_sd.h 文件第503行,所以不难发现这个uboot将uboot启动所需参数的地址设为0x30000100,将来linux会从这读取入口参数。

  1. interrupt_init 函数在…/uboot/cpu/s5pc11x/Interrupts.c 文件中,是uboot的计时器的初始化函数,之后用于bootdelay倒数计时,函数如下:
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);
}

第一句定义了定时器的寄存器变量,这儿用到了很巧妙的用法。首先S5PC11X_TIMERS 类型是一个结构体,这个结构体中的变量按照寄存器的排布而排布,就像下面这样:
在这里插入图片描述
这样编写的好处是,只要通过S5PC11X_GetBase_TIMERS 函数将这些寄存器基地址付给结构体的首地址(指针)即可访问所有寄存器,这样的写法在linux中很常见。定义完寄存器后,剩下的主要是配置Timer4的时钟频率以及装载值,第二句设置Timer4的第一级分频器为15 + 1 = 16。timer_load_val 是一个变量,初始值为0,所以执行为该变量赋值的操作,get_PCLK 函数获取了Timer4的时钟来源PCLK,整个式子计算出了10ms所需要的装载值赋给timer_load_val 变量。TCNTB4寄存器是用来计数的寄存器,TCON是关于定时器的设置寄存器,在函数最后几句代码,设置了Timer4的自动重装,装载值,定时器开启等配置。

  1. env_init 函数在…/uboot/common/Env_movi.h 文件中,是uboot的关于环境变量的初始化函数,主要检测一下是否有可用的环境变量。由于还未做环境变量的重定位,所以环境变量还不能使用。

  2. init_baudrate 函数在…/uboot/lib_arm/Board.c 文件中,这个函数完成了uboot的全局变量gd 中波特率的初始化。函数如下:

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 函数可以从环境变量中找到函数第一个参数的值,然后将值保存在第二个参数中,而且是以字符的形式保存的,最后返回一个大于0的数表示查找成功。所以在第三句为全局变量gd->bd->bi_baudrategd->baudrate 赋值时要先判断一下是否环境变量值查找成功,成功就将tmp字符串装换为int类型的值赋给它两,否则使用X210.h 文件中的默认配置值115200。

  1. serial_init 函数本应该是初始化串口的,但由于在uboot的第一阶段已经初始化过了,所以在第二阶段,这个函数其实什么也没做。

  2. console_init_f 函数在 …/uboot/common/Console.c 文件中,这个函数作为控制台初始化的第一阶段,实际上只做了一件事,就是给全局变量gd->have_console 赋值为1。

  3. display_banner 函数在 …/uboot/lib_arm/Board.c 文件中,这个函数完成了两件工作。首先,通过printf函数打印了uboot的版本号,编译时间以及对象。version_string字符串就是printf要打印的变量,定义如下:

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

U_BOOT_VERSION 宏是Makefile中创建的版本号,具体参考博客CONFIG_IDENT_STRING 宏是X210_sd.h 文件中定义的字符串"for x210"。然后第二件事就是通过open_backlight 函数将GPF3_5引脚输出高电平开启了LCD背光灯。

  1. print_cpuinfo 函数在 …/uboot/cpu/s5pc11x/s5pc110/Speed.c 文件中,这个函数与display_banner 函数一样也是打印信息的,主要显式了各个时钟总线上的频率,我们配置的X210开发板的内容如下:
    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

  2. checkboard 函数在 …/uboot/board/samsung/x210/X210.c 文件中,这个函数打印了开发板的名称,例如我们打印的是Board: X210。

  3. dram_init 函数在 …/uboot/board/samsung/x210/X210.c 文件中,该函数初始化了gd->bd->bi_dram 数组,该数组是一个结构体数组,有两个元素,一个是内存起始地址,另一个是内存大小,数组的长度是内存的片数,例如我们的开发板有两片内存,所以数组长度为2,且两片内存分别是0x30000000-0x3FFFFFFF,0x40000000-0x4FFFFFFF,所以gd->bd->bi_dram[0].start应该等于0x30000000,gd->bd->bi_dram[0].size应该等于256M,gd->bd->bi_dram[1].start应该等于0x40000000,gd->bd->bi_dram[1].size应该等于256M。这些值其实是以宏的方式赋值给数组的,关于宏的定义在X210_sd.h 中,修改这个文件第500行处即可以修改这些全局变量。关于内存的分配可以参考博客

  4. display_dram_config 函数在 …/uboot/lib_arm/Board.c 文件中,该函数通过上面的数组计算得到总的DRAM大小,并输出了DRAM的大小,以上述例子,输出信息如下:DRAM: 512MB。


uboot启动第二阶段–start_armboot函数分析(2)


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值