Kernel KVM virtualization development
 help / color / mirror / Atom feed
* [PATCH v2 00/19] guest_memfd: support in-place memory conversion
@ 2026-09-08 20:48 Michael Roth
  2026-09-08 20:48 ` [PATCH v2 01/19] accel/kvm: Add helper for handling conversions of MMIO holes Michael Roth
                   ` (18 more replies)
  0 siblings, 19 replies; 24+ messages in thread
From: Michael Roth @ 2026-09-08 20:48 UTC (permalink / raw)
  To: qemu-devel
  Cc: kvm, pbonzini, berrange, armbru, pankaj.gupta, isaku.yamahata,
	xiaoyao.li, chao.p.peng, david, ashish.kalra, ackerleytng,
	lpieralisi

v1: https://lore.kernel.org/qemu-devel/20260528000416.8161-1-michael.roth@amd.com/
v2:
 - reuse memory-backend-memfd instead of introducing a new
   guest-memfd-specific backend (Peter)
 - pull bug-fix-related refactorings/dependencies back into the beginning of
   this series for more visibility/context (patches 1-4, can be applied
   independently), originally submitted as:
   https://lore.kernel.org/qemu-devel/20260527223036.4614-1-michael.roth@amd.com/
 - move gmem flag specification to KVM-internal code to allow for better
   control of likely-global policies (like INIT_SHARED, which some confidential
   VM types might prefer to leave unset) (Lorenzo)
 - zero-initialize the kvm_memory_attributes2 struct to avoid garbage values
   ending up in reserved fields (Pankaj)
 - add a helper to check for in-place conversion instead of accessing CGS
   directly
 - add reasoning for introducing ConfidentialGuestSupportProperties base class
   now instead of deferring (Markus)
 - minor fixups/refactorings


This patchset is also available at:

  https://github.com/amdese/qemu/commits/snp-inplace-v2

which is in turn based on the following series:

  [PATCH v5 00/12] KVM/hostmem: Support init-shared guest-memfd as VM backends
  https://lore.kernel.org/qemu-devel/20260908133151.836685-1-michael.roth@amd.com/


OVERVIEW
--------

This series adds guest_memfd support for in-place conversion of memory
between private/shared, and enables it for SEV-SNP guests. It is based
on recently-added kernel support for mmap()-able guest_memfd
instances[1], which allow it to be used for shared memory, and the
following patchset[2], which adds additional guest_memfd interfaces to
allow it to be used to perform in-place conversion:

  [PATCH v12 00/45] guest_memfd: In-place conversion support
  https://lore.kernel.org/kvm/20260830-gmem-inplace-conversion-v12-0-85e5fd25252a@google.com/

That series also introduces a new 'gmem_in_place_conversion' KVM
module option, which sets whether memory attributes are tracked
VM-wide by KVM (vm_memory_attributes=1: the existing 'legacy' mode),
or per-guest_memfd instance (vm_memory_attributes=0: the new mode
which allows for in-place conversion). The latter is intended to
eventually deprecate the legacy mode, at which point in-place
conversion would become the primarily-supported mode.


MOTIVATION
----------

Today, SEV-SNP guests (and other CoCo VM types using guest_memfd) keep
shared and private memory on separate physical backings: a userspace
memory-backend object for shared pages, and a kernel-allocated
guest_memfd file descriptor for private pages. KVM_SET_MEMORY_ATTRIBUTES
flips which backing the guest sees for a given GPA range, and the old
backing is typically discarded / hole-punched on conversion to avoid
doubled memory usage.

That model works, but has a number of downsides that impact certain
use-cases:

  - Each conversion involves discarding pages on one side and faulting
    them in on the other, which incurs allocation overheads in the
    host kernel for every conversion.

  - Some use-cases, like pKVM[3], rely on memory isolation rather than
    encryption and rely on in-place conversion to pass through things
    like secured framebuffer memory without needing to bounce data
    through separate shared/private HPAs, which would introduce
    unacceptable latency for that sort of workload.

  - Hugetlb support[4] for guest_memfd will rely on it, since things like
    1GB hugepages with a mix of shared/private sub-ranges would generally
    require 2 1GB hugetlb pages to remain available to handle shared vs.
    private accesses, which quickly causes doubling of guest memory usage.

Recent kernel work[2] makes guest_memfd mmap()-able and lets the *same*
physical pages be used for both shared and private states for a given
GPA range, allowing the above pitfalls to be naturally avoided.

This series wires that support up in QEMU.


DESIGN
------

For confidential VMs, a new 'convert-in-place' flag is added to switch
on in-place conversion support. When running in this mode, the user
*MUST* use memory-backend-guest-memfd for backing guest RAM. A new
RAM_GUEST_MEMFD_SHARED RAMBlock flag is added to track/enforce the
dependency. Additionally, QEMU is modified to use mmap()-able
guest_memfd and set this flag for other cases where it allocates RAM
internally. As a result, block->fd will generally always be a
guest_memfd, and when RAM_GUEST_MEMFD_SHARED is set then that block->fd
will be qemu_dup()'d as the FD handle for private memory as well (which
is currently what block->guest_memfd points to). This allows the prior
non-in-place handling around block->guest_memfd to be kept mostly
unchanged for the in-place case.

When running with convert-in-place=true, shared/private conversions
are no longer handled directly by KVM, but instead by a new guest_memfd
ioctl, KVM_SET_MEMORY_ATTRIBUTES2, which purposely provides similar
naming/implementation to the KVM_SET_MEMORY_ATTRIBUTES KVM ioctl that
it replaces. This series adds handling to route conversion requests to
the appropriate ioctls based on whether or not in-place conversion is
enabled.

This support also relies on the memory-backend-memfd,guest-memfd=on
option, since it is necessary to use guest_memfd for both the shared
and private memory. This is set automatically based on whether or
not in-place conversion is enabled.

Since guest_memfd ioctls need to be called against the specific
guest_memfd inode associated with each memory slot/region, some
refactoring is needed to handle conversions on a per-region basis. Much
of that is inherited from the bugfix series this patchset is based on
top of, which adds the initial logic for handling multiple sections
within a range.


USAGE
-----

After applying this series against a kernel with the RFC patches above
present, an SEV-SNP guest can be started with in-place conversion via:

    qemu-system-x86_64 \
        -machine q35,confidential-guest-support=sev0,memory-backend=ram0 \
        -object memory-backend-memfd,id=ram0,size=8G,share=on \
        -object sev-snp-guest,id=sev0,cbitpos=51,reduced-phys-bits=1,\
                convert-in-place=on \
        ...

The new memory-backend-guest-memfd can also be used by normal VMs:

    qemu-system-x86_64 \
        -machine q35,memory-backend=ram0 \
        -object memory-backend-memfd,id=ram0,size=8G,share=on \
        ...

This is mainly only useful atm for testing, but in the future there may
be more use-cases around using guest_memfd as a general-purpose backend
for non-confidential VMs, so it is intended to work in this manner as
well.


NOTES/TODO
----------

  - TDX testing would be great, in theory it can be enabled with this
    series (similarly to the top patch) but I'm not sure if there are
    other special requirements before we can switch it on.
  - kernel patches are still in-flight, but fairly mature at this point
    and nearing upstream


REFERENCES
----------

[1] https://lore.kernel.org/kvm/20250729225455.670324-1-seanjc@google.com/
[2] https://lore.kernel.org/kvm/20260830-gmem-inplace-conversion-v12-0-85e5fd25252a@google.com/
[3] https://www.youtube.com/watch?v=MMfAGNW9RVg
[4] https://lore.kernel.org/kvm/cover.1747264138.git.ackerleytng@google.com/


Thoughts, feedback, and testing are very much appreciated.

Thanks,

Mike


----------------------------------------------------------------
Ashish Kalra (1):
      accel/kvm: Fix kvm_convert_memory() calls crossing memory regions

Michael Roth (18):
      accel/kvm: Add helper for handling conversions of MMIO holes
      accel/kvm: Fix handling of MMIO holes at start of conversion ranges
      accel/kvm: Fix handling of conversion ranges with multiple MMIO holes
      accel/kvm: Use dedicated helper for creating private-only gmem instances
      linux-headers: Update headers for v12 of in-place conversion kernel support
      accel/kvm: Add CGS option to control in-place conversion support
      system/memory: Re-use memory-backend-guest-memfd inode for private memory
      accel/kvm: Handle guest_memfd flags internally when creating instances
      system/memory: Default to guest_memfd for RAM for in-place conversion
      accel/kvm: Move post-conversion updates to a separate helper
      accel/kvm: Re-order attribute notifications for in-place conversion
      accel/kvm: Support shared/private conversions via guest_memfd ioctls
      accel/kvm: Don't default to private attributes for in-place conversion
      i386/sev: Update SNP_LAUNCH_UPDATE for in-place conversion
      i386/sev: Allow in-place conversion for SEV-SNP guests
      i386/sev: Update CPUID failure handling for in-place conversion
      accel/kvm: Disable discard for in-place conversion
      hostmem: Automatically select set guest-memfd=on for in-place conversion

 accel/kvm/kvm-all.c                         | 449 ++++++++++++++++++++++++----
 accel/stubs/kvm-stub.c                      |   8 +-
 backends/confidential-guest-support.c       |  26 ++
 backends/hostmem-memfd.c                    |  41 ++-
 hw/core/machine.c                           |   5 +
 include/hw/core/boards.h                    |   1 +
 include/system/confidential-guest-support.h |  14 +
 include/system/kvm.h                        |   3 +-
 include/system/memory.h                     |   3 +
 linux-headers/linux/kvm.h                   |  16 +
 qapi/qom.json                               |  21 +-
 system/memory.c                             |  23 +-
 system/physmem.c                            |  52 +++-
 target/i386/sev.c                           |  22 +-
 14 files changed, 593 insertions(+), 91 deletions(-)


^ permalink raw reply	[flat|nested] 24+ messages in thread

* [PATCH v2 01/19] accel/kvm: Add helper for handling conversions of MMIO holes
  2026-09-08 20:48 [PATCH v2 00/19] guest_memfd: support in-place memory conversion Michael Roth
@ 2026-09-08 20:48 ` Michael Roth
  2026-09-08 20:48 ` [PATCH v2 02/19] accel/kvm: Fix kvm_convert_memory() calls crossing memory regions Michael Roth
                   ` (17 subsequent siblings)
  18 siblings, 0 replies; 24+ messages in thread
From: Michael Roth @ 2026-09-08 20:48 UTC (permalink / raw)
  To: qemu-devel
  Cc: kvm, pbonzini, berrange, armbru, pankaj.gupta, isaku.yamahata,
	xiaoyao.li, chao.p.peng, david, ashish.kalra, ackerleytng,
	lpieralisi

When converting GPA ranges containing MMIO holes, the general intended
logic is to skip MMIO ranges as long as it is a private->shared
conversion, and to fail otherwise.

To better enable some future refactorings to address bugs in the current
code, go ahead and consolidate all of the MMIO handling to a
self-contained helper.

The only intended functional change is that the error message for
shared->private conversion of MMIO ranges corresponding to a
non-RAM/non-ROM region now report the range of the region that
specifically caused the failure, as opposed to reporting the entire
conversion range.

Signed-off-by: Michael Roth <michael.roth@amd.com>
---
 accel/kvm/kvm-all.c | 96 ++++++++++++++++++++++++++++++---------------
 1 file changed, 64 insertions(+), 32 deletions(-)

diff --git a/accel/kvm/kvm-all.c b/accel/kvm/kvm-all.c
index 70605a8d10..0b59ba0d3c 100644
--- a/accel/kvm/kvm-all.c
+++ b/accel/kvm/kvm-all.c
@@ -3334,6 +3334,67 @@ static void kvm_eat_signals(CPUState *cpu)
     } while (sigismember(&chkset, SIG_IPI));
 }
 
+/*
+ * This handles checking whether a conversion overlaps with any holes in
+ * the given section/range, and indicates whether or not the section/range
+ * can be safely skipped (which is generally allowed as long as the
+ * conversion is private->shared and not the other way around).
+ *
+ * Returns 0 if section is normal guest-memfd-backed memory, or is a hole or
+ * non-RAM/ROM region that should be skipped as MMIO. In the latter case, the
+ * 'skip' parameter will be set. Returns < 0 if the conversion request is not
+ * valid.
+ */
+static int handle_memory_hole(MemoryRegionSection *section, bool to_private,
+                              bool *skip)
+{
+    MemoryRegion *mr = section->mr;
+
+    if (!mr) {
+        /*
+         * TDX requires vMMIO region to be shared to inject #VE to guest.
+         * OVMF issues conservatively MapGPA(shared) on 32bit PCI MMIO
+         * region, and vIO-APIC 0xFEC00000 4K page.
+         * OVMF assigns 32bit PCI MMIO region to
+         * [top of low memory: typically 2GB=0xC000000,  0xFC00000)
+         *
+         * If the current lookup failed to find any regions, then skip the
+         * entire remaining range as a hole/MMIO.
+         */
+        if (!to_private) {
+            *skip = true;
+            return 0;
+        }
+        return -EINVAL;
+    }
+
+    /*
+     * Because vMMIO region must be shared, guest TD may convert vMMIO
+     * region to shared explicitly.  Don't complain such case.  See
+     * memory_region_type() for checking if the region is MMIO region.
+     */
+    if (!memory_region_has_guest_memfd_private(mr)) {
+        if (!to_private &&
+            !memory_region_is_ram(mr) &&
+            !memory_region_is_ram_device(mr) &&
+            !memory_region_is_rom(mr) &&
+            !memory_region_is_romd(mr)) {
+            *skip = true;
+            return 0;
+        } else {
+            error_report("Convert non guest_memfd backed memory region "
+                         "(0x%"HWADDR_PRIx" + 0x%"HWADDR_PRIx") to %s",
+                         section->offset_within_address_space,
+                         int128_get64(section->size),
+                         to_private ? "private" : "shared");
+            return -EINVAL;
+        }
+    }
+
+    *skip = false;
+    return 0;
+}
+
 int kvm_convert_memory(hwaddr start, hwaddr size, bool to_private)
 {
     MemoryRegionSection section;
@@ -3341,6 +3402,7 @@ int kvm_convert_memory(hwaddr start, hwaddr size, bool to_private)
     MemoryRegion *mr;
     RAMBlock *rb;
     void *addr;
+    bool skip;
     int ret = -EINVAL;
 
     trace_kvm_convert_memory(start, size, to_private ? "shared_to_private" : "private_to_shared");
@@ -3356,39 +3418,9 @@ int kvm_convert_memory(hwaddr start, hwaddr size, bool to_private)
 
     section = memory_region_find(get_system_memory(), start, size);
     mr = section.mr;
-    if (!mr) {
-        /*
-         * Ignore converting non-assigned region to shared.
-         *
-         * TDX requires vMMIO region to be shared to inject #VE to guest.
-         * OVMF issues conservatively MapGPA(shared) on 32bit PCI MMIO region,
-         * and vIO-APIC 0xFEC00000 4K page.
-         * OVMF assigns 32bit PCI MMIO region to
-         * [top of low memory: typically 2GB=0xC000000,  0xFC00000)
-         */
-        if (!to_private) {
-            return 0;
-        }
-        return ret;
-    }
 
-    if (!memory_region_has_guest_memfd_private(mr)) {
-        /*
-         * Because vMMIO region must be shared, guest TD may convert vMMIO
-         * region to shared explicitly.  Don't complain such case.  See
-         * memory_region_type() for checking if the region is MMIO region.
-         */
-        if (!to_private &&
-            !memory_region_is_ram(mr) &&
-            !memory_region_is_ram_device(mr) &&
-            !memory_region_is_rom(mr) &&
-            !memory_region_is_romd(mr)) {
-            ret = 0;
-        } else {
-            error_report("Convert non guest_memfd backed memory region "
-                        "(0x%"HWADDR_PRIx" ,+ 0x%"HWADDR_PRIx") to %s",
-                        start, size, to_private ? "private" : "shared");
-        }
+    ret = handle_memory_hole(&section, to_private, &skip);
+    if (ret || skip) {
         goto out_unref;
     }
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH v2 02/19] accel/kvm: Fix kvm_convert_memory() calls crossing memory regions
  2026-09-08 20:48 [PATCH v2 00/19] guest_memfd: support in-place memory conversion Michael Roth
  2026-09-08 20:48 ` [PATCH v2 01/19] accel/kvm: Add helper for handling conversions of MMIO holes Michael Roth
