]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/backlight/backlight.c
backlight: accept embedded display port named e.g. card0-eDP-1
[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;
0f4ba83c 133 const char *v, *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
9aadd281
YW
148 r = sd_device_get_subsystem(device, &subsystem);
149 if (r < 0)
150 return r;
151 if (!streq(subsystem, "backlight"))
0f4ba83c
LP
152 return true;
153
9aadd281
YW
154 r = sd_device_get_sysattr_value(device, "type", &v);
155 if (r < 0)
156 return r;
157 if (!streq(v, "raw"))
0f4ba83c
LP
158 return true;
159
9aadd281
YW
160 r = find_pci_or_platform_parent(device, &parent);
161 if (r < 0)
162 return r;
0f4ba83c 163
9aadd281
YW
164 r = sd_device_get_subsystem(parent, &subsystem);
165 if (r < 0)
166 return r;
0f4ba83c 167
9aadd281
YW
168 r = sd_device_enumerator_new(&enumerate);
169 if (r < 0)
170 return r;
0f4ba83c 171
9aadd281 172 r = sd_device_enumerator_allow_uninitialized(enumerate);
0f4ba83c 173 if (r < 0)
9aadd281 174 return r;
0f4ba83c 175
9aadd281 176 r = sd_device_enumerator_add_match_subsystem(enumerate, "backlight", true);
0f4ba83c 177 if (r < 0)
9aadd281 178 return r;
0f4ba83c 179
8437c059 180 FOREACH_DEVICE(enumerate, other) {
9aadd281 181 const char *other_subsystem;
e8596ca5 182 sd_device *other_parent;
0f4ba83c 183
9aadd281 184 if (same_device(device, other) > 0)
0f4ba83c
LP
185 continue;
186
9aadd281
YW
187 if (sd_device_get_sysattr_value(other, "type", &v) < 0 ||
188 !STR_IN_SET(v, "platform", "firmware"))
0f4ba83c
LP
189 continue;
190
b77c9299
YW
191 /* OK, so there's another backlight device, and it's a platform or firmware device.
192 * Let's see if we can verify it belongs to the same device as ours. */
9aadd281 193 if (find_pci_or_platform_parent(other, &other_parent) < 0)
0f4ba83c
LP
194 continue;
195
ba6c9b79 196 if (same_device(parent, other_parent) > 0) {
9aadd281
YW
197 const char *device_sysname = NULL, *other_sysname = NULL;
198
199 /* Both have the same PCI parent, that means we are out. */
200
201 (void) sd_device_get_sysname(device, &device_sysname);
202 (void) sd_device_get_sysname(other, &other_sysname);
203
938d2699 204 log_debug("Skipping backlight device %s, since device %s is on same PCI device and takes precedence.",
9aadd281 205 device_sysname, other_sysname);
0f4ba83c
LP
206 return false;
207 }
208
9aadd281
YW
209 if (sd_device_get_subsystem(other_parent, &other_subsystem) < 0)
210 continue;
211
212 if (streq(other_subsystem, "platform") && streq(subsystem, "pci")) {
213 const char *device_sysname = NULL, *other_sysname = NULL;
214
215 /* The other is connected to the platform bus and we are a PCI device, that also means we are out. */
216
217 (void) sd_device_get_sysname(device, &device_sysname);
218 (void) sd_device_get_sysname(other, &other_sysname);
219
938d2699 220 log_debug("Skipping backlight device %s, since device %s is a platform device and takes precedence.",
9aadd281 221 device_sysname, other_sysname);
0f4ba83c
LP
222 return false;
223 }
224 }
225
226 return true;
227}
228
9aadd281 229static int get_max_brightness(sd_device *device, unsigned *ret) {
c0391616 230 const char *s;
9aadd281 231 int r;
7b909d74 232
9aadd281
YW
233 assert(device);
234 assert(ret);
235
c0391616 236 r = sd_device_get_sysattr_value(device, "max_brightness", &s);
9aadd281 237 if (r < 0)
87a9a197 238 return log_device_warning_errno(device, r, "Failed to read 'max_brightness' attribute: %m");
7b909d74 239
c0391616 240 r = safe_atou(s, ret);
9aadd281 241 if (r < 0)
c0391616 242 return log_device_warning_errno(device, r, "Failed to parse 'max_brightness' \"%s\": %m", s);
7b909d74 243
9aadd281 244 return 0;
3cadce7d
TB
245}
246
3bacb7e7
YW
247static int clamp_brightness(sd_device *device, bool saved, unsigned max_brightness, unsigned *brightness) {
248 unsigned new_brightness, min_brightness;
4cd2b2cf 249 const char *subsystem;
3e44d24d 250 int r;
3cadce7d 251
3bacb7e7
YW
252 assert(device);
253 assert(brightness);
7b909d74 254
b77c9299
YW
255 /* Some systems turn the backlight all the way off at the lowest levels. This clamps the saved
256 * brightness to at least 1 or 5% of max_brightness in case of 'backlight' subsystem. This
257 * avoids preserving an unreadably dim screen, which would otherwise force the user to disable
258 * state restoration. */
259
9aadd281
YW
260 r = sd_device_get_subsystem(device, &subsystem);
261 if (r < 0)
87a9a197 262 return log_device_warning_errno(device, r, "Failed to get device subsystem: %m");
9aadd281
YW
263
264 if (streq(subsystem, "backlight"))
4cd2b2cf
DT
265 min_brightness = MAX(1U, max_brightness/20);
266 else
267 min_brightness = 0;
268
3bacb7e7
YW
269 new_brightness = CLAMP(*brightness, min_brightness, max_brightness);
270 if (new_brightness != *brightness)
271 log_device_info(device, "%s brightness %u is %s to %u.",
272 saved ? "Saved" : "Current",
273 *brightness,
274 new_brightness > *brightness ?
87a9a197 275 "too low; increasing" : "too high; decreasing",
3bacb7e7 276 new_brightness);
9aadd281 277
3bacb7e7 278 *brightness = new_brightness;
9aadd281 279 return 0;
7b909d74
JT
280}
281
9aadd281 282static bool shall_clamp(sd_device *d) {
4432b941
SR
283 const char *s;
284 int r;
285
286 assert(d);
287
9aadd281 288 r = sd_device_get_property_value(d, "ID_BACKLIGHT_CLAMP", &s);
87a9a197 289 if (r < 0) {
06d98bdc
YW
290 if (r != -ENOENT)
291 log_device_debug_errno(d, r, "Failed to get ID_BACKLIGHT_CLAMP property, ignoring: %m");
4432b941 292 return true;
87a9a197 293 }
4432b941
SR
294
295 r = parse_boolean(s);
296 if (r < 0) {
87a9a197 297 log_device_debug_errno(d, r, "Failed to parse ID_BACKLIGHT_CLAMP property, ignoring: %m");
4432b941
SR
298 return true;
299 }
300
301 return r;
302}
303
3bacb7e7
YW
304static int read_brightness(sd_device *device, unsigned max_brightness, unsigned *ret_brightness) {
305 const char *subsystem, *value;
306 unsigned brightness;
437b9a7f
YW
307 int r;
308
309 assert(device);
3bacb7e7 310 assert(ret_brightness);
437b9a7f
YW
311
312 r = sd_device_get_subsystem(device, &subsystem);
313 if (r < 0)
314 return log_device_debug_errno(device, r, "Failed to get subsystem: %m");
315
316 if (streq(subsystem, "backlight")) {
3bacb7e7
YW
317 r = sd_device_get_sysattr_value(device, "actual_brightness", &value);
318 if (r == -ENOENT) {
319 log_device_debug_errno(device, r, "Failed to read 'actual_brightness' attribute, "
320 "fall back to use 'brightness' attribute: %m");
321 goto use_brightness;
322 }
323 if (r < 0)
437b9a7f
YW
324 return log_device_debug_errno(device, r, "Failed to read 'actual_brightness' attribute: %m");
325
3bacb7e7
YW
326 r = safe_atou(value, &brightness);
327 if (r < 0) {
328 log_device_debug_errno(device, r, "Failed to parse 'actual_brightness' attribute, "
329 "fall back to use 'brightness' attribute: %s", value);
330 goto use_brightness;
331 }
332
333 if (brightness > max_brightness) {
334 log_device_debug(device, "actual_brightness=%u is larger than max_brightness=%u, "
335 "fall back to use 'brightness' attribute", brightness, max_brightness);
336 goto use_brightness;
337 }
338
8dc1ad04 339 log_device_debug(device, "Current actual_brightness is %u", brightness);
3bacb7e7
YW
340 *ret_brightness = brightness;
341 return 0;
437b9a7f
YW
342 }
343
3bacb7e7
YW
344use_brightness:
345 r = sd_device_get_sysattr_value(device, "brightness", &value);
437b9a7f
YW
346 if (r < 0)
347 return log_device_debug_errno(device, r, "Failed to read 'brightness' attribute: %m");
348
3bacb7e7
YW
349 r = safe_atou(value, &brightness);
350 if (r < 0)
351 return log_device_debug_errno(device, r, "Failed to parse 'brightness' attribute: %s", value);
352
353 if (brightness > max_brightness)
354 return log_device_debug_errno(device, SYNTHETIC_ERRNO(EINVAL),
355 "brightness=%u is larger than max_brightness=%u",
356 brightness, max_brightness);
357
8dc1ad04 358 log_device_debug(device, "Current brightness is %u", brightness);
3bacb7e7 359 *ret_brightness = brightness;
437b9a7f
YW
360 return 0;
361}
362
1ddae15a 363static int run(int argc, char *argv[]) {
9aadd281 364 _cleanup_(sd_device_unrefp) sd_device *device = NULL;
3e44d24d
LP
365 _cleanup_free_ char *escaped_ss = NULL, *escaped_sysname = NULL, *escaped_path_id = NULL;
366 const char *sysname, *path_id, *ss, *saved;
3bacb7e7 367 unsigned max_brightness, brightness;
3731acf1
LP
368 int r;
369
d2acb93d 370 log_setup();
daa227a3 371
542bb9be 372 if (argv_looks_like_help(argc, argv))
b23728ec
P
373 return help();
374
74f1bb5c
YW
375 if (argc != 3)
376 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program requires two arguments.");
3731acf1 377
7a9737bc
YW
378 if (!STR_IN_SET(argv[1], "load", "save"))
379 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown verb %s.", argv[1]);
380
3731acf1
LP
381 umask(0022);
382
ef5bfcf6 383 r = mkdir_p("/var/lib/systemd/backlight", 0755);
1ddae15a
YW
384 if (r < 0)
385 return log_error_errno(r, "Failed to create backlight directory /var/lib/systemd/backlight: %m");
3731acf1 386
0f4ba83c 387 sysname = strchr(argv[2], ':');
74f1bb5c
YW
388 if (!sysname)
389 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Requires a subsystem and sysname pair specifying a backlight device.");
0f4ba83c 390
2f82562b 391 ss = strndupa_safe(argv[2], sysname - argv[2]);
0f4ba83c
LP
392
393 sysname++;
394
74f1bb5c
YW
395 if (!STR_IN_SET(ss, "backlight", "leds"))
396 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a backlight or LED device: '%s:%s'", ss, sysname);
0f4ba83c 397
9aadd281 398 r = sd_device_new_from_subsystem_sysname(&device, ss, sysname);
f0f65087
YW
399 if (r < 0) {
400 bool ignore = r == -ENODEV;
401
402 /* Some drivers, e.g. for AMD GPU, removes acpi backlight device soon after it is added.
403 * See issue #21997. */
404 log_full_errno(ignore ? LOG_DEBUG : LOG_ERR, r,
405 "Failed to get backlight or LED device '%s:%s'%s: %m",
406 ss, sysname, ignore ? ", ignoring" : "");
407 return ignore ? 0 : r;
408 }
3731acf1 409
b77c9299
YW
410 /* If max_brightness is 0, then there is no actual backlight device. This happens on desktops
411 * with Asus mainboards that load the eeepc-wmi module. */
1ddae15a
YW
412 if (get_max_brightness(device, &max_brightness) < 0)
413 return 0;
3cadce7d 414
c0391616
ZJS
415 if (max_brightness == 0) {
416 log_device_warning(device, "Maximum brightness is 0, ignoring device.");
417 return 0;
418 }
419
420 log_device_debug(device, "Maximum brightness is %u", max_brightness);
421
be3f52f4 422 escaped_ss = cescape(ss);
1ddae15a
YW
423 if (!escaped_ss)
424 return log_oom();
be3f52f4
LP
425
426 escaped_sysname = cescape(sysname);
1ddae15a
YW
427 if (!escaped_sysname)
428 return log_oom();
be3f52f4 429
9aadd281 430 if (sd_device_get_property_value(device, "ID_PATH", &path_id) >= 0) {
be3f52f4 431 escaped_path_id = cescape(path_id);
1ddae15a
YW
432 if (!escaped_path_id)
433 return log_oom();
be3f52f4 434
3e44d24d 435 saved = strjoina("/var/lib/systemd/backlight/", escaped_path_id, ":", escaped_ss, ":", escaped_sysname);
be3f52f4 436 } else
3e44d24d 437 saved = strjoina("/var/lib/systemd/backlight/", escaped_ss, ":", escaped_sysname);
3731acf1 438
b77c9299
YW
439 /* If there are multiple conflicting backlight devices, then their probing at boot-time might
440 * happen in any order. This means the validity checking of the device then is not reliable,
441 * since it might not see other devices conflicting with a specific backlight. To deal with
442 * this, we will actively delete backlight state files at shutdown (where device probing should
443 * be complete), so that the validity check at boot time doesn't have to be reliable. */
0f4ba83c 444
b76388e1 445 if (streq(argv[1], "load")) {
3731acf1 446 _cleanup_free_ char *value = NULL;
4432b941 447 bool clamp;
3731acf1 448
934ae16b 449 if (shall_restore_state() == 0)
1ddae15a 450 return 0;
b76388e1 451
9aadd281 452 if (validate_device(device) == 0)
1ddae15a 453 return 0;
0f4ba83c 454
4432b941
SR
455 clamp = shall_clamp(device);
456
3731acf1 457 r = read_one_line_file(saved, &value);
3bacb7e7
YW
458 if (r < 0 && r != -ENOENT)
459 return log_error_errno(r, "Failed to read %s: %m", saved);
460 if (r > 0) {
461 r = safe_atou(value, &brightness);
462 if (r < 0) {
8dc1ad04
YW
463 log_warning_errno(r, "Failed to parse saved brightness '%s', removing %s.",
464 value, saved);
3bacb7e7
YW
465 (void) unlink(saved);
466 } else {
8dc1ad04 467 log_debug("Using saved brightness %u.", brightness);
3bacb7e7
YW
468 if (clamp)
469 (void) clamp_brightness(device, true, max_brightness, &brightness);
470
471 /* Do not fall back to read current brightness below. */
472 r = 1;
473 }
474 }
475 if (r <= 0) {
476 /* Fallback to clamping current brightness or exit early if clamping is not
477 * supported/enabled. */
4432b941 478 if (!clamp)
1ddae15a 479 return 0;
3731acf1 480
3bacb7e7 481 r = read_brightness(device, max_brightness, &brightness);
1ddae15a 482 if (r < 0)
437b9a7f 483 return log_device_error_errno(device, r, "Failed to read current brightness: %m");
4432b941 484
3bacb7e7
YW
485 (void) clamp_brightness(device, false, max_brightness, &brightness);
486 }
7b909d74 487
3bacb7e7 488 r = sd_device_set_sysattr_valuef(device, "brightness", "%u", brightness);
1ddae15a
YW
489 if (r < 0)
490 return log_device_error_errno(device, r, "Failed to write system 'brightness' attribute: %m");
3731acf1
LP
491
492 } else if (streq(argv[1], "save")) {
9aadd281 493 if (validate_device(device) == 0) {
6990fb6b 494 (void) unlink(saved);
1ddae15a 495 return 0;
0f4ba83c
LP
496 }
497
3bacb7e7 498 r = read_brightness(device, max_brightness, &brightness);
1ddae15a 499 if (r < 0)
437b9a7f 500 return log_device_error_errno(device, r, "Failed to read current brightness: %m");
3731acf1 501
3bacb7e7 502 r = write_string_filef(saved, WRITE_STRING_FILE_CREATE, "%u", brightness);
1ddae15a
YW
503 if (r < 0)
504 return log_device_error_errno(device, r, "Failed to write %s: %m", saved);
3731acf1 505
74f1bb5c 506 } else
04499a70 507 assert_not_reached();
3731acf1 508
1ddae15a 509 return 0;
3731acf1 510}
1ddae15a
YW
511
512DEFINE_MAIN_FUNCTION(run);