aboutsummaryrefslogblamecommitdiffstats
path: root/libgamma_linux_drm_get_crtc_information.c
blob: 2927702a45674c32dd45a421e6a7c35de016bdc1 (plain) (tree)
1
2
                                                         
                             

































































































































































































































































































































































































                                                                                                                                
/* 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(libgamma_crtc_state_t *restrict this, int *restrict error)
{
	uint32_t crtc_id = (uint32_t)(size_t)this->data;
	libgamma_drm_card_data_t *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(libgamma_crtc_information_t *restrict out, const libgamma_crtc_state_t *restrict crtc)
{
	libgamma_drm_card_data_t *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(libgamma_crtc_information_t *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(libgamma_crtc_information_t *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(libgamma_crtc_state_t *restrict crtc, libgamma_crtc_information_t *restrict out,
                    const drmModeConnector *restrict connector, int32_t fields)
{
	const char *connector_name_base = NULL;
	libgamma_drm_card_data_t *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(libgamma_crtc_state_t *restrict crtc, libgamma_crtc_information_t *restrict out, drmModeConnector *connector)
{
	libgamma_drm_card_data_t *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(libgamma_crtc_information_t *restrict this,
                                        libgamma_crtc_state_t *restrict crtc, int32_t 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(libgamma_crtc_information_t));

	/* 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
}