@ 2026-09-08 20:48 ` Michael Roth
  2026-09-08 20:48 ` [PATCH v2 03/19] accel/kvm: Fix handling of MMIO holes at start of conversion ranges Michael Roth
                   ` (16 subsequent siblings)
  18 siblings, 0 replies; 24+ messages in thread
From: Michael Roth @ 2026-09-08 20:48 UTC (permalink / raw)
  To: qemu-devel
  Cc: kvm, pbonzini, berrange, armbru, pankaj.gupta, isaku.yamahata,
	xiaoyao.li, chao.p.peng, david, ashish.kalra, ackerleytng,
	lpieralisi

From: Ashish Kalra <ashish.kalra@amd.com>

Page conversion calls can span multiple memory regions, potentially
resulting in a conversion failure if the memory range being converted
extends beyond the boundaries of the referenced memory region.

Handle the case of page conversion calls straddling across memory
regions by looping through the subregions and handling conversions and
related work section by section.

Fixes: c15e5684071d ("kvm: handle KVM_EXIT_MEMORY_FAULT")
Signed-off-by: Ashish Kalra <ashish.kalra@amd.com>
Co-developed-by: Michael Roth <michael.roth@amd.com>
Signed-off-by: Michael Roth <michael.roth@amd.com>
---
 accel/kvm/kvm-all.c | 94 ++++++++++++++++++++++++++++++---------------
 1 file changed, 63 insertions(+), 31 deletions(-)

diff --git a/accel/kvm/kvm-all.c b/accel/kvm/kvm-all.c
index 0b59ba0d3c..62565a544d 100644
--- a/accel/kvm/kvm-all.c
+++ b/accel/kvm/kvm-all.c
@@ -3395,54 +3395,35 @@ static int handle_memory_hole(MemoryRegionSection *section, bool to_private,
     return 0;
 }
 
-int kvm_convert_memory(hwaddr start, hwaddr size, bool to_private)
+static int kvm_convert_section(MemoryRegionSection *section, bool to_private)
 {
-    MemoryRegionSection section;
+    hwaddr start = section->offset_within_address_space;
+    hwaddr size = int128_get64(section->size);
+    MemoryRegion *mr = section->mr;
     ram_addr_t offset;
-    MemoryRegion *mr;
     RAMBlock *rb;
     void *addr;
-    bool skip;
     int ret = -EINVAL;
 
-    trace_kvm_convert_memory(start, size, to_private ? "shared_to_private" : "private_to_shared");
-
-    if (!QEMU_PTR_IS_ALIGNED(start, qemu_real_host_page_size()) ||
-        !QEMU_PTR_IS_ALIGNED(size, qemu_real_host_page_size())) {
-        return ret;
-    }
-
-    if (!size) {
-        return ret;
-    }
-
-    section = memory_region_find(get_system_memory(), start, size);
-    mr = section.mr;
-
-    ret = handle_memory_hole(&section, to_private, &skip);
-    if (ret || skip) {
-        goto out_unref;
-    }
-
     if (to_private) {
         ret = kvm_set_memory_attributes_private(start, size);
     } else {
         ret = kvm_set_memory_attributes_shared(start, size);
     }
     if (ret) {
-        goto out_unref;
+        return ret;
     }
 
-    addr = memory_region_get_ram_ptr(mr) + section.offset_within_region;
+    addr = memory_region_get_ram_ptr(mr) + section->offset_within_region;
     rb = qemu_ram_block_from_host(addr, false, &offset);
 
     ret = ram_block_attributes_state_change(rb->attributes,
                                             offset, size, to_private);
     if (ret) {
         error_report("Failed to notify the listener the state change of "
-                     "(0x%"HWADDR_PRIx" + 0x%"HWADDR_PRIx") to %s",
-                     start, size, to_private ? "private" : "shared");
-        goto out_unref;
+                     "(0x%"HWADDR_PRIx" + 0x%"HWADDR_PRIx") to %s, ret %d",
+                     start, size, to_private ? "private" : "shared", ret);
+        return ret;
     }
 
     if (to_private) {
@@ -3451,15 +3432,66 @@ int kvm_convert_memory(hwaddr start, hwaddr size, bool to_private)
              * shared memory is backed by hugetlb, which is supposed to be
              * pre-allocated and doesn't need to be discarded
              */
-            goto out_unref;
+            return 0;
         }
         ret = ram_block_discard_shared_range(rb, offset, size);
     } else {
         ret = ram_block_discard_guest_memfd_range(rb, offset, size);
     }
 
-out_unref:
-    memory_region_unref(mr);
+    return ret;
+}
+
+int kvm_convert_memory(hwaddr start, hwaddr size, bool to_private)
+{
+    int ret = -EINVAL;
+
+    trace_kvm_convert_memory(start, size, to_private ? "shared_to_private" : "private_to_shared");
+
+    if (!QEMU_PTR_IS_ALIGNED(start, qemu_real_host_page_size()) ||
+        !QEMU_PTR_IS_ALIGNED(size, qemu_real_host_page_size())) {
+        return ret;
+    }
+
+    /*
+     * Page conversions can span multiple memory regions, for example, if two
+     * memory backends are added to support two different NUMA nodes/policies.
+     * Handle the covered sections accordingly.
+     */
+    while (size) {
+        MemoryRegionSection section = memory_region_find(get_system_memory(),
+                                                         start, size);
+        hwaddr section_end;
+        bool skip;
+
+        /*
+         * If there's no region present, then the current hole "section"
+         * consumes the entire remaining range. In that case, update the
+         * relevant indices to terminate the loop after this iteration.
+         */
+        section_end = section.mr
+            ? section.offset_within_address_space + int128_get64(section.size)
+            : start + size;
+        assert(section_end > start);
+        assert(section_end - start <= size);
+
+        ret = handle_memory_hole(&section, to_private, &skip);
+        if (ret || skip) {
+            memory_region_unref(section.mr);
+            break;
+        }
+
+        ret = kvm_convert_section(&section, to_private);
+        memory_region_unref(section.mr);
+
+        if (ret) {
+            break;
+        }
+
+        size -= section_end - start;
+        start = section_end;
+    }
+
     return ret;
 }
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH v2 03/19] accel/kvm: Fix handling of MMIO holes at start of conversion ranges
  2026-09-08 20:48 [PATCH v2 00/19] guest_memfd: support in-place memory conversion Michael Roth
  2026-09-08 20:48 ` [PATCH v2 01/19] accel/kvm: Add helper for handling conversions of MMIO holes Michael Roth
  2026-09-08 20:48 ` [PATCH v2 02/19] accel/kvm: Fix kvm_convert_memory() calls crossing memory regions Michael Roth
@ 2026-09-08 20:48 ` Michael Roth
  2026-09-08 20:48 ` [PATCH v2 04/19] accel/kvm: Fix handling of conversion ranges with multiple MMIO holes Michael Roth
                   ` (15 subsequent siblings)
  18 siblings, 0 replies; 24+ messages in thread
From: Michael Roth @ 2026-09-08 20:48 UTC (permalink / raw)
  To: qemu-devel
  Cc: kvm, pbonzini, berrange, armbru, pankaj.gupta, isaku.yamahata,
	xiaoyao.li, chao.p.peng, david, ashish.kalra, ackerleytng,
	lpieralisi

Currently MMIO checks are done for ranges where memory_region_find()
finds no regions within the entire range, or for cases where
non-RAM/ROM regions at the beginning of the range.

However, if the first region in the range is a normal RAM/ROM region,
then the portion of the conversion range that overlaps the region is
processed normally, but any MMIO holes that might be present at the
beginning are ignored, so the checks are bypassed for those ranges.

Plumb the 'start' GPA that was used to query memory_region_find(), and
pass that into the MMIO-processing helper so that these gaps can be
detected and MMIO checks can be applied appropriately.

Fixes: c5d9425ef4da ("kvm/tdx: Don't complain when converting vMMIO region to shared")
Signed-off-by: Michael Roth <michael.roth@amd.com>
---
 accel/kvm/kvm-all.c | 18 ++++++++++++++++--
 1 file changed, 16 insertions(+), 2 deletions(-)

diff --git a/accel/kvm/kvm-all.c b/accel/kvm/kvm-all.c
index 62565a544d..463bbdadd2 100644
--- a/accel/kvm/kvm-all.c
+++ b/accel/kvm/kvm-all.c
@@ -3344,9 +3344,14 @@ static void kvm_eat_signals(CPUState *cpu)
  * non-RAM/ROM region that should be skipped as MMIO. In the latter case, the
  * 'skip' parameter will be set. Returns < 0 if the conversion request is not
  * valid.
+ *
+ * 'start' corresponds to the starting range memory_region_find() was
+ * called for, and is used to determine if there are any MMIO holes preceding
+ * the region passed in so the appropriate checks can be made on those
+ * ranges.
  */
 static int handle_memory_hole(MemoryRegionSection *section, bool to_private,
-                              bool *skip)
+                              hwaddr start, bool *skip)
 {
     MemoryRegion *mr = section->mr;
 
@@ -3391,6 +3396,15 @@ static int handle_memory_hole(MemoryRegionSection *section, bool to_private,
         }
     }
 
+    /*
+     * In this case the region should be processed as normal
+     * guest_memfd-backed RAM/ROM, but still need to check if there are
+     * preceding holes to apply the MMIO checks against.
+     */
+    if (start < section->offset_within_address_space && to_private) {
+        return -EINVAL;
+    }
+
     *skip = false;
     return 0;
 }
@@ -3475,7 +3489,7 @@ int kvm_convert_memory(hwaddr start, hwaddr size, bool to_private)
         assert(section_end > start);
         assert(section_end - start <= size);
 
-        ret = handle_memory_hole(&section, to_private, &skip);
+        ret = handle_memory_hole(&section, to_private, start, &skip);
         if (ret || skip) {
             memory_region_unref(section.mr);
             break;
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH v2 04/19] accel/kvm: Fix handling of conversion ranges with multiple MMIO holes
  2026-09-08 20:48 [PATCH v2 00/19] guest_memfd: support in-place memory conversion Michael Roth
                   ` (2 preceding siblings ...)
  2026-09-08 20:48 ` [PATCH v2 03/19] accel/kvm: Fix handling of MMIO holes at start of conversion ranges Michael Roth
@ 2026-09-08 20:48 ` Michael Roth
  2026-09-08 20:48 ` [PATCH v2 05/19] accel/kvm: Use dedicated helper for creating private-only gmem instances Michael Roth
                   ` (14 subsequent siblings)
  18 siblings, 0 replies; 24+ messages in thread
From: Michael Roth @ 2026-09-08 20:48 UTC (permalink / raw)
  To: qemu-devel
  Cc: kvm, pbonzini, berrange, armbru, pankaj.gupta, isaku.yamahata,
	xiaoyao.li, chao.p.peng, david, ashish.kalra, ackerleytng,
	lpieralisi

The current handling will properly handle MMIO checks for holes anchored
at the beginning of the conversion range, but misses subsequent holes.
The current handle_memory_hole() helper already processes all MMIO
ranges up to and including the sections found at each iteration, so
handle this by simply calling handle_memory_hole() for each additional
section/sub-range.

Fixes: c5d9425ef4da ("kvm/tdx: Don't complain when converting vMMIO region to shared")
Signed-off-by: Michael Roth <michael.roth@amd.com>
---
 accel/kvm/kvm-all.c | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/accel/kvm/kvm-all.c b/accel/kvm/kvm-all.c
index 463bbdadd2..518baa35b9 100644
--- a/accel/kvm/kvm-all.c
+++ b/accel/kvm/kvm-all.c
@@ -3492,7 +3492,12 @@ int kvm_convert_memory(hwaddr start, hwaddr size, bool to_private)
         ret = handle_memory_hole(&section, to_private, start, &skip);
         if (ret || skip) {
             memory_region_unref(section.mr);
-            break;
+            if (ret) {
+                break;
+            }
+            size -= section_end - start;
+            start = section_end;
+            continue;
         }
 
         ret = kvm_convert_section(&section, to_private);
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH v2 05/19] accel/kvm: Use dedicated helper for creating private-only gmem instances
  2026-09-08 20:48 [PATCH v2 00/19] guest_memfd: support in-place memory conversion Michael Roth
                   ` (3 preceding siblings ...)
  2026-09-08 20:48 ` [PATCH v2 04/19] accel/kvm: Fix handling of conversion ranges with multiple MMIO holes Michael Roth
@ 2026-09-08 20:48 ` Michael Roth
  2026-09-08 20:48 ` [PATCH v2 06/19] linux-headers: Update headers for v12 of in-place conversion kernel support Michael Roth
                   ` (13 subsequent siblings)
  18 siblings, 0 replies; 24+ messages in thread
From: Michael Roth @ 2026-09-08 20:48 UTC (permalink / raw)
  To: qemu-devel
  Cc: kvm, pbonzini, berrange, armbru, pankaj.gupta, isaku.yamahata,
	xiaoyao.li, chao.p.peng, david, ashish.kalra, ackerleytng,
	lpieralisi

Currently QEMU supports using guest_memfd internally (separately from
user-specified memory backends) to handle private memory for
confidential VMs. While KVM can switch between guest_memfd-backed
private memory and non-guest_memfd-backed shared memory via
KVM_SET_MEMORY_ATTRIBUTES, the memory in the guest_memfd inode can only
ever be private memory.

This is distinct from upcoming in-place conversion support, where
guest_memfd inodes can contain both private/shared memory and can
convert between the 2 in-place.

To help distinguish between these 2 uses of guest_memfd, add a dedicated
helper to handle the private-only uses of guest_memfd, and add some
additional sanity checks with that use-case in mind.

Signed-off-by: Michael Roth <michael.roth@amd.com>
---
 accel/kvm/kvm-all.c    | 15 +++++++++++++++
 accel/stubs/kvm-stub.c |  6 ++++++
 include/system/kvm.h   |  1 +
 system/physmem.c       |  6 +++---
 4 files changed, 25 insertions(+), 3 deletions(-)

diff --git a/accel/kvm/kvm-all.c b/accel/kvm/kvm-all.c
index 518baa35b9..15f237de4e 100644
--- a/accel/kvm/kvm-all.c
+++ b/accel/kvm/kvm-all.c
@@ -794,6 +794,11 @@ static int kvm_mem_flags(MemoryRegion *mr)
     }
     if (memory_region_has_guest_memfd_private(mr)) {
         assert(kvm_guest_memfd_supported);
+        /*
+         * memory_region_has_guest_memfd_private() is specifically pertaining to
+         * using guest_memfd to handle private memory use cases.
+         */
+        assert(kvm_supported_memory_attributes & KVM_MEMORY_ATTRIBUTE_PRIVATE);
         flags |= KVM_MEM_GUEST_MEMFD;
     }
     return flags;
@@ -4873,3 +4878,13 @@ int kvm_create_guest_memfd(uint64_t size, uint64_t flags, Error **errp)
 
     return fd;
 }
+
+int kvm_create_guest_memfd_private(uint64_t size, Error **errp)
+{
+    if (!(kvm_supported_memory_attributes & KVM_MEMORY_ATTRIBUTE_PRIVATE)) {
+        error_setg(errp, "KVM does not support using guest_memfd for private memory");
+        return -1;
+    }
+
+    return kvm_create_guest_memfd(size, 0, errp);
+}
diff --git a/accel/stubs/kvm-stub.c b/accel/stubs/kvm-stub.c
index acbd0785e0..9fe58efe91 100644
--- a/accel/stubs/kvm-stub.c
+++ b/accel/stubs/kvm-stub.c
@@ -145,6 +145,12 @@ int kvm_create_guest_memfd(uint64_t size, uint64_t flags, Error **errp)
     return -ENOSYS;
 }
 
+int kvm_create_guest_memfd_private(uint64_t size, Error **errp)
+{
+    error_setg(errp, "KVM is not enabled");
+    return -ENOSYS;
+}
+
 bool kvm_private_memory_attribute_supported(void)
 {
     return false;
diff --git a/include/system/kvm.h b/include/system/kvm.h
index d29624034c..b1e43ddc93 100644
--- a/include/system/kvm.h
+++ b/include/system/kvm.h
@@ -548,6 +548,7 @@ void kvm_mark_guest_state_protected(void);
 bool kvm_hwpoisoned_mem(void);
 
 int kvm_create_guest_memfd(uint64_t size, uint64_t flags, Error **errp);
+int kvm_create_guest_memfd_private(uint64_t size, Error **errp);
 
 int kvm_set_memory_attributes_private(hwaddr start, uint64_t size);
 int kvm_set_memory_attributes_shared(hwaddr start, uint64_t size);
diff --git a/system/physmem.c b/system/physmem.c
index f6dff18bbb..991f7bb815 100644
--- a/system/physmem.c
+++ b/system/physmem.c
@@ -2209,7 +2209,7 @@ static void ram_block_add(RAMBlock *new_block, Error **errp)
         }
 
         new_block->guest_memfd_private =
