udev.c 15.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
/*
 * Copyright © 2009 Julien Cristau
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 * Author: Julien Cristau <jcristau@debian.org>
 */

#ifdef HAVE_DIX_CONFIG_H
#include <dix-config.h>
#endif

#include <libudev.h>
31
#include <ctype.h>
32 33 34 35 36 37

#include "input.h"
#include "inputstr.h"
#include "hotplug.h"
#include "config-backends.h"
#include "os.h"
38
#include "globals.h"
39 40 41

#define UDEV_XKB_PROP_KEY "xkb"

42 43 44 45 46 47 48 49 50 51 52
#define LOG_PROPERTY(path, prop, val)                                   \
    LogMessageVerb(X_INFO, 10,                                          \
                   "config/udev: getting property %s on %s "            \
                   "returned \"%s\"\n",                                 \
                   (prop), (path), (val) ? (val) : "(null)")
#define LOG_SYSATTR(path, attr, val)                                    \
    LogMessageVerb(X_INFO, 10,                                          \
                   "config/udev: getting attribute %s on %s "           \
                   "returned \"%s\"\n",                                 \
                   (attr), (path), (val) ? (val) : "(null)")

53 54
static struct udev_monitor *udev_monitor;

55 56 57 58 59 60
#ifdef CONFIG_UDEV_KMS
static Bool
config_udev_odev_setup_attribs(const char *path, const char *syspath,
                               config_odev_probe_proc_ptr probe_callback);
#endif

