uboot移植准备工作三

来源:互联网 发布:windows家庭版怎么激活 编辑:程序博客网 时间:2024/06/14 05:30

1.5.1 start.S引入
u-boot.lds中找到start.S入口
1. c语言中整个项目的入口就是main函数(这是c语言规定的),所以譬如说一个有10000个.c文件的项目,第一个要分析的文件就是包含了main函数的那个文件。
2. 在uboot中因为有汇编阶段参与,因此不能直接找main.c。整个程序入口取决于链接器脚本中ENTRY声明的地方。ENTRY(_start)因此_start符号所在的文件就是整个程序的起始文件,_start所在处的代码就是整个程序的起始代码。
3. 利用SI工具搜索共_start,然后分析那个是我们所需要的文件。然后进入start.S文件中,然后进入76,77行,就是_start标号的定义处,我们就找到了整个uboot的入口代码。
4. 我们开始从start.S文件分析uboot的第一阶段代码。
5. 在SI中,如果我们知道我们要找的文件的名字,但是我们又不知道它在哪个目录下,我们要怎样找到并打开这个文件?方法是在SI中先打开右边的工程项目管理栏目,然后点击最左边那个(这个是以文件未单位来浏览的),然后再上面输入栏中输入要找的文件名字。我们在输入时,SI在不断帮我们进行匹配,即使你不记得文件名只能记得大概名字,也能帮你找到你要的文件。

1.5.2 start.S解析1
不简单的头文件包含
#include <config.h> config.h是在include目录下,这个文件不是源码中本身存在的文件。而是配置过程中生成的文件。(详见mkconfig脚本)。这个文件的内容其实又包含一个头文件:#include <configs/TQ210.h>
经过分析后,发现start.S中包含的第一个头文件就是include/configs/TQ210,这个文件是整个uboot移植时配置文件。这里面好多宏。因此这个头文件包含将include/configs/TQ210.h文件和start.S文件关联了起来。因此之后再分析start.S文件时,主要考虑就是TQ210.h
#include <version.h> include/version.h中包含了include/version_autogenerated.h,这个文件就是配置过程中自动生成的。里面就一行内容:#define U_BOOT_VERSION "U-Boot 1.3.4" 这里面定义的宏U_BOOT_VERSION的值是一个字符串,字符串中的版本号信息来至于Makefile中的配置值。这个宏在程序中会被调用,在uboot启动过程中会串口打印出uboot的版本号。那个版本号信息就是从这来的。
#include <asm/proc/domain.h>。asm目录不是uboot中的原生目录,uboot中本来是没有这个目录的。asm目录是在配置的创建的一个符号链接,实际指向的是asm-arm。 经过分析后发现,实际文件是/include/asm-arm/proc-armv/domain.h 从这里可以看出之前配置时穿件的符号链接的作用,如果没有这些符号链接则编译时根本通不过,因为找不到头文件。
思考:为什么start.S不直接包含asm-arm/proc-armv/domain.h而要用asm/proc/domain.h。这样的设计是为了可移植性。因为如果直接包含,则start.S文件和CPU架构(和硬件)有关。可移植性就差了。譬如我要把uboot移植到mips架构下,则start.S源代码中所有的头文件包含全部要修改。我们用了符号链接之后,则start.S中源代码不用改,只需要在具体的硬件移植时配置不同,创建符号链接指向的不同,则可以具有可移植性
1.5.3 start.S 解析2
启动代码的16字节头部:在裸机中讲过,在SD卡启动/Nand启动等整个镜像开头需要16字节的校验头。(mkv210image.c中。
1. 就是为了计算这个校验头),我们以前做裸机程序时根本没考虑这16字节校验头,因为:1.如果我们是usb启动直接下载的方式启动的则不需要16字节校验头(irom application note)2.如果是SD卡启动mkv210image.c中会给原镜像前加16字节的校验头。
2. Uboot这里start.S在开头位置放了16字节的填充占位,这个占位的16字节只是保证正式的image的头部确实有16字节,但是这16字节的内容是不对的,还是需要后面计算和重新填充的。
3. 异常向量表的构建:异常向量表是硬件决定的,软件只是参照硬件的设计来实现它。 异常向量表中每种异常都应该被处理,否则真遇到了这种异常就跑飞。但是我们在uboot中并非常细致的处理各种异常。 复位异常处的代码是: b reset,因此在CPU复位后真正去执行的有效代码是reset处的代码,因此rese符号处才是真正的有意义的代码开始的地方。
1.5.4 有点意思:.balignl 16,0xdeadbeef
这一句指令是让当前地址对齐排布,如果当前地址不对齐则自动向后走地址直到对齐,并且向后走的那些内存要用0xdeadbeef来填充。 0xdeadbeef这个一个十六制的数字,这个数字有意思,组成这个数字的16制全是abcdef之中的字母,而这8个字母刚好组成了英文的dead beef这个两个单词,字面意思是坏牛肉。
为什么要对齐:有时候是效率的要求,有时候是硬件的特殊要求。