-            kvm_create_guest_memfd(new_block->max_length, 0, errp);
+            kvm_create_guest_memfd_private(new_block->max_length, errp);
         if (new_block->guest_memfd_private < 0) {
             qemu_mutex_unlock_ramlist();
             goto out_free;
@@ -2839,8 +2839,8 @@ int ram_block_rebind(Error **errp)
             if (block->guest_memfd_private >= 0) {
                 close(block->guest_memfd_private);
             }
-            block->guest_memfd_private = kvm_create_guest_memfd(
-                block->max_length, 0, errp);
+            block->guest_memfd_private =
+                kvm_create_guest_memfd_private(block->max_length, errp);
             if (block->guest_memfd_private < 0) {
                 qemu_mutex_unlock_ramlist();
                 return -1;
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH v2 06/19] linux-headers: Update headers for v12 of in-place conversion kernel support
  2026-09-08 20:48 [PATCH v2 00/19] guest_memfd: support in-place memory conversion Michael Roth
                   ` (4 preceding siblings ...)
  2026-09-08 20:48 ` [PATCH v2 05/19] accel/kvm: Use dedicated helper for creating private-only gmem instances Michael Roth
@ 2026-09-08 20:48 ` Michael Roth
  2026-09-08 20:48 ` [PATCH v2 07/19] accel/kvm: Add CGS option to control in-place conversion support Michael Roth
                   ` (12 subsequent siblings)
  18 siblings, 0 replies; 24+ messages in thread
From: Michael Roth @ 2026-09-08 20:48 UTC (permalink / raw)
  To: qemu-devel
  Cc: kvm, pbonzini, berrange, armbru, pankaj.gupta, isaku.yamahata,
	xiaoyao.li, chao.p.peng, david, ashish.kalra, ackerleytng,
	lpieralisi

The kernel support is not yet upstream, so pull in the latest headers
for v12. This patch should be replaced with a full kernel header sync
once the kernel support lands.

Signed-off-by: Michael Roth <michael.roth@amd.com>
---
 linux-headers/linux/kvm.h | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/linux-headers/linux/kvm.h b/linux-headers/linux/kvm.h
index aea4ab5c69..7850bd4642 100644
--- a/linux-headers/linux/kvm.h
+++ b/linux-headers/linux/kvm.h
@@ -987,6 +987,7 @@ struct kvm_enable_cap {
 #define KVM_CAP_S390_VSIE_ESAMODE 248
 #define KVM_CAP_S390_HPAGE_2G 249
 #define KVM_CAP_PPC_COMPAT_CAPS 250
+#define KVM_CAP_GUEST_MEMFD_MEMORY_ATTRIBUTES 252
 
 struct kvm_irq_routing_irqchip {
 	__u32 irqchip;
@@ -1637,6 +1638,21 @@ struct kvm_memory_attributes {
 	__u64 flags;
 };
 
+/* Available with KVM_CAP_GUEST_MEMFD_MEMORY_ATTRIBUTES */
+#define KVM_SET_MEMORY_ATTRIBUTES2              _IOWR(KVMIO,  0xd2, struct kvm_memory_attributes2)
+
+struct kvm_memory_attributes2 {
+	union {
+		__u64 address;
+		__u64 offset;
+	};
+	__u64 size;
+	__u64 attributes;
+	__u64 flags;
+	__u64 error_offset;
+	__u64 reserved[11];
+};
+
 #define KVM_MEMORY_ATTRIBUTE_PRIVATE           (1ULL << 3)
 
 #define KVM_CREATE_GUEST_MEMFD	_IOWR(KVMIO,  0xd4, struct kvm_create_guest_memfd)
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH v2 07/19] accel/kvm: Add CGS option to control in-place conversion support
  2026-09-08 20:48 [PATCH v2 00/19] guest_memfd: support in-place memory conversion Michael Roth
                   ` (5 preceding siblings ...)
  2026-09-08 20:48 ` [PATCH v2 06/19] linux-headers: Update headers for v12 of in-place conversion kernel support Michael Roth
@ 2026-09-08 20:48 ` Michael Roth
  2026-09-09  6:20   ` Markus Armbruster
  2026-09-08 20:48 ` [PATCH v2 08/19] system/memory: Re-use memory-backend-guest-memfd inode for private memory Michael Roth
                   ` (11 subsequent siblings)
  18 siblings, 1 reply; 24+ messages in thread
From: Michael Roth @ 2026-09-08 20:48 UTC (permalink / raw)
  To: qemu-devel
  Cc: kvm, pbonzini, berrange, armbru, pankaj.gupta, isaku.yamahata,
	xiaoyao.li, chao.p.peng, david, ashish.kalra, ackerleytng,
	lpieralisi

For confidential guests, guest_memfd is currently used only for private
guest memory, and normal guest memory comes from the configured memory
backend just as it does for a non-confidential guest. It is now possible
to use the same physical memory to back a particular GPA regardless of
whether it is in a shared or private state. This avoids the need to
rely on discarding memory between shared/private conversions (to avoid
doubled memory usage), and is intended to be the primary mode of using
guest_memfd for confidential guests moving forward, and future features
like hugepage support will likely require it.

Add an option to enable this support. Since ConfidentialGuestSupport is
already used to track some guest_memfd-related functionality (e.g.
whether it is required for the configured machine), similarly introduce
this option as a property of ConfidentialGuestSupport.

Also add the KVM-specific checks to enable this support, but leave the
option disabled until other required changes are implemented for
CGS variants that intend to make use of KVM's in-place conversion
support.

While technically the convert-in-place option could be introduced as an
SEV-specific option, it is a given that TDX will also be introducing
in-place conversion support based on the same guest_memfd kernel
infrastructure, so introduce it via a new
ConfidentialGuestSupportProperties base class that other confidential VM
implementations can utilized for common options.

Signed-off-by: Michael Roth <michael.roth@amd.com>
---
 accel/kvm/kvm-all.c                         | 19 +++++++++++++++
 backends/confidential-guest-support.c       | 26 +++++++++++++++++++++
 hw/core/machine.c                           |  5 ++++
 include/hw/core/boards.h                    |  1 +
 include/system/confidential-guest-support.h | 14 +++++++++++
 qapi/qom.json                               | 16 +++++++++++++
 6 files changed, 81 insertions(+)

diff --git a/accel/kvm/kvm-all.c b/accel/kvm/kvm-all.c
index 15f237de4e..2b838eb690 100644
--- a/accel/kvm/kvm-all.c
+++ b/accel/kvm/kvm-all.c
@@ -52,6 +52,7 @@
 #include "kvm-cpus.h"
 #include "system/dirtylimit.h"
 #include "qemu/range.h"
+#include "system/confidential-guest-support.h"
 
 #include "hw/core/boards.h"
 #include "system/stats.h"
@@ -3074,6 +3075,24 @@ static int kvm_init(AccelState *as, MachineState *ms)
     kvm_guest_memfd_flags_supported =
         kvm_vm_check_extension(s, KVM_CAP_GUEST_MEMFD_FLAGS);
 
+    if (machine_require_guest_memfd_convert_in_place(ms)) {
+        uint64_t guest_memfd_supported_memory_attributes;
+
+        guest_memfd_supported_memory_attributes =
+            kvm_vm_check_extension(s, KVM_CAP_GUEST_MEMFD_MEMORY_ATTRIBUTES);
+
+        if (!(guest_memfd_supported_memory_attributes & KVM_MEMORY_ATTRIBUTE_PRIVATE)) {
+            ret = -EINVAL;
+            error_report("In-place conversion is only supported if private "
+                         "memory attributes can be set via guest_memfd. "
+                         "Please ensure the 'gmem_in_place_conversion' KVM "
+                         "module parameter is set to 0.");
+            goto err;
+        }
+
+        kvm_supported_memory_attributes = guest_memfd_supported_memory_attributes;
+    }
+
     if (s->kernel_irqchip_split == ON_OFF_AUTO_AUTO) {
         s->kernel_irqchip_split = mc->default_kernel_irqchip_split ? ON_OFF_AUTO_ON : ON_OFF_AUTO_OFF;
     }
diff --git a/backends/confidential-guest-support.c b/backends/confidential-guest-support.c
index 156dd15e66..29447fa18e 100644
--- a/backends/confidential-guest-support.c
+++ b/backends/confidential-guest-support.c
@@ -21,6 +21,25 @@ OBJECT_DEFINE_ABSTRACT_TYPE(ConfidentialGuestSupport,
                             CONFIDENTIAL_GUEST_SUPPORT,
                             OBJECT)
 
+static bool
+cgs_get_convert_in_place(Object *obj, Error **errp)
+{
+    return CONFIDENTIAL_GUEST_SUPPORT(obj)->convert_in_place;
+}
+
+static void
+cgs_set_convert_in_place(Object *obj, bool value, Error **errp)
+{
+    ConfidentialGuestSupport *cgs = CONFIDENTIAL_GUEST_SUPPORT(obj);
+
+    if (!cgs->allow_convert_in_place && value) {
+        error_setg(errp, "In-place conversion support is not supported for this guest configuration.");
+        return;
+    }
+
+    cgs->convert_in_place = value;
+}
+
 static bool check_support(ConfidentialGuestPlatformType platform,
                          uint16_t platform_version, uint8_t highest_vtl,
                          uint64_t shared_gpa_boundary)
@@ -70,6 +89,13 @@ static void confidential_guest_support_class_init(ObjectClass *oc,
 
 static void confidential_guest_support_init(Object *obj)
 {
+    ConfidentialGuestSupport *cgs = CONFIDENTIAL_GUEST_SUPPORT(obj);
+
+    object_property_add_bool(obj, "convert-in-place", cgs_get_convert_in_place,
+                             cgs_set_convert_in_place);
+
+    cgs->convert_in_place = false;
+    cgs->allow_convert_in_place = false;
 }
 
 static void confidential_guest_support_finalize(Object *obj)
diff --git a/hw/core/machine.c b/hw/core/machine.c
index 469033d6b3..5e66dbe193 100644
--- a/hw/core/machine.c
+++ b/hw/core/machine.c
@@ -1342,6 +1342,11 @@ bool machine_require_guest_memfd_private(MachineState *machine)
     return machine->cgs && machine->cgs->require_guest_memfd;
 }
 
+bool machine_require_guest_memfd_convert_in_place(MachineState *machine)
+{
+    return machine->cgs && machine->cgs->convert_in_place;
+}
+
 static char *cpu_slot_to_string(const CPUArchId *cpu)
 {
     GString *s = g_string_new(NULL);
diff --git a/include/hw/core/boards.h b/include/hw/core/boards.h
index 7925f0451a..5e67095f7c 100644
--- a/include/hw/core/boards.h
+++ b/include/hw/core/boards.h
@@ -44,6 +44,7 @@ int machine_phandle_start(MachineState *machine);
 bool machine_dump_guest_core(MachineState *machine);
 bool machine_mem_merge(MachineState *machine);
 bool machine_require_guest_memfd_private(MachineState *machine);
+bool machine_require_guest_memfd_convert_in_place(MachineState *machine);
 HotpluggableCPUList *machine_query_hotpluggable_cpus(MachineState *machine);
 void machine_set_cpu_numa_node(MachineState *machine,
                                const CpuInstanceProperties *props,
diff --git a/include/system/confidential-guest-support.h b/include/system/confidential-guest-support.h
index 5dca717308..c1e9c41ad2 100644
--- a/include/system/confidential-guest-support.h
+++ b/include/system/confidential-guest-support.h
@@ -20,6 +20,7 @@
 
 #include "qom/object.h"
 #include "exec/hwaddr.h"
+#include "qapi/qapi-visit-qom.h"
 
 #define TYPE_CONFIDENTIAL_GUEST_SUPPORT "confidential-guest-support"
 OBJECT_DECLARE_TYPE(ConfidentialGuestSupport,
@@ -92,6 +93,19 @@ struct ConfidentialGuestSupport {
      * so 'ready' is not set, we'll abort.
      */
     bool ready;
+
+    /*
+     * True if the machine re-uses physical pages when converting
+     * between shared/private (as opposed to using different
+     * physical pages depending on the access type).
+     */
+    bool convert_in_place;
+
+    /*
+     * CGS implementations will use this to indicate whether or not
+     * in-place conversion can be enabled by users.
+     */
+    bool allow_convert_in_place;
 };
 
 typedef struct ConfidentialGuestSupportClass {
diff --git a/qapi/qom.json b/qapi/qom.json
index 909add4299..20d6fefb04 100644
--- a/qapi/qom.json
+++ b/qapi/qom.json
@@ -1005,6 +1005,21 @@
   'if': 'CONFIG_IGVM',
   'data': { 'file': 'str' } }
 
+##
+# @ConfidentialGuestSupportProperties:
+#
+# Properties for ConfidentialGuestSupport base class.
+#
+# @convert-in-place: If true, the same physical pages are reused
+#     when memory is converted between shared and private states.
+#     If false (default), separate allocations are used depending
+#     on whether the page is private or shared.
+#
+# Since: 11.2
+##
+{ 'struct': 'ConfidentialGuestSupportProperties',
+  'data': { '*convert-in-place': 'bool' } }
+
 ##
 # @SevCommonProperties:
 #
@@ -1033,6 +1048,7 @@
 # Since: 9.1
 ##
 { 'struct': 'SevCommonProperties',
+  'base': 'ConfidentialGuestSupportProperties',
   'data': { '*sev-device': 'str',
             '*cbitpos': 'uint32',
             'reduced-phys-bits': 'uint32',
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH v2 08/19] system/memory: Re-use memory-backend-guest-memfd inode for private memory
  2026-09-08 20:48 [PATCH v2 00/19] guest_memfd: support in-place memory conversion Michael Roth
                   ` (6 preceding siblings ...)
  2026-09-08 20:48 ` [PATCH v2 07/19] accel/kvm: Add CGS option to control in-place conversion support Michael Roth
@ 2026-09-08 20:48 ` Michael Roth
  2026-09-10  8:39   ` David Hildenbrand
  2026-09-08 20:48 ` [PATCH v2 09/19] accel/kvm: Handle guest_memfd flags internally when creating instances Michael Roth
                   ` (10 subsequent siblings)
  18 siblings, 1 reply; 24+ messages in thread
From: Michael Roth @ 2026-09-08 20:48 UTC (permalink / raw)
  To: qemu-devel
  Cc: kvm, pbonzini, berrange, armbru, pankaj.gupta, isaku.yamahata,
	xiaoyao.li, chao.p.peng, david, ashish.kalra, ackerleytng,
	lpieralisi

When convert-in-place=true, the guest_memfd instance created by
memory-backend-memfd (when guest-memfd=on option is specified) should
also be used internally for private memory.

Do this by dup()'ing the guest_memfd FD provided by the backend so the
separate cleanup paths for shared vs. private FDs can be managed in the
same way they are currently for convert-in-place=false (where shared
memory must come from something other than guest_memfd).

Introduce a new RAM_GUEST_MEMFD_SHARED flag that can be used to
limit this dup()'ing to specific backend types like
memory-backend-memfd.

Signed-off-by: Michael Roth <michael.roth@amd.com>
---
 backends/hostmem-memfd.c |  1 +
 include/system/memory.h  |  3 +++
 system/physmem.c         | 48 +++++++++++++++++++++++++++++++++++++---
 3 files changed, 49 insertions(+), 3 deletions(-)

diff --git a/backends/hostmem-memfd.c b/backends/hostmem-memfd.c
index 6576331441..a9759e682b 100644
--- a/backends/hostmem-memfd.c
+++ b/backends/hostmem-memfd.c
@@ -89,6 +89,7 @@ have_fd:
     backend->aligned = true;
     ram_flags = backend->share ? RAM_SHARED : RAM_PRIVATE;
     ram_flags |= backend->reserve ? 0 : RAM_NORESERVE;
+    ram_flags |= RAM_GUEST_MEMFD_SHARED;
     ram_flags |= backend->guest_memfd_private ? RAM_GUEST_MEMFD_PRIVATE : 0;
     return memory_region_init_ram_from_fd(&backend->mr, OBJECT(backend), name,
                                           backend->size, ram_flags, fd, 0, errp);
diff --git a/include/system/memory.h b/include/system/memory.h
index 027ca81bd2..81616bfe39 100644
--- a/include/system/memory.h
+++ b/include/system/memory.h
@@ -274,6 +274,9 @@ typedef struct IOMMUTLBEvent {
  */
 #define RAM_PRIVATE (1 << 13)
 
+/* RAM can be shared that has kvm guest memfd backend */
+#define RAM_GUEST_MEMFD_SHARED   (1 << 14)
+
 static inline void iommu_notifier_init(IOMMUNotifier *n, IOMMUNotify fn,
                                        IOMMUNotifierFlag flags,
                                        hwaddr start, hwaddr end,
diff --git a/system/physmem.c b/system/physmem.c
index 991f7bb815..d77e6fccb5 100644
--- a/system/physmem.c
+++ b/system/physmem.c
@@ -59,6 +59,7 @@
 #include "system/hostmem.h"
 #include "system/hw_accel.h"
 #include "system/xen-mapcache.h"
+#include "system/confidential-guest-support.h"
 #include "trace.h"
 
 #ifdef CONFIG_FALLOCATE_PUNCH_HOLE
@@ -2185,6 +2186,8 @@ static void ram_block_add(RAMBlock *new_block, Error **errp)
     if (new_block->flags & RAM_GUEST_MEMFD_PRIVATE) {
         int ret;
 
+        assert(current_machine->cgs);
+
         if (!kvm_enabled()) {
             error_setg(errp, "cannot set up private guest memory for %s: KVM required",
                        object_get_typename(OBJECT(current_machine->cgs)));
@@ -2208,10 +2211,43 @@ static void ram_block_add(RAMBlock *new_block, Error **errp)
             goto out_free;
         }
 
-        new_block->guest_memfd_private =
-            kvm_create_guest_memfd_private(new_block->max_length, errp);
+        /*
+         * If both shared/private memory are handled by guest_memfd, make sure
+         * to re-use the guest_memfd inode that should have already been created
+         * for handling shared memory.
+         */
+        if (machine_require_guest_memfd_convert_in_place(current_machine)) {
+            if (!(new_block->flags & RAM_GUEST_MEMFD_SHARED)) {
+                error_setg(errp, "configured memory backend is not compatible"
+                                 " with in-place conversion");
+                qemu_mutex_unlock_ramlist();
+                goto out_free;
+            }
+            assert(new_block->fd >= 0);
+
+            /*
+             * Current logic calculates guest_memfd_offset on the assumption
+             * that offset 0 corresponds to the first GPA that is backed by the
+             * RAM block/backend. For cases where the guest_memfd is only used
+             * for private memory and created internally as-needed this is
+             * always the case, but when re-using a guest_memfd that's also
+             * usable for shared memory (e.g. via memory-backend-guest-memfd)
+             * it's possible that guest_memfd might be mmap()'d starting at some
+             * non-zero offset. For now, this isn't a reachable condition, but
+             * assert this in case this ever changes and the logic needs to be
+             * updated to account for this.
+             */
+            assert(new_block->fd_offset == 0);
+
+            new_block->guest_memfd_private = qemu_dup(new_block->fd);
+        } else {
+            new_block->guest_memfd_private =
+                kvm_create_guest_memfd_private(new_block->max_length, errp);
+        }
+
         if (new_block->guest_memfd_private < 0) {
             qemu_mutex_unlock_ramlist();
+            error_setg(errp, "failed to create guest_memfd instance.");
             goto out_free;
         }
 
@@ -2320,7 +2356,7 @@ RAMBlock *qemu_ram_alloc_from_fd(ram_addr_t size, ram_addr_t max_size,
     assert((ram_flags & ~(RAM_SHARED | RAM_PMEM | RAM_NORESERVE |
                           RAM_PROTECTED | RAM_NAMED_FILE | RAM_READONLY |
                           RAM_READONLY_FD | RAM_GUEST_MEMFD_PRIVATE |
-                          RAM_RESIZEABLE)) == 0);
+                          RAM_RESIZEABLE | RAM_GUEST_MEMFD_SHARED)) == 0);
     assert(max_size >= size);
 
     if (xen_enabled()) {
@@ -2832,6 +2868,12 @@ int ram_block_rebind(Error **errp)
 {
     RAMBlock *block;
 
+    if (machine_require_guest_memfd_convert_in_place(current_machine)) {
+        error_setg(errp,
+                   "rebind support is not yet enabled for in-place conversion");
+        return -1;
+    }
+
     qemu_mutex_lock_ramlist();
 
     RAMBLOCK_FOREACH(block) {
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH v2 09/19] accel/kvm: Handle guest_memfd flags internally when creating instances
  2026-09-08 20:48 [PATCH v2 00/19] guest_memfd: support in-place memory conversion Michael Roth
                   ` (7 preceding siblings ...)
  2026-09-08 20:48 ` [PATCH v2 08/19] system/memory: Re-use memory-backend-guest-memfd inode for private memory Michael Roth
@ 2026-09-08 20:48 ` Michael Roth
  2026-09-08 20:48 ` [PATCH v2 10/19] system/memory: Default to guest_memfd for RAM for in-place conversion Michael Roth
                   ` (9 subsequent siblings)
  18 siblings, 0 replies; 24+ messages in thread
From: Michael Roth @ 2026-09-08 20:48 UTC (permalink / raw)
  To: qemu-devel
  Cc: kvm, pbonzini, berrange, armbru, pankaj.gupta, isaku.yamahata,
	xiaoyao.li, chao.p.peng, david, ashish.kalra, ackerleytng,
	lpieralisi

Current hostmem code (which is callable even outside of KVM) specifies
guest_memfd flags directly, which requires pulling in kernel headers.
Additionally, it will eventually require additional knowledge that is
more suitable for KVM-aware code (e.g. whether or not to default to
private memory for confidential VMs).

Instead, push the determination for what flags are needed down into the
KVM/guest_memfd code so they can be handled internally based on VM
type/configuration and avoid further potential leakage of KVM-specific
code to general code.

Signed-off-by: Michael Roth <michael.roth@amd.com>
---
 accel/kvm/kvm-all.c      | 26 ++++++++++++++++++++++++--
 accel/stubs/kvm-stub.c   |  2 +-
 backends/hostmem-memfd.c |  6 +-----
 include/system/kvm.h     |  2 +-
 4 files changed, 27 insertions(+), 9 deletions(-)

diff --git a/accel/kvm/kvm-all.c b/accel/kvm/kvm-all.c
index 2b838eb690..bf81a19423 100644
--- a/accel/kvm/kvm-all.c
+++ b/accel/kvm/kvm-all.c
@@ -4857,7 +4857,8 @@ void kvm_mark_guest_state_protected(void)
     kvm_state->guest_state_protected = true;
 }
 
-int kvm_create_guest_memfd(uint64_t size, uint64_t flags, Error **errp)
+static int kvm_create_guest_memfd_flags(uint64_t size, uint64_t flags,
+                                        Error **errp)
 {
     int fd;
     struct kvm_create_guest_memfd guest_memfd = {
@@ -4898,6 +4899,27 @@ int kvm_create_guest_memfd(uint64_t size, uint64_t flags, Error **errp)
     return fd;
 }
 
+int kvm_create_guest_memfd(uint64_t size, Error **errp)
+{
+    /*
+     * There isn't currently any use for non-mmap()'able gmem instances
+     * outside of kvm_create_guest_memfd_private(), so hardcode it here
+     * for general use.
+     *
+     * Additionally, *_INIT_SHARED is needed for non-confidential VMs, and
+     * confidential VMs will default to this as well to allow similar flows
+     * for initializing the VM's initial memory contents as with normal
+     * guests. In the future, the initial state may become a global policy
+     * decision based on the confidential VM configuration, in which case
+     * this would likely be the right place to decide whether or not to set
+     * the flag since it would likely be a globally-configured option.
+     */
+    return kvm_create_guest_memfd_flags(size,
+                                        GUEST_MEMFD_FLAG_MMAP |
+                                        GUEST_MEMFD_FLAG_INIT_SHARED,
+                                        errp);
+}
+
 int kvm_create_guest_memfd_private(uint64_t size, Error **errp)
 {
     if (!(kvm_supported_memory_attributes & KVM_MEMORY_ATTRIBUTE_PRIVATE)) {
@@ -4905,5 +4927,5 @@ int kvm_create_guest_memfd_private(uint64_t size, Error **errp)
         return -1;
     }
 
-    return kvm_create_guest_memfd(size, 0, errp);
+    return kvm_create_guest_memfd_flags(size, 0, errp);
 }
diff --git a/accel/stubs/kvm-stub.c b/accel/stubs/kvm-stub.c
index 9fe58efe91..26003b01a3 100644
--- a/accel/stubs/kvm-stub.c
+++ b/accel/stubs/kvm-stub.c
@@ -139,7 +139,7 @@ bool kvm_hwpoisoned_mem(void)
     return false;
 }
 
