Skip to content

Commit f93f67d

Browse files
committed
LoongArch: Improve hardware page table walker
LoongArch has similar problems explained in commit 7f0b1bf ("arm64: Fix barriers used for page table modifications"), when hardware page table walker (PTW) enabled, speculative accesses may cause spurious page fault in kernel space. Theoretically, in order to completely avoid spurious page fault we need a "dbar + ibar" pair between the page table modifications and the subsequent memory accesses using the corresponding virtual address. But "ibar" is too heavy for performace, so we only use a "dbar 0b11000" in set_pte(). And let spurious_fault() filter the rest rare spurious page faults which should be avoided by "ibar". Besides, we replace the llsc loop with amo in set_pte() which has better performace, and refactor mmu_context.h to 1) avoid any load/store/branch instructions between the writing of CSR.ASID & CSR.PGDL, 2) ensure flush tlb operation is after updating ASID. Signed-off-by: Huacai Chen <[email protected]>
1 parent f04de6d commit f93f67d

File tree

4 files changed

+83
-27
lines changed

4 files changed

+83
-27
lines changed

arch/loongarch/include/asm/atomic.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,15 @@
1515
#define __LL "ll.w "
1616
#define __SC "sc.w "
1717
#define __AMADD "amadd.w "
18+
#define __AMOR "amor.w "
1819
#define __AMAND_DB "amand_db.w "
1920
#define __AMOR_DB "amor_db.w "
2021
#define __AMXOR_DB "amxor_db.w "
2122
#elif __SIZEOF_LONG__ == 8
2223
#define __LL "ll.d "
2324
#define __SC "sc.d "
2425
#define __AMADD "amadd.d "
26+
#define __AMOR "amor.d "
2527
#define __AMAND_DB "amand_db.d "
2628
#define __AMOR_DB "amor_db.d "
2729
#define __AMXOR_DB "amxor_db.d "

arch/loongarch/include/asm/mmu_context.h

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,12 @@ static inline void enter_lazy_tlb(struct mm_struct *mm, struct task_struct *tsk)
4949

5050
/* Normal, classic get_new_mmu_context */
5151
static inline void
52-
get_new_mmu_context(struct mm_struct *mm, unsigned long cpu)
52+
get_new_mmu_context(struct mm_struct *mm, unsigned long cpu, bool *need_flush)
5353
{
5454
u64 asid = asid_cache(cpu);
5555

5656
if (!((++asid) & cpu_asid_mask(&cpu_data[cpu])))
57-
local_flush_tlb_user(); /* start new asid cycle */
57+
*need_flush = true; /* start new asid cycle */
5858

5959
cpu_context(cpu, mm) = asid_cache(cpu) = asid;
6060
}
@@ -74,21 +74,34 @@ init_new_context(struct task_struct *tsk, struct mm_struct *mm)
7474
return 0;
7575
}
7676

77+
static inline void atomic_update_pgd_asid(unsigned long asid, unsigned long pgdl)
78+
{
79+
__asm__ __volatile__(
80+
"csrwr %[pgdl_val], %[pgdl_reg] \n\t"
81+
"csrwr %[asid_val], %[asid_reg] \n\t"
82+
: [asid_val] "+r" (asid), [pgdl_val] "+r" (pgdl)
83+
: [asid_reg] "i" (LOONGARCH_CSR_ASID), [pgdl_reg] "i" (LOONGARCH_CSR_PGDL)
84+
: "memory"
85+
);
86+
}
87+
7788
static inline void switch_mm_irqs_off(struct mm_struct *prev, struct mm_struct *next,
7889
struct task_struct *tsk)
7990
{
91+
bool need_flush = false;
8092
unsigned int cpu = smp_processor_id();
8193

8294
/* Check if our ASID is of an older version and thus invalid */
8395
if (!asid_valid(next, cpu))
84-
get_new_mmu_context(next, cpu);
85-
86-
write_csr_asid(cpu_asid(cpu, next));
96+
get_new_mmu_context(next, cpu, &need_flush);
8797

8898
if (next != &init_mm)
89-
csr_write64((unsigned long)next->pgd, LOONGARCH_CSR_PGDL);
99+
atomic_update_pgd_asid(cpu_asid(cpu, next), (unsigned long)next->pgd);
90100
else
91-
csr_write64((unsigned long)invalid_pg_dir, LOONGARCH_CSR_PGDL);
101+
atomic_update_pgd_asid(cpu_asid(cpu, next), (unsigned long)invalid_pg_dir);
102+
103+
if (need_flush)
104+
local_flush_tlb_user(); /* Flush tlb after update ASID */
92105

93106
/*
94107
* Mark current->active_mm as not "active" anymore.
@@ -135,9 +148,15 @@ drop_mmu_context(struct mm_struct *mm, unsigned int cpu)
135148
asid = read_csr_asid() & cpu_asid_mask(&current_cpu_data);
136149

137150
if (asid == cpu_asid(cpu, mm)) {
151+
bool need_flush = false;
152+
138153
if (!current->mm || (current->mm == mm)) {
139-
get_new_mmu_context(mm, cpu);
154+
get_new_mmu_context(mm, cpu, &need_flush);
155+
140156
write_csr_asid(cpu_asid(cpu, mm));
157+
if (need_flush)
158+
local_flush_tlb_user(); /* Flush tlb after update ASID */
159+
141160
goto out;
142161
}
143162
}

