Linux内核设备模型
翻译者:郭少悲
2009/12/01
原文:linux/Documentation/driver-model/overview.txt
概述
~~~~
Linux内核驱动模型是针对内核之前所有不同的驱动模型的统一抽象模型。它的目的是,通
过结合一套数据和操作集到一个全局可访问的数据结构里,从而添加基于某种指定总线的设
备和桥接驱动。
传统的驱动模型实现了类似树的数据结构(或者一个链表数据结构)来控制设备,
而没有一个跨越不同总线类型,统一的抽象数据结构。
当前的驱动模型提供了一个通用的统一的数据模型,用于描述一个总线,以及挂载在总线
上的设备。这个统一的总线模型包含了一套每个总线都拥有的通用属性,一套通用的回调
函数,例如bus probing, bus shutdown, bus power management回调函数
等等。
通用的设备和桥接接口体现了现代计算机的目标:即无缝设备“即插即用”的能力,电源管
理能力,和热插拔能力。特别地,由Intel和Microsoft支配的模型假定几乎所有的设备在
x86兼容的总线上能够以上述方式工作。当然,不是每一个总线能够支持所有的这些操作
,但是几乎绝大部分的总线支持这些操作的绝大部分。
底层访问
~~~~~~~~
通用的数据域已经从单个具体的总线层移置到一个通用的数据结构里。这些域仍然必须
能够被总线层访问,有时也可以被特定的设备驱动访问。
其他的总线层被鼓励学习PCI总线层的做法,struct pci_dev定义如下:
struct pci_dev {
...
struct device dev;
};
首先记住,它是静态分配的。这意味着在设备探测时只分配一次。也需要记住,这个域
(struct device dev)在struct pci_dev的尾部。这让人们理解在需要bus driver和
global driver之间进行转换时应当做什么,减少切换需要的开销。
(我的理解是,很简单,就是两个数据结构之间通过指针来查找定位)
PCI总线层完全能够自由地访问struct device的各个域。它了解struct pci_dev的结构,也应
当了解struct device的结构。具体的PCI设备驱动使用了新的驱动模型,就不能也不应当
接触struct device的各个域,除非有一个要做的强制理由。
这种抽象阻止了在过渡阶段不必要的阵痛(我的理解,指的是数据在不同结构层上的处理
)。如果一个域的名字改变或者移除,底层的驱动就会遭到破坏。从另一方面讲,如果仅
有总线层(非设备层)访问struct device,那么应当只有总线层发生改变。
用户接口
~~~~~~~~
借助系统对于所有设备有一个完整的层次视图的优势,向用户空间输出一个完整的设备层
次视图比较容易做到。这个功能已经通过实现一个特殊目的的虚拟文件系统sysfs而变为
现实了。用户可以在用户空间任意的地方mount sysfs文件系统。
你可以在/etc/fstab文件里添加下面一行内容,保证你的系统能够永久自动地mount
sysfs。(当然,你需要确认mount点/sys是存在的)
none /sys sysfs defaults 0 0
你也可以通过手动命令就行mount,如下:
# mount -t sysfs sysfs /sys
一旦设备被插入到这个树里,对应地会为它产生一个目录。这个目录可能会在树的每个层
里出现:全局层,总线层,或者是设备层。
全局层通常创建两个文件-'name'和'power'。前者仅仅报告了设备的名字;后者报告了
设备的当前电源状态,它也被用来设置当前的电源状态。
总线层也许会为它所检测到的挂载在其上的设备创建文件。例如,PCI层通常会为每个
PCI设备创建'irq'和'resource'文件。
一个特定设备的驱动也许会在它的目录里导出设备特定的文件,显示设备相关的数据
或者调整设备参数的接口。
更多的sysfs目录布局请参考文档Documentation/filesystems/sysfs.txt。
翻译者:郭少悲
2009/12/01
原文:linux/Documentation/driver-model/binding.txt
驱动绑定
驱动绑定就是将一个设备和控制该设备的驱动关联在一起的过程。
典型的情况下,总线驱动来处理驱动绑定,因为每个总线驱动都有总线相关的数据
结构来表示设备和设备驱动。有了表示设备和设备驱动的通用数据结构,绝大多数的绑
定过程可以使用这些通用代码。
总线
~~~
总线类型数据结构包含一个记录所有属于这个总线类型的设备链表。当一个设备驱动调用
device_register()时,它会被添加到这个链表的尾部。
总线类型数据结构也包含一个记录所有属于这个总线类型的驱动链表。当一个设备驱动调用
driver_register()时,该驱动会被添加到这个链表的尾部。
这是触发驱动绑定的两个主要事件。
设备注册
~~~~~~~~
当一个新设备被添加后,总线的驱动链表会被迭代,查找出支持该设备的驱动。为了达到
这个目的,新设备的设备ID必须匹配驱动支持的设备ID中的一个。设备ID的格式和
语法是总线相关的。不需要使用一个复杂的状态机和匹配算法,而是由总线驱动提供
一个callback函数来比较设备和一个驱动所支持的ID是否匹配,如果匹配返回值为1,否则
返回值为0。
int match(struct device * dev, struct device_driver * drv);
当一个匹配被找到时,设备的'driver'域会被设置为这个驱动,驱动的probe回调函数会被调用。
这使驱动有机会检验是否真正支持这个硬件设备,同时会检验设备是否处于工作状态。
设备类别
~~~~~~~~~~~~
当probe成功完成后,设备被注册到它属于的类中。设备驱动属于且仅属于一个类,这个类会在
驱动的devclass域中设置。
devclass_add_device被调用来枚举和注册类中的设备,这个过程发生在类的register_dev回调
函数中。
注意:设备class结构和其核心操作函数还没有进入mainline内核,所以以上讨论有一定推
测性。
驱动
~~~~
当一个驱动被关联到一个设备时,这个设备被插入到驱动的设备链表中。
sysfs
~~~~~
在bus目录的'devices'子目录里,创建符号链接,指向实际的device目录。
在driver的'devices'子目录里,创建符号链接,指向实际的device目录。
在class目录里为设备创建子目录,在子目录里,创建符号链接,指向该设备在sys
tree里的实际位置。
也可以在设备的实际目录里,创建符号链接,指向它的class目录,或者class目录的顶层
目录;也可以创建符号链接,指向它的driver目录。不过目前并没有这样做。
驱动注册
~~~~~~~~
添加一个新的driver的过程,与添加一个新的device的过程类似。
总线的设备链表会被迭代,找到一个匹配的设备。已经绑定在驱动上的设备可以被
跳过。所有的设备都要被迭代,多个设备可以绑定到一个驱动上。
删除
~~~~
当一个设备要被删除时,它的引用计数最终会为0。当为0时,驱动的remove回调函数会
被调用。设备会从驱动的设备链表中移除,驱动的引用计数会减1。设备和它的驱动
之间的符号链接均被删除。
当一个驱动要被删除时,它所支持的设备列表会被迭代,驱动的remove回到函数会为每一
个设备调用一次。设备从设备列表中删除,符号链接也要被删除。
译者:郭少悲
2009/12/01
原文:linux-2.6/Documentation/driver-model/bus.txt
总线类型
定义
~~~~
struct bus_type {
char * name;
struct subsystem subsys;
struct kset drivers;
struct kset devices;
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 (*hotplug) (struct device *dev, char **envp,
int num_envp, char *buffer, int buffer_size);
int (*suspend)(struct device * dev, pm_message_t state);
int (*resume)(struct device * dev);
};
int bus_register(struct bus_type * bus);
声明
~~~~
内核里的每一个总线类型(PCI,USB,等)应当声明一个静态的对象。它们必须初始
化name域,有时也初始化match()回调函数。
struct bus_type pci_bus_type = {
.name = "pci",
.match = pci_bus_match,
};
该数据结构应当被包含在一个头文件里,向相关的驱动导出:
extern struct bus_type pci_bus_type;
注册
~~~~~
总线驱动在初始化的时候,会调用bus_register()。它初始化总线对象的其余的域,
并将总线对象插入到总线类型的全局链表里。一旦总线对象被注册,
它的每个域对于总线驱动都是可用的。
回调函数
~~~~~~~~
match():关联驱动到设备
~~~~~~~~~~~~~~~~~~~~~~
设备的ID数据结构的格式和比较语法由其总线自己规定。驱动程序会在其对应的
总线驱动数据结构里定义一组它支持的设备ID集。
match()回调函数的目的是向总线提供机会, 通过将设备ID与驱动支持的ID集比较,
来判断是否指定的驱动支持指定的设备。这种方法不会牺牲总线的功能,或者牺牲总
线类型安全。
当驱动被注册到总线时,总线的设备链表会进行迭代,对那些还没有和驱动关联的设备
调用match()回调函数。
设备链表与驱动链表
~~~~~~~~~~~~~~~~~~
设备链表和驱动程序链表用来替代大多总线持有的本地链表。它们分别是struct device
和struct device_driver结构的链表。总线驱动可以自由的使用这些链表,但可能需要
转换为总线相关的数据类型。
Linux设备模型核心提供了迭代每个链表的帮助函数。
int bus_for_each_dev(struct bus_type * bus, struct device * start, void * data,
int (*fn)(struct device *, void *));
int bus_for_each_drv(struct bus_type * bus, struct device_driver * start,
void * data, int (*fn)(struct device_driver *, void *));
这些帮助函数分别迭代每个链表,在链表里对每个设备或者驱动调用回调函数。
对链表的所有访问通过获取总线锁(读锁)而同步访问。在调用回调函数之前,链表里的每个
对象的引用计数增加,在访问下一个对象后减少。在调用回调函数时锁被释放。
sysfs
~~~~~~~~
在sysfs的顶层目录里,有个目录被命名为'bus'。
每个总线在bus目录里获得一个子目录,子目录里又包含两个默认目录:
/sys/bus/pci/
|-- devices
`-- drivers
注册到总线上的驱动会在总线的驱动目录里获得一个子目录:
/sys/bus/pci/
|-- devices
`-- drivers
|-- Intel ICH
|-- Intel ICH Joystick
|-- agpgart
`-- e100
挂载在某个总线上的设备被检测到后,会在总线的设备目录里获得一个符号链接,
该链接指向实际的设备目录层次。
/sys/bus/pci/
|-- devices
| |-- 00:00.0 -> ../../../root/pci0/00:00.0
| |-- 00:01.0 -> ../../../root/pci0/00:01.0
| `-- 00:02.0 -> ../../../root/pci0/00:02.0
`-- drivers
导出属性
~~~~~~~~
struct bus_attribute {
struct attribute attr;
ssize_t (*show)(struct bus_type *, char * buf);
ssize_t (*store)(struct bus_type *, const char * buf, size_t count);
};
总线驱动会通过BUS_ATTR宏导出属性,BUS_ATTR与DEVICE_ATTR类似。例如,如下定义:
static BUS_ATTR(debug,0644,show_debug,store_debug);
等价于声明:
static bus_attribute bus_attr_debug;
这可以用来对sysfs文件系统上的总线目录添加和删除属性,通过使用:
int bus_create_file(struct bus_type *, struct bus_attribute *);
void bus_remove_file(struct bus_type *, struct bus_attribute *);
译者:郭少悲
2009/12/02
原文:linux/Documentation/driver-model/class.txt
设备类
介绍
~~~~
一个设备类描述了一类的设备,例如语音设备或者网络设备。下面是已定义的设备类:
每个设备类定义了一套语法和设备遵循的编程接口。设备驱动就是为特定总线上的
特定设备而完成的这套编程接口实现。
对于一个设备驻足在哪个总线上,设备类是不可知的。
编程接口
~~~~~~~~
设备类的数据结构如下:
typedef int (*devclass_add)(struct device *);
typedef void (*devclass_remove)(struct device *);
struct device_class {
char * name;
rwlock_t lock;
u32 devnum;
struct list_head node;
struct list_head drivers;
struct list_head intf_list;
struct driver_dir_entry dir;
struct driver_dir_entry device_dir;
struct driver_dir_entry driver_dir;
devclass_add add_device;
devclass_remove remove_device;
};
一个典型的设备类类似如下定义:
struct device_class input_devclass = {
.name = "input",
.add_device = input_add_device,
.remove_device = input_remove_device,
};
每个设备类数据结构放在一个头文件里导出,因此它能够被驱动,扩展程序和接口
使用。
内核里设备类的注册和注销函数接口如下所示:
int devclass_register(struct device_class * cls);
void devclass_unregister(struct device_class * cls);
设备
~~~~
当设备绑定到驱动上,它就会被添加到驱动所属的设备类里。在驱动模型问世之前,
这个过程在驱动的probe()回调函数里实现;当有了驱动模型后,这个过程在probe()
回调函数被调用后实现。
设备在设备类里会被枚举。当一个设备被加入到一个设备类里,设备类的devnum域就会
做加操作,分配给这个设备。这个域从来不会做减操作,所以当设备从设备类里
移除,重新添加时,它会获得一个不同的枚举值。
设备类被允许为设备创建一个类相关的数据结构,并将其保存到设备的class_data
指针。
设备类里没有设备链表。每个驱动有一个它支持的设备链表。为了能够访问到
设备类里的所有的设备,将会迭代设备类里的每个驱动的设备链表。
设备驱动
~~~~~~~~
当设备驱动被注册到内核里时,它们会被加入到设备类里。一个驱动通过设备
struct device_driver::devclass域来说明它属于哪个设备类。
sysfs目录结构
~~~~~~~~~~~~~
在sysfs的顶层目录里,有个名为'class'的目录。
每个设备类在class目录里获得一个自己的目录,以及两个默认的子目录:
class/
`-- input
|-- devices
`-- drivers
注册到设备类里的驱动会在drivers/目录里获得一个指向实际drivers目录
的符号链接(实际drivers目录在bus目录下):
class/
`-- input
|-- devices
`-- drivers
`-- usb:usb_mouse -> ../../../bus/drivers/usb_mouse/
每一个设备在devices/目录里获得一个指向实际驻扎的设备目录的符号链接:
class/
`-- input
|-- devices
| `-- 1 -> ../../../root/pci0/00:1f.0/usb_bus/00:1f.2-1:0/
`-- drivers
导出属性
~~~~~~~~
struct devclass_attribute {
struct attribute attr;
ssize_t (*show)(struct device_class *, char * buf, size_t count, loff_t off);
ssize_t (*store)(struct device_class *, const char * buf, size_t count, loff_t off);
};
设备类驱动使用DEVCLASS_ATTR宏导出属性,类似devices提供的DEVICE_ATTR宏。
例如,一个如下定义:
static DEVCLASS_ATTR(debug,0644,show_debug,store_debug);
等价于如下声明:
static devclass_attribute devclass_attr_debug;
总线驱动可以从设备类的sysfs目录里添加/删除一个属性,使用如下函数接口:
int devclass_create_file(struct device_class *, struct devclass_attribute *);
void devclass_remove_file(struct device_class *, struct devclass_attribute *);
在上面的例子里,放置在设备类目录里的文件会被命名为'debug'。
接口
~~~~
也许存在多个访问同一设备类下的同一设备的机制。设备接口就是用来描述
这样的机制的。
当一个设备被加入到一个设备类里,内核尝试将它添加到设备类的每一个被注册过的
接口里。
阅读(1241) | 评论(0) | 转发(1) |