Move some gmap shadowing functions from mm/gmap.c to kvm/kvm-s390.c and the newly created kvm/gmap-vsie.c This is a step toward removing gmap from mm. Reviewed-by: Janosch Frank <frankja@linux.ibm.com> Reviewed-by: Christoph Schlameuss <schlameuss@linux.ibm.com> Link: https://lore.kernel.org/r/20250123144627.312456-10-imbrenda@linux.ibm.com Signed-off-by: Claudio Imbrenda <imbrenda@linux.ibm.com> Message-ID: <20250123144627.312456-10-imbrenda@linux.ibm.com>
142 lines
4.2 KiB
C
142 lines
4.2 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Guest memory management for KVM/s390 nested VMs.
|
|
*
|
|
* Copyright IBM Corp. 2008, 2020, 2024
|
|
*
|
|
* Author(s): Claudio Imbrenda <imbrenda@linux.ibm.com>
|
|
* Martin Schwidefsky <schwidefsky@de.ibm.com>
|
|
* David Hildenbrand <david@redhat.com>
|
|
* Janosch Frank <frankja@linux.vnet.ibm.com>
|
|
*/
|
|
|
|
#include <linux/compiler.h>
|
|
#include <linux/kvm.h>
|
|
#include <linux/kvm_host.h>
|
|
#include <linux/pgtable.h>
|
|
#include <linux/pagemap.h>
|
|
#include <linux/mman.h>
|
|
|
|
#include <asm/lowcore.h>
|
|
#include <asm/gmap.h>
|
|
#include <asm/uv.h>
|
|
|
|
#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;
|
|
}
|