您的位置:首页 > 其它

UBOOT第二阶段源码分析1

2014-05-05 00:02 211 查看
好了我们现在来看看第二阶段要做什么事情,嗯,咱们换一个思路,先来说说要做什么事情再来分析这些事情是怎么做的。

1 cpu_init,cpu初始化,主要是设置中断的栈

2 board_init这个是板级初始化,主要是初始化系统时钟等等

3 interrupt_init初始化定时器

4 env_init主要是检查环境变量

5 init_baudrate

Serial_init

Console_init_f

初始化串口控制台



6 dram_init 检查系统内存映射



7 flash_init初始化NOR flash

8 env_relocate 将环境参数读入指定位置

9 cs8900_get_enetaddr 初始化网络设备

9 调用main_loop

10 启动内核



要完成这十件事情,我们需要分析代码,代码量有点大,自然不能一一详解,只能选取部分重要的来分析,而其中的重中之重就是启动内核的几个命令和参数传递,其他的分析方法都跟第一部分的分析大同小异,我们简单讲解一下然后重点分析内核的启动。



但是,在分析UBOOT完成这些动作之前,我们要先来看一看两个重要的数据结构,第一个闪亮登场bd_t,在include/asm-arm/u-boot.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 */

{

ulongstart;

ulongsize;

} bi_dram[CONFIG_NR_DRAM_BANKS];

#ifdef CONFIG_HAS_ETH1

/* second onboard ethernet port */

unsigned char bi_enet1addr[6];

#endif

} bd_t;

这个数据结构包含的信息有:1串口波特率 2 IP地址 3MAC地址 4环境变量 5板子的ID 6启动参数 7 RAM的配置 8没了



第二个数据gd_t结构亮瞎狗眼登场,在/include/global_data.h中定义如下:

typedef struct global_data{

bd_t *bd;

unsigned long flags;

unsignedlong baudrate;

unsignedlong have_console; /* serial_init() was called */

unsignedlong reloc_off; /* Relocation Offset */

unsignedlong env_addr; /* Address ofEnvironment struct */

unsignedlong env_valid; /* Checksum of Environment valid? */

unsignedlong fb_base; /* base address of frame buffer */

#ifdef CONFIG_VFD

unsignedchar vfd_type; /* display type */

#endif

#if 0

unsignedlong cpu_clk; /* CPU clock in Hz! */

unsignedlong bus_clk;

unsignedlong ram_size; /* RAM size */

unsignedlong reset_status; /* reset status register at boot */

#endif

void **jt; /*jump table */



} gd_t;

包含的信息有:1设备指示标志 2波特率3串口初始化标志 4重定位偏移 5环境变量的地址 6 CRC的有效标志 7帧缓冲的起始地址 8 显示器类型 8CPU频率 9 总线频率 10 RAM大小 11 复位状态 12 跳转表



这两个数据结构丰富地涵盖了面向对象的思想,它们描述了板子的信息和状态,并且有最后一个跳转表可以完成一些动作。面对象!=类!=继承!=多态!=…..总之面向对象是思想不是技术。



跳转表是什么?它是一个数组,数组里面有好多指向函数的指针,也就是说通过下标索引就可以调用不同的函数。这就是跳转表。



好了基础的东西具备了咱们开始分析源码。



第二阶段的入口是start_armboot。按照国际惯例。Look一下

void start_armboot (void)

