hw/acpi/ich9: Add periodic and swsmi timer

This patch implements the periodic and the swsmi ICH9 chipset timers. They are
especially useful when prototyping UEFI firmware (e.g. with EDK2's OVMF)
using QEMU.

For backwards compatibility, the compat properties "x-smi-swsmi-timer",
and "x-smi-periodic-timer" are introduced.

Additionally, writes to the SMI_STS register are enabled for the
corresponding two bits using a write mask to make future work easier.

Signed-off-by: Dominic Prinz <git@dprinz.de>
Message-Id: <1d90ea69e01ab71a0f2ced116801dc78e04f4448.1725991505.git.git@dprinz.de>
Reviewed-by: Michael S. Tsirkin <mst@redhat.com>
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
This commit is contained in:
Dominic Prinz 2024-09-10 20:08:20 +02:00 committed by Michael S. Tsirkin
parent 95b717a815
commit 6e3c2d58e9
8 changed files with 168 additions and 2 deletions

View File

@ -35,6 +35,7 @@
#include "sysemu/runstate.h"
#include "hw/acpi/acpi.h"
#include "hw/acpi/ich9_tco.h"
#include "hw/acpi/ich9_timer.h"
#include "hw/southbridge/ich9.h"
#include "hw/mem/pc-dimm.h"
@ -108,6 +109,18 @@ static void ich9_smi_writel(void *opaque, hwaddr addr, uint64_t val,
}
pm->smi_en &= ~pm->smi_en_wmask;
pm->smi_en |= (val & pm->smi_en_wmask);
if (pm->swsmi_timer_enabled) {
ich9_pm_update_swsmi_timer(pm, pm->smi_en &
ICH9_PMIO_SMI_EN_SWSMI_EN);
}
if (pm->periodic_timer_enabled) {
ich9_pm_update_periodic_timer(pm, pm->smi_en &
ICH9_PMIO_SMI_EN_PERIODIC_EN);
}
break;
case 4:
pm->smi_sts &= ~pm->smi_sts_wmask;
pm->smi_sts |= (val & pm->smi_sts_wmask);
break;
}
}
@ -286,6 +299,8 @@ static void pm_powerdown_req(Notifier *n, void *opaque)
void ich9_pm_init(PCIDevice *lpc_pci, ICH9LPCPMRegs *pm, qemu_irq sci_irq)
{
pm->smi_sts_wmask = 0;
memory_region_init(&pm->io, OBJECT(lpc_pci), "ich9-pm", ICH9_PMIO_SIZE);
memory_region_set_enabled(&pm->io, false);
memory_region_add_subregion(pci_address_space_io(lpc_pci),
@ -305,6 +320,14 @@ void ich9_pm_init(PCIDevice *lpc_pci, ICH9LPCPMRegs *pm, qemu_irq sci_irq)
"acpi-smi", 8);
memory_region_add_subregion(&pm->io, ICH9_PMIO_SMI_EN, &pm->io_smi);
if (pm->swsmi_timer_enabled) {
ich9_pm_swsmi_timer_init(pm);
}
if (pm->periodic_timer_enabled) {
ich9_pm_periodic_timer_init(pm);
}
if (pm->enable_tco) {
acpi_pm_tco_init(&pm->tco_regs, &pm->io);
}

93
hw/acpi/ich9_timer.c Normal file
View File

@ -0,0 +1,93 @@
/*
* QEMU ICH9 Timer emulation
*
* Copyright (c) 2024 Dominic Prinz <git@dprinz.de>
*
* 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 "qemu/osdep.h"
#include "hw/core/cpu.h"
#include "hw/pci/pci.h"
#include "hw/southbridge/ich9.h"
#include "qemu/timer.h"
#include "hw/acpi/ich9_timer.h"
void ich9_pm_update_swsmi_timer(ICH9LPCPMRegs *pm, bool enable)
{
uint16_t swsmi_rate_sel;
int64_t expire_time;
ICH9LPCState *lpc;
if (enable) {
lpc = container_of(pm, ICH9LPCState, pm);
swsmi_rate_sel =
(pci_get_word(lpc->d.config + ICH9_LPC_GEN_PMCON_3) & 0xc0) >> 6;
if (swsmi_rate_sel == 0) {
expire_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 1500000LL;
} else {
expire_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
8 * (1 << swsmi_rate_sel) * 1000000LL;
}
timer_mod(pm->swsmi_timer, expire_time);
} else {
timer_del(pm->swsmi_timer);
}
}
static void ich9_pm_swsmi_timer_expired(void *opaque)
{
ICH9LPCPMRegs *pm = opaque;
pm->smi_sts |= ICH9_PMIO_SMI_STS_SWSMI_STS;
ich9_generate_smi();
ich9_pm_update_swsmi_timer(pm, pm->smi_en & ICH9_PMIO_SMI_EN_SWSMI_EN);
}
void ich9_pm_swsmi_timer_init(ICH9LPCPMRegs *pm)
{
pm->smi_sts_wmask |= ICH9_PMIO_SMI_STS_SWSMI_STS;
pm->swsmi_timer =
timer_new_ns(QEMU_CLOCK_VIRTUAL, ich9_pm_swsmi_timer_expired, pm);
}
void ich9_pm_update_periodic_timer(ICH9LPCPMRegs *pm, bool enable)
{
uint16_t per_smi_sel;
int64_t expire_time;
ICH9LPCState *lpc;
if (enable) {
lpc = container_of(pm, ICH9LPCState, pm);
per_smi_sel = pci_get_word(lpc->d.config + ICH9_LPC_GEN_PMCON_1) & 3;
expire_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
8 * (1 << (3 - per_smi_sel)) * NANOSECONDS_PER_SECOND;
timer_mod(pm->periodic_timer, expire_time);
} else {
timer_del(pm->periodic_timer);
}
}
static void ich9_pm_periodic_timer_expired(void *opaque)
{
ICH9LPCPMRegs *pm = opaque;
pm->smi_sts = ICH9_PMIO_SMI_STS_PERIODIC_STS;
ich9_generate_smi();
ich9_pm_update_periodic_timer(pm,
pm->smi_en & ICH9_PMIO_SMI_EN_PERIODIC_EN);
}
void ich9_pm_periodic_timer_init(ICH9LPCPMRegs *pm)
{
pm->smi_sts_wmask |= ICH9_PMIO_SMI_STS_PERIODIC_STS;
pm->periodic_timer =
timer_new_ns(QEMU_CLOCK_VIRTUAL, ich9_pm_periodic_timer_expired, pm);
}

View File

@ -24,7 +24,7 @@ acpi_ss.add(when: 'CONFIG_ACPI_PCI_BRIDGE', if_true: files('pci-bridge.c'))
acpi_ss.add(when: 'CONFIG_ACPI_PCIHP', if_true: files('pcihp.c'))
acpi_ss.add(when: 'CONFIG_ACPI_PCIHP', if_false: files('acpi-pci-hotplug-stub.c'))
acpi_ss.add(when: 'CONFIG_ACPI_VIOT', if_true: files('viot.c'))
acpi_ss.add(when: 'CONFIG_ACPI_ICH9', if_true: files('ich9.c', 'ich9_tco.c'))
acpi_ss.add(when: 'CONFIG_ACPI_ICH9', if_true: files('ich9.c', 'ich9_tco.c', 'ich9_timer.c'))
acpi_ss.add(when: 'CONFIG_ACPI_ERST', if_true: files('erst.c'))
acpi_ss.add(when: 'CONFIG_IPMI', if_true: files('ipmi.c'), if_false: files('ipmi-stub.c'))
acpi_ss.add(when: 'CONFIG_PC', if_false: files('acpi-x86-stub.c'))

View File

@ -79,7 +79,10 @@
{ "qemu64-" TYPE_X86_CPU, "model-id", "QEMU Virtual CPU version " v, },\
{ "athlon-" TYPE_X86_CPU, "model-id", "QEMU Virtual CPU version " v, },
GlobalProperty pc_compat_9_1[] = {};
GlobalProperty pc_compat_9_1[] = {
{ "ICH9-LPC", "x-smi-swsmi-timer", "off" },
{ "ICH9-LPC", "x-smi-periodic-timer", "off" },
};
const size_t pc_compat_9_1_len = G_N_ELEMENTS(pc_compat_9_1);
GlobalProperty pc_compat_9_0[] = {

View File

@ -43,6 +43,7 @@
#include "hw/southbridge/ich9.h"
#include "hw/acpi/acpi.h"
#include "hw/acpi/ich9.h"
#include "hw/acpi/ich9_timer.h"
#include "hw/pci/pci_bus.h"
#include "hw/qdev-properties.h"
#include "sysemu/runstate.h"
@ -531,6 +532,15 @@ ich9_lpc_pmcon_update(ICH9LPCState *lpc)
uint16_t gen_pmcon_1 = pci_get_word(lpc->d.config + ICH9_LPC_GEN_PMCON_1);
uint16_t wmask;
if (lpc->pm.swsmi_timer_enabled) {
ich9_pm_update_swsmi_timer(
&lpc->pm, lpc->pm.smi_en & ICH9_PMIO_SMI_EN_SWSMI_EN);
}
if (lpc->pm.periodic_timer_enabled) {
ich9_pm_update_periodic_timer(
&lpc->pm, lpc->pm.smi_en & ICH9_PMIO_SMI_EN_PERIODIC_EN);
}
if (gen_pmcon_1 & ICH9_LPC_GEN_PMCON_1_SMI_LOCK) {
wmask = pci_get_word(lpc->d.wmask + ICH9_LPC_GEN_PMCON_1);
wmask &= ~ICH9_LPC_GEN_PMCON_1_SMI_LOCK;
@ -826,6 +836,10 @@ static Property ich9_lpc_properties[] = {
ICH9_LPC_SMI_F_CPU_HOTPLUG_BIT, true),
DEFINE_PROP_BIT64("x-smi-cpu-hotunplug", ICH9LPCState, smi_host_features,
ICH9_LPC_SMI_F_CPU_HOT_UNPLUG_BIT, true),
DEFINE_PROP_BOOL("x-smi-swsmi-timer", ICH9LPCState,
pm.swsmi_timer_enabled, true),
DEFINE_PROP_BOOL("x-smi-periodic-timer", ICH9LPCState,
pm.periodic_timer_enabled, true),
DEFINE_PROP_END_OF_LIST(),
};

View File

@ -46,6 +46,7 @@ typedef struct ICH9LPCPMRegs {
uint32_t smi_en;
uint32_t smi_en_wmask;
uint32_t smi_sts;
uint32_t smi_sts_wmask;
qemu_irq irq; /* SCI */
@ -68,6 +69,11 @@ typedef struct ICH9LPCPMRegs {
bool smm_compat;
bool enable_tco;
TCOIORegs tco_regs;
bool swsmi_timer_enabled;
bool periodic_timer_enabled;
QEMUTimer *swsmi_timer;
QEMUTimer *periodic_timer;
} ICH9LPCPMRegs;
#define ACPI_PM_PROP_TCO_ENABLED "enable_tco"

View File

@ -0,0 +1,23 @@
/*
* QEMU ICH9 Timer emulation
*
* Copyright (c) 2024 Dominic Prinz <git@dprinz.de>
*
* This work is licensed under the terms of the GNU GPL, version 2 or later.
* See the COPYING file in the top-level directory.
*/
#ifndef HW_ACPI_ICH9_TIMER_H
#define HW_ACPI_ICH9_TIMER_H
#include "hw/acpi/ich9.h"
void ich9_pm_update_swsmi_timer(ICH9LPCPMRegs *pm, bool enable);
void ich9_pm_swsmi_timer_init(ICH9LPCPMRegs *pm);
void ich9_pm_update_periodic_timer(ICH9LPCPMRegs *pm, bool enable);
void ich9_pm_periodic_timer_init(ICH9LPCPMRegs *pm);
#endif

View File

@ -196,8 +196,12 @@ struct ICH9LPCState {
#define ICH9_PMIO_GPE0_LEN 16
#define ICH9_PMIO_SMI_EN 0x30
#define ICH9_PMIO_SMI_EN_APMC_EN (1 << 5)
#define ICH9_PMIO_SMI_EN_SWSMI_EN (1 << 6)
#define ICH9_PMIO_SMI_EN_TCO_EN (1 << 13)
#define ICH9_PMIO_SMI_EN_PERIODIC_EN (1 << 14)
#define ICH9_PMIO_SMI_STS 0x34
#define ICH9_PMIO_SMI_STS_SWSMI_STS (1 << 6)
#define ICH9_PMIO_SMI_STS_PERIODIC_STS (1 << 14)
#define ICH9_PMIO_TCO_RLD 0x60
#define ICH9_PMIO_TCO_LEN 32