惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 【当耐特】
N
Netflix TechBlog - Medium
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
雷峰网
雷峰网
MongoDB | Blog
MongoDB | Blog
有赞技术团队
有赞技术团队
Engineering at Meta
Engineering at Meta
M
MIT News - Artificial intelligence
Google DeepMind News
Google DeepMind News
罗磊的独立博客
Hugging Face - Blog
Hugging Face - Blog
WordPress大学
WordPress大学
T
Tailwind CSS Blog
小众软件
小众软件
J
Java Code Geeks
人人都是产品经理
人人都是产品经理
博客园_首页
MyScale Blog
MyScale Blog
博客园 - 聂微东
V
Visual Studio Blog
The Cloudflare Blog
月光博客
月光博客
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
U
Unit 42

博客园 - sheldon_blogs

Android设备搭建本地RTSP服务器(基于live555) Android上玩转TUN设备及rtsp传输 Android14 RK3588平台内核异常中断占用CPU问题排查 Android平台移植stress-ng工具及使用 Android OTA的两种方式:Non-A/B(recovery)和A/B系统升级 Android设备之间UVC Gadget bulk模式无法正常打开问题 Android的USB网络共享功能 Linux修改Swap分区大小及使用优先级 Android开放配件 (AOA) 协议 Android(S)系统属性服务详解 Android12 双屏异显/异触流程分析 Android Webview 调试总结 RK3588 Android12 编译打包私有ext4格式vendor.img并挂载到新增vendor_private分区 C++提取字符串中的整数 Android UVC Camera H.265帧序错乱问题 RK3588 Android12 一个固件兼容多个板型方案 全志A133 Android10 Display框架实践 Android编译脚本添加kernel编译选项传入宏定义 UAC实例分析-USB音响 Android USB之复合设备(gadget)详解
RK3588 Android12 rknpu驱动升级及通过fastboot更新
sheldon_blogs · 2026-08-24 · via 博客园 - sheldon_blogs

一、验证环境与最终结果

验证环境:

项目 配置
SoC RK3588S2
开发板 Radxa ROCK 5C
Android Android 12 / arm64
Linux 5.10.160
原 RKNPU 0.8.8,内建于内核
目标 RKNPU 0.9.8,驱动日期 20240828
构建方式 Rockchip AOSP BSP + Clang
部署方式 设备专用 boot 镜像 + U-Boot Fastboot

最终设备能够普通重启进入 Android,并报告:

Linux 5.10.160 #57
RKNPU driver: v0.9.8
rknpu 0.9.8 20240828

整个过程只更新了 boot 分区,没有写入其他分区。

二、第一部分:升级 RK3588 RKNPU 驱动

1. 确认当前驱动版本

先在 Android 设备上读取当前状态:

adb -s <DEVICE> root
adb -s <DEVICE> wait-for-device
adb -s <DEVICE> shell cat /sys/kernel/debug/rknpu/version
adb -s <DEVICE> shell "dmesg | grep -m1 'Initialized rknpu'"

本次设备最初返回:

RKNPU driver: v0.8.8

还需要确认驱动是内建内核还是独立模块:

adb -s <DEVICE> shell \
  "zcat /proc/config.gz | grep CONFIG_ROCKCHIP_RKNPU"

结果为:

CONFIG_ROCKCHIP_RKNPU=y

=y 表示 RKNPU 已编进 kernel Image,没有可单独卸载和替换的 .ko
因此本次升级必须重新编译 kernel,并通过 boot 分区部署。

如果设备没有 /proc/config.gz,可以检查 AOSP 内核最终 .config,或者在
源码配置片段中搜索同一个配置项。

2. 准备 0.9.8 驱动源码

使用与 RKLLM SDK 同版本提供的官方驱动归档:

rknpu-driver/rknpu_driver_0.9.8_20241009.tar.bz2

先校验压缩包:

sha256sum \
  <RKNN_LLM_SDK>/rknpu-driver/rknpu_driver_0.9.8_20241009.tar.bz2

本次 SDK 1.3.0 归档的 SHA-256 为:

188306a3eba7ca48c186fe2a39acf232dec8536484581e54e9b96c3f65677eba

不同 SDK 发布版本应以自己的官方文件为准,不要看到文件名相同就跳过校验。

3. 备份原驱动并检查工作区

在修改内核前先检查 Git 状态:

cd <AOSP_ROOT>
git -C kernel-5.10 status --short

不要覆盖或清理其他开发者的改动。然后备份当前 RKNPU 0.8.8 源码:

