]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/backlight/backlight.c
Add SPDX license identifiers to source files under the LGPL
[thirdparty/systemd.git] / src / backlight / backlight.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
3731acf1
LP
2/***
3 This file is part of systemd.
4
5 Copyright 2013 Lennart Poettering
6
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19***/
20
b4bbcaa9
TA
21#include "libudev.h"
22
b5efdb8a 23#include "alloc-util.h"
4f5dd394
LP
24#include "def.h"
25#include "escape.h"
3731acf1 26#include "fileio.h"
4f5dd394 27#include "mkdir.h"
4e731273
LP
28#include "parse-util.h"
29#include "proc-cmdline.h"
07630cea 30#include "string-util.h"
1ca208fb 31#include "udev-util.h"
4f5dd394 32#include "util.h"
3731acf1 33
0f4ba83c
LP
34static struct udev_device *find_pci_or_platform_parent(struct udev_device *device) {
35 struct udev_device *parent;
36 const char *subsystem, *sysname;
37
38 assert(device);
39
40 parent = udev_device_get_parent(device);
41 if (!parent)
42 return NULL;
43
44 subsystem = udev_device_get_subsystem(parent);
45 if (!subsystem)
46 return NULL;
47
48 sysname = udev_device_get_sysname(parent);
49 if (!sysname)
50 return NULL;
51
52 if (streq(subsystem, "drm")) {
53 const char *c;
54
55 c = startswith(sysname, "card");
56 if (!c)
57 return NULL;
58
938d2699 59 c += strspn(c, DIGITS);
0f4ba83c
LP
60 if (*c == '-') {
61 /* A connector DRM device, let's ignore all but LVDS and eDP! */
62
63 if (!startswith(c, "-LVDS-") &&
64 !startswith(c, "-Embedded DisplayPort-"))
65 return NULL;
66 }
67
68 } else if (streq(subsystem, "pci")) {
69 const char *value;
70
71 value = udev_device_get_sysattr_value(parent, "class");
72 if (value) {
39883f62 73 unsigned long class = 0;
0f4ba83c
LP
74
75 if (safe_atolu(value, &class) < 0) {
938d2699
ZJS
76 log_warning("Cannot parse PCI class %s of device %s:%s.",
77 value, subsystem, sysname);
0f4ba83c
LP
78 return NULL;
79 }
80
81 /* Graphics card */
82 if (class == 0x30000)
83 return parent;
84 }
85
86 } else if (streq(subsystem, "platform"))
87 return parent;
88
89 return find_pci_or_platform_parent(parent);
90}
91
92static bool same_device(struct udev_device *a, struct udev_device *b) {
93 assert(a);
94 assert(b);
95
96 if (!streq_ptr(udev_device_get_subsystem(a), udev_device_get_subsystem(b)))
97 return false;
98
99 if (!streq_ptr(udev_device_get_sysname(a), udev_device_get_sysname(b)))
100 return false;
101
102 return true;
103}
104
105static bool validate_device(struct udev *udev, struct udev_device *device) {
106 _cleanup_udev_enumerate_unref_ struct udev_enumerate *enumerate = NULL;
107 struct udev_list_entry *item = NULL, *first = NULL;
108 struct udev_device *parent;
109 const char *v, *subsystem;
110 int r;
111
112 assert(udev);
113 assert(device);
114
115 /* Verify whether we should actually care for a specific
116 * backlight device. For backlight devices there might be
117 * multiple ways to access the same control: "firmware"
118 * (i.e. ACPI), "platform" (i.e. via the machine's EC) and
119 * "raw" (via the graphics card). In general we should prefer
120 * "firmware" (i.e. ACPI) or "platform" access over "raw"
121 * access, in order not to confuse the BIOS/EC, and
122 * compatibility with possible low-level hotkey handling of
123 * screen brightness. The kernel will already make sure to
124 * expose only one of "firmware" and "platform" for the same
125 * device to userspace. However, we still need to make sure
126 * that we use "raw" only if no "firmware" or "platform"
127 * device for the same device exists. */
128
129 subsystem = udev_device_get_subsystem(device);
130 if (!streq_ptr(subsystem, "backlight"))
131 return true;
132
133 v = udev_device_get_sysattr_value(device, "type");
134 if (!streq_ptr(v, "raw"))
135 return true;
136
137 parent = find_pci_or_platform_parent(device);
138 if (!parent)
139 return true;
140
141 subsystem = udev_device_get_subsystem(parent);
142 if (!subsystem)
143 return true;
144
145 enumerate = udev_enumerate_new(udev);
146 if (!enumerate)
147 return true;
148
149 r = udev_enumerate_add_match_subsystem(enumerate, "backlight");
150 if (r < 0)
151 return true;
152
153 r = udev_enumerate_scan_devices(enumerate);
154 if (r < 0)
155 return true;
156
157 first = udev_enumerate_get_list_entry(enumerate);
158 udev_list_entry_foreach(item, first) {
159 _cleanup_udev_device_unref_ struct udev_device *other;
160 struct udev_device *other_parent;
161 const char *other_subsystem;
162
163 other = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item));
164 if (!other)
165 return true;
166
167 if (same_device(device, other))
168 continue;
169
170 v = udev_device_get_sysattr_value(other, "type");
1cf03a4f 171 if (!STRPTR_IN_SET(v, "platform", "firmware"))
0f4ba83c
LP
172 continue;
173
174 /* OK, so there's another backlight device, and it's a
175 * platform or firmware device, so, let's see if we
176 * can verify it belongs to the same device as
177 * ours. */
178 other_parent = find_pci_or_platform_parent(other);
179 if (!other_parent)
180 continue;
181
182 if (same_device(parent, other_parent)) {
183 /* Both have the same PCI parent, that means
184 * we are out. */
938d2699
ZJS
185 log_debug("Skipping backlight device %s, since device %s is on same PCI device and takes precedence.",
186 udev_device_get_sysname(device),
187 udev_device_get_sysname(other));
0f4ba83c
LP
188 return false;
189 }
190
191 other_subsystem = udev_device_get_subsystem(other_parent);
192 if (streq_ptr(other_subsystem, "platform") && streq_ptr(subsystem, "pci")) {
193 /* The other is connected to the platform bus
194 * and we are a PCI device, that also means we
195 * are out. */
938d2699
ZJS
196 log_debug("Skipping backlight device %s, since device %s is a platform device and takes precedence.",
197 udev_device_get_sysname(device),
198 udev_device_get_sysname(other));
0f4ba83c
LP
199 return false;
200 }
201 }
202
203 return true;
204}
205
3cadce7d 206static unsigned get_max_brightness(struct udev_device *device) {
7b909d74
JT
207 int r;
208 const char *max_brightness_str;
3cadce7d 209 unsigned max_brightness;
7b909d74
JT
210
211 max_brightness_str = udev_device_get_sysattr_value(device, "max_brightness");
212 if (!max_brightness_str) {
c7fdf44d 213 log_warning("Failed to read 'max_brightness' attribute.");
3cadce7d 214 return 0;
7b909d74
JT
215 }
216
3cadce7d 217 r = safe_atou(max_brightness_str, &max_brightness);
7b909d74 218 if (r < 0) {
da927ba9 219 log_warning_errno(r, "Failed to parse 'max_brightness' \"%s\": %m", max_brightness_str);
c7fdf44d
LP
220 return 0;
221 }
222
223 if (max_brightness <= 0) {
224 log_warning("Maximum brightness is 0, ignoring device.");
3cadce7d 225 return 0;
7b909d74
JT
226 }
227
3cadce7d
TB
228 return max_brightness;
229}
230
231/* Some systems turn the backlight all the way off at the lowest levels.
232 * clamp_brightness clamps the saved brightness to at least 1 or 5% of
4cd2b2cf
DT
233 * max_brightness in case of 'backlight' subsystem. This avoids preserving
234 * an unreadably dim screen, which would otherwise force the user to
235 * disable state restoration. */
3cadce7d
TB
236static void clamp_brightness(struct udev_device *device, char **value, unsigned max_brightness) {
237 int r;
0c9d8f1d 238 unsigned brightness, new_brightness, min_brightness;
4cd2b2cf 239 const char *subsystem;
3cadce7d
TB
240
241 r = safe_atou(*value, &brightness);
7b909d74 242 if (r < 0) {
da927ba9 243 log_warning_errno(r, "Failed to parse brightness \"%s\": %m", *value);
7b909d74
JT
244 return;
245 }
246
4cd2b2cf
DT
247 subsystem = udev_device_get_subsystem(device);
248 if (streq_ptr(subsystem, "backlight"))
249 min_brightness = MAX(1U, max_brightness/20);
250 else
251 min_brightness = 0;
252
0c9d8f1d 253 new_brightness = CLAMP(brightness, min_brightness, max_brightness);
7b909d74
JT
254 if (new_brightness != brightness) {
255 char *old_value = *value;
256
257 r = asprintf(value, "%u", new_brightness);
258 if (r < 0) {
259 log_oom();
260 return;
261 }
262
0c9d8f1d
JN
263 log_info("Saved brightness %s %s to %s.", old_value,
264 new_brightness > brightness ?
265 "too low; increasing" : "too high; decreasing",
266 *value);
267
7b909d74
JT
268 free(old_value);
269 }
270}
271
3731acf1 272int main(int argc, char *argv[]) {
1ca208fb
ZJS
273 _cleanup_udev_unref_ struct udev *udev = NULL;
274 _cleanup_udev_device_unref_ struct udev_device *device = NULL;
be3f52f4
LP
275 _cleanup_free_ char *saved = NULL, *ss = NULL, *escaped_ss = NULL, *escaped_sysname = NULL, *escaped_path_id = NULL;
276 const char *sysname, *path_id;
3cadce7d 277 unsigned max_brightness;
3731acf1
LP
278 int r;
279
280 if (argc != 3) {
281 log_error("This program requires two arguments.");
282 return EXIT_FAILURE;
283 }
284
285 log_set_target(LOG_TARGET_AUTO);
286 log_parse_environment();
287 log_open();
288
289 umask(0022);
290
ef5bfcf6 291 r = mkdir_p("/var/lib/systemd/backlight", 0755);
3731acf1 292 if (r < 0) {
c33b3297 293 log_error_errno(r, "Failed to create backlight directory /var/lib/systemd/backlight: %m");
1ca208fb 294 return EXIT_FAILURE;
3731acf1
LP
295 }
296
297 udev = udev_new();
298 if (!udev) {
1ca208fb
ZJS
299 log_oom();
300 return EXIT_FAILURE;
3731acf1
LP
301 }
302
0f4ba83c
LP
303 sysname = strchr(argv[2], ':');
304 if (!sysname) {
938d2699 305 log_error("Requires a subsystem and sysname pair specifying a backlight device.");
0f4ba83c
LP
306 return EXIT_FAILURE;
307 }
308
309 ss = strndup(argv[2], sysname - argv[2]);
310 if (!ss) {
311 log_oom();
312 return EXIT_FAILURE;
313 }
314
315 sysname++;
316
317 if (!streq(ss, "backlight") && !streq(ss, "leds")) {
318 log_error("Not a backlight or LED device: '%s:%s'", ss, sysname);
319 return EXIT_FAILURE;
320 }
321
875c6e1b 322 errno = 0;
0f4ba83c 323 device = udev_device_new_from_subsystem_sysname(udev, ss, sysname);
3731acf1 324 if (!device) {
b3267152 325 if (errno > 0)
56f64d95 326 log_error_errno(errno, "Failed to get backlight or LED device '%s:%s': %m", ss, sysname);
1ca208fb 327 else
0f4ba83c 328 log_oom();
3731acf1 329
1ca208fb 330 return EXIT_FAILURE;
3731acf1
LP
331 }
332
3cadce7d
TB
333 /* If max_brightness is 0, then there is no actual backlight
334 * device. This happens on desktops with Asus mainboards
335 * that load the eeepc-wmi module.
336 */
337 max_brightness = get_max_brightness(device);
338 if (max_brightness == 0)
339 return EXIT_SUCCESS;
340
be3f52f4
LP
341 escaped_ss = cescape(ss);
342 if (!escaped_ss) {
343 log_oom();
344 return EXIT_FAILURE;
345 }
346
347 escaped_sysname = cescape(sysname);
348 if (!escaped_sysname) {
349 log_oom();
350 return EXIT_FAILURE;
351 }
352
353 path_id = udev_device_get_property_value(device, "ID_PATH");
354 if (path_id) {
355 escaped_path_id = cescape(path_id);
356 if (!escaped_path_id) {
357 log_oom();
358 return EXIT_FAILURE;
359 }
360
605405c6 361 saved = strjoin("/var/lib/systemd/backlight/", escaped_path_id, ":", escaped_ss, ":", escaped_sysname);
be3f52f4 362 } else
605405c6 363 saved = strjoin("/var/lib/systemd/backlight/", escaped_ss, ":", escaped_sysname);
be3f52f4 364
3731acf1 365 if (!saved) {
1ca208fb
ZJS
366 log_oom();
367 return EXIT_FAILURE;
3731acf1
LP
368 }
369
0f4ba83c
LP
370 /* If there are multiple conflicting backlight devices, then
371 * their probing at boot-time might happen in any order. This
372 * means the validity checking of the device then is not
373 * reliable, since it might not see other devices conflicting
73e231ab 374 * with a specific backlight. To deal with this, we will
0f4ba83c
LP
375 * actively delete backlight state files at shutdown (where
376 * device probing should be complete), so that the validity
377 * check at boot time doesn't have to be reliable. */
378
b76388e1 379 if (streq(argv[1], "load")) {
3731acf1 380 _cleanup_free_ char *value = NULL;
bca81be7 381 const char *clamp;
3731acf1 382
934ae16b 383 if (shall_restore_state() == 0)
b76388e1
MB
384 return EXIT_SUCCESS;
385
0f4ba83c
LP
386 if (!validate_device(udev, device))
387 return EXIT_SUCCESS;
388
3731acf1
LP
389 r = read_one_line_file(saved, &value);
390 if (r < 0) {
391
1ca208fb
ZJS
392 if (r == -ENOENT)
393 return EXIT_SUCCESS;
3731acf1 394
da927ba9 395 log_error_errno(r, "Failed to read %s: %m", saved);
1ca208fb 396 return EXIT_FAILURE;
3731acf1
LP
397 }
398
bca81be7
TM
399 clamp = udev_device_get_property_value(device, "ID_BACKLIGHT_CLAMP");
400 if (!clamp || parse_boolean(clamp) != 0) /* default to clamping */
401 clamp_brightness(device, &value, max_brightness);
7b909d74 402
3731acf1
LP
403 r = udev_device_set_sysattr_value(device, "brightness", value);
404 if (r < 0) {
c33b3297 405 log_error_errno(r, "Failed to write system 'brightness' attribute: %m");
1ca208fb 406 return EXIT_FAILURE;
3731acf1
LP
407 }
408
409 } else if (streq(argv[1], "save")) {
410 const char *value;
411
0f4ba83c
LP
412 if (!validate_device(udev, device)) {
413 unlink(saved);
414 return EXIT_SUCCESS;
415 }
416
3731acf1
LP
417 value = udev_device_get_sysattr_value(device, "brightness");
418 if (!value) {
938d2699 419 log_error("Failed to read system 'brightness' attribute");
1ca208fb 420 return EXIT_FAILURE;
3731acf1
LP
421 }
422
4c1fc3e4 423 r = write_string_file(saved, value, WRITE_STRING_FILE_CREATE);
3731acf1 424 if (r < 0) {
da927ba9 425 log_error_errno(r, "Failed to write %s: %m", saved);
1ca208fb 426 return EXIT_FAILURE;
3731acf1
LP
427 }
428
429 } else {
430 log_error("Unknown verb %s.", argv[1]);
1ca208fb 431 return EXIT_FAILURE;
3731acf1
LP
432 }
433
1ca208fb 434 return EXIT_SUCCESS;
3731acf1 435}