cortex-a8 uboot系列:第九章 uboot源码分析5-启动第二阶段
1赞一、 start_armboot 4 解析
1. DATAFLASH初始化
CONFIG_HAS_DATAFLASH为串行接口的FLASH,如spi接口的FLASH或者IIC接口的FLASH。小容量的FLASH芯片。但是x210没有这个东西,这部分代码不执行。
2. 环境变量设置
环境变量的初始化,这里是对环境变量的重定位,将SD/MMC的环境变量读取到DDR中。
ENV_IS_EMBEDDED表示环境变量是内嵌到设备内部(也就是环境变量时内嵌到text段中)。该宏在程序中没有被定义。因此下面的代码是没有被执行的。
在gd_t结构体中,有reloc_off变量,因为环境变量是内嵌在text段中的,所以这个变量指环境变量在相对于text段中首地址的偏移量。
env_ptr是第一个环境变量的地址。
这段代码是被执行的,为环境变量使用malloc分配CFG_ENV_SIZE大小(16K)的内存区域。分配了后,env_ptr就指向DDR中环境变量的首地址。
SD卡中有一些独立的扇区作为环境变量存储区域的。但是在烧录/部署系统时,只是烧录了uboot分区、kernel分区和rootfs分区,根本没有烧录env分区。当烧录完系统第一次启动时ENV分区是空的,本次uboot启动尝试去SD卡的ENV分区读取环境变量失败(读取回来后进行CRC校验时失败),uboot选择从uboot内部代码中设置的一套默认的环境变量用来使用(默认环境变量)。这套默认的环境变量在本次运行时会被读取到DDR中的环境变量中,然后被写入(可能是uboot自动写入,也可能是输入命令save写入)SD卡的ENV分区。下次再次启动时,uboot就会从SD卡的ENV分区读取环境变量到DDR中,读取就不会失败了。
Set_default_env(common/env_common.c),就是设置默认的环境变量,将默认的环境变量的值拷贝到DDR中的环境变量中。
当第二次启动的时候,执行env_relocate_spec函数(common/env_movi.c)
因为没有定义ENV_IS_EMBEDDED宏,所以这里什么都没有执行。这里是当环境变量是嵌入到text段中的话,从外部SD/MMC卡中读取环境变量的值,然后进行CRC判断。
virt_to_phys是虚拟地址转物理地址的函数。
movi_read_env(common/env_movi.c)是从外部MMC设备读取环境变量到DDR中。实际上是调用movi_read函数(drivers/mmc/mmc.c)。
最后将全局变量gd的环境变量指向内存中的环境变量首地址。这样,以后通过gd->env_addr参数,就可以获知环境变量。
3. IP地址初始化
获取IP地址,使用getenv_IPaddr函数(net/net.c)从环境变量的ipaddr获取。
getenv函数(common/cmd_nvedit.c)就是获取环境变量的值(字符串形式),使用string_to_ip函数(net/net.c)
IPaddr_t就是一个ulong类型的别名。
使用string_to_ip函数(net/net.c)将IP形式的字符串转化为ulong型。
如,对于192.168.0.1。 转化之后的结果为
C0 | A8 | 00 | 01 |
192的十六进制 | 168的十六进制 | 0的十六进制 | 1的十六进制 |
所以addr = 0xc0a80001。
使用htonl函数将addr转化为网络字节序。
IP地址由4个0-255之间的数字组成,因此一个IP地址在程序中最简单的存储方法就是一个unsigned int。但是这种对于我们不易读,使用点分十进制类型(192.168.0.1),两种方式可以相互转化。
在计算机中,使用IPaddr_t类型来存储IP地址,以节省空间。而对于我们,使用点分十进制类型来表示。所以在存储的时候,要进行转化,将点分十进制类型转化为IPaddr_t类型保存,显示的时候要将IPaddr_t类型转化为点分十进制类型。
4. 物理网卡设置
设置网卡物理地址。
通过getenv_r函数获取ethaddr环境变量的值,保存在tmp数组中。然后通过处理,将变量的值写入到gd->bd->bi_enetaddr数组变量中。
没有使用第二块网卡,所以CONFIG_HAS_ETH1宏没有定义,对第二块网卡的物理地址就没有设置。
5. 设备初始化
设备的初始化。这里的设备指开发板上的硬件设备。这里的初始化设备都是驱动设备,这个函数是从驱动框架从衍生出了的。Uboot中很多设备的驱动是直接移植linux内核的(如网卡、SD卡),linux内核中的驱动都有相应的设备初始化函数。Linux内核在启动过程中就有一个devices_init函数,作用就是集中执行各种硬件驱动的init函数。
Uboot的这个函数其实就是从linux内核中移植过来,作用也是去执行所有的从linux内核中继承来的那些硬件驱动的初始化函数。
对设备创建列表,然后各自调用硬件的初始化。device_t是一个结构体,表征所有的设备。
jumptable跳转表,本身是一个函数指针数组,里面记录了很多函数的函数名。应该是实现一个函数指针到具体函数的映射关系,将来通过跳转表中的函数指针就可以执行具体的函数。这个其实就是用C语言实现面向对象编程。Linux内核中有很多这种技巧。
这个函数就是对gd中的jt变量赋值,不过赋值了之后没有被使用。因此在uboot中没有对jumptable进行使用。
6. 控制台初始化
控制台的第二阶段初始化。console_init_r是第二阶段初始化,console_init_f是第一阶段初始化。之前分析第一阶段没有做什么实质性工作。
console_init_r函数(common/console.c中)
对输入输出设备进行扫描。
设置输入输出设备。
判断输入输出设备是否设置正确。正确的话打印信息。所以在uboot启动信息可以看到上述打印信息。
所以console_init_r函数,做的事就是console纯软件架构方面的初始化(就是给console相关的数据结构中填充相应的值),所以属于纯软件配置类型的初始化。
Uboot的console实际上没有做有意义的转化,就是直接调用串口通信的函数。所以用不用console实际并没有什么分别。(linux中console可以提供缓冲机制等)。
7. 中断设置
设置中断的使能,其实就是设置CPSR寄存器的I位。但是在这里是没有设置的。
因为uboot中没有使用中断,所以CONFIG_USE_IRQ宏是无定义的,所以enable_interrupts函数(cpu/s5pc11x/interrupts.c)就是一个空函数。
8. 获取loadaddr,bootfile环境变量
获取两个环境变量值,这两个环境变量都是和内核启动有关的,在启动linux内核是会参考这两个环境变量的值。
9. board_late_init
board_late_init函数(board/samsung/x210/x210.c)是开发板级别的一些晚期初始化。此时说明开发板级别的硬件软件初始化结束了。
因为不满足预编译条件,所以这个函数是空的。
没有定义CONFIG_NET_MULTI宏,所以puts不被编译。
10. 网卡初始化
网卡相关的初始化。eth_initialize函数(net/eth.c)。
这里不是soc的网卡控制器的初始化,而是外部网卡芯片本身的一些初始化。
对于x210(DM9000网卡)来说,这个函数是空的。X210的网卡控制器初始化在board_init函数中,网卡芯片的初始化在驱动中。
宏编译条件都不成立,该函数直接返回0。
对phy复位
11. IDE设备初始化
如果开发板有接IDE接口硬盘,对硬盘进行初始化。
12. LCD初始化
声明x210_preboot_init(board/samsung/x210/x210.c)为外部函数,这里直接调用该函数。
而这个函数又是去调用了一个外部函数mpadfb_init(drivers/video/mpadfb.c)
对LCD进行初始化,并将logo显示在LCD上。
fb_init函数(drivers/video/mpadfb.c),对显存设置
lcd_port_init函数(drivers/video/mpadfb.c),对LCD的port设置
lcd_reg_init函数(drivers/video/mpadfb.c),对LCD控制器的内部寄存器设置
display_logo函数(drivers/video/logo.c),将指定的log图片信息保存到显存中,从而LCD显示图片。
13. 自动升级
Uboot启动的最后阶段,设计了自动更新的功能。就是:可以将要升级的镜像放到SD卡的固定目录中,然后开机时,在uboot启动的最后阶段检查升级标志(是一个按键,按键中标志为“LEFT“的按键,这个按键如果按下则表示update mode,如启动时未按下则表示boot mode)。如果进入了update mode则uboot会自动从SD卡中读取镜像文件然后烧录到iNand中;如果进入了boot mode则uboot不执行update,直接启动正常运行
这种机制能够帮助我们快速烧录系统,常用于量产时用SD卡进行系统烧录部署。
14. 进入main_loop
进入到了uboot中的命令模式下了。等待命令输入,然后解析命令,最后执行命令。