]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/backlight/backlight.c
docs/RANDOM_SEEDS: update NetBSD link
[thirdparty/systemd.git] / src / backlight / backlight.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <sys/stat.h>
4 #include <sys/types.h>
5 #include <unistd.h>
6
7 #include "sd-device.h"
8
9 #include "alloc-util.h"
10 #include "device-util.h"
11 #include "escape.h"
12 #include "fileio.h"
13 #include "main-func.h"
14 #include "parse-util.h"
15 #include "percent-util.h"
16 #include "pretty-print.h"
17 #include "process-util.h"
18 #include "reboot-util.h"
19 #include "string-util.h"
20 #include "strv.h"
21 #include "terminal-util.h"
22 #include "verbs.h"
23
24 #define PCI_CLASS_GRAPHICS_CARD 0x30000
25
26 static int help(void) {
27 _cleanup_free_ char *link = NULL;
28 int r;
29
30 r = terminal_urlify_man("systemd-backlight", "8", &link);
31 if (r < 0)
32 return log_oom();
33
34 printf("%s save [backlight|leds]:DEVICE\n"
35 "%s load [backlight|leds]:DEVICE\n"
36 "\n%sSave and restore backlight brightness at shutdown and boot.%s\n\n"
37 " save Save current brightness\n"
38 " load Set brightness to be the previously saved value\n"
39 "\nSee the %s for details.\n",
40 program_invocation_short_name,
41 program_invocation_short_name,
42 ansi_highlight(),
43 ansi_normal(),
44 link);
45
46 return 0;
47 }
48
49 static int has_multiple_graphics_cards(void) {
50 _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
51 bool found = false;
52 int r;
53
54 r = sd_device_enumerator_new(&e);
55 if (r < 0)
56 return r;
57
58 r = sd_device_enumerator_add_match_subsystem(e, "pci", /* match = */ true);
59 if (r < 0)
60 return r;
61
62 /* class is an unsigned number, let's validate the value later. */
63 r = sd_device_enumerator_add_match_sysattr(e, "class", NULL, /* match = */ true);
64 if (r < 0)
65 return r;
66
67 FOREACH_DEVICE(e, dev) {
68 const char *s;
69 unsigned long c;
70
71 if (sd_device_get_sysattr_value(dev, "class", &s) < 0)
72 continue;
73
74 if (safe_atolu(s, &c) < 0)
75 continue;
76
77 if (c != PCI_CLASS_GRAPHICS_CARD)
78 continue;
79
80 if (found)
81 return true; /* This is the second device. */
82
83 found = true; /* Found the first device. */
84 }
85
86 return false;
87 }
88
89 static int find_pci_or_platform_parent(sd_device *device, sd_device **ret) {
90 sd_device *parent;
91 const char *s;
92 int r;
93
94 assert(device);
95 assert(ret);
96
97 r = sd_device_get_parent(device, &parent);
98 if (r < 0)
99 return r;
100
101 if (device_in_subsystem(parent, "drm")) {
102
103 r = sd_device_get_sysname(parent, &s);
104 if (r < 0)
105 return r;
106
107 s = startswith(s, "card");
108 if (!s)
109 return -ENODATA;
110
111 s += strspn(s, DIGITS);
112 if (*s == '-' && !STARTSWITH_SET(s, "-LVDS-", "-Embedded DisplayPort-", "-eDP-"))
113 /* A connector DRM device, let's ignore all but LVDS and eDP! */
114 return -EOPNOTSUPP;
115
116 } else if (device_in_subsystem(parent, "pci") &&
117 sd_device_get_sysattr_value(parent, "class", &s)) {
118
119 unsigned long class;
120
121 r = safe_atolu(s, &class);
122 if (r < 0)
123 return log_device_warning_errno(parent, r, "Cannot parse PCI class '%s': %m", s);
124
125 /* Graphics card */
126 if (class == PCI_CLASS_GRAPHICS_CARD) {
127 *ret = parent;
128 return 0;
129 }
130
131 } else if (device_in_subsystem(parent, "platform")) {
132 *ret = parent;
133 return 0;
134 }
135
136 return find_pci_or_platform_parent(parent, ret);
137 }
138
139 static int same_device(sd_device *a, sd_device *b) {
140 const char *a_val, *b_val;
141 int r;
142
143 assert(a);
144 assert(b);
145
146 r = sd_device_get_subsystem(a, &a_val);
147 if (r < 0)
148 return r;
149
150 r = sd_device_get_subsystem(b, &b_val);
151 if (r < 0)
152 return r;
153
154 if (!streq(a_val, b_val))
155 return false;
156
157 r = sd_device_get_sysname(a, &a_val);
158 if (r < 0)
159 return r;
160
161 r = sd_device_get_sysname(b, &b_val);
162 if (r < 0)
163 return r;
164
165 return streq(a_val, b_val);
166 }
167
168 static int validate_device(sd_device *device) {
169 _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *enumerate = NULL;
170 const char *v, *sysname;
171 sd_device *parent;
172 int r;
173
174 assert(device);
175
176 /* Verify whether we should actually care for a specific backlight device. For backlight devices
177 * there might be multiple ways to access the same control: "firmware" (i.e. ACPI), "platform"
178 * (i.e. via the machine's EC), and "raw" (via the graphics card). In general we should prefer
179 * "firmware" (i.e. ACPI) or "platform" access over "raw" access, in order not to confuse the
180 * BIOS/EC, and compatibility with possible low-level hotkey handling of screen brightness. The
181 * kernel will already make sure to expose only one of "firmware" and "platform" for the same
182 * device to userspace. However, we still need to make sure that we use "raw" only if no
183 * "firmware" or "platform" device for the same device exists. */
184
185 r = sd_device_get_sysname(device, &sysname);
186 if (r < 0)
187 return log_device_debug_errno(device, r, "Failed to get sysname: %m");
188
189 if (!device_in_subsystem(device, "backlight"))
190 return true; /* We assume LED device is always valid. */
191
192 r = sd_device_get_sysattr_value(device, "type", &v);
193 if (r < 0)
194 return log_device_debug_errno(device, r, "Failed to read 'type' sysattr: %m");
195 if (!streq(v, "raw"))
196 return true;
197
198 r = find_pci_or_platform_parent(device, &parent);
199 if (r < 0)
200 return log_device_debug_errno(device, r, "Failed to find PCI or platform parent: %m");
201
202 if (DEBUG_LOGGING) {
203 const char *s = NULL, *subsystem = NULL;
204
205 (void) sd_device_get_syspath(parent, &s);
206 (void) sd_device_get_subsystem(parent, &subsystem);
207 log_device_debug(device, "Found %s parent device: %s", strna(subsystem), strna(s));
208 }
209
210 r = sd_device_enumerator_new(&enumerate);
211 if (r < 0)
212 return log_oom_debug();
213
214 r = sd_device_enumerator_allow_uninitialized(enumerate);
215 if (r < 0)
216 return log_debug_errno(r, "Failed to allow uninitialized devices: %m");
217
218 r = sd_device_enumerator_add_match_subsystem(enumerate, "backlight", /* match = */ true);
219 if (r < 0)
220 return log_debug_errno(r, "Failed to add subsystem match: %m");
221
222 r = sd_device_enumerator_add_nomatch_sysname(enumerate, sysname);
223 if (r < 0)
224 return log_debug_errno(r, "Failed to add sysname unmatch: %m");
225
226 r = sd_device_enumerator_add_match_sysattr(enumerate, "type", "platform", /* match = */ true);
227 if (r < 0)
228 return log_debug_errno(r, "Failed to add sysattr match: %m");
229
230 r = sd_device_enumerator_add_match_sysattr(enumerate, "type", "firmware", /* match = */ true);
231 if (r < 0)
232 return log_debug_errno(r, "Failed to add sysattr match: %m");
233
234 if (device_in_subsystem(parent, "pci")) {
235 r = has_multiple_graphics_cards();
236 if (r < 0)
237 return log_debug_errno(r, "Failed to check if the system has multiple graphics cards: %m");
238 if (r > 0) {
239 /* If the system has multiple graphics cards, then we cannot associate platform
240 * devices on non-PCI bus (especially WMI bus) with PCI devices. Let's ignore all
241 * backlight devices that do not have the same parent PCI device. */
242 log_debug("Found multiple graphics cards on PCI bus; "
243 "skipping deduplication of platform backlight devices not on PCI bus.");
244
245 r = sd_device_enumerator_add_match_parent(enumerate, parent);
246 if (r < 0)
247 return log_debug_errno(r, "Failed to add parent match: %m");
248 }
249 }
250
251 FOREACH_DEVICE(enumerate, other) {
252 sd_device *other_parent;
253
254 /* OK, so there's another backlight device, and it's a platform or firmware device.
255 * Let's see if we can verify it belongs to the same device as ours. */
256 r = find_pci_or_platform_parent(other, &other_parent);
257 if (r < 0) {
258 log_device_debug_errno(other, r, "Failed to get PCI or platform parent, ignoring: %m");
259 continue;
260 }
261
262 if (same_device(parent, other_parent) > 0) {
263 /* Both have the same PCI parent, that means we are out. */
264 if (DEBUG_LOGGING) {
265 const char *other_sysname = NULL, *other_type = NULL;
266
267 (void) sd_device_get_sysname(other, &other_sysname);
268 (void) sd_device_get_sysattr_value(other, "type", &other_type);
269 log_device_debug(device,
270 "Found another %s backlight device %s on the same PCI, skipping.",
271 strna(other_type), strna(other_sysname));
272 }
273 return false;
274 }
275
276 if (device_in_subsystem(other_parent, "platform") && device_in_subsystem(parent, "pci")) {
277 /* The other is connected to the platform bus and we are a PCI device, that also means we are out. */
278 if (DEBUG_LOGGING) {
279 const char *other_sysname = NULL, *other_type = NULL;
280
281 (void) sd_device_get_sysname(other, &other_sysname);
282 (void) sd_device_get_sysattr_value(other, "type", &other_type);
283 log_device_debug(device,
284 "Found another %s backlight device %s, which has higher precedence, skipping.",
285 strna(other_type), strna(other_sysname));
286 }
287 return false;
288 }
289 }
290
291 return true;
292 }
293
294 static int read_max_brightness(sd_device *device, unsigned *ret) {
295 unsigned max_brightness;
296 const char *s;
297 int r;
298
299 assert(device);
300 assert(ret);
301
302 r = sd_device_get_sysattr_value(device, "max_brightness", &s);
303 if (r < 0)
304 return log_device_warning_errno(device, r, "Failed to read 'max_brightness' attribute: %m");
305
306 r = safe_atou(s, &max_brightness);
307 if (r < 0)
308 return log_device_warning_errno(device, r, "Failed to parse 'max_brightness' \"%s\": %m", s);
309
310 /* If max_brightness is 0, then there is no actual backlight device. This happens on desktops
311 * with Asus mainboards that load the eeepc-wmi module. */
312 if (max_brightness == 0) {
313 log_device_warning(device, "Maximum brightness is 0, ignoring device.");
314 *ret = 0;
315 return 0;
316 }
317
318 log_device_debug(device, "Maximum brightness is %u", max_brightness);
319
320 *ret = max_brightness;
321 return 1; /* valid max brightness */
322 }
323
324 static int clamp_brightness(
325 sd_device *device,
326 unsigned percent,
327 bool saved,
328 unsigned max_brightness,
329 unsigned *brightness) {
330
331 unsigned new_brightness, min_brightness;
332
333 assert(device);
334 assert(brightness);
335
336 /* Some systems turn the backlight all the way off at the lowest levels. This clamps the saved
337 * brightness to at least 1 or 5% of max_brightness in case of 'backlight' subsystem. This
338 * avoids preserving an unreadably dim screen, which would otherwise force the user to disable
339 * state restoration. */
340
341 min_brightness = (unsigned) ((double) max_brightness * percent / 100);
342 if (device_in_subsystem(device, "backlight"))
343 min_brightness = MAX(1U, min_brightness);
344
345 new_brightness = CLAMP(*brightness, min_brightness, max_brightness);
346 if (new_brightness != *brightness)
347 log_device_info(device, "%s brightness %u is %s to %u.",
348 saved ? "Saved" : "Current",
349 *brightness,
350 new_brightness > *brightness ?
351 "too low; increasing" : "too high; decreasing",
352 new_brightness);
353
354 *brightness = new_brightness;
355 return 0;
356 }
357
358 static bool shall_clamp(sd_device *device, unsigned *ret) {
359 const char *property, *s;
360 unsigned default_percent;
361 int r;
362
363 assert(device);
364 assert(ret);
365
366 if (device_in_subsystem(device, "backlight")) {
367 property = "ID_BACKLIGHT_CLAMP";
368 default_percent = 5;
369 } else {
370 property = "ID_LEDS_CLAMP";
371 default_percent = 0;
372 }
373
374 r = sd_device_get_property_value(device, property, &s);
375 if (r < 0) {
376 if (r != -ENOENT)
377 log_device_debug_errno(device, r, "Failed to get %s property, ignoring: %m", property);
378 *ret = default_percent;
379 return default_percent > 0;
380 }
381
382 r = parse_boolean(s);
383 if (r >= 0) {
384 *ret = r ? 5 : 0;
385 return r;
386 }
387
388 r = parse_percent(s);
389 if (r < 0) {
390 log_device_debug_errno(device, r, "Failed to parse %s property, ignoring: %m", property);
391 *ret = default_percent;
392 return default_percent > 0;
393 }
394
395 *ret = r;
396 return true;
397 }
398
399 static int read_brightness(sd_device *device, unsigned max_brightness, unsigned *ret_brightness) {
400 const char *value;
401 unsigned brightness;
402 int r;
403
404 assert(device);
405 assert(ret_brightness);
406
407 if (device_in_subsystem(device, "backlight")) {
408 r = sd_device_get_sysattr_value(device, "actual_brightness", &value);
409 if (r == -ENOENT) {
410 log_device_debug_errno(device, r, "Failed to read 'actual_brightness' attribute, "
411 "fall back to use 'brightness' attribute: %m");
412 goto use_brightness;
413 }
414 if (r < 0)
415 return log_device_debug_errno(device, r, "Failed to read 'actual_brightness' attribute: %m");
416
417 r = safe_atou(value, &brightness);
418 if (r < 0) {
419 log_device_debug_errno(device, r, "Failed to parse 'actual_brightness' attribute, "
420 "fall back to use 'brightness' attribute: %s", value);
421 goto use_brightness;
422 }
423
424 if (brightness > max_brightness) {
425 log_device_debug(device, "actual_brightness=%u is larger than max_brightness=%u, "
426 "fall back to use 'brightness' attribute", brightness, max_brightness);
427 goto use_brightness;
428 }
429
430 log_device_debug(device, "Current actual_brightness is %u", brightness);
431 *ret_brightness = brightness;
432 return 0;
433 }
434
435 use_brightness:
436 r = sd_device_get_sysattr_value(device, "brightness", &value);
437 if (r < 0)
438 return log_device_debug_errno(device, r, "Failed to read 'brightness' attribute: %m");
439
440 r = safe_atou(value, &brightness);
441 if (r < 0)
442 return log_device_debug_errno(device, r, "Failed to parse 'brightness' attribute: %s", value);
443
444 if (brightness > max_brightness)
445 return log_device_debug_errno(device, SYNTHETIC_ERRNO(EINVAL),
446 "brightness=%u is larger than max_brightness=%u",
447 brightness, max_brightness);
448
449 log_device_debug(device, "Current brightness is %u", brightness);
450 *ret_brightness = brightness;
451 return 0;
452 }
453
454 static int build_save_file_path(sd_device *device, char **ret) {
455 _cleanup_free_ char *escaped_subsystem = NULL, *escaped_sysname = NULL, *path = NULL;
456 const char *s;
457 int r;
458
459 assert(device);
460 assert(ret);
461
462 r = sd_device_get_subsystem(device, &s);
463 if (r < 0)
464 return log_device_error_errno(device, r, "Failed to get subsystem: %m");
465
466 escaped_subsystem = cescape(s);
467 if (!escaped_subsystem)
468 return log_oom();
469
470 r = sd_device_get_sysname(device, &s);
471 if (r < 0)
472 return log_device_error_errno(device, r, "Failed to get sysname: %m");
473
474 escaped_sysname = cescape(s);
475 if (!escaped_sysname)
476 return log_oom();
477
478 if (sd_device_get_property_value(device, "ID_PATH", &s) >= 0) {
479 _cleanup_free_ char *escaped_path_id = cescape(s);
480 if (!escaped_path_id)
481 return log_oom();
482
483 path = strjoin("/var/lib/systemd/backlight/", escaped_path_id, ":", escaped_subsystem, ":", escaped_sysname);
484 } else
485 path = strjoin("/var/lib/systemd/backlight/", escaped_subsystem, ":", escaped_sysname);
486 if (!path)
487 return log_oom();
488
489 *ret = TAKE_PTR(path);
490 return 0;
491 }
492
493 static int read_saved_brightness(sd_device *device, unsigned *ret) {
494 _cleanup_free_ char *path = NULL, *value = NULL;
495 int r;
496
497 assert(device);
498 assert(ret);
499
500 r = build_save_file_path(device, &path);
501 if (r < 0)
502 return r;
503
504 r = read_one_line_file(path, &value);
505 if (r < 0) {
506 if (r != -ENOENT)
507 log_device_error_errno(device, r, "Failed to read %s: %m", path);
508 return r;
509 }
510
511 r = safe_atou(value, ret);
512 if (r < 0) {
513 log_device_warning_errno(device, r,
514 "Failed to parse saved brightness '%s', removing %s.",
515 value, path);
516 (void) unlink(path);
517 return r;
518 }
519
520 log_device_debug(device, "Using saved brightness %u.", *ret);
521 return 0;
522 }
523
524 static int device_new_from_arg(const char *s, sd_device **ret) {
525 _cleanup_(sd_device_unrefp) sd_device *device = NULL;
526 _cleanup_free_ char *subsystem = NULL;
527 const char *sysname;
528 int r;
529
530 assert(s);
531 assert(ret);
532
533 sysname = strchr(s, ':');
534 if (!sysname)
535 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
536 "Requires a subsystem and sysname pair specifying a backlight or LED device.");
537
538 subsystem = strndup(s, sysname - s);
539 if (!subsystem)
540 return log_oom();
541
542 sysname++;
543
544 if (!STR_IN_SET(subsystem, "backlight", "leds"))
545 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
546 "Not a backlight or LED device: '%s:%s'",
547 subsystem, sysname);
548
549 r = sd_device_new_from_subsystem_sysname(&device, subsystem, sysname);
550 if (r == -ENODEV) {
551 /* Some drivers, e.g. for AMD GPU, removes acpi backlight device soon after it is added.
552 * See issue #21997. */
553 log_debug_errno(r, "Failed to get backlight or LED device '%s:%s', ignoring: %m", subsystem, sysname);
554 *ret = NULL;
555 return 0;
556 }
557 if (r < 0)
558 return log_error_errno(r, "Failed to get backlight or LED device '%s:%s': %m", subsystem, sysname);
559
560 *ret = TAKE_PTR(device);
561 return 1; /* Found. */
562 }
563
564 static int verb_load(int argc, char *argv[], void *userdata) {
565 _cleanup_(sd_device_unrefp) sd_device *device = NULL;
566 unsigned max_brightness, brightness, percent;
567 bool clamp;
568 int r;
569
570 assert(argc == 2);
571
572 if (!shall_restore_state())
573 return 0;
574
575 r = device_new_from_arg(argv[1], &device);
576 if (r <= 0)
577 return r;
578
579 r = read_max_brightness(device, &max_brightness);
580 if (r <= 0)
581 return r;
582
583 /* Ignore any errors in validation, and use the device as is. */
584 if (validate_device(device) == 0)
585 return 0;
586
587 clamp = shall_clamp(device, &percent);
588
589 r = read_saved_brightness(device, &brightness);
590 if (r < 0) {
591 /* Fallback to clamping current brightness or exit early if clamping is not
592 * supported/enabled. */
593 if (!clamp)
594 return 0;
595
596 r = read_brightness(device, max_brightness, &brightness);
597 if (r < 0)
598 return log_device_error_errno(device, r, "Failed to read current brightness: %m");
599
600 (void) clamp_brightness(device, percent, /* saved = */ false, max_brightness, &brightness);
601 } else if (clamp)
602 (void) clamp_brightness(device, percent, /* saved = */ true, max_brightness, &brightness);
603
604 r = sd_device_set_sysattr_valuef(device, "brightness", "%u", brightness);
605 if (r < 0)
606 return log_device_error_errno(device, r, "Failed to write system 'brightness' attribute: %m");
607
608 return 0;
609 }
610
611 static int verb_save(int argc, char *argv[], void *userdata) {
612 _cleanup_(sd_device_unrefp) sd_device *device = NULL;
613 _cleanup_free_ char *path = NULL;
614 unsigned max_brightness, brightness;
615 int r;
616
617 assert(argc == 2);
618
619 r = device_new_from_arg(argv[1], &device);
620 if (r <= 0)
621 return r;
622
623 r = read_max_brightness(device, &max_brightness);
624 if (r <= 0)
625 return r;
626
627 r = build_save_file_path(device, &path);
628 if (r < 0)
629 return r;
630
631 /* If there are multiple conflicting backlight devices, then their probing at boot-time might
632 * happen in any order. This means the validity checking of the device then is not reliable,
633 * since it might not see other devices conflicting with a specific backlight. To deal with
634 * this, we will actively delete backlight state files at shutdown (where device probing should
635 * be complete), so that the validity check at boot time doesn't have to be reliable. */
636 if (validate_device(device) == 0) {
637 (void) unlink(path);
638 return 0;
639 }
640
641 r = read_brightness(device, max_brightness, &brightness);
642 if (r < 0)
643 return log_device_error_errno(device, r, "Failed to read current brightness: %m");
644
645 r = write_string_filef(path, WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_MKDIR_0755, "%u", brightness);
646 if (r < 0)
647 return log_device_error_errno(device, r, "Failed to write %s: %m", path);
648
649 return 0;
650 }
651
652 static int run(int argc, char *argv[]) {
653 static const Verb verbs[] = {
654 { "load", 2, 2, VERB_ONLINE_ONLY, verb_load },
655 { "save", 2, 2, VERB_ONLINE_ONLY, verb_save },
656 {}
657 };
658
659 log_setup();
660
661 if (argv_looks_like_help(argc, argv))
662 return help();
663
664 umask(0022);
665
666 return dispatch_verb(argc, argv, verbs, NULL);
667 }
668
669 DEFINE_MAIN_FUNCTION(run);