summaryrefslogtreecommitdiff
path: root/arch/s390
diff options
context:
space:
mode:
authorPaolo Bonzini <pbonzini@redhat.com>2026-06-24 13:40:39 +0200
committerPaolo Bonzini <pbonzini@redhat.com>2026-06-24 13:41:41 +0200
commit91b16b53a08c3684ea2b0ad3cbf8ecd48c0f8b77 (patch)
tree977e7a4d4484bf69d61d3a16aa33474f74f97c81 /arch/s390
parentbb365a506b1e6fb050c0fceaad354fe395385ef0 (diff)
parentbabe08404e1993697a523e60bc0f9d096ffe1ef8 (diff)
Merge tag 'kvm-s390-next-7.2-2' of https://git.kernel.org/pub/scm/linux/kernel/git/kvms390/linux into HEAD
* Fix S390_USER_OPEREXEC so it can now be enabled regardless of other unrelated capabilities * Fix handling of the _PAGE_UNUSED pte bit that could lead to guest memory corruption in some scenarios * A bunch of misc gmap fixes (locking, behaviour under memory pressure) * Fix CMMA dirty tracking
Diffstat (limited to 'arch/s390')
-rw-r--r--arch/s390/Kconfig3
-rw-r--r--arch/s390/include/asm/pgtable.h4
-rw-r--r--arch/s390/kvm/dat.c5
-rw-r--r--arch/s390/kvm/gmap.c35
-rw-r--r--arch/s390/kvm/gmap.h12
-rw-r--r--arch/s390/kvm/kvm-s390.c71
-rw-r--r--arch/s390/kvm/priv.c10
-rw-r--r--arch/s390/mm/gmap_helpers.c3
8 files changed, 103 insertions, 40 deletions
diff --git a/arch/s390/Kconfig b/arch/s390/Kconfig
index ecbcbb781e40..9921a3772bf7 100644
--- a/arch/s390/Kconfig
+++ b/arch/s390/Kconfig
@@ -29,9 +29,6 @@ config GENERIC_BUG
config GENERIC_BUG_RELATIVE_POINTERS
def_bool y
-config GENERIC_LOCKBREAK
- def_bool y if PREEMPTION
-
config AUDIT_ARCH
def_bool y
diff --git a/arch/s390/include/asm/pgtable.h b/arch/s390/include/asm/pgtable.h
index 2c6cee8241e0..4740c75649eb 100644
--- a/arch/s390/include/asm/pgtable.h
+++ b/arch/s390/include/asm/pgtable.h
@@ -980,6 +980,8 @@ static inline void set_pmd(pmd_t *pmdp, pmd_t pmd)
static inline void set_pte(pte_t *ptep, pte_t pte)
{
+ if (pte_present(pte))
+ pte = clear_pte_bit(pte, __pgprot(_PAGE_UNUSED));
WRITE_ONCE(*ptep, pte);
}
@@ -1332,8 +1334,6 @@ pgprot_t pgprot_writecombine(pgprot_t prot);
static inline void set_ptes(struct mm_struct *mm, unsigned long addr,
pte_t *ptep, pte_t entry, unsigned int nr)
{
- if (pte_present(entry))
- entry = clear_pte_bit(entry, __pgprot(_PAGE_UNUSED));
page_table_check_ptes_set(mm, addr, ptep, entry, nr);
for (;;) {
set_pte(ptep, entry);
diff --git a/arch/s390/kvm/dat.c b/arch/s390/kvm/dat.c
index a4fe664f65ee..5f1960ec982d 100644
--- a/arch/s390/kvm/dat.c
+++ b/arch/s390/kvm/dat.c
@@ -1209,7 +1209,7 @@ int dat_peek_cmma(gfn_t start, union asce asce, unsigned int *count, u8 *values)
int rc;
rc = _dat_walk_gfn_range(start, start + *count, asce, &ops, DAT_WALK_DEFAULT, &state);
- *count = state.end - start;
+ *count = state.end >= start ? state.end - start : 0;
/* Return success if at least one value was saved, otherwise an error. */
return (rc == -EFAULT && *count > 0) ? 0 : rc;
}
@@ -1253,6 +1253,9 @@ int dat_get_cmma(union asce asce, gfn_t *start, unsigned int *count, u8 *values,
};
_dat_walk_gfn_range(*start, asce_end(asce), asce, &ops, DAT_WALK_IGN_HOLES, &state);
+ /* If no dirty pages were found, wrap around and continue searching */
+ if (*start && state.start == -1)
+ _dat_walk_gfn_range(0, *start, asce, &ops, DAT_WALK_IGN_HOLES, &state);
if (state.start == -1) {
*count = 0;
diff --git a/arch/s390/kvm/gmap.c b/arch/s390/kvm/gmap.c
index 77829f787dc7..298fbaecec28 100644
--- a/arch/s390/kvm/gmap.c
+++ b/arch/s390/kvm/gmap.c
@@ -332,7 +332,7 @@ static long gmap_clear_young_crste(union crste *crstep, gfn_t gfn, gfn_t end, st
new.h.i = 1;
new.s.fc1.y = 0;
new.s.fc1.prefix_notif = 0;
- if (new.s.fc1.d || !new.h.p)
+ if ((new.s.fc1.d || !new.h.p) && !new.s.fc1.s)
folio_set_dirty(phys_to_folio(crste_origin_large(crste)));
new.s.fc1.d = 0;
new.h.p = 1;
@@ -1098,23 +1098,46 @@ int gmap_protect_rmap(struct kvm_s390_mmu_cache *mc, struct gmap *sg, gfn_t p_gf
return 0;
}
+static long __set_cmma_clean_pte(union pte *ptep, gfn_t gfn, gfn_t next, struct dat_walk *walk)
+{
+ union pgste pgste;
+
+ pgste = pgste_get_lock(ptep);
+ pgste.cmma_d = 0;
+ pgste_set_unlock(ptep, pgste);
+
+ if (need_resched())
+ return next;
+ return 0;
+}
+
static long __set_cmma_dirty_pte(union pte *ptep, gfn_t gfn, gfn_t next, struct dat_walk *walk)
{
- __atomic64_or(PGSTE_CMMA_D_BIT, &pgste_of(ptep)->val);
+ union pgste pgste;
+
+ pgste = pgste_get_lock(ptep);
+ if (!pgste.cmma_d)
+ atomic64_inc(walk->priv);
+ pgste.cmma_d = 1;
+ pgste_set_unlock(ptep, pgste);
+
if (need_resched())
return next;
return 0;
}
-void gmap_set_cmma_all_dirty(struct gmap *gmap)
+void _gmap_set_cmma_all(struct gmap *gmap, bool dirty)
{
- const struct dat_walk_ops ops = { .pte_entry = __set_cmma_dirty_pte, };
+ const struct dat_walk_ops ops = {
+ .pte_entry = dirty ? __set_cmma_dirty_pte : __set_cmma_clean_pte,
+ };
gfn_t gfn = 0;
do {
scoped_guard(read_lock, &gmap->kvm->mmu_lock)
gfn = _dat_walk_gfn_range(gfn, asce_end(gmap->asce), gmap->asce, &ops,
- DAT_WALK_IGN_HOLES, NULL);
+ DAT_WALK_IGN_HOLES,
+ &gmap->kvm->arch.cmma_dirty_pages);
cond_resched();
} while (gfn);
}
@@ -1287,7 +1310,7 @@ static int gmap_protect_asce_top_level(struct kvm_s390_mmu_cache *mc, struct gma
/* Pairs with the smp_wmb() in kvm_mmu_invalidate_end(). */
smp_rmb();
- rc = kvm_s390_get_guest_pages(sg->kvm, context.f, asce.rsto, asce.dt + 1, false);
+ rc = kvm_s390_get_guest_pages(sg->kvm, context.f, asce.rsto, asce.tl + 1, false);
if (rc > 0)
rc = -EFAULT;
if (!rc)
diff --git a/arch/s390/kvm/gmap.h b/arch/s390/kvm/gmap.h
index 1c040472f56d..39938d363ec9 100644
--- a/arch/s390/kvm/gmap.h
+++ b/arch/s390/kvm/gmap.h
@@ -104,7 +104,7 @@ int gmap_insert_rmap(struct kvm_s390_mmu_cache *mc, struct gmap *sg, gfn_t p_gfn
gfn_t r_gfn, int level);
int gmap_protect_rmap(struct kvm_s390_mmu_cache *mc, struct gmap *sg, gfn_t p_gfn, gfn_t r_gfn,
kvm_pfn_t pfn, int level, bool wr);
-void gmap_set_cmma_all_dirty(struct gmap *gmap);
+void _gmap_set_cmma_all(struct gmap *gmap, bool dirty);
void _gmap_handle_vsie_unshadow_event(struct gmap *parent, gfn_t gfn);
struct gmap *gmap_create_shadow(struct kvm_s390_mmu_cache *mc, struct gmap *gmap,
union asce asce, int edat_level);
@@ -198,6 +198,16 @@ static inline bool pte_needs_unshadow(union pte oldpte, union pte newpte, union
return !newpte.h.p || !newpte.s.pr;
}
+static inline void gmap_set_cmma_all_dirty(struct gmap *gmap)
+{
+ _gmap_set_cmma_all(gmap, true);
+}
+
+static inline void gmap_set_cmma_all_clean(struct gmap *gmap)
+{
+ _gmap_set_cmma_all(gmap, false);
+}
+
static inline union pgste _gmap_ptep_xchg(struct gmap *gmap, union pte *ptep, union pte newpte,
union pgste pgste, gfn_t gfn, bool needs_lock)
{
diff --git a/arch/s390/kvm/kvm-s390.c b/arch/s390/kvm/kvm-s390.c
index dc9c7451fe97..23c817595e28 100644
--- a/arch/s390/kvm/kvm-s390.c
+++ b/arch/s390/kvm/kvm-s390.c
@@ -1022,9 +1022,11 @@ static int kvm_s390_set_mem_control(struct kvm *kvm, struct kvm_device_attr *att
if (!kvm->arch.use_cmma)
break;
+ guard(mutex)(&kvm->lock);
VM_EVENT(kvm, 3, "%s", "RESET: CMMA states");
do {
- start_gfn = dat_reset_cmma(kvm->arch.gmap->asce, start_gfn);
+ scoped_guard(read_lock, &kvm->mmu_lock)
+ start_gfn = dat_reset_cmma(kvm->arch.gmap->asce, start_gfn);
cond_resched();
} while (start_gfn);
ret = 0;
@@ -1217,13 +1219,13 @@ static void kvm_s390_sync_request_broadcast(struct kvm *kvm, int req)
/*
* Must be called with kvm->srcu held to avoid races on memslots, and with
- * kvm->slots_lock to avoid races with ourselves and kvm_s390_vm_stop_migration.
+ * kvm->slots_lock to avoid races with ourselves, kvm_s390_vm_stop_migration(),
+ * and kvm_s390_get_cmma_bits().
*/
static int kvm_s390_vm_start_migration(struct kvm *kvm)
{
struct kvm_memory_slot *ms;
struct kvm_memslots *slots;
- unsigned long ram_pages = 0;
int bkt;
/* migration mode already enabled */
@@ -1240,28 +1242,54 @@ static int kvm_s390_vm_start_migration(struct kvm *kvm)
kvm_for_each_memslot(ms, bkt, slots) {
if (!ms->dirty_bitmap)
return -EINVAL;
- ram_pages += ms->npages;
}
- /* mark all the pages as dirty */
- gmap_set_cmma_all_dirty(kvm->arch.gmap);
- atomic64_set(&kvm->arch.cmma_dirty_pages, ram_pages);
- kvm->arch.migration_mode = 1;
+ /*
+ * Set the flag and let KVM handle ESSA manually, potentially setting
+ * the cmma_d bit in some PGSTEs and increasing cmma_dirty_pages.
+ * At this point cmma_dirty_pages is still 0, and all existing PGSTEs
+ * have their cmma_d bit set to 0.
+ * Any newly allocated page table has its entries marked as cmma-clean,
+ * which is fine because the CMMA values are not dirty.
+ */
+ WRITE_ONCE(kvm->arch.migration_mode, 1);
kvm_s390_sync_request_broadcast(kvm, KVM_REQ_START_MIGRATION);
+ /*
+ * Mark all PGSTEs as cmma-dirty, increasing cmma_dirty_pages as needed,
+ * but without double-counting pages that have become dirty on their own
+ * in the meantime.
+ * At this point some pages might have become dirty on their own already
+ * and cmma_dirty_pages might therefore be non-zero.
+ */
+ gmap_set_cmma_all_dirty(kvm->arch.gmap);
return 0;
}
/*
- * Must be called with kvm->slots_lock to avoid races with ourselves and
- * kvm_s390_vm_start_migration.
+ * Must be called with kvm->slots_lock to avoid races with ourselves,
+ * kvm_s390_vm_start_migration() and kvm_s390_get_cmma_bits().
*/
static int kvm_s390_vm_stop_migration(struct kvm *kvm)
{
/* migration mode already disabled */
if (!kvm->arch.migration_mode)
return 0;
- kvm->arch.migration_mode = 0;
+ /*
+ * Unset the flag and propagate to all vCPUs. From now on the cmma_d
+ * bit will not be touched on any PGSTE.
+ * At this point cmma_dirty_pages is possibly non-zero, and thus some
+ * PGSTEs might have cmma_d set.
+ */
+ WRITE_ONCE(kvm->arch.migration_mode, 0);
if (kvm->arch.use_cmma)
kvm_s390_sync_request_broadcast(kvm, KVM_REQ_STOP_MIGRATION);
+ /* Clear cmma_d on all existing PGSTEs and set cmma_dirty_pages to 0. */
+ gmap_set_cmma_all_clean(kvm->arch.gmap);
+ atomic64_set(&kvm->arch.cmma_dirty_pages, 0);
+ /*
+ * At this point the system has the expected state: migration_mode is 0,
+ * cmma_dirty_pages is 0, and all existing PGSTEs have their cmma_d bit
+ * set to 0.
+ */
return 0;
}
@@ -2317,8 +2345,8 @@ static int kvm_s390_get_cmma_bits(struct kvm *kvm,
static int kvm_s390_set_cmma_bits(struct kvm *kvm,
const struct kvm_s390_cmma_log *args)
{
- struct kvm_s390_mmu_cache *mc;
- u8 *bits = NULL;
+ struct kvm_s390_mmu_cache *mc __free(kvm_s390_mmu_cache) = NULL;
+ u8 *bits __free(kvfree) = NULL;
int r = 0;
if (!kvm->arch.use_cmma)
@@ -2338,18 +2366,16 @@ static int kvm_s390_set_cmma_bits(struct kvm *kvm,
return -ENOMEM;
bits = vmalloc(array_size(sizeof(*bits), args->count));
if (!bits)
- goto out;
+ return -ENOMEM;
r = copy_from_user(bits, (void __user *)args->values, args->count);
- if (r) {
- r = -EFAULT;
- goto out;
- }
+ if (r)
+ return -EFAULT;
do {
r = kvm_s390_mmu_cache_topup(mc);
if (r)
- break;
+ return r;
scoped_guard(read_lock, &kvm->mmu_lock) {
r = dat_set_cmma_bits(mc, kvm->arch.gmap->asce, args->start_gfn,
args->count, args->mask, bits);
@@ -2357,9 +2383,7 @@ static int kvm_s390_set_cmma_bits(struct kvm *kvm,
} while (r == -ENOMEM);
set_bit(GMAP_FLAG_USES_CMM, &kvm->arch.gmap->flags);
-out:
- kvm_s390_free_mmu_cache(mc);
- vfree(bits);
+
return r;
}
@@ -3584,7 +3608,8 @@ void kvm_arch_vcpu_postcreate(struct kvm_vcpu *vcpu)
vcpu->arch.gmap = vcpu->kvm->arch.gmap;
sca_add_vcpu(vcpu);
}
- if (test_kvm_facility(vcpu->kvm, 74) || vcpu->kvm->arch.user_instr0)
+ if (test_kvm_facility(vcpu->kvm, 74) || vcpu->kvm->arch.user_instr0 ||
+ vcpu->kvm->arch.user_operexec)
vcpu->arch.sie_block->ictl |= ICTL_OPEREXC;
}
diff --git a/arch/s390/kvm/priv.c b/arch/s390/kvm/priv.c
index 447ec7ed423d..ad0ddc433a73 100644
--- a/arch/s390/kvm/priv.c
+++ b/arch/s390/kvm/priv.c
@@ -366,7 +366,9 @@ static int handle_sske(struct kvm_vcpu *vcpu)
if (rc > 1)
return kvm_s390_inject_program_int(vcpu, PGM_ADDRESSING);
if (rc == -ENOMEM) {
- kvm_s390_mmu_cache_topup(vcpu->arch.mc);
+ rc = kvm_s390_mmu_cache_topup(vcpu->arch.mc);
+ if (rc)
+ return rc;
continue;
}
if (rc < 0)
@@ -1122,7 +1124,9 @@ static int handle_pfmf(struct kvm_vcpu *vcpu)
if (rc > 1)
return kvm_s390_inject_program_int(vcpu, rc);
if (rc == -ENOMEM) {
- kvm_s390_mmu_cache_topup(vcpu->arch.mc);
+ rc = kvm_s390_mmu_cache_topup(vcpu->arch.mc);
+ if (rc)
+ return rc;
continue;
}
if (rc < 0)
@@ -1232,7 +1236,7 @@ static int handle_essa(struct kvm_vcpu *vcpu)
: ESSA_SET_STABLE_IF_RESIDENT))
return kvm_s390_inject_program_int(vcpu, PGM_SPECIFICATION);
- if (!vcpu->kvm->arch.migration_mode) {
+ if (!READ_ONCE(vcpu->kvm->arch.migration_mode)) {
/*
* CMMA is enabled in the KVM settings, but is disabled in
* the SIE block and in the mm_context, and we are not doing
diff --git a/arch/s390/mm/gmap_helpers.c b/arch/s390/mm/gmap_helpers.c
index ee3f37af8aee..4bf7c9012feb 100644
--- a/arch/s390/mm/gmap_helpers.c
+++ b/arch/s390/mm/gmap_helpers.c
@@ -181,7 +181,8 @@ void gmap_helper_try_set_pte_unused(struct mm_struct *mm, unsigned long vmaddr)
if (IS_ERR_OR_NULL(ptep))
return;
- __atomic64_or(_PAGE_UNUSED, (long *)ptep);
+ if (pte_present(*ptep))
+ __atomic64_or(_PAGE_UNUSED, (long *)ptep);
pte_unmap_unlock(ptep, ptl);
}
EXPORT_SYMBOL_GPL(gmap_helper_try_set_pte_unused);