Linux应用开发(十八)——U-Boot的启动过程源码分析


前言

参考的这本书籍使用的U-Boot从NOR Flash启动,所以介绍的内容和案例依旧参考书籍,使用开发板smdk2410的U-Boot为例进行分析

U-Boot第一阶段代码分析

1. 硬件设备初始化

依次完成如下设置:将CPU的工作模式设置为管理模式(svc),关闭WATCHDOG,设置FCLK,HCLK,PCLK的比例(FCLK:系统时钟,即CPU主频频率。HCLK:AHB,提供高速总线AHB的时钟,PCLK:提供低速总线APB的时钟)(即设置CLKDIVN寄存器),关闭MMU,CACHE。
分析如下
在这里插入图片描述在这里插入图片描述

在这里插入图片描述在这里插入图片描述在这里插入图片描述

2. 为加载Bootloader的第二阶段代码准备RAM空间。

所谓RAM空间,就是初始化内存芯片,使它可用。对于S3C2410,通过在Start.S中调用lowlevel_init函数来设置存储控制器,使得外接的SDRAM可用。分析如下

在这里插入图片描述
在这里插入图片描述

**注意:**lowlevel_init.S文件是与开发板相关的,这表示如果外接的设备不一样,可以修改lowlevel_init.S文件的相关值

lowlevel_init函数并不复杂,只要注意下面这段代码,数据都只保存在NOR Flash上,内存中还没有,所以读取数据时要变换地址。代码如下

129 _TEXT_BASE:
130         .word   TEXT_BASE
131
132 .globl lowlevel_init
133 lowlevel_init:
134         /* memory control configuration */
135         /* make r0 relative the current location so that it */
136         /* reads SMRDATA out of FLASH rather than memory ! */
137         ldr     r0, =SMRDATA
138         ldr     r1, _TEXT_BASE
139         sub     r0, r0, r1
140         ldr     r1, =BWSCON     /* Bus Width Status Controller */
141         add     r2, r0, #13*4
142 0:
143         ldr     r3, [r0], #4
144         str     r3, [r1], #4
145         cmp     r2, r0
146         bne     0b
147
148         /* everything is fine now */
149         mov     pc, lr
150
151         .ltorg
152 /* the literal pools origin */
153
154 SMRDATA:
155     .word (0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(    B6_BWSCON<<24)+(B7_BWSCON<<28))
156     .word ((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4)+(B0_Tacp<<2)+(B    0_PMC))
157     .word ((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4)+(B1_Tacp<<2)+(B    1_PMC))
158     .word ((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4)+(B2_Tacp<<2)+(B    2_PMC))
159     .word ((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4)+(B3_Tacp<<2)+(B    3_PMC))
160     .word ((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4)+(B4_Tacp<<2)+(B    4_PMC))
161     .word ((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4)+(B5_Tacp<<2)+(B    5_PMC))
162     .word ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN))
163     .word ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN))
164     .word ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT)
165     .word 0x32
166     .word 0x30
167     .word 0x30

第137~139行代码,表示进行地址交换,因为这个时候内存中还没有数据,不能使用连接程序时确定的地址来读取数据。

第137行中的SMRDATA表示这13个寄存器的值存放的开始地址(连续地址),值为0x33F8xxxxx,处于内存中。

第138行获得代码段的起始地址,他就是第130行中的“TEXT_BASE”,其值在这个文件中且值为0x33F80000
在这里插入图片描述
第139行将0x33F8xxx与0x33F80000相减,这就是13个寄存器值在NOR Flash上存放的开始地址。

3.复制Bootloader的第二阶段代码到RAM空间中

这里将整个U-Boot的代码(包括第一阶段,第二阶段)都复制到SDRAM中,如下
在这里插入图片描述

4. 设置好栈

栈的设置灵活性很大,只要让sp寄存器指向一段没有使用的内存即可。
在这里插入图片描述
相信到了这里,大家可以直到内存的使用情况了,如下图所示
(不过图中与上面的划分稍微有不同,因为在cpu/arm920t/cpu.c中的cpu_init函数中才真正为IRQ,FIQ模式划分了栈)。
在这里插入图片描述

