208 lines
5.1 KiB
C
208 lines
5.1 KiB
C
/*
|
|
* 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);
|
|
}
|