一、设备驱动
相比较设备、总想,设备驱动能够抽象的要少些,它的更多内容都是特定于硬件的,因而linux驱动模型中的驱动部分相对也比较简单。
1.1 数据结构
linux内核的设备驱动子系统使用数据结构struct device_driver来表示一个设备驱动,请定义如下:
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;
};
各个域的含义如下:
- name:驱动名字
- bus:这个驱动对应的设备所在的总线
- owner:所有者
- mod_name:驱动编译到内核镜像中时所使用的名字
- suppress_bind_attrs:如果非0,则无法通过sysfs来让驱动和设备之间关联(bind)的关系
- of_match_table: 固件表
- probe:用于检测驱动是否能支持某个设备,如果可以就将驱动和设备关联起来
- remove:当设备要从系统中删除时,用于将驱动和设备的关联关系去掉
- shutdown:用于关闭设备
- suspend:用于让设备进入睡眠模式
- resume:用于将设备从睡眠中唤醒
- groups:属性列表,属于该列表的属性会由驱动核心自动在sysfs中创建相应的属性文件
- pm:电源管理函数集
- p:驱动私有数据结构指针
struct driver_private定义如下:
struct driver_private {
struct kobject kobj;
struct klist klist_devices;
struct klist_node knode_bus;
struct module_kobject *mkobj;
struct device_driver *driver;
};
- kobj:用于在kobject层次结构中代表驱动
- klist_devices:由该驱动驱动的设备列表
- knode_bus:用于将该驱动添加到该驱动所关联的总线的的驱动列表中
- mkobj:用于将驱动和module(/sys/module)关联起来
- driver:指向驱动本身
1.2 添加、删除驱动
可以调用driver_register来添加一个驱动到系统中,如果要从系统中删除一个驱动,需要调用driver_unregister。需要注意驱动模型为驱动添加了一些限制:
- 一个总线上的所有驱动的名字必须是唯一的,如果往一条总线上添加了两个名字相同的驱动,则添加会失败。
- 驱动必须在某条总线上,从代码的角度来说就是调用driver_register之前,驱动的bus域必须指向一个总线结构,并且它的私有数据必须不为空,实际上就是说总线必须是初始好的总线。
- 如果总线和驱动都提供了探测函数或者删除函数或者关闭函数,就会答应一个警告,这意味着linux驱动模型的开发者期望一个总线上的设备只在一处指定其probe,remvoe以及shutdown函数,而不是到处指定。
driver_register的处理流程如下:
- 合法性检查
- 检查该驱动所在总线上是否已经存在同名驱动,如果是就失败返回
- 调用bus_add_driver将驱动添加到总线上
- 调用driver_add_groups在sysfs中未驱动的属性创建属性文件
bus_add_driver完成了驱动添加的大部分工作,其工作过程如下:
- 创建并初始化驱动的私有数据结构
- 将驱动的私有数据结构中的kobj.kset设置为它所在总线的驱动的kset,即bus->p->drivers_kset
- 将驱动的私有数据结构的kobj的kobj_type设置为driver_ktype
- 如果总线使能了自动探测功能,即drivers_autoprobe为1,就调用driver_attach对总线上的所有设备进行检查,看是否有本驱动可以支持的设备,如果有就将驱动和设备关联起来,并用该驱动加载驱动相应的设备。
- 将驱动添加到总线的驱动链表
- 调用module_add_driver
- 在sysfs中为驱动创建driver_attr_uevent属性文件,它用于发送uevent事件。
- 调用driver_add_attrs,在sysfs中为该驱动所在总线所定义的驱动属性创建属性文件。
- 如果支持热插拔,就调用add_bind_files在sysfs中为驱动创建driver_attr_unbind和driver_attr_bind属性文件。这两个文件用于手动的将驱动和设备关联起来或者手动的解除二者的关联关系(driver_bind和driver_unbind将该驱动和指定的设备绑定或者解绑定)。
- 调用kobject_uevent发送KOBJ_ADD通知
driver_ktype的定义如下
static struct kobj_type driver_ktype = {
.sysfs_ops = &driver_sysfs_ops,
.release = driver_release,
};
其中的实现都很简单,释放函数就是直接释放数据结构,而读写属性函数就是调用相关属性的读写函数。
1.3 驱动属性
驱动使用的属性结构如下:
struct driver_attribute {
struct attribute attr;
ssize_t (*show)(struct device_driver *driver, char *buf);
ssize_t (*store)(struct device_driver *driver, const char *buf, size_t count);
};
驱动框架提供了宏DRIVER_ATTR来定义一个驱动属性,其原型如下:
#define DRIVER_ATTR(_name, _mode, _show, _store) struct driver_attribute driver_attr_##_name =
__ATTR(_name, _mode, _show, _store)
驱动子系统所提供的添加属性和删除属性的api如下:
int driver_create_file(struct device_driver *drv, const struct driver_attribute *attr)
void driver_remove_file(struct device_driver *drv, const struct driver_attribute *attr)
1.4 遍历驱动所驱动的设备及查找驱动所驱动的设备
驱动子系统提供了遍历一个驱动所支持的所有设备以及查找它所驱动的某个设备的能力,相关的API为:
int driver_for_each_device(struct device_driver *drv, struct device *start, void *data, int (*fn)(struct device *, void *))
struct device *driver_find_device(struct device_driver *drv, struct device *start, void *data, int (*match)(struct device *dev, void *data))
1.5 查找总线下某个驱动
驱动子系统提供了在某个总线下查找特定驱动的功能,其API为:
struct device_driver *driver_find(const char *name, struct bus_type *bus)
二、类
在linux驱动模型中,总线、设备以及驱动都是真实存在的,不过除了这三类之外,linux驱动模型还提供了另外一个子系统--类。与其它三种子系统不同,类不是真实存在的,它是一种抽象,比如系统中可能有很多类型的硬盘,但是在类的层次上它们都是磁盘。抽象出类的价值在于有了类之后,用户就可以根据其目地来选择设备而不必依赖于设备的连接方式和工作方式。
2.1 数据结构
类的数据结构定义如下:
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:类的名字
- owner:类的所有者
- class_attrs:该类的默认属性
- dev_attrs:属于该类的设备所具有的默认的属性,在设备初始化时会通过device_add_attrs在sysfs中创建对应的属性文件
- dev_bin_attrs:属于该类的设备所具有的默认的二进制属性, 在设备初始化时会通过device_add_attrs在sysfs中创建对应的属性文件
- dev_kobj:用于将该类添加到kobject层次结构中
- dev_uevent: 当从类中添加或者删除设备或者发生其它一些需要产生uevent事件时被调用,用于准备uevent环境变量
- devnode:用于获取设备的devnode
- class_release:用于释放给定的类
- dev_release: 用于释放指定的设备
- suspend:用于让指定的设备进入休眠模式
- resume:用于将指定的设备从休眠中唤醒
- ns_type:提供给sysfs,用于获取命名空间
- namespace:用于获取指定的设备的命名空间
- pm:该类设备的默认电源管理操作集
- p:类的私有数据结构
我们在设备数据结构,总线数据结构以及类数据结构中都看到了电源管理的函数集,那么具体使用哪一个呢,在现在的设计中,其顺序依次是(都是设备数据结构struct device中的域)pm_domain > type->pm > class->pm > bus->pm。具体的代码位于文件drivers/base/power/main.c
2.2 添加、删除类
可以使用class_register来向系统添加一个class,如果要从系统删除一个类,可以使用class_unregister。class_register非常简单,其流程为:
- 创建并初始化类的私有数据结构
- 设置类的名字,如果失败,就出错返回。类的名字是保存在类的私有数据结构的kset的kobject中的
- 如果没有设置类的dev_kobj,它就会被设置为默认的sysfs_dev_char_kobj,这对应于/sys/dev/char
- 将类的subsys.kobj.kset设置为class_kset,将类的subsys.kobj.ktype设置为class_ktype。
- 调用kset_register初始化类的kset
- 调用add_class_attrs在sysfs中为类的默认属性创建属性文件
class_kset在classes_init中被创建和初始化,它没有提供kset_uevent_ops和父节点,因而发出uevent时,它没有提供特别的处理,它在sysfs中对应于位于/sys/class目录。
class_ktype的定义如下:
static struct kobj_type class_ktype = {
.sysfs_ops = &class_sysfs_ops,
.release = class_release,
.child_ns_type = class_child_ns_type,
};
当类被释放时,会调用class_release,该函数处理很简单,如果类提供有释放函数就调用它, 然后释放类数据结构。
其中的属性读写函数也很简单,就是调用属性自己的读写函数。
class_child_ns_type用于获取指定属性的命名空间。
2.3 类属性
驱动使用的属性结构如下:
struct class_attribute {
struct attribute attr;
ssize_t (*show)(struct class *class, struct class_attribute *attr, char *buf);
ssize_t (*store)(struct class *class, struct class_attribute *attr, const char *buf, size_t count);
const void *(*namespace)(struct class *class, const struct class_attribute *attr);
};
类框架提供了宏CLASS_ATTR来定义一个类属性,其原型如下:
#define CLASS_ATTR(_name, _mode, _show, _store)
struct class_attribute class_attr_##_name = __ATTR(_name, _mode, _show, _store)
类子系统所提供的添加属性和删除属性的api如下:
extern int __must_check class_create_file(struct class *class, const struct class_attribute *attr);
extern void class_remove_file(struct class *class, const struct class_attribute *attr);
2.4 遍历类所属的所有设备及查找类所属的某个设备
类子系统提供了遍历一个类所包括的所有设备以及查找某个设备的能力,相关的API为:
int class_for_each_device(struct class *class, struct device *start, void *data, int (*fn)(struct device *dev, void *data));
struct device *class_find_device(struct class *class, struct device *start, void *data, int (*match)(struct device *, void *));
同时类子系统还提供了迭代器来迭代类所拥有的每个设备。
void class_dev_iter_init(struct class_dev_iter *iter, struct class *class, struct device *start, const struct device_type *type)
struct device *class_dev_iter_next(struct class_dev_iter *iter)
void class_dev_iter_exit(struct class_dev_iter *iter)
其意义很明显。
2.5 子设备链表的保护
类子系统所包括的所有设备被保存在一个klist链表中,类子系统为它们提供了相关的get和put函数,并提供给了klist框架。相关的API为:
void klist_class_dev_get(struct klist_node *n)
void klist_class_dev_put(struct klist_node *n)
2.6 注册新的接口
类还提供了注册和删除class_interface的接口,用于向一个类添加class_interface类型的接口或者从其中删除class_interface类型的接口,其API如下:
int class_interface_register(struct class_interface *class_intf)
void class_interface_unregister(struct class_interface *class_intf)
这两个函数都会遍历类下的所有设备,然后对这些设备调用新接口的add_dev或者remove_dev。另外我们注意到在添加和删除设备的时候也会遍历类下的所有class_interface,然后调用每个class_interface的add_dev或者remove_dev,因此类框架提供了一个接口让我们可以添加一个class_interface到类中,然类框架保证我们添加接口时,它会对所有已经在类中的设备调用我们提供接口中的add_dev或者remove_dev,并且保证对于类中每一个设备变化(添加或者删除)我们都会得到相应的通知,这就使得我们可以依赖于自己的目地向某个类注册一个class_interface,然后通过这个class_interface来完成自己的工作。