root/arch/loongarch/include/asm/kvm_mmu.h
/* SPDX-License-Identifier: GPL-2.0 */
/*
 * Copyright (C) 2020-2023 Loongson Technology Corporation Limited
 */

#ifndef __ASM_LOONGARCH_KVM_MMU_H__
#define __ASM_LOONGARCH_KVM_MMU_H__

#include <linux/kvm_host.h>
#include <asm/pgalloc.h>
#include <asm/tlb.h>

/*
 * KVM_MMU_CACHE_MIN_PAGES is the number of GPA page table translation levels
 * for which pages need to be cached.
 */
#define KVM_MMU_CACHE_MIN_PAGES (CONFIG_PGTABLE_LEVELS - 1)

/*
 * _PAGE_MODIFIED is a SW pte bit, it records page ever written on host
 * kernel, on secondary MMU it records the page writeable attribute, in
 * order for fast path handling.
 */
#define KVM_PAGE_WRITEABLE      _PAGE_MODIFIED

#define _KVM_FLUSH_PGTABLE      0x1
#define _KVM_HAS_PGMASK         0x2
#define kvm_pfn_pte(pfn, prot)  (((pfn) << PFN_PTE_SHIFT) | pgprot_val(prot))
#define kvm_pte_pfn(x)          ((phys_addr_t)((x & _PFN_MASK) >> PFN_PTE_SHIFT))

typedef unsigned long kvm_pte_t;
typedef struct kvm_ptw_ctx kvm_ptw_ctx;
typedef int (*kvm_pte_ops)(kvm_pte_t *pte, phys_addr_t addr, kvm_ptw_ctx *ctx);

struct kvm_ptw_ctx {
        kvm_pte_ops     ops;
        unsigned long   flag;

        /* for kvm_arch_mmu_enable_log_dirty_pt_masked use */
        unsigned long   mask;
        unsigned long   gfn;

        /* page walk mmu info */
        unsigned int    level;
        unsigned long   pgtable_shift;
        unsigned long   invalid_entry;
        unsigned long   *invalid_ptes;
        unsigned int    *pte_shifts;
        void            *opaque;

        /* free pte table page list */
        struct list_head list;
};

kvm_pte_t *kvm_pgd_alloc(void);

static inline void kvm_set_pte(kvm_pte_t *ptep, kvm_pte_t val)
{
        WRITE_ONCE(*ptep, val);
}

static inline int kvm_pte_young(kvm_pte_t pte) { return pte & _PAGE_ACCESSED; }
static inline int kvm_pte_huge(kvm_pte_t pte) { return pte & _PAGE_HUGE; }
static inline int kvm_pte_dirty(kvm_pte_t pte) { return pte & __WRITEABLE; }
static inline int kvm_pte_writeable(kvm_pte_t pte) { return pte & KVM_PAGE_WRITEABLE; }

static inline kvm_pte_t kvm_pte_mkyoung(kvm_pte_t pte)
{
        return pte | _PAGE_ACCESSED;
}

static inline kvm_pte_t kvm_pte_mkold(kvm_pte_t pte)
{
        return pte & ~_PAGE_ACCESSED;
}

static inline kvm_pte_t kvm_pte_mkdirty(kvm_pte_t pte)
{
        return pte | __WRITEABLE;
}

static inline kvm_pte_t kvm_pte_mkclean(kvm_pte_t pte)
{
        return pte & ~__WRITEABLE;
}

static inline kvm_pte_t kvm_pte_mkhuge(kvm_pte_t pte)
{
        return pte | _PAGE_HUGE;
}

static inline kvm_pte_t kvm_pte_mksmall(kvm_pte_t pte)
{
        return pte & ~_PAGE_HUGE;
}

static inline kvm_pte_t kvm_pte_mkwriteable(kvm_pte_t pte)
{
        return pte | KVM_PAGE_WRITEABLE;
}

static inline int kvm_need_flush(kvm_ptw_ctx *ctx)
{
        return ctx->flag & _KVM_FLUSH_PGTABLE;
}

static inline kvm_pte_t *kvm_pgtable_offset(kvm_ptw_ctx *ctx, kvm_pte_t *table,
                                        phys_addr_t addr)
{

        return table + ((addr >> ctx->pgtable_shift) & (PTRS_PER_PTE - 1));
}

static inline phys_addr_t kvm_pgtable_addr_end(kvm_ptw_ctx *ctx,
                                phys_addr_t addr, phys_addr_t end)
{
        phys_addr_t boundary, size;

        size = 0x1UL << ctx->pgtable_shift;
        boundary = (addr + size) & ~(size - 1);
        return (boundary - 1 < end - 1) ? boundary : end;
}

static inline int kvm_pte_present(kvm_ptw_ctx *ctx, kvm_pte_t *entry)
{
        if (!ctx || ctx->level == 0)
                return !!(*entry & _PAGE_PRESENT);

        return *entry != ctx->invalid_entry;
}

static inline int kvm_pte_none(kvm_ptw_ctx *ctx, kvm_pte_t *entry)
{
        return *entry == ctx->invalid_entry;
}

static inline void kvm_ptw_enter(kvm_ptw_ctx *ctx)
{
        ctx->level--;
        ctx->pgtable_shift = ctx->pte_shifts[ctx->level];
        ctx->invalid_entry = ctx->invalid_ptes[ctx->level];
}

static inline void kvm_ptw_exit(kvm_ptw_ctx *ctx)
{
        ctx->level++;
        ctx->pgtable_shift = ctx->pte_shifts[ctx->level];
        ctx->invalid_entry = ctx->invalid_ptes[ctx->level];
}

#endif /* __ASM_LOONGARCH_KVM_MMU_H__ */