diff --git a/MAINTAINERS b/MAINTAINERS
index ffacd60f4075..230267597b5f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1884,12 +1884,14 @@ F: hw/core/machine-smp.c
 F: hw/core/null-machine.c
 F: hw/core/numa.c
 F: hw/cpu/cluster.c
+F: hw/cpu/cpu-topology.c
 F: qapi/machine.json
 F: qapi/machine-common.json
 F: qapi/machine-target.json
 F: include/hw/boards.h
 F: include/hw/core/cpu.h
 F: include/hw/cpu/cluster.h
+F: include/hw/cpu/cpu-topology.h
 F: include/sysemu/numa.h
 F: tests/functional/test_cpu_queries.py
 F: tests/functional/test_empty_cpu_model.py
diff --git a/hw/cpu/cpu-topology.c b/hw/cpu/cpu-topology.c
new file mode 100644
index 000000000000..e68c06132e7d
--- /dev/null
+++ b/hw/cpu/cpu-topology.c
@@ -0,0 +1,179 @@
+/*
+ * General CPU topology device abstraction
+ *
+ * Copyright (C) 2024 Intel Corporation.
+ *
+ * Author: Zhao Liu <zhao1.liu@intel.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or
+ * later.  See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+
+#include "hw/cpu/cpu-topology.h"
+#include "hw/qdev-core.h"
+#include "hw/qdev-properties.h"
+#include "hw/sysbus.h"
+#include "qapi/error.h"
+
+/* Roll up until topology root to check. */
+static bool cpu_parent_check_topology(DeviceState *parent,
+                                      DeviceState *dev,
+                                      Error **errp)
+{
+    BusClass *bc;
+
+    if (!parent || !parent->parent_bus ||
+        object_dynamic_cast(OBJECT(parent->parent_bus), TYPE_CPU_BUS)) {
+        return true;
+    }
+
+    bc = BUS_GET_CLASS(parent->parent_bus);
+    if (bc->check_address) {
+        return bc->check_address(parent->parent_bus, dev, errp);
+    }
+
+    return true;
+}
+
+static bool cpu_bus_check_address(BusState *bus, DeviceState *dev,
+                                  Error **errp)
+{
+    CPUBusState *cbus = CPU_BUS(bus);
+
+    if (cbus->check_topology) {
+        return cbus->check_topology(CPU_BUS(bus), CPU_TOPO(dev), errp);
+    }
+
+    return cpu_parent_check_topology(bus->parent, dev, errp);
+}
+
+static void cpu_bus_class_init(ObjectClass *oc, void *data)
+{
+    BusClass *bc = BUS_CLASS(oc);
+
+    bc->check_address = cpu_bus_check_address;
+}
+
+static const TypeInfo cpu_bus_type_info = {
+    .name = TYPE_CPU_BUS,
+    .parent = TYPE_BUS,
+    .class_init = cpu_bus_class_init,
+    .instance_size = sizeof(CPUBusState),
+};
+
+static bool cpu_topo_set_parent(CPUTopoState *topo, Error **errp)
+{
+    DeviceState *dev = DEVICE(topo);
+    BusState *bus = dev->parent_bus;
+    CPUTopoState *parent_topo = NULL;
+    Object *parent;
+
+    if (!bus || !bus->parent) {
+        return true;
+    }
+
+    if (topo->parent) {
+        error_setg(errp, "cpu topo: %s already have the parent?",
+                   object_get_typename(OBJECT(topo)));
+        return false;
+    }
+
+    parent = OBJECT(bus->parent);
+    if (object_dynamic_cast(parent, TYPE_CPU_TOPO)) {
+        parent_topo = CPU_TOPO(parent);
+
+        if (GET_CPU_TOPO_LEVEL(topo) >= GET_CPU_TOPO_LEVEL(parent_topo)) {
+            error_setg(errp, "cpu topo: current level (%s) should be "
+                       "lower than parent (%s) level",
+                       object_get_typename(OBJECT(topo)),
+                       object_get_typename(parent));
+            return false;
+        }
+    }
+
+    if (dev->id) {
+        /*
+         * Reparent topology device to make child<> match topological
+         * relationship.
+         */
+        if (!qdev_set_parent(dev, bus, parent, NULL, errp)) {
+            return false;
+        }
+    }
+
+    topo->parent = parent_topo;
+    return true;
+}
+
+static void cpu_topo_realize(DeviceState *dev, Error **errp)
+{
+    CPUTopoState *topo = CPU_TOPO(dev);
+    CPUTopoClass *tc = CPU_TOPO_GET_CLASS(topo);
+    HotplugHandler *hotplug_handler;
+
+    if (tc->level == CPU_TOPOLOGY_LEVEL_INVALID) {
+        error_setg(errp, "cpu topo: no level specified type: %s",
+                   object_get_typename(OBJECT(dev)));
+        return;
+    }
+
+    if (!cpu_topo_set_parent(topo, errp)) {
+        return;
+    }
+
+    topo->bus = CPU_BUS(qbus_new(TYPE_CPU_BUS, dev, dev->id));
+    hotplug_handler = qdev_get_bus_hotplug_handler(dev);
+    if (hotplug_handler) {
+        qbus_set_hotplug_handler(BUS(topo->bus), OBJECT(hotplug_handler));
+    }
+}
+
+static void cpu_topo_class_init(ObjectClass *oc, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(oc);
+    CPUTopoClass *tc = CPU_TOPO_CLASS(oc);
+
+    set_bit(DEVICE_CATEGORY_CPU, dc->categories);
+    dc->realize = cpu_topo_realize;
+
+    /*
+     * If people doesn't want a topology tree, it's necessary to
+     * derive a child class and override this as NULL.
+     */
+    dc->bus_type = TYPE_CPU_BUS;
+
+    /*
+     * The general topo device is not hotpluggable by default.
+     * If any topo device needs hotplug support, this flag must be
+     * overridden.
+     */
+    dc->hotpluggable = false;
+
+    tc->level = CPU_TOPOLOGY_LEVEL_INVALID;
+}
+
+static const TypeInfo cpu_topo_type_info = {
+    .name = TYPE_CPU_TOPO,
+    .parent = TYPE_DEVICE,
+    .abstract = true,
+    .class_size = sizeof(CPUTopoClass),
+    .class_init = cpu_topo_class_init,
+    .instance_size = sizeof(CPUTopoState),
+};
+
+static void cpu_topo_register_types(void)
+{
+    type_register_static(&cpu_bus_type_info);
+    type_register_static(&cpu_topo_type_info);
+}
+
+type_init(cpu_topo_register_types)
+
+int cpu_topo_get_instances_num(CPUTopoState *topo)
+{
+    BusState *bus = DEVICE(topo)->parent_bus;
+
+    return bus ? bus->num_children : 1;
+}
diff --git a/hw/cpu/meson.build b/hw/cpu/meson.build
index 9d36bf8ae2c1..6c6546646608 100644
--- a/hw/cpu/meson.build
+++ b/hw/cpu/meson.build
@@ -1,3 +1,5 @@
+common_ss.add(files('cpu-topology.c'))
+
 system_ss.add(files('core.c'))
 system_ss.add(when: 'CONFIG_CPU_CLUSTER', if_true: files('cluster.c'))
 
