/*******************************************************************************
uboot启动过程之第二阶段的分析(board.c的分析)
时间:201812月中旬
者:cryil_先森
以朱有鹏课程中uboot-samsung-dev为分析对象
*********************************************************************************/
宏观分析:
初始化第一阶段结束还没被初始化的硬件;SOC的外部硬件(inand,网卡等);uboot本身的一些东西(uboot的命令,环境变量),最终初始化完进入uboot的命令行准备接受命令。
在这个C语言程序之中,大部分是对于环境变量的初始化,对于函数的定义和初始化,真正这些变量和函数使用的地方是在void start_armboot(void) 函数之中,统筹了板子的硬件的初始化。
uboot的启动概述:
主要是对开发板级别的硬件的初始化、软件的数据结构进行初始化。uboot在启动完后打印出很多的信息,然后进入倒数bootdelay秒后执行bootcmd对应的启动命令。如果用户没有进行干涉则会执行bootcmd进入自动启动内核流程(此时uboot就死掉了),此时用户可以按下回车键打断uboot的自启动过程进入uboot的命令行下,然后uboot就会一直工作在命令行下。而uboot的命令行就是一个死循环,循环体内不断地重复:接受命令,解析命令,执行命令。
***1.一个重要的函数指针数组init_sequence[]:
cpu_init, 空的
reloc_init,
board_init, 网卡,机器码,内存传参地址
interrupt_init, 定时器
env_init,
init_baudrate, 波特率数据结构
serial_init, 串口初始化(空的)
console_init_f, 空的
display_banner, 打印启动信息
print_cpuinfo, 打印出CPU时钟设置信息
checkboard, 检验开发板名字
init_func_i2c,
dram_init, 初始化gd数据结构的
display_dram_config, 打印出DDR的配置信息
***2.其他
mem_malloc_init 初始化堆内存
mmc_initialize inand/SD卡的SOC控制器和卡的初始化
env_relocate (); 环境变量的重定位
devices_init (); 基本为空的
jumptable_init (); 无用的
console_init_r() 真正的控制台初始化
enable_interrupts 基本为空的
loadaddr和bootfile 环境变量的读出,初始化全局变量
board_late_init() 最后的补充初始化函数,基本为空
eth_initialize() 网卡芯片本身的一些硬件初始化
main_loop() uboot工作的死循环。
*******************************************************************************************************************************************
具体的分析如下:
***1.引用了一个全局变量:
DECLARE_GLOBAL_DATA_PTR
这个全局变量在Global_data.h头文件之中被定义
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
DECLARE_GLOBAL_DATA_PTR其实是一个全局变量gd,这个全局变量在asm寄存器r8位被使用 同时这个全局变量是一个指向gd_t类型变量的指针。
***2.引出了两个结构体gd_t和bd_t (Global_data.h中)
**1.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 */
#ifdef CONFIG_VFD
unsigned char vfd_type; /* display type */
#endif
#if 0
unsigned long cpu_clk; /* CPU clock in Hz! */
unsigned long bus_clk;
phys_size_t ram_size; /* RAM size */
unsigned long reset_status; /* reset status register at boot */
#endif
void **jt; /* jump table */
} gd_t;
分析:这个全局变量实际上是一个结构体,结构体内是uboot中常用的所有全局变量。而bd_t 则是一个和开发板版级信息直接相关的结构体,结构体内包含:板子的硬件信息,IP地址,机器码,
以及DDR内存分布情况的结构体bi_dram[CONFIG_NR_DRAM_BANKS]等。
**2.bd_t:
typedef struct bd_info {
int bi_baudrate; /* serial console baudrate */
定义了波特率
unsigned long bi_ip_addr; /* IP Address */
定义了IP地址
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];
定义了一个关于DDR的结构体
#ifdef CONFIG_HAS_ETH1
/* second onboard ethernet port */
unsigned char bi_enet1addr[6];
#endif
} bd_t;
***3.一系列初始化函数的定义:(在后面的程序之中会给出初始化函数的执行顺序)
**1.version_string[]数组:
const char version_string[] =
U_BOOT_VERSION" (" __DATE__ " - " __TIME__ ")"CONFIG_IDENT_STRING;
分析:定义了一个字符串数组,这个数组内记录了板子的信息和烧录信息。baudratebaudrateversion_string 这个参数的一部分是主Makefile传过来的,代表板子的版本号; " (" __DATE__ " - " __TIME__ ")"是烧录时的时间信息;CONFIG_IDENT_STRING是板子对应的芯片信息,而这个字符串数组在之后被引用 display_banner, /* say that we are here */用串口打印显示出uboot的logo,(此时控制台还未完全初始化完成,通过一系列函数的调用直接控制串口寄存器,打印输出uboot的logo)
static int init_baudrate (void) 波特率的初始化函数
static int display_banner (void) 串口打印初始化函数
**2一个重要的函数指针数组init_sequence[]
大致进行一下操作:板级硬件的初始化,gd、gd-bd的初始化,网卡的初始化,机器码(gd-bd-bi_arch_number),内核传参DDR的地址(gd->bd->bi_boot_params),波特率,打印出CPU的启动信息,打印出CPU的相关设置信息,检查并打印当前开发板名字,DDR的配置信息的初始化。
定义了一个init_fnc_t类型的数组,数组内存放的是指针(实际上是数组内函数的首地址)
数组内储存了很多个函数指针,这些指向的函数都是init_fnc_t类型的(接受void类型参数返回int型参数)。init_sequence在定义之初同时被初始化,初始化的函数指针都是一些函数名(函数名可以做指针使用),而这些函数都是board级别的硬件初始化函数。
typedef int (init_fnc_t) (void);
int print_cpuinfo (void); /* test-only */
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 */
详见下面
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 */
控制台初始化
注:在初始化时不能一次初始化完成时分两步进行,_f第一阶段 _r第二阶段
display_banner, /* say that we are here */
打印出version_string
#if defined(CONFIG_DISPLAY_CPUINFO)
print_cpuinfo, /* display cpu info (and speed) */
打印出CPU的信息(波特率)
#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 */
关于DDR的初始化,其实在汇编阶段已经进行了一次初始化(从硬件的角度)
查看原函数可知此次的初始化规定了各个内存块的起始地址和大小
display_dram_config,
NULL,
};
打印显示DDR的配置信息
**3.板子的初始化细究(board_init):
int board_init(void)
{
DECLARE_GLOBAL_DATA_PTR;
#ifdef CONFIG_DRIVER_SMC911X
smc9115_pre_init();
#endif
#ifdef CONFIG_DRIVER_DM9000
dm9000_pre_init();
#endif
分析:再次引用了gd这个结构体,主要完成网卡的GPIO和端口的配置,而不是驱动,移植时驱动不用修改,只用改动初始化函数。
gd->bd->bi_arch_number = MACH_TYPE;
分析:bi_arch_number:开发板的机器码,是board.info的一个元素。在uboot和Linux内核之间进行比对和配置。由于嵌入式设备的高度定制化,导致软件和硬件不能随意适配使用。板子的机器码和uboot、Linux的内核进行比对,然后决定是否启动。uboot中配置的机器码会作为参数传给Linux内核,内核启动时会进行再次比对决定是否启动。
gd->bd->bi_boot_params = (PHYS_SDRAM_1+0x100);
return 0;
}
分析:bi_boot_params是uboot传给Linux的参数,代表Linux内核启动时的内存地址,之后uboot就能启动内核(启动过程其实是寄存器R0 R1 R2来直接传递参数的) uboot事先准备好参数bootargs(字符串)放在内存之中,通过uboot的传参,内核启动时就会知道bootargs的内存地址(0x20000100)。
注:初始化DDR(和汇编阶段lowlever_init中的初始化DDR是不同的,当时只是进行硬件的初始化,使得DDR能够开始工作),进行软件结构中的一些DDR相关的属性配置、地址设置的初始化。
在uboot设计时,开发者在***.h(板载头文件)中使用宏定义去配置出来DDR的内存信息,然后uboot只需读取这些信息即可知道DDR的内存信息。
PHYS_SDRAM_1是DDR的地址信息,同时在它定义的地方之后也定义了DDR的个数,大小。
同时也对DDR的各个分区做出了定义,定义了DDR各个分区大小,起始地址
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
return 0;
}
***4.板子初始化启动过程的整体架构 void start_armboot (void)
**1.定义了一个二重函数指针init_fnc_ptr:
(这里用来指向一个函数指针数组)
init_fnc_t **init_fnc_ptr;
typedef int (init_fnc_t) (void);
char *s;
int mmc_exist = 0;
**2.一系列的判断是否初始化:
#if !defined(CFG_NO_FLASH) || defined (CONFIG_VFD) || defined(CONFIG_LCD)
ulong size;
#endif
#if defined(CONFIG_VFD) || defined(CONFIG_LCD)
unsigned long 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 */
ulong gd_base;
**3.uboot的内存排布:
gd_base = CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE - sizeof(gd_t);
由于gd和bd都是一个指针类型的全局变量,gd和bd变量在使用前需要被分配内存,而此时是裸机状态,系统不会自动的进行内存的分配,只能人为的为其指定内存区域。而gd_base规定了我们指针开始访问的地址,为指针找到了一块安全的内存空间。(详见文件夹内的图片)
分析:gd_base是gd这段内存存放在内存之中的起始地址。
CFG_UBOOT_BASE:uboot代码的起始地址。(uboot实际大小为200kb左右)
CFG_UBOOT_SIZE:系统为uboot分配的内存空间(2MB)
CFG_MALLOC_LEN:堆区,长度为912KB
CFG_STACK_SIZE:栈区,长度为512kb
sizeof(gd_t):gd全局变量的内存空间,长度为36字节
bd:bd全局变量的内存空间,长度为字节。
综上所述:gd的内存地址在uboot起始地址+600kb左右的空间内,而一般情况下uboot只有200kb左右,两者之间有着400kb左右的间隔。
#ifdef CONFIG_USE_IRQ
gd_base -= (CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ);
#endif
gd = (gd_t*)gd_base;
分析:将gd_base代表的数字强制类型转化为指针类型然后赋值给gd,此时gd就指向了这段存放gd全局变量的内存地址。
#else //CONFIG_USE_IRQ
gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));
#endif //CONFIG_MEMORY_UPPER_CODE
/* 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;
分析:在使用之前清除栈中的变量。
**4.板子初始化的真正开始:
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
for循环内从第一个函数cpu_init开始,遍历整个函数数组,从而依次调用每一个函数,而数组内的每一个函数的返回值都是0,依次将每一个函数的返回值和0进行判断,从而决定是否执行hang ()。
而hang ()函数为:
void hang (void)
{
puts ("### ERROR ### Please RESET the board ###\n");
for (;;);
}
hang ()是一个启动过程错误判断函数,如果某一个函数执行错误,uboot的启动就会中止,然后打印错误。
***5.一些零碎的初始化、定义。
**1.CFG_NO_FLASH
#ifndef CFG_NO_FLASH
/* configure available FLASH banks */
size = flash_init ();
display_flash_config (size);
#endif /* CFG_NO_FLASH */
分析:在启动时打印显示出nand的内存大小。
**2.CONFIG_VFD和CONFIG_LCD都是显示相关的
分析:uboot中自带的lcd显示架构,但是在启动时实际没有使用,我们在后面自己添加了一个lcd显示的部分。
**3.DDR堆的初始化
mem_malloc_init (CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE);
分析:具体看mem_malloc_init函数可知,括号内传入的参数是堆的起始地址,在初始化函数中申请了堆内存,并规定了堆的大小。从而达到自己维护堆内存的目的。
**4.判断适配出和自己板子相同的文件然后进行下一步的操作。
#if defined(CONFIG_SMDKC110)
#if defined(CONFIG_GENERIC_MMC)
puts ("SD/MMC: ");
mmc_exist = mmc_initialize(gd->bd);
if (mmc_exist != 0)
{
puts ("0 MB\n");
}
#endif
#if defined(CONFIG_MTD_ONENAND)
puts("OneNAND: ");
onenand_init();
/*setenv("bootcmd", "onenand read c0008000 80000 380000;bootm c0008000");*/
#else
//puts("OneNAND: (FSR layer enabled)\n");
#endif
#if defined(CONFIG_CMD_NAND)
puts("NAND: ");
nand_init();
#endif
分析:mmc_exist = mmc_initialize(gd->bd);
初始化SOC内部的SD/MMC控制器,实际上再次调用了board_mmc_init和cpu_mmu_init函数来完成具体的硬件MMC控制器的初始化。
注:uboot对硬件的初始化操作(网卡,SD卡..)都是通过借用Linux内核中的驱动来实现的在uboot/drivers内都是从内核移植过来的各种驱动的源代码。
**5.环境变量的导入
#ifdef CONFIG_HAS_DATAFLASH
AT91F_DataflashInit();
dataflash_print_info();
#endif
/* initialize environment */
env_relocate ();
分析:由于uboot中使用的是SD卡,不是DATAFLASH,所以直接进行endif的语句,进行环境变量的重定位,将SD卡的环境变量读取到DDR中。
#ifdef CONFIG_VFD和#ifdef CONFIG_SERIAL_MULT均与我们的板子无关
**6.IP地址和MAC地址的获取
/* IP Address */
gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");
IPaddr_t getenv_IPaddr (char *var)
{
return (string_to_ip(getenv(var)));
}
分析:开发板的IP地址用环境变量gd->bd->bi_ip_addr来表示。
getenv_IPaddr函数用来获取字符串格式的IP地址,然后用string_to_ip函数字符串格式的IP地址转换成点分式十进制格式。(也就是通常意义的IP地址)
/* MAC Address */
{
int i;
ulong reg;
char *s, *e;
char tmp[64];
i = getenv_r ("ethaddr", tmp, sizeof (tmp));
s = (i > 0) ? tmp : NULL;
for (reg = 0; reg < 6; ++reg) {
gd->bd->bi_enetaddr[reg] = s ? simple_strtoul (s, &e, 16) : 0;
if (s)
s = (*e) ? e + 1 : e;
}
分析:MAC Address是网卡的硬件地址。 用环境变量gd->bd->bi_enetaddr来表示。
可以看出来,网卡的硬件地址由六部分构成,由函数getenv_r获取,在经过非零判断最后将其赋值给gd->bd->bi_enetaddr。
**7.驱动设备的初始化
devices_init (); /* get the devices list going. */
分析:直接从驱动架构框架衍生出来的,来源于Linux内核中驱动的源码,作用就是集中执行各种硬件驱动的init函数。
**8.定义了一个跳转表
jumptable_init ();
分析:其实本身是一个函数指针数组,里面记录了很多函数名,将来可以使用跳转表内的函数指针执行相应的函数
(相当于init_fnc_t *init_sequence[])这就实现了用C语言完成面向对象的编程。但是分析可知跳转表只是被赋值,并没有被使用过。
**9.控制台的第二阶段的初始化
#if !defined(CONFIG_SMDK6442)
console_init_r (); /* fully init console as a device */
#endif
分析:console_init_r是console(控制台)纯软件配置架构方面的初始化,属于纯软件配置类型的初始化。他直接调用串口通讯的函数。
**10.中断的初始化函数
enable_interrupts ();
分析:CPSR总中断标志位的使能。
**11.两个环境变量loadaddr和bootfile
/* Initialize from environment */
if ((s = getenv ("loadaddr")) != NULL) {
load_addr = simple_strtoul (s, NULL, 16);
}
#if defined(CONFIG_CMD_NET)
if ((s = getenv ("bootfile")) != NULL) {
copy_filename (BootFile, s, sizeof (BootFile));
}
#endif
分析:内核启动有关的环境变量,在启动内核时会参考这两个环境变量的值。
**12.开发板级别的最后部分的初始化
#ifdef BOARD_LATE_INIT
board_late_init ();
#endif
**13.网卡芯片本身的一些硬件初始化
eth_initialize(gd->bd);
分析:这里的初始化不是SOC与网卡连接时SOC的初始化,只是本身的初始化。
**14.关于IDE的初始化
#if defined(CONFIG_CMD_IDE)
puts("IDE: ");
ide_init();
#endif
**15.启动成功,uboot进入死循环
/* main_loop() can return to retry autoboot, if so just run it again. */
for (;;) {
main_loop ();
}
/* NOTREACHED - no way out of command loop except booting */
}
分析:uboot进入死循环,不间断的进行检测命令,执行命令的操作。
main_loop的功能:解析器;开机倒数执行;命令的补全
**16.启动错误打印函数
void hang (void)
{
puts ("### ERROR ### Please RESET the board ###\n");
for (;;);
}
分析:在之前的start_armboot函数中有使用,当启动过程有任意一个初始化出错时,就会打印出### ERROR ### Please RESET the board ###,一些初始化归零,启动失败。