1.5.5 TESX_BASE等
这个TEXT_BASE就是上个课程中分析Makefile时讲到的那个配置阶段的TEXT_BASE,其实就是我们链接时指定的uboot的链接地址(值就是c3e00000) 源代码中和配置Makefile中很多变量是可以相互运算的。简单来说这些符号的值可以从Makefile中传递到源代码中。


第三部分:start.S解析3
1. CFG_PHY_UBOOT_BASE 23e00000 uboot在DDR中的物理地址,虚拟地址是C3E00000.这两个地址相对应。
2.设置Cpu为SVC模式
msr cpsr_c, #0xd3 将cpu设置为禁止FIQ IRQ, ARM状态svc模式。其实ARM cput在复位时默认就会进入svc模式,但是这里还是使用软件将其置为svc模式。Uboot在工作是cpu一直处于svc模式。
2. 设置L2,L1cache和MMU
bl disable_l2cache 禁止L2 cache
bl set_l2cache_auxctrl_cycle L2 cache 相关初始化
bl enable_l2cache 使能L2 cache
刷新L1 cache的的icache 和dcache.
关闭mmu。 上面这5步都是和CPU的cache和mmu有关的。
2.5.3.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中存储0x3,以后备用。


2.5.3.4 设置栈(SRAM中的栈)并调用lowlevel_init
1. 第317-319行第一次设置栈。这次设置栈是在SRAM中设置的,因为当前整个代码还在SRAM中运行,此时DDR还未被初始化,此时DDR还未被初始化还不能用。栈地址0xd003600是自己指定的,指定的原则就是这块空间只给栈用,不会被别人占用。
2. 在调用函数前初始化栈,主要原因是在被调用的函数内还有再次调用函数,而BL只会将返回地址存储到LR中,但是我们只有一个LR,所以在第二次调用函数时前要先将LR入栈,否则函数返回时第一层的返回地址就丢了。

第三部分:start.S解析4
1. 使用SI的reference 功能,找到lowlevel_init函数真正的地方,是\\192.168.64.139\root\home\S5-driver\TQ210\opt\EmbedSky\TQ210\uboot_TQ210_1.3.4\board\EmbedSky\TQ210/lowlevel_init.S中。
2. 检查复位状态 : 复杂cpu允许多种复位情况,譬如直接冷上电,热启动,睡眠(低功耗)状态下的唤醒等,这些情况都属于复位。所以我们在复位代码中要去检测复位状态,来判断到底是哪种情况。 判断哪种复位的意义在于:冷上电时DDR是需要初始化才能用的;而热启动或者低功耗状态下的复位则不需要再次初始化DDR。
3. IO状态恢复:这个和上一个主线代码都无关,因此不用去管它。
4. 关闭看门狗。
5. 一些SRAM SROM 相关GPIO设置
与主线启动代码无关,不用管。
6. 供电锁存
Lowlevel_init.S的第100-104行,开发板的供电锁存。
总结:在lowlevel_init.S 前100行,中并没有做太多有意义的事情(除了关看门狗,供电锁存外),然后下面从100行才开始进行有意义的操作。


2.5.6 start.S解析5
/* when we already run in ram, we don't need to relocate U-Boot.
* and actually, memory controller must be configured before U-Boot
* is running in ram.
*/
ldr r0, =0xff000fff
bic r1, pc, r0 /* r0 <- current base addr of code */
ldr r2, _TEXT_BASE /* r1 <- original base addr in ram */
bic r2, r2, r0 /* r0 <- current base addr of code */
cmp r1, r2 /* compare r0, r1 */
beq 1f /* r0 == r1 then skip sdram init */
这几行代码的作用就是判定当前代码执行的位置在SRAM中还是在DDR中。为什么要做这个判定?原因:BL1(uboot的前一部分)在SRAM中有一份,在DDR中也有一份。因此如果是冷启动那么当前代码应该是在SRAM中运行BL1,如果是低功耗状态的复位这时候应该就是在DDR中运行的。原因二:我们判定当前运行代码的地址是有用的,可以知道后面代码的运行,譬如在lowlevel_init.S中判定当前代码的运行地址,就是为了确定要不要执行时钟初始化和初始化DDR的代码。如果当前代码是在SRAM中,说明冷启动,那么时钟和DDR都需要初始化;如果当前代码在DDR中,说明是热启动,那么不需要DDR和时钟都不用再次初始化。
bic r1 ,pc,r0 这句代码的意义是:将pc的值中的某些bit位清0,剩下一些特殊bit位赋值给r1(r0中为1的那些位清零)相当于: r1 = pc & ~(0xff000fff) ; ldr r2 ,_TEXT_BASE 加载链接地址到r2,然后将r2的相应位清0剩下特定位。
最后比较r1, r2 总结:这一段代码是通过读取当前运行地址和链接地址,然后处理两个地址后对比是否相等,来判定当前运行是在SRAM(不相等)还是在DDR中(相等)。从而决定是否跳过下面的时钟和DDR初始化。

