Rockchip RK3399 - TPL/SPL方式加载uboot
----------------------------------------------------------------------------------------------------------------------------
开发板 :NanoPC-T4
开发板
eMMC
:16GB
LPDDR3
:4GB
显示屏 :15.6
英寸HDMI
接口显示屏
u-boot
:2017.09
----------------------------------------------------------------------------------------------------------------------------
NanoPC-T4
开发板,主控芯片是Rockchip RK3399
,big.LITTLE
大小核架构,双Cortex-A72
大核(up to 2.0GHz
) + 四Cortex-A53
小核结构(up to 1.5GHz
);Cortex-A72
处理器是Armv8-A
架构下的一款高性能、低功耗的处理器。
我们接着上一节,介绍《Rockchip
处理器启动支持的两种引导方式》:
TPL/SPL
加载:使用Rockchip
官方提供的TPL/SPL
U-boot(就是我们上面说的小的uboot
),该方式完全开源;- 官方固件加载:使用
Rockchip idbLoader
,来自Rockchip rkbin
项目的Rockchip DDR
初始化bin
和miniloader bin
,该方式不开源;
这一节我们将介绍采用TPL/SPL
方式,如何编译源码以及烧录程序到eMMC
,从而完成uboot
的启动。
一、uboot
uboot
通常有三种:
uboot
官方源码:https://github.com/u-boot/u-boot
,uboot
官方源码是由uboot
官方维护,支持非常全面的芯片,但对具体某款开发板支持情况一般;- 半导体厂商瑞芯微官方源码:
https://github.com/rockchip-linux/u-boot
,半导体厂商基于uboot
官方源码进行修改,对自家的芯片进行完善的支持,针对某款处理器支持情况较好; - 开发板友善之家官方源码:
https://github.com/friendlyarm/uboot-rockchip
,开发板厂商基于半导体厂商维护的uboot
,对自家的开发板进行板级支持,针对某款开发板支持情况较好;
我们不要上来就去移植uboot
官方的源码,一般来说uboot
官方的代码不做任何改动,是无法在我们板子上直接运行的。我们先去把Rockchip
官方提供的2017.09
版本的uboot
代码下载下来,编译之后看看能不能运行,如果可以的话,再去参考Rockchip
官方的uboot
去移植最新版本的uboot
。
这里有一点需要补充的是:Rockchip
官方提供的uboot 2017.09
版本做了大量的改动,尤其是引导内核启动上,为了支持多种内核镜像加载方式,对uboot
源码进行了大量修改,所以要求我们烧录的内核镜像也要按照官方指定的格式调整否则无法被uboot
正确引导。
1.1 下载源码
我们可以在Rockchip
的github
上下载到芯片厂商提供的u-boot
源码,如下图所示:
这里我们下载的是最新的next-dev
分支的代码:
root@ubuntu:/work/sambashare/rk3399# git clone https://github.com/rockchip-linux/u-boot.git --depth 1 -b next-dev
这里我是下载到/work/sambashare/rk3399
路径下的,这个路径后面专门存放与rk3399
相关的内容。
进入到u-boot
文件夹里,这就是我们需要的uboot
的源码了,后面就可以进行二次开发了;
root@ubuntu:/work/sambashare/rk3399/u-boot# cd ..
root@ubuntu:/work/sambashare/rk3399# cd u-boot/
root@ubuntu:/work/sambashare/rk3399/u-boot# ls -l
总用量 488
drwxr-xr-x 2 root root 4096 5月 7 20:00 api
drwxr-xr-x 14 root root 4096 5月 7 20:00 arch
drwxr-xr-x 181 root root 4096 5月 7 20:00 board
drwxr-xr-x 6 root root 4096 5月 7 20:00 cmd
drwxr-xr-x 5 root root 4096 5月 7 20:00 common
-rw-r--r-- 1 root root 2260 5月 7 20:00 config.mk
drwxr-xr-x 2 root root 69632 5月 7 20:00 configs
drwxr-xr-x 2 root root 4096 5月 7 20:00 disk
drwxr-xr-x 10 root root 12288 5月 7 20:00 doc
drwxr-xr-x 3 root root 4096 5月 7 20:00 Documentation
drwxr-xr-x 56 root root 4096 5月 7 20:00 drivers
drwxr-xr-x 2 root root 4096 5月 7 20:00 dts
drwxr-xr-x 2 root root 4096 5月 7 20:00 env
drwxr-xr-x 4 root root 4096 5月 7 20:00 examples
drwxr-xr-x 12 root root 4096 5月 7 20:00 fs
drwxr-xr-x 32 root root 16384 5月 7 20:00 include
-rw-r--r-- 1 root root 1863 5月 7 20:00 Kbuild
-rw-r--r-- 1 root root 14162 5月 7 20:00 Kconfig
drwxr-xr-x 14 root root 4096 5月 7 20:00 lib
drwxr-xr-x 2 root root 4096 5月 7 20:00 Licenses
-rw-r--r-- 1 root root 12587 5月 7 20:00 MAINTAINERS
-rw-r--r-- 1 root root 56469 5月 7 20:00 Makefile
-rwxr-xr-x 1 root root 19845 5月 7 20:00 make.sh
drwxr-xr-x 2 root root 4096 5月 7 20:00 net
-rwxr-xr-x 1 root root 1640 5月 7 20:00 pack_resource.sh
drwxr-xr-x 5 root root 4096 5月 7 20:00 post
-rw-r--r-- 1 root root 34 5月 7 20:00 PREUPLOAD.cfg
-rw-r--r-- 1 root root 189024 5月 7 20:00 README
drwxr-xr-x 6 root root 4096 5月 7 20:00 scripts
-rw-r--r-- 1 root root 17 5月 7 20:00 snapshot.commit
drwxr-xr-x 12 root root 4096 5月 7 20:00 test
drwxr-xr-x 16 root root 4096 5月 7 20:00 tools
需要注意的是:尽量不要下载release
分支,最初我也下载了这个分支,这里分支默认配置有问题。其中配置项:CONFIG_ROCKCHIP_SPL_RESERVE_IRAM=0x50000
,表示SPL为ATF
预留了0x50000
大小的内存空间,这个地址范围是从0x00000008
开始的,而0x50008
之后是SPL
代码。程序运行后,SPL
从eMMC
加载bl31_0x00040000.bin
到内存地址0x40000
时,由于文件也比较大,直接覆盖了原有的SPL
代码,导致程序直接卡死。当然修复这个问题也很简单,配置CONFIG_ROCKCHIP_SPL_RESERVE_IRAM=0
即可。
1.2 配置uboot
uboot
的编译分为两步:配置、编译。单板的默认配置在configs
目录下,这里我们直接选择configs/evb-rk3399_defconfig
,这是Rockchip
评估板的配置:
View Code
CONFIG_ARM=y
CONFIG_ARCH_ROCKCHIP=y
CONFIG_SPL_LIBCOMMON_SUPPORT=y
CONFIG_SPL_LIBGENERIC_SUPPORT=y
CONFIG_SYS_MALLOC_F_LEN=0x4000
CONFIG_ROCKCHIP_RK3399=y
CONFIG_RKIMG_BOOTLOADER=y
# CONFIG_USING_KERNEL_DTB is not set
CONFIG_DEFAULT_DEVICE_TREE="rk3399-evb"
CONFIG_DEBUG_UART=y
CONFIG_FIT=y
CONFIG_SPL_LOAD_FIT=y
CONFIG_SPL_FIT_GENERATOR="arch/arm/mach-rockchip/make_fit_atf.py"
# CONFIG_DISPLAY_CPUINFO is not set
CONFIG_ANDROID_BOOTLOADER=y
CONFIG_SPL_STACK_R=y
CONFIG_SPL_STACK_R_MALLOC_SIMPLE_LEN=0x10000
CONFIG_SPL_ATF=y
CONFIG_SPL_ATF_NO_PLATFORM_PARAM=y
CONFIG_FASTBOOT_BUF_ADDR=0x00800800
CONFIG_FASTBOOT_BUF_SIZE=0x04000000
CONFIG_FASTBOOT_FLASH=y
CONFIG_FASTBOOT_FLASH_MMC_DEV=0
CONFIG_CMD_BOOTZ=y
# CONFIG_CMD_IMLS is not set
CONFIG_CMD_GPT=y
CONFIG_CMD_LOAD_ANDROID=y
CONFIG_CMD_BOOT_ANDROID=y
CONFIG_CMD_BOOT_ROCKCHIP=y
CONFIG_CMD_MMC=y
CONFIG_CMD_SF=y
CONFIG_CMD_USB=y
CONFIG_CMD_USB_MASS_STORAGE=y
# CONFIG_CMD_SETEXPR is not set
CONFIG_CMD_TIME=y
CONFIG_RKPARM_PARTITION=y
CONFIG_SPL_OF_CONTROL=y
CONFIG_OF_LIVE=y
CONFIG_OF_SPL_REMOVE_PROPS="pinctrl-0 pinctrl-names clock-names interrupt-parent assigned-clocks assigned-clock-rates assigned-clock-parents"
CONFIG_NET_RANDOM_ETHADDR=y
CONFIG_REGMAP=y
CONFIG_SPL_REGMAP=y
CONFIG_SYSCON=y
CONFIG_SPL_SYSCON=y
CONFIG_CLK=y
CONFIG_SPL_CLK=y
CONFIG_ROCKCHIP_GPIO=y
CONFIG_SYS_I2C_ROCKCHIP=y
CONFIG_MISC=y
CONFIG_ROCKCHIP_EFUSE=y
CONFIG_MMC_DW=y
CONFIG_MMC_DW_ROCKCHIP=y
CONFIG_MMC_SDHCI=y
CONFIG_MMC_SDHCI_ROCKCHIP=y
CONFIG_DM_ETH=y
CONFIG_ETH_DESIGNWARE=y
CONFIG_GMAC_ROCKCHIP=y
CONFIG_PINCTRL=y
CONFIG_SPL_PINCTRL=y
CONFIG_DM_PMIC=y
CONFIG_PMIC_RK8XX=y
CONFIG_REGULATOR_PWM=y
CONFIG_DM_REGULATOR_FIXED=y
CONFIG_REGULATOR_RK8XX=y
CONFIG_PWM_ROCKCHIP=y
CONFIG_RAM=y
CONFIG_SPL_RAM=y
CONFIG_ROCKCHIP_SDRAM_COMMON=y
CONFIG_DM_RESET=y
CONFIG_BAUDRATE=1500000
CONFIG_DEBUG_UART_BASE=0xFF1A0000
CONFIG_DEBUG_UART_CLOCK=24000000
CONFIG_DEBUG_UART_SHIFT=2
CONFIG_SYSRESET=y
CONFIG_USB=y
CONFIG_USB_XHCI_HCD=y
CONFIG_USB_XHCI_DWC3=y
CONFIG_USB_EHCI_HCD=y
CONFIG_USB_EHCI_GENERIC=y
CONFIG_USB_OHCI_HCD=y
CONFIG_USB_OHCI_GENERIC=y
CONFIG_USB_DWC3=y
CONFIG_USB_DWC3_GADGET=y
CONFIG_USB_STORAGE=y
CONFIG_USB_GADGET=y
CONFIG_USB_GADGET_DOWNLOAD=y
CONFIG_USB_GADGET_MANUFACTURER="Rockchip"
CONFIG_USB_GADGET_VENDOR_NUM=0x2207
CONFIG_USB_GADGET_PRODUCT_NUM=0x330a
CONFIG_USB_HOST_ETHER=y
CONFIG_USB_ETHER_ASIX=y
CONFIG_USB_ETHER_ASIX88179=y
CONFIG_USB_ETHER_MCS7830=y
CONFIG_USB_ETHER_RTL8152=y
CONFIG_USB_ETHER_SMSC95XX=y
CONFIG_DM_VIDEO=y
CONFIG_DISPLAY=y
CONFIG_DRM_ROCKCHIP=y
CONFIG_DRM_ROCKCHIP_DW_MIPI_DSI=y
CONFIG_DRM_ROCKCHIP_ANALOGIX_DP=y
CONFIG_LCD=y
CONFIG_USE_TINY_PRINTF=y
CONFIG_SPL_TINY_MEMSET=y
CONFIG_ERRNO_STR=y
因此执行如下命令,生成.config
文件:
root@ubuntu:/work/sambashare/rk3399/u-boot# make evb-rk3399_defconfig V=1
输出如下(忽略编译器警告信息):
root@ubuntu:/work/sambashare/rk3399/u-boot# make evb-rk3399_defconfig V=1
make -f ./scripts/Makefile.build obj=scripts/basic
cc -Wp,-MD,scripts/basic/.fixdep.d -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer -o scripts/basic/fixdep scripts/basic/fixdep.c
rm -f .tmp_quiet_recordmcount
make -f ./scripts/Makefile.build obj=scripts/kconfig evb-rk3399_defconfig
cc -Wp,-MD,scripts/kconfig/.conf.o.d -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer -D_GNU_SOURCE -I/usr/include/ncursesw -DCURSES_LOC="<ncurses.h>" -DNCURSES_WIDECHAR=1 -DLOCALE -c -o scripts/kconfig/conf.o scripts/kconfig/conf.c
cat scripts/kconfig/zconf.tab.c_shipped > scripts/kconfig/zconf.tab.c
cat scripts/kconfig/zconf.lex.c_shipped > scripts/kconfig/zconf.lex.c
cat scripts/kconfig/zconf.hash.c_shipped > scripts/kconfig/zconf.hash.c
cc -Wp,-MD,scripts/kconfig/.zconf.tab.o.d -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer -D_GNU_SOURCE -I/usr/include/ncursesw -DCURSES_LOC="<ncurses.h>" -DNCURSES_WIDECHAR=1 -DLOCALE -Iscripts/kconfig -c -o scripts/kconfig/zconf.tab.o scripts/kconfig/zconf.tab.c
cc -o scripts/kconfig/conf scripts/kconfig/conf.o scripts/kconfig/zconf.tab.o
scripts/kconfig/conf --defconfig=arch/../configs/evb-rk3399_defconfig Kconfig
#
# configuration written to .config
#
配置主要分为三个步骤:
- 第一步:执行
make -f ./scripts/Makefile.build obj=scripts/basic
,编译生成scripts/basic/fixdep
工具; - 第二步:执行
make -f ./scripts/Makefile.build obj=scripts/kconfig evb-rk3399_defconfig
,编译生成scripts/kcofig/conf
工具; - 第三步:执行
scripts/kconfig/conf --defconfig=arch/../configs/evb-rk3399_defconfig Kconfig
,scripts/kconfig/conf
根据evb-rk3399_defconfig
生成.config
配置文件;
这里会在当前路径下生成.config
文件,这实际上是一个配置文件,这个文件是怎么生成的呢,实际上就是根据configs/evb-rk3399_defconfig
文件以及我们make menuconfig
看到的那些默认配置(或者说是各个目录下的Kconfig
文件中有效的default
项)生成的。
在进行make
编译的时候,会根据这个文件生成include/config/auto.conf
文件,同时在顶层Makefile
会引入auto.conf
文件:
ifeq ($(dot-config),1)
# Read in config
-include include/config/auto.conf
这样在执行make
编译过程中,就可以根据include/config/auto.conf
中的宏的定义编译不同的库文件。
1.2.1 配置串口波特率
uboot
中默认的调试串口波特率是1500000
,有很多的调试终端不支持1.5M
的波特率,我们可以把波特率重新配置下,在u-boot
文件夹下输入命令:make menuconfig
配置;
Device Drivers --->
Serial drivers --->
(150000) Default baudrate
注意: 波特率数值如果无法删除,按CTRL+
回车键尝试。
也可以直接编辑.config
配置项:
CONFIG_BAUDRATE=1500000
1.2.2 配置uboot
启动倒计时
如果在uboot
启动倒计时结束之前,没有按下任何键,将会执行那么将执行也就是bootcmd
中配置中的命令,bootcmd
中保存着默认的启动命令。
(5) delay in seconds before automatically booting
也可以直接编辑.config
配置项:
CONFIG_BOOTDELAY=5
保存文件,输入文件名为evb-rk3399_defconfig
,在当前路径下生成evb-rk3399_defconfig
:存档:
root@ubuntu:/work/sambashare/rk3399/u-boot# mv evb-rk3399_defconfig ./configs/
注意:如果需要配置生效,需要使用make distclean
清除之前的配置,重新执行配置命令。
更多内容可以参考我之前写的有关s3c2440 uboot
配置的文章:《make smdk2410_defconfig
配置分析》,虽然SoC
不同,但是make
配置流程是一样的。
1.2.3 开启调试信息
在uboot
启动时,如果我们想打印更加详细的信息,可以在include/configs/evb_rk3399.h
中加入如下宏:
#define DEBUG
后面测试发现,设置了这个虽然可以输出一些调试信息,但是确无法进入uboot
命令行,因此这个非特殊场景,尽量不要设置。
1.3 编译uboot
执行make
命令,生成u-boot
文件:
root@ubuntu:/work/sambashare/rk3399/u-boot# make ARCH=arm CROSS_COMPILE=arm-linux-
编译过程我们会发现有一些错误,这些错误的处理我们在文章的最后单独介绍。
更多内容可以参考我之前写的有关s3c2440 uboot
编译的文章:《make
编译正向分析之顶层目标依赖》,虽然SoC
不同,但是make
编译流程是一样的。
1.4 image
镜像
成功编译之后,就会在uboot
源码的根目录下产生多个可执行二进制文件以及编译过程文件,这些文件都是u-bootxxx
的命名方式。这些文件由一些列名为.xxx.cmd
的文件生成,.xxx.cmd
这些文件都是由编译系统产生的用于处理最终的可执行程序的。
在uboot
根录下生成文件有:
root@ubuntu:/work/sambashare/rk3399/u-boot# ll u-boot* Sys*
-rw-r--r-- 1 root root 153740 5月 14 10:30 System.map
-rwxr-xr-x 1 root root 6872736 5月 14 10:30 u-boot*
-rw-r--r-- 1 root root 931504 5月 14 10:30 u-boot.bin
-rw-r--r-- 1 root root 15808 5月 14 10:30 u-boot.cfg
-rw-r--r-- 1 root root 9996 5月 14 10:30 u-boot.cfg.configs
-rw-r--r-- 1 root root 51685 5月 14 10:30 u-boot.dtb # 设备树
-rw-r--r-- 1 root root 931501 5月 14 10:30 u-boot-dtb.bin # 等同u-boot.bin
-rw-r--r-- 1 root root 932864 5月 14 10:30 u-boot-dtb.img # 等同u-boot.img
-rw-r--r-- 1 root root 932864 5月 14 10:30 u-boot.img
-rw-r--r-- 1 root root 1304 5月 14 10:30 u-boot.lds
-rw-r--r-- 1 root root 800454 5月 14 10:30 u-boot.map
-rwxr-xr-x 1 root root 879816 5月 14 10:30 u-boot-nodtb.bin*
-rwxr-xr-x 1 root root 2529568 5月 14 10:30 u-boot.srec*
-rw-r--r-- 1 root root 300850 5月 14 10:30 u-boot.sym
其中:
u-boot
: 这个文件是编译后产生的ELF
格式的最原始的uboot
镜像文件,后续的文件都是由它产生的.u-boot.cmd
这个命令脚本描述了如何产生;u-boot-nodtb.bin
: 这文件是使用编译工具链的objcopy
工具从u-boot
这个文件中提取来的,它只包含可执行的二进制代码。就是把u-boot
这个文件中对于执行不需要的节区删除后剩余的仅执行需要的部分。由.u-boot-nodtb.bin.cmd
这个命令脚本产生;u-boot-dtb.bin
: 在u-boot-nodtb.bin
尾部拼接上设备树后形成的文件。由.u-boot-dtb.bin.cmd
这个命令脚本产生;u-boot.bin
: 就是把u-boot-dtb.bin
重命名得到的。由.u-boot.bin.cmd
这个命令脚本产生;u-boot.img
: 在u-boot.bin
开头拼接一些信息后形成的文件。由.u-boot.img.cmd
这个命令脚本产生;u-boot-dtb.img
: 在u-boot.bin
开头拼接一些信息后形成的文件。由.u-boot-dtb.img.cmd
这个命令脚本产生;u-boot.srec
:S-Record
格式的镜像文件。由.u-boot.srec.cmd
这个命令脚本产生;u-boot.sym
: 这个是从u-boot
中导出的符号表文件。由.u-boot.sym.cmd
这个命令脚本产生;u-boot.lds
: 编译使用的链接脚本文件。由.u-boot.lds.cmd
这个命令脚本产生;u-boot.map
: 编译的内存映射文件。该文件是有编译工具链的连接器输出的;System.map
: 记录u-boot
中各个符号在内核中位置,但是这个文件是使用了nm
和grep
工具来手动生成的;
由于开启了SPL
和TPL
的,因此,在编译uboot
时会额外单独编译SPL
、TPL
,编译产生的镜像文件就存放在 ./spl
、./tpl
目录下。这下面的镜像生成方式与uboot
基本是一模一样的。
root@ubuntu:/work/sambashare/rk3399/u-boot# ls tpl
arch cmd drivers env include u-boot.cfg u-boot-tpl u-boot-tpl.dtb u-boot-tpl.map u-boot-tpl.sym
board common dts fs lib u-boot-spl.lds u-boot-tpl.bin u-boot-tpl-dtb.bin u-boot-tpl-nodtb.bin
root@ubuntu:/work/sambashare/rk3399/u-boot# ls spl
arch cmd drivers env include u-boot.cfg u-boot-spl.bin u-boot-spl-dtb.bin u-boot-spl.map u-boot-spl.sym
board common dts fs lib u-boot-spl u-boot-spl.dtb u-boot-spl.lds u-boot-spl-nodtb.bin
以SPL
为例:
u-boot-spl
: 这个文件是编译后产生的ELF
格式的SPL
镜像文件,后续的文件都是由它产生的.u-boot-spl.cmd
这个命令脚本描述了如何产生;u-boot-spl-nodtb.bin
: 这文件是使用编译工具链的objcopy
工具从u-boot-spl
这个文件中提取来的,它只包含可执行的二进制代码。就是把u-boot-spl
这个文件中对于执行不需要的节区删除后剩余的仅执行需要的部分。由.u-boot-spl-nodtb.bin.cmd
这个命令脚本产生;u-boot-spl-dtb.bin
: 在u-boot-nodtb.bin
尾部依次拼接上u-boot-spl-pad.bin
和u-boot-spl.dtb
后形成的文件。由.u-boot-spl-dtb.bin.cmd
这个命令脚本产生;u-boot-spl.bin
: 就是把u-boot-dtb.bin
重命名得到的。由.u-boot-spl.bin.cmd
这个命令脚本产生;u-boot-spl.lds
: 编译使用的链接脚本文件。由.u-boot-spl.lds.cmd
这个命令脚本产生;u-boot-spl.map
: 编译SPL
的内存映射文件;u-boot-spl.dtb
: 这个是编译好的设备树二进制文件。就是./dts/dt.dtb
重命名得到的。./dts/dt.dtb
来自于arch/arm/dts/stm32f769-eval.dtb
重命名;
二、idbloader.img
我们基于uboot
源码编译出TPL/SPL
,其中TPL
负责实现DDR
初始化,TPL
初始化结束之后会回跳到BootROM
程序,BootROM
程序继续加载SPL
,SPL
加载u-boot.itb
文件,然后跳转到uboot
执行。
idbloader.img
是由tpl/u-boot-tpl.bin
和spl/u-boot-spl.bin
文件生成,这里我们需要使用到tools
目录下的mkimage
工具。
2.1 tpl/u-boot-tpl.bin
在u-boot
目录下执行:
root@ubuntu:/work/sambashare/rk3399/u-boot# tools/mkimage -n rk3399 -T rksd -d tpl/u-boot-tpl.bin idbloader.img
Image Type: Rockchip RK33 (SD/MMC) boot image
Init Data Size: 81920 bytes
其中:
-n rk3399
将镜像文件的名称设置为rk3399
;-T rksd
将映像类型指定为Rockchip SD
卡启动映像;-d tpl/u-boot-tpl.bin
将生成的TPL
镜像文件tpl/u-boot-tpl.bin
指定为输入文件,而idbloader.img
则指定为输出文件。
生成idbloader.img
文件:
root@ubuntu:/work/sambashare/rk3399/u-boot# ll idbloader.img
-rw-r--r-- 1 root root 83968 5月 14 10:38 idbloader.img
2.2 spl/u-boot-spl.bin
将spl/u-boot-spl.bin
合并到idbloader.img
:
root@ubuntu:/work/sambashare/rk3399/u-boot# cat spl/u-boot-spl.bin >> idbloader.img
root@ubuntu:/work/sambashare/rk3399/u-boot# ll idbloader.img
-rw-r--r-- 1 root root 210675 5月 14 10:39 idbloader.img
三、u-boot.idb
u-boot.itb
实际上是u-boot.img
的另一个变种,也是通过mkimage
构建出来的,依赖于u-boot.its u-boot.dtb u-boot-nodtb.bin
这三个文件。
mkimage
工具在u-boot
源码目录下的tools
目录中,不过由于u-boot
官方原本的FIT
功能无法满足实际产品需要,所以RK
平台对FIT
功能进行了适配和优化,所以自然对mkimage
工具的源代码进行了修改、优化;所以对于RK
平台硬件,如果使用FIT
格式镜像,必须使用RK u-boot
源码编译生成的mkimage
工具,不可使用u-boot
原版的mkimage
工具。
在顶层Makefile
文件中:
u-boot.itb: u-boot-nodtb.bin dts/dt.dtb $(U_BOOT_ITS) FORCE
$(call if_changed,mkfitimage)
这里的mkfitimage
被设置为了:
cmd_mkfitimage = $(objtree)/tools/mkimage $(MKIMAGEFLAGS_$(@F)) -f $(U_BOOT_ITS) -E $@ \
$(if $(KBUILD_VERBOSE:1=), >$(MKIMAGEOUTPUT))
3.1 FIT
介绍
FIT
是flattened image tree
的简称,它采用了device tree source filse(DTS)
的语法,生成的image
文件也和dtb
文件类似(称做itb
)。
注意:这一小节只是补充关于FIT
的相关知识,并不是编译u-boot.itb
的步骤。
具体可以参考:
- 《
doc/uImage.FIT/source_file_format.txt
》; - 《
Flattened Image Tree (FIT) Format
》: - 《
RK3568
开发笔记整理之FIT Image
》。
3.1.1 生成步骤
其中image source file(.its)
和device tree source file(.dts)
类似,负责描述要生成的image file
的信息。mkimage
和dtc
工具,可以将.its
文件以及对应的image data file
,打包成一个image file
。
3.1.2 image source file
语法
image source file
的语法和device tree source file
完全一样,只不过自定义了一些特有的节点,包括images
、configurations
等。1个FIT
根节点应该包含如下布局:
/ o image-tree
|- description = "image description" # 对本镜像的描述
|- timestamp = <12399321> # 镜像修改时间
|- #address-cells = <1>
|
o images # 镜像节点,必须包含一个子节点
| |
| o image-1 {...} #子节点
| o image-2 {...} #子节点
| ...
|
o configurations # 提供对上述镜像的组合,有默认配置和多种配置可选
|- default = "conf-1" # 默认配置,bootm未指定特殊配置时使用
|
o conf-1 {...} # 配置1
o conf-2 {...} # 配置2
...
其中:
description
:描述信息;timestamp
:修改镜像的时间,由mkimage
工具自动生成;images
:这个节点包含一组子节点,每个子节点代表一个单独的子镜像(如kernel
、ramdisk
等),至少需要一个子镜像;configurations
:配置项节点,可以将不同类型的二进制文件,根据不同的场景,组合起来,形成一个个的配置项。u-boot
在boot
的时候,以配置项为单位加载、执行,这样就可以根据不同的场景,方便的选择不同的配置;
3.1.2.1 /images
节点
该节点中描述了Image
镜像必要的信息,/images
节点的每个子节点应该具有以下布局:
o image-1
|- description = "component sub-image description"
|- data = /incbin/("path/to/data/file.bin")
|- type = "sub-image type name"
|- arch = "ARCH name"
|- os = "OS name"
|- compression = "compression name"
|- load = <00000000>
|- entry = <00000000>
|
o hash-1 {...}
o hash-2 {...}
...
可以包含如下的关键字:
description
:子镜像的描述,可以随便写;data
:二进制文件的路径,格式为/incbin/("path/to/data/file.bin")
;type
:子镜像的类型,standalone
,firmware
,kernel
,ramdisk
,flat_dt
等;arch
:平台类型,arm
,arm64
,i386
等;os
:操作系统类型,linux
、vxworks
等;compression
:二进制文件的压缩格式,none
、bzip2
、gzip
、lz4
等;load
:二进制文件的加载位置,SPL
或uboot
会把它copy
对应的地址上;entry
:二进制文件入口地址,一般kernel image
需要提供,uboot
会跳转到该地址上执行;hash
:镜像使用的校验算法,如sha256
,crc32
等。
这里我们节点说一下hash
节点;
o hash@1
|- algo = "hash or checksum algorithm name"
|- value = [hash or checksum value]
其中:
algo
:算法名称,如crc32
,md5
,sha256
等;value
:算法校验值,即algo
计算后的数值。
例如u-boot.its
中的包含了1个standalone image
、5个firmware image
、1个fdt image
。以atf@1
节点为例:
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>;
};
3.1.2.2 /configurations
节点
可以将不同类型的二进制文件,根据不同的场景,组合起来,形成一个个的配置项,u-boot
在boot
的时候,以配置项为单位加载、执行,这样就可以根据不同的场景,方便的选择不同的配置。
下面是u-boot.its
中的配置节点:
configurations {
default = "config";
config {
description = "Rockchip armv8 with ATF";
rollback-index = <0x0>;
firmware = "atf@1";
loadables = "uboot", "atf@2" , "atf@3" , "atf@4" , "atf@5" , "atf@6" ;
fdt = "fdt";
signature {
algo = "sha256,rsa2048";
padding = "pss";
key-name-hint = "dev";
sign-images = "fdt", "firmware", "loadables";
};
};
};
这里只包含了1种配置,默认配置项由default
指定,当然也可以在运行时指定。
config
节点指定该配置具体使用那些kernel Image
,ramdisk Image
等。
config
节点中支持的属性:
description
:该配置的名称;kernel
:镜像类型为kernel
的单元的名称;ramdisk
:镜像类型为ramdisk
的单元的名称;fdt
:镜像类型为fdt
的单元的名称;loadables
:额外的可加载的二进制文件的列表,u-boot
将在给定的起始地址加载每个二进制文件。
3.1.3 编译
FIT image
文件的编译过程很简单,根据实际情况,编写image source file
之后(假设名称为u-boot.its
),在命令行使用mkimage
工具编译即可:
./tools/mkimage [-D dtc_options] [-f fit-image.its|-f auto|-f auto-conf|-F] [-b <dtb> [-b <dtb>]] [-E] [-B size] [-i <ramdisk.cpio.gz>] fit-image
<dtb> file is used with -f auto, it may occur multiple times.
-D => set all options for device tree compiler
-f => input filename for FIT source
-i => input filename for ramdisk file
-E => place data outside of the FIT structure
-B => align size in hex for FIT structure and header
-b => append the device tree binary to the FIT
-t => update the timestamp in the FIT
其中:
-D
:指定DTC(Device Tree Compiler)
编译器的选项;-f
:指定需要编译的image source file
,并在后面指定需要生成的image
文件(一般以.itb
为后缀,例如u-boot.itb
);-i
:指定用于创建FIT
镜像的RAM disk
文件名;-E
:将image data file
放置在FIT结构外的选项;-B
:指定FIT
结构和头的对齐大小;-b
:支持将一个或多个设备树二进制文件附加到FIT
文件中(可使用-b <dtb>
多次指定);-t
:更新 FIT 文件中的时间戳;
其中-E
这个字段比较重要,它会影响生成的itb
的文件布局;
- 如果没有指定该选项,其生成的
itb
文件格式和dts
文件编译生成的dtb
文件布局一样,包括data
属性指定的/incbin/("bl31_0x00040000.bin")
文件也会以二进制数据格式的形式放到FIT
结构内; - 如果指定了该选项,会为
data
属性指向的文件扩充data-offset
(指定文件的偏移,这个偏移是以FIT
结构结束地址下一扇区起始地址开始计算的,即相对于fdt_blob
末尾的位置偏移量)、以及data-size
(指定文件的大小)属性,而在data
属性指向的二进制数据文件将会被追加到FIT
结构的尾部(也是扇区对齐);下文我们分析的itb
文件布局格式就是这种;
有关这两种布局效果具体如下图所示:

