root/arch/s390/mm/pgtable.c
// SPDX-License-Identifier: GPL-2.0
/*
 *    Copyright IBM Corp. 2007, 2011
 *    Author(s): Martin Schwidefsky <schwidefsky@de.ibm.com>
 */

#include <linux/cpufeature.h>
#include <linux/export.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/gfp.h>
#include <linux/mm.h>
#include <linux/swap.h>
#include <linux/smp.h>
#include <linux/spinlock.h>
#include <linux/rcupdate.h>
#include <linux/slab.h>
#include <linux/leafops.h>
#include <linux/sysctl.h>
#include <linux/ksm.h>
#include <linux/mman.h>

#include <asm/tlbflush.h>
#include <asm/mmu_context.h>
#include <asm/page-states.h>
#include <asm/machine.h>

pgprot_t pgprot_writecombine(pgprot_t prot)
{
        /*
         * mio_wb_bit_mask may be set on a different CPU, but it is only set
         * once at init and only read afterwards.
         */
        return __pgprot(pgprot_val(prot) | mio_wb_bit_mask);
}
EXPORT_SYMBOL_GPL(pgprot_writecombine);

static inline void ptep_ipte_local(struct mm_struct *mm, unsigned long addr,
                                   pte_t *ptep, int nodat)
{
        unsigned long opt, asce;

        if (machine_has_tlb_guest()) {
                opt = 0;
                asce = READ_ONCE(mm->context.gmap_asce);
                if (asce == 0UL || nodat)
                        opt |= IPTE_NODAT;
                if (asce != -1UL) {
                        asce = asce ? : mm->context.asce;
                        opt |= IPTE_GUEST_ASCE;
                }
                __ptep_ipte(addr, ptep, opt, asce, IPTE_LOCAL);
        } else {
                __ptep_ipte(addr, ptep, 0, 0, IPTE_LOCAL);
        }
}

static inline void ptep_ipte_global(struct mm_struct *mm, unsigned long addr,
                                    pte_t *ptep, int nodat)
{
        unsigned long opt, asce;

        if (machine_has_tlb_guest()) {
                opt = 0;
                asce = READ_ONCE(mm->context.gmap_asce);
                if (asce == 0UL || nodat)
                        opt |= IPTE_NODAT;
                if (asce != -1UL) {
                        asce = asce ? : mm->context.asce;
                        opt |= IPTE_GUEST_ASCE;
                }
                __ptep_ipte(addr, ptep, opt, asce, IPTE_GLOBAL);
        } else {
                __ptep_ipte(addr, ptep, 0, 0, IPTE_GLOBAL);
        }
}

static inline pte_t ptep_flush_direct(struct mm_struct *mm,
                                      unsigned long addr, pte_t *ptep,
                                      int nodat)
{
        pte_t old;

        old = *ptep;
        if (unlikely(pte_val(old) & _PAGE_INVALID))
                return old;
        atomic_inc(&mm->context.flush_count);
        if (cpu_has_tlb_lc() &&
            cpumask_equal(mm_cpumask(mm), cpumask_of(smp_processor_id())))
                ptep_ipte_local(mm, addr, ptep, nodat);
        else
                ptep_ipte_global(mm, addr, ptep, nodat);
        atomic_dec(&mm->context.flush_count);
        return old;
}

static inline pte_t ptep_flush_lazy(struct mm_struct *mm,
                                    unsigned long addr, pte_t *ptep,
                                    int nodat)
{
        pte_t old;

        old = *ptep;
        if (unlikely(pte_val(old) & _PAGE_INVALID))
                return old;
        atomic_inc(&mm->context.flush_count);
        if (cpumask_equal(&mm->context.cpu_attach_mask,
                          cpumask_of(smp_processor_id()))) {
                set_pte(ptep, set_pte_bit(*ptep, __pgprot(_PAGE_INVALID)));
                mm->context.flush_mm = 1;
        } else
                ptep_ipte_global(mm, addr, ptep, nodat);
        atomic_dec(&mm->context.flush_count);
        return old;
}

pte_t ptep_xchg_direct(struct mm_struct *mm, unsigned long addr,
                       pte_t *ptep, pte_t new)
{
        pte_t old;

        preempt_disable();
        old = ptep_flush_direct(mm, addr, ptep, 1);
        set_pte(ptep, new);
        preempt_enable();
        return old;
}
EXPORT_SYMBOL(ptep_xchg_direct);

/*
 * Caller must check that new PTE only differs in _PAGE_PROTECT HW bit, so that
 * RDP can be used instead of IPTE. See also comments at pte_allow_rdp().
 */
void ptep_reset_dat_prot(struct mm_struct *mm, unsigned long addr, pte_t *ptep,
                         pte_t new)
{
        preempt_disable();
        atomic_inc(&mm->context.flush_count);
        if (cpumask_equal(mm_cpumask(mm), cpumask_of(smp_processor_id())))
                __ptep_rdp(addr, ptep, 1);
        else
                __ptep_rdp(addr, ptep, 0);
        /*
         * PTE is not invalidated by RDP, only _PAGE_PROTECT is cleared. That
         * means it is still valid and active, and must not be changed according
         * to the architecture. But writing a new value that only differs in SW
         * bits is allowed.
         */
        set_pte(ptep, new);
        atomic_dec(&mm->context.flush_count);
        preempt_enable();
}
EXPORT_SYMBOL(ptep_reset_dat_prot);

pte_t ptep_xchg_lazy(struct mm_struct *mm, unsigned long addr,
                     pte_t *ptep, pte_t new)
{
        pte_t old;

        preempt_disable();
        old = ptep_flush_lazy(mm, addr, ptep, 1);
        set_pte(ptep, new);
        preempt_enable();
        return old;
}
EXPORT_SYMBOL(ptep_xchg_lazy);

