blob: 25f8276f6e4eb88255b96fd26119304c3942c605 [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>
#include <span>
namespace {
using boot_shim::testing::LoadDtb;
using boot_shim::testing::LoadedDtb;
class ArmDevicetreeTimerMmioItemTest
: public boot_shim::testing::TestMixin<boot_shim::testing::ArmDevicetreeTest,
boot_shim::testing::SyntheticDevicetreeTest> {
public:
static void SetUpTestSuite() {
Mixin::SetUpTestSuite();
auto loaded_dtb = LoadDtb("arm_timer_mmio.dtb");
ASSERT_TRUE(loaded_dtb.is_ok(), "%s", loaded_dtb.error_value().c_str());
timer_ = std::move(loaded_dtb).value();
loaded_dtb = LoadDtb("arm_timer_mmio_no_frequency_override.dtb");
ASSERT_TRUE(loaded_dtb.is_ok(), "%s", loaded_dtb.error_value().c_str());
timer_no_frequency_override_ = std::move(loaded_dtb).value();
loaded_dtb = LoadDtb("arm_timer_mmio_no_frames.dtb");
ASSERT_TRUE(loaded_dtb.is_ok(), "%s", loaded_dtb.error_value().c_str());
timer_no_frames_ = std::move(loaded_dtb).value();
loaded_dtb = LoadDtb("arm_timer_mmio_invalid_frames.dtb");
ASSERT_TRUE(loaded_dtb.is_ok(), "%s", loaded_dtb.error_value().c_str());
timer_invalid_frames_ = std::move(loaded_dtb).value();
}
static void TearDownTestSuite() {
timer_ = std::nullopt;
timer_invalid_frames_ = std::nullopt;
timer_no_frames_ = std::nullopt;
timer_no_frequency_override_ = std::nullopt;
Mixin::TearDownTestSuite();
}
devicetree::Devicetree timer() { return timer_->fdt(); }
devicetree::Devicetree timer_no_frequency_override() {
return timer_no_frequency_override_->fdt();
}
devicetree::Devicetree timer_invalid_frames() { return timer_invalid_frames_->fdt(); }
devicetree::Devicetree timer_no_frames() { return timer_no_frames_->fdt(); }
private:
static std::optional<LoadedDtb> timer_;
static std::optional<LoadedDtb> timer_no_frequency_override_;
static std::optional<LoadedDtb> timer_invalid_frames_;
static std::optional<LoadedDtb> timer_no_frames_;
};
std::optional<LoadedDtb> ArmDevicetreeTimerMmioItemTest::timer_ = std::nullopt;
std::optional<LoadedDtb> ArmDevicetreeTimerMmioItemTest::timer_no_frequency_override_ =
std::nullopt;
std::optional<LoadedDtb> ArmDevicetreeTimerMmioItemTest::timer_no_frames_ = std::nullopt;
std::optional<LoadedDtb> ArmDevicetreeTimerMmioItemTest::timer_invalid_frames_ = std::nullopt;
TEST_F(ArmDevicetreeTimerMmioItemTest, 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::ArmDevicetreeTimerMmioItem> shim("test", fdt);
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_GENERIC_TIMER_MMIO);
}
}
TEST_F(ArmDevicetreeTimerMmioItemTest, NoFrames) {
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 = timer_no_frames();
boot_shim::DevicetreeBootShim<boot_shim::ArmDevicetreeTimerMmioItem> shim("test", fdt);
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_GENERIC_TIMER_MMIO);
}
}
struct IrqInfo {
uint32_t irq;
uint32_t irq_flags;
};
struct ExpectedFrame {
uint8_t id;
uint32_t mmio_phys_el1;
std::optional<uint32_t> mmio_phys_el0;
IrqInfo phys_irq;
std::optional<IrqInfo> virt_irq = std::nullopt;
};
void CheckGenericTimerMmioItem(const zbi_dcfg_arm_generic_timer_mmio_driver_t& item,
uint32_t mmio_phys, std::optional<uint32_t> clock_frequency,
std::span<ExpectedFrame> frames, std::span<ExpectedFrame> disabled) {
ASSERT_EQ(item.mmio_phys, mmio_phys);
ASSERT_EQ(item.frequency, clock_frequency.value_or(0));
// Check that the number of active frames matches the number of expected frames.
uint32_t num_active = std::popcount(item.active_frames_mask);
ASSERT_EQ(num_active, frames.size());
IrqInfo empty = {
.irq = 0,
.irq_flags = 0,
};
for (const auto& expected_frame : frames) {
ASSERT_LT(expected_frame.id, 8);
uint8_t id_mask = static_cast<uint8_t>(1 << expected_frame.id);
ASSERT_NE(item.active_frames_mask & id_mask, 0);
const auto& timer_frame = item.frames[expected_frame.id];
EXPECT_EQ(timer_frame.mmio_phys_el1, expected_frame.mmio_phys_el1);
EXPECT_EQ(timer_frame.mmio_phys_el0, expected_frame.mmio_phys_el0.value_or(0));
EXPECT_EQ(timer_frame.irq_phys, expected_frame.phys_irq.irq);
EXPECT_EQ(timer_frame.irq_phys_flags, expected_frame.phys_irq.irq_flags);
EXPECT_EQ(timer_frame.irq_virt, expected_frame.virt_irq.value_or(empty).irq);
EXPECT_EQ(timer_frame.irq_virt_flags, expected_frame.virt_irq.value_or(empty).irq_flags);
}
// Check that all inactive frames are presented as disabled.
for (uint8_t i = 0; i < 8; i++) {
uint8_t mask = static_cast<uint8_t>(1 << i);
if ((mask & item.active_frames_mask) != 0) {
continue;
}
bool done = false;
auto inactive_frame = item.frames[i];
// Disabled frames keep their MMIO regions.
for (const auto& frame : disabled) {
if (frame.id != i) {
continue;
}
done = true;
EXPECT_EQ(inactive_frame.mmio_phys_el1, frame.mmio_phys_el1);
EXPECT_EQ(inactive_frame.mmio_phys_el0, frame.mmio_phys_el0.value_or(0));
EXPECT_EQ(inactive_frame.irq_phys, frame.phys_irq.irq);
EXPECT_EQ(inactive_frame.irq_phys_flags, frame.phys_irq.irq_flags);
EXPECT_EQ(inactive_frame.irq_virt, frame.virt_irq.value_or(empty).irq);
EXPECT_EQ(inactive_frame.irq_virt_flags, frame.virt_irq.value_or(empty).irq_flags);
}
if (done) {
continue;
}
EXPECT_EQ(inactive_frame.mmio_phys_el1, 0);
EXPECT_EQ(inactive_frame.mmio_phys_el0, 0);
EXPECT_EQ(inactive_frame.irq_phys, 0);
EXPECT_EQ(inactive_frame.irq_phys_flags, 0);
EXPECT_EQ(inactive_frame.irq_virt, 0);
EXPECT_EQ(inactive_frame.irq_virt_flags, 0);
}
}
TEST_F(ArmDevicetreeTimerMmioItemTest, MultipleFramesWithClockFrequencyOverride) {
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 kExpectedFrames = std::to_array<ExpectedFrame>({
{
.id = 1,
.mmio_phys_el1 = 0x121000,
.mmio_phys_el0 = 0x122000,
.phys_irq =
{
.irq = 40,
.irq_flags = ZBI_KERNEL_DRIVER_IRQ_FLAGS_LEVEL_TRIGGERED |
ZBI_KERNEL_DRIVER_IRQ_FLAGS_POLARITY_HIGH,
},
.virt_irq =
IrqInfo{
.irq = 39,
.irq_flags = ZBI_KERNEL_DRIVER_IRQ_FLAGS_LEVEL_TRIGGERED |
ZBI_KERNEL_DRIVER_IRQ_FLAGS_POLARITY_HIGH,
},
},
{
.id = 2,
.mmio_phys_el1 = 0x123000,
.mmio_phys_el0 = std::nullopt,
.phys_irq =
{
.irq = 41,
.irq_flags = ZBI_KERNEL_DRIVER_IRQ_FLAGS_LEVEL_TRIGGERED |
ZBI_KERNEL_DRIVER_IRQ_FLAGS_POLARITY_HIGH,
},
},
{
.id = 4,
.mmio_phys_el1 = 0x124000,
.mmio_phys_el0 = std::nullopt,
.phys_irq =
{
.irq = 42,
.irq_flags = ZBI_KERNEL_DRIVER_IRQ_FLAGS_LEVEL_TRIGGERED |
ZBI_KERNEL_DRIVER_IRQ_FLAGS_POLARITY_HIGH,
},
},
{
.id = 6,
.mmio_phys_el1 = 0x125000,
.mmio_phys_el0 = std::nullopt,
.phys_irq =
{
.irq = 43,
.irq_flags = ZBI_KERNEL_DRIVER_IRQ_FLAGS_LEVEL_TRIGGERED |
ZBI_KERNEL_DRIVER_IRQ_FLAGS_POLARITY_HIGH,
},
},
{
.id = 7,
.mmio_phys_el1 = 0x126000,
.mmio_phys_el0 = std::nullopt,
.phys_irq =
{
.irq = 44,
.irq_flags = ZBI_KERNEL_DRIVER_IRQ_FLAGS_LEVEL_TRIGGERED |
ZBI_KERNEL_DRIVER_IRQ_FLAGS_POLARITY_HIGH,
},
},
});
auto kInactiveFrames = std::to_array<ExpectedFrame>({
{
.id = 3,
.mmio_phys_el1 = 0x127000,
.mmio_phys_el0 = 0x128000,
.phys_irq =
{
.irq = 45,
.irq_flags = ZBI_KERNEL_DRIVER_IRQ_FLAGS_LEVEL_TRIGGERED |
ZBI_KERNEL_DRIVER_IRQ_FLAGS_POLARITY_HIGH,
},
},
{
.id = 0,
.mmio_phys_el1 = 0x129000,
.mmio_phys_el0 = 0,
.phys_irq =
{
.irq = 46,
.irq_flags = ZBI_KERNEL_DRIVER_IRQ_FLAGS_LEVEL_TRIGGERED |
ZBI_KERNEL_DRIVER_IRQ_FLAGS_POLARITY_HIGH,
},
},
});
auto fdt = timer();
boot_shim::DevicetreeBootShim<boot_shim::ArmDevicetreeTimerMmioItem> shim("test", fdt);
ASSERT_TRUE(shim.Init());
auto clear_errors = fit::defer([&]() { image.ignore_error(); });
ASSERT_TRUE(shim.AppendItems(image).is_ok());
bool present = false;
for (auto [header, payload] : image) {
if (header->type == ZBI_TYPE_KERNEL_DRIVER &&
header->extra == ZBI_KERNEL_DRIVER_ARM_GENERIC_TIMER_MMIO) {
present = true;
ASSERT_GE(payload.size_bytes(), sizeof(zbi_dcfg_arm_generic_timer_mmio_driver_t));
const auto* item =
reinterpret_cast<const zbi_dcfg_arm_generic_timer_mmio_driver_t*>(payload.data());
CheckGenericTimerMmioItem(*item, 0x120000, 0x124f800, kExpectedFrames, kInactiveFrames);
}
}
ASSERT_TRUE(present);
}
TEST_F(ArmDevicetreeTimerMmioItemTest, MultipleFramesWithoutClockFrequencyOverride) {
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 kExpectedFrames = std::to_array<ExpectedFrame>({
{
.id = 1,
.mmio_phys_el1 = 0x12341000,
.mmio_phys_el0 = 0x12342000,
.phys_irq =
{
.irq = 40,
.irq_flags = ZBI_KERNEL_DRIVER_IRQ_FLAGS_LEVEL_TRIGGERED |
ZBI_KERNEL_DRIVER_IRQ_FLAGS_POLARITY_HIGH,
},
.virt_irq =
IrqInfo{
.irq = 39,
.irq_flags = ZBI_KERNEL_DRIVER_IRQ_FLAGS_LEVEL_TRIGGERED |
ZBI_KERNEL_DRIVER_IRQ_FLAGS_POLARITY_HIGH,
},
},
{
.id = 2,
.mmio_phys_el1 = 0x12343000,
.mmio_phys_el0 = std::nullopt,
.phys_irq =
{
.irq = 41,
.irq_flags = ZBI_KERNEL_DRIVER_IRQ_FLAGS_LEVEL_TRIGGERED |
ZBI_KERNEL_DRIVER_IRQ_FLAGS_POLARITY_HIGH,
},
},
{
.id = 4,
.mmio_phys_el1 = 0x12344000,
.mmio_phys_el0 = std::nullopt,
.phys_irq =
{
.irq = 42,
.irq_flags = ZBI_KERNEL_DRIVER_IRQ_FLAGS_LEVEL_TRIGGERED |
ZBI_KERNEL_DRIVER_IRQ_FLAGS_POLARITY_HIGH,
},
},
{
.id = 6,
.mmio_phys_el1 = 0x12345000,
.mmio_phys_el0 = std::nullopt,
.phys_irq =
{
.irq = 43,
.irq_flags = ZBI_KERNEL_DRIVER_IRQ_FLAGS_LEVEL_TRIGGERED |
ZBI_KERNEL_DRIVER_IRQ_FLAGS_POLARITY_HIGH,
},
},
{
.id = 7,
.mmio_phys_el1 = 0x12346000,
.mmio_phys_el0 = std::nullopt,
.phys_irq =
{
.irq = 44,
.irq_flags = ZBI_KERNEL_DRIVER_IRQ_FLAGS_LEVEL_TRIGGERED |
ZBI_KERNEL_DRIVER_IRQ_FLAGS_POLARITY_HIGH,
},
},
});
auto kInactiveFrames = std::to_array<ExpectedFrame>({
{
.id = 3,
.mmio_phys_el1 = 0x12347000,
.mmio_phys_el0 = 0x12348000,
.phys_irq =
{
.irq = 45,
.irq_flags = ZBI_KERNEL_DRIVER_IRQ_FLAGS_LEVEL_TRIGGERED |
ZBI_KERNEL_DRIVER_IRQ_FLAGS_POLARITY_HIGH,
},
},
{
.id = 0,
.mmio_phys_el1 = 0x12349000,
.mmio_phys_el0 = 0,
.phys_irq =
{
.irq = 46,
.irq_flags = ZBI_KERNEL_DRIVER_IRQ_FLAGS_LEVEL_TRIGGERED |
ZBI_KERNEL_DRIVER_IRQ_FLAGS_POLARITY_HIGH,
},
},
});
auto fdt = timer_no_frequency_override();
boot_shim::DevicetreeBootShim<boot_shim::ArmDevicetreeTimerMmioItem> shim("test", fdt);
ASSERT_TRUE(shim.Init());
auto clear_errors = fit::defer([&]() { image.ignore_error(); });
ASSERT_TRUE(shim.AppendItems(image).is_ok());
bool present = false;
for (auto [header, payload] : image) {
if (header->type == ZBI_TYPE_KERNEL_DRIVER &&
header->extra == ZBI_KERNEL_DRIVER_ARM_GENERIC_TIMER_MMIO) {
present = true;
ASSERT_GE(payload.size_bytes(), sizeof(zbi_dcfg_arm_generic_timer_mmio_driver_t));
const auto* item =
reinterpret_cast<const zbi_dcfg_arm_generic_timer_mmio_driver_t*>(payload.data());
CheckGenericTimerMmioItem(*item, 0x12340000, std::nullopt, kExpectedFrames, kInactiveFrames);
}
}
ASSERT_TRUE(present);
}
TEST_F(ArmDevicetreeTimerMmioItemTest, MultipleInvalidFramesAreIgnored) {
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());
// While there are multiple frames in the DTB, there is only one that meets all the requirements,
// this will spew some logs about invalid frames being skipped, and if there is at least one valid
// frame emit the ZBI item.
auto kExpectedFrames = std::to_array<ExpectedFrame>({
ExpectedFrame{
.id = 1,
.mmio_phys_el1 = 0x12341000,
// While is available it is zero-size, so its ignored.
.mmio_phys_el0 = std::nullopt,
.phys_irq =
{
.irq = 40,
.irq_flags = ZBI_KERNEL_DRIVER_IRQ_FLAGS_LEVEL_TRIGGERED |
ZBI_KERNEL_DRIVER_IRQ_FLAGS_POLARITY_HIGH,
},
.virt_irq =
IrqInfo{
.irq = 39,
.irq_flags = ZBI_KERNEL_DRIVER_IRQ_FLAGS_LEVEL_TRIGGERED |
ZBI_KERNEL_DRIVER_IRQ_FLAGS_POLARITY_HIGH,
},
},
{
.id = 7,
.mmio_phys_el1 = 0x12346000,
.mmio_phys_el0 = std::nullopt,
.phys_irq =
{
.irq = 44,
.irq_flags = ZBI_KERNEL_DRIVER_IRQ_FLAGS_LEVEL_TRIGGERED |
ZBI_KERNEL_DRIVER_IRQ_FLAGS_POLARITY_HIGH,
},
},
});
auto kInactiveFrames = std::to_array<ExpectedFrame>({
{
.id = 3,
.mmio_phys_el1 = 0x12347000,
.mmio_phys_el0 = 0x12348000,
.phys_irq =
{
.irq = 45,
.irq_flags = ZBI_KERNEL_DRIVER_IRQ_FLAGS_LEVEL_TRIGGERED |
ZBI_KERNEL_DRIVER_IRQ_FLAGS_POLARITY_HIGH,
},
},
{
.id = 0,
.mmio_phys_el1 = 0x12349000,
.mmio_phys_el0 = 0,
.phys_irq =
{
.irq = 46,
.irq_flags = ZBI_KERNEL_DRIVER_IRQ_FLAGS_LEVEL_TRIGGERED |
ZBI_KERNEL_DRIVER_IRQ_FLAGS_POLARITY_HIGH,
},
},
});
auto fdt = timer_invalid_frames();
boot_shim::DevicetreeBootShim<boot_shim::ArmDevicetreeTimerMmioItem> shim("test", fdt);
ASSERT_TRUE(shim.Init());
auto clear_errors = fit::defer([&]() { image.ignore_error(); });
ASSERT_TRUE(shim.AppendItems(image).is_ok());
bool present = false;
for (auto [header, payload] : image) {
if (header->type == ZBI_TYPE_KERNEL_DRIVER &&
header->extra == ZBI_KERNEL_DRIVER_ARM_GENERIC_TIMER_MMIO) {
present = true;
ASSERT_GE(payload.size_bytes(), sizeof(zbi_dcfg_arm_generic_timer_mmio_driver_t));
const auto* item =
reinterpret_cast<const zbi_dcfg_arm_generic_timer_mmio_driver_t*>(payload.data());
CheckGenericTimerMmioItem(*item, 0x12340000, std::nullopt, kExpectedFrames, kInactiveFrames);
}
}
ASSERT_TRUE(present);
}
} // namespace