全部博文(2759)
分类: LINUX
2013-09-29 13:28:11
原文地址:linux设备驱动模型浅析3--设备热插拔的支持 作者:byr2006
热插拔(hotplug,打这个词的时候我常常想到热干面)不一定非要指类似U盘那样的插入拔出,此处的热插拔广义上讲,是指一个设备加入系统,内核如何通知用户空间。举个简单的例子,如果你的电脑中有块PCI网卡,针对该网卡的驱动程序以内核模块的形式被编译(obj-m),那么Linux系统在启动过程中是如何自动加载该网卡的驱动模块呢?大家都知道现在udev负责干这事,其实除了udev,还可以有其他的手法,你自己就可以这样做。
当然设备驱动程序一般不会和这些太底层的kobject/kset家伙打交道,因为更高层次的device,bus和driver把kobject/kset那一层的细节实现都给封装了起来。所以设备热插拔的uevent事件最终的源头来自于device_add,本帖这里肯定不会讨论device与driver如何绑定那一摊子事情。下面看看device_add的源码,是如何实现uevent机制的:
1kobject, ktype, kset
kobject代表sysfs中的目录。
ktype代表kobject的类型,主要包含release函数和attr的读写函数。比如,所有的bus都有同一个bus_type;所有的class都有同一个class_type。
kset包含了subsystem概念,kset本身也是一个kobject,所以里面包含了一个kobject对象。另外,kset中包含kset_uevent_ops,里面主要定义了三个函数
int (*filter)(struct kset *kset, struct kobject *kobj);
const char *(*name)(struct kset *kset, struct kobject *kobj);
int (*uevent)(struct kset *kset, struct kobject *kobj, struct kobj_uevent_env *env);
这三个函数都与uevent相关。filter用于判断uevent是否要发出去。name用于得到subsystem的名字。uevent用于填充env变量。
2device_add流程(uevent内核部分)
int device_add(struct device *dev)
{
...
kobject_uevent(&dev->kobj, KOBJ_ADD);
...
}
热插拔的核心实现就那一个函数调用,这里device_add对应的是KOBJ_ADD,那么移除设备自然对应KOBJ_REMOVE了。kobject_uevent函数最终调用的是kobject_uevent_env,后者才是真正干事的伙计。
下面给出kobject_uevent_env函数的核心框架:
int kobject_uevent_env(struct kobject *kobj, enum kobject_action action,
char *envp_ext[])
{
:
:
/* search the kset we belong to */
top_kobj = kobj;
while (!top_kobj->kset && top_kobj->parent)
top_kobj = top_kobj->parent;
if (!top_kobj->kset) {
pr_debug("kobject attempted to send uevent without kset!\n");
return -EINVAL;
}
kset = top_kobj->kset;
uevent_ops = kset->uevent_ops;
/* skip the event, if the filter returns zero. */
//如果kset中有filter函数,调用filter函数,看看是否需要过滤uevent消息。
if (uevent_ops && uevent_ops->filter)
if (!uevent_ops->filter(kset, kobj)) {
pr_debug("kobject filter function caused the event to drop!\n");
return 0;
}
/* originating subsystem */
//如果kset中有name函数,调用name函数得到subsystem的名字;否则,subsystem的名字是
//kset中kobject的名字。
if (uevent_ops && uevent_ops->name)
subsystem = uevent_ops->name(kset, kobj);
else
subsystem = kobject_name(&kset->kobj);
if (!subsystem) {
pr_debug("unset subsystem caused the event to drop!\n");
return 0;
}
/* environment buffer */
env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL);
if (!env)
return -ENOMEM;
/* complete object path */
devpath = kobject_get_path(kobj, GFP_KERNEL);
if (!devpath) {
retval = -ENOENT;
goto exit;
}
/* default keys */
//增加环境变量ACTION=
retval = add_uevent_var(env, "ACTION=%s", action_string);
if (retval)
goto exit;
//增加环境变量DEVPATH=
retval = add_uevent_var(env, "DEVPATH=%s", devpath);
if (retval)
goto exit;
//增加环境变量SUBSYSTEM=
retval = add_uevent_var(env, "SUBSYSTEM=%s", subsystem);
if (retval)
goto exit;
/* keys passed in from the caller */
//增加环境变量kobject_uevent_env中参数envp_ext指定的环境变量。
if (envp_ext) {
for (i = 0; envp_ext[i]; i++) {
retval = add_uevent_var(env, envp_ext[i]);
if (retval)
goto exit;
}
}
/* let the kset specific function add its stuff */
//调用kset的uevent函数,这个函数会继续填充环境变量。
if (uevent_ops && uevent_ops->uevent) {
retval = uevent_ops->uevent(kset, kobj, env);
if (retval) {
pr_debug ("%s - uevent() returned %d\n",
__FUNCTION__, retval);
goto exit;
}
}
/* we will send an event, so request a new sequence number */
spin_lock(&sequence_lock);
//增加环境变量SEQNUM=
seq = ++uevent_seqnum;
spin_unlock(&sequence_lock);
retval = add_uevent_var(env, "SEQNUM=%llu", (unsigned long long)seq);
if (retval)
goto exit;
//调用netlink发送uevent消息。
#if defined(CONFIG_NET)
/* send netlink message */
if (uevent_sock) {
struct sk_buff *skb;
size_t len;
/* allocate message with the maximum possible size */
len = strlen(action_string) + strlen(devpath) + 2;
skb = alloc_skb(len + env->buflen, GFP_KERNEL);
if (skb) {
char *scratch;
/* add header */
scratch = skb_put(skb, len);
sprintf(scratch, "%s@%s", action_string, devpath);
/* copy keys to our continuous event payload buffer */
for (i = 0; i < env->envp_idx; i++) {
len = strlen(env->envp[i]) + 1;
scratch = skb_put(skb, len);
strcpy(scratch, env->envp[i]);
}
NETLINK_CB(skb).dst_group = 1;
netlink_broadcast(uevent_sock, skb, 0, 1, GFP_KERNEL);
}
}
#endif
/* call uevent_helper, usually only enabled during early boot */
//调用uevent_helper,最终转换成对用户空间sbin/mdev的调用。
if (uevent_helper[0]) {
char *argv [3];
argv [0] = uevent_helper;
argv [1] = (char *)subsystem;
argv [2] = NULL;
retval = add_uevent_var(env, "HOME=/");
if (retval)
goto exit;
retval = add_uevent_var(env, "PATH=/sbin:/bin:/usr/sbin:/usr/bin");
if (retval)
goto exit;
call_usermodehelper (argv[0], argv, env->envp, UMH_WAIT_EXEC);
}
exit:
kfree(devpath);
kfree(env);
return retval;
}
3uevent的用户空间部分
uevent的用户空间程序有两个,一个是udev,一个是mdev。
udev通过netlink监听uevent消息,它能完成两个功能:
1.自动加载模块
2.根据uevent消息在dev目录下添加、删除设备节点。
另一个是mdev,mdev在busybox的代码包中能找到,它通过上节提到的uevent_helper函数被调用。
下面简要介绍udev的模块自动加载过程:
etc目录下有一个uevent规则文件/etc/udev/rules.d/50-udev.rules
udev程序收到uevent消息后,在这个规则文件里匹配,如果匹配成功,则执行这个匹配定义的shell命令。例如,规则文件里有这么一行:
ACTION=="add", SUBSYSTEM=="?*", ENV{MODALIAS}=="?*", RUN+="/sbin/modprobe $env{MODALIAS}"
所以,当收到uevent的add事件后,shell能自动加载在MODALIAS中定义的模块。
mdev的模块自动加载过程与之类似,它的配置文件在/etc/mdev.conf中。例如:
$MODALIAS=.* 0:0 660 @modprobe "$MODALIAS"
这条规则指的是:当收到的环境变量中含有MODALIAS,那么加载MODALIAS代表的模块。
mdev的详细说明在busybox的docs/mdev.txt中。
4.uevent在设备驱动模型中的应用
在sys目录下有一个子目录devices,代表一个kset。
创建设备时,调用的device_initialize函数中,默认会把kset设置成devices_kset,即devices子目录代表的kset。
devices_kset中设置了uevent操作集device_uevent_ops。
static struct kset_uevent_ops device_uevent_ops = {
.filter = dev_uevent_filter,
.name = dev_uevent_name,
.uevent = dev_uevent,
};
dev_uevent_filter中,主要是规定了要想发送uevent,dev必须有class或者bus。
dev_uevent_name中,返回dev的class或者bus的名字。
dev_uevent函数:
如果dev有设备号,添加环境变量MAJOR与MINOR。
如果dev->type有值,设置DEVTYPE=
如果dev->driver,设置DRIVER=
如果有bus,调用bus的uevent函数。
如果有class,调用class的uevent函数。
如果有dev->type,调用dev->type->uevent函数。
一般在bus的uevent函数中,都会添加MODALIAS环境变量,设置成dev的名字。这样,uevent传到用户空间后,就可以通过对MODALIAS的匹配自动加载模块。这样的bus例子有platform和I2C等等。