Branch data Line data Source code
1 : : /* SPDX-License-Identifier: LGPL-2.1+ */
2 : :
3 : : #include <sys/stat.h>
4 : : #include <sys/types.h>
5 : : #include <unistd.h>
6 : :
7 : : #include "sd-device.h"
8 : :
9 : : #include "alloc-util.h"
10 : : #include "device-util.h"
11 : : #include "escape.h"
12 : : #include "fileio.h"
13 : : #include "main-func.h"
14 : : #include "mkdir.h"
15 : : #include "parse-util.h"
16 : : #include "proc-cmdline.h"
17 : : #include "string-util.h"
18 : : #include "strv.h"
19 : : #include "util.h"
20 : :
21 : 0 : static int find_pci_or_platform_parent(sd_device *device, sd_device **ret) {
22 : : const char *subsystem, *sysname, *value;
23 : : sd_device *parent;
24 : : int r;
25 : :
26 [ # # ]: 0 : assert(device);
27 [ # # ]: 0 : assert(ret);
28 : :
29 : 0 : r = sd_device_get_parent(device, &parent);
30 [ # # ]: 0 : if (r < 0)
31 : 0 : return r;
32 : :
33 : 0 : r = sd_device_get_subsystem(parent, &subsystem);
34 [ # # ]: 0 : if (r < 0)
35 : 0 : return r;
36 : :
37 : 0 : r = sd_device_get_sysname(parent, &sysname);
38 [ # # ]: 0 : if (r < 0)
39 : 0 : return r;
40 : :
41 [ # # ]: 0 : if (streq(subsystem, "drm")) {
42 : : const char *c;
43 : :
44 : 0 : c = startswith(sysname, "card");
45 [ # # ]: 0 : if (!c)
46 : 0 : return -ENODATA;
47 : :
48 : 0 : c += strspn(c, DIGITS);
49 [ # # ]: 0 : if (*c == '-') {
50 : : /* A connector DRM device, let's ignore all but LVDS and eDP! */
51 [ # # # # : 0 : if (!STARTSWITH_SET(c, "-LVDS-", "-Embedded DisplayPort-"))
# # # # ]
52 : 0 : return -EOPNOTSUPP;
53 : : }
54 : :
55 [ # # # # ]: 0 : } else if (streq(subsystem, "pci") &&
56 : 0 : sd_device_get_sysattr_value(parent, "class", &value) >= 0) {
57 : 0 : unsigned long class = 0;
58 : :
59 : 0 : r = safe_atolu(value, &class);
60 [ # # ]: 0 : if (r < 0)
61 [ # # ]: 0 : return log_warning_errno(r, "Cannot parse PCI class '%s' of device %s:%s: %m",
62 : : value, subsystem, sysname);
63 : :
64 : : /* Graphics card */
65 [ # # ]: 0 : if (class == 0x30000) {
66 : 0 : *ret = parent;
67 : 0 : return 0;
68 : : }
69 : :
70 [ # # ]: 0 : } else if (streq(subsystem, "platform")) {
71 : 0 : *ret = parent;
72 : 0 : return 0;
73 : : }
74 : :
75 : 0 : return find_pci_or_platform_parent(parent, ret);
76 : : }
77 : :
78 : 0 : static int same_device(sd_device *a, sd_device *b) {
79 : : const char *a_val, *b_val;
80 : : int r;
81 : :
82 [ # # ]: 0 : assert(a);
83 [ # # ]: 0 : assert(b);
84 : :
85 : 0 : r = sd_device_get_subsystem(a, &a_val);
86 [ # # ]: 0 : if (r < 0)
87 : 0 : return r;
88 : :
89 : 0 : r = sd_device_get_subsystem(b, &b_val);
90 [ # # ]: 0 : if (r < 0)
91 : 0 : return r;
92 : :
93 [ # # ]: 0 : if (!streq(a_val, b_val))
94 : 0 : return false;
95 : :
96 : 0 : r = sd_device_get_sysname(a, &a_val);
97 [ # # ]: 0 : if (r < 0)
98 : 0 : return r;
99 : :
100 : 0 : r = sd_device_get_sysname(b, &b_val);
101 [ # # ]: 0 : if (r < 0)
102 : 0 : return r;
103 : :
104 : 0 : return streq(a_val, b_val);
105 : : }
106 : :
107 : 0 : static int validate_device(sd_device *device) {
108 : 0 : _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *enumerate = NULL;
109 : : const char *v, *subsystem;
110 : : sd_device *parent, *other;
111 : : int r;
112 : :
113 [ # # ]: 0 : 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 : 0 : r = sd_device_get_subsystem(device, &subsystem);
130 [ # # ]: 0 : if (r < 0)
131 : 0 : return r;
132 [ # # ]: 0 : if (!streq(subsystem, "backlight"))
133 : 0 : return true;
134 : :
135 : 0 : r = sd_device_get_sysattr_value(device, "type", &v);
136 [ # # ]: 0 : if (r < 0)
137 : 0 : return r;
138 [ # # ]: 0 : if (!streq(v, "raw"))
139 : 0 : return true;
140 : :
141 : 0 : r = find_pci_or_platform_parent(device, &parent);
142 [ # # ]: 0 : if (r < 0)
143 : 0 : return r;
144 : :
145 : 0 : r = sd_device_get_subsystem(parent, &subsystem);
146 [ # # ]: 0 : if (r < 0)
147 : 0 : return r;
148 : :
149 : 0 : r = sd_device_enumerator_new(&enumerate);
150 [ # # ]: 0 : if (r < 0)
151 : 0 : return r;
152 : :
153 : 0 : r = sd_device_enumerator_allow_uninitialized(enumerate);
154 [ # # ]: 0 : if (r < 0)
155 : 0 : return r;
156 : :
157 : 0 : r = sd_device_enumerator_add_match_subsystem(enumerate, "backlight", true);
158 [ # # ]: 0 : if (r < 0)
159 : 0 : return r;
160 : :
161 [ # # ]: 0 : FOREACH_DEVICE(enumerate, other) {
162 : : const char *other_subsystem;
163 : : sd_device *other_parent;
164 : :
165 [ # # ]: 0 : if (same_device(device, other) > 0)
166 : 0 : continue;
167 : :
168 [ # # ]: 0 : if (sd_device_get_sysattr_value(other, "type", &v) < 0 ||
169 [ # # ]: 0 : !STR_IN_SET(v, "platform", "firmware"))
170 : 0 : 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 ours. */
175 [ # # ]: 0 : if (find_pci_or_platform_parent(other, &other_parent) < 0)
176 : 0 : continue;
177 : :
178 [ # # ]: 0 : if (same_device(parent, other_parent)) {
179 : 0 : const char *device_sysname = NULL, *other_sysname = NULL;
180 : :
181 : : /* Both have the same PCI parent, that means we are out. */
182 : :
183 : 0 : (void) sd_device_get_sysname(device, &device_sysname);
184 : 0 : (void) sd_device_get_sysname(other, &other_sysname);
185 : :
186 [ # # ]: 0 : log_debug("Skipping backlight device %s, since device %s is on same PCI device and takes precedence.",
187 : : device_sysname, other_sysname);
188 : 0 : return false;
189 : : }
190 : :
191 [ # # ]: 0 : if (sd_device_get_subsystem(other_parent, &other_subsystem) < 0)
192 : 0 : continue;
193 : :
194 [ # # # # ]: 0 : if (streq(other_subsystem, "platform") && streq(subsystem, "pci")) {
195 : 0 : const char *device_sysname = NULL, *other_sysname = NULL;
196 : :
197 : : /* The other is connected to the platform bus and we are a PCI device, that also means we are out. */
198 : :
199 : 0 : (void) sd_device_get_sysname(device, &device_sysname);
200 : 0 : (void) sd_device_get_sysname(other, &other_sysname);
201 : :
202 [ # # ]: 0 : log_debug("Skipping backlight device %s, since device %s is a platform device and takes precedence.",
203 : : device_sysname, other_sysname);
204 : 0 : return false;
205 : : }
206 : : }
207 : :
208 : 0 : return true;
209 : : }
210 : :
211 : 0 : static int get_max_brightness(sd_device *device, unsigned *ret) {
212 : : const char *max_brightness_str;
213 : : unsigned max_brightness;
214 : : int r;
215 : :
216 [ # # ]: 0 : assert(device);
217 [ # # ]: 0 : assert(ret);
218 : :
219 : 0 : r = sd_device_get_sysattr_value(device, "max_brightness", &max_brightness_str);
220 [ # # ]: 0 : if (r < 0)
221 [ # # # # : 0 : return log_device_warning_errno(device, r, "Failed to read 'max_brightness' attribute: %m");
# # ]
222 : :
223 : 0 : r = safe_atou(max_brightness_str, &max_brightness);
224 [ # # ]: 0 : if (r < 0)
225 [ # # # # : 0 : return log_device_warning_errno(device, r, "Failed to parse 'max_brightness' \"%s\": %m", max_brightness_str);
# # ]
226 : :
227 [ # # ]: 0 : if (max_brightness <= 0) {
228 [ # # # # : 0 : log_device_warning(device, "Maximum brightness is 0, ignoring device.");
# # ]
229 : 0 : return -EINVAL;
230 : : }
231 : :
232 : 0 : *ret = max_brightness;
233 : 0 : return 0;
234 : : }
235 : :
236 : : /* Some systems turn the backlight all the way off at the lowest levels.
237 : : * clamp_brightness clamps the saved brightness to at least 1 or 5% of
238 : : * max_brightness in case of 'backlight' subsystem. This avoids preserving
239 : : * an unreadably dim screen, which would otherwise force the user to
240 : : * disable state restoration. */
241 : 0 : static int clamp_brightness(sd_device *device, char **value, unsigned max_brightness) {
242 : : unsigned brightness, new_brightness, min_brightness;
243 : : const char *subsystem;
244 : : int r;
245 : :
246 [ # # ]: 0 : assert(value);
247 [ # # ]: 0 : assert(*value);
248 : :
249 : 0 : r = safe_atou(*value, &brightness);
250 [ # # ]: 0 : if (r < 0)
251 [ # # # # : 0 : return log_device_warning_errno(device, r, "Failed to parse brightness \"%s\": %m", *value);
# # ]
252 : :
253 : 0 : r = sd_device_get_subsystem(device, &subsystem);
254 [ # # ]: 0 : if (r < 0)
255 [ # # # # : 0 : return log_device_warning_errno(device, r, "Failed to get device subsystem: %m");
# # ]
256 : :
257 [ # # ]: 0 : if (streq(subsystem, "backlight"))
258 : 0 : min_brightness = MAX(1U, max_brightness/20);
259 : : else
260 : 0 : min_brightness = 0;
261 : :
262 [ # # ]: 0 : new_brightness = CLAMP(brightness, min_brightness, max_brightness);
263 [ # # ]: 0 : if (new_brightness != brightness) {
264 : : char *new_value;
265 : :
266 : 0 : r = asprintf(&new_value, "%u", new_brightness);
267 [ # # ]: 0 : if (r < 0)
268 : 0 : return log_oom();
269 : :
270 [ # # # # : 0 : log_device_info(device, "Saved brightness %s %s to %s.", *value,
# # # # ]
271 : : new_brightness > brightness ?
272 : : "too low; increasing" : "too high; decreasing",
273 : : new_value);
274 : :
275 : 0 : free_and_replace(*value, new_value);
276 : : }
277 : :
278 : 0 : return 0;
279 : : }
280 : :
281 : 0 : static bool shall_clamp(sd_device *d) {
282 : : const char *s;
283 : : int r;
284 : :
285 [ # # ]: 0 : assert(d);
286 : :
287 : 0 : r = sd_device_get_property_value(d, "ID_BACKLIGHT_CLAMP", &s);
288 [ # # ]: 0 : if (r < 0) {
289 [ # # # # : 0 : log_device_debug_errno(d, r, "Failed to get ID_BACKLIGHT_CLAMP property, ignoring: %m");
# # ]
290 : 0 : return true;
291 : : }
292 : :
293 : 0 : r = parse_boolean(s);
294 [ # # ]: 0 : if (r < 0) {
295 [ # # # # : 0 : log_device_debug_errno(d, r, "Failed to parse ID_BACKLIGHT_CLAMP property, ignoring: %m");
# # ]
296 : 0 : return true;
297 : : }
298 : :
299 : 0 : return r;
300 : : }
301 : :
302 : 0 : static int run(int argc, char *argv[]) {
303 : 0 : _cleanup_(sd_device_unrefp) sd_device *device = NULL;
304 : 0 : _cleanup_free_ char *escaped_ss = NULL, *escaped_sysname = NULL, *escaped_path_id = NULL;
305 : : const char *sysname, *path_id, *ss, *saved;
306 : : unsigned max_brightness;
307 : : int r;
308 : :
309 [ # # ]: 0 : if (argc != 3) {
310 [ # # ]: 0 : log_error("This program requires two arguments.");
311 : 0 : return -EINVAL;
312 : : }
313 : :
314 : 0 : log_setup_service();
315 : :
316 : 0 : umask(0022);
317 : :
318 : 0 : r = mkdir_p("/var/lib/systemd/backlight", 0755);
319 [ # # ]: 0 : if (r < 0)
320 [ # # ]: 0 : return log_error_errno(r, "Failed to create backlight directory /var/lib/systemd/backlight: %m");
321 : :
322 : 0 : sysname = strchr(argv[2], ':');
323 [ # # ]: 0 : if (!sysname) {
324 [ # # ]: 0 : log_error("Requires a subsystem and sysname pair specifying a backlight device.");
325 : 0 : return -EINVAL;
326 : : }
327 : :
328 : 0 : ss = strndupa(argv[2], sysname - argv[2]);
329 : :
330 : 0 : sysname++;
331 : :
332 [ # # ]: 0 : if (!STR_IN_SET(ss, "backlight", "leds")) {
333 [ # # ]: 0 : log_error("Not a backlight or LED device: '%s:%s'", ss, sysname);
334 : 0 : return -EINVAL;
335 : : }
336 : :
337 : 0 : r = sd_device_new_from_subsystem_sysname(&device, ss, sysname);
338 [ # # ]: 0 : if (r < 0)
339 [ # # ]: 0 : return log_error_errno(r, "Failed to get backlight or LED device '%s:%s': %m", ss, sysname);
340 : :
341 : : /* If max_brightness is 0, then there is no actual backlight
342 : : * device. This happens on desktops with Asus mainboards
343 : : * that load the eeepc-wmi module. */
344 [ # # ]: 0 : if (get_max_brightness(device, &max_brightness) < 0)
345 : 0 : return 0;
346 : :
347 : 0 : escaped_ss = cescape(ss);
348 [ # # ]: 0 : if (!escaped_ss)
349 : 0 : return log_oom();
350 : :
351 : 0 : escaped_sysname = cescape(sysname);
352 [ # # ]: 0 : if (!escaped_sysname)
353 : 0 : return log_oom();
354 : :
355 [ # # ]: 0 : if (sd_device_get_property_value(device, "ID_PATH", &path_id) >= 0) {
356 : 0 : escaped_path_id = cescape(path_id);
357 [ # # ]: 0 : if (!escaped_path_id)
358 : 0 : return log_oom();
359 : :
360 [ # # # # : 0 : saved = strjoina("/var/lib/systemd/backlight/", escaped_path_id, ":", escaped_ss, ":", escaped_sysname);
# # # # #
# # # ]
361 : : } else
362 [ # # # # : 0 : saved = strjoina("/var/lib/systemd/backlight/", escaped_ss, ":", escaped_sysname);
# # # # #
# # # ]
363 : :
364 : : /* If there are multiple conflicting backlight devices, then
365 : : * their probing at boot-time might happen in any order. This
366 : : * means the validity checking of the device then is not
367 : : * reliable, since it might not see other devices conflicting
368 : : * with a specific backlight. To deal with this, we will
369 : : * actively delete backlight state files at shutdown (where
370 : : * device probing should be complete), so that the validity
371 : : * check at boot time doesn't have to be reliable. */
372 : :
373 [ # # ]: 0 : if (streq(argv[1], "load")) {
374 [ # # ]: 0 : _cleanup_free_ char *value = NULL;
375 : : bool clamp;
376 : :
377 [ # # ]: 0 : if (shall_restore_state() == 0)
378 : 0 : return 0;
379 : :
380 [ # # ]: 0 : if (validate_device(device) == 0)
381 : 0 : return 0;
382 : :
383 : 0 : clamp = shall_clamp(device);
384 : :
385 : 0 : r = read_one_line_file(saved, &value);
386 [ # # # # ]: 0 : if (IN_SET(r, -ENOENT, 0)) {
387 : : const char *curval;
388 : :
389 : : /* Fallback to clamping current brightness or exit early if
390 : : * clamping is not supported/enabled. */
391 [ # # ]: 0 : if (!clamp)
392 : 0 : return 0;
393 : :
394 : 0 : r = sd_device_get_sysattr_value(device, "brightness", &curval);
395 [ # # ]: 0 : if (r < 0)
396 [ # # # # : 0 : return log_device_warning_errno(device, r, "Failed to read 'brightness' attribute: %m");
# # ]
397 : :
398 : 0 : value = strdup(curval);
399 [ # # ]: 0 : if (!value)
400 : 0 : return log_oom();
401 [ # # ]: 0 : } else if (r < 0)
402 [ # # ]: 0 : return log_error_errno(r, "Failed to read %s: %m", saved);
403 : :
404 [ # # ]: 0 : if (clamp)
405 : 0 : (void) clamp_brightness(device, &value, max_brightness);
406 : :
407 : 0 : r = sd_device_set_sysattr_value(device, "brightness", value);
408 [ # # ]: 0 : if (r < 0)
409 [ # # # # : 0 : return log_device_error_errno(device, r, "Failed to write system 'brightness' attribute: %m");
# # ]
410 : :
411 [ # # ]: 0 : } else if (streq(argv[1], "save")) {
412 : : const char *value;
413 : :
414 [ # # ]: 0 : if (validate_device(device) == 0) {
415 : 0 : (void) unlink(saved);
416 : 0 : return 0;
417 : : }
418 : :
419 : 0 : r = sd_device_get_sysattr_value(device, "brightness", &value);
420 [ # # ]: 0 : if (r < 0)
421 [ # # # # : 0 : return log_device_error_errno(device, r, "Failed to read system 'brightness' attribute: %m");
# # ]
422 : :
423 : 0 : r = write_string_file(saved, value, WRITE_STRING_FILE_CREATE);
424 [ # # ]: 0 : if (r < 0)
425 [ # # # # : 0 : return log_device_error_errno(device, r, "Failed to write %s: %m", saved);
# # ]
426 : :
427 : : } else {
428 [ # # ]: 0 : log_error("Unknown verb %s.", argv[1]);
429 : 0 : return -EINVAL;
430 : : }
431 : :
432 : 0 : return 0;
433 : : }
434 : :
435 : 0 : DEFINE_MAIN_FUNCTION(run);
|