1 /* SPDX-License-Identifier: LGPL-2.1+ */
5 #include "alloc-util.h"
6 #include "device-util.h"
11 #include "parse-util.h"
12 #include "proc-cmdline.h"
13 #include "string-util.h"
17 static int find_pci_or_platform_parent(sd_device
*device
, sd_device
**ret
) {
18 const char *subsystem
, *sysname
, *value
;
25 r
= sd_device_get_parent(device
, &parent
);
29 r
= sd_device_get_subsystem(parent
, &subsystem
);
33 r
= sd_device_get_sysname(parent
, &sysname
);
37 if (streq(subsystem
, "drm")) {
40 c
= startswith(sysname
, "card");
44 c
+= strspn(c
, DIGITS
);
46 /* A connector DRM device, let's ignore all but LVDS and eDP! */
47 if (!STARTSWITH_SET(c
, "-LVDS-", "-Embedded DisplayPort-"))
51 } else if (streq(subsystem
, "pci") &&
52 sd_device_get_sysattr_value(parent
, "class", &value
) >= 0) {
53 unsigned long class = 0;
55 r
= safe_atolu(value
, &class);
57 return log_warning_errno(r
, "Cannot parse PCI class '%s' of device %s:%s: %m",
58 value
, subsystem
, sysname
);
61 if (class == 0x30000) {
66 } else if (streq(subsystem
, "platform")) {
71 return find_pci_or_platform_parent(parent
, ret
);
74 static int same_device(sd_device
*a
, sd_device
*b
) {
75 const char *a_val
, *b_val
;
81 r
= sd_device_get_subsystem(a
, &a_val
);
85 r
= sd_device_get_subsystem(b
, &b_val
);
89 if (!streq(a_val
, b_val
))
92 r
= sd_device_get_sysname(a
, &a_val
);
96 r
= sd_device_get_sysname(b
, &b_val
);
100 return streq(a_val
, b_val
);
103 static int validate_device(sd_device
*device
) {
104 _cleanup_(sd_device_enumerator_unrefp
) sd_device_enumerator
*enumerate
= NULL
;
105 const char *v
, *subsystem
;
106 sd_device
*parent
, *other
;
111 /* Verify whether we should actually care for a specific
112 * backlight device. For backlight devices there might be
113 * multiple ways to access the same control: "firmware"
114 * (i.e. ACPI), "platform" (i.e. via the machine's EC) and
115 * "raw" (via the graphics card). In general we should prefer
116 * "firmware" (i.e. ACPI) or "platform" access over "raw"
117 * access, in order not to confuse the BIOS/EC, and
118 * compatibility with possible low-level hotkey handling of
119 * screen brightness. The kernel will already make sure to
120 * expose only one of "firmware" and "platform" for the same
121 * device to userspace. However, we still need to make sure
122 * that we use "raw" only if no "firmware" or "platform"
123 * device for the same device exists. */
125 r
= sd_device_get_subsystem(device
, &subsystem
);
128 if (!streq(subsystem
, "backlight"))
131 r
= sd_device_get_sysattr_value(device
, "type", &v
);
134 if (!streq(v
, "raw"))
137 r
= find_pci_or_platform_parent(device
, &parent
);
141 r
= sd_device_get_subsystem(parent
, &subsystem
);
145 r
= sd_device_enumerator_new(&enumerate
);
149 r
= sd_device_enumerator_allow_uninitialized(enumerate
);
153 r
= sd_device_enumerator_add_match_subsystem(enumerate
, "backlight", true);
157 FOREACH_DEVICE(enumerate
, other
) {
158 const char *other_subsystem
;
159 sd_device
*other_parent
;
161 if (same_device(device
, other
) > 0)
164 if (sd_device_get_sysattr_value(other
, "type", &v
) < 0 ||
165 !STR_IN_SET(v
, "platform", "firmware"))
168 /* OK, so there's another backlight device, and it's a
169 * platform or firmware device, so, let's see if we
170 * can verify it belongs to the same device as ours. */
171 if (find_pci_or_platform_parent(other
, &other_parent
) < 0)
174 if (same_device(parent
, other_parent
)) {
175 const char *device_sysname
= NULL
, *other_sysname
= NULL
;
177 /* Both have the same PCI parent, that means we are out. */
179 (void) sd_device_get_sysname(device
, &device_sysname
);
180 (void) sd_device_get_sysname(other
, &other_sysname
);
182 log_debug("Skipping backlight device %s, since device %s is on same PCI device and takes precedence.",
183 device_sysname
, other_sysname
);
187 if (sd_device_get_subsystem(other_parent
, &other_subsystem
) < 0)
190 if (streq(other_subsystem
, "platform") && streq(subsystem
, "pci")) {
191 const char *device_sysname
= NULL
, *other_sysname
= NULL
;
193 /* The other is connected to the platform bus and we are a PCI device, that also means we are out. */
195 (void) sd_device_get_sysname(device
, &device_sysname
);
196 (void) sd_device_get_sysname(other
, &other_sysname
);
198 log_debug("Skipping backlight device %s, since device %s is a platform device and takes precedence.",
199 device_sysname
, other_sysname
);
207 static int get_max_brightness(sd_device
*device
, unsigned *ret
) {
208 const char *max_brightness_str
;
209 unsigned max_brightness
;
215 r
= sd_device_get_sysattr_value(device
, "max_brightness", &max_brightness_str
);
217 return log_device_warning_errno(device
, r
, "Failed to read 'max_brightness' attribute: %m");
219 r
= safe_atou(max_brightness_str
, &max_brightness
);
221 return log_device_warning_errno(device
, r
, "Failed to parse 'max_brightness' \"%s\": %m", max_brightness_str
);
223 if (max_brightness
<= 0) {
224 log_device_warning(device
, "Maximum brightness is 0, ignoring device.");
228 *ret
= max_brightness
;
232 /* Some systems turn the backlight all the way off at the lowest levels.
233 * clamp_brightness clamps the saved brightness to at least 1 or 5% of
234 * max_brightness in case of 'backlight' subsystem. This avoids preserving
235 * an unreadably dim screen, which would otherwise force the user to
236 * disable state restoration. */
237 static int clamp_brightness(sd_device
*device
, char **value
, unsigned max_brightness
) {
238 unsigned brightness
, new_brightness
, min_brightness
;
239 const char *subsystem
;
245 r
= safe_atou(*value
, &brightness
);
247 return log_device_warning_errno(device
, r
, "Failed to parse brightness \"%s\": %m", *value
);
249 r
= sd_device_get_subsystem(device
, &subsystem
);
251 return log_device_warning_errno(device
, r
, "Failed to get device subsystem: %m");
253 if (streq(subsystem
, "backlight"))
254 min_brightness
= MAX(1U, max_brightness
/20);
258 new_brightness
= CLAMP(brightness
, min_brightness
, max_brightness
);
259 if (new_brightness
!= brightness
) {
262 r
= asprintf(&new_value
, "%u", new_brightness
);
266 log_device_info(device
, "Saved brightness %s %s to %s.", *value
,
267 new_brightness
> brightness
?
268 "too low; increasing" : "too high; decreasing",
271 free_and_replace(*value
, new_value
);
277 static bool shall_clamp(sd_device
*d
) {
283 r
= sd_device_get_property_value(d
, "ID_BACKLIGHT_CLAMP", &s
);
285 log_device_debug_errno(d
, r
, "Failed to get ID_BACKLIGHT_CLAMP property, ignoring: %m");
289 r
= parse_boolean(s
);
291 log_device_debug_errno(d
, r
, "Failed to parse ID_BACKLIGHT_CLAMP property, ignoring: %m");
298 static int run(int argc
, char *argv
[]) {
299 _cleanup_(sd_device_unrefp
) sd_device
*device
= NULL
;
300 _cleanup_free_
char *escaped_ss
= NULL
, *escaped_sysname
= NULL
, *escaped_path_id
= NULL
;
301 const char *sysname
, *path_id
, *ss
, *saved
;
302 unsigned max_brightness
;
306 log_error("This program requires two arguments.");
314 r
= mkdir_p("/var/lib/systemd/backlight", 0755);
316 return log_error_errno(r
, "Failed to create backlight directory /var/lib/systemd/backlight: %m");
318 sysname
= strchr(argv
[2], ':');
320 log_error("Requires a subsystem and sysname pair specifying a backlight device.");
324 ss
= strndupa(argv
[2], sysname
- argv
[2]);
328 if (!STR_IN_SET(ss
, "backlight", "leds")) {
329 log_error("Not a backlight or LED device: '%s:%s'", ss
, sysname
);
333 r
= sd_device_new_from_subsystem_sysname(&device
, ss
, sysname
);
335 return log_error_errno(r
, "Failed to get backlight or LED device '%s:%s': %m", ss
, sysname
);
337 /* If max_brightness is 0, then there is no actual backlight
338 * device. This happens on desktops with Asus mainboards
339 * that load the eeepc-wmi module. */
340 if (get_max_brightness(device
, &max_brightness
) < 0)
343 escaped_ss
= cescape(ss
);
347 escaped_sysname
= cescape(sysname
);
348 if (!escaped_sysname
)
351 if (sd_device_get_property_value(device
, "ID_PATH", &path_id
) >= 0) {
352 escaped_path_id
= cescape(path_id
);
353 if (!escaped_path_id
)
356 saved
= strjoina("/var/lib/systemd/backlight/", escaped_path_id
, ":", escaped_ss
, ":", escaped_sysname
);
358 saved
= strjoina("/var/lib/systemd/backlight/", escaped_ss
, ":", escaped_sysname
);
360 /* If there are multiple conflicting backlight devices, then
361 * their probing at boot-time might happen in any order. This
362 * means the validity checking of the device then is not
363 * reliable, since it might not see other devices conflicting
364 * with a specific backlight. To deal with this, we will
365 * actively delete backlight state files at shutdown (where
366 * device probing should be complete), so that the validity
367 * check at boot time doesn't have to be reliable. */
369 if (streq(argv
[1], "load")) {
370 _cleanup_free_
char *value
= NULL
;
373 if (shall_restore_state() == 0)
376 if (validate_device(device
) == 0)
379 clamp
= shall_clamp(device
);
381 r
= read_one_line_file(saved
, &value
);
382 if (IN_SET(r
, -ENOENT
, 0)) {
385 /* Fallback to clamping current brightness or exit early if
386 * clamping is not supported/enabled. */
390 r
= sd_device_get_sysattr_value(device
, "brightness", &curval
);
392 return log_device_warning_errno(device
, r
, "Failed to read 'brightness' attribute: %m");
394 value
= strdup(curval
);
398 return log_error_errno(r
, "Failed to read %s: %m", saved
);
401 (void) clamp_brightness(device
, &value
, max_brightness
);
403 r
= sd_device_set_sysattr_value(device
, "brightness", value
);
405 return log_device_error_errno(device
, r
, "Failed to write system 'brightness' attribute: %m");
407 } else if (streq(argv
[1], "save")) {
410 if (validate_device(device
) == 0) {
415 r
= sd_device_get_sysattr_value(device
, "brightness", &value
);
417 return log_device_error_errno(device
, r
, "Failed to read system 'brightness' attribute: %m");
419 r
= write_string_file(saved
, value
, WRITE_STRING_FILE_CREATE
);
421 return log_device_error_errno(device
, r
, "Failed to write %s: %m", saved
);
424 log_error("Unknown verb %s.", argv
[1]);
431 DEFINE_MAIN_FUNCTION(run
);