Skip to content

Commit

Permalink
hw/acpi: Add ACPI Generic Event Device Support
Browse files Browse the repository at this point in the history
The ACPI Generic Event Device (GED) is a hardware-reduced specific
device[ACPI v6.1 Section 5.6.9] that handles all platform events,
including the hotplug ones. This patch generates the AML code that
defines GEDs.

Platforms need to specify their own GED Event bitmap to describe
what kind of events they want to support through GED.  Also this
uses a a single interrupt for the  GED device, relying on IO
memory region to communicate the type of device affected by the
interrupt. This way, we can support up to 32 events with a unique
interrupt.

This supports only memory hotplug for now.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
Signed-off-by: Shameer Kolothum <shameerali.kolothum.thodi@huawei.com>
Reviewed-by: Eric Auger <eric.auger@redhat.com>
Message-Id: <20190918130633.4872-4-shameerali.kolothum.thodi@huawei.com>
Acked-by: Peter Maydell <peter.maydell@linaro.org>
Reviewed-by: Michael S. Tsirkin <mst@redhat.com>
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
Reviewed-by: Igor Mammedov <imammedo@redhat.com>
  • Loading branch information
Samuel Ortiz authored and mstsirkin committed Oct 5, 2019
1 parent 22338fe commit ebb6207
Show file tree
Hide file tree
Showing 4 changed files with 408 additions and 0 deletions.
4 changes: 4 additions & 0 deletions hw/acpi/Kconfig
Expand Up @@ -31,3 +31,7 @@ config ACPI_VMGENID
bool
default y
depends on PC

config ACPI_HW_REDUCED
bool
depends on ACPI
1 change: 1 addition & 0 deletions hw/acpi/Makefile.objs
Expand Up @@ -6,6 +6,7 @@ common-obj-$(CONFIG_ACPI_MEMORY_HOTPLUG) += memory_hotplug.o
common-obj-$(CONFIG_ACPI_CPU_HOTPLUG) += cpu.o
common-obj-$(CONFIG_ACPI_NVDIMM) += nvdimm.o
common-obj-$(CONFIG_ACPI_VMGENID) += vmgenid.o
common-obj-$(CONFIG_ACPI_HW_REDUCED) += generic_event_device.o
common-obj-$(call lnot,$(CONFIG_ACPI_X86)) += acpi-stub.o

common-obj-y += acpi_interface.o
Expand Down
303 changes: 303 additions & 0 deletions hw/acpi/generic_event_device.c
@@ -0,0 +1,303 @@
/*
*
* Copyright (c) 2018 Intel Corporation
* Copyright (c) 2019 Huawei Technologies R & D (UK) Ltd
* Written by Samuel Ortiz, Shameer Kolothum
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2 or later, as published by the Free Software Foundation.
*/

#include "qemu/osdep.h"
#include "qapi/error.h"
#include "exec/address-spaces.h"
#include "hw/acpi/acpi.h"
#include "hw/acpi/generic_event_device.h"
#include "hw/irq.h"
#include "hw/mem/pc-dimm.h"
#include "hw/qdev-properties.h"
#include "migration/vmstate.h"
#include "qemu/error-report.h"

static const uint32_t ged_supported_events[] = {
ACPI_GED_MEM_HOTPLUG_EVT,
};

