aboutsummaryrefslogblamecommitdiffstats
path: root/libgamma_linux_drm_get_crtc_information.c
blob: 683fa0e12f13063cecdc3c36b8b8e6ed7bfa7705 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
                                                         
                             











                                                                     
                                                                              

                                                        
                                                                             



                                                                         
                                                                          












                                                                                                      

                                                                                       


















                                                                                                                 
                                                       













                                                                                          
                                                                                                                    
 
                                                                             

























                                                                                                                   
                                                                                                              






















                                                                                                        
                                                                                                              































































                                                                                               
                                                                                                              
                                                                                              

                                               
                                                     
                      
                            































                                                                                                                                



                                                         
                                                                   




                                                                                























                                                                                                    
                                                                                                                                
 
                                                                             



























                                                                                                             
                                                                                                                        


























                                                                                               

                                                                                                             






                                                                           
                                       






























































                                                                                                              
/* 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, len;

	/* 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 */
		len = strlen(connector_name_base);
		if (len > SIZE_MAX / sizeof(char) - 12) {
			errno = ENOMEM;
			out->connector_name = NULL;
			return (out->connector_name_error = errno);
		} else {
			out->connector_name = malloc((len + 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(unsigned 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
}