libdisplay-info: replace the stub with upstream 0.4.0
redbear-ci / check (push) Has been cancelled

The recipe was not building libdisplay-info. It synthesized its own
meson.build declaring `version: '0.2.3'` and compiled a hand-written
stub: one di.c and four headers, 518 lines, 20 exported functions.

0.2.3 does not exist upstream -- the tags are 0.1.0, 0.1.1, 0.2.0, 0.3.0
and 0.4.0. That fabricated version satisfied kwin's

    pkg_check_modules(libdisplayinfo REQUIRED IMPORTED_TARGET libdisplay-info>=0.2.0)

so kwin configured cleanly and then failed to compile utils/edid.cpp on
di_info_get_default_color_primaries, di_info_get_hdr_static_metadata,
di_info_get_supported_signal_colorimetry and di_edid_display_descriptor.
A version gate satisfied by a declaration rather than an implementation.

Now vendors upstream 0.4.0 (gitlab.freedesktop.org/emersion/libdisplay-info):
14 C files, 10898 lines, 10 headers, 94 exported symbols. 0.4.0 rather than
0.2.0 because the colorimetry and HDR static metadata accessors kwin needs
post-date the 0.2.x series -- and they feed the real colour-primaries and
HDR path of the display stack, so a stub returning NULL would have been
wrong at runtime even if it had compiled.

Red Bear delta is one hunk in source/meson.build: the unconditional
subdir('di-edid-decode') and subdir('test') are commented out. Both build
auxiliary executables and a shell test harness that are not part of the
runtime and do not cross-compile for Redox. Library, headers and
pkg-config are untouched.

Verified: builds clean, stages libdisplay-info.so.0.4.0 with all four
symbols kwin needs, pkg-config reports 0.4.0, and kwin's utils/edid.cpp
now compiles.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-03 22:19:38 +03:00
parent d1e8202958
commit e4a44377fb
45 changed files with 16074 additions and 574 deletions
+35 -50
View File
@@ -2,55 +2,40 @@
path = "source"
[build]
template = "custom"
script = '''
DYNAMIC_INIT
python3 - <<'PY' "${COOKBOOK_SOURCE}"
from pathlib import Path
import sys
source_root = Path(sys.argv[1])
meson_build = source_root / "meson.build"
if not meson_build.exists():
meson_build.write_text(
"""project('libdisplay-info', 'c',
version: '0.2.3',
meson_version: '>= 0.54.0',
default_options: ['warning_level=1', 'buildtype=debugoptimized'])
inc = include_directories('include')
libdisplay_info = shared_library('display-info',
'di.c',
include_directories: inc,
version: meson.project_version(),
install: true)
install_headers(
'include/libdisplay-info/cta.h',
'include/libdisplay-info/displayid.h',
'include/libdisplay-info/edid.h',
'include/libdisplay-info/info.h',
subdir: 'libdisplay-info',
)
pkg = import('pkgconfig')
pkg.generate(
libdisplay_info,
name: 'libdisplay-info',
description: 'Display identification data parsing library',
filebase: 'libdisplay-info',
version: meson.project_version(),
)
"""
)
PY
cookbook_meson
'''
template = "meson"
[package]
version = "0.2.3"
description = "libdisplay-info — EDID and display descriptor parsing library. Real C implementation (v6.0 2026) handling base EDID vendor/product, strings, physical size, chromaticity, detailed timings, and preferred-timing metadata. CTA/DisplayID remain unsupported (bounded by Red Bear's current DRM/KMS validation surface). Replaces the prior libdisplay-info-stub that always returned NULL."
version = "0.4.0+rb0.3.2"
description = """
libdisplay-info — EDID and DisplayID parsing library.
Upstream libdisplay-info 0.4.0 (gitlab.freedesktop.org/emersion/libdisplay-info),
vendored under Red Bear's local-fork convention.
This REPLACES a hand-written stub. The stub was 518 lines across one di.c and
four headers, exposing 20 functions, and it synthesized its own meson.build
declaring `version: '0.2.3'` -- a version that does not exist upstream (the tags
are 0.1.0, 0.1.1, 0.2.0, 0.3.0, 0.4.0). That fabricated version satisfied
kwin's `pkg_check_modules(libdisplay-info>=0.2.0)` while the API behind it was
absent, so kwin configured successfully and then failed to compile:
utils/edid.cpp: 'di_info_get_default_color_primaries' was not declared
utils/edid.cpp: 'di_info_get_hdr_static_metadata' was not declared
utils/edid.cpp: 'di_info_get_supported_signal_colorimetry' was not declared
utils/edid.cpp: 'di_edid_display_descriptor' does not name a type
Those four are why 0.4.0 rather than 0.2.0: the colorimetry and HDR static
metadata accessors kwin needs post-date the 0.2.x series. They feed the real
colour-primaries and HDR path of the display stack, so a stub returning NULL
would have been wrong at runtime even had it compiled.
Red Bear delta (one hunk, in source/meson.build): the unconditional
`subdir('di-edid-decode')` and `subdir('test')` are commented out. Both build
auxiliary executables and a shell test harness that are not part of the runtime
and do not cross-compile cleanly for Redox. The library, its headers and its
pkg-config file are unaffected.
The pnp-id table is generated at build time by tool/gen-search-table.py from
hwdata's pnp.ids, resolved via the host's `hwdata` pkg-config (native: true),
falling back to /usr/share/hwdata/pnp.ids.
"""
@@ -0,0 +1,18 @@
root = true
[*]
charset = utf-8
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = tab
indent_size = 8
max_line_length = 100
[*.py]
indent_style = space
indent_size = 4
[*.yml]
indent_style = space
indent_size = 2
@@ -0,0 +1,158 @@
include:
- project: 'freedesktop/ci-templates'
ref: 3f37cc0e461f5b0c815409bf6f55759f26a74e9c
file:
- '/templates/ci-fairy.yml'
- '/templates/alpine.yml'
# When updating the prepare-container step, make sure to bump
# FDO_DISTRIBUTION_TAG, otherwise the container won't get rebuilt.
# To force a rebuild of the container, use:
# $ git push -f -o ci.variable="FDO_FORCE_REBUILD=1"
variables:
FDO_UPSTREAM_REPO: 'emersion/libdisplay-info'
FDO_DISTRIBUTION_TAG: '2025-01-20.0'
workflow:
rules:
- if: $CI_PIPELINE_SOURCE == 'merge_request_event'
- if: $CI_PIPELINE_SOURCE == 'push'
stages:
- "Contribution checks"
- "Prepare container"
- "Build and test"
- "Publish"
check-mr:
extends: .fdo.ci-fairy
stage: "Contribution checks"
script:
- ci-fairy check-commits --signed-off-by
rules:
- if: '$CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "main"'
when: always
- when: never
prepare-container:
extends: .fdo.container-build@alpine@x86_64
stage: "Prepare container"
variables:
V4L_UTILS_COMMIT: 1316a80455ef70889bea89491f37cd69170f3ee7
FDO_BASE_IMAGE: alpine:latest
FDO_DISTRIBUTION_PACKAGES: |
build-base clang compiler-rt meson make git gcovr py3-pygments go hwdata
perl argp-standalone gettext-dev linux-headers
FDO_DISTRIBUTION_EXEC: |
grep $V4L_UTILS_COMMIT ./subprojects/v4l-utils.wrap >/dev/null || \
(echo "Keep v4l-utils.wrap and CI in sync!" && exit 1)
git clone https://git.linuxtv.org/v4l-utils.git
cd v4l-utils
git checkout $V4L_UTILS_COMMIT
meson setup build/ \
-Dauto_features=disabled \
-Dv4l-plugins=false \
-Dv4l-wrappers=false \
-Dv4l2-compliance-libv4l=false \
-Dv4l2-ctl-libv4l=false \
-Dv4l2-ctl-stream-to=false
ninja -C build/
ninja -C build/ install
cd ..
rm -rf edid-decode
go install git.sr.ht/~emersion/gyosu@latest
mv ~/go/bin/gyosu /usr/bin/
rm -rf ~/go
rules:
- when: on_success
testing-check:
extends: .fdo.distribution-image@alpine
stage: "Build and test"
script:
- |
# Check if the origin of all test EDIDs is documented
for f in ./test/data/*.edid; do
grep -q "^$(basename ${f%.edid})\s" ./test/data/README.md ||
(echo "$f not in README.md" && exit 1)
done
# Check if the test references checked in the repo match what we have in CI
meson setup build/
ninja -C build/ gen-test-data
git diff --quiet ||
(echo "Checked in reference output does not match generated reference output" && exit 1)
rules:
- when: on_success
build-gcc:
extends: .fdo.distribution-image@alpine
stage: "Build and test"
script:
- CC=gcc
CFLAGS=" -Wnull-dereference -fanalyzer -O1"
meson setup build/
--fatal-meson-warnings
-Dwerror=true
-Db_coverage=true
- ninja -C build/
- ninja -C build/ test
- ninja -C build/ -j1 coverage-xml coverage-html
artifacts:
when: always
paths:
- build/meson-logs/
reports:
junit: build/meson-logs/testlog.junit.xml
coverage_report:
coverage_format: cobertura
path: build/meson-logs/coverage.xml
rules:
- when: on_success
build-clang:
extends: .fdo.distribution-image@alpine
stage: "Build and test"
script:
- CC=clang
meson setup build/
--fatal-meson-warnings
-Dwerror=true
-Db_sanitize=address,undefined
-Db_lundef=false
- ninja -C build/
- UBSAN_OPTIONS=halt_on_error=1 ninja -C build/ test
artifacts:
when: always
paths:
- build/meson-logs/
reports:
junit: build/meson-logs/testlog.junit.xml
rules:
- when: on_success
build-docs:
extends: .fdo.distribution-image@alpine
stage: "Build and test"
script:
- gyosu -I$PWD/include/ -fexported-symbols='di_*'
-ffile-prefix-map=$PWD/include/= -fsite-name=libdisplay-info
-o public $PWD/include/libdisplay-info/
artifacts:
paths:
- public/
rules:
- when: on_success
pages:
extends: .fdo.distribution-image@alpine
stage: "Publish"
script:
- "true"
artifacts:
paths:
- public/
rules:
- if: '$CI_PROJECT_PATH == "emersion/libdisplay-info" && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
when: on_success
- when: never
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 The libdisplay-info Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,141 @@
# libdisplay-info
EDID and DisplayID library.
Goals:
- Provide a set of high-level, easy-to-use, opinionated functions as well as
low-level functions to access detailed information.
- Simplicity and correctness over performance and resource usage.
- Well-tested and fuzzed.
Documentation is available on the [website].
## Using
The public API headers are categorised as either high-level or low-level API
as per the comments in the header files. Users of libdisplay-info should prefer
high-level API over low-level API when possible.
If high-level API lacks needed features, please propose additions to the
high-level API upstream before using low-level API to get what you need.
If the additions are rejected, you are welcome to use the low-level API.
This policy is aimed to propagate best practises when interpreting EDID
and DisplayID information which can often be cryptic or even inconsistent.
libdisplay-info uses [semantic versioning]. The public API is not yet stable.
## Contributing
Open issues and merge requests on the [GitLab project]. Discuss and ask
questions in the [#wayland] IRC channel on OFTC.
In general, the [Wayland contribution guidelines] should be followed. In
particular, each commit must carry a Signed-off-by tag to denote that the
submitter adheres to the [Developer Certificate of Origin 1.1]. This project
follows the [freedesktop.org Contributor Covenant].
## Specifications
Both EDID and DisplayID are defined by VESA and the specifications are publicly
available. There are multiple versions defined and they have multiple
mechanisms for extending the base specification. Some of the extensions also
have mechanisms for additional extensions. This sometimes makes it hard to find
where a particular data structure is defined.
The raw data is usually read from the Display Data Channel (DDC) but other
methods of delivery exist.
Available freely [from VESA directly] are the base EDID specifications, VESA
specified EDID extensions, as well as the base DisplayID specifications. EDID
also specifies how DisplayID can be embedded in EDID.
* VESA Enhanced Extended Display Identification Data (E-EDID) Standard;
Release A, Revision 1; February 9, 2000; Defines EDID Structure Version 1,
Revision 3
* VESA Enhanced Extended Display Identification Data (E-EDID) Standard;
Release A, Revision 2; September 25, 2006; Defines EDID Structure Version 1,
Revision 4
* VESA Video Timing Block Extension Data (VTB-EXT) Standard; Release A;
November 24, 2003
* VESA Enhanced EDID Localized String Extension Standard; Release A;
July 10, 2003
* VESA Display Transfer Characteristics Data Block (DTCDB) Standard;
Version 1.0; August 31, 2006
* VESA Display Information Extension Block (DI-EXT) Standard; Release A;
August 21, 2001
* VESA Display Device Data Block (DDDB) Standard; Version 1;
September 25, 2006
* VESA Display Identification Data (DisplayID) Standard; Version 1.3;
July 5, 2013
* VESA Display Identification Data (DisplayID) Standard; Version 2.0;
11 September, 2017
* VESA Display Identification Data (DisplayID) Standard; Version 2.1;
18 November, 2021
* VESA Display Identification Data (DisplayID) Standard; Version 2.1a;
18 March, 2024
The most common extension to both EDID and DisplayID is CTA-861 which can be
downloaded for free [from CTA].
* A DTV Profile for Uncompressed High Speed Digital Interfaces
(CTA-861-E, CTA-861-F, CTA-861-G, CTA-861-H, CTA-861-I)
* HDR Static Metadata Extensions (CTA-861.3-A)
The CTA-861 specification allows for Vendor Specific Data Blocks (VSDB). Their
specifications are often proprietary and have to be reverse engineered. Some
specifications are available publicly.
* HDMI and HDMI Forum VSDB are specified in the HDMI specification
* Microsoft EDID Extension for [HMDs and Specialized Monitors]
The libdisplay-info structures of the low-level API always point to the
relevant specification and section therein.
## Building
libdisplay-info has the following dependencies:
- [hwdata] for the PNP ID database used at build-time only.
libdisplay-info is built using [Meson]:
meson setup build/
ninja -C build/
## Testing
The low-level EDID library is tested against [edid-decode]. `test/data/`
contains a small collection of EDID blobs and diffs between upstream
`edid-decode` and our `di-edid-decode` clone. Our CI ensures the diffs are
up-to-date. A patch should never make the diffs grow larger. To re-generate the
test data, build `edid-decode` at the Git revision mentioned in
`.gitlab-ci.yml`, put the executable in `PATH`, and run
`ninja -C build/ gen-test-data`.
The latest code coverage report is available on [GitLab CI][coverage].
## Fuzzing
To fuzz libdisplay-info with [AFL], the library needs to be instrumented:
CC=afl-gcc meson build/
ninja -C build/
afl-fuzz -i test/data/ -o afl/ build/di-edid-decode/di-edid-decode
[website]: https://emersion.pages.freedesktop.org/libdisplay-info/
[semantic versioning]: https://semver.org/
[GitLab project]: https://gitlab.freedesktop.org/emersion/libdisplay-info
[#wayland]: ircs://irc.oftc.net/#wayland
[Wayland contribution guidelines]: https://gitlab.freedesktop.org/wayland/wayland/-/blob/main/CONTRIBUTING.md
[Developer Certificate of Origin 1.1]: https://developercertificate.org/
[freedesktop.org Contributor Covenant]: https://www.freedesktop.org/wiki/CodeOfConduct/
[from VESA directly]: https://vesa.org/vesa-standards/
[from CTA]: https://www.cta.tech/SearchResults?search=CTA-861
[HMDs and Specialized Monitors]: https://learn.microsoft.com/en-us/windows-hardware/drivers/display/specialized-monitors-edid-extension
[hwdata]: https://github.com/vcrhonek/hwdata
[Meson]: https://mesonbuild.com/
[coverage]: https://gitlab.freedesktop.org/emersion/libdisplay-info/-/jobs/artifacts/main/file/build/meson-logs/coveragereport/index.html?job=build-gcc
[edid-decode]: https://git.linuxtv.org/edid-decode.git/
[AFL]: https://lcamtuf.coredump.cx/afl/
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,39 @@
#include <libdisplay-info/cta-vic.h>
extern const struct di_cta_vic_video_format _di_cta_vic_video_formats[];
extern const size_t _di_cta_vic_video_formats_len;
const struct di_cta_vic_video_format *
di_cta_vic_video_format_from_vic(struct di_cta_vic vic)
{
if (vic.code >= _di_cta_vic_video_formats_len ||
_di_cta_vic_video_formats[vic.code].vic.code == 0)
return NULL;
return &_di_cta_vic_video_formats[vic.code];
}
struct di_cta_vic
di_cta_vic_video_format_to_vic(const struct di_cta_vic_video_format *format)
{
size_t i;
for (i = 0; i < _di_cta_vic_video_formats_len; i++) {
const struct di_cta_vic_video_format *candidate = &_di_cta_vic_video_formats[i];
if (candidate->vic.code != 0 &&
candidate->h_active == format->h_active &&
candidate->v_active == format->v_active &&
candidate->h_sync == format->h_sync &&
candidate->v_sync == format->v_sync &&
candidate->h_back == format->h_back &&
candidate->v_back == format->v_back &&
candidate->h_sync_polarity == format->h_sync_polarity &&
candidate->v_sync_polarity == format->v_sync_polarity &&
candidate->pixel_clock_hz == format->pixel_clock_hz &&
candidate->interlaced == format->interlaced &&
candidate->picture_aspect_ratio == format->picture_aspect_ratio)
return candidate->vic;
}
return (struct di_cta_vic) { .code = 0 };
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,155 @@
/*
* Copyright 2006-2012 Red Hat, Inc.
* Copyright 2018-2021 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
*
* Originally imported from edid-decode.
*/
#include <math.h>
#include <libdisplay-info/cvt.h>
/**
* The size of the top and bottom overscan margin as a percentage of the active
* vertical image.
*/
#define MARGIN_PERC 1.8
#define MIN_VSYNC_BP 550.0
#define MIN_V_PORCH 3
/**
* Minimum vertical backporch for CVT and CVT RBv1.
*/
#define MIN_V_BPORCH 7
/**
* Fixed vertical backporch for CVT RBv2 and RBv3.
*/
#define FIXED_V_BPORCH 6
#define C_PRIME 30.0
#define M_PRIME 300.0
/**
* Minimum VBlank period (in µs) for RB timings.
*/
#define RB_MIN_VBLANK 460.0
void
di_cvt_compute(struct di_cvt_timing *t, const struct di_cvt_options *options)
{
enum di_cvt_reduced_blanking_version rb = options->red_blank_ver;
double cell_gran, h_pixels_rnd, v_lines_rnd, hor_margin, vert_margin,
interlace, total_active_pixels, v_field_rate_rqd, clock_step,
h_blank, rb_v_fporch, refresh_multiplier, rb_min_vblank, h_sync,
v_sync, pixel_freq, v_blank, v_sync_bp, additional_hblank,
h_period_est, ideal_duty_cycle, total_pixels, vbi_lines,
rb_v_bporch, rb_min_vbi, total_v_lines, freq, h_front_porch,
v_back_porch, act_h_freq;
cell_gran = rb == DI_CVT_REDUCED_BLANKING_V2 ? 1 : 8;
h_pixels_rnd = floor(options->h_pixels / cell_gran) * cell_gran;
v_lines_rnd = options->int_rqd ? floor(options->v_lines / 2.0) : options->v_lines;
hor_margin = options->margins_rqd
? floor((h_pixels_rnd * MARGIN_PERC / 100.0) / cell_gran) * cell_gran
: 0;
vert_margin = options->margins_rqd
? floor(MARGIN_PERC / 100.0 * v_lines_rnd)
: 0;
interlace = options->int_rqd ? 0.5 : 0;
total_active_pixels = h_pixels_rnd + hor_margin * 2;
v_field_rate_rqd = options->int_rqd
? options->ip_freq_rqd * 2
: options->ip_freq_rqd;
clock_step = rb >= DI_CVT_REDUCED_BLANKING_V2 ? 0.001 : 0.25;
h_blank = rb == DI_CVT_REDUCED_BLANKING_V1 ? 160 : 80;
rb_v_fporch = rb == DI_CVT_REDUCED_BLANKING_V1 ? 3 : 1;
refresh_multiplier = (rb == DI_CVT_REDUCED_BLANKING_V2 && options->video_opt) ? 1000.0 / 1001.0 : 1;
rb_min_vblank = rb == DI_CVT_REDUCED_BLANKING_V3 ? options->vblank : RB_MIN_VBLANK;
if (rb_min_vblank < RB_MIN_VBLANK)
rb_min_vblank = RB_MIN_VBLANK;
h_sync = 32;
if (rb == DI_CVT_REDUCED_BLANKING_V3) {
additional_hblank = options->additional_hblank;
if (additional_hblank < 0)
additional_hblank = 0;
else if (additional_hblank > 120)
additional_hblank = 120;
h_blank += additional_hblank;
}
/* Determine VSync Width from aspect ratio */
if ((options->v_lines * 4 / 3) == options->h_pixels)
v_sync = 4;
else if ((options->v_lines * 16 / 9) == options->h_pixels)
v_sync = 5;
else if ((options->v_lines * 16 / 10) == options->h_pixels)
v_sync = 6;
else if (!(options->v_lines % 4) && ((options->v_lines * 5 / 4) == options->h_pixels))
v_sync = 7;
else if ((options->v_lines * 15 / 9) == options->h_pixels)
v_sync = 7;
else /* Custom */
v_sync = 10;
if (rb >= DI_CVT_REDUCED_BLANKING_V2)
v_sync = 8;
if (rb == DI_CVT_REDUCED_BLANKING_NONE) {
h_period_est = (1.0 / v_field_rate_rqd - MIN_VSYNC_BP / 1000000.0) /
(v_lines_rnd + vert_margin * 2 + MIN_V_PORCH + interlace) * 1000000.0;
v_sync_bp = floor(MIN_VSYNC_BP / h_period_est) + 1;
if (v_sync_bp < v_sync + MIN_V_BPORCH)
v_sync_bp = v_sync + MIN_V_BPORCH;
v_blank = v_sync_bp + MIN_V_PORCH;
total_v_lines = v_lines_rnd + vert_margin * 2 + v_sync_bp +
interlace + MIN_V_PORCH;
ideal_duty_cycle = C_PRIME - M_PRIME * h_period_est / 1000.0;
if (ideal_duty_cycle < 20)
ideal_duty_cycle = 20;
h_blank = floor(total_active_pixels * ideal_duty_cycle /
(100.0 - ideal_duty_cycle) /
(2 * cell_gran)) * 2 * cell_gran;
total_pixels = total_active_pixels + h_blank;
h_sync = floor(total_pixels * 0.08 / cell_gran) * cell_gran;
pixel_freq = floor(total_pixels / h_period_est / clock_step) * clock_step;
} else {
h_period_est = (1000000.0 / v_field_rate_rqd - rb_min_vblank) /
(v_lines_rnd + vert_margin * 2);
vbi_lines = floor(rb_min_vblank / h_period_est) + 1;
rb_v_bporch = rb == DI_CVT_REDUCED_BLANKING_V1 ? MIN_V_BPORCH : FIXED_V_BPORCH;
rb_min_vbi = rb_v_fporch + v_sync + rb_v_bporch;
v_blank = vbi_lines < rb_min_vbi ? rb_min_vbi : vbi_lines;
total_v_lines = v_blank + v_lines_rnd + vert_margin * 2 + interlace;
if (rb == DI_CVT_REDUCED_BLANKING_V3 && options->early_vsync_rqd)
rb_v_bporch = floor(vbi_lines / 2.0);
if (rb == DI_CVT_REDUCED_BLANKING_V1)
v_sync_bp = v_blank - rb_v_fporch;
else
v_sync_bp = v_sync + rb_v_bporch;
total_pixels = h_blank + total_active_pixels;
freq = v_field_rate_rqd * total_v_lines * total_pixels * refresh_multiplier;
if (rb == DI_CVT_REDUCED_BLANKING_V3)
pixel_freq = ceil(freq / 1000000.0 / clock_step) * clock_step;
else
pixel_freq = floor(freq / 1000000.0 / clock_step) * clock_step;
}
if (rb >= DI_CVT_REDUCED_BLANKING_V2)
h_front_porch = 8;
else
h_front_porch = (h_blank / 2.0) - h_sync;
v_back_porch = v_sync_bp - v_sync;
act_h_freq = 1000 * pixel_freq / total_pixels;
*t = (struct di_cvt_timing){
.act_pixel_freq = pixel_freq,
.total_active_pixels = total_active_pixels,
.v_lines_rnd = v_lines_rnd,
.h_front_porch = h_front_porch,
.h_sync = h_sync,
.h_back_porch = h_blank - h_front_porch - h_sync,
.v_front_porch = v_blank - v_back_porch - v_sync,
.v_back_porch = v_back_porch,
.v_sync = v_sync,
.act_frame_rate = 1000 * act_h_freq / total_v_lines,
};
}
@@ -1,401 +0,0 @@
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "include/libdisplay-info/info.h"
#define EDID_BLOCK_SIZE 128
#define EDID_DESCRIPTOR_COUNT 4
#define EDID_DESCRIPTOR_OFFSET 54
struct di_edid {
struct di_edid_vendor_product vendor_product;
struct di_edid_chromaticity_coords chromaticity;
struct di_edid_screen_size screen_size;
struct di_edid_misc_features misc_features;
bool has_chromaticity;
struct di_edid_detailed_timing_def *detailed_timing_storage;
const struct di_edid_detailed_timing_def **detailed_timings;
const struct di_edid_ext **extensions;
};
struct di_info {
struct di_edid edid;
char *model;
char *serial;
};
static const uint8_t edid_header[8] = { 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00 };
static const struct di_edid_detailed_timing_def *empty_detailed_timings[] = { NULL };
static const struct di_edid_ext *empty_edid_exts[] = { NULL };
static const struct di_cta_data_block *empty_cta_blocks[] = { NULL };
static const struct di_displayid_data_block *empty_displayid_blocks[] = { NULL };
static const struct di_displayid_type_i_ii_vii_timing *empty_timings[] = { NULL };
static const struct di_edid_screen_size empty_screen_size = { 0, 0 };
static char *dup_string(const char *value)
{
size_t len;
char *copy;
if (!value) {
value = "";
}
len = strlen(value) + 1;
copy = malloc(len);
if (!copy) {
return NULL;
}
memcpy(copy, value, len);
return copy;
}
static bool has_nonzero_bytes(const uint8_t *data, size_t len)
{
size_t i;
for (i = 0; i < len; i++) {
if (data[i] != 0) {
return true;
}
}
return false;
}
static bool validate_edid_block(const uint8_t *block)
{
uint8_t checksum = 0;
size_t i;
for (i = 0; i < EDID_BLOCK_SIZE; i++) {
checksum = (uint8_t)(checksum + block[i]);
}
return checksum == 0;
}
static uint16_t read_le_u16(const uint8_t *data)
{
return (uint16_t)data[0] | ((uint16_t)data[1] << 8);
}
static uint32_t read_le_u32(const uint8_t *data)
{
return (uint32_t)data[0] | ((uint32_t)data[1] << 8) | ((uint32_t)data[2] << 16) | ((uint32_t)data[3] << 24);
}
static float decode_chromaticity_value(uint8_t high_bits, uint8_t low_bits)
{
return (float)(((unsigned int)high_bits << 2) | low_bits) / 1024.0f;
}
static void decode_manufacturer_id(const uint8_t *base, char manufacturer[4])
{
manufacturer[0] = (char)('A' + ((base[8] >> 2) & 0x1f) - 1);
manufacturer[1] = (char)('A' + (((base[8] & 0x03) << 3) | ((base[9] >> 5) & 0x07)) - 1);
manufacturer[2] = (char)('A' + (base[9] & 0x1f) - 1);
manufacturer[3] = '\0';
if (!isupper((unsigned char)manufacturer[0]) || !isupper((unsigned char)manufacturer[1]) || !isupper((unsigned char)manufacturer[2])) {
memcpy(manufacturer, "???", 4);
}
}
static char *parse_descriptor_string(const uint8_t *descriptor)
{
char buffer[14];
size_t out_len = 0;
size_t i;
for (i = 5; i < 18; i++) {
const uint8_t value = descriptor[i];
if (value == 0x00 || value == 0x0a || value == 0x0d) {
break;
}
if (value < 0x20 || value > 0x7e) {
continue;
}
buffer[out_len++] = (char)value;
}
while (out_len > 0 && isspace((unsigned char)buffer[out_len - 1])) {
out_len--;
}
buffer[out_len] = '\0';
return dup_string(buffer);
}
static bool parse_detailed_timing(const uint8_t *descriptor, struct di_edid_detailed_timing_def *timing)
{
const uint16_t pixel_clock = read_le_u16(descriptor);
if (pixel_clock == 0) {
return false;
}
timing->horiz_video = (int)(descriptor[2] | ((descriptor[4] & 0xf0) << 4));
timing->vert_video = (int)(descriptor[5] | ((descriptor[7] & 0xf0) << 4));
timing->horiz_image_mm = (int)(descriptor[12] | ((descriptor[14] & 0xf0) << 4));
timing->vert_image_mm = (int)(descriptor[13] | ((descriptor[14] & 0x0f) << 8));
return timing->horiz_video > 0 && timing->vert_video > 0;
}
static bool populate_detailed_timings(struct di_edid *edid, const uint8_t *base)
{
struct di_edid_detailed_timing_def parsed[EDID_DESCRIPTOR_COUNT];
size_t count = 0;
size_t i;
edid->detailed_timings = empty_detailed_timings;
for (i = 0; i < EDID_DESCRIPTOR_COUNT; i++) {
const uint8_t *descriptor = base + EDID_DESCRIPTOR_OFFSET + (i * 18);
if (parse_detailed_timing(descriptor, &parsed[count])) {
count++;
}
}
if (count == 0) {
return true;
}
edid->detailed_timing_storage = calloc(count, sizeof(*edid->detailed_timing_storage));
edid->detailed_timings = calloc(count + 1, sizeof(*edid->detailed_timings));
if (!edid->detailed_timing_storage || !edid->detailed_timings) {
return false;
}
for (i = 0; i < count; i++) {
edid->detailed_timing_storage[i] = parsed[i];
edid->detailed_timings[i] = &edid->detailed_timing_storage[i];
}
edid->detailed_timings[count] = NULL;
return true;
}
static void populate_vendor_product(struct di_edid *edid, const uint8_t *base)
{
const int manufacture_year = (int)base[17] + 1990;
const bool has_model_year = base[16] == 0xff;
decode_manufacturer_id(base, edid->vendor_product.manufacturer);
edid->vendor_product.product = (int)read_le_u16(base + 10);
edid->vendor_product.serial = (int)read_le_u32(base + 12);
edid->vendor_product.manufacture_week = has_model_year ? 0 : (int)base[16];
edid->vendor_product.manufacture_year = has_model_year ? 0 : manufacture_year;
edid->vendor_product.model_year = has_model_year ? manufacture_year : 0;
}
static void populate_chromaticity(struct di_edid *edid, const uint8_t *base)
{
if (!has_nonzero_bytes(base + 25, 10)) {
edid->has_chromaticity = false;
return;
}
edid->chromaticity.red_x = decode_chromaticity_value(base[27], (base[25] >> 6) & 0x03);
edid->chromaticity.red_y = decode_chromaticity_value(base[28], (base[25] >> 4) & 0x03);
edid->chromaticity.green_x = decode_chromaticity_value(base[29], (base[25] >> 2) & 0x03);
edid->chromaticity.green_y = decode_chromaticity_value(base[30], base[25] & 0x03);
edid->chromaticity.blue_x = decode_chromaticity_value(base[31], (base[26] >> 6) & 0x03);
edid->chromaticity.blue_y = decode_chromaticity_value(base[32], (base[26] >> 4) & 0x03);
edid->chromaticity.white_x = decode_chromaticity_value(base[33], (base[26] >> 2) & 0x03);
edid->chromaticity.white_y = decode_chromaticity_value(base[34], base[26] & 0x03);
edid->has_chromaticity = true;
}
static bool populate_strings(struct di_info *info, const uint8_t *base)
{
size_t i;
for (i = 0; i < EDID_DESCRIPTOR_COUNT; i++) {
const uint8_t *descriptor = base + EDID_DESCRIPTOR_OFFSET + (i * 18);
if (descriptor[0] != 0x00 || descriptor[1] != 0x00 || descriptor[2] != 0x00) {
continue;
}
if (descriptor[3] == 0xfc && !info->model) {
info->model = parse_descriptor_string(descriptor);
} else if (descriptor[3] == 0xff && !info->serial) {
info->serial = parse_descriptor_string(descriptor);
}
}
if (!info->model) {
info->model = dup_string("");
}
if (!info->serial) {
if (info->edid.vendor_product.serial != 0) {
char serial_buffer[32];
snprintf(serial_buffer, sizeof(serial_buffer), "%u", (unsigned int)info->edid.vendor_product.serial);
info->serial = dup_string(serial_buffer);
} else {
info->serial = dup_string("");
}
}
return info->model && info->serial;
}
const struct di_info *di_info_parse_edid(const void *data, size_t size)
{
const uint8_t *base = data;
struct di_info *info;
if (!base || size < EDID_BLOCK_SIZE) {
return NULL;
}
if (memcmp(base, edid_header, sizeof(edid_header)) != 0) {
return NULL;
}
if (!validate_edid_block(base)) {
return NULL;
}
info = calloc(1, sizeof(*info));
if (!info) {
return NULL;
}
info->edid.screen_size.width_cm = (int)base[21];
info->edid.screen_size.height_cm = (int)base[22];
info->edid.misc_features.preferred_timing_is_native = (base[24] & 0x02) != 0;
info->edid.extensions = empty_edid_exts;
populate_vendor_product(&info->edid, base);
populate_chromaticity(&info->edid, base);
if (!populate_detailed_timings(&info->edid, base) || !populate_strings(info, base)) {
di_info_destroy(info);
return NULL;
}
return info;
}
void di_info_destroy(const struct di_info *info)
{
struct di_info *mutable_info = (struct di_info *)info;
if (!mutable_info) {
return;
}
free(mutable_info->model);
free(mutable_info->serial);
if (mutable_info->edid.detailed_timings != empty_detailed_timings) {
free((void *)mutable_info->edid.detailed_timings);
}
free(mutable_info->edid.detailed_timing_storage);
free(mutable_info);
}
const struct di_edid *di_info_get_edid(const struct di_info *info)
{
return info ? &info->edid : NULL;
}
char *di_info_get_model(const struct di_info *info)
{
return dup_string(info ? info->model : "");
}
char *di_info_get_serial(const struct di_info *info)
{
return dup_string(info ? info->serial : "");
}
const struct di_edid_detailed_timing_def *const *di_edid_get_detailed_timing_defs(const struct di_edid *edid)
{
return edid && edid->detailed_timings ? edid->detailed_timings : empty_detailed_timings;
}
const struct di_edid_screen_size *di_edid_get_screen_size(const struct di_edid *edid)
{
return edid ? &edid->screen_size : &empty_screen_size;
}
const struct di_edid_vendor_product *di_edid_get_vendor_product(const struct di_edid *edid)
{
return edid ? &edid->vendor_product : NULL;
}
const struct di_edid_chromaticity_coords *di_edid_get_chromaticity_coords(const struct di_edid *edid)
{
return edid && edid->has_chromaticity ? &edid->chromaticity : NULL;
}
const struct di_edid_ext *const *di_edid_get_extensions(const struct di_edid *edid)
{
return edid && edid->extensions ? edid->extensions : empty_edid_exts;
}
const struct di_edid_misc_features *di_edid_get_misc_features(const struct di_edid *edid)
{
return edid ? &edid->misc_features : NULL;
}
const struct di_edid_cta *di_edid_ext_get_cta(const struct di_edid_ext *ext)
{
(void)ext;
return NULL;
}
const struct di_displayid *di_edid_ext_get_displayid(const struct di_edid_ext *ext)
{
(void)ext;
return NULL;
}
const struct di_cta_data_block *const *di_edid_cta_get_data_blocks(const struct di_edid_cta *cta)
{
(void)cta;
return empty_cta_blocks;
}
const struct di_cta_hdr_static_metadata_block *di_cta_data_block_get_hdr_static_metadata(const struct di_cta_data_block *block)
{
(void)block;
return NULL;
}
const struct di_cta_colorimetry_block *di_cta_data_block_get_colorimetry(const struct di_cta_data_block *block)
{
(void)block;
return NULL;
}
const struct di_displayid_data_block *const *di_displayid_get_data_blocks(const struct di_displayid *displayid)
{
(void)displayid;
return empty_displayid_blocks;
}
const struct di_displayid_display_params *di_displayid_data_block_get_display_params(const struct di_displayid_data_block *block)
{
(void)block;
return NULL;
}
const struct di_displayid_type_i_ii_vii_timing *const *di_displayid_data_block_get_type_i_timings(const struct di_displayid_data_block *block)
{
(void)block;
return empty_timings;
}
const struct di_displayid_type_i_ii_vii_timing *const *di_displayid_data_block_get_type_ii_timings(const struct di_displayid_data_block *block)
{
(void)block;
return empty_timings;
}
@@ -0,0 +1,920 @@
#include <assert.h>
#include <errno.h>
#include <inttypes.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include "bits.h"
#include "displayid.h"
/**
* The size of the mandatory fields in a DisplayID section.
*/
#define DISPLAYID_MIN_SIZE 5
/**
* The maximum size of a DisplayID section.
*/
#define DISPLAYID_MAX_SIZE 256
/**
* The size of a DisplayID data block header (tag, revision and size).
*/
#define DISPLAYID_DATA_BLOCK_HEADER_SIZE 3
/**
* The size of a DisplayID type I timing.
*/
#define DISPLAYID_TYPE_I_TIMING_SIZE 20
/**
* The size of a DisplayID type II timing.
*/
#define DISPLAYID_TYPE_II_TIMING_SIZE 11
/**
* The size of a DisplayID type III timing.
*/
#define DISPLAYID_TYPE_III_TIMING_SIZE 3
static bool
is_all_zeroes(const uint8_t *data, size_t size)
{
size_t i;
for (i = 0; i < size; i++) {
if (data[i] != 0)
return false;
}
return true;
}
static void
add_failure(struct di_displayid *displayid, const char fmt[], ...)
{
va_list args;
va_start(args, fmt);
_di_logger_va_add_failure(displayid->logger, fmt, args);
va_end(args);
}
static void
check_data_block_revision(struct di_displayid *displayid,
const uint8_t data[static DISPLAYID_DATA_BLOCK_HEADER_SIZE],
const char *block_name, uint8_t max_revision)
{
uint8_t revision;
revision = get_bit_range(data[0x01], 2, 0);
if (revision > max_revision) {
add_failure(displayid, "%s: Unexpected revision (%u != %u).",
block_name, revision, max_revision);
}
}
static void
check_data_block_flags_reserved(struct di_displayid *displayid,
const uint8_t data[static DISPLAYID_DATA_BLOCK_HEADER_SIZE],
const char *block_name)
{
uint8_t flags;
flags = get_bit_range(data[0x01], 7, 3);
if (flags != 0) {
add_failure(displayid, "%s: Unexpected flags (0x%02x).",
block_name, flags);
}
}
static bool
parse_display_params_block(struct di_displayid *displayid,
struct di_displayid_display_params_priv *priv,
const uint8_t *data, size_t size)
{
struct di_displayid_display_params *params = &priv->base;
uint8_t raw_features;
check_data_block_revision(displayid, data, "Display Parameters Data Block", 0);
check_data_block_flags_reserved(displayid, data, "Display Parameters Data Block");
if (size != 0x0F) {
add_failure(displayid, "Display Parameters Data Block: DisplayID payload length is different than expected (%zu != %zu)", size, 0x0F);
return false;
}
params->horiz_image_mm = 0.1f * (float)(data[0x03] | (data[0x04] << 8));
params->vert_image_mm = 0.1f * (float)(data[0x05] | (data[0x06] << 8));
params->horiz_pixels = data[0x07] | (data[0x08] << 8);
params->vert_pixels = data[0x09] | (data[0x0A] << 8);
raw_features = data[0x0B];
params->features = &priv->features;
priv->features.audio = has_bit(raw_features, 7);
priv->features.separate_audio_inputs = has_bit(raw_features, 6);
priv->features.audio_input_override = has_bit(raw_features, 5);
priv->features.power_management = has_bit(raw_features, 4);
priv->features.fixed_timing = has_bit(raw_features, 3);
priv->features.fixed_pixel_format = has_bit(raw_features, 2);
priv->features.ai = has_bit(raw_features, 1);
priv->features.deinterlacing = has_bit(raw_features, 0);
if (data[0x0C] != 0xFF)
params->gamma = (float)data[0x0C] / 100 + 1;
params->aspect_ratio = (float)data[0x0D] / 100 + 1;
params->bits_per_color_overall = get_bit_range(data[0x0E], 7, 4) + 1;
params->bits_per_color_native = get_bit_range(data[0x0E], 3, 0) + 1;
return true;
}
static bool
timing_aspect_ratio_is_valid(uint8_t raw)
{
switch (raw) {
case DI_DISPLAYID_TIMING_ASPECT_RATIO_1_1:
case DI_DISPLAYID_TIMING_ASPECT_RATIO_5_4:
case DI_DISPLAYID_TIMING_ASPECT_RATIO_4_3:
case DI_DISPLAYID_TIMING_ASPECT_RATIO_15_9:
case DI_DISPLAYID_TIMING_ASPECT_RATIO_16_9:
case DI_DISPLAYID_TIMING_ASPECT_RATIO_16_10:
case DI_DISPLAYID_TIMING_ASPECT_RATIO_64_27:
case DI_DISPLAYID_TIMING_ASPECT_RATIO_256_135:
case DI_DISPLAYID_TIMING_ASPECT_RATIO_UNDEFINED:
return true;
default:
return false;
}
}
void
_di_displayid_parse_type_1_7_timing(struct di_displayid_type_i_ii_vii_timing *t,
struct di_logger *logger,
const char *prefix,
const uint8_t *data,
bool is_type7)
{
int raw_pixel_clock;
uint8_t stereo_3d, aspect_ratio;
raw_pixel_clock = data[0] | (data[1] << 8) | (data[2] << 16);
if (is_type7)
t->pixel_clock_mhz = (double)(1 + raw_pixel_clock) * 0.001;
else
t->pixel_clock_mhz = (double)(1 + raw_pixel_clock) * 0.01;
t->preferred = has_bit(data[3], 7);
t->interlaced = has_bit(data[3], 4);
stereo_3d = get_bit_range(data[3], 6, 5);
switch (stereo_3d) {
case DI_DISPLAYID_TYPE_I_II_VII_TIMING_STEREO_3D_NEVER:
case DI_DISPLAYID_TYPE_I_II_VII_TIMING_STEREO_3D_ALWAYS:
case DI_DISPLAYID_TYPE_I_II_VII_TIMING_STEREO_3D_USER:
t->stereo_3d = stereo_3d;
break;
default:
_di_logger_add_failure(logger, "%s: Reserved stereo 0x%02x.",
prefix, stereo_3d);
break;
}
aspect_ratio = get_bit_range(data[3], 3, 0);
if (timing_aspect_ratio_is_valid(aspect_ratio)) {
t->aspect_ratio = aspect_ratio;
} else {
t->aspect_ratio = DI_DISPLAYID_TIMING_ASPECT_RATIO_UNDEFINED;
_di_logger_add_failure(logger, "%s: Unknown aspect 0x%02x.",
prefix, aspect_ratio);
}
t->horiz_active = 1 + (data[4] | (data[5] << 8));
t->horiz_blank = 1 + (data[6] | (data[7] << 8));
t->horiz_offset = 1 + (data[8] | (get_bit_range(data[9], 6, 0) << 8));
t->horiz_sync_polarity = has_bit(data[9], 7);
t->horiz_sync_width = 1 + (data[10] | (data[11] << 8));
t->vert_active = 1 + (data[12] | (data[13] << 8));
t->vert_blank = 1 + (data[14] | (data[15] << 8));
t->vert_offset = 1 + (data[16] | (get_bit_range(data[17], 6, 0) << 8));
t->vert_sync_polarity = has_bit(data[17], 7);
t->vert_sync_width = 1 + (data[18] | (data[19] << 8));
}
static bool
parse_type_i_timing(struct di_displayid *displayid,
struct di_displayid_data_block *data_block,
const uint8_t data[static DISPLAYID_TYPE_I_TIMING_SIZE])
{
struct di_displayid_type_i_ii_vii_timing timing, *t;
_di_displayid_parse_type_1_7_timing(&timing, displayid->logger,
"Video Timing Modes Type 1 - Detailed Timings Data Block",
data, false);
t = calloc(1, sizeof(*t));
if (t == NULL) {
return false;
}
*t = timing;
assert(data_block->type_i_timings_len < DISPLAYID_MAX_TYPE_I_TIMINGS);
data_block->type_i_timings[data_block->type_i_timings_len++] = t;
return true;
}
static bool
parse_type_i_timing_block(struct di_displayid *displayid,
struct di_displayid_data_block *data_block,
const uint8_t *data, size_t size)
{
size_t i;
check_data_block_revision(displayid, data,
"Video Timing Modes Type 1 - Detailed Timings Data Block",
1);
check_data_block_flags_reserved(displayid, data,
"Video Timing Modes Type 1 - Detailed Timings Data Block");
if ((size - DISPLAYID_DATA_BLOCK_HEADER_SIZE) % DISPLAYID_TYPE_I_TIMING_SIZE != 0) {
add_failure(displayid,
"Video Timing Modes Type 1 - Detailed Timings Data Block: payload size not divisible by element size.");
}
for (i = DISPLAYID_DATA_BLOCK_HEADER_SIZE;
i + DISPLAYID_TYPE_I_TIMING_SIZE <= size;
i += DISPLAYID_TYPE_I_TIMING_SIZE) {
if (!parse_type_i_timing(displayid, data_block, &data[i])) {
return false;
}
}
return true;
}
static bool
parse_type_ii_timing(struct di_displayid *displayid,
struct di_displayid_data_block *data_block,
const uint8_t data[static DISPLAYID_TYPE_II_TIMING_SIZE])
{
int raw_pixel_clock;
uint8_t stereo_3d;
struct di_displayid_type_i_ii_vii_timing *t = calloc(1, sizeof(*t));
if (t == NULL) {
return false;
}
t->aspect_ratio = DI_DISPLAYID_TIMING_ASPECT_RATIO_UNDEFINED;
raw_pixel_clock = data[0] | (data[1] << 8) | (data[2] << 16);
t->pixel_clock_mhz = (double)(1 + raw_pixel_clock) * 0.01;
t->preferred = has_bit(data[3], 7);
t->interlaced = has_bit(data[3], 4);
stereo_3d = get_bit_range(data[3], 6, 5);
switch (stereo_3d) {
case DI_DISPLAYID_TYPE_I_II_VII_TIMING_STEREO_3D_NEVER:
case DI_DISPLAYID_TYPE_I_II_VII_TIMING_STEREO_3D_ALWAYS:
case DI_DISPLAYID_TYPE_I_II_VII_TIMING_STEREO_3D_USER:
t->stereo_3d = stereo_3d;
break;
default:
add_failure(displayid,
"Video Timing Modes Type 2 - Detailed Timings Data Block: Reserved stereo 0x%02x.",
stereo_3d);
break;
}
t->horiz_sync_polarity = has_bit(data[3], 3);
t->vert_sync_polarity = has_bit(data[3], 2);
if (get_bit_range(data[3], 1, 0) != 0) {
add_failure(displayid,
"Video Timing Modes Type 2 - Detailed Timings Data Block: "
"Timing Options bit 1-0 are reserved.");
}
t->horiz_active = 8 + 8 * (data[4] | (get_bit_range(data[5], 0, 0) << 8));
t->horiz_blank = 8 + 8 * get_bit_range(data[5], 7, 1);
t->horiz_offset = 8 + 8 * get_bit_range(data[6], 7, 4);
t->horiz_sync_width = 8 + 8 * get_bit_range(data[6], 3, 0);
t->vert_active = 1 + (data[7] | (get_bit_range(data[8], 3, 0) << 8));
if (get_bit_range(data[8], 7, 4) != 0) {
add_failure(displayid,
"Video Timing Modes Type 2 - Detailed Timings Data Block: "
"Vertical Active Image bits 7-4 are reserved.");
}
t->vert_blank = 1 + data[9];
t->vert_offset = 1 + get_bit_range(data[9], 7, 4);
t->vert_sync_width = 1 + get_bit_range(data[9], 3, 0);
assert(data_block->type_ii_timings_len < DISPLAYID_MAX_TYPE_II_TIMINGS);
data_block->type_ii_timings[data_block->type_ii_timings_len++] = t;
return true;
}
static bool
parse_type_ii_timing_block(struct di_displayid *displayid,
struct di_displayid_data_block *data_block,
const uint8_t *data, size_t size)
{
size_t i;
check_data_block_revision(displayid, data,
"Video Timing Modes Type 2 - Detailed Timings Data Block",
0);
check_data_block_flags_reserved(displayid, data,
"Video Timing Modes Type 2 - Detailed Timings Data Block");
if ((size - DISPLAYID_DATA_BLOCK_HEADER_SIZE) % DISPLAYID_TYPE_II_TIMING_SIZE != 0) {
add_failure(displayid,
"Video Timing Modes Type 2 - Detailed Timings Data Block: payload size not divisible by element size.");
}
for (i = DISPLAYID_DATA_BLOCK_HEADER_SIZE;
i + DISPLAYID_TYPE_II_TIMING_SIZE <= size;
i += DISPLAYID_TYPE_II_TIMING_SIZE) {
if (!parse_type_ii_timing(displayid, data_block, &data[i])) {
return false;
}
}
return true;
}
static bool
parse_type_iv_timing_block(struct di_displayid *displayid,
struct di_displayid_type_iv_timing *type_iv_timing,
const uint8_t *data, size_t size)
{
uint8_t flags;
uint8_t type;
size_t i;
uint8_t code;
struct di_dmt_code *dmts = NULL;
struct di_cta_vic *cta_vics = NULL;
struct di_hdmi_vic *hdmi_vics = NULL;
check_data_block_revision(displayid, data,
"Video Timing Modes Type 4 - Short Timings Data Block",
1);
flags = get_bit_range(data[0x01], 5, 3);
if (flags != 0) {
add_failure(displayid,
"Video Timing Modes Type 4 - Short Timings Data Block: Unexpected flags (0x%02x).",
flags);
}
type = get_bit_range(data[0x01], 7, 6);
switch (type) {
case DI_DISPLAYID_TYPE_IV_TIMING_CODE_TYPE_DMT:
case DI_DISPLAYID_TYPE_IV_TIMING_CODE_TYPE_VIC_CTA:
case DI_DISPLAYID_TYPE_IV_TIMING_CODE_TYPE_VIC_HDMI:
type_iv_timing->type = type;
break;
default:
add_failure(displayid,
"Video Timing Modes Type 4 - Short Timings Data Block: Reserved Timing Code Type 0x%02x.",
type);
break;
}
type_iv_timing->codes_len = size - DISPLAYID_DATA_BLOCK_HEADER_SIZE;
if (type_iv_timing->codes_len == 0) {
add_failure(displayid,
"Video Timing Modes Type 4 - Short Timings Data Block: No Timing Code present.");
return true;
}
switch (type) {
case DI_DISPLAYID_TYPE_IV_TIMING_CODE_TYPE_DMT:
dmts = calloc(type_iv_timing->codes_len, sizeof(*dmts));
if (!dmts)
return false;
for (i = 0; i < type_iv_timing->codes_len; i++) {
code = data[DISPLAYID_DATA_BLOCK_HEADER_SIZE + i];
dmts[i] = (struct di_dmt_code) { .code = code };
}
break;
case DI_DISPLAYID_TYPE_IV_TIMING_CODE_TYPE_VIC_CTA:
cta_vics = calloc(type_iv_timing->codes_len, sizeof(*cta_vics));
if (!cta_vics)
return false;
for (i = 0; i < type_iv_timing->codes_len; i++) {
code = data[DISPLAYID_DATA_BLOCK_HEADER_SIZE + i];
cta_vics[i] = (struct di_cta_vic) { .code = code };
}
break;
case DI_DISPLAYID_TYPE_IV_TIMING_CODE_TYPE_VIC_HDMI:
hdmi_vics = calloc(type_iv_timing->codes_len, sizeof(*hdmi_vics));
if (!hdmi_vics)
return false;
for (i = 0; i < type_iv_timing->codes_len; i++) {
code = data[DISPLAYID_DATA_BLOCK_HEADER_SIZE + i];
hdmi_vics[i] = (struct di_hdmi_vic) { .code = code };
}
break;
}
type_iv_timing->dmts = dmts;
type_iv_timing->cta_vics = cta_vics;
type_iv_timing->hdmi_vics = hdmi_vics;
return true;
}
static bool
parse_tiled_topo_block(struct di_displayid *displayid,
struct di_displayid_tiled_topo_priv *priv,
const uint8_t *data, size_t size)
{
struct di_displayid_tiled_topo *tiled_topo = &priv->base;
uint8_t raw_caps, raw_missing_recv_behavior, raw_single_recv_behavior;
bool has_bezel;
float px_mult;
check_data_block_revision(displayid, data, "Tiled Display Topology Data Block", 0);
check_data_block_flags_reserved(displayid, data, "Tiled Display Topology Data Block");
if (size - DISPLAYID_DATA_BLOCK_HEADER_SIZE != 22) {
add_failure(displayid,
"Tiled Display Topology Data Block: DisplayID payload length is different than expected (%zu != %zu)",
size - DISPLAYID_DATA_BLOCK_HEADER_SIZE, 22);
return false;
}
raw_caps = data[0x03];
tiled_topo->caps = &priv->caps;
priv->caps.single_enclosure = has_bit(raw_caps, 7);
has_bezel = has_bit(raw_caps, 6);
raw_missing_recv_behavior = get_bit_range(raw_caps, 4, 3);
raw_single_recv_behavior = get_bit_range(raw_caps, 2, 0);
if (has_bit(raw_caps, 5)) {
add_failure(displayid, "Tiled Display Topology Data Block: Capability bit 5 is reserved.");
}
switch (raw_missing_recv_behavior) {
case DI_DISPLAYID_TILED_TOPO_MISSING_RECV_UNDEF:
case DI_DISPLAYID_TILED_TOPO_MISSING_RECV_TILE_ONLY:
priv->caps.missing_recv_behavior = raw_missing_recv_behavior;
break;
default:
add_failure(displayid,
"Tiled Display Topology Data Block: Behavior if more than one tile and fewer than total number of tiles set to reserved value 0x%02x.",
raw_missing_recv_behavior);
break;
}
switch (raw_single_recv_behavior) {
case DI_DISPLAYID_TILED_TOPO_SINGLE_RECV_UNDEF:
case DI_DISPLAYID_TILED_TOPO_SINGLE_RECV_TILE_ONLY:
case DI_DISPLAYID_TILED_TOPO_SINGLE_RECV_SCALED:
case DI_DISPLAYID_TILED_TOPO_SINGLE_RECV_CLONED:
priv->caps.single_recv_behavior = raw_single_recv_behavior;
break;
default:
add_failure(displayid,
"Tiled Display Topology Data Block: Behavior if it is the only tile set to reserved value 0x%02x.",
raw_single_recv_behavior);
break;
}
tiled_topo->total_horiz_tiles = 1 + (get_bit_range(data[0x04], 7, 4) |
(get_bit_range(data[0x06], 7, 6) << 4));
tiled_topo->total_vert_tiles = 1 + (get_bit_range(data[0x04], 3, 0) |
(get_bit_range(data[0x06], 5, 4) << 4));
tiled_topo->horiz_tile_location = 1 + (get_bit_range(data[0x05], 7, 4) |
(get_bit_range(data[0x06], 3, 2) << 4));
tiled_topo->vert_tile_location = 1 + (get_bit_range(data[0x05], 3, 0) |
(get_bit_range(data[0x06], 1, 0) << 4));
tiled_topo->horiz_tile_pixels = 1 + (data[0x07] | (data[0x08] << 8));
tiled_topo->vert_tile_lines = 1 + (data[0x09] | (data[0x0A] << 8));
px_mult = data[0x0B];
if (has_bezel && px_mult == 0) {
add_failure(displayid, "Tiled Display Topology Data Block: Bezel information bit is set, but the pixel multiplier is zero.");
has_bezel = false;
}
if (has_bezel) {
tiled_topo->bezel = &priv->bezel;
priv->bezel.top_px = px_mult * data[0x0C] * 0.1f;
priv->bezel.bottom_px = px_mult * data[0x0D] * 0.1f;
priv->bezel.right_px = px_mult * data[0x0E] * 0.1f;
priv->bezel.left_px = px_mult * data[0x0F] * 0.1f;
} else {
if (px_mult != 0)
add_failure(displayid, "Tiled Display Topology Data Block: No bezel information, but the pixel multiplier is non-zero.");
if (!is_all_zeroes(&data[0x0C], 4))
add_failure(displayid, "Tiled Display Topology Data Block: No bezel information, but the bezel size is non-zero.");
}
memcpy(tiled_topo->vendor_id, &data[0x10], 3);
tiled_topo->product_code = (uint16_t)(data[0x13] | (data[0x14] << 8));
tiled_topo->serial_number = data[0x15] |
(uint32_t)(data[0x16] << 8) |
(uint32_t)(data[0x17] << 16) |
(uint32_t)(data[0x18] << 24);
return true;
}
static bool
parse_type_iii_timing(struct di_displayid *displayid,
struct di_displayid_data_block *data_block,
const uint8_t data[static DISPLAYID_TYPE_III_TIMING_SIZE])
{
struct di_displayid_type_iii_timing *t;
uint8_t algo, aspect_ratio;
t = calloc(1, sizeof(*t));
if (t == NULL)
return false;
t->preferred = has_bit(data[0], 7);
algo = get_bit_range(data[0], 6, 4);
switch (algo) {
case DI_DISPLAYID_TYPE_III_TIMING_CVT_STANDARD_BLANKING:
case DI_DISPLAYID_TYPE_III_TIMING_CVT_REDUCED_BLANKING:
t->algo = algo;
break;
default:
add_failure(displayid,
"Video Timing Modes Type 3 - Short Timings Data Block: Reserved algorithm 0x%02x.",
algo);
goto error_reserved;
}
aspect_ratio = get_bit_range(data[0], 3, 0);
if (timing_aspect_ratio_is_valid(aspect_ratio)) {
t->aspect_ratio = aspect_ratio;
} else {
add_failure(displayid,
"Video Timing Modes Type 3 - Short Timings Data Block: Reserved aspect ratio 0x%02x.",
aspect_ratio);
goto error_reserved;
}
t->horiz_active = ((int32_t)data[1] + 1) * 8;
t->interlaced = has_bit(data[2], 7);
t->refresh_rate_hz = (int32_t)get_bit_range(data[2], 6, 0) + 1;
assert(data_block->type_iii_timings_len < DISPLAYID_MAX_TYPE_III_TIMINGS);
data_block->type_iii_timings[data_block->type_iii_timings_len++] = t;
return true;
error_reserved:
free(t);
return true;
}
static bool
parse_type_iii_timing_block(struct di_displayid *displayid,
struct di_displayid_data_block *data_block,
const uint8_t *data, size_t size)
{
size_t i;
check_data_block_revision(displayid, data,
"Video Timing Modes Type 3 - Short Timings Data Block",
1);
if ((size - DISPLAYID_DATA_BLOCK_HEADER_SIZE) % DISPLAYID_TYPE_III_TIMING_SIZE != 0)
add_failure(displayid,
"Video Timing Modes Type 3 - Short Timings Data Block: payload size not divisible by element size.");
for (i = DISPLAYID_DATA_BLOCK_HEADER_SIZE;
i + DISPLAYID_TYPE_III_TIMING_SIZE <= size;
i += DISPLAYID_TYPE_III_TIMING_SIZE) {
if (!parse_type_iii_timing(displayid, data_block, &data[i]))
return false;
}
return true;
}
static ssize_t
parse_data_block(struct di_displayid *displayid, const uint8_t *data,
size_t size)
{
uint8_t tag;
size_t data_block_size;
struct di_displayid_data_block *data_block = NULL;
assert(size >= DISPLAYID_DATA_BLOCK_HEADER_SIZE);
tag = data[0x00];
data_block_size = (size_t) data[0x02] + DISPLAYID_DATA_BLOCK_HEADER_SIZE;
if (data_block_size > size) {
add_failure(displayid,
"The length of this DisplayID data block (%d) exceeds the number of bytes remaining (%zu)",
data_block_size, size);
goto skip;
}
data_block = calloc(1, sizeof(*data_block));
if (!data_block)
goto error;
switch (tag) {
case DI_DISPLAYID_DATA_BLOCK_DISPLAY_PARAMS:
if (!parse_display_params_block(displayid,
&data_block->display_params,
data, data_block_size))
goto error;
break;
case DI_DISPLAYID_DATA_BLOCK_TYPE_I_TIMING:
if (!parse_type_i_timing_block(displayid, data_block, data, data_block_size))
goto error;
break;
case DI_DISPLAYID_DATA_BLOCK_TILED_DISPLAY_TOPO:
if (!parse_tiled_topo_block(displayid, &data_block->tiled_topo, data,
data_block_size))
goto skip;
break;
case DI_DISPLAYID_DATA_BLOCK_TYPE_II_TIMING:
if (!parse_type_ii_timing_block(displayid, data_block, data, data_block_size))
goto error;
break;
case DI_DISPLAYID_DATA_BLOCK_TYPE_III_TIMING:
if (!parse_type_iii_timing_block(displayid, data_block, data, data_block_size))
goto error;
break;
case DI_DISPLAYID_DATA_BLOCK_TYPE_IV_TIMING:
if (!parse_type_iv_timing_block(displayid,
&data_block->type_iv_timing,
data, data_block_size))
goto error;
break;
case DI_DISPLAYID_DATA_BLOCK_PRODUCT_ID:
case DI_DISPLAYID_DATA_BLOCK_COLOR_CHARACT:
case DI_DISPLAYID_DATA_BLOCK_VESA_TIMING:
case DI_DISPLAYID_DATA_BLOCK_CEA_TIMING:
case DI_DISPLAYID_DATA_BLOCK_TIMING_RANGE_LIMITS:
case DI_DISPLAYID_DATA_BLOCK_PRODUCT_SERIAL:
case DI_DISPLAYID_DATA_BLOCK_ASCII_STRING:
case DI_DISPLAYID_DATA_BLOCK_DISPLAY_DEVICE_DATA:
case DI_DISPLAYID_DATA_BLOCK_INTERFACE_POWER_SEQ:
case DI_DISPLAYID_DATA_BLOCK_TRANSFER_CHARACT:
case DI_DISPLAYID_DATA_BLOCK_DISPLAY_INTERFACE:
case DI_DISPLAYID_DATA_BLOCK_STEREO_DISPLAY_INTERFACE:
case DI_DISPLAYID_DATA_BLOCK_TYPE_V_TIMING:
case DI_DISPLAYID_DATA_BLOCK_TYPE_VI_TIMING:
break; /* Supported */
case 0x7F:
goto skip; /* Vendor-specific */
default:
add_failure(displayid,
"Unknown DisplayID Data Block (0x%" PRIx8 ", length %" PRIu8 ")",
tag, data_block_size - DISPLAYID_DATA_BLOCK_HEADER_SIZE);
goto skip;
}
data_block->tag = tag;
assert(displayid->data_blocks_len < DISPLAYID_MAX_DATA_BLOCKS);
displayid->data_blocks[displayid->data_blocks_len++] = data_block;
return (ssize_t) data_block_size;
skip:
free(data_block);
return (ssize_t) data_block_size;
error:
free(data_block);
return -1;
}
static bool
is_data_block_end(const uint8_t *data, size_t size)
{
if (size < DISPLAYID_DATA_BLOCK_HEADER_SIZE)
return true;
return is_all_zeroes(data, DISPLAYID_DATA_BLOCK_HEADER_SIZE);
}
static bool
validate_checksum(const uint8_t *data, size_t size)
{
uint8_t sum = 0;
size_t i;
for (i = 0; i < size; i++) {
sum += data[i];
}
return sum == 0;
}
int
_di_displayid_parse_version(const uint8_t *data, size_t size)
{
if (size == 0)
return 0;
return get_bit_range(data[0x00], 7, 4);
}
bool
_di_displayid_parse(struct di_displayid *displayid, const uint8_t *data,
size_t size, struct di_logger *logger)
{
size_t section_size, i, max_data_block_size;
ssize_t data_block_size;
uint8_t product_type;
if (size < DISPLAYID_MIN_SIZE) {
errno = EINVAL;
return false;
}
displayid->logger = logger;
displayid->version = _di_displayid_parse_version(data, size);
displayid->revision = get_bit_range(data[0x00], 3, 0);
if (displayid->version == 0 || displayid->version > 1) {
errno = ENOTSUP;
return false;
}
section_size = (size_t) data[0x01] + DISPLAYID_MIN_SIZE;
if (section_size > DISPLAYID_MAX_SIZE || section_size > size) {
errno = EINVAL;
return false;
}
if (!validate_checksum(data, section_size)) {
errno = EINVAL;
return false;
}
product_type = data[0x02];
switch (product_type) {
case DI_DISPLAYID_PRODUCT_TYPE_EXTENSION:
case DI_DISPLAYID_PRODUCT_TYPE_TEST:
case DI_DISPLAYID_PRODUCT_TYPE_DISPLAY_PANEL:
case DI_DISPLAYID_PRODUCT_TYPE_STANDALONE_DISPLAY:
case DI_DISPLAYID_PRODUCT_TYPE_TV_RECEIVER:
case DI_DISPLAYID_PRODUCT_TYPE_REPEATER:
case DI_DISPLAYID_PRODUCT_TYPE_DIRECT_DRIVE:
displayid->product_type = product_type;
break;
default:
errno = EINVAL;
return false;
}
i = DISPLAYID_MIN_SIZE - 1;
max_data_block_size = 0;
while (i < section_size - 1) {
max_data_block_size = section_size - 1 - i;
if (is_data_block_end(&data[i], max_data_block_size))
break;
data_block_size = parse_data_block(displayid, &data[i],
max_data_block_size);
if (data_block_size < 0)
return false;
assert(data_block_size > 0);
i += (size_t) data_block_size;
}
if (!is_all_zeroes(&data[i], max_data_block_size)) {
if (max_data_block_size < DISPLAYID_DATA_BLOCK_HEADER_SIZE)
add_failure(displayid,
"Not enough bytes remain (%zu) for a DisplayID data block and the DisplayID filler is non-0.",
max_data_block_size);
else
add_failure(displayid, "Padding: Contains non-zero bytes.");
}
displayid->logger = NULL;
return true;
}
static void
destroy_data_block(struct di_displayid_data_block *data_block)
{
size_t i;
switch (data_block->tag) {
case DI_DISPLAYID_DATA_BLOCK_TYPE_I_TIMING:
for (i = 0; i < data_block->type_i_timings_len; i++)
free(data_block->type_i_timings[i]);
break;
case DI_DISPLAYID_DATA_BLOCK_TYPE_II_TIMING:
for (i = 0; i < data_block->type_ii_timings_len; i++)
free(data_block->type_ii_timings[i]);
break;
case DI_DISPLAYID_DATA_BLOCK_TYPE_III_TIMING:
for (i = 0; i < data_block->type_iii_timings_len; i++)
free(data_block->type_iii_timings[i]);
break;
case DI_DISPLAYID_DATA_BLOCK_TYPE_IV_TIMING:
free((struct di_dmt_code *) data_block->type_iv_timing.dmts);
free((struct di_cta_vic *) data_block->type_iv_timing.cta_vics);
free((struct di_hdmi_vic *) data_block->type_iv_timing.hdmi_vics);
break;
default:
break; /* Nothing to do */
}
free(data_block);
}
void
_di_displayid_finish(struct di_displayid *displayid)
{
size_t i;
for (i = 0; i < displayid->data_blocks_len; i++)
destroy_data_block(displayid->data_blocks[i]);
}
int
di_displayid_get_version(const struct di_displayid *displayid)
{
return displayid->version;
}
int
di_displayid_get_revision(const struct di_displayid *displayid)
{
return displayid->revision;
}
enum di_displayid_product_type
di_displayid_get_product_type(const struct di_displayid *displayid)
{
return displayid->product_type;
}
enum di_displayid_data_block_tag
di_displayid_data_block_get_tag(const struct di_displayid_data_block *data_block)
{
return data_block->tag;
}
const struct di_displayid_display_params *
di_displayid_data_block_get_display_params(const struct di_displayid_data_block *data_block)
{
if (data_block->tag != DI_DISPLAYID_DATA_BLOCK_DISPLAY_PARAMS) {
return NULL;
}
return &data_block->display_params.base;
}
const struct di_displayid_type_i_ii_vii_timing *const *
di_displayid_data_block_get_type_i_timings(const struct di_displayid_data_block *data_block)
{
if (data_block->tag != DI_DISPLAYID_DATA_BLOCK_TYPE_I_TIMING) {
return NULL;
}
return (const struct di_displayid_type_i_ii_vii_timing *const *) data_block->type_i_timings;
}
const struct di_displayid_type_i_ii_vii_timing *const *
di_displayid_data_block_get_type_ii_timings(const struct di_displayid_data_block *data_block)
{
if (data_block->tag != DI_DISPLAYID_DATA_BLOCK_TYPE_II_TIMING) {
return NULL;
}
return (const struct di_displayid_type_i_ii_vii_timing *const *) data_block->type_ii_timings;
}
const struct di_displayid_tiled_topo *
di_displayid_data_block_get_tiled_topo(const struct di_displayid_data_block *data_block)
{
if (data_block->tag != DI_DISPLAYID_DATA_BLOCK_TILED_DISPLAY_TOPO) {
return NULL;
}
return &data_block->tiled_topo.base;
}
const struct di_displayid_type_iii_timing *const *
di_displayid_data_block_get_type_iii_timings(const struct di_displayid_data_block *data_block)
{
if (data_block->tag != DI_DISPLAYID_DATA_BLOCK_TYPE_III_TIMING) {
return NULL;
}
return (const struct di_displayid_type_iii_timing *const *) data_block->type_iii_timings;
}
const struct di_displayid_type_iv_timing *
di_displayid_data_block_get_type_iv_timing(const struct di_displayid_data_block *data_block)
{
if (data_block->tag != DI_DISPLAYID_DATA_BLOCK_TYPE_IV_TIMING) {
return NULL;
}
return &data_block->type_iv_timing;
}
const struct di_displayid_data_block *const *
di_displayid_get_data_blocks(const struct di_displayid *displayid)
{
return (const struct di_displayid_data_block *const *) displayid->data_blocks;
}
@@ -0,0 +1,313 @@
#include <errno.h>
#include <inttypes.h>
#include <stdlib.h>
#include <sys/types.h>
#include "bits.h"
#include "displayid.h"
#include "displayid2.h"
/**
* The size of a DisplayID v2 section header.
*/
#define DISPLAYID2_HEADER_SIZE 4
/**
* The size of the mandatory fields in a DisplayID v2 section (header + checksum).
*/
#define DISPLAYID2_MIN_SIZE (DISPLAYID2_HEADER_SIZE + 1)
/**
* The maximum size of a DisplayID v2 section.
*/
#define DISPLAYID2_MAX_SIZE 256
/**
* The size of a DisplayID v2 data block header (tag, revision and size).
*/
#define DISPLAYID2_DATA_BLOCK_HEADER_SIZE 3
static void
add_failure(struct di_displayid2 *displayid2, const char fmt[], ...)
{
va_list args;
va_start(args, fmt);
_di_logger_va_add_failure(displayid2->logger, fmt, args);
va_end(args);
}
static bool
parse_cta861_block(struct di_displayid2 *displayid2, struct di_displayid2_data_block *data_block,
const uint8_t *data, size_t size)
{
struct di_cta cta;
size_t i;
uint8_t data_block_tag, data_block_size;
struct di_cta_data_block *cta_data_block;
cta = (struct di_cta){
/* The DisplayID block doesn't specify the CTA revision. Use
* the latest one to silence warnings. */
.revision = 3,
.logger = displayid2->logger,
};
i = 0x03;
while (i < size) {
data_block_size = get_bit_range(data[i], 4, 0);
data_block_tag = get_bit_range(data[i], 7, 5);
if (data_block_size > size - i - 1) {
add_failure(displayid2,
"CTA data block size (%" PRIu8 ") exceeds number of remaining bytes (%zu)",
data_block_size, size - i - 1);
break;
}
if (!_di_cta_data_block_parse(&cta, data_block_tag,
&data[i + 1], data_block_size, &cta_data_block)) {
return false;
}
if (cta_data_block != NULL) {
assert(data_block->cta861.data_blocks_len < DISPLAYID2_CTA861_MAX_DATA_BLOCKS);
data_block->cta861.data_blocks[data_block->cta861.data_blocks_len++] = cta_data_block;
}
i += 1 + data_block_size;
}
return true;
}
static ssize_t
parse_data_block(struct di_displayid2 *displayid2, const uint8_t *data,
size_t size)
{
uint8_t tag;
size_t data_block_size;
struct di_displayid2_data_block *data_block = NULL;
assert(size >= DISPLAYID2_DATA_BLOCK_HEADER_SIZE);
tag = data[0x00];
data_block_size = (size_t) data[0x02] + DISPLAYID2_DATA_BLOCK_HEADER_SIZE;
if (data_block_size > size) {
add_failure(displayid2,
"The length of this DisplayID data block (%d) exceeds the number of bytes remaining (%zu)",
data_block_size, size);
goto skip;
}
data_block = calloc(1, sizeof(*data_block));
if (!data_block)
goto error;
switch (tag) {
case DI_DISPLAYID2_DATA_BLOCK_CTA861:
if (!parse_cta861_block(displayid2, data_block, data, data_block_size)) {
goto error;
}
break;
case DI_DISPLAYID2_DATA_BLOCK_PRODUCT_ID:
case DI_DISPLAYID2_DATA_BLOCK_DISPLAY_PARAMS:
case DI_DISPLAYID2_DATA_BLOCK_TYPE_VII_TIMING:
case DI_DISPLAYID2_DATA_BLOCK_TYPE_VIII_TIMING:
case DI_DISPLAYID2_DATA_BLOCK_TYPE_IX_TIMING:
case DI_DISPLAYID2_DATA_BLOCK_DYN_TIMING_RANGE_LIMITS:
case DI_DISPLAYID2_DATA_BLOCK_DISPLAY_INTERFACE_FEATURES:
case DI_DISPLAYID2_DATA_BLOCK_STEREO_DISPLAY_INTERFACE:
case DI_DISPLAYID2_DATA_BLOCK_TILED_DISPLAY_TOPO:
case DI_DISPLAYID2_DATA_BLOCK_CONTAINERID:
case DI_DISPLAYID2_DATA_BLOCK_TYPE_X_TIMING:
case DI_DISPLAYID2_DATA_BLOCK_ADAPTIVE_SYNC:
case DI_DISPLAYID2_DATA_BLOCK_ARVR_HMD:
case DI_DISPLAYID2_DATA_BLOCK_ARVR_LAYER:
break; /* Supported */
case 0x7E:
goto skip; /* Vendor-specific */
default:
add_failure(displayid2,
"Unknown DisplayID v2 Data Block (0x%" PRIx8 ", length %" PRIu8 ")",
tag, data_block_size - DISPLAYID2_DATA_BLOCK_HEADER_SIZE);
goto skip;
}
data_block->tag = tag;
assert(displayid2->data_blocks_len < DISPLAYID2_MAX_DATA_BLOCKS);
displayid2->data_blocks[displayid2->data_blocks_len++] = data_block;
return (ssize_t) data_block_size;
skip:
free(data_block);
return (ssize_t) data_block_size;
error:
free(data_block);
return -1;
}
static bool
is_all_zeroes(const uint8_t *data, size_t size)
{
size_t i;
for (i = 0; i < size; i++) {
if (data[i] != 0)
return false;
}
return true;
}
static bool
is_data_block_end(const uint8_t *data, size_t size)
{
if (size < DISPLAYID2_DATA_BLOCK_HEADER_SIZE)
return true;
return is_all_zeroes(data, DISPLAYID2_DATA_BLOCK_HEADER_SIZE);
}
static bool
validate_checksum(const uint8_t *data, size_t size)
{
uint8_t sum = 0;
size_t i;
for (i = 0; i < size; i++) {
sum += data[i];
}
return sum == 0;
}
bool
_di_displayid2_parse(struct di_displayid2 *displayid2, const uint8_t *data,
size_t size, struct di_logger *logger)
{
size_t section_size, i, max_data_block_size;
ssize_t data_block_size;
int version;
uint8_t product_primary_use_case;
if (size < DISPLAYID2_MIN_SIZE) {
errno = EINVAL;
return false;
}
displayid2->logger = logger;
version = _di_displayid_parse_version(data, size);
displayid2->revision = get_bit_range(data[0x00], 3, 0);
if (version != 2) {
errno = ENOTSUP;
return false;
}
section_size = (size_t) data[0x01] + DISPLAYID2_MIN_SIZE;
if (section_size > DISPLAYID2_MAX_SIZE || section_size > size) {
errno = EINVAL;
return false;
}
if (!validate_checksum(data, section_size)) {
errno = EINVAL;
return false;
}
product_primary_use_case = data[0x02];
switch (product_primary_use_case) {
case DI_DISPLAYID2_PRODUCT_PRIMARY_USE_CASE_EXTENSION:
case DI_DISPLAYID2_PRODUCT_PRIMARY_USE_CASE_TEST:
case DI_DISPLAYID2_PRODUCT_PRIMARY_USE_CASE_GENERIC:
case DI_DISPLAYID2_PRODUCT_PRIMARY_USE_CASE_TV:
case DI_DISPLAYID2_PRODUCT_PRIMARY_USE_CASE_DESKTOP_PRODUCTIVITY:
case DI_DISPLAYID2_PRODUCT_PRIMARY_USE_CASE_DESKTOP_GAMING:
case DI_DISPLAYID2_PRODUCT_PRIMARY_USE_CASE_PRESENTATION:
case DI_DISPLAYID2_PRODUCT_PRIMARY_USE_CASE_HMD_VR:
case DI_DISPLAYID2_PRODUCT_PRIMARY_USE_CASE_HMD_AR:
displayid2->product_primary_use_case = product_primary_use_case;
break;
default:
errno = EINVAL;
return false;
}
i = DISPLAYID2_HEADER_SIZE;
max_data_block_size = 0;
while (i < section_size - 1) {
max_data_block_size = section_size - 1 - i;
if (is_data_block_end(&data[i], max_data_block_size))
break;
data_block_size = parse_data_block(displayid2, &data[i],
max_data_block_size);
if (data_block_size < 0)
return false;
assert(data_block_size > 0);
i += (size_t) data_block_size;
}
if (!is_all_zeroes(&data[i], max_data_block_size)) {
if (max_data_block_size < DISPLAYID2_DATA_BLOCK_HEADER_SIZE) {
add_failure(displayid2,
"Not enough bytes remain (%zu) for a DisplayID data block and the DisplayID filler is non-0.",
max_data_block_size);
} else {
add_failure(displayid2, "Padding: Contains non-zero bytes.");
}
}
displayid2->logger = NULL;
return true;
}
static void
data_block_destroy(struct di_displayid2_data_block *data_block)
{
size_t i;
for (i = 0; i < data_block->cta861.data_blocks_len; i++)
_di_cta_data_block_destroy(data_block->cta861.data_blocks[i]);
free(data_block);
}
void
_di_displayid2_finish(struct di_displayid2 *displayid2)
{
size_t i;
for (i = 0; i < displayid2->data_blocks_len; i++)
data_block_destroy(displayid2->data_blocks[i]);
}
int
di_displayid2_get_revision(const struct di_displayid2 *displayid2)
{
return displayid2->revision;
}
enum di_displayid2_product_primary_use_case
di_displayid2_get_product_primary_use_case(const struct di_displayid2 *displayid2)
{
return displayid2->product_primary_use_case;
}
const struct di_displayid2_data_block *const *
di_displayid2_get_data_blocks(const struct di_displayid2 *displayid2)
{
return (const struct di_displayid2_data_block *const *) displayid2->data_blocks;
}
enum di_displayid2_data_block_tag
di_displayid2_data_block_get_tag(const struct di_displayid2_data_block *data_block)
{
return data_block->tag;
}
const struct di_cta_data_block *const *
di_displayid2_data_block_get_cta_data_blocks(const struct di_displayid2_data_block *data_block)
{
if (data_block->tag != DI_DISPLAYID2_DATA_BLOCK_CTA861) {
return NULL;
}
return (const struct di_cta_data_block *const *) data_block->cta861.data_blocks;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,73 @@
#include <float.h>
#include <math.h>
#include <libdisplay-info/dmt.h>
#include <libdisplay-info/edid.h>
extern const struct di_dmt_timing _di_dmt_timings[];
extern const size_t _di_dmt_timings_len;
const struct di_dmt_timing *
di_dmt_timing_from_code(struct di_dmt_code dmt_code)
{
size_t i;
for (i = 0; i < _di_dmt_timings_len; i++) {
const struct di_dmt_timing *t = &_di_dmt_timings[i];
if (t->dmt_code.code == dmt_code.code)
return t;
}
return NULL;
}
struct di_dmt_code
di_dmt_timing_to_code(const struct di_dmt_timing *timing)
{
size_t i;
for (i = 0; i < _di_dmt_timings_len; i++) {
const struct di_dmt_timing *t = &_di_dmt_timings[i];
if (t->horiz_video == timing->horiz_video &&
t->vert_video == timing->vert_video &&
fabs(t->refresh_rate_hz - timing->refresh_rate_hz) <= FLT_EPSILON &&
t->pixel_clock_hz == timing->pixel_clock_hz &&
t->horiz_blank == timing->horiz_blank &&
t->vert_blank == timing->vert_blank &&
t->horiz_front_porch == timing->horiz_front_porch &&
t->vert_front_porch == timing->vert_front_porch &&
t->horiz_sync_pulse == timing->horiz_sync_pulse &&
t->vert_sync_pulse == timing->vert_sync_pulse &&
t->horiz_border == timing->horiz_border &&
t->vert_border == timing->vert_border &&
t->reduced_blanking == timing->reduced_blanking)
return t->dmt_code;
}
return (struct di_dmt_code) { .code = 0 };
}
const struct di_dmt_timing *
di_dmt_timing_from_edid_standard_timing(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;
}
}
return 0;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,128 @@
/*
* Copyright 2006-2012 Red Hat, Inc.
* Copyright 2018-2021 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
*
* Originally imported from edid-decode.
*/
#include <math.h>
#include <libdisplay-info/gtf.h>
/**
* The assumed character cell granularity of the graphics system, in pixels.
*/
#define CELL_GRAN 8.0
/**
* The size of the top and bottom overscan margin as a percentage of the active
* vertical image.
*/
#define MARGIN_PERC 1.8
/**
* The minimum front porch in lines (vertical) and character cells (horizontal).
*/
#define MIN_PORCH 1.0
/**
* The width of the V sync in lines.
*/
#define V_SYNC_RQD 3.0
/**
* The width of the H sync as a percentage of the total line period.
*/
#define H_SYNC_PERC 8.0
/**
* Minimum time of vertical sync + back porch interval (µs).
*/
#define MIN_VSYNC_BP 550.0
void di_gtf_compute(struct di_gtf_timing *t, const struct di_gtf_options *options)
{
double c_prime, m_prime, h_pixels_rnd, v_lines_rnd, h_margin, v_margin, interlace,
total_active_pixels, v_field_rate_rqd, h_period_est, total_v_lines, v_field_rate_est,
h_period, ideal_duty_cycle, h_freq, ideal_h_period, v_back_porch, h_sync,
h_front_porch;
double v_sync_bp = 0.0, h_blank_pixels = 0.0, total_pixels = 0.0, pixel_freq = 0.0;
/* C' and M' are part of the Blanking Duty Cycle computation */
c_prime = ((options->c - options->j) * options->k / 256.0) + options->j;
m_prime = options->k / 256.0 * options->m;
h_pixels_rnd = round(options->h_pixels / CELL_GRAN) * CELL_GRAN;
v_lines_rnd = options->int_rqd ?
round(options->v_lines / 2.0) :
options->v_lines;
h_margin = options->margins_rqd ?
round(h_pixels_rnd * MARGIN_PERC / 100.0 / CELL_GRAN) * CELL_GRAN :
0;
v_margin = options->margins_rqd ?
round(MARGIN_PERC / 100.0 * v_lines_rnd) :
0;
interlace = options->int_rqd ? 0.5 : 0;
total_active_pixels = h_pixels_rnd + h_margin * 2;
switch (options->ip_param) {
case DI_GTF_IP_PARAM_V_FRAME_RATE:
// vertical frame frequency (Hz)
v_field_rate_rqd = options->int_rqd ?
options->ip_freq_rqd * 2 :
options->ip_freq_rqd;
h_period_est = (1.0 / v_field_rate_rqd - MIN_VSYNC_BP / 1000000.0) /
(v_lines_rnd + v_margin * 2 + MIN_PORCH + interlace) * 1000000.0;
v_sync_bp = round(MIN_VSYNC_BP / h_period_est);
total_v_lines = v_lines_rnd + v_margin * 2 +
v_sync_bp + interlace + MIN_PORCH;
v_field_rate_est = 1.0 / h_period_est / total_v_lines * 1000000.0;
h_period = h_period_est / (v_field_rate_rqd / v_field_rate_est);
ideal_duty_cycle = c_prime - m_prime * h_period / 1000.0;
h_blank_pixels = round(total_active_pixels * ideal_duty_cycle /
(100.0 - ideal_duty_cycle) /
(2 * CELL_GRAN)) * 2 * CELL_GRAN;
total_pixels = total_active_pixels + h_blank_pixels;
pixel_freq = total_pixels / h_period;
break;
case DI_GTF_IP_PARAM_H_FREQ:
// horizontal frequency (kHz)
h_freq = options->ip_freq_rqd;
v_sync_bp = round(MIN_VSYNC_BP * h_freq / 1000.0);
ideal_duty_cycle = c_prime - m_prime / h_freq;
h_blank_pixels = round(total_active_pixels * ideal_duty_cycle /
(100.0 - ideal_duty_cycle) /
(2 * CELL_GRAN)) * 2 * CELL_GRAN;
total_pixels = total_active_pixels + h_blank_pixels;
pixel_freq = total_pixels * h_freq / 1000.0;
break;
case DI_GTF_IP_PARAM_H_PIXELS:
// pixel clock rate (MHz)
pixel_freq = options->ip_freq_rqd;
ideal_h_period = (c_prime - 100.0 +
sqrt((100.0 - c_prime) * (100.0 - c_prime) +
0.4 * m_prime * (total_active_pixels + h_margin * 2) / pixel_freq))
/ 2.0 / m_prime * 1000.0;
ideal_duty_cycle = c_prime - m_prime * ideal_h_period / 1000.0;
h_blank_pixels = round(total_active_pixels * ideal_duty_cycle /
(100.0 - ideal_duty_cycle) /
(2 * CELL_GRAN)) * 2 * CELL_GRAN;
total_pixels = total_active_pixels + h_blank_pixels;
h_freq = pixel_freq / total_pixels * 1000.0;
v_sync_bp = round(MIN_VSYNC_BP * h_freq / 1000.0);
break;
}
v_back_porch = v_sync_bp - V_SYNC_RQD;
h_sync = round(H_SYNC_PERC / 100.0 * total_pixels / CELL_GRAN) * CELL_GRAN;
h_front_porch = h_blank_pixels / 2.0 - h_sync;
*t = (struct di_gtf_timing) {
.h_pixels = (int) h_pixels_rnd,
.v_lines = options->v_lines,
.v_sync = V_SYNC_RQD,
.h_sync = (int) h_sync,
.v_front_porch = MIN_PORCH,
.v_back_porch = (int) v_back_porch,
.h_front_porch = (int) h_front_porch,
.h_back_porch = (int) (h_front_porch + h_sync),
.h_border = (int) h_margin,
.v_border = (int) v_margin,
.pixel_freq_mhz = pixel_freq,
};
}
@@ -0,0 +1,97 @@
#include <stddef.h>
#include <libdisplay-info/hdmi-vic.h>
/**
* HDMI video format table. 8.2.3.1 section of the 1.4b HDMI spec.
*/
static const struct di_hdmi_vic_video_format _di_hdmi_vic_video_formats[] = {
{
.vic = { .code = 1 },
.h_active = 3840,
.v_active = 2160,
.pixel_clock_hz = 297000000,
.h_front = 176,
.h_sync = 88,
.h_back = 296,
.v_front = 8,
.v_sync = 10,
.v_back = 72,
},
{
.vic = { .code = 2 },
.h_active = 3840,
.v_active = 2160,
.pixel_clock_hz = 297000000,
.h_front = 1056,
.h_sync = 88,
.h_back = 296,
.v_front = 8,
.v_sync = 10,
.v_back = 72,
},
{
.vic = { .code = 3 },
.h_active = 3840,
.v_active = 2160,
.pixel_clock_hz = 297000000,
.h_front = 1276,
.h_sync = 88,
.h_back = 296,
.v_front = 8,
.v_sync = 10,
.v_back = 72,
},
{
.vic = { .code = 4 },
.h_active = 4096,
.v_active = 2160,
.pixel_clock_hz = 297000000,
.h_front = 1020,
.h_sync = 88,
.h_back = 296,
.v_front = 8,
.v_sync = 10,
.v_back = 72,
},
};
static const size_t _di_hdmi_vic_video_formats_len =
sizeof(_di_hdmi_vic_video_formats) / sizeof(_di_hdmi_vic_video_formats[0]);
const struct di_hdmi_vic_video_format *
di_hdmi_vic_video_format_from_vic(struct di_hdmi_vic vic)
{
size_t i;
for (i = 0; i < _di_hdmi_vic_video_formats_len; i++) {
if (_di_hdmi_vic_video_formats[i].vic.code == vic.code)
return &_di_hdmi_vic_video_formats[i];
}
return NULL;
}
struct di_hdmi_vic
di_hdmi_vic_video_format_to_vic(const struct di_hdmi_vic_video_format *format)
{
size_t i;
for (i = 0; i < _di_hdmi_vic_video_formats_len; i++) {
const struct di_hdmi_vic_video_format *candidate = &_di_hdmi_vic_video_formats[i];
if (candidate->vic.code != 0 &&
candidate->h_active == format->h_active &&
candidate->v_active == format->v_active &&
candidate->h_front == format->h_front &&
candidate->v_front == format->v_front &&
candidate->h_sync == format->h_sync &&
candidate->v_sync == format->v_sync &&
candidate->h_back == format->h_back &&
candidate->v_back == format->v_back &&
candidate->pixel_clock_hz == format->pixel_clock_hz)
return candidate->vic;
}
return (struct di_hdmi_vic) { .code = 0 };
}
@@ -0,0 +1,40 @@
#ifndef BITS_H
#define BITS_H
/**
* Utility functions to operate on bits.
*/
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
/**
* Check whether a byte has a bit set.
*/
static inline bool
has_bit(uint8_t val, size_t index)
{
return val & (1 << index);
}
/**
* Extract a bit range from a byte.
*
* Both offsets are inclusive, start from zero, and high must be greater than low.
*/
static inline uint8_t
get_bit_range(uint8_t val, size_t high, size_t low)
{
size_t n;
uint8_t bitmask;
assert(high <= 7 && high >= low);
n = high - low + 1;
bitmask = (uint8_t) ((1 << n) - 1);
return (uint8_t) (val >> low) & bitmask;
}
#endif
@@ -0,0 +1,287 @@
#ifndef CTA_H
#define CTA_H
/**
* Private header for the low-level CTA API.
*/
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <libdisplay-info/cta.h>
#include <displayid.h>
/**
* The maximum number of data blocks in an EDID CTA block.
*
* Each data block takes at least 1 byte, the CTA block can hold 128 bytes, and
* the mandatory fields take up 5 bytes (4 header bytes + checksum).
*/
#define EDID_CTA_MAX_DATA_BLOCKS 123
/**
* The maximum number of detailed timing definitions included in an EDID CTA
* block.
*
* The CTA extension leaves at most 122 bytes for timings, and each timing takes
* 18 bytes.
*/
#define EDID_CTA_MAX_DETAILED_TIMING_DEFS 6
/**
* The maximum number of SVD entries in a video data block.
*
* Each data block has its size described in a 5-bit field, so its maximum size
* is 63 bytes, and each SVD uses 1 byte.
*/
#define EDID_CTA_MAX_VIDEO_BLOCK_ENTRIES 63
/**
* The maximum number of SAD entries in an audio data block.
*
* Each data block has its size described in a 5-bit field, so its maximum size
* is 63 bytes, and each SAD uses 3 bytes.
*/
#define EDID_CTA_MAX_AUDIO_BLOCK_ENTRIES 21
/**
* The maximum number of Capability Bit Map entries in a YCbCr 4:2:0 video data
* block.
*
* Each data block has its size described in a 5-bit field, so its maximum size
* is 63 bytes, and each Capability Bit Map uses 1 byte.
*/
#define EDID_CTA_MAX_YCBCR420_CAP_MAP_BLOCK_ENTRIES 63
/**
* The maximum number of Short InfoFrame Descriptor or Short Vendor-Specific
* InfoFrame Descriptor entries in a InfoFrame data block.
*
* Each data block has its size described in a 5-bit field, so its maximum size
* is 63 bytes, the header takes up at least 2 bytes and the smallest Short
* InfoFrame Descriptor is 1 byte.
*/
#define EDID_CTA_INFOFRAME_BLOCK_ENTRIES 61
/**
* The maximum number of Speaker Location Descriptors in a Speaker Location data
* block.
*
* Each data block has its size described in a 5-bit field, so its maximum size
* is 63 bytes, and each Speaker Location Descriptors uses at least 2 byte.
*/
#define EDID_CTA_MAX_SPEAKER_LOCATION_BLOCK_ENTRIES 31
/**
* The maximum number of SVR entries in a video format preference block.
*
* Each data block has its size described in a 5-bit field, so its maximum size
* is 63 bytes, and each SVR uses 1 byte.
*/
#define EDID_CTA_MAX_VIDEO_FORMAT_PREF_BLOCK_ENTRIES 63
/**
* The maximum number of format entries in a HDMI audio block.
*
* Each data block has its size described in a 5-bit field, so its maximum size
* is 63 bytes, the header takes up 2 bytes and each format entry uses 4 bytes.
*/
#define EDID_CTA_MAX_HDMI_AUDIO_BLOCK_ENTRIES 15
/**
* Context for CTA parsing functions.
*/
struct di_cta {
int revision;
const struct di_edid_cta_flags *flags; /* may be NULL for non-EDID blocks */
struct di_logger *logger;
};
struct di_edid_cta {
int revision;
struct di_edid_cta_flags flags;
/* NULL-terminated */
struct di_cta_data_block *data_blocks[EDID_CTA_MAX_DATA_BLOCKS + 1];
size_t data_blocks_len;
/* NULL-terminated */
struct di_edid_detailed_timing_def_priv *detailed_timing_defs[EDID_CTA_MAX_DETAILED_TIMING_DEFS + 1];
size_t detailed_timing_defs_len;
};
struct di_cta_hdr_static_metadata_block_priv {
struct di_cta_hdr_static_metadata_block base;
struct di_cta_hdr_static_metadata_eotfs eotfs;
struct di_cta_hdr_static_metadata_descriptors descriptors;
};
struct di_cta_hdr_dynamic_metadata_type3 {
uint8_t unused;
};
struct di_cta_hdr_dynamic_metadata_block_priv {
struct di_cta_hdr_dynamic_metadata_block base;
struct di_cta_hdr_dynamic_metadata_type1 type1;
struct di_cta_hdr_dynamic_metadata_type2 type2;
struct di_cta_hdr_dynamic_metadata_type3 type3;
struct di_cta_hdr_dynamic_metadata_type4 type4;
struct di_cta_hdr_dynamic_metadata_type256 type256;
};
struct di_cta_video_block_priv {
struct di_cta_video_block base;
/* NULL-terminated */
struct di_cta_svd *svds[EDID_CTA_MAX_VIDEO_BLOCK_ENTRIES + 1];
size_t svds_len;
};
struct di_cta_ycbcr420_video_block_priv {
struct di_cta_ycbcr420_video_block base;
/* NULL-terminated */
struct di_cta_svd *svds[EDID_CTA_MAX_VIDEO_BLOCK_ENTRIES + 1];
size_t svds_len;
};
struct di_cta_sad_priv {
struct di_cta_sad base;
struct di_cta_sad_sample_rates supported_sample_rates;
struct di_cta_sad_lpcm lpcm;
struct di_cta_sad_mpegh_3d mpegh_3d;
struct di_cta_sad_mpeg_aac mpeg_aac;
struct di_cta_sad_mpeg_surround mpeg_surround;
struct di_cta_sad_mpeg_aac_le mpeg_aac_le;
struct di_cta_sad_enhanced_ac3 enhanced_ac3;
struct di_cta_sad_mat mat;
struct di_cta_sad_wma_pro wma_pro;
};
struct di_cta_audio_block_priv {
struct di_cta_audio_block audio;
/* NULL-terminated */
struct di_cta_sad_priv *sads[EDID_CTA_MAX_AUDIO_BLOCK_ENTRIES + 1];
size_t sads_len;
};
struct di_cta_ycbcr420_cap_map_block {
bool all;
uint8_t svd_bitmap[EDID_CTA_MAX_YCBCR420_CAP_MAP_BLOCK_ENTRIES];
};
struct di_cta_hdmi_audio_block_priv {
struct di_cta_hdmi_audio_block base;
struct di_cta_hdmi_audio_multi_stream ms;
struct di_cta_hdmi_audio_3d a3d;
/* NULL-terminated */
struct di_cta_sad_priv *sads[EDID_CTA_MAX_HDMI_AUDIO_BLOCK_ENTRIES + 1];
size_t sads_len;
};
struct di_cta_infoframe_block_priv {
struct di_cta_infoframe_block base;
/* NULL-terminated */
struct di_cta_infoframe_descriptor *infoframes[EDID_CTA_INFOFRAME_BLOCK_ENTRIES + 1];
size_t infoframes_len;
};
struct di_cta_speaker_location_priv {
struct di_cta_speaker_location_block base;
/* NULL-terminated */
struct di_cta_speaker_location_descriptor *locations[EDID_CTA_MAX_SPEAKER_LOCATION_BLOCK_ENTRIES + 1];
size_t locations_len;
};
struct di_cta_video_format_pref_priv {
struct di_cta_video_format_pref_block base;
/* NULL-terminated */
struct di_cta_svr *svrs[EDID_CTA_MAX_VIDEO_FORMAT_PREF_BLOCK_ENTRIES + 1];
size_t svrs_len;
};
struct di_cta_type_vii_timing_priv {
struct di_cta_type_vii_timing_block base;
struct di_displayid_type_i_ii_vii_timing timing;
};
struct di_cta_vendor_hdmi_block_priv {
struct di_cta_vendor_hdmi_block base;
/* HDMI VIC's. */
struct di_hdmi_vic *vics;
};
struct di_cta_dolby_video_block_priv {
struct di_cta_dolby_video_block base;
struct di_cta_dolby_video_block_v0 v0;
struct di_cta_dolby_video_block_v1 v1;
struct di_cta_dolby_video_block_v2 v2;
};
struct di_cta_vendor_hdmi_forum_block_priv {
struct di_cta_vendor_hdmi_forum_block base;
struct di_cta_hdmi_dsc dsc;
};
struct di_cta_hdmi_forum_sink_cap_priv {
struct di_cta_hdmi_forum_sink_cap base;
struct di_cta_hdmi_dsc dsc;
};
struct di_cta_data_block {
enum di_cta_data_block_tag tag;
/* Used for DI_CTA_DATA_BLOCK_VIDEO */
struct di_cta_video_block_priv video;
/* Used for DI_CTA_DATA_BLOCK_YCBCR420 */
struct di_cta_ycbcr420_video_block_priv ycbcr420;
/* used for DI_CTA_DATA_BLOCK_AUDIO */
struct di_cta_audio_block_priv audio;
/* Used for DI_CTA_DATA_BLOCK_SPEAKER_ALLOC */
struct di_cta_speaker_alloc_block speaker_alloc;
/* Used for DI_CTA_DATA_BLOCK_VIDEO_CAP */
struct di_cta_video_cap_block video_cap;
/* Used for DI_CTA_DATA_BLOCK_VESA_DISPLAY_DEVICE */
struct di_cta_vesa_display_device_block vesa_display_device;
/* Used for DI_CTA_DATA_BLOCK_COLORIMETRY */
struct di_cta_colorimetry_block colorimetry;
/* Used for DI_CTA_DATA_BLOCK_HDR_STATIC_METADATA */
struct di_cta_hdr_static_metadata_block_priv hdr_static_metadata;
/* Used for DI_CTA_DATA_BLOCK_HDR_DYNAMIC_METADATA */
struct di_cta_hdr_dynamic_metadata_block_priv hdr_dynamic_metadata;
/* Used for DI_CTA_DATA_BLOCK_VESA_DISPLAY_TRANSFER_CHARACTERISTIC */
struct di_cta_vesa_transfer_characteristics_block vesa_transfer_characteristics;
/* Used for DI_CTA_DATA_BLOCK_YCBCR420_CAP_MAP */
struct di_cta_ycbcr420_cap_map_block ycbcr420_cap_map;
/* Used for DI_CTA_DATA_BLOCK_HDMI_AUDIO */
struct di_cta_hdmi_audio_block_priv hdmi_audio;
/* Used for DI_CTA_DATA_BLOCK_INFOFRAME */
struct di_cta_infoframe_block_priv infoframe;
/* Used for DI_CTA_DATA_BLOCK_ROOM_CONFIG */
struct di_cta_room_configuration_block room_config;
/* Used for DI_CTA_DATA_BLOCK_SPEAKER_LOCATION */
struct di_cta_speaker_location_priv speaker_location;
/* Used for DI_CTA_DATA_BLOCK_VIDEO_FORMAT_PREF */
struct di_cta_video_format_pref_priv video_format_pref;
/* Used for DI_CTA_DATA_BLOCK_DISPLAYID_VIDEO_TIMING_VII */
struct di_cta_type_vii_timing_priv did_vii_timing;
/* Used for DI_CTA_DATA_BLOCK_VENDOR_HDMI */
struct di_cta_vendor_hdmi_block_priv vendor_hdmi;
/* Used for DI_CTA_DATA_BLOCK_HDR10PLUS */
struct di_cta_hdr10plus_block hdr10plus;
/* Used for DI_CTA_DATA_BLOCK_DOLBY_VIDEO */
struct di_cta_dolby_video_block_priv dolby_video;
/* Used for DI_CTA_DATA_BLOCK_HDMI_SINK_CAP */
struct di_cta_hdmi_forum_sink_cap_priv hdmi_sink_cap;
/* Used for DI_CTA_DATA_BLOCK_VENDOR_HDMI_FORUM */
struct di_cta_vendor_hdmi_forum_block_priv vendor_hdmi_forum;
};
bool
_di_edid_cta_parse(struct di_edid_cta *cta, const uint8_t *data, size_t size,
struct di_logger *logger);
void
_di_edid_cta_finish(struct di_edid_cta *cta);
bool
_di_cta_data_block_parse(struct di_cta *cta, uint8_t raw_tag, const uint8_t *data, size_t size,
struct di_cta_data_block **data_block_out);
void
_di_cta_data_block_destroy(struct di_cta_data_block *data_block);
#endif
@@ -0,0 +1,63 @@
#ifndef DI_EDID_DECODE_H
#define DI_EDID_DECODE_H
#include <stdbool.h>
#include <libdisplay-info/edid.h>
#include <libdisplay-info/cta.h>
#include <libdisplay-info/cvt.h>
#include <libdisplay-info/displayid.h>
struct uncommon_features {
bool color_point_descriptor;
bool color_management_data;
bool cta_transfer_characteristics;
};
extern struct uncommon_features uncommon_features;
struct di_edid;
struct di_edid_detailed_timing_def;
struct di_edid_cta;
struct di_displayid;
void
print_edid(const struct di_edid *edid);
void
print_detailed_timing_def(const struct di_edid_detailed_timing_def *def);
void
print_cta(const struct di_edid_cta *cta);
void
print_cta_data_block(const struct di_cta_data_block *data_block,
const struct di_cta_data_block *const *data_blocks);
void
print_displayid(const struct di_displayid *displayid);
void
print_displayid2(const struct di_displayid2 *displayid2);
void
print_displayid_type_i_ii_vii_timing(const struct di_displayid_type_i_ii_vii_timing *t,
int indent, const char *prefix);
void
print_cta_vic_timing(struct di_cta_vic vic);
void
print_hdmi_vic_timing(const struct di_hdmi_vic vic);
void
print_dmt_timing_code(struct di_dmt_code dmt_code);
void
print_cvt_timing(struct di_cvt_timing *t, struct di_cvt_options *options,
int hratio, int vratio, bool preferred, bool rb);
void
compute_aspect_ratio(int width, int height, int *horiz_ratio, int *vert_ratio);
#endif
@@ -0,0 +1,108 @@
#ifndef DISPLAYID_H
#define DISPLAYID_H
/**
* Private header for the low-level DisplayID API.
*/
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <libdisplay-info/displayid.h>
#include "log.h"
/**
* The maximum number of data blocks in a DisplayID section.
*
* A DisplayID section has a payload size of 251 bytes, and each data block has
* a minimum size of 3 bytes.
*/
#define DISPLAYID_MAX_DATA_BLOCKS 83
/**
* The maximum number of type I timings in a data block.
*
* A DisplayID data block has a maximum payload size of 248 bytes, and each type
* I timing takes up 20 bytes.
*/
#define DISPLAYID_MAX_TYPE_I_TIMINGS 12
/**
* The maximum number of type II timings in a data block.
*
* A DisplayID data block has a maximum payload size of 248 bytes, and each type
* I timing takes up 11 bytes.
*/
#define DISPLAYID_MAX_TYPE_II_TIMINGS 22
/**
* The maxiumum number of type III timings in a data block.
*
* A DisplayID data block has a maximum payload size of 248 bytes, and each type
* III timing takes up 3 bytes.
*/
#define DISPLAYID_MAX_TYPE_III_TIMINGS 82
struct di_displayid {
int version, revision;
enum di_displayid_product_type product_type;
struct di_displayid_data_block *data_blocks[DISPLAYID_MAX_DATA_BLOCKS + 1];
size_t data_blocks_len;
struct di_logger *logger;
};
struct di_displayid_display_params_priv {
struct di_displayid_display_params base;
struct di_displayid_display_params_features features;
};
struct di_displayid_tiled_topo_priv {
struct di_displayid_tiled_topo base;
struct di_displayid_tiled_topo_caps caps;
struct di_displayid_tiled_topo_bezel bezel;
};
struct di_displayid_data_block {
enum di_displayid_data_block_tag tag;
/* Used for TYPE_I_TIMING, NULL-terminated */
struct di_displayid_type_i_ii_vii_timing *type_i_timings[DISPLAYID_MAX_TYPE_I_TIMINGS + 1];
size_t type_i_timings_len;
/* Used for TYPE_II_TIMING, NULL-terminated */
struct di_displayid_type_i_ii_vii_timing *type_ii_timings[DISPLAYID_MAX_TYPE_II_TIMINGS + 1];
size_t type_ii_timings_len;
/* Used for TYPE_III_TIMING, NULL-terminated */
struct di_displayid_type_iii_timing *type_iii_timings[DISPLAYID_MAX_TYPE_III_TIMINGS + 1];
size_t type_iii_timings_len;
/* Used for TYPE_IV_TIMING */
struct di_displayid_type_iv_timing type_iv_timing;
/* Used for DISPLAY_PARAMS */
struct di_displayid_display_params_priv display_params;
/* Used for TILED_DISPLAY_TOPO */
struct di_displayid_tiled_topo_priv tiled_topo;
};
bool
_di_displayid_parse(struct di_displayid *displayid, const uint8_t *data,
size_t size, struct di_logger *logger);
void
_di_displayid_finish(struct di_displayid *displayid);
void
_di_displayid_parse_type_1_7_timing(struct di_displayid_type_i_ii_vii_timing *timing,
struct di_logger *logger,
const char *prefix,
const uint8_t *data,
bool is_type7);
int
_di_displayid_parse_version(const uint8_t *data, size_t size);
#endif
@@ -0,0 +1,64 @@
#ifndef DISPLAYID2_H
#define DISPLAYID2_H
/**
* Private header for the low-level DisplayID v2 API.
*/
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <libdisplay-info/displayid2.h>
#include "cta.h"
#include "log.h"
/**
* The maximum number of data blocks in a DisplayID v2 section.
*
* A DisplayID v2 section has a maximum payload size of 251 bytes (256 bytes
* maximum size, 5 bytes header), and each data block has a minimum size of
* 3 bytes.
*/
#define DISPLAYID2_MAX_DATA_BLOCKS 83
/**
* The maximum number of CTA data blocks in a DisplayID v2 CTA-861 data block.
*
* A DisplayID v2 section has a maximum payload size of 251 bytes (256 bytes
* maximum size, 5 bytes header), a DisplayID v2 data block has a maximum
* payload size of 248 bytes (3 bytes header), and each CTA data block takes at
* least 1 byte.
*/
#define DISPLAYID2_CTA861_MAX_DATA_BLOCKS 248
struct di_displayid2 {
int revision;
enum di_displayid2_product_primary_use_case product_primary_use_case;
struct di_displayid2_data_block *data_blocks[DISPLAYID2_MAX_DATA_BLOCKS + 1];
size_t data_blocks_len;
struct di_logger *logger;
};
struct di_displayid2_data_block {
enum di_displayid2_data_block_tag tag;
/* Used for DI_DISPLAYID2_DATA_BLOCK_CTA861 */
struct {
/* NULL-terminated */
struct di_cta_data_block *data_blocks[DISPLAYID2_CTA861_MAX_DATA_BLOCKS + 1];
size_t data_blocks_len;
} cta861;
};
bool
_di_displayid2_parse(struct di_displayid2 *displayid2, const uint8_t *data,
size_t size, struct di_logger *logger);
void
_di_displayid2_finish(struct di_displayid2 *displayid2);
#endif
@@ -0,0 +1,160 @@
#ifndef EDID_H
#define EDID_H
/**
* Private header for the low-level EDID API.
*/
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <libdisplay-info/edid.h>
#include "cta.h"
#include "displayid.h"
#include "displayid2.h"
/**
* The maximum number of EDID blocks (including the base block), defined in
* section 2.2.1.
*/
#define EDID_MAX_BLOCK_COUNT 256
/**
* The maximum number of EDID standard timings, defined in section 3.9.
*/
#define EDID_MAX_STANDARD_TIMING_COUNT 8
/**
* The number of EDID byte descriptors, defined in section 3.10.
*/
#define EDID_BYTE_DESCRIPTOR_COUNT 4
/**
* The size of an EDID byte descriptor, defined in section 3.10.
*/
#define EDID_BYTE_DESCRIPTOR_SIZE 18
/**
* The maximum number of standard timings in an EDID display descriptor, defined
* in section 3.10.3.6.
*/
#define EDID_MAX_DESCRIPTOR_STANDARD_TIMING_COUNT 6
/**
* The maximum number of color points in an EDID color point descriptor, defined
* in section 3.10.3.5.
*/
#define EDID_MAX_DESCRIPTOR_COLOR_POINT_COUNT 2
/**
* The maximum number of established timings III in an EDID display descriptor,
* defined in section 3.10.3.9.
*/
#define EDID_MAX_DESCRIPTOR_ESTABLISHED_TIMING_III_COUNT 44
/**
* The maximum number of CVT timing codes in an EDID display descriptor,
* defined in section 3.10.3.8.
*/
#define EDID_MAX_DESCRIPTOR_CVT_TIMING_CODES_COUNT 4
struct di_edid_detailed_timing_def_priv {
struct di_edid_detailed_timing_def base;
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;
};
struct di_edid {
struct di_edid_vendor_product vendor_product;
int version, revision;
bool is_digital;
struct di_edid_video_input_analog video_input_analog;
struct di_edid_video_input_digital video_input_digital;
struct di_edid_screen_size screen_size;
float gamma;
struct di_edid_dpms dpms;
enum di_edid_display_color_type display_color_type;
struct di_edid_color_encoding_formats color_encoding_formats;
struct di_edid_misc_features misc_features;
struct di_edid_chromaticity_coords chromaticity_coords;
struct di_edid_established_timings_i_ii established_timings_i_ii;
/* NULL-terminated */
struct di_edid_standard_timing *standard_timings[EDID_MAX_STANDARD_TIMING_COUNT + 1];
size_t standard_timings_len;
/* NULL-terminated */
struct di_edid_detailed_timing_def_priv *detailed_timing_defs[EDID_BYTE_DESCRIPTOR_COUNT + 1];
size_t detailed_timing_defs_len;
/* NULL-terminated */
struct di_edid_display_descriptor *display_descriptors[EDID_BYTE_DESCRIPTOR_COUNT + 1];
size_t display_descriptors_len;
/* NULL-terminated, doesn't include the base block */
struct di_edid_ext *exts[EDID_MAX_BLOCK_COUNT];
size_t exts_len;
struct di_logger *logger;
};
struct di_edid_display_range_limits_priv {
struct di_edid_display_range_limits base;
struct di_edid_display_range_limits_secondary_gtf secondary_gtf;
struct di_edid_display_range_limits_cvt cvt;
};
struct di_edid_display_descriptor {
enum di_edid_display_descriptor_tag tag;
/* Used for PRODUCT_SERIAL, DATA_STRING and PRODUCT_NAME,
* zero-terminated */
char str[14];
/* Used for RANGE_LIMITS */
struct di_edid_display_range_limits_priv range_limits;
/* Used for STD_TIMING_IDS, NULL-terminated */
struct di_edid_standard_timing *standard_timings[EDID_MAX_DESCRIPTOR_STANDARD_TIMING_COUNT + 1];
size_t standard_timings_len;
/* Used for COLOR_POINT, NULL-terminated */
struct di_edid_color_point *color_points[EDID_MAX_DESCRIPTOR_COLOR_POINT_COUNT + 1];
size_t color_points_len;
/* Used for ESTABLISHED_TIMINGS_III */
struct di_edid_established_timings_iii established_timings_iii;
/* Used for DCM_DATA */
struct di_edid_color_management_data dcm_data;
/* Used for CVT_TIMING_CODES, NULL-terminated */
struct di_edid_cvt_timing_code *cvt_timing_codes[EDID_MAX_DESCRIPTOR_CVT_TIMING_CODES_COUNT + 1];
size_t cvt_timing_codes_len;
};
struct di_edid_ext {
enum di_edid_ext_tag tag;
/* Used for DI_EDID_EXT_CEA */
struct di_edid_cta cta;
/* Used for DI_EDID_EXT_DISPLAYID */
int displayid_version;
struct di_displayid displayid;
struct di_displayid2 displayid2;
};
/**
* Create an EDID data structure.
*
* Callers do not need to keep the provided data pointer valid after calling
* this function. Callers should destroy the returned pointer via
* di_edid_destroy().
*/
struct di_edid *
_di_edid_parse(const void *data, size_t size, FILE *failure_msg_file);
/**
* Destroy an EDID data structure.
*/
void
_di_edid_destroy(struct di_edid *edid);
/**
* Parse an EDID detailed timing definition.
*/
struct di_edid_detailed_timing_def_priv *
_di_edid_parse_detailed_timing_def(const uint8_t data[static EDID_BYTE_DESCRIPTOR_SIZE]);
#endif
@@ -0,0 +1,28 @@
#ifndef INFO_H
#define INFO_H
/**
* Private header for the high-level API.
*/
#include <libdisplay-info/info.h>
/**
* All information here is derived from low-level information contained in
* struct di_info. These are exposed by the high-level API only.
*/
struct di_derived_info {
struct di_hdr_static_metadata hdr_static_metadata;
struct di_color_primaries color_primaries;
struct di_supported_signal_colorimetry supported_signal_colorimetry;
};
struct di_info {
struct di_edid *edid;
char *failure_msg;
struct di_derived_info derived;
};
#endif
@@ -0,0 +1,78 @@
#ifndef DI_CTA_VIC_H
#define DI_CTA_VIC_H
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
/**
* Low-level API for CTA Video Identification Codes (CTA VIC).
*/
/**
* A CTA Video Identification Code (VIC).
*/
struct di_cta_vic {
/* Video Identification Code (VIC) */
uint8_t code;
};
/**
* CTA video format picture aspect ratio.
*/
enum di_cta_vic_video_format_picture_aspect_ratio {
DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_4_3, /* 4:3 */
DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9, /* 16:9 */
DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27, /* 64:27 */
DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_256_135, /* 256:135 */
};
/**
* CTA video format sync pulse polarity.
*/
enum di_cta_vic_video_format_sync_polarity {
DI_CTA_VIC_VIDEO_FORMAT_SYNC_NEGATIVE, /* Negative */
DI_CTA_VIC_VIDEO_FORMAT_SYNC_POSITIVE, /* Positive */
};
/**
* A CTA-861 video format, defined in section 4.
*/
struct di_cta_vic_video_format {
/* Video Identification Code (VIC) */
struct di_cta_vic vic;
/* Horizontal/vertical active pixels/lines */
int32_t h_active, v_active;
/* Horizontal/vertical front porch */
int32_t h_front, v_front;
/* Horizontal/vertical sync pulse */
int32_t h_sync, v_sync;
/* Horizontal/vertical back porch */
int32_t h_back, v_back;
/* Horizontal/vertical sync pulse polarity */
enum di_cta_vic_video_format_sync_polarity h_sync_polarity, v_sync_polarity;
/* Pixel clock in Hz */
int64_t pixel_clock_hz;
/* Whether this timing is interlaced */
bool interlaced;
/* Picture aspect ratio */
enum di_cta_vic_video_format_picture_aspect_ratio picture_aspect_ratio;
};
/**
* Get a CTA-861 video format from a CTA VIC.
*
* Returns NULL if the CTA VIC is unknown.
*/
const struct di_cta_vic_video_format *
di_cta_vic_video_format_from_vic(struct di_cta_vic vic);
/**
* Get a CTA VIC from a CTA-861 video format.
*
* Returns 0 if the video format is unknown.
*/
struct di_cta_vic
di_cta_vic_video_format_to_vic(const struct di_cta_vic_video_format *format);
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,76 @@
#ifndef DI_CVT_H
#define DI_CVT_H
/**
* Low-level API for VESA Coordinated Video Timings (CVT) version 2.0.
*/
#include <stdbool.h>
#include <stdint.h>
enum di_cvt_reduced_blanking_version {
DI_CVT_REDUCED_BLANKING_NONE,
DI_CVT_REDUCED_BLANKING_V1,
DI_CVT_REDUCED_BLANKING_V2,
DI_CVT_REDUCED_BLANKING_V3,
};
/**
* Input parameters, defined in table 3-1.
*/
struct di_cvt_options {
/* Version of the reduced blanking timing formula to be used */
enum di_cvt_reduced_blanking_version red_blank_ver;
/* Desired active (visible) horizontal pixels and vertical lines per
* frame */
int32_t h_pixels, v_lines;
/* Target vertical refresh rate (in Hz) */
double ip_freq_rqd;
/* Whether to generate a "video-optimized" timing variant (RBv2 only) */
bool video_opt;
/* Desired VBlank time (in µs, RBv3 only, must be greater than 460) */
double vblank;
/* Desired additional number of pixels to add to the base HBlank
* duration (RBv3 only, must be a multiple of 8 between 0 and 120) */
int32_t additional_hblank;
/* Indicates whether the VSync location is early (RBv3 only) */
bool early_vsync_rqd;
/* Whether interlaced is required (no RB and RBv1 only) */
bool int_rqd;
/* Whether margins are required (no RB and RBv1 only) */
bool margins_rqd;
};
/**
* Output parameters, defined in table 3-4.
*/
struct di_cvt_timing {
/* Pixel clock frequency (in MHz) */
double act_pixel_freq;
/* Total number of active (visible) pixels per line */
double total_active_pixels;
/* Total number of active (visible) vertical lines per frame */
double v_lines_rnd;
/* Number of pixels in the horizontal front porch period */
double h_front_porch;
/* Number of pixels in the HSync period */
double h_sync;
/* Number of pixels in the horizontal back porch period */
double h_back_porch;
/* Number of lines in the vertical front porch period */
double v_front_porch;
/* Number of lines in the VSync period */
double v_sync;
/* Number of lines in the vertical back porch period */
double v_back_porch;
/* Frame rate (in Hz) */
double act_frame_rate;
};
/**
* Compute a timing via the CVT formula.
*/
void
di_cvt_compute(struct di_cvt_timing *t, const struct di_cvt_options *options);
#endif
@@ -1,3 +1,399 @@
#pragma once
#ifndef DI_DISPLAYID_H
#define DI_DISPLAYID_H
#include "info.h"
/**
* libdisplay-info's low-level API for VESA Display Identification Data
* (DisplayID).
*
* The library implements DisplayID version 1.3, available at:
* https://vesa.org/vesa-standards/
*/
#include <stdbool.h>
#include <stdint.h>
#include <libdisplay-info/dmt.h>
#include <libdisplay-info/hdmi-vic.h>
#include <libdisplay-info/cta-vic.h>
/**
* DisplayID data structure.
*/
struct di_displayid;
/**
* Get the DisplayID version.
*/
int
di_displayid_get_version(const struct di_displayid *displayid);
/**
* Get the DisplayID revision.
*/
int
di_displayid_get_revision(const struct di_displayid *displayid);
/**
* Product type identifier, defined in section 2.3.
*/
enum di_displayid_product_type {
/* Extension section */
DI_DISPLAYID_PRODUCT_TYPE_EXTENSION = 0x00,
/* Test structure or equipment */
DI_DISPLAYID_PRODUCT_TYPE_TEST = 0x01,
/* Display panel or other transducer, LCD or PDP module, etc. */
DI_DISPLAYID_PRODUCT_TYPE_DISPLAY_PANEL = 0x02,
/* Standalone display device, desktop monitor, TV monitor, etc. */
DI_DISPLAYID_PRODUCT_TYPE_STANDALONE_DISPLAY = 0x03,
/* Television receiver */
DI_DISPLAYID_PRODUCT_TYPE_TV_RECEIVER = 0x04,
/* Repeater/translator */
DI_DISPLAYID_PRODUCT_TYPE_REPEATER = 0x05,
/* Direct drive monitor */
DI_DISPLAYID_PRODUCT_TYPE_DIRECT_DRIVE = 0x06,
};
/**
* Get the DisplayID product type.
*/
enum di_displayid_product_type
di_displayid_get_product_type(const struct di_displayid *displayid);
/**
* DisplayID data block tag.
*/
enum di_displayid_data_block_tag {
/*Product Identification Data Block */
DI_DISPLAYID_DATA_BLOCK_PRODUCT_ID = 0x00,
/* Display Parameters Data Block */
DI_DISPLAYID_DATA_BLOCK_DISPLAY_PARAMS = 0x01,
/* Color Characteristics Data Block */
DI_DISPLAYID_DATA_BLOCK_COLOR_CHARACT = 0x02,
/* Video Timing Modes Type I - Detailed Timings Data Block */
DI_DISPLAYID_DATA_BLOCK_TYPE_I_TIMING = 0x03,
/* Video Timing Modes Type II - Detailed Timings Data Block */
DI_DISPLAYID_DATA_BLOCK_TYPE_II_TIMING = 0x04,
/* Video Timing Modes Type III - Short Timings Data Block */
DI_DISPLAYID_DATA_BLOCK_TYPE_III_TIMING = 0x05,
/* Video Timing Modes Type IV - DMT Timings Data Block */
DI_DISPLAYID_DATA_BLOCK_TYPE_IV_TIMING = 0x06,
/* Supported Timing Modes - VESA DMT Timings Data Block */
DI_DISPLAYID_DATA_BLOCK_VESA_TIMING = 0x07,
/* Supported Timing Modes - CTA-861 Timings Data Block */
DI_DISPLAYID_DATA_BLOCK_CEA_TIMING = 0x08,
/* Video Timing Range Data Block */
DI_DISPLAYID_DATA_BLOCK_TIMING_RANGE_LIMITS = 0x09,
/* Product Serial Number Data Block */
DI_DISPLAYID_DATA_BLOCK_PRODUCT_SERIAL = 0x0A,
/* General-purpose ASCII String Data Block */
DI_DISPLAYID_DATA_BLOCK_ASCII_STRING = 0x0B,
/* Display Device Data Data Block */
DI_DISPLAYID_DATA_BLOCK_DISPLAY_DEVICE_DATA = 0x0C,
/* Interface Power Sequencing Data Block */
DI_DISPLAYID_DATA_BLOCK_INTERFACE_POWER_SEQ = 0x0D,
/* Transfer Characteristics Data Block */
DI_DISPLAYID_DATA_BLOCK_TRANSFER_CHARACT = 0x0E,
/* Display Interface Data Block */
DI_DISPLAYID_DATA_BLOCK_DISPLAY_INTERFACE = 0x0F,
/* Stereo Display Interface Data Block */
DI_DISPLAYID_DATA_BLOCK_STEREO_DISPLAY_INTERFACE = 0x10,
/* Video Timing Modes Type V - Short Timings Data Block */
DI_DISPLAYID_DATA_BLOCK_TYPE_V_TIMING = 0x11,
/* Tiled Display Topology Data Block */
DI_DISPLAYID_DATA_BLOCK_TILED_DISPLAY_TOPO = 0x12,
/* Video Timing Modes Type VI - Detailed Timings Data Block */
DI_DISPLAYID_DATA_BLOCK_TYPE_VI_TIMING = 0x13,
};
/**
* A DisplayID data block.
*/
struct di_displayid_data_block;
/**
* Get a DisplayID data block tag.
*/
enum di_displayid_data_block_tag
di_displayid_data_block_get_tag(const struct di_displayid_data_block *data_block);
/**
* Display parameters feature support flags, defined in section 4.2.3.
*/
struct di_displayid_display_params_features {
/* Audio support on video interface */
bool audio;
/* Audio inputs are provided separately from the video interface */
bool separate_audio_inputs;
/* Audio information received via the video interface will automatically
* override any other audio input channels provided */
bool audio_input_override;
/* Display supports the VESA Display Power Management (DPM) standard */
bool power_management;
/* Display is capable of only a single fixed timing */
bool fixed_timing;
/* Display is capable of supporting timings at only a single fixed pixel
* format */
bool fixed_pixel_format;
/* Display supports ACP, ISRC1 or ISRC2 packets */
bool ai;
/* Display by default will de-interlace any interlaced video input */
bool deinterlacing;
};
/**
* Display parameters data block, defined in section 4.2.
*/
struct di_displayid_display_params {
/* Image size in millimeters accurate to the thenths place, zero if unset */
float horiz_image_mm, vert_image_mm;
/* Native format size in pixels, zero if unset */
int32_t horiz_pixels, vert_pixels;
/* Feature flags */
const struct di_displayid_display_params_features *features;
/* Transfer characteristic gamma, zero if unset */
float gamma;
/* Aspect ratio (long axis divided by short axis) */
float aspect_ratio;
/* Color bit depth (dynamic range) */
int32_t bits_per_color_overall, bits_per_color_native;
};
/**
* Get display parameters from a DisplayID data block.
*
* Returns NULL if the data block tag isn't
* DI_DISPLAYID_DATA_BLOCK_DISPLAY_PARAMS.
*/
const struct di_displayid_display_params *
di_displayid_data_block_get_display_params(const struct di_displayid_data_block *data_block);
enum di_displayid_type_i_ii_vii_timing_stereo_3d {
/* This timing is always displayed monoscopic (no stereo) */
DI_DISPLAYID_TYPE_I_II_VII_TIMING_STEREO_3D_NEVER = 0x00,
/* This timing is always displayed in stereo */
DI_DISPLAYID_TYPE_I_II_VII_TIMING_STEREO_3D_ALWAYS = 0x01,
/* This timing is displayed in mono or stereo depending on a user action
* (wearing the stereo glasses, etc.) */
DI_DISPLAYID_TYPE_I_II_VII_TIMING_STEREO_3D_USER = 0x02,
};
enum di_displayid_timing_aspect_ratio {
DI_DISPLAYID_TIMING_ASPECT_RATIO_1_1 = 0x00,
DI_DISPLAYID_TIMING_ASPECT_RATIO_5_4 = 0x01,
DI_DISPLAYID_TIMING_ASPECT_RATIO_4_3 = 0x02,
DI_DISPLAYID_TIMING_ASPECT_RATIO_15_9 = 0x03,
DI_DISPLAYID_TIMING_ASPECT_RATIO_16_9 = 0x04,
DI_DISPLAYID_TIMING_ASPECT_RATIO_16_10 = 0x05,
DI_DISPLAYID_TIMING_ASPECT_RATIO_64_27 = 0x06,
DI_DISPLAYID_TIMING_ASPECT_RATIO_256_135 = 0x07,
DI_DISPLAYID_TIMING_ASPECT_RATIO_UNDEFINED = 0x08,
};
enum di_displayid_type_i_ii_vii_timing_sync_polarity {
DI_DISPLAYID_TYPE_I_II_VII_TIMING_SYNC_NEGATIVE = 0x00,
DI_DISPLAYID_TYPE_I_II_VII_TIMING_SYNC_POSITIVE = 0x01,
};
/**
* Type I timing, defined in DisplayID 1.3 section 4.4.1 and
* Type II timing, defined in DisplayID 1.3 section 4.4.2 and
* Type VII timing, defined in DisplayID 2.0 section 4.3.1.
*/
struct di_displayid_type_i_ii_vii_timing {
double pixel_clock_mhz; /* mega-hertz */
bool preferred;
enum di_displayid_type_i_ii_vii_timing_stereo_3d stereo_3d;
bool interlaced;
enum di_displayid_timing_aspect_ratio aspect_ratio;
int32_t horiz_active, vert_active;
int32_t horiz_blank, vert_blank;
int32_t horiz_offset, vert_offset;
int32_t horiz_sync_width, vert_sync_width;
enum di_displayid_type_i_ii_vii_timing_sync_polarity horiz_sync_polarity, vert_sync_polarity;
};
/**
* Get type I timings from a DisplayID data block.
*
* The returned array is NULL-terminated.
*
* Returns NULL if the data block tag isn't
* DI_DISPLAYID_DATA_BLOCK_TYPE_I_TIMING.
*/
const struct di_displayid_type_i_ii_vii_timing *const *
di_displayid_data_block_get_type_i_timings(const struct di_displayid_data_block *data_block);
/**
* Get type II timings from a DisplayID data block.
*
* The returned array is NULL-terminated.
*
* Returns NULL if the data block tag isn't
* DI_DISPLAYID_DATA_BLOCK_TYPE_II_TIMING.
*/
const struct di_displayid_type_i_ii_vii_timing *const *
di_displayid_data_block_get_type_ii_timings(const struct di_displayid_data_block *data_block);
/**
* Behavior when more than 1 tile and less than total number of tiles are driven
* by the source.
*/
enum di_displayid_tiled_topo_missing_recv_behavior {
/* Undefined */
DI_DISPLAYID_TILED_TOPO_MISSING_RECV_UNDEF = 0,
/* The image is displayed on the tile only */
DI_DISPLAYID_TILED_TOPO_MISSING_RECV_TILE_ONLY = 1,
};
/**
* Behavior of this tile when it is the only tile receiving an image from the
* source.
*/
enum di_displayid_tiled_topo_single_recv_behavior {
/* Undefined */
DI_DISPLAYID_TILED_TOPO_SINGLE_RECV_UNDEF = 0,
/* Image is displayed on the tile only */
DI_DISPLAYID_TILED_TOPO_SINGLE_RECV_TILE_ONLY = 1,
/* Image is scaled to fit the entire tiled display */
DI_DISPLAYID_TILED_TOPO_SINGLE_RECV_SCALED = 2,
/* Image is cloned to all other tiles of the entire tiled display */
DI_DISPLAYID_TILED_TOPO_SINGLE_RECV_CLONED = 3,
};
/**
* Tiled display capabilities.
*/
struct di_displayid_tiled_topo_caps {
/* The tiled display is within a single physical display enclosure */
bool single_enclosure;
/* Behavior when subsets of the tiles of the entire tiled display are
* receiving images from source */
enum di_displayid_tiled_topo_missing_recv_behavior missing_recv_behavior;
enum di_displayid_tiled_topo_single_recv_behavior single_recv_behavior;
};
/**
* Tiled display bezel information.
*
* The lengths are measured in pixels, accurate to the tenths place.
*/
struct di_displayid_tiled_topo_bezel {
float top_px, bottom_px, right_px, left_px;
};
/**
* Tiled display topology, defined in section 4.14.
*/
struct di_displayid_tiled_topo {
/* Capabilities */
const struct di_displayid_tiled_topo_caps *caps;
/* Total number of horizontal/vertical tiles */
int32_t total_horiz_tiles, total_vert_tiles;
/* Horizontal/vertical tile location */
int32_t horiz_tile_location, vert_tile_location;
/* Horizontal/vertical size in pixels */
int32_t horiz_tile_pixels, vert_tile_lines;
/* Bezel information, NULL if unset */
const struct di_displayid_tiled_topo_bezel *bezel;
/* Vendor PnP ID, product code and serial number */
char vendor_id[3];
uint16_t product_code;
uint32_t serial_number;
};
/**
* Get tiled display topology from a DisplayID data block.
*
* Returns NULL if the data block tag isn't
* DI_DISPLAYID_DATA_BLOCK_TILED_DISPLAY_TOPO.
*/
const struct di_displayid_tiled_topo *
di_displayid_data_block_get_tiled_topo(const struct di_displayid_data_block *data_block);
/**
* Formula/algorithm for type III timings.
*/
enum di_displayid_type_iii_timing_algo {
/* VESA CVT, standard blanking */
DI_DISPLAYID_TYPE_III_TIMING_CVT_STANDARD_BLANKING = 0,
/* VESA CVT, reduced blanking */
DI_DISPLAYID_TYPE_III_TIMING_CVT_REDUCED_BLANKING = 1,
};
/**
* Type III timing, defined in section 4.4.3.
*/
struct di_displayid_type_iii_timing {
bool preferred;
enum di_displayid_type_iii_timing_algo algo;
enum di_displayid_timing_aspect_ratio aspect_ratio;
/* Horizontal Active Image (in pixels) */
int32_t horiz_active;
bool interlaced;
/* Frame/Field Refresh Rate (in Hz) */
int32_t refresh_rate_hz;
};
/**
* Get type III timings from a DisplayID data block.
*
* The returned array is NULL-terminated.
*
* Returns NULL if the data block tag isn't
* DI_DISPLAYID_DATA_BLOCK_TYPE_III_TIMING.
*/
const struct di_displayid_type_iii_timing *const *
di_displayid_data_block_get_type_iii_timings(const struct di_displayid_data_block *data_block);
/**
* Type IV timing, Timing Code Type
*/
enum di_displayid_type_iv_timing_code_type {
/* DMT Timing Code */
DI_DISPLAYID_TYPE_IV_TIMING_CODE_TYPE_DMT = 0,
/* CTA/CEA VIC Timing Code */
DI_DISPLAYID_TYPE_IV_TIMING_CODE_TYPE_VIC_CTA = 1,
/* HDMI VIC Timing Code */
DI_DISPLAYID_TYPE_IV_TIMING_CODE_TYPE_VIC_HDMI = 2,
};
/**
* Type IV timing, defined in section 4.4.4.
*/
struct di_displayid_type_iv_timing {
/* Type of codes */
enum di_displayid_type_iv_timing_code_type type;
/* Number of codes in one of dmts, cta_vics or hdmi_vics, depending on the value of type*/
size_t codes_len;
/* DMT Timing Codes, if type is DI_DISPLAYID_TYPE_IV_TIMING_CODE_TYPE_DMT, NULL otherwise */
const struct di_dmt_code *dmts;
/* CTA VICs, if type is DI_DISPLAYID_TYPE_IV_TIMING_CODE_TYPE_VIC_CTA, NULL otherwise */
const struct di_cta_vic *cta_vics;
/* HDMI VICs, if type is DI_DISPLAYID_TYPE_IV_TIMING_CODE_TYPE_VIC_HDMI, NULL otherwise */
const struct di_hdmi_vic *hdmi_vics;
};
/**
* Get type IV timing from a DisplayID data block.
*
* The returned array is NULL-terminated.
*
* Returns NULL if the data block tag isn't
* DI_DISPLAYID_DATA_BLOCK_TYPE_IV_TIMING.
*/
const struct di_displayid_type_iv_timing *
di_displayid_data_block_get_type_iv_timing(const struct di_displayid_data_block *data_block);
/**
* Get DisplayID data blocks.
*
* The returned array is NULL-terminated.
*/
const struct di_displayid_data_block *const *
di_displayid_get_data_blocks(const struct di_displayid *displayid);
#endif
@@ -0,0 +1,121 @@
#ifndef DI_DISPLAYID2_H
#define DI_DISPLAYID2_H
/**
* libdisplay-info's low-level API for VESA Display Identification Data
* (DisplayID) version 2.
*
* The library implements DisplayID version 2.1, available at:
* https://vesa.org/vesa-standards/
*/
#include <stdbool.h>
#include <stdint.h>
/**
* DisplayID v2 data structure.
*/
struct di_displayid2;
/**
* Get the DisplayID v2 revision.
*/
int
di_displayid2_get_revision(const struct di_displayid2 *displayid2);
/**
* Get DisplayID v2 data blocks.
*
* The returned array is NULL-terminated.
*/
const struct di_displayid2_data_block *const *
di_displayid2_get_data_blocks(const struct di_displayid2 *displayid2);
/**
* Product primary use case identifier, defined in table 2-3.
*/
enum di_displayid2_product_primary_use_case {
/* Extension section */
DI_DISPLAYID2_PRODUCT_PRIMARY_USE_CASE_EXTENSION = 0x00,
/* Test structure */
DI_DISPLAYID2_PRODUCT_PRIMARY_USE_CASE_TEST = 0x01,
/* Generic display */
DI_DISPLAYID2_PRODUCT_PRIMARY_USE_CASE_GENERIC = 0x02,
/* Television display */
DI_DISPLAYID2_PRODUCT_PRIMARY_USE_CASE_TV = 0x03,
/* Desktop productivity display */
DI_DISPLAYID2_PRODUCT_PRIMARY_USE_CASE_DESKTOP_PRODUCTIVITY = 0x04,
/* Desktop gaming display */
DI_DISPLAYID2_PRODUCT_PRIMARY_USE_CASE_DESKTOP_GAMING = 0x05,
/* Presentation display */
DI_DISPLAYID2_PRODUCT_PRIMARY_USE_CASE_PRESENTATION = 0x06,
/* Head-mounted Virtual Reality (VR) display */
DI_DISPLAYID2_PRODUCT_PRIMARY_USE_CASE_HMD_VR = 0x07,
/* Head-mounted Augmented Reality (AR) display */
DI_DISPLAYID2_PRODUCT_PRIMARY_USE_CASE_HMD_AR = 0x08,
};
/**
* DisplayID v2 data block tag.
*/
enum di_displayid2_data_block_tag {
/* Product Identification Data Block */
DI_DISPLAYID2_DATA_BLOCK_PRODUCT_ID = 0x20,
/* Display Parameters Data Block */
DI_DISPLAYID2_DATA_BLOCK_DISPLAY_PARAMS = 0x21,
/* Type VII Timing Detailed Timing Data Block */
DI_DISPLAYID2_DATA_BLOCK_TYPE_VII_TIMING = 0x22,
/* Type VIII Timing Enumerated Timing Code Data Block */
DI_DISPLAYID2_DATA_BLOCK_TYPE_VIII_TIMING = 0x23,
/* Type IX Timing Formula-based Timing Data Block */
DI_DISPLAYID2_DATA_BLOCK_TYPE_IX_TIMING = 0x24,
/* Dynamic Video Timing Range Limits Data Block */
DI_DISPLAYID2_DATA_BLOCK_DYN_TIMING_RANGE_LIMITS = 0x25,
/* Display Interface Features Data Block */
DI_DISPLAYID2_DATA_BLOCK_DISPLAY_INTERFACE_FEATURES = 0x26,
/* Stereo Display Interface Data Block */
DI_DISPLAYID2_DATA_BLOCK_STEREO_DISPLAY_INTERFACE = 0x27,
/* Tiled Display Topology Data Block */
DI_DISPLAYID2_DATA_BLOCK_TILED_DISPLAY_TOPO = 0x28,
/* ContainerID Data Block */
DI_DISPLAYID2_DATA_BLOCK_CONTAINERID = 0x29,
/* Type X Timing Formula-based Timing Data Block */
DI_DISPLAYID2_DATA_BLOCK_TYPE_X_TIMING = 0x2A,
/* Adaptive-Sync Data Block */
DI_DISPLAYID2_DATA_BLOCK_ADAPTIVE_SYNC = 0x2B,
/* ARVR_HMD Data Block */
DI_DISPLAYID2_DATA_BLOCK_ARVR_HMD = 0x2C,
/* ARVR_Layer Data Block */
DI_DISPLAYID2_DATA_BLOCK_ARVR_LAYER = 0x2D,
/* CTA-861 Data Block Encapsulation DisplayID Data Block */
DI_DISPLAYID2_DATA_BLOCK_CTA861 = 0x81,
};
/**
* A DisplayID v2 data block.
*/
struct di_displayid2_data_block;
/**
* Get a DisplayID v2 data block tag.
*/
enum di_displayid2_data_block_tag
di_displayid2_data_block_get_tag(const struct di_displayid2_data_block *data_block);
/**
* Get the DisplayID v2 product primary use case.
*/
enum di_displayid2_product_primary_use_case
di_displayid2_get_product_primary_use_case(const struct di_displayid2 *displayid2);
/**
* Get CTA data blocks from a DisplayID v2 CTA-861 data block.
*
* The returned array is NULL-terminated.
*
* Returns NULL if the data block tag isn't DI_DISPLAYID2_DATA_BLOCK_CTA861.
*/
const struct di_cta_data_block *const *
di_displayid2_data_block_get_cta_data_blocks(const struct di_displayid2_data_block *data_block);
#endif
@@ -0,0 +1,77 @@
#ifndef DI_DMT_H
#define DI_DMT_H
/**
* libdisplay-info's low-level API for VESA Display Monitor Timing (DMT).
*
* The library implements VESA DMT version 1.0 revision 13.
*/
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
/**
* A DMT Timing Code.
*/
struct di_dmt_code {
/* Timing Code */
uint8_t code;
};
/**
* A DMT timing.
*/
struct di_dmt_timing {
/* DMT Timing Code */
struct di_dmt_code dmt_code;
/* EDID standard timing 2-byte code, zero if unset */
uint16_t edid_std_id;
/* CVT 3-byte code, zero if unset */
uint32_t cvt_id;
/* Addressable pixels */
int32_t horiz_video, vert_video;
/* Field Refresh Rate in Hz */
float refresh_rate_hz;
/* Pixel clock in Hz */
int32_t pixel_clock_hz;
/* Horizontal/Vertical Blanking in pixels/lines */
int32_t horiz_blank, vert_blank;
/* Horizontal/Vertical Front Porch in pixels/lines */
int32_t horiz_front_porch, vert_front_porch;
/* Horizontal/Vertical Sync Pulse Width in pixels/lines */
int32_t horiz_sync_pulse, vert_sync_pulse;
/* Horizontal/Vertical Border in pixels/lines */
int32_t horiz_border, vert_border;
/* Whether the timing has reduced blanking */
bool reduced_blanking;
};
/**
* Get a VESA Display Monitor Timing (DMT) from a DMT Timing Code.
*
* Returns NULL if the DMT Timing Code is unknown.
*/
const struct di_dmt_timing *
di_dmt_timing_from_code(struct di_dmt_code dmt_code);
/**
* Get a DMT Timing Code from a VESA Display Monitor Timing (DMT).
*
* Returns a code of 0 if the DMT Timing Code is unknown.
*/
struct di_dmt_code
di_dmt_timing_to_code(const struct di_dmt_timing *timing);
/* See <libdisplay-info/edid.h> */
struct di_edid_standard_timing;
/**
* Get a VESA Display Monitor Timing (DMT), if any.
*
* NULL is returned if the standard timing doesn't have a DMT.
*/
const struct di_dmt_timing *
di_dmt_timing_from_edid_standard_timing(const struct di_edid_standard_timing *t);
#endif
@@ -1,3 +1,833 @@
#pragma once
#ifndef DI_EDID_H
#define DI_EDID_H
#include "info.h"
/**
* libdisplay-info's low-level API for Extended Display Identification
* Data (EDID).
*
* EDID 1.4 is defined in VESA Enhanced Extended Display Identification Data
* Standard release A revision 2.
*/
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
/**
* EDID data structure.
*/
struct di_edid;
/**
* Get the EDID version.
*/
int
di_edid_get_version(const struct di_edid *edid);
/**
* Get the EDID revision.
*/
int
di_edid_get_revision(const struct di_edid *edid);
/**
* EDID vendor & product identification.
*/
struct di_edid_vendor_product {
char manufacturer[3];
uint16_t product;
uint32_t serial; /* zero if unset */
/* These fields are zero if unset */
int manufacture_week;
int manufacture_year;
int model_year;
};
const struct di_edid_vendor_product *
di_edid_get_vendor_product(const struct di_edid *edid);
/**
* EDID analog signal level standard.
*/
enum di_edid_video_input_analog_signal_level_std {
/* 0.700 : 0.300 : 1.000 V p-p */
DI_EDID_VIDEO_INPUT_ANALOG_SIGNAL_LEVEL_0 = 0x00,
/* 0.714 : 0.286 : 1.000 V p-p */
DI_EDID_VIDEO_INPUT_ANALOG_SIGNAL_LEVEL_1 = 0x01,
/* 1.000 : 0.400 : 1.400 V p-p */
DI_EDID_VIDEO_INPUT_ANALOG_SIGNAL_LEVEL_2 = 0x02,
/* 0.700 : 0.000 : 0.700 V p-p */
DI_EDID_VIDEO_INPUT_ANALOG_SIGNAL_LEVEL_3 = 0x03,
};
/**
* EDID analog video setup.
*/
enum di_edid_video_input_analog_video_setup {
/* Blank level equals black level */
DI_EDID_VIDEO_INPUT_ANALOG_BLANK_LEVEL_EQ_BLACK = 0,
/* Blank-to-black setup or pedestal */
DI_EDID_VIDEO_INPUT_ANALOG_BLANK_TO_BLACK_SETUP_PEDESTAL = 1,
};
/**
* EDID analog video input basic information, defined in section 3.6.1.
*/
struct di_edid_video_input_analog {
enum di_edid_video_input_analog_signal_level_std signal_level_std;
enum di_edid_video_input_analog_video_setup video_setup;
/* Separate Sync H & V Signals are supported */
bool sync_separate;
/* Composite Sync Signal on Horizontal is supported */
bool sync_composite;
/* Composite Sync Signal on Green Video is supported */
bool sync_on_green;
/* Serration on the Vertical Sync is supported */
bool sync_serrations;
};
/**
* Get the analog video input basic information.
*
* Returns NULL if this isn't an analog display.
*/
const struct di_edid_video_input_analog *
di_edid_get_video_input_analog(const struct di_edid *edid);
/**
* Digital video input interface standard.
*/
enum di_edid_video_input_digital_interface {
DI_EDID_VIDEO_INPUT_DIGITAL_UNDEFINED = 0x00,
DI_EDID_VIDEO_INPUT_DIGITAL_DVI = 0x01,
DI_EDID_VIDEO_INPUT_DIGITAL_HDMI_A = 0x02,
DI_EDID_VIDEO_INPUT_DIGITAL_HDMI_B = 0x03,
DI_EDID_VIDEO_INPUT_DIGITAL_MDDI = 0x04,
DI_EDID_VIDEO_INPUT_DIGITAL_DISPLAYPORT = 0x05,
};
/**
* EDID digital video input basic information, defined in section 3.6.1.
*/
struct di_edid_video_input_digital {
/* EDID 1.2 and 1.3 */
bool dfp1; /* Compatible with VESA DFP 1.x TDMS CRGB */
/* EDID 1.4 and later */
int color_bit_depth; /* zero if undefined */
enum di_edid_video_input_digital_interface interface;
};
/**
* Get the digital video input basic information.
*
* Returns NULL if this isn't a digital display.
*/
const struct di_edid_video_input_digital *
di_edid_get_video_input_digital(const struct di_edid *edid);
struct di_edid_screen_size {
/* Physical size in centimeters, zero if unset */
int width_cm, height_cm;
/* Aspect ratio rounded to the hundredth decimal place, zero if unset. */
float landscape_aspect_ratio, portait_aspect_ratio;
};
/**
* Get the screen size.
*/
const struct di_edid_screen_size *
di_edid_get_screen_size(const struct di_edid *edid);
/**
* Get the display transfer characteristics from the basic EDID parameters, also
* known as "gamma".
*
* Returns 0 if unset (ie, stored in an extension block).
*/
float
di_edid_get_basic_gamma(const struct di_edid *edid);
/**
* Supported legacy Display Power Management Signaling (DPMS) states, defined in
* section 3.6.4.
*
* Display Power Management (DPM) compliant displays only support "off".
*/
struct di_edid_dpms {
bool standby;
bool suspend;
bool off;
};
/**
* Get the set of supported legacy DPMS states.
*/
const struct di_edid_dpms *
di_edid_get_dpms(const struct di_edid *edid);
/**
* Display color type.
*/
enum di_edid_display_color_type {
/* Monochrome or grayscale display */
DI_EDID_DISPLAY_COLOR_MONOCHROME = 0x00,
/* RGB color display */
DI_EDID_DISPLAY_COLOR_RGB = 0x01,
/* Non-RGB color display */
DI_EDID_DISPLAY_COLOR_NON_RGB = 0x02,
/* Undefined */
DI_EDID_DISPLAY_COLOR_UNDEFINED = 0x03,
};
/**
* Get the display color type.
*
* For digital displays using EDID 1.4 and later, DI_EDID_DISPLAY_COLOR_UNDEFINED
* is always returned.
*/
enum di_edid_display_color_type
di_edid_get_display_color_type(const struct di_edid *edid);
/**
* Basic color encoding formats, defined in section 3.6.4.
*/
struct di_edid_color_encoding_formats {
bool rgb444; /* RGB 4:4:4 */
bool ycrcb444; /* YCrCb 4:4:4 */
bool ycrcb422; /* YCrCb 4:2:2 */
};
/**
* Get the set of supported color encoding formats.
*
* Returns NULL if the display is analog or if the color encoding formats are
* not specified.
*/
const struct di_edid_color_encoding_formats *
di_edid_get_color_encoding_formats(const struct di_edid *edid);
/**
* Miscellaneous basic features, defined in section 3.6.4.
*
* Note, the enum values don't match the specification.
*/
struct di_edid_misc_features {
/**
* First detailed timing is the preferred timing.
*
* Always set for EDID 1.4 and later.
*/
bool has_preferred_timing;
/**
* GTF using the default parameters is supported.
*
* Never set for EDID 1.4 and later.
*/
bool default_gtf;
/**
* sRGB standard default color space is primary color space.
*/
bool srgb_is_primary;
/**
* Preferred timing mode includes native pixel format and rate.
*
* Never set for EDID 1.3 and earlier.
*/
bool preferred_timing_is_native;
/**
* GTF or CVT generated timings within the display's range limits are
* accepted.
*
* Never set for EDID 1.3 and earlier.
*/
bool continuous_freq;
};
/**
* Get the set of miscellaneous basic features.
*/
const struct di_edid_misc_features *
di_edid_get_misc_features(const struct di_edid *edid);
/**
* EDID display chromaticity coordinates, defined in section 3.7.
*
* The values are accurate to the thousandth place. The red, green and blue
* values are zero for monochrome displays.
*/
struct di_edid_chromaticity_coords {
float red_x, red_y;
float green_x, green_y;
float blue_x, blue_y;
float white_x, white_y;
};
/**
* Get chromaticity coordinates.
*/
const struct di_edid_chromaticity_coords *
di_edid_get_chromaticity_coords(const struct di_edid *edid);
/**
* Established timings I and II, defined in section 3.8.
*/
struct di_edid_established_timings_i_ii {
/* Established timings I */
bool has_720x400_70hz; /* 720 x 400 @ 70Hz (IBM, VGA) */
bool has_720x400_88hz; /* 720 x 400 @ 88Hz (IBM, XGA2) */
bool has_640x480_60hz; /* 640 x 480 @ 60Hz (IBM, VGA) */
bool has_640x480_67hz; /* 640 x 480 @ 67Hz (Apple, Mac II) */
bool has_640x480_72hz; /* 640 x 480 @ 72Hz (VESA) */
bool has_640x480_75hz; /* 640 x 480 @ 75Hz (VESA) */
bool has_800x600_56hz; /* 800 x 600 @ 56Hz (VESA) */
bool has_800x600_60hz; /* 800 x 600 @ 60Hz (VESA) */
/* Established timings II */
bool has_800x600_72hz; /* 800 x 600 @ 72Hz (VESA) */
bool has_800x600_75hz; /* 800 x 600 @ 75Hz (VESA) */
bool has_832x624_75hz; /* 832 x 624 @ 75Hz (Apple, Mac II) */
bool has_1024x768_87hz_interlaced; /* 1024 x 768 @ 87Hz interlaced (IBM) */
bool has_1024x768_60hz; /* 1024 x 768 @ 60Hz (VESA) */
bool has_1024x768_70hz; /* 1024 x 768 @ 70Hz (VESA) */
bool has_1024x768_75hz; /* 1024 x 768 @ 75Hz (VESA) */
bool has_1280x1024_75hz; /* 1280 x 1024 @ 75Hz (VESA) */
bool has_1152x870_75hz; /* 1152 x 870 @ 75Hz (Apple, Mac II) */
};
/**
* Get established timings I and II.
*/
const struct di_edid_established_timings_i_ii *
di_edid_get_established_timings_i_ii(const struct di_edid *edid);
/**
* Aspect ratio for an EDID standard timing.
*/
enum di_edid_standard_timing_aspect_ratio {
DI_EDID_STANDARD_TIMING_16_10 = 0, /* 16:10 */
DI_EDID_STANDARD_TIMING_4_3 = 1, /* 4:3 */
DI_EDID_STANDARD_TIMING_5_4 = 2, /* 5:4 */
DI_EDID_STANDARD_TIMING_16_9 = 3, /* 16:9 */
};
/**
* EDID standard timing, defined in section 3.9.
*/
struct di_edid_standard_timing {
/* Horizontal addressable pixels */
int32_t horiz_video;
/* Aspect ratio */
enum di_edid_standard_timing_aspect_ratio aspect_ratio;
/* Field Refresh Rate in Hz */
int32_t refresh_rate_hz;
};
/**
* Get the vertical addressable line count of an EDID standard timing.
*/
int32_t
di_edid_standard_timing_get_vert_video(const struct di_edid_standard_timing *t);
/**
* Get a list of EDID standard timings.
*
* The returned array is NULL-terminated.
*/
const struct di_edid_standard_timing *const *
di_edid_get_standard_timings(const struct di_edid *edid);
/**
* Stereo viewing support.
*/
enum di_edid_detailed_timing_def_stereo {
/* Normal Display No Stereo */
DI_EDID_DETAILED_TIMING_DEF_STEREO_NONE,
/* Field sequential stereo, right image when stereo sync signal = 1 */
DI_EDID_DETAILED_TIMING_DEF_STEREO_FIELD_SEQ_RIGHT,
/* Field sequential stereo, left image when stereo sync signal = 1 */
DI_EDID_DETAILED_TIMING_DEF_STEREO_FIELD_SEQ_LEFT,
/* 2-way interleaved stereo, right image on even lines */
DI_EDID_DETAILED_TIMING_DEF_STEREO_2_WAY_INTERLEAVED_RIGHT,
/* 2-way interleaved stereo, left image on even lines */
DI_EDID_DETAILED_TIMING_DEF_STEREO_2_WAY_INTERLEAVED_LEFT,
/* 4-way interleaved stereo */
DI_EDID_DETAILED_TIMING_DEF_STEREO_4_WAY_INTERLEAVED,
/* Side-by-Side interleaved stereo */
DI_EDID_DETAILED_TIMING_DEF_STEREO_SIDE_BY_SIDE_INTERLEAVED,
};
/**
* Signal definitions for EDID detailed timings, defined in notes for table 3.22.
*/
enum di_edid_detailed_timing_def_signal_type {
/* Analog composite */
DI_EDID_DETAILED_TIMING_DEF_SIGNAL_ANALOG_COMPOSITE = 0x00,
/* Bipolar analog composite */
DI_EDID_DETAILED_TIMING_DEF_SIGNAL_BIPOLAR_ANALOG_COMPOSITE = 0x01,
/* Digital composite */
DI_EDID_DETAILED_TIMING_DEF_SIGNAL_DIGITAL_COMPOSITE = 0x02,
/* Digital separate */
DI_EDID_DETAILED_TIMING_DEF_SIGNAL_DIGITAL_SEPARATE = 0x03,
};
/**
* Digital separate sync polarity for EDID detailed timings.
*/
enum di_edid_detailed_timing_def_sync_polarity {
DI_EDID_DETAILED_TIMING_DEF_SYNC_NEGATIVE = 0,
DI_EDID_DETAILED_TIMING_DEF_SYNC_POSITIVE = 1,
};
/**
* Flags for ANALOG_COMPOSITE signals
*/
struct di_edid_detailed_timing_analog_composite {
/* Sync with serrations (H-sync during V-sync) */
bool sync_serrations;
/* Sync on green signal only (as opposed to all three
* RGB video signals) */
bool sync_on_green;
};
/**
* Flags for BIPOLAR_ANALOG_COMPOSITE signals
*/
struct di_edid_detailed_timing_bipolar_analog_composite {
/* Sync with serrations (H-sync during V-sync) */
bool sync_serrations;
/* Sync on green signal only (as opposed to all three
* RGB video signals) */
bool sync_on_green;
};
/**
* Flags for DIGITAL_COMPOSITE signals
*/
struct di_edid_detailed_timing_digital_composite {
/* Sync with serrations (H-sync during V-sync) */
bool sync_serrations;
/* Horizontal polarity (outside of V-sync) */
enum di_edid_detailed_timing_def_sync_polarity sync_horiz_polarity;
};
/**
* Flags for DIGITAL_SEPARATE signals
*/
struct di_edid_detailed_timing_digital_separate {
/* Vertical polarity */
enum di_edid_detailed_timing_def_sync_polarity sync_vert_polarity;
/* Horizontal polarity (outside of V-sync) */
enum di_edid_detailed_timing_def_sync_polarity sync_horiz_polarity;
};
/**
* EDID detailed timing definition, defined in section 3.10.2.
*/
struct di_edid_detailed_timing_def {
/* Pixel clock */
int32_t pixel_clock_hz;
/* Horizontal/Vertical Addressable Video in pixels/lines */
int32_t horiz_video, vert_video;
/* Horizontal/Vertical Blanking in pixels/lines */
int32_t horiz_blank, vert_blank;
/* Horizontal/Vertical Front Porch in pixels/lines */
int32_t horiz_front_porch, vert_front_porch;
/* Horizontal/Vertical Sync Pulse Width in pixels/lines */
int32_t horiz_sync_pulse, vert_sync_pulse;
/* Horizontal/Vertical Addressable Video Image Size in mm, zero if unset */
int32_t horiz_image_mm, vert_image_mm;
/* Horizontal/Vertical Border in pixels/lines */
int32_t horiz_border, vert_border;
/* Interlaced signal */
bool interlaced;
/* Stereo Viewing Support */
enum di_edid_detailed_timing_def_stereo stereo;
/* Signal type */
enum di_edid_detailed_timing_def_signal_type signal_type;
/* Flags for ANALOG_COMPOSITE signals, NULL otherwise */
const struct di_edid_detailed_timing_analog_composite *analog_composite;
/* Flags for BIPOLAR_ANALOG_COMPOSITE signals, NULL otherwise */
const struct di_edid_detailed_timing_bipolar_analog_composite *bipolar_analog_composite;
/* Flags for DIGITAL_COMPOSITE signals, NULL otherwise */
const struct di_edid_detailed_timing_digital_composite *digital_composite;
/* Flags for DIGITAL_SEPARATE signals, NULL otherwise */
const struct di_edid_detailed_timing_digital_separate *digital_separate;
};
/**
* Get a list of EDID detailed timing definitions.
*
* The returned array is NULL-terminated.
*/
const struct di_edid_detailed_timing_def *const *
di_edid_get_detailed_timing_defs(const struct di_edid *edid);
/**
* EDID display descriptor.
*/
struct di_edid_display_descriptor;
/**
* Get a list of EDID display descriptors.
*
* The returned array is NULL-terminated.
*/
const struct di_edid_display_descriptor *const *
di_edid_get_display_descriptors(const struct di_edid *edid);
/**
* EDID display descriptor tag, defined in section 3.10.3.
*/
enum di_edid_display_descriptor_tag {
/* Display Product Serial Number */
DI_EDID_DISPLAY_DESCRIPTOR_PRODUCT_SERIAL = 0xFF,
/* Alphanumeric Data String (ASCII) */
DI_EDID_DISPLAY_DESCRIPTOR_DATA_STRING = 0xFE,
/* Display Range Limits */
DI_EDID_DISPLAY_DESCRIPTOR_RANGE_LIMITS = 0xFD,
/* Display Product Name */
DI_EDID_DISPLAY_DESCRIPTOR_PRODUCT_NAME = 0xFC,
/* Color Point Data */
DI_EDID_DISPLAY_DESCRIPTOR_COLOR_POINT = 0xFB,
/* Standard Timing Identifications */
DI_EDID_DISPLAY_DESCRIPTOR_STD_TIMING_IDS = 0xFA,
/* Display Color Management (DCM) Data */
DI_EDID_DISPLAY_DESCRIPTOR_DCM_DATA = 0xF9,
/* CVT 3 Byte Timing Codes */
DI_EDID_DISPLAY_DESCRIPTOR_CVT_TIMING_CODES = 0xF8,
/* Established Timings III */
DI_EDID_DISPLAY_DESCRIPTOR_ESTABLISHED_TIMINGS_III = 0xF7,
/* Dummy Descriptor */
DI_EDID_DISPLAY_DESCRIPTOR_DUMMY = 0x10,
};
/**
* Get the tag of an EDID display descriptor.
*/
enum di_edid_display_descriptor_tag
di_edid_display_descriptor_get_tag(const struct di_edid_display_descriptor *desc);
/**
* Get the contents of a product serial number, a data string, or a product name
* display descriptor.
*
* Returns NULL if the display descriptor tag isn't either
* DI_EDID_DISPLAY_DESCRIPTOR_PRODUCT_SERIAL_NUMBER,
* DI_EDID_DISPLAY_DESCRIPTOR_DATA_STRING or
* DI_EDID_DISPLAY_DESCRIPTOR_PRODUCT_NAME.
*/
const char *
di_edid_display_descriptor_get_string(const struct di_edid_display_descriptor *desc);
/**
* EDID display range limits type.
*
* The values do not match the EDID specification.
*
* The CVT entry was introduced in EDID 1.4.
*/
enum di_edid_display_range_limits_type {
/* Range Limits Only - no additional timing information is provided */
DI_EDID_DISPLAY_RANGE_LIMITS_BARE,
/* Default GTF supported */
DI_EDID_DISPLAY_RANGE_LIMITS_DEFAULT_GTF,
/* Secondary GTF supported */
DI_EDID_DISPLAY_RANGE_LIMITS_SECONDARY_GTF,
/* CVT supported */
DI_EDID_DISPLAY_RANGE_LIMITS_CVT,
};
struct di_edid_display_range_limits_secondary_gtf {
/* Start break frequency in Hz */
int start_freq_hz;
/* C, M, K, J parameters */
float c, m, k, j;
};
enum di_edid_cvt_aspect_ratio {
DI_EDID_CVT_ASPECT_RATIO_4_3 = 1 << 7,
DI_EDID_CVT_ASPECT_RATIO_16_9 = 1 << 6,
DI_EDID_CVT_ASPECT_RATIO_16_10 = 1 << 5,
DI_EDID_CVT_ASPECT_RATIO_5_4 = 1 << 4,
DI_EDID_CVT_ASPECT_RATIO_15_9 = 1 << 3,
};
enum di_edid_cvt_scaling {
DI_EDID_CVT_SCALING_HORIZ_SHRINK = 1 << 7,
DI_EDID_CVT_SCALING_HORIZ_STRETCH = 1 << 6,
DI_EDID_CVT_SCALING_VERT_SHRINK = 1 << 5,
DI_EDID_CVT_SCALING_VERT_STRETCH = 1 << 4,
};
struct di_edid_display_range_limits_cvt {
int32_t version, revision;
/* Maximum active pixels per line, zero for no limit */
int32_t max_horiz_px;
/* Supported aspect ratio, bitfield of enum di_edid_cvt_aspect_ratio */
uint32_t supported_aspect_ratio;
/* Preferred aspect ratio */
enum di_edid_cvt_aspect_ratio preferred_aspect_ratio;
/* Whether standard CVT blanking is supported */
bool standard_blanking;
/* Whether reduced CVT blanking is supported */
bool reduced_blanking;
/* Supported types of display scaling, bitfield of
* enum di_edid_cvt_scaling */
uint32_t supported_scaling;
/* Preferred vertical refresh rate, in Hz */
int32_t preferred_vert_refresh_hz;
};
/**
* EDID display range limits, defined in section 3.10.3.3.1.
*/
struct di_edid_display_range_limits {
/* Vertical rate limits in Hz, from 1 Hz to 510 Hz */
int32_t min_vert_rate_hz, max_vert_rate_hz;
/* Horizontal rate limits in Hz, from 1 KHz to 510 KHz, rounded to the
* nearest multiple of 1 KHz */
int32_t min_horiz_rate_hz, max_horiz_rate_hz;
/* Maximum pixel clock in Hz, zero if unset, rounded to the nearest
* multiple of 0.25 MHz if CVT, otherwise to the nearest multiple of
* 10 MHz */
int64_t max_pixel_clock_hz;
enum di_edid_display_range_limits_type type;
/* For SECONDARY_GTF limits, NULL otherwise */
const struct di_edid_display_range_limits_secondary_gtf *secondary_gtf;
/* For CVT limits, NULL otherwise */
const struct di_edid_display_range_limits_cvt *cvt;
};
/**
* Get the contents of a display range limits descriptor.
*
* Returns NULL if the display descriptor tag isn't
* DI_EDID_DISPLAY_DESCRIPTOR_RANGE_LIMITS.
*/
const struct di_edid_display_range_limits *
di_edid_display_descriptor_get_range_limits(const struct di_edid_display_descriptor *desc);
/**
* Get a standard timing list from an EDID display descriptor.
*
* The returned array is NULL-terminated.
*
* Returns NULL if the display descriptor tag isn't
* DI_EDID_DISPLAY_DESCRIPTOR_STD_TIMING_IDS.
*/
const struct di_edid_standard_timing *const *
di_edid_display_descriptor_get_standard_timings(const struct di_edid_display_descriptor *desc);
/**
* EDID Color Points, defined in section 3.10.3.5.
*/
struct di_edid_color_point {
/* White point index number */
int index;
/* The white chromaticity values, accurate to the thousandth place */
float white_x, white_y;
/* Gamma, zero if unset (ie, stored in an extension block) */
float gamma;
};
/**
* Get a color point list from an EDID display descriptor.
*
* The returned array is NULL-terminated.
*
* Returns NULL if the display descriptor tag isn't
* DI_EDID_DISPLAY_DESCRIPTOR_COLOR_POINT.
*
* Upstream is not aware of any EDID blob containing Color Point Descriptors.
* If such a blob is found, please share it with upstream!
*/
const struct di_edid_color_point *const *
di_edid_display_descriptor_get_color_points(const struct di_edid_display_descriptor *desc);
/**
* Established timings III
*/
struct di_edid_established_timings_iii {
size_t dmt_codes_len;
struct di_dmt_code *dmt_codes;
};
/**
* Get the established timings III from an EDID display descriptor.
*
* Returns NULL if the display descriptor tag isn't
* DI_EDID_DISPLAY_DESCRIPTOR_ESTABLISHED_TIMINGS_III.
*/
const struct di_edid_established_timings_iii *
di_edid_display_descriptor_get_established_timings_iii(const struct di_edid_display_descriptor *desc);
/**
* EDID display Color Management Data, defined in section 3.10.3.7
*
* Contains the coefficients for the function `L = a₃ × v³ + a₂ × v²`
* describing the luminance response L to some voltage v [0, 0.7] for each color
* channel.
*
* For more information see VESA DCM Standard, Version 1; January 6, 2003
*/
struct di_edid_color_management_data {
int version;
/* red polynomial coefficient a3 */
float red_a3;
/* red polynomial coefficient a2 */
float red_a2;
/* green polynomial coefficient a3 */
float green_a3;
/* green polynomial coefficient a2 */
float green_a2;
/* blue polynomial coefficient a3 */
float blue_a3;
/* blue polynomial coefficient a2 */
float blue_a2;
};
/**
* Get the contents of a Display Color Management (DCM) Data descriptor.
*
* Returns NULL if the display descriptor tag isn't
* DI_EDID_DISPLAY_DESCRIPTOR_DCM_DATA.
*
* Upstream is not aware of any EDID blob containing DCM Data descriptors.
* If such a blob is found, please share it with upstream!
*/
const struct di_edid_color_management_data *
di_edid_display_descriptor_get_color_management_data(const struct di_edid_display_descriptor *desc);
/**
* Aspect ratio for an EDID CVT Timing Code.
*/
enum di_edid_cvt_timing_code_aspect_ratio {
DI_EDID_CVT_TIMING_CODE_4_3 = 0, /* 4:3 */
DI_EDID_CVT_TIMING_CODE_16_9 = 1, /* 16:9 */
DI_EDID_CVT_TIMING_CODE_16_10 = 2, /* 16:10 */
DI_EDID_CVT_TIMING_CODE_15_9 = 3, /* 15:9 */
};
/**
* Preferred Vertical Rate for an EDID CVT Timing Code.
*/
enum di_edid_cvt_timing_code_preferred_vrate {
DI_EDID_CVT_TIMING_CODE_PREFERRED_VRATE_50HZ = 0, /* 50 Hz */
DI_EDID_CVT_TIMING_CODE_PREFERRED_VRATE_60HZ = 1, /* 60 Hz */
DI_EDID_CVT_TIMING_CODE_PREFERRED_VRATE_75HZ = 2, /* 75 Hz */
DI_EDID_CVT_TIMING_CODE_PREFERRED_VRATE_85HZ = 3, /* 85 Hz */
};
/**
* EDID CVT Timing Code, defined in section 3.10.3.8
*
* For more information see VESA Coordinated Video Timings (CVT) Standard.
*/
struct di_edid_cvt_timing_code {
int32_t addressable_lines_per_field;
enum di_edid_cvt_timing_code_aspect_ratio aspect_ratio;
bool supports_50hz_sb;
bool supports_60hz_sb;
bool supports_75hz_sb;
bool supports_85hz_sb;
bool supports_60hz_rb;
enum di_edid_cvt_timing_code_preferred_vrate preferred_vertical_rate;
};
/**
* Get a list of CVT timing codes from an EDID display descriptor.
* The highest priority code comes first, the lowest priority code last.
*
* The returned array is NULL-terminated.
*
* Returns NULL if the display descriptor tag isn't
* DI_EDID_DISPLAY_DESCRIPTOR_CVT_TIMING_CODES.
*/
const struct di_edid_cvt_timing_code *const *
di_edid_display_descriptor_get_cvt_timing_codes(const struct di_edid_display_descriptor *desc);
/**
* Get a list of EDID extensions.
*
* The returned array is NULL-terminated.
*/
const struct di_edid_ext *const *
di_edid_get_extensions(const struct di_edid *edid);
/**
* EDID extension block tags, defined in section 2.2.4.
*/
enum di_edid_ext_tag {
/* CEA 861 Series Extension */
DI_EDID_EXT_CEA = 0x02,
/* Video Timing Block Extension */
DI_EDID_EXT_VTB = 0x10,
/* Display Information Extension */
DI_EDID_EXT_DI = 0x40,
/* Localized String Extension */
DI_EDID_EXT_LS = 0x50,
/* Digital Packet Video Link Extension */
DI_EDID_EXT_DPVL = 0x60,
/* Extension Block Map */
DI_EDID_EXT_BLOCK_MAP = 0xF0,
/* Extension defined by the display manufacturer */
DI_EDID_EXT_VENDOR = 0xFF,
/* DisplayID Extension */
DI_EDID_EXT_DISPLAYID = 0x70,
};
/**
* EDID extension block.
*/
struct di_edid_ext;
/**
* Get the tag of an EDID extension block.
*/
enum di_edid_ext_tag
di_edid_ext_get_tag(const struct di_edid_ext *ext);
/* See <libdisplay-info/cta.h> */
struct di_edid_cta;
/**
* Get a CTA-861 extension block.
*
* Returns NULL if the extension block tag is not DI_EDID_EXT_CEA.
*/
const struct di_edid_cta *
di_edid_ext_get_cta(const struct di_edid_ext *ext);
/* See <libdisplay-info/displayid.h> */
struct di_displayid;
/**
* Get a DisplayID extension block.
*
* Returns NULL if the extension block tag is not DI_EDID_EXT_DISPLAYID or if
* the block does not contain a version 1 DisplayID blob.
*/
const struct di_displayid *
di_edid_ext_get_displayid(const struct di_edid_ext *ext);
/* See <libdisplay-info/displayid2.h> */
struct di_displayid2;
/**
* Get a DisplayID v2 extension block.
*
* Returns NULL if the extension block tag is not DI_EDID_EXT_DISPLAYID or if
* the block does not contain a version 2 DisplayID blob.
*/
const struct di_displayid2 *
di_edid_ext_get_displayid2(const struct di_edid_ext *ext);
#endif
@@ -0,0 +1,72 @@
#ifndef DI_GTF_H
#define DI_GTF_H
#include <stdbool.h>
/**
* Low-level API for Generalized Timing Formula Standard version 1.1.
*/
/**
* Type of frequency parameter used in di_gtf_options.ip_freq_rqd.
*/
enum di_gtf_ip_param {
/* Vertical frame frequency (Hz) */
DI_GTF_IP_PARAM_V_FRAME_RATE,
/* Horizontal frequency (kHz) */
DI_GTF_IP_PARAM_H_FREQ,
/* Pixel clock rate (MHz) */
DI_GTF_IP_PARAM_H_PIXELS,
};
/**
* Input options for GTF.
*/
struct di_gtf_options {
/* Number of active image pixels displayed on a line, not including any
* margin */
int h_pixels;
/* Number of vertical lines in the displayed image */
int v_lines;
/* Whether margins are required */
bool margins_rqd;
/* Indicates which frequency parameter is specified in ip_freq_rqd */
enum di_gtf_ip_param ip_param;
/* Vertical frame frequency (in Hz), horizontal frequency (in kHz) or
* pixel clock rate (in MHz) */
double ip_freq_rqd;
/* Whether interlaced is required */
bool int_rqd;
/* Blanking formula gradient */
double m;
/* Blanking formula offset */
double c;
/* Blanking formula scaling factor */
double k;
/* Blanking formula scaling factor weighting */
double j;
};
#define DI_GTF_DEFAULT_M 600.0
#define DI_GTF_DEFAULT_C 40.0
#define DI_GTF_DEFAULT_K 128.0
#define DI_GTF_DEFAULT_J 20.0
/**
* Output timing data for GTF.
*/
struct di_gtf_timing {
int h_pixels, v_lines;
int h_sync, v_sync;
int h_front_porch, h_back_porch;
int v_front_porch, v_back_porch;
int h_border, v_border;
double pixel_freq_mhz; /* in mega-hertz */
};
/**
* Compute a timing via the GTF formula.
*/
void di_gtf_compute(struct di_gtf_timing *t, const struct di_gtf_options *options);
#endif
@@ -0,0 +1,54 @@
#ifndef DI_HDMI_VIC_H
#define DI_HDMI_VIC_H
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
/**
* Low-level API for HDMI Video Identification Codes (HDMI VIC).
*/
/**
* A HDMI Video Identification Code (VIC).
*/
struct di_hdmi_vic {
/* Video Identification Code (VIC) */
uint8_t code;
};
/**
* A HDMI video format, not to be confused with a CTA-861 video format.
*/
struct di_hdmi_vic_video_format {
/* Video Identification Code (VIC) */
struct di_hdmi_vic vic;
/* Horizontal/vertical active pixels/lines */
int32_t h_active, v_active;
/* Horizontal/vertical front porch */
int32_t h_front, v_front;
/* Horizontal/vertical sync pulse */
int32_t h_sync, v_sync;
/* Horizontal/vertical back porch */
int32_t h_back, v_back;
/* Pixel clock in Hz */
int64_t pixel_clock_hz;
};
/**
* Get a HDMI video format from a HDMI VIC.
*
* Returns NULL if the HDMI VIC is unknown.
*/
const struct di_hdmi_vic_video_format *
di_hdmi_vic_video_format_from_vic(struct di_hdmi_vic vic);
/**
* Get a HDMI VIC from a HDMI video format.
*
* Returns 0 if the video format is unknown.
*/
struct di_hdmi_vic
di_hdmi_vic_video_format_to_vic(const struct di_hdmi_vic_video_format *format);
#endif
@@ -1,108 +1,247 @@
#pragma once
#ifndef DI_INFO_H
#define DI_INFO_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* libdisplay-info's high-level API.
*/
/**
* Information about a display device.
*
* This includes at least one EDID or DisplayID blob.
*
* Use di_info_parse_edid() to create a struct di_info from an EDID blob.
* DisplayID blobs are not yet supported.
*/
struct di_info;
struct di_edid;
struct di_edid_ext;
struct di_edid_cta;
struct di_displayid;
struct di_cta_data_block;
struct di_displayid_data_block;
struct di_edid_vendor_product {
char manufacturer[4];
int product;
int serial;
int manufacture_week;
int manufacture_year;
int model_year;
/**
* Parse an EDID blob.
*
* Callers do not need to keep the provided data pointer valid after calling
* this function. Callers should destroy the returned pointer via
* di_info_destroy().
*/
struct di_info *
di_info_parse_edid(const void *data, size_t size);
/**
* Destroy a display device information structure.
*/
void
di_info_destroy(struct di_info *info);
/**
* Returns the EDID the display device information was constructed with.
*
* The returned struct di_edid can be used to query low-level EDID information,
* see <libdisplay-info/edid.h>. Users should prefer the high-level API if
* possible.
*
* NULL is returned if the struct di_info doesn't contain an EDID. The returned
* struct di_edid is valid until di_info_destroy().
*/
const struct di_edid *
di_info_get_edid(const struct di_info *info);
/**
* Get the failure messages for this blob.
*
* NULL is returned if the blob conforms to the relevant specifications.
*/
const char *
di_info_get_failure_msg(const struct di_info *info);
/**
* Get the make of the display device.
*
* This is the manufacturer name, either company name or PNP ID.
* This string is informational and not meant to be used in programmatic
* decisions, configuration keys, etc.
*
* The string is in UTF-8 and may contain any characters except ASCII control
* codes.
*
* The caller is responsible for free'ing the returned string.
* NULL is returned if the information is not available.
*/
char *
di_info_get_make(const struct di_info *info);
/**
* Get the model of the display device.
*
* This is the product name/model string or product number.
* This string is informational and not meant to be used in programmatic
* decisions, configuration keys, etc.
*
* The string is in UTF-8 and may contain any characters except ASCII control
* codes.
*
* The caller is responsible for free'ing the returned string.
* NULL is returned if the information is not available.
*/
char *
di_info_get_model(const struct di_info *info);
/**
* Get the serial of the display device.
*
* This is the product serial string or the serial number.
* This string is informational and not meant to be used in programmatic
* decisions, configuration keys, etc.
*
* The string is in UTF-8 and may contain any characters except ASCII control
* codes.
*
* The caller is responsible for free'ing the returned string.
* NULL is returned if the information is not available.
*/
char *
di_info_get_serial(const struct di_info *info);
/**
* Display HDR static metadata
*/
struct di_hdr_static_metadata {
/* Desired content max luminance (cd/m²), zero if unset */
float desired_content_max_luminance;
/* Desired content max frame-average luminance (cd/m²), zero if unset */
float desired_content_max_frame_avg_luminance;
/* Desired content min luminance (cd/m²), zero if unset */
float desired_content_min_luminance;
/* Supported metadata types: */
/* Static Metadata Type 1 */
bool type1;
/* Supported EOFTs: */
/* Traditional gamma - SDR luminance range */
bool traditional_sdr;
/* Traditional gamma - HDR luminance range */
bool traditional_hdr;
/* Perceptual Quantization (PQ) based on SMPTE ST 2084 */
bool pq;
/* Hybrid Log-Gamma (HLG) based on Rec. ITU-R BT.2100 */
bool hlg;
};
struct di_edid_chromaticity_coords {
float red_x;
float red_y;
float green_x;
float green_y;
float blue_x;
float blue_y;
float white_x;
float white_y;
/**
* Get HDR static metadata support information as defined in ANSI/CTA-861-H
* as HDR Static Metadata Data Block.
*
* The returned pointer is owned by the struct di_info passed in. It remains
* valid only as long as the di_info exists, and must not be freed by the
* caller.
*
* This function does not return NULL. When HDR static metadata does not exist,
* all luminance fields are zero and only traditional_sdr is flagged as
* supported.
*/
const struct di_hdr_static_metadata *
di_info_get_hdr_static_metadata(const struct di_info *info);
/** CIE 1931 2-degree observer chromaticity coordinates */
struct di_chromaticity_cie1931 {
float x;
float y;
};
struct di_cta_hdr_static_metadata_eotfs {
bool pq;
/** Display color primaries and default white point */
struct di_color_primaries {
/* Are primary[] given */
bool has_primaries;
/* Is default_white given */
bool has_default_white_point;
/* Chromaticities of the primaries in RGB order
*
* Either all values are given, or they are all zeroes.
*/
struct di_chromaticity_cie1931 primary[3];
/* Default white point chromaticity
*
* If non-zero, this should be the display white point when it has
* been reset to its factory defaults. Either both x and y are given,
* or they are both zero.
*/
struct di_chromaticity_cie1931 default_white;
};
struct di_cta_hdr_static_metadata_block {
int desired_content_min_luminance;
int desired_content_max_luminance;
int desired_content_max_frame_avg_luminance;
const struct di_cta_hdr_static_metadata_eotfs *eotfs;
/**
* Get display color primaries and default white point
*
* Get the parameters of the default RGB colorimetry mode which is always
* supported. Primaries for monochrome displays might be all zeroes.
*
* These primaries might not be display's physical primaries, but only the
* primaries of the default RGB colorimetry signal when using IT Video Format
* (ANSI/CTA-861-H, Section 5).
*
* The returned pointer is owned by the struct di_info passed in. It remains
* valid only as long as the di_info exists, and must not be freed by the
* caller.
*
* This function does not return NULL.
*/
const struct di_color_primaries *
di_info_get_default_color_primaries(const struct di_info *info);
/** Additional signal colorimetry encodings supported by the display */
struct di_supported_signal_colorimetry {
/* Rec. ITU-R BT.2020 constant luminance YCbCr */
bool bt2020_cycc;
/* Rec. ITU-R BT.2020 non-constant luminance YCbCr */
bool bt2020_ycc;
/* Rec. ITU-R BT.2020 RGB */
bool bt2020_rgb;
/* SMPTE ST 2113 RGB: P3D65 and P3DCI */
bool st2113_rgb;
/* Rec. ITU-R BT.2100 ICtCp HDR (with PQ and/or HLG) */
bool ictcp;
};
struct di_cta_colorimetry_block {
bool bt2020_rgb;
};
/**
* Get signal colorimetry encodings supported by the display
*
* These signal colorimetry encodings are supported in addition to the
* display's default RGB colorimetry. When you wish to use one of the additional
* encodings, they need to be explicitly enabled in the video signal. How to
* do that is specific to the signalling used, e.g. HDMI.
*
* Signal colorimetry encoding provides the color space that the signal is
* encoded for. This includes primary and white point chromaticities, and the
* YCbCr-RGB conversion if necessary. Also the transfer function is implied
* unless explicitly set otherwise, e.g. with HDR static metadata.
* See ANSI/CTA-861-H for details.
*
* The signal color volume can be considerably larger than the physically
* displayable color volume.
*
* The returned pointer is owned by the struct di_info passed in. It remains
* valid only as long as the di_info exists, and must not be freed by the
* caller.
*
* This function does not return NULL.
*/
const struct di_supported_signal_colorimetry *
di_info_get_supported_signal_colorimetry(const struct di_info *info);
struct di_displayid_display_params {
int horiz_pixels;
int vert_pixels;
};
/**
* Get display default transfer characteristic exponent (gamma)
*
* This should be the display gamma value when the display has been reset to
* its factory defaults, and it is driven with the default RGB colorimetry.
* The value is zero when unknown.
*/
float
di_info_get_default_gamma(const struct di_info *info);
struct di_displayid_type_i_ii_vii_timing {
bool preferred;
int horiz_active;
int vert_active;
};
struct di_edid_misc_features {
bool preferred_timing_is_native;
};
struct di_edid_detailed_timing_def {
int horiz_image_mm;
int vert_image_mm;
int horiz_video;
int vert_video;
};
struct di_edid_screen_size {
int width_cm;
int height_cm;
};
const struct di_info *di_info_parse_edid(const void *data, size_t size);
void di_info_destroy(const struct di_info *info);
const struct di_edid *di_info_get_edid(const struct di_info *info);
char *di_info_get_model(const struct di_info *info);
char *di_info_get_serial(const struct di_info *info);
const struct di_edid_detailed_timing_def *const *di_edid_get_detailed_timing_defs(const struct di_edid *edid);
const struct di_edid_screen_size *di_edid_get_screen_size(const struct di_edid *edid);
const struct di_edid_vendor_product *di_edid_get_vendor_product(const struct di_edid *edid);
const struct di_edid_chromaticity_coords *di_edid_get_chromaticity_coords(const struct di_edid *edid);
const struct di_edid_ext *const *di_edid_get_extensions(const struct di_edid *edid);
const struct di_edid_misc_features *di_edid_get_misc_features(const struct di_edid *edid);
const struct di_edid_cta *di_edid_ext_get_cta(const struct di_edid_ext *ext);
const struct di_displayid *di_edid_ext_get_displayid(const struct di_edid_ext *ext);
const struct di_cta_data_block *const *di_edid_cta_get_data_blocks(const struct di_edid_cta *cta);
const struct di_cta_hdr_static_metadata_block *di_cta_data_block_get_hdr_static_metadata(const struct di_cta_data_block *block);
const struct di_cta_colorimetry_block *di_cta_data_block_get_colorimetry(const struct di_cta_data_block *block);
const struct di_displayid_data_block *const *di_displayid_get_data_blocks(const struct di_displayid *displayid);
const struct di_displayid_display_params *di_displayid_data_block_get_display_params(const struct di_displayid_data_block *block);
const struct di_displayid_type_i_ii_vii_timing *const *di_displayid_data_block_get_type_i_timings(const struct di_displayid_data_block *block);
const struct di_displayid_type_i_ii_vii_timing *const *di_displayid_data_block_get_type_ii_timings(const struct di_displayid_data_block *block);
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,24 @@
#ifndef LOG_H
#define LOG_H
/**
* Private logging utilities.
*/
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
struct di_logger {
FILE *f;
const char *section;
bool initialized;
};
void
_di_logger_va_add_failure(struct di_logger *logger, const char fmt[], va_list args);
void
_di_logger_add_failure(struct di_logger *logger, const char fmt[], ...);
#endif
@@ -0,0 +1,32 @@
#ifndef MEMORY_STREAM_H
#define MEMORY_STREAM_H
/**
* Utility functions for memory streams.
*/
#include <stdio.h>
#include <stddef.h>
#include <stdbool.h>
struct memory_stream {
FILE *fp;
char *str;
size_t str_len;
};
bool
memory_stream_open(struct memory_stream *m);
char *
memory_stream_close(struct memory_stream *m);
/**
* A small cleanup helper that simply
* calls free(memory_stream_close(m)) to avoid
* any dangling string pointers in cleanup/error paths.
*/
void
memory_stream_cleanup(struct memory_stream *m);
#endif
@@ -0,0 +1,483 @@
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "edid.h"
#include "info.h"
#include "memory-stream.h"
/* Generated file pnp-id-table.c: */
const char *
pnp_id_table(const char *key);
static bool
cta_data_block_allowed_multiple(enum di_cta_data_block_tag tag)
{
/* See CTA-861-H, 7.6 Multiple Instances of Data Blocks. */
switch (tag) {
case DI_CTA_DATA_BLOCK_SPEAKER_ALLOC:
case DI_CTA_DATA_BLOCK_VESA_DISPLAY_TRANSFER_CHARACTERISTIC:
case DI_CTA_DATA_BLOCK_VIDEO_CAP:
case DI_CTA_DATA_BLOCK_VESA_DISPLAY_DEVICE:
case DI_CTA_DATA_BLOCK_COLORIMETRY:
case DI_CTA_DATA_BLOCK_HDR_STATIC_METADATA:
case DI_CTA_DATA_BLOCK_VIDEO_FORMAT_PREF:
case DI_CTA_DATA_BLOCK_YCBCR420_CAP_MAP:
case DI_CTA_DATA_BLOCK_HDMI_AUDIO:
case DI_CTA_DATA_BLOCK_ROOM_CONFIG:
case DI_CTA_DATA_BLOCK_HDMI_EDID_EXT_OVERRIDE:
case DI_CTA_DATA_BLOCK_HDMI_SINK_CAP:
return false;
default:
return true;
}
}
static const struct di_cta_data_block *
find_cta_data_block(const struct di_cta_data_block *const *blocks, enum di_cta_data_block_tag tag)
{
size_t i;
for (i = 0; blocks[i] != NULL; i++) {
if (di_cta_data_block_get_tag(blocks[i]) == tag)
return blocks[i];
}
return NULL;
}
static const struct di_cta_data_block *
displayid2_get_cta_data_block(const struct di_displayid2 *displayid2, enum di_cta_data_block_tag tag)
{
size_t i;
const struct di_displayid2_data_block *const *data_blocks;
const struct di_cta_data_block *const *cta_data_blocks, *match;
data_blocks = di_displayid2_get_data_blocks(displayid2);
for (i = 0; data_blocks[i] != NULL; i++) {
cta_data_blocks = di_displayid2_data_block_get_cta_data_blocks(data_blocks[i]);
if (cta_data_blocks == NULL) {
continue;
}
match = find_cta_data_block(cta_data_blocks, tag);
if (match != NULL) {
return match;
}
}
return NULL;
}
static const struct di_cta_data_block *
edid_get_cta_data_block(const struct di_edid *edid, enum di_cta_data_block_tag tag)
{
const struct di_edid_ext *const *ext;
/*
* Here we do not handle blocks that are allowed to occur in
* multiple instances.
*/
assert(!cta_data_block_allowed_multiple(tag));
for (ext = di_edid_get_extensions(edid); *ext; ext++) {
const struct di_edid_cta *cta;
const struct di_displayid2 *displayid2;
const struct di_cta_data_block *match;
match = NULL;
switch (di_edid_ext_get_tag(*ext)) {
case DI_EDID_EXT_CEA:
cta = di_edid_ext_get_cta(*ext);
match = find_cta_data_block(di_edid_cta_get_data_blocks(cta), tag);
break;
case DI_EDID_EXT_DISPLAYID:
displayid2 = di_edid_ext_get_displayid2(*ext);
if (displayid2 == NULL) {
break;
}
match = displayid2_get_cta_data_block(displayid2, tag);
break;
default:
break; /* Ignore */
}
if (match != NULL) {
return match;
}
}
return NULL;
}
static void
derive_edid_hdr_static_metadata(const struct di_edid *edid,
struct di_hdr_static_metadata *hsm)
{
const struct di_cta_data_block *block;
const struct di_cta_hdr_static_metadata_block *cta_hsm;
/* By default, everything unset and only traditional gamma supported. */
hsm->traditional_sdr = true;
block = edid_get_cta_data_block(edid, DI_CTA_DATA_BLOCK_HDR_STATIC_METADATA);
if (!block)
return;
cta_hsm = di_cta_data_block_get_hdr_static_metadata(block);
assert(cta_hsm);
hsm->desired_content_max_luminance = cta_hsm->desired_content_max_luminance;
hsm->desired_content_max_frame_avg_luminance = cta_hsm->desired_content_max_frame_avg_luminance;
hsm->desired_content_min_luminance = cta_hsm->desired_content_min_luminance;
hsm->type1 = cta_hsm->descriptors->type1;
hsm->traditional_sdr = cta_hsm->eotfs->traditional_sdr;
hsm->traditional_hdr = cta_hsm->eotfs->traditional_hdr;
hsm->pq = cta_hsm->eotfs->pq;
hsm->hlg = cta_hsm->eotfs->hlg;
}
static void
derive_edid_color_primaries(const struct di_edid *edid,
struct di_color_primaries *cc)
{
const struct di_edid_chromaticity_coords *cm;
const struct di_edid_misc_features *misc;
/* Trust the flag more than the fields. */
misc = di_edid_get_misc_features(edid);
if (misc->srgb_is_primary) {
/*
* https://www.w3.org/Graphics/Color/sRGB.html
* for lack of access to IEC 61966-2-1
*/
cc->primary[0].x = 0.640f; /* red */
cc->primary[0].y = 0.330f;
cc->primary[1].x = 0.300f; /* green */
cc->primary[1].y = 0.600f;
cc->primary[2].x = 0.150f; /* blue */
cc->primary[2].y = 0.060f;
cc->has_primaries = true;
cc->default_white.x = 0.3127f; /* D65 */
cc->default_white.y = 0.3290f;
cc->has_default_white_point = true;
return;
}
cm = di_edid_get_chromaticity_coords(edid);
/*
* Broken EDID might have only partial values.
* Require all values to report anything.
*/
if (cm->red_x > 0.0f &&
cm->red_y > 0.0f &&
cm->green_x > 0.0f &&
cm->green_y > 0.0f &&
cm->blue_x > 0.0f &&
cm->blue_y > 0.0f) {
cc->primary[0].x = cm->red_x;
cc->primary[0].y = cm->red_y;
cc->primary[1].x = cm->green_x;
cc->primary[1].y = cm->green_y;
cc->primary[2].x = cm->blue_x;
cc->primary[2].y = cm->blue_y;
cc->has_primaries = true;
}
if (cm->white_x > 0.0f && cm->white_y > 0.0f) {
cc->default_white.x = cm->white_x;
cc->default_white.y = cm->white_y;
cc->has_default_white_point = true;
}
}
static void
derive_edid_supported_signal_colorimetry(const struct di_edid *edid,
struct di_supported_signal_colorimetry *ssc)
{
const struct di_cta_data_block *block;
const struct di_cta_colorimetry_block *cm;
/* Defaults to all unsupported. */
block = edid_get_cta_data_block(edid, DI_CTA_DATA_BLOCK_COLORIMETRY);
if (!block)
return;
cm = di_cta_data_block_get_colorimetry(block);
assert(cm);
ssc->bt2020_cycc = cm->bt2020_cycc;
ssc->bt2020_ycc = cm->bt2020_ycc;
ssc->bt2020_rgb = cm->bt2020_rgb;
ssc->st2113_rgb = cm->st2113_rgb;
ssc->ictcp = cm->ictcp;
}
struct di_info *
di_info_parse_edid(const void *data, size_t size)
{
struct memory_stream failure_msg;
struct di_edid *edid;
struct di_info *info;
char *failure_msg_str = NULL;
if (!memory_stream_open(&failure_msg))
return NULL;
edid = _di_edid_parse(data, size, failure_msg.fp);
if (!edid)
goto err_failure_msg_file;
info = calloc(1, sizeof(*info));
if (!info)
goto err_edid;
info->edid = edid;
failure_msg_str = memory_stream_close(&failure_msg);
if (failure_msg_str && failure_msg_str[0] != '\0')
info->failure_msg = failure_msg_str;
else
free(failure_msg_str);
derive_edid_hdr_static_metadata(info->edid, &info->derived.hdr_static_metadata);
derive_edid_color_primaries(info->edid, &info->derived.color_primaries);
derive_edid_supported_signal_colorimetry(info->edid, &info->derived.supported_signal_colorimetry);
return info;
err_edid:
_di_edid_destroy(edid);
err_failure_msg_file:
memory_stream_cleanup(&failure_msg);
return NULL;
}
void
di_info_destroy(struct di_info *info)
{
_di_edid_destroy(info->edid);
free(info->failure_msg);
free(info);
}
const struct di_edid *
di_info_get_edid(const struct di_info *info)
{
return info->edid;
}
const char *
di_info_get_failure_msg(const struct di_info *info)
{
return info->failure_msg;
}
static void
encode_ascii_byte(FILE *out, char ch)
{
uint8_t c = (uint8_t)ch;
/*
* Replace ASCII control codes and non-7-bit codes
* with an escape string. The result is guaranteed to be valid
* UTF-8.
*/
if (c < 0x20 || c >= 0x7f)
fprintf(out, "\\x%02x", c);
else
fputc(c, out);
}
static void
encode_ascii_string(FILE *out, const char *str)
{
size_t len = strlen(str);
size_t i;
for (i = 0; i < len; i++)
encode_ascii_byte(out, str[i]);
}
char *
di_info_get_make(const struct di_info *info)
{
const struct di_edid_vendor_product *evp;
char pnp_id[(sizeof(evp->manufacturer)) + 1] = { 0, };
const char *manuf;
struct memory_stream m;
if (!info->edid)
return NULL;
if (!memory_stream_open(&m))
return NULL;
evp = di_edid_get_vendor_product(info->edid);
memcpy(pnp_id, evp->manufacturer, sizeof(evp->manufacturer));
manuf = pnp_id_table(pnp_id);
if (manuf) {
encode_ascii_string(m.fp, manuf);
return memory_stream_close(&m);
}
fputs("PNP(", m.fp);
encode_ascii_string(m.fp, pnp_id);
fputs(")", m.fp);
return memory_stream_close(&m);
}
char *
di_info_get_model(const struct di_info *info)
{
const struct di_edid_vendor_product *evp;
const struct di_edid_display_descriptor *const *desc;
struct memory_stream m;
size_t i;
enum di_edid_display_descriptor_tag tag;
const char *str;
if (!info->edid)
return NULL;
if (!memory_stream_open(&m))
return NULL;
desc = di_edid_get_display_descriptors(info->edid);
for (i = 0; desc[i]; i++) {
tag = di_edid_display_descriptor_get_tag(desc[i]);
if (tag != DI_EDID_DISPLAY_DESCRIPTOR_PRODUCT_NAME)
continue;
str = di_edid_display_descriptor_get_string(desc[i]);
if (str[0] == '\0')
continue;
encode_ascii_string(m.fp, str);
return memory_stream_close(&m);
}
evp = di_edid_get_vendor_product(info->edid);
fprintf(m.fp, "0x%04" PRIX16, evp->product);
return memory_stream_close(&m);
}
char *
di_info_get_serial(const struct di_info *info)
{
const struct di_edid_display_descriptor *const *desc;
const struct di_edid_vendor_product *evp;
struct memory_stream m;
size_t i;
enum di_edid_display_descriptor_tag tag;
const char *str;
if (!info->edid)
return NULL;
if (!memory_stream_open(&m))
return NULL;
desc = di_edid_get_display_descriptors(info->edid);
for (i = 0; desc[i]; i++) {
tag = di_edid_display_descriptor_get_tag(desc[i]);
if (tag != DI_EDID_DISPLAY_DESCRIPTOR_PRODUCT_SERIAL)
continue;
str = di_edid_display_descriptor_get_string(desc[i]);
if (str[0] == '\0')
continue;
encode_ascii_string(m.fp, str);
return memory_stream_close(&m);
}
evp = di_edid_get_vendor_product(info->edid);
if (evp->serial != 0) {
fprintf(m.fp, "0x%08" PRIX32, evp->serial);
return memory_stream_close(&m);
}
memory_stream_cleanup(&m);
return NULL;
}
const struct di_hdr_static_metadata *
di_info_get_hdr_static_metadata(const struct di_info *info)
{
return &info->derived.hdr_static_metadata;
}
const struct di_color_primaries *
di_info_get_default_color_primaries(const struct di_info *info)
{
return &info->derived.color_primaries;
}
const struct di_supported_signal_colorimetry *
di_info_get_supported_signal_colorimetry(const struct di_info *info)
{
return &info->derived.supported_signal_colorimetry;
}
static const struct di_displayid *
edid_get_displayid(const struct di_edid *edid)
{
const struct di_edid_ext *const *ext;
for (ext = di_edid_get_extensions(edid); *ext; ext++) {
enum di_edid_ext_tag tag = di_edid_ext_get_tag(*ext);
if (tag == DI_EDID_EXT_DISPLAYID)
return di_edid_ext_get_displayid(*ext);
}
return NULL;
}
static const struct di_displayid_display_params *
displayid_get_display_params(const struct di_displayid *did)
{
const struct di_displayid_data_block *const *block =
di_displayid_get_data_blocks(did);
for (; *block; block++) {
enum di_displayid_data_block_tag tag = di_displayid_data_block_get_tag(*block);
if (tag == DI_DISPLAYID_DATA_BLOCK_DISPLAY_PARAMS)
return di_displayid_data_block_get_display_params(*block);
}
return NULL;
}
float
di_info_get_default_gamma(const struct di_info *info)
{
const struct di_edid *edid;
const struct di_displayid *did;
const struct di_edid_misc_features *misc;
edid = di_info_get_edid(info);
if (!edid)
return 0.0f;
did = edid_get_displayid(edid);
if (did) {
const struct di_displayid_display_params *did_params;
did_params = displayid_get_display_params(did);
if (did_params)
return did_params->gamma;
}
/* Trust the flag more than the gamma field value. */
misc = di_edid_get_misc_features(edid);
if (misc->srgb_is_primary)
return 2.2f;
return di_edid_get_basic_gamma(edid);
}
@@ -0,0 +1,6 @@
{
global:
di_*;
local:
*;
};
@@ -0,0 +1,33 @@
#include "log.h"
void
_di_logger_va_add_failure(struct di_logger *logger, const char fmt[], va_list args)
{
if (!logger->initialized) {
if (ftell(logger->f) > 0) {
fprintf(logger->f, "\n");
}
fprintf(logger->f, "%s:\n", logger->section);
logger->initialized = true;
}
fprintf(logger->f, " ");
vfprintf(logger->f, fmt, args);
fprintf(logger->f, "\n");
}
/**
* Sometimes calling the functions that wrap _di_logger_va_add_failure() (e.g.
* add_failure() in edid.c) is not possible, because we want to use a specific
* logger (and not e.g. edid->logger). This allow us to log with a custom
* logger. Avoid using this if not necessary.
*/
void
_di_logger_add_failure(struct di_logger *logger, const char fmt[], ...)
{
va_list args;
va_start(args, fmt);
_di_logger_va_add_failure(logger, fmt, args);
va_end(args);
}
@@ -0,0 +1,36 @@
#include <stdlib.h>
#include "memory-stream.h"
bool
memory_stream_open(struct memory_stream *m)
{
*m = (struct memory_stream){ 0 };
m->fp = open_memstream(&m->str, &m->str_len);
return m->fp != NULL;
}
char *
memory_stream_close(struct memory_stream *m)
{
char *str;
int ret;
ret = fclose(m->fp);
str = m->str;
*m = (struct memory_stream){ 0 };
if (ret != 0) {
free(str);
str = NULL;
}
return str;
}
void
memory_stream_cleanup(struct memory_stream *m)
{
free(memory_stream_close(m));
}
@@ -1,29 +1,109 @@
project('libdisplay-info', 'c',
version: '0.2.3',
meson_version: '>= 0.54.0',
default_options: ['warning_level=1', 'buildtype=debugoptimized'])
inc = include_directories('include')
libdisplay_info = shared_library('display-info',
'di.c',
include_directories: inc,
version: meson.project_version(),
install: true)
install_headers(
'include/libdisplay-info/cta.h',
'include/libdisplay-info/displayid.h',
'include/libdisplay-info/edid.h',
'include/libdisplay-info/info.h',
subdir: 'libdisplay-info',
project(
'libdisplay-info',
'c',
version: '0.4.0',
license: 'MIT',
meson_version: '>= 0.57.0',
default_options: [
'c_std=c11',
'warning_level=3',
'wrap_mode=nodownload',
],
)
pkg = import('pkgconfig')
pkg.generate(
libdisplay_info,
name: 'libdisplay-info',
description: 'Display identification data parsing library',
filebase: 'libdisplay-info',
version: meson.project_version(),
version = meson.project_version().split('-')[0]
version_major = version.split('.')[0]
version_minor = version.split('.')[1]
assert(version_major == '0')
dep_hwdata = dependency('hwdata', required: false, native: true)
if dep_hwdata.found()
hwdata_dir = dep_hwdata.get_variable(pkgconfig: 'pkgdatadir')
pnp_ids = files(hwdata_dir / 'pnp.ids')
else
pnp_ids = files('/usr/share/hwdata/pnp.ids')
endif
gen_search_table = find_program('tool/gen-search-table.py')
pnp_id_table = custom_target(
'pnp-id-table.c',
command: [ gen_search_table, pnp_ids, '@OUTPUT@', 'pnp_id_table' ],
output: 'pnp-id-table.c',
)
cc = meson.get_compiler('c')
math = cc.find_library('m', required: false)
add_project_arguments(['-D_POSIX_C_SOURCE=200809L'], language: 'c')
add_project_arguments(cc.get_supported_arguments([
'-Wundef',
'-Wmissing-prototypes',
'-Walloca',
'-Wdeclaration-after-statement',
'-Wconversion',
'-Wno-unused-parameter',
'-Wno-missing-field-initializers',
'-Werror=implicit',
]), language: 'c')
symbols_file = 'libdisplay-info.map'
symbols_flag = '-Wl,--version-script,@0@'.format(meson.current_source_dir() / symbols_file)
di_lib = library(
'display-info',
[
'cta.c',
'cvt.c',
'displayid.c',
'displayid2.c',
'dmt.c',
'dmt-table.c',
'edid.c',
'gtf.c',
'info.c',
'log.c',
'memory-stream.c',
'cta-vic.c',
'cta-vic-table.c',
'hdmi-vic.c',
pnp_id_table,
],
include_directories: include_directories('include'),
dependencies: [math],
link_args: symbols_flag,
link_depends: symbols_file,
install: true,
version: version,
soversion: version_minor,
)
install_subdir(
'include/libdisplay-info',
install_dir: get_option('includedir'),
)
pkgconfig = import('pkgconfig')
pkgconfig.generate(
di_lib,
filebase: 'libdisplay-info',
name: 'libdisplay-info',
url: 'https://gitlab.freedesktop.org/emersion/libdisplay-info',
description: 'EDID and DisplayID library',
)
di_dep = declare_dependency(
link_with: di_lib,
include_directories: include_directories('include'),
)
meson.override_dependency('libdisplay-info', di_dep)
# Red Bear: the library is what the distribution consumes. di-edid-decode and
# test/ build extra host-side executables and a shell test harness that are not
# part of the runtime and do not cross-compile cleanly for Redox.
# subdir('di-edid-decode')
# subdir('test')
+36
View File
@@ -0,0 +1,36 @@
#!/bin/sh -eu
if ! lsmod | grep i2c_dev >/dev/null 2>&1; then
echo "kernel module i2c-dev must be loaded"
exit
fi
if ! command -v i2cdump >/dev/null 2>&1; then
echo "i2cdump from i2c-tools must be installed"
exit
fi
for connector in $(find /sys/class/drm/ -maxdepth 1 -name 'card[0-9]-*'); do
bus=$(find "$connector/" -maxdepth 1 -mindepth 1 -type d -name 'i2c-*' -print -quit | sed -n 's/.*\/i2c\-\(.*\)/\1/p')
if [ "$bus" = "" ]; then
bus=$(find "$connector/ddc/i2c-dev/" -maxdepth 1 -mindepth 1 -type d -name 'i2c-*' -print -quit | sed -n 's/.*\/i2c\-\(.*\)/\1/p')
fi
if [ "$bus" = "" ]; then
echo "Connector $(basename $connector) does not have an i2c bus. Skipping."
continue
fi
echo "Dumping connector $(basename $connector) i2c bus address 0x50"
i2cdump -y $bus 0x50 b
echo "Dumping connector $(basename $connector) i2c bus address 0x51"
i2cdump -y $bus 0x51 b
echo "Dumping connector $(basename $connector) i2c bus address 0x54"
i2cdump -y $bus 0x54 b
echo "Dumping connector $(basename $connector) i2c bus address 0x55"
i2cdump -y $bus 0x55 b
done
@@ -0,0 +1,113 @@
#!/usr/bin/env python3
import os
import sys
import pdfplumber
if len(sys.argv) != 2:
print("usage: gen-cta-vic.py <CTA-861-I PDF>", file=sys.stderr)
sys.exit(1)
in_path = sys.argv[1]
in_basename = os.path.basename(in_path)
tool_dir = os.path.dirname(os.path.realpath(__file__))
out_path = tool_dir + "/../cta-vic-table.c"
# Page numbers for CTA-861-I
pages = {
"timing": (42, 45),
"aspect_ratio": (54, 57),
}
def extract_rows(pdf, page_range):
(first, last) = page_range
rows = []
for page in pdf.pages:
if page.page_number < first or page.page_number > last:
continue
table = page.extract_table()
rows += table[1:] # strip header
return rows
def parse_vic_list(s):
return [int(vic.strip()) for vic in s.split(",")]
def parse_interlaced(s):
if s == "Prog":
return "false"
elif s == "Int":
return "true"
else:
assert(False)
def parse_polarity(pol):
if pol == "P":
return "POSITIVE"
elif pol == "N":
return "NEGATIVE"
else:
assert(False)
pages_list = []
for type, (first, last) in pages.items():
pages_list += list(range(first, last + 1))
with pdfplumber.open(in_path, pages=pages_list) as pdf:
timing_rows = extract_rows(pdf, pages["timing"])
aspect_ratio_rows = extract_rows(pdf, pages["aspect_ratio"])
format_table = {}
for row in timing_rows:
if not row[1]:
continue
for vic in parse_vic_list(row[1]):
# Note, some values have a footnote marker
format_table[vic] = {
"h_active": int(row[3].rstrip("b")),
"v_active": int(row[4]),
"interlaced": parse_interlaced(row[5]),
"pixel_clock_hz": int(float(row[20]) * 1000 * 1000),
"h_front": int(row[8]),
"h_sync": int(row[9]),
"h_back": int(row[10]),
"h_sync_polarity": "DI_CTA_VIC_VIDEO_FORMAT_SYNC_" + parse_polarity(row[11]),
"v_front": int(row[14]),
"v_sync": int(row[15]),
"v_back": int(row[16]),
"v_sync_polarity": "DI_CTA_VIC_VIDEO_FORMAT_SYNC_" + parse_polarity(row[17]),
}
for row in aspect_ratio_rows:
if len(row) == 15:
# last two pages are extracted differently by pdfplumber due to
# grayed-out rows ("Forbidden" and "Reserved for the Future")
row = [row[0], row[3], row[6], row[9], row[12]]
if not row[3]:
continue
vic = int(row[0])
fmt = format_table[vic]
# Note, some values have a footnote marker
pic_ar = row[3].rstrip("f").replace(":", "_")
fmt["picture_aspect_ratio"] = "DI_CTA_VIC_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_" + pic_ar
max_vic = 0
for vic in format_table:
if vic > max_vic:
max_vic = vic
# Sanity check
for vic, fmt in format_table.items():
assert("picture_aspect_ratio" in fmt)
with open(out_path, "w+") as f:
f.write("/* DO NOT EDIT! This file has been generated by gen-cta-vic.py from {}. */\n\n".format(in_basename))
f.write('#include <libdisplay-info/cta-vic.h>\n\n')
f.write("const struct di_cta_vic_video_format _di_cta_vic_video_formats[] = {\n")
for vic in format_table:
f.write("\t[{}] = {{\n".format(vic))
f.write("\t\t.vic = {{ .code = {} }},\n".format(vic))
for k, v in format_table[vic].items():
f.write("\t\t.{} = {},\n".format(k, v))
f.write("\t},\n")
f.write("};\n\n")
f.write("const size_t _di_cta_vic_video_formats_len = {};\n".format(max_vic + 1))
+149
View File
@@ -0,0 +1,149 @@
#!/usr/bin/env python3
import os
import subprocess
import sys
def parse_hex_byte(s):
assert(s.endswith("h"))
s = s[:-1]
assert(0 <= int(s, 16) <= 255)
return "0x" + s
def parse_hex_list(s):
if s == "n/a":
return None
if s.startswith("("):
assert(s.endswith(")h"))
s = s[1:-2]
l = [b.strip() for b in s.split(",")]
l = [b[:-1] if b.endswith("h") else b for b in l]
for b in l:
assert(0 <= int(b, 16) <= 255)
return "0x" + "".join(l)
def parse_pixel_param(l):
l = l[2].split(" ")
assert(len(l) >= 2 and l[1] == "Pixels")
return int(l[0])
def parse_line_param(l):
l = l[1].split(" ")
assert(len(l) >= 2 and l[1] == "lines")
return int(l[0])
def parse_timing(page):
lines = [l.strip() for l in page.splitlines()]
if len(lines) < 6:
return None
try:
i = lines.index("Detailed Timing Parameters")
except ValueError:
return None
raw_metadata = lines[1:i]
raw_params = lines[i+1:]
metadata = {}
for l in raw_metadata:
if not ": " in l:
continue
k, v = l.split(":", 1)
metadata[k.strip()] = v.strip()
assert("Resolution" in metadata and "EDID ID" in metadata)
if "Proposed" in metadata and not "Adopted" in metadata:
print("skipping proposed but not adopted timing: " + metadata["Resolution"],
file=sys.stderr)
return None
params = {}
for l in raw_params:
if not " = " in l:
continue
tokens = [l.strip() for l in l.split("=")]
params[tokens[0]] = tokens[1:]
assert("Timing Name" in params)
res = metadata["Resolution"]
size, rest = res.split(" at ", 1)
horiz_video, vert_video = size.split("x", 1)
refresh_rate_hz, rest = rest.split(" Hz ", 1)
reduced_blanking = "reduced blanking" in rest.lower()
horiz_video = int(horiz_video.strip())
vert_video = int(vert_video.strip())
refresh_rate_hz = float(refresh_rate_hz.strip())
ids = {}
for kv in metadata["EDID ID"].split(";"):
k, v = kv.split(":")
ids[k.strip()] = v.strip()
assert("DMT ID" in ids)
dmt_id = parse_hex_byte(ids["DMT ID"])
edid_std_id = parse_hex_list(ids["Std. 2 Byte Code"])
cvt_id = parse_hex_list(ids["CVT 3 Byte Code"])
pixel_clock_mhz = float(params["Pixel Clock"][0].split(";")[0])
horiz_blank = parse_pixel_param(params["Hor Blank Time"])
horiz_front_porch = parse_pixel_param(params["// H Front Porch"])
horiz_sync_pulse = parse_pixel_param(params["Hor Sync Time"])
horiz_border = parse_pixel_param(params["// H Right Border"])
assert(horiz_border == parse_pixel_param(params["// H Left Border"]))
vert_blank = parse_line_param(params["Ver Blank Time"])
vert_front_porch = parse_line_param(params["// V Front Porch"])
vert_sync_pulse = parse_line_param(params["Ver Sync Time"])
vert_border = parse_line_param(params["// V Bottom Border"])
assert(vert_border == parse_line_param(params["// V Top Border"]))
return {
"dmt_code": "{{ .code = {} }}".format(dmt_id),
"edid_std_id": 0 if edid_std_id is None else edid_std_id,
"cvt_id": 0 if cvt_id is None else cvt_id,
"horiz_video": horiz_video,
"vert_video": vert_video,
"refresh_rate_hz": refresh_rate_hz,
"pixel_clock_hz": int(pixel_clock_mhz * 1000 * 1000),
"horiz_blank": horiz_blank,
"horiz_front_porch": horiz_front_porch,
"horiz_sync_pulse": horiz_sync_pulse,
"horiz_border": horiz_border,
"vert_blank": vert_blank,
"vert_front_porch": vert_front_porch,
"vert_sync_pulse": vert_sync_pulse,
"vert_border": vert_border,
"reduced_blanking": "true" if reduced_blanking else "false",
}
if len(sys.argv) != 2:
print("usage: gen-dmt.py <DMT PDF>", file=sys.stderr)
sys.exit(1)
in_path = sys.argv[1]
in_basename = os.path.basename(in_path)
tool_dir = os.path.dirname(os.path.realpath(__file__))
out_path = tool_dir + "/../dmt-table.c"
cmd = ["pdftotext", "-nodiag", "-layout", in_path, "-"]
page = ""
timings = []
for l in subprocess.check_output(cmd, text=True):
if l.startswith("\f"):
t = parse_timing(page)
if t is not None:
timings.append(t)
page = l[1:]
else:
page += l
with open(out_path, "w+") as f:
f.write("/* DO NOT EDIT! This file has been generated by gen-dmt.py from {}. */\n\n".format(in_basename))
f.write('#include <libdisplay-info/dmt.h>\n\n')
f.write("const struct di_dmt_timing _di_dmt_timings[] = {\n")
for t in timings:
f.write("\t{\n")
for k, v in t.items():
f.write("\t\t.{} = {},\n".format(k, v))
f.write("\t},\n")
f.write("};\n\n")
f.write("const size_t _di_dmt_timings_len = {};\n".format(len(timings)))
@@ -0,0 +1,91 @@
#!/usr/bin/env python3
"""
string to string mapping table
License: MIT
Copyright (c) 2020 Simon Ser
Copyright 2022 Collabora, Ltd.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
import sys
def escape_for_c(s):
l = [c if c.isalnum() or c in ' .,' else '\\%03o' % ord(c) for c in s]
return ''.join(l)
if len(sys.argv) != 4:
print('usage: ' + sys.argv[0] + ' <infile> <outfile> <ident>', file=sys.stderr)
sys.exit(1)
infile = sys.argv[1]
outfile = sys.argv[2]
ident = sys.argv[3]
records = {}
with open(infile, 'r') as f:
for line in f:
[pnpid, name] = line.split(maxsplit=1)
if len(pnpid) != 3:
print("Warning: skipping invalid PNP ID %s" % (repr(pnpid)), file=sys.stderr)
continue
records[pnpid] = escape_for_c(name.strip())
with open(outfile, 'w') as f:
f.write(
f'''
#include <string.h>
#include <stdint.h>
const char *
{ident}(const char *key);
const char *
{ident}(const char *key)
{{
size_t len = strlen(key);
size_t i;
uint32_t u = 0;
if (len > 4)
return NULL;
for (i = 0; i < len; i++)
u = (u << 8) | (uint8_t)key[i];
switch (u) {{
''')
for id in sorted(records.keys()):
u = 0
for c in id:
u = (u << 8) | ord(c)
f.write(f' case {u}: return "{records[id]}";\n')
f.write(
f'''
default:
return NULL;
}}
}}
''')