uboot中的知识点记录-1

1 函数类型的使用场景

1.1 第一层理解

通常使用定义函数类型的时候,我们习惯定义成函数指针来使用,如

typedef int(*fuc_ptr_t)(int,int);
int add(int a, int b)
{
	return a+b;
}
int main(void)
{
	fuc_ptr_t my_fuc = NULL;
	my_fuc = add;
	my_fuc(10,20);
}

上面定义了一个函数指针类型,指向了add函数

  • 类型定义:typedef int(*fuc_ptr_t)(int,int);注意fuc_ptr_t前面有*
  • 定义:fuc_ptr_t my_fuc = NULL;注意fuc_ptr_t后面没有*
  • 赋值:my_fuc = add

1.2 第二层理解

还可以这样来定义

typedef int(fuc_t)(int,int);
int add(int a, int b)
{
	return a+b;
}
int main(void)
{
	fuc_t* my_fuc = NULL;
	my_fuc = add;
	my_fuc(10,20);
}

上面定义了一个函数类型,不是函数指针类型。

  • 类型定义:tyepdef int(fuc_t)(int,int);注意fuc_t前面没有*
  • 定义:fuc_t* my_fuc,注意fuc_t后面有*
  • 赋值:my_fuc = add

1.3 第三层理解

如果有个数组是函数指针数组,那么前两种理解中的函数类型或者函数指针类型应该如何定义变量并指向函数指针数组?

1.3.1 第一种

typedef int(*fuc_ptr_t)(void);
int fuc1(void)
{
	...
}
...
int fuc10(void)
{
	...
}

fuc_ptr_t fuc_array[10] = 
{
	fuc1,...,fuc10
};

int main(void)
{
	fuc_ptr_t* p = fuc_array;
	p[0]();
	retrun 0;
}

这样定义的好处就是fuc_array怎么操作元素,p就怎么操作元素。
例如,如果想使用fuc1函数,可以fuc1();,可以fuc_array[0](),还可以p[0]()

1.3.2 第二种

typedef int(fuc_t)(void);
int fuc1(void)
{
	...
}
...
int fuc10(void)
{
	...
}

fuc_t* fuc_array[10] = 
{
	fuc1,...,fuc10
};

int main(void)
{
	fuc_t** pp = fuc_array;
	(*pp)();
	retrun 0;
}

这种情况下,fuc_array等价于*pp
例如:想要调用fuc1,可以fuc1(),可以fuc_array[0](),还可以(*pp)[0]()

2 二级指针遍历指针数组

2.1 源码理解

uboot启动的第二阶段中,lib_arm/board.cstart_armboot函数里有这样一段代码,
整理如下:


/*board.c*/
typedef int(init_fnc_t)(void);

init_fnc_t *init_sequence[] = {
	cpu_init,		/* basic cpu dependent setup */
	...
	NULL,/*遍历终止条件*/
};

void start_armboot (void)
{
	init_fnc_t **init_fnc_ptr;/*二级指针*/
	...
	for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
		if ((*init_fnc_ptr)() != 0) {
			hang ();
		}
	}
	...
}

函数类型定义:typedef int(init_fnc_t)(void),定义了一个函数类型,不是函数指针类型。
函数注册:init_fnc_t *init_sequence[] = {...},元素类型init_fnc_t *
迭代指针定义:init_fnc_t **init_fnc_ptr,指针数组的迭代指针是一个二级指针。
通过迭代指针遍历:for(init_fnc_ptr = init_sequence; *init_fnc_ptr != NULL; ++init_fnc_ptr)
迭代器解释:(*init_fnc_ptr)()
一些理解:

  • init_fnc_ptr指向的是init_sequence数组的首元素地址,所以++init_fnc_ptr指向了下一个元素的地址。*init_fnc_ptr对其进行解引用。

2.2 另一种形式

定义了函数类型还是函数指针类型,决定了后面的遍历方式,如果定义的是函数指针应该怎么遍历呢?

typedef int(*init_fnc_ptr_t)(void);/*注意这里定义的是函数指针类型*/
/*注意:init_fnc_ptr_t后面没有* 
*/
init_fnc_ptr_t init_sequence[] = {
	cpu_init,		/* basic cpu dependent setup */
	...
	NULL,/*遍历终止条件*/
};
void start_armboot (void)
{
	init_fnc_ptr_t *init_fnc_ptr;/*一级指针*/
	...
	for (init_fnc_ptr = init_sequence; (*init_fnc_ptr) != NULL; ++init_fnc_ptr) {
		if ((*init_fnc_ptr)() != 0) {
			hang ();
		}
	}
	...
}

函数指针类型定义:typedef int(*init_fnc_ptr_t)(void)
函数指针数组定义:init_fnc_ptr_t init_sequence[]
迭代器定义:init_fnc_ptr_t *init_fnc_ptr
遍历:for (init_fnc_ptr = init_sequence; (*init_fnc_ptr) != NULL; ++init_fnc_ptr)
解释:(*init_fnc_ptr)()

3 uboot中全局变量的处理及内存分配方法

uboot启动第二阶段,所有的全局变量组织在了一起,并不是散落在各个文件中。
代码整理如下:

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

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;

DECLARE_GLOBAL_DATA_PTR;

