Commit f15587c8 authored by Christian Borntraeger's avatar Christian Borntraeger

Merge branch 'pvbase' of git://git.kernel.org/pub/scm/linux/kernel/git/kvms390/linux into HEAD

s390 base parts (non kvm) for protvirt
parents 11a48a5a a0f60f84
...@@ -3795,6 +3795,11 @@ ...@@ -3795,6 +3795,11 @@
before loading. before loading.
See Documentation/admin-guide/blockdev/ramdisk.rst. See Documentation/admin-guide/blockdev/ramdisk.rst.
prot_virt= [S390] enable hosting protected virtual machines
isolated from the hypervisor (if hardware supports
that).
Format: <bool>
psi= [KNL] Enable or disable pressure stall information psi= [KNL] Enable or disable pressure stall information
tracking. tracking.
Format: <bool> Format: <bool>
......
...@@ -37,7 +37,7 @@ CFLAGS_sclp_early_core.o += -I$(srctree)/drivers/s390/char ...@@ -37,7 +37,7 @@ CFLAGS_sclp_early_core.o += -I$(srctree)/drivers/s390/char
obj-y := head.o als.o startup.o mem_detect.o ipl_parm.o ipl_report.o obj-y := head.o als.o startup.o mem_detect.o ipl_parm.o ipl_report.o
obj-y += string.o ebcdic.o sclp_early_core.o mem.o ipl_vmparm.o cmdline.o obj-y += string.o ebcdic.o sclp_early_core.o mem.o ipl_vmparm.o cmdline.o
obj-y += version.o pgm_check_info.o ctype.o text_dma.o obj-y += version.o pgm_check_info.o ctype.o text_dma.o
obj-$(CONFIG_PROTECTED_VIRTUALIZATION_GUEST) += uv.o obj-$(findstring y, $(CONFIG_PROTECTED_VIRTUALIZATION_GUEST) $(CONFIG_PGSTE)) += uv.o
obj-$(CONFIG_RELOCATABLE) += machine_kexec_reloc.o obj-$(CONFIG_RELOCATABLE) += machine_kexec_reloc.o
obj-$(CONFIG_RANDOMIZE_BASE) += kaslr.o obj-$(CONFIG_RANDOMIZE_BASE) += kaslr.o
targets := bzImage startup.a section_cmp.boot.data section_cmp.boot.preserved.data $(obj-y) targets := bzImage startup.a section_cmp.boot.data section_cmp.boot.preserved.data $(obj-y)
......
...@@ -3,7 +3,13 @@ ...@@ -3,7 +3,13 @@
#include <asm/facility.h> #include <asm/facility.h>
#include <asm/sections.h> #include <asm/sections.h>
/* will be used in arch/s390/kernel/uv.c */
#ifdef CONFIG_PROTECTED_VIRTUALIZATION_GUEST
int __bootdata_preserved(prot_virt_guest); int __bootdata_preserved(prot_virt_guest);
#endif
#if IS_ENABLED(CONFIG_KVM)
struct uv_info __bootdata_preserved(uv_info);
#endif
void uv_query_info(void) void uv_query_info(void)
{ {
...@@ -19,7 +25,21 @@ void uv_query_info(void) ...@@ -19,7 +25,21 @@ void uv_query_info(void)
if (uv_call(0, (uint64_t)&uvcb) && uvcb.header.rc != 0x100) if (uv_call(0, (uint64_t)&uvcb) && uvcb.header.rc != 0x100)
return; return;
if (IS_ENABLED(CONFIG_KVM)) {
memcpy(uv_info.inst_calls_list, uvcb.inst_calls_list, sizeof(uv_info.inst_calls_list));
uv_info.uv_base_stor_len = uvcb.uv_base_stor_len;
uv_info.guest_base_stor_len = uvcb.conf_base_phys_stor_len;
uv_info.guest_virt_base_stor_len = uvcb.conf_base_virt_stor_len;
uv_info.guest_virt_var_stor_len = uvcb.conf_virt_var_stor_len;
uv_info.guest_cpu_stor_len = uvcb.cpu_stor_len;
uv_info.max_sec_stor_addr = ALIGN(uvcb.max_guest_stor_addr, PAGE_SIZE);
uv_info.max_num_sec_conf = uvcb.max_num_sec_conf;
uv_info.max_guest_cpus = uvcb.max_guest_cpus;
}
#ifdef CONFIG_PROTECTED_VIRTUALIZATION_GUEST
if (test_bit_inv(BIT_UVC_CMD_SET_SHARED_ACCESS, (unsigned long *)uvcb.inst_calls_list) && if (test_bit_inv(BIT_UVC_CMD_SET_SHARED_ACCESS, (unsigned long *)uvcb.inst_calls_list) &&
test_bit_inv(BIT_UVC_CMD_REMOVE_SHARED_ACCESS, (unsigned long *)uvcb.inst_calls_list)) test_bit_inv(BIT_UVC_CMD_REMOVE_SHARED_ACCESS, (unsigned long *)uvcb.inst_calls_list))
prot_virt_guest = 1; prot_virt_guest = 1;
#endif
} }
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#ifndef _ASM_S390_GMAP_H #ifndef _ASM_S390_GMAP_H
#define _ASM_S390_GMAP_H #define _ASM_S390_GMAP_H
#include <linux/radix-tree.h>
#include <linux/refcount.h> #include <linux/refcount.h>
/* Generic bits for GMAP notification on DAT table entry changes. */ /* Generic bits for GMAP notification on DAT table entry changes. */
...@@ -31,6 +32,7 @@ ...@@ -31,6 +32,7 @@
* @table: pointer to the page directory * @table: pointer to the page directory
* @asce: address space control element for gmap page table * @asce: address space control element for gmap page table
* @pfault_enabled: defines if pfaults are applicable for the guest * @pfault_enabled: defines if pfaults are applicable for the guest
* @guest_handle: protected virtual machine handle for the ultravisor
* @host_to_rmap: radix tree with gmap_rmap lists * @host_to_rmap: radix tree with gmap_rmap lists
* @children: list of shadow gmap structures * @children: list of shadow gmap structures
* @pt_list: list of all page tables used in the shadow guest address space * @pt_list: list of all page tables used in the shadow guest address space
...@@ -54,6 +56,8 @@ struct gmap { ...@@ -54,6 +56,8 @@ struct gmap {
unsigned long asce_end; unsigned long asce_end;
void *private; void *private;
bool pfault_enabled; bool pfault_enabled;
/* only set for protected virtual machines */
unsigned long guest_handle;
/* Additional data for shadow guest address spaces */ /* Additional data for shadow guest address spaces */
struct radix_tree_root host_to_rmap; struct radix_tree_root host_to_rmap;
struct list_head children; struct list_head children;
......
...@@ -16,6 +16,8 @@ typedef struct { ...@@ -16,6 +16,8 @@ typedef struct {
unsigned long asce; unsigned long asce;
unsigned long asce_limit; unsigned long asce_limit;
unsigned long vdso_base; unsigned long vdso_base;
/* The mmu context belongs to a secure guest. */
atomic_t is_protected;
/* /*
* The following bitfields need a down_write on the mm * The following bitfields need a down_write on the mm
* semaphore when they are written to. As they are only * semaphore when they are written to. As they are only
......
...@@ -23,6 +23,7 @@ static inline int init_new_context(struct task_struct *tsk, ...@@ -23,6 +23,7 @@ static inline int init_new_context(struct task_struct *tsk,
INIT_LIST_HEAD(&mm->context.gmap_list); INIT_LIST_HEAD(&mm->context.gmap_list);
cpumask_clear(&mm->context.cpu_attach_mask); cpumask_clear(&mm->context.cpu_attach_mask);
atomic_set(&mm->context.flush_count, 0); atomic_set(&mm->context.flush_count, 0);
atomic_set(&mm->context.is_protected, 0);
mm->context.gmap_asce = 0; mm->context.gmap_asce = 0;
mm->context.flush_mm = 0; mm->context.flush_mm = 0;
mm->context.compat_mm = test_thread_flag(TIF_31BIT); mm->context.compat_mm = test_thread_flag(TIF_31BIT);
......
...@@ -153,6 +153,11 @@ static inline int devmem_is_allowed(unsigned long pfn) ...@@ -153,6 +153,11 @@ static inline int devmem_is_allowed(unsigned long pfn)
#define HAVE_ARCH_FREE_PAGE #define HAVE_ARCH_FREE_PAGE
#define HAVE_ARCH_ALLOC_PAGE #define HAVE_ARCH_ALLOC_PAGE
#if IS_ENABLED(CONFIG_PGSTE)
int arch_make_page_accessible(struct page *page);
#define HAVE_ARCH_MAKE_PAGE_ACCESSIBLE
#endif
#endif /* !__ASSEMBLY__ */ #endif /* !__ASSEMBLY__ */
#define __PAGE_OFFSET 0x0UL #define __PAGE_OFFSET 0x0UL
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
#include <linux/atomic.h> #include <linux/atomic.h>
#include <asm/bug.h> #include <asm/bug.h>
#include <asm/page.h> #include <asm/page.h>
#include <asm/uv.h>
extern pgd_t swapper_pg_dir[]; extern pgd_t swapper_pg_dir[];
extern void paging_init(void); extern void paging_init(void);
...@@ -520,6 +521,15 @@ static inline int mm_has_pgste(struct mm_struct *mm) ...@@ -520,6 +521,15 @@ static inline int mm_has_pgste(struct mm_struct *mm)
return 0; return 0;
} }
static inline int mm_is_protected(struct mm_struct *mm)
{
#ifdef CONFIG_PGSTE
if (unlikely(atomic_read(&mm->context.is_protected)))
return 1;
#endif
return 0;
}
static inline int mm_alloc_pgste(struct mm_struct *mm) static inline int mm_alloc_pgste(struct mm_struct *mm)
{ {
#ifdef CONFIG_PGSTE #ifdef CONFIG_PGSTE
...@@ -1061,7 +1071,12 @@ static inline int ptep_clear_flush_young(struct vm_area_struct *vma, ...@@ -1061,7 +1071,12 @@ static inline int ptep_clear_flush_young(struct vm_area_struct *vma,
static inline pte_t ptep_get_and_clear(struct mm_struct *mm, static inline pte_t ptep_get_and_clear(struct mm_struct *mm,
unsigned long addr, pte_t *ptep) unsigned long addr, pte_t *ptep)
{ {
return ptep_xchg_lazy(mm, addr, ptep, __pte(_PAGE_INVALID)); pte_t res;
res = ptep_xchg_lazy(mm, addr, ptep, __pte(_PAGE_INVALID));
if (mm_is_protected(mm) && pte_present(res))
uv_convert_from_secure(pte_val(res) & PAGE_MASK);
return res;
} }
#define __HAVE_ARCH_PTEP_MODIFY_PROT_TRANSACTION #define __HAVE_ARCH_PTEP_MODIFY_PROT_TRANSACTION
...@@ -1073,7 +1088,12 @@ void ptep_modify_prot_commit(struct vm_area_struct *, unsigned long, ...@@ -1073,7 +1088,12 @@ void ptep_modify_prot_commit(struct vm_area_struct *, unsigned long,
static inline pte_t ptep_clear_flush(struct vm_area_struct *vma, static inline pte_t ptep_clear_flush(struct vm_area_struct *vma,
unsigned long addr, pte_t *ptep) unsigned long addr, pte_t *ptep)
{ {
return ptep_xchg_direct(vma->vm_mm, addr, ptep, __pte(_PAGE_INVALID)); pte_t res;
res = ptep_xchg_direct(vma->vm_mm, addr, ptep, __pte(_PAGE_INVALID));
if (mm_is_protected(vma->vm_mm) && pte_present(res))
uv_convert_from_secure(pte_val(res) & PAGE_MASK);
return res;
} }
/* /*
...@@ -1088,12 +1108,17 @@ static inline pte_t ptep_get_and_clear_full(struct mm_struct *mm, ...@@ -1088,12 +1108,17 @@ static inline pte_t ptep_get_and_clear_full(struct mm_struct *mm,
unsigned long addr, unsigned long addr,
pte_t *ptep, int full) pte_t *ptep, int full)
{ {
pte_t res;
if (full) { if (full) {
pte_t pte = *ptep; res = *ptep;
*ptep = __pte(_PAGE_INVALID); *ptep = __pte(_PAGE_INVALID);
return pte; } else {
res = ptep_xchg_lazy(mm, addr, ptep, __pte(_PAGE_INVALID));
} }
return ptep_xchg_lazy(mm, addr, ptep, __pte(_PAGE_INVALID)); if (mm_is_protected(mm) && pte_present(res))
uv_convert_from_secure(pte_val(res) & PAGE_MASK);
return res;
} }
#define __HAVE_ARCH_PTEP_SET_WRPROTECT #define __HAVE_ARCH_PTEP_SET_WRPROTECT
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#include <linux/errno.h> #include <linux/errno.h>
#include <linux/bug.h> #include <linux/bug.h>
#include <asm/page.h> #include <asm/page.h>
#include <asm/gmap.h>
#define UVC_RC_EXECUTED 0x0001 #define UVC_RC_EXECUTED 0x0001
#define UVC_RC_INV_CMD 0x0002 #define UVC_RC_INV_CMD 0x0002
...@@ -23,14 +24,24 @@ ...@@ -23,14 +24,24 @@
#define UVC_RC_NO_RESUME 0x0007 #define UVC_RC_NO_RESUME 0x0007
#define UVC_CMD_QUI 0x0001 #define UVC_CMD_QUI 0x0001
#define UVC_CMD_INIT_UV 0x000f
#define UVC_CMD_CONV_TO_SEC_STOR 0x0200
#define UVC_CMD_CONV_FROM_SEC_STOR 0x0201
#define UVC_CMD_PIN_PAGE_SHARED 0x0341
#define UVC_CMD_UNPIN_PAGE_SHARED 0x0342
#define UVC_CMD_SET_SHARED_ACCESS 0x1000 #define UVC_CMD_SET_SHARED_ACCESS 0x1000
#define UVC_CMD_REMOVE_SHARED_ACCESS 0x1001 #define UVC_CMD_REMOVE_SHARED_ACCESS 0x1001
/* Bits in installed uv calls */ /* Bits in installed uv calls */
enum uv_cmds_inst { enum uv_cmds_inst {
BIT_UVC_CMD_QUI = 0, BIT_UVC_CMD_QUI = 0,
BIT_UVC_CMD_INIT_UV = 1,
BIT_UVC_CMD_CONV_TO_SEC_STOR = 6,
BIT_UVC_CMD_CONV_FROM_SEC_STOR = 7,
BIT_UVC_CMD_SET_SHARED_ACCESS = 8, BIT_UVC_CMD_SET_SHARED_ACCESS = 8,
BIT_UVC_CMD_REMOVE_SHARED_ACCESS = 9, BIT_UVC_CMD_REMOVE_SHARED_ACCESS = 9,
BIT_UVC_CMD_PIN_PAGE_SHARED = 21,
BIT_UVC_CMD_UNPIN_PAGE_SHARED = 22,
}; };
struct uv_cb_header { struct uv_cb_header {
...@@ -44,7 +55,40 @@ struct uv_cb_qui { ...@@ -44,7 +55,40 @@ struct uv_cb_qui {
struct uv_cb_header header; struct uv_cb_header header;
u64 reserved08; u64 reserved08;
u64 inst_calls_list[4]; u64 inst_calls_list[4];
u64 reserved30[15]; u64 reserved30[2];
u64 uv_base_stor_len;
u64 reserved48;
u64 conf_base_phys_stor_len;
u64 conf_base_virt_stor_len;
u64 conf_virt_var_stor_len;
u64 cpu_stor_len;
u32 reserved70[3];
u32 max_num_sec_conf;
u64 max_guest_stor_addr;
u8 reserved88[158 - 136];
u16 max_guest_cpus;
u8 reserveda0[200 - 160];
} __packed __aligned(8);
struct uv_cb_init {
struct uv_cb_header header;
u64 reserved08[2];
u64 stor_origin;
u64 stor_len;
u64 reserved28[4];
} __packed __aligned(8);
struct uv_cb_cts {
struct uv_cb_header header;
u64 reserved08[2];
u64 guest_handle;
u64 gaddr;
} __packed __aligned(8);
struct uv_cb_cfs {
struct uv_cb_header header;
u64 reserved08[2];
u64 paddr;
} __packed __aligned(8); } __packed __aligned(8);
struct uv_cb_share { struct uv_cb_share {
...@@ -69,6 +113,20 @@ static inline int uv_call(unsigned long r1, unsigned long r2) ...@@ -69,6 +113,20 @@ static inline int uv_call(unsigned long r1, unsigned long r2)
return cc; return cc;
} }
struct uv_info {
unsigned long inst_calls_list[4];
unsigned long uv_base_stor_len;
unsigned long guest_base_stor_len;
unsigned long guest_virt_base_stor_len;
unsigned long guest_virt_var_stor_len;
unsigned long guest_cpu_stor_len;
unsigned long max_sec_stor_addr;
unsigned int max_num_sec_conf;
unsigned short max_guest_cpus;
};
extern struct uv_info uv_info;
#ifdef CONFIG_PROTECTED_VIRTUALIZATION_GUEST #ifdef CONFIG_PROTECTED_VIRTUALIZATION_GUEST
extern int prot_virt_guest; extern int prot_virt_guest;
...@@ -121,11 +179,40 @@ static inline int uv_remove_shared(unsigned long addr) ...@@ -121,11 +179,40 @@ static inline int uv_remove_shared(unsigned long addr)
return share(addr, UVC_CMD_REMOVE_SHARED_ACCESS); return share(addr, UVC_CMD_REMOVE_SHARED_ACCESS);
} }
void uv_query_info(void);
#else #else
#define is_prot_virt_guest() 0 #define is_prot_virt_guest() 0
static inline int uv_set_shared(unsigned long addr) { return 0; } static inline int uv_set_shared(unsigned long addr) { return 0; }
static inline int uv_remove_shared(unsigned long addr) { return 0; } static inline int uv_remove_shared(unsigned long addr) { return 0; }
#endif
#if IS_ENABLED(CONFIG_KVM)
extern int prot_virt_host;
static inline int is_prot_virt_host(void)
{
return prot_virt_host;
}
int gmap_make_secure(struct gmap *gmap, unsigned long gaddr, void *uvcb);
int uv_convert_from_secure(unsigned long paddr);
int gmap_convert_to_secure(struct gmap *gmap, unsigned long gaddr);
void setup_uv(void);
void adjust_to_uv_max(unsigned long *vmax);
#else
#define is_prot_virt_host() 0
static inline void setup_uv(void) {}
static inline void adjust_to_uv_max(unsigned long *vmax) {}
static inline int uv_convert_from_secure(unsigned long paddr)
{
return 0;
}
#endif
#if defined(CONFIG_PROTECTED_VIRTUALIZATION_GUEST) || IS_ENABLED(CONFIG_KVM)
void uv_query_info(void);
#else
static inline void uv_query_info(void) {} static inline void uv_query_info(void) {}
#endif #endif
......
...@@ -78,6 +78,7 @@ obj-$(CONFIG_PERF_EVENTS) += perf_cpum_cf_events.o perf_regs.o ...@@ -78,6 +78,7 @@ obj-$(CONFIG_PERF_EVENTS) += perf_cpum_cf_events.o perf_regs.o
obj-$(CONFIG_PERF_EVENTS) += perf_cpum_cf_diag.o obj-$(CONFIG_PERF_EVENTS) += perf_cpum_cf_diag.o
obj-$(CONFIG_TRACEPOINTS) += trace.o obj-$(CONFIG_TRACEPOINTS) += trace.o
obj-$(findstring y, $(CONFIG_PROTECTED_VIRTUALIZATION_GUEST) $(CONFIG_PGSTE)) += uv.o
# vdso # vdso
obj-y += vdso64/ obj-y += vdso64/
...@@ -24,6 +24,8 @@ asmlinkage void do_syscall_trace_exit(struct pt_regs *regs); ...@@ -24,6 +24,8 @@ asmlinkage void do_syscall_trace_exit(struct pt_regs *regs);
void do_protection_exception(struct pt_regs *regs); void do_protection_exception(struct pt_regs *regs);
void do_dat_exception(struct pt_regs *regs); void do_dat_exception(struct pt_regs *regs);
void do_secure_storage_access(struct pt_regs *regs);
void do_non_secure_storage_access(struct pt_regs *regs);
void addressing_exception(struct pt_regs *regs); void addressing_exception(struct pt_regs *regs);
void data_exception(struct pt_regs *regs); void data_exception(struct pt_regs *regs);
......
...@@ -78,8 +78,8 @@ PGM_CHECK(do_dat_exception) /* 39 */ ...@@ -78,8 +78,8 @@ PGM_CHECK(do_dat_exception) /* 39 */
PGM_CHECK(do_dat_exception) /* 3a */ PGM_CHECK(do_dat_exception) /* 3a */
PGM_CHECK(do_dat_exception) /* 3b */ PGM_CHECK(do_dat_exception) /* 3b */
PGM_CHECK_DEFAULT /* 3c */ PGM_CHECK_DEFAULT /* 3c */
PGM_CHECK_DEFAULT /* 3d */ PGM_CHECK(do_secure_storage_access) /* 3d */
PGM_CHECK_DEFAULT /* 3e */ PGM_CHECK(do_non_secure_storage_access) /* 3e */
PGM_CHECK_DEFAULT /* 3f */ PGM_CHECK_DEFAULT /* 3f */
PGM_CHECK(monitor_event_exception) /* 40 */ PGM_CHECK(monitor_event_exception) /* 40 */
PGM_CHECK_DEFAULT /* 41 */ PGM_CHECK_DEFAULT /* 41 */
......
...@@ -92,10 +92,6 @@ char elf_platform[ELF_PLATFORM_SIZE]; ...@@ -92,10 +92,6 @@ char elf_platform[ELF_PLATFORM_SIZE];
unsigned long int_hwcap = 0; unsigned long int_hwcap = 0;
#ifdef CONFIG_PROTECTED_VIRTUALIZATION_GUEST
int __bootdata_preserved(prot_virt_guest);
#endif
int __bootdata(noexec_disabled); int __bootdata(noexec_disabled);
int __bootdata(memory_end_set); int __bootdata(memory_end_set);
unsigned long __bootdata(memory_end); unsigned long __bootdata(memory_end);
...@@ -564,6 +560,9 @@ static void __init setup_memory_end(void) ...@@ -564,6 +560,9 @@ static void __init setup_memory_end(void)
vmax = _REGION1_SIZE; /* 4-level kernel page table */ vmax = _REGION1_SIZE; /* 4-level kernel page table */
} }
if (is_prot_virt_host())
adjust_to_uv_max(&vmax);
/* module area is at the end of the kernel address space. */ /* module area is at the end of the kernel address space. */
MODULES_END = vmax; MODULES_END = vmax;
MODULES_VADDR = MODULES_END - MODULES_LEN; MODULES_VADDR = MODULES_END - MODULES_LEN;
...@@ -1138,6 +1137,8 @@ void __init setup_arch(char **cmdline_p) ...@@ -1138,6 +1137,8 @@ void __init setup_arch(char **cmdline_p)
*/ */
memblock_trim_memory(1UL << (MAX_ORDER - 1 + PAGE_SHIFT)); memblock_trim_memory(1UL << (MAX_ORDER - 1 + PAGE_SHIFT));
if (is_prot_virt_host())
setup_uv();
setup_memory_end(); setup_memory_end();
setup_memory(); setup_memory();
dma_contiguous_reserve(memory_end); dma_contiguous_reserve(memory_end);
......
// SPDX-License-Identifier: GPL-2.0
/*
* Common Ultravisor functions and initialization
*
* Copyright IBM Corp. 2019, 2020
*/
#define KMSG_COMPONENT "prot_virt"
#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/sizes.h>
#include <linux/bitmap.h>
#include <linux/memblock.h>
#include <linux/pagemap.h>
#include <linux/swap.h>
#include <asm/facility.h>
#include <asm/sections.h>
#include <asm/uv.h>
/* the bootdata_preserved fields come from ones in arch/s390/boot/uv.c */
#ifdef CONFIG_PROTECTED_VIRTUALIZATION_GUEST
int __bootdata_preserved(prot_virt_guest);
#endif
#if IS_ENABLED(CONFIG_KVM)
int prot_virt_host;
EXPORT_SYMBOL(prot_virt_host);
struct uv_info __bootdata_preserved(uv_info);
EXPORT_SYMBOL(uv_info);
static int __init prot_virt_setup(char *val)
{
bool enabled;
int rc;
rc = kstrtobool(val, &enabled);
if (!rc && enabled)
prot_virt_host = 1;
if (is_prot_virt_guest() && prot_virt_host) {
prot_virt_host = 0;
pr_warn("Protected virtualization not available in protected guests.");
}
if (prot_virt_host && !test_facility(158)) {
prot_virt_host = 0;
pr_warn("Protected virtualization not supported by the hardware.");
}
return rc;
}
early_param("prot_virt", prot_virt_setup);
static int __init uv_init(unsigned long stor_base, unsigned long stor_len)
{
struct uv_cb_init uvcb = {
.header.cmd = UVC_CMD_INIT_UV,
.header.len = sizeof(uvcb),
.stor_origin = stor_base,
.stor_len = stor_len,
};
if (uv_call(0, (uint64_t)&uvcb)) {
pr_err("Ultravisor init failed with rc: 0x%x rrc: 0%x\n",
uvcb.header.rc, uvcb.header.rrc);
return -1;
}
return 0;
}
void __init setup_uv(void)
{
unsigned long uv_stor_base;
uv_stor_base = (unsigned long)memblock_alloc_try_nid(
uv_info.uv_base_stor_len, SZ_1M, SZ_2G,
MEMBLOCK_ALLOC_ACCESSIBLE, NUMA_NO_NODE);
if (!uv_stor_base) {
pr_warn("Failed to reserve %lu bytes for ultravisor base storage\n",
uv_info.uv_base_stor_len);
goto fail;
}
if (uv_init(uv_stor_base, uv_info.uv_base_stor_len)) {
memblock_free(uv_stor_base, uv_info.uv_base_stor_len);
goto fail;
}
pr_info("Reserving %luMB as ultravisor base storage\n",
uv_info.uv_base_stor_len >> 20);
return;
fail:
pr_info("Disabling support for protected virtualization");
prot_virt_host = 0;
}
void adjust_to_uv_max(unsigned long *vmax)
{
*vmax = min_t(unsigned long, *vmax, uv_info.max_sec_stor_addr);
}
/*
* Requests the Ultravisor to pin the page in the shared state. This will
* cause an intercept when the guest attempts to unshare the pinned page.
*/
static int uv_pin_shared(unsigned long paddr)
{
struct uv_cb_cfs uvcb = {
.header.cmd = UVC_CMD_PIN_PAGE_SHARED,
.header.len = sizeof(uvcb),
.paddr = paddr,
};
if (uv_call(0, (u64)&uvcb))
return -EINVAL;
return 0;
}
/*
* Requests the Ultravisor to encrypt a guest page and make it
* accessible to the host for paging (export).
*
* @paddr: Absolute host address of page to be exported
*/
int uv_convert_from_secure(unsigned long paddr)
{
struct uv_cb_cfs uvcb = {
.header.cmd = UVC_CMD_CONV_FROM_SEC_STOR,
.header.len = sizeof(uvcb),
.paddr = paddr
};
if (uv_call(0, (u64)&uvcb))
return -EINVAL;
return 0;
}
/*
* Calculate the expected ref_count for a page that would otherwise have no
* further pins. This was cribbed from similar functions in other places in
* the kernel, but with some slight modifications. We know that a secure
* page can not be a huge page for example.
*/
static int expected_page_refs(struct page *page)
{
int res;
res = page_mapcount(page);
if (PageSwapCache(page)) {
res++;
} else if (page_mapping(page)) {
res++;
if (page_has_private(page))
res++;
}
return res;
}
static int make_secure_pte(pte_t *ptep, unsigned long addr,
struct page *exp_page, struct uv_cb_header *uvcb)
{
pte_t entry = READ_ONCE(*ptep);
struct page *page;
int expected, rc = 0;
if (!pte_present(entry))
return -ENXIO;
if (pte_val(entry) & _PAGE_INVALID)
return -ENXIO;
page = pte_page(entry);
if (page != exp_page)
return -ENXIO;
if (PageWriteback(page))
return -EAGAIN;
expected = expected_page_refs(page);
if (!page_ref_freeze(page, expected))
return -EBUSY;
set_bit(PG_arch_1, &page->flags);
rc = uv_call(0, (u64)uvcb);
page_ref_unfreeze(page, expected);
/* Return -ENXIO if the page was not mapped, -EINVAL otherwise */
if (rc)
rc = uvcb->rc == 0x10a ? -ENXIO : -EINVAL;
return rc;
}
/*
* Requests the Ultravisor to make a page accessible to a guest.
* If it's brought in the first time, it will be cleared. If
* it has been exported before, it will be decrypted and integrity
* checked.
*/
int gmap_make_secure(struct gmap *gmap, unsigned long gaddr, void *uvcb)
{
struct vm_area_struct *vma;
bool local_drain = false;
spinlock_t *ptelock;
unsigned long uaddr;
struct page *page;
pte_t *ptep;
int rc;
again:
rc = -EFAULT;
down_read(&gmap->mm->mmap_sem);
uaddr = __gmap_translate(gmap, gaddr);
if (IS_ERR_VALUE(uaddr))
goto out;
vma = find_vma(gmap->mm, uaddr);
if (!vma)
goto out;
/*
* 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 is playing dirty tricky with mapping huge pages later
* on this will result in a segmentation fault.
*/
if (is_vm_hugetlb_page(vma))
goto out;
rc = -ENXIO;
page = follow_page(vma, uaddr, FOLL_WRITE);
if (IS_ERR_OR_NULL(page))
goto out;
lock_page(page);
ptep = get_locked_pte(gmap->mm, uaddr, &ptelock);
rc = make_secure_pte(ptep, uaddr, page, uvcb);
pte_unmap_unlock(ptep, ptelock);
unlock_page(page);
out:
up_read(&gmap->mm->mmap_sem);
if (rc == -EAGAIN) {
wait_on_page_writeback(page);
} else if (rc == -EBUSY) {
/*
* If we have tried a local drain and the page refcount
* still does not match our expected safe value, try with a
* system wide drain. This is needed if the pagevecs holding
* the page are on a different CPU.
*/
if (local_drain) {
lru_add_drain_all();
/* We give up here, and let the caller try again */
return -EAGAIN;
}
/*
* We are here if the page refcount does not match the
* expected safe value. The main culprits are usually
* pagevecs. With lru_add_drain() we drain the pagevecs
* on the local CPU so that hopefully the refcount will
* reach the expected safe value.
*/
lru_add_drain();
local_drain = true;
/* And now we try again immediately after draining */
goto again;
} else if (rc == -ENXIO) {
if (gmap_fault(gmap, gaddr, FAULT_FLAG_WRITE))
return -EFAULT;
return -EAGAIN;
}
return rc;
}
EXPORT_SYMBOL_GPL(gmap_make_secure);
int gmap_convert_to_secure(struct gmap *gmap, unsigned long gaddr)
{
struct uv_cb_cts uvcb = {
.header.cmd = UVC_CMD_CONV_TO_SEC_STOR,
.header.len = sizeof(uvcb),
.guest_handle = gmap->guest_handle,
.gaddr = gaddr,
};
return gmap_make_secure(gmap, gaddr, &uvcb);
}
EXPORT_SYMBOL_GPL(gmap_convert_to_secure);
/*
* To be called with the page locked or with an extra reference! This will
* prevent gmap_make_secure from touching the page concurrently. Having 2
* parallel make_page_accessible is fine, as the UV calls will become a
* no-op if the page is already exported.
*/
int arch_make_page_accessible(struct page *page)
{
int rc = 0;
/* Hugepage cannot be protected, so nothing to do */
if (PageHuge(page))
return 0;
/*
* PG_arch_1 is used in 3 places:
* 1. for kernel page tables during early boot
* 2. for storage keys of huge pages and KVM
* 3. As an indication that this page might be secure. This can
* overindicate, e.g. we set the bit before calling
* convert_to_secure.
* As secure pages are never huge, all 3 variants can co-exists.
*/
if (!test_bit(PG_arch_1, &page->flags))
return 0;
rc = uv_pin_shared(page_to_phys(page));
if (!rc) {
clear_bit(PG_arch_1, &page->flags);
return 0;
}
rc = uv_convert_from_secure(page_to_phys(page));
if (!rc) {
clear_bit(PG_arch_1, &page->flags);
return 0;
}
return rc;
}
EXPORT_SYMBOL_GPL(arch_make_page_accessible);
#endif
#if defined(CONFIG_PROTECTED_VIRTUALIZATION_GUEST) || IS_ENABLED(CONFIG_KVM)
static ssize_t uv_query_facilities(struct kobject *kobj,
struct kobj_attribute *attr, char *page)
{
return snprintf(page, PAGE_SIZE, "%lx\n%lx\n%lx\n%lx\n",
uv_info.inst_calls_list[0],
uv_info.inst_calls_list[1],
uv_info.inst_calls_list[2],
uv_info.inst_calls_list[3]);
}
static struct kobj_attribute uv_query_facilities_attr =
__ATTR(facilities, 0444, uv_query_facilities, NULL);
static ssize_t uv_query_max_guest_cpus(struct kobject *kobj,
struct kobj_attribute *attr, char *page)
{
return snprintf(page, PAGE_SIZE, "%d\n",
uv_info.max_guest_cpus);
}
static struct kobj_attribute uv_query_max_guest_cpus_attr =
__ATTR(max_cpus, 0444, uv_query_max_guest_cpus, NULL);
static ssize_t uv_query_max_guest_vms(struct kobject *kobj,
struct kobj_attribute *attr, char *page)
{
return snprintf(page, PAGE_SIZE, "%d\n",
uv_info.max_num_sec_conf);
}
static struct kobj_attribute uv_query_max_guest_vms_attr =
__ATTR(max_guests, 0444, uv_query_max_guest_vms, NULL);
static ssize_t uv_query_max_guest_addr(struct kobject *kobj,
struct kobj_attribute *attr, char *page)
{
return snprintf(page, PAGE_SIZE, "%lx\n",
uv_info.max_sec_stor_addr);
}
static struct kobj_attribute uv_query_max_guest_addr_attr =
__ATTR(max_address, 0444, uv_query_max_guest_addr, NULL);
static struct attribute *uv_query_attrs[] = {
&uv_query_facilities_attr.attr,
&uv_query_max_guest_cpus_attr.attr,
&uv_query_max_guest_vms_attr.attr,
&uv_query_max_guest_addr_attr.attr,
NULL,
};
static struct attribute_group uv_query_attr_group = {
.attrs = uv_query_attrs,
};
static struct kset *uv_query_kset;
static struct kobject *uv_kobj;
static int __init uv_info_init(void)
{
int rc = -ENOMEM;
if (!test_facility(158))
return 0;
uv_kobj = kobject_create_and_add("uv", firmware_kobj);
if (!uv_kobj)
return -ENOMEM;
uv_query_kset = kset_create_and_add("query", NULL, uv_kobj);
if (!uv_query_kset)
goto out_kobj;
rc = sysfs_create_group(&uv_query_kset->kobj, &uv_query_attr_group);
if (!rc)
return 0;
kset_unregister(uv_query_kset);
out_kobj:
kobject_del(uv_kobj);
kobject_put(uv_kobj);
return rc;
}
device_initcall(uv_info_init);
#endif
...@@ -38,6 +38,7 @@ ...@@ -38,6 +38,7 @@
#include <asm/irq.h> #include <asm/irq.h>
#include <asm/mmu_context.h> #include <asm/mmu_context.h>
#include <asm/facility.h> #include <asm/facility.h>
#include <asm/uv.h>
#include "../kernel/entry.h" #include "../kernel/entry.h"
#define __FAIL_ADDR_MASK -4096L #define __FAIL_ADDR_MASK -4096L
...@@ -816,3 +817,80 @@ static int __init pfault_irq_init(void) ...@@ -816,3 +817,80 @@ static int __init pfault_irq_init(void)
early_initcall(pfault_irq_init); early_initcall(pfault_irq_init);
#endif /* CONFIG_PFAULT */ #endif /* CONFIG_PFAULT */
#if IS_ENABLED(CONFIG_PGSTE)
void do_secure_storage_access(struct pt_regs *regs)
{
unsigned long addr = regs->int_parm_long & __FAIL_ADDR_MASK;
struct vm_area_struct *vma;
struct mm_struct *mm;
struct page *page;
int rc;
switch (get_fault_type(regs)) {
case USER_FAULT:
mm = current->mm;
down_read(&mm->mmap_sem);
vma = find_vma(mm, addr);
if (!vma) {
up_read(&mm->mmap_sem);
do_fault_error(regs, VM_READ | VM_WRITE, VM_FAULT_BADMAP);
break;
}
page = follow_page(vma, addr, FOLL_WRITE | FOLL_GET);
if (IS_ERR_OR_NULL(page)) {
up_read(&mm->mmap_sem);
break;
}
if (arch_make_page_accessible(page))
send_sig(SIGSEGV, current, 0);
put_page(page);
up_read(&mm->mmap_sem);
break;
case KERNEL_FAULT:
page = phys_to_page(addr);
if (unlikely(!try_get_page(page)))
break;
rc = arch_make_page_accessible(page);
put_page(page);
if (rc)
BUG();
break;
case VDSO_FAULT:
/* fallthrough */
case GMAP_FAULT:
/* fallthrough */
default:
do_fault_error(regs, VM_READ | VM_WRITE, VM_FAULT_BADMAP);
WARN_ON_ONCE(1);
}
}
NOKPROBE_SYMBOL(do_secure_storage_access);
void do_non_secure_storage_access(struct pt_regs *regs)
{
unsigned long gaddr = regs->int_parm_long & __FAIL_ADDR_MASK;
struct gmap *gmap = (struct gmap *)S390_lowcore.gmap;
if (get_fault_type(regs) != GMAP_FAULT) {
do_fault_error(regs, VM_READ | VM_WRITE, VM_FAULT_BADMAP);
WARN_ON_ONCE(1);
return;
}
if (gmap_convert_to_secure(gmap, gaddr) == -EINVAL)
send_sig(SIGSEGV, current, 0);
}
NOKPROBE_SYMBOL(do_non_secure_storage_access);
#else
void do_secure_storage_access(struct pt_regs *regs)
{
default_trap_handler(regs);
}
void do_non_secure_storage_access(struct pt_regs *regs)
{
default_trap_handler(regs);
}
#endif
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment