1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2013 Lennart Poettering
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
24 #include "alloc-util.h"
29 #include "parse-util.h"
30 #include "proc-cmdline.h"
31 #include "string-util.h"
32 #include "udev-util.h"
35 static struct udev_device
*find_pci_or_platform_parent(struct udev_device
*device
) {
36 struct udev_device
*parent
;
37 const char *subsystem
, *sysname
;
41 parent
= udev_device_get_parent(device
);
45 subsystem
= udev_device_get_subsystem(parent
);
49 sysname
= udev_device_get_sysname(parent
);
53 if (streq(subsystem
, "drm")) {
56 c
= startswith(sysname
, "card");
60 c
+= strspn(c
, DIGITS
);
62 /* A connector DRM device, let's ignore all but LVDS and eDP! */
64 if (!startswith(c
, "-LVDS-") &&
65 !startswith(c
, "-Embedded DisplayPort-"))
69 } else if (streq(subsystem
, "pci")) {
72 value
= udev_device_get_sysattr_value(parent
, "class");
74 unsigned long class = 0;
76 if (safe_atolu(value
, &class) < 0) {
77 log_warning("Cannot parse PCI class %s of device %s:%s.",
78 value
, subsystem
, sysname
);
87 } else if (streq(subsystem
, "platform"))
90 return find_pci_or_platform_parent(parent
);
93 static bool same_device(struct udev_device
*a
, struct udev_device
*b
) {
97 if (!streq_ptr(udev_device_get_subsystem(a
), udev_device_get_subsystem(b
)))
100 if (!streq_ptr(udev_device_get_sysname(a
), udev_device_get_sysname(b
)))
106 static bool validate_device(struct udev
*udev
, struct udev_device
*device
) {
107 _cleanup_udev_enumerate_unref_
struct udev_enumerate
*enumerate
= NULL
;
108 struct udev_list_entry
*item
= NULL
, *first
= NULL
;
109 struct udev_device
*parent
;
110 const char *v
, *subsystem
;
116 /* Verify whether we should actually care for a specific
117 * backlight device. For backlight devices there might be
118 * multiple ways to access the same control: "firmware"
119 * (i.e. ACPI), "platform" (i.e. via the machine's EC) and
120 * "raw" (via the graphics card). In general we should prefer
121 * "firmware" (i.e. ACPI) or "platform" access over "raw"
122 * access, in order not to confuse the BIOS/EC, and
123 * compatibility with possible low-level hotkey handling of
124 * screen brightness. The kernel will already make sure to
125 * expose only one of "firmware" and "platform" for the same
126 * device to userspace. However, we still need to make sure
127 * that we use "raw" only if no "firmware" or "platform"
128 * device for the same device exists. */
130 subsystem
= udev_device_get_subsystem(device
);
131 if (!streq_ptr(subsystem
, "backlight"))
134 v
= udev_device_get_sysattr_value(device
, "type");
135 if (!streq_ptr(v
, "raw"))
138 parent
= find_pci_or_platform_parent(device
);
142 subsystem
= udev_device_get_subsystem(parent
);
146 enumerate
= udev_enumerate_new(udev
);
150 r
= udev_enumerate_add_match_subsystem(enumerate
, "backlight");
154 r
= udev_enumerate_scan_devices(enumerate
);
158 first
= udev_enumerate_get_list_entry(enumerate
);
159 udev_list_entry_foreach(item
, first
) {
160 _cleanup_udev_device_unref_
struct udev_device
*other
;
161 struct udev_device
*other_parent
;
162 const char *other_subsystem
;
164 other
= udev_device_new_from_syspath(udev
, udev_list_entry_get_name(item
));
168 if (same_device(device
, other
))
171 v
= udev_device_get_sysattr_value(other
, "type");
172 if (!streq_ptr(v
, "platform") && !streq_ptr(v
, "firmware"))
175 /* OK, so there's another backlight device, and it's a
176 * platform or firmware device, so, let's see if we
177 * can verify it belongs to the same device as
179 other_parent
= find_pci_or_platform_parent(other
);
183 if (same_device(parent
, other_parent
)) {
184 /* Both have the same PCI parent, that means
186 log_debug("Skipping backlight device %s, since device %s is on same PCI device and takes precedence.",
187 udev_device_get_sysname(device
),
188 udev_device_get_sysname(other
));
192 other_subsystem
= udev_device_get_subsystem(other_parent
);
193 if (streq_ptr(other_subsystem
, "platform") && streq_ptr(subsystem
, "pci")) {
194 /* The other is connected to the platform bus
195 * and we are a PCI device, that also means we
197 log_debug("Skipping backlight device %s, since device %s is a platform device and takes precedence.",
198 udev_device_get_sysname(device
),
199 udev_device_get_sysname(other
));
207 static unsigned get_max_brightness(struct udev_device
*device
) {
209 const char *max_brightness_str
;
210 unsigned max_brightness
;
212 max_brightness_str
= udev_device_get_sysattr_value(device
, "max_brightness");
213 if (!max_brightness_str
) {
214 log_warning("Failed to read 'max_brightness' attribute.");
218 r
= safe_atou(max_brightness_str
, &max_brightness
);
220 log_warning_errno(r
, "Failed to parse 'max_brightness' \"%s\": %m", max_brightness_str
);
224 if (max_brightness
<= 0) {
225 log_warning("Maximum brightness is 0, ignoring device.");
229 return 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 void clamp_brightness(struct udev_device
*device
, char **value
, unsigned max_brightness
) {
239 unsigned brightness
, new_brightness
, min_brightness
;
240 const char *subsystem
;
242 r
= safe_atou(*value
, &brightness
);
244 log_warning_errno(r
, "Failed to parse brightness \"%s\": %m", *value
);
248 subsystem
= udev_device_get_subsystem(device
);
249 if (streq_ptr(subsystem
, "backlight"))
250 min_brightness
= MAX(1U, max_brightness
/20);
254 new_brightness
= CLAMP(brightness
, min_brightness
, max_brightness
);
255 if (new_brightness
!= brightness
) {
256 char *old_value
= *value
;
258 r
= asprintf(value
, "%u", new_brightness
);
264 log_info("Saved brightness %s %s to %s.", old_value
,
265 new_brightness
> brightness
?
266 "too low; increasing" : "too high; decreasing",
273 int main(int argc
, char *argv
[]) {
274 _cleanup_udev_unref_
struct udev
*udev
= NULL
;
275 _cleanup_udev_device_unref_
struct udev_device
*device
= NULL
;
276 _cleanup_free_
char *saved
= NULL
, *ss
= NULL
, *escaped_ss
= NULL
, *escaped_sysname
= NULL
, *escaped_path_id
= NULL
;
277 const char *sysname
, *path_id
;
278 unsigned max_brightness
;
282 log_error("This program requires two arguments.");
286 log_set_target(LOG_TARGET_AUTO
);
287 log_parse_environment();
292 r
= mkdir_p("/var/lib/systemd/backlight", 0755);
294 log_error_errno(r
, "Failed to create backlight directory /var/lib/systemd/backlight: %m");
304 sysname
= strchr(argv
[2], ':');
306 log_error("Requires a subsystem and sysname pair specifying a backlight device.");
310 ss
= strndup(argv
[2], sysname
- argv
[2]);
318 if (!streq(ss
, "backlight") && !streq(ss
, "leds")) {
319 log_error("Not a backlight or LED device: '%s:%s'", ss
, sysname
);
324 device
= udev_device_new_from_subsystem_sysname(udev
, ss
, sysname
);
327 log_error_errno(errno
, "Failed to get backlight or LED device '%s:%s': %m", ss
, sysname
);
334 /* If max_brightness is 0, then there is no actual backlight
335 * device. This happens on desktops with Asus mainboards
336 * that load the eeepc-wmi module.
338 max_brightness
= get_max_brightness(device
);
339 if (max_brightness
== 0)
342 escaped_ss
= cescape(ss
);
348 escaped_sysname
= cescape(sysname
);
349 if (!escaped_sysname
) {
354 path_id
= udev_device_get_property_value(device
, "ID_PATH");
356 escaped_path_id
= cescape(path_id
);
357 if (!escaped_path_id
) {
362 saved
= strjoin("/var/lib/systemd/backlight/", escaped_path_id
, ":", escaped_ss
, ":", escaped_sysname
, NULL
);
364 saved
= strjoin("/var/lib/systemd/backlight/", escaped_ss
, ":", escaped_sysname
, NULL
);
371 /* If there are multiple conflicting backlight devices, then
372 * their probing at boot-time might happen in any order. This
373 * means the validity checking of the device then is not
374 * reliable, since it might not see other devices conflicting
375 * with a specific backlight. To deal with this, we will
376 * actively delete backlight state files at shutdown (where
377 * device probing should be complete), so that the validity
378 * check at boot time doesn't have to be reliable. */
380 if (streq(argv
[1], "load")) {
381 _cleanup_free_
char *value
= NULL
;
384 if (shall_restore_state() == 0)
387 if (!validate_device(udev
, device
))
390 r
= read_one_line_file(saved
, &value
);
396 log_error_errno(r
, "Failed to read %s: %m", saved
);
400 clamp
= udev_device_get_property_value(device
, "ID_BACKLIGHT_CLAMP");
401 if (!clamp
|| parse_boolean(clamp
) != 0) /* default to clamping */
402 clamp_brightness(device
, &value
, max_brightness
);
404 r
= udev_device_set_sysattr_value(device
, "brightness", value
);
406 log_error_errno(r
, "Failed to write system 'brightness' attribute: %m");
410 } else if (streq(argv
[1], "save")) {
413 if (!validate_device(udev
, device
)) {
418 value
= udev_device_get_sysattr_value(device
, "brightness");
420 log_error("Failed to read system 'brightness' attribute");
424 r
= write_string_file(saved
, value
, WRITE_STRING_FILE_CREATE
);
426 log_error_errno(r
, "Failed to write %s: %m", saved
);
431 log_error("Unknown verb %s.", argv
[1]);