blob: 4c0d55a447b56d4e0de658bf08892d40e975060e [file] [log] [blame]
// Copyright 2024 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 <fidl/fuchsia.hardware.platform.device/cpp/fidl.h>
#include <fidl/fuchsia.hardware.power/cpp/fidl.h>
#include <fidl/fuchsia.power.broker/cpp/fidl.h>
#include <fidl/fuchsia.power.system/cpp/fidl.h>
#include <lib/ddk/platform-defs.h>
#include <lib/driver/testing/cpp/fixtures/gtest_fixture.h>
#include <lib/fake-bti/bti.h>
#include <lib/fzl/vmo-mapper.h>
#include "src/devices/hrtimer/drivers/aml-hrtimer/aml-hrtimer.h"
namespace hrtimer {
class FakePlatformDevice : public fidl::Server<fuchsia_hardware_platform_device::Device> {
public:
fuchsia_hardware_platform_device::Service::InstanceHandler GetInstanceHandler() {
return fuchsia_hardware_platform_device::Service::InstanceHandler({
.device = bindings_.CreateHandler(this, fdf::Dispatcher::GetCurrent()->async_dispatcher(),
fidl::kIgnoreBindingClosure),
});
}
void InitResources() {
zx::vmo::create(kMmioSize, 0, &mmio_);
fake_bti_create(bti_.reset_and_get_address());
}
cpp20::span<uint32_t> mmio() {
// The test has to wait for the driver to set the MMIO cache policy before mapping.
if (!mapped_mmio_.start()) {
MapMmio();
}
return {reinterpret_cast<uint32_t*>(mapped_mmio_.start()), kMmioSize / sizeof(uint32_t)};
}
void TriggerAllIrqs() {
for (size_t i = 0; i < AmlHrtimer::GetNumberOfIrqs(); ++i) {
ASSERT_EQ(fake_interrupts_[i].trigger(0, zx::clock::get_monotonic()), ZX_OK);
}
}
private:
static constexpr size_t kMmioSize = 0x10000;
void GetMmioById(GetMmioByIdRequest& request, GetMmioByIdCompleter::Sync& completer) override {
if (request.index() != 0) {
return completer.Reply(zx::error(ZX_ERR_OUT_OF_RANGE));
}
zx::vmo vmo;
if (zx_status_t status = mmio_.duplicate(ZX_RIGHT_SAME_RIGHTS, &vmo); status != ZX_OK) {
return completer.Reply(zx::error(status));
}
completer.Reply(zx::ok(fuchsia_hardware_platform_device::Mmio{{
.offset = 0,
.size = kMmioSize,
.vmo = std::move(vmo),
}}));
}
void GetMmioByName(GetMmioByNameRequest& request,
GetMmioByNameCompleter::Sync& completer) override {
completer.Reply(zx::error(ZX_ERR_NOT_SUPPORTED));
}
void GetInterruptById(GetInterruptByIdRequest& request,
GetInterruptByIdCompleter::Sync& completer) override {
if (request.index() >= AmlHrtimer::GetNumberOfIrqs()) {
completer.Reply(zx::error(ZX_ERR_INVALID_ARGS));
return;
}
zx::interrupt interrupt;
ASSERT_EQ(zx::interrupt::create(zx::resource(), 0, ZX_INTERRUPT_VIRTUAL,
&fake_interrupts_[request.index()]),
ZX_OK);
zx_status_t status =
fake_interrupts_[request.index()].duplicate(ZX_RIGHT_SAME_RIGHTS, &interrupt);
if (status != ZX_OK) {
completer.Reply(zx::error(status));
return;
}
completer.Reply(zx::ok(std::move(interrupt)));
}
void GetInterruptByName(GetInterruptByNameRequest& request,
GetInterruptByNameCompleter::Sync& completer) override {
completer.Reply(zx::error(ZX_ERR_NOT_SUPPORTED));
}
void GetBtiById(GetBtiByIdRequest& request, GetBtiByIdCompleter::Sync& completer) override {
zx::bti bti;
if (zx_status_t status = bti_.duplicate(ZX_RIGHT_SAME_RIGHTS, &bti); status != ZX_OK) {
return completer.Reply(zx::error(status));
}
completer.Reply(zx::ok((std::move(bti))));
}
void GetBtiByName(GetBtiByNameRequest& request, GetBtiByNameCompleter::Sync& completer) override {
completer.Reply(zx::error(ZX_ERR_NOT_SUPPORTED));
}
void GetSmcById(GetSmcByIdRequest& request, GetSmcByIdCompleter::Sync& completer) override {
completer.Reply(zx::error(ZX_ERR_NOT_SUPPORTED));
}
void GetNodeDeviceInfo(GetNodeDeviceInfoCompleter::Sync& completer) override {
fuchsia_hardware_platform_device::NodeDeviceInfo info;
info.vid(PDEV_VID_AMLOGIC).pid(PDEV_PID_AMLOGIC_A311D);
completer.Reply(zx::ok(std::move(info)));
}
void GetSmcByName(GetSmcByNameRequest& request, GetSmcByNameCompleter::Sync& completer) override {
completer.Reply(zx::error(ZX_ERR_NOT_SUPPORTED));
}
void GetBoardInfo(GetBoardInfoCompleter::Sync& completer) override {
completer.Reply(zx::error(ZX_ERR_NOT_SUPPORTED));
}
void handle_unknown_method(
fidl::UnknownMethodMetadata<fuchsia_hardware_platform_device::Device> metadata,
fidl::UnknownMethodCompleter::Sync& completer) override {}
void MapMmio() { mapped_mmio_.Map(mmio_); }
void GetPowerConfiguration(GetPowerConfigurationCompleter::Sync& completer) override {
// fuchsia_hardware_power uses FIDL uint8 for power levels matching fuchsia_power_broker's.
constexpr uint8_t kPowerLevelOff =
static_cast<uint8_t>(fuchsia_power_broker::BinaryPowerLevel::kOff);
constexpr uint8_t kPowerLevelOn =
static_cast<uint8_t>(fuchsia_power_broker::BinaryPowerLevel::kOn);
constexpr char kPowerElementName[] = "aml-hrtimer-wake";
fuchsia_hardware_power::LevelTuple wake_handling_on = {{
.child_level = kPowerLevelOn,
.parent_level = static_cast<uint8_t>(fuchsia_power_system::WakeHandlingLevel::kActive),
}};
fuchsia_hardware_power::PowerDependency wake_handling = {{
.child = kPowerElementName,
.parent = fuchsia_hardware_power::ParentElement::WithSag(
fuchsia_hardware_power::SagElement::kWakeHandling),
.level_deps = {{std::move(wake_handling_on)}},
.strength = fuchsia_hardware_power::RequirementType::kActive,
}};
fuchsia_hardware_power::PowerLevel off = {{.level = kPowerLevelOff, .name = "off"}};
fuchsia_hardware_power::PowerLevel on = {{.level = kPowerLevelOn, .name = "on"}};
fuchsia_hardware_power::PowerElement element = {
{.name = kPowerElementName, .levels = {{std::move(off), std::move(on)}}}};
fuchsia_hardware_power::PowerElementConfiguration wake_config = {
{.element = std::move(element), .dependencies = {{std::move(wake_handling)}}}};
completer.Reply(zx::ok(
std::vector<fuchsia_hardware_power::PowerElementConfiguration>{{std::move(wake_config)}}));
}
zx::vmo mmio_;
fzl::VmoMapper mapped_mmio_;
zx::bti bti_;
zx::interrupt fake_interrupts_[AmlHrtimer::GetNumberOfIrqs()];
fidl::ServerBindingGroup<fuchsia_hardware_platform_device::Device> bindings_;
};
// Power Specific.
class FakeSystemActivityGovernor : public fidl::Server<fuchsia_power_system::ActivityGovernor> {
public:
FakeSystemActivityGovernor(zx::event wake_handling) : wake_handling_(std::move(wake_handling)) {}
fidl::ProtocolHandler<fuchsia_power_system::ActivityGovernor> CreateHandler() {
return bindings_.CreateHandler(this, fdf::Dispatcher::GetCurrent()->async_dispatcher(),
fidl::kIgnoreBindingClosure);
}
void GetPowerElements(GetPowerElementsCompleter::Sync& completer) override {
fuchsia_power_system::PowerElements elements;
zx::event duplicate;
wake_handling_.duplicate(ZX_RIGHT_SAME_RIGHTS, &duplicate);
fuchsia_power_system::WakeHandling wake_handling = {
{.active_dependency_token = std::move(duplicate)}};
elements = {{.wake_handling = std::move(wake_handling)}};
completer.Reply({{std::move(elements)}});
}
void RegisterListener(RegisterListenerRequest& request,
RegisterListenerCompleter::Sync& completer) override {}
void handle_unknown_method(fidl::UnknownMethodMetadata<fuchsia_power_system::ActivityGovernor> md,
fidl::UnknownMethodCompleter::Sync& completer) override {}
private:
zx::event wake_handling_;
fidl::ServerBindingGroup<fuchsia_power_system::ActivityGovernor> bindings_;
};
class FakeLeaseControl : public fidl::Server<fuchsia_power_broker::LeaseControl> {
public:
void WatchStatus(fuchsia_power_broker::LeaseControlWatchStatusRequest& request,
WatchStatusCompleter::Sync& completer) override {
completer.Reply(lease_status_);
}
void handle_unknown_method(fidl::UnknownMethodMetadata<fuchsia_power_broker::LeaseControl> md,
fidl::UnknownMethodCompleter::Sync& completer) override {}
fuchsia_power_broker::LeaseStatus lease_status_ = fuchsia_power_broker::LeaseStatus::kSatisfied;
};
class FakeLessor : public fidl::Server<fuchsia_power_broker::Lessor> {
public:
void Lease(fuchsia_power_broker::LessorLeaseRequest& request,
LeaseCompleter::Sync& completer) override {
auto lease_control = fidl::CreateEndpoints<fuchsia_power_broker::LeaseControl>();
auto lease_control_impl = std::make_unique<FakeLeaseControl>();
lease_control_ = lease_control_impl.get();
lease_control_binding_ = fidl::BindServer<fuchsia_power_broker::LeaseControl>(
fdf::Dispatcher::GetCurrent()->async_dispatcher(), std::move(lease_control->server),
std::move(lease_control_impl),
[this](FakeLeaseControl* impl, fidl::UnbindInfo info,
fidl::ServerEnd<fuchsia_power_broker::LeaseControl> server_end) mutable {
lease_requested_ = false;
});
lease_requested_ = true;
completer.Reply(fit::success(std::move(lease_control->client)));
}
void handle_unknown_method(fidl::UnknownMethodMetadata<fuchsia_power_broker::Lessor> md,
fidl::UnknownMethodCompleter::Sync& completer) override {}
bool GetLeaseRequested() { return lease_requested_; }
private:
bool lease_requested_ = false;
FakeLeaseControl* lease_control_;
std::optional<fidl::ServerBindingRef<fuchsia_power_broker::LeaseControl>> lease_control_binding_;
};
class FakePowerBroker : public fidl::Server<fuchsia_power_broker::Topology> {
public:
fidl::ProtocolHandler<fuchsia_power_broker::Topology> CreateHandler() {
return bindings_.CreateHandler(this, fdf::Dispatcher::GetCurrent()->async_dispatcher(),
fidl::kIgnoreBindingClosure);
}
void AddElement(fuchsia_power_broker::ElementSchema& request,
AddElementCompleter::Sync& completer) override {
auto element_control = fidl::CreateEndpoints<fuchsia_power_broker::ElementControl>();
element_control_server_ = std::move(element_control->server);
if (request.lessor_channel()) {
auto lessor_impl = std::make_unique<FakeLessor>();
wake_lessor_ = lessor_impl.get();
fidl::ServerBindingRef<fuchsia_power_broker::Lessor> lessor_binding =
fidl::BindServer<fuchsia_power_broker::Lessor>(
fdf::Dispatcher::GetCurrent()->async_dispatcher(),
std::move(*request.lessor_channel()), std::move(lessor_impl),
[](FakeLessor* impl, fidl::UnbindInfo info,
fidl::ServerEnd<fuchsia_power_broker::Lessor> server_end) mutable {});
}
fuchsia_power_broker::TopologyAddElementResponse result{
{.element_control_channel = std::move(element_control->client)},
};
completer.Reply(fit::success(std::move(result)));
}
void handle_unknown_method(fidl::UnknownMethodMetadata<fuchsia_power_broker::Topology> md,
fidl::UnknownMethodCompleter::Sync& completer) override {}
fidl::ServerEnd<fuchsia_power_broker::ElementControl>& element_control_server() {
return element_control_server_;
}
bool GetLeaseRequested() { return wake_lessor_ && wake_lessor_->GetLeaseRequested(); }
private:
FakeLessor* wake_lessor_ = nullptr;
fidl::ServerEnd<fuchsia_power_broker::ElementControl> element_control_server_;
fidl::ServerBindingGroup<fuchsia_power_broker::Topology> bindings_;
};
class TestEnvironment : public fdf_testing::Environment {
public:
zx::result<> Serve(fdf::OutgoingDirectory& to_driver_vfs) override {
platform_device_.InitResources();
auto result = to_driver_vfs.AddService<fuchsia_hardware_platform_device::Service>(
platform_device_.GetInstanceHandler());
EXPECT_EQ(ZX_OK, result.status_value());
// Power specific.
zx::event::create(0, &wake_handling_);
zx::event duplicate;
EXPECT_EQ(wake_handling_.duplicate(ZX_RIGHT_SAME_RIGHTS, &duplicate), ZX_OK);
system_activity_governor_.emplace(std::move(duplicate));
auto result_sag =
to_driver_vfs.component().AddUnmanagedProtocol<fuchsia_power_system::ActivityGovernor>(
system_activity_governor_->CreateHandler());
EXPECT_EQ(ZX_OK, result_sag.status_value());
auto result_broker =
to_driver_vfs.component().AddUnmanagedProtocol<fuchsia_power_broker::Topology>(
power_broker_.CreateHandler());
EXPECT_EQ(ZX_OK, result_broker.status_value());
return zx::ok();
}
FakePlatformDevice& platform_device() { return platform_device_; }
FakePowerBroker& power_broker() { return power_broker_; }
private:
FakePlatformDevice platform_device_;
std::optional<FakeSystemActivityGovernor> system_activity_governor_;
FakePowerBroker power_broker_;
zx::event wake_handling_;
};
class FixtureConfig final {
public:
static constexpr bool kDriverOnForeground = false;
static constexpr bool kAutoStartDriver = true;
static constexpr bool kAutoStopDriver = false;
using DriverType = AmlHrtimer;
using EnvironmentType = TestEnvironment;
};
class DriverTest : public fdf_testing::DriverTestFixture<FixtureConfig> {
protected:
void SetUp() override {
zx::result device_result = ConnectThroughDevfs<fuchsia_hardware_hrtimer::Device>("aml-hrtimer");
ASSERT_EQ(ZX_OK, device_result.status_value());
client_.Bind(std::move(device_result.value()));
}
void CheckLeaseRequested(size_t timer_id) {
RunInEnvironmentTypeContext([](TestEnvironment& env) {
ASSERT_FALSE(env.power_broker().GetLeaseRequested());
env.platform_device().TriggerAllIrqs();
});
auto result_start = client_->StartAndWait(
{timer_id, fuchsia_hardware_hrtimer::Resolution::WithDuration(1'000ULL), 0});
ASSERT_FALSE(result_start.is_error());
ASSERT_TRUE(result_start->keep_alive().is_valid());
RunInEnvironmentTypeContext(
[](TestEnvironment& env) { ASSERT_TRUE(env.power_broker().GetLeaseRequested()); });
}
fidl::SyncClient<fuchsia_hardware_hrtimer::Device> client_;
};
TEST_F(DriverTest, Properties) {
auto result = client_->GetProperties();
ASSERT_FALSE(result.is_error());
ASSERT_FALSE(result->properties().IsEmpty());
ASSERT_TRUE(result->properties().timers_properties().has_value());
ASSERT_EQ(result->properties().timers_properties()->size(), std::size_t{9});
auto& timers = result->properties().timers_properties().value();
// Timers id 0 to 8 inclusive, except timer id 4.
for (uint64_t i = 0; i < 9; ++i) {
ASSERT_TRUE(timers[i].id());
if (timers[i].id().value() == 4) {
continue;
}
ASSERT_EQ(timers[i].id().value(), static_cast<uint64_t>(i));
ASSERT_EQ(timers[i].supported_resolutions()->size(), 4ULL);
auto& resolutions = timers[i].supported_resolutions().value();
ASSERT_EQ(resolutions[0].duration().value(), 1'000);
ASSERT_EQ(resolutions[1].duration().value(), 10'000);
ASSERT_EQ(resolutions[2].duration().value(), 100'000);
ASSERT_EQ(resolutions[3].duration().value(), 1'000'000);
ASSERT_EQ(timers[i].max_ticks().value(), 0xffffULL);
ASSERT_TRUE(timers[i].supports_event().value());
}
/// Timer id 4 has no IRQ and higher max_range.
ASSERT_EQ(timers[4].id().value(), static_cast<uint64_t>(4));
ASSERT_EQ(timers[4].supported_resolutions()->size(), 3ULL);
auto& resolutions = timers[4].supported_resolutions().value();
ASSERT_EQ(resolutions[0].duration().value(), 1'000);
ASSERT_EQ(resolutions[1].duration().value(), 10'000);
ASSERT_EQ(resolutions[2].duration().value(), 100'000);
ASSERT_EQ(timers[4].max_ticks().value(), 0xffff'ffff'ffff'ffffULL);
ASSERT_FALSE(timers[4].supports_event().value());
}
TEST_F(DriverTest, StartTimerNoticks) {
// Timers id 0 to 8 inclusive are able to take a 0 ticks expiration request for durations 1, 10,
// and 100 usecs.
for (uint64_t i = 0; i < 9; ++i) {
auto result0 =
client_->Start({i, fuchsia_hardware_hrtimer::Resolution::WithDuration(1'000ULL), 0});
ASSERT_FALSE(result0.is_error());
auto result1 =
client_->Start({i, fuchsia_hardware_hrtimer::Resolution::WithDuration(10'000ULL), 0});
ASSERT_FALSE(result1.is_error());
auto result2 =
client_->Start({i, fuchsia_hardware_hrtimer::Resolution::WithDuration(100'000ULL), 0});
ASSERT_FALSE(result2.is_error());
}
/// Timer id 4 does not support 1 msec.
auto result =
client_->Start({4ULL, fuchsia_hardware_hrtimer::Resolution::WithDuration(1000'000ULL), 0});
ASSERT_TRUE(result.is_error());
ASSERT_EQ(result.error_value().domain_error(),
fuchsia_hardware_hrtimer::DriverError::kInvalidArgs);
// Timers id 0 to 8 inclusive but not 4 support 1 msec.
for (uint64_t i = 0; i < 9; ++i) {
if (i == 4) {
continue;
}
auto result =
client_->Start({i, fuchsia_hardware_hrtimer::Resolution::WithDuration(1'000'000ULL), 0});
ASSERT_FALSE(result.is_error());
}
}
TEST_F(DriverTest, StartTimerMaxTicks) {
// Timers id 0 to 8 inclusive are able to take a up to 0xffff ticks expiration request for
// durations 1, 10, and 100 usecs.
for (uint64_t i = 0; i < 9; ++i) {
auto result0 =
client_->Start({i, fuchsia_hardware_hrtimer::Resolution::WithDuration(1'000ULL), 0xffff});
ASSERT_FALSE(result0.is_error());
auto result1 =
client_->Start({i, fuchsia_hardware_hrtimer::Resolution::WithDuration(10'000ULL), 0xffff});
ASSERT_FALSE(result1.is_error());
auto result2 =
client_->Start({i, fuchsia_hardware_hrtimer::Resolution::WithDuration(100'000ULL), 0xffff});
ASSERT_FALSE(result2.is_error());
}
// Timers id 0 to 8 inclusive, but not 4 error on 0xffff+1 ticks.
for (uint64_t i = 0; i < 9; ++i) {
if (i == 4) {
continue;
}
auto result =
client_->Start({i, fuchsia_hardware_hrtimer::Resolution::WithDuration(1'000ULL), 0x1'0000});
ASSERT_TRUE(result.is_error());
}
// Timer id 4 supports 64 bits of ticks for 1, 10 and 100 usecs.
auto result0 = client_->Start({4ULL, fuchsia_hardware_hrtimer::Resolution::WithDuration(1'000ULL),
0xffff'ffff'ffff'ffffULL});
ASSERT_FALSE(result0.is_error());
auto result1 =
client_->Start({4ULL, fuchsia_hardware_hrtimer::Resolution::WithDuration(10'000ULL),
0xffff'ffff'ffff'ffffULL});
ASSERT_FALSE(result1.is_error());
auto result2 =
client_->Start({4ULL, fuchsia_hardware_hrtimer::Resolution::WithDuration(100'000ULL),
0xffff'ffff'ffff'ffffULL});
ASSERT_FALSE(result2.is_error());
}
TEST_F(DriverTest, StartStop) {
// Timers id 0 to 8 inclusive but not 4 support events.
for (uint64_t i = 0; i < 9; ++i) {
auto result_start =
client_->Start({i, fuchsia_hardware_hrtimer::Resolution::WithDuration(1'000ULL), 1});
ASSERT_FALSE(result_start.is_error());
}
// Timers are started.
RunInEnvironmentTypeContext([](TestEnvironment& env) {
ASSERT_EQ(env.platform_device().mmio()[0x3c50], 0x000f'0100UL); // Timers A, B, C and D.
// Timer E is always started.
ASSERT_EQ(env.platform_device().mmio()[0x3c64], 0x000f'0000UL); // Timers F, G, H and I.
});
for (uint64_t i = 0; i < 9; ++i) {
auto result_stop = client_->Stop(i);
ASSERT_FALSE(result_stop.is_error());
}
// Timers are stopped.
RunInEnvironmentTypeContext([](TestEnvironment& env) {
ASSERT_EQ(env.platform_device().mmio()[0x3c50], 0x0000'0100UL); // Timers A, B, C and D.
// Timer E can't actually be stopped.
ASSERT_EQ(env.platform_device().mmio()[0x3c64], 0x0000'0000UL); // Timers F, G, H and I.
});
}
TEST_F(DriverTest, GetTicks) {
constexpr uint32_t kArbitraryCount16bits = 0x1234;
RunInEnvironmentTypeContext([](TestEnvironment& env) {
env.platform_device().mmio()[0x3c51] = kArbitraryCount16bits << 16; // Timer A.
env.platform_device().mmio()[0x3c52] = kArbitraryCount16bits << 16; // Timer B.
env.platform_device().mmio()[0x3c53] = kArbitraryCount16bits << 16; // Timer C.
env.platform_device().mmio()[0x3c54] = kArbitraryCount16bits << 16; // Timer D.
// Gap and no timer E.
env.platform_device().mmio()[0x3c65] = kArbitraryCount16bits << 16; // Timer F.
env.platform_device().mmio()[0x3c66] = kArbitraryCount16bits << 16; // Timer G.
env.platform_device().mmio()[0x3c67] = kArbitraryCount16bits << 16; // Timer H.
env.platform_device().mmio()[0x3c68] = kArbitraryCount16bits << 16; // Timer I.
});
// Timers id 0 to 8 inclusive but not 4 support only 16 bits.
for (uint64_t i = 0; i < 9; ++i) {
if (i == 4) {
continue;
}
auto result_stop = client_->Stop(i);
ASSERT_FALSE(result_stop.is_error());
auto result = client_->GetTicksLeft(i);
ASSERT_FALSE(result.is_error());
ASSERT_EQ(result->ticks(), kArbitraryCount16bits);
}
// Timer id 4 support 64 bits.
constexpr uint64_t kArbitraryTicksRequest = 0xffff'ffff'ffff'ffff;
constexpr uint64_t kArbitraryCount64bits = 0xffff'ffff'ffff'fff0;
// Request a number of ticks first since this timer counts up so the driver subtracts.
auto result_start = client_->Start(
{4, fuchsia_hardware_hrtimer::Resolution::WithDuration(1'000ULL), kArbitraryTicksRequest});
ASSERT_FALSE(result_start.is_error());
// We set the amount read after starting the timer since starting the timer writes to the register
// we read upon GetTicksLeft.
RunInEnvironmentTypeContext([](TestEnvironment& env) {
env.platform_device().mmio()[0x3c62] =
static_cast<uint32_t>(kArbitraryCount64bits); // Timer E.
env.platform_device().mmio()[0x3c63] =
static_cast<uint32_t>(kArbitraryCount64bits >> 32); // Timer E High.
});
auto result_ticks = client_->GetTicksLeft(4);
ASSERT_FALSE(result_ticks.is_error());
ASSERT_EQ(result_ticks->ticks(), kArbitraryTicksRequest - kArbitraryCount64bits);
// Since we can't really stop the timer 4 from ticking after a Stop(), GetTicksLeft() starts
// to return 0.
auto result_stop = client_->Stop(4);
ASSERT_FALSE(result_stop.is_error());
{
auto result_ticks = client_->GetTicksLeft(4);
ASSERT_FALSE(result_ticks.is_error());
ASSERT_EQ(result_ticks->ticks(), 0ULL);
}
}
TEST_F(DriverTest, EventTriggering) {
// Timers id 0 to 8 inclusive but not 4 support events (via IRQ notification).
zx::event events[9];
for (uint64_t i = 0; i < 9; ++i) {
if (i == 4) {
continue;
}
ASSERT_EQ(zx::event::create(0, &events[i]), ZX_OK);
zx::event duplicate_event;
events[i].duplicate(ZX_RIGHT_SAME_RIGHTS, &duplicate_event);
auto result_event = client_->SetEvent({i, std::move(duplicate_event)});
ASSERT_FALSE(result_event.is_error());
auto result_start =
client_->Start({i, fuchsia_hardware_hrtimer::Resolution::WithDuration(1'000ULL), 0});
ASSERT_FALSE(result_start.is_error());
}
RunInEnvironmentTypeContext([](TestEnvironment& env) { env.platform_device().TriggerAllIrqs(); });
for (uint64_t i = 0; i < 9; ++i) {
if (i == 4) {
continue;
}
zx_signals_t signals = {};
ASSERT_EQ(events[i].wait_one(ZX_EVENT_SIGNALED, zx::time::infinite(), &signals), ZX_OK);
}
}
TEST_F(DriverTest, PowerLeaseControl) {
// Element control server in the driver is the same as provided by the fake SAG.
zx_info_handle_basic_t broker_element_control, driver_element_control;
RunInDriverContext([&](AmlHrtimer& driver) {
zx_status_t status = driver.element_control()->channel().get_info(
ZX_INFO_HANDLE_BASIC, &driver_element_control, sizeof(zx_info_handle_basic_t), nullptr,
nullptr);
ASSERT_EQ(status, ZX_OK);
});
RunInEnvironmentTypeContext([&](TestEnvironment& env) {
zx_status_t status = env.power_broker().element_control_server().channel().get_info(
ZX_INFO_HANDLE_BASIC, &broker_element_control, sizeof(zx_info_handle_basic_t), nullptr,
nullptr);
ASSERT_EQ(status, ZX_OK);
});
ASSERT_EQ(broker_element_control.koid, driver_element_control.related_koid);
}
TEST_F(DriverTest, WaitTriggering) {
RunInEnvironmentTypeContext([](TestEnvironment& env) { env.platform_device().TriggerAllIrqs(); });
// Timers id 0 to 8 inclusive but not 4 support wait (via IRQ notification).
for (uint64_t i = 0; i < 9; ++i) {
if (i == 4) {
continue;
}
auto result_start =
client_->StartAndWait({i, fuchsia_hardware_hrtimer::Resolution::WithDuration(1'000ULL), 0});
ASSERT_FALSE(result_start.is_error());
ASSERT_TRUE(result_start->keep_alive().is_valid());
}
}
TEST_F(DriverTest, WaitStop) {
// Timers id 0 to 8 inclusive but not 4 support wait (via IRQ notification).
for (uint64_t i = 0; i < 9; ++i) {
if (i == 4) {
continue;
}
std::thread thread([this, i]() {
auto result_start = client_->StartAndWait(
{i, fuchsia_hardware_hrtimer::Resolution::WithDuration(1'000ULL), 0});
ASSERT_TRUE(result_start.is_error());
ASSERT_EQ(result_start.error_value().domain_error(),
fuchsia_hardware_hrtimer::DriverError::kCanceled);
});
// Wait until the driver has acquired a wait completer such that we can cancel the timer.
bool has_wait_completer = false;
while (!has_wait_completer) {
RunInDriverContext([i, &has_wait_completer](AmlHrtimer& driver) {
has_wait_completer = driver.HasWaitCompleter(i);
});
zx::nanosleep(zx::deadline_after(zx::msec(1)));
}
auto result_start_stop = client_->Stop(i);
ASSERT_FALSE(result_start_stop.is_error());
thread.join();
}
}
TEST_F(DriverTest, CancelOnDriverStop) {
std::vector<std::thread> threads;
zx::event events[9];
// Timers id 0 to 8 inclusive but not 4 support events and wait (via IRQ notification).
for (uint64_t i = 0; i < 9; ++i) {
if (i == 4) {
continue;
}
ASSERT_EQ(zx::event::create(0, &events[i]), ZX_OK);
zx::event duplicate_event;
events[i].duplicate(ZX_RIGHT_SAME_RIGHTS, &duplicate_event);
auto result_event = client_->SetEvent({i, std::move(duplicate_event)});
ASSERT_FALSE(result_event.is_error());
threads.emplace_back([this, i]() {
auto result_start = client_->StartAndWait(
{i, fuchsia_hardware_hrtimer::Resolution::WithDuration(1'000ULL), 0});
ASSERT_TRUE(result_start.is_error());
// Check that we cancel on driver stop.
ASSERT_EQ(result_start.error_value().domain_error(),
fuchsia_hardware_hrtimer::DriverError::kCanceled);
});
// Wait until the driver has acquired a wait completer such that it can be canceled.
bool has_wait_completer = false;
while (!has_wait_completer) {
RunInDriverContext([i, &has_wait_completer](AmlHrtimer& driver) {
has_wait_completer = driver.HasWaitCompleter(i);
});
zx::nanosleep(zx::deadline_after(zx::msec(1)));
}
}
// Start timer 4 as well.
auto result_start =
client_->Start({4ULL, fuchsia_hardware_hrtimer::Resolution::WithDuration(1'000ULL),
0xffff'ffff'ffff'ffffULL});
ASSERT_FALSE(result_start.is_error());
// Force driver stop.
auto result_stop_driver = StopDriver();
ASSERT_FALSE(result_stop_driver.is_error());
// Join the threads such that we check for timers canceled.
for (auto& thread : threads) {
thread.join();
}
}
// TODO(https://fxbug.dev/332975913): deflake and reenable.
TEST_F(DriverTest, DISABLED_LeaseRequested0) { CheckLeaseRequested(0); }
TEST_F(DriverTest, DISABLED_LeaseRequested1) { CheckLeaseRequested(1); }
TEST_F(DriverTest, DISABLED_LeaseRequested2) { CheckLeaseRequested(2); }
TEST_F(DriverTest, DISABLED_LeaseRequested3) { CheckLeaseRequested(3); }
TEST_F(DriverTest, DISABLED_LeaseNotRequested4) {
RunInEnvironmentTypeContext(
[](TestEnvironment& env) { ASSERT_FALSE(env.power_broker().GetLeaseRequested()); });
auto result_start =
client_->StartAndWait({4, fuchsia_hardware_hrtimer::Resolution::WithDuration(1'000ULL), 0});
ASSERT_TRUE(result_start.is_error());
RunInEnvironmentTypeContext(
[](TestEnvironment& env) { ASSERT_FALSE(env.power_broker().GetLeaseRequested()); });
}
TEST_F(DriverTest, DISABLED_LeaseRequested5) { CheckLeaseRequested(5); }
TEST_F(DriverTest, DISABLED_LeaseRequested6) { CheckLeaseRequested(6); }
TEST_F(DriverTest, DISABLED_LeaseRequested7) { CheckLeaseRequested(7); }
TEST_F(DriverTest, DISABLED_LeaseRequested8) { CheckLeaseRequested(8); }
class TestEnvironmentNoPower : public fdf_testing::Environment {
public:
zx::result<> Serve(fdf::OutgoingDirectory& to_driver_vfs) override {
platform_device_.InitResources();
auto result = to_driver_vfs.AddService<fuchsia_hardware_platform_device::Service>(
platform_device_.GetInstanceHandler());
EXPECT_EQ(ZX_OK, result.status_value());
return zx::ok();
}
FakePlatformDevice& platform_device() { return platform_device_; }
private:
FakePlatformDevice platform_device_;
};
class FixtureConfigNoPower final {
public:
static constexpr bool kDriverOnForeground = false;
static constexpr bool kAutoStartDriver = true;
static constexpr bool kAutoStopDriver = false;
using DriverType = AmlHrtimer;
using EnvironmentType = TestEnvironmentNoPower;
};
class DriverTestNoPower : public fdf_testing::DriverTestFixture<FixtureConfigNoPower> {
protected:
void SetUp() override {
zx::result device_result = ConnectThroughDevfs<fuchsia_hardware_hrtimer::Device>("aml-hrtimer");
ASSERT_EQ(ZX_OK, device_result.status_value());
client_.Bind(std::move(device_result.value()));
}
fidl::SyncClient<fuchsia_hardware_hrtimer::Device> client_;
};
TEST_F(DriverTestNoPower, WaitTriggeringNoPower) {
RunInEnvironmentTypeContext(
[](TestEnvironmentNoPower& env) { env.platform_device().TriggerAllIrqs(); });
// Timers id 0 to 8 inclusive but not 4 support wait (via IRQ notification).
for (uint64_t i = 0; i < 9; ++i) {
if (i == 4) {
continue;
}
auto result_start =
client_->StartAndWait({i, fuchsia_hardware_hrtimer::Resolution::WithDuration(1'000ULL), 0});
ASSERT_TRUE(result_start.is_error()); // Must fail, no power configuration.
ASSERT_EQ(result_start.error_value().domain_error(),
fuchsia_hardware_hrtimer::DriverError::kNotSupported);
}
}
} // namespace hrtimer