改硬件几乎都从设备树开始。今天讲为什么你改了源文件、编译也成功,板上却没变化——开机往往没用你以为的那一份。

&hdmi { status = "okay"; }; 写进板级 dts,全量构建 exit 0,boot_linux 刷进去了。板上:

# cat /proc/device-tree/hdmi@fe0a0000/status
disabled

或者更气人:status 已经是 okay,uart0 仍然没有 /dev/ttyS0,手动 bind 才打出 pin gpio0-17 already requested by vcc-camera-regulator。设备树「写了」和「内核拿到的 dtb 里是这个值」和「probe 时这根脚还在」,是三件事。

开源鸿蒙在这块 RK3568 上,设备树还是 Linux 那套。多出来的痛苦在缓存:GN 不跟踪板级 dts,checkpoint 不跟踪 device/board/,U-Boot 真正交给内核的是 resource 分区里的 rk-kernel.dtb。三层任何一层没动,你改的那份源码只是源码。

产品名 rk3568_evb,板级 device/board/rk/rk3568_evb/kernel/。官方语法:设备树。下面按「为什么板上不是我写的」写。


1. include 链:你改的是最后那一环

编译 dts 时,cpp 先展开 include,再交给 dtc。RK3568 常见链:

rk3568.dtsi                 SoC:CPU、总线、uart、i2c、hdmi、vop 的寄存器和时钟
    └── rk3568-pinctrl.dtsi  引脚功能组 uart0-xfer、i2c1-xfer、...
rk3568-evb.dtsi             公版 EVB:regulator、HDMI、若干 always-on
    └── 板级 rk3568-evb-linux.dts   你的板:okay/disabled、抢脚删除、扩展模块

板级文件开头通常是:

/dts-v1/;
#include "rk3568.dtsi"
#include "rk3568-pinctrl.dtsi"
#include "rk3568-evb.dtsi"

/ {
    model = "RK3568 EVB OpenHarmony";
    compatible = "rockchip,rk3568-evb", "rockchip,rk3568";
    chosen {
        bootargs = "console=ttyS2,1500000n8 androidboot.hardware=rk30board \
                    hardware=rk30board earlycon=uart8250,mmio32,0xfe660000";
    };
};

SoC 级节点默认经常是 disabled。公版 EVB dtsi 打开它自己需要的:HDMI、若干 regulator、camera 供电。板级再用 &label 覆写。你不写,不等于没有。 公版已经 okay 的 HDMI,会在你打开 LVDS 的同时去抢主显示。你不写,不等于脚是空的——vcc_camera 已经占用 GPIO0_C1。

查最终展开,不要只读板级文件:

# 在 src_tmp 工作树
cd out/kernel/src_tmp/linux-5.10
export ARCH=arm64
# 预处理
clang -E -nostdinc -I include -I arch/arm64/boot/dts/rockchip \
      -undef -D__DTS__ -x assembler-with-cpp \
      arch/arm64/boot/dts/rockchip/rk3568-evb-linux.dts \
      -o /tmp/evb.pre.dts
# 或直接反编译产物
dtc -I dtb -O dts out/kernel/OBJ/linux-5.10/arch/arm64/boot/dts/rockchip/rk3568-evb-linux.dtb \
    2>/dev/null > /tmp/live.dts

/tmp/live.dts 里才是「内核将要看到的」。板级源码只是输入之一。

