* [PATCH v3 0/5] KVM: arm64: fix VGICv3 redistributor rollback
@ 2026-08-22 9:53 Karl Mehltretter
2026-08-22 9:53 ` [PATCH v3 1/5] KVM: arm64: vgic-v3: Undo assignment on iodev registration failure Karl Mehltretter
` (5 more replies)
0 siblings, 6 replies; 7+ messages in thread
From: Karl Mehltretter @ 2026-08-22 9:53 UTC (permalink / raw)
To: Marc Zyngier, Oliver Upton, kvmarm
Cc: Karl Mehltretter, Fuad Tabba, Joey Gouly, Steffen Eiden,
Suzuki K Poulose, Zenghui Yu, Catalin Marinas, Will Deacon,
Paolo Bonzini, Shuah Khan, Eric Auger, Christoffer Dall,
linux-arm-kernel, linux-kernel, kvm, linux-kselftest
A failed REDIST_REGION write can remove redistributor iodevs from
KVM_MMIO_BUS while leaving their cached vCPU assignments intact. A
corrected retry then skips those redistributors.
Userspace should instead see a failed region update atomically: no prior
redistributor assignment survives the failure, and the next successful
update rebuilds all possible assignments in region-index order.
Patch 1 fixes a separate accounting bug when an individual MMIO-bus
registration fails. It reserves the selected region slot before
registration and undoes that known-latest assignment if registration fails.
Patch 2 implements the atomic failed-region behavior. It unregisters every
redistributor iodev, clears every cached assignment, resets the region
counters, and frees the newly inserted region. An in-flight vCPU can have
an RD iodev before kvm_for_each_vcpu() can see it, so REDIST and
REDIST_REGION writes are serialized with vCPU creation and return -EBUSY
while the created_vcpus/online_vcpus counts differ.
Patch 3 is independent teardown cleanup. It separates MMIO-bus teardown
from config-locked assignment cleanup, preserves the cleanup required
before a late failed vCPU creation frees the vCPU, and removes the special
conditional from the common vCPU destructor.
Patch 4 keeps the selftest helper aligned with vm_create_with_vcpus(), and
patch 5 adds regression coverage for an overlapping region, retry, and
final GICR_TYPER accesses to all four redistributors. The test exercises
patch 2's final-state behavior; patch 1's MMIO-bus allocation failure is
not fault-injected.
Testing: built the patched kernel and the arm64 vgic_init selftest with
GCC 13.3.0 in an arm64 Linux container. The selftest passed under QEMU
11.0.2 TCG with -machine virt,virtualization=on,gic-version=3 and -cpu max.
---
Changes since v2:
- Patch 1: limit free_index rollback to the immediate registration failure
under slots_lock instead of generic unregistration. (Sashiko)
- Patch 2: reset all assignments and region counters after a failed region
update (Marc), and serialize REDIST and REDIST_REGION writes with vCPU
creation so rollback cannot miss an unpublished assignment.
- Patch 3: add an already-locked unassignment primitive, move failed-vCPU
cleanup to kvm_vgic_vcpu_destroy(), and remove the redundant base_addr
reset. (Marc)
- Patch 4: match vm_create_with_vcpus() by using void * for the guest-code
argument. (Sashiko)
- Patch 5: document how the first three redistributors span regions 0
and 1; no functional change.
Previous version:
v2: https://lore.kernel.org/r/20260819224229.82948-1-kmehltretter@gmail.com
Karl Mehltretter (5):
KVM: arm64: vgic-v3: Undo assignment on iodev registration failure
KVM: arm64: vgic-v3: Reset redistributors after failed region setup
KVM: arm64: vgic-v3: Separate redistributor teardown from unassignment
KVM: arm64: selftests: Pass guest code to vm_gic_create_with_vcpus()
KVM: arm64: selftests: Test VGICv3 redistributor region retry
arch/arm64/kvm/vgic/vgic-init.c | 46 +++----
arch/arm64/kvm/vgic/vgic-kvm-device.c | 20 +++
arch/arm64/kvm/vgic/vgic-mmio-v3.c | 83 ++++++++-----
arch/arm64/kvm/vgic/vgic.h | 1 +
tools/testing/selftests/kvm/arm64/vgic_init.c | 116 ++++++++++++++++--
5 files changed, 200 insertions(+), 66 deletions(-)
base-commit: 57e7cf13ac26bf1a3dba6cfa601f7b2481811575
--
2.39.5 (Apple Git-154)
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v3 1/5] KVM: arm64: vgic-v3: Undo assignment on iodev registration failure
2026-08-22 9:53 [PATCH v3 0/5] KVM: arm64: fix VGICv3 redistributor rollback Karl Mehltretter
@ 2026-08-22 9:53 ` Karl Mehltretter
2026-08-22 9:53 ` [PATCH v3 2/5] KVM: arm64: vgic-v3: Reset redistributors after failed region setup Karl Mehltretter
` (4 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Karl Mehltretter @ 2026-08-22 9:53 UTC (permalink / raw)
To: Marc Zyngier, Oliver Upton, kvmarm
Cc: Karl Mehltretter, Fuad Tabba, Joey Gouly, Steffen Eiden,
Suzuki K Poulose, Zenghui Yu, Catalin Marinas, Will Deacon,
Paolo Bonzini, Shuah Khan, Eric Auger, Christoffer Dall,
linux-arm-kernel, linux-kernel, kvm, linux-kselftest, stable
vgic_register_redist_iodev() assigns a redistributor region and base
address to the vCPU before adding its iodev to the MMIO bus. However, the
region's free_index is advanced only after registration succeeds.
If kvm_io_bus_register_dev() fails, the vCPU retains the assignment while
free_index still identifies the same slot as free. A later registration can
therefore reuse a slot that remains assigned to the vCPU.
Reserve the slot before registering its iodev. The caller holds slots_lock,
so a registration failure cannot race with a later assignment. Undo the
reservation and clear the cached assignment on failure.
Fixes: dbd9733ab674 ("KVM: arm/arm64: Replace the single rdist region by a list")
Cc: stable@vger.kernel.org
Assisted-by: Codex:gpt-5.6-sol
Signed-off-by: Karl Mehltretter <kmehltretter@gmail.com>
---
arch/arm64/kvm/vgic/vgic-mmio-v3.c | 22 ++++++++++++++++++----
1 file changed, 18 insertions(+), 4 deletions(-)
diff --git a/arch/arm64/kvm/vgic/vgic-mmio-v3.c b/arch/arm64/kvm/vgic/vgic-mmio-v3.c
index 5913a20d8301..22897ce64dbf 100644
--- a/arch/arm64/kvm/vgic/vgic-mmio-v3.c
+++ b/arch/arm64/kvm/vgic/vgic-mmio-v3.c
@@ -766,6 +766,19 @@ unsigned int vgic_v3_init_dist_iodev(struct vgic_io_device *dev)
return SZ_64K;
}
+static void vgic_undo_redist_assignment(struct kvm_vcpu *vcpu)
+{
+ struct vgic_cpu *vgic_cpu = &vcpu->arch.vgic_cpu;
+
+ lockdep_assert_held(&vcpu->kvm->slots_lock);
+
+ guard(mutex)(&vcpu->kvm->arch.config_lock);
+
+ vgic_cpu->rdreg->free_index--;
+ vgic_cpu->rdreg = NULL;
+ vgic_cpu->rd_iodev.base_addr = VGIC_ADDR_UNDEF;
+}
+
/**
* vgic_register_redist_iodev - register a single redist iodev
* @vcpu: The VCPU to which the redistributor belongs
@@ -818,16 +831,17 @@ int vgic_register_redist_iodev(struct kvm_vcpu *vcpu)
rd_dev->nr_regions = ARRAY_SIZE(vgic_v3_rd_registers);
rd_dev->redist_vcpu = vcpu;
+ /* Protected by slots_lock */
+ rdreg->free_index++;
+
mutex_unlock(&kvm->arch.config_lock);
ret = kvm_io_bus_register_dev(kvm, KVM_MMIO_BUS, rd_base,
2 * SZ_64K, &rd_dev->dev);
if (ret)
- return ret;
+ vgic_undo_redist_assignment(vcpu);
- /* Protected by slots_lock */
- rdreg->free_index++;
- return 0;
+ return ret;
out_unlock:
mutex_unlock(&kvm->arch.config_lock);
--
2.39.5 (Apple Git-154)
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v3 2/5] KVM: arm64: vgic-v3: Reset redistributors after failed region setup
2026-08-22 9:53 [PATCH v3 0/5] KVM: arm64: fix VGICv3 redistributor rollback Karl Mehltretter
2026-08-22 9:53 ` [PATCH v3 1/5] KVM: arm64: vgic-v3: Undo assignment on iodev registration failure Karl Mehltretter
@ 2026-08-22 9:53 ` Karl Mehltretter
2026-08-22 9:53 ` [PATCH v3 3/5] KVM: arm64: vgic-v3: Separate redistributor teardown from unassignment Karl Mehltretter
` (3 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Karl Mehltretter @ 2026-08-22 9:53 UTC (permalink / raw)
To: Marc Zyngier, Oliver Upton, kvmarm
Cc: Karl Mehltretter, Fuad Tabba, Joey Gouly, Steffen Eiden,
Suzuki K Poulose, Zenghui Yu, Catalin Marinas, Will Deacon,
Paolo Bonzini, Shuah Khan, Eric Auger, Christoffer Dall,
linux-arm-kernel, linux-kernel, kvm, linux-kselftest, stable
A REDIST_REGION write can fail after redistributor iodevs have been
registered. The existing rollback unregisters only vCPUs processed before
the failure, leaving their cached assignments and free_index values intact.
A retry then skips the unregistered iodevs.
Userspace has no guarantee that redistributor assignments survive a failed
region update. On failure, unregister every redistributor iodev and clear
every cached vCPU assignment. Reset all region free_index values and free
the newly inserted region. The next successful region update rebuilds all
possible assignments in region-index order.
While a vCPU is being created, its redistributor iodev may be registered
before the vCPU is visible to kvm_for_each_vcpu(). Reject REDIST and
REDIST_REGION writes while creation is in flight, so rollback can reset every
assignment.
The registration failure for the current vCPU is already undone by
vgic_register_redist_iodev().
Fixes: c011f4ea106b ("KVM: arm/arm64: Check vcpu redist base before registering an iodev")
Suggested-by: Marc Zyngier <maz@kernel.org>
Cc: stable@vger.kernel.org
Assisted-by: Codex:gpt-5.6-sol
Signed-off-by: Karl Mehltretter <kmehltretter@gmail.com>
---
arch/arm64/kvm/vgic/vgic-kvm-device.c | 20 ++++++++++
arch/arm64/kvm/vgic/vgic-mmio-v3.c | 53 +++++++++++++++++++--------
2 files changed, 57 insertions(+), 16 deletions(-)
diff --git a/arch/arm64/kvm/vgic/vgic-kvm-device.c b/arch/arm64/kvm/vgic/vgic-kvm-device.c
index 90be99443df3..48c3b2a48c20 100644
--- a/arch/arm64/kvm/vgic/vgic-kvm-device.c
+++ b/arch/arm64/kvm/vgic/vgic-kvm-device.c
@@ -97,6 +97,9 @@ static int kvm_vgic_addr(struct kvm *kvm, struct kvm_device_attr *attr, bool wri
phys_addr_t *addr_ptr, alignment, size;
u64 undef_value = VGIC_ADDR_UNDEF;
u64 addr;
+ bool redist_write = write &&
+ (attr->attr == KVM_VGIC_V3_ADDR_TYPE_REDIST ||
+ attr->attr == KVM_VGIC_V3_ADDR_TYPE_REDIST_REGION);
int r;
/* Reading a redistributor region addr implies getting the index */
@@ -104,6 +107,19 @@ static int kvm_vgic_addr(struct kvm *kvm, struct kvm_device_attr *attr, bool wri
if (get_user(addr, uaddr))
return -EFAULT;
+ /*
+ * A vCPU can have an RD assignment before it is visible to
+ * kvm_for_each_vcpu(). Reject redistributor updates while vCPU creation
+ * is in progress, so rollback can reset every assignment.
+ */
+ if (redist_write) {
+ mutex_lock(&kvm->lock);
+ if (kvm->created_vcpus != atomic_read(&kvm->online_vcpus)) {
+ r = -EBUSY;
+ goto out_unlock_kvm;
+ }
+ }
+
/*
* Since we can't hold config_lock while registering the redistributor
* iodevs, take the slots_lock immediately.
@@ -201,6 +217,10 @@ static int kvm_vgic_addr(struct kvm *kvm, struct kvm_device_attr *attr, bool wri
out:
mutex_unlock(&kvm->slots_lock);
+out_unlock_kvm:
+ if (redist_write)
+ mutex_unlock(&kvm->lock);
+
if (!r && !write)
r = put_user(addr, uaddr);
diff --git a/arch/arm64/kvm/vgic/vgic-mmio-v3.c b/arch/arm64/kvm/vgic/vgic-mmio-v3.c
index 22897ce64dbf..6c009deb11d4 100644
--- a/arch/arm64/kvm/vgic/vgic-mmio-v3.c
+++ b/arch/arm64/kvm/vgic/vgic-mmio-v3.c
@@ -855,6 +855,40 @@ void vgic_unregister_redist_iodev(struct kvm_vcpu *vcpu)
kvm_io_bus_unregister_dev(vcpu->kvm, KVM_MMIO_BUS, &rd_dev->dev);
}
+static void vgic_reset_redist_iodev(struct kvm_vcpu *vcpu)
+{
+ struct vgic_cpu *vgic_cpu = &vcpu->arch.vgic_cpu;
+
+ lockdep_assert_held(&vcpu->kvm->arch.config_lock);
+
+ vgic_cpu->rdreg = NULL;
+ vgic_cpu->rd_iodev.base_addr = VGIC_ADDR_UNDEF;
+}
+
+static void vgic_v3_rollback_redist_region(struct kvm *kvm, u32 index)
+{
+ struct vgic_redist_region *rdreg, *iter;
+ struct kvm_vcpu *vcpu;
+ unsigned long c;
+
+ lockdep_assert_held(&kvm->slots_lock);
+
+ rdreg = vgic_v3_rdist_region_from_index(kvm, index);
+
+ kvm_for_each_vcpu(c, vcpu, kvm)
+ vgic_unregister_redist_iodev(vcpu);
+
+ guard(mutex)(&kvm->arch.config_lock);
+
+ kvm_for_each_vcpu(c, vcpu, kvm)
+ vgic_reset_redist_iodev(vcpu);
+
+ list_for_each_entry(iter, &kvm->arch.vgic.rd_regions, list)
+ iter->free_index = 0;
+
+ vgic_v3_free_redist_region(kvm, rdreg);
+}
+
static int vgic_register_all_redist_iodevs(struct kvm *kvm)
{
struct kvm_vcpu *vcpu;
@@ -869,16 +903,6 @@ static int vgic_register_all_redist_iodevs(struct kvm *kvm)
break;
}
- if (ret) {
- /* The current c failed, so iterate over the previous ones. */
- int i;
-
- for (i = 0; i < c; i++) {
- vcpu = kvm_get_vcpu(kvm, i);
- vgic_unregister_redist_iodev(vcpu);
- }
- }
-
return ret;
}
@@ -986,6 +1010,8 @@ int vgic_v3_set_redist_base(struct kvm *kvm, u32 index, u64 addr, u32 count)
{
int ret;
+ lockdep_assert_held(&kvm->lock);
+
mutex_lock(&kvm->arch.config_lock);
ret = vgic_v3_alloc_redist_region(kvm, index, addr, count);
mutex_unlock(&kvm->arch.config_lock);
@@ -998,12 +1024,7 @@ int vgic_v3_set_redist_base(struct kvm *kvm, u32 index, u64 addr, u32 count)
*/
ret = vgic_register_all_redist_iodevs(kvm);
if (ret) {
- struct vgic_redist_region *rdreg;
-
- mutex_lock(&kvm->arch.config_lock);
- rdreg = vgic_v3_rdist_region_from_index(kvm, index);
- vgic_v3_free_redist_region(kvm, rdreg);
- mutex_unlock(&kvm->arch.config_lock);
+ vgic_v3_rollback_redist_region(kvm, index);
return ret;
}
--
2.39.5 (Apple Git-154)
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v3 3/5] KVM: arm64: vgic-v3: Separate redistributor teardown from unassignment
2026-08-22 9:53 [PATCH v3 0/5] KVM: arm64: fix VGICv3 redistributor rollback Karl Mehltretter
2026-08-22 9:53 ` [PATCH v3 1/5] KVM: arm64: vgic-v3: Undo assignment on iodev registration failure Karl Mehltretter
2026-08-22 9:53 ` [PATCH v3 2/5] KVM: arm64: vgic-v3: Reset redistributors after failed region setup Karl Mehltretter
@ 2026-08-22 9:53 ` Karl Mehltretter
2026-08-22 9:53 ` [PATCH v3 4/5] KVM: arm64: selftests: Pass guest code to vm_gic_create_with_vcpus() Karl Mehltretter
` (2 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Karl Mehltretter @ 2026-08-22 9:53 UTC (permalink / raw)
To: Marc Zyngier, Oliver Upton, kvmarm
Cc: Karl Mehltretter, Fuad Tabba, Joey Gouly, Steffen Eiden,
Suzuki K Poulose, Zenghui Yu, Catalin Marinas, Will Deacon,
Paolo Bonzini, Shuah Khan, Eric Auger, Christoffer Dall,
linux-arm-kernel, linux-kernel, kvm, linux-kselftest
MMIO-bus unregistration may synchronize SRCU and must run outside
config_lock. Conversely, clearing the redistributor assignment needs
config_lock, and teardown must do so before freeing the redistributor
regions.
Introduce an already-locked unassignment primitive that only clears the
cached region and base address. It deliberately does not adjust free_index:
failure rollback resets all region counters, while VM teardown frees the
regions. Keep MMIO-bus unregistration separate. Unregister devices before
taking config_lock in VM teardown, then unassign the vCPUs before freeing
their regions.
Move redistributor cleanup out of __kvm_vgic_vcpu_destroy() and into its
outer wrapper. This preserves failed-vCPU cleanup before its memory can be
freed, without the special conditional in the common destructor. The region
destructor no longer needs to scan the vCPUs.
Suggested-by: Marc Zyngier <maz@kernel.org>
Assisted-by: Codex:gpt-5.6-sol
Signed-off-by: Karl Mehltretter <kmehltretter@gmail.com>
---
arch/arm64/kvm/vgic/vgic-init.c | 46 +++++++++++-------------------
arch/arm64/kvm/vgic/vgic-mmio-v3.c | 16 ++---------
arch/arm64/kvm/vgic/vgic.h | 1 +
3 files changed, 21 insertions(+), 42 deletions(-)
diff --git a/arch/arm64/kvm/vgic/vgic-init.c b/arch/arm64/kvm/vgic/vgic-init.c
index 0a3df6d3a691..a0d72b540331 100644
--- a/arch/arm64/kvm/vgic/vgic-init.c
+++ b/arch/arm64/kvm/vgic/vgic-init.c
@@ -523,29 +523,6 @@ static void __kvm_vgic_vcpu_destroy(struct kvm_vcpu *vcpu)
INIT_LIST_HEAD(&vgic_cpu->ap_list_head);
kfree(vgic_cpu->private_irqs);
vgic_cpu->private_irqs = NULL;
-
- if (vcpu->kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V3) {
- /*
- * If this vCPU is being destroyed because of a failed creation
- * then unregister the redistributor to avoid leaving behind a
- * dangling pointer to the vCPU struct.
- *
- * vCPUs that have been successfully created (i.e. added to
- * kvm->vcpu_array) get unregistered in kvm_vgic_destroy(), as
- * this function gets called while holding kvm->arch.config_lock
- * in the VM teardown path and would otherwise introduce a lock
- * inversion w.r.t. kvm->srcu.
- *
- * vCPUs that failed creation are torn down outside of the
- * kvm->arch.config_lock and do not get unregistered in
- * kvm_vgic_destroy(), meaning it is both safe and necessary to
- * do so here.
- */
- if (kvm_get_vcpu_by_id(vcpu->kvm, vcpu->vcpu_id) != vcpu)
- vgic_unregister_redist_iodev(vcpu);
-
- vgic_cpu->rd_iodev.base_addr = VGIC_ADDR_UNDEF;
- }
}
void kvm_vgic_vcpu_destroy(struct kvm_vcpu *vcpu)
@@ -553,7 +530,16 @@ void kvm_vgic_vcpu_destroy(struct kvm_vcpu *vcpu)
struct kvm *kvm = vcpu->kvm;
mutex_lock(&kvm->slots_lock);
+ if (kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V3)
+ vgic_unregister_redist_iodev(vcpu);
+
__kvm_vgic_vcpu_destroy(vcpu);
+
+ if (kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V3) {
+ mutex_lock(&kvm->arch.config_lock);
+ __vgic_unassign_redist_iodev(vcpu);
+ mutex_unlock(&kvm->arch.config_lock);
+ }
mutex_unlock(&kvm->slots_lock);
}
@@ -563,21 +549,23 @@ void kvm_vgic_destroy(struct kvm *kvm)
unsigned long i;
mutex_lock(&kvm->slots_lock);
+ if (kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V3)
+ kvm_for_each_vcpu(i, vcpu, kvm)
+ vgic_unregister_redist_iodev(vcpu);
+
mutex_lock(&kvm->arch.config_lock);
vgic_debug_destroy(kvm);
- kvm_for_each_vcpu(i, vcpu, kvm)
+ kvm_for_each_vcpu(i, vcpu, kvm) {
__kvm_vgic_vcpu_destroy(vcpu);
+ if (kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V3)
+ __vgic_unassign_redist_iodev(vcpu);
+ }
kvm_vgic_dist_destroy(kvm);
mutex_unlock(&kvm->arch.config_lock);
-
- if (kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V3)
- kvm_for_each_vcpu(i, vcpu, kvm)
- vgic_unregister_redist_iodev(vcpu);
-
mutex_unlock(&kvm->slots_lock);
}
diff --git a/arch/arm64/kvm/vgic/vgic-mmio-v3.c b/arch/arm64/kvm/vgic/vgic-mmio-v3.c
index 6c009deb11d4..dc860178105d 100644
--- a/arch/arm64/kvm/vgic/vgic-mmio-v3.c
+++ b/arch/arm64/kvm/vgic/vgic-mmio-v3.c
@@ -775,8 +775,7 @@ static void vgic_undo_redist_assignment(struct kvm_vcpu *vcpu)
guard(mutex)(&vcpu->kvm->arch.config_lock);
vgic_cpu->rdreg->free_index--;
- vgic_cpu->rdreg = NULL;
- vgic_cpu->rd_iodev.base_addr = VGIC_ADDR_UNDEF;
+ __vgic_unassign_redist_iodev(vcpu);
}
/**
@@ -855,7 +854,7 @@ void vgic_unregister_redist_iodev(struct kvm_vcpu *vcpu)
kvm_io_bus_unregister_dev(vcpu->kvm, KVM_MMIO_BUS, &rd_dev->dev);
}
-static void vgic_reset_redist_iodev(struct kvm_vcpu *vcpu)
+void __vgic_unassign_redist_iodev(struct kvm_vcpu *vcpu)
{
struct vgic_cpu *vgic_cpu = &vcpu->arch.vgic_cpu;
@@ -881,7 +880,7 @@ static void vgic_v3_rollback_redist_region(struct kvm *kvm, u32 index)
guard(mutex)(&kvm->arch.config_lock);
kvm_for_each_vcpu(c, vcpu, kvm)
- vgic_reset_redist_iodev(vcpu);
+ __vgic_unassign_redist_iodev(vcpu);
list_for_each_entry(iter, &kvm->arch.vgic.rd_regions, list)
iter->free_index = 0;
@@ -991,17 +990,8 @@ static int vgic_v3_alloc_redist_region(struct kvm *kvm, uint32_t index,
void vgic_v3_free_redist_region(struct kvm *kvm, struct vgic_redist_region *rdreg)
{
- struct kvm_vcpu *vcpu;
- unsigned long c;
-
lockdep_assert_held(&kvm->arch.config_lock);
- /* Garbage collect the region */
- kvm_for_each_vcpu(c, vcpu, kvm) {
- if (vcpu->arch.vgic_cpu.rdreg == rdreg)
- vcpu->arch.vgic_cpu.rdreg = NULL;
- }
-
list_del(&rdreg->list);
kfree(rdreg);
}
diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h
index b71d486ae514..1a2e40004a47 100644
--- a/arch/arm64/kvm/vgic/vgic.h
+++ b/arch/arm64/kvm/vgic/vgic.h
@@ -350,6 +350,7 @@ int vgic_v3_save_pending_tables(struct kvm *kvm);
int vgic_v3_set_redist_base(struct kvm *kvm, u32 index, u64 addr, u32 count);
int vgic_register_redist_iodev(struct kvm_vcpu *vcpu);
void vgic_unregister_redist_iodev(struct kvm_vcpu *vcpu);
+void __vgic_unassign_redist_iodev(struct kvm_vcpu *vcpu);
bool vgic_v3_check_base(struct kvm *kvm);
void vgic_v3_load(struct kvm_vcpu *vcpu);
--
2.39.5 (Apple Git-154)
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v3 4/5] KVM: arm64: selftests: Pass guest code to vm_gic_create_with_vcpus()
2026-08-22 9:53 [PATCH v3 0/5] KVM: arm64: fix VGICv3 redistributor rollback Karl Mehltretter
` (2 preceding siblings ...)
2026-08-22 9:53 ` [PATCH v3 3/5] KVM: arm64: vgic-v3: Separate redistributor teardown from unassignment Karl Mehltretter
@ 2026-08-22 9:53 ` Karl Mehltretter
2026-08-22 9:53 ` [PATCH v3 5/5] KVM: arm64: selftests: Test VGICv3 redistributor region retry Karl Mehltretter
2026-08-23 15:39 ` [PATCH v3 0/5] KVM: arm64: fix VGICv3 redistributor rollback Fuad Tabba
5 siblings, 0 replies; 7+ messages in thread
From: Karl Mehltretter @ 2026-08-22 9:53 UTC (permalink / raw)
To: Marc Zyngier, Oliver Upton, kvmarm
Cc: Karl Mehltretter, Fuad Tabba, Joey Gouly, Steffen Eiden,
Suzuki K Poulose, Zenghui Yu, Catalin Marinas, Will Deacon,
Paolo Bonzini, Shuah Khan, Eric Auger, Christoffer Dall,
linux-arm-kernel, linux-kernel, kvm, linux-kselftest
Pass the guest-code address to vm_gic_create_with_vcpus(), and use
guest_code at the existing call sites. This lets tests with different guest
code reuse the common VM and VGIC setup while retaining the void *
interface of vm_create_with_vcpus().
Assisted-by: Codex:gpt-5.6-sol
Signed-off-by: Karl Mehltretter <kmehltretter@gmail.com>
---
tools/testing/selftests/kvm/arm64/vgic_init.c | 24 ++++++++++++-------
1 file changed, 16 insertions(+), 8 deletions(-)
diff --git a/tools/testing/selftests/kvm/arm64/vgic_init.c b/tools/testing/selftests/kvm/arm64/vgic_init.c
index 47e34b43afb2..84aa97204b7f 100644
--- a/tools/testing/selftests/kvm/arm64/vgic_init.c
+++ b/tools/testing/selftests/kvm/arm64/vgic_init.c
@@ -73,6 +73,7 @@ static int run_vcpu(struct kvm_vcpu *vcpu)
static struct vm_gic vm_gic_create_with_vcpus(u32 gic_dev_type,
u32 nr_vcpus,
+ void *guest_code,
struct kvm_vcpu *vcpus[])
{
struct vm_gic v;
@@ -338,7 +339,7 @@ static void test_vgic_then_vcpus(u32 gic_dev_type)
struct vm_gic v;
int ret, i;
- v = vm_gic_create_with_vcpus(gic_dev_type, 1, vcpus);
+ v = vm_gic_create_with_vcpus(gic_dev_type, 1, guest_code, vcpus);
subtest_dist_rdist(&v);
@@ -359,7 +360,8 @@ static void test_vcpus_then_vgic(u32 gic_dev_type)
struct vm_gic v;
int ret;
- v = vm_gic_create_with_vcpus(gic_dev_type, NR_VCPUS, vcpus);
+ v = vm_gic_create_with_vcpus(gic_dev_type, NR_VCPUS, guest_code,
+ vcpus);
subtest_dist_rdist(&v);
@@ -411,7 +413,8 @@ static void test_v3_new_redist_regions(void)
u64 addr;
int ret;
- v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS, vcpus);
+ v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS,
+ guest_code, vcpus);
subtest_v3_redist_regions(&v);
kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
KVM_DEV_ARM_VGIC_CTRL_INIT, NULL);
@@ -422,7 +425,8 @@ static void test_v3_new_redist_regions(void)
/* step2 */
- v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS, vcpus);
+ v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS,
+ guest_code, vcpus);
subtest_v3_redist_regions(&v);
addr = REDIST_REGION_ATTR_ADDR(1, 0x280000, 0, 2);
@@ -436,7 +440,8 @@ static void test_v3_new_redist_regions(void)
/* step 3 */
- v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS, vcpus);
+ v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS,
+ guest_code, vcpus);
subtest_v3_redist_regions(&v);
ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
@@ -608,7 +613,8 @@ static void test_v3_redist_ipa_range_check_at_vcpu_run(void)
int ret, i;
u64 addr;
- v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, 1, vcpus);
+ v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, 1, guest_code,
+ vcpus);
/* Set space for 3 redists, we have 1 vcpu, so this succeeds. */
addr = max_phys_size - (3 * 2 * 0x10000);
@@ -641,7 +647,8 @@ static void test_v3_its_region(void)
u64 addr;
int its_fd, ret;
- v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS, vcpus);
+ v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS,
+ guest_code, vcpus);
its_fd = kvm_create_device(v.vm, KVM_DEV_TYPE_ARM_VGIC_ITS);
addr = 0x401000;
@@ -684,7 +691,8 @@ static void test_v3_nassgicap(void)
u32 typer2;
int ret;
- vm = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS, vcpus);
+ vm = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS,
+ guest_code, vcpus);
kvm_device_attr_get(vm.gic_fd, KVM_DEV_ARM_VGIC_GRP_DIST_REGS,
GICD_TYPER2, &typer2);
has_nassgicap = typer2 & GICD_TYPER2_nASSGIcap;
--
2.39.5 (Apple Git-154)
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v3 5/5] KVM: arm64: selftests: Test VGICv3 redistributor region retry
2026-08-22 9:53 [PATCH v3 0/5] KVM: arm64: fix VGICv3 redistributor rollback Karl Mehltretter
` (3 preceding siblings ...)
2026-08-22 9:53 ` [PATCH v3 4/5] KVM: arm64: selftests: Pass guest code to vm_gic_create_with_vcpus() Karl Mehltretter
@ 2026-08-22 9:53 ` Karl Mehltretter
2026-08-23 15:39 ` [PATCH v3 0/5] KVM: arm64: fix VGICv3 redistributor rollback Fuad Tabba
5 siblings, 0 replies; 7+ messages in thread
From: Karl Mehltretter @ 2026-08-22 9:53 UTC (permalink / raw)
To: Marc Zyngier, Oliver Upton, kvmarm
Cc: Karl Mehltretter, Fuad Tabba, Joey Gouly, Steffen Eiden,
Suzuki K Poulose, Zenghui Yu, Catalin Marinas, Will Deacon,
Paolo Bonzini, Shuah Khan, Eric Auger, Christoffer Dall,
linux-arm-kernel, linux-kernel, kvm, linux-kselftest
A failed REDIST_REGION write can remove redistributor iodevs without
clearing their cached vCPU assignments. A retry then skips those
assignments and leaves the redistributors absent from the MMIO bus.
Create two partial regions, reject a third region that overlaps the
distributor, and retry at a valid address. After initializing the VGIC,
have the guest read GICR_TYPER from all four redistributors and check their
processor numbers. This verifies the final configuration without assuming
that assignments survive the failed write.
Keep the redistributor IPAs outside the guest RAM memslot and install only
the stage-1 mappings needed for the MMIO accesses. If an iodev is missing,
KVM_RUN therefore exits with KVM_EXIT_MMIO. Without the fix, the first
redistributor access exits at 0x8030008.
Assisted-by: Codex:gpt-5.6-sol
Signed-off-by: Karl Mehltretter <kmehltretter@gmail.com>
---
tools/testing/selftests/kvm/arm64/vgic_init.c | 92 +++++++++++++++++++
1 file changed, 92 insertions(+)
diff --git a/tools/testing/selftests/kvm/arm64/vgic_init.c b/tools/testing/selftests/kvm/arm64/vgic_init.c
index 84aa97204b7f..5a30f3cb039b 100644
--- a/tools/testing/selftests/kvm/arm64/vgic_init.c
+++ b/tools/testing/selftests/kvm/arm64/vgic_init.c
@@ -5,6 +5,7 @@
* Copyright (C) 2020, Red Hat, Inc.
*/
#include <linux/kernel.h>
+#include <linux/sizes.h>
#include <sys/syscall.h>
#include <asm/kvm.h>
#include <asm/kvm_para.h>
@@ -13,12 +14,21 @@
#include "test_util.h"
#include "kvm_util.h"
+#include "gic.h"
#include "processor.h"
#include "vgic.h"
#include "gic_v3.h"
#define NR_VCPUS 4
+#define REDIST_RETRY_REGION0_BASE GICR_BASE_GPA
+#define REDIST_RETRY_REGION1_BASE \
+ (REDIST_RETRY_REGION0_BASE + 2 * KVM_VGIC_V3_REDIST_SIZE)
+#define REDIST_RETRY_DIST_BASE \
+ (REDIST_RETRY_REGION1_BASE + KVM_VGIC_V3_REDIST_SIZE)
+#define REDIST_RETRY_REGION2_BASE \
+ (REDIST_RETRY_DIST_BASE + KVM_VGIC_V3_DIST_SIZE)
+
#define REG_OFFSET(vcpu, offset) (((u64)vcpu << 32) | offset)
#define VGIC_DEV_IS_V2(_d) ((_d) == KVM_DEV_TYPE_ARM_VGIC_V2)
@@ -65,6 +75,23 @@ static void guest_code(void)
GUEST_DONE();
}
+static void guest_check_redist_retry(void)
+{
+ unsigned int i;
+
+ /* The first three redistributors span adjacent regions 0 and 1. */
+ for (i = 0; i < NR_VCPUS; i++) {
+ u64 base = i < 3 ? REDIST_RETRY_REGION0_BASE +
+ i * KVM_VGIC_V3_REDIST_SIZE :
+ REDIST_RETRY_REGION2_BASE;
+ u64 typer = readq((void *)(unsigned long)(base + GICR_TYPER));
+
+ GUEST_ASSERT_EQ(GICR_TYPER_CPU_NUMBER(typer), i);
+ }
+
+ GUEST_DONE();
+}
+
/* we don't want to assert on run execution, hence that helper */
static int run_vcpu(struct kvm_vcpu *vcpu)
{
@@ -462,6 +489,70 @@ static void test_v3_new_redist_regions(void)
vm_gic_destroy(&v);
}
+static void test_v3_redist_region_retry(void)
+{
+ struct kvm_vcpu *vcpus[NR_VCPUS];
+ struct vm_gic v;
+ struct ucall uc;
+ u64 addr;
+ int ret;
+
+ v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS,
+ guest_check_redist_retry, vcpus);
+
+ addr = REDIST_REGION_ATTR_ADDR(2, REDIST_RETRY_REGION0_BASE, 0, 0);
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V3_ADDR_TYPE_REDIST_REGION, &addr);
+
+ addr = REDIST_REGION_ATTR_ADDR(1, REDIST_RETRY_REGION1_BASE, 0, 1);
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V3_ADDR_TYPE_REDIST_REGION, &addr);
+
+ addr = REDIST_RETRY_DIST_BASE;
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V3_ADDR_TYPE_DIST, &addr);
+
+ addr = REDIST_REGION_ATTR_ADDR(1, REDIST_RETRY_DIST_BASE, 0, 2);
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V3_ADDR_TYPE_REDIST_REGION,
+ &addr);
+ TEST_ASSERT(ret && errno == EINVAL,
+ "register redist region colliding with dist");
+
+ addr = REDIST_REGION_ATTR_ADDR(1, REDIST_RETRY_REGION2_BASE, 0, 2);
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V3_ADDR_TYPE_REDIST_REGION, &addr);
+
+ virt_map(v.vm, REDIST_RETRY_REGION0_BASE, REDIST_RETRY_REGION0_BASE,
+ vm_calc_num_guest_pages(v.vm->mode,
+ 3 * KVM_VGIC_V3_REDIST_SIZE));
+ virt_map(v.vm, REDIST_RETRY_REGION2_BASE, REDIST_RETRY_REGION2_BASE,
+ vm_calc_num_guest_pages(v.vm->mode,
+ KVM_VGIC_V3_REDIST_SIZE));
+
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
+ KVM_DEV_ARM_VGIC_CTRL_INIT, NULL);
+
+ vcpu_run(vcpus[0]);
+ switch (get_ucall(vcpus[0], &uc)) {
+ case UCALL_DONE:
+ break;
+ case UCALL_ABORT:
+ REPORT_GUEST_ASSERT(uc);
+ break;
+ case UCALL_NONE:
+ if (vcpus[0]->run->exit_reason == KVM_EXIT_MMIO)
+ TEST_FAIL("Unexpected MMIO exit at 0x%llx",
+ vcpus[0]->run->mmio.phys_addr);
+ fallthrough;
+ default:
+ TEST_FAIL("Unexpected ucall %lu, exit_reason %u",
+ uc.cmd, vcpus[0]->run->exit_reason);
+ }
+
+ vm_gic_destroy(&v);
+}
+
static void test_v3_typer_accesses(void)
{
struct vm_gic v;
@@ -986,6 +1077,7 @@ void run_tests(u32 gic_dev_type)
if (VGIC_DEV_IS_V3(gic_dev_type)) {
test_v3_new_redist_regions();
+ test_v3_redist_region_retry();
test_v3_typer_accesses();
test_v3_last_bit_redist_regions();
test_v3_last_bit_single_rdist();
--
2.39.5 (Apple Git-154)
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH v3 0/5] KVM: arm64: fix VGICv3 redistributor rollback
2026-08-22 9:53 [PATCH v3 0/5] KVM: arm64: fix VGICv3 redistributor rollback Karl Mehltretter
` (4 preceding siblings ...)
2026-08-22 9:53 ` [PATCH v3 5/5] KVM: arm64: selftests: Test VGICv3 redistributor region retry Karl Mehltretter
@ 2026-08-23 15:39 ` Fuad Tabba
5 siblings, 0 replies; 7+ messages in thread
From: Fuad Tabba @ 2026-08-23 15:39 UTC (permalink / raw)
To: Karl Mehltretter
Cc: Marc Zyngier, Oliver Upton, kvmarm, Joey Gouly, Steffen Eiden,
Suzuki K Poulose, Zenghui Yu, Catalin Marinas, Will Deacon,
Paolo Bonzini, Shuah Khan, Eric Auger, Christoffer Dall,
linux-arm-kernel, linux-kernel, kvm, linux-kselftest
Hi Karl,
On Sat, 22 Aug 2026 at 10:53, Karl Mehltretter <kmehltretter@gmail.com> wrote:
...
> Testing: built the patched kernel and the arm64 vgic_init selftest with
> GCC 13.3.0 in an arm64 Linux container. The selftest passed under QEMU
> 11.0.2 TCG with -machine virt,virtualization=on,gic-version=3 and -cpu max.
Applied this on kvmarm/next: protected and non-protected guest boots
under pKVM, plus VHE and nVHE, and the vgic_init, vgic_irq and
vgic_lpi_stress selftest. Also built the base with only patches 4-5
and confirmed the new test fails without patches 1-3.
Reviewed-by: Fuad Tabba <fuad.tabba@linux.dev>
Tested-by: Fuad Tabba <fuad.tabba@linux.dev>
Cheers,
/fuad
> ---
> Changes since v2:
> - Patch 1: limit free_index rollback to the immediate registration failure
> under slots_lock instead of generic unregistration. (Sashiko)
> - Patch 2: reset all assignments and region counters after a failed region
> update (Marc), and serialize REDIST and REDIST_REGION writes with vCPU
> creation so rollback cannot miss an unpublished assignment.
> - Patch 3: add an already-locked unassignment primitive, move failed-vCPU
> cleanup to kvm_vgic_vcpu_destroy(), and remove the redundant base_addr
> reset. (Marc)
> - Patch 4: match vm_create_with_vcpus() by using void * for the guest-code
> argument. (Sashiko)
> - Patch 5: document how the first three redistributors span regions 0
> and 1; no functional change.
>
> Previous version:
> v2: https://lore.kernel.org/r/20260819224229.82948-1-kmehltretter@gmail.com
>
> Karl Mehltretter (5):
> KVM: arm64: vgic-v3: Undo assignment on iodev registration failure
> KVM: arm64: vgic-v3: Reset redistributors after failed region setup
> KVM: arm64: vgic-v3: Separate redistributor teardown from unassignment
> KVM: arm64: selftests: Pass guest code to vm_gic_create_with_vcpus()
> KVM: arm64: selftests: Test VGICv3 redistributor region retry
>
> arch/arm64/kvm/vgic/vgic-init.c | 46 +++----
> arch/arm64/kvm/vgic/vgic-kvm-device.c | 20 +++
> arch/arm64/kvm/vgic/vgic-mmio-v3.c | 83 ++++++++-----
> arch/arm64/kvm/vgic/vgic.h | 1 +
> tools/testing/selftests/kvm/arm64/vgic_init.c | 116 ++++++++++++++++--
> 5 files changed, 200 insertions(+), 66 deletions(-)
>
>
> base-commit: 57e7cf13ac26bf1a3dba6cfa601f7b2481811575
> --
> 2.39.5 (Apple Git-154)
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-08-23 15:40 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-22 9:53 [PATCH v3 0/5] KVM: arm64: fix VGICv3 redistributor rollback Karl Mehltretter
2026-08-22 9:53 ` [PATCH v3 1/5] KVM: arm64: vgic-v3: Undo assignment on iodev registration failure Karl Mehltretter
2026-08-22 9:53 ` [PATCH v3 2/5] KVM: arm64: vgic-v3: Reset redistributors after failed region setup Karl Mehltretter
2026-08-22 9:53 ` [PATCH v3 3/5] KVM: arm64: vgic-v3: Separate redistributor teardown from unassignment Karl Mehltretter
2026-08-22 9:53 ` [PATCH v3 4/5] KVM: arm64: selftests: Pass guest code to vm_gic_create_with_vcpus() Karl Mehltretter
2026-08-22 9:53 ` [PATCH v3 5/5] KVM: arm64: selftests: Test VGICv3 redistributor region retry Karl Mehltretter
2026-08-23 15:39 ` [PATCH v3 0/5] KVM: arm64: fix VGICv3 redistributor rollback Fuad Tabba
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox