☰
RK3588固件打包全流程解析:从分区表到update.img生成
2026/9/28 8:10:23 网站建设 项目流程

1. 从一次"打包失败"说起:为什么RK3588的固件打包值得单独写一篇

第一次在RK3588上跑完整打包流程的人,大概率会经历这样一个场景:编译明明全过了,./build.sh也跑完了,结果烧录进板子起不来,串口一片安静,或者卡在U-Boot阶段反复重启。回头翻日志,发现是分区表对不上、boot.img里kernel和dtb版本错位,或者rootfs打包时漏了某个依赖。这类问题在RK3568上可能还能靠经验蒙混过去,到了RK3588这种多核异构、分区结构更复杂的平台上,就很容易翻车。

RK3588是瑞芯微目前主流的高性能SoC,四核A76加四核A55,带6TOPS NPU,支持8K编解码,被大量用在边缘计算盒子、NVR、工业网关、AI一体机这类产品上。它的固件打包不是简单地把几个镜像拼在一起,而是涉及分区表定义、镜像生成、打包脚本调用、update.img合成这一整条链路。任何一个环节的参数错了,最终产物就是一块"砖"。

这篇内容面向的是已经能在RK3588上完成基本编译、但对打包流程还停留在"照着文档敲命令"阶段的开发者。我会把从脚本调用到update.img生成的完整链路拆开讲,重点放在每一步到底在干什么、参数为什么这么设、出错时怎么定位。不是官方文档的复述,而是我在实际项目里踩过坑之后整理出来的可复现流程。

需要提前说明的是,不同SDK版本(Android 12、Ubuntu、Buildroot)的打包脚本细节会有差异,但核心逻辑是一致的。下面以瑞芯微官方Linux SDK的通用流程为主线,Android和Ubuntu的差异我会在对应位置单独标注。

2. 打包之前必须先搞懂的三件事:分区、镜像、打包脚本

2.1 parameter分区表:整个固件的地基

RK3588的固件不是一整块无差别的数据,而是被划分成多个逻辑分区,每个分区有固定的起始地址、大小和用途。这张"地图"就是parameter.txt(有些SDK里叫parameter或parameter-gpt.txt)。它决定了烧录工具把哪个镜像写到Flash的哪个位置。

一个典型的RK3588 Linux SDK的parameter文件长这样:

FIRMWARE_VER: 1.0 MACHINE_MODEL: RK3588 MACHINE_ID: 007 MANUFACTURER: RK3588 MAGIC: 0x5041524B ATAG: 0x00200800 MACHINE: 0xffffffff CHECK_MASK: 0x80 PWR_HLD: 0,0,A,0,1 TYPE: GPT CMDLINE: mtdparts=rk29xxnand:0x00002000@0x00004000(uboot),0x00002000@0x00006000(trust),0x00002000@0x00008000(misc),0x00010000@0x0000A000(boot),0x00010000@0x0001A000(recovery),0x00020000@0x0002A000(backup),0x00040000@0x0004A000(rootfs),-@0x0008A000(userdata:grow)

这里有几个关键点必须理解:

  • TYPE: GPT:RK3588默认用GPT分区表,不是MBR。这意味着分区数量可以超过4个,且支持大于2TB的存储。如果你从老平台移植过来还在用MBR格式,烧录工具会直接报错。
  • CMDLINE里的mtdparts:格式是大小@起始偏移(分区名)。大小和偏移的单位是扇区(512字节),不是字节。比如0x00010000个扇区 = 65536 × 512 = 32MB。这个换算关系搞错,分区就会重叠或留空。
  • -@0x0008A000(userdata:grow):-表示这个分区占用剩余所有空间,grow表示可动态扩展。userdata分区通常这样配置,方便首次启动时根据实际存储容量自动调整。

注意:修改parameter文件后,必须同步更新分区表相关的配置,否则烧录工具用的还是旧的GPT头,会出现"分区表与镜像不匹配"的错误。

2.2 各分区镜像的生成逻辑

parameter定义好之后,每个分区对应的镜像从哪来?这是打包流程里最容易混淆的部分。我整理了一张对照表:

分区名镜像文件生成方式关键依赖
ubootuboot.imgU-Boot编译产物 + trust合并rkbin、BL31
trusttrust.imgATF编译产物BL31、OP-TEE
bootboot.imgkernel + dtb + ramdisk打包Image、rk3588-xxx.dtb
recoveryrecovery.imgrecovery ramdisk + kernel独立配置
rootfsrootfs.imgBuildroot/Ubuntu根文件系统文件系统制作脚本
userdata无(空分区)首次启动格式化无

这里重点说boot.img。它不是简单地把kernel和dtb拼在一起,而是遵循Android boot image格式(即使你跑的是Ubuntu,很多SDK也沿用这个格式)。打包时会写入kernel的加载地址、ramdisk的偏移、dtb的附加位置等元信息。RK3588的kernel加载地址通常是0x02080000,dtb紧随其后。如果这个地址和U-Boot里配置的bootargs对不上,就会出现"kernel解压后跳转到错误地址"的经典问题。

uboot.img的生成稍微特殊一点。RK3588的U-Boot需要和ARM Trusted Firmware(ATF)的BL31、以及DDR初始化固件(rkbin里的ddr.bin)一起工作。打包脚本会把u-boot.bin、bl31.elf、ddr.bin按特定顺序合并成uboot.img。这个合并顺序是固定的,不能随意调整,否则芯片上电后的启动流程会断在DDR初始化阶段。

2.3 打包脚本的调用入口:build.sh到底做了什么

瑞芯微SDK的顶层入口通常是build.sh,但它本身不做实际打包,而是根据参数分发到不同的子脚本。常见的调用方式:

# 完整编译+打包 ./build.sh # 只打包,不重新编译 ./build.sh kernel ./build.sh rootfs ./build.sh firmware # 指定板级配置 ./build.sh rk3588_defconfig

build.sh内部会读取device/rockchip/rk3588/目录下的板级配置,确定用哪个parameter文件、哪个dtb、哪个rootfs配置。然后依次调用:

  1. build-kernel.sh:编译kernel,生成boot.img
  2. build-rootfs.sh:制作根文件系统,生成rootfs.img
  3. mk-firmware.sh:收集所有镜像,调用打包工具生成update.img

理解这个分发逻辑很重要,因为当你只想重新打包(比如只改了dtb)时,不需要跑完整编译,直接调对应的子脚本能省大量时间。我见过有人每次改一行dts就./build.sh全量跑一遍,四十分钟起步,其实./build.sh kernel两分钟就搞定了。

3. 脚本调用链路拆解:从build.sh到mk-firmware.sh

3.1 板级配置的加载顺序与覆盖规则

RK3588 SDK的配置加载有一套优先级规则,搞不清楚就会出现"我明明改了配置,为什么没生效"的情况。加载顺序大致是:

  1. 芯片级默认配置:device/rockchip/rk3588/BoardConfig.mk
  2. 板级配置:device/rockchip/rk3588/<board_name>/BoardConfig.mk
  3. 环境变量覆盖:命令行传入的参数或export的变量

后加载的会覆盖先加载的。比如你在板级配置里设了RK_KERNEL_DTS=rk3588-myboard.dtb,但环境变量里export了RK_KERNEL_DTS=rk3588-evb.dtb,那最终用的是evb的dtb。这个规则在调试阶段特别容易踩坑,建议每次打包前用env | grep RK_确认一下当前生效的变量。

几个最常需要关注的配置项:

# 内核设备树 RK_KERNEL_DTS=rk3588s-myboard.dtb # 分区表文件 RK_PARAMETER=parameter-buildroot.txt # 根文件系统类型 RK_ROOTFS_TYPE=ext4 # 是否打包成update.img RK_BUILD_UPDATE_IMG=true

提示:RK_KERNEL_DTS的值不带路径,脚本会自动去kernel/arch/arm64/boot/dts/rockchip/下找。如果你把dtb放在自定义目录,需要额外设置RK_KERNEL_DTS_PATH。

3.2 kernel打包:boot.img里到底塞了什么

build-kernel.sh执行到打包阶段时,核心动作是调用mkbootimg工具。这个工具在u-boot/tools/或kernel/scripts/下都能找到。它的输入参数决定了boot.img的内部结构:

mkbootimg \ --kernel arch/arm64/boot/Image \ --second arch/arm64/boot/dts/rockchip/rk3588-myboard.dtb \ --ramdisk ramdisk.img \ --base 0x02080000 \ --kernel_offset 0x00080000 \ --ramdisk_offset 0x08000000 \ --second_offset 0x00f00000 \ --tags_offset 0x00000100 \ --pagesize 2048 \ --os_version 12.0.0 \ --header_version 2 \ -o boot.img

这里每个offset都不是随便写的。--base是内核空间的基地址,RK3588的DDR起始映射在0x02000000附近,kernel放在0x02080000是SDK约定。--second_offset是dtb相对于base的偏移,0x00f00000意味着dtb加载在0x02f00000。这些地址必须和U-Boot的bootargs里kernel_addr_r、fdt_addr_r保持一致。

--header_version 2表示使用Android boot image v2格式,支持dtb附加。如果设成1,dtb会被塞进ramdisk里,启动时可能找不到。RK3588的SDK默认用v2。

打包完成后,可以用unpackbootimg反向验证:

unpackbootimg -i boot.img -o /tmp/boot_check/ cat /tmp/boot_check/boot.img-base cat /tmp/boot_check/boot.img-dt

确认输出的base地址和dtb名称和你预期的一致。这一步在排查"kernel起来但找不到根文件系统"时特别有用。

3.3 rootfs打包:ext4镜像的制作与大小控制

rootfs.img的生成分两步:先制作根文件系统目录树,再把它打包成ext4镜像。Buildroot和Ubuntu的目录树来源不同,但打包方式类似。

Buildroot的rootfs在buildroot/output/rockchip_rk3588/target/下,Ubuntu的通常在ubuntu/rootfs/下。打包命令核心是mkfs.ext4:

# 先创建一个固定大小的空镜像 dd if=/dev/zero of=rootfs.img bs=1M count=2048 # 格式化成ext4 mkfs.ext4 -F -L rootfs rootfs.img # 挂载并拷贝文件 mkdir -p /mnt/rootfs mount -o loop rootfs.img /mnt/rootfs cp -a target/* /mnt/rootfs/ umount /mnt/rootfs # 检查并调整大小 e2fsck -f -y rootfs.img resize2fs -M rootfs.img

这里有几个实操细节:

  • count=2048是初始大小,2GB。如果实际文件超过这个数,cp会失败。可以先du -sh target/看实际大小,再留20%余量。
  • resize2fs -M会把镜像缩小到最小可用尺寸,减少最终update.img的体积。但注意,如果parameter里rootfs分区大小是固定的,缩小后的镜像烧录后分区会有未使用空间,这是正常的。
  • -L rootfs设置的卷标要和fstab里的一致,否则启动时挂载根文件系统会失败。

Ubuntu根文件系统还有个额外步骤:需要处理etc/fstab和etc/passwd,确保串口登录、网络配置这些在首次启动时能正常工作。我遇到过打包时没清理machine-id,导致多台设备烧录后网络MAC地址冲突的情况。

3.4 mk-firmware.sh:update.img的最终合成

所有分区镜像准备好之后,mk-firmware.sh负责把它们合成一个update.img。这个文件是瑞芯微烧录工具(RKDevTool或upgrade_tool)直接识别的格式,内部包含一个头部、分区表、以及所有镜像的拼接数据。

核心调用是afptool和img_maker两个工具:

# 第一步:用afptool打包成临时文件 afptool -pack ./ update.tmp # 第二步:用img_maker加上RKFW头部 img_maker -rk3588 update.tmp update.img

