序言
从这一章开始,我们将详细的介绍Linux的设备驱动模型。Linux设备驱动模型是一个相当复杂的系统,对于初学者来说真有些无从入手。而且更加困难的是,随着新的Linux
Kernel的release,Linux的设备驱动模型总会有或大或小的变化,我们将尽量展现 Linux Kernel
的这种变化。
早期的Linux内核(版本2.4之前)并没有实现一个统一的设备模型,设备节点的创建一般是mknod命令手动创建或利用devfs文件系统创建。早期的Linux发行版一般会采用手动创建的方式预先把通常用到的节点都创建出来,而嵌入式系统则会采用devfs的方式。起初Linux
2.6
内核还支持devfs,但从2.6.18开始,内核完全移除了devfs系统而采用的udev的方式动态的创建设备节点。因此,新的Linux发行版都采用udev的方式管理设备节点文件。(关于udev的详细信息,请参考:http://www.kernel.org/pub/linux/utils/kernel/hotplug/udev.html)。
Linux2.6设备驱动模型的基本元素是Class、Bus、Device、Driver,下面我们分别介绍各个部分。
Class 和Class
Device
驱动模型最基本的概念是设备及其类别,Linux中使用struct class 和struct
class_device来管理不同类别的设备。由于设备驱动模型是一个复杂的系统,我们还是从一个简单的例子开始介绍,然后在逐步展开。其实实现设备节点的动态创建是一个很简单的事情,并不需要太多的代码。我们修改我们的驱动初始化函数如下:
#include
#define DEVNAME "hello" static dev_t
dev; static struct class *hello_class; static
struct cdev *hello_cdev; static int __init
hello_init(void) { int
error;
error =
alloc_chrdev_region(&dev, 0, 2,
"hello"); if
(error) { printk("hello:
alloc_chardev_region
failed!\n"); goto
out; } hello_cdev
= cdev_alloc(); if (hello_cdev ==
NULL) { printk("hello:
alloc cdev
failed!\n"); error
=
-ENOMEM; goto
out_chrdev; } hello_cdev->ops
= &hello_fops; hello_cdev->owner =
THIS_MODULE; error = cdev_add(hello_cdev,
dev, 1); if
(error) { printk("hello:
cdev_add
failed!\n"); goto
out_cdev; } hello_class =
class_create(THIS_MODULE, DEVNAME); if
(IS_ERR(hello_class)) { error
= PTR_ERR(hello_class); goto
out_chrdev; } class_device_create(hello_class,
NULL, dev, NULL, DEVNAME); memset
(hello_buf, 0,
sizeof(hello_buf)); memcpy(hello_buf,
DEFAULT_MSG,
sizeof(DEFAULT_MSG)); printk("hello: Hello
World!\n"); return
0;
out_cdev: cdev_del(hello_cdev); out_chrdev: unregister_chrdev_region(hello_cdev->dev,
2); out: return error; } static
void __exit hello_exit(void) { class_device_destroy(hello_class,
dev); class_destroy(hello_class); unregister_chrdev_region(hello_cdev->dev,
2); cdev_del(hello_cdev); printk("hello:
Goodbye World\n"); } |
重新编译这个驱动程序,当加载这个驱动到内核中时,系统(一般是hotplug和udev系统)就会自动的创建我们指定的设备名字:/dev/hello,同时,你也可以发现在sysfs系统中添加了新的文件:/sys/class/hello/hello/。
当然并不需要把所有的代码都贴到这里,但是这样做可能更加清楚。我们把添加的代码显示成蓝色,这样你可以清楚的看到代码的简单程度。这里主要用到了class_create
和class_device_create函数,它们定义在头文件中。
extern struct class *class_create(struct module *owner, const char
*name); extern void class_destroy(struct class *cls); extern struct
class_device *class_device_create(struct class
*cls, struct
class_device
*parent, dev_t
devt, struct
device
*device, const
char *fmt,
...) __attribute__((format(printf,5,6))); extern
void class_device_destroy(struct class *cls, dev_t
devt); |
Linux是通过设备与设备类来管理设备的,当你调用这些函数向系统注册设备及其类的时候,内核会自动的在sysfs文件系统中创建对应的文件。如果想了解更多的关于设备及其类的函数接口,请你阅读Linux
kernel的源文件(include/device.h头文件和drivers/base/class.c实现文件)。这里简单说明class_device_create函数,它的最后一个参数是一个变参,类似于printf函数参数,用于指定添加设备的名称也就是显示在/dev/目录下设备文件名称。
创建的class_device设备会自动注册到系统中,这样对于给定的设备,系统会自动找到匹配的设备驱动。
你可以在设备类或设备目录下(/sys/class)创建文件,这个文件提供了内核同用户空间程序的交互接口。内核提供了设备、设备类的内核函数接口,这些接口也定义在头文件中。
int class_create_file(struct class *cls, const struct
class_attribute *attr); void class_remove_file(struct class *cls, const
struct class_attribute *attr); int class_device_create_file(struct
class_device
*class_dev, const
struct class_device_attribute *attr); void class_device_remove_file(struct
class_device
*class_dev, const
struct class_device_attribute *attr); int class_device_create_bin_file(struct
class_device
*class_dev, struct
bin_attribute *attr); void class_device_remove_bin_file(struct class_device
*class_dev, struct
bin_attribute *attr); |
其实,这些函数仅仅是简单的封装了sysfs文件系统函数,但它为设备及设备类提供了统一的函数接口。
Drivers
当一个设备注册到系统中时,它就向系统表明了哪个驱动匹配这个设备。Linux内核会在注册的设备驱动中查找匹配的驱动并调用对应的探测函数(probe)来初始化设备。设备驱动接口也定义在头文件中。
struct device_driver { const
char
*name; struct
bus_type
*bus; struct
module
*owner; const
char
*mod_name;
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); struct
attribute_group **groups; struct
driver_private *p; }; |
其中,比较重要的数据是name,Linux内核就是根据name来匹配设备与驱动的。probe函数用于设备的探测及初始化,remove函数在设备移出系统时被触发调用。
一般来说,我们的驱动需要实现name、module、probe、remove函数。
Bus
通常我们的驱动并不需要实现Bus接口,也没有这个必要,因此你完全可以跳过这段,除非你想添加一个新的总线到系统中。
在Linux2.6内核中,struct
bus_type描述了一个bus对象,它定义在头文件中。(你发现没有,到目前Linux设备模型接口都定义在这个文件中)
struct bus_type { const char
*name; 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 (*suspend_late)(struct device
*dev, pm_message_t state); int
(*resume_early)(struct device *dev); int
(*resume)(struct device *dev);
struct
bus_type_private *p; };
extern int __must_check bus_register(struct
bus_type *bus); extern void bus_unregister(struct bus_type
*bus);
extern int __must_check bus_create_file(struct bus_type
*, struct
bus_attribute *); extern void bus_remove_file(struct bus_type *, struct
bus_attribute *); |
在这个结构中,name描述了bus的名字,如它会显示在/sys/bus/中。match函数用于匹配属于这个bus的设备和驱动,uevent用于处理Linux
uevent事件。probe和remove类似与driver的函数接口,主要用于设备的Hotplug处理。其他的函数是Power相关的函数接口。
同样,bus也需要注册到系统中,并可以在sysfs中创建文件。
sysfs文件系统
sysfs类似于proc文件系统,用于用户空间程序和内核空间交互数据的接口。但sysfs提供了更多的功能,其中之一就是显示Linux驱动程序模型的分层结构关系。Ubuntu
804的sysfs文件系统的目录显示如下:
block
bus class devices firmware
fs kernel module power
slab
当你浏览这个文件系统的时候,你会发现里面有很多链接文件,其实正是这些链接文件展现了Linux驱动模型各个组成部分之间的关系。
sysfs文件系统中,最重要的就是struct
attribute结构,它被用来管理内核sysfs文件的接口(名字,属性,读写函数等)。内核sysfs提供了基本的attribute接口,不同的设备如bus、device在基本attribute的基础上定义了自己的读写函数,sysfs提供了对应的宏来简化属性的操作。请参考头文件中。
struct attribute {
const char
*name; struct
module
*owner; mode_t
mode; };
#define
__ATTR(_name,_mode,_show,_store) { \ .attr =
{.name = __stringify(_name), .mode = _mode },
\ .show
= _show,
\ .store
= _store,
\ }
int
__must_check sysfs_create_file(struct kobject *kobj, const struct attribute
*attr); int __must_check sysfs_create_dir(struct kobject
*kobj); |
我们看到,sysfs的struct
attribute结构本身并不包含读写访问函数,驱动模型的各个部分都会扩展这个结构并定义自己的属性结构来引入各自的操作函数,如
class:(这个结构定义在头文件中)。
struct class_attribute
{ struct attribute
attr; ssize_t
(*show)(struct class *, char * buf); ssize_t
(*store)(struct class *, const char * buf, size_t count); }; #define
CLASS_ATTR(_name, _mode, _show, _store)
\ struct
class_attribute class_attr_##_name = __ATTR(_name, _mode, _show,
_store) |
关于sysfs的更多信息,请参考
Linux内核源代码树中的Documentation/filesystems/sysfs.txt文件。
Platform总线
platform总线是Linux内核中的一个虚拟总线,它使得设备的管理更加简单化。目前大部分的驱动都是用platform总线来写的。platform总线模型的各个部分都是继承自Device模型(姑且这么说吧),它在系统内实现了个虚拟的总线,即platform_bus,如果你的设备需要platform总线管理,那么就需要向系统中注册platform设备及其驱动程序。就像前面所介绍的那样,platform总线分为platform_bus,
platform_device
和platform_driver几个部分,他们的接口定义在头文件中。