diff options
Diffstat (limited to 'arch/metag/mm/mmu-meta2.c')
-rw-r--r-- | arch/metag/mm/mmu-meta2.c | 207 |
1 files changed, 207 insertions, 0 deletions
diff --git a/arch/metag/mm/mmu-meta2.c b/arch/metag/mm/mmu-meta2.c new file mode 100644 index 000000000000..81dcbb0bba34 --- /dev/null +++ b/arch/metag/mm/mmu-meta2.c @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2008,2009,2010,2011 Imagination Technologies Ltd. + * + * Meta 2 enhanced mode MMU handling code. + * + */ + +#include <linux/mm.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/io.h> +#include <linux/bootmem.h> +#include <linux/syscore_ops.h> + +#include <asm/mmu.h> +#include <asm/mmu_context.h> + +unsigned long mmu_read_first_level_page(unsigned long vaddr) +{ + unsigned int cpu = hard_processor_id(); + unsigned long offset, linear_base, linear_limit; + unsigned int phys0; + pgd_t *pgd, entry; + + if (is_global_space(vaddr)) + vaddr &= ~0x80000000; + + offset = vaddr >> PGDIR_SHIFT; + + phys0 = metag_in32(mmu_phys0_addr(cpu)); + + /* Top bit of linear base is always zero. */ + linear_base = (phys0 >> PGDIR_SHIFT) & 0x1ff; + + /* Limit in the range 0 (4MB) to 9 (2GB). */ + linear_limit = 1 << ((phys0 >> 8) & 0xf); + linear_limit += linear_base; + + /* + * If offset is below linear base or above the limit then no + * mapping exists. + */ + if (offset < linear_base || offset > linear_limit) + return 0; + + offset -= linear_base; + pgd = (pgd_t *)mmu_get_base(); + entry = pgd[offset]; + + return pgd_val(entry); +} + +unsigned long mmu_read_second_level_page(unsigned long vaddr) +{ + return __builtin_meta2_cacherd((void *)(vaddr & PAGE_MASK)); +} + +unsigned long mmu_get_base(void) +{ + unsigned int cpu = hard_processor_id(); + unsigned long stride; + + stride = cpu * LINSYSMEMTnX_STRIDE; + + /* + * Bits 18:2 of the MMCU_TnLocal_TABLE_PHYS1 register should be + * used as an offset to the start of the top-level pgd table. + */ + stride += (metag_in32(mmu_phys1_addr(cpu)) & 0x7fffc); + + if (is_global_space(PAGE_OFFSET)) + stride += LINSYSMEMTXG_OFFSET; + + return LINSYSMEMT0L_BASE + stride; +} + +#define FIRST_LEVEL_MASK 0xffffffc0 +#define SECOND_LEVEL_MASK 0xfffff000 +#define SECOND_LEVEL_ALIGN 64 + +static void repriv_mmu_tables(void) +{ + unsigned long phys0_addr; + unsigned int g; + + /* + * Check that all the mmu table regions are priv protected, and if not + * fix them and emit a warning. If we left them without priv protection + * then userland processes would have access to a 2M window into + * physical memory near where the page tables are. + */ + phys0_addr = MMCU_T0LOCAL_TABLE_PHYS0; + for (g = 0; g < 2; ++g) { + unsigned int t, phys0; + unsigned long flags; + for (t = 0; t < 4; ++t) { + __global_lock2(flags); + phys0 = metag_in32(phys0_addr); + if ((phys0 & _PAGE_PRESENT) && !(phys0 & _PAGE_PRIV)) { + pr_warn("Fixing priv protection on T%d %s MMU table region\n", + t, + g ? "global" : "local"); + phys0 |= _PAGE_PRIV; + metag_out32(phys0, phys0_addr); + } + __global_unlock2(flags); + + phys0_addr += MMCU_TnX_TABLE_PHYSX_STRIDE; + } + + phys0_addr += MMCU_TXG_TABLE_PHYSX_OFFSET + - 4*MMCU_TnX_TABLE_PHYSX_STRIDE; + } +} + +#ifdef CONFIG_METAG_SUSPEND_MEM +static void mmu_resume(void) +{ + /* + * If a full suspend to RAM has happened then the original bad MMU table + * priv may have been restored, so repriv them again. + */ + repriv_mmu_tables(); +} +#else +#define mmu_resume NULL +#endif /* CONFIG_METAG_SUSPEND_MEM */ + +static struct syscore_ops mmu_syscore_ops = { + .resume = mmu_resume, +}; + +void __init mmu_init(unsigned long mem_end) +{ + unsigned long entry, addr; + pgd_t *p_swapper_pg_dir; +#ifdef CONFIG_KERNEL_4M_PAGES + unsigned long mem_size = mem_end - PAGE_OFFSET; + unsigned int pages = DIV_ROUND_UP(mem_size, 1 << 22); + unsigned int second_level_entry = 0; + unsigned long *second_level_table; +#endif + + /* + * Now copy over any MMU pgd entries already in the mmu page tables + * over to our root init process (swapper_pg_dir) map. This map is + * then inherited by all other processes, which means all processes + * inherit a map of the kernel space. + */ + addr = META_MEMORY_BASE; + entry = pgd_index(META_MEMORY_BASE); + p_swapper_pg_dir = pgd_offset_k(0) + entry; + + while (entry < (PTRS_PER_PGD - pgd_index(META_MEMORY_BASE))) { + unsigned long pgd_entry; + /* copy over the current MMU value */ + pgd_entry = mmu_read_first_level_page(addr); + pgd_val(*p_swapper_pg_dir) = pgd_entry; + + p_swapper_pg_dir++; + addr += PGDIR_SIZE; + entry++; + } + +#ifdef CONFIG_KERNEL_4M_PAGES + /* + * At this point we can also map the kernel with 4MB pages to + * reduce TLB pressure. + */ + second_level_table = alloc_bootmem_pages(SECOND_LEVEL_ALIGN * pages); + + addr = PAGE_OFFSET; + entry = pgd_index(PAGE_OFFSET); + p_swapper_pg_dir = pgd_offset_k(0) + entry; + + while (pages > 0) { + unsigned long phys_addr, second_level_phys; + pte_t *pte = (pte_t *)&second_level_table[second_level_entry]; + + phys_addr = __pa(addr); + + second_level_phys = __pa(pte); + + pgd_val(*p_swapper_pg_dir) = ((second_level_phys & + FIRST_LEVEL_MASK) | + _PAGE_SZ_4M | + _PAGE_PRESENT); + + pte_val(*pte) = ((phys_addr & SECOND_LEVEL_MASK) | + _PAGE_PRESENT | _PAGE_DIRTY | + _PAGE_ACCESSED | _PAGE_WRITE | + _PAGE_CACHEABLE | _PAGE_KERNEL); + + p_swapper_pg_dir++; + addr += PGDIR_SIZE; + /* Second level pages must be 64byte aligned. */ + second_level_entry += (SECOND_LEVEL_ALIGN / + sizeof(unsigned long)); + pages--; + } + load_pgd(swapper_pg_dir, hard_processor_id()); + flush_tlb_all(); +#endif + + repriv_mmu_tables(); + register_syscore_ops(&mmu_syscore_ops); +} |