LibEDID: Fix handling extension maps

We weren't properly iterating the extension blocks and thought we
encountered an unexpected extension map block, when we really should
have just skipped over it.
This commit is contained in:
Tom 2022-01-24 11:00:51 -07:00 committed by Linus Groh
parent 18fc54fc34
commit a821aa5f50
Notes: sideshowbarker 2024-07-17 20:17:50 +09:00
2 changed files with 164 additions and 17 deletions

View File

@ -366,6 +366,148 @@ TEST_CASE(edid2)
}
}
// This EDID has extension maps
static const u8 edid_extension_maps[] = {
0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x4d, 0x29, 0x48, 0x44,
0x01, 0x00, 0x00, 0x00, 0x0a, 0x0d, 0x01, 0x03, 0x80, 0x50, 0x2d, 0x78,
0x0a, 0x0d, 0xc9, 0xa0, 0x57, 0x47, 0x98, 0x27, 0x12, 0x48, 0x4c, 0x20,
0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x1d, 0x80, 0x18, 0x71, 0x1c,
0x16, 0x20, 0x58, 0x2c, 0x25, 0x00, 0x20, 0xc2, 0x31, 0x00, 0x00, 0x9e,
0x8c, 0x0a, 0xd0, 0x8a, 0x20, 0xe0, 0x2d, 0x10, 0x10, 0x3e, 0x96, 0x00,
0x13, 0x8e, 0x21, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x48,
0x44, 0x4d, 0x49, 0x20, 0x54, 0x56, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
0x00, 0x00, 0x00, 0xfd, 0x00, 0x3b, 0x3d, 0x0f, 0x2e, 0x08, 0x02, 0x00,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x03, 0xf1, 0xf0, 0x02, 0x02, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x0c, 0x02, 0x03, 0x1e, 0xf1, 0x4a, 0x85, 0x04, 0x10,
0x02, 0x01, 0x06, 0x14, 0x12, 0x16, 0x13, 0x23, 0x09, 0x07, 0x07, 0x83,
0x01, 0x00, 0x00, 0x66, 0x03, 0x0c, 0x00, 0x10, 0x00, 0x80, 0x01, 0x1d,
0x00, 0x72, 0x51, 0xd0, 0x1e, 0x20, 0x6e, 0x28, 0x55, 0x00, 0xc4, 0x8e,
0x21, 0x00, 0x00, 0x1e, 0xd6, 0x09, 0x80, 0xa0, 0x20, 0xe0, 0x2d, 0x10,
0x10, 0x60, 0x22, 0x00, 0x12, 0x8e, 0x21, 0x08, 0x08, 0x18, 0x8c, 0x0a,
0xd0, 0x90, 0x20, 0x40, 0x31, 0x20, 0x0c, 0x40, 0x55, 0x00, 0xc4, 0x8e,
0x21, 0x00, 0x00, 0x18, 0x01, 0x1d, 0x80, 0xd0, 0x72, 0x1c, 0x16, 0x20,
0x10, 0x2c, 0x25, 0x80, 0xc4, 0x8e, 0x21, 0x00, 0x00, 0x9e, 0x8c, 0x0a,
0xa0, 0x14, 0x51, 0xf0, 0x16, 0x00, 0x26, 0x7c, 0x43, 0x00, 0x13, 0x8e,
0x21, 0x00, 0x00, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf5,
0x02, 0x03, 0x04, 0xf1, 0xf3, 0x39, 0x80, 0x18, 0x71, 0x38, 0x2d, 0x40,
0x58, 0x2c, 0x45, 0x00, 0xc4, 0x8e, 0x21, 0x00, 0x00, 0x1e, 0x8c, 0x0a,
0xa0, 0x20, 0x51, 0x20, 0x18, 0x10, 0x18, 0x7e, 0x23, 0x00, 0xc4, 0x8e,
0x21, 0x00, 0x00, 0x98, 0x01, 0x1d, 0x00, 0xbc, 0x52, 0xd0, 0x1e, 0x20,
0xb8, 0x28, 0x55, 0x40, 0xc4, 0x8e, 0x21, 0x00, 0x00, 0x1e, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdf
};
TEST_CASE(edid_extension_maps)
{
auto edid_load_result = EDID::Parser::from_bytes({ edid_extension_maps, sizeof(edid_extension_maps) });
EXPECT(!edid_load_result.is_error());
auto edid = edid_load_result.release_value();
EXPECT(edid.legacy_manufacturer_id() == "SII");
{
static constexpr struct {
unsigned block_id;
unsigned width;
unsigned height;
unsigned refresh_rate;
} expected_detailed_timings[] = {
{ 0, 1920, 1080, 60 },
{ 0, 720, 480, 60 },
{ 2, 1280, 720, 60 },
{ 2, 640, 480, 60 },
{ 2, 720, 576, 50 },
{ 2, 1920, 1080, 50 },
{ 2, 1440, 480, 60 },
{ 3, 1920, 1080, 60 },
{ 3, 1440, 576, 50 },
{ 3, 1280, 720, 50 }
};
static constexpr size_t expected_detailed_timings_count = sizeof(expected_detailed_timings) / sizeof(expected_detailed_timings[0]);
size_t detailed_timings_found = 0;
auto result = edid.for_each_detailed_timing([&](auto& detailed_timing, unsigned block_id) {
EXPECT(detailed_timings_found < expected_detailed_timings_count);
auto& expected_timings = expected_detailed_timings[detailed_timings_found];
EXPECT(block_id == expected_timings.block_id);
EXPECT(detailed_timing.horizontal_addressable_pixels() == expected_timings.width);
EXPECT(detailed_timing.vertical_addressable_lines() == expected_timings.height);
EXPECT(detailed_timing.refresh_rate().lround() == expected_timings.refresh_rate);
detailed_timings_found++;
return IterationDecision::Continue;
});
EXPECT(!result.is_error());
EXPECT(result.value() == IterationDecision::Continue);
EXPECT(detailed_timings_found == expected_detailed_timings_count);
}
}
static const u8 edid_1_0[] = {
0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x34, 0x38, 0xc2, 0x0b,
0x7b, 0x00, 0x00, 0x00, 0x0f, 0x0a, 0x01, 0x00, 0x28, 0x20, 0x18, 0x32,
0xe8, 0x7e, 0x4e, 0x9e, 0x57, 0x45, 0x98, 0x24, 0x10, 0x47, 0x4f, 0xa4,
0x42, 0x01, 0x31, 0x59, 0x45, 0x59, 0x61, 0x59, 0x71, 0x4f, 0x81, 0x80,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xf9, 0x15, 0x20, 0xf8, 0x30, 0x58,
0x1f, 0x20, 0x20, 0x40, 0x13, 0x00, 0x40, 0xf0, 0x10, 0x00, 0x00, 0x1e,
0xa4, 0x1a, 0x20, 0x10, 0x31, 0x58, 0x24, 0x20, 0x2f, 0x55, 0x33, 0x00,
0x40, 0xf0, 0x10, 0x00, 0x00, 0x1e, 0x30, 0x2a, 0x00, 0x98, 0x51, 0x00,
0x2a, 0x40, 0x30, 0x70, 0x13, 0x00, 0x40, 0xf0, 0x10, 0x00, 0x00, 0x1e,
0xea, 0x24, 0x00, 0x60, 0x41, 0x00, 0x28, 0x30, 0x30, 0x60, 0x13, 0x00,
0x40, 0xf0, 0x10, 0x00, 0x00, 0x1e, 0x00, 0x72
};
TEST_CASE(edid_1_0)
{
auto edid_load_result = EDID::Parser::from_bytes({ edid_1_0, sizeof(edid_1_0) });
EXPECT(!edid_load_result.is_error());
auto edid = edid_load_result.release_value();
EXPECT(edid.legacy_manufacturer_id() == "MAX");
EXPECT(edid.serial_number() == 123);
{
static constexpr struct {
unsigned block_id;
unsigned width;
unsigned height;
unsigned refresh_rate;
} expected_detailed_timings[] = {
{ 0, 800, 600, 85 },
{ 0, 800, 600, 100 },
{ 0, 1280, 1024, 60 },
{ 0, 1024, 768, 85 }
};
static constexpr size_t expected_detailed_timings_count = sizeof(expected_detailed_timings) / sizeof(expected_detailed_timings[0]);
size_t detailed_timings_found = 0;
auto result = edid.for_each_detailed_timing([&](auto& detailed_timing, unsigned block_id) {
EXPECT(detailed_timings_found < expected_detailed_timings_count);
auto& expected_timings = expected_detailed_timings[detailed_timings_found];
EXPECT(block_id == expected_timings.block_id);
EXPECT(detailed_timing.horizontal_addressable_pixels() == expected_timings.width);
EXPECT(detailed_timing.vertical_addressable_lines() == expected_timings.height);
EXPECT(detailed_timing.refresh_rate().lround() == expected_timings.refresh_rate);
detailed_timings_found++;
return IterationDecision::Continue;
});
EXPECT(!result.is_error());
EXPECT(result.value() == IterationDecision::Continue);
EXPECT(detailed_timings_found == expected_detailed_timings_count);
}
}
TEST_CASE(dmt_find_std_id)
{
auto* dmt = EDID::DMT::find_timing_by_std_id(0xd1, 0xf);

View File

@ -141,7 +141,7 @@ struct [[gnu::packed]] EDID {
u8 checksum;
};
enum ExtensionBlockTag : u8 {
enum class ExtensionBlockTag : u8 {
CEA_861 = 0x2,
VideoTimingBlock = 0x10,
DisplayInformation = 0x40,
@ -220,8 +220,10 @@ public:
ErrorOr<IterationDecision> for_each_dtd(Function<IterationDecision(Parser::DetailedTiming const&)> callback) const
{
u8 dtd_start = m_edid.read_host(&m_block->cea861extension.dtd_start_offset);
if (dtd_start <= 4)
if (dtd_start < 4) {
// dtd_start == 4 means there are no data blocks, but there are still DTDs
return IterationDecision::Continue;
}
if (dtd_start > offsetof(Definitions::ExtensionBlock, checksum) - sizeof(Definitions::DetailedTiming))
return Error::from_string_literal("CEA 861 extension block has invalid DTD list"sv);
@ -488,35 +490,39 @@ ErrorOr<IterationDecision> Parser::for_each_extension_block(Function<IterationDe
if (sizeof(Definitions::EDID) + (size_t)raw_extension_block_count * sizeof(Definitions::ExtensionBlock) > m_bytes.size())
return Error::from_string_literal("Truncated EDID");
auto validate_block_checksum = [&](Definitions::ExtensionBlock const& extension_map) {
auto validate_block_checksum = [&](Definitions::ExtensionBlock const& block) {
u8 checksum = 0x0;
auto* bytes = (u8 const*)&extension_map;
for (size_t i = 0; i < sizeof(extension_map); i++)
auto* bytes = (u8 const*)&block;
for (size_t i = 0; i < sizeof(block); i++)
checksum += bytes[i];
return checksum == 0;
};
size_t offset = sizeof(Definitions::EDID);
auto* raw_extension_blocks = (Definitions::ExtensionBlock const*)(m_bytes.data() + offset);
auto* raw_extension_blocks = (Definitions::ExtensionBlock const*)(m_bytes.data() + sizeof(Definitions::EDID));
Definitions::ExtensionBlock const* current_extension_map = nullptr;
unsigned raw_index = 0;
if (m_revision <= 3) {
if (raw_extension_block_count > 1) {
current_extension_map = &raw_extension_blocks[0];
if (read_host(&current_extension_map->tag) != Definitions::ExtensionBlockTag::ExtensionBlockMap)
raw_index++;
if (read_host(&current_extension_map->tag) != (u8)Definitions::ExtensionBlockTag::ExtensionBlockMap)
return Error::from_string_literal("Did not find extension map at block 1"sv);
if (!validate_block_checksum(*current_extension_map))
return Error::from_string_literal("Extension block map checksum mismatch"sv);
}
} else if (read_host(&raw_extension_blocks[0].tag) == Definitions::ExtensionBlockTag::ExtensionBlockMap) {
} else if (read_host(&raw_extension_blocks[0].tag) == (u8)Definitions::ExtensionBlockTag::ExtensionBlockMap) {
current_extension_map = &raw_extension_blocks[0];
raw_index++;
}
for (unsigned raw_index = 0; raw_index < raw_extension_block_count; raw_index++) {
for (; raw_index < raw_extension_block_count; raw_index++) {
auto& raw_block = raw_extension_blocks[raw_index];
u8 tag = read_host(&raw_block.tag);
if (current_extension_map && raw_index == 127) {
if (tag != Definitions::ExtensionBlockTag::ExtensionBlockMap)
if (tag != (u8)Definitions::ExtensionBlockTag::ExtensionBlockMap)
return Error::from_string_literal("Did not find extension map at block 128"sv);
current_extension_map = &raw_extension_blocks[127];
if (!validate_block_checksum(*current_extension_map))
@ -524,17 +530,16 @@ ErrorOr<IterationDecision> Parser::for_each_extension_block(Function<IterationDe
continue;
}
if (tag == Definitions::ExtensionBlockTag::ExtensionBlockMap)
if (tag == (u8)Definitions::ExtensionBlockTag::ExtensionBlockMap)
return Error::from_string_literal("Unexpected extension map encountered"sv);
if (!validate_block_checksum(raw_block))
return Error::from_string_literal("Extension block checksum mismatch"sv);
size_t offset = (u8 const*)&raw_block - m_bytes.data();
IterationDecision decision = callback(raw_index + 1, tag, raw_block.block.revision, m_bytes.slice(offset, sizeof(Definitions::ExtensionBlock)));
if (decision != IterationDecision::Continue)
return decision;
offset += sizeof(Definitions::ExtensionBlock);
}
return IterationDecision::Continue;
}
@ -1033,7 +1038,7 @@ ErrorOr<IterationDecision> Parser::for_each_detailed_timing(Function<IterationDe
Optional<Error> extension_error;
auto result = for_each_extension_block([&](u8 block_id, u8 tag, u8, ReadonlyBytes bytes) {
if (tag != Definitions::ExtensionBlockTag::CEA_861)
if (tag != (u8)Definitions::ExtensionBlockTag::CEA_861)
return IterationDecision::Continue;
CEA861ExtensionBlock cea861(*this, (Definitions::ExtensionBlock const*)bytes.data());
@ -1077,7 +1082,7 @@ ErrorOr<IterationDecision> Parser::for_each_short_video_descriptor(Function<Iter
{
Optional<Error> extension_error;
auto result = for_each_extension_block([&](u8 block_id, u8 tag, u8, ReadonlyBytes bytes) {
if (tag != Definitions::ExtensionBlockTag::CEA_861)
if (tag != (u8)Definitions::ExtensionBlockTag::CEA_861)
return IterationDecision::Continue;
CEA861ExtensionBlock cea861(*this, (Definitions::ExtensionBlock const*)bytes.data());
@ -1113,7 +1118,7 @@ ErrorOr<IterationDecision> Parser::for_each_display_descriptor(Function<Iteratio
Optional<Error> extension_error;
auto result = for_each_extension_block([&](u8, u8 tag, u8, ReadonlyBytes bytes) {
if (tag != Definitions::ExtensionBlockTag::CEA_861)
if (tag != (u8)Definitions::ExtensionBlockTag::CEA_861)
return IterationDecision::Continue;
CEA861ExtensionBlock cea861(*this, (Definitions::ExtensionBlock const*)bytes.data());