[zx][topology] Add core system-topology logic.

This defines both the "flat" form of the topology that we expect to be
passed from the bootloader or generated in early boot. It also defines
the "heap" form of the topology that will be maintained at runtime and
referenced by all interested parties.

This currently is static after early boot and as such doesn't use locks,
if we move to a world where we want to update this later in execution
(when we are multithreaded) we will need to add locks.

Issue: ZX-3068

Test: Added hosttest unit tests.
Change-Id: Ie2490a1d8ffa5b4fffa5531b0ee71118e0075279
diff --git a/kernel/lib/topology/include/lib/system-topology.h b/kernel/lib/topology/include/lib/system-topology.h
new file mode 100644
index 0000000..83d7a75
--- /dev/null
+++ b/kernel/lib/topology/include/lib/system-topology.h
@@ -0,0 +1,102 @@
+// Copyright 2018 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.
+
+#ifndef KERNEL_LIB_TOPOLOGY_SYSTEM_TOPOLOGY_H_
+#define KERNEL_LIB_TOPOLOGY_SYSTEM_TOPOLOGY_H_
+
+#include <fbl/unique_ptr.h>
+#include <fbl/vector.h>
+#include <zircon/boot/image.h>
+#include <zircon/types.h>
+
+/*
+ * Captures the physical layout of the core system (processors, caches, etc..).
+ * The data will be layed out as a tree, with processor nodes on the bottom and other types above
+ * them. The expected usage is to start from a processor node and walk up/down to discover the
+ * relationships you are interested in.
+ */
+
+namespace system_topology {
+// A single node in the topology graph. The union and types here mirror the flat structure,
+// zbi_topology_node_t.
+struct Node {
+    uint8_t entity_type;
+    union {
+        zbi_topology_processor_t processor;
+        zbi_topology_cluster_t cluster;
+        zbi_topology_numa_region_t numa_region;
+    } entity;
+    Node* parent;
+    fbl::Vector<Node*> children;
+};
+
+// We define a typedef here as we may want to change this type as the design evolves. For example,
+// if we add run-time updateability we may want to hold a lock.
+typedef const fbl::Vector<Node*>& IterableProcessors;
+
+// A view of the system topology that is defined in early boot and static during the run of the
+// system.
+class Graph {
+public:
+    // Takes the flat topology array, valdates it, and sets it as the current topology. Returns an
+    // error if the topology is invalid.
+    //
+    // This should only be called during early boot (platform_init), after that this data is
+    // considered static so no locks are used. If it is desired to set this later in operation than
+    // we MUST redesign this process to consider concurrent readers.
+    // Returns ZX_ERR_ALREADY_EXISTS if state already set or ZX_ERR_INVALID_ARGS if provided graph
+    // fails validation.
+    zx_status_t Update(zbi_topology_node_t* nodes, size_t count);
+
+    // Provides iterable container of pointers to all processor nodes.
+    IterableProcessors processors() {
+        return processors_;
+    }
+
+    // Finds the processor node that is assigned the given logical id.
+    // Sets processor to point to that node. If it wasn't found, returns ZX_ERR_NOT_FOUND.
+    zx_status_t ProcessorByLogicalId(uint16_t id, Node** processor) {
+        if (id > processors_by_logical_id_.size()) {
+            return ZX_ERR_NOT_FOUND;
+        }
+
+        *processor = processors_by_logical_id_[id];
+        return ZX_OK;
+    }
+
+private:
+    // Validates that in the provided flat topology:
+    //   - all processors are leaf nodes, and all leaf nodes are processors.
+    //   - there are no cycles.
+    //   - It is stored in a "depth first" ordering, with parents adjacent to
+    //   their children.
+    bool Validate(zbi_topology_node_t* nodes, int count) const;
+
+    fbl::unique_ptr<Node[]> nodes_;
+    fbl::Vector<Node*> processors_;
+
+    // This is in essence a map with logical ID being the index in the vector.
+    // It will contain duplicates for SMT processors so we need it in addition to processors_.
+    fbl::Vector<Node*> processors_by_logical_id_;
+};
+
+// Get the global instance of the SystemTopology. This will be updated in early boot to contain the
+// source of truth view of the system.
+// This should be called once before the platform becomes multithreaded. We don't use a raw global
+// because we can't ensure that it is initialized, if this is used in the initialization of other
+// global objects.
+inline Graph& GetMutableSystemTopology() {
+    static Graph graph;
+    return graph;
+}
+
+// The method of the above most things should use, only the platform init code needs the mutable
+// version.
+inline const Graph& GetSystemTopology() {
+    return GetMutableSystemTopology();
+}
+
+} // namespace system_topology
+
+#endif //KERNEL_LIB_TOPOLOGY_SYSTEM_TOPOLOGY_H_
diff --git a/kernel/lib/topology/rules.mk b/kernel/lib/topology/rules.mk
new file mode 100644
index 0000000..1039bbf
--- /dev/null
+++ b/kernel/lib/topology/rules.mk
@@ -0,0 +1,52 @@
+# Copyright 2018 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.
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+# Common Code
+
+LOCAL_SRCS := \
+    $(LOCAL_DIR)/system-topology.cpp \
+
+# system-topology
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_GROUP := core
+
+MODULE_SRCS := $(LOCAL_SRCS)
+
+MODULE_DEPS := \
+    kernel/lib/fbl \
+
+MODULE_NAME := system-topology
+
+include make/module.mk
+
+
+# system-topology_test
+
+MODULE := $(LOCAL_DIR).test
+
+MODULE_TYPE := hosttest
+
+MODULE_SRCS := $(LOCAL_SRCS) $(LOCAL_DIR)/system-topology_test.cpp
+
+MODULE_COMPILEFLAGS := \
+        -Isystem/ulib/fbl/include \
+        -Isystem/ulib/runtests-utils/include \
+        -Isystem/ulib/unittest/include \
+
+MODULE_HOST_LIBS := \
+    system/ulib/fbl.hostlib \
+    system/ulib/pretty.hostlib \
+    system/ulib/runtests-utils.hostlib \
+    system/ulib/unittest.hostlib \
+
+MODULE_NAME := system-topology_test
+
+MODULE_DEFINES += BUILD_FOR_TEST=1
+
+include make/module.mk
+
diff --git a/kernel/lib/topology/system-topology.cpp b/kernel/lib/topology/system-topology.cpp
new file mode 100644
index 0000000..7f96355
--- /dev/null
+++ b/kernel/lib/topology/system-topology.cpp
@@ -0,0 +1,163 @@
+#include <lib/system-topology.h>
+#include <zircon/errors.h>
+
+namespace system_topology {
+namespace {
+constexpr size_t kMaxTopologyDepth = 20;
+
+inline void ValidationError(int index, const char* message) {
+    printf("Error validating topology at node %d : %s \n", index, message);
+}
+
+} // namespace
+
+zx_status_t Graph::Update(zbi_topology_node_t* flat_nodes, size_t count) {
+    if (flat_nodes == nullptr || count == 0 || !Validate(flat_nodes, static_cast<int>(count))) {
+        return ZX_ERR_INVALID_ARGS;
+    }
+
+    if (nodes_.get() != nullptr) {
+        return ZX_ERR_ALREADY_EXISTS;
+    }
+
+    fbl::AllocChecker checker;
+    nodes_.reset(new Node[count]{{}});
+
+    Node* node = nullptr;
+    zbi_topology_node_t* flat_node = nullptr;
+    for (size_t i = 0; i < count; ++i) {
+        flat_node = &flat_nodes[i];
+        node = &nodes_[i];
+
+        node->entity_type = flat_node->entity_type;
+
+        // Copy info.
+        switch (node->entity_type) {
+        case ZBI_TOPOLOGY_ENTITY_PROCESSOR:
+            node->entity.processor = flat_node->entity.processor;
+
+            processors_.push_back(node, &checker);
+            if (!checker.check()) {
+                nodes_.reset(nullptr);
+                return ZX_ERR_NO_MEMORY;
+            }
+
+            for (int i = 0; i < node->entity.processor.logical_id_count; ++i) {
+                processors_by_logical_id_.insert(node->entity.processor.logical_ids[i],
+                                                 node, &checker);
+                if (!checker.check()) {
+                    nodes_.reset(nullptr);
+                    return ZX_ERR_NO_MEMORY;
+                }
+            }
+
+            break;
+        case ZBI_TOPOLOGY_ENTITY_CLUSTER:
+            node->entity.cluster = flat_node->entity.cluster;
+            break;
+        case ZBI_TOPOLOGY_ENTITY_NUMA_REGION:
+            node->entity.numa_region = flat_node->entity.numa_region;
+            break;
+        default:
+            // Other types don't have attached info.
+            break;
+        }
+
+        if (flat_node->parent_index != ZBI_TOPOLOGY_NO_PARENT) {
+            // Validation should have prevented this.
+            ZX_DEBUG_ASSERT_MSG(flat_node->parent_index >= 0 && flat_node->parent_index < count,
+                                "parent_index out of range: %u\n", flat_node->parent_index);
+
+            node->parent = &nodes_[flat_node->parent_index];
+            node->parent->children.push_back(node, &checker);
+            if (!checker.check()) {
+                nodes_.reset(nullptr);
+                return ZX_ERR_NO_MEMORY;
+            }
+        }
+    }
+
+    return ZX_OK;
+}
+
+bool Graph::Validate(zbi_topology_node_t* nodes, int count) const {
+    uint16_t parents[kMaxTopologyDepth];
+    for (size_t i = 0; i < kMaxTopologyDepth; ++i) {
+        parents[i] = ZBI_TOPOLOGY_NO_PARENT;
+    }
+
+    uint8_t current_type = ZBI_TOPOLOGY_ENTITY_UNDEFINED;
+    int current_depth = 0;
+
+    zbi_topology_node_t* node;
+    for (int current_index = count - 1; current_index >= 0; current_index--) {
+        node = &nodes[current_index];
+
+        if (current_type == ZBI_TOPOLOGY_ENTITY_UNDEFINED) {
+            current_type = node->entity_type;
+        }
+
+        if (current_type != node->entity_type) {
+
+            if (current_index == parents[current_depth]) {
+                // If the type changes then it should be the parent of the
+                // previous level.
+                current_depth++;
+
+                if (current_depth == kMaxTopologyDepth) {
+                    ValidationError(current_index,
+                                    "Structure is too deep, we only support 20 levels.");
+                    return false;
+                }
+            } else if (node->entity_type == ZBI_TOPOLOGY_ENTITY_PROCESSOR) {
+                // If it isn't the parent of the previous level, but it is a process than we have
+                // encountered a new branch and should start walking from the bottom again.
+
+                // Clear the parent index's for all levels but the top, we want to ensure that the
+                // top level of the new branch reports to the same parent as we do.
+                for (int i = current_depth - 1; i >= 0; --i) {
+                    parents[i] = ZBI_TOPOLOGY_NO_PARENT;
+                }
+                current_depth = 0;
+            } else {
+                // Otherwise the structure is incorrect.
+                ValidationError(current_index,
+                                "Graph is not stored in correct order, with children adjacent to "
+                                "parents");
+                return false;
+            }
+            current_type = node->entity_type;
+        }
+
+        if (parents[current_depth] == ZBI_TOPOLOGY_NO_PARENT) {
+            parents[current_depth] = node->parent_index;
+        } else if (parents[current_depth] != node->parent_index) {
+            ValidationError(current_index, "Parents at level do not match.");
+            return false;
+        }
+
+        // Ensure that all leaf nodes are processors.
+        if (current_depth == 0 && node->entity_type != ZBI_TOPOLOGY_ENTITY_PROCESSOR) {
+            ValidationError(current_index, "Encountered a leaf node that isn't a processor.");
+            return false;
+        }
+
+        // Ensure that all processors are leaf nodes.
+        if (current_depth != 0 && node->entity_type == ZBI_TOPOLOGY_ENTITY_PROCESSOR) {
+            ValidationError(current_index, "Encountered a processor that isn't a leaf node.");
+            return false;
+        }
+
+        // By the time we reach the first parent we should be at the maximum depth and have no
+        // parents defined.
+        if (current_index == 0 && parents[current_depth] != ZBI_TOPOLOGY_NO_PARENT &&
+            (current_depth == kMaxTopologyDepth - 1 ||
+             parents[current_depth + 1] == ZBI_TOPOLOGY_NO_PARENT)) {
+            ValidationError(current_index, "Top level of tree should not have a parent");
+            return false;
+        }
+    }
+    return true;
+}
+
+} // namespace system_topology
diff --git a/kernel/lib/topology/system-topology_test.cpp b/kernel/lib/topology/system-topology_test.cpp
new file mode 100644
index 0000000..32f37cd
--- /dev/null
+++ b/kernel/lib/topology/system-topology_test.cpp
@@ -0,0 +1,424 @@
+// Copyright 2018 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/system-topology.h>
+#include <unittest/unittest.h>
+
+namespace {
+
+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 noisey.
+
+// 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();
+
+    system_topology::Graph graph;
+    ASSERT_EQ(ZX_OK, graph.Update(topo.nodes, topo.node_count));
+    ASSERT_EQ(3, 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();
+
+    system_topology::Graph graph;
+    ASSERT_EQ(ZX_OK, graph.Update(topo.nodes, topo.node_count));
+    ASSERT_EQ(32, graph.processors().size());
+
+    END_TEST;
+}
+
+bool test_flat_to_heap_walk_result() {
+    BEGIN_TEST;
+    FlatTopo topo = ComplexTopology();
+
+    system_topology::Graph graph;
+    ASSERT_EQ(ZX_OK, graph.Update(topo.nodes, topo.node_count));
+    ASSERT_EQ(32, 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;
+
+    system_topology::Graph graph;
+    ASSERT_EQ(ZX_ERR_INVALID_ARGS, graph.Update(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;
+
+    system_topology::Graph graph;
+    ASSERT_EQ(ZX_ERR_INVALID_ARGS, graph.Update(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;
+
+    system_topology::Graph graph;
+    ASSERT_EQ(ZX_ERR_INVALID_ARGS, graph.Update(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;
+
+    system_topology::Graph graph;
+    ASSERT_EQ(ZX_ERR_INVALID_ARGS, graph.Update(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;
+
+    system_topology::Graph graph;
+    ASSERT_EQ(ZX_ERR_INVALID_ARGS, graph.Update(topo.nodes, topo.node_count));
+
+    END_TEST;
+}
+
+BEGIN_TEST_CASE(system_topology_parsing_tests)
+RUN_TEST(test_flat_to_heap_simple)
+RUN_TEST(test_flat_to_heap_complex)
+RUN_TEST(test_flat_to_heap_walk_result)
+END_TEST_CASE(system_topology_parsing_tests)
+
+BEGIN_TEST_CASE(system_topology_validation_tests)
+RUN_TEST(test_validate_processor_not_leaf)
+RUN_TEST(test_validate_leaf_not_processor)
+RUN_TEST(test_validate_cycle)
+RUN_TEST(test_validate_cycle_shared_parent)
+RUN_TEST(test_validate_hierarchical_storage)
+END_TEST_CASE(system_topology_validation_tests)
+
+// 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;
+
+    size_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,
+            }}};
+
+    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,
+                .architecture = ZBI_TOPOLOGY_ARCH_UNDEFINED,
+            }}};
+
+    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,
+                .architecture = ZBI_TOPOLOGY_ARCH_UNDEFINED,
+            }}};
+
+    EXPECT_LT(index, kTopologyArraySize);
+    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;
+
+    size_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,
+            }}};
+
+    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 = ZBI_TOPOLOGY_PROCESSOR_PRIMARY,
+                .architecture = ZBI_TOPOLOGY_ARCH_UNDEFINED,
+            }}};
+
+    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 = ZBI_TOPOLOGY_PROCESSOR_PRIMARY,
+                .architecture = ZBI_TOPOLOGY_ARCH_UNDEFINED,
+            }}};
+
+    EXPECT_LT(index, kTopologyArraySize);
+    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,
+            size_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,
+    };
+
+    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,
+                    .architecture = ZBI_TOPOLOGY_ARCH_UNDEFINED,
+                }}};
+    }
+}
+
+// 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};
+
+    size_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],
+    };
+
+    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],
+    };
+
+    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],
+    };
+
+    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],
+    };
+
+    AddCCX(die[3], nodes, &index, &logical_processor);
+    AddCCX(die[3], nodes, &index, &logical_processor);
+
+    EXPECT_LT(index, kTopologyArraySize);
+    topo.node_count = index;
+    return topo;
+}
+
+} // namespace
+
+int main(int argc, char** argv) {
+    return unittest_run_all_tests(argc, argv) ? 0 : -1;
+}
diff --git a/kernel/platform/rules.mk b/kernel/platform/rules.mk
index a30c7e2..550bcd4 100644
--- a/kernel/platform/rules.mk
+++ b/kernel/platform/rules.mk
@@ -19,5 +19,3 @@
 	kernel/lib/zxcpp
 
 include make/module.mk
-
-
diff --git a/system/public/zircon/boot/image.h b/system/public/zircon/boot/image.h
index d700516..de2411e 100644
--- a/system/public/zircon/boot/image.h
+++ b/system/public/zircon/boot/image.h
@@ -112,7 +112,8 @@
     macro(ZBI_TYPE_CRASHLOG, "CRASHLOG", ".bin") \
     macro(ZBI_TYPE_NVRAM, "NVRAM", ".bin") \
     macro(ZBI_TYPE_PLATFORM_ID, "PLATFORM_ID", ".bin") \
-    macro(ZBI_TYPE_CPU_CONFIG, "CPU_CONFIG", ".bin") \
+    macro(ZBI_TYPE_CPU_CONFIG, "CPU_CONFIG", ".bin") /* Deprecated */ \
+    macro(ZBI_TYPE_CPU_TOPOLOGY, "CPU_TOPOLOGY", ".bin") \
     macro(ZBI_TYPE_MEM_CONFIG, "MEM_CONFIG", ".bin") \
     macro(ZBI_TYPE_KERNEL_DRIVER, "KERNEL_DRIVER", ".bin") \
     macro(ZBI_TYPE_ACPI_RSDP, "ACPI_RSDP", ".bin") \
@@ -406,6 +407,103 @@
 } zbi_cpu_config_t;
 #endif
 
