#include #include #include #include #include #include "bits.h" #include "dmt.h" #include "edid.h" #include "log.h" /** * The size of an EDID block, defined in section 2.2. */ #define EDID_BLOCK_SIZE 128 /** * The size of an EDID standard timing, defined in section 3.9. */ #define EDID_STANDARD_TIMING_SIZE 2 /** * Fixed EDID header, defined in section 3.1. */ static const uint8_t header[] = { 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00 }; static void add_failure(struct di_edid *edid, const char fmt[], ...) { va_list args; va_start(args, fmt); _di_logger_va_add_failure(edid->logger, fmt, args); va_end(args); } static void add_failure_until(struct di_edid *edid, int revision, const char fmt[], ...) { va_list args; if (edid->revision > revision) { return; } va_start(args, fmt); _di_logger_va_add_failure(edid->logger, fmt, args); va_end(args); } static void parse_version_revision(const uint8_t data[static EDID_BLOCK_SIZE], int *version, int *revision) { *version = (int) data[0x12]; *revision = (int) data[0x13]; } static size_t parse_ext_count(const uint8_t data[static EDID_BLOCK_SIZE]) { return data[0x7E]; } static bool validate_block_checksum(const uint8_t data[static EDID_BLOCK_SIZE]) { uint8_t sum = 0; size_t i; for (i = 0; i < EDID_BLOCK_SIZE; i++) { sum += data[i]; } return sum == 0; } static void parse_vendor_product(struct di_edid *edid, const uint8_t data[static EDID_BLOCK_SIZE]) { struct di_edid_vendor_product *out = &edid->vendor_product; uint16_t man, raw_week, raw_year; int year = 0; /* The ASCII 3-letter manufacturer code is encoded in 5-bit codes. */ man = (uint16_t) ((data[0x08] << 8) | data[0x09]); out->manufacturer[0] = ((man >> 10) & 0x1F) + '@'; out->manufacturer[1] = ((man >> 5) & 0x1F) + '@'; out->manufacturer[2] = ((man >> 0) & 0x1F) + '@'; out->product = (uint16_t) (data[0x0A] | (data[0x0B] << 8)); out->serial = (uint32_t) (data[0x0C] | (data[0x0D] << 8) | (data[0x0E] << 16) | (data[0x0F] << 24)); raw_week = data[0x10]; raw_year = data[0x11]; if (raw_year >= 0x10 || edid->revision < 4) { year = data[0x11] + 1990; } else if (edid->revision == 4) { add_failure(edid, "Year set to reserved value."); } if (raw_week == 0xFF) { /* Special flag for model year */ out->model_year = year; } else { out->manufacture_year = year; if (raw_week > 54) { add_failure_until(edid, 4, "Invalid week %u of manufacture.", raw_week); } else if (raw_week > 0) { out->manufacture_week = raw_week; } } } static void parse_video_input_digital(struct di_edid *edid, uint8_t video_input) { uint8_t color_bit_depth, interface; struct di_edid_video_input_digital *digital = &edid->video_input_digital; if (edid->revision < 2) { if (get_bit_range(video_input, 6, 0) != 0) add_failure(edid, "Digital Video Interface Standard set to reserved value 0x%02x.", video_input); return; } if (edid->revision < 4) { if (get_bit_range(video_input, 6, 1) != 0) add_failure(edid, "Digital Video Interface Standard set to reserved value 0x%02x.", video_input); digital->dfp1 = has_bit(video_input, 0); return; } color_bit_depth = get_bit_range(video_input, 6, 4); if (color_bit_depth == 0x07) { /* Reserved */ add_failure_until(edid, 4, "Color Bit Depth set to reserved value."); } else if (color_bit_depth != 0) { digital->color_bit_depth = 2 * color_bit_depth + 4; } interface = get_bit_range(video_input, 3, 0); switch (interface) { case DI_EDID_VIDEO_INPUT_DIGITAL_UNDEFINED: case DI_EDID_VIDEO_INPUT_DIGITAL_DVI: case DI_EDID_VIDEO_INPUT_DIGITAL_HDMI_A: case DI_EDID_VIDEO_INPUT_DIGITAL_HDMI_B: case DI_EDID_VIDEO_INPUT_DIGITAL_MDDI: case DI_EDID_VIDEO_INPUT_DIGITAL_DISPLAYPORT: digital->interface = interface; break; default: add_failure_until(edid, 4, "Digital Video Interface Standard set to reserved value 0x%02x.", interface); digital->interface = DI_EDID_VIDEO_INPUT_DIGITAL_UNDEFINED; break; } } static void parse_video_input_analog(struct di_edid *edid, uint8_t video_input) { struct di_edid_video_input_analog *analog = &edid->video_input_analog; analog->signal_level_std = get_bit_range(video_input, 6, 5); analog->video_setup = has_bit(video_input, 4); analog->sync_separate = has_bit(video_input, 3); analog->sync_composite = has_bit(video_input, 2); analog->sync_on_green = has_bit(video_input, 1); analog->sync_serrations = has_bit(video_input, 0); } static void parse_basic_params_features(struct di_edid *edid, const uint8_t data[static EDID_BLOCK_SIZE]) { uint8_t video_input, width, height, features; struct di_edid_screen_size *screen_size = &edid->screen_size; video_input = data[0x14]; edid->is_digital = has_bit(video_input, 7); if (edid->is_digital) { parse_video_input_digital(edid, video_input); } else { parse_video_input_analog(edid, video_input); } /* v1.3 says screen size is undefined if either byte is zero, v1.4 says * screen size and aspect ratio are undefined if both bytes are zero and * encodes the aspect ratio if either byte is zero. */ width = data[0x15]; height = data[0x16]; if (width > 0 && height > 0) { screen_size->width_cm = width; screen_size->height_cm = height; } else if (edid->revision >= 4) { if (width > 0) { screen_size->landscape_aspect_ratio = ((float) width + 99) / 100; } else if (height > 0) { screen_size->portait_aspect_ratio = ((float) height + 99) / 100; } } if (data[0x17] != 0xFF) { edid->gamma = ((float) data[0x17] + 100) / 100; } else { edid->gamma = 0; } features = data[0x18]; edid->dpms.standby = has_bit(features, 7); edid->dpms.suspend = has_bit(features, 6); edid->dpms.off = has_bit(features, 5); if (edid->is_digital && edid->revision >= 4) { edid->color_encoding_formats.rgb444 = true; edid->color_encoding_formats.ycrcb444 = has_bit(features, 3); edid->color_encoding_formats.ycrcb422 = has_bit(features, 4); edid->display_color_type = DI_EDID_DISPLAY_COLOR_UNDEFINED; } else { edid->display_color_type = get_bit_range(features, 4, 3); } if (edid->revision >= 4) { edid->misc_features.has_preferred_timing = true; edid->misc_features.continuous_freq = has_bit(features, 0); edid->misc_features.preferred_timing_is_native = has_bit(features, 1); } else { edid->misc_features.default_gtf = has_bit(features, 0); edid->misc_features.has_preferred_timing = has_bit(features, 1); } edid->misc_features.srgb_is_primary = has_bit(features, 2); } static float decode_chromaticity_coord(uint8_t hi, uint8_t lo) { uint16_t raw; /* only 10 bits are used */ raw = (uint16_t) (hi << 2) | lo; return (float) raw / 1024; } static void parse_chromaticity_coords(struct di_edid *edid, const uint8_t data[static EDID_BLOCK_SIZE]) { uint8_t lo; bool all_set, any_set; struct di_edid_chromaticity_coords *coords; coords = &edid->chromaticity_coords; lo = data[0x19]; coords->red_x = decode_chromaticity_coord(data[0x1B], get_bit_range(lo, 7, 6)); coords->red_y = decode_chromaticity_coord(data[0x1C], get_bit_range(lo, 5, 4)); coords->green_x = decode_chromaticity_coord(data[0x1D], get_bit_range(lo, 3, 2)); coords->green_y = decode_chromaticity_coord(data[0x1E], get_bit_range(lo, 1, 0)); lo = data[0x1A]; coords->blue_x = decode_chromaticity_coord(data[0x1F], get_bit_range(lo, 7, 6)); coords->blue_y = decode_chromaticity_coord(data[0x20], get_bit_range(lo, 5, 4)); coords->white_x = decode_chromaticity_coord(data[0x21], get_bit_range(lo, 3, 2)); coords->white_y = decode_chromaticity_coord(data[0x22], get_bit_range(lo, 1, 0)); /* Either all primaries coords must be set, either none must be set */ any_set = coords->red_x != 0 || coords->red_y != 0 || coords->green_x != 0 || coords->green_y != 0 || coords->blue_x != 0 || coords->blue_y != 0; all_set = coords->red_x != 0 && coords->red_y != 0 && coords->green_x != 0 && coords->green_y != 0 && coords->blue_x != 0 && coords->blue_y != 0; if (any_set && !all_set) { add_failure(edid, "Some but not all primaries coordinates are unset."); } /* Both white-point coords must be set */ if (coords->white_x == 0 || coords->white_y == 0) { add_failure(edid, "White-point coordinates are unset."); } } static void parse_established_timings_i_ii(struct di_edid *edid, const uint8_t data[static EDID_BLOCK_SIZE]) { struct di_edid_established_timings_i_ii *timings = &edid->established_timings_i_ii; timings->has_720x400_70hz = has_bit(data[0x23], 7); timings->has_720x400_88hz = has_bit(data[0x23], 6); timings->has_640x480_60hz = has_bit(data[0x23], 5); timings->has_640x480_67hz = has_bit(data[0x23], 4); timings->has_640x480_72hz = has_bit(data[0x23], 3); timings->has_640x480_75hz = has_bit(data[0x23], 2); timings->has_800x600_56hz = has_bit(data[0x23], 1); timings->has_800x600_60hz = has_bit(data[0x23], 0); /* Established timings II */ timings->has_800x600_72hz = has_bit(data[0x24], 7); timings->has_800x600_75hz = has_bit(data[0x24], 6); timings->has_832x624_75hz = has_bit(data[0x24], 5); timings->has_1024x768_87hz_interlaced = has_bit(data[0x24], 4); timings->has_1024x768_60hz = has_bit(data[0x24], 3); timings->has_1024x768_70hz = has_bit(data[0x24], 2); timings->has_1024x768_75hz = has_bit(data[0x24], 1); timings->has_1280x1024_75hz = has_bit(data[0x24], 0); timings->has_1152x870_75hz = has_bit(data[0x25], 7); /* TODO: manufacturer specified timings in bits 6:0 */ } static bool parse_standard_timing(struct di_edid *edid, const uint8_t data[static EDID_STANDARD_TIMING_SIZE], struct di_edid_standard_timing **out) { struct di_edid_standard_timing *t; *out = NULL; if (data[0] == 0x01 && data[1] == 0x01) { /* Unused */ return true; } if (data[0] == 0x00) { add_failure_until(edid, 4, "Use 0x0101 as the invalid Standard Timings code, not 0x%02x%02x.", data[0], data[1]); return true; } t = calloc(1, sizeof(*t)); if (!t) { return false; } t->horiz_video = ((int32_t) data[0] + 31) * 8; t->aspect_ratio = get_bit_range(data[1], 7, 6); t->refresh_rate_hz = (int32_t) get_bit_range(data[1], 5, 0) + 60; *out = t; return true; } struct di_edid_detailed_timing_def_priv * _di_edid_parse_detailed_timing_def(const uint8_t data[static EDID_BYTE_DESCRIPTOR_SIZE]) { struct di_edid_detailed_timing_def_priv *priv; struct di_edid_detailed_timing_def *def; struct di_edid_detailed_timing_analog_composite *analog_composite; struct di_edid_detailed_timing_bipolar_analog_composite *bipolar_analog_composite; struct di_edid_detailed_timing_digital_composite *digital_composite; struct di_edid_detailed_timing_digital_separate *digital_separate; int raw; uint8_t flags, stereo_hi, stereo_lo; priv = calloc(1, sizeof(*priv)); if (!priv) { return NULL; } def = &priv->base; raw = (data[1] << 8) | data[0]; def->pixel_clock_hz = raw * 10 * 1000; def->horiz_video = (get_bit_range(data[4], 7, 4) << 8) | data[2]; def->horiz_blank = (get_bit_range(data[4], 3, 0) << 8) | data[3]; def->vert_video = (get_bit_range(data[7], 7, 4) << 8) | data[5]; def->vert_blank = (get_bit_range(data[7], 3, 0) << 8) | data[6]; def->horiz_front_porch = (get_bit_range(data[11], 7, 6) << 8) | data[8]; def->horiz_sync_pulse = (get_bit_range(data[11], 5, 4) << 8) | data[9]; def->vert_front_porch = (get_bit_range(data[11], 3, 2) << 4) | get_bit_range(data[10], 7, 4); def->vert_sync_pulse = (get_bit_range(data[11], 1, 0) << 4) | get_bit_range(data[10], 3, 0); def->horiz_image_mm = (get_bit_range(data[14], 7, 4) << 8) | data[12]; def->vert_image_mm = (get_bit_range(data[14], 3, 0) << 8) | data[13]; if ((def->horiz_image_mm == 16 && def->vert_image_mm == 9) || (def->horiz_image_mm == 4 && def->vert_image_mm == 3)) { /* Table 3.21 note 18.2: these are special cases and define the * aspect ratio rather than the size in mm. * TODO: expose these values */ def->horiz_image_mm = def->vert_image_mm = 0; } def->horiz_border = data[15]; def->vert_border = data[16]; flags = data[17]; def->interlaced = has_bit(flags, 7); stereo_hi = get_bit_range(flags, 6, 5); stereo_lo = get_bit_range(flags, 0, 0); if (stereo_hi == 0) { def->stereo = DI_EDID_DETAILED_TIMING_DEF_STEREO_NONE; } else { switch ((stereo_hi << 1) | stereo_lo) { case (1 << 1) | 0: def->stereo = DI_EDID_DETAILED_TIMING_DEF_STEREO_FIELD_SEQ_RIGHT; break; case (2 << 1) | 0: def->stereo = DI_EDID_DETAILED_TIMING_DEF_STEREO_FIELD_SEQ_LEFT; break; case (1 << 1) | 1: def->stereo = DI_EDID_DETAILED_TIMING_DEF_STEREO_2_WAY_INTERLEAVED_RIGHT; break; case (2 << 1) | 1: def->stereo = DI_EDID_DETAILED_TIMING_DEF_STEREO_2_WAY_INTERLEAVED_LEFT; break; case (3 << 1) | 0: def->stereo = DI_EDID_DETAILED_TIMING_DEF_STEREO_4_WAY_INTERLEAVED; break; case (3 << 1) | 1: def->stereo = DI_EDID_DETAILED_TIMING_DEF_STEREO_SIDE_BY_SIDE_INTERLEAVED; break; default: abort(); /* unreachable */ } } def->signal_type = get_bit_range(flags, 4, 3); switch (def->signal_type) { case DI_EDID_DETAILED_TIMING_DEF_SIGNAL_ANALOG_COMPOSITE: analog_composite = &priv->analog_composite; analog_composite->sync_serrations = has_bit(flags, 2); analog_composite->sync_on_green = has_bit(flags, 1); def->analog_composite = analog_composite; break; case DI_EDID_DETAILED_TIMING_DEF_SIGNAL_BIPOLAR_ANALOG_COMPOSITE: bipolar_analog_composite = &priv->bipolar_analog_composite; bipolar_analog_composite->sync_serrations = has_bit(flags, 2); bipolar_analog_composite->sync_on_green = has_bit(flags, 1); def->bipolar_analog_composite = bipolar_analog_composite; break; case DI_EDID_DETAILED_TIMING_DEF_SIGNAL_DIGITAL_COMPOSITE: digital_composite = &priv->digital_composite; digital_composite->sync_serrations = has_bit(flags, 2); digital_composite->sync_horiz_polarity = has_bit(flags, 1); def->digital_composite = digital_composite; break; case DI_EDID_DETAILED_TIMING_DEF_SIGNAL_DIGITAL_SEPARATE: digital_separate = &priv->digital_separate; digital_separate->sync_vert_polarity = has_bit(flags, 2); digital_separate->sync_horiz_polarity = has_bit(flags, 1); def->digital_separate = digital_separate; break; } return priv; } static bool decode_display_range_limits_offset(struct di_edid *edid, uint8_t flags, int *max_offset, int *min_offset) { switch (flags) { case 0x00: /* No offset */ break; case 0x02: *max_offset = 255; break; case 0x03: *max_offset = 255; *min_offset = 255; break; default: add_failure_until(edid, 4, "Range offset flags set to reserved value 0x%02x.", flags); return false; } return true; } static bool parse_display_range_limits(struct di_edid *edid, const uint8_t data[static EDID_BYTE_DESCRIPTOR_SIZE], struct di_edid_display_range_limits *out) { uint8_t offset_flags, vert_offset_flags, horiz_offset_flags; uint8_t support_flags; int max_vert_offset = 0, min_vert_offset = 0; int max_horiz_offset = 0, min_horiz_offset = 0; offset_flags = data[4]; if (edid->revision >= 4) { vert_offset_flags = get_bit_range(offset_flags, 1, 0); horiz_offset_flags = get_bit_range(offset_flags, 3, 2); if (!decode_display_range_limits_offset(edid, vert_offset_flags, &max_vert_offset, &min_vert_offset)) { return false; } if (!decode_display_range_limits_offset(edid, horiz_offset_flags, &max_horiz_offset, &min_horiz_offset)) { return false; } if (edid->revision <= 4 && get_bit_range(offset_flags, 7, 4) != 0) { add_failure(edid, "Display Range Limits: Bits 7:4 of the range offset flags are reserved."); } } else if (offset_flags != 0) { add_failure(edid, "Display Range Limits: Range offset flags are unsupported in EDID 1.3."); } if (edid->revision <= 4 && (data[5] == 0 || data[6] == 0 || data[7] == 0 || data[8] == 0)) { add_failure(edid, "Display Range Limits: Range limits set to reserved values."); return false; } out->min_vert_rate_hz = data[5] + min_vert_offset; out->max_vert_rate_hz = data[6] + max_vert_offset; out->min_horiz_rate_hz = (data[7] + min_horiz_offset) * 1000; out->max_horiz_rate_hz = (data[8] + max_horiz_offset) * 1000; if (out->min_vert_rate_hz > out->max_vert_rate_hz) { add_failure(edid, "Display Range Limits: Min vertical rate > max vertical rate."); return false; } if (out->min_horiz_rate_hz > out->max_horiz_rate_hz) { add_failure(edid, "Display Range Limits: Min horizontal freq > max horizontal freq."); return false; } out->max_pixel_clock_hz = (int32_t) data[9] * 10 * 1000 * 1000; if (edid->revision == 4 && out->max_pixel_clock_hz == 0) { add_failure(edid, "Display Range Limits: EDID 1.4 block does not set max dotclock."); } support_flags = data[10]; switch (support_flags) { case 0x00: /* For EDID 1.4 and later, always indicates support for default * GTF. For EDID 1.3 and earlier, a misc features bit indicates * support for default GTF. */ if (edid->revision >= 4 || edid->misc_features.default_gtf) { out->type = DI_EDID_DISPLAY_RANGE_LIMITS_DEFAULT_GTF; } else { out->type = DI_EDID_DISPLAY_RANGE_LIMITS_BARE; } break; case 0x01: if (edid->revision < 4) { /* Reserved */ add_failure(edid, "Display Range Limits: 'Bare Limits' is not allowed for EDID < 1.4."); return false; } out->type = DI_EDID_DISPLAY_RANGE_LIMITS_BARE; break; case 0x02: out->type = DI_EDID_DISPLAY_RANGE_LIMITS_SECONDARY_GTF; break; case 0x04: if (edid->revision < 4) { /* Reserved */ add_failure(edid, "Display Range Limits: 'CVT' is not allowed for EDID < 1.4."); return false; } out->type = DI_EDID_DISPLAY_RANGE_LIMITS_CVT; break; default: /* Reserved */ if (edid->revision <= 4) { add_failure(edid, "Display Range Limits: Unknown range class (0x%02x).", support_flags); return false; } out->type = DI_EDID_DISPLAY_RANGE_LIMITS_BARE; break; } /* Some types require the display to support continuous frequencies, but * this flag is only set for EDID 1.4 and later */ if (edid->revision >= 4 && !edid->misc_features.continuous_freq) { switch (out->type) { case DI_EDID_DISPLAY_RANGE_LIMITS_DEFAULT_GTF: case DI_EDID_DISPLAY_RANGE_LIMITS_SECONDARY_GTF: add_failure(edid, "Display Range Limits: GTF can't be combined with non-continuous frequencies."); return false; case DI_EDID_DISPLAY_RANGE_LIMITS_CVT: add_failure(edid, "Display Range Limits: CVT can't be combined with non-continuous frequencies."); return false; default: break; } } /* TODO: parse video timing data in bytes 11 to 17 */ return true; } static bool parse_standard_timings_descriptor(struct di_edid *edid, const uint8_t data[static EDID_BYTE_DESCRIPTOR_SIZE], struct di_edid_display_descriptor *desc) { struct di_edid_standard_timing *t; size_t i; const uint8_t *timing_data; for (i = 0; i < EDID_MAX_DESCRIPTOR_STANDARD_TIMING_COUNT; i++) { timing_data = &data[5 + i * EDID_STANDARD_TIMING_SIZE]; if (!parse_standard_timing(edid, timing_data, &t)) return false; if (t) desc->standard_timings[desc->standard_timings_len++] = t; } if (data[17] != 0x0A) add_failure_until(edid, 4, "Standard Timing Identifications: Last byte must be a line feed."); return true; } static bool parse_byte_descriptor(struct di_edid *edid, const uint8_t data[static EDID_BYTE_DESCRIPTOR_SIZE]) { struct di_edid_display_descriptor *desc; struct di_edid_detailed_timing_def_priv *detailed_timing_def; uint8_t tag; char *newline; if (data[0] || data[1]) { if (edid->display_descriptors_len > 0) { /* A detailed timing descriptor is not allowed after a * display descriptor per note 3 of table 3.20. */ add_failure(edid, "Invalid detailed timing descriptor ordering."); } detailed_timing_def = _di_edid_parse_detailed_timing_def(data); if (!detailed_timing_def) { return false; } edid->detailed_timing_defs[edid->detailed_timing_defs_len++] = detailed_timing_def; return true; } if (edid->revision >= 3 && edid->revision <= 4 && edid->detailed_timing_defs_len == 0) { /* Per section 3.10.1 */ add_failure(edid, "The first byte descriptor must contain the preferred timing."); } desc = calloc(1, sizeof(*desc)); if (!desc) { return false; } tag = data[3]; switch (tag) { case DI_EDID_DISPLAY_DESCRIPTOR_PRODUCT_SERIAL: case DI_EDID_DISPLAY_DESCRIPTOR_DATA_STRING: case DI_EDID_DISPLAY_DESCRIPTOR_PRODUCT_NAME: memcpy(desc->str, &data[5], 13); /* A newline (if any) indicates the end of the string. */ newline = strchr(desc->str, '\n'); if (newline) { newline[0] = '\0'; } break; case DI_EDID_DISPLAY_DESCRIPTOR_RANGE_LIMITS: if (!parse_display_range_limits(edid, data, &desc->range_limits)) { free(desc); return true; } break; case DI_EDID_DISPLAY_DESCRIPTOR_STD_TIMING_IDS: if (!parse_standard_timings_descriptor(edid, data, desc)) { free(desc); return false; } break; case DI_EDID_DISPLAY_DESCRIPTOR_COLOR_POINT: case DI_EDID_DISPLAY_DESCRIPTOR_DCM_DATA: case DI_EDID_DISPLAY_DESCRIPTOR_CVT_TIMING_CODES: case DI_EDID_DISPLAY_DESCRIPTOR_ESTABLISHED_TIMINGS_III: case DI_EDID_DISPLAY_DESCRIPTOR_DUMMY: break; /* Ignore */ default: free(desc); if (tag <= 0x0F) { /* Manufacturer-specific */ } else { add_failure_until(edid, 4, "Unknown Type 0x%02hhx.", tag); } return true; } desc->tag = tag; edid->display_descriptors[edid->display_descriptors_len++] = desc; return true; } static bool parse_ext(struct di_edid *edid, const uint8_t data[static EDID_BLOCK_SIZE]) { struct di_edid_ext *ext; uint8_t tag; struct di_logger logger; char section_name[64]; if (!validate_block_checksum(data)) { errno = EINVAL; return false; } ext = calloc(1, sizeof(*ext)); if (!ext) { return false; } tag = data[0x00]; switch (tag) { case DI_EDID_EXT_CEA: snprintf(section_name, sizeof(section_name), "Block %zu, CTA-861 Extension Block", edid->exts_len + 1); logger = (struct di_logger) { .f = edid->logger->f, .section = section_name, }; if (!_di_edid_cta_parse(&ext->cta, data, EDID_BLOCK_SIZE, &logger)) { free(ext); return false; } break; case DI_EDID_EXT_VTB: case DI_EDID_EXT_DI: case DI_EDID_EXT_LS: case DI_EDID_EXT_DPVL: case DI_EDID_EXT_BLOCK_MAP: case DI_EDID_EXT_VENDOR: /* Supported */ break; case DI_EDID_EXT_DISPLAYID: snprintf(section_name, sizeof(section_name), "Block %zu, DisplayID Extension Block", edid->exts_len + 1); logger = (struct di_logger) { .f = edid->logger->f, .section = section_name, }; if (!_di_displayid_parse(&ext->displayid, &data[1], EDID_BLOCK_SIZE - 2, &logger)) { free(ext); return false; } break; default: /* Unsupported */ free(ext); add_failure_until(edid, 4, "Unknown Extension Block."); return true; } ext->tag = tag; edid->exts[edid->exts_len++] = ext; return true; } struct di_edid * _di_edid_parse(const void *data, size_t size, FILE *failure_msg_file) { struct di_edid *edid; struct di_logger logger; int version, revision; size_t exts_len, i; const uint8_t *standard_timing_data, *byte_desc_data, *ext_data; struct di_edid_standard_timing *standard_timing; if (size < EDID_BLOCK_SIZE || size > EDID_MAX_BLOCK_COUNT * EDID_BLOCK_SIZE || size % EDID_BLOCK_SIZE != 0) { errno = EINVAL; return NULL; } if (memcmp(data, header, sizeof(header)) != 0) { errno = EINVAL; return NULL; } parse_version_revision(data, &version, &revision); if (version != 1) { /* Only EDID version 1 is supported -- as per section 2.1.7 * subsequent versions break the structure */ errno = ENOTSUP; return NULL; } if (!validate_block_checksum(data)) { errno = EINVAL; return NULL; } exts_len = size / EDID_BLOCK_SIZE - 1; if (exts_len != parse_ext_count(data)) { errno = EINVAL; return NULL; } edid = calloc(1, sizeof(*edid)); if (!edid) { return NULL; } logger = (struct di_logger) { .f = failure_msg_file, .section = "Block 0, Base EDID", }; edid->logger = &logger; edid->version = version; edid->revision = revision; parse_vendor_product(edid, data); parse_basic_params_features(edid, data); parse_chromaticity_coords(edid, data); parse_established_timings_i_ii(edid, data); for (i = 0; i < EDID_MAX_STANDARD_TIMING_COUNT; i++) { standard_timing_data = (const uint8_t *) data + 0x26 + i * EDID_STANDARD_TIMING_SIZE; if (!parse_standard_timing(edid, standard_timing_data, &standard_timing)) { _di_edid_destroy(edid); return NULL; } if (standard_timing) { edid->standard_timings[edid->standard_timings_len++] = standard_timing; } } for (i = 0; i < EDID_BYTE_DESCRIPTOR_COUNT; i++) { byte_desc_data = (const uint8_t *) data + 0x36 + i * EDID_BYTE_DESCRIPTOR_SIZE; if (!parse_byte_descriptor(edid, byte_desc_data)) { _di_edid_destroy(edid); return NULL; } } for (i = 0; i < exts_len; i++) { ext_data = (const uint8_t *) data + (i + 1) * EDID_BLOCK_SIZE; if (!parse_ext(edid, ext_data)) { _di_edid_destroy(edid); return NULL; } } edid->logger = NULL; return edid; } static void destroy_display_descriptor(struct di_edid_display_descriptor *desc) { size_t i; switch (desc->tag) { case DI_EDID_DISPLAY_DESCRIPTOR_STD_TIMING_IDS: for (i = 0; i < desc->standard_timings_len; i++) { free(desc->standard_timings[i]); } break; default: break; /* Nothing to do */ } free(desc); } void _di_edid_destroy(struct di_edid *edid) { size_t i; struct di_edid_ext *ext; for (i = 0; i < edid->standard_timings_len; i++) { free(edid->standard_timings[i]); } for (i = 0; i < edid->detailed_timing_defs_len; i++) { free(edid->detailed_timing_defs[i]); } for (i = 0; i < edid->display_descriptors_len; i++) { destroy_display_descriptor(edid->display_descriptors[i]); } for (i = 0; edid->exts[i] != NULL; i++) { ext = edid->exts[i]; switch (ext->tag) { case DI_EDID_EXT_CEA: _di_edid_cta_finish(&ext->cta); break; default: break; /* Nothing to do */ } free(ext); } free(edid); } int di_edid_get_version(const struct di_edid *edid) { return edid->version; } int di_edid_get_revision(const struct di_edid *edid) { return edid->revision; } const struct di_edid_vendor_product * di_edid_get_vendor_product(const struct di_edid *edid) { return &edid->vendor_product; } const struct di_edid_video_input_analog * di_edid_get_video_input_analog(const struct di_edid *edid) { return edid->is_digital ? NULL : &edid->video_input_analog; } const struct di_edid_video_input_digital * di_edid_get_video_input_digital(const struct di_edid *edid) { return edid->is_digital ? &edid->video_input_digital : NULL; } const struct di_edid_screen_size * di_edid_get_screen_size(const struct di_edid *edid) { return &edid->screen_size; } float di_edid_get_basic_gamma(const struct di_edid *edid) { return edid->gamma; } const struct di_edid_dpms * di_edid_get_dpms(const struct di_edid *edid) { return &edid->dpms; } enum di_edid_display_color_type di_edid_get_display_color_type(const struct di_edid *edid) { return edid->display_color_type; } const struct di_edid_color_encoding_formats * di_edid_get_color_encoding_formats(const struct di_edid *edid) { /* If color encoding formats are specified, RGB 4:4:4 is always * supported. */ return edid->color_encoding_formats.rgb444 ? &edid->color_encoding_formats : NULL; } const struct di_edid_misc_features * di_edid_get_misc_features(const struct di_edid *edid) { return &edid->misc_features; } const struct di_edid_chromaticity_coords * di_edid_get_chromaticity_coords(const struct di_edid *edid) { return &edid->chromaticity_coords; } const struct di_edid_established_timings_i_ii * di_edid_get_established_timings_i_ii(const struct di_edid *edid) { return &edid->established_timings_i_ii; } int32_t di_edid_standard_timing_get_vert_video(const struct di_edid_standard_timing *t) { switch (t->aspect_ratio) { case DI_EDID_STANDARD_TIMING_16_10: return t->horiz_video * 10 / 16; case DI_EDID_STANDARD_TIMING_4_3: return t->horiz_video * 3 / 4; case DI_EDID_STANDARD_TIMING_5_4: return t->horiz_video * 4 / 5; case DI_EDID_STANDARD_TIMING_16_9: return t->horiz_video * 9 / 16; } abort(); /* unreachable */ } uint8_t di_edid_standard_timing_get_dmt_id(const struct di_edid_standard_timing *t) { int32_t vert_video; size_t i; const struct di_dmt_timing *dmt; vert_video = di_edid_standard_timing_get_vert_video(t); for (i = 0; i < _di_dmt_timings_len; i++) { dmt = &_di_dmt_timings[i]; if (dmt->horiz_video == t->horiz_video && dmt->vert_video == vert_video && dmt->refresh_rate_hz == (float)t->refresh_rate_hz && dmt->edid_std_id != 0) { return dmt->dmt_id; } } return 0; } const struct di_edid_standard_timing *const * di_edid_get_standard_timings(const struct di_edid *edid) { return (const struct di_edid_standard_timing *const *) &edid->standard_timings; } const struct di_edid_detailed_timing_def *const * di_edid_get_detailed_timing_defs(const struct di_edid *edid) { return (const struct di_edid_detailed_timing_def *const *) &edid->detailed_timing_defs; } const struct di_edid_display_descriptor *const * di_edid_get_display_descriptors(const struct di_edid *edid) { return (const struct di_edid_display_descriptor *const *) &edid->display_descriptors; } enum di_edid_display_descriptor_tag di_edid_display_descriptor_get_tag(const struct di_edid_display_descriptor *desc) { return desc->tag; } const char * di_edid_display_descriptor_get_string(const struct di_edid_display_descriptor *desc) { switch (desc->tag) { case DI_EDID_DISPLAY_DESCRIPTOR_PRODUCT_SERIAL: case DI_EDID_DISPLAY_DESCRIPTOR_DATA_STRING: case DI_EDID_DISPLAY_DESCRIPTOR_PRODUCT_NAME: return desc->str; default: return NULL; } } const struct di_edid_display_range_limits * di_edid_display_descriptor_get_range_limits(const struct di_edid_display_descriptor *desc) { if (desc->tag != DI_EDID_DISPLAY_DESCRIPTOR_RANGE_LIMITS) { return NULL; } return &desc->range_limits; } const struct di_edid_standard_timing *const * di_edid_display_descriptor_get_standard_timings(const struct di_edid_display_descriptor *desc) { if (desc->tag != DI_EDID_DISPLAY_DESCRIPTOR_STD_TIMING_IDS) { return NULL; } return (const struct di_edid_standard_timing *const *) desc->standard_timings; } const struct di_edid_ext *const * di_edid_get_extensions(const struct di_edid *edid) { return (const struct di_edid_ext *const *) edid->exts; } enum di_edid_ext_tag di_edid_ext_get_tag(const struct di_edid_ext *ext) { return ext->tag; } const struct di_edid_cta * di_edid_ext_get_cta(const struct di_edid_ext *ext) { if (ext->tag != DI_EDID_EXT_CEA) { return NULL; } return &ext->cta; } const struct di_displayid * di_edid_ext_get_displayid(const struct di_edid_ext *ext) { if (ext->tag != DI_EDID_EXT_DISPLAYID) { return NULL; } return &ext->displayid; }