From: Ashish Kalra 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 Co-developed-by: Michael Roth Signed-off-by: Michael Roth --- 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(§ion, 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(§ion, to_private, &skip); + if (ret || skip) { + memory_region_unref(section.mr); + break; + } + + ret = kvm_convert_section(§ion, to_private); + memory_region_unref(section.mr); + + if (ret) { + break; + } + + size -= section_end - start; + start = section_end; + } + return ret; } -- 2.43.0