afptool -pack会读取当前目录下的parameter.txt和各个.img文件,按分区顺序打包。img_maker则添加瑞芯微私有的RKFW头部,包含芯片型号、固件版本、签名信息等。

注意:img_maker的-rk3588参数必须和实际芯片匹配。如果错写成-rk3568,烧录工具会拒绝加载,提示"固件与芯片不匹配"。

合成完成后,可以用afptool -unpack验证:

mkdir /tmp/update_check afptool -unpack update.img /tmp/update_check/ ls /tmp/update_check/

正常应该能看到parameter.txt、boot.img、rootfs.img等文件,且大小和原始镜像一致。如果某个镜像缺失或大小异常,说明打包过程中有文件没被正确收集。

4. 打包过程中最容易翻车的五个环节

4.1 分区表与镜像大小不匹配

这是最高频的问题。parameter里定义boot分区32MB,但实际生成的boot.img有40MB,打包时不会报错,但烧录后启动会失败,因为镜像被截断了。

排查方法:打包前用脚本自动检查每个镜像大小和parameter里的分区大小。

#!/bin/bash # check_partition_size.sh PARAM_FILE="parameter.txt" declare -A PART_SIZE # 解析parameter里的分区大小(扇区转MB) while IFS= read -r line; do if [[ $line =~ mtdparts=.*: ]]; then parts=$(echo "$line" | sed 's/.*mtdparts=[^:]*://') IFS=',' read -ra entries <<< "$parts" for entry in "${entries[@]}"; do size=$(echo "$entry" | cut -d'@' -f1) name=$(echo "$entry" | sed 's/.*(\(.*\)).*/\1/') if [[ "$size" != "-" ]]; then size_mb=$((16#$size * 512 / 1024 / 1024)) PART_SIZE[$name]=$size_mb fi done fi done < "$PARAM_FILE" # 检查各镜像 for img in boot.img recovery.img rootfs.img; do if [[ -f "$img" ]]; then img_mb=$(du -m "$img" | cut -f1) part_name="${img%.img}" limit=${PART_SIZE[$part_name]} if [[ -n "$limit" && $img_mb -gt $limit ]]; then echo "ERROR: $img is ${img_mb}MB, exceeds partition limit ${limit}MB" else echo "OK: $img ${img_mb}MB / ${limit}MB" fi fi done

这个脚本我放在SDK根目录,每次打包前跑一遍,省了很多事后排查的时间。

4.2 dtb选错或未更新

改了dts之后忘记重新编译dtb,或者RK_KERNEL_DTS指向了错误的板级文件,是另一个常见坑。表现是:kernel能启动,但外设(网口、USB、PCIe)不工作,或者直接卡在"Uncompressing Linux"之后。

验证方法:从boot.img里提取dtb,反编译回dts,确认关键节点。

# 提取dtb unpackbootimg -i boot.img -o /tmp/boot_check/ dtc -I dtb -O dts /tmp/boot_check/boot.img-dt -o /tmp/check.dts # 检查关键节点 grep -A5 "ethernet@" /tmp/check.dts grep -A5 "usbdrd" /tmp/check.dts

如果发现dts里还是旧的配置,说明编译时dtb没更新。可以手动删掉kernel/arch/arm64/boot/dts/rockchip/.rk3588-myboard.dtb.d这类依赖文件,强制重新编译。

4.3 rootfs里的fstab和实际分区对不上

rootfs.img里的/etc/fstab定义了启动时如何挂载各个分区。如果fstab里写的设备节点(比如/dev/mmcblk0p6)和实际GPT分区顺序不一致,根文件系统就挂不上。

RK3588的eMMC设备节点通常是/dev/mmcblk0,分区从p1开始。parameter里定义的顺序决定了分区编号:

分区顺序分区名设备节点
1uboot/dev/mmcblk0p1
2trust/dev/mmcblk0p2
3misc/dev/mmcblk0p3
4boot/dev/mmcblk0p4
5recovery/dev/mmcblk0p5
6backup/dev/mmcblk0p6
7rootfs/dev/mmcblk0p7
8userdata/dev/mmcblk0p8

fstab里rootfs那行应该写/dev/mmcblk0p7。如果parameter改了分区顺序,fstab必须同步改。更稳妥的做法是用PARTLABEL或PARTUUID来挂载,不依赖分区编号。

4.4 打包脚本权限与路径问题

mk-firmware.sh和afptool、img_maker这些工具需要可执行权限。从Windows拷贝到Linux、或者从压缩包解压时,权限可能丢失。表现是./build.sh报"Permission denied"。

# 批量恢复权限 chmod +x build.sh chmod +x device/rockchip/common/scripts/*.sh chmod +x tools/*/afptool tools/*/img_maker

另外,SDK路径里不要有中文或空格。afptool对路径中的特殊字符处理不好,路径带空格时打包会静默失败,生成的update.img只有几KB。

4.5 update.img生成后校验失败

烧录工具加载update.img时会校验RKFW头部和CRC。如果校验失败,提示"固件损坏"或"校验错误"。常见原因:

  • img_maker的芯片参数写错
  • 打包过程中某个镜像文件被占用或读取不完整
  • 磁盘空间不足导致写入中断

校验方法:

# 查看update.img头部信息 img_maker -i update.img # 应该输出类似: # Chip: RK3588 # Version: 1.0 # Image count: 8

如果输出的芯片型号不对,或者image count和parameter里的分区数不一致,就需要重新打包。

5. 一套可复现的完整打包流程与验证方法

5.1 从零开始的打包命令序列

假设SDK已经完整编译过一次,现在要重新打包生成update.img。以下是我在实际项目中验证过的命令序列:

# 1. 进入SDK根目录 cd /path/to/rk3588_sdk # 2. 确认板级配置 source device/rockchip/rk3588/rk3588_defconfig # 3. 清理旧的打包产物(可选,但推荐) rm -f rockdev/*.img rm -f update.img # 4. 重新生成boot.img(如果改了kernel或dts) ./build.sh kernel # 5. 重新生成rootfs.img(如果改了根文件系统) ./build.sh rootfs # 6. 执行完整打包 ./build.sh firmware # 7. 检查产物 ls -lh rockdev/ ls -lh update.img

rockdev/目录是SDK约定的镜像输出目录,mk-firmware.sh会从这里收集所有镜像。如果某个镜像不在这个目录,打包时会提示找不到。

5.2 打包产物的完整性检查清单

生成update.img之后,不要急着烧录,先做一轮检查:

检查项命令预期结果
芯片型号img_maker -i update.imgChip: RK3588
分区数量afptool -unpack update.img /tmp/chk/文件数与parameter一致
boot.img大小ls -l /tmp/chk/boot.img不超过分区限制
dtb正确性dtc -I dtb -O dts /tmp/chk/boot.img-dt包含目标板级节点
rootfs可挂载mount -o loop /tmp/chk/rootfs.img /mnt挂载成功,目录结构完整
fstab一致性cat /mnt/etc/fstab分区节点与parameter匹配

这套检查跑下来大概两分钟,但能避免大部分烧录后才发现的问题。

5.3 烧录验证与串口日志的关键观察点

烧录工具(RKDevTool)加载update.img后,选择"升级固件"模式,等待烧录完成。首次启动时,串口日志要重点看这几个阶段:

  1. DDR初始化:出现DDR Version字样,说明uboot.img里的ddr.bin被正确加载。
  2. BL31跳转:出现NOTICE: BL31: v2.x,说明trust.img正常。
  3. U-Boot启动:出现U-Boot 2017.09,说明uboot分区正确。
  4. kernel解压:出现Uncompressing Linux...,说明boot.img的kernel部分正常。
  5. dtb加载:出现Machine model: Rockchip RK3588,说明dtb被正确识别。
  6. 根文件系统挂载:出现EXT4-fs (mmcblk0p7): mounted filesystem,说明rootfs分区和fstab都正确。

