]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/backlight/backlight.c
kerne-command-line: introduce option 'systemd.restore_state'
[thirdparty/systemd.git] / src / backlight / backlight.c
CommitLineData
3731acf1
LP
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
3731acf1
LP
22#include "util.h"
23#include "mkdir.h"
24#include "fileio.h"
1ca208fb
ZJS
25#include "libudev.h"
26#include "udev-util.h"
295edddf 27#include "util.h"
3731acf1 28
0f4ba83c
LP
29static 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
86static 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
99static 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
3731acf1 196int main(int argc, char *argv[]) {
1ca208fb
ZJS
197 _cleanup_udev_unref_ struct udev *udev = NULL;
198 _cleanup_udev_device_unref_ struct udev_device *device = NULL;
be3f52f4
LP
199 _cleanup_free_ char *saved = NULL, *ss = NULL, *escaped_ss = NULL, *escaped_sysname = NULL, *escaped_path_id = NULL;
200 const char *sysname, *path_id;
3731acf1
LP
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
ef5bfcf6 214 r = mkdir_p("/var/lib/systemd/backlight", 0755);
3731acf1
LP
215 if (r < 0) {
216 log_error("Failed to create backlight directory: %s", strerror(-r));
1ca208fb 217 return EXIT_FAILURE;
3731acf1
LP
218 }
219
220 udev = udev_new();
221 if (!udev) {
1ca208fb
ZJS
222 log_oom();
223 return EXIT_FAILURE;
3731acf1
LP
224 }
225
0f4ba83c
LP
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
875c6e1b 245 errno = 0;
0f4ba83c 246 device = udev_device_new_from_subsystem_sysname(udev, ss, sysname);
3731acf1 247 if (!device) {
1ca208fb 248 if (errno != 0)
0f4ba83c 249 log_error("Failed to get backlight or LED device '%s:%s': %m", ss, sysname);
1ca208fb 250 else
0f4ba83c 251 log_oom();
3731acf1 252
1ca208fb 253 return EXIT_FAILURE;
3731acf1
LP
254 }
255
be3f52f4
LP
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
3731acf1 280 if (!saved) {
1ca208fb
ZJS
281 log_oom();
282 return EXIT_FAILURE;
3731acf1
LP
283 }
284
0f4ba83c
LP
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
295edddf 294 if (streq(argv[1], "load") && restore_state()) {
3731acf1
LP
295 _cleanup_free_ char *value = NULL;
296
0f4ba83c
LP
297 if (!validate_device(udev, device))
298 return EXIT_SUCCESS;
299
3731acf1
LP
300 r = read_one_line_file(saved, &value);
301 if (r < 0) {
302
1ca208fb
ZJS
303 if (r == -ENOENT)
304 return EXIT_SUCCESS;
3731acf1
LP
305
306 log_error("Failed to read %s: %s", saved, strerror(-r));
1ca208fb 307 return EXIT_FAILURE;
3731acf1
LP
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));
1ca208fb 313 return EXIT_FAILURE;
3731acf1
LP
314 }
315
316 } else if (streq(argv[1], "save")) {
317 const char *value;
318
0f4ba83c
LP
319 if (!validate_device(udev, device)) {
320 unlink(saved);
321 return EXIT_SUCCESS;
322 }
323
3731acf1
LP
324 value = udev_device_get_sysattr_value(device, "brightness");
325 if (!value) {
326 log_error("Failed to read system attribute: %s", strerror(-r));
1ca208fb 327 return EXIT_FAILURE;
3731acf1
LP
328 }
329
330 r = write_string_file(saved, value);
331 if (r < 0) {
332 log_error("Failed to write %s: %s", saved, strerror(-r));
1ca208fb 333 return EXIT_FAILURE;
3731acf1
LP
334 }
335
336 } else {
337 log_error("Unknown verb %s.", argv[1]);
1ca208fb 338 return EXIT_FAILURE;
3731acf1
LP
339 }
340
1ca208fb 341 return EXIT_SUCCESS;
3731acf1 342}