SPI设备标准驱动源码分析(linux kernel 5.18)

简介: SPI设备标准驱动源码分析(linux kernel 5.18)

SPI设备标准驱动源码分析(linux kernel 5.18)


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);
  else
    kfree(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);    /* 同步数据 */
  else
    status = -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;
  else
    status = 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;
    else
      status = 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);
    else
      retval = 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;
      else
        dev_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;
      else
        spi->mode &= ~SPI_LSB_FIRST;
      retval = spi_setup(spi);
      if (retval < 0)
        spi->mode = save;
      else
        dev_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;
      else
        dev_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;
}
相关文章
|
5天前
|
安全 Ubuntu Linux
Nipper 3.8.0 for Windows & Linux - 网络设备漏洞评估
Nipper 3.8.0 for Windows & Linux - 网络设备漏洞评估
21 0
Nipper 3.8.0 for Windows & Linux - 网络设备漏洞评估
|
1月前
|
Linux C语言
Linux读写锁源码分析
本文分析了读写锁的实现原理与应用场景,基于glibc 2.17源码。读写锁通过读引用计数、写线程ID、条件变量等实现,支持读优先(默认)和写优先模式。读优先时,写锁可能饥饿;写优先时,读线程需等待写锁释放。详细解析了`pthread_rwlock_t`数据结构及加解锁流程,并通过实验验证:2000个读线程与1个写线程测试下,读优先导致写锁饥饿,写优先则正常抢占锁。
53 19
|
2月前
|
监控 Linux
Linux基础:文件和目录类命令分析。
总的来说,这些基础命令,像是Linux中藏匿的小矮人,每一次我们使用他们,他们就把我们的指令准确的传递给Linux,让我们的指令变为现实。所以,现在就开始你的Linux之旅,挥动你的命令之剑,探索这个充满神秘而又奇妙的世界吧!
82 19
|
1月前
|
运维 安全 Linux
试试Linux设备命令行运维工具——Wowkey
WowKey 是一款专为 Linux 设备设计的命令行运维工具,提供自动化、批量化、标准化的运维解决方案。它简单易用、高效集成且无依赖,仅需 WIS 指令剧本文件、APT 账号密码文件和 wowkey 命令即可操作。通过分离鉴权与执行过程,WowKey 让运维人员专注于决策,摆脱繁琐的交互与执行工作,大幅提升运维效率与质量。无论是健康检查、数据采集还是配置更新,WowKey 都能助您轻松应对大规模设备运维挑战。立即从官方资源了解更多信息:https://atsight.top/training。
|
1月前
|
数据采集 运维 安全
Linux设备命令行运维工具WowKey问答
WowKey 是一款用于 Linux 设备运维的工具,可通过命令行手动或自动执行指令剧本,实现批量、标准化操作,如健康检查、数据采集、配置更新等。它简单易用,只需编写 WIS 指令剧本和 APT 帐号密码表文件,学习成本极低。支持不同流派的 Linux 系统,如 RHEL、Debian、SUSE 等,只要使用通用 Shell 命令即可通吃Linux设备。
|
2月前
|
自然语言处理 监控 Linux
Linux 内核源码分析---proc 文件系统
`proc`文件系统是Linux内核中一个灵活而强大的工具,提供了一个与内核数据结构交互的接口。通过本文的分析,我们深入探讨了 `proc`文件系统的实现原理,包括其初始化、文件的创建与操作、动态内容生成等方面。通过对这些内容的理解,开发者可以更好地利用 `proc`文件系统来监控和调试内核,同时也为系统管理提供了便利的工具。
93 16
|
2月前
|
监控 Shell Linux
Android调试终极指南:ADB安装+多设备连接+ANR日志抓取全流程解析,覆盖环境变量配置/多设备调试/ANR日志分析全流程,附Win/Mac/Linux三平台解决方案
ADB(Android Debug Bridge)是安卓开发中的重要工具,用于连接电脑与安卓设备,实现文件传输、应用管理、日志抓取等功能。本文介绍了 ADB 的基本概念、安装配置及常用命令。包括:1) 基本命令如 `adb version` 和 `adb devices`;2) 权限操作如 `adb root` 和 `adb shell`;3) APK 操作如安装、卸载应用;4) 文件传输如 `adb push` 和 `adb pull`;5) 日志记录如 `adb logcat`;6) 系统信息获取如屏幕截图和录屏。通过这些功能,用户可高效调试和管理安卓设备。
|
3月前
|
缓存 网络协议 Linux
PCIe 以太网芯片 RTL8125B 的 spec 和 Linux driver 分析备忘
本文详细介绍了 Realtek RTL8125B PCIe 以太网芯片的规格以及在 Linux 中的驱动安装和配置方法。通过深入分析驱动源码,可以更好地理解其工作原理和优化方法。在实际应用中,合理配置和优化驱动程序可以显著提升网络性能和稳定性。希望本文能帮助您更好地使用和管理 RTL8125B,以满足各种网络应用需求。
237 33
|
3月前
|
数据管理 Linux iOS开发
Splunk Enterprise 9.4.1 (macOS, Linux, Windows) 发布 - 机器数据管理和分析
Splunk Enterprise 9.4.1 (macOS, Linux, Windows) 发布 - 机器数据管理和分析
50 0
Splunk Enterprise 9.4.1 (macOS, Linux, Windows) 发布 - 机器数据管理和分析
|
5月前
|
存储 运维 监控
Linux--深入理与解linux文件系统与日志文件分析
深入理解 Linux 文件系统和日志文件分析,对于系统管理员和运维工程师来说至关重要。文件系统管理涉及到文件的组织、存储和检索,而日志文件则记录了系统和应用的运行状态,是排查故障和维护系统的重要依据。通过掌握文件系统和日志文件的管理和分析技能,可以有效提升系统的稳定性和安全性。
97 7