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/>.
28 #include "string-util.h"
29 #include "udev-util.h"
31 #include "parse-util.h"
33 static struct udev_device
*find_pci_or_platform_parent(struct udev_device
*device
) {
34 struct udev_device
*parent
;
35 const char *subsystem
, *sysname
;
39 parent
= udev_device_get_parent(device
);
43 subsystem
= udev_device_get_subsystem(parent
);
47 sysname
= udev_device_get_sysname(parent
);
51 if (streq(subsystem
, "drm")) {
54 c
= startswith(sysname
, "card");
58 c
+= strspn(c
, DIGITS
);
60 /* A connector DRM device, let's ignore all but LVDS and eDP! */
62 if (!startswith(c
, "-LVDS-") &&
63 !startswith(c
, "-Embedded DisplayPort-"))
67 } else if (streq(subsystem
, "pci")) {
70 value
= udev_device_get_sysattr_value(parent
, "class");
72 unsigned long class = 0;
74 if (safe_atolu(value
, &class) < 0) {
75 log_warning("Cannot parse PCI class %s of device %s:%s.",
76 value
, subsystem
, sysname
);
85 } else if (streq(subsystem
, "platform"))
88 return find_pci_or_platform_parent(parent
);
91 static bool same_device(struct udev_device
*a
, struct udev_device
*b
) {
95 if (!streq_ptr(udev_device_get_subsystem(a
), udev_device_get_subsystem(b
)))
98 if (!streq_ptr(udev_device_get_sysname(a
), udev_device_get_sysname(b
)))
104 static bool validate_device(struct udev
*udev
, struct udev_device
*device
) {
105 _cleanup_udev_enumerate_unref_
struct udev_enumerate
*enumerate
= NULL
;
106 struct udev_list_entry
*item
= NULL
, *first
= NULL
;
107 struct udev_device
*parent
;
108 const char *v
, *subsystem
;
114 /* Verify whether we should actually care for a specific
115 * backlight device. For backlight devices there might be
116 * multiple ways to access the same control: "firmware"
117 * (i.e. ACPI), "platform" (i.e. via the machine's EC) and
118 * "raw" (via the graphics card). In general we should prefer
119 * "firmware" (i.e. ACPI) or "platform" access over "raw"
120 * access, in order not to confuse the BIOS/EC, and
121 * compatibility with possible low-level hotkey handling of
122 * screen brightness. The kernel will already make sure to
123 * expose only one of "firmware" and "platform" for the same
124 * device to userspace. However, we still need to make sure
125 * that we use "raw" only if no "firmware" or "platform"
126 * device for the same device exists. */
128 subsystem
= udev_device_get_subsystem(device
);
129 if (!streq_ptr(subsystem
, "backlight"))
132 v
= udev_device_get_sysattr_value(device
, "type");
133 if (!streq_ptr(v
, "raw"))
136 parent
= find_pci_or_platform_parent(device
);
140 subsystem
= udev_device_get_subsystem(parent
);
144 enumerate
= udev_enumerate_new(udev
);
148 r
= udev_enumerate_add_match_subsystem(enumerate
, "backlight");
152 r
= udev_enumerate_scan_devices(enumerate
);
156 first
= udev_enumerate_get_list_entry(enumerate
);
157 udev_list_entry_foreach(item
, first
) {
158 _cleanup_udev_device_unref_
struct udev_device
*other
;
159 struct udev_device
*other_parent
;
160 const char *other_subsystem
;
162 other
= udev_device_new_from_syspath(udev
, udev_list_entry_get_name(item
));
166 if (same_device(device
, other
))
169 v
= udev_device_get_sysattr_value(other
, "type");
170 if (!streq_ptr(v
, "platform") && !streq_ptr(v
, "firmware"))
173 /* OK, so there's another backlight device, and it's a
174 * platform or firmware device, so, let's see if we
175 * can verify it belongs to the same device as
177 other_parent
= find_pci_or_platform_parent(other
);
181 if (same_device(parent
, other_parent
)) {
182 /* Both have the same PCI parent, that means
184 log_debug("Skipping backlight device %s, since device %s is on same PCI device and takes precedence.",
185 udev_device_get_sysname(device
),
186 udev_device_get_sysname(other
));
190 other_subsystem
= udev_device_get_subsystem(other_parent
);
191 if (streq_ptr(other_subsystem
, "platform") && streq_ptr(subsystem
, "pci")) {
192 /* The other is connected to the platform bus
193 * and we are a PCI device, that also means we
195 log_debug("Skipping backlight device %s, since device %s is a platform device and takes precedence.",
196 udev_device_get_sysname(device
),
197 udev_device_get_sysname(other
));
205 static unsigned get_max_brightness(struct udev_device
*device
) {
207 const char *max_brightness_str
;
208 unsigned max_brightness
;
210 max_brightness_str
= udev_device_get_sysattr_value(device
, "max_brightness");
211 if (!max_brightness_str
) {
212 log_warning("Failed to read 'max_brightness' attribute.");
216 r
= safe_atou(max_brightness_str
, &max_brightness
);
218 log_warning_errno(r
, "Failed to parse 'max_brightness' \"%s\": %m", max_brightness_str
);
222 if (max_brightness
<= 0) {
223 log_warning("Maximum brightness is 0, ignoring device.");
227 return max_brightness
;
230 /* Some systems turn the backlight all the way off at the lowest levels.
231 * clamp_brightness clamps the saved brightness to at least 1 or 5% of
232 * max_brightness in case of 'backlight' subsystem. This avoids preserving
233 * an unreadably dim screen, which would otherwise force the user to
234 * disable state restoration. */
235 static void clamp_brightness(struct udev_device
*device
, char **value
, unsigned max_brightness
) {
237 unsigned brightness
, new_brightness
, min_brightness
;
238 const char *subsystem
;
240 r
= safe_atou(*value
, &brightness
);
242 log_warning_errno(r
, "Failed to parse brightness \"%s\": %m", *value
);
246 subsystem
= udev_device_get_subsystem(device
);
247 if (streq_ptr(subsystem
, "backlight"))
248 min_brightness
= MAX(1U, max_brightness
/20);
252 new_brightness
= CLAMP(brightness
, min_brightness
, max_brightness
);
253 if (new_brightness
!= brightness
) {
254 char *old_value
= *value
;
256 r
= asprintf(value
, "%u", new_brightness
);
262 log_info("Saved brightness %s %s to %s.", old_value
,
263 new_brightness
> brightness
?
264 "too low; increasing" : "too high; decreasing",
271 int main(int argc
, char *argv
[]) {
272 _cleanup_udev_unref_
struct udev
*udev
= NULL
;
273 _cleanup_udev_device_unref_
struct udev_device
*device
= NULL
;
274 _cleanup_free_
char *saved
= NULL
, *ss
= NULL
, *escaped_ss
= NULL
, *escaped_sysname
= NULL
, *escaped_path_id
= NULL
;
275 const char *sysname
, *path_id
;
276 unsigned max_brightness
;
280 log_error("This program requires two arguments.");
284 log_set_target(LOG_TARGET_AUTO
);
285 log_parse_environment();
290 r
= mkdir_p("/var/lib/systemd/backlight", 0755);
292 log_error_errno(r
, "Failed to create backlight directory /var/lib/systemd/backlight: %m");
302 sysname
= strchr(argv
[2], ':');
304 log_error("Requires a subsystem and sysname pair specifying a backlight device.");
308 ss
= strndup(argv
[2], sysname
- argv
[2]);
316 if (!streq(ss
, "backlight") && !streq(ss
, "leds")) {
317 log_error("Not a backlight or LED device: '%s:%s'", ss
, sysname
);
322 device
= udev_device_new_from_subsystem_sysname(udev
, ss
, sysname
);
325 log_error_errno(errno
, "Failed to get backlight or LED device '%s:%s': %m", ss
, sysname
);
332 /* If max_brightness is 0, then there is no actual backlight
333 * device. This happens on desktops with Asus mainboards
334 * that load the eeepc-wmi module.
336 max_brightness
= get_max_brightness(device
);
337 if (max_brightness
== 0)
340 escaped_ss
= cescape(ss
);
346 escaped_sysname
= cescape(sysname
);
347 if (!escaped_sysname
) {
352 path_id
= udev_device_get_property_value(device
, "ID_PATH");
354 escaped_path_id
= cescape(path_id
);
355 if (!escaped_path_id
) {
360 saved
= strjoin("/var/lib/systemd/backlight/", escaped_path_id
, ":", escaped_ss
, ":", escaped_sysname
, NULL
);
362 saved
= strjoin("/var/lib/systemd/backlight/", escaped_ss
, ":", escaped_sysname
, NULL
);
369 /* If there are multiple conflicting backlight devices, then
370 * their probing at boot-time might happen in any order. This
371 * means the validity checking of the device then is not
372 * reliable, since it might not see other devices conflicting
373 * with a specific backlight. To deal with this, we will
374 * actively delete backlight state files at shutdown (where
375 * device probing should be complete), so that the validity
376 * check at boot time doesn't have to be reliable. */
378 if (streq(argv
[1], "load")) {
379 _cleanup_free_
char *value
= NULL
;
382 if (!shall_restore_state())
385 if (!validate_device(udev
, device
))
388 r
= read_one_line_file(saved
, &value
);
394 log_error_errno(r
, "Failed to read %s: %m", saved
);
398 clamp
= udev_device_get_property_value(device
, "ID_BACKLIGHT_CLAMP");
399 if (!clamp
|| parse_boolean(clamp
) != 0) /* default to clamping */
400 clamp_brightness(device
, &value
, max_brightness
);
402 r
= udev_device_set_sysattr_value(device
, "brightness", value
);
404 log_error_errno(r
, "Failed to write system 'brightness' attribute: %m");
408 } else if (streq(argv
[1], "save")) {
411 if (!validate_device(udev
, device
)) {
416 value
= udev_device_get_sysattr_value(device
, "brightness");
418 log_error("Failed to read system 'brightness' attribute");
422 r
= write_string_file(saved
, value
, WRITE_STRING_FILE_CREATE
);
424 log_error_errno(r
, "Failed to write %s: %m", saved
);
429 log_error("Unknown verb %s.", argv
[1]);