blob: 400d532c15d6b4aad2e28e53ddf3663c0b5fdc9e [file]
// Copyright 2024 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/virtio-gpu-display/cpp/imported-images.h"
#include <fidl/fuchsia.images2/cpp/wire.h>
#include <fidl/fuchsia.sysmem2/cpp/wire.h>
#include <lib/driver/logging/cpp/logger.h>
#include <lib/fit/result.h>
#include <lib/zx/process.h>
#include <lib/zx/result.h>
#include <lib/zx/vmo.h>
#include <zircon/assert.h>
#include <zircon/errors.h>
#include <zircon/syscalls/object.h>
#include <zircon/types.h>
#include <cinttypes>
#include <cstdint>
#include <utility>
#include <fbl/alloc_checker.h>
#include <fbl/intrusive_hash_table.h>
#include <fbl/string_printf.h>
#include "src/graphics/display/drivers/virtio-gpu-display/cpp/imported-image.h"
#include "src/graphics/display/lib/api-types/cpp/driver-buffer-collection-id.h"
#include "src/graphics/display/lib/api-types/cpp/driver-image-id.h"
#include "src/graphics/display/lib/api-types/cpp/pixel-format.h"
namespace virtio_display {
ImportedBufferCollection::ImportedBufferCollection(
fidl::ClientEnd<fuchsia_sysmem2::BufferCollection> sysmem_client)
: sysmem_client_(
fidl::WireSyncClient<fuchsia_sysmem2::BufferCollection>(std::move(sysmem_client))) {
ZX_DEBUG_ASSERT(sysmem_client_.is_valid());
}
ImportedBufferCollection::~ImportedBufferCollection() = default;
zx::result<SysmemBufferInfo> ImportedBufferCollection::GetSysmemMetadata(uint32_t buffer_index) {
// TODO(https://fxbug.dev/42072690): The sysmem FIDL error logging patterns are
// inconsistent across drivers. The FIDL error handling and logging should be
// unified.
// Ensure that the WaitForAllBuffersAllocated() call below will return quickly.
fidl::WireResult<fuchsia_sysmem2::BufferCollection::CheckAllBuffersAllocated> check_result =
sysmem_client_->CheckAllBuffersAllocated();
if (!check_result.ok()) {
fdf::error("CheckAllBuffersAllocated() FIDL call failed: {}", check_result.status_string());
return zx::error(check_result.status());
}
const fit::result<fuchsia_sysmem2::wire::Error>& check_response = check_result.value();
if (check_response.is_error()) {
fuchsia_sysmem2::wire::Error error_value = check_response.error_value();
if (error_value == fuchsia_sysmem2::wire::Error::kPending) {
return zx::error(ZX_ERR_SHOULD_WAIT);
}
fdf::error("CheckAllBuffersAllocated() failed: {}", fidl::ToUnderlying(error_value));
return zx::error(ZX_ERR_INTERNAL);
}
fidl::WireResult<fuchsia_sysmem2::BufferCollection::WaitForAllBuffersAllocated> wait_result =
sysmem_client_->WaitForAllBuffersAllocated();
if (!wait_result.ok()) {
fdf::error("WaitForAllBuffersAllocated() FIDL call failed: {}", wait_result.status_string());
return zx::error(wait_result.status());
}
const fit::result<fuchsia_sysmem2::wire::Error,
fuchsia_sysmem2::wire::BufferCollectionWaitForAllBuffersAllocatedResponse*>&
wait_response = wait_result.value();
if (wait_response.is_error()) {
fuchsia_sysmem2::Error error_value = check_response.error_value();
ZX_DEBUG_ASSERT_MSG(error_value != fuchsia_sysmem2::wire::Error::kPending,
"CheckAllBuffersAllocated() returned success incorrectly");
fdf::error("WaitForAllBuffersAllocated() failed: {}", fidl::ToUnderlying(error_value));
return zx::error(ZX_ERR_INTERNAL);
}
fuchsia_sysmem2::wire::BufferCollectionInfo& collection_info =
wait_response->buffer_collection_info();
ZX_DEBUG_ASSERT_MSG(collection_info.has_buffers(), "Sysmem deviated from its contract");
if (buffer_index >= collection_info.buffers().size()) {
fdf::warn("Rejecting access to out-of-range BufferCollection index: {}", buffer_index);
return zx::error(ZX_ERR_OUT_OF_RANGE);
}
fuchsia_sysmem2::wire::VmoBuffer& buffer = collection_info.buffers().at(buffer_index);
ZX_DEBUG_ASSERT_MSG(buffer.has_vmo(), "Sysmem deviated from its contract");
ZX_DEBUG_ASSERT_MSG(buffer.vmo().is_valid(), "Sysmem deviated from its contract");
ZX_DEBUG_ASSERT_MSG(buffer.has_vmo_usable_start(), "Sysmem deviated from its contract");
ZX_DEBUG_ASSERT_MSG(collection_info.has_settings(), "Sysmem deviated from its contract");
ZX_DEBUG_ASSERT_MSG(collection_info.settings().has_buffer_settings(),
"Sysmem deviated from its contract");
fuchsia_sysmem2::wire::BufferMemorySettings& buffer_memory_settings =
collection_info.settings().buffer_settings();
ZX_DEBUG_ASSERT_MSG(buffer_memory_settings.has_coherency_domain(),
"Sysmem deviated from its contract");
ZX_DEBUG_ASSERT_MSG(buffer_memory_settings.has_is_physically_contiguous(),
"Sysmem deviated from its contract");
ZX_DEBUG_ASSERT_MSG(buffer_memory_settings.has_is_secure(), "Sysmem deviated from its contract");
ZX_DEBUG_ASSERT_MSG(buffer_memory_settings.has_size_bytes(), "Sysmem deviated from its contract");
if (!collection_info.settings().has_image_format_constraints()) {
fdf::warn("Rejecting access to BufferCollection without ImageFormatConstraints");
return zx::error(ZX_ERR_INVALID_ARGS);
}
fuchsia_sysmem2::wire::ImageFormatConstraints& image_format_constraints =
collection_info.settings().image_format_constraints();
ZX_DEBUG_ASSERT_MSG(image_format_constraints.has_pixel_format(),
"Sysmem deviated from its contract");
ZX_DEBUG_ASSERT_MSG(image_format_constraints.has_pixel_format_modifier(),
"Sysmem deviated from its contract");
ZX_DEBUG_ASSERT_MSG(image_format_constraints.has_min_size(), "Sysmem deviated from its contract");
ZX_DEBUG_ASSERT_MSG(image_format_constraints.has_min_bytes_per_row(),
"Sysmem deviated from its contract");
uint32_t bytes_per_row_divisor = image_format_constraints.has_bytes_per_row_divisor()
? image_format_constraints.bytes_per_row_divisor()
: 1;
bytes_per_row_divisor = std::max<uint32_t>(bytes_per_row_divisor, 1);
fuchsia_images2::wire::PixelFormat fidl_pixel_format = image_format_constraints.pixel_format();
if (!display::PixelFormat::IsSupported(fidl_pixel_format)) {
fdf::warn("Rejecting access to BufferCollection with unsupported PixelFormat: {}",
static_cast<uint32_t>(fidl_pixel_format));
return zx::error(ZX_ERR_INVALID_ARGS);
}
return zx::ok(SysmemBufferInfo{
.image_vmo = std::move(buffer.vmo()),
.image_vmo_offset = buffer.vmo_usable_start(),
.pixel_format = display::PixelFormat(fidl_pixel_format),
.pixel_format_modifier = image_format_constraints.pixel_format_modifier(),
.minimum_size = image_format_constraints.min_size(),
.minimum_bytes_per_row = image_format_constraints.min_bytes_per_row(),
.bytes_per_row_divisor = bytes_per_row_divisor,
.coherency_domain = buffer_memory_settings.coherency_domain(),
});
}
ImportedImages::ImportedImages(fidl::ClientEnd<fuchsia_sysmem2::Allocator> sysmem_client)
: sysmem_client_(std::move(sysmem_client)) {
ZX_DEBUG_ASSERT(sysmem_client_.is_valid());
}
ImportedImages::~ImportedImages() = default;
namespace {
zx::result<zx_koid_t> GetOwnProcessKoid() {
zx::unowned_process process = zx::process::self();
zx_info_handle_basic_t process_info;
zx_status_t status = process->get_info(ZX_INFO_HANDLE_BASIC, &process_info, sizeof(process_info),
nullptr, nullptr);
if (status != ZX_OK) {
fdf::error("Failed to get process koid: {}", zx::make_result(status));
return zx::error(status);
}
return zx::ok(process_info.koid);
}
} // namespace
zx::result<> ImportedImages::Initialize() {
zx::result<zx_koid_t> koid_result = GetOwnProcessKoid();
if (koid_result.is_error()) {
return koid_result.take_error();
}
// TODO(costan): fbl::StringPrintf() may allocate memory and crash. Fix it to use checked
// dynamic memory allocation.
fbl::String debug_name = fbl::StringPrintf("virtio-gpu-display[%lu]", koid_result.value());
fidl::Arena arena;
fidl::OneWayStatus set_debug_status = sysmem_client_->SetDebugClientInfo(
fuchsia_sysmem2::wire::AllocatorSetDebugClientInfoRequest::Builder(arena)
.name(fidl::StringView::FromExternal(debug_name))
.id(koid_result.value())
.Build());
if (!set_debug_status.ok()) {
fdf::warn("Failed to set sysmem debug info: {}", set_debug_status.status_string());
// This step was not essential to the initialization process. Sysmem debug
// info is a developer convenience, and the driver can operate without it.
}
return zx::ok();
}
zx::result<> ImportedImages::ImportBufferCollection(
display::DriverBufferCollectionId buffer_collection_id,
fidl::ClientEnd<fuchsia_sysmem2::BufferCollectionToken> buffer_collection_token) {
auto buffer_collection_it = buffer_collections_.find(buffer_collection_id);
if (buffer_collection_it != buffer_collections_.end()) {
fdf::warn("Rejected BufferCollection import request with existing ID: {}",
buffer_collection_id.value());
return zx::error(ZX_ERR_ALREADY_EXISTS);
}
auto [collection_client_endpoint, collection_server_endpoint] =
fidl::Endpoints<fuchsia_sysmem2::BufferCollection>::Create();
// TODO(costan): fidl::Arena may allocate memory and crash. Find a way to get
// control over memory allocation.
fidl::Arena arena;
fidl::OneWayStatus bind_result = sysmem_client_->BindSharedCollection(
fuchsia_sysmem2::wire::AllocatorBindSharedCollectionRequest::Builder(arena)
.token(std::move(buffer_collection_token))
.buffer_collection_request(std::move(collection_server_endpoint))
.Build());
if (!bind_result.ok()) {
fdf::error("FIDL call BindSharedCollection failed: {}", bind_result.status_string());
return zx::error(ZX_ERR_INTERNAL);
}
fbl::AllocChecker alloc_checker;
auto buffer_collection_node = fbl::make_unique_checked<ImportedBufferCollectionNode>(
&alloc_checker, buffer_collection_id, std::move(collection_client_endpoint));
if (!alloc_checker.check()) {
fdf::error("Failed to allocate memory for ImportedBufferCollectionNode");
return zx::error(ZX_ERR_NO_MEMORY);
}
buffer_collections_.insert(std::move(buffer_collection_node));
return zx::ok();
}
zx::result<> ImportedImages::ReleaseBufferCollection(
display::DriverBufferCollectionId buffer_collection_id) {
auto buffer_collection_it = buffer_collections_.find(buffer_collection_id);
if (buffer_collection_it == buffer_collections_.end()) {
fdf::warn("Rejected request to release BufferCollection with unknown ID: {}",
buffer_collection_id.value());
return zx::error(ZX_ERR_NOT_FOUND);
}
buffer_collections_.erase(buffer_collection_it);
return zx::ok();
}
zx::result<display::DriverImageId> ImportedImages::ImportImage(
display::DriverBufferCollectionId buffer_collection_id, uint32_t buffer_index) {
auto buffer_collection_it = buffer_collections_.find(buffer_collection_id);
if (buffer_collection_it == buffer_collections_.end()) {
fdf::warn("Rejected request to release BufferCollection with unknown ID: {}",
buffer_collection_id.value());
return zx::error(ZX_ERR_NOT_FOUND);
}
zx::result<SysmemBufferInfo> sysmem_buffer_info =
buffer_collection_it->imported_buffer_collection.GetSysmemMetadata(buffer_index);
if (sysmem_buffer_info.is_error()) {
// GetSysmemMetadata() already logged the error.
return sysmem_buffer_info.take_error();
}
const display::DriverImageId image_id = next_image_id_;
fbl::AllocChecker alloc_checker;
auto image_node = fbl::make_unique_checked<ImportedImageNode>(
&alloc_checker, image_id, std::move(sysmem_buffer_info).value());
if (!alloc_checker.check()) {
fdf::error("Failed to allocate memory for ImportedImageNode");
return zx::error(ZX_ERR_NO_MEMORY);
}
images_.insert(std::move(image_node));
next_image_id_ = static_cast<display::DriverImageId>(next_image_id_.value() + 1);
return zx::ok(image_id);
}
zx::result<> ImportedImages::ReleaseImage(display::DriverImageId image_id) {
auto image_it = images_.find(image_id);
if (image_it == images_.end()) {
fdf::warn("Rejected request to release Image with unknown ID: {}", image_id.value());
return zx::error(ZX_ERR_NOT_FOUND);
}
images_.erase(image_it);
return zx::ok();
}
ImportedBufferCollection* ImportedImages::FindBufferCollectionById(
display::DriverBufferCollectionId buffer_collection_id) {
auto buffer_collection_it = buffer_collections_.find(buffer_collection_id);
if (buffer_collection_it == buffer_collections_.end()) {
return nullptr;
}
return &buffer_collection_it->imported_buffer_collection;
}
SysmemBufferInfo* ImportedImages::FindSysmemInfoById(display::DriverImageId image_id) {
auto image_it = images_.find(image_id);
if (image_it == images_.end()) {
return nullptr;
}
return &image_it->sysmem_buffer_info;
}
ImportedImage* ImportedImages::FindImageById(display::DriverImageId image_id) {
auto image_it = images_.find(image_id);
if (image_it == images_.end()) {
return nullptr;
}
return &image_it->imported_image;
}
ImportedImages::ImportedBufferCollectionNode::ImportedBufferCollectionNode(
display::DriverBufferCollectionId id, fidl::ClientEnd<fuchsia_sysmem2::BufferCollection> client)
: id(id), imported_buffer_collection(std::move(client)) {}
ImportedImages::ImportedBufferCollectionNode::~ImportedBufferCollectionNode() = default;
ImportedImages::ImportedImageNode::ImportedImageNode(display::DriverImageId id,
SysmemBufferInfo sysmem_buffer_info)
: id(id),
sysmem_buffer_info(std::move(sysmem_buffer_info)),
imported_image(ImportedImage::CreateEmpty()) {
ZX_DEBUG_ASSERT(this->sysmem_buffer_info.image_vmo.is_valid());
}
ImportedImages::ImportedImageNode::~ImportedImageNode() = default;
} // namespace virtio_display