blob: bfa2375e7a6374f1dd44420527d27df97aedacf9 [file] [log] [blame]
// 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, CreatePipeMultiThreading) {
ASSERT_OK(dut_->Bind());
auto create_pipe = [this](size_t num_pipes, std::vector<int32_t>* ids) {
for (size_t i = 0; i < num_pipes; i++) {
int32_t id;
zx::vmo vmo;
dut_->GoldfishPipeCreate(&id, &vmo);
ids->push_back(id);
}
};
std::vector<int32_t> ids_1, ids_2;
constexpr size_t kNumPipesPerThread = 1000u;
std::thread create_pipe_thread_1(
[create_pipe, ids = &ids_1]() { create_pipe(kNumPipesPerThread, ids); });
std::thread create_pipe_thread_2(
[create_pipe, ids = &ids_2]() { create_pipe(kNumPipesPerThread, ids); });
create_pipe_thread_1.join();
create_pipe_thread_2.join();
std::vector<int32_t> set_intersect, set_union;
std::set_intersection(ids_1.begin(), ids_1.end(), ids_2.begin(), ids_2.end(),
std::back_inserter(set_intersect));
std::set_union(ids_1.begin(), ids_1.end(), ids_2.begin(), ids_2.end(),
std::back_inserter(set_union));
ASSERT_EQ(set_intersect.size(), 0u);
ASSERT_EQ(set_union.size(), 2 * kNumPipesPerThread);
}
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