SPI基础支持此处不再赘述,直接分析linux中的SPI驱动源码。

1、SPI设备驱动架构图

2、源码分析

本次分析基于kernel5.18,linux/drivers/spi/spidev.c

设备树示例:

    &spis1 {tri-pin = <57>;slave@0 {compatible = "rohm,dh2228fv";spi-max-frequency = <6000000>;irq-pin = <56>;ack-pin = <58>;protocol = "hoot-protocol";};};

设备树里面SPI设备节点的compatible属性等于如下值,就会跟spidev驱动进行匹配:

static const struct spi_device_id spidev_spi_ids[] = {{ .name = "dh2228fv" },{ .name = "ltc2488" },{ .name = "sx1301" },{ .name = "bk4" },{ .name = "dhcom-board" },{ .name = "m53cpld" },{ .name = "spi-petra" },{ .name = "spi-authenta" },{},
};
MODULE_DEVICE_TABLE(spi, spidev_spi_ids);

匹配成功后spidev.c里面的spidev_probe就会被调用。

spidev_spi_driver源码分析

spidev_spi_driver源码具体实现如下:

static struct spi_driver spidev_spi_driver = {.driver = {.name =      "spidev",.of_match_table =     spidev_dt_ids,.acpi_match_table =   spidev_acpi_ids,},.probe =                spidev_probe,.remove =                 spidev_remove,.id_table =                  spidev_spi_ids,/* NOTE:  suspend/resume methods are not necessary here.* We don't do anything except pass the requests to/from* the underlying controller.  The refrigerator handles* most issues; the controller driver handles the rest.*/
};

其中spidev_probe的具体实现如下:

static int spidev_probe(struct spi_device *spi)
{int (*match)(struct device *dev);struct spidev_data    *spidev;int                 status;unsigned long        minor;match = device_get_match_data(&spi->dev);if (match) {status = match(&spi->dev);if (status)return status;}/* Allocate driver data */spidev = kzalloc(sizeof(*spidev), GFP_KERNEL);        /* 分配结构体 */if (!spidev)return -ENOMEM;/* Initialize the driver data */spidev->spi = spi;                                    /* spidev_data里面记录spi-device结构体 */spin_lock_init(&spidev->spi_lock);mutex_init(&spidev->buf_lock);INIT_LIST_HEAD(&spidev->device_entry);/* If we can allocate a minor number, hook up this device.* Reusing minors is fine so long as udev or mdev is working.*/mutex_lock(&device_list_lock);minor = find_first_zero_bit(minors, N_SPI_MINORS);    /* 找到一个空闲的次设备号 */if (minor < N_SPI_MINORS) {struct device *dev;spidev->devt = MKDEV(SPIDEV_MAJOR, minor);dev = device_create(spidev_class, &spi->dev, spidev->devt,      /* 创建一个设备,通过、dev/spidevx.x */spidev, "spidev%d.%d",spi->master->bus_num, spi->chip_select);            /* spi的第几个spi_master设备,spi的片选信号信息 */status = PTR_ERR_OR_ZERO(dev);} else {dev_dbg(&spi->dev, "no minor number available!\n");status = -ENODEV;}if (status == 0) {set_bit(minor, minors);list_add(&spidev->device_entry, &device_list);            /* 将这个spidev_data添加到device_list链表中 */}mutex_unlock(&device_list_lock);spidev->speed_hz = spi->max_speed_hz;if (status == 0)spi_set_drvdata(spi, spidev);elsekfree(spidev);return status;
}

主要功能就是调用device_create创建设备文件,生成设备节点,用户可以通过节点进行读写和iotrol操作,其次还完成了如下操作:

1、分配一个spidev_data结构体,用来记录对应的spi_device。

2、将spi_data记录在一个链表里。

3、分配一个设备好,以后可以根据这个次设备号在上述的链表里面查找spidev_data。

4、device_create函数会生成一个设备节点:/dev/spidevB.D。B表示总线号,B表示这是SPI master下第几个设备,后续就可以通过/dev/spidevB.D来访问spidev驱动。

设备驱动的初始化和退出:

