2017-11-24 22:00:35 +08:00
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
2007-10-22 18:52:47 +08:00
|
|
|
/*
|
2012-07-20 17:15:04 +08:00
|
|
|
* Copyright IBM Corp. 2007, 2011
|
2007-10-22 18:52:47 +08:00
|
|
|
* Author(s): Martin Schwidefsky <schwidefsky@de.ibm.com>
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/sched.h>
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/errno.h>
|
include cleanup: Update gfp.h and slab.h includes to prepare for breaking implicit slab.h inclusion from percpu.h
percpu.h is included by sched.h and module.h and thus ends up being
included when building most .c files. percpu.h includes slab.h which
in turn includes gfp.h making everything defined by the two files
universally available and complicating inclusion dependencies.
percpu.h -> slab.h dependency is about to be removed. Prepare for
this change by updating users of gfp and slab facilities include those
headers directly instead of assuming availability. As this conversion
needs to touch large number of source files, the following script is
used as the basis of conversion.
http://userweb.kernel.org/~tj/misc/slabh-sweep.py
The script does the followings.
* Scan files for gfp and slab usages and update includes such that
only the necessary includes are there. ie. if only gfp is used,
gfp.h, if slab is used, slab.h.
* When the script inserts a new include, it looks at the include
blocks and try to put the new include such that its order conforms
to its surrounding. It's put in the include block which contains
core kernel includes, in the same order that the rest are ordered -
alphabetical, Christmas tree, rev-Xmas-tree or at the end if there
doesn't seem to be any matching order.
* If the script can't find a place to put a new include (mostly
because the file doesn't have fitting include block), it prints out
an error message indicating which .h file needs to be added to the
file.
The conversion was done in the following steps.
1. The initial automatic conversion of all .c files updated slightly
over 4000 files, deleting around 700 includes and adding ~480 gfp.h
and ~3000 slab.h inclusions. The script emitted errors for ~400
files.
2. Each error was manually checked. Some didn't need the inclusion,
some needed manual addition while adding it to implementation .h or
embedding .c file was more appropriate for others. This step added
inclusions to around 150 files.
3. The script was run again and the output was compared to the edits
from #2 to make sure no file was left behind.
4. Several build tests were done and a couple of problems were fixed.
e.g. lib/decompress_*.c used malloc/free() wrappers around slab
APIs requiring slab.h to be added manually.
5. The script was run on all .h files but without automatically
editing them as sprinkling gfp.h and slab.h inclusions around .h
files could easily lead to inclusion dependency hell. Most gfp.h
inclusion directives were ignored as stuff from gfp.h was usually
wildly available and often used in preprocessor macros. Each
slab.h inclusion directive was examined and added manually as
necessary.
6. percpu.h was updated not to include slab.h.
7. Build test were done on the following configurations and failures
were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my
distributed build env didn't work with gcov compiles) and a few
more options had to be turned off depending on archs to make things
build (like ipr on powerpc/64 which failed due to missing writeq).
* x86 and x86_64 UP and SMP allmodconfig and a custom test config.
* powerpc and powerpc64 SMP allmodconfig
* sparc and sparc64 SMP allmodconfig
* ia64 SMP allmodconfig
* s390 SMP allmodconfig
* alpha SMP allmodconfig
* um on x86_64 SMP allmodconfig
8. percpu.h modifications were reverted so that it could be applied as
a separate patch and serve as bisection point.
Given the fact that I had only a couple of failures from tests on step
6, I'm fairly confident about the coverage of this conversion patch.
If there is a breakage, it's likely to be something in one of the arch
headers which should be easily discoverable easily on most builds of
the specific arch.
Signed-off-by: Tejun Heo <tj@kernel.org>
Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
2010-03-24 16:04:11 +08:00
|
|
|
#include <linux/gfp.h>
|
2007-10-22 18:52:47 +08:00
|
|
|
#include <linux/mm.h>
|
|
|
|
#include <linux/swap.h>
|
|
|
|
#include <linux/smp.h>
|
|
|
|
#include <linux/spinlock.h>
|
2010-10-25 22:10:11 +08:00
|
|
|
#include <linux/rcupdate.h>
|
2011-07-24 16:48:20 +08:00
|
|
|
#include <linux/slab.h>
|
2013-04-17 23:36:29 +08:00
|
|
|
#include <linux/swapops.h>
|
2015-04-15 19:23:26 +08:00
|
|
|
#include <linux/sysctl.h>
|
2014-10-23 18:09:17 +08:00
|
|
|
#include <linux/ksm.h>
|
|
|
|
#include <linux/mman.h>
|
2007-10-22 18:52:47 +08:00
|
|
|
|
|
|
|
#include <asm/pgtable.h>
|
|
|
|
#include <asm/pgalloc.h>
|
|
|
|
#include <asm/tlb.h>
|
|
|
|
#include <asm/tlbflush.h>
|
2008-02-10 01:24:37 +08:00
|
|
|
#include <asm/mmu_context.h>
|
2017-04-20 16:03:45 +08:00
|
|
|
#include <asm/page-states.h>
|
2007-10-22 18:52:47 +08:00
|
|
|
|
2016-07-26 22:53:09 +08:00
|
|
|
static inline void ptep_ipte_local(struct mm_struct *mm, unsigned long addr,
|
2016-07-26 23:02:31 +08:00
|
|
|
pte_t *ptep, int nodat)
|
2016-07-26 22:53:09 +08:00
|
|
|
{
|
|
|
|
unsigned long opt, asce;
|
|
|
|
|
|
|
|
if (MACHINE_HAS_TLB_GUEST) {
|
|
|
|
opt = 0;
|
|
|
|
asce = READ_ONCE(mm->context.gmap_asce);
|
2016-07-26 23:02:31 +08:00
|
|
|
if (asce == 0UL || nodat)
|
2016-07-26 22:53:09 +08:00
|
|
|
opt |= IPTE_NODAT;
|
2016-07-26 22:00:22 +08:00
|
|
|
if (asce != -1UL) {
|
|
|
|
asce = asce ? : mm->context.asce;
|
|
|
|
opt |= IPTE_GUEST_ASCE;
|
|
|
|
}
|
|
|
|
__ptep_ipte(addr, ptep, opt, asce, IPTE_LOCAL);
|
2016-07-26 22:53:09 +08:00
|
|
|
} else {
|
2016-07-26 22:00:22 +08:00
|
|
|
__ptep_ipte(addr, ptep, 0, 0, IPTE_LOCAL);
|
2016-07-26 22:53:09 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void ptep_ipte_global(struct mm_struct *mm, unsigned long addr,
|
2016-07-26 23:02:31 +08:00
|
|
|
pte_t *ptep, int nodat)
|
2016-07-26 22:53:09 +08:00
|
|
|
{
|
|
|
|
unsigned long opt, asce;
|
|
|
|
|
|
|
|
if (MACHINE_HAS_TLB_GUEST) {
|
|
|
|
opt = 0;
|
|
|
|
asce = READ_ONCE(mm->context.gmap_asce);
|
2016-07-26 23:02:31 +08:00
|
|
|
if (asce == 0UL || nodat)
|
2016-07-26 22:53:09 +08:00
|
|
|
opt |= IPTE_NODAT;
|
2016-07-26 22:00:22 +08:00
|
|
|
if (asce != -1UL) {
|
|
|
|
asce = asce ? : mm->context.asce;
|
|
|
|
opt |= IPTE_GUEST_ASCE;
|
|
|
|
}
|
|
|
|
__ptep_ipte(addr, ptep, opt, asce, IPTE_GLOBAL);
|
2016-07-26 22:53:09 +08:00
|
|
|
} else {
|
2016-07-26 22:00:22 +08:00
|
|
|
__ptep_ipte(addr, ptep, 0, 0, IPTE_GLOBAL);
|
2016-07-26 22:53:09 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-08 18:08:09 +08:00
|
|
|
static inline pte_t ptep_flush_direct(struct mm_struct *mm,
|
2016-07-26 23:02:31 +08:00
|
|
|
unsigned long addr, pte_t *ptep,
|
|
|
|
int nodat)
|
2016-03-08 18:08:09 +08:00
|
|
|
{
|
|
|
|
pte_t old;
|
|
|
|
|
|
|
|
old = *ptep;
|
|
|
|
if (unlikely(pte_val(old) & _PAGE_INVALID))
|
|
|
|
return old;
|
2016-05-25 15:45:26 +08:00
|
|
|
atomic_inc(&mm->context.flush_count);
|
|
|
|
if (MACHINE_HAS_TLB_LC &&
|
2016-03-08 18:08:09 +08:00
|
|
|
cpumask_equal(mm_cpumask(mm), cpumask_of(smp_processor_id())))
|
2016-07-26 23:02:31 +08:00
|
|
|
ptep_ipte_local(mm, addr, ptep, nodat);
|
2016-03-08 18:08:09 +08:00
|
|
|
else
|
2016-07-26 23:02:31 +08:00
|
|
|
ptep_ipte_global(mm, addr, ptep, nodat);
|
2016-05-25 15:45:26 +08:00
|
|
|
atomic_dec(&mm->context.flush_count);
|
2016-03-08 18:08:09 +08:00
|
|
|
return old;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline pte_t ptep_flush_lazy(struct mm_struct *mm,
|
2016-07-26 23:02:31 +08:00
|
|
|
unsigned long addr, pte_t *ptep,
|
|
|
|
int nodat)
|
2016-03-08 18:08:09 +08:00
|
|
|
{
|
|
|
|
pte_t old;
|
|
|
|
|
|
|
|
old = *ptep;
|
|
|
|
if (unlikely(pte_val(old) & _PAGE_INVALID))
|
|
|
|
return old;
|
2016-05-25 15:45:26 +08:00
|
|
|
atomic_inc(&mm->context.flush_count);
|
|
|
|
if (cpumask_equal(&mm->context.cpu_attach_mask,
|
|
|
|
cpumask_of(smp_processor_id()))) {
|
2016-03-08 18:08:09 +08:00
|
|
|
pte_val(*ptep) |= _PAGE_INVALID;
|
|
|
|
mm->context.flush_mm = 1;
|
|
|
|
} else
|
2016-07-26 23:02:31 +08:00
|
|
|
ptep_ipte_global(mm, addr, ptep, nodat);
|
2016-05-25 15:45:26 +08:00
|
|
|
atomic_dec(&mm->context.flush_count);
|
2016-03-08 18:08:09 +08:00
|
|
|
return old;
|
|
|
|
}
|
|
|
|
|
2016-03-08 18:49:57 +08:00
|
|
|
static inline pgste_t pgste_get_lock(pte_t *ptep)
|
|
|
|
{
|
|
|
|
unsigned long new = 0;
|
|
|
|
#ifdef CONFIG_PGSTE
|
|
|
|
unsigned long old;
|
|
|
|
|
|
|
|
asm(
|
|
|
|
" lg %0,%2\n"
|
|
|
|
"0: lgr %1,%0\n"
|
|
|
|
" nihh %0,0xff7f\n" /* clear PCL bit in old */
|
|
|
|
" oihh %1,0x0080\n" /* set PCL bit in new */
|
|
|
|
" csg %0,%1,%2\n"
|
|
|
|
" jl 0b\n"
|
|
|
|
: "=&d" (old), "=&d" (new), "=Q" (ptep[PTRS_PER_PTE])
|
|
|
|
: "Q" (ptep[PTRS_PER_PTE]) : "cc", "memory");
|
|
|
|
#endif
|
|
|
|
return __pgste(new);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void pgste_set_unlock(pte_t *ptep, pgste_t pgste)
|
|
|
|
{
|
|
|
|
#ifdef CONFIG_PGSTE
|
|
|
|
asm(
|
|
|
|
" nihh %1,0xff7f\n" /* clear PCL bit */
|
|
|
|
" stg %1,%0\n"
|
|
|
|
: "=Q" (ptep[PTRS_PER_PTE])
|
|
|
|
: "d" (pgste_val(pgste)), "Q" (ptep[PTRS_PER_PTE])
|
|
|
|
: "cc", "memory");
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline pgste_t pgste_get(pte_t *ptep)
|
|
|
|
{
|
|
|
|
unsigned long pgste = 0;
|
|
|
|
#ifdef CONFIG_PGSTE
|
|
|
|
pgste = *(unsigned long *)(ptep + PTRS_PER_PTE);
|
|
|
|
#endif
|
|
|
|
return __pgste(pgste);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void pgste_set(pte_t *ptep, pgste_t pgste)
|
|
|
|
{
|
|
|
|
#ifdef CONFIG_PGSTE
|
|
|
|
*(pgste_t *)(ptep + PTRS_PER_PTE) = pgste;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2016-03-08 18:08:09 +08:00
|
|
|
static inline pgste_t pgste_update_all(pte_t pte, pgste_t pgste,
|
|
|
|
struct mm_struct *mm)
|
|
|
|
{
|
|
|
|
#ifdef CONFIG_PGSTE
|
|
|
|
unsigned long address, bits, skey;
|
|
|
|
|
2018-02-15 23:33:47 +08:00
|
|
|
if (!mm_uses_skeys(mm) || pte_val(pte) & _PAGE_INVALID)
|
2016-03-08 18:08:09 +08:00
|
|
|
return pgste;
|
|
|
|
address = pte_val(pte) & PAGE_MASK;
|
|
|
|
skey = (unsigned long) page_get_storage_key(address);
|
|
|
|
bits = skey & (_PAGE_CHANGED | _PAGE_REFERENCED);
|
|
|
|
/* Transfer page changed & referenced bit to guest bits in pgste */
|
|
|
|
pgste_val(pgste) |= bits << 48; /* GR bit & GC bit */
|
|
|
|
/* Copy page access key and fetch protection bit to pgste */
|
|
|
|
pgste_val(pgste) &= ~(PGSTE_ACC_BITS | PGSTE_FP_BIT);
|
|
|
|
pgste_val(pgste) |= (skey & (_PAGE_ACC_BITS | _PAGE_FP_BIT)) << 56;
|
|
|
|
#endif
|
|
|
|
return pgste;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void pgste_set_key(pte_t *ptep, pgste_t pgste, pte_t entry,
|
|
|
|
struct mm_struct *mm)
|
|
|
|
{
|
|
|
|
#ifdef CONFIG_PGSTE
|
|
|
|
unsigned long address;
|
|
|
|
unsigned long nkey;
|
|
|
|
|
2018-02-15 23:33:47 +08:00
|
|
|
if (!mm_uses_skeys(mm) || pte_val(entry) & _PAGE_INVALID)
|
2016-03-08 18:08:09 +08:00
|
|
|
return;
|
|
|
|
VM_BUG_ON(!(pte_val(*ptep) & _PAGE_INVALID));
|
|
|
|
address = pte_val(entry) & PAGE_MASK;
|
|
|
|
/*
|
|
|
|
* Set page access key and fetch protection bit from pgste.
|
|
|
|
* The guest C/R information is still in the PGSTE, set real
|
|
|
|
* key C/R to 0.
|
|
|
|
*/
|
|
|
|
nkey = (pgste_val(pgste) & (PGSTE_ACC_BITS | PGSTE_FP_BIT)) >> 56;
|
|
|
|
nkey |= (pgste_val(pgste) & (PGSTE_GR_BIT | PGSTE_GC_BIT)) >> 48;
|
|
|
|
page_set_storage_key(address, nkey, 0);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline pgste_t pgste_set_pte(pte_t *ptep, pgste_t pgste, pte_t entry)
|
|
|
|
{
|
|
|
|
#ifdef CONFIG_PGSTE
|
|
|
|
if ((pte_val(entry) & _PAGE_PRESENT) &&
|
|
|
|
(pte_val(entry) & _PAGE_WRITE) &&
|
|
|
|
!(pte_val(entry) & _PAGE_INVALID)) {
|
|
|
|
if (!MACHINE_HAS_ESOP) {
|
|
|
|
/*
|
|
|
|
* Without enhanced suppression-on-protection force
|
|
|
|
* the dirty bit on for all writable ptes.
|
|
|
|
*/
|
|
|
|
pte_val(entry) |= _PAGE_DIRTY;
|
|
|
|
pte_val(entry) &= ~_PAGE_PROTECT;
|
|
|
|
}
|
|
|
|
if (!(pte_val(entry) & _PAGE_PROTECT))
|
|
|
|
/* This pte allows write access, set user-dirty */
|
|
|
|
pgste_val(pgste) |= PGSTE_UC_BIT;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
*ptep = entry;
|
|
|
|
return pgste;
|
|
|
|
}
|
|
|
|
|
2016-03-08 18:54:42 +08:00
|
|
|
static inline pgste_t pgste_pte_notify(struct mm_struct *mm,
|
|
|
|
unsigned long addr,
|
|
|
|
pte_t *ptep, pgste_t pgste)
|
2016-03-08 18:08:09 +08:00
|
|
|
{
|
|
|
|
#ifdef CONFIG_PGSTE
|
2016-03-08 19:12:18 +08:00
|
|
|
unsigned long bits;
|
|
|
|
|
|
|
|
bits = pgste_val(pgste) & (PGSTE_IN_BIT | PGSTE_VSIE_BIT);
|
|
|
|
if (bits) {
|
|
|
|
pgste_val(pgste) ^= bits;
|
|
|
|
ptep_notify(mm, addr, ptep, bits);
|
2016-03-08 18:08:09 +08:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
return pgste;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline pgste_t ptep_xchg_start(struct mm_struct *mm,
|
|
|
|
unsigned long addr, pte_t *ptep)
|
|
|
|
{
|
|
|
|
pgste_t pgste = __pgste(0);
|
|
|
|
|
|
|
|
if (mm_has_pgste(mm)) {
|
|
|
|
pgste = pgste_get_lock(ptep);
|
2016-03-08 18:54:42 +08:00
|
|
|
pgste = pgste_pte_notify(mm, addr, ptep, pgste);
|
2016-03-08 18:08:09 +08:00
|
|
|
}
|
|
|
|
return pgste;
|
|
|
|
}
|
|
|
|
|
2017-01-24 05:59:44 +08:00
|
|
|
static inline pte_t ptep_xchg_commit(struct mm_struct *mm,
|
2016-03-08 18:08:09 +08:00
|
|
|
unsigned long addr, pte_t *ptep,
|
|
|
|
pgste_t pgste, pte_t old, pte_t new)
|
|
|
|
{
|
|
|
|
if (mm_has_pgste(mm)) {
|
|
|
|
if (pte_val(old) & _PAGE_INVALID)
|
|
|
|
pgste_set_key(ptep, pgste, new, mm);
|
|
|
|
if (pte_val(new) & _PAGE_INVALID) {
|
|
|
|
pgste = pgste_update_all(old, pgste, mm);
|
|
|
|
if ((pgste_val(pgste) & _PGSTE_GPS_USAGE_MASK) ==
|
|
|
|
_PGSTE_GPS_USAGE_UNUSED)
|
|
|
|
pte_val(old) |= _PAGE_UNUSED;
|
|
|
|
}
|
|
|
|
pgste = pgste_set_pte(ptep, pgste, new);
|
|
|
|
pgste_set_unlock(ptep, pgste);
|
|
|
|
} else {
|
|
|
|
*ptep = new;
|
|
|
|
}
|
2017-01-24 05:59:44 +08:00
|
|
|
return old;
|
2016-03-08 18:08:09 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
pte_t ptep_xchg_direct(struct mm_struct *mm, unsigned long addr,
|
|
|
|
pte_t *ptep, pte_t new)
|
|
|
|
{
|
|
|
|
pgste_t pgste;
|
|
|
|
pte_t old;
|
2016-07-26 23:02:31 +08:00
|
|
|
int nodat;
|
2016-03-08 18:08:09 +08:00
|
|
|
|
2016-06-06 16:30:45 +08:00
|
|
|
preempt_disable();
|
2016-03-08 18:08:09 +08:00
|
|
|
pgste = ptep_xchg_start(mm, addr, ptep);
|
2016-07-26 23:02:31 +08:00
|
|
|
nodat = !!(pgste_val(pgste) & _PGSTE_GPS_NODAT);
|
|
|
|
old = ptep_flush_direct(mm, addr, ptep, nodat);
|
2017-01-24 05:59:44 +08:00
|
|
|
old = ptep_xchg_commit(mm, addr, ptep, pgste, old, new);
|
2016-06-06 16:30:45 +08:00
|
|
|
preempt_enable();
|
2016-03-08 18:08:09 +08:00
|
|
|
return old;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(ptep_xchg_direct);
|
|
|
|
|
|
|
|
pte_t ptep_xchg_lazy(struct mm_struct *mm, unsigned long addr,
|
|
|
|
pte_t *ptep, pte_t new)
|
|
|
|
{
|
|
|
|
pgste_t pgste;
|
|
|
|
pte_t old;
|
2016-07-26 23:02:31 +08:00
|
|
|
int nodat;
|
2016-03-08 18:08:09 +08:00
|
|
|
|
2016-06-06 16:30:45 +08:00
|
|
|
preempt_disable();
|
2016-03-08 18:08:09 +08:00
|
|
|
pgste = ptep_xchg_start(mm, addr, ptep);
|
2016-07-26 23:02:31 +08:00
|
|
|
nodat = !!(pgste_val(pgste) & _PGSTE_GPS_NODAT);
|
|
|
|
old = ptep_flush_lazy(mm, addr, ptep, nodat);
|
2017-01-24 05:59:44 +08:00
|
|
|
old = ptep_xchg_commit(mm, addr, ptep, pgste, old, new);
|
2016-06-06 16:30:45 +08:00
|
|
|
preempt_enable();
|
2016-03-08 18:08:09 +08:00
|
|
|
return old;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(ptep_xchg_lazy);
|
|
|
|
|
2019-03-06 07:46:26 +08:00
|
|
|
pte_t ptep_modify_prot_start(struct vm_area_struct *vma, unsigned long addr,
|
2016-03-08 18:08:09 +08:00
|
|
|
pte_t *ptep)
|
|
|
|
{
|
|
|
|
pgste_t pgste;
|
|
|
|
pte_t old;
|
2016-07-26 23:02:31 +08:00
|
|
|
int nodat;
|
2019-03-06 07:46:26 +08:00
|
|
|
struct mm_struct *mm = vma->vm_mm;
|
2016-03-08 18:08:09 +08:00
|
|
|
|
2016-06-06 16:30:45 +08:00
|
|
|
preempt_disable();
|
2016-03-08 18:08:09 +08:00
|
|
|
pgste = ptep_xchg_start(mm, addr, ptep);
|
2016-07-26 23:02:31 +08:00
|
|
|
nodat = !!(pgste_val(pgste) & _PGSTE_GPS_NODAT);
|
|
|
|
old = ptep_flush_lazy(mm, addr, ptep, nodat);
|
2016-03-08 18:08:09 +08:00
|
|
|
if (mm_has_pgste(mm)) {
|
|
|
|
pgste = pgste_update_all(old, pgste, mm);
|
|
|
|
pgste_set(ptep, pgste);
|
|
|
|
}
|
|
|
|
return old;
|
|
|
|
}
|
|
|
|
|
2019-03-06 07:46:26 +08:00
|
|
|
void ptep_modify_prot_commit(struct vm_area_struct *vma, unsigned long addr,
|
2019-03-06 07:46:29 +08:00
|
|
|
pte_t *ptep, pte_t old_pte, pte_t pte)
|
2016-03-08 18:08:09 +08:00
|
|
|
{
|
|
|
|
pgste_t pgste;
|
2019-03-06 07:46:26 +08:00
|
|
|
struct mm_struct *mm = vma->vm_mm;
|
2016-03-08 18:08:09 +08:00
|
|
|
|
2016-03-22 17:54:24 +08:00
|
|
|
if (!MACHINE_HAS_NX)
|
|
|
|
pte_val(pte) &= ~_PAGE_NOEXEC;
|
2016-03-08 18:08:09 +08:00
|
|
|
if (mm_has_pgste(mm)) {
|
|
|
|
pgste = pgste_get(ptep);
|
|
|
|
pgste_set_key(ptep, pgste, pte, mm);
|
|
|
|
pgste = pgste_set_pte(ptep, pgste, pte);
|
|
|
|
pgste_set_unlock(ptep, pgste);
|
|
|
|
} else {
|
|
|
|
*ptep = pte;
|
|
|
|
}
|
2016-06-06 16:30:45 +08:00
|
|
|
preempt_enable();
|
2016-03-08 18:08:09 +08:00
|
|
|
}
|
|
|
|
|
2016-07-26 22:53:09 +08:00
|
|
|
static inline void pmdp_idte_local(struct mm_struct *mm,
|
|
|
|
unsigned long addr, pmd_t *pmdp)
|
|
|
|
{
|
|
|
|
if (MACHINE_HAS_TLB_GUEST)
|
2016-07-26 22:00:22 +08:00
|
|
|
__pmdp_idte(addr, pmdp, IDTE_NODAT | IDTE_GUEST_ASCE,
|
|
|
|
mm->context.asce, IDTE_LOCAL);
|
2016-07-26 22:53:09 +08:00
|
|
|
else
|
2016-07-26 22:00:22 +08:00
|
|
|
__pmdp_idte(addr, pmdp, 0, 0, IDTE_LOCAL);
|
2018-07-13 18:28:37 +08:00
|
|
|
if (mm_has_pgste(mm) && mm->context.allow_gmap_hpage_1m)
|
2018-07-13 18:28:22 +08:00
|
|
|
gmap_pmdp_idte_local(mm, addr);
|
2016-07-26 22:53:09 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static inline void pmdp_idte_global(struct mm_struct *mm,
|
|
|
|
unsigned long addr, pmd_t *pmdp)
|
|
|
|
{
|
2018-07-13 18:28:22 +08:00
|
|
|
if (MACHINE_HAS_TLB_GUEST) {
|
2016-07-26 22:00:22 +08:00
|
|
|
__pmdp_idte(addr, pmdp, IDTE_NODAT | IDTE_GUEST_ASCE,
|
|
|
|
mm->context.asce, IDTE_GLOBAL);
|
2018-07-13 18:28:37 +08:00
|
|
|
if (mm_has_pgste(mm) && mm->context.allow_gmap_hpage_1m)
|
2018-07-13 18:28:22 +08:00
|
|
|
gmap_pmdp_idte_global(mm, addr);
|
|
|
|
} else if (MACHINE_HAS_IDTE) {
|
2016-07-26 22:00:22 +08:00
|
|
|
__pmdp_idte(addr, pmdp, 0, 0, IDTE_GLOBAL);
|
2018-07-13 18:28:37 +08:00
|
|
|
if (mm_has_pgste(mm) && mm->context.allow_gmap_hpage_1m)
|
2018-07-13 18:28:22 +08:00
|
|
|
gmap_pmdp_idte_global(mm, addr);
|
|
|
|
} else {
|
2016-07-26 22:53:09 +08:00
|
|
|
__pmdp_csp(pmdp);
|
2018-07-13 18:28:37 +08:00
|
|
|
if (mm_has_pgste(mm) && mm->context.allow_gmap_hpage_1m)
|
2018-07-13 18:28:22 +08:00
|
|
|
gmap_pmdp_csp(mm, addr);
|
|
|
|
}
|
2016-07-26 22:53:09 +08:00
|
|
|
}
|
|
|
|
|
2016-03-08 18:09:25 +08:00
|
|
|
static inline pmd_t pmdp_flush_direct(struct mm_struct *mm,
|
|
|
|
unsigned long addr, pmd_t *pmdp)
|
|
|
|
{
|
|
|
|
pmd_t old;
|
|
|
|
|
|
|
|
old = *pmdp;
|
|
|
|
if (pmd_val(old) & _SEGMENT_ENTRY_INVALID)
|
|
|
|
return old;
|
2016-05-25 15:45:26 +08:00
|
|
|
atomic_inc(&mm->context.flush_count);
|
|
|
|
if (MACHINE_HAS_TLB_LC &&
|
2016-03-08 18:09:25 +08:00
|
|
|
cpumask_equal(mm_cpumask(mm), cpumask_of(smp_processor_id())))
|
2016-07-26 22:53:09 +08:00
|
|
|
pmdp_idte_local(mm, addr, pmdp);
|
2016-03-08 18:09:25 +08:00
|
|
|
else
|
2016-07-26 22:53:09 +08:00
|
|
|
pmdp_idte_global(mm, addr, pmdp);
|
2016-05-25 15:45:26 +08:00
|
|
|
atomic_dec(&mm->context.flush_count);
|
2016-03-08 18:09:25 +08:00
|
|
|
return old;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline pmd_t pmdp_flush_lazy(struct mm_struct *mm,
|
|
|
|
unsigned long addr, pmd_t *pmdp)
|
|
|
|
{
|
|
|
|
pmd_t old;
|
|
|
|
|
|
|
|
old = *pmdp;
|
|
|
|
if (pmd_val(old) & _SEGMENT_ENTRY_INVALID)
|
|
|
|
return old;
|
2016-05-25 15:45:26 +08:00
|
|
|
atomic_inc(&mm->context.flush_count);
|
|
|
|
if (cpumask_equal(&mm->context.cpu_attach_mask,
|
|
|
|
cpumask_of(smp_processor_id()))) {
|
2016-03-08 18:09:25 +08:00
|
|
|
pmd_val(*pmdp) |= _SEGMENT_ENTRY_INVALID;
|
|
|
|
mm->context.flush_mm = 1;
|
2018-07-13 18:28:22 +08:00
|
|
|
if (mm_has_pgste(mm))
|
|
|
|
gmap_pmdp_invalidate(mm, addr);
|
2016-07-26 22:53:09 +08:00
|
|
|
} else {
|
|
|
|
pmdp_idte_global(mm, addr, pmdp);
|
|
|
|
}
|
2016-05-25 15:45:26 +08:00
|
|
|
atomic_dec(&mm->context.flush_count);
|
2016-03-08 18:09:25 +08:00
|
|
|
return old;
|
|
|
|
}
|
|
|
|
|
2019-04-07 20:55:09 +08:00
|
|
|
#ifdef CONFIG_PGSTE
|
2018-07-13 18:28:28 +08:00
|
|
|
static pmd_t *pmd_alloc_map(struct mm_struct *mm, unsigned long addr)
|
|
|
|
{
|
|
|
|
pgd_t *pgd;
|
|
|
|
p4d_t *p4d;
|
|
|
|
pud_t *pud;
|
|
|
|
pmd_t *pmd;
|
|
|
|
|
|
|
|
pgd = pgd_offset(mm, addr);
|
|
|
|
p4d = p4d_alloc(mm, pgd, addr);
|
|
|
|
if (!p4d)
|
|
|
|
return NULL;
|
|
|
|
pud = pud_alloc(mm, p4d, addr);
|
|
|
|
if (!pud)
|
|
|
|
return NULL;
|
|
|
|
pmd = pmd_alloc(mm, pud, addr);
|
|
|
|
return pmd;
|
|
|
|
}
|
2019-04-07 20:55:09 +08:00
|
|
|
#endif
|
2018-07-13 18:28:28 +08:00
|
|
|
|
2016-03-08 18:09:25 +08:00
|
|
|
pmd_t pmdp_xchg_direct(struct mm_struct *mm, unsigned long addr,
|
|
|
|
pmd_t *pmdp, pmd_t new)
|
|
|
|
{
|
|
|
|
pmd_t old;
|
|
|
|
|
2016-06-06 16:30:45 +08:00
|
|
|
preempt_disable();
|
2016-03-08 18:09:25 +08:00
|
|
|
old = pmdp_flush_direct(mm, addr, pmdp);
|
|
|
|
*pmdp = new;
|
2016-06-06 16:30:45 +08:00
|
|
|
preempt_enable();
|
2016-03-08 18:09:25 +08:00
|
|
|
return old;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(pmdp_xchg_direct);
|
|
|
|
|
|
|
|
pmd_t pmdp_xchg_lazy(struct mm_struct *mm, unsigned long addr,
|
|
|
|
pmd_t *pmdp, pmd_t new)
|
|
|
|
{
|
|
|
|
pmd_t old;
|
|
|
|
|
2016-06-06 16:30:45 +08:00
|
|
|
preempt_disable();
|
2016-03-08 18:09:25 +08:00
|
|
|
old = pmdp_flush_lazy(mm, addr, pmdp);
|
|
|
|
*pmdp = new;
|
2016-06-06 16:30:45 +08:00
|
|
|
preempt_enable();
|
2016-03-08 18:09:25 +08:00
|
|
|
return old;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(pmdp_xchg_lazy);
|
|
|
|
|
2016-07-26 22:53:09 +08:00
|
|
|
static inline void pudp_idte_local(struct mm_struct *mm,
|
|
|
|
unsigned long addr, pud_t *pudp)
|
2016-07-04 20:47:01 +08:00
|
|
|
{
|
2016-07-26 22:53:09 +08:00
|
|
|
if (MACHINE_HAS_TLB_GUEST)
|
2016-07-26 22:00:22 +08:00
|
|
|
__pudp_idte(addr, pudp, IDTE_NODAT | IDTE_GUEST_ASCE,
|
|
|
|
mm->context.asce, IDTE_LOCAL);
|
2016-07-26 22:53:09 +08:00
|
|
|
else
|
2016-07-26 22:00:22 +08:00
|
|
|
__pudp_idte(addr, pudp, 0, 0, IDTE_LOCAL);
|
2016-07-26 22:53:09 +08:00
|
|
|
}
|
2016-07-04 20:47:01 +08:00
|
|
|
|
2016-07-26 22:53:09 +08:00
|
|
|
static inline void pudp_idte_global(struct mm_struct *mm,
|
|
|
|
unsigned long addr, pud_t *pudp)
|
|
|
|
{
|
|
|
|
if (MACHINE_HAS_TLB_GUEST)
|
2016-07-26 22:00:22 +08:00
|
|
|
__pudp_idte(addr, pudp, IDTE_NODAT | IDTE_GUEST_ASCE,
|
|
|
|
mm->context.asce, IDTE_GLOBAL);
|
2016-07-26 22:53:09 +08:00
|
|
|
else if (MACHINE_HAS_IDTE)
|
2016-07-26 22:00:22 +08:00
|
|
|
__pudp_idte(addr, pudp, 0, 0, IDTE_GLOBAL);
|
2016-07-26 22:53:09 +08:00
|
|
|
else
|
2016-07-04 20:47:01 +08:00
|
|
|
/*
|
|
|
|
* Invalid bit position is the same for pmd and pud, so we can
|
|
|
|
* re-use _pmd_csp() here
|
|
|
|
*/
|
|
|
|
__pmdp_csp((pmd_t *) pudp);
|
2016-07-26 22:53:09 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static inline pud_t pudp_flush_direct(struct mm_struct *mm,
|
|
|
|
unsigned long addr, pud_t *pudp)
|
|
|
|
{
|
|
|
|
pud_t old;
|
|
|
|
|
|
|
|
old = *pudp;
|
|
|
|
if (pud_val(old) & _REGION_ENTRY_INVALID)
|
2016-07-04 20:47:01 +08:00
|
|
|
return old;
|
|
|
|
atomic_inc(&mm->context.flush_count);
|
|
|
|
if (MACHINE_HAS_TLB_LC &&
|
|
|
|
cpumask_equal(mm_cpumask(mm), cpumask_of(smp_processor_id())))
|
2016-07-26 22:53:09 +08:00
|
|
|
pudp_idte_local(mm, addr, pudp);
|
2016-07-04 20:47:01 +08:00
|
|
|
else
|
2016-07-26 22:53:09 +08:00
|
|
|
pudp_idte_global(mm, addr, pudp);
|
2016-07-04 20:47:01 +08:00
|
|
|
atomic_dec(&mm->context.flush_count);
|
|
|
|
return old;
|
|
|
|
}
|
|
|
|
|
|
|
|
pud_t pudp_xchg_direct(struct mm_struct *mm, unsigned long addr,
|
|
|
|
pud_t *pudp, pud_t new)
|
|
|
|
{
|
|
|
|
pud_t old;
|
|
|
|
|
|
|
|
preempt_disable();
|
|
|
|
old = pudp_flush_direct(mm, addr, pudp);
|
|
|
|
*pudp = new;
|
|
|
|
preempt_enable();
|
|
|
|
return old;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(pudp_xchg_direct);
|
|
|
|
|
2012-10-09 07:30:15 +08:00
|
|
|
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
|
2013-06-06 08:14:02 +08:00
|
|
|
void pgtable_trans_huge_deposit(struct mm_struct *mm, pmd_t *pmdp,
|
|
|
|
pgtable_t pgtable)
|
2012-10-09 07:30:18 +08:00
|
|
|
{
|
|
|
|
struct list_head *lh = (struct list_head *) pgtable;
|
|
|
|
|
2014-02-12 21:16:18 +08:00
|
|
|
assert_spin_locked(pmd_lockptr(mm, pmdp));
|
2012-10-09 07:30:18 +08:00
|
|
|
|
|
|
|
/* FIFO */
|
2013-11-15 06:30:59 +08:00
|
|
|
if (!pmd_huge_pte(mm, pmdp))
|
2012-10-09 07:30:18 +08:00
|
|
|
INIT_LIST_HEAD(lh);
|
|
|
|
else
|
2013-11-15 06:30:59 +08:00
|
|
|
list_add(lh, (struct list_head *) pmd_huge_pte(mm, pmdp));
|
|
|
|
pmd_huge_pte(mm, pmdp) = pgtable;
|
2012-10-09 07:30:18 +08:00
|
|
|
}
|
|
|
|
|
2013-06-06 08:14:02 +08:00
|
|
|
pgtable_t pgtable_trans_huge_withdraw(struct mm_struct *mm, pmd_t *pmdp)
|
2012-10-09 07:30:18 +08:00
|
|
|
{
|
|
|
|
struct list_head *lh;
|
|
|
|
pgtable_t pgtable;
|
|
|
|
pte_t *ptep;
|
|
|
|
|
2014-02-12 21:16:18 +08:00
|
|
|
assert_spin_locked(pmd_lockptr(mm, pmdp));
|
2012-10-09 07:30:18 +08:00
|
|
|
|
|
|
|
/* FIFO */
|
2013-11-15 06:30:59 +08:00
|
|
|
pgtable = pmd_huge_pte(mm, pmdp);
|
2012-10-09 07:30:18 +08:00
|
|
|
lh = (struct list_head *) pgtable;
|
|
|
|
if (list_empty(lh))
|
2013-11-15 06:30:59 +08:00
|
|
|
pmd_huge_pte(mm, pmdp) = NULL;
|
2012-10-09 07:30:18 +08:00
|
|
|
else {
|
2013-11-15 06:30:59 +08:00
|
|
|
pmd_huge_pte(mm, pmdp) = (pgtable_t) lh->next;
|
2012-10-09 07:30:18 +08:00
|
|
|
list_del(lh);
|
|
|
|
}
|
|
|
|
ptep = (pte_t *) pgtable;
|
2013-07-24 02:57:57 +08:00
|
|
|
pte_val(*ptep) = _PAGE_INVALID;
|
2012-10-09 07:30:18 +08:00
|
|
|
ptep++;
|
2013-07-24 02:57:57 +08:00
|
|
|
pte_val(*ptep) = _PAGE_INVALID;
|
2012-10-09 07:30:18 +08:00
|
|
|
return pgtable;
|
|
|
|
}
|
2012-10-09 07:30:15 +08:00
|
|
|
#endif /* CONFIG_TRANSPARENT_HUGEPAGE */
|
2016-03-08 18:49:57 +08:00
|
|
|
|
|
|
|
#ifdef CONFIG_PGSTE
|
|
|
|
void ptep_set_pte_at(struct mm_struct *mm, unsigned long addr,
|
|
|
|
pte_t *ptep, pte_t entry)
|
|
|
|
{
|
|
|
|
pgste_t pgste;
|
|
|
|
|
|
|
|
/* the mm_has_pgste() check is done in set_pte_at() */
|
2016-06-06 16:30:45 +08:00
|
|
|
preempt_disable();
|
2016-03-08 18:49:57 +08:00
|
|
|
pgste = pgste_get_lock(ptep);
|
|
|
|
pgste_val(pgste) &= ~_PGSTE_GPS_ZERO;
|
|
|
|
pgste_set_key(ptep, pgste, entry, mm);
|
|
|
|
pgste = pgste_set_pte(ptep, pgste, entry);
|
|
|
|
pgste_set_unlock(ptep, pgste);
|
2016-06-06 16:30:45 +08:00
|
|
|
preempt_enable();
|
2016-03-08 18:49:57 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void ptep_set_notify(struct mm_struct *mm, unsigned long addr, pte_t *ptep)
|
|
|
|
{
|
|
|
|
pgste_t pgste;
|
|
|
|
|
2016-06-06 16:30:45 +08:00
|
|
|
preempt_disable();
|
2016-03-08 18:49:57 +08:00
|
|
|
pgste = pgste_get_lock(ptep);
|
|
|
|
pgste_val(pgste) |= PGSTE_IN_BIT;
|
|
|
|
pgste_set_unlock(ptep, pgste);
|
2016-06-06 16:30:45 +08:00
|
|
|
preempt_enable();
|
2016-03-08 18:49:57 +08:00
|
|
|
}
|
|
|
|
|
2016-03-08 18:54:42 +08:00
|
|
|
/**
|
|
|
|
* ptep_force_prot - change access rights of a locked pte
|
|
|
|
* @mm: pointer to the process mm_struct
|
|
|
|
* @addr: virtual address in the guest address space
|
|
|
|
* @ptep: pointer to the page table entry
|
|
|
|
* @prot: indicates guest access rights: PROT_NONE, PROT_READ or PROT_WRITE
|
2016-03-08 19:12:18 +08:00
|
|
|
* @bit: pgste bit to set (e.g. for notification)
|
2016-03-08 18:54:42 +08:00
|
|
|
*
|
|
|
|
* Returns 0 if the access rights were changed and -EAGAIN if the current
|
|
|
|
* and requested access rights are incompatible.
|
|
|
|
*/
|
|
|
|
int ptep_force_prot(struct mm_struct *mm, unsigned long addr,
|
2016-03-08 19:12:18 +08:00
|
|
|
pte_t *ptep, int prot, unsigned long bit)
|
2016-03-08 18:54:42 +08:00
|
|
|
{
|
|
|
|
pte_t entry;
|
|
|
|
pgste_t pgste;
|
2016-07-26 23:02:31 +08:00
|
|
|
int pte_i, pte_p, nodat;
|
2016-03-08 18:54:42 +08:00
|
|
|
|
|
|
|
pgste = pgste_get_lock(ptep);
|
|
|
|
entry = *ptep;
|
|
|
|
/* Check pte entry after all locks have been acquired */
|
|
|
|
pte_i = pte_val(entry) & _PAGE_INVALID;
|
|
|
|
pte_p = pte_val(entry) & _PAGE_PROTECT;
|
|
|
|
if ((pte_i && (prot != PROT_NONE)) ||
|
|
|
|
(pte_p && (prot & PROT_WRITE))) {
|
|
|
|
pgste_set_unlock(ptep, pgste);
|
|
|
|
return -EAGAIN;
|
|
|
|
}
|
2016-03-08 19:12:18 +08:00
|
|
|
/* Change access rights and set pgste bit */
|
2016-07-26 23:02:31 +08:00
|
|
|
nodat = !!(pgste_val(pgste) & _PGSTE_GPS_NODAT);
|
2016-03-08 18:54:42 +08:00
|
|
|
if (prot == PROT_NONE && !pte_i) {
|
2016-07-26 23:02:31 +08:00
|
|
|
ptep_flush_direct(mm, addr, ptep, nodat);
|
2016-03-08 18:54:42 +08:00
|
|
|
pgste = pgste_update_all(entry, pgste, mm);
|
|
|
|
pte_val(entry) |= _PAGE_INVALID;
|
|
|
|
}
|
|
|
|
if (prot == PROT_READ && !pte_p) {
|
2016-07-26 23:02:31 +08:00
|
|
|
ptep_flush_direct(mm, addr, ptep, nodat);
|
2016-03-08 18:54:42 +08:00
|
|
|
pte_val(entry) &= ~_PAGE_INVALID;
|
|
|
|
pte_val(entry) |= _PAGE_PROTECT;
|
|
|
|
}
|
2016-03-08 19:12:18 +08:00
|
|
|
pgste_val(pgste) |= bit;
|
2016-03-08 18:54:42 +08:00
|
|
|
pgste = pgste_set_pte(ptep, pgste, entry);
|
|
|
|
pgste_set_unlock(ptep, pgste);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-03-08 19:12:18 +08:00
|
|
|
int ptep_shadow_pte(struct mm_struct *mm, unsigned long saddr,
|
2016-03-08 19:21:41 +08:00
|
|
|
pte_t *sptep, pte_t *tptep, pte_t pte)
|
2016-03-08 19:12:18 +08:00
|
|
|
{
|
|
|
|
pgste_t spgste, tpgste;
|
|
|
|
pte_t spte, tpte;
|
|
|
|
int rc = -EAGAIN;
|
|
|
|
|
2016-03-08 19:21:41 +08:00
|
|
|
if (!(pte_val(*tptep) & _PAGE_INVALID))
|
|
|
|
return 0; /* already shadowed */
|
2016-03-08 19:12:18 +08:00
|
|
|
spgste = pgste_get_lock(sptep);
|
|
|
|
spte = *sptep;
|
|
|
|
if (!(pte_val(spte) & _PAGE_INVALID) &&
|
2016-03-08 19:21:41 +08:00
|
|
|
!((pte_val(spte) & _PAGE_PROTECT) &&
|
|
|
|
!(pte_val(pte) & _PAGE_PROTECT))) {
|
2016-03-08 19:12:18 +08:00
|
|
|
pgste_val(spgste) |= PGSTE_VSIE_BIT;
|
|
|
|
tpgste = pgste_get_lock(tptep);
|
|
|
|
pte_val(tpte) = (pte_val(spte) & PAGE_MASK) |
|
2016-03-08 19:21:41 +08:00
|
|
|
(pte_val(pte) & _PAGE_PROTECT);
|
2016-03-08 19:12:18 +08:00
|
|
|
/* don't touch the storage key - it belongs to parent pgste */
|
|
|
|
tpgste = pgste_set_pte(tptep, tpgste, tpte);
|
|
|
|
pgste_set_unlock(tptep, tpgste);
|
2016-03-08 19:21:41 +08:00
|
|
|
rc = 1;
|
2016-03-08 19:12:18 +08:00
|
|
|
}
|
|
|
|
pgste_set_unlock(sptep, spgste);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ptep_unshadow_pte(struct mm_struct *mm, unsigned long saddr, pte_t *ptep)
|
|
|
|
{
|
|
|
|
pgste_t pgste;
|
2016-07-26 23:02:31 +08:00
|
|
|
int nodat;
|
2016-03-08 19:12:18 +08:00
|
|
|
|
|
|
|
pgste = pgste_get_lock(ptep);
|
|
|
|
/* notifier is called by the caller */
|
2016-07-26 23:02:31 +08:00
|
|
|
nodat = !!(pgste_val(pgste) & _PGSTE_GPS_NODAT);
|
|
|
|
ptep_flush_direct(mm, saddr, ptep, nodat);
|
2016-03-08 19:12:18 +08:00
|
|
|
/* don't touch the storage key - it belongs to parent pgste */
|
|
|
|
pgste = pgste_set_pte(ptep, pgste, __pte(_PAGE_INVALID));
|
|
|
|
pgste_set_unlock(ptep, pgste);
|
|
|
|
}
|
|
|
|
|
2016-03-08 18:49:57 +08:00
|
|
|
static void ptep_zap_swap_entry(struct mm_struct *mm, swp_entry_t entry)
|
|
|
|
{
|
|
|
|
if (!non_swap_entry(entry))
|
|
|
|
dec_mm_counter(mm, MM_SWAPENTS);
|
|
|
|
else if (is_migration_entry(entry)) {
|
|
|
|
struct page *page = migration_entry_to_page(entry);
|
|
|
|
|
|
|
|
dec_mm_counter(mm, mm_counter(page));
|
|
|
|
}
|
|
|
|
free_swap_and_cache(entry);
|
|
|
|
}
|
|
|
|
|
|
|
|
void ptep_zap_unused(struct mm_struct *mm, unsigned long addr,
|
|
|
|
pte_t *ptep, int reset)
|
|
|
|
{
|
|
|
|
unsigned long pgstev;
|
|
|
|
pgste_t pgste;
|
|
|
|
pte_t pte;
|
|
|
|
|
|
|
|
/* Zap unused and logically-zero pages */
|
2016-06-06 16:30:45 +08:00
|
|
|
preempt_disable();
|
2016-03-08 18:49:57 +08:00
|
|
|
pgste = pgste_get_lock(ptep);
|
|
|
|
pgstev = pgste_val(pgste);
|
|
|
|
pte = *ptep;
|
2016-06-13 19:14:56 +08:00
|
|
|
if (!reset && pte_swap(pte) &&
|
2016-03-08 18:49:57 +08:00
|
|
|
((pgstev & _PGSTE_GPS_USAGE_MASK) == _PGSTE_GPS_USAGE_UNUSED ||
|
|
|
|
(pgstev & _PGSTE_GPS_ZERO))) {
|
|
|
|
ptep_zap_swap_entry(mm, pte_to_swp_entry(pte));
|
|
|
|
pte_clear(mm, addr, ptep);
|
|
|
|
}
|
|
|
|
if (reset)
|
|
|
|
pgste_val(pgste) &= ~_PGSTE_GPS_USAGE_MASK;
|
|
|
|
pgste_set_unlock(ptep, pgste);
|
2016-06-06 16:30:45 +08:00
|
|
|
preempt_enable();
|
2016-03-08 18:49:57 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void ptep_zap_key(struct mm_struct *mm, unsigned long addr, pte_t *ptep)
|
|
|
|
{
|
|
|
|
unsigned long ptev;
|
|
|
|
pgste_t pgste;
|
|
|
|
|
2017-07-06 16:12:58 +08:00
|
|
|
/* Clear storage key ACC and F, but set R/C */
|
2016-06-06 16:30:45 +08:00
|
|
|
preempt_disable();
|
2016-03-08 18:49:57 +08:00
|
|
|
pgste = pgste_get_lock(ptep);
|
2017-07-06 16:12:58 +08:00
|
|
|
pgste_val(pgste) &= ~(PGSTE_ACC_BITS | PGSTE_FP_BIT);
|
|
|
|
pgste_val(pgste) |= PGSTE_GR_BIT | PGSTE_GC_BIT;
|
2016-03-08 18:49:57 +08:00
|
|
|
ptev = pte_val(*ptep);
|
|
|
|
if (!(ptev & _PAGE_INVALID) && (ptev & _PAGE_WRITE))
|
|
|
|
page_set_storage_key(ptev & PAGE_MASK, PAGE_DEFAULT_KEY, 1);
|
|
|
|
pgste_set_unlock(ptep, pgste);
|
2016-06-06 16:30:45 +08:00
|
|
|
preempt_enable();
|
2016-03-08 18:49:57 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Test and reset if a guest page is dirty
|
|
|
|
*/
|
2018-07-17 20:21:22 +08:00
|
|
|
bool ptep_test_and_clear_uc(struct mm_struct *mm, unsigned long addr,
|
|
|
|
pte_t *ptep)
|
2016-03-08 18:49:57 +08:00
|
|
|
{
|
|
|
|
pgste_t pgste;
|
|
|
|
pte_t pte;
|
|
|
|
bool dirty;
|
2016-07-26 23:02:31 +08:00
|
|
|
int nodat;
|
2016-03-08 18:49:57 +08:00
|
|
|
|
|
|
|
pgste = pgste_get_lock(ptep);
|
|
|
|
dirty = !!(pgste_val(pgste) & PGSTE_UC_BIT);
|
|
|
|
pgste_val(pgste) &= ~PGSTE_UC_BIT;
|
|
|
|
pte = *ptep;
|
|
|
|
if (dirty && (pte_val(pte) & _PAGE_PRESENT)) {
|
2016-03-08 18:54:42 +08:00
|
|
|
pgste = pgste_pte_notify(mm, addr, ptep, pgste);
|
2016-07-26 23:02:31 +08:00
|
|
|
nodat = !!(pgste_val(pgste) & _PGSTE_GPS_NODAT);
|
|
|
|
ptep_ipte_global(mm, addr, ptep, nodat);
|
2016-03-08 18:49:57 +08:00
|
|
|
if (MACHINE_HAS_ESOP || !(pte_val(pte) & _PAGE_WRITE))
|
|
|
|
pte_val(pte) |= _PAGE_PROTECT;
|
|
|
|
else
|
|
|
|
pte_val(pte) |= _PAGE_INVALID;
|
|
|
|
*ptep = pte;
|
|
|
|
}
|
|
|
|
pgste_set_unlock(ptep, pgste);
|
|
|
|
return dirty;
|
|
|
|
}
|
2018-07-17 20:21:22 +08:00
|
|
|
EXPORT_SYMBOL_GPL(ptep_test_and_clear_uc);
|
2016-03-08 18:49:57 +08:00
|
|
|
|
|
|
|
int set_guest_storage_key(struct mm_struct *mm, unsigned long addr,
|
|
|
|
unsigned char key, bool nq)
|
|
|
|
{
|
2018-07-13 18:28:28 +08:00
|
|
|
unsigned long keyul, paddr;
|
2016-03-08 18:49:57 +08:00
|
|
|
spinlock_t *ptl;
|
|
|
|
pgste_t old, new;
|
2018-07-13 18:28:28 +08:00
|
|
|
pmd_t *pmdp;
|
2016-03-08 18:49:57 +08:00
|
|
|
pte_t *ptep;
|
|
|
|
|
2018-07-13 18:28:28 +08:00
|
|
|
pmdp = pmd_alloc_map(mm, addr);
|
|
|
|
if (unlikely(!pmdp))
|
|
|
|
return -EFAULT;
|
|
|
|
|
|
|
|
ptl = pmd_lock(mm, pmdp);
|
|
|
|
if (!pmd_present(*pmdp)) {
|
|
|
|
spin_unlock(ptl);
|
|
|
|
return -EFAULT;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pmd_large(*pmdp)) {
|
|
|
|
paddr = pmd_val(*pmdp) & HPAGE_MASK;
|
|
|
|
paddr |= addr & ~HPAGE_MASK;
|
|
|
|
/*
|
|
|
|
* Huge pmds need quiescing operations, they are
|
|
|
|
* always mapped.
|
|
|
|
*/
|
|
|
|
page_set_storage_key(paddr, key, 1);
|
|
|
|
spin_unlock(ptl);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
spin_unlock(ptl);
|
|
|
|
|
|
|
|
ptep = pte_alloc_map_lock(mm, pmdp, addr, &ptl);
|
2016-03-08 18:53:35 +08:00
|
|
|
if (unlikely(!ptep))
|
2016-03-08 18:49:57 +08:00
|
|
|
return -EFAULT;
|
|
|
|
|
|
|
|
new = old = pgste_get_lock(ptep);
|
|
|
|
pgste_val(new) &= ~(PGSTE_GR_BIT | PGSTE_GC_BIT |
|
|
|
|
PGSTE_ACC_BITS | PGSTE_FP_BIT);
|
|
|
|
keyul = (unsigned long) key;
|
|
|
|
pgste_val(new) |= (keyul & (_PAGE_CHANGED | _PAGE_REFERENCED)) << 48;
|
|
|
|
pgste_val(new) |= (keyul & (_PAGE_ACC_BITS | _PAGE_FP_BIT)) << 56;
|
|
|
|
if (!(pte_val(*ptep) & _PAGE_INVALID)) {
|
2018-07-13 18:28:28 +08:00
|
|
|
unsigned long bits, skey;
|
2016-03-08 18:49:57 +08:00
|
|
|
|
2018-07-13 18:28:28 +08:00
|
|
|
paddr = pte_val(*ptep) & PAGE_MASK;
|
|
|
|
skey = (unsigned long) page_get_storage_key(paddr);
|
2016-03-08 18:49:57 +08:00
|
|
|
bits = skey & (_PAGE_CHANGED | _PAGE_REFERENCED);
|
|
|
|
skey = key & (_PAGE_ACC_BITS | _PAGE_FP_BIT);
|
|
|
|
/* Set storage key ACC and FP */
|
2018-07-13 18:28:28 +08:00
|
|
|
page_set_storage_key(paddr, skey, !nq);
|
2016-03-08 18:49:57 +08:00
|
|
|
/* Merge host changed & referenced into pgste */
|
|
|
|
pgste_val(new) |= bits << 52;
|
|
|
|
}
|
|
|
|
/* changing the guest storage key is considered a change of the page */
|
|
|
|
if ((pgste_val(new) ^ pgste_val(old)) &
|
|
|
|
(PGSTE_ACC_BITS | PGSTE_FP_BIT | PGSTE_GR_BIT | PGSTE_GC_BIT))
|
|
|
|
pgste_val(new) |= PGSTE_UC_BIT;
|
|
|
|
|
|
|
|
pgste_set_unlock(ptep, new);
|
|
|
|
pte_unmap_unlock(ptep, ptl);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(set_guest_storage_key);
|
|
|
|
|
2016-05-10 15:43:11 +08:00
|
|
|
/**
|
|
|
|
* Conditionally set a guest storage key (handling csske).
|
|
|
|
* oldkey will be updated when either mr or mc is set and a pointer is given.
|
|
|
|
*
|
|
|
|
* Returns 0 if a guests storage key update wasn't necessary, 1 if the guest
|
|
|
|
* storage key was updated and -EFAULT on access errors.
|
|
|
|
*/
|
|
|
|
int cond_set_guest_storage_key(struct mm_struct *mm, unsigned long addr,
|
|
|
|
unsigned char key, unsigned char *oldkey,
|
|
|
|
bool nq, bool mr, bool mc)
|
|
|
|
{
|
|
|
|
unsigned char tmp, mask = _PAGE_ACC_BITS | _PAGE_FP_BIT;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
/* we can drop the pgste lock between getting and setting the key */
|
|
|
|
if (mr | mc) {
|
|
|
|
rc = get_guest_storage_key(current->mm, addr, &tmp);
|
|
|
|
if (rc)
|
|
|
|
return rc;
|
|
|
|
if (oldkey)
|
|
|
|
*oldkey = tmp;
|
|
|
|
if (!mr)
|
|
|
|
mask |= _PAGE_REFERENCED;
|
|
|
|
if (!mc)
|
|
|
|
mask |= _PAGE_CHANGED;
|
|
|
|
if (!((tmp ^ key) & mask))
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
rc = set_guest_storage_key(current->mm, addr, key, nq);
|
|
|
|
return rc < 0 ? rc : 1;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(cond_set_guest_storage_key);
|
|
|
|
|
2016-05-10 15:50:21 +08:00
|
|
|
/**
|
|
|
|
* Reset a guest reference bit (rrbe), returning the reference and changed bit.
|
|
|
|
*
|
|
|
|
* Returns < 0 in case of error, otherwise the cc to be reported to the guest.
|
|
|
|
*/
|
|
|
|
int reset_guest_reference_bit(struct mm_struct *mm, unsigned long addr)
|
2016-03-08 18:49:57 +08:00
|
|
|
{
|
|
|
|
spinlock_t *ptl;
|
2018-07-13 18:28:28 +08:00
|
|
|
unsigned long paddr;
|
2016-05-10 15:50:21 +08:00
|
|
|
pgste_t old, new;
|
2018-07-13 18:28:28 +08:00
|
|
|
pmd_t *pmdp;
|
2016-03-08 18:49:57 +08:00
|
|
|
pte_t *ptep;
|
2016-05-10 15:50:21 +08:00
|
|
|
int cc = 0;
|
2016-03-08 18:49:57 +08:00
|
|
|
|
2018-07-13 18:28:28 +08:00
|
|
|
pmdp = pmd_alloc_map(mm, addr);
|
|
|
|
if (unlikely(!pmdp))
|
|
|
|
return -EFAULT;
|
|
|
|
|
|
|
|
ptl = pmd_lock(mm, pmdp);
|
|
|
|
if (!pmd_present(*pmdp)) {
|
|
|
|
spin_unlock(ptl);
|
|
|
|
return -EFAULT;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pmd_large(*pmdp)) {
|
|
|
|
paddr = pmd_val(*pmdp) & HPAGE_MASK;
|
|
|
|
paddr |= addr & ~HPAGE_MASK;
|
|
|
|
cc = page_reset_referenced(paddr);
|
|
|
|
spin_unlock(ptl);
|
|
|
|
return cc;
|
|
|
|
}
|
|
|
|
spin_unlock(ptl);
|
|
|
|
|
|
|
|
ptep = pte_alloc_map_lock(mm, pmdp, addr, &ptl);
|
2016-05-10 15:50:21 +08:00
|
|
|
if (unlikely(!ptep))
|
2016-03-08 18:49:57 +08:00
|
|
|
return -EFAULT;
|
|
|
|
|
2016-05-10 15:50:21 +08:00
|
|
|
new = old = pgste_get_lock(ptep);
|
|
|
|
/* Reset guest reference bit only */
|
|
|
|
pgste_val(new) &= ~PGSTE_GR_BIT;
|
2016-03-08 18:49:57 +08:00
|
|
|
|
2016-05-10 15:50:21 +08:00
|
|
|
if (!(pte_val(*ptep) & _PAGE_INVALID)) {
|
2018-07-13 18:28:28 +08:00
|
|
|
paddr = pte_val(*ptep) & PAGE_MASK;
|
|
|
|
cc = page_reset_referenced(paddr);
|
2016-05-10 15:50:21 +08:00
|
|
|
/* Merge real referenced bit into host-set */
|
|
|
|
pgste_val(new) |= ((unsigned long) cc << 53) & PGSTE_HR_BIT;
|
2016-03-08 18:49:57 +08:00
|
|
|
}
|
2016-05-10 15:50:21 +08:00
|
|
|
/* Reflect guest's logical view, not physical */
|
|
|
|
cc |= (pgste_val(old) & (PGSTE_GR_BIT | PGSTE_GC_BIT)) >> 49;
|
|
|
|
/* Changing the guest storage key is considered a change of the page */
|
|
|
|
if ((pgste_val(new) ^ pgste_val(old)) & PGSTE_GR_BIT)
|
|
|
|
pgste_val(new) |= PGSTE_UC_BIT;
|
|
|
|
|
|
|
|
pgste_set_unlock(ptep, new);
|
|
|
|
pte_unmap_unlock(ptep, ptl);
|
2017-01-27 17:23:59 +08:00
|
|
|
return cc;
|
2016-05-10 15:50:21 +08:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(reset_guest_reference_bit);
|
|
|
|
|
2016-05-09 17:22:34 +08:00
|
|
|
int get_guest_storage_key(struct mm_struct *mm, unsigned long addr,
|
|
|
|
unsigned char *key)
|
2016-03-08 18:49:57 +08:00
|
|
|
{
|
2018-07-13 18:28:28 +08:00
|
|
|
unsigned long paddr;
|
2016-03-08 18:49:57 +08:00
|
|
|
spinlock_t *ptl;
|
|
|
|
pgste_t pgste;
|
2018-07-13 18:28:28 +08:00
|
|
|
pmd_t *pmdp;
|
2016-03-08 18:49:57 +08:00
|
|
|
pte_t *ptep;
|
|
|
|
|
2018-07-13 18:28:28 +08:00
|
|
|
pmdp = pmd_alloc_map(mm, addr);
|
|
|
|
if (unlikely(!pmdp))
|
|
|
|
return -EFAULT;
|
|
|
|
|
|
|
|
ptl = pmd_lock(mm, pmdp);
|
|
|
|
if (!pmd_present(*pmdp)) {
|
|
|
|
/* Not yet mapped memory has a zero key */
|
|
|
|
spin_unlock(ptl);
|
|
|
|
*key = 0;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pmd_large(*pmdp)) {
|
|
|
|
paddr = pmd_val(*pmdp) & HPAGE_MASK;
|
|
|
|
paddr |= addr & ~HPAGE_MASK;
|
|
|
|
*key = page_get_storage_key(paddr);
|
|
|
|
spin_unlock(ptl);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
spin_unlock(ptl);
|
|
|
|
|
|
|
|
ptep = pte_alloc_map_lock(mm, pmdp, addr, &ptl);
|
2016-03-08 18:53:35 +08:00
|
|
|
if (unlikely(!ptep))
|
2016-03-08 18:49:57 +08:00
|
|
|
return -EFAULT;
|
|
|
|
|
2016-03-08 18:53:35 +08:00
|
|
|
pgste = pgste_get_lock(ptep);
|
2016-05-09 17:22:34 +08:00
|
|
|
*key = (pgste_val(pgste) & (PGSTE_ACC_BITS | PGSTE_FP_BIT)) >> 56;
|
2018-07-13 18:28:28 +08:00
|
|
|
paddr = pte_val(*ptep) & PAGE_MASK;
|
2016-05-09 17:15:32 +08:00
|
|
|
if (!(pte_val(*ptep) & _PAGE_INVALID))
|
2018-07-13 18:28:28 +08:00
|
|
|
*key = page_get_storage_key(paddr);
|
2016-05-09 17:15:32 +08:00
|
|
|
/* Reflect guest's logical view, not physical */
|
2016-05-09 17:22:34 +08:00
|
|
|
*key |= (pgste_val(pgste) & (PGSTE_GR_BIT | PGSTE_GC_BIT)) >> 48;
|
2016-03-08 18:49:57 +08:00
|
|
|
pgste_set_unlock(ptep, pgste);
|
|
|
|
pte_unmap_unlock(ptep, ptl);
|
2016-05-09 17:22:34 +08:00
|
|
|
return 0;
|
2016-03-08 18:49:57 +08:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(get_guest_storage_key);
|
2017-04-20 16:03:45 +08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* pgste_perform_essa - perform ESSA actions on the PGSTE.
|
|
|
|
* @mm: the memory context. It must have PGSTEs, no check is performed here!
|
|
|
|
* @hva: the host virtual address of the page whose PGSTE is to be processed
|
|
|
|
* @orc: the specific action to perform, see the ESSA_SET_* macros.
|
|
|
|
* @oldpte: the PTE will be saved there if the pointer is not NULL.
|
|
|
|
* @oldpgste: the old PGSTE will be saved there if the pointer is not NULL.
|
|
|
|
*
|
|
|
|
* Return: 1 if the page is to be added to the CBRL, otherwise 0,
|
|
|
|
* or < 0 in case of error. -EINVAL is returned for invalid values
|
|
|
|
* of orc, -EFAULT for invalid addresses.
|
|
|
|
*/
|
|
|
|
int pgste_perform_essa(struct mm_struct *mm, unsigned long hva, int orc,
|
|
|
|
unsigned long *oldpte, unsigned long *oldpgste)
|
|
|
|
{
|
|
|
|
unsigned long pgstev;
|
|
|
|
spinlock_t *ptl;
|
|
|
|
pgste_t pgste;
|
|
|
|
pte_t *ptep;
|
|
|
|
int res = 0;
|
|
|
|
|
|
|
|
WARN_ON_ONCE(orc > ESSA_MAX);
|
|
|
|
if (unlikely(orc > ESSA_MAX))
|
|
|
|
return -EINVAL;
|
|
|
|
ptep = get_locked_pte(mm, hva, &ptl);
|
|
|
|
if (unlikely(!ptep))
|
|
|
|
return -EFAULT;
|
|
|
|
pgste = pgste_get_lock(ptep);
|
|
|
|
pgstev = pgste_val(pgste);
|
|
|
|
if (oldpte)
|
|
|
|
*oldpte = pte_val(*ptep);
|
|
|
|
if (oldpgste)
|
|
|
|
*oldpgste = pgstev;
|
|
|
|
|
|
|
|
switch (orc) {
|
|
|
|
case ESSA_GET_STATE:
|
|
|
|
break;
|
|
|
|
case ESSA_SET_STABLE:
|
2016-08-29 21:56:55 +08:00
|
|
|
pgstev &= ~(_PGSTE_GPS_USAGE_MASK | _PGSTE_GPS_NODAT);
|
2017-04-20 16:03:45 +08:00
|
|
|
pgstev |= _PGSTE_GPS_USAGE_STABLE;
|
|
|
|
break;
|
|
|
|
case ESSA_SET_UNUSED:
|
|
|
|
pgstev &= ~_PGSTE_GPS_USAGE_MASK;
|
|
|
|
pgstev |= _PGSTE_GPS_USAGE_UNUSED;
|
|
|
|
if (pte_val(*ptep) & _PAGE_INVALID)
|
|
|
|
res = 1;
|
|
|
|
break;
|
|
|
|
case ESSA_SET_VOLATILE:
|
|
|
|
pgstev &= ~_PGSTE_GPS_USAGE_MASK;
|
|
|
|
pgstev |= _PGSTE_GPS_USAGE_VOLATILE;
|
|
|
|
if (pte_val(*ptep) & _PAGE_INVALID)
|
|
|
|
res = 1;
|
|
|
|
break;
|
|
|
|
case ESSA_SET_POT_VOLATILE:
|
|
|
|
pgstev &= ~_PGSTE_GPS_USAGE_MASK;
|
|
|
|
if (!(pte_val(*ptep) & _PAGE_INVALID)) {
|
|
|
|
pgstev |= _PGSTE_GPS_USAGE_POT_VOLATILE;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (pgstev & _PGSTE_GPS_ZERO) {
|
|
|
|
pgstev |= _PGSTE_GPS_USAGE_VOLATILE;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (!(pgstev & PGSTE_GC_BIT)) {
|
|
|
|
pgstev |= _PGSTE_GPS_USAGE_VOLATILE;
|
|
|
|
res = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case ESSA_SET_STABLE_RESIDENT:
|
|
|
|
pgstev &= ~_PGSTE_GPS_USAGE_MASK;
|
|
|
|
pgstev |= _PGSTE_GPS_USAGE_STABLE;
|
|
|
|
/*
|
|
|
|
* Since the resident state can go away any time after this
|
|
|
|
* call, we will not make this page resident. We can revisit
|
|
|
|
* this decision if a guest will ever start using this.
|
|
|
|
*/
|
|
|
|
break;
|
|
|
|
case ESSA_SET_STABLE_IF_RESIDENT:
|
|
|
|
if (!(pte_val(*ptep) & _PAGE_INVALID)) {
|
|
|
|
pgstev &= ~_PGSTE_GPS_USAGE_MASK;
|
|
|
|
pgstev |= _PGSTE_GPS_USAGE_STABLE;
|
|
|
|
}
|
|
|
|
break;
|
2016-08-29 21:56:55 +08:00
|
|
|
case ESSA_SET_STABLE_NODAT:
|
|
|
|
pgstev &= ~_PGSTE_GPS_USAGE_MASK;
|
|
|
|
pgstev |= _PGSTE_GPS_USAGE_STABLE | _PGSTE_GPS_NODAT;
|
|
|
|
break;
|
2017-04-20 16:03:45 +08:00
|
|
|
default:
|
|
|
|
/* we should never get here! */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
/* If we are discarding a page, set it to logical zero */
|
|
|
|
if (res)
|
|
|
|
pgstev |= _PGSTE_GPS_ZERO;
|
|
|
|
|
|
|
|
pgste_val(pgste) = pgstev;
|
|
|
|
pgste_set_unlock(ptep, pgste);
|
|
|
|
pte_unmap_unlock(ptep, ptl);
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(pgste_perform_essa);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* set_pgste_bits - set specific PGSTE bits.
|
|
|
|
* @mm: the memory context. It must have PGSTEs, no check is performed here!
|
|
|
|
* @hva: the host virtual address of the page whose PGSTE is to be processed
|
|
|
|
* @bits: a bitmask representing the bits that will be touched
|
|
|
|
* @value: the values of the bits to be written. Only the bits in the mask
|
|
|
|
* will be written.
|
|
|
|
*
|
|
|
|
* Return: 0 on success, < 0 in case of error.
|
|
|
|
*/
|
|
|
|
int set_pgste_bits(struct mm_struct *mm, unsigned long hva,
|
|
|
|
unsigned long bits, unsigned long value)
|
|
|
|
{
|
|
|
|
spinlock_t *ptl;
|
|
|
|
pgste_t new;
|
|
|
|
pte_t *ptep;
|
|
|
|
|
|
|
|
ptep = get_locked_pte(mm, hva, &ptl);
|
|
|
|
if (unlikely(!ptep))
|
|
|
|
return -EFAULT;
|
|
|
|
new = pgste_get_lock(ptep);
|
|
|
|
|
|
|
|
pgste_val(new) &= ~bits;
|
|
|
|
pgste_val(new) |= value & bits;
|
|
|
|
|
|
|
|
pgste_set_unlock(ptep, new);
|
|
|
|
pte_unmap_unlock(ptep, ptl);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(set_pgste_bits);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* get_pgste - get the current PGSTE for the given address.
|
|
|
|
* @mm: the memory context. It must have PGSTEs, no check is performed here!
|
|
|
|
* @hva: the host virtual address of the page whose PGSTE is to be processed
|
|
|
|
* @pgstep: will be written with the current PGSTE for the given address.
|
|
|
|
*
|
|
|
|
* Return: 0 on success, < 0 in case of error.
|
|
|
|
*/
|
|
|
|
int get_pgste(struct mm_struct *mm, unsigned long hva, unsigned long *pgstep)
|
|
|
|
{
|
|
|
|
spinlock_t *ptl;
|
|
|
|
pte_t *ptep;
|
|
|
|
|
|
|
|
ptep = get_locked_pte(mm, hva, &ptl);
|
|
|
|
if (unlikely(!ptep))
|
|
|
|
return -EFAULT;
|
|
|
|
*pgstep = pgste_val(pgste_get(ptep));
|
|
|
|
pte_unmap_unlock(ptep, ptl);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(get_pgste);
|
2016-03-08 18:49:57 +08:00
|
|
|
#endif
|