4. SPI设备驱动基本框架:module_init/exit、spi_driver结构体、probe/remove回调函数实现

SPI设备驱动在Linux内核中遵循标准的设备驱动模型(Driver Model)。驱动开发者需要注册一个spi_driver结构体,并实现其核心回调函数。本章将详细拆解驱动入口/出口函数、驱动结构体定义以及probe/remove回调的完整实现流程。

4.1 驱动入口与出口:module_init / module_exit

每个SPI设备驱动都必须通过module_init和module_exit宏向内核注册/注销驱动实例。这两个宏是驱动加载和卸载的“大门”。

宏 触发时机 典型操作
module_init(x) insmod 或内核启动自动加载时 调用 spi_register_driver()
module_exit(x) rmmod 或内核关闭时 调用 spi_unregister_driver()
// 驱动入口函数
static int __init my_spi_driver_init(void)
{
    int ret;
    ret = spi_register_driver(&my_spi_driver);
    if (ret)
        pr_err("Failed to register SPI driver: %d\n", ret);
    return ret;
}
module_init(my_spi_driver_init);

// 驱动出口函数
static void __exit my_spi_driver_exit(void)
{
    spi_unregister_driver(&my_spi_driver);
}
module_exit(my_spi_driver_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Embedded Linux Expert");
MODULE_DESCRIPTION("SPI device driver example");
关键点: __init 和 __exit 标记用于优化内存布局,驱动加载后 __init 函数占用的内存会被释放。

4.2 spi_driver 结构体:驱动的“身份证”

spi_driver 是SPI设备驱动的核心结构体,定义在 include/linux/spi/spi.h 中。它包含了驱动名称、设备ID表以及最重要的 probe 和 remove 回调函数指针。

struct spi_driver {
    const struct spi_device_id *id_table;  // 设备ID匹配表
    int (*probe)(struct spi_device *spi);  // 设备匹配成功时调用
    int (*remove)(struct spi_device *spi); // 设备移除时调用
    void (*shutdown)(struct spi_device *spi);
    struct device_driver driver;           // 通用驱动结构体
};
成员 作用 必须实现?
id_table 定义驱动支持的设备列表,用于和设备树/平台数据匹配 是(推荐)
probe 设备与驱动匹配成功后执行初始化 是
remove 设备移除或驱动卸载时执行清理 是
shutdown 系统关机或重启时调用 否
driver 内嵌通用驱动结构体,包含name、of_match_table等 是(需填充name)
// 设备ID表
static const struct spi_device_id my_spi_ids[] = {
    { "my_spi_dev", 0 },
    { }
};
MODULE_DEVICE_TABLE(spi, my_spi_ids);

// 设备树匹配表(可选,现代驱动推荐)
static const struct of_device_id my_spi_of_match[] = {
    { .compatible = "vendor,my-spi-device" },
    { }
};
MODULE_DEVICE_TABLE(of, my_spi_of_match);

// spi_driver 实例定义
static struct spi_driver my_spi_driver = {
    .driver = {
        .name = "my_spi_driver",
        .of_match_table = my_spi_of_match,
    },
    .id_table = my_spi_ids,
    .probe = my_spi_probe,
    .remove = my_spi_remove,
};
注意: 如果同时提供 id_table 和 of_match_table,内核优先使用设备树匹配。务必保证 driver.name 唯一,避免与其它驱动冲突。

4.3 probe 回调函数:设备初始化“工厂”

当内核发现一个SPI设备与驱动的 id_table 或 of_match_table 匹配时,自动调用 probe 函数。这是驱动完成硬件初始化、分配资源、注册字符设备/输入设备/网络设备等核心操作的入口。

static int my_spi_probe(struct spi_device *spi)
{
    struct my_spi_dev *dev;
    int ret;

    // 1. 设置SPI通信参数
    spi->mode = SPI_MODE_0;
    spi->max_speed_hz = 10 * 1000 * 1000; // 10MHz
    spi->bits_per_word = 8;
    ret = spi_setup(spi);
    if (ret)
        return ret;

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

    // 3. 初始化硬件(如复位、读取ID等)
    ret = my_spi_hw_init(dev);
    if (ret)
        return ret;

    // 4. 注册设备(如misc设备、IIO设备等)
    ret = my_spi_register_device(dev);
    if (ret)
        return ret;

    dev_info(&spi->dev, "SPI device probed successfully\n");
    return 0;
}
probe 函数典型流程:
  1. 配置SPI控制器参数(mode、speed、bits_per_word)并调用 spi_setup()。
  2. 使用 devm_kzalloc 分配驱动私有数据,利用设备资源管理自动释放。
  3. 执行硬件初始化序列(复位、读取设备ID、配置寄存器)。
  4. 向内核注册设备节点(如字符设备、IIO设备、输入设备等)。
  5. 使用 spi_set_drvdata() 保存私有数据指针,供后续回调使用。

4.4 remove 回调函数:优雅的“退场”

remove 函数在设备拔出或驱动卸载时被调用,负责释放 probe 中分配的所有资源。如果使用了 devm_ 系列API(如 devm_kzalloc、devm_request_irq),大部分资源会自动释放,但需要显式注销设备节点。

static int my_spi_remove(struct spi_device *spi)
{
    struct my_spi_dev *dev = spi_get_drvdata(spi);

    // 1. 注销设备节点
    my_spi_unregister_device(dev);

    // 2. 关闭硬件(如禁用中断、进入低功耗模式)
    my_spi_hw_deinit(dev);

    // 3. 清除驱动数据(可选,devm_ 会自动处理)
    spi_set_drvdata(spi, NULL);

    dev_info(&spi->dev, "SPI device removed\n");
    return 0;
}
最佳实践: 优先使用 devm_ 系列API(设备资源管理),这样即使 probe 中途失败或 remove 被跳过,内核也能自动回收资源,避免内存泄漏。

4.5 驱动框架完整流程图

下图展示了从驱动加载到设备移除的完整生命周期,以及 spi_driver 各组件之间的协作关系。

SPI设备驱动框架完整生命周期 module_init spi_register_driver id_table / of_match probe() 回调 设备读写操作 remove() 回调 spi_unregister_driver 生命周期说明 1. 加载驱动 → 注册spi_driver 2. 内核匹配设备ID/设备树 3. 调用probe初始化硬件 4. 卸载驱动 → 调用remove 关键数据结构 • spi_driver • spi_device_id • of_device_id

4.6 驱动框架核心要点总结

  • 入口/出口: module_init 调用 spi_register_driver,module_exit 调用 spi_unregister_driver。
  • 匹配机制: 内核优先使用设备树 of_match_table 匹配,其次使用 id_table 匹配。
  • probe 职责: 配置SPI参数、分配私有数据、初始化硬件、注册设备节点。
  • remove 职责: 注销设备节点、关闭硬件、释放资源(推荐使用 devm_ 系列API简化管理)。
  • 数据传递: 使用 spi_set_drvdata() 和 spi_get_drvdata() 在回调之间传递私有数据。
调试检查清单:
  • 确认 spi_driver.driver.name 与设备树中 compatible 或 spi-device 节点名称匹配。
  • 检查 probe 中是否调用了 spi_setup(),否则SPI控制器参数不会生效。
  • 验证 remove 中是否注销了 probe 中注册的所有设备节点,避免访问空指针。
  • 使用 devm_kzalloc 替代 kzalloc,减少资源泄漏风险。