Add a hotplug device in machine virt and add cpu hotplug support using cpu-add.
Signed-off-by: Shannon Zhao <zhaoshengl...@huawei.com> --- hw/arm/virt.c | 159 +++++++++++++++++++++++++++++++++++++++- include/hw/acpi/virt-hotplug.h | 1 + 2 files changed, 159 insertions(+), 1 deletions(-) diff --git a/hw/arm/virt.c b/hw/arm/virt.c index f689bc3..760afbb 100644 --- a/hw/arm/virt.c +++ b/hw/arm/virt.c @@ -43,6 +43,8 @@ #include "qemu/bitops.h" #include "qemu/error-report.h" #include "hw/arm/virt-acpi-build.h" +#include "hw/acpi/virt-hotplug.h" +#include "hw/hotplug.h" #define NUM_VIRTIO_TRANSPORTS 32 @@ -76,6 +78,7 @@ enum { VIRT_RTC, VIRT_FW_CFG, VIRT_GPIO, + VIRT_CPU_HOTPLUG, }; typedef struct MemMapEntry { @@ -102,8 +105,11 @@ typedef struct { typedef struct { MachineState parent; bool secure; + HotplugHandler *acpi_dev; } VirtMachineState; +#define VIRT_MACHINE_ACPI_DEVICE_PROP "acpi-device" + #define TYPE_VIRT_MACHINE "virt" #define VIRT_MACHINE(obj) \ OBJECT_CHECK(VirtMachineState, (obj), TYPE_VIRT_MACHINE) @@ -135,6 +141,7 @@ static const MemMapEntry a15memmap[] = { [VIRT_RTC] = { 0x09010000, 0x00001000 }, [VIRT_FW_CFG] = { 0x09020000, 0x0000000a }, [VIRT_GPIO] = { 0x09500000, 0x00001000 }, + [VIRT_CPU_HOTPLUG] = { 0x09600000, 0x00000020 }, [VIRT_MMIO] = { 0x0a000000, 0x00000200 }, /* ...repeating for a total of NUM_VIRTIO_TRANSPORTS, each of that size */ /* 0x10000000 .. 0x40000000 reserved for PCI */ @@ -381,7 +388,7 @@ static void create_gic(const VirtBoardInfo *vbi, qemu_irq *pic) gicdev = qdev_create(NULL, gictype); qdev_prop_set_uint32(gicdev, "revision", 2); - qdev_prop_set_uint32(gicdev, "num-cpu", smp_cpus); + qdev_prop_set_uint32(gicdev, "num-cpu", max_cpus); /* Note that the num-irq property counts both internal and external * interrupts; there are always 32 of the former (mandated by GIC spec). */ @@ -630,6 +637,136 @@ void virt_guest_info_machine_done(Notifier *notifier, void *data) virt_acpi_setup(&guest_info_state->info); } +static void virt_new_cpu(const char *cpu_model, int64_t apic_id, + Error **errp) +{ + ObjectClass *oc = cpu_class_by_name(TYPE_ARM_CPU, cpu_model); + Object *cpuobj; + VirtBoardInfo *vbi; + SysBusDevice *gicbusdev; + + vbi = find_machine_info(cpu_model); + + if (!oc) { + fprintf(stderr, "Unable to find CPU definition\n"); + exit(1); + } + cpuobj = object_new(object_class_get_name(oc)); + + object_property_set_int(cpuobj, QEMU_PSCI_CONDUIT_HVC, "psci-conduit", + NULL); + + /* Secondary CPUs start in PSCI powered-down state */ + object_property_set_bool(cpuobj, true, "start-powered-off", NULL); + + if (object_property_find(cpuobj, "reset-cbar", NULL)) { + object_property_set_int(cpuobj, vbi->memmap[VIRT_CPUPERIPHS].base, + "reset-cbar", &error_abort); + } + + object_property_set_bool(cpuobj, true, "realized", NULL); + + const char *gictype = "arm_gic"; + if (kvm_irqchip_in_kernel()) { + gictype = "kvm-arm-gic"; + } + + bool ambig; + Object *o = object_resolve_path_type("", gictype, &ambig); + DeviceState *gicdev = DEVICE(o); + DeviceState *cpudev = DEVICE(cpuobj); + gicbusdev = SYS_BUS_DEVICE(gicdev); + int ppibase = NUM_IRQS + apic_id * 32; + /* physical timer; we wire it up to the non-secure timer's ID, + * since a real A15 always has TrustZone but QEMU doesn't. + */ + qdev_connect_gpio_out(cpudev, 0, + qdev_get_gpio_in(gicdev, ppibase + 30)); + /* virtual timer */ + qdev_connect_gpio_out(cpudev, 1, + qdev_get_gpio_in(gicdev, ppibase + 27)); + + sysbus_connect_irq(gicbusdev, apic_id, + qdev_get_gpio_in(cpudev, ARM_CPU_IRQ)); +} + +static const char *current_cpu_model; + +static void virt_hot_add_cpu(const int64_t id, Error **errp) +{ + int64_t apic_id = arm_cpu_apic_id_from_index(id); + if (id < 0) { + error_setg(errp, "Invalid CPU id: %" PRIi64, id); + return; + } + + if (cpu_exists(apic_id)) { + error_setg(errp, "Unable to add CPU: %" PRIi64 + ", it already exists", id); + return; + } + + if (id >= max_cpus) { + error_setg(errp, "Unable to add CPU: %" PRIi64 + ", max allowed: %d", id, max_cpus - 1); + return; + } + + if (apic_id >= VIRT_ACPI_CPU_HOTPLUG_ID_LIMIT) { + error_setg(errp, "Unable to add CPU: %" PRIi64 + ", resulting APIC ID (%" PRIi64 ") is too large", + id, apic_id); + return; + } + + virt_new_cpu(current_cpu_model, apic_id, errp); +} + +static void virt_cpu_plug(HotplugHandler *hotplug_dev, + DeviceState *dev, Error **errp) +{ + HotplugHandlerClass *hhc; + Error *local_err = NULL; + VirtMachineState *virtms = VIRT_MACHINE(hotplug_dev); + + if (!dev->hotplugged) { + goto out; + } + + if (!virtms->acpi_dev) { + error_setg(&local_err, + "cpu hotplug is not enabled: missing acpi device"); + goto out; + } + + hhc = HOTPLUG_HANDLER_GET_CLASS(virtms->acpi_dev); + hhc->plug(HOTPLUG_HANDLER(virtms->acpi_dev), dev, &local_err); + if (local_err) { + goto out; + } + +out: + error_propagate(errp, local_err); +} + +static void virt_machine_device_plug_cb(HotplugHandler *hotplug_dev, + DeviceState *dev, Error **errp) +{ + if (object_dynamic_cast(OBJECT(dev), TYPE_CPU)) { + virt_cpu_plug(hotplug_dev, dev, errp); + } +} + +static HotplugHandler *virt_get_hotpug_handler(MachineState *machine, + DeviceState *dev) +{ + if (object_dynamic_cast(OBJECT(dev), TYPE_CPU)) { + return HOTPLUG_HANDLER(machine); + } + + return NULL; +} + static void machvirt_init(MachineState *machine) { VirtMachineState *vms = VIRT_MACHINE(machine); @@ -641,10 +778,12 @@ static void machvirt_init(MachineState *machine) VirtBoardInfo *vbi; VirtGuestInfoState *guest_info_state = g_malloc0(sizeof *guest_info_state); VirtGuestInfo *guest_info = &guest_info_state->info; + DeviceState *virt_hotplug; if (!cpu_model) { cpu_model = "cortex-a15"; } + current_cpu_model = cpu_model; vbi = find_machine_info(cpu_model); @@ -710,6 +849,16 @@ static void machvirt_init(MachineState *machine) create_gpio(vbi, pic); + virt_hotplug_init(&virt_hotplug); + + object_property_add_link(OBJECT(machine), VIRT_MACHINE_ACPI_DEVICE_PROP, + TYPE_HOTPLUG_HANDLER, + (Object **)&vms->acpi_dev, + object_property_allow_set_link, + OBJ_PROP_LINK_UNREF_ON_RELEASE, &error_abort); + object_property_set_link(OBJECT(machine), OBJECT(virt_hotplug), + VIRT_MACHINE_ACPI_DEVICE_PROP, &error_abort); + /* Create mmio transports, so the user can create virtio backends * (which will be automatically plugged in to the transports). If * no backend is created the transport will just sit harmlessly idle. @@ -772,11 +921,15 @@ static void virt_instance_init(Object *obj) static void virt_class_init(ObjectClass *oc, void *data) { MachineClass *mc = MACHINE_CLASS(oc); + HotplugHandlerClass *hc = HOTPLUG_HANDLER_CLASS(oc); mc->name = TYPE_VIRT_MACHINE; mc->desc = "ARM Virtual Machine", mc->init = machvirt_init; + mc->hot_add_cpu = virt_hot_add_cpu, mc->max_cpus = 8; + mc->get_hotplug_handler = virt_get_hotpug_handler; + hc->plug = virt_machine_device_plug_cb; } static const TypeInfo machvirt_info = { @@ -786,6 +939,10 @@ static const TypeInfo machvirt_info = { .instance_init = virt_instance_init, .class_size = sizeof(VirtMachineClass), .class_init = virt_class_init, + .interfaces = (InterfaceInfo[]) { + { TYPE_HOTPLUG_HANDLER }, + { } + }, }; static void machvirt_machine_init(void) diff --git a/include/hw/acpi/virt-hotplug.h b/include/hw/acpi/virt-hotplug.h index a668d16..8f94235 100644 --- a/include/hw/acpi/virt-hotplug.h +++ b/include/hw/acpi/virt-hotplug.h @@ -3,6 +3,7 @@ #include "qemu/typedefs.h" +#define VIRT_ACPI_CPU_HOTPLUG_ID_LIMIT 256 #define VIRT_CPU_HOTPLUG_MMIO_BASE 0x09600000 void virt_hotplug_init(DeviceState **virt_hotplug); -- 1.7.1