uboot之start_armboot分析1

注:本文是学习朱老师课程整理的笔记,基于uboot-1.3.4和s5pc11x分析。

概括来讲uboot第一阶段主要就是初始化了SoC内部的一些部件(譬如看门狗、时钟),然后初始化DDR并且完成重定位。然后调用start_armboot函数进入uboot启动第二阶段。

由宏观分析来讲,uboot的第二阶段就是要初始化剩下的还没被初始化的硬件。主要是SoC外部硬件(譬如iNand、网卡芯片····)、uboot本身的一些东西(uboot的命令、环境变量等····)。然后最终初始化完必要的东西后进入uboot的命令行准备接受命令。

start_armboot这个函数在uboot/lib_arm/board.c中,这个函数整个构成了uboot启动的第二阶段。

  • 变量定义
    init_fnc_t **init_fnc_ptr;
    char *s;
    int mmc_exist = 0;
    ulong gd_base;

init_fnc_t 在onenand_boot.c中定义:typedef int (init_fnc_t)(void);这是一个函数类型。
init_fnc_ptr是一个二重函数指针,二重指针的作用有2个(其中一个是用来指向一重指针),一个是用来指向指针数组。因此这里的init_fuc_ptr可以用来指向一个函数指针数组。

  • 全局变量gd
    gd_base = CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE - sizeof(gd_t);
    gd = (gd_t*)gd_base;

在board.c的开头有这样一句:DECLARE_GLOBAL_DATA_PTR; 找到它的定义处就是关于gd的定义。

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

定义了一个全局变量名字叫gd,这个全局变量是一个指针类型,占4字节。用volatile修饰表示可变的,用register修饰表示这个变量要尽量放到寄存器中,后面的asm(“r8”)是gcc支持的一种语法,意思就是要把gd放到寄存器r8中。

因此,DECLARE_GLOBAL_DATA_PTR就是定义了一个要放在寄存器r8中的全局变量,名字叫gd,类型是一个指向gd_t类型变量的指针。

为什么要定义为register?因为这个全局变量gd(global data的简称)是uboot中很重要的一个全局变量(准确的说这个全局变量是一个结构体,里面有很多内容,这些内容加起来构成的结构体就是uboot中常用的所有的全局变量),这个gd在程序中经常被访问,因此放在register中提升效率。因此纯粹是运行效率方面考虑,和功能要求无关。并不是必须的。

gd_t定义在include/asm-arm/global_data.h中。

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;

gd_t中定义了很多全局变量,都是整个uboot使用的;
其中有一个bd_t类型的指针,指向一个bd_t类型的变量,这个bd是开发板的板级信息的结构体,里面有不少硬件相关的参数,譬如波特率、IP地址、机器码、DDR内存分布。

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;
  • 内存使用排布

DECLARE_GLOBAL_DATA_PTR只能定义了一个指针,也就是说gd里的这些全局变量并没有被分配内存,我们在使用gd之前要给他分配内存,否则gd也只是一个野指针而已。

gd和bd需要内存,内存当前没有被人管理(因为没有操作系统统一管理内存),大片的DDR内存散放着可以随意使用(只要使用内存地址直接去访问内存即可)。但是因为uboot中后续很多操作还需要大片的连着内存块,因此这里使用内存要本着够用就好,紧凑排布的原则。所以我们在uboot中需要有一个整体规划。

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

由上面的内容可知内存排布:

  1. uboot区 CFG_UBOOT_BASE-xx(长度为uboot的实际长度)
  2. 堆区 长度为CFG_MALLOC_LEN,实际为912KB
  3. 栈区 长度为CFG_STACK_SIZE,实际为512KB
  4. gd 长度为sizeof(gd_t),实际36字节
  5. bd 长度为sizeof(bd_t),实际为44字节左右
/* compiler optimization barrier needed for GCC >= 3.4 */
    __asm__ __volatile__("": : :"memory");

为了防止高版本的gcc的优化造成错误。

  • 变量空间清零
memset ((void*)gd, 0, sizeof (gd_t));
gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));
memset (gd->bd, 0, sizeof (bd_t));
  • init_sequence
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
        if ((*init_fnc_ptr)() != 0) {
            hang ();
}    /* for循环的条件*init_fnc_ptr相当于:*init_fnc_ptr != NULL */

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

