During CPU core realization, we create all the thread objects and parent them to the core object in a loop. However, the realization of thread objects is done separately by walking the threads of a core using object_child_foreach(). With this, there is no guarantee on the order in which the child thread objects get realized. Since CPU device tree properties are currently derived from the CPU thread object, we assume thread0 of the core to be the representative thread of the core when creating device tree properties for the core. If thread0 is not the first thread that gets realized, then we would end up having an incorrect dt_id for the core and this causes hotplug failures from the guest. Fix this by realizing each thread object by walking the core's thread object list thereby ensuring that thread0 and other threads are always realized in the correct order. Future TODO: CPU DT nodes are per-core properties and we should ideally base the creation of CPU DT nodes on core objects rather than the thread objects. Signed-off-by: Bharata B Rao <bharata@linux.vnet.ibm.com> Reviewed-by: Greg Kurz <groug@kaod.org> Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
433 lines
13 KiB
C
433 lines
13 KiB
C
/*
|
|
* sPAPR CPU core device, acts as container of CPU thread devices.
|
|
*
|
|
* Copyright (C) 2016 Bharata B Rao <bharata@linux.vnet.ibm.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 "hw/cpu/core.h"
|
|
#include "hw/ppc/spapr_cpu_core.h"
|
|
#include "target-ppc/cpu.h"
|
|
#include "hw/ppc/spapr.h"
|
|
#include "hw/boards.h"
|
|
#include "qapi/error.h"
|
|
#include <sysemu/cpus.h>
|
|
#include "target-ppc/kvm_ppc.h"
|
|
#include "hw/ppc/ppc.h"
|
|
#include "target-ppc/mmu-hash64.h"
|
|
#include <sysemu/numa.h>
|
|
|
|
static void spapr_cpu_reset(void *opaque)
|
|
{
|
|
sPAPRMachineState *spapr = SPAPR_MACHINE(qdev_get_machine());
|
|
PowerPCCPU *cpu = opaque;
|
|
CPUState *cs = CPU(cpu);
|
|
CPUPPCState *env = &cpu->env;
|
|
|
|
cpu_reset(cs);
|
|
|
|
/* All CPUs start halted. CPU0 is unhalted from the machine level
|
|
* reset code and the rest are explicitly started up by the guest
|
|
* using an RTAS call */
|
|
cs->halted = 1;
|
|
|
|
env->spr[SPR_HIOR] = 0;
|
|
|
|
ppc_hash64_set_external_hpt(cpu, spapr->htab, spapr->htab_shift,
|
|
&error_fatal);
|
|
}
|
|
|
|
static void spapr_cpu_destroy(PowerPCCPU *cpu)
|
|
{
|
|
sPAPRMachineState *spapr = SPAPR_MACHINE(qdev_get_machine());
|
|
|
|
xics_cpu_destroy(spapr->xics, cpu);
|
|
qemu_unregister_reset(spapr_cpu_reset, cpu);
|
|
}
|
|
|
|
void spapr_cpu_init(sPAPRMachineState *spapr, PowerPCCPU *cpu, Error **errp)
|
|
{
|
|
CPUPPCState *env = &cpu->env;
|
|
CPUState *cs = CPU(cpu);
|
|
int i;
|
|
|
|
/* Set time-base frequency to 512 MHz */
|
|
cpu_ppc_tb_init(env, SPAPR_TIMEBASE_FREQ);
|
|
|
|
/* Enable PAPR mode in TCG or KVM */
|
|
cpu_ppc_set_papr(cpu);
|
|
|
|
if (cpu->max_compat) {
|
|
Error *local_err = NULL;
|
|
|
|
ppc_set_compat(cpu, cpu->max_compat, &local_err);
|
|
if (local_err) {
|
|
error_propagate(errp, local_err);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Set NUMA node for the added CPUs */
|
|
for (i = 0; i < nb_numa_nodes; i++) {
|
|
if (test_bit(cs->cpu_index, numa_info[i].node_cpu)) {
|
|
cs->numa_node = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
xics_cpu_setup(spapr->xics, cpu);
|
|
|
|
qemu_register_reset(spapr_cpu_reset, cpu);
|
|
spapr_cpu_reset(cpu);
|
|
}
|
|
|
|
/*
|
|
* Return the sPAPR CPU core type for @model which essentially is the CPU
|
|
* model specified with -cpu cmdline option.
|
|
*/
|
|
char *spapr_get_cpu_core_type(const char *model)
|
|
{
|
|
char *core_type;
|
|
gchar **model_pieces = g_strsplit(model, ",", 2);
|
|
|
|
core_type = g_strdup_printf("%s-%s", model_pieces[0], TYPE_SPAPR_CPU_CORE);
|
|
g_strfreev(model_pieces);
|
|
return core_type;
|
|
}
|
|
|
|
static void spapr_core_release(DeviceState *dev, void *opaque)
|
|
{
|
|
sPAPRCPUCore *sc = SPAPR_CPU_CORE(OBJECT(dev));
|
|
const char *typename = object_class_get_name(sc->cpu_class);
|
|
size_t size = object_type_get_instance_size(typename);
|
|
sPAPRMachineState *spapr = SPAPR_MACHINE(qdev_get_machine());
|
|
CPUCore *cc = CPU_CORE(dev);
|
|
int smt = kvmppc_smt_threads();
|
|
int i;
|
|
|
|
for (i = 0; i < cc->nr_threads; i++) {
|
|
void *obj = sc->threads + i * size;
|
|
DeviceState *dev = DEVICE(obj);
|
|
CPUState *cs = CPU(dev);
|
|
PowerPCCPU *cpu = POWERPC_CPU(cs);
|
|
|
|
spapr_cpu_destroy(cpu);
|
|
cpu_remove_sync(cs);
|
|
object_unparent(obj);
|
|
}
|
|
|
|
spapr->cores[cc->core_id / smt] = NULL;
|
|
|
|
g_free(sc->threads);
|
|
object_unparent(OBJECT(dev));
|
|
}
|
|
|
|
void spapr_core_unplug(HotplugHandler *hotplug_dev, DeviceState *dev,
|
|
Error **errp)
|
|
{
|
|
sPAPRCPUCore *core = SPAPR_CPU_CORE(OBJECT(dev));
|
|
PowerPCCPU *cpu = POWERPC_CPU(core->threads);
|
|
int id = ppc_get_vcpu_dt_id(cpu);
|
|
sPAPRDRConnector *drc =
|
|
spapr_dr_connector_by_id(SPAPR_DR_CONNECTOR_TYPE_CPU, id);
|
|
sPAPRDRConnectorClass *drck;
|
|
Error *local_err = NULL;
|
|
|
|
g_assert(drc);
|
|
|
|
drck = SPAPR_DR_CONNECTOR_GET_CLASS(drc);
|
|
drck->detach(drc, dev, spapr_core_release, NULL, &local_err);
|
|
if (local_err) {
|
|
error_propagate(errp, local_err);
|
|
return;
|
|
}
|
|
|
|
spapr_hotplug_req_remove_by_index(drc);
|
|
}
|
|
|
|
void spapr_core_plug(HotplugHandler *hotplug_dev, DeviceState *dev,
|
|
Error **errp)
|
|
{
|
|
sPAPRMachineClass *smc = SPAPR_MACHINE_GET_CLASS(OBJECT(hotplug_dev));
|
|
sPAPRMachineState *spapr = SPAPR_MACHINE(OBJECT(hotplug_dev));
|
|
sPAPRCPUCore *core = SPAPR_CPU_CORE(OBJECT(dev));
|
|
CPUCore *cc = CPU_CORE(dev);
|
|
CPUState *cs = CPU(core->threads);
|
|
sPAPRDRConnector *drc;
|
|
sPAPRDRConnectorClass *drck;
|
|
Error *local_err = NULL;
|
|
void *fdt = NULL;
|
|
int fdt_offset = 0;
|
|
int index;
|
|
int smt = kvmppc_smt_threads();
|
|
|
|
drc = spapr_dr_connector_by_id(SPAPR_DR_CONNECTOR_TYPE_CPU, cc->core_id);
|
|
index = cc->core_id / smt;
|
|
spapr->cores[index] = OBJECT(dev);
|
|
|
|
if (!smc->dr_cpu_enabled) {
|
|
/*
|
|
* This is a cold plugged CPU core but the machine doesn't support
|
|
* DR. So skip the hotplug path ensuring that the core is brought
|
|
* up online with out an associated DR connector.
|
|
*/
|
|
return;
|
|
}
|
|
|
|
g_assert(drc);
|
|
|
|
/*
|
|
* Setup CPU DT entries only for hotplugged CPUs. For boot time or
|
|
* coldplugged CPUs DT entries are setup in spapr_finalize_fdt().
|
|
*/
|
|
if (dev->hotplugged) {
|
|
fdt = spapr_populate_hotplug_cpu_dt(cs, &fdt_offset, spapr);
|
|
}
|
|
|
|
drck = SPAPR_DR_CONNECTOR_GET_CLASS(drc);
|
|
drck->attach(drc, dev, fdt, fdt_offset, !dev->hotplugged, &local_err);
|
|
if (local_err) {
|
|
g_free(fdt);
|
|
spapr->cores[index] = NULL;
|
|
error_propagate(errp, local_err);
|
|
return;
|
|
}
|
|
|
|
if (dev->hotplugged) {
|
|
/*
|
|
* Send hotplug notification interrupt to the guest only in case
|
|
* of hotplugged CPUs.
|
|
*/
|
|
spapr_hotplug_req_add_by_index(drc);
|
|
} else {
|
|
/*
|
|
* Set the right DRC states for cold plugged CPU.
|
|
*/
|
|
drck->set_allocation_state(drc, SPAPR_DR_ALLOCATION_STATE_USABLE);
|
|
drck->set_isolation_state(drc, SPAPR_DR_ISOLATION_STATE_UNISOLATED);
|
|
}
|
|
}
|
|
|
|
void spapr_core_pre_plug(HotplugHandler *hotplug_dev, DeviceState *dev,
|
|
Error **errp)
|
|
{
|
|
MachineState *machine = MACHINE(OBJECT(hotplug_dev));
|
|
sPAPRMachineClass *smc = SPAPR_MACHINE_GET_CLASS(OBJECT(hotplug_dev));
|
|
sPAPRMachineState *spapr = SPAPR_MACHINE(OBJECT(hotplug_dev));
|
|
int spapr_max_cores = max_cpus / smp_threads;
|
|
int index;
|
|
int smt = kvmppc_smt_threads();
|
|
Error *local_err = NULL;
|
|
CPUCore *cc = CPU_CORE(dev);
|
|
char *base_core_type = spapr_get_cpu_core_type(machine->cpu_model);
|
|
const char *type = object_get_typename(OBJECT(dev));
|
|
|
|
if (strcmp(base_core_type, type)) {
|
|
error_setg(&local_err, "CPU core type should be %s", base_core_type);
|
|
goto out;
|
|
}
|
|
|
|
if (!smc->dr_cpu_enabled && dev->hotplugged) {
|
|
error_setg(&local_err, "CPU hotplug not supported for this machine");
|
|
goto out;
|
|
}
|
|
|
|
if (cc->nr_threads != smp_threads) {
|
|
error_setg(&local_err, "threads must be %d", smp_threads);
|
|
goto out;
|
|
}
|
|
|
|
if (cc->core_id % smt) {
|
|
error_setg(&local_err, "invalid core id %d\n", cc->core_id);
|
|
goto out;
|
|
}
|
|
|
|
index = cc->core_id / smt;
|
|
if (index < 0 || index >= spapr_max_cores) {
|
|
error_setg(&local_err, "core id %d out of range", cc->core_id);
|
|
goto out;
|
|
}
|
|
|
|
if (spapr->cores[index]) {
|
|
error_setg(&local_err, "core %d already populated", cc->core_id);
|
|
goto out;
|
|
}
|
|
|
|
out:
|
|
g_free(base_core_type);
|
|
error_propagate(errp, local_err);
|
|
}
|
|
|
|
static void spapr_cpu_core_realize_child(Object *child, Error **errp)
|
|
{
|
|
Error *local_err = NULL;
|
|
sPAPRMachineState *spapr = SPAPR_MACHINE(qdev_get_machine());
|
|
CPUState *cs = CPU(child);
|
|
PowerPCCPU *cpu = POWERPC_CPU(cs);
|
|
|
|
object_property_set_bool(child, true, "realized", &local_err);
|
|
if (local_err) {
|
|
error_propagate(errp, local_err);
|
|
return;
|
|
}
|
|
|
|
spapr_cpu_init(spapr, cpu, &local_err);
|
|
if (local_err) {
|
|
error_propagate(errp, local_err);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void spapr_cpu_core_realize(DeviceState *dev, Error **errp)
|
|
{
|
|
sPAPRCPUCore *sc = SPAPR_CPU_CORE(OBJECT(dev));
|
|
CPUCore *cc = CPU_CORE(OBJECT(dev));
|
|
const char *typename = object_class_get_name(sc->cpu_class);
|
|
size_t size = object_type_get_instance_size(typename);
|
|
Error *local_err = NULL;
|
|
void *obj;
|
|
int i, j;
|
|
|
|
sc->threads = g_malloc0(size * cc->nr_threads);
|
|
for (i = 0; i < cc->nr_threads; i++) {
|
|
char id[32];
|
|
obj = sc->threads + i * size;
|
|
|
|
object_initialize(obj, size, typename);
|
|
snprintf(id, sizeof(id), "thread[%d]", i);
|
|
object_property_add_child(OBJECT(sc), id, obj, &local_err);
|
|
if (local_err) {
|
|
goto err;
|
|
}
|
|
object_unref(obj);
|
|
}
|
|
|
|
for (j = 0; j < cc->nr_threads; j++) {
|
|
obj = sc->threads + j * size;
|
|
|
|
spapr_cpu_core_realize_child(obj, &local_err);
|
|
if (local_err) {
|
|
goto err;
|
|
}
|
|
}
|
|
return;
|
|
|
|
err:
|
|
while (--i >= 0) {
|
|
obj = sc->threads + i * size;
|
|
object_unparent(obj);
|
|
}
|
|
g_free(sc->threads);
|
|
error_propagate(errp, local_err);
|
|
}
|
|
|
|
static void spapr_cpu_core_class_init(ObjectClass *oc, void *data)
|
|
{
|
|
DeviceClass *dc = DEVICE_CLASS(oc);
|
|
dc->realize = spapr_cpu_core_realize;
|
|
}
|
|
|
|
/*
|
|
* instance_init routines from different flavours of sPAPR CPU cores.
|
|
*/
|
|
#define SPAPR_CPU_CORE_INITFN(_type, _fname) \
|
|
static void glue(glue(spapr_cpu_core_, _fname), _initfn(Object *obj)) \
|
|
{ \
|
|
sPAPRCPUCore *core = SPAPR_CPU_CORE(obj); \
|
|
char *name = g_strdup_printf("%s-" TYPE_POWERPC_CPU, stringify(_type)); \
|
|
ObjectClass *oc = object_class_by_name(name); \
|
|
g_assert(oc); \
|
|
g_free((void *)name); \
|
|
core->cpu_class = oc; \
|
|
}
|
|
|
|
SPAPR_CPU_CORE_INITFN(970mp_v1.0, 970MP_v10);
|
|
SPAPR_CPU_CORE_INITFN(970mp_v1.1, 970MP_v11);
|
|
SPAPR_CPU_CORE_INITFN(970_v2.2, 970);
|
|
SPAPR_CPU_CORE_INITFN(POWER5+_v2.1, POWER5plus);
|
|
SPAPR_CPU_CORE_INITFN(POWER7_v2.3, POWER7);
|
|
SPAPR_CPU_CORE_INITFN(POWER7+_v2.1, POWER7plus);
|
|
SPAPR_CPU_CORE_INITFN(POWER8_v2.0, POWER8);
|
|
SPAPR_CPU_CORE_INITFN(POWER8E_v2.1, POWER8E);
|
|
SPAPR_CPU_CORE_INITFN(POWER8NVL_v1.0, POWER8NVL);
|
|
|
|
typedef struct SPAPRCoreInfo {
|
|
const char *name;
|
|
void (*initfn)(Object *obj);
|
|
} SPAPRCoreInfo;
|
|
|
|
static const SPAPRCoreInfo spapr_cores[] = {
|
|
/* 970 and aliaes */
|
|
{ .name = "970_v2.2", .initfn = spapr_cpu_core_970_initfn },
|
|
{ .name = "970", .initfn = spapr_cpu_core_970_initfn },
|
|
|
|
/* 970MP variants and aliases */
|
|
{ .name = "970MP_v1.0", .initfn = spapr_cpu_core_970MP_v10_initfn },
|
|
{ .name = "970mp_v1.0", .initfn = spapr_cpu_core_970MP_v10_initfn },
|
|
{ .name = "970MP_v1.1", .initfn = spapr_cpu_core_970MP_v11_initfn },
|
|
{ .name = "970mp_v1.1", .initfn = spapr_cpu_core_970MP_v11_initfn },
|
|
{ .name = "970mp", .initfn = spapr_cpu_core_970MP_v11_initfn },
|
|
|
|
/* POWER5 and aliases */
|
|
{ .name = "POWER5+_v2.1", .initfn = spapr_cpu_core_POWER5plus_initfn },
|
|
{ .name = "POWER5+", .initfn = spapr_cpu_core_POWER5plus_initfn },
|
|
|
|
/* POWER7 and aliases */
|
|
{ .name = "POWER7_v2.3", .initfn = spapr_cpu_core_POWER7_initfn },
|
|
{ .name = "POWER7", .initfn = spapr_cpu_core_POWER7_initfn },
|
|
|
|
/* POWER7+ and aliases */
|
|
{ .name = "POWER7+_v2.1", .initfn = spapr_cpu_core_POWER7plus_initfn },
|
|
{ .name = "POWER7+", .initfn = spapr_cpu_core_POWER7plus_initfn },
|
|
|
|
/* POWER8 and aliases */
|
|
{ .name = "POWER8_v2.0", .initfn = spapr_cpu_core_POWER8_initfn },
|
|
{ .name = "POWER8", .initfn = spapr_cpu_core_POWER8_initfn },
|
|
{ .name = "power8", .initfn = spapr_cpu_core_POWER8_initfn },
|
|
|
|
/* POWER8E and aliases */
|
|
{ .name = "POWER8E_v2.1", .initfn = spapr_cpu_core_POWER8E_initfn },
|
|
{ .name = "POWER8E", .initfn = spapr_cpu_core_POWER8E_initfn },
|
|
|
|
/* POWER8NVL and aliases */
|
|
{ .name = "POWER8NVL_v1.0", .initfn = spapr_cpu_core_POWER8NVL_initfn },
|
|
{ .name = "POWER8NVL", .initfn = spapr_cpu_core_POWER8NVL_initfn },
|
|
|
|
{ .name = NULL }
|
|
};
|
|
|
|
static void spapr_cpu_core_register(const SPAPRCoreInfo *info)
|
|
{
|
|
TypeInfo type_info = {
|
|
.parent = TYPE_SPAPR_CPU_CORE,
|
|
.instance_size = sizeof(sPAPRCPUCore),
|
|
.instance_init = info->initfn,
|
|
};
|
|
|
|
type_info.name = g_strdup_printf("%s-" TYPE_SPAPR_CPU_CORE, info->name);
|
|
type_register(&type_info);
|
|
g_free((void *)type_info.name);
|
|
}
|
|
|
|
static const TypeInfo spapr_cpu_core_type_info = {
|
|
.name = TYPE_SPAPR_CPU_CORE,
|
|
.parent = TYPE_CPU_CORE,
|
|
.abstract = true,
|
|
.instance_size = sizeof(sPAPRCPUCore),
|
|
.class_init = spapr_cpu_core_class_init,
|
|
};
|
|
|
|
static void spapr_cpu_core_register_types(void)
|
|
{
|
|
const SPAPRCoreInfo *info = spapr_cores;
|
|
|
|
type_register_static(&spapr_cpu_core_type_info);
|
|
while (info->name) {
|
|
spapr_cpu_core_register(info);
|
|
info++;
|
|
}
|
|
}
|
|
|
|
type_init(spapr_cpu_core_register_types)
|