首先,我先来描述一下需要移植uboot的这块板子上使用到的存储器(处理器架构可以暂时先不考虑,后面我会根据arm公司官方提供的ARMv7-A体系结构文档去分析uboot初始化处理器的汇编代码。无论是arm9 arm11 armv7 armv8都是相同的套路,首先需要根据存储器资源,去定制自己的启动流程)。在我手上的这块板子上使用存储器资源:Norflash(16MB) SRAM(512KB) SDRAM(512MB)。
看到这里应该想到两个问题:
1 Norflash SRAM SDRAM 作为存储器它们之间有何异同?
2 如何通过它们去启动arm处理器?(重要的事情说三遍:先一定要理解启动流程 然后再去看uboot源码 要不然看了也无法理解 嵌入式的代码都是与硬件息息相关 了解硬件结构再去读代码 事半功倍)
下面我将对以上问题依次做出解析:
一. Norflash SRAM SDRAM 异同
norflash:norfalsh是非易失存储器(失去电源供电后norflash里存储的数据依然存在),NOR flash带有SRAM接口,有足够的地址引脚来寻址,可以很容易地读取其内部的每一个字节(注意是读取!对于flash不是随意可以写入,一般写入NOR flash流程是:解保护->擦除->写入数据。由于flash特性只能从1翻转到0,无法从0翻转到1。擦除过程就是将flash中的某一个扇区全写0xFFFFFFF,再写入数据),代码指令可以直接在norflash上运行。(重要! 上电后可以读取norfalsh中的数据 写操作无法直接进行 前面我已经说得很清楚了)
SRAM:静态随机访问存储器(Static Random Access Memory)它是一种类型的半导体存储器。“静态”是指只要不掉电,存储在SRAM中的数据就不会丢失。这一点与动态RAM(DRAM)不同,DRAM需要进行周期性的刷新操作。然后,我们不应将SRAM与只读存储器(ROM)和Flash Memory相混淆,因为SRAM是一种易失性存储器,它只有在电源保持连续供应的情况下才能够保持数据。“随机访问”是指存储器的内容可以以任何顺序访问,而不管前一次访问的是哪一个位置。 (重要!上电后就可以读写SRAM中的数据,无需初始化操作)
SDRAM:同步动态随机存取存储器(Synchronous Dynamic Random Access Memory)需要不断的刷新,才能保存数据。而且是行列地址复用的,许多都有页模式。(重要! 需要对ddr控制器进行初始化<配置寄存器>,才能去读写SDRAM中的数据)
最后用表格来归纳总结下:
前面介绍了Norflash SRAM SDRAM存储特性,下面就来聊聊他们各自的功能和完成的任务(重要!!Norflash SRAM SDRAM在arm处理器启动阶段完成的任务,都是根据他们各自的存储特性来的。如果对于我前面介绍的Norflash SRAM SDRAM存储特性还不太理解的同学可以自行百度再深入理解。)
Norflash特性:掉电非易失(失去电源供电后norflash里存储的数据依然存在),可以直接<读取>数据,无法直接<写入>数据。
Norflash作为arm处理器程序存储器。可以试想一下,如果程序存储器掉电以后里面的数据没有了。那么你的电脑如何自启动,难道每次开机前都要重新烧写一次代码。在此处可以思考一个问题,在上电后norflash可以看作一个可以随机读取的只读存储器。但是我们运行的程序,一般情况下.text段(代码段)是只读(ok),.rodata(只读数据段)是只读(也ok)。那么问题来了,对于.data段(数据段)和.bss段(未初始化的全局变量和静态变量)在程序运行的过程中变量的值是需要改变的(改变一个变量的值在底层硬件完成操作<在相应的地址(变量在物理地址上存储地址)上写入数据>),很可惜Norflash只能直接读取无法直接进行写操作。(重要! 怎么解决这个问题? 这时就需要SRAM 因为SRAM上电后就可以直接去读写,下面我就解释下SRAM的功能和作用)
SRAM特性:掉电易失(失去电源供电后SRAM里存储的数据不存在了),可以随意<读写>数据。(重要!容量小:512KB 程序运行速度快 价格贵!)。
在实际运行时,SRAM可以作为c语言运行时的堆栈空间。把arm处理器的sp(堆栈指针寄存器)设置在sram中,为c语言运行提供环境。关于全局变量的问题,我单独提一下,uboot在重定位前(将uboot镜像从flash搬运到ddr中继续运行前),无论是汇编还是c程序中没有定义全局变量。只是定义了一个结构体指针gd_t *gd_ptr用于存储uboot镜像的长度,重定位地址等信息,将gd_ptr的地址存储在r9中,r9中存储的地址值为sram顶端减去一个sizeof(gd_t )。(存储在sram里就可以随意读写了嘛 后面分析uboot代码时我会详细讲解)
SDRAM特性:掉电易失(失去电源供电后SDRAM里存储的数据不存在了),上电后没有初始化DDR控制器,无法进行数据读写。(重要!容量大:512MB 程序运行速度快 价格自己猜一下<必须价格便宜>)
既然需要使用大容量的SDRAM,必须配置ddr时钟和ddr控制器的寄存器。这一步在哪完成呢?(思考一下)
没错就是在norflash和SRAM搭建的程序运行环境中完成。完成什么呢?
1.完成对处理器时钟的初始化
- DDR的初始化
3.给gd_t *gd_ptr赋值 (用于存储uboot镜像的长度,重定位地址,重定位偏移量等信息)
在uboot搬运到DDR中运行前进行最小系统的初始化,之后就将uboot搬运到ddr中进行运行。
(重要!此时Norfalsh和SRAM的任务就完成了(这俩就没用了),现在uboot就在ddr中运行了)
二. arm处理器启动流程分析
前面讲解了各个存储器在处理器启动阶段的作用,下面就归纳总结一下启动过程。
有时候描述说不清楚,直接上图。下图为手上这块ARM处理器的存储器地址空间(包括各存储器在arm处理器上的地址空间 以及容量大小)
那么启动流程可以总结为:
step1.uboot镜像(也就是bin文件 重要!不是elf文件 bin文件 和 elf文件异同 自行百度)存储在norflash中,上电后ARM默认从地址0x00000000处取出第一条指令。当运行C程时,形参和局部变量需要入栈,栈顶设置SRAM的顶端,为C程序提供运行环境。完成对gd_t *gd_ptr赋值(存储uboot镜像的长度,重定位地址等信息)。
step2.在第一阶段会完成arm处理器的时钟和DDR的配置以及对gd_t *gd_ptr赋值(用于存储uboot镜像的长度,重定位地址等信息)。此时SDRAM就可以正常进行数据读写。将uboot镜像复制到SDRAM中(重定位地址由gd_t *gd_ptr提供),根据重定位偏移量得到新的PC指针值,在DDR中继续运行uboot。
这里我想多提一下,uboot在ddr中运行后的地址跟其链接地址不一致(原来norflash中text_base = 0x0 在ddr中运行后text_base = 0x70453670 ),编译器会在链接时确定了其中变量以及函数的绝对地址,链接地址 加载地址 运行地址应该一致的(只是写裸机程序时大多数情况下)。
(1)如何对函数进行寻址调用(执行函数的过程就是跳转到某个地址上去执行一段代码)
(2)如何对全局变量进行寻址操作(全局变量的地址变了如何找到进行读写)
(3)对于全局指针变量中存储的其他变量或函数地址在重定位到DDR中之后如何操作?
问题先放在这 后面我会专门写一篇博客 将一个可执行程序静态链接过程 动态链接过程 与uboot重定位过程放在一起比较 感兴趣的可以直接去阅读这篇博客。
step3.在DDR中继续运行的uboot完成对ARM处理器各外围硬件的初始化,如SPI、I2C、网卡等。最终uboot进入main_loop,阻塞等待串口终端输入命令或者去启动内核。始终要清楚一点uboot的主要作用是通过 Flash和Sram初始化时钟和DDR(arm最小系统),最终uboot是需要在ddr中运行进行裸机调试或者去引导操作系统内核启动。
(如何编写uboot命令进行裸机调试 以及uboot如何引导操作系统 后面我会依次介绍)
最后上还是上图<启动流程图>:
上电后arm默认从地址0x00000000处取出第一条指令。SP指向SRAM的顶端,当运行C程序时,形参和局部变量需要入栈,SP依次减4入栈。在这部分运行的程序完成对时钟和ddr的配置(arm最小系统)以及对gd_t *gd_ptr赋值(用于存储uboot镜像的长度,重定位地址,重定位偏移量等信息)。此时ddr已经可以成功进行数据读写,将uboot镜像从flash复制到DDR,根据重定位偏移量获得新的PC位置 继续运行uboot。
step3
uboot已经在ddr中运行了,也就不存在任何硬件上限制了。step3完成对ARM处理器各外围硬件的初始化,如SPI、I2C、网卡等。最终uboot进入main_loop,阻塞等待串口终端输入命令或者去启动内核。
原作者:DMCF