blob: fdb532853abe33f09312c3b42eec8ff0988224b2 [file] [log] [blame]
// Copyright 2019 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-guest/v2/gpu-device-driver.h"
#include <fidl/fuchsia.hardware.pci/cpp/wire_test_base.h>
#include <fidl/fuchsia.hardware.sysmem/cpp/wire_test_base.h>
#include <fidl/fuchsia.sysmem2/cpp/wire_test_base.h>
#include <lib/async_patterns/testing/cpp/dispatcher_bound.h>
#include <lib/driver/component/cpp/driver_base.h>
#include <lib/driver/testing/cpp/driver_lifecycle.h>
#include <lib/driver/testing/cpp/driver_runtime.h>
#include <lib/driver/testing/cpp/test_environment.h>
#include <lib/driver/testing/cpp/test_node.h>
#include <lib/fdf/env.h>
#include <lib/fdf/testing.h>
#include <lib/syslog/cpp/macros.h>
#include <fbl/auto_lock.h>
#include <gtest/gtest.h>
#include "src/devices/testing/fake-bti/include/lib/fake-bti/bti.h"
#include "src/graphics/display/lib/api-types-cpp/driver-buffer-collection-id.h"
#include "src/lib/testing/predicates/status.h"
namespace {
// The GPU Virtual Device ID is 0x1040 + 16 = 0x1050, in accordance with
// Section 4.1.2.1 and Section 5 of the VIRTIO V1.2 Spec:
// https://docs.oasis-open.org/virtio/virtio/v1.2/csd01/virtio-v1.2-csd01.html#x1-2160005
// This also matches the definition at
// //zircon/system/ulib/virtio/include/virtio/virtio.h
constexpr int kTestDeviceId = 0x1050;
// The PCI Subsystem Vendor ID according to the same spec linked above.
constexpr int kTestSubsysDeviceId = 0x1af4;
// Use a stub buffer collection instead of the real sysmem since some tests may
// require things that aren't available on the current system.
//
// TODO(https://fxbug.dev/42072949): Consider creating and using a unified set of sysmem
// testing doubles instead of writing mocks for each display driver test.
class StubBufferCollection : public fidl::testing::WireTestBase<fuchsia_sysmem2::BufferCollection> {
public:
void SetConstraints(SetConstraintsRequestView request,
SetConstraintsCompleter::Sync& _completer) override {
if (!request->has_constraints()) {
return;
}
auto& image_constraints = request->constraints().image_format_constraints()[0];
EXPECT_EQ(fuchsia_images2::wire::PixelFormat::kB8G8R8A8, image_constraints.pixel_format());
EXPECT_EQ(4u, image_constraints.bytes_per_row_divisor());
}
void CheckAllBuffersAllocated(CheckAllBuffersAllocatedCompleter::Sync& completer) override {
completer.Reply(zx::ok());
}
void WaitForAllBuffersAllocated(WaitForAllBuffersAllocatedCompleter::Sync& _completer) override {
auto info = fuchsia_sysmem2::wire::BufferCollectionInfo::Builder(arena_);
info.settings(
fuchsia_sysmem2::wire::SingleBufferSettings::Builder(arena_)
.image_format_constraints(
fuchsia_sysmem2::wire::ImageFormatConstraints::Builder(arena_)
.pixel_format(fuchsia_images2::wire::PixelFormat::kB8G8R8A8)
.pixel_format_modifier(fuchsia_images2::wire::PixelFormatModifier::kLinear)
.max_size(fuchsia_math::wire::SizeU{
.width = 1000, .height = std::numeric_limits<uint32_t>::max()})
.max_bytes_per_row(4000)
.Build())
.Build());
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(4096, 0, &vmo));
info.buffers(std::vector{fuchsia_sysmem2::wire::VmoBuffer::Builder(arena_)
.vmo(std::move(vmo))
.vmo_usable_start(0)
.Build()});
auto response =
fuchsia_sysmem2::wire::BufferCollectionWaitForAllBuffersAllocatedResponse::Builder(arena_)
.buffer_collection_info(info.Build())
.Build();
_completer.Reply(fit::ok(&response));
}
void NotImplemented_(const std::string& name, fidl::CompleterBase& completer) override {
EXPECT_TRUE(false);
}
private:
fidl::Arena<fidl::kDefaultArenaInitialCapacity> arena_;
};
class MockAllocator : public fidl::testing::WireTestBase<fuchsia_sysmem2::Allocator> {
public:
explicit MockAllocator() = default;
explicit MockAllocator(async_dispatcher_t* dispatcher) : dispatcher_(dispatcher) {
EXPECT_TRUE(dispatcher_);
}
explicit MockAllocator(fidl::ServerEnd<fuchsia_sysmem2::Allocator> server_end) {
binding_group_.AddBinding(fdf::Dispatcher::GetCurrent()->async_dispatcher(),
std::move(server_end), this, fidl::kIgnoreBindingClosure);
}
zx_status_t Connect(fidl::ServerEnd<fuchsia_sysmem2::Allocator> request) {
binding_group_.AddBinding(fdf::Dispatcher::GetCurrent()->async_dispatcher(), std::move(request),
this, fidl::kIgnoreBindingClosure);
return ZX_OK;
}
void BindSharedCollection(BindSharedCollectionRequestView request,
BindSharedCollectionCompleter::Sync& completer) override {
display::DriverBufferCollectionId buffer_collection_id = next_buffer_collection_id_++;
fbl::AutoLock lock(&lock_);
active_buffer_collections_.emplace(
buffer_collection_id,
BufferCollection{.token_client = std::move(request->token()),
.unowned_collection_server = request->buffer_collection_request(),
.mock_buffer_collection = std::make_unique<StubBufferCollection>()});
auto ref = fidl::BindServer(
fdf::Dispatcher::GetCurrent()->async_dispatcher(),
std::move(request->buffer_collection_request()),
active_buffer_collections_.at(buffer_collection_id).mock_buffer_collection.get(),
[this, buffer_collection_id](StubBufferCollection*, fidl::UnbindInfo,
fidl::ServerEnd<fuchsia_sysmem2::BufferCollection>) {
fbl::AutoLock lock(&lock_);
inactive_buffer_collection_tokens_.push_back(
std::move(active_buffer_collections_.at(buffer_collection_id).token_client));
active_buffer_collections_.erase(buffer_collection_id);
});
}
void SetDebugClientInfo(SetDebugClientInfoRequestView request,
SetDebugClientInfoCompleter::Sync& completer) override {
EXPECT_EQ(request->name().get().find("virtio-gpu-display"), 0u);
}
std::vector<fidl::UnownedClientEnd<fuchsia_sysmem2::BufferCollectionToken>>
GetActiveBufferCollectionTokenClients() const {
fbl::AutoLock lock(&lock_);
std::vector<fidl::UnownedClientEnd<fuchsia_sysmem2::BufferCollectionToken>>
unowned_token_clients;
unowned_token_clients.reserve(active_buffer_collections_.size());
for (const auto& kv : active_buffer_collections_) {
unowned_token_clients.push_back(kv.second.token_client);
}
return unowned_token_clients;
}
std::vector<fidl::UnownedClientEnd<fuchsia_sysmem2::BufferCollectionToken>>
GetInactiveBufferCollectionTokenClients() const {
fbl::AutoLock lock(&lock_);
std::vector<fidl::UnownedClientEnd<fuchsia_sysmem2::BufferCollectionToken>>
unowned_token_clients;
unowned_token_clients.reserve(inactive_buffer_collection_tokens_.size());
for (const auto& token : inactive_buffer_collection_tokens_) {
unowned_token_clients.push_back(token);
}
return unowned_token_clients;
}
void NotImplemented_(const std::string& name, fidl::CompleterBase& completer) override {
EXPECT_TRUE(false);
}
private:
fidl::ServerBindingGroup<fuchsia_sysmem2::Allocator> binding_group_;
struct BufferCollection {
fidl::ClientEnd<fuchsia_sysmem2::BufferCollectionToken> token_client;
fidl::UnownedServerEnd<fuchsia_sysmem2::BufferCollection> unowned_collection_server;
std::unique_ptr<StubBufferCollection> mock_buffer_collection;
};
mutable fbl::Mutex lock_;
std::unordered_map<display::DriverBufferCollectionId, BufferCollection> active_buffer_collections_
__TA_GUARDED(lock_);
std::vector<fidl::ClientEnd<fuchsia_sysmem2::BufferCollectionToken>>
inactive_buffer_collection_tokens_ __TA_GUARDED(lock_);
display::DriverBufferCollectionId next_buffer_collection_id_ =
display::DriverBufferCollectionId(0);
async_dispatcher_t* dispatcher_ = nullptr;
};
class FakeSysmem : public fidl::testing::WireTestBase<fuchsia_hardware_sysmem::Sysmem> {
public:
fuchsia_hardware_sysmem::Service::InstanceHandler GetInstanceHandler() {
return fuchsia_hardware_sysmem::Service::InstanceHandler({
.sysmem = sysmem_bindings_.CreateHandler(
this, fdf::Dispatcher::GetCurrent()->async_dispatcher(), fidl::kIgnoreBindingClosure),
.allocator_v1 = [](fidl::ServerEnd<fuchsia_sysmem::Allocator> request) {},
.allocator_v2 =
allocator_v2_.bind_handler(fdf::Dispatcher::GetCurrent()->async_dispatcher()),
});
}
void NotImplemented_(const std::string& name, ::fidl::CompleterBase& completer) override {
completer.Close(ZX_ERR_NOT_SUPPORTED);
}
private:
MockAllocator allocator_v2_;
fidl::ServerBindingGroup<fuchsia_hardware_sysmem::Sysmem> sysmem_bindings_;
};
// Implement all the WireServer handlers of fuchsia_hardware_pci::Device as protocol as required by
// FIDL.
class FakePciParent : public fidl::WireServer<fuchsia_hardware_pci::Device> {
public:
fuchsia_hardware_pci::Service::InstanceHandler GetInstanceHandler() {
return fuchsia_hardware_pci::Service::InstanceHandler({
.device = binding_group_.CreateHandler(
this, fdf_dispatcher_get_async_dispatcher(fdf_dispatcher_get_current_dispatcher()),
fidl::kIgnoreBindingClosure),
});
}
void GetDeviceInfo(GetDeviceInfoCompleter::Sync& completer) override {
fuchsia_hardware_pci::wire::DeviceInfo info;
info.device_id = kTestDeviceId;
completer.Reply(info);
}
void GetBar(GetBarRequestView request, GetBarCompleter::Sync& completer) override {
fuchsia_hardware_pci::wire::Bar bar;
completer.ReplySuccess(std::move(bar));
}
void SetBusMastering(SetBusMasteringRequestView request,
SetBusMasteringCompleter::Sync& completer) override {
completer.ReplySuccess();
}
void ResetDevice(ResetDeviceCompleter::Sync& completer) override { completer.ReplySuccess(); }
void AckInterrupt(AckInterruptCompleter::Sync& completer) override { completer.ReplySuccess(); }
void MapInterrupt(MapInterruptRequestView request,
MapInterruptCompleter::Sync& completer) override {
zx::interrupt interrupt;
completer.ReplySuccess(std::move(interrupt));
}
void GetInterruptModes(GetInterruptModesCompleter::Sync& completer) override {
fuchsia_hardware_pci::wire::InterruptModes modes;
completer.Reply(modes);
}
void SetInterruptMode(SetInterruptModeRequestView request,
SetInterruptModeCompleter::Sync& completer) override {
completer.ReplySuccess();
}
void ReadConfig8(ReadConfig8RequestView request, ReadConfig8Completer::Sync& completer) override {
completer.ReplySuccess(0);
}
void ReadConfig16(ReadConfig16RequestView request,
ReadConfig16Completer::Sync& completer) override {
completer.ReplySuccess(kTestSubsysDeviceId);
}
void ReadConfig32(ReadConfig32RequestView request,
ReadConfig32Completer::Sync& completer) override {
completer.ReplySuccess(0);
}
void WriteConfig8(WriteConfig8RequestView request,
WriteConfig8Completer::Sync& completer) override {
completer.ReplySuccess();
}
void WriteConfig16(WriteConfig16RequestView request,
WriteConfig16Completer::Sync& completer) override {
completer.ReplySuccess();
}
void WriteConfig32(WriteConfig32RequestView request,
WriteConfig32Completer::Sync& completer) override {
completer.ReplySuccess();
}
void GetCapabilities(GetCapabilitiesRequestView request,
GetCapabilitiesCompleter::Sync& completer) override {
std::vector<uint8_t> empty_vec;
auto empty_vec_view = fidl::VectorView<uint8_t>::FromExternal(empty_vec);
completer.Reply(empty_vec_view);
}
void GetExtendedCapabilities(GetExtendedCapabilitiesRequestView request,
GetExtendedCapabilitiesCompleter::Sync& completer) override {
std::vector<uint16_t> empty_vec;
auto empty_vec_view = fidl::VectorView<uint16_t>::FromExternal(empty_vec);
completer.Reply(empty_vec_view);
}
void GetBti(GetBtiRequestView request, GetBtiCompleter::Sync& completer) override {
zx_handle_t fake_handle;
fake_bti_create(&fake_handle);
zx::bti bti(fake_handle);
completer.ReplySuccess(std::move(bti));
}
fidl::ServerBindingGroup<fuchsia_hardware_pci::Device> binding_group_;
};
using fdf_testing::TestEnvironment;
class VirtioGpuTest : public ::testing::Test {
public:
VirtioGpuTest() = default;
void SetUp() override {
// Create start args.
zx::result start_args = node_server_.SyncCall(&fdf_testing::TestNode::CreateStartArgsAndServe);
EXPECT_EQ(ZX_OK, start_args.status_value());
// Serve outgoing directory.
driver_outgoing_ = std::move(start_args->outgoing_directory_client);
auto svc_endpoints = fidl::CreateEndpoints<fuchsia_io::Directory>();
EXPECT_EQ(ZX_OK, svc_endpoints.status_value());
zx_status_t status = fdio_open_at(driver_outgoing_.handle()->get(), "/svc",
static_cast<uint32_t>(fuchsia_io::OpenFlags::kDirectory),
svc_endpoints->server.TakeChannel().release());
EXPECT_EQ(ZX_OK, status);
// Setup the test environment.
zx::result init_result = test_environment_.SyncCall(
&TestEnvironment::Initialize, std::move(start_args->incoming_directory_server));
EXPECT_EQ(ZX_OK, init_result.status_value());
auto pci_handler = fake_pci_parent_.SyncCall(&FakePciParent::GetInstanceHandler);
test_environment_.SyncCall(
[](TestEnvironment* env, auto handler) {
zx::result result = env->incoming_directory().AddService<fuchsia_hardware_pci::Service>(
std::move(handler));
EXPECT_TRUE(result.is_ok());
},
std::move(pci_handler));
// Serve Fake Sysmem.
fuchsia_hardware_sysmem::Service::InstanceHandler sysmem_handler =
sysmem_server_.SyncCall(&FakeSysmem::GetInstanceHandler);
test_environment_.SyncCall(
[](TestEnvironment* env, auto handler) {
zx::result result =
env->incoming_directory().AddService<fuchsia_hardware_sysmem::Service>(
std::move(handler));
EXPECT_TRUE(result.is_ok());
},
std::move(sysmem_handler));
// Start driver.
zx::result start_result = runtime_.RunToCompletion(
driver_.SyncCall(&fdf_testing::DriverUnderTest<virtio_display::GpuDeviceDriver>::Start,
std::move(start_args->start_args)));
EXPECT_EQ(ZX_OK, start_result.status_value());
}
void TearDown() override {
zx::result stop_result = runtime_.RunToCompletion(driver_.SyncCall(
&fdf_testing::DriverUnderTest<virtio_display::GpuDeviceDriver>::PrepareStop));
EXPECT_EQ(ZX_OK, stop_result.status_value());
}
async_dispatcher_t* env_dispatcher() { return env_dispatcher_->async_dispatcher(); }
private:
// Attaches a foreground dispatcher for us automatically.
fdf_testing::DriverRuntime runtime_;
// Env dispatcher runs in the background because we need to make sync calls into it.
fdf::UnownedSynchronizedDispatcher driver_dispatcher_ = runtime_.StartBackgroundDispatcher();
fdf::UnownedSynchronizedDispatcher env_dispatcher_ = runtime_.StartBackgroundDispatcher();
async_patterns::TestDispatcherBound<fdf_testing::TestNode> node_server_{
env_dispatcher(), std::in_place, std::string("root")};
async_patterns::TestDispatcherBound<TestEnvironment> test_environment_{env_dispatcher(),
std::in_place};
async_patterns::TestDispatcherBound<fdf_testing::DriverUnderTest<virtio_display::GpuDeviceDriver>>
driver_{driver_dispatcher_->async_dispatcher(), std::in_place};
async_patterns::TestDispatcherBound<FakePciParent> fake_pci_parent_{env_dispatcher(),
std::in_place};
async_patterns::TestDispatcherBound<FakeSysmem> sysmem_server_{env_dispatcher(), std::in_place};
fidl::ClientEnd<fuchsia_io::Directory> driver_outgoing_;
};
TEST_F(VirtioGpuTest, CreateAndStartDriver) {}
} // namespace