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回调被调用的完整流程。
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中被正确回滚,避免资源泄漏或系统不稳定。