| #include <assert.h> |
| #include <errno.h> |
| #include <inttypes.h> |
| #include <math.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include "bits.h" |
| #include "cta.h" |
| #include "log.h" |
| #include "edid.h" |
| |
| /** |
| * Number of bytes in the CTA header (tag + revision + DTD offset + flags). |
| */ |
| #define CTA_HEADER_SIZE 4 |
| /** |
| * Exclusive upper bound for the detailed timing definitions in the CTA block. |
| */ |
| #define CTA_DTD_END 127 |
| /** |
| * Number of bytes in a CTA short audio descriptor. |
| */ |
| #define CTA_SAD_SIZE 3 |
| |
| const struct di_cta_video_format * |
| di_cta_video_format_from_vic(uint8_t vic) |
| { |
| if (vic > _di_cta_video_formats_len || |
| _di_cta_video_formats[vic].vic == 0) |
| return NULL; |
| return &_di_cta_video_formats[vic]; |
| } |
| |
| static void |
| add_failure(struct di_edid_cta *cta, const char fmt[], ...) |
| { |
| va_list args; |
| |
| va_start(args, fmt); |
| _di_logger_va_add_failure(cta->logger, fmt, args); |
| va_end(args); |
| } |
| |
| static void |
| add_failure_until(struct di_edid_cta *cta, int revision, const char fmt[], ...) |
| { |
| va_list args; |
| |
| if (cta->revision > revision) { |
| return; |
| } |
| |
| va_start(args, fmt); |
| _di_logger_va_add_failure(cta->logger, fmt, args); |
| va_end(args); |
| } |
| |
| static struct di_cta_svd * |
| parse_svd(struct di_edid_cta *cta, uint8_t raw, const char *prefix) |
| { |
| struct di_cta_svd svd, *svd_ptr; |
| |
| if (raw == 0 || raw == 128 || raw >= 254) { |
| /* Reserved */ |
| add_failure_until(cta, 3, |
| "%s: Unknown VIC %" PRIu8 ".", |
| prefix, |
| raw); |
| return NULL; |
| } else if (raw <= 127 || raw >= 193) { |
| svd = (struct di_cta_svd) { |
| .vic = raw, |
| }; |
| } else { |
| svd = (struct di_cta_svd) { |
| .vic = get_bit_range(raw, 6, 0), |
| .native = true, |
| }; |
| } |
| |
| svd_ptr = calloc(1, sizeof(*svd_ptr)); |
| if (!svd_ptr) |
| return NULL; |
| *svd_ptr = svd; |
| return svd_ptr; |
| } |
| |
| static bool |
| parse_video_block(struct di_edid_cta *cta, struct di_cta_video_block *video, |
| const uint8_t *data, size_t size) |
| { |
| size_t i; |
| struct di_cta_svd *svd; |
| |
| if (size == 0) |
| add_failure(cta, "Video Data Block: Empty Data Block"); |
| |
| for (i = 0; i < size; i++) { |
| svd = parse_svd(cta, data[i], "Video Data Block"); |
| if (!svd) |
| continue; |
| assert(video->svds_len < EDID_CTA_MAX_VIDEO_BLOCK_ENTRIES); |
| video->svds[video->svds_len++] = svd; |
| } |
| |
| return true; |
| } |
| |
| static bool |
| parse_vendor_hdmi_forum_block(struct di_edid_cta *cta, |
| struct di_cta_vendor_hdmi_forum_block_priv *priv, |
| const uint8_t *data, size_t size) |
| { |
| const ssize_t offset = -1; /* Spec gives offset relative to header */ |
| const char block_name[] = "Vendor-Specific Data Block (HDMI Forum), OUI C4-5D-D8"; |
| struct di_cta_vendor_hdmi_forum_block *block = &priv->base; |
| uint8_t max_frl_rate; |
| |
| /* TODO: check size */ |
| |
| block->version = data[4 + offset]; |
| if (block->version != 1) { |
| add_failure(cta, "%s: Unsupported version %d.", block_name, block->version); |
| return false; |
| } |
| |
| block->max_tmds_char_rate_mhz = 5 * data[5 + offset]; |
| if (block->max_tmds_char_rate_mhz > 0 && block->max_tmds_char_rate_mhz <= 340) { |
| add_failure(cta, "%s: Max TMDS rate is > 0 and <= 340.", block_name); |
| block->max_tmds_char_rate_mhz = 0; |
| } |
| |
| block->supports_3d_osd_disparity = has_bit(data[6 + offset], 0); |
| block->supports_3d_dial_view = has_bit(data[6 + offset], 1); |
| block->supports_3d_independent_view = has_bit(data[6 + offset], 2); |
| block->supports_lte_340mcsc_scramble = has_bit(data[6 + offset], 3); |
| block->supports_ccbpci = has_bit(data[6 + offset], 4); |
| block->supports_scdc_read_request = has_bit(data[6 + offset], 6); |
| block->supports_scdc = has_bit(data[6 + offset], 7); |
| if (has_bit(data[6 + offset], 5)) |
| add_failure(cta, "%s: Bit 5 of byte 6 is reserved.", block_name); |
| |
| block->supports_dc_30bit_420 = has_bit(data[7 + offset], 0); |
| block->supports_dc_36bit_420 = has_bit(data[7 + offset], 1); |
| block->supports_dc_48bit_420 = has_bit(data[7 + offset], 2); |
| if (has_bit(data[7 + offset], 3)) |
| add_failure(cta, "%s: Bit 3 of byte 7 is reserved.", block_name); |
| |
| max_frl_rate = get_bit_range(data[7 + offset], 7, 4); |
| if (max_frl_rate != 0) { |
| block->frl = &priv->frl; |
| priv->frl.supports_3gbps_3lanes = max_frl_rate >= 1; |
| priv->frl.supports_6gbps_3lanes = max_frl_rate >= 2; |
| priv->frl.supports_6gbps_4lanes = max_frl_rate >= 3; |
| priv->frl.supports_8gbps_4lanes = max_frl_rate >= 4; |
| priv->frl.supports_10gbps_4lanes = max_frl_rate >= 5; |
| priv->frl.supports_12gbps_4lanes = max_frl_rate >= 6; |
| if (max_frl_rate >= 7) |
| add_failure(cta, "%s: Unknown Max Fixed Rate Link (0x%02x).", block_name, max_frl_rate); |
| if (max_frl_rate == 1 && block->max_tmds_char_rate_mhz < 300) |
| add_failure(cta, "%s: Max Fixed Rate Link is 1, but Max TMDS rate < 300.", block_name); |
| if (max_frl_rate >= 2 && block->max_tmds_char_rate_mhz != 600) |
| add_failure(cta, "%s: Max Fixed Rate Link is >= 2, but Max TMDS rate != 600.", block_name); |
| } |
| |
| block->supports_fapa_start_location = has_bit(data[8 + offset], 0); |
| block->supports_allm = has_bit(data[8 + offset], 1); |
| block->supports_fva = has_bit(data[8 + offset], 2); |
| block->supports_cnmvrr = has_bit(data[8 + offset], 3); |
| block->supports_cinema_vrr = has_bit(data[8 + offset], 4); |
| block->m_delta = has_bit(data[8 + offset], 5); |
| if (get_bit_range(data[8 + offset], 7, 6) != 0) |
| add_failure(cta, "%s: Bits 6 and 7 of byte 8 are reserved.", block_name); |
| |
| block->vrr_min_hz = get_bit_range(data[9 + offset], 5, 0); |
| block->vrr_max_hz = (get_bit_range(data[9 + offset], 7, 6) << 8) | data[10 + offset]; |
| |
| if (has_bit(data[11 + offset], 7)) { |
| block->dsc = &priv->dsc; |
| priv->dsc.supports_10bpc = has_bit(data[11 + offset], 0); |
| priv->dsc.supports_12bpc = has_bit(data[11 + offset], 1); |
| priv->dsc.supports_all_bpc = has_bit(data[11 + offset], 3); |
| priv->dsc.supports_native_420 = has_bit(data[11 + offset], 6); |
| if (has_bit(data[11 + offset], 2)) |
| add_failure(cta, "%s: DSC_16bpc bit is reserved.", block_name); |
| if (get_bit_range(data[11 + offset], 5, 4) != 0) |
| add_failure(cta, "%s: Bits 4 and 5 of byte 11 are reserved.", block_name); |
| priv->dsc.max_slices = get_bit_range(data[12 + offset], 3, 0); |
| priv->dsc.max_frl_rate_gbps = get_bit_range(data[12 + offset], 7, 4); |
| priv->dsc.max_total_chunk_bytes = get_bit_range(data[13 + offset], 5, 0); |
| if (get_bit_range(data[13 + offset], 7, 6) != 0) |
| add_failure(cta, "%s: Bits 6 and 7 of byte 13 are reserved.", block_name); |
| } |
| |
| /* TODO: all other bytes are reserved */ |
| |
| return true; |
| } |
| |
| static bool |
| parse_ycbcr420_block(struct di_edid_cta *cta, |
| struct di_cta_video_block *ycbcr420, |
| const uint8_t *data, size_t size) |
| { |
| size_t i; |
| struct di_cta_svd *svd; |
| |
| if (size == 0) |
| add_failure(cta, "YCbCr 4:2:0 Video Data Block: Empty Data Block"); |
| |
| for (i = 0; i < size; i++) { |
| svd = parse_svd(cta, data[i], "YCbCr 4:2:0 Video Data Block"); |
| if (!svd) |
| continue; |
| assert(ycbcr420->svds_len < EDID_CTA_MAX_VIDEO_BLOCK_ENTRIES); |
| ycbcr420->svds[ycbcr420->svds_len++] = svd; |
| } |
| |
| return true; |
| } |
| |
| static bool |
| parse_sad_format(struct di_edid_cta *cta, const uint8_t data[static CTA_SAD_SIZE], |
| enum di_cta_audio_format *format) |
| { |
| uint8_t code, code_ext; |
| |
| code = get_bit_range(data[0], 6, 3); |
| switch (code) { |
| case 0x0: |
| add_failure_until(cta, 3, |
| "Audio Data Block: Audio Format Code 0x00 is reserved."); |
| return false; |
| case 0x1: |
| *format = DI_CTA_AUDIO_FORMAT_LPCM; |
| break; |
| case 0x2: |
| *format = DI_CTA_AUDIO_FORMAT_AC3; |
| break; |
| case 0x3: |
| *format = DI_CTA_AUDIO_FORMAT_MPEG1; |
| break; |
| case 0x4: |
| *format = DI_CTA_AUDIO_FORMAT_MP3; |
| break; |
| case 0x5: |
| *format = DI_CTA_AUDIO_FORMAT_MPEG2; |
| break; |
| case 0x6: |
| *format = DI_CTA_AUDIO_FORMAT_AAC_LC; |
| break; |
| case 0x7: |
| *format = DI_CTA_AUDIO_FORMAT_DTS; |
| break; |
| case 0x8: |
| *format = DI_CTA_AUDIO_FORMAT_ATRAC; |
| break; |
| case 0x9: |
| *format = DI_CTA_AUDIO_FORMAT_ONE_BIT_AUDIO; |
| break; |
| case 0xA: |
| *format = DI_CTA_AUDIO_FORMAT_ENHANCED_AC3; |
| break; |
| case 0xB: |
| *format = DI_CTA_AUDIO_FORMAT_DTS_HD; |
| break; |
| case 0xC: |
| *format = DI_CTA_AUDIO_FORMAT_MAT; |
| break; |
| case 0xD: |
| *format = DI_CTA_AUDIO_FORMAT_DST; |
| break; |
| case 0xE: |
| *format = DI_CTA_AUDIO_FORMAT_WMA_PRO; |
| break; |
| case 0xF: |
| code_ext = get_bit_range(data[2], 7, 3); |
| switch (code_ext) { |
| case 0x04: |
| *format = DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC; |
| break; |
| case 0x05: |
| *format = DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC_V2; |
| break; |
| case 0x06: |
| *format = DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC; |
| break; |
| case 0x07: |
| *format = DI_CTA_AUDIO_FORMAT_DRA; |
| break; |
| case 0x08: |
| *format = DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC_MPEG_SURROUND; |
| break; |
| case 0x0A: |
| *format = DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC_MPEG_SURROUND; |
| break; |
| case 0x0B: |
| *format = DI_CTA_AUDIO_FORMAT_MPEGH_3D; |
| break; |
| case 0x0C: |
| *format = DI_CTA_AUDIO_FORMAT_AC4; |
| break; |
| case 0x0D: |
| *format = DI_CTA_AUDIO_FORMAT_LPCM_3D; |
| break; |
| default: |
| add_failure_until(cta, 3, |
| "Audio Data Block: Unknown Audio Ext Format 0x%02x.", |
| code_ext); |
| return false; |
| } |
| break; |
| default: |
| add_failure_until(cta, 3, |
| "Audio Data Block: Unknown Audio Format 0x%02x.", |
| code); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static bool |
| parse_sad(struct di_edid_cta *cta, struct di_cta_audio_block *audio, |
| const uint8_t data[static CTA_SAD_SIZE]) |
| { |
| enum di_cta_audio_format format; |
| struct di_cta_sad_priv *priv; |
| struct di_cta_sad *sad; |
| struct di_cta_sad_sample_rates *sample_rates; |
| struct di_cta_sad_lpcm *lpcm; |
| struct di_cta_sad_mpegh_3d *mpegh_3d; |
| struct di_cta_sad_mpeg_aac *mpeg_aac; |
| struct di_cta_sad_mpeg_surround *mpeg_surround; |
| struct di_cta_sad_mpeg_aac_le *mpeg_aac_le; |
| struct di_cta_sad_enhanced_ac3 *enhanced_ac3; |
| struct di_cta_sad_mat *mat; |
| struct di_cta_sad_wma_pro *wma_pro; |
| |
| if (!parse_sad_format(cta, data, &format)) |
| return true; |
| |
| priv = calloc(1, sizeof(*priv)); |
| if (!priv) |
| return false; |
| |
| sad = &priv->base; |
| sample_rates = &priv->supported_sample_rates; |
| lpcm = &priv->lpcm; |
| mpegh_3d = &priv->mpegh_3d; |
| mpeg_aac = &priv->mpeg_aac; |
| mpeg_surround = &priv->mpeg_surround; |
| mpeg_aac_le = &priv->mpeg_aac_le; |
| enhanced_ac3 = &priv->enhanced_ac3; |
| mat = &priv->mat; |
| wma_pro = &priv->wma_pro; |
| |
| sad->format = format; |
| |
| /* TODO: Find DRA documentation */ |
| |
| switch (format) { |
| case DI_CTA_AUDIO_FORMAT_LPCM: |
| case DI_CTA_AUDIO_FORMAT_AC3: |
| case DI_CTA_AUDIO_FORMAT_MPEG1: |
| case DI_CTA_AUDIO_FORMAT_MP3: |
| case DI_CTA_AUDIO_FORMAT_MPEG2: |
| case DI_CTA_AUDIO_FORMAT_AAC_LC: |
| case DI_CTA_AUDIO_FORMAT_DTS: |
| case DI_CTA_AUDIO_FORMAT_ATRAC: |
| case DI_CTA_AUDIO_FORMAT_ONE_BIT_AUDIO: |
| case DI_CTA_AUDIO_FORMAT_ENHANCED_AC3: |
| case DI_CTA_AUDIO_FORMAT_DTS_HD: |
| case DI_CTA_AUDIO_FORMAT_MAT: |
| case DI_CTA_AUDIO_FORMAT_DST: |
| case DI_CTA_AUDIO_FORMAT_WMA_PRO: |
| case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC: |
| case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC_V2: |
| case DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC: |
| /* DRA is not documented but this is what edid-decode does */ |
| case DI_CTA_AUDIO_FORMAT_DRA: |
| case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC_MPEG_SURROUND: |
| case DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC_MPEG_SURROUND: |
| sad->max_channels = get_bit_range(data[0], 2, 0) + 1; |
| break; |
| case DI_CTA_AUDIO_FORMAT_LPCM_3D: |
| sad->max_channels = (get_bit_range(data[0], 2, 0) | |
| (get_bit_range(data[0], 7, 7) << 3) | |
| (get_bit_range(data[1], 7, 7) << 4)) + 1; |
| break; |
| case DI_CTA_AUDIO_FORMAT_MPEGH_3D: |
| case DI_CTA_AUDIO_FORMAT_AC4: |
| break; |
| } |
| |
| switch (format) { |
| case DI_CTA_AUDIO_FORMAT_LPCM: |
| case DI_CTA_AUDIO_FORMAT_AC3: |
| case DI_CTA_AUDIO_FORMAT_MPEG1: |
| case DI_CTA_AUDIO_FORMAT_MP3: |
| case DI_CTA_AUDIO_FORMAT_MPEG2: |
| case DI_CTA_AUDIO_FORMAT_AAC_LC: |
| case DI_CTA_AUDIO_FORMAT_DTS: |
| case DI_CTA_AUDIO_FORMAT_ATRAC: |
| case DI_CTA_AUDIO_FORMAT_ONE_BIT_AUDIO: |
| case DI_CTA_AUDIO_FORMAT_ENHANCED_AC3: |
| case DI_CTA_AUDIO_FORMAT_DTS_HD: |
| case DI_CTA_AUDIO_FORMAT_MAT: |
| case DI_CTA_AUDIO_FORMAT_DST: |
| case DI_CTA_AUDIO_FORMAT_WMA_PRO: |
| /* DRA is not documented but this is what edid-decode does */ |
| case DI_CTA_AUDIO_FORMAT_DRA: |
| case DI_CTA_AUDIO_FORMAT_MPEGH_3D: |
| case DI_CTA_AUDIO_FORMAT_LPCM_3D: |
| sample_rates->has_192_khz = has_bit(data[1], 6); |
| sample_rates->has_176_4_khz = has_bit(data[1], 5); |
| /* fallthrough */ |
| case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC: |
| case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC_V2: |
| case DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC: |
| case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC_MPEG_SURROUND: |
| case DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC_MPEG_SURROUND: |
| sample_rates->has_96_khz = has_bit(data[1], 4); |
| sample_rates->has_88_2_khz = has_bit(data[1], 3); |
| sample_rates->has_48_khz = has_bit(data[1], 2); |
| sample_rates->has_44_1_khz = has_bit(data[1], 1); |
| sample_rates->has_32_khz = has_bit(data[1], 0); |
| break; |
| case DI_CTA_AUDIO_FORMAT_AC4: |
| sample_rates->has_192_khz = has_bit(data[1], 6); |
| sample_rates->has_96_khz = has_bit(data[1], 4); |
| sample_rates->has_48_khz = has_bit(data[1], 2); |
| sample_rates->has_44_1_khz = has_bit(data[1], 1); |
| break; |
| } |
| sad->supported_sample_rates = sample_rates; |
| |
| switch (format) { |
| case DI_CTA_AUDIO_FORMAT_AC3: |
| case DI_CTA_AUDIO_FORMAT_MPEG1: |
| case DI_CTA_AUDIO_FORMAT_MP3: |
| case DI_CTA_AUDIO_FORMAT_MPEG2: |
| case DI_CTA_AUDIO_FORMAT_AAC_LC: |
| case DI_CTA_AUDIO_FORMAT_DTS: |
| case DI_CTA_AUDIO_FORMAT_ATRAC: |
| sad->max_bitrate_kbs = data[2] * 8; |
| break; |
| default: |
| break; |
| } |
| |
| switch (format) { |
| case DI_CTA_AUDIO_FORMAT_LPCM: |
| case DI_CTA_AUDIO_FORMAT_LPCM_3D: |
| lpcm->has_sample_size_24_bits = has_bit(data[2], 2); |
| lpcm->has_sample_size_20_bits = has_bit(data[2], 1); |
| lpcm->has_sample_size_16_bits = has_bit(data[2], 0); |
| sad->lpcm = lpcm; |
| default: |
| break; |
| } |
| |
| switch (format) { |
| case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC: |
| case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC_V2: |
| case DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC: |
| case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC_MPEG_SURROUND: |
| case DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC_MPEG_SURROUND: |
| mpeg_aac->has_frame_length_1024 = has_bit(data[2], 2); |
| mpeg_aac->has_frame_length_960 = has_bit(data[2], 1); |
| sad->mpeg_aac = mpeg_aac; |
| break; |
| default: |
| break; |
| } |
| |
| if (format == DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC) { |
| mpeg_aac_le->supports_multichannel_sound = has_bit(data[2], 0); |
| sad->mpeg_aac_le = mpeg_aac_le; |
| } |
| |
| switch (format) { |
| case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC_MPEG_SURROUND: |
| case DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC_MPEG_SURROUND: |
| mpeg_surround->signaling = has_bit(data[2], 0); |
| sad->mpeg_surround = mpeg_surround; |
| break; |
| default: |
| break; |
| } |
| |
| if (format == DI_CTA_AUDIO_FORMAT_MPEGH_3D) { |
| mpegh_3d->low_complexity_profile = has_bit(data[2], 0); |
| mpegh_3d->baseline_profile = has_bit(data[2], 1); |
| mpegh_3d->level = get_bit_range(data[0], 2, 0); |
| if (mpegh_3d->level > DI_CTA_SAD_MPEGH_3D_LEVEL_5) { |
| add_failure_until(cta, 3, |
| "Unknown MPEG-H 3D Audio Level 0x%02x.", |
| mpegh_3d->level); |
| mpegh_3d->level = DI_CTA_SAD_MPEGH_3D_LEVEL_UNSPECIFIED; |
| } |
| sad->mpegh_3d = mpegh_3d; |
| } |
| |
| if (format == DI_CTA_AUDIO_FORMAT_ENHANCED_AC3) { |
| enhanced_ac3->supports_joint_object_coding = |
| has_bit(data[2], 0); |
| enhanced_ac3->supports_joint_object_coding_ACMOD28 = |
| has_bit(data[2], 1); |
| sad->enhanced_ac3 = enhanced_ac3; |
| } |
| |
| if (format == DI_CTA_AUDIO_FORMAT_MAT) { |
| mat->supports_object_audio_and_channel_based = |
| has_bit(data[2], 0); |
| if (mat->supports_object_audio_and_channel_based) |
| mat->requires_hash_calculation = !has_bit(data[2], 0); |
| sad->mat = mat; |
| } |
| |
| if (format == DI_CTA_AUDIO_FORMAT_WMA_PRO) { |
| wma_pro->profile = get_bit_range(data[2], 2, 0); |
| sad->wma_pro = wma_pro; |
| } |
| |
| switch (format) { |
| case DI_CTA_AUDIO_FORMAT_ONE_BIT_AUDIO: |
| case DI_CTA_AUDIO_FORMAT_DTS_HD: |
| case DI_CTA_AUDIO_FORMAT_DST: |
| /* TODO data[2] 7:0 contains unknown Audio Format Code dependent value */ |
| break; |
| default: |
| break; |
| } |
| |
| if (format == DI_CTA_AUDIO_FORMAT_AC4) { |
| /* TODO data[2] 2:0 contains unknown Audio Format Code dependent value */ |
| } |
| |
| switch (format) { |
| case DI_CTA_AUDIO_FORMAT_LPCM: |
| case DI_CTA_AUDIO_FORMAT_WMA_PRO: |
| if (has_bit(data[0], 7) || has_bit(data[1], 7) || |
| get_bit_range(data[2], 7, 3) != 0) |
| add_failure_until(cta, 3, |
| "Bits F17, F27, F37:F33 must be 0."); |
| break; |
| case DI_CTA_AUDIO_FORMAT_AC3: |
| case DI_CTA_AUDIO_FORMAT_MPEG1: |
| case DI_CTA_AUDIO_FORMAT_MP3: |
| case DI_CTA_AUDIO_FORMAT_MPEG2: |
| case DI_CTA_AUDIO_FORMAT_AAC_LC: |
| case DI_CTA_AUDIO_FORMAT_DTS: |
| case DI_CTA_AUDIO_FORMAT_ATRAC: |
| case DI_CTA_AUDIO_FORMAT_ONE_BIT_AUDIO: |
| case DI_CTA_AUDIO_FORMAT_ENHANCED_AC3: |
| case DI_CTA_AUDIO_FORMAT_DTS_HD: |
| case DI_CTA_AUDIO_FORMAT_MAT: |
| case DI_CTA_AUDIO_FORMAT_DST: |
| if (has_bit(data[0], 7) || has_bit(data[1], 7)) |
| add_failure_until(cta, 3, |
| "Bits F17, F27 must be 0."); |
| break; |
| case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC: |
| case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC_V2: |
| case DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC: |
| case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC_MPEG_SURROUND: |
| case DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC_MPEG_SURROUND: |
| if (has_bit(data[0], 7) || get_bit_range(data[2], 7, 5) != 0) |
| add_failure_until(cta, 3, |
| "Bits F17, F27:F25 must be 0."); |
| break; |
| case DI_CTA_AUDIO_FORMAT_MPEGH_3D: |
| if (has_bit(data[0], 7) || has_bit(data[1], 7) || |
| has_bit(data[2], 2)) |
| add_failure_until(cta, 3, |
| "Bits F17, F27, F32 must be 0."); |
| break; |
| case DI_CTA_AUDIO_FORMAT_AC4: |
| if ((data[0] & 0x87) != 0 || (data[1] & 0xA9) != 0) |
| add_failure_until(cta, 3, |
| "Bits F17, F12:F10, F27, F25, F23, " |
| "F20 must be 0."); |
| break; |
| /* DRA documentation missing */ |
| case DI_CTA_AUDIO_FORMAT_DRA: |
| case DI_CTA_AUDIO_FORMAT_LPCM_3D: |
| break; |
| } |
| |
| assert(audio->sads_len < EDID_CTA_MAX_AUDIO_BLOCK_ENTRIES); |
| audio->sads[audio->sads_len++] = priv; |
| return true; |
| } |
| |
| static bool |
| parse_audio_block(struct di_edid_cta *cta, struct di_cta_audio_block *audio, |
| const uint8_t *data, size_t size) |
| { |
| size_t i; |
| |
| if (size % 3 != 0) |
| add_failure(cta, "Broken CTA-861 audio block length %d.", size); |
| |
| for (i = 0; i + 3 <= size; i += 3) { |
| if (!parse_sad(cta, audio, &data[i])) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static bool |
| parse_speaker_alloc_block(struct di_edid_cta *cta, |
| struct di_cta_speaker_alloc_block *speaker_alloc, |
| const uint8_t *data, size_t size) |
| { |
| bool rlc_rrc; |
| |
| if (size < 3) { |
| add_failure(cta, |
| "Speaker Allocation Data Block: Empty Data Block with length %zu.", |
| size); |
| return false; |
| } |
| |
| speaker_alloc->flw_frw = has_bit(data[0], 7); |
| rlc_rrc = has_bit(data[0], 6); |
| speaker_alloc->flc_frc = has_bit(data[0], 5); |
| speaker_alloc->bc = has_bit(data[0], 4); |
| speaker_alloc->bl_br = has_bit(data[0], 3); |
| speaker_alloc->fc = has_bit(data[0], 2); |
| speaker_alloc->lfe1 = has_bit(data[0], 1); |
| speaker_alloc->fl_fr = has_bit(data[0], 0); |
| if (rlc_rrc) { |
| if (cta->revision >= 3) |
| add_failure(cta, "Speaker Allocation Data Block: Deprecated bit F16 must be 0."); |
| else |
| speaker_alloc->bl_br = true; |
| } |
| |
| speaker_alloc->tpsil_tpsir = has_bit(data[1], 7); |
| speaker_alloc->sil_sir = has_bit(data[1], 6); |
| speaker_alloc->tpbc = has_bit(data[1], 5); |
| speaker_alloc->lfe2 = has_bit(data[1], 4); |
| speaker_alloc->ls_rs = has_bit(data[1], 3); |
| speaker_alloc->tpfc = has_bit(data[1], 2); |
| speaker_alloc->tpc = has_bit(data[1], 1); |
| speaker_alloc->tpfl_tpfr = has_bit(data[1], 0); |
| |
| if (get_bit_range(data[2], 7, 4) != 0) |
| add_failure(cta, "Speaker Allocation Data Block: Bits F37, F36, F34 must be 0."); |
| if (cta->revision >= 3 && has_bit(data[2], 3)) |
| add_failure(cta, "Speaker Allocation Data Block: Deprecated bit F33 must be 0."); |
| speaker_alloc->btfl_btfr = has_bit(data[2], 2); |
| speaker_alloc->btfc = has_bit(data[2], 1); |
| speaker_alloc->tpbl_tpbr = has_bit(data[2], 0); |
| |
| return true; |
| } |
| |
| static bool |
| parse_video_cap_block(struct di_edid_cta *cta, |
| struct di_cta_video_cap_block *video_cap, |
| const uint8_t *data, size_t size) |
| { |
| if (size < 1) { |
| add_failure(cta, |
| "Video Capability Data Block: Empty Data Block with length %u.", |
| size); |
| return false; |
| } |
| |
| video_cap->selectable_ycc_quantization_range = has_bit(data[0], 7); |
| video_cap->selectable_rgb_quantization_range = has_bit(data[0], 6); |
| video_cap->pt_over_underscan = get_bit_range(data[0], 5, 4); |
| video_cap->it_over_underscan = get_bit_range(data[0], 3, 2); |
| video_cap->ce_over_underscan = get_bit_range(data[0], 1, 0); |
| |
| if (!video_cap->selectable_rgb_quantization_range && cta->revision >= 3) |
| add_failure(cta, |
| "Video Capability Data Block: Set Selectable RGB Quantization to avoid interop issues."); |
| /* TODO: add failure if selectable_ycc_quantization_range is unset, |
| * the sink supports YCbCr formats and the revision is 3+ */ |
| |
| switch (video_cap->it_over_underscan) { |
| case DI_CTA_VIDEO_CAP_ALWAYS_OVERSCAN: |
| if (cta->flags.it_underscan) |
| add_failure(cta, "Video Capability Data Block: IT video formats are always overscanned, but bit 7 of Byte 3 of the CTA-861 Extension header is set to underscanned."); |
| break; |
| case DI_CTA_VIDEO_CAP_ALWAYS_UNDERSCAN: |
| if (!cta->flags.it_underscan) |
| add_failure(cta, "Video Capability Data Block: IT video formats are always underscanned, but bit 7 of Byte 3 of the CTA-861 Extension header is set to overscanned."); |
| default: |
| break; |
| } |
| |
| return true; |
| } |
| |
| static bool |
| check_vesa_dddb_num_channels(enum di_cta_vesa_dddb_interface_type interface, |
| uint8_t num_channels) |
| { |
| switch (interface) { |
| case DI_CTA_VESA_DDDB_INTERFACE_VGA: |
| case DI_CTA_VESA_DDDB_INTERFACE_NAVI_V: |
| case DI_CTA_VESA_DDDB_INTERFACE_NAVI_D: |
| return num_channels == 0; |
| case DI_CTA_VESA_DDDB_INTERFACE_LVDS: |
| case DI_CTA_VESA_DDDB_INTERFACE_RSDS: |
| return true; |
| case DI_CTA_VESA_DDDB_INTERFACE_DVI_D: |
| return num_channels == 1 || num_channels == 2; |
| case DI_CTA_VESA_DDDB_INTERFACE_DVI_I_ANALOG: |
| return num_channels == 0; |
| case DI_CTA_VESA_DDDB_INTERFACE_DVI_I_DIGITAL: |
| return num_channels == 1 || num_channels == 2; |
| case DI_CTA_VESA_DDDB_INTERFACE_HDMI_A: |
| return num_channels == 1; |
| case DI_CTA_VESA_DDDB_INTERFACE_HDMI_B: |
| return num_channels == 2; |
| case DI_CTA_VESA_DDDB_INTERFACE_MDDI: |
| return num_channels == 1 || num_channels == 2; |
| case DI_CTA_VESA_DDDB_INTERFACE_DISPLAYPORT: |
| return num_channels == 1 || num_channels == 2 || num_channels == 4; |
| case DI_CTA_VESA_DDDB_INTERFACE_IEEE_1394: |
| case DI_CTA_VESA_DDDB_INTERFACE_M1_ANALOG: |
| return num_channels == 0; |
| case DI_CTA_VESA_DDDB_INTERFACE_M1_DIGITAL: |
| return num_channels == 1 || num_channels == 2; |
| } |
| abort(); /* unreachable */ |
| } |
| |
| static void |
| parse_vesa_dddb_additional_primary_chromaticity(struct di_cta_vesa_dddb_additional_primary_chromaticity *coords, |
| uint8_t low, |
| const uint8_t high[static 2]) |
| { |
| uint16_t raw_x, raw_y; /* only 10 bits are used */ |
| |
| raw_x = (uint16_t) ((high[0] << 2) | get_bit_range(low, 3, 2)); |
| raw_y = (uint16_t) ((high[1] << 2) | get_bit_range(low, 1, 0)); |
| |
| *coords = (struct di_cta_vesa_dddb_additional_primary_chromaticity) { |
| .x = (float) raw_x / 1024, |
| .y = (float) raw_y / 1024, |
| }; |
| } |
| |
| static bool |
| parse_vesa_dddb(struct di_edid_cta *cta, struct di_cta_vesa_dddb *dddb, |
| const uint8_t *data, size_t size) |
| { |
| const size_t offset = 2; /* CTA block header */ |
| uint8_t interface_type, num_channels, content_protection, scan_direction, |
| subpixel_layout; |
| |
| if (size + offset != 32) { |
| add_failure(cta, "VESA Video Display Device Data Block: Invalid length %u.", size); |
| return false; |
| } |
| |
| interface_type = get_bit_range(data[0x02 - offset], 7, 4); |
| num_channels = get_bit_range(data[0x02 - offset], 3, 0); |
| switch (interface_type) { |
| case 0x0: /* Analog */ |
| /* Special case: num_channels contains the detailed interface |
| * type. */ |
| switch (num_channels) { |
| case 0x0: |
| dddb->interface_type = DI_CTA_VESA_DDDB_INTERFACE_VGA; |
| break; |
| case 0x1: |
| dddb->interface_type = DI_CTA_VESA_DDDB_INTERFACE_NAVI_V; |
| break; |
| case 0x2: |
| dddb->interface_type = DI_CTA_VESA_DDDB_INTERFACE_NAVI_D; |
| break; |
| default: |
| add_failure(cta, |
| "VESA Video Display Device Data Block: Unknown analog interface type 0x%x.", |
| num_channels); |
| return false; |
| } |
| num_channels = 0; |
| break; |
| case 0x1: |
| dddb->interface_type = DI_CTA_VESA_DDDB_INTERFACE_LVDS; |
| break; |
| case 0x2: |
| dddb->interface_type = DI_CTA_VESA_DDDB_INTERFACE_RSDS; |
| break; |
| case 0x3: |
| dddb->interface_type = DI_CTA_VESA_DDDB_INTERFACE_DVI_D; |
| break; |
| case 0x4: |
| dddb->interface_type = DI_CTA_VESA_DDDB_INTERFACE_DVI_I_ANALOG; |
| break; |
| case 0x5: |
| dddb->interface_type = DI_CTA_VESA_DDDB_INTERFACE_DVI_I_DIGITAL; |
| break; |
| case 0x6: |
| dddb->interface_type = DI_CTA_VESA_DDDB_INTERFACE_HDMI_A; |
| break; |
| case 0x7: |
| dddb->interface_type = DI_CTA_VESA_DDDB_INTERFACE_HDMI_B; |
| break; |
| case 0x8: |
| dddb->interface_type = DI_CTA_VESA_DDDB_INTERFACE_MDDI; |
| break; |
| case 0x9: |
| dddb->interface_type = DI_CTA_VESA_DDDB_INTERFACE_DISPLAYPORT; |
| break; |
| case 0xA: |
| dddb->interface_type = DI_CTA_VESA_DDDB_INTERFACE_IEEE_1394; |
| break; |
| case 0xB: |
| dddb->interface_type = DI_CTA_VESA_DDDB_INTERFACE_M1_ANALOG; |
| break; |
| case 0xC: |
| dddb->interface_type = DI_CTA_VESA_DDDB_INTERFACE_M1_DIGITAL; |
| break; |
| default: |
| add_failure(cta, |
| "VESA Video Display Device Data Block: Unknown interface type 0x%x.", |
| interface_type); |
| return false; |
| } |
| |
| if (check_vesa_dddb_num_channels(dddb->interface_type, num_channels)) |
| dddb->num_channels = num_channels; |
| else |
| add_failure(cta, |
| "VESA Video Display Device Data Block: Invalid number of lanes/channels %u.", |
| num_channels); |
| |
| dddb->interface_version = get_bit_range(data[0x03 - offset], 7, 4); |
| dddb->interface_release = get_bit_range(data[0x03 - offset], 3, 0); |
| |
| content_protection = data[0x04 - offset]; |
| switch (content_protection) { |
| case DI_CTA_VESA_DDDB_CONTENT_PROTECTION_NONE: |
| case DI_CTA_VESA_DDDB_CONTENT_PROTECTION_HDCP: |
| case DI_CTA_VESA_DDDB_CONTENT_PROTECTION_DTCP: |
| case DI_CTA_VESA_DDDB_CONTENT_PROTECTION_DPCP: |
| dddb->content_protection = content_protection; |
| break; |
| default: |
| add_failure(cta, |
| "VESA Video Display Device Data Block: Invalid content protection 0x%x.", |
| content_protection); |
| } |
| |
| dddb->min_clock_freq_mhz = get_bit_range(data[0x05 - offset], 7, 2); |
| dddb->max_clock_freq_mhz = |
| (get_bit_range(data[0x05 - offset], 1, 0) << 8) | data[0x06 - offset]; |
| if (dddb->min_clock_freq_mhz > dddb->max_clock_freq_mhz) { |
| add_failure(cta, |
| "VESA Video Display Device Data Block: Minimum clock frequency (%d MHz) greater than maximum (%d MHz).", |
| dddb->min_clock_freq_mhz, dddb->max_clock_freq_mhz); |
| dddb->min_clock_freq_mhz = dddb->max_clock_freq_mhz = 0; |
| } |
| |
| dddb->native_horiz_pixels = data[0x07 - offset] | (data[0x08 - offset] << 8); |
| dddb->native_vert_pixels = data[0x09 - offset] | (data[0x0A - offset] << 8); |
| |
| dddb->aspect_ratio = (float)data[0x0B - offset] / 100 + 1; |
| dddb->default_orientation = get_bit_range(data[0x0C - offset], 7, 6); |
| dddb->rotation_cap = get_bit_range(data[0x0C - offset], 5, 4); |
| dddb->zero_pixel_location = get_bit_range(data[0x0C - offset], 3, 2); |
| scan_direction = get_bit_range(data[0x0C - offset], 1, 0); |
| if (scan_direction != 3) |
| dddb->scan_direction = scan_direction; |
| else |
| add_failure(cta, |
| "VESA Video Display Device Data Block: Invalid scan direction 0x%x.", |
| scan_direction); |
| |
| subpixel_layout = data[0x0D - offset]; |
| switch (subpixel_layout) { |
| case DI_CTA_VESA_DDDB_SUBPIXEL_UNDEFINED: |
| case DI_CTA_VESA_DDDB_SUBPIXEL_RGB_VERT: |
| case DI_CTA_VESA_DDDB_SUBPIXEL_RGB_HORIZ: |
| case DI_CTA_VESA_DDDB_SUBPIXEL_EDID_CHROM_VERT: |
| case DI_CTA_VESA_DDDB_SUBPIXEL_EDID_CHROM_HORIZ: |
| case DI_CTA_VESA_DDDB_SUBPIXEL_QUAD_RGGB: |
| case DI_CTA_VESA_DDDB_SUBPIXEL_QUAD_GBRG: |
| case DI_CTA_VESA_DDDB_SUBPIXEL_DELTA_RGB: |
| case DI_CTA_VESA_DDDB_SUBPIXEL_MOSAIC: |
| case DI_CTA_VESA_DDDB_SUBPIXEL_QUAD_ANY: |
| case DI_CTA_VESA_DDDB_SUBPIXEL_FIVE: |
| case DI_CTA_VESA_DDDB_SUBPIXEL_SIX: |
| case DI_CTA_VESA_DDDB_SUBPIXEL_CLAIRVOYANTE_PENTILE: |
| dddb->subpixel_layout = subpixel_layout; |
| break; |
| default: |
| add_failure(cta, |
| "VESA Video Display Device Data Block: Invalid subpixel layout 0x%x.", |
| subpixel_layout); |
| } |
| |
| dddb->horiz_pitch_mm = (float)data[0x0E - offset] * 0.01f; |
| dddb->vert_pitch_mm = (float)data[0x0F - offset] * 0.01f; |
| |
| dddb->dithering_type = get_bit_range(data[0x10 - offset], 7, 6); |
| dddb->direct_drive = has_bit(data[0x10 - offset], 5); |
| dddb->overdrive_not_recommended = has_bit(data[0x10 - offset], 4); |
| dddb->deinterlacing = has_bit(data[0x10 - offset], 3); |
| if (get_bit_range(data[0x10 - offset], 2, 0) != 0) |
| add_failure(cta, "VESA Video Display Device Data Block: Reserved miscellaneous display capabilities bits 2-0 must be 0."); |
| |
| dddb->audio_support = has_bit(data[0x11 - offset], 7); |
| dddb->separate_audio_inputs = has_bit(data[0x11 - offset], 6); |
| dddb->audio_input_override = has_bit(data[0x11 - offset], 5); |
| if (get_bit_range(data[0x11 - offset], 4, 0) != 0) |
| add_failure(cta, "VESA Video Display Device Data Block: Reserved audio bits 4-0 must be 0."); |
| |
| dddb->audio_delay_provided = data[0x12 - offset] != 0; |
| dddb->audio_delay_ms = 2 * get_bit_range(data[0x12 - offset], 6, 0); |
| if (!has_bit(data[0x12 - offset], 7)) |
| dddb->audio_delay_ms = -dddb->audio_delay_ms; |
| |
| dddb->frame_rate_conversion = get_bit_range(data[0x13 - offset], 7, 6); |
| dddb->frame_rate_range_hz = get_bit_range(data[0x13 - offset], 5, 0); |
| dddb->frame_rate_native_hz = data[0x14 - offset]; |
| |
| dddb->bit_depth_interface = get_bit_range(data[0x15 - offset], 7, 4) + 1; |
| dddb->bit_depth_display = get_bit_range(data[0x15 - offset], 3, 0) + 1; |
| |
| dddb->additional_primary_chromaticities_len = get_bit_range(data[0x17 - offset], 1, 0); |
| parse_vesa_dddb_additional_primary_chromaticity(&dddb->additional_primary_chromaticities[0], |
| get_bit_range(data[0x16 - offset], 7, 4), |
| &data[0x18 - offset]); |
| parse_vesa_dddb_additional_primary_chromaticity(&dddb->additional_primary_chromaticities[1], |
| get_bit_range(data[0x16 - offset], 3, 0), |
| &data[0x1A - offset]); |
| parse_vesa_dddb_additional_primary_chromaticity(&dddb->additional_primary_chromaticities[2], |
| get_bit_range(data[0x17 - offset], 7, 4), |
| &data[0x1C - offset]); |
| if (get_bit_range(data[0x17 - offset], 3, 2) != 0) |
| add_failure(cta, "VESA Video Display Device Data Block: Reserved additional primary chromaticities bits 3-2 of byte 0x17 must be 0."); |
| |
| dddb->resp_time_transition = has_bit(data[0x1E - offset], 7); |
| dddb->resp_time_ms = get_bit_range(data[0x1E - offset], 6, 0); |
| |
| dddb->overscan_horiz_pct = get_bit_range(data[0x1F - offset], 7, 4); |
| dddb->overscan_vert_pct = get_bit_range(data[0x1F - offset], 3, 0); |
| |
| return true; |
| } |
| |
| static bool |
| parse_colorimetry_block(struct di_edid_cta *cta, |
| struct di_cta_colorimetry_block *colorimetry, |
| const uint8_t *data, size_t size) |
| { |
| if (size < 2) { |
| add_failure(cta, "Colorimetry Data Block: Empty Data Block with length %u.", |
| size); |
| return false; |
| } |
| |
| colorimetry->bt2020_rgb = has_bit(data[0], 7); |
| colorimetry->bt2020_ycc = has_bit(data[0], 6); |
| colorimetry->bt2020_cycc = has_bit(data[0], 5); |
| colorimetry->oprgb = has_bit(data[0], 4); |
| colorimetry->opycc_601 = has_bit(data[0], 3); |
| colorimetry->sycc_601 = has_bit(data[0], 2); |
| colorimetry->xvycc_709 = has_bit(data[0], 1); |
| colorimetry->xvycc_601 = has_bit(data[0], 0); |
| |
| colorimetry->st2113_rgb = has_bit(data[1], 7); |
| colorimetry->ictcp = has_bit(data[1], 6); |
| |
| if (get_bit_range(data[1], 5, 0) != 0) |
| add_failure_until(cta, 3, |
| "Colorimetry Data Block: Reserved bits MD0-MD3 must be 0."); |
| |
| return true; |
| } |
| |
| static float |
| parse_max_luminance(uint8_t raw) |
| { |
| if (raw == 0) |
| return 0; |
| return 50 * powf(2, (float) raw / 32); |
| } |
| |
| static float |
| parse_min_luminance(uint8_t raw, float max) |
| { |
| if (raw == 0) |
| return 0; |
| return max * powf((float) raw / 255, 2) / 100; |
| } |
| |
| static bool |
| parse_hdr_static_metadata_block(struct di_edid_cta *cta, |
| struct di_cta_hdr_static_metadata_block_priv *metadata, |
| const uint8_t *data, size_t size) |
| { |
| uint8_t eotfs, descriptors; |
| |
| if (size < 2) { |
| add_failure(cta, "HDR Static Metadata Data Block: Empty Data Block with length %u.", |
| size); |
| return false; |
| } |
| |
| metadata->base.eotfs = &metadata->eotfs; |
| metadata->base.descriptors = &metadata->descriptors; |
| |
| eotfs = data[0]; |
| metadata->eotfs.traditional_sdr = has_bit(eotfs, 0); |
| metadata->eotfs.traditional_hdr = has_bit(eotfs, 1); |
| metadata->eotfs.pq = has_bit(eotfs, 2); |
| metadata->eotfs.hlg = has_bit(eotfs, 3); |
| if (get_bit_range(eotfs, 7, 4)) |
| add_failure_until(cta, 3, "HDR Static Metadata Data Block: Unknown EOTF."); |
| |
| descriptors = data[1]; |
| metadata->descriptors.type1 = has_bit(descriptors, 0); |
| if (get_bit_range(descriptors, 7, 1)) |
| add_failure_until(cta, 3, "HDR Static Metadata Data Block: Unknown descriptor type."); |
| |
| if (size > 2) |
| metadata->base.desired_content_max_luminance = parse_max_luminance(data[2]); |
| if (size > 3) |
| metadata->base.desired_content_max_frame_avg_luminance = parse_max_luminance(data[3]); |
| if (size > 4) { |
| if (metadata->base.desired_content_max_luminance == 0) |
| add_failure(cta, "HDR Static Metadata Data Block: Desired content min luminance is set, but max luminance is unset."); |
| else |
| metadata->base.desired_content_min_luminance = |
| parse_min_luminance(data[4], metadata->base.desired_content_max_luminance); |
| } |
| |
| return true; |
| } |
| static bool |
| parse_hdr_dynamic_metadata_block(struct di_edid_cta *cta, |
| struct di_cta_hdr_dynamic_metadata_block_priv *priv, |
| const uint8_t *data, size_t size) |
| { |
| struct di_cta_hdr_dynamic_metadata_block *base; |
| struct di_cta_hdr_dynamic_metadata_block_type1 *type1; |
| struct di_cta_hdr_dynamic_metadata_block_type2 *type2; |
| struct di_cta_hdr_dynamic_metadata_block_type3 *type3; |
| struct di_cta_hdr_dynamic_metadata_block_type4 *type4; |
| struct di_cta_hdr_dynamic_metadata_block_type256 *type256; |
| size_t length; |
| int type; |
| |
| base = &priv->base; |
| type1 = &priv->type1; |
| type2 = &priv->type2; |
| type3 = &priv->type3; |
| type4 = &priv->type4; |
| type256 = &priv->type256; |
| |
| if (size < 3) { |
| add_failure(cta, "HDR Dynamic Metadata Data Block: Empty Data Block with length %u.", |
| size); |
| return false; |
| } |
| |
| while (size >= 3) { |
| length = data[0]; |
| |
| if (size < length + 1) { |
| add_failure(cta, "HDR Dynamic Metadata Data Block: Length of type bigger than block size."); |
| return false; |
| } |
| |
| if (length < 2) { |
| add_failure(cta, "HDR Dynamic Metadata Data Block: Type has wrong length."); |
| return false; |
| } |
| |
| type = (data[2] << 8) | data[1]; |
| switch (type) { |
| case 0x0001: |
| if (length < 3) { |
| add_failure(cta, "HDR Dynamic Metadata Data Block: Type 1 missing Support Flags."); |
| break; |
| } |
| if (length != 3) |
| add_failure(cta, "HDR Dynamic Metadata Data Block: Type 1 length must be 3."); |
| type1->type_1_hdr_metadata_version = get_bit_range(data[3], 3, 0); |
| base->type1 = type1; |
| if (get_bit_range(data[3], 7, 4) != 0) |
| add_failure(cta, "HDR Dynamic Metadata Data Block: Type 1 support flags bits 7-4 must be 0."); |
| break; |
| case 0x0002: |
| if (length < 3) { |
| add_failure(cta, "HDR Dynamic Metadata Data Block: Type 2 missing Support Flags."); |
| break; |
| } |
| if (length != 3) |
| add_failure(cta, "HDR Dynamic Metadata Data Block: Type 2 length must be 3."); |
| type2->ts_103_433_spec_version = get_bit_range(data[3], 3, 0); |
| if (type2->ts_103_433_spec_version == 0) { |
| add_failure(cta, "HDR Dynamic Metadata Data Block: Type 2 spec version of 0 is not allowed."); |
| break; |
| } |
| type2->ts_103_433_1_capable = has_bit(data[3], 4); |
| type2->ts_103_433_2_capable = has_bit(data[3], 5); |
| type2->ts_103_433_3_capable = has_bit(data[3], 6); |
| base->type2 = type2; |
| if (has_bit(data[3], 7) != 0) |
| add_failure(cta, "HDR Dynamic Metadata Data Block: Type 1 support flags bit 7 must be 0."); |
| break; |
| case 0x0003: |
| if (length != 2) |
| add_failure(cta, "HDR Dynamic Metadata Data Block: Type 3 length must be 2."); |
| base->type3 = type3; |
| break; |
| case 0x0004: |
| if (length < 3) { |
| add_failure(cta, "HDR Dynamic Metadata Data Block: Type 4 missing Support Flags."); |
| break; |
| } |
| if (length != 3) |
| add_failure(cta, "HDR Dynamic Metadata Data Block: Type 4 length must be 3."); |
| type4->type_4_hdr_metadata_version = get_bit_range(data[3], 3, 0); |
| base->type4 = type4; |
| if (get_bit_range(data[3], 7, 4) != 0) |
| add_failure(cta, "HDR Dynamic Metadata Data Block: Type 4 support flags bits 7-4 must be 0."); |
| break; |
| case 0x0100: |
| if (length < 3) { |
| add_failure(cta, "HDR Dynamic Metadata Data Block: Type 256 missing Support Flags."); |
| break; |
| } |
| if (length != 3) |
| add_failure(cta, "HDR Dynamic Metadata Data Block: Type 256 length must be 3."); |
| type256->graphics_overlay_flag_version = get_bit_range(data[3], 3, 0); |
| base->type256 = type256; |
| if (get_bit_range(data[3], 7, 4) != 0) |
| add_failure(cta, "HDR Dynamic Metadata Data Block: Type 256 support flags bits 7-4 must be 0."); |
| break; |
| default: |
| add_failure(cta, "HDR Dynamic Metadata Data Block: Unknown Type 0x%04x.", type); |
| break; |
| } |
| |
| size -= length + 1; |
| data += length + 1; |
| } |
| |
| return true; |
| } |
| |
| static bool |
| parse_vesa_transfer_characteristics_block(struct di_edid_cta *cta, |
| struct di_cta_vesa_transfer_characteristics *tf, |
| const uint8_t *data, size_t size) |
| { |
| size_t i; |
| |
| if (size != 7 && size != 15 && size != 31) { |
| add_failure(cta, "Invalid length %u.", size); |
| return false; |
| } |
| |
| tf->points_len = (uint8_t) size + 1; |
| tf->usage = get_bit_range(data[0], 7, 6); |
| |
| tf->points[0] = get_bit_range(data[0], 5, 0) / 1023.0f; |
| for (i = 1; i < size; i++) |
| tf->points[i] = tf->points[i - 1] + data[i] / 1023.0f; |
| tf->points[i] = 1.0f; |
| |
| return true; |
| } |
| |
| static void |
| parse_ycbcr420_cap_map(struct di_edid_cta *cta, |
| struct di_cta_ycbcr420_cap_map *ycbcr420_cap_map, |
| const uint8_t *data, size_t size) |
| { |
| if (size == 0) { |
| ycbcr420_cap_map->all = true; |
| return; |
| } |
| |
| assert(size <= sizeof(ycbcr420_cap_map->svd_bitmap)); |
| memcpy(ycbcr420_cap_map->svd_bitmap, data, size); |
| } |
| |
| static struct di_cta_infoframe_descriptor * |
| parse_infoframe(struct di_edid_cta *cta, uint8_t type, |
| const uint8_t *data, size_t size) |
| { |
| struct di_cta_infoframe_descriptor infoframe = {0}; |
| struct di_cta_infoframe_descriptor *ifp; |
| |
| if (type >= 8 && type <= 0x1f) { |
| add_failure(cta, "InfoFrame Data Block: Type code %u is reserved.", |
| type); |
| return NULL; |
| } |
| |
| if (type >= 0x20) { |
| add_failure(cta, "InfoFrame Data Block: Type code %u is forbidden.", |
| type); |
| return NULL; |
| } |
| |
| if (type == 1) { |
| /* No known vendor specific InfoFrames, yet */ |
| return NULL; |
| } else { |
| switch (type) { |
| case 0x02: |
| infoframe.type = DI_CTA_INFOFRAME_TYPE_AUXILIARY_VIDEO_INFORMATION; |
| break; |
| case 0x03: |
| infoframe.type = DI_CTA_INFOFRAME_TYPE_SOURCE_PRODUCT_DESCRIPTION; |
| break; |
| case 0x04: |
| infoframe.type = DI_CTA_INFOFRAME_TYPE_AUDIO; |
| break; |
| case 0x05: |
| infoframe.type = DI_CTA_INFOFRAME_TYPE_MPEG_SOURCE; |
| break; |
| case 0x06: |
| infoframe.type = DI_CTA_INFOFRAME_TYPE_NTSC_VBI; |
| break; |
| case 0x07: |
| infoframe.type = DI_CTA_INFOFRAME_TYPE_DYNAMIC_RANGE_AND_MASTERING; |
| break; |
| default: |
| abort(); /* unreachable */ |
| } |
| } |
| |
| ifp = calloc(1, sizeof(*ifp)); |
| if (!ifp) |
| return NULL; |
| |
| *ifp = infoframe; |
| return ifp; |
| } |
| |
| static bool |
| parse_infoframe_block(struct di_edid_cta *cta, |
| struct di_cta_infoframe_block_priv *ifb, |
| const uint8_t *data, size_t size) |
| { |
| size_t index = 0, length; |
| uint8_t type; |
| struct di_cta_infoframe_descriptor *infoframe; |
| |
| if (size < 2) { |
| add_failure(cta, "InfoFrame Data Block: Empty Data Block with length %u.", |
| size); |
| return false; |
| } |
| |
| ifb->block.num_simultaneous_vsifs = data[1] + 1; |
| ifb->block.infoframes = (const struct di_cta_infoframe_descriptor *const *)ifb->infoframes; |
| |
| index = get_bit_range(data[0], 7, 5) + 2; |
| if (get_bit_range(data[0], 4, 0) != 0) |
| add_failure(cta, "InfoFrame Data Block: InfoFrame Processing " |
| "Descriptor Header bits F14-F10 shall be 0."); |
| |
| while (true) { |
| if (index == size) |
| break; |
| if (index > size) { |
| add_failure(cta, "InfoFrame Data Block: Payload length exceeds block size."); |
| return false; |
| } |
| |
| length = get_bit_range(data[index], 7, 5); |
| type = get_bit_range(data[index], 4, 0); |
| |
| if (type == 0) { |
| add_failure(cta, "InfoFrame Data Block: Short InfoFrame Descriptor with type 0 is forbidden."); |
| return false; |
| } else if (type == 1) { |
| length += 4; |
| } else { |
| length += 1; |
| } |
| |
| if (index + length > size) { |
| add_failure(cta, "InfoFrame Data Block: Payload length exceeds block size."); |
| return false; |
| } |
| |
| infoframe = parse_infoframe(cta, type, &data[index], length); |
| if (infoframe) { |
| assert(ifb->infoframes_len < EDID_CTA_INFOFRAME_BLOCK_ENTRIES); |
| ifb->infoframes[ifb->infoframes_len++] = infoframe; |
| } |
| |
| index += length; |
| } |
| |
| return true; |
| } |
| |
| static void |
| destroy_data_block(struct di_cta_data_block *data_block) |
| { |
| size_t i; |
| struct di_cta_video_block *video; |
| struct di_cta_audio_block *audio; |
| struct di_cta_infoframe_block_priv *infoframe; |
| |
| switch (data_block->tag) { |
| case DI_CTA_DATA_BLOCK_VIDEO: |
| video = &data_block->video; |
| for (i = 0; i < video->svds_len; i++) |
| free(video->svds[i]); |
| break; |
| case DI_CTA_DATA_BLOCK_YCBCR420: |
| video = &data_block->ycbcr420; |
| for (i = 0; i < video->svds_len; i++) |
| free(video->svds[i]); |
| break; |
| case DI_CTA_DATA_BLOCK_AUDIO: |
| audio = &data_block->audio; |
| for (i = 0; i < audio->sads_len; i++) |
| free(audio->sads[i]); |
| break; |
| case DI_CTA_DATA_BLOCK_INFOFRAME: |
| infoframe = &data_block->infoframe; |
| for (i = 0; i < infoframe->infoframes_len; i++) |
| free(infoframe->infoframes[i]); |
| break; |
| default: |
| break; /* Nothing to do */ |
| } |
| |
| free(data_block); |
| } |
| |
| static bool |
| parse_data_block(struct di_edid_cta *cta, uint8_t raw_tag, const uint8_t *data, size_t size) |
| { |
| enum di_cta_data_block_tag tag; |
| uint8_t extended_tag; |
| struct di_cta_data_block *data_block; |
| uint32_t ieee_oui; |
| |
| data_block = calloc(1, sizeof(*data_block)); |
| if (!data_block) { |
| return false; |
| } |
| |
| switch (raw_tag) { |
| case 1: |
| tag = DI_CTA_DATA_BLOCK_AUDIO; |
| if (!parse_audio_block(cta, &data_block->audio, data, size)) |
| goto error; |
| break; |
| case 2: |
| tag = DI_CTA_DATA_BLOCK_VIDEO; |
| if (!parse_video_block(cta, &data_block->video, data, size)) |
| goto error; |
| break; |
| case 3: |
| /* Vendor-Specific Data Block */ |
| |
| if (size < 3) { |
| add_failure(cta, |
| "Vendor-Specific Data Block: Data block length (%zu) is not enough to contain an OUI.", |
| size); |
| goto skip; |
| } |
| |
| ieee_oui = (data[2] << 16) | (data[1] << 8) | data[0]; |
| switch (ieee_oui) { |
| case IEEE_OUI_HDMI_FORUM: |
| tag = DI_CTA_DATA_BLOCK_VENDOR_HDMI_FORUM; |
| if (!parse_vendor_hdmi_forum_block(cta, &data_block->vendor_hdmi_forum, |
| data, size)) |
| goto skip; |
| break; |
| default: |
| goto skip; |
| } |
| break; |
| case 4: |
| tag = DI_CTA_DATA_BLOCK_SPEAKER_ALLOC; |
| if (!parse_speaker_alloc_block(cta, &data_block->speaker_alloc, |
| data, size)) |
| goto error; |
| break; |
| case 5: |
| tag = DI_CTA_DATA_BLOCK_VESA_DISPLAY_TRANSFER_CHARACTERISTIC; |
| if (!parse_vesa_transfer_characteristics_block(cta, |
| &data_block->vesa_transfer_characteristics, |
| data, size)) |
| goto error; |
| break; |
| case 6: |
| tag = DI_CTA_DATA_BLOCK_VIDEO_FORMAT; |
| break; |
| case 7: |
| /* Use Extended Tag */ |
| if (size < 1) { |
| add_failure(cta, "Empty block with extended tag."); |
| goto skip; |
| } |
| |
| extended_tag = data[0]; |
| data = &data[1]; |
| size--; |
| |
| switch (extended_tag) { |
| case 0: |
| tag = DI_CTA_DATA_BLOCK_VIDEO_CAP; |
| if (!parse_video_cap_block(cta, &data_block->video_cap, |
| data, size)) |
| goto skip; |
| break; |
| case 2: |
| tag = DI_CTA_DATA_BLOCK_VESA_DISPLAY_DEVICE; |
| if (!parse_vesa_dddb(cta, &data_block->vesa_dddb, |
| data, size)) |
| goto skip; |
| break; |
| case 5: |
| tag = DI_CTA_DATA_BLOCK_COLORIMETRY; |
| if (!parse_colorimetry_block(cta, |
| &data_block->colorimetry, |
| data, size)) |
| goto skip; |
| break; |
| case 6: |
| tag = DI_CTA_DATA_BLOCK_HDR_STATIC_METADATA; |
| if (!parse_hdr_static_metadata_block(cta, |
| &data_block->hdr_static_metadata, |
| data, size)) |
| goto skip; |
| break; |
| case 7: |
| tag = DI_CTA_DATA_BLOCK_HDR_DYNAMIC_METADATA; |
| if (!parse_hdr_dynamic_metadata_block(cta, |
| &data_block->hdr_dynamic_metadata, |
| data, size)) |
| goto skip; |
| break; |
| case 8: |
| tag = DI_CTA_DATA_BLOCK_NATIVE_VIDEO_RESOLUTION; |
| break; |
| case 13: |
| tag = DI_CTA_DATA_BLOCK_VIDEO_FORMAT_PREF; |
| break; |
| case 14: |
| tag = DI_CTA_DATA_BLOCK_YCBCR420; |
| if (!parse_ycbcr420_block(cta, |
| &data_block->ycbcr420, |
| data, size)) |
| goto skip; |
| break; |
| case 15: |
| tag = DI_CTA_DATA_BLOCK_YCBCR420_CAP_MAP; |
| parse_ycbcr420_cap_map(cta, |
| &data_block->ycbcr420_cap_map, |
| data, size); |
| break; |
| case 18: |
| tag = DI_CTA_DATA_BLOCK_HDMI_AUDIO; |
| break; |
| case 19: |
| tag = DI_CTA_DATA_BLOCK_ROOM_CONFIG; |
| break; |
| case 20: |
| tag = DI_CTA_DATA_BLOCK_SPEAKER_LOCATION; |
| break; |
| case 32: |
| tag = DI_CTA_DATA_BLOCK_INFOFRAME; |
| if (!parse_infoframe_block(cta, |
| &data_block->infoframe, |
| data, size)) |
| goto skip; |
| break; |
| case 34: |
| tag = DI_CTA_DATA_BLOCK_DISPLAYID_VIDEO_TIMING_VII; |
| break; |
| case 35: |
| tag = DI_CTA_DATA_BLOCK_DISPLAYID_VIDEO_TIMING_VIII; |
| break; |
| case 42: |
| tag = DI_CTA_DATA_BLOCK_DISPLAYID_VIDEO_TIMING_X; |
| break; |
| case 120: |
| tag = DI_CTA_DATA_BLOCK_HDMI_EDID_EXT_OVERRIDE; |
| break; |
| case 121: |
| tag = DI_CTA_DATA_BLOCK_HDMI_SINK_CAP; |
| break; |
| case 1: /* Vendor-Specific Video Data Block */ |
| case 17: /* Vendor-Specific Audio Data Block */ |
| goto skip; |
| default: |
| /* Reserved */ |
| add_failure_until(cta, 3, |
| "Unknown CTA-861 Data Block (extended tag 0x"PRIx8", length %zu).", |
| extended_tag, size); |
| goto skip; |
| } |
| break; |
| default: |
| /* Reserved */ |
| add_failure_until(cta, 3, "Unknown CTA-861 Data Block (tag 0x"PRIx8", length %zu).", |
| raw_tag, size); |
| goto skip; |
| } |
| |
| data_block->tag = tag; |
| assert(cta->data_blocks_len < EDID_CTA_MAX_DATA_BLOCKS); |
| cta->data_blocks[cta->data_blocks_len++] = data_block; |
| return true; |
| |
| skip: |
| free(data_block); |
| return true; |
| |
| error: |
| destroy_data_block(data_block); |
| return false; |
| } |
| |
| bool |
| _di_edid_cta_parse(struct di_edid_cta *cta, const uint8_t *data, size_t size, |
| struct di_logger *logger) |
| { |
| uint8_t flags, dtd_start; |
| uint8_t data_block_header, data_block_tag, data_block_size; |
| size_t i; |
| struct di_edid_detailed_timing_def_priv *detailed_timing_def; |
| |
| assert(size == 128); |
| assert(data[0] == 0x02); |
| |
| cta->logger = logger; |
| |
| cta->revision = data[1]; |
| dtd_start = data[2]; |
| |
| flags = data[3]; |
| if (cta->revision >= 2) { |
| cta->flags.it_underscan = has_bit(flags, 7); |
| cta->flags.basic_audio = has_bit(flags, 6); |
| cta->flags.ycc444 = has_bit(flags, 5); |
| cta->flags.ycc422 = has_bit(flags, 4); |
| cta->flags.native_dtds = get_bit_range(flags, 3, 0); |
| } else if (flags != 0) { |
| /* Reserved */ |
| add_failure(cta, "Non-zero byte 3."); |
| } |
| |
| if (dtd_start == 0) { |
| return true; |
| } else if (dtd_start < CTA_HEADER_SIZE || dtd_start >= size) { |
| errno = EINVAL; |
| return false; |
| } |
| |
| i = CTA_HEADER_SIZE; |
| while (i < dtd_start) { |
| data_block_header = data[i]; |
| data_block_tag = get_bit_range(data_block_header, 7, 5); |
| data_block_size = get_bit_range(data_block_header, 4, 0); |
| |
| if (i + 1 + data_block_size > dtd_start) { |
| add_failure(cta, "Data Block at offset %zu overlaps Detailed Timing " |
| "Definitions. Skipping all further Data Blocks.", i); |
| break; |
| } |
| |
| if (!parse_data_block(cta, data_block_tag, |
| &data[i + 1], data_block_size)) { |
| _di_edid_cta_finish(cta); |
| return false; |
| } |
| |
| i += 1 + data_block_size; |
| } |
| |
| if (i != dtd_start) |
| add_failure(cta, "Offset is %"PRIu8", but should be %zu.", |
| dtd_start, i); |
| |
| for (i = dtd_start; i + EDID_BYTE_DESCRIPTOR_SIZE <= CTA_DTD_END; |
| i += EDID_BYTE_DESCRIPTOR_SIZE) { |
| if (data[i] == 0) |
| break; |
| |
| detailed_timing_def = _di_edid_parse_detailed_timing_def(&data[i]); |
| if (!detailed_timing_def) { |
| _di_edid_cta_finish(cta); |
| return false; |
| } |
| assert(cta->detailed_timing_defs_len < EDID_CTA_MAX_DETAILED_TIMING_DEFS); |
| cta->detailed_timing_defs[cta->detailed_timing_defs_len++] = detailed_timing_def; |
| } |
| |
| /* All padding bytes after the last DTD must be zero */ |
| while (i < CTA_DTD_END) { |
| if (data[i] != 0) { |
| add_failure(cta, "Padding: Contains non-zero bytes."); |
| break; |
| } |
| i++; |
| } |
| |
| cta->logger = NULL; |
| return true; |
| } |
| |
| void |
| _di_edid_cta_finish(struct di_edid_cta *cta) |
| { |
| size_t i; |
| |
| for (i = 0; i < cta->data_blocks_len; i++) { |
| destroy_data_block(cta->data_blocks[i]); |
| } |
| |
| for (i = 0; i < cta->detailed_timing_defs_len; i++) { |
| free(cta->detailed_timing_defs[i]); |
| } |
| } |
| |
| int |
| di_edid_cta_get_revision(const struct di_edid_cta *cta) |
| { |
| return cta->revision; |
| } |
| |
| const struct di_edid_cta_flags * |
| di_edid_cta_get_flags(const struct di_edid_cta *cta) |
| { |
| return &cta->flags; |
| } |
| |
| const struct di_cta_data_block *const * |
| di_edid_cta_get_data_blocks(const struct di_edid_cta *cta) |
| { |
| return (const struct di_cta_data_block *const *) cta->data_blocks; |
| } |
| |
| enum di_cta_data_block_tag |
| di_cta_data_block_get_tag(const struct di_cta_data_block *block) |
| { |
| return block->tag; |
| } |
| |
| const struct di_cta_svd *const * |
| di_cta_data_block_get_svds(const struct di_cta_data_block *block) |
| { |
| if (block->tag != DI_CTA_DATA_BLOCK_VIDEO) { |
| return NULL; |
| } |
| return (const struct di_cta_svd *const *) block->video.svds; |
| } |
| |
| const struct di_cta_svd *const * |
| di_cta_data_block_get_ycbcr420_svds(const struct di_cta_data_block *block) |
| { |
| if (block->tag != DI_CTA_DATA_BLOCK_YCBCR420) { |
| return NULL; |
| } |
| return (const struct di_cta_svd *const *) block->ycbcr420.svds; |
| } |
| |
| const struct di_cta_sad *const * |
| di_cta_data_block_get_sads(const struct di_cta_data_block *block) |
| { |
| if (block->tag != DI_CTA_DATA_BLOCK_AUDIO) { |
| return NULL; |
| } |
| return (const struct di_cta_sad *const *) block->audio.sads; |
| } |
| |
| const struct di_cta_speaker_alloc_block * |
| di_cta_data_block_get_speaker_alloc(const struct di_cta_data_block *block) |
| { |
| if (block->tag != DI_CTA_DATA_BLOCK_SPEAKER_ALLOC) { |
| return NULL; |
| } |
| return &block->speaker_alloc; |
| } |
| |
| const struct di_cta_colorimetry_block * |
| di_cta_data_block_get_colorimetry(const struct di_cta_data_block *block) |
| { |
| if (block->tag != DI_CTA_DATA_BLOCK_COLORIMETRY) { |
| return NULL; |
| } |
| return &block->colorimetry; |
| } |
| |
| const struct di_cta_hdr_static_metadata_block * |
| di_cta_data_block_get_hdr_static_metadata(const struct di_cta_data_block *block) |
| { |
| if (block->tag != DI_CTA_DATA_BLOCK_HDR_STATIC_METADATA) { |
| return NULL; |
| } |
| return &block->hdr_static_metadata.base; |
| } |
| |
| const struct di_cta_hdr_dynamic_metadata_block * |
| di_cta_data_block_get_hdr_dynamic_metadata(const struct di_cta_data_block *block) |
| { |
| if (block->tag != DI_CTA_DATA_BLOCK_HDR_DYNAMIC_METADATA) { |
| return NULL; |
| } |
| return &block->hdr_dynamic_metadata.base; |
| } |
| |
| const struct di_cta_video_cap_block * |
| di_cta_data_block_get_video_cap(const struct di_cta_data_block *block) |
| { |
| if (block->tag != DI_CTA_DATA_BLOCK_VIDEO_CAP) { |
| return NULL; |
| } |
| return &block->video_cap; |
| } |
| |
| const struct di_cta_vesa_dddb * |
| di_cta_data_block_get_vesa_dddb(const struct di_cta_data_block *block) |
| { |
| if (block->tag != DI_CTA_DATA_BLOCK_VESA_DISPLAY_DEVICE) { |
| return NULL; |
| } |
| return &block->vesa_dddb; |
| } |
| |
| bool |
| di_cta_ycbcr420_cap_map_supported(const struct di_cta_ycbcr420_cap_map *cap_map, |
| size_t svd_index) |
| { |
| size_t byte, bit; |
| |
| if (cap_map->all) |
| return true; |
| |
| byte = svd_index / 8; |
| bit = svd_index % 8; |
| |
| if (byte >= EDID_CTA_MAX_YCBCR420_CAP_MAP_BLOCK_ENTRIES) |
| return false; |
| |
| return cap_map->svd_bitmap[byte] & (1 << bit); |
| } |
| |
| const struct di_cta_ycbcr420_cap_map * |
| di_cta_data_block_get_ycbcr420_cap_map(const struct di_cta_data_block *block) |
| { |
| if (block->tag != DI_CTA_DATA_BLOCK_YCBCR420_CAP_MAP) { |
| return NULL; |
| } |
| return &block->ycbcr420_cap_map; |
| } |
| |
| const struct di_cta_infoframe_block * |
| di_cta_data_block_get_infoframe(const struct di_cta_data_block *block) |
| { |
| if (block->tag != DI_CTA_DATA_BLOCK_INFOFRAME) { |
| return NULL; |
| } |
| return &block->infoframe.block; |
| } |
| |
| const struct di_edid_detailed_timing_def *const * |
| di_edid_cta_get_detailed_timing_defs(const struct di_edid_cta *cta) |
| { |
| return (const struct di_edid_detailed_timing_def *const *) cta->detailed_timing_defs; |
| } |
| |
| const struct di_cta_vesa_transfer_characteristics * |
| di_cta_data_block_get_vesa_transfer_characteristics(const struct di_cta_data_block *block) |
| { |
| if (block->tag != DI_CTA_DATA_BLOCK_VESA_DISPLAY_TRANSFER_CHARACTERISTIC) { |
| return NULL; |
| } |
| return &block->vesa_transfer_characteristics; |
| } |