/* * 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); }