diff options
| author | Jinke Han <jinkehan@didiglobal.com> | 2026-09-08 15:37:42 +0800 |
|---|---|---|
| committer | Ingo Molnar <mingo@kernel.org> | 2026-09-17 09:48:42 +0200 |
| commit | a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255 (patch) | |
| tree | 73fef824f9a846cf935869d0dbc5070ef6e2f741 /arch/m68k/mm | |
| download | linux-stable-a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255.tar.gz linux-stable-a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255.zip | |
x86/kprobes: Fix crash when probing CS CALL instructionsgrafted
When using eBPF to probe CS CALL instructions within a function,
a crash can be triggered.
The eBPF tool probes offset 257 of the __hrtimer_run_queues()
function:
<__hrtimer_run_queues+249>: nopl 0x0(%rax,%rax,1)
<__hrtimer_run_queues+254>: mov %r14,%rdi
<__hrtimer_run_queues+257>: cs call <__x86_indirect_thunk_r12>
<__hrtimer_run_queues+263>: mov %eax,%r12d
<__hrtimer_run_queues+266>: xchg %ax,%ax
<__hrtimer_run_queues+268>: mov %r13,%rdi
Which triggers this crash:
BUG: unable to handle page fault for address: 00000000000f41c9
#PF: supervisor write access in kernel mode
#PF: error_code(0x0002) - not-present page
PGD 0 P4D 0
Oops: 0002 [#1] SMP NOPTI
CPU: 1 PID: 0 Comm: swapper/1 Kdump: loaded Tainted: P
RIP: 0010:__hrtimer_run_queues+0x106/0x230
Note that __hrtimer_run_queues+0x106 is __hrtimer_run_queues+262, which is
at the 6th byte of the above CS CALL instruction. Since the CS CALL
instruction occupies 6 bytes, the exception occurred in the middle of that
call instruction.
The root cause is that when using eBPF tools to probe in the middle of a
function, a kprobe with INT3 is used as the underlying implementation.
During single-step emulation of the original CALL instruction,
int3_emulate_call() assumes that the probed CALL instruction is 5 bytes
long. However, the actual CS-prefixed CALL instruction occupies 6 bytes,
so it constructs an incorrect exception return address. When the CPU
returns from the kprobe handler, the next instruction to be executed is at
the address of the last byte of that CS CALL instruction. Coincidentally,
starting from that address, the CPU fetches and decodes a completely
different instruction, which ultimately triggers a kernel crash.
Fix the issue by using the actual instruction length obtained from
the instruction decoder when constructing the exception return
address, rather than relying on the hardcoded CALL_INSN_SIZE macro.
[ mingo: Refined the changelog ]
Fixes: 6256e668b7af ("x86/kprobes: Use int3 instead of debug trap for single-step")
Suggested-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Signed-off-by: Jinke Han <jinkehan@didiglobal.com>
Signed-off-by: Ingo Molnar <mingo@kernel.org>
Reviewed-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Acked-by: Yafang Shao <laoar.shao@gmail.com>
Acked-by: Borislav Petkov <bp@alien8.de>
Cc: Peter Zijlstra <peterz@infradead.org>
Link: https://patch.msgid.link/20260908073742.GA10517@didi-ThinkCentre-M920t-N000
Diffstat (limited to 'arch/m68k/mm')
| -rw-r--r-- | arch/m68k/mm/Makefile | 12 | ||||
| -rw-r--r-- | arch/m68k/mm/cache.c | 127 | ||||
| -rw-r--r-- | arch/m68k/mm/fault.c | 217 | ||||
| -rw-r--r-- | arch/m68k/mm/fault.h | 7 | ||||
| -rw-r--r-- | arch/m68k/mm/hwtest.c | 96 | ||||
| -rw-r--r-- | arch/m68k/mm/init.c | 118 | ||||
| -rw-r--r-- | arch/m68k/mm/kmap.c | 400 | ||||
| -rw-r--r-- | arch/m68k/mm/mcfmmu.c | 270 | ||||
| -rw-r--r-- | arch/m68k/mm/memory.c | 192 | ||||
| -rw-r--r-- | arch/m68k/mm/motorola.c | 512 | ||||
| -rw-r--r-- | arch/m68k/mm/sun3kmap.c | 159 | ||||
| -rw-r--r-- | arch/m68k/mm/sun3mmu.c | 100 |
12 files changed, 2210 insertions, 0 deletions
diff --git a/arch/m68k/mm/Makefile b/arch/m68k/mm/Makefile new file mode 100644 index 000000000..b3adb5253 --- /dev/null +++ b/arch/m68k/mm/Makefile @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the linux m68k-specific parts of the memory manager. +# + +obj-y := init.o + +obj-$(CONFIG_MMU) += cache.o fault.o +obj-$(CONFIG_MMU_MOTOROLA) += kmap.o memory.o motorola.o hwtest.o +obj-$(CONFIG_MMU_SUN3) += sun3kmap.o sun3mmu.o hwtest.o +obj-$(CONFIG_MMU_COLDFIRE) += kmap.o memory.o mcfmmu.o + diff --git a/arch/m68k/mm/cache.c b/arch/m68k/mm/cache.c new file mode 100644 index 000000000..dde978e66 --- /dev/null +++ b/arch/m68k/mm/cache.c @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * linux/arch/m68k/mm/cache.c + * + * Instruction cache handling + * + * Copyright (C) 1995 Hamish Macdonald + */ + +#include <linux/module.h> +#include <asm/cacheflush.h> +#include <asm/traps.h> + + +static unsigned long virt_to_phys_slow(unsigned long vaddr) +{ + if (CPU_IS_060) { + unsigned long paddr; + + /* The PLPAR instruction causes an access error if the translation + * is not possible. To catch this we use the same exception mechanism + * as for user space accesses in <asm/uaccess.h>. */ + asm volatile (".chip 68060\n" + "1: plpar (%0)\n" + ".chip 68k\n" + "2:\n" + ".section .fixup,\"ax\"\n" + " .even\n" + "3: sub.l %0,%0\n" + " jra 2b\n" + ".previous\n" + ".section __ex_table,\"a\"\n" + " .align 4\n" + " .long 1b,3b\n" + ".previous" + : "=a" (paddr) + : "0" (vaddr)); + return paddr; + } else if (CPU_IS_040) { + unsigned long mmusr; + + asm volatile (".chip 68040\n\t" + "ptestr (%1)\n\t" + "movec %%mmusr, %0\n\t" + ".chip 68k" + : "=r" (mmusr) + : "a" (vaddr)); + + if (mmusr & MMU_R_040) + return (mmusr & PAGE_MASK) | (vaddr & ~PAGE_MASK); + } else { + WARN_ON_ONCE(!CPU_IS_040_OR_060); + } + return 0; +} + +/* Push n pages at kernel virtual address and clear the icache */ +/* RZ: use cpush %bc instead of cpush %dc, cinv %ic */ +void flush_icache_user_range(unsigned long address, unsigned long endaddr) +{ + if (CPU_IS_COLDFIRE) { + unsigned long start, end; + start = address & ICACHE_SET_MASK; + end = endaddr & ICACHE_SET_MASK; + if (start > end) { + flush_cf_icache(0, end); + end = ICACHE_MAX_ADDR; + } + flush_cf_icache(start, end); + } else if (CPU_IS_040_OR_060) { + address &= PAGE_MASK; + + do { + asm volatile ("nop\n\t" + ".chip 68040\n\t" + "cpushp %%bc,(%0)\n\t" + ".chip 68k" + : : "a" (virt_to_phys_slow(address))); + address += PAGE_SIZE; + } while (address < endaddr); + } else { + unsigned long tmp; + asm volatile ("movec %%cacr,%0\n\t" + "orw %1,%0\n\t" + "movec %0,%%cacr" + : "=&d" (tmp) + : "di" (FLUSH_I)); + } +} + +void flush_icache_range(unsigned long address, unsigned long endaddr) +{ + set_fc(SUPER_DATA); + flush_icache_user_range(address, endaddr); + set_fc(USER_DATA); +} +EXPORT_SYMBOL(flush_icache_range); + +void flush_icache_user_page(struct vm_area_struct *vma, struct page *page, + unsigned long addr, int len) +{ + if (CPU_IS_COLDFIRE) { + unsigned long start, end; + start = addr & ICACHE_SET_MASK; + end = (addr + len) & ICACHE_SET_MASK; + if (start > end) { + flush_cf_icache(0, end); + end = ICACHE_MAX_ADDR; + } + flush_cf_icache(start, end); + + } else if (CPU_IS_040_OR_060) { + asm volatile ("nop\n\t" + ".chip 68040\n\t" + "cpushp %%bc,(%0)\n\t" + ".chip 68k" + : : "a" (page_to_phys(page))); + } else { + unsigned long tmp; + asm volatile ("movec %%cacr,%0\n\t" + "orw %1,%0\n\t" + "movec %0,%%cacr" + : "=&d" (tmp) + : "di" (FLUSH_I)); + } +} + diff --git a/arch/m68k/mm/fault.c b/arch/m68k/mm/fault.c new file mode 100644 index 000000000..fa3c5f38d --- /dev/null +++ b/arch/m68k/mm/fault.c @@ -0,0 +1,217 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * linux/arch/m68k/mm/fault.c + * + * Copyright (C) 1995 Hamish Macdonald + */ + +#include <linux/mman.h> +#include <linux/mm.h> +#include <linux/kernel.h> +#include <linux/ptrace.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/uaccess.h> +#include <linux/perf_event.h> + +#include <asm/setup.h> +#include <asm/traps.h> + +#include "fault.h" + +extern void die_if_kernel(char *, struct pt_regs *, long); + +int send_fault_sig(struct pt_regs *regs) +{ + int signo, si_code; + void __user *addr; + + signo = current->thread.signo; + si_code = current->thread.code; + addr = (void __user *)current->thread.faddr; + pr_debug("send_fault_sig: %p,%d,%d\n", addr, signo, si_code); + + if (user_mode(regs)) { + force_sig_fault(signo, si_code, addr); + } else { + if (fixup_exception(regs)) + return -1; + + //if (signo == SIGBUS) + // force_sig_fault(si_signo, si_code, addr); + + /* + * Oops. The kernel tried to access some bad page. We'll have to + * terminate things with extreme prejudice. + */ + if ((unsigned long)addr < PAGE_SIZE) + pr_alert("Unable to handle kernel NULL pointer dereference"); + else + pr_alert("Unable to handle kernel access"); + pr_cont(" at virtual address %p\n", addr); + die_if_kernel("Oops", regs, 0 /*error_code*/); + make_task_dead(SIGKILL); + } + + return 1; +} + +/* + * This routine handles page faults. It determines the problem, and + * then passes it off to one of the appropriate routines. + * + * error_code: + * bit 0 == 0 means no page found, 1 means protection fault + * bit 1 == 0 means read, 1 means write + * + * If this routine detects a bad access, it returns 1, otherwise it + * returns 0. + */ +int do_page_fault(struct pt_regs *regs, unsigned long address, + unsigned long error_code) +{ + struct mm_struct *mm = current->mm; + struct vm_area_struct * vma; + vm_fault_t fault; + unsigned int flags = FAULT_FLAG_DEFAULT; + + pr_debug("do page fault:\nregs->sr=%#x, regs->pc=%#lx, address=%#lx, %ld, %p\n", + regs->sr, regs->pc, address, error_code, mm ? mm->pgd : NULL); + + /* + * If we're in an interrupt or have no user + * context, we must not take the fault.. + */ + if (faulthandler_disabled() || !mm) + goto no_context; + + if (user_mode(regs)) + flags |= FAULT_FLAG_USER; + + perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS, 1, regs, address); +retry: + mmap_read_lock(mm); + + vma = find_vma(mm, address); + if (!vma) + goto map_err; + if (vma->vm_start <= address) + goto good_area; + if (!(vma->vm_flags & VM_GROWSDOWN)) + goto map_err; + if (user_mode(regs)) { + /* Accessing the stack below usp is always a bug. The + "+ 256" is there due to some instructions doing + pre-decrement on the stack and that doesn't show up + until later. */ + if (address + 256 < rdusp()) + goto map_err; + } + vma = expand_stack(mm, address); + if (!vma) + goto map_err_nosemaphore; + +/* + * Ok, we have a good vm_area for this memory access, so + * we can handle it.. + */ +good_area: + pr_debug("do_page_fault: good_area\n"); + switch (error_code & 3) { + default: /* 3: write, present */ + fallthrough; + case 2: /* write, not present */ + if (!(vma->vm_flags & VM_WRITE)) + goto acc_err; + flags |= FAULT_FLAG_WRITE; + break; + case 1: /* read, present */ + goto acc_err; + case 0: /* read, not present */ + if (unlikely(!vma_is_accessible(vma))) + goto acc_err; + } + + /* + * 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(vma, address, flags, regs); + pr_debug("handle_mm_fault returns %x\n", fault); + + if (fault_signal_pending(fault, regs)) { + if (!user_mode(regs)) + goto no_context; + return 0; + } + + /* The fault is fully completed (including releasing mmap lock) */ + if (fault & VM_FAULT_COMPLETED) + return 0; + + if (unlikely(fault & VM_FAULT_ERROR)) { + if (fault & VM_FAULT_OOM) + goto out_of_memory; + else if (fault & VM_FAULT_SIGSEGV) + goto map_err; + else if (fault & VM_FAULT_SIGBUS) + goto bus_err; + BUG(); + } + + if (fault & VM_FAULT_RETRY) { + flags |= FAULT_FLAG_TRIED; + + /* + * No need to mmap_read_unlock(mm) as we would + * have already released it in __lock_page_or_retry + * in mm/filemap.c. + */ + + goto retry; + } + + mmap_read_unlock(mm); + return 0; + +/* + * 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: + mmap_read_unlock(mm); + if (!user_mode(regs)) + goto no_context; + pagefault_out_of_memory(); + return 0; + +no_context: + current->thread.signo = SIGBUS; + current->thread.faddr = address; + return send_fault_sig(regs); + +bus_err: + current->thread.signo = SIGBUS; + current->thread.code = BUS_ADRERR; + current->thread.faddr = address; + goto send_sig; + +map_err: + mmap_read_unlock(mm); +map_err_nosemaphore: + current->thread.signo = SIGSEGV; + current->thread.code = SEGV_MAPERR; + current->thread.faddr = address; + return send_fault_sig(regs); + +acc_err: + current->thread.signo = SIGSEGV; + current->thread.code = SEGV_ACCERR; + current->thread.faddr = address; + +send_sig: + mmap_read_unlock(mm); + return send_fault_sig(regs); +} diff --git a/arch/m68k/mm/fault.h b/arch/m68k/mm/fault.h new file mode 100644 index 000000000..dab14ef7d --- /dev/null +++ b/arch/m68k/mm/fault.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +struct pt_regs; + +int do_page_fault(struct pt_regs *regs, unsigned long address, + unsigned long error_code); +int send_fault_sig(struct pt_regs *regs); diff --git a/arch/m68k/mm/hwtest.c b/arch/m68k/mm/hwtest.c new file mode 100644 index 000000000..8ee7a3368 --- /dev/null +++ b/arch/m68k/mm/hwtest.c @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Tests for presence or absence of hardware registers. + * This code was originally in atari/config.c, but I noticed + * that it was also in drivers/nubus/nubus.c and I wanted to + * use it in hp300/config.c, so it seemed sensible to pull it + * out into its own file. + * + * The test is for use when trying to read a hardware register + * that isn't present would cause a bus error. We set up a + * temporary handler so that this doesn't kill the kernel. + * + * There is a test-by-reading and a test-by-writing; I present + * them here complete with the comments from the original atari + * config.c... + * -- PMM <pmaydell@chiark.greenend.org.uk>, 05/1998 + */ + +/* This function tests for the presence of an address, specially a + * hardware register address. It is called very early in the kernel + * initialization process, when the VBR register isn't set up yet. On + * an Atari, it still points to address 0, which is unmapped. So a bus + * error would cause another bus error while fetching the exception + * vector, and the CPU would do nothing at all. So we needed to set up + * a temporary VBR and a vector table for the duration of the test. + */ + +#include <linux/module.h> + +#include <asm/hwtest.h> + +int hwreg_present(volatile void *regp) +{ + int ret = 0; + unsigned long flags; + long save_sp, save_vbr; + long tmp_vectors[3]; + + local_irq_save(flags); + __asm__ __volatile__ ( + "movec %/vbr,%2\n\t" + "movel #Lberr1,%4@(8)\n\t" + "movec %4,%/vbr\n\t" + "movel %/sp,%1\n\t" + "moveq #0,%0\n\t" + "tstb %3@\n\t" + "nop\n\t" + "moveq #1,%0\n" + "Lberr1:\n\t" + "movel %1,%/sp\n\t" + "movec %2,%/vbr" + : "=&d" (ret), "=&r" (save_sp), "=&r" (save_vbr) + : "a" (regp), "a" (tmp_vectors) + ); + local_irq_restore(flags); + + return ret; +} +EXPORT_SYMBOL(hwreg_present); + +/* Basically the same, but writes a value into a word register, protected + * by a bus error handler. Returns 1 if successful, 0 otherwise. + */ + +int hwreg_write(volatile void *regp, unsigned short val) +{ + int ret; + unsigned long flags; + long save_sp, save_vbr; + long tmp_vectors[3]; + + local_irq_save(flags); + __asm__ __volatile__ ( + "movec %/vbr,%2\n\t" + "movel #Lberr2,%4@(8)\n\t" + "movec %4,%/vbr\n\t" + "movel %/sp,%1\n\t" + "moveq #0,%0\n\t" + "movew %5,%3@\n\t" + "nop\n\t" + /* + * If this nop isn't present, 'ret' may already be loaded + * with 1 at the time the bus error happens! + */ + "moveq #1,%0\n" + "Lberr2:\n\t" + "movel %1,%/sp\n\t" + "movec %2,%/vbr" + : "=&d" (ret), "=&r" (save_sp), "=&r" (save_vbr) + : "a" (regp), "a" (tmp_vectors), "g" (val) + ); + local_irq_restore(flags); + + return ret; +} +EXPORT_SYMBOL(hwreg_write); + diff --git a/arch/m68k/mm/init.c b/arch/m68k/mm/init.c new file mode 100644 index 000000000..3b88c0dd1 --- /dev/null +++ b/arch/m68k/mm/init.c @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * linux/arch/m68k/mm/init.c + * + * Copyright (C) 1995 Hamish Macdonald + * + * Contains common initialization routines, specific init code moved + * to motorola.c and sun3mmu.c + */ + +#include <linux/module.h> +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/swap.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/init.h> +#include <linux/memblock.h> +#include <linux/gfp.h> + +#include <asm/setup.h> +#include <linux/uaccess.h> +#include <asm/page.h> +#include <asm/pgalloc.h> +#include <asm/traps.h> +#include <asm/machdep.h> +#include <asm/io.h> +#ifdef CONFIG_ATARI +#include <asm/atari_stram.h> +#endif +#include <asm/sections.h> +#include <asm/tlb.h> + +void __init arch_zone_limits_init(unsigned long *max_zone_pfns) +{ + max_zone_pfns[ZONE_DMA] = PFN_DOWN(memblock_end_of_DRAM()); +} + +#ifdef CONFIG_MMU + +int m68k_virt_to_node_shift; + +void __init m68k_setup_node(int node) +{ + node_set_online(node); +} + +#else /* CONFIG_MMU */ + +/* + * paging_init() continues the virtual memory environment setup which + * was begun by the code in arch/head.S. + * The parameters are pointers to where to stick the starting and ending + * addresses of available kernel virtual memory. + */ +void __init paging_init(void) +{ + /* + * Make sure start_mem is page aligned, otherwise bootmem and + * page_alloc get different views of the world. + */ + unsigned long end_mem = memory_end & PAGE_MASK; + + high_memory = (void *) end_mem; +} + +#endif /* CONFIG_MMU */ + +void free_initmem(void) +{ +#ifndef CONFIG_MMU_SUN3 + free_initmem_default(-1); +#endif /* CONFIG_MMU_SUN3 */ +} + +#if defined(CONFIG_MMU) && !defined(CONFIG_COLDFIRE) +#define VECTORS &vectors[0] +#else +#define VECTORS _ramvec +#endif + +static inline void init_pointer_tables(void) +{ +#if defined(CONFIG_MMU) && !defined(CONFIG_SUN3) && !defined(CONFIG_COLDFIRE) + int i, j; + + /* insert pointer tables allocated so far into the tablelist */ + init_pointer_table(kernel_pg_dir, TABLE_PGD); + for (i = 0; i < PTRS_PER_PGD; i++) { + pud_t *pud = (pud_t *)&kernel_pg_dir[i]; + pmd_t *pmd_dir; + + if (!pud_present(*pud)) + continue; + + pmd_dir = (pmd_t *)pgd_page_vaddr(kernel_pg_dir[i]); + init_pointer_table(pmd_dir, TABLE_PMD); + + for (j = 0; j < PTRS_PER_PMD; j++) { + pmd_t *pmd = &pmd_dir[j]; + pte_t *pte_dir; + + if (!pmd_present(*pmd)) + continue; + + pte_dir = (pte_t *)pmd_page_vaddr(*pmd); + init_pointer_table(pte_dir, TABLE_PTE); + } + } +#endif +} + +void __init mem_init(void) +{ + init_pointer_tables(); +} diff --git a/arch/m68k/mm/kmap.c b/arch/m68k/mm/kmap.c new file mode 100644 index 000000000..616799940 --- /dev/null +++ b/arch/m68k/mm/kmap.c @@ -0,0 +1,400 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * linux/arch/m68k/mm/kmap.c + * + * Copyright (C) 1997 Roman Hodek + * + * 10/01/99 cleaned up the code and changing to the same interface + * used by other architectures /Roman Zippel + */ + +#include <linux/module.h> +#include <linux/mm.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> + +#include <asm/setup.h> +#include <asm/page.h> +#include <asm/io.h> +#include <asm/tlbflush.h> + +#undef DEBUG + +/* + * For 040/060 we can use the virtual memory area like other architectures, + * but for 020/030 we want to use early termination page descriptors and we + * can't mix this with normal page descriptors, so we have to copy that code + * (mm/vmalloc.c) and return appropriately aligned addresses. + */ + +#ifdef CPU_M68040_OR_M68060_ONLY + +#define IO_SIZE PAGE_SIZE + +static inline struct vm_struct *get_io_area(unsigned long size) +{ + return get_vm_area(size, VM_IOREMAP); +} + + +static inline void free_io_area(void *addr) +{ + vfree((void *)(PAGE_MASK & (unsigned long)addr)); +} + +#else + +#define IO_SIZE PMD_SIZE + +static struct vm_struct *iolist; + +/* + * __free_io_area unmaps nearly everything, so be careful + * Currently it doesn't free pointer/page tables anymore but this + * wasn't used anyway and might be added later. + */ +static void __free_io_area(void *addr, unsigned long size) +{ + unsigned long virtaddr = (unsigned long)addr; + pgd_t *pgd_dir; + p4d_t *p4d_dir; + pud_t *pud_dir; + pmd_t *pmd_dir; + pte_t *pte_dir; + + while ((long)size > 0) { + pgd_dir = pgd_offset_k(virtaddr); + p4d_dir = p4d_offset(pgd_dir, virtaddr); + pud_dir = pud_offset(p4d_dir, virtaddr); + if (pud_bad(*pud_dir)) { + printk("iounmap: bad pud(%08lx)\n", pud_val(*pud_dir)); + pud_clear(pud_dir); + return; + } + pmd_dir = pmd_offset(pud_dir, virtaddr); + +#if CONFIG_PGTABLE_LEVELS == 3 + if (CPU_IS_020_OR_030) { + int pmd_type = pmd_val(*pmd_dir) & _DESCTYPE_MASK; + + if (pmd_type == _PAGE_PRESENT) { + pmd_clear(pmd_dir); + virtaddr += PMD_SIZE; + size -= PMD_SIZE; + + } else if (pmd_type == 0) + continue; + } +#endif + + if (pmd_bad(*pmd_dir)) { + printk("iounmap: bad pmd (%08lx)\n", pmd_val(*pmd_dir)); + pmd_clear(pmd_dir); + return; + } + pte_dir = pte_offset_kernel(pmd_dir, virtaddr); + + pte_val(*pte_dir) = 0; + virtaddr += PAGE_SIZE; + size -= PAGE_SIZE; + } + + flush_tlb_all(); +} + +static struct vm_struct *get_io_area(unsigned long size) +{ + unsigned long addr; + struct vm_struct **p, *tmp, *area; + + area = kmalloc_obj(*area); + if (!area) + return NULL; + addr = KMAP_START; + for (p = &iolist; (tmp = *p) ; p = &tmp->next) { + if (size + addr < (unsigned long)tmp->addr) + break; + if (addr > KMAP_END-size) { + kfree(area); + return NULL; + } + addr = tmp->size + (unsigned long)tmp->addr; + } + area->addr = (void *)addr; + area->size = size + IO_SIZE; + area->next = *p; + *p = area; + return area; +} + +static inline void free_io_area(void *addr) +{ + struct vm_struct **p, *tmp; + + if (!addr) + return; + addr = (void *)((unsigned long)addr & -IO_SIZE); + for (p = &iolist ; (tmp = *p) ; p = &tmp->next) { + if (tmp->addr == addr) { + *p = tmp->next; + /* remove gap added in get_io_area() */ + __free_io_area(tmp->addr, tmp->size - IO_SIZE); + kfree(tmp); + return; + } + } +} + +#endif + +/* + * Map some physical address range into the kernel address space. + */ +/* Rewritten by Andreas Schwab to remove all races. */ + +void __iomem *__ioremap(unsigned long physaddr, unsigned long size, int cacheflag) +{ + struct vm_struct *area; + unsigned long virtaddr, retaddr; + long offset; + pgd_t *pgd_dir; + p4d_t *p4d_dir; + pud_t *pud_dir; + pmd_t *pmd_dir; + pte_t *pte_dir; + + /* + * Don't allow mappings that wrap.. + */ + if (!size || physaddr > (unsigned long)(-size)) + return NULL; + +#ifdef CONFIG_AMIGA + if (MACH_IS_AMIGA) { + if ((physaddr >= 0x40000000) && (physaddr + size < 0x60000000) + && (cacheflag == IOMAP_NOCACHE_SER)) + return (void __iomem *)physaddr; + } +#endif +#ifdef CONFIG_VIRT + if (MACH_IS_VIRT) { + if (physaddr >= 0xff000000 && cacheflag == IOMAP_NOCACHE_SER) + return (void __iomem *)physaddr; + } +#endif +#ifdef CONFIG_COLDFIRE + if (__cf_internalio(physaddr)) + return (void __iomem *) physaddr; +#endif + +#ifdef DEBUG + printk("ioremap: 0x%lx,0x%lx(%d) - ", physaddr, size, cacheflag); +#endif + /* + * Mappings have to be aligned + */ + offset = physaddr & (IO_SIZE - 1); + physaddr &= -IO_SIZE; + size = (size + offset + IO_SIZE - 1) & -IO_SIZE; + + /* + * Ok, go for it.. + */ + area = get_io_area(size); + if (!area) + return NULL; + + virtaddr = (unsigned long)area->addr; + retaddr = virtaddr + offset; +#ifdef DEBUG + printk("0x%lx,0x%lx,0x%lx", physaddr, virtaddr, retaddr); +#endif + + /* + * add cache and table flags to physical address + */ + if (CPU_IS_040_OR_060) { + physaddr |= (_PAGE_PRESENT | _PAGE_GLOBAL040 | + _PAGE_ACCESSED | _PAGE_DIRTY); + switch (cacheflag) { + case IOMAP_FULL_CACHING: + physaddr |= _PAGE_CACHE040; + break; + case IOMAP_NOCACHE_SER: + default: + physaddr |= _PAGE_NOCACHE_S; + break; + case IOMAP_NOCACHE_NONSER: + physaddr |= _PAGE_NOCACHE; + break; + case IOMAP_WRITETHROUGH: + physaddr |= _PAGE_CACHE040W; + break; + } + } else { + physaddr |= (_PAGE_PRESENT | _PAGE_ACCESSED | + _PAGE_DIRTY | _PAGE_READWRITE); + switch (cacheflag) { + case IOMAP_NOCACHE_SER: + case IOMAP_NOCACHE_NONSER: + default: + physaddr |= _PAGE_NOCACHE030; + break; + case IOMAP_FULL_CACHING: + case IOMAP_WRITETHROUGH: + break; + } + } + + while ((long)size > 0) { +#ifdef DEBUG + if (!(virtaddr & (PMD_SIZE-1))) + printk ("\npa=%#lx va=%#lx ", physaddr, virtaddr); +#endif + pgd_dir = pgd_offset_k(virtaddr); + p4d_dir = p4d_offset(pgd_dir, virtaddr); + pud_dir = pud_offset(p4d_dir, virtaddr); + pmd_dir = pmd_alloc(&init_mm, pud_dir, virtaddr); + if (!pmd_dir) { + printk("ioremap: no mem for pmd_dir\n"); + return NULL; + } + +#if CONFIG_PGTABLE_LEVELS == 3 + if (CPU_IS_020_OR_030) { + pmd_val(*pmd_dir) = physaddr; + physaddr += PMD_SIZE; + virtaddr += PMD_SIZE; + size -= PMD_SIZE; + } else +#endif + { + pte_dir = pte_alloc_kernel(pmd_dir, virtaddr); + if (!pte_dir) { + printk("ioremap: no mem for pte_dir\n"); + return NULL; + } + + pte_val(*pte_dir) = physaddr; + virtaddr += PAGE_SIZE; + physaddr += PAGE_SIZE; + size -= PAGE_SIZE; + } + } +#ifdef DEBUG + printk("\n"); +#endif + flush_tlb_all(); + + return (void __iomem *)retaddr; +} +EXPORT_SYMBOL(__ioremap); + +/* + * Unmap an ioremap()ed region again + */ +void iounmap(void __iomem *addr) +{ +#ifdef CONFIG_AMIGA + if (MACH_IS_AMIGA && + ((unsigned long)addr >= 0x40000000) && + ((unsigned long)addr < 0x60000000)) + return; +#endif +#ifdef CONFIG_VIRT + if (MACH_IS_VIRT && (unsigned long)addr >= 0xff000000) + return; +#endif +#ifdef CONFIG_COLDFIRE + if (cf_internalio(addr)) + return; +#endif + free_io_area((__force void *)addr); +} +EXPORT_SYMBOL(iounmap); + +/* + * Set new cache mode for some kernel address space. + * The caller must push data for that range itself, if such data may already + * be in the cache. + */ +void kernel_set_cachemode(void *addr, unsigned long size, int cmode) +{ + unsigned long virtaddr = (unsigned long)addr; + pgd_t *pgd_dir; + p4d_t *p4d_dir; + pud_t *pud_dir; + pmd_t *pmd_dir; + pte_t *pte_dir; + + if (CPU_IS_040_OR_060) { + switch (cmode) { + case IOMAP_FULL_CACHING: + cmode = _PAGE_CACHE040; + break; + case IOMAP_NOCACHE_SER: + default: + cmode = _PAGE_NOCACHE_S; + break; + case IOMAP_NOCACHE_NONSER: + cmode = _PAGE_NOCACHE; + break; + case IOMAP_WRITETHROUGH: + cmode = _PAGE_CACHE040W; + break; + } + } else { + switch (cmode) { + case IOMAP_NOCACHE_SER: + case IOMAP_NOCACHE_NONSER: + default: + cmode = _PAGE_NOCACHE030; + break; + case IOMAP_FULL_CACHING: + case IOMAP_WRITETHROUGH: + cmode = 0; + } + } + + while ((long)size > 0) { + pgd_dir = pgd_offset_k(virtaddr); + p4d_dir = p4d_offset(pgd_dir, virtaddr); + pud_dir = pud_offset(p4d_dir, virtaddr); + if (pud_bad(*pud_dir)) { + printk("iocachemode: bad pud(%08lx)\n", pud_val(*pud_dir)); + pud_clear(pud_dir); + return; + } + pmd_dir = pmd_offset(pud_dir, virtaddr); + +#if CONFIG_PGTABLE_LEVELS == 3 + if (CPU_IS_020_OR_030) { + unsigned long pmd = pmd_val(*pmd_dir); + + if ((pmd & _DESCTYPE_MASK) == _PAGE_PRESENT) { + *pmd_dir = __pmd((pmd & _CACHEMASK040) | cmode); + virtaddr += PMD_SIZE; + size -= PMD_SIZE; + continue; + } + } +#endif + + if (pmd_bad(*pmd_dir)) { + printk("iocachemode: bad pmd (%08lx)\n", pmd_val(*pmd_dir)); + pmd_clear(pmd_dir); + return; + } + pte_dir = pte_offset_kernel(pmd_dir, virtaddr); + + pte_val(*pte_dir) = (pte_val(*pte_dir) & _CACHEMASK040) | cmode; + virtaddr += PAGE_SIZE; + size -= PAGE_SIZE; + } + + flush_tlb_all(); +} +EXPORT_SYMBOL(kernel_set_cachemode); diff --git a/arch/m68k/mm/mcfmmu.c b/arch/m68k/mm/mcfmmu.c new file mode 100644 index 000000000..4924f2ff8 --- /dev/null +++ b/arch/m68k/mm/mcfmmu.c @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Based upon linux/arch/m68k/mm/sun3mmu.c + * Based upon linux/arch/ppc/mm/mmu_context.c + * + * Implementations of mm routines specific to the Coldfire MMU. + * + * Copyright (c) 2008 Freescale Semiconductor, Inc. + */ + +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/mm.h> +#include <linux/init.h> +#include <linux/string.h> +#include <linux/memblock.h> + +#include <asm/setup.h> +#include <asm/page.h> +#include <asm/mmu_context.h> +#include <asm/mcf_pgalloc.h> +#include <asm/tlbflush.h> +#include <asm/pgalloc.h> + +#define KMAPAREA(x) ((x >= VMALLOC_START) && (x < KMAP_END)) + +mm_context_t next_mmu_context; +unsigned long context_map[LAST_CONTEXT / BITS_PER_LONG + 1]; +atomic_t nr_free_contexts; +struct mm_struct *context_mm[LAST_CONTEXT+1]; +unsigned long num_pages; + +/* + * ColdFire paging_init derived from sun3. + */ +void __init paging_init(void) +{ + pgd_t *pg_dir; + pte_t *pg_table; + unsigned long address, size; + unsigned long next_pgtable; + int i; + + pg_dir = swapper_pg_dir; + memset(swapper_pg_dir, 0, sizeof(swapper_pg_dir)); + + size = num_pages * sizeof(pte_t); + size = (size + PAGE_SIZE) & ~(PAGE_SIZE-1); + next_pgtable = (unsigned long) memblock_alloc_or_panic(size, PAGE_SIZE); + + pg_dir += PAGE_OFFSET >> PGDIR_SHIFT; + + address = PAGE_OFFSET; + while (address < (unsigned long)high_memory) { + pg_table = (pte_t *) next_pgtable; + next_pgtable += PTRS_PER_PTE * sizeof(pte_t); + pgd_val(*pg_dir) = (unsigned long) pg_table; + pg_dir++; + + /* now change pg_table to kernel virtual addresses */ + for (i = 0; i < PTRS_PER_PTE; ++i, ++pg_table) { + pte_t pte = pfn_pte(virt_to_pfn((void *)address), + PAGE_INIT); + if (address >= (unsigned long) high_memory) + pte_val(pte) = 0; + + set_pte(pg_table, pte); + address += PAGE_SIZE; + } + } + + current->mm = NULL; +} + +int cf_tlb_miss(struct pt_regs *regs, int write, int dtlb, int extension_word) +{ + unsigned long flags, mmuar, mmutr; + struct mm_struct *mm; + pgd_t *pgd; + p4d_t *p4d; + pud_t *pud; + pmd_t *pmd; + pte_t *pte = NULL; + int ret = -1; + int asid; + + local_irq_save(flags); + + mmuar = (dtlb) ? mmu_read(MMUAR) : + regs->pc + (extension_word * sizeof(long)); + + mm = (!user_mode(regs) && KMAPAREA(mmuar)) ? &init_mm : current->mm; + if (!mm) + goto out; + + pgd = pgd_offset(mm, mmuar); + if (pgd_none(*pgd)) + goto out; + + p4d = p4d_offset(pgd, mmuar); + if (p4d_none(*p4d)) + goto out; + + pud = pud_offset(p4d, mmuar); + if (pud_none(*pud)) + goto out; + + pmd = pmd_offset(pud, mmuar); + if (pmd_none(*pmd)) + goto out; + + pte = (KMAPAREA(mmuar)) ? pte_offset_kernel(pmd, mmuar) + : pte_offset_map(pmd, mmuar); + if (!pte || pte_none(*pte) || !pte_present(*pte)) + goto out; + + if (write) { + if (!pte_write(*pte)) + goto out; + set_pte(pte, pte_mkdirty(*pte)); + } + + set_pte(pte, pte_mkyoung(*pte)); + asid = mm->context & 0xff; + if (!pte_dirty(*pte) && !KMAPAREA(mmuar)) + set_pte(pte, pte_wrprotect(*pte)); + + mmutr = (mmuar & PAGE_MASK) | (asid << MMUTR_IDN) | MMUTR_V; + if ((mmuar < TASK_UNMAPPED_BASE) || (mmuar >= TASK_SIZE)) + mmutr |= (pte->pte & CF_PAGE_MMUTR_MASK) >> CF_PAGE_MMUTR_SHIFT; + mmu_write(MMUTR, mmutr); + + mmu_write(MMUDR, (pte_val(*pte) & PAGE_MASK) | + ((pte->pte) & CF_PAGE_MMUDR_MASK) | MMUDR_SZ_8KB | MMUDR_X); + + if (dtlb) + mmu_write(MMUOR, MMUOR_ACC | MMUOR_UAA); + else + mmu_write(MMUOR, MMUOR_ITLB | MMUOR_ACC | MMUOR_UAA); + ret = 0; +out: + if (pte && !KMAPAREA(mmuar)) + pte_unmap(pte); + local_irq_restore(flags); + return ret; +} + +void __init cf_bootmem_alloc(void) +{ + unsigned long memstart; + + /* _rambase and _ramend will be naturally page aligned */ + m68k_memory[0].addr = _rambase; + m68k_memory[0].size = _ramend - _rambase; + + memblock_add_node(m68k_memory[0].addr, m68k_memory[0].size, 0, + MEMBLOCK_NONE); + + /* compute total pages in system */ + num_pages = PFN_DOWN(_ramend - _rambase); + + /* page numbers */ + memstart = PAGE_ALIGN(_ramstart); + min_low_pfn = PFN_DOWN(_rambase); + max_pfn = max_low_pfn = PFN_DOWN(_ramend); + high_memory = (void *)_ramend; + + /* Reserve kernel text/data/bss */ + memblock_reserve(_rambase, memstart - _rambase); + + m68k_virt_to_node_shift = fls(_ramend - 1) - 6; + module_fixup(NULL, __start_fixup, __stop_fixup); + + /* setup node data */ + m68k_setup_node(0); +} + +/* + * Initialize the context management stuff. + * The following was taken from arch/ppc/mmu_context.c + */ +void __init cf_mmu_context_init(void) +{ + /* + * Some processors have too few contexts to reserve one for + * init_mm, and require using context 0 for a normal task. + * Other processors reserve the use of context zero for the kernel. + * This code assumes FIRST_CONTEXT < 32. + */ + context_map[0] = (1 << FIRST_CONTEXT) - 1; + next_mmu_context = FIRST_CONTEXT; + atomic_set(&nr_free_contexts, LAST_CONTEXT - FIRST_CONTEXT + 1); +} + +/* + * Steal a context from a task that has one at the moment. + * This isn't an LRU system, it just frees up each context in + * turn (sort-of pseudo-random replacement :). This would be the + * place to implement an LRU scheme if anyone was motivated to do it. + * -- paulus + */ +void steal_context(void) +{ + struct mm_struct *mm; + /* + * free up context `next_mmu_context' + * if we shouldn't free context 0, don't... + */ + if (next_mmu_context < FIRST_CONTEXT) + next_mmu_context = FIRST_CONTEXT; + mm = context_mm[next_mmu_context]; + flush_tlb_mm(mm); + destroy_context(mm); +} + +static const pgprot_t protection_map[16] = { + [VM_NONE] = PAGE_NONE, + [VM_READ] = __pgprot(CF_PAGE_VALID | + CF_PAGE_ACCESSED | + CF_PAGE_READABLE), + [VM_WRITE] = __pgprot(CF_PAGE_VALID | + CF_PAGE_ACCESSED | + CF_PAGE_WRITABLE), + [VM_WRITE | VM_READ] = __pgprot(CF_PAGE_VALID | + CF_PAGE_ACCESSED | + CF_PAGE_READABLE | + CF_PAGE_WRITABLE), + [VM_EXEC] = __pgprot(CF_PAGE_VALID | + CF_PAGE_ACCESSED | + CF_PAGE_EXEC), + [VM_EXEC | VM_READ] = __pgprot(CF_PAGE_VALID | + CF_PAGE_ACCESSED | + CF_PAGE_READABLE | + CF_PAGE_EXEC), + [VM_EXEC | VM_WRITE] = __pgprot(CF_PAGE_VALID | + CF_PAGE_ACCESSED | + CF_PAGE_WRITABLE | + CF_PAGE_EXEC), + [VM_EXEC | VM_WRITE | VM_READ] = __pgprot(CF_PAGE_VALID | + CF_PAGE_ACCESSED | + CF_PAGE_READABLE | + CF_PAGE_WRITABLE | + CF_PAGE_EXEC), + [VM_SHARED] = PAGE_NONE, + [VM_SHARED | VM_READ] = __pgprot(CF_PAGE_VALID | + CF_PAGE_ACCESSED | + CF_PAGE_READABLE), + [VM_SHARED | VM_WRITE] = PAGE_SHARED, + [VM_SHARED | VM_WRITE | VM_READ] = __pgprot(CF_PAGE_VALID | + CF_PAGE_ACCESSED | + CF_PAGE_READABLE | + CF_PAGE_SHARED), + [VM_SHARED | VM_EXEC] = __pgprot(CF_PAGE_VALID | + CF_PAGE_ACCESSED | + CF_PAGE_EXEC), + [VM_SHARED | VM_EXEC | VM_READ] = __pgprot(CF_PAGE_VALID | + CF_PAGE_ACCESSED | + CF_PAGE_READABLE | + CF_PAGE_EXEC), + [VM_SHARED | VM_EXEC | VM_WRITE] = __pgprot(CF_PAGE_VALID | + CF_PAGE_ACCESSED | + CF_PAGE_SHARED | + CF_PAGE_EXEC), + [VM_SHARED | VM_EXEC | VM_WRITE | VM_READ] = __pgprot(CF_PAGE_VALID | + CF_PAGE_ACCESSED | + CF_PAGE_READABLE | + CF_PAGE_SHARED | + CF_PAGE_EXEC) +}; +DECLARE_VM_GET_PAGE_PROT diff --git a/arch/m68k/mm/memory.c b/arch/m68k/mm/memory.c new file mode 100644 index 000000000..c2c03b0a1 --- /dev/null +++ b/arch/m68k/mm/memory.c @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * linux/arch/m68k/mm/memory.c + * + * Copyright (C) 1995 Hamish Macdonald + */ + +#include <linux/module.h> +#include <linux/mm.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/init.h> +#include <linux/pagemap.h> +#include <linux/gfp.h> + +#include <asm/setup.h> +#include <asm/page.h> +#include <asm/traps.h> +#include <asm/machdep.h> + + +/* invalidate page in both caches */ +static inline void clear040(unsigned long paddr) +{ + asm volatile ( + "nop\n\t" + ".chip 68040\n\t" + "cinvp %%bc,(%0)\n\t" + ".chip 68k" + : : "a" (paddr)); +} + +/* invalidate page in i-cache */ +static inline void cleari040(unsigned long paddr) +{ + asm volatile ( + "nop\n\t" + ".chip 68040\n\t" + "cinvp %%ic,(%0)\n\t" + ".chip 68k" + : : "a" (paddr)); +} + +/* push page in both caches */ +/* RZ: cpush %bc DOES invalidate %ic, regardless of DPI */ +static inline void push040(unsigned long paddr) +{ + asm volatile ( + "nop\n\t" + ".chip 68040\n\t" + "cpushp %%bc,(%0)\n\t" + ".chip 68k" + : : "a" (paddr)); +} + +/* push and invalidate page in both caches, must disable ints + * to avoid invalidating valid data */ +static inline void pushcl040(unsigned long paddr) +{ + unsigned long flags; + + local_irq_save(flags); + push040(paddr); + if (CPU_IS_060) + clear040(paddr); + local_irq_restore(flags); +} + +/* + * 040: Hit every page containing an address in the range paddr..paddr+len-1. + * (Low order bits of the ea of a CINVP/CPUSHP are "don't care"s). + * Hit every page until there is a page or less to go. Hit the next page, + * and the one after that if the range hits it. + */ +/* ++roman: A little bit more care is required here: The CINVP instruction + * invalidates cache entries WITHOUT WRITING DIRTY DATA BACK! So the beginning + * and the end of the region must be treated differently if they are not + * exactly at the beginning or end of a page boundary. Else, maybe too much + * data becomes invalidated and thus lost forever. CPUSHP does what we need: + * it invalidates the page after pushing dirty data to memory. (Thanks to Jes + * for discovering the problem!) + */ +/* ... but on the '060, CPUSH doesn't invalidate (for us, since we have set + * the DPI bit in the CACR; would it cause problems with temporarily changing + * this?). So we have to push first and then additionally to invalidate. + */ + + +/* + * cache_clear() semantics: Clear any cache entries for the area in question, + * without writing back dirty entries first. This is useful if the data will + * be overwritten anyway, e.g. by DMA to memory. The range is defined by a + * _physical_ address. + */ + +void cache_clear (unsigned long paddr, int len) +{ + if (CPU_IS_COLDFIRE) { + clear_cf_bcache(0, DCACHE_MAX_ADDR); + } else if (CPU_IS_040_OR_060) { + int tmp; + + /* + * We need special treatment for the first page, in case it + * is not page-aligned. Page align the addresses to work + * around bug I17 in the 68060. + */ + if ((tmp = -paddr & (PAGE_SIZE - 1))) { + pushcl040(paddr & PAGE_MASK); + if ((len -= tmp) <= 0) + return; + paddr += tmp; + } + tmp = PAGE_SIZE; + paddr &= PAGE_MASK; + while ((len -= tmp) >= 0) { + clear040(paddr); + paddr += tmp; + } + if ((len += tmp)) + /* a page boundary gets crossed at the end */ + pushcl040(paddr); + } + else /* 68030 or 68020 */ + asm volatile ("movec %/cacr,%/d0\n\t" + "oriw %0,%/d0\n\t" + "movec %/d0,%/cacr" + : : "i" (FLUSH_I_AND_D) + : "d0"); +#ifdef CONFIG_M68K_L2_CACHE + if(mach_l2_flush) + mach_l2_flush(0); +#endif +} +EXPORT_SYMBOL(cache_clear); + + +/* + * cache_push() semantics: Write back any dirty cache data in the given area, + * and invalidate the range in the instruction cache. It needs not (but may) + * invalidate those entries also in the data cache. The range is defined by a + * _physical_ address. + */ + +void cache_push (unsigned long paddr, int len) +{ + if (CPU_IS_COLDFIRE) { + flush_cf_bcache(0, DCACHE_MAX_ADDR); + } else if (CPU_IS_040_OR_060) { + int tmp = PAGE_SIZE; + + /* + * on 68040 or 68060, push cache lines for pages in the range; + * on the '040 this also invalidates the pushed lines, but not on + * the '060! + */ + len += paddr & (PAGE_SIZE - 1); + + /* + * Work around bug I17 in the 68060 affecting some instruction + * lines not being invalidated properly. + */ + paddr &= PAGE_MASK; + + do { + push040(paddr); + paddr += tmp; + } while ((len -= tmp) > 0); + } + /* + * 68030/68020 have no writeback cache. On the other hand, + * cache_push is actually a superset of cache_clear (the lines + * get written back and invalidated), so we should make sure + * to perform the corresponding actions. After all, this is getting + * called in places where we've just loaded code, or whatever, so + * flushing the icache is appropriate; flushing the dcache shouldn't + * be required. + */ + else /* 68030 or 68020 */ + asm volatile ("movec %/cacr,%/d0\n\t" + "oriw %0,%/d0\n\t" + "movec %/d0,%/cacr" + : : "i" (FLUSH_I) + : "d0"); +#ifdef CONFIG_M68K_L2_CACHE + if(mach_l2_flush) + mach_l2_flush(1); +#endif +} +EXPORT_SYMBOL(cache_push); + diff --git a/arch/m68k/mm/motorola.c b/arch/m68k/mm/motorola.c new file mode 100644 index 000000000..b30aa69a7 --- /dev/null +++ b/arch/m68k/mm/motorola.c @@ -0,0 +1,512 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * linux/arch/m68k/mm/motorola.c + * + * Routines specific to the Motorola MMU, originally from: + * linux/arch/m68k/init.c + * which are Copyright (C) 1995 Hamish Macdonald + * + * Moved 8/20/1999 Sam Creasey + */ + +#include <linux/module.h> +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/swap.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/init.h> +#include <linux/memblock.h> +#include <linux/gfp.h> + +#include <asm/setup.h> +#include <linux/uaccess.h> +#include <asm/page.h> +#include <asm/pgalloc.h> +#include <asm/machdep.h> +#include <asm/io.h> +#ifdef CONFIG_ATARI +#include <asm/atari_stram.h> +#endif +#include <asm/sections.h> + +#undef DEBUG + +#ifndef mm_cachebits +/* + * Bits to add to page descriptors for "normal" caching mode. + * For 68020/030 this is 0. + * For 68040, this is _PAGE_CACHE040 (cachable, copyback) + */ +unsigned long mm_cachebits; +EXPORT_SYMBOL(mm_cachebits); +#endif + +/* Prior to calling these routines, the page should have been flushed + * from both the cache and ATC, or the CPU might not notice that the + * cache setting for the page has been changed. -jskov + */ +static inline void nocache_page(void *vaddr) +{ + unsigned long addr = (unsigned long)vaddr; + + if (CPU_IS_040_OR_060) { + pte_t *ptep = virt_to_kpte(addr); + + *ptep = pte_mknocache(*ptep); + } +} + +static inline void cache_page(void *vaddr) +{ + unsigned long addr = (unsigned long)vaddr; + + if (CPU_IS_040_OR_060) { + pte_t *ptep = virt_to_kpte(addr); + + *ptep = pte_mkcache(*ptep); + } +} + +/* + * Motorola 680x0 user's manual recommends using uncached memory for address + * translation tables. + * + * Seeing how the MMU can be external on (some of) these chips, that seems like + * a very important recommendation to follow. Provide some helpers to combat + * 'variation' amongst the users of this. + */ + +void mmu_page_ctor(void *page) +{ + __flush_pages_to_ram(page, 1); + flush_tlb_kernel_page(page); + nocache_page(page); +} + +void mmu_page_dtor(void *page) +{ + cache_page(page); +} + +/* ++andreas: {get,free}_pointer_table rewritten to use unused fields from + struct ptdesc instead of separately kmalloced struct. Stolen from + arch/sparc/mm/srmmu.c ... */ + +typedef struct list_head ptable_desc; + +static struct list_head ptable_list[3] = { + LIST_HEAD_INIT(ptable_list[0]), + LIST_HEAD_INIT(ptable_list[1]), + LIST_HEAD_INIT(ptable_list[2]), +}; + +#define PD_PTABLE(ptdesc) ((ptable_desc *)&(virt_to_ptdesc((void *)(ptdesc))->pt_list)) +#define PD_PTDESC(ptable) (list_entry(ptable, struct ptdesc, pt_list)) +#define PD_MARKBITS(dp) (*(unsigned int *)&PD_PTDESC(dp)->pt_index) + +static const int ptable_shift[3] = { + 7+2, /* PGD */ + 7+2, /* PMD */ + 6+2, /* PTE */ +}; + +#define ptable_size(type) (1U << ptable_shift[type]) +#define ptable_mask(type) ((1U << (PAGE_SIZE / ptable_size(type))) - 1) + +void __init init_pointer_table(void *table, int type) +{ + ptable_desc *dp; + unsigned long ptable = (unsigned long)table; + unsigned long pt_addr = ptable & PAGE_MASK; + unsigned int mask = 1U << ((ptable - pt_addr)/ptable_size(type)); + + dp = PD_PTABLE(pt_addr); + if (!(PD_MARKBITS(dp) & mask)) { + PD_MARKBITS(dp) = ptable_mask(type); + list_add(dp, &ptable_list[type]); + } + + PD_MARKBITS(dp) &= ~mask; + pr_debug("init_pointer_table: %lx, %x\n", ptable, PD_MARKBITS(dp)); + + /* unreserve the ptdesc so it's possible to free that ptdesc */ + __ClearPageReserved(ptdesc_page(PD_PTDESC(dp))); + init_page_count(ptdesc_page(PD_PTDESC(dp))); + + return; +} + +void *get_pointer_table(struct mm_struct *mm, int type) +{ + ptable_desc *dp = ptable_list[type].next; + unsigned int mask = list_empty(&ptable_list[type]) ? 0 : PD_MARKBITS(dp); + unsigned int tmp, off; + + /* + * For a pointer table for a user process address space, a + * table is taken from a ptdesc allocated for the purpose. Each + * ptdesc can hold 8 pointer tables. The ptdesc is remapped in + * virtual address space to be noncacheable. + */ + if (mask == 0) { + struct ptdesc *ptdesc; + ptable_desc *new; + void *pt_addr; + + ptdesc = pagetable_alloc(GFP_KERNEL | __GFP_ZERO, 0); + if (!ptdesc) + return NULL; + + pt_addr = ptdesc_address(ptdesc); + + switch (type) { + case TABLE_PTE: + /* + * m68k doesn't have SPLIT_PTE_PTLOCKS for not having + * SMP. + */ + pagetable_pte_ctor(mm, ptdesc); + break; + case TABLE_PMD: + pagetable_pmd_ctor(mm, ptdesc); + break; + case TABLE_PGD: + pagetable_pgd_ctor(ptdesc); + break; + } + + mmu_page_ctor(pt_addr); + + new = PD_PTABLE(pt_addr); + PD_MARKBITS(new) = ptable_mask(type) - 1; + list_add_tail(new, dp); + + return (pmd_t *)pt_addr; + } + + for (tmp = 1, off = 0; (mask & tmp) == 0; tmp <<= 1, off += ptable_size(type)) + ; + PD_MARKBITS(dp) = mask & ~tmp; + if (!PD_MARKBITS(dp)) { + /* move to end of list */ + list_move_tail(dp, &ptable_list[type]); + } + return ptdesc_address(PD_PTDESC(dp)) + off; +} + +int free_pointer_table(void *table, int type) +{ + ptable_desc *dp; + unsigned long ptable = (unsigned long)table; + unsigned long pt_addr = ptable & PAGE_MASK; + unsigned int mask = 1U << ((ptable - pt_addr)/ptable_size(type)); + + dp = PD_PTABLE(pt_addr); + if (PD_MARKBITS (dp) & mask) + panic ("table already free!"); + + PD_MARKBITS (dp) |= mask; + + if (PD_MARKBITS(dp) == ptable_mask(type)) { + /* all tables in ptdesc are free, free ptdesc */ + list_del(dp); + mmu_page_dtor((void *)pt_addr); + pagetable_dtor_free(virt_to_ptdesc((void *)pt_addr)); + return 1; + } else if (ptable_list[type].next != dp) { + /* + * move this descriptor to the front of the list, since + * it has one or more free tables. + */ + list_move(dp, &ptable_list[type]); + } + return 0; +} + +/* size of memory already mapped in head.S */ +extern __initdata unsigned long m68k_init_mapped_size; + +extern unsigned long availmem; + +static pte_t *last_pte_table __initdata = NULL; + +static pte_t * __init kernel_page_table(void) +{ + pte_t *pte_table = last_pte_table; + + if (PAGE_ALIGNED(last_pte_table)) { + pte_table = memblock_alloc_low(PAGE_SIZE, PAGE_SIZE); + if (!pte_table) { + panic("%s: Failed to allocate %lu bytes align=%lx\n", + __func__, PAGE_SIZE, PAGE_SIZE); + } + + clear_page(pte_table); + mmu_page_ctor(pte_table); + + last_pte_table = pte_table; + } + + last_pte_table += PTRS_PER_PTE; + + return pte_table; +} + +static pmd_t *last_pmd_table __initdata = NULL; + +static pmd_t * __init kernel_ptr_table(void) +{ + if (!last_pmd_table) { + unsigned long pmd, last; + int i; + + /* Find the last ptr table that was used in head.S and + * reuse the remaining space in that page for further + * ptr tables. + */ + last = (unsigned long)kernel_pg_dir; + for (i = 0; i < PTRS_PER_PGD; i++) { + pud_t *pud = (pud_t *)(&kernel_pg_dir[i]); + + if (!pud_present(*pud)) + continue; + pmd = pgd_page_vaddr(kernel_pg_dir[i]); + if (pmd > last) + last = pmd; + } + + last_pmd_table = (pmd_t *)last; +#ifdef DEBUG + printk("kernel_ptr_init: %p\n", last_pmd_table); +#endif + } + + last_pmd_table += PTRS_PER_PMD; + if (PAGE_ALIGNED(last_pmd_table)) { + last_pmd_table = memblock_alloc_low(PAGE_SIZE, PAGE_SIZE); + if (!last_pmd_table) + panic("%s: Failed to allocate %lu bytes align=%lx\n", + __func__, PAGE_SIZE, PAGE_SIZE); + + clear_page(last_pmd_table); + mmu_page_ctor(last_pmd_table); + } + + return last_pmd_table; +} + +static void __init map_node(int node) +{ + unsigned long physaddr, virtaddr, size; + pgd_t *pgd_dir; + p4d_t *p4d_dir; + pud_t *pud_dir; + pmd_t *pmd_dir; + pte_t *pte_dir; + + size = m68k_memory[node].size; + physaddr = m68k_memory[node].addr; + virtaddr = (unsigned long)phys_to_virt(physaddr); + physaddr |= m68k_supervisor_cachemode | + _PAGE_PRESENT | _PAGE_ACCESSED | _PAGE_DIRTY; + if (CPU_IS_040_OR_060) + physaddr |= _PAGE_GLOBAL040; + + while (size > 0) { +#ifdef DEBUG + if (!(virtaddr & (PMD_SIZE-1))) + printk ("\npa=%#lx va=%#lx ", physaddr & PAGE_MASK, + virtaddr); +#endif + pgd_dir = pgd_offset_k(virtaddr); + if (virtaddr && CPU_IS_020_OR_030) { + if (!(virtaddr & (PGDIR_SIZE-1)) && + size >= PGDIR_SIZE) { +#ifdef DEBUG + printk ("[very early term]"); +#endif + pgd_val(*pgd_dir) = physaddr; + size -= PGDIR_SIZE; + virtaddr += PGDIR_SIZE; + physaddr += PGDIR_SIZE; + continue; + } + } + p4d_dir = p4d_offset(pgd_dir, virtaddr); + pud_dir = pud_offset(p4d_dir, virtaddr); + if (!pud_present(*pud_dir)) { + pmd_dir = kernel_ptr_table(); +#ifdef DEBUG + printk ("[new pointer %p]", pmd_dir); +#endif + pud_set(pud_dir, pmd_dir); + } else + pmd_dir = pmd_offset(pud_dir, virtaddr); + + if (CPU_IS_020_OR_030) { + if (virtaddr) { +#ifdef DEBUG + printk ("[early term]"); +#endif + pmd_val(*pmd_dir) = physaddr; + physaddr += PMD_SIZE; + } else { + int i; +#ifdef DEBUG + printk ("[zero map]"); +#endif + pte_dir = kernel_page_table(); + pmd_set(pmd_dir, pte_dir); + + pte_val(*pte_dir++) = 0; + physaddr += PAGE_SIZE; + for (i = 1; i < PTRS_PER_PTE; physaddr += PAGE_SIZE, i++) + pte_val(*pte_dir++) = physaddr; + } + size -= PMD_SIZE; + virtaddr += PMD_SIZE; + } else { + if (!pmd_present(*pmd_dir)) { +#ifdef DEBUG + printk ("[new table]"); +#endif + pte_dir = kernel_page_table(); + pmd_set(pmd_dir, pte_dir); + } + pte_dir = pte_offset_kernel(pmd_dir, virtaddr); + + if (virtaddr) { + if (!pte_present(*pte_dir)) + pte_val(*pte_dir) = physaddr; + } else + pte_val(*pte_dir) = 0; + size -= PAGE_SIZE; + virtaddr += PAGE_SIZE; + physaddr += PAGE_SIZE; + } + + } +#ifdef DEBUG + printk("\n"); +#endif +} + +/* + * Alternate definitions that are compile time constants, for + * initializing protection_map. The cachebits are fixed later. + */ +#define PAGE_NONE_C __pgprot(_PAGE_PROTNONE | _PAGE_ACCESSED) +#define PAGE_SHARED_C __pgprot(_PAGE_PRESENT | _PAGE_ACCESSED) +#define PAGE_COPY_C __pgprot(_PAGE_PRESENT | _PAGE_RONLY | _PAGE_ACCESSED) +#define PAGE_READONLY_C __pgprot(_PAGE_PRESENT | _PAGE_RONLY | _PAGE_ACCESSED) + +static pgprot_t protection_map[16] __ro_after_init = { + [VM_NONE] = PAGE_NONE_C, + [VM_READ] = PAGE_READONLY_C, + [VM_WRITE] = PAGE_COPY_C, + [VM_WRITE | VM_READ] = PAGE_COPY_C, + [VM_EXEC] = PAGE_READONLY_C, + [VM_EXEC | VM_READ] = PAGE_READONLY_C, + [VM_EXEC | VM_WRITE] = PAGE_COPY_C, + [VM_EXEC | VM_WRITE | VM_READ] = PAGE_COPY_C, + [VM_SHARED] = PAGE_NONE_C, + [VM_SHARED | VM_READ] = PAGE_READONLY_C, + [VM_SHARED | VM_WRITE] = PAGE_SHARED_C, + [VM_SHARED | VM_WRITE | VM_READ] = PAGE_SHARED_C, + [VM_SHARED | VM_EXEC] = PAGE_READONLY_C, + [VM_SHARED | VM_EXEC | VM_READ] = PAGE_READONLY_C, + [VM_SHARED | VM_EXEC | VM_WRITE] = PAGE_SHARED_C, + [VM_SHARED | VM_EXEC | VM_WRITE | VM_READ] = PAGE_SHARED_C +}; +DECLARE_VM_GET_PAGE_PROT + +/* + * paging_init() continues the virtual memory environment setup which + * was begun by the code in arch/head.S. + */ +void __init paging_init(void) +{ + unsigned long min_addr, max_addr; + unsigned long addr; + int i; + +#ifdef DEBUG + printk ("start of paging_init (%p, %lx)\n", kernel_pg_dir, availmem); +#endif + + /* Fix the cache mode in the page descriptors for the 680[46]0. */ + if (CPU_IS_040_OR_060) { + int i; +#ifndef mm_cachebits + mm_cachebits = _PAGE_CACHE040; +#endif + for (i = 0; i < 16; i++) + pgprot_val(protection_map[i]) |= _PAGE_CACHE040; + } + + min_addr = m68k_memory[0].addr; + max_addr = min_addr + m68k_memory[0].size - 1; + memblock_add_node(m68k_memory[0].addr, m68k_memory[0].size, 0, + MEMBLOCK_NONE); + for (i = 1; i < m68k_num_memory;) { + if (m68k_memory[i].addr < min_addr) { + printk("Ignoring memory chunk at 0x%lx:0x%lx before the first chunk\n", + m68k_memory[i].addr, m68k_memory[i].size); + printk("Fix your bootloader or use a memfile to make use of this area!\n"); + m68k_num_memory--; + memmove(m68k_memory + i, m68k_memory + i + 1, + (m68k_num_memory - i) * sizeof(struct m68k_mem_info)); + continue; + } + memblock_add_node(m68k_memory[i].addr, m68k_memory[i].size, i, + MEMBLOCK_NONE); + addr = m68k_memory[i].addr + m68k_memory[i].size - 1; + if (addr > max_addr) + max_addr = addr; + i++; + } + m68k_memoffset = min_addr - PAGE_OFFSET; + m68k_virt_to_node_shift = fls(max_addr - min_addr) - 6; + + module_fixup(NULL, __start_fixup, __stop_fixup); + flush_icache(); + + high_memory = phys_to_virt(max_addr) + 1; + + min_low_pfn = availmem >> PAGE_SHIFT; + max_pfn = max_low_pfn = (max_addr >> PAGE_SHIFT) + 1; + + /* Reserve kernel text/data/bss and the memory allocated in head.S */ + memblock_reserve(m68k_memory[0].addr, availmem - m68k_memory[0].addr); + + /* + * Map the physical memory available into the kernel virtual + * address space. Make sure memblock will not try to allocate + * pages beyond the memory we already mapped in head.S + */ + memblock_set_bottom_up(true); + + for (i = 0; i < m68k_num_memory; i++) { + m68k_setup_node(i); + map_node(i); + } + + flush_tlb_all(); + + early_memtest(min_addr, max_addr); + + /* + * Set up SFC/DFC registers + */ + set_fc(USER_DATA); + +#ifdef DEBUG + printk ("before node_set_state\n"); +#endif + for (i = 0; i < m68k_num_memory; i++) + if (node_present_pages(i)) + node_set_state(i, N_NORMAL_MEMORY); +} diff --git a/arch/m68k/mm/sun3kmap.c b/arch/m68k/mm/sun3kmap.c new file mode 100644 index 000000000..ac091892d --- /dev/null +++ b/arch/m68k/mm/sun3kmap.c @@ -0,0 +1,159 @@ +/* + * linux/arch/m68k/mm/sun3kmap.c + * + * Copyright (C) 2002 Sam Creasey <sammy@sammy.net> + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive + * for more details. + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/vmalloc.h> + +#include <asm/page.h> +#include <asm/io.h> +#include <asm/sun3mmu.h> + +#include "../sun3/sun3.h" + +#undef SUN3_KMAP_DEBUG + +extern void mmu_emu_map_pmeg (int context, int vaddr); + +static inline void do_page_mapin(unsigned long phys, unsigned long virt, + unsigned long type) +{ + unsigned long pte; + pte_t ptep; + + ptep = pfn_pte(phys >> PAGE_SHIFT, PAGE_KERNEL); + pte = pte_val(ptep); + pte |= type; + + sun3_put_pte(virt, pte); + +#ifdef SUN3_KMAP_DEBUG + pr_info("mapin:"); + print_pte_vaddr(virt); +#endif + +} + +static inline void do_pmeg_mapin(unsigned long phys, unsigned long virt, + unsigned long type, int pages) +{ + + if(sun3_get_segmap(virt & ~SUN3_PMEG_MASK) == SUN3_INVALID_PMEG) + mmu_emu_map_pmeg(sun3_get_context(), virt); + + while(pages) { + do_page_mapin(phys, virt, type); + phys += PAGE_SIZE; + virt += PAGE_SIZE; + pages--; + } +} + +void __iomem *sun3_ioremap(unsigned long phys, unsigned long size, + unsigned long type) +{ + struct vm_struct *area; + unsigned long offset, virt, ret; + int pages; + + if(!size) + return NULL; + + /* page align */ + offset = phys & (PAGE_SIZE-1); + phys &= ~(PAGE_SIZE-1); + + size += offset; + size = PAGE_ALIGN(size); + if((area = get_vm_area(size, VM_IOREMAP)) == NULL) + return NULL; + +#ifdef SUN3_KMAP_DEBUG + pr_info("ioremap: got virt %p size %lx(%lx)\n", area->addr, size, + area->size); +#endif + + pages = size / PAGE_SIZE; + virt = (unsigned long)area->addr; + ret = virt + offset; + + while(pages) { + int seg_pages; + + seg_pages = (SUN3_PMEG_SIZE - (virt & SUN3_PMEG_MASK)) / PAGE_SIZE; + if(seg_pages > pages) + seg_pages = pages; + + do_pmeg_mapin(phys, virt, type, seg_pages); + + pages -= seg_pages; + phys += seg_pages * PAGE_SIZE; + virt += seg_pages * PAGE_SIZE; + } + + return (void __iomem *)ret; + +} +EXPORT_SYMBOL(sun3_ioremap); + + +void __iomem *__ioremap(unsigned long phys, unsigned long size, int cache) +{ + + return sun3_ioremap(phys, size, SUN3_PAGE_TYPE_IO); + +} +EXPORT_SYMBOL(__ioremap); + +void iounmap(void __iomem *addr) +{ + vfree((void *)(PAGE_MASK & (unsigned long)addr)); +} +EXPORT_SYMBOL(iounmap); + +/* sun3_map_test(addr, val) -- Reads a byte from addr, storing to val, + * trapping the potential read fault. Returns 0 if the access faulted, + * 1 on success. + * + * This function is primarily used to check addresses on the VME bus. + * + * Mucking with the page fault handler seems a little hackish to me, but + * SunOS, NetBSD, and Mach all implemented this check in such a manner, + * so I figure we're allowed. + */ +int sun3_map_test(unsigned long addr, char *val) +{ + int ret = 0; + + __asm__ __volatile__ + (".globl _sun3_map_test_start\n" + "_sun3_map_test_start:\n" + "1: moveb (%2), (%0)\n" + " moveq #1, %1\n" + "2:\n" + ".section .fixup,\"ax\"\n" + ".even\n" + "3: moveq #0, %1\n" + " jmp 2b\n" + ".previous\n" + ".section __ex_table,\"a\"\n" + ".align 4\n" + ".long 1b,3b\n" + ".previous\n" + ".globl _sun3_map_test_end\n" + "_sun3_map_test_end:\n" + : "=a"(val), "=r"(ret) + : "a"(addr)); + + return ret; +} +EXPORT_SYMBOL(sun3_map_test); diff --git a/arch/m68k/mm/sun3mmu.c b/arch/m68k/mm/sun3mmu.c new file mode 100644 index 000000000..f139cc157 --- /dev/null +++ b/arch/m68k/mm/sun3mmu.c @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * linux/arch/m68k/mm/sun3mmu.c + * + * Implementations of mm routines specific to the sun3 MMU. + * + * Moved here 8/20/1999 Sam Creasey + * + */ + +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/swap.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/init.h> +#include <linux/memblock.h> + +#include <asm/setup.h> +#include <linux/uaccess.h> +#include <asm/page.h> +#include <asm/machdep.h> +#include <asm/io.h> + +#include "../sun3/sun3.h" + +const char bad_pmd_string[] = "Bad pmd in pte_alloc: %08lx\n"; + +extern unsigned long num_pages; + +/* For the sun3 we try to follow the i386 paging_init() more closely */ +/* start_mem and end_mem have PAGE_OFFSET added already */ +/* now sets up tables using sun3 PTEs rather than i386 as before. --m */ +void __init paging_init(void) +{ + pgd_t * pg_dir; + pte_t * pg_table; + int i; + unsigned long address; + unsigned long next_pgtable; + unsigned long bootmem_end; + unsigned long size; + + address = PAGE_OFFSET; + pg_dir = swapper_pg_dir; + memset (swapper_pg_dir, 0, sizeof (swapper_pg_dir)); + memset (kernel_pg_dir, 0, sizeof (kernel_pg_dir)); + + size = num_pages * sizeof(pte_t); + size = (size + PAGE_SIZE) & ~(PAGE_SIZE-1); + + next_pgtable = (unsigned long)memblock_alloc_or_panic(size, PAGE_SIZE); + bootmem_end = (next_pgtable + size + PAGE_SIZE) & PAGE_MASK; + + /* Map whole memory from PAGE_OFFSET (0x0E000000) */ + pg_dir += PAGE_OFFSET >> PGDIR_SHIFT; + + while (address < (unsigned long)high_memory) { + pg_table = (pte_t *) __pa (next_pgtable); + next_pgtable += PTRS_PER_PTE * sizeof (pte_t); + pgd_val(*pg_dir) = (unsigned long) pg_table; + pg_dir++; + + /* now change pg_table to kernel virtual addresses */ + pg_table = (pte_t *) __va ((unsigned long) pg_table); + for (i=0; i<PTRS_PER_PTE; ++i, ++pg_table) { + pte_t pte = pfn_pte(virt_to_pfn((void *)address), PAGE_INIT); + if (address >= (unsigned long)high_memory) + pte_val (pte) = 0; + set_pte (pg_table, pte); + address += PAGE_SIZE; + } + } + + mmu_emu_init(bootmem_end); + + current->mm = NULL; +} + +static const pgprot_t protection_map[16] = { + [VM_NONE] = PAGE_NONE, + [VM_READ] = PAGE_READONLY, + [VM_WRITE] = PAGE_COPY, + [VM_WRITE | VM_READ] = PAGE_COPY, + [VM_EXEC] = PAGE_READONLY, + [VM_EXEC | VM_READ] = PAGE_READONLY, + [VM_EXEC | VM_WRITE] = PAGE_COPY, + [VM_EXEC | VM_WRITE | VM_READ] = PAGE_COPY, + [VM_SHARED] = PAGE_NONE, + [VM_SHARED | VM_READ] = PAGE_READONLY, + [VM_SHARED | VM_WRITE] = PAGE_SHARED, + [VM_SHARED | VM_WRITE | VM_READ] = PAGE_SHARED, + [VM_SHARED | VM_EXEC] = PAGE_READONLY, + [VM_SHARED | VM_EXEC | VM_READ] = PAGE_READONLY, + [VM_SHARED | VM_EXEC | VM_WRITE] = PAGE_SHARED, + [VM_SHARED | VM_EXEC | VM_WRITE | VM_READ] = PAGE_SHARED +}; +DECLARE_VM_GET_PAGE_PROT |
