blob: 5cdaa716274f609cc008397a126f5c8ab7f8eb00 [file] [log] [blame]
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -07001/*
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>
Gil Dekelc37c9042024-11-13 16:53:33 -050022#include <cstdint>
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -070023#include <numeric>
24#include <optional>
Ryan Prichard3b17f282024-02-08 02:31:50 -080025#include <span>
Gil Dekelc37c9042024-11-13 16:53:33 -050026#include <string>
27#include <string_view>
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -070028
Alan Dingc3ccff12024-04-29 00:44:31 -070029#include <ftl/hash.h>
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -070030#include <log/log.h>
Alec Mouriff793872022-01-13 17:45:06 -080031#include <ui/DisplayIdentification.h>
Lucas Berthou8d0a0c42024-08-27 14:32:31 +000032#include <ui/Size.h>
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -070033
34namespace android {
35namespace {
36
Ryan Prichard3b17f282024-02-08 02:31:50 -080037using byte_view = std::span<const uint8_t>;
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -070038
Marin Shalamanov7a9ba302020-03-02 17:49:16 +010039constexpr size_t kEdidBlockSize = 128;
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -070040constexpr size_t kEdidHeaderLength = 5;
41
Dominik Laskowski075d3172018-05-24 15:50:06 -070042constexpr uint16_t kVirtualEdidManufacturerId = 0xffffu;
43
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -070044std::optional<uint8_t> getEdidDescriptorType(const byte_view& view) {
Ryan Prichard3b17f282024-02-08 02:31:50 -080045 if (static_cast<size_t>(view.size()) < kEdidHeaderLength || view[0] || view[1] || view[2] ||
46 view[4]) {
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -070047 return {};
48 }
49
50 return view[3];
51}
52
Lucas Berthou8d0a0c42024-08-27 14:32:31 +000053bool isDetailedTimingDescriptor(const byte_view& view) {
54 return view[0] != 0 && view[1] != 0;
55}
56
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -070057std::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.
70template <size_t I>
71char 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
Marin Shalamanovf5de90d2019-10-08 10:57:25 +020077DeviceProductInfo buildDeviceProductInfo(const Edid& edid) {
78 DeviceProductInfo info;
Marin Shalamanov359a7e72020-02-17 17:03:07 +010079 info.name.assign(edid.displayName);
80 info.productId = std::to_string(edid.productId);
Marin Shalamanovf5de90d2019-10-08 10:57:25 +020081 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
Marin Shalamanov896e6302020-04-06 16:11:25 +0200100 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 }
Marin Shalamanovf5de90d2019-10-08 10:57:25 +0200104 return info;
105}
106
Marin Shalamanov7a9ba302020-03-02 17:49:16 +0100107Cea861ExtensionBlock 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
Ryan Prichard3b17f282024-02-08 02:31:50 -0800125 if (static_cast<size_t>(block.size()) < dataBlockOffset + dataBlockSize) {
Marin Shalamanov7a9ba302020-03-02 17:49:16 +0100126 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) {
Marin Shalamanova524a092020-07-27 21:39:55 +0200134 const uint32_t ieeeRegistrationId = static_cast<uint32_t>(
135 dataBlock[1] | (dataBlock[2] << 8) | (dataBlock[3] << 16));
Marin Shalamanov7a9ba302020-03-02 17:49:16 +0100136 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
Dominik Laskowski34157762018-10-31 13:07:19 -0700158} // namespace
159
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700160bool 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
166std::optional<Edid> parseEdid(const DisplayIdentificationData& edid) {
Marin Shalamanov7a9ba302020-03-02 17:49:16 +0100167 if (edid.size() < kEdidBlockSize) {
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700168 ALOGW("Invalid EDID: structure is truncated.");
169 // Attempt parsing even if EDID is malformed.
170 } else {
Marin Shalamanov7a9ba302020-03-02 17:49:16 +0100171 ALOGW_IF(std::accumulate(edid.begin(), edid.begin() + kEdidBlockSize,
172 static_cast<uint8_t>(0)),
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700173 "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 =
Marin Shalamanova524a092020-07-27 21:39:55 +0200184 static_cast<uint16_t>((edid[kManufacturerOffset] << 8) | edid[kManufacturerOffset + 1]);
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700185
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
Marin Shalamanovf5de90d2019-10-08 10:57:25 +0200192 constexpr size_t kProductIdOffset = 10;
193 if (edid.size() < kProductIdOffset + sizeof(uint16_t)) {
194 ALOGE("Invalid EDID: product ID is truncated.");
195 return {};
196 }
Marin Shalamanova524a092020-07-27 21:39:55 +0200197 const uint16_t productId =
198 static_cast<uint16_t>(edid[kProductIdOffset] | (edid[kProductIdOffset + 1] << 8));
Marin Shalamanovf5de90d2019-10-08 10:57:25 +0200199
Gil Dekelc37c9042024-11-13 16:53:33 -0500200 // 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
Marin Shalamanovf5de90d2019-10-08 10:57:25 +0200215 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
Gil Dekel3e96f942024-11-13 14:51:24 -0500233 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
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700242 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());
Ryan Prichard3b17f282024-02-08 02:31:50 -0800249 view = view.subspan(kDescriptorOffset);
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700250
251 std::string_view displayName;
252 std::string_view serialNumber;
253 std::string_view asciiText;
Lucas Berthou8d0a0c42024-08-27 14:32:31 +0000254 ui::Size preferredDTDPixelSize;
255 ui::Size preferredDTDPhysicalSize;
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700256
257 constexpr size_t kDescriptorCount = 4;
258 constexpr size_t kDescriptorLength = 18;
259
260 for (size_t i = 0; i < kDescriptorCount; i++) {
Ryan Prichard3b17f282024-02-08 02:31:50 -0800261 if (static_cast<size_t>(view.size()) < kDescriptorLength) {
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700262 break;
263 }
264
265 if (const auto type = getEdidDescriptorType(view)) {
266 byte_view descriptor(view.data(), kDescriptorLength);
Ryan Prichard3b17f282024-02-08 02:31:50 -0800267 descriptor = descriptor.subspan(kEdidHeaderLength);
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700268
269 switch (*type) {
270 case 0xfc:
271 displayName = parseEdidText(descriptor);
272 break;
273 case 0xfe:
274 asciiText = parseEdidText(descriptor);
275 break;
276 case 0xff:
277 serialNumber = parseEdidText(descriptor);
278 break;
279 }
Lucas Berthou8d0a0c42024-08-27 14:32:31 +0000280 } else if (isDetailedTimingDescriptor(view)) {
281 static constexpr size_t kHorizontalPhysicalLsbOffset = 12;
282 static constexpr size_t kHorizontalPhysicalMsbOffset = 14;
283 static constexpr size_t kVerticalPhysicalLsbOffset = 13;
284 static constexpr size_t kVerticalPhysicalMsbOffset = 14;
285 const uint32_t hSize =
286 static_cast<uint32_t>(view[kHorizontalPhysicalLsbOffset] |
287 ((view[kHorizontalPhysicalMsbOffset] >> 4) << 8));
288 const uint32_t vSize =
289 static_cast<uint32_t>(view[kVerticalPhysicalLsbOffset] |
290 ((view[kVerticalPhysicalMsbOffset] & 0b1111) << 8));
291
292 static constexpr size_t kHorizontalPixelLsbOffset = 2;
293 static constexpr size_t kHorizontalPixelMsbOffset = 4;
294 static constexpr size_t kVerticalPixelLsbOffset = 5;
295 static constexpr size_t kVerticalPixelMsbOffset = 7;
296
297 const uint8_t hLsb = view[kHorizontalPixelLsbOffset];
298 const uint8_t hMsb = view[kHorizontalPixelMsbOffset];
299 const int32_t hPixel = hLsb + ((hMsb & 0xF0) << 4);
300
301 const uint8_t vLsb = view[kVerticalPixelLsbOffset];
302 const uint8_t vMsb = view[kVerticalPixelMsbOffset];
303 const int32_t vPixel = vLsb + ((vMsb & 0xF0) << 4);
304
305 preferredDTDPixelSize.setWidth(hPixel);
306 preferredDTDPixelSize.setHeight(vPixel);
307 preferredDTDPhysicalSize.setWidth(hSize);
308 preferredDTDPhysicalSize.setHeight(vSize);
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700309 }
310
Ryan Prichard3b17f282024-02-08 02:31:50 -0800311 view = view.subspan(kDescriptorLength);
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700312 }
313
Dominik Laskowski17337962020-03-02 15:51:15 -0800314 std::string_view modelString = displayName;
315
316 if (modelString.empty()) {
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700317 ALOGW("Invalid EDID: falling back to serial number due to missing display name.");
Dominik Laskowski17337962020-03-02 15:51:15 -0800318 modelString = serialNumber;
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700319 }
Dominik Laskowski17337962020-03-02 15:51:15 -0800320 if (modelString.empty()) {
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700321 ALOGW("Invalid EDID: falling back to ASCII text due to missing serial number.");
Dominik Laskowski17337962020-03-02 15:51:15 -0800322 modelString = asciiText;
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700323 }
Dominik Laskowski17337962020-03-02 15:51:15 -0800324 if (modelString.empty()) {
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700325 ALOGE("Invalid EDID: display name and fallback descriptors are missing.");
326 return {};
327 }
328
Dominik Laskowski17337962020-03-02 15:51:15 -0800329 // Hash model string instead of using product code or (integer) serial number, since the latter
Jason Macnak4afe8572021-07-16 13:57:41 -0700330 // have been observed to change on some displays with multiple inputs. Use a stable hash instead
331 // of std::hash which is only required to be same within a single execution of a program.
Alan Dingc3ccff12024-04-29 00:44:31 -0700332 const uint32_t modelHash = static_cast<uint32_t>(*ftl::stable_hash(modelString));
Dominik Laskowski17337962020-03-02 15:51:15 -0800333
Marin Shalamanov7a9ba302020-03-02 17:49:16 +0100334 // Parse extension blocks.
335 std::optional<Cea861ExtensionBlock> cea861Block;
336 if (edid.size() < kEdidBlockSize) {
337 ALOGW("Invalid EDID: block 0 is truncated.");
338 } else {
339 constexpr size_t kNumExtensionsOffset = 126;
340 const size_t numExtensions = edid[kNumExtensionsOffset];
341 view = byte_view(edid.data(), edid.size());
342 for (size_t blockNumber = 1; blockNumber <= numExtensions; blockNumber++) {
Ryan Prichard3b17f282024-02-08 02:31:50 -0800343 view = view.subspan(kEdidBlockSize);
344 if (static_cast<size_t>(view.size()) < kEdidBlockSize) {
Marin Shalamanov7a9ba302020-03-02 17:49:16 +0100345 ALOGW("Invalid EDID: block %zu is truncated.", blockNumber);
346 break;
347 }
348
349 const byte_view block(view.data(), kEdidBlockSize);
350 ALOGW_IF(std::accumulate(block.begin(), block.end(), static_cast<uint8_t>(0)),
351 "Invalid EDID: block %zu does not checksum.", blockNumber);
352 const uint8_t tag = block[0];
353
354 constexpr uint8_t kCea861BlockTag = 0x2;
355 if (tag == kCea861BlockTag) {
356 cea861Block = parseCea861Block(block);
357 } else {
358 ALOGV("Ignoring block number %zu with tag %x.", blockNumber, tag);
359 }
360 }
361 }
362
Lucas Berthou8d0a0c42024-08-27 14:32:31 +0000363 DetailedTimingDescriptor preferredDetailedTimingDescriptor{
364 .pixelSizeCount = preferredDTDPixelSize,
365 .physicalSizeInMm = preferredDTDPhysicalSize,
366 };
367
368 return Edid{
369 .manufacturerId = manufacturerId,
370 .productId = productId,
Gil Dekelc37c9042024-11-13 16:53:33 -0500371 .hashedBlockZeroSerialNumberOpt = hashedBlockZeroSNOpt,
Lucas Berthou8d0a0c42024-08-27 14:32:31 +0000372 .pnpId = *pnpId,
373 .modelHash = modelHash,
374 .displayName = displayName,
375 .manufactureOrModelYear = manufactureOrModelYear,
376 .manufactureWeek = manufactureWeek,
Gil Dekel3e96f942024-11-13 14:51:24 -0500377 .physicalSizeInCm = maxPhysicalSizeInCm,
Lucas Berthou8d0a0c42024-08-27 14:32:31 +0000378 .cea861Block = cea861Block,
379 .preferredDetailedTimingDescriptor = preferredDetailedTimingDescriptor,
380 };
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700381}
382
383std::optional<PnpId> getPnpId(uint16_t manufacturerId) {
384 const char a = getPnpLetter<0>(manufacturerId);
385 const char b = getPnpLetter<1>(manufacturerId);
386 const char c = getPnpLetter<2>(manufacturerId);
387 return a && b && c ? std::make_optional(PnpId{a, b, c}) : std::nullopt;
388}
389
Marin Shalamanova524a092020-07-27 21:39:55 +0200390std::optional<PnpId> getPnpId(PhysicalDisplayId displayId) {
391 return getPnpId(displayId.getManufacturerId());
Dominik Laskowski34157762018-10-31 13:07:19 -0700392}
393
Dominik Laskowski075d3172018-05-24 15:50:06 -0700394std::optional<DisplayIdentificationInfo> parseDisplayIdentificationData(
395 uint8_t port, const DisplayIdentificationData& data) {
Brian Lindahl8a96ef92024-05-24 14:46:29 +0000396 if (data.empty()) {
397 ALOGI("Display identification data is empty.");
398 return {};
399 }
400
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700401 if (!isEdid(data)) {
402 ALOGE("Display identification data has unknown format.");
403 return {};
404 }
405
406 const auto edid = parseEdid(data);
407 if (!edid) {
408 return {};
409 }
410
Marin Shalamanova524a092020-07-27 21:39:55 +0200411 const auto displayId = PhysicalDisplayId::fromEdid(port, edid->manufacturerId, edid->modelHash);
Lucas Berthou8d0a0c42024-08-27 14:32:31 +0000412 return DisplayIdentificationInfo{
413 .id = displayId,
414 .name = std::string(edid->displayName),
415 .deviceProductInfo = buildDeviceProductInfo(*edid),
416 .preferredDetailedTimingDescriptor = edid->preferredDetailedTimingDescriptor,
417 };
Dominik Laskowski075d3172018-05-24 15:50:06 -0700418}
419
Marin Shalamanova524a092020-07-27 21:39:55 +0200420PhysicalDisplayId getVirtualDisplayId(uint32_t id) {
421 return PhysicalDisplayId::fromEdid(0, kVirtualEdidManufacturerId, id);
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700422}
423
Yi Kong93926f62024-02-20 00:39:46 +0800424} // namespace android