当前位置:   article > 正文

Linux kernel SPI源码分析之SPI设备驱动源码分析(linux kernel 5(2)_kernel spi-msm-geni spi dev

kernel spi-msm-geni spi dev

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以点击这里获取!

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

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);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

匹配成功后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.
	 */
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

其中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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58

主要功能就是调用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);    /* 驱动模块注销 */
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

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)
  • 1
  • 2

入参传入 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,
};

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

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同步传输 */
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

上述代码中的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);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

通过源码可知,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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

梳理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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

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);             /* 发起数据传输 */
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

将要发送的数据填充到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函数分析

最后的话

最近很多小伙伴找我要Linux学习资料,于是我翻箱倒柜,整理了一些优质资源,涵盖视频、电子书、PPT等共享给大家!

资料预览

给大家整理的视频资料:

给大家整理的电子书资料:

如果本文对你有帮助,欢迎点赞、收藏、转发给朋友,让我有持续创作的动力!

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以点击这里获取!

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

电子书、PPT等共享给大家!

资料预览

给大家整理的视频资料:

[外链图片转存中…(img-s8HD5ajs-1714854796539)]

给大家整理的电子书资料:

[外链图片转存中…(img-nNbipMaO-1714854796540)]

如果本文对你有帮助,欢迎点赞、收藏、转发给朋友,让我有持续创作的动力!

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以点击这里获取!

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/712460
推荐阅读
相关标签
  

闽ICP备14008679号