文件名按你树里的实际 dts 为准。build_kernel.sh 会把 device/board/rk/rk3568_evb/kernel/*.dts 拷到 arch/arm64/boot/dts/rockchip/。权威在板级目录,src_tmp 是副本。


2. &label 覆写,以及 /delete-property/

dtsi 里给节点起了 label:

uart0: serial@fdd50000 {
    compatible = "rockchip,dw-apb-uart";
    status = "disabled";
    pinctrl-names = "default";
    pinctrl-0 = <&uart0_xfer>;
};

板级不要把整个节点再抄一遍。引用 label:

&uart0 {
    status = "okay";
};

合并规则:后出现的属性覆盖先出现的。status 被改成 okay。没提到的 pinctrl-0 保留。

要删掉公版加的属性,用 /delete-property/,不要写成空值:

&vcc_camera {
    /delete-property/ gpio;
    /delete-property/ pinctrl-names;
    /delete-property/ pinctrl-0;
    /delete-property/ enable-active-high;
};

写成 gpio; 或 gpio = <>; 经常仍留下一个属性,pinctrl 子系统照样 claim 这根脚。/delete-node/ 删整节点,regulator 还有别人引用时不要整段删,只删抢脚的那些属性。

/delete-property/ 必须出现在引用节点里,对已经合并进 blob 的属性生效。写在新节点里删不掉旧节点的属性。

覆盖 pinctrl 示例:

&i2c1 {
    status = "okay";
    clock-frequency = <100000>;   /* MLX90614 一类器件 400k 不稳 */
};

&hdmi {
    status = "disabled";          /* 要亮 LVDS / MIPI / RGB 时关掉它 */
};

同一时刻只允许一路显示 okay。公版 EVB 常把 HDMI 打开。你只加了 LVDS 节点、没关 HDMI,桌面跑到 HDMI 口上,本地屏只亮背光。显示那篇展开,这里只要知道:加 okay 之前,先 grep 已经 okay 的同类节点。

grep -n 'status = "okay"' /tmp/live.dts | grep -iE 'hdmi|dsi|lvds|rgb|edp'

3. 三层缓存:源码对了,dtb 还是旧的

3.1 GN 不跟踪板级 dts

内核 action 的 sources 指向 //kernel/linux/linux-5.10device/board/rk/rk3568_evb/kernel/*.dts 不在里面。ninja 看内核 git 没变,判定内核 target 不必跑,build_kernel.sh 整段跳过,拷 dts 的那一步根本不执行。

指纹:out/kernel/src_tmp/.../rk3568-evb-linux.dts 时间戳停在上次,内容是旧的。

3.2 is_kernel_change 也不跟踪 board

kernel_source_checker.sh 用一组 DEPS[] 目录的 git log + clang md5,和 checkpoint/last_build.info 比。DEPS[] 通常不含 device/board/。只改 dts、只改 build_kernel.sh、只改 khdf 子 hcs,判定结果是 没变。增量 make 用旧 .config、旧 dtb。

3.3 强制重跑

rm -rf out/kernel/checkpoint
rm -rf out/kernel/src_tmp/linux-5.10/boot_linux
# 更狠:
# rm -rf out/kernel
./build.sh --product-name rk3568_evb --ccache --build-target kernel --fast-rebuild

编完先验时间戳和内容,再刷:

stat out/kernel/OBJ/linux-5.10/arch/arm64/boot/dts/rockchip/*.dtb
grep -A2 '&hdmi' device/board/rk/rk3568_evb/kernel/*.dts
grep -A2 '&hdmi' out/kernel/src_tmp/linux-5.10/arch/arm64/boot/dts/rockchip/*.dts

三处对不上,不要刷。全清 out/kernel 能跑通,但会换模块签名钥匙,已经在板上的 .ko 会 Key was rejected。只删 checkpoint 通常够。

khdf 的 hcb 是另一份缓存,依赖只盯顶层 hdf.hcs。改 dts 同时改了 HCS,两边都要清。HDF 那篇写 hcb。这里只提醒:清了 checkpoint 不等于清了 hcb。


4. 只改 dts:单编 dtb,仍要刷 resource

全量 30~40 分钟。只改 dts 时走这条,一两分钟:

ROOT=/path/to/OpenHarmony
export PATH=$ROOT/prebuilts/clang/ohos/linux-x86_64/llvm/bin:$PATH
export KBUILD_OUTPUT=$ROOT/out/kernel/OBJ/linux-5.10
SRC=$ROOT/out/kernel/src_tmp/linux-5.10
BOARD=$ROOT/device/board/rk/rk3568_evb/kernel

# 权威 → 工作树(脚本没跑时你得手拷)
cp -f $BOARD/rk3568-evb-linux.dts \
      $SRC/arch/arm64/boot/dts/rockchip/rk3568-evb-linux.dts

cd $SRC
make LLVM=1 LLVM_IAS=1 CROSS_COMPILE=aarch64-linux-gnu- ARCH=arm64 \
     rockchip/rk3568-evb-linux.dtb -j$(nproc)

DTB=$KBUILD_OUTPUT/arch/arm64/boot/dts/rockchip/rk3568-evb-linux.dtb
ls -l $DTB
dtc -I dtb -O dts $DTB 2>/dev/null | grep -A5 'serial@fdd50000'

把新 dtb 打进 boot_linux 的 ext2(有的树叫 toybrick.dtb):

BOOT=$SRC/boot_linux
mkdir -p $BOOT/extlinux
cp -f $DTB $BOOT/extlinux/toybrick.dtb
# Image / ramdisk / extlinux.conf 已经在 BOOT 里的话不要动
genext2fs -B 4096 -b 16384 -d $BOOT -i 8192 -U $ROOT/out/rk3568_evb/packages/phone/images/boot_linux.img

-b 16384 是 64 MB。你原来的 boot_linux 多大,对 ls -l 再算。genext2fs 的块数乘 4096 等于镜像大小。

这一步只更新了 boot_linux 里那份 dtb。开机 U-Boot 交的是 resource 里的 rk-kernel.dtb。所以还要:

# 用新 dtb 重打 resource(logo 沿用旧的)
cd /tmp
cp $DTB ./rk-kernel.dtb
cp $BOARD/logo.bmp $BOARD/logo_kernel.bmp .
resource_tool --pack --image=$ROOT/out/rk3568_evb/packages/phone/images/resource.img \
    rk-kernel.dtb logo.bmp logo_kernel.bmp

然后刷 p4 + p5,eMMC 和 SD 都写。只刷 p5,/proc/device-tree 可以完全不动。这是「单编成功、板上没变」的第一号原因。


5. dtc 反编译:以 blob 为证,不以源码为证

三份 blob,三份真相:

# 1) OBJ 里刚编的
dtc -I dtb -O dts $DTB 2>/dev/null > /tmp/obj.dts

# 2) boot_linux 里那份,开机不一定用
debugfs -R 'dump /extlinux/toybrick.dtb /tmp/toybrick.dtb' \
    $ROOT/out/rk3568_evb/packages/phone/images/boot_linux.img
dtc -I dtb -O dts /tmp/toybrick.dtb 2>/dev/null > /tmp/boot.dts

# 3) resource 里那份,U-Boot 交给内核
resource_tool --unpack --image=$ROOT/out/rk3568_evb/packages/phone/images/resource.img \
    --output=/tmp/res
dtc -I dtb -O dts /tmp/res/rk-kernel.dtb 2>/dev/null > /tmp/res.dts

三份都 grep 同一节点:

for f in /tmp/obj.dts /tmp/boot.dts /tmp/res.dts; do
    echo "==== $f ===="
    grep -A12 'serial@fdd50000' "$f" | head -15
done

板上:

# cat /proc/device-tree/serial@fdd50000/status
# hexdump -C /proc/device-tree/serial@fdd50000/pinctrl-0
# ls /proc/device-tree/serial@fdd50000

/proc/device-tree 是当前内核拿到的树,只读。它和 /tmp/res.dts 不一致:U-Boot 取了另一块盘的 resource,或你刷 p4 刷到假文件上了。它和 res.dts 一致、和板级源码不一致:构建没用你的源。

dtc 反编译会丢掉 label 名,&uart0 变成完整路径。不要惊讶。status、reg、pinctrl phandle 还在。

检查抢脚,看同一 gpio 被几个节点引用。反编译后搜 RK_PC1 或 gpio 单元里的 pin 号。更直接的是上板:

# mount -t debugfs none /sys/kernel/debug
# cat /sys/kernel/debug/gpio | grep -i gpio0

gpio-17 后面跟的请求者名字,就是占用 uart0 TX 的那个设备。


6. 抢脚之一:公版 regulator 占 LED GPIO

底板上四颗用户 LED,设备树里用 gpio-leds。公版 EVB dtsi 可能已经把其中两根脚给了别的 always-on regulator,例如 VGA 供电、PCIe 3.3 V。表现:

# ls /sys/class/leds/
work          ← 系统灯还在,说明 leds 子系统活着
# ls /sys/class/leds/user-led*
ls: ... No such file

dmesg:

leds-gpio: probe of ... failed with error -16

-16 是 EBUSY。gpio-leds 对列表里任意一根脚 gpiod_get 失败,整节点 probe 失败,四颗灯全灭。不是四颗灯的驱动都坏了,是其中一根被别人先要了。

处理:把公版那两个没有消费者的 regulator 关掉。这块板若没有 VGA 座、PCIe 供电走别的节点,禁用是安全的:

&vcc3v3_vga {
    status = "disabled";
};

&pcie30_3v3 {
    status = "disabled";
};

label 名按你 dtsi 里的实际来。反编译搜 GPIO0_D4 / GPIO0_D5(示例)属于谁。

加自己的 gpio 节点之前,先在 live dts 里搜这根脚。公版 EVB 为了演示把很多脚占用了,板级硬件并不接那些外设。静默抢脚,节点看起来 okay,probe 失败。


7. 抢脚之二:uart0 TX 被 camera regulator 抢走

/dev/ttyS0 的两种假活着:

# ls -l /dev/ttyS0
crw-rw-rw- 1 root root 4, 64 ...   ← 真的,字符设备
# ls -l /dev/ttyS0
-rw-r--r-- 1 root root 5 ...       ← 假的,有人 echo 进不存在的路径

假的:删掉,tcgetattr 会报 Not a tty。真的没有:/proc/tty/driver/serial 无 0: 行。uart2/3/7 都在,只有 uart0 缺席。

dtb 里 uart0 已经 okay,时钟、pinctrl 齐全——源码没问题。运行时:

# ls -l /sys/bus/platform/devices/fdd50000.serial/driver
ls: ... No such file          ← 没绑定
# cat /sys/kernel/debug/devices_deferred
# 空                              ← 不是 defer,是返回了不会重试的错误

手动 bind,把错误逼出来(dmesg 被 audit 冲掉时尤其有用):

# readlink /sys/bus/platform/devices/fe660000.serial/driver
../../../../bus/platform/drivers/dw-apb-uart
# echo fdd50000.serial > /sys/bus/platform/drivers/dw-apb-uart/bind
# dmesg | tail -20

典型真因:

pin gpio0-17 already requested by vcc-camera-regulator; cannot claim for fdd50000.serial
could not request pin 17 (gpio0-17) from group uart0-xfer
probe failed with error -22

uart0 的 TX 是 GPIO0_C1(gpio0 pin 17)。公版 vcc_camera regulator gpio = <&gpio0 RK_PC1>regulator-always-on,开机先把这根脚要走。板级相机供电若已经走 vcc_avdd / vcc_dovdd / vcc_dvdd 那些固定轨,根本不引用 vcc_camera。它是 EVB 遗留。

&uart0 {
    status = "okay";
};

&vcc_camera {
    /delete-property/ gpio;
    /delete-property/ pinctrl-names;
    /delete-property/ pinctrl-0;
    /delete-property/ enable-active-high;
};

保留 regulator 节点(always-on 无 gpio 无害),只腾脚。重编 dtb,刷 resource + boot_linux。验:

# ls -l /dev/ttyS0
crw-rw-rw- ... 4, 64
# cat /proc/tty/driver/serial | grep '^0:'
0: uart:16550A mmio:0xFDD50000 irq:...

同类:PWM 风扇、MIPI 相机的复位脚,公版 dtsi 里常有 always-on 先占用。probe -22 + already requested by,去 dtsi 搜那个请求者,再决定 disable 还是 delete-property。不要先改驱动。


8. 状态、别名、和运行时 overlay

status 只认 "okay"(或 "ok")和 "disabled"。写成 "Ok""enable"、空着,行为按版本可能当 disabled。手写时复制已有节点。

别名让内核给 uart 编号:

aliases {
    serial0 = &uart0;
    serial2 = &uart2;
    serial7 = &uart7;
};

没有 aliases,tty 编号按 probe 顺序,uart0 不一定是 ttyS0。这块板约定 uart2 = 调试口 = ttyS2,cmdline 写死 console=ttyS2,1500000。别名乱改,console 会跑到别的口,串口没字。

运行时不要靠自己 mount 一个 dtbo 当日常手段。开源鸿蒙这套启动不走发行版 Linux 那种 U-Boot overlay 菜单。改硬件就改 dts,打进 resource。

/delete-node/ 公版的触摸节点,当 HDF 和内核 goodix 会抢同一地址:

&gt911 {
    status = "disabled";
};

或按节点路径删。I2C 上 0x5d 只能有一个主人。触摸那两篇写 HDF 侧。dts 侧至少保证内核原生驱动不要 okay。


9. 一份最小板级片段,能直接抄结构

/* device/board/rk/rk3568_evb/kernel/rk3568-evb-linux.dts 片段 */
&uart0 {
    status = "okay";
};

&uart2 {
    status = "okay";   /* console */
};

&uart7 {
    status = "okay";
};

&i2c1 {
    status = "okay";
    clock-frequency = <100000>;
};

&i2c2 {
    status = "okay";
};

&hdmi {
    status = "disabled";
};

&vcc_camera {
    /delete-property/ gpio;
    /delete-property/ pinctrl-names;
    /delete-property/ pinctrl-0;
    /delete-property/ enable-active-high;
};

&vcc3v3_vga {
    status = "disabled";
};

&gpio_leds {
    status = "okay";
};

每打开一个节点,同时问:公版有没有同类已经 okay?这根脚有没有 always-on regulator?问完再编。


10. 失败判断

源码是 okay,/proc/device-tree 是 disabled。 三层缓存,或没刷 resource,或 U-Boot 取了 eMMC 旧 p4。对时间戳,解 resource 的 dtb。

/proc/device-tree 已 okay,没有设备节点。 probe 失败。devices_deferred、手动 bind、dmesg。抢脚优先于时钟、优先于 compatible 写错。

-22 + already requested。 公版 regulator / pinctrl。delete-property 或 disabled 那个请求者。

-16 EBUSY 在 gpio-leds。 列表里有一根脚被占,整组灯灭。不要先换 LED 极性。

改 dts 全量很快结束。 GN / checkpoint 没看见。删 checkpoint。

只刷了 boot_linux。 开机 dtb 不变。刷 p4。两盘都刷。

dtc 报 duplicate label。 板级又定义了一个和 dtsi 同名的 label。用 & 引用,不要重开节点。

预处理找不到 rk3568.dtsi。 -I 路径没指到 arch/arm64/boot/dts/rockchip。在 src_tmp 里编,不要在板级目录直接 dtc 源码(include 解不开)。

菜单里 HDMI 和 LVDS 同时亮不了。 不是 dts 语法错,是这套 compositor 只认一块主显示。关一路。

设备树不生效时,不要加更多节点。先让 /proc/device-tree 和你反编译的 resource dtb 变成同一份,再处理 probe。源码、OBJ、resource、板上,四份对齐,这件事情才算改完。


系列第 12 篇 · 芯片:瑞芯微 RK3568 · OpenHarmony 4.1(API 11) · Linux 5.10

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