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