blob: 42e7f187441081955693aa2f525079f9da3df999 [file] [edit]
// Copyright 2024 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include "lib/power-management/power-state.h"
#include <lib/power-management/energy-model.h>
// TODO(https://fxbug.dev/415033686): Stop using `syscalls-next.h` on host.
#define FUCHSIA_UNSUPPORTED_ALLOW_SYSCALLS_NEXT_ON_HOST
#include <zircon/syscalls-next.h>
#undef FUCHSIA_UNSUPPORTED_ALLOW_SYSCALLS_NEXT_ON_HOST
#include <cstdint>
#include <gtest/gtest.h>
#include "test-helper.h"
namespace {
using power_management::ControlInterface;
using power_management::PowerDomain;
using power_management::PowerDomainSet;
using power_management::PowerLevelUpdateRequest;
using power_management::PowerState;
using power_management::Utilization;
constexpr uint32_t kModelId = 123;
constexpr uint32_t kTotalPowerLevels = 8;
constexpr Utilization kZeroUtilization{0};
constexpr Utilization kOneHalfUtilization = ffl::FromRatio(1, 2);
constexpr Utilization kOneQuarterUtilization = ffl::FromRatio(1, 4);
TEST(PowerStateTest, Default) {
PowerState state;
EXPECT_EQ(state.domain(), nullptr);
EXPECT_FALSE(state.is_serving());
EXPECT_FALSE(state.active_power_level());
EXPECT_FALSE(state.desired_active_power_level());
EXPECT_EQ(0u, state.active_power_coefficient_nw());
EXPECT_FALSE(state.max_idle_power_level());
EXPECT_EQ(0u, state.max_idle_power_coefficient_nw());
EXPECT_FALSE(state.max_idle_power_level_interface());
EXPECT_EQ(kZeroUtilization, state.normalized_utilization());
}
TEST(PowerStateTest, UpdateDomainSetsModel) {
auto domain = MakePowerDomainHelper(kModelId, 0, 1, 2, 3, 4, 5);
PowerState state;
state.UpdatePowerDomainSet(PowerDomainSet::CreateForTest(domain), 0);
EXPECT_EQ(state.domain(), domain);
EXPECT_FALSE(state.active_power_level());
EXPECT_FALSE(state.desired_active_power_level());
EXPECT_EQ(0u, state.active_power_coefficient_nw());
EXPECT_EQ(kMaxIdlePowerLevel, state.max_idle_power_level());
EXPECT_EQ(kMaxIdlePowerLevel + 1u, state.max_idle_power_coefficient_nw());
EXPECT_EQ(ControlInterface::kArmWfi, state.max_idle_power_level_interface());
}
TEST(PowerStateTest, UpdateDomainKeepsPowerLevelWhenSameModelId) {
auto domain = MakePowerDomainHelper(kModelId, 0, 1, 2, 3, 4, 5);
auto domain2 = MakePowerDomainHelper(kModelId, 0, 1, 2, 3, 4);
PowerState state;
state.UpdatePowerDomainSet(PowerDomainSet::CreateForTest(domain), 0);
ASSERT_EQ(state.domain(), domain);
ASSERT_EQ(ZX_OK, state.UpdateActivePowerLevel(kMinActivePowerLevel + 1).status_value());
static_cast<FakePowerLevelController*>(domain2->controller().get())->current_power_level =
kMinActivePowerLevel;
state.UpdatePowerDomainSet(PowerDomainSet::CreateForTest(domain2), 0);
EXPECT_EQ(state.domain(), domain2);
EXPECT_EQ(state.active_power_level(), kMinActivePowerLevel);
EXPECT_EQ(state.desired_active_power_level(), kMinActivePowerLevel);
}
TEST(PowerStateTest, UpdateDomainClearsPowerLevelWhenDifferentModelId) {
auto domain = MakePowerDomainHelper(kModelId, 0, 1, 2, 3, 4, 5);
auto domain2 = MakePowerDomainHelper(kModelId + 1, 0, 1, 2, 3, 4, 5);
PowerState state;
state.UpdatePowerDomainSet(PowerDomainSet::CreateForTest(domain), 0);
ASSERT_EQ(state.domain(), domain);
ASSERT_EQ(ZX_OK, state.UpdateActivePowerLevel(kMinActivePowerLevel + 1).status_value());
static_cast<FakePowerLevelController*>(domain2->controller().get())->current_power_level =
kMinActivePowerLevel;
state.UpdatePowerDomainSet(PowerDomainSet::CreateForTest(domain2), 0);
EXPECT_EQ(state.domain(), domain2);
EXPECT_EQ(state.active_power_level(), kMinActivePowerLevel);
EXPECT_EQ(state.desired_active_power_level(), kMinActivePowerLevel);
}
TEST(PowerStateTest, TransitionWhenModelIsUnknown) {
PowerState state;
EXPECT_FALSE(state.RequestTransition(1, 8));
}
TEST(PowerStateTest, TransitionWhenPowerLevelIsUnknown) {
auto energy_model = MakeFakeEnergyModel(kTotalPowerLevels);
auto domain = MakePowerDomainHelper(kModelId, energy_model, 0, 1, 2, 3, 4, 5);
PowerState state;
state.UpdatePowerDomainSet(PowerDomainSet::CreateForTest(domain), 0);
ASSERT_EQ(state.domain(), domain);
EXPECT_FALSE(state.RequestTransition(1, kMinActivePowerLevel + 1));
}
TEST(PowerStateTest, TransitionWhenPowerLevelIsDesiredPowerLevel) {
auto energy_model = MakeFakeEnergyModel(kTotalPowerLevels);
auto domain = MakePowerDomainHelper(kModelId, energy_model, 0, 1, 2, 3, 4, 5);
PowerState state;
state.UpdatePowerDomainSet(PowerDomainSet::CreateForTest(domain), 0);
ASSERT_EQ(state.domain(), domain);
ASSERT_EQ(ZX_OK, state.UpdateActivePowerLevel(kMinActivePowerLevel).status_value());
EXPECT_FALSE(state.RequestTransition(1, kMinActivePowerLevel));
}
TEST(PowerStateTest, TransitionWhenPowerLevelIsAlreadyInFlight) {
auto energy_model = MakeFakeEnergyModel(kTotalPowerLevels);
auto domain = MakePowerDomainHelper(kModelId, energy_model, 0, 1, 2, 3, 4, 5);
PowerState state;
state.UpdatePowerDomainSet(PowerDomainSet::CreateForTest(domain), 0);
ASSERT_EQ(state.domain(), domain);
ASSERT_EQ(ZX_OK, state.UpdateActivePowerLevel(kMinActivePowerLevel).status_value());
// First request for a higher level succeeds and enters in-flight state.
EXPECT_TRUE(state.RequestTransition(1, kMinActivePowerLevel + 1));
EXPECT_EQ(state.desired_active_power_level(), kMinActivePowerLevel + 1);
// Subsequent request for the same level is suppressed.
EXPECT_FALSE(state.RequestTransition(1, kMinActivePowerLevel + 1));
// Request for an even higher level updates the desired level and succeeds.
EXPECT_TRUE(state.RequestTransition(1, kMinActivePowerLevel + 2));
EXPECT_EQ(state.desired_active_power_level(), kMinActivePowerLevel + 2);
// Acknowledging the transition updates active and desired levels.
ASSERT_EQ(ZX_OK, state.UpdateActivePowerLevel(kMinActivePowerLevel + 2).status_value());
EXPECT_EQ(state.active_power_level(), kMinActivePowerLevel + 2);
EXPECT_EQ(state.desired_active_power_level(), kMinActivePowerLevel + 2);
EXPECT_FALSE(state.RequestTransition(1, kMinActivePowerLevel + 2));
}
TEST(PowerStateTest, TransitionWhenPowerLevelIsTooHigh) {
auto energy_model = MakeFakeEnergyModel(kTotalPowerLevels);
auto domain = MakePowerDomainHelper(kModelId, energy_model, 0, 1, 2, 3, 4, 5);
PowerState state;
state.UpdatePowerDomainSet(PowerDomainSet::CreateForTest(domain), 0);
ASSERT_EQ(state.domain(), domain);
ASSERT_EQ(ZX_OK, state.UpdateActivePowerLevel(kMinActivePowerLevel).status_value());
EXPECT_DEATH(state.RequestTransition(1, kTotalPowerLevels), "ASSERT FAILED");
}
TEST(PowerStateTest, TransitionWhenPowerLevelIsTooLow) {
auto energy_model = MakeFakeEnergyModel(kTotalPowerLevels);
auto domain = MakePowerDomainHelper(kModelId, energy_model, 0, 1, 2, 3, 4, 5);
PowerState state;
state.UpdatePowerDomainSet(PowerDomainSet::CreateForTest(domain), 0);
ASSERT_EQ(state.domain(), domain);
ASSERT_EQ(ZX_OK, state.UpdateActivePowerLevel(kMinActivePowerLevel).status_value());
EXPECT_DEATH(state.RequestTransition(1, kMaxIdlePowerLevel), "ASSERT FAILED");
}
TEST(PowerStateTest, UpdateUtilizationReflectsOnDomain) {
auto energy_model = MakeFakeEnergyModel(kTotalPowerLevels);
auto domain = MakePowerDomainHelper(kModelId, energy_model, 0, 1, 2, 3, 4, 5);
auto domain2 = MakePowerDomainHelper(kModelId + 1, energy_model, 0, 1, 2, 3, 4, 5);
PowerState state;
EXPECT_EQ(kZeroUtilization, state.normalized_utilization());
EXPECT_EQ(kZeroUtilization, domain->total_normalized_utilization());
EXPECT_EQ(kZeroUtilization, domain2->total_normalized_utilization());
// Update utilization before associating with a domain.
state.UpdateUtilization(kOneHalfUtilization);
EXPECT_EQ(kOneHalfUtilization, state.normalized_utilization());
EXPECT_EQ(kZeroUtilization, domain->total_normalized_utilization());
EXPECT_EQ(kZeroUtilization, domain2->total_normalized_utilization());
// Associating with a domain should update the domain total.
state.UpdatePowerDomainSet(PowerDomainSet::CreateForTest(domain), 0);
ASSERT_EQ(state.domain(), domain);
EXPECT_EQ(kOneHalfUtilization, domain->total_normalized_utilization());
EXPECT_EQ(kZeroUtilization, domain2->total_normalized_utilization());
// Update utilization while associated with a domain.
state.UpdateUtilization(-kOneQuarterUtilization);
EXPECT_EQ(kOneQuarterUtilization, state.normalized_utilization());
EXPECT_EQ(kOneQuarterUtilization, domain->total_normalized_utilization());
EXPECT_EQ(kZeroUtilization, domain2->total_normalized_utilization());
// Changing to a different domain should move the utilization from the
// previous domain to the new domain.
state.UpdatePowerDomainSet(PowerDomainSet::CreateForTest(domain2), 0);
EXPECT_EQ(kOneQuarterUtilization, state.normalized_utilization());
EXPECT_EQ(kZeroUtilization, domain->total_normalized_utilization());
EXPECT_EQ(kOneQuarterUtilization, domain2->total_normalized_utilization());
}
} // namespace