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