mirror of
https://github.com/torvalds/linux.git
synced 2026-07-27 09:36:22 +02:00
KVM: s390: Switch to new gmap
Switch KVM/s390 to use the new gmap code. Remove includes to <gmap.h> and include "gmap.h" instead; fix all the existing users of the old gmap functions to use the new ones instead. Fix guest storage key access functions to work with the new gmap. Acked-by: Heiko Carstens <hca@linux.ibm.com> Signed-off-by: Claudio Imbrenda <imbrenda@linux.ibm.com>
This commit is contained in:
parent
d29a29a9e1
commit
e38c884df9
|
|
@ -33,7 +33,7 @@ config GENERIC_LOCKBREAK
|
||||||
def_bool y if PREEMPTION
|
def_bool y if PREEMPTION
|
||||||
|
|
||||||
config PGSTE
|
config PGSTE
|
||||||
def_bool y if KVM
|
def_bool n
|
||||||
|
|
||||||
config AUDIT_ARCH
|
config AUDIT_ARCH
|
||||||
def_bool y
|
def_bool y
|
||||||
|
|
|
||||||
|
|
@ -442,7 +442,7 @@ struct kvm_vcpu_arch {
|
||||||
bool acrs_loaded;
|
bool acrs_loaded;
|
||||||
struct kvm_s390_pv_vcpu pv;
|
struct kvm_s390_pv_vcpu pv;
|
||||||
union diag318_info diag318_info;
|
union diag318_info diag318_info;
|
||||||
void *mc; /* Placeholder */
|
struct kvm_s390_mmu_cache *mc;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct kvm_vm_stat {
|
struct kvm_vm_stat {
|
||||||
|
|
@ -636,6 +636,8 @@ struct kvm_s390_pv {
|
||||||
struct mutex import_lock;
|
struct mutex import_lock;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct kvm_s390_mmu_cache;
|
||||||
|
|
||||||
struct kvm_arch {
|
struct kvm_arch {
|
||||||
struct esca_block *sca;
|
struct esca_block *sca;
|
||||||
debug_info_t *dbf;
|
debug_info_t *dbf;
|
||||||
|
|
@ -675,6 +677,7 @@ struct kvm_arch {
|
||||||
struct kvm_s390_pv pv;
|
struct kvm_s390_pv pv;
|
||||||
struct list_head kzdev_list;
|
struct list_head kzdev_list;
|
||||||
spinlock_t kzdev_list_lock;
|
spinlock_t kzdev_list_lock;
|
||||||
|
struct kvm_s390_mmu_cache *mc;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define KVM_HVA_ERR_BAD (-1UL)
|
#define KVM_HVA_ERR_BAD (-1UL)
|
||||||
|
|
|
||||||
|
|
@ -30,11 +30,7 @@ static inline int init_new_context(struct task_struct *tsk,
|
||||||
mm->context.gmap_asce = 0;
|
mm->context.gmap_asce = 0;
|
||||||
mm->context.flush_mm = 0;
|
mm->context.flush_mm = 0;
|
||||||
#if IS_ENABLED(CONFIG_KVM)
|
#if IS_ENABLED(CONFIG_KVM)
|
||||||
mm->context.has_pgste = 0;
|
|
||||||
mm->context.uses_skeys = 0;
|
|
||||||
mm->context.uses_cmm = 0;
|
|
||||||
mm->context.allow_cow_sharing = 1;
|
mm->context.allow_cow_sharing = 1;
|
||||||
mm->context.allow_gmap_hpage_1m = 0;
|
|
||||||
#endif
|
#endif
|
||||||
switch (mm->context.asce_limit) {
|
switch (mm->context.asce_limit) {
|
||||||
default:
|
default:
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,6 @@ static inline bool __tlb_remove_folio_pages(struct mmu_gather *tlb,
|
||||||
|
|
||||||
#include <asm/tlbflush.h>
|
#include <asm/tlbflush.h>
|
||||||
#include <asm-generic/tlb.h>
|
#include <asm-generic/tlb.h>
|
||||||
#include <asm/gmap.h>
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Release the page cache reference for a pte removed by
|
* Release the page cache reference for a pte removed by
|
||||||
|
|
@ -85,8 +84,6 @@ static inline void pte_free_tlb(struct mmu_gather *tlb, pgtable_t pte,
|
||||||
tlb->mm->context.flush_mm = 1;
|
tlb->mm->context.flush_mm = 1;
|
||||||
tlb->freed_tables = 1;
|
tlb->freed_tables = 1;
|
||||||
tlb->cleared_pmds = 1;
|
tlb->cleared_pmds = 1;
|
||||||
if (mm_has_pgste(tlb->mm))
|
|
||||||
gmap_unlink(tlb->mm, (unsigned long *)pte, address);
|
|
||||||
tlb_remove_ptdesc(tlb, virt_to_ptdesc(pte));
|
tlb_remove_ptdesc(tlb, virt_to_ptdesc(pte));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -471,65 +471,15 @@ do { \
|
||||||
#define arch_get_kernel_nofault __mvc_kernel_nofault
|
#define arch_get_kernel_nofault __mvc_kernel_nofault
|
||||||
#define arch_put_kernel_nofault __mvc_kernel_nofault
|
#define arch_put_kernel_nofault __mvc_kernel_nofault
|
||||||
|
|
||||||
void __cmpxchg_user_key_called_with_bad_pointer(void);
|
int __cmpxchg_key1(void *address, unsigned char *uval, unsigned char old,
|
||||||
|
unsigned char new, unsigned long key);
|
||||||
int __cmpxchg_user_key1(unsigned long address, unsigned char *uval,
|
int __cmpxchg_key2(void *address, unsigned short *uval, unsigned short old,
|
||||||
unsigned char old, unsigned char new, unsigned long key);
|
unsigned short new, unsigned long key);
|
||||||
int __cmpxchg_user_key2(unsigned long address, unsigned short *uval,
|
int __cmpxchg_key4(void *address, unsigned int *uval, unsigned int old,
|
||||||
unsigned short old, unsigned short new, unsigned long key);
|
unsigned int new, unsigned long key);
|
||||||
int __cmpxchg_user_key4(unsigned long address, unsigned int *uval,
|
int __cmpxchg_key8(void *address, unsigned long *uval, unsigned long old,
|
||||||
unsigned int old, unsigned int new, unsigned long key);
|
unsigned long new, unsigned long key);
|
||||||
int __cmpxchg_user_key8(unsigned long address, unsigned long *uval,
|
int __cmpxchg_key16(void *address, __uint128_t *uval, __uint128_t old,
|
||||||
unsigned long old, unsigned long new, unsigned long key);
|
__uint128_t new, unsigned long key);
|
||||||
int __cmpxchg_user_key16(unsigned long address, __uint128_t *uval,
|
|
||||||
__uint128_t old, __uint128_t new, unsigned long key);
|
|
||||||
|
|
||||||
static __always_inline int _cmpxchg_user_key(unsigned long address, void *uval,
|
|
||||||
__uint128_t old, __uint128_t new,
|
|
||||||
unsigned long key, int size)
|
|
||||||
{
|
|
||||||
switch (size) {
|
|
||||||
case 1: return __cmpxchg_user_key1(address, uval, old, new, key);
|
|
||||||
case 2: return __cmpxchg_user_key2(address, uval, old, new, key);
|
|
||||||
case 4: return __cmpxchg_user_key4(address, uval, old, new, key);
|
|
||||||
case 8: return __cmpxchg_user_key8(address, uval, old, new, key);
|
|
||||||
case 16: return __cmpxchg_user_key16(address, uval, old, new, key);
|
|
||||||
default: __cmpxchg_user_key_called_with_bad_pointer();
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* cmpxchg_user_key() - cmpxchg with user space target, honoring storage keys
|
|
||||||
* @ptr: User space address of value to compare to @old and exchange with
|
|
||||||
* @new. Must be aligned to sizeof(*@ptr).
|
|
||||||
* @uval: Address where the old value of *@ptr is written to.
|
|
||||||
* @old: Old value. Compared to the content pointed to by @ptr in order to
|
|
||||||
* determine if the exchange occurs. The old value read from *@ptr is
|
|
||||||
* written to *@uval.
|
|
||||||
* @new: New value to place at *@ptr.
|
|
||||||
* @key: Access key to use for checking storage key protection.
|
|
||||||
*
|
|
||||||
* Perform a cmpxchg on a user space target, honoring storage key protection.
|
|
||||||
* @key alone determines how key checking is performed, neither
|
|
||||||
* storage-protection-override nor fetch-protection-override apply.
|
|
||||||
* The caller must compare *@uval and @old to determine if values have been
|
|
||||||
* exchanged. In case of an exception *@uval is set to zero.
|
|
||||||
*
|
|
||||||
* Return: 0: cmpxchg executed
|
|
||||||
* -EFAULT: an exception happened when trying to access *@ptr
|
|
||||||
* -EAGAIN: maxed out number of retries (byte and short only)
|
|
||||||
*/
|
|
||||||
#define cmpxchg_user_key(ptr, uval, old, new, key) \
|
|
||||||
({ \
|
|
||||||
__typeof__(ptr) __ptr = (ptr); \
|
|
||||||
__typeof__(uval) __uval = (uval); \
|
|
||||||
\
|
|
||||||
BUILD_BUG_ON(sizeof(*(__ptr)) != sizeof(*(__uval))); \
|
|
||||||
might_fault(); \
|
|
||||||
__chk_user_ptr(__ptr); \
|
|
||||||
_cmpxchg_user_key((unsigned long)(__ptr), (void *)(__uval), \
|
|
||||||
(old), (new), (key), sizeof(*(__ptr))); \
|
|
||||||
})
|
|
||||||
|
|
||||||
#endif /* __S390_UACCESS_H */
|
#endif /* __S390_UACCESS_H */
|
||||||
|
|
|
||||||
|
|
@ -631,7 +631,6 @@ int uv_pin_shared(unsigned long paddr);
|
||||||
int uv_destroy_folio(struct folio *folio);
|
int uv_destroy_folio(struct folio *folio);
|
||||||
int uv_destroy_pte(pte_t pte);
|
int uv_destroy_pte(pte_t pte);
|
||||||
int uv_convert_from_secure_pte(pte_t pte);
|
int uv_convert_from_secure_pte(pte_t pte);
|
||||||
int make_hva_secure(struct mm_struct *mm, unsigned long hva, struct uv_cb_header *uvcb);
|
|
||||||
int s390_wiggle_split_folio(struct mm_struct *mm, struct folio *folio);
|
int s390_wiggle_split_folio(struct mm_struct *mm, struct folio *folio);
|
||||||
int __make_folio_secure(struct folio *folio, struct uv_cb_header *uvcb);
|
int __make_folio_secure(struct folio *folio, struct uv_cb_header *uvcb);
|
||||||
int uv_convert_from_secure(unsigned long paddr);
|
int uv_convert_from_secure(unsigned long paddr);
|
||||||
|
|
|
||||||
|
|
@ -209,39 +209,6 @@ int uv_convert_from_secure_pte(pte_t pte)
|
||||||
return uv_convert_from_secure_folio(pfn_folio(pte_pfn(pte)));
|
return uv_convert_from_secure_folio(pfn_folio(pte_pfn(pte)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* should_export_before_import - Determine whether an export is needed
|
|
||||||
* before an import-like operation
|
|
||||||
* @uvcb: the Ultravisor control block of the UVC to be performed
|
|
||||||
* @mm: the mm of the process
|
|
||||||
*
|
|
||||||
* Returns whether an export is needed before every import-like operation.
|
|
||||||
* This is needed for shared pages, which don't trigger a secure storage
|
|
||||||
* exception when accessed from a different guest.
|
|
||||||
*
|
|
||||||
* Although considered as one, the Unpin Page UVC is not an actual import,
|
|
||||||
* so it is not affected.
|
|
||||||
*
|
|
||||||
* No export is needed also when there is only one protected VM, because the
|
|
||||||
* page cannot belong to the wrong VM in that case (there is no "other VM"
|
|
||||||
* it can belong to).
|
|
||||||
*
|
|
||||||
* Return: true if an export is needed before every import, otherwise false.
|
|
||||||
*/
|
|
||||||
static bool should_export_before_import(struct uv_cb_header *uvcb, struct mm_struct *mm)
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* The misc feature indicates, among other things, that importing a
|
|
||||||
* shared page from a different protected VM will automatically also
|
|
||||||
* transfer its ownership.
|
|
||||||
*/
|
|
||||||
if (uv_has_feature(BIT_UV_FEAT_MISC))
|
|
||||||
return false;
|
|
||||||
if (uvcb->cmd == UVC_CMD_UNPIN_PAGE_SHARED)
|
|
||||||
return false;
|
|
||||||
return atomic_read(&mm->context.protected_count) > 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Calculate the expected ref_count for a folio that would otherwise have no
|
* Calculate the expected ref_count for a folio that would otherwise have no
|
||||||
* further pins. This was cribbed from similar functions in other places in
|
* further pins. This was cribbed from similar functions in other places in
|
||||||
|
|
@ -313,20 +280,6 @@ int __make_folio_secure(struct folio *folio, struct uv_cb_header *uvcb)
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(__make_folio_secure);
|
EXPORT_SYMBOL(__make_folio_secure);
|
||||||
|
|
||||||
static int make_folio_secure(struct mm_struct *mm, struct folio *folio, struct uv_cb_header *uvcb)
|
|
||||||
{
|
|
||||||
int rc;
|
|
||||||
|
|
||||||
if (!folio_trylock(folio))
|
|
||||||
return -EAGAIN;
|
|
||||||
if (should_export_before_import(uvcb, mm))
|
|
||||||
uv_convert_from_secure(folio_to_phys(folio));
|
|
||||||
rc = __make_folio_secure(folio, uvcb);
|
|
||||||
folio_unlock(folio);
|
|
||||||
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* s390_wiggle_split_folio() - try to drain extra references to a folio and
|
* s390_wiggle_split_folio() - try to drain extra references to a folio and
|
||||||
* split the folio if it is large.
|
* split the folio if it is large.
|
||||||
|
|
@ -414,56 +367,6 @@ int s390_wiggle_split_folio(struct mm_struct *mm, struct folio *folio)
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(s390_wiggle_split_folio);
|
EXPORT_SYMBOL_GPL(s390_wiggle_split_folio);
|
||||||
|
|
||||||
int make_hva_secure(struct mm_struct *mm, unsigned long hva, struct uv_cb_header *uvcb)
|
|
||||||
{
|
|
||||||
struct vm_area_struct *vma;
|
|
||||||
struct folio_walk fw;
|
|
||||||
struct folio *folio;
|
|
||||||
int rc;
|
|
||||||
|
|
||||||
mmap_read_lock(mm);
|
|
||||||
vma = vma_lookup(mm, hva);
|
|
||||||
if (!vma) {
|
|
||||||
mmap_read_unlock(mm);
|
|
||||||
return -EFAULT;
|
|
||||||
}
|
|
||||||
folio = folio_walk_start(&fw, vma, hva, 0);
|
|
||||||
if (!folio) {
|
|
||||||
mmap_read_unlock(mm);
|
|
||||||
return -ENXIO;
|
|
||||||
}
|
|
||||||
|
|
||||||
folio_get(folio);
|
|
||||||
/*
|
|
||||||
* Secure pages cannot be huge and userspace should not combine both.
|
|
||||||
* In case userspace does it anyway this will result in an -EFAULT for
|
|
||||||
* the unpack. The guest is thus never reaching secure mode.
|
|
||||||
* If userspace plays dirty tricks and decides to map huge pages at a
|
|
||||||
* later point in time, it will receive a segmentation fault or
|
|
||||||
* KVM_RUN will return -EFAULT.
|
|
||||||
*/
|
|
||||||
if (folio_test_hugetlb(folio))
|
|
||||||
rc = -EFAULT;
|
|
||||||
else if (folio_test_large(folio))
|
|
||||||
rc = -E2BIG;
|
|
||||||
else if (!pte_write(fw.pte) || (pte_val(fw.pte) & _PAGE_INVALID))
|
|
||||||
rc = -ENXIO;
|
|
||||||
else
|
|
||||||
rc = make_folio_secure(mm, folio, uvcb);
|
|
||||||
folio_walk_end(&fw, vma);
|
|
||||||
mmap_read_unlock(mm);
|
|
||||||
|
|
||||||
if (rc == -E2BIG || rc == -EBUSY) {
|
|
||||||
rc = s390_wiggle_split_folio(mm, folio);
|
|
||||||
if (!rc)
|
|
||||||
rc = -EAGAIN;
|
|
||||||
}
|
|
||||||
folio_put(folio);
|
|
||||||
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
EXPORT_SYMBOL_GPL(make_hva_secure);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* To be called with the folio locked or with an extra reference! This will
|
* To be called with the folio locked or with an extra reference! This will
|
||||||
* prevent kvm_s390_pv_make_secure() from touching the folio concurrently.
|
* prevent kvm_s390_pv_make_secure() from touching the folio concurrently.
|
||||||
|
|
@ -474,21 +377,18 @@ int arch_make_folio_accessible(struct folio *folio)
|
||||||
{
|
{
|
||||||
int rc = 0;
|
int rc = 0;
|
||||||
|
|
||||||
/* Large folios cannot be secure */
|
|
||||||
if (unlikely(folio_test_large(folio)))
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* PG_arch_1 is used in 2 places:
|
* PG_arch_1 is used as an indication that this small folio might be
|
||||||
* 1. for storage keys of hugetlb folios and KVM
|
* secure. This can overindicate, e.g. we set the bit before calling
|
||||||
* 2. As an indication that this small folio might be secure. This can
|
* convert_to_secure.
|
||||||
* overindicate, e.g. we set the bit before calling
|
|
||||||
* convert_to_secure.
|
|
||||||
* As secure pages are never large folios, both variants can co-exists.
|
|
||||||
*/
|
*/
|
||||||
if (!test_bit(PG_arch_1, &folio->flags.f))
|
if (!test_bit(PG_arch_1, &folio->flags.f))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
/* Large folios cannot be secure. */
|
||||||
|
if (WARN_ON_ONCE(folio_test_large(folio)))
|
||||||
|
return -EFAULT;
|
||||||
|
|
||||||
rc = uv_pin_shared(folio_to_phys(folio));
|
rc = uv_pin_shared(folio_to_phys(folio));
|
||||||
if (!rc) {
|
if (!rc) {
|
||||||
clear_bit(PG_arch_1, &folio->flags.f);
|
clear_bit(PG_arch_1, &folio->flags.f);
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ include $(srctree)/virt/kvm/Makefile.kvm
|
||||||
ccflags-y := -Ivirt/kvm -Iarch/s390/kvm
|
ccflags-y := -Ivirt/kvm -Iarch/s390/kvm
|
||||||
|
|
||||||
kvm-y += kvm-s390.o intercept.o interrupt.o priv.o sigp.o
|
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-vsie.o
|
kvm-y += diag.o gaccess.o guestdbg.o vsie.o pv.o
|
||||||
kvm-y += dat.o gmap.o faultin.o
|
kvm-y += dat.o gmap.o faultin.o
|
||||||
|
|
||||||
kvm-$(CONFIG_VFIO_PCI_ZDEV_KVM) += pci.o
|
kvm-$(CONFIG_VFIO_PCI_ZDEV_KVM) += pci.o
|
||||||
|
|
|
||||||
|
|
@ -10,13 +10,13 @@
|
||||||
|
|
||||||
#include <linux/kvm.h>
|
#include <linux/kvm.h>
|
||||||
#include <linux/kvm_host.h>
|
#include <linux/kvm_host.h>
|
||||||
#include <asm/gmap.h>
|
|
||||||
#include <asm/gmap_helpers.h>
|
#include <asm/gmap_helpers.h>
|
||||||
#include <asm/virtio-ccw.h>
|
#include <asm/virtio-ccw.h>
|
||||||
#include "kvm-s390.h"
|
#include "kvm-s390.h"
|
||||||
#include "trace.h"
|
#include "trace.h"
|
||||||
#include "trace-s390.h"
|
#include "trace-s390.h"
|
||||||
#include "gaccess.h"
|
#include "gaccess.h"
|
||||||
|
#include "gmap.h"
|
||||||
|
|
||||||
static void do_discard_gfn_range(struct kvm_vcpu *vcpu, gfn_t gfn_start, gfn_t gfn_end)
|
static void do_discard_gfn_range(struct kvm_vcpu *vcpu, gfn_t gfn_start, gfn_t gfn_end)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -206,7 +206,7 @@ int access_guest_with_key(struct kvm_vcpu *vcpu, unsigned long ga, u8 ar,
|
||||||
int access_guest_real(struct kvm_vcpu *vcpu, unsigned long gra,
|
int access_guest_real(struct kvm_vcpu *vcpu, unsigned long gra,
|
||||||
void *data, unsigned long len, enum gacc_mode mode);
|
void *data, unsigned long len, enum gacc_mode mode);
|
||||||
|
|
||||||
int cmpxchg_guest_abs_with_key(struct kvm *kvm, gpa_t gpa, int len, union kvm_s390_quad *old_addr,
|
int cmpxchg_guest_abs_with_key(struct kvm *kvm, gpa_t gpa, int len, union kvm_s390_quad *old,
|
||||||
union kvm_s390_quad new, u8 access_key, bool *success);
|
union kvm_s390_quad new, u8 access_key, bool *success);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -450,11 +450,17 @@ void ipte_unlock(struct kvm *kvm);
|
||||||
int ipte_lock_held(struct kvm *kvm);
|
int ipte_lock_held(struct kvm *kvm);
|
||||||
int kvm_s390_check_low_addr_prot_real(struct kvm_vcpu *vcpu, unsigned long gra);
|
int kvm_s390_check_low_addr_prot_real(struct kvm_vcpu *vcpu, unsigned long gra);
|
||||||
|
|
||||||
/* MVPG PEI indication bits */
|
union mvpg_pei {
|
||||||
#define PEI_DAT_PROT 2
|
unsigned long val;
|
||||||
#define PEI_NOT_PTE 4
|
struct {
|
||||||
|
unsigned long addr : 61;
|
||||||
|
unsigned long not_pte : 1;
|
||||||
|
unsigned long dat_prot: 1;
|
||||||
|
unsigned long real : 1;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
int kvm_s390_shadow_fault(struct kvm_vcpu *vcpu, struct gmap *shadow,
|
int gaccess_shadow_fault(struct kvm_vcpu *vcpu, struct gmap *sg, gpa_t saddr,
|
||||||
unsigned long saddr, unsigned long *datptr);
|
union mvpg_pei *datptr, bool wr);
|
||||||
|
|
||||||
#endif /* __KVM_S390_GACCESS_H */
|
#endif /* __KVM_S390_GACCESS_H */
|
||||||
|
|
|
||||||
|
|
@ -1,141 +0,0 @@
|
||||||
// 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"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
|
|
@ -21,6 +21,7 @@
|
||||||
#include "gaccess.h"
|
#include "gaccess.h"
|
||||||
#include "trace.h"
|
#include "trace.h"
|
||||||
#include "trace-s390.h"
|
#include "trace-s390.h"
|
||||||
|
#include "faultin.h"
|
||||||
|
|
||||||
u8 kvm_s390_get_ilen(struct kvm_vcpu *vcpu)
|
u8 kvm_s390_get_ilen(struct kvm_vcpu *vcpu)
|
||||||
{
|
{
|
||||||
|
|
@ -367,8 +368,11 @@ static int handle_mvpg_pei(struct kvm_vcpu *vcpu)
|
||||||
reg2, &srcaddr, GACC_FETCH, 0);
|
reg2, &srcaddr, GACC_FETCH, 0);
|
||||||
if (rc)
|
if (rc)
|
||||||
return kvm_s390_inject_prog_cond(vcpu, rc);
|
return kvm_s390_inject_prog_cond(vcpu, rc);
|
||||||
rc = kvm_s390_handle_dat_fault(vcpu, srcaddr, 0);
|
|
||||||
if (rc != 0)
|
do {
|
||||||
|
rc = kvm_s390_faultin_gfn_simple(vcpu, NULL, gpa_to_gfn(srcaddr), false);
|
||||||
|
} while (rc == -EAGAIN);
|
||||||
|
if (rc)
|
||||||
return rc;
|
return rc;
|
||||||
|
|
||||||
/* Ensure that the source is paged-in, no actual access -> no key checking */
|
/* Ensure that the source is paged-in, no actual access -> no key checking */
|
||||||
|
|
@ -376,8 +380,11 @@ static int handle_mvpg_pei(struct kvm_vcpu *vcpu)
|
||||||
reg1, &dstaddr, GACC_STORE, 0);
|
reg1, &dstaddr, GACC_STORE, 0);
|
||||||
if (rc)
|
if (rc)
|
||||||
return kvm_s390_inject_prog_cond(vcpu, rc);
|
return kvm_s390_inject_prog_cond(vcpu, rc);
|
||||||
rc = kvm_s390_handle_dat_fault(vcpu, dstaddr, FOLL_WRITE);
|
|
||||||
if (rc != 0)
|
do {
|
||||||
|
rc = kvm_s390_faultin_gfn_simple(vcpu, NULL, gpa_to_gfn(dstaddr), true);
|
||||||
|
} while (rc == -EAGAIN);
|
||||||
|
if (rc)
|
||||||
return rc;
|
return rc;
|
||||||
|
|
||||||
kvm_s390_retry_instr(vcpu);
|
kvm_s390_retry_instr(vcpu);
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@
|
||||||
#include <linux/uaccess.h>
|
#include <linux/uaccess.h>
|
||||||
#include <asm/sclp.h>
|
#include <asm/sclp.h>
|
||||||
#include <asm/isc.h>
|
#include <asm/isc.h>
|
||||||
#include <asm/gmap.h>
|
|
||||||
#include <asm/nmi.h>
|
#include <asm/nmi.h>
|
||||||
#include <asm/airq.h>
|
#include <asm/airq.h>
|
||||||
#include <asm/tpi.h>
|
#include <asm/tpi.h>
|
||||||
|
|
@ -34,6 +33,7 @@
|
||||||
#include "gaccess.h"
|
#include "gaccess.h"
|
||||||
#include "trace-s390.h"
|
#include "trace-s390.h"
|
||||||
#include "pci.h"
|
#include "pci.h"
|
||||||
|
#include "gmap.h"
|
||||||
|
|
||||||
#define PFAULT_INIT 0x0600
|
#define PFAULT_INIT 0x0600
|
||||||
#define PFAULT_DONE 0x0680
|
#define PFAULT_DONE 0x0680
|
||||||
|
|
@ -2632,12 +2632,12 @@ static int flic_set_attr(struct kvm_device *dev, struct kvm_device_attr *attr)
|
||||||
case KVM_DEV_FLIC_APF_ENABLE:
|
case KVM_DEV_FLIC_APF_ENABLE:
|
||||||
if (kvm_is_ucontrol(dev->kvm))
|
if (kvm_is_ucontrol(dev->kvm))
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
dev->kvm->arch.gmap->pfault_enabled = 1;
|
set_bit(GMAP_FLAG_PFAULT_ENABLED, &dev->kvm->arch.gmap->flags);
|
||||||
break;
|
break;
|
||||||
case KVM_DEV_FLIC_APF_DISABLE_WAIT:
|
case KVM_DEV_FLIC_APF_DISABLE_WAIT:
|
||||||
if (kvm_is_ucontrol(dev->kvm))
|
if (kvm_is_ucontrol(dev->kvm))
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
dev->kvm->arch.gmap->pfault_enabled = 0;
|
clear_bit(GMAP_FLAG_PFAULT_ENABLED, &dev->kvm->arch.gmap->flags);
|
||||||
/*
|
/*
|
||||||
* Make sure no async faults are in transition when
|
* Make sure no async faults are in transition when
|
||||||
* clearing the queues. So we don't need to worry
|
* clearing the queues. So we don't need to worry
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -19,6 +19,8 @@
|
||||||
#include <asm/facility.h>
|
#include <asm/facility.h>
|
||||||
#include <asm/processor.h>
|
#include <asm/processor.h>
|
||||||
#include <asm/sclp.h>
|
#include <asm/sclp.h>
|
||||||
|
#include "dat.h"
|
||||||
|
#include "gmap.h"
|
||||||
|
|
||||||
#define KVM_S390_UCONTROL_MEMSLOT (KVM_USER_MEM_SLOTS + 0)
|
#define KVM_S390_UCONTROL_MEMSLOT (KVM_USER_MEM_SLOTS + 0)
|
||||||
|
|
||||||
|
|
@ -114,9 +116,7 @@ static inline int is_vcpu_idle(struct kvm_vcpu *vcpu)
|
||||||
static inline int kvm_is_ucontrol(struct kvm *kvm)
|
static inline int kvm_is_ucontrol(struct kvm *kvm)
|
||||||
{
|
{
|
||||||
#ifdef CONFIG_KVM_S390_UCONTROL
|
#ifdef CONFIG_KVM_S390_UCONTROL
|
||||||
if (kvm->arch.gmap)
|
return test_bit(GMAP_FLAG_IS_UCONTROL, &kvm->arch.gmap->flags);
|
||||||
return 0;
|
|
||||||
return 1;
|
|
||||||
#else
|
#else
|
||||||
return 0;
|
return 0;
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -440,14 +440,9 @@ int kvm_s390_skey_check_enable(struct kvm_vcpu *vcpu);
|
||||||
/* implemented in vsie.c */
|
/* implemented in vsie.c */
|
||||||
int kvm_s390_handle_vsie(struct kvm_vcpu *vcpu);
|
int kvm_s390_handle_vsie(struct kvm_vcpu *vcpu);
|
||||||
void kvm_s390_vsie_kick(struct kvm_vcpu *vcpu);
|
void kvm_s390_vsie_kick(struct kvm_vcpu *vcpu);
|
||||||
void kvm_s390_vsie_gmap_notifier(struct gmap *gmap, unsigned long start,
|
void kvm_s390_vsie_gmap_notifier(struct gmap *gmap, gpa_t start, gpa_t end);
|
||||||
unsigned long end);
|
|
||||||
void kvm_s390_vsie_init(struct kvm *kvm);
|
void kvm_s390_vsie_init(struct kvm *kvm);
|
||||||
void kvm_s390_vsie_destroy(struct kvm *kvm);
|
void kvm_s390_vsie_destroy(struct kvm *kvm);
|
||||||
int gmap_shadow_valid(struct gmap *sg, unsigned long asce, int edat_level);
|
|
||||||
|
|
||||||
/* implemented in gmap-vsie.c */
|
|
||||||
struct gmap *gmap_shadow(struct gmap *parent, unsigned long asce, int edat_level);
|
|
||||||
|
|
||||||
/* implemented in sigp.c */
|
/* implemented in sigp.c */
|
||||||
int kvm_s390_handle_sigp(struct kvm_vcpu *vcpu);
|
int kvm_s390_handle_sigp(struct kvm_vcpu *vcpu);
|
||||||
|
|
@ -469,15 +464,9 @@ void kvm_s390_vcpu_unsetup_cmma(struct kvm_vcpu *vcpu);
|
||||||
void kvm_s390_set_cpu_timer(struct kvm_vcpu *vcpu, __u64 cputm);
|
void kvm_s390_set_cpu_timer(struct kvm_vcpu *vcpu, __u64 cputm);
|
||||||
__u64 kvm_s390_get_cpu_timer(struct kvm_vcpu *vcpu);
|
__u64 kvm_s390_get_cpu_timer(struct kvm_vcpu *vcpu);
|
||||||
int kvm_s390_cpus_from_pv(struct kvm *kvm, u16 *rc, u16 *rrc);
|
int kvm_s390_cpus_from_pv(struct kvm *kvm, u16 *rc, u16 *rrc);
|
||||||
int __kvm_s390_handle_dat_fault(struct kvm_vcpu *vcpu, gfn_t gfn, gpa_t gaddr, unsigned int flags);
|
|
||||||
int __kvm_s390_mprotect_many(struct gmap *gmap, gpa_t gpa, u8 npages, unsigned int prot,
|
int __kvm_s390_mprotect_many(struct gmap *gmap, gpa_t gpa, u8 npages, unsigned int prot,
|
||||||
unsigned long bits);
|
unsigned long bits);
|
||||||
|
|
||||||
static inline int kvm_s390_handle_dat_fault(struct kvm_vcpu *vcpu, gpa_t gaddr, unsigned int flags)
|
|
||||||
{
|
|
||||||
return __kvm_s390_handle_dat_fault(vcpu, gpa_to_gfn(gaddr), gaddr, flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool kvm_arch_setup_async_pf(struct kvm_vcpu *vcpu);
|
bool kvm_arch_setup_async_pf(struct kvm_vcpu *vcpu);
|
||||||
|
|
||||||
/* implemented in diag.c */
|
/* implemented in diag.c */
|
||||||
|
|
|
||||||
|
|
@ -21,13 +21,14 @@
|
||||||
#include <asm/ebcdic.h>
|
#include <asm/ebcdic.h>
|
||||||
#include <asm/sysinfo.h>
|
#include <asm/sysinfo.h>
|
||||||
#include <asm/page-states.h>
|
#include <asm/page-states.h>
|
||||||
#include <asm/gmap.h>
|
|
||||||
#include <asm/ptrace.h>
|
#include <asm/ptrace.h>
|
||||||
#include <asm/sclp.h>
|
#include <asm/sclp.h>
|
||||||
#include <asm/ap.h>
|
#include <asm/ap.h>
|
||||||
|
#include <asm/gmap_helpers.h>
|
||||||
#include "gaccess.h"
|
#include "gaccess.h"
|
||||||
#include "kvm-s390.h"
|
#include "kvm-s390.h"
|
||||||
#include "trace.h"
|
#include "trace.h"
|
||||||
|
#include "gmap.h"
|
||||||
|
|
||||||
static int handle_ri(struct kvm_vcpu *vcpu)
|
static int handle_ri(struct kvm_vcpu *vcpu)
|
||||||
{
|
{
|
||||||
|
|
@ -222,7 +223,7 @@ int kvm_s390_skey_check_enable(struct kvm_vcpu *vcpu)
|
||||||
if (vcpu->arch.skey_enabled)
|
if (vcpu->arch.skey_enabled)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
rc = s390_enable_skey();
|
rc = gmap_enable_skeys(vcpu->arch.gmap);
|
||||||
VCPU_EVENT(vcpu, 3, "enabling storage keys for guest: %d", rc);
|
VCPU_EVENT(vcpu, 3, "enabling storage keys for guest: %d", rc);
|
||||||
if (rc)
|
if (rc)
|
||||||
return rc;
|
return rc;
|
||||||
|
|
@ -255,10 +256,9 @@ static int try_handle_skey(struct kvm_vcpu *vcpu)
|
||||||
|
|
||||||
static int handle_iske(struct kvm_vcpu *vcpu)
|
static int handle_iske(struct kvm_vcpu *vcpu)
|
||||||
{
|
{
|
||||||
unsigned long gaddr, vmaddr;
|
unsigned long gaddr;
|
||||||
unsigned char key;
|
|
||||||
int reg1, reg2;
|
int reg1, reg2;
|
||||||
bool unlocked;
|
union skey key;
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
vcpu->stat.instruction_iske++;
|
vcpu->stat.instruction_iske++;
|
||||||
|
|
@ -275,37 +275,21 @@ static int handle_iske(struct kvm_vcpu *vcpu)
|
||||||
gaddr = vcpu->run->s.regs.gprs[reg2] & PAGE_MASK;
|
gaddr = vcpu->run->s.regs.gprs[reg2] & PAGE_MASK;
|
||||||
gaddr = kvm_s390_logical_to_effective(vcpu, gaddr);
|
gaddr = kvm_s390_logical_to_effective(vcpu, gaddr);
|
||||||
gaddr = kvm_s390_real_to_abs(vcpu, gaddr);
|
gaddr = kvm_s390_real_to_abs(vcpu, gaddr);
|
||||||
vmaddr = gfn_to_hva(vcpu->kvm, gpa_to_gfn(gaddr));
|
scoped_guard(read_lock, &vcpu->kvm->mmu_lock)
|
||||||
if (kvm_is_error_hva(vmaddr))
|
rc = dat_get_storage_key(vcpu->arch.gmap->asce, gpa_to_gfn(gaddr), &key);
|
||||||
return kvm_s390_inject_program_int(vcpu, PGM_ADDRESSING);
|
if (rc > 0)
|
||||||
retry:
|
return kvm_s390_inject_program_int(vcpu, rc);
|
||||||
unlocked = false;
|
|
||||||
mmap_read_lock(current->mm);
|
|
||||||
rc = get_guest_storage_key(current->mm, vmaddr, &key);
|
|
||||||
|
|
||||||
if (rc) {
|
|
||||||
rc = fixup_user_fault(current->mm, vmaddr,
|
|
||||||
FAULT_FLAG_WRITE, &unlocked);
|
|
||||||
if (!rc) {
|
|
||||||
mmap_read_unlock(current->mm);
|
|
||||||
goto retry;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mmap_read_unlock(current->mm);
|
|
||||||
if (rc == -EFAULT)
|
|
||||||
return kvm_s390_inject_program_int(vcpu, PGM_ADDRESSING);
|
|
||||||
if (rc < 0)
|
if (rc < 0)
|
||||||
return rc;
|
return rc;
|
||||||
vcpu->run->s.regs.gprs[reg1] &= ~0xff;
|
vcpu->run->s.regs.gprs[reg1] &= ~0xff;
|
||||||
vcpu->run->s.regs.gprs[reg1] |= key;
|
vcpu->run->s.regs.gprs[reg1] |= key.skey;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int handle_rrbe(struct kvm_vcpu *vcpu)
|
static int handle_rrbe(struct kvm_vcpu *vcpu)
|
||||||
{
|
{
|
||||||
unsigned long vmaddr, gaddr;
|
unsigned long gaddr;
|
||||||
int reg1, reg2;
|
int reg1, reg2;
|
||||||
bool unlocked;
|
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
vcpu->stat.instruction_rrbe++;
|
vcpu->stat.instruction_rrbe++;
|
||||||
|
|
@ -322,24 +306,10 @@ static int handle_rrbe(struct kvm_vcpu *vcpu)
|
||||||
gaddr = vcpu->run->s.regs.gprs[reg2] & PAGE_MASK;
|
gaddr = vcpu->run->s.regs.gprs[reg2] & PAGE_MASK;
|
||||||
gaddr = kvm_s390_logical_to_effective(vcpu, gaddr);
|
gaddr = kvm_s390_logical_to_effective(vcpu, gaddr);
|
||||||
gaddr = kvm_s390_real_to_abs(vcpu, gaddr);
|
gaddr = kvm_s390_real_to_abs(vcpu, gaddr);
|
||||||
vmaddr = gfn_to_hva(vcpu->kvm, gpa_to_gfn(gaddr));
|
scoped_guard(read_lock, &vcpu->kvm->mmu_lock)
|
||||||
if (kvm_is_error_hva(vmaddr))
|
rc = dat_reset_reference_bit(vcpu->arch.gmap->asce, gpa_to_gfn(gaddr));
|
||||||
return kvm_s390_inject_program_int(vcpu, PGM_ADDRESSING);
|
if (rc > 0)
|
||||||
retry:
|
return kvm_s390_inject_program_int(vcpu, rc);
|
||||||
unlocked = false;
|
|
||||||
mmap_read_lock(current->mm);
|
|
||||||
rc = reset_guest_reference_bit(current->mm, vmaddr);
|
|
||||||
if (rc < 0) {
|
|
||||||
rc = fixup_user_fault(current->mm, vmaddr,
|
|
||||||
FAULT_FLAG_WRITE, &unlocked);
|
|
||||||
if (!rc) {
|
|
||||||
mmap_read_unlock(current->mm);
|
|
||||||
goto retry;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mmap_read_unlock(current->mm);
|
|
||||||
if (rc == -EFAULT)
|
|
||||||
return kvm_s390_inject_program_int(vcpu, PGM_ADDRESSING);
|
|
||||||
if (rc < 0)
|
if (rc < 0)
|
||||||
return rc;
|
return rc;
|
||||||
kvm_s390_set_psw_cc(vcpu, rc);
|
kvm_s390_set_psw_cc(vcpu, rc);
|
||||||
|
|
@ -354,9 +324,8 @@ static int handle_sske(struct kvm_vcpu *vcpu)
|
||||||
{
|
{
|
||||||
unsigned char m3 = vcpu->arch.sie_block->ipb >> 28;
|
unsigned char m3 = vcpu->arch.sie_block->ipb >> 28;
|
||||||
unsigned long start, end;
|
unsigned long start, end;
|
||||||
unsigned char key, oldkey;
|
union skey key, oldkey;
|
||||||
int reg1, reg2;
|
int reg1, reg2;
|
||||||
bool unlocked;
|
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
vcpu->stat.instruction_sske++;
|
vcpu->stat.instruction_sske++;
|
||||||
|
|
@ -377,7 +346,7 @@ static int handle_sske(struct kvm_vcpu *vcpu)
|
||||||
|
|
||||||
kvm_s390_get_regs_rre(vcpu, ®1, ®2);
|
kvm_s390_get_regs_rre(vcpu, ®1, ®2);
|
||||||
|
|
||||||
key = vcpu->run->s.regs.gprs[reg1] & 0xfe;
|
key.skey = vcpu->run->s.regs.gprs[reg1] & 0xfe;
|
||||||
start = vcpu->run->s.regs.gprs[reg2] & PAGE_MASK;
|
start = vcpu->run->s.regs.gprs[reg2] & PAGE_MASK;
|
||||||
start = kvm_s390_logical_to_effective(vcpu, start);
|
start = kvm_s390_logical_to_effective(vcpu, start);
|
||||||
if (m3 & SSKE_MB) {
|
if (m3 & SSKE_MB) {
|
||||||
|
|
@ -389,27 +358,17 @@ static int handle_sske(struct kvm_vcpu *vcpu)
|
||||||
}
|
}
|
||||||
|
|
||||||
while (start != end) {
|
while (start != end) {
|
||||||
unsigned long vmaddr = gfn_to_hva(vcpu->kvm, gpa_to_gfn(start));
|
scoped_guard(read_lock, &vcpu->kvm->mmu_lock) {
|
||||||
unlocked = false;
|
rc = dat_cond_set_storage_key(vcpu->arch.mc, vcpu->arch.gmap->asce,
|
||||||
|
gpa_to_gfn(start), key, &oldkey,
|
||||||
if (kvm_is_error_hva(vmaddr))
|
m3 & SSKE_NQ, m3 & SSKE_MR, m3 & SSKE_MC);
|
||||||
return kvm_s390_inject_program_int(vcpu, PGM_ADDRESSING);
|
|
||||||
|
|
||||||
mmap_read_lock(current->mm);
|
|
||||||
rc = cond_set_guest_storage_key(current->mm, vmaddr, key, &oldkey,
|
|
||||||
m3 & SSKE_NQ, m3 & SSKE_MR,
|
|
||||||
m3 & SSKE_MC);
|
|
||||||
|
|
||||||
if (rc < 0) {
|
|
||||||
rc = fixup_user_fault(current->mm, vmaddr,
|
|
||||||
FAULT_FLAG_WRITE, &unlocked);
|
|
||||||
rc = !rc ? -EAGAIN : rc;
|
|
||||||
}
|
}
|
||||||
mmap_read_unlock(current->mm);
|
if (rc > 1)
|
||||||
if (rc == -EFAULT)
|
|
||||||
return kvm_s390_inject_program_int(vcpu, PGM_ADDRESSING);
|
return kvm_s390_inject_program_int(vcpu, PGM_ADDRESSING);
|
||||||
if (rc == -EAGAIN)
|
if (rc == -ENOMEM) {
|
||||||
|
kvm_s390_mmu_cache_topup(vcpu->arch.mc);
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
if (rc < 0)
|
if (rc < 0)
|
||||||
return rc;
|
return rc;
|
||||||
start += PAGE_SIZE;
|
start += PAGE_SIZE;
|
||||||
|
|
@ -422,7 +381,7 @@ static int handle_sske(struct kvm_vcpu *vcpu)
|
||||||
} else {
|
} else {
|
||||||
kvm_s390_set_psw_cc(vcpu, rc);
|
kvm_s390_set_psw_cc(vcpu, rc);
|
||||||
vcpu->run->s.regs.gprs[reg1] &= ~0xff00UL;
|
vcpu->run->s.regs.gprs[reg1] &= ~0xff00UL;
|
||||||
vcpu->run->s.regs.gprs[reg1] |= (u64) oldkey << 8;
|
vcpu->run->s.regs.gprs[reg1] |= (u64)oldkey.skey << 8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (m3 & SSKE_MB) {
|
if (m3 & SSKE_MB) {
|
||||||
|
|
@ -1082,7 +1041,7 @@ static int handle_pfmf(struct kvm_vcpu *vcpu)
|
||||||
bool mr = false, mc = false, nq;
|
bool mr = false, mc = false, nq;
|
||||||
int reg1, reg2;
|
int reg1, reg2;
|
||||||
unsigned long start, end;
|
unsigned long start, end;
|
||||||
unsigned char key;
|
union skey key;
|
||||||
|
|
||||||
vcpu->stat.instruction_pfmf++;
|
vcpu->stat.instruction_pfmf++;
|
||||||
|
|
||||||
|
|
@ -1110,7 +1069,7 @@ static int handle_pfmf(struct kvm_vcpu *vcpu)
|
||||||
}
|
}
|
||||||
|
|
||||||
nq = vcpu->run->s.regs.gprs[reg1] & PFMF_NQ;
|
nq = vcpu->run->s.regs.gprs[reg1] & PFMF_NQ;
|
||||||
key = vcpu->run->s.regs.gprs[reg1] & PFMF_KEY;
|
key.skey = vcpu->run->s.regs.gprs[reg1] & PFMF_KEY;
|
||||||
start = vcpu->run->s.regs.gprs[reg2] & PAGE_MASK;
|
start = vcpu->run->s.regs.gprs[reg2] & PAGE_MASK;
|
||||||
start = kvm_s390_logical_to_effective(vcpu, start);
|
start = kvm_s390_logical_to_effective(vcpu, start);
|
||||||
|
|
||||||
|
|
@ -1141,14 +1100,6 @@ static int handle_pfmf(struct kvm_vcpu *vcpu)
|
||||||
}
|
}
|
||||||
|
|
||||||
while (start != end) {
|
while (start != end) {
|
||||||
unsigned long vmaddr;
|
|
||||||
bool unlocked = false;
|
|
||||||
|
|
||||||
/* Translate guest address to host address */
|
|
||||||
vmaddr = gfn_to_hva(vcpu->kvm, gpa_to_gfn(start));
|
|
||||||
if (kvm_is_error_hva(vmaddr))
|
|
||||||
return kvm_s390_inject_program_int(vcpu, PGM_ADDRESSING);
|
|
||||||
|
|
||||||
if (vcpu->run->s.regs.gprs[reg1] & PFMF_CF) {
|
if (vcpu->run->s.regs.gprs[reg1] & PFMF_CF) {
|
||||||
if (kvm_clear_guest(vcpu->kvm, start, PAGE_SIZE))
|
if (kvm_clear_guest(vcpu->kvm, start, PAGE_SIZE))
|
||||||
return kvm_s390_inject_program_int(vcpu, PGM_ADDRESSING);
|
return kvm_s390_inject_program_int(vcpu, PGM_ADDRESSING);
|
||||||
|
|
@ -1159,19 +1110,17 @@ static int handle_pfmf(struct kvm_vcpu *vcpu)
|
||||||
|
|
||||||
if (rc)
|
if (rc)
|
||||||
return rc;
|
return rc;
|
||||||
mmap_read_lock(current->mm);
|
scoped_guard(read_lock, &vcpu->kvm->mmu_lock) {
|
||||||
rc = cond_set_guest_storage_key(current->mm, vmaddr,
|
rc = dat_cond_set_storage_key(vcpu->arch.mc, vcpu->arch.gmap->asce,
|
||||||
key, NULL, nq, mr, mc);
|
gpa_to_gfn(start), key,
|
||||||
if (rc < 0) {
|
NULL, nq, mr, mc);
|
||||||
rc = fixup_user_fault(current->mm, vmaddr,
|
|
||||||
FAULT_FLAG_WRITE, &unlocked);
|
|
||||||
rc = !rc ? -EAGAIN : rc;
|
|
||||||
}
|
}
|
||||||
mmap_read_unlock(current->mm);
|
if (rc > 1)
|
||||||
if (rc == -EFAULT)
|
return kvm_s390_inject_program_int(vcpu, rc);
|
||||||
return kvm_s390_inject_program_int(vcpu, PGM_ADDRESSING);
|
if (rc == -ENOMEM) {
|
||||||
if (rc == -EAGAIN)
|
kvm_s390_mmu_cache_topup(vcpu->arch.mc);
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
if (rc < 0)
|
if (rc < 0)
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
@ -1195,8 +1144,10 @@ static int handle_pfmf(struct kvm_vcpu *vcpu)
|
||||||
static inline int __do_essa(struct kvm_vcpu *vcpu, const int orc)
|
static inline int __do_essa(struct kvm_vcpu *vcpu, const int orc)
|
||||||
{
|
{
|
||||||
int r1, r2, nappended, entries;
|
int r1, r2, nappended, entries;
|
||||||
unsigned long gfn, hva, res, pgstev, ptev;
|
union essa_state state;
|
||||||
unsigned long *cbrlo;
|
unsigned long *cbrlo;
|
||||||
|
unsigned long gfn;
|
||||||
|
bool dirtied;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We don't need to set SD.FPF.SK to 1 here, because if we have a
|
* We don't need to set SD.FPF.SK to 1 here, because if we have a
|
||||||
|
|
@ -1205,33 +1156,12 @@ static inline int __do_essa(struct kvm_vcpu *vcpu, const int orc)
|
||||||
|
|
||||||
kvm_s390_get_regs_rre(vcpu, &r1, &r2);
|
kvm_s390_get_regs_rre(vcpu, &r1, &r2);
|
||||||
gfn = vcpu->run->s.regs.gprs[r2] >> PAGE_SHIFT;
|
gfn = vcpu->run->s.regs.gprs[r2] >> PAGE_SHIFT;
|
||||||
hva = gfn_to_hva(vcpu->kvm, gfn);
|
|
||||||
entries = (vcpu->arch.sie_block->cbrlo & ~PAGE_MASK) >> 3;
|
entries = (vcpu->arch.sie_block->cbrlo & ~PAGE_MASK) >> 3;
|
||||||
|
|
||||||
if (kvm_is_error_hva(hva))
|
nappended = dat_perform_essa(vcpu->arch.gmap->asce, gfn, orc, &state, &dirtied);
|
||||||
return kvm_s390_inject_program_int(vcpu, PGM_ADDRESSING);
|
vcpu->run->s.regs.gprs[r1] = state.val;
|
||||||
|
if (nappended < 0)
|
||||||
nappended = pgste_perform_essa(vcpu->kvm->mm, hva, orc, &ptev, &pgstev);
|
|
||||||
if (nappended < 0) {
|
|
||||||
res = orc ? 0x10 : 0;
|
|
||||||
vcpu->run->s.regs.gprs[r1] = res; /* Exception Indication */
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
|
||||||
res = (pgstev & _PGSTE_GPS_USAGE_MASK) >> 22;
|
|
||||||
/*
|
|
||||||
* Set the block-content state part of the result. 0 means resident, so
|
|
||||||
* nothing to do if the page is valid. 2 is for preserved pages
|
|
||||||
* (non-present and non-zero), and 3 for zero pages (non-present and
|
|
||||||
* zero).
|
|
||||||
*/
|
|
||||||
if (ptev & _PAGE_INVALID) {
|
|
||||||
res |= 2;
|
|
||||||
if (pgstev & _PGSTE_GPS_ZERO)
|
|
||||||
res |= 1;
|
|
||||||
}
|
|
||||||
if (pgstev & _PGSTE_GPS_NODAT)
|
|
||||||
res |= 0x20;
|
|
||||||
vcpu->run->s.regs.gprs[r1] = res;
|
|
||||||
/*
|
/*
|
||||||
* It is possible that all the normal 511 slots were full, in which case
|
* It is possible that all the normal 511 slots were full, in which case
|
||||||
* we will now write in the 512th slot, which is reserved for host use.
|
* we will now write in the 512th slot, which is reserved for host use.
|
||||||
|
|
@ -1243,17 +1173,34 @@ static inline int __do_essa(struct kvm_vcpu *vcpu, const int orc)
|
||||||
cbrlo[entries] = gfn << PAGE_SHIFT;
|
cbrlo[entries] = gfn << PAGE_SHIFT;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (orc) {
|
if (dirtied)
|
||||||
struct kvm_memory_slot *ms = gfn_to_memslot(vcpu->kvm, gfn);
|
atomic64_inc(&vcpu->kvm->arch.cmma_dirty_pages);
|
||||||
|
|
||||||
/* Increment only if we are really flipping the bit */
|
|
||||||
if (ms && !test_and_set_bit(gfn - ms->base_gfn, kvm_second_dirty_bitmap(ms)))
|
|
||||||
atomic64_inc(&vcpu->kvm->arch.cmma_dirty_pages);
|
|
||||||
}
|
|
||||||
|
|
||||||
return nappended;
|
return nappended;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void _essa_clear_cbrl(struct kvm_vcpu *vcpu, unsigned long *cbrl, int len)
|
||||||
|
{
|
||||||
|
union crste *crstep;
|
||||||
|
union pgste pgste;
|
||||||
|
union pte *ptep;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
lockdep_assert_held(&vcpu->kvm->mmu_lock);
|
||||||
|
|
||||||
|
for (i = 0; i < len; i++) {
|
||||||
|
if (dat_entry_walk(NULL, gpa_to_gfn(cbrl[i]), vcpu->arch.gmap->asce,
|
||||||
|
0, TABLE_TYPE_PAGE_TABLE, &crstep, &ptep))
|
||||||
|
continue;
|
||||||
|
if (!ptep || ptep->s.pr)
|
||||||
|
continue;
|
||||||
|
pgste = pgste_get_lock(ptep);
|
||||||
|
if (pgste.usage == PGSTE_GPS_USAGE_UNUSED || pgste.zero)
|
||||||
|
gmap_helper_zap_one_page(vcpu->kvm->mm, cbrl[i]);
|
||||||
|
pgste_set_unlock(ptep, pgste);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static int handle_essa(struct kvm_vcpu *vcpu)
|
static int handle_essa(struct kvm_vcpu *vcpu)
|
||||||
{
|
{
|
||||||
lockdep_assert_held(&vcpu->kvm->srcu);
|
lockdep_assert_held(&vcpu->kvm->srcu);
|
||||||
|
|
@ -1261,11 +1208,9 @@ static int handle_essa(struct kvm_vcpu *vcpu)
|
||||||
/* entries expected to be 1FF */
|
/* entries expected to be 1FF */
|
||||||
int entries = (vcpu->arch.sie_block->cbrlo & ~PAGE_MASK) >> 3;
|
int entries = (vcpu->arch.sie_block->cbrlo & ~PAGE_MASK) >> 3;
|
||||||
unsigned long *cbrlo;
|
unsigned long *cbrlo;
|
||||||
struct gmap *gmap;
|
|
||||||
int i, orc;
|
int i, orc;
|
||||||
|
|
||||||
VCPU_EVENT(vcpu, 4, "ESSA: release %d pages", entries);
|
VCPU_EVENT(vcpu, 4, "ESSA: release %d pages", entries);
|
||||||
gmap = vcpu->arch.gmap;
|
|
||||||
vcpu->stat.instruction_essa++;
|
vcpu->stat.instruction_essa++;
|
||||||
if (!vcpu->kvm->arch.use_cmma)
|
if (!vcpu->kvm->arch.use_cmma)
|
||||||
return kvm_s390_inject_program_int(vcpu, PGM_OPERATION);
|
return kvm_s390_inject_program_int(vcpu, PGM_OPERATION);
|
||||||
|
|
@ -1289,11 +1234,7 @@ static int handle_essa(struct kvm_vcpu *vcpu)
|
||||||
* value really needs to be written to; if the value is
|
* value really needs to be written to; if the value is
|
||||||
* already correct, we do nothing and avoid the lock.
|
* already correct, we do nothing and avoid the lock.
|
||||||
*/
|
*/
|
||||||
if (vcpu->kvm->mm->context.uses_cmm == 0) {
|
set_bit(GMAP_FLAG_USES_CMM, &vcpu->arch.gmap->flags);
|
||||||
mmap_write_lock(vcpu->kvm->mm);
|
|
||||||
vcpu->kvm->mm->context.uses_cmm = 1;
|
|
||||||
mmap_write_unlock(vcpu->kvm->mm);
|
|
||||||
}
|
|
||||||
/*
|
/*
|
||||||
* If we are here, we are supposed to have CMMA enabled in
|
* If we are here, we are supposed to have CMMA enabled in
|
||||||
* the SIE block. Enabling CMMA works on a per-CPU basis,
|
* the SIE block. Enabling CMMA works on a per-CPU basis,
|
||||||
|
|
@ -1307,20 +1248,22 @@ static int handle_essa(struct kvm_vcpu *vcpu)
|
||||||
/* Retry the ESSA instruction */
|
/* Retry the ESSA instruction */
|
||||||
kvm_s390_retry_instr(vcpu);
|
kvm_s390_retry_instr(vcpu);
|
||||||
} else {
|
} else {
|
||||||
mmap_read_lock(vcpu->kvm->mm);
|
scoped_guard(read_lock, &vcpu->kvm->mmu_lock)
|
||||||
i = __do_essa(vcpu, orc);
|
i = __do_essa(vcpu, orc);
|
||||||
mmap_read_unlock(vcpu->kvm->mm);
|
|
||||||
if (i < 0)
|
if (i < 0)
|
||||||
return i;
|
return i;
|
||||||
/* Account for the possible extra cbrl entry */
|
/* Account for the possible extra cbrl entry */
|
||||||
entries += i;
|
entries += i;
|
||||||
}
|
}
|
||||||
vcpu->arch.sie_block->cbrlo &= PAGE_MASK; /* reset nceo */
|
/* reset nceo */
|
||||||
|
vcpu->arch.sie_block->cbrlo &= PAGE_MASK;
|
||||||
cbrlo = phys_to_virt(vcpu->arch.sie_block->cbrlo);
|
cbrlo = phys_to_virt(vcpu->arch.sie_block->cbrlo);
|
||||||
mmap_read_lock(gmap->mm);
|
|
||||||
for (i = 0; i < entries; ++i)
|
mmap_read_lock(vcpu->kvm->mm);
|
||||||
__gmap_zap(gmap, cbrlo[i]);
|
scoped_guard(read_lock, &vcpu->kvm->mmu_lock)
|
||||||
mmap_read_unlock(gmap->mm);
|
_essa_clear_cbrl(vcpu, cbrlo, entries);
|
||||||
|
mmap_read_unlock(vcpu->kvm->mm);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,13 +12,16 @@
|
||||||
#include <linux/minmax.h>
|
#include <linux/minmax.h>
|
||||||
#include <linux/pagemap.h>
|
#include <linux/pagemap.h>
|
||||||
#include <linux/sched/signal.h>
|
#include <linux/sched/signal.h>
|
||||||
#include <asm/gmap.h>
|
|
||||||
#include <asm/uv.h>
|
#include <asm/uv.h>
|
||||||
#include <asm/mman.h>
|
#include <asm/mman.h>
|
||||||
#include <linux/pagewalk.h>
|
#include <linux/pagewalk.h>
|
||||||
#include <linux/sched/mm.h>
|
#include <linux/sched/mm.h>
|
||||||
#include <linux/mmu_notifier.h>
|
#include <linux/mmu_notifier.h>
|
||||||
#include "kvm-s390.h"
|
#include "kvm-s390.h"
|
||||||
|
#include "dat.h"
|
||||||
|
#include "gaccess.h"
|
||||||
|
#include "gmap.h"
|
||||||
|
#include "faultin.h"
|
||||||
|
|
||||||
bool kvm_s390_pv_is_protected(struct kvm *kvm)
|
bool kvm_s390_pv_is_protected(struct kvm *kvm)
|
||||||
{
|
{
|
||||||
|
|
@ -34,6 +37,85 @@ bool kvm_s390_pv_cpu_is_protected(struct kvm_vcpu *vcpu)
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(kvm_s390_pv_cpu_is_protected);
|
EXPORT_SYMBOL_GPL(kvm_s390_pv_cpu_is_protected);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* should_export_before_import() - Determine whether an export is needed
|
||||||
|
* before an import-like operation.
|
||||||
|
* @uvcb: The Ultravisor control block of the UVC to be performed.
|
||||||
|
* @mm: The mm of the process.
|
||||||
|
*
|
||||||
|
* Returns whether an export is needed before every import-like operation.
|
||||||
|
* This is needed for shared pages, which don't trigger a secure storage
|
||||||
|
* exception when accessed from a different guest.
|
||||||
|
*
|
||||||
|
* Although considered as one, the Unpin Page UVC is not an actual import,
|
||||||
|
* so it is not affected.
|
||||||
|
*
|
||||||
|
* No export is needed also when there is only one protected VM, because the
|
||||||
|
* page cannot belong to the wrong VM in that case (there is no "other VM"
|
||||||
|
* it can belong to).
|
||||||
|
*
|
||||||
|
* Return: %true if an export is needed before every import, otherwise %false.
|
||||||
|
*/
|
||||||
|
static bool should_export_before_import(struct uv_cb_header *uvcb, struct mm_struct *mm)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* The misc feature indicates, among other things, that importing a
|
||||||
|
* shared page from a different protected VM will automatically also
|
||||||
|
* transfer its ownership.
|
||||||
|
*/
|
||||||
|
if (uv_has_feature(BIT_UV_FEAT_MISC))
|
||||||
|
return false;
|
||||||
|
if (uvcb->cmd == UVC_CMD_UNPIN_PAGE_SHARED)
|
||||||
|
return false;
|
||||||
|
return atomic_read(&mm->context.protected_count) > 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct pv_make_secure {
|
||||||
|
void *uvcb;
|
||||||
|
struct folio *folio;
|
||||||
|
int rc;
|
||||||
|
bool needs_export;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int __kvm_s390_pv_make_secure(struct guest_fault *f, struct folio *folio)
|
||||||
|
{
|
||||||
|
struct pv_make_secure *priv = f->priv;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
if (priv->needs_export)
|
||||||
|
uv_convert_from_secure(folio_to_phys(folio));
|
||||||
|
|
||||||
|
if (folio_test_hugetlb(folio))
|
||||||
|
return -EFAULT;
|
||||||
|
if (folio_test_large(folio))
|
||||||
|
return -E2BIG;
|
||||||
|
|
||||||
|
if (!f->page)
|
||||||
|
folio_get(folio);
|
||||||
|
rc = __make_folio_secure(folio, priv->uvcb);
|
||||||
|
if (!f->page)
|
||||||
|
folio_put(folio);
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _kvm_s390_pv_make_secure(struct guest_fault *f)
|
||||||
|
{
|
||||||
|
struct pv_make_secure *priv = f->priv;
|
||||||
|
struct folio *folio;
|
||||||
|
|
||||||
|
folio = pfn_folio(f->pfn);
|
||||||
|
priv->rc = -EAGAIN;
|
||||||
|
if (folio_trylock(folio)) {
|
||||||
|
priv->rc = __kvm_s390_pv_make_secure(f, folio);
|
||||||
|
if (priv->rc == -E2BIG || priv->rc == -EBUSY) {
|
||||||
|
priv->folio = folio;
|
||||||
|
folio_get(folio);
|
||||||
|
}
|
||||||
|
folio_unlock(folio);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* kvm_s390_pv_make_secure() - make one guest page secure
|
* kvm_s390_pv_make_secure() - make one guest page secure
|
||||||
* @kvm: the guest
|
* @kvm: the guest
|
||||||
|
|
@ -45,14 +127,34 @@ EXPORT_SYMBOL_GPL(kvm_s390_pv_cpu_is_protected);
|
||||||
*/
|
*/
|
||||||
int kvm_s390_pv_make_secure(struct kvm *kvm, unsigned long gaddr, void *uvcb)
|
int kvm_s390_pv_make_secure(struct kvm *kvm, unsigned long gaddr, void *uvcb)
|
||||||
{
|
{
|
||||||
unsigned long vmaddr;
|
struct pv_make_secure priv = { .uvcb = uvcb };
|
||||||
|
struct guest_fault f = {
|
||||||
|
.write_attempt = true,
|
||||||
|
.gfn = gpa_to_gfn(gaddr),
|
||||||
|
.callback = _kvm_s390_pv_make_secure,
|
||||||
|
.priv = &priv,
|
||||||
|
};
|
||||||
|
int rc;
|
||||||
|
|
||||||
lockdep_assert_held(&kvm->srcu);
|
lockdep_assert_held(&kvm->srcu);
|
||||||
|
|
||||||
vmaddr = gfn_to_hva(kvm, gpa_to_gfn(gaddr));
|
priv.needs_export = should_export_before_import(uvcb, kvm->mm);
|
||||||
if (kvm_is_error_hva(vmaddr))
|
|
||||||
return -EFAULT;
|
scoped_guard(mutex, &kvm->arch.pv.import_lock) {
|
||||||
return make_hva_secure(kvm->mm, vmaddr, uvcb);
|
rc = kvm_s390_faultin_gfn(NULL, kvm, &f);
|
||||||
|
|
||||||
|
if (!rc) {
|
||||||
|
rc = priv.rc;
|
||||||
|
if (priv.folio) {
|
||||||
|
rc = s390_wiggle_split_folio(kvm->mm, priv.folio);
|
||||||
|
if (!rc)
|
||||||
|
rc = -EAGAIN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (priv.folio)
|
||||||
|
folio_put(priv.folio);
|
||||||
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
int kvm_s390_pv_convert_to_secure(struct kvm *kvm, unsigned long gaddr)
|
int kvm_s390_pv_convert_to_secure(struct kvm *kvm, unsigned long gaddr)
|
||||||
|
|
@ -299,35 +401,6 @@ static int kvm_s390_pv_dispose_one_leftover(struct kvm *kvm,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* kvm_s390_destroy_lower_2g - Destroy the first 2GB of protected guest memory.
|
|
||||||
* @kvm: the VM whose memory is to be cleared.
|
|
||||||
*
|
|
||||||
* Destroy the first 2GB of guest memory, to avoid prefix issues after reboot.
|
|
||||||
* The CPUs of the protected VM need to be destroyed beforehand.
|
|
||||||
*/
|
|
||||||
static void kvm_s390_destroy_lower_2g(struct kvm *kvm)
|
|
||||||
{
|
|
||||||
const unsigned long pages_2g = SZ_2G / PAGE_SIZE;
|
|
||||||
struct kvm_memory_slot *slot;
|
|
||||||
unsigned long len;
|
|
||||||
int srcu_idx;
|
|
||||||
|
|
||||||
srcu_idx = srcu_read_lock(&kvm->srcu);
|
|
||||||
|
|
||||||
/* Take the memslot containing guest absolute address 0 */
|
|
||||||
slot = gfn_to_memslot(kvm, 0);
|
|
||||||
/* Clear all slots or parts thereof that are below 2GB */
|
|
||||||
while (slot && slot->base_gfn < pages_2g) {
|
|
||||||
len = min_t(u64, slot->npages, pages_2g - slot->base_gfn) * PAGE_SIZE;
|
|
||||||
s390_uv_destroy_range(kvm->mm, slot->userspace_addr, slot->userspace_addr + len);
|
|
||||||
/* Take the next memslot */
|
|
||||||
slot = gfn_to_memslot(kvm, slot->base_gfn + slot->npages);
|
|
||||||
}
|
|
||||||
|
|
||||||
srcu_read_unlock(&kvm->srcu, srcu_idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int kvm_s390_pv_deinit_vm_fast(struct kvm *kvm, u16 *rc, u16 *rrc)
|
static int kvm_s390_pv_deinit_vm_fast(struct kvm *kvm, u16 *rc, u16 *rrc)
|
||||||
{
|
{
|
||||||
struct uv_cb_destroy_fast uvcb = {
|
struct uv_cb_destroy_fast uvcb = {
|
||||||
|
|
@ -342,7 +415,6 @@ static int kvm_s390_pv_deinit_vm_fast(struct kvm *kvm, u16 *rc, u16 *rrc)
|
||||||
*rc = uvcb.header.rc;
|
*rc = uvcb.header.rc;
|
||||||
if (rrc)
|
if (rrc)
|
||||||
*rrc = uvcb.header.rrc;
|
*rrc = uvcb.header.rrc;
|
||||||
WRITE_ONCE(kvm->arch.gmap->guest_handle, 0);
|
|
||||||
KVM_UV_EVENT(kvm, 3, "PROTVIRT DESTROY VM FAST: rc %x rrc %x",
|
KVM_UV_EVENT(kvm, 3, "PROTVIRT DESTROY VM FAST: rc %x rrc %x",
|
||||||
uvcb.header.rc, uvcb.header.rrc);
|
uvcb.header.rc, uvcb.header.rrc);
|
||||||
WARN_ONCE(cc && uvcb.header.rc != 0x104,
|
WARN_ONCE(cc && uvcb.header.rc != 0x104,
|
||||||
|
|
@ -391,7 +463,7 @@ int kvm_s390_pv_set_aside(struct kvm *kvm, u16 *rc, u16 *rrc)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
/* Guest with segment type ASCE, refuse to destroy asynchronously */
|
/* Guest with segment type ASCE, refuse to destroy asynchronously */
|
||||||
if ((kvm->arch.gmap->asce & _ASCE_TYPE_MASK) == _ASCE_TYPE_SEGMENT)
|
if (kvm->arch.gmap->asce.dt == TABLE_TYPE_SEGMENT)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
|
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
|
||||||
|
|
@ -404,8 +476,7 @@ int kvm_s390_pv_set_aside(struct kvm *kvm, u16 *rc, u16 *rrc)
|
||||||
priv->stor_var = kvm->arch.pv.stor_var;
|
priv->stor_var = kvm->arch.pv.stor_var;
|
||||||
priv->stor_base = kvm->arch.pv.stor_base;
|
priv->stor_base = kvm->arch.pv.stor_base;
|
||||||
priv->handle = kvm_s390_pv_get_handle(kvm);
|
priv->handle = kvm_s390_pv_get_handle(kvm);
|
||||||
priv->old_gmap_table = (unsigned long)kvm->arch.gmap->table;
|
priv->old_gmap_table = (unsigned long)dereference_asce(kvm->arch.gmap->asce);
|
||||||
WRITE_ONCE(kvm->arch.gmap->guest_handle, 0);
|
|
||||||
if (s390_replace_asce(kvm->arch.gmap))
|
if (s390_replace_asce(kvm->arch.gmap))
|
||||||
res = -ENOMEM;
|
res = -ENOMEM;
|
||||||
}
|
}
|
||||||
|
|
@ -415,7 +486,7 @@ int kvm_s390_pv_set_aside(struct kvm *kvm, u16 *rc, u16 *rrc)
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
kvm_s390_destroy_lower_2g(kvm);
|
gmap_pv_destroy_range(kvm->arch.gmap, 0, gpa_to_gfn(SZ_2G), false);
|
||||||
kvm_s390_clear_pv_state(kvm);
|
kvm_s390_clear_pv_state(kvm);
|
||||||
kvm->arch.pv.set_aside = priv;
|
kvm->arch.pv.set_aside = priv;
|
||||||
|
|
||||||
|
|
@ -449,7 +520,6 @@ int kvm_s390_pv_deinit_vm(struct kvm *kvm, u16 *rc, u16 *rrc)
|
||||||
|
|
||||||
cc = uv_cmd_nodata(kvm_s390_pv_get_handle(kvm),
|
cc = uv_cmd_nodata(kvm_s390_pv_get_handle(kvm),
|
||||||
UVC_CMD_DESTROY_SEC_CONF, rc, rrc);
|
UVC_CMD_DESTROY_SEC_CONF, rc, rrc);
|
||||||
WRITE_ONCE(kvm->arch.gmap->guest_handle, 0);
|
|
||||||
if (!cc) {
|
if (!cc) {
|
||||||
atomic_dec(&kvm->mm->context.protected_count);
|
atomic_dec(&kvm->mm->context.protected_count);
|
||||||
kvm_s390_pv_dealloc_vm(kvm);
|
kvm_s390_pv_dealloc_vm(kvm);
|
||||||
|
|
@ -532,7 +602,7 @@ int kvm_s390_pv_deinit_cleanup_all(struct kvm *kvm, u16 *rc, u16 *rrc)
|
||||||
* cleanup has been performed.
|
* cleanup has been performed.
|
||||||
*/
|
*/
|
||||||
if (need_zap && mmget_not_zero(kvm->mm)) {
|
if (need_zap && mmget_not_zero(kvm->mm)) {
|
||||||
s390_uv_destroy_range(kvm->mm, 0, TASK_SIZE);
|
gmap_pv_destroy_range(kvm->arch.gmap, 0, asce_end(kvm->arch.gmap->asce), false);
|
||||||
mmput(kvm->mm);
|
mmput(kvm->mm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -570,7 +640,7 @@ int kvm_s390_pv_deinit_aside_vm(struct kvm *kvm, u16 *rc, u16 *rrc)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
/* When a fatal signal is received, stop immediately */
|
/* When a fatal signal is received, stop immediately */
|
||||||
if (s390_uv_destroy_range_interruptible(kvm->mm, 0, TASK_SIZE_MAX))
|
if (gmap_pv_destroy_range(kvm->arch.gmap, 0, asce_end(kvm->arch.gmap->asce), true))
|
||||||
goto done;
|
goto done;
|
||||||
if (kvm_s390_pv_dispose_one_leftover(kvm, p, rc, rrc))
|
if (kvm_s390_pv_dispose_one_leftover(kvm, p, rc, rrc))
|
||||||
ret = -EIO;
|
ret = -EIO;
|
||||||
|
|
@ -609,6 +679,7 @@ static void kvm_s390_pv_mmu_notifier_release(struct mmu_notifier *subscription,
|
||||||
r = kvm_s390_cpus_from_pv(kvm, &dummy, &dummy);
|
r = kvm_s390_cpus_from_pv(kvm, &dummy, &dummy);
|
||||||
if (!r && is_destroy_fast_available() && kvm_s390_pv_get_handle(kvm))
|
if (!r && is_destroy_fast_available() && kvm_s390_pv_get_handle(kvm))
|
||||||
kvm_s390_pv_deinit_vm_fast(kvm, &dummy, &dummy);
|
kvm_s390_pv_deinit_vm_fast(kvm, &dummy, &dummy);
|
||||||
|
set_bit(GMAP_FLAG_EXPORT_ON_UNMAP, &kvm->arch.gmap->flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct mmu_notifier_ops kvm_s390_pv_mmu_notifier_ops = {
|
static const struct mmu_notifier_ops kvm_s390_pv_mmu_notifier_ops = {
|
||||||
|
|
@ -642,7 +713,7 @@ int kvm_s390_pv_init_vm(struct kvm *kvm, u16 *rc, u16 *rrc)
|
||||||
/* Inputs */
|
/* Inputs */
|
||||||
uvcb.guest_stor_origin = 0; /* MSO is 0 for KVM */
|
uvcb.guest_stor_origin = 0; /* MSO is 0 for KVM */
|
||||||
uvcb.guest_stor_len = kvm->arch.pv.guest_len;
|
uvcb.guest_stor_len = kvm->arch.pv.guest_len;
|
||||||
uvcb.guest_asce = kvm->arch.gmap->asce;
|
uvcb.guest_asce = kvm->arch.gmap->asce.val;
|
||||||
uvcb.guest_sca = virt_to_phys(kvm->arch.sca);
|
uvcb.guest_sca = virt_to_phys(kvm->arch.sca);
|
||||||
uvcb.conf_base_stor_origin =
|
uvcb.conf_base_stor_origin =
|
||||||
virt_to_phys((void *)kvm->arch.pv.stor_base);
|
virt_to_phys((void *)kvm->arch.pv.stor_base);
|
||||||
|
|
@ -669,7 +740,6 @@ int kvm_s390_pv_init_vm(struct kvm *kvm, u16 *rc, u16 *rrc)
|
||||||
}
|
}
|
||||||
return -EIO;
|
return -EIO;
|
||||||
}
|
}
|
||||||
kvm->arch.gmap->guest_handle = uvcb.guest_handle;
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -704,26 +774,14 @@ static int unpack_one(struct kvm *kvm, unsigned long addr, u64 tweak,
|
||||||
.tweak[1] = offset,
|
.tweak[1] = offset,
|
||||||
};
|
};
|
||||||
int ret = kvm_s390_pv_make_secure(kvm, addr, &uvcb);
|
int ret = kvm_s390_pv_make_secure(kvm, addr, &uvcb);
|
||||||
unsigned long vmaddr;
|
|
||||||
bool unlocked;
|
|
||||||
|
|
||||||
*rc = uvcb.header.rc;
|
*rc = uvcb.header.rc;
|
||||||
*rrc = uvcb.header.rrc;
|
*rrc = uvcb.header.rrc;
|
||||||
|
|
||||||
if (ret == -ENXIO) {
|
if (ret == -ENXIO) {
|
||||||
mmap_read_lock(kvm->mm);
|
ret = kvm_s390_faultin_gfn_simple(NULL, kvm, gpa_to_gfn(addr), true);
|
||||||
vmaddr = gfn_to_hva(kvm, gpa_to_gfn(addr));
|
|
||||||
if (kvm_is_error_hva(vmaddr)) {
|
|
||||||
ret = -EFAULT;
|
|
||||||
} else {
|
|
||||||
ret = fixup_user_fault(kvm->mm, vmaddr, FAULT_FLAG_WRITE, &unlocked);
|
|
||||||
if (!ret)
|
|
||||||
ret = __gmap_link(kvm->arch.gmap, addr, vmaddr);
|
|
||||||
}
|
|
||||||
mmap_read_unlock(kvm->mm);
|
|
||||||
if (!ret)
|
if (!ret)
|
||||||
return -EAGAIN;
|
return -EAGAIN;
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ret && ret != -EAGAIN)
|
if (ret && ret != -EAGAIN)
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@
|
||||||
#include <linux/io.h>
|
#include <linux/io.h>
|
||||||
#include <linux/mman.h>
|
#include <linux/mman.h>
|
||||||
|
|
||||||
#include <asm/gmap.h>
|
|
||||||
#include <asm/mmu_context.h>
|
#include <asm/mmu_context.h>
|
||||||
#include <asm/sclp.h>
|
#include <asm/sclp.h>
|
||||||
#include <asm/nmi.h>
|
#include <asm/nmi.h>
|
||||||
|
|
@ -23,6 +22,7 @@
|
||||||
#include <asm/facility.h>
|
#include <asm/facility.h>
|
||||||
#include "kvm-s390.h"
|
#include "kvm-s390.h"
|
||||||
#include "gaccess.h"
|
#include "gaccess.h"
|
||||||
|
#include "gmap.h"
|
||||||
|
|
||||||
enum vsie_page_flags {
|
enum vsie_page_flags {
|
||||||
VSIE_PAGE_IN_USE = 0,
|
VSIE_PAGE_IN_USE = 0,
|
||||||
|
|
@ -41,8 +41,11 @@ struct vsie_page {
|
||||||
* are reused conditionally, should be accessed via READ_ONCE.
|
* are reused conditionally, should be accessed via READ_ONCE.
|
||||||
*/
|
*/
|
||||||
struct kvm_s390_sie_block *scb_o; /* 0x0218 */
|
struct kvm_s390_sie_block *scb_o; /* 0x0218 */
|
||||||
/* the shadow gmap in use by the vsie_page */
|
/*
|
||||||
struct gmap *gmap; /* 0x0220 */
|
* Flags: must be set/cleared atomically after the vsie page can be
|
||||||
|
* looked up by other CPUs.
|
||||||
|
*/
|
||||||
|
unsigned long flags; /* 0x0220 */
|
||||||
/* address of the last reported fault to guest2 */
|
/* address of the last reported fault to guest2 */
|
||||||
unsigned long fault_addr; /* 0x0228 */
|
unsigned long fault_addr; /* 0x0228 */
|
||||||
/* calculated guest addresses of satellite control blocks */
|
/* calculated guest addresses of satellite control blocks */
|
||||||
|
|
@ -57,33 +60,14 @@ struct vsie_page {
|
||||||
* radix tree.
|
* radix tree.
|
||||||
*/
|
*/
|
||||||
gpa_t scb_gpa; /* 0x0258 */
|
gpa_t scb_gpa; /* 0x0258 */
|
||||||
/*
|
/* the shadow gmap in use by the vsie_page */
|
||||||
* Flags: must be set/cleared atomically after the vsie page can be
|
struct gmap_cache gmap_cache; /* 0x0260 */
|
||||||
* looked up by other CPUs.
|
__u8 reserved[0x0700 - 0x0278]; /* 0x0278 */
|
||||||
*/
|
|
||||||
unsigned long flags; /* 0x0260 */
|
|
||||||
__u8 reserved[0x0700 - 0x0268]; /* 0x0268 */
|
|
||||||
struct kvm_s390_crypto_cb crycb; /* 0x0700 */
|
struct kvm_s390_crypto_cb crycb; /* 0x0700 */
|
||||||
__u8 fac[S390_ARCH_FAC_LIST_SIZE_BYTE]; /* 0x0800 */
|
__u8 fac[S390_ARCH_FAC_LIST_SIZE_BYTE]; /* 0x0800 */
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
static_assert(sizeof(struct vsie_page) == PAGE_SIZE);
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* trigger a validity icpt for the given scb */
|
/* trigger a validity icpt for the given scb */
|
||||||
static int set_validity_icpt(struct kvm_s390_sie_block *scb,
|
static int set_validity_icpt(struct kvm_s390_sie_block *scb,
|
||||||
|
|
@ -612,26 +596,17 @@ static int shadow_scb(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page)
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
void kvm_s390_vsie_gmap_notifier(struct gmap *gmap, unsigned long start,
|
void kvm_s390_vsie_gmap_notifier(struct gmap *gmap, gpa_t start, gpa_t end)
|
||||||
unsigned long end)
|
|
||||||
{
|
{
|
||||||
struct kvm *kvm = gmap->private;
|
struct vsie_page *cur, *next;
|
||||||
struct vsie_page *cur;
|
|
||||||
unsigned long prefix;
|
unsigned long prefix;
|
||||||
int i;
|
|
||||||
|
|
||||||
if (!gmap_is_shadow(gmap))
|
KVM_BUG_ON(!test_bit(GMAP_FLAG_SHADOW, &gmap->flags), gmap->kvm);
|
||||||
return;
|
|
||||||
/*
|
/*
|
||||||
* Only new shadow blocks are added to the list during runtime,
|
* Only new shadow blocks are added to the list during runtime,
|
||||||
* therefore we can safely reference them all the time.
|
* therefore we can safely reference them all the time.
|
||||||
*/
|
*/
|
||||||
for (i = 0; i < kvm->arch.vsie.page_count; i++) {
|
list_for_each_entry_safe(cur, next, &gmap->scb_users, gmap_cache.list) {
|
||||||
cur = READ_ONCE(kvm->arch.vsie.pages[i]);
|
|
||||||
if (!cur)
|
|
||||||
continue;
|
|
||||||
if (READ_ONCE(cur->gmap) != gmap)
|
|
||||||
continue;
|
|
||||||
prefix = cur->scb_s.prefix << GUEST_PREFIX_SHIFT;
|
prefix = cur->scb_s.prefix << GUEST_PREFIX_SHIFT;
|
||||||
/* with mso/msl, the prefix lies at an offset */
|
/* with mso/msl, the prefix lies at an offset */
|
||||||
prefix += cur->scb_s.mso;
|
prefix += cur->scb_s.mso;
|
||||||
|
|
@ -667,9 +642,9 @@ static int map_prefix(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page, struct
|
||||||
/* with mso/msl, the prefix lies at offset *mso* */
|
/* with mso/msl, the prefix lies at offset *mso* */
|
||||||
prefix += scb_s->mso;
|
prefix += scb_s->mso;
|
||||||
|
|
||||||
rc = kvm_s390_shadow_fault(vcpu, sg, prefix, NULL);
|
rc = gaccess_shadow_fault(vcpu, sg, prefix, NULL, true);
|
||||||
if (!rc && (scb_s->ecb & ECB_TE))
|
if (!rc && (scb_s->ecb & ECB_TE))
|
||||||
rc = kvm_s390_shadow_fault(vcpu, sg, prefix + PAGE_SIZE, NULL);
|
rc = gaccess_shadow_fault(vcpu, sg, prefix + PAGE_SIZE, NULL, true);
|
||||||
/*
|
/*
|
||||||
* We don't have to mprotect, we will be called for all unshadows.
|
* We don't have to mprotect, we will be called for all unshadows.
|
||||||
* SIE will detect if protection applies and trigger a validity.
|
* SIE will detect if protection applies and trigger a validity.
|
||||||
|
|
@ -952,6 +927,7 @@ static int inject_fault(struct kvm_vcpu *vcpu, __u16 code, __u64 vaddr,
|
||||||
*/
|
*/
|
||||||
static int handle_fault(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page, struct gmap *sg)
|
static int handle_fault(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page, struct gmap *sg)
|
||||||
{
|
{
|
||||||
|
bool wr = kvm_s390_cur_gmap_fault_is_write();
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
if ((current->thread.gmap_int_code & PGM_INT_CODE_MASK) == PGM_PROTECTION)
|
if ((current->thread.gmap_int_code & PGM_INT_CODE_MASK) == PGM_PROTECTION)
|
||||||
|
|
@ -959,11 +935,10 @@ static int handle_fault(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page, stru
|
||||||
return inject_fault(vcpu, PGM_PROTECTION,
|
return inject_fault(vcpu, PGM_PROTECTION,
|
||||||
current->thread.gmap_teid.addr * PAGE_SIZE, 1);
|
current->thread.gmap_teid.addr * PAGE_SIZE, 1);
|
||||||
|
|
||||||
rc = kvm_s390_shadow_fault(vcpu, sg, current->thread.gmap_teid.addr * PAGE_SIZE, NULL);
|
rc = gaccess_shadow_fault(vcpu, sg, current->thread.gmap_teid.addr * PAGE_SIZE, NULL, wr);
|
||||||
if (rc > 0) {
|
if (rc > 0) {
|
||||||
rc = inject_fault(vcpu, rc,
|
rc = inject_fault(vcpu, rc,
|
||||||
current->thread.gmap_teid.addr * PAGE_SIZE,
|
current->thread.gmap_teid.addr * PAGE_SIZE, wr);
|
||||||
kvm_s390_cur_gmap_fault_is_write());
|
|
||||||
if (rc >= 0)
|
if (rc >= 0)
|
||||||
vsie_page->fault_addr = current->thread.gmap_teid.addr * PAGE_SIZE;
|
vsie_page->fault_addr = current->thread.gmap_teid.addr * PAGE_SIZE;
|
||||||
}
|
}
|
||||||
|
|
@ -979,7 +954,7 @@ static int handle_fault(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page, stru
|
||||||
static void handle_last_fault(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page, struct gmap *sg)
|
static void handle_last_fault(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page, struct gmap *sg)
|
||||||
{
|
{
|
||||||
if (vsie_page->fault_addr)
|
if (vsie_page->fault_addr)
|
||||||
kvm_s390_shadow_fault(vcpu, sg, vsie_page->fault_addr, NULL);
|
gaccess_shadow_fault(vcpu, sg, vsie_page->fault_addr, NULL, true);
|
||||||
vsie_page->fault_addr = 0;
|
vsie_page->fault_addr = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1064,8 +1039,9 @@ static u64 vsie_get_register(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page,
|
||||||
static int vsie_handle_mvpg(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page, struct gmap *sg)
|
static int vsie_handle_mvpg(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page, struct gmap *sg)
|
||||||
{
|
{
|
||||||
struct kvm_s390_sie_block *scb_s = &vsie_page->scb_s;
|
struct kvm_s390_sie_block *scb_s = &vsie_page->scb_s;
|
||||||
unsigned long pei_dest, pei_src, src, dest, mask, prefix;
|
unsigned long src, dest, mask, prefix;
|
||||||
u64 *pei_block = &vsie_page->scb_o->mcic;
|
u64 *pei_block = &vsie_page->scb_o->mcic;
|
||||||
|
union mvpg_pei pei_dest, pei_src;
|
||||||
int edat, rc_dest, rc_src;
|
int edat, rc_dest, rc_src;
|
||||||
union ctlreg0 cr0;
|
union ctlreg0 cr0;
|
||||||
|
|
||||||
|
|
@ -1079,8 +1055,8 @@ static int vsie_handle_mvpg(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page,
|
||||||
src = vsie_get_register(vcpu, vsie_page, scb_s->ipb >> 16) & mask;
|
src = vsie_get_register(vcpu, vsie_page, scb_s->ipb >> 16) & mask;
|
||||||
src = _kvm_s390_real_to_abs(prefix, src) + scb_s->mso;
|
src = _kvm_s390_real_to_abs(prefix, src) + scb_s->mso;
|
||||||
|
|
||||||
rc_dest = kvm_s390_shadow_fault(vcpu, sg, dest, &pei_dest);
|
rc_dest = gaccess_shadow_fault(vcpu, sg, dest, &pei_dest, true);
|
||||||
rc_src = kvm_s390_shadow_fault(vcpu, sg, src, &pei_src);
|
rc_src = gaccess_shadow_fault(vcpu, sg, src, &pei_src, false);
|
||||||
/*
|
/*
|
||||||
* Either everything went well, or something non-critical went wrong
|
* Either everything went well, or something non-critical went wrong
|
||||||
* e.g. because of a race. In either case, simply retry.
|
* e.g. because of a race. In either case, simply retry.
|
||||||
|
|
@ -1115,8 +1091,8 @@ static int vsie_handle_mvpg(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page,
|
||||||
rc_src = rc_src != PGM_PAGE_TRANSLATION ? rc_src : 0;
|
rc_src = rc_src != PGM_PAGE_TRANSLATION ? rc_src : 0;
|
||||||
}
|
}
|
||||||
if (!rc_dest && !rc_src) {
|
if (!rc_dest && !rc_src) {
|
||||||
pei_block[0] = pei_dest;
|
pei_block[0] = pei_dest.val;
|
||||||
pei_block[1] = pei_src;
|
pei_block[1] = pei_src.val;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1187,7 +1163,7 @@ static int do_vsie_run(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page, struc
|
||||||
goto xfer_to_guest_mode_check;
|
goto xfer_to_guest_mode_check;
|
||||||
}
|
}
|
||||||
guest_timing_enter_irqoff();
|
guest_timing_enter_irqoff();
|
||||||
rc = kvm_s390_enter_exit_sie(scb_s, vcpu->run->s.regs.gprs, sg->asce);
|
rc = kvm_s390_enter_exit_sie(scb_s, vcpu->run->s.regs.gprs, sg->asce.val);
|
||||||
guest_timing_exit_irqoff();
|
guest_timing_exit_irqoff();
|
||||||
local_irq_enable();
|
local_irq_enable();
|
||||||
}
|
}
|
||||||
|
|
@ -1237,43 +1213,63 @@ static int do_vsie_run(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page, struc
|
||||||
|
|
||||||
static void release_gmap_shadow(struct vsie_page *vsie_page)
|
static void release_gmap_shadow(struct vsie_page *vsie_page)
|
||||||
{
|
{
|
||||||
if (vsie_page->gmap)
|
struct gmap *gmap = vsie_page->gmap_cache.gmap;
|
||||||
gmap_put(vsie_page->gmap);
|
|
||||||
WRITE_ONCE(vsie_page->gmap, NULL);
|
lockdep_assert_held(&gmap->kvm->arch.gmap->children_lock);
|
||||||
|
|
||||||
|
list_del(&vsie_page->gmap_cache.list);
|
||||||
|
vsie_page->gmap_cache.gmap = NULL;
|
||||||
prefix_unmapped(vsie_page);
|
prefix_unmapped(vsie_page);
|
||||||
|
|
||||||
|
if (list_empty(&gmap->scb_users)) {
|
||||||
|
gmap_remove_child(gmap);
|
||||||
|
gmap_put(gmap);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int acquire_gmap_shadow(struct kvm_vcpu *vcpu,
|
static struct gmap *acquire_gmap_shadow(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page)
|
||||||
struct vsie_page *vsie_page)
|
|
||||||
{
|
{
|
||||||
unsigned long asce;
|
|
||||||
union ctlreg0 cr0;
|
union ctlreg0 cr0;
|
||||||
struct gmap *gmap;
|
struct gmap *gmap;
|
||||||
|
union asce asce;
|
||||||
int edat;
|
int edat;
|
||||||
|
|
||||||
asce = vcpu->arch.sie_block->gcr[1];
|
asce.val = vcpu->arch.sie_block->gcr[1];
|
||||||
cr0.val = vcpu->arch.sie_block->gcr[0];
|
cr0.val = vcpu->arch.sie_block->gcr[0];
|
||||||
edat = cr0.edat && test_kvm_facility(vcpu->kvm, 8);
|
edat = cr0.edat && test_kvm_facility(vcpu->kvm, 8);
|
||||||
edat += edat && test_kvm_facility(vcpu->kvm, 78);
|
edat += edat && test_kvm_facility(vcpu->kvm, 78);
|
||||||
|
|
||||||
/*
|
scoped_guard(spinlock, &vcpu->kvm->arch.gmap->children_lock) {
|
||||||
* ASCE or EDAT could have changed since last icpt, or the gmap
|
gmap = vsie_page->gmap_cache.gmap;
|
||||||
* we're holding has been unshadowed. If the gmap is still valid,
|
if (gmap) {
|
||||||
* we can safely reuse it.
|
/*
|
||||||
*/
|
* ASCE or EDAT could have changed since last icpt, or the gmap
|
||||||
if (vsie_page->gmap && gmap_shadow_valid(vsie_page->gmap, asce, edat)) {
|
* we're holding has been unshadowed. If the gmap is still valid,
|
||||||
vcpu->kvm->stat.gmap_shadow_reuse++;
|
* we can safely reuse it.
|
||||||
return 0;
|
*/
|
||||||
|
if (gmap_is_shadow_valid(gmap, asce, edat)) {
|
||||||
|
vcpu->kvm->stat.gmap_shadow_reuse++;
|
||||||
|
gmap_get(gmap);
|
||||||
|
return gmap;
|
||||||
|
}
|
||||||
|
/* release the old shadow and mark the prefix as unmapped */
|
||||||
|
release_gmap_shadow(vsie_page);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
gmap = gmap_create_shadow(vcpu->arch.mc, vcpu->kvm->arch.gmap, asce, edat);
|
||||||
/* release the old shadow - if any, and mark the prefix as unmapped */
|
|
||||||
release_gmap_shadow(vsie_page);
|
|
||||||
gmap = gmap_shadow(vcpu->arch.gmap, asce, edat);
|
|
||||||
if (IS_ERR(gmap))
|
if (IS_ERR(gmap))
|
||||||
return PTR_ERR(gmap);
|
return gmap;
|
||||||
vcpu->kvm->stat.gmap_shadow_create++;
|
scoped_guard(spinlock, &vcpu->kvm->arch.gmap->children_lock) {
|
||||||
WRITE_ONCE(vsie_page->gmap, gmap);
|
/* unlikely race condition, remove the previous shadow */
|
||||||
return 0;
|
if (vsie_page->gmap_cache.gmap)
|
||||||
|
release_gmap_shadow(vsie_page);
|
||||||
|
vcpu->kvm->stat.gmap_shadow_create++;
|
||||||
|
list_add(&vsie_page->gmap_cache.list, &gmap->scb_users);
|
||||||
|
vsie_page->gmap_cache.gmap = gmap;
|
||||||
|
prefix_unmapped(vsie_page);
|
||||||
|
gmap_get(gmap);
|
||||||
|
}
|
||||||
|
return gmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -1330,8 +1326,11 @@ static int vsie_run(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page)
|
||||||
int rc = 0;
|
int rc = 0;
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
rc = acquire_gmap_shadow(vcpu, vsie_page);
|
sg = acquire_gmap_shadow(vcpu, vsie_page);
|
||||||
sg = vsie_page->gmap;
|
if (IS_ERR(sg)) {
|
||||||
|
rc = PTR_ERR(sg);
|
||||||
|
sg = NULL;
|
||||||
|
}
|
||||||
if (!rc)
|
if (!rc)
|
||||||
rc = map_prefix(vcpu, vsie_page, sg);
|
rc = map_prefix(vcpu, vsie_page, sg);
|
||||||
if (!rc) {
|
if (!rc) {
|
||||||
|
|
@ -1359,6 +1358,9 @@ static int vsie_run(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page)
|
||||||
kvm_s390_rewind_psw(vcpu, 4);
|
kvm_s390_rewind_psw(vcpu, 4);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (sg)
|
||||||
|
sg = gmap_put(sg);
|
||||||
|
cond_resched();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rc == -EFAULT) {
|
if (rc == -EFAULT) {
|
||||||
|
|
@ -1455,8 +1457,7 @@ static struct vsie_page *get_vsie_page(struct kvm *kvm, unsigned long addr)
|
||||||
vsie_page->scb_gpa = ULONG_MAX;
|
vsie_page->scb_gpa = ULONG_MAX;
|
||||||
|
|
||||||
/* Double use of the same address or allocation failure. */
|
/* Double use of the same address or allocation failure. */
|
||||||
if (radix_tree_insert(&kvm->arch.vsie.addr_to_page, addr >> 9,
|
if (radix_tree_insert(&kvm->arch.vsie.addr_to_page, addr >> 9, vsie_page)) {
|
||||||
vsie_page)) {
|
|
||||||
put_vsie_page(vsie_page);
|
put_vsie_page(vsie_page);
|
||||||
mutex_unlock(&kvm->arch.vsie.mutex);
|
mutex_unlock(&kvm->arch.vsie.mutex);
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
@ -1465,7 +1466,12 @@ static struct vsie_page *get_vsie_page(struct kvm *kvm, unsigned long addr)
|
||||||
mutex_unlock(&kvm->arch.vsie.mutex);
|
mutex_unlock(&kvm->arch.vsie.mutex);
|
||||||
|
|
||||||
memset(&vsie_page->scb_s, 0, sizeof(struct kvm_s390_sie_block));
|
memset(&vsie_page->scb_s, 0, sizeof(struct kvm_s390_sie_block));
|
||||||
release_gmap_shadow(vsie_page);
|
if (vsie_page->gmap_cache.gmap) {
|
||||||
|
scoped_guard(spinlock, &kvm->arch.gmap->children_lock)
|
||||||
|
if (vsie_page->gmap_cache.gmap)
|
||||||
|
release_gmap_shadow(vsie_page);
|
||||||
|
}
|
||||||
|
prefix_unmapped(vsie_page);
|
||||||
vsie_page->fault_addr = 0;
|
vsie_page->fault_addr = 0;
|
||||||
vsie_page->scb_s.ihcpu = 0xffffU;
|
vsie_page->scb_s.ihcpu = 0xffffU;
|
||||||
return vsie_page;
|
return vsie_page;
|
||||||
|
|
@ -1543,8 +1549,10 @@ void kvm_s390_vsie_destroy(struct kvm *kvm)
|
||||||
mutex_lock(&kvm->arch.vsie.mutex);
|
mutex_lock(&kvm->arch.vsie.mutex);
|
||||||
for (i = 0; i < kvm->arch.vsie.page_count; i++) {
|
for (i = 0; i < kvm->arch.vsie.page_count; i++) {
|
||||||
vsie_page = kvm->arch.vsie.pages[i];
|
vsie_page = kvm->arch.vsie.pages[i];
|
||||||
|
scoped_guard(spinlock, &kvm->arch.gmap->children_lock)
|
||||||
|
if (vsie_page->gmap_cache.gmap)
|
||||||
|
release_gmap_shadow(vsie_page);
|
||||||
kvm->arch.vsie.pages[i] = NULL;
|
kvm->arch.vsie.pages[i] = NULL;
|
||||||
release_gmap_shadow(vsie_page);
|
|
||||||
/* free the radix tree entry */
|
/* free the radix tree entry */
|
||||||
if (vsie_page->scb_gpa != ULONG_MAX)
|
if (vsie_page->scb_gpa != ULONG_MAX)
|
||||||
radix_tree_delete(&kvm->arch.vsie.addr_to_page,
|
radix_tree_delete(&kvm->arch.vsie.addr_to_page,
|
||||||
|
|
|
||||||
|
|
@ -34,136 +34,19 @@ void debug_user_asce(int exit)
|
||||||
}
|
}
|
||||||
#endif /*CONFIG_DEBUG_ENTRY */
|
#endif /*CONFIG_DEBUG_ENTRY */
|
||||||
|
|
||||||
union oac {
|
|
||||||
unsigned int val;
|
|
||||||
struct {
|
|
||||||
struct {
|
|
||||||
unsigned short key : 4;
|
|
||||||
unsigned short : 4;
|
|
||||||
unsigned short as : 2;
|
|
||||||
unsigned short : 4;
|
|
||||||
unsigned short k : 1;
|
|
||||||
unsigned short a : 1;
|
|
||||||
} oac1;
|
|
||||||
struct {
|
|
||||||
unsigned short key : 4;
|
|
||||||
unsigned short : 4;
|
|
||||||
unsigned short as : 2;
|
|
||||||
unsigned short : 4;
|
|
||||||
unsigned short k : 1;
|
|
||||||
unsigned short a : 1;
|
|
||||||
} oac2;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
static uaccess_kmsan_or_inline __must_check unsigned long
|
|
||||||
raw_copy_from_user_key(void *to, const void __user *from, unsigned long size, unsigned long key)
|
|
||||||
{
|
|
||||||
unsigned long osize;
|
|
||||||
union oac spec = {
|
|
||||||
.oac2.key = key,
|
|
||||||
.oac2.as = PSW_BITS_AS_SECONDARY,
|
|
||||||
.oac2.k = 1,
|
|
||||||
.oac2.a = 1,
|
|
||||||
};
|
|
||||||
int cc;
|
|
||||||
|
|
||||||
while (1) {
|
|
||||||
osize = size;
|
|
||||||
asm_inline volatile(
|
|
||||||
" lr %%r0,%[spec]\n"
|
|
||||||
"0: mvcos %[to],%[from],%[size]\n"
|
|
||||||
"1: nopr %%r7\n"
|
|
||||||
CC_IPM(cc)
|
|
||||||
EX_TABLE_UA_MVCOS_FROM(0b, 0b)
|
|
||||||
EX_TABLE_UA_MVCOS_FROM(1b, 0b)
|
|
||||||
: CC_OUT(cc, cc), [size] "+d" (size), [to] "=Q" (*(char *)to)
|
|
||||||
: [spec] "d" (spec.val), [from] "Q" (*(const char __user *)from)
|
|
||||||
: CC_CLOBBER_LIST("memory", "0"));
|
|
||||||
if (CC_TRANSFORM(cc) == 0)
|
|
||||||
return osize - size;
|
|
||||||
size -= 4096;
|
|
||||||
to += 4096;
|
|
||||||
from += 4096;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned long _copy_from_user_key(void *to, const void __user *from,
|
|
||||||
unsigned long n, unsigned long key)
|
|
||||||
{
|
|
||||||
unsigned long res = n;
|
|
||||||
|
|
||||||
might_fault();
|
|
||||||
if (!should_fail_usercopy()) {
|
|
||||||
instrument_copy_from_user_before(to, from, n);
|
|
||||||
res = raw_copy_from_user_key(to, from, n, key);
|
|
||||||
instrument_copy_from_user_after(to, from, n, res);
|
|
||||||
}
|
|
||||||
if (unlikely(res))
|
|
||||||
memset(to + (n - res), 0, res);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
EXPORT_SYMBOL(_copy_from_user_key);
|
|
||||||
|
|
||||||
static uaccess_kmsan_or_inline __must_check unsigned long
|
|
||||||
raw_copy_to_user_key(void __user *to, const void *from, unsigned long size, unsigned long key)
|
|
||||||
{
|
|
||||||
unsigned long osize;
|
|
||||||
union oac spec = {
|
|
||||||
.oac1.key = key,
|
|
||||||
.oac1.as = PSW_BITS_AS_SECONDARY,
|
|
||||||
.oac1.k = 1,
|
|
||||||
.oac1.a = 1,
|
|
||||||
};
|
|
||||||
int cc;
|
|
||||||
|
|
||||||
while (1) {
|
|
||||||
osize = size;
|
|
||||||
asm_inline volatile(
|
|
||||||
" lr %%r0,%[spec]\n"
|
|
||||||
"0: mvcos %[to],%[from],%[size]\n"
|
|
||||||
"1: nopr %%r7\n"
|
|
||||||
CC_IPM(cc)
|
|
||||||
EX_TABLE_UA_MVCOS_TO(0b, 0b)
|
|
||||||
EX_TABLE_UA_MVCOS_TO(1b, 0b)
|
|
||||||
: CC_OUT(cc, cc), [size] "+d" (size), [to] "=Q" (*(char __user *)to)
|
|
||||||
: [spec] "d" (spec.val), [from] "Q" (*(const char *)from)
|
|
||||||
: CC_CLOBBER_LIST("memory", "0"));
|
|
||||||
if (CC_TRANSFORM(cc) == 0)
|
|
||||||
return osize - size;
|
|
||||||
size -= 4096;
|
|
||||||
to += 4096;
|
|
||||||
from += 4096;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned long _copy_to_user_key(void __user *to, const void *from,
|
|
||||||
unsigned long n, unsigned long key)
|
|
||||||
{
|
|
||||||
might_fault();
|
|
||||||
if (should_fail_usercopy())
|
|
||||||
return n;
|
|
||||||
instrument_copy_to_user(to, from, n);
|
|
||||||
return raw_copy_to_user_key(to, from, n, key);
|
|
||||||
}
|
|
||||||
EXPORT_SYMBOL(_copy_to_user_key);
|
|
||||||
|
|
||||||
#define CMPXCHG_USER_KEY_MAX_LOOPS 128
|
#define CMPXCHG_USER_KEY_MAX_LOOPS 128
|
||||||
|
|
||||||
static nokprobe_inline int __cmpxchg_user_key_small(unsigned long address, unsigned int *uval,
|
static nokprobe_inline int __cmpxchg_key_small(void *address, unsigned int *uval,
|
||||||
unsigned int old, unsigned int new,
|
unsigned int old, unsigned int new,
|
||||||
unsigned int mask, unsigned long key)
|
unsigned int mask, unsigned long key)
|
||||||
{
|
{
|
||||||
unsigned long count;
|
unsigned long count;
|
||||||
unsigned int prev;
|
unsigned int prev;
|
||||||
bool sacf_flag;
|
|
||||||
int rc = 0;
|
int rc = 0;
|
||||||
|
|
||||||
skey_regions_initialize();
|
skey_regions_initialize();
|
||||||
sacf_flag = enable_sacf_uaccess();
|
|
||||||
asm_inline volatile(
|
asm_inline volatile(
|
||||||
"20: spka 0(%[key])\n"
|
"20: spka 0(%[key])\n"
|
||||||
" sacf 256\n"
|
|
||||||
" llill %[count],%[max_loops]\n"
|
" llill %[count],%[max_loops]\n"
|
||||||
"0: l %[prev],%[address]\n"
|
"0: l %[prev],%[address]\n"
|
||||||
"1: nr %[prev],%[mask]\n"
|
"1: nr %[prev],%[mask]\n"
|
||||||
|
|
@ -178,8 +61,7 @@ static nokprobe_inline int __cmpxchg_user_key_small(unsigned long address, unsig
|
||||||
" nr %[tmp],%[mask]\n"
|
" nr %[tmp],%[mask]\n"
|
||||||
" jnz 5f\n"
|
" jnz 5f\n"
|
||||||
" brct %[count],2b\n"
|
" brct %[count],2b\n"
|
||||||
"5: sacf 768\n"
|
"5: spka %[default_key]\n"
|
||||||
" spka %[default_key]\n"
|
|
||||||
"21:\n"
|
"21:\n"
|
||||||
EX_TABLE_UA_LOAD_REG(0b, 5b, %[rc], %[prev])
|
EX_TABLE_UA_LOAD_REG(0b, 5b, %[rc], %[prev])
|
||||||
EX_TABLE_UA_LOAD_REG(1b, 5b, %[rc], %[prev])
|
EX_TABLE_UA_LOAD_REG(1b, 5b, %[rc], %[prev])
|
||||||
|
|
@ -197,16 +79,16 @@ static nokprobe_inline int __cmpxchg_user_key_small(unsigned long address, unsig
|
||||||
[default_key] "J" (PAGE_DEFAULT_KEY),
|
[default_key] "J" (PAGE_DEFAULT_KEY),
|
||||||
[max_loops] "J" (CMPXCHG_USER_KEY_MAX_LOOPS)
|
[max_loops] "J" (CMPXCHG_USER_KEY_MAX_LOOPS)
|
||||||
: "memory", "cc");
|
: "memory", "cc");
|
||||||
disable_sacf_uaccess(sacf_flag);
|
|
||||||
*uval = prev;
|
*uval = prev;
|
||||||
if (!count)
|
if (!count)
|
||||||
rc = -EAGAIN;
|
rc = -EAGAIN;
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
int __kprobes __cmpxchg_user_key1(unsigned long address, unsigned char *uval,
|
int __kprobes __cmpxchg_key1(void *addr, unsigned char *uval, unsigned char old,
|
||||||
unsigned char old, unsigned char new, unsigned long key)
|
unsigned char new, unsigned long key)
|
||||||
{
|
{
|
||||||
|
unsigned long address = (unsigned long)addr;
|
||||||
unsigned int prev, shift, mask, _old, _new;
|
unsigned int prev, shift, mask, _old, _new;
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
|
|
@ -215,15 +97,16 @@ int __kprobes __cmpxchg_user_key1(unsigned long address, unsigned char *uval,
|
||||||
_old = (unsigned int)old << shift;
|
_old = (unsigned int)old << shift;
|
||||||
_new = (unsigned int)new << shift;
|
_new = (unsigned int)new << shift;
|
||||||
mask = ~(0xff << shift);
|
mask = ~(0xff << shift);
|
||||||
rc = __cmpxchg_user_key_small(address, &prev, _old, _new, mask, key);
|
rc = __cmpxchg_key_small((void *)address, &prev, _old, _new, mask, key);
|
||||||
*uval = prev >> shift;
|
*uval = prev >> shift;
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(__cmpxchg_user_key1);
|
EXPORT_SYMBOL(__cmpxchg_key1);
|
||||||
|
|
||||||
int __kprobes __cmpxchg_user_key2(unsigned long address, unsigned short *uval,
|
int __kprobes __cmpxchg_key2(void *addr, unsigned short *uval, unsigned short old,
|
||||||
unsigned short old, unsigned short new, unsigned long key)
|
unsigned short new, unsigned long key)
|
||||||
{
|
{
|
||||||
|
unsigned long address = (unsigned long)addr;
|
||||||
unsigned int prev, shift, mask, _old, _new;
|
unsigned int prev, shift, mask, _old, _new;
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
|
|
@ -232,27 +115,23 @@ int __kprobes __cmpxchg_user_key2(unsigned long address, unsigned short *uval,
|
||||||
_old = (unsigned int)old << shift;
|
_old = (unsigned int)old << shift;
|
||||||
_new = (unsigned int)new << shift;
|
_new = (unsigned int)new << shift;
|
||||||
mask = ~(0xffff << shift);
|
mask = ~(0xffff << shift);
|
||||||
rc = __cmpxchg_user_key_small(address, &prev, _old, _new, mask, key);
|
rc = __cmpxchg_key_small((void *)address, &prev, _old, _new, mask, key);
|
||||||
*uval = prev >> shift;
|
*uval = prev >> shift;
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(__cmpxchg_user_key2);
|
EXPORT_SYMBOL(__cmpxchg_key2);
|
||||||
|
|
||||||
int __kprobes __cmpxchg_user_key4(unsigned long address, unsigned int *uval,
|
int __kprobes __cmpxchg_key4(void *address, unsigned int *uval, unsigned int old,
|
||||||
unsigned int old, unsigned int new, unsigned long key)
|
unsigned int new, unsigned long key)
|
||||||
{
|
{
|
||||||
unsigned int prev = old;
|
unsigned int prev = old;
|
||||||
bool sacf_flag;
|
|
||||||
int rc = 0;
|
int rc = 0;
|
||||||
|
|
||||||
skey_regions_initialize();
|
skey_regions_initialize();
|
||||||
sacf_flag = enable_sacf_uaccess();
|
|
||||||
asm_inline volatile(
|
asm_inline volatile(
|
||||||
"20: spka 0(%[key])\n"
|
"20: spka 0(%[key])\n"
|
||||||
" sacf 256\n"
|
|
||||||
"0: cs %[prev],%[new],%[address]\n"
|
"0: cs %[prev],%[new],%[address]\n"
|
||||||
"1: sacf 768\n"
|
"1: spka %[default_key]\n"
|
||||||
" spka %[default_key]\n"
|
|
||||||
"21:\n"
|
"21:\n"
|
||||||
EX_TABLE_UA_LOAD_REG(0b, 1b, %[rc], %[prev])
|
EX_TABLE_UA_LOAD_REG(0b, 1b, %[rc], %[prev])
|
||||||
EX_TABLE_UA_LOAD_REG(1b, 1b, %[rc], %[prev])
|
EX_TABLE_UA_LOAD_REG(1b, 1b, %[rc], %[prev])
|
||||||
|
|
@ -264,27 +143,22 @@ int __kprobes __cmpxchg_user_key4(unsigned long address, unsigned int *uval,
|
||||||
[key] "a" (key << 4),
|
[key] "a" (key << 4),
|
||||||
[default_key] "J" (PAGE_DEFAULT_KEY)
|
[default_key] "J" (PAGE_DEFAULT_KEY)
|
||||||
: "memory", "cc");
|
: "memory", "cc");
|
||||||
disable_sacf_uaccess(sacf_flag);
|
|
||||||
*uval = prev;
|
*uval = prev;
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(__cmpxchg_user_key4);
|
EXPORT_SYMBOL(__cmpxchg_key4);
|
||||||
|
|
||||||
int __kprobes __cmpxchg_user_key8(unsigned long address, unsigned long *uval,
|
int __kprobes __cmpxchg_key8(void *address, unsigned long *uval, unsigned long old,
|
||||||
unsigned long old, unsigned long new, unsigned long key)
|
unsigned long new, unsigned long key)
|
||||||
{
|
{
|
||||||
unsigned long prev = old;
|
unsigned long prev = old;
|
||||||
bool sacf_flag;
|
|
||||||
int rc = 0;
|
int rc = 0;
|
||||||
|
|
||||||
skey_regions_initialize();
|
skey_regions_initialize();
|
||||||
sacf_flag = enable_sacf_uaccess();
|
|
||||||
asm_inline volatile(
|
asm_inline volatile(
|
||||||
"20: spka 0(%[key])\n"
|
"20: spka 0(%[key])\n"
|
||||||
" sacf 256\n"
|
|
||||||
"0: csg %[prev],%[new],%[address]\n"
|
"0: csg %[prev],%[new],%[address]\n"
|
||||||
"1: sacf 768\n"
|
"1: spka %[default_key]\n"
|
||||||
" spka %[default_key]\n"
|
|
||||||
"21:\n"
|
"21:\n"
|
||||||
EX_TABLE_UA_LOAD_REG(0b, 1b, %[rc], %[prev])
|
EX_TABLE_UA_LOAD_REG(0b, 1b, %[rc], %[prev])
|
||||||
EX_TABLE_UA_LOAD_REG(1b, 1b, %[rc], %[prev])
|
EX_TABLE_UA_LOAD_REG(1b, 1b, %[rc], %[prev])
|
||||||
|
|
@ -296,27 +170,22 @@ int __kprobes __cmpxchg_user_key8(unsigned long address, unsigned long *uval,
|
||||||
[key] "a" (key << 4),
|
[key] "a" (key << 4),
|
||||||
[default_key] "J" (PAGE_DEFAULT_KEY)
|
[default_key] "J" (PAGE_DEFAULT_KEY)
|
||||||
: "memory", "cc");
|
: "memory", "cc");
|
||||||
disable_sacf_uaccess(sacf_flag);
|
|
||||||
*uval = prev;
|
*uval = prev;
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(__cmpxchg_user_key8);
|
EXPORT_SYMBOL(__cmpxchg_key8);
|
||||||
|
|
||||||
int __kprobes __cmpxchg_user_key16(unsigned long address, __uint128_t *uval,
|
int __kprobes __cmpxchg_key16(void *address, __uint128_t *uval, __uint128_t old,
|
||||||
__uint128_t old, __uint128_t new, unsigned long key)
|
__uint128_t new, unsigned long key)
|
||||||
{
|
{
|
||||||
__uint128_t prev = old;
|
__uint128_t prev = old;
|
||||||
bool sacf_flag;
|
|
||||||
int rc = 0;
|
int rc = 0;
|
||||||
|
|
||||||
skey_regions_initialize();
|
skey_regions_initialize();
|
||||||
sacf_flag = enable_sacf_uaccess();
|
|
||||||
asm_inline volatile(
|
asm_inline volatile(
|
||||||
"20: spka 0(%[key])\n"
|
"20: spka 0(%[key])\n"
|
||||||
" sacf 256\n"
|
|
||||||
"0: cdsg %[prev],%[new],%[address]\n"
|
"0: cdsg %[prev],%[new],%[address]\n"
|
||||||
"1: sacf 768\n"
|
"1: spka %[default_key]\n"
|
||||||
" spka %[default_key]\n"
|
|
||||||
"21:\n"
|
"21:\n"
|
||||||
EX_TABLE_UA_LOAD_REGPAIR(0b, 1b, %[rc], %[prev])
|
EX_TABLE_UA_LOAD_REGPAIR(0b, 1b, %[rc], %[prev])
|
||||||
EX_TABLE_UA_LOAD_REGPAIR(1b, 1b, %[rc], %[prev])
|
EX_TABLE_UA_LOAD_REGPAIR(1b, 1b, %[rc], %[prev])
|
||||||
|
|
@ -328,8 +197,7 @@ int __kprobes __cmpxchg_user_key16(unsigned long address, __uint128_t *uval,
|
||||||
[key] "a" (key << 4),
|
[key] "a" (key << 4),
|
||||||
[default_key] "J" (PAGE_DEFAULT_KEY)
|
[default_key] "J" (PAGE_DEFAULT_KEY)
|
||||||
: "memory", "cc");
|
: "memory", "cc");
|
||||||
disable_sacf_uaccess(sacf_flag);
|
|
||||||
*uval = prev;
|
*uval = prev;
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(__cmpxchg_user_key16);
|
EXPORT_SYMBOL(__cmpxchg_key16);
|
||||||
|
|
|
||||||
|
|
@ -34,28 +34,6 @@ static void ptep_zap_softleaf_entry(struct mm_struct *mm, softleaf_t entry)
|
||||||
free_swap_and_cache(entry);
|
free_swap_and_cache(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline pgste_t pgste_get_lock(pte_t *ptep)
|
|
||||||
{
|
|
||||||
unsigned long value = 0;
|
|
||||||
#ifdef CONFIG_PGSTE
|
|
||||||
unsigned long *ptr = (unsigned long *)(ptep + PTRS_PER_PTE);
|
|
||||||
|
|
||||||
do {
|
|
||||||
value = __atomic64_or_barrier(PGSTE_PCL_BIT, ptr);
|
|
||||||
} while (value & PGSTE_PCL_BIT);
|
|
||||||
value |= PGSTE_PCL_BIT;
|
|
||||||
#endif
|
|
||||||
return __pgste(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void pgste_set_unlock(pte_t *ptep, pgste_t pgste)
|
|
||||||
{
|
|
||||||
#ifdef CONFIG_PGSTE
|
|
||||||
barrier();
|
|
||||||
WRITE_ONCE(*(unsigned long *)(ptep + PTRS_PER_PTE), pgste_val(pgste) & ~PGSTE_PCL_BIT);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* gmap_helper_zap_one_page() - discard a page if it was swapped.
|
* gmap_helper_zap_one_page() - discard a page if it was swapped.
|
||||||
* @mm: the mm
|
* @mm: the mm
|
||||||
|
|
@ -68,9 +46,7 @@ static inline void pgste_set_unlock(pte_t *ptep, pgste_t pgste)
|
||||||
void gmap_helper_zap_one_page(struct mm_struct *mm, unsigned long vmaddr)
|
void gmap_helper_zap_one_page(struct mm_struct *mm, unsigned long vmaddr)
|
||||||
{
|
{
|
||||||
struct vm_area_struct *vma;
|
struct vm_area_struct *vma;
|
||||||
unsigned long pgstev;
|
|
||||||
spinlock_t *ptl;
|
spinlock_t *ptl;
|
||||||
pgste_t pgste;
|
|
||||||
pte_t *ptep;
|
pte_t *ptep;
|
||||||
|
|
||||||
mmap_assert_locked(mm);
|
mmap_assert_locked(mm);
|
||||||
|
|
@ -85,18 +61,8 @@ void gmap_helper_zap_one_page(struct mm_struct *mm, unsigned long vmaddr)
|
||||||
if (unlikely(!ptep))
|
if (unlikely(!ptep))
|
||||||
return;
|
return;
|
||||||
if (pte_swap(*ptep)) {
|
if (pte_swap(*ptep)) {
|
||||||
preempt_disable();
|
ptep_zap_softleaf_entry(mm, softleaf_from_pte(*ptep));
|
||||||
pgste = pgste_get_lock(ptep);
|
pte_clear(mm, vmaddr, ptep);
|
||||||
pgstev = pgste_val(pgste);
|
|
||||||
|
|
||||||
if ((pgstev & _PGSTE_GPS_USAGE_MASK) == _PGSTE_GPS_USAGE_UNUSED ||
|
|
||||||
(pgstev & _PGSTE_GPS_ZERO)) {
|
|
||||||
ptep_zap_softleaf_entry(mm, softleaf_from_pte(*ptep));
|
|
||||||
pte_clear(mm, vmaddr, ptep);
|
|
||||||
}
|
|
||||||
|
|
||||||
pgste_set_unlock(ptep, pgste);
|
|
||||||
preempt_enable();
|
|
||||||
}
|
}
|
||||||
pte_unmap_unlock(ptep, ptl);
|
pte_unmap_unlock(ptep, ptl);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user