03. Uboot启动流程详解

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/chenchengwudi/article/details/80874629

1. start.S解析

1)uboot入口分析

要分析uboot的流程首先要找到uboot的入口函数,从x210开发板的链接器脚本可以获得该信息


根据ENTRY(_start)可知,uboot的入口函数为start;再根据.text段的链接顺序,可知start函数位于cpu/s5pc11x/start.S

2)头文件包含

在start.S的起始处包含了如下4个头文件,


A. <config.h>
include/config.h由mkconfig脚本生成,其中包含了板级配置头文件include/configs/x210_sd.h


B. <version.h>
include/version.h包含了include/version_autogenerated.h,该头文件由Makefile在编译过程中生成,其中定义了标识uboot版本的宏。后续源代码中使用的版本信息,即由此而来。




C. <asm/proc/domain.h>
由于x210的uboot定义了CONFIG_ENABLE_MMU宏,所以会包含该文件。而include/asm/proc目录通过软链接会指向include/asm-arm/proc-armv

D. <regs.h>
include/regs.h文件是include/s5pc110.h的软链接,而s5pc110.h中定义了S5PV210芯片的片内外设地址及操作用宏

说明:上述头文件的包含就体现了uboot的跨平台移植性,通过配置编译脚本和软链接,使得源代码中能够以统一的方式包含头文件,而实际指向却因平台而异

3)启动代码的16B头部信息

为配合S5PV210的iROM启动流程,在uboot的起始位置预留了16B用于存放BL1的头部信息(sd_fusing.sh脚本会截取uboot.bin的前8KB作为BL1)。
根据头部信息的字段要求,此处的0x2000为BL1长度,即8KB。




4)构建异常向量表



异常向量表的功能在中断相关笔记中已有详细说明,此处不再赘述。异常向量表后的 .balignl 16,0xdeadbeef 用于实现地址对齐
此处的含义是让当前当前地址16字节对齐,如果不对齐,使用0xdeadbeef填充(deedbeef正好在十六进制数标识范围内~~)

说明1:进行内存对齐的2个原因
① 提高读写效率
② 硬件特殊要求

说明2:需要注意的是,在x210 uboot代码中并没有将异常向量表的地址写入CP15的c12寄存器,其实在整个x210 uboot中并没有使用中断

5)重要的变量定义



A. _TEXT_BASE
uboot的链接地址,该值首先由x210_sd_config配置项写入board/samsung/x210/config.mk配置文件,然后在顶层目录的config.mk中会解析该文件,然后在编译选项中添加-DTEXT_BASE,从而可以在源代码中获得链接地址的值(0xc3e00000)

B. _TEXT_PHY_BASE
#define MEMORY_BASE_ADDRESS 0x30000000
#define CFG_PHY_UBOOT_BASE MEMORY_BASE_ADDRESS + 0x3e00000

所以_TEXT_PHY_BASE的值为0x33e00000,即uboot的物理地址(正确的理解:链接地址VA对应PA)

C. _armboot_start
该变量实际是_start的链接地址,由于起始处预留头部信息的原因,_start的链接地址为0xc3e00010


D. _bss_start & _bss_end
这两个变量的值源自链接器脚本,后续用于清bss段


下面通过反汇编验证上述分析:

注:该方法可用于验证C语言对链接器脚本变量(e.g. _bss_start)的使用

6)设置SVC模式





由于直接将0xd3写入CPSR的低8位,所以此处在进入SVC模式的同时,还将CPU设置为ARM状态,并关闭 I & F中断(CPU端关中断)

7)设置cache & MMU

说明:对存储系统的设置都是通过操作cp15寄存器的实现

A. bl disable_l2cache



该函数将控制寄存器(c1)的bit 1清零,即不再对数据的地址对齐进行检查,该操作和cache也没啥关系啊~~




B. bl set_l2cache_auxctrl_cycle



该函数将L2 Cache辅助控制寄存器各位均置为0


C. bl enable_l2cache


调用该函数又将控制寄存器的bit 1置为1,即使能数据对齐检查,当数据不对齐时将触发异常

D. Invalidate L1 I/D



mcr p15, 0, r0, c8, c7, 0用于使I / D TLB均失效
mcr p15, 0, r0, c7, c5, 0用于使I cache失效

E. disable MMU and caches


此段流程对控制寄存器进行如下操作,
bit 13 --> 0:使用一般模式的异常向量表地址(即默认地址为0x0,也可通过c12配置)
bit [2:0] --> 0b000:关闭MMU / 关闭地址对齐检查 / 关闭D-cache
bit 1 --> 1:使能地址对齐检查
bit 11 --> 1:使能program flow prediction

说明:从整个cache和MMU的设置流程分析,控制寄存器A位的设置被反复进行,而且有些函数名与功能并不相符,应该是移植时造成的残留问题

