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