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