HI3516DV300_16_Uboot第一阶段的启动


一、uboot第一阶段的启动

1.1 起点在:arch\arm\cpu\armv7\hi3516dv300\start.S

1.2 分析start.S源码

  • 说明海思也是从其它地方移植过来的:

  • 复位向量表

_ start符号就是entry,这是这个系统最开始启动的地方

_ start符号下面就是异常向量表,这里主要关注:

第一条:复位异常,复位之后就会去执行reset

因此去分析reset

  • 寻找reset

reset往上这些就相当于定义了一些变量

reset就相当于一些函数

  • save_boot_params

调用了一个函数:叫save_boot_params,用于保存启动的一些参数,主要用来做一些类似于热启动之类的,启动了之后,要保存一些之前的状态,上下文之类的;但实际上,这个函数是空的,ENTRY就表示这个函数的入口点。

这个函数叫save_boot_params就是一个空函数,最后又跳回来了

  • 如果定义了CONFIG_ARMV7_LPAE

宏定义:CONFIG_ARMV7_LPAE是用来看看是不是支持虚拟化使用的

也就是基于这个芯片,要先跑一个虚拟化平台,比如说,双核A7的有没有可能一个核跑一个Linux,一个核跑一个RTOS呢?这个时候,就需要使用虚拟化技术,把这个平台给它虚拟化一下,其实就是虚拟机

其实是没有这个宏定义的,怎么确定是没有这个宏定义的,查找一下就知道了,要在以下两个符号里面去查找:

首先是.config文件,因为这个.config文件其实就是menuconfig的配置项

所以先在这里面搜一下,发现搜到后是被注释掉了,所以这也就是宁愿注释掉,也不删除掉的用意所在,注释后,去搜这个符号,是能够搜到的,所以可以明确地知道这个符号是没有被定义的。

其次是hi3516dv300.h文件里面,搜了也是没有的

所有与config相关的项目,应该就在这两个地方,如果这两个地方都没有,那么就可以确定是肯定都没有了

  • 把interrupt禁掉,并且设置为SVC模式,这些也是比较常规的

  • 如果没有:定义CONFIG_OMAP44XX并且没有定义CONFIG_SPL_BUILD

查阅CONFIG_SPL_BUILD发现没有被定义,并且是与操作,那么整体就是false,取反之后就是true

后面就应当进来,下面分析进来后的代码:

设置CP15里面的协处理器设置有关的,其中:for VBAR to point to vector 与异常向量表的重定位有关的
其中,Set vector address in CP15 VBAR register,也就是异常向量表可以通过软件来进行重定义

这一点,与CPU内部设置有关的,是不用去更改的。

  • mask ROM code,做进去,掩模是不能更改的,也就是irom的条件

这个需要保证mask ROM的PLL时钟是稳定的

也就是内部执行BL0的时候,也就是把时钟这一块已经做好了,而且时钟已经稳定了

如果没有定义CONFIG_SKIP_LOWLEVEL_INIT,实际上,是已经定义了这个宏定义

CONFIG_SKIP_LOWLEVEL_INIT_ONLY,实际上,也定义了这个定义

实际上,irom已经满足了条件,这部分代码是不会被执行的,也就是不需要执行CP15的初始化与PLL的初始化

  • 分析数据手册

访问寄存器一般都是:基地址+偏移量的方式

SYS_CTRL_REG_BASE:这是系统寄存器的基地址,
REG_SC_GEN2:这是系统寄存器的偏移量

首先查看系统控制寄存器基地址的定义:

CPU的地址空间是32位的,也就是4GB的地址空间,这个是怎么分配的呢

这个是要查看数据手册的,如下图所示,但是这个数据手册也还是太简单了,相比新的STM32动不动就3000页:
如果写得非常详细的话,这个手册写个4000页是没问题的

具体查看手册的如下位置,这里就告诉了你4GB的地址空间是如何映射的:

开机的时候会把内部的BOOTROM(iROM,BL0),映射到0地址开始执行;撤销映射后,指向片内RAM,也就是有一段片内的iRAM;这一部分有64MB。

沿着往下走,就有64KB的BOOTROM,也就是BOOTROM的代码自己在这个空间内。

往下走:内部的iRAM,肯定是sRAM,是40KB;第一次加载的BL1就加载了在Uboot的前一部分,是24KB大小,24KB是小于40KB的,还有富余的空间是为了给:栈,堆,等实用。

上一节中的截图:

往下走:就是各种外设的寄存器了

  • 寄存器:REG_SC_GEN2的分析

  • 寄存器地址中,有很大一部分是空白的,现在来看DDR的,理论上,这个芯片,可以带2GB的DDR

