xref: /aosp_15_r20/frameworks/native/libs/ui/DisplayIdentification.cpp (revision 38e8c45f13ce32b0dcecb25141ffecaf386fa17f)
1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #undef LOG_TAG
18 #define LOG_TAG "DisplayIdentification"
19 
20 #include <algorithm>
21 #include <cctype>
22 #include <cstdint>
23 #include <numeric>
24 #include <optional>
25 #include <span>
26 #include <string>
27 #include <string_view>
28 
29 #include <ftl/hash.h>
30 #include <log/log.h>
31 #include <ui/DisplayIdentification.h>
32 #include <ui/Size.h>
33 
34 namespace android {
35 namespace {
36 
37 using byte_view = std::span<const uint8_t>;
38 
39 constexpr size_t kEdidBlockSize = 128;
40 constexpr size_t kEdidHeaderLength = 5;
41 
42 constexpr uint16_t kVirtualEdidManufacturerId = 0xffffu;
43 
getEdidDescriptorType(const byte_view & view)44 std::optional<uint8_t> getEdidDescriptorType(const byte_view& view) {
45     if (static_cast<size_t>(view.size()) < kEdidHeaderLength || view[0] || view[1] || view[2] ||
46         view[4]) {
47         return {};
48     }
49 
50     return view[3];
51 }
52 
isDetailedTimingDescriptor(const byte_view & view)53 bool isDetailedTimingDescriptor(const byte_view& view) {
54     return view[0] != 0 && view[1] != 0;
55 }
56 
parseEdidText(const byte_view & view)57 std::string_view parseEdidText(const byte_view& view) {
58     std::string_view text(reinterpret_cast<const char*>(view.data()), view.size());
59     text = text.substr(0, text.find('\n'));
60 
61     if (!std::all_of(text.begin(), text.end(), ::isprint)) {
62         ALOGW("Invalid EDID: ASCII text is not printable.");
63         return {};
64     }
65 
66     return text;
67 }
68 
69 // Big-endian 16-bit value encodes three 5-bit letters where A is 0b00001.
70 template <size_t I>
getPnpLetter(uint16_t id)71 char getPnpLetter(uint16_t id) {
72     static_assert(I < 3);
73     const char letter = 'A' + (static_cast<uint8_t>(id >> ((2 - I) * 5)) & 0b00011111) - 1;
74     return letter < 'A' || letter > 'Z' ? '\0' : letter;
75 }
76 
buildDeviceProductInfo(const Edid & edid)77 DeviceProductInfo buildDeviceProductInfo(const Edid& edid) {
78     DeviceProductInfo info;
79     info.name.assign(edid.displayName);
80     info.productId = std::to_string(edid.productId);
81     info.manufacturerPnpId = edid.pnpId;
82 
83     constexpr uint8_t kModelYearFlag = 0xff;
84     constexpr uint32_t kYearOffset = 1990;
85 
86     const auto year = edid.manufactureOrModelYear + kYearOffset;
87     if (edid.manufactureWeek == kModelYearFlag) {
88         info.manufactureOrModelDate = DeviceProductInfo::ModelYear{.year = year};
89     } else if (edid.manufactureWeek == 0) {
90         DeviceProductInfo::ManufactureYear date;
91         date.year = year;
92         info.manufactureOrModelDate = date;
93     } else {
94         DeviceProductInfo::ManufactureWeekAndYear date;
95         date.year = year;
96         date.week = edid.manufactureWeek;
97         info.manufactureOrModelDate = date;
98     }
99 
100     if (edid.cea861Block && edid.cea861Block->hdmiVendorDataBlock) {
101         const auto& address = edid.cea861Block->hdmiVendorDataBlock->physicalAddress;
102         info.relativeAddress = {address.a, address.b, address.c, address.d};
103     }
104     return info;
105 }
106 
parseCea861Block(const byte_view & block)107 Cea861ExtensionBlock parseCea861Block(const byte_view& block) {
108     Cea861ExtensionBlock cea861Block;
109 
110     constexpr size_t kRevisionNumberOffset = 1;
111     cea861Block.revisionNumber = block[kRevisionNumberOffset];
112 
113     constexpr size_t kDetailedTimingDescriptorsOffset = 2;
114     const size_t dtdStart =
115             std::min(kEdidBlockSize, static_cast<size_t>(block[kDetailedTimingDescriptorsOffset]));
116 
117     // Parse data blocks.
118     for (size_t dataBlockOffset = 4; dataBlockOffset < dtdStart;) {
119         const uint8_t header = block[dataBlockOffset];
120         const uint8_t tag = header >> 5;
121         const size_t bodyLength = header & 0b11111;
122         constexpr size_t kDataBlockHeaderSize = 1;
123         const size_t dataBlockSize = bodyLength + kDataBlockHeaderSize;
124 
125         if (static_cast<size_t>(block.size()) < dataBlockOffset + dataBlockSize) {
126             ALOGW("Invalid EDID: CEA 861 data block is truncated.");
127             break;
128         }
129 
130         const byte_view dataBlock(block.data() + dataBlockOffset, dataBlockSize);
131         constexpr uint8_t kVendorSpecificDataBlockTag = 0x3;
132 
133         if (tag == kVendorSpecificDataBlockTag) {
134             const uint32_t ieeeRegistrationId = static_cast<uint32_t>(
135                     dataBlock[1] | (dataBlock[2] << 8) | (dataBlock[3] << 16));
136             constexpr uint32_t kHdmiIeeeRegistrationId = 0xc03;
137 
138             if (ieeeRegistrationId == kHdmiIeeeRegistrationId) {
139                 const uint8_t a = dataBlock[4] >> 4;
140                 const uint8_t b = dataBlock[4] & 0b1111;
141                 const uint8_t c = dataBlock[5] >> 4;
142                 const uint8_t d = dataBlock[5] & 0b1111;
143                 cea861Block.hdmiVendorDataBlock =
144                         HdmiVendorDataBlock{.physicalAddress = HdmiPhysicalAddress{a, b, c, d}};
145             } else {
146                 ALOGV("Ignoring vendor specific data block for vendor with IEEE OUI %x",
147                       ieeeRegistrationId);
148             }
149         } else {
150             ALOGV("Ignoring CEA-861 data block with tag %x", tag);
151         }
152         dataBlockOffset += bodyLength + kDataBlockHeaderSize;
153     }
154 
155     return cea861Block;
156 }
157 
158 } // namespace
159 
isEdid(const DisplayIdentificationData & data)160 bool isEdid(const DisplayIdentificationData& data) {
161     const uint8_t kMagic[] = {0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0};
162     return data.size() >= sizeof(kMagic) &&
163             std::equal(std::begin(kMagic), std::end(kMagic), data.begin());
164 }
165 
parseEdid(const DisplayIdentificationData & edid)166 std::optional<Edid> parseEdid(const DisplayIdentificationData& edid) {
167     if (edid.size() < kEdidBlockSize) {
168         ALOGW("Invalid EDID: structure is truncated.");
169         // Attempt parsing even if EDID is malformed.
170     } else {
171         ALOGW_IF(std::accumulate(edid.begin(), edid.begin() + kEdidBlockSize,
172                                  static_cast<uint8_t>(0)),
173                  "Invalid EDID: structure does not checksum.");
174     }
175 
176     constexpr size_t kManufacturerOffset = 8;
177     if (edid.size() < kManufacturerOffset + sizeof(uint16_t)) {
178         ALOGE("Invalid EDID: manufacturer ID is truncated.");
179         return {};
180     }
181 
182     // Plug and play ID encoded as big-endian 16-bit value.
183     const uint16_t manufacturerId =
184             static_cast<uint16_t>((edid[kManufacturerOffset] << 8) | edid[kManufacturerOffset + 1]);
185 
186     const auto pnpId = getPnpId(manufacturerId);
187     if (!pnpId) {
188         ALOGE("Invalid EDID: manufacturer ID is not a valid PnP ID.");
189         return {};
190     }
191 
192     constexpr size_t kProductIdOffset = 10;
193     if (edid.size() < kProductIdOffset + sizeof(uint16_t)) {
194         ALOGE("Invalid EDID: product ID is truncated.");
195         return {};
196     }
197     const uint16_t productId =
198             static_cast<uint16_t>(edid[kProductIdOffset] | (edid[kProductIdOffset + 1] << 8));
199 
200     //   Bytes 12-15: display serial number, in little-endian (LSB). This field is
201     //   optional and its absence is marked by having all bytes set to 0x00.
202     //   Values do not represent ASCII characters.
203     constexpr size_t kSerialNumberOffset = 12;
204     if (edid.size() < kSerialNumberOffset + sizeof(uint32_t)) {
205         ALOGE("Invalid EDID: block zero S/N is truncated.");
206         return {};
207     }
208     const uint32_t blockZeroSerialNumber = edid[kSerialNumberOffset] +
209             (edid[kSerialNumberOffset + 1] << 8) + (edid[kSerialNumberOffset + 2] << 16) +
210             (edid[kSerialNumberOffset + 3] << 24);
211     const auto hashedBlockZeroSNOpt = blockZeroSerialNumber == 0
212             ? std::nullopt
213             : ftl::stable_hash(std::string_view(std::to_string(blockZeroSerialNumber)));
214 
215     constexpr size_t kManufactureWeekOffset = 16;
216     if (edid.size() < kManufactureWeekOffset + sizeof(uint8_t)) {
217         ALOGE("Invalid EDID: manufacture week is truncated.");
218         return {};
219     }
220     const uint8_t manufactureWeek = edid[kManufactureWeekOffset];
221     ALOGW_IF(0x37 <= manufactureWeek && manufactureWeek <= 0xfe,
222              "Invalid EDID: week of manufacture cannot be in the range [0x37, 0xfe].");
223 
224     constexpr size_t kManufactureYearOffset = 17;
225     if (edid.size() < kManufactureYearOffset + sizeof(uint8_t)) {
226         ALOGE("Invalid EDID: manufacture year is truncated.");
227         return {};
228     }
229     const uint8_t manufactureOrModelYear = edid[kManufactureYearOffset];
230     ALOGW_IF(manufactureOrModelYear <= 0xf,
231              "Invalid EDID: model year or manufacture year cannot be in the range [0x0, 0xf].");
232 
233     constexpr size_t kMaxHorizontalPhysicalSizeOffset = 21;
234     constexpr size_t kMaxVerticalPhysicalSizeOffset = 22;
235     if (edid.size() < kMaxVerticalPhysicalSizeOffset + sizeof(uint8_t)) {
236         ALOGE("Invalid EDID: display's physical size is truncated.");
237         return {};
238     }
239     ui::Size maxPhysicalSizeInCm(edid[kMaxHorizontalPhysicalSizeOffset],
240                                  edid[kMaxVerticalPhysicalSizeOffset]);
241 
242     constexpr size_t kDescriptorOffset = 54;
243     if (edid.size() < kDescriptorOffset) {
244         ALOGE("Invalid EDID: descriptors are missing.");
245         return {};
246     }
247 
248     byte_view view(edid.data(), edid.size());
249     view = view.subspan(kDescriptorOffset);
250 
251     std::string_view displayName;
252     std::string_view descriptorBlockSerialNumber;
253     std::optional<uint64_t> hashedDescriptorBlockSNOpt = std::nullopt;
254     std::string_view asciiText;
255     ui::Size preferredDTDPixelSize;
256     ui::Size preferredDTDPhysicalSize;
257 
258     constexpr size_t kDescriptorCount = 4;
259     constexpr size_t kDescriptorLength = 18;
260 
261     for (size_t i = 0; i < kDescriptorCount; i++) {
262         if (static_cast<size_t>(view.size()) < kDescriptorLength) {
263             break;
264         }
265 
266         if (const auto type = getEdidDescriptorType(view)) {
267             byte_view descriptor(view.data(), kDescriptorLength);
268             descriptor = descriptor.subspan(kEdidHeaderLength);
269 
270             switch (*type) {
271                 case 0xfc:
272                     displayName = parseEdidText(descriptor);
273                     break;
274                 case 0xfe:
275                     asciiText = parseEdidText(descriptor);
276                     break;
277                 case 0xff:
278                     descriptorBlockSerialNumber = parseEdidText(descriptor);
279                     hashedDescriptorBlockSNOpt = descriptorBlockSerialNumber.empty()
280                             ? std::nullopt
281                             : ftl::stable_hash(descriptorBlockSerialNumber);
282                     break;
283             }
284         } else if (isDetailedTimingDescriptor(view)) {
285             static constexpr size_t kHorizontalPhysicalLsbOffset = 12;
286             static constexpr size_t kHorizontalPhysicalMsbOffset = 14;
287             static constexpr size_t kVerticalPhysicalLsbOffset = 13;
288             static constexpr size_t kVerticalPhysicalMsbOffset = 14;
289             const uint32_t hSize =
290                     static_cast<uint32_t>(view[kHorizontalPhysicalLsbOffset] |
291                                           ((view[kHorizontalPhysicalMsbOffset] >> 4) << 8));
292             const uint32_t vSize =
293                     static_cast<uint32_t>(view[kVerticalPhysicalLsbOffset] |
294                                           ((view[kVerticalPhysicalMsbOffset] & 0b1111) << 8));
295 
296             static constexpr size_t kHorizontalPixelLsbOffset = 2;
297             static constexpr size_t kHorizontalPixelMsbOffset = 4;
298             static constexpr size_t kVerticalPixelLsbOffset = 5;
299             static constexpr size_t kVerticalPixelMsbOffset = 7;
300 
301             const uint8_t hLsb = view[kHorizontalPixelLsbOffset];
302             const uint8_t hMsb = view[kHorizontalPixelMsbOffset];
303             const int32_t hPixel = hLsb + ((hMsb & 0xF0) << 4);
304 
305             const uint8_t vLsb = view[kVerticalPixelLsbOffset];
306             const uint8_t vMsb = view[kVerticalPixelMsbOffset];
307             const int32_t vPixel = vLsb + ((vMsb & 0xF0) << 4);
308 
309             preferredDTDPixelSize.setWidth(hPixel);
310             preferredDTDPixelSize.setHeight(vPixel);
311             preferredDTDPhysicalSize.setWidth(hSize);
312             preferredDTDPhysicalSize.setHeight(vSize);
313         }
314 
315         view = view.subspan(kDescriptorLength);
316     }
317 
318     std::string_view modelString = displayName;
319 
320     if (modelString.empty()) {
321         ALOGW("Invalid EDID: falling back to serial number due to missing display name.");
322         modelString = descriptorBlockSerialNumber;
323     }
324     if (modelString.empty()) {
325         ALOGW("Invalid EDID: falling back to ASCII text due to missing serial number.");
326         modelString = asciiText;
327     }
328     if (modelString.empty()) {
329         ALOGE("Invalid EDID: display name and fallback descriptors are missing.");
330         return {};
331     }
332 
333     // Hash model string instead of using product code or (integer) serial number, since the latter
334     // have been observed to change on some displays with multiple inputs. Use a stable hash instead
335     // of std::hash which is only required to be same within a single execution of a program.
336     const uint32_t modelHash = static_cast<uint32_t>(*ftl::stable_hash(modelString));
337 
338     // Parse extension blocks.
339     std::optional<Cea861ExtensionBlock> cea861Block;
340     if (edid.size() < kEdidBlockSize) {
341         ALOGW("Invalid EDID: block 0 is truncated.");
342     } else {
343         constexpr size_t kNumExtensionsOffset = 126;
344         const size_t numExtensions = edid[kNumExtensionsOffset];
345         view = byte_view(edid.data(), edid.size());
346         for (size_t blockNumber = 1; blockNumber <= numExtensions; blockNumber++) {
347             view = view.subspan(kEdidBlockSize);
348             if (static_cast<size_t>(view.size()) < kEdidBlockSize) {
349                 ALOGW("Invalid EDID: block %zu is truncated.", blockNumber);
350                 break;
351             }
352 
353             const byte_view block(view.data(), kEdidBlockSize);
354             ALOGW_IF(std::accumulate(block.begin(), block.end(), static_cast<uint8_t>(0)),
355                      "Invalid EDID: block %zu does not checksum.", blockNumber);
356             const uint8_t tag = block[0];
357 
358             constexpr uint8_t kCea861BlockTag = 0x2;
359             if (tag == kCea861BlockTag) {
360                 cea861Block = parseCea861Block(block);
361             } else {
362                 ALOGV("Ignoring block number %zu with tag %x.", blockNumber, tag);
363             }
364         }
365     }
366 
367     DetailedTimingDescriptor preferredDetailedTimingDescriptor{
368             .pixelSizeCount = preferredDTDPixelSize,
369             .physicalSizeInMm = preferredDTDPhysicalSize,
370     };
371 
372     return Edid{
373             .manufacturerId = manufacturerId,
374             .productId = productId,
375             .hashedBlockZeroSerialNumberOpt = hashedBlockZeroSNOpt,
376             .hashedDescriptorBlockSerialNumberOpt = hashedDescriptorBlockSNOpt,
377             .pnpId = *pnpId,
378             .modelHash = modelHash,
379             .displayName = displayName,
380             .manufactureOrModelYear = manufactureOrModelYear,
381             .manufactureWeek = manufactureWeek,
382             .physicalSizeInCm = maxPhysicalSizeInCm,
383             .cea861Block = cea861Block,
384             .preferredDetailedTimingDescriptor = preferredDetailedTimingDescriptor,
385     };
386 }
387 
getPnpId(uint16_t manufacturerId)388 std::optional<PnpId> getPnpId(uint16_t manufacturerId) {
389     const char a = getPnpLetter<0>(manufacturerId);
390     const char b = getPnpLetter<1>(manufacturerId);
391     const char c = getPnpLetter<2>(manufacturerId);
392     return a && b && c ? std::make_optional(PnpId{a, b, c}) : std::nullopt;
393 }
394 
getPnpId(PhysicalDisplayId displayId)395 std::optional<PnpId> getPnpId(PhysicalDisplayId displayId) {
396     return getPnpId(displayId.getManufacturerId());
397 }
398 
parseDisplayIdentificationData(uint8_t port,const DisplayIdentificationData & data)399 std::optional<DisplayIdentificationInfo> parseDisplayIdentificationData(
400         uint8_t port, const DisplayIdentificationData& data) {
401     if (data.empty()) {
402         ALOGI("Display identification data is empty.");
403         return {};
404     }
405 
406     if (!isEdid(data)) {
407         ALOGE("Display identification data has unknown format.");
408         return {};
409     }
410 
411     const auto edid = parseEdid(data);
412     if (!edid) {
413         return {};
414     }
415 
416     const auto displayId = PhysicalDisplayId::fromEdid(port, edid->manufacturerId, edid->modelHash);
417     return DisplayIdentificationInfo{
418             .id = displayId,
419             .name = std::string(edid->displayName),
420             .deviceProductInfo = buildDeviceProductInfo(*edid),
421             .preferredDetailedTimingDescriptor = edid->preferredDetailedTimingDescriptor,
422     };
423 }
424 
getVirtualDisplayId(uint32_t id)425 PhysicalDisplayId getVirtualDisplayId(uint32_t id) {
426     return PhysicalDisplayId::fromEdid(0, kVirtualEdidManufacturerId, id);
427 }
428 
429 } // namespace android
430