1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright 2013 Lennart Poettering
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
23 #include "alloc-util.h"
28 #include "parse-util.h"
29 #include "proc-cmdline.h"
30 #include "string-util.h"
31 #include "udev-util.h"
34 static struct udev_device
*find_pci_or_platform_parent(struct udev_device
*device
) {
35 struct udev_device
*parent
;
36 const char *subsystem
, *sysname
;
40 parent
= udev_device_get_parent(device
);
44 subsystem
= udev_device_get_subsystem(parent
);
48 sysname
= udev_device_get_sysname(parent
);
52 if (streq(subsystem
, "drm")) {
55 c
= startswith(sysname
, "card");
59 c
+= strspn(c
, DIGITS
);
61 /* A connector DRM device, let's ignore all but LVDS and eDP! */
63 if (!startswith(c
, "-LVDS-") &&
64 !startswith(c
, "-Embedded DisplayPort-"))
68 } else if (streq(subsystem
, "pci")) {
71 value
= udev_device_get_sysattr_value(parent
, "class");
73 unsigned long class = 0;
75 if (safe_atolu(value
, &class) < 0) {
76 log_warning("Cannot parse PCI class %s of device %s:%s.",
77 value
, subsystem
, sysname
);
86 } else if (streq(subsystem
, "platform"))
89 return find_pci_or_platform_parent(parent
);
92 static bool same_device(struct udev_device
*a
, struct udev_device
*b
) {
96 if (!streq_ptr(udev_device_get_subsystem(a
), udev_device_get_subsystem(b
)))
99 if (!streq_ptr(udev_device_get_sysname(a
), udev_device_get_sysname(b
)))
105 static bool validate_device(struct udev
*udev
, struct udev_device
*device
) {
106 _cleanup_udev_enumerate_unref_
struct udev_enumerate
*enumerate
= NULL
;
107 struct udev_list_entry
*item
= NULL
, *first
= NULL
;
108 struct udev_device
*parent
;
109 const char *v
, *subsystem
;
115 /* Verify whether we should actually care for a specific
116 * backlight device. For backlight devices there might be
117 * multiple ways to access the same control: "firmware"
118 * (i.e. ACPI), "platform" (i.e. via the machine's EC) and
119 * "raw" (via the graphics card). In general we should prefer
120 * "firmware" (i.e. ACPI) or "platform" access over "raw"
121 * access, in order not to confuse the BIOS/EC, and
122 * compatibility with possible low-level hotkey handling of
123 * screen brightness. The kernel will already make sure to
124 * expose only one of "firmware" and "platform" for the same
125 * device to userspace. However, we still need to make sure
126 * that we use "raw" only if no "firmware" or "platform"
127 * device for the same device exists. */
129 subsystem
= udev_device_get_subsystem(device
);
130 if (!streq_ptr(subsystem
, "backlight"))
133 v
= udev_device_get_sysattr_value(device
, "type");
134 if (!streq_ptr(v
, "raw"))
137 parent
= find_pci_or_platform_parent(device
);
141 subsystem
= udev_device_get_subsystem(parent
);
145 enumerate
= udev_enumerate_new(udev
);
149 r
= udev_enumerate_add_match_subsystem(enumerate
, "backlight");
153 r
= udev_enumerate_scan_devices(enumerate
);
157 first
= udev_enumerate_get_list_entry(enumerate
);
158 udev_list_entry_foreach(item
, first
) {
159 _cleanup_udev_device_unref_
struct udev_device
*other
;
160 struct udev_device
*other_parent
;
161 const char *other_subsystem
;
163 other
= udev_device_new_from_syspath(udev
, udev_list_entry_get_name(item
));
167 if (same_device(device
, other
))
170 v
= udev_device_get_sysattr_value(other
, "type");
171 if (!STRPTR_IN_SET(v
, "platform", "firmware"))
174 /* OK, so there's another backlight device, and it's a
175 * platform or firmware device, so, let's see if we
176 * can verify it belongs to the same device as
178 other_parent
= find_pci_or_platform_parent(other
);
182 if (same_device(parent
, other_parent
)) {
183 /* Both have the same PCI parent, that means
185 log_debug("Skipping backlight device %s, since device %s is on same PCI device and takes precedence.",
186 udev_device_get_sysname(device
),
187 udev_device_get_sysname(other
));
191 other_subsystem
= udev_device_get_subsystem(other_parent
);
192 if (streq_ptr(other_subsystem
, "platform") && streq_ptr(subsystem
, "pci")) {
193 /* The other is connected to the platform bus
194 * and we are a PCI device, that also means we
196 log_debug("Skipping backlight device %s, since device %s is a platform device and takes precedence.",
197 udev_device_get_sysname(device
),
198 udev_device_get_sysname(other
));
206 static unsigned get_max_brightness(struct udev_device
*device
) {
208 const char *max_brightness_str
;
209 unsigned max_brightness
;
211 max_brightness_str
= udev_device_get_sysattr_value(device
, "max_brightness");
212 if (!max_brightness_str
) {
213 log_warning("Failed to read 'max_brightness' attribute.");
217 r
= safe_atou(max_brightness_str
, &max_brightness
);
219 log_warning_errno(r
, "Failed to parse 'max_brightness' \"%s\": %m", max_brightness_str
);
223 if (max_brightness
<= 0) {
224 log_warning("Maximum brightness is 0, ignoring device.");
228 return max_brightness
;
231 /* Some systems turn the backlight all the way off at the lowest levels.
232 * clamp_brightness clamps the saved brightness to at least 1 or 5% of
233 * max_brightness in case of 'backlight' subsystem. This avoids preserving
234 * an unreadably dim screen, which would otherwise force the user to
235 * disable state restoration. */
236 static void clamp_brightness(struct udev_device
*device
, char **value
, unsigned max_brightness
) {
238 unsigned brightness
, new_brightness
, min_brightness
;
239 const char *subsystem
;
241 r
= safe_atou(*value
, &brightness
);
243 log_warning_errno(r
, "Failed to parse brightness \"%s\": %m", *value
);
247 subsystem
= udev_device_get_subsystem(device
);
248 if (streq_ptr(subsystem
, "backlight"))
249 min_brightness
= MAX(1U, max_brightness
/20);
253 new_brightness
= CLAMP(brightness
, min_brightness
, max_brightness
);
254 if (new_brightness
!= brightness
) {
255 char *old_value
= *value
;
257 r
= asprintf(value
, "%u", new_brightness
);
263 log_info("Saved brightness %s %s to %s.", old_value
,
264 new_brightness
> brightness
?
265 "too low; increasing" : "too high; decreasing",
272 int main(int argc
, char *argv
[]) {
273 _cleanup_udev_unref_
struct udev
*udev
= NULL
;
274 _cleanup_udev_device_unref_
struct udev_device
*device
= NULL
;
275 _cleanup_free_
char *saved
= NULL
, *ss
= NULL
, *escaped_ss
= NULL
, *escaped_sysname
= NULL
, *escaped_path_id
= NULL
;
276 const char *sysname
, *path_id
;
277 unsigned max_brightness
;
281 log_error("This program requires two arguments.");
285 log_set_target(LOG_TARGET_AUTO
);
286 log_parse_environment();
291 r
= mkdir_p("/var/lib/systemd/backlight", 0755);
293 log_error_errno(r
, "Failed to create backlight directory /var/lib/systemd/backlight: %m");
303 sysname
= strchr(argv
[2], ':');
305 log_error("Requires a subsystem and sysname pair specifying a backlight device.");
309 ss
= strndup(argv
[2], sysname
- argv
[2]);
317 if (!streq(ss
, "backlight") && !streq(ss
, "leds")) {
318 log_error("Not a backlight or LED device: '%s:%s'", ss
, sysname
);
323 device
= udev_device_new_from_subsystem_sysname(udev
, ss
, sysname
);
326 log_error_errno(errno
, "Failed to get backlight or LED device '%s:%s': %m", ss
, sysname
);
333 /* If max_brightness is 0, then there is no actual backlight
334 * device. This happens on desktops with Asus mainboards
335 * that load the eeepc-wmi module.
337 max_brightness
= get_max_brightness(device
);
338 if (max_brightness
== 0)
341 escaped_ss
= cescape(ss
);
347 escaped_sysname
= cescape(sysname
);
348 if (!escaped_sysname
) {
353 path_id
= udev_device_get_property_value(device
, "ID_PATH");
355 escaped_path_id
= cescape(path_id
);
356 if (!escaped_path_id
) {
361 saved
= strjoin("/var/lib/systemd/backlight/", escaped_path_id
, ":", escaped_ss
, ":", escaped_sysname
);
363 saved
= strjoin("/var/lib/systemd/backlight/", escaped_ss
, ":", escaped_sysname
);
370 /* If there are multiple conflicting backlight devices, then
371 * their probing at boot-time might happen in any order. This
372 * means the validity checking of the device then is not
373 * reliable, since it might not see other devices conflicting
374 * with a specific backlight. To deal with this, we will
375 * actively delete backlight state files at shutdown (where
376 * device probing should be complete), so that the validity
377 * check at boot time doesn't have to be reliable. */
379 if (streq(argv
[1], "load")) {
380 _cleanup_free_
char *value
= NULL
;
383 if (shall_restore_state() == 0)
386 if (!validate_device(udev
, device
))
389 r
= read_one_line_file(saved
, &value
);
395 log_error_errno(r
, "Failed to read %s: %m", saved
);
399 clamp
= udev_device_get_property_value(device
, "ID_BACKLIGHT_CLAMP");
400 if (!clamp
|| parse_boolean(clamp
) != 0) /* default to clamping */
401 clamp_brightness(device
, &value
, max_brightness
);
403 r
= udev_device_set_sysattr_value(device
, "brightness", value
);
405 log_error_errno(r
, "Failed to write system 'brightness' attribute: %m");
409 } else if (streq(argv
[1], "save")) {
412 if (!validate_device(udev
, device
)) {
417 value
= udev_device_get_sysattr_value(device
, "brightness");
419 log_error("Failed to read system 'brightness' attribute");
423 r
= write_string_file(saved
, value
, WRITE_STRING_FILE_CREATE
);
425 log_error_errno(r
, "Failed to write %s: %m", saved
);
430 log_error("Unknown verb %s.", argv
[1]);