1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
9 #include "alloc-util.h"
10 #include "device-util.h"
13 #include "main-func.h"
15 #include "parse-util.h"
16 #include "percent-util.h"
17 #include "pretty-print.h"
18 #include "process-util.h"
19 #include "reboot-util.h"
20 #include "string-util.h"
22 #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_allow_uninitialized(e
);
62 r
= sd_device_enumerator_add_match_subsystem(e
, "pci", /* match = */ true);
66 /* class is an unsigned number, let's validate the value later. */
67 r
= sd_device_enumerator_add_match_sysattr(e
, "class", NULL
, /* match = */ true);
71 FOREACH_DEVICE(e
, dev
) {
75 if (sd_device_get_sysattr_value(dev
, "class", &s
) < 0)
78 if (safe_atolu(s
, &c
) < 0)
81 if (c
!= PCI_CLASS_GRAPHICS_CARD
)
85 return true; /* This is the second device. */
87 found
= true; /* Found the first device. */
93 static int find_pci_or_platform_parent(sd_device
*device
, sd_device
**ret
) {
94 const char *subsystem
, *sysname
, *value
;
101 r
= sd_device_get_parent(device
, &parent
);
105 r
= sd_device_get_subsystem(parent
, &subsystem
);
109 r
= sd_device_get_sysname(parent
, &sysname
);
113 if (streq(subsystem
, "drm")) {
116 c
= startswith(sysname
, "card");
120 c
+= strspn(c
, DIGITS
);
121 if (*c
== '-' && !STARTSWITH_SET(c
, "-LVDS-", "-Embedded DisplayPort-", "-eDP-"))
122 /* A connector DRM device, let's ignore all but LVDS and eDP! */
125 } else if (streq(subsystem
, "pci") &&
126 sd_device_get_sysattr_value(parent
, "class", &value
) >= 0) {
129 r
= safe_atolu(value
, &class);
131 return log_warning_errno(r
, "Cannot parse PCI class '%s' of device %s:%s: %m",
132 value
, subsystem
, sysname
);
135 if (class == PCI_CLASS_GRAPHICS_CARD
) {
140 } else if (streq(subsystem
, "platform")) {
145 return find_pci_or_platform_parent(parent
, ret
);
148 static int same_device(sd_device
*a
, sd_device
*b
) {
149 const char *a_val
, *b_val
;
155 r
= sd_device_get_subsystem(a
, &a_val
);
159 r
= sd_device_get_subsystem(b
, &b_val
);
163 if (!streq(a_val
, b_val
))
166 r
= sd_device_get_sysname(a
, &a_val
);
170 r
= sd_device_get_sysname(b
, &b_val
);
174 return streq(a_val
, b_val
);
177 static int validate_device(sd_device
*device
) {
178 _cleanup_(sd_device_enumerator_unrefp
) sd_device_enumerator
*enumerate
= NULL
;
179 const char *v
, *sysname
, *subsystem
;
185 /* Verify whether we should actually care for a specific backlight device. For backlight devices
186 * there might be multiple ways to access the same control: "firmware" (i.e. ACPI), "platform"
187 * (i.e. via the machine's EC) and "raw" (via the graphics card). In general we should prefer
188 * "firmware" (i.e. ACPI) or "platform" access over "raw" access, in order not to confuse the
189 * BIOS/EC, and compatibility with possible low-level hotkey handling of screen brightness. The
190 * kernel will already make sure to expose only one of "firmware" and "platform" for the same
191 * device to userspace. However, we still need to make sure that we use "raw" only if no
192 * "firmware" or "platform" device for the same device exists. */
194 r
= sd_device_get_sysname(device
, &sysname
);
196 return log_device_debug_errno(device
, r
, "Failed to get sysname: %m");
198 r
= sd_device_get_subsystem(device
, &subsystem
);
200 return log_device_debug_errno(device
, r
, "Failed to get subsystem: %m");
201 if (!streq(subsystem
, "backlight"))
204 r
= sd_device_get_sysattr_value(device
, "type", &v
);
206 return log_device_debug_errno(device
, r
, "Failed to read 'type' sysattr: %m");
207 if (!streq(v
, "raw"))
210 r
= find_pci_or_platform_parent(device
, &parent
);
212 return log_device_debug_errno(device
, r
, "Failed to find PCI or platform parent: %m");
214 r
= sd_device_get_subsystem(parent
, &subsystem
);
216 return log_device_debug_errno(parent
, r
, "Failed to get subsystem: %m");
219 const char *s
= NULL
;
221 (void) sd_device_get_syspath(parent
, &s
);
222 log_device_debug(device
, "Found %s parent device: %s", subsystem
, strna(s
));
225 r
= sd_device_enumerator_new(&enumerate
);
227 return log_oom_debug();
229 r
= sd_device_enumerator_allow_uninitialized(enumerate
);
231 return log_debug_errno(r
, "Failed to allow uninitialized devices: %m");
233 r
= sd_device_enumerator_add_match_subsystem(enumerate
, "backlight", /* match = */ true);
235 return log_debug_errno(r
, "Failed to add subsystem match: %m");
237 r
= sd_device_enumerator_add_nomatch_sysname(enumerate
, sysname
);
239 return log_debug_errno(r
, "Failed to add sysname unmatch: %m");
241 r
= sd_device_enumerator_add_match_sysattr(enumerate
, "type", "platform", /* match = */ true);
243 return log_debug_errno(r
, "Failed to add sysattr match: %m");
245 r
= sd_device_enumerator_add_match_sysattr(enumerate
, "type", "firmware", /* match = */ true);
247 return log_debug_errno(r
, "Failed to add sysattr match: %m");
249 if (streq(subsystem
, "pci")) {
250 r
= has_multiple_graphics_cards();
252 return log_debug_errno(r
, "Failed to check if the system has multiple graphics cards: %m");
254 /* If the system has multiple graphics cards, then we cannot associate platform
255 * devices on non-PCI bus (especially WMI bus) with PCI devices. Let's ignore all
256 * backlight devices that do not have the same parent PCI device. */
257 log_debug("Found multiple graphics cards on PCI bus. "
258 "Skipping to associate platform backlight devices on non-PCI bus.");
260 r
= sd_device_enumerator_add_match_parent(enumerate
, parent
);
262 return log_debug_errno(r
, "Failed to add parent match: %m");
266 FOREACH_DEVICE(enumerate
, other
) {
267 const char *other_subsystem
;
268 sd_device
*other_parent
;
270 /* OK, so there's another backlight device, and it's a platform or firmware device.
271 * Let's see if we can verify it belongs to the same device as ours. */
272 r
= find_pci_or_platform_parent(other
, &other_parent
);
274 log_device_debug_errno(other
, r
, "Failed to get PCI or platform parent, ignoring: %m");
278 if (same_device(parent
, other_parent
) > 0) {
279 /* Both have the same PCI parent, that means we are out. */
281 const char *other_sysname
= NULL
, *other_type
= NULL
;
283 (void) sd_device_get_sysname(other
, &other_sysname
);
284 (void) sd_device_get_sysattr_value(other
, "type", &other_type
);
285 log_device_debug(device
,
286 "Found another %s backlight device %s on the same PCI, skipping.",
287 strna(other_type
), strna(other_sysname
));
292 r
= sd_device_get_subsystem(other_parent
, &other_subsystem
);
294 log_device_debug_errno(other_parent
, r
, "Failed to get subsystem, ignoring: %m");
298 if (streq(other_subsystem
, "platform") && streq(subsystem
, "pci")) {
299 /* The other is connected to the platform bus and we are a PCI device, that also means we are out. */
301 const char *other_sysname
= NULL
, *other_type
= NULL
;
303 (void) sd_device_get_sysname(other
, &other_sysname
);
304 (void) sd_device_get_sysattr_value(other
, "type", &other_type
);
305 log_device_debug(device
,
306 "Found another %s backlight device %s, which has higher precedence, skipping.",
307 strna(other_type
), strna(other_sysname
));
316 static int get_max_brightness(sd_device
*device
, unsigned *ret
) {
323 r
= sd_device_get_sysattr_value(device
, "max_brightness", &s
);
325 return log_device_warning_errno(device
, r
, "Failed to read 'max_brightness' attribute: %m");
327 r
= safe_atou(s
, ret
);
329 return log_device_warning_errno(device
, r
, "Failed to parse 'max_brightness' \"%s\": %m", s
);
334 static int clamp_brightness(
338 unsigned max_brightness
,
339 unsigned *brightness
) {
341 unsigned new_brightness
, min_brightness
;
342 const char *subsystem
;
348 /* Some systems turn the backlight all the way off at the lowest levels. This clamps the saved
349 * brightness to at least 1 or 5% of max_brightness in case of 'backlight' subsystem. This
350 * avoids preserving an unreadably dim screen, which would otherwise force the user to disable
351 * state restoration. */
353 r
= sd_device_get_subsystem(device
, &subsystem
);
355 return log_device_warning_errno(device
, r
, "Failed to get device subsystem: %m");
357 if (streq(subsystem
, "backlight"))
358 min_brightness
= MAX(1U, (unsigned) ((double) max_brightness
* percent
/ 100));
362 new_brightness
= CLAMP(*brightness
, min_brightness
, max_brightness
);
363 if (new_brightness
!= *brightness
)
364 log_device_info(device
, "%s brightness %u is %s to %u.",
365 saved
? "Saved" : "Current",
367 new_brightness
> *brightness
?
368 "too low; increasing" : "too high; decreasing",
371 *brightness
= new_brightness
;
375 static bool shall_clamp(sd_device
*d
, unsigned *ret
) {
382 r
= sd_device_get_property_value(d
, "ID_BACKLIGHT_CLAMP", &s
);
385 log_device_debug_errno(d
, r
, "Failed to get ID_BACKLIGHT_CLAMP property, ignoring: %m");
386 *ret
= 5; /* defaults to 5% */
390 r
= parse_boolean(s
);
396 r
= parse_percent(s
);
398 log_device_debug_errno(d
, r
, "Failed to parse ID_BACKLIGHT_CLAMP property, ignoring: %m");
407 static int read_brightness(sd_device
*device
, unsigned max_brightness
, unsigned *ret_brightness
) {
408 const char *subsystem
, *value
;
413 assert(ret_brightness
);
415 r
= sd_device_get_subsystem(device
, &subsystem
);
417 return log_device_debug_errno(device
, r
, "Failed to get subsystem: %m");
419 if (streq(subsystem
, "backlight")) {
420 r
= sd_device_get_sysattr_value(device
, "actual_brightness", &value
);
422 log_device_debug_errno(device
, r
, "Failed to read 'actual_brightness' attribute, "
423 "fall back to use 'brightness' attribute: %m");
427 return log_device_debug_errno(device
, r
, "Failed to read 'actual_brightness' attribute: %m");
429 r
= safe_atou(value
, &brightness
);
431 log_device_debug_errno(device
, r
, "Failed to parse 'actual_brightness' attribute, "
432 "fall back to use 'brightness' attribute: %s", value
);
436 if (brightness
> max_brightness
) {
437 log_device_debug(device
, "actual_brightness=%u is larger than max_brightness=%u, "
438 "fall back to use 'brightness' attribute", brightness
, max_brightness
);
442 log_device_debug(device
, "Current actual_brightness is %u", brightness
);
443 *ret_brightness
= brightness
;
448 r
= sd_device_get_sysattr_value(device
, "brightness", &value
);
450 return log_device_debug_errno(device
, r
, "Failed to read 'brightness' attribute: %m");
452 r
= safe_atou(value
, &brightness
);
454 return log_device_debug_errno(device
, r
, "Failed to parse 'brightness' attribute: %s", value
);
456 if (brightness
> max_brightness
)
457 return log_device_debug_errno(device
, SYNTHETIC_ERRNO(EINVAL
),
458 "brightness=%u is larger than max_brightness=%u",
459 brightness
, max_brightness
);
461 log_device_debug(device
, "Current brightness is %u", brightness
);
462 *ret_brightness
= brightness
;
466 static int run(int argc
, char *argv
[]) {
467 _cleanup_(sd_device_unrefp
) sd_device
*device
= NULL
;
468 _cleanup_free_
char *escaped_ss
= NULL
, *escaped_sysname
= NULL
, *escaped_path_id
= NULL
;
469 const char *sysname
, *path_id
, *ss
, *saved
;
470 unsigned max_brightness
, brightness
;
475 if (argv_looks_like_help(argc
, argv
))
479 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "This program requires two arguments.");
481 if (!STR_IN_SET(argv
[1], "load", "save"))
482 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Unknown verb %s.", argv
[1]);
486 r
= mkdir_p("/var/lib/systemd/backlight", 0755);
488 return log_error_errno(r
, "Failed to create backlight directory /var/lib/systemd/backlight: %m");
490 sysname
= strchr(argv
[2], ':');
492 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Requires a subsystem and sysname pair specifying a backlight device.");
494 ss
= strndupa_safe(argv
[2], sysname
- argv
[2]);
498 if (!STR_IN_SET(ss
, "backlight", "leds"))
499 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Not a backlight or LED device: '%s:%s'", ss
, sysname
);
501 r
= sd_device_new_from_subsystem_sysname(&device
, ss
, sysname
);
503 bool ignore
= r
== -ENODEV
;
505 /* Some drivers, e.g. for AMD GPU, removes acpi backlight device soon after it is added.
506 * See issue #21997. */
507 log_full_errno(ignore
? LOG_DEBUG
: LOG_ERR
, r
,
508 "Failed to get backlight or LED device '%s:%s'%s: %m",
509 ss
, sysname
, ignore
? ", ignoring" : "");
510 return ignore
? 0 : r
;
513 /* If max_brightness is 0, then there is no actual backlight device. This happens on desktops
514 * with Asus mainboards that load the eeepc-wmi module. */
515 if (get_max_brightness(device
, &max_brightness
) < 0)
518 if (max_brightness
== 0) {
519 log_device_warning(device
, "Maximum brightness is 0, ignoring device.");
523 log_device_debug(device
, "Maximum brightness is %u", max_brightness
);
525 escaped_ss
= cescape(ss
);
529 escaped_sysname
= cescape(sysname
);
530 if (!escaped_sysname
)
533 if (sd_device_get_property_value(device
, "ID_PATH", &path_id
) >= 0) {
534 escaped_path_id
= cescape(path_id
);
535 if (!escaped_path_id
)
538 saved
= strjoina("/var/lib/systemd/backlight/", escaped_path_id
, ":", escaped_ss
, ":", escaped_sysname
);
540 saved
= strjoina("/var/lib/systemd/backlight/", escaped_ss
, ":", escaped_sysname
);
542 /* If there are multiple conflicting backlight devices, then their probing at boot-time might
543 * happen in any order. This means the validity checking of the device then is not reliable,
544 * since it might not see other devices conflicting with a specific backlight. To deal with
545 * this, we will actively delete backlight state files at shutdown (where device probing should
546 * be complete), so that the validity check at boot time doesn't have to be reliable. */
548 if (streq(argv
[1], "load")) {
549 _cleanup_free_
char *value
= NULL
;
553 if (!shall_restore_state())
556 if (validate_device(device
) == 0)
559 clamp
= shall_clamp(device
, &percent
);
561 r
= read_one_line_file(saved
, &value
);
562 if (r
< 0 && r
!= -ENOENT
)
563 return log_error_errno(r
, "Failed to read %s: %m", saved
);
565 r
= safe_atou(value
, &brightness
);
567 log_warning_errno(r
, "Failed to parse saved brightness '%s', removing %s.",
569 (void) unlink(saved
);
571 log_debug("Using saved brightness %u.", brightness
);
573 (void) clamp_brightness(device
, percent
, /* saved = */ true, max_brightness
, &brightness
);
575 /* Do not fall back to read current brightness below. */
580 /* Fallback to clamping current brightness or exit early if clamping is not
581 * supported/enabled. */
585 r
= read_brightness(device
, max_brightness
, &brightness
);
587 return log_device_error_errno(device
, r
, "Failed to read current brightness: %m");
589 (void) clamp_brightness(device
, percent
, /* saved = */ false, max_brightness
, &brightness
);
592 r
= sd_device_set_sysattr_valuef(device
, "brightness", "%u", brightness
);
594 return log_device_error_errno(device
, r
, "Failed to write system 'brightness' attribute: %m");
596 } else if (streq(argv
[1], "save")) {
597 if (validate_device(device
) == 0) {
598 (void) unlink(saved
);
602 r
= read_brightness(device
, max_brightness
, &brightness
);
604 return log_device_error_errno(device
, r
, "Failed to read current brightness: %m");
606 r
= write_string_filef(saved
, WRITE_STRING_FILE_CREATE
, "%u", brightness
);
608 return log_device_error_errno(device
, r
, "Failed to write %s: %m", saved
);
611 assert_not_reached();
616 DEFINE_MAIN_FUNCTION(run
);