1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
9 #include "alloc-util.h"
10 #include "device-util.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"
21 #include "terminal-util.h"
24 #define PCI_CLASS_GRAPHICS_CARD 0x30000
26 static int help(void) {
27 _cleanup_free_
char *link
= NULL
;
30 r
= terminal_urlify_man("systemd-backlight", "8", &link
);
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
,
49 static int has_multiple_graphics_cards(void) {
50 _cleanup_(sd_device_enumerator_unrefp
) sd_device_enumerator
*e
= NULL
;
54 r
= sd_device_enumerator_new(&e
);
58 r
= sd_device_enumerator_add_match_subsystem(e
, "pci", /* match = */ true);
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);
67 FOREACH_DEVICE(e
, dev
) {
71 if (sd_device_get_sysattr_value(dev
, "class", &s
) < 0)
74 if (safe_atolu(s
, &c
) < 0)
77 if (c
!= PCI_CLASS_GRAPHICS_CARD
)
81 return true; /* This is the second device. */
83 found
= true; /* Found the first device. */
89 static int find_pci_or_platform_parent(sd_device
*device
, sd_device
**ret
) {
97 r
= sd_device_get_parent(device
, &parent
);
101 if (device_in_subsystem(parent
, "drm")) {
103 r
= sd_device_get_sysname(parent
, &s
);
107 s
= startswith(s
, "card");
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! */
116 } else if (device_in_subsystem(parent
, "pci") &&
117 sd_device_get_sysattr_value(parent
, "class", &s
)) {
121 r
= safe_atolu(s
, &class);
123 return log_device_warning_errno(parent
, r
, "Cannot parse PCI class '%s': %m", s
);
126 if (class == PCI_CLASS_GRAPHICS_CARD
) {
131 } else if (device_in_subsystem(parent
, "platform")) {
136 return find_pci_or_platform_parent(parent
, ret
);
139 static int same_device(sd_device
*a
, sd_device
*b
) {
140 const char *a_val
, *b_val
;
146 r
= sd_device_get_subsystem(a
, &a_val
);
150 r
= sd_device_get_subsystem(b
, &b_val
);
154 if (!streq(a_val
, b_val
))
157 r
= sd_device_get_sysname(a
, &a_val
);
161 r
= sd_device_get_sysname(b
, &b_val
);
165 return streq(a_val
, b_val
);
168 static int validate_device(sd_device
*device
) {
169 _cleanup_(sd_device_enumerator_unrefp
) sd_device_enumerator
*enumerate
= NULL
;
170 const char *v
, *sysname
;
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. */
185 r
= sd_device_get_sysname(device
, &sysname
);
187 return log_device_debug_errno(device
, r
, "Failed to get sysname: %m");
189 if (!device_in_subsystem(device
, "backlight"))
190 return true; /* We assume LED device is always valid. */
192 r
= sd_device_get_sysattr_value(device
, "type", &v
);
194 return log_device_debug_errno(device
, r
, "Failed to read 'type' sysattr: %m");
195 if (!streq(v
, "raw"))
198 r
= find_pci_or_platform_parent(device
, &parent
);
200 return log_device_debug_errno(device
, r
, "Failed to find PCI or platform parent: %m");
203 const char *s
= NULL
, *subsystem
= NULL
;
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
));
210 r
= sd_device_enumerator_new(&enumerate
);
212 return log_oom_debug();
214 r
= sd_device_enumerator_allow_uninitialized(enumerate
);
216 return log_debug_errno(r
, "Failed to allow uninitialized devices: %m");
218 r
= sd_device_enumerator_add_match_subsystem(enumerate
, "backlight", /* match = */ true);
220 return log_debug_errno(r
, "Failed to add subsystem match: %m");
222 r
= sd_device_enumerator_add_nomatch_sysname(enumerate
, sysname
);
224 return log_debug_errno(r
, "Failed to add sysname unmatch: %m");
226 r
= sd_device_enumerator_add_match_sysattr(enumerate
, "type", "platform", /* match = */ true);
228 return log_debug_errno(r
, "Failed to add sysattr match: %m");
230 r
= sd_device_enumerator_add_match_sysattr(enumerate
, "type", "firmware", /* match = */ true);
232 return log_debug_errno(r
, "Failed to add sysattr match: %m");
234 if (device_in_subsystem(parent
, "pci")) {
235 r
= has_multiple_graphics_cards();
237 return log_debug_errno(r
, "Failed to check if the system has multiple graphics cards: %m");
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.");
245 r
= sd_device_enumerator_add_match_parent(enumerate
, parent
);
247 return log_debug_errno(r
, "Failed to add parent match: %m");
251 FOREACH_DEVICE(enumerate
, other
) {
252 sd_device
*other_parent
;
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
);
258 log_device_debug_errno(other
, r
, "Failed to get PCI or platform parent, ignoring: %m");
262 if (same_device(parent
, other_parent
) > 0) {
263 /* Both have the same PCI parent, that means we are out. */
265 const char *other_sysname
= NULL
, *other_type
= NULL
;
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
));
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. */
279 const char *other_sysname
= NULL
, *other_type
= NULL
;
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
));
294 static int read_max_brightness(sd_device
*device
, unsigned *ret
) {
295 unsigned max_brightness
;
302 r
= sd_device_get_sysattr_value(device
, "max_brightness", &s
);
304 return log_device_warning_errno(device
, r
, "Failed to read 'max_brightness' attribute: %m");
306 r
= safe_atou(s
, &max_brightness
);
308 return log_device_warning_errno(device
, r
, "Failed to parse 'max_brightness' \"%s\": %m", s
);
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.");
318 log_device_debug(device
, "Maximum brightness is %u", max_brightness
);
320 *ret
= max_brightness
;
321 return 1; /* valid max brightness */
324 static int clamp_brightness(
328 unsigned max_brightness
,
329 unsigned *brightness
) {
331 unsigned new_brightness
, min_brightness
;
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. */
341 min_brightness
= (unsigned) ((double) max_brightness
* percent
/ 100);
342 if (device_in_subsystem(device
, "backlight"))
343 min_brightness
= MAX(1U, min_brightness
);
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",
350 new_brightness
> *brightness
?
351 "too low; increasing" : "too high; decreasing",
354 *brightness
= new_brightness
;
358 static bool shall_clamp(sd_device
*device
, unsigned *ret
) {
359 const char *property
, *s
;
360 unsigned default_percent
;
366 if (device_in_subsystem(device
, "backlight")) {
367 property
= "ID_BACKLIGHT_CLAMP";
370 property
= "ID_LEDS_CLAMP";
374 r
= sd_device_get_property_value(device
, property
, &s
);
377 log_device_debug_errno(device
, r
, "Failed to get %s property, ignoring: %m", property
);
378 *ret
= default_percent
;
379 return default_percent
> 0;
382 r
= parse_boolean(s
);
388 r
= parse_percent(s
);
390 log_device_debug_errno(device
, r
, "Failed to parse %s property, ignoring: %m", property
);
391 *ret
= default_percent
;
392 return default_percent
> 0;
399 static int read_brightness(sd_device
*device
, unsigned max_brightness
, unsigned *ret_brightness
) {
405 assert(ret_brightness
);
407 if (device_in_subsystem(device
, "backlight")) {
408 r
= sd_device_get_sysattr_value(device
, "actual_brightness", &value
);
410 log_device_debug_errno(device
, r
, "Failed to read 'actual_brightness' attribute, "
411 "fall back to use 'brightness' attribute: %m");
415 return log_device_debug_errno(device
, r
, "Failed to read 'actual_brightness' attribute: %m");
417 r
= safe_atou(value
, &brightness
);
419 log_device_debug_errno(device
, r
, "Failed to parse 'actual_brightness' attribute, "
420 "fall back to use 'brightness' attribute: %s", value
);
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
);
430 log_device_debug(device
, "Current actual_brightness is %u", brightness
);
431 *ret_brightness
= brightness
;
436 r
= sd_device_get_sysattr_value(device
, "brightness", &value
);
438 return log_device_debug_errno(device
, r
, "Failed to read 'brightness' attribute: %m");
440 r
= safe_atou(value
, &brightness
);
442 return log_device_debug_errno(device
, r
, "Failed to parse 'brightness' attribute: %s", value
);
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
);
449 log_device_debug(device
, "Current brightness is %u", brightness
);
450 *ret_brightness
= brightness
;
454 static int build_save_file_path(sd_device
*device
, char **ret
) {
455 _cleanup_free_
char *escaped_subsystem
= NULL
, *escaped_sysname
= NULL
, *path
= NULL
;
462 r
= sd_device_get_subsystem(device
, &s
);
464 return log_device_error_errno(device
, r
, "Failed to get subsystem: %m");
466 escaped_subsystem
= cescape(s
);
467 if (!escaped_subsystem
)
470 r
= sd_device_get_sysname(device
, &s
);
472 return log_device_error_errno(device
, r
, "Failed to get sysname: %m");
474 escaped_sysname
= cescape(s
);
475 if (!escaped_sysname
)
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
)
483 path
= strjoin("/var/lib/systemd/backlight/", escaped_path_id
, ":", escaped_subsystem
, ":", escaped_sysname
);
485 path
= strjoin("/var/lib/systemd/backlight/", escaped_subsystem
, ":", escaped_sysname
);
489 *ret
= TAKE_PTR(path
);
493 static int read_saved_brightness(sd_device
*device
, unsigned *ret
) {
494 _cleanup_free_
char *path
= NULL
, *value
= NULL
;
500 r
= build_save_file_path(device
, &path
);
504 r
= read_one_line_file(path
, &value
);
507 log_device_error_errno(device
, r
, "Failed to read %s: %m", path
);
511 r
= safe_atou(value
, ret
);
513 log_device_warning_errno(device
, r
,
514 "Failed to parse saved brightness '%s', removing %s.",
520 log_device_debug(device
, "Using saved brightness %u.", *ret
);
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
;
533 sysname
= strchr(s
, ':');
535 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
536 "Requires a subsystem and sysname pair specifying a backlight or LED device.");
538 subsystem
= strndup(s
, sysname
- s
);
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'",
549 r
= sd_device_new_from_subsystem_sysname(&device
, subsystem
, sysname
);
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
);
558 return log_error_errno(r
, "Failed to get backlight or LED device '%s:%s': %m", subsystem
, sysname
);
560 *ret
= TAKE_PTR(device
);
561 return 1; /* Found. */
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
;
572 if (!shall_restore_state())
575 r
= device_new_from_arg(argv
[1], &device
);
579 r
= read_max_brightness(device
, &max_brightness
);
583 /* Ignore any errors in validation, and use the device as is. */
584 if (validate_device(device
) == 0)
587 clamp
= shall_clamp(device
, &percent
);
589 r
= read_saved_brightness(device
, &brightness
);
591 /* Fallback to clamping current brightness or exit early if clamping is not
592 * supported/enabled. */
596 r
= read_brightness(device
, max_brightness
, &brightness
);
598 return log_device_error_errno(device
, r
, "Failed to read current brightness: %m");
600 (void) clamp_brightness(device
, percent
, /* saved = */ false, max_brightness
, &brightness
);
602 (void) clamp_brightness(device
, percent
, /* saved = */ true, max_brightness
, &brightness
);
604 r
= sd_device_set_sysattr_valuef(device
, "brightness", "%u", brightness
);
606 return log_device_error_errno(device
, r
, "Failed to write system 'brightness' attribute: %m");
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
;
619 r
= device_new_from_arg(argv
[1], &device
);
623 r
= read_max_brightness(device
, &max_brightness
);
627 r
= build_save_file_path(device
, &path
);
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) {
641 r
= read_brightness(device
, max_brightness
, &brightness
);
643 return log_device_error_errno(device
, r
, "Failed to read current brightness: %m");
645 r
= write_string_filef(path
, WRITE_STRING_FILE_CREATE
| WRITE_STRING_FILE_MKDIR_0755
, "%u", brightness
);
647 return log_device_error_errno(device
, r
, "Failed to write %s: %m", path
);
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
},
661 if (argv_looks_like_help(argc
, argv
))
666 return dispatch_verb(argc
, argv
, verbs
, NULL
);
669 DEFINE_MAIN_FUNCTION(run
);