2.5.uboot源码分析1-启动第一阶段

2.5.1.start.S引入

2.5.1.1、u-boot.lds中找到start.S入口

(1)在C语言中整个项目的入口就是main函数(这是C语言规定的),所以譬如说一个有10000个.c文件的项目,第一个要分析的文件就是包含了main函数的那个文件。


(2)在uboot中因为有汇编阶段参与,因此不能直接找main.c。整个程序的入口取决于链接脚本中ENTRY声明的地方。 ENTRY(_start)因此_start符号所在的文件就是整个程序的起始文件, _start所在处的代码就是整个程序的起始代码。

2.5.1.2、SourceInsight中如何找到文件

搜索文件
2.5.uboot源码分析1-启动第一阶段_第1张图片

行号显示
2.5.uboot源码分析1-启动第一阶段_第2张图片

(1)当前状况:我们知道在uboot中的1000多个文件中有一个符号叫_start,但是我们不知道这个符号在哪个文件中。这种情况下要查找一个符号在所有项目中文件中的引用,要使用SourceInsight的搜索功能。


(2)利用SI工具搜索到一共7个_start,然后分析搜索出来的7处,发现有2个是api_example,2个是onenand相关的,都不是我们要找的。剩下3个都在uboot/cpu/s5pc11x/start.S文件中。


(3)然后进入start.S文件中,发现57行中就是_start标号的定义处,于是乎我们就找到了整个uboot的入口代码,就是第57行。

.globl _start:声明_start为全局变量,将来一别的地方调用

两种写法:


2.5.1.3、SI中找文件技巧

(1)以上,找到了start.S文件,下面我们就从start.S文件开始分析uboot第一阶段。

(2)在SI中,如果我们知道我们要找的文件的名字,但是我们又不知道他在哪个目录下,我们要怎样找到并打开这个文件?方法是在SI中先打开右边的工程项目管理栏目,然后点击最左边那个(这个是以文件为单位来浏览的),然后在上面输入栏中输入要找的文件的名字。我们在输入的时候,SI在不断帮我们进行匹配,即使你不记得文件的全名只是大概记得名字,也能帮助你找到你要找的文件。

2.5.uboot源码分析1-启动第一阶段_第3张图片

2.5.2.start.S解析1

2.5.2.1、不简单的头文件包含

(1) #include 。config.h是在include目录下的,这个文件不是源码中本身存在的文件,而是配置过程中自动生成的文件。(详见mkconfig脚本\winshare\uboot-jiuding\mkconfig)。这个文件的内容其实是包含了一个头文件:#include ".

2.5.uboot源码分析1-启动第一阶段_第4张图片


(2)经过分析后,发现start.S中包含的第一个头文件就是:include/configs/x210_sd.h,这个文件是整个uboot移植时的配置文件。这里面是好多宏。因此这个头文件包含将include/configs/x210_sd.h文件和start.S文件关联了起来。因此之后在分析start.S文件时,主要要考虑的就是x210_sd.h文件。

2.5.uboot源码分析1-启动第一阶段_第5张图片
(3)#include 。include/version.h中包含了include/version_autogenerated.h,这个头文件就是配置过程中自动生成的。里面就一行内容:#define U_BOOT_VERSION "U-Boot 1.3.4"。这里面定义的宏U_BOOT_VERSION的值是一个字符串,字符串中的版本号信息来自于Makefile中的配置值。这个宏在程序中会被调用,在uboot启动过程中会串口打印出uboot的版本号,那个版本号信息就是从这来的。

2.5.uboot源码分析1-启动第一阶段_第6张图片


(4)#include 。asm目录不是uboot中的原生目录,uboot中本来是没有这个目录的。asm目录是配置时创建的一个符号链接,实际指向的是就是asm-arm(详解上一章节分析mkconfig脚本时).

2.5.uboot源码分析1-启动第一阶段_第7张图片
(5)经过分析后发现,实际文件是:include/asm-arm/proc-armv/domain.h