8)识别 & 保存启动设备信息


代码首先从0xE0000004寄存器中读取启动设备的信息,需要注意的是,手册中并没有描述该寄存器的内容(可见某些接近底层的移植,只能由原厂进行,居然还TM留了一手~~)





在经过一系列比较后,会将启动设备的信息保存在INFORM3寄存器中,但是芯片手册中并没有该寄存器的详细说明


9)调用lowlevel_init

A. 栈的设置


在调用lowlevel_init之前,需要先设置栈(此时栈在iRAM中)。虽然lowlevel_init函数也是用汇编语言完成,但是由于他并非叶子函数,所以在lowlevel_init调用其他函数之前需要将当前的lr入栈,以便lowlevel_init函数返回


说明:该栈并没有设置在iRAM规划的SVC stack区,而是设置在iRAM规划的RW / ZI区域(已初始化变量 / 未初始化变量)及堆区

B. 检查复位状态



说明:目前稍微复杂的CPU均支持多种复位状态(e.g. 上电启动 / 睡眠唤醒),此处检查复位状态的意义在于,如果是睡眠唤醒,很多上电启动需要进行的步骤此时 必须 略过(e.g. 时钟设置 / DDR初始化)


C. 释放IO的保持状态



实现的方式就是将MISC寄存器的bit 31置为1


D. 关闭看门狗



E. SROM相关GPIO管脚设置





此处设置了GPJ 1 & GPJ 4的工作模式及上下拉模式,其中,
GPJ 1:
将GPJ 1[0] ~ GPJ 1[5]的模式设置为SROM_ADDR_16to22[0] ~ [5]
将上下拉状态设置为Reserved(这就比较尴尬了~~)




GPJ 4:
将GPJ 4[4]的模式设置为SROM_ADDR_16to22[6]
将上下拉状态也设置为Reserved


最后将SROM0的位宽设置为16 bit


说明:由x210核心板原理图可知,此处的SROM用于连接camera


F. 供电锁存


由于x210使用软开关设计,如果不进行供电锁存,就需要长按power键进行供电。关于供电锁存的具体原理及操作可见体系结构相关笔记

G. 初始化时钟 & DDR



① 判断当前代码运行位置
在进行时钟和DDR初始化之前,首先要判断当前代码的运行位置。如果已经在内存中运行,则说明此时是从睡眠中被唤醒,无需进行时钟和内存初始化( 根据实际经验,如果此时重复初始化内存,内存中的数据会丢失,导致程序无法正确运行 )
判断的方式还是比较运行地址与链接地址的关系。经过掩码0xff000fff的处理后,
r2 = 0x 00 e00 000
如果此时代码在iRAM中运行,
r1 = 0x 00 200 000 (iRAM起始地址位0xd0020010)

注意:此处掩码将地址的低12位清零,即将比较的范围限制在4KB范围内,如果代码运行到此处已超过4KB,判断就会失效(e.g. uboot已经运行在内存中,但是超过4KB,按位与后就会出现如下现象,r2 = 0x 00 e00 000 ,r1 = 0x 00 e01 000 ,此时比较会失败,导致内存被错误初始化)
个人认为比较好的判断方法是比较_start标号的运行地址与链接地址

② 时钟初始化
x210时钟配置时的分频系数可配置,目前选择的为High performance方案,分频结果与之前裸机代码相同

最终配置的各时钟如下,
ARMCLK = 1GHz
SCLKA2M = 200MHz
HCLK_MSYS = 200MHz
PCKL_MSYS = 100MHz
HCLK_DSYS = 166MHz
PCLK_DSYS = 83MHz
HCLK_PSYS = 133MHz
PCLK_PSY = 66MHz
CAM0 = 24MHz
CAM1 = 24MHz

说明1:在x210的代码中,设置并使能各PLL后会检查MPLL是否成功锁存,如果锁存失败将重新设置一次时钟


说明2:根据x210官方移植的uboot,移植uboot时需要根据具体的硬件需求设置不同的时钟,比如此处就单独设置了camera时钟

③ DDR初始化
x210 uboot中的DDR初始化的流程和参数与裸机代码中基本一致,只有一处不同,
裸机代码中:
#define DMC0_MEMCONFIG_0 0x 20 F01323 // MemConfig0 256MB config, 8 banks,Mapping Method[12:15]0:linear, 1:linterleaved, 2:Mixed

uboot中:
#define DMC0_MEMCONFIG_0 0x 30 F01313 // MemConfig0 256MB config, 8 banks,Mapping Method[12:15]0:linear, 1:linterleaved, 2:Mixed

该设置造成的差别为,DMC0可以容纳512MB内存,虽然都是使用了其中的256MB,但是,
在裸机代码中,DMC0的256MB内存范围:0x20000000 ~ 0x2fffffff
在uboot中,DMC0的256MB内存范围:0x30000000 ~ 0x3fffffff


说明1: 最终uboot使用的物理内存范围 如下,
DMC0:0x30000000 - 0x3fffffff
DMC1:0x40000000 - 0x4fffffff

说明2:注意内存配置与时钟配置的关联性,一般先配置时钟,因为在内存配置中会使用到时钟配置的结果

H. 初始化串口



说明1:首先将UART相关的GPIO均配置为UART相关工作模式,此处同时设置了UART 0 ~ 3

说明2:作为uboot控制台的是UART2,格式为8n1,波特率为115200


说明3:串口配置完成后向串口输出字符'O'以验证是否配置成功

I. lowlevel_init返回



说明1:在lowlevel_init函数返回之前,向串口打印字符'K'。根据上文,完成后会打在串口初始化印字符'O',所以在uboot启动过程中会打印出"OK"。这点与实际开机过程是吻合的


说明2:调用pop {pc}实现函数返回,就是将在lowlevel_init函数起始处入栈的lr弹出赋值给pc

10)代码重定位

说明: 此处代码重定位的目的,是将BL2(内容为整个uboot.bin)从iNand或SD卡拷贝到uboot.bin的链接地址处

A. 重新设置栈



说明1:由于在lowlevel_init函数中已经完成DDR初始化,所以此处将 栈设置在内存中 ,而且是 以物理地址设置栈 (sp = 0x33dffff4)。
说明2:此处设置栈是因为后续要调用movi_bl2_copy函数,而该函数用C语言实现
说明3:该栈的位置在uboot.bin链接地址之前,而ARM中默认使用满减栈,所以后续的BL2拷贝不会影响该栈的工作(理解栈的设置需要注意2点:① 设置栈的目的;② 栈的位置是否合理)

注意:此处只是设置了栈就进入了C函数movi_bl2_copy,并没有清bss段,这是因为该函数中没有使用未初始化或初始化为0的全局变量

B. 判断当前代码运行位置



说明:此处的判断方法与lowlevel_init函数一致,如果当前代码已经在内存中运行(对应睡眠唤醒状态),则无需进行代码重定位

C. 根据启动设备选择拷贝函数



说明1:如果从iRAM Global Variable区域获取当前的启动设备是SD/MMC channel 2,即此时是SD卡启动(second boot),那么跳转到mmcsd_boot标号运行


说明2:此处从INFORM 3寄存器读出启动设备信息,而该信息正是在start.S的起始部分写入的

D. movi_bl2_copy函数分析



说明1:此处代码重定位使用的是iROM中提供的拷贝函数,根据当前的SD/MMC channel从iNand或SD卡拷贝BL2


说明2:拷贝函数参数解析
① channel:拷贝源的SD/MMC channel,通过iROM的Global Variable确定

② start_block:拷贝源要拷贝的起始block数(1 block = 512B)

每个block的大小 = 512B:

eFUSE_SIZE占用block数 = 512 / 512 = 1:


BL1占用block数 = (8 * 1024) / 512 = 16:// 此处可见BL1为8KB


ENV占用block数 = 16384 / 512 = 32:



说明:所以MOVI_BL2_POS(即BL2的起始block数)为 1 + 16 + 32 = 49 ,这也就和我们烧写SD启动卡时的布局相匹配了~~

③ block_size:要拷贝的block数


说明:MOVI_BL2_BLKCNT可以理解为当前支持的BL2最大长度,此处为512KB,即1024个block

④ trg:拷贝到内存的目的地址


说明:拷贝的目的地址应该是uboot.bin的链接地址,此处需要注意的是,编译uboot.bin时使用的链接地址是虚拟地址0xc3e00000。但是在拷贝时尚未开启MMU,所以仍要使用与链接地址对应的物理地址

⑤ init:拷贝时是否初始化SD卡

小结:SD/MMC中的uboot布局


说明1:目前x210平台的iNand & SD均按此布局,根据之前笔记介绍,无论是iNand还是SD卡,在分区时均空出了前10MB空间,因此后续的分区操作不会破坏启动部分

说明2:MBR和BL1的位置是固定不变的
操作系统会从0扇区读取分区表,所以MBR的位置不能随意指定
S5PV210的iROM会从1扇区读取BL1,所以BL1的位置也不能随意指定
注意:这也就限制了S5PV210平台无法使用GPT分区表,如果要增加分区超过4个,只能使用扩展分区 + 逻辑分区

说明3:ENV和BL2的位置是可配置的,只是修改配置后实现代码重定位的拷贝函数必须做相应的修改

11)使能MMU



关于该部分使能MMU的详细分析,可见体系结构中MMU编程笔记。关键是一旦使能MMU,软件层面就不再有物理地址,只有虚拟地址
需要注意的是,写入CP15 c2寄存器的Translation Table起始地址是物理地址!!!

12)跳转进入第二阶段

A. 重新设置栈



此处设置栈是因为后续要进入uboot的第二阶段,即C语言阶段运行,需要注意的是,
① 由于已经开启了MMU,此次栈的设置使用了虚拟地址
② 此处sp = 0xc3e00000 + (2 * 1024 * 1024) - 0x1000 = 0xc3fff000,该位置距离uboot.bin链接地址约2MB,考虑到目前配置最大支持512KB的BL2,因此该栈的可用空间约1.5MB(这个理解是错误的!!!从0xc3e00000开始的2MB空间内,实际规划了多个内存区块,栈实际只用了512KB,内存布局的具体划分详见下文中的start_armboot函数分析)

小结:uboot中共设置了3次栈,小结如下
① 调用lowlevel_init函数前,iRAM中,物理地址
② 调用movi_bl2_copy函数前,DDR中,物理地址
③ 调用start_armboot函数前,DDR中,虚拟地址

B. 清bss段



说明:清bss段是为了可以安全使用未初始化或初始化为0的全局 & 静态局部变量。需要进行清空操作是因为bss段中仅包含句柄,并不包含实际数据,所以在进入C语言阶段前必须将bss段对应的内存清空

C. 长跳转到start_armboot函数



说明:该长跳转直接跳转到DDR中的start_armboot标号处继续执行,BL2中与BL1重叠的部分将被跳过,不会执行(虽然我们也将其拷贝到DDR中)

至此,uboot启动的第一阶段就结束了,后续的启动流程将由C语言完成~~

2. start_armboot函数解析

1)start_armboot概述

① start_armboot函数构成了uboot启动的第二阶段
② uboot启动第一阶段主要初始化了CPU和SoC片内外设控制器(e.g. 时钟、串口),然后初始化DDR并完成代码重定位,这些都是为运行第二阶段做准备
③ uboot启动第二阶段就是要初始化剩余未被初始化的硬件,主要是SoC外部的硬件(e.g. iNand、网卡芯片);还要建立uboot本身的相关组件(e.g. uboot命令和环境变量)
最后进入uboot命令行或直接启动Linux内核

2)global data & uboot内存布局

A. gd指针赋值



此处给gd指针与gd->bd指针赋值,并且将这2个指针指向的结构体清空。问题是这2个指针是如何定义的呢~~

B. global data结构体分析

① gd声明方式


在board.c头文件包含之后,所有声明之前,调用了DECLARE_GLOBAL_DATA_PTR宏,该宏在include/asm-arm/global_data.h文件中定义,具体如下,

实际上此处声明了一个gd_t类型的指针变量,
使用volatile修饰:每次到内存中读取,不从cache中读取
使用register修改:建议使用寄存器存储该变量(仅是对编译器的建议)
asm("r8"):如果将该变量存储在寄存器中,则使用r8寄存器

② gd_t结构体分析



说明1:设置global_data类型的目的就是将uboot要用到的全局变量用结构体管理起来,其中存储了全局的系统初始化参数。在uboot中使用该类型需要注意2点,
① 要保持该结构体尽可能小,即设置尽可能少的全局变量
② 在板级配置头文件中定义的CFG_GBL_DATA_SIZE宏值必须大于sizeof(gd_t),目前x210定义的宏值为128

CFG_GBL_DATA_SIZE宏在多处内存分配的场景会使用到,用于为global data预留空间(额。。。冲突没想象中严重)

说明2:struct global_data字段解析
a. bd_t *bd //指向bd_info结构体
b. unsigned long flags
全局标志位,x210 uboot中可用标志位如下,明显以位或方式使用

c. unsigned long baudrate //串口波特率
d. unsigned long have_console //是否有控制台
e. unsigned long reloc_off
重定位偏移量,即uboot.bin的下载地址与链接地址位置之差,一般为0(即将uboot.bin下载到链接地址处)

f. unsigned long env_addr // 环境变量内容的地址
g. unsigned long env_valid //环境变量CRC校验标志
h. unsigned long fb_base //帧缓冲区基地址
i. void **jt //jump table,最终为一个函数指针数组

说明3:struct bd_info字段解析(板级信息结构体)
a. int bi_baudrate //串口波特率
b. unsigned long bi_ip_addr //IP地址
c. unsigned char bi_enetaddr[6] //MAC地址
d. struct environment_s *bi_env //指向环境变量结构体
e. ulong bi_arch_number //开发板机器码
f. ulong bi_boot_params //uboot给Linux内核传参的内存地址
g.
struct
{
ulong start;
ulong size;
}bi_dram[CONFIG_NR_DRAM_BANKS]
内存配置参数,每个数组成员描述一个内存bank,包含 物理 起始地址和内存大小

C. uboot内存布局分析


#define CFG_UBOOT_BASE 0xc3e00000
#define CFG_UBOOT_SIZE (2 * 1024 * 1024) //2MB
#define CFG_MALLOC_LEN (CFG_ENV_SIZE + 896 * 1024) //16KB + 896KB
#define CFG_ENV_SIZE 0x4000 //16KB
#define CFG_STATCK_SIZE 512 * 1024 //512KB



说明1:CFG_MALLOC_LEN中包含了ENV区的16KB空间,内存布局中ENV的16KB与SD卡/iNand的uboot布局是匹配的

说明2:强制类型转换对指针运算的影响
代码中在给gd->bd指针赋值时,先将gd强制转换为char *类型,此处增补一例说明强制类型转换对指针运算的影响(之前的理解有误)



D. 其余主题

① __asm__ __volatile__("": : :"memory")的作用
使用一条空的内嵌汇编,但是将破坏部分指定为memory,这将强制GCC编译器假设所有内存单元均被汇编指令修改,这样CPU中的register和cache中已缓存的内存单元中的数据将作废,CPU将不得不在需要的时候重新读取内存中的数据
这就阻止了CPU使用register和cache中的数据去优化指令,从而避免访问内存

② monitor_flash_len = _bss_start - _armboot_start
_armboot_start为_start标号的链接地址,此处为0xc3e00000 + 0x10(预留的头部信息)


_bss_start为bss段的链接起始地址,而bss段又是整个ELF文件中的最后一个段


3)循环调用初始化函数


uboot中定义了函数类型init_fnc_t,并据此定义了全局的函数指针数组init_sequence,在其中管理了需要在uboot启动第二阶段调用的初始化函数。注意该数组以NULL元素结尾,这将作为遍历数组的判断条件(同时也有利于数组的扩展)
个人:不理解此处为什么是定义函数类型而不是函数指针类型
typedef int (*init_fnc_t)(void)



start_armboot中将会循环调用init_sequence数组中的初始化函数,如果某个函数返回非零值(即执行失败),uboot启动将会hang住

4)初始化函数数组分析

说明:此处仅分析x210 uboot会执行到的函数

A. cpu_init


说明:由于x210的uboot没有使能中断,所以cpu_init相当于空函数。但是需要注意的是,如果使能了中断,中断的栈将设置在_arm_boot_start,即_start标号之前,并不会影响上文中分析的内存布局
但是需要注意中断栈不能覆盖uboot.bin的有效内容(虽然存在这种风险,但是冲突并不严重,毕竟每个中断栈只有4KB)

B. board_init


说明1:gd指针声明
对全局变量的另一种使用方式是在一个源文件中定义,并在头文件中声明,然后在需要使用的源文件中包含该头文件;uboot则采用了通过宏定义在使用时声明变量的方式。
两者相比较,传统方式声明的全局变量将具有文件作用域;uboot方式声明的变量则依据调用点的不同,具有文件或局部作用域

说明2:dm9000_pre_init

该函数完成如下2个任务,
① 设置SROM BANK1的属性(SROM_BW寄存器)与时钟(SROM_BC1寄存器)

② 设置SROM BANK1的CS管脚功能

注意:此处只涉及dm9000网卡与SoC连接用到SROM BANK的初始化,并不涉及dm9000驱动本身。实际在uboot的移植过程中,dm9000驱动是不变的,需要配置的是板级硬件相关的参数

说明3:开发板机器码设置
由于嵌入式设备的高度定制化,其硬件和软件往往是不通用的。因此给每个开发板设置一个唯一编号,即开发板机器码。
此处将开发板机器码存储在gd->bd->bi_arch_number字段,在启动内核时,这些参数会被传递给Linux内核,内核在启动过程中会对比该机器码和内核本身维护的机器码,只有二者匹配时才能顺利启动

注意:从理论上说,开发板机器码不能随便指定,应该提交给开源社区审定。但是 在实际使用时,只要uboot与Linux内核中的机器码匹配即可 ~~

说明4:传递启动参数的地址
uboot向Linux内核传参过程:uboot将要传递的参数(bootargs字符串)存放到指定内存处,然后启动内核。uboot在启动内核时通过r0、r1、r2寄存器传递参数,其中一个就是bi_boot_params,Linux内核根据bi_boot_params就可以找到uboot传递给他的参数
此处将bi_boot_params字段设置为0x30000100,使用的是物理地址(因为启动Linux内核时需要关闭MMU)。

C. interrupt_init


说明1:与中断无关
虽然函数名是interrupt_init,但是本函数与中断并木有什么关系,从实现上看,该函数初始化了PWM模块的Timer 4,定时值为10ms循环工作,且未使能Timer 4中断
注意:PWM模块的Timer 4没有外部引脚,也没有TCMPB寄存器,所以一般作为内部定时器使用。
由于此处没有使用中断,在使用中CPU需要通过轮询的方式实现定时

说明2:将模块相关寄存器组织为结构体
S5PC11X_TIMERS结构体根据地址先后顺序,将PWM的寄存器组织起来使用

说明3:时钟值获取函数
该函数中调用get_PCLK函数获取PCLK_PSYS的时钟值,uboot根据时钟体系结构设置了一系列获取时钟值的函数。由于时钟体系与SoC高度相关,x210 uboot关于时钟获取的函数均在cpu/s5pc11x/s5pc110/speed.c中

以get_PCLK函数为例,时钟值是通过 读取寄存器值并依据时钟体系结构计算 得到,因此这些函数的适用性非常强

特别注意: 此处在设置定时器计数值时是有问题的!!!
从 get_PCLK 函数的计算流程可知,该函数获取的是 PCLK_MSYS ,经过打印验证确实如此~~但是PWM Timer4使用的是 PCLK_PSYS 时钟,因此设置的计数值是错误的。正确的用法是应该调用 get_PCLKP 函数


D. env_init


说明1:找到正确的env_init函数
uboot代码中有多个env_init函数的实现,而且很多涉及的文件均被编译。之所以会有如此多的实现版本,是因为uboot支持多种启动设备,针对不同的启动设备,需要不同的函数将环境变量读取到内存中。


但是在最终连接的ELF文件中,只能有一个env_init函数的实现,否则就会链接出错。在uboot中是通过CFG_ENV_IS_IN_XXX宏实现控制的,在x210_sd.h中定义如下,
所以当前版本使用的是common/env_auto.c中的env_init

说明2:使用缺省参数初始化环境变量并标识环境变量CRC校验有效
首先说明一下default_environment数组的定义方式,

① 由于定义了CONFIG_S5PC110宏,所以数组形式为,
uchar default_environment[CFG_ENV_SIZE]
实际就是一个16KB的字符数组

② 由于所以环境变量以字符串的形式保存,所以如果配置项为字符串(e.g. CONFIG_BOOTARGS)则直接展开,如果配置项为"数字"(e.g. CONFIG_BOOTDELAY)则调用MK_STR宏实现字符串化

③ 由于default_environment数组定义为全局变量,因此这段16KB的缺省环境变量会被编译进uboot.bin镜像中

说明3:ENV_IS_EMBEDDED机制简介[x210 uboot中并未使用]
如果定义了ENV_IS_EMBEDDED宏,就会使能该机制,该机制的特点就是环境变量存储在.text段
首先说明一下env_t类型,其中个字段含义如下,
crc:环境变量的CRC校验值
flags:标识是否有冗余的环境变量env_t结构体
data:实际存储环境变量的字符数组,数组大小则是16KB - env_t结构头部信息


common/environment.c中定义的environment结构体如下,

① __PPCENV__宏用与指定将environment结构体链接到.text段

② 如果要使用environment结构体中的环境变量,需要定义ENV_CRC宏,且使其具有有效值,否则校验不通过也是无法使用的

③ 如果定义了CFG_ENV_ADDR_REDUND宏,将会 紧接着 environment结构体定义redundand_environment结构体,作为冗余数据

④ 在env_init中使用tmp_env1和tmp_env2分别指向environment结构和redundand_environment结构,并分别进行校验。

这里需要注意的是env_ptr的赋值方式,environment本身是一个结构体类型变量,对结构体类型变量取下标(相当于解一次引用)算是个什么东东呢 ???
根据在X86上的验证,根本无法编译通过~~(在uboot源码中尚无法测试,因为ENV_IS_EMBEDDED宏只在CFG_ENV_IS_IN_FLASH/NAND时才生效;单独定义ENV_IS_EMBEDDED宏,编译时会报出其他错误)



E. init_baudrate

说明:由于在lowlevel_init函数中已经对串口对应的GPIO和工作模式进行了初始化,此处只是获取串口的波特率信息。
首先尝试在环境变量中查找,如果环境变量中没有,则使用x210_sd.h中的配置值

F. serial_init



说明1:该函数只是简单计数延时,未做其他操作
说明2:完善的serial_setbrg函数是用于设置串口波特率的

G. console_init_f


说明1:该函数只是将gd->have_console字段置为1,即使用控制台

说明2:带_f 后缀的函数
在uboot中,有些初始化需要分段进行,一般加_f 后缀的就是第一阶段初始化(be f ore),加_r 后缀的就是第二阶段初始化(afte r )
示例:控制台初始化就分为前后两个阶段,因此在start_armboot中会先后调用如下2个函数,
console_init_f
console_init_r

应用场景:有些初始化不能连续完成,中间必须依赖其他部件的初始化

H. display_banner


说明1:该函数实现2个功能,
① 打印uboot版本信息
② 开启LCD背光(x210定制功能)
其中打印的字符串为,
version_string中的U_BOOT_VERSION宏定义在include/version_autogenerated.h中,由Makefile在编译过程中生成

说明2:debug函数打印的信息必须定义DEBUG宏才能从串口输出

说明3:控制台目前还没有初始化完成,为何能使用printf函数?
printf函数会调用puts函数,puts函数会根据GD_FLG_DEVINIT(设备是否初始化完成)确定使用哪个输出函数。
如果控制台已经初始化完毕,将会调用fputs函数,该函数会根据当前的句柄(stdin / stdout / stderr)调用不同的hook函数(详见后文分析)。
如果控制台尚未初始化完毕,则直接调用串口输出函数serial_puts,将打印信息输出到串口

说明4:如果不初始化控制台也能使用printf,那为何要使用控制台功能?
控制台是一个软件抽象层,控制台有一套专用的通信函数(e.g. 发送 / 接收函数),但是在实现时他们只是hook点。根据实际hook的输出函数,控制台可以映射到不同的物理设备(比如LCD;或者是另一个串口,即构成一个控制台串口,一个debug串口)
同时还可以在这个软件抽象层上进行优化,比如加入缓冲机制(uboot中可以不用,但是操作系统中需要实现)

说明5:开启LCD背光



说明:该函数将GPF3[5]管脚设置为输出模式,并且输出高电平。根据x210原理图可知,GPF3[5]为LCD的SYS_OE引脚

I. print_cpuinfo


说明1:print_cpuinfo函数用于打印当前各级时钟的配置值

说明2:代码中会对ARMCLK频率的有效性进行检查,有效值范围为设置值的[95%, 105%]

J. checkboard


说明:checkboard也是一个很水的函数,只是打印了开发板型号

K. dram_init


说明:由于lowlevel_init函数中已经对内存进行了初始化,此处只是将2个bank的内存信息( 物理起始地址 + 内存大小 )记录到gd结构体中
需要注意的是,此处内存大小单位为字节(B)

L. display_dram_config


说明1:该函数用于打印当前开发板内存总量,其中print_size函数会依次尝试当前内存总量等级,然后处理显示内存总量的整数和小数部分



说明2:uboot中的bdinfo命令可以打印gd->bd结构体中的信息,注意其中的env_t字段没赋值,仍为清空后的零值

5)CFG_NO_FLASH宏分析


说明1:此处的FLASH指NorFlash,虽然NandFlash和NorFlash都是Flash,但是一般NandFlash简称为Nand,而不是Flash。(其实和二者出现的先后顺序有关~~)

说明2:当前uboot中没有定义CFG_NO_FLASH宏,因此会调用flash_init & display_flash_config函数
实际上x210开发板并未配置NorFlash,但是如果在x210_sd.h中定义CFG_NO_FLASH宏,会引发其他文件的编译错误。可见保留此处的函数调用,是uboot移植时的遗留问题~~

6)堆内存初始化


说明1:根据上文对uboot内存布局的分析,uboot中维护了一片堆内存,配合uboot自身的管理代码,可以实现在uboot中使用malloc & free函数来申请和释放内存

说明2:mem_malloc_init函数记录堆内存的起始 & 终止地址,并将该段内存清空。需要注意的是,uboot将内存的env段也在malloc段之内,并同时将其清空(存疑:根据分析,下图中的env段和heap段应该对调,后续在代码中确证后会修改)


说明3:uboot中malloc & free的实现在common/dlmalloc.c文件中,需要注意的是实现的函数名并非全是小写。但是经过验证,在代码中调用malloc确实使用的就是该函数。(准确的原因我目前还无法解释~~)






uboot中malloc & free的代码颇有难度,而且学习该段代码也有利于对Linux内核中内存分配的理解,更详细的说明可参考链接中提供的文档。

7)开发板特有初始化分析

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: Uboot启动Linux过程详解: 1. Uboot加载内核镜像:Uboot首先会从存储设备(如SD卡、NAND Flash等)中加载内核镜像到内存中。 2. 设置内核启动参数:Uboot会设置内核启动参数,包括内核镜像在内存中的位置、根文件系统的位置、启动参数等。 3. 启动内核:Uboot会将控制权交给内核,让内核开始执行。内核会进行一系列初始化操作,包括初始化CPU、内存、设备驱动等。 4. 挂载根文件系统:内核会挂载根文件系统,使得用户可以访问文件系统中的文件和目录。 5. 启动用户空间:内核会启动用户空间,即启动init进程。init进程会读取配置文件,启动各种系统服务和应用程序。 6. 用户空间运行:一旦用户空间启动成功,系统就进入了正常的运行状态,用户可以使用各种应用程序和系统服务。 总之,Uboot启动Linux的过程是一个复杂的过程,需要多个组件协同工作,才能让系统正常启动并运行。 ### 回答2: Uboot是嵌入式系统中常用的一个开源启动加载程序,其主要功能是在嵌入式操作系统中启动Linux系统,并初始化系统硬件资源。Uboot在整个Linux系统启动过程中起到很关键的作用,下面就来详细介绍一下Uboot启动Linux过程. 一、Uboot的加载 第一个引导程序(Bootloader)需要存放在系统的闪存中,是Linux系统启动的重要组成部分。当开机后CPU默认开始执行闪存芯片0的地址,此时Uboot就被加载到RAM中,并执行。由于Uboot同样位于一块桥接器和一个NOR Flash的四接口ARM微控制器总线上,因此Uboot在存储器刚刚发挥了重要作用。 在Uboot启动过程中,系统会根据用户的选择进行环境的设置,比如启动选项、串口设置等。同时,Uboot还会初始化内存,并将内核镜像加载到内存中,准备启动内核。 二、内核的启动 Linux内核启动主要分为五个过程,分别是: 1.内核加载 当Uboot初始化完成后,系统进入内核加载阶段。Uboot会将存放在NOR Flash中的内核镜像加载到系统主内存中。在加载内核时,会有一个fdt文件,该文件是系统在启动时加载设备树的重要文件,在设备树的启动阶段,大部分设备驱动程序都是通过fdt中的节点进行解析。 2.内核装载初始化 在内核镜像成功加载到内存中后,Linux内核开始进入装载初始化,该阶段主要进行一些内部的初始化工作,比如初始化调度程序、内存管理、文件系统等;此外还会启动ELF文件解析程序,解析各个驱动模块,以便后续的设备树解析和驱动程序的加载。 3.设备树解析 在内核镜像加载到内存中之后,Linux会对设备树进行解析。设备树是在启动时由Uboot加载、传递给内核的一种数据结构,主要用于描述系统的硬件资源分布情况,是操作系统启动过程中很关键的一环,因为设备树可以为操作系统提供有关系统硬件的信息,便于操作系统启动后初始化对应的硬件资源。 4.初始化进程 在设备树解析完成之后,Linux会进入初始化进程的阶段。在这个过程中,系统会完成一系列的启动脚本,完成基本系统的初始化,并启动基本服务。 5.用户空间启动 当初始化进程执行完毕后,系统进入用户空间启动阶段。此时可以执行用户的应用程序,系统也正式进入了可用状态了。 三、总结 以上就是Uboot启动Linux过程的详解了。在整个启动过程中,Uboot不仅完成了硬件资源初始化,还实现了内核和用户空间的启动,是整个系统的重要组成部分。对于嵌入式设备的开发者来说,深入了解Uboot启动过程,对于准确定位问题和有序开发代码具有很大的帮助。 ### 回答3: uboot是嵌入式系统中常用的一个启动引导程序,其作用是加载Linux内核文件到系统中,并启动该内核从而让系统正常运行。本文就uboot启动linux的过程进行详细的分析。 uboot启动linux的过程: 1. CPU从复位向量开始执行: 当CPU启动时,会首先寻找复位向量所在的地址,并执行该地址中存储的指令。在嵌入式系统中,这个复位向量通常被配置为uboot程序的起始地址。 2. 加载uboot程序: uboot启动后会先加载自身的程序代码。uboot的程序包括bootloader和一些工具函数,它们可以执行一些用户定制的任务,比如读写参数、显示系统信息等操作,然后才会加载Linux内核。 3. 加载Linux内核: 在uboot加载内核时,它首先要根据指定的地址和大小,从存储介质中读取内核文件,并将其存储到内存中。在读取内核文件期间,uboot会进行一些配置操作,比如初始化内存、配置内存映射等操作。 4. 启动Linux内核: Linux内核启动时需要设置一些参数,这些参数通常由uboot传递给内核。例如,uboot会告诉内核内存的位置和大小、设备树等信息。接着,内核会根据这些参数进一步初始化系统,比如建立内存映射表、配置硬件设备等操作。这些操作完成后,Linux内核会开始执行用户空间程序,使得系统正常运行。 总结: 通过上述分析可知,uboot启动Linux的过程涉及到多个环节,其中包括uboot程序的加载、Linux内核的加载以及启动Linux内核时传递参数等操作。在实际系统中,这些过程需要针对具体的硬件平台进行适当的定制,才能保证系统正常启动和运行。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值