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