61 62 63 64 65 66
static void
device_added(struct udev_device *udev_device)
{
    const char *path, *name = NULL;
    char *config_info = NULL;
    const char *syspath;
67
    const char *tags_prop;
68
    const char *key, *value, *tmp;
69
    InputOption *input_options;
70
    InputAttributes attrs = { };
71 72 73 74
    DeviceIntPtr dev = NULL;
    struct udev_list_entry *set, *entry;
    struct udev_device *parent;
    int rc;
75
    const char *dev_seat;
76 77 78 79 80 81 82 83

    path = udev_device_get_devnode(udev_device);

    syspath = udev_device_get_syspath(udev_device);

    if (!path || !syspath)
        return;

84 85 86 87 88 89 90 91 92 93
    dev_seat = udev_device_get_property_value(udev_device, "ID_SEAT");
    if (!dev_seat)
        dev_seat = "seat0";

    if (SeatId && strcmp(dev_seat, SeatId))
        return;

    if (!SeatId && strcmp(dev_seat, "seat0"))
        return;

94 95 96 97 98 99 100 101 102 103 104 105 106 107
#ifdef CONFIG_UDEV_KMS
    if (!strcmp(udev_device_get_subsystem(udev_device), "drm")) {
        const char *sysname = udev_device_get_sysname(udev_device);

        if (strncmp(sysname, "card", 4) != 0)
            return;

        LogMessage(X_INFO, "config/udev: Adding drm device (%s)\n", path);

        config_udev_odev_setup_attribs(path, syspath, NewGPUDeviceRequest);
        return;
    }
#endif

108 109 110
    if (!udev_device_get_property_value(udev_device, "ID_INPUT")) {
        LogMessageVerb(X_INFO, 10,
                       "config/udev: ignoring device %s without "
111
                       "property ID_INPUT set\n", path);
112
        return;
113
    }
114

115 116 117
    input_options = input_option_new(NULL, "_source", "server/udev");
    if (!input_options)
        return;
118 119

    parent = udev_device_get_parent(udev_device);
120
    if (parent) {
121
        const char *ppath = udev_device_get_devnode(parent);
122
        const char *product = udev_device_get_property_value(parent, "PRODUCT");
123
        const char *pnp_id = udev_device_get_sysattr_value(parent, "id");
124
        unsigned int usb_vendor, usb_model;
125

126
        name = udev_device_get_sysattr_value(parent, "name");
127 128
        LOG_SYSATTR(ppath, "name", name);
        if (!name) {
129
            name = udev_device_get_property_value(parent, "NAME");
130 131
            LOG_PROPERTY(ppath, "NAME", name);
        }
132

133 134 135
        if (pnp_id)
            attrs.pnp_id = strdup(pnp_id);
        LOG_SYSATTR(ppath, "id", pnp_id);
136 137

        /* construct USB ID in lowercase hex - "0000:ffff" */
138 139
        if (product &&
            sscanf(product, "%*x/%4x/%4x/%*x", &usb_vendor, &usb_model) == 2) {
140 141
            char *usb_id;
            if (asprintf(&usb_id, "%04x:%04x", usb_vendor, usb_model)
142
                == -1)
143
                usb_id = NULL;
144
            else
Peter Hutterer's avatar
Peter Hutterer committed
145
                LOG_PROPERTY(ppath, "PRODUCT", product);
146
            attrs.usb_id = usb_id;
147
        }
148
    }
149 150 151
    if (!name)
        name = "(unnamed)";
    else
152
        attrs.product = strdup(name);
153 154 155
    input_options = input_option_new(input_options, "name", name);
    input_options = input_option_new(input_options, "path", path);
    input_options = input_option_new(input_options, "device", path);
156 157
    if (path)
        attrs.device = strdup(path);
158 159 160 161

    tags_prop = udev_device_get_property_value(udev_device, "ID_INPUT.tags");
    LOG_PROPERTY(path, "ID_INPUT.tags", tags_prop);
    attrs.tags = xstrtokenize(tags_prop, ",");
162

163 164
    if (asprintf(&config_info, "udev:%s", syspath) == -1) {
        config_info = NULL;
165
        goto unwind;
166
    }
167 168 169

    if (device_is_duplicate(config_info)) {
        LogMessage(X_WARNING, "config/udev: device %s already added. "
170
                   "Ignoring.\n", name);
171 172 173 174 175 176 177 178 179
        goto unwind;
    }

    set = udev_device_get_properties_list_entry(udev_device);
    udev_list_entry_foreach(entry, set) {
        key = udev_list_entry_get_name(entry);
        if (!key)
            continue;
        value = udev_list_entry_get_value(entry);
180
        if (!strncasecmp(key, UDEV_XKB_PROP_KEY, sizeof(UDEV_XKB_PROP_KEY) - 1)) {
181
            LOG_PROPERTY(path, key, value);
182 183
            tmp = key + sizeof(UDEV_XKB_PROP_KEY) - 1;
            if (!strcasecmp(tmp, "rules"))
184 185
                input_options =
                    input_option_new(input_options, "xkb_rules", value);
186
            else if (!strcasecmp(tmp, "layout"))
187 188
                input_options =
                    input_option_new(input_options, "xkb_layout", value);
189
            else if (!strcasecmp(tmp, "variant"))
190 191
                input_options =
                    input_option_new(input_options, "xkb_variant", value);
192
            else if (!strcasecmp(tmp, "model"))
193 194
                input_options =
                    input_option_new(input_options, "xkb_model", value);
195
            else if (!strcasecmp(tmp, "options"))
196 197 198 199
                input_options =
                    input_option_new(input_options, "xkb_options", value);
        }
        else if (!strcmp(key, "ID_VENDOR")) {
200
            LOG_PROPERTY(path, key, value);
201
            attrs.vendor = strdup(value);
202 203
        }
        else if (!strcmp(key, "ID_INPUT_KEY")) {
204
            LOG_PROPERTY(path, key, value);
205
            attrs.flags |= ATTR_KEYBOARD;
206 207
        }
        else if (!strcmp(key, "ID_INPUT_MOUSE")) {
208
            LOG_PROPERTY(path, key, value);
209
            attrs.flags |= ATTR_POINTER;
210 211
        }
        else if (!strcmp(key, "ID_INPUT_JOYSTICK")) {
212
            LOG_PROPERTY(path, key, value);
213
            attrs.flags |= ATTR_JOYSTICK;
214 215
        }
        else if (!strcmp(key, "ID_INPUT_TABLET")) {
216
            LOG_PROPERTY(path, key, value);
217
            attrs.flags |= ATTR_TABLET;
218 219
        }
        else if (!strcmp(key, "ID_INPUT_TOUCHPAD")) {
220
            LOG_PROPERTY(path, key, value);
221
            attrs.flags |= ATTR_TOUCHPAD;
222 223
        }
        else if (!strcmp(key, "ID_INPUT_TOUCHSCREEN")) {
224
            LOG_PROPERTY(path, key, value);
225 226 227
            attrs.flags |= ATTR_TOUCHSCREEN;
        }
    }
228

229
    input_options = input_option_new(input_options, "config_info", config_info);
230

231 232 233 234
    /* Default setting needed for non-seat0 seats */
    if (ServerIsNotSeat0())
        input_options = input_option_new(input_options, "GrabDevice", "on");

235 236
    LogMessage(X_INFO, "config/udev: Adding input device %s (%s)\n",
               name, path);
237
    rc = NewInputDeviceRequest(input_options, &attrs, &dev);
238 239 240 241
    if (rc != Success)
        goto unwind;

 unwind:
242
    free(config_info);
243
    input_option_free_list(&input_options);
244

245 246 247 248 249
    free((void *) attrs.usb_id);
    free((void *) attrs.pnp_id);
    free((void *) attrs.product);
    free((void *) attrs.device);
    free((void *) attrs.vendor);
250
    if (attrs.tags) {
251
        const char **tag = attrs.tags;
252

253
        while (*tag) {
254
            free((void *) *tag);
255 256
            tag++;
        }
257
        free(attrs.tags);
258 259
    }

260 261 262 263 264 265 266 267 268
    return;
}

