]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/backlight/backlight.c
rfkill: use ID_PATH as identifier for rfkill state files
[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"
3731acf1 27
0f4ba83c
LP
28static 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
85static 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
98static 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
3731acf1 195int main(int argc, char *argv[]) {
1ca208fb
ZJS
196 _cleanup_udev_unref_ struct udev *udev = NULL;
197 _cleanup_udev_device_unref_ struct udev_device *device = NULL;
0f4ba83c
LP
198 _cleanup_free_ char *saved = NULL, *ss = NULL;
199 const char *sysname;
3731acf1
LP
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
ef5bfcf6 213 r = mkdir_p("/var/lib/systemd/backlight", 0755);
3731acf1
LP
214 if (r < 0) {
215 log_error("Failed to create backlight directory: %s", strerror(-r));
1ca208fb 216 return EXIT_FAILURE;
3731acf1
LP
217 }
218
219 udev = udev_new();
220 if (!udev) {
1ca208fb
ZJS
221 log_oom();
222 return EXIT_FAILURE;
3731acf1
LP
223 }
224
0f4ba83c
LP
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
875c6e1b 244 errno = 0;
0f4ba83c 245 device = udev_device_new_from_subsystem_sysname(udev, ss, sysname);
3731acf1 246 if (!device) {
1ca208fb 247 if (errno != 0)
0f4ba83c 248 log_error("Failed to get backlight or LED device '%s:%s': %m", ss, sysname);
1ca208fb 249 else
0f4ba83c 250 log_oom();
3731acf1 251
1ca208fb 252 return EXIT_FAILURE;
3731acf1
LP
253 }
254
0f4ba83c 255 saved = strjoin("/var/lib/systemd/backlight/", ss, ":", sysname, NULL);
3731acf1 256 if (!saved) {
1ca208fb
ZJS
257 log_oom();
258 return EXIT_FAILURE;
3731acf1
LP
259 }
260
0f4ba83c
LP
261 /* If there are multiple conflicting backlight devices, then
262 * their probing at boot-time might happen in any order. This
263 * means the validity checking of the device then is not
264 * reliable, since it might not see other devices conflicting
265 * with a specific backlight. To deal with this we will
266 * actively delete backlight state files at shutdown (where
267 * device probing should be complete), so that the validity
268 * check at boot time doesn't have to be reliable. */
269
3731acf1
LP
270 if (streq(argv[1], "load")) {
271 _cleanup_free_ char *value = NULL;
272
0f4ba83c
LP
273 if (!validate_device(udev, device))
274 return EXIT_SUCCESS;
275
3731acf1
LP
276 r = read_one_line_file(saved, &value);
277 if (r < 0) {
278
1ca208fb
ZJS
279 if (r == -ENOENT)
280 return EXIT_SUCCESS;
3731acf1
LP
281
282 log_error("Failed to read %s: %s", saved, strerror(-r));
1ca208fb 283 return EXIT_FAILURE;
3731acf1
LP
284 }
285
286 r = udev_device_set_sysattr_value(device, "brightness", value);
287 if (r < 0) {
288 log_error("Failed to write system attribute: %s", strerror(-r));
1ca208fb 289 return EXIT_FAILURE;
3731acf1
LP
290 }
291
292 } else if (streq(argv[1], "save")) {
293 const char *value;
294
0f4ba83c
LP
295 if (!validate_device(udev, device)) {
296 unlink(saved);
297 return EXIT_SUCCESS;
298 }
299
3731acf1
LP
300 value = udev_device_get_sysattr_value(device, "brightness");
301 if (!value) {
302 log_error("Failed to read system attribute: %s", strerror(-r));
1ca208fb 303 return EXIT_FAILURE;
3731acf1
LP
304 }
305
306 r = write_string_file(saved, value);
307 if (r < 0) {
308 log_error("Failed to write %s: %s", saved, strerror(-r));
1ca208fb 309 return EXIT_FAILURE;
3731acf1
LP
310 }
311
312 } else {
313 log_error("Unknown verb %s.", argv[1]);
1ca208fb 314 return EXIT_FAILURE;
3731acf1
LP
315 }
316
1ca208fb 317 return EXIT_SUCCESS;
3731acf1 318}