(6)从这里可以看出之前配置时创建的符号链接的作用,如果没有这些符号链接则编译时根本通不过,因为找不到头文件。(所以uboot不能在windows的共享文件夹下配置编译,因为windows中没有符号链接)


思考:为什么start.S不直接包含asm-arm/proc-armv/domain.h,而要用asm/proc/domain.h。这样的设计主要是为了可移植性。因为如果直接包含,则start
.S文件和CPU架构(和硬件)有关了,可移植性就差了。譬如我要把uboot移植到mips架构下,则start.S源代码中所有的头文件包含全部要修改。我们用了符号链接之后,则start.S中源代码不用改,只需要在具体的硬件移植时配置不同,创建的符号链接指向的不同,则可以具有可移植性。

(7)

这一段代码没什么用,因为x210_sd.h中根本没有定义
CONFIG_ENABLE_MMU,所以下面也主不会成立
#ifndef CONFIG_ENABLE_MMU               
#ifndef CFG_PHY_UBOOT_BASE
#define CFG_PHY_UBOOT_BASE	CFG_UBOOT_BASE
#endif
#endif

2.5.3.start.S解析2

2.5.3.1、启动代码的16字节头部

(1)裸机中讲过,在SD卡启动/Nand启动等整个镜像开头需要 16字节的校验头。(mkv210image.c中就是为了计算这个校验头)。我们以前做裸机程序时根本没考虑这16字节校验头,因为:1、如果我们是usb启动直接下载的方式启动的则不需要16字节校验头(irom application note);2、如果是SD卡启动mkv210image.c中会给原镜像前加16字节的校验头。

2.5.uboot源码分析1-启动第一阶段_第8张图片


(2)uboot这里start.S中在开头位置放了16字节的填充占位,这个占位的16字节只是保证正式的image的头部确实有16字节,但是这16字节的内容是不对的,还是需要后面去计算校验和然后重新填充的。

\uboot-jiuding\sd_fusing\C110-EVT1-mkbl1.c 计算校验和然后重新填充的文件在这里相当于裸中的mkv210image.c

2.5.3.2、异常向量表的构建

(1)异常向量表是硬件决定的,软件只是参照硬件的设计来实现它。


(2)异常向量表中每种异常都应该被处理,否则真遇到了这种异常就跑飞了。但是我们在uboot中并未非常细致的处理各种异常。


(3)复位异常处的代码是:b reset,因此在CPU复位后真正去执行的有效代码是reset处的代码,因此reset符号处才是真正的有意义的代码开始的地方。

2.5.3.3、有点意思的deadbeef

(1).balignl 16,0xdeadbeef. 这一句指令是 让当前地址对齐排布,如果当前地址不对齐则自动向后走地址直到对齐,并且向后走的那些内存要用0xdeadbeef来填充。


(2)0xdeadbeef这是一个十六进制的数字,这个数字很有意思,组成这个数字的十六进制数全是abcdef之中的字母,而且这8个字母刚好组成了英文的dead beef这两个单词,字面意思是坏牛肉。


(3)为什么要对齐访问?有时候是效率的要求,有时候是硬件的特殊要求。

2.5.3.4、TEXT_BASE等

(1)第100行这个TEXT_BASE就是上个课程中分析Makefile时讲到的那个配置阶段的TEXT_BASE,其实就是我们链接时指定的uboot的链接地址。(值就是c3e00000)

(2) TEXT_PHY_BASE:物理地址基址

     MEMORY_BASE_ADDRESS:DDR内存的一个起始地址2.5.uboot源码分析1-启动第一阶段_第9张图片  

 (3)源代码中和配置Makefile中很多变量是可以互相运送的。简单来说有些符号的值可以从Makefile中传递到源代码中。

2.5.4.start.S解析3

(1)CFG_PHY_UBOOT_BASE       33e00000         uboot在DDR中的物理地址

2.5.4.1、设置CPU为SVC模式

(1)msr cpsr_c, #0xd3 将CPU设置为禁止FIQ IRQ,ARM状态,SVC模式。

(2)其实ARM CPU在复位时默认就会进入SVC模式,但是这里还是使用软件将其置为SVC模式。整个uboot工作时CPU一直处于SVC模式。

