libdisplay-info/edid.c
Sebastian Wick 44e1810f2b edid: split the detailed timing signal union into separate structs
Nested structs makes it impossible to extend the nested struct after
extending the base struct. The union acts as a single struct in this
case but the nesting is still an issue. Use pointers to those structs instead
to keep all of them extensible. Only the struct which is applicable for the
signal type is not a null pointer to make sure only valid data is accessed.

Signed-off-by: Sebastian Wick <sebastian.wick@redhat.com>
2022-08-30 12:24:46 +02:00

1117 lines
30 KiB
C

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