【Linux驱动开发】第23天:spi_driver 的 probe / remove 函数实现规范

发布时间:2026/7/5 3:04:59
【Linux驱动开发】第23天:spi_driver 的 probe / remove 函数实现规范 probe 和 remove 是 SPI 从设备驱动的核心入口函数一、函数触发时机与核心职责函数触发时机核心职责probe设备树compatible匹配成功SPI 核心层完成设备基础初始化后自动调用设备身份校验、硬件初始化、资源申请、内核子系统注册remove驱动卸载、设备被移除时自动调用子系统注销、硬件关停、资源释放是probe的逆操作函数原型Linux 5.15 标准// 成功返回0失败返回负错误码如-ENOMEM、-EIO、-ENODEVintprobe(structspi_device*spi);// 执行清理返回0或void不同内核版本略有差异统一按int返回0即可intremove(structspi_device*spi);二、probe 函数标准实现7步标准流程整体执行逻辑入参校验 → SPI参数配置 → 分配私有数据 → 申请硬件资源 → 外设初始化 → 注册子系统 → 错误回滚步骤1入参校验与设备信息提取struct spi_device *spi是 SPI 从设备的核心描述符内核已经根据设备树填充了基础参数。可直接获取片选号spi-chip_select、SPI模式spi-mode、最大时钟spi-max_speed_hz、位宽spi-bits_per_word设备节点spi-dev所有dev_*打印、资源申请都基于此staticintspi_demo_probe(structspi_device*spi){intret;structspi_demo_priv*priv;// 校验入参防御式编程if(!spi)return-ENODEV;dev_info(spi-dev,probe matched, cs%d, mode0x%x, max_freq%dHz\n,spi-chip_select,spi-mode,spi-max_speed_hz);步骤2SPI 通信参数配置spi_setup向 SPI 控制器驱动提交当前设备的时序参数控制器会据此配置硬件寄存器第一次传输前必须调用。设备树中的spi-cpol、spi-cpha、spi-max-frequency会自动填充到spi_device驱动可动态修改参数修改后必须重新调用spi_setup生效// 可选显式配置SPI参数设备树已配置的话可省略推荐显式调用确保生效spi-modeSPI_MODE_3;// 模式3CPOL1, CPHA1spi-bits_per_word8;// 8位数据位宽spi-max_speed_hz50000000;// 实际运行不超过设备树设定值retspi_setup(spi);if(ret0){dev_err(spi-dev,spi setup failed, ret%d\n,ret);returnret;}对应 I2C无完全对等接口I2C 时序参数由适配器驱动统一配置从设备无需单独设置。步骤3分配私有数据并绑定私有数据结构体是驱动的全局变量容器保存设备状态、硬件资源、数据缓存等所有工业级驱动都会使用。推荐使用devm_kzalloc随设备生命周期自动释放remove无需手动释放避免内存泄漏通过spi_set_drvdata将私有数据挂载到spi_device上后续中断、回调中可通过spi_get_drvdata获取// 分配私有数据结构体devm托管自动释放privdevm_kzalloc(spi-dev,sizeof(*priv),GFP_KERNEL);if(!priv)return-ENOMEM;priv-spispi;// 保存spi_device指针spi_set_drvdata(spi,priv);// 绑定到spi_device对应i2c_set_clientdata私有数据结构体示例structspi_demo_priv{structspi_device*spi;// 所属SPI设备structgpio_desc*reset_gpio;// 复位引脚structmutexlock;// 并发锁u8 tx_buf[256];// 发送缓存u8 rx_buf[256];// 接收缓存intdata;// 业务数据};步骤4申请硬件资源GPIO、中断等从设备树读取并申请外设所需的 GPIO、中断、电源等资源优先使用 devm 托管接口。复位引脚、中断引脚是 SPI 外设最常见的额外硬件资源使用devm_gpiod_get_optional读取设备树中的reset-gpios属性// 申请复位GPIO设备树中定义reset-gpios属性priv-reset_gpiodevm_gpiod_get_optional(spi-dev,reset,GPIOD_OUT_HIGH);if(IS_ERR(priv-reset_gpio)){retPTR_ERR(priv-reset_gpio);dev_err(spi-dev,get reset gpio failed, ret%d\n,ret);returnret;}// 初始化互斥锁保护SPI并发访问mutex_init(priv-lock);步骤5外设硬件初始化与身份校验这是 probe 的核心功能确认硬件真实存在并将外设设置为工作状态。执行硬件复位时序拉复位→延时→释放复位发送初始化指令配置外设寄存器读取芯片 ID / JEDEC ID验证设备身份防止匹配到不存在的硬件// 硬件复位时序if(priv-reset_gpio){gpiod_set_value(priv-reset_gpio,0);// 拉低复位msleep(10);gpiod_set_value(priv-reset_gpio,1);// 释放复位msleep(20);// 等待外设启动}// 关键读取芯片ID验证设备真实存在retspi_demo_read_chip_id(priv);if(ret0){dev_err(spi-dev,chip id verify failed\n);return-ENODEV;}dev_info(spi-dev,chip id verified, device ready\n);最佳实践不能只靠设备树compatible匹配就认为设备存在必须通过 SPI 通信读取 ID 校验这是驱动鲁棒性的基本要求。步骤6注册内核子系统按需根据外设类型将设备注册到对应的内核子系统向用户空间暴露操作接口。传感器类注册hwmon子系统触摸屏/按键类注册input子系统通用设备注册misc杂项设备存储类注册MTD/block子系统// 示例注册misc杂项设备priv-misc.minorMISC_DYNAMIC_MINOR;priv-misc.namespi_demo;priv-misc.fopsspi_demo_fops;priv-misc.parentspi-dev;retmisc_register(priv-misc);if(ret0){dev_err(spi-dev,misc register failed, ret%d\n,ret);returnret;}步骤7错误处理与逐级回滚每一步失败都必须撤销之前的操作避免资源泄漏。使用devm托管的资源内存、GPIO、中断内核会自动逆序释放无需手动处理非托管资源如misc_register必须在错误分支手动注销完整错误处理示例retmisc_register(priv-misc);if(ret0){dev_err(spi-dev,misc register failed\n);// 非托管资源手动回滚// 若有更多非托管资源按注册逆序依次注销returnret;}dev_info(spi-dev,probe success\n);return0;}三、remove 函数标准实现remove是probe的严格逆操作执行顺序与 probe 相反。标准执行顺序注销子系统 → 关停硬件 → 释放非托管资源 → devm资源自动释放完整实现示例staticintspi_demo_remove(structspi_device*spi){structspi_demo_priv*privspi_get_drvdata(spi);// 1. 最先注销probe最后注册的子系统misc_deregister(priv-misc);// 2. 关停外设硬件拉复位、进入休眠if(priv-reset_gpio)gpiod_set_value(priv-reset_gpio,0);// 拉复位停止外设工作// 3. 销毁锁、清理缓存mutex_destroy(priv-lock);// 4. devm托管的内存、GPIO、中断等会由内核自动释放无需手动处理dev_info(spi-dev,remove done\n);return0;}关键说明逆序原则probe 中先执行的操作remove 中后执行probe 最后注册的资源remove 最先注销。devm 优势90% 以上的资源都可以用 devm 托管remove 函数会非常简洁且彻底避免内存泄漏。并发安全remove 执行时要确保没有正在进行的 SPI 传输、中断处理必要时加标志位屏蔽后续操作。四、完整工业级驱动模板1. 驱动头文件与私有结构#includelinux/module.h#includelinux/spi/spi.h#includelinux/gpio/consumer.h#includelinux/miscdevice.h#includelinux/mutex.h#includelinux/delay.hstructspi_demo_priv{structspi_device*spi;structgpio_desc*reset_gpio;structmiscdevicemisc;structmutexlock;u32 chip_id;};2. 芯片ID读取函数probe校验用#defineCMD_READ_ID0x9Fstaticintspi_demo_read_chip_id(structspi_demo_priv*priv){intret;u8 txCMD_READ_ID;u8 rx[3]{0};mutex_lock(priv-lock);retspi_write_then_read(priv-spi,tx,1,rx,3);mutex_unlock(priv-lock);if(ret0)returnret;priv-chip_id(rx[0]16)|(rx[1]8)|rx[2];dev_info(priv-spi-dev,chip id: 0x%06x\n,priv-chip_id);// 校验ID是否合法以W25Q128为例0xEF4018if(priv-chip_id!0xEF4018)return-ENODEV;return0;}3. probe / remove 完整实现staticconststructof_device_idspi_demo_of_match[]{{.compatiblewinbond,w25q128},{/* sentinel */}};MODULE_DEVICE_TABLE(of,spi_demo_of_match);staticintspi_demo_probe(structspi_device*spi){intret;structspi_demo_priv*priv;privdevm_kzalloc(spi-dev,sizeof(*priv),GFP_KERNEL);if(!priv)return-ENOMEM;priv-spispi;spi_set_drvdata(spi,priv);mutex_init(priv-lock);// 配置SPI参数spi-modeSPI_MODE_3;spi-bits_per_word8;retspi_setup(spi);if(ret0)returnret;// 申请复位GPIOpriv-reset_gpiodevm_gpiod_get_optional(spi-dev,reset,GPIOD_OUT_HIGH);if(IS_ERR(priv-reset_gpio))returnPTR_ERR(priv-reset_gpio);// 硬件复位if(priv-reset_gpio){gpiod_set_value(priv-reset_gpio,0);msleep(10);gpiod_set_value(priv-reset_gpio,1);msleep(20);}// 校验芯片IDretspi_demo_read_chip_id(priv);if(ret0){dev_err(spi-dev,invalid chip id\n);returnret;}dev_info(spi-dev,probe success\n);return0;}staticintspi_demo_remove(structspi_device*spi){structspi_demo_priv*privspi_get_drvdata(spi);if(priv-reset_gpio)gpiod_set_value(priv-reset_gpio,0);mutex_destroy(priv-lock);dev_info(spi-dev,remove done\n);return0;}staticstructspi_driverspi_demo_driver{.driver{.namespi_demo_w25q,.ownerTHIS_MODULE,.of_match_tablespi_demo_of_match,},.probespi_demo_probe,.removespi_demo_remove,};module_spi_driver(spi_demo_driver);MODULE_LICENSE(GPL);MODULE_DESCRIPTION(Industrial grade SPI driver template);五、与 I2C 驱动 probe / remove 的对比操作I2C 驱动SPI 驱动入参设备结构体struct i2c_client *clientstruct spi_device *spi设备绑定私有数据i2c_set_clientdataspi_set_drvdata硬件参数配置无单独配置由适配器统一管理spi_setup单独配置每个设备设备身份校验读取寄存器 / SMBus 读IDspi_write_then_read读芯片ID资源管理通用 devm 接口通用 devm 接口子系统注册完全一致完全一致驱动注册宏module_i2c_drivermodule_spi_driver六、常见坑点与最佳实践必须调用spi_setup即使设备树已配置参数也建议显式调用避免内核版本差异导致参数不生效。必须做芯片ID校验不能仅凭compatible匹配就认为设备存在否则硬件不存在时驱动会异常。优先使用 devm除了子系统注册类操作尽量全部使用 devm 托管资源简化 remove 并杜绝泄漏。加锁保护 SPI 传输SPI 控制器不支持并发访问多线程场景下必须用互斥锁保护spi_write_then_read等传输接口。probe 中避免长延时超过 100ms 的初始化建议放到工作队列中异步执行避免阻塞内核启动流程。