3. 内核源码结构:arch/arm64、drivers、include/dt-bindings 关键目录解析、Kconfig 与 Makefile 机制
拿到一份 Rockchip 平台的内核源码,很多人第一反应是「这么大一坨,从哪看起?」。我刚开始接触时也懵过,后来发现只要抓住几个关键目录,整个脉络就清晰了。说白了,内核源码虽然庞大,但它的组织逻辑非常固定——你只要知道什么东西放在哪里,找起来就很快。
3.1 arch/arm64:芯片的「骨架」
arch/arm64 目录存放的是 ARMv8-A 64 位架构相关的代码。Rockchip 的 RK35xx 系列(比如 RK3568、RK3588)都属于这个体系。这个目录里最重要的几个子目录:
- boot/dts:设备树源文件存放处。Rockchip 平台的
.dts和.dtsi文件都在这里。我习惯先看rockchip/子目录,里面按芯片型号分文件夹。 - configs:默认内核配置文件。比如
defconfig或rockchip_linux_defconfig,编译前从这里开始裁剪。 - kernel:包含入口函数
head.S、异常向量表等底层启动代码。一般驱动开发不太动这里,但做启动优化时会用到。 - mm:内存管理相关,比如页表创建、缓存策略。Rockchip 的 iommu 配置有时会涉及这里。
arch/arm64/boot/dts/rockchip/ 下的 Makefile,确认哪些设备树文件被编译。这能快速了解官方支持了哪些板子。
3.2 drivers:驱动代码的「主战场」
drivers/ 是内核里最大的目录,没有之一。Rockchip 平台的大部分驱动都在这里。你不需要全部掌握,但要知道关键子目录:
| 子目录 | 对应 Rockchip 模块 | 常见文件/驱动 |
|---|---|---|
gpu/drm/rockchip/ |
显示控制器 (VOP)、HDMI、MIPI DSI | rockchip_drm_vop.c, inno_hdmi.c |
media/platform/rockchip/ |
ISP、视频编解码 (VDPU/HEVC) | rkisp1/, mpp_service/ |
i2c/busses/ |
I2C 控制器 | i2c-rk3x.c |
spi/ |
SPI 控制器 | spi-rockchip.c |
pinctrl/ |
引脚复用和 GPIO | pinctrl-rockchip.c |
clk/rockchip/ |
时钟树管理 | clk-rk3568.c, clk-rk3588.c |
power/avs/rockchip/ |
自适应电压调节 (AVS) | rockchip-avs.c |
soc/rockchip/ |
SoC 特有功能(如系统控制、efuse) | rockchip_system.c |
举个例子,如果你要调试 RK3588 的 HDMI 输出,直接去 drivers/gpu/drm/rockchip/ 找 rk3588_hdmi.c。我曾经花了一整天在 drivers/media/platform/rockchip/ 里追一个 ISP 的 buffer 问题,最后发现是 rkisp1 驱动里一个 DMA 对齐参数没配好。
3.3 include/dt-bindings:设备树的「字典」
include/dt-bindings/ 目录存放的是设备树中使用的宏定义和常量。Rockchip 平台大量使用这些头文件来避免魔法数字。比如:
// 在设备树中引用
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/pinctrl/rockchip.h>
#include <dt-bindings/clock/rk3588-cru.h>
// 实际使用
&gpio0 {
reset-gpios = <&gpio0 RK_PA0 GPIO_ACTIVE_LOW>;
};
这里 RK_PA0 定义在 rockchip.h 里,GPIO_ACTIVE_LOW 定义在 gpio.h 里。为什么要这么做?你想想看,如果设备树里直接写 0x1000 这种数字,三个月后你自己都看不懂。用宏定义,可读性高得多。
include/dt-bindings/clock/:时钟 ID 定义,比如rk3588-cru.hinclude/dt-bindings/pinctrl/:引脚功能定义,比如rockchip.hinclude/dt-bindings/power/:电源域定义,比如rk3588-power.hinclude/dt-bindings/reset/:复位信号定义
3.4 Kconfig 与 Makefile:内核的「构建规则」
这两个文件是内核配置和编译的核心。理解它们,你才能知道「我加了一个驱动,怎么让它出现在 menuconfig 里」以及「怎么让它被编译进内核」。
3.4.1 Kconfig:配置菜单的「语法」
Kconfig 文件定义了 menuconfig 里看到的选项。一个典型的 Rockchip 驱动 Kconfig 条目长这样:
config ROCKCHIP_DRM_HDMI
tristate "Rockchip DRM HDMI support"
depends on DRM_ROCKCHIP
select SND_SOC_HDMI_CODEC
help
Choose this option if you want to use HDMI on Rockchip SoCs.
This driver supports RK3288, RK3399, RK3568, RK3588.
这里 tristate 表示可以编译为模块(M)、编译进内核(Y)或不编译(N)。depends on 是依赖关系,select 是自动选中其他选项。我踩过一个坑:在 RK3568 上配 HDMI 音频,忘了加 select SND_SOC_HDMI_CODEC,结果编译出来的内核没有音频驱动,查了半天才发现是 Kconfig 漏了。
3.4.2 Makefile:编译的「指挥棒」
Makefile 告诉内核构建系统如何编译源文件。还是以 HDMI 为例:
# drivers/gpu/drm/rockchip/Makefile
obj-$(CONFIG_ROCKCHIP_DRM_HDMI) += rockchip_hdmi.o
rockchip_hdmi-y := rk3588_hdmi.o rk_hdmi_common.o
这里 obj-$(CONFIG_ROCKCHIP_DRM_HDMI) 的意思是:如果 CONFIG_ROCKCHIP_DRM_HDMI 被配置为 y 或 m,就编译 rockchip_hdmi.o。而 rockchip_hdmi-y 表示这个模块由多个源文件组成。
obj-y += my_driver.o,结果这个驱动无论如何都会被编译进内核,即使 Kconfig 里没选。正确的做法是用 obj-$(CONFIG_MY_DRIVER),让配置系统决定。
3.5 知识结构图:内核源码目录关系
下面这张图展示了 Rockchip 平台内核源码中几个关键目录之间的关系,以及数据流走向:
3.6 实战:如何快速定位一个驱动
假设你要找 RK3588 的 SPI 驱动。我的做法是:
- 查设备树:在
arch/arm64/boot/dts/rockchip/rk3588.dtsi里搜索spi,看到compatible = "rockchip,rk3588-spi"。 - 搜 compatible 字符串:在内核源码根目录执行
grep -r "rockchip,rk3588-spi" drivers/,找到drivers/spi/spi-rockchip.c。 - 看 Kconfig:在
drivers/spi/Kconfig里搜索SPI_ROCKCHIP,确认配置选项。 - 看 Makefile:在
drivers/spi/Makefile里确认obj-$(CONFIG_SPI_ROCKCHIP) += spi-rockchip.o。
整个过程不超过 5 分钟。嗯,这就是熟悉目录结构的好处——你不需要记住每个文件在哪,但你知道怎么找到它。
cscope 数据库,配合 grep 和 find 命令,定位速度飞快。特别是 compatible 字符串,这是驱动和设备树之间的「暗号」。