3. SPI设备驱动基础:核心数据结构与回调实现

SPI设备驱动是Linux内核中SPI子系统的最上层,直接与具体的SPI外设(如ADC、DAC、传感器、显示屏等)交互。本章深入剖析驱动开发中必须掌握的两个核心结构体——spi_device和spi_driver,以及驱动生命周期中的关键回调函数probe和remove的实现方法。

3.1 spi_device结构体:SPI设备的抽象

spi_device结构体是内核中代表一个SPI从设备的抽象。它包含了设备在SPI总线上的所有硬件属性和配置参数。驱动开发者通过填充该结构体的字段,告知SPI控制器如何与从设备通信。

字段名 类型 说明
chip_select u8 片选信号编号,决定选中哪个从设备
max_speed_hz u32 SPI通信时钟频率(Hz),驱动需根据从设备手册设置
mode u8 SPI模式(CPOL/CPHA组合),取值0~3
bits_per_word u8 每次传输的数据位宽,通常为8或16
irq unsigned int 从设备的中断号(可选)
controller_state void * 控制器私有数据指针,驱动可在此存储运行时上下文
dev struct device 内核通用设备结构体,用于设备模型管理
关键提示: spi_device通常在设备树(Device Tree)中定义,内核在解析设备树时自动创建并注册。驱动开发者的主要任务是在probe回调中读取这些配置,而不是手动构造该结构体。

3.2 spi_driver结构体:驱动的注册入口

spi_driver是SPI设备驱动的核心载体,它实现了驱动与内核SPI核心的绑定。驱动开发者需要填充该结构体,并调用spi_register_driver()将其注册到内核。

#include <linux/spi/spi.h>

static struct spi_driver my_spi_driver = {
    .driver = {
        .name   = "my_spi_device",
        .of_match_table = my_spi_of_match,
        .owner  = THIS_MODULE,
    },
    .probe  = my_spi_probe,
    .remove = my_spi_remove,
    .id_table = my_spi_id_table,  // 可选,用于非设备树匹配
};

module_spi_driver(my_spi_driver);
成员 说明
driver.name 驱动名称,用于与设备树中的compatible属性匹配
driver.of_match_table 设备树匹配表,定义驱动支持的设备列表
probe 设备与驱动匹配成功时自动调用的初始化函数
remove 设备移除或驱动卸载时调用的清理函数
id_table 传统SPI设备ID表,用于非设备树平台
最佳实践: 使用module_spi_driver()宏可以同时完成module_init和module_exit的注册,简化驱动入口代码。

3.3 probe回调函数:驱动的初始化入口

probe回调是SPI设备驱动的核心函数。当内核检测到SPI从设备与驱动匹配时,调用此函数。驱动开发者在此完成硬件初始化、资源分配、数据结构和接口注册等工作。

static int my_spi_probe(struct spi_device *spi)
{
    struct my_device_data *data;
    int ret;

    // 1. 配置SPI设备参数
    spi->mode = SPI_MODE_0;
    spi->max_speed_hz = 1000000;  // 1MHz
    spi->bits_per_word = 8;
    ret = spi_setup(spi);
    if (ret < 0) {
        dev_err(&spi->dev, "spi_setup failed\n");
        return ret;
    }

    // 2. 分配私有数据结构
    data = devm_kzalloc(&spi->dev, sizeof(*data), GFP_KERNEL);
    if (!data)
        return -ENOMEM;
    spi_set_drvdata(spi, data);

    // 3. 初始化硬件(如复位、读取设备ID等)
    ret = my_device_hw_init(spi);
    if (ret)
        return ret;

    // 4. 注册字符设备或输入设备等
    ret = my_device_register(spi);
    if (ret)
        return ret;

    dev_info(&spi->dev, "SPI device probed successfully\n");
    return 0;
}
常见错误: 在probe中忘记调用spi_setup(),导致SPI控制器未按预期配置模式或速率,通信失败。务必在设置完spi_device字段后立即调用此函数。