5.跳转到第二阶段代码的C入口点。

在跳转之前,还要清除BSS段(初始值为0,无初始值的全局变量、静态变量放在BSS段),代码如下:
在这里插入图片描述
现在,C函数的运行环境已经完全准备好了,通过如下命令直接跳转(这之后,程序才在内存中执行),

在这里插入图片描述

它将调用lib_arm/board.c中的start_armboot函数,这是第二阶段的入口点

在这里插入图片描述
在这里插入图片描述

U-Boot第二阶段代码分析

它与前面博客中描述的Bootloader第二阶段所完成的功能基本上一致,只是顺序上有差别
Bootlaoder第二阶段的功能:

  1. 初始化本阶段要使用到的硬件设备。
  2. 检测系统内存映射
  3. 将内核映象和根文件系统映象从Flash上读到RAM空间中。
  4. 为内核设置启动参数、
  5. 调用内核

另外,U-Boot在启动内核之前可以让用户决定是否进入下载模式,即进入U-Boot的控制界面。
第二阶段从lib_arm/board.c中的start_armboot函数开始,程序执行流程如下所示
在这里插入图片描述 移植U-Boot的主要工作在于对硬件的初始化、驱动、所以下面讲解时将重点放在硬件的操作上

1、初始化本阶段要使用到的硬件设备

最重要的是设置系统时钟、初始化串口、只要这两个设置好了,就可以从串口看到打印信息。

board_init函数设置MPLL、改变系统时钟,它是开发板相关的函数,在board/smdk2410/smdk2410.c中实现。值得注意的是,board_init函数中还保存了机器类型ID,这将在调用内核时传给内核,代码如下:
在这里插入图片描述
串口的初始化函数主要是serial_init,它设置UART控制器,是CPU相关的函数,在cpu/arm920t/s3c24x0/serial.c中实现。
在这里插入图片描述

2、检测系统内存映射(memory map)

对于特定的开发板,其内存的分布是明确的,所以可以直接设置。board/smdk2410/smdk2410.c中的dram_inti函数指定了开发板的内存起始地址为0x30000000,大小为0x4000000。代码如下
在这里插入图片描述这些设置的参数,将在后面向内核传递参数时用到

3、U-Boot命令的格式

从上面我们的U-Boot内存使用情况图可以得知,即使内核启动,也是通过U-Boot命令来实现的。U-Boot中每一个命令都通过U_BOOT_CM宏来定义,格式如下:

U_BOOT_CMD(name,maxargs,repeatable,command,"usage", "help")

各项参数的意义如下。

  • name :命令名字,注意,他不是一个字符串(不要用双引号括起来)
  • maxargs:最大的参数个数
  • repeatable:命令是不可重复,可重复是指运行一个命令,下次敲回车即可再次运行
  • command:对应的函数指针,类型为(*cmd)(struct cmd_tbl_s * ,int,int,char *[] )。
  • usage:简短的使用说明,这个是字符串。
  • help:较详细的使用说明,这个是个字符串。
    宏U_BOOT_CMD在include/command.h中定义,如下所示:
    在这里插入图片描述
    Struct_Section 也是在include/command.h中定义,如下所示
    在这里插入图片描述比如对于bootm命令,它如下定义:
U_BOOT_CMD(
	bootm, CFG_MAXARGS, 1 ,do_bootm,
	"string1",
	"string2"
);

宏U_BOOT_CMD扩展开后如下所示:

cmd_tbl_t __u_boot_cmd_bootm __attribute__((unused,section (".u_boot_cmd")))=
{"bootm",CFG_MAXARGS,1,do_bootm,"string1","string2"};

对于每个使用U_BOOT_CMD宏来定义的命令,其实都是在“.u_boot_cmd”段中定义一个cmd_tbl_t结构。连接脚本U-Boot.lds中有如下代码:
在这里插入图片描述程序中就是根据命令的名字在内存段_ _u_boot_cmd_start~ _ _ _u_boot_cmd_end找到它的cmd_tbl_t结构,然后调用它的函数(参考common/command.c中的find_cmd函数)。
在这里插入图片描述

