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