759 lines
24 KiB
Diff
759 lines
24 KiB
Diff
|
|
From 1fc8fa6cd621c17988b043c1b3abe9ccb189a1d7 Mon Sep 17 00:00:00 2001
|
||
|
|
From: lixianglai <lixianglai@loongson.cn>
|
||
|
|
Date: Tue, 7 Feb 2023 06:34:32 -0500
|
||
|
|
Subject: [PATCH] Add PowerManager support.
|
||
|
|
|
||
|
|
Add Loongarch ACPI power management device simulation.
|
||
|
|
|
||
|
|
Signed-off-by: lixianglai <lixianglai@loongson.cn>
|
||
|
|
---
|
||
|
|
hw/acpi/Kconfig | 8 +
|
||
|
|
hw/acpi/larch_7a.c | 616 +++++++++++++++++++++++++++++++++++++++++
|
||
|
|
hw/acpi/meson.build | 1 +
|
||
|
|
include/hw/acpi/ls7a.h | 79 ++++++
|
||
|
|
4 files changed, 704 insertions(+)
|
||
|
|
create mode 100644 hw/acpi/larch_7a.c
|
||
|
|
create mode 100644 include/hw/acpi/ls7a.h
|
||
|
|
|
||
|
|
diff --git a/hw/acpi/Kconfig b/hw/acpi/Kconfig
|
||
|
|
index 622b0b50b7..245c5554df 100644
|
||
|
|
--- a/hw/acpi/Kconfig
|
||
|
|
+++ b/hw/acpi/Kconfig
|
||
|
|
@@ -15,6 +15,14 @@ config ACPI_X86_ICH
|
||
|
|
bool
|
||
|
|
select ACPI_X86
|
||
|
|
|
||
|
|
+config ACPI_LOONGARCH
|
||
|
|
+ bool
|
||
|
|
+ select ACPI
|
||
|
|
+ select ACPI_CPU_HOTPLUG
|
||
|
|
+ select ACPI_MEMORY_HOTPLUG
|
||
|
|
+ select ACPI_PIIX4
|
||
|
|
+ select ACPI_PCIHP
|
||
|
|
+
|
||
|
|
config ACPI_CPU_HOTPLUG
|
||
|
|
bool
|
||
|
|
|
||
|
|
diff --git a/hw/acpi/larch_7a.c b/hw/acpi/larch_7a.c
|
||
|
|
new file mode 100644
|
||
|
|
index 0000000000..59b43170ff
|
||
|
|
--- /dev/null
|
||
|
|
+++ b/hw/acpi/larch_7a.c
|
||
|
|
@@ -0,0 +1,616 @@
|
||
|
|
+/*
|
||
|
|
+ * Loongarch acpi emulation
|
||
|
|
+ *
|
||
|
|
+ * Copyright (c) 2023 Loongarch Technology
|
||
|
|
+ *
|
||
|
|
+ * 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.
|
||
|
|
+ *
|
||
|
|
+ * This program is distributed in the hope it will be useful, but WITHOUT
|
||
|
|
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||
|
|
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||
|
|
+ * more details.
|
||
|
|
+ *
|
||
|
|
+ * You should have received a copy of the GNU General Public License along with
|
||
|
|
+ * this program. If not, see <http://www.gnu.org/licenses/>.
|
||
|
|
+ */
|
||
|
|
+
|
||
|
|
+#include "qemu/osdep.h"
|
||
|
|
+#include "sysemu/sysemu.h"
|
||
|
|
+#include "sysemu/runstate.h"
|
||
|
|
+#include "sysemu/reset.h"
|
||
|
|
+#include "hw/hw.h"
|
||
|
|
+#include "hw/irq.h"
|
||
|
|
+#include "hw/acpi/acpi.h"
|
||
|
|
+#include "hw/acpi/ls7a.h"
|
||
|
|
+#include "hw/nvram/fw_cfg.h"
|
||
|
|
+#include "qemu/config-file.h"
|
||
|
|
+#include "qapi/opts-visitor.h"
|
||
|
|
+#include "qapi/qapi-events-run-state.h"
|
||
|
|
+#include "qapi/error.h"
|
||
|
|
+#include "hw/loongarch/ls7a.h"
|
||
|
|
+#include "hw/mem/pc-dimm.h"
|
||
|
|
+#include "hw/mem/nvdimm.h"
|
||
|
|
+#include "migration/vmstate.h"
|
||
|
|
+
|
||
|
|
+static void ls7a_pm_update_sci_fn(ACPIREGS *regs)
|
||
|
|
+{
|
||
|
|
+ LS7APCIPMRegs *pm = container_of(regs, LS7APCIPMRegs, acpi_regs);
|
||
|
|
+ acpi_update_sci(&pm->acpi_regs, pm->irq);
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static uint64_t ls7a_gpe_readb(void *opaque, hwaddr addr, unsigned width)
|
||
|
|
+{
|
||
|
|
+ LS7APCIPMRegs *pm = opaque;
|
||
|
|
+ return acpi_gpe_ioport_readb(&pm->acpi_regs, addr);
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static void ls7a_gpe_writeb(void *opaque, hwaddr addr, uint64_t val,
|
||
|
|
+ unsigned width)
|
||
|
|
+{
|
||
|
|
+ LS7APCIPMRegs *pm = opaque;
|
||
|
|
+ acpi_gpe_ioport_writeb(&pm->acpi_regs, addr, val);
|
||
|
|
+ acpi_update_sci(&pm->acpi_regs, pm->irq);
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static const MemoryRegionOps ls7a_gpe_ops = {
|
||
|
|
+ .read = ls7a_gpe_readb,
|
||
|
|
+ .write = ls7a_gpe_writeb,
|
||
|
|
+ .valid.min_access_size = 1,
|
||
|
|
+ .valid.max_access_size = 8,
|
||
|
|
+ .impl.min_access_size = 1,
|
||
|
|
+ .impl.max_access_size = 1,
|
||
|
|
+ .endianness = DEVICE_LITTLE_ENDIAN,
|
||
|
|
+};
|
||
|
|
+
|
||
|
|
+#define VMSTATE_GPE_ARRAY(_field, _state) \
|
||
|
|
+{ \
|
||
|
|
+ .name = (stringify(_field)), .version_id = 0, .num = ACPI_GPE0_LEN, \
|
||
|
|
+ .info = &vmstate_info_uint8, .size = sizeof(uint8_t), \
|
||
|
|
+ .flags = VMS_ARRAY | VMS_POINTER, \
|
||
|
|
+ .offset = vmstate_offset_pointer(_state, _field, uint8_t), \
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static uint64_t ls7a_reset_readw(void *opaque, hwaddr addr, unsigned width)
|
||
|
|
+{
|
||
|
|
+ return 0;
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static void ls7a_reset_writew(void *opaque, hwaddr addr, uint64_t val,
|
||
|
|
+ unsigned width)
|
||
|
|
+{
|
||
|
|
+ if (val & 1) {
|
||
|
|
+ qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
|
||
|
|
+ }
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static const MemoryRegionOps ls7a_reset_ops = {
|
||
|
|
+ .read = ls7a_reset_readw,
|
||
|
|
+ .write = ls7a_reset_writew,
|
||
|
|
+ .valid.min_access_size = 4,
|
||
|
|
+ .valid.max_access_size = 4,
|
||
|
|
+ .endianness = DEVICE_LITTLE_ENDIAN,
|
||
|
|
+};
|
||
|
|
+
|
||
|
|
+static bool vmstate_test_use_memhp(void *opaque)
|
||
|
|
+{
|
||
|
|
+ LS7APCIPMRegs *s = opaque;
|
||
|
|
+ return s->acpi_memory_hotplug.is_enabled;
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static const VMStateDescription vmstate_memhp_state = {
|
||
|
|
+ .name = "ls7a_pm/memhp",
|
||
|
|
+ .version_id = 1,
|
||
|
|
+ .minimum_version_id = 1,
|
||
|
|
+ .minimum_version_id_old = 1,
|
||
|
|
+ .needed = vmstate_test_use_memhp,
|
||
|
|
+ .fields = (VMStateField[]){ VMSTATE_MEMORY_HOTPLUG(acpi_memory_hotplug,
|
||
|
|
+ LS7APCIPMRegs),
|
||
|
|
+ VMSTATE_END_OF_LIST() }
|
||
|
|
+};
|
||
|
|
+
|
||
|
|
+static const VMStateDescription vmstate_cpuhp_state = {
|
||
|
|
+ .name = "ls7a_pm/cpuhp",
|
||
|
|
+ .version_id = 1,
|
||
|
|
+ .minimum_version_id = 1,
|
||
|
|
+ .minimum_version_id_old = 1,
|
||
|
|
+ .fields =
|
||
|
|
+ (VMStateField[]){ VMSTATE_CPU_HOTPLUG(cpuhp_state, LS7APCIPMRegs),
|
||
|
|
+ VMSTATE_END_OF_LIST() }
|
||
|
|
+};
|
||
|
|
+
|
||
|
|
+const VMStateDescription vmstate_ls7a_pm = {
|
||
|
|
+ .name = "ls7a_pm",
|
||
|
|
+ .version_id = 1,
|
||
|
|
+ .minimum_version_id = 1,
|
||
|
|
+ .fields =
|
||
|
|
+ (VMStateField[]){
|
||
|
|
+ VMSTATE_UINT16(acpi_regs.pm1.evt.sts, LS7APCIPMRegs),
|
||
|
|
+ VMSTATE_UINT16(acpi_regs.pm1.evt.en, LS7APCIPMRegs),
|
||
|
|
+ VMSTATE_UINT16(acpi_regs.pm1.cnt.cnt, LS7APCIPMRegs),
|
||
|
|
+ VMSTATE_TIMER_PTR(acpi_regs.tmr.timer, LS7APCIPMRegs),
|
||
|
|
+ VMSTATE_INT64(acpi_regs.tmr.overflow_time, LS7APCIPMRegs),
|
||
|
|
+ VMSTATE_GPE_ARRAY(acpi_regs.gpe.sts, LS7APCIPMRegs),
|
||
|
|
+ VMSTATE_GPE_ARRAY(acpi_regs.gpe.en, LS7APCIPMRegs),
|
||
|
|
+ VMSTATE_END_OF_LIST() },
|
||
|
|
+ .subsections = (const VMStateDescription *[]){ &vmstate_memhp_state,
|
||
|
|
+ &vmstate_cpuhp_state, NULL }
|
||
|
|
+};
|
||
|
|
+
|
||
|
|
+static inline int64_t acpi_pm_tmr_get_clock(void)
|
||
|
|
+{
|
||
|
|
+ return muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), PM_TIMER_FREQUENCY,
|
||
|
|
+ NANOSECONDS_PER_SECOND);
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static uint32_t acpi_pm_tmr_get(ACPIREGS *ar)
|
||
|
|
+{
|
||
|
|
+ uint32_t d = acpi_pm_tmr_get_clock();
|
||
|
|
+ return d & 0xffffff;
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static void acpi_pm_tmr_timer(void *opaque)
|
||
|
|
+{
|
||
|
|
+ ACPIREGS *ar = opaque;
|
||
|
|
+ qemu_system_wakeup_request(QEMU_WAKEUP_REASON_PMTIMER, NULL);
|
||
|
|
+ ar->tmr.update_sci(ar);
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static uint64_t acpi_pm_tmr_read(void *opaque, hwaddr addr, unsigned width)
|
||
|
|
+{
|
||
|
|
+ return acpi_pm_tmr_get(opaque);
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static void acpi_pm_tmr_write(void *opaque, hwaddr addr, uint64_t val,
|
||
|
|
+ unsigned width)
|
||
|
|
+{
|
||
|
|
+ /* nothing */
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static const MemoryRegionOps acpi_pm_tmr_ops = {
|
||
|
|
+ .read = acpi_pm_tmr_read,
|
||
|
|
+ .write = acpi_pm_tmr_write,
|
||
|
|
+ .valid.min_access_size = 4,
|
||
|
|
+ .valid.max_access_size = 4,
|
||
|
|
+ .endianness = DEVICE_LITTLE_ENDIAN,
|
||
|
|
+};
|
||
|
|
+
|
||
|
|
+static void ls7a_pm_tmr_init(ACPIREGS *ar, acpi_update_sci_fn update_sci,
|
||
|
|
+ MemoryRegion *parent, uint64_t offset)
|
||
|
|
+{
|
||
|
|
+ ar->tmr.update_sci = update_sci;
|
||
|
|
+ ar->tmr.timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, acpi_pm_tmr_timer, ar);
|
||
|
|
+ memory_region_init_io(&ar->tmr.io, memory_region_owner(parent),
|
||
|
|
+ &acpi_pm_tmr_ops, ar, "acpi-tmr", 4);
|
||
|
|
+ memory_region_add_subregion(parent, offset, &ar->tmr.io);
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static void acpi_pm1_evt_write_sts(ACPIREGS *ar, uint16_t val)
|
||
|
|
+{
|
||
|
|
+ uint16_t pm1_sts = acpi_pm1_evt_get_sts(ar);
|
||
|
|
+ if (pm1_sts & val & ACPI_BITMASK_TIMER_STATUS) {
|
||
|
|
+ /* if TMRSTS is reset, then compute the new overflow time */
|
||
|
|
+ acpi_pm_tmr_calc_overflow_time(ar);
|
||
|
|
+ }
|
||
|
|
+ ar->pm1.evt.sts &= ~val;
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static uint64_t acpi_pm_evt_read(void *opaque, hwaddr addr, unsigned width)
|
||
|
|
+{
|
||
|
|
+ ACPIREGS *ar = opaque;
|
||
|
|
+ switch (addr) {
|
||
|
|
+ case 0:
|
||
|
|
+ return acpi_pm1_evt_get_sts(ar);
|
||
|
|
+ case 4:
|
||
|
|
+ return ar->pm1.evt.en;
|
||
|
|
+ default:
|
||
|
|
+ return 0;
|
||
|
|
+ }
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static void acpi_pm1_evt_write_en(ACPIREGS *ar, uint16_t val)
|
||
|
|
+{
|
||
|
|
+ ar->pm1.evt.en = val;
|
||
|
|
+ qemu_system_wakeup_enable(QEMU_WAKEUP_REASON_RTC,
|
||
|
|
+ val & ACPI_BITMASK_RT_CLOCK_ENABLE);
|
||
|
|
+ qemu_system_wakeup_enable(QEMU_WAKEUP_REASON_PMTIMER,
|
||
|
|
+ val & ACPI_BITMASK_TIMER_ENABLE);
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static void acpi_pm_evt_write(void *opaque, hwaddr addr, uint64_t val,
|
||
|
|
+ unsigned width)
|
||
|
|
+{
|
||
|
|
+ ACPIREGS *ar = opaque;
|
||
|
|
+ switch (addr) {
|
||
|
|
+ case 0:
|
||
|
|
+ acpi_pm1_evt_write_sts(ar, val);
|
||
|
|
+ ar->pm1.evt.update_sci(ar);
|
||
|
|
+ break;
|
||
|
|
+ case 4:
|
||
|
|
+ acpi_pm1_evt_write_en(ar, val);
|
||
|
|
+ ar->pm1.evt.update_sci(ar);
|
||
|
|
+ break;
|
||
|
|
+ default:
|
||
|
|
+ break;
|
||
|
|
+ }
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static const MemoryRegionOps acpi_pm_evt_ops = {
|
||
|
|
+ .read = acpi_pm_evt_read,
|
||
|
|
+ .write = acpi_pm_evt_write,
|
||
|
|
+ .valid.min_access_size = 4,
|
||
|
|
+ .valid.max_access_size = 4,
|
||
|
|
+ .endianness = DEVICE_LITTLE_ENDIAN,
|
||
|
|
+};
|
||
|
|
+
|
||
|
|
+static void ls7a_pm1_evt_init(ACPIREGS *ar, acpi_update_sci_fn update_sci,
|
||
|
|
+ MemoryRegion *parent, uint64_t offset)
|
||
|
|
+{
|
||
|
|
+ ar->pm1.evt.update_sci = update_sci;
|
||
|
|
+ memory_region_init_io(&ar->pm1.evt.io, memory_region_owner(parent),
|
||
|
|
+ &acpi_pm_evt_ops, ar, "acpi-evt", 8);
|
||
|
|
+ memory_region_add_subregion(parent, offset, &ar->pm1.evt.io);
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static uint64_t acpi_pm_cnt_read(void *opaque, hwaddr addr, unsigned width)
|
||
|
|
+{
|
||
|
|
+ ACPIREGS *ar = opaque;
|
||
|
|
+ return ar->pm1.cnt.cnt;
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+/* ACPI PM1aCNT */
|
||
|
|
+static void acpi_pm1_cnt_write(ACPIREGS *ar, uint16_t val)
|
||
|
|
+{
|
||
|
|
+ ar->pm1.cnt.cnt = val & ~(ACPI_BITMASK_SLEEP_ENABLE);
|
||
|
|
+ if (val & ACPI_BITMASK_SLEEP_ENABLE) {
|
||
|
|
+ /* change suspend type */
|
||
|
|
+ uint16_t sus_typ = (val >> 10) & 7;
|
||
|
|
+ switch (sus_typ) {
|
||
|
|
+ /* s3,s4 not support */
|
||
|
|
+ case 5:
|
||
|
|
+ case 6:
|
||
|
|
+ warn_report("acpi s3,s4 state not support");
|
||
|
|
+ break;
|
||
|
|
+ /* s5: soft off */
|
||
|
|
+ case 7:
|
||
|
|
+ qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN);
|
||
|
|
+ break;
|
||
|
|
+ default:
|
||
|
|
+ break;
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static void acpi_pm_cnt_write(void *opaque, hwaddr addr, uint64_t val,
|
||
|
|
+ unsigned width)
|
||
|
|
+{
|
||
|
|
+ acpi_pm1_cnt_write(opaque, val);
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static const MemoryRegionOps acpi_pm_cnt_ops = {
|
||
|
|
+ .read = acpi_pm_cnt_read,
|
||
|
|
+ .write = acpi_pm_cnt_write,
|
||
|
|
+ .valid.min_access_size = 4,
|
||
|
|
+ .valid.max_access_size = 4,
|
||
|
|
+ .endianness = DEVICE_LITTLE_ENDIAN,
|
||
|
|
+};
|
||
|
|
+
|
||
|
|
+static void acpi_notify_wakeup(Notifier *notifier, void *data)
|
||
|
|
+{
|
||
|
|
+ ACPIREGS *ar = container_of(notifier, ACPIREGS, wakeup);
|
||
|
|
+ WakeupReason *reason = data;
|
||
|
|
+
|
||
|
|
+ switch (*reason) {
|
||
|
|
+ case QEMU_WAKEUP_REASON_RTC:
|
||
|
|
+ ar->pm1.evt.sts |=
|
||
|
|
+ (ACPI_BITMASK_WAKE_STATUS | ACPI_BITMASK_RT_CLOCK_STATUS);
|
||
|
|
+ break;
|
||
|
|
+ case QEMU_WAKEUP_REASON_PMTIMER:
|
||
|
|
+ ar->pm1.evt.sts |=
|
||
|
|
+ (ACPI_BITMASK_WAKE_STATUS | ACPI_BITMASK_TIMER_STATUS);
|
||
|
|
+ break;
|
||
|
|
+ case QEMU_WAKEUP_REASON_OTHER:
|
||
|
|
+ /*
|
||
|
|
+ * ACPI_BITMASK_WAKE_STATUS should be set on resume.
|
||
|
|
+ * Pretend that resume was caused by power button
|
||
|
|
+ */
|
||
|
|
+ ar->pm1.evt.sts |=
|
||
|
|
+ (ACPI_BITMASK_WAKE_STATUS | ACPI_BITMASK_POWER_BUTTON_STATUS);
|
||
|
|
+ break;
|
||
|
|
+ default:
|
||
|
|
+ break;
|
||
|
|
+ }
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static void ls7a_pm1_cnt_init(ACPIREGS *ar, MemoryRegion *parent,
|
||
|
|
+ bool disable_s3, bool disable_s4, uint8_t s4_val,
|
||
|
|
+ uint64_t offset)
|
||
|
|
+{
|
||
|
|
+ FWCfgState *fw_cfg;
|
||
|
|
+
|
||
|
|
+ ar->pm1.cnt.s4_val = s4_val;
|
||
|
|
+ ar->wakeup.notify = acpi_notify_wakeup;
|
||
|
|
+ qemu_register_wakeup_notifier(&ar->wakeup);
|
||
|
|
+ memory_region_init_io(&ar->pm1.cnt.io, memory_region_owner(parent),
|
||
|
|
+ &acpi_pm_cnt_ops, ar, "acpi-cnt", 4);
|
||
|
|
+ memory_region_add_subregion(parent, offset, &ar->pm1.cnt.io);
|
||
|
|
+
|
||
|
|
+ fw_cfg = fw_cfg_find();
|
||
|
|
+ if (fw_cfg) {
|
||
|
|
+ uint8_t suspend[6] = { 128, 0, 0, 129, 128, 128 };
|
||
|
|
+ suspend[3] = 1 | ((!disable_s3) << 7);
|
||
|
|
+ suspend[4] = s4_val | ((!disable_s4) << 7);
|
||
|
|
+ fw_cfg_add_file(fw_cfg, "etc/system-states", g_memdup(suspend, 6), 6);
|
||
|
|
+ }
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static void ls7a_pm_reset(void *opaque)
|
||
|
|
+{
|
||
|
|
+ LS7APCIPMRegs *pm = opaque;
|
||
|
|
+
|
||
|
|
+ acpi_pm1_evt_reset(&pm->acpi_regs);
|
||
|
|
+ acpi_pm1_cnt_reset(&pm->acpi_regs);
|
||
|
|
+ acpi_pm_tmr_reset(&pm->acpi_regs);
|
||
|
|
+ acpi_gpe_reset(&pm->acpi_regs);
|
||
|
|
+
|
||
|
|
+ acpi_update_sci(&pm->acpi_regs, pm->irq);
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static void pm_powerdown_req(Notifier *n, void *opaque)
|
||
|
|
+{
|
||
|
|
+ LS7APCIPMRegs *pm = container_of(n, LS7APCIPMRegs, powerdown_notifier);
|
||
|
|
+
|
||
|
|
+ acpi_pm1_evt_power_down(&pm->acpi_regs);
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+void ls7a_pm_init(LS7APCIPMRegs *pm, qemu_irq *pic)
|
||
|
|
+{
|
||
|
|
+ unsigned long base, gpe_len, acpi_aci_irq;
|
||
|
|
+
|
||
|
|
+ /*
|
||
|
|
+ * ls7a board acpi hardware info, including
|
||
|
|
+ * acpi system io base address
|
||
|
|
+ * acpi gpe length
|
||
|
|
+ * acpi sci irq number
|
||
|
|
+ */
|
||
|
|
+ base = ACPI_IO_BASE;
|
||
|
|
+ gpe_len = ACPI_GPE0_LEN;
|
||
|
|
+ acpi_aci_irq = ACPI_SCI_IRQ;
|
||
|
|
+
|
||
|
|
+ pm->irq = pic[acpi_aci_irq - 64];
|
||
|
|
+ memory_region_init(&pm->iomem, NULL, "ls7a_pm", ACPI_IO_SIZE);
|
||
|
|
+ memory_region_add_subregion(get_system_memory(), base, &pm->iomem);
|
||
|
|
+
|
||
|
|
+ cpu_hotplug_hw_init(get_system_memory(), NULL, &pm->cpuhp_state,
|
||
|
|
+ CPU_HOTPLUG_BASE);
|
||
|
|
+
|
||
|
|
+ ls7a_pm_tmr_init(&pm->acpi_regs, ls7a_pm_update_sci_fn, &pm->iomem,
|
||
|
|
+ LS7A_PM_TMR_BLK);
|
||
|
|
+ ls7a_pm1_evt_init(&pm->acpi_regs, ls7a_pm_update_sci_fn, &pm->iomem,
|
||
|
|
+ LS7A_PM_EVT_BLK);
|
||
|
|
+ ls7a_pm1_cnt_init(&pm->acpi_regs, &pm->iomem, false, false, 2,
|
||
|
|
+ LS7A_PM_CNT_BLK);
|
||
|
|
+
|
||
|
|
+ acpi_gpe_init(&pm->acpi_regs, gpe_len);
|
||
|
|
+ memory_region_init_io(&pm->iomem_gpe, NULL, &ls7a_gpe_ops, pm, "acpi-gpe0",
|
||
|
|
+ gpe_len);
|
||
|
|
+ memory_region_add_subregion(&pm->iomem, LS7A_GPE0_STS_REG, &pm->iomem_gpe);
|
||
|
|
+
|
||
|
|
+ memory_region_init_io(&pm->iomem_reset, NULL, &ls7a_reset_ops, pm,
|
||
|
|
+ "acpi-reset", 4);
|
||
|
|
+ memory_region_add_subregion(&pm->iomem, LS7A_GPE0_RESET_REG,
|
||
|
|
+ &pm->iomem_reset);
|
||
|
|
+
|
||
|
|
+ qemu_register_reset(ls7a_pm_reset, pm);
|
||
|
|
+
|
||
|
|
+ pm->powerdown_notifier.notify = pm_powerdown_req;
|
||
|
|
+ qemu_register_powerdown_notifier(&pm->powerdown_notifier);
|
||
|
|
+
|
||
|
|
+ if (pm->acpi_memory_hotplug.is_enabled) {
|
||
|
|
+ acpi_memory_hotplug_init(get_system_memory(), NULL,
|
||
|
|
+ &pm->acpi_memory_hotplug,
|
||
|
|
+ MEMORY_HOTPLUG_BASE);
|
||
|
|
+ }
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static void ls7a_pm_get_gpe0_blk(Object *obj, Visitor *v, const char *name,
|
||
|
|
+ void *opaque, Error **errp)
|
||
|
|
+{
|
||
|
|
+ uint64_t value = ACPI_IO_BASE + LS7A_GPE0_STS_REG;
|
||
|
|
+
|
||
|
|
+ visit_type_uint64(v, name, &value, errp);
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static bool ls7a_pm_get_memory_hotplug_support(Object *obj, Error **errp)
|
||
|
|
+{
|
||
|
|
+ LS7APCIState *ls7a = get_ls7a_type(obj);
|
||
|
|
+
|
||
|
|
+ return ls7a->pm.acpi_memory_hotplug.is_enabled;
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static void ls7a_pm_set_memory_hotplug_support(Object *obj, bool value,
|
||
|
|
+ Error **errp)
|
||
|
|
+{
|
||
|
|
+ LS7APCIState *ls7a = get_ls7a_type(obj);
|
||
|
|
+
|
||
|
|
+ ls7a->pm.acpi_memory_hotplug.is_enabled = value;
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static void ls7a_pm_get_disable_s3(Object *obj, Visitor *v, const char *name,
|
||
|
|
+ void *opaque, Error **errp)
|
||
|
|
+{
|
||
|
|
+ LS7APCIPMRegs *pm = opaque;
|
||
|
|
+ uint8_t value = pm->disable_s3;
|
||
|
|
+
|
||
|
|
+ visit_type_uint8(v, name, &value, errp);
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static void ls7a_pm_set_disable_s3(Object *obj, Visitor *v, const char *name,
|
||
|
|
+ void *opaque, Error **errp)
|
||
|
|
+{
|
||
|
|
+ LS7APCIPMRegs *pm = opaque;
|
||
|
|
+ Error *local_err = NULL;
|
||
|
|
+ uint8_t value;
|
||
|
|
+
|
||
|
|
+ visit_type_uint8(v, name, &value, &local_err);
|
||
|
|
+ if (local_err) {
|
||
|
|
+ goto out;
|
||
|
|
+ }
|
||
|
|
+ pm->disable_s3 = value;
|
||
|
|
+out:
|
||
|
|
+ error_propagate(errp, local_err);
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static void ls7a_pm_get_disable_s4(Object *obj, Visitor *v, const char *name,
|
||
|
|
+ void *opaque, Error **errp)
|
||
|
|
+{
|
||
|
|
+ LS7APCIPMRegs *pm = opaque;
|
||
|
|
+ uint8_t value = pm->disable_s4;
|
||
|
|
+
|
||
|
|
+ visit_type_uint8(v, name, &value, errp);
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static void ls7a_pm_set_disable_s4(Object *obj, Visitor *v, const char *name,
|
||
|
|
+ void *opaque, Error **errp)
|
||
|
|
+{
|
||
|
|
+ LS7APCIPMRegs *pm = opaque;
|
||
|
|
+ Error *local_err = NULL;
|
||
|
|
+ uint8_t value;
|
||
|
|
+
|
||
|
|
+ visit_type_uint8(v, name, &value, &local_err);
|
||
|
|
+ if (local_err) {
|
||
|
|
+ goto out;
|
||
|
|
+ }
|
||
|
|
+ pm->disable_s4 = value;
|
||
|
|
+out:
|
||
|
|
+ error_propagate(errp, local_err);
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static void ls7a_pm_get_s4_val(Object *obj, Visitor *v, const char *name,
|
||
|
|
+ void *opaque, Error **errp)
|
||
|
|
+{
|
||
|
|
+ LS7APCIPMRegs *pm = opaque;
|
||
|
|
+ uint8_t value = pm->s4_val;
|
||
|
|
+
|
||
|
|
+ visit_type_uint8(v, name, &value, errp);
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static void ls7a_pm_set_s4_val(Object *obj, Visitor *v, const char *name,
|
||
|
|
+ void *opaque, Error **errp)
|
||
|
|
+{
|
||
|
|
+ LS7APCIPMRegs *pm = opaque;
|
||
|
|
+ Error *local_err = NULL;
|
||
|
|
+ uint8_t value;
|
||
|
|
+
|
||
|
|
+ visit_type_uint8(v, name, &value, &local_err);
|
||
|
|
+ if (local_err) {
|
||
|
|
+ goto out;
|
||
|
|
+ }
|
||
|
|
+ pm->s4_val = value;
|
||
|
|
+out:
|
||
|
|
+ error_propagate(errp, local_err);
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+void ls7a_pm_add_properties(Object *obj, LS7APCIPMRegs *pm, Error **errp)
|
||
|
|
+{
|
||
|
|
+ static const uint32_t gpe0_len = ACPI_GPE0_LEN;
|
||
|
|
+ pm->acpi_memory_hotplug.is_enabled = true;
|
||
|
|
+ pm->disable_s3 = 0;
|
||
|
|
+ pm->disable_s4 = 0;
|
||
|
|
+ pm->s4_val = 2;
|
||
|
|
+
|
||
|
|
+ object_property_add_uint32_ptr(obj, ACPI_PM_PROP_PM_IO_BASE,
|
||
|
|
+ &pm->pm_io_base, OBJ_PROP_FLAG_READ);
|
||
|
|
+ object_property_add(obj, ACPI_PM_PROP_GPE0_BLK, "uint32",
|
||
|
|
+ ls7a_pm_get_gpe0_blk, NULL, NULL, pm);
|
||
|
|
+ object_property_add_uint32_ptr(obj, ACPI_PM_PROP_GPE0_BLK_LEN, &gpe0_len,
|
||
|
|
+ OBJ_PROP_FLAG_READ);
|
||
|
|
+ object_property_add_bool(obj, "memory-hotplug-support",
|
||
|
|
+ ls7a_pm_get_memory_hotplug_support,
|
||
|
|
+ ls7a_pm_set_memory_hotplug_support);
|
||
|
|
+ object_property_add(obj, ACPI_PM_PROP_S3_DISABLED, "uint8",
|
||
|
|
+ ls7a_pm_get_disable_s3, ls7a_pm_set_disable_s3, NULL,
|
||
|
|
+ pm);
|
||
|
|
+ object_property_add(obj, ACPI_PM_PROP_S4_DISABLED, "uint8",
|
||
|
|
+ ls7a_pm_get_disable_s4, ls7a_pm_set_disable_s4, NULL,
|
||
|
|
+ pm);
|
||
|
|
+ object_property_add(obj, ACPI_PM_PROP_S4_VAL, "uint8", ls7a_pm_get_s4_val,
|
||
|
|
+ ls7a_pm_set_s4_val, NULL, pm);
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+void ls7a_pm_device_plug_cb(HotplugHandler *hotplug_dev, DeviceState *dev,
|
||
|
|
+ Error **errp)
|
||
|
|
+{
|
||
|
|
+ LS7APCIState *ls7a = get_ls7a_type(OBJECT(hotplug_dev));
|
||
|
|
+
|
||
|
|
+ if (ls7a->pm.acpi_memory_hotplug.is_enabled &&
|
||
|
|
+ object_dynamic_cast(OBJECT(dev), TYPE_PC_DIMM)) {
|
||
|
|
+ if (object_dynamic_cast(OBJECT(dev), TYPE_NVDIMM)) {
|
||
|
|
+ nvdimm_acpi_plug_cb(hotplug_dev, dev);
|
||
|
|
+ } else {
|
||
|
|
+ acpi_memory_plug_cb(hotplug_dev, &ls7a->pm.acpi_memory_hotplug,
|
||
|
|
+ dev, errp);
|
||
|
|
+ }
|
||
|
|
+ } else if (object_dynamic_cast(OBJECT(dev), TYPE_CPU)) {
|
||
|
|
+ acpi_cpu_plug_cb(hotplug_dev, &ls7a->pm.cpuhp_state, dev, errp);
|
||
|
|
+ } else {
|
||
|
|
+ error_setg(errp,
|
||
|
|
+ "acpi: device plug request for not supported device"
|
||
|
|
+ " type: %s",
|
||
|
|
+ object_get_typename(OBJECT(dev)));
|
||
|
|
+ }
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+void ls7a_pm_device_unplug_request_cb(HotplugHandler *hotplug_dev,
|
||
|
|
+ DeviceState *dev, Error **errp)
|
||
|
|
+{
|
||
|
|
+ LS7APCIState *ls7a = get_ls7a_type(OBJECT(hotplug_dev));
|
||
|
|
+
|
||
|
|
+ if (ls7a->pm.acpi_memory_hotplug.is_enabled &&
|
||
|
|
+ object_dynamic_cast(OBJECT(dev), TYPE_PC_DIMM)) {
|
||
|
|
+ acpi_memory_unplug_request_cb(
|
||
|
|
+ hotplug_dev, &ls7a->pm.acpi_memory_hotplug, dev, errp);
|
||
|
|
+ } else if (object_dynamic_cast(OBJECT(dev), TYPE_CPU)) {
|
||
|
|
+ acpi_cpu_unplug_request_cb(hotplug_dev, &ls7a->pm.cpuhp_state, dev,
|
||
|
|
+ errp);
|
||
|
|
+ } else {
|
||
|
|
+ error_setg(errp,
|
||
|
|
+ "acpi: device unplug request for not supported device"
|
||
|
|
+ " type: %s",
|
||
|
|
+ object_get_typename(OBJECT(dev)));
|
||
|
|
+ }
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+void ls7a_pm_device_unplug_cb(HotplugHandler *hotplug_dev, DeviceState *dev,
|
||
|
|
+ Error **errp)
|
||
|
|
+{
|
||
|
|
+ LS7APCIState *ls7a = get_ls7a_type(OBJECT(hotplug_dev));
|
||
|
|
+
|
||
|
|
+ if (ls7a->pm.acpi_memory_hotplug.is_enabled &&
|
||
|
|
+ object_dynamic_cast(OBJECT(dev), TYPE_PC_DIMM)) {
|
||
|
|
+ acpi_memory_unplug_cb(&ls7a->pm.acpi_memory_hotplug, dev, errp);
|
||
|
|
+ } else if (object_dynamic_cast(OBJECT(dev), TYPE_CPU)) {
|
||
|
|
+ acpi_cpu_unplug_cb(&ls7a->pm.cpuhp_state, dev, errp);
|
||
|
|
+ } else {
|
||
|
|
+ error_setg(errp,
|
||
|
|
+ "acpi: device unplug for not supported device"
|
||
|
|
+ " type: %s",
|
||
|
|
+ object_get_typename(OBJECT(dev)));
|
||
|
|
+ }
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+void ls7a_pm_ospm_status(AcpiDeviceIf *adev, ACPIOSTInfoList ***list)
|
||
|
|
+{
|
||
|
|
+ LS7APCIState *ls7a = get_ls7a_type(OBJECT(adev));
|
||
|
|
+
|
||
|
|
+ acpi_memory_ospm_status(&ls7a->pm.acpi_memory_hotplug, list);
|
||
|
|
+ acpi_cpu_ospm_status(&ls7a->pm.cpuhp_state, list);
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+void ls7a_send_gpe(AcpiDeviceIf *adev, AcpiEventStatusBits ev)
|
||
|
|
+{
|
||
|
|
+ LS7APCIState *ls7a = get_ls7a_type(OBJECT(adev));
|
||
|
|
+
|
||
|
|
+ acpi_send_gpe_event(&ls7a->pm.acpi_regs, ls7a->pm.irq, ev);
|
||
|
|
+}
|
||
|
|
diff --git a/hw/acpi/meson.build b/hw/acpi/meson.build
|
||
|
|
index 448ea6afb4..4718d143fc 100644
|
||
|
|
--- a/hw/acpi/meson.build
|
||
|
|
+++ b/hw/acpi/meson.build
|
||
|
|
@@ -6,6 +6,7 @@ acpi_ss.add(files(
|
||
|
|
'core.c',
|
||
|
|
'utils.c',
|
||
|
|
))
|
||
|
|
+acpi_ss.add(when: 'CONFIG_ACPI_LOONGARCH', if_true: files('larch_7a.c'))
|
||
|
|
acpi_ss.add(when: 'CONFIG_ACPI_CPU_HOTPLUG', if_true: files('cpu.c', 'cpu_hotplug.c'))
|
||
|
|
acpi_ss.add(when: 'CONFIG_ACPI_CPU_HOTPLUG', if_false: files('acpi-cpu-hotplug-stub.c'))
|
||
|
|
acpi_ss.add(when: 'CONFIG_ACPI_MEMORY_HOTPLUG', if_true: files('memory_hotplug.c'))
|
||
|
|
diff --git a/include/hw/acpi/ls7a.h b/include/hw/acpi/ls7a.h
|
||
|
|
new file mode 100644
|
||
|
|
index 0000000000..295baa4b5a
|
||
|
|
--- /dev/null
|
||
|
|
+++ b/include/hw/acpi/ls7a.h
|
||
|
|
@@ -0,0 +1,79 @@
|
||
|
|
+/*
|
||
|
|
+ * QEMU GMCH/LS7A PCI PM Emulation
|
||
|
|
+ *
|
||
|
|
+ * Copyright (c) 2023 Loongarch Technology
|
||
|
|
+ *
|
||
|
|
+ * 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.
|
||
|
|
+ *
|
||
|
|
+ * This program is distributed in the hope it will be useful, but WITHOUT
|
||
|
|
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||
|
|
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||
|
|
+ * more details.
|
||
|
|
+ *
|
||
|
|
+ * You should have received a copy of the GNU General Public License along with
|
||
|
|
+ * this program. If not, see <http://www.gnu.org/licenses/>.
|
||
|
|
+ *
|
||
|
|
+ */
|
||
|
|
+
|
||
|
|
+#ifndef HW_ACPI_LS7A_H
|
||
|
|
+#define HW_ACPI_LS7A_H
|
||
|
|
+
|
||
|
|
+#include "hw/acpi/acpi.h"
|
||
|
|
+#include "hw/acpi/cpu_hotplug.h"
|
||
|
|
+#include "hw/acpi/cpu.h"
|
||
|
|
+#include "hw/acpi/memory_hotplug.h"
|
||
|
|
+#include "hw/acpi/acpi_dev_interface.h"
|
||
|
|
+#include "hw/acpi/tco.h"
|
||
|
|
+
|
||
|
|
+#define CPU_HOTPLUG_BASE 0x1e000000
|
||
|
|
+#define MEMORY_HOTPLUG_BASE 0x1e00000c
|
||
|
|
+
|
||
|
|
+typedef struct LS7APCIPMRegs {
|
||
|
|
+ /*
|
||
|
|
+ * In ls7a spec says that pm1_cnt register is 32bit width and
|
||
|
|
+ * that the upper 16bits are reserved and unused.
|
||
|
|
+ * PM1a_CNT_BLK = 2 in FADT so it is defined as uint16_t.
|
||
|
|
+ */
|
||
|
|
+ ACPIREGS acpi_regs;
|
||
|
|
+
|
||
|
|
+ MemoryRegion iomem;
|
||
|
|
+ MemoryRegion iomem_gpe;
|
||
|
|
+ MemoryRegion iomem_smi;
|
||
|
|
+ MemoryRegion iomem_reset;
|
||
|
|
+
|
||
|
|
+ qemu_irq irq; /* SCI */
|
||
|
|
+
|
||
|
|
+ uint32_t pm_io_base;
|
||
|
|
+ Notifier powerdown_notifier;
|
||
|
|
+
|
||
|
|
+ bool cpu_hotplug_legacy;
|
||
|
|
+ AcpiCpuHotplug gpe_cpu;
|
||
|
|
+ CPUHotplugState cpuhp_state;
|
||
|
|
+
|
||
|
|
+ MemHotplugState acpi_memory_hotplug;
|
||
|
|
+
|
||
|
|
+ uint8_t disable_s3;
|
||
|
|
+ uint8_t disable_s4;
|
||
|
|
+ uint8_t s4_val;
|
||
|
|
+} LS7APCIPMRegs;
|
||
|
|
+
|
||
|
|
+void ls7a_pm_init(LS7APCIPMRegs *ls7a, qemu_irq *sci_irq);
|
||
|
|
+
|
||
|
|
+void ls7a_pm_iospace_update(LS7APCIPMRegs *pm, uint32_t pm_io_base);
|
||
|
|
+extern const VMStateDescription vmstate_ls7a_pm;
|
||
|
|
+
|
||
|
|
+void ls7a_pm_add_properties(Object *obj, LS7APCIPMRegs *pm, Error **errp);
|
||
|
|
+
|
||
|
|
+void ls7a_pm_device_plug_cb(HotplugHandler *hotplug_dev, DeviceState *dev,
|
||
|
|
+ Error **errp);
|
||
|
|
+void ls7a_pm_device_unplug_request_cb(HotplugHandler *hotplug_dev,
|
||
|
|
+ DeviceState *dev, Error **errp);
|
||
|
|
+void ls7a_pm_device_unplug_cb(HotplugHandler *hotplug_dev, DeviceState *dev,
|
||
|
|
+ Error **errp);
|
||
|
|
+
|
||
|
|
+void ls7a_pm_ospm_status(AcpiDeviceIf *adev, ACPIOSTInfoList ***list);
|
||
|
|
+
|
||
|
|
+void ls7a_send_gpe(AcpiDeviceIf *adev, AcpiEventStatusBits ev);
|
||
|
|
+#endif /* HW_ACPI_LS7A_H */
|
||
|
|
--
|
||
|
|
2.27.0
|
||
|
|
|