static int __init spidev_init(void)
{int status;/* Claim our 256 reserved device numbers.  Then register a class* that will key udev/mdev to add/remove /dev nodes.  Last, register* the driver which manages those device numbers.*/status = register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops);    /* 注册字符设备(spidev_fops) */if (status < 0)return status;spidev_class = class_create(THIS_MODULE, "spidev");    /* 注册sysfs spidev节点 */if (IS_ERR(spidev_class)) {unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);return PTR_ERR(spidev_class);}status = spi_register_driver(&spidev_spi_driver);    /* 注册spi设备驱动 */if (status < 0) {class_destroy(spidev_class);unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);}return status;
}module_init(spidev_init);    /* 驱动模块初始化 */static void __exit spidev_exit(void)
{spi_unregister_driver(&spidev_spi_driver);    /* 注销spi 设备驱动 */class_destroy(spidev_class);    /* 注销sysfs spidev节点 */unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);    /* 注销spi设备驱动 */
}module_exit(spidev_exit);    /* 驱动模块注销 */

module_init源码分析请关注:module_init源码分析。

module_exit源码分析请关注:module_exit源码分析。

class_create源码分析请关注:class_create源码分析

class_destroy源码分析请关注:class_destroy源码分析

register_chrdev源码分析请关注:后续更新(TODO)。

unregister_chrdev源码分析请关注:后续更新(TODO)。

SPIDEV_MAJOR:#define SPIDEV_MAJOR            153 /* assigned */

spidev_init源码分析

register_chrdev:创建字符设备,spi属于字符设备驱动,定义如下:

static inline int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)

入参传入 file_operations 结构体,结构体存了很多函数指针,实现读写和ioctrl相关操作,也是驱动最核心的功能,下面是spidev 实现的结构体:

static const struct file_operations spidev_fops = {.owner =    THIS_MODULE,/* REVISIT switch to aio primitives, so that userspace* gets more complete API coverage.  It'll simplify things* too, except for the locking.*/.write =           spidev_write,    /* 单工写模式 */.read =                spidev_read,     /* 单工读模式 */.unlocked_ioctl =   spidev_ioctl,    /* 设置频率、模式、进行双工传输 */.compat_ioctl =     spidev_compat_ioctl,.open =             spidev_open,.release =         spidev_release,.llseek =           no_llseek,
};

spidev_fops分析

spiev_write函数分析

spidev_write的源码如下:

/* Write-only message with current device setup */
static ssize_t
spidev_write(struct file *filp, const char __user *buf,size_t count, loff_t *f_pos)
{struct spidev_data *spidev;ssize_t             status;unsigned long        missing;/* chipselect only toggles at start or end of operation */if (count > bufsiz)return -EMSGSIZE;spidev = filp->private_data;    /* spidev_data结构体是很重要的数据传递类型 */mutex_lock(&spidev->buf_lock);missing = copy_from_user(spidev->tx_buffer, buf, count);    /* 数据从用户态copy到内核态 */if (missing == 0)status = spidev_sync_write(spidev, count);    /* 同步数据 */elsestatus = -EFAULT;mutex_unlock(&spidev->buf_lock);return status;
}

spidev_sync_write函数的具体实现如下:

static inline ssize_t
spidev_sync_write(struct spidev_data *spidev, size_t len)
{struct spi_transfer    t = {.tx_buf       = spidev->tx_buffer,    /* 指定tx_buffer */.len       = len,                  /* 指定长度 */      .speed_hz  = spidev->speed_hz,     /* 指定传输速率 */};struct spi_message    m;spi_message_init(&m);            /* spi消息初始化(初始化传输事务链表头) */spi_message_add_tail(&t, &m);    /* 添加spi传输到spi消息传输链表,将t放到message的尾部 */return spidev_sync(spidev, &m);  /* spi同步传输 */
}

上述代码中的spi_message_init函数,具体实现如下:

static inline void spi_message_init_no_memset(struct spi_message *m)
{INIT_LIST_HEAD(&m->transfers);INIT_LIST_HEAD(&m->resources);
}static inline void spi_message_init(struct spi_message *m)
{memset(m, 0, sizeof *m);spi_message_init_no_memset(m);
}

通过源码可知,spi_message_init将传入的结构体spi_message全部内容初始化为0,并被初始化过的结构体spi_message传递给了函数spi_message_init_no_memset。

在spi_message_init_no_memset通过INIT_LIST_HEAD为m->transfers和m->resources分别创建双向链表的头节点。

在spidev_sync_write函数中,在完成SPI数据的链表的初始化之后又通过调用spi_message_add_tail函数,将struct spi_transfer t和struct spi_message m分别添加到前一步创建的双向链表的尾部。

在spidev_sync_write函数的最后通过调用spidev_sync函数进行SPI的同步传输,并将结果返回,此处spidev_sync函数的具体实现如下:

