本文章相关专栏往期内容,SPI子系统专栏:

  1. SPI通信协议与Linux设备驱动框架解析
  2. SPI传输与驱动框架的实现

PCI/PCIe子系统专栏:

  1. 专栏地址:PCI/PCIe子系统
  2. PCIe设备MSI/MSI-X中断源码分析与驱动编写
    – 末片,有专栏内容观看顺序

Uart子系统专栏:

  1. 专栏地址:Uart子系统
  2. Linux内核早期打印机制与RS485通信技术
    – 末片,有专栏内容观看顺序

interrupt子系统专栏:

  1. 专栏地址:interrupt子系统
  2. Linux 链式与层级中断控制器讲解:原理与驱动开发
    – 末片,有专栏内容观看顺序

pinctrl和gpio子系统专栏:

  1. 专栏地址:pinctrl和gpio子系统

  2. 编写虚拟的GPIO控制器的驱动程序:和pinctrl的交互使用

    – 末片,有专栏内容观看顺序

input子系统专栏:

  1. 专栏地址:input子系统
  2. input角度:I2C触摸屏驱动分析和编写一个简单的I2C驱动程序
    – 末片,有专栏内容观看顺序

I2C子系统专栏:

  1. 专栏地址:IIC子系统
  2. 具体芯片的IIC控制器驱动程序分析:i2c-imx.c-CSDN博客
    – 末篇,有专栏内容观看顺序

总线和设备树专栏:

  1. 专栏地址:总线和设备树
  2. 设备树与 Linux 内核设备驱动模型的整合-CSDN博客
    – 末篇,有专栏内容观看顺序

img

img

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);
Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