diff options
3 files changed, 136 insertions, 6 deletions
diff --git a/services/surfaceflinger/DisplayHardware/DisplayIdentification.cpp b/services/surfaceflinger/DisplayHardware/DisplayIdentification.cpp index f24f3142f1..b6d904f9f9 100644 --- a/services/surfaceflinger/DisplayHardware/DisplayIdentification.cpp +++ b/services/surfaceflinger/DisplayHardware/DisplayIdentification.cpp @@ -35,6 +35,7 @@ namespace { using byte_view = std::basic_string_view<uint8_t>; +constexpr size_t kEdidBlockSize = 128; constexpr size_t kEdidHeaderLength = 5; constexpr uint16_t kFallbackEdidManufacturerId = 0; @@ -98,6 +99,57 @@ DeviceProductInfo buildDeviceProductInfo(const Edid& edid) { return info; } +Cea861ExtensionBlock parseCea861Block(const byte_view& block) { + Cea861ExtensionBlock cea861Block; + + constexpr size_t kRevisionNumberOffset = 1; + cea861Block.revisionNumber = block[kRevisionNumberOffset]; + + constexpr size_t kDetailedTimingDescriptorsOffset = 2; + const size_t dtdStart = + std::min(kEdidBlockSize, static_cast<size_t>(block[kDetailedTimingDescriptorsOffset])); + + // Parse data blocks. + for (size_t dataBlockOffset = 4; dataBlockOffset < dtdStart;) { + const uint8_t header = block[dataBlockOffset]; + const uint8_t tag = header >> 5; + const size_t bodyLength = header & 0b11111; + constexpr size_t kDataBlockHeaderSize = 1; + const size_t dataBlockSize = bodyLength + kDataBlockHeaderSize; + + if (block.size() < dataBlockOffset + dataBlockSize) { + ALOGW("Invalid EDID: CEA 861 data block is truncated."); + break; + } + + const byte_view dataBlock(block.data() + dataBlockOffset, dataBlockSize); + constexpr uint8_t kVendorSpecificDataBlockTag = 0x3; + + if (tag == kVendorSpecificDataBlockTag) { + const uint32_t ieeeRegistrationId = + dataBlock[1] | (dataBlock[2] << 8) | (dataBlock[3] << 16); + constexpr uint32_t kHdmiIeeeRegistrationId = 0xc03; + + if (ieeeRegistrationId == kHdmiIeeeRegistrationId) { + const uint8_t a = dataBlock[4] >> 4; + const uint8_t b = dataBlock[4] & 0b1111; + const uint8_t c = dataBlock[5] >> 4; + const uint8_t d = dataBlock[5] & 0b1111; + cea861Block.hdmiVendorDataBlock = + HdmiVendorDataBlock{.physicalAddress = HdmiPhysicalAddress{a, b, c, d}}; + } else { + ALOGV("Ignoring vendor specific data block for vendor with IEEE OUI %x", + ieeeRegistrationId); + } + } else { + ALOGV("Ignoring CEA-861 data block with tag %x", tag); + } + dataBlockOffset += bodyLength + kDataBlockHeaderSize; + } + + return cea861Block; +} + } // namespace uint16_t DisplayId::manufacturerId() const { @@ -115,13 +167,12 @@ bool isEdid(const DisplayIdentificationData& data) { } std::optional<Edid> parseEdid(const DisplayIdentificationData& edid) { - constexpr size_t kMinLength = 128; - if (edid.size() < kMinLength) { + if (edid.size() < kEdidBlockSize) { ALOGW("Invalid EDID: structure is truncated."); // Attempt parsing even if EDID is malformed. } else { - ALOGW_IF(edid[126] != 0, "EDID extensions are currently unsupported."); - ALOGW_IF(std::accumulate(edid.begin(), edid.begin() + kMinLength, static_cast<uint8_t>(0)), + ALOGW_IF(std::accumulate(edid.begin(), edid.begin() + kEdidBlockSize, + static_cast<uint8_t>(0)), "Invalid EDID: structure does not checksum."); } @@ -227,13 +278,43 @@ std::optional<Edid> parseEdid(const DisplayIdentificationData& edid) { // have been observed to change on some displays with multiple inputs. const auto modelHash = static_cast<uint32_t>(std::hash<std::string_view>()(modelString)); + // Parse extension blocks. + std::optional<Cea861ExtensionBlock> cea861Block; + if (edid.size() < kEdidBlockSize) { + ALOGW("Invalid EDID: block 0 is truncated."); + } else { + constexpr size_t kNumExtensionsOffset = 126; + const size_t numExtensions = edid[kNumExtensionsOffset]; + view = byte_view(edid.data(), edid.size()); + for (size_t blockNumber = 1; blockNumber <= numExtensions; blockNumber++) { + view.remove_prefix(kEdidBlockSize); + if (view.size() < kEdidBlockSize) { + ALOGW("Invalid EDID: block %zu is truncated.", blockNumber); + break; + } + + const byte_view block(view.data(), kEdidBlockSize); + ALOGW_IF(std::accumulate(block.begin(), block.end(), static_cast<uint8_t>(0)), + "Invalid EDID: block %zu does not checksum.", blockNumber); + const uint8_t tag = block[0]; + + constexpr uint8_t kCea861BlockTag = 0x2; + if (tag == kCea861BlockTag) { + cea861Block = parseCea861Block(block); + } else { + ALOGV("Ignoring block number %zu with tag %x.", blockNumber, tag); + } + } + } + return Edid{.manufacturerId = manufacturerId, .productId = productId, .pnpId = *pnpId, .modelHash = modelHash, .displayName = displayName, + .manufactureOrModelYear = manufactureOrModelYear, .manufactureWeek = manufactureWeek, - .manufactureOrModelYear = manufactureOrModelYear}; + .cea861Block = cea861Block}; } std::optional<PnpId> getPnpId(uint16_t manufacturerId) { diff --git a/services/surfaceflinger/DisplayHardware/DisplayIdentification.h b/services/surfaceflinger/DisplayHardware/DisplayIdentification.h index d91b95773b..fc2f72e80c 100644 --- a/services/surfaceflinger/DisplayHardware/DisplayIdentification.h +++ b/services/surfaceflinger/DisplayHardware/DisplayIdentification.h @@ -57,6 +57,26 @@ struct DisplayIdentificationInfo { std::optional<DeviceProductInfo> deviceProductInfo; }; +struct ExtensionBlock { + uint8_t tag; + uint8_t revisionNumber; +}; + +struct HdmiPhysicalAddress { + // The address describes the path from the display sink in the network of connected HDMI + // devices. The format of the address is "a.b.c.d". For example, address 2.1.0.0 means we are + // connected to port 1 of a device which is connected to port 2 of the sink. + uint8_t a, b, c, d; +}; + +struct HdmiVendorDataBlock { + std::optional<HdmiPhysicalAddress> physicalAddress; +}; + +struct Cea861ExtensionBlock : ExtensionBlock { + std::optional<HdmiVendorDataBlock> hdmiVendorDataBlock; +}; + struct Edid { uint16_t manufacturerId; uint16_t productId; @@ -65,6 +85,7 @@ struct Edid { std::string_view displayName; uint8_t manufactureOrModelYear; uint8_t manufactureWeek; + std::optional<Cea861ExtensionBlock> cea861Block; }; bool isEdid(const DisplayIdentificationData&); diff --git a/services/surfaceflinger/tests/unittests/DisplayIdentificationTest.cpp b/services/surfaceflinger/tests/unittests/DisplayIdentificationTest.cpp index c2ddfcee45..cc11aa9594 100644 --- a/services/surfaceflinger/tests/unittests/DisplayIdentificationTest.cpp +++ b/services/surfaceflinger/tests/unittests/DisplayIdentificationTest.cpp @@ -96,7 +96,7 @@ const unsigned char kHisenseTvEdid[] = "\x0a\x20\x20\x20\x20\x20\x01\x47\x02\x03\x2d\x71\x50\x90\x05" "\x04\x03\x07\x02\x06\x01\x1f\x14\x13\x12\x16\x11\x15\x20\x2c" "\x09\x07\x03\x15\x07\x50\x57\x07\x00\x39\x07\xbb\x66\x03\x0c" - "\x00\x20\x00\x00\x83\x01\x00\x00\x01\x1d\x00\x72\x51\xd0\x1e" + "\x00\x12\x34\x00\x83\x01\x00\x00\x01\x1d\x00\x72\x51\xd0\x1e" "\x20\x6e\x28\x55\x00\xc4\x8e\x21\x00\x00\x1e\x01\x1d\x80\x18" "\x71\x1c\x16\x20\x58\x2c\x25\x00\xc4\x8e\x21\x00\x00\x9e\x8c" "\x0a\xd0\x8a\x20\xe0\x2d\x10\x10\x3e\x96\x00\x13\x8e\x21\x00" @@ -185,6 +185,7 @@ TEST(DisplayIdentificationTest, parseEdid) { EXPECT_EQ(12610, edid->productId); EXPECT_EQ(21, edid->manufactureOrModelYear); EXPECT_EQ(0, edid->manufactureWeek); + EXPECT_FALSE(edid->cea861Block); edid = parseEdid(getExternalEdid()); ASSERT_TRUE(edid); @@ -195,6 +196,7 @@ TEST(DisplayIdentificationTest, parseEdid) { EXPECT_EQ(10348, edid->productId); EXPECT_EQ(22, edid->manufactureOrModelYear); EXPECT_EQ(2, edid->manufactureWeek); + EXPECT_FALSE(edid->cea861Block); edid = parseEdid(getExternalEedid()); ASSERT_TRUE(edid); @@ -205,6 +207,14 @@ TEST(DisplayIdentificationTest, parseEdid) { EXPECT_EQ(2302, edid->productId); EXPECT_EQ(21, edid->manufactureOrModelYear); EXPECT_EQ(41, edid->manufactureWeek); + ASSERT_TRUE(edid->cea861Block); + ASSERT_TRUE(edid->cea861Block->hdmiVendorDataBlock); + ASSERT_TRUE(edid->cea861Block->hdmiVendorDataBlock->physicalAddress); + auto physicalAddress = edid->cea861Block->hdmiVendorDataBlock->physicalAddress; + EXPECT_EQ(2, physicalAddress->a); + EXPECT_EQ(0, physicalAddress->b); + EXPECT_EQ(0, physicalAddress->c); + EXPECT_EQ(0, physicalAddress->d); edid = parseEdid(getPanasonicTvEdid()); ASSERT_TRUE(edid); @@ -215,6 +225,14 @@ TEST(DisplayIdentificationTest, parseEdid) { EXPECT_EQ(41622, edid->productId); EXPECT_EQ(29, edid->manufactureOrModelYear); EXPECT_EQ(0, edid->manufactureWeek); + ASSERT_TRUE(edid->cea861Block); + ASSERT_TRUE(edid->cea861Block->hdmiVendorDataBlock); + ASSERT_TRUE(edid->cea861Block->hdmiVendorDataBlock->physicalAddress); + physicalAddress = edid->cea861Block->hdmiVendorDataBlock->physicalAddress; + EXPECT_EQ(2, physicalAddress->a); + EXPECT_EQ(0, physicalAddress->b); + EXPECT_EQ(0, physicalAddress->c); + EXPECT_EQ(0, physicalAddress->d); edid = parseEdid(getHisenseTvEdid()); ASSERT_TRUE(edid); @@ -225,6 +243,14 @@ TEST(DisplayIdentificationTest, parseEdid) { EXPECT_EQ(0, edid->productId); EXPECT_EQ(29, edid->manufactureOrModelYear); EXPECT_EQ(18, edid->manufactureWeek); + ASSERT_TRUE(edid->cea861Block); + ASSERT_TRUE(edid->cea861Block->hdmiVendorDataBlock); + ASSERT_TRUE(edid->cea861Block->hdmiVendorDataBlock->physicalAddress); + physicalAddress = edid->cea861Block->hdmiVendorDataBlock->physicalAddress; + EXPECT_EQ(1, physicalAddress->a); + EXPECT_EQ(2, physicalAddress->b); + EXPECT_EQ(3, physicalAddress->c); + EXPECT_EQ(4, physicalAddress->d); edid = parseEdid(getCtlDisplayEdid()); ASSERT_TRUE(edid); @@ -235,6 +261,8 @@ TEST(DisplayIdentificationTest, parseEdid) { EXPECT_EQ(9373, edid->productId); EXPECT_EQ(23, edid->manufactureOrModelYear); EXPECT_EQ(0xff, edid->manufactureWeek); + ASSERT_TRUE(edid->cea861Block); + EXPECT_FALSE(edid->cea861Block->hdmiVendorDataBlock); } TEST(DisplayIdentificationTest, parseInvalidEdid) { |