void start_armboot (void)
{
	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;
	memset ((void*)gd, 0, sizeof (gd_t));/*做一个负责任的人,将内存清0后再使用*/
	gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));/*c语言知识点:结构体中的指针变量要手动分配内存*/
	memset (gd->bd, 0, sizeof (bd_t));/*做一个负责任的人,将内存清0后再使用*/
}
  • 以结构体global_data的形式组织uboot中的全局变量。
  • start_armboot中分配内存,uboot中不能使用malloc分配内存,因为没有OS对内存进行管理,直接使用malloc会将内存打乱,也是不安全的,后面的程序可能将gd中的内容污染。
  • ulong gd_base = xxxx,xxxxuboot提前规划好的内存分配的地址。
  • gd = (gd_t*)gd_basegd全局变量起始地址,将内存进行实例化。
  • memset ((void*)gd, 0, sizeof (gd_t)),清空内存内容,避免脏内存。
  • gd->bd = (bd_t*)((char*)gd - sizeof(bd_t)),这个很重要,结构体元素如果是指针,一定要分配内存。
  • memset (gd->bd, 0, sizeof (bd_t)),避免脏内存。

不理解的地方:
程序只分析到这里其实不能理解为什么不直接定义一个全局变量,例如gd_t gd,这样gd就直接分配了内存了,虽然这样gd地址是变化的,但是可以通过&gd得到。
uboot中是定义了一个全局的指针变量gd_t *gd,然后手动分配了内存空间,且空间的地址是提前规划好的。
这样做的好处是什么?必须这样处理吗?

4 uboot中的board_init函数

int board_init(void)
{
	DECLARE_GLOBAL_DATA_PTR;/*根据宏定义展开,这里是声明了gd*/
#ifdef CONFIG_DRIVER_SMC911X
	smc9115_pre_init();
#endif
#ifdef CONFIG_DRIVER_DM9000
	dm9000_pre_init();/*网卡配置,这个函数是网卡的GPIO和端口的配置,而不是驱动。网卡驱动是现成的,移植的时候不需要更改*/
#endif
	/*需要移植的其他网卡的配置可以在这里实现*/
	gd->bd->bi_arch_number = MACH_TYPE;
	gd->bd->bi_boot_params = (PHYS_SDRAM_1+0x100);/*记录ddr的在总线上的地址,用户自动配置该信息,启动时读取使用*/
	/*board的信息,也可以通过读取硬件的信息,来自动识别一些信息,这里使用的不是这种方式,而是通过用户自己配置(登记)
	 *登记的信息,都在include/configs/*.h中定义,通过宏定义进行配置值得更改
	 *bi_boot_params变量是kernel启动时,uboot传参的参数地址。内核启动后,从这个地址处获得传参的值。
	 */

	return 0;
}
  • DECLARE_GLOBAL_DATA_PTR,宏定义的展开值为:#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8"),作用:对gd进行声明,供后面使用。这样就不用通过包含头文件来访问gd了,这样是一种访问全局变量的方法,平时在项目开发中用的不多,但是有奇效。
  • 网卡配置,这个函数是网卡的GPIO端口的配置,而不是驱动。网卡驱动是现成的,移植的时候不需要更改
#ifdef CONFIG_DRIVER_SMC911X
	smc9115_pre_init();
#endif
#ifdef CONFIG_DRIVER_DM9000
	dm9000_pre_init();
#endif

通过宏定义开关选择不同的网卡进行配置

  • gd->bd->bi_arch_number = MACH_TYPE;,设置开发板的机器码。机器码是开发板的唯一办法。理论上是linux官方发布的,开发板厂家向linux官方申请。作用:针对开发板移植好的ubootkernal中都要定义这个机器码,在启动的时候,通过这个机器码进行适配,保证镜像是针对该开发板移植的。MACH_TYPE这个机器码是2456,没有特殊的含义,原理上是ubootkernal中的机器码相同就行。
  • gd->bd->bi_boot_params = (PHYS_SDRAM_1+0x100);kernal启动时,ubootkernal传递的参数之一,bi_boot_params是个地址,kernal程序从该地址处查询需要的变量的值。

5 uboot中interrupt_init函数

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);
}
  • interrupt_init是用来设置定时器的,用来产生tick时钟的。并不是设置cpu的中断的。不要被其名字误导。
  • uboot提供delay函数的。
  • timer设置成了定时10ms
  • uboot启动的最后有一个bootdelay阶段,就是用这个timer实现的。

6 uboot中env_init函数

uboot中的env_init函数
uboot中,env_init函数有很多个,原因是环境变量相关的初始化和uboot的启动介质有关。env相关的信息,是存放在启动介质中的。所以针对不同的启动介质分别实现了各自的env_init函数。

7 uboot中的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);
}
  • tmp[64]:定义一个临时字符数组,用来存放读取的环境变量中baudrate的值。
  • getenv_r:该函数用来读取环境变量,分析在下面。第一个参数:要读取的环境变量,第二个参数:读取到的环境变量的值,第三个参数:第二个参数的长度。返回值:>0表示读取成功,<0表示读取失败。
  • (int) simple_strtoul (tmp, NULL, 10):将字符串转换成数字。tmp:要转化的字符串,NULL:不读会处理剩下的字符串,10:十进制转换。
  • CONFIG_BAUDRATEuboot配置是定义的波特率的大小。
  • (i > 0)? (int) simple_strtoul (tmp, NULL, 10): CONFIG_BAUDRATE;:如果i>0(读取环境变量成功)则返回环境变量中的波特率,否则返回uboot配置的波特率。
  • 读取的环境变量的值在哪里?这里指的是在DDR中的那份环境变量。刚启动后,DDR中的环境变量与Flash(启动介质)中的环境变量是相同的。用户通过uboot的控制台更改了环境变量后,DDR中的环境变量就与Flash中的环境变量不一样了。

8 uboot中的serial_init函数

这里该函数什么都没有做,因为在start.Suboot启动第一阶段打印OK时就初始化了串口驱动,所以这里就不用初始化了。

下一节见:uboot中的知识点记录-2

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值