译者:郭少悲
2009/12/18
原文:linux-2.6/Documentation/driver-model/porting.txt
移植驱动到新的设备驱动模型
概述
请参考阅读Documentation/driver-model/*.txt关于各种驱动类型和概念的定义。
绝大多数的移植设备驱动到新模型的工作量在总线驱动层。这是有意设计的,目的
在于减少对内核驱动的负面影响,以及允许总线驱动渐进变化。
总之,新的驱动模型包含了一套对象,这些对象可以嵌入到更大的总线相关的具体
对象。这些通用对象的域可以代替总线相关的具体对象中的域。
通用对象必须注册到驱动模型核心。注册后,它们将通过sysfs文件系统被导出。
sysfs可以使用下面命令进行mount:
# mount -t sysfs sysfs /sys
过程
步骤0:阅读include/linux/device.h,获取对象和函数接口定义。
步骤1:注册总线驱动。
- 为总线驱动定义struct bus_type结构。
struct bus_type pci_bus_type = {
.name = "pci",
};
- 注册总线类型
这步在初始化函数中完成,通常是在module_init()里,或者其他等价功能的函数里。
static int __init pci_driver_init(void)
{
return bus_register(&pci_bus_type);
}
subsys_initcall(pci_driver_init);
总线类型也许会被注销(如果总线驱动被配置为模块),使用如下函数接口:
bus_unregister(&pci_bus_type);
- 导出总线类型,供其他代码使用
其他代码也许会引用总线类型,所以将它声明在共享的头文件里,并导出符号。
来自include/linux/pci.h头文件:
extern struct bus_type pci_bus_type;
来自上述代码实现的代码文件:
EXPORT_SYMBOL(pci_bus_type);
- 这会导致总线在/sys/bus/pci上显示两个子目录:'devices'和'drivers'。
# tree -d /sys/bus/pci/
/sys/bus/pci/
|-- devices
`-- drivers
步骤2:注册设备
struct device表示一个单一的设备。它主要包含了描述该设备与其他实体的关系
的元数据。
- 在一个特定总线的设备类型里嵌入一个struct device。
struct pci_dev {
...
struct device dev; /* Generic device interface */
...
};
建议不要将通用的设备结构(struct device)放置在某个具体结构的第一个位置
上,避免程序员在对象类型的转换上欠周全考虑。相反,应该使用宏或者内置
函数,来进行从通用对象类型到具体对象类型的转换。
#define to_pci_dev(n) container_of(n, struct pci_dev, dev)
or
static inline struct pci_dev * to_pci_dev(struct kobject * kobj)
{
return container_of(n, struct pci_dev, dev);
}
这允许编译器检验执行的操作的类型安全(好的做法)。
- 初始化注册设备
当设备被探测或者注册到总线类型上时,总线驱动应当初始化通用设备。最
重要的需要初始化的域有bus_id,parent,bus。
bus_id是ASCII字符串,包含了设备在总线上的地址。字符串的格式是总线相关的。
这对sysfs上描述设备信息是必需的。
parent是设备的物理父节点。总线驱动需要正确地设置这个域。
驱动模型维护一个有序的设备链表,用于电源管理。这个链表必须有序,以
确保设备在它们的父节点之前关闭,反之依然。链表的顺序由注册设备的父
节点确定。
设备在sysfs的目录位置也依赖于设备的父节点。sysfs导出了一个描述设备层次
结构的目录结构。准确的设置设备的父节点,保证sysfs导出的目录层次的正确性。
设备的bus域是一个执行设备所述的总线类型的指针。
它应当设置为之前声明和初始好的总线类型。
可选地,总线驱动也可以设置设备的name和release域。
name域是描述设备的ASCII码字符串,例如:
"ATI Technologies Inc Radeon QD"
release域是当设备被移除后驱动模型核心要调用的回调函数,所有对该
设备的引用都要释放。后面有更多的讨论。
- 注册设备
一旦一个通用设备被初始化后,它可以通过下面的函数接口向驱动模型核心
进行注册:
device_register(&dev->dev);
之后可以通过下面的函数接口进行注销:
device_unregister(&dev->dev);
设备注销会发生在支持设备热插拔功能的总线上。如果一个总线驱动注销一个
设备,它不应当立即释放设备。它应当等待驱动模型核心调用设备的release
方法完成后,再释放总线相关的对象。(也许其他代码正在引用设备的数据结构,
因此在引用期间立即释放设备是粗鲁的)
当设备完成注册后,sysfs下会创建一个对应的目录。sysfs里的PCI树看起来如下:
/sys/devices/pci0/
|-- 00:00.0
|-- 00:01.0
| `-- 01:00.0
|-- 00:02.0
| `-- 02:1f.0
| `-- 03:00.0
|-- 00:1e.0
| `-- 04:04.0
|-- 00:1f.0
|-- 00:1f.1
| |-- ide0
| | |-- 0.0
| | `-- 0.1
| `-- ide1
| `-- 1.0
|-- 00:1f.2
|-- 00:1f.3
`-- 00:1f.5
当然,在/sys/bus里的device目录里,创建了指向实际设备目录的符号
链接。
/sys/bus/pci/devices/
|-- 00:00.0 -> ../../../devices/pci0/00:00.0
|-- 00:01.0 -> ../../../devices/pci0/00:01.0
|-- 00:02.0 -> ../../../devices/pci0/00:02.0
|-- 00:1e.0 -> ../../../devices/pci0/00:1e.0
|-- 00:1f.0 -> ../../../devices/pci0/00:1f.0
|-- 00:1f.1 -> ../../../devices/pci0/00:1f.1
|-- 00:1f.2 -> ../../../devices/pci0/00:1f.2
|-- 00:1f.3 -> ../../../devices/pci0/00:1f.3
|-- 00:1f.5 -> ../../../devices/pci0/00:1f.5
|-- 01:00.0 -> ../../../devices/pci0/00:01.0/01:00.0
|-- 02:1f.0 -> ../../../devices/pci0/00:02.0/02:1f.0
|-- 03:00.0 -> ../../../devices/pci0/00:02.0/02:1f.0/03:00.0
`-- 04:04.0 -> ../../../devices/pci0/00:1e.0/04:04.0
步骤3:注册驱动
struct device_driver是一个简单的驱动数据结构,包含了一套操作集,供驱动
模型核心调用。
- 在总线相关的驱动里嵌入struct device_driver结构。
类似于总线相关的设备结构,总线相关的驱动结构如下:
struct pci_driver {
...
struct device_driver driver;
};
- 初始化通用驱动结构
在驱动向总线注册的期间(例如:执行pci_register_driver()),初始化驱动一些必需
的域:name域和bus域。
- 注册驱动
当通用驱动被初始化后,调用下面接口函数:
driver_register(&drv->driver);
向核心注册驱动。
当驱动从总线注销时,使用下面接口函数:
driver_unregister(&drv->driver);
注意,这步将阻塞,直到所有对驱动的引用都释放。正常情况下,此时对驱动
的引用为0。
- Sysfs描述
驱动导出到sysfs文件系统中的/sys/bus/driver目录,示例如下:
/sys/bus/pci/drivers/
|-- 3c59x
|-- Ensoniq AudioPCI
|-- agpgart-amdk7
|-- e100
`-- serial
步骤4:为驱动定义通用方法
struct device_driver定义了一套操作集,供驱动模型核心调用。这些操作函数的
大部分都类似总线已经为驱动定义好的操作函数集,只是处理的参数不同。
强制每个总线上的驱动让它们同时转换成通用格式是很困难的,也是很乏味的。
总线驱动应当定义通用方法的一个单一实例,将对通用方法的调用转移到总线
相关的驱动里。
static int pci_device_remove(struct device * dev)
{
struct pci_dev * pci_dev = to_pci_dev(dev);
struct pci_driver * drv = pci_dev->driver;
if (drv) {
if (drv->remove)
drv->remove(pci_dev);
pci_dev->driver = NULL;
}
return 0;
}
在被注册之前,通用驱动应当初始化这些方法:
/* initialize common driver fields */
drv->driver.name = drv->name;
drv->driver.bus = &pci_bus_type;
drv->driver.probe = pci_device_probe;
drv->driver.resume = pci_device_resume;
drv->driver.suspend = pci_device_suspend;
drv->driver.remove = pci_device_remove;
/* register with core */
driver_register(&drv->driver);
理想的,总线应当初始化那些没有设置好的域。这允许驱动去实现它们自己
的通用方法。
步骤5:支持通用驱动绑定
设备模型假定一个设备或者一个驱动能够在任何时间动态的注册到总线上。
在注册过程中,设备必须绑定到一个驱动,或者驱动必须绑定到它支持的设备上。
一个驱动典型地包含一个它所支持的设备ID链表。总线驱动会拿这些ID与注册其上
的设备ID进行比较,以确定是否支持。设备ID的格式和语法是总线相关的,因此
通用的设备模型会尝试通用化它们。
相反,一个总线也许会支持struct bus_type里的一个用于比较的方法:
int (*match)(struct device * dev, struct device_driver * drv);
match()返回'1',表示驱动支持设备;返回'0‘,表示不支持。
当一个设备注册时,总线上的驱动链表会进行迭代,bus->match()会在每次
迭代时调用,直到找到一个与设备匹配的驱动。
当一个驱动注册时,总线上的设备链表会进行迭代,bus->match()会为
每一个没有绑定到驱动上的设备进行调用。
当一个设备成功的绑定到一个驱动时,device->driver会被设置,设备会
被添加到设备的一个per-driver链表上,sysfs下的驱动目录里会创建一个指向
设备物理目录的符号链接。
/sys/bus/pci/drivers/
|-- 3c59x
| `-- 00:0b.0 -> ../../../../devices/pci0/00:0b.0
|-- Ensoniq AudioPCI
|-- agpgart-amdk7
| `-- 00:00.0 -> ../../../../devices/pci0/00:00.0
|-- e100
| `-- 00:0c.0 -> ../../../../devices/pci0/00:0c.0
`-- serial
这种驱动绑定模型应当代替总线当前使用的已经存在的驱动绑定机制。
步骤6:提供hotplug回调函数
一旦设备被注册到驱动模型的核心,用户程序/sbin/hotplug就会被调用以
通知用户空间。用户可以定义当设备插入或者移除时的响应行为。
驱动模型核心通过环境变量向用户空间传递若干参数,包括:
- ACTION: set to 'add' or 'remove'
- DEVPATH: set to the device's physical path in sysfs.
总线驱动也可能向用户空间提供若干额外的参数。为此,总线必须实现struct
bus_type里的'hotplug'方法:
int (*hotplug) (struct device *dev, char **envp,
int num_envp, char *buffer, int buffer_size);
在/sbin/hotplug被执行之前,这个回调函数会被立即调用。
步骤7:清理总线驱动
通用的总线,设备,和驱动的数据结构提供了若干域,替代总线驱动的私有域。
- 设备链表
struct bus_type包含了一个设备链表:记录了注册到总线上的所有设备。
总线使用的内部链表也许会被移除,取而代之使用这个设备链表。
驱动模型核心提供了一个访问这些设备的迭代函数:
int bus_for_each_dev(struct bus_type * bus, struct device * start,
void * data, int (*fn)(struct device *, void *));
- 驱动链表
struct bus_type也包含一个驱动链表:记录了注册到其上的所有驱动。总线
驱动维护的内部链表也许会被移除,取而代之使用这个通用的驱动链表。
驱动模型核心也提供了一个迭代驱动的迭代函数:
int bus_for_each_drv(struct bus_type * bus, struct device_driver * start,
void * data, int (*fn)(struct device_driver *, void *));
请参考drivers/base/bus.c,获取更多信息。
-读写锁
struct bus_type包含了一个读写锁,保护所有的对设备链表和驱动链表的核心
访问。它也能够被总线驱动内部使用,在访问总线维护的设备链表和驱动链表时
应当使用这个读写锁。
- 设备和驱动的若干域
在struct device和struct device_driver里的一些域复制了相对应的总线相关
的数据结构的域,移除总线相关的域,使用这些通用结构的域是可行的。记住,
这种做法也意味着要修改所有的引用总线相关域的驱动 (尽管可能你只需修改1行代码)