2020.02.08
- Uboot启动过程分析
1.启动的大概流程
注意:开发板刚上电时,cpu会首先从0x00000000地址处取值,所有会由硬件电路将高地址的IROM映射到0x00000000处,然后去执行
第零步:设置OM PIN引脚,就可以选择启动方式了,譬如从SD卡启动,emmc启动,norflash启动等等,我们这里假设从NAND启动。
第一步:IROM内的代码(BL0)对SOC进行初步的初始化工作
第二步:从NAND中复制BL1到SOC内的SRAM,执行BL1内的代码
第三步:从NAND中复制BL2到SOC内的SRAM,执行BL2内的代码(作用:初始化SDRAM控制器,那样我们就可以用DDR内存了)
第四步:把我们的操作系统OS复制到SDRAM中
第五步:我们在BL2中进行一个长跳转,跳转到SDRAM中去执行程序,这样我们整个启动过程就完成了
另外阐述另外一个比较容易忽略的问题
向量地址映射:根据ARM手册,一般异常发生以后,地址会被映射到0x00000000或者0xffff0000处,这两个地址可以通过CP15协处理器进行配置。即IRAM高地址区域会被映射到0x00000000处。
如何确定在0x00000004处;打开S5PV210 ApplicationNote.pdf文档第13页,BL1处加载镜像文件,地址为0xD0020000,则第一条指令地址为0xD0020010,0x00000010部分为校验和,ldr,pl等指令会被放在0xD0020014处,发现异常以后,ARM会跳转到0x00000004运行,而不是0xD0020014处,要解释这个问题,牵扯到另外一个比较大的知识点:异常向量和地址的映射问题
二、uboot第二阶段代码分析
- 分析start_armboot这个函数(逐行分析)
init_fnc_t **init_fnc_ptr;
char *s;
int mmc_exist = 0;
ulong size;
分析:
此处定义了四个变量
ulong gd_base;
gd_base = CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE - sizeof(gd_t);
分析:
CFG_UBOOT_BASE c3e0_0000
CFG_UBOOT_SIZE 3*1024*1024
CFG_MALLOC_LEN 0X8000 + 1024*1024
CFG_STACK_SIZE 512*1024
具体见下图分析:
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;
分析:
_bss_start是在链接文件中指定, _armboot_start的值就是代码段开始的地方
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
分析:
init_sequence在board.c文件中定义
init_fnc_t *init_sequence[] = {
cpu_init, /* basic cpu dependent setup */
#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 */
#ifndef CONFIG_DIS_BOARD_INFO
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
#endif
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C)
init_func_i2c,
#endif
dram_init, /* configure available RAM banks */
display_dram_config,
NULL,
};
分析:
下面逐个分析
Cpu_init
这个里面啥也没干
board_init
{
DECLARE_GLOBAL_DATA_PTR;
#ifdef CONFIG_DRIVER_SMC911X
smc9115_pre_init();
#endif
#ifdef CONFIG_DRIVER_DM9000
dm9000_pre_init();
#endif
gd->bd->bi_arch_number = MACH_TYPE;
gd->bd->bi_boot_params = (PHYS_SDRAM_1+0x100);
}
这个里面主要完成网卡的初步初始化,还有就是arch_number 的赋值(2345),还有boot_params赋值 (0x2000_0000 + 0x100)
interrupt_init
这个里面主要是中断相关的初始化
env_init
这个函数比较重要,主要完成环境变量相关的初始化
{
#if defined(ENV_IS_EMBEDDED) //此处未定义,直接跳过
size_t total;
int crc1_ok = 0, crc2_ok = 0;
env_t *tmp_env1, *tmp_env2;
total = CFG_ENV_SIZE; //0x4000
tmp_env1 = env_ptr;
tmp_env2 = (env_t *)((ulong)env_ptr + CFG_ENV_SIZE);
//env_ptr指向一个全局数组的第一个元素
extern uchar environment[];
env_t *env_ptr = (env_t *)(&environment[0]);
crc1_ok = (crc32(0, tmp_env1->data, ENV_SIZE) == tmp_env1->crc);
crc2_ok = (crc32(0, tmp_env2->data, ENV_SIZE) == tmp_env2->crc);
if (!crc1_ok && !crc2_ok)
gd->env_valid = 0;
else if(crc1_ok && !crc2_ok)
gd->env_valid = 1;
else if(!crc1_ok && crc2_ok)
gd->env_valid = 2;
else {
/* both ok - check serial */
if(tmp_env1->flags == 255 && tmp_env2->flags == 0)
gd->env_valid = 2;
else if(tmp_env2->flags == 255 && tmp_env1->flags == 0)
gd->env_valid = 1;
else if(tmp_env1->flags > tmp_env2->flags)
gd->env_valid = 1;
else if(tmp_env2->flags > tmp_env1->flags)
gd->env_valid = 2;
else /* flags are equal - almost impossible */
gd->env_valid = 1;
}
if (gd->env_valid == 1)
env_ptr = tmp_env1;
else if (gd->env_valid == 2)
env_ptr = tmp_env2;
#else /* ENV_IS_EMBEDDED */
gd->env_addr = (ulong)&default_environment[0]; //对环境变量赋值,使用默认值
gd->env_valid = 1;
#endif /* ENV_IS_EMBEDDED */
return (0);
}
init_baudrate
完成串口波特率的初始化
{
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);
}
紧接着就是
dram_init
{
DECLARE_GLOBAL_DATA_PTR;
gd->bd->bi_dram[0].start = PHYS_SDRAM_1;
gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;
//对dram参数赋值
#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
}
mem_malloc_init (CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE);
分析:
此处为初始化了malloc起始地址和结束地址
#if defined(CONFIG_TQ210) //定义了该宏,本开发板为TQ210
#if defined(CONFIG_GENERIC_MMC)
#ifndef CONFIG_DIS_BOARD_INFO
puts ("SD/MMC: ");
#endif
mmc_exist = mmc_initialize(gd->bd); //mmc初始化
if (mmc_exist != 0)
{
#ifndef CONFIG_DIS_BOARD_INFO
puts ("0 MB\n");
#endif
}
#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)
#ifndef CONFIG_DIS_BOARD_INFO
puts("NAND: ");
#endif
nand_init(); //nandflash初始化
#endif
#endif /* CONFIG_TQ210 */
/* initialize environment */
env_relocate ();
分析:
{
#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); // gd->reloc_off的值为0
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
}
#ifdef CONFIG_SERIAL_MULTI
serial_initialize();
#endif
分析:
完成串口的初始化
/* IP Address */
gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr"); //获取开发板的IP地址
/* MAC Address */
{
int i;
ulong reg;
char *s, *e;
char tmp[64];
i = getenv_r ("ethaddr", tmp, sizeof (tmp)); //获取开发板的MAC地址
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;
}
devices_init (); /* get the devices list going. */
分析:
这个里面主要实现开发板外接设备的初始化
jumptable_init ();
分析:
进行好一些回到函数的设置
{
int i;
gd->jt = (void **) malloc (XF_MAX * sizeof (void *));
for (i = 0; i < XF_MAX; i++)
gd->jt[i] = (void *) dummy;
gd->jt[XF_get_version] = (void *) get_version;
gd->jt[XF_malloc] = (void *) malloc;
gd->jt[XF_free] = (void *) free;
gd->jt[XF_getenv] = (void *) getenv;
gd->jt[XF_setenv] = (void *) setenv;
gd->jt[XF_get_timer] = (void *) get_timer;
gd->jt[XF_simple_strtoul] = (void *) simple_strtoul;
gd->jt[XF_udelay] = (void *) udelay;
gd->jt[XF_simple_strtol] = (void *) simple_strtol;
gd->jt[XF_strcmp] = (void *) strcmp;
#if defined(CONFIG_I386) || defined(CONFIG_PPC)
gd->jt[XF_install_hdlr] = (void *) irq_install_handler;
gd->jt[XF_free_hdlr] = (void *) irq_free_handler;
#endif /* I386 || PPC */
#if defined(CONFIG_CMD_I2C)
gd->jt[XF_i2c_write] = (void *) i2c_write;
gd->jt[XF_i2c_read] = (void *) i2c_read;
}
#if !defined(CONFIG_SMDK6442)
console_init_r (); /* fully init console as a device */
#endif
分析:
主要完成回话窗口的设置,stdin/stdout/stderr
/* enable exceptions */
enable_interrupts ();
分析:
开启中断异常
/* Initialize from environment */
if ((s = getenv ("loadaddr")) != NULL) {
load_addr = simple_strtoul (s, NULL, 16);
}
分析:
本开发板loadaddr环境变量未设置
#if defined(CONFIG_CMD_NET)
if ((s = getenv ("bootfile")) != NULL) {
copy_filename (BootFile, s, sizeof (BootFile));
}
#endif
分析:
Bootfile未定义
#ifdef BOARD_LATE_INIT
board_late_init ();
#endif
分析:
啥也没干,直接返回0
/* main_loop() can return to retry autoboot, if so just run it again. */
for (;;) {
main_loop ();
}
此处开始进行uboot启动菜单的显示,即主循环,先获取bootdelay的值,超时等待delay变为0,如果在delay时间内没有任何输入,则会去获取bootcmd参数,根据这个参数去加载内核到内存中,然后去启动内核,如果在delay结束之前有键盘输入,则会触发menu命令,显示主菜单