-int kvm_create_guest_memfd(uint64_t size, uint64_t flags, Error **errp)
+int kvm_create_guest_memfd(uint64_t size, Error **errp)
 {
     error_setg(errp, "KVM is not enabled");
     return -ENOSYS;
diff --git a/backends/hostmem-memfd.c b/backends/hostmem-memfd.c
index a9759e682b..de51cf738a 100644
--- a/backends/hostmem-memfd.c
+++ b/backends/hostmem-memfd.c
@@ -19,7 +19,6 @@
 #include "qom/object.h"
 #include "migration/cpr.h"
 #include "system/kvm.h"
-#include <linux/kvm.h>
 
 OBJECT_DECLARE_SIMPLE_TYPE(HostMemoryBackendMemfd, MEMORY_BACKEND_MEMFD)
 
@@ -69,10 +68,7 @@ memfd_backend_memory_alloc(HostMemoryBackend *backend, Error **errp)
             return false;
         }
 
-        fd = kvm_create_guest_memfd(backend->size,
-                                    GUEST_MEMFD_FLAG_MMAP |
-                                    GUEST_MEMFD_FLAG_INIT_SHARED,
-                                    errp);
+        fd = kvm_create_guest_memfd(backend->size, errp);
     } else {
         fd = qemu_memfd_create(TYPE_MEMORY_BACKEND_MEMFD, backend->size,
                                m->hugetlb, m->hugetlbsize, m->seal ?
diff --git a/include/system/kvm.h b/include/system/kvm.h
index b1e43ddc93..2a1501bba6 100644
--- a/include/system/kvm.h
+++ b/include/system/kvm.h
@@ -547,7 +547,7 @@ void kvm_mark_guest_state_protected(void);
  */
 bool kvm_hwpoisoned_mem(void);
 
-int kvm_create_guest_memfd(uint64_t size, uint64_t flags, Error **errp);
+int kvm_create_guest_memfd(uint64_t size, Error **errp);
 int kvm_create_guest_memfd_private(uint64_t size, Error **errp);
 
 int kvm_set_memory_attributes_private(hwaddr start, uint64_t size);
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH v2 10/19] system/memory: Default to guest_memfd for RAM for in-place conversion
  2026-09-08 20:48 [PATCH v2 00/19] guest_memfd: support in-place memory conversion Michael Roth
                   ` (8 preceding siblings ...)
  2026-09-08 20:48 ` [PATCH v2 09/19] accel/kvm: Handle guest_memfd flags internally when creating instances Michael Roth
@ 2026-09-08 20:48 ` Michael Roth
  2026-09-08 20:48 ` [PATCH v2 11/19] accel/kvm: Move post-conversion updates to a separate helper Michael Roth
                   ` (8 subsequent siblings)
  18 siblings, 0 replies; 24+ messages in thread
From: Michael Roth @ 2026-09-08 20:48 UTC (permalink / raw)
  To: qemu-devel
  Cc: kvm, pbonzini, berrange, armbru, pankaj.gupta, isaku.yamahata,
	xiaoyao.li, chao.p.peng, david, ashish.kalra, ackerleytng,
	lpieralisi

memory_region_init_ram_guest_memfd_private() is called in some cases
(legacy BIOS regions / IGVM regions) to allocate a new RAM region with a
guest_memfd FD under the covers to handle private memory since the GPA
range can be converted between shared/private guest RAM.

When in-place conversion is enabled, the conversions happen with the
guest_memfd inode itself, so the same inode must be used for both shared
and private memory. Handle this accordingly when convert-in-place=true.

Signed-off-by: Michael Roth <michael.roth@amd.com>
---
 system/memory.c | 23 ++++++++++++++++++++---
 1 file changed, 20 insertions(+), 3 deletions(-)

diff --git a/system/memory.c b/system/memory.c
index b3b678617b..ba3ef27b58 100644
--- a/system/memory.c
+++ b/system/memory.c
@@ -3623,10 +3623,27 @@ bool memory_region_init_ram_guest_memfd_private(MemoryRegion *mr,
                                                 uint64_t size,
                                                 Error **errp)
 {
-    if (!memory_region_init_ram_flags_nomigrate(mr, owner, name, size,
-                                                RAM_GUEST_MEMFD_PRIVATE, errp)) {
-        return false;
+    if (machine_require_guest_memfd_convert_in_place(current_machine)) {
+        int fd = kvm_create_guest_memfd(size, errp);
+        if (fd < 0) {
+            return false;
+        }
+
+        if (!memory_region_init_ram_from_fd(mr, owner, name, size,
+                                            RAM_SHARED |
+                                            RAM_GUEST_MEMFD_PRIVATE |
+                                            RAM_GUEST_MEMFD_SHARED,
+                                            fd, 0, errp)) {
+            close(fd);
+            return false;
+        }
+    } else {
+        if (!memory_region_init_ram_flags_nomigrate(mr, owner, name, size,
+                                                    RAM_GUEST_MEMFD_PRIVATE, errp)) {
+            return false;
+        }
     }
+
     memory_region_register_ram(mr, owner);
     return true;
 }
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH v2 11/19] accel/kvm: Move post-conversion updates to a separate helper
  2026-09-08 20:48 [PATCH v2 00/19] guest_memfd: support in-place memory conversion Michael Roth
                   ` (9 preceding siblings ...)
  2026-09-08 20:48 ` [PATCH v2 10/19] system/memory: Default to guest_memfd for RAM for in-place conversion Michael Roth
@ 2026-09-08 20:48 ` Michael Roth
  2026-09-08 20:48 ` [PATCH v2 12/19] accel/kvm: Re-order attribute notifications for in-place conversion Michael Roth
                   ` (7 subsequent siblings)
  18 siblings, 0 replies; 24+ messages in thread
From: Michael Roth @ 2026-09-08 20:48 UTC (permalink / raw)
  To: qemu-devel
  Cc: kvm, pbonzini, berrange, armbru, pankaj.gupta, isaku.yamahata,
	xiaoyao.li, chao.p.peng, david, ashish.kalra, ackerleytng,
	lpieralisi

Currently memory attribute conversions are followed up by other
bookkeeping tasks like discarding unused memory or issuing iommufd
notifications. Move these tasks to a separate post-conversions helper to
better compartmentalize and track these tasks, and in doing so lay the
groundwork for a pre-conversion helper which will be needed in the
future.

Signed-off-by: Michael Roth <michael.roth@amd.com>
---
 accel/kvm/kvm-all.c | 28 ++++++++++++++++++++--------
 1 file changed, 20 insertions(+), 8 deletions(-)

diff --git a/accel/kvm/kvm-all.c b/accel/kvm/kvm-all.c
index bf81a19423..b6a3839055 100644
--- a/accel/kvm/kvm-all.c
+++ b/accel/kvm/kvm-all.c
@@ -3437,20 +3437,26 @@ static int kvm_convert_section(MemoryRegionSection *section, bool to_private)
 {
     hwaddr start = section->offset_within_address_space;
     hwaddr size = int128_get64(section->size);
-    MemoryRegion *mr = section->mr;
-    ram_addr_t offset;
-    RAMBlock *rb;
-    void *addr;
-    int ret = -EINVAL;
+    int ret;
 
     if (to_private) {
         ret = kvm_set_memory_attributes_private(start, size);
     } else {
         ret = kvm_set_memory_attributes_shared(start, size);
     }
-    if (ret) {
-        return ret;
-    }
+
+    return ret;
+}
+
+static int kvm_post_convert_section(MemoryRegionSection *section, bool to_private)
+{
+    hwaddr start = section->offset_within_address_space;
+    hwaddr size = int128_get64(section->size);
+    MemoryRegion *mr = section->mr;
+    ram_addr_t offset;
+    RAMBlock *rb;
+    void *addr;
+    int ret;
 
     addr = memory_region_get_ram_ptr(mr) + section->offset_within_region;
     rb = qemu_ram_block_from_host(addr, false, &offset);
@@ -3525,6 +3531,12 @@ int kvm_convert_memory(hwaddr start, hwaddr size, bool to_private)
         }
 
         ret = kvm_convert_section(&section, to_private);
+        if (ret) {
+            memory_region_unref(section.mr);
+            break;
+        }
+
+        ret = kvm_post_convert_section(&section, to_private);
         memory_region_unref(section.mr);
 
         if (ret) {
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH v2 12/19] accel/kvm: Re-order attribute notifications for in-place conversion
  2026-09-08 20:48 [PATCH v2 00/19] guest_memfd: support in-place memory conversion Michael Roth
                   ` (10 preceding siblings ...)
  2026-09-08 20:48 ` [PATCH v2 11/19] accel/kvm: Move post-conversion updates to a separate helper Michael Roth
@ 2026-09-08 20:48 ` Michael Roth
  2026-09-08 20:48 ` [PATCH v2 13/19] accel/kvm: Support shared/private conversions via guest_memfd ioctls Michael Roth
                   ` (6 subsequent siblings)
  18 siblings, 0 replies; 24+ messages in thread
From: Michael Roth @ 2026-09-08 20:48 UTC (permalink / raw)
  To: qemu-devel
  Cc: kvm, pbonzini, berrange, armbru, pankaj.gupta, isaku.yamahata,
	xiaoyao.li, chao.p.peng, david, ashish.kalra, ackerleytng,
	lpieralisi

ram-block-attribute update notifications are currently sent after
conversions from/to private pages to trigger DMA maps/unmaps of shared
GPA ranges (respectively). However, with in-place conversion additional
requirements on the kernel side come into play which require this
behavior to be adjusted.

For shared->private conversions: the attributes need to be set to
private *after* the notification, since when using VFIO it may not be
possible to update the attribute while it remains pinned due to the
IOMMU mapping, so issue the notification first to ensure unmappings are
done in advance.

For private->shared conversions: the attributes need to be set to shared
*before* the notification, since it will possibly result in the page
being mapped into an IOMMU and trigger guest_memfd's fault handler,
which will expect the page to have its attributes set to shared or
otherwise SIGBUS.

Implement this to enable passthrough support for CoCo guests with
in-place conversion support enabled. For non-inplace conversion, pages
mapped into the IOMMU are not the same physical pages as the one used
for private accesses by the guest, so neither order risks DMA accesses
to private memory and that path can be consolidated to use the same
handling as well.

Signed-off-by: Michael Roth <michael.roth@amd.com>
---
 accel/kvm/kvm-all.c | 60 +++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 58 insertions(+), 2 deletions(-)

diff --git a/accel/kvm/kvm-all.c b/accel/kvm/kvm-all.c
index b6a3839055..17e25f9074 100644
--- a/accel/kvm/kvm-all.c
+++ b/accel/kvm/kvm-all.c
@@ -3448,7 +3448,7 @@ static int kvm_convert_section(MemoryRegionSection *section, bool to_private)
     return ret;
 }
 
-static int kvm_post_convert_section(MemoryRegionSection *section, bool to_private)
+static int kvm_pre_convert_section(MemoryRegionSection *section, bool to_private)
 {
     hwaddr start = section->offset_within_address_space;
     hwaddr size = int128_get64(section->size);
@@ -3458,16 +3458,66 @@ static int kvm_post_convert_section(MemoryRegionSection *section, bool to_privat
     void *addr;
     int ret;
 
+    if (!to_private)
+        return 0;
+
     addr = memory_region_get_ram_ptr(mr) + section->offset_within_region;
     rb = qemu_ram_block_from_host(addr, false, &offset);
 
+    /*
+     * The attributes need to be set to private *after* the notification
+     * of a shared->private conversion, since when using VFIO it may not
+     * be possible to update the attribute while it remains pinned due
+     * to the IOMMU mapping, so issue the notification first to ensure
+     * unmappings are done in advance.
+     *
+     * There is an asymmetry here in that if the subsequent memory
+     * attribute update fails, this notification is out of sync with the
+     * state as tracked by guest_memfd, which isn't ideal, but memory
+     * attribute failures are not expected to be recoverable any way so
+     * there it would be a waste of time to roll back the notification and
+     * re-trigger things like mapping the page via iommufd.
+     */
     ret = ram_block_attributes_state_change(rb->attributes,
                                             offset, size, to_private);
     if (ret) {
         error_report("Failed to notify the listener the state change of "
                      "(0x%"HWADDR_PRIx" + 0x%"HWADDR_PRIx") to %s, ret %d",
                      start, size, to_private ? "private" : "shared", ret);
-        return ret;
+    }
+
+    return ret;
+}
+
+static int kvm_post_convert_section(MemoryRegionSection *section, bool to_private)
+{
+    hwaddr start = section->offset_within_address_space;
+    hwaddr size = int128_get64(section->size);
+    MemoryRegion *mr = section->mr;
+    ram_addr_t offset;
+    RAMBlock *rb;
+    void *addr;
+    int ret;
+
+    addr = memory_region_get_ram_ptr(mr) + section->offset_within_region;
+    rb = qemu_ram_block_from_host(addr, false, &offset);
+
+    /*
+     * The attributes need to have been set to shared *before* the notification
+     * of a private->shared conversion, since it will possibly result in the
+     * page being mapped into an IOMMU when using VFIO and trigger
+     * guest_memfd's fault handler, which will expect the page to have its
+     * attributes set to shared.
+     */
+    if (!to_private) {
+        ret = ram_block_attributes_state_change(rb->attributes,
+                                                offset, size, to_private);
+        if (ret) {
+            error_report("Failed to notify the listener the state change of "
+                         "(0x%"HWADDR_PRIx" + 0x%"HWADDR_PRIx") to %s, ret %d",
+                         start, size, to_private ? "private" : "shared", ret);
+            return ret;
+        }
     }
 
     if (to_private) {
@@ -3530,6 +3580,12 @@ int kvm_convert_memory(hwaddr start, hwaddr size, bool to_private)
             continue;
         }
 
+        ret = kvm_pre_convert_section(&section, to_private);
+        if (ret) {
+            memory_region_unref(section.mr);
+            break;
+        }
+
         ret = kvm_convert_section(&section, to_private);
         if (ret) {
             memory_region_unref(section.mr);
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH v2 13/19] accel/kvm: Support shared/private conversions via guest_memfd ioctls
  2026-09-08 20:48 [PATCH v2 00/19] guest_memfd: support in-place memory conversion Michael Roth
                   ` (11 preceding siblings ...)
  2026-09-08 20:48 ` [PATCH v2 12/19] accel/kvm: Re-order attribute notifications for in-place conversion Michael Roth
@ 2026-09-08 20:48 ` Michael Roth
  2026-09-08 20:48 ` [PATCH v2 14/19] accel/kvm: Don't default to private attributes for in-place conversion Michael Roth
                   ` (5 subsequent siblings)
  18 siblings, 0 replies; 24+ messages in thread
From: Michael Roth @ 2026-09-08 20:48 UTC (permalink / raw)
  To: qemu-devel
  Cc: kvm, pbonzini, berrange, armbru, pankaj.gupta, isaku.yamahata,
	xiaoyao.li, chao.p.peng, david, ashish.kalra, ackerleytng,
	lpieralisi

When using guest_memfd with support for shared memory / in-place
conversion, it is necessary to use the guest_memfd ioctls to handle
conversions instead of KVM ioctls. Implement support for this by looping
through all the sections within a converison range. Implement everything
in terms of the kvm_convert_memory() loop, which already deals with some
special considerations regarding various holes / region types that might
be encountered.

Also update kvm_set_memory_attributes_*() to use the same common path
when convert-in-place=false. This potentially results in a small change
in behavior due to the additional MMIO checks/skips now being applied in
that case (generally qemu-triggered during setup) rather than only for
kvm_convert_memory() (generally guest-triggered), but this is arguably
safer, and it provides similar behavior between convert-in-place=false
vs. convert-in-place=true, the latter of which *must* skip MMIO holes
because the regions (and associated guest_memfds) themselves track
shared/private state internally and passing the whole conversion range
through to KVM is not an option in that case.

Signed-off-by: Michael Roth <michael.roth@amd.com>
---
 accel/kvm/kvm-all.c | 131 ++++++++++++++++++++++++++++++++++++++------
 1 file changed, 114 insertions(+), 17 deletions(-)

diff --git a/accel/kvm/kvm-all.c b/accel/kvm/kvm-all.c
index 17e25f9074..fcb6f3bee9 100644
--- a/accel/kvm/kvm-all.c
+++ b/accel/kvm/kvm-all.c
@@ -1624,14 +1624,78 @@ static int kvm_set_memory_attributes(hwaddr start, uint64_t size, uint64_t attr)
     return r;
 }
 
