boot and loader分析文档 - embedclub/Lavender GitHub Wiki
启动的种类
1.从硬件角度看
IROM启动
Stepping Stone启动
FIFO模式启动(较为少见)
2.从软件角度看
NandFlash启动
ARM从nandFlash启动 若从nandflash启动,上电后nandflash控制器自动把nandflash存储器中的0——4K内容加载到芯片内的起步石(Steppingstone,起步石这个机制是处理器中集成的功能,对程序员透明),即内部SRAM缓冲器中,同时把内部SRAM的起始地址设置为0x0(不同的CPU上电后的PC值不尽相同,对不同的CPU该值也不尽相同),然后把这段片内SRAM映射到nGCS0片选的空间,进而CPU开始从内部SRAM的0x0处开始取得第一条指令,该过程全部是硬件自动完成,不需要程序代码控制。
或许你有个疑问,为什么不能直接把nandflash映射到0x0地址处?非要经过内部SRAM缓冲?
答案是,nandflash根本没有地址线,没法直接映射,必须使用SRAM做一个载体,通过SRAM把剩余的nandflash代码(即剩余的uboot启动代码)复制到SDRAM中运行。
若想从nandflash启动,那么uboot最核心的代码必须放在前4k完成。这4k代码要完成ARM CPU的核心配置以及将剩余的代码拷贝到SDRAM中(若从norflash启动则没有4k这个大小的限制,但是还会在完成最主要的设置后进入SDRAM中运行)。
SD/eMMC卡启动
NoRFlash启动
ARM从norflash启动 若从norflash启动,则norflash直接被映射到内存的0x0地址处(就是nGCS0,这里就不需要片内SRAM来辅助了,所以片内SRAM的起始地址不变,还是0x40000000),然后cpu从0x00000000开始执行(也就是在Norfalsh中执行)。
需要说明的是,uboot代码段(.text段)起始位置必须是与上电后PC值一致,即编译uboot时,TEXT_BASE宏必须设置成0x0 ,反汇编uboot文件后,文本段第一条指令的地址也是0.
总结(针对S3C2440):
1、从norflash还是从nandflash启动,是由ARM的OM1和OM0引脚组合决定
2、不管从norflash还是nandflash启动,S3C2440上电后的pc值为0x0
3、如果某芯片上电后PC值不是0x0,假如是0x38ff0000,那么从norflash启动时,硬件就要自动将其映射到0x38ff0000地址处;如果从nandflash启动,那么硬件就要自动将nandflash中的前4K内容加载到0x38ff0000地址处。
启动的阶段分类:
启动阶段分为3个,bl0,bl1,bl2。
- BL0 运行BL0进行一些初始化工作,如关闭看门狗,初始化系统时钟、堆栈等;然后根据内置优先级,判断选定的存储设备的类型,初始化存储设备和它对应的控制器,从存储设备的特定区域读取4k/8k/32k的程序到SteppingStone中运行,被拷贝的这段代码称为Bootloader1(BL1)。
出厂的时候就固化在irom中一段代码,主要负责拷贝8kb的bl1到s5pv210的一个96kb大小内部sram(Internal SRAM)中运行。值得注意的是s5pv210的Internal SRAM支持的bl1的大小可以达到16kb,容量的扩增是为了适应bootloder变得越来复杂而做的。 2. BL1 BL1是用户自行编写的代码,必须简短精悍,运行与位置无关。BL1一般简单地重新初始化系统,开辟更加广阔的空间,并将更加完善的Bootloader2(BL2)拷贝到SDRAM中。
u-boot的前8kb代码(s5pv210也支持16kb大小),除了初始化系统时钟和一些基本的硬件外,主要负责完成代码的搬运工作(我设计成搬运bl1+bl2,而不仅仅是bl2),也就是将完整的u-boot代码(bl1+bl2)从nand flash或者mmcSD等的存储器中读取到内存中,然后跳转到内存中运行u-boot。 3. BL2 BL2的主要功能是把存储设备中的内核和文件系统加载到SDRAM中,从而启动系统。 完成全面的硬件初始化和加载OS到内存中,接着运行OS。
参考链接:bootloader
U-Boot to boot the kernel of the process can be divided into two stages, two stages of the following functions:
(1) The first stage from Hardware initialization Load U-Boot code to RAM space for the second stage Set stack Jump to the entrance of the second phase of the code
(2) The second phase of the function Initialize the hardware devices used at this stage Testing the system memory map Read the kernel from Flash to RAM, Set boot parameters for the kernel Call kernel
1. ARM SoC的启动过程:
RomBoot --> SPL --> u-boot --> Linux kernel --> file system --> start application
(RomBoot是固化在SoC内部的。u-boot SPL (second program loader))
u-boot实现了一个新功能,能在编译u-boot的同时生成SPL二进制文件。
2. SPL运行代码Go through
从u-boot-spl.lds链接文件可知,启动代码也是start.S。
(reset) <arch/arm/cpu/armv7/start.S> (b lowlevel_init: arch/arm/cpu/armv7/lowlevel_init.S) (b _main) --> <arch/arm/lib/crt0.S> (bl board_init_f) --> <arch/arm/lib/spl.c> (board_init_r) --> <common/spl/spl.c> (jump_to_image_no_args去启动u-boot) 到此SPL的生命周期结束。
简单来讲:SPL所做工作,一些硬件的初始化,然后读取u-boot,最后调转至u-boot。
参考链接:SPL
SPL功能
SPL是Secondary Program Loader的简称,之所以称作secondary,是相对于ROM code来说的。SPL是u-boot中独立的一个代码分支,由CONFIG_SPL_BUILD配置项控制,是为了在正常的u-boot image之外,提供一个独立的、小size的SPL image,通常用于那些SRAM比较小(或者其它限制)、无法直接装载并运行整个u-boot的平台。 如果使用了SPL功能,u-boot的启动流程通常是: ROM code加载SPL并运行; SPL进行必要的初始化之后,加载u-boot并运行; u-boot进行后续的操作。 因此,如果使用SPL功能,需要尽可能的减少SPL的代码量,以减小它的size。
#### SPL程序流程如下:
- 初始化ARM处理器
- 初始化串口控制台
- 配置时钟和最基础的分频
- 初始化SDRAM
- 配置引脚多路复用功能
- 启动设备初始化(即上面选择的启动设备)
- 加载完整的uboot程序并转交控制权
源码分析
Uboot.lds链接脚本分析
为什么要分析uboot链接脚本? 因为u-boot.lds决定了u-boot可执行映像的链接方式,以及各个段的装载地址(装载域)和执行地址(运行域),也就是说,Uboot.lds文件指定uboot.bin可执行文件放到ROM中的哪个地址、在运行时在RAM中运行的起始地址,具体内容涉及装载域和运行域的概念,这里不详述。
reset:
bl save_boot_params
/*
* disable interrupts (FIQ and IRQ), also set the cpu to SVC32 mode,
* except if in HYP mode already
*/
mrs r0, cpsr
and r1, r0, #0x1f @ mask mode bits
teq r1, #0x1a @ test for HYP mode
bicne r0, r0, #0x1f @ clear all mode bits
orrne r0, r0, #0x13 @ set SVC mode
orr r0, r0, #0xc0 @ disable FIQ and IRQ
msr cpsr,r0
在上电或者重启后,处理器取得第一条指令就是b reset,所以会直接跳转到reset函数处。reset首先是跳转到save_boot_params中,如下:
/*************************************************************************
*
* void save_boot_params(u32 r0, u32 r1, u32 r2, u32 r3)
* __attribute__((weak));
*
* Stack pointer is not yet initialized at this moment
* Don't save anything to stack even if compiled with -O0
*
*************************************************************************/
ENTRY(save_boot_params)
bx lr @ back to my caller
ENDPROC(save_boot_params)
.weak save_boot_params
_main
_main函数在arch/arm/lib/crt0.S中,mian函数的作用在注释中有详细的说明,我们分段来分析一下
ENTRY(_main)
/*
* Set up initial C runtime environment and call board_init_f(0).
*/
#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
ldr sp, =(CONFIG_SPL_STACK)
#else
ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)
#endif
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
sub sp, sp, #GD_SIZE /* allocate one GD above SP */
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
mov r9, sp /* GD is above SP */
mov r0, #0
bl board_init_f
首先将CONFIG_SYS_INIT_SP_ADDR定义的值加载到栈指针sp中,这个宏定义在配置头文件中指定。
这段代码是为board_init_f C函数调用提供环境,也就是栈指针sp初始化
8字节对齐,然后减掉GD_SIZE,这个宏定义是指的全局结构体gd的大小,是160字节在此处,这个结构体用来保存uboot一些全局信息,需要一块单独的内存。
最后将sp保存在r9寄存器中。因此r9寄存器中的地址就是gd结构体的首地址。
在后面所有code中如果要使用gd结构体,必须在文件中加入DECLARE_GLOBAL_DATA_PTR宏定义,定义如下: #define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r9")
gd结构体首地址就是r9中的值。 C语言函数栈是向下生长,这里sp为malloc空间顶端减去gd bd空间开始的,起初很纳闷,sp设在这里,以后的C函数调用不都会在malloc空间了吗,堆栈空间不就重合了嘛,不用急,看完board_init_f就明白了。 接着说_main上面一段代码,接着r0赋为0,也就是参数0为0,调用board_init_f
SPL阶段:
/* Allow the board to save important registers */
b save_boot_params // 里实际上就是保存一下函数的返回地址
|- disable interrupts
|- b cpu_init_cp15 ---> |- Invalidate L1 I/D // 无效 指令/数据 缓存
| |- disable MMU // 方便后续的内存访问, 不需要填写TLB
|
|- b cpu_init_crit ---> |- b lowlevel_init --> |- Setup a temporary stack // 设置SP指针, 估计马上要跑C代码了
| |- b s_init --> |- init_omap_revision // 获取芯片ID
| |- watchdog_init // 初始化看门狗
| |- force_emif_self_refresh(); // 初始化DDR
| |- setup_clocks_for_console // 开时钟等工作
|- b _main --> |- setup SP // Set up initial C runtime environment
|- board_init_f_mem // uboot设置内存
|- board_init_f // spl board_init_f
|- board_init_r // spl board_init_r
|- spl_load_image
|- spl_mmc_load_image
|- spl_mmc_do_fs_boot // 这里把uboot.img放到了 0x80800000上
|- jump_to_image_no_args // 跳到uboot上 (0x80800000)
uboot阶段和spl阶段类似, 只是后面有些不一样
...
|- b _main --> |- setup SP // Set up initial C runtime environment
|- relocate_code // 代码从定位
|- relocate_vectors // 设置中断向量
|- clear BSS
|- ldr pc, =board_init_r // 正式进入uboot地界
相关文件地址:
arch/arm/cpu/armv7/start.S
arch/arm/cpu/armv7/lowlevel_init.S
arch/arm/cpu/armv7/omap-common/hwinit-common.c
arch/arm/lib/crt0.S
arch/arm/lib/board.c
void main_loop (void)函数分析
arch/arm/lib/board.c
中调用main_loop函数
common/main.c的main_loop函数
Main_loop函数主要功能是处理环境变量,解析命令
install_auto_complete(); //安装自动补全的函数,分析如下 。
getenv(bootcmd)
bootdelay(自启动)
如果延时大于等于零,并且没有在延时过程中接收到按键,则引导内核。
自旋锁 and 原子锁
根文件系统挂载的相关命令
e2fsck -y /dev/nandd
e2fsck -y /dev/mmcblk0p7
mount -o rw,noatime,nodiratime,norelatime,noauto_da_alloc,barrier=0,data=ordered -t ext4 /dev/mmcblk0p7 /mnt
[ -x /mnt/init ] && exec switch_root /mnt /init
/sbin/getty -L ttyS0 115200 vt100 -n -l /bin/ash
switch_root命令
Bootloader
A bootloader is a computer program that loads an operating system (OS) or runtime environment for the computer after completion of the self-tests. —Wikipedia:booting
The bootloader configures the device to an initial known state and has a means to select where to start executing the kernel. It can allow you to make this selection, which allows the user, among other things, to load an alternative Linux kernel, or Windows. Because the bootloader is an essential component of the boot process, it is stored in non-volatile memory, such as flash memory.
Bootloaders are written by hardware vendors and are specialized for the hardware they run on.
For Android devices, the bootloader typically starts either Android or Recovery. Android bootloaders often have a basic interactive mode that can be triggered by holding the "volume down" button while the bootloader is executing.
内核一般是由bootloader来引导的,通过bootloader启动内核一般要传递三个参数,
-
第一个参数放在寄存器0中,一般都为0,r0 = 0;
-
第二个参数放在寄存器1中,是机器类型id,r1 = Machine Type Number;
-
第三个参数放在寄存器2中,是启动参数标记列表在ram中的起始基地址;
bootloader首先要将ramdisk(如果有)和内核拷贝到ram当中,然后可以通过c语言的模式启动内核:
void (*startkernel)(int zero, int arch, unsigned int params_addr) = (void(*)(int, int, unsigned int))KERNEL_RAM_BASE;
startkernel(0, ARCH_NUMBER, (unsigned int)kernel_params_start);
其中KERNEL_RAM_BASE为内核在ram中启动的地址,ARCH_NUMBER是Machine Type Number,kernel_params_start是参数在ram的偏移地址。
the boot loader should provide (as a minimum) the following:
- Setup and initialise the RAM.
- Initialise one serial port.
- Detect the machine type.
- Setup the kernel tagged list.
- Call the kernel image.
setenv bootargs console=ttyS0,115200 noinitrd disp.screen0_output_mode=EDID:1280x1024p60 init=/init root=/dev/mmcblk0p2 rootfstype=ext4 rootwait panic=10 ${extra}
设置uboot的bootargs启动参数,格式为 参数=值,不同参数使用空格分开,其中
- console=ttyS0,115200 含义为使用特定的串口ttyS0,波特率为 115200
- noinitrd 含义为不使用ramdisk(内存磁盘)
- init=/init 含义为内核启起来后,进入系统中运行的第一个脚本
- root=/dev/mmcblk0p2 含义为指定rootfs的位置为TF卡第二个分区
- rootfstype=ext4 含义为根文件系统类型
- rootwait 含义为等待设备/dev/mmcblk0p2就绪后才尝试挂载rootfs
- panic=10 传递内核参数,当遇到panic(内核严重错误)时等待10秒后重启
- screen0_output_mode 设置合适的屏幕显示分辨率