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
