您好,登录后才能下订单哦!
uboot源码分析1-启动第一阶段
1、starts.S是我们uboot源码的第一阶段:
从u-boot.lds链接脚本中也可以看出start.S是我们整个程序的入口处,怎么看出的呢,因为在链接脚本中有个ENTRY(_start)声明了_start是程序的入口。所以_start符号所在的文件,就是我们整个程序的起始文件,_start所在处的代码就是我们整个程序的起始代码。
2、我们知道了程序的入口是_start这个符号,但是却不知道是在哪一个文件中,所以要SI进行查找搜索,点击SI的大R进行搜索,lookup reference ,快捷键是CTRL + / 由链接脚本也可知道是在uboot/cpu/s5pc11x/
3、start.S解析1
(1)开始包含了#include<config.h>头文件,我们知道,这个config.h的头文件是在mkconfig脚本中配置在配置时生成的,echo "#include <configs/$1.h>" >>config.h,并且里面的内容是包含了一个configs下的x210_sd.h这个头文件,所以意思就是包含了这个configs下的x210_sd.h头文件。
我们在住makefile中的x210_nand_config类似的目标,_config除去后就成了我们mkconfig脚本中的$1,所以在mkconfig脚本中最后那个在config.h中写入的信息#include<configs/$1.h>中的$1就会不同,不同的配置,这里就会导致我们在start.S包含config.h这个头文件最终包含的头文件不同。而这个不同的头文件中的内容就是我们移植uboot的关键所在。
(2)#include <version.h> 这个头文件中,包含了这个信息#include "version_autogenerated.h",这个里面是uboot版本号的宏,这个版本号来自于makefile中开始的配置。将来在启动uboot的时候,串口打印出来的uboot版本号
(3)#include <asm/proc/domain.h>
asm是我们在mkconfig脚本配置的时候,一个符号链接,它指向了asm-arm,所以实际是这个文件夹下的。proc一样是符号链接。所以uboot不能在我们Windows共享文件夹下去编译,因为Windows中是没有符号链接的
(4)#include <regs.h> 其实就是我们的S5PC110.h 是在mkconfig脚本配置时,用符号链接的方式指向的,实际是指向的,uboot/include/s5pc110.h这个文件
4、start.S解析2
(1)#if defined(CONFIG_EVT1) && !defined(CONFIG_FUSED)
.word 0x2000
.word 0x0
.word 0x0
.word 0x0
#endif
.word是我们GNU汇编中的伪指令,相当于定义了一个变量,内存地址就当前位置的内存地址。连续四个word相当于定义了一个int类型的数据,里面有四个元素,每个元素占4个四节,所以这里占了16个字节
在裸机中,如果我们用SD/NAND等启动话,镜像的头部是需要16B的校验头的。我们那时是用,mk210vp_w_picpath.c去计算校验头填充到镜像的头部的,
但是在uboot中,镜像头部的16B没有去计算,是占位的,是放了16字节的东西去占位的,这16字节的内容是不对的,还是需要我们去计算校验并且重新填充的。
这占位的十六个字节,决定了,我们在将来计算校验和时,是否考虑前16个字节,在我们的uboot的sd_fusing下的E什么.c那个文件中,其实和我们之前的那个mk210_p_w_picpath.c很像,可以说就是
(2)异常向量表的构建(可以叫做一级中断向量表)
b reset //复位异常
ldr pc, _undefined_instruction //未定义指令异常
ldr pc, _software_interrupt //软中断异常
ldr pc, _prefetch_abort //预处理异常
ldr pc, _data_abort //数据异常
ldr pc, _not_used //
ldr pc, _irq //IRQ中断
ldr pc, _fiq //快速中断
当发生异常时,CPU就会长跳转到异常向量对应的的地方去运行执行代码。
异常向量表是硬件决定的,我们软件只是参照硬件的设计,去实现它。
正常情况下,每种异常我们都应该考虑,并且进行处理,但是uboot中,并没有完全的做好,因为uboot在内存中执行的时间很短,并且uboot的主要任务是启动内核,而且uboot就算真的遇到了异常,跑飞了,我们也可以进行复位,重启。
可以看见,我们复位异常时 b reset 所以当我们在复位的时候,真正开始执行的代码应该是reset异常对应的代码。
(3).balignl 16,0xdeadbeef
.balignl 是一个伪指令,目的是为了让当前内存地址对齐排布。如果没有按照16对齐,则内存地址向后走,直到对齐为止,并且内存地址向后走的同时,地址中的内容用0xdeadbeef进行填充。
为什么要对齐:一方面是我为了效率,另一方面是硬件的特殊需求
(4)TEXT_BASE
在uboot源代码没有配置编译的时候,这个TEXT_BASE是找不到定义的地方的在源码中,只能找到引用他的地方。
这个TEXT_BASE其实就是我们在makefile中配置时出现的,就是我们代码在链接时指定的链接地址,值就是那个0XC3E00000,
_TEXT_BASE:
.word TEXT_BASE 其实这两句代码就相当于定义了一个指针,_TEXT_BASE就相当于这个指针(因为指针本身就是地址,在汇编中,标号也就是相当于地址),.word相当于int类型的,TEXT_BASE相当于这个指针的值,也就是说,这个指针指向了一个int类型的东西,指针的值是TEXT_BASE。也就是说,这个_TEXT_BASE的起始地址开始的地方连续4个字节地址,里面放了一个int类型的,4字节的数TEXT_BASE
5、start.S解析3
(5)_TEXT_PHY_BASE:
_TEXT_PHY_BASE:
.word CFG_PHY_UBOOT_BASE
字面意思是物理地址,查找这个CFG_PHY_UBOOT_BASE值,可以看到这个值是DDR的起始地址0x30000000 + 0x3e00000,所以这个_TEXT_PHY_BASE为起始地址的连续四个字节的地址中放的数是0x33e00000,所以uboot在DDR中的物理地址就是这个值,那个0XC3E00000是虚拟地址,这个0x33e00000是物理地址,两者相对应的
将来我们可以直接用ldr的方式加载_TEXT_BASE或_TEXT_PHY_BASE
(5)在uboot中我们一般是不用中断的
(6)msr cpsr_c, #0xd3 @ I & F disable, Mode: 0x13 - SVC
像cpsr状态寄存器中的c位 写入0xd3
意思就是进制IRQ FIR中断,arm状态,SVC模式(特权模式)
(7)设置L2 L1 cache MMU
bl disable_l2cache //禁止 L2 cache 在CPU初始化时
bl set_l2cache_auxctrl_cycle // L2 cache 相关的
bl enable_l2cache // 使能了 L2 cache
Invalidate L1 I/D icache (指令cache) dcache(数据cache)
/*
* disable MMU stuff and caches 关MMU,因为刚开始还没有做虚拟地址映射呢,所以先关掉
*/
(8)225行(OM引脚相关高低电平的寄存器,0xe0000004,这个寄存器的值反应了OM引脚的接法,启动介质的选择)
ldr r0, =PRO_ID_BASE
ldr r1, [r0,#OMR_OFFSET]
bic r2, r1, #0xffffffc1
PRO_ID_BASE 是因为你包含了regs.h得到的,也就是包含了s5pc110.h得到的,这个值是0xe0000000,OMR_OFFSET是一个偏移量,偏移量的值是0x4,r1寄存器的值是0xe0000004,可以看下加载这个地址(对应的寄存器)的内容是什么。
将这个寄存器中的0XFFFFFFC1这些位清0后的值放入了 r2中,r2中的值就记录了其启动方式是哪一种,这个寄存器我们的数据手册中是找不到的,只要三星公司的工程师知道
(9)260行
/* SD/MMC BOOT */
cmp r2, #0xc
moveq r3, #BOOT_MMCSD
最终通过比较r2和一个数字的值,来判定哪种启动,将MMCSD启动的值,放在r3寄存中保存起来,将来以后用。
(10) 设置栈284行并调用函数
ldr sp, =0xd0036000 /* end of sram dedicated to u-boot */
sub sp, sp, #12 /* set stack */
mov fp, #0
我们知道给sp加载进去值,就相当于在设置栈,值是0xd0036000,第一次设置栈,这个栈是在我们SRAM中的,因为我当前的代码还没有到DDR中,还在SRAM中运行。
我们在调用函数的时候,要先设置栈,因为我们bl 到这个函数去运行的时候,这个函数内部可能又调用了函数,bl 去第一层函数时,lr中保存了第一层的返回地址,也就是我们最终返回到当前位置的地址,但是因为我们可能会在这个第一层函数内部去在调用一个函数,所以也要保存那时的当前地址,以便返回,因为现在你是在汇编中,所以你要考虑这些问题,不然程序就跑飞了,所以我们在调用函数时,要先将栈设置好,把我们lr中保存的返回地址先入栈,接着这个函数内部在调用函数的时候,就将那个返回地址也入栈,这样当我们执行完函数时,出栈的时候,也就最终找到我们lr返回地址,彻底的返回到我们调用函数的位置,要时刻记得栈的样子,入栈和出栈。
6、start.S解析4(lowlevel_init函数)
(1)lowlevel_init函数(和我们板子相对的那个函数哦,可不是随意的一个板子的函数哦,是s5pc110中的哦)
push {lr} //压栈这个返回地址,入栈
//刚进来这个函数,因为lr中保存的是我们在调用这个时的返回地址,所以我们要将这个地址压栈。
(2)、检查复位状态
为什么要检查复位状态呢,因为现在的复杂CPU,都有很多种复位状态,如:冷启动(直接开机上电),热启动,睡眠(低功耗)唤醒等等情况,都属于复位,所以我们要在复位代码中检查CPU是在哪种复位状态下,因为在不同的复位状态下,我们要做的工作是不一样的,比如如果是冷上电(开机上电启动)的话,我们就要需要做很多工作,如我们的DDR进行初始化,如果是在热启动和睡眠低功耗下的启动,我们就不需要做这个初始化DDR的工作了,因为之前已经初始化好了啊
ldr r0, =(ELFIN_CLOCK_POWER_BASE+RST_STAT_OFFSET)
//ELFIN_CLOCK_POWER_BASE = 0xE0100000 RST_STAT_OFFSET = 0x8300 这个寄存器在我们的数据手册中也没有找到,三星隐瞒的太多了
(3)、I0状态的恢复
上面两个过程知道就好
(4)、关看门狗
(5)、SROM、SRAM一些相关的设置初始化
因为从三星的这个CPU的设计可以看出,这个CPU的可以在SROM的地址处,外接SRAM或者SROM,这个SRAM并不是我们内部的SRAM,是外接的,九鼎应该是没有用,因为没看出来用。
(6)供电锁存(这个肯定非常的不陌生了,自己都在C代码下,操作内存业就是操作寄存实现了根据数据手册)
7、start.S解析5(lowlevel_init函数)
(1)110-115行(判断我们当前的代码是执行在SRAM中的,还是执行在DDR中的)
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中的,为什么要判定呢?
原因1、因为我们的高级CPU,他在复位的时候,有很多种复位情况,如果是在热启动,或者低功耗下进行的复位,我们复位后的代码就是执行在DDR中的,而在我们冷启动的时候,我们的代码就是SRAM中运行的。所以要进行判定。如果我们是在冷启动的情况下,那么开机上电的时候,首先在IROM中的代码BL0就会去我们放uboot的启动介质中,将我们uboot的BL1那部分代码加载到SRAM内存中去运行,去初始化SD卡,初始化DDR,重定位,将我们整个uboot的代码(BL1、BL2)复制到DDR中,那么就会在我们SRAM中有一份BL1,在我们的DDR中也会有一份BL1,所以如果我们是冷启动,那么当前运行的BL1代码就是在我们的SRAM中,如果是热启动等方式的话,我们当前代码的运行位置是在我们的DDR中的,所以确实有可能是在两个不同的地方运行的,所以我们要判断到底当前运行的位置到底是在SRAM中,还是DDR中。
原因2、判断了我们当前的代码的运行位置,可以指导我们接下来的代码到底是怎么运行的,比如我们可以通过判定当前代码的运行位置在哪里,可以指导我们的代码到底要不要在进行时钟初始化和初始化DDR的操作。如果我们的当前代码是在SRAM中运行的,就是冷启动造成的,所以我们重新进行时钟的初始化和DDR的初始化,如果我们当前代码是在DDR中运行的,则说明是热启动或者低功耗下的复位,我们就不需要在进行时钟的初始化和DDR的初始化,直接跳过这段代码即可。所以更加需要判定了。
(2)看上面的代码知道,前四行代码是加载当前的运行地址到r1中,加载链接地址到r2中,第五行则比较r1和r2中的地址是否相等,如果相等则说明,我们的当前代码是运行在DDR中的,beq 1f 的意思就是,如果cmp相等,则会去1这个标号去运行,f表示向下找这个标号。如果不相等,则说明我们当前代码运行在SRAM中的,所以要跳过beq这一句代码执行下面的代码初始化时钟,和DDR。
/* init system clock */
bl system_clock_init
/* Memory initialize */
bl mem_ctrl_asm_init
(3)是怎么比较判定的呢,在我们裸机中,我们是用adr指令加载,我们当前的_start处的运行地址的,用ldr加载我们的—链接地址的。用当前的运行地址和链接地址进行比较的,在uboot中略有不同,但也是89不离十了。
(4)bic r1, pc, r0 上面代码的这句话是,将我们pc的值,也就是当前pc运行时的值,将这个值的一些bit清0(r0寄存器中为1的bit清0)。
(5)系统时钟的初始化system_clock_init
当前函数的205行到385行
应该在x210_sd.h的300行到428行的宏定义,是跟我们时钟的配置值相关的,所以我在移植的时候,更改我们时钟的设置值时,不需要动我们的时钟初始化代码,只需要更改我们x210_sd.h中跟时钟相关的宏定义的设置值即可。
8、start.S解析6(继续分析lowlevel_init函数)
(1)
mem_ctrl_asm_init函数,是用来初始化我们的DDR,动态内存的。说明了在uboot的BL1代码阶段,确实有初始化DDR了。
(2)看着个函数知道,这个函数中的内容,和我们裸机中的初始化DDR的代码是一样的,所以我们裸机中初始化DDR的代码就是从uboot中抄过来的。但是有一个寄存的值我们不是抄的:
DMC0_MEMCONFIG_0寄存器,我们在裸机中配置的值为0x20F01323,
而在uboot中配置的值为0x30F01313,为什么不同呢,因为我们的SOC内存地址范围是512M的,DMC0,DMC1加起来一共512M,在裸机中我们配置的时候只用了DMC0,也就是内存的一半,我们用的是0x20000000-0x2fffffff,这256MB的内存空间,但是我们SOC支持的内存地址范围是0x20000000-0x3fffffff,因为我们只用DMC0,或者DMC1其中的一个,所以我们可以选择在这个地址范围内的256MB去使用。
(3)从uboot的这个代码可以看出,我们在uboot中的物理内存地址范围是0X30000000-0X4FFFFFFF,一共512MB,其中0X30000000-0X3FFFFFFF为我们的DMC0的内存地址范围,其中0X40000000-0X4FFFFFFF为我们DMC1的内存地址范围。
(4)uboot中的内存配置值,是和我们的时钟配置有关系的,都是通过条件编译实现的,我们用的时钟是1000MB主频,所以我们通过定义了这个宏,找到了我们所对应的内存配置值是在430行到452行,在x210_sd.h中
(5)uart_asm_init
初始化串口的函数,对照也应该会发现和我们裸机中的一样.
这个串口初始化完了以后给UTXH_OFFSET寄存器,写入了0x4f4f4f4f,意思就是初始化好了以后发送O,
(6)tzpc_init
这个函数老师也不搞过,不清楚,不管。
(7)pop {pc}以返回
当我们初始化都差不多的时候,返回前还通过串口发送了一个0x4b4b4b4b,打印了一个K。
总结,在我们lowlevel_init.S函数执行完时,会通过串口打印"OK"字样,所以我们也可以知道,当我们uboot在启动的时候,最开始应该会看见有OK字样打印出来
9、start.S解析6
(1)总结:总结一下lowlevel_init.S中,都做了哪些事情:
检查复位状态、IO恢复、关看门狗、电源开关自锁、代码处在SRAM和DDR中的两种不同处理初始化的方法(时钟初始化、内存DDR初始化、串口初始化,tzpc_init初始化),串口初始化后打印'O',整体初始化结束后打印'K'
其中值得关注的就是:关看门狗、电源开关自锁、时钟初始化、内存DDR初始化、串口初始化、串口最终是否打印了"OK"
(2)从lowlevel_init.S中出来后,回到start.S中,继续向下分析代码
又进行了一次电源的开关自锁,这仅仅是为了防止电源没有自锁成功,在上面的步骤,又重新来了一遍。
(3)又再次的设置栈(设置DDR中的栈)
之前在调用lowlevel_init.S之前,设置过一次栈第一次设置栈(start.S中284-286行),那个时候因为我们的DDR还没有初始化,我们的代码是运行在SRAM中的,所以那次设置的栈,设置的是SRAM中的栈。为了函数调用函数,让返回地址一次入栈,好从函数中返回用的。现在设置的这次栈是给DDR中设置的(第二次设置栈),因为我们DDR已经初始化好了,所以需要将栈挪移到DDR中(297-299)
ldr sp, _TEXT_PHY_BASE 代码段的物理地址的基础地址
地址是#define CFG_PHY_UBOOT_BASE MEMORY_BASE_ADDRESS + 0x3e00000 0x33e00000
所以第二次设置栈的实际地址是0x33e00000 刚好和我们uboot的代码段下面紧挨着的,uboot的代码段是在0x33e00000之上的,而arm中的栈是满减栈,所以地址是在0x33e00000这个地址向下生长的。所以不会冲掉uboot的代码段
(4)为什么要第二次设置栈呢?因为我们DDR已经初始化好了,有大片的内存可以使用,而且我们的sram中,内存很少,栈在sram中栈不能用的太多,用的太多会栈溢出,所以我们可以将栈移到DDR中,不用在sram中小心翼翼的使用栈了。
(5)再次判断当前地址是在sram中运行的还是在ddr中运行的,来确定是否要重定位。
在冷启动时,当前的情况是,我们的uboot的第一部分(uboot的前16kb或者是8kb)在sram中运行着呢,同时我们的uboot第二部分(指的是整个uboot)还在sd卡的某个扇区的开头躺着呢(这是在我们将程序下载到sd卡时决定的),此时uboot的第一阶段要结束了,但是在结束之前必须把后事处理好,不能自己死了,彻底死了,所以此时要将在sd卡中某个扇区开始的N个扇区中的uboot第二部分代码(整个uboot)加载到DDR中的链接地址处去,这个加载的过程就是重定位。
10、uboot的重定位
(1)在start.S的314行,0xD0037488 这个值,我们查找irom的手册,可以发现,这个地址中的值是被硬件设置的,这个地址中的值代表的是我们在sd卡启动时,是用的哪个SD卡通道启动,比如如果我们用的是SD卡的通道0,这地址中的值就是0xeb000000,如果我们是在sd卡的通道2启动的,那么这个地址中的值就是0xeb200000,这个地址中的值,是硬件通过看我们用的哪个sd卡通道来决定的,是硬件进行设置的。
(2)我们在260行,已经确定了是以什么启动介质启动的了,并且在278行,已经将这个值写入到了某个寄存器中了,在317行,又再次的判断了是在SD卡的哪个通道启动的(这里是条件编译),在后面的322行,又将这个寄存器中的值读出来了又再一次的判断是哪种方式启动的(主要是和#BOOT_MMCSD进行比较执行mmcsd_boot)。最终确定跳转到了mmcsd_boot去执行重定位了,接着就执行到了movi_bl2_copy这个函数,这个函数就执行了重定位,C好写,进到movi_bl2_copy函数中去
(3)movi_bl2_copy函数
(从sd中,我们放uboot第二部分也就是整个uboot的开始的N个扇区重定位也就是复制到DDR中,复制的函数是在一个地址中用函数指针的形式表达的。这个地址在irom中可以找到)
ch = *(volatile u32 *)(0xD0037488); 这个函数中,开始将这个地址中的内容存放到了ch中,我们通过查找irom手册可以知道,在上面也分析到了,这个地址中的值就是存放的我们在哪个sd卡通道启动的数字,在sd2启动就是0xeb200000,在sd0启动就是0xeb000000。、
接着下面的代码就应该不用在介绍了,在裸机中我都分析过了。应该没什么问题了。
copy_bl2(2, MOVI_BL2_POS, MOVI_BL2_BLKCNT,
CFG_PHY_UBOOT_BASE, 0);
参数: 2 表示是SD卡通道2, MOVI_BL2_POS的值是我们uboot在SD卡开始扇区的位置,这个扇区的位置必须和我们烧录uboot时的uboot在SD卡扇区开始扇区位置一致。、
MOVI_BL2_BLKCNT 是uboot的长度,就是uboot在SD卡中,占了多少个扇区。
CFG_PHY_UBOOT_BASE 将uboot第二部分,其实是整个uboot在SD卡中重定位拷贝到DDR中的哪个地址中去,这个地址要和我们链接地址一致,其实就是0x33e00000
11、虚拟地址映射
(1)什么是物理地址?物理设备设计生产时赋予的地址,我们裸机中使用的寄存器地址,就是CPU设计时指定的。物理地址是硬件编码,是设计生产时确定好的,一旦我们确定了就不能改了。事实就是,我们寄存器的一个物理地址是无法通过编程修改的,只能通过查询数据手册获得并操作,坏处就是不够灵活。一个解决方案就是使用虚拟地址,虚拟地址意思就是在我们软件操作和硬件被操作之间增加一个层次,叫做虚拟地址映射层。就是在用软件访问一个虚拟地址层,来达到访问到硬件的物理地址。
一般时软件用的全是虚拟地址,硬件用的全是物理地址(虚拟地址到物理地址的映射是不能通过软件来实现的)
12、MMU单元的作用
(1)内存管理单元,MMU实际上就是一个SOC中的内部外设,SOC中的一个硬件单元,它的主要功能就是实现虚拟地址到物理地址的映射,把虚拟地址翻译成物理地址
(2)MMU单元在CP15协处理器中进行控制,也就是说要操控MMU单元要对CP15协处理器中的寄存器进行操作编程
13、地址映射额外的收益1,。访问控制
(3)同时,虚拟MMU单元除了能映射到物理地址方面外,还提供了我们对每一块的映射关系的访问权限。访问控制。
就是可以控制这个映射关系是可读的还是可写的,还是可读可写的
(4)回想一下C语言中我们在编程的时候,有一种错误,叫做段错误,这种错误实际上就是由于MMU实现的访问控制实现的,当我们的指针访问了我们没有权限访问的内存块就会出现段错误,这个就是由MMU单元实现的
14、地址映射额外的收益2:cache
(1)cache的工作和虚拟地址是有关系的,cache的意思就告诉缓存的意思,意思就是比CPU慢,但是比DDR快。
(2)cpu嫌弃内存工作的速度太慢了,所以有了cache,我们内存中常用的部分放到了cache中,进行缓存,cache的速度很快,相对于内存来说,所以CPU在需要东西的时候,会现在cache中找,如果找到了,那么就使用cache中缓存的,如果没有找到的话,就会使用内存中的。所以存在的cache的命中的关系,cpu在cache中找到了,就命中了。
所以cache越大,电脑什么的东西性能就会越好,越快,所以缓存越大越好。
(3)cache也是要在cp15协处理器中进行控制的,所以在处理MMU的时候一般都顺带的把cache也处理了,因为都是用cp15协处理器进行控制的
参考阅读:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=22891521&id=2109284
15、使用能域访问(cp15协处理器中的c3寄存器)
(1)CP15协处理器中一共有C0-C15,16个寄存器。mcr指令是用来操作这些寄存器的,用来向这些寄存器中写值,mrc指令是用来从这些寄存器中读取数值的。
(2)其中C3寄存器中有对16个域的访问进行控制,对MMU的作用是,控制域访问的。所以其中一些域是和MMU的访问控制有关的
16、TTB(CP15协处理器中的C2寄存器)
(1)TTB就是转换表基地址。(TT,translation table )转换表:就是软件通过虚拟地址访问到物理地址时,虚拟地址到物理地址的转化。转换表中左面的就是我们滴虚拟地址,右边呢就是对应的物理地址的值,所以我们可以通过虚拟地址去查找这个表左面的内容,来达到找到右面对应的物理地址,所以这个表就叫做转换表,实现的任务就是,将虚拟地址翻译成物理地址,所以虚拟地址映射的关键,就是要建立这个映射表。
转换表基地址就是TTB,translation table base,
(2)转换表是建立我们虚拟地址映射的一个关键,转换表分为两个部分,一个表的索引,和一个表项,表索引对应的虚拟地址,表项对应于物理地址。一对表索引,表项,构成一个转换表单元,能够对一个内存块进行虚拟地址映射(我们在映射中,基本规定内存映射和管理是以块为单位的,至于块有多大,要看MMU的支持和自己的选择,在ARM中支持三种块大小,1KB叫细表,4KB叫粗表,1MB叫段式映射),真正的一个转换表,就是由若干个转换表单元构成的,每个转换表单元负责一个内存块的映射,整个转换表构成了对整个内存空间的映射(0-4G,32位cpu)。
(3)所以建立虚拟地址的映射关键就是建立这张转换表。
(4)转换表放在内存中,放置的时候起始地址要求内存对齐,可能是64B对齐,多少M对齐,不一定是4B对齐转换表不需要软件干涉使用。而是将转换表的基地址(TTB),设置到我们CP15的C2寄存器中,然后MMU工作的时候会自动去查转换表。
17、使能MMU(CP15协处理器的C1寄存器)
(1)当我们把转换表的基地址写入到c2寄存器中时,就可以将C2寄存器中的bit0设置为1,就开启了MMU,当MMU开启之后。
我们上层软件层发下来的地址,就是虚拟地址,就通过TT转换表查找对应的物理地址去执行。转换表在lowle_init.S中的500多行左右
18、宏观上来理解转换表,整个转换表可以看成是一个数组,转换表的索引就是数组的下标。
ARM中的段式映射,一个映射单元只能管理1MB的内存,所以在32位中,4G的内存空间需要4G/1MB=4096个映射单元,所以如果用数组理解的话,就是数组中的元素个数是4096个。
但是实际上,我们的处理方法是,将这些要映射单元,很多个组成一个块,用一个类似于for循环的方法
.rept 0x100
FL_SECTION_ENTRY __base,3,0,0,0
.set __base,__base+1
.endr
.rept是一个伪指令,他和endr一起配合使用,.rept是重复的意思,相当于for循环,中间的两行代码相当于For循环的循环体,循环次数就是0x100
.macro FL_SECTION_ENTRY base,ap,d,c,b
.word (\base << 20) | (\ap << 10) | \
(\d << 5) | (1<<4) | (\c << 3) | (\b << 2) | (1<<1)
.macro 定义宏的,在汇编中,FL_SECTION_ENTRY是宏名,后面五个是这个宏接受的五个参数,.word是定义一个uint类型的四字节的数,这个数是后面的内容就是构成这个数,就构建这个表项
.endm 用来终止这个宏
总结:前面那些我了解的也是不多,以后有需要的话,在结合老师的课程,在结合百度上的资料进行学习吧,
但是可以初步的总结出来,在x210开发板中,九鼎的板子上的DDR是两片,每片大小是256MB,九鼎的uboot中,将DDR1的起始地址配置成0x30000000,到0x3fffffff,DDR2是0x40000000-0x4fffffff,共256MB,九鼎的uboot中又将MMU的虚拟地址映射中的,虚拟地址,也就是软件访问的地址,0xc0000000-0xcfffffff这256MB的地址空间,映射到了0x30000000-0x3fffffff这256MB的空间,从九鼎的uboot中的MMU虚拟地址映射的TTB中的转换表可以看出来。
这也就是我们用九鼎的uboot的时候,要将链接地址,链接到DDR的0x33e00000或者0xc3e00000这两个地址都可以,就是因为他们两个地址实际上都是相同的。
当MMU开启了以后就不能使用物理地址了,但是我们为什么还能链接到0x33e00000这个物理地址中去呢,实际上这个地址也是虚拟地址映射的,因为在虚拟地址映射的表中,我们通过代码可以看出来,其中0x200-0x600这段的虚拟地址空间被映射到了0x200开始的地方,因为这段是原样映射,所以我们也是可以用0x33e00000这个地址的。
11、第三次设置栈
(1)前面已经设置了两次栈了,一次设置的栈是在sram中的,一次设置是在ddr中的,这次设置的栈还是设置在DDR中的,为什么要第三次在ddr中重新设置栈呢,这个栈,通过代码可以看出来,不是设置的特别随便的,而是设置在了uboot起始的地址到为整个uboot划分的2M空间,又减去了0x1000,这么做的目的是为了让内存排列的比较紧凑,同时也安全,不会被其他的内容冲掉。arm中的栈是满减栈,所以栈是向下生长的。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。