Linux SPI核心驱动spidev.c深度解析
Linux SPI核心驱动 spidev.c
深度解析
spidev.c
是Linux内核中SPI设备的通用用户空间接口驱动,它允许用户空间程序通过 /dev/spidevX.Y
文件直接访问SPI设备。以下是其核心工作流程的详细解析:
一、驱动注册与初始化流程
1. 模块初始化与注册
static LIST_HEAD(spidev_list); /* 全局SPI设备链表头 */
static DEFINE_SPINLOCK(spidev_lock); /* 保护spidev_list的锁 */static int __init spidev_init(void)
{int status;/* 分配并初始化字符设备区域 */status = alloc_chrdev_region(&spidev_major, 0,SPI_MAX_DEVICES, "spidev");if (status < 0)return status;/* 创建SPI设备类 */spidev_class = class_create(THIS_MODULE, "spidev");if (IS_ERR(spidev_class)) {status = PTR_ERR(spidev_class);goto fail_class;}/* 注册SPI驱动 */status = spi_register_driver(&spidev_spi_driver);if (status < 0)goto fail_driver;return 0;fail_driver:class_destroy(spidev_class);
fail_class:unregister_chrdev_region(spidev_major, SPI_MAX_DEVICES);return status;
}
关键数据结构:
spidev_list
:全局双向链表,用于存储所有注册的SPI设备spidev_lock
:自旋锁,保护对链表的并发访问
二、设备发现与结构体分配流程
1. 设备树触发匹配
- 内核解析设备树中的SPI设备节点(如
/spi@12340000/sensor@0
) - 创建
spi_device
结构体并注册到SPI总线 - SPI总线调用
spidev_spi_driver
的probe
函数进行匹配
2. spidev_probe
函数执行流程
static int spidev_probe(struct spi_device *spi)
{struct spidev_data *spidev;int status;/* 分配并初始化spidev_data结构体 */spidev = kzalloc(sizeof(*spidev), GFP_KERNEL);if (!spidev)return -ENOMEM;/* 初始化设备信息 */spidev->spi = spi;INIT_LIST_HEAD(&spidev->list); /* 初始化链表节点 */get_device(&spi->dev);/* 分配次设备号并加入全局链表 */spin_lock(&spidev_lock);status = -EBUSY;for (i = 0; i < SPI_MAX_DEVICES; i++) {if (!spidev_table[i])break;}if (i < SPI_MAX_DEVICES) {spidev->devt = MKDEV(MAJOR(spidev_major), i);spidev_table[i] = spidev; /* 加入全局表 */list_add_tail(&spidev->list, &spidev_list); /* 加入设备链表 */status = 0;}spin_unlock(&spidev_lock);if (status < 0)goto err;/* 创建设备文件 (/dev/spidevX.Y) */spidev->device = device_create(spidev_class, &spi->dev,spidev->devt, spidev,"spidev%d.%d", spi->master->bus_num,spi->chip_select);if (IS_ERR(spidev->device)) {status = PTR_ERR(spidev->device);goto err;}return 0;err:/* 错误处理和资源释放 */spin_lock(&spidev_lock);if (spidev->devt)spidev_table[MINOR(spidev->devt)] = NULL;list_del(&spidev->list); /* 从链表中移除 */spin_unlock(&spidev_lock);kfree(spidev);return status;
}
关键步骤:
- 分配私有数据:创建
spidev_data
结构体存储设备信息 - 初始化链表节点:通过
INIT_LIST_HEAD
初始化spidev->list
- 加入全局链表:使用
list_add_tail
将设备添加到spidev_list
尾部 - 加入全局数组:同时将设备存入
spidev_table
数组(双重索引)
三、核心数据结构
1. 设备私有数据结构
struct spidev_data {struct spi_device *spi; /* 关联的SPI设备 */dev_t devt; /* 设备号 */struct device *device; /* 设备实例 */spinlock_t spi_lock; /* 保护传输参数的锁 */u32 max_speed_hz; /* 最大传输速率 */u8 mode; /* SPI模式 */u8 bits_per_word; /* 位宽 */struct mutex buf_lock; /* 保护缓冲区的锁 */unsigned use_count; /* 引用计数 */struct list_head list; /* 链表节点,用于加入spidev_list */
};
2. 全局状态
static LIST_HEAD(spidev_list); /* 全局SPI设备链表 */
static struct spidev_data *spidev_table[SPI_MAX_DEVICES]; /* 全局设备数组 */
static dev_t spidev_major; /* 主设备号 */
static struct class *spidev_class; /* 设备类 */
- 双重索引机制:
spidev_list
:双向链表,便于遍历所有设备spidev_table
:数组,通过次设备号快速查找设备
四、打开SPI设备的执行流程
1. 用户空间调用 open("/dev/spidevX.Y")
触发内核调用 spidev_fops
中的 open
函数:
static int spidev_open(struct inode *inode, struct file *filp)
{struct spidev_data *spidev;int minor = iminor(inode);int status = -ENODEV;/* 从全局表中查找设备 */spin_lock(&spidev_lock);spidev = spidev_table[minor];if (!spidev)goto out;/* 获取设备引用计数 */get_device(&spidev->spi->dev);filp->private_data = spidev;/* 初始化传输设置(默认值) */spin_lock(&spidev->spi_lock);spidev->mode = spidev->spi->mode;spidev->bits_per_word = spidev->spi->bits_per_word;spidev->max_speed_hz = spidev->spi->max_speed_hz;spin_unlock(&spidev->spi_lock);status = 0;
out:spin_unlock(&spidev_lock);return status;
}
关键步骤:
- 获取次设备号:从
inode
中提取次设备号 - 查找设备数据:通过
spidev_table
数组快速定位设备 - 设置私有数据:将
spidev_data
存入file->private_data
- 初始化传输参数:从
spi_device
复制默认传输参数
五、设备移除流程
当SPI设备被移除时(如热插拔或驱动卸载),执行以下流程:
static void spidev_remove(struct spi_device *spi)
{struct spidev_data *spidev;int minor;/* 查找对应的spidev_data */spin_lock(&spidev_lock);list_for_each_entry(spidev, &spidev_list, list) {if (spidev->spi == spi)break;}if (list_entry_is_head(&spidev->list, &spidev_list, list)) {spin_unlock(&spidev_lock);return;}minor = MINOR(spidev->devt);/* 从链表和数组中移除 */list_del(&spidev->list);spidev_table[minor] = NULL;spin_unlock(&spidev_lock);/* 销毁设备文件 */device_destroy(spidev_class, spidev->devt);/* 释放资源 */put_device(&spi->dev);kfree(spidev);
}
关键步骤:
- 遍历链表查找设备:使用
list_for_each_entry
遍历spidev_list
- 从链表移除:使用
list_del
将设备从链表中删除 - 从数组清除:将
spidev_table
对应位置置为NULL - 销毁设备文件:调用
device_destroy
删除/dev/spidevX.Y
六、总结:数据流与调用链
1. 驱动加载与设备注册
spidev_init()→ alloc_chrdev_region() // 分配字符设备区域→ class_create() // 创建设备类→ spi_register_driver() // 注册SPI驱动→ spidev_probe() // 设备匹配成功后调用→ kzalloc(spidev_data) // 分配设备私有数据→ INIT_LIST_HEAD() // 初始化链表节点→ 从spidev_table分配次设备号→ list_add_tail() // 加入全局链表spidev_list→ spidev_table[i] = spidev // 加入全局数组→ device_create() // 创建/dev/spidevX.Y文件
2. 打开设备流程
用户空间: open("/dev/spidevX.Y")→ 内核: spidev_open()→ iminor(inode) // 获取次设备号→ spidev_table[minor] // 从全局数组查找设备→ get_device() // 增加设备引用计数→ filp->private_data = spidev // 设置私有数据→ 复制spi_device参数到spidev_data
3. 设备移除流程
spidev_remove()→ list_for_each_entry() // 遍历spidev_list查找设备→ list_del() // 从链表中移除→ spidev_table[minor] = NULL // 从数组中清除→ device_destroy() // 销毁设备文件→ kfree(spidev) // 释放内存
七、调试与验证方法
-
查看设备链表:
// 内核调试代码示例 static void debug_spidev_list(void) {struct spidev_data *spidev;printk(KERN_INFO "SPI设备链表:\n");list_for_each_entry(spidev, &spidev_list, list) {printk(KERN_INFO " 设备: /dev/spidev%d.%d, 模式: 0x%x\n",MAJOR(spidev->devt), MINOR(spidev->devt),spidev->mode);} }
-
使用
lsof
查看打开的设备:lsof | grep spidev
-
查看sysfs信息:
ls /sys/bus/spi/drivers/spidev/ cat /sys/bus/spi/drivers/spidev/*/modalias
spidev.c
通过双重索引机制(链表+数组)实现了SPI设备的高效管理:
- 链表:便于遍历所有设备,适用于批量操作和设备移除
- 数组:通过次设备号快速定位,适用于文件操作(如open)
- 自旋锁:保护共享资源,确保并发安全
理解这些数据结构和流程对于开发SPI驱动和应用程序至关重要。