设备驱动模型概述

早期linux内核为设备驱动开发者提供了很少的基本功能接口:申请动态内存,保留I/O地址范围等。早期的硬件设备编程困难,即使属于同一总线上的两个不同硬件设备之间也很少有共性。因此,需要为设备驱动开发者设计一个通用的模型。现如今,情况不一样了。总线类型规范了硬件设备的内部设计,导致如今的即使是不同类型的设备都支持相似的功能,针对这样的设备的设备驱动应该关心以下实现:电源管理,即插即用和热插拔。系统中每一个硬件设备的电源都由内核来管理。比如一个电池供电的电脑进入“standby”模式,内核必须强制每一个硬件设备为低电状态。因此,每一个可以设定为”standby“状态的设备的驱动必须包含一个可以将硬件设备设定为低电状态的回调函数。同时硬件设备也要按照精确的顺序设定为”standby“状态,否则有些硬件设备可能进入错误的电源状态,例如,linux内核必须先将硬盘设定为”standby“状态,然后才是硬盘控制器,如果该顺序反了,硬盘控制器将不能给硬盘发送命令。为了实现该类的操作,Linux2.6提供了一些数据结构和函数接口,这些数据结构和接口为系统中所有的总线,设备和设备驱动提供了统一的操作方法。这个框架就叫做设备驱动模型。

核心数据结构Kobject

kobject是设备驱动模型的核心数据结构。它被固有的绑定在sysfs文件系统中:每一个kobject相当于sysfs中的一个目录。kobject通常被嵌入到更大的对象中用来描述设备驱动模型的组件,因此这样的对象称作”容器“,总线,设备和设备驱动都是典型的容器。在容器中嵌入一个kobject使得内核能够:

  • 为容器维护一个引用计数
  • 为容器维护一个层级结构列表或集合
  • 为容器的属性提供一个用户模式下的视图

Kobject在内核中的定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct kobject {
const char *name;
struct list_head entry;
struct kobject *parent;
struct kset *kset;
struct kobj_type *ktype;
struct sysfs_dirent *sd;
struct kref kref;
unsigned int state_initialized:1; /*kobject初始化状态,初始化后值为1*/
unsigned int state_in_sysfs:1;/*在sysfs下创建目录的标记,创建后值为1*/
unsigned int state_add_uevent_sent:1;/*记录kobject_uevent添加事件*/
unsigned int state_remove_uevent_sent:1;/*记录kobject_uevent移除事件,和state_add_uevent_sent用于在释放kobject的时候确定是否已经添加并且未移除,未移除则先移除*/
unsigned int uevent_suppress:1;/*事件策略标记,不为0则不发送事件*/
};
  • name属性定义了该kobject的名字。
  • entry是用来将kobject链入kset中的链表元素。
  • parent是指向其父节点的指针,用来支持层级结构。一般指向其kset容器中的kobj对象。
  • ktype是该对象的类型,即包含该kobject的容器的类型。ktype在内核中的定义如下:
1
2
3
4
5
6
7
struct kobj_type {
void (*release)(struct kobject *kobj);
const struct sysfs_ops *sysfs_ops;
struct attribute **default_attrs;
const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
const void *(*namespace)(struct kobject *kobj);
};

其中,release回调函数在释放kobject本身的时候被调用。sysfs_ops是sysfs的操作函数表,default_attrs是该sysfs的默认属性。

  • sd属性表示该kobject对象的sysfs层级别构的构建块。

  • kref是该对象的引用计数,也就是包含该kobject的容器的引用计数。通过kobject_get和kobject_put函数增加和减少该计数器的值,当kref计数器的值为0时,调用kobj_type结构中的release回调函数,该函数通常实现为用来释放该kobject所属的容器(仅当容器本身为动态创建时)。

  • kset是该kobject的组,即kset是同一类型kobject的集合(也可以不属于同一类型)。同时,kset本身又包含自己的kobject对象。kset具有以下功能:

    1. kset是一组kobject对象的容器,内核可以通过kset来跟踪设备和设备驱动
    2. kset也是sysfs中的一个子目录,该目录显示了所有关联到的kobject。每个kset都包含一个kobject对象,该对象可以被设置为其它关联到的kobject的父节点。sysfs层级结构的顶层目录就是这样设计的。
    3. kset支持kobject的热插拔,并且影响uevent事件向用户空间报告的方式。

    kset结构在内核中的定义如下:

    1
    2
    3
    4
    5
    6
    struct kset {
    struct list_head list;
    spinlock_t list_lock;
    struct kobject kobj;
    const struct kset_uevent_ops *uevent_ops;
    };

    list是该集合中kobject的链表头。list_lock是kset中kobject链表头操作的保护自旋锁。kobj是kset对象本身的kobject元素。uevent_ops是该kset的uevent操作函数表,kset_uevent_ops在内核中的定义如下:

    1
    2
    3
    4
    5
    6
    struct kset_uevent_ops {
    int (* const filter)(struct kset *kset, struct kobject *kobj);
    const char *(* const name)(struct kset *kset, struct kobject *kobj);
    int (* const uevent)(struct kset *kset, struct kobject *kobj,
    struct kobj_uevent_env *env);
    };

    其中,filter函数用来决定某个uevent是否被发送到特定kobject的用户空间,该函数返回0表示uevent不会被发送。name函数用来覆盖uevent发送到用户空间的kset的默认名称。uevent函数在uevent要发送到用户空间以运行更多的环境变量添加到uevent时调用。

总结:kobject通常作为一个对象直接嵌入到其它类型的数据结构中,嵌入kobject的对象称作容器,ktype表示kobject的类型,也表示容器的类型,ktype提供了该类kobject的release方法和sysfs操作方法。kset是kobject的容器,kobject通过list_head链表元素链接在kset的list头节点上,一般来说一个kset中的kobject是同一类型的,即共享同一个ktype。注册到设备驱动模型中的所有kobject和kset对象的层级关系都在sysfs中呈现。

kobject和kset的初始化和添加接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
/*kobject初始化函数*/
void kobject_init(struct kobject *kobj, struct kobj_type *ktype);
/*kobject注册函数*/
int kobject_add(struct kobject *kobj, struct kobject *parent, const char *fmt, ...);
/*kobject初始化并注册函数*/
int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype, struct kobject *parent, const char *fmt, ...);
/*注册或移除kobject后发送事件函数,action为KOBJ_ADD或者KOBJ_REMOVE*/
int kobject_uevent(struct kobject *kobj, enum kobject_action action);
/*kset创建和注册函数,kset只动态创建*/
struct kset *kset_create_and_add(const char *name, struct kset_uevent_ops *u,
struct kobject *parent);
/*kset销毁函数*/
void kset_unregister(struct kset *kset);