pte_t ptep_modify_prot_start(struct vm_area_struct *vma, unsigned long addr,
                             pte_t *ptep)
{
        return ptep_flush_lazy(vma->vm_mm, addr, ptep, 1);
}

void ptep_modify_prot_commit(struct vm_area_struct *vma, unsigned long addr,
                             pte_t *ptep, pte_t old_pte, pte_t pte)
{
        set_pte(ptep, pte);
}

static inline void pmdp_idte_local(struct mm_struct *mm,
                                   unsigned long addr, pmd_t *pmdp)
{
        if (machine_has_tlb_guest())
                __pmdp_idte(addr, pmdp, IDTE_NODAT | IDTE_GUEST_ASCE, mm->context.asce, IDTE_LOCAL);
        else
                __pmdp_idte(addr, pmdp, 0, 0, IDTE_LOCAL);
}

static inline void pmdp_idte_global(struct mm_struct *mm,
                                    unsigned long addr, pmd_t *pmdp)
{
        if (machine_has_tlb_guest()) {
                __pmdp_idte(addr, pmdp, IDTE_NODAT | IDTE_GUEST_ASCE,
                            mm->context.asce, IDTE_GLOBAL);
        } else {
                __pmdp_idte(addr, pmdp, 0, 0, IDTE_GLOBAL);
        }
}

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;
        atomic_inc(&mm->context.flush_count);
        if (cpu_has_tlb_lc() &&
            cpumask_equal(mm_cpumask(mm), cpumask_of(smp_processor_id())))
                pmdp_idte_local(mm, addr, pmdp);
        else
                pmdp_idte_global(mm, addr, pmdp);
        atomic_dec(&mm->context.flush_count);
        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;
        atomic_inc(&mm->context.flush_count);
        if (cpumask_equal(&mm->context.cpu_attach_mask,
                          cpumask_of(smp_processor_id()))) {
                set_pmd(pmdp, set_pmd_bit(*pmdp, __pgprot(_SEGMENT_ENTRY_INVALID)));
                mm->context.flush_mm = 1;
        } else {
                pmdp_idte_global(mm, addr, pmdp);
        }
        atomic_dec(&mm->context.flush_count);
        return old;
}

pmd_t pmdp_xchg_direct(struct mm_struct *mm, unsigned long addr,
                       pmd_t *pmdp, pmd_t new)
{
        pmd_t old;

        preempt_disable();
        old = pmdp_flush_direct(mm, addr, pmdp);
        set_pmd(pmdp, new);
        preempt_enable();
        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;

        preempt_disable();
        old = pmdp_flush_lazy(mm, addr, pmdp);
        set_pmd(pmdp, new);
        preempt_enable();
        return old;
}
EXPORT_SYMBOL(pmdp_xchg_lazy);

static inline void pudp_idte_local(struct mm_struct *mm,
                                   unsigned long addr, pud_t *pudp)
{
        if (machine_has_tlb_guest())
                __pudp_idte(addr, pudp, IDTE_NODAT | IDTE_GUEST_ASCE,
                            mm->context.asce, IDTE_LOCAL);
        else
                __pudp_idte(addr, pudp, 0, 0, IDTE_LOCAL);
}

static inline void pudp_idte_global(struct mm_struct *mm,
                                    unsigned long addr, pud_t *pudp)
{
        if (machine_has_tlb_guest())
                __pudp_idte(addr, pudp, IDTE_NODAT | IDTE_GUEST_ASCE,
                            mm->context.asce, IDTE_GLOBAL);
        else
                __pudp_idte(addr, pudp, 0, 0, IDTE_GLOBAL);
}

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)
                return old;
        atomic_inc(&mm->context.flush_count);
        if (cpu_has_tlb_lc() &&
            cpumask_equal(mm_cpumask(mm), cpumask_of(smp_processor_id())))
                pudp_idte_local(mm, addr, pudp);
        else
                pudp_idte_global(mm, addr, pudp);
        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);
        set_pud(pudp, new);
        preempt_enable();
        return old;
}
EXPORT_SYMBOL(pudp_xchg_direct);

#ifdef CONFIG_TRANSPARENT_HUGEPAGE
void pgtable_trans_huge_deposit(struct mm_struct *mm, pmd_t *pmdp,
                                pgtable_t pgtable)
{
        struct list_head *lh = (struct list_head *) pgtable;

        assert_spin_locked(pmd_lockptr(mm, pmdp));

        /* FIFO */
        if (!pmd_huge_pte(mm, pmdp))
                INIT_LIST_HEAD(lh);
        else
                list_add(lh, (struct list_head *) pmd_huge_pte(mm, pmdp));
        pmd_huge_pte(mm, pmdp) = pgtable;
}

pgtable_t pgtable_trans_huge_withdraw(struct mm_struct *mm, pmd_t *pmdp)
{
        struct list_head *lh;
        pgtable_t pgtable;
        pte_t *ptep;

        assert_spin_locked(pmd_lockptr(mm, pmdp));

        /* FIFO */
        pgtable = pmd_huge_pte(mm, pmdp);
        lh = (struct list_head *) pgtable;
        if (list_empty(lh))
                pmd_huge_pte(mm, pmdp) = NULL;
        else {
                pmd_huge_pte(mm, pmdp) = (pgtable_t) lh->next;
                list_del(lh);
        }
        ptep = (pte_t *) pgtable;
        set_pte(ptep, __pte(_PAGE_INVALID));
        ptep++;
        set_pte(ptep, __pte(_PAGE_INVALID));
        return pgtable;
}
#endif /* CONFIG_TRANSPARENT_HUGEPAGE */