驱动模型核心,第一部分
在 2.5 Linux 内核开发系列中,Pat Mochel 创建了一个统一的设备驱动模型框架。这个框架由许多通用的结构体和函数组成,所有设备驱动子系统都已转换为使用它。它还包含一些通用的结构体,这些结构体开始在驱动代码之外被内核的其他部分使用。本文讨论了驱动模型的各个部分,并提供了一个如何将特定的设备驱动子系统转换为驱动模型的示例。
驱动框架将所有事物分解为总线、设备和类。使用这些基本元素,它可以控制驱动程序如何与物理和虚拟设备匹配,并向用户展示所有这些事物是如何互连的。
总线可以被描述为连接了设备的某种东西。总线的例子有 PCI、USB、i2c、PCMCIA 和 SCSI。通常只有一个总线驱动程序控制总线上的活动,它提供了一种从它所在的总线到它控制的总线的桥梁。
桥梁的一个例子是 USB 控制器,它位于 PCI 总线上。它作为 PCI 设备与 PCI 总线通信,并以 PCI 驱动程序的身份呈现给内核。但它控制着对特定 USB 总线的所有访问,与插入其中的不同 USB 设备进行通信。
总线在内核中用 struct bus_type 定义表示,定义位于 include/linux/device.h 中。系统中的所有总线都以子目录的形式显示在 sysfs 目录 /sys/bus/ 中,供用户查看。
设备是位于总线上的物理或虚拟设备。它们由 struct device 定义表示,并在总线看到它们存在于系统中时由总线创建。通常一次只有一个驱动程序控制一个特定的设备。它们可以在 /sys/devices 目录中以系统所有设备的巨型树状结构查看,也可以在 /sys/bus/总线类型/devices/ 目录中查看特定类型的设备。
设备还分配有驱动程序来控制如何通过特定的总线与设备通信。一些驱动程序知道如何与多个总线通信,例如 Tulip 网络驱动程序,它可以与 PCI 和 ISA Tulip 设备通信。所有驱动程序都由 struct device_driver 定义表示。它们可以在 sysfs 中的 /sys/bus/总线类型/drivers/ 中看到。驱动程序向特定的总线注册,并导出一个它们可以支持的不同类型设备的列表。总线根据这个导出的设备列表来匹配设备和驱动程序。该列表也导出到用户空间,以便可以使用 /sbin/hotplug 工具将驱动程序与尚未加载驱动程序的设备匹配。有关此接口及其工作原理的更多信息,请参阅我发表在 2002 年 4 月号 Linux Journal 上的文章“Hot Plug”[也可在 www.linuxjournal.com/article/5604 上找到]。
这里的类不是采用通用的面向对象定义,而是指为用户提供功能的东西。它们不是特定于总线或设备的东西,但在功能上对用户来说看起来是同类型的设备。类的例子有音频设备; pointing devices,如鼠标和触摸板;键盘;操纵杆; IDE 磁盘;和 tty 设备。内核一直都有这些类型的设备,传统上它们按主/次设备号范围分组在一起,以便用户可以轻松访问它们。类在内核中用 struct device_class 定义表示,它们可以作为子目录在 sysfs 目录 /sys/class/ 中查看。
有关整个驱动模型的描述,以及对驱动模型下执行所有实际工作的结构体的介绍,请参阅 www.kernel.org/pub/linux/kernel/people/mochel/doc/lca/driver-model-lca2003.tar.gz 上详尽的文档。它由 Pat Mochel 为 2003 年的 Linux.Conf.Au 会议编写。
以上所有描述在纸面上听起来都很棒,但是驱动模型实际上如何影响内核代码呢?为了展示这一点,让我们一起了解 i2c 驱动子系统是如何被修改以支持这个驱动模型的。
i2c 代码长期以来一直存在于主内核树之外,并且曾作为 2.0、2.2 和 2.4 内核的补丁提供。它也是 Simon G. Vogl(该代码的主要作者之一)的文章“Using the i2c Bus”的主题 [LJ,1997 年 3 月,www.linuxjournal.com/article/1342]。在 2.4 开发周期中,许多 i2c 核心文件和一些 i2c 总线驱动程序被接受到主内核中。在 2.5 开发周期中,又添加了一些驱动程序;希望所有这些驱动程序最终都将迁移到主树中。有关 i2c 代码、它支持的设备以及如何使用它的良好描述,请参阅主开发站点 secure.netroedge.com/~lm78/index.html。
加载后,与 i2c 控制器芯片通信的 i2c 总线驱动程序会在 /proc/bus 目录中导出许多文件。当 i2c 设备驱动程序加载并绑定到 i2c 设备时,它会在 /proc/sys/dev/sensors 目录中导出文件和目录。通过将设备和总线的表示移动到内核驱动核心,所有这些单独的文件都可以在 /sys 中正确的位置显示。
主要的 i2c 总线子系统需要在内核中声明并注册到驱动核心。为了实现这一点,以下代码被添加到 drivers/i2c/i2c-core.c 中
static int i2c_device_match(struct device *dev, struct device_driver *drv) { return 1; } struct bus_type i2c_bus_type = { .name = "i2c", .match = i2c_device_match, };
name 字段说明了总线应该被调用的名称,而 match 字段指向我们的匹配函数。目前,match 函数保持不变,无论何时驱动核心想要尝试将驱动程序与设备匹配,它总是返回 1。这个逻辑将在稍后进行修改。
然后,在 i2c 核心启动代码中,i2c_bus_type 通过调用以下代码注册
bus_register(&i2c_bus_type);
当 i2c 核心关闭时,添加了一个调用来注销这个总线
bus_unregister(&i2c_bus_type);当上述代码运行时,将在 sysfs 中创建以下树
$ tree /sys/bus/i2c/ /sys/bus/i2c/ |-- devices '-- drivers当 i2c 核心从系统中移除时,上述目录也会被移除。这就是创建 i2c 总线所需的全部内容。
i2c 总线本身很单调。现在,需要修改 i2c 总线适配器驱动程序,以便它们自己注册到这个总线。为此,将一个 struct device 变量添加到 struct i2c_adapter 结构体中
struct i2c_adapter { ..... struct device dev; };
to_i2c_adapter() 宏定义为
#define to_i2c_adapter(d) container_of(d, struct i2c_adapter, dev)当驱动核心将指向 struct device 的指针传递给 i2c 核心时,i2c 核心使用这个宏来获取指向实际 i2c_adapter 结构体的指针。
i2c_adapter 中的 struct device 是在结构体内部声明的完整变量,而不仅仅是指针。这样做是为了当驱动核心传递指向 struct device 的指针时,i2c 代码可以使用 to_i2c_adapter() 宏来获取指向实际 i2c_adapter 结构体的指针。
各个 struct i2c_driver 变量在不同的 i2c 总线驱动程序中声明。例如,在 i2c-piix4.c 驱动程序中,有一个名为 piix4_adapter 的变量,类型为 struct i2c_driver。当 i2c-piix4 驱动程序看到 PIIX4 适配器时,此变量在 i2c_add_adapter() 函数中传递给 i2c 核心。
在 i2c-piix4.c 驱动程序中,在调用 i2c_add_adapter() 之前,需要将指向 PIIX4 适配器的父设备的指针保存在 i2c_driver 结构体中。这可以通过一行代码完成
piix4_adapter.dev.parent = &dev->dev;
dev 是指向传递给 i2c-piix4 驱动程序的 PCI probe 函数的 struct pci_dev 的指针;PIIX4 是一个基于 PCI 的设备。
为了将 i2c_driver 变量链接到 sysfs 树,以下代码行被添加到 i2c_add_adapter() 函数中
/* add the adapter to the driver core. * The parent pointer should already * have been set up. */ sprintf(adap->dev.bus_id, "i2c-%d", i); strcpy(adap->dev.name, "i2c controller"); device_register(&adap->dev);
有了这段代码,当驱动程序检测到 PIIX4 设备时,将创建一个 i2c 总线树并链接到控制 PCI 设备
$ tree /sys/devices/pci0/00:07.3/i2c-0 /sys/devices/pci0/00:07.3/i2c-0 |-- name `-- power当 i2c-piix4 驱动程序卸载时,将调用 i2c_del_adapter() 函数。添加以下代码行以清理 i2c 总线设备
/* clean up the sysfs representation */ device_unregister(&adap->dev);
i2c 总线有许多不同的驱动程序,它们控制对位于 i2c 总线上的各种 i2c 设备的访问。这些驱动程序用 struct i2c_driver 结构体声明。在这个结构体中,添加了一个 struct device_driver 变量,以允许这些驱动程序注册到驱动核心
struct i2c_driver { ..... struct device_driver driver; };
并且,to_i2c_driver() 宏定义为
#define to_i2c_driver(d) container_of(d, struct i2c_driver, driver)i2c 驱动程序通过调用 i2c_add_driver() 将自己注册到 i2c 核心。为了为 i2c 驱动程序添加驱动核心支持,以下代码行被添加到此函数中
/* add the driver to the list of *i2c drivers in the driver core */ driver->driver.name = driver->name; driver->driver.bus = &i2c_bus_type; driver->driver.probe = i2c_device_probe; driver->driver.remove = i2c_device_remove; retval = driver_register(&driver->driver); if (retval) return retval;这设置了驱动核心结构,使其具有与驱动程序相同的名称和 i2c_bus_type 的总线类型;probe 和 remove 函数被设置为本地 i2c 函数。目前,这些函数被声明为
int i2c_device_probe(struct device *dev) { return -ENODEV; } int i2c_device_remove(struct device *dev) { return 0; }因为尚未添加 i2c 设备支持。这些函数将在 i2c 设备添加到驱动核心或从驱动核心移除时调用,但这将在下一篇文章中描述。
当调用 i2c_add_driver() 时,驱动程序将注册到 i2c_bus_type,并且它在 sysfs 中显示为
$ tree /sys/bus/i2c/ /sys/bus/i2c/ |-- devices `-- drivers |-- EEPROM READER `-- W83781D sensors
要从系统中移除 i2c 驱动程序,需要调用 i2c_del_driver() 函数。为了从通过调用 driver_register 注册的驱动核心中移除 i2c 驱动程序,以下代码行被添加到此函数中
driver_unregister(&driver->driver);