kobject和kset使用实例,参考linux内核源码:samples/kobject/kset-example.c和samples/kobject/kobject-example.c

sysfs文件系统

sysfs是一个给用户层提供设备驱动模型中所有对象的层级关系视图的文件系统,它被挂载的路径是/sys/目录,存在于/sys/目录下的每一个子目录都是一个kset对象(这里说的对象是kobject的容器)或者kobject关联到sysfs的目录。/sys/目录下存在以下目录(kset对象/kobject):

1
2
block     class     devices   fs        module
bus dev firmware kernel power

这些目录有的是通过kset_create_and_add接口注册到sysfs上的kset对象,比如class,devices,module,bus,以bus目录为例,在内核中该目录的注册代码为:

1
2
3
bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL);
if (!bus_kset)
return -ENOMEM;

有的是通过kobject_create_and_add接口注册到sysfs上的kobject对象,比如fs,目录,其注册代码为:

1
2
3
fs_kobj = kobject_create_and_add("fs", NULL);
if (!fs_kobj)
printk(KERN_WARNING "%s: kobj create error\n", __func__);

目录说明:

block(kobject):存放的是所有的块设备对象(kobject)的链接文件,这些块设备源文件在devices目录中。

class(kset):存放的是所有注册到设备驱动模型中的class对象,由于class是device容器,所以在每一个/sys/class目录的子目录中,存放的是该class包含的所以device对象的链接文件。

devices(kset):存放的是所有设备驱动模型中的device对象,device对象是层级结构的,每一个device对象关联的/sys/devices/中的目录中,每一个文件是该device对象的属性文件,每一个目录是该device子设备对象,即每一个device对象是所有其关联的sysfs目录的所有子目录对应的device对象的父设备对象。

fs(kobject):是文件系统基础对象,其子目录是具体文件系统的kset对象。每个文件系统的目录中存放的是其特有对象目录。

module(kset):存放的是所有有参数的module对象,这些对象一般包含一个parameters对象和一些属性文件。parameters目录中的每一个文件都表示模块的一个参数。

bus(kset):存放的是所有的总线的kset对象,每一个总线kset对象又包含一些属性文件和两个kset对象,分别是devices和drivers,分别表示注册打扫该总线上的设备和驱动列表。由于所有的设备都已经在/sys/devices目录中了,所以devices对象中的子设备对象都是相应的/sys/devices中的对象的链接。

dev(kobject):该目录为一个kobject,里面包含两个kobject对象,一个名为char,一个名为block。char目录中存放的是以【主设备号:此设备号】命名的字符设备的对象的链接目录文件,和char对象类似,block对象目录存放以主次设备号命名的块设备的对象的链接文件,它们都指向了/sys/devices中的设备对象。

firmware(kobject):存放固件相关的对象,比如设备树对象和设备树属性文件等。

kernel(kobject):存放内核中可调整参数属性文件和kobject对象或kset对象。

power(kobject):power对象存放的是电源管理子系统相关属性文件。

设备驱动模型组件

设备驱动模型由一些表示总线,设备和设备驱动等的基础数据结构组成。

Devices

设备驱动模型中的每一个设备用一个device对象表示,设备通常用来描述硬件信息。device对象在内核中的定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
struct device {
struct device *parent;

struct device_private *p;

struct kobject kobj;
const char *init_name; /* initial name of the device */
const struct device_type *type;

struct mutex mutex; /* mutex to synchronize calls to
* its driver.
*/

struct bus_type *bus; /* type of bus device is on */
struct device_driver *driver; /* which driver has allocated this
device */
void *platform_data; /* Platform specific data, device
core doesn't touch it */
struct dev_pm_info power;
struct dev_pm_domain *pm_domain;

#ifdef CONFIG_NUMA
int numa_node; /* NUMA node this device is close to */
#endif
u64 *dma_mask; /* dma mask (if dma'able device) */
u64 coherent_dma_mask;/* Like dma_mask, but for
alloc_coherent mappings as
not all hardware supports
64 bit addresses for consistent
allocations such descriptors. */

struct device_dma_parameters *dma_parms;

struct list_head dma_pools; /* dma pools (if dma'ble) */

struct dma_coherent_mem *dma_mem; /* internal for coherent mem
override */
#ifdef CONFIG_CMA
struct cma *cma_area; /* contiguous memory area for dma
allocations */
#endif
/* arch specific additions */
struct dev_archdata archdata;

struct device_node *of_node; /* associated device tree node */

dev_t devt; /* dev_t, creates the sysfs "dev" */
u32 id; /* device instance */

spinlock_t devres_lock;
struct list_head devres_head;

struct klist_node knode_class;
struct class *class;
const struct attribute_group **groups; /* optional groups */

void (*release)(struct device *dev);
};

device关联的sysfs的目录是/sys/devices。device也是层级结构的,device的parent成员指向其父亲节点,同时,该device又可能是其它节点的父节点。kobj成员是该对象的kobject对象,主要用来维护其计数器,通过get_device和put_device接口增加和减少设备计数。bus成员是该device所属总线的指针。driver成员指针指向该device关联的驱动结构。class指针指向该device所属的class。devres_head成员是其资源链表头,用来管理自己的资源。release函数在释放该device时调用。type指针指向一个device_type结构类型,用来描述该device的类型。该结构主要实现了一组操作函数,即通过操作分类。of_node成员是该设备的设备树节点。p成员指向一个device_private结构,用来描述该设备的驱动数据的核心部分信息。该结构在内核中的定义如下:

1
2
3
4
5
6
7
8
9
struct device_private {
struct klist klist_children;
struct klist_node knode_parent;
struct klist_node knode_driver;
struct klist_node knode_bus;
struct list_head deferred_probe;
void *driver_data;
struct device *device;
};

其中klist_children是该device的说有子节点的头节点。knode_parent是同级别列表中的节点。knode_driver是驱动列表中的节点。knode_bus表示其在bus的设备链表中的节点。deferred_probe是链入deferred_probe_list的链表元素,deferred_probe_list主要用来做因为某些资源不可得或依赖其它驱动先probe等原因需要延迟的probe操作。drvier_data是驱动特殊数据。device指向该device_private自身所属的device。

device注册接口为device_register,取消注册接口为device_unreigster。

device的注册分两个步骤,首先是调用device_initialize初始化device的kobj成员以及其它一些固有成员,接着调用device_add接口添加device。device_add函数主要依次完成了以下 操作:

