On Fri, Feb 6, 2026 at 6:45 PM Igor Mammedov <[email protected]> wrote: > > It will generate WDAT table [1] customized for TCO watchdog. > This allows Windows guests (Windows Server 2008/Vista+) to > use TCO watchdog using built-in generic driver, which > aleviates need to install vendor specific drivers. ^^^^^^^^^^ typo.
> > Given that enabling it might change guest behaviour > (both Windows/Linux) the feature is disabled by default. > > Users that need it can enable the feature with > following CLI option. > -machine 'acpi-watchdog=on' > > 1) > "Hardware Watchdog Timers Design Specification" > https://uefi.org/acpi 'Watchdog Action Table (WDAT)' > > Signed-off-by: Igor Mammedov <[email protected]> Other than below, Reviewed-by: Ani Sinha <[email protected]> > --- > include/hw/acpi/wdat-ich9.h | 15 +++++++ > hw/acpi/meson.build | 3 +- > hw/acpi/wdat-ich9-stub.c | 15 +++++++ > hw/acpi/wdat-ich9.c | 87 +++++++++++++++++++++++++++++++++++++ > hw/i386/acpi-build.c | 12 +++++ > 5 files changed, 131 insertions(+), 1 deletion(-) > create mode 100644 include/hw/acpi/wdat-ich9.h > create mode 100644 hw/acpi/wdat-ich9-stub.c > create mode 100644 hw/acpi/wdat-ich9.c > > diff --git a/include/hw/acpi/wdat-ich9.h b/include/hw/acpi/wdat-ich9.h > new file mode 100644 > index 0000000000..60a2a6a112 > --- /dev/null > +++ b/include/hw/acpi/wdat-ich9.h > @@ -0,0 +1,15 @@ > +/* > + * Copyright Red Hat, Inc. 2026 > + * Author(s): Igor Mammedov <[email protected]> > + * > + * SPDX-License-Identifier: GPL-2.0-or-later > + */ > +#ifndef QEMU_HW_ACPI_WDAT_ICH9_H > +#define QEMU_HW_ACPI_WDAT_ICH9_H > + > +#include "hw/acpi/aml-build.h" > + > +void build_ich9_wdat(GArray *table_data, BIOSLinker *linker, const char > *oem_id, > + const char *oem_table_id, uint64_t tco_base); > + > +#endif /* QEMU_HW_ACPI_WDAT_ICH9_H */ > diff --git a/hw/acpi/meson.build b/hw/acpi/meson.build > index 56b5d1ec96..ba974b2f0f 100644 > --- a/hw/acpi/meson.build > +++ b/hw/acpi/meson.build > @@ -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', > 'ich9_timer.c')) > +acpi_ss.add(when: 'CONFIG_ACPI_ICH9', if_true: files('ich9.c', 'ich9_tco.c', > 'ich9_timer.c', 'wdat-ich9.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')) > @@ -33,6 +33,7 @@ if have_tpm > endif > system_ss.add(when: 'CONFIG_ACPI', if_false: files('acpi-stub.c', > 'aml-build-stub.c', 'ghes-stub.c', 'acpi_interface.c')) > system_ss.add(when: 'CONFIG_ACPI_PCI_BRIDGE', if_false: > files('pci-bridge-stub.c')) > +system_ss.add(when: 'CONFIG_ACPI_ICH9', if_false: files('wdat-ich9-stub.c')) > system_ss.add_all(when: 'CONFIG_ACPI', if_true: acpi_ss) > system_ss.add(when: 'CONFIG_GHES_CPER', if_true: files('ghes_cper.c')) > system_ss.add(when: 'CONFIG_GHES_CPER', if_false: files('ghes_cper_stub.c')) > diff --git a/hw/acpi/wdat-ich9-stub.c b/hw/acpi/wdat-ich9-stub.c > new file mode 100644 > index 0000000000..7c30ef3558 > --- /dev/null > +++ b/hw/acpi/wdat-ich9-stub.c > @@ -0,0 +1,15 @@ > +/* > + * Copyright Red Hat, Inc. 2026 > + * Author(s): Igor Mammedov <[email protected]> > + * > + * SPDX-License-Identifier: GPL-2.0-or-later > + */ > + > +#include "qemu/osdep.h" > +#include "hw/acpi/wdat-ich9.h" > + > +void build_ich9_wdat(GArray *table_data, BIOSLinker *linker, const char > *oem_id, > + const char *oem_table_id, uint64_t tco_base) > +{ > + g_assert_not_reached(); > +} > diff --git a/hw/acpi/wdat-ich9.c b/hw/acpi/wdat-ich9.c > new file mode 100644 > index 0000000000..b1930dc71d > --- /dev/null > +++ b/hw/acpi/wdat-ich9.c > @@ -0,0 +1,87 @@ > +/* > + * TCO Watchdog Action Table (WDAT) > + * > + * Copyright Red Hat, Inc. 2026 > + * Author(s): Igor Mammedov <[email protected]> > + * > + * SPDX-License-Identifier: GPL-2.0-or-later > + */ > + > +#include "qemu/osdep.h" > +#include "hw/acpi/wdat.h" > +#include "hw/acpi/wdat-ich9.h" > +#include "hw/southbridge/ich9.h" > + > +#define TCO_REG(base, reg_offset, reg_width) { .space_id = AML_AS_SYSTEM_IO, > \ > + .address = base + reg_offset, .bit_width = reg_width, \ > + .access_width = AML_WORD_ACC, }; > + > +/* > + * "Hardware Watchdog Timers Design Specification" > + * https://uefi.org/acpi 'Watchdog Action Table (WDAT)' > + * > + * ICH9 specific implementation. > + */ > +void build_ich9_wdat(GArray *table_data, BIOSLinker *linker, const char > *oem_id, > + const char *oem_table_id, uint64_t tco_base) > +{ > + AcpiTable table = { .sig = "WDAT", .rev = 1, .oem_id = oem_id, > + .oem_table_id = oem_table_id }; > + struct AcpiGenericAddress tco_rld = TCO_REG(tco_base, 0x0, 16); > + struct AcpiGenericAddress tco2_sts = TCO_REG(tco_base, 0x6, 16); > + struct AcpiGenericAddress tco1_cnt = TCO_REG(tco_base, 0x8, 16); > + struct AcpiGenericAddress tco_tmr = TCO_REG(tco_base, 0x12, 16); > + > + acpi_table_begin(&table, table_data); > + build_append_int_noprefix(table_data, 0x20, 4); /* Watchdog Header > Length */ > + build_append_int_noprefix(table_data, 0xff, 2); /* PCI Segment */ > + build_append_int_noprefix(table_data, 0xff, 1); /* PCI Bus Number */ > + build_append_int_noprefix(table_data, 0xff, 1); /* PCI Device Number */ > + build_append_int_noprefix(table_data, 0xff, 1); /* PCI Function Number */ > + build_append_int_noprefix(table_data, 0, 3); /* Reserved */ > + build_append_int_noprefix(table_data, 0x258, 4);/* Timer Period, ms */ > + build_append_int_noprefix(table_data, 0x3ff, 4);/* Maximum Count */ > + build_append_int_noprefix(table_data, 0x4, 4); /* Minimum Count */ How are you getting the values for the above three (timer period, min count and max count)? A comment will help here. > + /* > + * WATCHDOG_ENABLED & WATCHDOG_STOPPED_IN_SLEEP_STATE > + */ > + build_append_int_noprefix(table_data, 0x81, 1); /* Watchdog Flags */ > + build_append_int_noprefix(table_data, 0, 3); /* Reserved */ > + /* > + * watchdog instruction entries > + */ > + build_append_int_noprefix(table_data, 10, 4); It might help to add a comment that we are adding 10 (decimal 10 even though other values above are in hex) instruction entries below. > + /* Action table */ > + build_append_wdat_ins(table_data, WDAT_ACTION_RESET, > + WDAT_INS_WRITE_VALUE, > + tco_rld, 0x1, 0x1ff); > + build_append_wdat_ins(table_data, WDAT_ACTION_QUERY_RUNNING_STATE, > + WDAT_INS_READ_VALUE, > + tco1_cnt, 0x0, 0x800); > + build_append_wdat_ins(table_data, WDAT_ACTION_SET_RUNNING_STATE, > + WDAT_INS_WRITE_VALUE | WDAT_INS_PRESERVE_REGISTER, > + tco1_cnt, 0, 0x800); > + build_append_wdat_ins(table_data, WDAT_ACTION_QUERY_STOPPED_STATE, > + WDAT_INS_READ_VALUE, > + tco1_cnt, 0x800, 0x800); > + build_append_wdat_ins(table_data, WDAT_ACTION_SET_STOPPED_STATE, > + WDAT_INS_WRITE_VALUE | WDAT_INS_PRESERVE_REGISTER, > + tco1_cnt, 0x800, 0x800); > + build_append_wdat_ins(table_data, WDAT_ACTION_SET_COUNTDOWN_PERIOD, > + WDAT_INS_WRITE_COUNTDOWN, > + tco_tmr, 0x0, 0x3FF); > + build_append_wdat_ins(table_data, WDAT_ACTION_QUERY_COUNTDOWN_PERIOD, > + WDAT_INS_READ_COUNTDOWN, > + tco_tmr, 0x0, 0x3FF); > + build_append_wdat_ins(table_data, WDAT_ACTION_QUERY_WATCHDOG_STATUS, > + WDAT_INS_READ_VALUE, > + tco2_sts, 0x2, 0x2); > + build_append_wdat_ins(table_data, WDAT_ACTION_SET_WATCHDOG_STATUS, > + WDAT_INS_WRITE_VALUE | WDAT_INS_PRESERVE_REGISTER, > + tco2_sts, 0x2, 0x2); > + build_append_wdat_ins(table_data, WDAT_ACTION_SET_WATCHDOG_STATUS, > + WDAT_INS_WRITE_VALUE | WDAT_INS_PRESERVE_REGISTER, > + tco2_sts, 0x4, 0x4); > + > + acpi_table_end(linker, &table); > +} > diff --git a/hw/i386/acpi-build.c b/hw/i386/acpi-build.c > index 19c62362e3..abb3e936fd 100644 > --- a/hw/i386/acpi-build.c > +++ b/hw/i386/acpi-build.c > @@ -77,6 +77,7 @@ > > #include "hw/acpi/hmat.h" > #include "hw/acpi/viot.h" > +#include "hw/acpi/wdat-ich9.h" > > #include CONFIG_DEVICES > > @@ -109,6 +110,7 @@ typedef struct AcpiPmInfo { > uint16_t cpu_hp_io_base; > uint16_t pcihp_io_base; > uint16_t pcihp_io_len; > + uint64_t tco_io_base; > } AcpiPmInfo; > > typedef struct AcpiMiscInfo { > @@ -203,6 +205,7 @@ static void acpi_get_pm_info(MachineState *machine, > AcpiPmInfo *pm) > pm->pcihp_io_len = 0; > pm->smi_on_cpuhp = false; > pm->smi_on_cpu_unplug = false; > + pm->tco_io_base = 0; > > assert(obj); > init_common_fadt_data(machine, obj, &pm->fadt); > @@ -224,6 +227,8 @@ static void acpi_get_pm_info(MachineState *machine, > AcpiPmInfo *pm) > !!(smi_features & BIT_ULL(ICH9_LPC_SMI_F_CPU_HOTPLUG_BIT)); > pm->smi_on_cpu_unplug = > !!(smi_features & BIT_ULL(ICH9_LPC_SMI_F_CPU_HOT_UNPLUG_BIT)); > + pm->tco_io_base = object_property_get_uint(obj, > ACPI_PM_PROP_PM_IO_BASE, > + NULL) + ICH9_PMIO_TCO_RLD; > } > pm->pcihp_io_base = > object_property_get_uint(obj, ACPI_PCIHP_IO_BASE_PROP, NULL); > @@ -2079,6 +2084,13 @@ void acpi_build(AcpiBuildTables *tables, MachineState > *machine) > acpi_add_table(table_offsets, tables_blob); > build_waet(tables_blob, tables->linker, x86ms->oem_id, > x86ms->oem_table_id); > > + if (machine->acpi_watchdog) { > + g_assert(pm.tco_io_base); > + acpi_add_table(table_offsets, tables_blob); > + build_ich9_wdat(tables_blob, tables->linker, x86ms->oem_id, > + x86ms->oem_table_id, pm.tco_io_base); > + } > + > /* Add tables supplied by user (if any) */ > for (u = acpi_table_first(); u; u = acpi_table_next(u)) { > unsigned len = acpi_table_len(u); > -- > 2.47.3 >
