Move CPU hotplug-related code from spapr.c into a new dedicated file
spapr_cpu_hotplug.c. A corresponding header file exports the public
interfaces needed by other sPAPR subsystems.

Also updating MAINTAINERS entry for PPC KVM CPUs to include these newly
added files along with spapr_cpu_core.c for better maintainability.

Signed-off-by: Vishal Chourasia <[email protected]>
---
 MAINTAINERS                        |   3 +
 hw/ppc/meson.build                 |   1 +
 hw/ppc/spapr.c                     | 903 +-------------------------
 hw/ppc/spapr_cpu_hotplug.c         | 984 +++++++++++++++++++++++++++++
 include/hw/ppc/spapr_cpu_hotplug.h |  74 +++
 5 files changed, 1063 insertions(+), 902 deletions(-)
 create mode 100644 hw/ppc/spapr_cpu_hotplug.c
 create mode 100644 include/hw/ppc/spapr_cpu_hotplug.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 97dcc78ded..cb19e868b4 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -490,6 +490,9 @@ M: Nicholas Piggin <[email protected]>
 R: Harsh Prateek Bora <[email protected]>
 S: Odd Fixes
 F: target/ppc/kvm.c
+F: hw/ppc/spapr_cpu_core.c
+F: hw/ppc/spapr_cpu_hotplug.c
+F: include/hw/ppc/spapr_cpu_hotplug.h
 
 S390 KVM CPUs
 M: Halil Pasic <[email protected]>