tar -cjf <BACKUP_DIR>/rknpu-before-0.9.8.tar.bz2 \
  -C kernel-5.10/drivers rknpu

解压官方归档:

mkdir -p <WORK_DIR>/rknpu-0.9.8
tar -xjf \
  <RKNN_LLM_SDK>/rknpu-driver/rknpu_driver_0.9.8_20241009.tar.bz2 \
  -C <WORK_DIR>/rknpu-0.9.8

归档中包含完整的 drivers/rknpu。升级时应整体更新驱动目录,而不是只修改
报告版本号的头文件,也不要混用 0.8.8 和 0.9.8 的零散文件。

4. 处理 Linux 5.10 BSP API 差异

官方 0.9.8 驱动与当前 Rockchip Android 12 / Linux 5.10 BSP 存在三处
编译接口差异:

  • MONITOR_TYPE_DEV 在当前 BSP 中拼写为 MONITOR_TPYE_DEV
  • RK3576 使用的 rockchip_opp_set_low_length 回调不在当前 5.10 OPP
    接口中,而且 RK3588 目标不使用它;
  • 新内核的 vm_flags_set()vm_flags_clear() 在当前 5.10 BSP 中需要
    改为等价的位操作。

项目提供了最小兼容补丁:

cd <AOSP_ROOT>/kernel-5.10
git apply --check \
  <PROJECT_ROOT>/patches/rknpu-0.9.8-android12-linux5.10.patch
git apply \
  <PROJECT_ROOT>/patches/rknpu-0.9.8-android12-linux5.10.patch
git diff --check -- drivers/rknpu

补丁只解决编译期 API 差异,没有删除或绕过 RK3588 的 OPP、任务提交和 NPU
执行路径。

5. 使用产品原配置编译内核

本次产品使用:

kernel: kernel-5.10
config: rockchip_defconfig android-11.config rock5c.config
DTS:    rk3588s-rock-5c

编译命令:

cd <AOSP_ROOT>
export PATH="$PWD/prebuilts/clang/host/linux-x86/clang-r416183b/bin:$PATH"

make -C kernel-5.10 clean
make -C kernel-5.10 \
  CROSS_COMPILE=aarch64-linux-gnu- \
  LLVM=1 LLVM_IAS=1 ARCH=arm64 \
  rockchip_defconfig android-11.config rock5c.config

make -C kernel-5.10 \
  CROSS_COMPILE=aarch64-linux-gnu- \
  LLVM=1 LLVM_IAS=1 ARCH=arm64 \
  rk3588s-rock-5c.img -j8

关键产物:

文件 用途
kernel-5.10/arch/arm64/boot/Image 包含内建 RKNPU 0.9.8 的 kernel
kernel-5.10/vmlinux 带符号内核,用于版本和符号检查
kernel-5.10/resource.img DTS/resource 产物
kernel-5.10/boot.img 内核目标生成物,不一定是完整 Android boot

检查源码和链接结果:

grep -E 'DRIVER_(DATE|MAJOR|MINOR|PATCHLEVEL)' \
  kernel-5.10/drivers/rknpu/include/rknpu_drv.h
strings kernel-5.10/vmlinux | grep -F '0.9.8'
sha256sum kernel-5.10/arch/arm64/boot/Image

到这里完成的是“新内核编译”,还没有得到可以安全刷入真机的完整 boot 镜像。

三、第二部分:Windows 无法识别 Fastboot 设备

1. 典型现象

串口日志已经显示:

Enter fastboot...OK

但 Windows 执行:

fastboot devices -l

没有任何输出。

U-Boot 中同时出现的:

ethernet address not set
No ethernet found

描述的是未使用的以太网启动路径,并不能证明 USB Fastboot 失败。应先检查
Windows 是否枚举了 USB gadget。

2. 检查 Windows PnP 状态

Get-PnpDevice -PresentOnly |
  Where-Object { $_.InstanceId -match 'VID_18D1&PID_D00D' } |
  Format-List Status,Class,FriendlyName,InstanceId,Problem

本次设备显示为:

FriendlyName: USB download gadget
Hardware ID:  USB\VID_18D1&PID_D00D
Problem:      CM_PROB_FAILED_INSTALL / code 28

这说明 U-Boot 已经通过 USB 把 gadget 暴露给 Windows,线材和 U-Boot USB
路径基本正常,但 Windows 没有绑定可供 Fastboot 使用的驱动。

3. 为什么常见 Rockchip 驱动没有解决

常见 Rockchip USB 驱动主要匹配 VID_2207,而本次 U-Boot Fastboot gadget
使用 Google VID/PID:

18D1:D00D

