打印
[ARM入门]

Rockchip RK3588 - uboot引导方式介绍

[复制链接]
280|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
sw10086|  楼主 | 2025-2-15 14:57 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 sw10086 于 2025-2-15 16:24 编辑

开发板 RK3588 EVB开发板
eMMC :256GB
LPDDR4 :16GB
显示屏 10.1英寸HDMI接口显示屏
u-boot :2017.09
linux :6.1
----------------------------------------------------------------------------------------------------------------------------
在前面的文章我们对Rockhip Linux SDK进行了深入分析,其中涉及到了SDK编译过程、编译源码,具体可以参考:
Rockchip RK3588 - Rockchip Linux SDK编译;(如需要此文档的可以联系博主获取)
Rockchip RK3588 - Rockchip Linux SDK Buildroot文件系统构建;(如需要此文档的可以联系博主获取)
Rockchip RK3588 - Rockchip Linux SDK脚本分析。(如需要此文档的可以联系博主获取)
此外,我们还是深入分析了Recovery模式下的系统升级功能,具体可参考:
Rockchip RK3588 - Rockchip Linux Recovery updateEngine源码分析;(如需要此文档的可以联系博主获取)
Rockchip RK3588 - Rockchip Linux Recovery updateEngine测试。 (如需要此文档的可以联系博主获取)
接下来我们将尝试在RK3588开发板实现系统升级功能,当然我们还期望当根文件系统损坏时,开发板能够通过按住GPIO口进入到recovery系统恢复正常系统。
一、uboot启动方式
既然要实现在开发板实现系统升级功能,我们就需要了解uboot启动内核的方式,并制作以下分区镜像;
misc.img:misc分区是一个没有文件系统的分区,用于存放一些引导配置参数
recovery.img:由kernel + dtb + ramdisk组成,主要用于升级操作;
uboot会根据misc分区存放的字段来判断将要引导的系统是normal系统还是recovery系统。
1.1 系统固件
我们使用的是RK3588开发板,这里我们就去下载官方提供的固件
这里我们选择debian-bullseye-desktop-arm64-images.tgz作为测试使用的镜像文件,将debian-bullseye-desktop-arm64-images.tgz(位于"�3_分区镜像文件"目录下,以实际下载的文件为准)拷贝到/work/sambashare/rk3588/friendly/sd-fuse_rk3588目录下;
root@ubuntu:/work/sambashare/rk3588/friendly/sd-fuse_rk3588# ll debian*
-rwxrw-rw- 1 root root 1590466719 Dec  3 01:49 debian-bullseye-desktop-arm64-images.tgz*
-rwxrw-rw- 1 root root         75 Nov 18 19:05 debian-bullseye-desktop-arm64-images.tgz.hash.md5*
root@ubuntu:/work/sambashare/rk3588/friendly/sd-fuse_rk3588# tar -xvzf debian-bullseye-desktop-arm64-images.tgz
解压得到debian-bullseye-desktop-arm64文件夹;
root@ubuntu:/work/sambashare/rk3588/friendly/sd-fuse_rk3588# ll debian-bullseye-desktop-arm64
-rw-r--r--  1 root root    8072140 May 28  2023 boot.img
-rw-r--r--  1 root root       1424 May 28  2023 dtbo.img
-rw-r--r--  1 root root     307200 Sep  8 23:33 idbloader.img
-rw-r--r--  1 root root         64 Nov 17 10:03 info.conf
-rw-r--r--  1 root root   35551252 Nov 16 16:17 kernel.img
-rw-r--r--  1 root root     471488 Sep  8 23:33 MiniLoaderAll.bin
-rw-r--r--  1 root root      49152 May 28  2023 misc.img
-rw-r--r--  1 root root        470 Nov 17 10:03 parameter.txt
-rw-r--r--  1 root root    6227456 Nov 16 16:17 resource.img
-rw-r--r--  1 root root 3992675220 Nov 17 10:03 rootfs.img
-rw-r--r--  1 root root    4194304 Sep  8 23:33 uboot.img
-rw-r--r--  1 root root     159868 Nov 17 10:03 userdata.img
可以看到解压的文件已经包含了misc.img,但是并没有recovery.img。
1.1.1 系统分区介绍
parameter.txt保存着分区信息
FIRMWARE_VER: 12.0
MACHINE_MODEL: RK3588
MACHINE_ID: 007
MANUFACTURER: RK3588
MAGIC: 0x5041524B
ATAG: 0x00200800
MACHINE: NanoPi6
CHECK_MASK: 0x80
PWR_HLD: 0,0,A,0,1
TYPE: GPT
CMDLINE: mtdparts=rk29xxnand:0x00002000@0x00004000(uboot),0x00002000@0x00006000(misc),0x00002000@0x00008000(dtbo),0x00008000@0x0000a000(resource),0x00014000@0x00012000(kernel),0x00010000@0x00026000(boot),0x00010000@0x00036000(recovery),0x007c0000@0x00046000(rootfs),-@0x00806000(userdata:grow)
解析信息如下:
Number
Name
镜像文件
Start (sector)
End (sector)
Size
1
uboot
uboot.img
0x4000(16384)
0x5FFF
4M
2
misc
misc.img
0x6000(24576)
0x7FFF
4M
3
dtbo
dtbo.img
0x8000(32768)
0x9FFF
4M
4
resource
resource.img
0xa000(40960)
0x11FFF
16MB
5
kernel
kernel.img
0x12000(73728)
0x25FFF
40MB
6
boot
boot.img
0x26000(155648)
0x35FFF
32MB
7
recovery
recovery.img
0x36000(221184)
0x45FFF
32MB
8
rootfs
rootfs.img
0x46000(286720)
0x804FFF
3.968GB
9
userdata
userdata.img
0x806000(8413184)
-
其中:
uboot分区:供uboot编译出来的uboot.img;
misc分区:引导参数分区,供misc.img,给recovery使用;
dtbo::供kernel编译出来的dtbo.img;
resource:资源分区,由设备树、图片资源文件组成,不包含内核;
boot:供kernel编译出来的boot.img(可能是FIT uImage镜像格式,也有可能是Android bootimg镜像格式);
kernel:供kernel编译出来的kernel.img(由tools/mkkrnlimg工具编译内核镜像Image文件得到);
recovery分区:供recovery编译出的recovery.img(kernel + dtb + ramdisk);
rootfs分区:供buildroot、debian或yocto编出来的rootfs.img;
userdata分区:供APP临时生成文件或给最终用户使用,挂载在/userdata目录下。
从上面我们可以看到这里有两个分区时存放了内核镜像,分别是boot和kernel,那问题来了,uboot启动到底使用的是哪个内核呢?
1.1.2 生成统一固件
debian-bullseye-desktop-arm64目录下的镜像文件重新打包成SD卡固件:
root@@ubuntu:/work/sambashare/rk3588/friendly/sd-fuse_rk3588# sudo ./mk-sd-image.sh debian-bullseye-desktop-arm64/
Creating RAW image: out/rk3588-sd-debian-bullseye-desktop-6.1-arm64-20240714.img (7800 MB)
---------------------------------
记录了0+0 的读入
记录了0+0 的写出
0字节已复制,0.0001181 s,0.0 kB/s
----------------------------------------------------------------
[out/rk3588-sd-debian-bullseye-desktop-6.1-arm64-20240714.img] capacity = 7438MB, 7799999488 bytes
current out/rk3588-sd-debian-bullseye-desktop-6.1-arm64-20240714.img partition:
----------------------------------------------------------------
parsing ./debian-bullseye-desktop-arm64//parameter.txt:
create new GPT 9:
----------------------------------------------------------------
copy from: ./debian-bullseye-desktop-arm64 to out/rk3588-sd-debian-bullseye-desktop-6.1-arm64-20240714.img
[RAW. 0]:      300 KB | ./debian-bullseye-desktop-arm64/idbloader.img  > 100% : done.
[RAW. 1]:     4096 KB | ./debian-bullseye-desktop-arm64/uboot.img      > 100% : done.
[RAW. 2]:       48 KB | ./debian-bullseye-desktop-arm64/misc.img       > 100% : done.
[RAW. 3]:        1 KB | ./debian-bullseye-desktop-arm64/dtbo.img       > 100% : done.
[RAW. 4]:     2518 KB | ./debian-bullseye-desktop-arm64/resource.img   > 100% : done.
[RAW. 5]:    34590 KB | ./debian-bullseye-desktop-arm64/kernel.img     > 100% : done.
[RAW. 6]:     7882 KB | ./debian-bullseye-desktop-arm64/boot.img       > 100% : done.
[RAW. 8]:  3907280 KB | ./debian-bullseye-desktop-arm64/rootfs.img     > 100% : done.
[RAW. 9]:      156 KB | ./debian-bullseye-desktop-arm64/userdata.img   > 100% : done.
----------------------------------------------------------------
---------------------------------
RAW image successfully created (21:09:10).
-rw-r--r-- 1 root root 7799999488  7月 14 21:09
sh脚本内部调用了Rockchip官方提供的打包工具sd_update生成的统一固件,由于打包工具并不开源,所以无法研究源码。
不过我们大致可以猜测出应该就是做了一个镜像文件,然后按照parameter.txt进行划分分区,并将各个分区镜像依次烧录进去。
View Code
1.1.3 制作
SD
启动卡
我们将SD卡插入PC上,在虚拟机ubuntu中运行demsg查看新接入的设备;
[36809.524292] usb 2-1: USB disconnect, device number 2
[36813.382382] usb 2-1: new high-speed USB device number 3 using ehci-pci
[36813.657882] usb 2-1: New USB device found, idVendor=14cd, idProduct=1212, bcdDevice= 1.00
[36813.657887] usb 2-1: New USB device strings: Mfr=1, Product=3, SerialNumber=2
[36813.657889] usb 2-1: Product: Mass Storage Device
[36813.657890] usb 2-1: Manufacturer: Generic
[36813.657891] usb 2-1: SerialNumber: 121220160204
[36813.660529] usb-storage 2-1:1.0: USB Mass Storage device detected
[36813.661135] scsi host33: usb-storage 2-1:1.0
[36814.676011] scsi 33:0:0:0: Direct-Access     Mass     Storage Device   1.00 PQ: 0 ANSI: 0 CCS
[36814.677119] sd 33:0:0:0: Attached scsi generic sg2 type 0
[36814.681851] sd 33:0:0:0: [sdb] 62333952 512-byte logical blocks: (31.9 GB/29.7 GiB)
[36814.685829] sd 33:0:0:0: [sdb] Write Protect is off
[36814.685833] sd 33:0:0:0: [sdb] Mode Sense: 03 00 00 00
[36814.690127] sd 33:0:0:0: [sdb] No Caching mode page found
[36814.690132] sd 33:0:0:0: [sdb] Assuming drive cache: write through
[36814.713610]  sdb: sdb1
[36814.714055] sd 33:0:0:0: [sdb] Attached SCSI removable disk
可以看到SD卡对应的设备节点为/dev/sdb,对应1个分区sdb1;
root@@ubuntu:/work/sambashare/rk3588/friendly/sd-fuse_rk3588# sudo ls /dev/sdb*
/dev/sdb  /dev/sdb1
root@ubuntu:/work/sambashare/rk3588/friendly/sd-fuse_rk3588# df -hT
文件系统       类型      容量  已用  可用 已用% 挂载点
udev           devtmpfs  3.9G     0  3.9G    0% /dev
tmpfs          tmpfs     791M  3.6M  787M    1% /run
/dev/sda5      ext4       98G   69G   24G   75% /
tmpfs          tmpfs     3.9G     0  3.9G    0% /dev/shm
tmpfs          tmpfs     5.0M  4.0K  5.0M    1% /run/lock
tmpfs          tmpfs     3.9G     0  3.9G    0% /sys/fs/cgroup
/dev/sda1      vfat      511M  4.0K  511M    1% /boot/efi
/dev/loop15    squashfs  497M  497M     0  100% /snap/gnome-42-2204/132
tmpfs          tmpfs     791M     0  791M    0% /run/user/0
tmpfs          tmpfs     791M   36K  791M    1% /run/user/1000
/dev/sdc2      ext4       11G  311M  9.8G    4% /media/zhengyang/userdata
/dev/sdc1      ext4      4.5G  4.4G   35M  100% /media/zhengyang/rootfs
开始制作SD启动卡:
root@ubuntu:/work/sambashare/rk3588/friendly/sd-fuse_rk3588# sudo dd if=out/rk3588-sd-debian-bullseye-desktop-6.1-arm64-20240714.img of=/dev/sdb bs=4M status=progress
1.2 uboot环境变量
SD卡插入到开发板,并使用准备好的USB转串口适配器和连接线(需另购),连接开发板,给开发板上电。在启动过程中按下CTRL+C进入uboot命令行模式;
View Code
1.2.1 启动命令行
查看内核启动命令;
=> print bootcmd
bootcmd=boot_fit;boot_android ${devtype} ${devnum};bootrkp;run distro_bootcmd;
1.2.2 启动参数
查看内核启动参数:
=> pri bootargs
bootargs=storagemedia=sd androidboot.storagemedia=sd androidboot.mode=normal androidboot.dtbo_idx=1
1.2.3 资源文件
uboot查看资源文件:
# 切换到SD卡所属设备
==> mmc dev 1
switch to partitions #0, OK
mmc1 is current device
==> mmc info
Device: mmc@fe2c0000
Manufacturer ID: 3
OEM: 5344
Name: SD32G
Timing Interface: Legacy
Tran Speed: 52000000
Rd Block Len: 512
SD version 3.0
High Capacity: Yes
Capacity: 29.7 GiB
Bus Width: 4-bit
Erase Group Size: 512 Bytes
# 从resource分区读取20个扇区数据
==> mmc read 0x10000000  0xa000 20
MMC read: dev # 1, block # 40960, count 32 ... 32 blocks read: OK
# 查看前两个扇区数据
==> md.b 0x10000000 0x400
10000000: 52 53 43 45 00 00 00 00 01 01 01 00 18 00 00 00    RSCE............
10000010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
......
10000200: 45 4e 54 52 72 6b 33 33 39 39 2d 6e 61 6e 6f 70    ENTRrk3399-nanop
10000210: 69 34 2d 72 65 76 30 30 2e 64 74 62 00 00 00 00    i4-rev00.dtb....
10000220: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
......
100002e0: 02 8a cd 4f a8 69 32 dd d0 bd de 09 34 59 ad 6e    ...O.i2.....4Y.n
100002f0: 7d 42 d6 ac 00 00 00 00 00 00 00 00 00 00 00 00    }B..............
......
这里我们读取resource分区的数据,也就是resource.img镜像,可以看到以上输出内容中包含了设备树文件的数据。
1.2.4 设备树
查看设备树:
=> print dtb_name
dtb_name=rk3588-nano0pi6-rev01.dtb
1.3 启动内核
当我们在uboot命令行执行了boot命令时,uboot会获取bootcmd环境变量的内容,然后执行bootcmd中保存的启动命令。
接下来我们来分析一下bootcmd默认配置,在默认环境变量default_environment(位于uboot-rockchip/include/env_default.h)中定义有,其内容大致如下:
const uchar default_environment[] = {
        "bootcmd="      CONFIG_BOOTCOMMAND              "�"
    "bootdelay="    __stringify(CONFIG_BOOTDELAY)    "�"
    "baudrate="    __stringify(CONFIG_BAUDRATE)    "�"
    "ipaddr="    __stringify(CONFIG_IPADDR)    "�"
    "serverip="    __stringify(CONFIG_SERVERIP)    "�"
    "netmask="    __stringify(CONFIG_NETMASK)    "�"
    ......
#ifdef  CONFIG_EXTRA_ENV_SETTINGS
        CONFIG_EXTRA_ENV_SETTINGS
#endif
        "�"
};
默认启动命令CONFIG_BOOTCOMMAND定义在uboot-rockchip/include/configs/nanopi6.h,该文件存放着开发板配置信息,被uboot-rockchip/include/config.h文件引入。
#include < configs/rk3588_common.h >
/* Remove or override few declarations from rk3588-common.h */
#undef CONFIG_BOOTCOMMAND
#undef CONFIG_DISPLAY_BOARDINFO_LATE
#undef RKIMG_DET_BOOTDEV
#undef RKIMG_BOOTCOMMAND
#define CONFIG_SYS_MMC_ENV_DEV          0
#define CONFIG_SYS_MMC_MAX_BLK_COUNT    32768
#define CONFIG_MISC_INIT_R
#define CONFIG_SERIAL_TAG
#ifndef CONFIG_SPL_BUILD
#define ROCKCHIP_DEVICE_SETTINGS
        "stdout=serial,vidconsole�"
        "stderr=serial,vidconsole�"
