[goldfish] Add goldfish Pipe device tests.
This change adds a new test package: goldfish-pipe-device-test
which tests the driver behavior of goldfish pipe device using
fake DDK and a mock ACPI bus.
It tests all the exposed APIs of goldfish Pipe banjo protocol.
TEST=goldfish-pipe-device-test
Change-Id: Id77fcee14807604e8fd62b955e4adafd3e7c1050
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/443514
Reviewed-by: David Reveman <reveman@google.com>
Testability-Review: David Reveman <reveman@google.com>
Commit-Queue: Yilong Li <liyl@google.com>
diff --git a/src/graphics/drivers/misc/goldfish/BUILD.gn b/src/graphics/drivers/misc/goldfish/BUILD.gn
index d6754fc..6e68e09 100644
--- a/src/graphics/drivers/misc/goldfish/BUILD.gn
+++ b/src/graphics/drivers/misc/goldfish/BUILD.gn
@@ -5,6 +5,8 @@
import("//build/bind/bind.gni")
import("//build/config/fuchsia/rules.gni")
import("//build/driver_package.gni")
+import("//build/test.gni")
+import("//build/test/test_package.gni")
bind_rules("goldfish-bind") {
rules = "goldfish.bind"
@@ -13,6 +15,25 @@
deps = [ "//src/devices/bind/fuchsia.acpi" ]
}
+common_deps = [
+ ":goldfish-bind",
+ "//sdk/banjo/ddk.protocol.acpi",
+ "//sdk/banjo/ddk.protocol.goldfish.pipe",
+ "//sdk/fidl/fuchsia.hardware.goldfish:fuchsia.hardware.goldfish_llcpp",
+ "//src/devices/lib/driver",
+ "//src/devices/lib/mmio",
+ "//src/lib/ddk",
+ "//src/lib/ddktl",
+ "//zircon/public/lib/fbl",
+ "//zircon/public/lib/fidl-llcpp",
+ "//zircon/public/lib/zircon-internal",
+ "//zircon/public/lib/zx",
+ "//zircon/system/ulib/async-loop:async-loop-cpp",
+ "//zircon/system/ulib/async-loop:async-loop-default",
+ "//zircon/system/ulib/trace",
+ "//zircon/system/ulib/trace:trace-driver",
+]
+
driver_module("goldfish_driver") {
output_name = "goldfish"
@@ -25,24 +46,7 @@
"pipe_device.h",
]
- deps = [
- ":goldfish-bind",
- "//sdk/banjo/ddk.protocol.acpi",
- "//sdk/banjo/ddk.protocol.goldfish.pipe",
- "//sdk/fidl/fuchsia.hardware.goldfish:fuchsia.hardware.goldfish_llcpp",
- "//src/devices/lib/driver",
- "//src/devices/lib/mmio",
- "//src/lib/ddk",
- "//src/lib/ddktl",
- "//zircon/public/lib/fbl",
- "//zircon/public/lib/fidl-llcpp",
- "//zircon/public/lib/zircon-internal",
- "//zircon/public/lib/zx",
- "//zircon/system/ulib/async-loop:async-loop-cpp",
- "//zircon/system/ulib/async-loop:async-loop-default",
- "//zircon/system/ulib/trace",
- "//zircon/system/ulib/trace:trace-driver",
- ]
+ deps = common_deps
configs -= [ "//build/config/fuchsia:no_cpp_standard_library" ]
configs += [
@@ -66,7 +70,47 @@
]
}
+test("goldfish-pipe-device-test") {
+ output_name = "goldfish-pipe-device-test"
+ sources = [
+ "instance.cc",
+ "pipe.cc",
+ "pipe_device.cc",
+ "pipe_device_test.cc",
+ ]
+ deps = common_deps + [
+ "//sdk/fidl/fuchsia.sysmem:fuchsia.sysmem_llcpp",
+ "//sdk/banjo/ddk.protocol.acpi:ddk.protocol.acpi_mock",
+ "//src/devices/testing/fake-bti",
+ "//src/devices/testing/fake_ddk",
+ "//zircon/system/ulib/mmio-ptr:mmio-ptr-fake",
+ "//zircon/public/lib/mock-function",
+ "//zircon/public/lib/sync",
+ "//zircon/public/lib/zircon-internal",
+ "//zircon/public/lib/zx",
+ "//zircon/public/lib/zxtest",
+ ]
+
+ configs += [ "//build/config:all_source" ]
+}
+
+unittest_package("goldfish-pipe-device-test-package") {
+ package_name = "goldfish-pipe-device-test"
+ deps = [ ":goldfish-pipe-device-test" ]
+
+ tests = [
+ {
+ name = "goldfish-pipe-device-test"
+ environments = basic_envs
+ },
+ ]
+}
+
group("tests") {
testonly = true
deps = [ ":goldfish-bind_test" ]
+
+ if (target_cpu == "x64") {
+ deps += [ ":goldfish-pipe-device-test-package" ]
+ }
}
diff --git a/src/graphics/drivers/misc/goldfish/pipe_device_test.cc b/src/graphics/drivers/misc/goldfish/pipe_device_test.cc
new file mode 100644
index 0000000..8b1e5923
--- /dev/null
+++ b/src/graphics/drivers/misc/goldfish/pipe_device_test.cc
@@ -0,0 +1,414 @@
+// 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/pipe_device.h"
+
+#include <fuchsia/hardware/goldfish/llcpp/fidl.h>
+#include <fuchsia/sysmem/llcpp/fidl.h>
+#include <lib/fake-bti/bti.h>
+#include <lib/fake_ddk/fake_ddk.h>
+#include <lib/fake_ddk/fidl-helper.h>
+#include <lib/zx/channel.h>
+#include <lib/zx/vmar.h>
+#include <zircon/errors.h>
+#include <zircon/syscalls.h>
+
+#include <algorithm>
+#include <cstring>
+#include <memory>
+#include <set>
+#include <thread>
+#include <vector>
+
+#include <ddk/protocol/goldfish/pipe.h>
+#include <mock/ddktl/protocol/acpi.h>
+#include <zxtest/zxtest.h>
+namespace goldfish {
+
+namespace {
+
+constexpr uint32_t kGoldfishBtiId = 0x80888088;
+
+constexpr uint32_t kPipeMinDeviceVersion = 2;
+constexpr uint32_t kMaxSignalledPipes = 64;
+
+constexpr llcpp::fuchsia::sysmem::HeapType kSysmemHeaps[] = {
+ llcpp::fuchsia::sysmem::HeapType::SYSTEM_RAM,
+ llcpp::fuchsia::sysmem::HeapType::GOLDFISH_DEVICE_LOCAL,
+ llcpp::fuchsia::sysmem::HeapType::GOLDFISH_HOST_VISIBLE,
+};
+
+// MMIO Registers of goldfish pipe.
+// The layout should match the register offsets defined in pipe_device.cc.
+struct Registers {
+ uint32_t command;
+ uint32_t signal_buffer_high;
+ uint32_t signal_buffer_low;
+ uint32_t signal_buffer_count;
+ uint32_t reserved0[1];
+ uint32_t open_buffer_high;
+ uint32_t open_buffer_low;
+ uint32_t reserved1[2];
+ uint32_t version;
+ uint32_t reserved2[3];
+ uint32_t get_signalled;
+
+ void DebugPrint() const {
+ printf(
+ "Registers [ command %08x signal_buffer: %08x %08x count %08x open_buffer: %08x %08x "
+ "version %08x get_signalled %08x ]\n",
+ command, signal_buffer_high, signal_buffer_low, signal_buffer_count, open_buffer_high,
+ open_buffer_low, version, get_signalled);
+ }
+};
+
+// A RAII memory mapping wrapper of VMO to memory.
+class VmoMapping {
+ public:
+ VmoMapping(const zx::vmo& vmo, size_t size, size_t offset = 0,
+ zx_vm_option_t perm = ZX_VM_PERM_READ | ZX_VM_PERM_WRITE)
+ : vmo_(vmo), size_(size), offset_(offset), perm_(perm) {
+ map();
+ }
+
+ ~VmoMapping() { unmap(); }
+
+ void map() {
+ if (!ptr_) {
+ zx::vmar::root_self()->map(perm_, 0, vmo_, offset_, size_,
+ reinterpret_cast<uintptr_t*>(&ptr_));
+ }
+ }
+
+ void unmap() {
+ if (ptr_) {
+ zx::vmar::root_self()->unmap(reinterpret_cast<uintptr_t>(ptr_), size_);
+ ptr_ = nullptr;
+ }
+ }
+
+ void* ptr() const { return ptr_; }
+
+ private:
+ const zx::vmo& vmo_;
+ size_t size_ = 0u;
+ size_t offset_ = 0u;
+ zx_vm_option_t perm_ = 0;
+ void* ptr_ = nullptr;
+};
+
+struct ProtocolDeviceOps {
+ const zx_protocol_device_t* ops = nullptr;
+ void* ctx = nullptr;
+};
+
+// Create our own fake_ddk Bind class. The Binder will have multiple devices
+// added (PipeDevice and Pipe Instance Device). Each device will have its own
+// FIDL messenger bound to the remote channel.
+class Binder : public fake_ddk::Bind {
+ public:
+ zx_status_t DeviceAdd(zx_driver_t* drv, zx_device_t* parent, device_add_args_t* args,
+ zx_device_t** out) override {
+ zx_status_t status;
+ if (parents_.find(parent) != parents_.end()) {
+ return ZX_ERR_ALREADY_BOUND;
+ }
+ parents_.insert(parent);
+
+ if (args && args->ops) {
+ if (args->ops->message) {
+ // We use parent device as a key to find device
+ // FIDL messengers device tree.
+ fidl_messengers_[parent] = std::make_unique<fake_ddk::FidlMessenger>();
+ auto* fidl = fidl_messengers_[parent].get();
+
+ std::optional<zx::channel> remote_channel = std::nullopt;
+ if (args->client_remote) {
+ remote_channel = zx::channel(args->client_remote);
+ }
+
+ if ((status = fidl->SetMessageOp(args->ctx, args->ops->message,
+ std::move(remote_channel))) < 0) {
+ return status;
+ }
+ }
+ }
+
+ *out = fake_ddk::kFakeDevice;
+ add_called_ = true;
+
+ last_ops_.ctx = args->ctx;
+ last_ops_.ops = args->ops;
+ return ZX_OK;
+ }
+
+ ProtocolDeviceOps GetLastDeviceOps() { return last_ops_; }
+
+ const zx::channel& GetFidlChannel(zx_device_t* parent) const {
+ return fidl_messengers_.at(parent)->local();
+ }
+
+ const std::set<zx_device_t*>& parents() const { return parents_; }
+
+ private:
+ std::set</*parent*/ zx_device_t*> parents_;
+ std::map</*parent*/ zx_device_t*, std::unique_ptr<fake_ddk::FidlMessenger>> fidl_messengers_;
+ ProtocolDeviceOps last_ops_;
+};
+
+} // namespace
+
+// Test suite creating fake PipeDevice on a mock ACPI bus.
+class PipeDeviceTest : public zxtest::Test {
+ public:
+ // |zxtest::Test|
+ void SetUp() override {
+ zx::bti out_bti;
+ ASSERT_OK(fake_bti_create(out_bti.reset_and_get_address()));
+ ASSERT_OK(out_bti.duplicate(ZX_RIGHT_SAME_RIGHTS, &acpi_bti_));
+
+ constexpr size_t kCtrlSize = 4096u;
+ zx::vmo vmo_control;
+ ASSERT_OK(zx::vmo::create(kCtrlSize, 0u, &vmo_control));
+ ASSERT_OK(vmo_control.duplicate(ZX_RIGHT_SAME_RIGHTS, &vmo_control_));
+
+ zx::interrupt irq;
+ ASSERT_OK(zx::interrupt::create(zx::resource(), 0u, ZX_INTERRUPT_VIRTUAL, &irq));
+ ASSERT_OK(irq.duplicate(ZX_RIGHT_SAME_RIGHTS, &irq_));
+
+ mock_acpi_.ExpectGetBti(ZX_OK, kGoldfishBtiId, 0, std::move(out_bti))
+ .ExpectGetMmio(ZX_OK, 0u, {.offset = 0u, .size = kCtrlSize, .vmo = vmo_control.release()})
+ .ExpectMapInterrupt(ZX_OK, 0u, std::move(irq));
+
+ mock_acpi_.mock_connect_sysmem().ExpectCallWithMatcher([this](const zx::channel& connection) {
+ zx_info_handle_basic_t info;
+ connection.get_info(ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr);
+ sysmem_request_koid_ = info.koid;
+ return ZX_OK;
+ });
+
+ auto register_heap = [this](uint64_t heap, const zx::channel& connection) -> zx_status_t {
+ if (sysmem_heap_request_koids_.find(heap) != sysmem_heap_request_koids_.end()) {
+ return ZX_ERR_ALREADY_BOUND;
+ }
+ if (!connection.is_valid()) {
+ return ZX_ERR_BAD_HANDLE;
+ }
+ zx_info_handle_basic_t info;
+ connection.get_info(ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr);
+ sysmem_heap_request_koids_[heap] = info.koid;
+ return ZX_OK;
+ };
+
+ for (const auto heap : kSysmemHeaps) {
+ uint64_t heap_id = static_cast<uint64_t>(heap);
+ mock_acpi_.mock_register_sysmem_heap().ExpectCallWithMatcher(
+ [register_heap, heap_id](uint64_t heap, const zx::channel& connection) {
+ EXPECT_EQ(heap, heap_id);
+ return register_heap(heap, connection);
+ });
+ }
+
+ fbl::Array<fake_ddk::ProtocolEntry> protocols(new fake_ddk::ProtocolEntry[1], 1);
+ protocols[0] = {ZX_PROTOCOL_ACPI,
+ *reinterpret_cast<const fake_ddk::Protocol*>(mock_acpi_.GetProto())};
+
+ ddk_.SetProtocols(std::move(protocols));
+ dut_ = std::make_unique<PipeDevice>(fake_ddk::FakeParent());
+ }
+
+ // |zxtest::Test|
+ void TearDown() override {}
+
+ std::unique_ptr<VmoMapping> MapControlRegisters() const {
+ return std::make_unique<VmoMapping>(vmo_control_, /*size=*/sizeof(Registers), /*offset=*/0);
+ }
+
+ template <typename T>
+ static void Flush(const T* t) {
+ zx_cache_flush(t, sizeof(T), ZX_CACHE_FLUSH_DATA | ZX_CACHE_FLUSH_INVALIDATE);
+ }
+
+ protected:
+ ddk::MockAcpi mock_acpi_;
+ Binder ddk_;
+ std::unique_ptr<PipeDevice> dut_;
+ ProtocolDeviceOps child_device_ops_;
+
+ zx::bti acpi_bti_;
+ zx::vmo vmo_control_;
+ zx::interrupt irq_;
+
+ zx_koid_t sysmem_request_koid_ = ZX_KOID_INVALID;
+ std::map<uint64_t, zx_koid_t> sysmem_heap_request_koids_;
+};
+
+TEST_F(PipeDeviceTest, Bind) {
+ {
+ auto mapped = MapControlRegisters();
+ Registers* ctrl_regs = reinterpret_cast<Registers*>(mapped->ptr());
+ ctrl_regs->version = kPipeMinDeviceVersion;
+ }
+
+ ASSERT_OK(dut_->Bind());
+
+ {
+ auto mapped = MapControlRegisters();
+ Registers* ctrl_regs = reinterpret_cast<Registers*>(mapped->ptr());
+ Flush(ctrl_regs);
+
+ zx_paddr_t signal_buffer = (static_cast<uint64_t>(ctrl_regs->signal_buffer_high) << 32u) |
+ (ctrl_regs->signal_buffer_low);
+ ASSERT_NE(signal_buffer, 0u);
+
+ uint32_t buffer_count = ctrl_regs->signal_buffer_count;
+ ASSERT_EQ(buffer_count, kMaxSignalledPipes);
+
+ zx_paddr_t open_buffer =
+ (static_cast<uint64_t>(ctrl_regs->open_buffer_high) << 32u) | (ctrl_regs->open_buffer_low);
+ ASSERT_NE(open_buffer, 0u);
+ }
+
+ dut_->DdkAsyncRemove();
+ EXPECT_TRUE(ddk_.Ok());
+}
+
+TEST_F(PipeDeviceTest, Open) {
+ ASSERT_OK(dut_->Bind());
+
+ zx_device_t* instance_dev;
+ ASSERT_OK(dut_->DdkOpen(&instance_dev, 0u));
+ ASSERT_EQ(instance_dev, fake_ddk::kFakeDevice);
+ ASSERT_TRUE(ddk_.parents().find(fake_ddk::kFakeParent) != ddk_.parents().end());
+ ASSERT_TRUE(ddk_.parents().find(fake_ddk::kFakeDevice) != ddk_.parents().end());
+
+ dut_->DdkAsyncRemove();
+ EXPECT_TRUE(ddk_.Ok());
+}
+
+TEST_F(PipeDeviceTest, CreatePipe) {
+ ASSERT_OK(dut_->Bind());
+
+ int32_t id;
+ zx::vmo vmo;
+ ASSERT_OK(dut_->GoldfishPipeCreate(&id, &vmo));
+ ASSERT_NE(id, 0u);
+ ASSERT_TRUE(vmo.is_valid());
+
+ dut_->GoldfishPipeDestroy(id);
+
+ dut_->DdkAsyncRemove();
+ EXPECT_TRUE(ddk_.Ok());
+}
+
+TEST_F(PipeDeviceTest, Exec) {
+ ASSERT_OK(dut_->Bind());
+
+ int32_t id;
+ zx::vmo vmo;
+ ASSERT_OK(dut_->GoldfishPipeCreate(&id, &vmo));
+ ASSERT_NE(id, 0u);
+ ASSERT_TRUE(vmo.is_valid());
+
+ dut_->GoldfishPipeExec(id);
+
+ {
+ auto mapped = MapControlRegisters();
+ Registers* ctrl_regs = reinterpret_cast<Registers*>(mapped->ptr());
+ ASSERT_EQ(ctrl_regs->command, static_cast<uint32_t>(id));
+ }
+
+ dut_->GoldfishPipeDestroy(id);
+
+ dut_->DdkAsyncRemove();
+ EXPECT_TRUE(ddk_.Ok());
+}
+
+TEST_F(PipeDeviceTest, TransferObservedSignals) {
+ ASSERT_OK(dut_->Bind());
+
+ int32_t id;
+ zx::vmo vmo;
+ ASSERT_OK(dut_->GoldfishPipeCreate(&id, &vmo));
+
+ zx::event old_event, old_event_dup;
+ ASSERT_OK(zx::event::create(0u, &old_event));
+ ASSERT_OK(old_event.duplicate(ZX_RIGHT_SAME_RIGHTS, &old_event_dup));
+ ASSERT_OK(dut_->GoldfishPipeSetEvent(id, std::move(old_event_dup)));
+
+ // Trigger signals on "old" event.
+ old_event.signal(0u, llcpp::fuchsia::hardware::goldfish::SIGNAL_READABLE);
+
+ zx::event new_event, new_event_dup;
+ ASSERT_OK(zx::event::create(0u, &new_event));
+ // Clear the target signal.
+ ASSERT_OK(new_event.signal(llcpp::fuchsia::hardware::goldfish::SIGNAL_READABLE, 0u));
+ ASSERT_OK(new_event.duplicate(ZX_RIGHT_SAME_RIGHTS, &new_event_dup));
+ ASSERT_OK(dut_->GoldfishPipeSetEvent(id, std::move(new_event_dup)));
+
+ // Wait for `SIGNAL_READABLE` signal on the new event.
+ zx_signals_t observed;
+ ASSERT_OK(new_event.wait_one(llcpp::fuchsia::hardware::goldfish::SIGNAL_READABLE,
+ zx::time::infinite_past(), &observed));
+
+ dut_->DdkAsyncRemove();
+ EXPECT_TRUE(ddk_.Ok());
+}
+
+TEST_F(PipeDeviceTest, GetBti) {
+ ASSERT_OK(dut_->Bind());
+
+ zx::bti bti;
+ ASSERT_OK(dut_->GoldfishPipeGetBti(&bti));
+
+ zx_info_bti_t goldfish_bti_info, acpi_bti_info;
+ ASSERT_OK(
+ bti.get_info(ZX_INFO_BTI, &goldfish_bti_info, sizeof(goldfish_bti_info), nullptr, nullptr));
+ ASSERT_OK(
+ acpi_bti_.get_info(ZX_INFO_BTI, &acpi_bti_info, sizeof(acpi_bti_info), nullptr, nullptr));
+
+ ASSERT_FALSE(memcmp(&goldfish_bti_info, &acpi_bti_info, sizeof(zx_info_bti_t)));
+
+ dut_->DdkAsyncRemove();
+ EXPECT_TRUE(ddk_.Ok());
+}
+
+TEST_F(PipeDeviceTest, ConnectToSysmem) {
+ ASSERT_OK(dut_->Bind());
+
+ zx::channel sysmem_server, sysmem_client;
+ zx_koid_t server_koid = ZX_KOID_INVALID, client_koid = ZX_KOID_INVALID;
+ ASSERT_OK(zx::channel::create(0u, &sysmem_server, &sysmem_client));
+
+ zx_info_handle_basic_t info;
+ ASSERT_OK(sysmem_server.get_info(ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr));
+ server_koid = info.koid;
+ client_koid = info.related_koid;
+
+ ASSERT_OK(dut_->GoldfishPipeConnectSysmem(std::move(sysmem_server)));
+ ASSERT_NE(sysmem_request_koid_, ZX_KOID_INVALID);
+ ASSERT_EQ(sysmem_request_koid_, server_koid);
+
+ for (const auto& heap : kSysmemHeaps) {
+ zx::channel heap_server, heap_client;
+ zx_koid_t server_koid = ZX_KOID_INVALID, client_koid = ZX_KOID_INVALID;
+ ASSERT_OK(zx::channel::create(0u, &heap_server, &heap_client));
+
+ zx_info_handle_basic_t info;
+ ASSERT_OK(heap_server.get_info(ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr));
+ server_koid = info.koid;
+ client_koid = info.related_koid;
+
+ uint64_t heap_id = static_cast<uint64_t>(heap);
+ ASSERT_OK(dut_->GoldfishPipeRegisterSysmemHeap(heap_id, std::move(heap_server)));
+ ASSERT_TRUE(sysmem_heap_request_koids_.find(heap_id) != sysmem_heap_request_koids_.end());
+ ASSERT_NE(sysmem_heap_request_koids_.at(heap_id), ZX_KOID_INVALID);
+ ASSERT_EQ(sysmem_heap_request_koids_.at(heap_id), server_koid);
+ }
+
+ dut_->DdkAsyncRemove();
+ EXPECT_TRUE(ddk_.Ok());
+}
+
+} // namespace goldfish