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