Google USB Driver 的部分版本也没有直接列出 PID_D00D。不建议编辑签名
INF,或者为了添加一个硬件 ID 而关闭 Windows 驱动签名验证。

4. 使用 Zadig 绑定 WinUSB

操作步骤:

  1. 以管理员权限运行 Zadig;
  2. 选择 USB download gadget
  3. 再次确认 USB ID 是 18D1:D00D
  4. 选择 WinUSB
  5. 安装或替换驱动;
  6. 在设备管理器确认服务为 WinUSB,问题代码为 0。

不要选择串口适配器、键盘、USB Hub 或存储设备,也不要将目标换成
libusbKlibusb-win32

5. WinUSB 正常,但 fastboot 仍然没有设备

本次绑定 WinUSB 后,设备管理器已经显示正常,但 AOSP fastboot.exe 仍然
看不到设备。

原因是 Zadig 为 WinUSB 创建了随机 DeviceInterfaceGUIDs,而 AOSP Windows
Fastboot transport 通过固定 Android USB 接口 GUID 枚举设备:

{F72FE0D4-CBCB-407D-8814-9ED673D0DD6B}

下面先给出不依赖项目脚本的手动操作。所有 PowerShell 命令都必须在“以
管理员身份运行”的窗口中执行。

第一步,查找当前连接的 18D1:D00D 设备。正常情况下只能找到一个:

$devices = @(
    Get-PnpDevice -PresentOnly |
        Where-Object { $_.InstanceId -match 'VID_18D1&PID_D00D' }
)
$devices | Format-List Status,Class,FriendlyName,InstanceId,Problem

如果返回零个设备,应回到 USB 枚举、线材或 U-Boot Fastboot 状态继续排查;
如果返回多个设备,不要继续修改注册表,先断开无关设备。确认只有一个目标后:

$instanceId = $devices[0].InstanceId

第二步,确认 Zadig 已经为它绑定 WinUSB。返回值必须是 WinUSB

$service = (
    Get-PnpDeviceProperty -InstanceId $instanceId |
        Where-Object KeyName -eq 'DEVPKEY_Device_Service'
).Data
$service

如果不是 WinUSB,先停止操作并回到上一节重新安装正确驱动。

第三步,定位设备参数注册表项,并查看已有 GUID:

$registrySubKey =
    'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\' +
    $instanceId + '\Device Parameters'
$registryPath = 'Registry::' + $registrySubKey

Get-ItemProperty -LiteralPath $registryPath |
    Select-Object -ExpandProperty DeviceInterfaceGUIDs

第四步,在修改前导出注册表备份。这里使用当前用户的临时目录,不依赖工程
路径:

$backupDirectory = Join-Path $env:TEMP 'rk3588-fastboot-driver-backup'
New-Item -ItemType Directory -Force -Path $backupDirectory | Out-Null
$backupFile = Join-Path $backupDirectory `
    'VID_18D1_PID_D00D-device-parameters-before-android-guid.reg'

reg.exe export $registrySubKey $backupFile /y
if ($LASTEXITCODE -ne 0) {
    throw '注册表备份失败,停止修改。'
}
$backupFile

第五步,保留 Zadig 已创建的 GUID,并追加 AOSP Android USB GUID:

$androidInterfaceGuid = '{F72FE0D4-CBCB-407D-8814-9ED673D0DD6B}'
$currentGuids = @(
    (Get-ItemProperty -LiteralPath $registryPath).DeviceInterfaceGUIDs
)
$updatedGuids = @(
    $currentGuids + $androidInterfaceGuid | Sort-Object -Unique
)

New-ItemProperty -LiteralPath $registryPath `
    -Name DeviceInterfaceGUIDs `
    -PropertyType MultiString `
    -Value $updatedGuids `
    -Force | Out-Null

不要用新 GUID 覆盖原数组。DeviceInterfaceGUIDs 是多字符串值,正确操作是
保留原值后追加并去重。

第六步,只重启目标 PnP 设备,再验证 GUID 和 Fastboot:

pnputil.exe /restart-device $instanceId
Start-Sleep -Seconds 3

Get-ItemProperty -LiteralPath $registryPath |
    Select-Object -ExpandProperty DeviceInterfaceGUIDs
fastboot devices -l

输出的 GUID 列表应包含 AOSP GUID,fastboot devices -l 应能列出目标设备。
如果希望以后重复使用同一套安全检查,本文附录还提供了完整 PowerShell 脚本,
它执行的就是以上步骤。

6. Fastboot 识别后的只读检查

所有命令都显式指定设备,避免连接多台设备时操作错误目标:

fastboot devices -l
fastboot -s <DEVICE> getvar product
fastboot -s <DEVICE> getvar partition-size:boot
fastboot -s <DEVICE> getvar has-slot:boot
fastboot -s <DEVICE> getvar max-download-size

本次读取结果表明:

  • 产品与目标开发板一致;
  • boot 分区为 40 MiB;
  • has-slot:bootno
  • 最大下载尺寸大于候选镜像。

四、第三部分:通过 Fastboot 更新 boot 分区中的 RKNPU 驱动

1. 不要直接烧录内核目录的 boot.img

本次发现三个名称相似、来源却完全不同的文件:

文件 实际来源 实际大小 本次结论
kernel-5.10/boot.img 内核目标生成物 小于完整产品镜像 缺少 Android 产品 ramdisk,不能直接刷
AOSP 产品 out/.../boot.img 当前产品配置重新构建 82,679,808 字节 大于真机 boot 分区,且 ramdisk 不同
boot-rknpu-0.9.8-candidate.img 真机 boot 为母版,只替换 kernel 39,505,920 字节 离线校验和真机刷写均通过

真机 boot 分区只有 41,943,040 字节,即 40 MiB。因此前两个镜像都不能
直接用于当前设备。文件名同为 boot.img 并不表示内部结构或分区布局一致。

判断 boot 镜像能否使用,不能只看文件名或“编译成功”,必须比较:

  • 真实分区容量;
  • Android boot header version;
  • kernel、ramdisk、second/resource 和 DTB;
  • cmdline、page size 和各项 offset;
  • 当前设备是否使用 A/B slot。

2. 完整备份真机 boot 分区

在已授权的 userdebug 设备中:

adb -s <DEVICE> root
adb -s <DEVICE> wait-for-device
adb -s <DEVICE> shell \
  "mkdir -p /data/local/tmp/rknpu-boot-backup"
adb -s <DEVICE> shell \
  "blockdev --getsize64 /dev/block/by-name/boot"
adb -s <DEVICE> shell \
  "dd if=/dev/block/by-name/boot \
   of=/data/local/tmp/rknpu-boot-backup/boot-before-rknpu.img bs=4M"
adb -s <DEVICE> shell \
  "sha256sum \
   /data/local/tmp/rknpu-boot-backup/boot-before-rknpu.img"
adb -s <DEVICE> pull \
  /data/local/tmp/rknpu-boot-backup/boot-before-rknpu.img \
  <LOCAL_BACKUP_DIR>/boot-before-rknpu.img

PC 再计算一次 SHA-256,必须与设备一致。该文件是完整分区回滚备份,可能
包含设备特定信息,应保存在 Git 和无关服务器之外。

为复核本文步骤,升级完成后又对当前设备执行了一次相同的只读导出。公开记录
不保存设备序列号,命令通过 ADB 自动取得当前唯一在线设备:

$devices = @(
    adb devices |
        Select-String '\tdevice$' |
        ForEach-Object { ($_ -split '\s+')[0] }
)
if ($devices.Count -ne 1) {
    throw "预期一台在线 ADB 设备,实际为 $($devices.Count) 台。"
}
$device = $devices[0]

adb -s $device root
adb -s $device wait-for-device
adb -s $device shell getprop ro.product.device
adb -s $device shell getprop ro.build.version.release
adb -s $device shell readlink -f /dev/block/by-name/boot
adb -s $device shell blockdev --getsize64 /dev/block/by-name/boot
adb -s $device shell cat /sys/kernel/debug/rknpu/version

当前设备实际返回:

RadxaRock5C
12
/dev/block/mmcblk1p7
41943040
RKNPU driver: v0.9.8

本次复核导出的 40 MiB 当前 boot 镜像,设备端与 PC 端 SHA-256 均为:

9a0f4d909429ab8e480f3346bbb859e5efd0f6a6d89066d0c4c5fd6f9be2be1a

这个哈希对应升级后的完整 live boot 分区;回滚时仍应使用升级前保存的原始
0.8.8 完整备份,不能拿当前 0.9.8 镜像冒充回滚镜像。

3. 以真机镜像为母版构建候选

使用当前 AOSP 源码树自带且版本配套的 unpack_bootimg.py
mkbootimg.py。它们通常位于:

<AOSP_ROOT>/system/tools/mkbootimg/unpack_bootimg.py
<AOSP_ROOT>/system/tools/mkbootimg/mkbootimg.py

不要从不明网站下载同名脚本,也不要跨 Android 大版本混用工具。先建立工作
目录并解包真机备份,同时保存工具输出:

mkdir -p <WORK_DIR>/original <WORK_DIR>/candidate-check
python <TOOLS_DIR>/unpack_bootimg.py \
  --boot_img <LOCAL_BACKUP_DIR>/boot-before-rknpu.img \
  --out <WORK_DIR>/original \
  --format=mkbootimg | tee <WORK_DIR>/original-mkbootimg-args.txt

unpack_bootimg.py 的终端输出中会给出与当前镜像匹配的完整 mkbootimg
参数。先打开并保存它:

cat <WORK_DIR>/original-mkbootimg-args.txt

保留原镜像的:

  • ramdisk;
  • second/resource;
  • DTB;
  • header version;
  • cmdline;
  • page size、base 和 offset。

手动复制工具输出的整条 mkbootimg 参数,把其中的 --kernel 改成新编译的:

kernel-5.10/arch/arm64/boot/Image

本次设备实际解包输出如下。路径已替换为公开占位符,其余数值均来自当前
真机,不是示例猜测值:

--header_version 2
--os_version 12.0.0
--os_patch_level 2022-07
--kernel <ORIGINAL_DIR>/kernel
--ramdisk <ORIGINAL_DIR>/ramdisk
--second <ORIGINAL_DIR>/second
--dtb <ORIGINAL_DIR>/dtb
--pagesize 0x00000800
--base 0x00000000
--kernel_offset 0x10008000
--ramdisk_offset 0x11000000
--second_offset 0x10f00000
--tags_offset 0x10000100
--dtb_offset 0x0000000011f00000
--board ''
--cmdline 'console=ttyFIQ0 firmware_class.path=/vendor/etc/firmware init=/init rootwait ro loop.max_part=7 androidboot.console=ttyFIQ0 androidboot.wificountrycode=CN androidboot.hardware=rk30board androidboot.boot_devices=fe2c0000.mmc,fe2e0000.mmc androidboot.selinux=permissive'

因此,这台 RK3588S2 / ROCK 5C 生成候选镜像时使用的完整命令如下。执行前
只需把前两行改成自己的源码目录和私有工作目录:

AOSP_ROOT="/path/to/android-source"
WORK_DIR="/path/to/private-rknpu-boot-work"
TOOLS_DIR="$AOSP_ROOT/system/tools/mkbootimg"
NEW_KERNEL_IMAGE="$AOSP_ROOT/kernel-5.10/arch/arm64/boot/Image"

python "$TOOLS_DIR/mkbootimg.py" \
  --header_version 2 \
  --os_version 12.0.0 \
  --os_patch_level 2022-07 \
  --kernel "$NEW_KERNEL_IMAGE" \
  --ramdisk "$WORK_DIR/original/ramdisk" \
  --second "$WORK_DIR/original/second" \
  --dtb "$WORK_DIR/original/dtb" \
  --pagesize 0x00000800 \
  --base 0x00000000 \
  --kernel_offset 0x10008000 \
  --ramdisk_offset 0x11000000 \
  --second_offset 0x10f00000 \
  --tags_offset 0x10000100 \
  --dtb_offset 0x0000000011f00000 \
  --board '' \
  --cmdline 'console=ttyFIQ0 firmware_class.path=/vendor/etc/firmware init=/init rootwait ro loop.max_part=7 androidboot.console=ttyFIQ0 androidboot.wificountrycode=CN androidboot.hardware=rk30board androidboot.boot_devices=fe2c0000.mmc,fe2e0000.mmc androidboot.selinux=permissive' \
  --output "$WORK_DIR/boot-rknpu-0.9.8-candidate.img"

如果工具输出中没有 --second--dtb 或某个 offset,就不要自行添加;如果
存在 --recovery_dtbo 等参数,则必须原样保留。原则是只改 --kernel
--output 两项,其余参数以及 ramdisk、second/resource、DTB 都沿用真机
备份。

本次实际组件记录:

组件 字节数 SHA-256
kernel 37,589,000 bd6bf21edb6ab9f5af9fe31b2c1a8855e8ddf095af9953a66675041ea6d11df0
ramdisk 1,351,166 c3d95c0209d239c85b3f56eac7c24937d2e8414bea781341c66588abf7b6099d
second 324,096 a8f92fd17658b112e21a3725c30730e56ef0ea304d8c3f16b5acb6d3dcd5bc7d
dtb 235,345 9dd391c1d8a10142f658ce70119b31fb6eb43b0d583240b6ac31ce7dde0d43b0

使用上述参数重新封装后得到:

文件:boot-rknpu-0.9.8-candidate.img
长度:39,505,920 字节
SHA-256:1a42fe060cf755bb306c620bc34f8c4826b642bedcffcbf5df8b476117dc8ff4

2026-08-24 再次从当前真机导出 boot、解包并按上述命令重新封装,得到的长度
和 SHA-256 与此前实际刷入的候选镜像完全一致,证明这组参数可以复现本次
候选镜像。

4. 重新解包并逐项验证

python <TOOLS_DIR>/unpack_bootimg.py \
  --boot_img <WORK_DIR>/boot-rknpu-0.9.8-candidate.img \
  --out <WORK_DIR>/candidate-check \
  --format=mkbootimg

cmp <NEW_KERNEL_IMAGE> <WORK_DIR>/candidate-check/kernel
cmp <WORK_DIR>/original/ramdisk \
  <WORK_DIR>/candidate-check/ramdisk
cmp <WORK_DIR>/original/second \
  <WORK_DIR>/candidate-check/second
cmp <WORK_DIR>/original/dtb \
  <WORK_DIR>/candidate-check/dtb
sha256sum <WORK_DIR>/boot-rknpu-0.9.8-candidate.img

如果原镜像包含 recovery_dtbo,也要进行相同比较。所有适用的 cmp 都应
成功,只有 kernel 可以发生变化。

本次容量结果:

真机 boot 分区:41,943,040 字节
候选 boot 镜像:39,505,920 字节
剩余空间:       2,437,120 字节

5. 正式写入前尝试一次性启动

在已确认物理恢复方式的情况下,优先尝试:

fastboot -s <DEVICE> boot <CANDIDATE_BOOT_IMG>

本次主机返回:

Sending 'boot' OKAY
Booting OKAY

但设备最终仍运行旧 RKNPU 0.8.8。Pstore 表明 U-Boot 的 do_bootm() 返回
后复位,随后从旧 boot 分区启动。运行 kernel 大小也与候选不同。

因此 Fastboot OKAY 不能作为候选内核已经运行的证据。每次一次性启动后
必须在设备端核对:

adb -s <DEVICE> shell uname -a
adb -s <DEVICE> shell cat /sys/kernel/debug/rknpu/version
adb -s <DEVICE> shell "dmesg | grep -m1 'Initialized rknpu'"

6. 临时启动后再次验证分区没有变化

正式烧录前重新读取整个 live boot 分区的 SHA-256,并与最初备份比较:

adb -s <DEVICE> root
adb -s <DEVICE> wait-for-device
adb -s <DEVICE> shell \
  "sha256sum /dev/block/by-name/boot"

本次哈希完全一致,证明失败的一次性启动没有静默修改 boot 分区。

7. 正式写入 boot 分区

这是持久化且可能导致设备无法启动的操作。执行前必须确认:

  • 设备 product 正确;
  • boot 分区和 slot 布局正确;
  • 候选小于分区;
  • 完整回滚备份可用;
  • 物理 Loader/Fastboot 恢复路径可用;
  • 本次只计划写 boot;
  • 已获得明确烧录授权。

下面是本次设备对应的完整 PowerShell 参考命令。公开文档不硬编码 Fastboot
序列号,而是要求当前只连接一台设备;执行前只需修改候选镜像路径:

$candidate = 'C:\path\to\boot-rknpu-0.9.8-candidate.img'
$expectedLength = 39505920
$expectedSha256 =
    '1a42fe060cf755bb306c620bc34f8c4826b642bedcffcbf5df8b476117dc8ff4'

if (-not (Test-Path -LiteralPath $candidate)) {
    throw "候选镜像不存在:$candidate"
}
$candidateFile = Get-Item -LiteralPath $candidate
$candidateHash = (
    Get-FileHash -Algorithm SHA256 -LiteralPath $candidate
).Hash.ToLowerInvariant()
if ($candidateFile.Length -ne $expectedLength) {
    throw "候选镜像长度不符:$($candidateFile.Length)"
}
if ($candidateHash -ne $expectedSha256) {
    throw "候选镜像 SHA-256 不符:$candidateHash"
}

$fastbootDevices = @(
    fastboot devices |
        ForEach-Object { ($_ -split '\s+')[0] } |
        Where-Object { $_ }
)
if ($fastbootDevices.Count -ne 1) {
    throw "预期一台 Fastboot 设备,实际为 $($fastbootDevices.Count) 台。"
}
$fastbootDevice = $fastbootDevices[0]

fastboot -s $fastbootDevice getvar product
fastboot -s $fastbootDevice getvar has-slot:boot
fastboot -s $fastbootDevice getvar partition-size:boot

本次设备应返回非 A/B boot,即 has-slot:boot: no;boot 分区容量为
0x2800000,即 41,943,040 字节。确认设备身份和两项结果正确后,才执行真正
的写入命令:

fastboot -s $fastbootDevice flash boot $candidate
if ($LASTEXITCODE -ne 0) {
    throw 'boot 分区写入失败,禁止继续执行自动重启。'
}

这条命令中的第一个 boot 是分区名,$candidate 是完整 Android boot
候选镜像。这里不能把 $candidate 改成裸 Imagekernel-5.10/boot.img
或超出分区容量的 out/.../boot.img

本次 Fastboot 返回:

Sending 'boot' OKAY
Writing 'boot' OKAY

没有写入 vendor、system、recovery、vbmeta 或其他分区。

8. 写入后设备停在 Fastboot/U-Boot

首次 fastboot reboot 因保留的 bootloader reason 又进入 Fastboot;
fastboot continue 后设备停在 U-Boot 提示符。

此时通过串口只读查看环境:

bootcmd=boot_android ${devtype} ${devnum};...
devtype=mmc
devnum=0

确认当前产品配置后执行:

boot_android mmc 0

该命令只启动已经写入 mmc 0 的 Android boot 分区,不会再次写入存储。
其他板卡的存储编号和 U-Boot 命令可能不同,不能直接照抄。

9. 验证新驱动实际运行

Android 启动后:

adb -s <DEVICE> wait-for-device
adb -s <DEVICE> shell getprop sys.boot_completed
adb -s <DEVICE> root
adb -s <DEVICE> wait-for-device
adb -s <DEVICE> shell uname -a
adb -s <DEVICE> shell cat /sys/kernel/debug/rknpu/version
adb -s <DEVICE> shell "dmesg | grep -m1 'Initialized rknpu'"

本次返回:

Linux localhost 5.10.160 #57 ... aarch64
RKNPU driver: v0.9.8
[drm] Initialized rknpu 0.9.8 20240828 ...

10. 普通重启验证持久性

串口启动成功只证明这一次能够运行。还必须执行普通 Android 重启:

adb -s <DEVICE> reboot
adb -s <DEVICE> wait-for-device
adb -s <DEVICE> shell getprop sys.boot_completed
adb -s <DEVICE> shell cat /sys/kernel/debug/rknpu/version

本次设备无需 Fastboot 或串口干预,自动进入 Android,RKNPU 仍为 0.9.8。
至此才能确认 boot 分区更新持久有效。

11. 回滚准备

如果新 boot 无法启动:

  1. 通过已经验证的物理 Loader 或 U-Boot Fastboot 进入恢复状态;
  2. 重新确认设备身份、boot 分区和 slot;
  3. 校验原始完整 boot 分区备份;
  4. 将备份写回正确 boot 分区;
  5. 重启并验证原 kernel/RKNPU 版本;
  6. 保存失败镜像、串口和 pstore 日志,离线分析。

不要在设备身份、分区或备份完整性不明确时尝试回写。

五、完整操作顺序总结

RKNPU 源码升级

检查设备驱动版本和 CONFIG
  -> 备份原 drivers/rknpu
  -> 解压官方 0.9.8 完整驱动
  -> 应用 Linux 5.10 最小兼容补丁
  -> 使用产品 config/DTS 编译
  -> 从 vmlinux 和 Image 验证 0.9.8

Fastboot 无法识别

串口确认 U-Boot Fastboot 已运行
  -> Windows PnP 检查 18D1:D00D
  -> Zadig 只为目标 gadget 绑定 WinUSB
  -> 检查 DeviceInterfaceGUIDs
  -> 备份注册表并追加 AOSP Android USB GUID
  -> fastboot devices/getvar 只读验证

boot 分区更新

读取真实 boot 分区容量
  -> 完整备份并双端校验哈希
  -> 解包真机 boot
  -> 只替换 kernel
  -> 重新解包逐项比较
  -> 校验候选容量和 Fastboot slot
  -> 尝试一次性 boot 并从设备端判断
  -> 再次确认 live boot 哈希未变
  -> 获得授权后只 flash boot
  -> 串口/Android 验证 RKNPU 0.9.8
  -> 普通 reboot 验证持久性

六、附录:Fastboot GUID 修复完整脚本

如果不想逐条输入第三部分第 5 节的 PowerShell 命令,可将下面内容保存为
fix-windows-fastboot-guid.ps1,然后在管理员 PowerShell 中运行:

.\fix-windows-fastboot-guid.ps1

脚本默认自动寻找当前唯一的 18D1:D00D 设备。也可以显式指定设备管理器中
看到的 Instance ID:

.\fix-windows-fastboot-guid.ps1 -InstanceId '<INSTANCE_ID>'

完整源码如下:

[CmdletBinding()]
param(
    [string]$InstanceId,
    [string]$BackupDirectory = (
        Join-Path $env:TEMP 'rk3588-fastboot-driver-backup'
    )
)

$ErrorActionPreference = 'Stop'

$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
$principal = [Security.Principal.WindowsPrincipal]::new($identity)
$isAdmin = $principal.IsInRole(
    [Security.Principal.WindowsBuiltInRole]::Administrator
)
if (-not $isAdmin) {
    throw '请在管理员 PowerShell 窗口中运行此脚本。'
}

if ([string]::IsNullOrWhiteSpace($InstanceId)) {
    $matches = @(
        Get-PnpDevice -PresentOnly |
            Where-Object { $_.InstanceId -match 'VID_18D1&PID_D00D' }
    )
    if ($matches.Count -ne 1) {
        throw "预期找到一个 18D1:D00D 设备,实际找到 $($matches.Count) 个。"
    }
    $InstanceId = $matches[0].InstanceId
}

$targetDevice = Get-PnpDevice `
    -InstanceId $InstanceId `
    -ErrorAction Stop
if ($targetDevice.InstanceId -notmatch 'VID_18D1&PID_D00D') {
    throw "拒绝修改非目标 USB 设备:$($targetDevice.InstanceId)"
}

$deviceProperties = Get-PnpDeviceProperty -InstanceId $InstanceId
$service = (
    $deviceProperties |
        Where-Object KeyName -eq 'DEVPKEY_Device_Service'
).Data
if ($service -ne 'WinUSB') {
    throw "预期设备服务为 WinUSB,实际为:$service"
}

$androidInterfaceGuid = '{F72FE0D4-CBCB-407D-8814-9ED673D0DD6B}'
$registrySubKey =
    'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\' +
    $InstanceId + '\Device Parameters'
$registryPath = 'Registry::' + $registrySubKey
if (-not (Test-Path -LiteralPath $registryPath)) {
    throw "未找到目标注册表项:$registrySubKey"
}

New-Item `
    -ItemType Directory `
    -Force `
    -Path $BackupDirectory | Out-Null
