]> git.ipfire.org Git - thirdparty/systemd.git/blob - 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
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
22 #include "libudev.h"
23
24 #include "def.h"
25 #include "escape.h"
26 #include "fileio.h"
27 #include "mkdir.h"
28 #include "string-util.h"
29 #include "udev-util.h"
30 #include "util.h"
31
32 static 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
57 c += strspn(c, DIGITS);
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) {
71 unsigned long class = 0;
72
73 if (safe_atolu(value, &class) < 0) {
74 log_warning("Cannot parse PCI class %s of device %s:%s.",
75 value, subsystem, sysname);
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
90 static 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
103 static 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. */
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));
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. */
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));
197 return false;
198 }
199 }
200
201 return true;
202 }
203
204 static unsigned get_max_brightness(struct udev_device *device) {
205 int r;
206 const char *max_brightness_str;
207 unsigned max_brightness;
208
209 max_brightness_str = udev_device_get_sysattr_value(device, "max_brightness");
210 if (!max_brightness_str) {
211 log_warning("Failed to read 'max_brightness' attribute.");
212 return 0;
213 }
214
215 r = safe_atou(max_brightness_str, &max_brightness);
216 if (r < 0) {
217 log_warning_errno(r, "Failed to parse 'max_brightness' \"%s\": %m", max_brightness_str);
218 return 0;
219 }
220
221 if (max_brightness <= 0) {
222 log_warning("Maximum brightness is 0, ignoring device.");
223 return 0;
224 }
225
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
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. */
234 static void clamp_brightness(struct udev_device *device, char **value, unsigned max_brightness) {
235 int r;
236 unsigned brightness, new_brightness, min_brightness;
237 const char *subsystem;
238
239 r = safe_atou(*value, &brightness);
240 if (r < 0) {
241 log_warning_errno(r, "Failed to parse brightness \"%s\": %m", *value);
242 return;
243 }
244
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
251 new_brightness = CLAMP(brightness, min_brightness, max_brightness);
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
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
266 free(old_value);
267 }
268 }
269
270 int main(int argc, char *argv[]) {
271 _cleanup_udev_unref_ struct udev *udev = NULL;
272 _cleanup_udev_device_unref_ struct udev_device *device = NULL;
273 _cleanup_free_ char *saved = NULL, *ss = NULL, *escaped_ss = NULL, *escaped_sysname = NULL, *escaped_path_id = NULL;
274 const char *sysname, *path_id;
275 unsigned max_brightness;
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
289 r = mkdir_p("/var/lib/systemd/backlight", 0755);
290 if (r < 0) {
291 log_error_errno(r, "Failed to create backlight directory /var/lib/systemd/backlight: %m");
292 return EXIT_FAILURE;
293 }
294
295 udev = udev_new();
296 if (!udev) {
297 log_oom();
298 return EXIT_FAILURE;
299 }
300
301 sysname = strchr(argv[2], ':');
302 if (!sysname) {
303 log_error("Requires a subsystem and sysname pair specifying a backlight device.");
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
320 errno = 0;
321 device = udev_device_new_from_subsystem_sysname(udev, ss, sysname);
322 if (!device) {
323 if (errno != 0)
324 log_error_errno(errno, "Failed to get backlight or LED device '%s:%s': %m", ss, sysname);
325 else
326 log_oom();
327
328 return EXIT_FAILURE;
329 }
330
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
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
363 if (!saved) {
364 log_oom();
365 return EXIT_FAILURE;
366 }
367
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
372 * with a specific backlight. To deal with this, we will
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
377 if (streq(argv[1], "load")) {
378 _cleanup_free_ char *value = NULL;
379 const char *clamp;
380
381 if (!shall_restore_state())
382 return EXIT_SUCCESS;
383
384 if (!validate_device(udev, device))
385 return EXIT_SUCCESS;
386
387 r = read_one_line_file(saved, &value);
388 if (r < 0) {
389
390 if (r == -ENOENT)
391 return EXIT_SUCCESS;
392
393 log_error_errno(r, "Failed to read %s: %m", saved);
394 return EXIT_FAILURE;
395 }
396
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);
400
401 r = udev_device_set_sysattr_value(device, "brightness", value);
402 if (r < 0) {
403 log_error_errno(r, "Failed to write system 'brightness' attribute: %m");
404 return EXIT_FAILURE;
405 }
406
407 } else if (streq(argv[1], "save")) {
408 const char *value;
409
410 if (!validate_device(udev, device)) {
411 unlink(saved);
412 return EXIT_SUCCESS;
413 }
414
415 value = udev_device_get_sysattr_value(device, "brightness");
416 if (!value) {
417 log_error("Failed to read system 'brightness' attribute");
418 return EXIT_FAILURE;
419 }
420
421 r = write_string_file(saved, value, WRITE_STRING_FILE_CREATE);
422 if (r < 0) {
423 log_error_errno(r, "Failed to write %s: %m", saved);
424 return EXIT_FAILURE;
425 }
426
427 } else {
428 log_error("Unknown verb %s.", argv[1]);
429 return EXIT_FAILURE;
430 }
431
432 return EXIT_SUCCESS;
433 }