总结于朱有鹏老师的嵌入式课程,感谢朱老师
uboot/lib_arm/board.c的第444行开始到908行结束
先介绍一个全局变量gd,类型是一个指向gd_t类型变量的指针,占4字节。用volatile修饰表示可变的,用register修饰表示这个变量要尽量放到寄存器中(放在register中提升效率),后面的asm(“r8”)是gcc支持的一种语法,意思就是要把gd放到寄存器r8中。
gd_t中定义了很多uboot使用的全局变量。
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
使用gd和bd之前要给他分配内存
#ifdef CONFIG_MEMORY_UPPER_CODE /* by scsuh */
ulong gd_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
怕内存不干净先置0
/* 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));
for循环执行init_sequence函数指针数组,数组中存储了很多个函数指针,这些指向指向的函数都是init_fnc_t类型,如果遍历中有一个函数返回值不等于0则hang()挂起
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) { //在数组中遍历直到遍历到null
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
挂起函数
void hang (void)
{
puts ("### ERROR ### Please RESET the board ###\n");
for (;;);
}
init_sequence中包含的函数如下
init_fnc_t *init_sequence[] = {
cpu_init, /* basic cpu dependent setup */ //cpu内部的初始化
#if defined(CONFIG_SKIP_RELOCATE_UBOOT)
reloc_init, /* Set the relocation done flag, must
do this AFTER cpu_init(), but as soon
as possible */
#endif
board_init, /* basic board dependent setup */ //在uboot/board/samsung/x210/x210.c中
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,
};
int board_init(void)
{
DECLARE_GLOBAL_DATA_PTR; //gd的声明定义
#ifdef CONFIG_DRIVER_SMC911X
smc9115_pre_init(); //MC911X网卡初始化
#endif
#ifdef CONFIG_DRIVER_DM9000
dm9000_pre_init(); //DM9000网卡初始化
#endif
//这里的初始化DDR和汇编阶段lowlevel_init中初始化DDR是不同的。当时是硬件的初始化,目的是让DDR可以开始工作。现在是软件结构中一些DDR相关的属性配置、地址设置的初始化,是纯软件层面的。
gd->bd->bi_arch_number = MACH_TYPE; //bi_arch_number是board_info中的一个元素,含义是:开发板的机器码MACH_TYPE在x210_sd.h中定义是当前开发板对应的编号。
gd->bd->bi_boot_params = (PHYS_SDRAM_1+0x100); //bi_boot_params表示uboot给linux kernel启动时的传参的内存地址
return 0;
}
这个函数是用来初始化定时器Timer4,这个定时器被设计用来做计时。不是中断!因为Timer4的定时是不能实现微观上的并行而uboot中定时就是通过Timer4来实现定时,所以uboot中定时时不能做其他事。
int interrupt_init(void)
{
S5PC11X_TIMERS *const timers = S5PC11X_GetBase_TIMERS(); //10ms
/* 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);
}
和环境变量有关的初始化。env_xx.c同时只有1个会起作用,其他不起作用。
getenv_r函数用来读取环境变量的值。用getenv函数读取环境变量中“baudrate”的值。因为读取到的是字符串类型,所以需要simple_strtoul函数将字符串转成数字格式的波特率。如果读取成功则使用这个值作为环境变量,记录在gd->baudrate和gd->bd->bi_baudrate中;如果读取不成功则使用x210_sd.h中的的CONFIG_BAUDRATE的值作为波特率。
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);
}
uboot中有很多serial_init函数。但只使用了cpu/s5pc11x/serial.c中的serial_init函数。但其实在lowlevel_init.S中串口早就初始化过了。所以这里的serial_init不再进行硬件初始化
int serial_init(void)
{
serial_setbrg();
return (0);
}
void serial_setbrg(void)
{
DECLARE_GLOBAL_DATA_PTR;
int i;
for (i = 0; i < 100; i++);
}
console_init_f是控制台的第一阶段(有时一些初始化无法一次性完成)初始化,f表示第一阶段,r表示第二阶段。
int console_init_f (void)
{
gd->have_console = 1; //用于后面display_banner中的printf中的puts判断console有没有被初始化好
#ifdef CONFIG_SILENT_CONSOLE
if (getenv("silent") != NULL)
gd->flags |= GD_FLG_SILENT;
#endif
return (0);
}
用来串口输出显示uboot的logo。如果console初始化好了则调用fputs完成串口发送;如果console尚未初始化好则会调用serial_puts。控制台就是一个用软件虚拟出来的设备,这个设备有一套专用的通信函数(发送、接收···),控制台的通信函数最终会映射到硬件的通信函数中来实现。在某些体系中,控制台的通信函数映射到硬件通信函数时可以用软件来做一些中间优化。
static int display_banner (void)
{
printf ("\n\n%s\n\n", version_string); //里面是puts用于判断console是否初始化完成。
debug ("U-Boot code: %08lX -> %08lX BSS: -> %08lX\n",
_armboot_start, _bss_start, _bss_end);
#ifdef CONFIG_MEMORY_UPPER_CODE /* by scsuh */
debug("\t\bMalloc and Stack is above the U-Boot Code.\n");
#else
debug("\t\bMalloc and Stack is below the U-Boot Code.\n");
#endif
#ifdef CONFIG_MODEM_SUPPORT
debug ("Modem Support enabled\n");
#endif
#ifdef CONFIG_USE_IRQ
debug ("IRQ Stack: %08lx\n", IRQ_STACK_START);
debug ("FIQ Stack: %08lx\n", FIQ_STACK_START);
#endif
open_backlight();//lqm.
//open_gprs();
return (0);
}
DDR的软件初始化,dram_init都是在给gd->bd里面关于DDR配置部分的全局变量赋值,让gd->bd数据记录下当前开发板的DDR的配置信息,以便uboot中使用内存。
int dram_init(void)
{
DECLARE_GLOBAL_DATA_PTR;
gd->bd->bi_dram[0].start = PHYS_SDRAM_1;
gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;
#if defined(PHYS_SDRAM_2)
gd->bd->bi_dram[1].start = PHYS_SDRAM_2;
gd->bd->bi_dram[1].size = PHYS_SDRAM_2_SIZE;
#endif
#if defined(PHYS_SDRAM_3)
gd->bd->bi_dram[2].start = PHYS_SDRAM_3;
gd->bd->bi_dram[2].size = PHYS_SDRAM_3_SIZE;
#endif
return 0;
}
打印显示dram的配置信息。uboot中有一个命令叫bdinfo,这个命令可以打印出gd->bd中记录的所有硬件相关的全局变量的值,因此可以得知DDR的配置信息。
static int display_dram_config (void)
{
int i;
#ifdef DEBUG
puts ("RAM Configuration:\n");
for(i=0; i<CONFIG_NR_DRAM_BANKS; i++) {
printf ("Bank #%d: %08lx ", i, gd->bd->bi_dram[i].start);
print_size (gd->bd->bi_dram[i].size, "\n");
}
#else
ulong size = 0;
for (i=0; i<CONFIG_NR_DRAM_BANKS; i++) {
size += gd->bd->bi_dram[i].size;
}
puts("DRAM: ");
print_size(size, "\n");
#endif
return (0);
}
这里的Flash都是指的Norflash。x210中并没有Norflash
#ifndef CFG_NO_FLASH
/* configure available FLASH banks */
size = flash_init ();//执行的是NorFlash的初始化
display_flash_config (size);//打印的是NorFlash的配置信息
#endif /* CFG_NO_FLASH */
显示相关的,这个是uboot中自带的LCD显示的软件架构。
#ifdef CONFIG_VFD
# ifndef PAGE_SIZE
# define PAGE_SIZE 4096
# endif
/*
* reserve memory for VFD display (always full pages)
*/
/* bss_end is defined in the board-specific linker script */
addr = (_bss_end + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);
size = vfd_setmem (addr);
gd->fb_base = addr;
#endif /* CONFIG_VFD */
#ifdef CONFIG_LCD
/* board init may have inited fb_base */
if (!gd->fb_base) {
# ifndef PAGE_SIZE
# define PAGE_SIZE 4096
# endif
/*
* reserve memory for LCD display (always full pages)
*/
/* bss_end is defined in the board-specific linker script */
addr = (_bss_end + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);
size = lcd_setmem (addr);
gd->fb_base = addr;
}
#endif /* CONFIG_LCD */
初始化uboot的堆管理器,只要有对管理器就可以用melloc、free管理内存
#ifdef CONFIG_MEMORY_UPPER_CODE /* by scsuh */
mem_malloc_init (CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE);
#else
mem_malloc_init (_armboot_start - CFG_MALLOC_LEN);
#endif
从536到768行为开发板独有的初始化。里面包含了多种开发板的初始化。
MMC相关的一些基础的初始化,其实就是用来初始化SoC内部的SD/MMC控制器的。mmc_initialize是具体硬件架构无关的一个MMC初始化函数,所有的使用了这套架构的代码都掉用这个函数来完成MMC的初始化。mmc_initialize中再调用board_mmc_init和cpu_mmc_init来完成具体的硬件的MMC控制器初始化工作。cpu_mmc_init里面又间接的调用了drivers/mmc/s3c_mmcxxx.c中的驱动代码来初始化硬件MMC控制器。
env_relocate是环境变量的重定位,完成从SD卡中将环境变量读取到DDR中的任务。真正的从SD卡到DDR中重定位ENV的代码是在env_relocate_spec内部的movi_read_env完成的。
void env_relocate (void)
{
DEBUGF ("%s[%d] offset = 0x%lx\n", __FUNCTION__,__LINE__,
gd->reloc_off); //reloc_off既重定位偏移量
#ifdef CONFIG_AMIGAONEG3SE
enable_nvram();
#endif
#ifdef ENV_IS_EMBEDDED
/*
* The environment buffer is embedded with the text 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 the environment
*/
env_ptr = (env_t *)malloc (CFG_ENV_SIZE);
DEBUGF ("%s[%d] malloced ENV at %p\n", __FUNCTION__,__LINE__,env_ptr);
#endif
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 - bad CRC, using default environment\n\n");
show_boot_progress (-60);
#endif
set_default_env();
}
else {
env_relocate_spec ();
}
gd->env_addr = (ulong)&(env_ptr->data);
#ifdef CONFIG_AMIGAONEG3SE
disable_nvram();
#endif
}
IP地址是在gd->bd中维护的,来源于环境变量ipaddr。getenv函数用来获取字符串格式的IP地址,然后用string_to_ip将字符串格式的IP地址转成字符串格式的点分十进制格式。
IPaddr_t getenv_IPaddr (char *var)
{
return (string_to_ip(getenv(var)));
}
开发板上的硬件驱动设备初始化。uboot中很多设备的驱动是直接移植linux内核的(譬如网卡、SD卡),linux内核中的驱动都有相应的设备初始化函数。这些设备其实前面都初始化过所以无作用
jumptable跳转表,本身是一个函数指针数组,里面记录了很多函数的函数名。实现一个函数指针到具体函数的映射关系。但无使用
控制台的第二阶段初始化,就是console的纯软件架构方面的初始化。uboot的console实际上并没有干有意义的转化,它就是直接调用的串口通信的函数。
中断初始化代码,指的是CPSR中总中断标志位的使能。但uboot中没有使用中断
void enable_interrupts (void)
{
unsigned long temp;
__asm__ __volatile__("mrs %0, cpsr\n"
"bic %0, %0, #0x80\n"
"msr cpsr_c, %0"
: "=r" (temp)
:
: "memory");
}
这两个环境变量都是内核启动有关的,在启动linux内核时会参考这两个环境变量的值。
硬件软件剩余的初始化,作收尾工作。X210的网卡初始化在board_init函数中,网卡芯片的初始化在驱动中。
网卡芯片本身的一些初始化。
启动起来之前的一些初始化,以及LCD屏幕上的logo显示。
自动更新功能,我们可以将要升级的镜像放到SD卡的固定目录中,然后开机时在uboot启动的最后阶段检查升级标志。