start_armboot函数分析
path = …/uboot/lib_arm/board.c
一. init_fnc_t类型及相关使用介绍
- init_fnc_t 类型的定义为
typedef int (init_fnc_t) (void);
,显然,这是一个返回类型为int的函数类型。在函数的第一行定义了一个二重指针init_fnc_ptr ,这个指针用于之后指向一个函数指针数组。 - 在board.c 文件中有一个init_sequence 函数指针数组,类型为init_fnc_t ,定义如下:
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 */
print_cpuinfo, /* display cpu info (and speed) */
checkboard, /* display board info */
dram_init, /* configure available RAM banks */
display_dram_config,
NULL,
};
里面按顺序存放了各个部件初始化程序函数地址,返回类型为int,成功返回0,否则不为0。二重指针init_fnc_ptr 指向这个数组。
- 第37行有一for循环如下:
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
二重指针init_fnc_ptr 先指向这个init_sequence 数组第一个元素,然后依次调用数组中的函数,直到调用到NULL地址,循环结束。如果调用的初始化程序未初始化成功,就会调用hang 函数,将程序挂起,即进入死循环直到重启开发板。hang函数如下:
void hang (void)
{
puts ("### ERROR ### Please RESET the board ###\n");
for (;;);
}
二. 全局变量gd及其巧妙的内存分配
- 关于gd的定义其实是一个宏
DECLARE_GLOBAL_DATA_PTR
,该宏的内容为:
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
由于gd作为全局变量,在整个uboot第二阶段用到的地方特别多,所以定义为register,volatile,这样可以提升访问效率。
- 全局变量gd的类型为gd_t,该类型的定义为:
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;
该结构体中定义了板信息(结构体指针bd),标志信息,波特率,控制台是否建立(布尔),重定位偏移量,环境变量地址,环境变量是否能够访问(布尔),frame基地址(关于LCD),跳转表。其中,结构体指针bd的类型为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;
bd_t类型包含了开发板波特率,开发板ip地址,开发板网卡地址,开发板环境变量,机器码,以及开发板启动参数与内存分配地址。
- 由于gd是一个指针变量,所以必须为gd分配内存,uboot为gd分配内存是在该函数第15行到33行。首先,uboot先定义了一个unsigned long 型的gd_base变量,然后将值CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE - sizeof(gd_t) 赋给gd_base变量,CFG_UBOOT_BASE 为uboot物理起始地址的宏,等于0x33E00000,CFG_UBOOT_SIZE 是uboot的“假设”大小,等于2M,CFG_MALLOC_LEN是堆的大小,等于912K,CFG_STACK_SIZE是栈的大小,等于512K,总的分配如下图:
gd_base被赋值后相当于获得了内存中的一个地址,也就是获得了空间,然后利用memset 函数将内存清0,至此申请内存工作完成。但可以注意到的一点是,在gd变量中,还有一个指针bd,所以同样的需要按照上面的方法为bd申请内存,关于其他详细内存分配请阅读uboot第一阶段分析。f分配具体过程如下:
ulong gd_base;
gd_base = CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE - sizeof(gd_t);
gd = (gd_t*)gd_base;
/* 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));
另外,计算出的实际地址在为指针赋值时需要强制类型转换,倒数第二句中(char*)是将gd强制类型转换为char型,以便在做运算时实际减去sizeof(bd_t)的真实大小。至于中间有一句内嵌汇编,这句话是为了防止版本大于3.4的gcc优化程序导致的错误。
三. init_sequence 函数指针数组元素分析
- cpu_init 函数在…/uboot/cpu/s5pc11x/Cpu.c 文件中,是CPU相关硬件的初始化,但由于在uboot的第一阶段已经初始化过了,所以在目前的cpu_init 函数什么也没做,直接返回0,表示初始化成功。
- board_init 函数在…/uboot/board/samsung/x210/X210.c 文件中,是开发板相关硬件的初始化,函数如下:
int board_init(void)
{
DECLARE_GLOBAL_DATA_PTR;
dm9000_pre_init();
gd->bd->bi_arch_number = MACH_TYPE;
gd->bd->bi_boot_params = (PHYS_SDRAM_1+0x100);
return 0;
}
第一句先声明了之后要使用的gd全局变量,然后初始化了开发板的网卡dm9000,初始化的工作主要是引脚与端口的配置。最后,第3,4句设置了uboot很重要的两个全局变量。第一个是uboot的机器码,只有uboot的机器码与linux内核的机器码相同,内核才会正常启动,否则,linux会认为与uboot不适配,导致启动失败。另外,这个机器码理论说来应是每个开发板配一个唯一固定的机器码,而且需要从官方申请,但如果没有特定要求,只是自己学习,随便写一个即可,只要linux与uboot的一致就行,这个机器码会在启动时作为参数传递给linux内核,在这,我们赋值2456。第二个是uboot启动所需参数的地址,在这PHYS_SDRAM_1 这个宏值为0x30000000,并且定义在…uboot/include/configs/X210_sd.h 文件第503行,所以不难发现这个uboot将uboot启动所需参数的地址设为0x30000100,将来linux会从这读取入口参数。
- interrupt_init 函数在…/uboot/cpu/s5pc11x/Interrupts.c 文件中,是uboot的计时器的初始化函数,之后用于bootdelay倒数计时,函数如下:
int interrupt_init(void)
{
S5PC11X_TIMERS *const timers = S5PC11X_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 @ 66 MHz
*/
timer_load_val = get_PCLK() / (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 & ~0x00700000) | TCON_4_AUTO | TCON_4_UPDATE;
/* auto load, start Timer 4 */
timers->TCON = (timers->TCON & ~0x00700000) | TCON_4_AUTO | COUNT_4_ON;
timestamp = 0;
return (0);
}
第一句定义了定时器的寄存器变量,这儿用到了很巧妙的用法。首先S5PC11X_TIMERS 类型是一个结构体,这个结构体中的变量按照寄存器的排布而排布,就像下面这样:
这样编写的好处是,只要通过S5PC11X_GetBase_TIMERS 函数将这些寄存器基地址付给结构体的首地址(指针)即可访问所有寄存器,这样的写法在linux中很常见。定义完寄存器后,剩下的主要是配置Timer4的时钟频率以及装载值,第二句设置Timer4的第一级分频器为15 + 1 = 16。timer_load_val 是一个变量,初始值为0,所以执行为该变量赋值的操作,get_PCLK 函数获取了Timer4的时钟来源PCLK,整个式子计算出了10ms所需要的装载值赋给timer_load_val 变量。TCNTB4寄存器是用来计数的寄存器,TCON是关于定时器的设置寄存器,在函数最后几句代码,设置了Timer4的自动重装,装载值,定时器开启等配置。
-
env_init 函数在…/uboot/common/Env_movi.h 文件中,是uboot的关于环境变量的初始化函数,主要检测一下是否有可用的环境变量。由于还未做环境变量的重定位,所以环境变量还不能使用。
-
init_baudrate 函数在…/uboot/lib_arm/Board.c 文件中,这个函数完成了uboot的全局变量gd 中波特率的初始化。函数如下:
static int init_baudrate (void)
{
char tmp[64]; /* long enough for environment variables */
int i = getenv_r ("baudrate", tmp, sizeof (tmp));
gd->bd->bi_baudrate = gd->baudrate = (i > 0)
? (int) simple_strtoul (tmp, NULL, 10)
: CONFIG_BAUDRATE;
return (0);
}
getenv_r 函数可以从环境变量中找到函数第一个参数的值,然后将值保存在第二个参数中,而且是以字符的形式保存的,最后返回一个大于0的数表示查找成功。所以在第三句为全局变量gd->bd->bi_baudrate 与gd->baudrate 赋值时要先判断一下是否环境变量值查找成功,成功就将tmp字符串装换为int类型的值赋给它两,否则使用X210.h 文件中的默认配置值115200。
-
serial_init 函数本应该是初始化串口的,但由于在uboot的第一阶段已经初始化过了,所以在第二阶段,这个函数其实什么也没做。
-
console_init_f 函数在 …/uboot/common/Console.c 文件中,这个函数作为控制台初始化的第一阶段,实际上只做了一件事,就是给全局变量gd->have_console 赋值为1。
-
display_banner 函数在 …/uboot/lib_arm/Board.c 文件中,这个函数完成了两件工作。首先,通过printf函数打印了uboot的版本号,编译时间以及对象。version_string字符串就是printf要打印的变量,定义如下:
const char version_string[] =
U_BOOT_VERSION" (" __DATE__ " - " __TIME__ ")"CONFIG_IDENT_STRING;
U_BOOT_VERSION 宏是Makefile中创建的版本号,具体参考博客。CONFIG_IDENT_STRING 宏是X210_sd.h 文件中定义的字符串"for x210"。然后第二件事就是通过open_backlight 函数将GPF3_5引脚输出高电平开启了LCD背光灯。
-
print_cpuinfo 函数在 …/uboot/cpu/s5pc11x/s5pc110/Speed.c 文件中,这个函数与display_banner 函数一样也是打印信息的,主要显式了各个时钟总线上的频率,我们配置的X210开发板的内容如下:
CPU: S5PV210@1000MHz(OK)
APLL = 1000MHz, HclkMsys = 200MHz, PclkMsys = 100MHz
MPLL = 667MHz, EPLL = 96MHz
HclkDsys = 166MHz, PclkDsys = 83MHz
HclkPsys = 133MHz, PclkPsys = 66MHz
SCLKA2M = 200MHz
Serial = CLKUART -
checkboard 函数在 …/uboot/board/samsung/x210/X210.c 文件中,这个函数打印了开发板的名称,例如我们打印的是Board: X210。
-
dram_init 函数在 …/uboot/board/samsung/x210/X210.c 文件中,该函数初始化了gd->bd->bi_dram 数组,该数组是一个结构体数组,有两个元素,一个是内存起始地址,另一个是内存大小,数组的长度是内存的片数,例如我们的开发板有两片内存,所以数组长度为2,且两片内存分别是0x30000000-0x3FFFFFFF,0x40000000-0x4FFFFFFF,所以gd->bd->bi_dram[0].start应该等于0x30000000,gd->bd->bi_dram[0].size应该等于256M,gd->bd->bi_dram[1].start应该等于0x40000000,gd->bd->bi_dram[1].size应该等于256M。这些值其实是以宏的方式赋值给数组的,关于宏的定义在X210_sd.h 中,修改这个文件第500行处即可以修改这些全局变量。关于内存的分配可以参考博客。
-
display_dram_config 函数在 …/uboot/lib_arm/Board.c 文件中,该函数通过上面的数组计算得到总的DRAM大小,并输出了DRAM的大小,以上述例子,输出信息如下:DRAM: 512MB。
uboot启动第二阶段–start_armboot函数分析(2)