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