// SPDX-License-Identifier: GPL-2.0 /* * Guest memory management for KVM/s390 nested VMs. * * Copyright IBM Corp. 2008, 2020, 2024 * * Author(s): Claudio Imbrenda * Martin Schwidefsky * David Hildenbrand * Janosch Frank */ #include #include #include #include #include #include #include #include #include #include "kvm-s390.h" #include "gmap.h" /** * gmap_find_shadow - find a specific asce in the list of shadow tables * @parent: pointer to the parent gmap * @asce: ASCE for which the shadow table is created * @edat_level: edat level to be used for the shadow translation * * Returns the pointer to a gmap if a shadow table with the given asce is * already available, ERR_PTR(-EAGAIN) if another one is just being created, * otherwise NULL * * Context: Called with parent->shadow_lock held */ static struct gmap *gmap_find_shadow(struct gmap *parent, unsigned long asce, int edat_level) { struct gmap *sg; lockdep_assert_held(&parent->shadow_lock); list_for_each_entry(sg, &parent->children, list) { if (!gmap_shadow_valid(sg, asce, edat_level)) continue; if (!sg->initialized) return ERR_PTR(-EAGAIN); refcount_inc(&sg->ref_count); return sg; } return NULL; } /** * gmap_shadow - create/find a shadow guest address space * @parent: pointer to the parent gmap * @asce: ASCE for which the shadow table is created * @edat_level: edat level to be used for the shadow translation * * The pages of the top level page table referred by the asce parameter * will be set to read-only and marked in the PGSTEs of the kvm process. * The shadow table will be removed automatically on any change to the * PTE mapping for the source table. * * Returns a guest address space structure, ERR_PTR(-ENOMEM) if out of memory, * ERR_PTR(-EAGAIN) if the caller has to retry and ERR_PTR(-EFAULT) if the * parent gmap table could not be protected. */ struct gmap *gmap_shadow(struct gmap *parent, unsigned long asce, int edat_level) { struct gmap *sg, *new; unsigned long limit; int rc; if (KVM_BUG_ON(parent->mm->context.allow_gmap_hpage_1m, (struct kvm *)parent->private) || KVM_BUG_ON(gmap_is_shadow(parent), (struct kvm *)parent->private)) return ERR_PTR(-EFAULT); spin_lock(&parent->shadow_lock); sg = gmap_find_shadow(parent, asce, edat_level); spin_unlock(&parent->shadow_lock); if (sg) return sg; /* Create a new shadow gmap */ limit = -1UL >> (33 - (((asce & _ASCE_TYPE_MASK) >> 2) * 11)); if (asce & _ASCE_REAL_SPACE) limit = -1UL; new = gmap_alloc(limit); if (!new) return ERR_PTR(-ENOMEM); new->mm = parent->mm; new->parent = gmap_get(parent); new->private = parent->private; new->orig_asce = asce; new->edat_level = edat_level; new->initialized = false; spin_lock(&parent->shadow_lock); /* Recheck if another CPU created the same shadow */ sg = gmap_find_shadow(parent, asce, edat_level); if (sg) { spin_unlock(&parent->shadow_lock); gmap_free(new); return sg; } if (asce & _ASCE_REAL_SPACE) { /* only allow one real-space gmap shadow */ list_for_each_entry(sg, &parent->children, list) { if (sg->orig_asce & _ASCE_REAL_SPACE) { spin_lock(&sg->guest_table_lock); gmap_unshadow(sg); spin_unlock(&sg->guest_table_lock); list_del(&sg->list); gmap_put(sg); break; } } } refcount_set(&new->ref_count, 2); list_add(&new->list, &parent->children); if (asce & _ASCE_REAL_SPACE) { /* nothing to protect, return right away */ new->initialized = true; spin_unlock(&parent->shadow_lock); return new; } spin_unlock(&parent->shadow_lock); /* protect after insertion, so it will get properly invalidated */ mmap_read_lock(parent->mm); rc = __kvm_s390_mprotect_many(parent, asce & _ASCE_ORIGIN, ((asce & _ASCE_TABLE_LENGTH) + 1), PROT_READ, GMAP_NOTIFY_SHADOW); mmap_read_unlock(parent->mm); spin_lock(&parent->shadow_lock); new->initialized = true; if (rc) { list_del(&new->list); gmap_free(new); new = ERR_PTR(rc); } spin_unlock(&parent->shadow_lock); return new; }