设备树DTS-【万物智能之开源鸿蒙OpenHarmony系统实战开发系列教程】
改硬件几乎都从设备树开始。今天讲为什么你改了源文件、编译也成功,板上却没变化——开机往往没用你以为的那一份。

&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.10。device/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 会抢同一地址:
>911 {
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
更多推荐

所有评论(0)