static ssize_t
spidev_sync(struct spidev_data *spidev, struct spi_message *message)
{int status;struct spi_device *spi;spin_lock_irq(&spidev->spi_lock);spi = spidev->spi;spin_unlock_irq(&spidev->spi_lock);if (spi == NULL)status = -ESHUTDOWN;elsestatus = spi_sync(spi, message);if (status == 0)status = message->actual_length;return status;
}

梳理spidev_sync的数据传输流程:spidev_sync --> spi_sync --> __spi_sync --> __spi_queued_transfer --> kthread_queue_work最终将数据放到工作队列中,通过SPI总线驱动实现数据的发送功能。

spiev_read函数分析

spidev_read函数源码如下:

/* Read-only message with current device setup */
static ssize_t
spidev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{struct spidev_data *spidev;ssize_t         status;/* chipselect only toggles at start or end of operation */if (count > bufsiz)return -EMSGSIZE;spidev = filp->private_data;    /* 从私有数据中获取spidev_data数据 */mutex_lock(&spidev->buf_lock);               /* 加锁操作,数据安全 */status = spidev_sync_read(spidev, count);    /* 同步读取数据 */if (status > 0) {unsigned long    missing;missing = copy_to_user(buf, spidev->rx_buffer, status);    /* 将读取的数据从内核态copy到用户态 */if (missing == status)status = -EFAULT;elsestatus = status - missing;}mutex_unlock(&spidev->buf_lock);             /* 解锁操作 */return status;
}

spidev_sync_read函数的具体实现如下:

static inline ssize_t
spidev_sync_read(struct spidev_data *spidev, size_t len)
{struct spi_transfer    t = {.rx_buf       = spidev->rx_buffer,    /* 指定rx_buffer */.len       = len,.speed_hz    = spidev->speed_hz,};struct spi_message m;                      /* 构造一个message */spi_message_init(&m);                       /* 初始化spi_message */spi_message_add_tail(&t, &m);               /* 将transfer放到message的尾部 */return spidev_sync(spidev, &m);             /* 发起数据传输 */
}

将要发送的数据填充到struct spi_transfer t结构体中,跟spidev_sync_write同样的将通过spi_message_init函数初始化spi_message全部为0,通过spi_message_init_no_memset函数调用INIT_LIST_HEAD为m->transfers和m->resources分别创建双向链表的头节点。

与spidev_sync_write函数一样,在完成SPI数据的链表的初始化之后又通过调用spi_message_add_tail函数,将struct spi_transfer t和struct spi_message m分别添加到前一步创建的双向链表的尾部。

spidev_sync函数完成数据同步的流程此处不在重复。

spidev_ioctl函数分析

        spidev_ioctl的源码如下:

