* Mon Jun 08 2026 Miroslav Rezanina <mrezanin@redhat.com> - 10.1.0-20

- kvm-accel-mshv-Remove-remap-overlapping-mappings-code.patch [RHEL-178767]
- kvm-accel-mshv-implement-cpu_thread_is_idle-hook.patch [RHEL-178767]
- kvm-scsi-adjust-error_prepend-formatting.patch [RHEL-155807]
- kvm-scsi-always-send-valid-PREEMPT-TYPE-field.patch [RHEL-155807]
- kvm-scsi-register-again-after-PREEMPT-without-reservatio.patch [RHEL-155807]
- kvm-hw-tpm-tpm_tis_sysbus-defer-resource-allocation-to-r.patch [RHEL-178846]
- kvm-vfio-container-Restrict-dma_map_file-to-shared-RAM-o.patch [RHEL-180750]
- kvm-scsi-change-buf_size-to-unsigned-int-in-scsi_SG_IO.patch [RHEL-153123]
- kvm-scsi-handle-reservation-changes-across-migration.patch [RHEL-153123]
- Resolves: RHEL-178767
  (MSHV backport onto QEMU 10.1.0 is not able to launch MSHV guests)
- Resolves: RHEL-155807
  (live migration failed the VM just register key only [rhel-10.3])
- Resolves: RHEL-178846
  ([aarch64] qemu-kvm crashes on --device tpm-tis-device,?)
- Resolves: RHEL-180750
  (Backport in QEMU : vfio/container: Restrict dma_map_file() to shared RAM or RAM devices)
- Resolves: RHEL-153123
  (live migration failed or get failed WSFC test result during WSFC testing [rhel-10.3])
This commit is contained in:
Miroslav Rezanina 2026-06-08 09:38:09 +02:00
parent 085da1e81c
commit e2263aca2f
11 changed files with 1472 additions and 1 deletions

View File

@ -0,0 +1,690 @@
From 0d484fe45aee9a7481d543ac2a0453a30872ffbb Mon Sep 17 00:00:00 2001
From: Magnus Kulke <magnuskulke@linux.microsoft.com>
Date: Tue, 13 Jan 2026 16:37:08 +0100
Subject: [PATCH 1/9] accel/mshv: Remove remap overlapping mappings code
RH-Author: Magnus Kulke <None>
RH-MergeRequest: 490: accel/mshv: backport required patches for mshv guests
RH-Jira: RHEL-178767
RH-Acked-by: Paolo Bonzini <pbonzini@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [1/2] 959df35b4cd77cf85f7a8a67d438ad71135525e9 (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 <magnuskulke@linux.microsoft.com>
Acked-by: Wei Liu (Microsoft) <wei.liu@kernel.org>
Tested-by: Mohamed Mediouni <mohamed@unpredictable.fr>
Link: https://lore.kernel.org/r/20260113153708.448968-1-magnuskulke@linux.microsoft.com
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
(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 <sys/ioctl.h>
#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, &region);
}
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, &region);
}
-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

View File

@ -0,0 +1,58 @@
From 2a298912b20e7d47ce5ac68c8524ab286d6ecf6f Mon Sep 17 00:00:00 2001
From: Magnus Kulke <magnuskulke@linux.microsoft.com>
Date: Tue, 21 Apr 2026 05:21:54 +0000
Subject: [PATCH 2/9] accel/mshv: implement cpu_thread_is_idle() hook
RH-Author: Magnus Kulke <None>
RH-MergeRequest: 490: accel/mshv: backport required patches for mshv guests
RH-Jira: RHEL-178767
RH-Acked-by: Paolo Bonzini <pbonzini@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [2/2] 3be517f22c0af3dccc6bc33ab2fee541a6802b6a (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 <magnuskulke@linux.microsoft.com>
Reviewed-by: Mohamed Mediouni <mohamed@unpredictable.fr>
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 <pbonzini@redhat.com>
(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

View File

@ -0,0 +1,109 @@
From 6526d78b916e57e08393da88124df3adfb40604d Mon Sep 17 00:00:00 2001
From: Mohammadfaiz Bawa <mbawa@redhat.com>
Date: Wed, 3 Jun 2026 14:05:52 +0530
Subject: [PATCH 6/9] hw/tpm/tpm_tis_sysbus: defer resource allocation to
realize
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
RH-Author: Mohammadfaiz Bawa <None>
RH-MergeRequest: 496: hw/tpm/tpm_tis_sysbus: defer resource allocation to realize
RH-Jira: RHEL-178846
RH-Acked-by: Gavin Shan <gshan@redhat.com>
RH-Acked-by: Eric Auger <eric.auger@redhat.com>
RH-Commit: [1/1] 934e0f23755aa7d675486deb8e57a94ae9f77251 (faizb/qemu-kvm)
JIRA: https://redhat.atlassian.net/browse/RHEL-178846
Calling memory_region_init_ram_device_ptr() and
memory_region_init_io() from tpm_tis_sysbus_initfn() crashes
when the device is introspected without being realized, because
the memory subsystem has not been initialized at that point.
So running:
$ qemu-system-aarch64 -device tpm-tis-device,help
triggers qdev_device_help() which creates the device object
to list its properties, calling instance_init, but never
realizefn. The memory region calls in instance_init then hit
uninitialized subsystems:
With CONFIG_DEBUG_TCG:
Assertion 'target_page.decided' failed. (physmem.c:2524)
Without CONFIG_DEBUG_TCG:
Assertion 'mutex->initialized' failed. (qemu-thread-posix.c:107)
Since realizefn is only called when the device is actually
used in a running VM, moving resource allocation there avoids
the crash without breaking introspection.
This also fixes a memory leak that is reported by the address
sanitizer during 'make check', because we currently allocate
ppi.buf during instance_init and never free it. "Allocate in
realize and never free" is less bad, because we don't currently
support "unrealize and destroy a sysbus device".
Fixes: 46cd2c1050f ("hw/tpm: add PPI support to tpm-tis-device for ARM64 virt")
Signed-off-by: Mohammadfaiz Bawa <mbawa@redhat.com>
Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
Message-ID: <20260528093123.55403-1-mbawa@redhat.com>
Message-ID: <CAFEAcA8fEYODmPhbh1W=oPGvju-P=qWvN_dyWrPqAr-E9FK7UA@mail.gmail.com>
[PMD: Amend Peter comment from previous mail in description]
Signed-off-by: Philippe Mathieu-Daudé <philmd@linaro.org>
(cherry picked from commit dc6444b9c595dc40d7ead0837b930b83ec966e1f)
Signed-off-by: Mohammadfaiz Bawa <mbawa@redhat.com>
---
hw/tpm/tpm_tis_sysbus.c | 18 ++++++++----------
1 file changed, 8 insertions(+), 10 deletions(-)
diff --git a/hw/tpm/tpm_tis_sysbus.c b/hw/tpm/tpm_tis_sysbus.c
index 3bea7cd16c..65ef59edd8 100644
--- a/hw/tpm/tpm_tis_sysbus.c
+++ b/hw/tpm/tpm_tis_sysbus.c
@@ -100,19 +100,9 @@ static void tpm_tis_sysbus_initfn(Object *obj)
{
TPMStateSysBus *sbdev = TPM_TIS_SYSBUS(obj);
TPMState *s = &sbdev->state;
- size_t host_page_size = qemu_real_host_page_size();
-
- memory_region_init_io(&s->mmio, obj, &tpm_tis_memory_ops,
- s, "tpm-tis-mmio",
- TPM_TIS_NUM_LOCALITIES << TPM_TIS_LOCALITY_SHIFT);
sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio);
sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq);
-
- s->ppi.buf = qemu_memalign(host_page_size,
- ROUND_UP(TPM_PPI_ADDR_SIZE, host_page_size));
- memory_region_init_ram_device_ptr(&s->ppi.ram, obj, "tpm-ppi",
- TPM_PPI_ADDR_SIZE, s->ppi.buf);
sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->ppi.ram);
}
@@ -120,6 +110,7 @@ static void tpm_tis_sysbus_realizefn(DeviceState *dev, Error **errp)
{
TPMStateSysBus *sbdev = TPM_TIS_SYSBUS(dev);
TPMState *s = &sbdev->state;
+ const size_t host_page_size = qemu_real_host_page_size();
if (!tpm_find()) {
error_setg(errp, "at most one TPM device is permitted");
@@ -131,6 +122,13 @@ static void tpm_tis_sysbus_realizefn(DeviceState *dev, Error **errp)
return;
}
+ s->ppi.buf = qemu_memalign(host_page_size,
+ ROUND_UP(TPM_PPI_ADDR_SIZE, host_page_size));
+ memory_region_init_io(&s->mmio, OBJECT(dev), &tpm_tis_memory_ops,
+ s, "tpm-tis-mmio",
+ TPM_TIS_NUM_LOCALITIES << TPM_TIS_LOCALITY_SHIFT);
+ memory_region_init_ram_device_ptr(&s->ppi.ram, OBJECT(dev), "tpm-ppi",
+ TPM_PPI_ADDR_SIZE, s->ppi.buf);
vmstate_register_ram(&s->ppi.ram, dev);
}
--
2.52.0

