Extract and parse the EDID when outputs are added

At the moment we're only extracting interesting strings. We have to be quite
careful parsing the EDID data, as vendors like to do insane things.

The original EDID parsing code was written by me for gnome-color-manager.
Richard Hughes 12 years ago committed by Kristian Høgsberg
parent f12a094ade
commit 2b2092adb4
  1. 149
      src/compositor-drm.c
  2. 2
      src/compositor.h

@ -25,6 +25,7 @@
#include <errno.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
@ -131,6 +132,13 @@ struct drm_fb {
void *map;
};
struct drm_edid {
char eisa_id[13];
char monitor_name[13];
char pnp_id[5];
char serial_number[13];
};
struct drm_output {
struct weston_output base;
@ -139,6 +147,7 @@ struct drm_output {
int pipe;
uint32_t connector_id;
drmModeCrtcPtr original_crtc;
struct drm_edid edid;
int vblank_pending;
int page_flip_pending;
@ -1486,6 +1495,143 @@ drm_output_fini_pixman(struct drm_output *output)
}
}
static void
edid_parse_string(const uint8_t *data, char text[])
{
int i;
int replaced = 0;
/* this is always 12 bytes, but we can't guarantee it's null
* terminated or not junk. */
strncpy(text, (const char *) data, 12);
/* remove insane chars */
for (i = 0; text[i] != '\0'; i++) {
if (text[i] == '\n' ||
text[i] == '\r') {
text[i] = '\0';
break;
}
}
/* ensure string is printable */
for (i = 0; text[i] != '\0'; i++) {
if (!isprint(text[i])) {
text[i] = '-';
replaced++;
}
}
/* if the string is random junk, ignore the string */
if (replaced > 4)
text[0] = '\0';
}
#define EDID_DESCRIPTOR_ALPHANUMERIC_DATA_STRING 0xfe
#define EDID_DESCRIPTOR_DISPLAY_PRODUCT_NAME 0xfc
#define EDID_DESCRIPTOR_DISPLAY_PRODUCT_SERIAL_NUMBER 0xff
#define EDID_OFFSET_DATA_BLOCKS 0x36
#define EDID_OFFSET_LAST_BLOCK 0x6c
#define EDID_OFFSET_PNPID 0x08
#define EDID_OFFSET_SERIAL 0x0c
static int
edid_parse(struct drm_edid *edid, const uint8_t *data, size_t length)
{
int i;
uint32_t serial_number;
/* check header */
if (length < 128)
return -1;
if (data[0] != 0x00 || data[1] != 0xff)
return -1;
/* decode the PNP ID from three 5 bit words packed into 2 bytes
* /--08--\/--09--\
* 7654321076543210
* |\---/\---/\---/
* R C1 C2 C3 */
edid->pnp_id[0] = 'A' + ((data[EDID_OFFSET_PNPID + 0] & 0x7c) / 4) - 1;
edid->pnp_id[1] = 'A' + ((data[EDID_OFFSET_PNPID + 0] & 0x3) * 8) + ((data[EDID_OFFSET_PNPID + 1] & 0xe0) / 32) - 1;
edid->pnp_id[2] = 'A' + (data[EDID_OFFSET_PNPID + 1] & 0x1f) - 1;
edid->pnp_id[3] = '\0';
/* maybe there isn't a ASCII serial number descriptor, so use this instead */
serial_number = (uint32_t) data[EDID_OFFSET_SERIAL + 0];
serial_number += (uint32_t) data[EDID_OFFSET_SERIAL + 1] * 0x100;
serial_number += (uint32_t) data[EDID_OFFSET_SERIAL + 2] * 0x10000;
serial_number += (uint32_t) data[EDID_OFFSET_SERIAL + 3] * 0x1000000;
if (serial_number > 0)
sprintf(edid->serial_number, "%lu", (unsigned long) serial_number);
/* parse EDID data */
for (i = EDID_OFFSET_DATA_BLOCKS;
i <= EDID_OFFSET_LAST_BLOCK;
i += 18) {
/* ignore pixel clock data */
if (data[i] != 0)
continue;
if (data[i+2] != 0)
continue;
/* any useful blocks? */
if (data[i+3] == EDID_DESCRIPTOR_DISPLAY_PRODUCT_NAME) {
edid_parse_string(&data[i+5],
edid->monitor_name);
} else if (data[i+3] == EDID_DESCRIPTOR_DISPLAY_PRODUCT_SERIAL_NUMBER) {
edid_parse_string(&data[i+5],
edid->serial_number);
} else if (data[i+3] == EDID_DESCRIPTOR_ALPHANUMERIC_DATA_STRING) {
edid_parse_string(&data[i+5],
edid->eisa_id);
}
}
return 0;
}
static void
find_and_parse_output_edid(struct drm_compositor *ec,
struct drm_output *output,
drmModeConnector *connector)
{
drmModePropertyBlobPtr edid_blob = NULL;
drmModePropertyPtr property;
int i;
int rc;
for (i = 0; i < connector->count_props && !edid_blob; i++) {
property = drmModeGetProperty(ec->drm.fd, connector->props[i]);
if (!property)
continue;
if ((property->flags & DRM_MODE_PROP_BLOB) &&
!strcmp(property->name, "EDID")) {
edid_blob = drmModeGetPropertyBlob(ec->drm.fd,
connector->prop_values[i]);
}
drmModeFreeProperty(property);
}
if (!edid_blob)
return;
rc = edid_parse(&output->edid,
edid_blob->data,
edid_blob->length);
if (!rc) {
weston_log("EDID data '%s', '%s', '%s'\n",
output->edid.pnp_id,
output->edid.monitor_name,
output->edid.serial_number);
if (output->edid.pnp_id[0] != '\0')
output->base.make = output->edid.pnp_id;
if (output->edid.monitor_name[0] != '\0')
output->base.model = output->edid.monitor_name;
if (output->edid.serial_number[0] != '\0')
output->base.serial_number = output->edid.serial_number;
}
drmModeFreePropertyBlob(edid_blob);
}
static int
create_output_for_connector(struct drm_compositor *ec,
drmModeRes *resources,
@ -1517,6 +1663,7 @@ create_output_for_connector(struct drm_compositor *ec,
output->base.subpixel = drm_subpixel_to_wayland(connector->subpixel);
output->base.make = "unknown";
output->base.model = "unknown";
output->base.serial_number = "unknown";
wl_list_init(&output->base.mode_list);
if (connector->connector_type < ARRAY_LENGTH(connector_type_names))
@ -1642,6 +1789,8 @@ create_output_for_connector(struct drm_compositor *ec,
wl_list_insert(ec->base.output_list.prev, &output->base.link);
find_and_parse_output_edid(ec, output, connector);
output->base.origin = output->base.current;
output->base.start_repaint_loop = drm_output_start_repaint_loop;
output->base.repaint = drm_output_repaint;

@ -178,7 +178,7 @@ struct weston_output {
uint32_t frame_time;
int disable_planes;
char *make, *model;
char *make, *model, *serial_number;
uint32_t subpixel;
uint32_t transform;

Loading…
Cancel
Save