Linux设备驱动模型与 sysfs实现分析以及设计模式应用

编程入门 行业动态 更新时间:2024-10-26 00:30:59

Linux设备驱动<a href=https://www.elefans.com/category/jswz/34/1771358.html style=模型与 sysfs实现分析以及设计模式应用"/>

Linux设备驱动模型与 sysfs实现分析以及设计模式应用

RTOS和Linux系统上开发驱动的方式非常的不同,在RTOS系统下,驱动和驱动之间并没有实质性的联系,不同的驱动和BSP之间仅仅通过一层很薄很薄的设备管理框架聚合在一起构成RTOS的设备管理子系统。图形化表示如下:

设备驱动&BSP之间互相独立,互不影响,互不依赖,独立实现,挂入设备管理框架中,构成一个类似“烟囱”式的垂直架构。这样的架构缺乏设备抽象,使设备的差异化特性透过设备管理框架传递给上层,增加了RTOS系统的设备管理难度。

和RTOS系统不同,Linux提出了一种新的方式,让系统中的各种设备及其驱动程序能够有效沟通起来,如同人类社会发展那样,孤独的原始人类需要进入群居时代,于是部落产生了,Linux参考社会构造,抽象出了设备的聚合 “部落”-设备模型。

sysfs就是这种设计思想的实现,Linux设备模型如同一栋规模宏大的建筑,为了构建它,除了基本的建筑材料外(kset,kobject等数据对象),需要一种机制,来向建筑面的世界展示其内部的构造,并且通过通用的接口方式实现与外界的沟通与互动,sysfs文件系统就充当了这种角色,它不但在各种基础的建造材料之间建立彼此的互联层次关系,而且向外界提供了与建筑内设施进行互动的文件接口,这种比喻反映到LINUX系统,就是sysfs文件系统在内核空间进行设备之间的合从连衡,以及以为文件系统的树形结构层次向用户空间提供了系统和硬件设备之间的一个拓扑图。

具备LINUX 内核驱动开发基础的工程师一定直到,IOCTL是万能的(因为没有名称固定内涵,所以外延广阔),SYSFS替代了IOCTL的功能。

sysfs最关键基础数据结构就是总线,搞懂总线是很好的切入点.

Platform driver&Platform Device

一个现实的 linux 设备和驱动通常都需要挂接在一种总线上,总线可以是一种真是存在的总线。对于挂接在 USB、I2C、SPI 的设备,自然就是真实的总线。但是 linux 中,还有一种设备形态,它有独立外设控制器、挂接在 soc 外设空间的外设不依赖前面说的总线。因此,此时需要另外一种总线了。Linux 发明了一种虚拟的总线,名为 platform 总线,相应的设备是 platform_device,相应的驱动是platform_driver。

关于Linux平台设备驱动模型,并不是创建新的设备分类,是在原有的字符设备基础上使用,将设备和驱动分开,形式上生成两个.ko文件。Linux内核维护一个全局设备链表,对应的总线会将驱动和设备链表里的设备名进行匹配,如果匹配成功就会将设备的信息传递给驱动的probe函数,probe函数得到设备的核心结构体platform_device的信息就可以进行对应的操作。

字符设备驱动则是驱动和设备是一体的,设备信息以常量的形式写在驱动中,不需要探测和PROBE的过程。

开发者只需实现平台驱动和平台设备即可,平台总线是内核实现的,常见的总线如IIC、SPI、CAN等,LED、KEY这类型的普通字符设备,linux内核就使用虚拟的平台总线struct bus_type platform_bus_type来匹配这类设备。

The bus type definition in kernel:

the relationship among platform_bus_type,platform_device,platform_driver is as below:

从数学因果和映射角度看,这是一个单向映射:

Key structure definition,The definition of platform_device:

Definition of platform driver:

Definition of platform_bus_type

platform_driver&platform_device match method

each bus type offer a match method for all the driver and device belongs to the bus probe each other. all the device&driver of specific bus share the same match method. for the platform_bus_type, the match method is platform_match:

several match  metho exists, the match will be sucess if any of this passed check.

因此,平台驱动有三种方式和平台设备相匹配。

第一种:也就是优先级最低的一种。struct device_driver下的name成员和平台设备struct platform_device下的name成员匹配。

第二种:优先级第二高。平台设备platform_device下的name成员和platform_driver下的platform_device_id所指向的数组的每个元素的name匹配。

第三种:优先级最高,使用的是设备树方式。platform_driver下的device_driver下的of_device_id所指向的每个元素的compatible值与设备树中设备节点的compatibl值相匹配。

