linux平台设备驱动详解---------热插拔

专栏
https://blog.csdn.net/pengliang528/category_9316091.html

原文链接:https://blog.csdn.net/pengliang528/article/details/100556340

概念:

平台总线只是一种设备模型,其管理的设备本质不变,因此应用访问平台总线上的设备通过/dev这个通道也不会发生改变,只是将设备(符合平台总线设备共性)塞入驱动框架的平台总线来管理,相比字符设备而言只是增加了sys目录下的视图关系。

本章将以平台设备的添加、驱动的注册为切入点,逐步分析总线、平台总线设备的软件层次、组成框架来展示平台总线设备的详情。

应用示例

1.平台备添加设

static struct platform_device xxx_device = {
    .name = "xxx",
    .id = 0,
    .dev= {
        .platform_data	= xxx_data,
    },
    .num_resources = ARRAY_SIZE(xxx_resources),
    .resource = xxx_resources,
};
 
platform_device_register(&smsc_lan9217_device);

总线的挂载

1.总线框架
1.总线的初始化

-----itop4412_kernel_3.0/init/main.c-----
start_kernel
    rest_init();
        kernel_init(void * unused)
            do_basic_setup();
----itop4412_kernel_3.0/drivers/base/init.c-----
                // /sys下创建class、bus、devices、driver关键子目录、并初始化关键总线
                driver_init();
                    // 依次生成/sys/devices、/sys/dev、/sys/dev/block、/sys/dev/char子目录及相关父kobj(作后续添加节点的根)
                    buses_init();
                        // 创建一个bus的kset对象,作为后续总线挂载的根节点并生成/sys/bus目录,不再延生,请查看kobject章节
                        bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL);
 
展开:
struct kset *kset_create_and_add(const char *name,const struct kset_uevent_ops     *uevent_ops,struct kobject *parent_kobj)
    name:所需要创建kset的name
    uevent_ops:uevent操作集
    parent_kobj:父节点对象

2.总线的注册:int bus_register(struct bus_type *bus)
1.申请一个总线资源对象,并绑定注入的总线包括总线名
2.将其根节点置为总线初始化所产生的所有总线根对象bus_kset
3.注册该总线至sysfs文件系统,通知用户态在/sys/bus下添加对应的总线目录
4.产生该总线下的devices、drivers目录、对应根节点、对应类型链表用于挂载对应这设备
5.设置总线的属性

3.总结
1.驱动框架模型是建立在kobject、sysfs基础之上,因此最终的操作都要归结至此,总线是属于其中的一个分支也需要满足
2.所有驱动框架实现需要依赖sysfs文件系统,因此该文件系统应该首先被注册
3.总线是虚拟的,并不对应实际的设备,因此总线的初始化并不依赖tmpfs文件系统

2.平台总线的注册流程

-----itop4412_kernel_3.0/init/main.c-----
start_kernel
    vfs_caches_init(totalram_pages)
        mnt_init();
            // sysfs文件系统先初始化,设备模型需要依赖于sysfs文件系统
            sysfs_init();
    rest_init();
        kernel_init(void * unused)
            do_basic_setup();
                // tmpfs文件系统先初始化,设备模型不仅在sys下创建视图,同时也会在/dev下创建用户访问通道,因为其本质上还是设备
                init_tmpfs();
                // /sys下创建class、bus、devices、driver关键子目录、并初始化关键总线
                driver_init();
                // 各模块初始化
                do_initcalls();
-----itop4412_kernel_3.0/drivers/base/init.c----
                    driver_init();
                        // 依次生成/sys/devices、/sys/dev、/sys/dev/block、/sys/dev/char子目录及相关父kobj(作后续添加节点的根)
                        devices_init();
                        // 生成/sys/bus目录及父节点bus_kset作为后续添加子节点的根
                        buses_init();
                        // 生成/sys/class目录及父节点class_kset作为后续添加子节点的根
                        classes_init();
                        // 生成/sys/firmware目录及父节点firmware_kobj作为后续添加子节点的根
                        firmware_init();
                        // 平台总线初始化
                        platform_bus_init();
                        // 生成/sys/devices/system目录及父节点system_kset作为后续添加子节点的根
                        system_bus_init();
                        // 生成/sys/devices/system/cpu目录及父节点system_kset作为后续添加子节点的根
                        cpu_dev_init();
