diff --git a/kvm-accel-mshv-Remove-remap-overlapping-mappings-code.patch b/kvm-accel-mshv-Remove-remap-overlapping-mappings-code.patch new file mode 100644 index 0000000..0ee7282 --- /dev/null +++ b/kvm-accel-mshv-Remove-remap-overlapping-mappings-code.patch @@ -0,0 +1,690 @@ +From f46de1630eddac4328b462070613235aca5e1149 Mon Sep 17 00:00:00 2001 +From: Magnus Kulke +Date: Tue, 13 Jan 2026 16:37:08 +0100 +Subject: [PATCH 4/5] accel/mshv: Remove remap overlapping mappings code + +RH-Author: Magnus Kulke +RH-MergeRequest: 491: accel/mshv: backport required patches for mshv guests +RH-Jira: RHEL-177969 +RH-Acked-by: Paolo Bonzini +RH-Acked-by: Jon Maloy +RH-Commit: [1/2] 78f5dc7530c84062f799cdc286f6cbb97b50b105 (mkulke/qemu-kvm) + +This change removes userland code that worked around a restriction +in the mshv driver in the 6.18 kernel: regions from userland +couldn't be mapped to multiple regions in the kernel. We maintained a +shadow mapping table in qemu and used a heuristic to swap in a requested +region in case of UNMAPPED_GPA exits. + +However, this heuristic wasn't reliable in all cases, since HyperV +behaviour is not 100% reliable across versions. HyperV itself doesn't +prohibit to map regions at multiple places into the guest, so the +restriction has been removed in the mshv driver. + +Hence we can remove the remapping code. Effectively this will mandate a +6.19 kernel, if the workload attempt to map e.g. BIOS to multiple +reagions. I still think it's the right call to remove this logic: + +- The workaround only seems to work reliably with a certain revision + of HyperV as a nested hypervisor. +- We expect Direct Virtualization (L1VH) to be the main platform for + the mshv accelerator, which also requires a 6.19 kernel + +This reverts commit efc4093358511a58846a409b965213aa1bb9f31a. + +Signed-off-by: Magnus Kulke +Acked-by: Wei Liu (Microsoft) +Tested-by: Mohamed Mediouni +Link: https://lore.kernel.org/r/20260113153708.448968-1-magnuskulke@linux.microsoft.com +Signed-off-by: Paolo Bonzini +(cherry picked from commit 626e5dc999353d2c4a89febd46301bfec6daa3a7) +--- + accel/mshv/mem.c | 406 +++--------------------------------- + accel/mshv/mshv-all.c | 2 - + accel/mshv/trace-events | 5 - + include/system/mshv_int.h | 22 +- + target/i386/mshv/mshv-cpu.c | 43 ---- + 5 files changed, 30 insertions(+), 448 deletions(-) + +diff --git a/accel/mshv/mem.c b/accel/mshv/mem.c +index 0e2164af3e..e55c38d4db 100644 +--- a/accel/mshv/mem.c ++++ b/accel/mshv/mem.c +@@ -11,9 +11,7 @@ + */ + + #include "qemu/osdep.h" +-#include "qemu/lockable.h" + #include "qemu/error-report.h" +-#include "qemu/rcu.h" + #include "linux/mshv.h" + #include "system/address-spaces.h" + #include "system/mshv.h" +@@ -22,137 +20,6 @@ + #include + #include "trace.h" + +-typedef struct SlotsRCUReclaim { +- struct rcu_head rcu; +- GList *old_head; +- MshvMemorySlot *removed_slot; +-} SlotsRCUReclaim; +- +-static void rcu_reclaim_slotlist(struct rcu_head *rcu) +-{ +- SlotsRCUReclaim *r = container_of(rcu, SlotsRCUReclaim, rcu); +- g_list_free(r->old_head); +- g_free(r->removed_slot); +- g_free(r); +-} +- +-static void publish_slots(GList *new_head, GList *old_head, +- MshvMemorySlot *removed_slot) +-{ +- MshvMemorySlotManager *manager = &mshv_state->msm; +- +- assert(manager); +- qatomic_store_release(&manager->slots, new_head); +- +- SlotsRCUReclaim *r = g_new(SlotsRCUReclaim, 1); +- r->old_head = old_head; +- r->removed_slot = removed_slot; +- +- call_rcu1(&r->rcu, rcu_reclaim_slotlist); +-} +- +-/* Needs to be called with mshv_state->msm.mutex held */ +-static int remove_slot(MshvMemorySlot *slot) +-{ +- GList *old_head, *new_head; +- MshvMemorySlotManager *manager = &mshv_state->msm; +- +- assert(manager); +- old_head = qatomic_load_acquire(&manager->slots); +- +- if (!g_list_find(old_head, slot)) { +- error_report("slot requested for removal not found"); +- return -1; +- } +- +- new_head = g_list_copy(old_head); +- new_head = g_list_remove(new_head, slot); +- manager->n_slots--; +- +- publish_slots(new_head, old_head, slot); +- +- return 0; +-} +- +-/* Needs to be called with mshv_state->msm.mutex held */ +-static MshvMemorySlot *append_slot(uint64_t gpa, uint64_t userspace_addr, +- uint64_t size, bool readonly) +-{ +- GList *old_head, *new_head; +- MshvMemorySlot *slot; +- MshvMemorySlotManager *manager = &mshv_state->msm; +- +- assert(manager); +- +- old_head = qatomic_load_acquire(&manager->slots); +- +- if (manager->n_slots >= MSHV_MAX_MEM_SLOTS) { +- error_report("no free memory slots available"); +- return NULL; +- } +- +- slot = g_new0(MshvMemorySlot, 1); +- slot->guest_phys_addr = gpa; +- slot->userspace_addr = userspace_addr; +- slot->memory_size = size; +- slot->readonly = readonly; +- +- new_head = g_list_copy(old_head); +- new_head = g_list_append(new_head, slot); +- manager->n_slots++; +- +- publish_slots(new_head, old_head, NULL); +- +- return slot; +-} +- +-static int slot_overlaps(const MshvMemorySlot *slot1, +- const MshvMemorySlot *slot2) +-{ +- uint64_t start_1 = slot1->userspace_addr, +- start_2 = slot2->userspace_addr; +- size_t len_1 = slot1->memory_size, +- len_2 = slot2->memory_size; +- +- if (slot1 == slot2) { +- return -1; +- } +- +- return ranges_overlap(start_1, len_1, start_2, len_2) ? 0 : -1; +-} +- +-static bool is_mapped(MshvMemorySlot *slot) +-{ +- /* Subsequent reads of mapped field see a fully-initialized slot */ +- return qatomic_load_acquire(&slot->mapped); +-} +- +-/* +- * Find slot that is: +- * - overlapping in userspace +- * - currently mapped in the guest +- * +- * Needs to be called with mshv_state->msm.mutex or RCU read lock held. +- */ +-static MshvMemorySlot *find_overlap_mem_slot(GList *head, MshvMemorySlot *slot) +-{ +- GList *found; +- MshvMemorySlot *overlap_slot; +- +- found = g_list_find_custom(head, slot, (GCompareFunc) slot_overlaps); +- +- if (!found) { +- return NULL; +- } +- +- overlap_slot = found->data; +- if (!overlap_slot || !is_mapped(overlap_slot)) { +- return NULL; +- } +- +- return overlap_slot; +-} +- + static int set_guest_memory(int vm_fd, + const struct mshv_user_mem_region *region) + { +@@ -160,169 +27,38 @@ static int set_guest_memory(int vm_fd, + + ret = ioctl(vm_fd, MSHV_SET_GUEST_MEMORY, region); + if (ret < 0) { +- error_report("failed to set guest memory: %s", strerror(errno)); +- return -1; ++ error_report("failed to set guest memory"); ++ return -errno; + } + + return 0; + } + +-static int map_or_unmap(int vm_fd, const MshvMemorySlot *slot, bool map) ++static int map_or_unmap(int vm_fd, const MshvMemoryRegion *mr, bool map) + { + struct mshv_user_mem_region region = {0}; + +- region.guest_pfn = slot->guest_phys_addr >> MSHV_PAGE_SHIFT; +- region.size = slot->memory_size; +- region.userspace_addr = slot->userspace_addr; ++ region.guest_pfn = mr->guest_phys_addr >> MSHV_PAGE_SHIFT; ++ region.size = mr->memory_size; ++ region.userspace_addr = mr->userspace_addr; + + if (!map) { + region.flags |= (1 << MSHV_SET_MEM_BIT_UNMAP); +- trace_mshv_unmap_memory(slot->userspace_addr, slot->guest_phys_addr, +- slot->memory_size); ++ trace_mshv_unmap_memory(mr->userspace_addr, mr->guest_phys_addr, ++ mr->memory_size); + return set_guest_memory(vm_fd, ®ion); + } + + region.flags = BIT(MSHV_SET_MEM_BIT_EXECUTABLE); +- if (!slot->readonly) { ++ if (!mr->readonly) { + region.flags |= BIT(MSHV_SET_MEM_BIT_WRITABLE); + } + +- trace_mshv_map_memory(slot->userspace_addr, slot->guest_phys_addr, +- slot->memory_size); ++ trace_mshv_map_memory(mr->userspace_addr, mr->guest_phys_addr, ++ mr->memory_size); + return set_guest_memory(vm_fd, ®ion); + } + +-static int slot_matches_region(const MshvMemorySlot *slot1, +- const MshvMemorySlot *slot2) +-{ +- return (slot1->guest_phys_addr == slot2->guest_phys_addr && +- slot1->userspace_addr == slot2->userspace_addr && +- slot1->memory_size == slot2->memory_size) ? 0 : -1; +-} +- +-/* Needs to be called with mshv_state->msm.mutex held */ +-static MshvMemorySlot *find_mem_slot_by_region(uint64_t gpa, uint64_t size, +- uint64_t userspace_addr) +-{ +- MshvMemorySlot ref_slot = { +- .guest_phys_addr = gpa, +- .userspace_addr = userspace_addr, +- .memory_size = size, +- }; +- GList *found; +- MshvMemorySlotManager *manager = &mshv_state->msm; +- +- assert(manager); +- found = g_list_find_custom(manager->slots, &ref_slot, +- (GCompareFunc) slot_matches_region); +- +- return found ? found->data : NULL; +-} +- +-static int slot_covers_gpa(const MshvMemorySlot *slot, uint64_t *gpa_p) +-{ +- uint64_t gpa_offset, gpa = *gpa_p; +- +- gpa_offset = gpa - slot->guest_phys_addr; +- return (slot->guest_phys_addr <= gpa && gpa_offset < slot->memory_size) +- ? 0 : -1; +-} +- +-/* Needs to be called with mshv_state->msm.mutex or RCU read lock held */ +-static MshvMemorySlot *find_mem_slot_by_gpa(GList *head, uint64_t gpa) +-{ +- GList *found; +- MshvMemorySlot *slot; +- +- trace_mshv_find_slot_by_gpa(gpa); +- +- found = g_list_find_custom(head, &gpa, (GCompareFunc) slot_covers_gpa); +- if (found) { +- slot = found->data; +- trace_mshv_found_slot(slot->userspace_addr, slot->guest_phys_addr, +- slot->memory_size); +- return slot; +- } +- +- return NULL; +-} +- +-/* Needs to be called with mshv_state->msm.mutex held */ +-static void set_mapped(MshvMemorySlot *slot, bool mapped) +-{ +- /* prior writes to mapped field becomes visible before readers see slot */ +- qatomic_store_release(&slot->mapped, mapped); +-} +- +-MshvRemapResult mshv_remap_overlap_region(int vm_fd, uint64_t gpa) +-{ +- MshvMemorySlot *gpa_slot, *overlap_slot; +- GList *head; +- int ret; +- MshvMemorySlotManager *manager = &mshv_state->msm; +- +- /* fast path, called often by unmapped_gpa vm exit */ +- WITH_RCU_READ_LOCK_GUARD() { +- assert(manager); +- head = qatomic_load_acquire(&manager->slots); +- /* return early if no slot is found */ +- gpa_slot = find_mem_slot_by_gpa(head, gpa); +- if (gpa_slot == NULL) { +- return MshvRemapNoMapping; +- } +- +- /* return early if no overlapping slot is found */ +- overlap_slot = find_overlap_mem_slot(head, gpa_slot); +- if (overlap_slot == NULL) { +- return MshvRemapNoOverlap; +- } +- } +- +- /* +- * We'll modify the mapping list, so we need to upgrade to mutex and +- * recheck. +- */ +- assert(manager); +- QEMU_LOCK_GUARD(&manager->mutex); +- +- /* return early if no slot is found */ +- gpa_slot = find_mem_slot_by_gpa(manager->slots, gpa); +- if (gpa_slot == NULL) { +- return MshvRemapNoMapping; +- } +- +- /* return early if no overlapping slot is found */ +- overlap_slot = find_overlap_mem_slot(manager->slots, gpa_slot); +- if (overlap_slot == NULL) { +- return MshvRemapNoOverlap; +- } +- +- /* unmap overlapping slot */ +- ret = map_or_unmap(vm_fd, overlap_slot, false); +- if (ret < 0) { +- error_report("failed to unmap overlap region"); +- abort(); +- } +- set_mapped(overlap_slot, false); +- warn_report("mapped out userspace_addr=0x%016lx gpa=0x%010lx size=0x%lx", +- overlap_slot->userspace_addr, +- overlap_slot->guest_phys_addr, +- overlap_slot->memory_size); +- +- /* map region for gpa */ +- ret = map_or_unmap(vm_fd, gpa_slot, true); +- if (ret < 0) { +- error_report("failed to map new region"); +- abort(); +- } +- set_mapped(gpa_slot, true); +- warn_report("mapped in userspace_addr=0x%016lx gpa=0x%010lx size=0x%lx", +- gpa_slot->userspace_addr, gpa_slot->guest_phys_addr, +- gpa_slot->memory_size); +- +- return MshvRemapOk; +-} +- + static int handle_unmapped_mmio_region_read(uint64_t gpa, uint64_t size, + uint8_t *data) + { +@@ -388,97 +124,20 @@ int mshv_guest_mem_write(uint64_t gpa, const uint8_t *data, uintptr_t size, + return -1; + } + +-static int tracked_unmap(int vm_fd, uint64_t gpa, uint64_t size, +- uint64_t userspace_addr) ++static int set_memory(const MshvMemoryRegion *mshv_mr, bool add) + { +- int ret; +- MshvMemorySlot *slot; +- MshvMemorySlotManager *manager = &mshv_state->msm; +- +- assert(manager); +- +- QEMU_LOCK_GUARD(&manager->mutex); +- +- slot = find_mem_slot_by_region(gpa, size, userspace_addr); +- if (!slot) { +- trace_mshv_skip_unset_mem(userspace_addr, gpa, size); +- /* no work to do */ +- return 0; +- } +- +- if (!is_mapped(slot)) { +- /* remove slot, no need to unmap */ +- return remove_slot(slot); +- } +- +- ret = map_or_unmap(vm_fd, slot, false); +- if (ret < 0) { +- error_report("failed to unmap memory region"); +- return ret; +- } +- return remove_slot(slot); +-} +- +-static int tracked_map(int vm_fd, uint64_t gpa, uint64_t size, bool readonly, +- uint64_t userspace_addr) +-{ +- MshvMemorySlot *slot, *overlap_slot; +- int ret; +- MshvMemorySlotManager *manager = &mshv_state->msm; +- +- assert(manager); +- +- QEMU_LOCK_GUARD(&manager->mutex); ++ int ret = 0; + +- slot = find_mem_slot_by_region(gpa, size, userspace_addr); +- if (slot) { +- error_report("memory region already mapped at gpa=0x%lx, " +- "userspace_addr=0x%lx, size=0x%lx", +- slot->guest_phys_addr, slot->userspace_addr, +- slot->memory_size); ++ if (!mshv_mr) { ++ error_report("Invalid mshv_mr"); + return -1; + } + +- slot = append_slot(gpa, userspace_addr, size, readonly); +- +- overlap_slot = find_overlap_mem_slot(manager->slots, slot); +- if (overlap_slot) { +- trace_mshv_remap_attempt(slot->userspace_addr, +- slot->guest_phys_addr, +- slot->memory_size); +- warn_report("attempt to map region [0x%lx-0x%lx], while " +- "[0x%lx-0x%lx] is already mapped in the guest", +- userspace_addr, userspace_addr + size - 1, +- overlap_slot->userspace_addr, +- overlap_slot->userspace_addr + +- overlap_slot->memory_size - 1); +- +- /* do not register mem slot in hv, but record for later swap-in */ +- set_mapped(slot, false); +- +- return 0; +- } +- +- ret = map_or_unmap(vm_fd, slot, true); +- if (ret < 0) { +- error_report("failed to map memory region"); +- return -1; +- } +- set_mapped(slot, true); +- +- return 0; +-} +- +-static int set_memory(uint64_t gpa, uint64_t size, bool readonly, +- uint64_t userspace_addr, bool add) +-{ +- int vm_fd = mshv_state->vm; +- +- if (add) { +- return tracked_map(vm_fd, gpa, size, readonly, userspace_addr); +- } +- +- return tracked_unmap(vm_fd, gpa, size, userspace_addr); ++ trace_mshv_set_memory(add, mshv_mr->guest_phys_addr, ++ mshv_mr->memory_size, ++ mshv_mr->userspace_addr, mshv_mr->readonly, ++ ret); ++ return map_or_unmap(mshv_state->vm, mshv_mr, add); + } + + /* +@@ -514,9 +173,7 @@ void mshv_set_phys_mem(MshvMemoryListener *mml, MemoryRegionSection *section, + bool writable = !area->readonly && !area->rom_device; + hwaddr start_addr, mr_offset, size; + void *ram; +- +- size = align_section(section, &start_addr); +- trace_mshv_set_phys_mem(add, section->mr->name, start_addr); ++ MshvMemoryRegion mshv_mr = {0}; + + size = align_section(section, &start_addr); + trace_mshv_set_phys_mem(add, section->mr->name, start_addr); +@@ -543,21 +200,14 @@ void mshv_set_phys_mem(MshvMemoryListener *mml, MemoryRegionSection *section, + + ram = memory_region_get_ram_ptr(area) + mr_offset; + +- ret = set_memory(start_addr, size, !writable, (uint64_t)ram, add); ++ mshv_mr.guest_phys_addr = start_addr; ++ mshv_mr.memory_size = size; ++ mshv_mr.readonly = !writable; ++ mshv_mr.userspace_addr = (uint64_t)ram; ++ ++ ret = set_memory(&mshv_mr, add); + if (ret < 0) { +- error_report("failed to set memory region"); ++ error_report("Failed to set memory region"); + abort(); + } + } +- +-void mshv_init_memory_slot_manager(MshvState *mshv_state) +-{ +- MshvMemorySlotManager *manager; +- +- assert(mshv_state); +- manager = &mshv_state->msm; +- +- manager->n_slots = 0; +- manager->slots = NULL; +- qemu_mutex_init(&manager->mutex); +-} +diff --git a/accel/mshv/mshv-all.c b/accel/mshv/mshv-all.c +index 80428d130d..88b66f2992 100644 +--- a/accel/mshv/mshv-all.c ++++ b/accel/mshv/mshv-all.c +@@ -437,8 +437,6 @@ static int mshv_init(AccelState *as, MachineState *ms) + + mshv_init_msicontrol(); + +- mshv_init_memory_slot_manager(s); +- + ret = create_vm(mshv_fd, &vm_fd); + if (ret < 0) { + close(mshv_fd); +diff --git a/accel/mshv/trace-events b/accel/mshv/trace-events +index 36f0d59b38..a4dffeb24a 100644 +--- a/accel/mshv/trace-events ++++ b/accel/mshv/trace-events +@@ -26,8 +26,3 @@ mshv_map_memory(uint64_t userspace_addr, uint64_t gpa, uint64_t size) "\tu_a=0x% + mshv_unmap_memory(uint64_t userspace_addr, uint64_t gpa, uint64_t size) "\tu_a=0x%" PRIx64 " gpa=0x%010" PRIx64 " size=0x%08" PRIx64 + mshv_set_phys_mem(bool add, const char *name, uint64_t gpa) "\tadd=%d name=%s gpa=0x%010" PRIx64 + mshv_handle_mmio(uint64_t gva, uint64_t gpa, uint64_t size, uint8_t access_type) "\tgva=0x%" PRIx64 " gpa=0x%010" PRIx64 " size=0x%" PRIx64 " access_type=%d" +- +-mshv_found_slot(uint64_t userspace_addr, uint64_t gpa, uint64_t size) "\tu_a=0x%" PRIx64 " gpa=0x%010" PRIx64 " size=0x%08" PRIx64 +-mshv_skip_unset_mem(uint64_t userspace_addr, uint64_t gpa, uint64_t size) "\tu_a=0x%" PRIx64 " gpa=0x%010" PRIx64 " size=0x%08" PRIx64 +-mshv_remap_attempt(uint64_t userspace_addr, uint64_t gpa, uint64_t size) "\tu_a=0x%" PRIx64 " gpa=0x%010" PRIx64 " size=0x%08" PRIx64 +-mshv_find_slot_by_gpa(uint64_t gpa) "\tgpa=0x%010" PRIx64 +diff --git a/include/system/mshv_int.h b/include/system/mshv_int.h +index 490563c1ab..ad4d001c3c 100644 +--- a/include/system/mshv_int.h ++++ b/include/system/mshv_int.h +@@ -16,8 +16,6 @@ + + #define MSHV_MSR_ENTRIES_COUNT 64 + +-#define MSHV_MAX_MEM_SLOTS 32 +- + typedef struct hyperv_message hv_message; + + typedef struct MshvHvCallArgs { +@@ -42,12 +40,6 @@ typedef struct MshvAddressSpace { + AddressSpace *as; + } MshvAddressSpace; + +-typedef struct MshvMemorySlotManager { +- size_t n_slots; +- GList *slots; +- QemuMutex mutex; +-} MshvMemorySlotManager; +- + struct MshvState { + AccelState parent_obj; + int vm; +@@ -56,7 +48,6 @@ struct MshvState { + int nr_as; + MshvAddressSpace *as; + int fd; +- MshvMemorySlotManager msm; + }; + + typedef struct MshvMsiControl { +@@ -87,12 +78,6 @@ typedef enum MshvVmExit { + MshvVmExitSpecial = 2, + } MshvVmExit; + +-typedef enum MshvRemapResult { +- MshvRemapOk = 0, +- MshvRemapNoMapping = 1, +- MshvRemapNoOverlap = 2, +-} MshvRemapResult; +- + void mshv_init_mmio_emu(void); + int mshv_create_vcpu(int vm_fd, uint8_t vp_index, int *cpu_fd); + void mshv_remove_vcpu(int vm_fd, int cpu_fd); +@@ -116,22 +101,19 @@ int mshv_hvcall(int fd, const struct mshv_root_hvcall *args); + #endif + + /* memory */ +-typedef struct MshvMemorySlot { ++typedef struct MshvMemoryRegion { + uint64_t guest_phys_addr; + uint64_t memory_size; + uint64_t userspace_addr; + bool readonly; +- bool mapped; +-} MshvMemorySlot; ++} MshvMemoryRegion; + +-MshvRemapResult mshv_remap_overlap_region(int vm_fd, uint64_t gpa); + int mshv_guest_mem_read(uint64_t gpa, uint8_t *data, uintptr_t size, + bool is_secure_mode, bool instruction_fetch); + int mshv_guest_mem_write(uint64_t gpa, const uint8_t *data, uintptr_t size, + bool is_secure_mode); + void mshv_set_phys_mem(MshvMemoryListener *mml, MemoryRegionSection *section, + bool add); +-void mshv_init_memory_slot_manager(MshvState *mshv_state); + + /* msr */ + typedef struct MshvMsrEntry { +diff --git a/target/i386/mshv/mshv-cpu.c b/target/i386/mshv/mshv-cpu.c +index 1c3db02188..c577e284b4 100644 +--- a/target/i386/mshv/mshv-cpu.c ++++ b/target/i386/mshv/mshv-cpu.c +@@ -1168,43 +1168,6 @@ static int handle_mmio(CPUState *cpu, const struct hyperv_message *msg, + return 0; + } + +-static int handle_unmapped_mem(int vm_fd, CPUState *cpu, +- const struct hyperv_message *msg, +- MshvVmExit *exit_reason) +-{ +- struct hv_x64_memory_intercept_message info = { 0 }; +- uint64_t gpa; +- int ret; +- enum MshvRemapResult remap_result; +- +- ret = set_memory_info(msg, &info); +- if (ret < 0) { +- error_report("failed to convert message to memory info"); +- return -1; +- } +- +- gpa = info.guest_physical_address; +- +- /* attempt to remap the region, in case of overlapping userspace mappings */ +- remap_result = mshv_remap_overlap_region(vm_fd, gpa); +- *exit_reason = MshvVmExitIgnore; +- +- switch (remap_result) { +- case MshvRemapNoMapping: +- /* if we didn't find a mapping, it is probably mmio */ +- return handle_mmio(cpu, msg, exit_reason); +- case MshvRemapOk: +- break; +- case MshvRemapNoOverlap: +- /* This should not happen, but we are forgiving it */ +- warn_report("found no overlap for unmapped region"); +- *exit_reason = MshvVmExitSpecial; +- break; +- } +- +- return 0; +-} +- + static int set_ioport_info(const struct hyperv_message *msg, + hv_x64_io_port_intercept_message *info) + { +@@ -1546,12 +1509,6 @@ int mshv_run_vcpu(int vm_fd, CPUState *cpu, hv_message *msg, MshvVmExit *exit) + case HVMSG_UNRECOVERABLE_EXCEPTION: + return MshvVmExitShutdown; + case HVMSG_UNMAPPED_GPA: +- ret = handle_unmapped_mem(vm_fd, cpu, msg, &exit_reason); +- if (ret < 0) { +- error_report("failed to handle unmapped memory"); +- return -1; +- } +- return exit_reason; + case HVMSG_GPA_INTERCEPT: + ret = handle_mmio(cpu, msg, &exit_reason); + if (ret < 0) { +-- +2.52.0 + diff --git a/kvm-accel-mshv-implement-cpu_thread_is_idle-hook.patch b/kvm-accel-mshv-implement-cpu_thread_is_idle-hook.patch new file mode 100644 index 0000000..c16977f --- /dev/null +++ b/kvm-accel-mshv-implement-cpu_thread_is_idle-hook.patch @@ -0,0 +1,58 @@ +From 4356c4191e1b7ba491a2dd6d8c6167f7eb9a3de1 Mon Sep 17 00:00:00 2001 +From: Magnus Kulke +Date: Tue, 21 Apr 2026 05:21:54 +0000 +Subject: [PATCH 5/5] accel/mshv: implement cpu_thread_is_idle() hook + +RH-Author: Magnus Kulke +RH-MergeRequest: 491: accel/mshv: backport required patches for mshv guests +RH-Jira: RHEL-177969 +RH-Acked-by: Paolo Bonzini +RH-Acked-by: Jon Maloy +RH-Commit: [2/2] 4880ec9edc01fda07186687f9d19159f3afd0689 (mkulke/qemu-kvm) + +In MSHV the hypervisor APIC is always used, so we to implement this hook +to make sure the AP's vcpu thread is not blocked waiting for an INIT SIPI +by the BSP. Without this change soft reboots with -smp cpus>=2 will +hang. + +Signed-off-by: Magnus Kulke +Reviewed-by: Mohamed Mediouni +Link: https://lore.kernel.org/r/20260421-mshv_accel_arm64_supp-v3-9-469f544778ba@linux.microsoft.com +[Make comment not x86 specific. - Paolo] +Signed-off-by: Paolo Bonzini +(cherry picked from commit dbfb680772d5184544f7c0a8bba96bec229c96e6) +--- + accel/mshv/mshv-all.c | 12 ++++++++++++ + 1 file changed, 12 insertions(+) + +diff --git a/accel/mshv/mshv-all.c b/accel/mshv/mshv-all.c +index 88b66f2992..d7df89dbd2 100644 +--- a/accel/mshv/mshv-all.c ++++ b/accel/mshv/mshv-all.c +@@ -699,11 +699,23 @@ static const TypeInfo mshv_accel_type = { + .instance_size = sizeof(MshvState), + }; + ++/* ++ * MSHV manages secondary processors in the hypervisor. SIPI for x86 and ++ * PSCI for Arm are handled internally. Halted vCPUs must still enter ++ * mshv_cpu_exec() so that MSHV_RUN_VP is called and the hypervisor will ++ * wake APs. ++ */ ++static bool mshv_vcpu_thread_is_idle(CPUState *cpu) ++{ ++ return false; ++} ++ + static void mshv_accel_ops_class_init(ObjectClass *oc, const void *data) + { + AccelOpsClass *ops = ACCEL_OPS_CLASS(oc); + + ops->create_vcpu_thread = mshv_start_vcpu_thread; ++ ops->cpu_thread_is_idle = mshv_vcpu_thread_is_idle; + ops->synchronize_post_init = mshv_cpu_synchronize_post_init; + ops->synchronize_post_reset = mshv_cpu_synchronize_post_reset; + ops->synchronize_state = mshv_cpu_synchronize; +-- +2.52.0 + diff --git a/kvm-scsi-adjust-error_prepend-formatting.patch b/kvm-scsi-adjust-error_prepend-formatting.patch new file mode 100644 index 0000000..64caf72 --- /dev/null +++ b/kvm-scsi-adjust-error_prepend-formatting.patch @@ -0,0 +1,49 @@ +From bc38981e098c0dd2430c125ceb08188813e7ba12 Mon Sep 17 00:00:00 2001 +From: Stefan Hajnoczi +Date: Wed, 1 Apr 2026 13:19:25 -0400 +Subject: [PATCH 1/5] scsi: adjust error_prepend() formatting + +RH-Author: Stefan Hajnoczi +RH-MergeRequest: 493: scsi: PR live migration PREEMPT fixes [9.9] +RH-Jira: RHEL-158243 +RH-Acked-by: Paolo Bonzini +RH-Acked-by: Jon Maloy +RH-Commit: [1/3] 8ba6738b878a8926f8aaab487b863a42a44a736e (stefanha/centos-stream-qemu-kvm) + +The error strings will be concatenated so add a separator to make the +combined error message easy to read. + +Signed-off-by: Stefan Hajnoczi +Link: https://lore.kernel.org/r/20260401171927.396672-2-stefanha@redhat.com +Signed-off-by: Paolo Bonzini +(cherry picked from commit 15c57ac351ccb4af625cd7dc582831f472c1a68f) +Signed-off-by: Stefan Hajnoczi +--- + hw/scsi/scsi-generic.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/hw/scsi/scsi-generic.c b/hw/scsi/scsi-generic.c +index b8b3f399f0..5825a47311 100644 +--- a/hw/scsi/scsi-generic.c ++++ b/hw/scsi/scsi-generic.c +@@ -438,7 +438,7 @@ static bool scsi_generic_pr_register(SCSIDevice *s, uint64_t key, Error **errp) + ret = scsi_SG_IO(s->conf.blk, SG_DXFER_TO_DEV, cmd, sizeof(cmd), + buf, sizeof(buf), s->io_timeout, errp); + if (ret < 0) { +- error_prepend(errp, "PERSISTENT RESERVE OUT with REGISTER"); ++ error_prepend(errp, "PERSISTENT RESERVE OUT with REGISTER: "); + return false; + } + return true; +@@ -462,7 +462,7 @@ static bool scsi_generic_pr_preempt(SCSIDevice *s, uint64_t key, + ret = scsi_SG_IO(s->conf.blk, SG_DXFER_TO_DEV, cmd, sizeof(cmd), + buf, sizeof(buf), s->io_timeout, errp); + if (ret < 0) { +- error_prepend(errp, "PERSISTENT RESERVE OUT with PREEMPT"); ++ error_prepend(errp, "PERSISTENT RESERVE OUT with PREEMPT: "); + return false; + } + return true; +-- +2.52.0 + diff --git a/kvm-scsi-always-send-valid-PREEMPT-TYPE-field.patch b/kvm-scsi-always-send-valid-PREEMPT-TYPE-field.patch new file mode 100644 index 0000000..7f44243 --- /dev/null +++ b/kvm-scsi-always-send-valid-PREEMPT-TYPE-field.patch @@ -0,0 +1,73 @@ +From 45ca2a7d7a348dd1a48a5f218ca02331bc3d29a7 Mon Sep 17 00:00:00 2001 +From: Stefan Hajnoczi +Date: Wed, 1 Apr 2026 13:19:26 -0400 +Subject: [PATCH 2/5] scsi: always send valid PREEMPT TYPE field + +RH-Author: Stefan Hajnoczi +RH-MergeRequest: 493: scsi: PR live migration PREEMPT fixes [9.9] +RH-Jira: RHEL-158243 +RH-Acked-by: Paolo Bonzini +RH-Acked-by: Jon Maloy +RH-Commit: [2/3] c1b10616023abe2ec648c1b0c4d095a87f0ff258 (stefanha/centos-stream-qemu-kvm) + +The SPC-6 specification says that the PREEMPT service action ignores the +TYPE field when there is no reservation. However, the LIO Linux iSCSI +target rejects commands with a zero TYPE field. The field never ends up +being used in this case, so replace it with a "valid" value to work +around the issue. + +Reported-by: Qing Wang +Buglink: https://redhat.atlassian.net/browse/RHEL-155807 +Signed-off-by: Stefan Hajnoczi +Link: https://lore.kernel.org/r/20260401171927.396672-3-stefanha@redhat.com +Signed-off-by: Paolo Bonzini +(cherry picked from commit 15a202656cd553911272f9666aa067c706fc3dfe) +Signed-off-by: Stefan Hajnoczi +--- + hw/scsi/scsi-generic.c | 10 ++++++++++ + include/scsi/constants.h | 10 ++++++++++ + 2 files changed, 20 insertions(+) + +diff --git a/hw/scsi/scsi-generic.c b/hw/scsi/scsi-generic.c +index 5825a47311..8a57473623 100644 +--- a/hw/scsi/scsi-generic.c ++++ b/hw/scsi/scsi-generic.c +@@ -452,6 +452,16 @@ static bool scsi_generic_pr_preempt(SCSIDevice *s, uint64_t key, + uint64_t key_be = cpu_to_be64(key); + int ret; + ++ /* ++ * The LIO iSCSI target in Linux up to at least version 7.0 rejects PREEMPT ++ * commands with a zero TYPE field although the SPC-6 specification says ++ * the field should be ignored when there is no persistent reservation. ++ * Work around this by choosing an arbitrary valid PR type value. ++ */ ++ if (resv_type == 0) { ++ resv_type = PR_TYPE_WRITE_EXCLUSIVE; ++ } ++ + cmd[0] = PERSISTENT_RESERVE_OUT; + cmd[1] = PRO_PREEMPT; + cmd[2] = resv_type & 0xf; +diff --git a/include/scsi/constants.h b/include/scsi/constants.h +index cb97bdb636..717e470a5d 100644 +--- a/include/scsi/constants.h ++++ b/include/scsi/constants.h +@@ -340,4 +340,14 @@ + #define PRO_REGISTER_AND_MOVE 0x07 + #define PRO_REPLACE_LOST_RESERVATION 0x08 + ++/* ++ * Persistent reservation types ++ */ ++#define PR_TYPE_WRITE_EXCLUSIVE 0x1 ++#define PR_TYPE_EXCLUSIVE_ACCESS 0x3 ++#define PR_TYPE_WRITE_EXCLUSIVE_REG_ONLY 0x5 ++#define PR_TYPE_EXCLUSIVE_ACCESS_REG_ONLY 0x6 ++#define PR_TYPE_WRITE_EXCLUSIVE_ALL_REGS 0x7 ++#define PR_TYPE_EXCLUSIVE_ACCESS_ALL_REGS 0x8 ++ + #endif +-- +2.52.0 + diff --git a/kvm-scsi-register-again-after-PREEMPT-without-reservatio.patch b/kvm-scsi-register-again-after-PREEMPT-without-reservatio.patch new file mode 100644 index 0000000..9843549 --- /dev/null +++ b/kvm-scsi-register-again-after-PREEMPT-without-reservatio.patch @@ -0,0 +1,54 @@ +From 520f3f124b79709b8da67f1a8adb77da60fc4753 Mon Sep 17 00:00:00 2001 +From: Stefan Hajnoczi +Date: Wed, 1 Apr 2026 13:19:27 -0400 +Subject: [PATCH 3/5] scsi: register again after PREEMPT without reservation + +RH-Author: Stefan Hajnoczi +RH-MergeRequest: 493: scsi: PR live migration PREEMPT fixes [9.9] +RH-Jira: RHEL-158243 +RH-Acked-by: Paolo Bonzini +RH-Acked-by: Jon Maloy +RH-Commit: [3/3] 4ba66cf81945645015cb5df9f0b7f4d163f84808 (stefanha/centos-stream-qemu-kvm) + +The SCSI specification says PREEMPT without a reservation removes all +registrations with the given key. Try to register again after PREEMPT +since our key will have been removed. + +In practice some SCSI targets keep the calling I_T nexus' registration +instead of removing it. Therefore we need to handle both the +spec-compliant and the non-compliant behavior. + +Signed-off-by: Stefan Hajnoczi +Reviewed-by: Paolo Bonzini +Link: https://lore.kernel.org/r/20260401171927.396672-4-stefanha@redhat.com +Signed-off-by: Paolo Bonzini +(cherry picked from commit b9a3b329d09dea5a4692de483419693ef0e23052) +Signed-off-by: Stefan Hajnoczi +--- + hw/scsi/scsi-generic.c | 10 ++++++++++ + 1 file changed, 10 insertions(+) + +diff --git a/hw/scsi/scsi-generic.c b/hw/scsi/scsi-generic.c +index 8a57473623..9c5927b543 100644 +--- a/hw/scsi/scsi-generic.c ++++ b/hw/scsi/scsi-generic.c +@@ -512,6 +512,16 @@ bool scsi_generic_pr_state_preempt(SCSIDevice *s, Error **errp) + if (!scsi_generic_pr_preempt(s, key, resv_type, errp)) { + return false; + } ++ ++ /* ++ * Some SCSI targets, like the Linux LIO target, remove our ++ * registration when preempting without a reservation (resv_type is 0). ++ * Try to register again but ignore the error since a RESERVATION ++ * CONFLICT is expected if our registration remained in place. ++ */ ++ if (resv_type == 0) { ++ scsi_generic_pr_register(s, key, NULL); ++ } + } + return true; + } +-- +2.52.0 + diff --git a/qemu-kvm.spec b/qemu-kvm.spec index e801d2d..5cf8624 100644 --- a/qemu-kvm.spec +++ b/qemu-kvm.spec @@ -149,7 +149,7 @@ Obsoletes: %{name}-block-ssh <= %{epoch}:%{version} \ Summary: QEMU is a machine emulator and virtualizer Name: qemu-kvm Version: 10.1.0 -Release: 20%{?rcrel}%{?dist}%{?cc_suffix} +Release: 21%{?rcrel}%{?dist}%{?cc_suffix} # Epoch because we pushed a qemu-1.0 package. AIUI this can't ever be dropped # Epoch 15 used for RHEL 8 # Epoch 17 used for RHEL 9 (due to release versioning offset in RHEL 8.5) @@ -385,6 +385,16 @@ Patch119: kvm-target-arm-cpu64-Define-cpreg-migration-tolerance-fo.patch Patch120: kvm-target-arm-helper-Define-cpreg-migration-tolerance-f.patch # For RHEL-174859 - [rhel9] Backport qemu cross-kernel migration mitigation series Patch121: kvm-Revert-target-arm-Reinstate-bogus-AArch32-DBGDTRTX-r.patch +# For RHEL-158243 - live migration failed the VM just register key only [rhel-9.9] +Patch122: kvm-scsi-adjust-error_prepend-formatting.patch +# For RHEL-158243 - live migration failed the VM just register key only [rhel-9.9] +Patch123: kvm-scsi-always-send-valid-PREEMPT-TYPE-field.patch +# For RHEL-158243 - live migration failed the VM just register key only [rhel-9.9] +Patch124: kvm-scsi-register-again-after-PREEMPT-without-reservatio.patch +# For RHEL-177969 - MSHV backport onto QEMU 10.1.0 is not able to launch MSHV guests +Patch125: kvm-accel-mshv-Remove-remap-overlapping-mappings-code.patch +# For RHEL-177969 - MSHV backport onto QEMU 10.1.0 is not able to launch MSHV guests +Patch126: kvm-accel-mshv-implement-cpu_thread_is_idle-hook.patch # For RHEL-11424 - [IBM 9.6 FEAT] KVM: Full boot order support - qemu part @@ -2099,6 +2109,17 @@ useradd -r -u 107 -g qemu -G kvm -d / -s /sbin/nologin \ %endif %changelog +* Mon Jun 08 2026 Jon Maloy - 10.1.0-21 +- kvm-scsi-adjust-error_prepend-formatting.patch [RHEL-158243] +- kvm-scsi-always-send-valid-PREEMPT-TYPE-field.patch [RHEL-158243] +- kvm-scsi-register-again-after-PREEMPT-without-reservatio.patch [RHEL-158243] +- kvm-accel-mshv-Remove-remap-overlapping-mappings-code.patch [RHEL-177969] +- kvm-accel-mshv-implement-cpu_thread_is_idle-hook.patch [RHEL-177969] +- Resolves: RHEL-158243 + (live migration failed the VM just register key only [rhel-9.9]) +- Resolves: RHEL-177969 + (MSHV backport onto QEMU 10.1.0 is not able to launch MSHV guests) + * Tue May 19 2026 Jon Maloy - 10.1.0-20 - kvm-vmstate-Introduce-VMSTATE_VARRAY_INT32_ALLOC.patch [RHEL-174859] - kvm-target-arm-Move-compare_u64-to-helper.c.patch [RHEL-174859]