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

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);
}