内核的复制和启动,可以通过如下命令来完成:bootm从内存、ROM、NOR Flash中启动内核,bootp通过网络来启动,而nboot从NAND Flash启动内核。它们都是先将内核映象从各个媒介中读出,存放在指定的位置;然后设置标记列表以给内核传递参数;最后跳到内核的入口点去执行。具体实现的细节不再描述,有兴趣的朋友可以阅读comman/cmd_boot.c、comman/cmd_net.c、common/cmd_nand.c来了解它们的实现。

4、为内核设置启动参数

U-Boot也是通过标记参数列表向内核传递参数。并且,在前面的博客中介绍到,内存标记,命令行标记的实例代码就是取自U-Boot中的setup_memory_tags、setup_commandline_tag函数,它们都是在lib_arm/armlinux.c中定义。一般而言,设置这两个标记就可以了,在配置文件include/configs/smdk2410.h增加如下两个配置项即可:

在这里插入图片描述
在这里插入图片描述
对于ARM架构的CPU,都是通过lib_arm/armlinux.c中的do_bootm_linux函数来启动内核
在这里插入图片描述
这个函数中,设置标记列表,最后通过“ theKernel (0, bd->bi_arch_number, bd->bi_boot_params);”调用内核。其中,theKernel指向内核存放的地址(对于ARM架构的CPU,通常是0x30008000),bd->bi_arch_number就是上面board_init函数设置的机器类型ID,而bd->bi_boot_params就是标记列表的开始地址。

总结

下一篇博客开始移植U-Boot。相信经过这几篇博客,你已经非常清除U-Boot的工作原理和流程了,最后一步就是移植了,坚持。我在坚持,也希望看到这篇博客的你也坚持。
在这里插入图片描述
9000多的字,真的很难。但是也要一直坚持,相信也许有一天,我也能写出如U-Boot这样优秀的项目。
加油!

  • 5
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
U-boot是一个开放代码的引导加载程序,它是嵌入式Linux系统中非常重要的一部分。它主要负责完成硬件初始化、文件系统加载、内核启动、DTB传递等工作。下面是U-boot启动过程的详细分析: 1. 通电启动 当开关通电时,处理器会执行引导ROM中的启动代码。 2. 初始化寄存器 在进入U-boot之前,系统所有的寄存器都是未初始化的。U-boot要负责初始化CPU的所有寄存器,以确保所有设备都能正常工作。 3. 读取U-boot U-boot位于NOR或NAND闪存器中。为了读取U-boot,首先必须确定闪存器的类型和接口。当读取闪存器中的第一个块(一般是U-boot头)时,U-boot校验它的合法性,包括校验和、magic number、版本号等。如果校验失败,U-boot将停止执行。 4. 解压缩U-boot 如果U-boot被压缩了,那么需要用解压缩算法对其进行解压缩。在这个过程中,需要注意解压缩的起始地址和大小。 5. 设置环境变量 U-boot提供了一些环境变量,可以用来配置系统参数,例如:IP地址、MAC地址等。在这一步骤中,U-boot会将环境变量加载到DRAM中进行管理。同时,也可以通过TFTP、NFS等方式从外部存储设备中加载环境变量。 6. 初始化硬件 在U-boot启动过程中,需要对各种设备进行初始化。这些设备包括:串口、网络接口、SD卡、USB设备、SPI设备等。初始化完成后,U-boot才能正常使用这些设备。 7. 加载内核 U-boot负责加载内核到指定的内存地址。这个过程可以通过很多方式来完成:串口、SD卡、网络等。在加载内核之前,U-boot还会加载设备树文件(DTB)。 8. 启动内核 U-boot会将内核启动参数(包括设备树的地址)传递给内核,并通过软件跳转实现内核启动。此时,U-boot的使命就完成了,内核将接管系统的控制权。 总之,U-boot启动过程非常复杂,但是也非常重要。因为U-boot提供了系统启动所需的所有基础设施,从而保证了Linux系统的正常运行。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jacky~~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值