0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Linux设备与驱动之间的联系描述

嵌入式小生 来源:嵌入式小生 2023-01-16 09:23 次阅读

一、导读

linux设备驱动模型中,总线是一个抽象的概念,是一类特殊的设备。在设备模型的实现中,内核规定了系统中的每个设备都需要连接到一个总线上,这个总线可以是一个内部的Bus、虚拟的Bus或者Platform 总线。在内核中通过struct but_type结构来描述总线,定义在include/linux/device.h中。

本文首先描述与总线相关的数据结构,重点描述struct bus_type结构体内部各个元素的含义以及内部之间的联系。接着会描述linux设备驱动模型初始化过程中关于总线的初始化流程,这部分由buses_init()完成,最后会描述对总线的几个操作接口函数。

本文所有源码分析基于linux内核版本:4.1.15。

一、导读

二、与总线相关的数据结构

(2-1)struct bus_type

(2-2)struct subsys_private

三、总线的初始化

四、总线的操作接口

(4-1)总线的注册

(4-2)总线的注销

(4-3)device和device_driver的添加

(4-4)driver的probe

二、与总线相关的数据结构

(2-1)struct bus_type

总线是处理器和更多设备之间的通道,对于linux的设备模型,所有的设备都通过总线连接在一起。总线之间可以互相连接,例如:USB控制器通常是一个PCI设备,设备模型表示总线和它们控制的设备之间的实际连接。总线由struct bus_type结构表示,该结构包含了总线名称、默认属性、总线的方法、PM操作和驱动核心的私有数据。sturct bus_type定义如下:

structbus_type{
constchar*name;
constchar*dev_name;
structdevice*dev_root;
structdevice_attribute*dev_attrs;/*usedev_groupsinstead*/
conststructattribute_group**bus_groups;
conststructattribute_group**dev_groups;
conststructattribute_group**drv_groups;

int(*match)(structdevice*dev,structdevice_driver*drv);
int(*uevent)(structdevice*dev,structkobj_uevent_env*env);
int(*probe)(structdevice*dev);
int(*remove)(structdevice*dev);
void(*shutdown)(structdevice*dev);

int(*online)(structdevice*dev);
int(*offline)(structdevice*dev);

int(*suspend)(structdevice*dev,pm_message_tstate);
int(*resume)(structdevice*dev);

conststructdev_pm_ops*pm;

conststructiommu_ops*iommu_ops;

structsubsys_private*p;
structlock_class_keylock_key;
};

name :总线的名称。

dev_name : 用于子系统枚举设备等,例如("foo%u", dev->id)。

dev_root : 表示要用于父设备的默认设备。

dev_attrs: 设备属性组。

bus_groups: bus属性组。

dev_groups: dev属性组。

drv_groups: drv属性组。

match: 是一个需要由具体bus驱动实现的回调函数,当属于该bus的所有device和驱动添加到内核时,内核都会调用该接口函数。

uevent:也是一个由具体的bus驱动实现的回到函数,当属于该bus的设备,触发添加、移除或者其他动作时,bus模块核心就会调用该接口,这样可以让bus的驱动能够修改环境变量。

probe、 remove:这两个也是回调函数,当有新的设备或者驱动添加到这个bus时,内核则会首先调用这个bus的probe,然后再调用具体驱动程序的probe去初始化匹配设备;当有设备从这个bus上移除的时候则会调用remove,所以这个两个回调函数非常重要。

shutdown:在需要shutdown的时候调用该回调函数,以让设备停止工作。该函数与电源管理相关。

online:当设备再脱机后重新联机时调用该函数。该函数与电源管理相关。

offline:当让设备脱机以便进行热插拔时调用该函数。

suspend:当总线上的设备想要进入睡眠模式时调用。

resume:让这个bus上的一个设备退出睡眠模式时调用该函数。

pm:是与之对应的bus的电源管理操作,会去回调执行特定驱动程序的pm的ops。

iommu_ops:该总线的IOMMU特定操作,用于将IOMMU驱动程序实现附加到总线上,并允许驱动程序进行总线上特殊的设定操作。

