嵌入式Linux驱动移植:从3 x到5 x内核升级 - 第4节

4.1 设备树(DTS)基础回顾

设备树(Device Tree,DT)是一种描述硬件资源的数据结构,由一系列节点(node)和属性(property)组成。在嵌入式Linux中,设备树源文件(.dts)经过设备树编译器(DTC)编译为二进制设备树(.dtb),由Bootloader传递给内核。内核通过解析设备树来发现平台设备、内存映射、中断、时钟、GPIO等硬件资源,从而替代了传统3.x内核中大量的板级文件(board-*.c)。

核心概念:设备树将硬件描述与内核驱动代码分离,使得同一份内核镜像可以支持多个硬件平台,只需更换不同的.dtb文件即可。

4.1.1 设备树语法速览

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

/dts-v1/;

/ {
    model = "TI AM335x BeagleBone Black";
    compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx";

    chosen {
        stdout-path = &uart0;
    };

    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x20000000>;  /* 512 MB */
    };

    leds {
        pinctrl-names = "default";
        pinctrl-0 = <&led_pins>;

        led@2 {
            label = "beaglebone:green:heartbeat";
            gpios = <&gpio1 21 GPIO_ACTIVE_HIGH>;
            linux,default-trigger = "heartbeat";
        };
    };
};
语法元素说明示例
节点以 node-name@unit-address 命名,描述一个硬件设备或总线i2c@44e0b000
属性键值对,值可以是字符串、32位整数数组、字节串等compatible = "ti,am335x-i2c";
reg描述设备地址空间,格式为 <address length>reg = <0x44e0b000 0x1000>;
interrupts描述中断号、触发类型interrupts = <56 2>;
pinctrl引脚复用控制,引用已定义的pinmux节点pinctrl-0 = <&i2c0_pins>;
status设备状态:okay、disabled、failstatus = "okay";
迁移提示:在3.x内核中,同样的I2C控制器信息通常写在arch/arm/mach-omap2/board-am335xevm.c中,使用platform_device和i2c_board_info结构体静态注册。5.x内核完全依赖设备树,不再需要这些板级文件。

4.1.2 设备树节点关系SVG图

下图展示了一个简化的设备树层级结构,从根节点到具体外设的路径关系:

设备树节点层级关系 / (根节点) chosen memory@80000000 cpus soc i2c@44e0b000 uart@44e09000 eeprom@50 rtc@6f 每个节点通过 compatible 属性与内核驱动匹配,reg 属性定义地址空间 中断、DMA、时钟等资源通过 phandle(&label)引用其他节点

4.2 3.x内核板级文件 vs 5.x内核设备树

在Linux 3.x时代,ARM架构的嵌入式平台普遍采用板级文件(board-*.c)来静态注册硬件设备和资源。每个开发板都需要在arch/arm/mach-xxx/目录下维护一个独立的board-xxx.c文件,其中包含大量的platform_device、i2c_board_info、spi_board_info结构体定义。这种方式导致内核代码冗余、维护困难,且无法支持多平台通用内核镜像。

4.2.1 核心差异对比表

对比维度3.x 板级文件5.x 设备树
硬件描述位置arch/arm/mach-xxx/board-xxx.carch/arm/boot/dts/xxx.dts
设备注册方式platform_device_register() 静态注册内核解析 DTB 后动态创建 platform_device
资源定义struct resource 数组,手动填写地址、中断reg、interrupts 属性自动解析
引脚复用pinmux_register() 或板级初始化函数pinctrl-* 属性引用 pinmux 节点
时钟/电源管理clk_get() + 板级时钟初始化clocks 属性 + 通用时钟框架
多板支持每个板子一个 board-*.c,编译时选择同一内核镜像,启动时选择 DTB
驱动匹配platform_driver 通过 id_table 或名称匹配of_match_table 通过 compatible 匹配
维护成本高,新增板子需大量重复代码低,只需编写 DTS 文件
迁移陷阱:在3.x内核中,部分驱动直接访问板级文件中的全局变量(如omap_gpio_base)来获取硬件信息。迁移到5.x设备树后,必须改为通过device_node和of_* API读取设备树属性,否则驱动将无法正确获取资源。

4.2.2 板级文件到设备树的迁移流程SVG图

下图展示了从3.x板级文件到5.x设备树的典型迁移路径:

从板级文件到设备树的迁移流程 board-xxx.c platform_device 静态注册 步骤1:提取硬件资源 地址、中断、GPIO、时钟 步骤2:编写DTS节点 compatible + reg + interrupts 步骤3:修改驱动匹配 of_match_table + of_* API xxx.dts 设备树源文件 迁移后,驱动不再依赖板级全局变量,所有硬件信息通过设备树传递 驱动代码变得平台无关,可在不同SoC间复用

4.3 设备树绑定文档的规范化

设备树绑定文档(Device Tree Binding Documentation)是描述设备树节点属性含义、数据类型、必选/可选字段的规范文档。在3.x内核中,绑定文档分散在Documentation/devicetree/bindings/目录下,格式不统一,部分绑定甚至缺失。5.x内核社区对绑定文档进行了严格的规范化,引入了YAML格式的绑定描述,并配合DT schema工具进行自动化校验。

4.3.1 绑定文档的演进对比