申请和初始化其device_private结构成员:

1
2
3
4
5
if (!dev->p) {
error = device_private_init(dev);
if (error)
goto done;
}

调用kobject_add添加kobject:

1
2
3
error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);
if (error)
goto Error;

kobject_add同时会在sys目录下或者sys目录的子目录下(取决于parent)创建其目录。

调用device_create_file在sys目录中对应的目录下创建uevent文件。

1
2
3
error = device_create_file(dev, &uevent_attr);
if (error)
goto attrError;

如果该设备有设备号,则在其关联的目录下创建dev文件,用来获取该设备的主次设备号,并且在/sys/class目录下(如果设备有class对象)或者/sys/dev/目录下为其创建链接,并在devtmpfs中创建设备节点。

1
2
3
4
5
6
7
8
9
10
11
if (MAJOR(dev->devt)) {
error = device_create_file(dev, &devt_attr);
if (error)
goto ueventattrError;

error = device_create_sys_dev_entry(dev);
if (error)
goto devtattrError;

devtmpfs_create_node(dev);
}

由上面的代码片段可知,在系统中不但可以使用mdev通过扫描/sys/class来在/dev/下创建设备节点,也可以通过挂载devtmpfs到/dev/目录下实现设备节点可见。在系统中可以通过下面的命令挂载devtmpfs:

1
mount -t devtmpfs none /dev

接着通过device_add_class_symlinks接口在/sys/class/xxx下创建符号链接文件。(只有关联到某个已经创建的class的设备才会在class关联到的/sys/class/目录下创建链接文件,在该接口中如果判断dev->class为空立即返回)

1
2
3
error = device_add_class_symlinks(dev);
if (error)
goto SymlinkError;

接着调用device_add_attrs在/sys/devices下相关的目录中创建其属性文件。

1
2
3
error = device_add_attrs(dev);
if (error)
goto AttrsError;

接着调用bus_add_device将该dev添加到dev所属bus的设备链表上。同时,为该dev在/sys/bus/目录中其相关bus中创建符号链接文件,并且在该dev对应的sys/devices/目录中创建名为subsystem的符号链接目录,subsystem是来自该设备所属总线的subsys中的kset成员,该过程见Buses章节中向总线添加设备一节的描述:

1
2
3
error = bus_add_device(dev);
if (error)
goto BusError;

接着调用dpm_sysfs_add接口在sys/devices下该设备的目录中添加power目录以及在power目录中添加一些属性文件。

1
2
3
error = dpm_sysfs_add(dev);
if (error)
goto DPMError;

接着向总线通知链发送总线添加设备通知,在向用户空间发送kobj添加事件。

1
2
3
4
5
6
7
8
/* Notify clients of device addition.  This call must come
* after dpm_sysfs_add() and before kobject_uevent().
*/
if (dev->bus)
blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
BUS_NOTIFY_ADD_DEVICE, dev);

kobject_uevent(&dev->kobj, KOBJ_ADD);

接着调用bus_probe_device,在该函数中判断总线是否设置了drivers_autoprobe,如果设置了,则调用device_attach函数来和对应的驱动进行绑定,device_attach实现过程见Buses章节的设备和驱动的绑定一节。绑定驱动之后再调用所有注册到总线的subsys_interface的add_dev回调函数。

1
bus_probe_device(dev);

然后如果该设备有父设备,在将该设备链接到父设备的子设备链表中。

1
2
3
if (parent)
klist_add_tail(&dev->p->knode_parent,
&parent->p->klist_children);

最后判断如果该设备属于某个class,则将该设备添加到其所属class的设备链表上,并将该添加事件通知到class上的所有interface 上。

1
2
3
4
5
6
7
8
9
10
11
12
13
if (dev->class) {
mutex_lock(&dev->class->p->mutex);
/* tie the class to the device */
klist_add_tail(&dev->knode_class,
&dev->class->p->klist_devices);

/* notify any interfaces that the device is here */
list_for_each_entry(class_intf,
&dev->class->p->interfaces, node)
if (class_intf->add_dev)
class_intf->add_dev(dev, class_intf);
mutex_unlock(&dev->class->p->mutex);
}

至此,device向内核设备驱动模型的添加结束。主要建立了设备和sysfs的关联,和驱动的绑定,和总线的关联以及和devtmpfs的关联等。device的取消注册接口device_unreigster的过程和注册接口的相反,依次取消关联和删除设备。

Drivers

在设备驱动模型中,驱动通常提供设备的操作方法,即设备能力的代码实现。驱动在内核中用device_driver结构体来描述:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct device_driver {
const char *name;
struct bus_type *bus;

struct module *owner;
const char *mod_name; /* used for built-in modules */

bool suppress_bind_attrs; /* disables bind/unbind via sysfs */

const struct of_device_id *of_match_table;

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 attribute_group **groups;

const struct dev_pm_ops *pm;

struct driver_private *p;
};

其中,bus成员指向该驱动所属的总线,of_match_table成员主要用来和设备中的of_node节点做匹配。其中的回调函数主要用来实现热插拔,即插即用,以及电源管理相关的功能。probe函数在总线通过设备匹配到驱动或通过驱动匹配到设备的时候调用。remove函数在关联的设备移除的时候或者驱动本身退出的时候调用以取消关联。shutdown函数在关机的时候调用以停止关联的设备。suspend用来使设备进入睡眠模式,低电模式。resume函数用来将设备从睡眠模式唤醒。pm指针成员指向匹配到的设备的电源管理操作集。p指针指向该驱动核心的私有数据,该成员的类型定义为:

1
2
3
4
5
6
7
struct driver_private {
struct kobject kobj;
struct klist klist_devices;
struct klist_node knode_bus;
struct module_kobject *mkobj;
struct device_driver *driver;
};

其中,kobj是该driver的kobject对象,主要用来维护所属驱动的使用计数,通过get_driver和put_driver函数来增加或减少计数。klist_devices是该driver关联的设备的链表头,一般在总线匹配到设备的时候将设备加如该链表。knode_bus是driver加入总线上驱动链表的链表元素。mkobj指向驱动所属module_kobject,module_kobject用来关联/sys/module中的对象。driver指针指向该driver_pribate结构所属的device_driver对象。

和device对象一样,device_driver对象通常静态嵌入在更大的容器对象中,比如platform_driver对象的driver成员就是一个device_driver结构。

device_driver的注册接口是driver_register,该接口用来向内核设备驱动模型中添加一个device_driver对象,并为其在sysfs中创建关联。

driver_register函数分析:

driver_register中首先通过driver_find函数在总线上根据其name属性查找该driver,如果找到则返回,不再添加,防止其重复添加。