可以这样理解:4GB的空间,一半(2GB)用于寄存器等等,一半(2GB)用于DDR

因此,现在对于芯片的地址空间有了更进一步的理解了

  • 查阅系统控制寄存器以及相关的偏移值

但是这里展示得依然不是很全面,比如:REG_SC_GEN2就找不着

所以要查阅的寄存器,在数据手册里面是找不到的

因为海思觉得,这些东西,你不需要了解,这块代码,也不需要你管,咱们拿过来用就行了,毕竟不是原厂

  • 继续分析下面这一段代码

硬件的自举,自己能把自己的电路给运行起来
软件的自举,能够自动启动

  • 下面来分析一下非正常的启动流程

也就是如果:REG_SC_GEN2写的是”ziju”的ASCII码,那么下面就会把寄存器REG_SC_GEN2给清空掉

这个寄存器就是用来做flag的

接下来,下一步,做以下事情:

初始化时钟有关的,DDR控制有关的,GPIO多路复用相关的
还调用了init_registers:正真去初始化PLL,DDRC有关的

但是注意,这里第二次进来的时候,sp就不是随机值了,因为在“自举”的过程中,sp已经被赋值了

  • 来分析函数init_registers函数

这里面进行寄存器的读写,就是为了初始化PLL,DDR等基础硬件,
这里面进行初始化的时候,肯定有很多寄存器的值,这些寄存器的值,就是从.reg里面读出来的,不在Uboot代码里面
因为是通过那个Excel表格来配置,配置好了之后,生成.reg文件
这些细节都在init_registers函数里面

init_registers这个函数在Lowlevel_init.c里面也是有的,但目前使用的不是这个,因为Makefile里面,没有包含Lowlevel_init.c这个文件

  • ddr training:

所以要理解自举,在这里面,自举就是去初始化DDR,PLL等硬件设施使用的

其实这里,就是对DDR的参数做进一步的校准和确认

也就是很多比较高端的DDR,它的很多参数是跟这个具体的硬件是有关的,也就是你没有办法事先写一个确定的值,一定是最佳的。
可能在运行的过程中,运行这个DDR Training这个函数
DDR Training这个函数是先设定一个值,然后读取一个反馈,看是不是OK,相当于一个动态的调整过程。
所以这个函数叫训练,通过不断地去设置,读返回,通过这种方式,把DDR校准到一个比较合适的一个位置,然后就返回了。

返回bootrom,也就是返回到系统的BL0去了

也就是说,BL0内部是肯定要初始化很多硬件

BL0如果要读取外部的EMMC,肯定要首先能初始化EMMC

BL0能够对接HiTool,进行Uboot的下载,肯定要能初始化DDR

  • BL0是内部代码呀,将来也不知道用户到底使用什么DDR呀,这样子怎么能初始化DDR呢?

但是反过来想:BL0是内部代码呀,将来也不知道用户到底使用什么DDR呀,这样子怎么能初始化DDR呢?

这里的解决方法是:先自举启动一次,在自举启动的过程中,把DDR,PLL给初始化了;然后再回到bootrom,也就是回到BL0去,再重新启动,在重新启动的过程中,已经启动了DDR、PLL,这样就能开始正常启动流程了。

回到bootrom相当于又是重头开始了,因为异常向量表只有一条,也就是相当于又跳转到了reset

也就是这里:

所以这里的设计是非常巧妙的

重新进来的时候,就不会进入“自举”了,而是开启正常启动流程

  • normal_start_flow开始部分

BL0跳到BL1,经过”自举”,又跳回了BL0,然后又回到BL1,这回呢,就不自举了,开启正常启动流程

  • init serial and printf a string

把SP进行赋值
调用了uart_early_init

其中,uart_early_init函数位于下列图中,是初始化串口用的:

调用了msg_main_cpu_startup

其中,msg_main_cpu_startup函数位于下列图中,是打印信息用的:

上面也就是U-boot启动后,最早打印的信息了,鸿蒙的Hi3516DV300使用的是和这个一样的U-Boot

  • enable sysnt

计算系统的频率之类的

如果不是从nand,SPI,Flash启动,那么就跳过检查是从哪里启动(不执行check_boot_type),否则,执行check_boot_type:

如果不执行check_boot_type,那么

把PC寄存器的数值读进来,然后左移24位,其实就是看低8位
和0进行比较
低8位不等于0,那就证明,不是从nand,SPI,Flash启动的,执行do_clr_remap,进行重映射
低8位等于0,那就证明,是从nand,SPI,Flash启动的,执行check_boot_type

  • check_boot_type

读取寄存器,进行计算,然后PC跳到 _ clr_remap_fmc_entry

