深入理解uboot (处理器启动流程分析)

最近一段时间一直在做uboot移植相关的工作,需要将uboot-2016-7移植到单位设计的ARMv7的处理器上。正好元旦放假三天闲来无事,有段完整的时间来整理下最近的工作成果。之前在学习uboot时,在网上看了很多文章,很多都是基于老版本的的uboot,并且很多都是直接从代码开始分析,并没有将uboot与ARM处理器体系结构结合起来。毕竟很多时候做一件事情,你知道怎么去做这件事和你知道这件事为什么要这么去做两个不同的概念。即英文中常说的:how do?和 why do?  

       将2016年7月发布的新版本uboot移植到单位的板子上并进行了调试,在此也将我的学习成果分享给大家。文中有很多地方加入了我自己的理解,可能不太准确,希望大家提出修改意见。

       开始深入了解处理器启动流程前,请准备好两个工具:

1.uboot-2016-7源码包 

(开源的随便下载  官方下载地址:ftp://ftp.denx.de/pub/u-boot/    下载u-boot-2016.07.tar.bz2)

2.代码阅读软件source insight

(推荐! 可以想象uboot源码包有10000多个文件,每个文件都有几百行甚至上千行代码。没有专业的代码阅读器如何查找函数原型。 当然不是全都需要看的!  根据自己的需要去查看)

同时下载好这几个技术文档

 (笔者在学习uboot时阅读的几个文档  可以根据需求去查找阅读  完全没必要重头读到尾)

1.arm公司官方提供的ARMv7-A体系结构文档 

《ARM® Architecture Reference Manual ARM®v7-A and ARM®v7-R 》

( 官方 品质保证! 如果觉得英文看着费劲 可以去看  杜春雷  《ARM体系结构与编程》 )
2.《Uboot中start.S源码的指令级的详尽解析_v1.6.pdf》(好吧  中文滴)

(对理解gnu arm汇编极其有帮助)

3.《ARM指令集快速查询手册.pdf》(中文滴)

(工具手册,快速查找各种arm指令用法)

4.《ARM指令详解[ARM标准].pdf》(还是中文滴)

(arm汇编的一些规范和常用形式,很多例子非常有用:子程序调用、散转、数据块复制等。对写裸机程序很有帮助,同样对理解uboot和内核启动代码有很大帮助)

5.《程序员的自我修养—链接、装载与库.pdf》(全是中文滴)

(里面会介绍ELF文件  程序链接的过程  最重要的部分就是帮助理解uboot的核心重定位!这个真的很重要!!)

       好了,现在进入正题。

     什么是uboot?

        在嵌入式操作系统中,Boot Loader是在操作系统内核运行之前运行。可以初始化硬件设备、建立内存空间映射图,从而将系统的软硬件环境带到一个合适状态,以便为最终调用操作系统内核准备好正确的环境。在嵌入式系统中,通常并没有像BIOS那样的固件程序(注,有的嵌入式CPU也会内嵌一段短小的启动程序),因此整个系统的加载启动任务就完全由Boot Loader来完成。   uboot就是bootloader的一种,全称Universal Boot Loader。

      目标:用uboot来启动单位设计的ARMv7的处理器。

      问题:如何启动? 启动流程是什么?

      首先,我先来描述一下需要移植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
上电后没有初始化DDR控制器
         无法进行数据读写
数据不存在

       前面介绍了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.完成对处理器时钟的初始化 

       2. 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,阻塞等待串口终端输入命令或者去启动内核。

以上就是arm处理器的启动流程,是否弄明白了呢?

       下一篇我就以三星出品的S3C2410以及S5PV210为例子,讲解一下他们从nandflash启动。至于现在手机和平板电脑上广泛运用的eMCC(内部就是nandflash  只不过是改变一下读写接口)也是一样的套路(请记住万事不离其宗 以不变应万变)。

发布了136 篇原创文章 · 获赞 71 · 访问量 16万+

猜你喜欢

转载自blog.csdn.net/u012308586/article/details/104969582