Commit c9f721ed authored by Claudio Imbrenda's avatar Claudio Imbrenda
Browse files

KVM: s390: move some gmap shadowing functions away from mm/gmap.c



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: default avatarJanosch Frank <frankja@linux.ibm.com>
Reviewed-by: default avatarChristoph Schlameuss <schlameuss@linux.ibm.com>
Link: https://lore.kernel.org/r/20250123144627.312456-10-imbrenda@linux.ibm.com


Signed-off-by: default avatarClaudio Imbrenda <imbrenda@linux.ibm.com>
Message-ID: <20250123144627.312456-10-imbrenda@linux.ibm.com>
parent d41993f7
Loading
Loading
Loading
Loading
+4 −5
Original line number Diff line number Diff line
@@ -106,6 +106,8 @@ struct gmap *gmap_create(struct mm_struct *mm, unsigned long limit);
void gmap_remove(struct gmap *gmap);
struct gmap *gmap_get(struct gmap *gmap);
void gmap_put(struct gmap *gmap);
void gmap_free(struct gmap *gmap);
struct gmap *gmap_alloc(unsigned long limit);

int gmap_map_segment(struct gmap *gmap, unsigned long from,
		     unsigned long to, unsigned long len);
@@ -118,9 +120,7 @@ void gmap_unlink(struct mm_struct *, unsigned long *table, unsigned long vmaddr)

int gmap_read_table(struct gmap *gmap, unsigned long gaddr, unsigned long *val);

struct gmap *gmap_shadow(struct gmap *parent, unsigned long asce,
			 int edat_level);
int gmap_shadow_valid(struct gmap *sg, unsigned long asce, int edat_level);
void gmap_unshadow(struct gmap *sg);
int gmap_shadow_r2t(struct gmap *sg, unsigned long saddr, unsigned long r2t,
		    int fake);
int gmap_shadow_r3t(struct gmap *sg, unsigned long saddr, unsigned long r3t,
@@ -136,8 +136,7 @@ int gmap_shadow_page(struct gmap *sg, unsigned long saddr, pte_t pte);
void gmap_register_pte_notifier(struct gmap_notifier *);
void gmap_unregister_pte_notifier(struct gmap_notifier *);

int gmap_mprotect_notify(struct gmap *, unsigned long start,
			 unsigned long len, int prot);
int gmap_protect_one(struct gmap *gmap, unsigned long gaddr, int prot, unsigned long bits);

void gmap_sync_dirty_log_pmd(struct gmap *gmap, unsigned long dirty_bitmap[4],
			     unsigned long gaddr, unsigned long vmaddr);
+1 −1
Original line number Diff line number Diff line
@@ -8,7 +8,7 @@ include $(srctree)/virt/kvm/Makefile.kvm
ccflags-y := -Ivirt/kvm -Iarch/s390/kvm

kvm-y += kvm-s390.o intercept.o interrupt.o priv.o sigp.o
kvm-y += diag.o gaccess.o guestdbg.o vsie.o pv.o gmap.o
kvm-y += diag.o gaccess.o guestdbg.o vsie.o pv.o gmap.o gmap-vsie.o

kvm-$(CONFIG_VFIO_PCI_ZDEV_KVM) += pci.o
obj-$(CONFIG_KVM) += kvm.o
+142 −0
Original line number Diff line number Diff line
// 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;
}
+20 −0
Original line number Diff line number Diff line
@@ -13,5 +13,25 @@
int gmap_make_secure(struct gmap *gmap, unsigned long gaddr, void *uvcb);
int gmap_convert_to_secure(struct gmap *gmap, unsigned long gaddr);
int gmap_destroy_page(struct gmap *gmap, unsigned long gaddr);
struct gmap *gmap_shadow(struct gmap *parent, unsigned long asce, int edat_level);

/**
 * gmap_shadow_valid - check if a shadow guest address space matches the
 *                     given properties and is still valid
 * @sg: pointer to the shadow guest address space structure
 * @asce: ASCE for which the shadow table is requested
 * @edat_level: edat level to be used for the shadow translation
 *
 * Returns 1 if the gmap shadow is still valid and matches the given
 * properties, the caller can continue using it. Returns 0 otherwise, the
 * caller has to request a new shadow gmap in this case.
 *
 */
