diff --git a/src/compositor-drm.c b/src/compositor-drm.c index da1ba79e..c8016cd3 100644 --- a/src/compositor-drm.c +++ b/src/compositor-drm.c @@ -25,6 +25,7 @@ #include #include +#include #include #include #include @@ -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; diff --git a/src/compositor.h b/src/compositor.h index 1e999a67..eb8ad82a 100644 --- a/src/compositor.h +++ b/src/compositor.h @@ -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;