linux内核学习初笔记(6)uboot第…

uboot第一阶段分析完毕,根据第一阶段最后的分析,程序跳转到了ram中继续执行start_armboot这个函数。

下面来看看这个函数主要完成了什么功能。

首先要说明一个结构体,之前提到过,叫全局数据结构体,用于保存一些全局数据,在uboot第一阶段设置栈指针时,其实就已经为这个结构体在内存中留出了空间,它的地址就是在堆的地址下方,栈的上方。

全局数据结构体定义如下所示:

typedef struct global_data {
 bd_t  *bd;
 unsigned long flags;
 unsigned long baudrate;
 unsigned long have_console; 
 unsigned long reloc_off; 
 unsigned long env_addr; 
 unsigned long env_valid; 
 unsigned long fb_base; 
#ifdef CONFIG_VFD
 unsigned char vfd_type; 
#endif
#if 0
 unsigned long cpu_clk; 
 unsigned long bus_clk;
 unsigned long ram_size; 
 unsigned long reset_status; 
#endif
 void  **jt;  
} gd_t;

其中的bd_t也是一个结构体,后面可以发现,uboot为bd_t也在内存分配了空间,就在gd_t之下。

bd_t的定义如下:

typedef struct bd_info {
    int   bi_baudrate; 
    unsigned long bi_ip_addr; 
    unsigned char bi_enetaddr[6];
    struct environment_s        *bi_env;
    ulong         bi_arch_number; 
    ulong         bi_boot_params; 
    struct    
    {
 ulong start;
 ulong size;
    }    bi_dram[CONFIG_NR_DRAM_BANKS];
} bd_t;

在start_armboot函数中的第一条就是:

DECLARE_GLOBAL_DATA_PTR

这个宏展开就是:

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

DECLARE_GLOBAL_DATA_PTR 定义一个gd_t 全局数据结构的指针,这个指针存放在指定的寄
存器r8 中。这个声明也避免编译器把r8 分配给其它的变量。任何想要访问全局数据区的代码,
只要代码开头加入“DECLARE_GLOBAL_DATA_PTR”一行代码,然后就可以使用gd 指针来访问全局
数据区了。

在下面的程序:


 gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));
 
 __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));

由上面程序可见,先在内存中为gd_t分配了空间,然后让前面定义的gd指针指向它的首地址,接着也分配了bd_t结构体的空间,这个空间在gd_t之下,然后将gd_t中的bd指针赋值为它的首地址。

继续贴程序:

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

这段程序从表面上来看就是在执行一个函数指针数组中的函数。这个函数指针数组是:

init_fnc_t *init_sequence[] = {
 cpu_init,  
 board_init,  
 interrupt_init,  
 env_init,  
 init_baudrate,  
 serial_init,  
 console_init_f,  
 display_banner,  
 dram_init,  
 display_dram_config,
#if defined(CONFIG_VCMA9) || defined (CONFIG_CMC_PU2)
 checkboard,
#endif
 NULL,
};

这里还需要补充一点儿C语言的知识。我们都知道数组名本身就是一个指针,而且是指针常量,指针值本身不能随意更改,这个指针常量指向的就是数组中的第一个变量。如果数组中的内容本身就是指针,如本例所示,那数组名就是双重指针,这很好理解,首先它是一个指针,并且它指向数组中第一个元素也是一个指针,也就是这个指针指向一个指针,那它自然就是双重指针了。所以根据分析,我们这里的数组名init_sequence就是一个双重指针。

我们在start_armboot函数之前已经定义了一个双重指针:init_fnc_t **init_fnc_ptr;那么这个init_fnc_t 是什么数据类型呢,在找找,发现以下代码:

typedef int (init_fnc_t) (void);

很显然,这里我们用typedef定义了一种新的数据类型,这种数据类型应该是一种函数,而且这种函数的参数为void,返回值为int。

