diff --git a/MAINTAINERS b/MAINTAINERS index 97dcc78ded..cbdfce4866 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3417,13 +3417,16 @@ F: tests/unit/test-ram-discard-manager-stubs.c Memory devices M: David Hildenbrand M: Igor Mammedov +R: FangSheng Huang S: Supported F: hw/mem/memory-device*.c F: hw/mem/nvdimm.c F: hw/mem/pc-dimm.c +F: hw/mem/sp-mem.c F: include/hw/mem/memory-device.h F: include/hw/mem/nvdimm.h F: include/hw/mem/pc-dimm.h +F: include/hw/mem/sp-mem.h F: docs/nvdimm.txt SPICE @@ -4103,6 +4106,7 @@ F: include/hw/i386/intel_iommu.h F: tests/functional/x86_64/test_intel_iommu.py F: tests/qtest/intel-iommu-test.c F: tests/qtest/iommu-intel-test.c +F: tests/qtest/iommu-intel-inv-test.c AMD-Vi Emulation M: Alejandro Jimenez diff --git a/docs/interop/vhost-user.rst b/docs/interop/vhost-user.rst index c83ae2accb..ae8c7ed995 100644 --- a/docs/interop/vhost-user.rst +++ b/docs/interop/vhost-user.rst @@ -214,6 +214,18 @@ fields at the end. :domid: a 32-bit Xen hypervisor specific domain id. +For all memory regions active at a given time: + +- ``[guest address, guest address + size)`` of one memory region never overlaps + the ``[guest address, guest address + size)`` of another memory region. + +- ``[user address, user address + size)`` of one memory region never overlaps + the ``[user address, user address + size)`` of another memory region. + +Violating any of these is a bug in the front-end. This ensures that a guest +address or user address always refers to at most one location in memory. +The front-end must remove a region before it can add an overlapping one. + Single memory region description ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -518,12 +530,26 @@ Rings have two independent states: started/stopped, and enabled/disabled. * started and enabled: The back-end must process the ring normally, i.e. process all requests and execute them. -Each ring is initialized in a stopped and disabled state. The back-end -must start a ring upon receiving a kick (that is, detecting that file -descriptor is readable) on the descriptor specified by -``VHOST_USER_SET_VRING_KICK`` or receiving the in-band message -``VHOST_USER_VRING_KICK`` if negotiated, and stop a ring upon receiving -``VHOST_USER_GET_VRING_BASE``. +Each ring is initialized in a stopped and disabled state. Rings are started +with ``VHOST_USER_SET_VRING_KICK`` (or ``VHOST_USER_VRING_KICK`` if +``VHOST_USER_PROTOCOL_F_INBAND_NOTIFICATIONS`` is negotiated) and stopped with +``VHOST_USER_GET_VRING_BASE``. A stopped ring enters the started state again +with ``VHOST_USER_SET_VRING_KICK`` (or ``VHOST_USER_VRING_KICK`` if +``VHOST_USER_PROTOCOL_F_INBAND_NOTIFICATIONS`` is negotiated) and the back-end +resumes processing requests. + +Note that previous versions of this specification stated that rings start when +the back-end receives a kick (that is, detecting that file descriptor is +readable) on the descriptor specified by ``VHOST_USER_SET_VRING_KICK`` or +receiving the in-band message ``VHOST_USER_VRING_KICK`` if negotiated. +Widely-used front-ends and back-ends did not implement this behavior and it +complicates poll mode back-ends that do not rely on the kick file descriptor. + +For compatibility with back-ends that implemented the start on kick behavior, +front-ends SHOULD inject a kick after ``VHOST_USER_SET_VRING_KICK``. This +ensures that the back-end processes any available requests in the ring. +Back-ends SHOULD NOT rely on receiving a kick after +``VHOST_USER_SET_VRING_KICK``. Rings can be enabled or disabled by ``VHOST_USER_SET_VRING_ENABLE``. @@ -731,6 +757,15 @@ The front-end sends a list of vhost memory regions to the back-end using the ``VHOST_USER_SET_MEM_TABLE`` message. Each region has two base addresses: a guest address and a user address. +Memory regions can be added via the ``VHOST_USER_ADD_MEM_REG`` message. They +can be removed via the ``VHOST_USER_REM_MEM_REG`` message. These messages can +only be used if the ``VHOST_USER_PROTOCOL_F_CONFIGURE_MEM_SLOTS`` protocol +feature has been successfully negotiated. + +Guest addresses are physical addresses in the guest. User addresses are +arbitrary opaque values, though they typically refer to userspace addresses in +the client process. + Messages contain guest addresses and/or user addresses to reference locations within the shared memory. The mapping of these addresses works as follows. diff --git a/docs/system/arm/virt.rst b/docs/system/arm/virt.rst index ae0be35d57..5f6dd17978 100644 --- a/docs/system/arm/virt.rst +++ b/docs/system/arm/virt.rst @@ -39,6 +39,7 @@ The virt board supports: - A PL061 GPIO controller - An optional machine-wide SMMUv3 IOMMU - User-creatable SMMUv3 devices (see below for example) +- An optional SBSA Generic Watchdog Timer (see below) - hotpluggable DIMMs - hotpluggable NVDIMMs - An MSI controller (GICv2m or ITS). @@ -314,6 +315,29 @@ User-creatable SMMUv3 devices bypassing QEMU and improving throughput for workloads that issue many invalidations. Without it, every invalidation command traps into QEMU. +SBSA Generic Watchdog +""""""""""""""""""""" + +The SBSA Generic Watchdog Timer (GWDT) can be added to the virt machine +using ``-device sbsa-gwdt``. It is only supported on the virt machine, +which wires up statically assigned MMIO regions and IRQs via +machine-specific plug handlers. + +Two modes are available: + +Native mode (default) + The watchdog is described via the ACPI GTDT table and FDT, using + the system counter frequency. Example:: + + -device sbsa-gwdt + +WDAT mode + The watchdog is described via the ACPI WDAT table (no FDT node), + using a 1 kHz timer frequency. WDAT and GTDT watchdog entries are + mutually exclusive. Example:: + + -device sbsa-gwdt,wdat=on + Linux guest kernel configuration """""""""""""""""""""""""""""""" diff --git a/hw/acpi/meson.build b/hw/acpi/meson.build index 0c7bfb278a..09ad488a04 100644 --- a/hw/acpi/meson.build +++ b/hw/acpi/meson.build @@ -31,11 +31,13 @@ acpi_ss.add(when: 'CONFIG_ACPI_ERST', if_true: files('erst.c')) acpi_ss.add(when: 'CONFIG_IPMI', if_true: files('ipmi.c')) stub_ss.add(files('ipmi-stub.c')) stub_ss.add(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 stub_ss.add(files('acpi-stub.c', 'aml-build-stub.c', 'ghes-stub.c')) stub_ss.add(files('pci-bridge-stub.c')) +stub_ss.add(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')) stub_ss.add(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 + * + * 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..b30566ef6a --- /dev/null +++ b/hw/acpi/wdat-gwdt.c @@ -0,0 +1,99 @@ +/* + * SBSA GWDT Watchdog Action Table (WDAT) + * + * Copyright Red Hat, Inc. 2026 + * Author(s): Igor Mammedov + * + * 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 */ + /* + * PCI location fields are set to 0xff to indicate + * that the watchdog is not a PCI device. + */ + 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: "The clock interval that the WDT uses must be + * greater than or equal to 1 millisecond." + */ + g_assert(freq <= 1000); + /* Timer Period, ms */ + build_append_int_noprefix(table_data, 1000 / freq, 4); + /* + * WDAT spec: "The time-out period before the WDT fires is recommended + * to be at least 5 minutes." + * Set max count to 10min and min count 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 | 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, 8, 4); + /* Action table: WCS (control/status) register actions */ + 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_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); + /* WOR (offset) and WRR (refresh) register actions */ + build_append_wdat_ins(table_data, WDAT_ACTION_SET_COUNTDOWN_PERIOD, + WDAT_INS_WRITE_COUNTDOWN, + wor_l, 0, 0xffffffff); + /* WRR: any write refreshes the watchdog, value is ignored */ + build_append_wdat_ins(table_data, WDAT_ACTION_RESET, + WDAT_INS_WRITE_VALUE, + wrr, 0x1, 0x1); + 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 5f5c4899ad..f9a225c19b 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 99490aa7b1..f6386088b6 100644 --- a/hw/arm/virt-acpi-build.c +++ b/hw/arm/virt-acpi-build.c @@ -64,6 +64,8 @@ #include "hw/virtio/virtio-acpi.h" #include "target/arm/cpu.h" #include "target/arm/multiprocessing.h" +#include "hw/watchdog/sbsa_gwdt.h" +#include "hw/acpi/wdat-gwdt.h" #include "smmuv3-accel.h" #include "tegra241-cmdqv.h" @@ -858,7 +860,8 @@ build_srat(GArray *table_data, BIOSLinker *linker, VirtMachineState *vms) * 5.2.25 Generic Timer Description Table (GTDT) */ static void -build_gtdt(GArray *table_data, BIOSLinker *linker, VirtMachineState *vms) +build_gtdt(GArray *table_data, BIOSLinker *linker, VirtMachineState *vms, + bool add_watchdog) { /* * Table 5-117 Flag Definitions @@ -868,6 +871,7 @@ build_gtdt(GArray *table_data, BIOSLinker *linker, VirtMachineState *vms) const uint32_t irqflags = 0; /* Interrupt is Level triggered */ AcpiTable table = { .sig = "GTDT", .rev = 3, .oem_id = vms->oem_id, .oem_table_id = vms->oem_table_id }; + uint32_t gtdt_start = table_data->len; acpi_table_begin(&table, table_data); @@ -898,10 +902,15 @@ build_gtdt(GArray *table_data, BIOSLinker *linker, VirtMachineState *vms) build_append_int_noprefix(table_data, irqflags, 4); /* CntReadBase Physical address */ build_append_int_noprefix(table_data, 0xFFFFFFFFFFFFFFFF, 8); + /* Platform Timer Count */ - build_append_int_noprefix(table_data, 0, 4); + build_append_int_noprefix(table_data, add_watchdog ? 1 : 0, 4); /* Platform Timer Offset */ - build_append_int_noprefix(table_data, 0, 4); + build_append_int_noprefix(table_data, + add_watchdog ? (table_data->len - gtdt_start) + + 4 + 4 + 4 /* len of this & following 2 fields to skip */ + : 0, 4); + if (vms->ns_el2_virt_timer_irq) { /* Virtual EL2 Timer GSIV */ build_append_int_noprefix(table_data, ARCH_TIMER_NS_EL2_VIRT_IRQ, 4); @@ -911,6 +920,23 @@ build_gtdt(GArray *table_data, BIOSLinker *linker, VirtMachineState *vms) build_append_int_noprefix(table_data, 0, 4); build_append_int_noprefix(table_data, 0, 4); } + + /* ACPI 6.5 spec: 5.2.25.2 ARM Generic Watchdog Structure (Table 5-124) */ + if (add_watchdog) { + hwaddr rbase = vms->memmap[VIRT_GWDT_REFRESH].base; + hwaddr cbase = vms->memmap[VIRT_GWDT_CONTROL].base; + int irq = ARM_SPI_BASE + vms->irqmap[VIRT_GWDT_WS0]; + + build_append_int_noprefix(table_data, 1 /* Type: Watchdog GT */, 1); + build_append_int_noprefix(table_data, 28 /* Length */, 2); + build_append_int_noprefix(table_data, 0, 1); /* Reserved */ + /* RefreshFrame Physical Address */ + build_append_int_noprefix(table_data, rbase, 8); + /* WatchdogControlFrame Physical Address */ + build_append_int_noprefix(table_data, cbase, 8); + build_append_int_noprefix(table_data, irq, 4); /* Watchdog Timer GSIV */ + build_append_int_noprefix(table_data, 0, 4); /* Watchdog Timer Flags */ + } acpi_table_end(linker, &table); } @@ -1307,13 +1333,19 @@ void virt_acpi_build(VirtMachineState *vms, AcpiBuildTables *tables) VirtMachineClass *vmc = VIRT_MACHINE_GET_CLASS(vms); GArray *table_offsets; unsigned dsdt, xsdt; + bool has_wdat = false; GArray *tables_blob = tables->table_data; MachineState *ms = MACHINE(vms); CPUCoreCaches caches[CPU_MAX_CACHES]; unsigned int num_caches; + Object *wdt = object_resolve_type_unambiguous(TYPE_WDT_SBSA, NULL); num_caches = virt_get_caches(vms, caches); + if (wdt) { + has_wdat = object_property_get_bool(wdt, "wdat", &error_abort); + } + table_offsets = g_array_new(false, true /* clear */, sizeof(uint32_t)); @@ -1332,6 +1364,17 @@ 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 (wdt && has_wdat) { + uint64_t freq = object_property_get_uint(wdt, "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, vms->oem_id, @@ -1339,7 +1382,7 @@ void virt_acpi_build(VirtMachineState *vms, AcpiBuildTables *tables) } acpi_add_table(table_offsets, tables_blob); - build_gtdt(tables_blob, tables->linker, vms); + build_gtdt(tables_blob, tables->linker, vms, wdt && !has_wdat); acpi_add_table(table_offsets, tables_blob); { diff --git a/hw/arm/virt.c b/hw/arm/virt.c index d8d27f2ef6..fb916c341b 100644 --- a/hw/arm/virt.c +++ b/hw/arm/virt.c @@ -95,6 +95,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_defaults[] = { { TYPE_VIRTIO_IOMMU_PCI, "aw-bits", "48" }, @@ -214,6 +215,8 @@ static const MemMapEntry base_memmap[] = { /* ...repeating for a total of NUM_VIRTIO_TRANSPORTS, each of that size */ [VIRT_PLATFORM_BUS] = { 0x0c000000, 0x02000000 }, [VIRT_SECURE_MEM] = { 0x0e000000, 0x01000000 }, + [VIRT_GWDT_REFRESH] = { 0x0f000000, 0x00001000 }, + [VIRT_GWDT_CONTROL] = { 0x0f001000, 0x00001000 }, [VIRT_PCIE_MMIO] = { 0x10000000, 0x2eff0000 }, [VIRT_PCIE_PIO] = { 0x3eff0000, 0x00010000 }, [VIRT_PCIE_ECAM] = { 0x3f000000, 0x01000000 }, @@ -264,6 +267,7 @@ 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 */ @@ -2085,6 +2089,27 @@ static void create_smmu(const VirtMachineState *vms, PCIBus *bus) create_smmuv3_dt_bindings(vms, base, size, irq); } +static void create_gwdt_dt_bindings(VirtMachineState *vms) +{ + MachineState *ms = MACHINE(vms); + hwaddr rbase = vms->memmap[VIRT_GWDT_REFRESH].base; + hwaddr cbase = vms->memmap[VIRT_GWDT_CONTROL].base; + int irq = vms->irqmap[VIRT_GWDT_WS0]; + char *nodename = g_strdup_printf("/watchdog@%" PRIx64, cbase); + + qemu_fdt_add_subnode(ms->fdt, nodename); + qemu_fdt_setprop_string(ms->fdt, nodename, + "compatible", "arm,sbsa-gwdt"); + qemu_fdt_setprop_sized_cells(ms->fdt, nodename, "reg", + 2, cbase, 2, SBSA_GWDT_CMMIO_SIZE, + 2, rbase, 2, SBSA_GWDT_RMMIO_SIZE); + qemu_fdt_setprop_cells(ms->fdt, nodename, "interrupts", + GIC_FDT_IRQ_TYPE_SPI, irq, + GIC_FDT_IRQ_FLAGS_LEVEL_HI); + qemu_fdt_setprop_cell(ms->fdt, nodename, "timeout-sec", 30); + g_free(nodename); +} + static void create_virtio_iommu_dt_bindings(VirtMachineState *vms) { const char compat[] = "virtio,pci-iommu\0pci1af4,1057"; @@ -3820,6 +3845,13 @@ static void virt_machine_device_pre_plug_cb(HotplugHandler *hotplug_dev, qlist_append_str(reserved_regions, resv_prop_str); qdev_prop_set_array(dev, "reserved-regions", reserved_regions); g_free(resv_prop_str); + } else if (object_dynamic_cast(OBJECT(dev), TYPE_WDT_SBSA)) { + if (!object_property_get_bool(OBJECT(dev), "wdat", &error_abort)) { + uint64_t cntfrq = object_property_get_int(OBJECT(qemu_get_cpu(0)), + "cntfrq", &error_abort); + + qdev_prop_set_uint64(dev, "clock-frequency", cntfrq); + } } else if (object_dynamic_cast(OBJECT(dev), TYPE_ARM_SMMUV3)) { if (vms->legacy_smmuv3_present || vms->iommu == VIRT_IOMMU_VIRTIO) { error_setg(errp, "virt machine already has %s set. " @@ -3871,6 +3903,21 @@ static void virt_machine_device_plug_cb(HotplugHandler *hotplug_dev, { VirtMachineState *vms = VIRT_MACHINE(hotplug_dev); + if (object_dynamic_cast(OBJECT(dev), TYPE_WDT_SBSA)) { + SysBusDevice *s = SYS_BUS_DEVICE(dev); + hwaddr rbase = vms->memmap[VIRT_GWDT_REFRESH].base; + hwaddr cbase = vms->memmap[VIRT_GWDT_CONTROL].base; + int irq = vms->irqmap[VIRT_GWDT_WS0]; + + sysbus_mmio_map(s, 0, rbase); + sysbus_mmio_map(s, 1, cbase); + sysbus_connect_irq(s, 0, qdev_get_gpio_in(vms->gic, irq)); + + if (!object_property_get_bool(OBJECT(dev), "wdat", &error_abort)) { + create_gwdt_dt_bindings(vms); + } + } + if (vms->platform_bus_dev) { MachineClass *mc = MACHINE_GET_CLASS(vms); @@ -4123,6 +4170,7 @@ static void virt_machine_class_init(ObjectClass *oc, const void *data) machine_class_allow_dynamic_sysbus_dev(mc, TYPE_RAMFB_DEVICE); machine_class_allow_dynamic_sysbus_dev(mc, TYPE_UEFI_VARS_SYSBUS); machine_class_allow_dynamic_sysbus_dev(mc, TYPE_ARM_SMMUV3); + machine_class_allow_dynamic_sysbus_dev(mc, TYPE_WDT_SBSA); #ifdef CONFIG_TPM machine_class_allow_dynamic_sysbus_dev(mc, TYPE_TPM_TIS_SYSBUS); #endif diff --git a/hw/block/vhost-user-blk.c b/hw/block/vhost-user-blk.c index b2e46eb3f9..2e5b3ae1b1 100644 --- a/hw/block/vhost-user-blk.c +++ b/hw/block/vhost-user-blk.c @@ -66,6 +66,12 @@ static void vhost_user_blk_update_config(VirtIODevice *vdev, uint8_t *config) /* Our num_queues overrides the device backend */ virtio_stw_p(vdev, &s->blkcfg.num_queues, s->num_queues); + if (s->seg_max_adjust) { + uint32_t seg_max = MIN(s->blkcfg.seg_max, s->queue_size - 2); + + virtio_stl_p(vdev, &s->blkcfg.seg_max, seg_max); + } + memcpy(config, &s->blkcfg, vdev->config_len); } @@ -489,6 +495,10 @@ static void vhost_user_blk_device_realize(DeviceState *dev, Error **errp) error_setg(errp, "queue size must be non-zero"); return; } + if (s->queue_size < 4 && s->seg_max_adjust) { + error_setg(errp, "queue size must be >= 4 when seg-max-adjust is set"); + return; + } if (s->queue_size > VIRTQUEUE_MAX_SIZE) { error_setg(errp, "queue size must not exceed %d", VIRTQUEUE_MAX_SIZE); @@ -624,6 +634,8 @@ static const Property vhost_user_blk_properties[] = { DEFINE_PROP_UINT16("num-queues", VHostUserBlk, num_queues, VHOST_USER_BLK_AUTO_NUM_QUEUES), DEFINE_PROP_UINT32("queue-size", VHostUserBlk, queue_size, 128), + DEFINE_PROP_BOOL("seg-max-adjust", VHostUserBlk, seg_max_adjust, + false), DEFINE_PROP_BIT64("config-wce", VHostUserBlk, parent_obj.host_features, VIRTIO_BLK_F_CONFIG_WCE, true), DEFINE_PROP_BIT64("discard", VHostUserBlk, parent_obj.host_features, diff --git a/hw/char/virtio-serial-bus.c b/hw/char/virtio-serial-bus.c index cd234dc6db..c1973f0248 100644 --- a/hw/char/virtio-serial-bus.c +++ b/hw/char/virtio-serial-bus.c @@ -344,22 +344,16 @@ void virtio_serial_throttle_port(VirtIOSerialPort *port, bool throttle) } /* Guest wants to notify us of some event */ -static void handle_control_message(VirtIOSerial *vser, void *buf, size_t len) +static void handle_control_message(VirtIOSerial *vser, + struct virtio_console_control *gcpkt) { VirtIODevice *vdev = VIRTIO_DEVICE(vser); struct VirtIOSerialPort *port; VirtIOSerialPortClass *vsc; - struct virtio_console_control cpkt, *gcpkt; + struct virtio_console_control cpkt; uint8_t *buffer; size_t buffer_len; - gcpkt = buf; - - if (len < sizeof(cpkt)) { - /* The guest sent an invalid control packet */ - return; - } - cpkt.event = virtio_lduw_p(vdev, &gcpkt->event); cpkt.value = virtio_lduw_p(vdev, &gcpkt->value); @@ -457,41 +451,27 @@ static void control_in(VirtIODevice *vdev, VirtQueue *vq) static void control_out(VirtIODevice *vdev, VirtQueue *vq) { + struct virtio_console_control cpkt; VirtQueueElement *elem; VirtIOSerial *vser; - uint8_t *buf; size_t len; vser = VIRTIO_SERIAL(vdev); - len = 0; - buf = NULL; for (;;) { - size_t cur_len; - elem = virtqueue_pop(vq, sizeof(VirtQueueElement)); if (!elem) { break; } - cur_len = iov_size(elem->out_sg, elem->out_num); - /* - * Allocate a new buf only if we didn't have one previously or - * if the size of the buf differs - */ - if (cur_len > len) { - g_free(buf); - - buf = g_malloc(cur_len); - len = cur_len; + len = iov_to_buf(elem->out_sg, elem->out_num, 0, &cpkt, sizeof(cpkt)); + if (len == sizeof(cpkt)) { + handle_control_message(vser, &cpkt); } - iov_to_buf(elem->out_sg, elem->out_num, 0, buf, cur_len); - handle_control_message(vser, buf, cur_len); virtqueue_push(vq, elem, 0); g_free(elem); } - g_free(buf); virtio_notify(vdev, vq); } diff --git a/hw/core/machine-hmp-cmds.c b/hw/core/machine-hmp-cmds.c index 46846f741a..686304bafa 100644 --- a/hw/core/machine-hmp-cmds.c +++ b/hw/core/machine-hmp-cmds.c @@ -279,6 +279,7 @@ void hmp_info_memory_devices(Monitor *mon, const QDict *qdict) PCDIMMDeviceInfo *di; SgxEPCDeviceInfo *se; HvBalloonDeviceInfo *hi; + SpMemDeviceInfo *spmi; for (info = info_list; info; info = info->next) { value = info->value; @@ -350,6 +351,16 @@ void hmp_info_memory_devices(Monitor *mon, const QDict *qdict) monitor_printf(mon, " memdev: %s\n", hi->memdev); } break; + case MEMORY_DEVICE_INFO_KIND_SP_MEM: + spmi = value->u.sp_mem.data; + monitor_printf(mon, "Memory device [%s]: \"%s\"\n", + MemoryDeviceInfoKind_str(value->type), + spmi->id ? spmi->id : ""); + monitor_printf(mon, " addr: 0x%" PRIx64 "\n", spmi->addr); + monitor_printf(mon, " node: %" PRId64 "\n", spmi->node); + monitor_printf(mon, " size: %" PRIu64 "\n", spmi->size); + monitor_printf(mon, " memdev: %s\n", spmi->memdev); + break; default: g_assert_not_reached(); } diff --git a/hw/core/sysbus-fdt.c b/hw/core/sysbus-fdt.c index 89d0c46445..12238570b5 100644 --- a/hw/core/sysbus-fdt.c +++ b/hw/core/sysbus-fdt.c @@ -36,6 +36,7 @@ #include "hw/display/ramfb.h" #include "hw/uefi/var-service-api.h" #include "hw/arm/fdt.h" +#include "hw/watchdog/sbsa_gwdt.h" /* * internal struct that contains the information to create dynamic @@ -140,6 +141,7 @@ static const BindingEntry bindings[] = { TYPE_BINDING(TYPE_ARM_SMMUV3, no_fdt_node), TYPE_BINDING(TYPE_RAMFB_DEVICE, no_fdt_node), TYPE_BINDING(TYPE_UEFI_VARS_SYSBUS, add_uefi_vars_node), + TYPE_BINDING(TYPE_WDT_SBSA, no_fdt_node), TYPE_BINDING("", NULL), /* last element */ }; diff --git a/hw/i386/Kconfig b/hw/i386/Kconfig index 12473acaa7..e27d8816e5 100644 --- a/hw/i386/Kconfig +++ b/hw/i386/Kconfig @@ -84,6 +84,7 @@ config I440FX select PCI_I440FX select PIIX select DIMM + select SP_MEM select SMBIOS select SMBIOS_LEGACY select FW_CFG_DMA @@ -113,6 +114,7 @@ config Q35 select LPC_ICH9 select AHCI_ICH9 select DIMM + select SP_MEM select SMBIOS select FW_CFG_DMA diff --git a/hw/i386/acpi-build.c b/hw/i386/acpi-build.c index 2ee061558c..8837b69687 100644 --- a/hw/i386/acpi-build.c +++ b/hw/i386/acpi-build.c @@ -52,6 +52,7 @@ #include "migration/vmstate.h" #include "hw/mem/memory-device.h" #include "hw/mem/nvdimm.h" +#include "hw/mem/sp-mem.h" #include "system/numa.h" #include "system/reset.h" #include "hw/hyperv/vmbus-bridge.h" @@ -1351,6 +1352,96 @@ build_tpm_tcpa(GArray *table_data, BIOSLinker *linker, GArray *tcpalog, } #endif +typedef struct { + uint64_t addr; + uint64_t size; + uint32_t node; +} SpMemRange; + +static int sp_mem_collect_ranges_cb(Object *obj, void *opaque) +{ + GArray *ranges = opaque; + SpMemDevice *spm; + MemoryDeviceClass *mdc; + SpMemRange r; + + if (!object_dynamic_cast(obj, TYPE_SP_MEM)) { + return 0; + } + spm = SP_MEM(obj); + mdc = MEMORY_DEVICE_GET_CLASS(MEMORY_DEVICE(spm)); + r.addr = mdc->get_addr(MEMORY_DEVICE(spm)); + r.size = memory_region_size( + host_memory_backend_get_memory(spm->hostmem)); + r.node = spm->node; + g_array_append_val(ranges, r); + return 0; +} + +static gint sp_mem_range_compare(gconstpointer a, gconstpointer b) +{ + const SpMemRange *range_a = a; + const SpMemRange *range_b = b; + + if (range_a->addr < range_b->addr) { + return -1; + } + if (range_a->addr > range_b->addr) { + return 1; + } + return 0; +} + +/* + * Emit SRAT memory-affinity entries covering the device_memory region. + * + * For each plugged TYPE_SP_MEM device, emit an ENABLED entry at the + * device's own proximity_domain. All remaining sub-ranges (gaps + * between sp-mem devices, leading and trailing padding, and ranges + * occupied by other memory devices) are covered by HOTPLUGGABLE | + * ENABLED placeholder entries at PXM = nb_numa_nodes - 1. + */ +static void build_srat_device_memory(GArray *table_data, MachineState *ms) +{ + g_autoptr(GArray) ranges = g_array_new(FALSE, TRUE, sizeof(SpMemRange)); + uint32_t hotplug_pxm = ms->numa_state->num_nodes - 1; + uint64_t region_start, region_end; + guint i; + + region_start = ms->device_memory->base; + region_end = region_start + memory_region_size(&ms->device_memory->mr); + + object_child_foreach_recursive(qdev_get_machine(), + sp_mem_collect_ranges_cb, ranges); + g_array_sort(ranges, sp_mem_range_compare); + + for (i = 0; i < ranges->len; i++) { + SpMemRange *r = &g_array_index(ranges, SpMemRange, i); + + if (region_start < r->addr) { + build_srat_memory(table_data, region_start, r->addr - region_start, + hotplug_pxm, + MEM_AFFINITY_HOTPLUGGABLE | + MEM_AFFINITY_ENABLED); + } + build_srat_memory(table_data, r->addr, r->size, r->node, + MEM_AFFINITY_ENABLED); + region_start = r->addr + r->size; + } + + /* + * Cover the rest of the device_memory window that no sp-mem device + * occupies. Keeping it HOTPLUGGABLE preserves the umbrella entry's + * role for future pc-dimm / virtio-mem hot-add into this window. + */ + if (region_start < region_end) { + build_srat_memory(table_data, region_start, region_end - region_start, + hotplug_pxm, + MEM_AFFINITY_HOTPLUGGABLE | + MEM_AFFINITY_ENABLED); + } +} + #define HOLE_640K_START (640 * KiB) #define HOLE_640K_END (1 * MiB) @@ -1487,10 +1578,7 @@ build_srat(GArray *table_data, BIOSLinker *linker, MachineState *machine) * providing _PXM method if necessary. */ if (machine->device_memory) { - build_srat_memory(table_data, machine->device_memory->base, - memory_region_size(&machine->device_memory->mr), - nb_numa_nodes - 1, - MEM_AFFINITY_HOTPLUGGABLE | MEM_AFFINITY_ENABLED); + build_srat_device_memory(table_data, machine); } acpi_table_end(linker, &table); diff --git a/hw/i386/amd_iommu.c b/hw/i386/amd_iommu.c index 79216fb305..90252c52af 100644 --- a/hw/i386/amd_iommu.c +++ b/hw/i386/amd_iommu.c @@ -34,6 +34,7 @@ #include "hw/core/qdev-properties.h" #include "kvm/kvm_i386.h" #include "qemu/iova-tree.h" +#include "hw/core/registerfields.h" struct AMDVIAddressSpace { PCIBus *bus; /* PCIBus (for bus number) */ @@ -88,6 +89,49 @@ typedef struct AMDVIIOTLBKey { uint16_t devid; } AMDVIIOTLBKey; +typedef struct AMDVIIrteGA { + uint64_t ga_lo; + uint64_t ga_hi; +} AMDVIIrteGA; + +/* XT IOMMU General Interrupt Control Register layout */ +FIELD(AMDVI_XT_GEN_INTR, DEST_MODE, 2, 1) +FIELD(AMDVI_XT_GEN_INTR, DEST_LO, 8, 24) +FIELD(AMDVI_XT_GEN_INTR, VECTOR, 32, 8) +FIELD(AMDVI_XT_GEN_INTR, DELIVERY_MODE, 40, 1) +FIELD(AMDVI_XT_GEN_INTR, DEST_HI, 56, 8) + +/* Interrupt Remapping Table Fields Formats */ + +/* Basic 32-bit IRTE layout (GAEn=0) */ +FIELD(AMDVI_IRTE, VALID, 0, 1) +FIELD(AMDVI_IRTE, SUP_IOPF, 1, 1) +FIELD(AMDVI_IRTE, INT_TYPE, 2, 3) +FIELD(AMDVI_IRTE, RQ_EOI, 5, 1) +FIELD(AMDVI_IRTE, DM, 6, 1) +FIELD(AMDVI_IRTE, GUEST_MODE, 7, 1) +FIELD(AMDVI_IRTE, DESTINATION, 8, 8) +FIELD(AMDVI_IRTE, VECTOR, 16, 8) + +/* 128-bit IRTE layout (GAEn=1) */ +FIELD(AMDVI_IRTE_GA_LO, VALID, 0, 1) +FIELD(AMDVI_IRTE_GA_LO, SUP_IOPF, 1, 1) +FIELD(AMDVI_IRTE_GA_LO, INT_TYPE, 2, 3) +FIELD(AMDVI_IRTE_GA_LO, RQ_EOI, 5, 1) +FIELD(AMDVI_IRTE_GA_LO, DM, 6, 1) +FIELD(AMDVI_IRTE_GA_LO, GUEST_MODE, 7, 1) +/* + * In the 128-bit IRTE format, XT mode uses IRTE_GA_LOW.Destination[23:0] + * together with IRTE_GA_HI.DestinationHi[7:0] to construct a 32-bit x2APIC + * destination. + * Without XTEn (i.e. when x2APIC support is not enabled), only + * IRTE_GA_LOW.Destination[7:0] is used. + */ +FIELD(AMDVI_IRTE_GA_LO, DESTINATION, 8, 24) + +FIELD(AMDVI_IRTE_GA_HI, VECTOR, 0, 8) +FIELD(AMDVI_IRTE_GA_HI, DESTINATION_HI, 56, 8) + uint64_t amdvi_extended_feature_register(AMDVIState *s) { uint64_t feature = AMDVI_DEFAULT_EXT_FEATURES; @@ -194,17 +238,17 @@ static void amdvi_assign_andq(AMDVIState *s, hwaddr addr, uint64_t val) static void amdvi_build_xt_msi_msg(AMDVIState *s, MSIMessage *msg) { - union mmio_xt_intr xt_reg; - struct X86IOMMUIrq irq; + uint64_t xt_reg = amdvi_readq(s, AMDVI_MMIO_XT_GEN_INTR); - xt_reg.val = amdvi_readq(s, AMDVI_MMIO_XT_GEN_INTR); - - irq.vector = xt_reg.vector; - irq.delivery_mode = xt_reg.delivery_mode; - irq.dest_mode = xt_reg.destination_mode; - irq.dest = (xt_reg.destination_hi << 24) | xt_reg.destination_lo; - irq.trigger_mode = 0; - irq.redir_hint = 0; + X86IOMMUIrq irq = { + .vector = FIELD_EX64(xt_reg, AMDVI_XT_GEN_INTR, VECTOR), + .delivery_mode = FIELD_EX64(xt_reg, AMDVI_XT_GEN_INTR, DELIVERY_MODE), + .dest_mode = FIELD_EX64(xt_reg, AMDVI_XT_GEN_INTR, DEST_MODE), + .dest = (FIELD_EX64(xt_reg, AMDVI_XT_GEN_INTR, DEST_HI) << 24) | + FIELD_EX64(xt_reg, AMDVI_XT_GEN_INTR, DEST_LO), + .trigger_mode = 0, + .redir_hint = 0, + }; x86_iommu_irq_to_msi_message(&irq, msg); } @@ -234,6 +278,7 @@ static uint32_t get_next_eventlog_entry(AMDVIState *s) static void amdvi_log_event(AMDVIState *s, uint64_t *evt) { + uint64_t le_evt[2]; uint32_t evtlog_tail_next; /* event logging not enabled */ @@ -254,8 +299,14 @@ static void amdvi_log_event(AMDVIState *s, uint64_t *evt) return; } + /* + * Convert event buffer to little-endian before writing it to guest memory. + */ + le_evt[0] = cpu_to_le64(evt[0]); + le_evt[1] = cpu_to_le64(evt[1]); + if (dma_memory_write(&address_space_memory, s->evtlog + s->evtlog_tail, - evt, AMDVI_EVENT_LEN, MEMTXATTRS_UNSPECIFIED)) { + le_evt, AMDVI_EVENT_LEN, MEMTXATTRS_UNSPECIFIED)) { trace_amdvi_evntlog_fail(s->evtlog, s->evtlog_tail); } @@ -501,15 +552,18 @@ static void amdvi_update_iotlb(AMDVIState *s, uint16_t devid, static void amdvi_completion_wait(AMDVIState *s, uint64_t *cmd) { /* pad the last 3 bits */ - hwaddr addr = cpu_to_le64(extract64(cmd[0], 3, 49)) << 3; - uint64_t data = cpu_to_le64(cmd[1]); + hwaddr addr = extract64(cmd[0], 3, 49) << 3; + uint64_t data = cmd[1]; + + /* Format the data to be written to guest memory as little-endian */ + uint64_t le_data = cpu_to_le64(data); if (extract64(cmd[0], 52, 8)) { amdvi_log_illegalcom_error(s, extract64(cmd[0], 60, 4), s->cmdbuf + s->cmdbuf_head); } if (extract64(cmd[0], 0, 1)) { - if (dma_memory_write(&address_space_memory, addr, &data, + if (dma_memory_write(&address_space_memory, addr, &le_data, AMDVI_COMPLETION_DATA_SIZE, MEMTXATTRS_UNSPECIFIED)) { trace_amdvi_completion_wait_fail(addr); @@ -659,7 +713,7 @@ static uint64_t large_pte_page_size(uint64_t pte) * - IOVA exceeds the address width supported by DTE[Mode] * In all such cases a page walk must be aborted. */ -static uint64_t amdvi_get_top_pt_level_and_perms(hwaddr address, uint64_t dte, +static int amdvi_get_top_pt_level_and_perms(hwaddr address, uint64_t dte, uint8_t *top_level, IOMMUAccessFlags *dte_perms) { @@ -702,7 +756,7 @@ static uint64_t amdvi_get_top_pt_level_and_perms(hwaddr address, uint64_t dte, * page table walk. This means that the DTE has valid data, but one of the * lower level entries in the Page Table could not be read. */ -static uint64_t fetch_pte(AMDVIAddressSpace *as, hwaddr address, uint64_t dte, +static int fetch_pte(AMDVIAddressSpace *as, hwaddr address, uint64_t dte, uint64_t *pte, hwaddr *page_size) { uint64_t pte_addr; @@ -1237,7 +1291,7 @@ static void amdvi_update_addr_translation_mode(AMDVIState *s, uint16_t devid) /* log error without aborting since linux seems to be using reserved bits */ static void amdvi_inval_devtab_entry(AMDVIState *s, uint64_t *cmd) { - uint16_t devid = cpu_to_le16((uint16_t)extract64(cmd[0], 0, 16)); + uint16_t devid = extract64(cmd[0], 0, 16); trace_amdvi_devtab_inval(PCI_BUS_NUM(devid), PCI_SLOT(devid), PCI_FUNC(devid)); @@ -1404,9 +1458,9 @@ static void amdvi_sync_domain(AMDVIState *s, uint16_t domid, uint64_t addr, /* we don't have devid - we can't remove pages by address */ static void amdvi_inval_pages(AMDVIState *s, uint64_t *cmd) { - uint16_t domid = cpu_to_le16((uint16_t)extract64(cmd[0], 32, 16)); - uint64_t addr = cpu_to_le64(extract64(cmd[1], 12, 52)) << 12; - uint16_t flags = cpu_to_le16((uint16_t)extract64(cmd[1], 0, 3)); + uint16_t domid = extract64(cmd[0], 32, 16); + uint64_t addr = extract64(cmd[1], 12, 52) << 12; + uint16_t flags = extract64(cmd[1], 0, 3); if (extract64(cmd[0], 20, 12) || extract64(cmd[0], 48, 12) || extract64(cmd[1], 3, 9)) { @@ -1453,7 +1507,7 @@ static void amdvi_inval_inttable(AMDVIState *s, uint64_t *cmd) static void iommu_inval_iotlb(AMDVIState *s, uint64_t *cmd) { - uint16_t devid = cpu_to_le16(extract64(cmd[0], 0, 16)); + uint16_t devid = extract64(cmd[0], 0, 16); if (extract64(cmd[1], 1, 1) || extract64(cmd[1], 3, 1) || extract64(cmd[1], 6, 6)) { amdvi_log_illegalcom_error(s, extract64(cmd[0], 60, 4), @@ -1465,7 +1519,7 @@ static void iommu_inval_iotlb(AMDVIState *s, uint64_t *cmd) g_hash_table_foreach_remove(s->iotlb, amdvi_iotlb_remove_by_devid, &devid); } else { - amdvi_iotlb_remove_page(s, cpu_to_le64(extract64(cmd[1], 12, 52)) << 12, + amdvi_iotlb_remove_page(s, extract64(cmd[1], 12, 52) << 12, devid); } trace_amdvi_iotlb_inval(); @@ -1483,6 +1537,15 @@ static void amdvi_cmdbuf_exec(AMDVIState *s) return; } + /* + * Commands in guest memory are little-endian. Convert once after reading + * so that command handlers can decode values in host native endianness. + * Convert back to little-endian only when writing data to guest memory via + * dma_memory_write(). + */ + cmd[0] = le64_to_cpu(cmd[0]); + cmd[1] = le64_to_cpu(cmd[1]); + switch (extract64(cmd[0], 60, 4)) { case AMDVI_CMD_COMPLETION_WAIT: amdvi_completion_wait(s, cmd); @@ -1509,9 +1572,9 @@ static void amdvi_cmdbuf_exec(AMDVIState *s) amdvi_inval_all(s, cmd); break; default: - trace_amdvi_unhandled_command(extract64(cmd[1], 60, 4)); + trace_amdvi_unhandled_command(extract64(cmd[0], 60, 4)); /* log illegal command */ - amdvi_log_illegalcom_error(s, extract64(cmd[1], 60, 4), + amdvi_log_illegalcom_error(s, extract64(cmd[0], 60, 4), s->cmdbuf + s->cmdbuf_head); } } @@ -1975,7 +2038,7 @@ static IOMMUTLBEntry amdvi_translate(IOMMUMemoryRegion *iommu, hwaddr addr, } static int amdvi_get_irte(AMDVIState *s, MSIMessage *origin, uint64_t *dte, - union irte *irte, uint16_t devid) + uint32_t *irte, uint16_t devid) { uint64_t irte_root, offset; @@ -1990,7 +2053,8 @@ static int amdvi_get_irte(AMDVIState *s, MSIMessage *origin, uint64_t *dte, return -AMDVI_IR_GET_IRTE; } - trace_amdvi_ir_irte_val(irte->val); + *irte = le32_to_cpu(*irte); + trace_amdvi_ir_irte_val(*irte); return 0; } @@ -2002,8 +2066,9 @@ static int amdvi_int_remap_legacy(AMDVIState *iommu, X86IOMMUIrq *irq, uint16_t sid) { + uint8_t int_type; + uint32_t irte; int ret; - union irte irte; /* get interrupt remapping table */ ret = amdvi_get_irte(iommu, origin, dte, &irte, sid); @@ -2011,32 +2076,33 @@ static int amdvi_int_remap_legacy(AMDVIState *iommu, return ret; } - if (!irte.fields.valid) { + if (!FIELD_EX32(irte, AMDVI_IRTE, VALID)) { trace_amdvi_ir_target_abort("RemapEn is disabled"); return -AMDVI_IR_TARGET_ABORT; } - if (irte.fields.guest_mode) { + if (FIELD_EX32(irte, AMDVI_IRTE, GUEST_MODE)) { error_report_once("guest mode is not zero"); return -AMDVI_IR_ERR; } - if (irte.fields.int_type > AMDVI_IOAPIC_INT_TYPE_ARBITRATED) { + int_type = FIELD_EX32(irte, AMDVI_IRTE, INT_TYPE); + if (int_type > AMDVI_IOAPIC_INT_TYPE_ARBITRATED) { error_report_once("reserved int_type"); return -AMDVI_IR_ERR; } - irq->delivery_mode = irte.fields.int_type; - irq->vector = irte.fields.vector; - irq->dest_mode = irte.fields.dm; - irq->redir_hint = irte.fields.rq_eoi; - irq->dest = irte.fields.destination; + irq->delivery_mode = int_type; + irq->vector = FIELD_EX32(irte, AMDVI_IRTE, VECTOR); + irq->dest_mode = FIELD_EX32(irte, AMDVI_IRTE, DM); + irq->redir_hint = FIELD_EX32(irte, AMDVI_IRTE, RQ_EOI); + irq->dest = FIELD_EX32(irte, AMDVI_IRTE, DESTINATION); return 0; } static int amdvi_get_irte_ga(AMDVIState *s, MSIMessage *origin, uint64_t *dte, - struct irte_ga *irte, uint16_t devid) + AMDVIIrteGA *irte, uint16_t devid) { uint64_t irte_root, offset; @@ -2050,7 +2116,9 @@ static int amdvi_get_irte_ga(AMDVIState *s, MSIMessage *origin, uint64_t *dte, return -AMDVI_IR_GET_IRTE; } - trace_amdvi_ir_irte_ga_val(irte->hi.val, irte->lo.val); + irte->ga_lo = le64_to_cpu(irte->ga_lo); + irte->ga_hi = le64_to_cpu(irte->ga_hi); + trace_amdvi_ir_irte_ga_val(irte->ga_hi, irte->ga_lo); return 0; } @@ -2061,8 +2129,9 @@ static int amdvi_int_remap_ga(AMDVIState *iommu, X86IOMMUIrq *irq, uint16_t sid) { + AMDVIIrteGA irte; + uint8_t int_type; int ret; - struct irte_ga irte; /* get interrupt remapping table */ ret = amdvi_get_irte_ga(iommu, origin, dte, &irte, sid); @@ -2070,30 +2139,33 @@ static int amdvi_int_remap_ga(AMDVIState *iommu, return ret; } - if (!irte.lo.fields_remap.valid) { + if (!FIELD_EX64(irte.ga_lo, AMDVI_IRTE_GA_LO, VALID)) { trace_amdvi_ir_target_abort("RemapEn is disabled"); return -AMDVI_IR_TARGET_ABORT; } - if (irte.lo.fields_remap.guest_mode) { + if (FIELD_EX64(irte.ga_lo, AMDVI_IRTE_GA_LO, GUEST_MODE)) { error_report_once("guest mode is not zero"); return -AMDVI_IR_ERR; } - if (irte.lo.fields_remap.int_type > AMDVI_IOAPIC_INT_TYPE_ARBITRATED) { + int_type = FIELD_EX64(irte.ga_lo, AMDVI_IRTE_GA_LO, INT_TYPE); + if (int_type > AMDVI_IOAPIC_INT_TYPE_ARBITRATED) { error_report_once("reserved int_type is set"); return -AMDVI_IR_ERR; } - irq->delivery_mode = irte.lo.fields_remap.int_type; - irq->vector = irte.hi.fields.vector; - irq->dest_mode = irte.lo.fields_remap.dm; - irq->redir_hint = irte.lo.fields_remap.rq_eoi; + irq->delivery_mode = int_type; + irq->vector = FIELD_EX64(irte.ga_hi, AMDVI_IRTE_GA_HI, VECTOR); + irq->dest_mode = FIELD_EX64(irte.ga_lo, AMDVI_IRTE_GA_LO, DM); + irq->redir_hint = FIELD_EX64(irte.ga_lo, AMDVI_IRTE_GA_LO, RQ_EOI); if (iommu->xten) { - irq->dest = irte.lo.fields_remap.destination | - (irte.hi.fields.destination_hi << 24); + irq->dest = FIELD_EX64(irte.ga_lo, AMDVI_IRTE_GA_LO, DESTINATION) | + (FIELD_EX64(irte.ga_hi, AMDVI_IRTE_GA_HI, DESTINATION_HI) + << 24); } else { - irq->dest = irte.lo.fields_remap.destination & 0xff; + irq->dest = FIELD_EX64(irte.ga_lo, AMDVI_IRTE_GA_LO, DESTINATION) & + 0xff; } return 0; diff --git a/hw/i386/amd_iommu.h b/hw/i386/amd_iommu.h index 3cab04a6d4..687691ec1c 100644 --- a/hw/i386/amd_iommu.h +++ b/hw/i386/amd_iommu.h @@ -291,69 +291,6 @@ #define AMDVI_DEV_LINT0_PASS_MASK (1ULL << 62) #define AMDVI_DEV_LINT1_PASS_MASK (1ULL << 63) -/* Interrupt remapping table fields (Guest VAPIC not enabled) */ -union irte { - uint32_t val; - struct { - uint32_t valid:1, - no_fault:1, - int_type:3, - rq_eoi:1, - dm:1, - guest_mode:1, - destination:8, - vector:8, - rsvd:8; - } fields; -}; - -/* Interrupt remapping table fields (Guest VAPIC is enabled) */ -union irte_ga_lo { - uint64_t val; - - /* For int remapping */ - struct { - uint64_t valid:1, - no_fault:1, - /* ------ */ - int_type:3, - rq_eoi:1, - dm:1, - /* ------ */ - guest_mode:1, - destination:24, - rsvd_1:32; - } fields_remap; -}; - -union irte_ga_hi { - uint64_t val; - struct { - uint64_t vector:8, - rsvd_2:48, - destination_hi:8; - } fields; -}; - -struct irte_ga { - union irte_ga_lo lo; - union irte_ga_hi hi; -}; - -union mmio_xt_intr { - uint64_t val; - struct { - uint64_t rsvd_1:2, - destination_mode:1, - rsvd_2:5, - destination_lo:24, - vector:8, - delivery_mode:1, - rsvd_3:15, - destination_hi:8; - }; -}; - #define TYPE_AMD_IOMMU_DEVICE "amd-iommu" OBJECT_DECLARE_SIMPLE_TYPE(AMDVIState, AMD_IOMMU_DEVICE) diff --git a/hw/i386/e820_memory_layout.h b/hw/i386/e820_memory_layout.h index b50acfa201..6ef169db9c 100644 --- a/hw/i386/e820_memory_layout.h +++ b/hw/i386/e820_memory_layout.h @@ -10,11 +10,12 @@ #define HW_I386_E820_MEMORY_LAYOUT_H /* e820 types */ -#define E820_RAM 1 -#define E820_RESERVED 2 -#define E820_ACPI 3 -#define E820_NVS 4 -#define E820_UNUSABLE 5 +#define E820_RAM 1 +#define E820_RESERVED 2 +#define E820_ACPI 3 +#define E820_NVS 4 +#define E820_UNUSABLE 5 +#define E820_SOFT_RESERVED 0xefffffff struct e820_entry { uint64_t address; diff --git a/hw/i386/intel_iommu.c b/hw/i386/intel_iommu.c index 744cdfd2e6..d1af7a3135 100644 --- a/hw/i386/intel_iommu.c +++ b/hw/i386/intel_iommu.c @@ -4988,7 +4988,7 @@ static void vtd_cap_init(IntelIOMMUState *s) { X86IOMMUState *x86_iommu = X86_IOMMU_DEVICE(s); - s->cap = VTD_CAP_FRO | VTD_CAP_NFR | VTD_CAP_ND | VTD_ECAP_PT | + s->cap = VTD_CAP_FRO | VTD_CAP_NFR | VTD_CAP_ND | VTD_CAP_MAMV | VTD_CAP_PSI | VTD_CAP_SSLPS | VTD_CAP_DRAIN | VTD_CAP_ESRTPS | VTD_CAP_MGAW(s->aw_bits); if (x86_iommu->dma_translation) { @@ -4999,7 +4999,7 @@ static void vtd_cap_init(IntelIOMMUState *s) s->cap |= VTD_CAP_SAGAW_48bit; } } - s->ecap = VTD_ECAP_QI | VTD_ECAP_IRO; + s->ecap = VTD_ECAP_QI | VTD_ECAP_IRO | VTD_ECAP_PT; if (x86_iommu_ir_supported(x86_iommu)) { s->ecap |= VTD_ECAP_IR | VTD_ECAP_MHMV; diff --git a/hw/i386/pc.c b/hw/i386/pc.c index 73a625327c..2bacece249 100644 --- a/hw/i386/pc.c +++ b/hw/i386/pc.c @@ -63,6 +63,7 @@ #include "hw/i386/kvm/xen_gnttab.h" #include "hw/i386/kvm/xen_xenstore.h" #include "hw/mem/memory-device.h" +#include "hw/mem/sp-mem.h" #include "e820_memory_layout.h" #include "trace.h" #include "sev.h" @@ -1285,11 +1286,43 @@ static void pc_hv_balloon_plug(HotplugHandler *hotplug_dev, memory_device_plug(MEMORY_DEVICE(dev), MACHINE(hotplug_dev)); } +static void pc_sp_mem_pre_plug(HotplugHandler *hotplug_dev, + DeviceState *dev, Error **errp) +{ + MachineState *ms = MACHINE(hotplug_dev); + SpMemDevice *spm = SP_MEM(dev); + + if (ms->numa_state && spm->node >= ms->numa_state->num_nodes) { + error_setg(errp, + "'node' property value %" PRIu32 + " exceeds the number of NUMA nodes (%d)", + spm->node, ms->numa_state->num_nodes); + return; + } + memory_device_pre_plug(MEMORY_DEVICE(dev), ms, errp); +} + +static void pc_sp_mem_plug(HotplugHandler *hotplug_dev, + DeviceState *dev, Error **errp) +{ + SpMemDevice *spm = SP_MEM(dev); + MemoryDeviceClass *mdc = MEMORY_DEVICE_GET_CLASS(MEMORY_DEVICE(dev)); + uint64_t addr, size; + + memory_device_plug(MEMORY_DEVICE(dev), MACHINE(hotplug_dev)); + + addr = mdc->get_addr(MEMORY_DEVICE(dev)); + size = memory_region_size(host_memory_backend_get_memory(spm->hostmem)); + e820_add_entry(addr, size, E820_SOFT_RESERVED); +} + static void pc_machine_device_pre_plug_cb(HotplugHandler *hotplug_dev, DeviceState *dev, Error **errp) { if (object_dynamic_cast(OBJECT(dev), TYPE_PC_DIMM)) { pc_memory_pre_plug(hotplug_dev, dev, errp); + } else if (object_dynamic_cast(OBJECT(dev), TYPE_SP_MEM)) { + pc_sp_mem_pre_plug(hotplug_dev, dev, errp); } else if (object_dynamic_cast(OBJECT(dev), TYPE_CPU)) { x86_cpu_pre_plug(hotplug_dev, dev, errp); } else if (object_dynamic_cast(OBJECT(dev), TYPE_VIRTIO_MD_PCI)) { @@ -1326,6 +1359,8 @@ static void pc_machine_device_plug_cb(HotplugHandler *hotplug_dev, { if (object_dynamic_cast(OBJECT(dev), TYPE_PC_DIMM)) { pc_memory_plug(hotplug_dev, dev, errp); + } else if (object_dynamic_cast(OBJECT(dev), TYPE_SP_MEM)) { + pc_sp_mem_plug(hotplug_dev, dev, errp); } else if (object_dynamic_cast(OBJECT(dev), TYPE_CPU)) { x86_cpu_plug(hotplug_dev, dev, errp); } else if (object_dynamic_cast(OBJECT(dev), TYPE_VIRTIO_MD_PCI)) { @@ -1370,6 +1405,7 @@ static HotplugHandler *pc_get_hotplug_handler(MachineState *machine, DeviceState *dev) { if (object_dynamic_cast(OBJECT(dev), TYPE_PC_DIMM) || + object_dynamic_cast(OBJECT(dev), TYPE_SP_MEM) || object_dynamic_cast(OBJECT(dev), TYPE_CPU) || object_dynamic_cast(OBJECT(dev), TYPE_VIRTIO_MD_PCI) || object_dynamic_cast(OBJECT(dev), TYPE_VIRTIO_IOMMU_PCI) || diff --git a/hw/mem/Kconfig b/hw/mem/Kconfig index 73c5ae8ad9..39ddb36710 100644 --- a/hw/mem/Kconfig +++ b/hw/mem/Kconfig @@ -16,3 +16,7 @@ config CXL_MEM_DEVICE bool default y if CXL select MEM_DEVICE + +config SP_MEM + bool + select MEM_DEVICE diff --git a/hw/mem/meson.build b/hw/mem/meson.build index 8c2beeb7d4..f410d75475 100644 --- a/hw/mem/meson.build +++ b/hw/mem/meson.build @@ -4,6 +4,7 @@ mem_ss.add(when: 'CONFIG_DIMM', if_true: files('pc-dimm.c')) mem_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx_mc.c')) mem_ss.add(when: 'CONFIG_NVDIMM', if_true: files('nvdimm.c')) mem_ss.add(when: 'CONFIG_CXL_MEM_DEVICE', if_true: files('cxl_type3.c')) +mem_ss.add(when: 'CONFIG_SP_MEM', if_true: files('sp-mem.c')) stub_ss.add(files('cxl_type3_stubs.c')) stub_ss.add(files('memory-device-stubs.c')) diff --git a/hw/mem/sp-mem.c b/hw/mem/sp-mem.c new file mode 100644 index 0000000000..962d0f937e --- /dev/null +++ b/hw/mem/sp-mem.c @@ -0,0 +1,128 @@ +/* + * Specific Purpose Memory (SPM) device + * + * Copyright (c) 2026 Advanced Micro Devices, Inc. + * + * Authors: + * FangSheng Huang + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/module.h" +#include "qapi/error.h" +#include "hw/core/qdev-properties.h" +#include "hw/core/qdev.h" +#include "hw/mem/sp-mem.h" +#include "hw/mem/memory-device.h" +#include "system/hostmem.h" + +#define SP_MEM_MEMDEV_PROP "memdev" +#define SP_MEM_NODE_PROP "node" +#define SP_MEM_ADDR_PROP "addr" + +static const Property sp_mem_properties[] = { + DEFINE_PROP_LINK(SP_MEM_MEMDEV_PROP, SpMemDevice, hostmem, + TYPE_MEMORY_BACKEND, HostMemoryBackend *), + DEFINE_PROP_UINT32(SP_MEM_NODE_PROP, SpMemDevice, node, 0), + DEFINE_PROP_UINT64(SP_MEM_ADDR_PROP, SpMemDevice, addr, 0), +}; + +static uint64_t sp_mem_get_addr(const MemoryDeviceState *md) +{ + return object_property_get_uint(OBJECT(md), SP_MEM_ADDR_PROP, + &error_abort); +} + +static void sp_mem_set_addr(MemoryDeviceState *md, uint64_t addr, + Error **errp) +{ + object_property_set_uint(OBJECT(md), SP_MEM_ADDR_PROP, addr, errp); +} + +static MemoryRegion *sp_mem_get_memory_region(MemoryDeviceState *md, + Error **errp) +{ + SpMemDevice *spm = SP_MEM(md); + + if (!spm->hostmem) { + error_setg(errp, "'%s' property must be set", SP_MEM_MEMDEV_PROP); + return NULL; + } + return host_memory_backend_get_memory(spm->hostmem); +} + +static void sp_mem_fill_device_info(const MemoryDeviceState *md, + MemoryDeviceInfo *info) +{ + SpMemDeviceInfo *di = g_new0(SpMemDeviceInfo, 1); + SpMemDevice *spm = SP_MEM(md); + DeviceState *dev = DEVICE(md); + + di->id = dev->id ? g_strdup(dev->id) : NULL; + di->addr = spm->addr; + di->size = memory_region_size( + host_memory_backend_get_memory(spm->hostmem)); + di->node = spm->node; + di->memdev = object_get_canonical_path(OBJECT(spm->hostmem)); + + info->u.sp_mem.data = di; + info->type = MEMORY_DEVICE_INFO_KIND_SP_MEM; +} + +static void sp_mem_realize(DeviceState *dev, Error **errp) +{ + SpMemDevice *spm = SP_MEM(dev); + + if (!spm->hostmem) { + error_setg(errp, "'%s' property is required", SP_MEM_MEMDEV_PROP); + return; + } + if (host_memory_backend_is_mapped(spm->hostmem)) { + error_setg(errp, "memory backend '%s' is already in use", + object_get_canonical_path_component(OBJECT(spm->hostmem))); + return; + } + host_memory_backend_set_mapped(spm->hostmem, true); +} + +static void sp_mem_unrealize(DeviceState *dev) +{ + SpMemDevice *spm = SP_MEM(dev); + + host_memory_backend_set_mapped(spm->hostmem, false); +} + +static void sp_mem_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + MemoryDeviceClass *mdc = MEMORY_DEVICE_CLASS(oc); + + dc->desc = "SPM (Specific Purpose Memory) device"; + dc->hotpluggable = false; + dc->realize = sp_mem_realize; + dc->unrealize = sp_mem_unrealize; + device_class_set_props(dc, sp_mem_properties); + + mdc->get_addr = sp_mem_get_addr; + mdc->set_addr = sp_mem_set_addr; + mdc->get_memory_region = sp_mem_get_memory_region; + mdc->get_plugged_size = memory_device_get_region_size; + mdc->fill_device_info = sp_mem_fill_device_info; +} + +static const TypeInfo sp_mem_types[] = { + { + .name = TYPE_SP_MEM, + .parent = TYPE_DEVICE, + .class_init = sp_mem_class_init, + .instance_size = sizeof(SpMemDevice), + .interfaces = (InterfaceInfo[]) { + { TYPE_MEMORY_DEVICE }, + { } + }, + }, +}; + +DEFINE_TYPES(sp_mem_types) diff --git a/hw/net/virtio-net.c b/hw/net/virtio-net.c index 2a5d642a64..f0e3beb290 100644 --- a/hw/net/virtio-net.c +++ b/hw/net/virtio-net.c @@ -1374,6 +1374,11 @@ static void virtio_net_unload_ebpf(VirtIONet *n) ebpf_rss_unload(&n->ebpf_rss); } +static bool virtio_net_rss_indirections_len_valid(uint16_t len) +{ + return is_power_of_2(len) && len <= VIRTIO_NET_RSS_MAX_TABLE_LEN; +} + static uint16_t virtio_net_handle_rss(VirtIONet *n, struct iovec *iov, unsigned int iov_cnt, @@ -1411,14 +1416,9 @@ static uint16_t virtio_net_handle_rss(VirtIONet *n, if (!do_rss) { n->rss_data.indirections_len = 0; } - if (n->rss_data.indirections_len >= VIRTIO_NET_RSS_MAX_TABLE_LEN) { - err_msg = "Too large indirection table"; - err_value = n->rss_data.indirections_len; - goto error; - } n->rss_data.indirections_len++; - if (!is_power_of_2(n->rss_data.indirections_len)) { - err_msg = "Invalid size of indirection table"; + if (!virtio_net_rss_indirections_len_valid(n->rss_data.indirections_len)) { + err_msg = "Invalid indirection table length"; err_value = n->rss_data.indirections_len; goto error; } @@ -3427,6 +3427,13 @@ static int virtio_net_rss_post_load(void *opaque, int version_id) n->rss_data.supported_hash_types = VIRTIO_NET_RSS_SUPPORTED_HASHES; } + if (!virtio_net_rss_indirections_len_valid(n->rss_data.indirections_len)) { + error_report("virtio-net: saved image has invalid RSS " + "indirections_len: %u", + n->rss_data.indirections_len); + return -EINVAL; + } + return 0; } diff --git a/hw/virtio/vdpa-dev.c b/hw/virtio/vdpa-dev.c index 94188d37bb..089a77f4d0 100644 --- a/hw/virtio/vdpa-dev.c +++ b/hw/virtio/vdpa-dev.c @@ -172,6 +172,7 @@ static void vhost_vdpa_device_unrealize(DeviceState *dev) { VirtIODevice *vdev = VIRTIO_DEVICE(dev); VhostVdpaDevice *s = VHOST_VDPA_DEVICE(vdev); + struct vhost_virtqueue *vqs = s->dev.vqs; int i; virtio_set_status(vdev, 0); @@ -183,8 +184,8 @@ static void vhost_vdpa_device_unrealize(DeviceState *dev) virtio_cleanup(vdev); g_free(s->config); - g_free(s->dev.vqs); vhost_dev_cleanup(&s->dev); + g_free(vqs); g_free(s->vdpa.shared); qemu_close(s->vhostfd); s->vhostfd = -1; diff --git a/hw/virtio/vhost-user-base.c b/hw/virtio/vhost-user-base.c index 39b5e637fc..478ec68f09 100644 --- a/hw/virtio/vhost-user-base.c +++ b/hw/virtio/vhost-user-base.c @@ -193,9 +193,13 @@ static void do_vhost_user_cleanup(VirtIODevice *vdev, VHostUserBase *vub) { vhost_user_cleanup(&vub->vhost_user); - for (int i = 0; i < vub->num_vqs; i++) { - VirtQueue *vq = g_ptr_array_index(vub->vqs, i); - virtio_delete_queue(vq); + if (vub->vqs) { + for (int i = 0; i < vub->num_vqs; i++) { + VirtQueue *vq = g_ptr_array_index(vub->vqs, i); + virtio_delete_queue(vq); + } + g_ptr_array_free(vub->vqs, true); + vub->vqs = NULL; } virtio_cleanup(vdev); @@ -283,6 +287,7 @@ static void vub_device_realize(DeviceState *dev, Error **errp) VirtIODevice *vdev = VIRTIO_DEVICE(dev); VHostUserBase *vub = VHOST_USER_BASE(dev); uint64_t memory_sizes[VIRTIO_MAX_SHMEM_REGIONS]; + struct vhost_virtqueue *vhost_vqs = NULL; int i, ret, nregions, regions_processed = 0; if (!vub->chardev.chr) { @@ -334,6 +339,7 @@ static void vub_device_realize(DeviceState *dev, Error **errp) vub->vhost_dev.nvqs = vub->num_vqs; vub->vhost_dev.vqs = g_new0(struct vhost_virtqueue, vub->vhost_dev.nvqs); + vhost_vqs = vub->vhost_dev.vqs; /* connect to backend */ ret = vhost_dev_init(&vub->vhost_dev, &vub->vhost_user, @@ -349,7 +355,7 @@ static void vub_device_realize(DeviceState *dev, Error **errp) errp); if (ret < 0) { - goto err; + goto err_vhost_dev; } for (i = 0; i < VIRTIO_MAX_SHMEM_REGIONS && regions_processed < nregions; i++) { @@ -364,14 +370,14 @@ static void vub_device_realize(DeviceState *dev, Error **errp) errp); if (ret < 0) { - goto err; + goto err_vhost_dev; } } if (memory_sizes[i] % qemu_real_host_page_size() != 0) { error_setg(errp, "Shared memory %d size must be a multiple of " "the host page size", i); - goto err; + goto err_vhost_dev; } virtio_new_shmem_region(vdev, i, memory_sizes[i]); @@ -381,7 +387,10 @@ static void vub_device_realize(DeviceState *dev, Error **errp) qemu_chr_fe_set_handlers(&vub->chardev, NULL, NULL, vub_event, NULL, dev, NULL, true); return; +err_vhost_dev: + vhost_dev_cleanup(&vub->vhost_dev); err: + g_free(vhost_vqs); do_vhost_user_cleanup(vdev, vub); } diff --git a/hw/virtio/vhost-user-scmi.c b/hw/virtio/vhost-user-scmi.c index 9470f68c1f..02dc088ea9 100644 --- a/hw/virtio/vhost-user-scmi.c +++ b/hw/virtio/vhost-user-scmi.c @@ -220,11 +220,12 @@ static void vu_scmi_event(void *opaque, QEMUChrEvent event) } } -static void do_vhost_user_cleanup(VirtIODevice *vdev, VHostUserSCMI *scmi) +static void do_vhost_user_cleanup(VirtIODevice *vdev, VHostUserSCMI *scmi, + struct vhost_virtqueue *vhost_vqs) { virtio_delete_queue(scmi->cmd_vq); virtio_delete_queue(scmi->event_vq); - g_free(scmi->vhost_dev.vqs); + g_free(vhost_vqs); virtio_cleanup(vdev); vhost_user_cleanup(&scmi->vhost_user); } @@ -233,6 +234,7 @@ static void vu_scmi_device_realize(DeviceState *dev, Error **errp) { VirtIODevice *vdev = VIRTIO_DEVICE(dev); VHostUserSCMI *scmi = VHOST_USER_SCMI(dev); + struct vhost_virtqueue *vhost_vqs; int ret; if (!scmi->chardev.chr) { @@ -252,13 +254,14 @@ static void vu_scmi_device_realize(DeviceState *dev, Error **errp) scmi->event_vq = virtio_add_queue(vdev, 256, vu_scmi_handle_output); scmi->vhost_dev.nvqs = 2; scmi->vhost_dev.vqs = g_new0(struct vhost_virtqueue, scmi->vhost_dev.nvqs); + vhost_vqs = scmi->vhost_dev.vqs; ret = vhost_dev_init(&scmi->vhost_dev, &scmi->vhost_user, VHOST_BACKEND_TYPE_USER, 0, errp); if (ret < 0) { error_setg_errno(errp, -ret, "vhost-user-scmi: vhost_dev_init() failed"); - do_vhost_user_cleanup(vdev, scmi); + do_vhost_user_cleanup(vdev, scmi, vhost_vqs); return; } @@ -270,10 +273,11 @@ static void vu_scmi_device_unrealize(DeviceState *dev) { VirtIODevice *vdev = VIRTIO_DEVICE(dev); VHostUserSCMI *scmi = VHOST_USER_SCMI(dev); + struct vhost_virtqueue *vhost_vqs = scmi->vhost_dev.vqs; vu_scmi_set_status(vdev, 0); vhost_dev_cleanup(&scmi->vhost_dev); - do_vhost_user_cleanup(vdev, scmi); + do_vhost_user_cleanup(vdev, scmi, vhost_vqs); } static const VMStateDescription vu_scmi_vmstate = { diff --git a/hw/virtio/vhost-user.c b/hw/virtio/vhost-user.c index d627351f45..517cc4ca71 100644 --- a/hw/virtio/vhost-user.c +++ b/hw/virtio/vhost-user.c @@ -1487,7 +1487,29 @@ static int vhost_set_vring_file(struct vhost_dev *dev, static int vhost_user_set_vring_kick(struct vhost_dev *dev, struct vhost_vring_file *file) { - return vhost_set_vring_file(dev, VHOST_USER_SET_VRING_KICK, file); + int ret = vhost_set_vring_file(dev, VHOST_USER_SET_VRING_KICK, file); + if (ret < 0) { + return ret; + } + + /* + * Inject a kick in case the back-end only starts vring processing upon + * receiving a kick. The spec suggests this to improve compatibility. + */ + if (file->fd != -1) { + uint64_t val = 1; + ssize_t nwritten; + + do { + nwritten = write(file->fd, &val, sizeof(val)); + } while (nwritten < 0 && errno == EINTR); + + if (nwritten < 0 && errno != EAGAIN /* back-end can already read */) { + return -errno; + } + } + + return 0; } static int vhost_user_set_vring_call(struct vhost_dev *dev, diff --git a/hw/virtio/virtio-crypto.c b/hw/virtio/virtio-crypto.c index 6fceb39681..79e2acb56c 100644 --- a/hw/virtio/virtio-crypto.c +++ b/hw/virtio/virtio-crypto.c @@ -216,6 +216,12 @@ virtio_crypto_create_asym_session(VirtIOCrypto *vcrypto, return -VIRTIO_CRYPTO_NOTSUPP; } + if (unlikely(keylen > vcrypto->conf.max_size)) { + error_report("virtio-crypto length of akcipher key is too large: %u", + keylen); + return -VIRTIO_CRYPTO_ERR; + } + if (keylen) { asym_info->key = g_malloc(keylen); if (iov_to_buf(iov, out_num, 0, asym_info->key, keylen) != keylen) { diff --git a/hw/watchdog/sbsa_gwdt.c b/hw/watchdog/sbsa_gwdt.c index 7ade5c6f18..330a74798a 100644 --- a/hw/watchdog/sbsa_gwdt.c +++ b/hw/watchdog/sbsa_gwdt.c @@ -24,6 +24,7 @@ #include "migration/vmstate.h" #include "qemu/log.h" #include "qemu/module.h" +#include "trace.h" static const VMStateDescription vmstate_sbsa_gwdt = { .name = "sbsa-gwdt", @@ -62,6 +63,7 @@ static uint64_t sbsa_gwdt_rread(void *opaque, hwaddr addr, unsigned int size) qemu_log_mask(LOG_GUEST_ERROR, "bad address in refresh frame read :" " 0x%x\n", (int)addr); } + trace_sbsa_gwdt_refresh_read(addr, ret); return ret; } @@ -93,6 +95,7 @@ static uint64_t sbsa_gwdt_read(void *opaque, hwaddr addr, unsigned int size) qemu_log_mask(LOG_GUEST_ERROR, "bad address in control frame read :" " 0x%x\n", (int)addr); } + trace_sbsa_gwdt_control_read(addr, ret); return ret; } @@ -127,6 +130,7 @@ static void sbsa_gwdt_rwrite(void *opaque, hwaddr offset, uint64_t data, unsigned size) { SBSA_GWDTState *s = SBSA_GWDT(opaque); + trace_sbsa_gwdt_refresh_write(offset, data); if (offset == SBSA_GWDT_WRR) { s->wcs &= ~(SBSA_GWDT_WCS_WS0 | SBSA_GWDT_WCS_WS1); @@ -141,6 +145,7 @@ static void sbsa_gwdt_write(void *opaque, hwaddr offset, uint64_t data, unsigned size) { SBSA_GWDTState *s = SBSA_GWDT(opaque); + trace_sbsa_gwdt_control_write(offset, data); switch (offset) { case SBSA_GWDT_WCS: s->wcs = data & SBSA_GWDT_WCS_EN; @@ -180,6 +185,7 @@ static void wdt_sbsa_gwdt_reset(DeviceState *dev) { SBSA_GWDTState *s = SBSA_GWDT(dev); + trace_sbsa_gwdt_reset(); timer_del(s->timer); s->wcs = 0; @@ -196,10 +202,12 @@ static void sbsa_gwdt_timer_sysinterrupt(void *opaque) if (!(s->wcs & SBSA_GWDT_WCS_WS0)) { s->wcs |= SBSA_GWDT_WCS_WS0; + trace_sbsa_gwdt_ws0_asserted(); sbsa_gwdt_update_timer(s, TIMEOUT_REFRESH); qemu_set_irq(s->irq, 1); } else { s->wcs |= SBSA_GWDT_WCS_WS1; + trace_sbsa_gwdt_ws1_asserted(); qemu_log_mask(CPU_LOG_RESET, "Watchdog timer expired.\n"); /* * Reset the watchdog only if the guest gets notified about @@ -257,6 +265,14 @@ static void wdt_sbsa_gwdt_realize(DeviceState *dev, Error **errp) sysbus_init_irq(sbd, &s->irq); + /* + * WDAT spec: "The clock interval that the WDT uses must be + * greater than or equal to 1 millisecond." + */ + if (s->wdat) { + s->freq = 1000; + } + s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, sbsa_gwdt_timer_sysinterrupt, dev); } @@ -264,11 +280,11 @@ static void wdt_sbsa_gwdt_realize(DeviceState *dev, Error **errp) static const Property wdt_sbsa_gwdt_props[] = { /* * Timer frequency in Hz. This must match the frequency used by - * the CPU's generic timer. Default 62.5Hz matches QEMU's legacy - * CPU timer frequency default. + * the CPU's generic timer. */ DEFINE_PROP_UINT64("clock-frequency", struct SBSA_GWDTState, freq, - 62500000), + 1000000000), + DEFINE_PROP_BOOL("wdat", struct SBSA_GWDTState, wdat, false), }; static void wdt_sbsa_gwdt_class_init(ObjectClass *klass, const void *data) @@ -278,6 +294,8 @@ static void wdt_sbsa_gwdt_class_init(ObjectClass *klass, const void *data) dc->realize = wdt_sbsa_gwdt_realize; device_class_set_legacy_reset(dc, wdt_sbsa_gwdt_reset); dc->hotpluggable = false; + /* requires machine-specific wiring (MMIO/IRQ/FDT) at plug time */ + dc->user_creatable = true; set_bit(DEVICE_CATEGORY_WATCHDOG, dc->categories); dc->vmsd = &vmstate_sbsa_gwdt; dc->desc = "SBSA-compliant generic watchdog device"; diff --git a/hw/watchdog/trace-events b/hw/watchdog/trace-events index d85b3ca769..b388f7b250 100644 --- a/hw/watchdog/trace-events +++ b/hw/watchdog/trace-events @@ -42,3 +42,12 @@ k230_wdt_interrupt(void) "K230 WDT interrupt" k230_wdt_reset(void) "K230 WDT system reset" k230_wdt_restart(void) "K230 WDT restart" k230_wdt_reset_device(void) "K230 WDT device reset" + +#sbsa_gwdt.c +sbsa_gwdt_refresh_read(uint64_t addr, uint32_t value) "[0x%" PRIx64 "] -> 0x%" PRIx32 +sbsa_gwdt_refresh_write(uint64_t addr, uint64_t value) "[0x%" PRIx64 "] <- 0x%" PRIx64 +sbsa_gwdt_control_read(uint64_t addr, uint32_t value) "[0x%" PRIx64 "] -> 0x%" PRIx32 +sbsa_gwdt_control_write(uint64_t addr, uint64_t value) "[0x%" PRIx64 "] <- 0x%" PRIx64 +sbsa_gwdt_ws0_asserted(void) "WS0 signal is asserted" +sbsa_gwdt_ws1_asserted(void) "WS1 signal is asserted" +sbsa_gwdt_reset(void) "reset watchdog to default state" 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 + * + * 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 171d44c644..22e66d1a11 100644 --- a/include/hw/arm/virt.h +++ b/include/hw/arm/virt.h @@ -97,6 +97,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/include/hw/mem/sp-mem.h b/include/hw/mem/sp-mem.h new file mode 100644 index 0000000000..a8951b49e6 --- /dev/null +++ b/include/hw/mem/sp-mem.h @@ -0,0 +1,33 @@ +/* + * Specific Purpose Memory (SPM) device + * + * TYPE_MEMORY_DEVICE subclass for boot-time-only memory exposed to the + * guest as an E820 SOFT_RESERVED range with a SRAT memory-affinity entry. + * + * Copyright (c) 2026 Advanced Micro Devices, Inc. + * + * Authors: + * FangSheng Huang + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef QEMU_SP_MEM_H +#define QEMU_SP_MEM_H + +#include "hw/core/qdev.h" +#include "qom/object.h" + +#define TYPE_SP_MEM "sp-mem" + +OBJECT_DECLARE_SIMPLE_TYPE(SpMemDevice, SP_MEM) + +struct SpMemDevice { + DeviceState parent_obj; + + HostMemoryBackend *hostmem; + uint32_t node; + uint64_t addr; +}; + +#endif /* QEMU_SP_MEM_H */ diff --git a/include/hw/virtio/vhost-user-blk.h b/include/hw/virtio/vhost-user-blk.h index 1e41a2bcdf..dee848cfd8 100644 --- a/include/hw/virtio/vhost-user-blk.h +++ b/include/hw/virtio/vhost-user-blk.h @@ -34,6 +34,7 @@ struct VHostUserBlk { struct virtio_blk_config blkcfg; uint16_t num_queues; uint32_t queue_size; + bool seg_max_adjust; struct vhost_dev dev; struct vhost_inflight *inflight; VhostUserState vhost_user; diff --git a/include/hw/watchdog/sbsa_gwdt.h b/include/hw/watchdog/sbsa_gwdt.h index 307a4f291a..5ad7c7a798 100644 --- a/include/hw/watchdog/sbsa_gwdt.h +++ b/include/hw/watchdog/sbsa_gwdt.h @@ -16,7 +16,7 @@ #include "hw/core/sysbus.h" #include "hw/core/irq.h" -#define TYPE_WDT_SBSA "sbsa_gwdt" +#define TYPE_WDT_SBSA "sbsa-gwdt" #define SBSA_GWDT(obj) \ OBJECT_CHECK(SBSA_GWDTState, (obj), TYPE_WDT_SBSA) #define SBSA_GWDT_CLASS(klass) \ @@ -73,6 +73,7 @@ typedef struct SBSA_GWDTState { uint32_t woru; uint32_t wcvl; uint32_t wcvu; + bool wdat; } SBSA_GWDTState; #endif /* WDT_SBSA_GWDT_H */ diff --git a/qapi/machine.json b/qapi/machine.json index 685e4e29b8..9b2248038f 100644 --- a/qapi/machine.json +++ b/qapi/machine.json @@ -1413,6 +1413,32 @@ } } +## +# @SpMemDeviceInfo: +# +# sp-mem device state information +# +# @id: device's ID +# +# @addr: physical address, where device is mapped +# +# @size: size of memory that the device provides, in bytes +# +# @node: NUMA proximity domain to which the device is assigned +# +# @memdev: memory backend linked with device +# +# Since: 11.1 +## +{ 'struct': 'SpMemDeviceInfo', + 'data': { '*id': 'str', + 'addr': 'size', + 'size': 'size', + 'node': 'int', + 'memdev': 'str' + } +} + ## # @MemoryDeviceInfoKind: # @@ -1426,11 +1452,13 @@ # # @hv-balloon: since 8.2. # +# @sp-mem: since 11.1. +# # Since: 2.1 ## { 'enum': 'MemoryDeviceInfoKind', 'data': [ 'dimm', 'nvdimm', 'virtio-pmem', 'virtio-mem', 'sgx-epc', - 'hv-balloon' ] } + 'hv-balloon', 'sp-mem' ] } ## # @PCDIMMDeviceInfoWrapper: @@ -1482,6 +1510,16 @@ { 'struct': 'HvBalloonDeviceInfoWrapper', 'data': { 'data': 'HvBalloonDeviceInfo' } } +## +# @SpMemDeviceInfoWrapper: +# +# @data: sp-mem device state information +# +# Since: 11.1 +## +{ 'struct': 'SpMemDeviceInfoWrapper', + 'data': { 'data': 'SpMemDeviceInfo' } } + ## # @MemoryDeviceInfo: # @@ -1499,7 +1537,8 @@ 'virtio-pmem': 'VirtioPMEMDeviceInfoWrapper', 'virtio-mem': 'VirtioMEMDeviceInfoWrapper', 'sgx-epc': 'SgxEPCDeviceInfoWrapper', - 'hv-balloon': 'HvBalloonDeviceInfoWrapper' + 'hv-balloon': 'HvBalloonDeviceInfoWrapper', + 'sp-mem': 'SpMemDeviceInfoWrapper' } } diff --git a/subprojects/libvhost-user/libvhost-user.c b/subprojects/libvhost-user/libvhost-user.c index d2df50e3d0..2c35bddd6f 100644 --- a/subprojects/libvhost-user/libvhost-user.c +++ b/subprojects/libvhost-user/libvhost-user.c @@ -1390,11 +1390,6 @@ vu_check_queue_inflights(VuDev *dev, VuVirtq *vq) vq->counter = vq->resubmit_list[0].counter + 1; } - /* in case of I/O hang after reconnecting */ - if (eventfd_write(vq->kick_fd, 1)) { - return -1; - } - return 0; } @@ -1436,6 +1431,20 @@ vu_set_vring_kick_exec(VuDev *dev, VhostUserMsg *vmsg) vu_panic(dev, "Failed to check inflights for vq: %d\n", index); } + /* Inject a kick to look for available vq buffers */ + if (dev->vq[index].kick_fd != -1) { + int ret; + + do { + ret = eventfd_write(dev->vq[index].kick_fd, 1); + } while (ret != 0 && errno == EINTR); + + if (ret != 0 && errno != EAGAIN /* already readable */) { + vu_panic(dev, "Failed to inject kick during SET_VRING_KICK " + "on vq: %d with error: %m\n", index); + } + } + return false; } diff --git a/tests/data/acpi/aarch64/virt/GTDT.gwdt b/tests/data/acpi/aarch64/virt/GTDT.gwdt new file mode 100644 index 0000000000..44e6f2b18d Binary files /dev/null and b/tests/data/acpi/aarch64/virt/GTDT.gwdt differ diff --git a/tests/data/acpi/aarch64/virt/WDAT.wdat b/tests/data/acpi/aarch64/virt/WDAT.wdat new file mode 100644 index 0000000000..d5adacf911 Binary files /dev/null and b/tests/data/acpi/aarch64/virt/WDAT.wdat differ diff --git a/tests/data/acpi/x86/q35/DSDT.spmem b/tests/data/acpi/x86/q35/DSDT.spmem new file mode 100644 index 0000000000..7e6f850e7a Binary files /dev/null and b/tests/data/acpi/x86/q35/DSDT.spmem differ diff --git a/tests/data/acpi/x86/q35/SRAT.spmem b/tests/data/acpi/x86/q35/SRAT.spmem new file mode 100644 index 0000000000..9ecd266eda Binary files /dev/null and b/tests/data/acpi/x86/q35/SRAT.spmem differ diff --git a/tests/qtest/bios-tables-test.c b/tests/qtest/bios-tables-test.c index af6d9b5136..5cc526510a 100644 --- a/tests/qtest/bios-tables-test.c +++ b/tests/qtest/bios-tables-test.c @@ -1416,6 +1416,26 @@ static void test_acpi_q35_tcg_numamem(void) free_test_data(&data); } +static void test_acpi_q35_tcg_sp_mem(void) +{ + test_data data = {}; + + data.machine = MACHINE_Q35; + data.arch = "x86", + data.variant = ".spmem"; + test_acpi_one(" -m 128M,slots=4,maxmem=1G" + " -object memory-backend-ram,id=ram0,size=128M" + " -numa node,nodeid=0,memdev=ram0" + " -numa node,nodeid=1" + " -numa node,nodeid=2" + " -object memory-backend-ram,id=spm0,size=128M" + " -object memory-backend-ram,id=spm1,size=128M" + " -device sp-mem,id=sp0,memdev=spm0,node=1" + " -device sp-mem,id=sp1,memdev=spm1,node=2", + &data); + free_test_data(&data); +} + static void test_acpi_q35_kvm_xapic(void) { test_data data = {}; @@ -2275,6 +2295,43 @@ static void test_acpi_aarch64_virt_tcg_msi_gicv2m(void) free_test_data(&data); } +static void test_acpi_aarch64_virt_tcg_wdat(void) +{ + test_data data = { + .machine = "virt", + .arch = "aarch64", + .variant = ".wdat", + .tcg_only = true, + .uefi_fl1 = "pc-bios/edk2-aarch64-code.fd", + .uefi_fl2 = "pc-bios/edk2-arm-vars.fd", + .cd = "tests/data/uefi-boot-images/bios-tables-test.aarch64.iso.qcow2", + .ram_start = 0x40000000ULL, + .scan_len = 128ULL * MiB, + }; + + test_acpi_one("-cpu cortex-a57 " + "-device sbsa-gwdt,wdat=on", &data); + free_test_data(&data); +} + +static void test_acpi_aarch64_virt_tcg_gtdt_wd(void) +{ + test_data data = { + .machine = "virt", + .arch = "aarch64", + .variant = ".gwdt", + .tcg_only = true, + .uefi_fl1 = "pc-bios/edk2-aarch64-code.fd", + .uefi_fl2 = "pc-bios/edk2-arm-vars.fd", + .cd = "tests/data/uefi-boot-images/bios-tables-test.aarch64.iso.qcow2", + .ram_start = 0x40000000ULL, + .scan_len = 128ULL * MiB, + }; + + test_acpi_one("-cpu cortex-a57 -device sbsa-gwdt", &data); + free_test_data(&data); +} + static void test_acpi_q35_viot(void) { test_data data = { @@ -2807,6 +2864,7 @@ int main(int argc, char *argv[]) if (strcmp(arch, "i386")) { qtest_add_func("acpi/q35/memhp", test_acpi_q35_tcg_memhp); qtest_add_func("acpi/q35/dimmpxm", test_acpi_q35_tcg_dimm_pxm); + qtest_add_func("acpi/q35/sp-mem", test_acpi_q35_tcg_sp_mem); qtest_add_func("acpi/q35/acpihmat", test_acpi_q35_tcg_acpi_hmat); qtest_add_func("acpi/q35/mmio64", test_acpi_q35_tcg_mmio64); @@ -2893,6 +2951,10 @@ int main(int argc, char *argv[]) qtest_add_func("acpi/virt/smmuv3-dev", test_acpi_aarch64_virt_smmuv3_dev); } + qtest_add_func("acpi/virt/acpi-watchdog", + test_acpi_aarch64_virt_tcg_wdat); + qtest_add_func("acpi/virt/gwdt-watchdog", + test_acpi_aarch64_virt_tcg_gtdt_wd); } } else if (strcmp(arch, "riscv64") == 0) { if (has_tcg && qtest_has_device("virtio-blk-pci")) { diff --git a/tests/qtest/e820-test.c b/tests/qtest/e820-test.c new file mode 100644 index 0000000000..aafa3c1aa2 --- /dev/null +++ b/tests/qtest/e820-test.c @@ -0,0 +1,129 @@ +/* + * qtest e820 fw_cfg test case + * + * Validate the "etc/e820" fw_cfg table that QEMU hands to the firmware. + * + * Copyright (c) 2026 Advanced Micro Devices, Inc. + * + * Authors: + * FangSheng Huang + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" + +#include "libqtest.h" +#include "libqos/fw_cfg.h" +#include "qemu/bswap.h" +#include "qemu/units.h" + +/* e820 entry layout and types (cf. hw/i386/e820_memory_layout.h) */ +#define E820_RAM 1 +#define E820_SOFT_RESERVED 0xefffffff + +struct e820_entry { + uint64_t address; + uint64_t length; + uint32_t type; +} QEMU_PACKED; + +#define E820_MAX_ENTRIES 128 + +/* + * Read and structurally validate "etc/e820": the file is a packed array + * of struct e820_entry, so its size must be a whole multiple of the entry + * size and every entry must have a non-zero length. Returns the entry + * count and fills @table. + */ +static size_t get_e820_table(QFWCFG *fw_cfg, struct e820_entry *table) +{ + size_t filesize, n, i; + + filesize = qfw_cfg_get_file(fw_cfg, "etc/e820", table, + E820_MAX_ENTRIES * sizeof(*table)); + g_assert_cmpint(filesize, >, 0); + g_assert_cmpint(filesize % sizeof(struct e820_entry), ==, 0); + + n = filesize / sizeof(struct e820_entry); + g_assert_cmpint(n, <=, E820_MAX_ENTRIES); + + for (i = 0; i < n; i++) { + g_assert_cmpint(le64_to_cpu(table[i].length), >, 0); + } + + return n; +} + +static void test_e820_basic(void) +{ + struct e820_entry table[E820_MAX_ENTRIES]; + QFWCFG *fw_cfg; + QTestState *s; + size_t n, i; + bool found_ram = false, found_soft_reserved = false; + + s = qtest_init("-machine q35 -m 256M"); + fw_cfg = pc_fw_cfg_init(s); + + n = get_e820_table(fw_cfg, table); + for (i = 0; i < n; i++) { + switch (le32_to_cpu(table[i].type)) { + case E820_RAM: + found_ram = true; + break; + case E820_SOFT_RESERVED: + found_soft_reserved = true; + break; + } + } + + /* baseline: RAM present, no SOFT_RESERVED range */ + g_assert_true(found_ram); + g_assert_false(found_soft_reserved); + + pc_fw_cfg_uninit(fw_cfg); + qtest_quit(s); +} + +static void test_e820_sp_mem(void) +{ + struct e820_entry table[E820_MAX_ENTRIES]; + QFWCFG *fw_cfg; + QTestState *s; + size_t n, i; + int soft_reserved = 0; + uint64_t soft_reserved_len = 0; + + s = qtest_init("-machine q35 -m 256M,slots=2,maxmem=2G " + "-object memory-backend-ram,id=ram0,size=256M " + "-numa node,nodeid=0,memdev=ram0 " + "-object memory-backend-ram,id=spm0,size=128M " + "-device sp-mem,id=sp0,memdev=spm0,node=0"); + fw_cfg = pc_fw_cfg_init(s); + + n = get_e820_table(fw_cfg, table); + for (i = 0; i < n; i++) { + if (le32_to_cpu(table[i].type) == E820_SOFT_RESERVED) { + soft_reserved++; + soft_reserved_len = le64_to_cpu(table[i].length); + } + } + + /* exactly one SOFT_RESERVED range, sized to the backend */ + g_assert_cmpint(soft_reserved, ==, 1); + g_assert_cmpint(soft_reserved_len, ==, 128 * MiB); + + pc_fw_cfg_uninit(fw_cfg); + qtest_quit(s); +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + + qtest_add_func("e820/basic", test_e820_basic); + qtest_add_func("e820/sp-mem", test_e820_sp_mem); + + return g_test_run(); +} diff --git a/tests/qtest/iommu-intel-inv-test.c b/tests/qtest/iommu-intel-inv-test.c new file mode 100644 index 0000000000..1467eef647 --- /dev/null +++ b/tests/qtest/iommu-intel-inv-test.c @@ -0,0 +1,346 @@ +/* + * QTest for Intel IOMMU (VT-d) IOTLB invalidation via Invalidation Queue + * + * Validates that IOTLB invalidation descriptors submitted through the + * queued invalidation interface correctly flush cached translations, + * forcing the IOMMU to re-walk page tables on subsequent DMA. + * + * Copyright (c) 2026 Intel Corporation. + * + * Author: Junjie Cao + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "libqtest.h" +#include "libqos/pci.h" +#include "libqos/pci-pc.h" +#include "hw/i386/intel_iommu_internal.h" +#include "hw/misc/iommu-testdev.h" +#include "libqos/qos-intel-iommu.h" +#include "libqos/qos-iommu-testdev.h" + +#define DMA_LEN 4 + +/* + * Second DMA target page, chosen to fall well outside any address used by + * qos-intel-iommu's fixed structure layout. + */ +#define QVTD_PT_VAL_B (QVTD_MEM_BASE + 0x00200000) + +/* + * A second IOVA/target page for the page-selectivity test. QVTD_IOVA_2 is + * QVTD_IOVA + 4K: it shares the L4/L3/L2 walk built by + * qvtd_build_translation() and differs only in the leaf (L1) slot, so mapping + * it costs one extra leaf PTE. QVTD_PT_VAL_2 is its distinct target page. + */ +#define QVTD_IOVA_2 (QVTD_IOVA + 0x1000) +#define QVTD_PT_VAL_2 (QVTD_MEM_BASE + 0x00300000) + +typedef enum { + IOTLB_INV_GLOBAL, + IOTLB_INV_DOMAIN, + IOTLB_INV_PAGE, +} IOTLBInvGranularity; + +/* + * Core invalidation test, parameterized by translation mode and + * invalidation granularity. + * + * The iommu-testdev device performs DMA writes via the IOMMU (using the + * IOVA) and verifies by reading back from the expected GPA directly. If + * the IOTLB is stale, the DMA write lands at the old PA while readback + * uses the GPA we supply, causing a mismatch (ITD_DMA_ERR_MISMATCH). + * + * Test sequence: + * 1. Setup translation: IOVA -> PA_A + * 2. DMA(gpa=PA_A) -> success (IOTLB populates cache) + * 3. Modify PTE: IOVA -> PA_B (no invalidation) + * 4. DMA(gpa=PA_B) -> MISMATCH (stale IOTLB directs write to PA_A) + * 5. Issue IOTLB invalidation + wait + * 6. DMA(gpa=PA_B) -> success (cache flushed, fresh page walk) + * + * Phase 4 depends on QEMU's IOTLB caching the Phase 1 translation; if a + * future change makes IOTLB caching lazy this assertion would no longer + * exercise the stale-cache path. + */ +static void run_iotlb_inv_test(QVTDTransMode mode, IOTLBInvGranularity gran) +{ + QTestState *qts; + QPCIBus *pcibus; + QPCIDevice *dev; + QPCIBar bar; + uint32_t tail = 0; + uint32_t result; + uint64_t pa_a, pa_b; + + if (!qtest_has_machine("q35")) { + g_test_skip("q35 machine not available"); + return; + } + + qts = qtest_initf("-machine q35 -smp 1 -m 512 -net none " + "%s -device iommu-testdev", + qvtd_iommu_args(mode)); + + if (!qvtd_check_caps(qts, mode)) { + qtest_quit(qts); + return; + } + + dev = qvtd_setup_qtest_pci_device(qts, &pcibus, &bar); + + /* + * The IOMMU translates an IOVA to a page base, then the page offset + * from the IOVA is added. So GPA = page_base + (IOVA & 0xfff). + */ + pa_a = (QVTD_PT_VAL & VTD_PAGE_MASK_4K) + (QVTD_IOVA & 0xfff); + pa_b = (QVTD_PT_VAL_B & VTD_PAGE_MASK_4K) + (QVTD_IOVA & 0xfff); + + /* --- Phase 1: Setup and initial DMA (populates IOTLB) --- */ + qvtd_build_translation(qts, mode, dev->devfn); + qvtd_program_regs(qts, Q35_HOST_BRIDGE_IOMMU_ADDR, mode); + + qtest_memset(qts, pa_a, 0, DMA_LEN); + qtest_memset(qts, pa_b, 0, DMA_LEN); + + result = qos_iommu_testdev_trigger_dma(dev, bar, QVTD_IOVA, pa_a, + DMA_LEN, 0); + g_assert_cmpuint(result, ==, 0); + + /* --- Phase 2: Modify PTE without invalidation -> stale IOTLB --- */ + qtest_writeq(qts, qvtd_leaf_pte_addr(QVTD_IOVA), + qvtd_make_leaf_pte(QVTD_PT_VAL_B & VTD_PAGE_MASK_4K, mode)); + qtest_memset(qts, pa_a, 0, DMA_LEN); + qtest_memset(qts, pa_b, 0, DMA_LEN); + + result = qos_iommu_testdev_trigger_dma(dev, bar, QVTD_IOVA, pa_b, + DMA_LEN, 0); + g_assert_cmpuint(result, ==, ITD_DMA_ERR_MISMATCH); + + /* --- Phase 3: Invalidate IOTLB -> fresh page walk succeeds --- */ + switch (gran) { + case IOTLB_INV_GLOBAL: + tail = qvtd_submit_iotlb_global_inv(qts, Q35_HOST_BRIDGE_IOMMU_ADDR, + tail); + break; + case IOTLB_INV_DOMAIN: + tail = qvtd_submit_iotlb_domain_inv(qts, Q35_HOST_BRIDGE_IOMMU_ADDR, + QVTD_DOMAIN_ID, tail); + break; + case IOTLB_INV_PAGE: + tail = qvtd_submit_iotlb_page_inv(qts, Q35_HOST_BRIDGE_IOMMU_ADDR, + QVTD_DOMAIN_ID, QVTD_IOVA, 0, + tail); + break; + } + tail = qvtd_submit_inv_wait_and_poll(qts, Q35_HOST_BRIDGE_IOMMU_ADDR, + tail); + + qtest_memset(qts, pa_a, 0, DMA_LEN); + qtest_memset(qts, pa_b, 0, DMA_LEN); + + result = qos_iommu_testdev_trigger_dma(dev, bar, QVTD_IOVA, pa_b, + DMA_LEN, 0); + g_assert_cmpuint(result, ==, 0); + + g_free(dev); + qpci_free_pc(pcibus); + qtest_quit(qts); +} + +/* + * Page-selectivity test: verify that a page-selective invalidation flushes + * the named page and touches other cached pages only as far as the model + * intends. run_iotlb_inv_test() caches a single entry, so it cannot tell a + * page-selective flush from a domain-wide or global one; this test caches two + * pages in the same domain and checks the second one's fate. + * + * The expected fate of the second page depends on the translation level: + * + * - second-level (legacy / scalable-slt): a page-selective descriptor + * evicts only the matching gfn, so IOVA_2 survives. + * - first-level (scalable-flt): QEMU invalidates all first-stage entries of + * the domain on a page-selective descriptor (vtd_hash_remove_by_page() + * returns true for any pgtt==FST entry of the domain, matching the VT-d + * spec for first-stage IOTLB invalidation), so IOVA_2 is flushed too. + * + * Method: map IOVA -> PA_A and IOVA_2 -> PA_A_2, DMA both to populate two + * IOTLB entries, rewrite both leaf PTEs to PA_B* without invalidating, then + * page-invalidate IOVA only. IOVA always re-walks to PA_B. For IOVA_2 we + * verify the DMA against its *original* page PA_A_2: if the entry survived, + * the stale cache still serves PA_A_2 (success); if it was flushed, the fresh + * walk reaches PA_B_2 and mismatches PA_A_2. So a survived entry gives + * success and a flushed entry gives MISMATCH, and we assert whichever the + * mode requires -- catching both an over-matching second-level flush and a + * regression that stopped flushing first-stage entries domain-wide. + */ +static void run_page_selectivity_test(QVTDTransMode mode) +{ + QTestState *qts; + QPCIBus *pcibus; + QPCIDevice *dev; + QPCIBar bar; + uint32_t tail = 0; + uint32_t result; + uint64_t pa_a, pa_b, pa_a2, pa_b2; + bool fl_domain_wide = (mode == QVTD_TM_SCALABLE_FLT); + + if (!qtest_has_machine("q35")) { + g_test_skip("q35 machine not available"); + return; + } + + qts = qtest_initf("-machine q35 -smp 1 -m 512 -net none " + "%s -device iommu-testdev", + qvtd_iommu_args(mode)); + + if (!qvtd_check_caps(qts, mode)) { + qtest_quit(qts); + return; + } + + dev = qvtd_setup_qtest_pci_device(qts, &pcibus, &bar); + + pa_a = (QVTD_PT_VAL & VTD_PAGE_MASK_4K) + (QVTD_IOVA & 0xfff); + pa_b = (QVTD_PT_VAL_B & VTD_PAGE_MASK_4K) + (QVTD_IOVA & 0xfff); + pa_a2 = (QVTD_PT_VAL_2 & VTD_PAGE_MASK_4K) + (QVTD_IOVA_2 & 0xfff); + pa_b2 = (QVTD_PT_VAL_B & VTD_PAGE_MASK_4K) + (QVTD_IOVA_2 & 0xfff); + + /* --- Setup: IOVA -> PA_A (built by helper) and IOVA_2 -> PA_A_2 --- */ + qvtd_build_translation(qts, mode, dev->devfn); + qtest_writeq(qts, qvtd_leaf_pte_addr(QVTD_IOVA_2), + qvtd_make_leaf_pte(QVTD_PT_VAL_2 & VTD_PAGE_MASK_4K, mode)); + qvtd_program_regs(qts, Q35_HOST_BRIDGE_IOMMU_ADDR, mode); + + /* Populate both IOTLB entries. */ + qtest_memset(qts, pa_a, 0, DMA_LEN); + qtest_memset(qts, pa_a2, 0, DMA_LEN); + result = qos_iommu_testdev_trigger_dma(dev, bar, QVTD_IOVA, pa_a, + DMA_LEN, 0); + g_assert_cmpuint(result, ==, 0); + result = qos_iommu_testdev_trigger_dma(dev, bar, QVTD_IOVA_2, pa_a2, + DMA_LEN, 0); + g_assert_cmpuint(result, ==, 0); + + /* Rewrite both leaf PTEs to PA_B* without invalidating. */ + qtest_writeq(qts, qvtd_leaf_pte_addr(QVTD_IOVA), + qvtd_make_leaf_pte(QVTD_PT_VAL_B & VTD_PAGE_MASK_4K, mode)); + qtest_writeq(qts, qvtd_leaf_pte_addr(QVTD_IOVA_2), + qvtd_make_leaf_pte(QVTD_PT_VAL_B & VTD_PAGE_MASK_4K, mode)); + + /* Page-selective invalidation of IOVA only. */ + tail = qvtd_submit_iotlb_page_inv(qts, Q35_HOST_BRIDGE_IOMMU_ADDR, + QVTD_DOMAIN_ID, QVTD_IOVA, 0, tail); + tail = qvtd_submit_inv_wait_and_poll(qts, Q35_HOST_BRIDGE_IOMMU_ADDR, + tail); + + /* IOVA was flushed: fresh walk reaches PA_B. */ + qtest_memset(qts, pa_a, 0, DMA_LEN); + qtest_memset(qts, pa_b, 0, DMA_LEN); + result = qos_iommu_testdev_trigger_dma(dev, bar, QVTD_IOVA, pa_b, + DMA_LEN, 0); + g_assert_cmpuint(result, ==, 0); + + /* + * IOVA_2's fate, verified against its original page PA_A_2: + * - second-level: entry survives, stale cache serves PA_A_2 -> success; + * - first-level: entry was flushed domain-wide, fresh walk reaches + * PA_B_2 -> MISMATCH against PA_A_2. + */ + qtest_memset(qts, pa_a2, 0, DMA_LEN); + qtest_memset(qts, pa_b2, 0, DMA_LEN); + result = qos_iommu_testdev_trigger_dma(dev, bar, QVTD_IOVA_2, pa_a2, + DMA_LEN, 0); + if (fl_domain_wide) { + g_assert_cmpuint(result, ==, ITD_DMA_ERR_MISMATCH); + } else { + g_assert_cmpuint(result, ==, 0); + } + + g_free(dev); + qpci_free_pc(pcibus); + qtest_quit(qts); +} + +/* + * scalable-flt is covered here even though, per the VT-d spec, first-level + * mappings are invalidated with the PASID-based descriptor + * (VTD_INV_DESC_PIOTLB). QEMU keeps first- and second-level mappings in a + * single IOTLB that the legacy VTD_INV_DESC_IOTLB descriptor flushes for + * every level, so this test drives that descriptor across all modes. + * PASID-selective (PIOTLB) invalidation is a separate path, left for a + * follow-up. + */ +static const struct { + const char *name; + QVTDTransMode mode; +} trans_modes[] = { + { "legacy", QVTD_TM_LEGACY_TRANS }, + { "scalable-slt", QVTD_TM_SCALABLE_SLT }, + { "scalable-flt", QVTD_TM_SCALABLE_FLT }, +}; + +static const struct { + const char *name; + IOTLBInvGranularity gran; +} granularities[] = { + { "global", IOTLB_INV_GLOBAL }, + { "domain", IOTLB_INV_DOMAIN }, + { "page", IOTLB_INV_PAGE }, +}; + +typedef struct { + QVTDTransMode mode; + IOTLBInvGranularity gran; +} TestCase; + +static void test_iotlb_inv(const void *opaque) +{ + const TestCase *tc = opaque; + + run_iotlb_inv_test(tc->mode, tc->gran); +} + +static void test_page_selectivity(const void *opaque) +{ + const QVTDTransMode *mode = opaque; + + run_page_selectivity_test(*mode); +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + + for (size_t m = 0; m < ARRAY_SIZE(trans_modes); m++) { + for (size_t g = 0; g < ARRAY_SIZE(granularities); g++) { + TestCase *tc = g_new(TestCase, 1); + char *path; + + tc->mode = trans_modes[m].mode; + tc->gran = granularities[g].gran; + + path = g_strdup_printf("/iommu-testdev/intel/iotlb-inv/%s-%s", + granularities[g].name, + trans_modes[m].name); + qtest_add_data_func_full(path, tc, test_iotlb_inv, g_free); + g_free(path); + } + } + + for (size_t m = 0; m < ARRAY_SIZE(trans_modes); m++) { + QVTDTransMode *mode = g_new(QVTDTransMode, 1); + char *path; + + *mode = trans_modes[m].mode; + path = g_strdup_printf( + "/iommu-testdev/intel/iotlb-inv/page-selective/%s", + trans_modes[m].name); + qtest_add_data_func_full(path, mode, test_page_selectivity, g_free); + g_free(path); + } + + return g_test_run(); +} diff --git a/tests/qtest/iommu-intel-test.c b/tests/qtest/iommu-intel-test.c index 703bbfef73..c2744def6f 100644 --- a/tests/qtest/iommu-intel-test.c +++ b/tests/qtest/iommu-intel-test.c @@ -24,82 +24,6 @@ static uint64_t intel_iommu_expected_gpa(uint64_t iova) return (QVTD_PT_VAL & VTD_PAGE_MASK_4K) + (iova & 0xfff); } -static void save_fn(QPCIDevice *dev, int devfn, void *data) -{ - QPCIDevice **pdev = (QPCIDevice **) data; - - *pdev = dev; -} - -static QPCIDevice *setup_qtest_pci_device(QTestState *qts, QPCIBus **pcibus, - QPCIBar *bar) -{ - QPCIDevice *dev = NULL; - - *pcibus = qpci_new_pc(qts, NULL); - g_assert(*pcibus != NULL); - - qpci_device_foreach(*pcibus, IOMMU_TESTDEV_VENDOR_ID, - IOMMU_TESTDEV_DEVICE_ID, save_fn, &dev); - - g_assert(dev); - qpci_device_enable(dev); - *bar = qpci_iomap(dev, 0, NULL); - g_assert_false(bar->is_io); - - return dev; -} - -static const char *qvtd_iommu_args(QVTDTransMode mode) -{ - switch (mode) { - case QVTD_TM_SCALABLE_FLT: - return "-device intel-iommu,scalable-mode=on,fsts=on "; - case QVTD_TM_SCALABLE_PT: - case QVTD_TM_SCALABLE_SLT: - return "-device intel-iommu,scalable-mode=on "; - default: - return "-device intel-iommu "; - } -} - -static bool qvtd_check_caps(QTestState *qts, QVTDTransMode mode) -{ - uint64_t ecap = qtest_readq(qts, - Q35_HOST_BRIDGE_IOMMU_ADDR + DMAR_ECAP_REG); - - /* All scalable modes require SMTS */ - if (qvtd_is_scalable(mode) && !(ecap & VTD_ECAP_SMTS)) { - g_test_skip("ECAP.SMTS not supported"); - return false; - } - - switch (mode) { - case QVTD_TM_SCALABLE_PT: - if (!(ecap & VTD_ECAP_PT)) { - g_test_skip("ECAP.PT not supported"); - return false; - } - break; - case QVTD_TM_SCALABLE_SLT: - if (!(ecap & VTD_ECAP_SSTS)) { - g_test_skip("ECAP.SSTS not supported"); - return false; - } - break; - case QVTD_TM_SCALABLE_FLT: - if (!(ecap & VTD_ECAP_FSTS)) { - g_test_skip("ECAP.FSTS not supported"); - return false; - } - break; - default: - break; - } - - return true; -} - static void run_intel_iommu_translation(const QVTDTestConfig *cfg) { QTestState *qts; @@ -124,7 +48,7 @@ static void run_intel_iommu_translation(const QVTDTestConfig *cfg) } /* Setup and configure IOMMU-testdev PCI device */ - dev = setup_qtest_pci_device(qts, &pcibus, &bar); + dev = qvtd_setup_qtest_pci_device(qts, &pcibus, &bar); g_assert(dev); g_test_message("### Intel IOMMU translation mode=%d ###", cfg->trans_mode); diff --git a/tests/qtest/libqos/qos-intel-iommu.c b/tests/qtest/libqos/qos-intel-iommu.c index f8ca4c871b..c793ec899c 100644 --- a/tests/qtest/libqos/qos-intel-iommu.c +++ b/tests/qtest/libqos/qos-intel-iommu.c @@ -11,9 +11,14 @@ #include "qemu/osdep.h" #include "hw/i386/intel_iommu_internal.h" #include "tests/qtest/libqos/pci.h" +#include "tests/qtest/libqos/pci-pc.h" #include "qos-iommu-testdev.h" #include "qos-intel-iommu.h" +/* Bounded poll for the invalidation-wait Status Write. */ +#define QVTD_INV_WAIT_POLL_MAX_ITERS 1000 +#define QVTD_INV_WAIT_POLL_INTERVAL_US 1000 + #define QVTD_AW_48BIT_ENCODING 2 uint32_t qvtd_expected_dma_result(QVTDTestContext *ctx) @@ -231,6 +236,30 @@ static uint64_t qvtd_get_fl_pte_attrs(bool is_leaf) return attrs; } +uint64_t qvtd_leaf_pte_addr(uint64_t iova) +{ + return qvtd_get_table_addr(QVTD_PT_L1_BASE, 1, iova); +} + +uint64_t qvtd_make_leaf_pte(uint64_t pa, QVTDTransMode mode) +{ + uint64_t attrs; + + /* + * Reuse the same leaf attributes qvtd_setup_translation_tables() writes, + * so a rewritten PTE stays consistent with the initial mapping. US=1 in + * the first-level format is required because the PASID entry runs with + * SRE=0, which makes every DMA appear as a user-mode access + * (VT-d 3.6.2 / 9.9). + */ + if (mode == QVTD_TM_SCALABLE_FLT) { + attrs = qvtd_get_fl_pte_attrs(true); + } else { + attrs = qvtd_get_pte_attrs(); + } + return (pa & VTD_PAGE_MASK_4K) | attrs; +} + void qvtd_setup_translation_tables(QTestState *qts, uint64_t iova, QVTDTransMode mode) { @@ -452,3 +481,159 @@ void qvtd_run_translation_case(QTestState *qts, QPCIDevice *dev, } } } + +const char *qvtd_iommu_args(QVTDTransMode mode) +{ + switch (mode) { + case QVTD_TM_SCALABLE_FLT: + return "-device intel-iommu,scalable-mode=on,fsts=on "; + case QVTD_TM_SCALABLE_PT: + case QVTD_TM_SCALABLE_SLT: + return "-device intel-iommu,scalable-mode=on "; + default: + return "-device intel-iommu "; + } +} + +bool qvtd_check_caps(QTestState *qts, QVTDTransMode mode) +{ + uint64_t ecap = qtest_readq(qts, + Q35_HOST_BRIDGE_IOMMU_ADDR + DMAR_ECAP_REG); + + /* All scalable modes require SMTS */ + if (qvtd_is_scalable(mode) && !(ecap & VTD_ECAP_SMTS)) { + g_test_skip("ECAP.SMTS not supported"); + return false; + } + + switch (mode) { + case QVTD_TM_SCALABLE_PT: + if (!(ecap & VTD_ECAP_PT)) { + g_test_skip("ECAP.PT not supported"); + return false; + } + break; + case QVTD_TM_SCALABLE_SLT: + if (!(ecap & VTD_ECAP_SSTS)) { + g_test_skip("ECAP.SSTS not supported"); + return false; + } + break; + case QVTD_TM_SCALABLE_FLT: + if (!(ecap & VTD_ECAP_FSTS)) { + g_test_skip("ECAP.FSTS not supported"); + return false; + } + break; + default: + break; + } + + return true; +} + +static void qvtd_save_pci_dev(QPCIDevice *dev, int devfn, void *data) +{ + QPCIDevice **pdev = (QPCIDevice **)data; + + *pdev = dev; +} + +QPCIDevice *qvtd_setup_qtest_pci_device(QTestState *qts, QPCIBus **pcibus, + QPCIBar *bar) +{ + QPCIDevice *dev = NULL; + + *pcibus = qpci_new_pc(qts, NULL); + g_assert(*pcibus != NULL); + + qpci_device_foreach(*pcibus, IOMMU_TESTDEV_VENDOR_ID, + IOMMU_TESTDEV_DEVICE_ID, qvtd_save_pci_dev, &dev); + + g_assert(dev); + qpci_device_enable(dev); + *bar = qpci_iomap(dev, 0, NULL); + g_assert_false(bar->is_io); + + return dev; +} + +/* + * Write a 128-bit invalidation descriptor at the current tail and advance + * IQT_REG. Internal helper for the IOTLB / wait variants below. + */ +static uint32_t qvtd_submit_inv_desc(QTestState *qts, uint64_t iommu_base, + uint64_t desc_lo, uint64_t desc_hi, + uint32_t tail) +{ + uint64_t desc_addr = QVTD_IQ_BASE + (uint64_t)tail * QVTD_IQ_DESC_SIZE; + + qtest_writeq(qts, desc_addr, desc_lo); + qtest_writeq(qts, desc_addr + 8, desc_hi); + tail++; + + qtest_writeq(qts, iommu_base + DMAR_IQT_REG, + (uint64_t)tail << QVTD_IQT_SHIFT); + return tail; +} + +uint32_t qvtd_submit_inv_wait_and_poll(QTestState *qts, uint64_t iommu_base, + uint32_t tail) +{ + uint64_t lo, hi; + uint32_t status = 0; + int i; + + qtest_writel(qts, QVTD_INV_WAIT_ADDR, 0); + + lo = VTD_INV_DESC_WAIT | VTD_INV_DESC_WAIT_SW | + ((uint64_t)QVTD_INV_WAIT_DATA << VTD_INV_DESC_WAIT_DATA_SHIFT); + hi = QVTD_INV_WAIT_ADDR; + + tail = qvtd_submit_inv_desc(qts, iommu_base, lo, hi, tail); + + for (i = 0; i < QVTD_INV_WAIT_POLL_MAX_ITERS; i++) { + status = qtest_readl(qts, QVTD_INV_WAIT_ADDR); + if (status == QVTD_INV_WAIT_DATA) { + return tail; + } + g_usleep(QVTD_INV_WAIT_POLL_INTERVAL_US); + } + + g_assert_cmphex(status, ==, QVTD_INV_WAIT_DATA); + return tail; +} + +uint32_t qvtd_submit_iotlb_global_inv(QTestState *qts, uint64_t iommu_base, + uint32_t tail) +{ + uint64_t lo = VTD_INV_DESC_IOTLB | VTD_INV_DESC_IOTLB_GLOBAL; + + return qvtd_submit_inv_desc(qts, iommu_base, lo, 0, tail); +} + +uint32_t qvtd_submit_iotlb_domain_inv(QTestState *qts, uint64_t iommu_base, + uint16_t domain_id, uint32_t tail) +{ + uint64_t lo = VTD_INV_DESC_IOTLB | VTD_INV_DESC_IOTLB_DOMAIN | + ((uint64_t)domain_id << 16); + + return qvtd_submit_inv_desc(qts, iommu_base, lo, 0, tail); +} + +uint32_t qvtd_submit_iotlb_page_inv(QTestState *qts, uint64_t iommu_base, + uint16_t domain_id, uint64_t addr, + uint8_t am, uint32_t tail) +{ + uint64_t lo = VTD_INV_DESC_IOTLB | VTD_INV_DESC_IOTLB_PAGE | + ((uint64_t)domain_id << 16); + /* + * AM selects the invalidation range per VT-d 6.5.2.4: + * am=0 → 4 KB, am=9 → 2 MB, am=18 → 1 GB. + * IH (hi[6]) is left clear, requesting full invalidation + * including non-leaf paging-structure caches. + */ + uint64_t hi = (addr & ~0xfffULL) | (am & 0x3fULL); + + return qvtd_submit_inv_desc(qts, iommu_base, lo, hi, tail); +} diff --git a/tests/qtest/libqos/qos-intel-iommu.h b/tests/qtest/libqos/qos-intel-iommu.h index c6cacc5c3f..3a97d14786 100644 --- a/tests/qtest/libqos/qos-intel-iommu.h +++ b/tests/qtest/libqos/qos-intel-iommu.h @@ -40,6 +40,16 @@ */ #define QVTD_IQ_BASE (QVTD_MEM_BASE + 0x00020000) #define QVTD_IQ_QS 0 /* QS=0 → 256 entries */ +#define QVTD_IQ_DESC_SIZE 16 /* 128-bit descriptor (iq_dw=0) */ +#define QVTD_IQT_SHIFT 4 /* IQT_REG[18:4] is the tail index */ + +/* + * Invalidation Wait Descriptor with Status Write (VT-d 6.5.2.8). + * The IOMMU writes QVTD_INV_WAIT_DATA to QVTD_INV_WAIT_ADDR after all + * preceding invalidation descriptors complete. + */ +#define QVTD_INV_WAIT_ADDR (QVTD_MEM_BASE + 0x00040000) +#define QVTD_INV_WAIT_DATA 0x1u /* * Fault Event MSI configuration. @@ -182,4 +192,68 @@ void qvtd_run_translation_case(QTestState *qts, QPCIDevice *dev, QPCIBar bar, uint64_t iommu_base, const QVTDTestConfig *cfg); +/* + * qvtd_iommu_args - Build the -device intel-iommu command-line fragment + * for the requested translation mode. + */ +const char *qvtd_iommu_args(QVTDTransMode mode); + +/* + * qvtd_check_caps - Check whether the running QEMU exposes the ECAP bits + * required by @mode. Calls g_test_skip() and returns + * false when a required capability is missing. + */ +bool qvtd_check_caps(QTestState *qts, QVTDTransMode mode); + +/* + * qvtd_setup_qtest_pci_device - Create a QPCIBus, locate the iommu-testdev + * PCI function, enable it and map BAR0. + * + * On success, *pcibus and *bar are populated and the returned QPCIDevice + * must be released by the caller via g_free(). + */ +QPCIDevice *qvtd_setup_qtest_pci_device(QTestState *qts, QPCIBus **pcibus, + QPCIBar *bar); + +/* + * qvtd_leaf_pte_addr - Address of the leaf (L1) PTE for @iova, assuming + * the hierarchy was built by + * qvtd_setup_translation_tables() with 4 KB leaves. + */ +uint64_t qvtd_leaf_pte_addr(uint64_t iova); + +/* + * qvtd_make_leaf_pte - Build a leaf PTE value mapping @pa, for the + * 4-level / 4 KB-leaf layout used by + * qvtd_setup_translation_tables(). + * Selects FLT or SLT/legacy attributes from @mode. + */ +uint64_t qvtd_make_leaf_pte(uint64_t pa, QVTDTransMode mode); + +/* + * qvtd_submit_inv_wait_and_poll - Submit an Invalidation Wait Descriptor + * with Status Write and poll until the + * IOMMU writes the expected status data. + * + * Asserts on timeout. Returns the new tail index. + */ +uint32_t qvtd_submit_inv_wait_and_poll(QTestState *qts, uint64_t iommu_base, + uint32_t tail); + +/* + * qvtd_submit_iotlb_global_inv - global IOTLB invalidation. + * qvtd_submit_iotlb_domain_inv - domain-selective IOTLB invalidation. + * qvtd_submit_iotlb_page_inv - page-selective IOTLB invalidation; + * @addr must be 4 KB aligned. @am + * selects the address mask per VT-d + * 6.5.2.4 (am=0 → single 4 KB page). + */ +uint32_t qvtd_submit_iotlb_global_inv(QTestState *qts, uint64_t iommu_base, + uint32_t tail); +uint32_t qvtd_submit_iotlb_domain_inv(QTestState *qts, uint64_t iommu_base, + uint16_t domain_id, uint32_t tail); +uint32_t qvtd_submit_iotlb_page_inv(QTestState *qts, uint64_t iommu_base, + uint16_t domain_id, uint64_t addr, + uint8_t am, uint32_t tail); + #endif /* QTEST_LIBQOS_INTEL_IOMMU_H */ diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index 5e18b947c7..56ff860e21 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -58,6 +58,7 @@ qtests_i386 = \ (config_all_devices.has_key('CONFIG_AHCI_ICH9') ? ['tco-test'] : []) + \ (config_all_devices.has_key('CONFIG_FDC_ISA') ? ['fdc-test'] : []) + \ (config_all_devices.has_key('CONFIG_I440FX') ? ['fw_cfg-test'] : []) + \ + (config_all_devices.has_key('CONFIG_Q35') ? ['e820-test'] : []) + \ (config_all_devices.has_key('CONFIG_FW_CFG_DMA') ? ['vmcoreinfo-test'] : []) + \ (config_all_devices.has_key('CONFIG_Q35') ? ['dump-test'] : []) + \ (config_all_devices.has_key('CONFIG_I440FX') ? ['i440fx-test'] : []) + \ @@ -99,7 +100,7 @@ qtests_i386 = \ (config_all_devices.has_key('CONFIG_AMD_IOMMU') ? ['amd-iommu-test'] : []) + \ (config_all_devices.has_key('CONFIG_VTD') ? ['intel-iommu-test'] : []) + \ (config_all_devices.has_key('CONFIG_VTD') and - config_all_devices.has_key('CONFIG_IOMMU_TESTDEV') ? ['iommu-intel-test'] : []) + \ + config_all_devices.has_key('CONFIG_IOMMU_TESTDEV') ? ['iommu-intel-test', 'iommu-intel-inv-test'] : []) + \ (host_os != 'windows' and \ config_all_devices.has_key('CONFIG_ACPI_ERST') ? ['erst-test'] : []) + \ (config_all_devices.has_key('CONFIG_PCIE_PORT') and \