相关连接
1. UBOOT的编译:
a) 对于UBOOT的编译:cd /uboot/u-boot-2017.09; ./make. rv1126; 其中的make.sh是RK封装好的一个脚本。
b) Uboot也有dtb(设备树),但是可以控制dtb和uboot是否分开:
2. 芯片的内部有一段iROM,一般是芯片厂商写死的,用于引导启动程序。
所谓的iROM,即internal ROM,
IROM的流程:关中断和MMU,关D-cache,使能I-cache,失效TLB表,让CORE1进入空闲,初始化栈,初始化Zi,RW段,注册函数指针,判断启动方式,从对应启动设备中加载Bl1到SRAM中,然后对Bl1进行校验和检测,加密校验,解密处理,最后跳转到Bl1代码执行。
总结:iROM的作用是:iROM本身也称为Bl0
根据启动方式的不同(MMC或者NorFlash),从不同的存储介质中加载Bl1 到RAM
对加载的Bl1 进行检测校验
跳转到Bl1运行
3. Bl1的作用:运行在芯片内的SRAM上
a) 重新初始化IRQ,
b) 判断启动方式(MMC,Nor)从对应的存储中加载Bl2到RAM中,
c) 如果是安全模式,则对Bl2进行校验,
d) 跳转到Bl2 运行
4. Bl2的作用:运行在外扩SDRAM上(DDR),即uboot:
a) 重新初始化IRQ,设置时钟,初始化外扩RAM,
b) 判断启动设备,从对应的设备中读取OS或FW(firmware),
c) 跳转到OS或者FW中运行
5. 上述Blx为了执行正常,会分别占用一段内存,不同的公司不一样。上述的启动方式可选:nand mmc sd卡 USB等
--------------------·-------uboot流程概述-----------·-------------
1. Boot的基本功能:核心功能是引导操作系统,其余部分工作如下:
a) 初始化部分硬件,时钟 内存等。
b) 加载内核到内存上
c) 启动操作系统
d) 命令行功能,即uboot下的命令,setenv mmc write等。
2. 几种常见的bootloader:Uboot: 用于ARM,支持命令行(monitor)
3. Uboot-spl: 即Bl1 spl.bin: 我司RK生成的spl.bin大小为 220KB左右。
主要工作有:
初始化部分时钟(和SDRAM相关)
初始化SDRAM
从flash读取Bl2(uboot)到SDRAM
跳转到Bl2(uboot)地址。
4. uboot.bin Bl2:
a) 初始化硬件,包括时钟,内存等
b) 将kernel加载到内存上
c) 加载文件系统 dtb 到内存上。
d) 启动OS
e) 命令行:
i. Flash的操作
ii. Env的操作
iii. 启动相关的操作
-----------------------·-------RK平台 spl.bin的编译流程----------·--------
cd $(UBOOT_DIR); 【./make.sh $(UBOOT_CONFIG);】 · 【./make.sh spl-s】 .
通用的来说,spl的编译是uboot编译的一部分,但是和uboot是分开编译的,一般是先编译uboot,然后编译spl.bin.
注意在编译过程中会通过一些 u-boot-spl.lds 的连接文件连接生成bin
------------------------·-----spl的流程--------------------·-
Lds文件:./arch/arm/cpu/u-boot-spl.lds
Lds即linker script 链接脚本,链接脚本主要用于规定如何把输入文件内的section放入输出文件内, 并控制输出文件内各部分在程序地址空间内的布局. 连接器有个默认的内置连接脚本,
即:lds文件决定了各部分内容在最终文件里的位置,
ENTRY(xxx) 即 最终文件的开头,放在最前面的文件,即最先被运行的部分
通过查看得知,入口为“_start”
详见vectors.S 中定义的全局的符号 _start
1. lib/ vectors.S
2. armv7/start.S
cpu_init_cp15 接口:设置 CP15 寄存器(缓存、MMU、TLB)。 除非定义了 CONFIG_SYS_ICACHE_OFF,否则将打开 I-cache。
cpu_init_crit 接口:进行一些关键的初始化动作,也就是平台级和板级的初始化。其代码核心就是lowlevel_init:
* 检查一些复位状态
* 关闭看门狗
* 系统时钟的初始化 system_clock_init
* 内存DDR的初始化 mem_ctrl_asm_init
* 串口初始化(可选)
* Nand flash的初始化
_main 位于crt0.s中
3. arch/arm/lib/crt0.S 该脚本的内容其实仅有一个函数 _main,
所谓的crt0 即 c run time 即运行环境将由汇编向C语言转换。
main的主要目标是调用board_init_f进行先前的板级初始化动作,大致操作如下:
(1) 因为后面是C语言环境,首先是设置堆栈(汇编)
(2) 为GD分配空间:关于GD,也就是struct global_data,可以简单的理解为uboot的全局变量都放在了这里,比较重要
(3) 初始化GD空间:board_init_f_alloc_reserve:common/init/board_init.c
(4) 跳转到板级前期的初始化函数中 Bl board_init_f
注意,经过上述四步后,就从spl里面跳转到 uboot里面了。board_init_f 一般各家的实现都不同.
-----------------------·------uboot启动流程--------------·-----------
前面讲到,SPL执行的末尾会调用 board_init_f 自此转入uboot阶段。
该函数位 u-boot-2017.09/common/board_f.c
1. CPU刚上电。要设置很多状态: cpu状态、中断状态、MMU状态等。其次,初始化硬件资源。最后进入命令行状态,等待命令输入。
a) arch级的初始化
b) 关闭中断,设置svc模式, 禁用MMU、TLB
c) 关键寄存器的设置,包括时钟、看门狗的寄存器
d) 堆栈环境的设置
e) 代码重定向之前的板级初始化,包括串口、定时器、环境变量、I2C\SPI等初始化
f) 进行代码重定向代码重定向之后的板级初始化,包括板级代码中定义的初始化操作、emmc、nand flash、网络、中断等等的初始化。
g) 进入命令行状态,等待终端输入命令以及对命令进行处理
2. spl已对arch级进行了初始化,为什么uboot还要对arch再初始化?
回答:spl对启动uboot来说并不是必须的,某些情况下可能没有spl而直接运行boot,因此uboot不会考虑spl是否已对arch进行了初始化,uboot会完整的初始化一遍,以保证cpu处于所需状态下。
3. Spl运行结束后,在_main里面会运行两个重要的函数 board_init_f 和 board_init_r
4. Board_init_f 的流程:
a) setup_mon_len, //计算整个镜像的长度
b) env_init 环境变量初始化:
c) init_baud_rate 波特率初始化
d) serial_init, 串口初始化
e) console_init_f, 控制台初始化
f) init_func_i2c, //IIC初始化
g) init_func_spi, //SPI始化
h) reloc_fdt, //fdt相关
5. board_init_r的实现:
a) initr_reloc, // global data 中一些关于relocate的标识的设置
b) initr_malloc, malloc内存池的设置
c) interrupt_init, //中断初始化
d) board_init, /* Setup chipselect不同厂家的各自的初始化函数 */
e) board_late_init
f) initr_serial, //串口初始化
g) initr_flash, // // 如果使用emmc,那么这里需要对nand进行初始化
h) initr_nand, //如果使用nand flash,那么这里需要对nand进行初始
i) initr_env, //初始化环境变量
j) timer_init, /* initialize timer *///定时器初始化
k) run_main_loop, //进死循环 ,在死循环里面处理终端的命令
-------------------------------·------uboot relocation begin-----------------·--------------------
UBOOT即Bl2,需要拷贝到DDR上运行。拷贝Bl2 可以通过SPL来运行,但是有可能没有SPL。所以UBOOT自身也要支持拷贝到DDR。
即UBOOT的relocation实际就是将自身拷贝到DDR上的动作。
问题:既然boot会将自身拷贝到DDR上,相当于执行地址会变化,要求boot在r
elocate前后都可以运行,要求boot使用“位置无关代码”技术。
1. 位置无关代码技术:
a) 无论代码被加载到内存的什么地址,均可以运行。即加载地址和连接地址不同时,CPU可以通过相对寻址获得正确的指令。
b) 位置无关代码的生成:
i. 编译时 gcc的 -fpic 或 mword-relocations 选项
其中fPIC选项在编译应用层的时候也是常用的。
ii. 连接时 通过ld的 -fpie 选项。
iii. ARM实际用 “-mword-relocations -fno-pic” 其中-fno-pic 意为 不使用PIC
iv. ARM的连接阶段使用原生的LD的-fpie 选项
2. 位置无关的原理:
a) uboot对自身relocate后,全局变量的地址会变化,因此,在relocate过程中要对全局变量的label中的地址进行修改,uboot将label的地址维护在.rel.dyn段中,然后再统一对.rel.dyn段指向的label进行修改。
3. relocation的过程:
a) 把自己本身relocate到ddr的顶端地址使之不会和kernel的冲突。并非直接把uboot放到ddr的顶端,而有一定布局,预留空间给其他需要的功能使用。
4. 从高地址到低地址布局如下(并非所有区域都需要,可据宏定义确定)
prom页表区域 8192byte
logbuffer LOGBUFF_RESERVE
pram区域 CONFIG_PRAM<<10
round_4k 用于4kb对齐
mmu页表区域 PGTABlE_SIZE
video buffer 不关心。但是是确定的。不会随着代码变化
lcd buffer 不关心。但是是确定的。不会随着代码变化
trace buffer CONFIG_TRACE_BUFFER_SIZE
uboot代码区域 d->mon_len,并且对齐4KB对齐
malloc内存池 TOTAL_MALLOC_LEN
Board Info区域 sizeof(bd_t)
新global_data区域 sizeof(gd_t)
fdt区域 gd->fdt_size
对齐 16b对齐
堆栈区域 无限制
5. Relocate代码流程:
* 对relocate进行空间规划
* 计算uboot代码空间到relocation的位置的偏移
* relocate旧的global_data到新的global_data的空间上
----- 旧空间--relocate_code ----新空间---
* relocate旧的uboot代码空间到新的空间上去
* 修改relocate之后全局变量的label。(不懂的话参考第二节)
* relocate中断向量表
综述:relocation的实现: arch/arm/lib/crt0.S 文件,详细参考链接,注意relocate_code之后,代码就在新的boot空间上运行了。
6. 具体代码分析:relocation功能的实现在init_sequence_f的后半部分,分析如下
如上图所示,所谓的规划,起始就是对各个分区的指针进行重定向。
7. 中断向量表: u-boot-2017.09/arch/arm/lib/vectors.S 的 relocation:
a) arm的异常中断向量表需要复制到0x00000000处或者0xFFFF0000处。
b) 需把新的异常中断向量表复制到0x00000000处或者0xFFFF0000处。
这部分操作就是在relocate_vectors中进行。用汇编实现的。
=中断向量表relocate结束后,relocation就算正式结束了=
----------------·---------UBOOT启动kernel----------------------·----
1. Bootm命令:是UBOOT下的一个命令
RK的: arm-linux-gnueabihf-objdump 经过尝试发现, 应用层的可以dump,但是内核和boot仅能dump .o文件。
------------------------·------------------map相关 begin -------------------·----
经过测试,编译完uboot后,会在u-boot-2017.09 目录下生成一个u-boot.map文件,
经过测试,发现uboot uimage均会生成一个 .map 文件,里面是所有的sym的集合,
经过实测好用,可以定位到目录,并非准确的文件
1. 对map文件的用处的猜想:
通过map定位函数所在的源文件
2. Kernel下 有用的map文件为 linux4.1.9/System.map 文件,该文件区别于boot的map文件,内部仅是符号的集合,并没有具体的文件的目录。具体为按链接地址从小到大的顺序列出所有的符号:
3. 关于map文件的另一种解释:所有的函数或者变量都会对应一个地址,内核使用函数时,比如malloc时,是去找malloc对应的地址。(此外gdb也是利用符号表,将地址转换为具体的接口名称),其实符号表可以做到符号和地址的相互转换。
a) 当编译新的镜像时,原来的 .map 或 .sym 就不能用了,需要用新产生的替换旧的。
b) 关于oops:内核oops时,会使用klogd解析map文件和ko的符号表,以返回给用户问题大概的出处。
扩展:关于内核的panic 和 oops
4. 相对于system.map的仅符号表,u-boot.map的内容更丰富:
a) Uboot.map中的地址是运行时的地址,即uboot运行起来后打印函数名出现的地址与map中是相同的。
-------------------------------·-----------map相关 end ----------------·---------------------------
-------------------------------·----------UBOOT下fdt的介绍---------------·---------------------------
UBOOT中对FDT的操作:
1. 获取DTB的地址,并且验证DTB的合法性:
源码位于 u-boot-2017.09/lib/fdtdec.c文件,主要思路如下:
Fdt与UBOOT有两种方式:CONFIG_OF_EMBED CONFIG_OF_SEPARATE,
CONFIG_OFEMBED:dtb集成到UBOOT的固定的字段中,通过该固定字段获取UBOOT
CONFIG_OF_SEPARATE:dtb追加到UBOOT的末尾,通过UBOOT的end作为fdt的起始位置。
获取到fdt后,通过判断前面几个字节是否为“magic“来判断fdt的合法性。
2. 为dtb分配新的内存空间:reserve_fdt: common/board_f.c
主要思路:如果是CONFIG_OF_EMBED即dtb在UBOOT中,则不需要额外为relocate分配空间。但是如果是CONFIG_OF_SEPARATE,则需要先获取fdt的size,然后分配size大小的内存。
3. Relocate dtb: common/board_f.c 接口 reloc_fdt
如果是CONFIG_OF_EMBED,则不处理(dtb在uboot中,不单独reloc)
如果是CONFIG_OF_SEPARATE,则拷贝就的dtb到新的dtb地址,然后更新一下gd(global_data)的相关字段。具体是 gd->uffdt_Blob(与配置相关)
Uboot会通过gd->fdt_Blob提供fdt的API:
用节点在dtb中偏移地址表示该节点。也就是节点变量node中,存放的是节点的偏移地址
lib/fdtdec.c文件中:
1. fdt_path_offset
int fdt_path_offset(const void *fdt, const char *path)
eg:node = fdt_path_offset(gd->fdt_Blob, “/aliases”);
功能:获得dtb下某个节点的路径path的偏移。这个偏移就代表了这个节点。
2. fdt_getprop
const void *fdt_getprop(const void *fdt, int nodeoffset, const char *name, int *lenp)
eg: mac = fdt_getprop(gd->fdt_Blob, node, “mac-address”, &len);
功能:获得节点node的某个字符串属性值。
3. fdtdec_get_int_array、fdtdec_get_byte_array
int fdtdec_get_int_array(const void *Blob, int node, const char *prop_name, u32 *array, int count)
eg: ret = fdtdec_get_int_array(Blob, node, “interrupts”, cell, ARRAY_SIZE(cell))
功能:获得节点node的某个整形数组属性值。
4. fdtdec_get_addr
fdt_addr_t fdtdec_get_addr(const void *Blob, int node, const char *prop_name)
eg:fdtdec_get_addr(Blob, node, “reg”);
功能:获得节点node的地址属性值。
5. fdtdec_get_config_int、fdtdec_get_config_bool、fdtdec_get_config_string
功能:获得config节点下的整形属性、bool属性、字符串等等。
6. fdtdec_get_chosen_node
int fdtdec_get_chosen_node(const void *Blob, const char *name)
功能:获得chosen下的name节点的偏移
7. fdtdec_get_chosen_prop
const char *fdtdec_get_chosen_prop(const void *Blob, const char *name)
功能:获得chosen下name属性的值
lib/fdtdec_common.c中
1. fdtdec_get_int
int fdtdec_get_int(const void *Blob, int node, const char *prop_name, int default_val)
eg: bus->udelay = fdtdec_get_int(Blob, node, “i2c-gpio,delay-us”, DEFAULT_UDELAY);
功能:获得节点node的某个整形属性值。
2. fdtdec_get_uint
功能:获得节点node的某个无符号整形属性值。
-------------------------------·----------UBOOT下bootm---------------·------------------------
Bootm是uboot下的一个命令,用于启动OS,通过从镜像文件获取ARCH,OS类型,镜像类型及压缩方式,在内存中的加载地址,镜像文件的入口地址等,解压镜像,加载到指定地址,跳转运行OS。