blob: 81dcbb0bba342c4c63f4ef3c16c87b2b982b9c85 [file] [log] [blame]
/*
* 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);
}