2. Linux内核驱动基础回顾:字符设备驱动框架、platform驱动模型、设备树(DTS)基础

在开始Sensor驱动从ARM到RISC-V的移植适配之前,必须夯实Linux内核驱动的基础知识。本章聚焦三大核心主题:字符设备驱动框架、platform驱动模型以及设备树(DTS)基础。这三者构成了现代Linux驱动开发的基石,也是后续移植工作的核心操作对象。

学习提示:本章内容并非简单的理论回顾,而是为后续移植实战提供可直接对照的“参考坐标系”。请重点关注ARM与RISC-V平台在设备树描述和驱动匹配机制上的差异点。

2.1 字符设备驱动框架

字符设备是Linux驱动中最基础、最常用的设备类型,Sensor通常被抽象为字符设备。其核心框架围绕struct file_operations和struct cdev展开。

2.1.1 核心数据结构

数据结构 作用 关键成员
struct cdev 内核中表示字符设备的对象 owner, ops, dev
struct file_operations 定义设备支持的系统调用接口 open, read, write, ioctl, release
dev_t 设备号(主设备号 + 次设备号) MAJOR(), MINOR()

2.1.2 标准注册流程

  1. 分配设备号:使用alloc_chrdev_region()动态分配,或register_chrdev_region()静态指定。
  2. 初始化cdev:cdev_init()绑定file_operations。
  3. 添加cdev:cdev_add()将设备注册到内核。
  4. 创建设备节点:使用class_create()和device_create()在/dev/下自动生成节点。
// 字符设备驱动核心骨架示例
static int __init sensor_drv_init(void)
{
    dev_t dev_num;
    struct class *sensor_class;

    // 1. 分配设备号
    alloc_chrdev_region(&dev_num, 0, 1, "sensor_dev");

    // 2. 初始化cdev
    cdev_init(&sensor_cdev, &sensor_fops);
    sensor_cdev.owner = THIS_MODULE;

    // 3. 添加cdev到内核
    cdev_add(&sensor_cdev, dev_num, 1);

    // 4. 创建设备节点
    sensor_class = class_create(THIS_MODULE, "sensor_class");
    device_create(sensor_class, NULL, dev_num, NULL, "sensor0");

    return 0;
}

static void __exit sensor_drv_exit(void)
{
    dev_t dev_num = sensor_cdev.dev;
    // 反向清理:销毁设备节点、删除cdev、释放设备号
    device_destroy(sensor_class, dev_num);
    class_destroy(sensor_class);
    cdev_del(&sensor_cdev);
    unregister_chrdev_region(dev_num, 1);
}

module_init(sensor_drv_init);
module_exit(sensor_drv_exit);
移植关注点:字符设备框架本身是平台无关的,ARM和RISC-V上完全一致。但ioctl命令码的构造(_IOR/_IOW)涉及sizeof和数据类型对齐,在64位RISC-V平台上需注意long类型大小与ARM32的差异。

2.2 platform驱动模型

platform驱动模型是Linux设备驱动模型的核心,用于管理那些不依赖PCI、USB等热插拔总线的设备(如SoC内部外设、Sensor)。它将设备与驱动分离,通过匹配机制进行绑定。

2.2.1 模型三要素

platform驱动模型三要素 platform_device 设备资源(内存、中断、DMA) platform_driver probe/remove/PM回调 platform_bus 匹配表(match) 匹配成功 → 调用驱动probe()

2.2.2 匹配方式

匹配方式 匹配依据 典型场景
OF匹配 设备树中的compatible属性 现代ARM/RISC-V平台(主流方式)
ACPI匹配 ACPI表HID/CID x86/ARM服务器
ID表匹配 platform_device.name vs id_table 旧式平台或非设备树平台
名称回退 驱动名称与设备名称相同 兼容性回退机制
// platform_driver 定义示例(OF匹配方式)
static const struct of_device_id sensor_of_match[] = {
    { .compatible = "vendor,imu-sensor", },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, sensor_of_match);

static struct platform_driver sensor_platform_driver = {
    .probe  = sensor_probe,
    .remove = sensor_remove,
    .driver = {
        .name = "sensor_imu",
        .of_match_table = sensor_of_match,
        .pm = &sensor_pm_ops,
    },
};

module_platform_driver(sensor_platform_driver);
移植陷阱:ARM平台常使用arch/arm/boot/dts/下的设备树,而RISC-V平台使用arch/riscv/boot/dts/。驱动中的compatible字符串必须与设备树中的定义完全一致,包括大小写和标点符号。移植时需检查RISC-V平台设备树中Sensor节点的compatible值。

2.3 设备树(DTS)基础

设备树(Device Tree)是一种描述硬件资源的数据结构,用于将硬件配置从内核代码中解耦。在ARM和RISC-V平台上,设备树是描述Sensor挂接位置、中断号、I2C/SPI总线地址等信息的标准方式。

2.3.1 设备树语法核心

// 设备树源文件(.dts)示例:I2C总线上的Sensor节点
/dts-v1/;

