blob: f7362639e8b1e57c86676b429bad0a94c45d9b70 [file] [log] [blame]
// Copyright 2020 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 <lib/stdcompat/span.h>
#include <lib/zbitl/items/cpu-topology.h>
#include <lib/zbitl/storage-traits.h>
#include <algorithm>
#include <iterator>
#include <memory>
#include <string_view>
#include <gtest/gtest.h>
namespace {
class CpuConfigPayload {
public:
explicit CpuConfigPayload(cpp20::span<const zbi_cpu_cluster_t> clusters) {
zbi_cpu_config_t config{
.cluster_count = static_cast<uint32_t>(clusters.size()),
};
data_.append(reinterpret_cast<const std::byte*>(&config), sizeof(config));
data_.append(reinterpret_cast<const std::byte*>(clusters.data()), clusters.size_bytes());
}
cpp20::span<const std::byte> as_bytes() const { return {data_}; }
private:
std::basic_string<std::byte> data_;
};
class CpuTopologyPayload {
public:
explicit CpuTopologyPayload(cpp20::span<const zbi_topology_node_t> nodes) : nodes_(nodes) {}
cpp20::span<const std::byte> as_bytes() const { return cpp20::as_bytes(nodes_); }
private:
cpp20::span<const zbi_topology_node_t> nodes_;
};
void ExpectArmNodesAreEqual(const zbi_topology_node_t& expected_node,
const zbi_topology_node_t& actual_node) {
ASSERT_EQ(expected_node.entity_type, actual_node.entity_type);
EXPECT_EQ(expected_node.parent_index, actual_node.parent_index);
switch (actual_node.entity_type) {
case ZBI_TOPOLOGY_ENTITY_CLUSTER: {
const zbi_topology_cluster_t& actual = actual_node.entity.cluster;
const zbi_topology_cluster_t& expected = expected_node.entity.cluster;
EXPECT_EQ(expected.performance_class, actual.performance_class);
break;
}
case ZBI_TOPOLOGY_ENTITY_PROCESSOR: {
const zbi_topology_processor_t& actual = actual_node.entity.processor;
const zbi_topology_processor_t& expected = expected_node.entity.processor;
ASSERT_EQ(expected.logical_id_count, actual.logical_id_count);
for (size_t j = 0; j < actual.logical_id_count; ++j) {
EXPECT_EQ(expected.logical_ids[j], actual.logical_ids[j]) << "logical_ids[" << j << "])";
}
EXPECT_EQ(actual.flags, expected.flags);
ASSERT_EQ(actual.architecture, ZBI_TOPOLOGY_ARCH_ARM);
ASSERT_EQ(expected.architecture, ZBI_TOPOLOGY_ARCH_ARM);
const zbi_topology_arm_info_t& actual_info = actual.architecture_info.arm;
const zbi_topology_arm_info_t& expected_info = expected.architecture_info.arm;
EXPECT_EQ(expected_info.cluster_1_id, actual_info.cluster_1_id);
EXPECT_EQ(expected_info.cluster_2_id, actual_info.cluster_2_id);
EXPECT_EQ(expected_info.cluster_3_id, actual_info.cluster_3_id);
EXPECT_EQ(expected_info.cpu_id, actual_info.cpu_id);
EXPECT_EQ(expected_info.gic_id, actual_info.gic_id);
break;
}
}
}
void ExpectTableHasArmNodes(const zbitl::CpuTopologyTable& table,
cpp20::span<const zbi_topology_node_t> nodes) {
EXPECT_EQ(table.size(), nodes.size());
EXPECT_EQ(table.size(), static_cast<size_t>(std::distance(table.begin(), table.end())));
auto it = table.begin();
for (size_t i = 0; i < nodes.size(); ++i, ++it) {
const zbi_topology_node_t& expected = nodes[i];
const zbi_topology_node_t actual = *it;
ASSERT_NO_FATAL_FAILURE(ExpectArmNodesAreEqual(expected, actual)) << i;
}
}
TEST(CpuTopologyTableTests, BadType) {
CpuConfigPayload payload({});
auto result = zbitl::CpuTopologyTable::FromPayload(ZBI_TYPE_DISCARD, payload.as_bytes());
ASSERT_TRUE(result.is_error());
std::string_view error = std::move(result).error_value();
EXPECT_EQ("invalid ZBI item type for CpuTopologyTable", error);
}
TEST(CpuTopologyTableTests, NoCores) {
// CONFIG: empty payload.
{
auto result = zbitl::CpuTopologyTable::FromPayload(ZBI_TYPE_CPU_CONFIG, {});
ASSERT_TRUE(result.is_error());
std::string_view error = std::move(result).error_value();
EXPECT_EQ("ZBI_TYPE_CPU_CONFIG too small for header", error);
}
// CONFIG: empty cluster.
{
CpuConfigPayload payload({});
auto result = zbitl::CpuTopologyTable::FromPayload(ZBI_TYPE_CPU_CONFIG, payload.as_bytes());
ASSERT_FALSE(result.is_error()) << result.error_value();
const auto table = std::move(result).value();
ASSERT_NO_FATAL_FAILURE(ExpectTableHasArmNodes(table, {}));
}
// TOPOLOGY: empty payload.
{
auto result = zbitl::CpuTopologyTable::FromPayload(ZBI_TYPE_CPU_TOPOLOGY, {});
ASSERT_TRUE(result.is_error());
std::string_view error = std::move(result).error_value();
EXPECT_EQ("ZBI_TYPE_CPU_TOPOLOGY payload is empty", error);
}
}
TEST(CpuTopologyTableTests, SingleArmCore) {
constexpr zbi_cpu_cluster_t kConfig[] = {{.cpu_count = 1}};
constexpr zbi_topology_node_t kNodes[] = {
{
.entity_type = ZBI_TOPOLOGY_ENTITY_CLUSTER,
.parent_index = ZBI_TOPOLOGY_NO_PARENT,
.entity = {.cluster = {.performance_class = 0}},
},
{
.entity_type = ZBI_TOPOLOGY_ENTITY_PROCESSOR,
.parent_index = 0,
.entity =
{
.processor =
{
.logical_ids = {0},
.logical_id_count = 1,
.architecture = ZBI_TOPOLOGY_ARCH_ARM,
.architecture_info =
{
.arm =
{
.cluster_1_id = 0,
.cpu_id = 0,
.gic_id = 0,
},
},
},
},
},
};
{
CpuConfigPayload payload({kConfig});
auto result = zbitl::CpuTopologyTable::FromPayload(ZBI_TYPE_CPU_CONFIG, payload.as_bytes());
ASSERT_FALSE(result.is_error()) << result.error_value();
const auto table = std::move(result).value();
ASSERT_NO_FATAL_FAILURE(ExpectTableHasArmNodes(table, {kNodes}));
}
{
CpuTopologyPayload payload({kNodes});
auto result = zbitl::CpuTopologyTable::FromPayload(ZBI_TYPE_CPU_TOPOLOGY, payload.as_bytes());
ASSERT_FALSE(result.is_error()) << result.error_value();
const auto table = std::move(result).value();
ASSERT_NO_FATAL_FAILURE(ExpectTableHasArmNodes(table, {kNodes}));
}
}
TEST(CpuTopologyTableTests, TwoArmCoresAcrossOneCluster) {
constexpr zbi_cpu_cluster_t kConfig[] = {{.cpu_count = 2}};
constexpr zbi_topology_node_t kNodes[] = {
{
.entity_type = ZBI_TOPOLOGY_ENTITY_CLUSTER,
.parent_index = ZBI_TOPOLOGY_NO_PARENT,
.entity = {.cluster = {.performance_class = 0}},
},
{
.entity_type = ZBI_TOPOLOGY_ENTITY_PROCESSOR,
.parent_index = 0,
.entity =
{
.processor =
{
.logical_ids = {0},
.logical_id_count = 1,
.architecture = ZBI_TOPOLOGY_ARCH_ARM,
.architecture_info =
{
.arm =
{
.cluster_1_id = 0,
.cpu_id = 0,
.gic_id = 0,
},
},
},
},
},
{
.entity_type = ZBI_TOPOLOGY_ENTITY_PROCESSOR,
.parent_index = 0,
.entity =
{
.processor =
{
.logical_ids = {1},
.logical_id_count = 1,
.architecture = ZBI_TOPOLOGY_ARCH_ARM,
.architecture_info =
{
.arm =
{
.cluster_1_id = 0,
.cpu_id = 1,
.gic_id = 1,
},
},
},
},
},
};
{
CpuConfigPayload payload({kConfig});
auto result = zbitl::CpuTopologyTable::FromPayload(ZBI_TYPE_CPU_CONFIG, payload.as_bytes());
ASSERT_FALSE(result.is_error()) << result.error_value();
const auto table = std::move(result).value();
ASSERT_NO_FATAL_FAILURE(ExpectTableHasArmNodes(table, {kNodes}));
}
{
CpuTopologyPayload payload({kNodes});
auto result = zbitl::CpuTopologyTable::FromPayload(ZBI_TYPE_CPU_TOPOLOGY, payload.as_bytes());
ASSERT_FALSE(result.is_error()) << result.error_value();
const auto table = std::move(result).value();
ASSERT_NO_FATAL_FAILURE(ExpectTableHasArmNodes(table, {kNodes}));
}
}
TEST(CpuTopologyTableTests, FourArmCoresAcrossOneCluster) {
constexpr zbi_cpu_cluster_t kConfig[] = {{.cpu_count = 4}};
constexpr zbi_topology_node_t kNodes[] = {
{
.entity_type = ZBI_TOPOLOGY_ENTITY_CLUSTER,
.parent_index = ZBI_TOPOLOGY_NO_PARENT,
.entity = {.cluster = {.performance_class = 0}},
},
{
.entity_type = ZBI_TOPOLOGY_ENTITY_PROCESSOR,
.parent_index = 0,
.entity =
{
.processor =
{
.logical_ids = {0},
.logical_id_count = 1,
.architecture = ZBI_TOPOLOGY_ARCH_ARM,
.architecture_info =
{
.arm =
{
.cluster_1_id = 0,
.cpu_id = 0,
.gic_id = 0,
},
},
},
},
},
{
.entity_type = ZBI_TOPOLOGY_ENTITY_PROCESSOR,
.parent_index = 0,
.entity =
{
.processor =
{
.logical_ids = {1},
.logical_id_count = 1,
.architecture = ZBI_TOPOLOGY_ARCH_ARM,
.architecture_info =
{
.arm =
{
.cluster_1_id = 0,
.cpu_id = 1,
.gic_id = 1,
},
},
},
},
},
{
.entity_type = ZBI_TOPOLOGY_ENTITY_PROCESSOR,
.parent_index = 0,
.entity =
{
.processor =
{
.logical_ids = {2},
.logical_id_count = 1,
.architecture = ZBI_TOPOLOGY_ARCH_ARM,
.architecture_info =
{
.arm =
{
.cluster_1_id = 0,
.cpu_id = 2,
.gic_id = 2,
},
},
},
},
},
{
.entity_type = ZBI_TOPOLOGY_ENTITY_PROCESSOR,
.parent_index = 0,
.entity =
{
.processor =
{
.logical_ids = {3},
.logical_id_count = 1,
.architecture = ZBI_TOPOLOGY_ARCH_ARM,
.architecture_info =
{
.arm =
{
.cluster_1_id = 0,
.cpu_id = 3,
.gic_id = 3,
},
},
},
},
},
};
{
CpuConfigPayload payload({kConfig});
auto result = zbitl::CpuTopologyTable::FromPayload(ZBI_TYPE_CPU_CONFIG, payload.as_bytes());
ASSERT_FALSE(result.is_error()) << result.error_value();
const auto table = std::move(result).value();
ASSERT_NO_FATAL_FAILURE(ExpectTableHasArmNodes(table, {kNodes}));
}
{
CpuTopologyPayload payload({kNodes});
auto result = zbitl::CpuTopologyTable::FromPayload(ZBI_TYPE_CPU_TOPOLOGY, payload.as_bytes());
ASSERT_FALSE(result.is_error()) << result.error_value();
const auto table = std::move(result).value();
ASSERT_NO_FATAL_FAILURE(ExpectTableHasArmNodes(table, {kNodes}));
}
}
TEST(CpuTopologyTableTests, TwoArmCoresAcrossTwoClusters) {
constexpr zbi_cpu_cluster_t kConfig[] = {{.cpu_count = 1}, {.cpu_count = 1}};
constexpr zbi_topology_node_t kNodes[] = {
{
.entity_type = ZBI_TOPOLOGY_ENTITY_CLUSTER,
.parent_index = ZBI_TOPOLOGY_NO_PARENT,
.entity = {.cluster = {.performance_class = 0}},
},
{
.entity_type = ZBI_TOPOLOGY_ENTITY_PROCESSOR,
.parent_index = 0,
.entity =
{
.processor =
{
.logical_ids = {0},
.logical_id_count = 1,
.architecture = ZBI_TOPOLOGY_ARCH_ARM,
.architecture_info =
{
.arm =
{
.cluster_1_id = 0,
.cpu_id = 0,
.gic_id = 0,
},
},
},
},
},
{
.entity_type = ZBI_TOPOLOGY_ENTITY_CLUSTER,
.parent_index = ZBI_TOPOLOGY_NO_PARENT,
.entity = {.cluster = {.performance_class = 1}},
},
{
.entity_type = ZBI_TOPOLOGY_ENTITY_PROCESSOR,
.parent_index = 2,
.entity =
{
.processor =
{
.logical_ids = {1},
.logical_id_count = 1,
.architecture = ZBI_TOPOLOGY_ARCH_ARM,
.architecture_info =
{
.arm =
{
.cluster_1_id = 1,
.cpu_id = 0,
.gic_id = 1,
},
},
},
},
},
};
{
CpuConfigPayload payload({kConfig});
auto result = zbitl::CpuTopologyTable::FromPayload(ZBI_TYPE_CPU_CONFIG, payload.as_bytes());
ASSERT_FALSE(result.is_error()) << result.error_value();
const auto table = std::move(result).value();
ASSERT_NO_FATAL_FAILURE(ExpectTableHasArmNodes(table, {kNodes}));
}
{
CpuTopologyPayload payload({kNodes});
auto result = zbitl::CpuTopologyTable::FromPayload(ZBI_TYPE_CPU_TOPOLOGY, payload.as_bytes());
ASSERT_FALSE(result.is_error()) << result.error_value();
const auto table = std::move(result).value();
ASSERT_NO_FATAL_FAILURE(ExpectTableHasArmNodes(table, {kNodes}));
}
}
TEST(CpuTopologyTableTests, SixArmCoresAcrossThreeClusters) {
constexpr zbi_cpu_cluster_t kConfig[] = {{.cpu_count = 1}, {.cpu_count = 3}, {.cpu_count = 2}};
constexpr zbi_topology_node_t kNodes[] = {
{
.entity_type = ZBI_TOPOLOGY_ENTITY_CLUSTER,
.parent_index = ZBI_TOPOLOGY_NO_PARENT,
.entity = {.cluster = {.performance_class = 0}},
},
{
.entity_type = ZBI_TOPOLOGY_ENTITY_PROCESSOR,
.parent_index = 0,
.entity =
{
.processor =
{
.logical_ids = {0},
.logical_id_count = 1,
.architecture = ZBI_TOPOLOGY_ARCH_ARM,
.architecture_info =
{
.arm =
{
.cluster_1_id = 0,
.cpu_id = 0,
.gic_id = 0,
},
},
},
},
},
{
.entity_type = ZBI_TOPOLOGY_ENTITY_CLUSTER,
.parent_index = ZBI_TOPOLOGY_NO_PARENT,
.entity = {.cluster = {.performance_class = 1}},
},
{
.entity_type = ZBI_TOPOLOGY_ENTITY_PROCESSOR,
.parent_index = 2,
.entity =
{
.processor =
{
.logical_ids = {1},
.logical_id_count = 1,
.architecture = ZBI_TOPOLOGY_ARCH_ARM,
.architecture_info =
{
.arm =
{
.cluster_1_id = 1,
.cpu_id = 0,
.gic_id = 1,
},
},
},
},
},
{
.entity_type = ZBI_TOPOLOGY_ENTITY_PROCESSOR,
.parent_index = 2,
.entity =
{
.processor =
{
.logical_ids = {2},
.logical_id_count = 1,
.architecture = ZBI_TOPOLOGY_ARCH_ARM,
.architecture_info =
{
.arm =
{
.cluster_1_id = 1,
.cpu_id = 1,
.gic_id = 2,
},
},
},
},
},
{
.entity_type = ZBI_TOPOLOGY_ENTITY_PROCESSOR,
.parent_index = 2,
.entity =
{
.processor =
{
.logical_ids = {3},
.logical_id_count = 1,
.architecture = ZBI_TOPOLOGY_ARCH_ARM,
.architecture_info =
{
.arm =
{
.cluster_1_id = 1,
.cpu_id = 2,
.gic_id = 3,
},
},
},
},
},
{
.entity_type = ZBI_TOPOLOGY_ENTITY_CLUSTER,
.parent_index = ZBI_TOPOLOGY_NO_PARENT,
.entity = {.cluster = {.performance_class = 2}},
},
{
.entity_type = ZBI_TOPOLOGY_ENTITY_PROCESSOR,
.parent_index = 6,
.entity =
{
.processor =
{
.logical_ids = {4},
.logical_id_count = 1,
.architecture = ZBI_TOPOLOGY_ARCH_ARM,
.architecture_info =
{
.arm =
{
.cluster_1_id = 2,
.cpu_id = 0,
.gic_id = 4,
},
},
},
},
},
{
.entity_type = ZBI_TOPOLOGY_ENTITY_PROCESSOR,
.parent_index = 6,
.entity =
{
.processor =
{
.logical_ids = {5},
.logical_id_count = 1,
.architecture = ZBI_TOPOLOGY_ARCH_ARM,
.architecture_info =
{
.arm =
{
.cluster_1_id = 2,
.cpu_id = 1,
.gic_id = 5,
},
},
},
},
},
};
{
CpuConfigPayload payload({kConfig});
auto result = zbitl::CpuTopologyTable::FromPayload(ZBI_TYPE_CPU_CONFIG, payload.as_bytes());
ASSERT_FALSE(result.is_error()) << result.error_value();
const auto table = std::move(result).value();
ASSERT_NO_FATAL_FAILURE(ExpectTableHasArmNodes(table, {kNodes}));
}
{
CpuTopologyPayload payload({kNodes});
auto result = zbitl::CpuTopologyTable::FromPayload(ZBI_TYPE_CPU_TOPOLOGY, payload.as_bytes());
ASSERT_FALSE(result.is_error()) << result.error_value();
const auto table = std::move(result).value();
ASSERT_NO_FATAL_FAILURE(ExpectTableHasArmNodes(table, {kNodes}));
}
}
TEST(CpuTopologyTableTests, Sherlock) {
// The CPU topology of the Sherlock board.
constexpr zbi_topology_node_t kSherlockNodes[] = {
{
.entity_type = ZBI_TOPOLOGY_ENTITY_CLUSTER,
.parent_index = 0,
.entity = {.cluster = {.performance_class = 0}},
},
{
.entity_type = ZBI_TOPOLOGY_ENTITY_PROCESSOR,
.parent_index = 0,
.entity =
{
.processor =
{
.logical_ids = {0},
.logical_id_count = 1,
.flags = ZBI_TOPOLOGY_PROCESSOR_PRIMARY,
.architecture = ZBI_TOPOLOGY_ARCH_ARM,
.architecture_info =
{
.arm =
{
.cluster_1_id = 0,
.cpu_id = 0,
.gic_id = 0,
},
},
},
},
},
{
.entity_type = ZBI_TOPOLOGY_ENTITY_PROCESSOR,
.parent_index = 0,
.entity =
{
.processor =
{
.logical_ids = {1},
.logical_id_count = 1,
.flags = 0,
.architecture = ZBI_TOPOLOGY_ARCH_ARM,
.architecture_info =
{
.arm =
{
.cluster_1_id = 0,
.cpu_id = 1,
.gic_id = 1,
},
},
},
},
},
{
.entity_type = ZBI_TOPOLOGY_ENTITY_CLUSTER,
.parent_index = 0,
.entity = {.cluster = {.performance_class = 1}},
},
{
.entity_type = ZBI_TOPOLOGY_ENTITY_PROCESSOR,
.parent_index = 3,
.entity =
{
.processor =
{
.logical_ids = {2},
.logical_id_count = 1,
.flags = 0,
.architecture = ZBI_TOPOLOGY_ARCH_ARM,
.architecture_info =
{
.arm =
{
.cluster_1_id = 1,
.cpu_id = 0,
.gic_id = 4,
},
},
},
},
},
{
.entity_type = ZBI_TOPOLOGY_ENTITY_PROCESSOR,
.parent_index = 3,
.entity =
{
.processor =
{
.logical_ids = {3},
.logical_id_count = 1,
.flags = 0,
.architecture = ZBI_TOPOLOGY_ARCH_ARM,
.architecture_info =
{
.arm =
{
.cluster_1_id = 1,
.cpu_id = 1,
.gic_id = 5,
},
},
},
},
},
{
.entity_type = ZBI_TOPOLOGY_ENTITY_PROCESSOR,
.parent_index = 3,
.entity =
{
.processor =
{
.logical_ids = {4},
.logical_id_count = 1,
.flags = 0,
.architecture = ZBI_TOPOLOGY_ARCH_ARM,
.architecture_info =
{
.arm =
{
.cluster_1_id = 1,
.cpu_id = 2,
.gic_id = 6,
},
},
},
},
},
{
.entity_type = ZBI_TOPOLOGY_ENTITY_PROCESSOR,
.parent_index = 3,
.entity =
{
.processor =
{
.logical_ids = {5},
.logical_id_count = 1,
.flags = 0,
.architecture = ZBI_TOPOLOGY_ARCH_ARM,
.architecture_info =
{
.arm =
{
.cluster_1_id = 1,
.cpu_id = 3,
.gic_id = 7,
},
},
},
},
},
};
CpuTopologyPayload payload({kSherlockNodes});
auto result = zbitl::CpuTopologyTable::FromPayload(ZBI_TYPE_CPU_TOPOLOGY, payload.as_bytes());
ASSERT_FALSE(result.is_error()) << result.error_value();
const auto table = std::move(result).value();
ASSERT_NO_FATAL_FAILURE(ExpectTableHasArmNodes(table, {kSherlockNodes}));
}
} // namespace