本文测试系统为:Ubuntu 10.10 x86_64 2.6.35-24-generic
上节中,除了简单的框架,我们还看到了一个和总线驱动相关的结构体bus_type。这个结构体的定义在include/linux/device.h中。本节先简单介绍结构体中的成员,再对每个成员作详细描述。
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 (*resume)(struct device *dev); const struct dev_pm_ops *pm; struct bus_type_private *p; };
1. 成员简单介绍
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 (*resume)(struct device *dev); 恢复时调用。 const struct dev_pm_ops *pm; 设备电源管理。 struct bus_type_private *p; 私有数据。完全由驱动核心初始化并使用。
2. 成员详细描述
2.0 预备知识
在开始之前,我们先简单介绍一个结构体struct attribute。这个结构体作为属性的基本结构,嵌入在struct bus_attribute等结构体中。如下:
/* FIXME * The *owner field is no longer used. * x86 tree has been cleaned up. The owner * attribute is still left for other arches. */ struct attribute { const char *name; struct module *owner; mode_t mode; #ifdef CONFIG_DEBUG_LOCK_ALLOC struct lock_class_key *key; struct lock_class_key skey; #endif }; // 注:在2.6.37中,owner已经被移除了。
我们只关心name和mode成员。name作为属性文件名称,会出现在sysfs伪系统系统的特定目录下(具体目录和struct attribute所在的驱动相关),而mode是name文件的权限。 作为文件名称,name不能含有”/”,同时最好不含有空格,因为 shell分隔符IFS一般包含空格,空格对shell应用处理等会带来不便和错误。name值必须具有持久属性,例如静态字符数组或者字符串字面值(不能是栈内字符数组)。 mode和用户空间中文件的属性一致,可以针对所有者、同组、其他用户分别设置读、写、可执行权限等,需要注意的是,即使设置可执行权限,属性文件也是不允许执行的,最起码在当前的内核版本下是不允许执行的。
2.1 const char *name; 总线名称,体现在sysfs文件系统的/sys/bus下,不能和别的总线名称有冲突。作为文件名称,它不能含有”/”;同时最好不含有空格,值必须有持久性。例如: struct bus_type ycbus_type = { .name = “ycbus” };
2.2 struct bus_attribute *bus_attrs; 总线属性。表现为/sys/bus/<name>/文件夹下的文件。这个结构体的具体定义如下: struct bus_attribute { struct attribute attr; ssize_t (*show)(struct bus_type *bus, char *buf); ssize_t (*store)(struct bus_type *bus, const char *buf, size_t count); }; 其中struct attribute attr的如2.0所述。show和store方法的第一个参数都是struct bus_type指针,用来指示属性所在总线。 show方法实现数据读取。当用户空间读取属性值时,核心调用该方法实现编码,结果存放在形参buf中,注意大小不能超过过PAGE_SIZE。 store方法实现数据保存。当用户控件设置属性值时,核心调用该方法实现解码,使用buf传递的数据解码,count指示buf传递的数据长度。注意buf信息来自用户空间,因此在解码前应当检测数据合法性,如果数据格式或者数值和期望的不符,应该返回一个负的错误码,而不是采取不可预期或者无法恢复的动作。 另外一个需要注意的是,对于store方法,不能返回0,否则会产生死循环。因为如果store返回小于形参count,驱动核心会认为解码未完成,并以本次解码剩余的缓冲区继续调用store。我们假设一个最多一次只能解码4个字符的store函数,见如下代码调用 const char *p=”1234567890”; attrs->store(bus,p,10); 第一次返回5,驱动核心会接着调用: attrs->store(bus,p+4,10-4); attrs->store(bus,p+8,10-8); … 持续直到count为0。因此一个返回0的store会导致永久循环。 下文的设备属性和驱动属性的store有同样的限制。
2.3 struct device_attribute *dev_attrs; 默认设备属性。对于每个将要注册到该总线上的设备,在设备注册时,默认添加dev_attrs数组指定的属性。这个结构体定义如下: struct device_attribute { struct attribute attr; ssize_t (*show)(struct device *dev, struct device_attribute *attr, char *buf); ssize_t (*store)(struct device *dev, struct device_attribute *attr, const char *buf, size_t count); }; 这个结构体和struct bus_attribute的成员相类似。成员attr保存名称和权限,show和store方法分别在用户空间获取/设置属性值时调用。值得注意的是,此处show和store的函数接口,与struct bus_attribute中的完全不同。
2.4 struct driver_attribute *drv_attrs; 默认驱动属性。对于每个将要注册到该总线上的驱动,在驱动注册时,默认添加drv_attrs数组指定的属性。这个结构体定义如下: struct driver_attribute { struct attribute attr; ssize_t (*show)(struct device_driver *driver, char *buf); ssize_t (*store)(struct device_driver *driver, const char *buf, size_t count); }; 现在观看这个结构体,会觉得十分熟悉。如同struct bus_attribute或struct device_attribute,attr设定属性的名称和权限,show和store方法指定用户空间数据获取/设置的编解码算法。需要注意show和store有不同的形参列表。
2.5 int (*match)(struct device *dev, struct device_driver *drv); 匹配函数。 判定设备和驱动是否匹配,是总线体系相关的。驱动核心通过match和probe两个函数来完成匹配。其中match就是此处讨论的函数,而probe在下文提及。 每当有设备添加到总线时,驱动核心遍历总线上的驱动链表(执行流程device_register->device_add->bus_probe_device->device_attach->bus_for_each_drv->match/probe)查找设备驱动;每当有驱动添加到总线时,驱动核心遍历总线上的设备链表(执行流程driver_register->bus_add_driver->driver_attach->bus_for_each_dev->match/probe)查找驱动可操控的设备。当前,match一般只执行总线特定的匹配处理,而在probe中,通过回调设备驱动probe,完成设备特定的匹配、设备初始化等。举例来说,对于PCI总线,match判断驱动支持的ID列表是否包含设备ID,如果包含则匹配,否则失配;probe会再次执行ID匹配判断,并回调驱动提供的probe函数。 match匹配成功则返回1,失配返回0。match函数指针为NULL,驱动核心返回1。 对于一次遍历匹配而言,如果match和probe均成功,则结束匹配过程;如果match成功而probe失配,继续遍历查找匹配;如果遍历结束而没有找到成功的匹配,对于驱动而言表示没有可操控设备,对于设备而言表示没有适当的驱动。
2.6 int (*uevent)(struct device *dev, struct kobj_uevent_env *env); 在发送热插拔事件消息到用户空间之前添加环境变量。 在设备注册、移除,或者状态更改时,内核负责发送通知事件到用户空间。通过man udev和man udevd可获取相关帮助。uevent在事件发送到用户空间之前调用,用来给事件添加总线特定的环境变量。 在Linux-2.6.13以后,采用udev机制动态创建设备文件,本文暂时不讨论这个细节。 2.7 int (*probe)(struct device *dev); 探测函数。 如在match中所述,probe执行设备相关的匹配探测、设备初始化、资源分配等。 需要注意,在probe调用时,dev->driver已经被设置为match成功匹配的驱动指针了,因此不再需要struct device_driver指针。 2.8 int (*remove)(struct device *dev); 移除设备。 设备移除时,调用该方法,完成部分清理工作。如删除设备驱动中,设备链表下的该设备。 2.9 void (*shutdown)(struct device *dev); 系统关机。 系统关机时,调用该方法关闭设备。 2.10 int (*suspend)(struct device *dev, pm_message_t state); 设备休眠(挂起)。 设备休眠(挂起)时调用该方法。一般在该方法中设置设备为低耗电状态。 2.11 int (*resume)(struct device *dev); 设备恢复。 设备从休眠中恢复时调用该方法。 2.12 const struct dev_pm_ops *pm; 电源管理。 一些设备有电源状态转换。结构体内部提供很多方法实现这个过程。暂时忽略这个结构体。 2.13 struct bus_type_private *p; 总线私有数据。 驱动核心设置并使用,总线驱动不必关心这个成员,并且一般不要去修改它。 3. 示例代码
我们实现一个示例驱动,以ycbus为基础,添加一个设备ycbus-dev0和一个驱动ycbus-drv0。设置总线属性、默认设备属性和默认驱动属性,各自包含一个只读version属性和一个读写rw-test属性,因为共享驱动数据缓冲,其中一个设置rw-test另一个也更改。如下代码保存为ycbus.c
/* * ycbus: a software bus driver (virtual bus driver) * * a trivial ycbus driver */ #include <linux/device.h> #include <linux/module.h> /* * attributes helper */ static const char *attrs_rw_test(bool bset, const char *value, size_t l) { static char buf[64] = "rw-test-default"; if (bset) { if (value) { /* Despite of c-library snprintf, kernel snprintf will appended '/0' automatically */ if (l > 63) l = 63; memcpy(buf, value, l); buf[l] = '/0'; } else { buf[0] = '/0'; } } return buf; } static inline ssize_t attrs_version_show(const char *prefix, char *buf) { return snprintf(buf, PAGE_SIZE, "%s: version 1.0.0/n", prefix); } static inline ssize_t attrs_rw_test_show(const char *prefix, char *buf) { return snprintf(buf, PAGE_SIZE, "%s: %s/n", prefix, attrs_rw_test(false, NULL, 0)); } static inline ssize_t attrs_rw_test_store(const char *prefix, const char *buf, size_t count) { /* it will not be failed */ attrs_rw_test(true, buf, count); return count; } /* * bus attributes methods */ ssize_t bus_attrs_version_show(struct bus_type *bus, char *buf) { return attrs_version_show("ycbus", buf); } ssize_t bus_attrs_rw_test_show(struct bus_type *bus, char *buf) { return attrs_rw_test_show("ycbus", buf); } ssize_t bus_attrs_rw_test_store(struct bus_type *bus, const char *buf, size_t count) { return attrs_rw_test_store("ycbus", buf, count); } /* * device attribute methods */ ssize_t dev_attrs_version_show(struct device *dev, struct device_attribute *attr, char *buf) { return attrs_version_show("ycbus-dev0", buf); } ssize_t dev_attrs_rw_test_show(struct device *dev, struct device_attribute *attr, char *buf) { return attrs_rw_test_show("ycbus-dev0", buf); } ssize_t dev_attrs_rw_test_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { return attrs_rw_test_store("ycbus-dev0", buf, count); } /* * driver attribute */ ssize_t drv_attrs_version_show(struct device_driver *driver, char *buf) { return attrs_version_show("ycbus-drv0", buf); } ssize_t drv_attrs_rw_test_show(struct device_driver *drv, char *buf) { return attrs_rw_test_show("ycbus-drv0", buf); } ssize_t drv_attrs_rw_test_store(struct device_driver *drv, const char *buf, size_t count) { return attrs_rw_test_store("ycbus-drv0", buf, count); } void dev_release(struct device *dev) { } static struct bus_attribute bus_attrs[] = { __ATTR(version, S_IRUGO, bus_attrs_version_show, NULL), __ATTR(rw-test, (S_IRUGO|S_IWUGO), bus_attrs_rw_test_show, bus_attrs_rw_test_store), __ATTR_NULL, }; static struct device_attribute dev_attrs[] = { __ATTR(version, S_IRUGO, dev_attrs_version_show, NULL), __ATTR(rw-test, (S_IRUGO|S_IWUGO), dev_attrs_rw_test_show, dev_attrs_rw_test_store), __ATTR_NULL, }; struct driver_attribute drv_attrs[] = { __ATTR(version, S_IRUGO, drv_attrs_version_show, NULL), __ATTR(rw-test, (S_IRUGO|S_IWUGO), drv_attrs_rw_test_show, drv_attrs_rw_test_store), __ATTR_NULL, }; static struct bus_type ycbus_type = { .name = "ycbus", .bus_attrs = bus_attrs, .dev_attrs = dev_attrs, .drv_attrs = drv_attrs, }; static struct device ycbus_dev = { .init_name = "ycbus-dev0", .bus = &ycbus_type, }; static struct device_driver ycbus_drv = { .name = "ycbus-drv0", .bus = &ycbus_type, }; static int __init ycbus_driver_init(void) { int ret; printk(KERN_DEBUG "ycbus_driver_init/n"); ret = bus_register(&ycbus_type); if (ret) goto bus_fail; ret = device_register(&ycbus_dev); if (ret) goto dev_fail; ret = driver_register(&ycbus_drv); if (ret) goto drv_fail; return ret; drv_fail: device_unregister(&ycbus_dev); dev_fail: bus_unregister(&ycbus_type); bus_fail: return ret; } static void __exit ycbus_driver_exit(void) { printk(KERN_DEBUG "ycbus_driver_exit/n"); driver_unregister(&ycbus_drv); device_unregister(&ycbus_dev); bus_unregister(&ycbus_type); } MODULE_AUTHOR("yc <cppgp@qq.com>"); MODULE_DESCRIPTION("yc pseudo-bus driver"); MODULE_LICENSE("GPL"); module_init(ycbus_driver_init); module_exit(ycbus_driver_exit);
提供Makefile如下。保存为Makefile,注意大写首字母M。
# A trivial bus driver Makefile. Saved as “Makefile” exactly ifneq ($(KERNELRELEASE),) obj-m := ycbus.o else KERNDIR ?= /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) default: $(MAKE) -C ${KERNDIR} M=${PWD} modules endif clean: rm -rf modules.order Module.symvers .tmp_versions ycbus.ko .ycbus.ko.cmd ycbus.mod.c ycbus.mod.o .ycbus.mod.o.cmd ycbus.o .ycbus.o.cmd
注意 $(MAKE)和rm两行前是TAB而不是空格。
编译、加载、测试:
~$ make ~$ sudo insmod ycbus.ko ~$ tree /sys/bus/ycbus/ /sys/bus/ycbus/ ├── devices │ └── ycbus-dev0 -> ../../../devices/ycbus-dev0 ├── drivers │ └── ycbus-drv0 │ ├── bind │ ├── rw-test │ ├── uevent │ ├── unbind │ ├── version │ └── ycbus-dev0 -> ../../../../devices/ycbus-dev0 ├── drivers_autoprobe ├── drivers_probe ├── rw-test ├── uevent └── version 5 directories, 10 files ~$ tree /sys/devices/ycbus-dev0/ /sys/devices/ycbus-dev0/ ├── driver -> ../../bus/ycbus/drivers/ycbus-drv0 ├── power │ ├── control │ ├── runtime_active_time │ ├── runtime_status │ ├── runtime_suspended_time │ └── wakeup ├── rw-test ├── subsystem -> ../../bus/ycbus ├── uevent └── version 3 directories, 8 files $ tree /sys/bus/ycbus/drivers/ycbus-drv0/ /sys/bus/ycbus/drivers/ycbus-drv0/ ├── bind ├── rw-test ├── uevent ├── unbind ├── version └── ycbus-dev0 -> ../../../../devices/ycbus-dev0 1 directory, 5 files ~$ ls /sys/bus/ycbus/ -l total 0 drwxr-xr-x 2 root root 0 2011-04-19 11:39 devices drwxr-xr-x 3 root root 0 2011-04-19 11:38 drivers -rw-r--r-- 1 root root 4096 2011-04-19 11:39 drivers_autoprobe --w------- 1 root root 4096 2011-04-19 11:39 drivers_probe -rw-rw-rw- 1 root root 4096 2011-04-19 11:39 rw-test --w------- 1 root root 4096 2011-04-19 11:39 uevent -r--r--r-- 1 root root 4096 2011-04-19 11:39 version ~$ cat /sys/bus/ycbus/rw-test ycbus: rw-test-default ~$ cat /sys/bus/ycbus/devices/ycbus-dev0/rw-test ycbus-dev0: rw-test-default ~$ cat /sys/bus/ycbus/drivers/ycbus-drv0/rw-test ycbus-drv0: rw-test-default ~$ echo -n "set ycbus new value" > /sys/bus/ycbus/rw-test ~$ cat /sys/bus/ycbus/rw-test ycbus: set ycbus new value ~$ cat /sys/bus/ycbus/devices/ycbus-dev0/rw-test ycbus-dev0: set ycbus new value ~$ cat /sys/bus/ycbus/drivers/ycbus-drv0/rw-test ycbus-drv0: set ycbus new value ~$ echo -n "set ycbus-dev0 new value" > /sys/bus/ycbus/devices/ycbus-dev0/rw-test ~$ cat /sys/bus/ycbus/drivers/ycbus-drv0/rw-test ycbus-drv0: set ycbus-dev0 new value :~$ cat /sys/bus/ycbus/version ycbus: version 1.0.0 ~$ cat /sys/bus/ycbus/drivers/ycbus-drv0/version ycbus-drv0: version 1.0.0 ~$ cat /proc/kallsyms | grep ycbus e0aae280 t ycbus_driver_exit [ycbus] e0aae3c0 d ycbus_drv [ycbus] e0aae400 d ycbus_dev [ycbus] e0aae540 d ycbus_type [ycbus] e0aae580 d buf.15065 [ycbus] e0aae5c0 d bus_attrs [ycbus] e0aae600 d dev_attrs [ycbus] e0aae640 d __this_module [ycbus] e0aae1b0 t bus_attrs_rw_test_show [ycbus] e0aae280 t cleanup_module [ycbus] e0aae010 t drv_attrs_rw_test_store [ycbus] e0aae0d0 t bus_attrs_rw_test_store [ycbus] e0aae000 t dev_release [ycbus] e0aae220 t dev_attrs_version_show [ycbus] e0aae070 t dev_attrs_rw_test_store [ycbus] e0aae380 d drv_attrs [ycbus] e0aae1f0 t drv_attrs_version_show [ycbus] e0aae250 t bus_attrs_version_show [ycbus] e0aae170 t dev_attrs_rw_test_show [ycbus] e0aae130 t drv_attrs_rw_test_show [ycbus]
代码很好理解,只是增加了一个虚拟设备和一个驱动到总线上,因为我们并未提供match和probe,而核心的默认处理是认为设备和驱动是匹配的(对于match和probe为空,核心直接返回1),因此它们是匹配的。可以在ycbus-dev0设备下看到驱动,也可以在ycbus-drv0下看到设备。另外,一个需要注意的问题是,注册一个设备必须提供release方法,否则核心会发出抱怨,并打印Call Trace信息。