]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/backlight/backlight.c
backlight: filter out unnecessary backlight devices by device enumerator
[thirdparty/systemd.git] / src / backlight / backlight.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
3731acf1 2
ca78ad1d
ZJS
3#include <sys/stat.h>
4#include <sys/types.h>
5#include <unistd.h>
6
9aadd281 7#include "sd-device.h"
b4bbcaa9 8
b5efdb8a 9#include "alloc-util.h"
8437c059 10#include "device-util.h"
4f5dd394 11#include "escape.h"
3731acf1 12#include "fileio.h"
1ddae15a 13#include "main-func.h"
4f5dd394 14#include "mkdir.h"
4e731273 15#include "parse-util.h"
b23728ec 16#include "pretty-print.h"
542bb9be 17#include "process-util.h"
2bfa8466 18#include "reboot-util.h"
07630cea 19#include "string-util.h"
9aadd281 20#include "strv.h"
542bb9be 21#include "terminal-util.h"
4f5dd394 22#include "util.h"
3731acf1 23
b23728ec
P
24static int help(void) {
25 _cleanup_free_ char *link = NULL;
26 int r;
27
28 r = terminal_urlify_man("systemd-backlight", "8", &link);
29 if (r < 0)
30 return log_oom();
31
32 printf("%s save [backlight|leds]:DEVICE\n"
33 "%s load [backlight|leds]:DEVICE\n"
34 "\n%sSave and restore backlight brightness at shutdown and boot.%s\n\n"
35 " save Save current brightness\n"
36 " load Set brightness to be the previously saved value\n"
bc556335
DDM
37 "\nSee the %s for details.\n",
38 program_invocation_short_name,
39 program_invocation_short_name,
40 ansi_highlight(),
41 ansi_normal(),
42 link);
b23728ec
P
43
44 return 0;
45}
46
9aadd281
YW
47static int find_pci_or_platform_parent(sd_device *device, sd_device **ret) {
48 const char *subsystem, *sysname, *value;
49 sd_device *parent;
50 int r;
0f4ba83c
LP
51
52 assert(device);
9aadd281 53 assert(ret);
0f4ba83c 54
9aadd281
YW
55 r = sd_device_get_parent(device, &parent);
56 if (r < 0)
57 return r;
0f4ba83c 58
9aadd281
YW
59 r = sd_device_get_subsystem(parent, &subsystem);
60 if (r < 0)
61 return r;
0f4ba83c 62
9aadd281
YW
63 r = sd_device_get_sysname(parent, &sysname);
64 if (r < 0)
65 return r;
0f4ba83c
LP
66
67 if (streq(subsystem, "drm")) {
68 const char *c;
69
70 c = startswith(sysname, "card");
71 if (!c)
9aadd281 72 return -ENODATA;
0f4ba83c 73
938d2699 74 c += strspn(c, DIGITS);
85ff4150 75 if (*c == '-' && !STARTSWITH_SET(c, "-LVDS-", "-Embedded DisplayPort-", "-eDP-"))
0f4ba83c 76 /* A connector DRM device, let's ignore all but LVDS and eDP! */
d17d66f0 77 return -EOPNOTSUPP;
0f4ba83c 78
9aadd281
YW
79 } else if (streq(subsystem, "pci") &&
80 sd_device_get_sysattr_value(parent, "class", &value) >= 0) {
afa8ffae 81 unsigned long class;
0f4ba83c 82
9aadd281
YW
83 r = safe_atolu(value, &class);
84 if (r < 0)
85 return log_warning_errno(r, "Cannot parse PCI class '%s' of device %s:%s: %m",
86 value, subsystem, sysname);
0f4ba83c 87
9aadd281
YW
88 /* Graphics card */
89 if (class == 0x30000) {
e8596ca5 90 *ret = parent;
9aadd281 91 return 0;
0f4ba83c
LP
92 }
93
9aadd281 94 } else if (streq(subsystem, "platform")) {
e8596ca5 95 *ret = parent;
9aadd281
YW
96 return 0;
97 }
0f4ba83c 98
9aadd281 99 return find_pci_or_platform_parent(parent, ret);
0f4ba83c
LP
100}
101
9aadd281
YW
102static int same_device(sd_device *a, sd_device *b) {
103 const char *a_val, *b_val;
104 int r;
105
0f4ba83c
LP
106 assert(a);
107 assert(b);
108
9aadd281
YW
109 r = sd_device_get_subsystem(a, &a_val);
110 if (r < 0)
111 return r;
112
113 r = sd_device_get_subsystem(b, &b_val);
114 if (r < 0)
115 return r;
0f4ba83c 116
403660c5 117 if (!streq(a_val, b_val))
0f4ba83c
LP
118 return false;
119
9aadd281
YW
120 r = sd_device_get_sysname(a, &a_val);
121 if (r < 0)
122 return r;
123
124 r = sd_device_get_sysname(b, &b_val);
125 if (r < 0)
126 return r;
127
403660c5 128 return streq(a_val, b_val);
0f4ba83c
LP
129}
130
9aadd281
YW
131static int validate_device(sd_device *device) {
132 _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *enumerate = NULL;
b2f77b5e 133 const char *v, *sysname, *subsystem;
e8596ca5 134 sd_device *parent, *other;
0f4ba83c
LP
135 int r;
136
0f4ba83c
LP
137 assert(device);
138
b77c9299
YW
139 /* Verify whether we should actually care for a specific backlight device. For backlight devices
140 * there might be multiple ways to access the same control: "firmware" (i.e. ACPI), "platform"
141 * (i.e. via the machine's EC) and "raw" (via the graphics card). In general we should prefer
142 * "firmware" (i.e. ACPI) or "platform" access over "raw" access, in order not to confuse the
143 * BIOS/EC, and compatibility with possible low-level hotkey handling of screen brightness. The
144 * kernel will already make sure to expose only one of "firmware" and "platform" for the same
145 * device to userspace. However, we still need to make sure that we use "raw" only if no
146 * "firmware" or "platform" device for the same device exists. */
0f4ba83c 147
b2f77b5e
YW
148 r = sd_device_get_sysname(device, &sysname);
149 if (r < 0)
150 return log_device_debug_errno(device, r, "Failed to get sysname: %m");
151
9aadd281
YW
152 r = sd_device_get_subsystem(device, &subsystem);
153 if (r < 0)
b2f77b5e 154 return log_device_debug_errno(device, r, "Failed to get subsystem: %m");
9aadd281 155 if (!streq(subsystem, "backlight"))
0f4ba83c
LP
156 return true;
157
9aadd281
YW
158 r = sd_device_get_sysattr_value(device, "type", &v);
159 if (r < 0)
b2f77b5e 160 return log_device_debug_errno(device, r, "Failed to read 'type' sysattr: %m");
9aadd281 161 if (!streq(v, "raw"))
0f4ba83c
LP
162 return true;
163
9aadd281
YW
164 r = find_pci_or_platform_parent(device, &parent);
165 if (r < 0)
b2f77b5e 166 return log_device_debug_errno(device, r, "Failed to find PCI or platform parent: %m");
0f4ba83c 167
9aadd281
YW
168 r = sd_device_get_subsystem(parent, &subsystem);
169 if (r < 0)
b2f77b5e
YW
170 return log_device_debug_errno(parent, r, "Failed to get subsystem: %m");
171
172 if (DEBUG_LOGGING) {
173 const char *s = NULL;
174
175 (void) sd_device_get_syspath(parent, &s);
176 log_device_debug(device, "Found %s parent device: %s", subsystem, strna(s));
177 }
0f4ba83c 178
9aadd281
YW
179 r = sd_device_enumerator_new(&enumerate);
180 if (r < 0)
b2f77b5e 181 return log_oom_debug();
0f4ba83c 182
9aadd281 183 r = sd_device_enumerator_allow_uninitialized(enumerate);
0f4ba83c 184 if (r < 0)
b2f77b5e 185 return log_debug_errno(r, "Failed to allow uninitialized devices: %m");
0f4ba83c 186
b2f77b5e 187 r = sd_device_enumerator_add_match_subsystem(enumerate, "backlight", /* match = */ true);
0f4ba83c 188 if (r < 0)
b2f77b5e 189 return log_debug_errno(r, "Failed to add subsystem match: %m");
0f4ba83c 190
f8ff4b60
YW
191 r = sd_device_enumerator_add_nomatch_sysname(enumerate, sysname);
192 if (r < 0)
193 return log_debug_errno(r, "Failed to add sysname unmatch: %m");
194
195 r = sd_device_enumerator_add_match_sysattr(enumerate, "type", "platform", /* match = */ true);
196 if (r < 0)
197 return log_debug_errno(r, "Failed to add sysattr match: %m");
198
199 r = sd_device_enumerator_add_match_sysattr(enumerate, "type", "firmware", /* match = */ true);
200 if (r < 0)
201 return log_debug_errno(r, "Failed to add sysattr match: %m");
202
8437c059 203 FOREACH_DEVICE(enumerate, other) {
9aadd281 204 const char *other_subsystem;
e8596ca5 205 sd_device *other_parent;
0f4ba83c 206
b77c9299
YW
207 /* OK, so there's another backlight device, and it's a platform or firmware device.
208 * Let's see if we can verify it belongs to the same device as ours. */
b2f77b5e
YW
209 r = find_pci_or_platform_parent(other, &other_parent);
210 if (r < 0) {
211 log_device_debug_errno(other, r, "Failed to get PCI or platform parent, ignoring: %m");
0f4ba83c 212 continue;
b2f77b5e 213 }
0f4ba83c 214
ba6c9b79 215 if (same_device(parent, other_parent) > 0) {
9aadd281 216 /* Both have the same PCI parent, that means we are out. */
b2f77b5e
YW
217 if (DEBUG_LOGGING) {
218 const char *other_sysname = NULL, *other_type = NULL;
219
220 (void) sd_device_get_sysname(other, &other_sysname);
221 (void) sd_device_get_sysattr_value(other, "type", &other_type);
222 log_device_debug(device,
223 "Found another %s backlight device %s on the same PCI, skipping.",
224 strna(other_type), strna(other_sysname));
225 }
0f4ba83c
LP
226 return false;
227 }
228
b2f77b5e
YW
229 r = sd_device_get_subsystem(other_parent, &other_subsystem);
230 if (r < 0) {
231 log_device_debug_errno(other_parent, r, "Failed to get subsystem, ignoring: %m");
9aadd281 232 continue;
b2f77b5e 233 }
9aadd281
YW
234
235 if (streq(other_subsystem, "platform") && streq(subsystem, "pci")) {
9aadd281 236 /* The other is connected to the platform bus and we are a PCI device, that also means we are out. */
b2f77b5e
YW
237 if (DEBUG_LOGGING) {
238 const char *other_sysname = NULL, *other_type = NULL;
239
240 (void) sd_device_get_sysname(other, &other_sysname);
241 (void) sd_device_get_sysattr_value(other, "type", &other_type);
242 log_device_debug(device,
243 "Found another %s backlight device %s, which has higher precedence, skipping.",
244 strna(other_type), strna(other_sysname));
245 }
0f4ba83c
LP
246 return false;
247 }
248 }
249
250 return true;
251}
252
9aadd281 253static int get_max_brightness(sd_device *device, unsigned *ret) {
c0391616 254 const char *s;
9aadd281 255 int r;
7b909d74 256
9aadd281
YW
257 assert(device);
258 assert(ret);
259
c0391616 260 r = sd_device_get_sysattr_value(device, "max_brightness", &s);
9aadd281 261 if (r < 0)
87a9a197 262 return log_device_warning_errno(device, r, "Failed to read 'max_brightness' attribute: %m");
7b909d74 263
c0391616 264 r = safe_atou(s, ret);
9aadd281 265 if (r < 0)
c0391616 266 return log_device_warning_errno(device, r, "Failed to parse 'max_brightness' \"%s\": %m", s);
7b909d74 267
9aadd281 268 return 0;
3cadce7d
TB
269}
270
3bacb7e7
YW
271static int clamp_brightness(sd_device *device, bool saved, unsigned max_brightness, unsigned *brightness) {
272 unsigned new_brightness, min_brightness;
4cd2b2cf 273 const char *subsystem;
3e44d24d 274 int r;
3cadce7d 275
3bacb7e7
YW
276 assert(device);
277 assert(brightness);
7b909d74 278
b77c9299
YW
279 /* Some systems turn the backlight all the way off at the lowest levels. This clamps the saved
280 * brightness to at least 1 or 5% of max_brightness in case of 'backlight' subsystem. This
281 * avoids preserving an unreadably dim screen, which would otherwise force the user to disable
282 * state restoration. */
283
9aadd281
YW
284 r = sd_device_get_subsystem(device, &subsystem);
285 if (r < 0)
87a9a197 286 return log_device_warning_errno(device, r, "Failed to get device subsystem: %m");
9aadd281
YW
287
288 if (streq(subsystem, "backlight"))
4cd2b2cf
DT
289 min_brightness = MAX(1U, max_brightness/20);
290 else
291 min_brightness = 0;
292
3bacb7e7
YW
293 new_brightness = CLAMP(*brightness, min_brightness, max_brightness);
294 if (new_brightness != *brightness)
295 log_device_info(device, "%s brightness %u is %s to %u.",
296 saved ? "Saved" : "Current",
297 *brightness,
298 new_brightness > *brightness ?
87a9a197 299 "too low; increasing" : "too high; decreasing",
3bacb7e7 300 new_brightness);
9aadd281 301
3bacb7e7 302 *brightness = new_brightness;
9aadd281 303 return 0;
7b909d74
JT
304}
305
9aadd281 306static bool shall_clamp(sd_device *d) {
4432b941
SR
307 const char *s;
308 int r;
309
310 assert(d);
311
9aadd281 312 r = sd_device_get_property_value(d, "ID_BACKLIGHT_CLAMP", &s);
87a9a197 313 if (r < 0) {
06d98bdc
YW
314 if (r != -ENOENT)
315 log_device_debug_errno(d, r, "Failed to get ID_BACKLIGHT_CLAMP property, ignoring: %m");
4432b941 316 return true;
87a9a197 317 }
4432b941
SR
318
319 r = parse_boolean(s);
320 if (r < 0) {
87a9a197 321 log_device_debug_errno(d, r, "Failed to parse ID_BACKLIGHT_CLAMP property, ignoring: %m");
4432b941
SR
322 return true;
323 }
324
325 return r;
326}
327
3bacb7e7
YW
328static int read_brightness(sd_device *device, unsigned max_brightness, unsigned *ret_brightness) {
329 const char *subsystem, *value;
330 unsigned brightness;
437b9a7f
YW
331 int r;
332
333 assert(device);
3bacb7e7 334 assert(ret_brightness);
437b9a7f
YW
335
336 r = sd_device_get_subsystem(device, &subsystem);
337 if (r < 0)
338 return log_device_debug_errno(device, r, "Failed to get subsystem: %m");
339
340 if (streq(subsystem, "backlight")) {
3bacb7e7
YW
341 r = sd_device_get_sysattr_value(device, "actual_brightness", &value);
342 if (r == -ENOENT) {
343 log_device_debug_errno(device, r, "Failed to read 'actual_brightness' attribute, "
344 "fall back to use 'brightness' attribute: %m");
345 goto use_brightness;
346 }
347 if (r < 0)
437b9a7f
YW
348 return log_device_debug_errno(device, r, "Failed to read 'actual_brightness' attribute: %m");
349
3bacb7e7
YW
350 r = safe_atou(value, &brightness);
351 if (r < 0) {
352 log_device_debug_errno(device, r, "Failed to parse 'actual_brightness' attribute, "
353 "fall back to use 'brightness' attribute: %s", value);
354 goto use_brightness;
355 }
356
357 if (brightness > max_brightness) {
358 log_device_debug(device, "actual_brightness=%u is larger than max_brightness=%u, "
359 "fall back to use 'brightness' attribute", brightness, max_brightness);
360 goto use_brightness;
361 }
362
8dc1ad04 363 log_device_debug(device, "Current actual_brightness is %u", brightness);
3bacb7e7
YW
364 *ret_brightness = brightness;
365 return 0;
437b9a7f
YW
366 }
367
3bacb7e7
YW
368use_brightness:
369 r = sd_device_get_sysattr_value(device, "brightness", &value);
437b9a7f
YW
370 if (r < 0)
371 return log_device_debug_errno(device, r, "Failed to read 'brightness' attribute: %m");
372
3bacb7e7
YW
373 r = safe_atou(value, &brightness);
374 if (r < 0)
375 return log_device_debug_errno(device, r, "Failed to parse 'brightness' attribute: %s", value);
376
377 if (brightness > max_brightness)
378 return log_device_debug_errno(device, SYNTHETIC_ERRNO(EINVAL),
379 "brightness=%u is larger than max_brightness=%u",
380 brightness, max_brightness);
381
8dc1ad04 382 log_device_debug(device, "Current brightness is %u", brightness);
3bacb7e7 383 *ret_brightness = brightness;
437b9a7f
YW
384 return 0;
385}
386
1ddae15a 387static int run(int argc, char *argv[]) {
9aadd281 388 _cleanup_(sd_device_unrefp) sd_device *device = NULL;
3e44d24d
LP
389 _cleanup_free_ char *escaped_ss = NULL, *escaped_sysname = NULL, *escaped_path_id = NULL;
390 const char *sysname, *path_id, *ss, *saved;
3bacb7e7 391 unsigned max_brightness, brightness;
3731acf1
LP
392 int r;
393
d2acb93d 394 log_setup();
daa227a3 395
542bb9be 396 if (argv_looks_like_help(argc, argv))
b23728ec
P
397 return help();
398
74f1bb5c
YW
399 if (argc != 3)
400 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program requires two arguments.");
3731acf1 401
7a9737bc
YW
402 if (!STR_IN_SET(argv[1], "load", "save"))
403 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown verb %s.", argv[1]);
404
3731acf1
LP
405 umask(0022);
406
ef5bfcf6 407 r = mkdir_p("/var/lib/systemd/backlight", 0755);
1ddae15a
YW
408 if (r < 0)
409 return log_error_errno(r, "Failed to create backlight directory /var/lib/systemd/backlight: %m");
3731acf1 410
0f4ba83c 411 sysname = strchr(argv[2], ':');
74f1bb5c
YW
412 if (!sysname)
413 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Requires a subsystem and sysname pair specifying a backlight device.");
0f4ba83c 414
2f82562b 415 ss = strndupa_safe(argv[2], sysname - argv[2]);
0f4ba83c
LP
416
417 sysname++;
418
74f1bb5c
YW
419 if (!STR_IN_SET(ss, "backlight", "leds"))
420 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a backlight or LED device: '%s:%s'", ss, sysname);
0f4ba83c 421
9aadd281 422 r = sd_device_new_from_subsystem_sysname(&device, ss, sysname);
f0f65087
YW
423 if (r < 0) {
424 bool ignore = r == -ENODEV;
425
426 /* Some drivers, e.g. for AMD GPU, removes acpi backlight device soon after it is added.
427 * See issue #21997. */
428 log_full_errno(ignore ? LOG_DEBUG : LOG_ERR, r,
429 "Failed to get backlight or LED device '%s:%s'%s: %m",
430 ss, sysname, ignore ? ", ignoring" : "");
431 return ignore ? 0 : r;
432 }
3731acf1 433
b77c9299
YW
434 /* If max_brightness is 0, then there is no actual backlight device. This happens on desktops
435 * with Asus mainboards that load the eeepc-wmi module. */
1ddae15a
YW
436 if (get_max_brightness(device, &max_brightness) < 0)
437 return 0;
3cadce7d 438
c0391616
ZJS
439 if (max_brightness == 0) {
440 log_device_warning(device, "Maximum brightness is 0, ignoring device.");
441 return 0;
442 }
443
444 log_device_debug(device, "Maximum brightness is %u", max_brightness);
445
be3f52f4 446 escaped_ss = cescape(ss);
1ddae15a
YW
447 if (!escaped_ss)
448 return log_oom();
be3f52f4
LP
449
450 escaped_sysname = cescape(sysname);
1ddae15a
YW
451 if (!escaped_sysname)
452 return log_oom();
be3f52f4 453
9aadd281 454 if (sd_device_get_property_value(device, "ID_PATH", &path_id) >= 0) {
be3f52f4 455 escaped_path_id = cescape(path_id);
1ddae15a
YW
456 if (!escaped_path_id)
457 return log_oom();
be3f52f4 458
3e44d24d 459 saved = strjoina("/var/lib/systemd/backlight/", escaped_path_id, ":", escaped_ss, ":", escaped_sysname);
be3f52f4 460 } else
3e44d24d 461 saved = strjoina("/var/lib/systemd/backlight/", escaped_ss, ":", escaped_sysname);
3731acf1 462
b77c9299
YW
463 /* If there are multiple conflicting backlight devices, then their probing at boot-time might
464 * happen in any order. This means the validity checking of the device then is not reliable,
465 * since it might not see other devices conflicting with a specific backlight. To deal with
466 * this, we will actively delete backlight state files at shutdown (where device probing should
467 * be complete), so that the validity check at boot time doesn't have to be reliable. */
0f4ba83c 468
b76388e1 469 if (streq(argv[1], "load")) {
3731acf1 470 _cleanup_free_ char *value = NULL;
4432b941 471 bool clamp;
3731acf1 472
934ae16b 473 if (shall_restore_state() == 0)
1ddae15a 474 return 0;
b76388e1 475
9aadd281 476 if (validate_device(device) == 0)
1ddae15a 477 return 0;
0f4ba83c 478
4432b941
SR
479 clamp = shall_clamp(device);
480
3731acf1 481 r = read_one_line_file(saved, &value);
3bacb7e7
YW
482 if (r < 0 && r != -ENOENT)
483 return log_error_errno(r, "Failed to read %s: %m", saved);
484 if (r > 0) {
485 r = safe_atou(value, &brightness);
486 if (r < 0) {
8dc1ad04
YW
487 log_warning_errno(r, "Failed to parse saved brightness '%s', removing %s.",
488 value, saved);
3bacb7e7
YW
489 (void) unlink(saved);
490 } else {
8dc1ad04 491 log_debug("Using saved brightness %u.", brightness);
3bacb7e7
YW
492 if (clamp)
493 (void) clamp_brightness(device, true, max_brightness, &brightness);
494
495 /* Do not fall back to read current brightness below. */
496 r = 1;
497 }
498 }
499 if (r <= 0) {
500 /* Fallback to clamping current brightness or exit early if clamping is not
501 * supported/enabled. */
4432b941 502 if (!clamp)
1ddae15a 503 return 0;
3731acf1 504
3bacb7e7 505 r = read_brightness(device, max_brightness, &brightness);
1ddae15a 506 if (r < 0)
437b9a7f 507 return log_device_error_errno(device, r, "Failed to read current brightness: %m");
4432b941 508
3bacb7e7
YW
509 (void) clamp_brightness(device, false, max_brightness, &brightness);
510 }
7b909d74 511
3bacb7e7 512 r = sd_device_set_sysattr_valuef(device, "brightness", "%u", brightness);
1ddae15a
YW
513 if (r < 0)
514 return log_device_error_errno(device, r, "Failed to write system 'brightness' attribute: %m");
3731acf1
LP
515
516 } else if (streq(argv[1], "save")) {
9aadd281 517 if (validate_device(device) == 0) {
6990fb6b 518 (void) unlink(saved);
1ddae15a 519 return 0;
0f4ba83c
LP
520 }
521
3bacb7e7 522 r = read_brightness(device, max_brightness, &brightness);
1ddae15a 523 if (r < 0)
437b9a7f 524 return log_device_error_errno(device, r, "Failed to read current brightness: %m");
3731acf1 525
3bacb7e7 526 r = write_string_filef(saved, WRITE_STRING_FILE_CREATE, "%u", brightness);
1ddae15a
YW
527 if (r < 0)
528 return log_device_error_errno(device, r, "Failed to write %s: %m", saved);
3731acf1 529
74f1bb5c 530 } else
04499a70 531 assert_not_reached();
3731acf1 532
1ddae15a 533 return 0;
3731acf1 534}
1ddae15a
YW
535
536DEFINE_MAIN_FUNCTION(run);