嵌入式Linux——分析u-boot运行过程(2):u-boot第二阶段代码

2019-07-12 18:03发布

简介:

        本文主要介绍在u-boot-1.1.6中代码的运行过程,以此来了解在u-boot中如何实现引导并启动内核。这里我们就要介绍u-boot第二阶段的代码了。而第二阶段的代码主要就是填充gd_t结构体,并做一些其他硬件的设置。而这里对gd_t结构体的填充以及对硬件的设置则是在为下面的第三阶段代码做铺垫。

声明:

        本文主要是看了韦东山老师的视频后所写,希望对你有所帮助。

u-boot版本 : u-boot-1.1.6

开发板 : JZ2440

Linux内核 : Linux-2.6.22.6

 

设置硬件以及填充gd_t结构体 :

        我们在这里做的一些硬件的设置,其实也包括对一些硬件的初始化,以及对一些硬件做进一步的设置来让他们为CPU提供更多的功能。而在程序中我们以一个函数指针加for循环的形式来对每个硬件进行初始化。而详细的代码为: typedef int (init_fnc_t) (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 (); } }         上面代码就是对init_sequence列表中的各个函数进行的初始化或者设置,在这里我要说明的有两点,第一是上面这种代码的使用。第二个是init_sequence列表。         我们先说代码的使用,这里用typedef定义了一种init_fnc_t的变量,只不过这种变量只能对函数使用。而我们比较常见的定义函数变量的形式是函数指针,他的形式为 : typedef int (init_fnc_t*) (void);         而当函数指针指向某个函数并执行这个函数时,用下面的表达式 : init_fnc_t ();         而定义init_fnc_t变量的形式其实与我们上面函数指针的形式相似。但由于init_fnc_t变量要实现在列表中函数地址的变化,即当我们执行完上一个操作函数后,通过init_fnc_t型指针的加一来实现指向下一个操作函数的功能,这里就不能简单的使用一个单一的指针了。因为在上面函数指针的例子中我们不能实现指针的移动(即在不同操作函数之间跳转)。而要实现指针的移动我们就要使用双重指针,即指向指针的指针。也就是我们在上文中所使用的 : init_fnc_t **init_fnc_ptr;         而当我们像函数指针一样实现函数调用时,我们要做的其实就是为这个双重指针去一重指针。即文中的:  (*init_fnc_ptr)() ;         我想讲到这里大家对这种方式的代码使用了解了吧。而在文中又由于init_sequence被定义为:init_fnc_t *init_sequence[]  是指向数组的指针,其实就可以理解为指向指针的指针。所以我们可以用init_fnc_ptr直接指向init_sequence,而现在我们看init_sequence其实更容易理解。因为init_sequence可以理解为函数指针的数组,而*init_fnc_ptr是数组中的一项。而++init_fnc_ptr表示的是数组地址的增加。好了讲了这么多,确实也很绕,希望大家可以理解。         下面我们看一下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, };         前面我们已经说了这是个函数指针数组,所以可以知道数组中各个函数的基本形式为:没有输入参数,而输出整型参数,并且0表示下面还有函数,而非0表示下面没有函数。接下来我们介绍init_sequence列表中一些重要的设置函数。

cpu_init : 内核中各存储空间大小的设置

int cpu_init (void) { /* * setup up stacks if necessary */ #ifdef CONFIG_USE_IRQ IRQ_STACK_START = _armboot_start - CFG_MALLOC_LEN - CFG_GBL_DATA_SIZE - 4; FIQ_STACK_START = IRQ_STACK_START - CONFIG_STACKSIZE_IRQ; FREE_RAM_END = FIQ_STACK_START - CONFIG_STACKSIZE_FIQ - CONFIG_STACKSIZE; FREE_RAM_SIZE = FREE_RAM_END - PHYS_SDRAM_1; #else FREE_RAM_END = _armboot_start - CFG_MALLOC_LEN - CFG_GBL_DATA_SIZE - 4 - CONFIG_STACKSIZE; FREE_RAM_SIZE = FREE_RAM_END - PHYS_SDRAM_1; #endif return 0; }         我们看代码会发现其实这里主要是定义了一些栈空间大小以及RAM的结束地址和大小。也可能是我没有看懂,我觉得这里的设置与CPU并没有直接的关系。不知道他为什么会将这个函数命名为cpu_init ??

board_init : 单板相关的设置

int board_init (void) { S3C24X0_CLOCK_POWER * const clk_power = S3C24X0_GetBase_CLOCK_POWER(); S3C24X0_GPIO * const gpio = S3C24X0_GetBase_GPIO(); /* set up the I/O ports */ gpio->GPACON = 0x007FFFFF; gpio->GPBCON = 0x00044555; gpio->GPBUP = 0x000007FF; gpio->GPCCON = 0xAAAAAAAA; gpio->GPCUP = 0x0000FFFF; gpio->GPDCON = 0xAAAAAAAA; gpio->GPDUP = 0x0000FFFF; gpio->GPECON = 0xAAAAAAAA; gpio->GPEUP = 0x0000FFFF; gpio->GPFCON = 0x000055AA; gpio->GPFUP = 0x000000FF; gpio->GPGCON = 0xFF95FFBA; gpio->GPGUP = 0x0000FFFF; gpio->GPHCON = 0x002AFAAA; gpio->GPHUP = 0x000007FF; /* support both of S3C2410 and S3C2440, by www.100ask.net */ if (isS3C2410) { /* arch number of SMDK2410-Board */ gd->bd->bi_arch_number = MACH_TYPE_SMDK2410; } else { /* arch number of SMDK2440-Board */ gd->bd->bi_arch_number = MACH_TYPE_S3C2440; } /* adress of boot parameters */ gd->bd->bi_boot_params = 0x30000100; return 0; }         从上面代码知道这里主要是对GPIO端口的设置,同时分别对2440和2410单板的机器ID进行设置。最后是对TAG参数的地址进行的设置。而关于单板机器ID和TAG参数的设置我在文章:嵌入式Linux——写jz2440BootLoader的第二阶段代码 中已经做了详细的说明,这里不再细说,只是在这里强调一下这两个参数在后面很重要。而我们从上面的代码就可以看出这里就有了对gd_t结构体的填充了。

interrupt_init : 中断相关的设置

int interrupt_init (void) { S3C24X0_TIMERS * const timers = S3C24X0_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 15625 @ 50 MHz */ timer_load_val = get_PCLK()/(2 * 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 & ~0x0700000) | 0x600000; /* auto load, start Timer 4 */ timers->TCON = (timers->TCON & ~0x0700000) | 0x500000; timestamp = 0; return (0); }         这段代码更应该说是对定时器的设置(定时器中断只是各个中断中的一种),而不应该说是对中断的设置。从上面的代码我们还可以看出这是设置了一个10ms自动重载的定时器4 。而具体的设置就是向定时器中断控制寄存器中写入预设值。其中函数:S3C24X0_GetBase_TIMERS() 就是获得定时器中断寄存器首地址的函数。而对于预设值的确定就要大家看2440 的芯片手册了。

env_init :环境变量相关的初始化

#ifdef ENV_IS_EMBEDDED extern uchar environment[]; env_t *env_ptr = (env_t *)(&environment[0]); #else /* ! ENV_IS_EMBEDDED */ env_t *env_ptr = 0; #endif /* ENV_IS_EMBEDDED */ #define CONFIG_BOOTARGS "noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0" static uchar default_environment[] = { #if defined(CONFIG_BOOTARGS) "bootargs=" CONFIG_BOOTARGS "