Commit | Line | Data |
---|---|---|
a09e091a JB |
1 | /* |
2 | * Copyright © 2009 Julien Cristau | |
3 | * | |
4 | * Permission is hereby granted, free of charge, to any person obtaining a | |
5 | * copy of this software and associated documentation files (the "Software"), | |
6 | * to deal in the Software without restriction, including without limitation | |
7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
8 | * and/or sell copies of the Software, and to permit persons to whom the | |
9 | * Software is furnished to do so, subject to the following conditions: | |
10 | * | |
11 | * The above copyright notice and this permission notice (including the next | |
12 | * paragraph) shall be included in all copies or substantial portions of the | |
13 | * Software. | |
14 | * | |
15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
18 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
20 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | |
21 | * DEALINGS IN THE SOFTWARE. | |
22 | * | |
23 | * Author: Julien Cristau <jcristau@debian.org> | |
24 | */ | |
25 | ||
26 | #ifdef HAVE_DIX_CONFIG_H | |
27 | #include <dix-config.h> | |
28 | #endif | |
29 | ||
30 | #include <libudev.h> | |
31 | #include <ctype.h> | |
32 | ||
33 | #include "input.h" | |
34 | #include "inputstr.h" | |
35 | #include "hotplug.h" | |
36 | #include "config-backends.h" | |
37 | #include "os.h" | |
38 | #include "globals.h" | |
39 | ||
40 | #define UDEV_XKB_PROP_KEY "xkb" | |
41 | ||
42 | #define LOG_PROPERTY(path, prop, val) \ | |
43 | LogMessageVerb(X_INFO, 10, \ | |
44 | "config/udev: getting property %s on %s " \ | |
45 | "returned \"%s\"\n", \ | |
46 | (prop), (path), (val) ? (val) : "(null)") | |
47 | #define LOG_SYSATTR(path, attr, val) \ | |
48 | LogMessageVerb(X_INFO, 10, \ | |
49 | "config/udev: getting attribute %s on %s " \ | |
50 | "returned \"%s\"\n", \ | |
51 | (attr), (path), (val) ? (val) : "(null)") | |
52 | ||
53 | static struct udev_monitor *udev_monitor; | |
54 | ||
55 | #ifdef CONFIG_UDEV_KMS | |
56 | static Bool | |
57 | config_udev_odev_setup_attribs(const char *path, const char *syspath, | |
58 | config_odev_probe_proc_ptr probe_callback); | |
59 | #endif | |
60 | ||
61 | static void | |
62 | device_added(struct udev_device *udev_device) | |
63 | { | |
64 | const char *path, *name = NULL; | |
65 | char *config_info = NULL; | |
66 | const char *syspath; | |
67 | const char *tags_prop; | |
68 | const char *key, *value, *tmp; | |
69 | InputOption *input_options; | |
70 | InputAttributes attrs = { }; | |
71 | DeviceIntPtr dev = NULL; | |
72 | struct udev_list_entry *set, *entry; | |
73 | struct udev_device *parent; | |
74 | int rc; | |
75 | const char *dev_seat; | |
76 | ||
77 | path = udev_device_get_devnode(udev_device); | |
78 | ||
79 | syspath = udev_device_get_syspath(udev_device); | |
80 | ||
81 | if (!path || !syspath) | |
82 | return; | |
83 | ||
84 | dev_seat = udev_device_get_property_value(udev_device, "ID_SEAT"); | |
85 | if (!dev_seat) | |
86 | dev_seat = "seat0"; | |
87 | ||
88 | if (SeatId && strcmp(dev_seat, SeatId)) | |
89 | return; | |
90 | ||
91 | if (!SeatId && strcmp(dev_seat, "seat0")) | |
92 | return; | |
93 | ||
94 | #ifdef CONFIG_UDEV_KMS | |
95 | if (!strcmp(udev_device_get_subsystem(udev_device), "drm")) { | |
96 | const char *sysname = udev_device_get_sysname(udev_device); | |
97 | ||
98 | if (strncmp(sysname, "card", 4) != 0) | |
99 | return; | |
100 | ||
101 | LogMessage(X_INFO, "config/udev: Adding drm device (%s)\n", path); | |
102 | ||
103 | config_udev_odev_setup_attribs(path, syspath, NewGPUDeviceRequest); | |
104 | return; | |
105 | } | |
106 | #endif | |
107 | ||
108 | if (!udev_device_get_property_value(udev_device, "ID_INPUT")) { | |
109 | LogMessageVerb(X_INFO, 10, | |
110 | "config/udev: ignoring device %s without " | |
111 | "property ID_INPUT set\n", path); | |
112 | return; | |
113 | } | |
114 | ||
115 | input_options = input_option_new(NULL, "_source", "server/udev"); | |
116 | if (!input_options) | |
117 | 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 | LOG_SYSATTR(ppath, "name", name); | |
128 | if (!name) { | |
129 | name = udev_device_get_property_value(parent, "NAME"); | |
130 | LOG_PROPERTY(ppath, "NAME", name); | |
131 | } | |
132 | ||
133 | /* construct USB ID in lowercase hex - "0000:ffff" */ | |
134 | if (product && | |
135 | sscanf(product, "%*x/%4x/%4x/%*x", &usb_vendor, &usb_model) == 2) { | |
136 | if (asprintf(&attrs.usb_id, "%04x:%04x", usb_vendor, usb_model) | |
137 | == -1) | |
138 | attrs.usb_id = NULL; | |
139 | else | |
140 | LOG_PROPERTY(ppath, "PRODUCT", product); | |
141 | } | |
142 | ||
143 | while (!pnp_id && (parent = udev_device_get_parent(parent))) { | |
144 | pnp_id = udev_device_get_sysattr_value(parent, "id"); | |
145 | if (!pnp_id) | |
146 | continue; | |
147 | ||
148 | attrs.pnp_id = strdup(pnp_id); | |
149 | ppath = udev_device_get_devnode(parent); | |
150 | LOG_SYSATTR(ppath, "id", pnp_id); | |
151 | } | |
152 | ||
153 | } | |
154 | if (!name) | |
155 | name = "(unnamed)"; | |
156 | else | |
157 | attrs.product = strdup(name); | |
158 | input_options = input_option_new(input_options, "name", name); | |
159 | input_options = input_option_new(input_options, "path", path); | |
160 | input_options = input_option_new(input_options, "device", path); | |
161 | if (path) | |
162 | attrs.device = strdup(path); | |
163 | ||
164 | tags_prop = udev_device_get_property_value(udev_device, "ID_INPUT.tags"); | |
165 | LOG_PROPERTY(path, "ID_INPUT.tags", tags_prop); | |
166 | attrs.tags = xstrtokenize(tags_prop, ","); | |
167 | ||
168 | if (asprintf(&config_info, "udev:%s", syspath) == -1) { | |
169 | config_info = NULL; | |
170 | goto unwind; | |
171 | } | |
172 | ||
173 | if (device_is_duplicate(config_info)) { | |
174 | LogMessage(X_WARNING, "config/udev: device %s already added. " | |
175 | "Ignoring.\n", name); | |
176 | goto unwind; | |
177 | } | |
178 | ||
179 | set = udev_device_get_properties_list_entry(udev_device); | |
180 | udev_list_entry_foreach(entry, set) { | |
181 | key = udev_list_entry_get_name(entry); | |
182 | if (!key) | |
183 | continue; | |
184 | value = udev_list_entry_get_value(entry); | |
185 | if (!strncasecmp(key, UDEV_XKB_PROP_KEY, sizeof(UDEV_XKB_PROP_KEY) - 1)) { | |
186 | LOG_PROPERTY(path, key, value); | |
187 | tmp = key + sizeof(UDEV_XKB_PROP_KEY) - 1; | |
188 | if (!strcasecmp(tmp, "rules")) | |
189 | input_options = | |
190 | input_option_new(input_options, "xkb_rules", value); | |
191 | else if (!strcasecmp(tmp, "layout")) | |
192 | input_options = | |
193 | input_option_new(input_options, "xkb_layout", value); | |
194 | else if (!strcasecmp(tmp, "variant")) | |
195 | input_options = | |
196 | input_option_new(input_options, "xkb_variant", value); | |
197 | else if (!strcasecmp(tmp, "model")) | |
198 | input_options = | |
199 | input_option_new(input_options, "xkb_model", value); | |
200 | else if (!strcasecmp(tmp, "options")) | |
201 | input_options = | |
202 | input_option_new(input_options, "xkb_options", value); | |
203 | } | |
204 | else if (!strcmp(key, "ID_VENDOR")) { | |
205 | LOG_PROPERTY(path, key, value); | |
206 | attrs.vendor = strdup(value); | |
207 | } | |
208 | else if (!strcmp(key, "ID_INPUT_KEY")) { | |
209 | LOG_PROPERTY(path, key, value); | |
210 | attrs.flags |= ATTR_KEYBOARD; | |
211 | } | |
212 | else if (!strcmp(key, "ID_INPUT_MOUSE")) { | |
213 | LOG_PROPERTY(path, key, value); | |
214 | attrs.flags |= ATTR_POINTER; | |
215 | } | |
216 | else if (!strcmp(key, "ID_INPUT_JOYSTICK")) { | |
217 | LOG_PROPERTY(path, key, value); | |
218 | attrs.flags |= ATTR_JOYSTICK; | |
219 | } | |
220 | else if (!strcmp(key, "ID_INPUT_TABLET")) { | |
221 | LOG_PROPERTY(path, key, value); | |
222 | attrs.flags |= ATTR_TABLET; | |
223 | } | |
224 | else if (!strcmp(key, "ID_INPUT_TOUCHPAD")) { | |
225 | LOG_PROPERTY(path, key, value); | |
226 | attrs.flags |= ATTR_TOUCHPAD; | |
227 | } | |
228 | else if (!strcmp(key, "ID_INPUT_TOUCHSCREEN")) { | |
229 | LOG_PROPERTY(path, key, value); | |
230 | attrs.flags |= ATTR_TOUCHSCREEN; | |
231 | } | |
232 | } | |
233 | ||
234 | input_options = input_option_new(input_options, "config_info", config_info); | |
235 | ||
236 | /* Default setting needed for non-seat0 seats */ | |
237 | if (ServerIsNotSeat0()) | |
238 | input_options = input_option_new(input_options, "GrabDevice", "on"); | |
239 | ||
240 | LogMessage(X_INFO, "config/udev: Adding input device %s (%s)\n", | |
241 | name, path); | |
242 | rc = NewInputDeviceRequest(input_options, &attrs, &dev); | |
243 | if (rc != Success) | |
244 | goto unwind; | |
245 | ||
246 | unwind: | |
247 | free(config_info); | |
248 | input_option_free_list(&input_options); | |
249 | ||
250 | free(attrs.usb_id); | |
251 | free(attrs.pnp_id); | |
252 | free(attrs.product); | |
253 | free(attrs.device); | |
254 | free(attrs.vendor); | |
255 | if (attrs.tags) { | |
256 | char **tag = attrs.tags; | |
257 | ||
258 | while (*tag) { | |
259 | free(*tag); | |
260 | tag++; | |
261 | } | |
262 | free(attrs.tags); | |
263 | } | |
264 | ||
265 | return; | |
266 | } | |
267 | ||
268 | static void | |
269 | device_removed(struct udev_device *device) | |
270 | { | |
271 | char *value; | |
272 | const char *syspath = udev_device_get_syspath(device); | |
273 | ||
274 | #ifdef CONFIG_UDEV_KMS | |
275 | if (!strcmp(udev_device_get_subsystem(device), "drm")) { | |
276 | const char *sysname = udev_device_get_sysname(device); | |
277 | const char *path = udev_device_get_devnode(device); | |
278 | ||
279 | if (strncmp(sysname,"card", 4) != 0) | |
280 | return; | |
281 | ErrorF("removing GPU device %s %s\n", syspath, path); | |
282 | if (!path) | |
283 | return; | |
284 | ||
285 | config_udev_odev_setup_attribs(path, syspath, DeleteGPUDeviceRequest); | |
286 | return; | |
287 | } | |
288 | #endif | |
289 | ||
290 | if (asprintf(&value, "udev:%s", syspath) == -1) | |
291 | return; | |
292 | ||
293 | remove_devices("udev", value); | |
294 | ||
295 | free(value); | |
296 | } | |
297 | ||
298 | static void | |
299 | wakeup_handler(pointer data, int err, pointer read_mask) | |
300 | { | |
301 | int udev_fd = udev_monitor_get_fd(udev_monitor); | |
302 | struct udev_device *udev_device; | |
303 | const char *action; | |
304 | ||
305 | if (err < 0) | |
306 | return; | |
307 | ||
308 | if (FD_ISSET(udev_fd, (fd_set *) read_mask)) { | |
309 | udev_device = udev_monitor_receive_device(udev_monitor); | |
310 | if (!udev_device) | |
311 | return; | |
312 | action = udev_device_get_action(udev_device); | |
313 | if (action) { | |
314 | if (!strcmp(action, "add")) { | |
315 | device_removed(udev_device); | |
316 | device_added(udev_device); | |
317 | } else if (!strcmp(action, "change")) { | |
318 | /* ignore change for the drm devices */ | |
319 | if (strcmp(udev_device_get_subsystem(udev_device), "drm")) { | |
320 | device_removed(udev_device); | |
321 | device_added(udev_device); | |
322 | } | |
323 | } | |
324 | else if (!strcmp(action, "remove")) | |
325 | device_removed(udev_device); | |
326 | } | |
327 | udev_device_unref(udev_device); | |
328 | } | |
329 | } | |
330 | ||
331 | static void | |
332 | block_handler(pointer data, struct timeval **tv, pointer read_mask) | |
333 | { | |
334 | } | |
335 | ||
336 | int | |
337 | config_udev_pre_init(void) | |
338 | { | |
339 | struct udev *udev; | |
340 | ||
341 | udev = udev_new(); | |
342 | if (!udev) | |
343 | return 0; | |
344 | ||
345 | udev_monitor = udev_monitor_new_from_netlink(udev, "udev"); | |
346 | if (!udev_monitor) | |
347 | return 0; | |
348 | ||
349 | udev_monitor_filter_add_match_subsystem_devtype(udev_monitor, "input", | |
350 | NULL); | |
351 | /* For Wacom serial devices */ | |
352 | udev_monitor_filter_add_match_subsystem_devtype(udev_monitor, "tty", NULL); | |
353 | #ifdef CONFIG_UDEV_KMS | |
354 | /* For output GPU devices */ | |
355 | udev_monitor_filter_add_match_subsystem_devtype(udev_monitor, "drm", NULL); | |
356 | #endif | |
357 | ||
358 | #ifdef HAVE_UDEV_MONITOR_FILTER_ADD_MATCH_TAG | |
359 | if (ServerIsNotSeat0()) | |
360 | udev_monitor_filter_add_match_tag(udev_monitor, SeatId); | |
361 | #endif | |
362 | if (udev_monitor_enable_receiving(udev_monitor)) { | |
363 | ErrorF("config/udev: failed to bind the udev monitor\n"); | |
364 | return 0; | |
365 | } | |
366 | return 1; | |
367 | } | |
368 | ||
369 | int | |
370 | config_udev_init(void) | |
371 | { | |
372 | struct udev *udev; | |
373 | struct udev_enumerate *enumerate; | |
374 | struct udev_list_entry *devices, *device; | |
375 | ||
376 | udev = udev_monitor_get_udev(udev_monitor); | |
377 | enumerate = udev_enumerate_new(udev); | |
378 | if (!enumerate) | |
379 | return 0; | |
380 | ||
381 | udev_enumerate_add_match_subsystem(enumerate, "input"); | |
382 | udev_enumerate_add_match_subsystem(enumerate, "tty"); | |
383 | #ifdef CONFIG_UDEV_KMS | |
384 | udev_enumerate_add_match_subsystem(enumerate, "drm"); | |
385 | #endif | |
386 | ||
387 | #ifdef HAVE_UDEV_ENUMERATE_ADD_MATCH_TAG | |
388 | if (ServerIsNotSeat0()) | |
389 | udev_enumerate_add_match_tag(enumerate, SeatId); | |
390 | #endif | |
391 | ||
392 | udev_enumerate_scan_devices(enumerate); | |
393 | devices = udev_enumerate_get_list_entry(enumerate); | |
394 | udev_list_entry_foreach(device, devices) { | |
395 | const char *syspath = udev_list_entry_get_name(device); | |
396 | struct udev_device *udev_device = | |
397 | udev_device_new_from_syspath(udev, syspath); | |
398 | ||
399 | /* Device might be gone by the time we try to open it */ | |
400 | if (!udev_device) | |
401 | continue; | |
402 | ||
403 | device_added(udev_device); | |
404 | udev_device_unref(udev_device); | |
405 | } | |
406 | udev_enumerate_unref(enumerate); | |
407 | ||
408 | RegisterBlockAndWakeupHandlers(block_handler, wakeup_handler, NULL); | |
409 | AddGeneralSocket(udev_monitor_get_fd(udev_monitor)); | |
410 | ||
411 | return 1; | |
412 | } | |
413 | ||
414 | void | |
415 | config_udev_fini(void) | |
416 | { | |
417 | struct udev *udev; | |
418 | ||
419 | if (!udev_monitor) | |
420 | return; | |
421 | ||
422 | udev = udev_monitor_get_udev(udev_monitor); | |
423 | ||
424 | RemoveGeneralSocket(udev_monitor_get_fd(udev_monitor)); | |
425 | RemoveBlockAndWakeupHandlers(block_handler, wakeup_handler, NULL); | |
426 | udev_monitor_unref(udev_monitor); | |
427 | udev_monitor = NULL; | |
428 | udev_unref(udev); | |
429 | } | |
430 | ||
431 | #ifdef CONFIG_UDEV_KMS | |
432 | ||
433 | static Bool | |
434 | config_udev_odev_setup_attribs(const char *path, const char *syspath, | |
435 | config_odev_probe_proc_ptr probe_callback) | |
436 | { | |
437 | struct OdevAttributes *attribs = config_odev_allocate_attribute_list(); | |
438 | int ret; | |
439 | ||
440 | if (!attribs) | |
441 | return FALSE; | |
442 | ||
443 | ret = config_odev_add_attribute(attribs, ODEV_ATTRIB_PATH, path); | |
444 | if (ret == FALSE) | |
445 | goto fail; | |
446 | ||
447 | ret = config_odev_add_attribute(attribs, ODEV_ATTRIB_SYSPATH, syspath); | |
448 | if (ret == FALSE) | |
449 | goto fail; | |
450 | ||
451 | /* ownership of attribs is passed to probe layer */ | |
452 | probe_callback(attribs); | |
453 | return TRUE; | |
454 | fail: | |
455 | config_odev_free_attributes(attribs); | |
456 | free(attribs); | |
457 | return FALSE; | |
458 | } | |
459 | ||
460 | void | |
461 | config_udev_odev_probe(config_odev_probe_proc_ptr probe_callback) | |
462 | { | |
463 | struct udev *udev; | |
464 | struct udev_enumerate *enumerate; | |
465 | struct udev_list_entry *devices, *device; | |
466 | ||
467 | udev = udev_monitor_get_udev(udev_monitor); | |
468 | enumerate = udev_enumerate_new(udev); | |
469 | if (!enumerate) | |
470 | return; | |
471 | ||
472 | udev_enumerate_add_match_subsystem(enumerate, "drm"); | |
473 | udev_enumerate_add_match_sysname(enumerate, "card[0-9]*"); | |
474 | #ifdef HAVE_UDEV_ENUMERATE_ADD_MATCH_TAG | |
475 | if (ServerIsNotSeat0()) | |
476 | udev_enumerate_add_match_tag(enumerate, SeatId); | |
477 | #endif | |
478 | udev_enumerate_scan_devices(enumerate); | |
479 | devices = udev_enumerate_get_list_entry(enumerate); | |
480 | udev_list_entry_foreach(device, devices) { | |
481 | const char *syspath = udev_list_entry_get_name(device); | |
482 | struct udev_device *udev_device = udev_device_new_from_syspath(udev, syspath); | |
483 | const char *path = udev_device_get_devnode(udev_device); | |
484 | const char *sysname = udev_device_get_sysname(udev_device); | |
485 | ||
486 | if (!path || !syspath) | |
487 | goto no_probe; | |
488 | else if (strcmp(udev_device_get_subsystem(udev_device), "drm") != 0) | |
489 | goto no_probe; | |
490 | else if (strncmp(sysname, "card", 4) != 0) | |
491 | goto no_probe; | |
492 | ||
493 | config_udev_odev_setup_attribs(path, syspath, probe_callback); | |
494 | ||
495 | no_probe: | |
496 | udev_device_unref(udev_device); | |
497 | } | |
498 | udev_enumerate_unref(enumerate); | |
499 | return; | |
500 | } | |
501 | #endif | |
502 |