/* * arch/xtensa/mm/mmu.c * * Logic that manipulates the Xtensa MMU. Derived from MIPS. * * This file is subject to the terms and conditions of the GNU General Public * License. See the file "COPYING" in the main directory of this archive * for more details. * * Copyright (C) 2001 - 2003 Tensilica Inc. * * Joe Taylor * Chris Zankel * Marc Gauthier */ #include #include #include #include #include #include static inline void __flush_itlb_all (void) { int way, index; 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); } } asm volatile ("isync\n"); } static inline void __flush_dtlb_all (void) { int way, index; 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); } } asm volatile ("isync\n"); } void flush_tlb_all (void) { __flush_itlb_all(); __flush_dtlb_all(); } /* If mm is current, we simply assign the current task a new ASID, thus, * invalidating all previous tlb entries. If mm is someone else's user mapping, * wie invalidate the context, thus, when that user mapping is swapped in, * a new context will be assigned to it. */ void flush_tlb_mm(struct mm_struct *mm) { #if 0 printk("[tlbmm<%lx>]\n", (unsigned long)mm->context); #endif 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)); local_irq_restore(flags); } else mm->context = 0; } void flush_tlb_range (struct vm_area_struct *vma, unsigned long start, unsigned long end) { struct mm_struct *mm = vma->vm_mm; unsigned long flags; if (mm->context == NO_CONTEXT) return; #if 0 printk("[tlbrange<%02lx,%08lx,%08lx>]\n", (unsigned long)mm->context, start, end); #endif local_save_flags(flags); if (end-start + (PAGE_SIZE-1) <= SMALLEST_NTLB_ENTRIES << PAGE_SHIFT) { int oldpid = get_rasid_register(); set_rasid_register (ASID_INSERT(mm->context)); start &= PAGE_MASK; if (vma->vm_flags & VM_EXEC) while(start < end) { invalidate_itlb_mapping(start); invalidate_dtlb_mapping(start); start += PAGE_SIZE; } else while(start < end) { invalidate_dtlb_mapping(start); start += PAGE_SIZE; } 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_irq_restore(flags); } void flush_tlb_page (struct vm_area_struct *vma, unsigned long page) { 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) return; local_save_flags(flags); oldpid = get_rasid_register(); if (vma->vm_flags & VM_EXEC) invalidate_itlb_mapping(page); invalidate_dtlb_mapping(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) { 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); } } } 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] ); } } void dump_tlbs2 (int showinv) { 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); } void dump_all_tlbs (void) { dump_tlbs2 (1); } void dump_valid_tlbs (void) { dump_tlbs2 (0); } void dump_tlbs (void) { dump_itlb(); dump_dtlb(); } void dump_cache_tag(int dcache, int idx) { 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); } } 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); } } printk ("\n"); } void dump_cache_tags(void) { printk("Instruction cache\n"); dump_cache_tag(0, -1); printk("Data cache\n"); dump_cache_tag(1, -1); } #endif