diff --git a/zircon/kernel/arch/arm64/BUILD.gn b/zircon/kernel/arch/arm64/BUILD.gn
index c4f0d94bf0eb2871ee045a65b3739d2cc9ddb573..964909f36c0cd6c72838bc18fa86fa0762ba9182 100644
--- a/zircon/kernel/arch/arm64/BUILD.gn
+++ b/zircon/kernel/arch/arm64/BUILD.gn
@@ -61,10 +61,6 @@ if (current_toolchain == default_toolchain) {
   }
 
   source_set("arm64") {
-    defines = [
-      "SMP_CPU_MAX_CLUSTER_CPUS=$smp_max_cpus",
-      "SMP_CPU_MAX_CLUSTERS=$smp_max_clusters",
-    ]
     sources = [
       "arch.cpp",
       "asm.S",
diff --git a/zircon/kernel/arch/arm64/include/arch/arm64/mp.h b/zircon/kernel/arch/arm64/include/arch/arm64/mp.h
index 27a78e70894ee4191614d8dc69381f79ebf22da2..1125dab3cdf3ab7824cd71cf76064a32b77d5c85 100644
--- a/zircon/kernel/arch/arm64/include/arch/arm64/mp.h
+++ b/zircon/kernel/arch/arm64/include/arch/arm64/mp.h
@@ -107,8 +107,6 @@ static inline uint arch_cpu_num_to_cpu_id(uint cpu) {
     return arm64_cpu_cpu_ids[cpu];
 }
 
-cpu_num_t arch_mpid_to_cpu_num(uint cluster, uint cpu);
-
 #define READ_PERCPU_FIELD32(field) \
     arm64_read_percpu_u32(offsetof(struct arm64_percpu, field))
 
diff --git a/zircon/kernel/arch/arm64/mp.cpp b/zircon/kernel/arch/arm64/mp.cpp
index e3e4d862bc2b68af86dafce81db50b58fbc53b87..1a633c00d8b310d707935babe661e463abaa79a0 100644
--- a/zircon/kernel/arch/arm64/mp.cpp
+++ b/zircon/kernel/arch/arm64/mp.cpp
@@ -27,11 +27,6 @@ struct MpidCpuidPair {
     uint cpu_id;
 };
 
-// TODO(ZX-3068) Switch completely to list and remove map.
-bool use_cpu_map = true;
-
-// map of cluster/cpu to cpu_id
-uint arm64_cpu_map[SMP_CPU_MAX_CLUSTERS][SMP_CPU_MAX_CLUSTER_CPUS] = {{0}};
 MpidCpuidPair arm64_cpu_list[SMP_MAX_CPUS];
 size_t arm64_cpu_list_count = 0;
 
@@ -47,34 +42,6 @@ uint arm_num_cpus = 1;
 // per cpu structures, each cpu will point to theirs using the x18 register
 arm64_percpu arm64_percpu_array[SMP_MAX_CPUS];
 
-// initializes cpu_map and arm_num_cpus
-void arch_init_cpu_map(uint cluster_count, const uint* cluster_cpus) {
-    ASSERT(cluster_count <= SMP_CPU_MAX_CLUSTERS);
-
-    // assign cpu_ids sequentially
-    uint cpu_id = 0;
-    for (uint cluster = 0; cluster < cluster_count; cluster++) {
-        uint cpus = *cluster_cpus++;
-        ASSERT(cpus <= SMP_CPU_MAX_CLUSTER_CPUS);
-        for (uint cpu = 0; cpu < cpus; cpu++) {
-            // given cluster:cpu, translate to global cpu id
-            arm64_cpu_map[cluster][cpu] = cpu_id;
-
-            // given global gpu_id, translate to cluster and cpu number within cluster
-            arm64_cpu_cluster_ids[cpu_id] = cluster;
-            arm64_cpu_cpu_ids[cpu_id] = cpu;
-
-            // set the per cpu structure's cpu id
-            arm64_percpu_array[cpu_id].cpu_num = cpu_id;
-
-            cpu_id++;
-        }
-    }
-    arm_num_cpus = cpu_id;
-    use_cpu_map = true;
-    smp_mb();
-}
-
 void arch_register_mpid(uint cpu_id, uint64_t mpid) {
     // TODO(ZX-3068) transition off of these maps to the topology.
     arm64_cpu_cluster_ids[cpu_id] = (mpid & 0xFF00) >> MPIDR_AFF1_SHIFT; // "cluster" here is AFF1.
@@ -83,37 +50,25 @@ void arch_register_mpid(uint cpu_id, uint64_t mpid) {
     arm64_percpu_array[cpu_id].cpu_num = cpu_id;
 
     arm64_cpu_list[arm64_cpu_list_count++] = {.mpid = mpid, .cpu_id = cpu_id};
-
-    use_cpu_map = false;
 }
 
 // do the 'slow' lookup by mpidr to cpu number
 static uint arch_curr_cpu_num_slow() {
     uint64_t mpidr = __arm_rsr64("mpidr_el1");
-    if (use_cpu_map) {
-        uint cluster = (mpidr & MPIDR_AFF1_MASK) >> MPIDR_AFF1_SHIFT;
-        uint cpu = (mpidr & MPIDR_AFF0_MASK) >> MPIDR_AFF0_SHIFT;
-
-        return arm64_cpu_map[cluster][cpu];
-    } else {
-        mpidr &= kMpidAffMask;
-        for (size_t i = 0; i < arm64_cpu_list_count; ++i) {
-            if (arm64_cpu_list[i].mpid == mpidr) {
-                return arm64_cpu_list[i].cpu_id;
-            }
-        }
-
-        // The only time we shouldn't find a cpu is when the list isn't
-        // defined yet during early boot, in this case the only processor up is 0
-        // so returning 0 is correct.
-        DEBUG_ASSERT(arm64_cpu_list_count == 0);
+    mpidr &= kMpidAffMask;
 
-        return 0;
+    for (size_t i = 0; i < arm64_cpu_list_count; ++i) {
+        if (arm64_cpu_list[i].mpid == mpidr) {
+            return arm64_cpu_list[i].cpu_id;
+        }
     }
-}
 
-cpu_num_t arch_mpid_to_cpu_num(uint cluster, uint cpu) {
-    return arm64_cpu_map[cluster][cpu];
+    // The only time we shouldn't find a cpu is when the list isn't
+    // defined yet during early boot, in this case the only processor up is 0
+    // so returning 0 is correct.
+    DEBUG_ASSERT(arm64_cpu_list_count == 0);
+
+    return 0;
 }
 
 void arch_prepare_current_cpu_idle_state(bool idle) {
diff --git a/zircon/kernel/lib/topology/include/lib/system-topology.h b/zircon/kernel/lib/topology/include/lib/system-topology.h
index e30161c0b57b7b6a658b9ade6a22c69fc0f51b92..299cb1df79559b01a6d0e8fbff08f789c3e1741e 100644
--- a/zircon/kernel/lib/topology/include/lib/system-topology.h
+++ b/zircon/kernel/lib/topology/include/lib/system-topology.h
@@ -47,7 +47,7 @@ public:
     // 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);
+    zx_status_t Update(const zbi_topology_node_t* nodes, size_t count);
 
     // Provides iterable container of pointers to all processor nodes.
     IterableProcessors processors() const {
@@ -75,7 +75,7 @@ private:
     //   - 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;
+    bool Validate(const zbi_topology_node_t* nodes, int count) const;
 
     fbl::unique_ptr<Node[]> nodes_;
     fbl::Vector<Node*> processors_;
diff --git a/zircon/kernel/lib/topology/system-topology.cpp b/zircon/kernel/lib/topology/system-topology.cpp
index 17a9fbd6cecf3d6e4d57ea7e825f601626745a14..cf9dbf216bbcac1d9e75d437ae590910d28eea11 100644
--- a/zircon/kernel/lib/topology/system-topology.cpp
+++ b/zircon/kernel/lib/topology/system-topology.cpp
@@ -22,26 +22,30 @@ zx_status_t GrowVector(size_t new_size, fbl::Vector<T>* vector, fbl::AllocChecke
 
 } // 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;
-    }
-
+zx_status_t Graph::Update(const zbi_topology_node_t* flat_nodes, size_t count) {
     if (nodes_.get() != nullptr) {
         return ZX_ERR_ALREADY_EXISTS;
     }
 
+    if (flat_nodes == nullptr || count == 0 || !Validate(flat_nodes, static_cast<int>(count))) {
+        return ZX_ERR_INVALID_ARGS;
+    }
+
     fbl::AllocChecker checker;
-    nodes_.reset(new (&checker) Node[count]{{}});
+    fbl::unique_ptr<Node[]> nodes(new (&checker) Node[count]{{}});
     if (!checker.check()) {
         return ZX_ERR_NO_MEMORY;
     }
 
+    // Create local instances, if successful we will move them to the Graph's fields.
+    fbl::Vector<Node*> processors;
+    fbl::Vector<Node*> processors_by_logical_id;
+
     Node* node = nullptr;
-    zbi_topology_node_t* flat_node = nullptr;
+    const zbi_topology_node_t* flat_node = nullptr;
     for (size_t i = 0; i < count; ++i) {
         flat_node = &flat_nodes[i];
-        node = &nodes_[i];
+        node = &nodes[i];
 
         node->entity_type = flat_node->entity_type;
 
@@ -50,18 +54,16 @@ zx_status_t Graph::Update(zbi_topology_node_t* flat_nodes, size_t count) {
         case ZBI_TOPOLOGY_ENTITY_PROCESSOR:
             node->entity.processor = flat_node->entity.processor;
 
-            processors_.push_back(node, &checker);
+            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) {
                 const auto index = node->entity.processor.logical_ids[i];
-                GrowVector(index + 1, &processors_by_logical_id_, &checker);
-                processors_by_logical_id_[index] = node;
+                GrowVector(index + 1, &processors_by_logical_id, &checker);
+                processors_by_logical_id[index] = node;
                 if (!checker.check()) {
-                    nodes_.reset(nullptr);
                     return ZX_ERR_NO_MEMORY;
                 }
             }
@@ -82,19 +84,22 @@ zx_status_t Graph::Update(zbi_topology_node_t* flat_nodes, size_t count) {
             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 = &nodes[flat_node->parent_index];
             node->parent->children.push_back(node, &checker);
             if (!checker.check()) {
-                nodes_.reset(nullptr);
                 return ZX_ERR_NO_MEMORY;
             }
         }
     }
 
+    nodes_.swap(nodes);
+    processors_ = std::move(processors);
+    processors_by_logical_id_ = std::move(processors_by_logical_id);
+
     return ZX_OK;
 }
 
-bool Graph::Validate(zbi_topology_node_t* nodes, int count) const {
+bool Graph::Validate(const 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;
@@ -103,7 +108,7 @@ bool Graph::Validate(zbi_topology_node_t* nodes, int count) const {
     uint8_t current_type = ZBI_TOPOLOGY_ENTITY_UNDEFINED;
     int current_depth = 0;
 
-    zbi_topology_node_t* node;
+    const zbi_topology_node_t* node;
     for (int current_index = count - 1; current_index >= 0; current_index--) {
         node = &nodes[current_index];
 
diff --git a/zircon/kernel/params.gni b/zircon/kernel/params.gni
index 9e0acfed8f4090983d64bc7d2fa0816d6db8deec..55a50d2e8c66ef3d1c4c05225cd39a7ba5c6d1ad 100644
--- a/zircon/kernel/params.gni
+++ b/zircon/kernel/params.gni
@@ -7,10 +7,7 @@ declare_args() {
   smp_max_cpus = 32
 
   if (current_cpu == "arm64") {
-    # Maximum number of CPU clusters the kernel will support.
-    # The kernel will panic at boot on hardware with more clusters.
     smp_max_cpus = 16
-    smp_max_clusters = 2
   }
 
   # Virtual address where the kernel is mapped statically.  This is the
diff --git a/zircon/kernel/platform/generic-arm/BUILD.gn b/zircon/kernel/platform/generic-arm/BUILD.gn
index 0aa57e83867bae48f4ddaf2e39b04cf9e9b3e6bf..da0558aa574c3df648b80835163c2565d89cf1b3 100644
--- a/zircon/kernel/platform/generic-arm/BUILD.gn
+++ b/zircon/kernel/platform/generic-arm/BUILD.gn
@@ -8,7 +8,6 @@ source_set("generic-arm") {
   sources = [
     "platform.cpp",
   ]
-  defines = [ "SMP_CPU_MAX_CLUSTERS=$smp_max_clusters" ]
   deps = [
     "$zx/kernel/dev/hdcp/amlogic_s912",
     "$zx/kernel/dev/hw_rng",
diff --git a/zircon/kernel/platform/generic-arm/platform.cpp b/zircon/kernel/platform/generic-arm/platform.cpp
index 97e4293ad1e4a2c5b71d5cfcaeedcd1237e5bd4d..220ad31a3890fb4510a789643af16040639aa016 100644
--- a/zircon/kernel/platform/generic-arm/platform.cpp
+++ b/zircon/kernel/platform/generic-arm/platform.cpp
@@ -69,9 +69,6 @@ static zbi_header_t* zbi_root = nullptr;
 
 static zbi_nvram_t lastlog_nvram;
 
-static uint cpu_cluster_count = 0;
-static uint cpu_cluster_cpus[SMP_CPU_MAX_CLUSTERS] = {0};
-
 static bool halt_on_panic = false;
 static bool uart_disabled = false;
 
@@ -92,9 +89,6 @@ static size_t mexec_zbi_length = 0;
 
 static volatile int panic_started;
 
-// TODO(ZX-3068) This is temporary until we fully deprecate ZBI_CPU_CONFIG.
-static bool use_topology = false;
-
 static constexpr bool kProcessZbiEarly = true;
 
 static void halt_other_cpus(void) {
@@ -221,38 +215,6 @@ static void topology_cpu_init(void) {
     }
 }
 
-static void platform_cpu_init(void) {
-    for (uint cluster = 0; cluster < cpu_cluster_count; cluster++) {
-        for (uint cpu = 0; cpu < cpu_cluster_cpus[cluster]; cpu++) {
-            if (cluster != 0 || cpu != 0) {
-                const uint cpu_num = arch_mpid_to_cpu_num(cluster, cpu);
-                const uint64_t mpid = ARM64_MPID(cluster, cpu);
-
-                // create a stack for the cpu we're about to start
-                zx_status_t status = arm64_create_secondary_stack(cpu_num, mpid);
-
-                DEBUG_ASSERT(status == ZX_OK);
-
-                // start the cpu
-                status = platform_start_cpu(mpid);
-
-                if (status != ZX_OK) {
-                    // TODO(maniscalco): Is continuing really the right thing to do here?
-
-                    // start failed, free the stack
-                    zx_status_t status = arm64_free_secondary_stack(cpu_num);
-                    DEBUG_ASSERT(status == ZX_OK);
-                    continue;
-                }
-
-                // the cpu booted
-                //
-                // bootstrap thread is now responsible for freeing its stack
-            }
-        }
-    }
-}
-
 static inline bool is_zbi_container(void* addr) {
     DEBUG_ASSERT(addr);
 
@@ -335,16 +297,7 @@ static zbi_result_t process_zbi_item_early(zbi_header_t* item,
         save_mexec_zbi(item);
         break;
     }
-    case ZBI_TYPE_CPU_CONFIG: {
-        zbi_cpu_config_t* cpu_config = reinterpret_cast<zbi_cpu_config_t*>(payload);
-        cpu_cluster_count = cpu_config->cluster_count;
-        for (uint32_t i = 0; i < cpu_cluster_count; i++) {
-            cpu_cluster_cpus[i] = cpu_config->clusters[i].cpu_count;
-        }
-        arch_init_cpu_map(cpu_cluster_count, cpu_cluster_cpus);
-        save_mexec_zbi(item);
-        break;
-    }
+
     case ZBI_TYPE_NVRAM: {
         zbi_nvram_t* nvram = reinterpret_cast<zbi_nvram_t*>(payload);
         memcpy(&lastlog_nvram, nvram, sizeof(lastlog_nvram));
@@ -359,40 +312,125 @@ static zbi_result_t process_zbi_item_early(zbi_header_t* item,
     return ZBI_RESULT_OK;
 }
 
+static constexpr zbi_topology_node_t fallback_topology = {
+    .entity_type = ZBI_TOPOLOGY_ENTITY_PROCESSOR,
+    .parent_index = ZBI_TOPOLOGY_NO_PARENT,
+    .entity = {
+        .processor = {
+            .logical_ids = {0},
+            .logical_id_count = 1,
+            .flags = 0,
+            .architecture = ZBI_TOPOLOGY_ARCH_ARM,
+            .architecture_info = {
+                .arm = {
+                    .cluster_1_id = 0,
+                    .cluster_2_id = 0,
+                    .cluster_3_id = 0,
+                    .cpu_id = 0,
+                    .gic_id = 0,
+                }
+            }
+        }
+    }
+};
+
+static void init_topology(zbi_topology_node_t* nodes, size_t node_count) {
+    auto result = system_topology::GetMutableSystemTopology()
+            .Update(nodes, node_count);
+    if (result != ZX_OK) {
+        printf("Failed to initialize system topology! error: %d \n",
+               result);
+
+        // Try to fallback to a topology of just this processor.
+        result = system_topology::GetMutableSystemTopology()
+                .Update(&fallback_topology, 1);
+        ASSERT(result == ZX_OK);
+    }
+
+    arch_set_num_cpus(static_cast<uint>(
+        system_topology::GetSystemTopology().processor_count()));
+
+    // TODO(ZX-3068) Print the whole topology of the system.
+    if (LK_DEBUGLEVEL >= INFO) {
+        for (auto* proc :
+             system_topology::GetSystemTopology().processors()) {
+            auto& info = proc->entity.processor.architecture_info.arm;
+            dprintf(INFO, "System topology: CPU %u:%u:%u:%u\n",
+                    info.cluster_3_id,
+                    info.cluster_2_id,
+                    info.cluster_1_id,
+                    info.cpu_id);
+        }
+    }
+}
+
 // Called after heap is up, but before multithreading.
 static zbi_result_t process_zbi_item_late(zbi_header_t* item,
                                           void* payload, void*) {
     switch (item->type) {
+    case ZBI_TYPE_CPU_CONFIG: {
+        zbi_cpu_config_t* cpu_config = reinterpret_cast<zbi_cpu_config_t*>(payload);
+
+        // Convert old zbi_cpu_config into zbi_topology structure.
+
+        // Allocate some memory to work in.
+        size_t node_count = 0;
+        for (size_t cluster = 0; cluster < cpu_config->cluster_count; cluster++) {
+            // Each cluster will get a node.
+            node_count++;
+            node_count += cpu_config->clusters[cluster].cpu_count;
+        }
+
+        fbl::AllocChecker checker;
+        auto flat_topology = ktl::unique_ptr<zbi_topology_node_t[]> {
+            new (&checker) zbi_topology_node_t[node_count]};
+        if (!checker.check()) {
+            return ZBI_RESULT_ERROR;
+        }
+
+        // Initialize to 0.
+        memset(flat_topology.get(), 0, sizeof(zbi_topology_node_t) * node_count);
+
+        // Create topology structure.
+        size_t flat_index = 0;
+        uint16_t logical_id = 0;
+        for (size_t cluster = 0; cluster < cpu_config->cluster_count; cluster++) {
+            const auto cluster_index = flat_index;
+            auto& node = flat_topology.get()[flat_index++];
+            node.entity_type = ZBI_TOPOLOGY_ENTITY_CLUSTER;
+            node.parent_index = ZBI_TOPOLOGY_NO_PARENT;
+
+            // We don't have this data so it is a guess that little cores are
+            // first.
+            node.entity.cluster.performance_class = static_cast<uint8_t>(cluster);
+
+            for (size_t i = 0; i < cpu_config->clusters[cluster].cpu_count; i++) {
+                auto& node = flat_topology.get()[flat_index++];
+                node.entity_type = ZBI_TOPOLOGY_ENTITY_PROCESSOR;
+                node.parent_index = static_cast<uint16_t>(cluster_index);
+                node.entity.processor.logical_id_count = 1;
+                node.entity.processor.logical_ids[0] = logical_id++;
+                node.entity.processor.architecture = ZBI_TOPOLOGY_ARCH_ARM;
+                node.entity.processor.architecture_info.arm.cluster_1_id =
+                        static_cast<uint8_t>(cluster);
+                node.entity.processor.architecture_info.arm.cpu_id = static_cast<uint8_t>(i);
+            }
+        }
+        DEBUG_ASSERT(flat_index == node_count);
+
+        // Initialize topology subsystem.
+        init_topology(flat_topology.get(), node_count);
+        save_mexec_zbi(item);
+        break;
+    }
     case ZBI_TYPE_CPU_TOPOLOGY: {
         const int node_count = item->length / item->extra;
 
         zbi_topology_node_t* nodes =
             reinterpret_cast<zbi_topology_node_t*>(payload);
 
-        auto result = system_topology::GetMutableSystemTopology()
-                          .Update(nodes, node_count);
-        if (result != ZX_OK) {
-            printf("Failed to initialize system topology! error: %d \n",
-                   result);
-        } else {
-            use_topology = true;
-            arch_set_num_cpus(static_cast<uint>(
-                system_topology::GetSystemTopology().processor_count()));
-
-            // TODO(ZX-3068) Print the whole topology of the system.
-            if (LK_DEBUGLEVEL >= INFO) {
-                for (auto* proc :
-                     system_topology::GetSystemTopology().processors()) {
-                    auto& info = proc->entity.processor.architecture_info.arm;
-                    dprintf(INFO, "System topology: CPU %u:%u:%u:%u\n",
-                            info.cluster_3_id,
-                            info.cluster_2_id,
-                            info.cluster_1_id,
-                            info.cpu_id);
-                }
-            }
-        }
-
+        init_topology(nodes, node_count);
+        save_mexec_zbi(item);
         break;
     }
     }
@@ -500,11 +538,7 @@ LK_INIT_HOOK(platform_init_pre_thread, platform_init_pre_thread,
              LK_INIT_LEVEL_THREADING - 1)
 
 void platform_init(void) {
-    if (use_topology) {
-        topology_cpu_init();
-    } else {
-        platform_cpu_init();
-    }
+    topology_cpu_init();
 }
 
 // after the fact create a region to reserve the peripheral map(s)