diff options
author | GuanXuetao <gxt@mprc.pku.edu.cn> | 2011-01-15 18:17:56 +0800 |
---|---|---|
committer | GuanXuetao <gxt@mprc.pku.edu.cn> | 2011-03-17 09:19:09 +0800 |
commit | 56372b0b2f533c9a25bd40a0577405f6ddb7cff2 (patch) | |
tree | c428578bf7392232e946756cfe12e9144122c212 /arch/unicore32 | |
parent | b50f1704e9c441c58cf6dc05e72953ca30e1d4d2 (diff) |
unicore32 core architecture: mm related: fault handling
This patch implements fault handling of memory management.
Signed-off-by: Guan Xuetao <gxt@mprc.pku.edu.cn>
Reviewed-by: Arnd Bergmann <arnd@arndb.de>
Diffstat (limited to 'arch/unicore32')
-rw-r--r-- | arch/unicore32/include/asm/mmu.h | 17 | ||||
-rw-r--r-- | arch/unicore32/include/asm/mmu_context.h | 87 | ||||
-rw-r--r-- | arch/unicore32/include/asm/pgalloc.h | 110 | ||||
-rw-r--r-- | arch/unicore32/include/asm/pgtable-hwdef.h | 55 | ||||
-rw-r--r-- | arch/unicore32/include/asm/pgtable.h | 317 | ||||
-rw-r--r-- | arch/unicore32/mm/alignment.c | 523 | ||||
-rw-r--r-- | arch/unicore32/mm/extable.c | 24 | ||||
-rw-r--r-- | arch/unicore32/mm/fault.c | 479 | ||||
-rw-r--r-- | arch/unicore32/mm/mmu.c | 533 | ||||
-rw-r--r-- | arch/unicore32/mm/pgd.c | 102 |
10 files changed, 2247 insertions, 0 deletions
diff --git a/arch/unicore32/include/asm/mmu.h b/arch/unicore32/include/asm/mmu.h new file mode 100644 index 000000000000..66fa341dc2c6 --- /dev/null +++ b/arch/unicore32/include/asm/mmu.h @@ -0,0 +1,17 @@ +/* + * linux/arch/unicore32/include/asm/mmu.h + * + * Code specific to PKUnity SoC and UniCore ISA + * + * Copyright (C) 2001-2010 GUAN Xue-tao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#ifndef __UNICORE_MMU_H__ +#define __UNICORE_MMU_H__ + +typedef unsigned long mm_context_t; + +#endif diff --git a/arch/unicore32/include/asm/mmu_context.h b/arch/unicore32/include/asm/mmu_context.h new file mode 100644 index 000000000000..fb5e4c658f7a --- /dev/null +++ b/arch/unicore32/include/asm/mmu_context.h @@ -0,0 +1,87 @@ +/* + * linux/arch/unicore32/include/asm/mmu_context.h + * + * Code specific to PKUnity SoC and UniCore ISA + * + * Copyright (C) 2001-2010 GUAN Xue-tao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#ifndef __UNICORE_MMU_CONTEXT_H__ +#define __UNICORE_MMU_CONTEXT_H__ + +#include <linux/compiler.h> +#include <linux/sched.h> +#include <linux/io.h> + +#include <asm/cacheflush.h> +#include <asm/cpu-single.h> + +#define init_new_context(tsk, mm) 0 + +#define destroy_context(mm) do { } while (0) + +/* + * This is called when "tsk" is about to enter lazy TLB mode. + * + * mm: describes the currently active mm context + * tsk: task which is entering lazy tlb + * cpu: cpu number which is entering lazy tlb + * + * tsk->mm will be NULL + */ +static inline void +enter_lazy_tlb(struct mm_struct *mm, struct task_struct *tsk) +{ +} + +/* + * This is the actual mm switch as far as the scheduler + * is concerned. No registers are touched. We avoid + * calling the CPU specific function when the mm hasn't + * actually changed. + */ +static inline void +switch_mm(struct mm_struct *prev, struct mm_struct *next, + struct task_struct *tsk) +{ + unsigned int cpu = smp_processor_id(); + + if (!cpumask_test_and_set_cpu(cpu, mm_cpumask(next)) || prev != next) + cpu_switch_mm(next->pgd, next); +} + +#define deactivate_mm(tsk, mm) do { } while (0) +#define activate_mm(prev, next) switch_mm(prev, next, NULL) + +/* + * We are inserting a "fake" vma for the user-accessible vector page so + * gdb and friends can get to it through ptrace and /proc/<pid>/mem. + * But we also want to remove it before the generic code gets to see it + * during process exit or the unmapping of it would cause total havoc. + * (the macro is used as remove_vma() is static to mm/mmap.c) + */ +#define arch_exit_mmap(mm) \ +do { \ + struct vm_area_struct *high_vma = find_vma(mm, 0xffff0000); \ + if (high_vma) { \ + BUG_ON(high_vma->vm_next); /* it should be last */ \ + if (high_vma->vm_prev) \ + high_vma->vm_prev->vm_next = NULL; \ + else \ + mm->mmap = NULL; \ + rb_erase(&high_vma->vm_rb, &mm->mm_rb); \ + mm->mmap_cache = NULL; \ + mm->map_count--; \ + remove_vma(high_vma); \ + } \ +} while (0) + +static inline void arch_dup_mmap(struct mm_struct *oldmm, + struct mm_struct *mm) +{ +} + +#endif diff --git a/arch/unicore32/include/asm/pgalloc.h b/arch/unicore32/include/asm/pgalloc.h new file mode 100644 index 000000000000..0213e373a895 --- /dev/null +++ b/arch/unicore32/include/asm/pgalloc.h @@ -0,0 +1,110 @@ +/* + * linux/arch/unicore32/include/asm/pgalloc.h + * + * Code specific to PKUnity SoC and UniCore ISA + * + * Copyright (C) 2001-2010 GUAN Xue-tao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#ifndef __UNICORE_PGALLOC_H__ +#define __UNICORE_PGALLOC_H__ + +#include <asm/pgtable-hwdef.h> +#include <asm/processor.h> +#include <asm/cacheflush.h> +#include <asm/tlbflush.h> + +#define check_pgt_cache() do { } while (0) + +#define _PAGE_USER_TABLE (PMD_TYPE_TABLE | PMD_PRESENT) +#define _PAGE_KERNEL_TABLE (PMD_TYPE_TABLE | PMD_PRESENT) + +extern pgd_t *get_pgd_slow(struct mm_struct *mm); +extern void free_pgd_slow(struct mm_struct *mm, pgd_t *pgd); + +#define pgd_alloc(mm) get_pgd_slow(mm) +#define pgd_free(mm, pgd) free_pgd_slow(mm, pgd) + +#define PGALLOC_GFP (GFP_KERNEL | __GFP_NOTRACK | __GFP_REPEAT | __GFP_ZERO) + +/* + * Allocate one PTE table. + */ +static inline pte_t * +pte_alloc_one_kernel(struct mm_struct *mm, unsigned long addr) +{ + pte_t *pte; + + pte = (pte_t *)__get_free_page(PGALLOC_GFP); + if (pte) + clean_dcache_area(pte, PTRS_PER_PTE * sizeof(pte_t)); + + return pte; +} + +static inline pgtable_t +pte_alloc_one(struct mm_struct *mm, unsigned long addr) +{ + struct page *pte; + + pte = alloc_pages(PGALLOC_GFP, 0); + if (pte) { + if (!PageHighMem(pte)) { + void *page = page_address(pte); + clean_dcache_area(page, PTRS_PER_PTE * sizeof(pte_t)); + } + pgtable_page_ctor(pte); + } + + return pte; +} + +/* + * Free one PTE table. + */ +static inline void pte_free_kernel(struct mm_struct *mm, pte_t *pte) +{ + if (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); +} + +static inline void __pmd_populate(pmd_t *pmdp, unsigned long pmdval) +{ + set_pmd(pmdp, __pmd(pmdval)); + flush_pmd_entry(pmdp); +} + +/* + * Populate the pmdp entry with a pointer to the pte. This pmd is part + * of the mm address space. + */ +static inline void +pmd_populate_kernel(struct mm_struct *mm, pmd_t *pmdp, pte_t *ptep) +{ + unsigned long pte_ptr = (unsigned long)ptep; + + /* + * The pmd must be loaded with the physical + * address of the PTE table + */ + __pmd_populate(pmdp, __pa(pte_ptr) | _PAGE_KERNEL_TABLE); +} + +static inline void +pmd_populate(struct mm_struct *mm, pmd_t *pmdp, pgtable_t ptep) +{ + __pmd_populate(pmdp, + page_to_pfn(ptep) << PAGE_SHIFT | _PAGE_USER_TABLE); +} +#define pmd_pgtable(pmd) pmd_page(pmd) + +#endif diff --git a/arch/unicore32/include/asm/pgtable-hwdef.h b/arch/unicore32/include/asm/pgtable-hwdef.h new file mode 100644 index 000000000000..7314e859cca0 --- /dev/null +++ b/arch/unicore32/include/asm/pgtable-hwdef.h @@ -0,0 +1,55 @@ +/* + * linux/arch/unicore32/include/asm/pgtable-hwdef.h + * + * Code specific to PKUnity SoC and UniCore ISA + * + * Copyright (C) 2001-2010 GUAN Xue-tao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#ifndef __UNICORE_PGTABLE_HWDEF_H__ +#define __UNICORE_PGTABLE_HWDEF_H__ + +/* + * Hardware page table definitions. + * + * + Level 1 descriptor (PMD) + * - common + */ +#define PMD_TYPE_MASK (3 << 0) +#define PMD_TYPE_TABLE (0 << 0) +/*#define PMD_TYPE_LARGE (1 << 0) */ +#define PMD_TYPE_INVALID (2 << 0) +#define PMD_TYPE_SECT (3 << 0) + +#define PMD_PRESENT (1 << 2) +#define PMD_YOUNG (1 << 3) + +/*#define PMD_SECT_DIRTY (1 << 4) */ +#define PMD_SECT_CACHEABLE (1 << 5) +#define PMD_SECT_EXEC (1 << 6) +#define PMD_SECT_WRITE (1 << 7) +#define PMD_SECT_READ (1 << 8) + +/* + * + Level 2 descriptor (PTE) + * - common + */ +#define PTE_TYPE_MASK (3 << 0) +#define PTE_TYPE_SMALL (0 << 0) +#define PTE_TYPE_MIDDLE (1 << 0) +#define PTE_TYPE_LARGE (2 << 0) +#define PTE_TYPE_INVALID (3 << 0) + +#define PTE_PRESENT (1 << 2) +#define PTE_FILE (1 << 3) /* only when !PRESENT */ +#define PTE_YOUNG (1 << 3) +#define PTE_DIRTY (1 << 4) +#define PTE_CACHEABLE (1 << 5) +#define PTE_EXEC (1 << 6) +#define PTE_WRITE (1 << 7) +#define PTE_READ (1 << 8) + +#endif diff --git a/arch/unicore32/include/asm/pgtable.h b/arch/unicore32/include/asm/pgtable.h new file mode 100644 index 000000000000..68b2f297ac97 --- /dev/null +++ b/arch/unicore32/include/asm/pgtable.h @@ -0,0 +1,317 @@ +/* + * linux/arch/unicore32/include/asm/pgtable.h + * + * Code specific to PKUnity SoC and UniCore ISA + * + * Copyright (C) 2001-2010 GUAN Xue-tao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#ifndef __UNICORE_PGTABLE_H__ +#define __UNICORE_PGTABLE_H__ + +#include <asm-generic/pgtable-nopmd.h> +#include <asm/cpu-single.h> + +#include <asm/memory.h> +#include <asm/pgtable-hwdef.h> + +/* + * Just any arbitrary offset to the start of the vmalloc VM area: the + * current 8MB value just means that there will be a 8MB "hole" after the + * physical memory until the kernel virtual memory starts. That means that + * any out-of-bounds memory accesses will hopefully be caught. + * The vmalloc() routines leaves a hole of 4kB between each vmalloced + * area for the same reason. ;) + * + * Note that platforms may override VMALLOC_START, but they must provide + * VMALLOC_END. VMALLOC_END defines the (exclusive) limit of this space, + * which may not overlap IO space. + */ +#ifndef VMALLOC_START +#define VMALLOC_OFFSET SZ_8M +#define VMALLOC_START (((unsigned long)high_memory + VMALLOC_OFFSET) \ + & ~(VMALLOC_OFFSET-1)) +#define VMALLOC_END (0xff000000UL) +#endif + +#define PTRS_PER_PTE 1024 +#define PTRS_PER_PGD 1024 + +/* + * PGDIR_SHIFT determines what a third-level page table entry can map + */ +#define PGDIR_SHIFT 22 + +#ifndef __ASSEMBLY__ +extern void __pte_error(const char *file, int line, unsigned long val); +extern void __pgd_error(const char *file, int line, unsigned long val); + +#define pte_ERROR(pte) __pte_error(__FILE__, __LINE__, pte_val(pte)) +#define pgd_ERROR(pgd) __pgd_error(__FILE__, __LINE__, pgd_val(pgd)) +#endif /* !__ASSEMBLY__ */ + +#define PGDIR_SIZE (1UL << PGDIR_SHIFT) +#define PGDIR_MASK (~(PGDIR_SIZE-1)) + +/* + * This is the lowest virtual address we can permit any user space + * mapping to be mapped at. This is particularly important for + * non-high vector CPUs. + */ +#define FIRST_USER_ADDRESS PAGE_SIZE + +#define FIRST_USER_PGD_NR 1 +#define USER_PTRS_PER_PGD ((TASK_SIZE/PGDIR_SIZE) - FIRST_USER_PGD_NR) + +/* + * section address mask and size definitions. + */ +#define SECTION_SHIFT 22 +#define SECTION_SIZE (1UL << SECTION_SHIFT) +#define SECTION_MASK (~(SECTION_SIZE-1)) + +#ifndef __ASSEMBLY__ + +/* + * The pgprot_* and protection_map entries will be fixed up in runtime + * to include the cachable bits based on memory policy, as well as any + * architecture dependent bits. + */ +#define _PTE_DEFAULT (PTE_PRESENT | PTE_YOUNG | PTE_CACHEABLE) + +extern pgprot_t pgprot_user; +extern pgprot_t pgprot_kernel; + +#define PAGE_NONE pgprot_user +#define PAGE_SHARED __pgprot(pgprot_val(pgprot_user | PTE_READ \ + | PTE_WRITE) +#define PAGE_SHARED_EXEC __pgprot(pgprot_val(pgprot_user | PTE_READ \ + | PTE_WRITE \ + | PTE_EXEC) +#define PAGE_COPY __pgprot(pgprot_val(pgprot_user | PTE_READ) +#define PAGE_COPY_EXEC __pgprot(pgprot_val(pgprot_user | PTE_READ \ + | PTE_EXEC) +#define PAGE_READONLY __pgprot(pgprot_val(pgprot_user | PTE_READ) +#define PAGE_READONLY_EXEC __pgprot(pgprot_val(pgprot_user | PTE_READ \ + | PTE_EXEC) +#define PAGE_KERNEL pgprot_kernel +#define PAGE_KERNEL_EXEC __pgprot(pgprot_val(pgprot_kernel | PTE_EXEC)) + +#define __PAGE_NONE __pgprot(_PTE_DEFAULT) +#define __PAGE_SHARED __pgprot(_PTE_DEFAULT | PTE_READ \ + | PTE_WRITE) +#define __PAGE_SHARED_EXEC __pgprot(_PTE_DEFAULT | PTE_READ \ + | PTE_WRITE \ + | PTE_EXEC) +#define __PAGE_COPY __pgprot(_PTE_DEFAULT | PTE_READ) +#define __PAGE_COPY_EXEC __pgprot(_PTE_DEFAULT | PTE_READ \ + | PTE_EXEC) +#define __PAGE_READONLY __pgprot(_PTE_DEFAULT | PTE_READ) +#define __PAGE_READONLY_EXEC __pgprot(_PTE_DEFAULT | PTE_READ \ + | PTE_EXEC) + +#endif /* __ASSEMBLY__ */ + +/* + * The table below defines the page protection levels that we insert into our + * Linux page table version. These get translated into the best that the + * architecture can perform. Note that on UniCore hardware: + * 1) We cannot do execute protection + * 2) If we could do execute protection, then read is implied + * 3) write implies read permissions + */ +#define __P000 __PAGE_NONE +#define __P001 __PAGE_READONLY +#define __P010 __PAGE_COPY +#define __P011 __PAGE_COPY +#define __P100 __PAGE_READONLY_EXEC +#define __P101 __PAGE_READONLY_EXEC +#define __P110 __PAGE_COPY_EXEC +#define __P111 __PAGE_COPY_EXEC + +#define __S000 __PAGE_NONE +#define __S001 __PAGE_READONLY +#define __S010 __PAGE_SHARED +#define __S011 __PAGE_SHARED +#define __S100 __PAGE_READONLY_EXEC +#define __S101 __PAGE_READONLY_EXEC +#define __S110 __PAGE_SHARED_EXEC +#define __S111 __PAGE_SHARED_EXEC + +#ifndef __ASSEMBLY__ +/* + * ZERO_PAGE is a global shared page that is always zero: used + * for zero-mapped memory areas etc.. + */ +extern struct page *empty_zero_page; +#define ZERO_PAGE(vaddr) (empty_zero_page) + +#define pte_pfn(pte) (pte_val(pte) >> PAGE_SHIFT) +#define pfn_pte(pfn, prot) (__pte(((pfn) << PAGE_SHIFT) \ + | pgprot_val(prot))) + +#define pte_none(pte) (!pte_val(pte)) +#define pte_clear(mm, addr, ptep) set_pte(ptep, __pte(0)) +#define pte_page(pte) (pfn_to_page(pte_pfn(pte))) +#define pte_offset_kernel(dir, addr) (pmd_page_vaddr(*(dir)) \ + + __pte_index(addr)) + +#define pte_offset_map(dir, addr) (pmd_page_vaddr(*(dir)) \ + + __pte_index(addr)) +#define pte_unmap(pte) do { } while (0) + +#define set_pte(ptep, pte) cpu_set_pte(ptep, pte) + +#define set_pte_at(mm, addr, ptep, pteval) \ + do { \ + set_pte(ptep, pteval); \ + } while (0) + +/* + * The following only work if pte_present() is true. + * Undefined behaviour if not.. + */ +#define pte_present(pte) (pte_val(pte) & PTE_PRESENT) +#define pte_write(pte) (pte_val(pte) & PTE_WRITE) +#define pte_dirty(pte) (pte_val(pte) & PTE_DIRTY) +#define pte_young(pte) (pte_val(pte) & PTE_YOUNG) +#define pte_exec(pte) (pte_val(pte) & PTE_EXEC) +#define pte_special(pte) (0) + +#define PTE_BIT_FUNC(fn, op) \ +static inline pte_t pte_##fn(pte_t pte) { pte_val(pte) op; return pte; } + +PTE_BIT_FUNC(wrprotect, &= ~PTE_WRITE); +PTE_BIT_FUNC(mkwrite, |= PTE_WRITE); +PTE_BIT_FUNC(mkclean, &= ~PTE_DIRTY); +PTE_BIT_FUNC(mkdirty, |= PTE_DIRTY); +PTE_BIT_FUNC(mkold, &= ~PTE_YOUNG); +PTE_BIT_FUNC(mkyoung, |= PTE_YOUNG); + +static inline pte_t pte_mkspecial(pte_t pte) { return pte; } + +/* + * Mark the prot value as uncacheable. + */ +#define pgprot_noncached(prot) \ + __pgprot(pgprot_val(prot) & ~PTE_CACHEABLE) +#define pgprot_writecombine(prot) \ + __pgprot(pgprot_val(prot) & ~PTE_CACHEABLE) +#define pgprot_dmacoherent(prot) \ + __pgprot(pgprot_val(prot) & ~PTE_CACHEABLE) + +#define pmd_none(pmd) (!pmd_val(pmd)) +#define pmd_present(pmd) (pmd_val(pmd) & PMD_PRESENT) +#define pmd_bad(pmd) (((pmd_val(pmd) & \ + (PMD_PRESENT | PMD_TYPE_MASK)) \ + != (PMD_PRESENT | PMD_TYPE_TABLE))) + +#define set_pmd(pmdpd, pmdval) \ + do { \ + *(pmdpd) = pmdval; \ + } while (0) + +#define pmd_clear(pmdp) \ + do { \ + set_pmd(pmdp, __pmd(0));\ + clean_pmd_entry(pmdp); \ + } while (0) + +#define pmd_page_vaddr(pmd) ((pte_t *)__va(pmd_val(pmd) & PAGE_MASK)) +#define pmd_page(pmd) pfn_to_page(__phys_to_pfn(pmd_val(pmd))) + +/* + * Conversion functions: convert a page and protection to a page entry, + * and a page entry and page directory to the page they refer to. + */ +#define mk_pte(page, prot) pfn_pte(page_to_pfn(page), prot) + +/* to find an entry in a page-table-directory */ +#define pgd_index(addr) ((addr) >> PGDIR_SHIFT) + +#define pgd_offset(mm, addr) ((mm)->pgd+pgd_index(addr)) + +/* to find an entry in a kernel page-table-directory */ +#define pgd_offset_k(addr) pgd_offset(&init_mm, addr) + +/* Find an entry in the third-level page table.. */ +#define __pte_index(addr) (((addr) >> PAGE_SHIFT) & (PTRS_PER_PTE - 1)) + +static inline pte_t pte_modify(pte_t pte, pgprot_t newprot) +{ + const unsigned long mask = PTE_EXEC | PTE_WRITE | PTE_READ; + pte_val(pte) = (pte_val(pte) & ~mask) | (pgprot_val(newprot) & mask); + return pte; +} + +extern pgd_t swapper_pg_dir[PTRS_PER_PGD]; + +/* + * Encode and decode a swap entry. Swap entries are stored in the Linux + * page tables as follows: + * + * 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 + * 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 + * <--------------- offset --------------> <--- type --> 0 0 0 0 0 + * + * This gives us up to 127 swap files and 32GB per swap file. Note that + * the offset field is always non-zero. + */ +#define __SWP_TYPE_SHIFT 5 +#define __SWP_TYPE_BITS 7 +#define __SWP_TYPE_MASK ((1 << __SWP_TYPE_BITS) - 1) +#define __SWP_OFFSET_SHIFT (__SWP_TYPE_BITS + __SWP_TYPE_SHIFT) + +#define __swp_type(x) (((x).val >> __SWP_TYPE_SHIFT) \ + & __SWP_TYPE_MASK) +#define __swp_offset(x) ((x).val >> __SWP_OFFSET_SHIFT) +#define __swp_entry(type, offset) ((swp_entry_t) { \ + ((type) << __SWP_TYPE_SHIFT) | \ + ((offset) << __SWP_OFFSET_SHIFT) }) + +#define __pte_to_swp_entry(pte) ((swp_entry_t) { pte_val(pte) }) +#define __swp_entry_to_pte(swp) ((pte_t) { (swp).val }) + +/* + * It is an error for the kernel to have more swap files than we can + * encode in the PTEs. This ensures that we know when MAX_SWAPFILES + * is increased beyond what we presently support. + */ +#define MAX_SWAPFILES_CHECK() \ + BUILD_BUG_ON(MAX_SWAPFILES_SHIFT > __SWP_TYPE_BITS) + +/* + * Encode and decode a file entry. File entries are stored in the Linux + * page tables as follows: + * + * 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 + * 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 + * <----------------------- offset ----------------------> 1 0 0 0 + */ +#define pte_file(pte) (pte_val(pte) & PTE_FILE) +#define pte_to_pgoff(x) (pte_val(x) >> 4) +#define pgoff_to_pte(x) __pte(((x) << 4) | PTE_FILE) + +#define PTE_FILE_MAX_BITS 28 + +/* Needs to be defined here and not in linux/mm.h, as it is arch dependent */ +/* FIXME: this is not correct */ +#define kern_addr_valid(addr) (1) + +#include <asm-generic/pgtable.h> + +/* + * remap a physical page `pfn' of size `size' with page protection `prot' + * into virtual address `from' + */ +#define io_remap_pfn_range(vma, from, pfn, size, prot) \ + remap_pfn_range(vma, from, pfn, size, prot) + +#define pgtable_cache_init() do { } while (0) + +#endif /* !__ASSEMBLY__ */ + +#endif /* __UNICORE_PGTABLE_H__ */ diff --git a/arch/unicore32/mm/alignment.c b/arch/unicore32/mm/alignment.c new file mode 100644 index 000000000000..28f576d733ee --- /dev/null +++ b/arch/unicore32/mm/alignment.c @@ -0,0 +1,523 @@ +/* + * linux/arch/unicore32/mm/alignment.c + * + * Code specific to PKUnity SoC and UniCore ISA + * + * Copyright (C) 2001-2010 GUAN Xue-tao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +/* + * TODO: + * FPU ldm/stm not handling + */ +#include <linux/compiler.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/uaccess.h> + +#include <asm/tlbflush.h> +#include <asm/unaligned.h> + +#define CODING_BITS(i) (i & 0xe0000120) + +#define LDST_P_BIT(i) (i & (1 << 28)) /* Preindex */ +#define LDST_U_BIT(i) (i & (1 << 27)) /* Add offset */ +#define LDST_W_BIT(i) (i & (1 << 25)) /* Writeback */ +#define LDST_L_BIT(i) (i & (1 << 24)) /* Load */ + +#define LDST_P_EQ_U(i) ((((i) ^ ((i) >> 1)) & (1 << 27)) == 0) + +#define LDSTH_I_BIT(i) (i & (1 << 26)) /* half-word immed */ +#define LDM_S_BIT(i) (i & (1 << 26)) /* write ASR from BSR */ +#define LDM_H_BIT(i) (i & (1 << 6)) /* select r0-r15 or r16-r31 */ + +#define RN_BITS(i) ((i >> 19) & 31) /* Rn */ +#define RD_BITS(i) ((i >> 14) & 31) /* Rd */ +#define RM_BITS(i) (i & 31) /* Rm */ + +#define REGMASK_BITS(i) (((i & 0x7fe00) >> 3) | (i & 0x3f)) +#define OFFSET_BITS(i) (i & 0x03fff) + +#define SHIFT_BITS(i) ((i >> 9) & 0x1f) +#define SHIFT_TYPE(i) (i & 0xc0) +#define SHIFT_LSL 0x00 +#define SHIFT_LSR 0x40 +#define SHIFT_ASR 0x80 +#define SHIFT_RORRRX 0xc0 + +union offset_union { + unsigned long un; + signed long sn; +}; + +#define TYPE_ERROR 0 +#define TYPE_FAULT 1 +#define TYPE_LDST 2 +#define TYPE_DONE 3 +#define TYPE_SWAP 4 +#define TYPE_COLS 5 /* Coprocessor load/store */ + +#define get8_unaligned_check(val, addr, err) \ + __asm__( \ + "1: ldb.u %1, [%2], #1\n" \ + "2:\n" \ + " .pushsection .fixup,\"ax\"\n" \ + " .align 2\n" \ + "3: mov %0, #1\n" \ + " b 2b\n" \ + " .popsection\n" \ + " .pushsection __ex_table,\"a\"\n" \ + " .align 3\n" \ + " .long 1b, 3b\n" \ + " .popsection\n" \ + : "=r" (err), "=&r" (val), "=r" (addr) \ + : "0" (err), "2" (addr)) + +#define get8t_unaligned_check(val, addr, err) \ + __asm__( \ + "1: ldb.u %1, [%2], #1\n" \ + "2:\n" \ + " .pushsection .fixup,\"ax\"\n" \ + " .align 2\n" \ + "3: mov %0, #1\n" \ + " b 2b\n" \ + " .popsection\n" \ + " .pushsection __ex_table,\"a\"\n" \ + " .align 3\n" \ + " .long 1b, 3b\n" \ + " .popsection\n" \ + : "=r" (err), "=&r" (val), "=r" (addr) \ + : "0" (err), "2" (addr)) + +#define get16_unaligned_check(val, addr) \ + do { \ + unsigned int err = 0, v, a = addr; \ + get8_unaligned_check(val, a, err); \ + get8_unaligned_check(v, a, err); \ + val |= v << 8; \ + if (err) \ + goto fault; \ + } while (0) + +#define put16_unaligned_check(val, addr) \ + do { \ + unsigned int err = 0, v = val, a = addr; \ + __asm__( \ + "1: stb.u %1, [%2], #1\n" \ + " mov %1, %1 >> #8\n" \ + "2: stb.u %1, [%2]\n" \ + "3:\n" \ + " .pushsection .fixup,\"ax\"\n" \ + " .align 2\n" \ + "4: mov %0, #1\n" \ + " b 3b\n" \ + " .popsection\n" \ + " .pushsection __ex_table,\"a\"\n" \ + " .align 3\n" \ + " .long 1b, 4b\n" \ + " .long 2b, 4b\n" \ + " .popsection\n" \ + : "=r" (err), "=&r" (v), "=&r" (a) \ + : "0" (err), "1" (v), "2" (a)); \ + if (err) \ + goto fault; \ + } while (0) + +#define __put32_unaligned_check(ins, val, addr) \ + do { \ + unsigned int err = 0, v = val, a = addr; \ + __asm__( \ + "1: "ins" %1, [%2], #1\n" \ + " mov %1, %1 >> #8\n" \ + "2: "ins" %1, [%2], #1\n" \ + " mov %1, %1 >> #8\n" \ + "3: "ins" %1, [%2], #1\n" \ + " mov %1, %1 >> #8\n" \ + "4: "ins" %1, [%2]\n" \ + "5:\n" \ + " .pushsection .fixup,\"ax\"\n" \ + " .align 2\n" \ + "6: mov %0, #1\n" \ + " b 5b\n" \ + " .popsection\n" \ + " .pushsection __ex_table,\"a\"\n" \ + " .align 3\n" \ + " .long 1b, 6b\n" \ + " .long 2b, 6b\n" \ + " .long 3b, 6b\n" \ + " .long 4b, 6b\n" \ + " .popsection\n" \ + : "=r" (err), "=&r" (v), "=&r" (a) \ + : "0" (err), "1" (v), "2" (a)); \ + if (err) \ + goto fault; \ + } while (0) + +#define get32_unaligned_check(val, addr) \ + do { \ + unsigned int err = 0, v, a = addr; \ + get8_unaligned_check(val, a, err); \ + get8_unaligned_check(v, a, err); \ + val |= v << 8; \ + get8_unaligned_check(v, a, err); \ + val |= v << 16; \ + get8_unaligned_check(v, a, err); \ + val |= v << 24; \ + if (err) \ + goto fault; \ + } while (0) + +#define put32_unaligned_check(val, addr) \ + __put32_unaligned_check("stb.u", val, addr) + +#define get32t_unaligned_check(val, addr) \ + do { \ + unsigned int err = 0, v, a = addr; \ + get8t_unaligned_check(val, a, err); \ + get8t_unaligned_check(v, a, err); \ + val |= v << 8; \ + get8t_unaligned_check(v, a, err); \ + val |= v << 16; \ + get8t_unaligned_check(v, a, err); \ + val |= v << 24; \ + if (err) \ + goto fault; \ + } while (0) + +#define put32t_unaligned_check(val, addr) \ + __put32_unaligned_check("stb.u", val, addr) + +static void +do_alignment_finish_ldst(unsigned long addr, unsigned long instr, + struct pt_regs *regs, union offset_union offset) +{ + if (!LDST_U_BIT(instr)) + offset.un = -offset.un; + + if (!LDST_P_BIT(instr)) + addr += offset.un; + + if (!LDST_P_BIT(instr) || LDST_W_BIT(instr)) + regs->uregs[RN_BITS(instr)] = addr; +} + +static int +do_alignment_ldrhstrh(unsigned long addr, unsigned long instr, + struct pt_regs *regs) +{ + unsigned int rd = RD_BITS(instr); + + /* old value 0x40002120, can't judge swap instr correctly */ + if ((instr & 0x4b003fe0) == 0x40000120) + goto swp; + + if (LDST_L_BIT(instr)) { + unsigned long val; + get16_unaligned_check(val, addr); + + /* signed half-word? */ + if (instr & 0x80) + val = (signed long)((signed short)val); + + regs->uregs[rd] = val; + } else + put16_unaligned_check(regs->uregs[rd], addr); + + return TYPE_LDST; + +swp: + /* only handle swap word + * for swap byte should not active this alignment exception */ + get32_unaligned_check(regs->uregs[RD_BITS(instr)], addr); + put32_unaligned_check(regs->uregs[RM_BITS(instr)], addr); + return TYPE_SWAP; + +fault: + return TYPE_FAULT; +} + +static int +do_alignment_ldrstr(unsigned long addr, unsigned long instr, + struct pt_regs *regs) +{ + unsigned int rd = RD_BITS(instr); + + if (!LDST_P_BIT(instr) && LDST_W_BIT(instr)) + goto trans; + + if (LDST_L_BIT(instr)) + get32_unaligned_check(regs->uregs[rd], addr); + else + put32_unaligned_check(regs->uregs[rd], addr); + return TYPE_LDST; + +trans: + if (LDST_L_BIT(instr)) + get32t_unaligned_check(regs->uregs[rd], addr); + else + put32t_unaligned_check(regs->uregs[rd], addr); + return TYPE_LDST; + +fault: + return TYPE_FAULT; +} + +/* + * LDM/STM alignment handler. + * + * There are 4 variants of this instruction: + * + * B = rn pointer before instruction, A = rn pointer after instruction + * ------ increasing address -----> + * | | r0 | r1 | ... | rx | | + * PU = 01 B A + * PU = 11 B A + * PU = 00 A B + * PU = 10 A B + */ +static int +do_alignment_ldmstm(unsigned long addr, unsigned long instr, + struct pt_regs *regs) +{ + unsigned int rd, rn, pc_correction, reg_correction, nr_regs, regbits; + unsigned long eaddr, newaddr; + + if (LDM_S_BIT(instr)) + goto bad; + + pc_correction = 4; /* processor implementation defined */ + + /* count the number of registers in the mask to be transferred */ + nr_regs = hweight16(REGMASK_BITS(instr)) * 4; + + rn = RN_BITS(instr); + newaddr = eaddr = regs->uregs[rn]; + + if (!LDST_U_BIT(instr)) + nr_regs = -nr_regs; + newaddr += nr_regs; + if (!LDST_U_BIT(instr)) + eaddr = newaddr; + + if (LDST_P_EQ_U(instr)) /* U = P */ + eaddr += 4; + + /* + * This is a "hint" - we already have eaddr worked out by the + * processor for us. + */ + if (addr != eaddr) { + printk(KERN_ERR "LDMSTM: PC = %08lx, instr = %08lx, " + "addr = %08lx, eaddr = %08lx\n", + instruction_pointer(regs), instr, addr, eaddr); + show_regs(regs); + } + + if (LDM_H_BIT(instr)) + reg_correction = 0x10; + else + reg_correction = 0x00; + + for (regbits = REGMASK_BITS(instr), rd = 0; regbits; + regbits >>= 1, rd += 1) + if (regbits & 1) { + if (LDST_L_BIT(instr)) + get32_unaligned_check(regs-> + uregs[rd + reg_correction], eaddr); + else + put32_unaligned_check(regs-> + uregs[rd + reg_correction], eaddr); + eaddr += 4; + } + + if (LDST_W_BIT(instr)) + regs->uregs[rn] = newaddr; + return TYPE_DONE; + +fault: + regs->UCreg_pc -= pc_correction; + return TYPE_FAULT; + +bad: + printk(KERN_ERR "Alignment trap: not handling ldm with s-bit set\n"); + return TYPE_ERROR; +} + +static int +do_alignment(unsigned long addr, unsigned int error_code, struct pt_regs *regs) +{ + union offset_union offset; + unsigned long instr, instrptr; + int (*handler) (unsigned long addr, unsigned long instr, + struct pt_regs *regs); + unsigned int type; + + instrptr = instruction_pointer(regs); + if (instrptr >= PAGE_OFFSET) + instr = *(unsigned long *)instrptr; + else { + __asm__ __volatile__( + "ldw.u %0, [%1]\n" + : "=&r"(instr) + : "r"(instrptr)); + } + + regs->UCreg_pc += 4; + + switch (CODING_BITS(instr)) { + case 0x40000120: /* ldrh or strh */ + if (LDSTH_I_BIT(instr)) + offset.un = (instr & 0x3e00) >> 4 | (instr & 31); + else + offset.un = regs->uregs[RM_BITS(instr)]; + handler = do_alignment_ldrhstrh; + break; + + case 0x60000000: /* ldr or str immediate */ + case 0x60000100: /* ldr or str immediate */ + case 0x60000020: /* ldr or str immediate */ + case 0x60000120: /* ldr or str immediate */ + offset.un = OFFSET_BITS(instr); + handler = do_alignment_ldrstr; + break; + + case 0x40000000: /* ldr or str register */ + offset.un = regs->uregs[RM_BITS(instr)]; + { + unsigned int shiftval = SHIFT_BITS(instr); + + switch (SHIFT_TYPE(instr)) { + case SHIFT_LSL: + offset.un <<= shiftval; + break; + + case SHIFT_LSR: + offset.un >>= shiftval; + break; + + case SHIFT_ASR: + offset.sn >>= shiftval; + break; + + case SHIFT_RORRRX: + if (shiftval == 0) { + offset.un >>= 1; + if (regs->UCreg_asr & PSR_C_BIT) + offset.un |= 1 << 31; + } else + offset.un = offset.un >> shiftval | + offset.un << (32 - shiftval); + break; + } + } + handler = do_alignment_ldrstr; + break; + + case 0x80000000: /* ldm or stm */ + case 0x80000020: /* ldm or stm */ + handler = do_alignment_ldmstm; + break; + + default: + goto bad; + } + + type = handler(addr, instr, regs); + + if (type == TYPE_ERROR || type == TYPE_FAULT) + goto bad_or_fault; + + if (type == TYPE_LDST) + do_alignment_finish_ldst(addr, instr, regs, offset); + + return 0; + +bad_or_fault: + if (type == TYPE_ERROR) + goto bad; + regs->UCreg_pc -= 4; + /* + * We got a fault - fix it up, or die. + */ + do_bad_area(addr, error_code, regs); + return 0; + +bad: + /* + * Oops, we didn't handle the instruction. + * However, we must handl |