程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)

Rockchip RK3399 - TPL/SPL方式加载uboot

----------------------------------------------------------------------------------------------------------------------------
开发板 :NanoPC-T4开发板
eMMC16GB
LPDDR34GB
显示屏 :15.6英寸HDMI接口显示屏
u-boot2017.09
----------------------------------------------------------------------------------------------------------------------------

NanoPC-T4开发板,主控芯片是Rockchip RK3399big.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初始化binminiloader bin,该方式不开源;

这一节我们将介绍采用TPL/SPL方式,如何编译源码以及烧录程序到eMMC,从而完成uboot的启动。

一、uboot

uboot通常有三种:

  • uboot官方源码:https://github.com/u-boot/u-bootuboot官方源码是由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 下载源码

我们可以在Rockchipgithub上下载到芯片厂商提供的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代码。程序运行后,SPLeMMC加载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 Kconfigscripts/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.srecS-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中各个符号在内核中位置,但是这个文件是使用了nmgrep工具来手动生成的;

由于开启了SPLTPL 的,因此,在编译uboot时会额外单独编译SPLTPL,编译产生的镜像文件就存放在 ./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.binu-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程序继续加载SPLSPL加载u-boot.itb文件,然后跳转到uboot执行。

idbloader.img是由tpl/u-boot-tpl.binspl/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介绍

FITflattened image tree的简称,它采用了device tree source filse(DTS)的语法,生成的image文件也和dtb文件类似(称做itb)。

注意:这一小节只是补充关于FIT的相关知识,并不是编译u-boot.itb的步骤。

具体可以参考:

3.1.1 生成步骤

其中image source file(.its)device tree source file(.dts)类似,负责描述要生成的image file的信息。mkimagedtc工具,可以将.its文件以及对应的image data file,打包成一个image file

3.1.2 image source file语法

image source file的语法和device tree source file完全一样,只不过自定义了一些特有的节点,包括imagesconfigurations等。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:这个节点包含一组子节点,每个子节点代表一个单独的子镜像(如kernelramdisk等),至少需要一个子镜像;
  • configurations:配置项节点,可以将不同类型的二进制文件,根据不同的场景,组合起来,形成一个个的配置项。u-bootboot的时候,以配置项为单位加载、执行,这样就可以根据不同的场景,方便的选择不同的配置;
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:子镜像的类型,standalonefirmwarekernel, ramdisk, flat_dt等;
  • arch:平台类型,arm, arm64, i386等;
  • os:操作系统类型,linuxvxworks等;
  • compression:二进制文件的压缩格式,nonebzip2gziplz4等;
  • load:二进制文件的加载位置,SPLuboot会把它copy对应的地址上;
  • entry:二进制文件入口地址,一般kernel image需要提供,uboot会跳转到该地址上执行;
  • hash:镜像使用的校验算法,如sha256crc32等。

这里我们节点说一下hash节点;

o hash@1
  |- algo = "hash or checksum algorithm name"
  |- value = [hash or checksum value]

其中:

  • algo:算法名称,如crc32md5sha256等;
  • 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-bootboot的时候,以配置项为单位加载、执行,这样就可以根据不同的场景,方便的选择不同的配置。

下面是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 Imageramdisk 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_stringsstrings block的大小为0xd1
  • 地址0x00000024: 对应size_dt_structstructure block的大小为0x00000930

其中地址范围0x00000028~0x00000037表示的是fdt_reserve_entry结构体的成员信息:

  • 对应结构体fdt_reserve_entry:它所在的地址为0x28u-boot.its设备树文件没有设置/memreserve/,所以address = 0x0size = 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-offsetdata-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

因为rk399arm64,所以我们还需要编译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错误,如下:

tcdevice-tree-compiler的缩写,即设备树编译器,说明系统中没有安装这个编译器,安装设备树编译器重新编译即可:

root@ubuntu:/work/sambashare/rk3399/u-boot# sudo apt-get install device-tree-compiler

四、rkdeveloptool

rkdeveloptoolRockchip提供的一个与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 配置

首先安装libusbudev,例如对于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-configlibusb-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.binusbplug.binminiloader.bin这三个包:

  • ddr.bin:等价于我们之前说的TPL,用于初始化DDR
  • usbplug.binRockusb驱动程序,用于将程序通过usb下载到eMMC
  • miniloader.binRockchip修改的一个bootloader,等价于我们之前说的SPL,用于加载uboot
4.4.1 下载rkbin

我们可以在Rockchipgithub上下载到Rockchip rkbin项目,如下所示:

root@ubuntu:/work/sambashare/rk3399# git clone https://github.com/rockchip-linux/rkbin.git --depth 1 
4.4.2 合并

rkbin目录下执行如下命令,可以将ddr.binusbplug.binminiloader.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修改ddrusbplugminiloader

[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.imgu-boot.itb文件,由于我们这里不进行内核和根文件系统的烧录,所以暂时不需要准备这俩。

按照Rockchip官方要求将idbloader.img烧录到eMMC0x40扇区,u-boot.itb烧录到0x4000扇区。

我们需要将idbloader.imgu-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 烧录

使用下载引导命令去使目标机器初始化DDR与运行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个引脚,其使用的是RK3399UART2

引脚 开发板接口 USB转串口
1 GND GND
2 VCC 5V VCC
3 UART2DBG_TX RX
4 UART2DBG_RX TX

其电路原理图如下:

需要注意的是:这四根线都要接上,我只接TXRX串口输出数据都是乱码。

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.hinclude/configs/rk3399_common.hinclude/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-30I*B1
2024-01-28*兴20
2024-02-01QYing20
2024-02-11*督6
2024-02-18一*x1
2024-02-20c*l18.88
2024-01-01*I5
2024-04-08*程150
2024-04-18*超20
2024-04-26.*V30
2024-05-08D*W5
2024-05-29*辉20
2024-05-30*雄10
2024-06-08*:10
2024-06-23小狮子666
2024-06-28*s6.66
2024-06-29*炼1
2024-06-30*!1
2024-07-08*方20
2024-07-18A*16.66
2024-07-31*北12
2024-08-13*基1
2024-08-23n*s2
2024-09-02*源50
2024-09-04*J2
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-26B*h10
2024-09-3010
2024-10-02M*i1
2024-10-14*朋10
2024-10-22*海10
2024-10-23*南10
2024-10-26*节6.66
2024-10-27*o5
2024-10-28W*F6.66
2024-10-29R*n6.66
2024-11-02*球6
2024-11-021*鑫6.66
2024-11-25*沙5
2024-11-29C*n2.88
posted @   大奥特曼打小怪兽  阅读(6702)  评论(13编辑  收藏  举报
如果有任何技术小问题,欢迎大家交流沟通,共同进步

公告 & 打赏

>>

欢迎打赏支持我 ^_^

最新公告

程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)。

了解更多

点击右上角即可分享
微信分享提示