blob: 1dabea7ad00f51ffcb05fee4eccf2c4ee0aca447 [file] [log] [blame]
// Copyright 2018 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/drivers/amlogic-display/display-engine.h"
#include <fidl/fuchsia.hardware.amlogiccanvas/cpp/wire.h>
#include <fidl/fuchsia.hardware.platform.device/cpp/wire.h>
#include <fidl/fuchsia.hardware.sysmem/cpp/wire.h>
#include <fidl/fuchsia.images2/cpp/fidl.h>
#include <fidl/fuchsia.math/cpp/fidl.h>
#include <fidl/fuchsia.sysmem2/cpp/fidl.h>
#include <fidl/fuchsia.sysmem2/cpp/wire.h>
#include <lib/component/incoming/cpp/constants.h>
#include <lib/ddk/metadata.h>
#include <lib/ddk/platform-defs.h>
#include <lib/device-protocol/display-panel.h>
#include <lib/driver/compat/cpp/metadata.h>
#include <lib/driver/incoming/cpp/namespace.h>
#include <lib/driver/logging/cpp/logger.h>
#include <lib/fit/function.h>
#include <lib/image-format/image_format.h>
#include <lib/sysmem-version/sysmem-version.h>
#include <lib/zx/bti.h>
#include <lib/zx/result.h>
#include <lib/zx/time.h>
#include <zircon/assert.h>
#include <zircon/errors.h>
#include <zircon/limits.h>
#include <zircon/process.h>
#include <zircon/syscalls.h>
#include <zircon/types.h>
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include <bind/fuchsia/amlogic/platform/sysmem/heap/cpp/bind.h>
#include <bind/fuchsia/sysmem/heap/cpp/bind.h>
#include <fbl/algorithm.h>
#include <fbl/alloc_checker.h>
#include <fbl/auto_lock.h>
#include "src/graphics/display/drivers/amlogic-display/board-resources.h"
#include "src/graphics/display/drivers/amlogic-display/capture.h"
#include "src/graphics/display/drivers/amlogic-display/hot-plug-detection.h"
#include "src/graphics/display/drivers/amlogic-display/image-info.h"
#include "src/graphics/display/drivers/amlogic-display/pixel-grid-size2d.h"
#include "src/graphics/display/drivers/amlogic-display/vout-dsi.h"
#include "src/graphics/display/drivers/amlogic-display/vout-hdmi.h"
#include "src/graphics/display/drivers/amlogic-display/vout.h"
#include "src/graphics/display/drivers/amlogic-display/vpu.h"
#include "src/graphics/display/drivers/amlogic-display/vsync-receiver.h"
#include "src/graphics/display/lib/api-types/cpp/alpha-mode.h"
#include "src/graphics/display/lib/api-types/cpp/color-conversion.h"
#include "src/graphics/display/lib/api-types/cpp/config-check-result.h"
#include "src/graphics/display/lib/api-types/cpp/coordinate-transformation.h"
#include "src/graphics/display/lib/api-types/cpp/display-id.h"
#include "src/graphics/display/lib/api-types/cpp/display-timing.h"
#include "src/graphics/display/lib/api-types/cpp/driver-buffer-collection-id.h"
#include "src/graphics/display/lib/api-types/cpp/driver-capture-image-id.h"
#include "src/graphics/display/lib/api-types/cpp/driver-config-stamp.h"
#include "src/graphics/display/lib/api-types/cpp/driver-image-id.h"
#include "src/graphics/display/lib/api-types/cpp/driver-layer.h"
#include "src/graphics/display/lib/api-types/cpp/engine-info.h"
#include "src/graphics/display/lib/api-types/cpp/image-buffer-usage.h"
#include "src/graphics/display/lib/api-types/cpp/image-metadata.h"
#include "src/graphics/display/lib/api-types/cpp/image-tiling-type.h"
#include "src/graphics/display/lib/api-types/cpp/mode-id.h"
#include "src/graphics/display/lib/api-types/cpp/mode.h"
#include "src/graphics/display/lib/api-types/cpp/pixel-format.h"
#include "src/graphics/display/lib/api-types/cpp/rectangle.h"
#include "src/lib/fxl/strings/string_printf.h"
namespace amlogic_display {
// Currently amlogic-display implementation uses pointers to ImageInfo as
// handles to images, while handles to images are defined as a fixed-size
// uint64_t in the banjo protocol. This works on platforms where uint64_t and
// uintptr_t are equivalent but this may cause portability issues in the future.
// TODO(https://fxbug.dev/42079128): Do not use pointers as handles.
static_assert(std::is_same_v<uint64_t, uintptr_t>);
namespace {
// TODO(https://fxbug.dev/42148348): Add more supported formats.
constexpr std::array<display::PixelFormat, 2> kSupportedPixelFormats = {
display::PixelFormat::kB8G8R8A8,
display::PixelFormat::kR8G8B8A8,
};
constexpr uint32_t kBufferAlignment = 64;
bool IsFormatSupported(display::PixelFormat format) {
return std::ranges::find(kSupportedPixelFormats, format) != kSupportedPixelFormats.end();
}
void SetDefaultImageFormatConstraints(fuchsia_images2::wire::PixelFormat format, uint64_t modifier,
fuchsia_sysmem2::wire::ImageFormatConstraints& constraints,
fidl::AnyArena& arena) {
constraints.Allocate(arena);
constraints.set_color_spaces(arena, std::array{fuchsia_images2::wire::ColorSpace::kSrgb});
constraints.set_pixel_format(format);
constraints.set_pixel_format_modifier(arena, fuchsia_images2::PixelFormatModifier(modifier));
constraints.set_bytes_per_row_divisor(kBufferAlignment);
constraints.set_start_offset_divisor(kBufferAlignment);
}
ColorSpaceConversionMode GetColorSpaceConversionMode(VoutType vout_type) {
switch (vout_type) {
case VoutType::kDsi:
return ColorSpaceConversionMode::kRgbInternalRgbOut;
case VoutType::kHdmi:
return ColorSpaceConversionMode::kRgbInternalYuvOut;
}
ZX_ASSERT_MSG(false, "Invalid VoutType: %u", static_cast<uint8_t>(vout_type));
}
// TODO(https://fxbug.dev/329375540): Use driver metadata instead of hardcoded
// per-board rules to determine the display engine reset policy.
bool IsFullHardwareResetRequired(
fidl::UnownedClientEnd<fuchsia_hardware_platform_device::Device> platform_device) {
constexpr bool kDefaultValue = true;
zx::result<BoardInfo> board_info_result = GetBoardInfo(platform_device);
if (board_info_result.is_error()) {
fdf::error(
"Failed to get board information: {}. Falling back to "
"default option ({})",
board_info_result, kDefaultValue);
return kDefaultValue;
}
const uint32_t vendor_id = board_info_result->board_vendor_id;
const uint32_t product_id = board_info_result->board_product_id;
// On Khadas VIM3, the bootloader display driver may set the display engine
// and the display device in an invalid state. We completely clear the stale
// hardware configuration by performing a reset on the display engine.
if (vendor_id == PDEV_VID_KHADAS && product_id == PDEV_PID_VIM3) {
return true;
}
// On Astro, Sherlock and Nelson, the bootloader display driver sets up the
// display engine and the panel. The Fuchsia driver doesn't initialize the
// hardware registers and only loads the current hardware state.
if (vendor_id == PDEV_VID_GOOGLE && product_id == PDEV_PID_ASTRO) {
return false;
}
if (vendor_id == PDEV_VID_GOOGLE && product_id == PDEV_PID_SHERLOCK) {
return false;
}
if (vendor_id == PDEV_VID_GOOGLE && product_id == PDEV_PID_NELSON) {
return false;
}
fdf::info("Unknown board type (vid={}, pid={}). Falling back to default option ({}).", vendor_id,
product_id, kDefaultValue);
return kDefaultValue;
}
} // namespace
zx::result<> DisplayEngine::SetMinimumRgb(uint8_t minimum_rgb) {
if (fully_initialized()) {
video_input_unit_->SetMinimumRgb(minimum_rgb);
return zx::ok();
}
return zx::error(ZX_ERR_INTERNAL);
}
zx::result<> DisplayEngine::ResetDisplayEngine() {
ZX_DEBUG_ASSERT(!fully_initialized());
fdf::trace("Display engine reset started.");
zx::result<> result = vout_->PowerOff();
if (!result.is_ok()) {
fdf::error("Failed to power off Vout before VPU reset: {}", result);
}
vpu_->PowerOff();
vpu_->PowerOn();
// TODO(https://fxbug.dev/42082920): Instead of enabling it ad-hoc here, make
// `Vpu::PowerOn()` idempotent and always enable the AFBC in `Vpu::PowerOn()`.
vpu_->AfbcPower(/*power_on=*/true);
const ColorSpaceConversionMode color_conversion_mode = GetColorSpaceConversionMode(vout_->type());
vpu_->SetupPostProcessorColorConversion(color_conversion_mode);
// All the VPU registers are now reset. We need to claim the hardware
// ownership again.
vpu_->CheckAndClaimHardwareOwnership();
result = vout_->PowerOn();
if (!result.is_ok()) {
fdf::error("Failed to power on Vout after VPU reset: {}", result);
return result.take_error();
}
fdf::trace("Display engine reset finished successfully.");
return zx::ok();
}
display::EngineInfo DisplayEngine::CompleteCoordinatorConnection() {
fbl::AutoLock display_lock(&display_mutex_);
if (display_attached_) {
const AddedDisplayInfo added_display_info = vout_->CreateAddedDisplayInfo(display_id_);
engine_events_.OnDisplayAdded(added_display_info.display_id, added_display_info.preferred_modes,
kSupportedPixelFormats);
}
return display::EngineInfo{{
.max_layer_count = 1,
.max_connected_display_count = 1,
.is_capture_supported = true,
}};
}
zx::result<> DisplayEngine::ImportBufferCollection(
display::DriverBufferCollectionId buffer_collection_id,
fidl::ClientEnd<fuchsia_sysmem2::BufferCollectionToken> buffer_collection_token) {
if (buffer_collections_.find(buffer_collection_id) != buffer_collections_.end()) {
fdf::error("Buffer Collection (id={}) already exists", buffer_collection_id.value());
return zx::error(ZX_ERR_ALREADY_EXISTS);
}
ZX_DEBUG_ASSERT_MSG(sysmem_.is_valid(), "sysmem allocator is not initialized");
auto endpoints = fidl::CreateEndpoints<fuchsia_sysmem2::BufferCollection>();
if (!endpoints.is_ok()) {
fdf::error("Failed to create sysmem BufferCollection endpoints: {}", endpoints);
return zx::error(ZX_ERR_INTERNAL);
}
auto& [collection_client_endpoint, collection_server_endpoint] = endpoints.value();
fidl::Arena arena;
auto bind_result = sysmem_->BindSharedCollection(
fuchsia_sysmem2::wire::AllocatorBindSharedCollectionRequest::Builder(arena)
.buffer_collection_request(std::move(collection_server_endpoint))
.token(std::move(buffer_collection_token))
.Build());
if (!bind_result.ok()) {
fdf::error("Failed to complete FIDL call BindSharedCollection: {}",
bind_result.error().FormatDescription());
return zx::error(ZX_ERR_INTERNAL);
}
buffer_collections_[buffer_collection_id] =
fidl::WireSyncClient(std::move(collection_client_endpoint));
return zx::ok();
}
zx::result<> DisplayEngine::ReleaseBufferCollection(
display::DriverBufferCollectionId buffer_collection_id) {
if (buffer_collections_.find(buffer_collection_id) == buffer_collections_.end()) {
fdf::error("Failed to release buffer collection {}: buffer collection doesn't exist",
buffer_collection_id.value());
return zx::error(ZX_ERR_NOT_FOUND);
}
buffer_collections_.erase(buffer_collection_id);
return zx::ok();
}
zx::result<display::DriverImageId> DisplayEngine::ImportImage(
const display::ImageMetadata& image_metadata,
display::DriverBufferCollectionId buffer_collection_id, uint32_t buffer_index) {
if (buffer_collections_.find(buffer_collection_id) == buffer_collections_.end()) {
fdf::error("Failed to import Image on collection {}: buffer collection doesn't exist",
buffer_collection_id.value());
return zx::error(ZX_ERR_NOT_FOUND);
}
auto import_info = std::make_unique<ImageInfo>();
if (import_info == nullptr) {
return zx::error(ZX_ERR_NO_MEMORY);
}
if (image_metadata.tiling_type() != display::ImageTilingType::kLinear) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
const fidl::WireSyncClient<fuchsia_sysmem2::BufferCollection>& collection =
buffer_collections_.at(buffer_collection_id);
fidl::WireResult check_result = collection->CheckAllBuffersAllocated();
// TODO(https://fxbug.dev/42072690): The sysmem FIDL error logging patterns are
// inconsistent across drivers. The FIDL error handling and logging should be
// unified.
if (!check_result.ok()) {
return zx::error(check_result.status());
}
const auto& check_response = check_result.value();
if (check_response.is_error() &&
check_response.error_value() == fuchsia_sysmem2::Error::kPending) {
return zx::error(ZX_ERR_SHOULD_WAIT);
}
if (check_response.is_error()) {
return zx::error(ZX_ERR_UNAVAILABLE);
}
fidl::WireResult wait_result = collection->WaitForAllBuffersAllocated();
// TODO(https://fxbug.dev/42072690): The sysmem FIDL error logging patterns are
// inconsistent across drivers. The FIDL error handling and logging should be
// unified.
if (!wait_result.ok()) {
return zx::error(wait_result.status());
}
auto& wait_response = wait_result.value();
if (wait_response.is_error()) {
return zx::error(ZX_ERR_UNAVAILABLE);
}
fuchsia_sysmem2::wire::BufferCollectionInfo& collection_info =
wait_response.value()->buffer_collection_info();
if (!collection_info.settings().has_image_format_constraints() ||
buffer_index >= collection_info.buffers().size()) {
return zx::error(ZX_ERR_OUT_OF_RANGE);
}
const fuchsia_images2::wire::PixelFormat fidl_pixel_format =
collection_info.settings().image_format_constraints().pixel_format();
if (!display::PixelFormat::IsSupported(fidl_pixel_format)) {
fdf::error("Failed to import image: pixel format {} not supported by display::PixelFormat",
static_cast<uint32_t>(fidl_pixel_format));
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
display::PixelFormat pixel_format(fidl_pixel_format);
if (!format_support_check_(pixel_format)) {
fdf::error("Failed to import image: pixel format {} not supported by display engine",
pixel_format);
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
ZX_DEBUG_ASSERT(
collection_info.settings().image_format_constraints().has_pixel_format_modifier());
const auto format_modifier =
collection_info.settings().image_format_constraints().pixel_format_modifier();
import_info->pixel_format = PixelFormatAndModifier(fidl_pixel_format, format_modifier);
switch (format_modifier) {
case fuchsia_images2::wire::PixelFormatModifier::kArmAfbc16X16SplitBlockSparseYuv:
case fuchsia_images2::wire::PixelFormatModifier::kArmAfbc16X16SplitBlockSparseYuvTe: {
fidl::Arena arena;
// AFBC does not use canvas.
uint64_t offset = collection_info.buffers().at(buffer_index).vmo_usable_start();
size_t size = fbl::round_up(
ImageFormatImageSize(
ImageConstraintsToFormat(arena, collection_info.settings().image_format_constraints(),
image_metadata.dimensions().width(),
image_metadata.dimensions().height())
.value()),
ZX_PAGE_SIZE);
zx_paddr_t paddr;
zx::result<> pin_result = zx::make_result(bti_.pin(
ZX_BTI_PERM_READ | ZX_BTI_CONTIGUOUS, collection_info.buffers().at(buffer_index).vmo(),
offset & ~(ZX_PAGE_SIZE - 1), size, &paddr, 1, &import_info->pmt));
if (pin_result.is_error()) {
fdf::error("Failed to pin BTI: {}", pin_result);
return pin_result.take_error();
}
import_info->paddr = paddr;
import_info->image_height = image_metadata.dimensions().height();
import_info->image_width = image_metadata.dimensions().width();
import_info->is_afbc = true;
} break;
case fuchsia_images2::wire::PixelFormatModifier::kLinear:
case fuchsia_images2::wire::PixelFormatModifier::kArmLinearTe: {
uint32_t minimum_row_bytes;
if (!ImageFormatMinimumRowBytes(collection_info.settings().image_format_constraints(),
image_metadata.dimensions().width(), &minimum_row_bytes)) {
fdf::error("Invalid image width {} for collection", image_metadata.dimensions().width());
return zx::error(ZX_ERR_INVALID_ARGS);
}
fuchsia_hardware_amlogiccanvas::wire::CanvasInfo canvas_info;
canvas_info.height = image_metadata.dimensions().height();
canvas_info.stride_bytes = minimum_row_bytes;
canvas_info.blkmode = fuchsia_hardware_amlogiccanvas::CanvasBlockMode::kLinear;
canvas_info.endianness = fuchsia_hardware_amlogiccanvas::CanvasEndianness();
canvas_info.flags = fuchsia_hardware_amlogiccanvas::CanvasFlags::kRead;
fidl::WireResult result = canvas_->Config(
std::move(collection_info.buffers().at(buffer_index).vmo()),
collection_info.buffers().at(buffer_index).vmo_usable_start(), canvas_info);
if (!result.ok()) {
fdf::error("Failed to configure canvas: {}", result.error().FormatDescription());
return zx::error(ZX_ERR_NO_RESOURCES);
}
fidl::WireResultUnwrapType<fuchsia_hardware_amlogiccanvas::Device::Config>& response =
result.value();
if (response.is_error()) {
fdf::error("Failed to configure canvas: {}", zx::make_result(response.error_value()));
return zx::error(ZX_ERR_NO_RESOURCES);
}
import_info->canvas = canvas_.client_end();
import_info->canvas_idx = result->value()->canvas_idx;
import_info->image_height = image_metadata.dimensions().height();
import_info->image_width = image_metadata.dimensions().width();
import_info->is_afbc = false;
} break;
default:
ZX_DEBUG_ASSERT_MSG(false, "Invalid pixel format modifier: %lu",
static_cast<uint64_t>(format_modifier));
return zx::error(ZX_ERR_INVALID_ARGS);
}
// TODO(https://fxbug.dev/42079128): Using pointers as handles impedes portability of
// the driver. Do not use pointers as handles.
display::DriverImageId driver_image_id(reinterpret_cast<uint64_t>(import_info.get()));
fbl::AutoLock lock(&image_mutex_);
imported_images_.push_back(std::move(import_info));
return zx::ok(driver_image_id);
}
void DisplayEngine::ReleaseImage(display::DriverImageId image_id) {
fbl::AutoLock lock(&image_mutex_);
auto info = reinterpret_cast<ImageInfo*>(image_id.value());
imported_images_.erase(*info);
}
display::ConfigCheckResult DisplayEngine::CheckConfiguration(
display::DisplayId display_id, display::ModeId mode_id,
display::ColorConversion color_conversion, cpp20::span<const display::DriverLayer> layers) {
fbl::AutoLock lock(&display_mutex_);
// no-op, just wait for the client to try a new config
if (!display_attached_ || display_id != display_id_) {
return display::ConfigCheckResult::kOk;
}
// Guaranteed by DisplayEngineInterface.
ZX_DEBUG_ASSERT(mode_id != display::kInvalidModeId);
std::optional<display::Mode> get_display_mode_result = vout_->GetDisplayMode(mode_id);
if (!get_display_mode_result.has_value()) {
fdf::warn("CheckConfig failure: display mode ID {} not supported", mode_id);
return display::ConfigCheckResult::kUnsupportedDisplayModes;
}
display::Mode target_display_mode = std::move(get_display_mode_result).value();
display::ConfigCheckResult check_result = [&] {
if (layers.size() > 1) {
// We only support 1 layer
fdf::warn("CheckConfig failure: {} layers requested, we only support 1", layers.size());
return display::ConfigCheckResult::kUnsupportedConfig;
}
// TODO(https://fxbug.dev/42080882): Move color conversion validation code to a common
// library.
for (float preoffset : color_conversion.preoffsets()) {
if (preoffset <= -1 || preoffset >= 1) {
fdf::warn("CheckConfig failure: preoffset {} out of range (-1, 1)", preoffset);
return display::ConfigCheckResult::kUnsupportedConfig;
;
}
}
for (float postoffset : color_conversion.postoffsets()) {
if (postoffset <= -1 || postoffset >= 1) {
fdf::warn("CheckConfig failure: postoffset {} out of range (-1, 1)", postoffset);
return display::ConfigCheckResult::kUnsupportedConfig;
;
}
}
// Make sure the layer configuration is supported.
const display::DriverLayer& layer = layers[0];
const display::Rectangle display_area({
.x = 0,
.y = 0,
.width = target_display_mode.active_area().width(),
.height = target_display_mode.active_area().height(),
});
if (layer.alpha_mode() == display::AlphaMode::kPremultiplied) {
// we don't support pre-multiplied alpha mode
fdf::warn("CheckConfig failure: pre-multipled alpha not supported");
return display::ConfigCheckResult::kUnsupportedConfig;
}
if (layer.image_source_transformation() != display::CoordinateTransformation::kIdentity) {
fdf::warn("CheckConfig failure: coordinate transformation not supported");
return display::ConfigCheckResult::kUnsupportedConfig;
}
if (layer.image_metadata().dimensions().width() != target_display_mode.active_area().width() ||
layer.image_metadata().dimensions().height() !=
target_display_mode.active_area().height()) {
// TODO(https://fxbug.dev/409473403): Restore to `warn` level once the infrastructure issue
// is resolved.
fdf::debug("CheckConfig failure: image metadata dimensions {}x{} do not match display {}",
layer.image_metadata().dimensions().width(),
layer.image_metadata().dimensions().height(), target_display_mode);
return display::ConfigCheckResult::kUnsupportedConfig;
}
if (layer.display_destination() != display_area) {
fdf::warn(
"CheckConfig failure: layer output {}x{} at ({}, {}) does not cover entire display {}",
layer.display_destination().width(), layer.display_destination().height(),
layer.display_destination().x(), layer.display_destination().y(), target_display_mode);
return display::ConfigCheckResult::kUnsupportedConfig;
}
if (layer.image_source().width() == 0 || layer.image_source().height() == 0) {
// TODO(https://fxbug.dev/401286733): color layers not yet supported.
fdf::warn("CheckConfig failure: color layers not supported");
return display::ConfigCheckResult::kUnsupportedConfig;
}
if (layer.image_source() != display_area) {
fdf::warn("CheckConfig failure: image source {}x{} at ({}, {}) requires cropping or scaling",
layer.image_source().width(), layer.image_source().height(),
layer.image_source().x(), layer.image_source().y());
return display::ConfigCheckResult::kUnsupportedConfig;
}
return display::ConfigCheckResult::kOk;
}();
return check_result;
}
void DisplayEngine::ApplyConfiguration(display::DisplayId display_id, display::ModeId mode_id,
display::ColorConversion color_conversion,
cpp20::span<const display::DriverLayer> layers,
display::DriverConfigStamp config_stamp) {
fbl::AutoLock lock(&display_mutex_);
if (!layers.empty()) {
// Perform Vout modeset first.
//
// Setting up OSD may require Vout framebuffer information, which may be
// changed on each ApplyConfiguration(), so we need to apply the
// configuration to Vout first before initializing the display and OSD.
zx::result<> vout_apply_config_result = vout_->ApplyConfiguration(mode_id);
if (!vout_apply_config_result.is_ok()) {
fdf::error("Failed to apply config to Vout: {}", vout_apply_config_result);
return;
}
std::optional<display::Mode> get_display_mode_result = vout_->GetDisplayMode(mode_id);
// Guaranteed to be true in `CheckConfiguration()`.
ZX_DEBUG_ASSERT(get_display_mode_result.has_value());
display::Mode display_mode = std::move(get_display_mode_result).value();
// The only way a checked configuration could now be invalid is if display was
// unplugged. If that's the case, then the upper layers will give a new configuration
// once they finish handling the unplug event. So just return.
if (!display_attached_ || display_id != display_id_) {
return;
}
// Since Amlogic does not support plug'n play (fixed display), there is no way
// a checked configuration could be invalid at this point.
video_input_unit_->FlipOnVsync(layers[0], display_mode, color_conversion, config_stamp);
} else {
if (fully_initialized()) {
{
fbl::AutoLock lock2(&capture_mutex_);
if (current_capture_target_image_ != nullptr) {
// there's an active capture. stop it before disabling osd
vpu_->CaptureDone();
current_capture_target_image_ = nullptr;
}
}
video_input_unit_->DisableLayer(config_stamp);
}
}
}
void DisplayEngine::Deinitialize() {
vsync_receiver_.reset();
// TODO(https://fxbug.dev/42082206): Power off should occur after all threads are
// destroyed. Otherwise other threads may still write to the VPU MMIO which
// can cause the system to hang.
if (fully_initialized()) {
video_input_unit_->Release();
vpu_->PowerOff();
}
capture_.reset();
hot_plug_detection_.reset();
}
zx::result<> DisplayEngine::SetBufferCollectionConstraints(
const display::ImageBufferUsage& image_buffer_usage,
display::DriverBufferCollectionId buffer_collection_id) {
if (buffer_collections_.find(buffer_collection_id) == buffer_collections_.end()) {
fdf::error(
"Failed to set buffer collection constraints for %lu: buffer collection doesn't exist",
buffer_collection_id.value());
return zx::error(ZX_ERR_NOT_FOUND);
}
fidl::Arena arena;
const fidl::WireSyncClient<fuchsia_sysmem2::BufferCollection>& collection =
buffer_collections_.at(buffer_collection_id);
auto constraints = fuchsia_sysmem2::wire::BufferCollectionConstraints::Builder(arena);
const char* buffer_name;
if (image_buffer_usage.tiling_type() == display::ImageTilingType::kCapture) {
constraints.usage(fuchsia_sysmem2::wire::BufferUsage::Builder(arena)
.cpu(fuchsia_sysmem2::wire::kCpuUsageReadOften |
fuchsia_sysmem2::wire::kCpuUsageWriteOften)
.Build());
} else {
constraints.usage(fuchsia_sysmem2::wire::BufferUsage::Builder(arena)
.display(fuchsia_sysmem2::wire::kDisplayUsageLayer)
.Build());
}
constraints.buffer_memory_constraints(
fuchsia_sysmem2::wire::BufferMemoryConstraints::Builder(arena)
.physically_contiguous_required(true)
.secure_required(false)
.ram_domain_supported(true)
.cpu_domain_supported(false)
.inaccessible_domain_supported(true)
.permitted_heaps(
arena,
std::vector{
fuchsia_sysmem2::wire::Heap::Builder(arena)
.heap_type(arena, bind_fuchsia_sysmem_heap::HEAP_TYPE_SYSTEM_RAM)
.id(0)
.Build(),
fuchsia_sysmem2::wire::Heap::Builder(arena)
.heap_type(arena, bind_fuchsia_amlogic_platform_sysmem_heap::HEAP_TYPE_SECURE)
.id(0)
.Build()})
.Build());
if (image_buffer_usage.tiling_type() == display::ImageTilingType::kCapture) {
fuchsia_sysmem2::wire::ImageFormatConstraints image_constraints;
SetDefaultImageFormatConstraints(
fuchsia_images2::wire::PixelFormat::kB8G8R8,
static_cast<uint64_t>(fuchsia_images2::wire::PixelFormatModifier::kLinear),
image_constraints, arena);
const PixelGridSize2D display_contents_size = video_input_unit_->display_contents_size();
image_constraints.set_min_size(
arena,
fuchsia_math::wire::SizeU{.width = static_cast<uint32_t>(display_contents_size.width),
.height = static_cast<uint32_t>(display_contents_size.height)});
// Amlogic display capture engine (VDIN) outputs in formats with 3 bytes per
// pixel.
constexpr uint32_t kCaptureImageBytesPerPixel = 3;
image_constraints.set_min_bytes_per_row(
fbl::round_up(display_contents_size.width * kCaptureImageBytesPerPixel, kBufferAlignment));
image_constraints.set_max_width_times_height(
arena, display_contents_size.width * display_contents_size.height);
buffer_name = "Display capture";
constraints.image_format_constraints(arena, std::vector{image_constraints});
} else {
// TODO(https://fxbug.dev/42176441): Currently the buffer collection constraints are
// applied to all displays. If the |vout_| device type changes, then the
// existing image formats might not work for the new device type. To resolve
// this, the driver should set per-display buffer collection constraints
// instead.
ZX_DEBUG_ASSERT(format_support_check_ != nullptr);
std::vector<fuchsia_sysmem2::wire::ImageFormatConstraints> image_constraints_vec = {};
if (format_support_check_(display::PixelFormat::kB8G8R8A8)) {
for (const auto format_modifier :
{fuchsia_images2::wire::PixelFormatModifier::kLinear,
fuchsia_images2::wire::PixelFormatModifier::kArmLinearTe}) {
fuchsia_sysmem2::wire::ImageFormatConstraints image_constraints;
SetDefaultImageFormatConstraints(fuchsia_images2::wire::PixelFormat::kB8G8R8A8,
static_cast<uint64_t>(format_modifier), image_constraints,
arena);
image_constraints_vec.push_back(image_constraints);
}
}
if (format_support_check_(display::PixelFormat::kR8G8B8A8)) {
for (const auto format_modifier :
{fuchsia_images2::wire::PixelFormatModifier::kLinear,
fuchsia_images2::wire::PixelFormatModifier::kArmLinearTe,
fuchsia_images2::wire::PixelFormatModifier::kArmAfbc16X16SplitBlockSparseYuv,
fuchsia_images2::wire::PixelFormatModifier::kArmAfbc16X16SplitBlockSparseYuvTe}) {
fuchsia_sysmem2::wire::ImageFormatConstraints image_constraints;
SetDefaultImageFormatConstraints(fuchsia_images2::wire::PixelFormat::kR8G8B8A8,
static_cast<uint64_t>(format_modifier), image_constraints,
arena);
image_constraints_vec.push_back(image_constraints);
}
}
constraints.image_format_constraints(arena, image_constraints_vec);
buffer_name = "Display";
}
// Set priority to 10 to override the Vulkan driver name priority of 5, but be less than most
// application priorities.
constexpr uint32_t kNamePriority = 10;
fidl::OneWayStatus set_name_result =
collection->SetName(fuchsia_sysmem2::wire::NodeSetNameRequest::Builder(arena)
.priority(kNamePriority)
.name(fidl::StringView::FromExternal(buffer_name))
.Build());
if (!set_name_result.ok()) {
fdf::error("Failed to set name: {}", set_name_result.status());
return zx::error(set_name_result.status());
}
fidl::OneWayStatus set_constraints_result = collection->SetConstraints(
fuchsia_sysmem2::wire::BufferCollectionSetConstraintsRequest::Builder(arena)
.constraints(constraints.Build())
.Build());
if (!set_constraints_result.ok()) {
fdf::error("Failed to set constraints: {}", set_constraints_result.status());
return zx::error(set_constraints_result.status());
}
return zx::ok();
}
zx::result<> DisplayEngine::SetDisplayPower(display::DisplayId display_id, bool power_on) {
fbl::AutoLock lock(&display_mutex_);
if (display_id != display_id_ || !display_attached_) {
return zx::error(ZX_ERR_NOT_FOUND);
}
fdf::info("Powering {} Display {}", power_on ? "on" : "off", display_id);
if (vout_->type() == VoutType::kHdmi) {
// TODO(https://fxbug.dev/335303016): This is a workaround to not trigger
// display disconnection interrupts on HDMI display power off. It doesn't
// enable or disable the display hardware. Instead, it only enables /
// disables the Vsync interrupt delivery and shows / hides the current
// frame on the display.
zx::result<> set_receiving_state_result =
vsync_receiver_->SetReceivingState(/*receiving=*/power_on);
if (set_receiving_state_result.is_error()) {
fdf::error("Failed to set Vsync interrupt receiving state: {}", set_receiving_state_result);
return set_receiving_state_result.take_error();
}
zx::result<> set_frame_visibility_result =
vout_->SetFrameVisibility(/*frame_visible=*/power_on);
if (set_frame_visibility_result.is_error()) {
fdf::error("Failed to set frame visibility: {}", set_frame_visibility_result);
return set_frame_visibility_result.take_error();
}
return zx::ok();
}
ZX_DEBUG_ASSERT(vout_->type() == VoutType::kDsi);
if (power_on) {
return vout_->PowerOn();
}
return vout_->PowerOff();
}
zx::result<display::DriverCaptureImageId> DisplayEngine::ImportImageForCapture(
display::DriverBufferCollectionId buffer_collection_id, uint32_t buffer_index) {
if (buffer_collections_.find(buffer_collection_id) == buffer_collections_.end()) {
fdf::error("Failed to import capture image on collection {}: buffer collection doesn't exist",
buffer_collection_id.value());
return zx::error(ZX_ERR_NOT_FOUND);
}
const fidl::WireSyncClient<fuchsia_sysmem2::BufferCollection>& collection =
buffer_collections_.at(buffer_collection_id);
auto import_capture = std::make_unique<ImageInfo>();
if (import_capture == nullptr) {
return zx::error(ZX_ERR_NO_MEMORY);
}
fbl::AutoLock lock(&capture_mutex_);
fidl::WireResult check_result = collection->CheckAllBuffersAllocated();
// TODO(https://fxbug.dev/42072690): The sysmem FIDL error logging patterns are
// inconsistent across drivers. The FIDL error handling and logging should be
// unified.
if (!check_result.ok()) {
return zx::error(check_result.status());
}
const auto& check_response = check_result.value();
if (check_response.is_error() &&
check_response.error_value() == fuchsia_sysmem2::Error::kPending) {
return zx::error(ZX_ERR_SHOULD_WAIT);
}
if (check_response.is_error()) {
return zx::error(ZX_ERR_UNAVAILABLE);
}
fidl::WireResult wait_result = collection->WaitForAllBuffersAllocated();
// TODO(https://fxbug.dev/42072690): The sysmem FIDL error logging patterns are
// inconsistent across drivers. The FIDL error handling and logging should be
// unified.
if (!wait_result.ok()) {
return zx::error(wait_result.status());
}
auto& wait_response = wait_result.value();
if (wait_response.is_error()) {
return zx::error(ZX_ERR_UNAVAILABLE);
}
fuchsia_sysmem2::wire::BufferCollectionInfo collection_info =
wait_response->buffer_collection_info();
if (!collection_info.settings().has_image_format_constraints() ||
buffer_index >= collection_info.buffers().size()) {
return zx::error(ZX_ERR_OUT_OF_RANGE);
}
// Ensure the proper format
ZX_DEBUG_ASSERT(collection_info.settings().image_format_constraints().pixel_format() ==
fuchsia_images2::wire::PixelFormat::kB8G8R8);
// Allocate a canvas for the capture image
fuchsia_hardware_amlogiccanvas::wire::CanvasInfo canvas_info;
canvas_info.height = collection_info.settings().image_format_constraints().min_size().height;
canvas_info.stride_bytes =
collection_info.settings().image_format_constraints().min_bytes_per_row();
canvas_info.blkmode = fuchsia_hardware_amlogiccanvas::CanvasBlockMode::kLinear;
// Canvas images are by default little-endian for each 128-bit (16-byte)
// chunk. By default, for 8-bit YUV444 images, the pixels are interpreted as
// Y0 U0 V0 Y1 U1 V1 Y2 U2 V2 Y3 U3 V3 Y4 U4 V4 Y5...
//
// However, capture memory interface uses big-endian for each 128-bit
// (16-byte) chunk (defined in Vpu::CaptureInit), and the high- and low-64
// bits (8 bytes) are already swapped. This is effectively big-endian for
// each 64-bit chunk. So, the 8-bit YUV444 pixels are stored by the capture
// memory interface as
// U2 Y2 V1 U1 Y1 V0 U0 Y0 Y5 V4 U4 Y4 V3 U3 Y3 V2...
//
// In order to read / write the captured canvas image correctly, the canvas
// endianness must match that of capture memory interface.
//
// To convert 128-bit little-endian to 64-bit big-endian, we need to swap
// every 8-bit pairs, 16-bit pairs and 32-bit pairs within every 64-bit chunk:
//
// The original bytes written by the capture memory interface:
// U2 Y2 V1 U1 Y1 V0 U0 Y0 Y5 V4 U4 Y4 V3 U3 Y3 V2...
// Swapping every 8-bit pairs we get:
// Y2 U2 U1 V1 V0 Y1 Y0 U0 V4 Y5 Y4 U4 U3 V3 V2 Y3...
// Then we swap every 16-bit pairs:
// U1 V1 Y2 U2 Y0 U0 V0 Y1 Y4 U4 V4 Y5 V2 Y3 U3 V3...
// Then we swap every 32-bit pairs:
// Y0 U0 V0 Y1 U1 V1 Y2 U2 V2 Y3 U3 V3 Y4 U4 V4 Y5...
// Then we got the correct pixel interpretation.
constexpr fuchsia_hardware_amlogiccanvas::wire::CanvasEndianness kCanvasBigEndian64Bit =
fuchsia_hardware_amlogiccanvas::wire::CanvasEndianness::kSwap8BitPairs |
fuchsia_hardware_amlogiccanvas::wire::CanvasEndianness::kSwap16BitPairs |
fuchsia_hardware_amlogiccanvas::wire::CanvasEndianness::kSwap32BitPairs;
canvas_info.endianness = fuchsia_hardware_amlogiccanvas::CanvasEndianness(kCanvasBigEndian64Bit);
canvas_info.flags = fuchsia_hardware_amlogiccanvas::CanvasFlags::kRead |
fuchsia_hardware_amlogiccanvas::CanvasFlags::kWrite;
fidl::WireResult result =
canvas_->Config(std::move(collection_info.buffers().at(buffer_index).vmo()),
collection_info.buffers().at(buffer_index).vmo_usable_start(), canvas_info);
if (!result.ok()) {
fdf::error("Failed to configure canvas: {}", result.error().FormatDescription());
return zx::error(ZX_ERR_NO_RESOURCES);
}
fidl::WireResultUnwrapType<fuchsia_hardware_amlogiccanvas::Device::Config>& response =
result.value();
if (response.is_error()) {
fdf::error("Failed to configure canvas: {}", zx::make_result(response.error_value()));
return zx::error(ZX_ERR_NO_RESOURCES);
}
// At this point, we have setup a canvas with the BufferCollection-based VMO. Store the
// capture information
//
// TODO(https://fxbug.dev/42082204): Currently there's no guarantee in the canvas API
// for the uniqueness of `canvas_idx`, and this driver doesn't check if there
// is any image with the same canvas index either. We should either make this
// a formal guarantee in Canvas.Config() API, or perform a check against all
// imported images to make sure the canvas is unique so that the driver won't
// overwrite other images.
import_capture->canvas_idx = result->value()->canvas_idx;
import_capture->canvas = canvas_.client_end();
import_capture->image_height =
collection_info.settings().image_format_constraints().min_size().height;
import_capture->image_width =
collection_info.settings().image_format_constraints().min_size().width;
// TODO(https://fxbug.dev/42079128): Using pointers as handles impedes portability of
// the driver. Do not use pointers as handles.
display::DriverCaptureImageId driver_capture_image_id(
reinterpret_cast<uint64_t>(import_capture.get()));
imported_captures_.push_back(std::move(import_capture));
return zx::ok(driver_capture_image_id);
}
zx::result<> DisplayEngine::StartCapture(display::DriverCaptureImageId capture_image_id) {
if (!fully_initialized()) {
fdf::error("Failed to start capture before initializing the display");
return zx::error(ZX_ERR_SHOULD_WAIT);
}
fbl::AutoLock lock(&capture_mutex_);
if (current_capture_target_image_ != nullptr) {
fdf::error("Failed to start capture while another capture is in progress");
return zx::error(ZX_ERR_SHOULD_WAIT);
}
// Confirm that the handle was previously imported (hence valid)
// TODO(https://fxbug.dev/42079128): This requires an enumeration over all the imported
// capture images for each StartCapture(). We should use hash maps to map
// handles (which shouldn't be pointers) to ImageInfo instead.
ImageInfo* info = reinterpret_cast<ImageInfo*>(capture_image_id.value());
uint8_t canvas_index = info->canvas_idx;
if (imported_captures_.find_if([canvas_index](const ImageInfo& info) {
return info.canvas_idx == canvas_index;
}) == imported_captures_.end()) {
// invalid handle
fdf::error("Invalid capture image ID: {}", capture_image_id);
return zx::error(ZX_ERR_NOT_FOUND);
}
// TODO(https://fxbug.dev/42082204): A valid canvas index can be zero.
ZX_DEBUG_ASSERT(info->canvas_idx > 0);
ZX_DEBUG_ASSERT(info->image_height > 0);
ZX_DEBUG_ASSERT(info->image_width > 0);
auto status = vpu_->CaptureInit(info->canvas_idx, info->image_height, info->image_width);
if (status != ZX_OK) {
fdf::error("Failed to init capture: {}", zx::make_result(status));
return zx::make_result(status);
}
status = vpu_->CaptureStart();
if (status != ZX_OK) {
fdf::error("Failed to start capture: {}", zx::make_result(status));
return zx::make_result(status);
}
current_capture_target_image_ = info;
return zx::ok();
}
zx::result<> DisplayEngine::ReleaseCapture(display::DriverCaptureImageId capture_image_id) {
fbl::AutoLock lock(&capture_mutex_);
if (capture_image_id ==
display::DriverCaptureImageId(reinterpret_cast<uint64_t>(current_capture_target_image_))) {
return zx::error(ZX_ERR_SHOULD_WAIT);
}
// Find and erase previously imported capture
// TODO(https://fxbug.dev/42079128): This requires an enumeration over all the imported
// capture images for each StartCapture(). We should use hash maps to map
// handles (which shouldn't be pointers) to ImageInfo instead.
uint8_t canvas_index = reinterpret_cast<ImageInfo*>(capture_image_id.value())->canvas_idx;
if (imported_captures_.erase_if(
[canvas_index](const ImageInfo& i) { return i.canvas_idx == canvas_index; }) == nullptr) {
fdf::error("Tried to release non-existent capture image {}", canvas_index);
return zx::error(ZX_ERR_NOT_FOUND);
}
return zx::ok();
}
void DisplayEngine::OnVsync(zx::time_monotonic timestamp) {
display::DriverConfigStamp current_config_stamp = display::kInvalidDriverConfigStamp;
if (fully_initialized()) {
current_config_stamp = video_input_unit_->GetLastConfigStampApplied();
}
if (current_config_stamp == display::kInvalidDriverConfigStamp) {
return;
}
fbl::AutoLock lock(&display_mutex_);
if (display_attached_) {
engine_events_.OnDisplayVsync(display_id_, timestamp, current_config_stamp);
}
}
void DisplayEngine::OnCaptureComplete() {
if (!fully_initialized()) {
fdf::warn("Capture interrupt fired before the display was initialized");
return;
}
vpu_->CaptureDone();
{
fbl::AutoLock display_lock(&display_mutex_);
engine_events_.OnCaptureComplete();
}
{
fbl::AutoLock capture_lock(&capture_mutex_);
current_capture_target_image_ = nullptr;
}
}
void DisplayEngine::OnHotPlugStateChange(HotPlugDetectionState current_state) {
fbl::AutoLock lock(&display_mutex_);
if (current_state == HotPlugDetectionState::kDetected && !display_attached_) {
fdf::info("Display is connected");
display_attached_ = true;
zx::result<> update_vout_display_state_result = vout_->UpdateStateOnDisplayConnected();
if (update_vout_display_state_result.is_error()) {
fdf::error("Failed to update Vout display state: {}", update_vout_display_state_result);
return;
}
const AddedDisplayInfo added_display_info = vout_->CreateAddedDisplayInfo(display_id_);
engine_events_.OnDisplayAdded(added_display_info.display_id, added_display_info.preferred_modes,
kSupportedPixelFormats);
return;
}
if (current_state == HotPlugDetectionState::kNotDetected && display_attached_) {
fdf::info("Display Disconnected!");
vout_->DisplayDisconnected();
const display::DisplayId removed_display_id = display_id_;
display_id_++;
display_attached_ = false;
engine_events_.OnDisplayRemoved(removed_display_id);
return;
}
}
zx_status_t DisplayEngine::SetupHotplugDisplayDetection() {
ZX_DEBUG_ASSERT_MSG(!hot_plug_detection_, "HPD already set up");
zx::result<std::unique_ptr<HotPlugDetection>> hot_plug_detection_result =
HotPlugDetection::Create(*incoming_,
fit::bind_member<&DisplayEngine::OnHotPlugStateChange>(this));
if (hot_plug_detection_result.is_error()) {
// HotPlugDetection::Create() logged the error.
return hot_plug_detection_result.status_value();
}
hot_plug_detection_ = std::move(hot_plug_detection_result).value();
return ZX_OK;
}
zx_status_t DisplayEngine::InitializeVout() {
ZX_ASSERT(vout_ == nullptr);
zx::result<std::unique_ptr<display::PanelType>> metadata_result =
compat::GetMetadata<display::PanelType>(incoming_, DEVICE_METADATA_DISPLAY_PANEL_TYPE,
component::kDefaultInstance);
if (metadata_result.is_ok()) {
display::PanelType panel_type = *std::move(metadata_result).value();
fdf::info("Provided panel type: {}", static_cast<uint32_t>(panel_type));
fbl::AutoLock lock(&display_mutex_);
zx::result<std::unique_ptr<VoutDsi>> dsi_vout_result =
VoutDsi::Create(*incoming_, panel_type, root_node_.CreateChild("vout"));
if (dsi_vout_result.is_error()) {
fdf::error("Failed to create DSI Vout: %s", dsi_vout_result.status_string());
return dsi_vout_result.status_value();
}
vout_ = std::move(dsi_vout_result).value();
display_attached_ = true;
return ZX_OK;
}
if (metadata_result.status_value() == ZX_ERR_NOT_FOUND) {
zx::result<std::unique_ptr<VoutHdmi>> hdmi_vout_result = VoutHdmi::Create(
*incoming_, root_node_.CreateChild("vout"), structured_config_.visual_debugging_level());
if (hdmi_vout_result.is_error()) {
fdf::error("Failed to create HDMI Vout: %s", hdmi_vout_result.status_string());
return hdmi_vout_result.status_value();
}
vout_ = std::move(hdmi_vout_result).value();
return ZX_OK;
}
fdf::error("Failed to get display panel metadata: {}", metadata_result);
return metadata_result.status_value();
}
zx_status_t DisplayEngine::GetCommonProtocolsAndResources() {
ZX_ASSERT(!pdev_.is_valid());
ZX_ASSERT(!sysmem_.is_valid());
ZX_ASSERT(!canvas_.is_valid());
ZX_ASSERT(!bti_.is_valid());
static constexpr char kPdevFragmentName[] = "pdev";
zx::result<fidl::ClientEnd<fuchsia_hardware_platform_device::Device>> pdev_result =
incoming_->Connect<fuchsia_hardware_platform_device::Service::Device>(kPdevFragmentName);
if (pdev_result.is_error()) {
fdf::error("Failed to get the pdev client: {}", pdev_result);
return pdev_result.status_value();
}
pdev_ = fidl::WireSyncClient(std::move(pdev_result).value());
if (!pdev_.is_valid()) {
fdf::error("Failed to get a valid platform device client");
return ZX_ERR_INTERNAL;
}
zx::result sysmem_client_result = incoming_->Connect<fuchsia_sysmem2::Allocator>();
if (sysmem_client_result.is_error()) {
fdf::error("Failed to get sysmem protocol: {}", sysmem_client_result);
return sysmem_client_result.status_value();
}
sysmem_.Bind(std::move(sysmem_client_result.value()));
zx::result<fidl::ClientEnd<fuchsia_hardware_amlogiccanvas::Device>> canvas_client_result =
incoming_->Connect<fuchsia_hardware_amlogiccanvas::Service::Device>("canvas");
if (canvas_client_result.is_error()) {
fdf::error("Failed to get Amlogic canvas protocol: {}", canvas_client_result);
return canvas_client_result.status_value();
}
canvas_.Bind(std::move(canvas_client_result.value()));
zx::result<zx::bti> bti_result = GetBti(BtiResourceIndex::kDma, pdev_.client_end());
if (bti_result.is_error()) {
return bti_result.error_value();
}
bti_ = std::move(bti_result).value();
return ZX_OK;
}
zx_koid_t GetKoid(zx_handle_t handle) {
zx_info_handle_basic_t info;
zx_status_t status =
zx_object_get_info(handle, ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr);
return status == ZX_OK ? info.koid : ZX_KOID_INVALID;
}
zx_status_t DisplayEngine::InitializeSysmemAllocator() {
ZX_ASSERT(sysmem_.is_valid());
const zx_koid_t current_process_koid = GetKoid(zx_process_self());
const std::string debug_name = fxl::StringPrintf("amlogic-display[%lu]", current_process_koid);
fidl::Arena arena;
fidl::OneWayStatus set_debug_status = sysmem_->SetDebugClientInfo(
fuchsia_sysmem2::wire::AllocatorSetDebugClientInfoRequest::Builder(arena)
.name(fidl::StringView::FromExternal(debug_name))
.id(current_process_koid)
.Build());
if (!set_debug_status.ok()) {
fdf::error("Failed to set sysmem allocator debug info: {}", set_debug_status.status_string());
return set_debug_status.status();
}
return ZX_OK;
}
zx_status_t DisplayEngine::Initialize() {
SetFormatSupportCheck([](display::PixelFormat format) { return IsFormatSupported(format); });
// Set up inspect first, since other components may add inspect children
// during initialization.
root_node_ = inspector_.GetRoot().CreateChild("amlogic-display");
zx_status_t status = GetCommonProtocolsAndResources();
if (status != ZX_OK) {
fdf::error("Failed to get common protocols resources from parent devices: {}",
zx::make_result(status));
return status;
}
status = InitializeVout();
if (status != ZX_OK) {
fdf::error("Failed to initalize Vout: {}", zx::make_result(status));
return status;
}
video_input_unit_node_ = root_node_.CreateChild("video_input_unit");
zx::result<std::unique_ptr<VideoInputUnit>> video_input_unit_create_result =
VideoInputUnit::Create(pdev_.client_end(), &video_input_unit_node_);
if (video_input_unit_create_result.is_error()) {
fdf::error("Failed to create VideoInputUnit instance: {}", video_input_unit_create_result);
return video_input_unit_create_result.status_value();
}
video_input_unit_ = std::move(video_input_unit_create_result).value();
zx::result<std::unique_ptr<Vpu>> vpu_result = Vpu::Create(pdev_.client_end());
if (vpu_result.is_error()) {
fdf::error("Failed to initialize VPU object: {}", vpu_result);
return vpu_result.status_value();
}
vpu_ = std::move(vpu_result).value();
// If the display engine was previously owned by a different driver, we
// attempt to complete a seamless takeover. If we previously owned the
// hardware, our driver must have been unloaded and reloaded.
// We currently do a full hardware reset in that case.
const bool performs_full_hardware_reset =
IsFullHardwareResetRequired(pdev_.client_end()) || !vpu_->CheckAndClaimHardwareOwnership();
if (performs_full_hardware_reset) {
fbl::AutoLock lock(&display_mutex_);
zx::result<> reset_result = ResetDisplayEngine();
if (!reset_result.is_ok()) {
fdf::error("Failed to reset the display engine: {}", reset_result);
return reset_result.status_value();
}
} else {
// It's possible that the AFBC engine is not yet turned on by the
// previous driver when the driver takes it over so we should ensure it's
// enabled.
//
// TODO(https://fxbug.dev/42082920): Instead of enabling it ad-hoc here, make
// `Vpu::PowerOn()` idempotent and always call it when initializing the
// driver.
vpu_->AfbcPower(true);
}
status = InitializeSysmemAllocator();
if (status != ZX_OK) {
fdf::error("Failed to initialize sysmem allocator: {}", zx::make_result(status));
return status;
}
{
zx::result<std::unique_ptr<VsyncReceiver>> vsync_receiver_result =
VsyncReceiver::Create(pdev_.client_end(), fit::bind_member<&DisplayEngine::OnVsync>(this));
if (vsync_receiver_result.is_error()) {
// Create() already logged the error.
return vsync_receiver_result.error_value();
}
vsync_receiver_ = std::move(vsync_receiver_result).value();
}
{
zx::result<std::unique_ptr<Capture>> capture_result = Capture::Create(
pdev_.client_end(), fit::bind_member<&DisplayEngine::OnCaptureComplete>(this));
if (capture_result.is_error()) {
// Create() already logged the error.
return capture_result.error_value();
}
capture_ = std::move(capture_result).value();
}
if (vout_->SupportsHotplugDetection()) {
if (zx_status_t status = SetupHotplugDisplayDetection(); status != ZX_OK) {
fdf::error("Failed to set up hotplug display: {}", zx::make_result(status));
return status;
}
}
set_fully_initialized();
return ZX_OK;
}
DisplayEngine::DisplayEngine(std::shared_ptr<fdf::Namespace> incoming,
display::DisplayEngineEventsInterface* engine_events,
structured_config::Config structured_config)
: incoming_(std::move(incoming)),
engine_events_(*engine_events),
structured_config_(structured_config) {
ZX_DEBUG_ASSERT(incoming_ != nullptr);
ZX_DEBUG_ASSERT(engine_events != nullptr);
}
DisplayEngine::~DisplayEngine() {}
// static
zx::result<std::unique_ptr<DisplayEngine>> DisplayEngine::Create(
std::shared_ptr<fdf::Namespace> incoming, display::DisplayEngineEventsInterface* engine_events,
structured_config::Config structured_config) {
ZX_DEBUG_ASSERT(incoming != nullptr);
ZX_DEBUG_ASSERT(engine_events != nullptr);
fbl::AllocChecker alloc_checker;
auto display_engine = fbl::make_unique_checked<DisplayEngine>(&alloc_checker, std::move(incoming),
engine_events, structured_config);
if (!alloc_checker.check()) {
fdf::error("Failed to allocate memory for DisplayEngine");
return zx::error(ZX_ERR_NO_MEMORY);
}
const zx_status_t status = display_engine->Initialize();
if (status != ZX_OK) {
fdf::error("Failed to initialize DisplayEngine instance: {}", zx::make_result(status));
return zx::error(status);
}
return zx::ok(std::move(display_engine));
}
} // namespace amlogic_display