chiark / gitweb /
TODO: add some fsck items
[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;
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 int main(int argc, char *argv[]) {
196         _cleanup_udev_unref_ struct udev *udev = NULL;
197         _cleanup_udev_device_unref_ struct udev_device *device = NULL;
198         _cleanup_free_ char *saved = NULL, *ss = NULL, *escaped_ss = NULL, *escaped_sysname = NULL, *escaped_path_id = NULL;
199         const char *sysname, *path_id;
200         int r;
201
202         if (argc != 3) {
203                 log_error("This program requires two arguments.");
204                 return EXIT_FAILURE;
205         }
206
207         log_set_target(LOG_TARGET_AUTO);
208         log_parse_environment();
209         log_open();
210
211         umask(0022);
212
213         r = mkdir_p("/var/lib/systemd/backlight", 0755);
214         if (r < 0) {
215                 log_error("Failed to create backlight directory: %s", strerror(-r));
216                 return EXIT_FAILURE;
217         }
218
219         udev = udev_new();
220         if (!udev) {
221                 log_oom();
222                 return EXIT_FAILURE;
223         }
224
225         sysname = strchr(argv[2], ':');
226         if (!sysname) {
227                 log_error("Requires pair of subsystem and sysname for specifying backlight device.");
228                 return EXIT_FAILURE;
229         }
230
231         ss = strndup(argv[2], sysname - argv[2]);
232         if (!ss) {
233                 log_oom();
234                 return EXIT_FAILURE;
235         }
236
237         sysname++;
238
239         if (!streq(ss, "backlight") && !streq(ss, "leds")) {
240                 log_error("Not a backlight or LED device: '%s:%s'", ss, sysname);
241                 return EXIT_FAILURE;
242         }
243
244         errno = 0;
245         device = udev_device_new_from_subsystem_sysname(udev, ss, sysname);
246         if (!device) {
247                 if (errno != 0)
248                         log_error("Failed to get backlight or LED device '%s:%s': %m", ss, sysname);
249                 else
250                         log_oom();
251
252                 return EXIT_FAILURE;
253         }
254
255         escaped_ss = cescape(ss);
256         if (!escaped_ss) {
257                 log_oom();
258                 return EXIT_FAILURE;
259         }
260
261         escaped_sysname = cescape(sysname);
262         if (!escaped_sysname) {
263                 log_oom();
264                 return EXIT_FAILURE;
265         }
266
267         path_id = udev_device_get_property_value(device, "ID_PATH");
268         if (path_id) {
269                 escaped_path_id = cescape(path_id);
270                 if (!escaped_path_id) {
271                         log_oom();
272                         return EXIT_FAILURE;
273                 }
274
275                 saved = strjoin("/var/lib/systemd/backlight/", escaped_path_id, ":", escaped_ss, ":", escaped_sysname, NULL);
276         } else
277                 saved = strjoin("/var/lib/systemd/backlight/", escaped_ss, ":", escaped_sysname, NULL);
278
279         if (!saved) {
280                 log_oom();
281                 return EXIT_FAILURE;
282         }
283
284         /* If there are multiple conflicting backlight devices, then
285          * their probing at boot-time might happen in any order. This
286          * means the validity checking of the device then is not
287          * reliable, since it might not see other devices conflicting
288          * with a specific backlight. To deal with this we will
289          * actively delete backlight state files at shutdown (where
290          * device probing should be complete), so that the validity
291          * check at boot time doesn't have to be reliable. */
292
293         if (streq(argv[1], "load")) {
294                 _cleanup_free_ char *value = NULL;
295
296                 if (!validate_device(udev, device))
297                         return EXIT_SUCCESS;
298
299                 r = read_one_line_file(saved, &value);
300                 if (r < 0) {
301
302                         if (r == -ENOENT)
303                                 return EXIT_SUCCESS;
304
305                         log_error("Failed to read %s: %s", saved, strerror(-r));
306                         return EXIT_FAILURE;
307                 }
308
309                 r = udev_device_set_sysattr_value(device, "brightness", value);
310                 if (r < 0) {
311                         log_error("Failed to write system attribute: %s", strerror(-r));
312                         return EXIT_FAILURE;
313                 }
314
315         } else if (streq(argv[1], "save")) {
316                 const char *value;
317
318                 if (!validate_device(udev, device)) {
319                         unlink(saved);
320                         return EXIT_SUCCESS;
321                 }
322
323                 value = udev_device_get_sysattr_value(device, "brightness");
324                 if (!value) {
325                         log_error("Failed to read system attribute: %s", strerror(-r));
326                         return EXIT_FAILURE;
327                 }
328
329                 r = write_string_file(saved, value);
330                 if (r < 0) {
331                         log_error("Failed to write %s: %s", saved, strerror(-r));
332                         return EXIT_FAILURE;
333                 }
334
335         } else {
336                 log_error("Unknown verb %s.", argv[1]);
337                 return EXIT_FAILURE;
338         }
339
340         return EXIT_SUCCESS;
341 }