| // Copyright 2017 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/graphics/display/lib/edid/edid.h" |
| |
| #include <lib/driver/logging/cpp/logger.h> |
| #include <lib/fit/result.h> |
| #include <lib/stdcompat/span.h> |
| #include <lib/zx/result.h> |
| #include <zircon/assert.h> |
| #include <zircon/errors.h> |
| |
| #include <algorithm> |
| #include <cmath> |
| #include <cstddef> |
| #include <cstdint> |
| #include <cstdio> |
| #include <cstring> |
| #include <iterator> |
| #include <optional> |
| #include <sstream> |
| #include <string> |
| #include <string_view> |
| #include <utility> |
| |
| #include <fbl/alloc_checker.h> |
| #include <fbl/vector.h> |
| |
| #include "src/graphics/display/lib/api-types/cpp/display-timing.h" |
| #include "src/graphics/display/lib/edid/eisa_vid_lut.h" |
| #include "src/graphics/display/lib/edid/timings.h" |
| |
| namespace { |
| |
| template <typename T> |
| bool base_validate(const T* block) { |
| static_assert(sizeof(T) == edid::kBlockSize, "Size check for Edid struct"); |
| |
| const uint8_t* edid_bytes = reinterpret_cast<const uint8_t*>(block); |
| if (edid_bytes[0] != T::kTag) { |
| return false; |
| } |
| |
| // The last byte of the 128-byte EDID data is a checksum byte which |
| // should make the 128 bytes sum to zero. |
| uint8_t sum = 0; |
| for (uint32_t i = 0; i < edid::kBlockSize; ++i) { |
| sum = static_cast<uint8_t>(sum + edid_bytes[i]); |
| } |
| return sum == 0; |
| } |
| |
| } // namespace |
| |
| namespace edid { |
| |
| namespace { |
| |
| // Unpacks the ID Manufacturer Name Field specified in the base EDID. |
| // |
| // The ID Manufacturer name is a 3-character code containing three upper case |
| // letters. They are encoded in the base EDID byte 08h and 09h based on a 5-bit |
| // compressed ASCII code. |
| // |
| // E-EDID standard Section 3.4.1 "ID Manufacture Name", page 21. |
| std::string UnpackIdManufacturerName(uint8_t byte_08h, uint8_t byte_09h) { |
| int compressed_character1 = (byte_08h & 0b01111100) >> 2; |
| int compressed_character2 = ((byte_08h & 0b00000011) << 3) | ((byte_09h & 0b11100000) >> 5); |
| int compressed_character3 = byte_09h & 0b0011111; |
| |
| // Some EDIDs may contain invalid manufacturer name codes. We replace the |
| // invalid characters with the fallback character 'A'. |
| if (compressed_character1 < 1 || compressed_character1 > 26) { |
| fdf::warn("Invalid manufacturer name code character #1: {}", compressed_character1); |
| compressed_character1 = 1; |
| } |
| if (compressed_character2 < 1 || compressed_character2 > 26) { |
| fdf::warn("Invalid manufacturer name code character #2: {}", compressed_character2); |
| compressed_character2 = 1; |
| } |
| if (compressed_character3 < 1 || compressed_character3 > 26) { |
| fdf::warn("Invalid manufacturer name code character #3: {}", compressed_character3); |
| compressed_character3 = 1; |
| } |
| |
| // The cast won't overflow because the compressed_character values are |
| // guaranteed to be in the range [1, 26]. |
| const char characters[3] = { |
| static_cast<char>(compressed_character1 + 'A' - 1), |
| static_cast<char>(compressed_character2 + 'A' - 1), |
| static_cast<char>(compressed_character3 + 'A' - 1), |
| }; |
| return std::string(characters, std::size(characters)); |
| } |
| |
| bool IsDisplayDescriptor(const Descriptor& descriptor) { |
| // A Descriptor can be either a Detailed Timing Descriptor or a Display |
| // Descriptor. For Display Descriptors, its first two bytes must be 0x0000, |
| // while for Detailed Timing Descriptors the first two bytes |
| // (`pixel_clock_10khz`) must not be 0x0000. |
| // |
| // Accessing any field within the union before figuring out its underlying |
| // type is an undefined behavior in C++. So we use reinterpret_cast to access |
| // its first two bytes. |
| const uint8_t* descriptor_bytes = reinterpret_cast<const uint8_t*>(&descriptor); |
| |
| // sizeof(Descriptor) > 2, so it's always valid to access the first two bytes. |
| const uint8_t descriptor_type_indicator_low_byte = *descriptor_bytes; |
| const uint8_t descriptor_type_indicator_high_byte = *(descriptor_bytes + 1); |
| return descriptor_type_indicator_low_byte == 0x00 && descriptor_type_indicator_high_byte == 0x00; |
| } |
| |
| } // namespace |
| |
| const char* GetEisaVendorName(uint16_t manufacturer_name_code) { |
| uint8_t c1 = static_cast<uint8_t>((((manufacturer_name_code >> 8) & 0x7c) >> 2) + 'A' - 1); |
| uint8_t c2 = static_cast<uint8_t>( |
| ((((manufacturer_name_code >> 8) & 0x03) << 3) | (manufacturer_name_code & 0xe0) >> 5) + 'A' - |
| 1); |
| uint8_t c3 = static_cast<uint8_t>(((manufacturer_name_code & 0x1f)) + 'A' - 1); |
| return lookup_eisa_vid(EISA_ID(c1, c2, c3)); |
| } |
| |
| bool BaseEdid::validate() const { |
| static const uint8_t kEdidHeader[8] = {0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0}; |
| return base_validate<BaseEdid>(this) && memcmp(header, kEdidHeader, sizeof(kEdidHeader)) == 0; |
| } |
| |
| bool CeaEdidTimingExtension::validate() const { |
| if (!(dtd_start_idx <= sizeof(payload) && base_validate<CeaEdidTimingExtension>(this))) { |
| return false; |
| } |
| |
| // If this is zero, there is no DTDs present and no non-DTD data. |
| if (dtd_start_idx == 0) { |
| return true; |
| } |
| |
| if (dtd_start_idx > 0 && dtd_start_idx < offsetof(CeaEdidTimingExtension, payload)) { |
| return false; |
| } |
| |
| size_t offset = 0; |
| size_t dbc_end = dtd_start_idx - offsetof(CeaEdidTimingExtension, payload); |
| while (offset < dbc_end) { |
| const DataBlock* data_block = reinterpret_cast<const DataBlock*>(payload + offset); |
| offset += (1 + data_block->length()); // Length doesn't include the header |
| // Check that the block doesn't run past the end if the dbc |
| if (offset > dbc_end) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| // static |
| fit::result<const char*, Edid> Edid::Create(cpp20::span<const uint8_t> bytes) { |
| if (bytes.empty()) { |
| return fit::error("EDID is empty"); |
| } |
| if (bytes.size() < kBlockSize) { |
| return fit::error("EDID size is too small (no room for the base block)"); |
| } |
| |
| // Engine drivers may report zero-padded EDID data. Work around this issue by |
| // computing the EDID size based on the extension block count in the EDID base |
| // block. |
| const BaseEdid* base_edid = reinterpret_cast<const BaseEdid*>(bytes.data()); |
| |
| // The addition will not overflow (causing UB) because `num_extension` is a |
| // 1-byte field. The maximum addition result is 256. |
| const int total_edid_block_count = 1 + base_edid->num_extensions; |
| |
| // The multiplication will not overflow because of the quantities involved. |
| // `kBlockSize` is 128 and `total_edid_block_count` is at most 256, so the |
| // multiplication result is at most 32,768. |
| const size_t declared_edid_size = size_t{kBlockSize} * total_edid_block_count; |
| if (declared_edid_size > bytes.size()) { |
| return fit::error("EDID size based on declared block count exceeds byte buffer size"); |
| } |
| cpp20::span<const uint8_t> valid_edid_bytes = bytes.subspan(0, declared_edid_size); |
| |
| // The maximum EDID block count is based on the fact that the size field is stored in a |
| // byte. |
| ZX_DEBUG_ASSERT_MSG(valid_edid_bytes.size() <= size_t{kBlockSize} * kMaxEdidBlockCount, |
| "Maximum EDID size reasoning was incorrect"); |
| |
| fbl::Vector<uint8_t> edid_bytes_buffer; |
| fbl::AllocChecker alloc_checker; |
| edid_bytes_buffer.resize(valid_edid_bytes.size(), 0, &alloc_checker); |
| if (!alloc_checker.check()) { |
| return fit::error("Failed to allocate memory for EDID"); |
| } |
| std::copy(valid_edid_bytes.begin(), valid_edid_bytes.end(), edid_bytes_buffer.begin()); |
| |
| Edid edid(std::move(edid_bytes_buffer)); |
| fit::result<const char*> validate_result = edid.Validate(); |
| if (validate_result.is_error()) { |
| return validate_result.take_error(); |
| } |
| return fit::ok(std::move(edid)); |
| } |
| |
| Edid::Edid(fbl::Vector<uint8_t> bytes) : bytes_(std::move(bytes)) { |
| ZX_DEBUG_ASSERT(!bytes_.is_empty()); |
| ZX_DEBUG_ASSERT(bytes_.size() % kBlockSize == 0); |
| ZX_DEBUG_ASSERT(bytes_.size() <= size_t{kBlockSize} * kMaxEdidBlockCount); |
| } |
| |
| std::string Edid::GetManufacturerId() const { |
| const BaseEdid& base = base_edid(); |
| return UnpackIdManufacturerName(base.manufacturer_id1, base.manufacturer_id2); |
| } |
| |
| const char* Edid::GetManufacturerName() const { |
| std::string manufacturer_id = GetManufacturerId(); |
| return lookup_eisa_vid(EISA_ID(manufacturer_id[0], manufacturer_id[1], manufacturer_id[2])); |
| } |
| |
| std::string Edid::GetDisplayProductName() const { |
| for (auto it = internal::descriptor_iterator(this); it.is_valid(); ++it) { |
| const Descriptor* descriptor_raw = it.get(); |
| ZX_DEBUG_ASSERT(descriptor_raw != nullptr); |
| |
| // `descriptor` may not be aligned correctly. Copy the bytes to a locally- |
| // constructed `Descriptor` first. |
| Descriptor descriptor; |
| memcpy(&descriptor, descriptor_raw, sizeof(Descriptor)); |
| |
| if (!IsDisplayDescriptor(descriptor)) { |
| continue; |
| } |
| |
| const Descriptor::Monitor& display_descriptor = descriptor.monitor; |
| if (display_descriptor.type == Descriptor::Monitor::kName) { |
| std::string_view name(reinterpret_cast<const char*>(display_descriptor.data), |
| std::size(display_descriptor.data)); |
| |
| // The E-EDID standard requires that the display product name data string |
| // is terminated with ASCII code 0Ah (line feed) if there are less than |
| // 13 characters in the string. Thus, we truncate the string at the first |
| // line feed character. |
| // |
| // E-EDID standard, Section 3.10.3.4 "Display Product Name (ASCII) String |
| // Descriptor Definition", page 44. |
| static constexpr char kStringTerminatorCharacter = 0x0a; |
| size_t terminator_pos = name.find(kStringTerminatorCharacter); |
| std::string_view actual_name = name.substr(/*pos=*/0, /*n=*/terminator_pos); |
| |
| return std::string(actual_name); |
| } |
| } |
| |
| // No display product name is provided in the Display Descriptors. Return |
| // an empty string. |
| return {}; |
| } |
| |
| std::string Edid::GetDisplayProductSerialNumber() const { |
| for (auto it = internal::descriptor_iterator(this); it.is_valid(); ++it) { |
| const Descriptor* descriptor_raw = it.get(); |
| ZX_DEBUG_ASSERT(descriptor_raw != nullptr); |
| |
| // `descriptor` may not be aligned correctly. Copy the bytes to a locally- |
| // constructed `Descriptor` first. |
| Descriptor descriptor; |
| memcpy(&descriptor, descriptor_raw, sizeof(Descriptor)); |
| |
| if (!IsDisplayDescriptor(descriptor)) { |
| continue; |
| } |
| |
| const Descriptor::Monitor& display_descriptor = descriptor.monitor; |
| if (display_descriptor.type == Descriptor::Monitor::kSerial) { |
| // The E-EDID standard requires that the serial number data string must be |
| // ASCII-encoded. So `data` can be directly casted to a string_view. |
| // |
| // E-EDID standard, Section 3.10.3.1 "Display Product Serial Number |
| // Descriptor Definition", page 38. |
| std::string_view serial_number(reinterpret_cast<const char*>(display_descriptor.data), |
| std::size(display_descriptor.data)); |
| |
| // The E-EDID standard requires that the serial number data string must be |
| // terminated with ASCII code 0Ah (line feed) if there are less than 13 |
| // characters in the string. |
| // |
| // E-EDID standard, Section 3.10.3.1 "Display Product Serial Number |
| // Descriptor Definition", page 38. |
| static constexpr char kStringTerminatorCharacter = 0x0a; |
| size_t terminator_pos = serial_number.find(kStringTerminatorCharacter); |
| std::string_view actual_serial_number = serial_number.substr(/*pos=*/0, /*n=*/terminator_pos); |
| |
| return std::string(actual_serial_number); |
| } |
| } |
| |
| // No display product serial number is provided in the Display Descriptors. |
| // Fall back to the "ID Serial Number" field defined in the base EDID. |
| // |
| // E-EDID standard, Section 3.4.3 "ID Serial Number", page 22. |
| std::ostringstream fallback_serial_number; |
| const BaseEdid& base = base_edid(); |
| |
| fallback_serial_number << base.serial_number; |
| return fallback_serial_number.str(); |
| } |
| |
| fit::result<const char*> Edid::Validate() { |
| const BaseEdid& base = base_edid(); |
| if (!base.validate()) { |
| return fit::error("Failed to validate base edid"); |
| } |
| if (((base.num_extensions + 1) * kBlockSize) != bytes_.size()) { |
| return fit::error("Bad extension count"); |
| } |
| if (!base.digital()) { |
| return fit::error("Analog displays not supported"); |
| } |
| |
| for (uint8_t i = 1; i < bytes_.size() / kBlockSize; i++) { |
| if (bytes_[i * kBlockSize] == CeaEdidTimingExtension::kTag) { |
| if (!GetBlock<CeaEdidTimingExtension>(i)->validate()) { |
| return fit::error("Failed to validate extensions"); |
| } |
| } |
| } |
| return fit::ok(); |
| } |
| |
| int Edid::horizontal_size_mm() const { |
| static constexpr int kMillimetersPerCentimeter = 10; |
| // The multiplication result meets the API contract because |
| // `horizontal_size_cm` is at most 255. |
| return int{base_edid().horizontal_size_cm} * kMillimetersPerCentimeter; |
| } |
| |
| int Edid::vertical_size_mm() const { |
| static constexpr int kMillimetersPerCentimeter = 10; |
| // The multiplication result meets the API contract because |
| // `vertical_size_cm` is at most 255. |
| return int{base_edid().vertical_size_cm} * kMillimetersPerCentimeter; |
| } |
| |
| bool Edid::is_hdmi() const { |
| internal::data_block_iterator dbs(this); |
| if (!dbs.is_valid() || dbs.cea_revision() < 0x03) { |
| return false; |
| } |
| |
| do { |
| if (dbs->type() == VendorSpecificBlock::kType) { |
| // HDMI's 24-bit IEEE registration is 0x000c03 - vendor_number is little endian |
| if (dbs->payload.vendor.vendor_number[0] == 0x03 && |
| dbs->payload.vendor.vendor_number[1] == 0x0c && |
| dbs->payload.vendor.vendor_number[2] == 0x00) { |
| return true; |
| } |
| } |
| } while ((++dbs).is_valid()); |
| return false; |
| } |
| |
| display::DisplayTiming DetailedTimingDescriptorToDisplayTiming( |
| const DetailedTimingDescriptor& dtd) { |
| // A valid DetailedTimingDescriptor guarantees that |
| // horizontal_blanking >= horizontal_front_porch + horizontal_sync_pulse, and |
| // all of them are non-negative and fit in [0, kMaxTimingValue]. |
| // |
| // This constraint guarantees that |
| // horizontal_blanking - (horizontal_front_porch + horizontal_sync_pulse) |
| // will also fit in [0, kMaxTimingValue]; the calculation won't overflow, |
| // causing undefined behaviors. |
| int32_t horizontal_back_porch_px = |
| static_cast<int32_t>(dtd.horizontal_blanking() - |
| (dtd.horizontal_front_porch() + dtd.horizontal_sync_pulse_width())); |
| |
| // A similar argument holds for the vertical back porch. |
| int32_t vertical_back_porch_lines = static_cast<int32_t>( |
| dtd.vertical_blanking() - (dtd.vertical_front_porch() + dtd.vertical_sync_pulse_width())); |
| |
| if (dtd.type() != TYPE_DIGITAL_SEPARATE) { |
| // TODO(https://fxbug.dev/42086615): Displays using composite syncs are not |
| // supported. We treat them as if they were using separate sync signals. |
| fdf::warn("The detailed timing descriptor uses composite sync; this is not supported."); |
| } |
| |
| return display::DisplayTiming{ |
| .horizontal_active_px = static_cast<int32_t>(dtd.horizontal_addressable()), |
| .horizontal_front_porch_px = static_cast<int32_t>(dtd.horizontal_front_porch()), |
| .horizontal_sync_width_px = static_cast<int32_t>(dtd.horizontal_sync_pulse_width()), |
| .horizontal_back_porch_px = horizontal_back_porch_px, |
| |
| .vertical_active_lines = static_cast<int32_t>(dtd.vertical_addressable()), |
| .vertical_front_porch_lines = static_cast<int32_t>(dtd.vertical_front_porch()), |
| .vertical_sync_width_lines = static_cast<int32_t>(dtd.vertical_sync_pulse_width()), |
| .vertical_back_porch_lines = vertical_back_porch_lines, |
| |
| .pixel_clock_frequency_hz = int64_t{dtd.pixel_clock_10khz} * 10'000, |
| .fields_per_frame = dtd.interlaced() ? display::FieldsPerFrame::kInterlaced |
| : display::FieldsPerFrame::kProgressive, |
| .hsync_polarity = dtd.hsync_polarity() ? display::SyncPolarity::kPositive |
| : display::SyncPolarity::kNegative, |
| .vsync_polarity = dtd.vsync_polarity() ? display::SyncPolarity::kPositive |
| : display::SyncPolarity::kNegative, |
| .vblank_alternates = false, |
| .pixel_repetition = 0, |
| }; |
| } |
| |
| // Returns std::nullopt if the standard timing descriptor `std` is invalid or |
| // unsupported. |
| // Otherwise, returns the DisplayTiming converted from the descriptor. |
| std::optional<display::DisplayTiming> StandardTimingDescriptorToDisplayTiming( |
| const BaseEdid& edid, const StandardTimingDescriptor& std) { |
| // Pick the largest resolution advertised by the display and then use the |
| // generalized timing formula to compute the timing parameters. |
| // TODO(stevensd): Support interlaced modes and margins |
| int32_t width = static_cast<int32_t>(std.horizontal_resolution()); |
| int32_t height = |
| static_cast<int32_t>(std.vertical_resolution(edid.edid_version, edid.edid_revision)); |
| int32_t v_rate = static_cast<int32_t>(std.vertical_freq()) + 60; |
| |
| if (!width || !height || !v_rate) { |
| fdf::warn("Invalid standard timing descriptor: {} x {} @ {} Hz", width, height, v_rate); |
| return std::nullopt; |
| } |
| |
| for (const display::DisplayTiming& dmt_timing : internal::kDmtDisplayTimings) { |
| if (dmt_timing.horizontal_active_px == width && dmt_timing.vertical_active_lines == height && |
| ((dmt_timing.vertical_field_refresh_rate_millihertz() + 500) / 1000) == v_rate) { |
| return dmt_timing; |
| } |
| } |
| |
| fdf::warn( |
| "This EDID contains a non-DMT standard timing ({} x {} @ {} Hz). The timing " |
| "is not supported and will be ignored. See https://fxbug.dev/42085380 for details.", |
| width, height, v_rate); |
| return std::nullopt; |
| } |
| |
| timing_iterator& timing_iterator::operator++() { |
| while (state_ != kDone) { |
| Advance(); |
| // If either of these are 0, then the timing value is definitely wrong |
| if (display_timing_.vertical_active_lines != 0 && display_timing_.horizontal_active_px != 0) { |
| break; |
| } |
| } |
| return *this; |
| } |
| |
| void timing_iterator::Advance() { |
| if (state_ == kDtds) { |
| while (descriptors_.is_valid()) { |
| if (descriptors_->timing.pixel_clock_10khz != 0) { |
| display_timing_ = DetailedTimingDescriptorToDisplayTiming(descriptors_->timing); |
| ++descriptors_; |
| return; |
| } |
| ++descriptors_; |
| } |
| state_ = kSvds; |
| state_index_ = UINT16_MAX; |
| } |
| |
| if (state_ == kSvds) { |
| while (dbs_.is_valid()) { |
| if (dbs_->type() == ShortVideoDescriptor::kType) { |
| state_index_++; |
| uint32_t modes_to_skip = state_index_; |
| for (unsigned i = 0; i < dbs_->length(); i++) { |
| uint32_t idx = dbs_->payload.video[i].standard_mode_idx() - 1; |
| if (idx >= internal::kCtaDisplayTimings.size()) { |
| continue; |
| } |
| if (modes_to_skip == 0) { |
| display_timing_ = internal::kCtaDisplayTimings[idx]; |
| return; |
| } |
| |
| // For timings with refresh rates that are multiples of 6, there are |
| // corresponding timings adjusted by a factor of 1000/1001. |
| // |
| // TODO(https://fxbug.dev/42086617): Revise the refresh rate adjustment |
| // logic to make sure that it complies with the CTA-861 standards. |
| uint32_t rounded_refresh = |
| (internal::kCtaDisplayTimings[idx].vertical_field_refresh_rate_millihertz() + 999) / |
| 1000; |
| if (rounded_refresh % 6 == 0) { |
| if (modes_to_skip == 1) { |
| display_timing_ = internal::kCtaDisplayTimings[idx]; |
| // `pixel_clock_frequency_hz` is less than 2^41, and `double` has |
| // 51 fractional bits, so `pixel_clock_frequency_hz` can be |
| // converted to a double value without losing precision. |
| double clock = static_cast<double>(display_timing_.pixel_clock_frequency_hz); |
| // 240/480 height entries are already multiplied by 1000/1001 |
| double mult = display_timing_.vertical_active_lines == 240 || |
| display_timing_.vertical_active_lines == 480 |
| ? 1.001 |
| : (1000. / 1001.); |
| display_timing_.pixel_clock_frequency_hz = static_cast<int64_t>(round(clock * mult)); |
| return; |
| } |
| modes_to_skip -= 2; |
| } else { |
| modes_to_skip--; |
| } |
| } |
| } |
| |
| ++dbs_; |
| // Reset the index for either the next SVD block or the STDs. |
| state_index_ = UINT16_MAX; |
| } |
| |
| state_ = kStds; |
| } |
| |
| if (state_ == kStds) { |
| while (++state_index_ < std::size(edid_->base_edid().standard_timings)) { |
| const StandardTimingDescriptor* desc = edid_->base_edid().standard_timings + state_index_; |
| if (desc->byte1 == 0x01 && desc->byte2 == 0x01) { |
| continue; |
| } |
| std::optional<display::DisplayTiming> display_timing = |
| StandardTimingDescriptorToDisplayTiming(edid_->base_edid(), *desc); |
| |
| // A VESA Standard Timing Descriptor may be unsupported. In that case, |
| // we should skip the timing and continue iterating through the standard |
| // timing descriptors list. |
| if (display_timing.has_value()) { |
| display_timing_ = *display_timing; |
| } else { |
| continue; |
| } |
| |
| // Early return to update the display timing returned while keeping the |
| // current `state_`. `state_` changes only when all timings have been |
| // iterated through. |
| return; |
| } |
| |
| state_ = kDone; |
| } |
| } |
| |
| void Edid::Print(void (*print_fn)(const char* str)) const { |
| char str_buf[128]; |
| print_fn("Raw edid:\n"); |
| for (size_t i = 0; i < edid_length(); i++) { |
| constexpr int kBytesPerLine = 16; |
| char* b = str_buf; |
| if (i % kBytesPerLine == 0) { |
| b += sprintf(b, "%04zx: ", i); |
| } |
| sprintf(b, "%02x%s", edid_bytes()[i], i % kBytesPerLine == kBytesPerLine - 1 ? "\n" : " "); |
| print_fn(str_buf); |
| } |
| } |
| |
| bool Edid::supports_basic_audio() const { |
| uint8_t block_idx = 1; // Skip block 1, since it can't be a CEA block |
| const int num_blocks = static_cast<int>(bytes_.size() / kBlockSize); |
| while (block_idx < num_blocks) { |
| auto cea_extn_block = GetBlock<CeaEdidTimingExtension>(block_idx); |
| if (cea_extn_block && cea_extn_block->revision_number >= 2) { |
| return cea_extn_block->basic_audio(); |
| } |
| block_idx++; |
| } |
| return false; |
| } |
| |
| zx::result<fbl::Vector<display::DisplayTiming>> Edid::GetSupportedDisplayTimings() const { |
| fbl::Vector<display::DisplayTiming> timings; |
| |
| size_t max_timing_count = 0; |
| for (auto it = timing_iterator(this); it.is_valid(); ++it) { |
| ++max_timing_count; |
| } |
| |
| fbl::AllocChecker alloc_checker; |
| timings.reserve(max_timing_count, &alloc_checker); |
| if (!alloc_checker.check()) { |
| return zx::error(ZX_ERR_NO_MEMORY); |
| } |
| |
| // `timing_iterator` outputs display timing parameters in the following order: |
| // - Detailed Timing Descriptors (DTDs) in the base EDID block (block 0) |
| // - DTDs in the CTA-861 extension blocks |
| // - Short Video Descriptors (SVDs) in the CTA-861 extension blocks |
| // - Standard timings in the base EDID block |
| // |
| // VESA E-EDID Standard (Release A, Rev. 2, page 28) specifies that the |
| // preferred timing mode is the first DTD in the base EDID. |
| // |
| // However, CTA-861 (CTA-861-I 7.2.2) specifies that the preferred video |
| // formats are specified by: |
| // - the Video Format Preference Data Block if it exists, or |
| // - the first DTD in the base EDID block and the first SVD in the CTA-861 |
| // extension block. |
| // |
| // Our current implementation only guarantees that the first timing is from |
| // the first DTD, which matches VESA E-EDID's definition of preferred modes, |
| // but doesn't always match the definition from CTA-861. |
| // TODO(https://fxbug.dev/438953308): Comply with CTA-861 on mode preferences. |
| for (auto it = timing_iterator(this); it.is_valid(); ++it) { |
| display::DisplayTiming new_timing = *it; |
| if (std::ranges::find(timings, new_timing) == timings.end()) { |
| ZX_DEBUG_ASSERT_MSG( |
| timings.size() < max_timing_count, |
| "The push_back() below was not supposed to allocate memory, but it might"); |
| timings.push_back(*it, &alloc_checker); |
| ZX_DEBUG_ASSERT_MSG(alloc_checker.check(), |
| "The push_back() above failed to allocate memory; " |
| "it was not supposed to allocate at all"); |
| } |
| } |
| return zx::ok(std::move(timings)); |
| } |
| |
| } // namespace edid |