UBOOT源码详解之BL2第二阶段

本文深入剖析UBOOT启动流程的第二阶段,讲解了从命令行启动、CPU初始化到硬件配置如DDR内存、网卡、定时器、串口和控制台初始化的详细过程。此外,还探讨了环境变量、内存分配和堆管理,以及如何通过UBOOT进行系统更新。通过对关键函数的分析,揭示了UBOOT如何为启动内核做好准备。
摘要由CSDN通过智能技术生成
start_armboot()函数详解
一个很长的函数
(1)这个函数在uboot/lib_arm/board.c的第444行开始到908行结束。
(2)450行还不是全部,因为里面还调用了别的函数。
(3)为什么这么长的函数,怎么不分成两三个函数?主要因为这个函数整个构成了uboot启动的第二阶段。
一个函数组成uboot第二阶段
宏观分析:uboot第二阶段应该做什么
(1)概括来讲uboot第一阶段主要就是初始化了SoC内部的一些部件(譬如看门狗、时钟),然后初始化DDR并且完成重定位。
(2)由宏观分析来讲,uboot的第二阶段就是要初始化剩下的还没被初始化的硬件。主要是SoC外部硬件(譬如iNand、网卡芯片····)、uboot本身的一些东西(uboot的命令、环境变量等····)。然后最终初始化完必要的东西后进入uboot的命令行准备接受命令。
uboot第二阶段完结于何处?
(1)uboot启动后自动运行打印出很多信息(这些信息就是uboot在第一和第二阶段不断进行初始化时,打印出来的信息)。然后uboot进入了倒数bootdelay秒然后执行bootcmd对应的启动命令。

(2)如果用户没有干涉则会执行bootcmd进入自动启动内核流程(uboot就死掉了);此时用户可以按下回车键打断uboot的自动启动进入uboot的命令行下。然后uboot就一直工作在命令行下。

(3)uboot的命令行就是一个死循环,循环体内不断重复:接收命令、解析命令、执行命令。这就是uboot最终的归宿。

init_fnc_t
(1)typedef int (init_fnc_t) (void); 这是一个函数类型


(2)init_fnc_ptr是一个二重函数指针,回顾高级C语言中讲过:二重指针的作用有2个(其中一个是用来指向一重指针),一个是用来指向指针数组。因此这里的init_fuc_ptr可以用来指向一个函数指针数组。

DECLARE_GLOBAL_DATA_PTR

(1)#define DECLARE_GLOBAL_DATA_PTR     register volatile gd_t *gd asm ("r8")


定义了一个全局变量名字叫gd,这个全局变量是一个指针类型,占4字节。用volatile修饰表示可变的,用register修饰表示这个变量要尽量放到寄存器中,后面的asm("r8")是gcc支持的一种语法,意思就是要把gd放到寄存器r8中。

(2)综合分析,DECLARE_GLOBAL_DATA_PTR就是定义了一个要放在寄存器r8中的全局变量,名字叫gd,类型是一个指向gd_t类型变量的指针。

(3)为什么要定义为register?因为这个全局变量gd(global data的简称)是uboot中很重要的一个全局变量(准确的说这个全局变量是一个结构体,里面有很多内容,这些内容加起来构成的结构体就是uboot中常用的所有的全局变量),这个gd在程序中经常被访问,因此放在register中提升效率。因此纯粹是运行效率方面考虑,和功能要求无关。并不是必须的。
(4)gd_t定义在include/asm-arm/global_data.h中。


gd_t中定义了很多全局变量,都是整个uboot使用的;其中有一个bd_t类型的指针,指向一个bd_t类型的变量,这个bd是开发板的板级信息的结构体,里面有不少硬件相关的参数,譬如波特率、IP地址、机器码、DDR内存分布。

内存使用排布

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



(2)堆区 长度为CFG_MALLOC_LEN,实际为912KB


(3)栈区 长度为CFG_STACK_SIZE,实际为512KB


(4)gd 长度为sizeof(gd_t),实际36字节
(5)bd 长度为sizeof(bd_t),实际为44字节左右
(6)内存间隔 为了防止高版本的gcc的优化造成错误。



for循环执行init_sequence
(1)init_sequence是一个函数指针数组,数组中存储了很多个函数指针,这些指向指向的函数都是init_fnc_t类型(特征是接收参数是void类型,返回值是int)。
(2)init_sequence在定义时就同时给了初始化,初始化的函数指针都是一些函数名。(C语言高级专题中讲过:函数名的实质)

(3)init_fnc_ptr是一个二重函数指针,可以指向init_sequence这个函数指针数组。
(4)用for循环肯定是想要去遍历这个函数指针数组(遍历的目的也是去依次执行这个函数指针数组中的所有函数)。思考:如何遍历一个函数指针数组?有2种方法:第一种也是最常用的一种,用下标去遍历,用数组元素个数来截至。第二种不常用,但是也可以。就是在数组的有效元素末尾放一个标志,依次遍历到标准处即可截至(有点类似字符串的思路)。
我们这里使用了第二种思路。因为数组中存的全是函数指针,因此我们选用了NULL来作为标志。我们遍历时从开头依次进行,直到看到NULL标志截至。这种方法的优势是不用事先统计数组有多少个元素。
(5)init_fnc_t的这些函数的返回值定义方式一样的,都是:函数执行正确时返回0,不正确时返回-1.所以我们在遍历时去检查函数返回值,如果遍历中有一个函数返回值不等于0则hang()挂起。从分析hang函数可知:uboot启动过程中初始化板级硬件时不能出任何错误,只要有一个错误整个启动就终止,除了重启开发板没有任何办法。
(6)init_sequence中的这些函数,都是board级别的各种硬件初始化。

cpu_init

(1)看名字这个函数应该是cpu内部的初始化,所以这里是空的。
board_init


(1)board_init在uboot/board/samsung/x210/x210.c中,这个看名字就知道是x210开发板相关的初始化。
(2)DECLARE_GLOBAL_DATA_PTR在这里声明是为了后面使用gd方便。可以看出把gd的声明定义成一个宏的原因就是我们要到处去使用gd,因此就要到处声明,定义成宏比较方便。
(3)网卡初始化。CONFIG_DRIVER_DM9000这个宏是x210_sd.h中定义的,这个宏用来配置开发板的网卡的。dm9000_pre_init函数就是对应的DM9000网卡的初始化函数。开发板移植uboot时,如果要移植网卡,主要的工作就在这里。
(4)这个函数中主要是网卡的GPIO和端口的配置,而不是驱动。因为网卡的驱动都是现成的正确的,移植的时候驱动是不需要改动的,关键是这里的基本初始化。因为这些基本初始化是硬件相关的。

gd->bd->bi_arch_number
(1)bi_arch_number是board_info中的一个元素,含义是:开发板的机器码。所谓机器码就是uboot给这个开发板定义的一个唯一编号
(2)机器码的主要作用就是在uboot和linux内核之间进行比对和适配。
(3)嵌入式设备中每一个设备的硬件都是定制化的,不能通用。嵌入式设备的高度定制化导致硬件和软件不能随便适配使用。这就告诉我们:这个开发板移植的内核镜像绝对不能下载到另一个开发板去,否则也不能启动,就算启动也不能正常工作,有很多隐患。因此linux做了个设置:给每个开发板做个唯一编号(机器码),然后在uboot、linux内核中都有一个软件维护的机器码编号。然后开发板、uboot、linux三者去比对机器码,如果机器码对上了就启动,否则就不启动(因为软件认为我和这个硬件不适配ÿ

  • 0
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值