-----itop4412_kernel_3.0/drivers/base/platform.c---
                            platform_bus_init();
                                device_register(&platform_bus);				// 注册平台总线设备、为虚拟设备,只有设备名无硬件资源
                                bus_register(&platform_bus_type);			// 注册平台总线驱动
 
 
总结:
 
    1>字符设备驱动不依赖于驱动模型,因此可以在内核资源完成后即可添加字符设备驱动。
    2>由于平台总线模型需要依托/sys,因此需要先初始化sysfs文件系统。
    3>平台总线管理的设备脱离不了设备的本质,而设备的共性就是在/dev下预留给用户的访问通道,因此在此之前tmpfs文件系统也需要被初始化。
    4>平台总线依赖于总线,因此总线必须先初始化。
    5>platform总线通常管理SOC上的控制芯片,而很多总线在初始化阶段需要初始化其控制器,因此需要优先注册platform总线。
    6>平台总线归于/sys设备模型,因此需要满足其3要求(设备、驱动、总线)、而平台总线是一个虚拟的总线,因此在内核态虚拟一个这样的设备出来(只有设备名无硬件资源)

总线、设备、驱动三者的关系视图

在这里插入图片描述

总线存在的意义

用来管理、做经分离后的硬件资源、驱动的匹配,此模型实现了热插拔

1.依据模型构建/sys/下各关键子目录及文件节点关联视图,以便用户访问,该关联视图见sysfs与kobject相关章节
  设置依据kobject关联(如父节点、自身引用计数、所属总线、类别、创建并管理设备、驱动的链表)构建sysfs视图下的各个节点形成整体视图
 
2.构建一个统一模型,将硬件资源和驱动分离、构建该模型下的硬件资源和驱动的关联机制
  本节着重以此为线索展开:
 
    
硬件资源:
    相关接口:platform_device_register(struct platform_device *);
 
    1>硬件资源以struct device为基类,进行当前所属设备类别的再次封装,所作动作分41>构建4.1的视图
        2>创建/dev下的访问通道
          devtmpfs文件节点只分字符设备和块设备,同时通过kobject_uevent(&dev->kobj, KOBJ_ADD)来通知上层应用得mdev通过mknod来创建相应的设备节点,最终在/dev下创建对应的用于应用访问的设备通道,并与cdev_map形成关联,详见“字符设备驱动详解”4.2章节
        3>在该总线上添加设备并探测驱动
          bus_add_device(dev);
          bus_probe_device(dev)
        
    2>驱动
      相关接口:int platform_driver_register(struct platform_driver *drv)
      驱动以struct device_driver driver为基类,进行当前所属设备类别操作集的再次封装,所作动作分31>构建4.1的视图
          2>在该总线上探测设备并将device上的drv绑定为此drv				//注意和设备绑定驱动之间的区别和关联,设备有drv成员,而drv没有设备成员,因为驱动是依赖于设备才存在意义
            bus_add_driver(drv);
          3>设置groups
    3>匹配机制
      见下一节

设备与驱动的相互探测

1.总线初始化

platform_bus_init()
    bus_register(&platform_bus_type);
展开:
    struct bus_type platform_bus_type = {
        .name		= "platform",
        .dev_attrs	= platform_dev_attrs,
        .match		= platform_match,					// 匹配规则
        .uevent		= platform_uevent,					// 环境变量设置规则,用于支持用户态的热插拔
        .pm		= &platform_dev_pm_ops,				        // 电源管理规则
    };
        platform_match
            platform_match_id(dev, drv)					        // 类型匹配
            platform_match_id(pdrv->id_table, pdev)		                // id匹配
            strcmp(pdev->name, drv->name)				        // 名字匹配
 
总结:
    挂载一条总线并指定总线的匹配规则、uevent处理、电源管理机制

2.设备探测驱动详解

device_add(struct device *dev)
    bus_probe_device(dev);
        device_attach(dev);
            bus_for_each_drv(dev->bus, NULL, dev, __device_attach);
                while ((drv = next_driver(&i)) && !error) {			// 拿当前设备逐一探测该总线上挂载的所有设备驱动(遵顼之前设置的当前总线匹配原则)
                    error = fn(drv, data);
                }
                    __device_attach(drv, data)
                        driver_match_device					
                            drv->bus->match(dev, drv)			        // 按照5.1设定的总线匹配规则进行匹配,匹配上则进入对应设备驱动的实际探究
                        driver_probe_device(drv, dev)
                            really_probe(dev, drv)
                            展开:
                                dev->driver = drv;				// 再device对象上先绑定驱动,如失败再解绑定
                                driver_sysfs_add(dev)				// 在sysfs设备驱动模型对象上进行绑定(创建链接)
                                ret = drv->probe(dev);				// 进入驱动的probe函数进行实际探究

