Commit f5df8e26 authored by James Hogan's avatar James Hogan

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: default avatarJames Hogan <james.hogan@imgtec.com>
parent 99ef7c2a
#ifndef __MMU_H
#define __MMU_H
#ifdef CONFIG_METAG_USER_TCM
#include <linux/list.h>
#endif
#ifdef CONFIG_HUGETLB_PAGE
#include <asm/page.h>
#endif
typedef struct {
/* Software pgd base pointer used for Meta 1.x MMU. */
unsigned long pgd_base;
#ifdef CONFIG_METAG_USER_TCM
struct list_head tcm;
#endif
#ifdef CONFIG_HUGETLB_PAGE
#if HPAGE_SHIFT < HUGEPT_SHIFT
/* last partially filled huge page table address */
unsigned long part_huge;
#endif
#endif
} mm_context_t;
/* Given a virtual address, return the pte for the top level 4meg entry
* that maps that address.
* Returns 0 (an empty pte) if that range is not mapped.
*/
unsigned long mmu_read_first_level_page(unsigned long vaddr);
/* Given a linear (virtual) address, return the second level 4k pte
* that maps that address. Returns 0 if the address is not mapped.
*/
unsigned long mmu_read_second_level_page(unsigned long vaddr);
/* Get the virtual base address of the MMU */
unsigned long mmu_get_base(void);
/* Initialize the MMU. */
void mmu_init(unsigned long mem_end);
#ifdef CONFIG_METAG_META21_MMU
/*
* For cpu "cpu" calculate and return the address of the
* MMCU_TnLOCAL_TABLE_PHYS0 if running in local-space or
* MMCU_TnGLOBAL_TABLE_PHYS0 if running in global-space.
*/
static inline unsigned long mmu_phys0_addr(unsigned int cpu)
{
unsigned long phys0;
phys0 = (MMCU_T0LOCAL_TABLE_PHYS0 +
(MMCU_TnX_TABLE_PHYSX_STRIDE * cpu)) +
(MMCU_TXG_TABLE_PHYSX_OFFSET * is_global_space(PAGE_OFFSET));
return phys0;
}
/*
* For cpu "cpu" calculate and return the address of the
* MMCU_TnLOCAL_TABLE_PHYS1 if running in local-space or
* MMCU_TnGLOBAL_TABLE_PHYS1 if running in global-space.
*/
static inline unsigned long mmu_phys1_addr(unsigned int cpu)
{
unsigned long phys1;
phys1 = (MMCU_T0LOCAL_TABLE_PHYS1 +
(MMCU_TnX_TABLE_PHYSX_STRIDE * cpu)) +
(MMCU_TXG_TABLE_PHYSX_OFFSET * is_global_space(PAGE_OFFSET));
return phys1;
}
#endif /* CONFIG_METAG_META21_MMU */
#endif
#ifndef __METAG_MMU_CONTEXT_H
#define __METAG_MMU_CONTEXT_H
#include <asm-generic/mm_hooks.h>
#include <asm/page.h>
#include <asm/mmu.h>
#include <asm/tlbflush.h>
#include <asm/cacheflush.h>
#include <linux/io.h>
static inline void enter_lazy_tlb(struct mm_struct *mm,
struct task_struct *tsk)
{
}
static inline int init_new_context(struct task_struct *tsk,
struct mm_struct *mm)
{
#ifndef CONFIG_METAG_META21_MMU
/* We use context to store a pointer to the page holding the
* pgd of a process while it is running. While a process is not
* running the pgd and context fields should be equal.
*/
mm->context.pgd_base = (unsigned long) mm->pgd;
#endif
#ifdef CONFIG_METAG_USER_TCM
INIT_LIST_HEAD(&mm->context.tcm);
#endif
return 0;
}
#ifdef CONFIG_METAG_USER_TCM
#include <linux/slab.h>
#include <asm/tcm.h>
static inline void destroy_context(struct mm_struct *mm)
{
struct tcm_allocation *pos, *n;
list_for_each_entry_safe(pos, n, &mm->context.tcm, list) {
tcm_free(pos->tag, pos->addr, pos->size);
list_del(&pos->list);
kfree(pos);
}
}
#else
#define destroy_context(mm) do { } while (0)
#endif
#ifdef CONFIG_METAG_META21_MMU
static inline void load_pgd(pgd_t *pgd, int thread)
{
unsigned long phys0 = mmu_phys0_addr(thread);
unsigned long phys1 = mmu_phys1_addr(thread);
/*
* 0x900 2Gb address space
* The permission bits apply to MMU table region which gives a 2MB
* window into physical memory. We especially don't want userland to be
* able to access this.
*/
metag_out32(0x900 | _PAGE_CACHEABLE | _PAGE_PRIV | _PAGE_WRITE |
_PAGE_PRESENT, phys0);
/* Set new MMU base address */
metag_out32(__pa(pgd) & MMCU_TBLPHYS1_ADDR_BITS, phys1);
}
#endif
static inline void switch_mmu(struct mm_struct *prev, struct mm_struct *next)
{
#ifdef CONFIG_METAG_META21_MMU
load_pgd(next->pgd, hard_processor_id());
#else
unsigned int i;
/* prev->context == prev->pgd in the case where we are initially
switching from the init task to the first process. */
if (prev->context.pgd_base != (unsigned long) prev->pgd) {
for (i = FIRST_USER_PGD_NR; i < USER_PTRS_PER_PGD; i++)
((pgd_t *) prev->context.pgd_base)[i] = prev->pgd[i];
} else
prev->pgd = (pgd_t *)mmu_get_base();
next->pgd = prev->pgd;
prev->pgd = (pgd_t *) prev->context.pgd_base;
for (i = FIRST_USER_PGD_NR; i < USER_PTRS_PER_PGD; i++)
next->pgd[i] = ((pgd_t *) next->context.pgd_base)[i];
flush_cache_all();
#endif
flush_tlb_all();
}
static inline void switch_mm(struct mm_struct *prev, struct mm_struct *next,
struct task_struct *tsk)
{
if (prev != next)
switch_mmu(prev, next);
}
static inline void activate_mm(struct mm_struct *prev_mm,
struct mm_struct *next_mm)
{
switch_mmu(prev_mm, next_mm);
}
#define deactivate_mm(tsk, mm) do { } while (0)
#endif
#ifndef _METAG_PAGE_H
#define _METAG_PAGE_H
#include <linux/const.h>
#include <asm/metag_mem.h>
/* PAGE_SHIFT determines the page size */
#if defined(CONFIG_PAGE_SIZE_4K)
#define PAGE_SHIFT 12
#elif defined(CONFIG_PAGE_SIZE_8K)
#define PAGE_SHIFT 13
#elif defined(CONFIG_PAGE_SIZE_16K)
#define PAGE_SHIFT 14
#endif
#define PAGE_SIZE (_AC(1, UL) << PAGE_SHIFT)
#define PAGE_MASK (~(PAGE_SIZE-1))
#if defined(CONFIG_HUGETLB_PAGE_SIZE_8K)
# define HPAGE_SHIFT 13
#elif defined(CONFIG_HUGETLB_PAGE_SIZE_16K)
# define HPAGE_SHIFT 14
#elif defined(CONFIG_HUGETLB_PAGE_SIZE_32K)
# define HPAGE_SHIFT 15
#elif defined(CONFIG_HUGETLB_PAGE_SIZE_64K)
# define HPAGE_SHIFT 16
#elif defined(CONFIG_HUGETLB_PAGE_SIZE_128K)
# define HPAGE_SHIFT 17
#elif defined(CONFIG_HUGETLB_PAGE_SIZE_256K)
# define HPAGE_SHIFT 18
#elif defined(CONFIG_HUGETLB_PAGE_SIZE_512K)
# define HPAGE_SHIFT 19
#elif defined(CONFIG_HUGETLB_PAGE_SIZE_1M)
# define HPAGE_SHIFT 20
#elif defined(CONFIG_HUGETLB_PAGE_SIZE_2M)
# define HPAGE_SHIFT 21
#elif defined(CONFIG_HUGETLB_PAGE_SIZE_4M)
# define HPAGE_SHIFT 22
#endif
#ifdef CONFIG_HUGETLB_PAGE
# define HPAGE_SIZE (1UL << HPAGE_SHIFT)
# define HPAGE_MASK (~(HPAGE_SIZE-1))
# define HUGETLB_PAGE_ORDER (HPAGE_SHIFT-PAGE_SHIFT)
/*
* We define our own hugetlb_get_unmapped_area so we don't corrupt 2nd level
* page tables with normal pages in them.
*/
# define HUGEPT_SHIFT (22)
# define HUGEPT_ALIGN (1 << HUGEPT_SHIFT)
# define HUGEPT_MASK (HUGEPT_ALIGN - 1)
# define ALIGN_HUGEPT(x) ALIGN(x, HUGEPT_ALIGN)
# define HAVE_ARCH_HUGETLB_UNMAPPED_AREA
#endif
#ifndef __ASSEMBLY__
/* On the Meta, we would like to know if the address (heap) we have is
* in local or global space.
*/
#define is_global_space(addr) ((addr) > 0x7fffffff)
#define is_local_space(addr) (!is_global_space(addr))
extern void clear_page(void *to);
extern void copy_page(void *to, void *from);
#define clear_user_page(page, vaddr, pg) clear_page(page)
#define copy_user_page(to, from, vaddr, pg) copy_page(to, from)
/*
* These are used to make use of C type-checking..
*/
typedef struct { unsigned long pte; } pte_t;
typedef struct { unsigned long pgd; } pgd_t;
typedef struct { unsigned long pgprot; } pgprot_t;
typedef struct page *pgtable_t;
#define pte_val(x) ((x).pte)
#define pgd_val(x) ((x).pgd)
#define pgprot_val(x) ((x).pgprot)
#define __pte(x) ((pte_t) { (x) })
#define __pgd(x) ((pgd_t) { (x) })
#define __pgprot(x) ((pgprot_t) { (x) })
/* The kernel must now ALWAYS live at either 0xC0000000 or 0x40000000 - that
* being either global or local space.
*/
#define PAGE_OFFSET (CONFIG_PAGE_OFFSET)
#if PAGE_OFFSET >= LINGLOBAL_BASE
#define META_MEMORY_BASE LINGLOBAL_BASE
#define META_MEMORY_LIMIT LINGLOBAL_LIMIT
#else
#define META_MEMORY_BASE LINLOCAL_BASE
#define META_MEMORY_LIMIT LINLOCAL_LIMIT
#endif
/* Offset between physical and virtual mapping of kernel memory. */
extern unsigned int meta_memoffset;
#define __pa(x) ((unsigned long)(((unsigned long)(x)) - meta_memoffset))
#define __va(x) ((void *)((unsigned long)(((unsigned long)(x)) + meta_memoffset)))
extern unsigned long pfn_base;
#define ARCH_PFN_OFFSET (pfn_base)
#define virt_to_page(kaddr) pfn_to_page(__pa(kaddr) >> PAGE_SHIFT)
#define page_to_virt(page) __va(page_to_pfn(page) << PAGE_SHIFT)
#define virt_addr_valid(kaddr) pfn_valid(__pa(kaddr) >> PAGE_SHIFT)
#define page_to_phys(page) (page_to_pfn(page) << PAGE_SHIFT)
#ifdef CONFIG_FLATMEM
extern unsigned long max_pfn;
extern unsigned long min_low_pfn;
#define pfn_valid(pfn) ((pfn) >= min_low_pfn && (pfn) < max_pfn)
#endif
#define pfn_to_kaddr(pfn) __va((pfn) << PAGE_SHIFT)
#define VM_DATA_DEFAULT_FLAGS (VM_READ | VM_WRITE | VM_EXEC | \
VM_MAYREAD | VM_MAYWRITE | VM_MAYEXEC)
#include <asm-generic/memory_model.h>
#include <asm-generic/getorder.h>
#endif /* __ASSMEBLY__ */
#endif /* _METAG_PAGE_H */
#ifndef _METAG_PGALLOC_H
#define _METAG_PGALLOC_H
#include <linux/threads.h>
#include <linux/mm.h>
#define pmd_populate_kernel(mm, pmd, pte) \
set_pmd(pmd, __pmd(_PAGE_TABLE | __pa(pte)))
#define pmd_populate(mm, pmd, pte) \
set_pmd(pmd, __pmd(_PAGE_TABLE | page_to_phys(pte)))
#define pmd_pgtable(pmd) pmd_page(pmd)
/*
* Allocate and free page tables.
*/
#ifdef CONFIG_METAG_META21_MMU
static inline void pgd_ctor(pgd_t *pgd)
{
memcpy(pgd + USER_PTRS_PER_PGD,
swapper_pg_dir + USER_PTRS_PER_PGD,
(PTRS_PER_PGD - USER_PTRS_PER_PGD) * sizeof(pgd_t));
}
#else
#define pgd_ctor(x) do { } while (0)
#endif
static inline pgd_t *pgd_alloc(struct mm_struct *mm)
{
pgd_t *pgd = (pgd_t *)get_zeroed_page(GFP_KERNEL);
if (pgd)
pgd_ctor(pgd);
return pgd;
}
static inline void pgd_free(struct mm_struct *mm, pgd_t *pgd)
{
free_page((unsigned long)pgd);
}
static inline pte_t *pte_alloc_one_kernel(struct mm_struct *mm,
unsigned long address)
{
pte_t *pte = (pte_t *)__get_free_page(GFP_KERNEL | __GFP_REPEAT |
__GFP_ZERO);
return pte;
}
static inline pgtable_t pte_alloc_one(struct mm_struct *mm,
unsigned long address)
{
struct page *pte;
pte = alloc_pages(GFP_KERNEL | __GFP_REPEAT | __GFP_ZERO, 0);
if (pte)
pgtable_page_ctor(pte);
return pte;
}
static inline void pte_free_kernel(struct mm_struct *mm, pte_t *pte)
{
free_page((unsigned long)pte);
}
static inline void pte_free(struct mm_struct *mm, pgtable_t pte)
{
pgtable_page_dtor(pte);
__free_page(pte);
}
#define __pte_free_tlb(tlb, pte, addr) \
do { \
pgtable_page_dtor(pte); \
tlb_remove_page((tlb), (pte)); \
} while (0)
#define check_pgt_cache() do { } while (0)
#endif
This diff is collapsed.
#include <linux/module.h>
#include <linux/uaccess.h>
int fixup_exception(struct pt_regs *regs)
{
const struct exception_table_entry *fixup;
unsigned long pc = instruction_pointer(regs);
fixup = search_exception_tables(pc);
if (fixup)
regs->ctx.CurrPC = fixup->fixup;
return fixup != NULL;
}
/*
* Meta page fault handling.
*
* Copyright (C) 2005-2012 Imagination Technologies Ltd.
*/
#include <linux/mman.h>
#include <linux/mm.h>
#include <linux/kernel.h>
#include <linux/ptrace.h>
#include <linux/interrupt.h>
#include <linux/uaccess.h>
#include <asm/tlbflush.h>
#include <asm/mmu.h>
#include <asm/traps.h>
/* Clear any pending catch buffer state. */
static void clear_cbuf_entry(struct pt_regs *regs, unsigned long addr,
unsigned int trapno)
{
PTBICTXEXTCB0 cbuf = regs->extcb0;
switch (trapno) {
/* Instruction fetch faults leave no catch buffer state. */
case TBIXXF_SIGNUM_IGF:
case TBIXXF_SIGNUM_IPF:
return;
default:
if (cbuf[0].CBAddr == addr) {
cbuf[0].CBAddr = 0;
cbuf[0].CBFlags &= ~TXCATCH0_FAULT_BITS;
/* And, as this is the ONLY catch entry, we
* need to clear the cbuf bit from the context!
*/
regs->ctx.SaveMask &= ~(TBICTX_CBUF_BIT |
TBICTX_XCBF_BIT);
return;
}
pr_err("Failed to clear cbuf entry!\n");
}
}
int show_unhandled_signals = 1;
int do_page_fault(struct pt_regs *regs, unsigned long address,
unsigned int write_access, unsigned int trapno)
{
struct task_struct *tsk;
struct mm_struct *mm;
struct vm_area_struct *vma, *prev_vma;
siginfo_t info;
int fault;
unsigned int flags = FAULT_FLAG_ALLOW_RETRY | FAULT_FLAG_KILLABLE |
(write_access ? FAULT_FLAG_WRITE : 0);
tsk = current;
if ((address >= VMALLOC_START) && (address < VMALLOC_END)) {
/*
* Synchronize this task's top level page-table
* with the 'reference' page table.
*
* Do _not_ use "tsk" here. We might be inside
* an interrupt in the middle of a task switch..
*/
int offset = pgd_index(address);
pgd_t *pgd, *pgd_k;
pud_t *pud, *pud_k;
pmd_t *pmd, *pmd_k;
pte_t *pte_k;
pgd = ((pgd_t *)mmu_get_base()) + offset;
pgd_k = swapper_pg_dir + offset;
/* This will never happen with the folded page table. */
if (!pgd_present(*pgd)) {
if (!pgd_present(*pgd_k))
goto bad_area_nosemaphore;
set_pgd(pgd, *pgd_k);
return 0;
}
pud = pud_offset(pgd, address);
pud_k = pud_offset(pgd_k, address);
if (!pud_present(*pud_k))
goto bad_area_nosemaphore;
set_pud(pud, *pud_k);
pmd = pmd_offset(pud, address);
pmd_k = pmd_offset(pud_k, address);
if (!pmd_present(*pmd_k))
goto bad_area_nosemaphore;
set_pmd(pmd, *pmd_k);
pte_k = pte_offset_kernel(pmd_k, address);
if (!pte_present(*pte_k))
goto bad_area_nosemaphore;
/* May only be needed on Chorus2 */
flush_tlb_all();
return 0;
}
mm = tsk->mm;
if (in_atomic() || !mm)
goto no_context;
retry:
down_read(&mm->mmap_sem);
vma = find_vma_prev(mm, address, &prev_vma);
if (!vma || address < vma->vm_start)
goto check_expansion;
good_area:
if (write_access) {
if (!(vma->vm_flags & VM_WRITE))
goto bad_area;
} else {
if (!(vma->vm_flags & (VM_READ | VM_EXEC | VM_WRITE)))
goto bad_area;
}
/*
* If for any reason at all we couldn't handle the fault,
* make sure we exit gracefully rather than endlessly redo
* the fault.
*/
fault = handle_mm_fault(mm, vma, address, flags);
if ((fault & VM_FAULT_RETRY) && fatal_signal_pending(current))
return 0;
if (unlikely(fault & VM_FAULT_ERROR)) {
if (fault & VM_FAULT_OOM)
goto out_of_memory;
else if (fault & VM_FAULT_SIGBUS)
goto do_sigbus;
BUG();
}
if (flags & FAULT_FLAG_ALLOW_RETRY) {
if (fault & VM_FAULT_MAJOR)
tsk->maj_flt++;
else
tsk->min_flt++;
if (fault & VM_FAULT_RETRY) {
flags &= ~FAULT_FLAG_ALLOW_RETRY;
flags |= FAULT_FLAG_TRIED;
/*
* No need to up_read(&mm->mmap_sem) as we would
* have already released it in __lock_page_or_retry
* in mm/filemap.c.
*/
goto retry;
}
}
up_read(&mm->mmap_sem);
return 0;
check_expansion:
vma = prev_vma;
if (vma && (expand_stack(vma, address) == 0))
goto good_area;
bad_area:
up_read(&mm->mmap_sem);
bad_area_nosemaphore:
if (user_mode(regs)) {
info.si_signo = SIGSEGV;
info.si_errno = 0;
info.si_code = SEGV_MAPERR;
info.si_addr = (__force void __user *)address;
info.si_trapno = trapno;
if (show_unhandled_signals && unhandled_signal(tsk, SIGSEGV) &&
printk_ratelimit()) {
pr_info("%s%s[%d]: segfault at %lx pc %08x sp %08x write %d trap %#x (%s)",
task_pid_nr(tsk) > 1 ? KERN_INFO : KERN_EMERG,
tsk->comm, task_pid_nr(tsk), address,
regs->ctx.CurrPC, regs->ctx.AX[0].U0,
write_access, trapno, trap_name(trapno));
print_vma_addr(" in ", regs->ctx.CurrPC);
print_vma_addr(" rtp in ", regs->ctx.DX[4].U1);
printk("\n");
show_regs(regs);
}
force_sig_info(SIGSEGV, &info, tsk);
return 1;
}
goto no_context;
do_sigbus:
up_read(&mm->mmap_sem);
/*
* Send a sigbus, regardless of whether we were in kernel
* or user mode.
*/
info.si_signo = SIGBUS;
info.si_errno = 0;
info.si_code = BUS_ADRERR;
info.si_addr = (__force void __user *)address;
info.si_trapno = trapno;
force_sig_info(SIGBUS, &info, tsk);
/* Kernel mode? Handle exceptions or die */
if (!user_mode(regs))
goto no_context;
return 1;
/*
* We ran out of memory, or some other thing happened to us that made
* us unable to handle the page fault gracefully.
*/
out_of_memory:
up_read(&mm->mmap_sem);
if (user_mode(regs))
do_group_exit(SIGKILL);
no_context:
/* Are we prepared to handle this kernel fault? */
if (fixup_exception(regs)) {
clear_cbuf_entry(regs, address, trapno);
return 1;
}
die("Oops", regs, (write_access << 15) | trapno, address);
do_exit(SIGKILL);
}
This diff is collapsed.
/*
* Copyright (C) 2005,2006,2007,2008,2009 Imagination Technologies
*
* Meta 1 MMU handling code.
*
*/
#include <linux/sched.h>
#include <linux/mm.h>
#include <linux/io.h>
#include <asm/mmu.h>
#define DM3_BASE (LINSYSDIRECT_BASE + (MMCU_DIRECTMAPn_ADDR_SCALE * 3))
/*
* This contains the physical address of the top level 2k pgd table.
*/
static unsigned long mmu_base_phys;
/*
* Given a physical address, return a mapped virtual address that can be used
* to access that location.
* In practice, we use the DirectMap region to make this happen.
*/
static unsigned long map_addr(unsigned long phys)
{
static unsigned long dm_base = 0xFFFFFFFF;
int offset;
offset = phys - dm_base;
/* Are we in the current map range ? */
if ((offset < 0) || (offset >= MMCU_DIRECTMAPn_ADDR_SCALE)) {
/* Calculate new DM area */
dm_base = phys & ~(MMCU_DIRECTMAPn_ADDR_SCALE - 1);
/* Actually map it in! */
metag_out32(dm_base, MMCU_DIRECTMAP3_ADDR);
/* And calculate how far into that area our reference is */
offset = phys - dm_base;
}
return DM3_BASE + offset;
}
/*
* Return the physical address of the base of our pgd table.
*/
static inline unsigned long __get_mmu_base(void)
{
unsigned long base_phys;
unsigned int stride;
if (is_global_space(PAGE_OFFSET))
stride = 4;
else
stride = hard_processor_id(); /* [0..3] */
base_phys = metag_in32(MMCU_TABLE_PHYS_ADDR);
base_phys += (0x800 * stride);
return base_phys;
}
/* Given a virtual address, return the virtual address of the relevant pgd */
static unsigned long pgd_entry_addr(unsigned long virt)
{
unsigned long pgd_phys;
unsigned long pgd_virt;
if (!mmu_base_phys)
mmu_base_phys = __get_mmu_base();
/*
* Are we trying to map a global address. If so, then index
* the global pgd table instead of our local one.
*/
if (is_global_space(virt)) {
/* Scale into 2gig map */
virt &= ~0x80000000;
}
/* Base of the pgd table plus our 4Meg entry, 4bytes each */
pgd_phys = mmu_base_phys + ((virt >> PGDIR_SHIFT) * 4);
pgd_virt = map_addr(pgd_phys);
return pgd_virt;
}
/* Given a virtual address, return the virtual address of the relevant pte */
static unsigned long pgtable_entry_addr(unsigned long virt)
{
unsigned long pgtable_phys;
unsigned long pgtable_virt, pte_virt;
/* Find the physical address of the 4MB page table*/
pgtable_phys = metag_in32(pgd_entry_addr(virt)) & MMCU_ENTRY_ADDR_BITS;
/* Map it to a virtual address */
pgtable_virt = map_addr(pgtable_phys);
/* And index into it for our pte */
pte_virt = pgtable_virt + ((virt >> PAGE_SHIFT) & 0x3FF) * 4;
return pte_virt;
}
unsigned long mmu_read_first_level_page(unsigned long vaddr)
{
return metag_in32(pgd_entry_addr(vaddr));
}
unsigned long mmu_read_second_level_page(unsigned long vaddr)
{
return metag_in32(pgtable_entry_addr(vaddr));
}
unsigned long mmu_get_base(void)
{
static unsigned long __base;
/* Find the base of our MMU pgd table */
if (!__base)
__base = pgd_entry_addr(0);
return __base;
}
void __init mmu_init(unsigned long mem_end)
{
unsigned long entry, addr;
pgd_t *p_swapper_pg_dir;
/*
* 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 = PAGE_OFFSET;
entry = pgd_index(PAGE_OFFSET);
p_swapper_pg_dir = pgd_offset_k(0) + entry;
while (addr <= META_MEMORY_LIMIT) {
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++;
}
}
/*
* 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);
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment