Add SBSA generic watchdog to virt machine type with all necessary wiring for ACPI watchdog. Which includes setting its frequency to 1KHz (max that WDAT is able to handle).
Signed-off-by: Igor Mammedov <[email protected]> --- include/hw/acpi/wdat-gwdt.h | 19 ++++++++ include/hw/arm/virt.h | 3 ++ hw/acpi/meson.build | 2 + hw/acpi/wdat-gwdt-stub.c | 16 +++++++ hw/acpi/wdat-gwdt.c | 92 +++++++++++++++++++++++++++++++++++++ hw/arm/Kconfig | 1 + hw/arm/virt-acpi-build.c | 16 +++++++ hw/arm/virt.c | 26 +++++++++++ 8 files changed, 175 insertions(+) create mode 100644 include/hw/acpi/wdat-gwdt.h create mode 100644 hw/acpi/wdat-gwdt-stub.c create mode 100644 hw/acpi/wdat-gwdt.c diff --git a/include/hw/acpi/wdat-gwdt.h b/include/hw/acpi/wdat-gwdt.h new file mode 100644 index 0000000000..42339e031e --- /dev/null +++ b/include/hw/acpi/wdat-gwdt.h @@ -0,0 +1,19 @@ +/* + * GWDT Watchdog Action Table (WDAT) definition + * + * Copyright Red Hat, Inc. 2026 + * Author(s): Igor Mammedov <[email protected]> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#ifndef QEMU_HW_ACPI_WDAT_GWDT_H +#define QEMU_HW_ACPI_WDAT_GWDT_H + +#include "hw/acpi/aml-build.h" +#include "hw/watchdog/sbsa_gwdt.h" + +void build_gwdt_wdat(GArray *table_data, BIOSLinker *linker, const char *oem_id, + const char *oem_table_id, uint64_t rbase, uint64_t cbase, + uint64_t freq); + +#endif /* QEMU_HW_ACPI_WDAT_GWDT_H */ diff --git a/include/hw/arm/virt.h b/include/hw/arm/virt.h index 3b382bdf49..d47bb8a72d 100644 --- a/include/hw/arm/virt.h +++ b/include/hw/arm/virt.h @@ -82,6 +82,9 @@ enum { VIRT_NVDIMM_ACPI, VIRT_PVTIME, VIRT_ACPI_PCIHP, + VIRT_GWDT_WS0, + VIRT_GWDT_REFRESH, + VIRT_GWDT_CONTROL, VIRT_LOWMEMMAP_LAST, }; diff --git a/hw/acpi/meson.build b/hw/acpi/meson.build index ba974b2f0f..200b525ae1 100644 --- a/hw/acpi/meson.build +++ b/hw/acpi/meson.build @@ -28,12 +28,14 @@ acpi_ss.add(when: 'CONFIG_ACPI_ICH9', if_true: files('ich9.c', 'ich9_tco.c', 'ic 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')) +acpi_ss.add(when: 'CONFIG_WDT_SBSA', if_true: files('wdat-gwdt.c')) if have_tpm acpi_ss.add(files('tpm.c')) 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(when: 'CONFIG_WDT_SBSA', if_false: files('wdat-gwdt-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-gwdt-stub.c b/hw/acpi/wdat-gwdt-stub.c new file mode 100644 index 0000000000..4d43783f70 --- /dev/null +++ b/hw/acpi/wdat-gwdt-stub.c @@ -0,0 +1,16 @@ +/* + * 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-gwdt.h" + +void build_gwdt_wdat(GArray *table_data, BIOSLinker *linker, const char *oem_id, + const char *oem_table_id, uint64_t rbase, uint64_t cbase, + uint64_t freq) +{ + g_assert_not_reached(); +} diff --git a/hw/acpi/wdat-gwdt.c b/hw/acpi/wdat-gwdt.c new file mode 100644 index 0000000000..226ba3f01e --- /dev/null +++ b/hw/acpi/wdat-gwdt.c @@ -0,0 +1,92 @@ +/* + * SBSA GWDT 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/aml-build.h" +#include "hw/acpi/wdat-gwdt.h" +#include "hw/acpi/wdat.h" +#include "hw/watchdog/sbsa_gwdt.h" + +#define GWDT_REG(base, reg_offset, reg_width) { \ + .space_id = AML_AS_SYSTEM_MEMORY, \ + .address = base + reg_offset, .bit_width = reg_width, \ + .access_width = AML_DWORD_ACC }; + +/* + * "Hardware Watchdog Timers Design Specification" + * https://uefi.org/acpi 'Watchdog Action Table (WDAT)' + */ +void build_gwdt_wdat(GArray *table_data, BIOSLinker *linker, const char *oem_id, + const char *oem_table_id, uint64_t rbase, uint64_t cbase, + uint64_t freq) +{ + AcpiTable table = { .sig = "WDAT", .rev = 1, .oem_id = oem_id, + .oem_table_id = oem_table_id }; + + struct AcpiGenericAddress wrr = GWDT_REG(rbase, 0x0, 32); + struct AcpiGenericAddress wor_l = GWDT_REG(cbase, SBSA_GWDT_WOR, 32); + struct AcpiGenericAddress wcs = GWDT_REG(cbase, SBSA_GWDT_WCS, 32); + + 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 */ + /* + * WDAT spec suports only 1KHz or more coarse watchdog timer, + * Set resolution to minimum supported 1ms. + * Before starting watchdog Windows set countdown value to 5min. + */ + g_assert(freq <= 1000); + build_append_int_noprefix(table_data, 1, 4);/* Timer Period, ms */ + /* + * Needs to be more than 4min, otherwise Windows 11 won't start watchdog. + * Set max to limits to arbitrary max 10min and min to 5sec. + */ + build_append_int_noprefix(table_data, 600 * freq, 4);/* Maximum Count */ + build_append_int_noprefix(table_data, 5 * freq, 4); /* Minimum Count */ + /* + * WATCHDOG_ENABLED + */ + 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, 8, 4); + /* Action table */ + build_append_wdat_ins(table_data, WDAT_ACTION_QUERY_RUNNING_STATE, + WDAT_INS_READ_VALUE, + wcs, 0x1, 0x1); + build_append_wdat_ins(table_data, WDAT_ACTION_RESET, + WDAT_INS_WRITE_VALUE, + wrr, 0x1, 0x7); + build_append_wdat_ins(table_data, WDAT_ACTION_SET_COUNTDOWN_PERIOD, + WDAT_INS_WRITE_COUNTDOWN, + wor_l, 0, 0xffffffff); + build_append_wdat_ins(table_data, WDAT_ACTION_SET_RUNNING_STATE, + WDAT_INS_WRITE_VALUE | WDAT_INS_PRESERVE_REGISTER, + wcs, 1, 0x00000001); + build_append_wdat_ins(table_data, WDAT_ACTION_QUERY_STOPPED_STATE, + WDAT_INS_READ_VALUE, + wcs, 0x0, 0x00000001); + build_append_wdat_ins(table_data, WDAT_ACTION_SET_STOPPED_STATE, + WDAT_INS_WRITE_VALUE | WDAT_INS_PRESERVE_REGISTER, + wcs, 0x0, 0x00000001); + build_append_wdat_ins(table_data, WDAT_ACTION_QUERY_WATCHDOG_STATUS, + WDAT_INS_READ_VALUE, + wcs, 0x4, 0x00000004); + build_append_wdat_ins(table_data, WDAT_ACTION_SET_WATCHDOG_STATUS, + WDAT_INS_WRITE_VALUE | WDAT_INS_PRESERVE_REGISTER, + wrr, 0x4, 0x4); + + acpi_table_end(linker, &table); +} diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index c66c452737..1222efadd1 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -36,6 +36,7 @@ config ARM_VIRT select VIRTIO_MEM_SUPPORTED select ACPI_CXL select ACPI_HMAT + select WDT_SBSA config CUBIEBOARD bool diff --git a/hw/arm/virt-acpi-build.c b/hw/arm/virt-acpi-build.c index c145678185..51a1c040a4 100644 --- a/hw/arm/virt-acpi-build.c +++ b/hw/arm/virt-acpi-build.c @@ -64,6 +64,7 @@ #include "hw/virtio/virtio-acpi.h" #include "target/arm/cpu.h" #include "target/arm/multiprocessing.h" +#include "hw/acpi/wdat-gwdt.h" #define ARM_SPI_BASE 32 @@ -1270,6 +1271,21 @@ void virt_acpi_build(VirtMachineState *vms, AcpiBuildTables *tables) acpi_add_table(table_offsets, tables_blob); build_madt(tables_blob, tables->linker, vms); + acpi_add_table(table_offsets, tables_blob); + if (ms->acpi_watchdog) { + uint64_t freq; + + freq = object_property_get_uint( + object_resolve_type_unambiguous(TYPE_WDT_SBSA, &error_abort), + "clock-frequency", &error_abort); + + build_gwdt_wdat(tables_blob, tables->linker, + vms->oem_id, vms->oem_table_id, + vms->memmap[VIRT_GWDT_REFRESH].base, + vms->memmap[VIRT_GWDT_CONTROL].base, + freq); + } + if (!vmc->no_cpu_topology) { acpi_add_table(table_offsets, tables_blob); build_pptt(tables_blob, tables->linker, ms, diff --git a/hw/arm/virt.c b/hw/arm/virt.c index 390845c503..caf5700ed2 100644 --- a/hw/arm/virt.c +++ b/hw/arm/virt.c @@ -93,6 +93,7 @@ #include "hw/cxl/cxl.h" #include "hw/cxl/cxl_host.h" #include "qemu/guest-random.h" +#include "hw/watchdog/sbsa_gwdt.h" static GlobalProperty arm_virt_compat[] = { { TYPE_VIRTIO_IOMMU_PCI, "aw-bits", "48" }, @@ -194,6 +195,8 @@ static const MemMapEntry base_memmap[] = { [VIRT_PVTIME] = { 0x090a0000, 0x00010000 }, [VIRT_SECURE_GPIO] = { 0x090b0000, 0x00001000 }, [VIRT_ACPI_PCIHP] = { 0x090c0000, ACPI_PCIHP_SIZE }, + [VIRT_GWDT_REFRESH] = { 0x090d0000, 0x00001000 }, + [VIRT_GWDT_CONTROL] = { 0x090d1000, 0x00001000 }, [VIRT_MMIO] = { 0x0a000000, 0x00000200 }, /* ...repeating for a total of NUM_VIRTIO_TRANSPORTS, each of that size */ [VIRT_PLATFORM_BUS] = { 0x0c000000, 0x02000000 }, @@ -245,12 +248,32 @@ static const int a15irqmap[] = { [VIRT_GPIO] = 7, [VIRT_UART1] = 8, [VIRT_ACPI_GED] = 9, + [VIRT_GWDT_WS0] = 10, [VIRT_MMIO] = 16, /* ...to 16 + NUM_VIRTIO_TRANSPORTS - 1 */ [VIRT_GIC_V2M] = 48, /* ...to 48 + NUM_GICV2M_SPIS - 1 */ [VIRT_SMMU] = 74, /* ...to 74 + NUM_SMMU_IRQS - 1 */ [VIRT_PLATFORM_BUS] = 112, /* ...to 112 + PLATFORM_BUS_NUM_IRQS -1 */ }; +static void create_wdt(const VirtMachineState *vms) +{ + hwaddr rbase = vms->memmap[VIRT_GWDT_REFRESH].base; + hwaddr cbase = vms->memmap[VIRT_GWDT_CONTROL].base; + int irq = vms->irqmap[VIRT_GWDT_WS0]; + DeviceState *dev = qdev_new(TYPE_WDT_SBSA); + SysBusDevice *s = SYS_BUS_DEVICE(dev); + + /* + * Set watchdog tick freq to 1Kz as it's the max WDAT driver + * is able to handle. + */ + qdev_prop_set_uint64(dev, "clock-frequency", 1000 /* 1KHz */); + sysbus_realize_and_unref(s, &error_fatal); + sysbus_mmio_map(s, 0, rbase); + sysbus_mmio_map(s, 1, cbase); + sysbus_connect_irq(s, 0, qdev_get_gpio_in(vms->gic, irq)); +} + static void create_randomness(MachineState *ms, const char *node) { struct { @@ -2515,6 +2538,9 @@ static void machvirt_init(MachineState *machine) vms->highmem_ecam &= (!firmware_loaded || aarch64); create_rtc(vms); + if (machine->acpi_watchdog) { + create_wdt(vms); + } create_pcie(vms); create_cxl_host_reg_region(vms); -- 2.47.3
