aboutsummaryrefslogtreecommitdiffstats
path: root/libgamma_linux_drm_get_crtc_information.c
blob: a47687826f3a213be13c23dcb6faf35de00efa7b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
/* See LICENSE file for copyright and license details. */
#define IN_LIBGAMMA_LINUX_DRM
#include "common.h"


/**
 * Find the connector that a CRTC belongs to
 * 
 * @param   this   The CRTC state
 * @param   error  Output of the error value to store of error report
 *                 fields for data that requires the connector
'* @return         The CRTC's conncetor, `NULL` on error
 */
static drmModeConnector *
find_connector(struct libgamma_crtc_state *restrict this, int *restrict error)
{
	uint32_t crtc_id = (uint32_t)(size_t)this->data;
	struct libgamma_drm_card_data *restrict card = this->partition->data;
	size_t i, n = (size_t)card->res->count_connectors;
	/* Open connectors and encoders if not already opened */
	if (!card->connectors) {
		/* Allocate connector and encoder arrays; we use `calloc`
		   so all non-loaded elements are `NULL` after an error */
		card->connectors = calloc(n, sizeof(drmModeConnector *));
		if (!card->connectors)
			goto fail;
		card->encoders = calloc(n, sizeof(drmModeEncoder *));
		if (!card->encoders)
			goto fail;
		/* Fill connector and encoder arrays */
		for (i = 0; i < n; i++) {
			/* Get connector */
			card->connectors[i] = drmModeGetConnector(card->fd, card->res->connectors[i]);
			if (!card->connectors[i])
				goto fail;
			/* Get encoder if the connector is enabled. If it is disabled it
			   will not have an encoder, which is indicated by the encoder
			   ID being 0. In such case, leave the encoder to be `NULL`. */
			if (card->connectors[i]->encoder_id) {
				card->encoders[i] = drmModeGetEncoder(card->fd, card->connectors[i]->encoder_id);
				if (!card->encoders[i])
					goto fail;
			}
		}
	}
	/* No error has occurred yet */
	*error = 0;
	/* Find connector */
	for (i = 0; i < n; i++)
		if (card->encoders[i] && card->connectors[i] && card->encoders[i]->crtc_id == crtc_id)
			return card->connectors[i];
	/* We did not find the connector */
	*error = LIBGAMMA_CONNECTOR_UNKNOWN;
	return NULL;

fail:
	/* Report the error that got us here, release
	   resouces and exit with `NULL` for failure */
	*error = errno;
	libgamma_linux_drm_internal_release_connectors_and_encoders(card);
	return NULL;
}


/**
 * Get the size of the gamma ramps for a CRTC
 * 
 * @param   out   Instance of a data structure to fill with the information about the CRTC
 * @param   crtc  The state of the CRTC whose information should be read
 * @return        The value stored in `out->gamma_size_error`
 */
static int
get_gamma_ramp_size(struct libgamma_crtc_information *restrict out, const struct libgamma_crtc_state *restrict crtc)
{
	struct libgamma_drm_card_data *restrict card = crtc->partition->data;
	uint32_t crtc_id = card->res->crtcs[crtc->crtc];
	drmModeCrtc *restrict crtc_info;
	/* Get CRTC information */
	errno = 0;
	crtc_info = drmModeGetCrtc(card->fd, crtc_id);
	out->gamma_size_error = crtc_info ? 0 : errno;
	/* Get gamma ramp size */
	if (!out->gamma_size_error) {
		/* Store gamma ramp size */
		out->red_gamma_size = out->green_gamma_size = out->blue_gamma_size = (size_t)crtc_info->gamma_size;
		/* Sanity check gamma ramp size */
		out->gamma_size_error = crtc_info->gamma_size < 2 ? LIBGAMMA_SINGLETON_GAMMA_RAMP : 0;
		/* Release CRTC information */
		drmModeFreeCrtc(crtc_info);
	}
	return out->gamma_size_error;
}


