最近一段时间由于项目需求,又要再一次对uboot动刀实现快速启动以及一些前置的初始化工作。uboot这东西啊,常用不常动,每一次涉及到修改或移植uboot时大多要间隔一两个月的时间,甚至更久。所以为了方便大家不重复劳动,在这里记录下uboot的启动流程;同时希望能对初学者有所帮助。本文是对hisi3518ev200平台下的uboot-2010.06进行分析。由于个人水平有限,难免会对某些概念理解有所偏差,希望大家能指出修正;如有哪些地方没有解释清楚,欢迎留言,共同学习。
一、uboot的启动顺序
uboot的入口是start.S汇编文件中名为_start的标识符。它的执行流程是:
_start --> reset --> normal_start_flow --> start_armboot(完成初始化) --> main_loop(控制台循环检索命令与启动内核)
二、uboot执行流程概览
1、reset(start.S)
(1)设置cpu模式。(设置到SVC32模式)
(2)清除指令、数据高速缓存。
(3)关闭mmu和高速缓存。
(4)读取系统寄存器REG_SC_GEN2,检查ziju标志位。
- 自举启动就是冷启动。芯片上电启动一般都会执行由硬件厂商写入并锁死的启动程序,用这个程序去执行flash和sram地址分配等操作。而海思芯片的自举程序执行结束前应该把ziju标志设置到了相应的寄存器上。
- 冷启动时SOC上的所有外部都没被初始化,所以要初始化PLL、DDRC、引脚复用、消除ziju标志,随后跳到自举代码指定的地方执行操作(可能是热启动操作)。
- ziju标志没设置就是热启动。
(5)执行normal_start_flow。
2、normal_start_flow(start.S)
(1)初始化串口,打印启动信息System startup。执行了uart_early_init(uart.S),这个函数初始化了串口外设,操作如下:
- Disable UART
- Set baud rate to 115200, uart clock:24M
- Set the UART to be 8 bits, 1 stop bit, no parity, fifo enabled.
- Enable UART
(2)检查是否从fmc进行引导。
- 是,再检查是否从emmc引导。
(3)清除重映射,及重映射标志位。
(4)使能指令高速缓存。
(5)检查动态内存(DDR等)是否在工作。
- 否,重新初始化,重新引导。
(6)代码重定位到动态内存。执行relocate。相当于memcpy(起始地址,目标地址,大小)
adrl r0, _start @ r0 stores current position of code
ldr r1, _TEXT_BASE @ r1 stores where we will copy uboot to
ldr r2, _armboot_start
ldr r3, _bss_start
sub r2, r3, r2 @ r2 <- size of armboot
bl memcpy
- 重定向涉及到位置无关码与位置有关码,位置无关码指的是不涉及链接脚本地址的代码段,位置无关码跳转时一般只会进行短跳转(短跳转是用当前地址来计算的相对偏移值,长跳转是链接地址的绝对值),位置有关码指的是涉及链接脚本地址的跳转代码。当链接地址与运行地址相同时,就没有这种区分了。
- 代码的第一行取的是当前运行代码的地址,第二行则是链接的地址,在代码编译链接时根据makefile的设置会将其链接到DDR对应的地址上。
(7)重定位异常代码。
(8)设置uboot堆栈。
(9)清除bss段。
- 综上,我们可以得出DDR上区间的分布,如图:
(10)转跳到uboot第二阶段,C代码阶段start_armboot。
- 代码的跳转时,使用的是链接的地址进行的,代码链接时会根据makefile会把链接地址设置在DDR对应的地址,所以start_armboot的代码就是我们所说的位置有关码。这时开始正式在DDR运行了。
3、start_armboot(board.c)
(1)在uboot启动的第二个阶段涉及到几个重要的数据结构,先列出来。
(global_data.h)。
typedef struct global_data {
bd_t *bd;
unsigned long flags;
unsigned long baudrate; //串口波特率
unsigned long have_console; /* serial_init() was called */
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
#ifdef CONFIG_FSL_ESDHC
unsigned long sdhc_clk;
#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;
(u-boot.h)
typedef struct bd_info {
int bi_baudrate; /* serial console baudrate */
unsigned long bi_ip_addr; /* IP Address */
struct environment_s *bi_env;
ulong bi_arch_number; /* unique id for this board */
ulong bi_boot_params; /* where this board expects params */uboot给kernel传参用的地址
struct /* RAM configuration */
{
ulong start;
ulong size;
} bi_dram[CONFIG_NR_DRAM_BANKS];
} bd_t;
(environment.h)
typedef struct environment_s {
uint32_t crc; /* CRC32 over data bytes */
#ifdef CONFIG_SYS_REDUNDAND_ENVIRONMENT
unsigned char flags; /* active/obsolete flags */
#endif
unsigned char data[ENV_SIZE]; /* Environment data */
} env_t;
(2)在这一阶段中,可以看出DDR上的区间分布为:
(3)start_armboot所做的工作
1)init_sequence的板载初始化
1)cpu初始化
2)定时器初始化
3)板载初始化
- 1、选择串口时钟
- 2、设置全局变量gd中的cpu以及引导参数
- 3、确认引导方式(spi nor 、spi nand)
4)中断初始化,其时是定时器初始化,这个定时器主要是用来定时或延时的。由于没有中断处理,定时或延时只能使用查询的方法。
5)环境初始化,将环境变量设置成默认环境。
6)串口波特率初始化,从环境变量中打到 baudrate,将其转化成十进制并给全局变量gd中的波特率成员赋值。
7)串口初始化,再次初始化一次,所用的波特率并不是环境变量中的。
8)控制台首次初始化,设置全局变量have_console成员。
9)显示logo (display_banner),打印版本信息。
10)打印cpu信息
11)打印板信息 checkboard
12)i2c初始化
13)dram初始化,设置全局变量有关dram的成员(起始地址、空间大小)
14)pci初始化
15)打印dram信息
2)初始化内存堆栈,清除malloc空间,做好相关记录。
3)初始化nor flash,打印nor flash配置信息。VFD的配置或LCD配置。
4)初始化各种类型的储存器(flash、nand、onenand、mmc等)。检查是否SD、USB卡升级等。
5)环境变量重定位、VFD驱动、初始化其他串口。
6)设置全局变量的ip地址。
7)初始化标准输入输出设备,并将其写入链表。
8)控制台的第二次初始化,为控制台邦定输入输出,从输入输出链表中找出设备进行邦定。设置相应的控制台环境变量。
9)初始化杂散类设备。(空)
10)使能中断。(空)
11)初始化网卡。
4、main_loop(main.c)
(1)读取与更新启动次数。
(2)初始化自动补全功能。
(3)tftp自动更新。
(4)读取bootdelay。
(5)检查在bootdelay的时间里,是否有按键按下。
- 否,执行最后加载的命令,一般是bootcmd中的命令。
(6)运行命令监测程序。根据串口输入的命令行事。