2022-06-11 11:38:49 +02:00
# include <assert.h>
2022-06-11 12:19:27 +02:00
# include <errno.h>
2022-08-02 12:48:33 +02:00
# include <inttypes.h>
2022-08-03 10:37:07 +02:00
# include <math.h>
2022-06-11 14:06:23 +02:00
# include <stdlib.h>
2022-08-02 18:56:55 +02:00
# include <string.h>
2022-06-11 11:38:49 +02:00
2022-06-11 12:19:27 +02:00
# include "bits.h"
2022-06-11 11:38:49 +02:00
# include "cta.h"
2022-08-02 12:48:33 +02:00
# include "log.h"
2022-08-02 18:56:55 +02:00
# include "edid.h"
2022-06-11 11:38:49 +02:00
2022-06-11 14:06:23 +02:00
/**
* Number of bytes in the CTA header ( tag + revision + DTD offset + flags ) .
*/
# define CTA_HEADER_SIZE 4
2022-08-02 18:56:55 +02:00
/**
* Exclusive upper bound for the detailed timing definitions in the CTA block .
*/
# define CTA_DTD_END 127
2022-06-11 14:06:23 +02:00
2022-08-02 12:48:33 +02:00
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 ) ;
}
2022-08-29 15:17:11 +02:00
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 ;
uint8_t raw ;
struct di_cta_svd svd , * svd_ptr ;
if ( size = = 0 )
add_failure ( cta , " Video Data Block: Empty Data Block " ) ;
for ( i = 0 ; i < size ; i + + ) {
raw = data [ i ] ;
if ( raw = = 0 | | raw = = 128 | | raw > = 254 ) {
/* Reserved */
add_failure_until ( cta , 3 ,
" Video Data Block: Unknown VIC % " PRIu8 " . " ,
raw ) ;
continue ;
} 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 false ;
* svd_ptr = svd ;
video - > svds [ video - > svds_len + + ] = svd_ptr ;
}
return true ;
}
2022-09-01 13:43:22 +02:00
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 ;
}
2022-07-27 17:51:53 +02:00
static bool
2022-08-02 12:48:33 +02:00
parse_colorimetry_block ( struct di_edid_cta * cta ,
struct di_cta_colorimetry_block * colorimetry ,
2022-07-27 17:51:53 +02:00
const uint8_t * data , size_t size )
{
if ( size < 2 ) {
2022-08-02 12:48:33 +02:00
add_failure ( cta , " Colorimetry Data Block: Empty Data Block with length %u. " ,
size ) ;
2022-07-27 17:51:53 +02:00
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 ) ;
2022-08-02 12:48:33 +02:00
if ( get_bit_range ( data [ 1 ] , 5 , 0 ) ! = 0 )
add_failure_until ( cta , 3 ,
" Colorimetry Data Block: Reserved bits MD0-MD3 must be 0. " ) ;
2022-07-27 17:51:53 +02:00
return true ;
}
2022-08-03 10:37:07 +02:00
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 ;
}
2022-08-30 15:10:40 +02:00
metadata - > base . eotfs = & metadata - > eotfs ;
metadata - > base . descriptors = & metadata - > descriptors ;
2022-08-03 10:37:07 +02:00
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 ;
}
2022-08-29 15:17:11 +02:00
static void
destroy_data_block ( struct di_cta_data_block * data_block )
{
size_t i ;
struct di_cta_video_block * video ;
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 ;
default :
break ; /* Nothing to do */
}
free ( data_block ) ;
}
2022-06-11 14:06:23 +02:00
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 ;
2022-07-27 17:51:53 +02:00
data_block = calloc ( 1 , sizeof ( * data_block ) ) ;
if ( ! data_block ) {
return false ;
}
2022-06-11 14:06:23 +02:00
switch ( raw_tag ) {
case 1 :
tag = DI_CTA_DATA_BLOCK_AUDIO ;
break ;
case 2 :
tag = DI_CTA_DATA_BLOCK_VIDEO ;
2022-08-29 15:17:11 +02:00
if ( ! parse_video_block ( cta , & data_block - > video , data , size ) )
goto error ;
2022-06-11 14:06:23 +02:00
break ;
case 3 :
/* Vendor-Specific Data Block */
2022-08-02 12:48:33 +02:00
goto skip ;
2022-06-11 14:06:23 +02:00
case 4 :
tag = DI_CTA_DATA_BLOCK_SPEAKER_ALLOC ;
break ;
case 5 :
tag = DI_CTA_DATA_BLOCK_VESA_DISPLAY_TRANSFER_CHARACTERISTIC ;
break ;
case 7 :
/* Use Extended Tag */
if ( size < 1 ) {
2022-08-02 12:48:33 +02:00
add_failure ( cta , " Empty block with extended tag. " ) ;
goto skip ;
2022-06-11 14:06:23 +02:00
}
extended_tag = data [ 0 ] ;
data = & data [ 1 ] ;
size - - ;
switch ( extended_tag ) {
case 0 :
tag = DI_CTA_DATA_BLOCK_VIDEO_CAP ;
2022-09-01 13:43:22 +02:00
if ( ! parse_video_cap_block ( cta , & data_block - > video_cap ,
data , size ) )
goto skip ;
2022-06-11 14:06:23 +02:00
break ;
case 2 :
tag = DI_CTA_DATA_BLOCK_VESA_DISPLAY_DEVICE ;
break ;
case 5 :
tag = DI_CTA_DATA_BLOCK_COLORIMETRY ;
2022-08-02 12:48:33 +02:00
if ( ! parse_colorimetry_block ( cta ,
& data_block - > colorimetry ,
2022-07-27 17:51:53 +02:00
data , size ) )
2022-08-02 12:48:33 +02:00
goto skip ;
2022-06-11 14:06:23 +02:00
break ;
case 6 :
tag = DI_CTA_DATA_BLOCK_HDR_STATIC_METADATA ;
2022-08-03 10:37:07 +02:00
if ( ! parse_hdr_static_metadata_block ( cta ,
& data_block - > hdr_static_metadata ,
data , size ) )
goto skip ;
2022-06-11 14:06:23 +02:00
break ;
case 7 :
tag = DI_CTA_DATA_BLOCK_HDR_DYNAMIC_METADATA ;
break ;
case 13 :
tag = DI_CTA_DATA_BLOCK_VIDEO_FORMAT_PREF ;
break ;
case 14 :
tag = DI_CTA_DATA_BLOCK_YCBCR420 ;
break ;
case 15 :
tag = DI_CTA_DATA_BLOCK_YCBCR420_CAP_MAP ;
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 ;
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 */
2022-08-02 12:48:33 +02:00
goto skip ;
2022-06-11 14:06:23 +02:00
default :
/* Reserved */
2022-08-02 12:48:33 +02:00
add_failure_until ( cta , 3 ,
" Unknown CTA-861 Data Block (extended tag 0x " PRIx8 " , length %zu). " ,
extended_tag , size ) ;
goto skip ;
2022-06-11 14:06:23 +02:00
}
break ;
default :
/* Reserved */
2022-08-02 12:48:33 +02:00
add_failure_until ( cta , 3 , " Unknown CTA-861 Data Block (tag 0x " PRIx8 " , length %zu). " ,
raw_tag , size ) ;
goto skip ;
2022-06-11 14:06:23 +02:00
}
data_block - > tag = tag ;
cta - > data_blocks [ cta - > data_blocks_len + + ] = data_block ;
return true ;
2022-07-27 17:51:53 +02:00
2022-08-02 12:48:33 +02:00
skip :
2022-07-27 17:51:53 +02:00
free ( data_block ) ;
2022-08-02 12:48:33 +02:00
return true ;
2022-08-29 15:17:11 +02:00
error :
destroy_data_block ( data_block ) ;
return false ;
2022-06-11 14:06:23 +02:00
}
2022-06-11 11:38:49 +02:00
bool
2022-08-02 12:48:33 +02:00
_di_edid_cta_parse ( struct di_edid_cta * cta , const uint8_t * data , size_t size ,
struct di_logger * logger )
2022-06-11 11:38:49 +02:00
{
2022-06-11 14:06:23 +02:00
uint8_t flags , dtd_start ;
uint8_t data_block_header , data_block_tag , data_block_size ;
size_t i ;
2022-08-17 00:47:08 +02:00
struct di_edid_detailed_timing_def_priv * detailed_timing_def ;
2022-06-11 12:19:27 +02:00
2022-06-11 11:38:49 +02:00
assert ( size = = 128 ) ;
assert ( data [ 0 ] = = 0x02 ) ;
2022-08-02 12:48:33 +02:00
cta - > logger = logger ;
2022-06-11 11:38:49 +02:00
cta - > revision = data [ 1 ] ;
2022-06-11 14:06:23 +02:00
dtd_start = data [ 2 ] ;
2022-06-11 11:38:49 +02:00
2022-06-11 12:19:27 +02:00
flags = data [ 3 ] ;
if ( cta - > revision > = 2 ) {
2022-09-01 12:32:37 +02:00
cta - > flags . it_underscan = has_bit ( flags , 7 ) ;
2022-06-11 12:19:27 +02:00
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 */
2022-08-02 12:48:33 +02:00
add_failure ( cta , " Non-zero byte 3. " ) ;
2022-06-11 12:19:27 +02:00
}
2022-06-11 14:06:23 +02:00
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 ) {
_di_edid_cta_finish ( cta ) ;
errno = EINVAL ;
return false ;
}
if ( ! parse_data_block ( cta , data_block_tag ,
2022-08-02 12:48:33 +02:00
& data [ i + 1 ] , data_block_size ) ) {
2022-06-11 14:06:23 +02:00
_di_edid_cta_finish ( cta ) ;
return false ;
}
i + = 1 + data_block_size ;
}
2022-08-02 12:48:33 +02:00
if ( i ! = dtd_start )
add_failure ( cta , " Offset is % " PRIu8 " , but should be %zu. " ,
dtd_start , i ) ;
2022-06-11 14:06:23 +02:00
2022-08-02 18:56:55 +02:00
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 ;
}
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 + + ;
}
2022-08-02 12:48:33 +02:00
cta - > logger = NULL ;
2022-06-11 11:38:49 +02:00
return true ;
}
2022-06-11 14:06:23 +02:00
void
_di_edid_cta_finish ( struct di_edid_cta * cta )
{
size_t i ;
for ( i = 0 ; i < cta - > data_blocks_len ; i + + ) {
2022-08-29 15:17:11 +02:00
destroy_data_block ( cta - > data_blocks [ i ] ) ;
2022-06-11 14:06:23 +02:00
}
2022-08-02 18:56:55 +02:00
for ( i = 0 ; i < cta - > detailed_timing_defs_len ; i + + ) {
free ( cta - > detailed_timing_defs [ i ] ) ;
}
2022-06-11 14:06:23 +02:00
}
2022-06-11 11:38:49 +02:00
int
di_edid_cta_get_revision ( const struct di_edid_cta * cta )
{
return cta - > revision ;
}
2022-06-11 12:19:27 +02:00
const struct di_edid_cta_flags *
di_edid_cta_get_flags ( const struct di_edid_cta * cta )
{
return & cta - > flags ;
}
2022-06-11 14:06:23 +02:00
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 ;
}
2022-07-27 17:51:53 +02:00
2022-08-29 15:17:11 +02:00
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 ;
}
2022-07-27 17:51:53 +02:00
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 ;
}
2022-08-02 18:56:55 +02:00
2022-08-03 10:37:07 +02:00
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 ;
}
2022-09-01 13:43:22 +02:00
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 ;
}
2022-08-02 18:56:55 +02:00
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 ;
}