#define RKIMG_DET_BOOTDEV
        "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 rksfc dev 1; then "
                "setenv devtype spinor; setenv devnum 1;"
        "fi; �"
#define RKIMG_BOOTCOMMAND
        "boot_fit;"
        "boot_android ${devtype} ${devnum};"
        "bootrkp;"
        "run distro_bootcmd;"
#define CONFIG_BOOTCOMMAND              RKIMG_BOOTCOMMAND
#endif
这里引入了uboot-rockchip/include/configs/rk3588_common.h,而该文件又引入了uboot-rockchip/include/configs/rockchip-common.h。
这里支持了内核的4种引导方式:
boot_fit:从eMMC中boot/recovery分区(如果进入的是normal系统,则为boot分区;如果进入的是recovery系统,则为recovery分区)加载FIT uImage镜像文件(通常由kernel + dtb + ramdisk组成)到内存,然后启动内核 ;
boot_android:启动Android内核镜像;
bootrkp:通常用于Rockchip平台上的特定启动操作,可能用于启动特定的固件或者特殊的操作模式;
distro_bootcmd:运行uboot环境中定义的 distro_bootcmd,这是一个uboot环境变量,通常包含了一系列的启动命令,比如尝试从网络引导、从存储设备引导等;
其中boot_fit、distro_bootcmd启动方式我们在《 Rockchip RK3399 - 移植linux 5.2.8》中有过介绍。
1.3.1 内核启动日志
输入boot命令启动内核:
=> boot
## Booting FIT Image FIT: No fit blob           # 命令boot_fit
FIT: No FIT image
ANDROID: reboot reason: "(none)"                # 命令boot_android
Not AVB images, AVB skip
No valid android hdr
Android image load failed
Android boot failed, error -1.
## Booting Rockchip Format Image                # 命令bootrkp
fdt      @ 0x08300000 (0x000421b2)              # fdt加载到内存的地址
kernel   @ 0x00400000 (0x021c7808)              # kernel加载到内存的地址
ramdisk  @ 0x0a200000 (0x007b2bc0)              # ramdisk加载到内存的地址
Fdt Ramdisk skip relocation
## Flattened Device Tree blob at 0x08300000
   Booting using the fdt blob at 0x08300000
   Using Device Tree in place at 0000000008300000, end 00000000083451b1
## reserved-memory:
  cma: addr=10000000 size=8000000
  drm-**@00000000: addr=edf00000 size=468000
  vendor-storage-rm@00000000: addr=ebcd3000 size=10000
  ramoops@110000: addr=110000 size=e0000
Adding bank: 0x00200000 - 0x08400000 (size: 0x08200000)
Adding bank: 0x09400000 - 0xf0000000 (size: 0xe6c00000)
Adding bank: 0x100000000 - 0x3fc000000 (size: 0x2fc000000)
Adding bank: 0x3fc500000 - 0x3fff00000 (size: 0x03a00000)
Adding bank: 0x4f0000000 - 0x500000000 (size: 0x10000000)
Total: 10246.299/11135.828 ms
Starting kernel ...
[   11.146608] Booting Linux on physical CPU 0x0000000000 [0x412fd050]
[   11.146631] Linux version 6.1.25 (root@ubuntu) (aarch64-linux-gnu-gcc (Ubuntu 10.5.0-1ubuntu1~20.04) 10.5.0, GNU ld (GNU Binutils for Ubuntu) 2.34) #1 SMP Wed Dec 27 21:53:18 CST 2023
[   11.153743] Machine model: FriendlyElec NanoPC-T6
......
[   11.510154] Kernel command line: storagemedia=sd androidboot.storagemedia=sd androidboot.mode=normal androidboot.dtbo_idx=1 androidboot.verifiedbootstate=orange earlycon=uart8250,mmio32,0xfeb50000 console=ttyFIQ0 coherent_pool=1m irqchip.gicv3_pseudo_nmi=0 rw root=/dev/mmcblk0p8 rootfstype=ext4 data=/dev/mmcblk0p9 consoleblank=0 cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory swapaccount=1
......
Debian GNU/Linux 11 NanoPC-T6 ttyFIQ0
NanoPC-T6 login: [   20.885195] systemd-journald[409]: File /var/log/journal/b9164042f80842f6968af54e1d15c9af/user-1000.journal corrupted or uncleanly shut down, renaming and replacing.
[   21.783657] rk_hdmirx fdee0000.hdmirx-controller: hdmirx_audio_startup: device is no connected or audio is off
[   26.433687] platform mtd_vendor_storage: deferred probe pending
NanoPC-T6 login:
(1) 首先执行boot_fit命令,对于FIT uImage,其中地址范围0x00000000~0x00000027表示的是fdt_header结构体的成员信息。
因此会调用fit_get_blob函数获取boot/recovery分区(如果进入的是normal系统,则获取boot分区;如果进入的是recovery系统,则获取recovery分区)第一个扇区数据,并对fdt_header结构体进行校验判断是不是FIT uImage。
由于正常情况下我们进入的是normal系统,则从boot分区加载boot.img数据,从输出的日志信息可以看出我们烧录的boot.img并不是FIT uIamge。
(2) 接着执行boot_android命令,从输出日志可以看到应该也是引导失败了。
(3) 执行bootrkp命令。
(4) 执行distro_bootcmd命令。
有关bootrkp和distro_bootcmd启动方式,我们接下来详细介绍。
1.3.2 加载命令行
不知道你有没有留意内核启动输出命令行信息;
[   11.510154] Kernel command line: storagemedia=sd androidboot.storagemedia=sd androidboot.mode=normal androidboot.dtbo_idx=1 androidboot.verifiedbootstate=orange earlycon=uart8250,mmio32,0xfeb50000 console=ttyFIQ0 coherent_pool=1m irqchip.gicv3_pseudo_nmi=0 rw root=/dev/mmcblk0p8 rootfstype=ext4 data=/dev/mmcblk0p9 consoleblank=0 cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory swapaccount=1
这里输出的信息为啥和bootargs环境变量以及arch/arm64/boot/dts/rockchip/rk3588-nanopi6-common.dtsi内容不一样呢?
=> pri bootargs
bootargs=storagemedia=sd androidboot.storagemedia=sd androidboot.mode=normal androidboot.dtbo_idx=1
# 设备树设备节点内容
chosen: chosen {
        bootargs = "earlycon=uart8250,mmio32,0xfeb50000 console=ttyFIQ0 coherent_pool=1m irqchip.gicv3_pseudo_nmi=0";
};
那么我们不得不介绍内核启动后是如何获取到启动参数。对于ARM64来说,uboot在启动内核时会将r2设置为dtb文件的开始地址。
1.3.2.1 内核
bootargs
来源
对于开发板开发板而言,r2设置为了rk3588-nanopi6-rev01.dtb加载到内存的地址。
这里我们直接从内核start_kernel函数开始说起,其位于init/main.c文件,函数调用栈如下;
#char __initdata boot_command_line[COMMAND_LINE_SIZE];   // 全局变量,定义在init/main.c
start_kernel()  // init/main.c
    char *command_line;       
        .......
        setup_arch(&command_line);  // arch/arm64/kernel/setup.c
                  ......
                  *cmdline_p = boot_command_line;
                  ......
          // __fdt_pointer:dtb所在的物理地址,由bootloader通过x0寄存器传递过来
                  setup_machine_fdt(__fdt_pointer);      // arch/arm64/kernel/setup.c
                                // 返回dtb所在的虚拟地址dt_virt
                                  void *dt_virt = fixmap_remap_fdt(dt_phys, &size, PAGE_KERNEL)
                                if (!dt_virt || !early_init_dt_scan(dt_virt)) {
                    ........
                }
                name = of_flat_dt_get_machine_name();
                                pr_info("Machine model: %sn", name);
         machine_desc = mdesc;
        ......