这里的优先级是指,当设备树方式实现时,会先用设备树方式匹配,当设备树无法匹配时会使用platform_device_id匹配,最后再使用platform_driver下的name成员匹配。

so we can write a platform driver for a specify device in this way.

platform_dev.c


#include <linux/module.h>
#include <linux/device.h>
#include <linux/platform_device.h>static void platform_dev_release(struct device *dev)
{printk("%s -- %d.\n", __FUNCTION__, __LINE__);
}struct platform_device pdev_1 = {.name = "demo_platform",.id = 1,.num_resources = 0,.resource = NULL,.dev = {.release = platform_dev_release,},
};struct platform_device pdev_2 = {.name = "demo_platform",.id = 2,.num_resources = 0,.resource = NULL,.dev = {.release = platform_dev_release,},
};static int __init platform_dev_init(void)
{printk("%s -- %d.\n", __FUNCTION__, __LINE__);platform_device_register(&pdev_1);platform_device_register(&pdev_2);return 0;
}static void __exit platform_dev_exit(void)
{printk("%s -- %d.\n", __FUNCTION__, __LINE__);platform_device_unregister(&pdev_2);    platform_device_unregister(&pdev_1);
}module_init(platform_dev_init);
module_exit(platform_dev_exit);
MODULE_LICENSE("GPL");

platform_drv.c


