diff options
Diffstat (limited to 'arch/m68k/mm')
-rw-r--r-- | arch/m68k/mm/Makefile | 8 | ||||
-rw-r--r-- | arch/m68k/mm/fault.c | 219 | ||||
-rw-r--r-- | arch/m68k/mm/hwtest.c | 85 | ||||
-rw-r--r-- | arch/m68k/mm/init.c | 147 | ||||
-rw-r--r-- | arch/m68k/mm/kmap.c | 361 | ||||
-rw-r--r-- | arch/m68k/mm/memory.c | 471 | ||||
-rw-r--r-- | arch/m68k/mm/motorola.c | 285 | ||||
-rw-r--r-- | arch/m68k/mm/sun3kmap.c | 156 | ||||
-rw-r--r-- | arch/m68k/mm/sun3mmu.c | 102 |
9 files changed, 1834 insertions, 0 deletions
diff --git a/arch/m68k/mm/Makefile b/arch/m68k/mm/Makefile new file mode 100644 index 00000000000..90f1c735c11 --- /dev/null +++ b/arch/m68k/mm/Makefile @@ -0,0 +1,8 @@ +# +# Makefile for the linux m68k-specific parts of the memory manager. +# + +obj-y := init.o fault.o hwtest.o + +obj-$(CONFIG_MMU_MOTOROLA) += kmap.o memory.o motorola.o +obj-$(CONFIG_MMU_SUN3) += sun3kmap.o sun3mmu.o diff --git a/arch/m68k/mm/fault.c b/arch/m68k/mm/fault.c new file mode 100644 index 00000000000..ac48b6d2aff --- /dev/null +++ b/arch/m68k/mm/fault.c @@ -0,0 +1,219 @@ +/* + * 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 <asm/setup.h> +#include <asm/traps.h> +#include <asm/system.h> +#include <asm/uaccess.h> +#include <asm/pgalloc.h> + +extern void die_if_kernel(char *, struct pt_regs *, long); +extern const int frame_extra_sizes[]; /* in m68k/kernel/signal.c */ + +int send_fault_sig(struct pt_regs *regs) +{ + siginfo_t siginfo = { 0, 0, 0, }; + + siginfo.si_signo = current->thread.signo; + siginfo.si_code = current->thread.code; + siginfo.si_addr = (void *)current->thread.faddr; +#ifdef DEBUG + printk("send_fault_sig: %p,%d,%d\n", siginfo.si_addr, siginfo.si_signo, siginfo.si_code); +#endif + + if (user_mode(regs)) { + force_sig_info(siginfo.si_signo, + &siginfo, current); + } else { + const struct exception_table_entry *fixup; + + /* Are we prepared to handle this kernel fault? */ + if ((fixup = search_exception_tables(regs->pc))) { + struct pt_regs *tregs; + /* Create a new four word stack frame, discarding the old + one. */ + regs->stkadj = frame_extra_sizes[regs->format]; + tregs = (struct pt_regs *)((ulong)regs + regs->stkadj); + tregs->vector = regs->vector; + tregs->format = 0; + tregs->pc = fixup->fixup; + tregs->sr = regs->sr; + return -1; + } + + //if (siginfo.si_signo == SIGBUS) + // force_sig_info(siginfo.si_signo, + // &siginfo, current); + + /* + * Oops. The kernel tried to access some bad page. We'll have to + * terminate things with extreme prejudice. + */ + if ((unsigned long)siginfo.si_addr < PAGE_SIZE) + printk(KERN_ALERT "Unable to handle kernel NULL pointer dereference"); + else + printk(KERN_ALERT "Unable to handle kernel access"); + printk(" at virtual address %p\n", siginfo.si_addr); + die_if_kernel("Oops", regs, 0 /*error_code*/); + do_exit(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; + int write, fault; + +#ifdef DEBUG + printk ("do page fault:\nregs->sr=%#x, regs->pc=%#lx, address=%#lx, %ld, %p\n", + regs->sr, regs->pc, address, error_code, + current->mm->pgd); +#endif + + /* + * If we're in an interrupt or have no user + * context, we must not take the fault.. + */ + if (in_interrupt() || !mm) + goto no_context; + + down_read(&mm->mmap_sem); + + vma = find_vma(mm, address); + if (!vma) + goto map_err; + if (vma->vm_flags & VM_IO) + goto acc_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; + } + if (expand_stack(vma, address)) + goto map_err; + +/* + * Ok, we have a good vm_area for this memory access, so + * we can handle it.. + */ +good_area: +#ifdef DEBUG + printk("do_page_fault: good_area\n"); +#endif + write = 0; + switch (error_code & 3) { + default: /* 3: write, present */ + /* fall through */ + case 2: /* write, not present */ + if (!(vma->vm_flags & VM_WRITE)) + goto acc_err; + write++; + break; + case 1: /* read, present */ + goto acc_err; + case 0: /* read, not present */ + if (!(vma->vm_flags & (VM_READ | VM_EXEC))) + 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. + */ + + survive: + fault = handle_mm_fault(mm, vma, address, write); +#ifdef DEBUG + printk("handle_mm_fault returns %d\n",fault); +#endif + switch (fault) { + case 1: + current->min_flt++; + break; + case 2: + current->maj_flt++; + break; + case 0: + goto bus_err; + default: + goto out_of_memory; + } + + up_read(&mm->mmap_sem); + 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: + up_read(&mm->mmap_sem); + if (current->pid == 1) { + yield(); + down_read(&mm->mmap_sem); + goto survive; + } + + printk("VM: killing process %s\n", current->comm); + if (user_mode(regs)) + do_exit(SIGKILL); + +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: + current->thread.signo = SIGSEGV; + current->thread.code = SEGV_MAPERR; + current->thread.faddr = address; + goto send_sig; + +acc_err: + current->thread.signo = SIGSEGV; + current->thread.code = SEGV_ACCERR; + current->thread.faddr = address; + +send_sig: + up_read(&mm->mmap_sem); + return send_fault_sig(regs); +} diff --git a/arch/m68k/mm/hwtest.c b/arch/m68k/mm/hwtest.c new file mode 100644 index 00000000000..2c7dde3c643 --- /dev/null +++ b/arch/m68k/mm/hwtest.c @@ -0,0 +1,85 @@ +/* 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> + +int hwreg_present( volatile void *regp ) +{ + int ret = 0; + long save_sp, save_vbr; + long tmp_vectors[3]; + + __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) + ); + + 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; + long save_sp, save_vbr; + long tmp_vectors[3]; + + __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) + ); + + 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 00000000000..c45beb95594 --- /dev/null +++ b/arch/m68k/mm/init.c @@ -0,0 +1,147 @@ +/* + * 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/config.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/bootmem.h> + +#include <asm/setup.h> +#include <asm/uaccess.h> +#include <asm/page.h> +#include <asm/pgalloc.h> +#include <asm/system.h> +#include <asm/machdep.h> +#include <asm/io.h> +#ifdef CONFIG_ATARI +#include <asm/atari_stram.h> +#endif +#include <asm/tlb.h> + +DEFINE_PER_CPU(struct mmu_gather, mmu_gathers); + +/* + * ZERO_PAGE is a special page that is used for zero-initialized + * data and COW. + */ + +void *empty_zero_page; + +void show_mem(void) +{ + unsigned long i; + int free = 0, total = 0, reserved = 0, shared = 0; + int cached = 0; + + printk("\nMem-info:\n"); + show_free_areas(); + printk("Free swap: %6ldkB\n", nr_swap_pages<<(PAGE_SHIFT-10)); + i = max_mapnr; + while (i-- > 0) { + total++; + if (PageReserved(mem_map+i)) + reserved++; + else if (PageSwapCache(mem_map+i)) + cached++; + else if (!page_count(mem_map+i)) + free++; + else + shared += page_count(mem_map+i) - 1; + } + printk("%d pages of RAM\n",total); + printk("%d free pages\n",free); + printk("%d reserved pages\n",reserved); + printk("%d pages shared\n",shared); + printk("%d pages swap cached\n",cached); +} + +extern void init_pointer_table(unsigned long ptable); + +/* References to section boundaries */ + +extern char _text, _etext, _edata, __bss_start, _end; +extern char __init_begin, __init_end; + +extern pmd_t *zero_pgtable; + +void __init mem_init(void) +{ + int codepages = 0; + int datapages = 0; + int initpages = 0; + unsigned long tmp; +#ifndef CONFIG_SUN3 + int i; +#endif + + max_mapnr = num_physpages = (((unsigned long)high_memory - PAGE_OFFSET) >> PAGE_SHIFT); + +#ifdef CONFIG_ATARI + if (MACH_IS_ATARI) + atari_stram_mem_init_hook(); +#endif + + /* this will put all memory onto the freelists */ + totalram_pages = free_all_bootmem(); + + for (tmp = PAGE_OFFSET ; tmp < (unsigned long)high_memory; tmp += PAGE_SIZE) { + if (PageReserved(virt_to_page(tmp))) { + if (tmp >= (unsigned long)&_text + && tmp < (unsigned long)&_etext) + codepages++; + else if (tmp >= (unsigned long) &__init_begin + && tmp < (unsigned long) &__init_end) + initpages++; + else + datapages++; + continue; + } + } + +#ifndef CONFIG_SUN3 + /* insert pointer tables allocated so far into the tablelist */ + init_pointer_table((unsigned long)kernel_pg_dir); + for (i = 0; i < PTRS_PER_PGD; i++) { + if (pgd_present(kernel_pg_dir[i])) + init_pointer_table(__pgd_page(kernel_pg_dir[i])); + } + + /* insert also pointer table that we used to unmap the zero page */ + if (zero_pgtable) + init_pointer_table((unsigned long)zero_pgtable); +#endif + + printk("Memory: %luk/%luk available (%dk kernel code, %dk data, %dk init)\n", + (unsigned long)nr_free_pages() << (PAGE_SHIFT-10), + max_mapnr << (PAGE_SHIFT-10), + codepages << (PAGE_SHIFT-10), + datapages << (PAGE_SHIFT-10), + initpages << (PAGE_SHIFT-10)); +} + +#ifdef CONFIG_BLK_DEV_INITRD +void free_initrd_mem(unsigned long start, unsigned long end) +{ + int pages = 0; + for (; start < end; start += PAGE_SIZE) { + ClearPageReserved(virt_to_page(start)); + set_page_count(virt_to_page(start), 1); + free_page(start); + totalram_pages++; + pages++; + } + printk ("Freeing initrd memory: %dk freed\n", pages); +} +#endif diff --git a/arch/m68k/mm/kmap.c b/arch/m68k/mm/kmap.c new file mode 100644 index 00000000000..5dcb3fa35ea --- /dev/null +++ b/arch/m68k/mm/kmap.c @@ -0,0 +1,361 @@ +/* + * 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/config.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/segment.h> +#include <asm/page.h> +#include <asm/pgalloc.h> +#include <asm/io.h> +#include <asm/system.h> + +#undef DEBUG + +#define PTRTREESIZE (256*1024) + +/* + * For 040/060 we can use the virtual memory area like other architectures, + * but for 020/030 we want to use early termination page descriptor and we + * can't mix this with normal page descriptors, so we have to copy that code + * (mm/vmalloc.c) and return appriorate 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 (256*1024) + +static struct vm_struct *iolist; + +static struct vm_struct *get_io_area(unsigned long size) +{ + unsigned long addr; + struct vm_struct **p, *tmp, *area; + + area = (struct vm_struct *)kmalloc(sizeof(*area), GFP_KERNEL); + 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) + 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; + __iounmap(tmp->addr, tmp->size); + kfree(tmp); + return; + } + } +} + +#endif + +/* + * Map some physical address range into the kernel address space. The + * code is copied and adapted from map_chunk(). + */ +/* Rewritten by Andreas Schwab to remove all races. */ + +void *__ioremap(unsigned long physaddr, unsigned long size, int cacheflag) +{ + struct vm_struct *area; + unsigned long virtaddr, retaddr; + long offset; + pgd_t *pgd_dir; + pmd_t *pmd_dir; + pte_t *pte_dir; + + /* + * Don't allow mappings that wrap.. + */ + if (!size || size > physaddr + size) + return NULL; + +#ifdef CONFIG_AMIGA + if (MACH_IS_AMIGA) { + if ((physaddr >= 0x40000000) && (physaddr + size < 0x60000000) + && (cacheflag == IOMAP_NOCACHE_SER)) + return (void *)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); + 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 & (PTRTREESIZE-1))) + printk ("\npa=%#lx va=%#lx ", physaddr, virtaddr); +#endif + pgd_dir = pgd_offset_k(virtaddr); + pmd_dir = pmd_alloc(&init_mm, pgd_dir, virtaddr); + if (!pmd_dir) { + printk("ioremap: no mem for pmd_dir\n"); + return NULL; + } + + if (CPU_IS_020_OR_030) { + pmd_dir->pmd[(virtaddr/PTRTREESIZE) & 15] = physaddr; + physaddr += PTRTREESIZE; + virtaddr += PTRTREESIZE; + size -= PTRTREESIZE; + } else { + pte_dir = pte_alloc_kernel(&init_mm, 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 *)retaddr; +} + +/* + * Unmap a ioremap()ed region again + */ +void iounmap(void *addr) +{ +#ifdef CONFIG_AMIGA + if ((!MACH_IS_AMIGA) || + (((unsigned long)addr < 0x40000000) || + ((unsigned long)addr > 0x60000000))) + free_io_area(addr); +#else + free_io_area(addr); +#endif +} + +/* + * __iounmap unmaps nearly everything, so be careful + * it doesn't free currently pointer/page tables anymore but it + * wans't used anyway and might be added later. + */ +void __iounmap(void *addr, unsigned long size) +{ + unsigned long virtaddr = (unsigned long)addr; + pgd_t *pgd_dir; + pmd_t *pmd_dir; + pte_t *pte_dir; + + while ((long)size > 0) { + pgd_dir = pgd_offset_k(virtaddr); + if (pgd_bad(*pgd_dir)) { + printk("iounmap: bad pgd(%08lx)\n", pgd_val(*pgd_dir)); + pgd_clear(pgd_dir); + return; + } + pmd_dir = pmd_offset(pgd_dir, virtaddr); + + if (CPU_IS_020_OR_030) { + int pmd_off = (virtaddr/PTRTREESIZE) & 15; + + if ((pmd_dir->pmd[pmd_off] & _DESCTYPE_MASK) == _PAGE_PRESENT) { + pmd_dir->pmd[pmd_off] = 0; + virtaddr += PTRTREESIZE; + size -= PTRTREESIZE; + continue; + } + } + + 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(); +} + +/* + * 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; + 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); + if (pgd_bad(*pgd_dir)) { + printk("iocachemode: bad pgd(%08lx)\n", pgd_val(*pgd_dir)); + pgd_clear(pgd_dir); + return; + } + pmd_dir = pmd_offset(pgd_dir, virtaddr); + + if (CPU_IS_020_OR_030) { + int pmd_off = (virtaddr/PTRTREESIZE) & 15; + + if ((pmd_dir->pmd[pmd_off] & _DESCTYPE_MASK) == _PAGE_PRESENT) { + pmd_dir->pmd[pmd_off] = (pmd_dir->pmd[pmd_off] & + _CACHEMASK040) | cmode; + virtaddr += PTRTREESIZE; + size -= PTRTREESIZE; + continue; + } + } + + 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(); +} diff --git a/arch/m68k/mm/memory.c b/arch/m68k/mm/memory.c new file mode 100644 index 00000000000..1453a601372 --- /dev/null +++ b/arch/m68k/mm/memory.c @@ -0,0 +1,471 @@ +/* + * linux/arch/m68k/mm/memory.c + * + * Copyright (C) 1995 Hamish Macdonald + */ + +#include <linux/config.h> +#include <linux/mm.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/pagemap.h> + +#include <asm/setup.h> +#include <asm/segment.h> +#include <asm/page.h> +#include <asm/pgalloc.h> +#include <asm/system.h> +#include <asm/traps.h> +#include <asm/machdep.h> + + +/* ++andreas: {get,free}_pointer_table rewritten to use unused fields from + struct page instead of separately kmalloced struct. Stolen from + arch/sparc/mm/srmmu.c ... */ + +typedef struct list_head ptable_desc; +static LIST_HEAD(ptable_list); + +#define PD_PTABLE(page) ((ptable_desc *)&(virt_to_page(page)->lru)) +#define PD_PAGE(ptable) (list_entry(ptable, struct page, lru)) +#define PD_MARKBITS(dp) (*(unsigned char *)&PD_PAGE(dp)->index) + +#define PTABLE_SIZE (PTRS_PER_PMD * sizeof(pmd_t)) + +void __init init_pointer_table(unsigned long ptable) +{ + ptable_desc *dp; + unsigned long page = ptable & PAGE_MASK; + unsigned char mask = 1 << ((ptable - page)/PTABLE_SIZE); + + dp = PD_PTABLE(page); + if (!(PD_MARKBITS(dp) & mask)) { + PD_MARKBITS(dp) = 0xff; + list_add(dp, &ptable_list); + } + + PD_MARKBITS(dp) &= ~mask; +#ifdef DEBUG + printk("init_pointer_table: %lx, %x\n", ptable, PD_MARKBITS(dp)); +#endif + + /* unreserve the page so it's possible to free that page */ + PD_PAGE(dp)->flags &= ~(1 << PG_reserved); + set_page_count(PD_PAGE(dp), 1); + + return; +} + +pmd_t *get_pointer_table (void) +{ + ptable_desc *dp = ptable_list.next; + unsigned char mask = PD_MARKBITS (dp); + unsigned char tmp; + unsigned int off; + + /* + * For a pointer table for a user process address space, a + * table is taken from a page allocated for the purpose. Each + * page can hold 8 pointer tables. The page is remapped in + * virtual address space to be noncacheable. + */ + if (mask == 0) { + void *page; + ptable_desc *new; + + if (!(page = (void *)get_zeroed_page(GFP_KERNEL))) + return NULL; + + flush_tlb_kernel_page(page); + nocache_page(page); + + new = PD_PTABLE(page); + PD_MARKBITS(new) = 0xfe; + list_add_tail(new, dp); + + return (pmd_t *)page; + } + + for (tmp = 1, off = 0; (mask & tmp) == 0; tmp <<= 1, off += PTABLE_SIZE) + ; + PD_MARKBITS(dp) = mask & ~tmp; + if (!PD_MARKBITS(dp)) { + /* move to end of list */ + list_del(dp); + list_add_tail(dp, &ptable_list); + } + return (pmd_t *) (page_address(PD_PAGE(dp)) + off); +} + +int free_pointer_table (pmd_t *ptable) +{ + ptable_desc *dp; + unsigned long page = (unsigned long)ptable & PAGE_MASK; + unsigned char mask = 1 << (((unsigned long)ptable - page)/PTABLE_SIZE); + + dp = PD_PTABLE(page); + if (PD_MARKBITS (dp) & mask) + panic ("table already free!"); + + PD_MARKBITS (dp) |= mask; + + if (PD_MARKBITS(dp) == 0xff) { + /* all tables in page are free, free page */ + list_del(dp); + cache_page((void *)page); + free_page (page); + return 1; + } else if (ptable_list.next != dp) { + /* + * move this descriptor to the front of the list, since + * it has one or more free tables. + */ + list_del(dp); + list_add(dp, &ptable_list); + } + return 0; +} + +#ifdef DEBUG_INVALID_PTOV +int mm_inv_cnt = 5; +#endif + +#ifndef CONFIG_SINGLE_MEMORY_CHUNK +/* + * The following two routines map from a physical address to a kernel + * virtual address and vice versa. + */ +unsigned long mm_vtop(unsigned long vaddr) +{ + int i=0; + unsigned long voff = (unsigned long)vaddr - PAGE_OFFSET; + + do { + if (voff < m68k_memory[i].size) { +#ifdef DEBUGPV + printk ("VTOP(%p)=%lx\n", vaddr, + m68k_memory[i].addr + voff); +#endif + return m68k_memory[i].addr + voff; + } + voff -= m68k_memory[i].size; + } while (++i < m68k_num_memory); + + /* As a special case allow `__pa(high_memory)'. */ + if (voff == 0) + return m68k_memory[i-1].addr + m68k_memory[i-1].size; + + return -1; +} +#endif + +#ifndef CONFIG_SINGLE_MEMORY_CHUNK +unsigned long mm_ptov (unsigned long paddr) +{ + int i = 0; + unsigned long poff, voff = PAGE_OFFSET; + + do { + poff = paddr - m68k_memory[i].addr; + if (poff < m68k_memory[i].size) { +#ifdef DEBUGPV + printk ("PTOV(%lx)=%lx\n", paddr, poff + voff); +#endif + return poff + voff; + } + voff += m68k_memory[i].size; + } while (++i < m68k_num_memory); + +#ifdef DEBUG_INVALID_PTOV + if (mm_inv_cnt > 0) { + mm_inv_cnt--; + printk("Invalid use of phys_to_virt(0x%lx) at 0x%p!\n", + paddr, __builtin_return_address(0)); + } +#endif + return -1; +} +#endif + +/* 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_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 +} + + +/* + * 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_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); + |