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	b->has_power = 0;
85}
86
87#if defined(__OpenBSD__) || defined(__NetBSD__)
88
89#include <dev/wscons/wsconsio.h>
90#include <xf86Priv.h>
91
92int backlight_set(struct backlight *b, int level)
93{
94	struct wsdisplay_param param;
95
96	if (b->iface == NULL)
97		return -1;
98
99	if ((unsigned)level > b->max)
100		level = b->max;
101
102	memset(&param, 0, sizeof(param));
103	param.param = WSDISPLAYIO_PARAM_BRIGHTNESS;
104	param.curval = level;
105
106	return ioctl(xf86Info.consoleFd, WSDISPLAYIO_SETPARAM, &param);
107}
108
109int backlight_get(struct backlight *b)
110{
111	struct wsdisplay_param param;
112
113	if (b->iface == NULL)
114		return -1;
115
116	memset(&param, 0, sizeof(param));
117	param.param = WSDISPLAYIO_PARAM_BRIGHTNESS;
118
119	if (ioctl(xf86Info.consoleFd, WSDISPLAYIO_GETPARAM, &param))
120		return -1;
121
122	return param.curval;
123}
124
125int backlight_open(struct backlight *b, char *iface)
126{
127	struct wsdisplay_param param;
128
129	if (iface != NULL)
130		return -1;
131
132	memset(&param, 0, sizeof(param));
133	param.param = WSDISPLAYIO_PARAM_BRIGHTNESS;
134
135	if (ioctl(xf86Info.consoleFd, WSDISPLAYIO_GETPARAM, &param) == -1)
136		return -1;
137
138	b->iface = strdup("wscons");
139	if (b->iface == NULL)
140		return -1;
141
142	b->max = param.max;
143	b->fd = -1;
144	b->type = BL_PLATFORM;
145
146	return param.curval;
147}
148
149enum backlight_type backlight_exists(const char *iface)
150{
151	if (iface != NULL)
152		return BL_NONE;
153
154	return BL_PLATFORM;
155}
156
157int backlight_on(struct backlight *b)
158{
159	return 0;
160}
161
162int backlight_off(struct backlight *b)
163{
164	return 0;
165}
166#else
167
168static int
169is_sysfs_fd(int fd)
170{
171	struct stat st;
172	return fstat(fd, &st) == 0 && major(st.st_dev) == 0;
173}
174
175static int
176__backlight_open(const char *iface, const char *file, int mode)
177{
178	char buf[1024];
179	int fd;
180
181	snprintf(buf, sizeof(buf), BACKLIGHT_CLASS "/%s/%s", iface, file);
182	fd = open(buf, mode);
183	if (fd == -1)
184		return -1;
185
186	if (!is_sysfs_fd(fd)) {
187		close(fd);
188		return -1;
189	}
190
191	return fd;
192}
193
194static int
195__backlight_read(const char *iface, const char *file)
196{
197	char buf[BACKLIGHT_VALUE_LEN];
198	int fd, val;
199
200	fd = __backlight_open(iface, file, O_RDONLY);
201	if (fd < 0)
202		return -1;
203
204	val = read(fd, buf, BACKLIGHT_VALUE_LEN - 1);
205	if (val > 0) {
206		buf[val] = '\0';
207		val = atoi(buf);
208	} else
209		val = -1;
210	close(fd);
211
212	return val;
213}
214
215static int
216__backlight_write(const char *iface, const char *file, const char *value)
217{
218	int fd, ret;
219
220	fd = __backlight_open(iface, file, O_WRONLY);
221	if (fd < 0)
222		return -1;
223
224	ret = write(fd, value, strlen(value)+1);
225	close(fd);
226
227	return ret;
228}
229
230/* List of available kernel interfaces in priority order */
231static const char *known_interfaces[] = {
232	"dell_backlight",
233	"gmux_backlight",
234	"asus-laptop",
235	"asus-nb-wmi",
236	"eeepc",
237	"thinkpad_screen",
238	"mbp_backlight",
239	"fujitsu-laptop",
240	"sony",
241	"samsung",
242	"acpi_video1",
243	"acpi_video0",
244	"intel_backlight",
245};
246
247static enum backlight_type __backlight_type(const char *iface)
248{
249	char buf[1024];
250	int fd, v;
251
252	v = -1;
253	fd = __backlight_open(iface, "type", O_RDONLY);
254	if (fd >= 0) {
255		v = read(fd, buf, sizeof(buf)-1);
256		close(fd);
257	}
258	if (v > 0) {
259		while (v > 0 && isspace(buf[v-1]))
260			v--;
261		buf[v] = '\0';
262
263		if (strcmp(buf, "raw") == 0)
264			v = BL_RAW;
265		else if (strcmp(buf, "platform") == 0)
266			v = BL_PLATFORM;
267		else if (strcmp(buf, "firmware") == 0)
268			v = BL_FIRMWARE;
269		else
270			v = BL_NAMED;
271	} else
272		v = BL_NAMED;
273
274	if (v == BL_NAMED) {
275		int i;
276		for (i = 0; i < ARRAY_SIZE(known_interfaces); i++) {
277			if (strcmp(iface, known_interfaces[i]) == 0)
278				break;
279		}
280		v += i;
281	}
282
283	return v;
284}
285
286enum backlight_type backlight_exists(const char *iface)
287{
288	if (__backlight_read(iface, "brightness") < 0)
289		return BL_NONE;
290
291	if (__backlight_read(iface, "max_brightness") <= 0)
292		return BL_NONE;
293
294	return __backlight_type(iface);
295}
296
297static int __backlight_init(struct backlight *b, char *iface, int fd)
298{
299	b->fd = fd_move_cloexec(fd_set_nonblock(fd));
300	b->iface = iface;
301	return 1;
302}
303
304static int __backlight_direct_init(struct backlight *b, char *iface)
305{
306	int fd;
307
308	fd = __backlight_open(iface, "brightness", O_RDWR);
309	if (fd < 0)
310		return 0;
311
312	if (__backlight_read(iface, "bl_power") != -1)
313		b->has_power = 1;
314
315	return __backlight_init(b, iface, fd);
316}
317
318static int __backlight_helper_init(struct backlight *b, char *iface)
319{
320#if USE_BACKLIGHT_HELPER
321	struct stat st;
322	char *env[] = { NULL };
323	int use_pkexec = 0;
324	int fds[2];
325
326	/*
327	 * Some systems may prefer using PolicyKit's pkexec over
328	 * making the helper suid root, since the suid option will allow
329	 * anyone to control the backlight.  However, as pkexec
330	 * is quite troublesome and not universally available, we
331	 * still try the old fashioned and simple method first.
332	 * Either way, we have to trust that it is our backlight-helper
333	 * that is run and that we have scrutinised it carefully.
334	 */
335	if (stat(LIBEXEC_PATH "/xf86-video-intel-backlight-helper", &st))
336		return 0;
337
338	if ((st.st_mode & (S_IFREG | S_ISUID | S_IXUSR)) != (S_IFREG | S_ISUID | S_IXUSR)) {
339		if (System("pkexec --version"))
340			return 0;
341
342		use_pkexec = 1;
343	}
344
345	if (pipe(fds))
346		return 0;
347
348	switch ((b->pid = fork())) {
349	case 0:
350		if (setgid(getgid()) || setuid(getuid()))
351			_exit(127);
352
353		close(fds[1]);
354		if (dup2(fds[0], 0))
355			_exit(127);
356		close(fds[0]);
357
358		if (use_pkexec) {
359			execlp("pkexec", "pkexec",
360			       LIBEXEC_PATH "/xf86-video-intel-backlight-helper",
361			       iface, (char *)0);
362		} else {
363			execle(LIBEXEC_PATH "/xf86-video-intel-backlight-helper",
364			       "xf86-video-intel-backlight-helper",
365			       iface, (char *)0, env);
366		}
367		_exit(1);
368		/* unreachable fallthrough */
369	case -1:
370		close(fds[1]);
371		close(fds[0]);
372		return 0;
373
374	default:
375		close(fds[0]);
376		return __backlight_init(b, iface, fds[1]);
377	}
378#else
379	return 0;
380#endif
381}
382
383static char *
384__backlight_find(void)
385{
386	char *best_iface = NULL;
387	unsigned best_type = INT_MAX;
388	DIR *dir;
389	struct dirent *de;
390
391	dir = opendir(BACKLIGHT_CLASS);
392	if (dir == NULL)
393		return NULL;
394
395	while ((de = readdir(dir))) {
396		int v;
397
398		if (*de->d_name == '.')
399			continue;
400
401		/* Fallback to priority list of known iface for old kernels */
402		v = backlight_exists(de->d_name);
403		if (v < best_type) {
404			char *copy = strdup(de->d_name);
405			if (copy) {
406				free(best_iface);
407				best_iface = copy;
408				best_type = v;
409			}
410		}
411	}
412	closedir(dir);
413
414	return best_iface;
415}
416
417int backlight_open(struct backlight *b, char *iface)
418{
419	int level;
420
421	if (iface == NULL)
422		iface = __backlight_find();
423	if (iface == NULL)
424		goto err;
425
426	b->type = __backlight_type(iface);
427
428	b->max = __backlight_read(iface, "max_brightness");
429	if (b->max <= 0)
430		goto err;
431
432	level = __backlight_read(iface, "brightness");
433	if (level < 0)
434		goto err;
435
436	if (!__backlight_direct_init(b, iface) &&
437	    !__backlight_helper_init(b, iface))
438		goto err;
439
440	return level;
441
442err:
443	backlight_init(b);
444	return -1;
445}
446
447int backlight_set(struct backlight *b, int level)
448{
449	char val[BACKLIGHT_VALUE_LEN];
450	int len, ret = 0;
451
452	if (b->iface == NULL)
453		return 0;
454
455	if ((unsigned)level > b->max)
456		level = b->max;
457
458	len = snprintf(val, BACKLIGHT_VALUE_LEN, "%d\n", level);
459	if (write(b->fd, val, len) != len)
460		ret = -1;
461
462	return ret;
463}
464
465int backlight_get(struct backlight *b)
466{
467	int level;
468
469	if (b->iface == NULL)
470		return -1;
471
472	level = __backlight_read(b->iface, "brightness");
473	if (level > b->max)
474		level = b->max;
475	else if (level < 0)
476		level = -1;
477	return level;
478}
479
480int backlight_off(struct backlight *b)
481{
482	if (b->iface == NULL)
483		return 0;
484
485	if (!b->has_power)
486		return 0;
487
488	/* 4 -> FB_BLANK_POWERDOWN */
489	return __backlight_write(b->iface, "bl_power", "4");
490}
491
492int backlight_on(struct backlight *b)
493{
494	if (b->iface == NULL)
495		return 0;
496
497	if (!b->has_power)
498		return 0;
499
500	/* 0 -> FB_BLANK_UNBLANK */
501	return __backlight_write(b->iface, "bl_power", "0");
502}
503#endif
504
505void backlight_disable(struct backlight *b)
506{
507	if (b->iface == NULL)
508		return;
509
510	if (b->fd != -1)
511		close(b->fd);
512
513	free(b->iface);
514	b->iface = NULL;
515}
516
517void backlight_close(struct backlight *b)
518{
519	backlight_disable(b);
520	if (b->pid)
521		waitpid(b->pid, NULL, 0);
522}
523
524char *backlight_find_for_device(struct pci_device *pci)
525{
526	char path[200];
527	unsigned best_type = INT_MAX;
528	char *best_iface = NULL;
529	DIR *dir;
530	struct dirent *de;
531
532	snprintf(path, sizeof(path),
533		 "/sys/bus/pci/devices/%04x:%02x:%02x.%d/backlight",
534		 pci->domain, pci->bus, pci->dev, pci->func);
535
536	dir = opendir(path);
537	if (dir == NULL)
538		return NULL;
539
540	while ((de = readdir(dir))) {
541		int v;
542
543		if (*de->d_name == '.')
544			continue;
545
546		v = backlight_exists(de->d_name);
547		if (v < best_type) {
548			char *copy = strdup(de->d_name);
549			if (copy) {
550				free(best_iface);
551				best_iface = copy;
552				best_type = v;
553			}
554		}
555	}
556	closedir(dir);
557
558	return best_iface;
559}
560