| #include <assert.h> |
| #include <errno.h> |
| #include <stdbool.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #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 |
| /** |
| * The size of an EDID CVT timing code, defined in section 3.10.3.8. |
| */ |
| #define EDID_CVT_TIMING_CODE_SIZE 3 |
| |
| /** |
| * 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_priv *priv) |
| { |
| uint8_t offset_flags, vert_offset_flags, horiz_offset_flags; |
| uint8_t support_flags, preferred_aspect_ratio; |
| int max_vert_offset = 0, min_vert_offset = 0; |
| int max_horiz_offset = 0, min_horiz_offset = 0; |
| size_t i; |
| struct di_edid_display_range_limits *base; |
| struct di_edid_display_range_limits_secondary_gtf *secondary_gtf; |
| struct di_edid_display_range_limits_cvt *cvt; |
| |
| base = &priv->base; |
| |
| 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; |
| } |
| |
| base->min_vert_rate_hz = data[5] + min_vert_offset; |
| base->max_vert_rate_hz = data[6] + max_vert_offset; |
| base->min_horiz_rate_hz = (data[7] + min_horiz_offset) * 1000; |
| base->max_horiz_rate_hz = (data[8] + max_horiz_offset) * 1000; |
| |
| if (base->min_vert_rate_hz > base->max_vert_rate_hz) { |
| add_failure(edid, "Display Range Limits: Min vertical rate > max vertical rate."); |
| return false; |
| } |
| if (base->min_horiz_rate_hz > base->max_horiz_rate_hz) { |
| add_failure(edid, "Display Range Limits: Min horizontal freq > max horizontal freq."); |
| return false; |
| } |
| |
| base->max_pixel_clock_hz = (int64_t) data[9] * 10 * 1000 * 1000; |
| if (edid->revision == 4 && base->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) { |
| base->type = DI_EDID_DISPLAY_RANGE_LIMITS_DEFAULT_GTF; |
| } else { |
| base->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; |
| } |
| base->type = DI_EDID_DISPLAY_RANGE_LIMITS_BARE; |
| break; |
| case 0x02: |
| base->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; |
| } |
| base->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; |
| } |
| base->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 (base->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; |
| } |
| } |
| |
| switch (base->type) { |
| case DI_EDID_DISPLAY_RANGE_LIMITS_SECONDARY_GTF: |
| secondary_gtf = &priv->secondary_gtf; |
| |
| if (data[11] != 0) |
| add_failure(edid, |
| "Display Range Limits: Byte 11 is 0x%02x instead of 0x00.", |
| data[11]); |
| |
| secondary_gtf->start_freq_hz = data[12] * 2 * 1000; |
| secondary_gtf->c = (float) data[13] / 2; |
| secondary_gtf->m = (float) ((data[15] << 8) | data[14]); |
| secondary_gtf->k = (float) data[16]; |
| secondary_gtf->j = (float) data[17] / 2; |
| |
| base->secondary_gtf = secondary_gtf; |
| break; |
| case DI_EDID_DISPLAY_RANGE_LIMITS_CVT: |
| cvt = &priv->cvt; |
| |
| cvt->version = get_bit_range(data[11], 7, 4); |
| cvt->revision = get_bit_range(data[11], 3, 0); |
| |
| base->max_pixel_clock_hz -= get_bit_range(data[12], 7, 2) * 250 * 1000; |
| cvt->max_horiz_px = 8 * ((get_bit_range(data[12], 1, 0) << 8) | data[13]); |
| |
| cvt->supported_aspect_ratio = data[14]; |
| if (get_bit_range(data[14], 2, 0) != 0) |
| add_failure_until(edid, 4, |
| "Display Range Limits: Reserved bits of byte 14 are non-zero."); |
| |
| preferred_aspect_ratio = get_bit_range(data[15], 7, 5); |
| switch (preferred_aspect_ratio) { |
| case 0: |
| cvt->preferred_aspect_ratio = DI_EDID_CVT_ASPECT_RATIO_4_3; |
| break; |
| case 1: |
| cvt->preferred_aspect_ratio = DI_EDID_CVT_ASPECT_RATIO_16_9; |
| break; |
| case 2: |
| cvt->preferred_aspect_ratio = DI_EDID_CVT_ASPECT_RATIO_16_10; |
| break; |
| case 3: |
| cvt->preferred_aspect_ratio = DI_EDID_CVT_ASPECT_RATIO_5_4; |
| break; |
| case 4: |
| cvt->preferred_aspect_ratio = DI_EDID_CVT_ASPECT_RATIO_15_9; |
| break; |
| default: |
| /* Reserved */ |
| add_failure_until(edid, 4, |
| "Display Range Limits: Invalid preferred aspect ratio 0x%02x.", |
| preferred_aspect_ratio); |
| return false; |
| } |
| |
| cvt->standard_blanking = has_bit(data[15], 3); |
| cvt->reduced_blanking = has_bit(data[15], 4); |
| |
| if (get_bit_range(data[15], 2, 0) != 0) |
| add_failure_until(edid, 4, |
| "Display Range Limits: Reserved bits of byte 15 are non-zero."); |
| |
| cvt->supported_scaling = data[16]; |
| if (get_bit_range(data[16], 3, 0) != 0) |
| add_failure_until(edid, 4, |
| "Display Range Limits: Reserved bits of byte 16 are non-zero."); |
| |
| cvt->preferred_vert_refresh_hz = data[17]; |
| if (cvt->preferred_vert_refresh_hz == 0) { |
| add_failure_until(edid, 4, |
| "Display Range Limits: Preferred vertical refresh rate must be specified."); |
| return false; |
| } |
| |
| base->cvt = cvt; |
| break; |
| case DI_EDID_DISPLAY_RANGE_LIMITS_BARE: |
| case DI_EDID_DISPLAY_RANGE_LIMITS_DEFAULT_GTF: |
| if (data[11] != 0x0A) |
| add_failure(edid, |
| "Display Range Limits: Byte 11 is 0x%02x instead of 0x0a.", |
| data[11]); |
| for (i = 12; i < EDID_BYTE_DESCRIPTOR_SIZE; i++) { |
| if (data[i] != 0x20) { |
| add_failure(edid, |
| "Display Range Limits: Bytes 12-17 must be 0x20."); |
| break; |
| } |
| } |
| break; |
| } |
| |
| 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) { |
| assert(desc->standard_timings_len < EDID_MAX_DESCRIPTOR_STANDARD_TIMING_COUNT); |
| 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; |
| } |
| |
| /** |
| * Mapping table for established timings III. |
| * |
| * Contains one entry per bit, with the value set to the DMT ID. |
| */ |
| static const uint8_t established_timings_iii[] = { |
| /* 0x06 */ |
| 0x01, /* 640 x 350 @ 85 Hz */ |
| 0x02, /* 640 x 400 @ 85 Hz */ |
| 0x03, /* 720 x 400 @ 85 Hz */ |
| 0x07, /* 640 x 480 @ 85 Hz */ |
| 0x0e, /* 848 x 480 @ 60 Hz */ |
| 0x0c, /* 800 x 600 @ 85 Hz */ |
| 0x13, /* 1024 x 768 @ 85 Hz */ |
| 0x15, /* 1152 x 864 @ 75 Hz */ |
| /* 0x07 */ |
| 0x16, /* 1280 x 768 @ 60 Hz (RB) */ |
| 0x17, /* 1280 x 768 @ 60 Hz */ |
| 0x18, /* 1280 x 768 @ 75 Hz */ |
| 0x19, /* 1280 x 768 @ 85 Hz */ |
| 0x20, /* 1280 x 960 @ 60 Hz */ |
| 0x21, /* 1280 x 960 @ 85 Hz */ |
| 0x23, /* 1280 x 1024 @ 60 Hz */ |
| 0x25, /* 1280 x 1024 @ 85 Hz */ |
| /* 0x08 */ |
| 0x27, /* 1360 x 768 @ 60 Hz */ |
| 0x2e, /* 1440 x 900 @ 60 Hz (RB) */ |
| 0x2f, /* 1440 x 900 @ 60 Hz */ |
| 0x30, /* 1440 x 900 @ 75 Hz */ |
| 0x31, /* 1440 x 900 @ 85 Hz */ |
| 0x29, /* 1400 x 1050 @ 60 Hz (RB) */ |
| 0x2a, /* 1400 x 1050 @ 60 Hz */ |
| 0x2b, /* 1400 x 1050 @ 75 Hz */ |
| /* 0x09 */ |
| 0x2c, /* 1400 x 1050 @ 85 Hz */ |
| 0x39, /* 1680 x 1050 @ 60 Hz (RB) */ |
| 0x3a, /* 1680 x 1050 @ 60 Hz */ |
| 0x3b, /* 1680 x 1050 @ 75 Hz */ |
| 0x3c, /* 1680 x 1050 @ 85 Hz */ |
| 0x33, /* 1600 x 1200 @ 60 Hz */ |
| 0x34, /* 1600 x 1200 @ 65 Hz */ |
| 0x35, /* 1600 x 1200 @ 70 Hz */ |
| /* 0x0a */ |
| 0x36, /* 1600 x 1200 @ 75 Hz */ |
| 0x37, /* 1600 x 1200 @ 85 Hz */ |
| 0x3e, /* 1792 x 1344 @ 60 Hz */ |
| 0x3f, /* 1792 x 1344 @ 75 Hz */ |
| 0x41, /* 1856 x 1392 @ 60 Hz */ |
| 0x42, /* 1856 x 1392 @ 75 Hz */ |
| 0x44, /* 1920 x 1200 @ 60 Hz (RB) */ |
| 0x45, /* 1920 x 1200 @ 60 Hz */ |
| /* 0x0b */ |
| 0x46, /* 1920 x 1200 @ 75 Hz */ |
| 0x47, /* 1920 x 1200 @ 85 Hz */ |
| 0x49, /* 1920 x 1440 @ 60 Hz */ |
| 0x4a, /* 1920 x 1440 @ 75 Hz */ |
| }; |
| static_assert(EDID_MAX_DESCRIPTOR_ESTABLISHED_TIMING_III_COUNT |
| == sizeof(established_timings_iii) / sizeof(established_timings_iii[0]), |
| "Invalid number of established timings III in table"); |
| |
| static const struct di_dmt_timing * |
| get_dmt_timing(uint8_t dmt_id) |
| { |
| size_t i; |
| const struct di_dmt_timing *t; |
| |
| for (i = 0; i < _di_dmt_timings_len; i++) { |
| t = &_di_dmt_timings[i]; |
| if (t->dmt_id == dmt_id) |
| return t; |
| } |
| |
| return NULL; |
| } |
| |
| static void |
| parse_established_timings_iii_descriptor(struct di_edid *edid, |
| const uint8_t data[static EDID_BYTE_DESCRIPTOR_SIZE], |
| struct di_edid_display_descriptor *desc) |
| { |
| size_t i, offset, bit; |
| uint8_t dmt_id; |
| const struct di_dmt_timing *t; |
| bool has_zeroes; |
| |
| if (edid->revision < 4) |
| add_failure(edid, "Established timings III: Not allowed for EDID < 1.4."); |
| |
| for (i = 0; i < EDID_MAX_DESCRIPTOR_ESTABLISHED_TIMING_III_COUNT; i++) { |
| dmt_id = established_timings_iii[i]; |
| offset = 0x06 + i / 8; |
| bit = 7 - i % 8; |
| assert(offset < EDID_BYTE_DESCRIPTOR_SIZE); |
| if (has_bit(data[offset], bit)) { |
| t = get_dmt_timing(dmt_id); |
| assert(t != NULL); |
| desc->established_timings_iii[desc->established_timings_iii_len++] = t; |
| } |
| } |
| |
| has_zeroes = get_bit_range(data[11], 3, 0) == 0; |
| for (i = 12; i < EDID_BYTE_DESCRIPTOR_SIZE; i++) { |
| has_zeroes = has_zeroes && data[i] == 0; |
| } |
| if (!has_zeroes) { |
| add_failure_until(edid, 4, |
| "Established timings III: Reserved bits must be set to zero."); |
| } |
| } |
| |
| static bool |
| parse_color_point_descriptor(struct di_edid *edid, |
| const uint8_t data[static EDID_BYTE_DESCRIPTOR_SIZE], |
| struct di_edid_display_descriptor *desc) |
| { |
| struct di_edid_color_point *c; |
| |
| if (data[5] == 0) { |
| add_failure(edid, "White Point Index Number set to reserved value 0"); |
| } |
| |
| c = calloc(1, sizeof(*c)); |
| if (!c) { |
| return false; |
| } |
| |
| c->index = data[5]; |
| c->white_x = decode_chromaticity_coord(data[7], get_bit_range(data[6], 3, 2)); |
| c->white_y = decode_chromaticity_coord(data[8], get_bit_range(data[6], 1, 0)); |
| |
| if (data[9] != 0xFF) { |
| c->gamma = ((float) data[9] + 100) / 100; |
| } |
| |
| desc->color_points[desc->color_points_len++] = c; |
| if (data[10] == 0) { |
| return true; |
| } |
| |
| c = calloc(1, sizeof(*c)); |
| if (!c) { |
| return false; |
| } |
| |
| c->index = data[10]; |
| c->white_x = decode_chromaticity_coord(data[12], get_bit_range(data[11], 3, 2)); |
| c->white_y = decode_chromaticity_coord(data[13], get_bit_range(data[11], 1, 0)); |
| |
| if (data[14] != 0xFF) { |
| c->gamma = ((float) data[14] + 100) / 100; |
| } |
| |
| desc->color_points[desc->color_points_len++] = c; |
| return true; |
| } |
| |
| static void |
| parse_color_management_data_descriptor(struct di_edid *edid, |
| const uint8_t data[static EDID_BYTE_DESCRIPTOR_SIZE], |
| struct di_edid_display_descriptor *desc) |
| { |
| desc->dcm_data.version = data[5]; |
| |
| desc->dcm_data.red_a3 = (uint16_t)(data[6] | (data[7] << 8)) / 100.0f; |
| desc->dcm_data.red_a2 = (uint16_t)(data[8] | (data[9] << 8)) / 100.0f; |
| desc->dcm_data.green_a3 = (uint16_t)(data[10] | (data[11] << 8)) / 100.0f; |
| desc->dcm_data.green_a2 = (uint16_t)(data[12] | (data[13] << 8)) / 100.0f; |
| desc->dcm_data.blue_a3 = (uint16_t)(data[14] | (data[15] << 8)) / 100.0f; |
| desc->dcm_data.blue_a2 = (uint16_t)(data[16] | (data[17] << 8)) / 100.0f; |
| |
| if (desc->dcm_data.version != 3) { |
| add_failure_until(edid, 4, |
| "Color Management Data version must be 3"); |
| } |
| } |
| |
| static bool |
| is_cvt_timing_code_preferred_vrate_supported(const struct di_edid_cvt_timing_code *t) |
| { |
| switch (t->preferred_vertical_rate) { |
| case DI_EDID_CVT_TIMING_CODE_PREFERRED_VRATE_50HZ: |
| return t->supports_50hz_sb; |
| case DI_EDID_CVT_TIMING_CODE_PREFERRED_VRATE_60HZ: |
| return t->supports_60hz_sb || t->supports_60hz_rb; |
| case DI_EDID_CVT_TIMING_CODE_PREFERRED_VRATE_75HZ: |
| return t->supports_75hz_sb; |
| case DI_EDID_CVT_TIMING_CODE_PREFERRED_VRATE_85HZ: |
| return t->supports_85hz_sb; |
| } |
| abort(); /* unreachable */ |
| } |
| |
| static bool |
| parse_cvt_timing_code(struct di_edid *edid, |
| const uint8_t data[static EDID_CVT_TIMING_CODE_SIZE], |
| struct di_edid_cvt_timing_code **out, |
| bool first) |
| { |
| struct di_edid_cvt_timing_code *t; |
| int32_t raw; |
| |
| *out = NULL; |
| |
| if (!first && data[0] == 0 && data[1] == 0 && data[2] == 0) { |
| /* Unused */ |
| return true; |
| } |
| if (data[0] == 0) { |
| add_failure(edid, |
| "CVT byte 0 is 0, which is a reserved value."); |
| } |
| |
| t = calloc(1, sizeof(*t)); |
| if (!t) { |
| return false; |
| } |
| |
| raw = (int32_t)(data[0] | (get_bit_range(data[1], 7, 4) << 8)); |
| t->addressable_lines_per_field = (raw + 1) * 2; |
| t->aspect_ratio = get_bit_range(data[1], 3, 2); |
| |
| if (get_bit_range(data[1], 1, 0) != 0) { |
| add_failure(edid, |
| "Reserved bits of CVT byte 1 are non-zero."); |
| } |
| |
| t->supports_50hz_sb = has_bit(data[2], 4); |
| t->supports_60hz_sb = has_bit(data[2], 3); |
| t->supports_75hz_sb = has_bit(data[2], 2); |
| t->supports_85hz_sb = has_bit(data[2], 1); |
| t->supports_60hz_rb = has_bit(data[2], 0); |
| |
| if (get_bit_range(data[2], 4, 0) == 0) { |
| add_failure(edid, |
| "CVT byte 2 does not support any vertical rates."); |
| } |
| |
| t->preferred_vertical_rate = get_bit_range(data[2], 6, 5); |
| |
| if (has_bit(data[2], 7) != 0) { |
| add_failure(edid, |
| "Reserved bit of CVT byte 2 is non-zero."); |
| } |
| |
| if (!is_cvt_timing_code_preferred_vrate_supported(t)) |
| add_failure(edid, "The preferred CVT Vertical Rate is not supported."); |
| |
| *out = t; |
| return true; |
| } |
| |
| static bool |
| parse_cvt_timing_codes_descriptor(struct di_edid *edid, |
| const uint8_t data[static EDID_BYTE_DESCRIPTOR_SIZE], |
| struct di_edid_display_descriptor *desc) |
| { |
| struct di_edid_cvt_timing_code *t; |
| size_t i; |
| const uint8_t *timing_data; |
| |
| if (data[5] != 1) { |
| add_failure_until(edid, 4, "Invalid version number %u.", |
| data[5]); |
| } |
| |
| for (i = 0; i < EDID_MAX_DESCRIPTOR_CVT_TIMING_CODES_COUNT; i++) { |
| timing_data = &data[6 + i * EDID_CVT_TIMING_CODE_SIZE]; |
| if (!parse_cvt_timing_code(edid, timing_data, &t, !i)) |
| return false; |
| if (t) { |
| assert(desc->cvt_timing_codes_len < EDID_MAX_DESCRIPTOR_CVT_TIMING_CODES_COUNT); |
| desc->cvt_timing_codes[desc->cvt_timing_codes_len++] = t; |
| } |
| } |
| |
| 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; |
| } |
| |
| assert(edid->detailed_timing_defs_len < EDID_BYTE_DESCRIPTOR_COUNT); |
| 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_ESTABLISHED_TIMINGS_III: |
| parse_established_timings_iii_descriptor(edid, data, desc); |
| break; |
| case DI_EDID_DISPLAY_DESCRIPTOR_COLOR_POINT: |
| if (!parse_color_point_descriptor(edid, data, desc)) { |
| free(desc); |
| return false; |
| } |
| break; |
| case DI_EDID_DISPLAY_DESCRIPTOR_DCM_DATA: |
| parse_color_management_data_descriptor(edid, data, desc); |
| break; |
| case DI_EDID_DISPLAY_DESCRIPTOR_CVT_TIMING_CODES: |
| if (!parse_cvt_timing_codes_descriptor(edid, data, desc)) { |
| free(desc); |
| return false; |
| } |
| break; |
| 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; |
| assert(edid->display_descriptors_len < EDID_BYTE_DESCRIPTOR_COUNT); |
| 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; |
| assert(edid->exts_len < EDID_MAX_BLOCK_COUNT - 1); |
| 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) { |
| assert(edid->standard_timings_len < EDID_MAX_STANDARD_TIMING_COUNT); |
| 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; |
| case DI_EDID_DISPLAY_DESCRIPTOR_COLOR_POINT: |
| for (i = 0; i < desc->color_points_len; i++) { |
| free(desc->color_points[i]); |
| } |
| break; |
| case DI_EDID_DISPLAY_DESCRIPTOR_CVT_TIMING_CODES: |
| for (i = 0; i < desc->cvt_timing_codes_len; i++) { |
| free(desc->cvt_timing_codes[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; |
| case DI_EDID_EXT_DISPLAYID: |
| _di_displayid_finish(&ext->displayid); |
| 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 */ |
| } |
| |
| const struct di_dmt_timing * |
| di_edid_standard_timing_get_dmt(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; |
| } |
| } |
| |
| 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.base; |
| } |
| |
| 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_color_point *const * |
| di_edid_display_descriptor_get_color_points(const struct di_edid_display_descriptor *desc) |
| { |
| if (desc->tag != DI_EDID_DISPLAY_DESCRIPTOR_COLOR_POINT) { |
| return NULL; |
| } |
| return (const struct di_edid_color_point *const *) desc->color_points; |
| } |
| |
| const struct di_dmt_timing *const * |
| di_edid_display_descriptor_get_established_timings_iii(const struct di_edid_display_descriptor *desc) |
| { |
| if (desc->tag != DI_EDID_DISPLAY_DESCRIPTOR_ESTABLISHED_TIMINGS_III) { |
| return NULL; |
| } |
| return desc->established_timings_iii; |
| } |
| |
| const struct di_edid_color_management_data * |
| di_edid_display_descriptor_get_color_management_data(const struct di_edid_display_descriptor *desc) |
| { |
| if (desc->tag != DI_EDID_DISPLAY_DESCRIPTOR_DCM_DATA) { |
| return NULL; |
| } |
| return &desc->dcm_data; |
| } |
| |
| const struct di_edid_cvt_timing_code *const * |
| di_edid_display_descriptor_get_cvt_timing_codes(const struct di_edid_display_descriptor *desc) |
| { |
| if (desc->tag != DI_EDID_DISPLAY_DESCRIPTOR_CVT_TIMING_CODES) { |
| return NULL; |
| } |
| return (const struct di_edid_cvt_timing_code *const *) desc->cvt_timing_codes; |
| } |
| |
| 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; |
| } |