backlight.c revision 42542f5f
1/***************************************************************************
2
3 Copyright 2014 Intel Corporation.  All Rights Reserved.
4 Copyright 2014 Red Hat, Inc.
5
6 Permission is hereby granted, free of charge, to any person obtaining a
7 copy of this software and associated documentation files (the
8 "Software"), to deal in the Software without restriction, including
9 without limitation the rights to use, copy, modify, merge, publish,
10 distribute, sub license, and/or sell copies of the Software, and to
11 permit persons to whom the Software is furnished to do so, subject to
12 the following conditions:
13
14 The above copyright notice and this permission notice (including the
15 next paragraph) shall be included in all copies or substantial portions
16 of the Software.
17
18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
19 OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
21 IN NO EVENT SHALL INTEL, AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,
22 DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
23 OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
24 THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
26 **************************************************************************/
27
28#ifdef HAVE_CONFIG_H
29#include "config.h"
30#endif
31
32#include <sys/types.h>
33#include <sys/wait.h>
34#include <sys/stat.h>
35#include <sys/ioctl.h>
36
37#include <stdio.h>
38#include <stdlib.h>
39#include <string.h>
40#include <ctype.h>
41#include <limits.h>
42#include <fcntl.h>
43#include <unistd.h>
44#include <dirent.h>
45
46#include <xorg-server.h>
47#include <xf86.h>
48#include <pciaccess.h>
49
50#include "backlight.h"
51#include "fd.h"
52
53#define BACKLIGHT_CLASS "/sys/class/backlight"
54
55/* Enough for 10 digits of backlight + '\n' + '\0' */
56#define BACKLIGHT_VALUE_LEN 12
57
58#ifndef ARRAY_SIZE
59#define ARRAY_SIZE(A) (sizeof(A)/sizeof(A[0]))
60#endif
61
62/*
63 * Unfortunately this is not as simple as I would like it to be. If selinux is
64 * dropping dbus messages pkexec may block *forever*.
65 *
66 * Backgrounding pkexec by doing System("pkexec ...&") does not work because
67 * that detaches pkexec from its parent at which point its security checks
68 * fail and it refuses to execute the helper.
69 *
70 * So we're left with spawning a helper child which gets levels to set written
71 * to it through a pipe. This turns the blocking forever problem from a hung
72 * machine problem into a simple backlight control not working problem.
73 *
74 * If only things were as simple as on OpenBSD! :)
75 */
76
77void backlight_init(struct backlight *b)
78{
79	b->type = BL_NONE;
80	b->iface = NULL;
81	b->fd = -1;
82	b->pid = -1;
83	b->max = -1;
84}
85
86#ifdef __OpenBSD__
87
88#include <dev/wscons/wsconsio.h>
89#include <xf86Priv.h>
90
91int backlight_set(struct backlight *b, int level)
92{
93	struct wsdisplay_param param;
94
95	if (b->iface == NULL)
96		return -1;
97
98	if ((unsigned)level > b->max)
99		level = b->max;
100
101	memset(&param, 0, sizeof(param));
102	param.param = WSDISPLAYIO_PARAM_BRIGHTNESS;
103	param.curval = level;
104
105	return ioctl(xf86Info.consoleFd, WSDISPLAYIO_SETPARAM, &param);
106}
107
108int backlight_get(struct backlight *b)
109{
110	struct wsdisplay_param param;
111
112	if (b->iface == NULL)
113		return -1;
114
115	memset(&param, 0, sizeof(param));
116	param.param = WSDISPLAYIO_PARAM_BRIGHTNESS;
117
118	if (ioctl(xf86Info.consoleFd, WSDISPLAYIO_GETPARAM, &param))
119		return -1;
120
121	return param.curval;
122}
123
124int backlight_open(struct backlight *b, char *iface)
125{
126	struct wsdisplay_param param;
127
128	if (iface != NULL)
129		return -1;
130
131	memset(&param, 0, sizeof(param));
132	param.param = WSDISPLAYIO_PARAM_BRIGHTNESS;
133
134	if (ioctl(xf86Info.consoleFd, WSDISPLAYIO_GETPARAM, &param) == -1)
135		return -1;
136
137	b->iface = strdup("wscons");
138	if (b->iface == NULL)
139		return -1;
140
141	b->max = param.max;
142	b->fd = -1;
143	b->type = BL_PLATFORM;
144
145	return param.curval;
146}
147
148enum backlight_type backlight_exists(const char *iface)
149{
150	if (iface != NULL)
151		return BL_NONE;
152
153	return BL_PLATFORM;
154}
155
156#else
157
158static int
159is_sysfs_fd(int fd)
160{
161	struct stat st;
162	return fstat(fd, &st) == 0 && major(st.st_dev) == 0;
163}
164
165static int
166__backlight_open(const char *iface, const char *file, int mode)
167{
168	char buf[1024];
169	int fd;
170
171	snprintf(buf, sizeof(buf), BACKLIGHT_CLASS "/%s/%s", iface, file);
172	fd = open(buf, mode);
173	if (fd == -1)
174		return -1;
175
176	if (!is_sysfs_fd(fd)) {
177		close(fd);
178		return -1;
179	}
180
181	return fd;
182}
183
184static int
185__backlight_read(const char *iface, const char *file)
186{
187	char buf[BACKLIGHT_VALUE_LEN];
188	int fd, val;
189
190	fd = __backlight_open(iface, file, O_RDONLY);
191	if (fd < 0)
192		return -1;
193
194	val = read(fd, buf, BACKLIGHT_VALUE_LEN - 1);
195	if (val > 0) {
196		buf[val] = '\0';
197		val = atoi(buf);
198	} else
199		val = -1;
200	close(fd);
201
202	return val;
203}
204
205/* List of available kernel interfaces in priority order */
206static const char *known_interfaces[] = {
207	"dell_backlight",
208	"gmux_backlight",
209	"asus-laptop",
210	"asus-nb-wmi",
211	"eeepc",
212	"thinkpad_screen",
213	"mbp_backlight",
214	"fujitsu-laptop",
215	"sony",
216	"samsung",
217	"acpi_video1",
218	"acpi_video0",
219	"intel_backlight",
220};
221
222static enum backlight_type __backlight_type(const char *iface)
223{
224	char buf[1024];
225	int fd, v;
226
227	v = -1;
228	fd = __backlight_open(iface, "type", O_RDONLY);
229	if (fd >= 0) {
230		v = read(fd, buf, sizeof(buf)-1);
231		close(fd);
232	}
233	if (v > 0) {
234		while (v > 0 && isspace(buf[v-1]))
235			v--;
236		buf[v] = '\0';
237
238		if (strcmp(buf, "raw") == 0)
239			v = BL_RAW;
240		else if (strcmp(buf, "platform") == 0)
241			v = BL_PLATFORM;
242		else if (strcmp(buf, "firmware") == 0)
243			v = BL_FIRMWARE;
244		else
245			v = BL_NAMED;
246	} else
247		v = BL_NAMED;
248
249	if (v == BL_NAMED) {
250		int i;
251		for (i = 0; i < ARRAY_SIZE(known_interfaces); i++) {
252			if (strcmp(iface, known_interfaces[i]) == 0)
253				break;
254		}
255		v += i;
256	}
257
258	return v;
259}
260
261enum backlight_type backlight_exists(const char *iface)
262{
263	if (__backlight_read(iface, "brightness") < 0)
264		return BL_NONE;
265
266	if (__backlight_read(iface, "max_brightness") <= 0)
267		return BL_NONE;
268
269	return __backlight_type(iface);
270}
271
272static int __backlight_init(struct backlight *b, char *iface, int fd)
273{
274	b->fd = fd_move_cloexec(fd_set_nonblock(fd));
275	b->iface = iface;
276	return 1;
277}
278
279static int __backlight_direct_init(struct backlight *b, char *iface)
280{
281	int fd;
282
283	fd = __backlight_open(iface, "brightness", O_RDWR);
284	if (fd < 0)
285		return 0;
286
287	return __backlight_init(b, iface, fd);
288}
289
290static int __backlight_helper_init(struct backlight *b, char *iface)
291{
292#if USE_BACKLIGHT_HELPER
293	struct stat st;
294	char *env[] = { NULL };
295	int use_pkexec = 0;
296	int fds[2];
297
298	/*
299	 * Some systems may prefer using PolicyKit's pkexec over
300	 * making the helper suid root, since the suid option will allow
301	 * anyone to control the backlight.  However, as pkexec
302	 * is quite troublesome and not universally available, we
303	 * still try the old fashioned and simple method first.
304	 * Either way, we have to trust that it is our backlight-helper
305	 * that is run and that we have scrutinised it carefully.
306	 */
307	if (stat(LIBEXEC_PATH "/xf86-video-intel-backlight-helper", &st))
308		return 0;
309
310	if ((st.st_mode & (S_IFREG | S_ISUID | S_IXUSR)) != (S_IFREG | S_ISUID | S_IXUSR)) {
311		if (System("pkexec --version"))
312			return 0;
313
314		use_pkexec = 1;
315	}
316
317	if (pipe(fds))
318		return 0;
319
320	switch ((b->pid = fork())) {
321	case 0:
322		if (setgid(getgid()) || setuid(getuid()))
323			_exit(127);
324
325		close(fds[1]);
326		if (dup2(fds[0], 0))
327			_exit(127);
328		close(fds[0]);
329
330		if (use_pkexec) {
331			execlp("pkexec", "pkexec",
332			       LIBEXEC_PATH "/xf86-video-intel-backlight-helper",
333			       iface, (char *)0);
334		} else {
335			execle(LIBEXEC_PATH "/xf86-video-intel-backlight-helper",
336			       "xf86-video-intel-backlight-helper",
337			       iface, (char *)0, env);
338		}
339		_exit(1);
340		/* unreachable fallthrough */
341	case -1:
342		close(fds[1]);
343		close(fds[0]);
344		return 0;
345
346	default:
347		close(fds[0]);
348		return __backlight_init(b, iface, fds[1]);
349	}
350#else
351	return 0;
352#endif
353}
354
355static char *
356__backlight_find(void)
357{
358	char *best_iface = NULL;
359	unsigned best_type = INT_MAX;
360	DIR *dir;
361	struct dirent *de;
362
363	dir = opendir(BACKLIGHT_CLASS);
364	if (dir == NULL)
365		return NULL;
366
367	while ((de = readdir(dir))) {
368		int v;
369
370		if (*de->d_name == '.')
371			continue;
372
373		/* Fallback to priority list of known iface for old kernels */
374		v = backlight_exists(de->d_name);
375		if (v < best_type) {
376			char *copy = strdup(de->d_name);
377			if (copy) {
378				free(best_iface);
379				best_iface = copy;
380				best_type = v;
381			}
382		}
383	}
384	closedir(dir);
385
386	return best_iface;
387}
388
389int backlight_open(struct backlight *b, char *iface)
390{
391	int level;
392
393	if (iface == NULL)
394		iface = __backlight_find();
395	if (iface == NULL)
396		goto err;
397
398	b->type = __backlight_type(iface);
399
400	b->max = __backlight_read(iface, "max_brightness");
401	if (b->max <= 0)
402		goto err;
403
404	level = __backlight_read(iface, "brightness");
405	if (level < 0)
406		goto err;
407
408	if (!__backlight_direct_init(b, iface) &&
409	    !__backlight_helper_init(b, iface))
410		goto err;
411
412	return level;
413
414err:
415	backlight_init(b);
416	return -1;
417}
418
419int backlight_set(struct backlight *b, int level)
420{
421	char val[BACKLIGHT_VALUE_LEN];
422	int len, ret = 0;
423
424	if (b->iface == NULL)
425		return 0;
426
427	if ((unsigned)level > b->max)
428		level = b->max;
429
430	len = snprintf(val, BACKLIGHT_VALUE_LEN, "%d\n", level);
431	if (write(b->fd, val, len) != len)
432		ret = -1;
433
434	return ret;
435}
436
437int backlight_get(struct backlight *b)
438{
439	int level;
440
441	if (b->iface == NULL)
442		return -1;
443
444	level = __backlight_read(b->iface, "brightness");
445	if (level > b->max)
446		level = b->max;
447	else if (level < 0)
448		level = -1;
449	return level;
450}
451#endif
452
453void backlight_disable(struct backlight *b)
454{
455	if (b->iface == NULL)
456		return;
457
458	if (b->fd != -1)
459		close(b->fd);
460
461	free(b->iface);
462	b->iface = NULL;
463}
464
465void backlight_close(struct backlight *b)
466{
467	backlight_disable(b);
468	if (b->pid)
469		waitpid(b->pid, NULL, 0);
470}
471
472char *backlight_find_for_device(struct pci_device *pci)
473{
474	char path[200];
475	unsigned best_type = INT_MAX;
476	char *best_iface = NULL;
477	DIR *dir;
478	struct dirent *de;
479
480	snprintf(path, sizeof(path),
481		 "/sys/bus/pci/devices/%04x:%02x:%02x.%d/backlight",
482		 pci->domain, pci->bus, pci->dev, pci->func);
483
484	dir = opendir(path);
485	if (dir == NULL)
486		return NULL;
487
488	while ((de = readdir(dir))) {
489		int v;
490
491		if (*de->d_name == '.')
492			continue;
493
494		v = backlight_exists(de->d_name);
495		if (v < best_type) {
496			char *copy = strdup(de->d_name);
497			if (copy) {
498				free(best_iface);
499				best_iface = copy;
500				best_type = v;
501			}
502		}
503	}
504	closedir(dir);
505
506	return best_iface;
507}
508