1 /* SPDX-License-Identifier: LGPL-2.1+ */
5 #include "alloc-util.h"
6 #include "device-util.h"
10 #include "parse-util.h"
11 #include "proc-cmdline.h"
12 #include "string-util.h"
16 static int find_pci_or_platform_parent(sd_device
*device
, sd_device
**ret
) {
17 const char *subsystem
, *sysname
, *value
;
24 r
= sd_device_get_parent(device
, &parent
);
28 r
= sd_device_get_subsystem(parent
, &subsystem
);
32 r
= sd_device_get_sysname(parent
, &sysname
);
36 if (streq(subsystem
, "drm")) {
39 c
= startswith(sysname
, "card");
43 c
+= strspn(c
, DIGITS
);
45 /* A connector DRM device, let's ignore all but LVDS and eDP! */
47 if (!startswith(c
, "-LVDS-") &&
48 !startswith(c
, "-Embedded DisplayPort-"))
52 } else if (streq(subsystem
, "pci") &&
53 sd_device_get_sysattr_value(parent
, "class", &value
) >= 0) {
54 unsigned long class = 0;
56 r
= safe_atolu(value
, &class);
58 return log_warning_errno(r
, "Cannot parse PCI class '%s' of device %s:%s: %m",
59 value
, subsystem
, sysname
);
62 if (class == 0x30000) {
67 } else if (streq(subsystem
, "platform")) {
72 return find_pci_or_platform_parent(parent
, ret
);
75 static int same_device(sd_device
*a
, sd_device
*b
) {
76 const char *a_val
, *b_val
;
82 r
= sd_device_get_subsystem(a
, &a_val
);
86 r
= sd_device_get_subsystem(b
, &b_val
);
90 if (!streq(a_val
, b_val
))
93 r
= sd_device_get_sysname(a
, &a_val
);
97 r
= sd_device_get_sysname(b
, &b_val
);
101 return streq(a_val
, b_val
);
104 static int validate_device(sd_device
*device
) {
105 _cleanup_(sd_device_enumerator_unrefp
) sd_device_enumerator
*enumerate
= NULL
;
106 const char *v
, *subsystem
;
107 sd_device
*parent
, *other
;
112 /* Verify whether we should actually care for a specific
113 * backlight device. For backlight devices there might be
114 * multiple ways to access the same control: "firmware"
115 * (i.e. ACPI), "platform" (i.e. via the machine's EC) and
116 * "raw" (via the graphics card). In general we should prefer
117 * "firmware" (i.e. ACPI) or "platform" access over "raw"
118 * access, in order not to confuse the BIOS/EC, and
119 * compatibility with possible low-level hotkey handling of
120 * screen brightness. The kernel will already make sure to
121 * expose only one of "firmware" and "platform" for the same
122 * device to userspace. However, we still need to make sure
123 * that we use "raw" only if no "firmware" or "platform"
124 * device for the same device exists. */
126 r
= sd_device_get_subsystem(device
, &subsystem
);
129 if (!streq(subsystem
, "backlight"))
132 r
= sd_device_get_sysattr_value(device
, "type", &v
);
135 if (!streq(v
, "raw"))
138 r
= find_pci_or_platform_parent(device
, &parent
);
142 r
= sd_device_get_subsystem(parent
, &subsystem
);
146 r
= sd_device_enumerator_new(&enumerate
);
150 r
= sd_device_enumerator_allow_uninitialized(enumerate
);
154 r
= sd_device_enumerator_add_match_subsystem(enumerate
, "backlight", true);
158 FOREACH_DEVICE(enumerate
, other
) {
159 const char *other_subsystem
;
160 sd_device
*other_parent
;
162 if (same_device(device
, other
) > 0)
165 if (sd_device_get_sysattr_value(other
, "type", &v
) < 0 ||
166 !STR_IN_SET(v
, "platform", "firmware"))
169 /* OK, so there's another backlight device, and it's a
170 * platform or firmware device, so, let's see if we
171 * can verify it belongs to the same device as ours. */
172 if (find_pci_or_platform_parent(other
, &other_parent
) < 0)
175 if (same_device(parent
, other_parent
)) {
176 const char *device_sysname
= NULL
, *other_sysname
= NULL
;
178 /* Both have the same PCI parent, that means we are out. */
180 (void) sd_device_get_sysname(device
, &device_sysname
);
181 (void) sd_device_get_sysname(other
, &other_sysname
);
183 log_debug("Skipping backlight device %s, since device %s is on same PCI device and takes precedence.",
184 device_sysname
, other_sysname
);
188 if (sd_device_get_subsystem(other_parent
, &other_subsystem
) < 0)
191 if (streq(other_subsystem
, "platform") && streq(subsystem
, "pci")) {
192 const char *device_sysname
= NULL
, *other_sysname
= NULL
;
194 /* The other is connected to the platform bus and we are a PCI device, that also means we are out. */
196 (void) sd_device_get_sysname(device
, &device_sysname
);
197 (void) sd_device_get_sysname(other
, &other_sysname
);
199 log_debug("Skipping backlight device %s, since device %s is a platform device and takes precedence.",
200 device_sysname
, other_sysname
);
208 static int get_max_brightness(sd_device
*device
, unsigned *ret
) {
209 const char *max_brightness_str
;
210 unsigned max_brightness
;
216 r
= sd_device_get_sysattr_value(device
, "max_brightness", &max_brightness_str
);
218 return log_device_warning_errno(device
, r
, "Failed to read 'max_brightness' attribute: %m");
220 r
= safe_atou(max_brightness_str
, &max_brightness
);
222 return log_device_warning_errno(device
, r
, "Failed to parse 'max_brightness' \"%s\": %m", max_brightness_str
);
224 if (max_brightness
<= 0) {
225 log_device_warning(device
, "Maximum brightness is 0, ignoring device.");
229 *ret
= max_brightness
;
233 /* Some systems turn the backlight all the way off at the lowest levels.
234 * clamp_brightness clamps the saved brightness to at least 1 or 5% of
235 * max_brightness in case of 'backlight' subsystem. This avoids preserving
236 * an unreadably dim screen, which would otherwise force the user to
237 * disable state restoration. */
238 static int clamp_brightness(sd_device
*device
, char **value
, unsigned max_brightness
) {
239 unsigned brightness
, new_brightness
, min_brightness
;
240 const char *subsystem
;
246 r
= safe_atou(*value
, &brightness
);
248 return log_device_warning_errno(device
, r
, "Failed to parse brightness \"%s\": %m", *value
);
250 r
= sd_device_get_subsystem(device
, &subsystem
);
252 return log_device_warning_errno(device
, r
, "Failed to get device subsystem: %m");
254 if (streq(subsystem
, "backlight"))
255 min_brightness
= MAX(1U, max_brightness
/20);
259 new_brightness
= CLAMP(brightness
, min_brightness
, max_brightness
);
260 if (new_brightness
!= brightness
) {
263 r
= asprintf(&new_value
, "%u", new_brightness
);
267 log_device_info(device
, "Saved brightness %s %s to %s.", *value
,
268 new_brightness
> brightness
?
269 "too low; increasing" : "too high; decreasing",
272 free_and_replace(*value
, new_value
);
278 static bool shall_clamp(sd_device
*d
) {
284 r
= sd_device_get_property_value(d
, "ID_BACKLIGHT_CLAMP", &s
);
286 log_device_debug_errno(d
, r
, "Failed to get ID_BACKLIGHT_CLAMP property, ignoring: %m");
290 r
= parse_boolean(s
);
292 log_device_debug_errno(d
, r
, "Failed to parse ID_BACKLIGHT_CLAMP property, ignoring: %m");
299 int main(int argc
, char *argv
[]) {
300 _cleanup_(sd_device_unrefp
) sd_device
*device
= NULL
;
301 _cleanup_free_
char *escaped_ss
= NULL
, *escaped_sysname
= NULL
, *escaped_path_id
= NULL
;
302 const char *sysname
, *path_id
, *ss
, *saved
;
303 unsigned max_brightness
;
307 log_error("This program requires two arguments.");
311 log_set_target(LOG_TARGET_AUTO
);
312 log_parse_environment();
317 r
= mkdir_p("/var/lib/systemd/backlight", 0755);
319 log_error_errno(r
, "Failed to create backlight directory /var/lib/systemd/backlight: %m");
323 sysname
= strchr(argv
[2], ':');
325 log_error("Requires a subsystem and sysname pair specifying a backlight device.");
329 ss
= strndupa(argv
[2], sysname
- argv
[2]);
333 if (!STR_IN_SET(ss
, "backlight", "leds")) {
334 log_error("Not a backlight or LED device: '%s:%s'", ss
, sysname
);
338 r
= sd_device_new_from_subsystem_sysname(&device
, ss
, sysname
);
340 log_error_errno(r
, "Failed to get backlight or LED device '%s:%s': %m", ss
, sysname
);
344 /* If max_brightness is 0, then there is no actual backlight
345 * device. This happens on desktops with Asus mainboards
346 * that load the eeepc-wmi module. */
347 r
= get_max_brightness(device
, &max_brightness
);
351 escaped_ss
= cescape(ss
);
357 escaped_sysname
= cescape(sysname
);
358 if (!escaped_sysname
) {
363 if (sd_device_get_property_value(device
, "ID_PATH", &path_id
) >= 0) {
364 escaped_path_id
= cescape(path_id
);
365 if (!escaped_path_id
) {
370 saved
= strjoina("/var/lib/systemd/backlight/", escaped_path_id
, ":", escaped_ss
, ":", escaped_sysname
);
372 saved
= strjoina("/var/lib/systemd/backlight/", escaped_ss
, ":", escaped_sysname
);
374 /* If there are multiple conflicting backlight devices, then
375 * their probing at boot-time might happen in any order. This
376 * means the validity checking of the device then is not
377 * reliable, since it might not see other devices conflicting
378 * with a specific backlight. To deal with this, we will
379 * actively delete backlight state files at shutdown (where
380 * device probing should be complete), so that the validity
381 * check at boot time doesn't have to be reliable. */
383 if (streq(argv
[1], "load")) {
384 _cleanup_free_
char *value
= NULL
;
387 if (shall_restore_state() == 0)
390 if (validate_device(device
) == 0)
393 clamp
= shall_clamp(device
);
395 r
= read_one_line_file(saved
, &value
);
399 /* Fallback to clamping current brightness or exit early if
400 * clamping is not supported/enabled. */
404 r
= sd_device_get_sysattr_value(device
, "brightness", &curval
);
406 log_device_warning_errno(device
, r
, "Failed to read 'brightness' attribute: %m");
410 value
= strdup(curval
);
416 log_error_errno(r
, "Failed to read %s: %m", saved
);
421 (void) clamp_brightness(device
, &value
, max_brightness
);
423 r
= sd_device_set_sysattr_value(device
, "brightness", value
);
425 log_device_error_errno(device
, r
, "Failed to write system 'brightness' attribute: %m");
429 } else if (streq(argv
[1], "save")) {
432 if (validate_device(device
) == 0) {
437 r
= sd_device_get_sysattr_value(device
, "brightness", &value
);
439 log_device_error_errno(device
, r
, "Failed to read system 'brightness' attribute: %m");
443 r
= write_string_file(saved
, value
, WRITE_STRING_FILE_CREATE
);
445 log_device_error_errno(device
, r
, "Failed to write %s: %m", saved
);
450 log_error("Unknown verb %s.", argv
[1]);