3. SPI控制器驱动注册与注销:核心接口与回调机制
在Linux内核中,SPI控制器驱动遵循标准的平台驱动模型。驱动通过 platform_driver 结构体与内核框架绑定,利用 probe 和 remove 回调函数完成控制器的初始化和资源释放。本章将深入剖析驱动注册/注销的完整流程,并详细讲解 spi_alloc_master 与 spi_register_master 两个核心接口的使用方法。
3.1 platform_driver 结构体:驱动与设备的桥梁
platform_driver 是 SPI 控制器驱动注册的入口。它定义了驱动与内核平台层交互的接口,包括探测、移除、电源管理等回调函数。
| 成员 | 类型 | 说明 |
|---|---|---|
| probe | int (*probe)(struct platform_device *) | 设备匹配成功后调用,完成控制器硬件初始化、master 分配与注册 |
| remove | int (*remove)(struct platform_device *) | 设备移除时调用,执行 master 注销、资源释放、硬件去初始化 |
| shutdown | void (*shutdown)(struct platform_device *) | 系统关机或重启时调用,用于优雅关闭 SPI 控制器 |
| driver | struct device_driver | 内嵌的通用驱动结构,包含 name、of_match_table、pm 等字段 |
| id_table | const struct platform_device_id * | 传统设备 ID 匹配表,用于非设备树平台 |
关键点:
probe 回调是 SPI 控制器驱动的“构造函数”,所有硬件资源(时钟、中断、DMA、GPIO)的获取和 master 对象的创建都在此完成。remove 则是“析构函数”,必须确保所有资源被正确释放,避免内存泄漏或内核崩溃。
3.2 probe/remove 回调函数:驱动的生命周期管理
当内核发现平台设备与驱动匹配时,自动调用 probe 函数。SPI 控制器驱动的 probe 通常遵循以下步骤:
- 获取硬件资源: 从
platform_device中获取 IO 内存、中断号、时钟、DMA 通道等。 - 分配 master 对象: 调用
spi_alloc_master()分配一个spi_master结构体。 - 设置 master 参数: 配置总线号、模式、传输队列、传输函数指针等。
- 硬件初始化: 复位控制器、配置时钟极性/相位、设置 FIFO 阈值、使能中断。
- 注册 master: 调用
spi_register_master()将 master 注册到 SPI 核心层。
最佳实践: 在
probe 中建议使用 devm_* 系列资源管理函数(如 devm_ioremap_resource、devm_clk_get),这样 remove 函数可以大幅简化,甚至在某些场景下可以省略。
以下是一个典型的 probe 函数代码框架:
static int spi_controller_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct spi_master *master;
struct spi_controller_data *cdata;
struct resource *res;
int ret;
/* 1. 获取硬件资源 */
cdata = devm_kzalloc(dev, sizeof(*cdata), GFP_KERNEL);
if (!cdata)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
cdata->base = devm_ioremap_resource(dev, res);
if (IS_ERR(cdata->base))
return PTR_ERR(cdata->base);
cdata->irq = platform_get_irq(pdev, 0);
if (cdata->irq < 0)
return cdata->irq;
/* 2. 分配 master 对象 */
master = spi_alloc_master(dev, sizeof(struct spi_controller_data));
if (!master) {
dev_err(dev, "Failed to allocate SPI master\n");
return -ENOMEM;
}
/* 3. 设置 master 参数 */
master->bus_num = pdev->id;
master->num_chipselect = 4;
master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;
master->transfer_one = spi_controller_transfer_one;
master->setup = spi_controller_setup;
platform_set_drvdata(pdev, master);
/* 4. 硬件初始化 */
spi_controller_hw_init(cdata);
/* 5. 注册 master */
ret = spi_register_master(master);
if (ret) {
dev_err(dev, "Failed to register SPI master: %d\n", ret);
spi_master_put(master);
return ret;
}
dev_info(dev, "SPI controller registered successfully\n");
return 0;
}
注意:
spi_alloc_master 返回的 master 对象引用计数为 1。如果 spi_register_master 失败,必须调用 spi_master_put 释放引用,否则会造成内存泄漏。
3.3 spi_alloc_master 与 spi_register_master 接口详解
这两个接口是 SPI 控制器驱动与 SPI 核心层交互的核心 API。理解它们的工作原理是编写正确驱动的关键。
3.3.1 spi_alloc_master:分配并初始化 master 对象
spi_alloc_master 在 SPI 核心层中分配一个 spi_master 结构体,并为其关联的设备初始化部分字段。
| 函数原型 | 参数说明 | 返回值 |
|---|---|---|
struct spi_master *spi_alloc_master(struct device *dev, unsigned int size) |
dev: 控制器设备指针;size: 附加的私有数据大小 | 成功返回 master 指针,失败返回 NULL |
该函数执行以下操作:
- 通过
kzalloc分配spi_master结构体 + 附加私有数据的内存块。 - 初始化 master 内部的设备对象、传输队列、锁、引用计数。
- 将附加私有数据的起始地址赋值给
master->dev.driver_data。 - 设置默认的传输模式、时钟速率等参数。
3.3.2 spi_register_master:将 master 注册到 SPI 核心
spi_register_master 将分配好的 master 对象正式注册到内核 SPI 子系统,使其对 SPI 设备驱动可见。
| 函数原型 | 参数说明 | 返回值 |
|---|---|---|
int spi_register_master(struct spi_master *master) |
master: 已分配并配置好的 master 指针 | 成功返回 0,失败返回负的错误码 |
注册过程的核心步骤:
- 检查 master 的
transfer_one或transfer_one_message回调是否已设置。 - 为 master 分配唯一的总线编号(如果
bus_num为负值则动态分配)。 - 创建 SPI 总线设备,并注册到设备模型。
- 扫描设备树或 ACPI 表,为每个子节点创建 SPI 设备。
- 将 master 添加到全局的 SPI master 链表。
核心关系:
spi_alloc_master 负责“生”,spi_register_master 负责“立”。两者必须成对使用,且顺序不可颠倒。
3.4 驱动注册与注销完整流程
下图展示了 SPI 控制器驱动从加载到卸载的完整生命周期,以及各阶段调用的关键函数:
3.5 remove 回调与资源释放
remove 回调的执行顺序与 probe 相反,确保资源按依赖关系正确释放:
static int spi_controller_remove(struct platform_device *pdev)
{
struct spi_master *master = platform_get_drvdata(pdev);
struct spi_controller_data *cdata = spi_master_get_devdata(master);
/* 1. 注销 SPI master,断开与所有 SPI 设备的连接 */
spi_unregister_master(master);
/* 2. 硬件去初始化:禁用控制器、关闭时钟、释放中断 */
spi_controller_hw_deinit(cdata);
/* 3. 释放 master 引用(如果使用 devm 资源管理,此步可省略) */
spi_master_put(master);
return 0;
}
简化技巧: 如果
probe 中全部使用 devm_* 函数(如 devm_ioremap_resource、devm_clk_get、devm_request_irq),则 remove 中只需调用 spi_unregister_master,其余资源由内核自动释放。
3.6 常见错误与避坑指南
| 错误场景 | 后果 | 解决方案 |
|---|---|---|
spi_alloc_master 后未检查返回值 |
空指针解引用导致内核崩溃 | 始终检查返回值,失败时返回 -ENOMEM |
spi_register_master 失败后未调用 spi_master_put |
master 对象内存泄漏 | 在错误路径中调用 spi_master_put |
remove 中未调用 spi_unregister_master |
SPI 设备无法正常断开,可能导致系统挂起 | 确保 remove 中调用 spi_unregister_master |
在 probe 中修改 master->dev.driver_data |
覆盖核心层内部数据,引发不可预知行为 | 使用 spi_master_get_devdata 访问私有数据 |
未设置 transfer_one 或 transfer_one_message 回调 |
spi_register_master 返回 -EINVAL |
注册前必须至少设置一个传输回调 |