spidev.c:SPI设备驱动的核心实现逻辑
本文章相关专栏往期内容,SPI子系统专栏:
PCI/PCIe子系统专栏:
- 专栏地址:PCI/PCIe子系统
- PCIe设备MSI/MSI-X中断源码分析与驱动编写
– 末片,有专栏内容观看顺序Uart子系统专栏:
- 专栏地址:Uart子系统
- Linux内核早期打印机制与RS485通信技术
– 末片,有专栏内容观看顺序interrupt子系统专栏:
- 专栏地址:interrupt子系统
- Linux 链式与层级中断控制器讲解:原理与驱动开发
– 末片,有专栏内容观看顺序pinctrl和gpio子系统专栏:
专栏地址:pinctrl和gpio子系统
编写虚拟的GPIO控制器的驱动程序:和pinctrl的交互使用
– 末片,有专栏内容观看顺序
input子系统专栏:
- 专栏地址:input子系统
- input角度:I2C触摸屏驱动分析和编写一个简单的I2C驱动程序
– 末片,有专栏内容观看顺序I2C子系统专栏:
- 专栏地址:IIC子系统
- 具体芯片的IIC控制器驱动程序分析:i2c-imx.c-CSDN博客
– 末篇,有专栏内容观看顺序总线和设备树专栏:
- 专栏地址:总线和设备树
- 设备树与 Linux 内核设备驱动模型的整合-CSDN博客
– 末篇,有专栏内容观看顺序


