在嵌入式linux设备中,uboot的最终目的就是启动kernel。对于uboot而言,没有人把它引导起来,所以uboot首先需要把自己加载起来,然后再去引导kernel的启动,这也就可以大致的分为Uboot启动的第一阶段和第二阶段。具体内容可以参考博客《序言和目录》

(一)start.S第一阶段启动总结

    在海思hi3251a官方的《Hi3521A_PINOUT_CN》手册上有上电锁存管脚BOOTROM_SEL,这个是引脚用来定义是从BOOTROM启动还是从spi flash启动 。

(1)BOOTROM启动

    当启动模式为从 BOOTROM 启动时,海思的BOOTROM的程序会去初始化串口,然后与海思的HiBurn工具建立通信,如果能建立通信,则启动HiBurn升级uboot程序流程,如果不能与HiBurn建立连接,等待一段时间之后它就转去从外部的spi flash启动

(2)HiBurn 烧入原理

    HiBurn烧入的基本原理是,HiBurn工具与BOOTROM程序建立连接之后,先下载uboot程序的开始4KB数据到海思芯片的内部RAM,然后通过下载的那一小部分uboot代码去初始化外部的DDR,如果DDR初始化成功,HiBurn再将剩下的uboot程序下载到外部的DDR中去,最后是在DDR中启动uboot,如果要进行烧入操作,是通过DDR中的Uboot程序,发送uboot命令将DDR中的uboot程序烧入到外部的flash中去,这样就实现了uboot程序的升级。

(3)spi flash启动

    海思的hi3521a芯片支持从 SPI NOR Flash 和 SPI NAND Flash 直接启动,它的启动也就是我们start.s中的大部分实现。主要做的工作有:

  1. 初始化CPU相关功能(关缓存,关MMU,设置SVC模式等)
  2. 将异常中断向量表重定向到内部RAM
  3. 将uboot代码重定向到外部DDR中
  4. 设置栈空间
  5. 清空BSS段
  6. 最后调用C语言接口去执行C语言代码实现第二阶段的启动。

第一阶段在执行之后,在DDR中的映射空间的数据分布如下:


 (二)start_armboot第二阶段启动总结

在uboot的启动的第二阶段,主要做的是:

  1. 初始化变量和结构体
  2. 重定向环境变量
  3. 初始化堆管理器
  4. 初始化环境变量
  5. 进入命令处理循环

    在命令处理循环中,默认Uboot是超时等待用户命令输入,如果有检测到命令的输入,则执行相应的命令,如果超时没有命令输入,uboot默认执行的bootm命令进行kernel启动引导。
    这里涉及到两个比较总要的点:环境变量的重定向和uboot命令处理。

(1)环境变量重定向

    在uboot第二阶段的时候,uboot会去检测flash中环境变量的地址中环境变量是否有效,如果有效,则使用flash中的环境变量值来初始化uboot运行中的环境变量,如果flash中的环境变量无效,则使用默认的环境变量来初始化运行中的uboot。在uboot的用户命令操作过程中,如果有对环境变量进行修改,都是修改的内存中的那一份环境变量,如果要将修改的环境变量保存到flash中去,则需要运行命令saveenv.
    uboot在flash的分布如下:

    uboot.bin烧入到flash的0地址位置,boot.bin里面的数据就是按我们连接脚本中的数据那样分布。在0x80000(512KB)开始的位置就是环境变量的在flash的位置,其大小为256KB。

(2)uboot命令处理

    在启动的最后阶段,就是执行uboot的bootm命令,主要就是uboot将kernel程序从flash中复制到内存去,然后就是检验kernel镜像文件的有效性,设置uboot传递给kernel的参数,设置kernel启动的环境(比如关缓存,关中断,CPU处于SVC模式等),最后就是通过一个函数指针指向kernel的启动地址,调用该函数实现uboot到kernel的运行。在这个过程中,启动参数和机器码同时传递给kernel,关于参数传递,实际在这里是通过寄存器间参数的地址传递给了kernel。

(3)参数传递

    C语言进行函数调用的时候,常常会传递给被调用的函数一些参数,对于这些C语言级别的参数,被编译器翻译成汇编语言的时候,就要找个地方存放一下,并且让被调用的函数能够访问,否则就没发实现传递参数了。对于找个地方放一下,分两种情况。

    一种情况是,本身传递的参数就很少,就可以通过寄存器传送参数。因为在前面的保存现场的动作中,已经保存好了对应的寄存器的值,那么此时,这些寄存器就是空闲的,可以供我们使用的了,那就可以放参数,而参数少的情况下,就足够存放参数了,比如参数有2个,那么就用r0和r1存放即可。(关于参数1和参数2,具体哪个放在r0,哪个放在r1,就是和APCS中的“在函数调用之间传递/返回参数”相关了,APCS中会有详细的约定。感兴趣的自己去研究。)

    另外一种情况是,如果参数太多,寄存器不够用,那么就得把多余的参数堆栈中了。即,可以用堆栈来传递所有的或寄存器放不下的那些多余的参数。

                                                                                               完

                                                                                         2019.12.06

Logo

更多推荐