特性3.x 绑定文档5.x 绑定文档
格式纯文本(.txt),自由格式描述YAML(.yaml),结构化定义
校验方式人工审查,无自动化工具dt_binding_check + dtbs_check 自动化校验
属性定义文字描述,可能遗漏必选字段required 明确列出必选属性,properties 定义类型
示例有时包含DTS片段,但无语法检查示例DTS片段会被编译校验
维护者通常由驱动作者维护绑定文档与驱动分离,由设备树维护者审核

4.3.2 YAML绑定文档示例

以下是一个规范的YAML绑定文档示例,描述一个I2C EEPROM设备:

# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/eeprom/at24.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#

title: AT24 compatible I2C EEPROM

maintainers:
  - Bartosz Golaszewski <bgolaszewski@baylibre.com>

properties:
  compatible:
    enum:
      - atmel,at24c01
      - atmel,at24c02
      - atmel,at24c04
      - atmel,at24c08
      - atmel,at24c16

  reg:
    maxItems: 1
    description: I2C slave address

  pagesize:
    $ref: /schemas/types.yaml#/definitions/uint32
    description: Page size in bytes for write operations

  read-only:
    type: boolean
    description: Mark the EEPROM as read-only

required:
  - compatible
  - reg

additionalProperties: false

examples:
  - |
    eeprom@50 {
        compatible = "atmel,at24c02";
        reg = <0x50>;
        pagesize = <8>;
    };
规范化收益:YAML绑定文档使得设备树属性定义变得机器可读。内核编译时,dtbs_check目标会自动校验所有.dts文件是否符合对应的YAML绑定,提前发现属性缺失、类型错误、值越界等问题,极大降低了运行时因设备树错误导致的驱动加载失败。

4.3.3 绑定文档规范化流程SVG图

下图展示了从传统文本绑定到YAML规范化绑定的工作流:

设备树绑定文档规范化工作流 传统 .txt 绑定 人工编写,无校验 转换为 .yaml 格式 定义属性类型与约束 dt_binding_check 校验绑定文档自身 dtbs_check 校验所有DTS文件 规范化绑定文档 可维护、可校验 规范化后的绑定文档可在内核编译阶段自动发现设备树错误,避免运行时故障

4.4 驱动移植中的设备树适配要点

在将3.x内核驱动移植到5.x内核时,设备树相关的适配工作通常包括以下几个关键步骤:

  1. 移除板级依赖:删除驱动中对arch/arm/mach-xxx/中全局变量或函数的直接调用,改为通过device_node获取资源。
  2. 添加of_match_table:在platform_driver中增加of_match_table,并定义compatible字符串列表。
  3. 替换资源获取API:将platform_get_resource()替换为of_iomap()、of_property_read_u32()等设备树API。
  4. 处理中断:使用irq_of_parse_and_map()替代platform_get_irq()(后者在设备树环境下仍然可用,但推荐使用of_*系列)。
  5. 引脚控制:确保DTS中定义了pinctrl-*属性,驱动通过pinctrl子系统自动配置引脚复用。
  6. 时钟与电源:使用clk_get()配合设备树中的clocks属性,避免硬编码时钟名称。
关键API对照表:
3.x 板级API5.x 设备树API说明
platform_get_resource(pdev, IORESOURCE_MEM, 0)of_iomap(dev->of_node, 0)获取内存基地址并映射
platform_get_irq(pdev, 0)irq_of_parse_and_map(dev->of_node, 0)解析中断号
clk_get(&pdev->dev, "uart_clk")of_clk_get(dev->of_node, 0)从设备树获取时钟
gpio_request() + 板级GPIO号of_get_named_gpio() + devm_gpio_request()通过GPIO属性名获取引脚

4.4.1 驱动匹配方式变更示例

3.x内核中,驱动通常使用platform_driver的id_table进行匹配:

/* 3.x 风格:基于id_table匹配 */
static struct platform_device_id my_driver_ids[] = {
    { "my_device", 0 },
    { }
};

static struct platform_driver my_driver = {
    .probe  = my_probe,
    .remove = my_remove,
    .driver = {
        .name = "my_device",
    },
    .id_table = my_driver_ids,
};

在5.x内核中,推荐使用of_match_table进行设备树匹配:

/* 5.x 风格:基于of_match_table匹配 */
static const struct of_device_id my_of_match[] = {
    { .compatible = "vendor,my-device", },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_of_match);

static struct platform_driver my_driver = {
    .probe  = my_probe,
    .remove = my_remove,
    .driver = {
        .name = "my_device",
        .of_match_table = my_of_match,
    },
};
注意:如果驱动同时需要支持旧版板级文件和新版设备树,可以同时保留id_table和of_match_table。内核会优先使用of_match_table进行匹配。但在纯设备树平台(如5.x的ARM64、RISC-V)上,id_table将不会被使用,因此建议完全迁移到of_match_table。

4.5 本章小结

设备树是嵌入式Linux从3.x到5.x内核升级中最核心的变革之一。本章回顾了设备树的基础语法,对比了3.x板级文件与5.x设备树在硬件描述、资源注册、驱动匹配等方面的差异,并介绍了设备树绑定文档从自由文本到YAML规范化的演进。掌握设备树的编写与绑定规范,是成功完成驱动移植的前提。在实际移植工作中,建议优先为硬件平台编写完整的DTS文件,并利用dtbs_check工具确保设备树语法和属性定义的正确性。