就是用来判断boot_type

  • do_clr_remap

要么就是在内部的sram,ddr,启动

如果是在ddr里面,那么ddr就已经启动了
如果不是在ddr里面,那么后面还要去初始化这个ddr,后面再重映射到ddr里面去

使能I-Cache

最后跳转到ddr_init

  • ddr_init

在这个ddr_init里面,还主要是这个init_registers,以及ddr_training

一般是走不到ddr_init里面来了,因为之间就初始化过ddr了,参考后面的no_ddr_init

  • _ clr_remap_fmc_entry

这里相当于定义了一个地址:

在上一节课中,提到了这个地址

所以,将来运行的时候,肯定也会到这个地址;下面来具体计算这个地址:

这里找了内部的SRAM

这里也是在做运算,这里做了do_clr_remap,还是要执行这一句:do_clr_remap

为什么要执行这一番计算呢? 因为这时候运行地址跟链接地址不一定相同

这里涉及到重定位

  • no_ddr_init

不需要ddr初始化的话,就会copy_to_init

copy_to_init:就是把完整的U-boot给Copy到DDR里面去

截止目前为止,如果是从eMMC启动,我们运行的应该还是前面Load进去那24KB的Uboot的第一阶段

第一阶段必须要做的事情就是把完整的U-boot给Load到DDR里面去,在后面跳转的时候做一次长跳转,直接跳到DDR里面去运行,这是必须要完成的,否则这段代码运行完了,就死了,所以这一个步骤是必须完成的。

  • copy_to_ddr

首先是:_ image_copy_start

然后是:如果r0和r1相等,那么就执行start_armboot

如果r0和r1相等,就证明你已经在ddr里面了,如果已经在ddr里面了,那么就去执行start_armboot,就相当于ddr里面的第二阶段的开

如果r0和r1不相等,就证明现在还不在ddr里面,而是在sRAM里面,那么就要去执行copy,copy就是bl:memcpy

_ img_copy_start,_bbs_start,得到地址,然后执行copy

这里面copy的应该不是完整的U-boot,应该是第一阶段加载到sRAM里面的那24KB,只是把这24KB给Copy到DDR里面去了;然后试图跳转到DDR里面去。

  • check_boot_mode

这里跳过了:ddr_init

这里经过了比较呢,最后是执行了:copy_flash_to_ddr

  • eMMC_boot

如果是eMMC启动,那么是一定能跳转到这个地方来的,具体的跳转还没分析好

这里执行了:emmc_boot_read

这个函数才是从eMMC里面copy完整的U-Boot到ddr的过程

然后执行relocate

这个就是:copy完了,进行重定位

  • emmc_boot_read

上面这个函数就会将完整的烧录在eMMC中的完整的U-Boot给Copy到DDR中

  • relocate

其中:CONFIG_EDMA_PLL_TRAINING这个符号是没有定义的,所以中间一段都是没有的

所以:就直接执行_start_armboot,也就是执行第二阶段

  • _ start_armboot

以上就是U-Boot第二阶段开头的那个文件

1.3 碎语

没有讲得特别清楚,因为没必要

因为第一阶段的内容,都是严格和CPU有关的
需要知道海思的CPU的整体流程是怎样子的,就够了

这里跳来跳去,是为了应对各种各样的可能的情况

可能有硬复位
可能有冷启动:没有上电的情况下,突然直接给它上电
也有热启动:本来就是上电运行的,按了复位按键,或通过软件寄存器的方法,触发了热启动
从上位机启动:从Hitool通过串口给你下载
应对各种情况,所以又各种跳转
这种东西,不难,海思如果给了所有细节的话,能够全部分析出来,但是没必要,分析到目前这种细节就可以了

第一阶段:完整的U-Boot已经从eMMC被拷贝到DDR里面来了

1.4 U-Boot的第一阶段结束总结:

  • 第一阶段公24KB,在内部sRAM中运行

  • 第一阶段完成了初始化时钟,DDR的工作,并且将完整的U-Boot已经copy到DDR中0x80700000了

  • 第一阶段结束跳转到DDR中的那一份U-Boot的前面未压缩部分的start_armboot函数中

  • 第二阶段就是从:DDR里面的那一份U-boot里面的第三部分;并且是第三部分的那前面的一部分,前面那一部分才是未压缩部分,后面那一部分是压缩了的传统U-Boot。

  • 到目前为止,传统的U-Boot还在被压缩着呢?缩在第三部分的后面;目前运行的还是:前面那部分未压缩的第二部分(也就是初始化DDR,eMMC,PLL等等,还未涉及到解压代码)


Author: Ruimin Huang
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source Ruimin Huang !
  TOC