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,其他全砍。操作流程:

  1. make rockchip_linux_defconfig
  2. make menuconfig 进入图形界面
  3. 关闭不需要的驱动:比如 Device Drivers -> Graphics support -> DRM drivers 里只保留 Rockchip DRM 和 DesignWare HDMI
  4. 保存退出,生成 .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 知识体系总览

下面这张图,把内核编译与模块化的核心流程串起来了。你照着这个思路走,基本不会迷路。

Rockchip内核编译与模块化流程 Rockchip内核编译与模块化流程 1. 获取Rockchip内核源码 2. 内核配置 (defconfig + menuconfig) 编译为模块? 是 3a. 模块编译 (make M=xxx) 否 3b. 全量编译 (make -jN) 4a. 模块安装到目标板 4b. 烧录内核镜像到板子 5. 动态加载 (insmod/modprobe) 提示:调试阶段优先用模块编译 + insmod,节省时间;量产阶段用全量编译 + 烧录,稳定可靠

4.6 实战:编译一个Rockchip显示驱动模块

光说不练假把式。咱们来一次完整的模块编译和加载实战。假设你改了 rk3588_drm.c 里的一个bug。

4.6.1 步骤清单

  1. 确认内核版本: uname -r 查看板子运行的内核版本
  2. 配置编译环境: 用相同的源码和配置编译
  3. 编译模块: make M=drivers/gpu/drm/rockchip modules
  4. 拷贝到板子: scp drivers/gpu/drm/rockchip/rockchipdrm.ko root@192.168.1.100:/lib/modules/5.10.160/
  5. 加载测试: modprobe rockchipdrm 或者 insmod rockchipdrm.ko
  6. 验证: 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 避坑指南

我曾经踩过的三个大坑:

  1. 内核版本不一致: 编译环境和目标板的内核版本必须完全一致,包括小版本号。否则模块加载直接报错。
  2. 配置不一致: 即使版本相同,如果 .config 不同(比如一个开了 CONFIG_DEBUG_INFO 一个没开),模块也可能加载失败。
  3. 符号依赖缺失: 模块A依赖模块B的导出符号,但B没加载。用 modprobe 可以自动处理,用 insmod 就得自己注意顺序。