blob: 7b5825035fff74d625ba27dbea2da3e79d71514e [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/display/drivers/coordinator/tests/fidl_client.h"
#include <fidl/fuchsia.hardware.display.types/cpp/wire.h>
#include <fidl/fuchsia.hardware.display/cpp/wire.h>
#include <fidl/fuchsia.sysmem2/cpp/fidl.h>
#include <lib/async/cpp/task.h>
#include <lib/sysmem-version/sysmem-version.h>
#include <zircon/assert.h>
#include <fbl/auto_lock.h>
#include <gtest/gtest.h>
#include "src/graphics/display/lib/api-types-cpp/buffer-collection-id.h"
#include "src/graphics/display/lib/api-types-cpp/event-id.h"
#include "src/graphics/display/lib/api-types-cpp/image-id.h"
#include "src/graphics/display/lib/api-types-cpp/layer-id.h"
#include "src/graphics/display/lib/api-types-cpp/vsync-ack-cookie.h"
#include "src/graphics/display/lib/driver-framework-migration-utils/logging/zxlogf.h"
#include "src/lib/testing/predicates/status.h"
#include "zircon/status.h"
namespace fhd = fuchsia_hardware_display;
namespace fhdt = fuchsia_hardware_display_types;
namespace sysmem1 = fuchsia_sysmem;
namespace sysmem2 = fuchsia_sysmem2;
namespace display {
TestFidlClient::Display::Display(const fhd::wire::Info& info) {
id_ = ToDisplayId(info.id);
for (size_t i = 0; i < info.pixel_format.count(); i++) {
pixel_formats_.push_back(info.pixel_format[i]);
}
for (size_t i = 0; i < info.modes.count(); i++) {
modes_.push_back(info.modes[i]);
}
manufacturer_name_ = fbl::String(info.manufacturer_name.data());
monitor_name_ = fbl::String(info.monitor_name.data());
monitor_serial_ = fbl::String(info.monitor_serial.data());
image_metadata_.height = modes_[0].vertical_resolution;
image_metadata_.width = modes_[0].horizontal_resolution;
image_metadata_.tiling_type = fhdt::wire::kImageTilingTypeLinear;
}
DisplayId TestFidlClient::display_id() const { return displays_[0].id_; }
bool TestFidlClient::CreateChannel(const fidl::WireSyncClient<fhd::Provider>& provider,
bool is_vc) {
auto [dc_client, dc_server] = fidl::Endpoints<fhd::Coordinator>::Create();
zxlogf(INFO, "Opening coordinator");
if (is_vc) {
auto response = provider->OpenCoordinatorForVirtcon(std::move(dc_server));
if (!response.ok()) {
zxlogf(ERROR, "Could not open Virtcon coordinator, error=%s",
response.FormatDescription().c_str());
return false;
}
} else {
auto response = provider->OpenCoordinatorForPrimary(std::move(dc_server));
if (!response.ok()) {
zxlogf(ERROR, "Could not open coordinator, error=%s", response.FormatDescription().c_str());
return false;
}
}
fbl::AutoLock lock(mtx());
dc_.Bind(std::move(dc_client));
return true;
}
zx::result<ImageId> TestFidlClient::CreateImage() {
return ImportImageWithSysmem(displays_[0].image_metadata_);
}
zx::result<LayerId> TestFidlClient::CreateLayer() {
fbl::AutoLock lock(mtx());
return CreateLayerLocked();
}
zx::result<TestFidlClient::EventInfo> TestFidlClient::CreateEvent() {
fbl::AutoLock lock(mtx());
return CreateEventLocked();
}
zx::result<LayerId> TestFidlClient::CreateLayerLocked() {
ZX_DEBUG_ASSERT(dc_);
auto reply = dc_->CreateLayer();
if (!reply.ok()) {
zxlogf(ERROR, "Failed to create layer (fidl=%d)", reply.status());
return zx::error(reply.status());
} else if (reply.value().is_error() != ZX_OK) {
zxlogf(ERROR, "Failed to create layer: %s", zx_status_get_string(reply.value().error_value()));
return zx::error(reply.value().error_value());
}
EXPECT_EQ(
dc_->SetLayerPrimaryConfig(reply.value()->layer_id, displays_[0].image_metadata_).status(),
ZX_OK);
return zx::ok(ToLayerId(reply.value()->layer_id));
}
zx::result<TestFidlClient::EventInfo> TestFidlClient::CreateEventLocked() {
zx::event event;
if (auto status = zx::event::create(0u, &event); status != ZX_OK) {
zxlogf(ERROR, "Failed to create zx::event: %d", status);
return zx::error(status);
}
zx_info_handle_basic_t info;
if (auto status = event.get_info(ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr);
status != ZX_OK) {
zxlogf(ERROR, "Failed to get zx handle (%u) info: %d", event.get(), status);
return zx::error(status);
}
zx::event dup;
if (auto status = event.duplicate(ZX_RIGHT_SAME_RIGHTS, &dup); status != ZX_OK) {
zxlogf(ERROR, "Failed to duplicate zx event (%u): %d", event.get(), status);
return zx::error(status);
}
const EventId event_id(info.koid);
auto import_result = dc_->ImportEvent(std::move(event), ToFidlEventId(event_id));
if (!import_result.ok()) {
zxlogf(ERROR, "Failed to import event to display controller: %d", import_result.status());
}
return zx::ok(EventInfo{
.id = event_id,
.event = std::move(dup),
});
}
bool TestFidlClient::Bind(async_dispatcher_t* dispatcher) {
dispatcher_ = dispatcher;
while (displays_.is_empty() || !has_ownership_) {
fbl::AutoLock lock(mtx());
class EventHandler : public fidl::WireSyncEventHandler<fhd::Coordinator> {
public:
explicit EventHandler(TestFidlClient* client) : client_(client) {}
bool ok() const { return ok_; }
void OnDisplaysChanged(fidl::WireEvent<fhd::Coordinator::OnDisplaysChanged>* event) override {
for (size_t i = 0; i < event->added.count(); i++) {
client_->displays_.push_back(Display(event->added[i]));
}
}
void OnVsync(fidl::WireEvent<fhd::Coordinator::OnVsync>* event) override { ok_ = false; }
void OnClientOwnershipChange(
fidl::WireEvent<fhd::Coordinator::OnClientOwnershipChange>* event) override {
client_->has_ownership_ = event->has_ownership;
}
private:
TestFidlClient* const client_;
bool ok_ = true;
};
EventHandler event_handler(this);
auto result = dc_.HandleOneEvent(event_handler);
if (!result.ok() || !event_handler.ok()) {
zxlogf(ERROR, "Got unexpected message");
return false;
}
}
fbl::AutoLock lock(mtx());
EXPECT_TRUE(has_ownership_);
EXPECT_FALSE(displays_.is_empty());
event_msg_wait_event_.set_object(dc_.client_end().channel().get());
event_msg_wait_event_.set_trigger(ZX_CHANNEL_READABLE);
EXPECT_OK(event_msg_wait_event_.Begin(dispatcher));
return dc_->EnableVsync(true).ok();
}
void TestFidlClient::OnEventMsgAsync(async_dispatcher_t* dispatcher, async::WaitBase* self,
zx_status_t status, const zx_packet_signal_t* signal) {
if (status != ZX_OK) {
return;
}
if (!(signal->observed & ZX_CHANNEL_READABLE)) {
return;
}
fbl::AutoLock lock(mtx());
class EventHandler : public fidl::WireSyncEventHandler<fhd::Coordinator> {
public:
explicit EventHandler(TestFidlClient* client) : client_(client) {}
void OnDisplaysChanged(fidl::WireEvent<fhd::Coordinator::OnDisplaysChanged>* event) override {}
// The FIDL bindings do not know that the caller holds mtx(), so we can't TA_REQ(mtx()) here.
void OnVsync(fidl::WireEvent<fhd::Coordinator::OnVsync>* event) override
TA_NO_THREAD_SAFETY_ANALYSIS {
client_->vsync_count_++;
client_->recent_presented_config_stamp_ = event->applied_config_stamp;
VsyncAckCookie vsync_ack_cookie = ToVsyncAckCookie(event->cookie);
if (vsync_ack_cookie != kInvalidVsyncAckCookie) {
client_->vsync_ack_cookie_ = vsync_ack_cookie;
}
}
void OnClientOwnershipChange(
fidl::WireEvent<fhd::Coordinator::OnClientOwnershipChange>* message) override {}
private:
TestFidlClient* const client_;
};
EventHandler event_handler(this);
auto result = dc_.HandleOneEvent(event_handler);
if (!result.ok()) {
zxlogf(ERROR, "Failed to handle events: %s", result.FormatDescription().c_str());
return;
}
if (event_msg_wait_event_.object() == ZX_HANDLE_INVALID) {
return;
}
// Re-arm the wait.
self->Begin(dispatcher);
}
TestFidlClient::~TestFidlClient() {
if (dispatcher_) {
// Cancel must be issued from the dispatcher thread.
sync_completion_t done;
auto task = new async::Task();
task->set_handler(
[this, done_ptr = &done](async_dispatcher_t*, async::Task* task_ptr, zx_status_t) {
// Ensures that `task` gets deleted when the handler completes.
std::unique_ptr<async::Task> task(task_ptr);
event_msg_wait_event_.Cancel();
event_msg_wait_event_.set_object(ZX_HANDLE_INVALID);
sync_completion_signal(done_ptr);
});
if (task->Post(dispatcher_) != ZX_OK) {
delete task;
event_msg_wait_event_.Cancel();
event_msg_wait_event_.set_object(ZX_HANDLE_INVALID);
} else {
while (true) {
if (sync_completion_wait(&done, ZX_MSEC(10)) == ZX_OK) {
break;
}
}
}
}
}
zx_status_t TestFidlClient::PresentLayers(std::vector<PresentLayerInfo> present_layers) {
fbl::AutoLock l(mtx());
std::vector<fhd::wire::LayerId> fidl_layers;
for (const auto& info : present_layers) {
fidl_layers.push_back(ToFidlLayerId(info.layer_id));
}
if (auto reply =
dc_->SetDisplayLayers(ToFidlDisplayId(display_id()),
fidl::VectorView<fhd::wire::LayerId>::FromExternal(fidl_layers));
!reply.ok()) {
return reply.status();
}
for (const auto& info : present_layers) {
const fhd::wire::LayerId fidl_layer_id = ToFidlLayerId(info.layer_id);
const EventId wait_event_id = info.image_ready_wait_event_id.value_or(kInvalidEventId);
if (auto reply = dc_->SetLayerImage(fidl_layer_id, ToFidlImageId(info.image_id),
/*wait_event_id=*/ToFidlEventId(wait_event_id),
/*signal_event_id=*/ToFidlEventId(kInvalidEventId));
!reply.ok()) {
return reply.status();
}
}
if (auto reply = dc_->CheckConfig(false);
!reply.ok() || reply.value().res != fhdt::wire::ConfigResult::kOk) {
return reply.ok() ? ZX_ERR_INVALID_ARGS : reply.status();
}
return dc_->ApplyConfig().status();
}
fhdt::wire::ConfigStamp TestFidlClient::GetRecentAppliedConfigStamp() {
fbl::AutoLock lock(mtx());
EXPECT_TRUE(dc_);
auto result = dc_->GetLatestAppliedConfigStamp();
EXPECT_TRUE(result.ok());
return result.value().stamp;
}
zx::result<ImageId> TestFidlClient::ImportImageWithSysmem(
const fhdt::wire::ImageMetadata& image_metadata) {
fbl::AutoLock lock(mtx());
return ImportImageWithSysmemLocked(image_metadata);
}
std::vector<TestFidlClient::PresentLayerInfo> TestFidlClient::CreateDefaultPresentLayerInfo() {
zx::result<LayerId> layer_result = CreateLayer();
EXPECT_OK(layer_result.status_value());
zx::result<ImageId> image_result = ImportImageWithSysmem(displays_[0].image_metadata_);
EXPECT_OK(image_result.status_value());
return {
{.layer_id = layer_result.value(),
.image_id = image_result.value(),
.image_ready_wait_event_id = std::nullopt},
};
}
zx::result<ImageId> TestFidlClient::ImportImageWithSysmemLocked(
const fhdt::wire::ImageMetadata& image_metadata) {
// Create all the tokens.
fidl::WireSyncClient<sysmem2::BufferCollectionToken> local_token;
{
auto [client, server] = fidl::Endpoints<sysmem2::BufferCollectionToken>::Create();
fidl::Arena arena;
auto allocate_shared_request =
sysmem2::wire::AllocatorAllocateSharedCollectionRequest::Builder(arena);
allocate_shared_request.token_request(std::move(server));
auto result = sysmem_->AllocateSharedCollection(allocate_shared_request.Build());
if (!result.ok()) {
zxlogf(ERROR, "Failed to allocate shared collection: %s", result.status_string());
return zx::error(result.status());
}
local_token = fidl::WireSyncClient<sysmem2::BufferCollectionToken>(std::move(client));
EXPECT_NE(ZX_HANDLE_INVALID, local_token.client_end().channel().get());
}
auto [client, server] = fidl::Endpoints<sysmem2::BufferCollectionToken>::Create();
{
fidl::Arena arena;
auto duplicate_request = sysmem2::wire::BufferCollectionTokenDuplicateRequest::Builder(arena);
duplicate_request.rights_attenuation_mask(ZX_RIGHT_SAME_RIGHTS);
duplicate_request.token_request(std::move(server));
if (auto result = local_token->Duplicate(duplicate_request.Build()); !result.ok()) {
zxlogf(ERROR, "Failed to duplicate token: %s", result.FormatDescription().c_str());
return zx::error(ZX_ERR_NO_MEMORY);
}
}
// Set display buffer constraints.
static BufferCollectionId next_display_collection_id(0);
const BufferCollectionId display_collection_id = ++next_display_collection_id;
if (auto result = local_token->Sync(); !result.ok()) {
zxlogf(ERROR, "Failed to sync token %d %s", result.status(),
result.FormatDescription().c_str());
return zx::error(result.status());
}
const fuchsia_hardware_display::wire::BufferCollectionId fidl_display_collection_id =
ToFidlBufferCollectionId(display_collection_id);
const auto result = dc_->ImportBufferCollection(
fidl_display_collection_id,
fidl::ClientEnd<sysmem1::BufferCollectionToken>(client.TakeChannel()));
if (!result.ok()) {
zxlogf(ERROR, "Failed to call FIDL ImportBufferCollection %lu (%s)",
display_collection_id.value(), result.status_string());
return zx::error(result.status());
}
if (result.value().is_error()) {
zxlogf(ERROR, "Failed to import buffer collection %lu (%s)", display_collection_id.value(),
zx_status_get_string(result.value().error_value()));
return zx::error(result.value().error_value());
}
const fhdt::wire::ImageBufferUsage image_buffer_usage = {
.tiling_type = image_metadata.tiling_type,
};
const auto set_constraints_result =
dc_->SetBufferCollectionConstraints(fidl_display_collection_id, image_buffer_usage);
if (!set_constraints_result.ok()) {
zxlogf(ERROR, "Failed to call FIDL SetBufferCollectionConstraints %lu (%s)",
display_collection_id.value(), set_constraints_result.status_string());
(void)dc_->ReleaseBufferCollection(fidl_display_collection_id);
return zx::error(set_constraints_result.status());
}
if (set_constraints_result.value().is_error()) {
zxlogf(ERROR, "Failed to set buffer collection constraints: %s",
zx_status_get_string(set_constraints_result.value().error_value()));
(void)dc_->ReleaseBufferCollection(fidl_display_collection_id);
return zx::error(set_constraints_result.value().error_value());
}
// Use the local collection so we can read out the error if allocation
// fails, and to ensure everything's allocated before trying to import it
// into another process.
fidl::WireSyncClient<sysmem2::BufferCollection> sysmem_collection;
{
auto [client, server] = fidl::Endpoints<sysmem2::BufferCollection>::Create();
fidl::Arena arena;
auto bind_shared_request = sysmem2::wire::AllocatorBindSharedCollectionRequest::Builder(arena);
bind_shared_request.token(local_token.TakeClientEnd());
bind_shared_request.buffer_collection_request(std::move(server));
if (auto result = sysmem_->BindSharedCollection(bind_shared_request.Build()); !result.ok()) {
zxlogf(ERROR, "Failed to bind shared collection: %s", result.FormatDescription().c_str());
return zx::error(result.status());
}
sysmem_collection = fidl::WireSyncClient<sysmem2::BufferCollection>(std::move(client));
}
// TODO(https://fxbug.dev/42180237) Consider handling the error instead of ignoring it.
fidl::Arena arena;
auto set_name_request = sysmem2::wire::NodeSetNameRequest::Builder(arena);
set_name_request.priority(10000u);
set_name_request.name("display-client-unittest");
(void)sysmem_collection->SetName(set_name_request.Build());
arena.Reset();
auto constraints = sysmem2::wire::BufferCollectionConstraints::Builder(arena);
constraints.min_buffer_count(1);
constraints.usage(
sysmem2::wire::BufferUsage::Builder(arena).none(sysmem2::wire::kNoneUsage).Build());
// We specify min_size_bytes 1 so that something is specifying a minimum size. More typically the
// display client would specify ImageFormatConstraints that implies a non-zero min_size_bytes.
constraints.buffer_memory_constraints(sysmem2::wire::BufferMemoryConstraints::Builder(arena)
.min_size_bytes(1)
.ram_domain_supported(true)
.Build());
auto set_constraints_request =
sysmem2::wire::BufferCollectionSetConstraintsRequest::Builder(arena);
set_constraints_request.constraints(constraints.Build());
zx_status_t status = sysmem_collection->SetConstraints(set_constraints_request.Build()).status();
if (status != ZX_OK) {
zxlogf(ERROR, "Unable to set constraints (%d)", status);
return zx::error(status);
}
// Wait for the buffers to be allocated.
auto info_result = sysmem_collection->WaitForAllBuffersAllocated();
if (!info_result.ok()) {
zxlogf(ERROR, "Waiting for buffers failed (fidl=%d res=%u)", info_result.status(),
fidl::ToUnderlying(info_result->error_value()));
zx_status_t status = info_result.status();
if (status == ZX_OK) {
status = sysmem::V1CopyFromV2Error(info_result->error_value());
}
return zx::error(status);
}
auto& info = info_result.value()->buffer_collection_info();
if (info.buffers().count() < 1) {
zxlogf(ERROR, "Incorrect buffer collection count %zu", info.buffers().count());
return zx::error(ZX_ERR_NO_MEMORY);
}
const ImageId image_id = next_image_id_++;
const fhd::wire::ImageId fidl_image_id = ToFidlImageId(image_id);
const auto import_result =
dc_->ImportImage(image_metadata,
fhd::wire::BufferId{
.buffer_collection_id = fidl_display_collection_id,
.buffer_index = 0,
},
fidl_image_id);
if (!import_result.ok()) {
zxlogf(ERROR, "Failed to call FIDL ImportImage %" PRIu64 " (%s)", fidl_image_id.value,
import_result.status_string());
return zx::error(import_result.status());
}
if (import_result.value().is_error()) {
zxlogf(ERROR, "Failed to import image %" PRIu64 " (%s)", fidl_image_id.value,
zx_status_get_string(import_result.value().error_value()));
return zx::error(import_result.value().error_value());
}
// TODO(https://fxbug.dev/42180237) Consider handling the error instead of ignoring it.
(void)sysmem_collection->Release();
return zx::ok(image_id);
}
} // namespace display