/*
* The ACPI Generic Event Device (GED) is a hardware-reduced specific
* device[ACPI v6.1 Section 5.6.9] that handles all platform events,
* including the hotplug ones. Platforms need to specify their own
* GED Event bitmap to describe what kind of events they want to support
* through GED. This routine uses a single interrupt for the GED device,
* relying on IO memory region to communicate the type of device
* affected by the interrupt. This way, we can support up to 32 events
* with a unique interrupt.
*/
void build_ged_aml(Aml *table, const char *name, HotplugHandler *hotplug_dev,
uint32_t ged_irq, AmlRegionSpace rs, hwaddr ged_base)
{
AcpiGedState *s = ACPI_GED(hotplug_dev);
Aml *crs = aml_resource_template();
Aml *evt, *field;
Aml *dev = aml_device("%s", name);
Aml *evt_sel = aml_local(0);
Aml *esel = aml_name(AML_GED_EVT_SEL);

/* _CRS interrupt */
aml_append(crs, aml_interrupt(AML_CONSUMER, AML_EDGE, AML_ACTIVE_HIGH,
AML_EXCLUSIVE, &ged_irq, 1));

aml_append(dev, aml_name_decl("_HID", aml_string("ACPI0013")));
aml_append(dev, aml_name_decl("_UID", aml_string(GED_DEVICE)));
aml_append(dev, aml_name_decl("_CRS", crs));

/* Append IO region */
aml_append(dev, aml_operation_region(AML_GED_EVT_REG, rs,
aml_int(ged_base + ACPI_GED_EVT_SEL_OFFSET),
ACPI_GED_EVT_SEL_LEN));
field = aml_field(AML_GED_EVT_REG, AML_DWORD_ACC, AML_NOLOCK,
AML_WRITE_AS_ZEROS);
aml_append(field, aml_named_field(AML_GED_EVT_SEL,
ACPI_GED_EVT_SEL_LEN * BITS_PER_BYTE));
aml_append(dev, field);

/*
* For each GED event we:
* - Add a conditional block for each event, inside a loop.
* - Call a method for each supported GED event type.
*
* The resulting ASL code looks like:
*
* Local0 = ESEL
* If ((Local0 & One) == One)
* {
* MethodEvent0()
* }
*
* If ((Local0 & 0x2) == 0x2)
* {
* MethodEvent1()
* }
* ...
*/
evt = aml_method("_EVT", 1, AML_SERIALIZED);
{
Aml *if_ctx;
uint32_t i;
uint32_t ged_events = ctpop32(s->ged_event_bitmap);

/* Local0 = ESEL */
aml_append(evt, aml_store(esel, evt_sel));

for (i = 0; i < ARRAY_SIZE(ged_supported_events) && ged_events; i++) {
uint32_t event = s->ged_event_bitmap & ged_supported_events[i];

if (!event) {
continue;
}

if_ctx = aml_if(aml_equal(aml_and(evt_sel, aml_int(event), NULL),
aml_int(event)));
switch (event) {
case ACPI_GED_MEM_HOTPLUG_EVT:
aml_append(if_ctx, aml_call0(MEMORY_DEVICES_CONTAINER "."
MEMORY_SLOT_SCAN_METHOD));
break;
default:
/*
* Please make sure all the events in ged_supported_events[]
* are handled above.
*/
g_assert_not_reached();
}

aml_append(evt, if_ctx);
ged_events--;
}

if (ged_events) {
error_report("Unsupported events specified");
abort();
}
}

/* Append _EVT method */
aml_append(dev, evt);

aml_append(table, dev);
}

/* Memory read by the GED _EVT AML dynamic method */
static uint64_t ged_read(void *opaque, hwaddr addr, unsigned size)
{
uint64_t val = 0;
GEDState *ged_st = opaque;

switch (addr) {
case ACPI_GED_EVT_SEL_OFFSET:
/* Read the selector value and reset it */
val = ged_st->sel;
ged_st->sel = 0;
break;
default:
break;
}

return val;
}

/* Nothing is expected to be written to the GED memory region */
static void ged_write(void *opaque, hwaddr addr, uint64_t data,
unsigned int size)
{
}

static const MemoryRegionOps ged_ops = {
.read = ged_read,
.write = ged_write,
.endianness = DEVICE_LITTLE_ENDIAN,
.valid = {
.min_access_size = 4,
.max_access_size = 4,
},
};

static void acpi_ged_device_plug_cb(HotplugHandler *hotplug_dev,
DeviceState *dev, Error **errp)
{
AcpiGedState *s = ACPI_GED(hotplug_dev);

if (object_dynamic_cast(OBJECT(dev), TYPE_PC_DIMM)) {
acpi_memory_plug_cb(hotplug_dev, &s->memhp_state, dev, errp);
} else {
error_setg(errp, "virt: device plug request for unsupported device"
" type: %s", object_get_typename(OBJECT(dev)));
}
}

