【linux kernel】Linux设备驱动模型 | bus
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
文章目录
一、导读
在linux设备驱动模型中总线是一个抽象的概念是一类特殊的设备。在设备模型的实现中内核规定了系统中的每个设备都需要连接到一个总线上这个总线可以是一个内部的Bus、虚拟的Bus或者Platform 总线。在内核中通过struct but_type
结构来描述总线定义在include/linux/device.h中。
本文首先描述与总线相关的数据结构重点描述struct bus_type
结构体内部各个元素的含义以及内部之间的联系。接着会描述linux设备驱动模型初始化过程中关于总线的初始化流程这部分由buses_init()
完成最后会描述对总线的几个操作接口函数。
👀本文所有源码分析基于linux内核版本4.1.15
。
文章目录
二、与总线相关的数据结构
2-1struct bus_type
总线是处理器和更多设备之间的通道对于linux的设备模型所有的设备都通过总线连接在一起。总线之间可以互相连接例如USB控制器通常是一个PCI设备设备模型表示总线和它们控制的设备之间的实际连接。总线由struct bus_type
结构表示该结构包含了总线名称、默认属性、总线的方法、PM操作和驱动核心的私有数据。sturct bus_type
定义如下
struct bus_type {
const char *name;
const char *dev_name;
struct device *dev_root;
struct device_attribute *dev_attrs;
const struct attribute_group **bus_groups;
const struct attribute_group **dev_groups;
const struct attribute_group **drv_groups;
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 (*online)(struct device *dev);
int (*offline)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
const struct dev_pm_ops *pm;
const struct iommu_ops *iommu_ops;
struct subsys_private *p;
struct lock_class_key lock_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-2struct 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)
struct subsys_private {
struct kset subsys;
struct kset *devices_kset;
struct list_head interfaces;
struct mutex mutex;
struct kset *drivers_kset;
struct klist klist_devices;
struct klist klist_drivers;
struct blocking_notifier_head bus_notifier;
unsigned int drivers_autoprobe:1;
struct bus_type *bus;
struct kset glue_dirs;
struct class *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()
完成总线相关的初始化操作
在buses_init()
的操作逻辑中完成了以下几件事情
1动态创建bus
内核kset并指定其事件操作函数然后添加到sysfs中。
2动态创建system
内核kset并指定其父级kset为devices_kset->kobj
然后添加到sysfs中。
四、总线的操作接口
本小节描述linux内核中对总线的操作API接口
extern int bus_add_device(struct device *dev);
extern void bus_probe_device(struct device *dev);
extern void bus_remove_device(struct device *dev);
extern int bus_add_driver(struct device_driver *drv);
extern void bus_remove_driver(struct device_driver *drv);
extern void driver_detach(struct device_driver *drv);
extern int driver_probe_device(struct device_driver *drv, struct device *dev);
extern int __must_check bus_register(struct bus_type *bus);
extern void bus_unregister(struct bus_type *bus);
extern int bus_register_notifier(struct bus_type *bus,
struct notifier_block *nb);
extern int bus_unregister_notifier(struct bus_type *bus,
struct notifier_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
- 7调用
kset_create_and_add()
向内核分别添加devices
和drivers
kset这样便可以在sysfs中查看了
- 8初始化
priv
指针中的mutex
、klist_devices
和klist_drivers
等变量。 - 9调用
add_probe_files
函数在bus下添加bus_attr_drivers_probe
和bus_attr_drivers_autoprobe
两个attribute
- 10调用
bus_add_groups
添加bus_groups
属性组。
4-2总线的注销
调用bus_unregister
执行具体总线的注销操作该函数同样实现在/drivers/base/bus.c中
void bus_unregister(struct bus_type *bus)
{
pr_debug("bus: '%s': unregistering\n", 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-3device和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-4driver的probe
当一个driver在进行probe的时候大部分逻辑都会依赖总线的具体实现核心操作是bus_probe_device()
和driver_attach()
两个接口这两个接口都是在drivers/base/base.h中声明在drivers/base/bus.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
接口。