2.5.4.2、设置L2、L1cache和MMU

(1)bl disable_l2cache // 禁止L2 cache

(2)bl set_l2cache_auxctrl_cycle// l2 cache相关初始化

(3)bl enable_l2cache// 使能l2 cache

(4)刷新L1 cache的icache和dcache。

(5)关闭MMU

总结:上面这5步都是和CPU的cache和mmu有关的,不用去细看,大概知道即可。

2.5.4.3、识别并暂存启动介质选择

(1)从哪里启动是由SoC的OM5:OM0这6个引脚的高低电平决定的。


(2)实际上在210内部有一个寄存器(地址是0xE0000004),这个寄存器中的值是硬件根据OM引脚的设置而自动设置值的。这个值反映的就是OM引脚的接法(电平高低),也就是真正的启动介质是谁。

(3)我们代码中可以通过读取这个寄存器的值然后判断其值来确定当前选中的启动介质是Nand还是SD还是其他的。

(4)start.S的225-227行执行完后,在r2寄存器中存储了一个数字,这个数字等于某个特定值时就表示SD启动,等于另一个特定值时表示从Nand启动····

(5)260行中给r3中赋值#BOOT_MMCSD(0x03),这个在SD启动时实际会被执行,因此执行完这一段代码后r3中存储了0x03,以后备用。

2.5.4.4、设置栈(SRAM中的栈)并调用lowlevel_init

(1)284-286行第一次设置栈。这次设置栈是在SRAM中设置的,因为当前整个代码还在SRAM中运行,此时DDR还未被初始化还不能用。栈地址0xd0036000是自己指定的,指定的原则就是这块空间只给栈用,不会被别人占用。

(2)在调用函数前初始化栈,主要原因是在被调用的函数内还有再次调用函数,而BL只会将返回地址存储到LR中,但是我们只有一个LR,所以在第二层调用函数前要先将LR入栈,否则函数返回时第一层的返回地址就丢了。

2.5.5.start.S解析4

(1)使用SourceInsight的Reference功能,找到lowlevel_init函数真正的地方,是在uboot/board/samsumg/x210/lowlevel_init.S中。

1、 分析star.S 288行:bl lowlevel_init时不能右键Jump to definition跳转到lowlevel_init函数,因为这样跳转会跳
到onenand_ip/目录下,不是我们想要的,那如何找到正确函数所在位置?选中lowlevel_init单击Lookup References搜索
\board\samsung\x210):lowlevel_init:  这个才是我们想要的

2、push     {lr}:就是为了把lr压栈,为什么要把lr压栈,因为我们进到这个函数后lr就是我们的返回地址,往下看,在
这个lowlevel_init函数内还有用bl调用其它函数的,如果在这个函数前没有push   {ls}保存返回地址的话,后面调用函数
后,就无法返回了,相当于递归调用。这也是为什么我们一开始就先设置栈

2.5.5.1、检查复位状态

46~52行

(1)复杂CPU允许多种复位情况。譬如直接冷上电、热启动、睡眠(低功耗)状态下的唤醒等,这些情况都属于复位。所以我们在复位代码中要去检测复位状态,来判断到底是哪种情况。

(2)判断哪种复位的意义在于:冷上电时DDR是需要初始化才能用的;而热启动或者低功耗状态下的复位则不需要再次初始化DDR。

2.5.5.2、IO状态恢复

54~59行
(1)这个和上一个和主线启动代码都无关,因此不用去管他。

2.5.5.3、关看门狗

(1)参考裸机中看门狗章节


2.5.5.4、一些SRAM SROM相关GPIO设置

(1)与主线启动代码无关,不用管

2.5.5.5、供电锁存

(1)lowlevel_init.S的第100-104行,开发板供电锁存。
总结:在前100行,lowlevel_init.S中并没有做太多有意义的事情(除了关看门狗、供电锁存外),然后下面从110行才开始进行有意义的操作。

2.5.6.start.S解析5

2.5.6.1、判断当前代码执行位置

(1)lowlevel_init.S的110-115行。


