blob: 4e385b26d5e1365500bf9b74599a4e287a6e6667 [file]
// Copyright 2021 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/devices/board/lib/acpi/device.h"
#include <fidl/fuchsia.hardware.acpi/cpp/wire.h>
#include <lib/fidl/llcpp/connect_service.h>
#include <zxtest/zxtest.h>
#include "fidl/fuchsia.hardware.acpi/cpp/markers.h"
#include "lib/fidl/llcpp/channel.h"
#include "src/devices/board/lib/acpi/manager.h"
#include "src/devices/board/lib/acpi/test/device.h"
#include "src/devices/board/lib/acpi/test/mock-acpi.h"
#include "src/devices/testing/mock-ddk/mock-device.h"
class NotifyHandlerServer : public fidl::WireServer<fuchsia_hardware_acpi::NotifyHandler> {
public:
using Callback = std::function<void(uint32_t, HandleCompleter::Sync& completer)>;
explicit NotifyHandlerServer(Callback cb) : callback_(std::move(cb)) {}
~NotifyHandlerServer() override {
if (ref_ != std::nullopt) {
Close();
}
}
static fidl::ClientEnd<fuchsia_hardware_acpi::NotifyHandler> CreateAndServe(
Callback cb, async_dispatcher_t* dispatcher, std::unique_ptr<NotifyHandlerServer>* out) {
*out = std::make_unique<NotifyHandlerServer>(std::move(cb));
auto endpoints = fidl::CreateEndpoints<fuchsia_hardware_acpi::NotifyHandler>();
out->get()->ref_ = fidl::BindServer(dispatcher, std::move(endpoints->server), out->get());
return std::move(endpoints->client);
}
void Handle(HandleRequestView rv, HandleCompleter::Sync& completer) override {
callback_(rv->value, completer);
}
void Close() {
ref_->Close(ZX_ERR_PEER_CLOSED);
ref_ = std::nullopt;
}
private:
std::optional<fidl::ServerBindingRef<fuchsia_hardware_acpi::NotifyHandler>> ref_;
Callback callback_;
};
class AddressSpaceHandlerServer
: public fidl::WireServer<fuchsia_hardware_acpi::AddressSpaceHandler> {
public:
~AddressSpaceHandlerServer() override {
if (ref_ != std::nullopt) {
Close();
}
}
static std::pair<std::unique_ptr<AddressSpaceHandlerServer>,
fidl::ClientEnd<fuchsia_hardware_acpi::AddressSpaceHandler>>
CreateAndServe(async_dispatcher_t* dispatcher) {
auto server = std::make_unique<AddressSpaceHandlerServer>();
auto endpoints = fidl::CreateEndpoints<fuchsia_hardware_acpi::AddressSpaceHandler>();
server->ref_ = fidl::BindServer(dispatcher, std::move(endpoints->server), server.get());
return std::pair(std::move(server), std::move(endpoints->client));
}
void Read(ReadRequestView request, ReadCompleter::Sync& completer) override {
uint64_t ret;
switch (request->width) {
case 8:
ret = data_[request->address];
break;
case 16: {
uint16_t val;
memcpy(&val, &data_[request->address], sizeof(val));
ret = val;
break;
}
case 32: {
uint32_t val;
memcpy(&val, &data_[request->address], sizeof(val));
ret = val;
break;
}
case 64:
memcpy(&ret, &data_[request->address], sizeof(ret));
break;
default:
ZX_ASSERT(false);
}
completer.ReplySuccess(ret);
}
void Write(WriteRequestView request, WriteCompleter::Sync& completer) override {
switch (request->width) {
case 8:
data_[request->address] = request->value & UINT8_MAX;
break;
case 16: {
uint16_t val = request->value & UINT16_MAX;
memcpy(&data_[request->address], &val, sizeof(val));
break;
}
case 32: {
uint32_t val = request->value & UINT32_MAX;
memcpy(&data_[request->address], &val, sizeof(val));
break;
}
case 64:
memcpy(&data_[request->address], &request->value, sizeof(request->value));
break;
default:
ZX_ASSERT(false);
}
completer.ReplySuccess();
}
void Close() {
ref_->Close(ZX_ERR_PEER_CLOSED);
ref_ = std::nullopt;
}
std::vector<uint8_t> data_;
private:
std::optional<fidl::ServerBindingRef<fuchsia_hardware_acpi::AddressSpaceHandler>> ref_;
};
class AcpiDeviceTest : public zxtest::Test {
public:
AcpiDeviceTest() : mock_root_(MockDevice::FakeRootParent()), manager_(&acpi_, mock_root_.get()) {}
void SetUp() override {
acpi_.SetDeviceRoot(std::make_unique<acpi::test::Device>("\\"));
ASSERT_OK(manager_.StartFidlLoop());
}
void TearDown() override {
for (auto& child : mock_root_->children()) {
device_async_remove(child.get());
}
ASSERT_OK(mock_ddk::ReleaseFlaggedDevices(mock_root_.get()));
}
void SetUpFidlServer(std::unique_ptr<acpi::Device> device) {
ASSERT_OK(device
->AddDevice("test-acpi-device", cpp20::span<zx_device_prop_t>(),
cpp20::span<zx_device_str_prop_t>(), 0)
.status_value());
// Give mock_ddk ownership of the device.
zx_device_t* dev = device.release()->zxdev();
dev->InitOp();
dev->WaitUntilInitReplyCalled(zx::time::infinite());
// Bind FIDL device.
auto endpoints = fidl::CreateEndpoints<fuchsia_hardware_acpi::Device>();
ASSERT_OK(endpoints.status_value());
fidl::BindServer(manager_.fidl_dispatcher(), std::move(endpoints->server),
dev->GetDeviceContext<acpi::Device>());
fidl_client_.Bind(std::move(endpoints->client));
}
acpi::DeviceArgs Args(void* handle) {
return acpi::DeviceArgs(mock_root_.get(), &manager_, handle);
}
protected:
std::shared_ptr<MockDevice> mock_root_;
acpi::Manager manager_;
acpi::test::MockAcpi acpi_;
fidl::WireSyncClient<fuchsia_hardware_acpi::Device> fidl_client_;
};
TEST_F(AcpiDeviceTest, TestGetBusId) {
auto device = std::make_unique<acpi::Device>(std::move(
Args(ACPI_ROOT_OBJECT).SetBusMetadata(std::vector<uint8_t>(), acpi::BusType::kI2c, 37)));
SetUpFidlServer(std::move(device));
auto result = fidl_client_->GetBusId();
ASSERT_OK(result.status());
ASSERT_TRUE(result.value().result.is_response());
ASSERT_EQ(result.value().result.response().bus_id, 37);
}
TEST_F(AcpiDeviceTest, TestAcquireGlobalLockAccessDenied) {
auto test_dev = std::make_unique<acpi::test::Device>("TEST");
acpi::test::Device* hnd = test_dev.get();
acpi_.GetDeviceRoot()->AddChild(std::move(test_dev));
auto device = std::make_unique<acpi::Device>(Args(hnd));
SetUpFidlServer(std::move(device));
auto result = fidl_client_->AcquireGlobalLock();
ASSERT_TRUE(result.ok());
ASSERT_TRUE(result->result.is_err());
ASSERT_EQ(result->result.err(), fuchsia_hardware_acpi::wire::Status::kAccess);
}
// _GLK method exists, but returns zero.
TEST_F(AcpiDeviceTest, TestAcquireGlobalLockAccessDeniedButMethodExists) {
auto test_dev = std::make_unique<acpi::test::Device>("TEST");
test_dev->SetGlk(false);
acpi::test::Device* hnd = test_dev.get();
acpi_.GetDeviceRoot()->AddChild(std::move(test_dev));
auto device = std::make_unique<acpi::Device>(Args(hnd));
SetUpFidlServer(std::move(device));
auto result = fidl_client_->AcquireGlobalLock();
ASSERT_TRUE(result.ok());
ASSERT_TRUE(result->result.is_err());
ASSERT_EQ(result->result.err(), fuchsia_hardware_acpi::wire::Status::kAccess);
}
TEST_F(AcpiDeviceTest, TestAcquireGlobalLockImplicitRelease) {
auto test_dev = std::make_unique<acpi::test::Device>("TEST");
test_dev->SetGlk(true);
acpi::test::Device* hnd = test_dev.get();
acpi_.GetDeviceRoot()->AddChild(std::move(test_dev));
auto device = std::make_unique<acpi::Device>(Args(hnd));
SetUpFidlServer(std::move(device));
sync_completion_t acquired;
sync_completion_t running;
{
auto result = fidl_client_->AcquireGlobalLock();
ASSERT_TRUE(result.ok());
ASSERT_TRUE(result->result.is_response(), "ACPI error %d", int(result->result.err()));
std::thread thread([&acquired, &running, this]() {
sync_completion_signal(&running);
ASSERT_TRUE(fidl_client_->AcquireGlobalLock().ok());
sync_completion_signal(&acquired);
});
// Make sure thread keeps running when it goes out of scope.
thread.detach();
ASSERT_OK(sync_completion_wait(&running, ZX_TIME_INFINITE));
ASSERT_STATUS(sync_completion_wait(&acquired, ZX_MSEC(50)), ZX_ERR_TIMED_OUT);
// result, which holds the GlobalLock ClientEnd, will go out of scope here
// and close the channel, which should release the global lock.
}
ASSERT_OK(sync_completion_wait(&acquired, ZX_TIME_INFINITE));
}
TEST_F(AcpiDeviceTest, TestInstallNotifyHandler) {
auto test_dev = std::make_unique<acpi::test::Device>("TEST");
acpi::test::Device* hnd = test_dev.get();
acpi_.GetDeviceRoot()->AddChild(std::move(test_dev));
auto device = std::make_unique<acpi::Device>(Args(hnd));
async::Loop server_loop(&kAsyncLoopConfigNeverAttachToThread);
SetUpFidlServer(std::move(device));
sync_completion_t done;
std::unique_ptr<NotifyHandlerServer> server;
auto client = NotifyHandlerServer::CreateAndServe(
[&](uint32_t type, NotifyHandlerServer::HandleCompleter::Sync& completer) {
ASSERT_EQ(type, 32);
completer.Reply();
sync_completion_signal(&done);
},
manager_.fidl_dispatcher(), &server);
auto result = fidl_client_->InstallNotifyHandler(
fuchsia_hardware_acpi::wire::NotificationMode::kSystem, std::move(client));
ASSERT_OK(result.status());
ASSERT_FALSE(result->result.is_err());
hnd->Notify(32);
sync_completion_wait(&done, ZX_TIME_INFINITE);
}
TEST_F(AcpiDeviceTest, TestNotifyHandlerDropsEvents) {
auto test_dev = std::make_unique<acpi::test::Device>("TEST");
acpi::test::Device* hnd = test_dev.get();
acpi_.GetDeviceRoot()->AddChild(std::move(test_dev));
auto device = std::make_unique<acpi::Device>(Args(hnd));
async::Loop server_loop(&kAsyncLoopConfigNeverAttachToThread);
SetUpFidlServer(std::move(device));
size_t received_events = 0;
std::vector<NotifyHandlerServer::HandleCompleter::Async> completers;
std::unique_ptr<NotifyHandlerServer> server;
sync_completion_t received;
auto client = NotifyHandlerServer::CreateAndServe(
[&](uint32_t type, NotifyHandlerServer::HandleCompleter::Sync& completer) {
ASSERT_EQ(type, 32);
completers.emplace_back(completer.ToAsync());
received_events++;
sync_completion_signal(&received);
},
manager_.fidl_dispatcher(), &server);
auto result = fidl_client_->InstallNotifyHandler(
fuchsia_hardware_acpi::wire::NotificationMode::kSystem, std::move(client));
ASSERT_OK(result.status());
ASSERT_FALSE(result->result.is_err());
zx_status_t status = ZX_OK;
for (size_t i = 0; i < 2000; i++) {
sync_completion_reset(&received);
hnd->Notify(32);
status = sync_completion_wait(&received, ZX_MSEC(500));
if (status == ZX_ERR_TIMED_OUT) {
break;
}
}
// Should have eventually timed out.
ASSERT_NE(status, ZX_OK);
// Respond to the events.
for (auto& completer : completers) {
completer.Reply();
}
completers.clear();
}
TEST_F(AcpiDeviceTest, RemoveAndAddNotifyHandler) {
auto test_dev = std::make_unique<acpi::test::Device>("TEST");
acpi::test::Device* hnd = test_dev.get();
acpi_.GetDeviceRoot()->AddChild(std::move(test_dev));
auto device = std::make_unique<acpi::Device>(Args(hnd));
async::Loop server_loop(&kAsyncLoopConfigNeverAttachToThread);
SetUpFidlServer(std::move(device));
std::vector<NotifyHandlerServer::HandleCompleter::Async> completers;
std::unique_ptr<NotifyHandlerServer> server;
sync_completion_t received;
auto handler = [&](uint32_t type, NotifyHandlerServer::HandleCompleter::Sync& completer) {
completer.Reply();
sync_completion_signal(&received);
};
{
auto client = NotifyHandlerServer::CreateAndServe(handler, manager_.fidl_dispatcher(), &server);
auto result = fidl_client_->InstallNotifyHandler(
fuchsia_hardware_acpi::wire::NotificationMode::kSystem, std::move(client));
ASSERT_OK(result.status());
ASSERT_FALSE(result->result.is_err(), "error %d", int(result->result.err()));
}
// Destroy the server, which will close the channel.
server.reset();
// Wait for the async close event to propagate.
while (hnd->HasNotifyHandler()) {
zx::nanosleep(zx::deadline_after(zx::msec(100)));
}
// Try installing a new handler.
{
auto client = NotifyHandlerServer::CreateAndServe(handler, manager_.fidl_dispatcher(), &server);
auto result = fidl_client_->InstallNotifyHandler(
fuchsia_hardware_acpi::wire::NotificationMode::kSystem, std::move(client));
ASSERT_OK(result.status());
ASSERT_FALSE(result->result.is_err());
}
hnd->Notify(32);
sync_completion_wait(&received, ZX_TIME_INFINITE);
}
TEST_F(AcpiDeviceTest, ReceiveEventAfterUnbind) {
auto test_dev = std::make_unique<acpi::test::Device>("TEST");
acpi::test::Device* hnd = test_dev.get();
acpi_.GetDeviceRoot()->AddChild(std::move(test_dev));
auto device = std::make_unique<acpi::Device>(Args(hnd));
auto ptr = device.get();
async::Loop server_loop(&kAsyncLoopConfigNeverAttachToThread);
SetUpFidlServer(std::move(device));
sync_completion_t done;
std::unique_ptr<NotifyHandlerServer> server;
auto client = NotifyHandlerServer::CreateAndServe(
[&](uint32_t type, NotifyHandlerServer::HandleCompleter::Sync& completer) {
ASSERT_EQ(type, 32);
completer.Reply();
sync_completion_signal(&done);
},
manager_.fidl_dispatcher(), &server);
auto result = fidl_client_->InstallNotifyHandler(
fuchsia_hardware_acpi::wire::NotificationMode::kSystem, std::move(client));
ASSERT_OK(result.status());
ASSERT_FALSE(result->result.is_err());
device_async_remove(ptr->zxdev());
ASSERT_OK(mock_ddk::ReleaseFlaggedDevices(mock_root_.get()));
ASSERT_FALSE(hnd->HasNotifyHandler());
}
TEST_F(AcpiDeviceTest, TestAddressHandlerInstall) {
auto test_dev = std::make_unique<acpi::test::Device>("TEST");
acpi::test::Device* hnd = test_dev.get();
acpi_.GetDeviceRoot()->AddChild(std::move(test_dev));
auto device = std::make_unique<acpi::Device>(Args(hnd));
SetUpFidlServer(std::move(device));
auto endpoints = fidl::CreateEndpoints<fuchsia_hardware_acpi::AddressSpaceHandler>();
ASSERT_OK(endpoints.status_value());
auto [server, client] = AddressSpaceHandlerServer::CreateAndServe(manager_.fidl_dispatcher());
auto result = fidl_client_->InstallAddressSpaceHandler(
fuchsia_hardware_acpi::wire::AddressSpace::kEc, std::move(client));
ASSERT_OK(result.status());
ASSERT_TRUE(result->result.is_response());
}
TEST_F(AcpiDeviceTest, TestAddressHandlerReadWrite) {
auto test_dev = std::make_unique<acpi::test::Device>("TEST");
acpi::test::Device* hnd = test_dev.get();
acpi_.GetDeviceRoot()->AddChild(std::move(test_dev));
auto device = std::make_unique<acpi::Device>(Args(hnd));
SetUpFidlServer(std::move(device));
auto endpoints = fidl::CreateEndpoints<fuchsia_hardware_acpi::AddressSpaceHandler>();
ASSERT_OK(endpoints.status_value());
auto [server, client] = AddressSpaceHandlerServer::CreateAndServe(manager_.fidl_dispatcher());
auto result = fidl_client_->InstallAddressSpaceHandler(
fuchsia_hardware_acpi::wire::AddressSpace::kEc, std::move(client));
ASSERT_OK(result.status());
ASSERT_TRUE(result->result.is_response());
server->data_.resize(256, 0);
UINT64 value = 0xff;
ASSERT_EQ(hnd->AddressSpaceOp(ACPI_ADR_SPACE_EC, ACPI_READ, 0, 64, &value).status_value(), AE_OK);
ASSERT_EQ(value, 0);
value = 0xdeadbeefd00dfeed;
ASSERT_EQ(hnd->AddressSpaceOp(ACPI_ADR_SPACE_EC, ACPI_WRITE, 0, 64, &value).status_value(),
AE_OK);
value = 0;
ASSERT_EQ(hnd->AddressSpaceOp(ACPI_ADR_SPACE_EC, ACPI_READ, 0, 64, &value).status_value(), AE_OK);
ASSERT_EQ(value, 0xdeadbeefd00dfeed);
}