{

init_fnc_t**init_fnc_ptr;

char*s;

#ifndef CFG_NO_FLASH

ulongsize;

#endif



#if defined(CONFIG_VFD) ||defined(CONFIG_LCD)

unsignedlong addr;

#endif



#if defined(CONFIG_BOOT_MOVINAND)

uint*magic = (uint *) (PHYS_SDRAM_1);

#endif



/*Pointer is writable since we allocated a register for it */

#ifdef CONFIG_MEMORY_UPPER_CODE /* by scsuh*/

ulonggd_base;



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

#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



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



monitor_flash_len= _bss_start - _armboot_start;



for(init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {

if((*init_fnc_ptr)() != 0) {

hang();

}

}



看这个init_fnc_t**init_fnc_ptr;。哇塞,一上来就遇到这种奇葩指针,不行了,像我这70分C语言的人被打败了。走了拜拜,分析完毕,谢谢大家围观。————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

哈哈哈,当然剧情不能这么发展。

我们来看看init_fnc_t这个数据类型,找到如下定义typedefint (init_fnc_t) (void);;这个数据类型…….我也不知道怎么叫,总之就像是int一样的一个数据类型,然后int*a就像是init_fnc_t*a就是定义出来一个指针,不过前者指向整形的a而后者指向返回值int 参数为void的函数。而int fnc **就是一个指向函数的指针的指针,呸呸呸!尼玛比英语还拗口,就这么看(int_fnc_t*)这个定义出来一个函数指针。init_fnc_t**init_fnc_ptr;这句的意思就是(*init_fnc_t)指向一个返回值为整形,参数为void的函数。



下一步就是将我们的gd数据结构取出来,我们在第一阶段分析的时候不是说过了吗?在RAM起始部分留出来一段空间,其中就有一段是给gd用的。现在我们来看代码吧:



/*Pointer is writable since we allocated a register for it */

#ifdef CONFIG_MEMORY_UPPER_CODE /* by scsuh*/

ulonggd_base;



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

#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

根据配置来得到gd_base,接着进行强制类型转化,就可以得到一个gd结构体。得到了gd结构体之后,我们还要把它清干净备用。所以下面的一个动作是

memset((void*)gd, 0, sizeof (gd_t));

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

memset(gd->bd, 0, sizeof (bd_t));

当然gd要清,bd 也要清啦,一样的道理。



接着看:

monitor_flash_len = _bss_start - _armboot_start;

这一句明显是计算出来了bss段的长度,因为后面还要清bss段。所以必须要计算出来它的长度。

接下来的一句很重要,一起来look一下

for(init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {

if((*init_fnc_ptr)() != 0) {

hang();

}

}

首先我们看init_fnc_prt,它是一个数据类型,那么,很显然它也可定义一个数组,当然也可以定义一个init_fnc_prt指针类型的数组,也就是说数组里面有一大堆指针,这些指针指向了一个函数,我们可以通过这些指针去调用函数。init_sequence定义如下:

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

#if defined(CONFIG_DISPLAY_CPUINFO)

print_cpuinfo, /* display cpu info (and speed) */

#endif

#if defined(CONFIG_DISPLAY_BOARDINFO)

checkboard, /* display board info */

#endif

dram_init, /* configure available RAM banks */

display_dram_config,



NULL,

};

里面包含了必要的init函数,通过for (init_fnc_ptr =init_sequence; *init_fnc_ptr; ++init_fnc_ptr)来进行历遍调用,当*init_fnc_ptr;为NULL(0)的时候就会退出,这时候刚好调用完所有函数。至于if((*init_fnc_ptr)() != 0) {

hang();

}

这一句只是判断函数能不能正确执行,因为函数正确执行后返回值会是0.如果出错,就不往下执行了。hang如下:

void hang (void)

{

puts("### ERROR ### Please RESET the board ###\n");

for(;;);

}

这些函数主要都是进行必要的初始化,自己打开来看看就知道。但是我们要重点关注其中一个:env_init, /* initializeenvironment */

这个函数要进行环境变量的初始化。

要分析这个函数事情,首先就要分析一下什么是环境变量,所谓的环境变量们,就是一个数据结构,我们通过这个数据结构的数据就可以设置UBOOT工作的某些参数,比如说波特率。

这个数据结构在]environment.h中被定义,如下:

typedef structenvironment_s {

unsignedlong crc; /*CRC32 over data bytes */

#ifdef CFG_REDUNDAND_ENVIRONMENT

unsignedchar flags; /* active/obsolete flags */

#endif

unsignedchar data[ENV_SIZE]; /* Environment data */

} env_t;



其中保存了CRC值,是一个校验码,可以不用管,我们假设校验成功。

Date[ENV_SIZE]里面保存了真正的环境变量的数据。



一起look一下这个函数。
int env_init(void)
{
int crc1_ok = 0, crc2_ok = 0;

uchar flag1 = flash_addr->flags;
uchar flag2 = flash_addr_new->flags;

ulong addr_default =(ulong)&default_environment[0];
ulong addr1 =(ulong)&(flash_addr->data);
ulong addr2 =(ulong)&(flash_addr_new->data);

#ifdefCONFIG_OMAP2420H4
int flash_probe(void);

if(flash_probe() == 0)
goto bad_flash;
#endif

crc1_ok = (crc32(0, flash_addr->data,ENV_SIZE) == flash_addr->crc);
crc2_ok = (crc32(0,flash_addr_new->data, ENV_SIZE) == flash_addr_new->crc);

if (crc1_ok && ! crc2_ok) {
gd->env_addr = addr1;
gd->env_valid = 1;
} else if (! crc1_ok && crc2_ok){
gd->env_addr = addr2;
gd->env_valid = 1;
} else if (! crc1_ok && !crc2_ok) {
gd->env_addr = addr_default;
gd->env_valid = 0;
} else if (flag1 == ACTIVE_FLAG&& flag2 == OBSOLETE_FLAG) {
gd->env_addr = addr1;
gd->env_valid = 1;
} else if (flag1 == OBSOLETE_FLAG&& flag2 == ACTIVE_FLAG) {
gd->env_addr = addr2;
gd->env_valid = 1;
} else if (flag1 == flag2) {
gd->env_addr = addr1;
gd->env_valid = 2;
} else if (flag1 == 0xFF) {
gd->env_addr = addr1;
gd->env_valid = 2;
} else if (flag2 == 0xFF) {
gd->env_addr = addr2;
gd->env_valid= 2;
}

#ifdefCONFIG_OMAP2420H4
bad_flash:
#endif
return (0);
}这个函数主要做的事情就是标记环境变量可用。
来分析一下:
ulong addr1 =(ulong)&(flash_addr->data);
ulong addr2 =(ulong)&(flash_addr_new->data);
我们在第一阶段源码分析的时候已经说过了,在RAM起始地址的一段空间有专门给环境变量留出来的地址,一共有0x20000。然后看一看这个:

unsigned char data[ENV_SIZE]; /* Environment data */

data[ENV_SIZE]长度为ENV_SIZE,这个值的大小大约为0x10000所以我们留出来的地址空间可以存放两组环境变量,为什要用到两组变量,这样是为了防止数据出错,所以保留了一个副本。要用到哪组变量,那就需要具体的判定,gd->env_valid = 2用副本,gd->env_valid = 1则用我们自己准备好的变量,gd->env_valid = 0,则用默认变量。

还要接着初始化,接下来就会初始化flash,size =flash_init ();。然后将flash的配置打印出来,display_flash_config (size);这一点自己打开板子试试吧。



来看一看,现在做到哪一步了?初始化NORFLASH,回头看一看,我们已经做完7件事情了,挺快的啊有木有。

第八件事情,将环境变量读入指定的位置

对环境变量的重定位,是一个env_relocate函数,我们可以看看这个函数究竟做了什么。

函数体如下:

void env_relocate (void)

{

DEBUGF("%s[%d] offset = 0x%lx\n", __FUNCTION__,__LINE__,

gd->reloc_off);



#ifdef CONFIG_AMIGAONEG3SE

enable_nvram();

#endif



#ifdef ENV_IS_EMBEDDED

/*

* The environment buffer is embedded with thetext segment,

* just relocate the environment pointer

*/

env_ptr= (env_t *)((ulong)env_ptr + gd->reloc_off);

DEBUGF("%s[%d] embedded ENV at %p\n", __FUNCTION__,__LINE__,env_ptr);

#else

/*

* We must allocate a buffer for theenvironment

*/

env_ptr= (env_t *)malloc (CFG_ENV_SIZE);

DEBUGF("%s[%d] malloced ENV at %p\n", __FUNCTION__,__LINE__,env_ptr);

#endif



/*

* After relocation to RAM, we can always usethe "memory" functions

*/

env_get_char= env_get_char_memory;



if (gd->env_valid == 0)

{

#if defined(CONFIG_GTH) || defined(CFG_ENV_IS_NOWHERE) /* Environment not changable */

puts("Using default environment\n\n");

#else

//puts ("*** Warning - badCRC, using default environment\n\n");

SHOW_BOOT_PROGRESS(-1);

#endif



if(sizeof(default_environment) > ENV_SIZE)

{

puts("*** Error - default environment is too large\n\n");

return;

}



memset(env_ptr, 0, sizeof(env_t));

memcpy(env_ptr->data,

default_environment,

sizeof(default_environment));

#ifdef CFG_REDUNDAND_ENVIRONMENT

env_ptr->flags= 0xFF;

#endif

env_crc_update();

gd->env_valid= 1;

}

else

{

env_relocate_spec();

}



gd->env_addr= (ulong)&(env_ptr->data);



#ifdef CONFIG_AMIGAONEG3SE

disable_nvram();

#endif

}

看起来挺复杂的,我也被吓了一跳,但是还是要耐心来分析完。嗯大家要加油,每一次把难题解决就是一次进步,代码分析能力就是这么慢慢提升起来的。

首先进来重定位环境变量之前,我们要使能非易失性存储,就是这个函数enable_nvram();

因为我们的环境变量是保存在非易失性储存里的,既然要重定位,最起码要能对这个储存器进行读写对吧?(事实上配置里注释了这一句)





好多宏定义,不知道要注释哪一行,所以这个要看我们怎么配置的,我们按照国际惯例来看一看配置文件



#ifdef ENV_IS_EMBEDDED



extern uchar environment[];

env_t *env_ptr =(env_t *)(&environment[0]);



#else /* ! ENV_IS_EMBEDDED */



env_t *env_ptr = (env_t *)CFG_ENV_ADDR;

#ifdef CMD_S***EENV

static env_t *flash_addr = (env_t*)CFG_ENV_ADDR;

#endif

第一行从字面意思就可以看出来(看人家的C写的多好啊)如果环境变量是“EMBEDDED

”——嵌入的,那么环境变量指针直接指向保存环境变量的数组。如果不是嵌入的,那么就指向CFG_ENV_ADDR;这个地址,这个地址有定义如下:

#ifdef CONFIG_AMD_LV800

#define PHYS_FLASH_SIZE 0x00100000 /* 1MB */

#define CFG_MAX_FLASH_SECT (19) /*允许分最多的扇区数目 */

#define CFG_ENV_ADDR (CFG_FLASH_BASE + 0x0F0000) /* addr of environment */

#endif

#ifdef CONFIG_AMD_LV400

#define PHYS_FLASH_SIZE 0x00080000 /* 512KB */

#define CFG_MAX_FLASH_SECT (11) /*max number of sectors on one chip */

#define CFG_ENV_ADDR (CFG_FLASH_BASE + 0x070000) /* addr of environment */

#endif

根据设备的不同被设置成FLASH偏移地址0x00100000或者0x00080000。

这样说肯定还是很难懂。什么叫做嵌入的?为什么嵌入的就直接指向数组就行了?而不是嵌入的就要指向某个地址?

是这样的:如果环境变量是“嵌入的”就是说环境变量被链接到了代码段里.text,在程序的链接阶段,连接器会对代码进行重定位,从而计算出来每个符号的地址,也就是说

Environment这个符号会得到一个地址,我们的指针指向它,然后在第一阶段进行代码重定位的时候跟着镜像一起copy到RAM里面,注意,在程序当中对符号的访问的地址是连接地址!意思就是,虽然Environment这个符号刚开始是在NOR里面的,但是我们指针env_ptr指向的地址是RAM里面的。所以重定位之后就可以直接用了。

如果环境变量不是被链接到代码段内的,那么我们在NOR内的片地址内为它保留出一个空间,然后指针指向这个空间去,重定位的时候,整个UBOOT镜像被COPY到RAM内,然后再从NOR中把环境变量COPY过去。就是这样。我们先后讨论这两种情况。

先假设环境变量被链接到了代码段内,也就是嵌入的。

那么env_ptr =(env_t *)((ulong)env_ptr + gd->reloc_off); 事实上经过测试,gd->reloc_off这个值为0,那么env_ptr仍然指向环境变量数组。

中间做一些检测,我们假设检测都成功,这时候就要清空环境变量数组以待来客。

memset (env_ptr, 0, sizeof(env_t)); 填充0;

最后,再将默认环境变量((gd->env_valid== 0表示默认环境变量,也可以不是)拷贝到我们的环境变量数组去,大功告成。操作如下:

memcpy (env_ptr->data,

default_environment,

sizeof(default_environment)); //将flash中的默认的环境变量拷贝到RAM对应的区域中去

接下来就更新一些标志和校验。

好了咱们接着讨论第二种情况。

如果环境变量没有被链接到代码段,那么RAM里的镜像自然就没有环境变量的位置啦,所以我们要给它分配空间。

挤一挤,挤出来

env_ptr = (env_t *)malloc (CFG_ENV_SIZE);

然后,接下来我们重复上面的操作,copy环境变量到这个挤出来的空间。大功告成。



好了。环境变量已经重定位完成。接下来看看下一个任务。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: