Linux-uboot-学习笔记(7):uboot启动第二阶段源码分析

Linux-uboot-学习笔记(7):uboot启动第二阶段源码分析

uboot启动的第二阶段主要是执行第一阶段跳转到的start_armboot函数,该BL2阶段在DDR中初始化第一阶段未完成的任务和SoC各种外设。

start_armboot函数分析

start_armboot函数位于Board.c文件中,从文件名可以看出,该函数主要针对板级初始化的。

定义全局变量数据结构体(70)

在这里插入图片描述
定义了一个全局变量名字叫gd,这个全局变量是一个指针类型,占4字节。用volatile修饰表示可变的,用register修饰表示这个变量要尽量放到寄存器中(因为这些内容加起来构成的结构体就是uboot中常用的所有的全局变量,放到寄存器中提升效率),后面的asm(“r8”) 是gcc支持的一种语法,意思就是要把gd放到寄存器r8中
DECLARE_GLOBAL_DATA_PTR就是定义了一个要放在寄存器r8中的全局变量,名字叫gd,类型是一个指向gd_t类型变量的指针。

内存使用排布(462-472)

DECLARE_GLOBAL_DATA_PTR只能定义了一个指针,也就是说gd里的这些全局变量并没有被分配内存,我们在使用gd之前要给他分配内存,否则gd也只是一个野指针而已。
gd和bd需要内存,内存当前没有被人管理(因为没有操作系统统一管理内存),大片的DDR内存散放着可以随意使用(只要使用内存地址直接去访问内存即可)。但是因为uboot中后续很多操作还需要大片的连着内存块,因此这里使用内存要本着够用就好,紧凑排布的原则。所以我们在uboot中需要有一个整体规划。
在这里插入图片描述
uboot区 CFG_UBOOT_BASE-xx(长度xx为uboot的实际长度)
堆区 长度为CFG_MALLOC_LEN,实际为912KB
栈区 长度为CFG_STACK_SIZE,实际为512KB
gd 长度为sizeof(gd_t),实际36字节
bd 长度为sizeof(bd_t),实际为44字节左右

通过init_fnc_ptr对函数指针数组进行访问(446,483-487)

在这里插入图片描述
init_sequence是一个函数指针数组,数组中存储了很多个函数指针,这些指针指向的函数都是init_fnc_t类型(特征是接收参数是void类型,返回值是int)。
init_fnc_ptr是一个二重函数指针,可以指向init_sequence这个函数指针数组。
用for循环肯定是想要去遍历这个函数指针数组(遍历的目的也是去依次执行这个函数指针数组中的所有函数)。我们采用了一种特殊的遍历方法。因为数组中存的全是函数指针,因此我们选用了NULL来作为标志。我们遍历时从开头依次进行,直到看到NULL标志截至。这种方法的优势是不用事先统计数组有多少个元素。
在这里插入图片描述
init_fnc_t的这些函数的返回值定义方式一样的,都是:函数执行正确时返回0,不正确时返回-1。所以我们在遍历时去检查函数返回值,如果遍历中有一个函数返回值不等于0则hang()挂起。从分析hang函数可知:uboot启动过程中初始化板级硬件时不能出任何错误,只要有一个错误整个启动就终止,除了重启开发板没有任何办法。

init_sequence数组内部函数分析(416-442)

(1)cpu_init [cpu内部初始化]
在这里插入图片描述
cpu内部的初始化,所以这里是空的,cpu相关初始化在start.S中都结束了。

(2)board_init [开发板相关初始化]
在这里插入图片描述
bi_arch_number是board_info中的一个元素,含义是:开发板的机器码。 所谓机器码就是uboot给这个开发板定义的一个唯一编号。机器码的主要作用就是在uboot和linux内核之间进行比对和适配。
嵌入式设备中每一个设备的硬件都是定制化的,不能通用。嵌入式设备的高度定制化导致硬件和软件不能随便适配使用。这就告诉我们:这个开发板移植的内核镜像绝对不能下载到另一个开发板去,否则也不能启动,就算启动也不能正常工作,有很多隐患。因此linux做了个设置:给每个开发板做个唯一编号(机器码),然后在uboot、linux内核中都有一个软件维护的机器码编号。然后开发板、uboot、linux三者去比对机器码,如果机器码对上了就启动,否则就不启动(因为软件认为我和这个硬件不适配)。
uboot中配置的这个机器码,会作为uboot给linux内核的传参的一部分传给linux内核,内核启动过程中会比对这个接收到的机器码,和自己本身的机器码相对比,如果相等就启动,如果不相等就不启动。

bd_info中另一个主要元素,bi_boot_params表示uboot给linux kernel启动时的传参的内存地址。也就是说uboot给linux内核传参的时候是这么传的:uboot事先将准备好的传参(字符串,就是bootargs)放在内存的一个地址处(就是bi_boot_params),然后uboot就启动了内核(uboot在启动内核时真正是通过寄存器r0 r1 r2来直接传递参数的,其中有一个寄存器中就是bi_boot_params)。内核启动后从寄存器中读取bi_boot_params就知道了uboot给我传递的参数到底在内存的哪里。然后自己去内存的那个地方去找bootargs。
经过计算得知:X210中bi_boot_params的值为0x30000100,这个内存地址就被分配用来做内核传参了。所以在uboot的其他地方使用内存时要注意。

(3)interrupt_init [初始化定时器]
在这里插入图片描述
interrupt_init函数将timer4设置为定时10ms。关键部位就是get_PCLK函数获取系统设置的PCLK_PSYS时钟频率,然后设置TCFG0和TCFG1进行分频,然后计算出设置为10ms时需要向TCNTB中写入的值,将其写入TCNTB,然后设置为auto reload模式,然后开定时器开始计时就没了。

(4)env_init [初始化环境变量]
在这里插入图片描述
经过基本分析,这个函数只是对内存里维护的那一份uboot的env做了基本的初始化或者说是判定(判定里面有没有能用的环境变量)。当前因为我们还没进行环境变量从SD卡到DDR中的relocate,因此当前环境变量是不能用的。
在start_armboot函数中(776行)调用env_relocate才进行环境变量从SD卡中到DDR中的重定位。重定位之后需要环境变量时才可以从DDR中去取,重定位之前如果要使用环境变量只能从SD卡中去读取。

(5)init_baudrate [初始化波特率]
在这里插入图片描述
getenv_r函数用来读取环境变量的值。用getenv函数读取环境变量中“baudrate”的值(注意读取到的不是int型而是字符串类型),然后用simple_strtoul函数将字符串转成数字格式的波特率
baudrate初始化时的规则是:先去环境变量中读取"baudrate"这个环境变量的值。如果读取成功则使用这个值作为环境变量,记录在gd->baudrate和gd->bd->bi_baudrate中;如果读取不成功则使用x210_sd.h中的的CONFIG_BAUDRATE的值作为波特率。从这可以看出:环境变量的优先级是很高的。

(6)serial_init [初始化串口]
在这里插入图片描述
进来后发现serial_init函数其实什么都没做。因为在汇编阶段串口已经被初始化过了,因此这里就不再进行硬件寄存器的初始化了。

(7)console_init_f [控制台初始化第一阶段]
在这里插入图片描述
console_init_f在uboot/common/console.c中,仅仅是对gd->have_console设置为1而已,其他事情都没做。

(8)display_banner [串口显示LOGO]
在这里插入图片描述
在这里插入图片描述
通过追踪printf的实现,发现printf->puts,而puts函数中会判断当前uboot中console有没有被初始化好。如果console初始化好了则调用fputs完成串口发送(这条线才是控制台);如果console尚未初始化好则会调用serial_puts(再调用serial_putc直接操作串口寄存器进行内容发送)。
控制台也是通过串口输出,非控制台也是通过串口输出。究竟什么是控制台?和不用控制台的区别?实际上分析代码会发现,控制台就是一个用软件虚拟出来的设备,这个设备有一套专用的通信函数(发送、接收···),控制台的通信函数最终会映射到硬件的通信函数中来实现。uboot中实际上控制台的通信函数是直接映射到硬件串口的通信函数中的,也就是说uboot中用没用控制器其实并没有本质差别。

(9)print_cpuinfo [打印CPU信息]
在这里插入图片描述
通过宏定义的方式能够直接对CPU的一整套时钟进行设置,而print_cpuinfo即可将这些时钟信息打印出来。
在这里插入图片描述
(10)checkboard [检查开发板]
检查、确认开发板的意思。这个函数的作用就是检查当前开发板是哪个开发板并且打印出开发板的名字。

(11)init_func_i2c [初始化I2C]
这个函数实际没有被执行,X210的uboot中并没有使用I2C。如果将来我们的开发板要扩展I2C来接外接硬件,则在x210_sd.h中配置相应的宏即可开启。

(12)dram_init [初始化DDR]
在这里插入图片描述
dram_init都是在给gd->bd里面关于DDR配置部分的全局变量赋值,让gd->bd数据记录下当前开发板的DDR的配置信息,以便uboot中使用内存。
从代码来看,其实就是初始化gd->bd->bi_dram这个结构体数组,通过基地址+内存大小的方式,即可指定内存的起始位置。

(13)display_dram_config [打印DRAM配置信息]
在这里插入图片描述

init_sequence总结:
网卡初始化、机器码(gd->bd->bi_arch_number)、内核传参DDR地址(gd->bd->bi_boot_params)、Timer4初始化为10ms一次、波特率设置(gd->bd->bi_baudrate和gd->baudrate)、console第一阶段初始化(gd->have_console设置为1)、打印uboot的启动信息、打印cpu相关设置信息、检查并打印当前开发板名字、DDR配置信息初始化(gd->bd->bi_dram)、打印DDR总容量。

mem_malloc_init初始化堆内存(525-529)

在这里插入图片描述
mem_malloc_init函数用来初始化uboot的堆管理器。uboot中自己维护了一段堆内存,肯定自己就有一套代码来管理这个堆内存。有了这些东西uboot中你也可以malloc、free这套机制来申请内存和释放内存。我们在DDR内存中给堆预留了896KB的内存。
start堆起始,end堆结束,CFG)MALLOC_LEN是896K预留的堆内存大小。

mmc_initialize初始化MMC(599-632)

在这里插入图片描述
mmc_initialize是MMC相关的一些基础的初始化,其实就是用来初始化SoC内部的SD/MMC控制器的。函数在uboot/drivers/mmc/mmc.c里。
uboot中对硬件的操作(譬如网卡、SD卡···)都是借用的linux内核中的驱动来实现的,uboot根目录底下有个drivers文件夹,这里面放的全都是从linux内核中移植过来的各种驱动源文件。
mmc_initialize是具体硬件架构无关的一个MMC初始化函数,所有的使用了这套架构的代码都掉用这个函数来完成MMC的初始化。mmc_initialize中再调用board_mmc_init和cpu_mmc_init来完成具体的硬件的MMC控制器初始化工作
cpu_mmc_init在uboot/cpu/s5pc11x/cpu.c中,这里面又间接的调用了drivers/mmc/s3c_mmcxxx.c中的驱动代码来初始化硬件MMC控制器。

env_relocate环境变量重定位(776)

在这里插入图片描述
env_relocate是环境变量的重定位,完成从SD卡中将环境变量读取到DDR中的任务。
环境变量到底从哪里来?SD卡中有一些(8个)独立的扇区作为环境变量存储区域的。但是我们烧录/部署系统时,我们只是烧录了uboot分区、kernel分区和rootfs分区,根本不曾烧录env分区。所以当我们烧录完系统第一次启动时ENV分区是空的,本次启动uboot尝试去SD卡的ENV分区读取环境变量时失败(读取回来后进行CRC校验时失败),我们uboot选择从uboot内部代码中设置的一套默认的环境变量出发来使用(这就是默认环境变量);这套默认的环境变量在本次运行时会被读取到DDR中的环境变量中,然后被写入(也可能是你saveenv时写入,也可能是uboot设计了第一次读取默认环境变量后就写入)SD卡的ENV分区。然后下次再次开机时uboot就会从SD卡的ENV分区读取环境变量到DDR中,这次读取就不会失败了。
真正的从SD卡到DDR中重定位ENV的代码是在env_relocate_spec内部的movi_read_env完成的。
最后将env_ptr中的内容赋给gd->env_addr,完成重定位。

设置IP地址(788)

在这里插入图片描述
(1)开发板的IP地址是在gd->bd中维护的,来源于环境变量ipaddr。getenv函数用来获取字符串格式的IP地址,然后用string_to_ip将字符串格式的IP地址转成字符串格式的点分十进制格式。
IP地址由4个0-255之间的数字组成,因此一个IP地址在程序中最简单的存储方法就是一个unsigend int。但是人类容易看懂的并不是这种类型,而是点分十进制类型(192.168.1.2)。这两种类型可以互相转换。
例如在用户看的时候为:192.168.1.2 ;在存储时为0xC0AB0102。

设备初始化(817)

在这里插入图片描述
这里的设备指的就是开发板上的硬件设备。放在这里初始化的设备都是驱动设备,这个函数本来就是从驱动框架中衍生出来的。uboot中很多设备的驱动是直接移植linux内核的(譬如网卡、SD卡),linux内核中的驱动都有相应的设备初始化函数。linux内核在启动过程中就有一个devices_init(名字不一定完全对,但是差不多),作用就是集中执行各种硬件驱动的init函数。
uboot的这个函数其实就是从linux内核中移植过来的,它的作用也是去执行所有的从linux内核中继承来的那些硬件驱动的初始化函数

跳转表(824)

jumptable跳转表,本身是一个函数指针数组,里面记录了很多函数的函数名。看这阵势是要实现一个函数指针到具体函数的映射关系,将来通过跳转表中的函数指针就可以执行具体的函数。这个其实就是在用C语言实现面向对象编程。在linux内核中有很多这种技巧。
通过分析发现跳转表只是被赋值从未被引用,因此跳转表在uboot中根本就没使用。

控制台第二阶段初始化(826)

在这里插入图片描述
console_init_f是控制台的第一阶段初始化,console_init_r是第二阶段初始化。
console_init_r就是console的纯软件架构方面的初始化(说白了就是去给console相关的数据结构中填充相应的值),所以属于纯软件配置类型的初始化。
uboot的console实际上并没有干有意义的转化,它就是直接调用的串口通信的函数。所以用不用console实际并没有什么分别。(在linux内console就可以提供缓冲机制等不用console不能实现的东西)。

中断初始化(835)

在这里插入图片描述
这里指的是CPSR中总中断标志位的使能。因为我们uboot中没有使用中断,因此没有定义CONFIG_USE_IRQ宏,因此我们这里这个函数是个空壳子。
uboot中经常出现一种情况就是根据一个宏是否定义了来条件编译决定是否调用一个函数内部的代码。uboot中有2种解决方案来处理这种情况:方案一:在调用函数处使用条件编译,然后函数体实际完全提供代码。方案二:在调用函数处直接调用,然后在函数体处提供2个函数体,一个是有实体的一个是空壳子,用宏定义条件编译来决定实际编译时编译哪个函数进去。

初始化两个环境变量(856-862)

在这里插入图片描述
这两个环境变量都是内核启动有关的,在启动linux内核时会参考这两个环境变量的值。

最后的一些初始化(866)

在这里插入图片描述
看名字这个函数就是开发板级别的一些初始化里比较晚的了,就是晚期初始化。所以晚期就是前面该初始化的都初始化过了,剩下的一些必须放在后面初始化的就在这里了。侧面说明了开发板级别的硬件软件初始化告一段落了。(但在这里是空的,因为没有其余需要初始化的)

eth_initialize [网卡初始化](872)

网卡相关的初始化。这里不是SoC与网卡芯片连接时SoC这边的初始化,而是网卡芯片本身的一些初始化
对于X210(DM9000)来说,这个函数是空的。X210的网卡初始化在board_init函数中,网卡芯片的初始化在驱动中。

x210_preboot_init [LOGO显示](887)

LCD屏幕上的logo显示。

检查自动更新(892-900)

uboot启动的最后阶段设计了一个自动更新的功能。就是:我们可以将要升级的镜像放到SD卡的固定目录中,然后开机时在uboot启动的最后阶段检查升级标志(是一个按键。按键中标志为"LEFT"的那个按键,这个按键如果按下则表示update mode,如果启动时未按下则表示boot mode)。如果进入update mode则uboot会自动从SD卡中读取镜像文件然后烧录到iNand中;如果进入boot mode则uboot不执行update,直接启动正常运行。
这种机制能够帮助我们快速烧录系统,常用于量产时用SD卡进行系统烧录部署。

死循环(903-905)

(1)解析器
(2)开机倒数自动执行
(3)命令补全

总结start_armboot函数都做了什么?

	定义全局变量数据结构体(70)
	init_sequence		函数指针数组(416-442)
		cpu_init		cpu内部初始化,空的
		board_init		开发板相关初始化
			dm9000_pre_init			网卡
			gd->bd->bi_arch_number	机器码
			gd->bd->bi_boot_params	内存传参地址
		interrupt_init	初始化定时器
		env_init		初始化环境变量
		init_baudrate	初始化波特率
		serial_init		初始化串口,空的
		console_init_f	初始化控制台第一阶段,空的
		display_banner	串口显示LOGO
		print_cpuinfo	打印CPU时钟设置信息
		checkboard		检验开发板名字
		dram_init		gd数据结构中DDR信息
		display_dram_config	打印DDR配置信息表
	mem_malloc_init		初始化uboot自己维护的堆管理器的内存(525-529)
	mmc_initialize		inand/SD卡的SoC控制器和卡的初始化(599-632)
	env_relocate		环境变量重定位(776)
	gd->bd->bi_ip_addr	gd数据结构赋值(788)
	gd->bd->bi_enetaddr	gd数据结构赋值
	devices_init		设备初始化,空的(817)
	jumptable_init		跳转表(824)
	console_init_r		真正的控制台初始化(826)
	enable_interrupts	中断初始化,空的(835)
	loadaddr、bootfile 	环境变量读出初始化全局变量(856-862)
	board_late_init		最后的初始化,空的(866)
	eth_initialize		网卡初始化,空的(872)
	x210_preboot_init	LCD初始化和显示logo(887)
	check_menu_update_from_sd	检查自动更新(892-900)
	main_loop			主循环(903-905)
发布了72 篇原创文章 · 获赞 98 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/qq_42826337/article/details/105106932