mirror of
https://gitlab.freedesktop.org/emersion/libdisplay-info.git
synced 2024-12-26 21:59:15 +01:00
44e1810f2b
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>
1117 lines
30 KiB
C
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;
|
|
}
|