static void
device_removed(struct udev_device *device)
{
    char *value;
    const char *syspath = udev_device_get_syspath(device);

269 270 271 272 273 274 275
#ifdef CONFIG_UDEV_KMS
    if (!strcmp(udev_device_get_subsystem(device), "drm")) {
        const char *sysname = udev_device_get_sysname(device);
        const char *path = udev_device_get_devnode(device);

        if (strncmp(sysname,"card", 4) != 0)
            return;
276
        ErrorF("removing GPU device %s %s\n", syspath, path);
277 278 279 280 281 282 283 284
        if (!path)
            return;

        config_udev_odev_setup_attribs(path, syspath, DeleteGPUDeviceRequest);
        return;
    }
#endif

285
    if (asprintf(&value, "udev:%s", syspath) == -1)
286 287 288 289
        return;

    remove_devices("udev", value);

290
    free(value);
291 292 293 294 295 296 297 298 299 300 301 302
}

static void
wakeup_handler(pointer data, int err, pointer read_mask)
{
    int udev_fd = udev_monitor_get_fd(udev_monitor);
    struct udev_device *udev_device;
    const char *action;

    if (err < 0)
        return;

303
    if (FD_ISSET(udev_fd, (fd_set *) read_mask)) {
304 305 306 307 308
        udev_device = udev_monitor_receive_device(udev_monitor);
        if (!udev_device)
            return;
        action = udev_device_get_action(udev_device);
        if (action) {
309
            if (!strcmp(action, "add")) {
310 311
                device_removed(udev_device);
                device_added(udev_device);
312 313 314 315 316 317
            } else if (!strcmp(action, "change")) {
                /* ignore change for the drm devices */
                if (strcmp(udev_device_get_subsystem(udev_device), "drm")) {
                    device_removed(udev_device);
                    device_added(udev_device);
                }
318
            }
319 320
            else if (!strcmp(action, "remove"))
                device_removed(udev_device);
321 322 323 324 325 326 327 328 329 330 331
        }
        udev_device_unref(udev_device);
    }
}

static void
block_handler(pointer data, struct timeval **tv, pointer read_mask)
{
}

int
332
config_udev_pre_init(void)
333 334 335 336 337 338
{
    struct udev *udev;

    udev = udev_new();
    if (!udev)
        return 0;
339

340 341 342 343
    udev_monitor = udev_monitor_new_from_netlink(udev, "udev");
    if (!udev_monitor)
        return 0;

344 345
    udev_monitor_filter_add_match_subsystem_devtype(udev_monitor, "input",
                                                    NULL);
346 347
    /* For Wacom serial devices */
    udev_monitor_filter_add_match_subsystem_devtype(udev_monitor, "tty", NULL);
348
#ifdef CONFIG_UDEV_KMS
349 350
    /* For output GPU devices */
    udev_monitor_filter_add_match_subsystem_devtype(udev_monitor, "drm", NULL);
351
#endif
352

353
#ifdef HAVE_UDEV_MONITOR_FILTER_ADD_MATCH_TAG
354
    if (ServerIsNotSeat0())
355
        udev_monitor_filter_add_match_tag(udev_monitor, SeatId);
356
#endif
357 358 359 360
    if (udev_monitor_enable_receiving(udev_monitor)) {
        ErrorF("config/udev: failed to bind the udev monitor\n");
        return 0;
    }
361 362 363 364 365 366 367 368 369
    return 1;
}