static void acpi_ged_send_event(AcpiDeviceIf *adev, AcpiEventStatusBits ev)
{
AcpiGedState *s = ACPI_GED(adev);
GEDState *ged_st = &s->ged_state;
uint32_t sel;

if (ev & ACPI_MEMORY_HOTPLUG_STATUS) {
sel = ACPI_GED_MEM_HOTPLUG_EVT;
} else {
/* Unknown event. Return without generating interrupt. */
warn_report("GED: Unsupported event %d. No irq injected", ev);
return;
}

/*
* Set the GED selector field to communicate the event type.
* This will be read by GED aml code to select the appropriate
* event method.
*/
ged_st->sel |= sel;

/* Trigger the event by sending an interrupt to the guest. */
qemu_irq_pulse(s->irq);
}

static Property acpi_ged_properties[] = {
DEFINE_PROP_UINT32("ged-event", AcpiGedState, ged_event_bitmap, 0),
DEFINE_PROP_END_OF_LIST(),
};

static const VMStateDescription vmstate_memhp_state = {
.name = "acpi-ged/memhp",
.version_id = 1,
.minimum_version_id = 1,
.fields = (VMStateField[]) {
VMSTATE_MEMORY_HOTPLUG(memhp_state, AcpiGedState),
VMSTATE_END_OF_LIST()
}
};

static const VMStateDescription vmstate_ged_state = {
.name = "acpi-ged-state",
.version_id = 1,
.minimum_version_id = 1,
.fields = (VMStateField[]) {
VMSTATE_UINT32(sel, GEDState),
VMSTATE_END_OF_LIST()
}
};

static const VMStateDescription vmstate_acpi_ged = {
.name = "acpi-ged",
.version_id = 1,
.minimum_version_id = 1,
.fields = (VMStateField[]) {
VMSTATE_STRUCT(ged_state, AcpiGedState, 1, vmstate_ged_state, GEDState),
VMSTATE_END_OF_LIST(),
},
.subsections = (const VMStateDescription * []) {
&vmstate_memhp_state,
NULL
}
};

static void acpi_ged_initfn(Object *obj)
{
DeviceState *dev = DEVICE(obj);
AcpiGedState *s = ACPI_GED(dev);
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
GEDState *ged_st = &s->ged_state;

memory_region_init_io(&ged_st->io, obj, &ged_ops, ged_st,
TYPE_ACPI_GED, ACPI_GED_EVT_SEL_LEN);
sysbus_init_mmio(sbd, &ged_st->io);

sysbus_init_irq(sbd, &s->irq);

s->memhp_state.is_enabled = true;
/*
* GED handles memory hotplug event and acpi-mem-hotplug
* memory region gets initialized here. Create an exclusive
* container for memory hotplug IO and expose it as GED sysbus
* MMIO so that boards can map it separately.
*/
memory_region_init(&s->container_memhp, OBJECT(dev), "memhp container",
MEMORY_HOTPLUG_IO_LEN);
sysbus_init_mmio(sbd, &s->container_memhp);
acpi_memory_hotplug_init(&s->container_memhp, OBJECT(dev),
&s->memhp_state, 0);
}

static void acpi_ged_class_init(ObjectClass *class, void *data)
{
DeviceClass *dc = DEVICE_CLASS(class);
HotplugHandlerClass *hc = HOTPLUG_HANDLER_CLASS(class);
AcpiDeviceIfClass *adevc = ACPI_DEVICE_IF_CLASS(class);

dc->desc = "ACPI Generic Event Device";
dc->props = acpi_ged_properties;
dc->vmsd = &vmstate_acpi_ged;

hc->plug = acpi_ged_device_plug_cb;

adevc->send_event = acpi_ged_send_event;
}

static const TypeInfo acpi_ged_info = {
.name = TYPE_ACPI_GED,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(AcpiGedState),
.instance_init = acpi_ged_initfn,
.class_init = acpi_ged_class_init,
.interfaces = (InterfaceInfo[]) {
{ TYPE_HOTPLUG_HANDLER },
{ TYPE_ACPI_DEVICE_IF },
{ }
}
};

static void acpi_ged_register_types(void)
{
type_register_static(&acpi_ged_info);
}

type_init(acpi_ged_register_types)

0 comments on commit ebb6207

Please sign in to comment.