(2)这几行代码的作用就是判定当前代码执行的位置在SRAM中还是在DDR中。为什么要做这个判定?原因1:BL1(uboot的前一部分)在SRAM中有一份,在DDR中也有一份,因此如果是冷启动那么当前代码应该是在SRAM中运行的BL1,如果是低功耗状态的复位这时候应该就是在DDR中运行的。原因2:我们判定当前运行代码的地址是有用的,可以指导后面代码的运行。譬如在lowlevel_init.S中判定当前代码的运行地址,就是为了确定要不要执行时钟初始化和初始化DDR的代码。如果当前代码是在SRAM中,说明冷启动,那么时钟和DDR都需要初始化;如果当前代码是在DDR中,那么说明是热启动则时钟和DDR都不用再次初始化。


(2)bic r1, pc, r0这句代码的意义是:将pc的值中的某些bit位清0,剩下一些特殊的bit位赋值给r1(r0中为1的那些位清零)相等于:r1 = pc & ~(ff000fff)
ldr r2, _TEXT_BASE加载链接地址到r2,然后将r2的相应位清0剩下特定位。


(3)最后比较r1和r2.
总结:这一段代码是通过读取当前运行地址和链接地址,然后处理两个地址后对比是否相等,来判定当前运行是在SRAM中(不相等)还是DDR中(相等)。从而决定是否跳过下面的时钟和DDR初始化。

2.5.6.2、system_clock_init

(1)使用SI搜索功能,确定这个函数就在当前文件的205行,一直到第385行。这个初始化时钟的过程和裸机中初始化的过程一样的,只是更加完整而且是用汇编代码写的。
(2)在x210_sd.h中300行到428行,都是和时钟相关的配置值。这些宏定义就决定了210的时钟配置是多少。也就是说代码在lowlevel_init.S中都写好了,但是代码的设置值都被宏定义在x210_sd.h中了。因此,如果移植时需要更改CPU的时钟设置,根本不需要动代码,只需要在x210_sd.h中更改配置值即可。

2.5.7.start.S解析6

2.5.7.1、mem_ctrl_asm_init

(1)该函数用来初始化DDR


(2)函数位置在uboot/cpu/s5pc11x/s5pc110/cpu_init.S文件中。


(3)该函数和裸机中初始化DDR代码是一样的。实际上裸机中初始化DDR的代码就是从这里抄的。配置值也可以从这里抄,但是当时我自己根据理解+抄袭整出来的一份。


(4)配置值中其他配置值参考裸机中的解释即可明白,有一个和裸机中讲的不一样。DMC0_MEMCONFIG_0,在裸机中配置值为0x20E01323;在uboot中配置为0x30F01313.这个配置不同就导致结果不同。在 裸机中DMC0的256MB内存地址范围是0x20000000-0x2FFFFFFF; 在uboot中DMC0的256MB内存地址范围为0x30000000-0x3FFFFFFF。

(5)之前在裸机中时配置为2开头的地址,当时并没有说可以配置为3开头。从分析九鼎移植的uboot可以看出:DMC0上允许的地址范围是20000000-3FFFFFFF(一共是512MB),而我们实际只接了256MB物理内存,SoC允许我们给这256MB挑选地址范围。


(6)总结一下:在uboot中,可用的物理地址范围为:0x30000000-0x4FFFFFFF。一共512MB,其中30000000-3FFFFFFF为DMC0,40000000-4FFFFFFF为DMC1。


(7)我们需要的内存配置值在x210_sd.h的438行到468行之间。分析的时候要注意条件编译的条件,配置头文件中考虑了不同时钟配置下的内存配置值,这个的主要目的是让不同时钟需求的客户都能找到合适自己的内存配置值。


(8)在uboot中DMC0和DMC1都工作了,所以在裸机中只要把uboot中的配置值和配置代码全部移植过去,应该是能够让DMC0和DMC1都工作的。


2.5.7.2、uart_asm_init

(1)这个函数用来初始化串口
(2)初始化完了后通过串口发送了一个'O'


2.5.7.3、tzpc_init

