James Hogan | f5df8e2 | 2012-10-09 10:54:17 +0100 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2008,2009,2010,2011 Imagination Technologies Ltd. |
| 3 | * |
| 4 | * Meta 2 enhanced mode MMU handling code. |
| 5 | * |
| 6 | */ |
| 7 | |
| 8 | #include <linux/mm.h> |
| 9 | #include <linux/init.h> |
| 10 | #include <linux/kernel.h> |
| 11 | #include <linux/io.h> |
| 12 | #include <linux/bootmem.h> |
| 13 | #include <linux/syscore_ops.h> |
| 14 | |
| 15 | #include <asm/mmu.h> |
| 16 | #include <asm/mmu_context.h> |
| 17 | |
| 18 | unsigned long mmu_read_first_level_page(unsigned long vaddr) |
| 19 | { |
| 20 | unsigned int cpu = hard_processor_id(); |
| 21 | unsigned long offset, linear_base, linear_limit; |
| 22 | unsigned int phys0; |
| 23 | pgd_t *pgd, entry; |
| 24 | |
| 25 | if (is_global_space(vaddr)) |
| 26 | vaddr &= ~0x80000000; |
| 27 | |
| 28 | offset = vaddr >> PGDIR_SHIFT; |
| 29 | |
| 30 | phys0 = metag_in32(mmu_phys0_addr(cpu)); |
| 31 | |
| 32 | /* Top bit of linear base is always zero. */ |
| 33 | linear_base = (phys0 >> PGDIR_SHIFT) & 0x1ff; |
| 34 | |
| 35 | /* Limit in the range 0 (4MB) to 9 (2GB). */ |
| 36 | linear_limit = 1 << ((phys0 >> 8) & 0xf); |
| 37 | linear_limit += linear_base; |
| 38 | |
| 39 | /* |
| 40 | * If offset is below linear base or above the limit then no |
| 41 | * mapping exists. |
| 42 | */ |
| 43 | if (offset < linear_base || offset > linear_limit) |
| 44 | return 0; |
| 45 | |
| 46 | offset -= linear_base; |
| 47 | pgd = (pgd_t *)mmu_get_base(); |
| 48 | entry = pgd[offset]; |
| 49 | |
| 50 | return pgd_val(entry); |
| 51 | } |
| 52 | |
| 53 | unsigned long mmu_read_second_level_page(unsigned long vaddr) |
| 54 | { |
| 55 | return __builtin_meta2_cacherd((void *)(vaddr & PAGE_MASK)); |
| 56 | } |
| 57 | |
| 58 | unsigned long mmu_get_base(void) |
| 59 | { |
| 60 | unsigned int cpu = hard_processor_id(); |
| 61 | unsigned long stride; |
| 62 | |
| 63 | stride = cpu * LINSYSMEMTnX_STRIDE; |
| 64 | |
| 65 | /* |
| 66 | * Bits 18:2 of the MMCU_TnLocal_TABLE_PHYS1 register should be |
| 67 | * used as an offset to the start of the top-level pgd table. |
| 68 | */ |
| 69 | stride += (metag_in32(mmu_phys1_addr(cpu)) & 0x7fffc); |
| 70 | |
| 71 | if (is_global_space(PAGE_OFFSET)) |
| 72 | stride += LINSYSMEMTXG_OFFSET; |
| 73 | |
| 74 | return LINSYSMEMT0L_BASE + stride; |
| 75 | } |
| 76 | |
| 77 | #define FIRST_LEVEL_MASK 0xffffffc0 |
| 78 | #define SECOND_LEVEL_MASK 0xfffff000 |
| 79 | #define SECOND_LEVEL_ALIGN 64 |
| 80 | |
| 81 | static void repriv_mmu_tables(void) |
| 82 | { |
| 83 | unsigned long phys0_addr; |
| 84 | unsigned int g; |
| 85 | |
| 86 | /* |
| 87 | * Check that all the mmu table regions are priv protected, and if not |
| 88 | * fix them and emit a warning. If we left them without priv protection |
| 89 | * then userland processes would have access to a 2M window into |
| 90 | * physical memory near where the page tables are. |
| 91 | */ |
| 92 | phys0_addr = MMCU_T0LOCAL_TABLE_PHYS0; |
| 93 | for (g = 0; g < 2; ++g) { |
| 94 | unsigned int t, phys0; |
| 95 | unsigned long flags; |
| 96 | for (t = 0; t < 4; ++t) { |
| 97 | __global_lock2(flags); |
| 98 | phys0 = metag_in32(phys0_addr); |
| 99 | if ((phys0 & _PAGE_PRESENT) && !(phys0 & _PAGE_PRIV)) { |
| 100 | pr_warn("Fixing priv protection on T%d %s MMU table region\n", |
| 101 | t, |
| 102 | g ? "global" : "local"); |
| 103 | phys0 |= _PAGE_PRIV; |
| 104 | metag_out32(phys0, phys0_addr); |
| 105 | } |
| 106 | __global_unlock2(flags); |
| 107 | |
| 108 | phys0_addr += MMCU_TnX_TABLE_PHYSX_STRIDE; |
| 109 | } |
| 110 | |
| 111 | phys0_addr += MMCU_TXG_TABLE_PHYSX_OFFSET |
| 112 | - 4*MMCU_TnX_TABLE_PHYSX_STRIDE; |
| 113 | } |
| 114 | } |
| 115 | |
| 116 | #ifdef CONFIG_METAG_SUSPEND_MEM |
| 117 | static void mmu_resume(void) |
| 118 | { |
| 119 | /* |
| 120 | * If a full suspend to RAM has happened then the original bad MMU table |
| 121 | * priv may have been restored, so repriv them again. |
| 122 | */ |
| 123 | repriv_mmu_tables(); |
| 124 | } |
| 125 | #else |
| 126 | #define mmu_resume NULL |
| 127 | #endif /* CONFIG_METAG_SUSPEND_MEM */ |
| 128 | |
| 129 | static struct syscore_ops mmu_syscore_ops = { |
| 130 | .resume = mmu_resume, |
| 131 | }; |
| 132 | |
| 133 | void __init mmu_init(unsigned long mem_end) |
| 134 | { |
| 135 | unsigned long entry, addr; |
| 136 | pgd_t *p_swapper_pg_dir; |
| 137 | #ifdef CONFIG_KERNEL_4M_PAGES |
| 138 | unsigned long mem_size = mem_end - PAGE_OFFSET; |
| 139 | unsigned int pages = DIV_ROUND_UP(mem_size, 1 << 22); |
| 140 | unsigned int second_level_entry = 0; |
| 141 | unsigned long *second_level_table; |
| 142 | #endif |
| 143 | |
| 144 | /* |
| 145 | * Now copy over any MMU pgd entries already in the mmu page tables |
| 146 | * over to our root init process (swapper_pg_dir) map. This map is |
| 147 | * then inherited by all other processes, which means all processes |
| 148 | * inherit a map of the kernel space. |
| 149 | */ |
| 150 | addr = META_MEMORY_BASE; |
| 151 | entry = pgd_index(META_MEMORY_BASE); |
| 152 | p_swapper_pg_dir = pgd_offset_k(0) + entry; |
| 153 | |
| 154 | while (entry < (PTRS_PER_PGD - pgd_index(META_MEMORY_BASE))) { |
| 155 | unsigned long pgd_entry; |
| 156 | /* copy over the current MMU value */ |
| 157 | pgd_entry = mmu_read_first_level_page(addr); |
| 158 | pgd_val(*p_swapper_pg_dir) = pgd_entry; |
| 159 | |
| 160 | p_swapper_pg_dir++; |
| 161 | addr += PGDIR_SIZE; |
| 162 | entry++; |
| 163 | } |
| 164 | |
| 165 | #ifdef CONFIG_KERNEL_4M_PAGES |
| 166 | /* |
| 167 | * At this point we can also map the kernel with 4MB pages to |
| 168 | * reduce TLB pressure. |
| 169 | */ |
| 170 | second_level_table = alloc_bootmem_pages(SECOND_LEVEL_ALIGN * pages); |
| 171 | |
| 172 | addr = PAGE_OFFSET; |
| 173 | entry = pgd_index(PAGE_OFFSET); |
| 174 | p_swapper_pg_dir = pgd_offset_k(0) + entry; |
| 175 | |
| 176 | while (pages > 0) { |
| 177 | unsigned long phys_addr, second_level_phys; |
| 178 | pte_t *pte = (pte_t *)&second_level_table[second_level_entry]; |
| 179 | |
| 180 | phys_addr = __pa(addr); |
| 181 | |
| 182 | second_level_phys = __pa(pte); |
| 183 | |
| 184 | pgd_val(*p_swapper_pg_dir) = ((second_level_phys & |
| 185 | FIRST_LEVEL_MASK) | |
| 186 | _PAGE_SZ_4M | |
| 187 | _PAGE_PRESENT); |
| 188 | |
| 189 | pte_val(*pte) = ((phys_addr & SECOND_LEVEL_MASK) | |
| 190 | _PAGE_PRESENT | _PAGE_DIRTY | |
| 191 | _PAGE_ACCESSED | _PAGE_WRITE | |
| 192 | _PAGE_CACHEABLE | _PAGE_KERNEL); |
| 193 | |
| 194 | p_swapper_pg_dir++; |
| 195 | addr += PGDIR_SIZE; |
| 196 | /* Second level pages must be 64byte aligned. */ |
| 197 | second_level_entry += (SECOND_LEVEL_ALIGN / |
| 198 | sizeof(unsigned long)); |
| 199 | pages--; |
| 200 | } |
| 201 | load_pgd(swapper_pg_dir, hard_processor_id()); |
| 202 | flush_tlb_all(); |
| 203 | #endif |
| 204 | |
| 205 | repriv_mmu_tables(); |
| 206 | register_syscore_ops(&mmu_syscore_ops); |
| 207 | } |