1
2
3
4
5
6
other = driver_find(drv->name, drv->bus);
if (other) {
printk(KERN_ERR "Error: Driver '%s' is already registered, "
"aborting...\n", drv->name);
return -EBUSY;
}

接着调用bus_add_driver接口将device_driver对象添加到其所属的总线上,为该device_driver绑定设备,在sysfs中创建相关对象,以及创建其与总线和设备的关联。bus_add_driver详细过程参加Buses章节的向总线添加驱动一节。

1
2
3
ret = bus_add_driver(drv);
if (ret)
return ret;

最后在driver对应的sysfs目录中添加组目录,在组目录中添加组文件。至此,整个driver_register过程结束。

1
2
3
ret = driver_add_groups(drv, drv->groups);
if (ret)
bus_remove_driver(drv);

driver_unregister接口和driver_register接口相反,首先driver目录中删除groups文件,然后调用bus_remove_driver接口从总线上删除driver相关文件。

1
2
3
4
5
6
7
8
9
void driver_unregister(struct device_driver *drv)
{
if (!drv || !drv->p) {
WARN(1, "Unexpected driver unregister!\n");
return;
}
driver_remove_groups(drv, drv->groups);
bus_remove_driver(drv);
}

Buses

总线是处理器和设备之间的通道,在设备驱动模型里面,所有的设备都通过一个总线连接在一起,总线包括内部总线和虚拟总线。

在设备驱动模型中,kset是kobject的集合,subsystem是kset的集合,每一个总线都是一个subsystem,关联的susfs目录/sys/bus/是基础对象,所有总线(subsystem)被注册到该对象上。每一个总线subsystem中包含两个kset对象,它们关联的sysfs总线上目录名分别为devices和drivers。drivers包含了添加到该总线上的所有的device_drivers对象(kobject),devices目录包含了所有添加到该总线上的device对象(kobject),由于所有device对象已经注册在devices目录中,所以在总线subsystem的devices(kset)中的device对象都是其对应的/sys/devices中的链接文件。

在设备驱动模型中,当一个驱动要注册时,会调用到总线的驱动添加接口将驱动添加到总线,像总线添加驱动的时候会根据总线设定的规则匹配可以被该驱动处理的所有设备,并与之建立关联。当一个设备注册的时候也会调用总线添加设备的接口将设备添加到总线上,和总线创建关联,同时会在总线上遍历驱动,根据总线的匹配规则匹配到自己的驱动,并和驱动创建关联。

内核中总线描述符定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
struct bus_type {
const char *name;
const char *dev_name;
struct device *dev_root;
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 iommu_ops *iommu_ops;

struct subsys_private *p;
};

name:指向该总线的名称。

dev_name:在子系统中枚举设备是使用的设备名指针。

dev_root:默认的父设备。

bus_attrs:默认的总线属性,在sysfs中以文件形式呈现。

dev_attrs:默认的设备属性,在sysfs中以文件形式呈现。

drv_attrs:默认的驱动属性,在sysfs中以文件形式呈现。

match:驱动匹配回调函数,当一个设备或驱动向总线注册时被调用,如果匹配成功返回非零值。

uevent:当设备添加,移除或其它生成uevents添加环境变量事件时调用。

probe:当一个设备或驱动添加到该总线上时,如果设备和驱动匹配成功,就会调用该回调函数,一般用来初始化设备。

remove:当一个设备从该总线上移除时调用,以释放设备相关的资源。

shutdown:在系统关机的时候调用以停止设备。

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

resume:当要唤醒睡眠中的设备时调用。

pm:表示该总线上的电源管理的操作函数集。

iommu_ops:该总线上IOMMU特定测操作集合,用来关联某个总线上的IOMMU驱动,运行总线做一些特殊的设定操作。

p:subsys_private类型的总线子系统私有数据。包含总线的kset,以及设备和驱动链表等。

subsys_private类型在内核中的定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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:定义该子系统的kset。

devices_kset:该总线子系统的devices目录

interfaces:总线子系统上的interfaces链表。

mutex:用来保护devices和interfaces链表的互斥锁。

drivers_kset:该总线子系统的drivers目录。

klist_devices:devices目录中的设备的链表。

klist_drivers:drivers目录中的驱动的链表。

bus_notifier:总线上通知链头。

drivers_autoprobe:驱动和是否可以动态匹配的属性标记。

bus:指向该subsys_private结构所属的总线对象。

glue_dirs:放置在父设备直接的glue目录,用来避免命名空间冲突。

class:指向该结构关联的class对象。

总线的注册

总线注册接口为bus_register,是一个宏函数,实际调用的接口是__bus_register函数。

1
2
3
4
5
#define bus_register(subsys)			\
({ \
static struct lock_class_key __key; \
__bus_register(subsys, &__key); \
})

在__bus_register函数中首先为要注册的总线对象申请了subsys_private对象,并初始化了总线的通知链头:

1
2
3
4
5
6
7
8
priv = kzalloc(sizeof(struct subsys_private), GFP_KERNEL);
if (!priv)
return -ENOMEM;

priv->bus = bus;
bus->p = priv;

BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);

接着初始化总线p成员的subsys(kset),并通过kset_register注册该kset对象,即在/sys/bus下创建了以该总线的名字为名称的目录。

1
2
3
4
5
6
7
8
9
10
11
retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name);
if (retval)
goto out;

priv->subsys.kobj.kset = bus_kset;
priv->subsys.kobj.ktype = &bus_ktype;
priv->drivers_autoprobe = 1;

retval = kset_register(&priv->subsys);
if (retval)
goto out;

接着在刚创建的sysfs关联的目录下创建了uevent属性文件,同时在该目录添加了devices和drivers两个kset对象,即在该目录创建了devices和drivers两个目录。uevent文件用来控制内核向用户空间发送uevent事件,对应的向该文件写入的字符串为:“add”,“remove”,“change”,“move”, “online”, “offline”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
retval = bus_create_file(bus, &bus_attr_uevent);
if (retval)
goto bus_uevent_fail;

priv->devices_kset = kset_create_and_add("devices", NULL,
&priv->subsys.kobj);
if (!priv->devices_kset) {
retval = -ENOMEM;
goto bus_devices_fail;
}

priv->drivers_kset = kset_create_and_add("drivers", NULL,
&priv->subsys.kobj);
if (!priv->drivers_kset) {
retval = -ENOMEM;
goto bus_drivers_fail;
}

接着初始化了总线p成员的interfaces链表头,mutex互斥锁,klist_devices设备链表以及klist_drivers驱动链表。

