平台:orangepi4 rockchip rk3399 LPDDR4 4G eMMC 16G
系统:ubuntu 20.04
主要介绍移植最新的bootloader到RK3399,我使用的是orangepi4主板。RK3399的uboot采用的方案有两种:瑞芯微原厂提供的idbloader.bin方案和uboot TPL/SPL方案
idbloader.bin方案是基于rkbin项目,这段代码是不开源的,对于我们想研究齐原理部分不是很方便,本文不做讨论,idbloader.bin方案移植可以参考uboot编译移植
瑞芯微还提供了一种采用U-Boot TPL/SPL 模式的开源uboot方案,参考官方移植,本文就是参考官网的移植方法进行移植,对于出现的问题进行修改,以达到启动的目的
1.RK3399上电启动过程
对于CPU来说实际上第一个阶段就是CPU刚启动的时候的bootrom阶段,这段代码是固化在CPU内部,CPU上电后自动将内部ROM的代码拷贝到SRAM的0xFFFF0000处然后执行。这段程序的功能是:程序首先做一些判断,程序会判断SPI Flash/eMMC/SDCard里面是否存在及里面是否有uboot固件,如果存在,读取固件到SRAM,然后程序到转到uboot的固件里面执行,这里只是读取了uboot前面的一小部分,因为毕竟内部SRAM空间是有限的(只有192KB,上电后被映射到了0xffff0000) ,假如这时候eMMC存在并且里面有程序,则idbloader.bin或TPL这部分代码就会被拷贝到SRAM并执行,如果所有的存储设备都找不到程序,则CPU会进入到一个模式叫MASKROM模式,此时USB会被设置成Device模式,使用瑞芯微的工具可以进行固件升级。
从这里可以看出bootrom内的程序是包含了SPI、eMMC、SDCard及USB设备驱动程序的,但是不包含对DDR的初始化。
2. uboot的启动阶段
主要是三个阶段:TPL SPL UBOOT
TPL阶段 意思是这样Target Program Loader,就是芯片级的初始化过程,这个时候的代码都是基于芯片平台的部分,此时出现在芯片的内部SRAM里面运行,主要的工作就是做一些CPU芯片的初始化、内存的初始化以及存储设备驱动加载。如果内存初始化失败则不能进入下一阶段,这一部分的代码相对独立,不能共用,对于RK3399来说,其代码就在arch\arm\mach-rockchip目录里面。
SPL阶段 指的是Secondary Program Loader 第二阶段程序加载器,主要是进行存储设备的初始化eMMC、SD等,这个阶段的目的主要是将存储设备的程序拷贝到DDR里面去,这一部分代码基本上是还可以通用,所以放在第二阶段来处理,这一步执行仍然是在CPU内部的SRAM中进行。
UBOOT阶段 这时候才是进行真正的uboot执行这部分的代码大部分都是通用的。
瑞芯微提供的idbloader.bin固件实际上是执行了前面的两个阶段任务,第三阶段代码实际上是一样的。
3.固件升级方式
通常我们是用瑞芯微提供的Linux下的工具rkdeveloptool进行升级,首先要知道rkdeveloptool是基于什么情况下才会起作用的,是在CPU进入MASKROM模式后而且跟主机通过USB连接,因为这个时候主板的DDR并没有初始化,而升级过程是需要很大的内存空间的,所以升级之前第一步要做的就是执行rkdeveloptool db rkxx_loader_vx.xx.bin ,这个固件其实就是idbloader.bin只不过这时候只是在内存中执行,如果不执行db命令的话其他的命令则无法执行因为没有做内存初始化工作。
按照开头uboot编译移植获取rkdeveloptool工具及rk3399_loader_v1.2x.bin文件
4. ATF移植
因为 RK3399 是 Arm64,所以我们还需要编译 ATF (Arm trust firmware), ATF 主要负责在启动 U-Boot 之前把 CPU 从安全的 EL3 切换到 EL2,然后跳转到 U-Boot,并且在内核启动后负责启动其他的 CPU。
ATF将系统启动从最底层进行了完整的统一划分,将secure monitor的功能放到了bl31中进行,这样当系统完全启动之后,在CA或者TEE OS中触发了smc或者是其他的中断之后,首先是遍历注册到bl31中的对应的service来判定具体的handle,这样可以对系统所有的关键smc或者是中断操作做统一的管理和分配。ATF的code boot整个启动过程框图如下:
这个固件对于uboot来说不是必须的,但是如果不添加会导致内核无法启动,我之前没有添加就导致进入内核时卡住了启动不了,会停在Starting kernel …这个地方
具体论述可以参考正点原子的关于STM32MP1开发板linux驱动教程第六章的说明STM32MP1嵌入式Linux驱动开发指南V2.0-嵌入式文档类资源-CSDN下载
安装工具:
sudo apt-get install gcc-arm-none-eabi gcc-aarch64-linux-gnu
下载源码:
git clone https://github.com/ARM-software/arm-trusted-firmware.git
编译:
选择编译平台rk3399和编译器
make PLAT=rk3399 CROSS_COMPILE=aarch64-linux-gnu-
编译成功后会在build/rk3399/release/bl31/目录下生成一个bl31.elf文件
4.uboot移植
移植采用的是eMMC方式,其他方式类似
下载源码
git clone https://gitlab.denx.de/u-boot/u-boot.git
查看Makefile版本号是 2022.01
将bl31.elf文件拷贝到uboot代码根目录
配置
选择一个配置版本,查看configs文件夹中RK3399的配置文件,要选择一个适的DDR版本的配置,因为我使用的是LPDDR4的板子,所以查看
grep -r “LPDDR4” configs/
一个LPDDR4开关打开的配置,因为我没有找到orangepi4的配置文件,所以我使用了另一个常用的开发板的配置
make nanopi-r4s-rk3399_defconfig
编译
make ARCH=arm CROSS_COMPILE=aarch64-linux-gnu-
编译成功后会生成idbloader.img u-boot.itb 这时候的idbloader.img文件并非通过rkbin工程得到的,而是由TPL/SPL两个文件组合起来的,实际的构建方式是:
tools/mkimage -n rkxxxx -T rksd -d rkxx_ddr_vx.xx.bin idbloader.img
cat rkxx_miniloader_vx.xx.bin >> idbloader.img
2022.01版本这里是自动生成的,在Makefile 1462行:
u-boot.itb也是自动生成的,在Makefile 1426行:
u-boot.itb实际上是u-boot.img的另一个变种,也是通过mkimage构建出来的,依赖于u-boot.its u-boot.dtb u-boot-nodtb.bin这三个文件,主要是为了支持DeviceTree新内核架构,实际上是通过mkimage fit的方法实现的,基于u-boot.its文件,可以查看u-boot.its文件:
/*
* This is a generated file.
*/
/dts-v1/;
/ {
description = "FIT image for U-Boot with bl31 (TF-A)";
#address-cells = <1>;
images {
uboot {
description = "U-Boot (64-bit)";
data = /incbin/("u-boot-nodtb.bin");
type = "standalone";
os = "U-Boot";
arch = "arm64";
compression = "none";
load = <0x00200000>;
};
atf_1 {
description = "ARM Trusted Firmware";
data = /incbin/("bl31_0x00040000.bin");
type = "firmware";
arch = "arm64";
os = "arm-trusted-firmware";
compression = "none";
load = <0x00040000>;
entry = <0x00040000>;
};
atf_2 {
description = "ARM Trusted Firmware";
data = /incbin/("bl31_0xff3b0000.bin");
type = "firmware";
arch = "arm64";
os = "arm-trusted-firmware";
compression = "none";
load = <0xff3b0000>;
};
atf_3 {
description = "ARM Trusted Firmware";
data = /incbin/("bl31_0xff8c0000.bin");
type = "firmware";
arch = "arm64";
os = "arm-trusted-firmware";
compression = "none";
load = <0xff8c0000>;
};
fdt_1 {
description = "rk3399-khadas-edge.dtb";
data = /incbin/("arch/arm/dts/rk3399-khadas-edge.dtb");
type = "flat_dt";
compression = "none";
};
};
configurations {
default = "config_1";
config_1 {
description = "rk3399-khadas-edge.dtb";
firmware = "atf_1";
loadables = "uboot","atf_2","atf_3";
fdt = "fdt_1";
};
};
};
具体构建过程可参见uboot构建框架6-u-boot.bin生成过程追踪_sunxiaohusunke的专栏-CSDN博客_uboot.bin
烧录
进入MaskRom模式,将EMMC_CLKO这个信号接地上电后就会进入MaskRom模式,注意此时不要插SD卡,我测试如果插SD卡是不会进入MaskRom模式的
sudo rkdeveloptool db rk3399_loader_v1.25.126.bin
sudo rkdeveloptool wl 0x40 idbloader.img
sudo rkdeveloptool wl 0x4000 u-boot.itb
sudo rkdeveloptool rd
上电
给开发板上电,通过串口打印输出
分析
通过串口发现TPL阶段执行完毕,DDR初始化成功,在进入SPL后MMC读取错误,eMMC应该是有一个通用的驱动程序来驱动,虽然芯片可能不会不一样不是一个厂商但是其接口操作方式应该是一样的不然CPU上电第一步也不会认到eMMC了,重点查看驱动有没有加载。
查看rk3399.dtsi找到emmc设备对应的compatible是arasan,sdhci-5.1通过查找发现其驱动是在drivers\mmc\rockchip_sdhci.c 在rockchip_sdhci_probe添加打印函数:
重新编译后下载发现并没有打印,这说明驱动并没有加载成功,所以要一步步查找了。有一种解决方法是先换一种配置方案,我换了一个配置khadas-edge-rk3399_defconfig,这时候有打印信息出来:
说明这个配置是有用的,但是还是不能启动,打开rockchip_sdhci.c Debug信息(不能打开所有的debug函数,因为这样操作会有问题,具体原因还不清楚,我的做法是把单个文件里面的debug函数全部替换成printf函数)发现:
先不管这么多把return -ENODEV;屏蔽掉:
这时候发现了没检测到上电:
这应该是跟寄存器有关,查看struct rockchip_emmc_phy定义
这个指针应该是指向一个寄存器,经过查看发现phy指针是在rk3399_emmc_get_phy()函数中赋值的,因为RK3399有一个General Register Files 的struct rk3399_grf_regs寄存器表,这是一个全局静态结构体,指向通用寄存器地址,所以phy指针应该是指向这个寄存器表里面的某一个区域,
因为在rk3399_emmc_get_phy()函数通过 syscon_get_first_range获取寄存器表指针并没有报错,所以应该是priv->phy指向有错误
通过查询RK3399TRM手册发现EMMCPHY_CON0的偏移地址是0x0f780
我在这里直接将grf_phy_offset=0x0f780;然后重新编译:
成功进入命令行!!!
这时候再来找一下为何nanopi-r4s-rk3399_defconfig配置不行的原因,查看nanopi-r4s-rk3399_defconfig找到DEVICE_TREE配置
打开rk3399-nanopi-r4s.dts发现emmc_phy、sdhci是disable的,把它们改成okay后重新编译后发现就正常啦,关于dts原理后面会讲到。
这是我从源头开始移植uboot的记录,其中也不乏一些分析调试的过程,有时候想解决一个问题确实不是那么简单,不知道从什么地方入手,但是按部就班的去做,总是能找到方法突破点,当然我的上面还有一些未解决的问题,没有找到emmc_phy设备的问题,后面有时间再拿出来研究,有什么问题欢迎指正。