1 /* gamma-drm.c -- DRM gamma adjustment source
2    This file is part of Redshift.
3 
4    Redshift is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8 
9    Redshift is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13 
14    You should have received a copy of the GNU General Public License
15    along with Redshift.  If not, see <http://www.gnu.org/licenses/>.
16 
17    Copyright (c) 2014  Mattias Andrée <maandree@member.fsf.org>
18    Copyright (c) 2017  Jon Lund Steffensen <jonlst@gmail.com>
19 */
20 
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <stdint.h>
24 #include <string.h>
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #include <fcntl.h>
28 #include <unistd.h>
29 
30 #ifdef ENABLE_NLS
31 # include <libintl.h>
32 # define _(s) gettext(s)
33 #else
34 # define _(s) s
35 #endif
36 
37 #ifndef O_CLOEXEC
38   #define O_CLOEXEC  02000000
39 #endif
40 
41 #include <xf86drm.h>
42 #include <xf86drmMode.h>
43 
44 #include "gamma-drm.h"
45 #include "colorramp.h"
46 
47 
48 typedef struct {
49 	int crtc_num;
50 	int crtc_id;
51 	int gamma_size;
52 	uint16_t* r_gamma;
53 	uint16_t* g_gamma;
54 	uint16_t* b_gamma;
55 } drm_crtc_state_t;
56 
57 typedef struct {
58 	int card_num;
59 	int crtc_num;
60 	int fd;
61 	drmModeRes* res;
62 	drm_crtc_state_t* crtcs;
63 } drm_state_t;
64 
65 
66 static int
drm_init(drm_state_t ** state)67 drm_init(drm_state_t **state)
68 {
69 	/* Initialize state. */
70 	*state = malloc(sizeof(drm_state_t));
71 	if (*state == NULL) return -1;
72 
73 	drm_state_t *s = *state;
74 	s->card_num = 0;
75 	s->crtc_num = -1;
76 	s->fd = -1;
77 	s->res = NULL;
78 	s->crtcs = NULL;
79 
80 	return 0;
81 }
82 
83 static int
drm_start(drm_state_t * state)84 drm_start(drm_state_t *state)
85 {
86 	/* Acquire access to a graphics card. */
87 	long maxlen = strlen(DRM_DIR_NAME) + strlen(DRM_DEV_NAME) + 10;
88 	char pathname[maxlen];
89 
90 	sprintf(pathname, DRM_DEV_NAME, DRM_DIR_NAME, state->card_num);
91 
92 	state->fd = open(pathname, O_RDWR | O_CLOEXEC);
93 	if (state->fd < 0) {
94 		/* TODO check if access permissions, normally root or
95 		        membership of the video group is required. */
96 		perror("open");
97 		fprintf(stderr, _("Failed to open DRM device: %s\n"),
98 			pathname);
99 		return -1;
100 	}
101 
102 	/* Acquire mode resources. */
103 	state->res = drmModeGetResources(state->fd);
104 	if (state->res == NULL) {
105 		fprintf(stderr, _("Failed to get DRM mode resources\n"));
106 		close(state->fd);
107 		state->fd = -1;
108 		return -1;
109 	}
110 
111 	/* Create entries for selected CRTCs. */
112 	int crtc_count = state->res->count_crtcs;
113 	if (state->crtc_num >= 0) {
114 		if (state->crtc_num >= crtc_count) {
115 			fprintf(stderr, _("CRTC %d does not exist. "),
116 				state->crtc_num);
117 			if (crtc_count > 1) {
118 				fprintf(stderr, _("Valid CRTCs are [0-%d].\n"),
119 					crtc_count-1);
120 			} else {
121 				fprintf(stderr, _("Only CRTC 0 exists.\n"));
122 			}
123 			close(state->fd);
124 			state->fd = -1;
125 			drmModeFreeResources(state->res);
126 			state->res = NULL;
127 			return -1;
128 		}
129 
130 		state->crtcs = malloc(2 * sizeof(drm_crtc_state_t));
131 		state->crtcs[1].crtc_num = -1;
132 
133 		state->crtcs->crtc_num = state->crtc_num;
134 		state->crtcs->crtc_id = -1;
135 		state->crtcs->gamma_size = -1;
136 		state->crtcs->r_gamma = NULL;
137 		state->crtcs->g_gamma = NULL;
138 		state->crtcs->b_gamma = NULL;
139 	} else {
140 		int crtc_num;
141 		state->crtcs = malloc((crtc_count + 1) * sizeof(drm_crtc_state_t));
142 		state->crtcs[crtc_count].crtc_num = -1;
143 		for (crtc_num = 0; crtc_num < crtc_count; crtc_num++) {
144 			state->crtcs[crtc_num].crtc_num = crtc_num;
145 			state->crtcs[crtc_num].crtc_id = -1;
146 			state->crtcs[crtc_num].gamma_size = -1;
147 			state->crtcs[crtc_num].r_gamma = NULL;
148 			state->crtcs[crtc_num].g_gamma = NULL;
149 			state->crtcs[crtc_num].b_gamma = NULL;
150 		}
151 	}
152 
153 	/* Load CRTC information and gamma ramps. */
154 	drm_crtc_state_t *crtcs = state->crtcs;
155 	for (; crtcs->crtc_num >= 0; crtcs++) {
156 		crtcs->crtc_id = state->res->crtcs[crtcs->crtc_num];
157 		drmModeCrtc* crtc_info = drmModeGetCrtc(state->fd, crtcs->crtc_id);
158 		if (crtc_info == NULL) {
159 			fprintf(stderr, _("CRTC %i lost, skipping\n"), crtcs->crtc_num);
160 			continue;
161 		}
162 		crtcs->gamma_size = crtc_info->gamma_size;
163 		drmModeFreeCrtc(crtc_info);
164 		if (crtcs->gamma_size <= 1) {
165 			fprintf(stderr, _("Could not get gamma ramp size for CRTC %i\n"
166 					  "on graphics card %i, ignoring device.\n"),
167 				crtcs->crtc_num, state->card_num);
168 			continue;
169 		}
170 		/* Valgrind complains about us reading uninitialize memory if we just use malloc. */
171 		crtcs->r_gamma = calloc(3 * crtcs->gamma_size, sizeof(uint16_t));
172 		crtcs->g_gamma = crtcs->r_gamma + crtcs->gamma_size;
173 		crtcs->b_gamma = crtcs->g_gamma + crtcs->gamma_size;
174 		if (crtcs->r_gamma != NULL) {
175 			int r = drmModeCrtcGetGamma(state->fd, crtcs->crtc_id, crtcs->gamma_size,
176 						    crtcs->r_gamma, crtcs->g_gamma, crtcs->b_gamma);
177 			if (r < 0) {
178 				fprintf(stderr, _("DRM could not read gamma ramps on CRTC %i on\n"
179 						  "graphics card %i, ignoring device.\n"),
180 					crtcs->crtc_num, state->card_num);
181 				free(crtcs->r_gamma);
182 				crtcs->r_gamma = NULL;
183 			}
184 		} else {
185 			perror("malloc");
186 			drmModeFreeResources(state->res);
187 			state->res = NULL;
188 			close(state->fd);
189 			state->fd = -1;
190 			while (crtcs-- != state->crtcs)
191 				free(crtcs->r_gamma);
192 			free(state->crtcs);
193 			state->crtcs = NULL;
194 			return -1;
195 		}
196 	}
197 
198 	return 0;
199 }
200 
201 static void
drm_restore(drm_state_t * state)202 drm_restore(drm_state_t *state)
203 {
204 	drm_crtc_state_t *crtcs = state->crtcs;
205 	while (crtcs->crtc_num >= 0) {
206 		if (crtcs->r_gamma != NULL) {
207 			drmModeCrtcSetGamma(state->fd, crtcs->crtc_id, crtcs->gamma_size,
208 					    crtcs->r_gamma, crtcs->g_gamma, crtcs->b_gamma);
209 		}
210 		crtcs++;
211 	}
212 }
213 
214 static void
drm_free(drm_state_t * state)215 drm_free(drm_state_t *state)
216 {
217 	if (state->crtcs != NULL) {
218 		drm_crtc_state_t *crtcs = state->crtcs;
219 		while (crtcs->crtc_num >= 0) {
220 			free(crtcs->r_gamma);
221 			crtcs->crtc_num = -1;
222 			crtcs++;
223 		}
224 		free(state->crtcs);
225 		state->crtcs = NULL;
226 	}
227 	if (state->res != NULL) {
228 		drmModeFreeResources(state->res);
229 		state->res = NULL;
230 	}
231 	if (state->fd >= 0) {
232 		close(state->fd);
233 		state->fd = -1;
234 	}
235 
236 	free(state);
237 }
238 
239 static void
drm_print_help(FILE * f)240 drm_print_help(FILE *f)
241 {
242 	fputs(_("Adjust gamma ramps with Direct Rendering Manager.\n"), f);
243 	fputs("\n", f);
244 
245 	/* TRANSLATORS: DRM help output
246 	   left column must not be translated */
247 	fputs(_("  card=N\tGraphics card to apply adjustments to\n"
248 		"  crtc=N\tCRTC to apply adjustments to\n"), f);
249 	fputs("\n", f);
250 }
251 
252 static int
drm_set_option(drm_state_t * state,const char * key,const char * value)253 drm_set_option(drm_state_t *state, const char *key, const char *value)
254 {
255 	if (strcasecmp(key, "card") == 0) {
256 		state->card_num = atoi(value);
257 	} else if (strcasecmp(key, "crtc") == 0) {
258 		state->crtc_num = atoi(value);
259 		if (state->crtc_num < 0) {
260 			fprintf(stderr, _("CRTC must be a non-negative integer\n"));
261 			return -1;
262 		}
263 	} else {
264 		fprintf(stderr, _("Unknown method parameter: `%s'.\n"), key);
265 		return -1;
266 	}
267 
268 	return 0;
269 }
270 
271 static int
drm_set_temperature(drm_state_t * state,const color_setting_t * setting,int preserve)272 drm_set_temperature(
273 	drm_state_t *state, const color_setting_t *setting, int preserve)
274 {
275 	drm_crtc_state_t *crtcs = state->crtcs;
276 	int last_gamma_size = 0;
277 	uint16_t *r_gamma = NULL;
278 	uint16_t *g_gamma = NULL;
279 	uint16_t *b_gamma = NULL;
280 
281 	for (; crtcs->crtc_num >= 0; crtcs++) {
282 		if (crtcs->gamma_size <= 1)
283 			continue;
284 		if (crtcs->gamma_size != last_gamma_size) {
285 			if (last_gamma_size == 0) {
286 				r_gamma = malloc(3 * crtcs->gamma_size * sizeof(uint16_t));
287 				g_gamma = r_gamma + crtcs->gamma_size;
288 				b_gamma = g_gamma + crtcs->gamma_size;
289 			} else if (crtcs->gamma_size > last_gamma_size) {
290 				r_gamma = realloc(r_gamma, 3 * crtcs->gamma_size * sizeof(uint16_t));
291 				g_gamma = r_gamma + crtcs->gamma_size;
292 				b_gamma = g_gamma + crtcs->gamma_size;
293 			}
294 			if (r_gamma == NULL) {
295 				perror(last_gamma_size == 0 ? "malloc" : "realloc");
296 				return -1;
297 			}
298 			last_gamma_size = crtcs->gamma_size;
299 		}
300 
301 		/* Initialize gamma ramps to pure state */
302 		int ramp_size = crtcs->gamma_size;
303 		for (int i = 0; i < ramp_size; i++) {
304 			uint16_t value = (double)i/ramp_size * (UINT16_MAX+1);
305 			r_gamma[i] = value;
306 			g_gamma[i] = value;
307 			b_gamma[i] = value;
308 		}
309 
310 		colorramp_fill(r_gamma, g_gamma, b_gamma, crtcs->gamma_size,
311 			       setting);
312 		drmModeCrtcSetGamma(state->fd, crtcs->crtc_id, crtcs->gamma_size,
313 				    r_gamma, g_gamma, b_gamma);
314 	}
315 
316 	free(r_gamma);
317 
318 	return 0;
319 }
320 
321 
322 const gamma_method_t drm_gamma_method = {
323 	"drm", 0,
324 	(gamma_method_init_func *)drm_init,
325 	(gamma_method_start_func *)drm_start,
326 	(gamma_method_free_func *)drm_free,
327 	(gamma_method_print_help_func *)drm_print_help,
328 	(gamma_method_set_option_func *)drm_set_option,
329 	(gamma_method_restore_func *)drm_restore,
330 	(gamma_method_set_temperature_func *)drm_set_temperature
331 };
332