-int kvm_set_memory_attributes_private(hwaddr start, uint64_t size)
+static int kvm_gmem_ioctl(int guest_memfd, unsigned long type, ...)
 {
-    return kvm_set_memory_attributes(start, size, KVM_MEMORY_ATTRIBUTE_PRIVATE);
+    int ret;
+    void *arg;
+    va_list ap;
+
+    va_start(ap, type);
+    arg = va_arg(ap, void *);
+    va_end(ap);
+
+    ret = ioctl(guest_memfd, type, arg);
+    if (ret == -1) {
+        ret = -errno;
+    }
+    return ret;
 }
 
-int kvm_set_memory_attributes_shared(hwaddr start, uint64_t size)
+static int guest_memfd_set_memory_attributes_fd(int guest_memfd, hwaddr offset,
+                                                uint64_t size, uint64_t attr)
 {
-    return kvm_set_memory_attributes(start, size, 0);
+    struct kvm_memory_attributes2 attrs = {0};
+    int r;
+
+    assert((attr & kvm_supported_memory_attributes) == attr);
+    attrs.attributes = attr;
+    attrs.offset = offset;
+    attrs.size = size;
+    attrs.flags = 0;
+
+    /*
+     * guest_memfd may need to delay conversion requests due to
+     * the memory being in-use by the kernel. In most cases these
+     * will be transient uses. In some cases, userspace itself may
+     * be the cause of the memory being considered in-use, though
+     * QEMU currently takes steps to avoid this (e.g. via
+     * RamBlockAttributes). On that basis, this code loops
+     * indefinitely with the assumption that only transient cases
+     * will block, and that those will be for relatively short
+     * periods vs. the overall conversion path.
+     * If those assumptions at some point prove false, most likely
+     * this will manifest as guest-side lockups on their conversion
+     * path, which seems like the appropriate way to surface this
+     * situation to the guest owner rather than some hard timeout.
+     */
+    do {
+        r = kvm_gmem_ioctl(guest_memfd, KVM_SET_MEMORY_ATTRIBUTES2, &attrs);
+    } while (r == -EAGAIN);
+
+    if (r) {
+        error_report("failed to set memory (0x%" HWADDR_PRIx "+0x%" PRIx64 ") "
+                     "with attr 0x%" PRIx64 " error '%s'",
+                     offset, size, attr, strerror(-r));
+    }
+    return r;
+}
+
+static int guest_memfd_set_memory_section_attributes(MemoryRegionSection *section, uint64_t attr)
+{
+    hwaddr convert_offset, convert_size;
+    MemoryRegion *mr = section->mr;
+    RAMBlock *rb;
+
+    assert(mr);
+    rb = mr->ram_block;
+    assert(rb->guest_memfd_private >= 0);
+    convert_offset = section->offset_within_region;
+    convert_size = int128_get64(section->size);
+
+    return guest_memfd_set_memory_attributes_fd(rb->guest_memfd_private,
+                                                convert_offset,
+                                                convert_size,
+                                                attr);
 }
 
 bool kvm_private_memory_attribute_supported(void)
@@ -3439,10 +3503,18 @@ static int kvm_convert_section(MemoryRegionSection *section, bool to_private)
     hwaddr size = int128_get64(section->size);
     int ret;
 
-    if (to_private) {
-        ret = kvm_set_memory_attributes_private(start, size);
+    if (machine_require_guest_memfd_convert_in_place(current_machine)) {
+        ret = guest_memfd_set_memory_section_attributes(section,
+                                                        to_private ? KVM_MEMORY_ATTRIBUTE_PRIVATE
+                                                                   : 0);
     } else {
-        ret = kvm_set_memory_attributes_shared(start, size);
+        /*
+         * Without in-place conversion, attribute-tracking is handled by KVM
+         * across all guest memory rather than on a per-section/slot basis.
+         */
+        ret = kvm_set_memory_attributes(start, size,
+                                        to_private ? KVM_MEMORY_ATTRIBUTE_PRIVATE
+                                                   : 0);
     }
 
     return ret;
@@ -3536,7 +3608,8 @@ static int kvm_post_convert_section(MemoryRegionSection *section, bool to_privat
     return ret;
 }
 
-int kvm_convert_memory(hwaddr start, hwaddr size, bool to_private)
+static int kvm_convert_memory_full(hwaddr start, hwaddr size, bool to_private,
+                                   bool pre_hooks, bool post_hooks)
 {
     int ret = -EINVAL;
 
@@ -3580,10 +3653,12 @@ int kvm_convert_memory(hwaddr start, hwaddr size, bool to_private)
             continue;
         }
 
-        ret = kvm_pre_convert_section(&section, to_private);
-        if (ret) {
-            memory_region_unref(section.mr);
-            break;
+        if (pre_hooks) {
+            ret = kvm_pre_convert_section(&section, to_private);
+            if (ret) {
+                memory_region_unref(section.mr);
+                break;
+            }
         }
 
         ret = kvm_convert_section(&section, to_private);
@@ -3592,13 +3667,15 @@ int kvm_convert_memory(hwaddr start, hwaddr size, bool to_private)
             break;
         }
 
-        ret = kvm_post_convert_section(&section, to_private);
-        memory_region_unref(section.mr);
-
-        if (ret) {
-            break;
+        if (post_hooks) {
+            ret = kvm_post_convert_section(&section, to_private);
+            if (ret) {
+                memory_region_unref(section.mr);
+                break;
+            }
         }
 
+        memory_region_unref(section.mr);
         size -= section_end - start;
         start = section_end;
     }
@@ -3606,6 +3683,26 @@ int kvm_convert_memory(hwaddr start, hwaddr size, bool to_private)
     return ret;
 }
 
+int kvm_convert_memory(hwaddr start, hwaddr size, bool to_private)
+{
+    return kvm_convert_memory_full(start, size, to_private, true, true);
+}
+
+static int kvm_convert_memory_attributes(hwaddr start, hwaddr size, bool to_private)
+{
+    return kvm_convert_memory_full(start, size, to_private, false, false);
+}
+
+int kvm_set_memory_attributes_private(hwaddr start, uint64_t size)
+{
+    return kvm_convert_memory_attributes(start, size, KVM_MEMORY_ATTRIBUTE_PRIVATE);
+}
+
+int kvm_set_memory_attributes_shared(hwaddr start, uint64_t size)
+{
+    return kvm_convert_memory_attributes(start, size, 0);
+}
+
 int kvm_cpu_exec(CPUState *cpu)
 {
     struct kvm_run *run = cpu->kvm_run;
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH v2 14/19] accel/kvm: Don't default to private attributes for in-place conversion
  2026-09-08 20:48 [PATCH v2 00/19] guest_memfd: support in-place memory conversion Michael Roth
                   ` (12 preceding siblings ...)
  2026-09-08 20:48 ` [PATCH v2 13/19] accel/kvm: Support shared/private conversions via guest_memfd ioctls Michael Roth
@ 2026-09-08 20:48 ` Michael Roth
  2026-09-08 20:48 ` [PATCH v2 15/19] i386/sev: Update SNP_LAUNCH_UPDATE " Michael Roth
                   ` (4 subsequent siblings)
  18 siblings, 0 replies; 24+ messages in thread
From: Michael Roth @ 2026-09-08 20:48 UTC (permalink / raw)
  To: qemu-devel
  Cc: kvm, pbonzini, berrange, armbru, pankaj.gupta, isaku.yamahata,
	xiaoyao.li, chao.p.peng, david, ashish.kalra, ackerleytng,
	lpieralisi

Without in-place conversion, QEMU can still access shared memory to load
initial state into guest memory prior to launch even if the GPA's memory
attributes default to private, since userspace is accessing a completely
separate pool of memory. With in-place conversion, all these accesses
would need to first be converted to shared, then back to private, since
the memory all comes from guest_memfd and only shared memory can be
accessed by userspace.

Additionally, with in-place conversion, the most efficient way to set
the default memory attribute state is via the presence/abscence of the
GUEST_MEMFD_FLAG_INIT_SHARED, which is handled in the guest_memfd
creation routine, so if future in-place conversion implementations wish
to set the default to something otherwise then this would likely not be
the right place to do so.

To account for that and avoid sprinkling these differences in behavior
throughout QEMU when in-place conversion is enabled, just default to
shared. This does not compromise guest security, since Confidential VMs
will necessarily enforce this via trusted entities, and simply generate
implicit page state changes if their default expectations don't match
KVM's. However, in most cases a guest will explicitly convert memory to
a particular state before actually using it, so even these implicit
conversion requests should be rare. If this ends up needing to be
changed for other reasons in the future, the guest_memfd creation is the
right place to handle it for in-place conversions.

Signed-off-by: Michael Roth <michael.roth@amd.com>
---
 accel/kvm/kvm-all.c | 21 ++++++++++++++++++++-
 1 file changed, 20 insertions(+), 1 deletion(-)

diff --git a/accel/kvm/kvm-all.c b/accel/kvm/kvm-all.c
index fcb6f3bee9..ff07050935 100644
--- a/accel/kvm/kvm-all.c
+++ b/accel/kvm/kvm-all.c
@@ -1811,7 +1811,26 @@ static void kvm_set_phys_mem(KVMMemoryListener *kml,
             abort();
         }
 
-        if (memory_region_has_guest_memfd_private(mr)) {
+        /*
+         * Without in-place conversion, QEMU can still access shared memory
+         * to load initial state into guest memory prior to launch even if
+         * the GPA's memory attributes default to private, since userspace
+         * is accessing a completely separate pool of memory. With in-place
+         * conversion, all these accesses would need to first be converted
+         * to shared, then back to private, since the memory all comes from
+         * guest_memfd and only shared memory can be accessed by userspace.
+         *
+         * To avoid sprinkling these differences in behavior throughout QEMU
+         * when in-place conversion is enabled, just default to shared. This
+         * does not compromise guest security, since Confidential VMs will
+         * necessarily enforce this via trusted entities, and simply generate
+         * implicit page state changes if their default expectations don't
+         * match KVM's. However, in most cases a guest will explicitly
+         * convert memory to a particular state before actually using it, so
+         * even these implicit conversion requests should be rare.
+         */
+        if (memory_region_has_guest_memfd_private(mr) &&
+            !machine_require_guest_memfd_convert_in_place(current_machine)) {
             err = kvm_set_memory_attributes_private(start_addr, slot_size);
             if (err) {
                 error_report("%s: failed to set memory attribute private: %s",
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH v2 15/19] i386/sev: Update SNP_LAUNCH_UPDATE for in-place conversion
  2026-09-08 20:48 [PATCH v2 00/19] guest_memfd: support in-place memory conversion Michael Roth
                   ` (13 preceding siblings ...)
  2026-09-08 20:48 ` [PATCH v2 14/19] accel/kvm: Don't default to private attributes for in-place conversion Michael Roth
@ 2026-09-08 20:48 ` Michael Roth
  2026-09-08 20:48 ` [PATCH v2 16/19] i386/sev: Allow in-place conversion for SEV-SNP guests Michael Roth
                   ` (3 subsequent siblings)
  18 siblings, 0 replies; 24+ messages in thread
From: Michael Roth @ 2026-09-08 20:48 UTC (permalink / raw)
  To: qemu-devel
  Cc: kvm, pbonzini, berrange, armbru, pankaj.gupta, isaku.yamahata,
	xiaoyao.li, chao.p.peng, david, ashish.kalra, ackerleytng,
	lpieralisi

For in-place conversion, the source pointer is expected to be NULL since
the data has already been written directly to guest memory and doesn't
need to be copied in prior to encrypting it in-place for initial guest
memory payload.

Signed-off-by: Michael Roth <michael.roth@amd.com>
---
 target/i386/sev.c | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/target/i386/sev.c b/target/i386/sev.c