/**
 * Get the a monitor's subpixel order
 * 
 * @param  out        Instance of a data structure to fill with the information about the CRTC
 * @param  connector  The connector
 */
static void
get_subpixel_order(struct libgamma_crtc_information *restrict out, const drmModeConnector *restrict connector)
{
	switch (connector->subpixel) {
#define X(CONST, NAME, DRM_SUFFIX)\
	case DRM_MODE_SUBPIXEL_##DRM_SUFFIX:\
		out->subpixel_order = CONST;\
		break;
	LIST_SUBPIXEL_ORDERS(X)
	default:
		out->subpixel_order_error = LIBGAMMA_SUBPIXEL_ORDER_NOT_RECOGNISED;
		break;
#undef X
	}
}


/**
 * Get a connector's type
 * 
 * @param  out                  Instance of a data structure to fill with the information about the CRTC
 * @param  connector            The connector
 * @param  connector_name_base  Output for the basename of the connector
 */
static void
get_connector_type(struct libgamma_crtc_information *restrict out, const drmModeConnector *restrict connector,
                   const char **restrict connector_name_base)
{
	/* These may not have been included by <xf86drmMode.h>,
	   but they should be available. Because we define them
	   ourself, it is best to test them last. */
#ifndef DRM_MODE_CONNECTOR_VIRTUAL
# define DRM_MODE_CONNECTOR_VIRTUAL 15
#endif
#ifndef DRM_MODE_CONNECTOR_DSI
# define DRM_MODE_CONNECTOR_DSI 16
#endif
#ifndef DRM_MODE_CONNECTOR_DPI
# define DRM_MODE_CONNECTOR_DPI 17
#endif
#ifndef DRM_MODE_CONNECTOR_WRITEBACK
# define DRM_MODE_CONNECTOR_WRITEBACK 18
#endif
#ifndef DRM_MODE_CONNECTOR_SPI
# define DRM_MODE_CONNECTOR_SPI 19
#endif

	/* These are not defined */
#ifndef DRM_MODE_CONNECTOR_DVI
# define DRM_MODE_CONNECTOR_DVI 123000001
#elif defined(__GNUC__)
# warning DRM_MODE_CONNECTOR_DVI is defined, update fallback definitions
#endif
#ifndef DRM_MODE_CONNECTOR_HDMI
# define DRM_MODE_CONNECTOR_HDMI 123000002
#elif defined(__GNUC__)
# warning DRM_MODE_CONNECTOR_HDMI is defined, update fallback definitions
#endif
#ifndef DRM_MODE_CONNECTOR_LFP
# define DRM_MODE_CONNECTOR_LFP 123000003
#elif defined(__GNUC__)
# warning DRM_MODE_CONNECTOR_LFP is defined, update fallback definitions
#endif

	switch (connector->connector_type) {
#define X(CONST, NAME, DRM_SUFFIX)\
	case DRM_MODE_CONNECTOR_##DRM_SUFFIX:\
		out->connector_type = CONST;\
		*connector_name_base = NAME;\
		break;
	LIST_CONNECTOR_TYPES(X)
#undef X
	default:
		out->connector_type_error = LIBGAMMA_CONNECTOR_TYPE_NOT_RECOGNISED;
		out->connector_name_error = LIBGAMMA_CONNECTOR_TYPE_NOT_RECOGNISED;
		break;
	}
}


/**
 * Read information from the CRTC's conncetor
 * 
 * @param   crtc       The state of the CRTC whose information should be read
 * @param   out        Instance of a data structure to fill with the information about the CRTC
 * @param   connector  The CRTC's connector
 * @param   fields     OR:ed identifiers for the information about the CRTC that should be read
 * @return             Non-zero if at least on error occured
 */
