blob: 1239710d115fe1178674c7a0f0fd835979f107b6 [file] [log] [blame]
// SPDX-License-Identifier: MIT
/*
* Copyright 2006-2012 Red Hat, Inc.
* Copyright 2018-2020 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
*
* Author: Adam Jackson <[email protected]>
* Maintainer: Hans Verkuil <[email protected]>
*/
#include <ctype.h>
#include <fcntl.h>
#include <getopt.h>
#include <math.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "edid-decode.h"
static edid_state state;
static unsigned char edid[EDID_PAGE_SIZE * EDID_MAX_BLOCKS];
enum output_format {
OUT_FMT_DEFAULT,
OUT_FMT_HEX,
OUT_FMT_RAW,
OUT_FMT_CARRAY
};
/*
* Options
* Please keep in alphabetical order of the short option.
* That makes it easier to see which options are still free.
*/
enum Option {
OptCheck = 'c',
OptCheckInline = 'C',
OptExtract = 'e',
OptHelp = 'h',
OptOutputFormat = 'o',
OptPreferredTiming = 'p',
OptLongTimings = 'L',
OptShortTimings = 'S',
OptFBModeTimings = 'F',
OptXModeLineTimings = 'X',
OptV4L2Timings = 'V',
OptSkipHexDump = 's',
OptSkipSHA = 128,
OptLast = 256
};
static char options[OptLast];
static struct option long_options[] = {
{ "help", no_argument, 0, OptHelp },
{ "output-format", required_argument, 0, OptOutputFormat },
{ "extract", no_argument, 0, OptExtract },
{ "preferred-timing", no_argument, 0, OptPreferredTiming },
{ "skip-hex-dump", no_argument, 0, OptSkipHexDump },
{ "skip-sha", no_argument, 0, OptSkipSHA },
{ "check-inline", no_argument, 0, OptCheckInline },
{ "check", no_argument, 0, OptCheck },
{ "short-timings", no_argument, 0, OptShortTimings },
{ "long-timings", no_argument, 0, OptLongTimings },
{ "xmodeline", no_argument, 0, OptXModeLineTimings },
{ "fbmode", no_argument, 0, OptFBModeTimings },
{ "v4l2-timings", no_argument, 0, OptV4L2Timings },
{ 0, 0, 0, 0 }
};
static void usage(void)
{
printf("Usage: edid-decode <options> [in [out]]\n"
" [in] EDID file to parse. Read from standard input if none given\n"
" or if the input filename is '-'.\n"
" [out] Output the read EDID to this file. Write to standard output\n"
" if the output filename is '-'.\n"
"\nOptions:\n"
" -o, --output-format <fmt>\n"
" if [out] is specified, then write the EDID in this format\n"
" <fmt> is one of:\n"
" hex: hex numbers in ascii text (default for stdout)\n"
" raw: binary data (default unless writing to stdout)\n"
" carray: c-program struct\n"
" -c, --check check if the EDID conforms to the standards, failures and\n"
" warnings are reported at the end.\n"
" -C, --check-inline check if the EDID conforms to the standards, failures and\n"
" warnings are reported inline.\n"
" -p, --preferred-timing report the preferred timing\n"
" -S, --short-timings report all video timings in a short format\n"
" -L, --long-timings report all video timings in a long format\n"
" -X, --xmodeline report all long video timings in Xorg.conf format\n"
" -F, --fbmode report all long video timings in fb.modes format\n"
" -V, --v4l2-timings report all long video timings in v4l2-dv-timings.h format\n"
" -s, --skip-hex-dump skip the initial hex dump of the EDID\n"
" --skip-sha skip the SHA report\n"
" -e, --extract extract the contents of the first block in hex values\n"
" -h, --help display this help message\n");
}
static std::string s_msgs[EDID_MAX_BLOCKS + 1][2];
void msg(bool is_warn, const char *fmt, ...)
{
char buf[256] = "";
va_list ap;
va_start(ap, fmt);
vsprintf(buf, fmt, ap);
va_end(ap);
if (is_warn)
state.warnings++;
else
state.failures++;
if (state.data_block.empty())
s_msgs[state.block_nr][is_warn] += std::string(" ") + buf;
else
s_msgs[state.block_nr][is_warn] += " " + state.data_block + ": " + buf;
if (options[OptCheckInline])
printf("%s: %s", is_warn ? "WARN" : "FAIL", buf);
}
static void show_msgs(bool is_warn)
{
printf("\n%s:\n\n", is_warn ? "Warnings" : "Failures");
for (unsigned i = 0; i < state.num_blocks; i++) {
if (s_msgs[i][is_warn].empty())
continue;
printf("Block %u (%s):\n%s",
i, block_name(edid[i * EDID_PAGE_SIZE]).c_str(),
s_msgs[i][is_warn].c_str());
}
if (s_msgs[EDID_MAX_BLOCKS][is_warn].empty())
return;
printf("All Blocks:\n%s",
s_msgs[EDID_MAX_BLOCKS][is_warn].c_str());
}
void do_checksum(const char *prefix, const unsigned char *x, size_t len)
{
unsigned char check = x[len - 1];
unsigned char sum = 0;
unsigned i;
printf("%sChecksum: 0x%hhx", prefix, check);
for (i = 0; i < len-1; i++)
sum += x[i];
if ((unsigned char)(check + sum) != 0) {
printf(" (should be 0x%02x)\n", -sum & 0xff);
fail("Invalid checksum 0x%02x (should be 0x%02x).\n",
check, -sum & 0xff);
return;
}
printf("\n");
}
static unsigned gcd(unsigned a, unsigned b)
{
while (b) {
unsigned t = b;
b = a % b;
a = t;
}
return a;
}
void calc_ratio(struct timings *t)
{
unsigned d = gcd(t->hact, t->vact);
if (d == 0) {
t->hratio = t->vratio = 0;
return;
}
t->hratio = t->hact / d;
t->vratio = t->vact / d;
}
std::string edid_state::dtd_name()
{
unsigned len = std::to_string(preparse_total_dtds).length();
char buf[16];
sprintf(buf, "DTD %*u", len, dtd_cnt);
return buf;
}
bool edid_state::match_timings(const timings &t1, const timings &t2)
{
if (t1.hact != t2.hact ||
t1.vact != t2.vact ||
t1.rb != t2.rb ||
t1.interlaced != t2.interlaced ||
t1.hfp != t2.hfp ||
t1.hbp != t2.hbp ||
t1.hsync != t2.hsync ||
t1.pos_pol_hsync != t2.pos_pol_hsync ||
t1.hratio != t2.hratio ||
t1.vfp != t2.vfp ||
t1.vbp != t2.vbp ||
t1.vsync != t2.vsync ||
t1.pos_pol_vsync != t2.pos_pol_vsync ||
t1.vratio != t2.vratio ||
t1.pixclk_khz != t2.pixclk_khz)
return false;
return true;
}
static void or_str(std::string &s, const std::string &flag, unsigned &num_flags)
{
if (!num_flags)
s = flag;
else if (num_flags % 2 == 0)
s = s + " | \\\n\t\t" + flag;
else
s = s + " | " + flag;
num_flags++;
}
static void print_modeline(unsigned indent, const struct timings *t, double refresh)
{
unsigned offset = (!t->even_vtotal && t->interlaced) ? 1 : 0;
printf("%*sModeline \"%ux%u_%.2f%s\" %.3f %u %u %u %u %u %u %u %u %cHSync",
indent, "",
t->hact, t->vact, refresh,
t->interlaced ? "i" : "", t->pixclk_khz / 1000.0,
t->hact, t->hact + t->hfp, t->hact + t->hfp + t->hsync,
t->hact + t->hfp + t->hsync + t->hbp,
t->vact, t->vact + t->vfp, t->vact + t->vfp + t->vsync,
t->vact + t->vfp + t->vsync + t->vbp + offset,
t->pos_pol_hsync ? '+' : '-');
if (!t->no_pol_vsync)
printf(" %cVSync", t->pos_pol_vsync ? '+' : '-');
if (t->interlaced)
printf(" Interlace");
printf("\n");
}
static void print_fbmode(unsigned indent, const struct timings *t,
double refresh, double hor_freq_khz)
{
printf("%*smode \"%ux%u-%u%s\"\n",
indent, "",
t->hact, t->vact,
(unsigned)(0.5 + t->interlaced ? refresh / 2.0 : refresh),
t->interlaced ? "-lace" : "");
printf("%*s# D: %.2f MHz, H: %.3f kHz, V: %.2f Hz\n",
indent + 8, "",
t->pixclk_khz / 1000.0, hor_freq_khz, refresh);
printf("%*sgeometry %u %u %u %u 32\n",
indent + 8, "",
t->hact, t->vact, t->hact, t->vact);
unsigned mult = t->interlaced ? 2 : 1;
unsigned offset = !t->even_vtotal && t->interlaced;
printf("%*stimings %llu %d %d %d %u %u %u\n",
indent + 8, "",
(unsigned long long)(1000000000.0 / (double)(t->pixclk_khz) + 0.5),
t->hbp, t->hfp, mult * t->vbp, mult * t->vfp + offset, t->hsync, mult * t->vsync);
if (t->interlaced)
printf("%*slaced true\n", indent + 8, "");
if (t->pos_pol_hsync)
printf("%*shsync high\n", indent + 8, "");
if (t->pos_pol_vsync)
printf("%*svsync high\n", indent + 8, "");
printf("%*sendmode\n", indent, "");
}
static void print_v4l2_timing(const struct timings *t,
double refresh, const char *type)
{
printf("\t#define V4L2_DV_BT_%uX%u%c%u_%02u { \\\n",
t->hact, t->vact, t->interlaced ? 'I' : 'P',
(unsigned)refresh, (unsigned)(0.5 + 100.0 * (refresh - (unsigned)refresh)));
printf("\t\t.type = V4L2_DV_BT_656_1120, \\\n");
printf("\t\tV4L2_INIT_BT_TIMINGS(%u, %u, %u, ",
t->hact, t->vact, t->interlaced);
if (!t->pos_pol_hsync && !t->pos_pol_vsync)
printf("0, \\\n");
else if (t->pos_pol_hsync && t->pos_pol_vsync)
printf("\\\n\t\t\tV4L2_DV_HSYNC_POS_POL | V4L2_DV_VSYNC_POS_POL, \\\n");
else if (t->pos_pol_hsync)
printf("V4L2_DV_HSYNC_POS_POL, \\\n");
else
printf("V4L2_DV_VSYNC_POS_POL, \\\n");
printf("\t\t\t%lluULL, %d, %u, %d, %u, %u, %d, %u, %u, %d, \\\n",
t->pixclk_khz * 1000ULL, t->hfp, t->hsync, t->hbp,
t->vfp, t->vsync, t->vbp,
t->interlaced ? t->vfp : 0,
t->interlaced ? t->vsync : 0,
t->interlaced ? t->vbp + !t->even_vtotal : 0);
std::string flags;
unsigned num_flags = 0;
unsigned vic = 0;
unsigned hdmi_vic = 0;
const char *std = "0";
if (t->interlaced && !t->even_vtotal)
or_str(flags, "V4L2_DV_FL_HALF_LINE", num_flags);
if (!memcmp(type, "VIC", 3)) {
or_str(flags, "V4L2_DV_FL_HAS_CEA861_VIC", num_flags);
or_str(flags, "V4L2_DV_FL_IS_CE_VIDEO", num_flags);
vic = strtoul(type + 4, 0, 0);
}
if (!memcmp(type, "HDMI VIC", 8)) {
or_str(flags, "V4L2_DV_FL_HAS_HDMI_VIC", num_flags);
or_str(flags, "V4L2_DV_FL_IS_CE_VIDEO", num_flags);
hdmi_vic = strtoul(type + 9, 0, 0);
vic = hdmi_vic_to_vic(hdmi_vic);
if (vic)
or_str(flags, "V4L2_DV_FL_HAS_CEA861_VIC", num_flags);
}
if (vic && (fmod(refresh, 6)) == 0.0)
or_str(flags, "V4L2_DV_FL_CAN_REDUCE_FPS", num_flags);
if (t->rb)
or_str(flags, "V4L2_DV_FL_REDUCED_BLANKING", num_flags);
if (t->hratio && t->vratio)
or_str(flags, "V4L2_DV_FL_HAS_PICTURE_ASPECT", num_flags);
if (!memcmp(type, "VIC", 3) || !memcmp(type, "HDMI VIC", 8))
std = "V4L2_DV_BT_STD_CEA861";
else if (!memcmp(type, "DMT", 3))
std = "V4L2_DV_BT_STD_DMT";
else if (!memcmp(type, "CVT", 3))
std = "V4L2_DV_BT_STD_CVT";
else if (!memcmp(type, "GTF", 3))
std = "V4L2_DV_BT_STD_GTF";
printf("\t\t\t%s, \\\n", std);
printf("\t\t\t%s, \\\n", flags.empty() ? "0" : flags.c_str());
printf("\t\t\t{ %u, %u }, %u, %u) \\\n",
t->hratio, t->vratio, vic, hdmi_vic);
printf("\t}\n");
}
static void print_detailed_timing(unsigned indent, const struct timings *t)
{
printf("%*sHfront %4d Hsync %3u Hback %3d Hpol %s",
indent, "",
t->hfp, t->hsync, t->hbp, t->pos_pol_hsync ? "P" : "N");
if (t->hborder)
printf(" Hborder %u", t->hborder);
printf("\n");
printf("%*sVfront %4u Vsync %3u Vback %3d",
indent, "", t->vfp, t->vsync, t->vbp);
if (!t->no_pol_vsync)
printf(" Vpol %s", t->pos_pol_vsync ? "P" : "N");
if (t->vborder)
printf(" Vborder %u", t->vborder);
if (t->even_vtotal) {
printf(" Both Fields");
} else if (t->interlaced) {
printf(" Vfront +0.5 Odd Field\n");
printf("%*sVfront %4d Vsync %3u Vback %3d",
indent, "", t->vfp, t->vsync, t->vbp);
if (!t->no_pol_vsync)
printf(" Vpol %s", t->pos_pol_vsync ? "P" : "N");
if (t->vborder)
printf(" Vborder %u", t->vborder);
printf(" Vback +0.5 Even Field");
}
printf("\n");
}
bool edid_state::print_timings(const char *prefix, const struct timings *t,
const char *type, const char *flags,
bool detailed)
{
if (!t) {
// Should not happen
fail("Unknown video timings.\n");
return false;
}
if (!type)
type = "";
if (detailed && options[OptShortTimings])
detailed = false;
if (options[OptLongTimings])
detailed = true;
unsigned vact = t->vact;
unsigned hbl = t->hfp + t->hsync + t->hbp;
unsigned vbl = t->vfp + t->vsync + t->vbp;
unsigned htotal = t->hact + hbl;
double hor_freq_khz = htotal ? (double)t->pixclk_khz / htotal : 0;
if (t->interlaced)
vact /= 2;
double vtotal = vact + vbl;
bool ok = true;
if (!t->hact || !hbl || !t->hfp || !t->hsync ||
!vact || !vbl || (!t->vfp && !t->interlaced && !t->even_vtotal) || !t->vsync) {
fail("0 values in the video timing:\n"
" Horizontal Active/Blanking %u/%u\n"
" Horizontal Frontporch/Sync Width %u/%u\n"
" Vertical Active/Blanking %u/%u\n"
" Vertical Frontporch/Sync Width %u/%u\n",
t->hact, hbl, t->hfp, t->hsync, vact, vbl, t->vfp, t->vsync);
ok = false;
}
if (t->even_vtotal)
vtotal = vact + t->vfp + t->vsync + t->vbp;
else if (t->interlaced)
vtotal = vact + t->vfp + t->vsync + t->vbp + 0.5;
double refresh = (double)t->pixclk_khz * 1000.0 / (htotal * vtotal);
std::string s;
if (t->rb) {
s = "RB";
if (t->rb == 2)
s += "v2";
}
if (flags && *flags) {
if (!s.empty())
s += ", ";
s += flags;
}
if (t->hsize_mm || t->vsize_mm) {
if (!s.empty())
s += ", ";
s += std::to_string(t->hsize_mm) + " mm x " + std::to_string(t->vsize_mm) + " mm";
}
if (!s.empty())
s = " (" + s + ")";
char buf[10];
sprintf(buf, "%u%s", t->vact, t->interlaced ? "i" : "");
printf("%s%s: %5ux%-5s %7.3f Hz %3u:%-3u %7.3f kHz %7.3f MHz%s\n",
prefix, type,
t->hact, buf,
refresh,
t->hratio, t->vratio,
hor_freq_khz,
t->pixclk_khz / 1000.0,
s.c_str());
unsigned len = strlen(prefix) + strlen(type) + 2;
if (detailed && options[OptXModeLineTimings])
print_modeline(len, t, refresh);
else if (detailed && options[OptFBModeTimings])
print_fbmode(len, t, refresh, hor_freq_khz);
else if (detailed && options[OptV4L2Timings])
print_v4l2_timing(t, refresh, type);
else if (detailed)
print_detailed_timing(len, t);
if (t->hfp <= 0)
fail("0 or negative horizontal front porch.\n");
if (t->hbp <= 0)
fail("0 or negative horizontal back porch.\n");
if (t->vbp <= 0)
fail("0 or negative vertical back porch.\n");
if ((!max_display_width_mm && t->hsize_mm) ||
(!max_display_height_mm && t->vsize_mm)) {
fail("Mismatch of image size vs display size: image size is set, but not display size.\n");
} else if (!t->hsize_mm && !t->vsize_mm) {
/* this is valid */
} else if (t->hsize_mm > max_display_width_mm + 9 ||
t->vsize_mm > max_display_height_mm + 9) {
fail("Mismatch of image size %ux%u mm vs display size %ux%u mm.\n",
t->hsize_mm, t->vsize_mm, max_display_width_mm, max_display_height_mm);
} else if (t->hsize_mm < max_display_width_mm - 9 &&
t->vsize_mm < max_display_height_mm - 9) {
fail("Mismatch of image size %ux%u mm vs display size %ux%u mm.\n",
t->hsize_mm, t->vsize_mm, max_display_width_mm, max_display_height_mm);
}
if (refresh) {
min_vert_freq_hz = min(min_vert_freq_hz, refresh);
max_vert_freq_hz = max(max_vert_freq_hz, refresh);
}
if (t->pixclk_khz && (t->hact + hbl)) {
min_hor_freq_hz = min(min_hor_freq_hz, (t->pixclk_khz * 1000) / (t->hact + hbl));
max_hor_freq_hz = max(max_hor_freq_hz, (t->pixclk_khz * 1000) / (t->hact + hbl));
max_pixclk_khz = max(max_pixclk_khz, t->pixclk_khz);
}
return ok;
}
std::string utohex(unsigned char x)
{
char buf[10];
sprintf(buf, "0x%02hhx", x);
return buf;
}
const char *oui_name(unsigned oui, bool reverse)
{
if (reverse)
oui = (oui >> 16) | (oui & 0xff00) | ((oui & 0xff) << 16);
switch (oui) {
case 0x00001a: return "AMD";
case 0x000c03: return "HDMI";
case 0x00044b: return "NVIDIA";
case 0x000c6e: return "ASUS";
case 0x0010fa: return "Apple";
case 0x0014b9: return "MSTAR";
case 0x00d046: return "Dolby Vision";
case 0x00e047: return "InFocus";
case 0x3a0292: return "VESA";
case 0x90848b: return "HDR10+";
case 0xc45dd8: return "HDMI Forum";
case 0xca125c: return "Microsoft";
default: return NULL;
}
}
std::string ouitohex(unsigned oui)
{
char buf[32];
sprintf(buf, "%02X-%02X-%02X", (oui >> 16) & 0xff, (oui >> 8) & 0xff, oui & 0xff);
return buf;
}
bool memchk(const unsigned char *x, unsigned len, unsigned char v)
{
for (unsigned i = 0; i < len; i++)
if (x[i] != v)
return false;
return true;
}
void hex_block(const char *prefix, const unsigned char *x,
unsigned length, bool show_ascii, unsigned step)
{
unsigned i, j;
if (!length)
return;
for (i = 0; i < length; i += step) {
unsigned len = min(step, length - i);
printf("%s", prefix);
for (j = 0; j < len; j++)
printf("%s%02x", j ? " " : "", x[i + j]);
if (show_ascii) {
for (j = len; j < step; j++)
printf(" ");
printf(" '");
for (j = 0; j < len; j++)
printf("%c", x[i + j] >= ' ' && x[i + j] <= '~' ? x[i + j] : '.');
printf("'");
}
printf("\n");
}
}
static bool edid_add_byte(const char *s, bool two_digits = true)
{
char buf[3];
if (state.edid_size == sizeof(edid))
return false;
buf[0] = s[0];
buf[1] = two_digits ? s[1] : 0;
buf[2] = 0;
edid[state.edid_size++] = strtoul(buf, NULL, 16);
return true;
}
static bool extract_edid_quantumdata(const char *start)
{
/* Parse QuantumData 980 EDID files */
do {
start = strstr(start, ">");
if (!start)
return false;
start++;
for (unsigned i = 0; start[i] && start[i + 1] && i < 256; i += 2)
if (!edid_add_byte(start + i))
return false;
start = strstr(start, "<BLOCK");
} while (start);
return state.edid_size && !(state.edid_size % EDID_PAGE_SIZE);
}
static const char *ignore_chars = ",:;";
static bool extract_edid_hex(const char *s)
{
for (; *s; s++) {
if (isspace(*s) || strchr(ignore_chars, *s))
continue;
if (*s == '0' && tolower(s[1]) == 'x') {
s++;
continue;
}
/* Read one or two hex digits from the log */
if (!isxdigit(s[0])) {
if (state.edid_size && state.edid_size % 128 == 0)
break;
return false;
}
if (!edid_add_byte(s, isxdigit(s[1])))
return false;
if (isxdigit(s[1]))
s++;
}
return state.edid_size && !(state.edid_size % EDID_PAGE_SIZE);
}
static bool extract_edid_xrandr(const char *start)
{
static const char indentation1[] = " ";
static const char indentation2[] = "\t\t";
/* Used to detect that we've gone past the EDID property */
static const char half_indentation1[] = " ";
static const char half_indentation2[] = "\t";
const char *indentation;
const char *s;
for (;;) {
unsigned j;
/* Get the next start of the line of EDID hex, assuming spaces for indentation */
s = strstr(start, indentation = indentation1);
/* Did we skip the start of another property? */
if (s && s > strstr(start, half_indentation1))
break;
/* If we failed, retry assuming tabs for indentation */
if (!s) {
s = strstr(start, indentation = indentation2);
/* Did we skip the start of another property? */
if (s && s > strstr(start, half_indentation2))
break;
}
if (!s)
break;
start = s + strlen(indentation);
for (j = 0; j < 16; j++, start += 2) {
/* Read a %02x from the log */
if (!isxdigit(start[0]) || !isxdigit(start[1])) {
if (j)
break;
return false;
}
if (!edid_add_byte(start))
return false;
}
}
return state.edid_size && !(state.edid_size % EDID_PAGE_SIZE);
}
static bool extract_edid_xorg(const char *start)
{
bool find_first_num = true;
for (; *start; start++) {
if (find_first_num) {
const char *s;
/* skip ahead to the : */
s = strstr(start, ": \t");
if (!s)
s = strstr(start, ": ");
if (!s)
break;
start = s;
/* and find the first number */
while (!isxdigit(start[1]))
start++;
find_first_num = false;
continue;
} else {
/* Read a %02x from the log */
if (!isxdigit(*start)) {
find_first_num = true;
continue;
}
if (!edid_add_byte(start))
return false;
start++;
}
}
return state.edid_size && !(state.edid_size % EDID_PAGE_SIZE);
}
static bool extract_edid(int fd)
{
std::vector<char> edid_data;
char buf[EDID_PAGE_SIZE];
for (;;) {
ssize_t i = read(fd, buf, sizeof(buf));
if (i < 0)
return false;
if (i == 0)
break;
edid_data.insert(edid_data.end(), buf, buf + i);
}
if (edid_data.empty())
return false;
const char *data = &edid_data[0];
const char *start;
/* Look for edid-decode output */
start = strstr(data, "EDID (hex):");
if (!start)
start = strstr(data, "edid-decode (hex):");
if (start)
return extract_edid_hex(strchr(start, ':'));
/* Look for C-array */
start = strstr(data, "unsigned char edid[] = {");
if (start)
return extract_edid_hex(strchr(start, '{') + 1);
/* Look for QuantumData EDID output */
start = strstr(data, "<BLOCK");
if (start)
return extract_edid_quantumdata(start);
/* Look for xrandr --verbose output (lines of 16 hex bytes) */
start = strstr(data, "EDID_DATA:");
if (!start)
start = strstr(data, "EDID:");
if (start)
return extract_edid_xrandr(start);
/* Look for an EDID in an Xorg.0.log file */
start = strstr(data, "EDID (in hex):");
if (start)
start = strstr(start, "(II)");
if (start)
return extract_edid_xorg(start);
unsigned i;
/* Is the EDID provided in hex? */
for (i = 0; i < 32 && (isspace(data[i]) || strchr(ignore_chars, data[i]) ||
tolower(data[i]) == 'x' || isxdigit(data[i])); i++);
if (i == 32)
return extract_edid_hex(data);
/* Assume binary */
if (edid_data.size() % EDID_PAGE_SIZE || edid_data.size() > sizeof(edid))
return false;
memcpy(edid, data, edid_data.size());
state.edid_size = edid_data.size();
return true;
}
static void print_subsection(const char *name, const unsigned char *edid,
unsigned start, unsigned end)
{
unsigned i;
printf("%s:", name);
for (i = strlen(name); i < 15; i++)
printf(" ");
for (i = start; i <= end; i++)
printf(" %02x", edid[i]);
printf("\n");
}
static void dump_breakdown(const unsigned char *edid)
{
printf("Extracted contents:\n");
print_subsection("header", edid, 0, 7);
print_subsection("serial number", edid, 8, 17);
print_subsection("version", edid,18, 19);
print_subsection("basic params", edid, 20, 24);
print_subsection("chroma info", edid, 25, 34);
print_subsection("established", edid, 35, 37);
print_subsection("standard", edid, 38, 53);
print_subsection("descriptor 1", edid, 54, 71);
print_subsection("descriptor 2", edid, 72, 89);
print_subsection("descriptor 3", edid, 90, 107);
print_subsection("descriptor 4", edid, 108, 125);
print_subsection("extensions", edid, 126, 126);
print_subsection("checksum", edid, 127, 127);
printf("\n");
}
static unsigned char crc_calc(const unsigned char *b)
{
unsigned char sum = 0;
unsigned i;
for (i = 0; i < 127; i++)
sum += b[i];
return 256 - sum;
}
static int crc_ok(const unsigned char *b)
{
return crc_calc(b) == b[127];
}
static void hexdumpedid(FILE *f, const unsigned char *edid, unsigned size)
{
unsigned b, i, j;
for (b = 0; b < size / 128; b++) {
const unsigned char *buf = edid + 128 * b;
if (b)
fprintf(f, "\n");
for (i = 0; i < 128; i += 0x10) {
fprintf(f, "%02x", buf[i]);
for (j = 1; j < 0x10; j++) {
fprintf(f, " %02x", buf[i + j]);
}
fprintf(f, "\n");
}
if (!crc_ok(buf))
fprintf(f, "Block %u has a checksum error (should be 0x%02x)\n",
b, crc_calc(buf));
}
}
static void carraydumpedid(FILE *f, const unsigned char *edid, unsigned size)
{
unsigned b, i, j;
fprintf(f, "const unsigned char edid[] = {\n");
for (b = 0; b < size / 128; b++) {
const unsigned char *buf = edid + 128 * b;
if (b)
fprintf(f, "\n");
for (i = 0; i < 128; i += 8) {
fprintf(f, "\t0x%02x,", buf[i]);
for (j = 1; j < 8; j++) {
fprintf(f, " 0x%02x,", buf[i + j]);
}
fprintf(f, "\n");
}
if (!crc_ok(buf))
fprintf(f, "\t/* Block %u has a checksum error (should be 0x%02x) */\n",
b, crc_calc(buf));
}
fprintf(f, "};\n");
}
static int edid_to_file(const char *to_file, enum output_format out_fmt)
{
FILE *out;
if (!strcmp(to_file, "-")) {
to_file = "stdout";
out = stdout;
} else if ((out = fopen(to_file, "w")) == NULL) {
perror(to_file);
return -1;
}
if (out_fmt == OUT_FMT_DEFAULT)
out_fmt = out == stdout ? OUT_FMT_HEX : OUT_FMT_RAW;
switch (out_fmt) {
default:
case OUT_FMT_HEX:
hexdumpedid(out, edid, state.edid_size);
break;
case OUT_FMT_RAW:
fwrite(edid, state.edid_size, 1, out);
break;
case OUT_FMT_CARRAY:
carraydumpedid(out, edid, state.edid_size);
break;
}
if (out != stdout)
fclose(out);
return 0;
}
static int edid_from_file(const char *from_file)
{
int fd;
if (!strcmp(from_file, "-")) {
from_file = "stdin";
fd = 0;
} else if ((fd = open(from_file, O_RDONLY)) == -1) {
perror(from_file);
return -1;
}
if (!extract_edid(fd)) {
fprintf(stderr, "EDID extract of '%s' failed\n", from_file);
return -1;
}
state.num_blocks = state.edid_size / EDID_PAGE_SIZE;
if (fd != 0)
close(fd);
if (memcmp(edid, "\x00\xFF\xFF\xFF\xFF\xFF\xFF\x00", 8)) {
fprintf(stderr, "No EDID header found in '%s'\n", from_file);
return -1;
}
return 0;
}
/* generic extension code */
std::string block_name(unsigned char block)
{
char buf[10];
switch (block) {
case 0x00: return "Base Block";
case 0x02: return "CTA-861 Extension Block";
case 0x10: return "VTB Extension Block";
case 0x40: return "Display Information Extension Block";
case 0x50: return "Localized String Extension Block";
case 0x70: return "DisplayID Extension Block";
case 0xf0: return "Block Map Extension Block";
case 0xff: return "Manufacturer-Specific Extension Block";
default:
sprintf(buf, " 0x%02x", block);
return std::string("Unknown EDID Extension Block") + buf;
}
}
void edid_state::parse_block_map(const unsigned char *x)
{
unsigned last_valid_block_tag = 0;
bool fail_once = false;
unsigned offset = 1;
unsigned i;
printf("%s\n", block.c_str());
if (block_nr == 1)
saw_block_1 = true;
else if (!saw_block_1)
fail("No EDID Block Map Extension found in block 1.\n");
if (block_nr > 1)
offset = 128;
for (i = 1; i < 127; i++) {
unsigned block = offset + i;
if (x[i]) {
last_valid_block_tag++;
if (i != last_valid_block_tag && !fail_once) {
fail("Valid block tags are not consecutive.\n");
fail_once = true;
}
printf(" Block %3u: %s\n", block, block_name(block).c_str());
if (block >= num_blocks && !fail_once) {
fail("Invalid block number %u.\n", block);
fail_once = true;
}
}
}
}
void edid_state::preparse_extension(const unsigned char *x)
{
switch (x[0]) {
case 0x02:
preparse_cta_block(x);
break;
case 0x10:
preparse_vtb_ext_block(x);
break;
case 0x70:
preparse_displayid_block(x);
break;
}
}
void edid_state::parse_extension(const unsigned char *x)
{
block = block_name(x[0]);
data_block.clear();
printf("\n");
switch (x[0]) {
case 0x02:
parse_cta_block(x);
break;
case 0x10:
parse_vtb_ext_block(x);
break;
case 0x20:
printf("%s\n", block.c_str());
fail("Deprecated extension block, do not use.\n");
break;
case 0x40:
parse_di_ext_block(x);
break;
case 0x50:
parse_ls_ext_block(x);
break;
case 0x70:
parse_displayid_block(x);
break;
case 0xf0:
parse_block_map(x);
if (block_nr != 1 && block_nr != 128)
fail("Must be used in block 1 and 128.\n");
break;
default:
printf("%s\n", block.c_str());
hex_block(" ", x, EDID_PAGE_SIZE);
fail("Unknown Extension Block.\n");
break;
}
data_block.clear();
do_checksum("", x, EDID_PAGE_SIZE);
}
int edid_state::parse_edid()
{
if (!options[OptSkipHexDump]) {
printf("edid-decode (hex):\n\n");
for (unsigned i = 0; i < num_blocks; i++) {
hex_block("", edid + i * EDID_PAGE_SIZE, EDID_PAGE_SIZE, false);
printf("\n");
}
printf("----------------\n\n");
}
if (options[OptExtract])
dump_breakdown(edid);
for (unsigned i = 1; i < num_blocks; i++)
preparse_extension(edid + i * EDID_PAGE_SIZE);
block = block_name(0x00);
parse_base_block(edid);
for (unsigned i = 1; i < num_blocks; i++) {
block_nr++;
printf("\n----------------\n");
parse_extension(edid + i * EDID_PAGE_SIZE);
}
block = "";
block_nr = EDID_MAX_BLOCKS;
if (uses_gtf && !supports_gtf)
fail("GTF timings are used, but the EDID does not signal GTF support.\n");
if (uses_cvt && !supports_cvt)
fail("CVT timings are used, but the EDID does not signal CVT support.\n");
if (has_display_range_descriptor &&
(min_vert_freq_hz < min_display_vert_freq_hz ||
max_vert_freq_hz > max_display_vert_freq_hz ||
min_hor_freq_hz / 1000 < min_display_hor_freq_hz / 1000 ||
max_hor_freq_hz / 1000 > max_display_hor_freq_hz / 1000 ||
max_pixclk_khz > max_display_pixclk_khz)) {
/*
* EDID 1.4 states (in an Errata) that explicitly defined
* timings supersede the monitor range definition.
*/
char buf[512];
snprintf(buf, sizeof(buf),
"One or more of the timings is out of range of the Monitor Ranges:\n"
" Vertical Freq: %u - %u Hz (Monitor: %u - %u Hz)\n"
" Horizontal Freq: %.3f - %.3f kHz (Monitor: %.3f - %.3f kHz)\n"
" Maximum Clock: %.3f MHz (Monitor: %.3f MHz)\n",
min_vert_freq_hz, max_vert_freq_hz,
min_display_vert_freq_hz, max_display_vert_freq_hz,
min_hor_freq_hz / 1000.0, max_hor_freq_hz / 1000.0,
min_display_hor_freq_hz / 1000.0, max_display_hor_freq_hz / 1000.0,
max_pixclk_khz / 1000.0, max_display_pixclk_khz / 1000.0);
msg(edid_minor >= 4, "%s", buf);
}
if (options[OptPreferredTiming]) {
printf("\n----------------\n");
printf("\nPreferred Video Timing:\n");
print_timings("", &preferred_timings,
preferred_type.c_str(),
preferred_flags.c_str(), true);
}
if (!options[OptCheck] && !options[OptCheckInline])
return 0;
printf("\n----------------\n");
if (!options[OptSkipSHA]) {
#ifdef SHA
#define STR(x) #x
#define STRING(x) STR(x)
printf("\nedid-decode SHA: %s\n", STRING(SHA));
#else
printf("\nedid-decode SHA: not available\n");
#endif
}
if (options[OptCheck]) {
if (warnings)
show_msgs(true);
if (failures)
show_msgs(false);
}
printf("\nEDID conformity: %s\n", failures ? "FAIL" : "PASS");
return failures ? -2 : 0;
}
int main(int argc, char **argv)
{
char short_options[26 * 2 * 2 + 1];
enum output_format out_fmt = OUT_FMT_DEFAULT;
int ret;
while (1) {
int option_index = 0;
unsigned idx = 0;
unsigned i;
for (i = 0; long_options[i].name; i++) {
if (!isalpha(long_options[i].val))
continue;
short_options[idx++] = long_options[i].val;
if (long_options[i].has_arg == required_argument)
short_options[idx++] = ':';
}
short_options[idx] = 0;
int ch = getopt_long(argc, argv, short_options,
long_options, &option_index);
if (ch == -1)
break;
options[ch] = 1;
switch (ch) {
case OptHelp:
usage();
return -1;
case OptOutputFormat:
if (!strcmp(optarg, "hex")) {
out_fmt = OUT_FMT_HEX;
} else if (!strcmp(optarg, "raw")) {
out_fmt = OUT_FMT_RAW;
} else if (!strcmp(optarg, "carray")) {
out_fmt = OUT_FMT_CARRAY;
} else {
usage();
exit(1);
}
break;
case ':':
fprintf(stderr, "Option '%s' requires a value\n",
argv[optind]);
usage();
return -1;
case '?':
fprintf(stderr, "Unknown argument '%s'\n",
argv[optind]);
usage();
return -1;
}
}
if (optind == argc)
ret = edid_from_file("-");
else
ret = edid_from_file(argv[optind]);
if (optind < argc - 1)
return ret ? ret : edid_to_file(argv[optind + 1], out_fmt);
return ret ? ret : state.parse_edid();
}