static inline int gmap_shadow_valid(struct gmap *sg, unsigned long asce, int edat_level)
{
	if (sg->removed)
		return 0;
	return sg->orig_asce == asce && sg->edat_level == edat_level;
}

#endif
+71 −3
Original line number Diff line number Diff line
@@ -4511,6 +4511,75 @@ static bool ibs_enabled(struct kvm_vcpu *vcpu)
	return kvm_s390_test_cpuflags(vcpu, CPUSTAT_IBS);
}

static int __kvm_s390_fixup_fault_sync(struct gmap *gmap, gpa_t gaddr, unsigned int flags)
{
	struct kvm *kvm = gmap->private;
	gfn_t gfn = gpa_to_gfn(gaddr);
	bool unlocked;
	hva_t vmaddr;
	gpa_t tmp;
	int rc;

	if (kvm_is_ucontrol(kvm)) {
		tmp = __gmap_translate(gmap, gaddr);
		gfn = gpa_to_gfn(tmp);
	}

	vmaddr = gfn_to_hva(kvm, gfn);
	rc = fixup_user_fault(gmap->mm, vmaddr, FAULT_FLAG_WRITE, &unlocked);
	if (!rc)
		rc = __gmap_link(gmap, gaddr, vmaddr);
	return rc;
}

/**
 * __kvm_s390_mprotect_many() - Apply specified protection to guest pages
 * @gmap: the gmap of the guest
 * @gpa: the starting guest address
 * @npages: how many pages to protect
 * @prot: indicates access rights: PROT_NONE, PROT_READ or PROT_WRITE
 * @bits: pgste notification bits to set
 *
 * Returns: 0 in case of success, < 0 in case of error - see gmap_protect_one()
 *
 * Context: kvm->srcu and gmap->mm need to be held in read mode
 */
int __kvm_s390_mprotect_many(struct gmap *gmap, gpa_t gpa, u8 npages, unsigned int prot,
			     unsigned long bits)
{
	unsigned int fault_flag = (prot & PROT_WRITE) ? FAULT_FLAG_WRITE : 0;
	gpa_t end = gpa + npages * PAGE_SIZE;
	int rc;

	for (; gpa < end; gpa = ALIGN(gpa + 1, rc)) {
		rc = gmap_protect_one(gmap, gpa, prot, bits);
		if (rc == -EAGAIN) {
			__kvm_s390_fixup_fault_sync(gmap, gpa, fault_flag);
			rc = gmap_protect_one(gmap, gpa, prot, bits);
		}
		if (rc < 0)
			return rc;
	}

	return 0;
}

static int kvm_s390_mprotect_notify_prefix(struct kvm_vcpu *vcpu)
{
	gpa_t gaddr = kvm_s390_get_prefix(vcpu);
	int idx, rc;

	idx = srcu_read_lock(&vcpu->kvm->srcu);
	mmap_read_lock(vcpu->arch.gmap->mm);

	rc = __kvm_s390_mprotect_many(vcpu->arch.gmap, gaddr, 2, PROT_WRITE, GMAP_NOTIFY_MPROT);

	mmap_read_unlock(vcpu->arch.gmap->mm);
	srcu_read_unlock(&vcpu->kvm->srcu, idx);

	return rc;
}

static int kvm_s390_handle_requests(struct kvm_vcpu *vcpu)
{
retry:
@@ -4526,9 +4595,8 @@ static int kvm_s390_handle_requests(struct kvm_vcpu *vcpu)
	 */
	if (kvm_check_request(KVM_REQ_REFRESH_GUEST_PREFIX, vcpu)) {
		int rc;
		rc = gmap_mprotect_notify(vcpu->arch.gmap,
					  kvm_s390_get_prefix(vcpu),
					  PAGE_SIZE * 2, PROT_WRITE);

		rc = kvm_s390_mprotect_notify_prefix(vcpu);
		if (rc) {
			kvm_make_request(KVM_REQ_REFRESH_GUEST_PREFIX, vcpu);
			return rc;
Loading