我们将详细的介绍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的方式管理设备节点文件。
Linux2.6设备驱动模型的基本元素是Class、Bus、Device、Driver,下面我们分别介绍各个部分。
Class 和Class Device
驱动模型最基本的概念是设备及其类别,Linux中使用struct class 和struct class_device来管理不同类别的设备。由于设备驱动模型是一个复杂的系统,我们还是从一个简单的例子开始介绍,然后在逐步展开。其实实现设备节点的动态创建是一个很简单的事情,并不需要太多的代码。我们修改我们的驱动初始化函数如下:
block bus class devices firmware fs kernel module power slab
当你浏览这个文件系统的时候,你会发现里面有很多链接文件,其实正是这些链接文件展现了Linux驱动模型各个组成部分之间的关系。
sysfs文件系统中,最重要的就是struct attribute结构,它被用来管理内核sysfs文件的接口(名字,属性,读写函数等)。内核sysfs提供了基本的attribute接口,不同的设备如bus、device在基本attribute的基础上定义了自己的读写函数,sysfs提供了对应的宏来简化属性的操作。请参考<linux/sysfs.h>头文件中。
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:(这个结构定义在<linux/device.h>头文件中)。
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几个部分,他们的接口定义在<linux/platform.h>头文件中。
我们先来看看platform_bus的定义:
struct device platform_bus = { .bus_id = "platform", }; struct bus_type platform_bus_type = { .name = "platform", .dev_attrs = platform_dev_attrs, .match = platform_match, .uevent = platform_uevent, .suspend = platform_suspend, .suspend_late = platform_suspend_late, .resume_early = platform_resume_early, .resume = platform_resume, }; int __init platform_bus_init(void) { int error; error = device_register(&platform_bus); if (error) return error; error = bus_register(&platform_bus_type); if (error) device_unregister(&platform_bus); return error; } |
platform_bus数据结构描述了platform bus设备,platform_bus_type描述了platform bus总线,它提供了platform总线设备和驱动的匹配函数。platform总线是由函数platform_bus_init(void)初始化的。
对于Linux我们一般的设备驱动程序来说,就像前面Bus一段提到的那样,我们不需要关心platform总线本身,我们只要调用我们的设备和驱动接口就可以了。
如果你想让platform总线来管理设备,那么,你需要先向platform系统注册设备,这个过程是通过下面的函数接口来实现的:
int platform_device_add(struct platform_device *pdev); int platform_device_register(struct platform_device *pdev); |
我们一般需要调用
platform_device_register 函数来向系统添加platform设备。这两个函数唯一的差别就是
platform_device_register 在添加设备前会初始化platform_device的dev数据成员,它是一个struct device类型数据。当一个platform_device添加到platform总线中后,platform总线就会为它找到匹配的设备驱动程序,很显然,在这之前,你需要向系统注册platform_driver。
我们先来看看platform总线设备驱动的结构:
struct platform_driver { int (*probe)(struct platform_device *); int (*remove)(struct platform_device *); void (*shutdown)(struct platform_device *); int (*suspend)(struct platform_device *, pm_message_t state); int (*suspend_late)(struct platform_device *, pm_message_t state); int (*resume_early)(struct platform_device *); int (*resume)(struct platform_device *); struct device_driver driver; }; extern int platform_driver_register(struct platform_driver *); |
很显然,它“继承”自
struct device_driver ,同样类似于
struct device_driver ,一般我们需要实现probe函数,及指定platform_driver能驱动的设备的名字。
下面这个例子告诉你如何使用platoform总线,这是一个Android Goldfish GPIO驱动程序。它本身就是一个platform设备驱动(goldfish-gpio),同时,它又会向系统注新的设备(android-timed-gpio),这个新设备又被timed_output.c驱动程序驱动。
...... #include <linux/platform_device.h> struct platform_device timed_gpio_device = { .name = "android-timed-gpio ", .id = -1, .dev.platform_data = &timed_gpio_platform_data, }; static int goldfish_gpio_probe(struct platform_device *pdev) { struct goldfish_gpio_data *gpio_data; ...... error = platform_device_register(&timed_gpio_device); ...... return 0; } static int goldfish_gpio_remove(struct platform_device *pdev) { int i; struct goldfish_gpio_data *gpio_data; ...... platform_device_unregister( &timed_gpio_device ); ...... return 0; } static struct platform_driver goldfish_gpio_driver = { .probe = goldfish_gpio_probe, .remove = goldfish_gpio_remove, .driver = { .name = "goldfish-gpio" } }; static int __init goldfish_gpio_init(void) { return platform_driver_register(&goldfish_gpio_driver); } static void __exit goldfish_gpio_exit(void) { platform_driver_unregister(&goldfish_gpio_driver); } |
这个新注册的设备(timed_gpio_device)由timed_output驱动管理,通过浏览这段代码,你应该对如何使用platform总线有个全面的了解。(本想把全部code放在这里,但超过最大字数限制!)
static struct class *timed_gpio_class; struct timed_gpio_data { struct device *dev; struct hrtimer timer; spinlock_t lock; unsigned gpio; int max_timeout; u8 active_low; }; ...... static int android_timed_gpio_probe(struct platform_device *pdev) { struct timed_gpio_platform_data *pdata = pdev->dev.platform_data; struct timed_gpio *cur_gpio; struct timed_gpio_data *gpio_data, *gpio_dat; int i, ret = 0; ...... } static int android_timed_gpio_remove(struct platform_device *pdev) { } static struct platform_driver android_timed_gpio_driver = { .probe = android_timed_gpio_probe, .remove = android_timed_gpio_remove, .driver = { .name = "android-timed-gpio", .owner = THIS_MODULE, }, }; static int __init android_timed_gpio_init(void) { timed_gpio_class = class_create(THIS_MODULE, "timed_output"); if (IS_ERR(timed_gpio_class)) return PTR_ERR(timed_gpio_class); return platform_driver_register(&android_timed_gpio_driver); } static void __exit android_timed_gpio_exit(void) { class_destroy(timed_gpio_class); platform_driver_unregister(&android_timed_gpio_driver); } |
Kobject和kset
提到Linux的设备模型,就不得不提kobject和kset这两个内核对象,他们才是Linux内核设备模型的最基础的结构,但讲解他们却是一个枯燥过程,限于篇幅,这个就不作介绍了,请参考Linux文档<documentation/kobject.txt>。
后记
在这里,我们简单的介绍了Linux的设备模型,包括基本总线、设备、驱动的关系,同时也简单的介绍了Linux2.6内核的platform总线。这些内容应该足够让你了解如何使用Linux设备模型来管理设备了。