diff --git a/include/hw/cpu/cpu-topology.h b/include/hw/cpu/cpu-topology.h
new file mode 100644
index 000000000000..7a447ad16ee7
--- /dev/null
+++ b/include/hw/cpu/cpu-topology.h
@@ -0,0 +1,68 @@
+/*
+ * General CPU topology device abstraction
+ *
+ * Copyright (C) 2024 Intel Corporation.
+ *
+ * Author: Zhao Liu <zhao1.liu@intel.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or
+ * later.  See the COPYING file in the top-level directory.
+ */
+
+#ifndef CPU_TOPO_H
+#define CPU_TOPO_H
+
+#include "hw/qdev-core.h"
+#include "qapi/qapi-types-machine-common.h"
+#include "qom/object.h"
+
+#define TYPE_CPU_BUS "cpu-bus"
+OBJECT_DECLARE_SIMPLE_TYPE(CPUBusState, CPU_BUS)
+
+/**
+ * CPUBusState:
+ * @check_topology: Method to check if @topo is supported by @cbus.
+ */
+struct CPUBusState {
+    /*< private >*/
+    BusState parent_obj;
+
+    /*< public >*/
+    bool (*check_topology)(CPUBusState *cbus, CPUTopoState *topo,
+                           Error **errp);
+};
+
+#define TYPE_CPU_TOPO "cpu-topo"
+OBJECT_DECLARE_TYPE(CPUTopoState, CPUTopoClass, CPU_TOPO)
+
+/**
+ * CPUTopoClass:
+ * @level: Topology level for this CPUTopoClass.
+ */
+struct CPUTopoClass {
+    /*< private >*/
+    DeviceClass parent_class;
+
+    /*< public >*/
+    CpuTopologyLevel level;
+};
+
+/**
+ * CPUTopoState:
+ * @parent: Topology parent of this topology device.
+ * @bus: The CPU bus to add the children device.
+ */
+struct CPUTopoState {
+    /*< private >*/
+    DeviceState parent_obj;
+
+    /*< public >*/
+    struct CPUTopoState *parent;
+    CPUBusState *bus;
+};
+
+#define GET_CPU_TOPO_LEVEL(topo)    (CPU_TOPO_GET_CLASS(topo)->level)
+
+int cpu_topo_get_instances_num(CPUTopoState *topo);
+
+#endif /* CPU_TOPO_H */
diff --git a/include/qemu/typedefs.h b/include/qemu/typedefs.h
index aef41c4e67ce..d62d8687403f 100644
--- a/include/qemu/typedefs.h
+++ b/include/qemu/typedefs.h
@@ -39,8 +39,10 @@ typedef struct Chardev Chardev;
 typedef struct Clock Clock;
 typedef struct ConfidentialGuestSupport ConfidentialGuestSupport;
 typedef struct CPUArchState CPUArchState;
+typedef struct CPUBusState CPUBusState;
 typedef struct CPUPluginState CPUPluginState;
 typedef struct CPUState CPUState;
+typedef struct CPUTopoState CPUTopoState;
 typedef struct DeviceState DeviceState;
 typedef struct DirtyBitmapSnapshot DirtyBitmapSnapshot;
 typedef struct DisasContextBase DisasContextBase;
diff --git a/stubs/hotplug-stubs.c b/stubs/hotplug-stubs.c
index 7aadaa29bd57..791fae079d6d 100644
--- a/stubs/hotplug-stubs.c
+++ b/stubs/hotplug-stubs.c
@@ -19,6 +19,11 @@ HotplugHandler *qdev_get_hotplug_handler(DeviceState *dev)
     return NULL;
 }
 
+HotplugHandler *qdev_get_bus_hotplug_handler(DeviceState *dev)
+{
+    return NULL;
+}
+
 void hotplug_handler_pre_plug(HotplugHandler *plug_handler,
                               DeviceState *plugged_dev,
                               Error **errp)