2.5.6.2 system_clock_init:
1.使用SI搜索功能,确定这个函数就在当前文件的256行,。这个初始化时钟的过程和裸机中初始化的过程一样的,只是更加完整而且用汇编代码写的。
2.在TQ210.h中的345行。都是和时钟相关的配置值。这些宏定义就决定了210的时钟配置是多少。也就是说代码在lowle_init.S中都写好了,但是代码的设置都被宏定义在TQ210.h中了。因此,如果移植时需要更改cpu时钟设置,根本不需要更改代码,只需在TQ210.h更改配置值。

2.5.7 start.S解析6
1. mem_ctrl_asm_init
1.该函数用来初始化DDR,函数的位置在uboot/cpu/s5pc11x/s5pc110/cpu_init.S文件中。 2)该函数和裸机中初始化DDR代码是一样的。实际上裸机中初始化DDR的代码就是从这里抄的。配置值也可以从这里抄,但是当时我们自己根据自己理解+抄袭整出来的一份。3)配置值中其他配置值参考裸机中的解释即可明白,有一个和裸机中讲的不一样。DMC0_MEMCONFIG_0,在裸机中配置为0x20e01323,在uboot中配置为0x30f01313,这个配置不同导致结果不用。 在裸机中DMC0的256MB内存地址范围是0X20000000-0X2FFFFFFF; 在uboot中DMC0的256MB内存地址范围为0X30000000-0X3FFFFFFF. 之前在裸机中时配置为2开头的地址,当时并没有说可以配置3开头。从分析九鼎移植的uboot可以看出;DMC0上允许的地址范围:20000000-3ffffffff(一共512MB),而我们实际只接了256MB物理内存,SOC允许我们给这256MB挑选地址范围。

总结一下:在uboot中,可用的物理地址范围为:0x30000000-0x4fffffff。一共512MB,其中30000000-3fffffff为DMC0,40000000-4FFFFFFF为DMC1。 我们需要的内存配置值在TQ210.h在468行之后,分析的时候要注意条件编译的条件,配置头文件中考虑不同时钟配置下的内存配置值,这个的主要目的是让不同时钟需求的客户都能找到合适自己的内存配置值。 在uboot中DMC0和DMC1都工作了,所以在裸机中只要把uboot中的配置值和配置代码全部移植过去,应该是能够让DMC0和DMC1都工作的。

2.5.7.2 uart_asm_init
1.这个函数用来初始化串口 ,初始化完了后通过串口发送一个’O’
2.5.7.3 tzpc_init 这个没用过。

2.5.7.4 pop {pc} 以返回
1.返回前通过串口打印’K’
分析:lowlevel_init.S执行完如果每次那么就会串口打印出“OK”字样。这应该是我们uboot中看到的最早的输出信息。


2.5.8 start.S解析7
1. lowlevel_init.S中总共做了哪些事情:
检查复位状态,IO恢复 关看门狗,开发板供电锁存,时钟初始化,ddr初始化 。串口初始化并打印’O’ TZPC初始化。 打印’K’ .