1
2
3
4
INIT_LIST_HEAD(&priv->interfaces);
__mutex_init(&priv->mutex, "subsys mutex", key);
klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);
klist_init(&priv->klist_drivers, NULL, NULL);

最后先向总线关联的sysfs目录中添加了drivers_probe和drivers_autoprobe属性文件,然后添加了其它总线属性文件。drivers_autoprobe属性表示设备或驱动添加的时候是否匹配并调用驱动probe函数(创建关联),该属性值为非零表示自动创建关联。drivers_probe文件权限为只写,向该文件写入设备名称,则内核开始在总线上找到该设备,并为其和总线上的驱动创建关联。

1
2
3
4
5
6
7
retval = add_probe_files(bus);
if (retval)
goto bus_probe_files_fail;

retval = bus_add_attrs(bus);
if (retval)
goto bus_attrs_fail;

至此,完成了整个总线基础部分的注册以及其和sysfs的关联。

向总线添加设备(device)

向总线添加设备的接口为bus_add_device函数,该函数首先从设备中获取到设备设定的所属总线:

1
struct bus_type *bus = bus_get(dev->bus);

接着向设备中添加总线上设定的设备属性文件,即在设备关联的sysfs目录中添加总线上设定的设备属性文件:

1
2
3
error = device_add_attrs(bus, dev);
if (error)
goto out_put;

再接着在总线的devices目录中为设备所属sysfs目录创建链接文件,同时在设备所属sysfs目录中创建名为subsystem的链接文件,subsystem链接文件指向总线在sysfs中所属的目录。

1
2
3
4
5
6
7
8
error = sysfs_create_link(&bus->p->devices_kset->kobj,
&dev->kobj, dev_name(dev));
if (error)
goto out_id;
error = sysfs_create_link(&dev->kobj,
&dev->bus->p->subsys.kobj, "subsystem");
if (error)
goto out_subsys;

最后通过设备的p成员的knode_bus(klist链表成员)将该设备添加到总线的klist_devices链表上。

1
klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);

至此完成设备和总线的关联以及设备所属sysfs中的目录和总线所属sysfs中的目录的关联。

向总线添加驱动(device_driver)

向总线添加驱动的接口为bus_add_driver函数,该函数先从要添加驱动中获得驱动设定的总线:

1
2
3
bus = bus_get(drv->bus);
if (!bus)
return -EINVAL;

接着为该驱动申请driver_private对象,并初始化其设备链表头,初始化其kset为总线的驱动kset对象,即要注册的驱动对象在sysfs下的父目录是sysfs中总线目录的drivers目录(/sys/bus/xxx/drivers)然后在该目录中创建该驱动对象。

1
2
3
4
5
6
7
8
9
10
11
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
if (!priv) {
error = -ENOMEM;
goto out_put_bus;
}
klist_init(&priv->klist_devices, NULL, NULL);
priv->driver = drv;
drv->p = priv;
priv->kobj.kset = bus->p->drivers_kset;
error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,
"%s", drv->name);

接着判断总线的drivers_autoprobe属性,如果该属性值不为零,则调用driver_attach函数将驱动和匹配到的设备绑定,绑定成功后将该驱动添加到总线的驱动链表上。同时将该驱动和module_kset创建关联,即在该驱动关联的sysfs的目录中创建名为module的链接文件,指向该驱动所属module在/sys/module目录中的对象,并在module所属的sysfs目录中创建drivers目录,同时在该module目录的drivers目录中创建正在添加的device_driver对象的链接文件:

1
2
3
4
5
6
7
if (drv->bus->p->drivers_autoprobe) {
error = driver_attach(drv);
if (error)
goto out_unregister;
}
klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
module_add_driver(drv->owner, drv);

接着在该device_driver所属的sysfs目录中添加uevent文件,添加总线上指定的属性文件,如果没有设置该驱动的suppress_bind_attrs标记,则同时在该目录中添加bind和ubind文件。uevent属性文件用来控制内核向用户空间发送kobject事件。bind和ubind属性文件用来控制内核让驱动绑定或解除绑定写入bind或ubind文件的设备名表示的设备:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
error = driver_create_file(drv, &driver_attr_uevent);
if (error) {
printk(KERN_ERR "%s: uevent attr (%s) failed\n",
__func__, drv->name);
}
error = driver_add_attrs(bus, drv);
if (error) {
/* How the hell do we get out of this pickle? Give up */
printk(KERN_ERR "%s: driver_add_attrs(%s) failed\n",
__func__, drv->name);
}

if (!drv->suppress_bind_attrs) {
error = add_bind_files(drv);
if (error) {
/* Ditto */
printk(KERN_ERR "%s: add_bind_files(%s) failed\n",
__func__, drv->name);
}
}

最后调用kobject_uevent函数向用户空间发送kobject的添加事件,完成驱动向总线的添加过程:

1
kobject_uevent(&priv->kobj, KOBJ_ADD);

设备和驱动的绑定

device和device_driver在注册过程中通过driver_attach和device_attach函数来互相创建绑定关系,首先看driver_attach函数(驱动绑定设备),该函数遍历总线上的所有设备,为所有设备指向__driver_attach函数:

1
2
3
4
int driver_attach(struct device_driver *drv)
{
return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}

在__driver_attach函数中首先调用driver_match_device函数为驱动匹配设备,其实就是调用总线的match回调函数,如果匹配成功则调用driver_probe_device函数为设备和驱动建立绑定关系:

1
2
3
4
5
6
7
8
9
if (!driver_match_device(drv, dev))
return 0;

if (dev->parent) /* Needed for USB */
device_lock(dev->parent);
device_lock(dev);
if (!dev->driver)
driver_probe_device(drv, dev);
device_unlock(dev);

再看device_attach函数(设备绑定驱动),该函数先是判断是否该设备已经关联了驱动,如果关联的驱动则判断是否已经和该关联驱动建立绑定关系,如果没有则调用device_bind_driver函数和该关联的驱动建立绑定关系。如果没有关联任何驱动,则遍历设备所属总线上的所有驱动,为其执行__device_attach函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if (dev->driver) {
if (klist_node_attached(&dev->p->knode_driver)) {
ret = 1;
goto out_unlock;
}
ret = device_bind_driver(dev);
if (ret == 0)
ret = 1;
else {
dev->driver = NULL;
ret = 0;
}
} else {
pm_runtime_get_noresume(dev);
ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach);
pm_runtime_put_sync(dev);
}

再__device_attach函数中,同样首先通过driver_match_device函数调用总线上的match函数和驱动做匹配,如果匹配成功则调用driver_probe_device函数绑定设备和驱动:

1
2
3
4
5
6
7
8
9
static int __device_attach(struct device_driver *drv, void *data)
{
struct device *dev = data;

if (!driver_match_device(drv, dev))
return 0;

return driver_probe_device(drv, dev);
}

设备和驱动都最终通过driver_probe_device函数来和彼此建立绑定关系,下面看看该函数的实现。

在driver_probe_device函数中,通过调用really_probe函数使设备和驱动建立绑定:

1
ret = really_probe(dev, drv);

在really_probe函数中,首先让device的driver指针指向要绑定的device_driver对象。并通过driver_sysfs_add函数发送驱动绑定的通知到所有注册到其所属总线上的通知块,在驱动所属的sysfs目录中创建设备sysfs目录的链接文件,在设备所属的sysfs目录中创建名sysfs目录的链接文件,名字为driver:

1
2
3
4
5
6
dev->driver = drv;
if (driver_sysfs_add(dev)) {
printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n",
__func__, dev_name(dev));
goto probe_failed;
}

接着调用probe回调函数,如果设置了总线的probe回调函数,则调用总线的probe函数,否则调用驱动的probe回调函数:

1
2
3
4
5
6
7
8
9
if (dev->bus->probe) {
ret = dev->bus->probe(dev);
if (ret)
goto probe_failed;
} else if (drv->probe) {
ret = drv->probe(dev);
if (ret)
goto probe_failed;
}

最后调用driver_bound函数完成设备和驱动的绑定以及其对应sysfs中对象的关联。在driver_bound函数首先将设备添加到驱动的设备链表头中,接着将所有延迟probe列表中的该设备删除,最后向所有注册到总线上的通知块发送绑定结束的通知。

1
driver_bound(dev);

Classes

class是设备高级别的抽象,屏蔽了设备底层的实现细节。class结构在内核中的定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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, umode_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;
};

name:描述class的名字。

owner:指向class所属的模块。

class_attrs:class对象的属性文件。

dev_attrs:class关联的设备的属性文件。

dev_bin_attrs:属于该class的设备的二进制文件。

dev_kobj:class的kobject对象。

dev_uevent:当一个设备添加到该class或从该class移除时调用,或者其它情况生成uevent事件时调用。

devnode:用来在/dev/目录下创建设备文件时回调。

class_release:用来释放class。

dev_release:用来释放class中的设备。

suspend:使该class中的设备进入睡眠模式时调用。

resume:从睡眠模式中唤醒设备时调用。

namespace:该class的命名空间。

pm:电源关联相关操作集合。

p:驱动核心私有数据。该结构和总线中的p成员相同。

创建和注册

class的创建和注册接口为class_create,class单独的注册接口为class_register,class_create和class_register都是调用__class_register函数注册的。在这里只关注class_create接口。

class_create是一个宏函数,其真正调用的是__class_create函数。

在__class_create函数中,首先创建class对象,并根据初始化其名称和所属模块,然后设置其class_reliase回调函数:

1
2
3
4
5
6
7
8
9
cls = kzalloc(sizeof(*cls), GFP_KERNEL);
if (!cls) {
retval = -ENOMEM;
goto error;
}

cls->name = name;
cls->owner = owner;
cls->class_release = class_create_release;

然后调用__class_register函数注册刚创建的class:

1
2
3
retval = __class_register(cls, key);
if (retval)
goto error;

在__class_register函数中首先为class的p成员申请空间并初始化其设备链表头,interfaces链表头,sysfs关联目录的名称等:

1
2
3
4
5
6
7
8
9
10
11
12
cp = kzalloc(sizeof(*cp), GFP_KERNEL);
if (!cp)
return -ENOMEM;
klist_init(&cp->klist_devices, klist_class_dev_get, klist_class_dev_put);
INIT_LIST_HEAD(&cp->interfaces);
kset_init(&cp->glue_dirs);
__mutex_init(&cp->mutex, "subsys mutex", key);
error = kobject_set_name(&cp->subsys.kobj, "%s", cls->name);
if (error) {
kfree(cp);
return error;
}

接着设置class包含的设备在sysfs下所属的目录为/sys/dev目录:

1
2
3
/* set the default /sys/dev directory for devices of this class */
if (!cls->dev_kobj)
cls->dev_kobj = sysfs_dev_char_kobj;

然后设置其所属的kset对象为class_kset,即class被注册到的sysfs目录为/sys/class目录。接着调用kset_register将该class注册到/sys/class目录中。再接着通过add_class_attrs向其注册的目录中添加class的属性文件。

1
2
3
4
5
6
7
8
9
10
11
cp->subsys.kobj.kset = class_kset;
cp->subsys.kobj.ktype = &class_ktype;
cp->class = cls;
cls->p = cp;

error = kset_register(&cp->subsys);
if (error) {
kfree(cp);
return error;
}
error = add_class_attrs(class_get(cls));

向class添加设备

向class中创建设备的接口是device_create函数,该函数主要通过调用device_create_vargs函数添加设备,在device_create_vargs函数中,先创建了device对象,并根据传入参数设置了其devt和class以及parent属性。并设置了device对象的release回调函数,以及device对象的drvdata:

1
2
3
4
5
6
7
8
9
10
11
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev) {
retval = -ENOMEM;
goto error;
}

dev->devt = devt;
dev->class = class;
dev->parent = parent;
dev->release = device_create_release;
dev_set_drvdata(dev, drvdata);

最后根据传入的参数设置了device对象在sysfs中名字,并调用device_register函数将设备添加到总线和sysfs中,正如在Devices章节分析的那样,这里设置了device对象的class成员和devt成员后,在device_register函数中不但会将该device对象添加到/sys/devices中,并且会在该class对应/sys/class中的目录中创建该设备的链接,同时在/sys/dev/目录下创建该设备的链接,同时也会在devtmpfs中创建设备节点。

/dev下设备文件的创建

在linux系统中,可以通过mknod程序手动创建设备文件。

在较早版本的内核中,可以在内核中创建class,并在class中创建device对象并设置device对象的devno,这样注册到sysfs的device对象会在/sys/class目录中对应的class目录中创建device对象的链接文件。当系统启动初始化的时候,用户空间的udev或者mdev程序可以扫描/sys/class目录,对具有设备号的设备自动在/dev/目录下为其创建设备文件。

在较新版本的内核中引入了devtmpfs的概念,对于属于class和有devno的device对象,在其注册阶段就会在devtmpfs中创建设备文件,系统启动后不需要udev或mdev的扫描,直接通过下面的命令挂载devtmpfs到/dev目录即可:

1
mount -t devtmpfs none /dev

内核uevent向用户空间的通知

内核发送事件

从设备驱动模型各组件的注册过程可知内核通过kobject_uevent函数向用户空间发送通知,kobject_uevent函数大概流程如下:

1.通过发送事件的kobject获取到其kset对象,从kset中获取到其kset_uevent_ops。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* search the kset we belong to */
top_kobj = kobj;
while (!top_kobj->kset && top_kobj->parent)
top_kobj = top_kobj->parent;

if (!top_kobj->kset) {
pr_debug("kobject: '%s' (%p): %s: attempted to send uevent "
"without kset!\n", kobject_name(kobj), kobj,
__func__);
return -EINVAL;
}

kset = top_kobj->kset;
uevent_ops = kset->uevent_ops;

2.判断该kobj本事是否设置了uevent_suppress,该属性不为零表示该kobject对象不发送uevent。

1
2
3
4
5
6
7
/* skip the event, if uevent_suppress is set*/
if (kobj->uevent_suppress) {
pr_debug("kobject: '%s' (%p): %s: uevent_suppress "
"caused the event to drop!\n",
kobject_name(kobj), kobj, __func__);
return 0;
}

3.执行kset_uevent_ops中的filter回调函数,已确定该kobj的事件是否被过滤。

1
2
3
4
5
6
7
8
/* skip the event, if the filter returns zero. */
if (uevent_ops && uevent_ops->filter)
if (!uevent_ops->filter(kset, kobj)) {
pr_debug("kobject: '%s' (%p): %s: filter function "
"caused the event to drop!\n",
kobject_name(kobj), kobj, __func__);
return 0;
}

4.确定其subsystem名字。

1
2
3
4
5
6
7
8
9
10
11
/* originating subsystem */
if (uevent_ops && uevent_ops->name)
subsystem = uevent_ops->name(kset, kobj);
else
subsystem = kobject_name(&kset->kobj);
if (!subsystem) {
pr_debug("kobject: '%s' (%p): %s: unset subsystem caused the "
"event to drop!\n", kobject_name(kobj), kobj,
__func__);
return 0;
}

5.申请环境变量buf,设置环境变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/* environment buffer */
env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL);
if (!env)
return -ENOMEM;

/* complete object path */
devpath = kobject_get_path(kobj, GFP_KERNEL);
if (!devpath) {
retval = -ENOENT;
goto exit;
}

/* default keys */
retval = add_uevent_var(env, "ACTION=%s", action_string);
if (retval)
goto exit;
retval = add_uevent_var(env, "DEVPATH=%s", devpath);
if (retval)
goto exit;
retval = add_uevent_var(env, "SUBSYSTEM=%s", subsystem);
if (retval)
goto exit;

/* keys passed in from the caller */
if (envp_ext) {
for (i = 0; envp_ext[i]; i++) {
retval = add_uevent_var(env, "%s", envp_ext[i]);
if (retval)
goto exit;
}
}

6.如果是添加或移除事件,在kobject对象中的相关属性中标记,以确保该kobject能正确的被cleanup,同时向环境变量缓冲区中设置事件序号。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*
* Mark "add" and "remove" events in the object to ensure proper
* events to userspace during automatic cleanup. If the object did
* send an "add" event, "remove" will automatically generated by
* the core, if not already done by the caller.
*/
if (action == KOBJ_ADD)
kobj->state_add_uevent_sent = 1;
else if (action == KOBJ_REMOVE)
kobj->state_remove_uevent_sent = 1;

mutex_lock(&uevent_sock_mutex);
/* we will send an event, so request a new sequence number */
retval = add_uevent_var(env, "SEQNUM=%llu", (unsigned long long)++uevent_seqnum);
if (retval) {
mutex_unlock(&uevent_sock_mutex);
goto exit;
}

7.将事件和其环境变量缓冲区内容通过netlink发送到用户空间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* send netlink message */
list_for_each_entry(ue_sk, &uevent_sock_list, list) {
struct sock *uevent_sock = ue_sk->sk;
struct sk_buff *skb;
size_t len;

if (!netlink_has_listeners(uevent_sock, 1))
continue;

/*
* ...
*/
retval = netlink_broadcast_filtered(uevent_sock, skb,
0, 1, GFP_KERNEL,
kobj_bcast_filter,
kobj);
/* ... */
}

8.如果用户空间设置了uevent_helper程序,则通过call_usermodehelper调用该程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* call uevent_helper, usually only enabled during early boot */
if (uevent_helper[0] && !kobj_usermode_filter(kobj)) {
char *argv [3];

argv [0] = uevent_helper;
argv [1] = (char *)subsystem;
argv [2] = NULL;
retval = add_uevent_var(env, "HOME=/");
if (retval)
goto exit;
retval = add_uevent_var(env,
"PATH=/sbin:/bin:/usr/sbin:/usr/bin");
if (retval)
goto exit;

retval = call_usermodehelper(argv[0], argv,
env->envp, UMH_WAIT_EXEC);
}

用户空间处理事件

用户空间一般用mdev/udev工具处理内核发送到用户空间的事件。内核一般用两种机制完成该过程,一种是通过netlink网络将事件信息发送到用户mdev/udev守护进程。一种是直接调用用户程序,这就要求用户空间向内核设置一个用户空间处理事件的程序,需内核开启CONFIG_HOTPLUG配置,以可通过procfs或sysfs向内核设置该程序,默认由CONFIG_UEVENT_HELPER_PATH配置设定,一般的默认设置是/sbin/hotplug,通过sysfs或procfs设置方法如下:

1
2
echo /sbin/mdev > /proc/sys/kernel/hotplug
echo /sbin/mdev > /sys/kernel/uevent_helper

以mdev为例说明,mdev主要负责两个功能,第一是在系统启动的时候扫描/sys/class目录,将该目录下有设备号的设备在/dev/目录下创建设备文件。第二个功能就是接受内核uevent事件,在/dev下动态添加设备或改变设备的属性。

设备驱动模型应用实例

实例说明

通过一个简单的设备驱动模型的应用实例验证设备驱动模型。

内核samples代码由关于kset和kobject的测试代码,在这里直接使用一些原有的对象,在其基础之上做进一步验证。

实现一个ddm(device driver model)模块,向系统中注册一个新的总线ddm,并为其注册设备的父设备为ddm_bus设备,在注册总线之后,将总线上的drivers_autoprobe属性值设置为0,是为了手动绑定ddm总线上的设备和驱动。然后定义一种ddm_device类型的设备和ddm_driver类型的驱动,总线上简单的通过名字的比较匹配ddm设备和ddm驱动。并实现ddm设备的注册和取消注册接口以及ddm驱动的注册和取消注册接口。在模块加载函数中注册总线设备父设备以及总线,之后再注册一个ddm驱动和两个ddm设备,以此来验证设备驱动模型。在用户空间sysfs视角,该模块实现以下功能:

1.在/sys/devices目录中新增ddm_bus目录,在ddm_bus目录中存在ddm_driver-0和ddm_driver-1两个目录。

2.在/sys/bus目录中新增ddm目录,在ddm目录的driver目录中存在ddm_driver目录以及ddm_driver-0和ddm_driver-1两个目录的链接文件。

3.在/sys/module下新增ddm目录,在ddm目录中存在ddm_driver目录的链接文件。

实例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/device.h>

#define to_ddm_device(x) container_of((x), struct ddm_device, dev)
#define to_ddm_driver(x) container_of((x), struct ddm_driver, driver)

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;
};

/*
* ddm device struct
*/
struct ddm_device {
const char *name;
int id;
struct device dev;
void *priv;
};

/*
* ddm driver struct
*/
struct ddm_driver {
struct device_driver driver;
};

void ddm_device_release(struct device *dev)
{
printk("%s\n", __func__);
}

static struct device ddm_bus = {
.init_name = "ddm_bus",
.release = ddm_device_release,
};
static int ddev_id = 0;

/*
* match according the name of dev's container and drv.
*/
static int ddm_match(struct device *dev, struct device_driver *drv)
{
struct ddm_device *ddev = to_ddm_device(dev);

return (strcmp(ddev->name, drv->name) == 0);
}

static struct bus_type ddm_bus_type = {
.name = "ddm",
.match = ddm_match,
};

int ddm_device_register(struct ddm_device *ddev)
{
if (!ddev)
return -EINVAL;
device_initialize(&ddev->dev);

if (!ddev->dev.parent)
ddev->dev.parent = &ddm_bus;
ddev->dev.bus = &ddm_bus_type;
ddev->dev.release = ddm_device_release;

ddev->id = ddev_id++;
dev_set_name(&ddev->dev, "%s-%d", ddev->name, ddev->id);

return device_add(&ddev->dev);
}

void ddm_device_unregister(struct ddm_device *ddev)
{
if (!ddev)
return;
device_del(&ddev->dev);
put_device(&ddev->dev);
}

int ddm_driver_register(struct ddm_driver *drv)
{
drv->driver.bus = &ddm_bus_type;
if (!drv->driver.probe || !drv->driver.remove) {
printk("ddm driver need the device_driver initialized\n");
return -EINVAL;
}

return driver_register(&drv->driver);
}

void ddm_driver_unregister(struct ddm_driver *drv)
{
driver_unregister(&drv->driver);
}

static int ddm_driver_probe(struct device *dev)
{
struct ddm_device *ddev = to_ddm_device(dev);
printk("%s:ddm device %s-%d probed\n", __func__, ddev->name, ddev->id);
return 0;
}

static int ddm_driver_remove(struct device *dev)
{
struct ddm_device *ddev = to_ddm_device(dev);
printk("%s:ddm device %s-%d removed\n", __func__, ddev->name, ddev->id);
return 0;
}

static struct ddm_driver test_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "ddm_driver",
.probe = ddm_driver_probe,
.remove = ddm_driver_remove,
},
};

static struct ddm_device test_devices[] = {
{
.name = "ddm_driver",
},
{
.name = "ddm_driver",
},
};

static int __init ddm_init(void)
{
int error;
int i;
struct ddm_device *ddev;

/*register the parent device of this bus type*/
error = device_register(&ddm_bus);
if (error)
return error;

/*register the bus type*/
error = bus_register(&ddm_bus_type);
if (error)
goto unregister_device;
ddm_bus_type.p->drivers_autoprobe = 0;

/*register driver to ddm_bus_type */
error = ddm_driver_register(&test_driver);
if (error) {
goto unregister_bus;
}

for (i = 0; i < ARRAY_SIZE(test_devices); i++) {
error = ddm_device_register(&test_devices[i]);
if (error)
goto unregister_dev;
}

/*register devices to ddm_bus_type */
return error;

unregister_dev:
ddev = &test_devices[0];
while (i--)
ddm_device_unregister(ddev++);
ddm_driver_unregister(&test_driver);
unregister_bus:
bus_unregister(&ddm_bus_type);
unregister_device:
device_unregister(&ddm_bus);
return error;
}

static void __exit ddm_exit(void)
{
int i;
for (i = 0; i < ARRAY_SIZE(test_devices); i++)
ddm_device_unregister(&test_devices[i]);
ddm_driver_unregister(&test_driver);
bus_unregister(&ddm_bus_type);
device_unregister(&ddm_bus);
}

module_init(ddm_init);
module_exit(ddm_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("shad");
MODULE_DESCRIPTION("Device driver model test!");
MODULE_VERSION("DDM_V0.1");

实例测试

将实例代码编译成模块文件ddm.ko,将ddm.ko通过insmod程序加载到内核后可以看到:

1.在/sys/module目录下出现ddm目录,该目录的drivers目录中中存在指向/sys/bus/ddm/drivers/ddm_driver目录的链接文件。

2./sys/bus目录中出现ddm目录,在该ddm目录中的drivers目录下有一个ddm_driver目录,devices目录下有两个链接文件ddm_driver-0和ddm_driver-1

3.在/sys/devices目录中出现ddm_bus目录,该目录中有ddm_driver-0和ddm_driver-1两个目录。

以上现象说明模块实例成功向内核注册了ddm总线,并在ddm总线上注册了一个ddm_driver和两个设备(ddm_driver-0和ddm_driver-1)。

接下来向/sys/bus/ddm/drivers_probe或者/sys/bus/ddm/drivers/ddm_driver/bind文件中写入“ddm_driver-0”:

1
2
echo ddm_driver-0 > /sys/bus/ddm/drivers_probe 或者
echo ddm_driver-0 > /sys/bus/ddm/drivers/ddm_driver/bind

出现的新现象为:在/sys/bus/ddm/drivers/ddm_driver目录中出现了ddm_driver-0的链接文件,指向/sys/devices/ddm_bus/ddm_driver-0目录。在/sys/bus/ddm/devices/ddm_driber-0目录中出现了driver链接文件,指向/sys/bus/ddm/drivers/ddm_driver。

以上现象说明,通过向总线的drivers_probe或者驱动的bind属性文件写入要绑定的设备名后,内核成功将了匹配到的设备和驱动建立绑定关系。