3.驱动探测设备

platform_driver_register(struct platform_driver *drv)
    driver_register(&drv->driver)
        bus_add_driver(drv);
            driver_attach(drv);
                bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);			// 拿当前驱动逐一探测该总线上挂载的所有设备(遵顼之前设置的当前总线匹配原则)
                    __driver_attach(dev, drv)
                        driver_match_device(drv, dev)				        // 与设备探测驱动一致
                        driver_probe_device(drv, dev)
                            ->really_probe(dev, drv)			                // 与设备探测驱动一致

4.探测原理图
在这里插入图片描述

结构体及接口说明

1.结构体
1.设备:include/linux/platfrom_device.h

struct platform_device {
    const char			* name;				  // 平台设备的名字
    int			id;				          // ID 是用来区分如果设备名字相同的时候(通过在后面添加一个数字来代表不同的设备,因为有时候有这种需求)
    struct device		dev;				  // 内置的device结构体-------参考设备驱动模型
    u32			num_resources;			          // 资源结构体数量
    struct resource		* resource;                        // 指向一个资源结构体数组
    const struct platform_device_id	*id_entry;                // 用来进行与设备驱动匹配用的id_table表
    /* MFD cell pointer */
    struct mfd_cell 		*mfd_cell;
    /* arch specific additions */
    struct pdev_archdata		archdata;                // 自留地添加自己的东西
};
struct resource {
    resource_size_t start;                                        // 资源的起始值,如果是地址,那么是物理地址,不是虚拟地址
    resource_size_t end;					  // 资源的结束值,如果是地址,那么是物理地址,不是虚拟地址
    const char *name;           				  // 资源名
    unsigned long flags;        				  // 资源的标示,用来识别不同的资源(如终端、内存、dma等资源) 
    struct resource *parent, *sibling, *child;                   // 资源指针,可以构成链表
 };

2.驱动:include\linux\platform_device.h

struct platform_driver {
    int (*probe)(struct platform_device *);                     // 查询设备,和device_driver中的是一样的功能,但是一般是使用device_driver中的那个
    int (*remove)(struct platform_device *);                    // 卸载,	平台设备驱动的时候会调用这个函数,但是device_driver下面也有,具体调用的是谁这个就得分析了
    void (*shutdown)(struct platform_device *);                 // 断电
    int (*suspend)(struct platform_device *, pm_message_t state);// 休眠
    int (*resume)(struct platform_device *);			// 恢复
    struct device_driver driver;                		// 内置的device_driver 结构体 -------参考设备驱动模型
    const struct platform_device_id *id_table;  		// 该设备驱动支持的设备的列表  他是通过这个指针去指向  platform_device_id 类型的数组
};

3.总线

struct bus_type {
    const char        *name;					                // 总线名字
    struct bus_attribute    *bus_attrs;                                         // 该总线的属性
    struct device_attribute    *dev_attrs;                                      // 该总线下设备的属性
    struct driver_attribute    *drv_attrs;                                      // 该总线下设备驱动的属性
 
    int (*match)(struct device *dev, struct device_driver *drv);     		// 该总线下设备与设备驱动的匹配函数
    int (*uevent)(struct device *dev, struct kobj_uevent_env *env); 		// 事件函数    热拨插
    int (*probe)(struct device *dev);                                		// 总线下的  探针函数
    int (*remove)(struct device *dev);
    void (*shutdown)(struct device *dev);
 
    int (*suspend)(struct device *dev, pm_message_t state);			// 休眠
    int (*resume)(struct device *dev);					        // 唤醒
 
    const struct dev_pm_ops *pm;  					        // 电源管理相关的
 
    struct bus_type_private *p;   					        // 总线的私有数据  p->subsys.kobj 表示该总线在驱动模型中对应的对象
};
 
struct bus_type_private {
    struct kset subsys;                				                // 这个是bus主要的kset
    struct kset *drivers_kset;         				                //  这个kset指针用来指向该总线的 drivers目录的
    struct kset *devices_kset;         				                // 这个kse指针用来指向该总线的devices目录的
    struct klist klist_devices;        				                // 用来挂接该总线下的设备的一个链表头
    struct klist klist_drivers;        				                // 用来挂接该总线下的设备驱动的一个链表头    
    struct blocking_notifier_head bus_notifier;
    unsigned int drivers_autoprobe:1;  				                // 是否需要在设备驱动注册时候子自动匹配设备
    struct bus_type *bus;              				                // 指向本bus结构体
};
 
