当前位置: 首页 > news >正文

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. 设备树触发匹配
  1. 内核解析设备树中的SPI设备节点(如 /spi@12340000/sensor@0
  2. 创建 spi_device 结构体并注册到SPI总线
  3. SPI总线调用 spidev_spi_driverprobe 函数进行匹配
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;
}

关键步骤:

  1. 分配私有数据:创建 spidev_data 结构体存储设备信息
  2. 初始化链表节点:通过 INIT_LIST_HEAD 初始化 spidev->list
  3. 加入全局链表:使用 list_add_tail 将设备添加到 spidev_list 尾部
  4. 加入全局数组:同时将设备存入 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;
}

关键步骤:

  1. 获取次设备号:从 inode 中提取次设备号
  2. 查找设备数据:通过 spidev_table 数组快速定位设备
  3. 设置私有数据:将 spidev_data 存入 file->private_data
  4. 初始化传输参数:从 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);
}

关键步骤:

  1. 遍历链表查找设备:使用 list_for_each_entry 遍历 spidev_list
  2. 从链表移除:使用 list_del 将设备从链表中删除
  3. 从数组清除:将 spidev_table 对应位置置为NULL
  4. 销毁设备文件:调用 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)                // 释放内存

七、调试与验证方法

  1. 查看设备链表

    // 内核调试代码示例
    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);}
    }
    
  2. 使用 lsof 查看打开的设备

    lsof | grep spidev
    
  3. 查看sysfs信息

    ls /sys/bus/spi/drivers/spidev/
    cat /sys/bus/spi/drivers/spidev/*/modalias
    

spidev.c 通过双重索引机制(链表+数组)实现了SPI设备的高效管理:

  • 链表:便于遍历所有设备,适用于批量操作和设备移除
  • 数组:通过次设备号快速定位,适用于文件操作(如open)
  • 自旋锁:保护共享资源,确保并发安全

理解这些数据结构和流程对于开发SPI驱动和应用程序至关重要。

相关文章:

  • 做网站注册哪类商标seo在线培训机构排名
  • 专门做自驾游攻略的网站网站产品怎么优化
  • 中学校园网网站建设规划书网站设计制作培训
  • 手机wap网站制作引流客户的最快方法是什么
  • 互动网站建设什么意思seo运营推广
  • 开淘宝店要自己做网站吗想要推广网页正式版
  • svn域名更换,批量修改项目svn地址(linux)
  • FineBI(二)- 数据导入
  • AI时代工具:AIGC导航——AI工具集合
  • day041-web集群架构搭建
  • 阿里最新开源:Mnn3dAvatar 3D数字人框架, 无需联网,本地部署可离线运行,支持多模态实时交互
  • Docker 报错“x509: certificate signed by unknown authority”的排查与解决实录
  • CentOS下安装JDK17
  • CentOS 7 编译安装Nginx 1.27.5完整指南及负载均衡配置
  • Luckysheet Excel xlsx 导入导出互相转换
  • RSS解析并转换为JSON的API集成指南
  • 关键领域软件工厂的安全中枢如何全面升级供应链检测能力
  • CentOS 7 通过YUM安装MySQL 8.0完整指南
  • Redis的渐进式hash和缓存时间戳深入学习
  • PYTHON从入门到实践4-数据类型
  • 深入JVM:从零到实战,解锁Java性能与调优的终极武器
  • doris_工作使用整理
  • 记录某企业存储型XSS漏洞从发现到数据外泄全路径分析
  • Python 数据分析与可视化 Day 6 - 可视化整合报告实战
  • logback 日志不打印
  • uni-app项目实战笔记24--uniapp实现图片保存到手机相册