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

本文深入探讨Linux设备驱动模型,重点分析sysfs的实现、设备与驱动的匹配方法以及设计模式的应用。介绍了platform_driver和platform_device的关系,强调了sysfs在设备管理中的作用,包括sysfs的注册、架构和属性文件创建。同时,讨论了驱动设计中的迭代器、工厂和适配器模式,以及Linux内核中的其他设计模式如命令模式。此外,文章还涉及热插拔逻辑、设备释放和内存管理等关键概念。
摘要由CSDN通过智能技术生成

在驱动设计中,驱动开发者需要处理两类对象,分别是过程和设备,设备对象是驱动操作的目标,而过程则是设备运行规则的描述,RTOS和Linux系统上开发驱动的方式非常的不同,在RTOS系统下,驱动和驱动之间并没有实质性的联系,不同的驱动和BSP之间仅仅通过一层很薄很薄的设备管理框架聚合在一起构成RTOS的设备管理子系统。图形化表示如下:

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

这个世界上没有绝对的优点和缺点,虽然RTOS的这种设计增加了设备驱动的管理难度,但是方便了系统移植,RTOS一般运行于深嵌入式场合,在这种场景下,硬件和OS类型众多,较薄的管理复杂度,使切换硬件和切换OS变得比较容易,比如,如果更换硬件,就个呢更换BSP(HAL),如果更换OS,就更换设备驱动。在LINUX下,做这两件事是比较复杂的。

和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:

Linux设备驱动有一套bus,device,driver基础框架,这种设计将设备与板载信息解耦,使得设备和驱动可以灵活加载,屏蔽了板载信息的变化。当内核从一个平台移植到另外一个平台时,bus,device,driver这套机制的实现使得设备驱动的加载可以自适应。驱动不会因为板载设备的变化而重写代码。
设备驱动框架中,bus是连接device和driver的桥梁,因此在系统启动过程中它首先被注册。bus的作用,就是让系统启动时注册的设备可以找到对应的驱动,注册的驱动可以找到对应的设备,这个过程称为设备与驱动的绑定。
总线上发生两类事件可以导致设备与驱动绑定行为的发生:一是通过device_register函数向某一bus上注册设备,这种情况下内核除了将该设备加入到bus上设备链表的尾端,同时会试图将此设备与总线上所有驱动对象进行绑定操作;二是通过driver_register将某一驱动注册到其所属的bus上,内核此时除了将驱动对象加入到bus所有驱动对象构成的链表的尾部,也会试图将驱动与器上的所有设备进行绑定操作:

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

driver=f(device) \\ device \neq f(driver)

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值相匹配。

最后一中比较方式超脱三届外,不在五行中,有些设备,比如PCI设备,rpmsg设备的设备结构体中定义了driver_override字段,表示force match的驱动名字,只要这个设置上了,其他所有的匹配都无效,这个最大。

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

另外,既然实现匹配逻辑的函数是在bus对象中,所以,世纪上匹配的算法每条BUS可以自定义实现,并不局限于以上几种顺序。

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 函数暂不调用,而是要等到设备注册成功并与自己匹配绑定后才会调用。总的来说,就是完成设备和驱动的实现,每一个实现包含相关结构体的定义和注册。

match,probe和总线

LINUX内核中,总线是驱动和设备的容器,设备和容器通过总线进行关联,总线提供match和probe回调进行设备和驱动比对以及设备探测,而驱动仅仅提供PROBE函数执行具体的探测即可。

总线一般会提供MATCH回调,PROBE则由驱动实现,PCI设备的探测过程比较特殊,pci_bus_type既提供了match方法也提供了probe方法,并且在两个方法中都进行了驱动和设备的match操作,相当于寄生在总线MATCH回调和总线PROBE回调中调用两次。具体分析可看pci_match_device函数的调用上下文。

另一个比较特殊的总线是mdev_bus_type,它定义了PROBE方法,但是没有定义MATCH方法,而且PROBE方法中也没有进行驱动和设备的MATCH操作,这就造成了一个很有意思的现象,MDEV 总线只有一个驱动,注册到这个总线上的设备都无条件和这个驱动绑定,那么怎么区分设备的不同驱动方式呢?原来mdev_register_driver接口注册的驱动仅仅是一个通用的桥接框架,真正的驱动OPS是struct mdev_parent_ops,这个OPS在最开始调用mdev_register_device的时候已经和父设备以及父设备建立的SYSFS对象绑定好了,当创建MDEV设备时,通过“echo "uuid" > ../../create"方式创建的MDEV设备进而可以根据SYSFS上下文和父设备绑定,进而和mdev_parent_ops绑定,不同的MDEV设备创建的时候通过不同的SYSFS CREATE节点创建,也就区分了不同的MDEV设备的驱动方式。

相当于设备(parent device已经提前和parent ops绑定了,mdev device 和mdev driver的绑定只是走个过场)。

如下图,在mdev总线上注册的驱动只有一个(linux-5.4 kernel),但是注册在mdev总线上的设备却可以创建无数个,每个设备用UUID命名。mdev虚拟化PCI串口试验-CSDN博客

5.15.146内核的MDEV 驱动探测上的实现作了一些调整,mdev_driver不再只有一个,同时mdev_bus_type也实现了match函数。

但是由于match函数始终返回0, 所以BUS上起不到自动探测绑定的作用,最终还是需要手动调用device_driver_attach来实现struct mdev_device 和mdev_driver的绑定。

新版强化了struct vfio_device_ops作用,弱化了struct mdev_parent_ops的作用,vendor回调完全由struct vfio_device_ops实现。

平台设备的初始化

参考文章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) {
          
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

papaofdoudou

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值