tinker R基于RK3399PRO芯片。目前tinker R发布的Android SDK uboot部分是基于RK官方的uboot源码修改的。本文首先介绍了uboot 的编译方法,然后介绍了如何将修改后的uboot镜像烧录到设备上,最后介绍了rk uboot的修改方法,对于不需要对uboot进行修改的用户,只需要看编译和烧录方法即可。如果要进行更深入的了解,可以参考SDK下的文档,路径为RKDocs/common/u-boot。
在阅读下面的内容前,应当先进行以下准备
假设已经将整个SDK通过wiki或者网盘的方式拉下来了,目前在SDK的安装目录下。首先进入uboot的目录
cd u-boot
在u-boot下执行
# 对应的config文件为tinker_edge_r_defconfig
./make.sh tinker_edge_r
或者
# 对应的config文件为rk3399pro_defconfig
./make.sh rk3399pro
都可以,二者的区别仅仅在于后面的ums模式中,前者使用的VID PID为0x0b05 0x7820,后者使用的VID PID为 0x2207 0x330a
编译成功之后会有如下信息打印
编译出来的文件为
u-boot/uboot.img
u-boot/trust.img
rk3399pro_loader_v1.24.119.bin
note:不要更改SDK里面u-boot、rkbin和prebuilts之间的目录关系,否则会编译失败
rk的平台有两种下载模式,分别为loader模式和maskrom模式。对于tinkerR而言,它还支持UMS模式。这种模式与loader模式类似,是在uboot中进行下载。maskrom模式是在EMMC为空的情况下或者强制把EMMC的数据脚接地(tinkerR上是短接recovery header)进入强制下载模式,与高通平台的9008模式类似。
打开烧录工具,烧录建议在Windows下进行操作。烧录工具为
RKTools/windows/AndroidTool/AndroidTool_Release_v2.65.zip
将这个工具拷贝到windows系统下,并解压,里面有个AndroidTool.exe可执行程序
用typec线连接tinkerR与电脑,短接下图所示的位置,然后上电,此时 tinkerR进入MASKROM模式
note:必须先短接recovery header,再上电,之后取掉recovery header上面的跳线帽
在板上已经有其他部分固件的情况下,使用AndroidTool.exe,按照如下步骤烧录单独编译出来的uboot镜像
rk的bootloader分为两个部分,第一个部分是loader固件,包含ddr.bin和miniloader.bin等,没有源码,只能通过配置文件修改其配置,第二个部分uboot 基于开源的 U-Boot 2017.09版本进行定制,有源码
下面介绍一下第一级loader以及trust的打包流程,所有的流程都体现在make.sh脚本中
这个函数通过对应.config文件,找到了芯片类型,并将此类型赋值给RKCHIP_LOADER RKCHIP_TRUST变量
然后通过boot_merge命令生成目标的loader固件,其配置文件为${RKCHIP_LOADER}MINIALL.ini
因此,如果想要修改打包的目标文件,可以修改此ini文件。
要对uboot进行功能修改,必须要使能调试串口。板上没有默认的调试串口,因为RK3399PRO上面,默认的调试串口和SDMMC是复用的,板上已经将其作为SDMMC使用了,因此要选择另外一路串口作为调试串口。
40pin header上面也有个uart4,如果要选择uart4,修改方法类似
RK的SDK中一般默认串口0是用作蓝牙通信的,像AP6212这类模组,它的蓝牙使用串口。不过在tinkerR上面,蓝牙走的USB,所以串口0没有其他默认功能占用,因此选择串口0作为调试串口
要修改loader部分,只能通过改其配置文件,然后通过rk的工具生成一份。具体步骤如下
- 进入rkbin目录,找到当前使用的ddr.bin,并复制一份。
在SDK根目录下执行
cd rkbin/bin/rk33
cp rk3399pro_ddr_933MHz_v1.24.bin rk3399pro_ddr_933MHz_v1.24_uart0.bin
- 编写配置文件
比如配置文件名ddrbin_param_uart0.txt,内容如下
将这个文件存放到rkbin/tools下
- 生成ddr固件
在SDK根目录下执行
cd rkbin/tools
./ddrbin_tool ddrbin_param_uart0.txt ../bin/rk33/rk3399pro_ddr_933MHz_v1.24_uart0.bin
即可产生一个默认使用uart0输出调试信息的ddr固件
- 修改loader打包配置。
通过上一章节的描述,可知RK3399PRO使用的配置文件为rkbin/RKBOOT/RK3399PROMINIALL.ini
- 编译与验证
在uboot下执行./make.sh rk3399pro即可输出新的loader固件,其文件名为rk3399pro_loader_v1.24.119.bin
按前面介绍的烧录方法烧录之后,此时loader部分的调试信息会被打印出来
可以看uboot部分打印的编译时间,如果和实际的编译时间一致,说明uboot更新成功
rk uboot的启动也是先初始化一些时钟和外设,这一部分通常不用修改。此后,如果没有用ctrl+c进入命令行模式,则会执行默认的bootcmd。
如果按照上面的方式修改了调试串口的配置,此时调试串口可用,在进入uboot时按ctrl+c,可以进入uboot的命令模式。
使用如下命令可以查看其默认的启动命令
默认的启动命令是boot_android,如果失败,则会执行bootrkp,失败之后会使用distro_bootcmd命令,现在rk平台不管是android还是linux使用的都是android格式的boot.img,因此最终是boot_android命令生效
boot_android的实现在u-boot/cmd/boot_android.c下,可以看到它调用了do_boot_android
static int do_boot_android(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[])
{
unsigned long load_address;
int ret = CMD_RET_SUCCESS;
char *addr_arg_endp, *addr_str;
struct blk_desc *dev_desc;
...
if (argc >= 5) {
...
} else {
addr_str = env_get("kernel_addr_r");
...
}
...
dev_desc = blk_get_dev(argv[1], simple_strtoul(argv[2], NULL, 16));
if (!dev_desc) {
printf("Could not get %s %s\n", argv[1], argv[2]);
return CMD_RET_FAILURE;
}
ret = android_bootloader_boot_flow(dev_desc, load_address);
if (ret < 0) {
printf("Android boot failed, error %d.\n", ret);
return CMD_RET_FAILURE;
}
return CMD_RET_SUCCESS;
}
U_BOOT_CMD(
boot_android, 5, 0, do_boot_android,
"...",
"..."
);
这个函数首先获取kernel_addr_r指定的地址作为内核镜像加载地址,然后获取第一个和第二个参数中的存储器信息,并赋值给dev_desc,最终调用android_bootloader_boot_flow
android_bootloader_boot_flow 的实现在u-boot/common/android_bootloader.c下
int android_bootloader_boot_flow(struct blk_desc *dev_desc,
unsigned long load_address)
{
enum android_boot_mode mode = ANDROID_BOOT_MODE_NORMAL;
disk_partition_t misc_part_info;
int part_num;
int ret;
char *command_line;
char slot_suffix[3] = {0};
const char *mode_cmdline = NULL;
char *boot_partname = ANDROID_PARTITION_BOOT;
ulong fdt_addr;
/*
* 1. Load MISC partition and determine the boot mode
* clear its value for the next boot if needed.
*/
part_num = part_get_info_by_name(dev_desc, ANDROID_PARTITION_MISC,
&misc_part_info);
if (part_num < 0) {
printf("Could not find misc partition\n");
} else {
mode = android_bootloader_load_and_clear_mode(dev_desc,
&misc_part_info);
#ifdef CONFIG_RKIMG_BOOTLOADER
if (mode == ANDROID_BOOT_MODE_NORMAL) {
if (rockchip_get_boot_mode() == BOOT_MODE_RECOVERY)
mode = ANDROID_BOOT_MODE_RECOVERY;
}
#endif
}
...
switch (mode) {
case ANDROID_BOOT_MODE_NORMAL:
/* In normal mode, we load the kernel from "boot" but append
* "skip_initramfs" to the cmdline to make it ignore the
* recovery initramfs in the boot partition.
*/
...
break;
case ANDROID_BOOT_MODE_RECOVERY:
/* In recovery mode we still boot the kernel from "boot" but
* don't skip the initramfs so it boots to recovery.
*/
boot_partname = ANDROID_PARTITION_RECOVERY;
break;
...
}
...
if (load_android_image(dev_desc, boot_partname,
slot_suffix, &load_address)) {
printf("Android image load failed\n");
return -1;
}
...
/* Assemble the command line */
command_line = android_assemble_cmdline(slot_suffix, mode_cmdline);
env_update("bootargs", command_line);
debug("ANDROID: bootargs: \"%s\"\n", command_line);
...
ret = android_image_get_fdt((void *)load_address, &fdt_addr);
...
android_bootloader_boot_kernel(load_address);
/* TODO: If the kernel doesn't boot mark the selected slot as bad. */
return -1;
}
这个函数的大概流程就是先通过part_get_info_by_name找到misc分区里面的启动命令,决定是正常启动还是进入recovery模式,对应的是uboot里面的是这个打印
如果是正常启动,就通过load_android_image函数加载boot分区里面的镜像,否则是加载recovery分区里面的镜像
通过android_assemble_cmdline可以将uboot的命令行和kernel设备树里面的命令行合并。
通过android_image_get_fdt命令,从load_address指向的boot镜像加载地址中找到设备树部分的内容,并加载到fdt_addr指向的地址
最终通过android_bootloader_boot_kernel启动load_address里面boot的kernel镜像,这里最终也是调用bootm来实现的
修改建议:uboot部分经过rk多年的定制修改,和它的硬件外设,分区等高度耦合,因此可以修改的东西不多。建议如果要修改bootcmd的可以直接修改内核设备树,如果想在内核启动前进行一些外设操作,比如写入一两个i2c寄存器,可以修改bootcmd的流程,在boot_android之前进行添加。