blob: 6c706b67b6de94209595587987fcebe4977f27af [file] [log] [blame]
// Copyright 2017 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 <machina/gpu.h>
#include <fbl/unique_ptr.h>
#include <unittest/unittest.h>
#include "virtio_queue_fake.h"
static constexpr uint32_t kDisplayWidth = 1024;
static constexpr uint32_t kDisplayHeight = 768;
static constexpr uint32_t kPixelFormat = VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM;
static constexpr uint16_t kQueueSize = 32;
static constexpr uint32_t kRootResourceId = 1;
static constexpr uint32_t kScanoutId = 0;
struct BackingPages : public fbl::SinglyLinkedListable<fbl::unique_ptr<BackingPages>> {
BackingPages(size_t size): buffer(new uint8_t[size]), len(size) {}
fbl::unique_ptr<uint8_t[]> buffer;
size_t len;
};
// Scanout that uses an in-memory buffer instead of a framebuffer device.
class TestScanout : public GpuScanout {
public:
TestScanout(uint32_t width, uint32_t height, uint32_t format,
fbl::unique_ptr<uint8_t[]> buffer)
: GpuScanout(width, height, format, buffer.get()), fb_(fbl::move(buffer)) {}
private:
fbl::unique_ptr<uint8_t[]> fb_;
};
class VirtioGpuTest {
public:
VirtioGpuTest() : gpu_(0, UINTPTR_MAX), control_queue_(&gpu_.control_queue()) {}
zx_status_t Init() {
zx_status_t status = control_queue_.Init(kQueueSize);
if (status != ZX_OK)
return status;
return CreateScanout(kDisplayWidth, kDisplayHeight);
}
VirtioGpu& gpu() { return gpu_; }
VirtioQueueFake& control_queue() { return control_queue_; }
uint8_t* scanout_buffer() const { return scanout_buffer_; }
size_t scanout_size() const { return scanout_size_; }
fbl::SinglyLinkedList<fbl::unique_ptr<BackingPages>>& backing_pages() { return backing_pages_; }
zx_status_t CreateScanout(uint32_t width, uint32_t height) {
size_t scanout_size = width * height * VirtioGpu::kBytesPerPixel;
fbl::unique_ptr<uint8_t[]> buffer(new uint8_t[scanout_size]);
scanout_buffer_ = buffer.get();
scanout_size_ = scanout_size;
auto scanout = fbl::make_unique<TestScanout>(
width, height, kPixelFormat, fbl::move(buffer));
gpu_.AddScanout(fbl::move(scanout));
return ZX_OK;
}
zx_status_t CreateRootResource() {
virtio_gpu_resource_create_2d_t request = {};
request.hdr.type = VIRTIO_GPU_CMD_RESOURCE_CREATE_2D;
request.format = kPixelFormat;
request.resource_id = kRootResourceId;
request.width = kDisplayWidth;
request.height = kDisplayHeight;
uint16_t desc = 0;
virtio_gpu_ctrl_hdr_t response = {};
zx_status_t status = control_queue().BuildDescriptor()
.AppendReadable(&request, sizeof(request))
.AppendWriteable(&response, sizeof(response))
.Build(&desc);
if (status != ZX_OK)
return status;
uint32_t used;
status = gpu_.HandleGpuCommand(&gpu_.control_queue(), desc, &used);
if (status != ZX_OK)
return status;
return response.type == VIRTIO_GPU_RESP_OK_NODATA ? ZX_OK : response.type;
}
// Attaches a single, contiguous memory region.
zx_status_t AttachBacking() {
virtio_gpu_resource_attach_backing_t request = {};
request.hdr.type = VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING;
request.resource_id = kRootResourceId;
request.nr_entries = 1;
uint32_t size = kDisplayWidth * kDisplayHeight * VirtioGpu::kBytesPerPixel;
auto backing = fbl::make_unique<BackingPages>(size);
virtio_gpu_mem_entry_t entry = {};
entry.addr = reinterpret_cast<uint64_t>(backing->buffer.get());
entry.length = size;
backing_pages_.push_front(fbl::move(backing));
uint16_t desc = 0;
virtio_gpu_ctrl_hdr_t response = {};
zx_status_t status = control_queue().BuildDescriptor()
.AppendReadable(&request, sizeof(request))
.AppendReadable(&entry, sizeof(entry))
.AppendWriteable(&response, sizeof(response))
.Build(&desc);
if (status != ZX_OK)
return status;
uint32_t used;
status = gpu_.HandleGpuCommand(&gpu_.control_queue(), desc, &used);
if (status != ZX_OK)
return status;
return response.type == VIRTIO_GPU_RESP_OK_NODATA ? ZX_OK : response.type;
}
zx_status_t SetScanout() {
virtio_gpu_set_scanout_t request = {};
request.hdr.type = VIRTIO_GPU_CMD_SET_SCANOUT;
request.resource_id = kRootResourceId;
request.scanout_id = kScanoutId;
request.r.x = 0;
request.r.y = 0;
request.r.width = kDisplayWidth;
request.r.height = kDisplayHeight;
uint16_t desc = 0;
virtio_gpu_ctrl_hdr_t response = {};
zx_status_t status = control_queue().BuildDescriptor()
.AppendReadable(&request, sizeof(request))
.AppendWriteable(&response, sizeof(response))
.Build(&desc);
if (status != ZX_OK)
return status;
uint32_t used;
status = gpu_.HandleGpuCommand(&gpu_.control_queue(), desc, &used);
if (status != ZX_OK)
return status;
return response.type == VIRTIO_GPU_RESP_OK_NODATA ? ZX_OK : response.type;
}
private:
VirtioGpu gpu_;
VirtioQueueFake control_queue_;
// Backing pages for the root resource.
fbl::SinglyLinkedList<fbl::unique_ptr<BackingPages>> backing_pages_;
// A direct pointer into our scanout buffer.
uint8_t* scanout_buffer_ = nullptr;
size_t scanout_size_ = 0;
};
static bool test_get_display_info(void) {
BEGIN_TEST;
uint16_t desc;
VirtioGpuTest test;
ASSERT_EQ(test.Init(), ZX_OK);
virtio_gpu_ctrl_hdr_t request = {};
virtio_gpu_resp_display_info_t response = {};
ASSERT_EQ(
test.control_queue().BuildDescriptor()
.AppendReadable(&request, sizeof(request))
.AppendWriteable(&response, sizeof(response))
.Build(&desc),
ZX_OK);
uint32_t used = 0;
request.type = VIRTIO_GPU_CMD_GET_DISPLAY_INFO;
ASSERT_EQ(test.gpu().HandleGpuCommand(&test.gpu().control_queue(), desc, &used), ZX_OK);
EXPECT_EQ(response.hdr.type, VIRTIO_GPU_RESP_OK_DISPLAY_INFO);
EXPECT_EQ(response.pmodes[0].r.x, 0);
EXPECT_EQ(response.pmodes[0].r.y, 0);
EXPECT_EQ(response.pmodes[0].r.width, kDisplayWidth);
EXPECT_EQ(response.pmodes[0].r.height, kDisplayHeight);
END_TEST;
}
// Test the basic device initialization sequence.
static bool test_device_initialization(void) {
BEGIN_TEST;
VirtioGpuTest test;
ASSERT_EQ(test.Init(), ZX_OK);
ASSERT_EQ(test.CreateRootResource(), ZX_OK);
ASSERT_EQ(test.AttachBacking(), ZX_OK);
ASSERT_EQ(test.SetScanout(), ZX_OK);
END_TEST;
}
static bool test_set_scanout_invalid_resource_id(void) {
BEGIN_TEST;
VirtioGpuTest test;
ASSERT_EQ(test.Init(), ZX_OK);
ASSERT_EQ(test.CreateRootResource(), ZX_OK);
ASSERT_EQ(test.AttachBacking(), ZX_OK);
virtio_gpu_set_scanout_t request = {};
request.hdr.type = VIRTIO_GPU_CMD_SET_SCANOUT;
request.resource_id = 123;
request.scanout_id = kScanoutId;
request.r.x = 0;
request.r.y = 0;
request.r.width = kDisplayWidth;
request.r.height = kDisplayHeight;
uint16_t desc = 0;
virtio_gpu_ctrl_hdr_t response = {};
ASSERT_EQ(
test.control_queue().BuildDescriptor()
.AppendReadable(&request, sizeof(request))
.AppendWriteable(&response, sizeof(response))
.Build(&desc),
ZX_OK);
uint32_t used;
ASSERT_EQ(test.gpu().HandleGpuCommand(&test.gpu().control_queue(), desc, &used), ZX_OK);
ASSERT_EQ(response.type, VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID);
return response.type == VIRTIO_GPU_RESP_OK_NODATA ? ZX_OK : response.type;
END_TEST;
}
// Verify a basic transfer 2d command correctly fills in the scanout.
static bool test_fill_display(void) {
BEGIN_TEST;
VirtioGpuTest test;
ASSERT_EQ(test.Init(), ZX_OK);
ASSERT_EQ(test.CreateRootResource(), ZX_OK);
ASSERT_EQ(test.AttachBacking(), ZX_OK);
ASSERT_EQ(test.SetScanout(), ZX_OK);
virtio_gpu_transfer_to_host_2d_t request = {};
request.hdr.type = VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D;
request.resource_id = kRootResourceId;
request.r.x = 0;
request.r.y = 0;
request.r.width = kDisplayWidth;
request.r.height = kDisplayHeight;
uint16_t desc = 0;
virtio_gpu_ctrl_hdr_t response = {};
ASSERT_EQ(
test.control_queue().BuildDescriptor()
.AppendReadable(&request, sizeof(request))
.AppendWriteable(&response, sizeof(response))
.Build(&desc),
ZX_OK);
// Initialize the scanout to 0x00 and write 0xff to the backing pages.
// A transfer 2d command will copy the 0xff into the scanout buffer.
memset(test.scanout_buffer(), 0, test.scanout_size());
for (const auto& entry : test.backing_pages()) {
memset(entry.buffer.get(), 0xff, entry.len);
}
uint32_t used;
ASSERT_EQ(test.gpu().HandleGpuCommand(&test.gpu().control_queue(), desc, &used), ZX_OK);
ASSERT_EQ(response.type, VIRTIO_GPU_RESP_OK_NODATA);
// Verify backing/scanout are now in sync.
size_t offset = 0;
for (const auto& entry : test.backing_pages()) {
ASSERT_EQ(memcmp(entry.buffer.get(), test.scanout_buffer() + offset, entry.len), 0);
offset += entry.len;
}
END_TEST;
}
BEGIN_TEST_CASE(virtio_gpu)
RUN_TEST(test_get_display_info);
RUN_TEST(test_device_initialization);
RUN_TEST(test_set_scanout_invalid_resource_id);
RUN_TEST(test_fill_display);
END_TEST_CASE(virtio_gpu)