blob: 8b13d788400f7be94d652258b324145247792c1f [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
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700215 constexpr size_t kDescriptorOffset = 54;
216 if (edid.size() < kDescriptorOffset) {
217 ALOGE("Invalid EDID: descriptors are missing.");
218 return {};
219 }
220
221 byte_view view(edid.data(), edid.size());
Ryan Prichard3b17f282024-02-08 02:31:50 -0800222 view = view.subspan(kDescriptorOffset);
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700223
224 std::string_view displayName;
225 std::string_view serialNumber;
226 std::string_view asciiText;
Lucas Berthou8d0a0c42024-08-27 14:32:31 +0000227 ui::Size preferredDTDPixelSize;
228 ui::Size preferredDTDPhysicalSize;
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700229
230 constexpr size_t kDescriptorCount = 4;
231 constexpr size_t kDescriptorLength = 18;
232
233 for (size_t i = 0; i < kDescriptorCount; i++) {
Ryan Prichard3b17f282024-02-08 02:31:50 -0800234 if (static_cast<size_t>(view.size()) < kDescriptorLength) {
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700235 break;
236 }
237
238 if (const auto type = getEdidDescriptorType(view)) {
239 byte_view descriptor(view.data(), kDescriptorLength);
Ryan Prichard3b17f282024-02-08 02:31:50 -0800240 descriptor = descriptor.subspan(kEdidHeaderLength);
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700241
242 switch (*type) {
243 case 0xfc:
244 displayName = parseEdidText(descriptor);
245 break;
246 case 0xfe:
247 asciiText = parseEdidText(descriptor);
248 break;
249 case 0xff:
250 serialNumber = parseEdidText(descriptor);
251 break;
252 }
Lucas Berthou8d0a0c42024-08-27 14:32:31 +0000253 } else if (isDetailedTimingDescriptor(view)) {
254 static constexpr size_t kHorizontalPhysicalLsbOffset = 12;
255 static constexpr size_t kHorizontalPhysicalMsbOffset = 14;
256 static constexpr size_t kVerticalPhysicalLsbOffset = 13;
257 static constexpr size_t kVerticalPhysicalMsbOffset = 14;
258 const uint32_t hSize =
259 static_cast<uint32_t>(view[kHorizontalPhysicalLsbOffset] |
260 ((view[kHorizontalPhysicalMsbOffset] >> 4) << 8));
261 const uint32_t vSize =
262 static_cast<uint32_t>(view[kVerticalPhysicalLsbOffset] |
263 ((view[kVerticalPhysicalMsbOffset] & 0b1111) << 8));
264
265 static constexpr size_t kHorizontalPixelLsbOffset = 2;
266 static constexpr size_t kHorizontalPixelMsbOffset = 4;
267 static constexpr size_t kVerticalPixelLsbOffset = 5;
268 static constexpr size_t kVerticalPixelMsbOffset = 7;
269
270 const uint8_t hLsb = view[kHorizontalPixelLsbOffset];
271 const uint8_t hMsb = view[kHorizontalPixelMsbOffset];
272 const int32_t hPixel = hLsb + ((hMsb & 0xF0) << 4);
273
274 const uint8_t vLsb = view[kVerticalPixelLsbOffset];
275 const uint8_t vMsb = view[kVerticalPixelMsbOffset];
276 const int32_t vPixel = vLsb + ((vMsb & 0xF0) << 4);
277
278 preferredDTDPixelSize.setWidth(hPixel);
279 preferredDTDPixelSize.setHeight(vPixel);
280 preferredDTDPhysicalSize.setWidth(hSize);
281 preferredDTDPhysicalSize.setHeight(vSize);
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700282 }
283
Ryan Prichard3b17f282024-02-08 02:31:50 -0800284 view = view.subspan(kDescriptorLength);
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700285 }
286
Dominik Laskowski17337962020-03-02 15:51:15 -0800287 std::string_view modelString = displayName;
288
289 if (modelString.empty()) {
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700290 ALOGW("Invalid EDID: falling back to serial number due to missing display name.");
Dominik Laskowski17337962020-03-02 15:51:15 -0800291 modelString = serialNumber;
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700292 }
Dominik Laskowski17337962020-03-02 15:51:15 -0800293 if (modelString.empty()) {
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700294 ALOGW("Invalid EDID: falling back to ASCII text due to missing serial number.");
Dominik Laskowski17337962020-03-02 15:51:15 -0800295 modelString = asciiText;
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700296 }
Dominik Laskowski17337962020-03-02 15:51:15 -0800297 if (modelString.empty()) {
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700298 ALOGE("Invalid EDID: display name and fallback descriptors are missing.");
299 return {};
300 }
301
Dominik Laskowski17337962020-03-02 15:51:15 -0800302 // Hash model string instead of using product code or (integer) serial number, since the latter
Jason Macnak4afe8572021-07-16 13:57:41 -0700303 // have been observed to change on some displays with multiple inputs. Use a stable hash instead
304 // of std::hash which is only required to be same within a single execution of a program.
Alan Dingc3ccff12024-04-29 00:44:31 -0700305 const uint32_t modelHash = static_cast<uint32_t>(*ftl::stable_hash(modelString));
Dominik Laskowski17337962020-03-02 15:51:15 -0800306
Marin Shalamanov7a9ba302020-03-02 17:49:16 +0100307 // Parse extension blocks.
308 std::optional<Cea861ExtensionBlock> cea861Block;
309 if (edid.size() < kEdidBlockSize) {
310 ALOGW("Invalid EDID: block 0 is truncated.");
311 } else {
312 constexpr size_t kNumExtensionsOffset = 126;
313 const size_t numExtensions = edid[kNumExtensionsOffset];
314 view = byte_view(edid.data(), edid.size());
315 for (size_t blockNumber = 1; blockNumber <= numExtensions; blockNumber++) {
Ryan Prichard3b17f282024-02-08 02:31:50 -0800316 view = view.subspan(kEdidBlockSize);
317 if (static_cast<size_t>(view.size()) < kEdidBlockSize) {
Marin Shalamanov7a9ba302020-03-02 17:49:16 +0100318 ALOGW("Invalid EDID: block %zu is truncated.", blockNumber);
319 break;
320 }
321
322 const byte_view block(view.data(), kEdidBlockSize);
323 ALOGW_IF(std::accumulate(block.begin(), block.end(), static_cast<uint8_t>(0)),
324 "Invalid EDID: block %zu does not checksum.", blockNumber);
325 const uint8_t tag = block[0];
326
327 constexpr uint8_t kCea861BlockTag = 0x2;
328 if (tag == kCea861BlockTag) {
329 cea861Block = parseCea861Block(block);
330 } else {
331 ALOGV("Ignoring block number %zu with tag %x.", blockNumber, tag);
332 }
333 }
334 }
335
Lucas Berthou8d0a0c42024-08-27 14:32:31 +0000336 DetailedTimingDescriptor preferredDetailedTimingDescriptor{
337 .pixelSizeCount = preferredDTDPixelSize,
338 .physicalSizeInMm = preferredDTDPhysicalSize,
339 };
340
341 return Edid{
342 .manufacturerId = manufacturerId,
343 .productId = productId,
344 .pnpId = *pnpId,
345 .modelHash = modelHash,
346 .displayName = displayName,
347 .manufactureOrModelYear = manufactureOrModelYear,
348 .manufactureWeek = manufactureWeek,
349 .cea861Block = cea861Block,
350 .preferredDetailedTimingDescriptor = preferredDetailedTimingDescriptor,
351 };
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700352}
353
354std::optional<PnpId> getPnpId(uint16_t manufacturerId) {
355 const char a = getPnpLetter<0>(manufacturerId);
356 const char b = getPnpLetter<1>(manufacturerId);
357 const char c = getPnpLetter<2>(manufacturerId);
358 return a && b && c ? std::make_optional(PnpId{a, b, c}) : std::nullopt;
359}
360
Marin Shalamanova524a092020-07-27 21:39:55 +0200361std::optional<PnpId> getPnpId(PhysicalDisplayId displayId) {
362 return getPnpId(displayId.getManufacturerId());
Dominik Laskowski34157762018-10-31 13:07:19 -0700363}
364
Dominik Laskowski075d3172018-05-24 15:50:06 -0700365std::optional<DisplayIdentificationInfo> parseDisplayIdentificationData(
366 uint8_t port, const DisplayIdentificationData& data) {
Brian Lindahl8a96ef92024-05-24 14:46:29 +0000367 if (data.empty()) {
368 ALOGI("Display identification data is empty.");
369 return {};
370 }
371
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700372 if (!isEdid(data)) {
373 ALOGE("Display identification data has unknown format.");
374 return {};
375 }
376
377 const auto edid = parseEdid(data);
378 if (!edid) {
379 return {};
380 }
381
Marin Shalamanova524a092020-07-27 21:39:55 +0200382 const auto displayId = PhysicalDisplayId::fromEdid(port, edid->manufacturerId, edid->modelHash);
Lucas Berthou8d0a0c42024-08-27 14:32:31 +0000383 return DisplayIdentificationInfo{
384 .id = displayId,
385 .name = std::string(edid->displayName),
386 .deviceProductInfo = buildDeviceProductInfo(*edid),
387 .preferredDetailedTimingDescriptor = edid->preferredDetailedTimingDescriptor,
388 };
Dominik Laskowski075d3172018-05-24 15:50:06 -0700389}
390
Marin Shalamanova524a092020-07-27 21:39:55 +0200391PhysicalDisplayId getVirtualDisplayId(uint32_t id) {
392 return PhysicalDisplayId::fromEdid(0, kVirtualEdidManufacturerId, id);
Dominik Laskowskie9ef7c42018-03-12 19:34:30 -0700393}
394
Yi Kong93926f62024-02-20 00:39:46 +0800395} // namespace android