我们可以采用如下方式生成u-boot.itb
文件:
./tools/mkimage -f u-boot.its -E u-boot.itb
u-boot.itb
生成后,也可以使用mkimage
命令查看它的信息:
tools/mkimage -l u-boot.itb
3.1.4 itb
文件布局
编译生成u-boot.itb
文件的u-boot.its
源文件如下:
View Code
root@ubuntu:/work/sambashare/rk3399/u-boot# cat u-boot.its
/*
* Copyright (C) 2017 Fuzhou Rockchip Electronics Co., Ltd
*
* Minimal dts for a SPL FIT image payload.
*
* SPDX-License-Identifier: GPL-2.0+ X11
*/
/dts-v1/;
/ {
description = "Configuration to load ATF before U-Boot";
#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>;
hash {
algo = "sha256";
};
};
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>;
hash {
algo = "sha256";
};
};
atf@2 {
description = "ARM Trusted Firmware";
data = /incbin/("bl31_0xff3b0000.bin");
type = "firmware";
arch = "arm64";
os = "arm-trusted-firmware";
compression = "none";
load = <0xff3b0000>;
hash {
algo = "sha256";
};
};
atf@3 {
description = "ARM Trusted Firmware";
data = /incbin/("bl31_0xff8c0000.bin");
type = "firmware";
arch = "arm64";
os = "arm-trusted-firmware";
compression = "none";
load = <0xff8c0000>;
hash {
algo = "sha256";
};
};
atf@4 {
description = "ARM Trusted Firmware";
data = /incbin/("bl31_0xff8c1000.bin");
type = "firmware";
arch = "arm64";
os = "arm-trusted-firmware";
compression = "none";
load = <0xff8c1000>;
hash {
algo = "sha256";
};
};
atf@5 {
description = "ARM Trusted Firmware";
data = /incbin/("bl31_0xff8c2000.bin");
type = "firmware";
arch = "arm64";
os = "arm-trusted-firmware";
compression = "none";
load = <0xff8c2000>;
hash {
algo = "sha256";
};
};
fdt {
description = "U-Boot device tree blob";
data = /incbin/("u-boot.dtb");
type = "flat_dt";
arch = "arm64";
compression = "none";
hash {
algo = "sha256";
};
};
};
configurations {
default = "config";
config {
description = "Rockchip armv8 with ATF";
rollback-index = <0x0>;
firmware = "atf@1";
loadables = "uboot", "atf@2" , "atf@3" , "atf@4" , "atf@5" , "atf@6" ;
fdt = "fdt";
signature {
algo = "sha256,rsa2048";
padding = "pss";
key-name-hint = "dev";
sign-images = "fdt", "firmware", "loadables";
};
};
};
};
注意:这里我们补充一下u-boot.its
文件的来源,在Makefile
中有如下配置:
# Boards with more complex image requirments can provide an .its source file
# or a generator script
ifneq ($(CONFIG_SPL_FIT_SOURCE),"") # 指定了u-boot.its文件
U_BOOT_ITS = $(subst ",,$(CONFIG_SPL_FIT_SOURCE))
else
ifneq ($(CONFIG_SPL_FIT_GENERATOR),"") # 走这里
U_BOOT_ITS := u-boot.its
$(U_BOOT_ITS): FORCE
$(srctree)/$(CONFIG_SPL_FIT_GENERATOR) \
$(patsubst %,arch/$(ARCH)/dts/%.dtb,$(subst ",,$(CONFIG_OF_LIST))) > $@ # 利用python脚本生成u-boot.its
endif
endif
由于configs/evb-rk3399_defconfig
中配置了CONFIG_SPL_FIT_GENERATOR
,因此这里会通过python
脚本生成u-boot.its
;
CONFIG_SPL_FIT_GENERATOR="arch/arm/mach-rockchip/make_fit_atf.py"
编译生成的u-boot.itb
文件,以16
进制查看:

由于itb
文件布局和dtb
文件布局一样,所以我们按照dtb
文件布局格式来解读。
其中地址范围0x00000000~0x00000027
表示的是fdt_header
结构体的成员信息:
- 地址
0x00000000
:对应magic
,表示设备树魔数,固定为0xd00dfeed
; - 地址
0x00000004
:对应totalsize
,表示源u-boot.its
文件打包后在u-boot.itb
中所占的大小,由于我们编译指定了-E
属性,因此这里计算的是不包含image data file
文件的大小,更准确的说应该是FIT
结构的大小。从上图可知这个值为0x00000a39
; - 地址
0x00000008
:对应off_dt_struct
,表示structure block
的偏移地址,为0x00000038
; - 地址
0x0000000c
:对应off_dt_strings
,表示strings block
的偏移地址,为0x00000968
; - 地址
0x00000010
:对应off_mem_rsvmap
:表示memory reservation block
的偏移地址,为0x00000028
; - 地址
0x00000014
:对应version
,设备树版本的版本号为0x11
; - 地址
0x00000018
:对应last_comp_version
:向下兼容版本号0x10
; - 地址
0x0000001C
:对应boot_cpuid_phys
:在多核处理器中用于启动的主cpu
的物理id
,为0x0
; - 地址
0x00000020
:对应size_dt_strings
,strings block
的大小为0xd1
; - 地址
0x00000024
: 对应size_dt_struct
,structure block
的大小为0x00000930
;
其中地址范围0x00000028~0x00000037
表示的是fdt_reserve_entry
结构体的成员信息:
- 对应结构体
fdt_reserve_entry
:它所在的地址为0x28
,u-boot.its
设备树文件没有设置/memreserve/
,所以address = 0x0
,size = 0x0
;
其中地址范围0x00000038~0x00000967
表示的是structure block
信息:
- 地址
0x00000038
:值0x00000001
表示的是设备节点的开始; - 地址
0x0000003c
:表示设备节点的名字,这里是根节点,所以为0x00000000
; - 地址
0x00000040
:值0x00000003
表示的是开始描述设备节点的一个属性; - 地址
0x00000044
:表示这个属性值的长度为0x04
; - 地址
0x00000048
:表示这个属性的名字在strings block
的偏移量是0xad
,找到strings block
的地址0x00000968+0xad=0xA15
的地方,可知这个属性的名字是version
; - 地址
0x0000004c
:这个version
属性的值是0;
我们来看一下uboot
节点,由于uboot
属性比较多,这里我并没有截全:

其属性data-size
地址范围在0x000000c0~0x000000db
:
- 地址
0x000000c0
:值0x00000001
表示的是设备节点的开始; - 地址
0x000000c4
:表示设备节点的名字,这里是uboot
节点,所以为uboot
,占用8个字节; - 地址
0x000000cc
:值0x00000003
表示的是开始描述设备节点的一个属性; - 地址
0x000000d0
:表示这个属性值的长度为0x04
; - 地址
0x000000d4
:表示这个属性的名字在strings block
的偏移量是0xc7
,找到strings block
的地址0x00000968+0xc7=0xa2f
的地方,可知这个属性的名字是data-size
; - 地址
0x000000d8
:这个data-size
属性的值是0x0d72e0
;
属性data-offset
地址范围在0x000000dc~0x000000eb
:
- 地址
0x000000dc
:值0x00000003
表示的是开始描述设备节点的一个属性; - 地址
0x000000e0
:表示这个属性值的长度为0x04
; - 地址
0x000000e4
:表示这个属性的名字在strings block
的偏移量是0xbb
,找到strings block
的地址0x00000968+0xbb=0xa23
的地方,可知这个属性的名字是data-offset
; - 地址
0x000000e8
:这个data-offset
属性的值是0x00
;需要注意的这个描述的就是uboot
节点data
属性指定的u-boot-nodtb.bin
文件的偏移,偏移0x00
是从FIT
文件结束下一扇区地址开始计算;也就是0xa3f
取下一扇区起始地址,即0x0c00
;
需要注意的是:data-offset
、data-size
这两个属性在u-boot.its
文件uboot
节点中是没有的,这俩应该是自动扩充的属性的属性,用来描述data
属性指向的u-boot-nodtb.bin
文件的信息。
我们验证一下u-boot-nodtb.bin
文件大小0xd72e0 =881376
,和命令看到的完全匹配:
root@ubuntu:/work/sambashare/rk3399/u-boot# ll u-boot-nodtb.bin
-rwxr-xr-x 1 root root 881376 5月 14 18:27 u-boot-nodtb.bin*
我们验证一下文件内容,u-boot-nodtb.bin
源文件内容:
root@ubuntu:/work/sambashare/rk3399/u-boot# hexdump u-boot-nodtb.bin -n 16
0000000 000a 1400 201f d503 0000 0020 0000 0000
定位到u-boot.itb
文件地址0x0c00
:

这样我们可以得到一个文件内容布局表:
地址范围 | 偏移 | 大小 | 内容 | 加载到内存地址 |
---|---|---|---|---|
0x0000 0000 ~ 0x0000 0a39 | 0x0a39 | FIT:存放u-boot.its文件信息 | ||
0x0000 0c00~ 0x000d 7ee0 | 0x00(按扇区大小对齐) | 0xd72e0 | uboot:u-boot-nodtb.bin | 0x00200000 |
0x000d 8000~ 0x000f b04e | 0xd7400(按扇区大小对齐) | 0x2304e | atf@1:bl31_0x00040000.bin | 0x00040000 |
自己计算 | 0xfa600(按扇区大小对齐) | 0x1f58 | atf@2:bl31_0xff3b0000.bin | 0xff3b0000 |
自己计算 | 0xfc600(按扇区大小对齐) | 0x1000 | at3@3:bl31_0xff8c0000.bin | 0xff8c0000 |
自己计算 | 0xfd600(按扇区大小对齐) | 0x1000 | atf@4:bl31_0xff8c1000.bin | 0xff8c1000 |
自己计算 | 0xfe600(按扇区大小对齐) | 0x00 | atf@5:bl31_0xff8c2000.bin | 0xff8c2000 |
自己计算 | 0xfe600(按扇区大小对齐) | 0xc9e5 | fdt:u-boot.dtb |
这里我只列出了一部分地址范围,有兴趣自己去搞吧,太费劲了。
3.1.5 fdtdump
fdtdump
命令可以查看itb
文件内容:
fdtdump uboot.itb
3.2 编译ATF
因为rk399
是arm64
,所以我们还需要编译ATF (Arm trust firmware)
, ATF
主要负责在启动uboot
之前把CPU
从安全的EL3
切换到 EL2
,然后跳转到uboot
,并且在内核启动后负责启动其他的CPU
。
3.2.1 下载源码
下载《arm-trusted-fireware
》到uboot
同级目录:
root@ubuntu:/work/sambashare/rk3399# git clone https://github.com/ARM-software/arm-trusted-firmware.git --depth 1
3.2.2 编译ATF
运行如下命令:
root@ubuntu:/work/sambashare/rk3399/arm-trusted-firmware# make CROSS_COMPILE=arm-linux- PLAT=rk3399
编译后报出一个缺少arm-none-eabi-gcc
工具链的错误:
安装该工具链并重新编译:
root@ubuntu:/work/sambashare/rk3399/arm-trusted-firmware# sudo apt-get install gcc-arm-none-eabi
root@ubuntu:/work/sambashare/rk3399/arm-trusted-firmware# make CROSS_COMPILE=arm-linux- PLAT=rk3399
最终编译出来的目标文件为:build/rk3399/release/bl31/bl31.elf
, 这个文件需要和编译出来的uboot
一起打包成fit
格式的镜像才能被SPL
加载。
3.3 生成u-boot.itb
3.3.1 拷贝bl31.elf
将bl31.elf
拷贝到uboot
根目录下:
root@ubuntu:/work/sambashare/rk3399/u-boot# cp /work/sambashare/rk3399/arm-trusted-firmware/build/rk3399/release/bl31/bl31.elf ./
3.3.2 编译
然后执行编译命令:
root@ubuntu:/work/sambashare/rk3399/u-boot# make u-boot.itb ARCH=arm CROSS_COMPILE=arm-linux-
这里提示我们缺少python
依赖elffile
,如下图所示:
我们直接使用如下命令安装python
依赖包(需要注意自己的python
版,必须是2.7
版本,使用pip2
安装依赖):
root@ubuntu:/work/sambashare/rk3399/u-boot# pip2 install pyelftools
重新编译生成u-boot.itb
文件:
root@ubuntu:/work/sambashare/rk3399/u-boot# ls -l u-boot.itb
-rw-r--r-- 1 root root 1095168 5月 14 10:40 u-boot.itb
如果出现1:dtc: not found make
错误,如下:
tc
是device-tree-compiler
的缩写,即设备树编译器,说明系统中没有安装这个编译器,安装设备树编译器重新编译即可:
root@ubuntu:/work/sambashare/rk3399/u-boot# sudo apt-get install device-tree-compiler
四、rkdeveloptool
rkdeveloptool
是Rockchip
提供的一个与Rockusb
设备进行通信的工具,通过该工具我们可以将镜像文件下载到开发板的eMMC
。它被认为是upgrade_tool
的一个开源版本,只有很少区别。
要使用rkdeveloptool
进行升级,首先要知道rkdeveloptool
是基于什么情况下才会起作用的,是在SoC
进入MASKROM
模式后而且跟主机通过USB
连接,因为这个时候主板的DDR
并没有初始化,而升级过程是需要很大的内存空间的,所以升级之前第一步要做的就是执行rkdeveloptool db rkxx_loader_vx.xx.bin
(这个固件本质上也是idbloader.img
),只不过这时候只是在内存中执行,如果不执行db命令的话其他的命令则无法执行因为没有做内存初始化工作。
4.1 下载源码
在/work/sambashare/rk3399
目录下执行如下命令:
root@ubuntu:/work/sambashare/rk3399# git clone https://github.com/rockchip-linux/rkdeveloptool.git --depth 1
4.2 配置
首先安装libusb
与udev
,例如对于ubuntu
:
root@ubuntu:/work/sambashare/rk3399# sudo apt-get install libudev-dev libusb-1.0-0-dev dh-autoreconf
切换到rkdeveloptool/
目录进行配置:
root@ubuntu:/work/sambashare/rk3399# cd rkdeveloptool/
root@ubuntu:/work/sambashare/rk3399/rkdeveloptool# autoreconf -i
configure.ac:12: installing 'cfg/compile'
configure.ac:19: installing 'cfg/config.guess'
configure.ac:19: installing 'cfg/config.sub'
configure.ac:7: installing 'cfg/install-sh'
configure.ac:7: installing 'cfg/missing'
Makefile.am: installing 'cfg/depcomp'
root@ubuntu:/work/sambashare/rk3399/rkdeveloptool# ./configure
4.3 编译安装
运行如下命令:
root@ubuntu:/work/sambashare/rk3399/rkdeveloptool# make
root@ubuntu:/work/sambashare/rk3399/rkdeveloptool# make install
如果遇到如下编译错误:
./configure: line 4269: syntax error near unexpected token `LIBUSB1,libusb-1.0'
./configure: line 4269: `PKG_CHECK_MODULES(LIBUSB1,libusb-1.0)'
还需要安装pkg-config
与libusb-1.0
:
root@ubuntu:/work/sambashare/rk3399/rkdeveloptool# sudo apt-get install pkg-config libusb-1.0
编译完成后,在当前路径下生成rkdeveloptool
可执行文件:
root@ubuntu:/work/sambashare/rk3399/rkdeveloptool# ll rkdeveloptool
-rwxr-xr-x 1 root root 1059720 5月 11 19:56 rkdeveloptool*
4.4 rk3399_loader_v1.27.126.bin
由于SoC
进入到MASKROM
模式后,目标板子会运行Rockusb
驱动程序。在MASKROM
模式下,需要使用到DDR
,因此需要下载固件进行DDR
的初始化。
《Rockchip rkbin
项目》提供了ddr.bin
、usbplug.bin
、miniloader.bin
这三个包:
ddr.bin
:等价于我们之前说的TPL
,用于初始化DDR
;usbplug.bin
:Rockusb
驱动程序,用于将程序通过usb
下载到eMMC
;miniloader.bin
:Rockchip
修改的一个bootloader
,等价于我们之前说的SPL
,用于加载uboot
;
4.4.1 下载rkbin
我们可以在Rockchip
的github
上下载到Rockchip rkbin
项目,如下所示:
root@ubuntu:/work/sambashare/rk3399# git clone https://github.com/rockchip-linux/rkbin.git --depth 1
4.4.2 合并
在rkbin
目录下执行如下命令,可以将ddr.bin
、usbplug.bin
、miniloader.bin
这三个包合并:
root@ubuntu:/work/sambashare/rk3399# cd rkbin/
root@ubuntu:/work/sambashare/rk3399/rkbin# tools/boot_merger /work/sambashare/rk3399/rkbin/RKBOOT/RK3399MINIALL.ini
********boot_merger ver 1.2********
Info:Pack loader ok.
root@ubuntu:/work/sambashare/rk3399/rkbin# ll rk3399_loader_v1.27.126.bin
-rw-r--r-- 1 root root 465230 5月 11 20:06 rk3399_loader_v1.27.126.bin
可以根据自己的需求可以在./RKBOOT/RK3399MINIALL.ini
修改ddr
、usbplug
、miniloader
:
[CHIP_NAME]
NAME=RK330C
[VERSION]
MAJOR=1
MINOR=26
[CODE471_OPTION]
NUM=1
Path1=bin/rk33/rk3399_ddr_800MHz_v1.27.bin
Sleep=1
[CODE472_OPTION]
NUM=1
Path1=bin/rk33/rk3399_usbplug_v1.26.bin
[LOADER_OPTION]
NUM=2
LOADER1=FlashData
LOADER2=FlashBoot
FlashData=bin/rk33/rk3399_ddr_800MHz_v1.27.bin
FlashBoot=bin/rk33/rk3399_miniloader_v1.26.bin
[OUTPUT]
PATH=rk3399_loader_v1.27.126.bin
将rk3399_loader_v1.27.126.bin
拷贝到rkdeveloptool
路径下:
root@ubuntu:/work/sambashare/rk3399/rkbin# cp rk3399_loader_v1.27.126.bin ../rkdeveloptool/
五、烧录程序
烧录方法有两种:
- 一种是基于
Rockchip
的官方烧录工具RKDevTool
;官方RKDevTool
是基于recovery
模式实现的,如果板子带有recovery
按键,可以使用这种方式; - 另外一种是在开发板上使用
rkdeveloptool
工具直接烧写eMMC
;这里我们采用rkdeveloptool
烧录的方式;
5.1 准备镜像
我们需按照之前的流程得到了idbloader.img
、u-boot.itb
文件,由于我们这里不进行内核和根文件系统的烧录,所以暂时不需要准备这俩。
按照Rockchip
官方要求将idbloader.img
烧录到eMMC
的0x40
扇区,u-boot.itb
烧录到0x4000
扇区。
我们需要将idbloader.img
、u-boot.itb
拷贝到rkdeveloptool
路径下:
root@ubuntu:/work/sambashare/rk3399/rkdeveloptool# cp ../u-boot/u-boot.itb ./
root@ubuntu:/work/sambashare/rk3399/rkdeveloptool# cp ../u-boot/idbloader.img ./
5.2 进入MASKROM
升级模式
NanoPC-T4
开发板如需进入MASKROM
升级模式,需要进入如下操作:
- 将开发板连接上电源,并且连接
Type-C
数据线到PC
; - 按住
BOOT
键再长按Power
键开机(保持按下BOOT
键5秒以上),将强制进入MASKROM
模式。
一般电脑识别到USB
连接,都会发出声音。或者观察虚拟机右下角是否突然多个USB
设备:右键点击链接;
5.3 烧录
使用下载引导命令去使目标机器初始化DD
R与运行usbplug
(初始化DDR
的原因是由于升级需要很大的内存,所以需要使用到DDR
);
root@ubuntu:/work/sambashare/rk3399/rkdeveloptool# rkdeveloptool db rk3399_loader_v1.27.126.bin
Downloading bootloader succeeded.
由于BootROM
启动会将rk3399_loader_v1.27.126.bin
将在到内部SRAM
中,然后跳转到ddr.bin
代码进行DDR
的初始化,ddr.bin
执行之后会回跳到BootROM
程序,BootROM
程序继续加载usbplug.bin
,由usbplug.bin
完成程序的下载以及烧录到eMMC
。
如果接上串口的话,执行完这一步可以看到如下输出信息:
View Code
DDR Version 1.27 20211018
In
Channel 0: LPDDR3, 800MHz
CS = 0
MR0=0x58
MR1=0x58
MR2=0x58
MR3=0x58
MR4=0x2
MR5=0x1
MR6=0x5
MR7=0x0
MR8=0x1F
MR9=0x1F
MR10=0x1F
MR11=0x1F
MR12=0x1F
MR13=0x1F
MR14=0x1F
MR15=0x1F
MR16=0x1F
CS = 1
MR0=0x58
MR1=0x58
MR2=0x58
MR3=0x58
MR4=0x2
MR5=0x1
MR6=0x5
MR7=0x0
MR8=0x1F
MR9=0x1F
MR10=0x1F
MR11=0x1F
MR12=0x1F
MR13=0x1F
MR14=0x1F
MR15=0x1F
MR16=0x1F
Bus Width=32 Col=10 Bank=8 Row=15/15 CS=2 Die Bus-Width=32 Size=2048MB
Channel 1: LPDDR3, 800MHz
CS = 0
MR0=0x58
MR1=0x58
MR2=0x58
MR3=0x58
MR4=0x2
MR5=0x1
MR6=0x5
MR7=0x0
MR8=0x1F
MR9=0x1F
MR10=0x1F
MR11=0x1F
MR12=0x1F
MR13=0x1F
MR14=0x1F
MR15=0x1F
MR16=0x1F
CS = 1
MR0=0x58
MR1=0x58
MR2=0x58
MR3=0x58
MR4=0x2
MR5=0x1
MR6=0x5
MR7=0x0
MR8=0x1F
MR9=0x1F
MR10=0x1F
MR11=0x1F
MR12=0x1F
MR13=0x1F
MR14=0x1F
MR15=0x1F
MR16=0x1F
Bus Width=32 Col=10 Bank=8 Row=15/15 CS=2 Die Bus-Width=32 Size=2048MB
256B stride
ch 0 ddrconfig = 0x101, ddrsize = 0x2020
ch 1 ddrconfig = 0x101, ddrsize = 0x2020
pmugrf_os_reg[2] = 0x3AA0DAA0, stride = 0xD
OUT
Boot1 Release Time: Jun 2 2020 15:02:17, version: 1.26
CPUId = 0x0
SdmmcInit=2 0
BootCapSize=100000
UserCapSize=14910MB
FwPartOffset=2000 , 100000
UsbBoot ...73858
powerOn 86071
使用wl
命令烧写镜像到目标机器的eMMC
,需要注意的是访问DDR
所需的所有其他命令都应在使用db
命令之后才能使用;
root@ubuntu:/work/sambashare/rk3399/rkdeveloptool# rkdeveloptool wl 0x40 idbloader.img
Write LBA from file (100%)
root@ubuntu:/work/sambashare/rk3399/rkdeveloptool# rkdeveloptool wl 0x4000 u-boot.itb
Write LBA from file (100%)
在烧写镜像完成后使用rd
命令重启目标机器:
root@ubuntu:/work/sambashare/rk3399/rkdeveloptool# rkdeveloptool rd
Reset Device OK.
需要注意的是:如果这个时候你也烧录了内核程序,执行完rd
命令后是无法进入uboot
命令的的,这里会直接启动内核。
六、测试
6.1 串口连接
使用准备好的USB
转串口适配器和连接线(需另购),连接开发板,我使用的开发板提供了DEG_UART
的4个引脚,其使用的是RK3399
的UART2
;
引脚 | 开发板接口 | USB转串口 |
---|---|---|
1 | GND | GND |
2 | VCC 5V | VCC |
3 | UART2DBG_TX | RX |
4 | UART2DBG_RX | TX |
其电路原理图如下:

需要注意的是:这四根线都要接上,我只接TX
、RX
串口输出数据都是乱码。
6.2 MobaXterm
这里我使用的串口调试工具是MobaXterm
,选择串口端口,设置波特率为1500000
,8位数据位,1位停止位。

6.3 上电
重启开发板,通过串口打印输出:
View Code
U-Boot TPL 2017.09-gef1dd65-dirty #root (May 14 2023 - 12:08:25)
Channel 0: LPDDR3, 800MHz
BW=32 Col=10 Bk=8 CS0 Row=15 CS1 Row=15 CS=2 Die BW=32 Size=2048MB
Channel 1: LPDDR3, 800MHz
BW=32 Col=10 Bk=8 CS0 Row=15 CS1 Row=15 CS=2 Die BW=32 Size=2048MB
256B stride
Returning to boot ROM...
U-Boot SPL board init
U-Boot SPL 2017.09-gef1dd65-dirty #root (May 14 2023 - 12:08:25)
Trying to boot from MMC1
Trying fit image at 0x4000 sector
## Verified-boot: 0
## Checking atf@1 0x00040000 ... sha256(7de676d49d...) + OK
## Checking uboot 0x00200000 ... sha256(d1dc81ced6...) + OK
## Checking fdt 0x002d6d58 ... sha256(5416082316...) + OK
## Checking atf@2 0xff3b0000 ... sha256(5e781f41af...) + OK
## Checking atf@3 0xff8c0000 ... sha256(97b03d23aa...) + OK
## Checking atf@4 0xff8c1000 ... sha256(50a7da66c2...) + OK
## Checking atf@5 0xff8c2000 ... sha256(e3b0c44298...) + OK
Jumping to U-Boot(0x00200000) via ARM Trusted Firmware(0x00040000)
Total: 905.962 ms
NOTICE: BL31: v2.8(release):c194aa0
NOTICE: BL31: Built : 19:26:54, May 11 2023
U-Boot 2017.09-gef1dd65-dirty #root (May 14 2023 - 12:08:32 +0800)
Model: Rockchip RK3399 Evaluation Board
Serial: raw, 0xff1a0000
DRAM: 3.9 GiB
Sysmem: init
Relocation Offset: f5ba0000
Relocation fdt: f3d87a20 - f3d96cc8
CR: M/C/I
I2c0 speed: 400000Hz
PMIC: RK808
vdd_center init 950000 uV
Using default environment
MMC: dwmmc@fe320000: 1, sdhci@fe330000: 0
Card did not respond to voltage select!
mmc_init: -95, time 12
switch to partitions #0, OK
mmc0(part 0) is current device
Bootdev(scan): mmc 0
MMC0: HS400, 150Mhz
PartType: RKPARM
Could not find baseparameter partition
In: serial
Out: serial
Err: serial
Model: Rockchip RK3399 Evaluation Board
boot mode: None
Found DTB in resource part
Rockchip UBOOT DRM driver version: v1.0.1
AUX CH command reply failed!
AUX CH error happens: 2
AUX CH error happens: 2
failed to read link rate: -121
edp@ff970000 disconnected
CLK: (uboot. arml: enter 816000 KHz, init 816000 KHz, kernel 0N/A)
CLK: (uboot. armb: enter 816000 KHz, init 816000 KHz, kernel 0N/A)
aplll 816000 KHz
apllb 816000 KHz
dpll 800000 KHz
cpll 24000 KHz
gpll 800000 KHz
npll 600000 KHz
vpll 24000 KHz
aclk_perihp 133333 KHz
hclk_perihp 66666 KHz
pclk_perihp 33333 KHz
aclk_perilp0 266666 KHz
hclk_perilp0 88888 KHz
pclk_perilp0 44444 KHz
hclk_perilp1 100000 KHz
pclk_perilp1 50000 KHz
Net: eth0: ethernet@fe300000
### main_loop entered: bootdelay=5
### main_loop: bootcmd="boot_android ${devtype} ${devnum};boot_fit;bootrkp;run distro_bootcmd;"
Hit key to stop autoboot('CTRL+C'): 0
=>
在倒计时执行完之前,按CTRL+C
即可进入uboot
命令行。
需要注意的是:有时候按CTRL+C
并没有进入uboot
命令行,可能串口有问题,没有接收到输入内容么?试着将串口重新连接,切换端口号试试。如果实在不行,可以尝试修改common/autoboot.c
函数__abortboot
,将if (ctrlc())
修改为if (tstc())
,这样按下任何键都可以进入uboot
命令行。或者干脆函数返回1。
6.3.1 查看环境变量
在命令行输入print
,查看所有环境变量:
View Code
=> print
arch=arm
autoload=no
baudrate=150000
board=evb_rk3399
board_name=evb_rk3399
boot_a_script=load ${devtype} ${devnum}:${distro_bootpart} ${scriptaddr} ${prefix}${script}; source ${scriptaddr}
boot_efi_binary=load ${devtype} ${devnum}:${distro_bootpart} ${kernel_addr_r} efi/boot/bootaa64.efi; if fdt addr ${fdt_addr_r}; then bootefi ${kernel_addr_r} ${fdt_addr_r};else bootefi ${kernel_addr_r} ${fdtcontroladdr};fi
boot_extlinux=sysboot ${devtype} ${devnum}:${distro_bootpart} any ${scriptaddr} ${prefix}extlinux/extlinux.conf
boot_net_usb_start=usb start
boot_prefixes=/ /boot/
boot_script_dhcp=boot.scr.uimg
boot_scripts=boot.scr.uimg boot.scr
boot_targets=mmc1 mmc0 usb0 pxe dhcp
bootargs= root=/dev/mmcblk1p7 rw rootfstype=ext4 data=/dev/mmcblk1p8 consoleblank=0 cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory swapaccount=1 mtdparts=rk29xxnand:0x00002000@0x00002000(uboot),0x00002000@0x00004000(trust),0x00002000@0x00006000(misc),0x00006000@0x00008000(resource),0x00010000@0x0000e000(kernel),0x00010000@0x0001e000(boot),0x00c80000@0x00030000(rootfs),-@0x00cb0000(userdata) storagemedia=emmc androidboot.storagemedia=emmc androidboot.mode=normal
bootcmd=boot_android ${devtype} ${devnum};boot_fit;bootrkp;run distro_bootcmd;
bootcmd_dhcp=run boot_net_usb_start; if dhcp ${scriptaddr} ${boot_script_dhcp}; then source ${scriptaddr}; fi;setenv efi_fdtfile ${fdtfile}; setenv efi_old_vci ${bootp_vci};setenv efi_old_arch ${bootp_arch};setenv bootp_vci PXEClient:Arch:00011:UNDI:003000;setenv bootp_arch 0xb;if dhcp ${kernel_addr_r}; then tftpboot ${fdt_addr_r} dtb/${efi_fdtfile};if fdt addr ${fdt_addr_r}; then bootefi ${kernel_addr_r} ${fdt_addr_r}; else bootefi ${kernel_addr_r} ${fdtcontroladdr};fi;fi;setenv bootp_vci ${efi_old_vci};setenv bootp_arch ${efi_old_arch};setenv efi_fdtfile;setenv efi_old_arch;setenv efi_old_vci;
bootcmd_mmc0=setenv devnum 0; run mmc_boot
bootcmd_mmc1=setenv devnum 1; run mmc_boot
bootcmd_pxe=run boot_net_usb_start; dhcp; if pxe get; then pxe boot; fi
bootcmd_usb0=setenv devnum 0; run usb_boot
bootdelay=5
cpu=armv8
cpuid#=544d533632342e303000000000050607
devnum=0
devtype=mmc
distro_bootcmd=for target in ${boot_targets}; do run bootcmd_${target}; done
efi_dtb_prefixes=/ /dtb/ /dtb/current/
eth1addr=ce:ff:bf:e6:3b:7d
ethaddr=ca:ff:bf:e6:3b:7d
fdt_addr_r=0x08300000
fdtcontroladdr=0xf3d87a20
kernel_addr_r=0x00280000
load_efi_dtb=load ${devtype} ${devnum}:${distro_bootpart} ${fdt_addr_r} ${prefix}${efi_fdtfile}
mmc_boot=if mmc dev ${devnum}; then setenv devtype mmc; run scan_dev_for_boot_part; fi
partitions=uuid_disk=${uuid_gpt_disk};name=loader1,start=32K,size=4000K,uuid=${uuid_gpt_loader1};name=loader2,start=8MB,size=4MB,uuid=${uuid_gpt_loader2};name=trust,size=4M,uuid=${uuid_gpt_atf};name=boot,size=112M,bootable,uuid=${uuid_gpt_boot};name=rootfs,size=-,uuid=B921B045-1DF0-41C3-AF44-4C6F280D3FAE;
pxefile_addr_r=0x00600000
ramdisk_addr_r=0x0a200000
rkimg_bootdev=if mmc dev 1 && rkimgtest mmc 1; then setenv devtype mmc; setenv devnum 1; echo Boot from SDcard;elif mmc dev 0; then setenv devtype mmc; setenv devnum 0;elif mtd_blk dev 0; then setenv devtype mtd; setenv devnum 0;elif mtd_blk dev 1; then setenv devtype mtd; setenv devnum 1;elif mtd_blk dev 2; then setenv devtype mtd; setenv devnum 2;elif rknand dev 0; then setenv devtype rknand; setenv devnum 0;elif rksfc dev 0; then setenv devtype spinand; setenv devnum 0;elif rksfc dev 1; then setenv devtype spinor; setenv devnum 1;elsesetenv devtype ramdisk; setenv devnum 0;fi;
scan_dev_for_boot=echo Scanning ${devtype} ${devnum}:${distro_bootpart}...; for prefix in ${boot_prefixes}; do run scan_dev_for_extlinux; run scan_dev_for_scripts; done;run scan_dev_for_efi;
scan_dev_for_boot_part=part list ${devtype} ${devnum} -bootable devplist; env exists devplist || setenv devplist 1; for distro_bootpart in ${devplist}; do if fstype ${devtype} ${devnum}:${distro_bootpart} bootfstype; then run scan_dev_for_boot; fi; done
scan_dev_for_efi=setenv efi_fdtfile ${fdtfile}; for prefix in ${efi_dtb_prefixes}; do if test -e ${devtype} ${devnum}:${distro_bootpart} ${prefix}${efi_fdtfile}; then run load_efi_dtb; fi;done;if test -e ${devtype} ${devnum}:${distro_bootpart} efi/boot/bootaa64.efi; then echo Found EFI removable media binary efi/boot/bootaa64.efi; run boot_efi_binary; echo EFI LOAD FAILED: continuing...; fi; setenv efi_fdtfile
scan_dev_for_extlinux=if test -e ${devtype} ${devnum}:${distro_bootpart} ${prefix}extlinux/extlinux.conf; then echo Found ${prefix}extlinux/extlinux.conf; run boot_extlinux; echo SCRIPT FAILED: continuing...; fi
scan_dev_for_scripts=for script in ${boot_scripts}; do if test -e ${devtype} ${devnum}:${distro_bootpart} ${prefix}${script}; then echo Found U-Boot script ${prefix}${script}; run boot_a_script; echo SCRIPT FAILED: continuing...; fi; done
scriptaddr=0x00500000
serial#=137825cca4f2db8f
soc=rockchip
stderr=serial,vidconsole
stdout=serial,vidconsole
usb_boot=usb start; if usb dev ${devnum}; then setenv devtype usb; run scan_dev_for_boot_part; fi
vendor=rockchip
Environment size: 5132/32764 bytes
其中比较重要的环境变量有:
bootcmd
:通过CONFIG_BOOTCOMMAND
设置,用来启动内核的命令;bootdelay
:通过CONFIG_BOOTDELAY
设置,uboot
启动倒计时,默认值为5s
,只有设置了bootcmd
,它才有用;baudrate
:通过CONFIG_BAUDRATE
设置,波特率默认为1500000
;ipaddr
:通过CONFIG_IPADDR
设置,IP
地址;可以不设置,使用dhcp
命令来从路由器获取IP
地址;serverip
:通过CONFIG_SERVERIP
设置,服务器IP
地址;也就是ubuntu
主机IP
地址,用于调试代码;netmask
:通过CONFIG_NETMASK
设置,子网掩码;gatewayip
:通过CONFIG_GATEWAYIP
设置,网关地址;ethaddr
:通过CONFIG_ETHADDR
设置,网卡地址; 如果设置了CONFIG_NET_RANDOM_ETHADDR
此宏的话就会随机分配网卡物理地址;bootargs
:通过CONFIG_BOOTARGS
设置,启动参数;
这些配置信息大部分配置在include/configs/evb_rk3399.h
、include/configs/rk3399_common.h
、include/configs/rockchip-common.h
、.config
文件中。
6.3.2 内核启动命令
这里我们重点看一下启动内核的命令:
bootcmd=boot_android ${devtype} ${devnum};boot_fit;bootrkp;run distro_bootcmd;
bootcmd_dhcp=run boot_net_usb_start; if dhcp ${scriptaddr} ${boot_script_dhcp}; then source ${scriptaddr}; fi;setenv efi_fdtfile ${fdtfile}; setenv efi_old_vci ${bootp_vci};setenv efi_old_arch ${bootp_arch};setenv bootp_vci PXEClient:Arch:00011:UNDI:003000;setenv bootp_arch 0xb;if dhcp ${kernel_addr_r}; then tftpboot ${fdt_addr_r} dtb/${efi_fdtfile};if fdt addr ${fdt_addr_r}; then bootefi ${kernel_addr_r} ${fdt_addr_r}; else bootefi ${kernel_addr_r} ${fdtcontroladdr};fi;fi;setenv bootp_vci ${efi_old_vci};setenv bootp_arch ${efi_old_arch};setenv efi_fdtfile;setenv efi_old_arch;setenv efi_old_vci;
bootcmd_mmc0=setenv devnum 0; run mmc_boot
bootcmd_mmc1=setenv devnum 1; run mmc_boot
bootcmd_pxe=run boot_net_usb_start; dhcp; if pxe get; then pxe boot; fi
bootcmd_usb0=setenv devnum 0; run usb_boot
distro_bootcmd=for target in ${boot_targets}; do run bootcmd_${target}; done
咦,看到这里是不是很奇怪,我记得我们在学习Mini2440
的时候,bootcmd
配置的很简单:
nand read 0x30000000 kernel; bootm 0x30000000
只是把uImage从Nand Flash
内核分区加载到内存,然后直接bootm <Legacy uImage addr>
,即可启动内核。这里咋搞了一大堆启动相关的命令呢?
实际上Rockchip
为了支持从各种外部设备启动,同时也为了支持各种启动镜像格式,采用了《Distro Bootcmd
启动机制》。更多细节可以参考《树莓派4B(rpi4b)
引导ubuntu
分析.md》。
6.3.3 查看板子信息
在命令行输入bdinfo
,查看板子信息;
=> bdinfo
arch_number = 0x00000000
boot_params = 0x00000000
DRAM bank = 0x00000000
-> start = 0x00200000
-> size = 0xF7E00000
baudrate = 115200 bps
TLB addr = 0xF7FF0000
relocaddr = 0xF5DA0000
reloc off = 0xF5BA0000
irq_sp = 0xF3D87A10
sp start = 0xF3D87A10
FB base = 0x00000000
Early malloc usage: 14b0 / 4000
fdt_blob = 00000000f3d87a20
6.3.4 查看版本信息
在命令行输入version
,查看板子信息;
=> version
U-Boot 2017.09-gef1dd65-dirty #root (May 14 2023 - 12:08:32 +0800)
arm-linux-gcc (Arm GNU Toolchain 12.2.Rel1 (Build arm-12.24)) 12.2.1 20221205
GNU ld (Arm GNU Toolchain 12.2.Rel1 (Build arm-12.24)) 2.39.0.20221210
6.4 设置环境变量
通常环境变量是存放在外部存储设备中,uboot
启动的时候会将环境变量读取DDR
中。所以使用命令setenv
修改的DDR
中的环境变量值,修改以后要使用save
命令将修改后的环境变量保存到eMMC
中,否则的话uboot
下一次重启会继续使用以前的环境变量值。
6.4.1 设置ip
我们设置以下环境变量:
=> set ipaddr 192.168.0.105
Unknown command 'set' - try 'help'
=> setenv ipaddr 192.168.0.105
=> setenv serverip 192.168.0.200
=> setenv netmask 255.255.255.0
=> setenv gatewayip 192.168.0.1
亲爱的读者和支持者们,自动博客加入了打赏功能,陆陆续续收到了各位老铁的打赏。在此,我想由衷地感谢每一位对我们博客的支持和打赏。你们的慷慨与支持,是我们前行的动力与源泉。
日期 | 姓名 | 金额 |
---|---|---|
2023-09-06 | *源 | 19 |
2023-09-11 | *朝科 | 88 |
2023-09-21 | *号 | 5 |
2023-09-16 | *真 | 60 |
2023-10-26 | *通 | 9.9 |
2023-11-04 | *慎 | 0.66 |
2023-11-24 | *恩 | 0.01 |
2023-12-30 | I*B | 1 |
2024-01-28 | *兴 | 20 |
2024-02-01 | QYing | 20 |
2024-02-11 | *督 | 6 |
2024-02-18 | 一*x | 1 |
2024-02-20 | c*l | 18.88 |
2024-01-01 | *I | 5 |
2024-04-08 | *程 | 150 |
2024-04-18 | *超 | 20 |
2024-04-26 | .*V | 30 |
2024-05-08 | D*W | 5 |
2024-05-29 | *辉 | 20 |
2024-05-30 | *雄 | 10 |
2024-06-08 | *: | 10 |
2024-06-23 | 小狮子 | 666 |
2024-06-28 | *s | 6.66 |
2024-06-29 | *炼 | 1 |
2024-06-30 | *! | 1 |
2024-07-08 | *方 | 20 |
2024-07-18 | A*1 | 6.66 |
2024-07-31 | *北 | 12 |
2024-08-13 | *基 | 1 |
2024-08-23 | n*s | 2 |
2024-09-02 | *源 | 50 |
2024-09-04 | *J | 2 |
2024-09-06 | *强 | 8.8 |
2024-09-09 | *波 | 1 |
2024-09-10 | *口 | 1 |
2024-09-10 | *波 | 1 |
2024-09-12 | *波 | 10 |
2024-09-18 | *明 | 1.68 |
2024-09-26 | B*h | 10 |
2024-09-30 | 岁 | 10 |
2024-10-02 | M*i | 1 |
2024-10-14 | *朋 | 10 |
2024-10-22 | *海 | 10 |
2024-10-23 | *南 | 10 |
2024-10-26 | *节 | 6.66 |
2024-10-27 | *o | 5 |
2024-10-28 | W*F | 6.66 |
2024-10-29 | R*n | 6.66 |
2024-11-02 | *球 | 6 |
2024-11-021 | *鑫 | 6.66 |
2024-11-25 | *沙 | 5 |
2024-11-29 | C*n | 2.88 |

【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步