blob: fabf806516fd93eb641c9084432d4bb40f8e023e [file]
// 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/lib/magma/include/virtio/virtio_magma.h"
#include <fuchsia/virtualization/cpp/fidl.h>
#include <fuchsia/virtualization/hardware/cpp/fidl.h>
#include <lib/zx/socket.h>
#include <string.h>
#include <fbl/algorithm.h>
#include "src/graphics/lib/magma/include/magma_abi/magma.h"
#include "src/virtualization/bin/vmm/device/test_with_device.h"
#include "src/virtualization/bin/vmm/device/virtio_queue_fake.h"
namespace {
// VirtioMagma links against libmagma, which requires an instantiated hardware
// device under /dev/class/gpu. Since the library interface cannot be
// redirected as a FIDL interface would be, a separate package must be used.
// The mock package is identical except for which libmagma it links against.
static constexpr char kVirtioMagmaUrl[] =
"fuchsia-pkg://fuchsia.com/virtio_magma_mock_system#meta/"
"virtio_magma_mock_system.cmx";
static constexpr uint16_t kQueueSize = 32;
static constexpr size_t kDescriptorSize = PAGE_SIZE;
static constexpr uint32_t kVirtioMagmaVmarSize = 1 << 16;
static constexpr uint32_t kAllocateFlags = ZX_VM_CAN_MAP_READ | ZX_VM_CAN_MAP_WRITE;
static const size_t kBufferSize = 65536;
static const uint32_t kMockVfdId = 42;
class WaylandImporterMock : public fuchsia::virtualization::hardware::VirtioWaylandImporter {
public:
// |fuchsia::virtualization::hardware::VirtioWaylandImporter|
void Import(zx::vmo vmo, ImportCallback callback) override {
EXPECT_EQ(zx_object_get_info(vmo.get(), ZX_INFO_HANDLE_VALID, nullptr, 0, nullptr, nullptr),
ZX_OK);
zx_info_handle_basic_t handle_info{};
EXPECT_EQ(zx_object_get_info(vmo.get(), ZX_INFO_HANDLE_BASIC, &handle_info, sizeof(handle_info),
nullptr, nullptr),
ZX_OK);
EXPECT_EQ(handle_info.type, ZX_OBJ_TYPE_VMO);
callback(kMockVfdId);
}
};
class VirtioMagmaTest : public TestWithDevice {
public:
VirtioMagmaTest()
: out_queue_(phys_mem_, kDescriptorSize, kQueueSize),
wayland_importer_mock_binding_(&wayland_importer_mock_),
wayland_importer_mock_loop_(&kAsyncLoopConfigNoAttachToCurrentThread) {}
void SetUp() override {
uintptr_t vmar_addr;
zx::vmar vmar;
ASSERT_EQ(zx::vmar::root_self()->allocate2(kAllocateFlags, 0u, kVirtioMagmaVmarSize, &vmar,
&vmar_addr),
ZX_OK);
fuchsia::virtualization::hardware::StartInfo start_info;
zx_status_t status = LaunchDevice(kVirtioMagmaUrl, out_queue_.end(), &start_info);
ASSERT_EQ(ZX_OK, status);
// Start device execution.
ASSERT_EQ(wayland_importer_mock_loop_.StartThread(), ZX_OK);
services_->Connect(magma_.NewRequest());
magma_->Start(
std::move(start_info), std::move(vmar),
wayland_importer_mock_binding_.NewBinding(wayland_importer_mock_loop_.dispatcher()),
&status);
if (status == ZX_ERR_NOT_FOUND) {
ADD_FAILURE() << "Failed to start VirtioMagma because no GPU devices were found.";
}
ASSERT_EQ(ZX_OK, status);
// Configure device queues.
out_queue_.Configure(0, kDescriptorSize);
status = magma_->ConfigureQueue(0, out_queue_.size(), out_queue_.desc(), out_queue_.avail(),
out_queue_.used());
ASSERT_EQ(ZX_OK, status);
}
std::optional<VirtioQueueFake::UsedElement> NextUsed(VirtioQueueFake* queue) {
auto elem = queue->NextUsed();
while (!elem && WaitOnInterrupt() == ZX_OK) {
elem = queue->NextUsed();
}
return elem;
}
protected:
// Note: use of sync can be problematic here if the test environment needs to handle
// some incoming FIDL requests.
fuchsia::virtualization::hardware::VirtioMagmaSyncPtr magma_;
VirtioQueueFake out_queue_;
WaylandImporterMock wayland_importer_mock_;
fidl::Binding<fuchsia::virtualization::hardware::VirtioWaylandImporter>
wayland_importer_mock_binding_;
async::Loop wayland_importer_mock_loop_;
};
TEST_F(VirtioMagmaTest, HandleQuery) {
virtio_magma_query2_ctrl_t request{};
request.hdr.type = VIRTIO_MAGMA_CMD_QUERY2;
request.id = MAGMA_QUERY_DEVICE_ID;
virtio_magma_query2_resp_t* response = nullptr;
uint16_t descriptor_id;
ASSERT_EQ(DescriptorChainBuilder(out_queue_)
.AppendReadableDescriptor(&request, sizeof(request))
.AppendWritableDescriptor(&response, sizeof(*response))
.Build(&descriptor_id),
ZX_OK);
ASSERT_EQ(magma_->NotifyQueue(0), ZX_OK);
auto used_elem = NextUsed(&out_queue_);
EXPECT_TRUE(used_elem);
EXPECT_EQ(used_elem->id, descriptor_id);
EXPECT_EQ(used_elem->len, sizeof(*response));
EXPECT_EQ(response->hdr.type, VIRTIO_MAGMA_RESP_QUERY2);
EXPECT_EQ(response->hdr.flags, 0u);
EXPECT_GT(response->value_out, 0u);
EXPECT_EQ(static_cast<magma_status_t>(response->result_return), MAGMA_STATUS_OK);
}
TEST_F(VirtioMagmaTest, HandleConnectionMethod) {
uint64_t connection = 0;
{ // Create a new connection
virtio_magma_create_connection2_ctrl_t request{};
request.hdr.type = VIRTIO_MAGMA_CMD_CREATE_CONNECTION2;
virtio_magma_create_connection2_resp_t* response = nullptr;
uint16_t descriptor_id;
ASSERT_EQ(DescriptorChainBuilder(out_queue_)
.AppendReadableDescriptor(&request, sizeof(request))
.AppendWritableDescriptor(&response, sizeof(*response))
.Build(&descriptor_id),
ZX_OK);
ASSERT_EQ(magma_->NotifyQueue(0), ZX_OK);
auto used_elem = NextUsed(&out_queue_);
EXPECT_TRUE(used_elem);
EXPECT_EQ(used_elem->id, descriptor_id);
EXPECT_EQ(used_elem->len, sizeof(*response));
EXPECT_EQ(response->hdr.type, VIRTIO_MAGMA_RESP_CREATE_CONNECTION2);
EXPECT_EQ(response->hdr.flags, 0u);
EXPECT_GT(response->connection_out, 0u);
ASSERT_EQ(static_cast<magma_status_t>(response->result_return), MAGMA_STATUS_OK);
connection = response->connection_out;
}
{ // Try to call a method on the connection
virtio_magma_get_error_ctrl_t request{};
request.hdr.type = VIRTIO_MAGMA_CMD_GET_ERROR;
request.connection = connection;
virtio_magma_get_error_resp_t* response = nullptr;
uint16_t descriptor_id;
ASSERT_EQ(DescriptorChainBuilder(out_queue_)
.AppendReadableDescriptor(&request, sizeof(request))
.AppendWritableDescriptor(&response, sizeof(*response))
.Build(&descriptor_id),
ZX_OK);
ASSERT_EQ(magma_->NotifyQueue(0), ZX_OK);
auto used_elem = NextUsed(&out_queue_);
EXPECT_TRUE(used_elem);
EXPECT_EQ(used_elem->id, descriptor_id);
EXPECT_EQ(used_elem->len, sizeof(*response));
EXPECT_EQ(response->hdr.type, VIRTIO_MAGMA_RESP_GET_ERROR);
EXPECT_EQ(response->hdr.flags, 0u);
ASSERT_EQ(static_cast<magma_status_t>(response->result_return), MAGMA_STATUS_OK);
}
}
TEST_F(VirtioMagmaTest, HandleExport) {
uint64_t connection = 0;
magma_buffer_t buffer{};
{ // Create a new connection
virtio_magma_create_connection2_ctrl_t request{};
request.hdr.type = VIRTIO_MAGMA_CMD_CREATE_CONNECTION2;
virtio_magma_create_connection2_resp_t* response = nullptr;
uint16_t descriptor_id;
ASSERT_EQ(DescriptorChainBuilder(out_queue_)
.AppendReadableDescriptor(&request, sizeof(request))
.AppendWritableDescriptor(&response, sizeof(*response))
.Build(&descriptor_id),
ZX_OK);
ASSERT_EQ(magma_->NotifyQueue(0), ZX_OK);
auto used_elem = NextUsed(&out_queue_);
EXPECT_TRUE(used_elem);
EXPECT_EQ(used_elem->id, descriptor_id);
EXPECT_EQ(used_elem->len, sizeof(*response));
EXPECT_EQ(response->hdr.type, VIRTIO_MAGMA_RESP_CREATE_CONNECTION2);
EXPECT_EQ(response->hdr.flags, 0u);
EXPECT_GT(response->connection_out, 0u);
ASSERT_EQ(static_cast<magma_status_t>(response->result_return), MAGMA_STATUS_OK);
connection = response->connection_out;
}
{ // Create a buffer
virtio_magma_create_buffer_ctrl_t request{};
request.hdr.type = VIRTIO_MAGMA_CMD_CREATE_BUFFER;
request.connection = connection;
request.size = kBufferSize;
virtio_magma_create_buffer_resp_t* response = nullptr;
uint16_t descriptor_id;
ASSERT_EQ(DescriptorChainBuilder(out_queue_)
.AppendReadableDescriptor(&request, sizeof(request))
.AppendWritableDescriptor(&response, sizeof(*response))
.Build(&descriptor_id),
ZX_OK);
ASSERT_EQ(magma_->NotifyQueue(0), ZX_OK);
auto used_elem = NextUsed(&out_queue_);
EXPECT_TRUE(used_elem);
EXPECT_EQ(used_elem->id, descriptor_id);
EXPECT_EQ(used_elem->len, sizeof(*response));
EXPECT_EQ(response->hdr.type, VIRTIO_MAGMA_RESP_CREATE_BUFFER);
EXPECT_EQ(response->hdr.flags, 0u);
EXPECT_NE(response->buffer_out, 0u);
EXPECT_GE(response->size_out, kBufferSize); // The implementation is free to use a larger size.
ASSERT_EQ(static_cast<magma_status_t>(response->result_return), MAGMA_STATUS_OK);
buffer = response->buffer_out;
}
{ // Export the buffer
virtio_magma_export_ctrl_t request{};
request.hdr.type = VIRTIO_MAGMA_CMD_EXPORT;
request.connection = connection;
request.buffer = buffer;
virtio_magma_export_resp_t* response = nullptr;
uint16_t descriptor_id;
ASSERT_EQ(DescriptorChainBuilder(out_queue_)
.AppendReadableDescriptor(&request, sizeof(request))
.AppendWritableDescriptor(&response, sizeof(*response))
.Build(&descriptor_id),
ZX_OK);
ASSERT_EQ(magma_->NotifyQueue(0), ZX_OK);
auto used_elem = NextUsed(&out_queue_);
EXPECT_TRUE(used_elem);
EXPECT_EQ(used_elem->id, descriptor_id);
EXPECT_EQ(used_elem->len, sizeof(*response));
EXPECT_EQ(response->hdr.type, VIRTIO_MAGMA_RESP_EXPORT);
EXPECT_EQ(response->hdr.flags, 0u);
EXPECT_EQ(response->buffer_handle_out, kMockVfdId);
ASSERT_EQ(static_cast<magma_status_t>(response->result_return), MAGMA_STATUS_OK);
}
}
} // namespace