p: 一个struct subsys_private类型的指针,是驱动核心的私有数据,只有驱动核心可以使用,

lock_key: 该参数供锁验证器使用。

(2-2)struct subsys_private

在bus_type和class结构中都有一个指向struct subsys_private的指针,用于保存bus_type和class结构的驱动程序核心部分的私有数据。从命名上似乎不容易理解struct subsys_private,由于bus_type和clsss结构中都有一个struct subsys_private指针,所以可以将subsys_private理解成bus_type和class的上层,包含了bus和class。

struct subsys_private结构定义如下(/drivers/base/base.h):

structsubsys_private{
structksetsubsys;
structkset*devices_kset;
structlist_headinterfaces;
structmutexmutex;

structkset*drivers_kset;
structklistklist_devices;
structklistklist_drivers;
structblocking_notifier_headbus_notifier;
unsignedintdrivers_autoprobe:1;
structbus_type*bus;

structksetglue_dirs;
structclass*class;
};

subsys:用于描述本subsystem的kset,用于代表其自身。

devices_kset: 表示subsystem的device目录。

interfaces: interfaces是一个list_head类型数据,用于保存与之相关的interface。在内核中interface用于抽象bus下所有关联设备的一些特殊的功能。

mutex: mutex类型锁,用于保护设备和interface链表。

drivers_kset: 表示subsystem中驱动相关链表。

klist_devices: 设备链表,用于保存本bus下所有的device的指针,以方便查找。

klist_drivers: 驱动链表,用于保存本bus下所有的device_driver的指针,以方便查找。

bus_notifier: bus_notifier是一个总线通知列表,用于监测bus上发生的任何事情。

drivers_autoprobe:用于控制该bus下的drivers或者device是否具有自动probe属性。

bus:是一个指向与之关联的struct bus_type类型的指针。用于保存上层的bus。

glue_dirs:表示glue目录,用于放在父设备之间,以避免名称空间出现冲突。

class:是一个指向与之关联的struct class类型的指针。用于保存上层的class。

三、总线的初始化

总线属于linux驱动模型的一部分,所以在内核启动过程中,在driver_init()函数中会调用buses_init(),完成总线相关的初始化操作:

0b3084c2-9535-11ed-bfe3-dac502259ad0.png在buses_init()的操作逻辑中,完成了以下几件事情:

(1)动态创建bus内核kset,并指定其事件操作函数,然后添加到sysfs中。

(2)动态创建system内核kset,并指定其父级kset为devices_kset->kobj,然后添加到sysfs中。

四、总线的操作接口

本小节描述linux内核中对总线的操作API接口:

//添加设备到总线
externintbus_add_device(structdevice*dev);
//为新的设备探测驱动
externvoidbus_probe_device(structdevice*dev);
//从总线中将设备移除
externvoidbus_remove_device(structdevice*dev);

//添加一个驱动到总线
externintbus_add_driver(structdevice_driver*drv);
//从总线将移除驱动
externvoidbus_remove_driver(structdevice_driver*drv);

//将驱动程序从该驱动控制的所有设备中分离
externvoiddriver_detach(structdevice_driver*drv);
//尝试将设备和驱动程序绑定在一起
externintdriver_probe_device(structdevice_driver*drv,structdevice*dev);
//注册一个驱动核心总线子系统
externint__must_checkbus_register(structbus_type*bus);

//注销驱动核心总线子系统
externvoidbus_unregister(structbus_type*bus);

/*注册总线通知器。总线通知器,用于获取当设备添加/移除或者驱动程序与
设备绑定/解绑定时的通知。*/
externintbus_register_notifier(structbus_type*bus,
structnotifier_block*nb);

//注销总线通知器
externintbus_unregister_notifier(structbus_type*bus,
structnotifier_block*nb);

(4-1)总线的注册

调用bus_register执行具体总线的注册操作,该函数实现在/drivers/base/bus.c中,具体执行逻辑如下:

(1)调用kzalloc()为struct subsys_private创建内存,设置priv->bus的值为想要注册的总线类型,然后将bus->p赋值为priv。