index cc16c6b071..87d88b7920 100644
--- a/target/i386/sev.c
+++ b/target/i386/sev.c
@@ -1240,7 +1240,15 @@ sev_snp_launch_update(SevSnpGuestState *sev_snp_guest,
         memcpy(&snp_cpuid_info, data->hva, sizeof(snp_cpuid_info));
     }
 
-    update.uaddr = (__u64)(unsigned long)data->hva;
+    /*
+     * For in-place conversion, the source pointer is expected to be NULL
+     * since the data has already been written directly to guest memory
+     * and only needs to be encrypted in-place for secure access.
+     */
+    if (!machine_require_guest_memfd_convert_in_place(
+            MACHINE(qdev_get_machine()))) {
+        update.uaddr = (__u64)(unsigned long)data->hva;
+    }
     update.gfn_start = data->gpa >> TARGET_PAGE_BITS;
     update.len = data->len;
     update.type = data->type;
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH v2 16/19] i386/sev: Allow in-place conversion for SEV-SNP guests
  2026-09-08 20:48 [PATCH v2 00/19] guest_memfd: support in-place memory conversion Michael Roth
                   ` (14 preceding siblings ...)
  2026-09-08 20:48 ` [PATCH v2 15/19] i386/sev: Update SNP_LAUNCH_UPDATE " Michael Roth
@ 2026-09-08 20:48 ` Michael Roth
  2026-09-08 20:48 ` [PATCH v2 17/19] i386/sev: Update CPUID failure handling for in-place conversion Michael Roth
                   ` (2 subsequent siblings)
  18 siblings, 0 replies; 24+ messages in thread
From: Michael Roth @ 2026-09-08 20:48 UTC (permalink / raw)
  To: qemu-devel
  Cc: kvm, pbonzini, berrange, armbru, pankaj.gupta, isaku.yamahata,
	xiaoyao.li, chao.p.peng, david, ashish.kalra, ackerleytng,
	lpieralisi

All the necessary changes are now in place for an SNP guest to be able
to leverage in-place conversion support. Allow it to be switched on by
users. KVM-specific checks will still gate whether or not the option is
ultimately allowed, this just allows the option to be set via
command-line.

Signed-off-by: Michael Roth <michael.roth@amd.com>
---
 target/i386/sev.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/target/i386/sev.c b/target/i386/sev.c
index 87d88b7920..dc9934d5f3 100644
--- a/target/i386/sev.c
+++ b/target/i386/sev.c
@@ -3313,6 +3313,7 @@ sev_snp_guest_instance_init(Object *obj)
     SevSnpGuestState *sev_snp_guest = SEV_SNP_GUEST(obj);
 
     cgs->require_guest_memfd = true;
+    cgs->allow_convert_in_place = true;
 
     /* default init/start/finish params for kvm */
     sev_snp_guest->kvm_start_conf.policy = DEFAULT_SEV_SNP_POLICY;
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH v2 17/19] i386/sev: Update CPUID failure handling for in-place conversion
  2026-09-08 20:48 [PATCH v2 00/19] guest_memfd: support in-place memory conversion Michael Roth
                   ` (15 preceding siblings ...)
  2026-09-08 20:48 ` [PATCH v2 16/19] i386/sev: Allow in-place conversion for SEV-SNP guests Michael Roth
@ 2026-09-08 20:48 ` Michael Roth
  2026-09-08 20:48 ` [PATCH v2 18/19] accel/kvm: Disable discard " Michael Roth
  2026-09-08 20:48 ` [PATCH v2 19/19] hostmem: Automatically select set guest-memfd=on " Michael Roth
  18 siblings, 0 replies; 24+ messages in thread
From: Michael Roth @ 2026-09-08 20:48 UTC (permalink / raw)
  To: qemu-devel
  Cc: kvm, pbonzini, berrange, armbru, pankaj.gupta, isaku.yamahata,
	xiaoyao.li, chao.p.peng, david, ashish.kalra, ackerleytng,
	lpieralisi

When SNP_LAUNCH_UPDATE fails to update the CPUID page, the CPUID page
data passed in will be re-written with metadata about what CPUID fields
didn't match with what trusted firmware expected. In the case of
in-place conversion, this will have been the same page that was
initially passed to SNP_LAUNCH_UPDATE, and so it will have been put in
a private state prior to making the call.

Make sure to switch it back to shared before accessing it for
error-reporting, otherwise this path will generate a bus error.

While here, sneak in a typo fixup for the error message that gets
printed immediately afterward.

Signed-off-by: Michael Roth <michael.roth@amd.com>
---
 target/i386/sev.c | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/target/i386/sev.c b/target/i386/sev.c
index dc9934d5f3..46ef3f7778 100644
--- a/target/i386/sev.c
+++ b/target/i386/sev.c
@@ -1277,8 +1277,17 @@ sev_snp_launch_update(SevSnpGuestState *sev_snp_guest,
                          ret, fw_error, fw_error_to_str(fw_error));
 
             if (data->type == KVM_SEV_SNP_PAGE_TYPE_CPUID) {
+                if (machine_require_guest_memfd_convert_in_place(
+                        MACHINE(qdev_get_machine()))) {
+                    ret = kvm_set_memory_attributes_shared(data->gpa, data->len);
+                    if (ret) {
+                        error_report("SEV-SNP: unable to access CPUID page to "
+                                     "check failure reasons");
+                        goto out;
+                    }
+                }
                 sev_snp_cpuid_report_mismatches(&snp_cpuid_info, data->hva);
-                error_report("SEV-SNP: failed update CPUID page");
+                error_report("SEV-SNP: failed to update CPUID page");
             }
             break;
         }
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH v2 18/19] accel/kvm: Disable discard for in-place conversion
  2026-09-08 20:48 [PATCH v2 00/19] guest_memfd: support in-place memory conversion Michael Roth
                   ` (16 preceding siblings ...)
  2026-09-08 20:48 ` [PATCH v2 17/19] i386/sev: Update CPUID failure handling for in-place conversion Michael Roth
@ 2026-09-08 20:48 ` Michael Roth
  2026-09-08 22:16   ` Michael Roth
  2026-09-08 20:48 ` [PATCH v2 19/19] hostmem: Automatically select set guest-memfd=on " Michael Roth
  18 siblings, 1 reply; 24+ messages in thread
From: Michael Roth @ 2026-09-08 20:48 UTC (permalink / raw)
  To: qemu-devel
  Cc: kvm, pbonzini, berrange, armbru, pankaj.gupta, isaku.yamahata,
	xiaoyao.li, chao.p.peng, david, ashish.kalra, ackerleytng,
	lpieralisi

When using in-place conversion, there is no need to discard memory after
conversion because the same memory will continue to be used after the
conversion to back the same GPA. Discarding it would only cause
unnecessary reallocation of memory after each conversion. Instead, only
enable it for non-in-place conversions.

Signed-off-by: Michael Roth <michael.roth@amd.com>
---
 accel/kvm/kvm-all.c | 22 ++++++++++++----------
 1 file changed, 12 insertions(+), 10 deletions(-)

diff --git a/accel/kvm/kvm-all.c b/accel/kvm/kvm-all.c
index ff07050935..b28c9fbd33 100644
--- a/accel/kvm/kvm-all.c
+++ b/accel/kvm/kvm-all.c
@@ -3611,17 +3611,19 @@ static int kvm_post_convert_section(MemoryRegionSection *section, bool to_privat
         }
     }
 
-    if (to_private) {
-        if (rb->page_size != qemu_real_host_page_size()) {
-            /*
-             * shared memory is backed by hugetlb, which is supposed to be
-             * pre-allocated and doesn't need to be discarded
-             */
-            return 0;
+    if (!machine_require_guest_memfd_convert_in_place(current_machine)) {
+        if (to_private) {
+            if (rb->page_size != qemu_real_host_page_size()) {
+                /*
+                 * shared memory is backed by hugetlb, which is supposed to be
+                 * pre-allocated and doesn't need to be discarded
+                 */
+                return 0;
+            }
+            ret = ram_block_discard_shared_range(rb, offset, size);
+        } else {
+            ret = ram_block_discard_guest_memfd_range(rb, offset, size);
         }
-        ret = ram_block_discard_shared_range(rb, offset, size);
-    } else {
-        ret = ram_block_discard_guest_memfd_range(rb, offset, size);
     }
 
     return ret;
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH v2 19/19] hostmem: Automatically select set guest-memfd=on for in-place conversion
  2026-09-08 20:48 [PATCH v2 00/19] guest_memfd: support in-place memory conversion Michael Roth
                   ` (17 preceding siblings ...)
  2026-09-08 20:48 ` [PATCH v2 18/19] accel/kvm: Disable discard " Michael Roth
@ 2026-09-08 20:48 ` Michael Roth
  2026-09-09  6:30   ` Markus Armbruster
  18 siblings, 1 reply; 24+ messages in thread
From: Michael Roth @ 2026-09-08 20:48 UTC (permalink / raw)
  To: qemu-devel
  Cc: kvm, pbonzini, berrange, armbru, pankaj.gupta, isaku.yamahata,
	xiaoyao.li, chao.p.peng, david, ashish.kalra, ackerleytng,
	lpieralisi

When in-place conversion is enabled, both the shared memory and private
memory must come from the same guest_memfd instance. Thus, the
memory-backend-memfd options must in turn correspond to a guest_memfd
instance, e.g. guest-memfd=on must be specified. Since there is no
use-case for enabling in-place conversion without setting
guest-memfd=on, just set it automatically if in-place conversion is
enabled.

Signed-off-by: Michael Roth <michael.roth@amd.com>
---
 backends/hostmem-memfd.c | 34 ++++++++++++++++++++++++----------
 qapi/qom.json            |  5 +++--
 2 files changed, 27 insertions(+), 12 deletions(-)

diff --git a/backends/hostmem-memfd.c b/backends/hostmem-memfd.c
index de51cf738a..85ed973082 100644
--- a/backends/hostmem-memfd.c
+++ b/backends/hostmem-memfd.c
@@ -19,6 +19,9 @@
 #include "qom/object.h"
 #include "migration/cpr.h"
 #include "system/kvm.h"
+#include "qapi/qapi-visit-common.h"
+#include "hw/core/boards.h"
+#include "hw/core/qdev.h"
 
 OBJECT_DECLARE_SIMPLE_TYPE(HostMemoryBackendMemfd, MEMORY_BACKEND_MEMFD)
 
@@ -35,7 +38,7 @@ struct HostMemoryBackendMemfd {
      * private pages.  Instead, this flag marks the memory backend will
      * 100% use the guest-memfd pages in-place.
      */
-    bool guest_memfd;
+    OnOffAuto guest_memfd;
 };
 
 static bool
@@ -43,6 +46,7 @@ memfd_backend_memory_alloc(HostMemoryBackend *backend, Error **errp)
 {
     HostMemoryBackendMemfd *m = MEMORY_BACKEND_MEMFD(backend);
     g_autofree char *name = host_memory_backend_get_name(backend);
+    MachineState *machine = MACHINE(qdev_get_machine());
     int fd = cpr_find_fd(name, 0);
     uint32_t ram_flags;
 
@@ -55,7 +59,9 @@ memfd_backend_memory_alloc(HostMemoryBackend *backend, Error **errp)
         goto have_fd;
     }
 
-    if (m->guest_memfd) {
+    if (m->guest_memfd == ON_OFF_AUTO_ON ||
+        (m->guest_memfd == ON_OFF_AUTO_AUTO &&
+         machine_require_guest_memfd_convert_in_place(machine))) {
         /*
          * NOTE: guest-memfd ignores seal=on/off because it always
          * implicitly seals the FD by definition.
@@ -91,16 +97,24 @@ have_fd:
                                           backend->size, ram_flags, fd, 0, errp);
 }
 
-static bool
-memfd_backend_get_guest_memfd(Object *o, Error **errp)
+static void
+memfd_backend_get_guest_memfd(Object *o, Visitor *v,
+                              const char *value, void *opaque,
+                              Error **errp)
 {
-    return MEMORY_BACKEND_MEMFD(o)->guest_memfd;
+    HostMemoryBackendMemfd *m = MEMORY_BACKEND_MEMFD(o);
+
+    visit_type_OnOffAuto(v, value, &m->guest_memfd, errp);
 }
 
 static void
-memfd_backend_set_guest_memfd(Object *o, bool value, Error **errp)
+memfd_backend_set_guest_memfd(Object *o, Visitor *v,
+                              const char *value, void *opaque,
+                              Error **errp)
 {
-    MEMORY_BACKEND_MEMFD(o)->guest_memfd = value;
+    HostMemoryBackendMemfd *m = MEMORY_BACKEND_MEMFD(o);
+
+    visit_type_OnOffAuto(v, value, &m->guest_memfd, errp);
 }
 
 static bool
@@ -191,9 +205,9 @@ memfd_backend_class_init(ObjectClass *oc, const void *data)
                                               "Huge pages size (ex: 2M, 1G)");
     }
 
-    object_class_property_add_bool(oc, "guest-memfd",
-                                   memfd_backend_get_guest_memfd,
-                                   memfd_backend_set_guest_memfd);
+    object_class_property_add(oc, "guest-memfd", "OnOffAuto",
+                              memfd_backend_get_guest_memfd,
+                              memfd_backend_set_guest_memfd, NULL, NULL);
     object_class_property_set_description(oc, "guest-memfd",
                                           "Use guest memfd");
 
diff --git a/qapi/qom.json b/qapi/qom.json
index 20d6fefb04..32df7cb794 100644
--- a/qapi/qom.json
+++ b/qapi/qom.json
@@ -772,7 +772,8 @@
 #     resizing of the memory (default: true)
 #
 # @guest-memfd: if true, use guest-memfd to back the memory region.
-#     (default: false, since: 11.2)
+#     (default: true for Confidential VMs with in-place conversion,
+#     false otherwise, since: 11.2)
 #
 # Since: 2.12
 ##
@@ -781,7 +782,7 @@
   'data': { '*hugetlb': 'bool',
             '*hugetlbsize': 'size',
             '*seal': 'bool',
-            '*guest-memfd': 'bool' },
+            '*guest-memfd': 'OnOffAuto' },
   'if': 'CONFIG_LINUX' }
 
 ##
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* Re: [PATCH v2 18/19] accel/kvm: Disable discard for in-place conversion
  2026-09-08 20:48 ` [PATCH v2 18/19] accel/kvm: Disable discard " Michael Roth
