UBOOT第二阶段源码分析1

来源:互联网 发布:网络的利与弊400字 编辑:程序博客网 时间:2024/06/10 19:21

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

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_SAVEENV

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环境变量到这个挤出来的空间。大功告成。

 

好了。环境变量已经重定位完成。接下来看看下一个任务。

0 0
原创粉丝点击