+#define ZBI_TYPE_CPU_TOPOLOGY           (0x544F504F) // TOPO
+
+#ifndef __ASSEMBLER__
+
+#define ZBI_MAX_SMT 4
+
+// These are Used in the flags field of zbi_topology_processor_t.
+
+// This is the processor that boots the system and the last to be shutdown.
+#define ZBI_TOPOLOGY_PROCESSOR_PRIMARY 0b1
+
+// This is the processor that handles all interrupts, some architectures will
+// not have one.
+#define ZBI_TOPOLOGY_PROCESSOR_INTERRUPT 0b10
+
+#define ZBI_TOPOLOGY_NO_PARENT 0xFFFF
+
+typedef enum {
+    ZBI_TOPOLOGY_ARCH_UNDEFINED = 0, // Intended primarily for testing.
+    ZBI_TOPOLOGY_ARCH_X86 = 1,
+    ZBI_TOPOLOGY_ARCH_ARM = 2,
+} zbi_topology_architecture_t;
+
+typedef struct {
+    // Cluster ids for each level, one being closest to the cpu.
+    // These map to aff1, aff2, and aff3 values in the ARM registers.
+    uint8_t cluster_1_id;
+    uint8_t cluster_2_id;
+    uint8_t cluster_3_id;
+
+    // Id of the cpu inside of the bottom-most cluster, aff0 value.
+    uint8_t cpu_id;
+
+    // The GIC interface number for this processor.
+    // In GIC v3+ this is not necessary as the processors are addressed by their
+    // affinity routing (all cluster ids followed by cpu_id).
+    uint8_t gic_id;
+}  zbi_topology_arm_info_t;
+
+typedef struct {
+    uint32_t apic_id;
+}  zbi_topology_x86_info_t;
+
+typedef struct {
+    uint16_t logical_ids[ZBI_MAX_SMT];
+    uint8_t logical_id_count;
+
+    uint16_t flags;
+
+    // Should be one of zbi_topology_arm_info_t.
+    // If UNDEFINED then nothing will be set in arch_info.
+    uint8_t architecture;
+    union {
+        zbi_topology_arm_info_t arm;
+        zbi_topology_x86_info_t x86;
+    } architecture_info;
+
+} zbi_topology_processor_t;
+
+typedef struct {
+    // Relative performance level of this processor in the system, with 0
+    // representing the lowest performance.
+    // For example on a two cluster ARM big.LITTLE system 0 would be the little
+    // cores and 1 would represent the big cores.
+    uint8_t performance_class;
+} zbi_topology_cluster_t;
+
+typedef struct {
+  // Starting and ending memory addresses of this numa region.
+  uint64_t start_address;
+  uint64_t end_address;
+} zbi_topology_numa_region_t;
+
+typedef enum {
+    ZBI_TOPOLOGY_ENTITY_UNDEFINED = 0, // Unused default.
+    ZBI_TOPOLOGY_ENTITY_PROCESSOR = 1,
+    ZBI_TOPOLOGY_ENTITY_CLUSTER = 2,
+    ZBI_TOPOLOGY_ENTITY_CACHE = 3,
+    ZBI_TOPOLOGY_ENTITY_DIE = 4,
+    ZBI_TOPOLOGY_ENTITY_SOCKET = 5,
+    ZBI_TOPOLOGY_ENTITY_POWER_PLANE = 6,
+    ZBI_TOPOLOGY_ENTITY_NUMA_REGION = 7,
+} zbi_topology_entity_type_t;
+
+typedef struct {
+    // Should be one of zbi_topology_entity_type_t.
+    uint8_t entity_type;
+    uint16_t parent_index;
+    union {
+        zbi_topology_processor_t processor;
+        zbi_topology_cluster_t cluster;
+        zbi_topology_numa_region_t numa_region;
+    } entity;
+} zbi_topology_node_t;
+
+#endif
+
 // Memory configuration, one or more zbi_mem_range_t entries.
 // zbi_header_t.length is sizeof(zbi_mem_range_t) times the number of entries.
 #define ZBI_TYPE_MEM_CONFIG             (0x434D454D) // MEMC