Linux kernel SPI源码分析之SPI设备驱动源码分析(linux kernel 5

150 阅读6分钟

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) {


![](https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/96947ec75d2b427999f51e4c2e59b192~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg55So5oi3MDgwNDUxMTkwMTI=:q75.awebp?rk3s=f64ab15b&x-expires=1772114175&x-signature=LLq5e6XTI55e4bsyNECB05g8u%2BE%3D)

最全的Linux教程,Linux从入门到精通

======================

1.  **linux从入门到精通(第2版)**

2.  **Linux系统移植**

3.  **Linux驱动开发入门与实战**

4.  **LINUX 系统移植 第2版**

5.  **Linux开源网络全栈详解 从DPDK到OpenFlow**



![华为18级工程师呕心沥血撰写3000页Linux学习笔记教程](https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/6bfbf7d45c464f40bbb1fc18f3f914cb~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg55So5oi3MDgwNDUxMTkwMTI=:q75.awebp?rk3s=f64ab15b&x-expires=1772114175&x-signature=b7BJIoFp%2Fp8l2q1Ml1Mpn%2F1SB20%3D)



第一份《Linux从入门到精通》466页

====================

内容简介

====

本书是获得了很多读者好评的Linux经典畅销书**《Linux从入门到精通》的第2版**。本书第1版出版后曾经多次印刷,并被51CTO读书频道评为“最受读者喜爱的原创IT技术图书奖”。本书第﹖版以最新的Ubuntu 12.04为版本,循序渐进地向读者介绍了Linux 的基础应用、系统管理、网络应用、娱乐和办公、程序开发、服务器配置、系统安全等。本书附带1张光盘,内容为本书配套多媒体教学视频。另外,本书还为读者提供了大量的Linux学习资料和Ubuntu安装镜像文件,供读者免费下载。


![华为18级工程师呕心沥血撰写3000页Linux学习笔记教程](https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/a57f2094008441bd81a3966e7a596dbc~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg55So5oi3MDgwNDUxMTkwMTI=:q75.awebp?rk3s=f64ab15b&x-expires=1772114175&x-signature=PB4sTKA%2B6e5hza0pr2LT8fKT8tI%3D)


**本书适合广大Linux初中级用户、开源软件爱好者和大专院校的学生阅读,同时也非常适合准备从事Linux平台开发的各类人员。**

> 需要《Linux入门到精通》、《linux系统移植》、《Linux驱动开发入门实战》、《Linux开源网络全栈》电子书籍及教程的工程师朋友们劳烦您转发+评论


详情docs.qq.com/doc/DSmdCdUNwcEJDTXFK