A 32-byte register is used to present up to 256 hotplug-able memory devices to BIOS and OSPM. Hot-add and hot-remove functions trigger an ACPI hotplug event through these. Only reads are allowed from these registers.
An ACPI hot-remove event but needs to wait for OSPM to eject the device. We use a single-byte register to know when OSPM has called the _EJ function for a particular dimm. A write to this byte will depopulate the respective dimm. Only writes are allowed to this byte.
v1->v2: mems_sts address moved from 0xaf20 to 0xaf80 (to accomodate more space for cpu-hotplugging in the future). _EJ array is reduced to a single byte. Add documentation in docs/specs/acpi_hotplug.txt
v3->v4: Removed hot-remove functions, will be added separately. Updated for memory API.
Signed-off-by: Vasilis Liaskovitis vasilis.liaskovitis@profitbricks.com --- docs/specs/acpi_hotplug.txt | 14 +++++++++ hw/acpi.h | 5 +++ hw/acpi_piix4.c | 65 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 docs/specs/acpi_hotplug.txt
diff --git a/docs/specs/acpi_hotplug.txt b/docs/specs/acpi_hotplug.txt new file mode 100644 index 0000000..8391713 --- /dev/null +++ b/docs/specs/acpi_hotplug.txt @@ -0,0 +1,14 @@ +QEMU<->ACPI BIOS hotplug interface +-------------------------------------- +This document describes the interface between QEMU and the ACPI BIOS for non-PCI +space. For the PCI interface please look at docs/specs/acpi_pci_hotplug.txt + +QEMU<->ACPI BIOS memory hotplug interface +-------------------------------------- + +Memory Dimm status array (IO port 0xaf80-0xaf9f, 1-byte access): +--------------------------------------------------------------- +Dimm hot-plug notification pending. One bit per slot. + +Read by ACPI BIOS GPE.3 handler to notify OS of memory hot-add or hot-remove +events. Read-only. diff --git a/hw/acpi.h b/hw/acpi.h index afda153..dc617d3 100644 --- a/hw/acpi.h +++ b/hw/acpi.h @@ -120,6 +120,11 @@ struct ACPIREGS { Notifier wakeup; };
+#include "dimm.h" +struct gpe_regs { + uint8_t mems_sts[DIMM_BITMAP_BYTES]; +}; + /* PM_TMR */ void acpi_pm_tmr_update(ACPIREGS *ar, bool enable); void acpi_pm_tmr_calc_overflow_time(ACPIREGS *ar); diff --git a/hw/acpi_piix4.c b/hw/acpi_piix4.c index 0b5b0d3..879d8a0 100644 --- a/hw/acpi_piix4.c +++ b/hw/acpi_piix4.c @@ -29,6 +29,8 @@ #include "ioport.h" #include "fw_cfg.h" #include "exec-memory.h" +#include "sysbus.h" +#include "dimm.h"
//#define DEBUG
@@ -47,7 +49,9 @@ #define PCI_DOWN_BASE 0xae04 #define PCI_EJ_BASE 0xae08 #define PCI_RMV_BASE 0xae0c +#define MEM_BASE 0xaf80
+#define PIIX4_MEM_HOTPLUG_STATUS 8 #define PIIX4_PCI_HOTPLUG_STATUS 2
struct pci_status { @@ -60,6 +64,7 @@ typedef struct PIIX4PMState { MemoryRegion io; MemoryRegion io_gpe; MemoryRegion io_pci; + MemoryRegion io_memhp; ACPIREGS ar;
APMState apm; @@ -74,6 +79,7 @@ typedef struct PIIX4PMState { Notifier powerdown_notifier;
/* for pci hotplug */ + struct gpe_regs gperegs; struct pci_status pci0_status; uint32_t pci0_hotplug_enable; uint32_t pci0_slot_device_present; @@ -98,8 +104,8 @@ static void pm_update_sci(PIIX4PMState *s) ACPI_BITMASK_POWER_BUTTON_ENABLE | ACPI_BITMASK_GLOBAL_LOCK_ENABLE | ACPI_BITMASK_TIMER_ENABLE)) != 0) || - (((s->ar.gpe.sts[0] & s->ar.gpe.en[0]) - & PIIX4_PCI_HOTPLUG_STATUS) != 0); + (((s->ar.gpe.sts[0] & s->ar.gpe.en[0]) & + (PIIX4_PCI_HOTPLUG_STATUS | PIIX4_MEM_HOTPLUG_STATUS)) != 0);
qemu_set_irq(s->irq, sci_level); /* schedule a timer interruption if needed */ @@ -526,6 +532,29 @@ static const MemoryRegionOps piix4_gpe_ops = { .endianness = DEVICE_LITTLE_ENDIAN, };
+static uint32_t memhp_readb(void *opaque, uint32_t addr) +{ + PIIX4PMState *s = opaque; + uint32_t val = 0; + struct gpe_regs *g = &s->gperegs; + if (addr < DIMM_BITMAP_BYTES) { + val = (uint32_t) g->mems_sts[addr]; + } + PIIX4_DPRINTF(stderr, "memhp read %x == %x\n", addr, val); + return val; +} + +static const MemoryRegionOps piix4_memhp_ops = { + .old_portio = (MemoryRegionPortio[]) { + { + .offset = 0, .len = DIMM_BITMAP_BYTES, .size = 1, + .read = memhp_readb, + }, + PORTIO_END_OF_LIST() + }, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + static uint32_t pci_up_read(void *opaque, uint32_t addr) { PIIX4PMState *s = opaque; @@ -592,9 +621,11 @@ static const MemoryRegionOps piix4_pci_ops = {
static int piix4_device_hotplug(DeviceState *qdev, PCIDevice *dev, PCIHotplugState state); +static int piix4_dimm_hotplug(DeviceState *qdev, DimmDevice *dev, int add);
static void piix4_acpi_system_hot_add_init(PCIBus *bus, PIIX4PMState *s) { + int i = 0; memory_region_init_io(&s->io_gpe, &piix4_gpe_ops, s, "apci-gpe0", GPE_LEN); memory_region_add_subregion(get_system_io(), GPE_BASE, &s->io_gpe); @@ -603,7 +634,16 @@ static void piix4_acpi_system_hot_add_init(PCIBus *bus, PIIX4PMState *s) PCI_HOTPLUG_SIZE); memory_region_add_subregion(get_system_io(), PCI_HOTPLUG_ADDR, &s->io_pci); + memory_region_init_io(&s->io_memhp, &piix4_memhp_ops, s, "apci-memhp0", + DIMM_BITMAP_BYTES); + memory_region_add_subregion(get_system_io(), MEM_BASE, &s->io_memhp); + + for (i = 0; i < DIMM_BITMAP_BYTES; i++) { + s->gperegs.mems_sts[i] = 0; + } + pci_bus_hotplug(bus, piix4_device_hotplug, &s->dev.qdev); + dimm_bus_hotplug(piix4_dimm_hotplug, &s->dev.qdev); }
static void enable_device(PIIX4PMState *s, int slot) @@ -618,6 +658,27 @@ static void disable_device(PIIX4PMState *s, int slot) s->pci0_status.down |= (1U << slot); }
+static void enable_mem_device(PIIX4PMState *s, int memdevice) +{ + struct gpe_regs *g = &s->gperegs; + s->ar.gpe.sts[0] |= PIIX4_MEM_HOTPLUG_STATUS; + g->mems_sts[memdevice/8] |= (1 << (memdevice%8)); +} + +static int piix4_dimm_hotplug(DeviceState *qdev, DimmDevice *dev, int + add) +{ + PCIDevice *pci_dev = DO_UPCAST(PCIDevice, qdev, qdev); + PIIX4PMState *s = DO_UPCAST(PIIX4PMState, dev, pci_dev); + DimmDevice *slot = DIMM(dev); + + if (add) { + enable_mem_device(s, slot->idx); + } + pm_update_sci(s); + return 0; +} + static int piix4_device_hotplug(DeviceState *qdev, PCIDevice *dev, PCIHotplugState state) {