4. 内核编译与模块化:Rockchip内核源码结构、内核配置、模块编译与动态加载
说实话,很多刚接触Rockchip平台的朋友,第一反应就是拿一个通用内核来编译。结果呢?要么起不来,要么外设全罢工。我早期也踩过这个坑——拿着主线内核直接怼,结果HDMI没输出,MIPI DSI点不亮,折腾了两天才发现是DTS和驱动补丁没打全。
这一章,我就带你彻底搞懂Rockchip内核那点事。从源码结构到模块编译,再到动态加载的实战技巧,咱们一步到位。
4.1 Rockchip内核源码结构
Rockchip官方维护的内核,通常基于LTS版本(比如5.10、6.1)。你从官方仓库拉下来后,会发现几个关键目录:
| 目录/文件 | 作用 | 我常用的关注点 |
|---|---|---|
arch/arm64/boot/dts/rockchip/ |
设备树源文件,板级硬件描述 | rk3588-evb1.dts、rk3568-evb.dts 这些是起点 |
drivers/ |
驱动源码,按子系统分类 | gpu/drm、media、phy、pinctrl 最常改 |
sound/soc/rockchip/ |
音频驱动,Rockchip特有 | rk3588_hdmi.c、rk3568_i2s.c |
include/dt-bindings/ |
设备树绑定头文件 | gpio.h、pinctrl/rockchip.h |
Documentation/devicetree/bindings/ |
设备树绑定文档 | 写新外设时必查 |
我个人习惯,拿到一个新板子,第一件事就是看 arch/arm64/boot/dts/rockchip/ 下的 Makefile。这里面列出了所有支持的设备树文件。比如你要编译RK3588 EVB1的DTB,直接找 rk3588-evb1-lp4.dtb 对应的源文件就行。
核心思路:Rockchip内核 = 主线内核 + Rockchip补丁集 + 板级DTS。不要自己从头配,基于官方分支改最稳。
4.2 内核配置:从零到能跑
配置内核,说白了就是告诉编译器:哪些功能要编进去,哪些编成模块,哪些直接砍掉。Rockchip平台有现成的defconfig文件,省了不少事。
4.2.1 找到正确的defconfig
在 arch/arm64/configs/ 下,Rockchip的配置文件命名很规律:
rockchip_linux_defconfig—— 通用Linux配置rockchip_linux_defconfig+ 板级覆盖(部分SDK有)- Android平台用
rockchip_defconfig
我建议你直接用 make rockchip_linux_defconfig 作为起点。然后根据你的硬件裁剪。
4.2.2 配置裁剪实战
假设你只需要HDMI显示、以太网和USB,其他全砍。操作流程:
make rockchip_linux_defconfigmake menuconfig进入图形界面- 关闭不需要的驱动:比如
Device Drivers -> Graphics support -> DRM drivers里只保留Rockchip DRM和DesignWare HDMI - 保存退出,生成
.config
我的小技巧:用 make savedefconfig 生成精简的defconfig文件。这样下次编译直接 cp defconfig .config,不用再跑一遍menuconfig。我在项目里经常维护3-4个不同板型的defconfig,切换起来特别快。
4.2.3 常见配置项速查
| 功能 | 配置项 | 说明 |
|---|---|---|
| Rockchip DRM显示 | CONFIG_DRM_ROCKCHIP |
必须开启,否则HDMI/MIPI不工作 |
| HDMI音频 | CONFIG_SND_SOC_ROCKCHIP_HDMI |
音视频同步的基础 |
| 以太网 | CONFIG_STMMAC_ETH |
Rockchip GMAC控制器 |
| USB 3.0 | CONFIG_USB_DWC3 |
DWC3控制器驱动 |
| MIPI DSI | CONFIG_DRM_ROCKCHIP_MIPI_DSI |
点屏必备 |
4.3 模块编译:单独编译驱动模块
全量编译内核太慢了。尤其是调试阶段,改一行代码就要等10分钟,谁也受不了。所以模块化编译是必备技能。
4.3.1 编译单个模块
假设你改了 drivers/gpu/drm/rockchip/ 下的代码,只想编译这个目录:
# 先确保内核已配置
make rockchip_linux_defconfig
make modules_prepare
# 编译指定模块
make M=drivers/gpu/drm/rockchip modules
# 编译完成后,模块文件在 drivers/gpu/drm/rockchip/*.ko
嗯,这里要注意:modules_prepare 这一步不能省。它生成模块编译需要的头文件和符号信息。我刚开始不知道,直接 make M=xxx,结果报一堆找不到头文件的错误。
4.3.2 模块安装到目标板
编译出来的 .ko 文件怎么用?两种方式:
- 直接拷贝:
scp *.ko root@板子IP:/lib/modules/$(uname -r)/ - 模块安装:
make modules_install INSTALL_MOD_PATH=/path/to/rootfs
我个人更推荐第二种。因为 modules_install 会自动处理依赖关系,生成 modules.dep 文件。否则你手动 depmod 也麻烦。
曾经踩过的坑:我试过直接拷贝.ko文件到板子上,然后 insmod 报错 Invalid module format。查了半天,发现是板子运行的内核版本和编译环境的内核版本不一致。记住:模块必须和运行内核完全匹配,包括配置选项!
4.4 动态加载:insmod、modprobe与自动加载
模块编译好了,怎么加载到内核里?这里有三个命令,各有各的用法。
4.4.1 insmod vs modprobe
| 命令 | 特点 | 适用场景 |
|---|---|---|
insmod |
直接加载,不处理依赖 | 调试单个模块,你知道它没依赖 |
modprobe |
自动处理依赖,加载所有需要的模块 | 生产环境,或者模块有依赖链 |
modprobe -r |
卸载模块及其依赖 | 安全卸载,避免内核崩溃 |
举个例子:加载Rockchip DRM驱动,它依赖 drm 和 drm_kms_helper。用 insmod 你得手动先加载这两个,用 modprobe rockchipdrm 一步到位。
4.4.2 模块参数传递
有些模块支持运行时参数。比如调试显示驱动时,我想打开日志:
# 加载时传参
insmod rockchipdrm.ko debug=1
# 或者用modprobe
modprobe rockchipdrm debug=1
怎么看模块支持哪些参数?用 modinfo:
modinfo rockchipdrm.ko
输出里 parm 开头的行就是参数列表。
4.4.3 开机自动加载
想让模块在系统启动时自动加载?两种方法:
- 方法一: 把模块名写入
/etc/modules,每行一个 - 方法二: 在
/etc/modprobe.d/下创建.conf文件,比如rockchip.conf:
# 开机自动加载rockchipdrm
options rockchipdrm debug=0
softdep rockchipdrm pre: drm drm_kms_helper
我个人习惯用第二种。因为可以同时配置参数和依赖顺序,更灵活。
4.5 知识体系总览
下面这张图,把内核编译与模块化的核心流程串起来了。你照着这个思路走,基本不会迷路。
4.6 实战:编译一个Rockchip显示驱动模块
光说不练假把式。咱们来一次完整的模块编译和加载实战。假设你改了 rk3588_drm.c 里的一个bug。
4.6.1 步骤清单
- 确认内核版本:
uname -r查看板子运行的内核版本 - 配置编译环境: 用相同的源码和配置编译
- 编译模块:
make M=drivers/gpu/drm/rockchip modules - 拷贝到板子:
scp drivers/gpu/drm/rockchip/rockchipdrm.ko root@192.168.1.100:/lib/modules/5.10.160/ - 加载测试:
modprobe rockchipdrm或者insmod rockchipdrm.ko - 验证:
lsmod | grep rockchip确认加载成功
关键点:如果 modprobe 报 modprobe: FATAL: Module rockchipdrm not found in directory /lib/modules/5.10.160,说明你没运行 depmod。执行 depmod -a 重新生成依赖文件即可。
4.6.2 调试技巧
模块加载后,怎么看它有没有正常工作?我一般用这几招:
dmesg | tail -20—— 看内核日志,驱动初始化信息都在这里cat /sys/kernel/debug/dri/0/state—— 查看DRM状态(需要开启debugfs)ls -l /sys/class/drm/—— 查看显示设备节点
如果驱动加载后HDMI没输出,先别急着怀疑代码。检查一下设备树里HDMI节点是否使能了,电压是否正常。我曾经有一次折腾了一下午,最后发现是HDMI的5V供电没开——硬件问题。
4.7 避坑指南
我曾经踩过的三个大坑:
- 内核版本不一致: 编译环境和目标板的内核版本必须完全一致,包括小版本号。否则模块加载直接报错。
- 配置不一致: 即使版本相同,如果
.config不同(比如一个开了CONFIG_DEBUG_INFO一个没开),模块也可能加载失败。 - 符号依赖缺失: 模块A依赖模块B的导出符号,但B没加载。用
modprobe可以自动处理,用insmod就得自己注意顺序。