本文测试系统为: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, structdevice_driver *drv);
int (*uevent)(struct device *dev, structkobj_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);
conststruct dev_pm_ops *pm;
struct bus_type_private *p;
};
1. 成员简单介绍
const char*name;
总线名称。
structbus_attribute *bus_attrs;
总线属性。
structdevice_attribute *dev_attrs;
该总线上所有设备的默认属性。
structdriver_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;
设备电源管理。
structbus_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 otherarches.
*/
struct attribute{
const char *name;
struct module *owner;
mode_t mode;
#ifdefCONFIG_DEBUG_LOCK_ALLOC
struct lock_class_key *key;
struct lock_class_key skey;
#endif
};
// 注:在2.6.37中,owner已经被移除了。
我们只关心name和mode成员。name作为属性文件名称,会出现在sysfs伪系统系统的特定目录下(具体目录和structattribute所在的驱动相关),而mode是name文件的权限。
作为文件名称,name不能含有”/”,同时最好不含有空格,因为 shell分隔符IFS一般包含空格,空格对shell应用处理等会带来不便和错误。name值必须具有持久属性,例如静态字符数组或者字符串字面值(不能是栈内字符数组)。
mode和用户空间中文件的属性一致,可以针对所有者、同组、其他用户分别设置读、写、可执行权限等,需要注意的是,即使设置可执行权限,属性文件也是不允许执行的,最起码在当前的内核版本下是不允许执行的。
2.1 const char*name;
总线名称,体现在sysfs文件系统的/sys/bus下,不能和别的总线名称有冲突。作为文件名称,它不能含有”/”;同时最好不含有空格,值必须有持久性。例如:
struct bus_typeycbus_type = { .name = “ycbus” };
2.2 structbus_attribute *bus_attrs;
总线属性。表现为/sys/bus/<name>/文件夹下的文件。这个结构体的具体定义如下:
structbus_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 structdevice_attribute *dev_attrs;
默认设备属性。对于每个将要注册到该总线上的设备,在设备注册时,默认添加dev_attrs数组指定的属性。这个结构体定义如下:
structdevice_attribute {
struct attribute attr;
ssize_t (*show)(struct device *dev, structdevice_attribute *attr,
char *buf);
ssize_t (*store)(struct device *dev, structdevice_attribute *attr,
const char *buf, size_tcount);
};
这个结构体和struct bus_attribute的成员相类似。成员attr保存名称和权限,show和store方法分别在用户空间获取/设置属性值时调用。值得注意的是,此处show和store的函数接口,与struct bus_attribute中的完全不同。
2.4 structdriver_attribute *drv_attrs;
默认驱动属性。对于每个将要注册到该总线上的驱动,在驱动注册时,默认添加drv_attrs数组指定的属性。这个结构体定义如下:
structdriver_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 conststruct dev_pm_ops *pm;
电源管理。
一些设备有电源状态转换。结构体内部提供很多方法实现这个过程。暂时忽略这个结构体。
2.13 structbus_type_private *p;
总线私有数据。
驱动核心设置并使用,总线驱动不必关心这个成员,并且一般不要去修改它。
3. 示例代码
我们实现一个示例驱动,以ycbus为基础,添加一个设备ycbus-dev0和一个驱动ycbus-drv0。设置总线属性、默认设备属性和默认驱动属性,各自包含一个只读version属性和一个读写rw-test属性,因为共享驱动数据缓冲,其中一个设置rw-test另一个也更改。如下代码保存为ycbus.c
/*
* ycbus: a software bus driver (virtual busdriver)
*
* a trivial ycbus driver
*/
#include<linux/device.h>
#include<linux/module.h>
/*
* attributes helper
*/
static constchar *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, kernelsnprintf will appended '\0' automatically */
if (l > 63) l = 63;
memcpy(buf, value, l);
buf[l] = '\0';
}
else
{
buf[0] = '\0';
}
}
return buf;
}
static inlinessize_t attrs_version_show(const char *prefix, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%s:version 1.0.0\n", prefix);
}
static inlinessize_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 inlinessize_t attrs_rw_test_store(const char *prefix, const char *buf, size_tcount)
{
/* it will not be failed */
attrs_rw_test(true, buf, count);
return count;
}
/*
* bus attributes methods
*/
ssize_tbus_attrs_version_show(struct bus_type *bus, char *buf)
{
return attrs_version_show("ycbus",buf);
}
ssize_tbus_attrs_rw_test_show(struct bus_type *bus, char *buf)
{
return attrs_rw_test_show("ycbus",buf);
}
ssize_t bus_attrs_rw_test_store(structbus_type *bus, const char *buf, size_t count)
{
return attrs_rw_test_store("ycbus",buf, count);
}
/*
* device attribute methods
*/
ssize_tdev_attrs_version_show(struct device *dev, struct device_attribute *attr, char*buf)
{
returnattrs_version_show("ycbus-dev0", buf);
}
ssize_tdev_attrs_rw_test_show(struct device *dev, struct device_attribute *attr, char*buf)
{
returnattrs_rw_test_show("ycbus-dev0", buf);
}
ssize_tdev_attrs_rw_test_store(struct device *dev, struct device_attribute *attr,const char *buf, size_t count)
{
returnattrs_rw_test_store("ycbus-dev0", buf, count);
}
/*
* driver attribute
*/
ssize_tdrv_attrs_version_show(struct device_driver *driver, char *buf)
{
returnattrs_version_show("ycbus-drv0", buf);
}
ssize_tdrv_attrs_rw_test_show(struct device_driver *drv, char *buf)
{
returnattrs_rw_test_show("ycbus-drv0", buf);
}
ssize_tdrv_attrs_rw_test_store(struct device_driver *drv, const char *buf, size_tcount)
{
returnattrs_rw_test_store("ycbus-drv0", buf, count);
}
voiddev_release(struct device *dev)
{
}
static structbus_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 structdevice_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,
};
structdriver_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 structbus_type ycbus_type = {
.name = "ycbus",
.bus_attrs = bus_attrs,
.dev_attrs = dev_attrs,
.drv_attrs = drv_attrs,
};
static structdevice ycbus_dev = {
.init_name = "ycbus-dev0",
.bus = &ycbus_type,
};
static structdevice_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("ycpseudo-bus driver");
MODULE_LICENSE("GPL");
module_init(ycbus_driver_init);
module_exit(ycbus_driver_exit);
提供Makefile如下。保存为Makefile,注意大写首字母M。
# A trivial busdriver 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.cmdycbus.o .ycbus.o.cmd
注意 $(MAKE)和rm两行前是TAB而不是空格。
编译、加载、测试:
~$ make
~$ sudo insmodycbus.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, 8files
$ 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, 5files
~$ ls/sys/bus/ycbus/ -l
total 0
drwxr-xr-x 2root root 0 2011-04-19 11:39devices
drwxr-xr-x 3root root 0 2011-04-19 11:38drivers
-rw-r--r-- 1root root 4096 2011-04-19 11:39 drivers_autoprobe
--w------- 1root root 4096 2011-04-19 11:39 drivers_probe
-rw-rw-rw- 1root root 4096 2011-04-19 11:39 rw-test
--w------- 1 rootroot 4096 2011-04-19 11:39 uevent
-r--r--r-- 1root 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 ycbusnew value
~$ cat/sys/bus/ycbus/devices/ycbus-dev0/rw-test
ycbus-dev0: setycbus new value
~$ cat/sys/bus/ycbus/drivers/ycbus-drv0/rw-test
ycbus-drv0: setycbus 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: setycbus-dev0 new value
:~$ cat/sys/bus/ycbus/version
ycbus: version1.0.0
~$ cat/sys/bus/ycbus/drivers/ycbus-drv0/version
ycbus-drv0:version 1.0.0
~$ cat/proc/kallsyms | grep ycbus
e0aae280 tycbus_driver_exit [ycbus]
e0aae3c0 dycbus_drv [ycbus]
e0aae400 dycbus_dev [ycbus]
e0aae540 dycbus_type [ycbus]
e0aae580 dbuf.15065 [ycbus]
e0aae5c0 dbus_attrs [ycbus]
e0aae600 ddev_attrs [ycbus]
e0aae640 d__this_module [ycbus]
e0aae1b0 tbus_attrs_rw_test_show [ycbus]
e0aae280 tcleanup_module [ycbus]
e0aae010 tdrv_attrs_rw_test_store [ycbus]
e0aae0d0 tbus_attrs_rw_test_store [ycbus]
e0aae000 tdev_release [ycbus]
e0aae220 tdev_attrs_version_show [ycbus]
e0aae070 tdev_attrs_rw_test_store [ycbus]
e0aae380 ddrv_attrs [ycbus]
e0aae1f0 tdrv_attrs_version_show [ycbus]
e0aae250 tbus_attrs_version_show [ycbus]
e0aae170 tdev_attrs_rw_test_show [ycbus]
e0aae130 tdrv_attrs_rw_test_show [ycbus]
代码很好理解,只是增加了一个虚拟设备和一个驱动到总线上,因为我们并未提供match和probe,而核心的默认处理是认为设备和驱动是匹配的(对于match和probe为空,核心直接返回1),因此它们是匹配的。可以在ycbus-dev0设备下看到驱动,也可以在ycbus-drv0下看到设备。另外,一个需要注意的问题是,注册一个设备必须提供release方法,否则核心会发出抱怨,并打印Call Trace信息。