blob: a2de599da052cabbc77941b6b55e80021112bc9e [file] [log] [blame]
// Copyright 2025 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/boot-shim/devicetree-boot-shim.h>
#include <lib/boot-shim/devicetree.h>
#include <lib/boot-shim/testing/devicetree-test-fixture.h>
#include <lib/fit/defer.h>
#include <lib/zbitl/image.h>
namespace {
using boot_shim::testing::LoadDtb;
using boot_shim::testing::LoadedDtb;
class TestAllocator {
public:
TestAllocator() = default;
TestAllocator(TestAllocator&& other) {
allocs_ = std::move(other.allocs_);
other.allocs_.clear();
}
~TestAllocator() {
for (uint8_t* alloc : allocs_) {
delete[] alloc;
}
}
void* operator()(size_t size, size_t alignment, fbl::AllocChecker& ac) {
uint8_t* alloc = new (static_cast<std::align_val_t>(alignment), &ac) uint8_t[size];
allocs_.push_back(alloc);
return reinterpret_cast<void*>(alloc);
}
private:
std::vector<uint8_t*> allocs_;
};
class ArmDevicetreePsciCpuSuspendItemTest
: public boot_shim::testing::TestMixin<boot_shim::testing::ArmDevicetreeTest,
boot_shim::testing::SyntheticDevicetreeTest> {
public:
static void SetUpTestSuite() {
Mixin::SetUpTestSuite();
auto loaded_dtb = LoadDtb("arm_idle_states.dtb");
ASSERT_TRUE(loaded_dtb.is_ok(), "%s", loaded_dtb.error_value().c_str());
idle_states_ = std::move(loaded_dtb).value();
loaded_dtb = LoadDtb("arm_idle_states_and_domain.dtb");
ASSERT_TRUE(loaded_dtb.is_ok(), "%s", loaded_dtb.error_value().c_str());
idle_states_and_domain_ = std::move(loaded_dtb).value();
loaded_dtb = LoadDtb("arm_idle_states_multiple.dtb");
ASSERT_TRUE(loaded_dtb.is_ok(), "%s", loaded_dtb.error_value().c_str());
idle_states_multiple_ = std::move(loaded_dtb).value();
loaded_dtb = LoadDtb("arm_idle_states_multiple_with_invalid.dtb");
ASSERT_TRUE(loaded_dtb.is_ok(), "%s", loaded_dtb.error_value().c_str());
idle_states_multiple_with_invalid_ = std::move(loaded_dtb).value();
}
static void TearDownTestSuite() {
idle_states_ = std::nullopt;
idle_states_and_domain_ = std::nullopt;
idle_states_multiple_ = std::nullopt;
idle_states_multiple_with_invalid_ = std::nullopt;
Mixin::TearDownTestSuite();
}
devicetree::Devicetree idle_states() { return idle_states_->fdt(); }
devicetree::Devicetree idle_states_and_domain() { return idle_states_and_domain_->fdt(); }
devicetree::Devicetree idle_states_multiple() { return idle_states_multiple_->fdt(); }
devicetree::Devicetree idle_states_multiple_with_invalid() {
return idle_states_multiple_with_invalid_->fdt();
}
private:
static inline std::optional<LoadedDtb> idle_states_;
static inline std::optional<LoadedDtb> idle_states_and_domain_;
static inline std::optional<LoadedDtb> idle_states_multiple_;
static inline std::optional<LoadedDtb> idle_states_multiple_with_invalid_;
};
TEST_F(ArmDevicetreePsciCpuSuspendItemTest, MissingNode) {
std::array<std::byte, 1024> image_buffer;
std::vector<void*> allocs;
zbitl::Image<std::span<std::byte>> image(image_buffer);
ASSERT_TRUE(image.clear().is_ok());
auto fdt = empty_fdt();
boot_shim::DevicetreeBootShim<boot_shim::ArmDevicetreePsciCpuSuspendItem> shim("test", fdt);
shim.set_allocator(TestAllocator());
ASSERT_TRUE(shim.Init());
auto clear_errors = fit::defer([&]() { image.ignore_error(); });
ASSERT_TRUE(shim.AppendItems(image).is_ok());
for (auto [header, payload] : image) {
EXPECT_FALSE(header->type == ZBI_TYPE_KERNEL_DRIVER &&
header->extra == ZBI_KERNEL_DRIVER_ARM_PSCI_CPU_SUSPEND);
}
}
struct ExpectedIdleState {
uint32_t psci_param;
uint32_t entry;
uint32_t exit;
uint32_t residency;
bool is_domain_idle_state;
bool local_timer_stops;
};
void CheckIdleStates(zbitl::Image<std::span<std::byte>>& zbi,
std::span<const ExpectedIdleState> states) {
bool present = false;
auto clear_errors = fit::defer([&]() { zbi.ignore_error(); });
for (const auto& [header, payload] : zbi) {
if (header->type != ZBI_TYPE_KERNEL_DRIVER) {
continue;
}
if (header->extra != ZBI_KERNEL_DRIVER_ARM_PSCI_CPU_SUSPEND) {
continue;
}
present = true;
ASSERT_EQ(header->length, sizeof(zbi_dcfg_arm_psci_cpu_suspend_state_t) * states.size());
auto* psci_states = reinterpret_cast<zbi_dcfg_arm_psci_cpu_suspend_state_t*>(payload.data());
// Simple lookup based on the psci parameter.
for (const auto& state : states) {
bool state_found = false;
for (const auto& psci_state : std::span(psci_states, states.size())) {
if (psci_state.power_state != state.psci_param) {
continue;
}
state_found = true;
EXPECT_EQ(
(psci_state.flags & ZBI_ARM_PSCI_CPU_SUSPEND_STATE_FLAGS_TARGETS_POWER_DOMAIN) != 0,
state.is_domain_idle_state);
EXPECT_EQ((psci_state.flags & ZBI_ARM_PSCI_CPU_SUSPEND_STATE_FLAGS_LOCAL_TIMER_STOPS) != 0,
state.local_timer_stops);
EXPECT_EQ(psci_state.entry_latency_us, state.entry);
EXPECT_EQ(psci_state.exit_latency_us, state.exit);
EXPECT_EQ(psci_state.min_residency_us, state.residency);
}
ASSERT_TRUE(state_found);
}
}
ASSERT_TRUE(present);
}
TEST_F(ArmDevicetreePsciCpuSuspendItemTest, IdleStates) {
std::array<std::byte, 1024> image_buffer;
std::vector<void*> allocs;
zbitl::Image<std::span<std::byte>> image(image_buffer);
ASSERT_TRUE(image.clear().is_ok());
auto fdt = idle_states();
boot_shim::DevicetreeBootShim<boot_shim::ArmDevicetreePsciCpuSuspendItem> shim("test", fdt);
shim.set_allocator(TestAllocator());
ASSERT_TRUE(shim.Init());
ASSERT_TRUE(shim.AppendItems(image).is_ok());
constexpr auto kExpected = std::to_array<ExpectedIdleState>({
{
.psci_param = 0x40000003,
.entry = 0x123,
.exit = 0x456,
.residency = 0x589,
.is_domain_idle_state = false,
.local_timer_stops = true,
},
{
.psci_param = 0x41000043,
.entry = 0x321,
.exit = 0x456,
.residency = 0x987,
.is_domain_idle_state = true,
.local_timer_stops = false,
},
});
CheckIdleStates(image, kExpected);
}
TEST_F(ArmDevicetreePsciCpuSuspendItemTest, IdleStatesAndDomainStates) {
std::array<std::byte, 1024> image_buffer;
std::vector<void*> allocs;
zbitl::Image<std::span<std::byte>> image(image_buffer);
ASSERT_TRUE(image.clear().is_ok());
auto fdt = idle_states_and_domain();
boot_shim::DevicetreeBootShim<boot_shim::ArmDevicetreePsciCpuSuspendItem> shim("test", fdt);
shim.set_allocator(TestAllocator());
ASSERT_TRUE(shim.Init());
ASSERT_TRUE(shim.AppendItems(image).is_ok());
constexpr auto kExpected = std::to_array<ExpectedIdleState>({
{
.psci_param = 0x40000003,
.entry = 0x123,
.exit = 0x456,
.residency = 0x589,
.is_domain_idle_state = false,
.local_timer_stops = true,
},
{
.psci_param = 0x41000043,
.entry = 0x321,
.exit = 0x456,
.residency = 0x987,
.is_domain_idle_state = true,
.local_timer_stops = false,
},
});
CheckIdleStates(image, kExpected);
}
TEST_F(ArmDevicetreePsciCpuSuspendItemTest, IdleStatesMultiple) {
std::array<std::byte, 1024> image_buffer;
std::vector<void*> allocs;
zbitl::Image<std::span<std::byte>> image(image_buffer);
ASSERT_TRUE(image.clear().is_ok());
auto fdt = idle_states_multiple();
boot_shim::DevicetreeBootShim<boot_shim::ArmDevicetreePsciCpuSuspendItem> shim("test", fdt);
shim.set_allocator(TestAllocator());
ASSERT_TRUE(shim.Init());
ASSERT_TRUE(shim.AppendItems(image).is_ok());
constexpr auto kExpected = std::to_array<ExpectedIdleState>({
{
.psci_param = 0x40000003,
.entry = 0x123,
.exit = 0x456,
.residency = 0x589,
.is_domain_idle_state = false,
.local_timer_stops = true,
},
{
.psci_param = 0x40000004,
.entry = 0x124,
.exit = 0x457,
.residency = 0x580,
.is_domain_idle_state = false,
.local_timer_stops = false,
},
{
.psci_param = 0x40000005,
.entry = 0x122,
.exit = 0x455,
.residency = 0x588,
.is_domain_idle_state = false,
.local_timer_stops = true,
},
{
.psci_param = 0x41000043,
.entry = 0x321,
.exit = 0x456,
.residency = 0x987,
.is_domain_idle_state = true,
.local_timer_stops = false,
},
});
CheckIdleStates(image, kExpected);
}
TEST_F(ArmDevicetreePsciCpuSuspendItemTest, IdleStatesMultipleWithInvalid) {
std::array<std::byte, 1024> image_buffer;
std::vector<void*> allocs;
zbitl::Image<std::span<std::byte>> image(image_buffer);
ASSERT_TRUE(image.clear().is_ok());
auto fdt = idle_states_multiple_with_invalid();
boot_shim::DevicetreeBootShim<boot_shim::ArmDevicetreePsciCpuSuspendItem> shim("test", fdt);
shim.set_allocator(TestAllocator());
ASSERT_TRUE(shim.Init());
ASSERT_TRUE(shim.AppendItems(image).is_ok());
constexpr auto kExpected = std::to_array<ExpectedIdleState>({
{
.psci_param = 0x40000003,
.entry = 0x123,
.exit = 0x456,
.residency = 0x589,
.is_domain_idle_state = false,
.local_timer_stops = true,
},
{
.psci_param = 0x41000043,
.entry = 0x321,
.exit = 0x456,
.residency = 0x987,
.is_domain_idle_state = true,
.local_timer_stops = false,
},
});
CheckIdleStates(image, kExpected);
}
} // namespace