如果卡在某个阶段,对应的分区或镜像就有问题。比如卡在BL31之前,检查trust.img;卡在kernel解压之后,检查dtb和bootargs。

5.4 增量打包:只改一个文件时怎么最快出包

实际开发中,大部分时候只改了一两个文件,不需要全量打包。几种常见场景的最快路径:

  • 只改dts:./build.sh kernel→./build.sh firmware,约3分钟。
  • 只改rootfs里某个配置:直接修改buildroot/output/.../target/下的文件,然后./build.sh rootfs→./build.sh firmware,约5分钟。
  • 只改U-Boot:./build.sh uboot→./build.sh firmware,约2分钟。
  • 什么都不改,只重新合成update.img:./build.sh firmware,约30秒。

关键是要知道每个子命令的依赖范围。./build.sh firmware只做收集和合成,不重新编译任何东西,所以最快。

6. 几个容易被忽略但很关键的实操细节

6.1 打包时间的优化:并行编译与缓存利用

RK3588的kernel编译在普通开发机上大概15-20分钟,rootfs(尤其是Ubuntu)可能更久。几个提速手段:

  • ccache:在BoardConfig.mk里设置RK_CCACHE=y,第二次编译能快50%以上。
  • 并行度:export RK_JOBS=$(nproc),让编译脚本用满所有核心。
  • 增量编译:不要每次make clean,只改dts的话kernel编译只重新编译dtb,一分钟内完成。

我自己的开发机上,全量编译一次大概25分钟,但日常改dts的增量打包只要2-3分钟。这个差距在调试阶段非常关键。

6.2 多板级配置的管理:避免defconfig互相覆盖

一个SDK里同时维护多个板级配置时,device/rockchip/rk3588/下会有多个BoardConfig.mk。切换板级用./build.sh <board>_defconfig,但这个命令会覆盖当前的环境变量。

建议的做法是:每个板级配置单独建目录,用脚本切换时同时清理旧的编译产物。否则容易出现"编译的是A板,打包用的是B板的parameter"这种错位。

# 切换板级的推荐流程 ./build.sh rk3588_boardA_defconfig make clean # 或者至少清理kernel和uboot ./build.sh

6.3 固件版本号与发布管理

parameter.txt里的FIRMWARE_VER和img_maker写入的版本号要一致。量产时建议在打包脚本里自动生成版本号,比如用日期加git commit短哈希:

VERSION="$(date +%Y%m%d)-$(git rev-parse --short HEAD)" sed -i "s/FIRMWARE_VER:.*/FIRMWARE_VER: $VERSION/" parameter.txt

这样每个update.img都能追溯到对应的代码版本,出了问题能快速定位。

6.4 打包环境的可复现性

最后说一个容易被忽视的点:打包环境的可复现性。不同机器上的mkfs.ext4版本、dtc版本、甚至locale设置,都可能影响最终镜像。建议用Docker固定打包环境,或者在文档里明确记录工具链版本。

我遇到过在Ubuntu 20.04上打包正常,换到22.04后rootfs镜像大了200MB的情况,原因是mkfs.ext4的默认参数变了。后来在脚本里显式指定了所有参数,才保证跨环境一致。

mkfs.ext4 -F -L rootfs -O ^has_journal -b 4096 -m 0 rootfs.img

-O ^has_journal关闭日志(嵌入式场景通常不需要),-m 0保留0%给root,这些参数能显著减小镜像体积,同时保证不同环境下结果一致。

整套流程走下来,从脚本调用到update.img生成,核心就是理解分区表定义了什么、每个镜像怎么来的、打包工具怎么把它们拼起来。把这三点搞清楚,剩下的就是参数细节和排查经验。我在实际项目里最大的体会是:打包失败不可怕,可怕的是不知道失败在哪一步。串口日志、afptool解包、dtb反编译这三个手段配合使用,基本能定位90%以上的问题。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询