@ 2026-09-08 22:16   ` Michael Roth
  0 siblings, 0 replies; 24+ messages in thread
From: Michael Roth @ 2026-09-08 22:16 UTC (permalink / raw)
  To: qemu-devel
  Cc: kvm, pbonzini, berrange, armbru, pankaj.gupta, isaku.yamahata,
	xiaoyao.li, chao.p.peng, david, ashish.kalra, ackerleytng,
	lpieralisi

On Tue, Sep 08, 2026 at 03:48:38PM -0500, Michael Roth wrote:
> When using in-place conversion, there is no need to discard memory after
> conversion because the same memory will continue to be used after the
> conversion to back the same GPA. Discarding it would only cause
> unnecessary reallocation of memory after each conversion. Instead, only
> enable it for non-in-place conversions.
> 
> Signed-off-by: Michael Roth <michael.roth@amd.com>
> ---
>  accel/kvm/kvm-all.c | 22 ++++++++++++----------
>  1 file changed, 12 insertions(+), 10 deletions(-)
> 
> diff --git a/accel/kvm/kvm-all.c b/accel/kvm/kvm-all.c
> index ff07050935..b28c9fbd33 100644
> --- a/accel/kvm/kvm-all.c
> +++ b/accel/kvm/kvm-all.c
> @@ -3611,17 +3611,19 @@ static int kvm_post_convert_section(MemoryRegionSection *section, bool to_privat
>          }
>      }
>  
> -    if (to_private) {
> -        if (rb->page_size != qemu_real_host_page_size()) {
> -            /*
> -             * shared memory is backed by hugetlb, which is supposed to be
> -             * pre-allocated and doesn't need to be discarded
> -             */
> -            return 0;
> +    if (!machine_require_guest_memfd_convert_in_place(current_machine)) {
> +        if (to_private) {
> +            if (rb->page_size != qemu_real_host_page_size()) {
> +                /*
> +                 * shared memory is backed by hugetlb, which is supposed to be
> +                 * pre-allocated and doesn't need to be discarded
> +                 */
> +                return 0;
> +            }
> +            ret = ram_block_discard_shared_range(rb, offset, size);
> +        } else {
> +            ret = ram_block_discard_guest_memfd_range(rb, offset, size);
>          }
> -        ret = ram_block_discard_shared_range(rb, offset, size);
> -    } else {
> -        ret = ram_block_discard_guest_memfd_range(rb, offset, size);
>      }
>  
>      return ret;

Sorry, with this patch it's now possible to return ret when it is still
uninitialized, so a "int ret = 0;" is needed above. I had the change
made locally but apparently didn't amend the commit before sending :(

I've gone ahead and pushed the fixed patch to the snp-inplace-v2 branch.

-Mike

> -- 
> 2.43.0
> 

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH v2 07/19] accel/kvm: Add CGS option to control in-place conversion support
  2026-09-08 20:48 ` [PATCH v2 07/19] accel/kvm: Add CGS option to control in-place conversion support Michael Roth
@ 2026-09-09  6:20   ` Markus Armbruster
  0 siblings, 0 replies; 24+ messages in thread
From: Markus Armbruster @ 2026-09-09  6:20 UTC (permalink / raw)
  To: Michael Roth
  Cc: qemu-devel, kvm, pbonzini, berrange, pankaj.gupta, isaku.yamahata,
	xiaoyao.li, chao.p.peng, david, ashish.kalra, ackerleytng,
	lpieralisi

Michael Roth <michael.roth@amd.com> writes:

> For confidential guests, guest_memfd is currently used only for private
> guest memory, and normal guest memory comes from the configured memory
> backend just as it does for a non-confidential guest. It is now possible
> to use the same physical memory to back a particular GPA regardless of
> whether it is in a shared or private state. This avoids the need to
> rely on discarding memory between shared/private conversions (to avoid
> doubled memory usage), and is intended to be the primary mode of using
> guest_memfd for confidential guests moving forward, and future features
> like hugepage support will likely require it.
>
> Add an option to enable this support. Since ConfidentialGuestSupport is
> already used to track some guest_memfd-related functionality (e.g.
> whether it is required for the configured machine), similarly introduce
> this option as a property of ConfidentialGuestSupport.
>
> Also add the KVM-specific checks to enable this support, but leave the
> option disabled until other required changes are implemented for
> CGS variants that intend to make use of KVM's in-place conversion
> support.
>
> While technically the convert-in-place option could be introduced as an
> SEV-specific option, it is a given that TDX will also be introducing
> in-place conversion support based on the same guest_memfd kernel
> infrastructure, so introduce it via a new
> ConfidentialGuestSupportProperties base class that other confidential VM
> implementations can utilized for common options.
>
> Signed-off-by: Michael Roth <michael.roth@amd.com>

[...]

> diff --git a/qapi/qom.json b/qapi/qom.json
> index 909add4299..20d6fefb04 100644
> --- a/qapi/qom.json
> +++ b/qapi/qom.json
> @@ -1005,6 +1005,21 @@
>    'if': 'CONFIG_IGVM',
>    'data': { 'file': 'str' } }
>  
> +##
> +# @ConfidentialGuestSupportProperties:
> +#
> +# Properties for ConfidentialGuestSupport base class.
> +#
> +# @convert-in-place: If true, the same physical pages are reused
> +#     when memory is converted between shared and private states.
> +#     If false (default), separate allocations are used depending
> +#     on whether the page is private or shared.

Any guidance on when to enable @convert-in-place?

> +#
> +# Since: 11.2
> +##
> +{ 'struct': 'ConfidentialGuestSupportProperties',
> +  'data': { '*convert-in-place': 'bool' } }
> +
>  ##
>  # @SevCommonProperties:
>  #
> @@ -1033,6 +1048,7 @@
>  # Since: 9.1
>  ##
>  { 'struct': 'SevCommonProperties',
> +  'base': 'ConfidentialGuestSupportProperties',
>    'data': { '*sev-device': 'str',
>              '*cbitpos': 'uint32',
>              'reduced-phys-bits': 'uint32',

Can you explain why you put @convert-in-place into a new base type
instead of right here?


^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH v2 19/19] hostmem: Automatically select set guest-memfd=on for in-place conversion
  2026-09-08 20:48 ` [PATCH v2 19/19] hostmem: Automatically select set guest-memfd=on " Michael Roth
@ 2026-09-09  6:30   ` Markus Armbruster
  0 siblings, 0 replies; 24+ messages in thread
From: Markus Armbruster @ 2026-09-09  6:30 UTC (permalink / raw)
  To: Michael Roth
  Cc: qemu-devel, kvm, pbonzini, berrange, pankaj.gupta, isaku.yamahata,
	xiaoyao.li, chao.p.peng, david, ashish.kalra, ackerleytng,
	lpieralisi

Michael Roth <michael.roth@amd.com> writes:

> When in-place conversion is enabled, both the shared memory and private
> memory must come from the same guest_memfd instance. Thus, the
> memory-backend-memfd options must in turn correspond to a guest_memfd
> instance, e.g. guest-memfd=on must be specified. Since there is no
> use-case for enabling in-place conversion without setting
> guest-memfd=on, just set it automatically if in-place conversion is
> enabled.
>
> Signed-off-by: Michael Roth <michael.roth@amd.com>

[...]

> diff --git a/qapi/qom.json b/qapi/qom.json
> index 20d6fefb04..32df7cb794 100644
> --- a/qapi/qom.json
> +++ b/qapi/qom.json
> @@ -772,7 +772,8 @@
>  #     resizing of the memory (default: true)
>  #
>  # @guest-memfd: if true, use guest-memfd to back the memory region.
> -#     (default: false, since: 11.2)
> +#     (default: true for Confidential VMs with in-place conversion,
> +#     false otherwise, since: 11.2)

What does "Confidential VMs with in-place conversion" mean exactly?
Which configuration settings need to be set how?

>  #
>  # Since: 2.12
>  ##
> @@ -781,7 +782,7 @@
>    'data': { '*hugetlb': 'bool',
>              '*hugetlbsize': 'size',
>              '*seal': 'bool',
> -            '*guest-memfd': 'bool' },
> +            '*guest-memfd': 'OnOffAuto' },

Valid values change from JSON true and false to JSON "on", "off", and
"auto".

If @guest-memfd was already in a release, this would be a compatibility
break, i.e. a hard "don't".  But it isn't, it's in the series this is
one based on.  So it's merely a "either avoid or justify in the commit
message, and avoiding feels easier to me."

The doc comment still uses true and false.

>    'if': 'CONFIG_LINUX' }
>  
>  ##


^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH v2 08/19] system/memory: Re-use memory-backend-guest-memfd inode for private memory
  2026-09-08 20:48 ` [PATCH v2 08/19] system/memory: Re-use memory-backend-guest-memfd inode for private memory Michael Roth
@ 2026-09-10  8:39   ` David Hildenbrand
  0 siblings, 0 replies; 24+ messages in thread
From: David Hildenbrand @ 2026-09-10  8:39 UTC (permalink / raw)
  To: Michael Roth, qemu-devel
  Cc: kvm, pbonzini, berrange, armbru, pankaj.gupta, isaku.yamahata,
	xiaoyao.li, chao.p.peng, ashish.kalra, ackerleytng, lpieralisi

On 9/8/26 22:48, Michael Roth wrote:
> When convert-in-place=true, the guest_memfd instance created by
> memory-backend-memfd (when guest-memfd=on option is specified) should
> also be used internally for private memory.
> 
> Do this by dup()'ing the guest_memfd FD provided by the backend so the
> separate cleanup paths for shared vs. private FDs can be managed in the
> same way they are currently for convert-in-place=false (where shared
> memory must come from something other than guest_memfd).
> 
> Introduce a new RAM_GUEST_MEMFD_SHARED flag that can be used to
> limit this dup()'ing to specific backend types like
> memory-backend-memfd.
> 
> Signed-off-by: Michael Roth <michael.roth@amd.com>
> ---
>  backends/hostmem-memfd.c |  1 +
>  include/system/memory.h  |  3 +++
>  system/physmem.c         | 48 +++++++++++++++++++++++++++++++++++++---
>  3 files changed, 49 insertions(+), 3 deletions(-)
> 
> diff --git a/backends/hostmem-memfd.c b/backends/hostmem-memfd.c
> index 6576331441..a9759e682b 100644
> --- a/backends/hostmem-memfd.c
> +++ b/backends/hostmem-memfd.c
> @@ -89,6 +89,7 @@ have_fd:
>      backend->aligned = true;
>      ram_flags = backend->share ? RAM_SHARED : RAM_PRIVATE;
>      ram_flags |= backend->reserve ? 0 : RAM_NORESERVE;
> +    ram_flags |= RAM_GUEST_MEMFD_SHARED;
>      ram_flags |= backend->guest_memfd_private ? RAM_GUEST_MEMFD_PRIVATE : 0;
>      return memory_region_init_ram_from_fd(&backend->mr, OBJECT(backend), name,
>                                            backend->size, ram_flags, fd, 0, errp);
> diff --git a/include/system/memory.h b/include/system/memory.h
> index 027ca81bd2..81616bfe39 100644
> --- a/include/system/memory.h
> +++ b/include/system/memory.h
> @@ -274,6 +274,9 @@ typedef struct IOMMUTLBEvent {
>   */
>  #define RAM_PRIVATE (1 << 13)
>  
> +/* RAM can be shared that has kvm guest memfd backend */
> +#define RAM_GUEST_MEMFD_SHARED   (1 << 14)

Trying to understand the semantics, should that be SHAREABLE ?

-- 
Cheers,

David


^ permalink raw reply	[flat|nested] 24+ messages in thread

end of thread, other threads:[~2026-09-10  8:39 UTC | newest]

Thread overview: 24+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-08 20:48 [PATCH v2 00/19] guest_memfd: support in-place memory conversion Michael Roth
2026-09-08 20:48 ` [PATCH v2 01/19] accel/kvm: Add helper for handling conversions of MMIO holes Michael Roth
2026-09-08 20:48 ` [PATCH v2 02/19] accel/kvm: Fix kvm_convert_memory() calls crossing memory regions Michael Roth
2026-09-08 20:48 ` [PATCH v2 03/19] accel/kvm: Fix handling of MMIO holes at start of conversion ranges Michael Roth
2026-09-08 20:48 ` [PATCH v2 04/19] accel/kvm: Fix handling of conversion ranges with multiple MMIO holes Michael Roth
2026-09-08 20:48 ` [PATCH v2 05/19] accel/kvm: Use dedicated helper for creating private-only gmem instances Michael Roth
2026-09-08 20:48 ` [PATCH v2 06/19] linux-headers: Update headers for v12 of in-place conversion kernel support Michael Roth
2026-09-08 20:48 ` [PATCH v2 07/19] accel/kvm: Add CGS option to control in-place conversion support Michael Roth
2026-09-09  6:20   ` Markus Armbruster
2026-09-08 20:48 ` [PATCH v2 08/19] system/memory: Re-use memory-backend-guest-memfd inode for private memory Michael Roth
2026-09-10  8:39   ` David Hildenbrand
2026-09-08 20:48 ` [PATCH v2 09/19] accel/kvm: Handle guest_memfd flags internally when creating instances Michael Roth
2026-09-08 20:48 ` [PATCH v2 10/19] system/memory: Default to guest_memfd for RAM for in-place conversion Michael Roth
2026-09-08 20:48 ` [PATCH v2 11/19] accel/kvm: Move post-conversion updates to a separate helper Michael Roth
2026-09-08 20:48 ` [PATCH v2 12/19] accel/kvm: Re-order attribute notifications for in-place conversion Michael Roth
2026-09-08 20:48 ` [PATCH v2 13/19] accel/kvm: Support shared/private conversions via guest_memfd ioctls Michael Roth
2026-09-08 20:48 ` [PATCH v2 14/19] accel/kvm: Don't default to private attributes for in-place conversion Michael Roth
2026-09-08 20:48 ` [PATCH v2 15/19] i386/sev: Update SNP_LAUNCH_UPDATE " Michael Roth
2026-09-08 20:48 ` [PATCH v2 16/19] i386/sev: Allow in-place conversion for SEV-SNP guests Michael Roth
2026-09-08 20:48 ` [PATCH v2 17/19] i386/sev: Update CPUID failure handling for in-place conversion Michael Roth
2026-09-08 20:48 ` [PATCH v2 18/19] accel/kvm: Disable discard " Michael Roth
2026-09-08 22:16   ` Michael Roth
2026-09-08 20:48 ` [PATCH v2 19/19] hostmem: Automatically select set guest-memfd=on " Michael Roth
2026-09-09  6:30   ` Markus Armbruster

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox