嵌入式Linux基础:交叉编译工具链、内核源码结构、设备树基本概念与语法

在瑞芯微RK3588平台上进行外设驱动开发,首先需要建立三个核心认知:交叉编译工具链解决“在哪里编译”的问题,内核源码结构解决“驱动代码放在哪里、如何组织”的问题,而设备树(Device Tree)则解决“硬件资源如何描述、驱动如何匹配硬件”的问题。这三者构成了嵌入式Linux驱动开发的基石。本章将围绕这三个主题,从原理到实践,结合RK3588的具体情况展开深入讲解。

RK3588 嵌入式Linux驱动开发三大基础 交叉编译工具链 • aarch64-linux-gnu-* • 主机x86 → 目标ARM • GCC/ld/objcopy • sysroot 头文件/库 • 环境变量 CROSS_COMPILE → 产出: vmlinux, .ko, dtb 内核源码结构 • arch/arm64/boot/dts • drivers/ 子系统目录 • include/linux 头文件 • Documentation/devicetree • Kconfig / Makefile → 驱动模块化编译 设备树 (Device Tree) • dts/dtsi 源文件 • dtc 编译器 → dtb • 节点/属性/reg/中断 • compatible 匹配驱动 • pinctrl/时钟/电源 → 硬件描述与驱动解耦 三者在驱动开发中的协作关系 1. 设备树描述硬件资源 2. 驱动通过compatible匹配节点 3. 交叉工具链编译驱动模块 4. 内核加载驱动并解析设备树 典型开发流程 编写/修改 dts 编译 dtb 编译驱动 .ko 部署到RK3588 图:RK3588驱动开发三大基础及其协作流程

2.1 交叉编译工具链:原理与配置

核心原理

交叉编译(Cross Compilation)是指在一种架构的主机(Host)上,编译生成另一种架构目标机(Target)可执行代码的过程。在RK3588开发中,主机通常是x86_64架构的Ubuntu工作站,目标机则是ARMv8.2-A架构的RK3588 SoC。由于RK3588的算力有限且通常无完整的编译环境,我们必须在高性能主机上完成内核、驱动模块和应用程序的编译。

交叉编译工具链的核心组件包括:

  • 交叉编译器:如 aarch64-linux-gnu-gcc,用于将C/C++源码编译为ARM64指令集的目标文件。
  • 汇编器与链接器:aarch64-linux-gnu-as 和 aarch64-linux-gnu-ld,负责汇编和链接。
  • 库与头文件(sysroot):目标机运行所需的C库(如glibc或uClibc)和内核头文件,确保编译时能找到正确的API声明。
  • 二进制工具:objcopy、objdump、strip 等,用于格式转换、反汇编和裁剪。

对于RK3588,Rockchip官方推荐使用 aarch64-linux-gnu- 前缀的工具链,通常基于GCC 10.3或更高版本。在SDK中,工具链路径通常位于 prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin。

判断方法与操作流程

要确认工具链是否可用,执行以下命令:

# 查看工具链版本
export PATH=/path/to/toolchain/bin:$PATH
aarch64-linux-gnu-gcc --version

# 查看默认目标架构
aarch64-linux-gnu-gcc -v 2>&1 | grep Target

在编译内核或驱动时,需要设置两个关键环境变量:

export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-

其中 ARCH 告诉内核构建系统目标架构为arm64,CROSS_COMPILE 指定交叉编译工具链前缀。编译内核时,make 命令会自动调用 aarch64-linux-gnu-gcc 等工具。

案例拆解:编译一个外部驱动模块

假设我们为RK3588编写了一个简单的字符设备驱动 hello_rk3588.c,其Makefile如下:

obj-m := hello_rk3588.o
KERNEL_DIR := /path/to/kernel-source
PWD := $(shell pwd)

all:
    make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -C $(KERNEL_DIR) M=$(PWD) modules
clean:
    make -C $(KERNEL_DIR) M=$(PWD) clean

执行 make 后,会生成 hello_rk3588.ko 文件。使用 file 命令验证其架构:

file hello_rk3588.ko
# 输出: ELF 64-bit LSB relocatable, ARM aarch64, version 1 (SYSV), BuildID[sha1]=..., not stripped

确认是ARM aarch64架构后,即可通过 scp 或 adb push 将其部署到RK3588开发板上,使用 insmod 加载。

常见误区:不要使用主机自带的 gcc 编译驱动模块,否则会生成x86_64架构的 .ko 文件,加载时内核会报 Invalid module format 错误。另外,内核源码版本必须与开发板上运行的内核版本完全一致,否则模块加载会因版本魔术(vermagic)不匹配而失败。

2.2 内核源码结构:从全局到驱动目录

核心原理

Linux内核源码是一个庞大而有序的工程。对于驱动开发者而言,理解其目录结构有助于快速定位代码、编写Kconfig和Makefile。RK3588的SDK通常基于Linux 5.10或6.1内核,其顶层目录结构如下:

目录作用驱动开发关注点
arch/体系结构相关代码arch/arm64/boot/dts/rockchip/ 存放RK3588设备树源文件
drivers/设备驱动子目录drivers/gpio, drivers/i2c, drivers/spi, drivers/mfd 等
include/内核头文件include/linux/ 下为驱动API,include/dt-bindings/ 为设备树绑定宏
Documentation/内核文档Documentation/devicetree/bindings/ 为设备树绑定文档
scripts/构建脚本checkpatch.pl 用于代码风格检查
tools/用户态工具有时包含调试和测试工具

驱动开发的核心区域是 drivers/ 目录,其内部按子系统分类,如 drivers/i2c、drivers/spi、drivers/pinctrl 等。每个子系统目录下通常包含:

  • 核心框架代码:如 i2c-core.c,实现总线协议和驱动模型。
  • 控制器驱动:如 i2c-rockchip.c,实现RK3588的I2C控制器适配器。
  • 客户端驱动:如 eeprom.c,挂载在I2C总线上的具体设备驱动。

判断方法与操作流程

当需要添加一个新的外设驱动时,应遵循以下流程:

  1. 判断外设属于哪种总线类型(I2C、SPI、PCIe、USB等),进入对应的 drivers/ 子目录。
  2. 查看该子目录下的 Kconfig 文件,了解已有的配置选项,添加新的配置项。
  3. 查看 Makefile,将新驱动源文件与配置项关联。
  4. 编写驱动源码,并参考同目录下类似驱动的实现。

例如,若要添加一个I2C接口的温湿度传感器,应在 drivers/i2c 下新建 htu21d.c,并在 Kconfig 中添加 config SENSORS_HTU21D,在 Makefile 中添加 obj-$(CONFIG_SENSORS_HTU21D) += htu21d.o。

案例拆解:定位RK3588的GPIO驱动

RK3588的GPIO驱动位于 drivers/pinctrl/pinctrl-rockchip.c。该文件实现了 pinctrl 子系统,负责引脚复用、上拉/下拉、驱动强度等配置。设备树中的 pinctrl-0 属性引用了该驱动提供的引脚配置函数。当我们在设备树中为某个外设配置引脚时,内核会通过 pinctrl 框架调用此驱动完成硬件初始化。

通过阅读该驱动,可以了解到RK3588的GPIO bank分为4组(GPIO0-GPIO4),每组32个引脚,支持复用功能(如I2C、UART、SPI)。驱动中通过 rockchip_pinctrl_set_mux 函数设置引脚复用,通过 rockchip_pinconf_set 设置电气属性。

经验提醒:在修改内核配置时,推荐使用 make menuconfig 进行图形化配置。对于RK3588 SDK,通常使用 make rockchip_defconfig 生成默认配置,然后在此基础上调整。配置完成后,务必保存为新的 defconfig,以便后续复用。

2.3 设备树:硬件描述语言与驱动匹配机制

核心原理

设备树是一种描述硬件资源的数据结构,它通过节点(Node)和属性(Property)来描述SoC内部外设、外部总线设备、中断、时钟、GPIO、DMA等资源。设备树源文件(.dts)经过设备树编译器(dtc)编译为二进制设备树(.dtb),在系统启动时由Bootloader加载并传递给内核。

设备树的核心价值在于将硬件配置信息从内核源码中剥离出来,使得同一份内核镜像可以支持不同的硬件平台。驱动通过 compatible 属性与设备树节点匹配,从而获取硬件资源。

一个典型的设备树节点如下:

&i2c2 {
    status = "okay";
    clock-frequency = <400000>;

    htu21d@40 {
        compatible = "meas,htu21d";
        reg = <0x40>;
        interrupt-parent = <&gpio1>;
        interrupts = <RK_PB2 IRQ_TYPE_LEVEL_LOW>;
        vdd-supply = <&vcc_3v3>;
    };
};

其中 &i2c2 表示引用I2C2控制器节点,htu21d@40 是子节点,compatible 用于匹配驱动,reg 表示设备地址,interrupts 描述中断资源。

判断方法与操作流程

编写设备树节点的基本流程:

  1. 查阅RK3588的TRM(技术参考手册)或SDK中的 rk3588.dtsi,确认外设挂接的总线控制器节点名称(如 &i2c2)。
  2. 在板级 .dts 文件中,通过 &节点名 引用并添加子节点。
  3. 设置 status = "okay" 使能控制器,并配置必要属性(如时钟频率)。
  4. 为外设添加子节点,设置 compatible、reg、interrupts 等属性。
  5. 使用 make dtbs 编译设备树,生成 .dtb 文件。

验证设备树是否正确,可以在开发板上执行:

# 查看设备树中I2C2节点信息
ls /proc/device-tree/i2c@feac0000/
cat /proc/device-tree/i2c@feac0000/htu21d@40/compatible

案例拆解:为RK3588添加SPI设备节点

假设我们需要在RK3588的SPI1总线上挂载一个MCP2515 CAN控制器。首先在 rk3588.dtsi 中确认SPI1节点存在,然后在板级 .dts 中添加:

&spi1 {
    status = "okay";
    pinctrl-names = "default";
    pinctrl-0 = <&spi1m2_cs0 &spi1m2_pins>;
    mcp2515@0 {
        compatible = "microchip,mcp2515";
        reg = <0>;
        spi-max-frequency = <10000000>;
        clocks = <&cru CLK_REF_MCP2515>;
        interrupt-parent = <&gpio3>;
        interrupts = <RK_PA5 IRQ_TYPE_LEVEL_LOW>;
        vdd-supply = <&vcc_3v3>;
        xceiver-supply = <&vcc_3v3>;
    };
};

这里的关键属性包括:reg = <0> 表示片选0,spi-max-frequency 设置SPI时钟频率,interrupts 指定中断引脚。编译后,内核的 mcp251x 驱动会通过 compatible 匹配该节点,并调用 spi_setup 初始化设备。

深度复盘:在调试设备树时,常见的问题是节点地址与硬件实际地址不匹配。例如,I2C设备地址是7位还是8位,需要查阅芯片数据手册。另外,interrupts 属性中的 RK_PB2 宏定义在 include/dt-bindings/pinctrl/rockchip.h 中,表示GPIO1_B2引脚。如果宏定义缺失,编译会报错。建议在修改设备树后,使用 dtc -I fs -O dts /proc/device-tree 反编译当前设备树,确认实际生效的配置。

常见误区与总结

  • 误区1:设备树中的 reg 属性值必须与硬件实际地址一致,否则驱动无法正确访问寄存器。
  • 误区2:忘记设置 status = "okay",导致控制器被禁用,外设无法工作。
  • 误区3:中断号使用错误。RK3588的中断号是SPI(Shared Peripheral Interrupt)编号,需要在 include/dt-bindings/interrupt-controller/arm-gic.h 中查找正确的宏。
  • 误区4:编译设备树时,未包含正确的 #include 路径,导致找不到 rk3588.dtsi 或宏定义。

总结而言,交叉编译工具链、内核源码结构和设备树是嵌入式Linux驱动开发的“铁三角”。工具链决定了代码如何被编译,内核源码结构决定了驱动代码的组织方式,而设备树则提供了硬件与驱动之间的桥梁。掌握这三者,是后续编写RK3588外设驱动的基础。在实际开发中,建议先搭建好交叉编译环境,再深入阅读内核源码中与目标外设相关的子系统代码,最后通过设备树将硬件资源描述清楚,形成“硬件-设备树-驱动”的完整链路。