2.5.8.1 再次设置栈(ddr中的栈)
1. 再次开发板供电锁存。第一次,做2次是不会错的。第二,做2次则第2次无意义;做代码移植时有一个古怪谨慎保守策略就是尽量添加代码而不要删除代码。
2. 之前在调用lowlevel_init程序前设置过1次栈,那时候因为DDR尚未初始化,因此程序执行都在SRAM中,所以在SRAM中分配了一部分内存作为栈,本次因为DDR已经被初始化了,因此要把栈挪移到DDR中,所以要重新设置栈,这是第二次(331),这里实际设置的栈地址为33e00000,刚好在uboot的代码段的下面紧挨着。
3. 为什么要再次设置栈?ddr已经初始化了,已经有大片内存可以用了,没必要再把栈放在SRAM中;原来SRAM中内存大小空间有限,栈放在那里要注意不能使用过多的栈,否则栈会溢出,我们及时将栈迁移到DDR中也是为了尽可能避免栈使用时候的小心翼翼。
感慨:uboot的启动阶段主要技巧在于小范围内有限条件下的辗转腾挪。
2.5.8.2 再次判断当前地址以决定是否重定位
1. 再次用相同的代码判断运行地址是在SRAM中还是DDR中,不过本次判断的目的不同,这次判断时为了决定是否进行uboot的relocate。
2. 冷启动时当前情况是uboot的前一部分(16kb或者8kb)开机自动从sd卡加载到sram中正在运行,uboot的第二部分(其实是整个buoot)还躺在SD卡的某个扇区开头的N个扇区中,此时uboot的第一阶段已经即将结束(第一阶段该做的事基本做完了),结束之前要把第二部分加载到DDR中链接地址处(33e00000),整个加载过程就叫重定义。
2.5.9 uboot重定位详解:
1. 0xD0037488这个内存地址在SRAM中,这个地址中的值是被硬件自动设置的。硬件根据我们世界电路中SD卡在哪个通道中,会将这个地址中的值设置为相应的数字。譬如我们从SD0通道启动时,这个值为EB00000;从SD2通道启动时,这个值为EB2000000.
2. 我们在start.S的260行确定了从MMCSD启动,然后又在278行将#boot_mmcsd写入INF_REG3寄存器中存储着。然后再322行读出来,在和#boot_mmcsd去比较,确定是从mmcsd启动。最终调到mmcsd_boot函数去执行重定位动作。
3. 真正的重定位是通过调用movi_b12_copy函数。在uboot/cpu/s5pc11x/movi.c中。是一个c语言的函数
4. copy_bl2(2,MOVI_BL2_POS,MOVI_BL2_BLKCNT,CFG_PHY_UBOOT_BASE, 0) 参数分析:2表示通道; MOVI_BL2_ POS:是uboot的第二部分在SD卡中的开始扇区,这个扇区数字必须和烧录uboot时烧录的位置相同;MOVI_BL2_BLKCNT是uboot的长度占用的扇区数;CFG_PHY_UBOOT_BASE是重定位时将uboot的第二部分复制到DDR中的起始地址(33E00000)。
2.5.10 start.S解析9
1. 物理地址就是物理设备设计生产时赋予的地址。像裸机中使用的寄存器的地址就是CPU设计时指定的,这个就是物理地址。物理地址是硬件编码的,是设计生产确定好的,一旦确定了就不能改了。
2. 一个事实就是:寄存器的物理地址无法通过编程修改的,是多少就是多少,只能通过查询数据手册获得并操作。坏处就是不够灵活。一个解决方案就是使用虚拟地址。
3. 虚拟地址意思就是我们软件操作和硬件操作之间增加一个层次,叫做虚拟地址映射层。有了虚拟地址映射后,软件操作只需要给虚拟地址,硬件操作还是用原来的物理地址,映射层建立一个虚拟地址到物理地址的映射表。当我们软件运行时,软件中使用的虚拟地址在映射表中查询得到对应的物理地址再发给硬件去执行。(虚拟地址到物理地址的映射是不可能通过软件来实现的)
2.5.10.2 mmu单元的作用
1. mmu就是memory management unit 内存管理单元。Mmu实际上就是soc中一个硬件单元,它的主要功能就是实现虚拟地址到物理地址的映射。
2.mmu单元在cp15协处理器进行控制,也就是说要操控MMU进行虚拟地址映射,方法就是对CP15协处理器的寄存器进行编程。
2,5,10.3 地址映射的额外收益1:访问控制
1. 访问控制就是在管理上对内存进行分块,然后每块进行独立的虚拟地址映射,然后再每一块的映射关系中同时还实现了访问控制(对该块可读,可写,只读,只写,不可访问等控制)。
2. 回想在c语言中编程中经常会出现一个错误: segmentation fault。实际上这个段错误就和mmu实现的访问控制有关。当前程序只能操作自己有权操作的地址范围(若干个内存块),如果当前程序指针出错访问了不该访问的内存块则就会触发段错误。
2.5.10.4 地址映射的额外收益2:cache
1. cache的工作和虚拟地址映射有关系。
2. cache是快速缓存,意思就是比cpu慢但是比ddr块。CPU嫌DDR太慢,于是乎把一些DDR中常用的内容事先读取缓存在CACHE中,然后cpu每次需要找东西时先在cache中找。如果cache中就直接用cache中的;如果cache中没有才会去DDR中寻找。
2.5.11 start.S解析8
2.5.11.1 使能域访问(cp15的c3寄存器)
1. cp15协处理器内部有c0到c15共16个寄存器,这些寄存器每一个都有自己的作用。我们通过mrc和mcr指令来访问这些寄存器。所谓的操作cp协处理器其实就是操作cp15的这些寄存器。
2. c3寄存器在mmu中的作用是控制域访问。与访问是和mmu的访问控制有关的。

