框架的位置清楚了。今天说加一颗外设能走哪三条路,以及选错路时为什么编译成功、板上却有时有节点、有时没有。


现象很具体:你 menuconfig 里把某个驱动勾成 *,编完、刷完,dmesg 里没有 probe。或者 insmod r8188eu.ko 报 Key was rejected by service。或者超声波节点 /dev/hcsr04 好好的,你又在 HCS 里给同一条 I2C 登记了一个 HDF 设备,两边开始互相 NACK。
原因不在芯片。开源鸿蒙这块 RK3568 上,一个外设能走三条路进系统,配置却分散在 defconfig、build_kernel.sh、设备树、HCS 四处。改错地方,编译照样 exit 0。
产品目录按教程代号 rk3568_evb。

现象:menuconfig 勾了,重启又没了
bash
cd out/kernel/src_tmp/linux-5.10
make LLVM=1 ARCH=arm64 menuconfig
CONFIG_FOO=y 当时在 .config 里。下一次 ./build.sh --product-name rk3568_evb --ccache 跑完,打开 out/kernel/OBJ/linux-5.10/.config,FOO 又是 is not set。
原因
板级 build_kernel.sh 每次都会用碎片 defconfig 重新合成 .config。menuconfig 改的是当前 OBJ 里那一份,不是权威源。权威源有三处,缺一处,下一次合成就丢:
- 源码树里的 defconfig
- 本次构建用的
out/kernel/.../rockchip_linux_defconfig(只对「此刻不重建、先测一把」有用) device/board/rk/rk3568_evb/kernel/build_kernel.sh第 13 段的幂等sed/append
GN 的内核 action 还不跟踪 device/board/。你只改了 build_kernel.sh,ninja 可能判定「内核没变」,连合成都不跑。
代码
bash
# device/board/rk/rk3568_evb/kernel/build_kernel.sh §13 MERGED_CFG
CFG="$KERNEL_SRC/arch/arm64/configs/rockchip_linux_defconfig"
set_y() {
local k="$1"
if grep -q "^${k}=y" "$CFG"; then
return 0
fi
if grep -q "^# ${k} is not set" "$CFG"; then
sed -i "s/^# ${k} is not set/${k}=y/" "$CFG"
else
echo "${k}=y" >> "$CFG"
fi
}
set_y CONFIG_EVB_MISC
set_y CONFIG_CFI_PERMISSIVE
# R8188EU 不能 set_y
编完对产物,不要信 exit 0:
bash
grep '^CONFIG_EVB_MISC' out/kernel/OBJ/linux-5.10/.config
strings out/kernel/OBJ/linux-5.10/vmlinux | grep evb_misc | head
.config 是 =y 而 vmlinux 里没有符号:删 out/kernel/checkpoint 再编。
现象:staging 驱动一 insmod,整板复位
原因
内核开了 CONFIG_CFI_CLANG=y 加 ThinLTO。间接调用核对函数指针类型,签名稍不对就 panic。staging 里 callback 类型不严是常态。bring-up 把 CFI 从「杀」降成「告警继续跑」:CONFIG_CFI_PERMISSIVE=y。
代码
写进 §13,不要只在 menuconfig 里勾:
bash
set_y CONFIG_CFI_PERMISSIVE
rm -rf out/kernel/checkpoint
./build.sh --product-name rk3568_evb --ccache --build-target kernel --fast-rebuild --disable-package-image
bash
grep CFI_PERMISSIVE out/kernel/OBJ/linux-5.10/.config
hdc shell "dmesg | grep -i cfi"
现象:insmod 报 Key was rejected
bash
Loading of module with unavailable key is rejected
insmod: failed to load /data/r8188eu.ko: Key was rejected by service
脚本把失败打印成「跳过(已加载)」------2>/dev/null && ... || log 跳过 把错误吞了。先 lsmod。
原因
CONFIG_MODULE_SIG_FORCE=y。未签名、或公钥和当前内核嵌的那把对不上,一律拒绝。重编内核若重新生成 certs/signing_key.pem,旧 .ko 全废。钥匙必须固定在板级目录,每次构建拷进内核树。
代码
bash
device/board/rk/rk3568_evb/kernel/certs/signing_key.pem
device/board/rk/rk3568_evb/kernel/certs/signing_key.x509
bash
CERT_SRC="$BOARD_KERNEL/certs"
CERT_DST="$KERNEL_SRC/certs"
mkdir -p "$CERT_DST"
cp -f "$CERT_SRC/signing_key.pem" "$CERT_DST/"
cp -f "$CERT_SRC/signing_key.x509" "$CERT_DST/"
bash
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
cd $ROOT/out/kernel/src_tmp/linux-5.10
make LLVM=1 LLVM_IAS=1 CROSS_COMPILE=aarch64-linux-gnu- ARCH=arm64 \
M=drivers/staging/rtl8188eu modules -j$(nproc)
$KBUILD_OUTPUT/scripts/sign-file sha1 \
$KBUILD_OUTPUT/certs/signing_key.pem \
$KBUILD_OUTPUT/certs/signing_key.x509 \
drivers/staging/rtl8188eu/r8188eu.ko
md5sum device/board/rk/rk3568_evb/kernel/certs/signing_key.x509 \
out/kernel/OBJ/linux-5.10/certs/signing_key.x509
两份 x509 md5 必须一样。
bash
hdc file send r8188eu.ko /data/r8188eu.ko
hdc shell "lsmod | grep r8188eu || insmod /data/r8188eu.ko"
hdc shell "dmesg | tail -20"
现象:R8188EU 写成 =y,.config 里仍是 =m
原因
drivers/staging/rtl8188eu/Kconfig 有 depends on m。=y 会被降回模块。必须当 .ko 打进镜像,开机 insmod。
代码
bash
if grep -q '^CONFIG_R8188EU=y' "$CFG"; then
sed -i 's/^CONFIG_R8188EU=y/CONFIG_R8188EU=m/' "$CFG"
fi
if ! grep -q '^CONFIG_R8188EU=m' "$CFG"; then
echo 'CONFIG_R8188EU=m' >> "$CFG"
fi
init(init.rk30board.cfg 的 cmds 只能放合法命令):
bash
{
"name": "net_wifi_r8188eu",
"path": "/vendor/bin/wifi-r8188eu-up.sh",
"once": 1
}
脚本先 lsmod 再 insmod,失败留 stderr。
三条路怎么选
现象:同一个超声波,你写了 HDF Sensor 又写了内核 hcsr04.c,节点有时在、有时不在。
原因:Linux 原生驱动和 HDF 并存。一个硬件只能被一边 probe。选路看上层要什么:
- 只要
/dev或 sysfs,外设测试应用open→ 路径 A,内核原生 - 要用 HDF 的 GPIO/I2C/PWM API,但还不是标准 Kit → 路径 B,HDF 平台
- 要进桌面 / ADM / composer / 相机 Kit → 路径 C,HDF 外设模型
触摸、音频、显示走 C。超声波、RFID、电机、LED、矩阵键盘走 A。选一条,不要双边 okay。
路径 A:misc 字符设备接到内核
目标:DTS 一个节点,misc_register 出 /dev/evb_misc,用户态能 read、能 ioctl。
代码:evb_misc.h
bash
#ifndef EVB_MISC_H
#define EVB_MISC_H
#include <linux/ioctl.h>
#define EVB_MISC_IOC_MAGIC 'E'
struct evb_misc_msg {
unsigned int len;
char text[120];
};
#define EVB_MISC_IOC_GET _IOR(EVB_MISC_IOC_MAGIC, 1, struct evb_misc_msg)
#define EVB_MISC_IOC_SET _IOW(EVB_MISC_IOC_MAGIC, 2, struct evb_misc_msg)
#endif
用户态把 <linux/ioctl.h> 换成 <sys/ioctl.h>。本产品用户态 32 位 musl,.ko 仍是 aarch64。ioctl 参数不要塞指针,上面已经给 compat_ioctl 留了同一函数。
代码:evb_misc.c
放到 device/board/rk/rk3568_evb/kernel/drivers/,由 build_kernel.sh 拷进 drivers/misc/。
bash
// SPDX-License-Identifier: GPL-2.0
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
#include <linux/of.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include "evb_misc.h"
struct evb_misc_dev {
struct miscdevice misc;
struct mutex lock;
char buf[128];
size_t len;
};
static ssize_t evb_misc_read(struct file *file, char __user *ubuf,
size_t count, loff_t *ppos)
{
struct evb_misc_dev *edev = file->private_data;
ssize_t n;
mutex_lock(&edev->lock);
n = simple_read_from_buffer(ubuf, count, ppos, edev->buf, edev->len);
mutex_unlock(&edev->lock);
return n;
}
static long evb_misc_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct evb_misc_dev *edev = file->private_data;
struct evb_misc_msg msg;
int ret = 0;
if (_IOC_TYPE(cmd) != EVB_MISC_IOC_MAGIC)
return -ENOTTY;
mutex_lock(&edev->lock);
switch (cmd) {
case EVB_MISC_IOC_GET:
memset(&msg, 0, sizeof(msg));
memcpy(msg.text, edev->buf, min(sizeof(msg.text) - 1, edev->len));
msg.len = edev->len;
if (copy_to_user((void __user *)arg, &msg, sizeof(msg)))
ret = -EFAULT;
break;
case EVB_MISC_IOC_SET:
if (copy_from_user(&msg, (void __user *)arg, sizeof(msg))) {
ret = -EFAULT;
break;
}
if (msg.len >= sizeof(edev->buf)) {
ret = -EINVAL;
break;
}
memcpy(edev->buf, msg.text, msg.len);
edev->buf[msg.len] = '\0';
edev->len = msg.len;
break;
default:
ret = -ENOTTY;
break;
}
mutex_unlock(&edev->lock);
return ret;
}
static int evb_misc_open(struct inode *inode, struct file *file)
{
struct evb_misc_dev *edev = container_of(file->private_data,
struct evb_misc_dev, misc);
file->private_data = edev;
return 0;
}
static const struct file_operations evb_misc_fops = {
.owner = THIS_MODULE,
.open = evb_misc_open,
.read = evb_misc_read,
.unlocked_ioctl = evb_misc_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = evb_misc_ioctl,
#endif
};
static int evb_misc_probe(struct platform_device *pdev)
{
struct evb_misc_dev *edev;
int ret;
edev = devm_kzalloc(&pdev->dev, sizeof(*edev), GFP_KERNEL);
if (!edev)
return -ENOMEM;
mutex_init(&edev->lock);
strscpy(edev->buf, "evb_misc ready\n", sizeof(edev->buf));
edev->len = strlen(edev->buf);
edev->misc.minor = MISC_DYNAMIC_MINOR;
edev->misc.name = "evb_misc";
edev->misc.fops = &evb_misc_fops;
edev->misc.mode = 0666;
ret = misc_register(&edev->misc);
if (ret)
return ret;
platform_set_drvdata(pdev, edev);
dev_info(&pdev->dev, "/dev/evb_misc minor=%d\n", edev->misc.minor);
return 0;
}
static int evb_misc_remove(struct platform_device *pdev)
{
struct evb_misc_dev *edev = platform_get_drvdata(pdev);
misc_deregister(&edev->misc);
return 0;
}
static const struct of_device_id evb_misc_of_match[] = {
{ .compatible = "rk3568,evb-misc" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, evb_misc_of_match);
static struct platform_driver evb_misc_driver = {
.probe = evb_misc_probe,
.remove = evb_misc_remove,
.driver = {
.name = "evb-misc",
.of_match_table = evb_misc_of_match,
},
};
module_platform_driver(evb_misc_driver);
MODULE_LICENSE("GPL");
Kconfig:
bash
config EVB_MISC
tristate "rk3568_evb misc char skeleton"
default n
Makefile:obj-$(CONFIG_EVB_MISC) += evb_misc.o
build_kernel.sh §13 拷源并 set_y CONFIG_EVB_MISC。
DTS:
bash
/ {
evb_misc: evb-misc {
compatible = "rk3568,evb-misc";
status = "okay";
};
};
compatible 必须和 of_device_id 逐字符相同。开发期当 .ko:
bash
make LLVM=1 LLVM_IAS=1 CROSS_COMPILE=aarch64-linux-gnu- ARCH=arm64 \
M=drivers/misc evb_misc.ko -j$(nproc)
$KBUILD_OUTPUT/scripts/sign-file sha1 \
$KBUILD_OUTPUT/certs/signing_key.pem \
$KBUILD_OUTPUT/certs/signing_key.x509 \
drivers/misc/evb_misc.ko
hdc file send evb_misc.ko /data/evb_misc.ko
hdc shell "insmod /data/evb_misc.ko; cat /dev/evb_misc"
路径 B:HDF 平台
现象:PWM 在 /sys/class/pwm 能转,HDF 程序 GpioRead 失败。
原因:要在 device_info.hcs 登记,policy = 2 用户态才 Bind 得到。Linux 已经 export 的脚,HDF 再申请会抢。
代码
bash
device_pwm :: device {
device0 :: deviceNode {
policy = 2;
priority = 70;
permission = 0644;
moduleName = "HDF_PLATFORM_PWM";
serviceName = "HDF_PLATFORM_PWM_0";
deviceMatchAttr = "rockchip_rk3568_pwm_0";
}
}
改 khdf 刷 boot_linux,改 uhdf 刷 vendor。改子 hcs 必须删 hcb。风扇演示走 sysfs 就够:
bash
hdc shell "echo 0 > /sys/class/pwm/pwmchip0/export"
hdc shell "echo 1000000 > /sys/class/pwm/pwmchip0/pwm0/period"
hdc shell "echo 500000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle"
hdc shell "echo 1 > /sys/class/pwm/pwmchip0/pwm0/enable"
路径 C:HDF 外设模型
现象:GT911 内核 goodix probe 成功,桌面划不动;或者坐标按 720×1280 缩。
原因:桌面走 Input 模型 + HCS 的 solutionX/Y。DTS 里 goodix 必须 disabled。
代码
bash
&i2c2 {
status = "okay";
clock-frequency = <100000>;
gt911_lvds: gt911@5d {
compatible = "goodix,gt911";
reg = <0x5d>;
status = "disabled";
};
};
bash
inputAttr {
solutionX = 1024;
solutionY = 600;
}
busConfig { busType = 0; busNum = 2; }
device_info.hcs:moduleName = "HDF_TOUCH_GT911",policy = 2,preload = 0。
bash
hdc shell "cat /proc/bus/input/devices"
hdc shell "ls /sys/bus/i2c/devices/2-005d/driver"
hdc shell "i2cdetect -y -r 2"
现象对照
| 现象 | 原因 | 先做什么 |
|---|---|---|
| menuconfig 勾了下次丢 | 合成从 defconfig+§13 来 | 写进 §13,三处同步 |
| `.config` 有、vmlinux 无符号 | ninja 没重跑内核 | 删 checkpoint |
| insmod Key rejected | 没签或钥匙换了 | 固定 certs,sign-file |
| R8188EU 写 =y 仍是 =m | Kconfig `depends on m` | 保持 =m,开机 insmod |
| insmod 整板复位 | CFI | `CFI_PERMISSIVE=y` |
| 有节点无 `/dev` | 驱动没编进来或 compatible 不对 | grep .config,对 of_match |
| 用户态 so 段错误 | 用 aarch64-linux-gnu 编了 | armv7 + musl |
| HDF 和内核抢 I2C | 两边都 okay | DTS disabled 或 HCS 不要登记 |
系列第 14 篇 · 芯片:瑞芯微 RK3568 · OpenHarmony 4.1(API 11) · Linux 5.10