/ {
    model = "RISC-V Sensor Board";
    compatible = "vendor,riscv-sensor-board";

    soc {
        #address-cells = <1>;
        #size-cells = <1>;

        i2c0: i2c@10010000 {
            compatible = "snps,designware-i2c";
            reg = <0x10010000 0x1000>;
            interrupts = <31>;
            #address-cells = <1>;
            #size-cells = <0>;

            // Sensor子节点
            imu_sensor: imu@68 {
                compatible = "vendor,imu-sensor";
                reg = <0x68>;               // I2C从设备地址
                interrupt-parent = <&gpio0>;
                interrupts = <5 2>;          // GPIO5, 下降沿触发
                vdd-supply = <&reg_3v3>;    // 电源供应
                mount-matrix = "0", "1", "0",
                               "-1", "0", "0",
                               "0", "0", "1"; // 安装方向矩阵
            };
        };
    };
};

2.3.2 关键属性说明

属性 含义 Sensor驱动常用场景
compatible 设备兼容性字符串,驱动匹配依据 必须与驱动of_match_table一致
reg 寄存器地址或I2C/SPI从设备地址 Sensor的I2C地址或SPI片选
interrupts 中断号及触发类型 Sensor数据就绪中断
xxx-supply 电源 regulator 引用 Sensor的VDD/VDDIO供电控制
pinctrl-* 引脚复用配置 I2C/SPI引脚功能选择

2.3.3 驱动中读取设备树信息

// 在probe函数中解析设备树属性
static int sensor_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct device_node *np = dev->of_node;
    struct i2c_client *client;
    u32 irq_num;
    u32 reg_addr;
    int ret;

    // 1. 获取I2C从设备地址
    ret = of_property_read_u32(np, "reg", &reg_addr);
    if (ret) {
        dev_err(dev, "failed to get reg property\n");
        return ret;
    }

    // 2. 获取中断号
    irq_num = irq_of_parse_and_map(np, 0);
    if (!irq_num) {
        dev_err(dev, "failed to parse interrupt\n");
        return -EINVAL;
    }

    // 3. 获取电源供应
    struct regulator *vdd = devm_regulator_get(dev, "vdd");
    if (IS_ERR(vdd)) {
        dev_err(dev, "failed to get vdd regulator\n");
        return PTR_ERR(vdd);
    }

    // 4. 获取自定义属性(如安装矩阵)
    struct property *prop = of_find_property(np, "mount-matrix", NULL);
    if (prop) {
        // 解析安装方向矩阵
    }

    return 0;
}

2.3.4 ARM vs RISC-V 设备树差异

ARM vs RISC-V 设备树关键差异 ARM 平台 • 根节点 compatible 含 "arm,vexpress" 等 • CPU节点: arm,cortex-a9 • 中断控制器: arm,gic-400 • 时钟: fixed-clock / arm,pll RISC-V 平台 • 根节点 compatible 含 "riscv" • CPU节点: riscv,rocket0 / sifive,u74 • 中断控制器: riscv,cpu-intc • 时钟: sifive,clint / fixed-clock 对比
移植核心要点:Sensor驱动本身的设备树节点结构(compatible、reg、interrupts、xxx-supply)在ARM和RISC-V平台上基本一致。主要差异在于父节点(如I2C控制器、中断控制器、GPIO控制器)的compatible字符串和寄存器布局。移植时只需修改设备树包含的头文件路径和父节点引用,Sensor子节点通常无需改动。

2.4 三者的协同关系

字符设备框架、platform驱动模型和设备树在实际Sensor驱动中协同工作:

  • 设备树描述Sensor的硬件连接和配置参数。
  • platform驱动模型负责在设备树中查找匹配的Sensor节点,并触发probe()。
  • 字符设备框架在probe()中注册,为用户空间提供open/read/ioctl接口。
// 三者协同的典型probe函数结构
static int sensor_probe(struct platform_device *pdev)
{
    // 1. 从设备树获取资源(中断、地址、电源)
    struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    int irq = platform_get_irq(pdev, 0);

    // 2. 初始化硬件(寄存器映射、复位、配置)
    sensor->base = devm_ioremap_resource(&pdev->dev, res);

    // 3. 注册字符设备(为用户空间提供接口)
    alloc_chrdev_region(&sensor->dev_num, 0, 1, "sensor");
    cdev_init(&sensor->cdev, &sensor_fops);
    cdev_add(&sensor->cdev, sensor->dev_num, 1);

    // 4. 创建sysfs设备节点
    device_create(sensor_class, &pdev->dev, sensor->dev_num, NULL, "sensor%d", sensor->id);

    return 0;
}
回顾总结:本章的三个知识点构成了Sensor驱动移植的“三脚架”。在后续章节中,我们将反复使用设备树解析、platform驱动匹配和字符设备注册这三个操作。请确保理解每个环节的作用,特别是设备树中compatible字符串与驱动中of_match_table的对应关系——这是跨平台移植时最容易出错的地方。