Device Drivers
struct device_driver {
char * name;
structbus_type * bus;
structcompletion unloaded;
structkobject kobj;
list_t devices;
struct module *owner;
int (*probe) (struct device * dev);
int (*remove) (struct device * dev);
int (*suspend) (struct device * dev, pm_message_tstate);
int (*resume) (struct device * dev);
};
Allocation~~~~~~~~~~
device_driver结构是静态创建的. 系统中或许有多个被同一驱动支持的设备,这个结构代表的是驱动自身(而不是某个特定的设备).
Initialization~~~~~~~~~~~~~~
驱动必须至少初始化域name 和bus. 还应该要初始化域devclass (如果有的话), 这样才能获得正确的内部链接. 虽然对于callback的初始化是可选的,但多多益善。
Declaration~~~~~~~~~~~
如上所述, 结构device_driver的对象是静态创建的. 下面是声明eepro100驱动的例子. 这个声明不是真实的;取决于驱动是否被改造为符合新的驱动模型.
static struct device_driver eepro100_driver = {
.name = "eepro100",
.bus =&pci_bus_type,
.probe = eepro100_probe,
.remove = eepro100_remove,
.suspend = eepro100_suspend,
.resume = eepro100_resume,
};
绝大多数的驱动无法被改造为完全符合新的驱动模型,这是因为驱动所属的bus有该bus特有的数据结构,该结构又包含有该bus特有的域,而这些特有的部分是无法被范化的。
最普遍的例子是device ID 结构. 驱动一般会定义一个device ID数组,用于存放该驱动所支持的设备. 该结构的格式及其语义完全是其所属bus所特有的. 将它们定义在bus的数据结构中会损害类型安全性,所以我们还是保留bus相关的数据结构。.
和特定bus相关的驱动声明中应该要包含与bus无关的device_driver结构及bus相关的结构. 就象这样:
struct pci_driver {
conststruct pci_device_id *id_table;
struct device_driver driver;
};
包含bus相关的驱动定义则看起来是这样的 (还是使用eepro100驱动):
static struct pci_driver eepro100_driver = {
.id_table = eepro100_pci_tbl,
.driver = {
.name = "eepro100",
.bus = &pci_bus_type,
.probe = eepro100_probe,
.remove = eepro100_remove,
.suspend = eepro100_suspend,
.resume = eepro100_resume,
},
};
有人会觉得嵌套数据结构的初始化很难看,但这是截止目前为止我们能想到的最佳解决方案...
Registration~~~~~~~~~~~~
int driver_register(struct device_driver * drv);
驱动程序在启动的时候将上述结构注册到内核. 那些不包含特定总线的驱动使用函数driver_register()进行注册,参数为指向device_driver结构对象的指针.
然而绝大多数的驱动都包含有总线相关的数据结构,这种情形下就要使用诸如pci_driver_register()这样的函数进行注册.
驱动程序尽可能早地注册至关重要. 注册时内核会对device_driver对象的几个域进行初始化,包括reference count 和lock. 这些域被认为是可以随时可以访问的,设备模型核心和总线驱动都有可能要访问它们。.
Transition BusDrivers ~~~~~~~~~~~~~~~~~~~~~~
通过定义一些wrapper函数, 向新模型的转变会更容易一些. 驱动程序可以选择忽略整个device_driver结构而让bus的wrapper函数填充各个域. 对结构中的那些callback函数来说,bus会定义通用的callback,通用callback会将调用转到驱动中的与bus相关的那些callback去.
这是个暂时方案. 为了从驱动中获取class信息,现有的驱动无论润和都要被修改的. 将驱动程序更新到新的驱动模型会减少底层的复杂度和代码量,因此更新时推荐加入对class信息的支持.
Access ~~~~~~
对象一旦被注册,就可以对其中的一些普通域进行访问,比如lock 和设备列表.
int driver_for_each_dev(struct device_driver * drv,
void* data,
int(*callback)(struct device * dev, void * data)
);
Devices域是绑定到该驱动的所有设备的列表. LDM核心提供一个辅助函数用以对驱动程序控制的所有设备进行操作. 每当有节点被访问时,该辅助函数都会锁住驱动并维护每一个设备的reference count.
sysfs ~~~~~
驱动注册后, 内核会在/sysfs中的相应总线类型下创建目录. 这个目录是驱动提供给用户空间的接口,用以控制对驱动的操作。比如,打开/关闭驱动的调试信息开关.
未来/sysfs下还会支持'devices'目录. 这个目录下会包含指向设备目录的符号链接.
Callbacks~~~~~~~~~
int (*probe) (struct device * dev);
probe()在任务(task)上下文中被调用, 此时,bus的读写信号量(rwsem)是锁住的,而驱动只是部分地绑定到设备. 在probe()和其它函数中,驱动程序一般使用宏container_of()将"dev"转变为设备相关的类型. 这个数据类型通常提供设备的资源信息,比如pci_dev.resource[]或platform_device.resources, 这些信息连同dev->platform_data一起被用来初始化驱动程序.
这个callback函数包含有驱动相关的逻辑用以将驱动绑定到设备. 包括:
确认设备存在;
设备的版本号是驱动所支持的;
驱动自身的数据结构可以申请到内存并正确初始化。
驱动通常在dev_set_drvdata()中保存一个指向驱动状态的指针. 驱动和设备绑定成功的情况下,probe()返回零,内核中驱动模型的代码会继续完成绑定的工作. 否则,返回错误码(负数)指示绑定没有成功。这种情形下要释放所有已申请的资源.
int (*remove) (struct device * dev);
remove()用于解除绑定. 这个回调函数可能在以下情形被调用:
物理设备自系统中移除;
驱动模块被移除(rmmod);
系统重启;
其它.
判断设备是否存在的工作由驱动程序完成. 如果设备被移除,驱动要释放为该设备申请的所有资源-也就是设备的driver_data结构中的所有域.
如果设备还在,驱动则要将设备置于低功耗状态。
int (*suspend) (struct device * dev, pm_message_tstate);
suspend用于将设备置于低功耗状态.
int (*resume) (struct device * dev);
Resume 用于将设备从低功耗状态转回正常工作状态.
Attributes~~~~~~~~~~
structdriver_attribute {
structattribute attr;
ssize_t(*show)(struct device_driver *, char * buf, size_t count, loff_t off);
ssize_t(*store)(struct device_driver *, const char * buf, size_t count, loff_t off);
};
驱动可以透过它在/sysfs中的目录向外界声明自身的属性. 用宏DRIVER_ATTR 定义属性。顺便说一句,宏DEVICE_ATTR的工作方式是一样的.
Example:
DRIVER_ATTR(debug,0644,show_debug,store_debug);
上面这个宏等同于:
struct driver_attribute driver_attr_debug;
这个数据结构可进一步用来增加/移除属性:
int driver_create_file(struct device_driver *, structdriver_attribute *);
void driver_remove_file(struct device_driver *, structdriver_attribute *);