这里我们重点关注early_init_dt_scan函数,early_init_dt_scan主要是对dtb进行早期的扫描工作,下面是简要介绍函数的调用流程和实现细节:
early_init_dt_scan(dt_virt)                // drivers/of/fdt.c  
    // 对dtb头进行检查
        early_init_dt_verify(dt_virt)
           early_init_dt_scan_nodes()   // 遍历设备树的节点,解析出重要的信息用于内核启动
        /* Retrieve various information from the /chosen node */
        of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
        /* Initialize {size,address}-cells info */
        of_scan_flat_dt(early_init_dt_scan_root, NULL);
        /* Setup memory, calling early_init_dt_add_memory_arch */
        of_scan_flat_dt(early_init_dt_scan_memory, NULL);
of_scan_flat_dt对dtb里面的所有节点进行扫描,用提供的回调函数循环处理节点信息,回调函数返回0继续扫描,返回非0结束扫描,当扫描到最后一个节点也会结束扫描;
/**
* of_scan_flat_dt - scan flattened tree blob and call callback on each.
* @it: callback function
* @data: context data pointer
*
* This function is used to scan the flattened device-tree, it is
* used to extract the memory information at boot before we can
* unflatten the tree
*/
int __init of_scan_flat_dt(int (*it)(unsigned long node,
                                     const char *uname, int depth,
                                     void *data),
                           void *data)
{
            //dtb数据的地址,也就是根节点的地址
        const void *blob = initial_boot_params;
        const char *pathp;
        int offset, rc = 0, depth = -1;
        if (!blob)
                return 0;
            // 从根节点遍历dtb中每个节点,返回的offset就是每个节点的地址
        // offset:表示节点的地址相对于根节点的偏移量,也是节点数据所在地址
        // depth:代表节点相对于根节点的深度,比如根节点深度是0,/chosen节点是1
        for (offset = fdt_next_node(blob, -1, &depth);
             offset >= 0 && depth >= 0 && !rc;
             offset = fdt_next_node(blob, offset, &depth)) {
                    // 解析出节点名称
                pathp = fdt_get_name(blob, offset, NULL);
                if (*pathp == '/')
                        pathp = kbasename(pathp);
                    // 回调函数解析节点,it是传递进来的设备树节点的解析函数,需要解析什么消息就传递进来相应的节点解析函数
                rc = it(offset, pathp, depth, data);
        }
        return rc;
}
early_init_dt_scan_chosen用于扫描chosen节点,并把bootargs属性值拷贝到boot_command_line中,如果内核定义了CONFIG_CMDLINE这个宏,则把配置的命令行参数也拷贝到boot_command_line;
/*
* Convert configs to something easy to use in C code
*/
#if defined(CONFIG_CMDLINE_FORCE)
static const int overwrite_incoming_cmdline = 1;
static const int read_dt_cmdline;
static const int concat_cmdline;
#elif defined(CONFIG_CMDLINE_EXTEND)
static const int overwrite_incoming_cmdline;
static const int read_dt_cmdline = 1;
static const int concat_cmdline = 1;
#else /* CMDLINE_FROM_BOOTLOADER */             // 走这里
static const int overwrite_incoming_cmdline;
static const int read_dt_cmdline = 1;
static const int concat_cmdline;
#endif
#ifdef CONFIG_CMDLINE
static const char *config_cmdline = CONFIG_CMDLINE;
#else
static const char *config_cmdline = "";
#endif
int __init early_init_dt_scan_chosen(unsigned long node, const char *uname,
                                     int depth, void *data)
{
        int l = 0;
        const char *p = NULL;
        char *cmdline = data;     // 即boot_command_line
        const void *rng_seed;
        pr_debug("search "chosen", depth: %d, uname: %sn", depth, uname);
            // 节点的深度要为1,数据不能使NULL,同时节点名字是"chosen"或者"chosen@0"
        if (depth != 1 || !cmdline ||
            (strcmp(uname, "chosen") != 0 && strcmp(uname, "chosen@0") != 0))
                return 0;
            // 解析initrd相关
        early_init_dt_check_for_initrd(node);
        /* Put CONFIG_CMDLINE in if forced or if data had nothing in it to start */
        if (overwrite_incoming_cmdline || !cmdline[0])  // 进入
                strlcpy(cmdline, config_cmdline, COMMAND_LINE_SIZE);
        /* Retrieve command line unless forcing */
        if (read_dt_cmdline)   // 从chosen节点中解析出bootargs属性
                p = of_get_flat_dt_prop(node, "bootargs", &l);
        if (p != NULL && l > 0) {
                if (concat_cmdline) {
                        int cmdline_len;
                        int copy_len;
                        strlcat(cmdline, " ", COMMAND_LINE_SIZE);
                        cmdline_len = strlen(cmdline);
                        copy_len = COMMAND_LINE_SIZE - cmdline_len - 1;
                        copy_len = min((int)l, copy_len);
                        strncpy(cmdline + cmdline_len, p, copy_len);
                        cmdline[cmdline_len + copy_len] = '�';
                } else {   // 追加bootargs参数到boot_command_line
                        strlcpy(cmdline, p, min((int)l, COMMAND_LINE_SIZE));
                }
        }
        pr_debug("Command line is: %sn", (char*)data);
        rng_seed = of_get_flat_dt_prop(node, "rng-seed", &l);
        if (rng_seed && l > 0) {
                add_bootloader_randomness(rng_seed, l);
                /* try to clear seed so it won't be found. */
                fdt_nop_property(initial_boot_params, node, "rng-seed");
                /* update CRC check value */
                of_fdt_crc32 = crc32_be(~0, initial_boot_params,
                                fdt_totalsize(initial_boot_params));
        }
        /* break now */
        return 1;
}
如果想查看内核debug级别日志可以配置:
# arch/arm64/configs/nanopi6_linux_defconfig
Kernel hacking  --->
    printk and dmesg options  --->
        (8) Default console loglevel (1-15)   # CONFIG_CONSOLE_LOGLEVEL_DEFAULT
        
