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