Currently we only support reset but not poweroff on x86 QEMU.
As we are running on top of QEMU we know the relevant ports.

Signed-off-by: Heinrich Schuchardt <[email protected]>
---
 drivers/sysreset/Kconfig             |   8 ++
 drivers/sysreset/Makefile            |   1 +
 drivers/sysreset/sysreset_qemu_x86.c | 155 +++++++++++++++++++++++++++
 3 files changed, 164 insertions(+)
 create mode 100644 drivers/sysreset/sysreset_qemu_x86.c

diff --git a/drivers/sysreset/Kconfig b/drivers/sysreset/Kconfig
index a046dd02e66..ed4df7cef42 100644
--- a/drivers/sysreset/Kconfig
+++ b/drivers/sysreset/Kconfig
@@ -320,6 +320,14 @@ config SYSRESET_QEMU_VIRT_CTRL
          This device is used in QEMU machines (e.g. m68k virt) to trigger
          system reset and poweroff events.
 
+config SYSRESET_QEMU_X86
+       bool "Support sysreset for QEMU i440fx and q35"
+       depends on SYSRESET
+       depends on X86
+       help
+         This driver supports reboot and poweroff on the i440fx and q35
+         QEMU virtual machines.
+
 endif
 
 endmenu
diff --git a/drivers/sysreset/Makefile b/drivers/sysreset/Makefile
index d18a5d52360..c6f15aa9176 100644
--- a/drivers/sysreset/Makefile
+++ b/drivers/sysreset/Makefile
@@ -33,3 +33,4 @@ obj-$(CONFIG_SYSRESET_RAA215300) += sysreset_raa215300.o
 obj-$(CONFIG_SYSRESET_QCOM_PSHOLD) += sysreset_qcom-pshold.o
 obj-$(CONFIG_TARGET_XTFPGA) += sysreset_xtfpga.o
 obj-$(CONFIG_SYSRESET_QEMU_VIRT_CTRL) += sysreset_qemu_virt_ctrl.o
+obj-$(CONFIG_$(PHASE_)SYSRESET_QEMU_X86) += sysreset_qemu_x86.o
diff --git a/drivers/sysreset/sysreset_qemu_x86.c 
b/drivers/sysreset/sysreset_qemu_x86.c
new file mode 100644
index 00000000000..c1451a87fc8
--- /dev/null
+++ b/drivers/sysreset/sysreset_qemu_x86.c
@@ -0,0 +1,155 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026, Heinrich Schuchardt <[email protected]>
+ *
+ * Poweroff and reset driver for x86 processor on QEMU
+ */
+
+#include <dm.h>
+#include <sysreset.h>
+#include <pci.h>
+#include <pci_ids.h>
+#include <asm/io.h>
+#include <linux/bitops.h>
+
+/* ACPI S5 Soft-Off Register Flags */
+#define ACPI_SLP_TYP_S5 (0) /* Bits 10-12 = 000 */
+#define ACPI_SLP_EN BIT(13) /* Bit 13 = 1 */
+#define QEMU_POWEROFF_WORD (ACPI_SLP_TYP_S5 | ACPI_SLP_EN) /* 0x2000 */
+
+/* Standard x86 Hardware Reset Register Definitions */
+#define X86_RESET_REG_PORT 0x0cf9
+#define X86_SYS_RST BIT(1) /* Bit 1: System Reset */
+#define X86_RST_CPU BIT(2) /* Bit 2: Reset CPU */
+#define X86_FULL_RST BIT(3) /* Bit 3: Full/Cold Reset Toggle */
+
+/* Reset Command Byte Schemes */
+#define X86_WARM_RESET_VAL (X86_SYS_RST | X86_RST_CPU) /* 0x06 */
+#define X86_COLD_RESET_VAL (X86_SYS_RST | X86_RST_CPU | X86_FULL_RST) /* 0x0e 
*/
+
+enum qemu_x86_chipset {
+       CHIPSET_I440FX,
+       CHIPSET_Q35
+};
+
+struct qemu_x86_sysreset_plat {
+       u16 pmbase_reg; /* PCI configuration space offset for PMBASE */
+       u32 pmbase_val; /* Assigned target structural I/O window address */
+       u16 pcicnt_reg; /* Subsystem bit routing enable switch register */
+       u8 pcicnt_val; /* Bitmask to assert the target configuration decoder */
+       u16 pm1_cnt_port; /* Calculated absolute runtime I/O port address */
+};
+
+static int qemu_x86_sysreset_request(struct udevice *dev, enum sysreset_t type)
+{
+       struct qemu_x86_sysreset_plat *plat = dev_get_plat(dev);
+       enum qemu_x86_chipset chipset =
+               (enum qemu_x86_chipset)dev_get_driver_data(dev);
+       struct udevice *pci_bridge_dev = NULL;
+       int ret;
+       u16 product_id;
+
+       switch (type) {
+       case SYSRESET_WARM:
+               /* Issue standard x86 warm reset via I/O Port 0xcf9 */
+               outb(X86_WARM_RESET_VAL, X86_RESET_REG_PORT);
+               break;
+
+       case SYSRESET_COLD:
+               /* Issue standard x86 cold reset via I/O Port 0xcf9 */
+               outb(X86_COLD_RESET_VAL, X86_RESET_REG_PORT);
+               break;
+
+       case SYSRESET_POWER_OFF:
+               if (chipset == CHIPSET_Q35)
+                       /* Intel 82801IR (ICH9R) LPC Interface Controller */
+                       product_id = PCI_DEVICE_ID_INTEL_ICH9_8;
+               else
+                       /* Intel 82371AB/EB/MB PIIX4 ACPI-Controller */
+                       product_id = PCI_DEVICE_ID_INTEL_82371AB_3;
+
+               ret = dm_pci_find_device(PCI_VENDOR_ID_INTEL, product_id, 0,
+                                        &pci_bridge_dev);
+
+               if (ret)
+                       return -ENODEV;
+
+               /* Map the target ACPI base window using DM PCI config write */
+               dm_pci_write_config32(pci_bridge_dev, plat->pmbase_reg,
+                                     plat->pmbase_val);
+
+               /* Enable the hardware bridge routing decoder switch via DM */
+               dm_pci_write_config8(pci_bridge_dev, plat->pcicnt_reg,
+                                    plat->pcicnt_val);
+
+               /* Write S5 word */
+               outw(QEMU_POWEROFF_WORD, plat->pm1_cnt_port);
+               break;
+
+       default:
+               return -ENOSYS;
+       }
+
+       /* Wait for poweroff or reboot */
+       while (1)
+               asm volatile("hlt");
+}
+
+/**
+ * qemu_x86_sysreset_of_to_plat() - setup platform defaults
+ *
+ * @dev:       sysreset device
+ * Return:     0 on success
+ */
+static int qemu_x86_sysreset_of_to_plat(struct udevice *dev)
+{
+       struct qemu_x86_sysreset_plat *plat = dev_get_plat(dev);
+       enum qemu_x86_chipset chipset =
+               (enum qemu_x86_chipset)dev_get_driver_data(dev);
+
+       if (chipset == CHIPSET_Q35) {
+               plat->pmbase_reg =
+                       dev_read_u32_default(dev, "intel,pmbase-reg", 0x40);
+               plat->pmbase_val = dev_read_u32_default(dev, "intel,pmbase-val",
+                                                       0x00000b01);
+               plat->pcicnt_reg =
+                       dev_read_u32_default(dev, "intel,pcicnt-reg", 0x44);
+               plat->pcicnt_val =
+                       dev_read_u32_default(dev, "intel,pcicnt-val", 0x80);
+               plat->pm1_cnt_port =
+                       dev_read_u32_default(dev, "intel,pm1-cnt-port", 0x0b04);
+       } else {
+               plat->pmbase_reg =
+                       dev_read_u32_default(dev, "intel,pmbase-reg", 0x40);
+               plat->pmbase_val = dev_read_u32_default(dev, "intel,pmbase-val",
+                                                       0x00000601);
+               plat->pcicnt_reg =
+                       dev_read_u32_default(dev, "intel,pcicnt-reg", 0x80);
+               plat->pcicnt_val =
+                       dev_read_u32_default(dev, "intel,pcicnt-val", 0x01);
+               plat->pm1_cnt_port =
+                       dev_read_u32_default(dev, "intel,pm1-cnt-port", 0x0604);
+       }
+
+       return 0;
+}
+
+static struct sysreset_ops qemu_x86_sysreset_ops = {
+       .request = qemu_x86_sysreset_request,
+};
+
+static const struct udevice_id qemu_x86_sysreset_ids[] = {
+       { .compatible = "u-boot,i440fx-sysreset", .data = CHIPSET_I440FX },
+       { .compatible = "u-boot,q35-sysreset", .data = CHIPSET_Q35 },
+       {}
+};
+
+U_BOOT_DRIVER(qemu_x86_sysreset) = {
+       .name = "qemu_x86_sysreset",
+       .id = UCLASS_SYSRESET,
+       .of_match = qemu_x86_sysreset_ids,
+       .of_to_plat = qemu_x86_sysreset_of_to_plat,
+       .ops = &qemu_x86_sysreset_ops,
+       .plat_auto = sizeof(struct qemu_x86_sysreset_plat),
+       .flags = DM_FLAG_PRE_RELOC,
+};
-- 
2.53.0

Reply via email to