(1)trust zone初始化,没搞过,不管


2.5.7.4、pop {pc}以返回

(1)返回前通过串口打印'K'


分析;lowlevel_init.S执行完如果没错那么就会串口打印出"OK"字样。这应该是我们uboot中看到的最早的输出信息。

2.5.8.start.S解析7

总结回顾:lowlevel_init.S中总共做了哪些事情:
检查复位状态、IO恢复、关看门狗、开发板供电锁存、时钟初始化、DDR初始化、串口初始化并打印'O'、tzpc初始化、打印'K'。
其中值得关注的:关看门狗、开发板供电锁存、时钟初始化、DDR初始化、打印"OK"


2.5.8.1、再次设置栈(DDR中的栈)

(1)再次开发板供电锁存。第一,做2次是不会错的;第二,做2次则第2次无意义;做代码移植时有一个古怪谨慎保守策略就是尽量添加代码而不要删除代码。


(2)之前在调用lowlevel_init程序前设置过1次栈(start.S 284-287行),那时候因为DDR尚未初始化,因此程序执行都是在SRAM中,所以在SRAM中分配了一部分内存作为栈。本次因为DDR已经被初始化了,因此要把栈挪移到DDR中,所以要重新设置栈,这是第二次(start.S 297-299行);这里实际设置的栈的地址是33E00000,刚好在uboot的代码段的下面紧挨着。


(3)为什么要再次设置栈?DDR已经初始化了,已经有大片内存可以用了,没必要再把栈放在SRAM中可怜兮兮的了;原来SRAM中内存大小空间有限,栈放在那里要注意不能使用过多的栈否则栈会溢出,我们及时将栈迁移到DDR中也是为了尽可能避免栈使用时候的小心翼翼。
感慨:uboot的启动阶段主要技巧就在于小范围内有限条件下的辗转腾挪。


2.5.8.2、再次判断当前地址以决定是否重定位

(1)再次用相同的代码判断运行地址是在SRAM中还是DDR中,不过本次判断的目的不同(上次判断是为了决定是否要执行初始化时钟和DDR的代码)这次判断是为了决定是否进行uboot的relocate。


(2)冷启动时当前情况是uboot的前一部分(16kb或者8kb)开机自动从SD卡加载到SRAM中正在运行,uboot的第二部分(其实第二部分是整个uboot)还躺在SD卡的某个扇区开头的N个扇区中。此时uboot的第一阶段已经即将结束了(第一阶段该做的事基本做完了),结束之前要把第二部分加载到DDR中链接地址处(33e00000),这个加载过程就叫重定位。

2.5.9.uboot重定位详解

(1)D0037488这个内存地址在SRAM中,这个地址中的值是被硬件自动设置的。硬件根据我们实际电路中SD卡在哪个通道中,会将这个地址中的值设置为相应的数字。譬如我们从SD0通道启动时,这个值为EB000000;从SD2通道启动时,这个值为EB200000

2.5.uboot源码分析1-启动第一阶段_第10张图片
(2)我们在start.S的260行确定了从MMCSD启动,然后又在278行将#BOOT_MMCSD写入了INF_REG3寄存器中存储着。然后又在322行读出来,再和#BOOT_MMCSD去比较,确定是从MMCSD启动。最终跳转到mmcsd_boot函数中去执行重定位动作。


(3)真正的重定位是通过调用movi_bl2_copy函数完成的,在uboot/cpu/s5pc11x/movi.c中。是一个C语言的函数
(4)copy_bl2(2, MOVI_BL2_POS, MOVI_BL2_BLKCNT,
CFG_PHY_UBOOT_BASE, 0);


分析参数:2表示通道2;MOVI_BL2_POS是uboot的第二部分在SD卡中的开始扇区,这个扇区数字必须和烧录uboot时烧录的位置相同;MOVI_BL2_BLKCNT是uboot的长度占用的扇区数;CFG_PHY_UBOOT_BASE是重定位时将uboot的第二部分复制到DDR中的起始地址(33E00000).

















你可能感兴趣的:(2.5.uboot源码分析1-启动第一阶段)