arch/loongarch/include/asm/pgtable.h

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -331,29 +331,23 @@ static inline void set_pte(pte_t *ptep, pte_t pteval)
331331
* Make sure the buddy is global too (if it's !none,
332332
* it better already be global)
333333
*/
334+
if (pte_none(ptep_get(buddy))) {
334335
#ifdef CONFIG_SMP
335-
/*
336-
* For SMP, multiple CPUs can race, so we need to do
337-
* this atomically.
338-
*/
339-
unsigned long page_global = _PAGE_GLOBAL;
340-
unsigned long tmp;
341-
342-
__asm__ __volatile__ (
343-
"1:" __LL "%[tmp], %[buddy] \n"
344-
" bnez %[tmp], 2f \n"
345-
" or %[tmp], %[tmp], %[global] \n"
346-
__SC "%[tmp], %[buddy] \n"
347-
" beqz %[tmp], 1b \n"
348-
" nop \n"
349-
"2: \n"
350-
__WEAK_LLSC_MB
351-
: [buddy] "+m" (buddy->pte), [tmp] "=&r" (tmp)
352-
: [global] "r" (page_global));
336+
/*
337+
* For SMP, multiple CPUs can race, so we need
338+
* to do this atomically.
339+
*/
340+
__asm__ __volatile__(
341+
__AMOR "$zero, %[global], %[buddy] \n"
342+
: [buddy] "+ZB" (buddy->pte)
343+
: [global] "r" (_PAGE_GLOBAL)
344+
: "memory");
345+
346+
DBAR(0b11000); /* o_wrw = 0b11000 */
353347
#else /* !CONFIG_SMP */
354-
if (pte_none(ptep_get(buddy)))
355348
WRITE_ONCE(*buddy, __pte(pte_val(ptep_get(buddy)) | _PAGE_GLOBAL));
356349
#endif /* CONFIG_SMP */
350+
}
357351
}
358352
}
359353

arch/loongarch/mm/fault.c

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,52 @@
3131

3232
int show_unhandled_signals = 1;
3333

34+
static int __kprobes spurious_fault(unsigned long write, unsigned long address)
35+
{
36+
pgd_t *pgd;
37+
p4d_t *p4d;
38+
pud_t *pud;
39+
pmd_t *pmd;
40+
pte_t *pte;
41+
42+
if (!(address & __UA_LIMIT))
43+
return 0;
44+
45+
pgd = pgd_offset_k(address);
46+
if (!pgd_present(pgdp_get(pgd)))
47+
return 0;
48+
49+
p4d = p4d_offset(pgd, address);
50+
if (!p4d_present(p4dp_get(p4d)))
51+
return 0;
52+
53+
pud = pud_offset(p4d, address);
54+
if (!pud_present(pudp_get(pud)))
55+
return 0;
56+
57+
pmd = pmd_offset(pud, address);
58+
if (!pmd_present(pmdp_get(pmd)))
59+
return 0;
60+
61+
if (pmd_leaf(*pmd)) {
62+
return write ? pmd_write(pmdp_get(pmd)) : 1;
63+
} else {
64+
pte = pte_offset_kernel(pmd, address);
65+
if (!pte_present(ptep_get(pte)))
66+
return 0;
67+
68+
return write ? pte_write(ptep_get(pte)) : 1;
69+
}
70+
}
71+
3472
static void __kprobes no_context(struct pt_regs *regs,
3573
unsigned long write, unsigned long address)
3674
{
3775
const int field = sizeof(unsigned long) * 2;
3876

77+
if (spurious_fault(write, address))
78+
return;
79+
3980
/* Are we prepared to handle this kernel fault? */
4081
if (fixup_exception(regs))
4182
return;

0 commit comments

Comments
 (0)