#include <linux/module.h>
#include <linux/platform_device.h>static int demo_probe(struct platform_device *pdev)    // 平台驱动和平台设备匹配成功时调用
{    printk("%s -- %d.\n", __FUNCTION__, __LINE__);return 0;
}static int demo_remove(struct platform_device *pdev) // 平台设备移除时调用 
{printk("%s -- %d.\n", __FUNCTION__, __LINE__);return 0;
}struct platform_driver demo_drv = {.probe = demo_probe,.remove = demo_remove,.driver = {.name = "demo_platform",    // 用于和平台设备匹配的名字.owner = THIS_MODULE,},
};static int __init platform_drv_init(void)
{printk("%s -- %d.\n", __FUNCTION__, __LINE__);platform_driver_register(&demo_drv);    // 注册平台驱动return 0;
}static void __exit platform_drv_exit(void)
{printk("%s -- %d.\n", __FUNCTION__, __LINE__);platform_driver_unregister(&demo_drv);    // 注销平台驱动
}module_init(platform_drv_init);
module_exit(platform_drv_exit);
MODULE_LICENSE("GPL");

device topology:

insmod sequence effect

first insmod platform_dev.ko and then insmod platform_drv.ko, the probe will be launched by platform_drv_init.

vice vesa.the probe will be launch by platform_dev_init.

this process can be illustration as below graph:

bus框架起到了热插拔探测的作用,这样当设备插拔或者驱动安装时,能自动发现插入的设备或者安装的驱动。实现对设备的自动化支持。

如果是同样的驱动移植到RTOS平台,则完全不需要BUS这套框架,实现设备和驱动的自动化绑定,只要在驱动中静态分配好设备需要的资源即可,把设备和驱动的动态绑定改为RTOS系统初始化时的静态绑定,也不需要probe函数中的设备参数了,直接给值就可以了,如下图所示:

vin_probe in melis:

vin_probe in linux:

设备 (或驱动) 注册的时候,都会引发总线调用自己的 match 函数来寻找目前 platform 总线是否挂载有与该设备 (或驱动) 名字匹配的驱动 (或设备),如果存在则将双方绑定;如果先注册设备,驱动还没有注册,那么设备在被注册到总线上时,将不会匹配到与自己同名的驱动,然后在驱动注册到总线上时,因为设备已注册,那么总线会立即匹配与绑定这时的同名的设备与驱动,再调用驱动中的 probe 函数等;如果是驱动先注册,同设备驱动一样先会匹配失败,匹配失败将导致它的 probe 函数暂不调用,而是要等到设备注册成功并与自己匹配绑定后才会调用。总的来说,就是完成设备和驱动的实现,每一个实现包含相关结构体的定义和注册。

平台设备的初始化

参考文章Linux 系统Candy_papaofdoudou的博客-CSDN博客

平台设备初始化阶段的注册是在哪里?一节

Relationship with char device driver

通常编写linux字符设备常接触到的file_operations以及miscdevice,然后申请设备号,注册字符设备,没有涉及到设备驱动模型,而驱动模型里,device_driver根本没有涉及到设备操作的函数、file_operations等,只有一些电源管理,热插拔相关的函数。platform_device里也主要是resource的管理,所以感觉两者根本就没关系,也很奇怪为什么要弄两套东西来实现,而且两者也对应不起来。

actually, platform device usually taken as the parent device of char device. take below for example.

platform_device->dev passed to device_create during char device created process.

find all the devices use the same "demo_platform" drivers.


#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/stat.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/seq_file.h>
#include <linux/sched/signal.h>
#include <linux/proc_fs.h>
#include <linux/pid.h>
#include <linux/pci.h>
#include <linux/usb.h>
#include <linux/kobject.h>
#include <linux/sched/mm.h>
#include <linux/platform_device.h>MODULE_AUTHOR("zlcao");
MODULE_LICENSE("GPL");int seqfile_debug_mode = 0;
EXPORT_SYMBOL(seqfile_debug_mode);
module_param(seqfile_debug_mode, int, 0664);int pid_number = -1;
EXPORT_SYMBOL(pid_number);
module_param(pid_number, int, 0664);static void kill_processes(int pid_nr);
// 开始输出任务列表
// my_seq_ops_start()的返回值,会传递给my_seq_ops_next()的v参数
static void *my_seq_ops_start(struct seq_file *m, loff_t *pos)
{loff_t index = *pos;struct task_struct *task;printk("%s line %d, index %lld.count %ld, size %ld here.\n", __func__, __LINE__, index, m->count, m->size);if(seqfile_debug_mode == 0) {// 如果缓冲区不足, seq_file可能会重新调用start()函数,// 并且传入的pos是之前已经遍历到的位置,// 这里需要根据pos重新计算开始的位置for_each_process(task) {if (index-- == 0) {return task;}}} else {return NULL + (*pos == 0);}return NULL;
}// 继续遍历, 直到my_seq_ops_next()放回NULL或者错误
static void *my_seq_ops_next(struct seq_file *m, void *v, loff_t *pos)
{struct task_struct *task = NULL;if(seqfile_debug_mode == 0) {task = next_task((struct task_struct *)v);// 这里加不加好像都没有作用++ *pos;// 返回NULL, 遍历结束if(task == &init_task) {return NULL;}} else {++ *pos;}return task;
}// 遍历完成/出错时seq_file会调用stop()函数
static void my_seq_ops_stop(struct seq_file *m, void *v)
{}static int lookup_pci_devices(struct device *dev, void *data)
{struct seq_file *m = (struct seq_file *)data;struct pci_dev *pdev = to_pci_dev(dev);seq_printf(m, "vendor id 0x%x, device id 0x%x, devname %s.\n", pdev->vendor, pdev->device, dev_name(&pdev->dev));return 0;
}static int lookup_pci_drivers(struct device_driver *drv, void *data)
{struct seq_file *m = (struct seq_file *)data;seq_printf(m, "driver name %s.\n", drv->name);return 0;
}static int lookup_platform_devices(struct device *dev, void *data)
{struct seq_file *m = (struct seq_file *)data;struct platform_device *platdev = to_platform_device(dev);seq_printf(m, "devpath %s.\n", platdev->name);return 0;
}static int lookup_platform_drivers(struct device_driver *drv, void *data)
{struct seq_file *m = (struct seq_file *)data;seq_printf(m, "driver name %s.\n", drv->name);return 0;
}static int list_device_belongs_todriver_pci(struct device *dev, void *p) 
{struct seq_file *m = (struct seq_file *)p;struct pci_dev *pdev = to_pci_dev(dev);seq_printf(m, "vendor id 0x%x, device id 0x%x, devname %s.\n", pdev->vendor, pdev->device, dev_name(&pdev->dev));return 0;
}static int list_device_belongs_todriver_platform(struct device *dev, void *p) 
{struct seq_file *m = (struct seq_file *)p;struct platform_device *platdev = to_platform_device(dev);seq_printf(m, "platdevname %s.\n", platdev->name);return 0;
}static int pcie_device_info(struct pci_dev *pdev, void *data)
{struct seq_file *m = (struct seq_file *)data;seq_printf(m, "vendor id 0x%04x, device id 0x%04x, devname %s, belongs to bus %16s, parent bus name %6s subordinate 0x%p.\n", \pdev->vendor, pdev->device, dev_name(&pdev->dev), pdev->bus->name, pdev->bus->parent? pdev->bus->parent->name : "null", pdev->subordinate);if(pdev->subordinate) {seq_printf(m, "    subordinate have bus name %s.\n", pdev->subordinate->name);if(pdev->subordinate->self) {seq_printf(m, "        subordinate have dev name %s.\n", dev_name(&pdev->subordinate->self->dev));if(pdev->subordinate->self != pdev) {seq_printf(m, "            cant happend!\n");} else {seq_printf(m, "            surely!\n");}}} else {seq_printf(m, "    subordinate not have.\n");}if(pdev->bus->self) {seq_printf(m, "    device belongs to child pci bus %s.\n", dev_name(&pdev->bus->self->dev));} else {seq_printf(m, "    device belongs to top lvl pci bus.\n");}seq_printf(m, "\n");return 0;
}static ssize_t zilong_attr_show(struct kobject *kobj, struct attribute *attr,char *buf)
{return 0;
}static ssize_t zilong_attr_store(struct kobject *kobj, struct attribute *attr, const char *buf, size_t count)
{return 0;
}static const struct sysfs_ops zilong_sysfs_ops = {.show   = zilong_attr_show,.store  = zilong_attr_store,
};static struct kobj_type zilong_ktype = {.release        = NULL,.sysfs_ops      = &zilong_sysfs_ops,.namespace      = NULL,.get_ownership  = NULL,
};// 此函数将数据写入`seq_file`内部的缓冲区
// `seq_file`会在合适的时候把缓冲区的数据拷贝到应用层
// 参数@V是start/next函数的返回值
static int my_seq_ops_show(struct seq_file *m, void *v)
{struct task_struct *task = NULL;struct task_struct *tsk = NULL;struct task_struct *p = NULL;struct file *file = m->private;struct pid *session = NULL;if(seqfile_debug_mode == 0) {seq_puts(m, " file=");seq_file_path(m, file, "\n");seq_putc(m, ' ');task = (struct task_struct *)v;session = task_session(task);tsk = pid_task(session, PIDTYPE_PID);if(task->flags & PF_KTHREAD) {seq_printf(m, "Kernel thread: PID=%u, task: %s, index=%lld, read_pos=%lld, %s.\n", task->tgid, task->comm, m->index, m->read_pos, tsk? "has session" : "no session");} else {seq_printf(m, "User thread: PID=%u, task: %s, index=%lld, read_pos=%lld %s.\n", task->tgid, task->comm, m->index, m->read_pos, tsk? "has session" : "no session");}} else if(seqfile_debug_mode == 1) {struct task_struct *g, *p;static int oldcount = 0;static int entercount = 0;char *str;printk("%s line %d here enter %d times.\n", __func__, __LINE__, ++ entercount);seq_printf(m, "%s line %d here enter %d times.\n", __func__, __LINE__, ++ entercount);rcu_read_lock();for_each_process_thread(g, p) {struct task_struct *session = pid_task(task_session(g), PIDTYPE_PID);struct task_struct *thread = pid_task(task_session(p), PIDTYPE_PID);struct task_struct *ggroup = pid_task(task_pgrp(g), PIDTYPE_PID);struct task_struct *pgroup = pid_task(task_pgrp(p), PIDTYPE_PID);struct pid * pid = task_session(g);if(list_empty(&p->tasks)) {str = "empty";} else {str = "not empty";}seq_printf(m, "process %s(pid %d tgid %d,cpu%d) thread %s(pid %d tgid %d,cpu%d),threadnum %d, %d. tasks->prev = %p, tasks->next = %p, p->tasks=%p, %s, process parent %s(pid %d tgid %d), thread parent%s(pid %d, tgid %d, files %p\n)",g->comm, task_pid_nr(g), task_tgid_nr(g), task_cpu(g), \p->comm, task_pid_nr(p), task_tgid_nr(p), task_cpu(p), \get_nr_threads(g), get_nr_threads(p), p->tasks.prev, p->tasks.next, &p->tasks, str, g->real_parent->comm, \task_pid_nr(g->real_parent),task_tgid_nr(g->real_parent), p->real_parent->comm, task_pid_nr(p->real_parent), task_tgid_nr(p->real_parent), p->files);if(ggroup) {seq_printf(m, "ggroup(pid %d tgid %d).", task_pid_nr(ggroup),task_tgid_nr(ggroup));}if(pgroup) {seq_printf(m, "pgroup(pid %d tgid %d).", task_pid_nr(pgroup),task_tgid_nr(pgroup));}seq_printf(m, "current smp processor id %d.", smp_processor_id());if(thread) {seq_printf(m, "thread session %s(%d).", thread->comm, task_pid_nr(thread));}if(session) {seq_printf(m, &

更多推荐

Linux设备驱动模型与 sysfs实现分析以及设计模式应用

本文发布于:2024-02-10 20:56:56,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1677236.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:模型   模式   设备   Linux   sysfs

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!