View File

@ -0,0 +1,49 @@
From 77d65b4a5dc0172a29dd810363d4f702242b929a Mon Sep 17 00:00:00 2001
From: Stefan Hajnoczi <stefanha@redhat.com>
Date: Wed, 1 Apr 2026 13:19:25 -0400
Subject: [PATCH 3/9] scsi: adjust error_prepend() formatting
RH-Author: Stefan Hajnoczi <stefanha@redhat.com>
RH-MergeRequest: 492: scsi: PR live migration PREEMPT fixes
RH-Jira: RHEL-155807
RH-Acked-by: Paolo Bonzini <pbonzini@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [1/3] 8d4442c1d4ec94db0cbea00f3cd32e0f50ef13dc (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 <stefanha@redhat.com>
Link: https://lore.kernel.org/r/20260401171927.396672-2-stefanha@redhat.com
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
(cherry picked from commit 15c57ac351ccb4af625cd7dc582831f472c1a68f)
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
---
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

View File

@ -0,0 +1,73 @@
From 406d4eeb15d45107490b444482df8fcb9945da02 Mon Sep 17 00:00:00 2001
From: Stefan Hajnoczi <stefanha@redhat.com>
Date: Wed, 1 Apr 2026 13:19:26 -0400
Subject: [PATCH 4/9] scsi: always send valid PREEMPT TYPE field
RH-Author: Stefan Hajnoczi <stefanha@redhat.com>
RH-MergeRequest: 492: scsi: PR live migration PREEMPT fixes
RH-Jira: RHEL-155807
RH-Acked-by: Paolo Bonzini <pbonzini@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [2/3] 6fc4c51732256ea5e06eb05e7666cca216f8d45f (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 <qinwang@redhat.com>
Buglink: https://redhat.atlassian.net/browse/RHEL-155807
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
Link: https://lore.kernel.org/r/20260401171927.396672-3-stefanha@redhat.com
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
(cherry picked from commit 15a202656cd553911272f9666aa067c706fc3dfe)
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
---
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

View File

@ -0,0 +1,55 @@
From 364a6ea9f63c7bfe25ced3cea0d10e3e5177677f Mon Sep 17 00:00:00 2001
From: Stefan Hajnoczi <stefanha@redhat.com>
Date: Wed, 15 Apr 2026 19:29:05 -0400
Subject: [PATCH 8/9] scsi: change buf_size to unsigned int in scsi_SG_IO()
RH-Author: Stefan Hajnoczi <stefanha@redhat.com>
RH-MergeRequest: 498: scsi: handle reservation changes across migration
RH-Jira: RHEL-153123
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [4/5] fe0a33daf96237ea2baf03afaccf9dd310e207c7 (stefanha/centos-stream-qemu-kvm)
SG_IO supports an unsigned int dxfer_len value. Existing callers use
less than 256 bytes, so scsi_SG_IO()'s uint8_t buf_size type was
sufficient. The next patch will use a larger value, so update the type.
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
Link: https://lore.kernel.org/r/20260415232906.212349-2-stefanha@redhat.com
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
(cherry picked from commit f4193228dd20d3d5b8eaaf65f46e2426181794f3)
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
---
hw/scsi/scsi-generic.c | 2 +-
include/hw/scsi/scsi.h | 3 ++-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/hw/scsi/scsi-generic.c b/hw/scsi/scsi-generic.c
index 9c5927b543..c2f61b320f 100644
--- a/hw/scsi/scsi-generic.c
+++ b/hw/scsi/scsi-generic.c
@@ -793,7 +793,7 @@ static int read_naa_id(const uint8_t *p, uint64_t *p_wwn)
}
int scsi_SG_IO(BlockBackend *blk, int direction, uint8_t *cmd,
- uint8_t cmd_size, uint8_t *buf, uint8_t buf_size,
+ uint8_t cmd_size, uint8_t *buf, unsigned int buf_size,
uint32_t timeout, Error **errp)
{
sg_io_hdr_t io_header;
diff --git a/include/hw/scsi/scsi.h b/include/hw/scsi/scsi.h
index f61c63c5ea..640a11522b 100644
--- a/include/hw/scsi/scsi.h
+++ b/include/hw/scsi/scsi.h
@@ -247,7 +247,8 @@ void scsi_device_unit_attention_reported(SCSIDevice *dev);
void scsi_generic_read_device_inquiry(SCSIDevice *dev);
int scsi_device_get_sense(SCSIDevice *dev, uint8_t *buf, int len, bool fixed);
int scsi_SG_IO(BlockBackend *blk, int direction, uint8_t *cmd, uint8_t cmd_size,
- uint8_t *buf, uint8_t buf_size, uint32_t timeout, Error **errp);
+ uint8_t *buf, unsigned int buf_size, uint32_t timeout,
+ Error **errp);
SCSIDevice *scsi_device_find(SCSIBus *bus, int channel, int target, int lun);
SCSIDevice *scsi_device_get(SCSIBus *bus, int channel, int target, int lun);
--
2.52.0

View File

@ -0,0 +1,246 @@
From 6724a87dab9516a52086427ab53453171bada849 Mon Sep 17 00:00:00 2001
From: Stefan Hajnoczi <stefanha@redhat.com>
Date: Wed, 15 Apr 2026 19:29:06 -0400
Subject: [PATCH 9/9] scsi: handle reservation changes across migration
RH-Author: Stefan Hajnoczi <stefanha@redhat.com>
RH-MergeRequest: 498: scsi: handle reservation changes across migration
RH-Jira: RHEL-153123
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [5/5] 4ba82297c25e0992ec3f695f6bd3337aa44bb50b (stefanha/centos-stream-qemu-kvm)
Other nodes in the cluster can preempt or clear SCSI Persistent
Reservations at any time. When this happens across live migration, the
reservation state transferred with the guest might be outdated.
Attempt to handle such cases gracefully by checking the current
reservation or registered keys to detect stale state before restoring.
If the actual state of the disk has changed, do not modify it and accept
that as the most up-to-date state.
Do this using READ RESERVATION when the guest holds a reservation or
READ KEYS when the guest has registered a key but does not hold a
reservation.
There is still a race condition between checking and restoring state,
but it seems unavoidable and is no worse than before.
Buglink: https://redhat.atlassian.net/browse/RHEL-153123
Fixes: ab57b51f1375b6a6f098a74c6f79207a9630948d ("scsi: save/load SCSI reservation state")
Reported-by: Qing Wang
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
Link: https://lore.kernel.org/r/20260415232906.212349-3-stefanha@redhat.com
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
(cherry picked from commit 87c7b07fe4aa43dc7257b1e711faa835fac1cccb)
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
---
hw/scsi/scsi-generic.c | 173 +++++++++++++++++++++++++++++++++++------
1 file changed, 149 insertions(+), 24 deletions(-)
diff --git a/hw/scsi/scsi-generic.c b/hw/scsi/scsi-generic.c
index c2f61b320f..452908ba95 100644
--- a/hw/scsi/scsi-generic.c
+++ b/hw/scsi/scsi-generic.c
@@ -478,13 +478,84 @@ static bool scsi_generic_pr_preempt(SCSIDevice *s, uint64_t key,
return true;
}
+/*
+ * Returns true if the given key is registered or false otherwise (including
+ * errors).
+ */
+static bool scsi_generic_pr_key_registered(SCSIDevice *s, uint64_t key,
+ Error **errp)
+{
+ const size_t key_list_offset = 8; /* in READ KEYS parameter data */
+ uint64_t key_be = cpu_to_be64(key);
+ uint8_t cmd[10] = {};
+ size_t buf_len;
+ g_autofree uint8_t *buf = NULL;
+ uint32_t additional_length = 16 * 8; /* initial key list size */
+
+ /*
+ * Loop to resize parameter data buffer when there are many keys. It would
+ * be simpler to hardcode the maximum buffer size (it's only 64 KB), but
+ * SG_IO can fail with EINVAL if the host kernel blkdev queue limits are
+ * too low.
+ */
+ do {
+ uint16_t allocation_length_be;
+ int ret;
+
+ buf_len = key_list_offset + additional_length;
+ buf = g_realloc(buf, buf_len);
+ memset(buf, 0, buf_len);
+
+ cmd[0] = PERSISTENT_RESERVE_IN;
+ cmd[1] = PRI_READ_KEYS;
+ allocation_length_be = cpu_to_be16(buf_len);
+ memcpy(&cmd[7], &allocation_length_be, sizeof(allocation_length_be));
+
+ ret = scsi_SG_IO(s->conf.blk, SG_DXFER_FROM_DEV, cmd, sizeof(cmd),
+ buf, buf_len, s->io_timeout, errp);
+ if (ret < 0) {
+ error_prepend(errp, "PERSISTENT RESERVE IN with READ KEYS: ");
+ return false;
+ }
+
+ memcpy(&additional_length, &buf[4], sizeof(additional_length));
+ be32_to_cpus(&additional_length);
+
+ /*
+ * The parameter data's ADDITIONAL LENGTH must not overflow the CDB's
+ * 16-bit ALLOCATION LENGTH field since the next loop iteration will
+ * compute ALLOCATION LENGTH based on ADDITIONAL LENGTH.
+ */
+ if (additional_length > UINT16_MAX - key_list_offset) {
+ error_setg(errp, "got invalid ADDITIONAL LENGTH %" PRIu32
+ " from READ KEYS", additional_length);
+ return false;
+ }
+
+ for (size_t i = key_list_offset; i < buf_len; i += sizeof(key_be)) {
+ if (i - key_list_offset >= additional_length) {
+ break; /* end of parameter list */
+ }
+
+ if (memcmp(&key_be, &buf[i], sizeof(key_be)) == 0) {
+ return true; /* key found */
+ }
+ }
+ } while (additional_length > buf_len - key_list_offset);
+
+ return false; /* key not found */
+}
+
/* Register keys and preempt reservations after live migration */
bool scsi_generic_pr_state_preempt(SCSIDevice *s, Error **errp)
{
SCSIPRState *pr_state = &s->pr_state;
+ Error *local_err = NULL;
+ bool check_stale_key = true;
uint64_t key;
uint8_t resv_type;
+ /* Get the migrated PR state */
WITH_QEMU_LOCK_GUARD(&pr_state->mutex) {
key = pr_state->key;
resv_type = pr_state->resv_type;
@@ -492,36 +563,90 @@ bool scsi_generic_pr_state_preempt(SCSIDevice *s, Error **errp)
trace_scsi_generic_pr_state_preempt(key, resv_type);
- if (key) {
- if (!scsi_generic_pr_register(s, key, errp)) {
+ /* Handle stale PR state (e.g. another node preempted) */
+ if (resv_type) {
+ uint64_t dev_key;
+ uint8_t dev_resv_type;
+
+ if (scsi_generic_read_reservation(s, &dev_key, &dev_resv_type,
+ errp) < 0) {
return false;
}
- /*
- * Two cases:
- *
- * 1. There is no reservation (resv_type is 0) and the other I_T nexus
- * will be unregistered. This is important so the source host does
- * not leak registered keys across live migration.
- *
- * 2. There is a reservation (resv_type is not 0) and the other I_T
- * nexus will be unregistered and its reservation is atomically
- * taken over by us. This is the scenario where a reservation is
- * migrated along with the guest.
- */
- if (!scsi_generic_pr_preempt(s, key, resv_type, errp)) {
- return false;
+ if (dev_resv_type != resv_type) {
+ /* vmstate had a stale reservation type */
+ g_autofree char *name = qdev_get_human_name(&s->qdev);
+ warn_report("Expected SCSI reservation type 0x%x on device '%s', "
+ "got 0x%x, using new type",
+ resv_type, name, dev_resv_type);
+ resv_type = dev_resv_type;
}
- /*
- * 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);
+ if (dev_key == key) {
+ /* The reservation exists, no need to check for a stale key */
+ check_stale_key = false;
+ } else {
+ g_autofree char *name = qdev_get_human_name(&s->qdev);
+ warn_report("Expected SCSI reservation with key 0x%" PRIx64
+ " on device '%s', got 0x%" PRIx64 ", ignoring "
+ "reservation",
+ key, name, dev_key);
+ resv_type = 0; /* vmstate had a stale reservation */
+ }
+ }
+
+ if (key != 0 && check_stale_key &&
+ !scsi_generic_pr_key_registered(s, key, &local_err)) {
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return false;
}
+
+ g_autofree char *name = qdev_get_human_name(&s->qdev);
+ warn_report("SCSI reservation key 0x%" PRIx64 " on device '%s' not "
+ "registered after migration, ignoring",
+ key, name);
+ key = 0; /* vmstate had a stale key */
+ }
+
+ /* Stale PR state may have been updated */
+ WITH_QEMU_LOCK_GUARD(&pr_state->mutex) {
+ pr_state->key = key;
+ pr_state->resv_type = resv_type;
+ }
+
+ if (key == 0) {
+ return true; /* no PR state, do nothing */
+ }
+
+ if (!scsi_generic_pr_register(s, key, errp)) {
+ return false;
+ }
+
+ /*
+ * Two cases:
+ *
+ * 1. There is no reservation (resv_type is 0) and the other I_T nexus
+ * will be unregistered. This is important so the source host does
+ * not leak registered keys across live migration.
+ *
+ * 2. There is a reservation (resv_type is not 0) and the other I_T
+ * nexus will be unregistered and its reservation is atomically
+ * taken over by us. This is the scenario where a reservation is
+ * migrated along with the guest.
+ */
+ 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

View File

@ -0,0 +1,54 @@
From 5ccd7244084cc6820a0a00df01c25484181b7fca Mon Sep 17 00:00:00 2001
From: Stefan Hajnoczi <stefanha@redhat.com>
Date: Wed, 1 Apr 2026 13:19:27 -0400
Subject: [PATCH 5/9] scsi: register again after PREEMPT without reservation
RH-Author: Stefan Hajnoczi <stefanha@redhat.com>
RH-MergeRequest: 492: scsi: PR live migration PREEMPT fixes
RH-Jira: RHEL-155807
RH-Acked-by: Paolo Bonzini <pbonzini@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [3/3] 21242c1e293a3611c75a5cdbc99e55c6ab86cc20 (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 <stefanha@redhat.com>
Reviewed-by: Paolo Bonzini <pbonzini@redhat.com>
Link: https://lore.kernel.org/r/20260401171927.396672-4-stefanha@redhat.com
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
(cherry picked from commit b9a3b329d09dea5a4692de483419693ef0e23052)
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
---
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

View File

@ -0,0 +1,97 @@
From 773e27e2cfefb0958df726b726a6de91bda8771d Mon Sep 17 00:00:00 2001
From: Chenyi Qiang <chenyi.qiang@intel.com>
Date: Wed, 27 May 2026 18:11:08 +0800
Subject: [PATCH 7/9] vfio/container: Restrict dma_map_file() to shared RAM or
RAM devices
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
RH-Author: Rodolfo Vick <None>
RH-MergeRequest: 497: vfio/container: Restrict dma_map_file() to shared RAM or RAM devices
RH-Jira: RHEL-180750
RH-Acked-by: Eric Auger <eric.auger@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Acked-by: Cédric Le Goater <clg@redhat.com>
RH-Commit: [1/1] 423a647a11151907ab26eb341e73298a2880908e (rovick1/qemu-kvm)
vfio_container_dma_map() uses dma_map_file() whenever a RAMBlock has an
fd and the VFIO IOMMU backend supports file-based DMA mapping. That is
not correct for private file-backed guest RAM.
dma_map_file() resolves PFNs from the backing file, but private guest
RAM mappings (MAP_PRIVATE) can run on different PFNs than the file
because they are subject to copy-on-write (COW) anomalies. As a result,
using dma_map_file() on a privately mapped RAMBlock can program DMA
against pages that do not back QEMU's actual guest memory.
Fix this by using dma_map_file() only for shared mapped RAMBlocks
MAP_SHARED) or RAM device regions.
Fixes: fb32965b6dd8 ("vfio/iommufd: use IOMMU_IOAS_MAP_FILE")
Reported-by: Farrah Chen <farrah.chen@intel.com>
Closes: https://bugzilla.kernel.org/show_bug.cgi?id=220776
Reviewed-by: Zhenzhong Duan <zhenzhong.duan@intel.com>
Suggested-by: Cédric Le Goater <clg@redhat.com>
Signed-off-by: Chenyi Qiang <chenyi.qiang@intel.com>
Link: https://lore.kernel.org/qemu-devel/20260527101109.71781-1-chenyi.qiang@intel.com
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
hw/vfio/container.c | 34 +++++++++++++++++++++++++++++++---
1 file changed, 31 insertions(+), 3 deletions(-)
diff --git a/hw/vfio/container.c b/hw/vfio/container.c
index 1b8569d36a..f534797a98 100644
--- a/hw/vfio/container.c
+++ b/hw/vfio/container.c
@@ -73,15 +73,43 @@ void vfio_address_space_insert(VFIOAddressSpace *space,
bcontainer->space = space;
}
+static bool vfio_container_can_dma_map_file(VFIOContainer *bcontainer,
+ MemoryRegion *mr, int *fd)
+{
+ VFIOIOMMUClass *vioc = VFIO_IOMMU_GET_CLASS(bcontainer);
+ RAMBlock *rb = mr->ram_block;
+
+ if (!vioc->dma_map_file || !rb) {
+ return false;
+ }
+
+ *fd = qemu_ram_get_fd(rb);
+ if (*fd < 0) {
+ return false;
+ }
+
+ /*
+ * We can use IOMMU DMA mapping (IOMMU_IOAS_MAP_FILE) for :
+ *
+ * 1) Guest RAM blocks explicitly configured as shared (MAP_SHARED)
+ * 2) RAM device sub-regions (MMIO BARs)
+ *
+ * Private RAM mappings (MAP_PRIVATE) are strictly excluded. Because
+ * they are subject to copy-on-write (COW) anomalies, their underlying
+ * PFNs can permanently diverge from the backing file
+ */
+ return qemu_ram_is_shared(rb) || memory_region_is_ram_device(mr);
+}
+
int vfio_container_dma_map(VFIOContainer *bcontainer,
hwaddr iova, uint64_t size,
void *vaddr, bool readonly, MemoryRegion *mr)
{
VFIOIOMMUClass *vioc = VFIO_IOMMU_GET_CLASS(bcontainer);
- RAMBlock *rb = mr->ram_block;
- int mfd = rb ? qemu_ram_get_fd(rb) : -1;
+ int mfd;
- if (mfd >= 0 && vioc->dma_map_file) {
+ if (vfio_container_can_dma_map_file(bcontainer, mr, &mfd)) {
+ RAMBlock *rb = mr->ram_block;
unsigned long start = vaddr - qemu_ram_get_host_addr(rb);
unsigned long offset = qemu_ram_get_fd_offset(rb);
--
2.52.0

View File

@ -143,7 +143,7 @@ Obsoletes: %{name}-block-ssh <= %{epoch}:%{version} \
Summary: QEMU is a machine emulator and virtualizer
Name: qemu-kvm
Version: 10.1.0
Release: 19%{?rcrel}%{?dist}%{?cc_suffix}
Release: 20%{?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)
@ -676,6 +676,24 @@ Patch259: kvm-linux-headers-Update-to-Linux-v6.19-rc1.patch
Patch260: kvm-hw-vfio-Add-helper-to-retrieve-device-feature.patch
# For RHEL-138494 - NVIDIA:Grace-Hopper:Backport vfio: Add DMABUF support for PCI BAR regions - RHEL 10.3
Patch261: kvm-hw-vfio-region-Create-dmabuf-for-PCI-BAR-per-region.patch
# For RHEL-178767 - MSHV backport onto QEMU 10.1.0 is not able to launch MSHV guests
Patch262: kvm-accel-mshv-Remove-remap-overlapping-mappings-code.patch
# For RHEL-178767 - MSHV backport onto QEMU 10.1.0 is not able to launch MSHV guests
Patch263: kvm-accel-mshv-implement-cpu_thread_is_idle-hook.patch
# For RHEL-155807 - live migration failed the VM just register key only [rhel-10.3]
Patch264: kvm-scsi-adjust-error_prepend-formatting.patch
# For RHEL-155807 - live migration failed the VM just register key only [rhel-10.3]
Patch265: kvm-scsi-always-send-valid-PREEMPT-TYPE-field.patch
# For RHEL-155807 - live migration failed the VM just register key only [rhel-10.3]
Patch266: kvm-scsi-register-again-after-PREEMPT-without-reservatio.patch
# For RHEL-178846 - [aarch64] qemu-kvm crashes on --device tpm-tis-device,?
Patch267: kvm-hw-tpm-tpm_tis_sysbus-defer-resource-allocation-to-r.patch
# For RHEL-180750 - Backport in QEMU : vfio/container: Restrict dma_map_file() to shared RAM or RAM devices
Patch268: kvm-vfio-container-Restrict-dma_map_file-to-shared-RAM-o.patch
# For RHEL-153123 - live migration failed or get failed WSFC test result during WSFC testing [rhel-10.3]
Patch269: kvm-scsi-change-buf_size-to-unsigned-int-in-scsi_SG_IO.patch
# For RHEL-153123 - live migration failed or get failed WSFC test result during WSFC testing [rhel-10.3]
Patch270: kvm-scsi-handle-reservation-changes-across-migration.patch
%if %{have_clang}
BuildRequires: clang
@ -1755,6 +1773,27 @@ useradd -r -u 107 -g qemu -G kvm -d / -s /sbin/nologin \
%endif
%changelog
* Mon Jun 08 2026 Miroslav Rezanina <mrezanin@redhat.com> - 10.1.0-20
- kvm-accel-mshv-Remove-remap-overlapping-mappings-code.patch [RHEL-178767]
- kvm-accel-mshv-implement-cpu_thread_is_idle-hook.patch [RHEL-178767]
- kvm-scsi-adjust-error_prepend-formatting.patch [RHEL-155807]
- kvm-scsi-always-send-valid-PREEMPT-TYPE-field.patch [RHEL-155807]
- kvm-scsi-register-again-after-PREEMPT-without-reservatio.patch [RHEL-155807]
- kvm-hw-tpm-tpm_tis_sysbus-defer-resource-allocation-to-r.patch [RHEL-178846]
- kvm-vfio-container-Restrict-dma_map_file-to-shared-RAM-o.patch [RHEL-180750]
- kvm-scsi-change-buf_size-to-unsigned-int-in-scsi_SG_IO.patch [RHEL-153123]
- kvm-scsi-handle-reservation-changes-across-migration.patch [RHEL-153123]
- Resolves: RHEL-178767
(MSHV backport onto QEMU 10.1.0 is not able to launch MSHV guests)
- Resolves: RHEL-155807
(live migration failed the VM just register key only [rhel-10.3])
- Resolves: RHEL-178846
([aarch64] qemu-kvm crashes on --device tpm-tis-device,?)
- Resolves: RHEL-180750
(Backport in QEMU : vfio/container: Restrict dma_map_file() to shared RAM or RAM devices)
- Resolves: RHEL-153123
(live migration failed or get failed WSFC test result during WSFC testing [rhel-10.3])
* Tue May 26 2026 Miroslav Rezanina <mrezanin@redhat.com> - 10.1.0-19
- kvm-vmstate-Introduce-VMSTATE_VARRAY_INT32_ALLOC.patch [RHEL-174858]
- kvm-target-arm-Move-compare_u64-to-helper.c.patch [RHEL-174858]

View File

@ -3,6 +3,7 @@ elf:
exclude_path: (.*s390-ccw.img.*)|(.*s390-netboot.img.*)
inspections:
badfuncs: off
lostpayload: off
annocheck:
- hardened: --skip-cf-protection --skip-property-note --skip-stack-clash --ignore-unknown --verbose
- rhel-policy: --skip-cf-protection --skip-property-note --skip-stack-clash --ignore-unknown --verbose