blob: a536a9324fe8935d6c5cc5fcf001844d4483e29d [file] [log] [blame]
// Copyright 2020 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/drivers/misc/goldfish_control/host_visible_heap.h"
#include <fidl/fuchsia.hardware.goldfish/cpp/wire.h>
#include <fidl/fuchsia.sysmem2/cpp/wire.h>
#include <lib/async-loop/default.h>
#include <lib/async-loop/loop.h>
#include <lib/async/cpp/task.h>
#include <lib/ddk/debug.h>
#include <lib/ddk/trace/event.h>
#include <lib/fidl/llcpp/server.h>
#include <lib/fit/defer.h>
#include <lib/fit/function.h>
#include <lib/fpromise/result.h>
#include <lib/zx/vmar.h>
#include <zircon/assert.h>
#include <zircon/rights.h>
#include <zircon/types.h>
#include <memory>
#include <fbl/algorithm.h>
#include "src/graphics/drivers/misc/goldfish_control/control_device.h"
#include "src/lib/fsl/handles/object_info.h"
namespace goldfish {
namespace {
static const char* kTag = "goldfish-host-visible-heap";
fuchsia_sysmem2::wire::HeapProperties GetHeapProperties(fidl::AnyArena& allocator) {
fuchsia_sysmem2::wire::CoherencyDomainSupport coherency_domain_support(allocator);
coherency_domain_support.set_cpu_supported(true)
.set_ram_supported(true)
.set_inaccessible_supported(false);
fuchsia_sysmem2::wire::HeapProperties heap_properties(allocator);
heap_properties
.set_coherency_domain_support(allocator, std::move(coherency_domain_support))
// Allocated VMOs are not directly writeable since they are physical
// VMOs on MMIO; Also, contents of VMOs allocated by this Heap are only
// valid after |CreateColorBuffer()| render control call. Thus it doesn't
// work for sysmem to clear the VMO contents, instead we do map-and-clear
// in the end of |CreateResource()|.
.set_need_clear(false);
return heap_properties;
}
zx_status_t CheckSingleBufferSettings(
const fuchsia_sysmem2::wire::SingleBufferSettings& single_buffer_settings) {
bool has_image_format_constraints = single_buffer_settings.has_image_format_constraints();
bool has_buffer_settings = single_buffer_settings.has_buffer_settings();
if (!has_buffer_settings && !has_image_format_constraints) {
zxlogf(ERROR,
"[%s][%s] Both buffer_settings and image_format_constraints "
"are missing, SingleBufferSettings is invalid.",
kTag, __func__);
return ZX_ERR_INVALID_ARGS;
}
if (has_image_format_constraints) {
const auto& image_constraints = single_buffer_settings.image_format_constraints();
if (!image_constraints.has_pixel_format() || !image_constraints.pixel_format().has_type() ||
!image_constraints.has_min_coded_width() || !image_constraints.has_min_coded_height()) {
zxlogf(ERROR,
"[%s][%s] image_constraints missing arguments: pixel_format %d width %d height %d",
kTag, __func__, image_constraints.has_pixel_format(),
image_constraints.has_min_coded_width(), image_constraints.has_min_coded_height());
return ZX_ERR_INVALID_ARGS;
}
}
if (has_buffer_settings) {
const auto& buffer_settings = single_buffer_settings.buffer_settings();
if (!buffer_settings.has_size_bytes()) {
zxlogf(ERROR, "[%s][%s] buffer_settings missing arguments: size %d", kTag, __func__,
buffer_settings.has_size_bytes());
return ZX_ERR_INVALID_ARGS;
}
}
return ZX_OK;
}
fpromise::result<fuchsia_hardware_goldfish::wire::CreateColorBuffer2Params, zx_status_t>
GetCreateColorBuffer2Params(fidl::AnyArena& allocator,
const fuchsia_sysmem2::wire::SingleBufferSettings& buffer_settings,
uint64_t paddr) {
using fuchsia_hardware_goldfish::wire::ColorBufferFormatType;
using fuchsia_hardware_goldfish::wire::CreateColorBuffer2Params;
using fuchsia_sysmem2::wire::PixelFormatType;
ZX_DEBUG_ASSERT(buffer_settings.has_image_format_constraints());
const auto& image_constraints = buffer_settings.image_format_constraints();
ZX_DEBUG_ASSERT(
image_constraints.has_pixel_format() && image_constraints.pixel_format().has_type() &&
image_constraints.has_min_coded_width() && image_constraints.has_min_coded_height());
// TODO(fxbug.dev/59804): Support other pixel formats.
const auto& pixel_format_type = image_constraints.pixel_format().type();
ColorBufferFormatType color_buffer_format;
switch (pixel_format_type) {
case PixelFormatType::kBgra32:
color_buffer_format = ColorBufferFormatType::kBgra;
break;
case PixelFormatType::kR8G8B8A8:
color_buffer_format = ColorBufferFormatType::kRgba;
break;
case PixelFormatType::kR8:
color_buffer_format = ColorBufferFormatType::kLuminance;
break;
case PixelFormatType::kR8G8:
color_buffer_format = ColorBufferFormatType::kRg;
break;
default:
zxlogf(ERROR, "[%s][%s] pixel_format_type unsupported: type %u", __func__, kTag,
pixel_format_type);
return fpromise::error(ZX_ERR_NOT_SUPPORTED);
}
uint32_t width = image_constraints.min_coded_width();
if (image_constraints.has_required_max_coded_width()) {
width = std::max(width, image_constraints.required_max_coded_width());
}
width = fbl::round_up(width, image_constraints.has_coded_width_divisor()
? image_constraints.coded_width_divisor()
: 1);
uint32_t height = image_constraints.min_coded_height();
if (image_constraints.has_required_max_coded_height()) {
height = std::max(height, image_constraints.required_max_coded_height());
}
height = fbl::round_up(height, image_constraints.has_coded_height_divisor()
? image_constraints.coded_height_divisor()
: 1);
CreateColorBuffer2Params buffer2_params(allocator);
buffer2_params.set_width(width)
.set_height(height)
.set_memory_property(fuchsia_hardware_goldfish::wire::kMemoryPropertyHostVisible)
.set_physical_address(allocator, paddr)
.set_format(color_buffer_format);
return fpromise::ok(std::move(buffer2_params));
}
fuchsia_hardware_goldfish::wire::CreateBuffer2Params GetCreateBuffer2Params(
fidl::AnyArena& allocator,
const fuchsia_sysmem2::wire::SingleBufferSettings& single_buffer_settings, uint64_t paddr) {
using fuchsia_hardware_goldfish::wire::CreateBuffer2Params;
using fuchsia_sysmem2::wire::PixelFormatType;
ZX_DEBUG_ASSERT(single_buffer_settings.has_buffer_settings());
const auto& buffer_settings = single_buffer_settings.buffer_settings();
ZX_DEBUG_ASSERT(buffer_settings.has_size_bytes());
uint64_t size_bytes = buffer_settings.size_bytes();
CreateBuffer2Params buffer2_params(allocator);
buffer2_params.set_size(allocator, size_bytes)
.set_memory_property(fuchsia_hardware_goldfish::wire::kMemoryPropertyHostVisible)
.set_physical_address(allocator, paddr);
return buffer2_params;
}
} // namespace
HostVisibleHeap::Block::Block(zx::vmo vmo, uint64_t paddr, fit::closure deallocate_callback,
async_dispatcher_t* dispatcher)
: vmo(std::move(vmo)),
paddr(paddr),
wait_deallocate(this->vmo.get(), ZX_VMO_ZERO_CHILDREN, 0u,
[deallocate_callback = std::move(deallocate_callback)](
async_dispatcher_t* dispatcher, async::Wait* wait, zx_status_t status,
const zx_packet_signal_t* signal) { deallocate_callback(); }) {
wait_deallocate.Begin(dispatcher);
}
// static
std::unique_ptr<HostVisibleHeap> HostVisibleHeap::Create(Control* control) {
// Using `new` to access a non-public constructor.
return std::unique_ptr<HostVisibleHeap>(new HostVisibleHeap(control));
}
HostVisibleHeap::HostVisibleHeap(Control* control) : Heap(control, kTag) {}
HostVisibleHeap::~HostVisibleHeap() = default;
void HostVisibleHeap::AllocateVmo(AllocateVmoRequestView request,
AllocateVmoCompleter::Sync& completer) {
TRACE_DURATION("gfx", "HostVisibleHeap::AllocateVmo", "size", request->size);
auto result = control()->address_space_child()->AllocateBlock(request->size);
if (!result.ok()) {
zxlogf(ERROR, "[%s] AllocateBlock FIDL call failed: status %d", kTag, result.status());
completer.Reply(result.status(), zx::vmo{});
return;
}
if (result.value().res != ZX_OK) {
zxlogf(ERROR, "[%s] AllocateBlock failed: res %d", kTag, result.value().res);
completer.Reply(result.value().res, zx::vmo{});
return;
}
// We need to clean up the allocated block if |zx_vmo_create_child| or
// |fsl::GetKoid| fails, which could happen before we create and bind the
// |DeallocateBlock()| wait in |Block|.
auto cleanup_block = fit::defer([this, paddr = result.value().paddr] {
auto result = control()->address_space_child()->DeallocateBlock(paddr);
if (!result.ok()) {
zxlogf(ERROR, "[%s] DeallocateBlock FIDL call failed: status %d", kTag, result.status());
} else if (result.value().res != ZX_OK) {
zxlogf(ERROR, "[%s] DeallocateBlock failed: res %d", kTag, result.value().res);
}
});
zx::vmo vmo = std::move(result.value().vmo);
zx::vmo child;
zx_status_t status = vmo.create_child(ZX_VMO_CHILD_SLICE, /*offset=*/0u, request->size, &child);
if (status != ZX_OK) {
zxlogf(ERROR, "[%s] zx_vmo_create_child failed: %d", kTag, status);
completer.Close(status);
return;
}
zx_handle_t child_handle = child.get();
zx_koid_t child_koid = fsl::GetKoid(child_handle);
if (child_koid == ZX_KOID_INVALID) {
zxlogf(ERROR, "[%s] fsl::GetKoid failed: child_handle %u", kTag, child_handle);
completer.Close(ZX_ERR_BAD_HANDLE);
return;
}
blocks_.try_emplace(
child_koid, std::move(vmo), result.value().paddr,
[this, child_koid] { DeallocateVmo(child_koid); }, loop()->dispatcher());
cleanup_block.cancel();
completer.Reply(ZX_OK, std::move(child));
}
void HostVisibleHeap::DeallocateVmo(zx_koid_t koid) {
TRACE_DURATION("gfx", "HostVisibleHeap::DeallocateVmo");
ZX_DEBUG_ASSERT(koid != ZX_KOID_INVALID);
ZX_DEBUG_ASSERT(blocks_.find(koid) != blocks_.end());
uint64_t paddr = blocks_.at(koid).paddr;
blocks_.erase(koid);
auto result = control()->address_space_child()->DeallocateBlock(paddr);
if (!result.ok()) {
zxlogf(ERROR, "[%s] DeallocateBlock FIDL call error: status %d", kTag, result.status());
} else if (result.value().res != ZX_OK) {
zxlogf(ERROR, "[%s] DeallocateBlock failed: res %d", kTag, result.value().res);
}
}
void HostVisibleHeap::CreateResource(CreateResourceRequestView request,
CreateResourceCompleter::Sync& completer) {
using fuchsia_hardware_goldfish::wire::ColorBufferFormatType;
using fuchsia_hardware_goldfish::wire::CreateColorBuffer2Params;
using fuchsia_sysmem2::wire::PixelFormatType;
ZX_DEBUG_ASSERT(request->vmo.is_valid());
zx_status_t status = CheckSingleBufferSettings(request->buffer_settings);
if (status != ZX_OK) {
zxlogf(ERROR, "[%s] Invalid single buffer settings", kTag);
completer.Reply(status, 0u);
return;
}
bool is_image = request->buffer_settings.has_image_format_constraints();
TRACE_DURATION(
"gfx", "HostVisibleHeap::CreateResource", "type", is_image ? "image" : "buffer",
"image:width",
is_image ? request->buffer_settings.image_format_constraints().min_coded_width() : 0,
"image:height",
is_image ? request->buffer_settings.image_format_constraints().min_coded_height() : 0,
"image:format",
is_image ? static_cast<int>(
request->buffer_settings.image_format_constraints().pixel_format().type())
: 0,
"buffer:size", is_image ? 0 : request->buffer_settings.buffer_settings().size_bytes());
// Get |paddr| of the |Block| to use in Buffer create params.
zx_info_vmo_t vmo_info;
status = request->vmo.get_info(ZX_INFO_VMO, &vmo_info, sizeof(vmo_info), nullptr, nullptr);
if (status != ZX_OK) {
zxlogf(ERROR, "[%s] zx_object_get_info failed: status %d", kTag, status);
completer.Close(status);
return;
}
// The |vmo| passed in to this function is the child of VMO returned by
// |AllocateVmo()| above. So the key should be the parent koid of |vmo|.
zx_koid_t vmo_parent_koid = vmo_info.parent_koid;
if (blocks_.find(vmo_parent_koid) == blocks_.end()) {
zxlogf(ERROR, "[%s] Cannot find parent VMO koid in heap: parent_koid %lu", kTag,
vmo_parent_koid);
completer.Close(ZX_ERR_INVALID_ARGS);
return;
}
uint64_t paddr = blocks_.at(vmo_parent_koid).paddr;
// Duplicate VMO to create ColorBuffer/Buffer.
zx::vmo vmo_dup;
status = request->vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &vmo_dup);
if (status != ZX_OK) {
zxlogf(ERROR, "[%s] zx_handle_duplicate failed: %d", kTag, status);
completer.Close(status);
return;
}
// Register buffer handle for VMO.
uint64_t id = control()->RegisterBufferHandle(request->vmo);
if (id == ZX_KOID_INVALID) {
completer.Close(ZX_ERR_INVALID_ARGS);
return;
}
// If the following part fails, we need to free the ColorBuffer/Buffer
// handle so that there is no handle/resource leakage.
auto cleanup_handle = fit::defer([this, id] { control()->FreeBufferHandle(id); });
if (is_image) {
fidl::Arena allocator;
// ColorBuffer creation.
auto create_params = GetCreateColorBuffer2Params(allocator, request->buffer_settings, paddr);
if (create_params.is_error()) {
completer.Reply(create_params.error(), 0u);
return;
}
// Create actual ColorBuffer and map physical address |paddr| to
// address of the ColorBuffer's host memory.
auto result = control()->CreateColorBuffer2(std::move(vmo_dup), create_params.take_value());
if (result.is_error()) {
zxlogf(ERROR, "[%s] CreateColorBuffer error: status %d", kTag, status);
completer.Close(result.error());
return;
}
if (result.value().res != ZX_OK) {
zxlogf(ERROR, "[%s] CreateColorBuffer2 failed: res = %d", kTag, result.value().res);
completer.Reply(result.value().res, 0u);
return;
}
// Host visible ColorBuffer should have page offset of zero, otherwise
// part of the page mapped from address space device not used for the
// ColorBuffer can be leaked.
ZX_DEBUG_ASSERT(result.value().hw_address_page_offset == 0u);
} else {
fidl::Arena allocator;
// Data buffer creation.
auto create_params = GetCreateBuffer2Params(allocator, request->buffer_settings, paddr);
// Create actual data buffer and map physical address |paddr| to
// address of the buffer's host memory.
auto result = control()->CreateBuffer2(allocator, std::move(vmo_dup), std::move(create_params));
if (result.is_error()) {
zxlogf(ERROR, "[%s] CreateBuffer2 error: status %d", kTag, status);
completer.Close(result.error());
return;
}
if (result.value().is_err()) {
zxlogf(ERROR, "[%s] CreateBuffer2 failed: res = %d", kTag, result.value().err());
completer.Reply(result.value().err(), 0u);
return;
}
// Host visible Buffer should have page offset of zero, otherwise
// part of the page mapped from address space device not used for
// the buffer can be leaked.
ZX_DEBUG_ASSERT(result.value().response().hw_address_page_offset == 0u);
}
// Heap should fill VMO with zeroes before returning it to clients.
// Since VMOs allocated by address space device are physical VMOs not
// supporting zx_vmo_write, we map it and fill the mapped memory address
// with zero.
uint64_t vmo_size;
status = request->vmo.get_size(&vmo_size);
if (status != ZX_OK) {
zxlogf(ERROR, "[%s] zx_vmo_get_size failed: %d", kTag, status);
completer.Close(status);
return;
}
zx_paddr_t addr;
status = zx::vmar::root_self()->map(ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, /*vmar_offset=*/0u,
request->vmo,
/*vmo_offset=*/0u, vmo_size, &addr);
if (status != ZX_OK) {
zxlogf(ERROR, "[%s] zx_vmar_map failed: %d", kTag, status);
completer.Close(status);
return;
}
memset(reinterpret_cast<uint8_t*>(addr), 0u, vmo_size);
status = zx::vmar::root_self()->unmap(addr, vmo_size);
if (status != ZX_OK) {
zxlogf(ERROR, "[%s] zx_vmar_unmap failed: %d", kTag, status);
completer.Close(status);
return;
}
// Everything is done, now we can cancel the cleanup auto call.
cleanup_handle.cancel();
completer.Reply(ZX_OK, id);
}
void HostVisibleHeap::DestroyResource(DestroyResourceRequestView request,
DestroyResourceCompleter::Sync& completer) {
// This destroys the color buffer associated with |id| and frees the color
// buffer handle |id|.
control()->FreeBufferHandle(request->id);
completer.Reply();
}
void HostVisibleHeap::Bind(zx::channel server_request) {
auto allocator = std::make_unique<fidl::Arena<512>>();
fuchsia_sysmem2::wire::HeapProperties heap_properties = GetHeapProperties(*allocator.get());
BindWithHeapProperties(std::move(server_request), std::move(allocator),
std::move(heap_properties));
}
} // namespace goldfish