$backupFile = Join-Path `
    -Path $BackupDirectory `
    -ChildPath (
        'VID_18D1_PID_D00D-' +
        'device-parameters-before-android-guid.reg'
    )
& reg.exe export $registrySubKey $backupFile /y
if ($LASTEXITCODE -ne 0) {
    throw '注册表备份失败,未继续修改。'
}

$currentGuids = @(
    (Get-ItemProperty -LiteralPath $registryPath).DeviceInterfaceGUIDs
)
$updatedGuids = @(
    $currentGuids + $androidInterfaceGuid |
        Sort-Object -Unique
)

New-ItemProperty `
    -LiteralPath $registryPath `
    -Name DeviceInterfaceGUIDs `
    -PropertyType MultiString `
    -Value $updatedGuids `
    -Force | Out-Null

& pnputil.exe /restart-device $InstanceId
if ($LASTEXITCODE -ne 0) {
    throw 'PnP 设备重启失败。'
}

Start-Sleep -Seconds 3

Write-Output 'Fastboot Android 接口 GUID 更新完成。'
Write-Output "注册表备份:$backupFile"
Get-ItemProperty -LiteralPath $registryPath |
    Select-Object -ExpandProperty DeviceInterfaceGUIDs

if (Get-Command fastboot -ErrorAction SilentlyContinue) {
    fastboot devices -l
}

脚本中的三项保护不要删除:限定 18D1:D00D、核对 WinUSB 服务、修改前
导出注册表。它们用于降低误修改其他 USB 设备的风险。

七、结语

RK3588 RKNPU 驱动升级真正困难的地方,不是修改版本号,而是保证三个条件:

  1. 新驱动确实按当前 BSP 接口编译进了运行中的 kernel;
  2. Fastboot 主机和设备端都能明确识别同一目标;
  3. 写入的 boot 镜像与真机 ramdisk、DTB、header 和分区布局完全匹配,并且
    有可验证的回滚路径。

只要始终坚持“先只读验证、再生成候选、设备端证明、最后持久化写入”,就能
把一次高风险的内核升级拆解成可检查、可复现、可回滚的工程流程。

更完整的逐步操作和故障表可参考:

  • docs/04-rknpu-driver-compatibility.md
  • docs/05-local-llm-deployment-guide-zh.md