4. Linux内核源码结构

在嵌入式Linux开发中,内核源码的组织方式直接决定了我们能否高效定位驱动代码、配置功能模块以及理解编译链接过程。对于RK3568平台而言,其BSP(Board Support Package)基于标准Linux内核(通常为5.10或5.15版本)并加入了Rockchip官方维护的补丁与驱动。本章将深入剖析内核目录树的结构设计、Kconfig与Makefile的协同工作机制,并精准定位RK3568平台相关的代码存放位置,帮助开发者建立从“源码浏览”到“配置编译”再到“驱动开发”的完整认知链路。

RK3568 内核源码结构总览 arch/arm64/ boot/dts/rockchip/ configs/rockchip_linux_defconfig kernel/ (平台相关) drivers/ soc/rockchip/ media/platform/rockchip/ phy/rockchip/ clk/rockchip/ pinctrl/rockchip/ Kconfig menuconfig 配置界面 .config 生成 Makefile obj-y / obj-$(CONFIG_XX) 编译链接 vmlinux 编译产物:vmlinux + .dtb + ko模块 rk3568-evb.dtb / rk3568-rock-3a.dtb / boot.img 平台代码 驱动代码 配置/构建 输出产物

4.1 内核目录树:从顶层到底层的职责划分

Linux内核源码采用分层目录结构,每个顶层目录都有明确的职责边界。理解这些目录的用途,是进行内核开发的第一步。对于RK3568开发,我们最常打交道的目录包括 arch/、drivers/、include/、kernel/ 和 Documentation/。

核心原理:目录即模块边界

内核顶层目录的设计遵循“关注点分离”原则。例如,arch/ 目录存放与CPU架构相关的代码,其中 arm64/ 子目录对应64位ARM架构,RK3568作为ARMv8-A架构芯片,其平台初始化代码、异常处理、内存管理底层实现均位于 arch/arm64/ 下。而 drivers/ 目录则按设备类型进一步细分,如 drivers/gpio/、drivers/i2c/、drivers/net/ 等,每个子目录对应一类外设驱动框架。

对于Rockchip平台,其特有的代码并不完全遵循标准内核目录,而是以 soc/rockchip/ 或 mfd/rockchip/ 等形式嵌入到 drivers/ 下。这种设计既保持了与上游内核的兼容性,又方便Rockchip维护自己的BSP补丁。

判断方法:快速定位目录归属

当我们需要查找某个功能的代码时,可以遵循以下路径判断:

  • CPU/架构相关:查看 arch/arm64/ 下的 kernel/、mm/、include/ 子目录。
  • 设备驱动:根据设备类别进入 drivers/ 下对应子目录,例如显示驱动在 drivers/gpu/drm/rockchip/,MIPI DSI在 drivers/gpu/drm/rockchip/dsi/。
  • 设备树文件:统一存放在 arch/arm64/boot/dts/rockchip/ 目录下,文件名以 rk3568- 开头。
  • 平台初始化代码:在 arch/arm64/mach-rockchip/ 或 drivers/soc/rockchip/ 中。
目录路径职责说明RK3568开发关注点
arch/arm64/ARM64架构核心代码启动流程、异常向量、DMA映射
arch/arm64/boot/dts/rockchip/Rockchip平台设备树源文件rk3568-evb.dts、rk3568-rock-3a.dts
drivers/soc/rockchip/Rockchip SoC级驱动电源管理、系统控制、IO域配置
drivers/clk/rockchip/时钟驱动clk-rk3568.c 时钟树初始化
drivers/pinctrl/rockchip/引脚复用与GPIO控制pinctrl-rk3568.c 引脚功能配置
drivers/phy/rockchip/PHY驱动(USB/PCIe/SATA)phy-rockchip-naneng-combphy.c
include/dt-bindings/设备树绑定头文件clock/rk3568-cru.h、power/rk3568-power.h
实战提示:在RK3568的SDK中,内核源码通常位于 kernel/ 目录,而设备树文件可能被单独抽取到 kernel/arch/arm64/boot/dts/rockchip/。使用 find . -name "*rk3568*" 可以快速列出所有相关文件。

4.2 Kconfig与Makefile机制:配置与编译的协同工作

Kconfig和Makefile是内核配置与编译的两大支柱。Kconfig定义了配置选项的层次结构和依赖关系,而Makefile则根据配置结果决定哪些文件被编译、如何编译。两者通过 .config 文件建立联系。

核心原理:配置到编译的完整链路

当开发者执行 make menuconfig 时,内核构建系统会读取各目录下的Kconfig文件,生成配置界面。用户选择保存后,生成 .config 文件。随后执行 make 时,顶层Makefile会读取 .config,将每个配置项转换为 CONFIG_XXX=y 或 CONFIG_XXX=m 的变量。各子目录的Makefile通过 obj-$(CONFIG_XXX) 语法判断是否编译对应源文件。

以RK3568的GMAC(以太网)驱动为例,其Kconfig位于 drivers/net/ethernet/stmicro/stmmac/Kconfig,定义了 CONFIG_STMMAC_ETH 选项。Makefile中则包含 obj-$(CONFIG_STMMAC_ETH) += stmmac.o。当我们在menuconfig中启用该选项后,.config 中会出现 CONFIG_STMMAC_ETH=y,最终stmmac.o被编译并链接进内核。

操作流程:添加一个新的驱动配置项

  1. 在驱动源码所在目录创建或修改Kconfig文件,添加如下内容:
    config ROCKCHIP_MY_DRIVER
        tristate "Rockchip My Driver Support"
        depends on ARCH_ROCKCHIP
        default y
        help
          This option enables support for Rockchip custom driver.
  2. 在同一目录的Makefile中添加:
    obj-$(CONFIG_ROCKCHIP_MY_DRIVER) += my_driver.o
  3. 执行 make menuconfig,在Device Drivers下找到新选项并启用。
  4. 保存退出后,检查 .config 中是否生成 CONFIG_ROCKCHIP_MY_DRIVER=y。
  5. 执行 make 编译,确认 my_driver.o 被生成。
常见误区:很多初学者在修改Kconfig后忘记在Makefile中添加对应的 obj-$(CONFIG_XXX) 条目,导致配置选项存在但编译时被忽略。务必确保Kconfig与Makefile成对修改。

案例拆解:RK3568 defconfig的配置继承

Rockchip平台使用 arch/arm64/configs/rockchip_linux_defconfig 作为默认配置。该文件并非完整列出所有配置项,而是利用内核的 make savedefconfig 机制,只保存与默认值不同的选项。例如,要启用RK3568的NPU驱动,我们需要在defconfig中添加:

CONFIG_ROCKCHIP_NPU=y
CONFIG_ROCKCHIP_RKNPU=y
CONFIG_ROCKCHIP_RKNPU_DRIVER=y

然后执行 make rockchip_linux_defconfig 生成完整的 .config。这种做法的好处是配置简洁、易于维护,但缺点是如果依赖关系复杂,可能因缺少某个依赖选项而导致配置不生效。此时应使用 make menuconfig 手动检查依赖项。

4.3 RK3568平台代码位置:从设备树到驱动实现

RK3568的BSP代码分布遵循“设备树描述硬件、驱动实现逻辑”的原则。设备树文件(.dts)定义了硬件拓扑和资源,驱动代码则通过匹配设备树节点来绑定和操作硬件。

核心原理:设备树与驱动的匹配机制

RK3568的设备树文件采用 .dtsi(SoC级描述)与 .dts(板级描述)分离的结构。例如,rk3568.dtsi 包含CPU、内存控制器、时钟、中断控制器等SoC内部模块的描述,而 rk3568-evb.dts 则包含具体开发板上的外设(如以太网PHY、HDMI接口、USB端口等)。驱动通过 compatible 属性与设备树节点匹配,例如:

gmac1: ethernet@fe010000 {
    compatible = "rockchip,rk3568-gmac";
    reg = <0x0 0xfe010000 0x0 0x10000>;
    interrupts = <GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>;
    ...
};

对应的驱动在 drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c 中通过 of_match_table 匹配 "rockchip,rk3568-gmac"。

判断方法:定位RK3568特定代码的步骤

  • 查看设备树:进入 arch/arm64/boot/dts/rockchip/,打开 rk3568.dtsi 了解SoC级硬件资源。
  • 搜索compatible字符串:在 drivers/ 目录下使用 grep -r "rockchip,rk3568" 查找匹配的驱动文件。
  • 检查平台驱动注册:在 drivers/soc/rockchip/ 下查看 rockchip.c 或 pm_domains.c 等文件,了解电源域和系统控制逻辑。
  • 确认时钟与引脚:drivers/clk/rockchip/clk-rk3568.c 定义了时钟树,drivers/pinctrl/rockchip/pinctrl-rk3568.c 定义了引脚复用。

案例拆解:调试RK3568的I2C控制器

假设我们需要调试RK3568的I2C2控制器,步骤如下:

  1. 设备树定位:在 rk3568.dtsi 中搜索 i2c2,找到其基地址(如 0xfe5c0000)和中断号。
  2. 驱动确认:I2C控制器驱动位于 drivers/i2c/busses/i2c-rk3x.c,其 compatible 为 "rockchip,rk3568-i2c"。
  3. 板级配置:在 rk3568-evb.dts 中检查I2C2的 status 属性是否为 "okay",以及连接的从设备节点。
  4. 调试验证:使用 i2cdetect -y 2 扫描总线,确认设备地址;使用 i2cget 读取寄存器值。
经验总结:RK3568的BSP代码质量较高,但仍有部分驱动依赖外部补丁。在开发前,建议先阅读 Documentation/devicetree/bindings/ 下对应设备的绑定文档,确认设备树属性是否符合规范。同时,利用 scripts/checkpatch.pl 检查代码风格,避免因格式问题被社区拒绝。

4.4 常见误区与调试技巧

在内核源码结构的学习和实践中,开发者常会遇到以下问题,需要特别注意规避。

误区一:混淆设备树与驱动代码的修改位置

设备树描述硬件“有什么”,驱动代码实现“怎么用”。修改硬件连接关系(如更换GPIO引脚)应修改设备树,修改驱动逻辑(如调整时序参数)应修改驱动源码。如果两者混淆,可能导致编译通过但运行时行为异常。

误区二:忽略Kconfig的依赖关系

RK3568的某些驱动依赖于特定的电源管理框架或时钟框架。例如,启用PCIe驱动前,必须确保 CONFIG_PCIEPORTBUS 和 CONFIG_PCI_MSI 已启用。使用 make menuconfig 时,注意查看选项的依赖提示,必要时使用 / 键搜索配置项。

误区三:直接修改 .config 文件

手动编辑 .config 容易引入格式错误或依赖不一致。正确做法是使用 make menuconfig 或 make defconfig 生成配置,然后通过 make savedefconfig 生成精简的defconfig文件。

调试技巧:利用内核日志验证配置

编译完成后,通过 dmesg | grep -i "rockchip\|rk3568" 查看内核启动日志,确认平台代码是否正确初始化。例如,如果看到 rk3568-gmac fe010000.ethernet: MAC address 类似输出,说明GMAC驱动已成功绑定设备树节点。

调试场景推荐命令/工具预期结果
确认设备树是否被正确编译fdtdump rk3568-evb.dtb显示设备树节点结构
检查驱动是否加载lsmod | grep rk列出已加载的Rockchip模块
查看设备树与驱动的匹配情况cat /sys/firmware/devicetree/base/展示运行时设备树
跟踪驱动初始化流程echo 'file drivers/i2c/busses/i2c-rk3x.c +p' > /sys/kernel/debug/dynamic_debug/control动态开启调试日志