blob: 503f92ff320cd792450540964045ba0f17eed69d [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>
22#include <numeric>
23#include <optional>
Ryan Prichard3b17f282024-02-08 02:31:50 -080024#include <span>
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -070025
Alan Dingc3ccff12024-04-29 00:44:31 -070026#include <ftl/hash.h>
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -070027#include <log/log.h>
Alec Mouriff793872022-01-13 17:45:06 -080028#include <ui/DisplayIdentification.h>
Lucas Berthou8d0a0c42024-08-27 14:32:31 +000029#include <ui/Size.h>
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -070030
31namespace android {
32namespace {
33
Ryan Prichard3b17f282024-02-08 02:31:50 -080034using byte_view = std::span<const uint8_t>;
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -070035
Marin Shalamanov7a9ba302020-03-02 17:49:16 +010036constexpr size_t kEdidBlockSize = 128;
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -070037constexpr size_t kEdidHeaderLength = 5;
38
Dominik Laskowski075d3172018-05-24 15:50:06 -070039constexpr uint16_t kVirtualEdidManufacturerId = 0xffffu;
40
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -070041std::optional<uint8_t> getEdidDescriptorType(const byte_view& view) {
Ryan Prichard3b17f282024-02-08 02:31:50 -080042 if (static_cast<size_t>(view.size()) < kEdidHeaderLength || view[0] || view[1] || view[2] ||
43 view[4]) {
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -070044 return {};
45 }
46
47 return view[3];
48}
49
Lucas Berthou8d0a0c42024-08-27 14:32:31 +000050bool isDetailedTimingDescriptor(const byte_view& view) {
51 return view[0] != 0 && view[1] != 0;
52}
53
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -070054std::string_view parseEdidText(const byte_view& view) {
55 std::string_view text(reinterpret_cast<const char*>(view.data()), view.size());
56 text = text.substr(0, text.find('\n'));
57
58 if (!std::all_of(text.begin(), text.end(), ::isprint)) {
59 ALOGW("Invalid EDID: ASCII text is not printable.");
60 return {};
61 }
62
63 return text;
64}
65
66// Big-endian 16-bit value encodes three 5-bit letters where A is 0b00001.
67template <size_t I>
68char getPnpLetter(uint16_t id) {
69 static_assert(I < 3);
70 const char letter = 'A' + (static_cast<uint8_t>(id >> ((2 - I) * 5)) & 0b00011111) - 1;
71 return letter < 'A' || letter > 'Z' ? '\0' : letter;
72}
73
Marin Shalamanovf5de90d2019-10-08 10:57:25 +020074DeviceProductInfo buildDeviceProductInfo(const Edid& edid) {
75 DeviceProductInfo info;
Marin Shalamanov359a7e72020-02-17 17:03:07 +010076 info.name.assign(edid.displayName);
77 info.productId = std::to_string(edid.productId);
Marin Shalamanovf5de90d2019-10-08 10:57:25 +020078 info.manufacturerPnpId = edid.pnpId;
79
80 constexpr uint8_t kModelYearFlag = 0xff;
81 constexpr uint32_t kYearOffset = 1990;
82
83 const auto year = edid.manufactureOrModelYear + kYearOffset;
84 if (edid.manufactureWeek == kModelYearFlag) {
85 info.manufactureOrModelDate = DeviceProductInfo::ModelYear{.year = year};
86 } else if (edid.manufactureWeek == 0) {
87 DeviceProductInfo::ManufactureYear date;
88 date.year = year;
89 info.manufactureOrModelDate = date;
90 } else {
91 DeviceProductInfo::ManufactureWeekAndYear date;
92 date.year = year;
93 date.week = edid.manufactureWeek;
94 info.manufactureOrModelDate = date;
95 }
96
Marin Shalamanov896e6302020-04-06 16:11:25 +020097 if (edid.cea861Block && edid.cea861Block->hdmiVendorDataBlock) {
98 const auto& address = edid.cea861Block->hdmiVendorDataBlock->physicalAddress;
99 info.relativeAddress = {address.a, address.b, address.c, address.d};
100 }
Marin Shalamanovf5de90d2019-10-08 10:57:25 +0200101 return info;
102}
103
Marin Shalamanov7a9ba302020-03-02 17:49:16 +0100104Cea861ExtensionBlock parseCea861Block(const byte_view& block) {
105 Cea861ExtensionBlock cea861Block;
106
107 constexpr size_t kRevisionNumberOffset = 1;
108 cea861Block.revisionNumber = block[kRevisionNumberOffset];
109
110 constexpr size_t kDetailedTimingDescriptorsOffset = 2;
111 const size_t dtdStart =
112 std::min(kEdidBlockSize, static_cast<size_t>(block[kDetailedTimingDescriptorsOffset]));
113
114 // Parse data blocks.
115 for (size_t dataBlockOffset = 4; dataBlockOffset < dtdStart;) {
116 const uint8_t header = block[dataBlockOffset];
117 const uint8_t tag = header >> 5;
118 const size_t bodyLength = header & 0b11111;
119 constexpr size_t kDataBlockHeaderSize = 1;
120 const size_t dataBlockSize = bodyLength + kDataBlockHeaderSize;
121
Ryan Prichard3b17f282024-02-08 02:31:50 -0800122 if (static_cast<size_t>(block.size()) < dataBlockOffset + dataBlockSize) {
Marin Shalamanov7a9ba302020-03-02 17:49:16 +0100123 ALOGW("Invalid EDID: CEA 861 data block is truncated.");
124 break;
125 }
126
127 const byte_view dataBlock(block.data() + dataBlockOffset, dataBlockSize);
128 constexpr uint8_t kVendorSpecificDataBlockTag = 0x3;
129
130 if (tag == kVendorSpecificDataBlockTag) {
Marin Shalamanova524a092020-07-27 21:39:55 +0200131 const uint32_t ieeeRegistrationId = static_cast<uint32_t>(
132 dataBlock[1] | (dataBlock[2] << 8) | (dataBlock[3] << 16));
Marin Shalamanov7a9ba302020-03-02 17:49:16 +0100133 constexpr uint32_t kHdmiIeeeRegistrationId = 0xc03;
134
135 if (ieeeRegistrationId == kHdmiIeeeRegistrationId) {
136 const uint8_t a = dataBlock[4] >> 4;
137 const uint8_t b = dataBlock[4] & 0b1111;
138 const uint8_t c = dataBlock[5] >> 4;
139 const uint8_t d = dataBlock[5] & 0b1111;
140 cea861Block.hdmiVendorDataBlock =
141 HdmiVendorDataBlock{.physicalAddress = HdmiPhysicalAddress{a, b, c, d}};
142 } else {
143 ALOGV("Ignoring vendor specific data block for vendor with IEEE OUI %x",
144 ieeeRegistrationId);
145 }
146 } else {
147 ALOGV("Ignoring CEA-861 data block with tag %x", tag);
148 }
149 dataBlockOffset += bodyLength + kDataBlockHeaderSize;
150 }
151
152 return cea861Block;
153}
154
Dominik Laskowski34157762018-10-31 13:07:19 -0700155} // namespace
156
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700157bool isEdid(const DisplayIdentificationData& data) {
158 const uint8_t kMagic[] = {0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0};
159 return data.size() >= sizeof(kMagic) &&
160 std::equal(std::begin(kMagic), std::end(kMagic), data.begin());
161}
162
163std::optional<Edid> parseEdid(const DisplayIdentificationData& edid) {
Marin Shalamanov7a9ba302020-03-02 17:49:16 +0100164 if (edid.size() < kEdidBlockSize) {
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700165 ALOGW("Invalid EDID: structure is truncated.");
166 // Attempt parsing even if EDID is malformed.
167 } else {
Marin Shalamanov7a9ba302020-03-02 17:49:16 +0100168 ALOGW_IF(std::accumulate(edid.begin(), edid.begin() + kEdidBlockSize,
169 static_cast<uint8_t>(0)),
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700170 "Invalid EDID: structure does not checksum.");
171 }
172
173 constexpr size_t kManufacturerOffset = 8;
174 if (edid.size() < kManufacturerOffset + sizeof(uint16_t)) {
175 ALOGE("Invalid EDID: manufacturer ID is truncated.");
176 return {};
177 }
178
179 // Plug and play ID encoded as big-endian 16-bit value.
180 const uint16_t manufacturerId =
Marin Shalamanova524a092020-07-27 21:39:55 +0200181 static_cast<uint16_t>((edid[kManufacturerOffset] << 8) | edid[kManufacturerOffset + 1]);
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700182
183 const auto pnpId = getPnpId(manufacturerId);
184 if (!pnpId) {
185 ALOGE("Invalid EDID: manufacturer ID is not a valid PnP ID.");
186 return {};
187 }
188
Marin Shalamanovf5de90d2019-10-08 10:57:25 +0200189 constexpr size_t kProductIdOffset = 10;
190 if (edid.size() < kProductIdOffset + sizeof(uint16_t)) {
191 ALOGE("Invalid EDID: product ID is truncated.");
192 return {};
193 }
Marin Shalamanova524a092020-07-27 21:39:55 +0200194 const uint16_t productId =
195 static_cast<uint16_t>(edid[kProductIdOffset] | (edid[kProductIdOffset + 1] << 8));
Marin Shalamanovf5de90d2019-10-08 10:57:25 +0200196
197 constexpr size_t kManufactureWeekOffset = 16;
198 if (edid.size() < kManufactureWeekOffset + sizeof(uint8_t)) {
199 ALOGE("Invalid EDID: manufacture week is truncated.");
200 return {};
201 }
202 const uint8_t manufactureWeek = edid[kManufactureWeekOffset];
203 ALOGW_IF(0x37 <= manufactureWeek && manufactureWeek <= 0xfe,
204 "Invalid EDID: week of manufacture cannot be in the range [0x37, 0xfe].");
205
206 constexpr size_t kManufactureYearOffset = 17;
207 if (edid.size() < kManufactureYearOffset + sizeof(uint8_t)) {
208 ALOGE("Invalid EDID: manufacture year is truncated.");
209 return {};
210 }
211 const uint8_t manufactureOrModelYear = edid[kManufactureYearOffset];
212 ALOGW_IF(manufactureOrModelYear <= 0xf,
213 "Invalid EDID: model year or manufacture year cannot be in the range [0x0, 0xf].");
214
Gil Dekel3e96f942024-11-13 14:51:24 -0500215 constexpr size_t kMaxHorizontalPhysicalSizeOffset = 21;
216 constexpr size_t kMaxVerticalPhysicalSizeOffset = 22;
217 if (edid.size() < kMaxVerticalPhysicalSizeOffset + sizeof(uint8_t)) {
218 ALOGE("Invalid EDID: display's physical size is truncated.");
219 return {};
220 }
221 ui::Size maxPhysicalSizeInCm(edid[kMaxHorizontalPhysicalSizeOffset],
222 edid[kMaxVerticalPhysicalSizeOffset]);
223
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700224 constexpr size_t kDescriptorOffset = 54;
225 if (edid.size() < kDescriptorOffset) {
226 ALOGE("Invalid EDID: descriptors are missing.");
227 return {};
228 }
229
230 byte_view view(edid.data(), edid.size());
Ryan Prichard3b17f282024-02-08 02:31:50 -0800231 view = view.subspan(kDescriptorOffset);
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700232
233 std::string_view displayName;
234 std::string_view serialNumber;
235 std::string_view asciiText;
Lucas Berthou8d0a0c42024-08-27 14:32:31 +0000236 ui::Size preferredDTDPixelSize;
237 ui::Size preferredDTDPhysicalSize;
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700238
239 constexpr size_t kDescriptorCount = 4;
240 constexpr size_t kDescriptorLength = 18;
241
242 for (size_t i = 0; i < kDescriptorCount; i++) {
Ryan Prichard3b17f282024-02-08 02:31:50 -0800243 if (static_cast<size_t>(view.size()) < kDescriptorLength) {
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700244 break;
245 }
246
247 if (const auto type = getEdidDescriptorType(view)) {
248 byte_view descriptor(view.data(), kDescriptorLength);
Ryan Prichard3b17f282024-02-08 02:31:50 -0800249 descriptor = descriptor.subspan(kEdidHeaderLength);
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700250
251 switch (*type) {
252 case 0xfc:
253 displayName = parseEdidText(descriptor);
254 break;
255 case 0xfe:
256 asciiText = parseEdidText(descriptor);
257 break;
258 case 0xff:
259 serialNumber = parseEdidText(descriptor);
260 break;
261 }
Lucas Berthou8d0a0c42024-08-27 14:32:31 +0000262 } else if (isDetailedTimingDescriptor(view)) {
263 static constexpr size_t kHorizontalPhysicalLsbOffset = 12;
264 static constexpr size_t kHorizontalPhysicalMsbOffset = 14;
265 static constexpr size_t kVerticalPhysicalLsbOffset = 13;
266 static constexpr size_t kVerticalPhysicalMsbOffset = 14;
267 const uint32_t hSize =
268 static_cast<uint32_t>(view[kHorizontalPhysicalLsbOffset] |
269 ((view[kHorizontalPhysicalMsbOffset] >> 4) << 8));
270 const uint32_t vSize =
271 static_cast<uint32_t>(view[kVerticalPhysicalLsbOffset] |
272 ((view[kVerticalPhysicalMsbOffset] & 0b1111) << 8));
273
274 static constexpr size_t kHorizontalPixelLsbOffset = 2;
275 static constexpr size_t kHorizontalPixelMsbOffset = 4;
276 static constexpr size_t kVerticalPixelLsbOffset = 5;
277 static constexpr size_t kVerticalPixelMsbOffset = 7;
278
279 const uint8_t hLsb = view[kHorizontalPixelLsbOffset];
280 const uint8_t hMsb = view[kHorizontalPixelMsbOffset];
281 const int32_t hPixel = hLsb + ((hMsb & 0xF0) << 4);
282
283 const uint8_t vLsb = view[kVerticalPixelLsbOffset];
284 const uint8_t vMsb = view[kVerticalPixelMsbOffset];
285 const int32_t vPixel = vLsb + ((vMsb & 0xF0) << 4);
286
287 preferredDTDPixelSize.setWidth(hPixel);
288 preferredDTDPixelSize.setHeight(vPixel);
289 preferredDTDPhysicalSize.setWidth(hSize);
290 preferredDTDPhysicalSize.setHeight(vSize);
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700291 }
292
Ryan Prichard3b17f282024-02-08 02:31:50 -0800293 view = view.subspan(kDescriptorLength);
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700294 }
295
Dominik Laskowski17337962020-03-02 15:51:15 -0800296 std::string_view modelString = displayName;
297
298 if (modelString.empty()) {
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700299 ALOGW("Invalid EDID: falling back to serial number due to missing display name.");
Dominik Laskowski17337962020-03-02 15:51:15 -0800300 modelString = serialNumber;
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700301 }
Dominik Laskowski17337962020-03-02 15:51:15 -0800302 if (modelString.empty()) {
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700303 ALOGW("Invalid EDID: falling back to ASCII text due to missing serial number.");
Dominik Laskowski17337962020-03-02 15:51:15 -0800304 modelString = asciiText;
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700305 }
Dominik Laskowski17337962020-03-02 15:51:15 -0800306 if (modelString.empty()) {
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700307 ALOGE("Invalid EDID: display name and fallback descriptors are missing.");
308 return {};
309 }
310
Dominik Laskowski17337962020-03-02 15:51:15 -0800311 // Hash model string instead of using product code or (integer) serial number, since the latter
Jason Macnak4afe8572021-07-16 13:57:41 -0700312 // have been observed to change on some displays with multiple inputs. Use a stable hash instead
313 // of std::hash which is only required to be same within a single execution of a program.
Alan Dingc3ccff12024-04-29 00:44:31 -0700314 const uint32_t modelHash = static_cast<uint32_t>(*ftl::stable_hash(modelString));
Dominik Laskowski17337962020-03-02 15:51:15 -0800315
Marin Shalamanov7a9ba302020-03-02 17:49:16 +0100316 // Parse extension blocks.
317 std::optional<Cea861ExtensionBlock> cea861Block;
318 if (edid.size() < kEdidBlockSize) {
319 ALOGW("Invalid EDID: block 0 is truncated.");
320 } else {
321 constexpr size_t kNumExtensionsOffset = 126;
322 const size_t numExtensions = edid[kNumExtensionsOffset];
323 view = byte_view(edid.data(), edid.size());
324 for (size_t blockNumber = 1; blockNumber <= numExtensions; blockNumber++) {
Ryan Prichard3b17f282024-02-08 02:31:50 -0800325 view = view.subspan(kEdidBlockSize);
326 if (static_cast<size_t>(view.size()) < kEdidBlockSize) {
Marin Shalamanov7a9ba302020-03-02 17:49:16 +0100327 ALOGW("Invalid EDID: block %zu is truncated.", blockNumber);
328 break;
329 }
330
331 const byte_view block(view.data(), kEdidBlockSize);
332 ALOGW_IF(std::accumulate(block.begin(), block.end(), static_cast<uint8_t>(0)),
333 "Invalid EDID: block %zu does not checksum.", blockNumber);
334 const uint8_t tag = block[0];
335
336 constexpr uint8_t kCea861BlockTag = 0x2;
337 if (tag == kCea861BlockTag) {
338 cea861Block = parseCea861Block(block);
339 } else {
340 ALOGV("Ignoring block number %zu with tag %x.", blockNumber, tag);
341 }
342 }
343 }
344
Lucas Berthou8d0a0c42024-08-27 14:32:31 +0000345 DetailedTimingDescriptor preferredDetailedTimingDescriptor{
346 .pixelSizeCount = preferredDTDPixelSize,
347 .physicalSizeInMm = preferredDTDPhysicalSize,
348 };
349
350 return Edid{
351 .manufacturerId = manufacturerId,
352 .productId = productId,
353 .pnpId = *pnpId,
354 .modelHash = modelHash,
355 .displayName = displayName,
356 .manufactureOrModelYear = manufactureOrModelYear,
357 .manufactureWeek = manufactureWeek,
Gil Dekel3e96f942024-11-13 14:51:24 -0500358 .physicalSizeInCm = maxPhysicalSizeInCm,
Lucas Berthou8d0a0c42024-08-27 14:32:31 +0000359 .cea861Block = cea861Block,
360 .preferredDetailedTimingDescriptor = preferredDetailedTimingDescriptor,
361 };
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700362}
363
364std::optional<PnpId> getPnpId(uint16_t manufacturerId) {
365 const char a = getPnpLetter<0>(manufacturerId);
366 const char b = getPnpLetter<1>(manufacturerId);
367 const char c = getPnpLetter<2>(manufacturerId);
368 return a && b && c ? std::make_optional(PnpId{a, b, c}) : std::nullopt;
369}
370
Marin Shalamanova524a092020-07-27 21:39:55 +0200371std::optional<PnpId> getPnpId(PhysicalDisplayId displayId) {
372 return getPnpId(displayId.getManufacturerId());
Dominik Laskowski34157762018-10-31 13:07:19 -0700373}
374
Dominik Laskowski075d3172018-05-24 15:50:06 -0700375std::optional<DisplayIdentificationInfo> parseDisplayIdentificationData(
376 uint8_t port, const DisplayIdentificationData& data) {
Brian Lindahl8a96ef92024-05-24 14:46:29 +0000377 if (data.empty()) {
378 ALOGI("Display identification data is empty.");
379 return {};
380 }
381
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700382 if (!isEdid(data)) {
383 ALOGE("Display identification data has unknown format.");
384 return {};
385 }
386
387 const auto edid = parseEdid(data);
388 if (!edid) {
389 return {};
390 }
391
Marin Shalamanova524a092020-07-27 21:39:55 +0200392 const auto displayId = PhysicalDisplayId::fromEdid(port, edid->manufacturerId, edid->modelHash);
Lucas Berthou8d0a0c42024-08-27 14:32:31 +0000393 return DisplayIdentificationInfo{
394 .id = displayId,
395 .name = std::string(edid->displayName),
396 .deviceProductInfo = buildDeviceProductInfo(*edid),
397 .preferredDetailedTimingDescriptor = edid->preferredDetailedTimingDescriptor,
398 };
Dominik Laskowski075d3172018-05-24 15:50:06 -0700399}
400
Marin Shalamanova524a092020-07-27 21:39:55 +0200401PhysicalDisplayId getVirtualDisplayId(uint32_t id) {
402 return PhysicalDisplayId::fromEdid(0, kVirtualEdidManufacturerId, id);
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700403}
404
Yi Kong93926f62024-02-20 00:39:46 +0800405} // namespace android