diff options
| author | Paolo Bonzini <pbonzini@redhat.com> | 2026-06-24 13:40:39 +0200 |
|---|---|---|
| committer | Paolo Bonzini <pbonzini@redhat.com> | 2026-06-24 13:41:41 +0200 |
| commit | 91b16b53a08c3684ea2b0ad3cbf8ecd48c0f8b77 (patch) | |
| tree | 977e7a4d4484bf69d61d3a16aa33474f74f97c81 /arch/s390 | |
| parent | bb365a506b1e6fb050c0fceaad354fe395385ef0 (diff) | |
| parent | babe08404e1993697a523e60bc0f9d096ffe1ef8 (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/Kconfig | 3 | ||||
| -rw-r--r-- | arch/s390/include/asm/pgtable.h | 4 | ||||
| -rw-r--r-- | arch/s390/kvm/dat.c | 5 | ||||
| -rw-r--r-- | arch/s390/kvm/gmap.c | 35 | ||||
| -rw-r--r-- | arch/s390/kvm/gmap.h | 12 | ||||
| -rw-r--r-- | arch/s390/kvm/kvm-s390.c | 71 | ||||
| -rw-r--r-- | arch/s390/kvm/priv.c | 10 | ||||
| -rw-r--r-- | arch/s390/mm/gmap_helpers.c | 3 |
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); |