static struct device_attribute platform_dev_attrs[] = {
    __ATTR_RO(modalias),
    __ATTR_NULL,
};

4.类

struct class {
	const char		*name;
	struct module		*owner;
 
	struct class_attribute		*class_attrs;
	struct device_attribute		*dev_attrs;
	struct bin_attribute		*dev_bin_attrs;
	struct kobject			*dev_kobj;
 
	int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);
	char *(*devnode)(struct device *dev, mode_t *mode);
 
	void (*class_release)(struct class *class);
	void (*dev_release)(struct device *dev);
 
	int (*suspend)(struct device *dev, pm_message_t state);
	int (*resume)(struct device *dev);
 
	const struct kobj_ns_type_operations *ns_type;
	const void *(*namespace)(struct device *dev);
 
	const struct dev_pm_ops *pm;
 
	struct subsys_private *p;
};

2.接口

int platform_driver_register(struct platform_driver *);       			      // 注册设备驱动
void platform_driver_unregister(struct platform_driver *);  			      // 卸载设备驱动
int platform_device_register(struct platform_device *);      			      // 注册设备
void platform_device_unregister(struct platform_device *); 			      // 卸载设备

总结

1.设备是硬件唯一的实体,驱动只是软件层面的操作依赖于设备,因此设备预留了指向驱动的指针。从2者的结构体成员可知,相互之间的关联仅仅是匹配规则所需要的关键信息即name、所属总线,一旦匹配上则将驱动绑定之设备对应的驱动指针
 
对比2者结构体:
    1>驱动:struct device_driver包含4类信息
        1.匹配规则信息:name、of_match_table及所属bus
        2.构建sys所需要的kobject、klist等
        3.指明驱动支持哪些设备:struct list_head devices(驱动和设备是一对多的关系)
        4.设备操作集
    2>设备:struct device包含5类信息
        1.匹配规则信息:name、所属bus
        2.构建sys所需要的kobject、class相关信息
        3.指明用哪个驱动:struct device_driver *driver(设备只能对应唯一的驱动)
        4.设备的硬件资源:platform_data、dma等
        5.设备的属性:设备号、设备访问互斥控制量
 
    3>总线只是规定了挂载其上的驱动和设备之间的统一的匹配、环境变量设置、电源管理等规则
    4>总线模型下支持设备和驱动的单独挂载(达到分离)、在各自注册中去匹配对方
    5>总线模型下抽离了创建sys视图的共性
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
将可移动设备连入系统时,系统的后台中会依次发生如下事件: l 内核检测到新硬件入,然后分别通知hotplug和udev。前者用来装入相应的内核模块(如usb-storage),而后者用来在/dev中创建相应的设备节点(如/dev/sda1)。 l udev创建了相应的设备节点之后,会将这一消息通知hal的守护程序(hald)。当然udev还得保证新创建的设备节点可以被普通用户访问。 l hotplug装入了相应的内核模块之后,会把这一消息通知给hald。 l hald在受到hotplug和udev发出的消息之后,认为新硬件已经正式被系统认可了。此时它会通过一系列精心编写的规则文件(就是传说中的xxx-policy.fdi),把发现新硬件的消息通过dbus发送出去,同时还会调用update-fstab或fstab-sync来更新/etc/fstab,为相应的设备节点创建适合的挂载点。 l 卷管理器会监听dbus中发现新硬件的消息。根据所入的硬件(区分U盘和数码相机等)不同,卷管理器会先将相应的设备节点挂载到hald创建的挂载点上,然后再打开不同的应用程序。 当然,如果是在CDROM中入光盘,过程可能比较简单。因为CDROM本身就是一个固定的硬件,无需hotplug和udev的协助: l hald会自己监视CDROM,并且将光盘托架开合的消息通过dbus发出去。 l 卷管理器负责检查CDROM中的盘片内容,进行挂载,并调用合适的应用程序。 要注意,hald的工作是从上游得到硬件就绪的消息,然后将这个消息转发到dbus中。尽管它会调用程序来更新fstab,但实际上它自己并不执行挂载的工作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

学无止境2022

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

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

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

打赏作者

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

抵扣说明:

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

余额充值