3.4 remove回调函数:驱动的清理出口

remove回调在设备拔出或驱动卸载时被调用。它的职责是probe的逆操作:释放资源、注销接口、关闭硬件。

static int my_spi_remove(struct spi_device *spi)
{
    struct my_device_data *data = spi_get_drvdata(spi);

    // 1. 注销注册的接口(如字符设备、sysfs等)
    my_device_unregister(spi);

    // 2. 关闭硬件(如进入低功耗模式)
    my_device_hw_deinit(spi);

    // 3. 清理工作队列、定时器等
    cancel_work_sync(&data->work);

    // 4. 私有数据由devm_*自动释放,无需手动kfree
    dev_info(&spi->dev, "SPI device removed\n");
    return 0;
}
资源管理技巧: 优先使用devm_系列API(如devm_kzalloc、devm_request_irq),这些资源在设备移除或驱动卸载时会自动释放,减少remove中的手动清理工作,避免内存泄漏。

3.5 设备树匹配与probe触发流程

SPI设备驱动与设备的匹配通常通过设备树完成。以下SVG图展示了从设备树解析到probe回调被调用的完整流程。

SPI设备驱动probe触发流程 设备树(DTS) 内核解析DTS 创建spi_device 匹配spi_driver 调用probe回调 驱动初始化完成 匹配条件 • compatible属性匹配 • 或id_table匹配 • 或SPI设备名称匹配

3.6 完整驱动框架示例

以下代码展示了一个完整的SPI设备驱动骨架,整合了spi_driver、probe、remove和设备树匹配表。

#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/of.h>

struct my_device_data {
    struct spi_device *spi;
    struct mutex lock;
    // 其他私有成员...
};

static const struct of_device_id my_spi_of_match[] = {
    { .compatible = "vendor,my-spi-device", },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_spi_of_match);

static int my_spi_probe(struct spi_device *spi)
{
    struct my_device_data *data;
    int ret;

    data = devm_kzalloc(&spi->dev, sizeof(*data), GFP_KERNEL);
    if (!data)
        return -ENOMEM;

    data->spi = spi;
    mutex_init(&data->lock);
    spi_set_drvdata(spi, data);

    spi->mode = SPI_MODE_0;
    spi->max_speed_hz = 5000000;
    spi->bits_per_word = 8;
    ret = spi_setup(spi);
    if (ret)
        return ret;

    dev_info(&spi->dev, "probe success, speed=%dHz\n", spi->max_speed_hz);
    return 0;
}

static int my_spi_remove(struct spi_device *spi)
{
    struct my_device_data *data = spi_get_drvdata(spi);
    mutex_destroy(&data->lock);
    dev_info(&spi->dev, "device removed\n");
    return 0;
}

static struct spi_driver my_spi_driver = {
    .driver = {
        .name   = "my_spi_device",
        .of_match_table = my_spi_of_match,
        .owner  = THIS_MODULE,
    },
    .probe  = my_spi_probe,
    .remove = my_spi_remove,
};

module_spi_driver(my_spi_driver);

MODULE_AUTHOR("Kernel Developer");
MODULE_DESCRIPTION("SPI device driver example");
MODULE_LICENSE("GPL");

3.7 probe/remove回调对比总结

对比维度 probe回调 remove回调
调用时机 设备与驱动匹配成功时 设备移除或驱动卸载时
主要职责 初始化硬件、分配资源、注册接口 释放资源、注销接口、关闭硬件
错误处理 返回负值错误码,内核取消设备绑定 通常返回0,错误仅记录日志
资源管理 推荐使用devm_* API自动管理 手动清理非devm资源
执行顺序 先于任何I/O操作 最后执行,之后设备不可用
核心要点: probe和remove是SPI设备驱动的生命周期锚点。一个健壮的驱动必须确保probe中的每一步初始化都能在remove中被正确回滚,避免资源泄漏或系统不稳定。