static int
read_connector_data(struct libgamma_crtc_state *restrict crtc, struct libgamma_crtc_information *restrict out,
                    const drmModeConnector *restrict connector, unsigned long long int fields)
{
	const char *connector_name_base = NULL;
	struct libgamma_drm_card_data *restrict card;
	uint32_t type;
	size_t i, n, c;

	/* Get some information that does not require too much work */
	if (fields & (LIBGAMMA_CRTC_INFO_MACRO_ACTIVE | LIBGAMMA_CRTC_INFO_MACRO_CONNECTOR)) {
		/* Get whether or not a monitor is plugged in */
		out->active = connector->connection == DRM_MODE_CONNECTED;
		out->active_error = connector->connection == DRM_MODE_UNKNOWNCONNECTION ? LIBGAMMA_STATE_UNKNOWN : 0;
		if (!out->active) {
			if (fields & (LIBGAMMA_CRTC_INFO_MACRO_VIEWPORT | LIBGAMMA_CRTC_INFO_SUBPIXEL_ORDER))
				out->width_mm_error = out->height_mm_error = out->subpixel_order_error = LIBGAMMA_NOT_CONNECTED;
			goto not_connected;
		}

		/* Get viewport dimension */
		out->width_mm = connector->mmWidth;
		out->height_mm = connector->mmHeight;

		/* Get subpixel order */
		get_subpixel_order(out, connector);

	not_connected:

		/* Get connector type */
		get_connector_type(out, connector, &connector_name_base);
	}

	/* Get the connector's name */
	if ((fields & LIBGAMMA_CRTC_INFO_CONNECTOR_NAME) && !out->connector_name_error) {
		card = crtc->partition->data;
		type = connector->connector_type;
		n = (size_t)card->res->count_connectors;

		/* Allocate memory for the name of the connector */
		out->connector_name = malloc((strlen(connector_name_base) + 12) * sizeof(char));
		if (!out->connector_name)
			return (out->connector_name_error = errno);

		/* Get the number of connectors with the same type on the same graphics card */
		for (i = c = 0; i < n && card->connectors[i] != connector; i++)
			if (card->connectors[i]->connector_type == type)
				c++;

		/* Construct and store connect name that is unique to the graphics card */
		sprintf(out->connector_name, "%s-%" PRIu32, connector_name_base, (uint32_t)(c + 1));
	}

	/* Did something go wrong? */
	return out->subpixel_order_error | out->active_error | out->connector_name_error;
}


/**
 * Get the extended display identification data for a monitor
 * 
 * @param   crtc       The CRTC state
 * @param   out        Instance of a data structure to fill with the information about the CRTC
 * @param   connector  The CRTC's connector
 * @reutnr             Non-zero on error
 */
static int
get_edid(struct libgamma_crtc_state *restrict crtc, struct libgamma_crtc_information *restrict out, drmModeConnector *connector)
{
	struct libgamma_drm_card_data *restrict card = crtc->partition->data;
	int prop_n = connector->count_props;
	int prop_i;
	drmModePropertyRes *restrict prop;
	drmModePropertyBlobRes *restrict blob;

	/* Test all properies on the connector */
	for (prop_i = 0; prop_i < prop_n; prop_i++) {
		/* Get output property */
		prop = drmModeGetProperty(card->fd, connector->props[prop_i]);
		if (!prop)
			continue;
		/* Is this property the EDID? */
		if (!strcmp(prop->name, "EDID")) {
			/* Get the property value */
			blob = drmModeGetPropertyBlob(card->fd, (uint32_t)connector->prop_values[prop_i]);
			if (!blob) {
				drmModeFreeProperty(prop);
				return (out->edid_error = LIBGAMMA_PROPERTY_VALUE_QUERY_FAILED);
			}
			if (blob->data) {
				/* Get and store the length of the EDID */
				out->edid_length = blob->length;
				/* Allocate memory for a copy of the EDID that is under our memory control */
				out->edid = malloc(out->edid_length * sizeof(unsigned char));
				if (!out->edid) {
					out->edid_error = errno;
				} else {
					/* Copy the EDID so we can free resources that got us here */
					memcpy(out->edid, blob->data, (size_t)out->edid_length * sizeof(char));
				}
				/* Free the propriety value and the propery */
				drmModeFreePropertyBlob(blob);
				drmModeFreeProperty(prop);
				/* Were we successful? */
				return !out->edid;
			}
			/* Free the propriety value */
			drmModeFreePropertyBlob(blob);
		}
		/* Free the propriety */
		drmModeFreeProperty(prop);
	}
	/* If we get here, we did not find a EDID */
	return (out->edid_error = LIBGAMMA_EDID_NOT_FOUND);
}