其中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
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C)
    init_func_i2c,
#endif
    dram_init,      /* configure available RAM banks */
    display_dram_config,
    NULL,
};

这些指向的函数都是init_fnc_t类型(特征是接收参数是void类型,返回值是int)。
init_sequence在定义时就同时给了初始化,初始化的函数指针都是一些函数名。
init_fnc_ptr是一个二重函数指针,可以指向init_sequence这个函数指针数组。
用for循环肯定是想要去遍历这个函数指针数组(遍历的目的也是去依次执行这个函数指针数组中的所有函数)。

思考:如何遍历一个函数指针数组?有2种方法:第一种也是最常用的一种,用下标去遍历,用数组元素个数来截至。第二种不常用,但是也可以。就是在数组的有效元素末尾放一个标志,依次遍历到标准处即可截至(有点类似字符串的思路)。

我们这里使用了第二种思路。因为数组中存的全是函数指针,因此我们选用了NULL来作为标志。我们遍历时从开头依次进行,直到看到NULL标志截至。这种方法的优势是不用事先统计数组有多少个元素,。

init_fnc_t的这些函数的返回值定义方式一样的,都是:函数执行正确时返回0,不正确时返回-1。所以我们在遍历时去检查函数返回值,如果遍历中有一个函数返回值不等于0则hang()挂起。从分析hang函数可知:uboot启动过程中初始化板级硬件时不能出任何错误,只要有一个错误整个启动就终止,除了重启开发板没有任何办法。

init_sequence中的这些函数,都是board级别的各种硬件初始化。

  • cpu_init
    这个函数是空的,因为第一阶段已经初始化过。

  • board_init

int board_init(void)
{
    DECLARE_GLOBAL_DATA_PTR;

#ifdef CONFIG_DRIVER_DM9000
    dm9000_pre_init();
#endif

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

    return 0;
}

board_init在uboot/board/samsung/x210/x210.c中,这个看名字就知道是x210开发板相关的初始化。

DECLARE_GLOBAL_DATA_PTR在这里声明是为了后面使用gd方便。可以看出把gd的声明定义成一个宏的原因就是我们要到处去使用gd,因此就要到处声明,定义成宏比较方便。

网卡初始化。CONFIG_DRIVER_DM9000这个宏是x210_sd.h中定义的,这个宏用来配置开发板的网卡的。dm9000_pre_init函数就是对应的DM9000网卡的初始化函数。开发板移植uboot时,如果要移植网卡,主要的工作就在这里。

这个函数中主要是网卡的GPIO和端口的配置,而不是驱动。因为网卡的驱动都是现成的,移植的时候驱动是不需要改动的,关键是这里的基本初始化。因为这些基本初始化是硬件相关的。

bi_arch_number是board_info中的一个元素,含义是:开发板的机器码。所谓机器码就是uboot给这个开发板定义的一个唯一编号。机器码的主要作用就是在uboot和linux内核之间进行比对和适配。

MACH_TYPE在x210_sd.h中定义,值是2456,并没有特殊含义,只是当前开发板对应的编号。这个编号就代表了x210这个开发板的机器码,将来这个开发板上面移植的linux内核中的机器码也必须是2456,否则就启动不起来。

uboot中配置的这个机器码,会作为uboot给linux内核的传参的一部分传给linux内核,内核启动过程中会比对这个接收到的机器码,和自己本身的机器码相对比,如果相等就启动,如果不相等就不启动。

bi_boot_params是bd_info中另一个主要元素,表示uboot给linux kernel启动时的传参的内存地址。也就是说uboot给linux内核传参的时候是这么传的:uboot事先将准备好的传参放在内存的一个地址处,然后uboot就启动了内核(uboot在启动内核时真正是通过寄存器r0 r1 r2来直接传递参数的,其中有一个寄存器中就是bi_boot_params)。内核启动后从寄存器中读取bi_boot_params就知道了uboot给我传递的参数到底在内存的哪里。然后自己去内存的那个地方去找bootargs。

经过计算得知:X210中bi_boot_params的值为0x30000100,这个内存地址就被分配用来做内核传参了。所以在uboot的其他地方使用内存时要注意,千万不敢把这里给淹没了。

后面的分析见:uboot之start_armboot分析2

你可能感兴趣的:(uboot之start_armboot分析1)