int
config_udev_init(void)
{
    struct udev *udev;
    struct udev_enumerate *enumerate;
    struct udev_list_entry *devices, *device;
370

371
    udev = udev_monitor_get_udev(udev_monitor);
372 373 374
    enumerate = udev_enumerate_new(udev);
    if (!enumerate)
        return 0;
375 376 377

    udev_enumerate_add_match_subsystem(enumerate, "input");
    udev_enumerate_add_match_subsystem(enumerate, "tty");
378 379 380
#ifdef CONFIG_UDEV_KMS
    udev_enumerate_add_match_subsystem(enumerate, "drm");
#endif
381

382
#ifdef HAVE_UDEV_ENUMERATE_ADD_MATCH_TAG
383
    if (ServerIsNotSeat0())
384
        udev_enumerate_add_match_tag(enumerate, SeatId);
385
#endif
386

387 388 389 390
    udev_enumerate_scan_devices(enumerate);
    devices = udev_enumerate_get_list_entry(enumerate);
    udev_list_entry_foreach(device, devices) {
        const char *syspath = udev_list_entry_get_name(device);
391 392
        struct udev_device *udev_device =
            udev_device_new_from_syspath(udev, syspath);
393 394 395 396 397

        /* Device might be gone by the time we try to open it */
        if (!udev_device)
            continue;

398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419
        device_added(udev_device);
        udev_device_unref(udev_device);
    }
    udev_enumerate_unref(enumerate);

    RegisterBlockAndWakeupHandlers(block_handler, wakeup_handler, NULL);
    AddGeneralSocket(udev_monitor_get_fd(udev_monitor));

    return 1;
}

void
config_udev_fini(void)
{
    struct udev *udev;

    if (!udev_monitor)
        return;

    udev = udev_monitor_get_udev(udev_monitor);

    RemoveGeneralSocket(udev_monitor_get_fd(udev_monitor));
420
    RemoveBlockAndWakeupHandlers(block_handler, wakeup_handler, NULL);
421 422 423 424
    udev_monitor_unref(udev_monitor);
    udev_monitor = NULL;
    udev_unref(udev);
}
425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468

#ifdef CONFIG_UDEV_KMS

static Bool
config_udev_odev_setup_attribs(const char *path, const char *syspath,
                               config_odev_probe_proc_ptr probe_callback)
{
    struct OdevAttributes *attribs = config_odev_allocate_attribute_list();
    int ret;

    if (!attribs)
        return FALSE;

    ret = config_odev_add_attribute(attribs, ODEV_ATTRIB_PATH, path);
    if (ret == FALSE)
        goto fail;

    ret = config_odev_add_attribute(attribs, ODEV_ATTRIB_SYSPATH, syspath);
    if (ret == FALSE)
        goto fail;

    /* ownership of attribs is passed to probe layer */
    probe_callback(attribs);
    return TRUE;
fail:
    config_odev_free_attributes(attribs);
    free(attribs);
    return FALSE;
}

void
config_udev_odev_probe(config_odev_probe_proc_ptr probe_callback)
{
    struct udev *udev;
    struct udev_enumerate *enumerate;
    struct udev_list_entry *devices, *device;

    udev = udev_monitor_get_udev(udev_monitor);
    enumerate = udev_enumerate_new(udev);
    if (!enumerate)
        return;

    udev_enumerate_add_match_subsystem(enumerate, "drm");
    udev_enumerate_add_match_sysname(enumerate, "card[0-9]*");
469 470 471 472
#ifdef HAVE_UDEV_ENUMERATE_ADD_MATCH_TAG
    if (ServerIsNotSeat0())
        udev_enumerate_add_match_tag(enumerate, SeatId);
#endif
473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497
    udev_enumerate_scan_devices(enumerate);
    devices = udev_enumerate_get_list_entry(enumerate);
    udev_list_entry_foreach(device, devices) {
        const char *syspath = udev_list_entry_get_name(device);
        struct udev_device *udev_device = udev_device_new_from_syspath(udev, syspath);
        const char *path = udev_device_get_devnode(udev_device);
        const char *sysname = udev_device_get_sysname(udev_device);

        if (!path || !syspath)
            goto no_probe;
        else if (strcmp(udev_device_get_subsystem(udev_device), "drm") != 0)
            goto no_probe;
        else if (strncmp(sysname, "card", 4) != 0)
            goto no_probe;

        config_udev_odev_setup_attribs(path, syspath, probe_callback);

    no_probe:
        udev_device_unref(udev_device);
    }
    udev_enumerate_unref(enumerate);
    return;
}
#endif