# 修改drivers/of/fdt.c  即在需要输出debug级别日志的文件头部定义如下宏
#define DEBUG
通过追加日志,我们重新编译并烧录会发现启动命令行的确是如下这个内容:
[    0.000000] OF: fdt: search "chosen", depth: 1, uname: chosen
[    0.000000] OF: fdt: Looking for initrd properties...
[    0.000000] OF: fdt: initrd_start=0xa200000  initrd_end=0xa9b2bc0
[    0.000000] OF: fdt: Command line is: storagemedia=sd androidboot.storagemedia=sd androidboot.mode=normal androidboot.dtbo_idx=1 androidboot.verifiedbootstate=orange earlycon=uart8250,mmio32,0xfeb50000 console=ttyFIQ0 coherent_pool=1m irqchip.gicv3_pseudo_nmi=0 rw root=/dev/mmcblk0p8 rootfstype=ext4 data=/dev/mmcblk0p9 consoleblank=0 cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory swapaccount=1
1.3.2.2
dtb
bootargs
来源
实际上dtb中的bootargs的来源有如下几种;
内核启动参数bootargs保存在dts的chosen节点的bootargs属性,这里就是arch/arm64/boot/dts/rockchip/rk3588-nanopi6-common.dtsi这个设备树源文件;
bootargs数据可以是在dts源文件中定义,也可以是uboot启动内核时传递给内核;
其中uboot传递的bootargs参数优先级高于设备树中定义的bootargs,如果是uboot传递的bootargs,在内核启动阶段就会调用fdt_chosen函数将环境变量中的bootargs参数写进dtb数据中;
既然uboot传递了bootargs参数,那么内核将会使用uboot传递过来的bootargs参数,不过该参数为何和内核启动输出的不太一样呢?为此我们不得不去研究bootrkp启动是否追加了启动参数;
boot_rockchip_image(dev_desc, &part)   // bootrkp启动方式
        ......
    // 设置内核加载地址(Image镜像)
        images.ep = kernel_addr_r;
        images.initrd_start = ramdisk_addr_r;
        images.initrd_end = ramdisk_addr_r + ramdisk_size;
        // 设置设备树加载地址
        images.ft_addr = (void *)fdt_addr_r;
    // 设备树长度
        images.ft_len = fdt_totalsize(fdt_addr_r);
        do_bootm_linux(0, 0, NULL, &images);      // arch/arm/lib/bootm.c
                boot_prep_linux(images);              // arch/arm/lib/bootm.c
                        image_setup_linux(images)         // common/image.c
                ulong of_size = images->ft_len;
                char **of_flat_tree = &images->ft_addr;       
                                struct lmb *lmb = &images->lmb;
                boot_fdt_add_mem_rsv_regions(lmb, *of_flat_tree);       
                                boot_get_cmdline(lmb, &images->cmdline_start,
                                &images->cmdline_end);
                                boot_relocate_fdt(lmb, of_flat_tree, &of_size);
                                image_setup_libfdt(images, *of_flat_tree, of_size, lmb); // common/image-fdt.c
            ......   
        ......
               
# 可以通过如下代码输出启动参数
char *commandline = env_get("bootargs");
printf("%s %d:%sn", __func__, __LINE__, commandline);
重点关注image_setup_libfdt,定义在common/image-fdt.c;
int image_setup_libfdt(bootm_headers_t *images, void *blob,
                       int of_size, struct lmb *lmb)
{
        ulong *initrd_start = &images->initrd_start;
        ulong *initrd_end = &images->initrd_end;
        int ret = -EPERM;
        int fdt_ret;
            // 进行架构特定的设备树修正
        if (arch_fixup_fdt(blob) < 0) {
                printf("ERROR: arch-specific fdt fixup failedn");
                goto err;
        }
#if defined(CONFIG_PASS_DEVICE_SERIAL_BY_FDT)     // 定义
            // 配置根节点
        if (fdt_root(blob) < 0) {
                printf("ERROR: root node setup failedn");
                goto err;
        }
#endif
            // 创建/chosen节点
        if (fdt_chosen(blob) < 0) {
                printf("ERROR: /chosen node create failedn");
                goto err;
        }
        /* Update ethernet nodes */
        fdt_fixup_ethernet(blob);
        if (IMAGE_OF_BOARD_SETUP) {
                fdt_ret = ft_board_setup(blob, gd- >bd);
                if (fdt_ret) {
                        printf("ERROR: board-specific fdt fixup failed: %sn",
                               fdt_strerror(fdt_ret));
                        goto err;
                }
        }
        if (IMAGE_OF_SYSTEM_SETUP) {
                fdt_ret = ft_system_setup(blob, gd->bd);
                if (fdt_ret) {
                        printf("ERROR: system-specific fdt fixup failed: %sn",
                               fdt_strerror(fdt_ret));
                        goto err;
                }
        }
        /* Delete the old LMB reservation */
        if (lmb)
                lmb_free(lmb, (phys_addr_t)(u32)(uintptr_t)blob,
                         (phys_size_t)fdt_totalsize(blob));
        ret = fdt_shrink_to_minimum(blob, 0);
        if (ret < 0)
                goto err;
        of_size = ret;
        if (*initrd_start && *initrd_end) {
                of_size += FDT_RAMDISK_OVERHEAD;
                fdt_set_totalsize(blob, of_size);
        }
        /* Create a new LMB reservation */
        if (lmb)
                lmb_reserve(lmb, (ulong)blob, of_size);
        fdt_initrd(blob, *initrd_start, *initrd_end);
        if (!ft_verify_fdt(blob))
                goto err;
#if defined(CONFIG_SOC_KEYSTONE)
        if (IMAGE_OF_BOARD_SETUP)
                ft_board_setup_ex(blob, gd- >bd);
#endif
        return 0;
err:
        printf(" - must RESET the board to recover.nn");
        return ret;
}
这里我们只需要关注fdt_chosen函数,定义在common/fdt_support.c;其中rk3399和rk3588 SDK的u-boot源码是不一样的;
rk3588为例:
int fdt_chosen(void *fdt)
{
        int   nodeoffset;
        int   err;
        char  *str;             /* used to set string properties */
            // 检查设备树头部是否有效
        err = fdt_check_header(fdt);
        if (err < 0) {
                printf("fdt_chosen: %sn", fdt_strerror(err));
                return err;
        }
        /* find or create "/chosen" node.  查找或创建/chosen节点 */
        nodeoffset = fdt_find_or_add_subnode(fdt, 0, "chosen");
        if (nodeoffset < 0)
                return nodeoffset;
            // 获取环境变量bootargs的值
        str = board_fdt_chosen_bootargs(fdt);
        if (str) {
                    // 设置设备树中的bootargs属性
                err = fdt_setprop(fdt, nodeoffset, "bootargs", str,
                                  strlen(str) + 1);
                if (err < 0) {
                        printf("WARNING: could not set bootargs %s.n",
                               fdt_strerror(err));
                        return err;
                }
        }
        return fdt_fixup_stdout(fdt, nodeoffset);
}
其中board_fdt_chosen_bootargs定义在arch/arm/mach-rockchip/board.c:
char *board_fdt_chosen_bootargs(void *fdt)
{
        /* bootargs_ext is used when dtbo is applied. */
        const char *arr_bootargs[] = { "bootargs", "bootargs_ext" };
        const char *bootargs;
        int nodeoffset;
        int i, dump;
        char *msg = "kernel";
        /* debug */
        hotkey_run(HK_INITCALL);
        dump = is_hotkey(HK_CMDLINE);
        if (dump)
                printf("## bootargs(u-boot): %snn", env_get("bootargs"));
        /* find or create "/chosen" node. */
        nodeoffset = fdt_find_or_add_subnode(fdt, 0, "chosen");
        if (nodeoffset < 0)
                return NULL;
            // 遍历arr_bootargs,检查设备树中是否已有相关的bootargs
        for (i = 0; i < ARRAY_SIZE(arr_bootargs); i++) {
                    // 获取/chosen节点的bootargs、bootargs_ext属性值
                bootargs = fdt_getprop(fdt, nodeoffset, arr_bootargs, NULL);
                if (!bootargs)
                        continue;
                if (dump)
                        printf("## bootargs(%s-%s): %snn",
                               msg, arr_bootargs, bootargs);
                /*
                 * Append kernel bootargs
                 * If use AB system, delete default "root=" which route
                 * to rootfs. Then the ab bootctl will choose the
                 * high priority system to boot and add its UUID
                 * to cmdline. The format is "roo=PARTUUID=xxxx...".
                 */
#ifdef CONFIG_ANDROID_AB
                env_update_filter("bootargs", bootargs, "root=");
#else
                    // 进入,更新bootargs环境变量,追加设备树中配置的bootargs       
                env_update("bootargs", bootargs);
#endif
        }
#ifdef CONFIG_VENDOR_FRIENDLYELEC // 针对FriendlyELEC板卡的处理,进入
        char *panel = board_get_panel_name();
            // 如果设置了panel,更新bootargs环境变量,比如追加lcd=HD702E,213dpi
        if (panel) {
                char lcdinfo[128] = { 0 };
                strcpy(lcdinfo, "lcd=");
                strncat(lcdinfo, panel, sizeof(lcdinfo) - 5);
                env_update("bootargs", lcdinfo);
        }
#endif
#if defined(CONFIG_ENVF) || defined(CONFIG_ENV_PARTITION)  
        ......
#endif
#ifdef CONFIG_MTD_BLK
        ......
#endif
#ifdef CONFIG_ANDROID_AB
        ab_update_root_partition();
#endif
        /*
         * Initrd fixup: remove unused "initrd=0x...,0x...",
         * this for compatible with legacy parameter.txt
         */
        env_delete("bootargs", "initrd=", 0);
        /*
         * If uart is required to be disabled during
         * power on, it would be not initialized by
         * any pre-loader and U-Boot.
         *
         * If we don't remove earlycon from commandline,
         * kernel hangs while using earlycon to putc/getc
         * which may dead loop for waiting uart status.
         * (It seems the root cause is baundrate is not
         * initilalized)
         *
         * So let's remove earlycon from commandline.
         */
        if (gd- >flags & GD_**_DISABLE_CONSOLE)
                env_delete("bootargs", "earlycon=", 0);
        /* Android header v4+ need this handle */
#ifdef CONFIG_ANDROID_BOOT_IMAGE
        struct andr_img_hdr *hdr;
        hdr = (void *)env_get_ulong("android_addr_r", 16, 0);
        if (hdr && !android_image_check_header(hdr) && hdr->header_version >= 4) {
                if (env_update_extract_subset("bootargs", "andr_bootargs", "androidboot."))
                        printf("extract androidboot.xxx errorn");
                if (dump)
                        printf("## bootargs(android): %snn", env_get("andr_bootargs"));
        }
#endif
        bootargs = env_get("bootargs");
        if (dump)
                printf("## bootargs(merged): %snn", bootargs);
        return (char *)bootargs;
}
rk3399为例:
int fdt_chosen(void *fdt)
{
        /*
         * "bootargs_ext" is used when dtbo is applied.
         */
        const char *arr_bootargs[] = { "bootargs", "bootargs_ext" };
        int   nodeoffset;
        int   err;
        int   i;
        char  *str;             /* used to set string properties */
        int dump;
            // 判断HK_CMDLINE是否是热键,返回false
        dump = is_hotkey(HK_CMDLINE);
            // 检查设备树头部是否有效
        err = fdt_check_header(fdt);
        if (err < 0) {
                printf("fdt_chosen: %sn", fdt_strerror(err));
                return err;
        }
        /* find or create "/chosen" node.  查找或创建/chosen节点 */
        nodeoffset = fdt_find_or_add_subnode(fdt, 0, "chosen");
        if (nodeoffset < 0)
                return nodeoffset;
            // 获取环境变量bootargs的值
        str = env_get("bootargs");
        if (str) {  // 如果环境变量配置了bootargs
#ifdef CONFIG_ARCH_ROCKCHIP       // 针对Rockchip架构的处理
                const char *bootargs;
                if (dump)
                        printf("## U-Boot bootargs: %sn", str);
                    // 遍历arr_bootargs,检查设备树中是否已有相关的bootargs
                for (i = 0; i < ARRAY_SIZE(arr_bootargs); i++) {
                            // 获取/chosen节点的bootargs、bootargs_ext属性值
                        bootargs = fdt_getprop(fdt, nodeoffset,
                                               arr_bootargs, NULL);
                            // 1. fdt_chosen 389:earlycon=uart8250,mmio32,0xff1a0000 swiotlb=1 coherent_pool=1m
                            // 2. fdt_chosen 389:root=/dev/mmcblk2p8 rw rootfstype=ext4 rootflags=discard data=/dev/mmcblk2p9 console=ttyFIQ0 consoleblank=0 cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory swapaccount=1
                                                printf("%s %d:%sn", __func__, __LINE__, bootargs);
                        // 如果存在,更新环境变量bootargs
                        if (bootargs) {
                                if (dump)
                                        printf("## Kernel %s: %sn",
                                               arr_bootargs, bootargs);
                                /*
                                 * Append kernel bootargs
                                 * If use AB system, delete default "root=" which route
                                 * to rootfs. Then the ab bootctl will choose the
                                 * high priority system to boot and add its UUID
                                 * to cmdline. The format is "roo=PARTUUID=xxxx...".
                                 */
                                hotkey_run(HK_INITCALL);
#ifdef CONFIG_ANDROID_AB       // 未定义
                                env_update_filter("bootargs", bootargs, "root=");
#else                           // 进入,更新bootargs环境变量,追加设备树中配置的bootargs
                                env_update("bootargs", bootargs);
#endif
#ifdef CONFIG_MTD_BLK          // 未定义
                                char *mtd_par_info = mtd_part_parse();
                                if (mtd_par_info) {
                                        if (memcmp(env_get("devtype"), "mtd", 3) == 0)
                                                env_update("bootargs", mtd_par_info);
                                }
#endif
                                /*
                                 * Initrd fixup: remove unused "initrd=0x...,0x...",
                                 * this for compatible with legacy parameter.txt
                                 */
                                env_delete("bootargs", "initrd=", 0);
                                /*
                                 * If uart is required to be disabled during
                                 * power on, it would be not initialized by
                                 * any pre-loader and U-Boot.
                                 *
                                 * If we don't remove earlycon from commandline,
                                 * kernel hangs while using earlycon to putc/getc
                                 * which may dead loop for waiting uart status.
                                 * (It seems the root cause is baundrate is not
                                 * initilalized)
                                 *
                                 * So let's remove earlycon from commandline.
                                 */
                                if (gd- >flags & GD_**_DISABLE_CONSOLE)
                                        env_delete("bootargs", "earlycon=", 0);
                        }
                }
#endif
#ifdef CONFIG_VENDOR_FRIENDLYELEC        // 针对FriendlyELEC板卡的处理,进入
                char *panel = board_get_panel_name();
                    // 如果设置了panel,更新bootargs环境变量,比如追加lcd=HD702E,213dpi
                if (panel) {
                        char lcdinfo[128] = { 0 };
                        strcpy(lcdinfo, "lcd=");
                        strncat(lcdinfo, panel, sizeof(lcdinfo) - 5);
                        env_update("bootargs", lcdinfo);
                }
#endif
                                // 获取更新后的bootargs环境变量,并设置设备树中的bootargs属性
                str = env_get("bootargs");
                    // fdt_chosen 451:storagemedia=emmc androidboot.storagemedia=emmc androidboot.mode=normal androidboot.dtbo_idx=0 earlycon=uart8250,mmio32,0xff1a0000 swiotlb=1 coherent_pool=1m rw root=/dev/mmcblk2p8 rootfstype=ext4 rootflags=discard data=/dev/mmcblk2p9 console=ttyFIQ0 consoleblank=0 cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory swapaccount=1 lcd=HD702E,213dpi
                printf("%s %d:%sn", __func__, __LINE__, str);
                err = fdt_setprop(fdt, nodeoffset, "bootargs", str,
                                  strlen(str) + 1);
                if (err < 0) {
                        printf("WARNING: could not set bootargs %s.n",
                               fdt_strerror(err));
                        return err;
                }
        }
        if (dump)
                printf("## Merged bootargs: %sn", env_get("bootargs"));
        return fdt_fixup_stdout(fdt, nodeoffset);
}
通过分析,可以了解到fdt_chosen 函数主要完成了以下任务:
确保设备树的 /chosen 节点存在;
从环境变量中获取和处理启动参数 bootargs;
根据不同的硬件配置(如Rockchip架构或FriendlyELEC板卡)调整启动参数;
更新设备树中的 bootargs 属性,确保内核可以正确获得启动参数;
修正标准输出设备配置。
在上面代码执行过程中我们输出了/chosen节点的bootargs、bootargs_ext属性值,其中bootargs_ext属性值哪里来的呢?
root=/dev/mmcblk2p8 rw rootfstype=ext4 rootflags=discard data=/dev/mmcblk2p9 console=ttyFIQ0 consoleblank=0 cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory swapaccount=1
这个值实际上配置在dtbo.img镜像中,具体可以参考android_fdt_overlay_apply函数,这个我们在接下来的内容会介绍到。
1.4 uboot编译和烧录
1.4.1 编译
如果我们对uboot源码有改动,执行如下命令进行编译;
root@ubuntu:/work/sambashare/rk3588/friendly/sd-fuse_rk3588# UBOOT_SRC=$PWD/uboot-rockchip ./build-uboot.sh debian-bullseye-desktop-arm64
编译完成后debian-bullseye-desktop-arm64目录下的uboot.img被更新了。
1.4.2
dd
烧录
由于uboot.img占用的分区是uboot分区,假设SD/TF Card设备节点为/dev/mmcblk0。
我们在ubuntu开启http下载服务,或者使用scp将镜像文件发送到开发版;
root@ubuntu:/work/sambashare/rk3588/friendly/sd-fuse_rk3588/debian-bullseye-desktop-arm64$ python3 -m http.server 8080
开发板下载uboot.img,然后使用如下命令烧录;
root@linaro-alip:/opt# sudo wget 192.168.0.200:8080/uboot.img
root@linaro-alip:/opt# sudo dd if=uboot.img of=/dev/mmcblk0p1 bs=1M
同样如果我们修改了resource.img,也可以使用如下命令烧录;
root@linaro-alip:/opt# sudo wget 192.168.0.200:8080/resource.img
root@linaro-alip:/opt# sudo dd if=resource.img of=/dev/mmcblk0p4 bs=1M
1.5 补充
新创云RK3588核心板介绍
1.1 适用范围
产品采用核心板加底板方式,核心板主要集成四大主件(分别为主控,内存,存储和电源管理),核心板安装可方便拆卸,客户可以快速进行二次开发,主要应用于工业控制,商显,智能家居汽车电子,医疗设备,产品形态有,广告*,无人值守机器人,送餐机器人,工业应用机器人,平板电脑,学习机,匝机通道,客流统计,车牌识别,数字标牌、智能自助终端、智能零售终端等相关产品。
1.2 产品概述
核心板采用 ROCKCHIP 八核 RK3588 四核 A76+四核 A55,搭载Android/Linux+QT/Ubuntu 系 统 ,A76 主 频 2.4GHz , A55 主 频1.8G,采用 Mali-G610 MP4 GPU,内置 6T 算力 NPU,内存最大支持16GB,支持市面上通用显示屏接口,支持多屏异显,核心板接口丰富,引出全部功能引脚,支持多款外设扩展,是您在人机交互、工控项目上的最佳选择。
1.3 产品特点
◆ BDB 板对板连接器可拆卸式核心板,引脚高达 320P,引出全部功能。
◆ 支持 2 路 HDMI 输出,2 路 MIPI 显示屏,2 路 EDP 显示屏,支持多屏异显。
◆ 内置 6T 算力 NPU。
◆ 最大支持 16GB 内存。
◆ 支持 Android/Linux+QT/麒麟信安、鸿蒙系统定制,提供系统调用接口 API 参考代码,完美支持客户上层应用 APP 开发及SDK。
file:///C:/Users/Admin/AppData/Local/Temp/ksohtml4004/wps1.jpg
主要硬件指标
CPU
ROCKCHIP RK3588 八核 A76+A55
NPU
6 TOPS
GPU
Mali G61 MP4
DDR
LPDDR4x 可选配 4G/8G/16G
EMMC
EMMC 5.1 标配 8GB 选配 32G/64G/128G
工作电压
3.4-5.5V 5A 以上
工作温度
-10 到+60 度
连接方式
板对板连接器
尺寸
60mm 长*50mm 宽*4.2mm 高
寿命
连续运行寿命大于 5 年以上

wKgZO2ev-l-AFls3AAmjrQA1k3c116.png (509.07 KB )

wKgZO2ev-l-AFls3AAmjrQA1k3c116.png

使用特权

评论回复

相关帖子

发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

6

主题

65

帖子

0

粉丝