赞
踩
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事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);
匹配成功后spidev.c里面的spidev_probe就会被调用。
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 */
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,
};
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总线驱动实现数据的发送功能。
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函数分析
最近很多小伙伴找我要Linux学习资料,于是我翻箱倒柜,整理了一些优质资源,涵盖视频、电子书、PPT等共享给大家!
给大家整理的视频资料:
给大家整理的电子书资料:
如果本文对你有帮助,欢迎点赞、收藏、转发给朋友,让我有持续创作的动力!
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
电子书、PPT等共享给大家!
给大家整理的视频资料:
[外链图片转存中…(img-s8HD5ajs-1714854796539)]
给大家整理的电子书资料:
[外链图片转存中…(img-nNbipMaO-1714854796540)]
如果本文对你有帮助,欢迎点赞、收藏、转发给朋友,让我有持续创作的动力!
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。