static long
spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{int                    retval = 0;struct spidev_data  *spidev;struct spi_device   *spi;u32                    tmp;unsigned            n_ioc;struct spi_ioc_transfer   *ioc;/* Check type and command number */if (_IOC_TYPE(cmd) != SPI_IOC_MAGIC)return -ENOTTY;/* guard against device removal before, or while,* we issue this ioctl.*/spidev = filp->private_data;spin_lock_irq(&spidev->spi_lock);spi = spi_dev_get(spidev->spi);spin_unlock_irq(&spidev->spi_lock);if (spi == NULL)return -ESHUTDOWN;/* use the buffer lock here for triple duty:*  - prevent I/O (from us) so calling spi_setup() is safe;*  - prevent concurrent SPI_IOC_WR_* from morphing*    data fields while SPI_IOC_RD_* reads them;*  - SPI_IOC_MESSAGE needs the buffer locked "normally".*/mutex_lock(&spidev->buf_lock);switch (cmd) {/* read requests */case SPI_IOC_RD_MODE:retval = put_user(spi->mode & SPI_MODE_MASK,(__u8 __user *)arg);break;case SPI_IOC_RD_MODE32:retval = put_user(spi->mode & SPI_MODE_MASK,(__u32 __user *)arg);break;case SPI_IOC_RD_LSB_FIRST:retval = put_user((spi->mode & SPI_LSB_FIRST) ?  1 : 0,(__u8 __user *)arg);break;case SPI_IOC_RD_BITS_PER_WORD:retval = put_user(spi->bits_per_word, (__u8 __user *)arg);break;case SPI_IOC_RD_MAX_SPEED_HZ:retval = put_user(spidev->speed_hz, (__u32 __user *)arg);break;/* write requests */case SPI_IOC_WR_MODE:case SPI_IOC_WR_MODE32:if (cmd == SPI_IOC_WR_MODE)retval = get_user(tmp, (u8 __user *)arg);elseretval = get_user(tmp, (u32 __user *)arg);if (retval == 0) {struct spi_controller *ctlr = spi->controller;u32    save = spi->mode;if (tmp & ~SPI_MODE_MASK) {retval = -EINVAL;break;}if (ctlr->use_gpio_descriptors && ctlr->cs_gpiods &&ctlr->cs_gpiods[spi->chip_select])tmp |= SPI_CS_HIGH;tmp |= spi->mode & ~SPI_MODE_MASK;spi->mode = (u16)tmp;retval = spi_setup(spi);if (retval < 0)spi->mode = save;elsedev_dbg(&spi->dev, "spi mode %x\n", tmp);}break;case SPI_IOC_WR_LSB_FIRST:retval = get_user(tmp, (__u8 __user *)arg);if (retval == 0) {u32    save = spi->mode;if (tmp)spi->mode |= SPI_LSB_FIRST;elsespi->mode &= ~SPI_LSB_FIRST;retval = spi_setup(spi);if (retval < 0)spi->mode = save;elsedev_dbg(&spi->dev, "%csb first\n",tmp ? 'l' : 'm');}break;case SPI_IOC_WR_BITS_PER_WORD:retval = get_user(tmp, (__u8 __user *)arg);if (retval == 0) {u8 save = spi->bits_per_word;spi->bits_per_word = tmp;retval = spi_setup(spi);if (retval < 0)spi->bits_per_word = save;elsedev_dbg(&spi->dev, "%d bits per word\n", tmp);}break;case SPI_IOC_WR_MAX_SPEED_HZ:retval = get_user(tmp, (__u32 __user *)arg);if (retval == 0) {u32 save = spi->max_speed_hz;spi->max_speed_hz = tmp;retval = spi_setup(spi);if (retval == 0) {spidev->speed_hz = tmp;dev_dbg(&spi->dev, "%d Hz (max)\n",spidev->speed_hz);}spi->max_speed_hz = save;}break;default:/* segmented and/or full-duplex I/O request *//* Check message and copy into scratch area */ioc = spidev_get_ioc_message(cmd,(struct spi_ioc_transfer __user *)arg, &n_ioc);if (IS_ERR(ioc)) {retval = PTR_ERR(ioc);break;}if (!ioc)break; /* n_ioc is also 0 *//* translate to spi_message, execute */retval = spidev_message(spidev, ioc, n_ioc);kfree(ioc);break;}mutex_unlock(&spidev->buf_lock);spi_dev_put(spi);return retval;
}

spidev_compat_ioctl函数分析

spidev_open函数分析

spidev_open函数源码如下:

static int spidev_open(struct inode *inode, struct file *filp)
{struct spidev_data *spidev;int         status = -ENXIO;mutex_lock(&device_list_lock);/* 在device_list链表中查找和inode下的注册此设备号一致的设备 */list_for_each_entry(spidev, &device_list, device_entry) {if (spidev->devt == inode->i_rdev) {status = 0;break;}}if (status) {pr_debug("spidev: nothing for minor %d\n", iminor(inode));goto err_find_dev;}if (!spidev->tx_buffer) {spidev->tx_buffer = kmalloc(bufsiz, GFP_KERNEL);if (!spidev->tx_buffer) {dev_dbg(&spidev->spi->dev, "open/ENOMEM\n");status = -ENOMEM;goto err_find_dev;}}if (!spidev->rx_buffer) {spidev->rx_buffer = kmalloc(bufsiz, GFP_KERNEL);if (!spidev->rx_buffer) {dev_dbg(&spidev->spi->dev, "open/ENOMEM\n");status = -ENOMEM;goto err_alloc_rx_buf;}}spidev->users++;/* 把找到的spidev_data保存在私有数据中 */filp->private_data = spidev;stream_open(inode, filp);mutex_unlock(&device_list_lock);return 0;err_alloc_rx_buf:kfree(spidev->tx_buffer);spidev->tx_buffer = NULL;
err_find_dev:mutex_unlock(&device_list_lock);return status;
}

Linux kernel SPI源码分析之SPI设备驱动源码分析(linux kernel 5.18)相关推荐

  1. 通用USB设备驱动源码分析

    通用USB设备驱动源码分析 Author:aaron 前段时间写了篇<qualcomm usb modem驱动小结>的文章, 描述了自己如何为高通的一个usb modem设备写驱动的过程, ...

  2. linux内核部件分析(十)——设备驱动模型之class,linux内核部件分析(十)——设备驱动模型之class...

    前面看过了设备驱动模型中的bus.device.driver,这三种都是有迹可循的.其中bus代表实际的总线,device代表实际的设备和接口,而driver则对应存在的驱动.但本节要介绍的class ...

  3. linux 内核驱动模型,linux设备驱动模型架构分析 一

    linux设备驱动模型架构分析 一 发布时间:2018-07-04 15:14, 浏览次数:584 , 标签: linux 概述 LDD3中说:"Linux内核需要一个对系统结构的一般性描述 ...

  4. linux设备驱动之串口移植,linux设备驱动之UART移植

    对于串口驱动的移植准备自己分析一下源代码的,但是发现自己好多地方都只知道一 些皮毛,不明白其中的道理,所以我上网搜的时候发现有好多人写了很多很好的文章了,下面我转载的这篇就非常不错,一个困恼我好久的问 ...

  5. 【分析笔记】Linux gpio_wdt.c 看门狗设备驱动源码分析

    基本原理 该看门狗的设备驱动实现原理很简单,比较主要的有两点: 一.定时器喂狗 通过定时器根据配置文件配置的喂狗方式(如脉冲切换.电平切换),对指定的 gpio 进行脉冲切换或电平切换实现喂狗. 脉冲 ...

  6. spi总线 上层调用_spi总线设备驱动分析

    今天折腾了一天的SPI设备的驱动加载,甚至动用了逻辑分析仪来查看spi总线的波形,主要包括两个SPI设备,at45db321d和mcp2515,一个是串行的dataflash,一个是can总线设备芯片 ...

  7. java mysql 源码分析_JAVA JDBC(MySQL)驱动源码分析

    JAVA连接数据库是其众多功能中的一部分,主要有两种方式连接DataBase: 一种是采用JDBC-ODBC桥,另一种则是称之为纯驱动连接DataBase,第一种方式在大型项目中基本上不再使用,本系列 ...

  8. 【linux驱动分析】misc设备驱动

        misc设备驱动.又称混杂设备驱动. misc设备驱动共享一个设备驱动号MISC_MAJOR.它在include\linux\major.h中定义:         #define MISC_ ...

  9. linux设备驱动 注册 命令6,Linux设备驱动调试

    GDB基本用法 GDB是GNU开源组织发布的一项强大的UNIX下的程序调试工具,GDB主要完成下面4个方面的功能 1.启动程序,可以按照工程师自定义的要求运行程序 2.让被调试的程序在工程师的指定端点 ...

最新文章

  1. js实现网页页面回到顶部
  2. zend studio安装xdebug调试工具
  3. python类汇总_Python 类总结
  4. indesign如何画弧线_彩铅画入门教程,如何给独角兽设计一款好发型
  5. 重新启动系统中的network服务器,linux系统调优-Network
  6. springboot整合mybatis 使用HikariCP连接池
  7. winfrom 去掉字符串空格
  8. redis---安全设置
  9. 通过实现ServletContextListener接口创建数据库连接池(C3P0方式)
  10. Struts2表单验证的xml配置
  11. tomcat 启动编码设置
  12. bochs运行xp_使用Bochs模拟器瞬间安卓变XP教程
  13. 三维扫描+逆向建模+3D打印复刻工艺品
  14. 无法打开ONEDRIVE的解决方法【等待验证】
  15. Linux数据结构之radix-tree
  16. init mysql db error_Python mysql curs错误
  17. Tensor.shape[0]的理解
  18. [转载]徐文兵:梦与健康
  19. 从Q2财报看百度的转型三部曲
  20. BUUCTF misc第二页WP

热门文章

  1. android studio便签需求介绍,Android我的便签-----SQLite的使用方法
  2. 如何把mysql的ID归0?
  3. Qt 实现 QQ 9.0版 自定义菜单控件
  4. android+待办事项+日历,TickTick Pro「滴答清单」v5.9.4.4 for Android 直装解锁高级版 —— 一个强大的待办事项和任务管理应用...
  5. java mahout使用教程_Mahout使用简例
  6. Unity VR手柄画圆判断顺/逆时针
  7. 如何制作毕业地图分布图_如何绘制127个特色小镇地图分布地图
  8. 桂电七院数据库实验报告四
  9. windows下python安装pip方法详解
  10. 强烈推荐几个web中常用js图表插件