]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/backlight/backlight.c
util-lib: split out allocation calls into alloc-util.[ch]
[thirdparty/systemd.git] / src / backlight / backlight.c
CommitLineData
3731acf1
LP
1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3/***
4 This file is part of systemd.
5
6 Copyright 2013 Lennart Poettering
7
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.
12
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.
17
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/>.
20***/
21
07630cea
LP
22#include "libudev.h"
23
b5efdb8a 24#include "alloc-util.h"
4f5dd394
LP
25#include "def.h"
26#include "escape.h"
3731acf1 27#include "fileio.h"
4f5dd394 28#include "mkdir.h"
4e731273
LP
29#include "parse-util.h"
30#include "proc-cmdline.h"
07630cea 31#include "string-util.h"
1ca208fb 32#include "udev-util.h"
4f5dd394 33#include "util.h"
3731acf1 34
0f4ba83c
LP
35static struct udev_device *find_pci_or_platform_parent(struct udev_device *device) {
36 struct udev_device *parent;
37 const char *subsystem, *sysname;
38
39 assert(device);
40
41 parent = udev_device_get_parent(device);
42 if (!parent)
43 return NULL;
44
45 subsystem = udev_device_get_subsystem(parent);
46 if (!subsystem)
47 return NULL;
48
49 sysname = udev_device_get_sysname(parent);
50 if (!sysname)
51 return NULL;
52
53 if (streq(subsystem, "drm")) {
54 const char *c;
55
56 c = startswith(sysname, "card");
57 if (!c)
58 return NULL;
59
938d2699 60 c += strspn(c, DIGITS);
0f4ba83c
LP
61 if (*c == '-') {
62 /* A connector DRM device, let's ignore all but LVDS and eDP! */
63
64 if (!startswith(c, "-LVDS-") &&
65 !startswith(c, "-Embedded DisplayPort-"))
66 return NULL;
67 }
68
69 } else if (streq(subsystem, "pci")) {
70 const char *value;
71
72 value = udev_device_get_sysattr_value(parent, "class");
73 if (value) {
39883f62 74 unsigned long class = 0;
0f4ba83c
LP
75
76 if (safe_atolu(value, &class) < 0) {
938d2699
ZJS
77 log_warning("Cannot parse PCI class %s of device %s:%s.",
78 value, subsystem, sysname);
0f4ba83c
LP
79 return NULL;
80 }
81
82 /* Graphics card */
83 if (class == 0x30000)
84 return parent;
85 }
86
87 } else if (streq(subsystem, "platform"))
88 return parent;
89
90 return find_pci_or_platform_parent(parent);
91}
92
93static bool same_device(struct udev_device *a, struct udev_device *b) {
94 assert(a);
95 assert(b);
96
97 if (!streq_ptr(udev_device_get_subsystem(a), udev_device_get_subsystem(b)))
98 return false;
99
100 if (!streq_ptr(udev_device_get_sysname(a), udev_device_get_sysname(b)))
101 return false;
102
103 return true;
104}
105
106static 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;
111 int r;
112
113 assert(udev);
114 assert(device);
115
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. */
129
130 subsystem = udev_device_get_subsystem(device);
131 if (!streq_ptr(subsystem, "backlight"))
132 return true;
133
134 v = udev_device_get_sysattr_value(device, "type");
135 if (!streq_ptr(v, "raw"))
136 return true;
137
138 parent = find_pci_or_platform_parent(device);
139 if (!parent)
140 return true;
141
142 subsystem = udev_device_get_subsystem(parent);
143 if (!subsystem)
144 return true;
145
146 enumerate = udev_enumerate_new(udev);
147 if (!enumerate)
148 return true;
149
150 r = udev_enumerate_add_match_subsystem(enumerate, "backlight");
151 if (r < 0)
152 return true;
153
154 r = udev_enumerate_scan_devices(enumerate);
155 if (r < 0)
156 return true;
157
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;
163
164 other = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item));
165 if (!other)
166 return true;
167
168 if (same_device(device, other))
169 continue;
170
171 v = udev_device_get_sysattr_value(other, "type");
172 if (!streq_ptr(v, "platform") && !streq_ptr(v, "firmware"))
173 continue;
174
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
178 * ours. */
179 other_parent = find_pci_or_platform_parent(other);
180 if (!other_parent)
181 continue;
182
183 if (same_device(parent, other_parent)) {
184 /* Both have the same PCI parent, that means
185 * we are out. */
938d2699
ZJS
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));
0f4ba83c
LP
189 return false;
190 }
191
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
196 * are out. */
938d2699
ZJS
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));
0f4ba83c
LP
200 return false;
201 }
202 }
203
204 return true;
205}
206
3cadce7d 207static unsigned get_max_brightness(struct udev_device *device) {
7b909d74
JT
208 int r;
209 const char *max_brightness_str;
3cadce7d 210 unsigned max_brightness;
7b909d74
JT
211
212 max_brightness_str = udev_device_get_sysattr_value(device, "max_brightness");
213 if (!max_brightness_str) {
c7fdf44d 214 log_warning("Failed to read 'max_brightness' attribute.");
3cadce7d 215 return 0;
7b909d74
JT
216 }
217
3cadce7d 218 r = safe_atou(max_brightness_str, &max_brightness);
7b909d74 219 if (r < 0) {
da927ba9 220 log_warning_errno(r, "Failed to parse 'max_brightness' \"%s\": %m", max_brightness_str);
c7fdf44d
LP
221 return 0;
222 }
223
224 if (max_brightness <= 0) {
225 log_warning("Maximum brightness is 0, ignoring device.");
3cadce7d 226 return 0;
7b909d74
JT
227 }
228
3cadce7d
TB
229 return max_brightness;
230}
231
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
4cd2b2cf
DT
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. */
3cadce7d
TB
237static void clamp_brightness(struct udev_device *device, char **value, unsigned max_brightness) {
238 int r;
0c9d8f1d 239 unsigned brightness, new_brightness, min_brightness;
4cd2b2cf 240 const char *subsystem;
3cadce7d
TB
241
242 r = safe_atou(*value, &brightness);
7b909d74 243 if (r < 0) {
da927ba9 244 log_warning_errno(r, "Failed to parse brightness \"%s\": %m", *value);
7b909d74
JT
245 return;
246 }
247
4cd2b2cf
DT
248 subsystem = udev_device_get_subsystem(device);
249 if (streq_ptr(subsystem, "backlight"))
250 min_brightness = MAX(1U, max_brightness/20);
251 else
252 min_brightness = 0;
253
0c9d8f1d 254 new_brightness = CLAMP(brightness, min_brightness, max_brightness);
7b909d74
JT
255 if (new_brightness != brightness) {
256 char *old_value = *value;
257
258 r = asprintf(value, "%u", new_brightness);
259 if (r < 0) {
260 log_oom();
261 return;
262 }
263
0c9d8f1d
JN
264 log_info("Saved brightness %s %s to %s.", old_value,
265 new_brightness > brightness ?
266 "too low; increasing" : "too high; decreasing",
267 *value);
268
7b909d74
JT
269 free(old_value);
270 }
271}
272
3731acf1 273int main(int argc, char *argv[]) {
1ca208fb
ZJS
274 _cleanup_udev_unref_ struct udev *udev = NULL;
275 _cleanup_udev_device_unref_ struct udev_device *device = NULL;
be3f52f4
LP
276 _cleanup_free_ char *saved = NULL, *ss = NULL, *escaped_ss = NULL, *escaped_sysname = NULL, *escaped_path_id = NULL;
277 const char *sysname, *path_id;
3cadce7d 278 unsigned max_brightness;
3731acf1
LP
279 int r;
280
281 if (argc != 3) {
282 log_error("This program requires two arguments.");
283 return EXIT_FAILURE;
284 }
285
286 log_set_target(LOG_TARGET_AUTO);
287 log_parse_environment();
288 log_open();
289
290 umask(0022);
291
ef5bfcf6 292 r = mkdir_p("/var/lib/systemd/backlight", 0755);
3731acf1 293 if (r < 0) {
c33b3297 294 log_error_errno(r, "Failed to create backlight directory /var/lib/systemd/backlight: %m");
1ca208fb 295 return EXIT_FAILURE;
3731acf1
LP
296 }
297
298 udev = udev_new();
299 if (!udev) {
1ca208fb
ZJS
300 log_oom();
301 return EXIT_FAILURE;
3731acf1
LP
302 }
303
0f4ba83c
LP
304 sysname = strchr(argv[2], ':');
305 if (!sysname) {
938d2699 306 log_error("Requires a subsystem and sysname pair specifying a backlight device.");
0f4ba83c
LP
307 return EXIT_FAILURE;
308 }
309
310 ss = strndup(argv[2], sysname - argv[2]);
311 if (!ss) {
312 log_oom();
313 return EXIT_FAILURE;
314 }
315
316 sysname++;
317
318 if (!streq(ss, "backlight") && !streq(ss, "leds")) {
319 log_error("Not a backlight or LED device: '%s:%s'", ss, sysname);
320 return EXIT_FAILURE;
321 }
322
875c6e1b 323 errno = 0;
0f4ba83c 324 device = udev_device_new_from_subsystem_sysname(udev, ss, sysname);
3731acf1 325 if (!device) {
1ca208fb 326 if (errno != 0)
56f64d95 327 log_error_errno(errno, "Failed to get backlight or LED device '%s:%s': %m", ss, sysname);
1ca208fb 328 else
0f4ba83c 329 log_oom();
3731acf1 330
1ca208fb 331 return EXIT_FAILURE;
3731acf1
LP
332 }
333
3cadce7d
TB
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.
337 */
338 max_brightness = get_max_brightness(device);
339 if (max_brightness == 0)
340 return EXIT_SUCCESS;
341
be3f52f4
LP
342 escaped_ss = cescape(ss);
343 if (!escaped_ss) {
344 log_oom();
345 return EXIT_FAILURE;
346 }
347
348 escaped_sysname = cescape(sysname);
349 if (!escaped_sysname) {
350 log_oom();
351 return EXIT_FAILURE;
352 }
353
354 path_id = udev_device_get_property_value(device, "ID_PATH");
355 if (path_id) {
356 escaped_path_id = cescape(path_id);
357 if (!escaped_path_id) {
358 log_oom();
359 return EXIT_FAILURE;
360 }
361
362 saved = strjoin("/var/lib/systemd/backlight/", escaped_path_id, ":", escaped_ss, ":", escaped_sysname, NULL);
363 } else
364 saved = strjoin("/var/lib/systemd/backlight/", escaped_ss, ":", escaped_sysname, NULL);
365
3731acf1 366 if (!saved) {
1ca208fb
ZJS
367 log_oom();
368 return EXIT_FAILURE;
3731acf1
LP
369 }
370
0f4ba83c
LP
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
73e231ab 375 * with a specific backlight. To deal with this, we will
0f4ba83c
LP
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. */
379
b76388e1 380 if (streq(argv[1], "load")) {
3731acf1 381 _cleanup_free_ char *value = NULL;
bca81be7 382 const char *clamp;
3731acf1 383
b76388e1
MB
384 if (!shall_restore_state())
385 return EXIT_SUCCESS;
386
0f4ba83c
LP
387 if (!validate_device(udev, device))
388 return EXIT_SUCCESS;
389
3731acf1
LP
390 r = read_one_line_file(saved, &value);
391 if (r < 0) {
392
1ca208fb
ZJS
393 if (r == -ENOENT)
394 return EXIT_SUCCESS;
3731acf1 395
da927ba9 396 log_error_errno(r, "Failed to read %s: %m", saved);
1ca208fb 397 return EXIT_FAILURE;
3731acf1
LP
398 }
399
bca81be7
TM
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);
7b909d74 403
3731acf1
LP
404 r = udev_device_set_sysattr_value(device, "brightness", value);
405 if (r < 0) {
c33b3297 406 log_error_errno(r, "Failed to write system 'brightness' attribute: %m");
1ca208fb 407 return EXIT_FAILURE;
3731acf1
LP
408 }
409
410 } else if (streq(argv[1], "save")) {
411 const char *value;
412
0f4ba83c
LP
413 if (!validate_device(udev, device)) {
414 unlink(saved);
415 return EXIT_SUCCESS;
416 }
417
3731acf1
LP
418 value = udev_device_get_sysattr_value(device, "brightness");
419 if (!value) {
938d2699 420 log_error("Failed to read system 'brightness' attribute");
1ca208fb 421 return EXIT_FAILURE;
3731acf1
LP
422 }
423
4c1fc3e4 424 r = write_string_file(saved, value, WRITE_STRING_FILE_CREATE);
3731acf1 425 if (r < 0) {
da927ba9 426 log_error_errno(r, "Failed to write %s: %m", saved);
1ca208fb 427 return EXIT_FAILURE;
3731acf1
LP
428 }
429
430 } else {
431 log_error("Unknown verb %s.", argv[1]);
1ca208fb 432 return EXIT_FAILURE;
3731acf1
LP
433 }
434
1ca208fb 435 return EXIT_SUCCESS;
3731acf1 436}