(2)初始化总线通知器。

(3)为priv->subsys.kobj重新设置名称,即总线的名称。

(4)初始化priv->subsys.kobj的kset和ktype字段。

(5)调用kset_register将private->subsys.kobj注册到内核中。

(6)调用bus_create_file向sysfs文件系统中的bus目录下添加一个uevnet attribute:0b5a5afe-9535-11ed-bfe3-dac502259ad0.png

(7)调用kset_create_and_add()向内核分别添加devices和drivers kset,这样便可以在sysfs中查看了:0b783f88-9535-11ed-bfe3-dac502259ad0.png

(8)初始化priv指针中的mutex、klist_devices和klist_drivers等变量。

(9)调用add_probe_files函数,在bus下添加bus_attr_drivers_probe和bus_attr_drivers_autoprobe两个attribute:0b9f2b3e-9535-11ed-bfe3-dac502259ad0.png

(10)调用bus_add_groups添加bus_groups属性组。

(4-2)总线的注销

调用bus_unregister执行具体总线的注销操作,该函数同样实现在/drivers/base/bus.c中:

voidbus_unregister(structbus_type*bus)
{
pr_debug("bus:'%s':unregistering
",bus->name);
if(bus->dev_root)
device_unregister(bus->dev_root);
bus_remove_groups(bus,bus->bus_groups);
remove_probe_files(bus);
kset_unregister(bus->p->drivers_kset);
kset_unregister(bus->p->devices_kset);
bus_remove_file(bus,&bus_attr_uevent);
kset_unregister(&bus->p->subsys);
}

(4-3)device和device_driver的添加

linux内核的驱动模型中,提供了device_register()和driver_register()两个接口,供各个驱动模块使用。从linux内核多个子系统的源码中可以发现,对于各种驱动程序的注册最终都会调用到driver_register()。然而这两个接口函数的核心逻辑中,是通过调用总线的bus_add_device()和bus_add_driver()实现的:在driver_register()中调用driver_find()在给定的总线中查找给定名称的驱动,如果驱动已经存在则返回-EBUSY;如果驱动在总线中不存在,则调用bus_add_driver()注册驱动。在device_register()中则首先调用device_initialize初始化设备(本质上是对sturct device结构赋值),然后调用device_add向linux内核驱动模型注册设备。

device_register()和driver_register()两个接口都在/drivers/base/bus.c文件中实现。下文来具体看看这两个接口的执行逻辑:

1、bus_add_device的执行逻辑:

(1)从dev->bus中取得bus_type*类型的指针bus,如果获取bus不成功,则函数直接返回;如果bus获取成功,则会继续后续的第(2)步操作。

(2)调用device_add_attrs接口,将由bus->dev_attrs指针定义的默认attribute添加到内核中,这个操作会体现在sysfs文件系统中的/sys/devices/xxx/xxx_device/目录中。

(3)调用device_add_groups将bus_dev_groups添加到内核中。

(4)调用sysfs_create_link将该设备在sysfs中的目录,链接到该bus的devices目录下

(5)接着依然调用sysfs_create_link,在该设备的sysfs目录中,创建一个指向该设备所在bus目录的链接,命名为subsystem。

(6)前面几个操作实则是向sysfs文件系统注册关于设备的信息,向用户空间抛出接口。最后步骤则是调用klist_add_tail()将该设备指针保存到bus->p->klist_devices中。

2、bus_add_driver的执行逻辑:

(1)首先调用bus_get从驱动程序中获取到该驱动程序所属的bus_type指针。如果该指针为零(即获取失败)则返回-EINVAL;反之则继续执行后续操作。

(2)为该驱动的struct driver_private指针(priv)分配空间,并初始化其中的priv->klist_devices、priv->driver、priv->kobj.kset等变量,同时将priv保存到device_driver的p中。

(3)调用kobject_init_and_add,并传入该驱动的名称作为参数,向sysfs中注册driver的kobject。该操作体现在sysfs文件中的/sys/bus/xxx/drivers/目录下。

(4)调用klist_add_tail将驱动添加到总线的klist_drivers链表中,如果该驱动的drivers_autoprobe为真,还将调用driver_attach尝试将驱动绑定到设备。

(5)调用module_add_driver()将驱动添加到drv->owner中,咱不过多分析。

(6)调用driver_create_file,在sysfs文件系统中的driver目录下,创建uevent attribute。

(7)调用driver_add_groups将bus->drv_groups属性组添加到驱动中。

(4-4)driver的probe

当一个driver在进行probe的时候,大部分逻辑都会依赖总线的具体实现,核心操作是bus_probe_device()和driver_attach()两个接口,这两个接口都是在drivers/base/base.h中声明,在drivers/base/bus.c中实现。

bus_probe_device()实现如下:

0bb5ac4c-9535-11ed-bfe3-dac502259ad0.png

driver_attach()实现如下:

0be8011a-9535-11ed-bfe3-dac502259ad0.png

从上图可知,driver_attach中会调用bus_for_each_dev遍历设备,该函数用于遍历drv->bus的设备列表,并为每个设备调用__driver_attach(),并将drv传递给__driver_attach,故而可以明确知道对每个设备执行的操作则是__driver_attach(),该函数定义在/drivers/base/dd.c文件中,具体的执行逻辑如下:

(1)使用driver_match_device()判断驱动和设备是否匹配(本质上是判断是否指定了drv->bus->match,如果指定了则执行与之对应的函数,否则返回1),如果驱动和设备已经匹配了则直接返回;否则,则继续执行后续的操作。

(2)调用driver_probe_device()尝试将驱动和设备绑定在一起。

总体上,bus_probe_device()和driver_attach()这两个接口的操作流程类似,即:搜索所在的总线,比对是否有同名的device_driver(或device),如果有并且该设备没有绑定Driver(注:这一点很重要,则可以使同一个Driver驱动相同名称的多个设备)则调用device_driver的probe接口。






审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 处理器
    +关注

    关注

    68

    文章

    19281

    浏览量

    229776
  • USB控制器
    +关注

    关注

    1

    文章

    35

    浏览量

    11864
  • PCI
    PCI
    +关注

    关注

    4

    文章

    666

    浏览量

    130265
  • platform
    +关注

    关注

    0

    文章

    19

    浏览量

    17406
  • Linux驱动
    +关注

    关注

    0

    文章

    43

    浏览量

    9965

原文标题:Linux设备与驱动的“鹊桥” | Bus

文章出处:【微信号:嵌入式小生,微信公众号:嵌入式小生】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    浅谈Linux发行版之间联系和区别

    前言:现如今Linux的发行版本多之又多,其大家族可谓是“枝繁叶茂,子孙满堂”。那么它们各版本之间有着怎样的联系和区别呢?Linux发行版之间
    发表于 07-08 08:25

    嵌入式Linux设备驱动开发

    嵌入式Linux设备驱动开发 Linux 设备驱动的基本概念
    发表于 09-10 13:10 82次下载
    嵌入式<b class='flag-5'>Linux</b><b class='flag-5'>设备</b><b class='flag-5'>驱动</b>开发

    嵌入式Linux字符设备驱动的设计与应用

    描述了基于嵌入式Linux的字符设备驱动程序的设计方法和实现过程。以电机、数码管、串口和mini键盘的驱动设计为例,详细阐述了嵌入式
    发表于 02-23 15:45 24次下载

    嵌入式Linux字符设备驱动的设计与应用

    描述了基于嵌入式Linux的字符设备驱动程序的设计方法和实现过程。以电机、数码管、串口和mini键盘的驱动设计为例,详细阐述了嵌入式
    发表于 07-14 17:31 31次下载

    Linux设备驱动开发详解》第23章、Linux设备驱动的移植

    Linux设备驱动开发详解》第23章、Linux设备驱动的移植
    发表于 10-27 10:58 9次下载
    《<b class='flag-5'>Linux</b><b class='flag-5'>设备</b><b class='flag-5'>驱动</b>开发详解》第23章、<b class='flag-5'>Linux</b><b class='flag-5'>设备</b><b class='flag-5'>驱动</b>的移植

    Linux设备驱动开发详解》第17章、Linux音频设备驱动

    Linux设备驱动开发详解》第17章、Linux音频设备驱动
    发表于 10-27 11:14 17次下载
    《<b class='flag-5'>Linux</b><b class='flag-5'>设备</b><b class='flag-5'>驱动</b>开发详解》第17章、<b class='flag-5'>Linux</b>音频<b class='flag-5'>设备</b><b class='flag-5'>驱动</b>

    Linux设备驱动开发详解》第16章、Linux网络设备驱动

    Linux设备驱动开发详解》第16章、Linux网络设备驱动
    发表于 10-27 11:17 5次下载
    《<b class='flag-5'>Linux</b><b class='flag-5'>设备</b><b class='flag-5'>驱动</b>开发详解》第16章、<b class='flag-5'>Linux</b>网络<b class='flag-5'>设备</b><b class='flag-5'>驱动</b>

    Linux设备驱动开发详解》第14章、Linux终端设备驱动

    Linux设备驱动开发详解》第14章、Linux终端设备驱动
    发表于 10-27 11:22 8次下载
    《<b class='flag-5'>Linux</b><b class='flag-5'>设备</b><b class='flag-5'>驱动</b>开发详解》第14章、<b class='flag-5'>Linux</b>终端<b class='flag-5'>设备</b><b class='flag-5'>驱动</b>

    Linux设备驱动开发详解》第13章、Linux设备驱动

    Linux设备驱动开发详解》第13章、Linux设备驱动
    发表于 10-27 11:24 18次下载
    《<b class='flag-5'>Linux</b><b class='flag-5'>设备</b><b class='flag-5'>驱动</b>开发详解》第13章、<b class='flag-5'>Linux</b>块<b class='flag-5'>设备</b><b class='flag-5'>驱动</b>

    Linux设备驱动开发详解》第9章、Linux设备驱动中的异步通知与异步IO

    Linux设备驱动开发详解》第9章、Linux设备驱动中的异步通知与异步IO
    发表于 10-27 11:33 0次下载
    《<b class='flag-5'>Linux</b><b class='flag-5'>设备</b><b class='flag-5'>驱动</b>开发详解》第9章、<b class='flag-5'>Linux</b><b class='flag-5'>设备</b><b class='flag-5'>驱动</b>中的异步通知与异步IO

    Linux设备驱动开发详解》第8章、Linux设备驱动中的阻塞与非阻塞IO

    Linux设备驱动开发详解》第8章、Linux设备驱动中的阻塞与非阻塞IO
    发表于 10-27 11:35 9次下载
    《<b class='flag-5'>Linux</b><b class='flag-5'>设备</b><b class='flag-5'>驱动</b>开发详解》第8章、<b class='flag-5'>Linux</b><b class='flag-5'>设备</b><b class='flag-5'>驱动</b>中的阻塞与非阻塞IO

    Linux设备驱动开发详解》第7章、Linux设备驱动中的并发控制

    Linux设备驱动开发详解》第7章、Linux设备驱动中的并发控制
    发表于 10-27 11:37 10次下载
    《<b class='flag-5'>Linux</b><b class='flag-5'>设备</b><b class='flag-5'>驱动</b>开发详解》第7章、<b class='flag-5'>Linux</b><b class='flag-5'>设备</b><b class='flag-5'>驱动</b>中的并发控制

    嵌入式Linux设备驱动原理原来是这样编写的!

    系统调用是操作系统内核和应用程序之间的接口,设备驱动程序是操作系统内核和机器硬件之间的接口。Linux
    发表于 04-28 15:15 1967次阅读

    Linux设备驱动程序分类有哪些

    Linux设备驱动程序是操作系统与硬件设备之间的桥梁,负责实现硬件设备与操作系统
    的头像 发表于 08-30 15:11 556次阅读

    linux系统的设备驱动一般分几类

    Linux系统的设备驱动是操作系统与硬件设备之间的桥梁,负责实现操作系统与硬件设备
    的头像 发表于 08-30 15:13 432次阅读