| // 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 <fidl/fuchsia.hardware.goldfish.pipe/cpp/wire.h> |
| #include <fidl/fuchsia.hardware.goldfish/cpp/wire.h> |
| #include <fidl/fuchsia.hardware.sysmem/cpp/wire_test_base.h> |
| #include <fidl/fuchsia.sysmem/cpp/wire.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/async-loop/default.h> |
| #include <lib/async/default.h> |
| #include <lib/async_patterns/testing/cpp/dispatcher_bound.h> |
| #include <lib/component/outgoing/cpp/outgoing_directory.h> |
| #include <lib/ddk/binding_driver.h> |
| #include <lib/ddk/platform-defs.h> |
| #include <lib/fake-bti/bti.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 <zxtest/zxtest.h> |
| |
| #include "src/devices/lib/acpi/mock/mock-acpi.h" |
| #include "src/devices/testing/mock-ddk/mock-device.h" |
| |
| namespace goldfish { |
| |
| using MockAcpiFidl = acpi::mock::Device; |
| |
| namespace { |
| |
| constexpr uint32_t kPipeMinDeviceVersion = 2; |
| constexpr uint32_t kMaxSignalledPipes = 64; |
| |
| constexpr zx_device_prop_t kDefaultPipeDeviceProps[] = { |
| {BIND_PLATFORM_DEV_VID, 0, PDEV_VID_GOOGLE}, |
| {BIND_PLATFORM_DEV_PID, 0, PDEV_PID_GOLDFISH}, |
| {BIND_PLATFORM_DEV_DID, 0, PDEV_DID_GOLDFISH_PIPE_CONTROL}, |
| }; |
| constexpr const char* kDefaultPipeDeviceName = "goldfish-pipe"; |
| |
| using fuchsia_sysmem::wire::HeapType; |
| constexpr HeapType kSysmemHeaps[] = { |
| HeapType::kSystemRam, |
| HeapType::kGoldfishDeviceLocal, |
| HeapType::kGoldfishHostVisible, |
| }; |
| |
| // 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; |
| }; |
| |
| class FakeSysmem : public fidl::testing::WireTestBase<fuchsia_hardware_sysmem::Sysmem> { |
| public: |
| FakeSysmem() = default; |
| |
| void RegisterHeap(RegisterHeapRequestView request, |
| RegisterHeapCompleter::Sync& completer) override { |
| if (heap_request_koids_.find(request->heap) != heap_request_koids_.end()) { |
| completer.Close(ZX_ERR_ALREADY_BOUND); |
| return; |
| } |
| |
| if (!request->heap_connection.is_valid()) { |
| completer.Close(ZX_ERR_BAD_HANDLE); |
| return; |
| } |
| |
| zx_info_handle_basic_t info; |
| request->heap_connection.handle()->get_info(ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, |
| nullptr); |
| heap_request_koids_[request->heap] = info.koid; |
| } |
| |
| void NotImplemented_(const std::string& name, ::fidl::CompleterBase& completer) final { |
| completer.Close(ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| std::map<uint64_t, zx_koid_t> heap_request_koids_; |
| }; |
| |
| struct IncomingNamespace { |
| IncomingNamespace() : outgoing(async_get_default_dispatcher()) {} |
| |
| FakeSysmem fake_sysmem; |
| component::OutgoingDirectory outgoing; |
| }; |
| |
| // Test suite creating fake PipeDevice on a mock ACPI bus. |
| class PipeDeviceTest : public zxtest::Test { |
| public: |
| PipeDeviceTest() |
| // The IncomingNamespace must live on a different thread because the |
| // pipe-device makes synchronous FIDL calls to it. |
| : ns_loop_(&kAsyncLoopConfigNoAttachToCurrentThread), |
| ns_(ns_loop_.dispatcher(), std::in_place), |
| fake_root_(MockDevice::FakeRootParent()), |
| test_loop_(&kAsyncLoopConfigAttachToCurrentThread) {} |
| |
| // |zxtest::Test| |
| void SetUp() override { |
| ASSERT_OK(ns_loop_.StartThread("incoming-namespace-loop-dispatcher")); |
| |
| ASSERT_OK(fake_bti_create(acpi_bti_.reset_and_get_address())); |
| |
| constexpr size_t kCtrlSize = 4096u; |
| ASSERT_OK(zx::vmo::create(kCtrlSize, 0u, &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_fidl_.SetMapInterrupt( |
| [this](acpi::mock::Device::MapInterruptRequestView rv, |
| acpi::mock::Device::MapInterruptCompleter::Sync& completer) { |
| zx::interrupt dupe; |
| ASSERT_OK(zx::interrupt::create(zx::resource(), 0, ZX_INTERRUPT_VIRTUAL, &dupe)); |
| ASSERT_OK(irq_.duplicate(ZX_RIGHT_SAME_RIGHTS, &dupe)); |
| completer.ReplySuccess(std::move(dupe)); |
| }); |
| mock_acpi_fidl_.SetGetMmio([this](acpi::mock::Device::GetMmioRequestView rv, |
| acpi::mock::Device::GetMmioCompleter::Sync& completer) { |
| ASSERT_EQ(rv->index, 0); |
| zx::vmo dupe; |
| ASSERT_OK(vmo_control_.duplicate(ZX_RIGHT_SAME_RIGHTS, &dupe)); |
| completer.ReplySuccess(fuchsia_mem::wire::Range{ |
| .vmo = std::move(dupe), |
| .offset = 0, |
| .size = kCtrlSize, |
| }); |
| }); |
| |
| mock_acpi_fidl_.SetGetBti([this](acpi::mock::Device::GetBtiRequestView rv, |
| acpi::mock::Device::GetBtiCompleter::Sync& completer) { |
| ASSERT_EQ(rv->index, 0); |
| zx::bti out_bti; |
| ASSERT_OK(acpi_bti_.duplicate(ZX_RIGHT_SAME_RIGHTS, &out_bti)); |
| completer.ReplySuccess(std::move(out_bti)); |
| }); |
| |
| auto acpi_client = mock_acpi_fidl_.CreateClient(ns_loop_.dispatcher()); |
| ASSERT_OK(acpi_client.status_value()); |
| |
| fake_root_->AddProtocol(ZX_PROTOCOL_ACPI, nullptr, nullptr, "acpi"); |
| |
| auto endpoints = fidl::Endpoints<fuchsia_io::Directory>::Create(); |
| |
| ns_.SyncCall([&endpoints](IncomingNamespace* ns) { |
| zx::result service_result = ns->outgoing.AddService<fuchsia_hardware_sysmem::Service>( |
| fuchsia_hardware_sysmem::Service::InstanceHandler({ |
| .sysmem = ns->fake_sysmem.bind_handler(async_get_default_dispatcher()), |
| })); |
| ASSERT_EQ(service_result.status_value(), ZX_OK); |
| |
| ASSERT_OK(ns->outgoing.Serve(std::move(endpoints.server)).status_value()); |
| }); |
| |
| fake_root_->AddFidlService(fuchsia_hardware_sysmem::Service::Name, std::move(endpoints.client), |
| "sysmem"); |
| |
| auto dut = std::make_unique<PipeDevice>(fake_root_.get(), std::move(acpi_client.value()), |
| test_loop_.dispatcher()); |
| ASSERT_OK(dut->ConnectToSysmem()); |
| ASSERT_OK(dut->Bind()); |
| dut_ = dut.release(); |
| |
| { |
| auto endpoints = fidl::Endpoints<fuchsia_hardware_goldfish_pipe::GoldfishPipe>::Create(); |
| |
| dut_child_ = std::make_unique<PipeChildDevice>(dut_, test_loop_.dispatcher()); |
| binding_ = |
| fidl::BindServer(test_loop_.dispatcher(), std::move(endpoints.server), dut_child_.get()); |
| EXPECT_TRUE(binding_.has_value()); |
| |
| client_.Bind(std::move(endpoints.client), test_loop_.dispatcher()); |
| } |
| } |
| |
| // |zxtest::Test| |
| void TearDown() override { |
| device_async_remove(fake_root_.get()); |
| mock_ddk::ReleaseFlaggedDevices(fake_root_.get()); |
| } |
| |
| 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: |
| async::Loop ns_loop_; |
| async_patterns::TestDispatcherBound<IncomingNamespace> ns_; |
| acpi::mock::Device mock_acpi_fidl_; |
| |
| std::shared_ptr<MockDevice> fake_root_; |
| async::Loop test_loop_; |
| PipeDevice* dut_; |
| std::unique_ptr<PipeChildDevice> dut_child_; |
| fidl::WireClient<fuchsia_hardware_goldfish_pipe::GoldfishPipe> client_; |
| std::optional<fidl::ServerBindingRef<fuchsia_hardware_goldfish_pipe::GoldfishPipe>> binding_; |
| |
| zx::bti acpi_bti_; |
| zx::vmo vmo_control_; |
| zx::interrupt irq_; |
| }; |
| |
| TEST_F(PipeDeviceTest, Bind) { |
| { |
| auto mapped = MapControlRegisters(); |
| Registers* ctrl_regs = reinterpret_cast<Registers*>(mapped->ptr()); |
| ctrl_regs->version = kPipeMinDeviceVersion; |
| } |
| |
| { |
| 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); |
| } |
| } |
| |
| TEST_F(PipeDeviceTest, CreatePipe) { |
| ASSERT_OK(dut_child_->Bind(kDefaultPipeDeviceProps, kDefaultPipeDeviceName)); |
| dut_child_.release(); |
| |
| int32_t id = 0; |
| zx::vmo vmo; |
| client_->Create().Then([&](auto& result) { |
| ASSERT_OK(result.status()); |
| id = result->value()->id; |
| vmo = std::move(result->value()->vmo); |
| }); |
| test_loop_.RunUntilIdle(); |
| |
| ASSERT_NE(id, 0u); |
| ASSERT_TRUE(vmo.is_valid()); |
| |
| client_->Destroy(id).Then([](auto& result) { ASSERT_OK(result.status()); }); |
| test_loop_.RunUntilIdle(); |
| } |
| |
| TEST_F(PipeDeviceTest, Exec) { |
| ASSERT_OK(dut_child_->Bind(kDefaultPipeDeviceProps, kDefaultPipeDeviceName)); |
| dut_child_.release(); |
| |
| int32_t id = 0; |
| zx::vmo vmo; |
| client_->Create().Then([&](auto& result) { |
| ASSERT_OK(result.status()); |
| id = result->value()->id; |
| vmo = std::move(result->value()->vmo); |
| }); |
| test_loop_.RunUntilIdle(); |
| |
| ASSERT_NE(id, 0u); |
| ASSERT_TRUE(vmo.is_valid()); |
| |
| client_->Exec(id).Then([](auto& result) { ASSERT_OK(result.status()); }); |
| test_loop_.RunUntilIdle(); |
| |
| { |
| auto mapped = MapControlRegisters(); |
| Registers* ctrl_regs = reinterpret_cast<Registers*>(mapped->ptr()); |
| ASSERT_EQ(ctrl_regs->command, static_cast<uint32_t>(id)); |
| } |
| |
| client_->Destroy(id).Then([](auto& result) { ASSERT_OK(result.status()); }); |
| test_loop_.RunUntilIdle(); |
| } |
| |
| TEST_F(PipeDeviceTest, TransferObservedSignals) { |
| ASSERT_OK(dut_child_->Bind(kDefaultPipeDeviceProps, kDefaultPipeDeviceName)); |
| dut_child_.release(); |
| |
| int32_t id = 0; |
| zx::vmo vmo; |
| client_->Create().Then([&](auto& result) { |
| ASSERT_OK(result.status()); |
| id = result->value()->id; |
| vmo = std::move(result->value()->vmo); |
| }); |
| test_loop_.RunUntilIdle(); |
| |
| 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)); |
| |
| client_->SetEvent(id, std::move(old_event_dup)).Then([](auto& result) { |
| ASSERT_OK(result.status()); |
| }); |
| test_loop_.RunUntilIdle(); |
| |
| // Trigger signals on "old" event. |
| old_event.signal(0u, fuchsia_hardware_goldfish::wire::kSignalReadable); |
| |
| zx::event new_event, new_event_dup; |
| ASSERT_OK(zx::event::create(0u, &new_event)); |
| // Clear the target signal. |
| ASSERT_OK(new_event.signal(fuchsia_hardware_goldfish::wire::kSignalReadable, 0u)); |
| ASSERT_OK(new_event.duplicate(ZX_RIGHT_SAME_RIGHTS, &new_event_dup)); |
| |
| client_->SetEvent(id, std::move(new_event_dup)).Then([](auto& result) { |
| ASSERT_OK(result.status()); |
| }); |
| test_loop_.RunUntilIdle(); |
| |
| // Wait for `SIGNAL_READABLE` signal on the new event. |
| zx_signals_t observed; |
| ASSERT_OK(new_event.wait_one(fuchsia_hardware_goldfish::wire::kSignalReadable, |
| zx::time::infinite_past(), &observed)); |
| } |
| |
| TEST_F(PipeDeviceTest, GetBti) { |
| ASSERT_OK(dut_child_->Bind(kDefaultPipeDeviceProps, kDefaultPipeDeviceName)); |
| dut_child_.release(); |
| |
| zx::bti bti; |
| client_->GetBti().Then([&](auto& result) { |
| ASSERT_OK(result.status()); |
| bti = std::move(result->value()->bti); |
| }); |
| test_loop_.RunUntilIdle(); |
| |
| 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))); |
| } |
| |
| // TODO(https://fxbug.dev/42073955): Re-enable the test once the flake is fixed. |
| TEST_F(PipeDeviceTest, DISABLED_ConnectToSysmem) { |
| ASSERT_OK(dut_child_->Bind(kDefaultPipeDeviceProps, kDefaultPipeDeviceName)); |
| dut_child_.release(); |
| |
| zx::channel sysmem_server, sysmem_client; |
| ASSERT_OK(zx::channel::create(0u, &sysmem_server, &sysmem_client)); |
| |
| zx::bti bti; |
| client_->ConnectSysmem(std::move(sysmem_server)).Then([&](auto& result) { |
| ASSERT_OK(result.status()); |
| }); |
| test_loop_.RunUntilIdle(); |
| |
| ASSERT_NO_FATAL_FAILURE(); |
| |
| for (const auto& heap : kSysmemHeaps) { |
| zx::channel heap_server, heap_client; |
| zx_koid_t server_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; |
| |
| uint64_t heap_id = static_cast<uint64_t>(heap); |
| client_->RegisterSysmemHeap(heap_id, std::move(heap_server)).Then([](auto& result) { |
| ASSERT_OK(result.status()); |
| }); |
| test_loop_.RunUntilIdle(); |
| ns_.SyncCall([heap_id, server_koid](IncomingNamespace* ns) { |
| ASSERT_TRUE(ns->fake_sysmem.heap_request_koids_.find(heap_id) != |
| ns->fake_sysmem.heap_request_koids_.end()); |
| ASSERT_NE(ns->fake_sysmem.heap_request_koids_.at(heap_id), ZX_KOID_INVALID); |
| ASSERT_EQ(ns->fake_sysmem.heap_request_koids_.at(heap_id), server_koid); |
| }); |
| } |
| } |
| |
| TEST_F(PipeDeviceTest, ChildDevice) { |
| // Test creating multiple child devices. Each child device can access the |
| // GoldfishPipe FIDL protocol, and they should share the same parent device. |
| |
| auto child1 = std::make_unique<PipeChildDevice>(dut_, test_loop_.dispatcher()); |
| auto child2 = std::make_unique<PipeChildDevice>(dut_, test_loop_.dispatcher()); |
| |
| constexpr zx_device_prop_t kPropsChild1[] = { |
| {BIND_PLATFORM_DEV_VID, 0, PDEV_VID_GOOGLE}, |
| {BIND_PLATFORM_DEV_PID, 0, PDEV_PID_GOLDFISH}, |
| {BIND_PLATFORM_DEV_DID, 0, 0x01}, |
| }; |
| constexpr const char* kDeviceNameChild1 = "goldfish-pipe-child1"; |
| ASSERT_OK(child1->Bind(kPropsChild1, kDeviceNameChild1)); |
| child1.release(); |
| |
| constexpr zx_device_prop_t kPropsChild2[] = { |
| {BIND_PLATFORM_DEV_VID, 0, PDEV_VID_GOOGLE}, |
| {BIND_PLATFORM_DEV_PID, 0, PDEV_PID_GOLDFISH}, |
| {BIND_PLATFORM_DEV_DID, 0, 0x02}, |
| }; |
| constexpr const char* kDeviceNameChild2 = "goldfish-pipe-child2"; |
| ASSERT_OK(child2->Bind(kPropsChild2, kDeviceNameChild2)); |
| child2.release(); |
| |
| int32_t id1 = 0; |
| int32_t id2 = 0; |
| client_->Create().Then([&](auto& result) { |
| ASSERT_OK(result.status()); |
| id1 = result->value()->id; |
| }); |
| client_->Create().Then([&](auto& result) { |
| ASSERT_OK(result.status()); |
| id2 = result->value()->id; |
| }); |
| test_loop_.RunUntilIdle(); |
| ASSERT_NE(id1, 0); |
| ASSERT_NE(id2, 0); |
| |
| ASSERT_NE(id1, id2); |
| } |
| |
| } // namespace |
| |
| } // namespace goldfish |