init_fnc_t **init_fnc_ptr就是定义了一个init_fnc_t这样类型函数的双重指针。而我们init_sequence数组中的每个函数其实都是参数为void,返回值为int的函数,所以数组名init_sequence就也是参数为void,返回值为int类型函数的双重指针。这样,我们很好理解,在for语句中:init_fnc_ptr = init_sequence。*init_fnc_ptr是判断函数指针是否存在。++init_fnc_ptr是指向数组的下一个元素,即指向下一个函数指针。for循环中的if语句:if ((*init_fnc_ptr)() != 0)就是用双重函数指针调用数组中的这些函数。*init_fnc_ptr就是提取init_fnc_ptr指向的内容,自然就是得到数组中的某个元素,即某个函数指针,然后使用函数指针直接调用这个函数。如果函数返回为非0,则调用hang(),停止执行。

下面来看看函数指针数组中的每个函数究竟完成什么功能。

1、cpu_init();定义于cpu/arm920t/cpu.c 

分配IRQ,FIQ 栈底地址,由于没有定义CONFIG_USE_IRQ,所以相当于空实现。

2、board_init:极级初始化,定义于board/smdk2410/smdk2410.c

其中有这么两句:

S3C24X0_CLOCK_POWER * const clk_power = S3C24X0_GetBase_CLOCK_POWER();
 S3C24X0_GPIO * const gpio = S3C24X0_GetBase_GPIO();

这两句是得到2410时钟控制寄存器和GPIO控制寄存器的基地址并将其转为S3C24X0_CLOCK_POWER和S3C24X0_GPIO类型的指针,而这两种类型是两种已经定义的结构体类型,这个指针认为在这个基地址起始的地方有这样一个结构体,又可以看到这两种结构体中的数据的定义分配方式与2410的时钟控制寄存器和GPIO控制寄存器的分布情况是一样的。所以通过这两个指针就可以访问这些控制寄存器的值,避免经常与地址打交道(uboot的代码真是好神气)以后还会经常有这样类似的代码。

设置PLL 时钟,GPIO,使能I/D cache.
设置bd 信息:gd->bd->bi_arch_number = MACH_TYPE_SMDK2410;//板子的ID,没啥意
义。
gd->bd->bi_boot_params = 0x30000100;//内核启动参数存放地址

LOCKTIME用于设置PLL设置好后的时钟锁定之间。

3、interrupt_init

初始化2410 的PWM timer 4,使其能自动装载计数值,恒定的产生时间中断信号,但是中断被屏蔽了用不上。PWM4技术的频率是100hz,即10ms周期。这个函数里面有几个全局变量,后面udelay函数会用到,后面再分析。

4、env_init

这个函数用于初始化环境变量相关参数,因为环境变量可以保存在nand或者nor中,所以这个函数有不同的实现方式。

先说说在nor中的实现:

定义于common/env_flash.c功能:指定环境区的地址。default_environment 是默认的环境参数设置。

 

 if (crc32(0, env_ptr->data, ENV_SIZE) == env_ptr->crc) {
  gd->env_addr  = (ulong)&(env_ptr->data);
  gd->env_valid = 1;
  return(0);
 }
gd->env_addr = (ulong)&default_environment[0];
gd->env_valid = 0;

这里要分清楚两种情况,一种是我们第一次从norflash启动uboot或者不是第一次启动但是之前并没有将环境变量保存在norflash中,另一种情况当然就是我们已经将环境变量保存在了norflash中了。我们这里需要判断这两种情况,env_ptr指针:env_t *env_ptr = (env_t *)CFG_ENV_ADDR;指向的就是norflash中环境结构体的首地址。另外有必要看一下环境变量结构体的定义:

typedef struct environment_s {
 unsigned long crc;  
#ifdef CFG_REDUNDAND_ENVIRONMENT
 unsigned char flags;  
#endif
 unsigned char data[ENV_SIZE];
} env_t;

所以我们判断时就是看看norflash的环境变量结构体的数据做CRC校验能不能和结构体已经保存的CRC校验码一致,若是一致说明norflash中已经保存了有效的环境变量,则将gd的env_valid置1,说明环境变量有效。并且在gd中记录下norflash中环境变量结构体中环境变量数组的地址。否则,若norflash中没有环境变量,则使用默认的环境变量。

再说说在nand中的实现:

int env_init(void)
{
 DECLARE_GLOBAL_DATA_PTR;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值