一、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等等,还未涉及到解压代码)