b681604eda
In virtual machine (guest mode), the tlbwr instruction can not write the last entry of MTLB, so we need to make it non-present by invtlb and then write it by tlbfill. This also simplify the whole logic. Signed-off-by: Rui Wang <wangrui@loongson.cn> Signed-off-by: Huacai Chen <chenhuacai@loongson.cn>
506 lines
12 KiB
ArmAsm
506 lines
12 KiB
ArmAsm
/* SPDX-License-Identifier: GPL-2.0 */
|
|
/*
|
|
* Copyright (C) 2020-2022 Loongson Technology Corporation Limited
|
|
*/
|
|
#include <asm/asm.h>
|
|
#include <asm/export.h>
|
|
#include <asm/loongarch.h>
|
|
#include <asm/page.h>
|
|
#include <asm/pgtable.h>
|
|
#include <asm/regdef.h>
|
|
#include <asm/stackframe.h>
|
|
|
|
#define INVTLB_ADDR_GFALSE_AND_ASID 5
|
|
|
|
#define PTRS_PER_PGD_BITS (PAGE_SHIFT - 3)
|
|
#define PTRS_PER_PUD_BITS (PAGE_SHIFT - 3)
|
|
#define PTRS_PER_PMD_BITS (PAGE_SHIFT - 3)
|
|
#define PTRS_PER_PTE_BITS (PAGE_SHIFT - 3)
|
|
|
|
.macro tlb_do_page_fault, write
|
|
SYM_FUNC_START(tlb_do_page_fault_\write)
|
|
SAVE_ALL
|
|
csrrd a2, LOONGARCH_CSR_BADV
|
|
move a0, sp
|
|
REG_S a2, sp, PT_BVADDR
|
|
li.w a1, \write
|
|
la.abs t0, do_page_fault
|
|
jirl ra, t0, 0
|
|
RESTORE_ALL_AND_RET
|
|
SYM_FUNC_END(tlb_do_page_fault_\write)
|
|
.endm
|
|
|
|
tlb_do_page_fault 0
|
|
tlb_do_page_fault 1
|
|
|
|
SYM_FUNC_START(handle_tlb_protect)
|
|
BACKUP_T0T1
|
|
SAVE_ALL
|
|
move a0, sp
|
|
move a1, zero
|
|
csrrd a2, LOONGARCH_CSR_BADV
|
|
REG_S a2, sp, PT_BVADDR
|
|
la.abs t0, do_page_fault
|
|
jirl ra, t0, 0
|
|
RESTORE_ALL_AND_RET
|
|
SYM_FUNC_END(handle_tlb_protect)
|
|
|
|
SYM_FUNC_START(handle_tlb_load)
|
|
csrwr t0, EXCEPTION_KS0
|
|
csrwr t1, EXCEPTION_KS1
|
|
csrwr ra, EXCEPTION_KS2
|
|
|
|
/*
|
|
* The vmalloc handling is not in the hotpath.
|
|
*/
|
|
csrrd t0, LOONGARCH_CSR_BADV
|
|
bltz t0, vmalloc_load
|
|
csrrd t1, LOONGARCH_CSR_PGDL
|
|
|
|
vmalloc_done_load:
|
|
/* Get PGD offset in bytes */
|
|
bstrpick.d ra, t0, PTRS_PER_PGD_BITS + PGDIR_SHIFT - 1, PGDIR_SHIFT
|
|
alsl.d t1, ra, t1, 3
|
|
#if CONFIG_PGTABLE_LEVELS > 3
|
|
ld.d t1, t1, 0
|
|
bstrpick.d ra, t0, PTRS_PER_PUD_BITS + PUD_SHIFT - 1, PUD_SHIFT
|
|
alsl.d t1, ra, t1, 3
|
|
#endif
|
|
#if CONFIG_PGTABLE_LEVELS > 2
|
|
ld.d t1, t1, 0
|
|
bstrpick.d ra, t0, PTRS_PER_PMD_BITS + PMD_SHIFT - 1, PMD_SHIFT
|
|
alsl.d t1, ra, t1, 3
|
|
#endif
|
|
ld.d ra, t1, 0
|
|
|
|
/*
|
|
* For huge tlb entries, pmde doesn't contain an address but
|
|
* instead contains the tlb pte. Check the PAGE_HUGE bit and
|
|
* see if we need to jump to huge tlb processing.
|
|
*/
|
|
rotri.d ra, ra, _PAGE_HUGE_SHIFT + 1
|
|
bltz ra, tlb_huge_update_load
|
|
|
|
rotri.d ra, ra, 64 - (_PAGE_HUGE_SHIFT + 1)
|
|
bstrpick.d t0, t0, PTRS_PER_PTE_BITS + PAGE_SHIFT - 1, PAGE_SHIFT
|
|
alsl.d t1, t0, ra, _PTE_T_LOG2
|
|
|
|
#ifdef CONFIG_SMP
|
|
smp_pgtable_change_load:
|
|
ll.d t0, t1, 0
|
|
#else
|
|
ld.d t0, t1, 0
|
|
#endif
|
|
andi ra, t0, _PAGE_PRESENT
|
|
beqz ra, nopage_tlb_load
|
|
|
|
ori t0, t0, _PAGE_VALID
|
|
#ifdef CONFIG_SMP
|
|
sc.d t0, t1, 0
|
|
beqz t0, smp_pgtable_change_load
|
|
#else
|
|
st.d t0, t1, 0
|
|
#endif
|
|
tlbsrch
|
|
bstrins.d t1, zero, 3, 3
|
|
ld.d t0, t1, 0
|
|
ld.d t1, t1, 8
|
|
csrwr t0, LOONGARCH_CSR_TLBELO0
|
|
csrwr t1, LOONGARCH_CSR_TLBELO1
|
|
tlbwr
|
|
|
|
csrrd t0, EXCEPTION_KS0
|
|
csrrd t1, EXCEPTION_KS1
|
|
csrrd ra, EXCEPTION_KS2
|
|
ertn
|
|
|
|
#ifdef CONFIG_64BIT
|
|
vmalloc_load:
|
|
la.abs t1, swapper_pg_dir
|
|
b vmalloc_done_load
|
|
#endif
|
|
|
|
/* This is the entry point of a huge page. */
|
|
tlb_huge_update_load:
|
|
#ifdef CONFIG_SMP
|
|
ll.d ra, t1, 0
|
|
#endif
|
|
andi t0, ra, _PAGE_PRESENT
|
|
beqz t0, nopage_tlb_load
|
|
|
|
#ifdef CONFIG_SMP
|
|
ori t0, ra, _PAGE_VALID
|
|
sc.d t0, t1, 0
|
|
beqz t0, tlb_huge_update_load
|
|
ori t0, ra, _PAGE_VALID
|
|
#else
|
|
rotri.d ra, ra, 64 - (_PAGE_HUGE_SHIFT + 1)
|
|
ori t0, ra, _PAGE_VALID
|
|
st.d t0, t1, 0
|
|
#endif
|
|
csrrd ra, LOONGARCH_CSR_ASID
|
|
csrrd t1, LOONGARCH_CSR_BADV
|
|
andi ra, ra, CSR_ASID_ASID
|
|
invtlb INVTLB_ADDR_GFALSE_AND_ASID, ra, t1
|
|
|
|
/*
|
|
* A huge PTE describes an area the size of the
|
|
* configured huge page size. This is twice the
|
|
* of the large TLB entry size we intend to use.
|
|
* A TLB entry half the size of the configured
|
|
* huge page size is configured into entrylo0
|
|
* and entrylo1 to cover the contiguous huge PTE
|
|
* address space.
|
|
*/
|
|
/* Huge page: Move Global bit */
|
|
xori t0, t0, _PAGE_HUGE
|
|
lu12i.w t1, _PAGE_HGLOBAL >> 12
|
|
and t1, t0, t1
|
|
srli.d t1, t1, (_PAGE_HGLOBAL_SHIFT - _PAGE_GLOBAL_SHIFT)
|
|
or t0, t0, t1
|
|
|
|
move ra, t0
|
|
csrwr ra, LOONGARCH_CSR_TLBELO0
|
|
|
|
/* Convert to entrylo1 */
|
|
addi.d t1, zero, 1
|
|
slli.d t1, t1, (HPAGE_SHIFT - 1)
|
|
add.d t0, t0, t1
|
|
csrwr t0, LOONGARCH_CSR_TLBELO1
|
|
|
|
/* Set huge page tlb entry size */
|
|
addu16i.d t0, zero, (CSR_TLBIDX_PS >> 16)
|
|
addu16i.d t1, zero, (PS_HUGE_SIZE << (CSR_TLBIDX_PS_SHIFT - 16))
|
|
csrxchg t1, t0, LOONGARCH_CSR_TLBIDX
|
|
|
|
tlbfill
|
|
|
|
addu16i.d t0, zero, (CSR_TLBIDX_PS >> 16)
|
|
addu16i.d t1, zero, (PS_DEFAULT_SIZE << (CSR_TLBIDX_PS_SHIFT - 16))
|
|
csrxchg t1, t0, LOONGARCH_CSR_TLBIDX
|
|
|
|
csrrd t0, EXCEPTION_KS0
|
|
csrrd t1, EXCEPTION_KS1
|
|
csrrd ra, EXCEPTION_KS2
|
|
ertn
|
|
|
|
nopage_tlb_load:
|
|
dbar 0
|
|
csrrd ra, EXCEPTION_KS2
|
|
la.abs t0, tlb_do_page_fault_0
|
|
jr t0
|
|
SYM_FUNC_END(handle_tlb_load)
|
|
|
|
SYM_FUNC_START(handle_tlb_store)
|
|
csrwr t0, EXCEPTION_KS0
|
|
csrwr t1, EXCEPTION_KS1
|
|
csrwr ra, EXCEPTION_KS2
|
|
|
|
/*
|
|
* The vmalloc handling is not in the hotpath.
|
|
*/
|
|
csrrd t0, LOONGARCH_CSR_BADV
|
|
bltz t0, vmalloc_store
|
|
csrrd t1, LOONGARCH_CSR_PGDL
|
|
|
|
vmalloc_done_store:
|
|
/* Get PGD offset in bytes */
|
|
bstrpick.d ra, t0, PTRS_PER_PGD_BITS + PGDIR_SHIFT - 1, PGDIR_SHIFT
|
|
alsl.d t1, ra, t1, 3
|
|
#if CONFIG_PGTABLE_LEVELS > 3
|
|
ld.d t1, t1, 0
|
|
bstrpick.d ra, t0, PTRS_PER_PUD_BITS + PUD_SHIFT - 1, PUD_SHIFT
|
|
alsl.d t1, ra, t1, 3
|
|
#endif
|
|
#if CONFIG_PGTABLE_LEVELS > 2
|
|
ld.d t1, t1, 0
|
|
bstrpick.d ra, t0, PTRS_PER_PMD_BITS + PMD_SHIFT - 1, PMD_SHIFT
|
|
alsl.d t1, ra, t1, 3
|
|
#endif
|
|
ld.d ra, t1, 0
|
|
|
|
/*
|
|
* For huge tlb entries, pmde doesn't contain an address but
|
|
* instead contains the tlb pte. Check the PAGE_HUGE bit and
|
|
* see if we need to jump to huge tlb processing.
|
|
*/
|
|
rotri.d ra, ra, _PAGE_HUGE_SHIFT + 1
|
|
bltz ra, tlb_huge_update_store
|
|
|
|
rotri.d ra, ra, 64 - (_PAGE_HUGE_SHIFT + 1)
|
|
bstrpick.d t0, t0, PTRS_PER_PTE_BITS + PAGE_SHIFT - 1, PAGE_SHIFT
|
|
alsl.d t1, t0, ra, _PTE_T_LOG2
|
|
|
|
#ifdef CONFIG_SMP
|
|
smp_pgtable_change_store:
|
|
ll.d t0, t1, 0
|
|
#else
|
|
ld.d t0, t1, 0
|
|
#endif
|
|
andi ra, t0, _PAGE_PRESENT | _PAGE_WRITE
|
|
xori ra, ra, _PAGE_PRESENT | _PAGE_WRITE
|
|
bnez ra, nopage_tlb_store
|
|
|
|
ori t0, t0, (_PAGE_VALID | _PAGE_DIRTY | _PAGE_MODIFIED)
|
|
#ifdef CONFIG_SMP
|
|
sc.d t0, t1, 0
|
|
beqz t0, smp_pgtable_change_store
|
|
#else
|
|
st.d t0, t1, 0
|
|
#endif
|
|
tlbsrch
|
|
bstrins.d t1, zero, 3, 3
|
|
ld.d t0, t1, 0
|
|
ld.d t1, t1, 8
|
|
csrwr t0, LOONGARCH_CSR_TLBELO0
|
|
csrwr t1, LOONGARCH_CSR_TLBELO1
|
|
tlbwr
|
|
|
|
csrrd t0, EXCEPTION_KS0
|
|
csrrd t1, EXCEPTION_KS1
|
|
csrrd ra, EXCEPTION_KS2
|
|
ertn
|
|
|
|
#ifdef CONFIG_64BIT
|
|
vmalloc_store:
|
|
la.abs t1, swapper_pg_dir
|
|
b vmalloc_done_store
|
|
#endif
|
|
|
|
/* This is the entry point of a huge page. */
|
|
tlb_huge_update_store:
|
|
#ifdef CONFIG_SMP
|
|
ll.d ra, t1, 0
|
|
#endif
|
|
andi t0, ra, _PAGE_PRESENT | _PAGE_WRITE
|
|
xori t0, t0, _PAGE_PRESENT | _PAGE_WRITE
|
|
bnez t0, nopage_tlb_store
|
|
|
|
#ifdef CONFIG_SMP
|
|
ori t0, ra, (_PAGE_VALID | _PAGE_DIRTY | _PAGE_MODIFIED)
|
|
sc.d t0, t1, 0
|
|
beqz t0, tlb_huge_update_store
|
|
ori t0, ra, (_PAGE_VALID | _PAGE_DIRTY | _PAGE_MODIFIED)
|
|
#else
|
|
rotri.d ra, ra, 64 - (_PAGE_HUGE_SHIFT + 1)
|
|
ori t0, ra, (_PAGE_VALID | _PAGE_DIRTY | _PAGE_MODIFIED)
|
|
st.d t0, t1, 0
|
|
#endif
|
|
csrrd ra, LOONGARCH_CSR_ASID
|
|
csrrd t1, LOONGARCH_CSR_BADV
|
|
andi ra, ra, CSR_ASID_ASID
|
|
invtlb INVTLB_ADDR_GFALSE_AND_ASID, ra, t1
|
|
|
|
/*
|
|
* A huge PTE describes an area the size of the
|
|
* configured huge page size. This is twice the
|
|
* of the large TLB entry size we intend to use.
|
|
* A TLB entry half the size of the configured
|
|
* huge page size is configured into entrylo0
|
|
* and entrylo1 to cover the contiguous huge PTE
|
|
* address space.
|
|
*/
|
|
/* Huge page: Move Global bit */
|
|
xori t0, t0, _PAGE_HUGE
|
|
lu12i.w t1, _PAGE_HGLOBAL >> 12
|
|
and t1, t0, t1
|
|
srli.d t1, t1, (_PAGE_HGLOBAL_SHIFT - _PAGE_GLOBAL_SHIFT)
|
|
or t0, t0, t1
|
|
|
|
move ra, t0
|
|
csrwr ra, LOONGARCH_CSR_TLBELO0
|
|
|
|
/* Convert to entrylo1 */
|
|
addi.d t1, zero, 1
|
|
slli.d t1, t1, (HPAGE_SHIFT - 1)
|
|
add.d t0, t0, t1
|
|
csrwr t0, LOONGARCH_CSR_TLBELO1
|
|
|
|
/* Set huge page tlb entry size */
|
|
addu16i.d t0, zero, (CSR_TLBIDX_PS >> 16)
|
|
addu16i.d t1, zero, (PS_HUGE_SIZE << (CSR_TLBIDX_PS_SHIFT - 16))
|
|
csrxchg t1, t0, LOONGARCH_CSR_TLBIDX
|
|
|
|
tlbfill
|
|
|
|
/* Reset default page size */
|
|
addu16i.d t0, zero, (CSR_TLBIDX_PS >> 16)
|
|
addu16i.d t1, zero, (PS_DEFAULT_SIZE << (CSR_TLBIDX_PS_SHIFT - 16))
|
|
csrxchg t1, t0, LOONGARCH_CSR_TLBIDX
|
|
|
|
csrrd t0, EXCEPTION_KS0
|
|
csrrd t1, EXCEPTION_KS1
|
|
csrrd ra, EXCEPTION_KS2
|
|
ertn
|
|
|
|
nopage_tlb_store:
|
|
dbar 0
|
|
csrrd ra, EXCEPTION_KS2
|
|
la.abs t0, tlb_do_page_fault_1
|
|
jr t0
|
|
SYM_FUNC_END(handle_tlb_store)
|
|
|
|
SYM_FUNC_START(handle_tlb_modify)
|
|
csrwr t0, EXCEPTION_KS0
|
|
csrwr t1, EXCEPTION_KS1
|
|
csrwr ra, EXCEPTION_KS2
|
|
|
|
/*
|
|
* The vmalloc handling is not in the hotpath.
|
|
*/
|
|
csrrd t0, LOONGARCH_CSR_BADV
|
|
bltz t0, vmalloc_modify
|
|
csrrd t1, LOONGARCH_CSR_PGDL
|
|
|
|
vmalloc_done_modify:
|
|
/* Get PGD offset in bytes */
|
|
bstrpick.d ra, t0, PTRS_PER_PGD_BITS + PGDIR_SHIFT - 1, PGDIR_SHIFT
|
|
alsl.d t1, ra, t1, 3
|
|
#if CONFIG_PGTABLE_LEVELS > 3
|
|
ld.d t1, t1, 0
|
|
bstrpick.d ra, t0, PTRS_PER_PUD_BITS + PUD_SHIFT - 1, PUD_SHIFT
|
|
alsl.d t1, ra, t1, 3
|
|
#endif
|
|
#if CONFIG_PGTABLE_LEVELS > 2
|
|
ld.d t1, t1, 0
|
|
bstrpick.d ra, t0, PTRS_PER_PMD_BITS + PMD_SHIFT - 1, PMD_SHIFT
|
|
alsl.d t1, ra, t1, 3
|
|
#endif
|
|
ld.d ra, t1, 0
|
|
|
|
/*
|
|
* For huge tlb entries, pmde doesn't contain an address but
|
|
* instead contains the tlb pte. Check the PAGE_HUGE bit and
|
|
* see if we need to jump to huge tlb processing.
|
|
*/
|
|
rotri.d ra, ra, _PAGE_HUGE_SHIFT + 1
|
|
bltz ra, tlb_huge_update_modify
|
|
|
|
rotri.d ra, ra, 64 - (_PAGE_HUGE_SHIFT + 1)
|
|
bstrpick.d t0, t0, PTRS_PER_PTE_BITS + PAGE_SHIFT - 1, PAGE_SHIFT
|
|
alsl.d t1, t0, ra, _PTE_T_LOG2
|
|
|
|
#ifdef CONFIG_SMP
|
|
smp_pgtable_change_modify:
|
|
ll.d t0, t1, 0
|
|
#else
|
|
ld.d t0, t1, 0
|
|
#endif
|
|
andi ra, t0, _PAGE_WRITE
|
|
beqz ra, nopage_tlb_modify
|
|
|
|
ori t0, t0, (_PAGE_VALID | _PAGE_DIRTY | _PAGE_MODIFIED)
|
|
#ifdef CONFIG_SMP
|
|
sc.d t0, t1, 0
|
|
beqz t0, smp_pgtable_change_modify
|
|
#else
|
|
st.d t0, t1, 0
|
|
#endif
|
|
tlbsrch
|
|
bstrins.d t1, zero, 3, 3
|
|
ld.d t0, t1, 0
|
|
ld.d t1, t1, 8
|
|
csrwr t0, LOONGARCH_CSR_TLBELO0
|
|
csrwr t1, LOONGARCH_CSR_TLBELO1
|
|
tlbwr
|
|
|
|
csrrd t0, EXCEPTION_KS0
|
|
csrrd t1, EXCEPTION_KS1
|
|
csrrd ra, EXCEPTION_KS2
|
|
ertn
|
|
|
|
#ifdef CONFIG_64BIT
|
|
vmalloc_modify:
|
|
la.abs t1, swapper_pg_dir
|
|
b vmalloc_done_modify
|
|
#endif
|
|
|
|
/* This is the entry point of a huge page. */
|
|
tlb_huge_update_modify:
|
|
#ifdef CONFIG_SMP
|
|
ll.d ra, t1, 0
|
|
#endif
|
|
andi t0, ra, _PAGE_WRITE
|
|
beqz t0, nopage_tlb_modify
|
|
|
|
#ifdef CONFIG_SMP
|
|
ori t0, ra, (_PAGE_VALID | _PAGE_DIRTY | _PAGE_MODIFIED)
|
|
sc.d t0, t1, 0
|
|
beqz t0, tlb_huge_update_modify
|
|
ori t0, ra, (_PAGE_VALID | _PAGE_DIRTY | _PAGE_MODIFIED)
|
|
#else
|
|
rotri.d ra, ra, 64 - (_PAGE_HUGE_SHIFT + 1)
|
|
ori t0, ra, (_PAGE_VALID | _PAGE_DIRTY | _PAGE_MODIFIED)
|
|
st.d t0, t1, 0
|
|
#endif
|
|
csrrd ra, LOONGARCH_CSR_ASID
|
|
csrrd t1, LOONGARCH_CSR_BADV
|
|
andi ra, ra, CSR_ASID_ASID
|
|
invtlb INVTLB_ADDR_GFALSE_AND_ASID, ra, t1
|
|
|
|
/*
|
|
* A huge PTE describes an area the size of the
|
|
* configured huge page size. This is twice the
|
|
* of the large TLB entry size we intend to use.
|
|
* A TLB entry half the size of the configured
|
|
* huge page size is configured into entrylo0
|
|
* and entrylo1 to cover the contiguous huge PTE
|
|
* address space.
|
|
*/
|
|
/* Huge page: Move Global bit */
|
|
xori t0, t0, _PAGE_HUGE
|
|
lu12i.w t1, _PAGE_HGLOBAL >> 12
|
|
and t1, t0, t1
|
|
srli.d t1, t1, (_PAGE_HGLOBAL_SHIFT - _PAGE_GLOBAL_SHIFT)
|
|
or t0, t0, t1
|
|
|
|
move ra, t0
|
|
csrwr ra, LOONGARCH_CSR_TLBELO0
|
|
|
|
/* Convert to entrylo1 */
|
|
addi.d t1, zero, 1
|
|
slli.d t1, t1, (HPAGE_SHIFT - 1)
|
|
add.d t0, t0, t1
|
|
csrwr t0, LOONGARCH_CSR_TLBELO1
|
|
|
|
/* Set huge page tlb entry size */
|
|
addu16i.d t0, zero, (CSR_TLBIDX_PS >> 16)
|
|
addu16i.d t1, zero, (PS_HUGE_SIZE << (CSR_TLBIDX_PS_SHIFT - 16))
|
|
csrxchg t1, t0, LOONGARCH_CSR_TLBIDX
|
|
|
|
tlbfill
|
|
|
|
/* Reset default page size */
|
|
addu16i.d t0, zero, (CSR_TLBIDX_PS >> 16)
|
|
addu16i.d t1, zero, (PS_DEFAULT_SIZE << (CSR_TLBIDX_PS_SHIFT - 16))
|
|
csrxchg t1, t0, LOONGARCH_CSR_TLBIDX
|
|
|
|
csrrd t0, EXCEPTION_KS0
|
|
csrrd t1, EXCEPTION_KS1
|
|
csrrd ra, EXCEPTION_KS2
|
|
ertn
|
|
|
|
nopage_tlb_modify:
|
|
dbar 0
|
|
csrrd ra, EXCEPTION_KS2
|
|
la.abs t0, tlb_do_page_fault_1
|
|
jr t0
|
|
SYM_FUNC_END(handle_tlb_modify)
|
|
|
|
SYM_FUNC_START(handle_tlb_refill)
|
|
csrwr t0, LOONGARCH_CSR_TLBRSAVE
|
|
csrrd t0, LOONGARCH_CSR_PGD
|
|
lddir t0, t0, 3
|
|
#if CONFIG_PGTABLE_LEVELS > 3
|
|
lddir t0, t0, 2
|
|
#endif
|
|
#if CONFIG_PGTABLE_LEVELS > 2
|
|
lddir t0, t0, 1
|
|
#endif
|
|
ldpte t0, 0
|
|
ldpte t0, 1
|
|
tlbfill
|
|
csrrd t0, LOONGARCH_CSR_TLBRSAVE
|
|
ertn
|
|
SYM_FUNC_END(handle_tlb_refill)
|