4. SPI设备驱动注册:spi_driver结构体、spi_register_driver接口、probe/remove回调函数实现
SPI设备驱动的核心注册机制围绕 spi_driver 结构体展开,通过 spi_register_driver 接口将驱动与内核SPI核心层绑定。本章深入剖析驱动注册的完整流程,包括结构体定义、接口调用规范以及 probe/remove 回调函数的实现要点。
4.1 spi_driver结构体深度解析
spi_driver 是SPI设备驱动在Linux内核中的标准描述符,定义在 include/linux/spi/spi.h 中。它封装了驱动与设备匹配、初始化和销毁的全部回调接口。
| 成员 | 类型 | 说明 |
|---|---|---|
probe |
int (*)(struct spi_device *) |
设备匹配成功后调用,完成硬件初始化、资源分配 |
remove |
int (*)(struct spi_device *) |
设备移除或驱动卸载时调用,释放资源 |
shutdown |
void (*)(struct spi_device *) |
系统关机或重启时调用,执行优雅关闭 |
id_table |
const struct spi_device_id * |
设备ID匹配表,用于传统匹配方式 |
driver |
struct device_driver |
通用设备驱动结构体,包含名称、of_match_table等 |
关键点:
spi_driver 不直接包含SPI传输参数(如模式、频率、字长),这些参数通过 spi_device 结构体中的 mode、max_speed_hz、bits_per_word 字段传递,在 probe 回调中由驱动读取并配置。
// spi_driver 结构体定义(内核源码精简版)
struct spi_driver {
const struct spi_device_id *id_table;
int (*probe)(struct spi_device *spi);
int (*remove)(struct spi_device *spi);
void (*shutdown)(struct spi_device *spi);
struct device_driver driver;
};
4.2 spi_register_driver接口与注册流程
spi_register_driver 是驱动注册的入口函数,它将 spi_driver 实例注册到内核SPI子系统。该接口内部调用 driver_register 通用驱动注册函数,并建立与SPI总线类型的关联。
最佳实践: 推荐使用
module_spi_driver() 宏来简化驱动注册和卸载代码,该宏自动展开为 module_init 和 module_exit 的标准模板。
// 标准注册方式
static int __init my_spi_driver_init(void)
{
return spi_register_driver(&my_spi_driver);
}
static void __exit my_spi_driver_exit(void)
{
spi_unregister_driver(&my_spi_driver);
}
module_init(my_spi_driver_init);
module_exit(my_spi_driver_exit);
// 简化宏方式(推荐)
module_spi_driver(my_spi_driver);
避坑指南: 使用
module_spi_driver() 时,必须确保 spi_driver 实例的 driver.name 字段已正确初始化,否则驱动注册会因名称缺失而失败。
4.3 probe回调函数实现规范
probe 回调是驱动初始化的核心,它在设备与驱动匹配成功后由内核自动调用。实现质量直接影响设备的稳定性和性能。
probe回调标准实现步骤:
- 获取设备参数: 从
spi_device中读取mode、max_speed_hz、bits_per_word等配置。 - 设置SPI参数: 调用
spi_setup()将参数同步到控制器硬件。 - 分配私有数据: 使用
devm_kzalloc()分配驱动私有数据结构体。 - 初始化硬件: 执行设备复位、寄存器初始化等硬件操作。
- 注册子系统接口: 如字符设备、输入设备、IIO设备等。
- 保存设备指针: 将
spi_device指针存入私有数据供后续使用。
static int my_spi_probe(struct spi_device *spi)
{
struct my_spi_dev *dev;
int ret;
// 1. 读取并验证设备参数
dev_info(&spi->dev, "SPI mode 0x%x, speed %d Hz, bits %d\n",
spi->mode, spi->max_speed_hz, spi->bits_per_word);
// 2. 设置SPI参数(必须调用)
spi->mode = SPI_MODE_0;
spi->max_speed_hz = 10000000; // 10 MHz
spi->bits_per_word = 8;
ret = spi_setup(spi);
if (ret) {
dev_err(&spi->dev, "spi_setup failed: %d\n", ret);
return ret;
}
// 3. 分配私有数据
dev = devm_kzalloc(&spi->dev, sizeof(*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
dev->spi = spi;
spi_set_drvdata(spi, dev);
// 4. 硬件初始化(示例:复位设备)
ret = my_spi_hw_reset(dev);
if (ret)
return ret;
// 5. 注册字符设备或其它子系统
ret = my_spi_register_device(dev);
if (ret)
return ret;
dev_info(&spi->dev, "probe success\n");
return 0;
}
4.4 remove回调函数实现规范
remove 回调负责在设备拔出或驱动卸载时执行清理操作,确保资源完全释放,避免内存泄漏或内核崩溃。
remove回调标准实现步骤:
- 取消注册子系统接口(如字符设备、中断处理函数)。
- 释放硬件资源(如GPIO、DMA通道、中断)。
- 执行设备断电或进入低功耗模式。
- 清理工作队列、定时器等内核对象。
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. 释放中断(如果使用 devm 则自动释放)
free_irq(dev->irq, dev);
// 3. 硬件关闭
my_spi_hw_powerdown(dev);
// 4. 清理工作队列
cancel_work_sync(&dev->work);
dev_info(&spi->dev, "remove complete\n");
return 0;
}
注意: 如果使用
devm_ 系列API(如 devm_kzalloc、devm_request_irq),内核会在 remove 后自动释放资源,但显式清理硬件状态和取消异步操作仍是必要的。
4.5 驱动注册与回调完整流程图
4.6 完整驱动注册示例
以下是一个完整的SPI设备驱动注册示例,整合了 spi_driver 定义、设备树匹配表、probe/remove回调以及模块入口/出口。
#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/of.h>
/* 驱动私有数据结构 */
struct my_spi_dev {
struct spi_device *spi;
struct mutex lock;
/* 其他硬件相关字段 */
};
/* probe 回调 */
static int my_spi_probe(struct spi_device *spi)
{
struct my_spi_dev *dev;
int ret;
dev = devm_kzalloc(&spi->dev, sizeof(*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
dev->spi = spi;
mutex_init(&dev->lock);
spi_set_drvdata(spi, dev);
/* 配置SPI参数 */
spi->mode = SPI_MODE_0;
spi->max_speed_hz = 5000000; /* 5 MHz */
spi->bits_per_word = 8;
ret = spi_setup(spi);
if (ret) {
dev_err(&spi->dev, "spi_setup failed: %d\n", ret);
return ret;
}
dev_info(&spi->dev, "device probed at %d Hz\n", spi->max_speed_hz);
return 0;
}
/* remove 回调 */
static int my_spi_remove(struct spi_device *spi)
{
struct my_spi_dev *dev = spi_get_drvdata(spi);
/* 执行硬件关闭 */
dev_info(&spi->dev, "device removed\n");
return 0;
}
/* 设备树匹配表 */
static const struct of_device_id my_spi_of_match[] = {
{ .compatible = "myvendor,my-spi-device" },
{ }
};
MODULE_DEVICE_TABLE(of, my_spi_of_match);
/* SPI设备ID表(传统匹配) */
static const struct spi_device_id my_spi_id[] = {
{ "my-spi-device", 0 },
{ }
};
MODULE_DEVICE_TABLE(spi, my_spi_id);
/* 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_id,
.probe = my_spi_probe,
.remove = my_spi_remove,
};
/* 使用简化宏注册 */
module_spi_driver(my_spi_driver);
MODULE_AUTHOR("Linux SPI Expert");
MODULE_DESCRIPTION("SPI device driver example");
MODULE_LICENSE("GPL v2");
4.7 常见错误与调试技巧
| 错误场景 | 现象 | 解决方案 |
|---|---|---|
| probe未调用 | 驱动加载成功但probe不执行 | 检查设备树compatible属性与of_match_table是否匹配;检查spi_device_id表 |
| spi_setup失败 | probe返回-EINVAL | 确认mode、max_speed_hz、bits_per_word在控制器支持范围内 |
| 资源泄漏 | 多次加载/卸载后内存不足 | 优先使用devm_系列API;remove中显式取消工作队列和定时器 |
| 中断未释放 | 卸载后系统崩溃 | 使用devm_request_irq或remove中调用free_irq |
调试技巧: 在probe和remove函数中添加
dev_dbg() 或 dev_info() 日志,通过 dmesg 观察驱动加载/卸载流程。使用 ls /sys/bus/spi/drivers/ 查看已注册的SPI驱动列表。
4.8 设备树与驱动匹配关系图
核心要点: 设备树中的
compatible 属性与驱动中 of_match_table 的 .compatible 字符串必须完全一致(包括大小写和厂商前缀),否则probe回调不会被触发。这是SPI驱动开发中最常见的匹配失败原因。