udev 和mdev 是两个使用uevent 机制处理热插拔问题的用户空间程序,两者的实现机理不同。
udev 是基于netlink 机制的,它在系统启动时运行了一个deamon 程序udevd,通过监听内核发送的uevent 来执行相应的热拔插动作,包括创建/删除设备节点,加载/卸载驱动模块等等。
mdev 是基于uevent_helper 机制的,它在系统启动时修改了内核中的uevnet_helper 变量(通过写/proc/sys/kernel/hotplug),值为“/sbin/mdev”。这样内核产生uevent 时会调用uevent_helper 所指的用户级程序,也就是mdev,来执行相应的热拔插动作。
udev 使用的netlink 机制在有大量uevent 的场合效率高,适合用在PC 机上;
而mdev 使用的uevent_helper 机制实现简单,适合用在嵌入式系统中。
另外要说明的一点是,uevent_helper 的初始值在内核编译时时可配置的,默认值为/sbin/hotplug。如果想修改它的值,写/proc/sys/kernel/hotplug 文件就可以了,例如:
echo "/sbin/mdev" > /proc/sys/kernel/hotplug
mdev有两个主要用途:初始人口和动态更新。 都在内核中需要sysfs支持并将其挂载在/ sys。 对于动态更新,还需要在内核中启用热插拔。
下面给出init脚本的典型代码片段:
[0] mount -t proc proc /proc
[1] mount -t sysfs sysfs /sys
[2] echo /sbin/mdev > /proc/sys/kernel/hotplug
[3] mdev -s
如果没有procfs,上述内容将成为:
[1] mount -t sysfs sysfs /sys
[2] sysctl -w kernel.hotplug = /sbin/mdev
[3] mdev -s
当然,更加“完整”的设置需要在前一个代码片段之前执行此操作:
[4] mount -t tmpfs -o size=64k,mode=0755 tmpfs /dev
[5] mkdir /dev/pts
[6] mount -t devpts devpts /dev/pts
这里简单的解释是[1]你需要在执行mdev之前安装/ sys。 然后你[2]指示内核在添加或删除设备时执行/ sbin / mdev,以便可以创建或销毁设备节点。 然后用[3]创建/ dev与系统启动时创建的所有设备节点。
对于“完整”设置,[4]确保/dev是一个tmpfs文件系统(假设您的闪存用完了)。 然后你想[5]创建/ dev / pts挂载点,最后[6]挂载devpts文件系统。
这里可以看一下我的文件系统的启动设置。
# /etc/fstab: static file system information.
#
# Use 'vol_id --uuid' to print the universally unique identifier for a
# device; this may be used with UUID= as a more robust way to name devices
# that works even if disks are added and removed. See fstab(5).
#
# <file system> <mount point> <type> <options> <dump> <pass>
proc /proc proc defaults 0 0
sysfs /sys sysfs defaults 0 0
tmpfs /var tmpfs defaults 0 0
tmpfs /tmp tmpfs defaults 0 0
tmpfs /dev tmpfs defaults 0 0
挂载文件系统
在启动脚本,中指定内核中使用的热插拔程序
/etc/init.d/rcS
#echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s
这里我们看一下mdev的软件代码。
int mdev_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int mdev_main(int argc UNUSED_PARAM, char **argv)
{
RESERVE_CONFIG_BUFFER(temp, PATH_MAX + SCRATCH_SIZE);
INIT_G();
#if ENABLE_FEATURE_MDEV_CONF
G.filename = "/etc/mdev.conf";
#endif
/* We can be called as hotplug helper */
/* Kernel cannot provide suitable stdio fds for us, do it ourself */
bb_sanitize_stdio();
/* Force the configuration file settings exactly */
umask(0);
xchdir("/dev");
if (argv[1] && strcmp(argv[1], "-s") == 0) {
/*
* Scan: mdev -s
*/
struct stat st;
#if ENABLE_FEATURE_MDEV_CONF
/* Same as xrealloc_vector(NULL, 4, 0): */
G.rule_vec = xzalloc((1 << 4) * sizeof(*G.rule_vec));
#endif
xstat("/", &st);
G.root_major = major(st.st_dev);
G.root_minor = minor(st.st_dev);
putenv((char*)"ACTION=add");
/* Create all devices from /sys/dev hierarchy */
recursive_action("/sys/dev",
ACTION_RECURSE | ACTION_FOLLOWLINKS,
fileAction, dirAction, temp, 0);
} else {
char *fw;
char *seq;
char *action;
char *env_devname;
char *env_devpath;
unsigned my_pid;
unsigned seqnum = seqnum; /* for compiler */
int seq_fd;
smalluint op;
/* Hotplug:
* env ACTION=... DEVPATH=... SUBSYSTEM=... [SEQNUM=...] mdev
* ACTION can be "add", "remove", "change"
* DEVPATH is like "/block/sda" or "/class/input/mice"
*/
env_devname = getenv("DEVNAME"); /* can be NULL */
G.subsystem = getenv("SUBSYSTEM");
action = getenv("ACTION");
env_devpath = getenv("DEVPATH");
if (!action || !env_devpath /*|| !G.subsystem*/)
bb_show_usage();
fw = getenv("FIRMWARE");
seq = getenv("SEQNUM");
op = index_in_strings(keywords, action);
my_pid = getpid();
open_mdev_log(seq, my_pid);
seq_fd = -1;
if (seq) {
seqnum = atoll(seq);
seq_fd = wait_for_seqfile(seqnum);
}
dbg1("%s "
"ACTION:%s SUBSYSTEM:%s DEVNAME:%s DEVPATH:%s"
"%s%s",
curtime(),
action, G.subsystem, env_devname, env_devpath,
fw ? " FW:" : "", fw ? fw : ""
);
snprintf(temp, PATH_MAX, "/sys%s", env_devpath);
if (op == OP_remove) {
/* Ignoring "remove firmware". It was reported
* to happen and to cause erroneous deletion
* of device nodes. */
if (!fw)
make_device(env_devname, temp, op);
}
else {
make_device(env_devname, temp, op);
if (ENABLE_FEATURE_MDEV_LOAD_FIRMWARE) {
if (op == OP_add && fw)
load_firmware(fw, temp);
}
}
dbg1("%s exiting", curtime());
if (seq_fd >= 0) {
xwrite_str(seq_fd, utoa(seqnum + 1));
signal_mdevs(my_pid);
}
}
if (ENABLE_FEATURE_CLEAN_UP)
RELEASE_CONFIG_BUFFER(temp);
return EXIT_SUCCESS;
}
检查查看后我们可以发现,这个应用程序主要执行三件事
- 切换到/dev目录[xchdir("/dev")]
- 依据第一个参数,是不是-s讲应用分为两部分。第一部分,shell中执行mdev -s命令这个一般在系统启动后执行,主要是扫描/sys/dev目录下的char和block目录,并根据这里面的的设备号和名字,在/dev目录下创建所有设备驱动的节点。
- 第二种情况就是第一个参数不是-s,那就是转们给某一个设备创建设备节点。
可以看到,/sys/dev目录下都是以注册设备为名字的,符号链接文件。
当人这个符号连接里面的uevent就是存放,设备的信息。
可以看到上面这个例子。
下面就是这部分的应用程序代码了
if (argv[1] && strcmp(argv[1], "-s") == 0) {
/*
* Scan: mdev -s
*/
struct stat st;
#if ENABLE_FEATURE_MDEV_CONF
/* Same as xrealloc_vector(NULL, 4, 0): */
G.rule_vec = xzalloc((1 << 4) * sizeof(*G.rule_vec));
#endif
xstat("/", &st);
G.root_major = major(st.st_dev);
G.root_minor = minor(st.st_dev);
putenv((char*)"ACTION=add");
/* Create all devices from /sys/dev hierarchy */
recursive_action("/sys/dev",
ACTION_RECURSE | ACTION_FOLLOWLINKS,
fileAction, dirAction, temp, 0);
} else {
xstat("/", &st);
G.root_major = major(st.st_dev);
G.root_minor = minor(st.st_dev);
上面这句为获取根文件系统所在的设备标识
/* Create all devices from /sys/dev hierarchy */
recursive_action("/sys/dev",
ACTION_RECURSE | ACTION_FOLLOWLINKS,
fileAction, dirAction, temp, 0);
这个函数是递归函数,它会把/sys/dev目录下的所有文件文件夹都去查看一遍,如果发现dev文件,那么将按照/etc/mdev.conf文件进行相应的配置。如果没有配置文件,那么直接创建设备节点。
这里列出代码,不进行升入分析,只需要知道他的目的是为了扫描/dev目录,根据每个目录里面的uevent里面的参数,执行mknod命令来创建设备就行。
/* fileName is (l)stat'ed (depending on ACTION_FOLLOWLINKS[_L0]).
*
* If it is a file: fileAction in run on it, its return value is returned.
*
* In case we are in a recursive invocation (see below):
* normally, fileAction should return 1 (TRUE) to indicate that
* everything is okay and processing should continue.
* fileAction return value of 0 (FALSE) on any file in directory will make
* recursive_action() also return 0, but it doesn't stop directory traversal
* (fileAction/dirAction will be called on each file).
*
* [TODO: maybe introduce -1 to mean "stop traversal NOW and return"]
*
* If it is a directory:
*
* If !ACTION_RECURSE, dirAction is called and its
* return value is returned from recursive_action(). No recursion.
*
* If ACTION_RECURSE, directory is opened, and recursive_action() is called
* on each file/subdirectory.
* If any one of these calls returns 0, current recursive_action() returns 0.
*
* If !ACTION_DEPTHFIRST, dirAction is called before recurse.
* Return value of 0 (FALSE) is an error: prevents recursion,
* the warning is printed (unless ACTION_QUIET) and recursive_action() returns 0.
* Return value of 2 (SKIP) prevents recursion, instead recursive_action()
* returns 1 (TRUE, no error).
*
* If ACTION_DEPTHFIRST, dirAction is called after recurse.
* If it returns 0, the warning is printed and recursive_action() returns 0.
*
* ACTION_FOLLOWLINKS mainly controls handling of links to dirs.
* 0: lstat(statbuf). Calls fileAction on link name even if points to dir.
* 1: stat(statbuf). Calls dirAction and optionally recurse on link to dir.
*/
int FAST_FUNC recursive_action(const char *fileName,
unsigned flags,
int FAST_FUNC (*fileAction)(const char *fileName, struct stat *statbuf, void* userData, int depth),
int FAST_FUNC (*dirAction)(const char *fileName, struct stat *statbuf, void* userData, int depth),
void* userData,
unsigned depth)
{
struct stat statbuf;
unsigned follow;
int status;
DIR *dir;
struct dirent *next;
if (!fileAction) fileAction = true_action;
if (!dirAction) dirAction = true_action;
follow = ACTION_FOLLOWLINKS;
if (depth == 0)
follow = ACTION_FOLLOWLINKS | ACTION_FOLLOWLINKS_L0;
follow &= flags;
status = (follow ? stat : lstat)(fileName, &statbuf);
if (status < 0) {
#ifdef DEBUG_RECURS_ACTION
bb_error_msg("status=%d flags=%x", status, flags);
#endif
if ((flags & ACTION_DANGLING_OK)
&& errno == ENOENT
&& lstat(fileName, &statbuf) == 0
) {
/* Dangling link */
return fileAction(fileName, &statbuf, userData, depth);
}
goto done_nak_warn;
}
/* If S_ISLNK(m), then we know that !S_ISDIR(m).
* Then we can skip checking first part: if it is true, then
* (!dir) is also true! */
if ( /* (!(flags & ACTION_FOLLOWLINKS) && S_ISLNK(statbuf.st_mode)) || */
!S_ISDIR(statbuf.st_mode)
) {
return fileAction(fileName, &statbuf, userData, depth);
}
/* It's a directory (or a link to one, and followLinks is set) */
if (!(flags & ACTION_RECURSE)) {
return dirAction(fileName, &statbuf, userData, depth);
}
if (!(flags & ACTION_DEPTHFIRST)) {
status = dirAction(fileName, &statbuf, userData, depth);
if (status == FALSE)
goto done_nak_warn;
if (status == SKIP)
return TRUE;
}
dir = opendir(fileName);
if (!dir) {
/* findutils-4.1.20 reports this */
/* (i.e. it doesn't silently return with exit code 1) */
/* To trigger: "find -exec rm -rf {} \;" */
goto done_nak_warn;
}
status = TRUE;
while ((next = readdir(dir)) != NULL) {
char *nextFile;
int s;
nextFile = concat_subpath_file(fileName, next->d_name);
if (nextFile == NULL)
continue;
/* process every file (NB: ACTION_RECURSE is set in flags) */
s = recursive_action(nextFile, flags, fileAction, dirAction,
userData, depth + 1);
if (s == FALSE)
status = FALSE;
free(nextFile);
//#define RECURSE_RESULT_ABORT -1
// if (s == RECURSE_RESULT_ABORT) {
// closedir(dir);
// return s;
// }
}
closedir(dir);
if (flags & ACTION_DEPTHFIRST) {
if (!dirAction(fileName, &statbuf, userData, depth))
goto done_nak_warn;
}
return status;
done_nak_warn:
if (!(flags & ACTION_QUIET))
bb_simple_perror_msg(fileName);
return FALSE;
}
fileAction是根据找文件和创建,dir是找目录或创建目录。我们这里就看一下怎么创建文件。
/* File callback for /sys/ traversal.
* We act only on "/sys/.../dev" (pseudo)file
*/
static int FAST_FUNC fileAction(const char *fileName,
struct stat *statbuf UNUSED_PARAM,
void *userData,
int depth UNUSED_PARAM)
{
size_t len = strlen(fileName) - 4; /* can't underflow */
char *path = userData; /* char array[PATH_MAX + SCRATCH_SIZE] */
char subsys[PATH_MAX];
int res;
/* Is it a ".../dev" file? (len check is for paranoid reasons) */
if (strcmp(fileName + len, "/dev") != 0 || len >= PATH_MAX - 32)
return FALSE; /* not .../dev */
strcpy(path, fileName);
path[len] = '\0';
/* Read ".../subsystem" symlink in the same directory where ".../dev" is */
strcpy(subsys, path);
strcpy(subsys + len, "/subsystem");
res = readlink(subsys, subsys, sizeof(subsys)-1);
if (res > 0) {
subsys[res] = '\0';
free(G.subsystem);
if (G.subsys_env) {
bb_unsetenv_and_free(G.subsys_env);
G.subsys_env = NULL;
}
/* Set G.subsystem and $SUBSYSTEM from symlink's last component */
G.subsystem = strrchr(subsys, '/');
if (G.subsystem) {
G.subsystem = xstrdup(G.subsystem + 1);
G.subsys_env = xasprintf("%s=%s", "SUBSYSTEM", G.subsystem);
putenv(G.subsys_env);
}
}
make_device(/*DEVNAME:*/ NULL, path, OP_add);
return TRUE;
}
创建设备,这里是通过uevent里面的DEVNMAE来创建设备名的。
/* mknod in /dev based on a path like "/sys/block/hda/hda1"
* NB1: path parameter needs to have SCRATCH_SIZE scratch bytes
* after NUL, but we promise to not mangle it (IOW: to restore NUL if needed).
* NB2: "mdev -s" may call us many times, do not leak memory/fds!
*
* device_name = $DEVNAME (may be NULL)
* path = /sys/$DEVPATH
*/
static void make_device(char *device_name, char *path, int operation)
{
int major, minor, type, len;
char *path_end = path + strlen(path);
/* Try to read major/minor string. Note that the kernel puts \n after
* the data, so we don't need to worry about null terminating the string
* because sscanf() will stop at the first nondigit, which \n is.
* We also depend on path having writeable space after it.
*/
major = -1;
if (operation == OP_add) {
strcpy(path_end, "/dev");
len = open_read_close(path, path_end + 1, SCRATCH_SIZE - 1);
*path_end = '\0';
if (len < 1) {
if (!ENABLE_FEATURE_MDEV_EXEC)
return;
/* no "dev" file, but we can still run scripts
* based on device name */
} else if (sscanf(path_end + 1, "%u:%u", &major, &minor) == 2) {
dbg1("dev %u,%u", major, minor);
} else {
major = -1;
}
}
/* else: for delete, -1 still deletes the node, but < -1 suppresses that */
/* Determine device name */
if (!device_name) {
/*
* There was no $DEVNAME envvar (for example, mdev -s never has).
* But it is very useful: it contains the *path*, not only basename,
* Thankfully, uevent file has it.
* Example of .../sound/card0/controlC0/uevent file on Linux-3.7.7:
* MAJOR=116
* MINOR=7
* DEVNAME=snd/controlC0
*/
strcpy(path_end, "/uevent");
len = open_read_close(path, path_end + 1, SCRATCH_SIZE - 1);
if (len < 0)
len = 0;
*path_end = '\0';
path_end[1 + len] = '\0';
device_name = strstr(path_end + 1, "\nDEVNAME=");
if (device_name) {
device_name += sizeof("\nDEVNAME=")-1;
strchrnul(device_name, '\n')[0] = '\0';
} else {
/* Fall back to just basename */
device_name = (char*) bb_basename(path);
}
}
/* Determine device type */
/*
* http://kernel.org/doc/pending/hotplug.txt says that only
* "/sys/block/..." is for block devices. "/sys/bus" etc is not.
* But since 2.6.25 block devices are also in /sys/class/block.
* We use strstr("/block/") to forestall future surprises.
*/
type = S_IFCHR;
if (strstr(path, "/block/") || (G.subsystem && is_prefixed_with(G.subsystem, "block")))
type = S_IFBLK;
#if ENABLE_FEATURE_MDEV_CONF
G.rule_idx = 0; /* restart from the beginning (think mdev -s) */
#endif
for (;;) {
const char *str_to_match;
regmatch_t off[1 + 9 * ENABLE_FEATURE_MDEV_RENAME_REGEXP];
char *command;
char *alias;
char aliaslink = aliaslink; /* for compiler */
char *node_name;
const struct rule *rule;
str_to_match = device_name;
rule = next_rule();
#if ENABLE_FEATURE_MDEV_CONF
if (!env_matches(rule->envmatch))
continue;
if (rule->maj >= 0) { /* @maj,min rule */
if (major != rule->maj)
continue;
if (minor < rule->min0 || minor > rule->min1)
continue;
memset(off, 0, sizeof(off));
goto rule_matches;
}
if (rule->envvar) { /* $envvar=regex rule */
str_to_match = getenv(rule->envvar);
dbg3("getenv('%s'):'%s'", rule->envvar, str_to_match);
if (!str_to_match)
continue;
}
/* else: str_to_match = device_name */
if (rule->regex_compiled) {
int regex_match = regexec(&rule->match, str_to_match, ARRAY_SIZE(off), off, 0);
dbg3("regex_match for '%s':%d", str_to_match, regex_match);
//bb_error_msg("matches:");
//for (int i = 0; i < ARRAY_SIZE(off); i++) {
// if (off[i].rm_so < 0) continue;
// bb_error_msg("match %d: '%.*s'\n", i,
// (int)(off[i].rm_eo - off[i].rm_so),
// device_name + off[i].rm_so);
//}
if (regex_match != 0
/* regexec returns whole pattern as "range" 0 */
|| off[0].rm_so != 0
|| (int)off[0].rm_eo != (int)strlen(str_to_match)
) {
continue; /* this rule doesn't match */
}
}
/* else: it's final implicit "match-all" rule */
rule_matches:
dbg2("rule matched, line %d", G.parser ? G.parser->lineno : -1);
#endif
/* Build alias name */
alias = NULL;
if (ENABLE_FEATURE_MDEV_RENAME && rule->ren_mov) {
aliaslink = rule->ren_mov[0];
if (aliaslink == '!') {
/* "!": suppress node creation/deletion */
major = -2;
}
else if (aliaslink == '>' || aliaslink == '=') {
if (ENABLE_FEATURE_MDEV_RENAME_REGEXP) {
char *s;
char *p;
unsigned n;
/* substitute %1..9 with off[1..9], if any */
n = 0;
s = rule->ren_mov;
while (*s)
if (*s++ == '%')
n++;
p = alias = xzalloc(strlen(rule->ren_mov) + n * strlen(str_to_match));
s = rule->ren_mov + 1;
while (*s) {
*p = *s;
if ('%' == *s) {
unsigned i = (s[1] - '0');
if (i <= 9 && off[i].rm_so >= 0) {
n = off[i].rm_eo - off[i].rm_so;
strncpy(p, str_to_match + off[i].rm_so, n);
p += n - 1;
s++;
}
}
p++;
s++;
}
} else {
alias = xstrdup(rule->ren_mov + 1);
}
}
}
dbg3("alias:'%s'", alias);
command = NULL;
IF_FEATURE_MDEV_EXEC(command = rule->r_cmd;)
if (command) {
/* Are we running this command now?
* Run @cmd on create, $cmd on delete, *cmd on any
*/
if ((command[0] == '@' && operation == OP_add)
|| (command[0] == '$' && operation == OP_remove)
|| (command[0] == '*')
) {
command++;
} else {
command = NULL;
}
}
dbg3("command:'%s'", command);
/* "Execute" the line we found */
node_name = device_name;
if (ENABLE_FEATURE_MDEV_RENAME && alias) {
node_name = alias = build_alias(alias, device_name);
dbg3("alias2:'%s'", alias);
}
if (operation == OP_add && major >= 0) {
char *slash = strrchr(node_name, '/');
if (slash) {
*slash = '\0';
mkdir_recursive(node_name);
*slash = '/';
}
if (ENABLE_FEATURE_MDEV_CONF) {
dbg1("mknod %s (%d,%d) %o"
" %u:%u",
node_name, major, minor, rule->mode | type,
rule->ugid.uid, rule->ugid.gid
);
} else {
dbg1("mknod %s (%d,%d) %o",
node_name, major, minor, rule->mode | type
);
}
if (mknod(node_name, rule->mode | type, makedev(major, minor)) && errno != EEXIST)
bb_perror_msg("can't create '%s'", node_name);
if (ENABLE_FEATURE_MDEV_CONF) {
chmod(node_name, rule->mode);
chown(node_name, rule->ugid.uid, rule->ugid.gid);
}
if (major == G.root_major && minor == G.root_minor)
symlink(node_name, "root");
if (ENABLE_FEATURE_MDEV_RENAME && alias) {
if (aliaslink == '>') {
//TODO: on devtmpfs, device_name already exists and symlink() fails.
//End result is that instead of symlink, we have two nodes.
//What should be done?
dbg1("symlink: %s", device_name);
symlink(node_name, device_name);
}
}
}
if (ENABLE_FEATURE_MDEV_EXEC && command) {
/* setenv will leak memory, use putenv/unsetenv/free */
char *s = xasprintf("%s=%s", "MDEV", node_name);
putenv(s);
dbg1("running: %s", command);
if (system(command) == -1)
bb_perror_msg("can't run '%s'", command);
bb_unsetenv_and_free(s);
}
if (operation == OP_remove && major >= -1) {
if (ENABLE_FEATURE_MDEV_RENAME && alias) {
if (aliaslink == '>') {
dbg1("unlink: %s", device_name);
unlink(device_name);
}
}
dbg1("unlink: %s", node_name);
unlink(node_name);
}
if (ENABLE_FEATURE_MDEV_RENAME)
free(alias);
/* We found matching line.
* Stop unless it was prefixed with '-'
*/
if (!ENABLE_FEATURE_MDEV_CONF || !rule->keep_matching)
break;
} /* for (;;) */
}
我们要看到的就是它确实调用这个mknod系统函数来创建了设备节点就行,具体其他的都是一些字符串解析,参数分析之类。
if (mknod(node_name, rule->mode | type, makedev(major, minor)) && errno != EEXIST)
bb_perror_msg("can't create '%s'", node_name);
看完了mdev -s方式,下面这种方式就更简单了。不用递归的搜索过程,参数,路径,都是argv传过来的。可以直接找到路径,调用make_device函数来创建设备。当然删除也是相同的道理,这里就不再叙述。
char *fw;
char *seq;
char *action;
char *env_devname;
char *env_devpath;
unsigned my_pid;
unsigned seqnum = seqnum; /* for compiler */
int seq_fd;
smalluint op;
/* Hotplug:
* env ACTION=... DEVPATH=... SUBSYSTEM=... [SEQNUM=...] mdev
* ACTION can be "add", "remove", "change"
* DEVPATH is like "/block/sda" or "/class/input/mice"
*/
env_devname = getenv("DEVNAME"); /* can be NULL */
G.subsystem = getenv("SUBSYSTEM");
action = getenv("ACTION");
env_devpath = getenv("DEVPATH");
if (!action || !env_devpath /*|| !G.subsystem*/)
bb_show_usage();
fw = getenv("FIRMWARE");
seq = getenv("SEQNUM");
op = index_in_strings(keywords, action);
my_pid = getpid();
open_mdev_log(seq, my_pid);
seq_fd = -1;
if (seq) {
seqnum = atoll(seq);
seq_fd = wait_for_seqfile(seqnum);
}
dbg1("%s "
"ACTION:%s SUBSYSTEM:%s DEVNAME:%s DEVPATH:%s"
"%s%s",
curtime(),
action, G.subsystem, env_devname, env_devpath,
fw ? " FW:" : "", fw ? fw : ""
);
snprintf(temp, PATH_MAX, "/sys%s", env_devpath);
if (op == OP_remove) {
/* Ignoring "remove firmware". It was reported
* to happen and to cause erroneous deletion
* of device nodes. */
if (!fw)
make_device(env_devname, temp, op);
}
else {
make_device(env_devname, temp, op);
if (ENABLE_FEATURE_MDEV_LOAD_FIRMWARE) {
if (op == OP_add && fw)
load_firmware(fw, temp);
}
}
dbg1("%s exiting", curtime());
if (seq_fd >= 0) {
xwrite_str(seq_fd, utoa(seqnum + 1));
signal_mdevs(my_pid);
}
}