/**
 * Read information about a CRTC
 * 
 * @param   this    Instance of a data structure to fill with the information about the CRTC
 * @param   crtc    The state of the CRTC whose information should be read
 * @param   fields  OR:ed identifiers for the information about the CRTC that should be read
 * @return          Zero on success, -1 on error; on error refer to the error reports in `this`
 */
int
libgamma_linux_drm_get_crtc_information(struct libgamma_crtc_information *restrict this,
                                        struct libgamma_crtc_state *restrict crtc, unsigned long long fields)
{
#define _E(FIELD) ((fields & FIELD) ? LIBGAMMA_CRTC_INFO_NOT_SUPPORTED : 0)

	int e = 0, require_connector, free_edid, error;
	drmModeConnector *restrict connector;

	/* Wipe all error indicators */
	memset(this, 0, sizeof(*this));

	/* We need to free the EDID after us if it is not explicitly requested */
	free_edid = !(fields & LIBGAMMA_CRTC_INFO_EDID);

	/* Figure out whether we require the connector to get all information we want */
	require_connector = fields & (LIBGAMMA_CRTC_INFO_MACRO_ACTIVE | LIBGAMMA_CRTC_INFO_MACRO_CONNECTOR);

	/* If we are not interested in the connector or monitor, jump */
	if (!require_connector)
		goto cont;
	/* Find connector. */
	connector = find_connector(crtc, &error);
	if (!connector) {
		/* Store reported error in affected fields */
		e |= this->width_mm_error       = this->height_mm_error
		   = this->connector_type_error = this->subpixel_order_error
		   = this->active_error         = this->connector_name_error
		   = this->edid_error           = this->gamma_error
		   = this->width_mm_edid_error  = this->height_mm_edid_error = error;
		goto cont;
	}

	/* Read connector data and monitor data, excluding EDID */
	e |= read_connector_data(crtc, this, connector, fields);

	/* If we do not want any EDID information, jump */
	if (!(fields & LIBGAMMA_CRTC_INFO_MACRO_EDID))
		goto cont;
	/* If there is not monitor that report error in EDID related fields */
	if (this->active_error || !this->active) {
		e |= this->edid_error = this->gamma_error
		   = this->width_mm_edid_error = this->height_mm_edid_error
		   = LIBGAMMA_NOT_CONNECTED;
		goto cont;
	}
	/* Get EDID */
	e |= get_edid(crtc, this, connector);
	if (!this->edid) {
		this->gamma_error = this->width_mm_edid_error = this->height_mm_edid_error = this->edid_error;
		goto cont;
	}
	/* Parse EDID */
	if (fields & (LIBGAMMA_CRTC_INFO_MACRO_EDID ^ LIBGAMMA_CRTC_INFO_EDID))
		e |= libgamma_internal_parse_edid(this, fields);

cont:
	/* Get gamma ramp size */
	e |= (fields & LIBGAMMA_CRTC_INFO_GAMMA_SIZE) ? get_gamma_ramp_size(this, crtc) : 0;
	/* Store gamma ramp depth */
	this->gamma_depth = 16;
	/* DRM does not support quering gamma ramp support */
	e |= this->gamma_support_error = _E(LIBGAMMA_CRTC_INFO_GAMMA_SUPPORT);

	/* Free the EDID after us */
	if (free_edid) {
		free(this->edid);
		this->edid = NULL;
	}

	return e ? -1 : 0;

#undef _E
}