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"
32 static struct udev_device
*find_pci_or_platform_parent(struct udev_device
*device
) {
33 struct udev_device
*parent
;
34 const char *subsystem
, *sysname
;
38 parent
= udev_device_get_parent(device
);
42 subsystem
= udev_device_get_subsystem(parent
);
46 sysname
= udev_device_get_sysname(parent
);
50 if (streq(subsystem
, "drm")) {
53 c
= startswith(sysname
, "card");
57 c
+= strspn(c
, DIGITS
);
59 /* A connector DRM device, let's ignore all but LVDS and eDP! */
61 if (!startswith(c
, "-LVDS-") &&
62 !startswith(c
, "-Embedded DisplayPort-"))
66 } else if (streq(subsystem
, "pci")) {
69 value
= udev_device_get_sysattr_value(parent
, "class");
71 unsigned long class = 0;
73 if (safe_atolu(value
, &class) < 0) {
74 log_warning("Cannot parse PCI class %s of device %s:%s.",
75 value
, subsystem
, sysname
);
84 } else if (streq(subsystem
, "platform"))
87 return find_pci_or_platform_parent(parent
);
90 static bool same_device(struct udev_device
*a
, struct udev_device
*b
) {
94 if (!streq_ptr(udev_device_get_subsystem(a
), udev_device_get_subsystem(b
)))
97 if (!streq_ptr(udev_device_get_sysname(a
), udev_device_get_sysname(b
)))
103 static bool validate_device(struct udev
*udev
, struct udev_device
*device
) {
104 _cleanup_udev_enumerate_unref_
struct udev_enumerate
*enumerate
= NULL
;
105 struct udev_list_entry
*item
= NULL
, *first
= NULL
;
106 struct udev_device
*parent
;
107 const char *v
, *subsystem
;
113 /* Verify whether we should actually care for a specific
114 * backlight device. For backlight devices there might be
115 * multiple ways to access the same control: "firmware"
116 * (i.e. ACPI), "platform" (i.e. via the machine's EC) and
117 * "raw" (via the graphics card). In general we should prefer
118 * "firmware" (i.e. ACPI) or "platform" access over "raw"
119 * access, in order not to confuse the BIOS/EC, and
120 * compatibility with possible low-level hotkey handling of
121 * screen brightness. The kernel will already make sure to
122 * expose only one of "firmware" and "platform" for the same
123 * device to userspace. However, we still need to make sure
124 * that we use "raw" only if no "firmware" or "platform"
125 * device for the same device exists. */
127 subsystem
= udev_device_get_subsystem(device
);
128 if (!streq_ptr(subsystem
, "backlight"))
131 v
= udev_device_get_sysattr_value(device
, "type");
132 if (!streq_ptr(v
, "raw"))
135 parent
= find_pci_or_platform_parent(device
);
139 subsystem
= udev_device_get_subsystem(parent
);
143 enumerate
= udev_enumerate_new(udev
);
147 r
= udev_enumerate_add_match_subsystem(enumerate
, "backlight");
151 r
= udev_enumerate_scan_devices(enumerate
);
155 first
= udev_enumerate_get_list_entry(enumerate
);
156 udev_list_entry_foreach(item
, first
) {
157 _cleanup_udev_device_unref_
struct udev_device
*other
;
158 struct udev_device
*other_parent
;
159 const char *other_subsystem
;
161 other
= udev_device_new_from_syspath(udev
, udev_list_entry_get_name(item
));
165 if (same_device(device
, other
))
168 v
= udev_device_get_sysattr_value(other
, "type");
169 if (!streq_ptr(v
, "platform") && !streq_ptr(v
, "firmware"))
172 /* OK, so there's another backlight device, and it's a
173 * platform or firmware device, so, let's see if we
174 * can verify it belongs to the same device as
176 other_parent
= find_pci_or_platform_parent(other
);
180 if (same_device(parent
, other_parent
)) {
181 /* Both have the same PCI parent, that means
183 log_debug("Skipping backlight device %s, since device %s is on same PCI device and takes precedence.",
184 udev_device_get_sysname(device
),
185 udev_device_get_sysname(other
));
189 other_subsystem
= udev_device_get_subsystem(other_parent
);
190 if (streq_ptr(other_subsystem
, "platform") && streq_ptr(subsystem
, "pci")) {
191 /* The other is connected to the platform bus
192 * and we are a PCI device, that also means we
194 log_debug("Skipping backlight device %s, since device %s is a platform device and takes precedence.",
195 udev_device_get_sysname(device
),
196 udev_device_get_sysname(other
));
204 static unsigned get_max_brightness(struct udev_device
*device
) {
206 const char *max_brightness_str
;
207 unsigned max_brightness
;
209 max_brightness_str
= udev_device_get_sysattr_value(device
, "max_brightness");
210 if (!max_brightness_str
) {
211 log_warning("Failed to read 'max_brightness' attribute.");
215 r
= safe_atou(max_brightness_str
, &max_brightness
);
217 log_warning_errno(r
, "Failed to parse 'max_brightness' \"%s\": %m", max_brightness_str
);
221 if (max_brightness
<= 0) {
222 log_warning("Maximum brightness is 0, ignoring device.");
226 return max_brightness
;
229 /* Some systems turn the backlight all the way off at the lowest levels.
230 * clamp_brightness clamps the saved brightness to at least 1 or 5% of
231 * max_brightness in case of 'backlight' subsystem. This avoids preserving
232 * an unreadably dim screen, which would otherwise force the user to
233 * disable state restoration. */
234 static void clamp_brightness(struct udev_device
*device
, char **value
, unsigned max_brightness
) {
236 unsigned brightness
, new_brightness
, min_brightness
;
237 const char *subsystem
;
239 r
= safe_atou(*value
, &brightness
);
241 log_warning_errno(r
, "Failed to parse brightness \"%s\": %m", *value
);
245 subsystem
= udev_device_get_subsystem(device
);
246 if (streq_ptr(subsystem
, "backlight"))
247 min_brightness
= MAX(1U, max_brightness
/20);
251 new_brightness
= CLAMP(brightness
, min_brightness
, max_brightness
);
252 if (new_brightness
!= brightness
) {
253 char *old_value
= *value
;
255 r
= asprintf(value
, "%u", new_brightness
);
261 log_info("Saved brightness %s %s to %s.", old_value
,
262 new_brightness
> brightness
?
263 "too low; increasing" : "too high; decreasing",
270 int main(int argc
, char *argv
[]) {
271 _cleanup_udev_unref_
struct udev
*udev
= NULL
;
272 _cleanup_udev_device_unref_
struct udev_device
*device
= NULL
;
273 _cleanup_free_
char *saved
= NULL
, *ss
= NULL
, *escaped_ss
= NULL
, *escaped_sysname
= NULL
, *escaped_path_id
= NULL
;
274 const char *sysname
, *path_id
;
275 unsigned max_brightness
;
279 log_error("This program requires two arguments.");
283 log_set_target(LOG_TARGET_AUTO
);
284 log_parse_environment();
289 r
= mkdir_p("/var/lib/systemd/backlight", 0755);
291 log_error_errno(r
, "Failed to create backlight directory /var/lib/systemd/backlight: %m");
301 sysname
= strchr(argv
[2], ':');
303 log_error("Requires a subsystem and sysname pair specifying a backlight device.");
307 ss
= strndup(argv
[2], sysname
- argv
[2]);
315 if (!streq(ss
, "backlight") && !streq(ss
, "leds")) {
316 log_error("Not a backlight or LED device: '%s:%s'", ss
, sysname
);
321 device
= udev_device_new_from_subsystem_sysname(udev
, ss
, sysname
);
324 log_error_errno(errno
, "Failed to get backlight or LED device '%s:%s': %m", ss
, sysname
);
331 /* If max_brightness is 0, then there is no actual backlight
332 * device. This happens on desktops with Asus mainboards
333 * that load the eeepc-wmi module.
335 max_brightness
= get_max_brightness(device
);
336 if (max_brightness
== 0)
339 escaped_ss
= cescape(ss
);
345 escaped_sysname
= cescape(sysname
);
346 if (!escaped_sysname
) {
351 path_id
= udev_device_get_property_value(device
, "ID_PATH");
353 escaped_path_id
= cescape(path_id
);
354 if (!escaped_path_id
) {
359 saved
= strjoin("/var/lib/systemd/backlight/", escaped_path_id
, ":", escaped_ss
, ":", escaped_sysname
, NULL
);
361 saved
= strjoin("/var/lib/systemd/backlight/", escaped_ss
, ":", escaped_sysname
, NULL
);
368 /* If there are multiple conflicting backlight devices, then
369 * their probing at boot-time might happen in any order. This
370 * means the validity checking of the device then is not
371 * reliable, since it might not see other devices conflicting
372 * with a specific backlight. To deal with this, we will
373 * actively delete backlight state files at shutdown (where
374 * device probing should be complete), so that the validity
375 * check at boot time doesn't have to be reliable. */
377 if (streq(argv
[1], "load")) {
378 _cleanup_free_
char *value
= NULL
;
381 if (!shall_restore_state())
384 if (!validate_device(udev
, device
))
387 r
= read_one_line_file(saved
, &value
);
393 log_error_errno(r
, "Failed to read %s: %m", saved
);
397 clamp
= udev_device_get_property_value(device
, "ID_BACKLIGHT_CLAMP");
398 if (!clamp
|| parse_boolean(clamp
) != 0) /* default to clamping */
399 clamp_brightness(device
, &value
, max_brightness
);
401 r
= udev_device_set_sysattr_value(device
, "brightness", value
);
403 log_error_errno(r
, "Failed to write system 'brightness' attribute: %m");
407 } else if (streq(argv
[1], "save")) {
410 if (!validate_device(udev
, device
)) {
415 value
= udev_device_get_sysattr_value(device
, "brightness");
417 log_error("Failed to read system 'brightness' attribute");
421 r
= write_string_file(saved
, value
, WRITE_STRING_FILE_CREATE
);
423 log_error_errno(r
, "Failed to write %s: %m", saved
);
428 log_error("Unknown verb %s.", argv
[1]);