1. probe函数
根据上述的框架图来看一下probe函数
\Linux-4.9.88\drivers\spi\spidev.c:
static int spidev_probe(struct spi_device *spi)
{
struct spidev_data *spidev;
int status;
unsigned long minor;
/*
* spidev should never be referenced in DT without a specific
* compatible string, it is a Linux implementation thing
* rather than a description of the hardware.
*/
if (spi->dev.of_node && !of_match_device(spidev_dt_ids, &spi->dev)) {
dev_err(&spi->dev, "buggy DT: spidev listed directly in DT\n");
WARN_ON(spi->dev.of_node &&
!of_match_device(spidev_dt_ids, &spi->dev));
}
spidev_probe_acpi(spi);
/* Allocate driver data */
spidev = kzalloc(sizeof(*spidev), GFP_KERNEL); //------(1)
if (!spidev)
return -ENOMEM;
/* Initialize the driver data */
spidev->spi = spi;
spin_lock_init(&spidev->spi_lock);
mutex_init(&spidev->buf_lock); //------(1)
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); //-----(2)
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,
spidev, "spidev%d.%d",
spi->master->bus_num, spi->chip_select);
status = PTR_ERR_OR_ZERO(dev);
} else {
dev_dbg(&spi->dev, "no minor number available!\n");
status = -ENODEV;
} //-----(2)
if (status == 0) { //-----(3)
set_bit(minor, minors);
list_add(&spidev->device_entry, &device_list);
}
mutex_unlock(&device_list_lock); //-----(3)
spidev->speed_hz = spi->max_speed_hz;
if (status == 0)
spi_set_drvdata(spi, spidev); //-----(4)
else
kfree(spidev);
return status;
}
(1)分配了一个spidev_data *spidev,有啥用???;来看一下它的成员就知道了:
struct spidev_data {
dev_t devt;
//存储设备号(包括主设备号和次设备号),用于标识这个 SPI 设备在系统中的设备文件。
spinlock_t spi_lock;
//自旋锁,用于保护 SPI 设备的并发访问。它确保在访问或修改 spi 设备时不会发生竞态条件,适用于短时间的锁定操作。
struct spi_device *spi;
//指向与该设备关联的 SPI 设备结构体。这是 SPI 核心层分配并传递给驱动程序的,用于存储 SPI 设备的相关信息。
struct list_head device_entry;
//设备链表节点,用于将该 SPI 设备添加到驱动程序管理的设备列表中。它帮助驱动程序管理多个 SPI 设备实例。
/* TX/RX buffers are NULL unless this device is open (users > 0) */
struct mutex buf_lock;
//互斥锁,用于在访问传输和接收缓冲区(tx_buffer 和 rx_buffer)时提供同步保护。适用于较长时间的锁定操作,比如当设备文件被打开或关闭时。
unsigned users;
//记录当前打开该设备的用户数量。只有当这个计数大于 0 时,tx_buffer 和 rx_buffer 才会被分配。每当设备被打开时,用户计数会增加;当设备被关闭时,计数减少。
u8 *tx_buffer;
//发送数据的缓冲区。这个缓冲区在设备被用户打开时分配,在数据传输过程中用于存储需要发送给 SPI 设备的数据。
u8 *rx_buffer;
//接收数据的缓冲区。这个缓冲区同样在设备被打开时分配,用于存储从 SPI 设备接收到的数据。
u32 speed_hz;
//存储 SPI 总线的通信速度(以 Hz 为单位)。在每次数据传输时,会使用这个字段来设置 SPI 总线的频率。如果需要,用户可以通过修改该值来更改传输速度。
};
- 是 SPI 设备驱动程序
spidev用于保存设备特定信息的数据结构。它封装了与 SPI 设备交互时所需的各种状态和缓冲区。当作对spi_device设备的抽象表达的进一步完善吧,就是多给了两个x/r buff之类的??。在probe函数中分配后也有对其进行赋值设置的操作
(2)这一部分,都应该比较熟悉了吧,无非就是为设备分配次设备号(minor number)
- 获取设备列表锁,并寻找可用的次设备号(minor number)。次设备号用于唯一标识该设备。
- 如果找到可用的次设备号,将其分配给
spidev->devt,并使用device_create注册该设备,同时将设备的名字设置为spidevX.Y(X 是总线号/主设备号,Y 是次设备号)。 - 如果没有可用的次设备号,则输出调试信息并返回错误状态
-ENODEV(表示无设备)。
(3)设备创建后,肯定就是更新设备列表和驱动数据: 将次设备号标记为已使用,并将设备条目添加到设备列表中。最后释放设备列表的互斥锁。
(4)既然设置好了spidev_data *spidev,data??那么就肯定同步进spi_device中(真要说起来spi_device才是对spi设备的抽象表达),同步进spi_deivce的哪里:spidev_data *spi_deivce->device *dev->void *driver_data。这个driver_data是什么在讲驱动的deivce和driver结构体的时候已经讲过了(驱动中的device和device_driver结构体-CSDN博客):驱动程序的私有数据
2. 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 = 0;
/* chipselect only toggles at start or end of operation */
if (count > bufsiz)
return -EMSGSIZE;
spidev = filp->private_data;
mutex_lock(&spidev->buf_lock);
status = spidev_sync_read(spidev, count); //-------(1)
if (status > 0) {
unsigned long missing;
missing = copy_to_user(buf, spidev->rx_buffer, status);//-------(1)
if (missing == status)
status = -EFAULT;
else
status = status - missing;
}
mutex_unlock(&spidev->buf_lock);
return status;
}
(1)一看应该就能了然了,应该不用多讲吧,主要就是spidev_sync_read函数读到spidev中的rx_buffer,再用copy_to_user拷贝出来。
但是,不是说传输单位是spi_transfer吗??看一下spidev_sync_read组成就知道了,不要当作它是内核提供的接口API:
static inline ssize_t
spidev_sync_read(struct spidev_data *spidev, size_t len)
{
struct spi_transfer t = {
.rx_buf = spidev->rx_buffer,
.len = len,
.speed_hz = spidev->speed_hz,
};
struct spi_message m;
spi_message_init(&m);
spi_message_add_tail(&t, &m);
return spidev_sync(spidev, &m); //---(A)
}
//----------------------------分割-------------------------------------------------
//其实(A)spidev_sync又是如下:
static ssize_t
spidev_sync(struct spidev_data *spidev, struct spi_message *message)
{
DECLARE_COMPLETION_ONSTACK(done);
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;
}
最后不就调用了内核提供的API:spi_sync。弯弯绕绕的,还要搞一个spidev_data出来,不直接用spi_device,spidev_data不在probe函数赋值给了spi_deivce->dev->driver_data么,怎么不直接用spi_device中的呢?我认为应该是既然是spi的驱动程序,就得有自己的spidev_data来表示一个设备,直接对spi_deivce进行操作,可能会更改到spi_deivce吧。
spidev_data 作为 SPI 设备的特定驱动数据结构,负责管理用户空间和硬件设备之间的交互,而 spi_device 则专注于硬件设备本身的底层通信。通过这种设计,spidev 驱动程序能够有效地处理用户空间的请求,同时确保不直接影响到 spi_device 的底层硬件状态。
3. 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 = 0;
unsigned long missing;
/* chipselect only toggles at start or end of operation */
if (count > bufsiz)
return -EMSGSIZE;
spidev = filp->private_data;
mutex_lock(&spidev->buf_lock);
missing = copy_from_user(spidev->tx_buffer, buf, count);
if (missing == 0)
status = spidev_sync_write(spidev, count);
else
status = -EFAULT;
mutex_unlock(&spidev->buf_lock);
return status;
}
spidev_write差不多,不多讲了
4. sidev_ioctl函数
通过 **ioctl** 调用,进入调用**spidev_ioctl**,用户可以读取设备的参数、设置设备的配置,甚至执行复杂的消息传输。
static long
spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int err = 0;
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;
/* Check access direction once here; don't repeat below.
* IOC_DIR is from the user perspective, while access_ok is
* from the kernel perspective; so they look reversed.
*/
if (_IOC_DIR(cmd) & _IOC_READ)
err = !access_ok(VERIFY_WRITE,
(void __user *)arg, _IOC_SIZE(cmd));
if (err == 0 && _IOC_DIR(cmd) & _IOC_WRITE)
err = !access_ok(VERIFY_READ,
(void __user *)arg, _IOC_SIZE(cmd));
if (err)
return -EFAULT;
/* 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) {
u32 save = spi->mode;
if (tmp & ~SPI_MODE_MASK) {
retval = -EINVAL;
break;
}
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;
else
dev_dbg(&spi->dev, "%d Hz (max)\n", tmp);
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;
}
主要看switch (cmd) { }中的内存。 函数根据 cmd 的值来区分不同的操作。它包括:
- 读取请求:例如,读取 SPI 模式、位数和最大速度等。
- 写入请求:例如,设置 SPI 模式、位数和最大速度等。
- 消息请求:处理分段传输(full-duplex)和消息的请求。具体调用
spidev_message函数来执行消息传输。
用户程序使用ioctl函数示例:
#include <fcntl.h>
#include <linux/spi/spidev.h>
#include <sys/ioctl.h>
#include <unistd.h>
// 打开 SPI 设备
int fd = open("/dev/spidev0.0", O_RDWR);
// 设置 SPI 模式
unsigned char mode = SPI_MODE_0;
ioctl(fd, SPI_IOC_WR_MODE, &mode);
// 读取 SPI 模式
ioctl(fd, SPI_IOC_RD_MODE, &mode);
// 设置最大速度
unsigned int speed = 500000;
ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);
// 关闭设备
close(fd);
更多推荐
所有评论(0)