diff --git a/hw/ppc/meson.build b/hw/ppc/meson.build
index 37aa535db2..98435bbca9 100644
--- a/hw/ppc/meson.build
+++ b/hw/ppc/meson.build
@@ -20,6 +20,7 @@ ppc_ss.add(when: 'CONFIG_PSERIES', if_true: files(
   'spapr_rtc.c',
   'spapr_drc.c',
   'spapr_cpu_core.c',
+  'spapr_cpu_hotplug.c',
   'spapr_ovec.c',
   'spapr_irq.c',
   'spapr_tpm_proxy.c',
diff --git a/hw/ppc/spapr.c b/hw/ppc/spapr.c
index b79828b4e9..374bf1cfbf 100644
--- a/hw/ppc/spapr.c
+++ b/hw/ppc/spapr.c
@@ -88,6 +88,7 @@
 #include "hw/intc/intc.h"
 
 #include "hw/ppc/spapr_cpu_core.h"
+#include "hw/ppc/spapr_cpu_hotplug.h"
 #include "hw/mem/memory-device.h"
 #include "hw/ppc/spapr_tpm_proxy.h"
 #include "hw/ppc/spapr_nvdimm.h"
@@ -116,220 +117,6 @@
 
 #define PHANDLE_INTC            0x00001111
 
-/* These two functions implement the VCPU id numbering: one to compute them
- * all and one to identify thread 0 of a VCORE. Any change to the first one
- * is likely to have an impact on the second one, so let's keep them close.
- */
-static int spapr_vcpu_id(SpaprMachineState *spapr, int cpu_index)
-{
-    MachineState *ms = MACHINE(spapr);
-    unsigned int smp_threads = ms->smp.threads;
-
-    assert(spapr->vsmt);
-    return
-        (cpu_index / smp_threads) * spapr->vsmt + cpu_index % smp_threads;
-}
-static bool spapr_is_thread0_in_vcore(SpaprMachineState *spapr,
-                                      PowerPCCPU *cpu)
-{
-    assert(spapr->vsmt);
-    return spapr_get_vcpu_id(cpu) % spapr->vsmt == 0;
-}
-
-int spapr_max_server_number(SpaprMachineState *spapr)
-{
-    MachineState *ms = MACHINE(spapr);
-
-    assert(spapr->vsmt);
-    return DIV_ROUND_UP(ms->smp.max_cpus * spapr->vsmt, ms->smp.threads);
-}
-
-static int spapr_fixup_cpu_smt_dt(void *fdt, int offset, PowerPCCPU *cpu,
-                                  int smt_threads)
-{
-    int i, ret = 0;
-    g_autofree uint32_t *servers_prop = g_new(uint32_t, smt_threads);
-    g_autofree uint32_t *gservers_prop = g_new(uint32_t, smt_threads * 2);
-    int index = spapr_get_vcpu_id(cpu);
-
-    if (cpu->compat_pvr) {
-        ret = fdt_setprop_cell(fdt, offset, "cpu-version", cpu->compat_pvr);
-        if (ret < 0) {
-            return ret;
-        }
-    }
-
-    /* Build interrupt servers and gservers properties */
-    for (i = 0; i < smt_threads; i++) {
-        servers_prop[i] = cpu_to_be32(index + i);
-        /* Hack, direct the group queues back to cpu 0 */
-        gservers_prop[i*2] = cpu_to_be32(index + i);
-        gservers_prop[i*2 + 1] = 0;
-    }
-    ret = fdt_setprop(fdt, offset, "ibm,ppc-interrupt-server#s",
-                      servers_prop, sizeof(*servers_prop) * smt_threads);
-    if (ret < 0) {
-        return ret;
-    }
-    ret = fdt_setprop(fdt, offset, "ibm,ppc-interrupt-gserver#s",
-                      gservers_prop, sizeof(*gservers_prop) * smt_threads * 2);
-
-    return ret;
-}
-
-static void spapr_dt_pa_features(SpaprMachineState *spapr,
-                                 PowerPCCPU *cpu,
-                                 void *fdt, int offset)
-{
-    /*
-     * SSO (SAO) ordering is supported on KVM and thread=single hosts,
-     * but not MTTCG, so disable it. To advertise it, a cap would have
-     * to be added, or support implemented for MTTCG.
-     *
-     * Copy/paste is not supported by TCG, so it is not advertised. KVM
-     * can execute them but it has no accelerator drivers which are usable,
-     * so there isn't much need for it anyway.
-     */
-
-    /* These should be kept in sync with pnv */
-    uint8_t pa_features_206[] = { 6, 0,
-        0xf6, 0x1f, 0xc7, 0x00, 0x00, 0xc0 };
-    uint8_t pa_features_207[] = { 24, 0,
-        0xf6, 0x1f, 0xc7, 0xc0, 0x00, 0xf0,
-        0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
-        0x00, 0x00, 0x00, 0x00, 0x80, 0x00,
-        0x80, 0x00, 0x80, 0x00, 0x00, 0x00 };
-    uint8_t pa_features_300[] = { 66, 0,
-        /* 0: MMU|FPU|SLB|RUN|DABR|NX, 1: fri[nzpm]|DABRX|SPRG3|SLB0|PP110 */
-        /* 2: VPM|DS205|PPR|DS202|DS206, 3: LSD|URG, 5: LE|CFAR|EB|LSQ */
-        0xf6, 0x1f, 0xc7, 0xc0, 0x00, 0xf0, /* 0 - 5 */
-        /* 6: DS207 */
-        0x80, 0x00, 0x00, 0x00, 0x00, 0x00, /* 6 - 11 */
-        /* 16: Vector */
-        0x00, 0x00, 0x00, 0x00, 0x80, 0x00, /* 12 - 17 */
-        /* 18: Vec. Scalar, 20: Vec. XOR */
-        0x80, 0x00, 0x80, 0x00, 0x00, 0x00, /* 18 - 23 */
-        /* 24: Ext. Dec, 26: 64 bit ftrs, 28: PM ftrs */
-        0x80, 0x00, 0x80, 0x00, 0x80, 0x00, /* 24 - 29 */
-        /* 32: LE atomic, 34: EBB + ext EBB */
-        0x00, 0x00, 0x80, 0x00, 0xC0, 0x00, /* 30 - 35 */
-        /* 40: Radix MMU */
-        0x00, 0x00, 0x00, 0x00, 0x80, 0x00, /* 36 - 41 */
-        /* 42: PM, 44: PC RA, 46: SC vec'd */
-        0x80, 0x00, 0x80, 0x00, 0x80, 0x00, /* 42 - 47 */
-        /* 48: SIMD, 50: QP BFP, 52: String */
-        0x80, 0x00, 0x80, 0x00, 0x80, 0x00, /* 48 - 53 */
-        /* 54: DecFP, 56: DecI, 58: SHA */
-        0x80, 0x00, 0x80, 0x00, 0x80, 0x00, /* 54 - 59 */
-        /* 60: NM atomic, 62: RNG */
-        0x80, 0x00, 0x80, 0x00, 0x00, 0x00, /* 60 - 65 */
-    };
-    /* 3.1 removes SAO, HTM support */
-    uint8_t pa_features_31[] = { 74, 0,
-        /* 0: MMU|FPU|SLB|RUN|DABR|NX, 1: fri[nzpm]|DABRX|SPRG3|SLB0|PP110 */
-        /* 2: VPM|DS205|PPR|DS202|DS206, 3: LSD|URG, 5: LE|CFAR|EB|LSQ */
-        0xf6, 0x1f, 0xc7, 0xc0, 0x00, 0xf0, /* 0 - 5 */
-        /* 6: DS207 */
-        0x80, 0x00, 0x00, 0x00, 0x00, 0x00, /* 6 - 11 */
-        /* 16: Vector */
-        0x00, 0x00, 0x00, 0x00, 0x80, 0x00, /* 12 - 17 */
-        /* 18: Vec. Scalar, 20: Vec. XOR */
-        0x80, 0x00, 0x80, 0x00, 0x00, 0x00, /* 18 - 23 */
-        /* 24: Ext. Dec, 26: 64 bit ftrs, 28: PM ftrs */
-        0x80, 0x00, 0x80, 0x00, 0x80, 0x00, /* 24 - 29 */
-        /* 32: LE atomic, 34: EBB + ext EBB */
-        0x00, 0x00, 0x80, 0x00, 0xC0, 0x00, /* 30 - 35 */
-        /* 40: Radix MMU */
-        0x00, 0x00, 0x00, 0x00, 0x80, 0x00, /* 36 - 41 */
-        /* 42: PM, 44: PC RA, 46: SC vec'd */
-        0x80, 0x00, 0x80, 0x00, 0x80, 0x00, /* 42 - 47 */
-        /* 48: SIMD, 50: QP BFP, 52: String */
-        0x80, 0x00, 0x80, 0x00, 0x80, 0x00, /* 48 - 53 */
-        /* 54: DecFP, 56: DecI, 58: SHA */
-        0x80, 0x00, 0x80, 0x00, 0x80, 0x00, /* 54 - 59 */
-        /* 60: NM atomic, 62: RNG, 64: DAWR1 (ISA 3.1) */
-        0x80, 0x00, 0x80, 0x00, 0x00, 0x00, /* 60 - 65 */
-        /* 68: DEXCR[SBHE|IBRTPDUS|SRAPD|NPHIE|PHIE] */
-        0x00, 0x00, 0xce, 0x00, 0x00, 0x00, /* 66 - 71 */
-        /* 72: [P]HASHST/[P]HASHCHK */
-        0x80, 0x00,                         /* 72 - 73 */
-    };
-    uint8_t *pa_features = NULL;
-    size_t pa_size;
-
-    if (ppc_check_compat(cpu, CPU_POWERPC_LOGICAL_2_06, 0, cpu->compat_pvr)) {
-        pa_features = pa_features_206;
-        pa_size = sizeof(pa_features_206);
-    }
-    if (ppc_check_compat(cpu, CPU_POWERPC_LOGICAL_2_07, 0, cpu->compat_pvr)) {
-        pa_features = pa_features_207;
-        pa_size = sizeof(pa_features_207);
-    }
-    if (ppc_check_compat(cpu, CPU_POWERPC_LOGICAL_3_00, 0, cpu->compat_pvr)) {
-        pa_features = pa_features_300;
-        pa_size = sizeof(pa_features_300);
-    }
-    if (ppc_check_compat(cpu, CPU_POWERPC_LOGICAL_3_10, 0, cpu->compat_pvr)) {
-        pa_features = pa_features_31;
-        pa_size = sizeof(pa_features_31);
-    }
-    if (!pa_features) {
-        return;
-    }
-
-    if (ppc_hash64_has(cpu, PPC_HASH64_CI_LARGEPAGE)) {
-        /*
-         * Note: we keep CI large pages off by default because a 64K capable
-         * guest provisioned with large pages might otherwise try to map a qemu
-         * framebuffer (or other kind of memory mapped PCI BAR) using 64K pages
-         * even if that qemu runs on a 4k host.
-         * We dd this bit back here if we are confident this is not an issue
-         */
-        pa_features[3] |= 0x20;
-    }
-    if ((spapr_get_cap(spapr, SPAPR_CAP_HTM) != 0) && pa_size > 24) {
-        pa_features[24] |= 0x80;    /* Transactional memory support */
-    }
-    if (spapr->cas_pre_isa3_guest && pa_size > 40) {
-        /* Workaround for broken kernels that attempt (guest) radix
-         * mode when they can't handle it, if they see the radix bit set
-         * in pa-features. So hide it from them. */
-        pa_features[40 + 2] &= ~0x80; /* Radix MMU */
-    }
-    if (spapr_get_cap(spapr, SPAPR_CAP_DAWR1)) {
-        g_assert(pa_size > 66);
-        pa_features[66] |= 0x80;
-    }
-
-    _FDT((fdt_setprop(fdt, offset, "ibm,pa-features", pa_features, pa_size)));
-}
-
-static void spapr_dt_pi_features(SpaprMachineState *spapr,
-                                 PowerPCCPU *cpu,
-                                 void *fdt, int offset)
-{
-    uint8_t pi_features[] = { 1, 0,
-        0x00 };
-
-    if (kvm_enabled() && ppc_check_compat(cpu, CPU_POWERPC_LOGICAL_3_00,
-                                          0, cpu->compat_pvr)) {
-        /*
-         * POWER9 and later CPUs with KVM run in LPAR-per-thread mode where
-         * all threads are essentially independent CPUs, and msgsndp does not
-         * work (because it is physically-addressed) and therefore is
-         * emulated by KVM, so disable it here to ensure XIVE will be used.
-         * This is both KVM and CPU implementation-specific behaviour so a KVM
-         * cap would be cleanest, but for now this works. If KVM ever permits
-         * native msgsndp execution by guests, a cap could be added at that
-         * time.
-         */
-        pi_features[2] |= 0x08; /* 4: No msgsndp */
-    }
-
-    _FDT((fdt_setprop(fdt, offset, "ibm,pi-features", pi_features,
-                      sizeof(pi_features))));
-}
-
 static hwaddr spapr_node0_size(MachineState *machine)
 {
     if (machine->numa_state->num_nodes) {
@@ -685,200 +472,6 @@ static int spapr_dt_memory(SpaprMachineState *spapr, void 
*fdt)
     return 0;
 }
 
-static void spapr_dt_cpu(CPUState *cs, void *fdt, int offset,
-                         SpaprMachineState *spapr)
-{
-    MachineState *ms = MACHINE(spapr);
-    PowerPCCPU *cpu = POWERPC_CPU(cs);
-    CPUPPCState *env = &cpu->env;
-    PowerPCCPUClass *pcc = POWERPC_CPU_GET_CLASS(cs);
-    int index = spapr_get_vcpu_id(cpu);
-    uint32_t segs[] = {cpu_to_be32(28), cpu_to_be32(40),
-                       0xffffffff, 0xffffffff};
-    uint32_t tbfreq = kvm_enabled() ? kvmppc_get_tbfreq()
-        : SPAPR_TIMEBASE_FREQ;
-    uint32_t cpufreq = kvm_enabled() ? kvmppc_get_clockfreq() : 1000000000;
-    uint32_t page_sizes_prop[64];
-    size_t page_sizes_prop_size;
-    unsigned int smp_threads = ms->smp.threads;
-    uint32_t vcpus_per_socket = smp_threads * ms->smp.cores;
-    uint32_t pft_size_prop[] = {0, cpu_to_be32(spapr->htab_shift)};
-    int compat_smt = MIN(smp_threads, ppc_compat_max_vthreads(cpu));
-    SpaprDrc *drc;
-    int drc_index;
-    uint32_t radix_AP_encodings[PPC_PAGE_SIZES_MAX_SZ];
-    int i;
-
-    drc = spapr_drc_by_id(TYPE_SPAPR_DRC_CPU, env->core_index);
-    if (drc) {
-        drc_index = spapr_drc_index(drc);
-        _FDT((fdt_setprop_cell(fdt, offset, "ibm,my-drc-index", drc_index)));
-    }
-
-    _FDT((fdt_setprop_cell(fdt, offset, "reg", index)));
-    _FDT((fdt_setprop_string(fdt, offset, "device_type", "cpu")));
-
-    _FDT((fdt_setprop_cell(fdt, offset, "cpu-version", env->spr[SPR_PVR])));
-    _FDT((fdt_setprop_cell(fdt, offset, "d-cache-block-size",
-                           env->dcache_line_size)));
-    _FDT((fdt_setprop_cell(fdt, offset, "d-cache-line-size",
-                           env->dcache_line_size)));
-    _FDT((fdt_setprop_cell(fdt, offset, "i-cache-block-size",
-                           env->icache_line_size)));
-    _FDT((fdt_setprop_cell(fdt, offset, "i-cache-line-size",
-                           env->icache_line_size)));
-
-    if (pcc->l1_dcache_size) {
-        _FDT((fdt_setprop_cell(fdt, offset, "d-cache-size",
-                               pcc->l1_dcache_size)));
-    } else {
-        warn_report("Unknown L1 dcache size for cpu");
-    }
-    if (pcc->l1_icache_size) {
-        _FDT((fdt_setprop_cell(fdt, offset, "i-cache-size",
-                               pcc->l1_icache_size)));
-    } else {
-        warn_report("Unknown L1 icache size for cpu");
-    }
-
-    _FDT((fdt_setprop_cell(fdt, offset, "timebase-frequency", tbfreq)));
-    _FDT((fdt_setprop_cell(fdt, offset, "clock-frequency", cpufreq)));
-    _FDT((fdt_setprop_cell(fdt, offset, "slb-size", 
cpu->hash64_opts->slb_size)));
-    _FDT((fdt_setprop_cell(fdt, offset, "ibm,slb-size", 
cpu->hash64_opts->slb_size)));
-    _FDT((fdt_setprop_string(fdt, offset, "status", "okay")));
-    _FDT((fdt_setprop(fdt, offset, "64-bit", NULL, 0)));
-
-    if (ppc_has_spr(cpu, SPR_PURR)) {
-        _FDT((fdt_setprop_cell(fdt, offset, "ibm,purr", 1)));
-    }
-    if (ppc_has_spr(cpu, SPR_PURR)) {
-        _FDT((fdt_setprop_cell(fdt, offset, "ibm,spurr", 1)));
-    }
-
-    if (ppc_hash64_has(cpu, PPC_HASH64_1TSEG)) {
-        _FDT((fdt_setprop(fdt, offset, "ibm,processor-segment-sizes",
-                          segs, sizeof(segs))));
-    }
-
-    /* Advertise VSX (vector extensions) if available
-     *   1               == VMX / Altivec available
-     *   2               == VSX available
-     *
-     * Only CPUs for which we create core types in spapr_cpu_core.c
-     * are possible, and all of those have VMX */
-    if (env->insns_flags & PPC_ALTIVEC) {
-        if (spapr_get_cap(spapr, SPAPR_CAP_VSX) != 0) {
-            _FDT((fdt_setprop_cell(fdt, offset, "ibm,vmx", 2)));
-        } else {
-            _FDT((fdt_setprop_cell(fdt, offset, "ibm,vmx", 1)));
-        }
-    }
-
-    /* Advertise DFP (Decimal Floating Point) if available
-     *   0 / no property == no DFP
-     *   1               == DFP available */
-    if (spapr_get_cap(spapr, SPAPR_CAP_DFP) != 0) {
-        _FDT((fdt_setprop_cell(fdt, offset, "ibm,dfp", 1)));
-    }
-
-    page_sizes_prop_size = ppc_create_page_sizes_prop(cpu, page_sizes_prop,
-                                                      sizeof(page_sizes_prop));
-    if (page_sizes_prop_size) {
-        _FDT((fdt_setprop(fdt, offset, "ibm,segment-page-sizes",
-                          page_sizes_prop, page_sizes_prop_size)));
-    }
-
-    spapr_dt_pa_features(spapr, cpu, fdt, offset);
-
-    spapr_dt_pi_features(spapr, cpu, fdt, offset);
-
-    _FDT((fdt_setprop_cell(fdt, offset, "ibm,chip-id",
-                           cs->cpu_index / vcpus_per_socket)));
-
-    _FDT((fdt_setprop(fdt, offset, "ibm,pft-size",
-                      pft_size_prop, sizeof(pft_size_prop))));
-
-    if (ms->numa_state->num_nodes > 1) {
-        _FDT(spapr_numa_fixup_cpu_dt(spapr, fdt, offset, cpu));
-    }
-
-    _FDT(spapr_fixup_cpu_smt_dt(fdt, offset, cpu, compat_smt));
-
-    if (pcc->radix_page_info) {
-        for (i = 0; i < pcc->radix_page_info->count; i++) {
-            radix_AP_encodings[i] =
-                cpu_to_be32(pcc->radix_page_info->entries[i]);
-        }
-        _FDT((fdt_setprop(fdt, offset, "ibm,processor-radix-AP-encodings",
-                          radix_AP_encodings,
-                          pcc->radix_page_info->count *
-                          sizeof(radix_AP_encodings[0]))));
-    }
-
-    /*
-     * We set this property to let the guest know that it can use the large
-     * decrementer and its width in bits.
-     */
-    if (spapr_get_cap(spapr, SPAPR_CAP_LARGE_DECREMENTER) != SPAPR_CAP_OFF)
-        _FDT((fdt_setprop_u32(fdt, offset, "ibm,dec-bits",
-                              pcc->lrg_decr_bits)));
-}
-
-static void spapr_dt_one_cpu(void *fdt, SpaprMachineState *spapr, CPUState *cs,
-                             int cpus_offset)
-{
-    PowerPCCPU *cpu = POWERPC_CPU(cs);
-    int index = spapr_get_vcpu_id(cpu);
-    DeviceClass *dc = DEVICE_GET_CLASS(cs);
-    g_autofree char *nodename = NULL;
-    int offset;
-
-    if (!spapr_is_thread0_in_vcore(spapr, cpu)) {
-        return;
-    }
-
-    nodename = g_strdup_printf("%s@%x", dc->fw_name, index);
-    offset = fdt_add_subnode(fdt, cpus_offset, nodename);
-    _FDT(offset);
-    spapr_dt_cpu(cs, fdt, offset, spapr);
-}
-
-
-static void spapr_dt_cpus(void *fdt, SpaprMachineState *spapr)
-{
-    CPUState **rev;
-    CPUState *cs;
-    int n_cpus;
-    int cpus_offset;
-    int i;
-
-    cpus_offset = fdt_add_subnode(fdt, 0, "cpus");
-    _FDT(cpus_offset);
-    _FDT((fdt_setprop_cell(fdt, cpus_offset, "#address-cells", 0x1)));
-    _FDT((fdt_setprop_cell(fdt, cpus_offset, "#size-cells", 0x0)));
-
-    /*
-     * We walk the CPUs in reverse order to ensure that CPU DT nodes
-     * created by fdt_add_subnode() end up in the right order in FDT
-     * for the guest kernel the enumerate the CPUs correctly.
-     *
-     * The CPU list cannot be traversed in reverse order, so we need
-     * to do extra work.
-     */
-    n_cpus = 0;
-    rev = NULL;
-    CPU_FOREACH(cs) {
-        rev = g_renew(CPUState *, rev, n_cpus + 1);
-        rev[n_cpus++] = cs;
-    }
-
-    for (i = n_cpus - 1; i >= 0; i--) {
-        spapr_dt_one_cpu(fdt, spapr, rev[i], cpus_offset);
-    }
-
-    g_free(rev);
-}
-
 static int spapr_dt_rng(void *fdt)
 {
     int node;
@@ -2650,150 +2243,6 @@ static void spapr_validate_node_memory(MachineState 
*machine, Error **errp)
 }
 
 /* find cpu slot in machine->possible_cpus by core_id */
-static CPUArchId *spapr_find_cpu_slot(MachineState *ms, uint32_t id, int *idx)
-{
-    int index = id / ms->smp.threads;
-
-    if (index >= ms->possible_cpus->len) {
-        return NULL;
-    }
-    if (idx) {
-        *idx = index;
-    }
-    return &ms->possible_cpus->cpus[index];
-}
-
-static void spapr_set_vsmt_mode(SpaprMachineState *spapr, Error **errp)
-{
-    MachineState *ms = MACHINE(spapr);
-    Error *local_err = NULL;
-    bool vsmt_user = !!spapr->vsmt;
-    int kvm_smt = kvmppc_smt_threads();
-    int ret;
-    unsigned int smp_threads = ms->smp.threads;
-
-    if (tcg_enabled()) {
-        if (smp_threads > 1 &&
-            !ppc_type_check_compat(ms->cpu_type, CPU_POWERPC_LOGICAL_2_07, 0,
-                                   spapr->max_compat_pvr)) {
-            error_setg(errp, "TCG only supports SMT on POWER8 or newer CPUs");
-            return;
-        }
-
-        if (smp_threads > 8) {
-            error_setg(errp, "TCG cannot support more than 8 threads/core "
-                       "on a pseries machine");
-            return;
-        }
-    }
-    if (!is_power_of_2(smp_threads)) {
-        error_setg(errp, "Cannot support %d threads/core on a pseries "
-                   "machine because it must be a power of 2", smp_threads);
-        return;
-    }
-
-    /* Determine the VSMT mode to use: */
-    if (vsmt_user) {
-        if (spapr->vsmt < smp_threads) {
-            error_setg(errp, "Cannot support VSMT mode %d"
-                       " because it must be >= threads/core (%d)",
-                       spapr->vsmt, smp_threads);
-            return;
-        }
-        /* In this case, spapr->vsmt has been set by the command line */
-    } else {
-        spapr->vsmt = smp_threads;
-    }
-
-    /* KVM: If necessary, set the SMT mode: */
-    if (kvm_enabled() && (spapr->vsmt != kvm_smt)) {
-        ret = kvmppc_set_smt_threads(spapr->vsmt);
-        if (ret) {
-            /* Looks like KVM isn't able to change VSMT mode */
-            error_setg_errno(&local_err, -ret,
-                             "Failed to set KVM's VSMT mode to %d",
-                             spapr->vsmt);
-            /* We can live with that if the default one is big enough
-             * for the number of threads, and a submultiple of the one
-             * we want.  In this case we'll waste some vcpu ids, but
-             * behaviour will be correct */
-            if ((kvm_smt >= smp_threads) && ((spapr->vsmt % kvm_smt) == 0)) {
-                warn_report_err(local_err);
-            } else {
-                if (!vsmt_user) {
-                    error_append_hint(&local_err,
-                                      "On PPC, a VM with %d threads/core"
-                                      " on a host with %d threads/core"
-                                      " requires the use of VSMT mode %d.\n",
-                                      smp_threads, kvm_smt, spapr->vsmt);
-                }
-                kvmppc_error_append_smt_possible_hint(&local_err);
-                error_propagate(errp, local_err);
-            }
-        }
-    }
-    /* else TCG: nothing to do currently */
-}
-
-static void spapr_init_cpus(SpaprMachineState *spapr)
-{
-    MachineState *machine = MACHINE(spapr);
-    MachineClass *mc = MACHINE_GET_CLASS(machine);
-    const char *type = spapr_get_cpu_core_type(machine->cpu_type);
-    const CPUArchIdList *possible_cpus;
-    unsigned int smp_cpus = machine->smp.cpus;
-    unsigned int smp_threads = machine->smp.threads;
-    unsigned int max_cpus = machine->smp.max_cpus;
-    int boot_cores_nr = smp_cpus / smp_threads;
-    int i;
-
-    possible_cpus = mc->possible_cpu_arch_ids(machine);
-    if (mc->has_hotpluggable_cpus) {
-        if (smp_cpus % smp_threads) {
-            error_report("smp_cpus (%u) must be multiple of threads (%u)",
-                         smp_cpus, smp_threads);
-            exit(1);
-        }
-        if (max_cpus % smp_threads) {
-            error_report("max_cpus (%u) must be multiple of threads (%u)",
-                         max_cpus, smp_threads);
-            exit(1);
-        }
-    } else {
-        if (max_cpus != smp_cpus) {
-            error_report("This machine version does not support CPU hotplug");
-            exit(1);
-        }
-        boot_cores_nr = possible_cpus->len;
-    }
-
-    for (i = 0; i < possible_cpus->len; i++) {
-        int core_id = i * smp_threads;
-
-        if (mc->has_hotpluggable_cpus) {
-            spapr_dr_connector_new(OBJECT(spapr), TYPE_SPAPR_DRC_CPU,
-                                   spapr_vcpu_id(spapr, core_id));
-        }
-
-        if (i < boot_cores_nr) {
-            Object *core  = object_new(type);
-            int nr_threads = smp_threads;
-
-            /* Handle the partially filled core for older machine types */
-            if ((i + 1) * smp_threads >= smp_cpus) {
-                nr_threads = smp_cpus - i * smp_threads;
-            }
-
-            object_property_set_int(core, "nr-threads", nr_threads,
-                                    &error_fatal);
-            object_property_set_int(core, CPU_CORE_PROP_CORE_ID, core_id,
-                                    &error_fatal);
-            qdev_realize(DEVICE(core), NULL, &error_fatal);
-
-            object_unref(core);
-        }
-    }
-}
 
 static PCIHostState *spapr_create_default_phb(void)
 {
@@ -3896,206 +3345,6 @@ static void spapr_memory_unplug_request(HotplugHandler 
*hotplug_dev,
                                               nr_lmbs, spapr_drc_index(drc));
 }
 
-/* Callback to be called during DRC release. */
-void spapr_core_release(DeviceState *dev)
-{
-    HotplugHandler *hotplug_ctrl = qdev_get_hotplug_handler(dev);
-
-    /* Call the unplug handler chain. This can never fail. */
-    hotplug_handler_unplug(hotplug_ctrl, dev, &error_abort);
-    object_unparent(OBJECT(dev));
-}
-
-static void spapr_core_unplug(HotplugHandler *hotplug_dev, DeviceState *dev)
-{
-    MachineState *ms = MACHINE(hotplug_dev);
-    CPUCore *cc = CPU_CORE(dev);
-    CPUArchId *core_slot = spapr_find_cpu_slot(ms, cc->core_id, NULL);
-
-    assert(core_slot);
-    core_slot->cpu = NULL;
-    qdev_unrealize(dev);
-}
-
-static
-void spapr_core_unplug_request(HotplugHandler *hotplug_dev, DeviceState *dev,
-                               Error **errp)
-{
-    SpaprMachineState *spapr = SPAPR_MACHINE(OBJECT(hotplug_dev));
-    int index;
-    SpaprDrc *drc;
-    CPUCore *cc = CPU_CORE(dev);
-
-    if (!spapr_find_cpu_slot(MACHINE(hotplug_dev), cc->core_id, &index)) {
-        error_setg(errp, "Unable to find CPU core with core-id: %d",
-                   cc->core_id);
-        return;
-    }
-    if (index == 0) {
-        error_setg(errp, "Boot CPU core may not be unplugged");
-        return;
-    }
-
-    drc = spapr_drc_by_id(TYPE_SPAPR_DRC_CPU,
-                          spapr_vcpu_id(spapr, cc->core_id));
-    g_assert(drc);
-
-    if (!spapr_drc_unplug_requested(drc)) {
-        spapr_drc_unplug_request(drc);
-    }
-
-    /*
-     * spapr_hotplug_req_remove_by_index is left unguarded, out of the
-     * "!spapr_drc_unplug_requested" check, to allow for multiple IRQ
-     * pulses removing the same CPU. Otherwise, in an failed hotunplug
-     * attempt (e.g. the kernel will refuse to remove the last online
-     * CPU), we will never attempt it again because unplug_requested
-     * will still be 'true' in that case.
-     */
-    spapr_hotplug_req_remove_by_index(drc);
-}
-
-int spapr_core_dt_populate(SpaprDrc *drc, SpaprMachineState *spapr,
-                           void *fdt, int *fdt_start_offset, Error **errp)
-{
-    SpaprCpuCore *core = SPAPR_CPU_CORE(drc->dev);
-    CPUState *cs = CPU(core->threads[0]);
-    PowerPCCPU *cpu = POWERPC_CPU(cs);
-    DeviceClass *dc = DEVICE_GET_CLASS(cs);
-    int id = spapr_get_vcpu_id(cpu);
-    g_autofree char *nodename = NULL;
-    int offset;
-
-    nodename = g_strdup_printf("%s@%x", dc->fw_name, id);
-    offset = fdt_add_subnode(fdt, 0, nodename);
-
-    spapr_dt_cpu(cs, fdt, offset, spapr);
-
-    /*
-     * spapr_dt_cpu() does not fill the 'name' property in the
-     * CPU node. The function is called during boot process, before
-     * and after CAS, and overwriting the 'name' property written
-     * by SLOF is not allowed.
-     *
-     * Write it manually after spapr_dt_cpu(). This makes the hotplug
-     * CPUs more compatible with the coldplugged ones, which have
-     * the 'name' property. Linux Kernel also relies on this
-     * property to identify CPU nodes.
-     */
-    _FDT((fdt_setprop_string(fdt, offset, "name", nodename)));
-
-    *fdt_start_offset = offset;
-    return 0;
-}
-
-static void spapr_core_plug(HotplugHandler *hotplug_dev, DeviceState *dev)
-{
-    SpaprMachineState *spapr = SPAPR_MACHINE(OBJECT(hotplug_dev));
-    MachineClass *mc = MACHINE_GET_CLASS(spapr);
-    SpaprCpuCore *core = SPAPR_CPU_CORE(OBJECT(dev));
-    CPUCore *cc = CPU_CORE(dev);
-    SpaprDrc *drc;
-    CPUArchId *core_slot;
-    int index;
-    bool hotplugged = spapr_drc_hotplugged(dev);
-    int i;
-
-    core_slot = spapr_find_cpu_slot(MACHINE(hotplug_dev), cc->core_id, &index);
-    g_assert(core_slot); /* Already checked in spapr_core_pre_plug() */
-
-    drc = spapr_drc_by_id(TYPE_SPAPR_DRC_CPU,
-                          spapr_vcpu_id(spapr, cc->core_id));
-
-    g_assert(drc || !mc->has_hotpluggable_cpus);
-
-    if (drc) {
-        /*
-         * spapr_core_pre_plug() already buys us this is a brand new
-         * core being plugged into a free slot. Nothing should already
-         * be attached to the corresponding DRC.
-         */
-        spapr_drc_attach(drc, dev);
-
-        if (hotplugged) {
-            /*
-             * Send hotplug notification interrupt to the guest only
-             * in case of hotplugged CPUs.
-             */
-            spapr_hotplug_req_add_by_index(drc);
-        } else {
-            spapr_drc_reset(drc);
-        }
-    }
-
-    core_slot->cpu = CPU(dev);
-
-    /*
-     * Set compatibility mode to match the boot CPU, which was either set
-     * by the machine reset code or by CAS. This really shouldn't fail at
-     * this point.
-     */
-    if (hotplugged) {
-        for (i = 0; i < cc->nr_threads; i++) {
-            ppc_set_compat(core->threads[i], 
POWERPC_CPU(first_cpu)->compat_pvr,
-                           &error_abort);
-        }
-    }
-
-}
-
-static void spapr_core_pre_plug(HotplugHandler *hotplug_dev, DeviceState *dev,
-                                Error **errp)
-{
-    MachineState *machine = MACHINE(OBJECT(hotplug_dev));
-    MachineClass *mc = MACHINE_GET_CLASS(hotplug_dev);
-    CPUCore *cc = CPU_CORE(dev);
-    const char *base_core_type = spapr_get_cpu_core_type(machine->cpu_type);
-    const char *type = object_get_typename(OBJECT(dev));
-    CPUArchId *core_slot;
-    int index;
-    unsigned int smp_threads = machine->smp.threads;
-
-    if (dev->hotplugged && !mc->has_hotpluggable_cpus) {
-        error_setg(errp, "CPU hotplug not supported for this machine");
-        return;
-    }
-
-    if (strcmp(base_core_type, type)) {
-        error_setg(errp, "CPU core type should be %s", base_core_type);
-        return;
-    }
-
-    if (cc->core_id % smp_threads) {
-        error_setg(errp, "invalid core id %d", cc->core_id);
-        return;
-    }
-
-    /*
-     * In general we should have homogeneous threads-per-core, but old
-     * (pre hotplug support) machine types allow the last core to have
-     * reduced threads as a compatibility hack for when we allowed
-     * total vcpus not a multiple of threads-per-core.
-     */
-    if (mc->has_hotpluggable_cpus && (cc->nr_threads != smp_threads)) {
-        error_setg(errp, "invalid nr-threads %d, must be %d", cc->nr_threads,
-                   smp_threads);
-        return;
-    }
-
-    core_slot = spapr_find_cpu_slot(MACHINE(hotplug_dev), cc->core_id, &index);
-    if (!core_slot) {
-        error_setg(errp, "core id %d out of range", cc->core_id);
-        return;
-    }
-
-    if (core_slot->cpu) {
-        error_setg(errp, "core %d already populated", cc->core_id);
-        return;
-    }
-
-    numa_cpu_pre_plug(core_slot, dev, errp);
-}
-
 int spapr_phb_dt_populate(SpaprDrc *drc, SpaprMachineState *spapr,
                           void *fdt, int *fdt_start_offset, Error **errp)
 {
@@ -4386,63 +3635,6 @@ static HotplugHandler 
*spapr_get_hotplug_handler(MachineState *machine,
     return NULL;
 }
 
-static CpuInstanceProperties
-spapr_cpu_index_to_props(MachineState *machine, unsigned cpu_index)
-{
-    CPUArchId *core_slot;
-    MachineClass *mc = MACHINE_GET_CLASS(machine);
-
-    /* make sure possible_cpu are initialized */
-    mc->possible_cpu_arch_ids(machine);
-    /* get CPU core slot containing thread that matches cpu_index */
-    core_slot = spapr_find_cpu_slot(machine, cpu_index, NULL);
-    assert(core_slot);
-    return core_slot->props;
-}
-
-static int64_t spapr_get_default_cpu_node_id(const MachineState *ms, int idx)
-{
-    return idx / ms->smp.cores % ms->numa_state->num_nodes;
-}
-
-static const CPUArchIdList *spapr_possible_cpu_arch_ids(MachineState *machine)
-{
-    int i;
-    unsigned int smp_threads = machine->smp.threads;
-    unsigned int smp_cpus = machine->smp.cpus;
-    const char *core_type;
-    int spapr_max_cores = machine->smp.max_cpus / smp_threads;
-    MachineClass *mc = MACHINE_GET_CLASS(machine);
-
-    if (!mc->has_hotpluggable_cpus) {
-        spapr_max_cores = QEMU_ALIGN_UP(smp_cpus, smp_threads) / smp_threads;
-    }
-    if (machine->possible_cpus) {
-        assert(machine->possible_cpus->len == spapr_max_cores);
-        return machine->possible_cpus;
-    }
-
-    core_type = spapr_get_cpu_core_type(machine->cpu_type);
-    if (!core_type) {
-        error_report("Unable to find sPAPR CPU Core definition");
-        exit(1);
-    }
-
-    machine->possible_cpus = g_malloc0(sizeof(CPUArchIdList) +
-                             sizeof(CPUArchId) * spapr_max_cores);
-    machine->possible_cpus->len = spapr_max_cores;
-    for (i = 0; i < machine->possible_cpus->len; i++) {
-        int core_id = i * smp_threads;
-
-        machine->possible_cpus->cpus[i].type = core_type;
-        machine->possible_cpus->cpus[i].vcpus_count = smp_threads;
-        machine->possible_cpus->cpus[i].arch_id = core_id;
-        machine->possible_cpus->cpus[i].props.has_core_id = true;
-        machine->possible_cpus->cpus[i].props.core_id = core_id;
-    }
-    return machine->possible_cpus;
-}
-
 static ICSState *spapr_ics_get(XICSFabric *dev, int irq)
 {
     SpaprMachineState *spapr = SPAPR_MACHINE(dev);
@@ -4504,99 +3696,6 @@ static bool spapr_match_nvt(XiveFabric *xfb, uint8_t 
format,
     return true;
 }
 
-int spapr_get_vcpu_id(PowerPCCPU *cpu)
-{
-    return cpu->vcpu_id;
-}
-
-bool spapr_set_vcpu_id(PowerPCCPU *cpu, int cpu_index, Error **errp)
-{
-    SpaprMachineState *spapr = SPAPR_MACHINE(qdev_get_machine());
-    MachineState *ms = MACHINE(spapr);
-    int vcpu_id;
-
-    vcpu_id = spapr_vcpu_id(spapr, cpu_index);
-
-    if (kvm_enabled() && !kvm_vcpu_id_is_valid(vcpu_id)) {
-        error_setg(errp, "Can't create CPU with id %d in KVM", vcpu_id);
-        error_append_hint(errp, "Adjust the number of cpus to %d "
-                          "or try to raise the number of threads per core\n",
-                          vcpu_id * ms->smp.threads / spapr->vsmt);
-        return false;
-    }
-
-    cpu->vcpu_id = vcpu_id;
-    return true;
-}
-
-PowerPCCPU *spapr_find_cpu(int vcpu_id)
-{
-    CPUState *cs;
-
-    CPU_FOREACH(cs) {
-        PowerPCCPU *cpu = POWERPC_CPU(cs);
-
-        if (spapr_get_vcpu_id(cpu) == vcpu_id) {
-            return cpu;
-        }
-    }
-
-    return NULL;
-}
-
-static bool spapr_cpu_in_nested(PowerPCCPU *cpu)
-{
-    SpaprCpuState *spapr_cpu = spapr_cpu_state(cpu);
-
-    return spapr_cpu->in_nested;
-}
-
-static void spapr_cpu_exec_enter(PPCVirtualHypervisor *vhyp, PowerPCCPU *cpu)
-{
-    SpaprCpuState *spapr_cpu = spapr_cpu_state(cpu);
-
-    /* These are only called by TCG, KVM maintains dispatch state */
-
-    spapr_cpu->prod = false;
-    if (spapr_cpu->vpa_addr) {
-        CPUState *cs = CPU(cpu);
-        uint32_t dispatch;
-
-        dispatch = ldl_be_phys(cs->as,
-                               spapr_cpu->vpa_addr + VPA_DISPATCH_COUNTER);
-        dispatch++;
-        if ((dispatch & 1) != 0) {
-            qemu_log_mask(LOG_GUEST_ERROR,
-                          "VPA: incorrect dispatch counter value for "
-                          "dispatched partition %u, correcting.\n", dispatch);
-            dispatch++;
-        }
-        stl_be_phys(cs->as,
-                    spapr_cpu->vpa_addr + VPA_DISPATCH_COUNTER, dispatch);
-    }
-}
-
-static void spapr_cpu_exec_exit(PPCVirtualHypervisor *vhyp, PowerPCCPU *cpu)
-{
-    SpaprCpuState *spapr_cpu = spapr_cpu_state(cpu);
-
-    if (spapr_cpu->vpa_addr) {
-        CPUState *cs = CPU(cpu);
-        uint32_t dispatch;
-
-        dispatch = ldl_be_phys(cs->as,
-                               spapr_cpu->vpa_addr + VPA_DISPATCH_COUNTER);
-        dispatch++;
-        if ((dispatch & 1) != 1) {
-            qemu_log_mask(LOG_GUEST_ERROR,
-                          "VPA: incorrect dispatch counter value for "
-                          "preempted partition %u, correcting.\n", dispatch);
-            dispatch++;
-        }
-        stl_be_phys(cs->as,
-                    spapr_cpu->vpa_addr + VPA_DISPATCH_COUNTER, dispatch);
-    }
-}
 
 static void spapr_machine_class_init(ObjectClass *oc, const void *data)
 {
diff --git a/hw/ppc/spapr_cpu_hotplug.c b/hw/ppc/spapr_cpu_hotplug.c
new file mode 100644
index 0000000000..d570fca417
--- /dev/null
+++ b/hw/ppc/spapr_cpu_hotplug.c
@@ -0,0 +1,984 @@
+/*
+ * sPAPR CPU hotplug — VCPU-id helpers, device-tree generation, and
+ * HotplugHandler / PPCVirtualHypervisor callbacks for sPAPR CPU cores.
+ *
+ * Code moved from hw/ppc/spapr.c; no functional changes.
+ *
+ * Copyright (c) 2004-2007 Fabrice Bellard
+ * Copyright (c) 2007 Jocelyn Mayer
+ * Copyright (c) 2010-2024 IBM Corporation.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/error-report.h"
+#include "qemu/log.h"
+#include "qapi/error.h"
+#include "exec/cpu-common.h"
+#include "system/cpus.h"
+#include "system/kvm.h"
+#include "system/numa.h"
+#include "system/tcg.h"
+#include "hw/core/cpu.h"
+#include "hw/ppc/fdt.h"
+#include "hw/ppc/ppc.h"
+#include "hw/ppc/spapr.h"
+#include "hw/ppc/spapr_cpu_core.h"
+#include "hw/ppc/spapr_cpu_hotplug.h"
+#include "hw/ppc/spapr_drc.h"
+#include "hw/ppc/spapr_numa.h"
+#include "kvm_ppc.h"
+#include "mmu-hash64.h"
+#include "target/ppc/cpu.h"
+#include "target/ppc/cpu-qom.h"
+#include "target/ppc/cpu-models.h"
+#include "target/ppc/mmu-hash64.h"
+
+#include <libfdt.h>
+
+/*
+ * VCPU-id / VSMT helpers
+ */
+
+/*
+ * These two functions implement the VCPU id numbering: one to compute them
+ * all and one to identify thread 0 of a VCORE. Any change to the first one
+ * is likely to have an impact on the second one, so let's keep them close.
+ */
+int spapr_vcpu_id(SpaprMachineState *spapr, int cpu_index)
+{
+    MachineState *ms = MACHINE(spapr);
+    unsigned int smp_threads = ms->smp.threads;
+
+    assert(spapr->vsmt);
+    return
+        (cpu_index / smp_threads) * spapr->vsmt + cpu_index % smp_threads;
+}
+
+bool spapr_is_thread0_in_vcore(SpaprMachineState *spapr, PowerPCCPU *cpu)
+{
+    assert(spapr->vsmt);
+    return spapr_get_vcpu_id(cpu) % spapr->vsmt == 0;
+}
+
+int spapr_max_server_number(SpaprMachineState *spapr)
+{
+    MachineState *ms = MACHINE(spapr);
+
+    assert(spapr->vsmt);
+    return DIV_ROUND_UP(ms->smp.max_cpus * spapr->vsmt, ms->smp.threads);
+}
+
+/*
+ * CPU slot lookup
+ */
+
+/* find cpu slot in machine->possible_cpus by core_id */
+CPUArchId *spapr_find_cpu_slot(MachineState *ms, uint32_t id, int *idx)
+{
+    int index = id / ms->smp.threads;
+
+    if (index >= ms->possible_cpus->len) {
+        return NULL;
+    }
+    if (idx) {
+        *idx = index;
+    }
+    return &ms->possible_cpus->cpus[index];
+}
+
+/*
+ * Machine-initialisation helpers
+ */
+
+void spapr_set_vsmt_mode(SpaprMachineState *spapr, Error **errp)
+{
+    MachineState *ms = MACHINE(spapr);
+    Error *local_err = NULL;
+    bool vsmt_user = !!spapr->vsmt;
+    int kvm_smt = kvmppc_smt_threads();
+    int ret;
+    unsigned int smp_threads = ms->smp.threads;
+
+    if (tcg_enabled()) {
+        if (smp_threads > 1 &&
+            !ppc_type_check_compat(ms->cpu_type, CPU_POWERPC_LOGICAL_2_07, 0,
+                                   spapr->max_compat_pvr)) {
+            error_setg(errp, "TCG only supports SMT on POWER8 or newer CPUs");
+            return;
+        }
+
+        if (smp_threads > 8) {
+            error_setg(errp, "TCG cannot support more than 8 threads/core "
+                       "on a pseries machine");
+            return;
+        }
+    }
+    if (!is_power_of_2(smp_threads)) {
+        error_setg(errp, "Cannot support %d threads/core on a pseries "
+                   "machine because it must be a power of 2", smp_threads);
+        return;
+    }
+
+    /* Determine the VSMT mode to use: */
+    if (vsmt_user) {
+        if (spapr->vsmt < smp_threads) {
+            error_setg(errp, "Cannot support VSMT mode %d"
+                       " because it must be >= threads/core (%d)",
+                       spapr->vsmt, smp_threads);
+            return;
+        }
+        /* In this case, spapr->vsmt has been set by the command line */
+    } else {
+        spapr->vsmt = smp_threads;
+    }
+
+    /* KVM: If necessary, set the SMT mode: */
+    if (kvm_enabled() && (spapr->vsmt != kvm_smt)) {
+        ret = kvmppc_set_smt_threads(spapr->vsmt);
+        if (ret) {
+            /* Looks like KVM isn't able to change VSMT mode */
+            error_setg_errno(&local_err, -ret,
+                             "Failed to set KVM's VSMT mode to %d",
+                             spapr->vsmt);
+            /*
+             * We can live with that if the default one is big enough
+             * for the number of threads, and a submultiple of the one
+             * we want.  In this case we'll waste some vcpu ids, but
+             * behaviour will be correct
+             */
+            if ((kvm_smt >= smp_threads) && ((spapr->vsmt % kvm_smt) == 0)) {
+                warn_report_err(local_err);
+            } else {
+                if (!vsmt_user) {
+                    error_append_hint(&local_err,
+                                      "On PPC, a VM with %d threads/core"
+                                      " on a host with %d threads/core"
+                                      " requires the use of VSMT mode %d.\n",
+                                      smp_threads, kvm_smt, spapr->vsmt);
+                }
+                kvmppc_error_append_smt_possible_hint(&local_err);
+                error_propagate(errp, local_err);
+            }
+        }
+    }
+    /* else TCG: nothing to do currently */
+}
+
+void spapr_init_cpus(SpaprMachineState *spapr)
+{
+    MachineState *machine = MACHINE(spapr);
+    MachineClass *mc = MACHINE_GET_CLASS(machine);
+    const char *type = spapr_get_cpu_core_type(machine->cpu_type);
+    const CPUArchIdList *possible_cpus;
+    unsigned int smp_cpus = machine->smp.cpus;
+    unsigned int smp_threads = machine->smp.threads;
+    unsigned int max_cpus = machine->smp.max_cpus;
+    int boot_cores_nr = smp_cpus / smp_threads;
+    int i;
+
+    possible_cpus = mc->possible_cpu_arch_ids(machine);
+    if (mc->has_hotpluggable_cpus) {
+        if (smp_cpus % smp_threads) {
+            error_report("smp_cpus (%u) must be multiple of threads (%u)",
+                         smp_cpus, smp_threads);
+            exit(1);
+        }
+        if (max_cpus % smp_threads) {
+            error_report("max_cpus (%u) must be multiple of threads (%u)",
+                         max_cpus, smp_threads);
+            exit(1);
+        }
+    } else {
+        if (max_cpus != smp_cpus) {
+            error_report("This machine version does not support CPU hotplug");
+            exit(1);
+        }
+        boot_cores_nr = possible_cpus->len;
+    }
+
+    for (i = 0; i < possible_cpus->len; i++) {
+        int core_id = i * smp_threads;
+
+        if (mc->has_hotpluggable_cpus) {
+            spapr_dr_connector_new(OBJECT(spapr), TYPE_SPAPR_DRC_CPU,
+                                   spapr_vcpu_id(spapr, core_id));
+        }
+
+        if (i < boot_cores_nr) {
+            Object *core  = object_new(type);
+            int nr_threads = smp_threads;
+
+            /* Handle the partially filled core for older machine types */
+            if ((i + 1) * smp_threads >= smp_cpus) {
+                nr_threads = smp_cpus - i * smp_threads;
+            }
+
+            object_property_set_int(core, "nr-threads", nr_threads,
+                                    &error_fatal);
+            object_property_set_int(core, CPU_CORE_PROP_CORE_ID, core_id,
+                                    &error_fatal);
+            qdev_realize(DEVICE(core), NULL, &error_fatal);
+
+            object_unref(core);
+        }
+    }
+}
+
+/*
+ * Device-tree helpers
+ */
+
+static int spapr_fixup_cpu_smt_dt(void *fdt, int offset, PowerPCCPU *cpu,
+                                  int smt_threads)
+{
+    int i, ret = 0;
+    g_autofree uint32_t *servers_prop = g_new(uint32_t, smt_threads);
+    g_autofree uint32_t *gservers_prop = g_new(uint32_t, smt_threads * 2);
+    int index = spapr_get_vcpu_id(cpu);
+
+    if (cpu->compat_pvr) {
+        ret = fdt_setprop_cell(fdt, offset, "cpu-version", cpu->compat_pvr);
+        if (ret < 0) {
+            return ret;
+        }
+    }
+
+    /* Build interrupt servers and gservers properties */
+    for (i = 0; i < smt_threads; i++) {
+        servers_prop[i] = cpu_to_be32(index + i);
+        /* Hack, direct the group queues back to cpu 0 */
+        gservers_prop[i * 2] = cpu_to_be32(index + i);
+        gservers_prop[i * 2 + 1] = 0;
+    }
+    ret = fdt_setprop(fdt, offset, "ibm,ppc-interrupt-server#s",
+                      servers_prop, sizeof(*servers_prop) * smt_threads);
+    if (ret < 0) {
+        return ret;
+    }
+    ret = fdt_setprop(fdt, offset, "ibm,ppc-interrupt-gserver#s",
+                      gservers_prop, sizeof(*gservers_prop) * smt_threads * 2);
+
+    return ret;
+}
+
+static void spapr_dt_pa_features(SpaprMachineState *spapr,
+                                 PowerPCCPU *cpu,
+                                 void *fdt, int offset)
+{
+    /*
+     * SSO (SAO) ordering is supported on KVM and thread=single hosts,
+     * but not MTTCG, so disable it. To advertise it, a cap would have
+     * to be added, or support implemented for MTTCG.
+     *
+     * Copy/paste is not supported by TCG, so it is not advertised. KVM
+     * can execute them but it has no accelerator drivers which are usable,
+     * so there isn't much need for it anyway.
+     */
+
+    /* These should be kept in sync with pnv */
+    uint8_t pa_features_206[] = { 6, 0,
+        0xf6, 0x1f, 0xc7, 0x00, 0x00, 0xc0 };
+    uint8_t pa_features_207[] = { 24, 0,
+        0xf6, 0x1f, 0xc7, 0xc0, 0x00, 0xf0,
+        0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x80, 0x00,
+        0x80, 0x00, 0x80, 0x00, 0x00, 0x00 };
+    uint8_t pa_features_300[] = { 66, 0,
+        /* 0: MMU|FPU|SLB|RUN|DABR|NX, 1: fri[nzpm]|DABRX|SPRG3|SLB0|PP110 */
+        /* 2: VPM|DS205|PPR|DS202|DS206, 3: LSD|URG, 5: LE|CFAR|EB|LSQ */
+        0xf6, 0x1f, 0xc7, 0xc0, 0x00, 0xf0, /* 0 - 5 */
+        /* 6: DS207 */
+        0x80, 0x00, 0x00, 0x00, 0x00, 0x00, /* 6 - 11 */
+        /* 16: Vector */
+        0x00, 0x00, 0x00, 0x00, 0x80, 0x00, /* 12 - 17 */
+        /* 18: Vec. Scalar, 20: Vec. XOR */
+        0x80, 0x00, 0x80, 0x00, 0x00, 0x00, /* 18 - 23 */
+        /* 24: Ext. Dec, 26: 64 bit ftrs, 28: PM ftrs */
+        0x80, 0x00, 0x80, 0x00, 0x80, 0x00, /* 24 - 29 */
+        /* 32: LE atomic, 34: EBB + ext EBB */
+        0x00, 0x00, 0x80, 0x00, 0xC0, 0x00, /* 30 - 35 */
+        /* 40: Radix MMU */
+        0x00, 0x00, 0x00, 0x00, 0x80, 0x00, /* 36 - 41 */
+        /* 42: PM, 44: PC RA, 46: SC vec'd */
+        0x80, 0x00, 0x80, 0x00, 0x80, 0x00, /* 42 - 47 */
+        /* 48: SIMD, 50: QP BFP, 52: String */
+        0x80, 0x00, 0x80, 0x00, 0x80, 0x00, /* 48 - 53 */
+        /* 54: DecFP, 56: DecI, 58: SHA */
+        0x80, 0x00, 0x80, 0x00, 0x80, 0x00, /* 54 - 59 */
+        /* 60: NM atomic, 62: RNG */
+        0x80, 0x00, 0x80, 0x00, 0x00, 0x00, /* 60 - 65 */
+    };
+    /* 3.1 removes SAO, HTM support */
+    uint8_t pa_features_31[] = { 74, 0,
+        /* 0: MMU|FPU|SLB|RUN|DABR|NX, 1: fri[nzpm]|DABRX|SPRG3|SLB0|PP110 */
+        /* 2: VPM|DS205|PPR|DS202|DS206, 3: LSD|URG, 5: LE|CFAR|EB|LSQ */
+        0xf6, 0x1f, 0xc7, 0xc0, 0x00, 0xf0, /* 0 - 5 */
+        /* 6: DS207 */
+        0x80, 0x00, 0x00, 0x00, 0x00, 0x00, /* 6 - 11 */
+        /* 16: Vector */
+        0x00, 0x00, 0x00, 0x00, 0x80, 0x00, /* 12 - 17 */
+        /* 18: Vec. Scalar, 20: Vec. XOR */
+        0x80, 0x00, 0x80, 0x00, 0x00, 0x00, /* 18 - 23 */
+        /* 24: Ext. Dec, 26: 64 bit ftrs, 28: PM ftrs */
+        0x80, 0x00, 0x80, 0x00, 0x80, 0x00, /* 24 - 29 */
+        /* 32: LE atomic, 34: EBB + ext EBB */
+        0x00, 0x00, 0x80, 0x00, 0xC0, 0x00, /* 30 - 35 */
+        /* 40: Radix MMU */
+        0x00, 0x00, 0x00, 0x00, 0x80, 0x00, /* 36 - 41 */
+        /* 42: PM, 44: PC RA, 46: SC vec'd */
+        0x80, 0x00, 0x80, 0x00, 0x80, 0x00, /* 42 - 47 */
+        /* 48: SIMD, 50: QP BFP, 52: String */
+        0x80, 0x00, 0x80, 0x00, 0x80, 0x00, /* 48 - 53 */
+        /* 54: DecFP, 56: DecI, 58: SHA */
+        0x80, 0x00, 0x80, 0x00, 0x80, 0x00, /* 54 - 59 */
+        /* 60: NM atomic, 62: RNG, 64: DAWR1 (ISA 3.1) */
+        0x80, 0x00, 0x80, 0x00, 0x00, 0x00, /* 60 - 65 */
+        /* 68: DEXCR[SBHE|IBRTPDUS|SRAPD|NPHIE|PHIE] */
+        0x00, 0x00, 0xce, 0x00, 0x00, 0x00, /* 66 - 71 */
+        /* 72: [P]HASHST/[P]HASHCHK */
+        0x80, 0x00,                         /* 72 - 73 */
+    };
+    uint8_t *pa_features = NULL;
+    size_t pa_size;
+
+    if (ppc_check_compat(cpu, CPU_POWERPC_LOGICAL_2_06, 0, cpu->compat_pvr)) {
+        pa_features = pa_features_206;
+        pa_size = sizeof(pa_features_206);
+    }
+    if (ppc_check_compat(cpu, CPU_POWERPC_LOGICAL_2_07, 0, cpu->compat_pvr)) {
+        pa_features = pa_features_207;
+        pa_size = sizeof(pa_features_207);
+    }
+    if (ppc_check_compat(cpu, CPU_POWERPC_LOGICAL_3_00, 0, cpu->compat_pvr)) {
+        pa_features = pa_features_300;
+        pa_size = sizeof(pa_features_300);
+    }
+    if (ppc_check_compat(cpu, CPU_POWERPC_LOGICAL_3_10, 0, cpu->compat_pvr)) {
+        pa_features = pa_features_31;
+        pa_size = sizeof(pa_features_31);
+    }
+    if (!pa_features) {
+        return;
+    }
+
+    if (ppc_hash64_has(cpu, PPC_HASH64_CI_LARGEPAGE)) {
+        /*
+         * Note: we keep CI large pages off by default because a 64K capable
+         * guest provisioned with large pages might otherwise try to map a qemu
+         * framebuffer (or other kind of memory mapped PCI BAR) using 64K pages
+         * even if that qemu runs on a 4k host.
+         * We dd this bit back here if we are confident this is not an issue
+         */
+        pa_features[3] |= 0x20;
+    }
+    if ((spapr_get_cap(spapr, SPAPR_CAP_HTM) != 0) && pa_size > 24) {
+        pa_features[24] |= 0x80;    /* Transactional memory support */
+    }
+    if (spapr->cas_pre_isa3_guest && pa_size > 40) {
+        /*
+         * Workaround for broken kernels that attempt (guest) radix
+         * mode when they can't handle it, if they see the radix bit set
+         * in pa-features. So hide it from them.
+         */
+        pa_features[40 + 2] &= ~0x80; /* Radix MMU */
+    }
+    if (spapr_get_cap(spapr, SPAPR_CAP_DAWR1)) {
+        g_assert(pa_size > 66);
+        pa_features[66] |= 0x80;
+    }
+
+    _FDT((fdt_setprop(fdt, offset, "ibm,pa-features", pa_features, pa_size)));
+}
+
+static void spapr_dt_pi_features(SpaprMachineState *spapr,
+                                 PowerPCCPU *cpu,
+                                 void *fdt, int offset)
+{
+    uint8_t pi_features[] = { 1, 0,
+        0x00 };
+
+    if (kvm_enabled() && ppc_check_compat(cpu, CPU_POWERPC_LOGICAL_3_00,
+                                          0, cpu->compat_pvr)) {
+        /*
+         * POWER9 and later CPUs with KVM run in LPAR-per-thread mode where
+         * all threads are essentially independent CPUs, and msgsndp does not
+         * work (because it is physically-addressed) and therefore is
+         * emulated by KVM, so disable it here to ensure XIVE will be used.
+         * This is both KVM and CPU implementation-specific behaviour so a KVM
+         * cap would be cleanest, but for now this works. If KVM ever permits
+         * native msgsndp execution by guests, a cap could be added at that
+         * time.
+         */
+        pi_features[2] |= 0x08; /* 4: No msgsndp */
+    }
+
+    _FDT((fdt_setprop(fdt, offset, "ibm,pi-features", pi_features,
+                      sizeof(pi_features))));
+}
+
+void spapr_dt_cpu(CPUState *cs, void *fdt, int offset,
+                  SpaprMachineState *spapr)
+{
+    MachineState *ms = MACHINE(spapr);
+    PowerPCCPU *cpu = POWERPC_CPU(cs);
+    CPUPPCState *env = &cpu->env;
+    PowerPCCPUClass *pcc = POWERPC_CPU_GET_CLASS(cs);
+    int index = spapr_get_vcpu_id(cpu);
+    uint32_t segs[] = {cpu_to_be32(28), cpu_to_be32(40),
+                       0xffffffff, 0xffffffff};
+    uint32_t tbfreq = kvm_enabled() ? kvmppc_get_tbfreq()
+        : SPAPR_TIMEBASE_FREQ;
+    uint32_t cpufreq = kvm_enabled() ? kvmppc_get_clockfreq() : 1000000000;
+    uint32_t page_sizes_prop[64];
+    size_t page_sizes_prop_size;
+    unsigned int smp_threads = ms->smp.threads;
+    uint32_t vcpus_per_socket = smp_threads * ms->smp.cores;
+    uint32_t pft_size_prop[] = {0, cpu_to_be32(spapr->htab_shift)};
+    int compat_smt = MIN(smp_threads, ppc_compat_max_vthreads(cpu));
+    SpaprDrc *drc;
+    int drc_index;
+    uint32_t radix_AP_encodings[PPC_PAGE_SIZES_MAX_SZ];
+    int i;
+
+    drc = spapr_drc_by_id(TYPE_SPAPR_DRC_CPU, env->core_index);
+    if (drc) {
+        drc_index = spapr_drc_index(drc);
+        _FDT((fdt_setprop_cell(fdt, offset, "ibm,my-drc-index", drc_index)));
+    }
+
+    _FDT((fdt_setprop_cell(fdt, offset, "reg", index)));
+    _FDT((fdt_setprop_string(fdt, offset, "device_type", "cpu")));
+
+    _FDT((fdt_setprop_cell(fdt, offset, "cpu-version", env->spr[SPR_PVR])));
+    _FDT((fdt_setprop_cell(fdt, offset, "d-cache-block-size",
+                           env->dcache_line_size)));
+    _FDT((fdt_setprop_cell(fdt, offset, "d-cache-line-size",
+                           env->dcache_line_size)));
+    _FDT((fdt_setprop_cell(fdt, offset, "i-cache-block-size",
+                           env->icache_line_size)));
+    _FDT((fdt_setprop_cell(fdt, offset, "i-cache-line-size",
+                           env->icache_line_size)));
+
+    if (pcc->l1_dcache_size) {
+        _FDT((fdt_setprop_cell(fdt, offset, "d-cache-size",
+                               pcc->l1_dcache_size)));
+    } else {
+        warn_report("Unknown L1 dcache size for cpu");
+    }
+    if (pcc->l1_icache_size) {
+        _FDT((fdt_setprop_cell(fdt, offset, "i-cache-size",
+                               pcc->l1_icache_size)));
+    } else {
+        warn_report("Unknown L1 icache size for cpu");
+    }
+
+    _FDT((fdt_setprop_cell(fdt, offset, "timebase-frequency", tbfreq)));
+    _FDT((fdt_setprop_cell(fdt, offset, "clock-frequency", cpufreq)));
+    _FDT((fdt_setprop_cell(fdt, offset, "slb-size",
+                           cpu->hash64_opts->slb_size)));
+    _FDT((fdt_setprop_cell(fdt, offset, "ibm,slb-size",
+                           cpu->hash64_opts->slb_size)));
+    _FDT((fdt_setprop_string(fdt, offset, "status", "okay")));
+    _FDT((fdt_setprop(fdt, offset, "64-bit", NULL, 0)));
+
+    if (ppc_has_spr(cpu, SPR_PURR)) {
+        _FDT((fdt_setprop_cell(fdt, offset, "ibm,purr", 1)));
+    }
+    if (ppc_has_spr(cpu, SPR_PURR)) {
+        _FDT((fdt_setprop_cell(fdt, offset, "ibm,spurr", 1)));
+    }
+
+    if (ppc_hash64_has(cpu, PPC_HASH64_1TSEG)) {
+        _FDT((fdt_setprop(fdt, offset, "ibm,processor-segment-sizes",
+                          segs, sizeof(segs))));
+    }
+
+    /*
+     * Advertise VSX (vector extensions) if available
+     *   1               == VMX / Altivec available
+     *   2               == VSX available
+     *
+     * Only CPUs for which we create core types in spapr_cpu_core.c
+     * are possible, and all of those have VMX
+     */
+    if (env->insns_flags & PPC_ALTIVEC) {
+        if (spapr_get_cap(spapr, SPAPR_CAP_VSX) != 0) {
+            _FDT((fdt_setprop_cell(fdt, offset, "ibm,vmx", 2)));
+        } else {
+            _FDT((fdt_setprop_cell(fdt, offset, "ibm,vmx", 1)));
+        }
+    }
+
+    /*
+     * Advertise DFP (Decimal Floating Point) if available
+     *   0 / no property == no DFP
+     *   1               == DFP available
+     */
+    if (spapr_get_cap(spapr, SPAPR_CAP_DFP) != 0) {
+        _FDT((fdt_setprop_cell(fdt, offset, "ibm,dfp", 1)));
+    }
+
+    page_sizes_prop_size = ppc_create_page_sizes_prop(cpu, page_sizes_prop,
+                                                      sizeof(page_sizes_prop));
+    if (page_sizes_prop_size) {
+        _FDT((fdt_setprop(fdt, offset, "ibm,segment-page-sizes",
+                          page_sizes_prop, page_sizes_prop_size)));
+    }
+
+    spapr_dt_pa_features(spapr, cpu, fdt, offset);
+
+    spapr_dt_pi_features(spapr, cpu, fdt, offset);
+
+    _FDT((fdt_setprop_cell(fdt, offset, "ibm,chip-id",
+                           cs->cpu_index / vcpus_per_socket)));
+
+    _FDT((fdt_setprop(fdt, offset, "ibm,pft-size",
+                      pft_size_prop, sizeof(pft_size_prop))));
+
+    if (ms->numa_state->num_nodes > 1) {
+        _FDT(spapr_numa_fixup_cpu_dt(spapr, fdt, offset, cpu));
+    }
+
+    _FDT(spapr_fixup_cpu_smt_dt(fdt, offset, cpu, compat_smt));
+
+    if (pcc->radix_page_info) {
+        for (i = 0; i < pcc->radix_page_info->count; i++) {
+            radix_AP_encodings[i] =
+                cpu_to_be32(pcc->radix_page_info->entries[i]);
+        }
+        _FDT((fdt_setprop(fdt, offset, "ibm,processor-radix-AP-encodings",
+                          radix_AP_encodings,
+                          pcc->radix_page_info->count *
+                          sizeof(radix_AP_encodings[0]))));
+    }
+
+    /*
+     * We set this property to let the guest know that it can use the large
+     * decrementer and its width in bits.
+     */
+    if (spapr_get_cap(spapr, SPAPR_CAP_LARGE_DECREMENTER) != SPAPR_CAP_OFF)
+        _FDT((fdt_setprop_u32(fdt, offset, "ibm,dec-bits",
+                              pcc->lrg_decr_bits)));
+}
+
+static void spapr_dt_one_cpu(void *fdt, SpaprMachineState *spapr, CPUState *cs,
+                             int cpus_offset)
+{
+    PowerPCCPU *cpu = POWERPC_CPU(cs);
+    int index = spapr_get_vcpu_id(cpu);
+    DeviceClass *dc = DEVICE_GET_CLASS(cs);
+    g_autofree char *nodename = NULL;
+    int offset;
+
+    if (!spapr_is_thread0_in_vcore(spapr, cpu)) {
+        return;
+    }
+
+    nodename = g_strdup_printf("%s@%x", dc->fw_name, index);
+    offset = fdt_add_subnode(fdt, cpus_offset, nodename);
+    _FDT(offset);
+    spapr_dt_cpu(cs, fdt, offset, spapr);
+}
+
+void spapr_dt_cpus(void *fdt, SpaprMachineState *spapr)
+{
+    CPUState **rev;
+    CPUState *cs;
+    int n_cpus;
+    int cpus_offset;
+    int i;
+
+    cpus_offset = fdt_add_subnode(fdt, 0, "cpus");
+    _FDT(cpus_offset);
+    _FDT((fdt_setprop_cell(fdt, cpus_offset, "#address-cells", 0x1)));
+    _FDT((fdt_setprop_cell(fdt, cpus_offset, "#size-cells", 0x0)));
+
+    /*
+     * We walk the CPUs in reverse order to ensure that CPU DT nodes
+     * created by fdt_add_subnode() end up in the right order in FDT
+     * for the guest kernel the enumerate the CPUs correctly.
+     *
+     * The CPU list cannot be traversed in reverse order, so we need
+     * to do extra work.
+     */
+    n_cpus = 0;
+    rev = NULL;
+    CPU_FOREACH(cs) {
+        rev = g_renew(CPUState *, rev, n_cpus + 1);
+        rev[n_cpus++] = cs;
+    }
+
+    for (i = n_cpus - 1; i >= 0; i--) {
+        spapr_dt_one_cpu(fdt, spapr, rev[i], cpus_offset);
+    }
+
+    g_free(rev);
+}
+
+/*
+ * HotplugHandler callbacks
+ */
+
+/* Callback to be called during DRC release. */
+void spapr_core_release(DeviceState *dev)
+{
+    HotplugHandler *hotplug_ctrl = qdev_get_hotplug_handler(dev);
+
+    /* Call the unplug handler chain. This can never fail. */
+    hotplug_handler_unplug(hotplug_ctrl, dev, &error_abort);
+    object_unparent(OBJECT(dev));
+}
+
+void spapr_core_unplug(HotplugHandler *hotplug_dev, DeviceState *dev)
+{
+    MachineState *ms = MACHINE(hotplug_dev);
+    CPUCore *cc = CPU_CORE(dev);
+    CPUArchId *core_slot = spapr_find_cpu_slot(ms, cc->core_id, NULL);
+
+    assert(core_slot);
+    core_slot->cpu = NULL;
+    qdev_unrealize(dev);
+}
+
+void spapr_core_unplug_request(HotplugHandler *hotplug_dev, DeviceState *dev,
+                               Error **errp)
+{
+    SpaprMachineState *spapr = SPAPR_MACHINE(OBJECT(hotplug_dev));
+    int index;
+    SpaprDrc *drc;
+    CPUCore *cc = CPU_CORE(dev);
+
+    if (!spapr_find_cpu_slot(MACHINE(hotplug_dev), cc->core_id, &index)) {
+        error_setg(errp, "Unable to find CPU core with core-id: %d",
+                   cc->core_id);
+        return;
+    }
+    if (index == 0) {
+        error_setg(errp, "Boot CPU core may not be unplugged");
+        return;
+    }
+
+    drc = spapr_drc_by_id(TYPE_SPAPR_DRC_CPU,
+                          spapr_vcpu_id(spapr, cc->core_id));
+    g_assert(drc);
+
+    if (!spapr_drc_unplug_requested(drc)) {
+        spapr_drc_unplug_request(drc);
+    }
+
+    /*
+     * spapr_hotplug_req_remove_by_index is left unguarded, out of the
+     * "!spapr_drc_unplug_requested" check, to allow for multiple IRQ
+     * pulses removing the same CPU. Otherwise, in an failed hotunplug
+     * attempt (e.g. the kernel will refuse to remove the last online
+     * CPU), we will never attempt it again because unplug_requested
+     * will still be 'true' in that case.
+     */
+    spapr_hotplug_req_remove_by_index(drc);
+}
+
+int spapr_core_dt_populate(SpaprDrc *drc, SpaprMachineState *spapr,
+                           void *fdt, int *fdt_start_offset, Error **errp)
+{
+    SpaprCpuCore *core = SPAPR_CPU_CORE(drc->dev);
+    CPUState *cs = CPU(core->threads[0]);
+    PowerPCCPU *cpu = POWERPC_CPU(cs);
+    DeviceClass *dc = DEVICE_GET_CLASS(cs);
+    int id = spapr_get_vcpu_id(cpu);
+    g_autofree char *nodename = NULL;
+    int offset;
+
+    nodename = g_strdup_printf("%s@%x", dc->fw_name, id);
+    offset = fdt_add_subnode(fdt, 0, nodename);
+
+    spapr_dt_cpu(cs, fdt, offset, spapr);
+
+    /*
+     * spapr_dt_cpu() does not fill the 'name' property in the
+     * CPU node. The function is called during boot process, before
+     * and after CAS, and overwriting the 'name' property written
+     * by SLOF is not allowed.
+     *
+     * Write it manually after spapr_dt_cpu(). This makes the hotplug
+     * CPUs more compatible with the coldplugged ones, which have
+     * the 'name' property. Linux Kernel also relies on this
+     * property to identify CPU nodes.
+     */
+    _FDT((fdt_setprop_string(fdt, offset, "name", nodename)));
+
+    *fdt_start_offset = offset;
+    return 0;
+}
+
+void spapr_core_plug(HotplugHandler *hotplug_dev, DeviceState *dev)
+{
+    SpaprMachineState *spapr = SPAPR_MACHINE(OBJECT(hotplug_dev));
+    MachineClass *mc = MACHINE_GET_CLASS(spapr);
+    SpaprCpuCore *core = SPAPR_CPU_CORE(OBJECT(dev));
+    CPUCore *cc = CPU_CORE(dev);
+    SpaprDrc *drc;
+    CPUArchId *core_slot;
+    int index;
+    bool hotplugged = spapr_drc_hotplugged(dev);
+    int i;
+
+    core_slot = spapr_find_cpu_slot(MACHINE(hotplug_dev), cc->core_id, &index);
+    g_assert(core_slot); /* Already checked in spapr_core_pre_plug() */
+
+    drc = spapr_drc_by_id(TYPE_SPAPR_DRC_CPU,
+                          spapr_vcpu_id(spapr, cc->core_id));
+
+    g_assert(drc || !mc->has_hotpluggable_cpus);
+
+    if (drc) {
+        /*
+         * spapr_core_pre_plug() already buys us this is a brand new
+         * core being plugged into a free slot. Nothing should already
+         * be attached to the corresponding DRC.
+         */
+        spapr_drc_attach(drc, dev);
+
+        if (hotplugged) {
+            /*
+             * Send hotplug notification interrupt to the guest only
+             * in case of hotplugged CPUs.
+             */
+            spapr_hotplug_req_add_by_index(drc);
+        } else {
+            spapr_drc_reset(drc);
+        }
+    }
+
+    core_slot->cpu = CPU(dev);
+
+    /*
+     * Set compatibility mode to match the boot CPU, which was either set
+     * by the machine reset code or by CAS. This really shouldn't fail at
+     * this point.
+     */
+    if (hotplugged) {
+        for (i = 0; i < cc->nr_threads; i++) {
+            ppc_set_compat(core->threads[i], 
POWERPC_CPU(first_cpu)->compat_pvr,
+                           &error_abort);
+        }
+    }
+
+}
+
+void spapr_core_pre_plug(HotplugHandler *hotplug_dev, DeviceState *dev,
+                         Error **errp)
+{
+    MachineState *machine = MACHINE(OBJECT(hotplug_dev));
+    MachineClass *mc = MACHINE_GET_CLASS(hotplug_dev);
+    CPUCore *cc = CPU_CORE(dev);
+    const char *base_core_type = spapr_get_cpu_core_type(machine->cpu_type);
+    const char *type = object_get_typename(OBJECT(dev));
+    CPUArchId *core_slot;
+    int index;
+    unsigned int smp_threads = machine->smp.threads;
+
+    if (dev->hotplugged && !mc->has_hotpluggable_cpus) {
+        error_setg(errp, "CPU hotplug not supported for this machine");
+        return;
+    }
+
+    if (strcmp(base_core_type, type)) {
+        error_setg(errp, "CPU core type should be %s", base_core_type);
+        return;
+    }
+
+    if (cc->core_id % smp_threads) {
+        error_setg(errp, "invalid core id %d", cc->core_id);
+        return;
+    }
+
+    /*
+     * In general we should have homogeneous threads-per-core, but old
+     * (pre hotplug support) machine types allow the last core to have
+     * reduced threads as a compatibility hack for when we allowed
+     * total vcpus not a multiple of threads-per-core.
+     */
+    if (mc->has_hotpluggable_cpus && (cc->nr_threads != smp_threads)) {
+        error_setg(errp, "invalid nr-threads %d, must be %d", cc->nr_threads,
+                   smp_threads);
+        return;
+    }
+
+    core_slot = spapr_find_cpu_slot(MACHINE(hotplug_dev), cc->core_id, &index);
+    if (!core_slot) {
+        error_setg(errp, "core id %d out of range", cc->core_id);
+        return;
+    }
+
+    if (core_slot->cpu) {
+        error_setg(errp, "core %d already populated", cc->core_id);
+        return;
+    }
+
+    numa_cpu_pre_plug(core_slot, dev, errp);
+}
+
+/*
+ * VCPU-id public accessors  (consumed by spapr_hcall.c, spapr_rtas.c, …)
+ */
+
+int spapr_get_vcpu_id(PowerPCCPU *cpu)
+{
+    return cpu->vcpu_id;
+}
+
+bool spapr_set_vcpu_id(PowerPCCPU *cpu, int cpu_index, Error **errp)
+{
+    SpaprMachineState *spapr = SPAPR_MACHINE(qdev_get_machine());
+    MachineState *ms = MACHINE(spapr);
+    int vcpu_id;
+
+    vcpu_id = spapr_vcpu_id(spapr, cpu_index);
+
+    if (kvm_enabled() && !kvm_vcpu_id_is_valid(vcpu_id)) {
+        error_setg(errp, "Can't create CPU with id %d in KVM", vcpu_id);
+        error_append_hint(errp, "Adjust the number of cpus to %d "
+                          "or try to raise the number of threads per core\n",
+                          vcpu_id * ms->smp.threads / spapr->vsmt);
+        return false;
+    }
+
+    cpu->vcpu_id = vcpu_id;
+    return true;
+}
+
+PowerPCCPU *spapr_find_cpu(int vcpu_id)
+{
+    CPUState *cs;
+
+    CPU_FOREACH(cs) {
+        PowerPCCPU *cpu = POWERPC_CPU(cs);
+
+        if (spapr_get_vcpu_id(cpu) == vcpu_id) {
+            return cpu;
+        }
+    }
+
+    return NULL;
+}
+
+/*
+ * Machine-class CPU-topology callbacks
+ */
+
+CpuInstanceProperties
+spapr_cpu_index_to_props(MachineState *machine, unsigned cpu_index)
+{
+    CPUArchId *core_slot;
+    MachineClass *mc = MACHINE_GET_CLASS(machine);
+
+    /* make sure possible_cpu are initialized */
+    mc->possible_cpu_arch_ids(machine);
+    /* get CPU core slot containing thread that matches cpu_index */
+    core_slot = spapr_find_cpu_slot(machine, cpu_index, NULL);
+    assert(core_slot);
+    return core_slot->props;
+}
+
+int64_t spapr_get_default_cpu_node_id(const MachineState *ms, int idx)
+{
+    return idx / ms->smp.cores % ms->numa_state->num_nodes;
+}
+
+const CPUArchIdList *spapr_possible_cpu_arch_ids(MachineState *machine)
+{
+    int i;
+    unsigned int smp_threads = machine->smp.threads;
+    unsigned int smp_cpus = machine->smp.cpus;
+    const char *core_type;
+    int spapr_max_cores = machine->smp.max_cpus / smp_threads;
+    MachineClass *mc = MACHINE_GET_CLASS(machine);
+
+    if (!mc->has_hotpluggable_cpus) {
+        spapr_max_cores = QEMU_ALIGN_UP(smp_cpus, smp_threads) / smp_threads;
+    }
+    if (machine->possible_cpus) {
+        assert(machine->possible_cpus->len == spapr_max_cores);
+        return machine->possible_cpus;
+    }
+
+    core_type = spapr_get_cpu_core_type(machine->cpu_type);
+    if (!core_type) {
+        error_report("Unable to find sPAPR CPU Core definition");
+        exit(1);
+    }
+
+    machine->possible_cpus = g_malloc0(sizeof(CPUArchIdList) +
+                             sizeof(CPUArchId) * spapr_max_cores);
+    machine->possible_cpus->len = spapr_max_cores;
+    for (i = 0; i < machine->possible_cpus->len; i++) {
+        int core_id = i * smp_threads;
+
+        machine->possible_cpus->cpus[i].type = core_type;
+        machine->possible_cpus->cpus[i].vcpus_count = smp_threads;
+        machine->possible_cpus->cpus[i].arch_id = core_id;
+        machine->possible_cpus->cpus[i].props.has_core_id = true;
+        machine->possible_cpus->cpus[i].props.core_id = core_id;
+    }
+    return machine->possible_cpus;
+}
+
+/*
+ * PPCVirtualHypervisor callbacks
+ */
+
+bool spapr_cpu_in_nested(PowerPCCPU *cpu)
+{
+    SpaprCpuState *spapr_cpu = spapr_cpu_state(cpu);
+
+    return spapr_cpu->in_nested;
+}
+
+void spapr_cpu_exec_enter(PPCVirtualHypervisor *vhyp, PowerPCCPU *cpu)
+{
+    SpaprCpuState *spapr_cpu = spapr_cpu_state(cpu);
+
+    /* These are only called by TCG, KVM maintains dispatch state */
+
+    spapr_cpu->prod = false;
+    if (spapr_cpu->vpa_addr) {
+        CPUState *cs = CPU(cpu);
+        uint32_t dispatch;
+
+        dispatch = ldl_be_phys(cs->as,
+                               spapr_cpu->vpa_addr + VPA_DISPATCH_COUNTER);
+        dispatch++;
+        if ((dispatch & 1) != 0) {
+            qemu_log_mask(LOG_GUEST_ERROR,
+                          "VPA: incorrect dispatch counter value for "
+                          "dispatched partition %u, correcting.\n", dispatch);
+            dispatch++;
+        }
+        stl_be_phys(cs->as,
+                    spapr_cpu->vpa_addr + VPA_DISPATCH_COUNTER, dispatch);
+    }
+}
+
+void spapr_cpu_exec_exit(PPCVirtualHypervisor *vhyp, PowerPCCPU *cpu)
+{
+    SpaprCpuState *spapr_cpu = spapr_cpu_state(cpu);
+
+    if (spapr_cpu->vpa_addr) {
+        CPUState *cs = CPU(cpu);
+        uint32_t dispatch;
+
+        dispatch = ldl_be_phys(cs->as,
+                               spapr_cpu->vpa_addr + VPA_DISPATCH_COUNTER);
+        dispatch++;
+        if ((dispatch & 1) != 1) {
+            qemu_log_mask(LOG_GUEST_ERROR,
+                          "VPA: incorrect dispatch counter value for "
+                          "preempted partition %u, correcting.\n", dispatch);
+            dispatch++;
+        }
+        stl_be_phys(cs->as,
+                    spapr_cpu->vpa_addr + VPA_DISPATCH_COUNTER, dispatch);
+    }
+}
+
diff --git a/include/hw/ppc/spapr_cpu_hotplug.h 
b/include/hw/ppc/spapr_cpu_hotplug.h
new file mode 100644
index 0000000000..f3b74396b5
--- /dev/null
+++ b/include/hw/ppc/spapr_cpu_hotplug.h
@@ -0,0 +1,74 @@
+/*
+ * sPAPR CPU hotplug helpers.
+ *
+ * Declarations for functions that manage CPU device-tree generation,
+ * VSMT/VCPU-id mapping, CPU-slot lookup, machine initialisation, and
+ * the HotplugHandler / PPCVirtualHypervisor callbacks for sPAPR CPU cores.
+ *
+ * Copyright (c) 2010-2024 IBM Corporation.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_SPAPR_CPU_HOTPLUG_H
+#define HW_SPAPR_CPU_HOTPLUG_H
+
+#include "hw/ppc/spapr.h"
+#include "hw/ppc/spapr_drc.h"
+#include "target/ppc/cpu-qom.h"
+#include "hw/core/hotplug.h"
+
+/* ---- VCPU-id / VSMT helpers ------------------------------------------ */
+
+/*
+ * These two functions implement the VCPU-id numbering: one to compute
+ * them all, one to identify thread 0 of a vcore.  Any change to the
+ * first is likely to affect the second, so they live together.
+ */
+int  spapr_vcpu_id(SpaprMachineState *spapr, int cpu_index);
+bool spapr_is_thread0_in_vcore(SpaprMachineState *spapr, PowerPCCPU *cpu);
+
+/* ---- CPU-slot lookup ------------------------------------------------- */
+
+/* Find the CPUArchId slot in machine->possible_cpus by core_id. */
+CPUArchId *spapr_find_cpu_slot(MachineState *ms, uint32_t id, int *idx);
+
+/* ---- Machine-initialisation helpers ---------------------------------- */
+
+void spapr_set_vsmt_mode(SpaprMachineState *spapr, Error **errp);
+void spapr_init_cpus(SpaprMachineState *spapr);
+
+/* ---- Device-tree helpers --------------------------------------------- */
+
+/*
+ * spapr_dt_cpu() fills the FDT node at @offset for vCPU @cs, including
+ * ibm,my-drc-index, pa-features, interrupt-server#s, etc.  It is called
+ * both during boot-time FDT construction and from spapr_core_dt_populate()
+ * during CPU hotplug.
+ */
+void spapr_dt_cpu(CPUState *cs, void *fdt, int offset,
+                  SpaprMachineState *spapr);
+void spapr_dt_cpus(void *fdt, SpaprMachineState *spapr);
+
+/* ---- HotplugHandler callbacks ---------------------------------------- */
+
+void spapr_core_pre_plug(HotplugHandler *hotplug_dev, DeviceState *dev,
+                         Error **errp);
+void spapr_core_plug(HotplugHandler *hotplug_dev, DeviceState *dev);
+void spapr_core_unplug(HotplugHandler *hotplug_dev, DeviceState *dev);
+void spapr_core_unplug_request(HotplugHandler *hotplug_dev, DeviceState *dev,
+                               Error **errp);
+
+/* ---- Machine-class CPU-topology callbacks ----------------------------- */
+
+CpuInstanceProperties spapr_cpu_index_to_props(MachineState *machine,
+                                               unsigned cpu_index);
+int64_t spapr_get_default_cpu_node_id(const MachineState *ms, int idx);
+const CPUArchIdList *spapr_possible_cpu_arch_ids(MachineState *machine);
+
+/* ---- PPCVirtualHypervisor callbacks ---------------------------------- */
+
+bool spapr_cpu_in_nested(PowerPCCPU *cpu);
+void spapr_cpu_exec_enter(PPCVirtualHypervisor *vhyp, PowerPCCPU *cpu);
+void spapr_cpu_exec_exit(PPCVirtualHypervisor *vhyp, PowerPCCPU *cpu);
+
+#endif /* HW_SPAPR_CPU_HOTPLUG_H */
-- 
2.54.0



Reply via email to