alistair23-linux/arch/metag/mm/mmu-meta2.c
James Hogan f5df8e268f metag: Memory management
Add memory management files for metag.

Meta's 32bit virtual address space is split into two halves:
 - local (0x08000000-0x7fffffff): traditionally local to a hardware
   thread and incoherent between hardware threads. Each hardware thread
   has it's own local MMU table. On Meta2 the local space can be
   globally coherent (GCOn) if the cache partitions coincide.
 - global (0x88000000-0xffff0000): coherent and traditionally global
   between hardware threads. On Meta2, each hardware thread has it's own
   global MMU table.

The low 128MiB of each half is non-MMUable and maps directly to the
physical address space:
 - 0x00010000-0x07ffffff: contains Meta core registers and maps SoC bus
 - 0x80000000-0x87ffffff: contains low latency global core memories

Linux usually further splits the local virtual address space like this:
 - 0x08000000-0x3fffffff: user mappings
 - 0x40000000-0x7fffffff: kernel mappings

Signed-off-by: James Hogan <james.hogan@imgtec.com>
2013-03-02 20:09:19 +00:00

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