diff --git a/hw/core/machine.c b/hw/core/machine.c
index b6258d95b1e8..076bd365197b 100644
--- a/hw/core/machine.c
+++ b/hw/core/machine.c
@@ -1638,6 +1638,11 @@ void machine_run_board_init(MachineState *machine, const char *mem_path, Error *
                                    "on", false);
     }
 
+    if (machine_class->smp_props.topo_tree_supported &&
+        !machine_create_topo_tree(machine, errp)) {
+        return;
+    }
+
     accel_init_interfaces(ACCEL_GET_CLASS(machine->accelerator));
     machine_class->init(machine);
     phase_advance(PHASE_MACHINE_INITIALIZED);
diff --git a/hw/cpu/cpu-slot.c b/hw/cpu/cpu-slot.c
index 4dbd5b7b7e00..1cc3b32ed675 100644
--- a/hw/cpu/cpu-slot.c
+++ b/hw/cpu/cpu-slot.c
@@ -12,8 +12,12 @@
 #include "qemu/osdep.h"
 
 #include "hw/boards.h"
+#include "hw/cpu/core.h"
 #include "hw/cpu/cpu-slot.h"
 #include "hw/cpu/cpu-topology.h"
+#include "hw/cpu/die.h"
+#include "hw/cpu/module.h"
+#include "hw/cpu/socket.h"
 #include "hw/qdev-core.h"
 #include "hw/qdev-properties.h"
 #include "hw/sysbus.h"
@@ -172,3 +176,152 @@ void machine_plug_cpu_slot(MachineState *ms)
         qbus_set_hotplug_handler(BUS(&slot->bus), OBJECT(ms));
     }
 }
+
+static int get_smp_info_by_level(const CpuTopology *smp_info,
+                                 CpuTopologyLevel child_level)
+{
+    switch (child_level) {
+    case CPU_TOPOLOGY_LEVEL_THREAD:
+        return smp_info->threads;
+    case CPU_TOPOLOGY_LEVEL_CORE:
+        return smp_info->cores;
+    case CPU_TOPOLOGY_LEVEL_MODULE:
+        return smp_info->modules;
+    case CPU_TOPOLOGY_LEVEL_DIE:
+        return smp_info->dies;
+    case CPU_TOPOLOGY_LEVEL_SOCKET:
+        return smp_info->sockets;
+    default:
+        /* TODO: Add support for other levels. */
+        g_assert_not_reached();
+    }
+
+    return 0;
+}
+
+static const char *get_topo_typename_by_level(CpuTopologyLevel level)
+{
+    switch (level) {
+    case CPU_TOPOLOGY_LEVEL_CORE:
+        return TYPE_CPU_CORE;
+    case CPU_TOPOLOGY_LEVEL_MODULE:
+        return TYPE_CPU_MODULE;
+    case CPU_TOPOLOGY_LEVEL_DIE:
+        return TYPE_CPU_DIE;
+    case CPU_TOPOLOGY_LEVEL_SOCKET:
+        return TYPE_CPU_SOCKET;
+    default:
+        /* TODO: Add support for other levels. */
+        g_assert_not_reached();
+    }
+
+    return NULL;
+}
+
+typedef struct SMPBuildCbData {
+    DECLARE_BITMAP(create_levels, CPU_TOPOLOGY_LEVEL__MAX);
+    const CpuTopology *smp_info;
+    CPUTopoStat *stat;
+    Error **errp;
+} SMPBuildCbData;
+
+static int create_smp_topo_children(DeviceState *dev, void *opaque)
+{
+    Object *parent = OBJECT(dev);
+    CpuTopologyLevel child_level;
+    SMPBuildCbData *cb = opaque;
+    CPUTopoState *topo = NULL;
+    BusState *qbus;
+    CPUBusState *cbus;
+    Error **errp = cb->errp;
+    int max_children;
+
+    if (object_dynamic_cast(parent, TYPE_CPU_TOPO)) {
+        topo = CPU_TOPO(parent);
+        CpuTopologyLevel parent_level;
+
+        parent_level = GET_CPU_TOPO_LEVEL(topo);
+        child_level = find_last_bit(cb->create_levels, parent_level);
+
+        if (child_level == parent_level) {
+            return TOPO_FOREACH_CONTINUE;
+        }
+
+        cbus = topo->bus;
+    } else if (object_dynamic_cast(parent, TYPE_CPU_SLOT)) {
+        child_level = find_last_bit(cb->create_levels, CPU_TOPOLOGY_LEVEL__MAX);
+        cbus = &CPU_SLOT(parent)->bus;
+    } else {
+        return TOPO_FOREACH_ERR;
+    }
+
+    qbus = BUS(cbus);
+    max_children = get_smp_info_by_level(cb->smp_info, child_level);
+    for (int i = 0; i < max_children; i++) {
+        DeviceState *child;
+
+        child = qdev_new(get_topo_typename_by_level(child_level));
+
+        /*
+         * Bus inserts child device at head (QTAILQ_INSERT_HEAD_RCU), This
+         * could result in the device IDs in the created topology having a
+         * zig-zag arrangement.
+         *
+         * TODO: Remove obstacles preventing the use of QTAILQ_INSERT_HEAD_RCU
+         * for bus to insert kid device.
+         */
+        child->id = g_strdup_printf("%s[%d]",
+            CpuTopologyLevel_str(child_level),
+            cb->stat->entries[child_level].total_instances);
+
+        if (!qdev_realize_and_unref(child, qbus, errp)) {
+            return TOPO_FOREACH_ERR;
+        }
+    }
+
+    return TOPO_FOREACH_CONTINUE;
+}
+
+bool machine_create_topo_tree(MachineState *ms, Error **errp)
+{
+    MachineClass *mc = MACHINE_GET_CLASS(ms);
+    CPUSlot *slot = ms->topo;
+    CpuTopologyLevel level;
+    SMPBuildCbData cb;
+
+    if (!slot) {
+        error_setg(errp, "Invalid machine: "
+                   "the cpu-slot of machine is not initialized.");
+        return false;
+    }
+
+    /*
+     * Don't support full topology tree.
+     * Just use slot to collect topology device.
+     */
+    if (!mc->smp_props.arch_id_topo_level) {
+        return true;
+    }
+
+    bitmap_copy(cb.create_levels, slot->supported_levels,
+                CPU_TOPOLOGY_LEVEL__MAX);
+    cb.smp_info = &ms->smp;
+    cb.stat = &slot->stat;
+    cb.errp = errp;
+
+    /*
+     * Topology objects at arch_id_topo_level and lower levels will be
+     * created by MachineClass.possible_cpu_arch_ids().
+     */
+    FOR_EACH_SET_BIT(level, slot->supported_levels,
+                     mc->smp_props.arch_id_topo_level + 1) {
+        clear_bit(level, cb.create_levels);
+    }
+
+    if (qdev_walk_children(DEVICE(slot), create_smp_topo_children,
+                           NULL, NULL, NULL, &cb) < 0) {
+        return false;
+    }
+
+    return true;
+}
diff --git a/include/hw/boards.h b/include/hw/boards.h
index eeb4e7e2ce9f..a49677466ef6 100644
--- a/include/hw/boards.h
+++ b/include/hw/boards.h
@@ -155,6 +155,7 @@ typedef struct {
  *                    supported by the machine
  * @topo_tree_supported - whether QOM topology tree is supported by the
  *                        machine
+ * @arch_id_topo_level - topology granularity for possible_cpus[]
  */
 typedef struct {
     bool prefer_sockets;
@@ -166,6 +167,7 @@ typedef struct {
     bool modules_supported;
     bool cache_supported[CACHE_LEVEL_AND_TYPE__MAX];
     bool topo_tree_supported;
+    CpuTopologyLevel arch_id_topo_level;
 } SMPCompatProps;
 
 /**
diff --git a/include/hw/cpu/cpu-slot.h b/include/hw/cpu/cpu-slot.h
index 24e122013bf7..1838e8c0c3f9 100644
--- a/include/hw/cpu/cpu-slot.h
+++ b/include/hw/cpu/cpu-slot.h
@@ -69,6 +69,11 @@ struct CPUSlot {
     DeviceListener listener;
 };
 
+#define TOPO_FOREACH_END             1
+#define TOPO_FOREACH_CONTINUE        0
+#define TOPO_FOREACH_ERR             -1
+
 void machine_plug_cpu_slot(MachineState *ms);
+bool machine_create_topo_tree(MachineState *ms, Error **errp);
 
 #endif /* CPU_SLOT_H */
diff --git a/include/qemu/bitops.h b/include/qemu/bitops.h
index 2c0a2fe7512d..d1c0e52219de 100644
--- a/include/qemu/bitops.h
+++ b/include/qemu/bitops.h
@@ -631,4 +631,9 @@ static inline uint64_t half_unshuffle64(uint64_t x)
     return x;
 }
 
+#define FOR_EACH_SET_BIT(bit, addr, size)               \
+    for ((bit) = find_first_bit((addr), (size));        \
+         (bit) < (size);                                \
+         (bit) = find_next_bit((addr), (size), (bit) + 1))
+
 #endif
