ueventd_device_handler.c 17.0 KB
Newer Older
S
sun_fan 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/*
 * Copyright (c) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "ueventd_device_handler.h"
S
sun_fan 已提交
17

S
sun_fan 已提交
18 19
#include <errno.h>
#include <libgen.h>
S
sun_fan 已提交
20
#include <limits.h>
S
sun_fan 已提交
21 22 23 24 25
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
S
sun_fan 已提交
26
#include "init_utils.h"
S
sun_fan 已提交
27
#include "ueventd.h"
X
xionglei6 已提交
28
#ifndef __RAMDISK__
X
xionglei6 已提交
29
#include "ueventd_parameter.h"
X
xionglei6 已提交
30
#endif
S
sun_fan 已提交
31 32 33 34 35
#include "ueventd_read_cfg.h"
#include "ueventd_utils.h"
#include "securec.h"
#define INIT_LOG_TAG "ueventd"
#include "init_log.h"
R
renwei 已提交
36 37 38
#ifdef WITH_SELINUX
#include <policycoreutils.h>
#endif
S
sun_fan 已提交
39

M
ueventd  
Mupceet 已提交
40 41 42 43 44 45 46 47 48 49 50
static bool IsBootDeviceLinkDir(const char *linkDir, const char *bootDevice)
{
    size_t pathLen = strlen("/dev/block/platform/");
    INIT_CHECK_RETURN_VALUE(strncmp(linkDir, "/dev/block/platform/", pathLen) == 0, false);
    const char *vernier = linkDir + pathLen;
    INIT_CHECK_RETURN_VALUE(strncmp(vernier, bootDevice, strlen(bootDevice)) == 0, false);
    vernier += strlen(bootDevice);
    INIT_CHECK_RETURN_VALUE(strncmp(vernier, "/by-name", strlen("/by-name")) == 0, false);
    return true;
}

S
sun_fan 已提交
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
static void CreateSymbolLinks(const char *deviceNode, char **symLinks)
{
    if (INVALIDSTRING(deviceNode) || symLinks == NULL) {
        return;
    }

    for (int i = 0; symLinks[i] != NULL; i++) {
        const char *linkName = symLinks[i];
        char linkBuf[DEVICE_FILE_SIZE] = {};

        if (strncpy_s(linkBuf, DEVICE_FILE_SIZE - 1, linkName, strlen(linkName)) != EOK) {
            INIT_LOGE("Failed to copy link name");
            return;
        }
        const char *linkDir = dirname(linkBuf);
        if (MakeDirRecursive(linkDir, DIRMODE) < 0) {
X
xionglei6 已提交
67
            INIT_LOGE("[uevent] Failed to create dir \" %s \", err = %d", linkDir, errno);
M
ueventd  
Mupceet 已提交
68 69 70 71 72
            return;
        }
        if (IsBootDeviceLinkDir(linkDir, bootDevice) && access("/dev/block/by-name", F_OK) != 0) {
            INIT_CHECK_ONLY_ELOG(symlink(linkDir, "/dev/block/by-name") == 0,
                "Failed to create by-name symlink, err %d", errno);
S
sun_fan 已提交
73 74 75 76
        }
        errno = 0;
        int rc = symlink(deviceNode, linkName);
        if (rc != 0) {
C
fix log  
cheng_jinsong 已提交
77
            if (errno != EEXIST) {
S
sun_fan 已提交
78 79 80 81 82 83 84 85 86 87 88 89
                INIT_LOGE("Failed to link \" %s \" to \" %s \", err = %d", deviceNode, linkName, errno);
            }
        }
    }
}

static inline void AdjustDeviceNodePermissions(const char *deviceNode, uid_t uid, gid_t gid, mode_t mode)
{
    if (INVALIDSTRING(deviceNode)) {
        return;
    }
    if (chown(deviceNode, uid, gid) != 0) {
C
chengjinsong 已提交
90
        INIT_LOGW("Failed to change \" %s \" owner, errno %d", deviceNode, errno);
S
sun_fan 已提交
91 92 93
    }

    if (chmod(deviceNode, mode) != 0) {
C
chengjinsong 已提交
94
        INIT_LOGW("Failed to change \" %s \" mode, errno %d", deviceNode, errno);
S
sun_fan 已提交
95 96 97
    }
}

R
renwei 已提交
98
static void SetDeviceLable(const char *path)
R
renwei 已提交
99 100 101
{
#ifdef WITH_SELINUX
    int rc = 0;
R
renwei 已提交
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
    char buffer[PATH_MAX] = {};
    const char *p = NULL;
    char *slash = NULL;

    p = path;
    slash = strchr(path, '/');
    while (slash != NULL) {
        int gap = slash - p;
        p = slash + 1;
        if (gap == 0) {
            slash = strchr(p, '/');
            continue;
        }
        if (gap < 0) { // end with '/'
            break;
        }

        if (memcpy_s(buffer, PATH_MAX, path, p - path - 1) != EOK) {
            INIT_LOGE("[uevent] Failed to memcpy path %s", path);
            return;
        }
        rc += Restorecon(buffer);
        slash = strchr(p, '/');
R
renwei 已提交
125 126 127 128
    }

    rc += Restorecon(path);
    if (rc != 0) {
R
renwei 已提交
129
        INIT_LOGE("[uevent] Failed to Restorecon \" %s \"", path);
R
renwei 已提交
130
    }
R
renwei 已提交
131 132

    return;
R
renwei 已提交
133 134 135
#endif
}

S
sun_fan 已提交
136 137 138
static int CreateDeviceNode(const struct Uevent *uevent, const char *deviceNode, char **symLinks, bool isBlock)
{
    int rc = -1;
139 140
    int major = uevent->major;
    int minor = uevent->minor;
S
sun_fan 已提交
141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
    uid_t uid = uevent->ug.uid;
    gid_t gid = uevent->ug.gid;
    mode_t mode = DEVMODE;

    if (deviceNode == NULL || *deviceNode == '\0') {
        INIT_LOGE("Invalid device file");
        return rc;
    }

    char deviceNodeBuffer[DEVICE_FILE_SIZE] = {};
    if (strncpy_s(deviceNodeBuffer, DEVICE_FILE_SIZE - 1, deviceNode, strlen(deviceNode)) != EOK) {
        INIT_LOGE("Failed to copy device node");
        return rc;
    }
    const char *devicePath = dirname(deviceNodeBuffer);
    // device node always installed in /dev, should not be other locations.
    if (STRINGEQUAL(devicePath, ".") || STRINGEQUAL(devicePath, "/")) {
        INIT_LOGE("device path is not valid. should be starts with /dev");
        return rc;
    }

    rc = MakeDirRecursive(devicePath, DIRMODE);
    if (rc < 0) {
        INIT_LOGE("Create path \" %s \" failed", devicePath);
        return rc;
    }
S
sun_fan 已提交
167

S
sun_fan 已提交
168 169 170 171 172 173 174
    GetDeviceNodePermissions(deviceNode, &uid, &gid, &mode);
    mode |= isBlock ? S_IFBLK : S_IFCHR;
    dev_t dev = makedev(major, minor);
    setegid(0);
    rc = mknod(deviceNode, mode, dev);
    if (rc < 0) {
        if (errno != EEXIST) {
S
sun_fan 已提交
175
            INIT_LOGE("Create device node[%s %d, %d] failed. %d", deviceNode, major, minor, errno);
S
sun_fan 已提交
176 177 178 179
            return rc;
        }
    }
    AdjustDeviceNodePermissions(deviceNode, uid, gid, mode);
S
sun_fan 已提交
180
    if (symLinks != NULL) {
S
sun_fan 已提交
181 182
        CreateSymbolLinks(deviceNode, symLinks);
    }
R
renwei 已提交
183
    SetDeviceLable(deviceNode);
S
sun_fan 已提交
184
    // No matter what result the symbol links returns,
S
sun_fan 已提交
185 186 187 188 189 190 191 192 193
    // as long as create device node done, just returns success.
    rc = 0;
    return rc;
}

static int RemoveDeviceNode(const char *deviceNode, char **symLinks)
{
    if (INVALIDSTRING(deviceNode)) {
        INIT_LOGE("Invalid device node");
S
sun_fan 已提交
194
        return -1;
S
sun_fan 已提交
195 196 197
    }
    if (symLinks != NULL) {
        for (int i = 0; symLinks[i] != NULL; i++) {
198
            char realPath[DEVICE_FILE_SIZE] = {0};
S
sun_fan 已提交
199 200 201 202 203 204 205 206 207 208 209 210 211
            const char *linkName = symLinks[i];
            ssize_t ret = readlink(linkName, realPath, DEVICE_FILE_SIZE - 1);
            if (ret < 0) {
                continue;
            }
            if (STRINGEQUAL(deviceNode, realPath)) {
                unlink(linkName);
            }
        }
    }
    return unlink(deviceNode);
}

S
sun_fan 已提交
212 213 214 215 216 217 218 219 220 221
static char *FindPlatformDeviceName(char *path)
{
    if (INVALIDSTRING(path)) {
        return NULL;
    }

    if (STARTSWITH(path, "/sys/devices/platform/")) {
        path += strlen("/sys/devices/platform/");
        return path;
    }
222 223 224 225 226 227

    // Some platform devices may not be registered under platform device.
    if (STARTSWITH(path, "/sys/devices/")) {
        path += strlen("/sys/devices/");
        return path;
    }
S
sun_fan 已提交
228 229 230
    return NULL;
}

S
sun_fan 已提交
231 232
static void BuildDeviceSymbolLinks(char **links, int linkNum, const char *parent,
    const char *partitionName, const char *deviceName)
S
sun_fan 已提交
233
{
234 235
    if (linkNum > BLOCKDEVICE_LINKS - 1) {
        INIT_LOGW("Too many links, ignore");
S
sun_fan 已提交
236 237 238 239 240
        return;
    }
    links[linkNum] = calloc(sizeof(char), DEVICE_FILE_SIZE);
    if (links[linkNum] == NULL) {
        INIT_LOGE("Failed to allocate memory for link, err = %d", errno);
S
sun_fan 已提交
241 242 243 244 245 246
        return;
    }

    // If a block device without partition name.
    // For now, we will not create symbol link for it.
    if (!INVALIDSTRING(partitionName)) {
S
sun_fan 已提交
247
        if (snprintf_s(links[linkNum], DEVICE_FILE_SIZE, DEVICE_FILE_SIZE - 1,
S
sun_fan 已提交
248
            "/dev/block/platform/%s/by-name/%s", parent, partitionName) == -1) {
S
sun_fan 已提交
249
            INIT_LOGE("Failed to build link");
S
sun_fan 已提交
250
        }
S
sun_fan 已提交
251
    } else if (!INVALIDSTRING(deviceName)) {
S
sun_fan 已提交
252
        if (snprintf_s(links[linkNum], DEVICE_FILE_SIZE, DEVICE_FILE_SIZE - 1,
X
add ut  
xionglei6 已提交
253
            "/dev/block/platform/%s/%s", parent, deviceName) == -1) {
S
sun_fan 已提交
254 255 256 257 258
            INIT_LOGE("Failed to build link");
        }
    }
}

X
xionglei6 已提交
259 260 261 262 263 264 265 266 267 268 269 270
static void FreeSymbolLinks(char **links, int length)
{
    if (links != NULL) {
        for (int i = 0; i < length && links[i] != NULL; i++) {
            free(links[i]);
            links[i] = NULL;
        }
        free(links);
        links = NULL;
    }
}

S
sun_fan 已提交
271 272
static char **GetBlockDeviceSymbolLinks(const struct Uevent *uevent)
{
273
    if (uevent == NULL || uevent->subsystem == NULL || STRINGEQUAL(uevent->subsystem, "block") == 0) {
S
sun_fan 已提交
274 275 276 277 278 279 280 281 282 283 284
        INIT_LOGW("Invalid arguments, Skip to get device symbol links.");
        return NULL;
    }

    // Only if current uevent is for real device.
    if (!STARTSWITH(uevent->syspath, "/devices")) {
        return NULL;
    }
    // For block device under one platform device.
    // check subsystem file under directory, see if it links to bus/platform.
    // For now, only support platform device.
S
sun_fan 已提交
285
    char sysPath[SYSPATH_SIZE] = {};
S
sun_fan 已提交
286 287 288 289 290 291 292 293
    if (snprintf_s(sysPath, SYSPATH_SIZE, SYSPATH_SIZE - 1, "/sys%s", uevent->syspath) == -1) {
        INIT_LOGE("Failed to build sys path for device %s", uevent->syspath);
        return NULL;
    }
    char **links = calloc(sizeof(char *), BLOCKDEVICE_LINKS);
    int linkNum = 0;
    if (links == NULL) {
        INIT_LOGE("Failed to allocate memory for links, err = %d", errno);
S
sun_fan 已提交
294
        return NULL;
S
sun_fan 已提交
295 296
    }

297
    // Reverse walk through sysPath, and check subsystem file under each directory.
S
sun_fan 已提交
298 299
    char *parent = dirname(sysPath);
    while (parent != NULL && !STRINGEQUAL(parent, "/") && !STRINGEQUAL(parent, ".")) {
300
        char subsystem[SYSPATH_SIZE];
S
sun_fan 已提交
301 302
        if (snprintf_s(subsystem, SYSPATH_SIZE, SYSPATH_SIZE - 1, "%s/subsystem", parent) == -1) {
            INIT_LOGE("Failed to build subsystem path for device \" %s \"", uevent->syspath);
X
xionglei6 已提交
303
            FreeSymbolLinks(links, BLOCKDEVICE_LINKS);
S
sun_fan 已提交
304 305
            return NULL;
        }
306 307
        char *bus = GetRealPath(subsystem);
        if (bus == NULL) {
S
sun_fan 已提交
308 309 310 311
            parent = dirname(parent);
            continue;
        }
        if (STRINGEQUAL(bus, "/sys/bus/platform")) {
X
xionglei6 已提交
312
            INIT_LOGV("Find a platform device: %s", parent);
S
sun_fan 已提交
313 314 315
            parent = FindPlatformDeviceName(parent);
            if (parent != NULL) {
                BuildDeviceSymbolLinks(links, linkNum, parent, uevent->partitionName, uevent->deviceName);
X
xionglei6 已提交
316 317
                linkNum++;
            }
S
sun_fan 已提交
318
        }
319
        free(bus);
S
sun_fan 已提交
320 321
        parent = dirname(parent);
    }
S
sun_fan 已提交
322

S
sun_fan 已提交
323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341
    links[linkNum] = NULL;
    return links;
}

static void HandleDeviceNode(const struct Uevent *uevent, const char *deviceNode, bool isBlock)
{
    ACTION action = uevent->action;
    char **symLinks = NULL;

    // Block device path and name maybe not human readable.
    // Consider to create symbol links for them.
    // Make block device more readable.
    if (isBlock) {
        symLinks = GetBlockDeviceSymbolLinks(uevent);
    }

    if (action == ACTION_ADD) {
        if (CreateDeviceNode(uevent, deviceNode, symLinks, isBlock) < 0) {
            INIT_LOGE("Create device \" %s \" failed", deviceNode);
X
xionglei6 已提交
342
        } else {
X
xionglei6 已提交
343
#ifndef __RAMDISK__
X
xionglei6 已提交
344
            if (SetUeventDeviceParameter(deviceNode, action) != 0) {
X
xionglei6 已提交
345 346
                INIT_LOGE("Set device parameter added failed");
            }
X
xionglei6 已提交
347
#endif
S
sun_fan 已提交
348 349 350 351
        }
    } else if (action == ACTION_REMOVE) {
        if (RemoveDeviceNode(deviceNode, symLinks) < 0) {
            INIT_LOGE("Remove device \" %s \" failed", deviceNode);
X
xionglei6 已提交
352
        } else {
X
xionglei6 已提交
353
#ifndef __RAMDISK__
X
xionglei6 已提交
354
            if (SetUeventDeviceParameter(deviceNode, action) != 0) {
X
xionglei6 已提交
355 356
                INIT_LOGE("Set device parameter removed failed");
            }
X
xionglei6 已提交
357
#endif
S
sun_fan 已提交
358 359
        }
    } else if (action == ACTION_CHANGE) {
360
        INIT_LOGV("Device %s changed", uevent->syspath);
S
sun_fan 已提交
361 362
    }
    // Ignore other actions
X
xionglei6 已提交
363
    FreeSymbolLinks(symLinks, BLOCKDEVICE_LINKS);
S
sun_fan 已提交
364 365 366 367 368 369 370 371 372 373 374
}

static const char *GetDeviceName(char *sysPath, const char *deviceName)
{
    const char *devName = NULL;
    if (INVALIDSTRING(sysPath)) {
        INIT_LOGE("Invalid sys path");
        return NULL;
    }
    if (deviceName != NULL && deviceName[0] != '\0') {
        // if device name reported by kernel includes '/', skip it.
S
sun_fan 已提交
375
        // use entire device name reported by kernel
S
sun_fan 已提交
376 377 378 379 380 381 382 383 384 385 386 387 388
        devName = basename((char *)deviceName);
        char *p = strrchr(deviceName, '/');
        if (p != NULL) { // device name includes slash
            p++;
            if (p == NULL || *p == '\0') {
                // device name ends with '/', which should never happen.
                // Get name from sys path.
                devName = basename(sysPath);
            } else {
                devName = p;
            }
        }
    } else {
Z
zhong_ning 已提交
389
        // kernel does not report DEVNAME, which is possible. use base name of syspath instead.
S
sun_fan 已提交
390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411
        devName = basename(sysPath);
    }
    return devName;
}

static const char *GetDeviceBasePath(const char *subsystem)
{
    char *devPath = NULL;
    if (INVALIDSTRING(subsystem)) {
        return devPath;
    }

    if (STRINGEQUAL(subsystem, "block")) {
        devPath = "/dev/block";
    } else if (STRINGEQUAL(subsystem, "input")) {
        devPath = "/dev/input";
    } else if (STRINGEQUAL(subsystem, "drm")) {
        devPath = "/dev/dri";
    } else if (STRINGEQUAL(subsystem, "graphics")) {
        devPath = "/dev/graphics";
    } else if (STRINGEQUAL(subsystem, "sound")) {
        devPath = "/dev/snd";
B
bigA2021 已提交
412 413
    } else if (STRINGEQUAL(subsystem, "functionfs")) {
        devPath = "/dev/functionfs";
414 415
    } else if (STRINGEQUAL(subsystem, "dma_heap")) {
        devPath = "/dev/dma_heap";
S
sun_fan 已提交
416 417 418 419 420 421 422 423 424 425 426 427 428 429 430
    } else {
        devPath = "/dev";
    }
    return devPath;
}

void HandleBlockDeviceEvent(const struct Uevent *uevent)
{
    // Sanity checks
    if (uevent == NULL || uevent->subsystem == NULL) {
        INIT_LOGE("Invalid uevent message received");
        return;
    }

    if (strcmp(uevent->subsystem, "block") != 0) {
C
cheng_jinsong 已提交
431
        INIT_LOGE("Unexpected uevent subsystem \" %s \" received in block device handler", uevent->subsystem);
S
sun_fan 已提交
432 433 434 435 436 437 438 439 440 441 442 443 444
        return;
    }

    if (uevent->major < 0 || uevent->minor < 0) {
        return;
    }

    bool isBlock = true;
    // Block device always installed into /dev/block
    const char *devPath = GetDeviceBasePath(uevent->subsystem);
    char deviceNode[DEVICE_FILE_SIZE] = {};
    char sysPath[SYSPATH_SIZE] = {};

S
sun_fan 已提交
445 446 447
    if (uevent->syspath == NULL) {
        return;
    }
S
sun_fan 已提交
448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466
    if (strncpy_s(sysPath, SYSPATH_SIZE - 1, uevent->syspath, strlen(uevent->syspath) != EOK)) {
        INIT_LOGE("Failed to copy sys path");
        return;
    }
    const char *devName = GetDeviceName(sysPath, uevent->deviceName);

    if (devPath == NULL || devName == NULL) {
        INIT_LOGE("Cannot get device path or device name");
        return;
    }
    if (snprintf_s(deviceNode, DEVICE_FILE_SIZE, DEVICE_FILE_SIZE - 1, "%s/%s", devPath, devName) == -1) {
        INIT_LOGE("Make device file for device [%d : %d]", uevent->major, uevent->minor);
        return;
    }
    HandleDeviceNode(uevent, deviceNode, isBlock);
}

void HandleOtherDeviceEvent(const struct Uevent *uevent)
{
S
sun_fan 已提交
467
    if (uevent == NULL || uevent->subsystem == NULL || uevent->syspath == NULL) {
S
sun_fan 已提交
468 469 470 471 472 473 474 475 476 477
        INIT_LOGE("Invalid uevent received");
        return;
    }

    if (uevent->major < 0 || uevent->minor < 0) {
        return;
    }

    char deviceNode[DEVICE_FILE_SIZE] = {};
    char sysPath[SYSPATH_SIZE] = {};
C
cheng_jinsong 已提交
478
    if (strncpy_s(sysPath, SYSPATH_SIZE - 1, uevent->syspath, strlen(uevent->syspath)) != EOK) {
S
sun_fan 已提交
479 480 481 482 483 484 485 486 487 488
        INIT_LOGE("Failed to copy sys path");
        return;
    }
    const char *devName = GetDeviceName(sysPath, uevent->deviceName);
    const char *devPath = GetDeviceBasePath(uevent->subsystem);

    if (devPath == NULL || devName == NULL) {
        INIT_LOGE("Cannot get device path or device name");
        return;
    }
X
xionglei6 已提交
489
    INIT_LOGV("HandleOtherDeviceEvent, devPath = %s, devName = %s", devPath, devName);
S
sun_fan 已提交
490

S
sun_fan 已提交
491 492 493 494
    // For usb devices, should take care of it specially.
    // if usb devices report DEVNAME, just create device node.
    // otherwise, create deviceNode with bus number and device number.
    if (STRINGEQUAL(uevent->subsystem, "usb")) {
S
sun_fan 已提交
495 496 497 498 499 500 501 502 503 504 505
        if (uevent->deviceName != NULL) {
            if (snprintf_s(deviceNode, DEVICE_FILE_SIZE, DEVICE_FILE_SIZE - 1, "/dev/%s", uevent->deviceName) == -1) {
                INIT_LOGE("Make device file for device [%d : %d]", uevent->major, uevent->minor);
                return;
            }
        } else {
            if (uevent->busNum < 0 || uevent->devNum < 0) {
                // usb device should always report bus number and device number.
                INIT_LOGE("usb device with invalid bus number or device number");
                return;
            }
S
sun_fan 已提交
506
            if (snprintf_s(deviceNode, DEVICE_FILE_SIZE, DEVICE_FILE_SIZE - 1,
S
sun_fan 已提交
507
                "/dev/bus/usb/%03d/%03d", uevent->busNum, uevent->devNum) == -1) {
S
sun_fan 已提交
508
                INIT_LOGE("Make usb device node for device [%d : %d]", uevent->busNum, uevent->devNum);
S
sun_fan 已提交
509 510
            }
        }
S
sun_fan 已提交
511 512 513 514
    } else if (STARTSWITH(uevent->subsystem, "usb")) {
        // Other usb devies, do not handle it.
        return;
    } else {
S
sun_fan 已提交
515 516 517 518
        if (snprintf_s(deviceNode, DEVICE_FILE_SIZE, DEVICE_FILE_SIZE - 1, "%s/%s", devPath, devName) == -1) {
            INIT_LOGE("Make device file for device [%d : %d]", uevent->major, uevent->minor);
            return;
        }
S
sun_fan 已提交
519 520
    }
    HandleDeviceNode(uevent, deviceNode, false);
S
sun_fan 已提交
521
}