diff options
Diffstat (limited to 'arch/xtensa/mm/tlb.c')
| -rw-r--r-- | arch/xtensa/mm/tlb.c | 563 |
1 files changed, 148 insertions, 415 deletions
diff --git a/arch/xtensa/mm/tlb.c b/arch/xtensa/mm/tlb.c index d3bd3bfc3b3..5ece856c572 100644 --- a/arch/xtensa/mm/tlb.c +++ b/arch/xtensa/mm/tlb.c @@ -1,5 +1,5 @@ /* - * arch/xtensa/mm/mmu.c + * arch/xtensa/mm/tlb.c * * Logic that manipulates the Xtensa MMU. Derived from MIPS. * @@ -18,18 +18,17 @@ #include <asm/processor.h> #include <asm/mmu_context.h> #include <asm/tlbflush.h> -#include <asm/system.h> #include <asm/cacheflush.h> static inline void __flush_itlb_all (void) { - int way, index; + int w, i; - for (way = 0; way < XCHAL_ITLB_ARF_WAYS; way++) { - for (index = 0; index < ITLB_ENTRIES_PER_ARF_WAY; index++) { - int entry = way + (index << PAGE_SHIFT); - invalidate_itlb_entry_no_isync (entry); + for (w = 0; w < ITLB_ARF_WAYS; w++) { + for (i = 0; i < (1 << XCHAL_ITLB_ARF_ENTRIES_LOG2); i++) { + int e = w + (i << PAGE_SHIFT); + invalidate_itlb_entry_no_isync(e); } } asm volatile ("isync\n"); @@ -37,19 +36,19 @@ static inline void __flush_itlb_all (void) static inline void __flush_dtlb_all (void) { - int way, index; + int w, i; - for (way = 0; way < XCHAL_DTLB_ARF_WAYS; way++) { - for (index = 0; index < DTLB_ENTRIES_PER_ARF_WAY; index++) { - int entry = way + (index << PAGE_SHIFT); - invalidate_dtlb_entry_no_isync (entry); + for (w = 0; w < DTLB_ARF_WAYS; w++) { + for (i = 0; i < (1 << XCHAL_DTLB_ARF_ENTRIES_LOG2); i++) { + int e = w + (i << PAGE_SHIFT); + invalidate_dtlb_entry_no_isync(e); } } asm volatile ("isync\n"); } -void flush_tlb_all (void) +void local_flush_tlb_all(void) { __flush_itlb_all(); __flush_dtlb_all(); @@ -61,43 +60,53 @@ void flush_tlb_all (void) * a new context will be assigned to it. */ -void flush_tlb_mm(struct mm_struct *mm) +void local_flush_tlb_mm(struct mm_struct *mm) { -#if 0 - printk("[tlbmm<%lx>]\n", (unsigned long)mm->context); -#endif + int cpu = smp_processor_id(); if (mm == current->active_mm) { - int flags; - local_save_flags(flags); - get_new_mmu_context(mm, asid_cache); - set_rasid_register(ASID_INSERT(mm->context)); + unsigned long flags; + local_irq_save(flags); + mm->context.asid[cpu] = NO_CONTEXT; + activate_context(mm, cpu); local_irq_restore(flags); + } else { + mm->context.asid[cpu] = NO_CONTEXT; + mm->context.cpu = -1; } - else - mm->context = 0; } -void flush_tlb_range (struct vm_area_struct *vma, - unsigned long start, unsigned long end) + +#define _ITLB_ENTRIES (ITLB_ARF_WAYS << XCHAL_ITLB_ARF_ENTRIES_LOG2) +#define _DTLB_ENTRIES (DTLB_ARF_WAYS << XCHAL_DTLB_ARF_ENTRIES_LOG2) +#if _ITLB_ENTRIES > _DTLB_ENTRIES +# define _TLB_ENTRIES _ITLB_ENTRIES +#else +# define _TLB_ENTRIES _DTLB_ENTRIES +#endif + +void local_flush_tlb_range(struct vm_area_struct *vma, + unsigned long start, unsigned long end) { + int cpu = smp_processor_id(); struct mm_struct *mm = vma->vm_mm; unsigned long flags; - if (mm->context == NO_CONTEXT) + if (mm->context.asid[cpu] == NO_CONTEXT) return; #if 0 printk("[tlbrange<%02lx,%08lx,%08lx>]\n", - (unsigned long)mm->context, start, end); + (unsigned long)mm->context.asid[cpu], start, end); #endif - local_save_flags(flags); + local_irq_save(flags); - if (end-start + (PAGE_SIZE-1) <= SMALLEST_NTLB_ENTRIES << PAGE_SHIFT) { + if (end-start + (PAGE_SIZE-1) <= _TLB_ENTRIES << PAGE_SHIFT) { int oldpid = get_rasid_register(); - set_rasid_register (ASID_INSERT(mm->context)); + + set_rasid_register(ASID_INSERT(mm->context.asid[cpu])); start &= PAGE_MASK; - if (vma->vm_flags & VM_EXEC) + if (vma->vm_flags & VM_EXEC) while(start < end) { invalidate_itlb_mapping(start); invalidate_dtlb_mapping(start); @@ -111,29 +120,25 @@ void flush_tlb_range (struct vm_area_struct *vma, set_rasid_register(oldpid); } else { - get_new_mmu_context(mm, asid_cache); - if (mm == current->active_mm) - set_rasid_register(ASID_INSERT(mm->context)); + local_flush_tlb_mm(mm); } local_irq_restore(flags); } -void flush_tlb_page (struct vm_area_struct *vma, unsigned long page) +void local_flush_tlb_page(struct vm_area_struct *vma, unsigned long page) { + int cpu = smp_processor_id(); struct mm_struct* mm = vma->vm_mm; unsigned long flags; int oldpid; -#if 0 - printk("[tlbpage<%02lx,%08lx>]\n", - (unsigned long)mm->context, page); -#endif - if(mm->context == NO_CONTEXT) + if (mm->context.asid[cpu] == NO_CONTEXT) return; - local_save_flags(flags); + local_irq_save(flags); - oldpid = get_rasid_register(); + oldpid = get_rasid_register(); + set_rasid_register(ASID_INSERT(mm->context.asid[cpu])); if (vma->vm_flags & VM_EXEC) invalidate_itlb_mapping(page); @@ -142,404 +147,132 @@ void flush_tlb_page (struct vm_area_struct *vma, unsigned long page) set_rasid_register(oldpid); local_irq_restore(flags); - -#if 0 - flush_tlb_all(); - return; -#endif -} - - -#ifdef DEBUG_TLB - -#define USE_ITLB 0 -#define USE_DTLB 1 - -struct way_config_t { - int indicies; - int indicies_log2; - int pgsz_log2; - int arf; -}; - -static struct way_config_t itlb[XCHAL_ITLB_WAYS] = -{ - { XCHAL_ITLB_SET(XCHAL_ITLB_WAY0_SET, ENTRIES), - XCHAL_ITLB_SET(XCHAL_ITLB_WAY0_SET, ENTRIES_LOG2), - XCHAL_ITLB_SET(XCHAL_ITLB_WAY0_SET, PAGESZ_LOG2_MIN), - XCHAL_ITLB_SET(XCHAL_ITLB_WAY0_SET, ARF) - }, - { XCHAL_ITLB_SET(XCHAL_ITLB_WAY1_SET, ENTRIES), - XCHAL_ITLB_SET(XCHAL_ITLB_WAY1_SET, ENTRIES_LOG2), - XCHAL_ITLB_SET(XCHAL_ITLB_WAY1_SET, PAGESZ_LOG2_MIN), - XCHAL_ITLB_SET(XCHAL_ITLB_WAY1_SET, ARF) - }, - { XCHAL_ITLB_SET(XCHAL_ITLB_WAY2_SET, ENTRIES), - XCHAL_ITLB_SET(XCHAL_ITLB_WAY2_SET, ENTRIES_LOG2), - XCHAL_ITLB_SET(XCHAL_ITLB_WAY2_SET, PAGESZ_LOG2_MIN), - XCHAL_ITLB_SET(XCHAL_ITLB_WAY2_SET, ARF) - }, - { XCHAL_ITLB_SET(XCHAL_ITLB_WAY3_SET, ENTRIES), - XCHAL_ITLB_SET(XCHAL_ITLB_WAY3_SET, ENTRIES_LOG2), - XCHAL_ITLB_SET(XCHAL_ITLB_WAY3_SET, PAGESZ_LOG2_MIN), - XCHAL_ITLB_SET(XCHAL_ITLB_WAY3_SET, ARF) - }, - { XCHAL_ITLB_SET(XCHAL_ITLB_WAY4_SET, ENTRIES), - XCHAL_ITLB_SET(XCHAL_ITLB_WAY4_SET, ENTRIES_LOG2), - XCHAL_ITLB_SET(XCHAL_ITLB_WAY4_SET, PAGESZ_LOG2_MIN), - XCHAL_ITLB_SET(XCHAL_ITLB_WAY4_SET, ARF) - }, - { XCHAL_ITLB_SET(XCHAL_ITLB_WAY5_SET, ENTRIES), - XCHAL_ITLB_SET(XCHAL_ITLB_WAY5_SET, ENTRIES_LOG2), - XCHAL_ITLB_SET(XCHAL_ITLB_WAY5_SET, PAGESZ_LOG2_MIN), - XCHAL_ITLB_SET(XCHAL_ITLB_WAY5_SET, ARF) - }, - { XCHAL_ITLB_SET(XCHAL_ITLB_WAY6_SET, ENTRIES), - XCHAL_ITLB_SET(XCHAL_ITLB_WAY6_SET, ENTRIES_LOG2), - XCHAL_ITLB_SET(XCHAL_ITLB_WAY6_SET, PAGESZ_LOG2_MIN), - XCHAL_ITLB_SET(XCHAL_ITLB_WAY6_SET, ARF) - } -}; - -static struct way_config_t dtlb[XCHAL_DTLB_WAYS] = -{ - { XCHAL_DTLB_SET(XCHAL_DTLB_WAY0_SET, ENTRIES), - XCHAL_DTLB_SET(XCHAL_DTLB_WAY0_SET, ENTRIES_LOG2), - XCHAL_DTLB_SET(XCHAL_DTLB_WAY0_SET, PAGESZ_LOG2_MIN), - XCHAL_DTLB_SET(XCHAL_DTLB_WAY0_SET, ARF) - }, - { XCHAL_DTLB_SET(XCHAL_DTLB_WAY1_SET, ENTRIES), - XCHAL_DTLB_SET(XCHAL_DTLB_WAY1_SET, ENTRIES_LOG2), - XCHAL_DTLB_SET(XCHAL_DTLB_WAY1_SET, PAGESZ_LOG2_MIN), - XCHAL_DTLB_SET(XCHAL_DTLB_WAY1_SET, ARF) - }, - { XCHAL_DTLB_SET(XCHAL_DTLB_WAY2_SET, ENTRIES), - XCHAL_DTLB_SET(XCHAL_DTLB_WAY2_SET, ENTRIES_LOG2), - XCHAL_DTLB_SET(XCHAL_DTLB_WAY2_SET, PAGESZ_LOG2_MIN), - XCHAL_DTLB_SET(XCHAL_DTLB_WAY2_SET, ARF) - }, - { XCHAL_DTLB_SET(XCHAL_DTLB_WAY3_SET, ENTRIES), - XCHAL_DTLB_SET(XCHAL_DTLB_WAY3_SET, ENTRIES_LOG2), - XCHAL_DTLB_SET(XCHAL_DTLB_WAY3_SET, PAGESZ_LOG2_MIN), - XCHAL_DTLB_SET(XCHAL_DTLB_WAY3_SET, ARF) - }, - { XCHAL_DTLB_SET(XCHAL_DTLB_WAY4_SET, ENTRIES), - XCHAL_DTLB_SET(XCHAL_DTLB_WAY4_SET, ENTRIES_LOG2), - XCHAL_DTLB_SET(XCHAL_DTLB_WAY4_SET, PAGESZ_LOG2_MIN), - XCHAL_DTLB_SET(XCHAL_DTLB_WAY4_SET, ARF) - }, - { XCHAL_DTLB_SET(XCHAL_DTLB_WAY5_SET, ENTRIES), - XCHAL_DTLB_SET(XCHAL_DTLB_WAY5_SET, ENTRIES_LOG2), - XCHAL_DTLB_SET(XCHAL_DTLB_WAY5_SET, PAGESZ_LOG2_MIN), - XCHAL_DTLB_SET(XCHAL_DTLB_WAY5_SET, ARF) - }, - { XCHAL_DTLB_SET(XCHAL_DTLB_WAY6_SET, ENTRIES), - XCHAL_DTLB_SET(XCHAL_DTLB_WAY6_SET, ENTRIES_LOG2), - XCHAL_DTLB_SET(XCHAL_DTLB_WAY6_SET, PAGESZ_LOG2_MIN), - XCHAL_DTLB_SET(XCHAL_DTLB_WAY6_SET, ARF) - }, - { XCHAL_DTLB_SET(XCHAL_DTLB_WAY7_SET, ENTRIES), - XCHAL_DTLB_SET(XCHAL_DTLB_WAY7_SET, ENTRIES_LOG2), - XCHAL_DTLB_SET(XCHAL_DTLB_WAY7_SET, PAGESZ_LOG2_MIN), - XCHAL_DTLB_SET(XCHAL_DTLB_WAY7_SET, ARF) - }, - { XCHAL_DTLB_SET(XCHAL_DTLB_WAY8_SET, ENTRIES), - XCHAL_DTLB_SET(XCHAL_DTLB_WAY8_SET, ENTRIES_LOG2), - XCHAL_DTLB_SET(XCHAL_DTLB_WAY8_SET, PAGESZ_LOG2_MIN), - XCHAL_DTLB_SET(XCHAL_DTLB_WAY8_SET, ARF) - }, - { XCHAL_DTLB_SET(XCHAL_DTLB_WAY9_SET, ENTRIES), - XCHAL_DTLB_SET(XCHAL_DTLB_WAY9_SET, ENTRIES_LOG2), - XCHAL_DTLB_SET(XCHAL_DTLB_WAY9_SET, PAGESZ_LOG2_MIN), - XCHAL_DTLB_SET(XCHAL_DTLB_WAY9_SET, ARF) - } -}; - -/* Total number of entries: */ -#define ITLB_TOTAL_ENTRIES \ - XCHAL_ITLB_SET(XCHAL_ITLB_WAY0_SET, ENTRIES) + \ - XCHAL_ITLB_SET(XCHAL_ITLB_WAY1_SET, ENTRIES) + \ - XCHAL_ITLB_SET(XCHAL_ITLB_WAY2_SET, ENTRIES) + \ - XCHAL_ITLB_SET(XCHAL_ITLB_WAY3_SET, ENTRIES) + \ - XCHAL_ITLB_SET(XCHAL_ITLB_WAY4_SET, ENTRIES) + \ - XCHAL_ITLB_SET(XCHAL_ITLB_WAY5_SET, ENTRIES) + \ - XCHAL_ITLB_SET(XCHAL_ITLB_WAY6_SET, ENTRIES) -#define DTLB_TOTAL_ENTRIES \ - XCHAL_DTLB_SET(XCHAL_DTLB_WAY0_SET, ENTRIES) + \ - XCHAL_DTLB_SET(XCHAL_DTLB_WAY1_SET, ENTRIES) + \ - XCHAL_DTLB_SET(XCHAL_DTLB_WAY2_SET, ENTRIES) + \ - XCHAL_DTLB_SET(XCHAL_DTLB_WAY3_SET, ENTRIES) + \ - XCHAL_DTLB_SET(XCHAL_DTLB_WAY4_SET, ENTRIES) + \ - XCHAL_DTLB_SET(XCHAL_DTLB_WAY5_SET, ENTRIES) + \ - XCHAL_DTLB_SET(XCHAL_DTLB_WAY6_SET, ENTRIES) + \ - XCHAL_DTLB_SET(XCHAL_DTLB_WAY7_SET, ENTRIES) + \ - XCHAL_DTLB_SET(XCHAL_DTLB_WAY8_SET, ENTRIES) + \ - XCHAL_DTLB_SET(XCHAL_DTLB_WAY9_SET, ENTRIES) - - -typedef struct { - unsigned va; - unsigned pa; - unsigned char asid; - unsigned char ca; - unsigned char way; - unsigned char index; - unsigned char pgsz_log2; /* 0 .. 32 */ - unsigned char type; /* 0=ITLB 1=DTLB */ -} tlb_dump_entry_t; - -/* Return -1 if a precedes b, +1 if a follows b, 0 if same: */ -int cmp_tlb_dump_info( tlb_dump_entry_t *a, tlb_dump_entry_t *b ) -{ - if (a->asid < b->asid) return -1; - if (a->asid > b->asid) return 1; - if (a->va < b->va) return -1; - if (a->va > b->va) return 1; - if (a->pa < b->pa) return -1; - if (a->pa > b->pa) return 1; - if (a->ca < b->ca) return -1; - if (a->ca > b->ca) return 1; - if (a->way < b->way) return -1; - if (a->way > b->way) return 1; - if (a->index < b->index) return -1; - if (a->index > b->index) return 1; - return 0; -} - -void sort_tlb_dump_info( tlb_dump_entry_t *t, int n ) -{ - int i, j; - /* Simple O(n*n) sort: */ - for (i = 0; i < n-1; i++) - for (j = i+1; j < n; j++) - if (cmp_tlb_dump_info(t+i, t+j) > 0) { - tlb_dump_entry_t tmp = t[i]; - t[i] = t[j]; - t[j] = tmp; - } -} - - -static tlb_dump_entry_t itlb_dump_info[ITLB_TOTAL_ENTRIES]; -static tlb_dump_entry_t dtlb_dump_info[DTLB_TOTAL_ENTRIES]; - - -static inline char *way_type (int type) -{ - return type ? "autorefill" : "non-autorefill"; -} - -void print_entry (struct way_config_t *way_info, - unsigned int way, - unsigned int index, - unsigned int virtual, - unsigned int translation) -{ - char valid_chr; - unsigned int va, pa, asid, ca; - - va = virtual & - ~((1 << (way_info->pgsz_log2 + way_info->indicies_log2)) - 1); - asid = virtual & ((1 << XCHAL_MMU_ASID_BITS) - 1); - pa = translation & ~((1 << way_info->pgsz_log2) - 1); - ca = translation & ((1 << XCHAL_MMU_CA_BITS) - 1); - valid_chr = asid ? 'V' : 'I'; - - /* Compute and incorporate the effect of the index bits on the - * va. It's more useful for kernel debugging, since we always - * want to know the effective va anyway. */ - - va += index << way_info->pgsz_log2; - - printk ("\t[%d,%d] (%c) vpn 0x%.8x ppn 0x%.8x asid 0x%.2x am 0x%x\n", - way, index, valid_chr, va, pa, asid, ca); -} - -void print_itlb_entry (struct way_config_t *way_info, int way, int index) -{ - print_entry (way_info, way, index, - read_itlb_virtual (way + (index << way_info->pgsz_log2)), - read_itlb_translation (way + (index << way_info->pgsz_log2))); -} - -void print_dtlb_entry (struct way_config_t *way_info, int way, int index) -{ - print_entry (way_info, way, index, - read_dtlb_virtual (way + (index << way_info->pgsz_log2)), - read_dtlb_translation (way + (index << way_info->pgsz_log2))); -} - -void dump_itlb (void) -{ - int way, index; - - printk ("\nITLB: ways = %d\n", XCHAL_ITLB_WAYS); - - for (way = 0; way < XCHAL_ITLB_WAYS; way++) { - printk ("\nWay: %d, Entries: %d, MinPageSize: %d, Type: %s\n", - way, itlb[way].indicies, - itlb[way].pgsz_log2, way_type(itlb[way].arf)); - for (index = 0; index < itlb[way].indicies; index++) { - print_itlb_entry(&itlb[way], way, index); - } - } } -void dump_dtlb (void) +void local_flush_tlb_kernel_range(unsigned long start, unsigned long end) { - int way, index; - - printk ("\nDTLB: ways = %d\n", XCHAL_DTLB_WAYS); - - for (way = 0; way < XCHAL_DTLB_WAYS; way++) { - printk ("\nWay: %d, Entries: %d, MinPageSize: %d, Type: %s\n", - way, dtlb[way].indicies, - dtlb[way].pgsz_log2, way_type(dtlb[way].arf)); - for (index = 0; index < dtlb[way].indicies; index++) { - print_dtlb_entry(&dtlb[way], way, index); + if (end > start && start >= TASK_SIZE && end <= PAGE_OFFSET && + end - start < _TLB_ENTRIES << PAGE_SHIFT) { + start &= PAGE_MASK; + while (start < end) { + invalidate_itlb_mapping(start); + invalidate_dtlb_mapping(start); + start += PAGE_SIZE; } + } else { + local_flush_tlb_all(); } } -void dump_tlb (tlb_dump_entry_t *tinfo, struct way_config_t *config, - int entries, int ways, int type, int show_invalid) -{ - tlb_dump_entry_t *e = tinfo; - int way, i; - - /* Gather all info: */ - for (way = 0; way < ways; way++) { - struct way_config_t *cfg = config + way; - for (i = 0; i < cfg->indicies; i++) { - unsigned wayindex = way + (i << cfg->pgsz_log2); - unsigned vv = (type ? read_dtlb_virtual (wayindex) - : read_itlb_virtual (wayindex)); - unsigned pp = (type ? read_dtlb_translation (wayindex) - : read_itlb_translation (wayindex)); - - /* Compute and incorporate the effect of the index bits on the - * va. It's more useful for kernel debugging, since we always - * want to know the effective va anyway. */ - - e->va = (vv & ~((1 << (cfg->pgsz_log2 + cfg->indicies_log2)) - 1)); - e->va += (i << cfg->pgsz_log2); - e->pa = (pp & ~((1 << cfg->pgsz_log2) - 1)); - e->asid = (vv & ((1 << XCHAL_MMU_ASID_BITS) - 1)); - e->ca = (pp & ((1 << XCHAL_MMU_CA_BITS) - 1)); - e->way = way; - e->index = i; - e->pgsz_log2 = cfg->pgsz_log2; - e->type = type; - e++; - } - } -#if 1 - /* Sort by ASID and VADDR: */ - sort_tlb_dump_info (tinfo, entries); -#endif - - /* Display all sorted info: */ - printk ("\n%cTLB dump:\n", (type ? 'D' : 'I')); - for (e = tinfo, i = 0; i < entries; i++, e++) { -#if 0 - if (e->asid == 0 && !show_invalid) - continue; -#endif - printk ("%c way=%d i=%d ASID=%02X V=%08X -> P=%08X CA=%X (%d %cB)\n", - (e->type ? 'D' : 'I'), e->way, e->index, - e->asid, e->va, e->pa, e->ca, - (1 << (e->pgsz_log2 % 10)), - " kMG"[e->pgsz_log2 / 10] - ); - } -} +#ifdef CONFIG_DEBUG_TLB_SANITY -void dump_tlbs2 (int showinv) +static unsigned get_pte_for_vaddr(unsigned vaddr) { - dump_tlb (itlb_dump_info, itlb, ITLB_TOTAL_ENTRIES, XCHAL_ITLB_WAYS, 0, showinv); - dump_tlb (dtlb_dump_info, dtlb, DTLB_TOTAL_ENTRIES, XCHAL_DTLB_WAYS, 1, showinv); + struct task_struct *task = get_current(); + struct mm_struct *mm = task->mm; + pgd_t *pgd; + pmd_t *pmd; + pte_t *pte; + + if (!mm) + mm = task->active_mm; + pgd = pgd_offset(mm, vaddr); + if (pgd_none_or_clear_bad(pgd)) + return 0; + pmd = pmd_offset(pgd, vaddr); + if (pmd_none_or_clear_bad(pmd)) + return 0; + pte = pte_offset_map(pmd, vaddr); + if (!pte) + return 0; + return pte_val(*pte); } -void dump_all_tlbs (void) -{ - dump_tlbs2 (1); -} +enum { + TLB_SUSPICIOUS = 1, + TLB_INSANE = 2, +}; -void dump_valid_tlbs (void) +static void tlb_insane(void) { - dump_tlbs2 (0); + BUG_ON(1); } - -void dump_tlbs (void) +static void tlb_suspicious(void) { - dump_itlb(); - dump_dtlb(); + WARN_ON(1); } -void dump_cache_tag(int dcache, int idx) +/* + * Check that TLB entries with kernel ASID (1) have kernel VMA (>= TASK_SIZE), + * and TLB entries with user ASID (>=4) have VMA < TASK_SIZE. + * + * Check that valid TLB entries either have the same PA as the PTE, or PTE is + * marked as non-present. Non-present PTE and the page with non-zero refcount + * and zero mapcount is normal for batched TLB flush operation. Zero refcount + * means that the page was freed prematurely. Non-zero mapcount is unusual, + * but does not necessary means an error, thus marked as suspicious. + */ +static int check_tlb_entry(unsigned w, unsigned e, bool dtlb) { - int w, i, s, e; - unsigned long tag, index; - unsigned long num_lines, num_ways, cache_size, line_size; - - num_ways = dcache ? XCHAL_DCACHE_WAYS : XCHAL_ICACHE_WAYS; - cache_size = dcache ? XCHAL_DCACHE_SIZE : XCHAL_ICACHE_SIZE; - line_size = dcache ? XCHAL_DCACHE_LINESIZE : XCHAL_ICACHE_LINESIZE; - - num_lines = cache_size / num_ways; - - s = 0; e = num_lines; - - if (idx >= 0) - e = (s = idx * line_size) + 1; - - for (i = s; i < e; i+= line_size) { - printk("\nline %#08x:", i); - for (w = 0; w < num_ways; w++) { - index = w * num_lines + i; - if (dcache) - __asm__ __volatile__("ldct %0, %1\n\t" - : "=a"(tag) : "a"(index)); - else - __asm__ __volatile__("lict %0, %1\n\t" - : "=a"(tag) : "a"(index)); - - printk(" %#010lx", tag); - } + unsigned tlbidx = w | (e << PAGE_SHIFT); + unsigned r0 = dtlb ? + read_dtlb_virtual(tlbidx) : read_itlb_virtual(tlbidx); + unsigned vpn = (r0 & PAGE_MASK) | (e << PAGE_SHIFT); + unsigned pte = get_pte_for_vaddr(vpn); + unsigned mm_asid = (get_rasid_register() >> 8) & ASID_MASK; + unsigned tlb_asid = r0 & ASID_MASK; + bool kernel = tlb_asid == 1; + int rc = 0; + + if (tlb_asid > 0 && ((vpn < TASK_SIZE) == kernel)) { + pr_err("%cTLB: way: %u, entry: %u, VPN %08x in %s PTE\n", + dtlb ? 'D' : 'I', w, e, vpn, + kernel ? "kernel" : "user"); + rc |= TLB_INSANE; } - printk ("\n"); -} - -void dump_icache(int index) -{ - unsigned long data, addr; - int w, i; - - const unsigned long num_ways = XCHAL_ICACHE_WAYS; - const unsigned long cache_size = XCHAL_ICACHE_SIZE; - const unsigned long line_size = XCHAL_ICACHE_LINESIZE; - const unsigned long num_lines = cache_size / num_ways / line_size; - for (w = 0; w < num_ways; w++) { - printk ("\nWay %d", w); - - for (i = 0; i < line_size; i+= 4) { - addr = w * num_lines + index * line_size + i; - __asm__ __volatile__("licw %0, %1\n\t" - : "=a"(data) : "a"(addr)); - printk(" %#010lx", data); + if (tlb_asid == mm_asid) { + unsigned r1 = dtlb ? read_dtlb_translation(tlbidx) : + read_itlb_translation(tlbidx); + if ((pte ^ r1) & PAGE_MASK) { + pr_err("%cTLB: way: %u, entry: %u, mapping: %08x->%08x, PTE: %08x\n", + dtlb ? 'D' : 'I', w, e, r0, r1, pte); + if (pte == 0 || !pte_present(__pte(pte))) { + struct page *p = pfn_to_page(r1 >> PAGE_SHIFT); + pr_err("page refcount: %d, mapcount: %d\n", + page_count(p), + page_mapcount(p)); + if (!page_count(p)) + rc |= TLB_INSANE; + else if (page_mapped(p)) + rc |= TLB_SUSPICIOUS; + } else { + rc |= TLB_INSANE; + } } } - printk ("\n"); + return rc; } -void dump_cache_tags(void) +void check_tlb_sanity(void) { - printk("Instruction cache\n"); - dump_cache_tag(0, -1); - printk("Data cache\n"); - dump_cache_tag(1, -1); + unsigned long flags; + unsigned w, e; + int bug = 0; + + local_irq_save(flags); + for (w = 0; w < DTLB_ARF_WAYS; ++w) + for (e = 0; e < (1 << XCHAL_DTLB_ARF_ENTRIES_LOG2); ++e) + bug |= check_tlb_entry(w, e, true); + for (w = 0; w < ITLB_ARF_WAYS; ++w) + for (e = 0; e < (1 << XCHAL_ITLB_ARF_ENTRIES_LOG2); ++e) + bug |= check_tlb_entry(w, e, false); + if (bug & TLB_INSANE) + tlb_insane(); + if (bug & TLB_SUSPICIOUS) + tlb_suspicious(); + local_irq_restore(flags); } -#endif +#endif /* CONFIG_DEBUG_TLB_SANITY */ |
