blob: 05a8db1e0d3767f726cda1e5dbc0ac357257578a [file] [log] [blame]
// Copyright 2018 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/system-topology.h>
#include <lib/unittest/unittest.h>
namespace {
using system_topology::Graph;
using system_topology::Node;
// Should be larger than the largest topology used here.
constexpr size_t kTopologyArraySize = 60;
struct FlatTopo {
zbi_topology_node_t nodes[kTopologyArraySize];
size_t node_count = 0;
};
// Defined at bottom of file, they are long and noisy.
// A generic arm big.LITTLE.
FlatTopo SimpleTopology();
// Roughly a threadripper 2990X.
FlatTopo ComplexTopology();
// Same as Simple but stored with all nodes on a level adjacent to each other.
FlatTopo HierarchicalTopology();
bool test_flat_to_heap_simple() {
BEGIN_TEST;
FlatTopo topo = SimpleTopology();
Graph graph;
ASSERT_EQ(ZX_OK, Graph::Initialize(&graph, topo.nodes, topo.node_count));
ASSERT_EQ(3u, graph.processors().size());
// Test lookup.
system_topology::Node* node;
ASSERT_EQ(ZX_OK, graph.ProcessorByLogicalId(1, &node));
ASSERT_EQ(ZBI_TOPOLOGY_ENTITY_PROCESSOR, node->entity_type);
ASSERT_EQ(ZBI_TOPOLOGY_PROCESSOR_PRIMARY, node->entity.processor.flags);
ASSERT_EQ(ZBI_TOPOLOGY_ENTITY_CLUSTER, node->parent->entity_type);
ASSERT_EQ(1, node->parent->entity.cluster.performance_class);
END_TEST;
}
bool test_flat_to_heap_complex() {
BEGIN_TEST;
FlatTopo topo = ComplexTopology();
Graph graph;
ASSERT_EQ(ZX_OK, Graph::Initialize(&graph, topo.nodes, topo.node_count));
ASSERT_EQ(32u, graph.processors().size());
END_TEST;
}
bool test_flat_to_heap_walk_result() {
BEGIN_TEST;
FlatTopo topo = ComplexTopology();
Graph graph;
ASSERT_EQ(ZX_OK, Graph::Initialize(&graph, topo.nodes, topo.node_count));
ASSERT_EQ(32u, graph.processors().size());
// For each processor we walk all the way up the graph.
for (Node* processor : graph.processors()) {
Node* current = processor;
Node* next = current->parent;
while (next != nullptr) {
// Ensure that the children lists contain all children.
bool found = false;
for (Node* child : next->children) {
found |= child == current;
}
ASSERT_TRUE(found, "A node is not listed as a child of its parent.");
current = current->parent;
next = current->parent;
}
}
END_TEST;
}
bool test_validate_processor_not_leaf() {
BEGIN_TEST;
FlatTopo topo = ComplexTopology();
// Replace a die node with a processor.
topo.nodes[1].entity_type = ZBI_TOPOLOGY_ENTITY_PROCESSOR;
Graph graph;
ASSERT_EQ(ZX_ERR_INVALID_ARGS, Graph::Initialize(&graph, topo.nodes, topo.node_count));
END_TEST;
}
bool test_validate_leaf_not_processor() {
BEGIN_TEST;
FlatTopo topo = SimpleTopology();
// Replace a processor node with a cluster.
topo.nodes[4].entity_type = ZBI_TOPOLOGY_ENTITY_CLUSTER;
Graph graph;
ASSERT_EQ(ZX_ERR_INVALID_ARGS, Graph::Initialize(&graph, topo.nodes, topo.node_count));
END_TEST;
}
bool test_validate_cycle() {
BEGIN_TEST;
FlatTopo topo = ComplexTopology();
// Set the parent index of the die to a processor under it.
topo.nodes[1].parent_index = 4;
Graph graph;
ASSERT_EQ(ZX_ERR_INVALID_ARGS, Graph::Initialize(&graph, topo.nodes, topo.node_count));
END_TEST;
}
// This is a cycle like above but fails due to parent mismatch with other nodes
// on its level.
bool test_validate_cycle_shared_parent() {
BEGIN_TEST;
FlatTopo topo = ComplexTopology();
// Set the parent index of the die to a processor under it.
topo.nodes[2].parent_index = 4;
Graph graph;
ASSERT_EQ(ZX_ERR_INVALID_ARGS, Graph::Initialize(&graph, topo.nodes, topo.node_count));
END_TEST;
}
// Another logical way to store the graph would be hierarchical, all the top
// level nodes together, followed by the next level, and so on.
// We are proscriptive however that they should be stored in a depth-first
// ordering, so this other ordering should fail validation.
bool test_validate_hierarchical_storage() {
BEGIN_TEST;
FlatTopo topo = HierarchicalTopology();
// Set the parent index of the die to a processor under it.
topo.nodes[2].parent_index = 4;
Graph graph;
ASSERT_EQ(ZX_ERR_INVALID_ARGS, Graph::Initialize(&graph, topo.nodes, topo.node_count));
END_TEST;
}
UNITTEST_START_TESTCASE(system_topology_tests)
UNITTEST("Parse flat topology, simple.", test_flat_to_heap_simple)
UNITTEST("Parse flat topology, complex.", test_flat_to_heap_complex)
UNITTEST("Parse complex then walk result.", test_flat_to_heap_walk_result)
UNITTEST("Fail validation if processor is not a leaf.", test_validate_processor_not_leaf)
UNITTEST("Fail validation if leaf is not processor.", test_validate_leaf_not_processor)
UNITTEST("Fail validation if there is a cycle.", test_validate_cycle)
UNITTEST("Fail validation if a cycle with a shared parent.", test_validate_cycle_shared_parent)
UNITTEST("Fail validation if storage order is incorrect.", test_validate_hierarchical_storage)
UNITTEST_END_TESTCASE(system_topology_tests, "system-topology",
"Test parsing and validation of the flat system topology.")
// Generic ARM big.LITTLE layout.
// [cluster] [cluster]
// [p1] [p3] [p4]
FlatTopo SimpleTopology() {
FlatTopo topo;
zbi_topology_node_t* nodes = topo.nodes;
uint16_t logical_processor = 0;
uint16_t big_cluster = 0, little_cluster = 0;
uint16_t index = 0;
nodes[big_cluster = index++] = (zbi_topology_node_t){.entity_type = ZBI_TOPOLOGY_ENTITY_CLUSTER,
.parent_index = ZBI_TOPOLOGY_NO_PARENT,
.entity = {.cluster = {
.performance_class = 1,
}}};
nodes[index++] = (zbi_topology_node_t){
.entity_type = ZBI_TOPOLOGY_ENTITY_PROCESSOR,
.parent_index = big_cluster,
.entity = {.processor = {
.logical_ids = {logical_processor++, logical_processor++},
.logical_id_count = 2,
.flags = ZBI_TOPOLOGY_PROCESSOR_PRIMARY,
.architecture = ZBI_TOPOLOGY_ARCH_UNDEFINED,
.architecture_info = {},
}}};
nodes[little_cluster = index++] =
(zbi_topology_node_t){.entity_type = ZBI_TOPOLOGY_ENTITY_CLUSTER,
.parent_index = ZBI_TOPOLOGY_NO_PARENT,
.entity = {.cluster = {
.performance_class = 0,
}}};
nodes[index++] = (zbi_topology_node_t){.entity_type = ZBI_TOPOLOGY_ENTITY_PROCESSOR,
.parent_index = little_cluster,
.entity = {.processor = {
.logical_ids = {logical_processor++},
.logical_id_count = 1,
.flags = 0,
.architecture = ZBI_TOPOLOGY_ARCH_UNDEFINED,
.architecture_info = {},
}}};
nodes[index++] = (zbi_topology_node_t){.entity_type = ZBI_TOPOLOGY_ENTITY_PROCESSOR,
.parent_index = little_cluster,
.entity = {.processor = {
.logical_ids = {logical_processor++},
.logical_id_count = 1,
.flags = 0,
.architecture = ZBI_TOPOLOGY_ARCH_UNDEFINED,
.architecture_info = {},
}}};
topo.node_count = index;
return topo;
}
FlatTopo HierarchicalTopology() {
FlatTopo topo;
zbi_topology_node_t* nodes = topo.nodes;
uint16_t logical_processor = 0;
uint16_t big_cluster = 0, little_cluster = 0;
uint16_t index = 0;
nodes[big_cluster = index++] = (zbi_topology_node_t){.entity_type = ZBI_TOPOLOGY_ENTITY_CLUSTER,
.parent_index = ZBI_TOPOLOGY_NO_PARENT,
.entity = {.cluster = {
.performance_class = 1,
}}};
nodes[little_cluster = index++] =
(zbi_topology_node_t){.entity_type = ZBI_TOPOLOGY_ENTITY_CLUSTER,
.parent_index = ZBI_TOPOLOGY_NO_PARENT,
.entity = {.cluster = {
.performance_class = 0,
}}};
nodes[index++] = (zbi_topology_node_t){
.entity_type = ZBI_TOPOLOGY_ENTITY_PROCESSOR,
.parent_index = big_cluster,
.entity = {.processor = {
.logical_ids = {logical_processor++, logical_processor++},
.logical_id_count = 2,
.flags = ZBI_TOPOLOGY_PROCESSOR_PRIMARY,
.architecture = ZBI_TOPOLOGY_ARCH_UNDEFINED,
.architecture_info = {},
}}};
nodes[index++] = (zbi_topology_node_t){.entity_type = ZBI_TOPOLOGY_ENTITY_PROCESSOR,
.parent_index = little_cluster,
.entity = {.processor = {
.logical_ids = {logical_processor++},
.logical_id_count = 1,
.flags = 0,
.architecture = ZBI_TOPOLOGY_ARCH_UNDEFINED,
.architecture_info = {},
}}};
nodes[index++] = (zbi_topology_node_t){.entity_type = ZBI_TOPOLOGY_ENTITY_PROCESSOR,
.parent_index = little_cluster,
.entity = {.processor = {
.logical_ids = {logical_processor++},
.logical_id_count = 1,
.flags = 0,
.architecture = ZBI_TOPOLOGY_ARCH_UNDEFINED,
.architecture_info = {},
}}};
topo.node_count = index;
return topo;
}
// Add a threadripper CCX (CPU complex), a four core cluster.
void AddCCX(uint16_t parent, zbi_topology_node_t* nodes, uint16_t* index,
uint16_t* logical_processor) {
uint16_t cache = 0, cluster = 0;
nodes[cluster = (*index)++] = (zbi_topology_node_t){.entity_type = ZBI_TOPOLOGY_ENTITY_CLUSTER,
.parent_index = parent,
.entity = {.cluster = {
.performance_class = 0,
}}};
nodes[cache = (*index)++] = (zbi_topology_node_t){
.entity_type = ZBI_TOPOLOGY_ENTITY_CACHE,
.parent_index = cluster,
.entity = {},
};
for (int i = 0; i < 4; i++) {
nodes[(*index)++] = (zbi_topology_node_t){
.entity_type = ZBI_TOPOLOGY_ENTITY_PROCESSOR,
.parent_index = cache,
.entity = {.processor = {
.logical_ids = {(*logical_processor)++, (*logical_processor)++},
.logical_id_count = 2,
.flags = 0,
.architecture = ZBI_TOPOLOGY_ARCH_UNDEFINED,
.architecture_info = {},
}}};
}
}
// Roughly a threadripper 2990X.
// Four sets of the following:
// [numa1]
// [die1]
// [cluster1] [cluster2]
// [cache1] [cache2]
// [p1][p2][p3][p4] [p5][p6][p7][p8]
FlatTopo ComplexTopology() {
FlatTopo topo;
zbi_topology_node_t* nodes = topo.nodes;
uint16_t logical_processor = 0;
uint16_t die[4] = {0};
uint16_t numa[4] = {0};
uint16_t index = 0;
nodes[numa[0] = index++] = (zbi_topology_node_t){.entity_type = ZBI_TOPOLOGY_ENTITY_NUMA_REGION,
.parent_index = ZBI_TOPOLOGY_NO_PARENT,
.entity = {.numa_region = {
.start_address = 0x1,
.end_address = 0x2,
}}};
nodes[die[0] = index++] = (zbi_topology_node_t){
.entity_type = ZBI_TOPOLOGY_ENTITY_DIE,
.parent_index = numa[0],
.entity = {},
};
AddCCX(die[0], nodes, &index, &logical_processor);
AddCCX(die[0], nodes, &index, &logical_processor);
nodes[numa[1] = index++] = (zbi_topology_node_t){.entity_type = ZBI_TOPOLOGY_ENTITY_NUMA_REGION,
.parent_index = ZBI_TOPOLOGY_NO_PARENT,
.entity = {.numa_region = {
.start_address = 0x3,
.end_address = 0x4,
}}};
nodes[die[1] = index++] = (zbi_topology_node_t){
.entity_type = ZBI_TOPOLOGY_ENTITY_DIE,
.parent_index = numa[1],
.entity = {},
};
AddCCX(die[1], nodes, &index, &logical_processor);
AddCCX(die[1], nodes, &index, &logical_processor);
nodes[numa[2] = index++] = (zbi_topology_node_t){.entity_type = ZBI_TOPOLOGY_ENTITY_NUMA_REGION,
.parent_index = ZBI_TOPOLOGY_NO_PARENT,
.entity = {.numa_region = {
.start_address = 0x5,
.end_address = 0x6,
}}};
nodes[die[2] = index++] = (zbi_topology_node_t){
.entity_type = ZBI_TOPOLOGY_ENTITY_DIE,
.parent_index = numa[2],
.entity = {},
};
AddCCX(die[2], nodes, &index, &logical_processor);
AddCCX(die[2], nodes, &index, &logical_processor);
nodes[numa[3] = index++] = (zbi_topology_node_t){.entity_type = ZBI_TOPOLOGY_ENTITY_NUMA_REGION,
.parent_index = ZBI_TOPOLOGY_NO_PARENT,
.entity = {.numa_region = {
.start_address = 0x7,
.end_address = 0x8,
}}};
nodes[die[3] = index++] = (zbi_topology_node_t){
.entity_type = ZBI_TOPOLOGY_ENTITY_DIE,
.parent_index = numa[3],
.entity = {},
};
AddCCX(die[3], nodes, &index, &logical_processor);
AddCCX(die[3], nodes, &index, &logical_processor);
topo.node_count = index;
return topo;
}
} // namespace
/*
int main(int argc, char** argv) {
return unittest_run_all_tests(argc, argv) ? 0 : -1;
}
*/