2.5.11.2 设置TTB(cp15的c2寄存器)
1. TTB就是translation table base 转换表基地址。首先要明白什么是TT(translation table转换表),TTB其实就是转换表的基地址。
2. 转换表是建立一套虚拟地址映射的关键。转换表分2部分,表索引和表项。表索引对应虚拟地址,表项对应物理地址。一对表索引和表项构成一个转换表单元,能够对一个内存块进行虚拟地址转换。(映射中基本规定中规定了内存映射和管理是以块为单位的,至于块有多大,要看你的MMU的支持和你自己选择。在ARM中支持3中块大小,1kb (细表)4kb(粗表) 1MB(段))。真正的转换表就是由若干个转换表单元构成,每个单元负责1个内存块,总体的转换表负责整个内存空间(0-4G)的映射。
3. 整个建立虚拟地址映射的主要工作就是建立这张转换表
4. 转换表放置在内存中的,放置时要求起始地址在内存中要xx位对齐。转换表不需要软件去干涉使用,而是将基地址TTB设置到cp15的c2寄存器中,然后MMU工作时会自动去查转换表。

2.5.11.3 使能MMU单元(cp15的从c1寄存器)
1. cp15的寄存器的bit0控制MMU的开关。只要将这一个bit置1即可开启MMU。开启MMU之后上层软件层的地址就必须经过TT的转换才能发给下层物理层去执行。

2.5.12 start.S解析10
2.5.12.1 宏
ARM的段式映射中长度为1MB,因此一个映射单元只能管1MB内存,那我们整个4G范围内需要4G/1MB=4096个映射单元,也就是说这个数组的元素个数是4096.实际上我们做的时候并没有依次单个处理这4096个单元,而是把4096个分成几部分,然后每部分用for循环做相同的处理。

宏观上理解转换表:整个转换表可以看作是一个int类型的数组,数组的一个元素就是一个表索引和表项的单元。数组中的元素值就是表项,这个元素的数组下标就是表索引。

结论:虚拟地址映射只是把虚拟地址的c0000000开头的256MB映射到了DMC0的30000000开头的256MB物理内存上去了。其他的虚拟地址空间根本没有动,还是原样映射的。
思考:为什么配置时将连接地址设置为C3E00000,因为这个地址将来会被映射到33e00000这个物理地址。

2.5.13 start.S 解析
1.再次设置栈 第三次设置栈。这次设置栈还是在DDR中,之前虽然已经在DDR中设置栈了,但是本次设置栈的目的是将栈放在比较合适(安全,紧凑而不浪费)的地方。
2.我们实际将栈设置在uboot起始地址上方2MB,这样安全的栈空间是2MB-UBOOT大小-0x1000 = 1.8MB
3.清理bss段代码和裸机中一样,这样bss段的开头和结尾是从链接器脚本u-boot.lds得来的。
4.start_armboot是uboot/lib_arm/board.c 中,这是一个c语言实现的函数。这个函数就是uboot的第二阶段。这句代码的作用就是将uboot第二阶段执行的函数的地址传给pc,实际上就是使用一个远眺转直接跳转到DDR中的第二阶段开始地址处。
5.远眺转的含义就是这句话加载的地址和当前运行地址无关,而和链接地址有关。因此这个远眺转可以实现从SRAM中的第一阶段跳转到DDR中的第二阶段。
3.这里这个远眺转就是uboot第一阶段和第二阶段的分界线。
总结:uboot第一阶段做了哪些工作:
1. 构建异常向量表
2. 设置CPU为SVC模式
3. 关看门狗
4. 开发板供电自锁
5. 时钟初始化
6. DDR初始化
7. 串口初始化并打印“OK”
8. 重定位
9. 建立映射表并开启MMU
10. 跳转第二阶段。
0 0
原创粉丝点击