diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-17 02:20:36 +0400 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-17 02:20:36 +0400 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /arch/sparc/mm | |
download | linux-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.tar.xz |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'arch/sparc/mm')
-rw-r--r-- | arch/sparc/mm/Makefile | 23 | ||||
-rw-r--r-- | arch/sparc/mm/btfixup.c | 336 | ||||
-rw-r--r-- | arch/sparc/mm/extable.c | 77 | ||||
-rw-r--r-- | arch/sparc/mm/fault.c | 596 | ||||
-rw-r--r-- | arch/sparc/mm/generic.c | 154 | ||||
-rw-r--r-- | arch/sparc/mm/highmem.c | 120 | ||||
-rw-r--r-- | arch/sparc/mm/hypersparc.S | 413 | ||||
-rw-r--r-- | arch/sparc/mm/init.c | 515 | ||||
-rw-r--r-- | arch/sparc/mm/io-unit.c | 318 | ||||
-rw-r--r-- | arch/sparc/mm/iommu.c | 475 | ||||
-rw-r--r-- | arch/sparc/mm/loadmmu.c | 46 | ||||
-rw-r--r-- | arch/sparc/mm/nosrmmu.c | 59 | ||||
-rw-r--r-- | arch/sparc/mm/nosun4c.c | 77 | ||||
-rw-r--r-- | arch/sparc/mm/srmmu.c | 2274 | ||||
-rw-r--r-- | arch/sparc/mm/sun4c.c | 2276 | ||||
-rw-r--r-- | arch/sparc/mm/swift.S | 256 | ||||
-rw-r--r-- | arch/sparc/mm/tsunami.S | 133 | ||||
-rw-r--r-- | arch/sparc/mm/viking.S | 284 |
18 files changed, 8432 insertions, 0 deletions
diff --git a/arch/sparc/mm/Makefile b/arch/sparc/mm/Makefile new file mode 100644 index 000000000000..16eeba4b991a --- /dev/null +++ b/arch/sparc/mm/Makefile @@ -0,0 +1,23 @@ +# $Id: Makefile,v 1.38 2000/12/15 00:41:22 davem Exp $ +# Makefile for the linux Sparc-specific parts of the memory manager. +# + +EXTRA_AFLAGS := -ansi + +obj-y := fault.o init.o loadmmu.o generic.o extable.o btfixup.o + +ifeq ($(CONFIG_SUN4),y) +obj-y += nosrmmu.o +else +obj-y += srmmu.o iommu.o io-unit.o hypersparc.o viking.o tsunami.o swift.o +endif + +ifdef CONFIG_HIGHMEM +obj-y += highmem.o +endif + +ifdef CONFIG_SMP +obj-y += nosun4c.o +else +obj-y += sun4c.o +endif diff --git a/arch/sparc/mm/btfixup.c b/arch/sparc/mm/btfixup.c new file mode 100644 index 000000000000..f147a44c9780 --- /dev/null +++ b/arch/sparc/mm/btfixup.c @@ -0,0 +1,336 @@ +/* $Id: btfixup.c,v 1.10 2000/05/09 17:40:13 davem Exp $ + * btfixup.c: Boot time code fixup and relocator, so that + * we can get rid of most indirect calls to achieve single + * image sun4c and srmmu kernel. + * + * Copyright (C) 1998 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + */ + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <asm/btfixup.h> +#include <asm/page.h> +#include <asm/pgalloc.h> +#include <asm/pgtable.h> +#include <asm/oplib.h> +#include <asm/system.h> +#include <asm/cacheflush.h> + +#define BTFIXUP_OPTIMIZE_NOP +#define BTFIXUP_OPTIMIZE_OTHER + +extern char *srmmu_name; +static char version[] __initdata = "Boot time fixup v1.6. 4/Mar/98 Jakub Jelinek (jj@ultra.linux.cz). Patching kernel for "; +#ifdef CONFIG_SUN4 +static char str_sun4c[] __initdata = "sun4\n"; +#else +static char str_sun4c[] __initdata = "sun4c\n"; +#endif +static char str_srmmu[] __initdata = "srmmu[%s]/"; +static char str_iommu[] __initdata = "iommu\n"; +static char str_iounit[] __initdata = "io-unit\n"; + +static int visited __initdata = 0; +extern unsigned int ___btfixup_start[], ___btfixup_end[], __init_begin[], __init_end[], __init_text_end[]; +extern unsigned int _stext[], _end[], __start___ksymtab[], __stop___ksymtab[]; +static char wrong_f[] __initdata = "Trying to set f fixup %p to invalid function %08x\n"; +static char wrong_b[] __initdata = "Trying to set b fixup %p to invalid function %08x\n"; +static char wrong_s[] __initdata = "Trying to set s fixup %p to invalid value %08x\n"; +static char wrong_h[] __initdata = "Trying to set h fixup %p to invalid value %08x\n"; +static char wrong_a[] __initdata = "Trying to set a fixup %p to invalid value %08x\n"; +static char wrong[] __initdata = "Wrong address for %c fixup %p\n"; +static char insn_f[] __initdata = "Fixup f %p refers to weird instructions at %p[%08x,%08x]\n"; +static char insn_b[] __initdata = "Fixup b %p doesn't refer to a SETHI at %p[%08x]\n"; +static char insn_s[] __initdata = "Fixup s %p doesn't refer to an OR at %p[%08x]\n"; +static char insn_h[] __initdata = "Fixup h %p doesn't refer to a SETHI at %p[%08x]\n"; +static char insn_a[] __initdata = "Fixup a %p doesn't refer to a SETHI nor OR at %p[%08x]\n"; +static char insn_i[] __initdata = "Fixup i %p doesn't refer to a valid instruction at %p[%08x]\n"; +static char fca_und[] __initdata = "flush_cache_all undefined in btfixup()\n"; +static char wrong_setaddr[] __initdata = "Garbled CALL/INT patch at %p[%08x,%08x,%08x]=%08x\n"; + +#ifdef BTFIXUP_OPTIMIZE_OTHER +static void __init set_addr(unsigned int *addr, unsigned int q1, int fmangled, unsigned int value) +{ + if (!fmangled) + *addr = value; + else { + unsigned int *q = (unsigned int *)q1; + if (*addr == 0x01000000) { + /* Noped */ + *q = value; + } else if (addr[-1] == *q) { + /* Moved */ + addr[-1] = value; + *q = value; + } else { + prom_printf(wrong_setaddr, addr-1, addr[-1], *addr, *q, value); + prom_halt(); + } + } +} +#else +static __inline__ void set_addr(unsigned int *addr, unsigned int q1, int fmangled, unsigned int value) +{ + *addr = value; +} +#endif + +void __init btfixup(void) +{ + unsigned int *p, *q; + int type, count; + unsigned insn; + unsigned *addr; + int fmangled = 0; + void (*flush_cacheall)(void); + + if (!visited) { + visited++; + printk(version); + if (ARCH_SUN4C_SUN4) + printk(str_sun4c); + else { + printk(str_srmmu, srmmu_name); + if (sparc_cpu_model == sun4d) + printk(str_iounit); + else + printk(str_iommu); + } + } + for (p = ___btfixup_start; p < ___btfixup_end; ) { + count = p[2]; + q = p + 3; + switch (type = *(unsigned char *)p) { + case 'f': + count = p[3]; + q = p + 4; + if (((p[0] & 1) || p[1]) + && ((p[1] & 3) || (unsigned *)(p[1]) < _stext || (unsigned *)(p[1]) >= _end)) { + prom_printf(wrong_f, p, p[1]); + prom_halt(); + } + break; + case 'b': + if (p[1] < (unsigned long)__init_begin || p[1] >= (unsigned long)__init_text_end || (p[1] & 3)) { + prom_printf(wrong_b, p, p[1]); + prom_halt(); + } + break; + case 's': + if (p[1] + 0x1000 >= 0x2000) { + prom_printf(wrong_s, p, p[1]); + prom_halt(); + } + break; + case 'h': + if (p[1] & 0x3ff) { + prom_printf(wrong_h, p, p[1]); + prom_halt(); + } + break; + case 'a': + if (p[1] + 0x1000 >= 0x2000 && (p[1] & 0x3ff)) { + prom_printf(wrong_a, p, p[1]); + prom_halt(); + } + break; + } + if (p[0] & 1) { + p[0] &= ~1; + while (count) { + fmangled = 0; + addr = (unsigned *)*q; + if (addr < _stext || addr >= _end) { + prom_printf(wrong, type, p); + prom_halt(); + } + insn = *addr; +#ifdef BTFIXUP_OPTIMIZE_OTHER + if (type != 'f' && q[1]) { + insn = *(unsigned int *)q[1]; + if (!insn || insn == 1) + insn = *addr; + else + fmangled = 1; + } +#endif + switch (type) { + case 'f': /* CALL */ + if (addr >= __start___ksymtab && addr < __stop___ksymtab) { + *addr = p[1]; + break; + } else if (!q[1]) { + if ((insn & 0xc1c00000) == 0x01000000) { /* SETHI */ + *addr = (insn & 0xffc00000) | (p[1] >> 10); break; + } else if ((insn & 0xc1f82000) == 0x80102000) { /* OR X, %LO(i), Y */ + *addr = (insn & 0xffffe000) | (p[1] & 0x3ff); break; + } else if ((insn & 0xc0000000) != 0x40000000) { /* !CALL */ + bad_f: + prom_printf(insn_f, p, addr, insn, addr[1]); + prom_halt(); + } + } else if (q[1] != 1) + addr[1] = q[1]; + if (p[2] == BTFIXUPCALL_NORM) { + norm_f: + *addr = 0x40000000 | ((p[1] - (unsigned)addr) >> 2); + q[1] = 0; + break; + } +#ifndef BTFIXUP_OPTIMIZE_NOP + goto norm_f; +#else + if (!(addr[1] & 0x80000000)) { + if ((addr[1] & 0xc1c00000) != 0x01000000) /* !SETHI */ + goto bad_f; /* CALL, Bicc, FBfcc, CBccc are weird in delay slot, aren't they? */ + } else { + if ((addr[1] & 0x01800000) == 0x01800000) { + if ((addr[1] & 0x01f80000) == 0x01e80000) { + /* RESTORE */ + goto norm_f; /* It is dangerous to patch that */ + } + goto bad_f; + } + if ((addr[1] & 0xffffe003) == 0x9e03e000) { + /* ADD %O7, XX, %o7 */ + int displac = (addr[1] << 19); + + displac = (displac >> 21) + 2; + *addr = (0x10800000) + (displac & 0x3fffff); + q[1] = addr[1]; + addr[1] = p[2]; + break; + } + if ((addr[1] & 0x201f) == 0x200f || (addr[1] & 0x7c000) == 0x3c000) + goto norm_f; /* Someone is playing bad tricks with us: rs1 or rs2 is o7 */ + if ((addr[1] & 0x3e000000) == 0x1e000000) + goto norm_f; /* rd is %o7. We'd better take care. */ + } + if (p[2] == BTFIXUPCALL_NOP) { + *addr = 0x01000000; + q[1] = 1; + break; + } +#ifndef BTFIXUP_OPTIMIZE_OTHER + goto norm_f; +#else + if (addr[1] == 0x01000000) { /* NOP in the delay slot */ + q[1] = addr[1]; + *addr = p[2]; + break; + } + if ((addr[1] & 0xc0000000) != 0xc0000000) { + /* Not a memory operation */ + if ((addr[1] & 0x30000000) == 0x10000000) { + /* Ok, non-memory op with rd %oX */ + if ((addr[1] & 0x3e000000) == 0x1c000000) + goto bad_f; /* Aiee. Someone is playing strange %sp tricks */ + if ((addr[1] & 0x3e000000) > 0x12000000 || + ((addr[1] & 0x3e000000) == 0x12000000 && + p[2] != BTFIXUPCALL_STO1O0 && p[2] != BTFIXUPCALL_SWAPO0O1) || + ((p[2] & 0xffffe000) == BTFIXUPCALL_RETINT(0))) { + /* Nobody uses the result. We can nop it out. */ + *addr = p[2]; + q[1] = addr[1]; + addr[1] = 0x01000000; + break; + } + if ((addr[1] & 0xf1ffffe0) == 0x90100000) { + /* MOV %reg, %Ox */ + if ((addr[1] & 0x3e000000) == 0x10000000 && + (p[2] & 0x7c000) == 0x20000) { + /* Ok, it is call xx; mov reg, %o0 and call optimizes + to doing something on %o0. Patch the patch. */ + *addr = (p[2] & ~0x7c000) | ((addr[1] & 0x1f) << 14); + q[1] = addr[1]; + addr[1] = 0x01000000; + break; + } + if ((addr[1] & 0x3e000000) == 0x12000000 && + p[2] == BTFIXUPCALL_STO1O0) { + *addr = (p[2] & ~0x3e000000) | ((addr[1] & 0x1f) << 25); + q[1] = addr[1]; + addr[1] = 0x01000000; + break; + } + } + } + } + *addr = addr[1]; + q[1] = addr[1]; + addr[1] = p[2]; + break; +#endif /* BTFIXUP_OPTIMIZE_OTHER */ +#endif /* BTFIXUP_OPTIMIZE_NOP */ + case 'b': /* BLACKBOX */ + /* Has to be sethi i, xx */ + if ((insn & 0xc1c00000) != 0x01000000) { + prom_printf(insn_b, p, addr, insn); + prom_halt(); + } else { + void (*do_fixup)(unsigned *); + + do_fixup = (void (*)(unsigned *))p[1]; + do_fixup(addr); + } + break; + case 's': /* SIMM13 */ + /* Has to be or %g0, i, xx */ + if ((insn & 0xc1ffe000) != 0x80102000) { + prom_printf(insn_s, p, addr, insn); + prom_halt(); + } + set_addr(addr, q[1], fmangled, (insn & 0xffffe000) | (p[1] & 0x1fff)); + break; + case 'h': /* SETHI */ + /* Has to be sethi i, xx */ + if ((insn & 0xc1c00000) != 0x01000000) { + prom_printf(insn_h, p, addr, insn); + prom_halt(); + } + set_addr(addr, q[1], fmangled, (insn & 0xffc00000) | (p[1] >> 10)); + break; + case 'a': /* HALF */ + /* Has to be sethi i, xx or or %g0, i, xx */ + if ((insn & 0xc1c00000) != 0x01000000 && + (insn & 0xc1ffe000) != 0x80102000) { + prom_printf(insn_a, p, addr, insn); + prom_halt(); + } + if (p[1] & 0x3ff) + set_addr(addr, q[1], fmangled, + (insn & 0x3e000000) | 0x80102000 | (p[1] & 0x1fff)); + else + set_addr(addr, q[1], fmangled, + (insn & 0x3e000000) | 0x01000000 | (p[1] >> 10)); + break; + case 'i': /* INT */ + if ((insn & 0xc1c00000) == 0x01000000) /* %HI */ + set_addr(addr, q[1], fmangled, (insn & 0xffc00000) | (p[1] >> 10)); + else if ((insn & 0x80002000) == 0x80002000 && + (insn & 0x01800000) != 0x01800000) /* %LO */ + set_addr(addr, q[1], fmangled, (insn & 0xffffe000) | (p[1] & 0x3ff)); + else { + prom_printf(insn_i, p, addr, insn); + prom_halt(); + } + break; + } + count -= 2; + q += 2; + } + } else + p = q + count; + } +#ifdef CONFIG_SMP + flush_cacheall = (void (*)(void))BTFIXUPVAL_CALL(local_flush_cache_all); +#else + flush_cacheall = (void (*)(void))BTFIXUPVAL_CALL(flush_cache_all); +#endif + if (!flush_cacheall) { + prom_printf(fca_und); + prom_halt(); + } + (*flush_cacheall)(); +} diff --git a/arch/sparc/mm/extable.c b/arch/sparc/mm/extable.c new file mode 100644 index 000000000000..c9845c71f426 --- /dev/null +++ b/arch/sparc/mm/extable.c @@ -0,0 +1,77 @@ +/* + * linux/arch/sparc/mm/extable.c + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <asm/uaccess.h> + +void sort_extable(struct exception_table_entry *start, + struct exception_table_entry *finish) +{ +} + +/* Caller knows they are in a range if ret->fixup == 0 */ +const struct exception_table_entry * +search_extable(const struct exception_table_entry *start, + const struct exception_table_entry *last, + unsigned long value) +{ + const struct exception_table_entry *walk; + + /* Single insn entries are encoded as: + * word 1: insn address + * word 2: fixup code address + * + * Range entries are encoded as: + * word 1: first insn address + * word 2: 0 + * word 3: last insn address + 4 bytes + * word 4: fixup code address + * + * See asm/uaccess.h for more details. + */ + + /* 1. Try to find an exact match. */ + for (walk = start; walk <= last; walk++) { + if (walk->fixup == 0) { + /* A range entry, skip both parts. */ + walk++; + continue; + } + + if (walk->insn == value) + return walk; + } + + /* 2. Try to find a range match. */ + for (walk = start; walk <= (last - 1); walk++) { + if (walk->fixup) + continue; + + if (walk[0].insn <= value && walk[1].insn > value) + return walk; + + walk++; + } + + return NULL; +} + +/* Special extable search, which handles ranges. Returns fixup */ +unsigned long search_extables_range(unsigned long addr, unsigned long *g2) +{ + const struct exception_table_entry *entry; + + entry = search_exception_tables(addr); + if (!entry) + return 0; + + /* Inside range? Fix g2 and return correct fixup */ + if (!entry->fixup) { + *g2 = (addr - entry->insn) / 4; + return (entry + 1)->fixup; + } + + return entry->fixup; +} diff --git a/arch/sparc/mm/fault.c b/arch/sparc/mm/fault.c new file mode 100644 index 000000000000..37f4107bae66 --- /dev/null +++ b/arch/sparc/mm/fault.c @@ -0,0 +1,596 @@ +/* $Id: fault.c,v 1.122 2001/11/17 07:19:26 davem Exp $ + * fault.c: Page fault handlers for the Sparc. + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + * Copyright (C) 1996 Eddie C. Dost (ecd@skynet.be) + * Copyright (C) 1997 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + */ + +#include <asm/head.h> + +#include <linux/string.h> +#include <linux/types.h> +#include <linux/sched.h> +#include <linux/ptrace.h> +#include <linux/mman.h> +#include <linux/threads.h> +#include <linux/kernel.h> +#include <linux/signal.h> +#include <linux/mm.h> +#include <linux/smp.h> +#include <linux/smp_lock.h> +#include <linux/interrupt.h> +#include <linux/module.h> + +#include <asm/system.h> +#include <asm/segment.h> +#include <asm/page.h> +#include <asm/pgtable.h> +#include <asm/memreg.h> +#include <asm/openprom.h> +#include <asm/oplib.h> +#include <asm/smp.h> +#include <asm/traps.h> +#include <asm/kdebug.h> +#include <asm/uaccess.h> + +#define ELEMENTS(arr) (sizeof (arr)/sizeof (arr[0])) + +extern int prom_node_root; + +/* At boot time we determine these two values necessary for setting + * up the segment maps and page table entries (pte's). + */ + +int num_segmaps, num_contexts; +int invalid_segment; + +/* various Virtual Address Cache parameters we find at boot time... */ + +int vac_size, vac_linesize, vac_do_hw_vac_flushes; +int vac_entries_per_context, vac_entries_per_segment; +int vac_entries_per_page; + +/* Nice, simple, prom library does all the sweating for us. ;) */ +int prom_probe_memory (void) +{ + register struct linux_mlist_v0 *mlist; + register unsigned long bytes, base_paddr, tally; + register int i; + + i = 0; + mlist= *prom_meminfo()->v0_available; + bytes = tally = mlist->num_bytes; + base_paddr = (unsigned long) mlist->start_adr; + + sp_banks[0].base_addr = base_paddr; + sp_banks[0].num_bytes = bytes; + + while (mlist->theres_more != (void *) 0){ + i++; + mlist = mlist->theres_more; + bytes = mlist->num_bytes; + tally += bytes; + if (i > SPARC_PHYS_BANKS-1) { + printk ("The machine has more banks than " + "this kernel can support\n" + "Increase the SPARC_PHYS_BANKS " + "setting (currently %d)\n", + SPARC_PHYS_BANKS); + i = SPARC_PHYS_BANKS-1; + break; + } + + sp_banks[i].base_addr = (unsigned long) mlist->start_adr; + sp_banks[i].num_bytes = mlist->num_bytes; + } + + i++; + sp_banks[i].base_addr = 0xdeadbeef; + sp_banks[i].num_bytes = 0; + + /* Now mask all bank sizes on a page boundary, it is all we can + * use anyways. + */ + for(i=0; sp_banks[i].num_bytes != 0; i++) + sp_banks[i].num_bytes &= PAGE_MASK; + + return tally; +} + +/* Traverse the memory lists in the prom to see how much physical we + * have. + */ +unsigned long +probe_memory(void) +{ + int total; + + total = prom_probe_memory(); + + /* Oh man, much nicer, keep the dirt in promlib. */ + return total; +} + +extern void sun4c_complete_all_stores(void); + +/* Whee, a level 15 NMI interrupt memory error. Let's have fun... */ +asmlinkage void sparc_lvl15_nmi(struct pt_regs *regs, unsigned long serr, + unsigned long svaddr, unsigned long aerr, + unsigned long avaddr) +{ + sun4c_complete_all_stores(); + printk("FAULT: NMI received\n"); + printk("SREGS: Synchronous Error %08lx\n", serr); + printk(" Synchronous Vaddr %08lx\n", svaddr); + printk(" Asynchronous Error %08lx\n", aerr); + printk(" Asynchronous Vaddr %08lx\n", avaddr); + if (sun4c_memerr_reg) + printk(" Memory Parity Error %08lx\n", *sun4c_memerr_reg); + printk("REGISTER DUMP:\n"); + show_regs(regs); + prom_halt(); +} + +static void unhandled_fault(unsigned long, struct task_struct *, + struct pt_regs *) __attribute__ ((noreturn)); + +static void unhandled_fault(unsigned long address, struct task_struct *tsk, + struct pt_regs *regs) +{ + if((unsigned long) address < PAGE_SIZE) { + printk(KERN_ALERT + "Unable to handle kernel NULL pointer dereference\n"); + } else { + printk(KERN_ALERT "Unable to handle kernel paging request " + "at virtual address %08lx\n", address); + } + printk(KERN_ALERT "tsk->{mm,active_mm}->context = %08lx\n", + (tsk->mm ? tsk->mm->context : tsk->active_mm->context)); + printk(KERN_ALERT "tsk->{mm,active_mm}->pgd = %08lx\n", + (tsk->mm ? (unsigned long) tsk->mm->pgd : + (unsigned long) tsk->active_mm->pgd)); + die_if_kernel("Oops", regs); +} + +asmlinkage int lookup_fault(unsigned long pc, unsigned long ret_pc, + unsigned long address) +{ + struct pt_regs regs; + unsigned long g2; + unsigned int insn; + int i; + + i = search_extables_range(ret_pc, &g2); + switch (i) { + case 3: + /* load & store will be handled by fixup */ + return 3; + + case 1: + /* store will be handled by fixup, load will bump out */ + /* for _to_ macros */ + insn = *((unsigned int *) pc); + if ((insn >> 21) & 1) + return 1; + break; + + case 2: + /* load will be handled by fixup, store will bump out */ + /* for _from_ macros */ + insn = *((unsigned int *) pc); + if (!((insn >> 21) & 1) || ((insn>>19)&0x3f) == 15) + return 2; + break; + + default: + break; + }; + + memset(®s, 0, sizeof (regs)); + regs.pc = pc; + regs.npc = pc + 4; + __asm__ __volatile__( + "rd %%psr, %0\n\t" + "nop\n\t" + "nop\n\t" + "nop\n" : "=r" (regs.psr)); + unhandled_fault(address, current, ®s); + + /* Not reached */ + return 0; +} + +extern unsigned long safe_compute_effective_address(struct pt_regs *, + unsigned int); + +static unsigned long compute_si_addr(struct pt_regs *regs, int text_fault) +{ + unsigned int insn; + + if (text_fault) + return regs->pc; + + if (regs->psr & PSR_PS) { + insn = *(unsigned int *) regs->pc; + } else { + __get_user(insn, (unsigned int *) regs->pc); + } + + return safe_compute_effective_address(regs, insn); +} + +asmlinkage void do_sparc_fault(struct pt_regs *regs, int text_fault, int write, + unsigned long address) +{ + struct vm_area_struct *vma; + struct task_struct *tsk = current; + struct mm_struct *mm = tsk->mm; + unsigned int fixup; + unsigned long g2; + siginfo_t info; + int from_user = !(regs->psr & PSR_PS); + + if(text_fault) + address = regs->pc; + + /* + * We fault-in kernel-space virtual memory on-demand. The + * 'reference' page table is init_mm.pgd. + * + * NOTE! We MUST NOT take any locks for this case. We may + * be in an interrupt or a critical region, and should + * only copy the information from the master page table, + * nothing more. + */ + if (!ARCH_SUN4C_SUN4 && address >= TASK_SIZE) + goto vmalloc_fault; + + info.si_code = SEGV_MAPERR; + + /* + * If we're in an interrupt or have no user + * context, we must not take the fault.. + */ + if (in_atomic() || !mm) + goto no_context; + + down_read(&mm->mmap_sem); + + /* + * The kernel referencing a bad kernel pointer can lock up + * a sun4c machine completely, so we must attempt recovery. + */ + if(!from_user && address >= PAGE_OFFSET) + goto bad_area; + + vma = find_vma(mm, address); + if(!vma) + goto bad_area; + if(vma->vm_start <= address) + goto good_area; + if(!(vma->vm_flags & VM_GROWSDOWN)) + goto bad_area; + if(expand_stack(vma, address)) + goto bad_area; + /* + * Ok, we have a good vm_area for this memory access, so + * we can handle it.. + */ +good_area: + info.si_code = SEGV_ACCERR; + if(write) { + if(!(vma->vm_flags & VM_WRITE)) + goto bad_area; + } else { + /* Allow reads even for write-only mappings */ + if(!(vma->vm_flags & (VM_READ | VM_EXEC))) + goto bad_area; + } + + /* + * If for any reason at all we couldn't handle the fault, + * make sure we exit gracefully rather than endlessly redo + * the fault. + */ + switch (handle_mm_fault(mm, vma, address, write)) { + case VM_FAULT_SIGBUS: + goto do_sigbus; + case VM_FAULT_OOM: + goto out_of_memory; + case VM_FAULT_MAJOR: + current->maj_flt++; + break; + case VM_FAULT_MINOR: + default: + current->min_flt++; + break; + } + up_read(&mm->mmap_sem); + return; + + /* + * Something tried to access memory that isn't in our memory map.. + * Fix it, but check if it's kernel or user first.. + */ +bad_area: + up_read(&mm->mmap_sem); + +bad_area_nosemaphore: + /* User mode accesses just cause a SIGSEGV */ + if(from_user) { +#if 0 + printk("Fault whee %s [%d]: segfaults at %08lx pc=%08lx\n", + tsk->comm, tsk->pid, address, regs->pc); +#endif + info.si_signo = SIGSEGV; + info.si_errno = 0; + /* info.si_code set above to make clear whether + this was a SEGV_MAPERR or SEGV_ACCERR fault. */ + info.si_addr = (void __user *)compute_si_addr(regs, text_fault); + info.si_trapno = 0; + force_sig_info (SIGSEGV, &info, tsk); + return; + } + + /* Is this in ex_table? */ +no_context: + g2 = regs->u_regs[UREG_G2]; + if (!from_user && (fixup = search_extables_range(regs->pc, &g2))) { + if (fixup > 10) { /* Values below are reserved for other things */ + extern const unsigned __memset_start[]; + extern const unsigned __memset_end[]; + extern const unsigned __csum_partial_copy_start[]; + extern const unsigned __csum_partial_copy_end[]; + +#ifdef DEBUG_EXCEPTIONS + printk("Exception: PC<%08lx> faddr<%08lx>\n", regs->pc, address); + printk("EX_TABLE: insn<%08lx> fixup<%08x> g2<%08lx>\n", + regs->pc, fixup, g2); +#endif + if ((regs->pc >= (unsigned long)__memset_start && + regs->pc < (unsigned long)__memset_end) || + (regs->pc >= (unsigned long)__csum_partial_copy_start && + regs->pc < (unsigned long)__csum_partial_copy_end)) { + regs->u_regs[UREG_I4] = address; + regs->u_regs[UREG_I5] = regs->pc; + } + regs->u_regs[UREG_G2] = g2; + regs->pc = fixup; + regs->npc = regs->pc + 4; + return; + } + } + + unhandled_fault (address, tsk, regs); + do_exit(SIGKILL); + +/* + * 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); + printk("VM: killing process %s\n", tsk->comm); + if (from_user) + do_exit(SIGKILL); + goto no_context; + +do_sigbus: + up_read(&mm->mmap_sem); + info.si_signo = SIGBUS; + info.si_errno = 0; + info.si_code = BUS_ADRERR; + info.si_addr = (void __user *) compute_si_addr(regs, text_fault); + info.si_trapno = 0; + force_sig_info (SIGBUS, &info, tsk); + if (!from_user) + goto no_context; + +vmalloc_fault: + { + /* + * Synchronize this task's top level page-table + * with the 'reference' page table. + */ + int offset = pgd_index(address); + pgd_t *pgd, *pgd_k; + pmd_t *pmd, *pmd_k; + + pgd = tsk->active_mm->pgd + offset; + pgd_k = init_mm.pgd + offset; + + if (!pgd_present(*pgd)) { + if (!pgd_present(*pgd_k)) + goto bad_area_nosemaphore; + pgd_val(*pgd) = pgd_val(*pgd_k); + return; + } + + pmd = pmd_offset(pgd, address); + pmd_k = pmd_offset(pgd_k, address); + + if (pmd_present(*pmd) || !pmd_present(*pmd_k)) + goto bad_area_nosemaphore; + *pmd = *pmd_k; + return; + } +} + +asmlinkage void do_sun4c_fault(struct pt_regs *regs, int text_fault, int write, + unsigned long address) +{ + extern void sun4c_update_mmu_cache(struct vm_area_struct *, + unsigned long,pte_t); + extern pte_t *sun4c_pte_offset_kernel(pmd_t *,unsigned long); + struct task_struct *tsk = current; + struct mm_struct *mm = tsk->mm; + pgd_t *pgdp; + pte_t *ptep; + + if (text_fault) { + address = regs->pc; + } else if (!write && + !(regs->psr & PSR_PS)) { + unsigned int insn, __user *ip; + + ip = (unsigned int __user *)regs->pc; + if (!get_user(insn, ip)) { + if ((insn & 0xc1680000) == 0xc0680000) + write = 1; + } + } + + if (!mm) { + /* We are oopsing. */ + do_sparc_fault(regs, text_fault, write, address); + BUG(); /* P3 Oops already, you bitch */ + } + + pgdp = pgd_offset(mm, address); + ptep = sun4c_pte_offset_kernel((pmd_t *) pgdp, address); + + if (pgd_val(*pgdp)) { + if (write) { + if ((pte_val(*ptep) & (_SUN4C_PAGE_WRITE|_SUN4C_PAGE_PRESENT)) + == (_SUN4C_PAGE_WRITE|_SUN4C_PAGE_PRESENT)) { + unsigned long flags; + + *ptep = __pte(pte_val(*ptep) | _SUN4C_PAGE_ACCESSED | + _SUN4C_PAGE_MODIFIED | + _SUN4C_PAGE_VALID | + _SUN4C_PAGE_DIRTY); + + local_irq_save(flags); + if (sun4c_get_segmap(address) != invalid_segment) { + sun4c_put_pte(address, pte_val(*ptep)); + local_irq_restore(flags); + return; + } + local_irq_restore(flags); + } + } else { + if ((pte_val(*ptep) & (_SUN4C_PAGE_READ|_SUN4C_PAGE_PRESENT)) + == (_SUN4C_PAGE_READ|_SUN4C_PAGE_PRESENT)) { + unsigned long flags; + + *ptep = __pte(pte_val(*ptep) | _SUN4C_PAGE_ACCESSED | + _SUN4C_PAGE_VALID); + + local_irq_save(flags); + if (sun4c_get_segmap(address) != invalid_segment) { + sun4c_put_pte(address, pte_val(*ptep)); + local_irq_restore(flags); + return; + } + local_irq_restore(flags); + } + } + } + + /* This conditional is 'interesting'. */ + if (pgd_val(*pgdp) && !(write && !(pte_val(*ptep) & _SUN4C_PAGE_WRITE)) + && (pte_val(*ptep) & _SUN4C_PAGE_VALID)) + /* Note: It is safe to not grab the MMAP semaphore here because + * we know that update_mmu_cache() will not sleep for + * any reason (at least not in the current implementation) + * and therefore there is no danger of another thread getting + * on the CPU and doing a shrink_mmap() on this vma. + */ + sun4c_update_mmu_cache (find_vma(current->mm, address), address, + *ptep); + else + do_sparc_fault(regs, text_fault, write, address); +} + +/* This always deals with user addresses. */ +inline void force_user_fault(unsigned long address, int write) +{ + struct vm_area_struct *vma; + struct task_struct *tsk = current; + struct mm_struct *mm = tsk->mm; + siginfo_t info; + + info.si_code = SEGV_MAPERR; + +#if 0 + printk("wf<pid=%d,wr=%d,addr=%08lx>\n", + tsk->pid, write, address); +#endif + down_read(&mm->mmap_sem); + vma = find_vma(mm, address); + if(!vma) + goto bad_area; + if(vma->vm_start <= address) + goto good_area; + if(!(vma->vm_flags & VM_GROWSDOWN)) + goto bad_area; + if(expand_stack(vma, address)) + goto bad_area; +good_area: + info.si_code = SEGV_ACCERR; + if(write) { + if(!(vma->vm_flags & VM_WRITE)) + goto bad_area; + } else { + if(!(vma->vm_flags & (VM_READ | VM_EXEC))) + goto bad_area; + } + switch (handle_mm_fault(mm, vma, address, write)) { + case VM_FAULT_SIGBUS: + case VM_FAULT_OOM: + goto do_sigbus; + } + up_read(&mm->mmap_sem); + return; +bad_area: + up_read(&mm->mmap_sem); +#if 0 + printk("Window whee %s [%d]: segfaults at %08lx\n", + tsk->comm, tsk->pid, address); +#endif + info.si_signo = SIGSEGV; + info.si_errno = 0; + /* info.si_code set above to make clear whether + this was a SEGV_MAPERR or SEGV_ACCERR fault. */ + info.si_addr = (void __user *) address; + info.si_trapno = 0; + force_sig_info (SIGSEGV, &info, tsk); + return; + +do_sigbus: + up_read(&mm->mmap_sem); + info.si_signo = SIGBUS; + info.si_errno = 0; + info.si_code = BUS_ADRERR; + info.si_addr = (void __user *) address; + info.si_trapno = 0; + force_sig_info (SIGBUS, &info, tsk); +} + +void window_overflow_fault(void) +{ + unsigned long sp; + + sp = current_thread_info()->rwbuf_stkptrs[0]; + if(((sp + 0x38) & PAGE_MASK) != (sp & PAGE_MASK)) + force_user_fault(sp + 0x38, 1); + force_user_fault(sp, 1); +} + +void window_underflow_fault(unsigned long sp) +{ + if(((sp + 0x38) & PAGE_MASK) != (sp & PAGE_MASK)) + force_user_fault(sp + 0x38, 0); + force_user_fault(sp, 0); +} + +void window_ret_fault(struct pt_regs *regs) +{ + unsigned long sp; + + sp = regs->u_regs[UREG_FP]; + if(((sp + 0x38) & PAGE_MASK) != (sp & PAGE_MASK)) + force_user_fault(sp + 0x38, 0); + force_user_fault(sp, 0); +} diff --git a/arch/sparc/mm/generic.c b/arch/sparc/mm/generic.c new file mode 100644 index 000000000000..db27eee3bda1 --- /dev/null +++ b/arch/sparc/mm/generic.c @@ -0,0 +1,154 @@ +/* $Id: generic.c,v 1.14 2001/12/21 04:56:15 davem Exp $ + * generic.c: Generic Sparc mm routines that are not dependent upon + * MMU type but are Sparc specific. + * + * Copyright (C) 1996 David S. Miller (davem@caip.rutgers.edu) + */ + +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/swap.h> +#include <linux/pagemap.h> + +#include <asm/pgalloc.h> +#include <asm/pgtable.h> +#include <asm/page.h> +#include <asm/cacheflush.h> +#include <asm/tlbflush.h> + +static inline void forget_pte(pte_t page) +{ +#if 0 /* old 2.4 code */ + if (pte_none(page)) + return; + if (pte_present(page)) { + unsigned long pfn = pte_pfn(page); + struct page *ptpage; + if (!pfn_valid(pfn)) + return; + ptpage = pfn_to_page(pfn); + if (PageReserved(ptpage)) + return; + page_cache_release(ptpage); + return; + } + swap_free(pte_to_swp_entry(page)); +#else + if (!pte_none(page)) { + printk("forget_pte: old mapping existed!\n"); + BUG(); + } +#endif +} + +/* Remap IO memory, the same way as remap_pfn_range(), but use + * the obio memory space. + * + * They use a pgprot that sets PAGE_IO and does not check the + * mem_map table as this is independent of normal memory. + */ +static inline void io_remap_pte_range(struct mm_struct *mm, pte_t * pte, unsigned long address, unsigned long size, + unsigned long offset, pgprot_t prot, int space) +{ + unsigned long end; + + address &= ~PMD_MASK; + end = address + size; + if (end > PMD_SIZE) + end = PMD_SIZE; + do { + pte_t oldpage = *pte; + pte_clear(mm, address, pte); + set_pte(pte, mk_pte_io(offset, prot, space)); + forget_pte(oldpage); + address += PAGE_SIZE; + offset += PAGE_SIZE; + pte++; + } while (address < end); +} + +static inline int io_remap_pmd_range(struct mm_struct *mm, pmd_t * pmd, unsigned long address, unsigned long size, + unsigned long offset, pgprot_t prot, int space) +{ + unsigned long end; + + address &= ~PGDIR_MASK; + end = address + size; + if (end > PGDIR_SIZE) + end = PGDIR_SIZE; + offset -= address; + do { + pte_t * pte = pte_alloc_map(mm, pmd, address); + if (!pte) + return -ENOMEM; + io_remap_pte_range(mm, pte, address, end - address, address + offset, prot, space); + address = (address + PMD_SIZE) & PMD_MASK; + pmd++; + } while (address < end); + return 0; +} + +int io_remap_page_range(struct vm_area_struct *vma, unsigned long from, unsigned long offset, unsigned long size, pgprot_t prot, int space) +{ + int error = 0; + pgd_t * dir; + unsigned long beg = from; + unsigned long end = from + size; + struct mm_struct *mm = vma->vm_mm; + + prot = __pgprot(pg_iobits); + offset -= from; + dir = pgd_offset(mm, from); + flush_cache_range(vma, beg, end); + + spin_lock(&mm->page_table_lock); + while (from < end) { + pmd_t *pmd = pmd_alloc(current->mm, dir, from); + error = -ENOMEM; + if (!pmd) + break; + error = io_remap_pmd_range(mm, pmd, from, end - from, offset + from, prot, space); + if (error) + break; + from = (from + PGDIR_SIZE) & PGDIR_MASK; + dir++; + } + spin_unlock(&mm->page_table_lock); + + flush_tlb_range(vma, beg, end); + return error; +} + +int io_remap_pfn_range(struct vm_area_struct *vma, unsigned long from, + unsigned long pfn, unsigned long size, pgprot_t prot) +{ + int error = 0; + pgd_t * dir; + unsigned long beg = from; + unsigned long end = from + size; + struct mm_struct *mm = vma->vm_mm; + int space = GET_IOSPACE(pfn); + unsigned long offset = GET_PFN(pfn) << PAGE_SHIFT; + + prot = __pgprot(pg_iobits); + offset -= from; + dir = pgd_offset(mm, from); + flush_cache_range(vma, beg, end); + + spin_lock(&mm->page_table_lock); + while (from < end) { + pmd_t *pmd = pmd_alloc(current->mm, dir, from); + error = -ENOMEM; + if (!pmd) + break; + error = io_remap_pmd_range(mm, pmd, from, end - from, offset + from, prot, space); + if (error) + break; + from = (from + PGDIR_SIZE) & PGDIR_MASK; + dir++; + } + spin_unlock(&mm->page_table_lock); + + flush_tlb_range(vma, beg, end); + return error; +} diff --git a/arch/sparc/mm/highmem.c b/arch/sparc/mm/highmem.c new file mode 100644 index 000000000000..4d8ed9c65182 --- /dev/null +++ b/arch/sparc/mm/highmem.c @@ -0,0 +1,120 @@ +/* + * highmem.c: virtual kernel memory mappings for high memory + * + * Provides kernel-static versions of atomic kmap functions originally + * found as inlines in include/asm-sparc/highmem.h. These became + * needed as kmap_atomic() and kunmap_atomic() started getting + * called from within modules. + * -- Tomas Szepe <szepe@pinerecords.com>, September 2002 + * + * But kmap_atomic() and kunmap_atomic() cannot be inlined in + * modules because they are loaded with btfixup-ped functions. + */ + +/* + * The use of kmap_atomic/kunmap_atomic is discouraged - kmap/kunmap + * gives a more generic (and caching) interface. But kmap_atomic can + * be used in IRQ contexts, so in some (very limited) cases we need it. + * + * XXX This is an old text. Actually, it's good to use atomic kmaps, + * provided you remember that they are atomic and not try to sleep + * with a kmap taken, much like a spinlock. Non-atomic kmaps are + * shared by CPUs, and so precious, and establishing them requires IPI. + * Atomic kmaps are lightweight and we may have NCPUS more of them. + */ +#include <linux/mm.h> +#include <linux/highmem.h> +#include <asm/pgalloc.h> +#include <asm/cacheflush.h> +#include <asm/tlbflush.h> +#include <asm/fixmap.h> + +void *kmap_atomic(struct page *page, enum km_type type) +{ + unsigned long idx; + unsigned long vaddr; + + /* even !CONFIG_PREEMPT needs this, for in_atomic in do_page_fault */ + inc_preempt_count(); + if (!PageHighMem(page)) + return page_address(page); + + idx = type + KM_TYPE_NR*smp_processor_id(); + vaddr = __fix_to_virt(FIX_KMAP_BEGIN + idx); + +/* XXX Fix - Anton */ +#if 0 + __flush_cache_one(vaddr); +#else + flush_cache_all(); +#endif + +#ifdef CONFIG_DEBUG_HIGHMEM + BUG_ON(!pte_none(*(kmap_pte-idx))); +#endif + set_pte(kmap_pte-idx, mk_pte(page, kmap_prot)); +/* XXX Fix - Anton */ +#if 0 + __flush_tlb_one(vaddr); +#else + flush_tlb_all(); +#endif + + return (void*) vaddr; +} + +void kunmap_atomic(void *kvaddr, enum km_type type) +{ +#ifdef CONFIG_DEBUG_HIGHMEM + unsigned long vaddr = (unsigned long) kvaddr & PAGE_MASK; + unsigned long idx = type + KM_TYPE_NR*smp_processor_id(); + + if (vaddr < FIXADDR_START) { // FIXME + dec_preempt_count(); + preempt_check_resched(); + return; + } + + BUG_ON(vaddr != __fix_to_virt(FIX_KMAP_BEGIN+idx)); + +/* XXX Fix - Anton */ +#if 0 + __flush_cache_one(vaddr); +#else + flush_cache_all(); +#endif + + /* + * force other mappings to Oops if they'll try to access + * this pte without first remap it + */ + pte_clear(&init_mm, vaddr, kmap_pte-idx); +/* XXX Fix - Anton */ +#if 0 + __flush_tlb_one(vaddr); +#else + flush_tlb_all(); +#endif +#endif + + dec_preempt_count(); + preempt_check_resched(); +} + +/* We may be fed a pagetable here by ptep_to_xxx and others. */ +struct page *kmap_atomic_to_page(void *ptr) +{ + unsigned long idx, vaddr = (unsigned long)ptr; + pte_t *pte; + + if (vaddr < SRMMU_NOCACHE_VADDR) + return virt_to_page(ptr); + if (vaddr < PKMAP_BASE) + return pfn_to_page(__nocache_pa(vaddr) >> PAGE_SHIFT); + BUG_ON(vaddr < FIXADDR_START); + BUG_ON(vaddr > FIXADDR_TOP); + + idx = virt_to_fix(vaddr); + pte = kmap_pte - (idx - FIX_KMAP_BEGIN); + return pte_page(*pte); +} diff --git a/arch/sparc/mm/hypersparc.S b/arch/sparc/mm/hypersparc.S new file mode 100644 index 000000000000..54b8e764b042 --- /dev/null +++ b/arch/sparc/mm/hypersparc.S @@ -0,0 +1,413 @@ +/* $Id: hypersparc.S,v 1.18 2001/12/21 04:56:15 davem Exp $ + * hypersparc.S: High speed Hypersparc mmu/cache operations. + * + * Copyright (C) 1997 David S. Miller (davem@caip.rutgers.edu) + */ + +#include <asm/ptrace.h> +#include <asm/psr.h> +#include <asm/asm_offsets.h> +#include <asm/asi.h> +#include <asm/page.h> +#include <asm/pgtsrmmu.h> +#include <linux/config.h> +#include <linux/init.h> + + .text + .align 4 + + .globl hypersparc_flush_cache_all, hypersparc_flush_cache_mm + .globl hypersparc_flush_cache_range, hypersparc_flush_cache_page + .globl hypersparc_flush_page_to_ram + .globl hypersparc_flush_page_for_dma, hypersparc_flush_sig_insns + .globl hypersparc_flush_tlb_all, hypersparc_flush_tlb_mm + .globl hypersparc_flush_tlb_range, hypersparc_flush_tlb_page + +hypersparc_flush_cache_all: + WINDOW_FLUSH(%g4, %g5) + sethi %hi(vac_cache_size), %g4 + ld [%g4 + %lo(vac_cache_size)], %g5 + sethi %hi(vac_line_size), %g1 + ld [%g1 + %lo(vac_line_size)], %g2 +1: + subcc %g5, %g2, %g5 ! hyper_flush_unconditional_combined + bne 1b + sta %g0, [%g5] ASI_M_FLUSH_CTX + retl + sta %g0, [%g0] ASI_M_FLUSH_IWHOLE ! hyper_flush_whole_icache + + /* We expand the window flush to get maximum performance. */ +hypersparc_flush_cache_mm: +#ifndef CONFIG_SMP + ld [%o0 + AOFF_mm_context], %g1 + cmp %g1, -1 + be hypersparc_flush_cache_mm_out +#endif + WINDOW_FLUSH(%g4, %g5) + + sethi %hi(vac_line_size), %g1 + ld [%g1 + %lo(vac_line_size)], %o1 + sethi %hi(vac_cache_size), %g2 + ld [%g2 + %lo(vac_cache_size)], %o0 + add %o1, %o1, %g1 + add %o1, %g1, %g2 + add %o1, %g2, %g3 + add %o1, %g3, %g4 + add %o1, %g4, %g5 + add %o1, %g5, %o4 + add %o1, %o4, %o5 + + /* BLAMMO! */ +1: + subcc %o0, %o5, %o0 ! hyper_flush_cache_user + sta %g0, [%o0 + %g0] ASI_M_FLUSH_USER + sta %g0, [%o0 + %o1] ASI_M_FLUSH_USER + sta %g0, [%o0 + %g1] ASI_M_FLUSH_USER + sta %g0, [%o0 + %g2] ASI_M_FLUSH_USER + sta %g0, [%o0 + %g3] ASI_M_FLUSH_USER + sta %g0, [%o0 + %g4] ASI_M_FLUSH_USER + sta %g0, [%o0 + %g5] ASI_M_FLUSH_USER + bne 1b + sta %g0, [%o0 + %o4] ASI_M_FLUSH_USER +hypersparc_flush_cache_mm_out: + retl + nop + + /* The things we do for performance... */ +hypersparc_flush_cache_range: + ld [%o0 + 0x0], %o0 /* XXX vma->vm_mm, GROSS XXX */ +#ifndef CONFIG_SMP + ld [%o0 + AOFF_mm_context], %g1 + cmp %g1, -1 + be hypersparc_flush_cache_range_out +#endif + WINDOW_FLUSH(%g4, %g5) + + sethi %hi(vac_line_size), %g1 + ld [%g1 + %lo(vac_line_size)], %o4 + sethi %hi(vac_cache_size), %g2 + ld [%g2 + %lo(vac_cache_size)], %o3 + + /* Here comes the fun part... */ + add %o2, (PAGE_SIZE - 1), %o2 + andn %o1, (PAGE_SIZE - 1), %o1 + add %o4, %o4, %o5 + andn %o2, (PAGE_SIZE - 1), %o2 + add %o4, %o5, %g1 + sub %o2, %o1, %g4 + add %o4, %g1, %g2 + sll %o3, 2, %g5 + add %o4, %g2, %g3 + cmp %g4, %g5 + add %o4, %g3, %g4 + blu 0f + add %o4, %g4, %g5 + add %o4, %g5, %g7 + + /* Flush entire user space, believe it or not this is quicker + * than page at a time flushings for range > (cache_size<<2). + */ +1: + subcc %o3, %g7, %o3 + sta %g0, [%o3 + %g0] ASI_M_FLUSH_USER + sta %g0, [%o3 + %o4] ASI_M_FLUSH_USER + sta %g0, [%o3 + %o5] ASI_M_FLUSH_USER + sta %g0, [%o3 + %g1] ASI_M_FLUSH_USER + sta %g0, [%o3 + %g2] ASI_M_FLUSH_USER + sta %g0, [%o3 + %g3] ASI_M_FLUSH_USER + sta %g0, [%o3 + %g4] ASI_M_FLUSH_USER + bne 1b + sta %g0, [%o3 + %g5] ASI_M_FLUSH_USER + retl + nop + + /* Below our threshold, flush one page at a time. */ +0: + ld [%o0 + AOFF_mm_context], %o0 + mov SRMMU_CTX_REG, %g7 + lda [%g7] ASI_M_MMUREGS, %o3 + sta %o0, [%g7] ASI_M_MMUREGS + add %o2, -PAGE_SIZE, %o0 +1: + or %o0, 0x400, %g7 + lda [%g7] ASI_M_FLUSH_PROBE, %g7 + orcc %g7, 0, %g0 + be,a 3f + mov %o0, %o2 + add %o4, %g5, %g7 +2: + sub %o2, %g7, %o2 + sta %g0, [%o2 + %g0] ASI_M_FLUSH_PAGE + sta %g0, [%o2 + %o4] ASI_M_FLUSH_PAGE + sta %g0, [%o2 + %o5] ASI_M_FLUSH_PAGE + sta %g0, [%o2 + %g1] ASI_M_FLUSH_PAGE + sta %g0, [%o2 + %g2] ASI_M_FLUSH_PAGE + sta %g0, [%o2 + %g3] ASI_M_FLUSH_PAGE + andcc %o2, 0xffc, %g0 + sta %g0, [%o2 + %g4] ASI_M_FLUSH_PAGE + bne 2b + sta %g0, [%o2 + %g5] ASI_M_FLUSH_PAGE +3: + cmp %o2, %o1 + bne 1b + add %o2, -PAGE_SIZE, %o0 + mov SRMMU_FAULT_STATUS, %g5 + lda [%g5] ASI_M_MMUREGS, %g0 + mov SRMMU_CTX_REG, %g7 + sta %o3, [%g7] ASI_M_MMUREGS +hypersparc_flush_cache_range_out: + retl + nop + + /* HyperSparc requires a valid mapping where we are about to flush + * in order to check for a physical tag match during the flush. + */ + /* Verified, my ass... */ +hypersparc_flush_cache_page: + ld [%o0 + 0x0], %o0 /* XXX vma->vm_mm, GROSS XXX */ + ld [%o0 + AOFF_mm_context], %g2 +#ifndef CONFIG_SMP + cmp %g2, -1 + be hypersparc_flush_cache_page_out +#endif + WINDOW_FLUSH(%g4, %g5) + + sethi %hi(vac_line_size), %g1 + ld [%g1 + %lo(vac_line_size)], %o4 + mov SRMMU_CTX_REG, %o3 + andn %o1, (PAGE_SIZE - 1), %o1 + lda [%o3] ASI_M_MMUREGS, %o2 + sta %g2, [%o3] ASI_M_MMUREGS + or %o1, 0x400, %o5 + lda [%o5] ASI_M_FLUSH_PROBE, %g1 + orcc %g0, %g1, %g0 + be 2f + add %o4, %o4, %o5 + sub %o1, -PAGE_SIZE, %o1 + add %o4, %o5, %g1 + add %o4, %g1, %g2 + add %o4, %g2, %g3 + add %o4, %g3, %g4 + add %o4, %g4, %g5 + add %o4, %g5, %g7 + + /* BLAMMO! */ +1: + sub %o1, %g7, %o1 + sta %g0, [%o1 + %g0] ASI_M_FLUSH_PAGE + sta %g0, [%o1 + %o4] ASI_M_FLUSH_PAGE + sta %g0, [%o1 + %o5] ASI_M_FLUSH_PAGE + sta %g0, [%o1 + %g1] ASI_M_FLUSH_PAGE + sta %g0, [%o1 + %g2] ASI_M_FLUSH_PAGE + sta %g0, [%o1 + %g3] ASI_M_FLUSH_PAGE + andcc %o1, 0xffc, %g0 + sta %g0, [%o1 + %g4] ASI_M_FLUSH_PAGE + bne 1b + sta %g0, [%o1 + %g5] ASI_M_FLUSH_PAGE +2: + mov SRMMU_FAULT_STATUS, %g7 + mov SRMMU_CTX_REG, %g4 + lda [%g7] ASI_M_MMUREGS, %g0 + sta %o2, [%g4] ASI_M_MMUREGS +hypersparc_flush_cache_page_out: + retl + nop + +hypersparc_flush_sig_insns: + flush %o1 + retl + flush %o1 + 4 + + /* HyperSparc is copy-back. */ +hypersparc_flush_page_to_ram: + sethi %hi(vac_line_size), %g1 + ld [%g1 + %lo(vac_line_size)], %o4 + andn %o0, (PAGE_SIZE - 1), %o0 + add %o4, %o4, %o5 + or %o0, 0x400, %g7 + lda [%g7] ASI_M_FLUSH_PROBE, %g5 + add %o4, %o5, %g1 + orcc %g5, 0, %g0 + be 2f + add %o4, %g1, %g2 + add %o4, %g2, %g3 + sub %o0, -PAGE_SIZE, %o0 + add %o4, %g3, %g4 + add %o4, %g4, %g5 + add %o4, %g5, %g7 + + /* BLAMMO! */ +1: + sub %o0, %g7, %o0 + sta %g0, [%o0 + %g0] ASI_M_FLUSH_PAGE + sta %g0, [%o0 + %o4] ASI_M_FLUSH_PAGE + sta %g0, [%o0 + %o5] ASI_M_FLUSH_PAGE + sta %g0, [%o0 + %g1] ASI_M_FLUSH_PAGE + sta %g0, [%o0 + %g2] ASI_M_FLUSH_PAGE + sta %g0, [%o0 + %g3] ASI_M_FLUSH_PAGE + andcc %o0, 0xffc, %g0 + sta %g0, [%o0 + %g4] ASI_M_FLUSH_PAGE + bne 1b + sta %g0, [%o0 + %g5] ASI_M_FLUSH_PAGE +2: + mov SRMMU_FAULT_STATUS, %g1 + retl + lda [%g1] ASI_M_MMUREGS, %g0 + + /* HyperSparc is IO cache coherent. */ +hypersparc_flush_page_for_dma: + retl + nop + + /* It was noted that at boot time a TLB flush all in a delay slot + * can deliver an illegal instruction to the processor if the timing + * is just right... + */ +hypersparc_flush_tlb_all: + mov 0x400, %g1 + sta %g0, [%g1] ASI_M_FLUSH_PROBE + retl + nop + +hypersparc_flush_tlb_mm: + mov SRMMU_CTX_REG, %g1 + ld [%o0 + AOFF_mm_context], %o1 + lda [%g1] ASI_M_MMUREGS, %g5 +#ifndef CONFIG_SMP + cmp %o1, -1 + be hypersparc_flush_tlb_mm_out +#endif + mov 0x300, %g2 + sta %o1, [%g1] ASI_M_MMUREGS + sta %g0, [%g2] ASI_M_FLUSH_PROBE +hypersparc_flush_tlb_mm_out: + retl + sta %g5, [%g1] ASI_M_MMUREGS + +hypersparc_flush_tlb_range: + ld [%o0 + 0x00], %o0 /* XXX vma->vm_mm GROSS XXX */ + mov SRMMU_CTX_REG, %g1 + ld [%o0 + AOFF_mm_context], %o3 + lda [%g1] ASI_M_MMUREGS, %g5 +#ifndef CONFIG_SMP + cmp %o3, -1 + be hypersparc_flush_tlb_range_out +#endif + sethi %hi(~((1 << SRMMU_PGDIR_SHIFT) - 1)), %o4 + sta %o3, [%g1] ASI_M_MMUREGS + and %o1, %o4, %o1 + add %o1, 0x200, %o1 + sta %g0, [%o1] ASI_M_FLUSH_PROBE +1: + sub %o1, %o4, %o1 + cmp %o1, %o2 + blu,a 1b + sta %g0, [%o1] ASI_M_FLUSH_PROBE +hypersparc_flush_tlb_range_out: + retl + sta %g5, [%g1] ASI_M_MMUREGS + +hypersparc_flush_tlb_page: + ld [%o0 + 0x00], %o0 /* XXX vma->vm_mm GROSS XXX */ + mov SRMMU_CTX_REG, %g1 + ld [%o0 + AOFF_mm_context], %o3 + andn %o1, (PAGE_SIZE - 1), %o1 +#ifndef CONFIG_SMP + cmp %o3, -1 + be hypersparc_flush_tlb_page_out +#endif + lda [%g1] ASI_M_MMUREGS, %g5 + sta %o3, [%g1] ASI_M_MMUREGS + sta %g0, [%o1] ASI_M_FLUSH_PROBE +hypersparc_flush_tlb_page_out: + retl + sta %g5, [%g1] ASI_M_MMUREGS + + __INIT + + /* High speed page clear/copy. */ +hypersparc_bzero_1page: +/* NOTE: This routine has to be shorter than 40insns --jj */ + clr %g1 + mov 32, %g2 + mov 64, %g3 + mov 96, %g4 + mov 128, %g5 + mov 160, %g7 + mov 192, %o2 + mov 224, %o3 + mov 16, %o1 +1: + stda %g0, [%o0 + %g0] ASI_M_BFILL + stda %g0, [%o0 + %g2] ASI_M_BFILL + stda %g0, [%o0 + %g3] ASI_M_BFILL + stda %g0, [%o0 + %g4] ASI_M_BFILL + stda %g0, [%o0 + %g5] ASI_M_BFILL + stda %g0, [%o0 + %g7] ASI_M_BFILL + stda %g0, [%o0 + %o2] ASI_M_BFILL + stda %g0, [%o0 + %o3] ASI_M_BFILL + subcc %o1, 1, %o1 + bne 1b + add %o0, 256, %o0 + + retl + nop + +hypersparc_copy_1page: +/* NOTE: This routine has to be shorter than 70insns --jj */ + sub %o1, %o0, %o2 ! difference + mov 16, %g1 +1: + sta %o0, [%o0 + %o2] ASI_M_BCOPY + add %o0, 32, %o0 + sta %o0, [%o0 + %o2] ASI_M_BCOPY + add %o0, 32, %o0 + sta %o0, [%o0 + %o2] ASI_M_BCOPY + add %o0, 32, %o0 + sta %o0, [%o0 + %o2] ASI_M_BCOPY + add %o0, 32, %o0 + sta %o0, [%o0 + %o2] ASI_M_BCOPY + add %o0, 32, %o0 + sta %o0, [%o0 + %o2] ASI_M_BCOPY + add %o0, 32, %o0 + sta %o0, [%o0 + %o2] ASI_M_BCOPY + add %o0, 32, %o0 + sta %o0, [%o0 + %o2] ASI_M_BCOPY + subcc %g1, 1, %g1 + bne 1b + add %o0, 32, %o0 + + retl + nop + + .globl hypersparc_setup_blockops +hypersparc_setup_blockops: + sethi %hi(bzero_1page), %o0 + or %o0, %lo(bzero_1page), %o0 + sethi %hi(hypersparc_bzero_1page), %o1 + or %o1, %lo(hypersparc_bzero_1page), %o1 + sethi %hi(hypersparc_copy_1page), %o2 + or %o2, %lo(hypersparc_copy_1page), %o2 + ld [%o1], %o4 +1: + add %o1, 4, %o1 + st %o4, [%o0] + add %o0, 4, %o0 + cmp %o1, %o2 + bne 1b + ld [%o1], %o4 + sethi %hi(__copy_1page), %o0 + or %o0, %lo(__copy_1page), %o0 + sethi %hi(hypersparc_setup_blockops), %o2 + or %o2, %lo(hypersparc_setup_blockops), %o2 + ld [%o1], %o4 +1: + add %o1, 4, %o1 + st %o4, [%o0] + add %o0, 4, %o0 + cmp %o1, %o2 + bne 1b + ld [%o1], %o4 + sta %g0, [%g0] ASI_M_FLUSH_IWHOLE + retl + nop diff --git a/arch/sparc/mm/init.c b/arch/sparc/mm/init.c new file mode 100644 index 000000000000..a2dea69b2f07 --- /dev/null +++ b/arch/sparc/mm/init.c @@ -0,0 +1,515 @@ +/* $Id: init.c,v 1.103 2001/11/19 19:03:08 davem Exp $ + * linux/arch/sparc/mm/init.c + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + * Copyright (C) 1995 Eddie C. Dost (ecd@skynet.be) + * Copyright (C) 1998 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + * Copyright (C) 2000 Anton Blanchard (anton@samba.org) + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/ptrace.h> +#include <linux/mman.h> +#include <linux/mm.h> +#include <linux/swap.h> +#include <linux/initrd.h> +#include <linux/init.h> +#include <linux/highmem.h> +#include <linux/bootmem.h> + +#include <asm/system.h> +#include <asm/segment.h> +#include <asm/vac-ops.h> +#include <asm/page.h> +#include <asm/pgtable.h> +#include <asm/vaddrs.h> +#include <asm/pgalloc.h> /* bug in asm-generic/tlb.h: check_pgt_cache */ +#include <asm/tlb.h> + +DEFINE_PER_CPU(struct mmu_gather, mmu_gathers); + +unsigned long *sparc_valid_addr_bitmap; + +unsigned long phys_base; +unsigned long pfn_base; + +unsigned long page_kernel; + +struct sparc_phys_banks sp_banks[SPARC_PHYS_BANKS+1]; +unsigned long sparc_unmapped_base; + +struct pgtable_cache_struct pgt_quicklists; + +/* References to section boundaries */ +extern char __init_begin, __init_end, _start, _end, etext , edata; + +/* Initial ramdisk setup */ +extern unsigned int sparc_ramdisk_image; +extern unsigned int sparc_ramdisk_size; + +unsigned long highstart_pfn, highend_pfn; + +pte_t *kmap_pte; +pgprot_t kmap_prot; + +#define kmap_get_fixmap_pte(vaddr) \ + pte_offset_kernel(pmd_offset(pgd_offset_k(vaddr), (vaddr)), (vaddr)) + +void __init kmap_init(void) +{ + /* cache the first kmap pte */ + kmap_pte = kmap_get_fixmap_pte(__fix_to_virt(FIX_KMAP_BEGIN)); + kmap_prot = __pgprot(SRMMU_ET_PTE | SRMMU_PRIV | SRMMU_CACHE); +} + +void show_mem(void) +{ + printk("Mem-info:\n"); + show_free_areas(); + printk("Free swap: %6ldkB\n", + nr_swap_pages << (PAGE_SHIFT-10)); + printk("%ld pages of RAM\n", totalram_pages); + printk("%d free pages\n", nr_free_pages()); +#if 0 /* undefined pgtable_cache_size, pgd_cache_size */ + printk("%ld pages in page table cache\n",pgtable_cache_size); +#ifndef CONFIG_SMP + if (sparc_cpu_model == sun4m || sparc_cpu_model == sun4d) + printk("%ld entries in page dir cache\n",pgd_cache_size); +#endif +#endif +} + +void __init sparc_context_init(int numctx) +{ + int ctx; + + ctx_list_pool = __alloc_bootmem(numctx * sizeof(struct ctx_list), SMP_CACHE_BYTES, 0UL); + + for(ctx = 0; ctx < numctx; ctx++) { + struct ctx_list *clist; + + clist = (ctx_list_pool + ctx); + clist->ctx_number = ctx; + clist->ctx_mm = NULL; + } + ctx_free.next = ctx_free.prev = &ctx_free; + ctx_used.next = ctx_used.prev = &ctx_used; + for(ctx = 0; ctx < numctx; ctx++) + add_to_free_ctxlist(ctx_list_pool + ctx); +} + +extern unsigned long cmdline_memory_size; +unsigned long last_valid_pfn; + +unsigned long calc_highpages(void) +{ + int i; + int nr = 0; + + for (i = 0; sp_banks[i].num_bytes != 0; i++) { + unsigned long start_pfn = sp_banks[i].base_addr >> PAGE_SHIFT; + unsigned long end_pfn = (sp_banks[i].base_addr + sp_banks[i].num_bytes) >> PAGE_SHIFT; + + if (end_pfn <= max_low_pfn) + continue; + + if (start_pfn < max_low_pfn) + start_pfn = max_low_pfn; + + nr += end_pfn - start_pfn; + } + + return nr; +} + +unsigned long calc_max_low_pfn(void) +{ + int i; + unsigned long tmp = pfn_base + (SRMMU_MAXMEM >> PAGE_SHIFT); + unsigned long curr_pfn, last_pfn; + + last_pfn = (sp_banks[0].base_addr + sp_banks[0].num_bytes) >> PAGE_SHIFT; + for (i = 1; sp_banks[i].num_bytes != 0; i++) { + curr_pfn = sp_banks[i].base_addr >> PAGE_SHIFT; + + if (curr_pfn >= tmp) { + if (last_pfn < tmp) + tmp = last_pfn; + break; + } + + last_pfn = (sp_banks[i].base_addr + sp_banks[i].num_bytes) >> PAGE_SHIFT; + } + + return tmp; +} + +unsigned long __init bootmem_init(unsigned long *pages_avail) +{ + unsigned long bootmap_size, start_pfn; + unsigned long end_of_phys_memory = 0UL; + unsigned long bootmap_pfn, bytes_avail, size; + int i; + + bytes_avail = 0UL; + for (i = 0; sp_banks[i].num_bytes != 0; i++) { + end_of_phys_memory = sp_banks[i].base_addr + + sp_banks[i].num_bytes; + bytes_avail += sp_banks[i].num_bytes; + if (cmdline_memory_size) { + if (bytes_avail > cmdline_memory_size) { + unsigned long slack = bytes_avail - cmdline_memory_size; + + bytes_avail -= slack; + end_of_phys_memory -= slack; + + sp_banks[i].num_bytes -= slack; + if (sp_banks[i].num_bytes == 0) { + sp_banks[i].base_addr = 0xdeadbeef; + } else { + sp_banks[i+1].num_bytes = 0; + sp_banks[i+1].base_addr = 0xdeadbeef; + } + break; + } + } + } + + /* Start with page aligned address of last symbol in kernel + * image. + */ + start_pfn = (unsigned long)__pa(PAGE_ALIGN((unsigned long) &_end)); + + /* Now shift down to get the real physical page frame number. */ + start_pfn >>= PAGE_SHIFT; + + bootmap_pfn = start_pfn; + + max_pfn = end_of_phys_memory >> PAGE_SHIFT; + + max_low_pfn = max_pfn; + highstart_pfn = highend_pfn = max_pfn; + + if (max_low_pfn > pfn_base + (SRMMU_MAXMEM >> PAGE_SHIFT)) { + highstart_pfn = pfn_base + (SRMMU_MAXMEM >> PAGE_SHIFT); + max_low_pfn = calc_max_low_pfn(); + printk(KERN_NOTICE "%ldMB HIGHMEM available.\n", + calc_highpages() >> (20 - PAGE_SHIFT)); + } + +#ifdef CONFIG_BLK_DEV_INITRD + /* Now have to check initial ramdisk, so that bootmap does not overwrite it */ + if (sparc_ramdisk_image) { + if (sparc_ramdisk_image >= (unsigned long)&_end - 2 * PAGE_SIZE) + sparc_ramdisk_image -= KERNBASE; + initrd_start = sparc_ramdisk_image + phys_base; + initrd_end = initrd_start + sparc_ramdisk_size; + if (initrd_end > end_of_phys_memory) { + printk(KERN_CRIT "initrd extends beyond end of memory " + "(0x%016lx > 0x%016lx)\ndisabling initrd\n", + initrd_end, end_of_phys_memory); + initrd_start = 0; + } + if (initrd_start) { + if (initrd_start >= (start_pfn << PAGE_SHIFT) && + initrd_start < (start_pfn << PAGE_SHIFT) + 2 * PAGE_SIZE) + bootmap_pfn = PAGE_ALIGN (initrd_end) >> PAGE_SHIFT; + } + } +#endif + /* Initialize the boot-time allocator. */ + bootmap_size = init_bootmem_node(NODE_DATA(0), bootmap_pfn, pfn_base, + max_low_pfn); + + /* Now register the available physical memory with the + * allocator. + */ + *pages_avail = 0; + for (i = 0; sp_banks[i].num_bytes != 0; i++) { + unsigned long curr_pfn, last_pfn; + + curr_pfn = sp_banks[i].base_addr >> PAGE_SHIFT; + if (curr_pfn >= max_low_pfn) + break; + + last_pfn = (sp_banks[i].base_addr + sp_banks[i].num_bytes) >> PAGE_SHIFT; + if (last_pfn > max_low_pfn) + last_pfn = max_low_pfn; + + /* + * .. finally, did all the rounding and playing + * around just make the area go away? + */ + if (last_pfn <= curr_pfn) + continue; + + size = (last_pfn - curr_pfn) << PAGE_SHIFT; + *pages_avail += last_pfn - curr_pfn; + + free_bootmem(sp_banks[i].base_addr, size); + } + +#ifdef CONFIG_BLK_DEV_INITRD + if (initrd_start) { + /* Reserve the initrd image area. */ + size = initrd_end - initrd_start; + reserve_bootmem(initrd_start, size); + *pages_avail -= PAGE_ALIGN(size) >> PAGE_SHIFT; + + initrd_start = (initrd_start - phys_base) + PAGE_OFFSET; + initrd_end = (initrd_end - phys_base) + PAGE_OFFSET; + } +#endif + /* Reserve the kernel text/data/bss. */ + size = (start_pfn << PAGE_SHIFT) - phys_base; + reserve_bootmem(phys_base, size); + *pages_avail -= PAGE_ALIGN(size) >> PAGE_SHIFT; + + /* Reserve the bootmem map. We do not account for it + * in pages_avail because we will release that memory + * in free_all_bootmem. + */ + size = bootmap_size; + reserve_bootmem((bootmap_pfn << PAGE_SHIFT), size); + *pages_avail -= PAGE_ALIGN(size) >> PAGE_SHIFT; + + return max_pfn; +} + +/* + * check_pgt_cache + * + * This is called at the end of unmapping of VMA (zap_page_range), + * to rescan the page cache for architecture specific things, + * presumably something like sun4/sun4c PMEGs. Most architectures + * define check_pgt_cache empty. + * + * We simply copy the 2.4 implementation for now. + */ +int pgt_cache_water[2] = { 25, 50 }; + +void check_pgt_cache(void) +{ + do_check_pgt_cache(pgt_cache_water[0], pgt_cache_water[1]); +} + +/* + * paging_init() sets up the page tables: We call the MMU specific + * init routine based upon the Sun model type on the Sparc. + * + */ +extern void sun4c_paging_init(void); +extern void srmmu_paging_init(void); +extern void device_scan(void); + +void __init paging_init(void) +{ + switch(sparc_cpu_model) { + case sun4c: + case sun4e: + case sun4: + sun4c_paging_init(); + sparc_unmapped_base = 0xe0000000; + BTFIXUPSET_SETHI(sparc_unmapped_base, 0xe0000000); + break; + case sun4m: + case sun4d: + srmmu_paging_init(); + sparc_unmapped_base = 0x50000000; + BTFIXUPSET_SETHI(sparc_unmapped_base, 0x50000000); + break; + default: + prom_printf("paging_init: Cannot init paging on this Sparc\n"); + prom_printf("paging_init: sparc_cpu_model = %d\n", sparc_cpu_model); + prom_printf("paging_init: Halting...\n"); + prom_halt(); + }; + + /* Initialize the protection map with non-constant, MMU dependent values. */ + protection_map[0] = PAGE_NONE; + protection_map[1] = PAGE_READONLY; + protection_map[2] = PAGE_COPY; + protection_map[3] = PAGE_COPY; + protection_map[4] = PAGE_READONLY; + protection_map[5] = PAGE_READONLY; + protection_map[6] = PAGE_COPY; + protection_map[7] = PAGE_COPY; + protection_map[8] = PAGE_NONE; + protection_map[9] = PAGE_READONLY; + protection_map[10] = PAGE_SHARED; + protection_map[11] = PAGE_SHARED; + protection_map[12] = PAGE_READONLY; + protection_map[13] = PAGE_READONLY; + protection_map[14] = PAGE_SHARED; + protection_map[15] = PAGE_SHARED; + btfixup(); + device_scan(); +} + +struct cache_palias *sparc_aliases; + +static void __init taint_real_pages(void) +{ + int i; + + for (i = 0; sp_banks[i].num_bytes; i++) { + unsigned long start, end; + + start = sp_banks[i].base_addr; + end = start + sp_banks[i].num_bytes; + + while (start < end) { + set_bit(start >> 20, sparc_valid_addr_bitmap); + start += PAGE_SIZE; + } + } +} + +void map_high_region(unsigned long start_pfn, unsigned long end_pfn) +{ + unsigned long tmp; + +#ifdef CONFIG_DEBUG_HIGHMEM + printk("mapping high region %08lx - %08lx\n", start_pfn, end_pfn); +#endif + + for (tmp = start_pfn; tmp < end_pfn; tmp++) { + struct page *page = pfn_to_page(tmp); + + ClearPageReserved(page); + set_bit(PG_highmem, &page->flags); + set_page_count(page, 1); + __free_page(page); + totalhigh_pages++; + } +} + +void __init mem_init(void) +{ + int codepages = 0; + int datapages = 0; + int initpages = 0; + int reservedpages = 0; + int i; + + if (PKMAP_BASE+LAST_PKMAP*PAGE_SIZE >= FIXADDR_START) { + prom_printf("BUG: fixmap and pkmap areas overlap\n"); + prom_printf("pkbase: 0x%lx pkend: 0x%lx fixstart 0x%lx\n", + PKMAP_BASE, + (unsigned long)PKMAP_BASE+LAST_PKMAP*PAGE_SIZE, + FIXADDR_START); + prom_printf("Please mail sparclinux@vger.kernel.org.\n"); + prom_halt(); + } + + + /* Saves us work later. */ + memset((void *)&empty_zero_page, 0, PAGE_SIZE); + + i = last_valid_pfn >> ((20 - PAGE_SHIFT) + 5); + i += 1; + sparc_valid_addr_bitmap = (unsigned long *) + __alloc_bootmem(i << 2, SMP_CACHE_BYTES, 0UL); + + if (sparc_valid_addr_bitmap == NULL) { + prom_printf("mem_init: Cannot alloc valid_addr_bitmap.\n"); + prom_halt(); + } + memset(sparc_valid_addr_bitmap, 0, i << 2); + + taint_real_pages(); + + max_mapnr = last_valid_pfn - pfn_base; + high_memory = __va(max_low_pfn << PAGE_SHIFT); + + totalram_pages = free_all_bootmem(); + + for (i = 0; sp_banks[i].num_bytes != 0; i++) { + unsigned long start_pfn = sp_banks[i].base_addr >> PAGE_SHIFT; + unsigned long end_pfn = (sp_banks[i].base_addr + sp_banks[i].num_bytes) >> PAGE_SHIFT; + + num_physpages += sp_banks[i].num_bytes >> PAGE_SHIFT; + + if (end_pfn <= highstart_pfn) + continue; + + if (start_pfn < highstart_pfn) + start_pfn = highstart_pfn; + + map_high_region(start_pfn, end_pfn); + } + + totalram_pages += totalhigh_pages; + + codepages = (((unsigned long) &etext) - ((unsigned long)&_start)); + codepages = PAGE_ALIGN(codepages) >> PAGE_SHIFT; + datapages = (((unsigned long) &edata) - ((unsigned long)&etext)); + datapages = PAGE_ALIGN(datapages) >> PAGE_SHIFT; + initpages = (((unsigned long) &__init_end) - ((unsigned long) &__init_begin)); + initpages = PAGE_ALIGN(initpages) >> PAGE_SHIFT; + + /* Ignore memory holes for the purpose of counting reserved pages */ + for (i=0; i < max_low_pfn; i++) + if (test_bit(i >> (20 - PAGE_SHIFT), sparc_valid_addr_bitmap) + && PageReserved(pfn_to_page(i))) + reservedpages++; + + printk(KERN_INFO "Memory: %luk/%luk available (%dk kernel code, %dk reserved, %dk data, %dk init, %ldk highmem)\n", + (unsigned long) nr_free_pages() << (PAGE_SHIFT-10), + num_physpages << (PAGE_SHIFT - 10), + codepages << (PAGE_SHIFT-10), + reservedpages << (PAGE_SHIFT - 10), + datapages << (PAGE_SHIFT-10), + initpages << (PAGE_SHIFT-10), + totalhigh_pages << (PAGE_SHIFT-10)); +} + +void free_initmem (void) +{ + unsigned long addr; + + addr = (unsigned long)(&__init_begin); + for (; addr < (unsigned long)(&__init_end); addr += PAGE_SIZE) { + struct page *p; + + p = virt_to_page(addr); + + ClearPageReserved(p); + set_page_count(p, 1); + __free_page(p); + totalram_pages++; + num_physpages++; + } + printk (KERN_INFO "Freeing unused kernel memory: %dk freed\n", (&__init_end - &__init_begin) >> 10); +} + +#ifdef CONFIG_BLK_DEV_INITRD +void free_initrd_mem(unsigned long start, unsigned long end) +{ + if (start < end) + printk (KERN_INFO "Freeing initrd memory: %ldk freed\n", (end - start) >> 10); + for (; start < end; start += PAGE_SIZE) { + struct page *p = virt_to_page(start); + + ClearPageReserved(p); + set_page_count(p, 1); + __free_page(p); + num_physpages++; + } +} +#endif + +void sparc_flush_page_to_ram(struct page *page) +{ + unsigned long vaddr = (unsigned long)page_address(page); + + if (vaddr) + __flush_page_to_ram(vaddr); +} diff --git a/arch/sparc/mm/io-unit.c b/arch/sparc/mm/io-unit.c new file mode 100644 index 000000000000..eefffa1dc5de --- /dev/null +++ b/arch/sparc/mm/io-unit.c @@ -0,0 +1,318 @@ +/* $Id: io-unit.c,v 1.24 2001/12/17 07:05:09 davem Exp $ + * io-unit.c: IO-UNIT specific routines for memory management. + * + * Copyright (C) 1997,1998 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + */ + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/mm.h> +#include <linux/highmem.h> /* pte_offset_map => kmap_atomic */ +#include <linux/bitops.h> + +#include <asm/scatterlist.h> +#include <asm/pgalloc.h> +#include <asm/pgtable.h> +#include <asm/sbus.h> +#include <asm/io.h> +#include <asm/io-unit.h> +#include <asm/mxcc.h> +#include <asm/cacheflush.h> +#include <asm/tlbflush.h> +#include <asm/dma.h> + +/* #define IOUNIT_DEBUG */ +#ifdef IOUNIT_DEBUG +#define IOD(x) printk(x) +#else +#define IOD(x) do { } while (0) +#endif + +#define IOPERM (IOUPTE_CACHE | IOUPTE_WRITE | IOUPTE_VALID) +#define MKIOPTE(phys) __iopte((((phys)>>4) & IOUPTE_PAGE) | IOPERM) + +void __init +iounit_init(int sbi_node, int io_node, struct sbus_bus *sbus) +{ + iopte_t *xpt, *xptend; + struct iounit_struct *iounit; + struct linux_prom_registers iommu_promregs[PROMREG_MAX]; + struct resource r; + + iounit = kmalloc(sizeof(struct iounit_struct), GFP_ATOMIC); + + memset(iounit, 0, sizeof(*iounit)); + iounit->limit[0] = IOUNIT_BMAP1_START; + iounit->limit[1] = IOUNIT_BMAP2_START; + iounit->limit[2] = IOUNIT_BMAPM_START; + iounit->limit[3] = IOUNIT_BMAPM_END; + iounit->rotor[1] = IOUNIT_BMAP2_START; + iounit->rotor[2] = IOUNIT_BMAPM_START; + + xpt = NULL; + if(prom_getproperty(sbi_node, "reg", (void *) iommu_promregs, + sizeof(iommu_promregs)) != -1) { + prom_apply_generic_ranges(io_node, 0, iommu_promregs, 3); + memset(&r, 0, sizeof(r)); + r.flags = iommu_promregs[2].which_io; + r.start = iommu_promregs[2].phys_addr; + xpt = (iopte_t *) sbus_ioremap(&r, 0, PAGE_SIZE * 16, "XPT"); + } + if(!xpt) panic("Cannot map External Page Table."); + + sbus->iommu = (struct iommu_struct *)iounit; + iounit->page_table = xpt; + + for (xptend = iounit->page_table + (16 * PAGE_SIZE) / sizeof(iopte_t); + xpt < xptend;) + iopte_val(*xpt++) = 0; +} + +/* One has to hold iounit->lock to call this */ +static unsigned long iounit_get_area(struct iounit_struct *iounit, unsigned long vaddr, int size) +{ + int i, j, k, npages; + unsigned long rotor, scan, limit; + iopte_t iopte; + + npages = ((vaddr & ~PAGE_MASK) + size + (PAGE_SIZE-1)) >> PAGE_SHIFT; + + /* A tiny bit of magic ingredience :) */ + switch (npages) { + case 1: i = 0x0231; break; + case 2: i = 0x0132; break; + default: i = 0x0213; break; + } + + IOD(("iounit_get_area(%08lx,%d[%d])=", vaddr, size, npages)); + +next: j = (i & 15); + rotor = iounit->rotor[j - 1]; + limit = iounit->limit[j]; + scan = rotor; +nexti: scan = find_next_zero_bit(iounit->bmap, limit, scan); + if (scan + npages > limit) { + if (limit != rotor) { + limit = rotor; + scan = iounit->limit[j - 1]; + goto nexti; + } + i >>= 4; + if (!(i & 15)) + panic("iounit_get_area: Couldn't find free iopte slots for (%08lx,%d)\n", vaddr, size); + goto next; + } + for (k = 1, scan++; k < npages; k++) + if (test_bit(scan++, iounit->bmap)) + goto nexti; + iounit->rotor[j - 1] = (scan < limit) ? scan : iounit->limit[j - 1]; + scan -= npages; + iopte = MKIOPTE(__pa(vaddr & PAGE_MASK)); + vaddr = IOUNIT_DMA_BASE + (scan << PAGE_SHIFT) + (vaddr & ~PAGE_MASK); + for (k = 0; k < npages; k++, iopte = __iopte(iopte_val(iopte) + 0x100), scan++) { + set_bit(scan, iounit->bmap); + iounit->page_table[scan] = iopte; + } + IOD(("%08lx\n", vaddr)); + return vaddr; +} + +static __u32 iounit_get_scsi_one(char *vaddr, unsigned long len, struct sbus_bus *sbus) +{ + unsigned long ret, flags; + struct iounit_struct *iounit = (struct iounit_struct *)sbus->iommu; + + spin_lock_irqsave(&iounit->lock, flags); + ret = iounit_get_area(iounit, (unsigned long)vaddr, len); + spin_unlock_irqrestore(&iounit->lock, flags); + return ret; +} + +static void iounit_get_scsi_sgl(struct scatterlist *sg, int sz, struct sbus_bus *sbus) +{ + unsigned long flags; + struct iounit_struct *iounit = (struct iounit_struct *)sbus->iommu; + + /* FIXME: Cache some resolved pages - often several sg entries are to the same page */ + spin_lock_irqsave(&iounit->lock, flags); + while (sz != 0) { + --sz; + sg[sz].dvma_address = iounit_get_area(iounit, (unsigned long)page_address(sg[sz].page) + sg[sz].offset, sg[sz].length); + sg[sz].dvma_length = sg[sz].length; + } + spin_unlock_irqrestore(&iounit->lock, flags); +} + +static void iounit_release_scsi_one(__u32 vaddr, unsigned long len, struct sbus_bus *sbus) +{ + unsigned long flags; + struct iounit_struct *iounit = (struct iounit_struct *)sbus->iommu; + + spin_lock_irqsave(&iounit->lock, flags); + len = ((vaddr & ~PAGE_MASK) + len + (PAGE_SIZE-1)) >> PAGE_SHIFT; + vaddr = (vaddr - IOUNIT_DMA_BASE) >> PAGE_SHIFT; + IOD(("iounit_release %08lx-%08lx\n", (long)vaddr, (long)len+vaddr)); + for (len += vaddr; vaddr < len; vaddr++) + clear_bit(vaddr, iounit->bmap); + spin_unlock_irqrestore(&iounit->lock, flags); +} + +static void iounit_release_scsi_sgl(struct scatterlist *sg, int sz, struct sbus_bus *sbus) +{ + unsigned long flags; + unsigned long vaddr, len; + struct iounit_struct *iounit = (struct iounit_struct *)sbus->iommu; + + spin_lock_irqsave(&iounit->lock, flags); + while (sz != 0) { + --sz; + len = ((sg[sz].dvma_address & ~PAGE_MASK) + sg[sz].length + (PAGE_SIZE-1)) >> PAGE_SHIFT; + vaddr = (sg[sz].dvma_address - IOUNIT_DMA_BASE) >> PAGE_SHIFT; + IOD(("iounit_release %08lx-%08lx\n", (long)vaddr, (long)len+vaddr)); + for (len += vaddr; vaddr < len; vaddr++) + clear_bit(vaddr, iounit->bmap); + } + spin_unlock_irqrestore(&iounit->lock, flags); +} + +#ifdef CONFIG_SBUS +static int iounit_map_dma_area(dma_addr_t *pba, unsigned long va, __u32 addr, int len) +{ + unsigned long page, end; + pgprot_t dvma_prot; + iopte_t *iopte; + struct sbus_bus *sbus; + + *pba = addr; + + dvma_prot = __pgprot(SRMMU_CACHE | SRMMU_ET_PTE | SRMMU_PRIV); + end = PAGE_ALIGN((addr + len)); + while(addr < end) { + page = va; + { + pgd_t *pgdp; + pmd_t *pmdp; + pte_t *ptep; + long i; + + pgdp = pgd_offset(&init_mm, addr); + pmdp = pmd_offset(pgdp, addr); + ptep = pte_offset_map(pmdp, addr); + + set_pte(ptep, mk_pte(virt_to_page(page), dvma_prot)); + + i = ((addr - IOUNIT_DMA_BASE) >> PAGE_SHIFT); + + for_each_sbus(sbus) { + struct iounit_struct *iounit = (struct iounit_struct *)sbus->iommu; + + iopte = (iopte_t *)(iounit->page_table + i); + *iopte = MKIOPTE(__pa(page)); + } + } + addr += PAGE_SIZE; + va += PAGE_SIZE; + } + flush_cache_all(); + flush_tlb_all(); + + return 0; +} + +static void iounit_unmap_dma_area(unsigned long addr, int len) +{ + /* XXX Somebody please fill this in */ +} + +/* XXX We do not pass sbus device here, bad. */ +static struct page *iounit_translate_dvma(unsigned long addr) +{ + struct sbus_bus *sbus = sbus_root; /* They are all the same */ + struct iounit_struct *iounit = (struct iounit_struct *)sbus->iommu; + int i; + iopte_t *iopte; + + i = ((addr - IOUNIT_DMA_BASE) >> PAGE_SHIFT); + iopte = (iopte_t *)(iounit->page_table + i); + return pfn_to_page(iopte_val(*iopte) >> (PAGE_SHIFT-4)); /* XXX sun4d guru, help */ +} +#endif + +static char *iounit_lockarea(char *vaddr, unsigned long len) +{ +/* FIXME: Write this */ + return vaddr; +} + +static void iounit_unlockarea(char *vaddr, unsigned long len) +{ +/* FIXME: Write this */ +} + +void __init ld_mmu_iounit(void) +{ + BTFIXUPSET_CALL(mmu_lockarea, iounit_lockarea, BTFIXUPCALL_RETO0); + BTFIXUPSET_CALL(mmu_unlockarea, iounit_unlockarea, BTFIXUPCALL_NOP); + + BTFIXUPSET_CALL(mmu_get_scsi_one, iounit_get_scsi_one, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(mmu_get_scsi_sgl, iounit_get_scsi_sgl, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(mmu_release_scsi_one, iounit_release_scsi_one, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(mmu_release_scsi_sgl, iounit_release_scsi_sgl, BTFIXUPCALL_NORM); + +#ifdef CONFIG_SBUS + BTFIXUPSET_CALL(mmu_map_dma_area, iounit_map_dma_area, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(mmu_unmap_dma_area, iounit_unmap_dma_area, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(mmu_translate_dvma, iounit_translate_dvma, BTFIXUPCALL_NORM); +#endif +} + +__u32 iounit_map_dma_init(struct sbus_bus *sbus, int size) +{ + int i, j, k, npages; + unsigned long rotor, scan, limit; + unsigned long flags; + __u32 ret; + struct iounit_struct *iounit = (struct iounit_struct *)sbus->iommu; + + npages = (size + (PAGE_SIZE-1)) >> PAGE_SHIFT; + i = 0x0213; + spin_lock_irqsave(&iounit->lock, flags); +next: j = (i & 15); + rotor = iounit->rotor[j - 1]; + limit = iounit->limit[j]; + scan = rotor; +nexti: scan = find_next_zero_bit(iounit->bmap, limit, scan); + if (scan + npages > limit) { + if (limit != rotor) { + limit = rotor; + scan = iounit->limit[j - 1]; + goto nexti; + } + i >>= 4; + if (!(i & 15)) + panic("iounit_map_dma_init: Couldn't find free iopte slots for %d bytes\n", size); + goto next; + } + for (k = 1, scan++; k < npages; k++) + if (test_bit(scan++, iounit->bmap)) + goto nexti; + iounit->rotor[j - 1] = (scan < limit) ? scan : iounit->limit[j - 1]; + scan -= npages; + ret = IOUNIT_DMA_BASE + (scan << PAGE_SHIFT); + for (k = 0; k < npages; k++, scan++) + set_bit(scan, iounit->bmap); + spin_unlock_irqrestore(&iounit->lock, flags); + return ret; +} + +__u32 iounit_map_dma_page(__u32 vaddr, void *addr, struct sbus_bus *sbus) +{ + int scan = (vaddr - IOUNIT_DMA_BASE) >> PAGE_SHIFT; + struct iounit_struct *iounit = (struct iounit_struct *)sbus->iommu; + + iounit->page_table[scan] = MKIOPTE(__pa(((unsigned long)addr) & PAGE_MASK)); + return vaddr + (((unsigned long)addr) & ~PAGE_MASK); +} diff --git a/arch/sparc/mm/iommu.c b/arch/sparc/mm/iommu.c new file mode 100644 index 000000000000..489bf68d5f05 --- /dev/null +++ b/arch/sparc/mm/iommu.c @@ -0,0 +1,475 @@ +/* + * iommu.c: IOMMU specific routines for memory management. + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + * Copyright (C) 1995,2002 Pete Zaitcev (zaitcev@yahoo.com) + * Copyright (C) 1996 Eddie C. Dost (ecd@skynet.be) + * Copyright (C) 1997,1998 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + */ + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/highmem.h> /* pte_offset_map => kmap_atomic */ + +#include <asm/scatterlist.h> +#include <asm/pgalloc.h> +#include <asm/pgtable.h> +#include <asm/sbus.h> +#include <asm/io.h> +#include <asm/mxcc.h> +#include <asm/mbus.h> +#include <asm/cacheflush.h> +#include <asm/tlbflush.h> +#include <asm/bitext.h> +#include <asm/iommu.h> +#include <asm/dma.h> + +/* + * This can be sized dynamically, but we will do this + * only when we have a guidance about actual I/O pressures. + */ +#define IOMMU_RNGE IOMMU_RNGE_256MB +#define IOMMU_START 0xF0000000 +#define IOMMU_WINSIZE (256*1024*1024U) +#define IOMMU_NPTES (IOMMU_WINSIZE/PAGE_SIZE) /* 64K PTEs, 265KB */ +#define IOMMU_ORDER 6 /* 4096 * (1<<6) */ + +/* srmmu.c */ +extern int viking_mxcc_present; +BTFIXUPDEF_CALL(void, flush_page_for_dma, unsigned long) +#define flush_page_for_dma(page) BTFIXUP_CALL(flush_page_for_dma)(page) +extern int flush_page_for_dma_global; +static int viking_flush; +/* viking.S */ +extern void viking_flush_page(unsigned long page); +extern void viking_mxcc_flush_page(unsigned long page); + +/* + * Values precomputed according to CPU type. + */ +static unsigned int ioperm_noc; /* Consistent mapping iopte flags */ +static pgprot_t dvma_prot; /* Consistent mapping pte flags */ + +#define IOPERM (IOPTE_CACHE | IOPTE_WRITE | IOPTE_VALID) +#define MKIOPTE(pfn, perm) (((((pfn)<<8) & IOPTE_PAGE) | (perm)) & ~IOPTE_WAZ) + +void __init +iommu_init(int iommund, struct sbus_bus *sbus) +{ + unsigned int impl, vers; + unsigned long tmp; + struct iommu_struct *iommu; + struct linux_prom_registers iommu_promregs[PROMREG_MAX]; + struct resource r; + unsigned long *bitmap; + + iommu = kmalloc(sizeof(struct iommu_struct), GFP_ATOMIC); + if (!iommu) { + prom_printf("Unable to allocate iommu structure\n"); + prom_halt(); + } + iommu->regs = NULL; + if (prom_getproperty(iommund, "reg", (void *) iommu_promregs, + sizeof(iommu_promregs)) != -1) { + memset(&r, 0, sizeof(r)); + r.flags = iommu_promregs[0].which_io; + r.start = iommu_promregs[0].phys_addr; + iommu->regs = (struct iommu_regs *) + sbus_ioremap(&r, 0, PAGE_SIZE * 3, "iommu_regs"); + } + if (!iommu->regs) { + prom_printf("Cannot map IOMMU registers\n"); + prom_halt(); + } + impl = (iommu->regs->control & IOMMU_CTRL_IMPL) >> 28; + vers = (iommu->regs->control & IOMMU_CTRL_VERS) >> 24; + tmp = iommu->regs->control; + tmp &= ~(IOMMU_CTRL_RNGE); + tmp |= (IOMMU_RNGE_256MB | IOMMU_CTRL_ENAB); + iommu->regs->control = tmp; + iommu_invalidate(iommu->regs); + iommu->start = IOMMU_START; + iommu->end = 0xffffffff; + + /* Allocate IOMMU page table */ + /* Stupid alignment constraints give me a headache. + We need 256K or 512K or 1M or 2M area aligned to + its size and current gfp will fortunately give + it to us. */ + tmp = __get_free_pages(GFP_KERNEL, IOMMU_ORDER); + if (!tmp) { + prom_printf("Unable to allocate iommu table [0x%08x]\n", + IOMMU_NPTES*sizeof(iopte_t)); + prom_halt(); + } + iommu->page_table = (iopte_t *)tmp; + + /* Initialize new table. */ + memset(iommu->page_table, 0, IOMMU_NPTES*sizeof(iopte_t)); + flush_cache_all(); + flush_tlb_all(); + iommu->regs->base = __pa((unsigned long) iommu->page_table) >> 4; + iommu_invalidate(iommu->regs); + + bitmap = kmalloc(IOMMU_NPTES>>3, GFP_KERNEL); + if (!bitmap) { + prom_printf("Unable to allocate iommu bitmap [%d]\n", + (int)(IOMMU_NPTES>>3)); + prom_halt(); + } + bit_map_init(&iommu->usemap, bitmap, IOMMU_NPTES); + /* To be coherent on HyperSparc, the page color of DVMA + * and physical addresses must match. + */ + if (srmmu_modtype == HyperSparc) + iommu->usemap.num_colors = vac_cache_size >> PAGE_SHIFT; + else + iommu->usemap.num_colors = 1; + + printk("IOMMU: impl %d vers %d table 0x%p[%d B] map [%d b]\n", + impl, vers, iommu->page_table, + (int)(IOMMU_NPTES*sizeof(iopte_t)), (int)IOMMU_NPTES); + + sbus->iommu = iommu; +} + +/* This begs to be btfixup-ed by srmmu. */ +/* Flush the iotlb entries to ram. */ +/* This could be better if we didn't have to flush whole pages. */ +static void iommu_flush_iotlb(iopte_t *iopte, unsigned int niopte) +{ + unsigned long start; + unsigned long end; + + start = (unsigned long)iopte & PAGE_MASK; + end = PAGE_ALIGN(start + niopte*sizeof(iopte_t)); + if (viking_mxcc_present) { + while(start < end) { + viking_mxcc_flush_page(start); + start += PAGE_SIZE; + } + } else if (viking_flush) { + while(start < end) { + viking_flush_page(start); + start += PAGE_SIZE; + } + } else { + while(start < end) { + __flush_page_to_ram(start); + start += PAGE_SIZE; + } + } +} + +static u32 iommu_get_one(struct page *page, int npages, struct sbus_bus *sbus) +{ + struct iommu_struct *iommu = sbus->iommu; + int ioptex; + iopte_t *iopte, *iopte0; + unsigned int busa, busa0; + int i; + + /* page color = pfn of page */ + ioptex = bit_map_string_get(&iommu->usemap, npages, page_to_pfn(page)); + if (ioptex < 0) + panic("iommu out"); + busa0 = iommu->start + (ioptex << PAGE_SHIFT); + iopte0 = &iommu->page_table[ioptex]; + + busa = busa0; + iopte = iopte0; + for (i = 0; i < npages; i++) { + iopte_val(*iopte) = MKIOPTE(page_to_pfn(page), IOPERM); + iommu_invalidate_page(iommu->regs, busa); + busa += PAGE_SIZE; + iopte++; + page++; + } + + iommu_flush_iotlb(iopte0, npages); + + return busa0; +} + +static u32 iommu_get_scsi_one(char *vaddr, unsigned int len, + struct sbus_bus *sbus) +{ + unsigned long off; + int npages; + struct page *page; + u32 busa; + + off = (unsigned long)vaddr & ~PAGE_MASK; + npages = (off + len + PAGE_SIZE-1) >> PAGE_SHIFT; + page = virt_to_page((unsigned long)vaddr & PAGE_MASK); + busa = iommu_get_one(page, npages, sbus); + return busa + off; +} + +static __u32 iommu_get_scsi_one_noflush(char *vaddr, unsigned long len, struct sbus_bus *sbus) +{ + return iommu_get_scsi_one(vaddr, len, sbus); +} + +static __u32 iommu_get_scsi_one_gflush(char *vaddr, unsigned long len, struct sbus_bus *sbus) +{ + flush_page_for_dma(0); + return iommu_get_scsi_one(vaddr, len, sbus); +} + +static __u32 iommu_get_scsi_one_pflush(char *vaddr, unsigned long len, struct sbus_bus *sbus) +{ + unsigned long page = ((unsigned long) vaddr) & PAGE_MASK; + + while(page < ((unsigned long)(vaddr + len))) { + flush_page_for_dma(page); + page += PAGE_SIZE; + } + return iommu_get_scsi_one(vaddr, len, sbus); +} + +static void iommu_get_scsi_sgl_noflush(struct scatterlist *sg, int sz, struct sbus_bus *sbus) +{ + int n; + + while (sz != 0) { + --sz; + n = (sg->length + sg->offset + PAGE_SIZE-1) >> PAGE_SHIFT; + sg->dvma_address = iommu_get_one(sg->page, n, sbus) + sg->offset; + sg->dvma_length = (__u32) sg->length; + sg++; + } +} + +static void iommu_get_scsi_sgl_gflush(struct scatterlist *sg, int sz, struct sbus_bus *sbus) +{ + int n; + + flush_page_for_dma(0); + while (sz != 0) { + --sz; + n = (sg->length + sg->offset + PAGE_SIZE-1) >> PAGE_SHIFT; + sg->dvma_address = iommu_get_one(sg->page, n, sbus) + sg->offset; + sg->dvma_length = (__u32) sg->length; + sg++; + } +} + +static void iommu_get_scsi_sgl_pflush(struct scatterlist *sg, int sz, struct sbus_bus *sbus) +{ + unsigned long page, oldpage = 0; + int n, i; + + while(sz != 0) { + --sz; + + n = (sg->length + sg->offset + PAGE_SIZE-1) >> PAGE_SHIFT; + + /* + * We expect unmapped highmem pages to be not in the cache. + * XXX Is this a good assumption? + * XXX What if someone else unmaps it here and races us? + */ + if ((page = (unsigned long) page_address(sg->page)) != 0) { + for (i = 0; i < n; i++) { + if (page != oldpage) { /* Already flushed? */ + flush_page_for_dma(page); + oldpage = page; + } + page += PAGE_SIZE; + } + } + + sg->dvma_address = iommu_get_one(sg->page, n, sbus) + sg->offset; + sg->dvma_length = (__u32) sg->length; + sg++; + } +} + +static void iommu_release_one(u32 busa, int npages, struct sbus_bus *sbus) +{ + struct iommu_struct *iommu = sbus->iommu; + int ioptex; + int i; + + if (busa < iommu->start) + BUG(); + ioptex = (busa - iommu->start) >> PAGE_SHIFT; + for (i = 0; i < npages; i++) { + iopte_val(iommu->page_table[ioptex + i]) = 0; + iommu_invalidate_page(iommu->regs, busa); + busa += PAGE_SIZE; + } + bit_map_clear(&iommu->usemap, ioptex, npages); +} + +static void iommu_release_scsi_one(__u32 vaddr, unsigned long len, struct sbus_bus *sbus) +{ + unsigned long off; + int npages; + + off = vaddr & ~PAGE_MASK; + npages = (off + len + PAGE_SIZE-1) >> PAGE_SHIFT; + iommu_release_one(vaddr & PAGE_MASK, npages, sbus); +} + +static void iommu_release_scsi_sgl(struct scatterlist *sg, int sz, struct sbus_bus *sbus) +{ + int n; + + while(sz != 0) { + --sz; + + n = (sg->length + sg->offset + PAGE_SIZE-1) >> PAGE_SHIFT; + iommu_release_one(sg->dvma_address & PAGE_MASK, n, sbus); + sg->dvma_address = 0x21212121; + sg++; + } +} + +#ifdef CONFIG_SBUS +static int iommu_map_dma_area(dma_addr_t *pba, unsigned long va, + unsigned long addr, int len) +{ + unsigned long page, end; + struct iommu_struct *iommu = sbus_root->iommu; + iopte_t *iopte = iommu->page_table; + iopte_t *first; + int ioptex; + + if ((va & ~PAGE_MASK) != 0) BUG(); + if ((addr & ~PAGE_MASK) != 0) BUG(); + if ((len & ~PAGE_MASK) != 0) BUG(); + + /* page color = physical address */ + ioptex = bit_map_string_get(&iommu->usemap, len >> PAGE_SHIFT, + addr >> PAGE_SHIFT); + if (ioptex < 0) + panic("iommu out"); + + iopte += ioptex; + first = iopte; + end = addr + len; + while(addr < end) { + page = va; + { + pgd_t *pgdp; + pmd_t *pmdp; + pte_t *ptep; + + if (viking_mxcc_present) + viking_mxcc_flush_page(page); + else if (viking_flush) + viking_flush_page(page); + else + __flush_page_to_ram(page); + + pgdp = pgd_offset(&init_mm, addr); + pmdp = pmd_offset(pgdp, addr); + ptep = pte_offset_map(pmdp, addr); + + set_pte(ptep, mk_pte(virt_to_page(page), dvma_prot)); + } + iopte_val(*iopte++) = + MKIOPTE(page_to_pfn(virt_to_page(page)), ioperm_noc); + addr += PAGE_SIZE; + va += PAGE_SIZE; + } + /* P3: why do we need this? + * + * DAVEM: Because there are several aspects, none of which + * are handled by a single interface. Some cpus are + * completely not I/O DMA coherent, and some have + * virtually indexed caches. The driver DMA flushing + * methods handle the former case, but here during + * IOMMU page table modifications, and usage of non-cacheable + * cpu mappings of pages potentially in the cpu caches, we have + * to handle the latter case as well. + */ + flush_cache_all(); + iommu_flush_iotlb(first, len >> PAGE_SHIFT); + flush_tlb_all(); + iommu_invalidate(iommu->regs); + + *pba = iommu->start + (ioptex << PAGE_SHIFT); + return 0; +} + +static void iommu_unmap_dma_area(unsigned long busa, int len) +{ + struct iommu_struct *iommu = sbus_root->iommu; + iopte_t *iopte = iommu->page_table; + unsigned long end; + int ioptex = (busa - iommu->start) >> PAGE_SHIFT; + + if ((busa & ~PAGE_MASK) != 0) BUG(); + if ((len & ~PAGE_MASK) != 0) BUG(); + + iopte += ioptex; + end = busa + len; + while (busa < end) { + iopte_val(*iopte++) = 0; + busa += PAGE_SIZE; + } + flush_tlb_all(); + iommu_invalidate(iommu->regs); + bit_map_clear(&iommu->usemap, ioptex, len >> PAGE_SHIFT); +} + +static struct page *iommu_translate_dvma(unsigned long busa) +{ + struct iommu_struct *iommu = sbus_root->iommu; + iopte_t *iopte = iommu->page_table; + + iopte += ((busa - iommu->start) >> PAGE_SHIFT); + return pfn_to_page((iopte_val(*iopte) & IOPTE_PAGE) >> (PAGE_SHIFT-4)); +} +#endif + +static char *iommu_lockarea(char *vaddr, unsigned long len) +{ + return vaddr; +} + +static void iommu_unlockarea(char *vaddr, unsigned long len) +{ +} + +void __init ld_mmu_iommu(void) +{ + viking_flush = (BTFIXUPVAL_CALL(flush_page_for_dma) == (unsigned long)viking_flush_page); + BTFIXUPSET_CALL(mmu_lockarea, iommu_lockarea, BTFIXUPCALL_RETO0); + BTFIXUPSET_CALL(mmu_unlockarea, iommu_unlockarea, BTFIXUPCALL_NOP); + + if (!BTFIXUPVAL_CALL(flush_page_for_dma)) { + /* IO coherent chip */ + BTFIXUPSET_CALL(mmu_get_scsi_one, iommu_get_scsi_one_noflush, BTFIXUPCALL_RETO0); + BTFIXUPSET_CALL(mmu_get_scsi_sgl, iommu_get_scsi_sgl_noflush, BTFIXUPCALL_NORM); + } else if (flush_page_for_dma_global) { + /* flush_page_for_dma flushes everything, no matter of what page is it */ + BTFIXUPSET_CALL(mmu_get_scsi_one, iommu_get_scsi_one_gflush, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(mmu_get_scsi_sgl, iommu_get_scsi_sgl_gflush, BTFIXUPCALL_NORM); + } else { + BTFIXUPSET_CALL(mmu_get_scsi_one, iommu_get_scsi_one_pflush, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(mmu_get_scsi_sgl, iommu_get_scsi_sgl_pflush, BTFIXUPCALL_NORM); + } + BTFIXUPSET_CALL(mmu_release_scsi_one, iommu_release_scsi_one, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(mmu_release_scsi_sgl, iommu_release_scsi_sgl, BTFIXUPCALL_NORM); + +#ifdef CONFIG_SBUS + BTFIXUPSET_CALL(mmu_map_dma_area, iommu_map_dma_area, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(mmu_unmap_dma_area, iommu_unmap_dma_area, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(mmu_translate_dvma, iommu_translate_dvma, BTFIXUPCALL_NORM); +#endif + + if (viking_mxcc_present || srmmu_modtype == HyperSparc) { + dvma_prot = __pgprot(SRMMU_CACHE | SRMMU_ET_PTE | SRMMU_PRIV); + ioperm_noc = IOPTE_CACHE | IOPTE_WRITE | IOPTE_VALID; + } else { + dvma_prot = __pgprot(SRMMU_ET_PTE | SRMMU_PRIV); + ioperm_noc = IOPTE_WRITE | IOPTE_VALID; + } +} diff --git a/arch/sparc/mm/loadmmu.c b/arch/sparc/mm/loadmmu.c new file mode 100644 index 000000000000..e9f9571601ba --- /dev/null +++ b/arch/sparc/mm/loadmmu.c @@ -0,0 +1,46 @@ +/* $Id: loadmmu.c,v 1.56 2000/02/08 20:24:21 davem Exp $ + * loadmmu.c: This code loads up all the mm function pointers once the + * machine type has been determined. It also sets the static + * mmu values such as PAGE_NONE, etc. + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + * Copyright (C) 1998 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + */ + +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/init.h> + +#include <asm/system.h> +#include <asm/page.h> +#include <asm/pgtable.h> +#include <asm/a.out.h> +#include <asm/mmu_context.h> +#include <asm/oplib.h> + +struct ctx_list *ctx_list_pool; +struct ctx_list ctx_free; +struct ctx_list ctx_used; + +unsigned int pg_iobits; + +extern void ld_mmu_sun4c(void); +extern void ld_mmu_srmmu(void); + +void __init load_mmu(void) +{ + switch(sparc_cpu_model) { + case sun4c: + case sun4: + ld_mmu_sun4c(); + break; + case sun4m: + case sun4d: + ld_mmu_srmmu(); + break; + default: + prom_printf("load_mmu: %d unsupported\n", (int)sparc_cpu_model); + prom_halt(); + } + btfixup(); +} diff --git a/arch/sparc/mm/nosrmmu.c b/arch/sparc/mm/nosrmmu.c new file mode 100644 index 000000000000..9e215659697e --- /dev/null +++ b/arch/sparc/mm/nosrmmu.c @@ -0,0 +1,59 @@ +/* $Id: nosrmmu.c,v 1.5 1999/11/19 04:11:54 davem Exp $ + * nosrmmu.c: This file is a bunch of dummies for sun4 compiles, + * so that it does not need srmmu and avoid ifdefs. + * + * Copyright (C) 1998 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + */ + +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/init.h> +#include <asm/mbus.h> +#include <asm/sbus.h> + +static char shouldnothappen[] __initdata = "SUN4 kernel can only run on SUN4\n"; + +enum mbus_module srmmu_modtype; +void *srmmu_nocache_pool; + +int vac_cache_size = 0; + +static void __init should_not_happen(void) +{ + prom_printf(shouldnothappen); + prom_halt(); +} + +void __init srmmu_frob_mem_map(unsigned long start_mem) +{ + should_not_happen(); +} + +unsigned long __init srmmu_paging_init(unsigned long start_mem, unsigned long end_mem) +{ + should_not_happen(); + return 0; +} + +void __init ld_mmu_srmmu(void) +{ + should_not_happen(); +} + +void srmmu_mapioaddr(unsigned long physaddr, unsigned long virt_addr, int bus_type, int rdonly) +{ +} + +void srmmu_unmapioaddr(unsigned long virt_addr) +{ +} + +__u32 iounit_map_dma_init(struct sbus_bus *sbus, int size) +{ + return 0; +} + +__u32 iounit_map_dma_page(__u32 vaddr, void *addr, struct sbus_bus *sbus) +{ + return 0; +} diff --git a/arch/sparc/mm/nosun4c.c b/arch/sparc/mm/nosun4c.c new file mode 100644 index 000000000000..ea2e2105341d --- /dev/null +++ b/arch/sparc/mm/nosun4c.c @@ -0,0 +1,77 @@ +/* $Id: nosun4c.c,v 1.3 2000/02/14 04:52:36 jj Exp $ + * nosun4c.c: This file is a bunch of dummies for SMP compiles, + * so that it does not need sun4c and avoid ifdefs. + * + * Copyright (C) 1998 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + */ + +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/init.h> +#include <asm/pgtable.h> + +static char shouldnothappen[] __initdata = "32bit SMP kernel only supports sun4m and sun4d\n"; + +/* Dummies */ +struct sun4c_mmu_ring { + unsigned long xxx1[3]; + unsigned char xxx2[2]; + int xxx3; +}; +struct sun4c_mmu_ring sun4c_kernel_ring; +struct sun4c_mmu_ring sun4c_kfree_ring; +unsigned long sun4c_kernel_faults; +unsigned long *sun4c_memerr_reg; + +static void __init should_not_happen(void) +{ + prom_printf(shouldnothappen); + prom_halt(); +} + +unsigned long __init sun4c_paging_init(unsigned long start_mem, unsigned long end_mem) +{ + should_not_happen(); + return 0; +} + +void __init ld_mmu_sun4c(void) +{ + should_not_happen(); +} + +void sun4c_mapioaddr(unsigned long physaddr, unsigned long virt_addr, int bus_type, int rdonly) +{ +} + +void sun4c_unmapioaddr(unsigned long virt_addr) +{ +} + +void sun4c_complete_all_stores(void) +{ +} + +pte_t *sun4c_pte_offset(pmd_t * dir, unsigned long address) +{ + return NULL; +} + +pte_t *sun4c_pte_offset_kernel(pmd_t *dir, unsigned long address) +{ + return NULL; +} + +void sun4c_update_mmu_cache(struct vm_area_struct *vma, unsigned long address, pte_t pte) +{ +} + +void __init sun4c_probe_vac(void) +{ + should_not_happen(); +} + +void __init sun4c_probe_memerr_reg(void) +{ + should_not_happen(); +} diff --git a/arch/sparc/mm/srmmu.c b/arch/sparc/mm/srmmu.c new file mode 100644 index 000000000000..c89a803cbc20 --- /dev/null +++ b/arch/sparc/mm/srmmu.c @@ -0,0 +1,2274 @@ +/* + * srmmu.c: SRMMU specific routines for memory management. + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + * Copyright (C) 1995,2002 Pete Zaitcev (zaitcev@yahoo.com) + * Copyright (C) 1996 Eddie C. Dost (ecd@skynet.be) + * Copyright (C) 1997,1998 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + * Copyright (C) 1999,2000 Anton Blanchard (anton@samba.org) + */ + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/pagemap.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/bootmem.h> +#include <linux/fs.h> +#include <linux/seq_file.h> + +#include <asm/bitext.h> +#include <asm/page.h> +#include <asm/pgalloc.h> +#include <asm/pgtable.h> +#include <asm/io.h> +#include <asm/kdebug.h> +#include <asm/vaddrs.h> +#include <asm/traps.h> +#include <asm/smp.h> +#include <asm/mbus.h> +#include <asm/cache.h> +#include <asm/oplib.h> +#include <asm/sbus.h> +#include <asm/asi.h> +#include <asm/msi.h> +#include <asm/a.out.h> +#include <asm/mmu_context.h> +#include <asm/io-unit.h> +#include <asm/cacheflush.h> +#include <asm/tlbflush.h> + +/* Now the cpu specific definitions. */ +#include <asm/viking.h> +#include <asm/mxcc.h> +#include <asm/ross.h> +#include <asm/tsunami.h> +#include <asm/swift.h> +#include <asm/turbosparc.h> + +#include <asm/btfixup.h> + +enum mbus_module srmmu_modtype; +unsigned int hwbug_bitmask; +int vac_cache_size; +int vac_line_size; + +extern struct resource sparc_iomap; + +extern unsigned long last_valid_pfn; + +extern unsigned long page_kernel; + +pgd_t *srmmu_swapper_pg_dir; + +#ifdef CONFIG_SMP +#define FLUSH_BEGIN(mm) +#define FLUSH_END +#else +#define FLUSH_BEGIN(mm) if((mm)->context != NO_CONTEXT) { +#define FLUSH_END } +#endif + +BTFIXUPDEF_CALL(void, flush_page_for_dma, unsigned long) +#define flush_page_for_dma(page) BTFIXUP_CALL(flush_page_for_dma)(page) + +int flush_page_for_dma_global = 1; + +#ifdef CONFIG_SMP +BTFIXUPDEF_CALL(void, local_flush_page_for_dma, unsigned long) +#define local_flush_page_for_dma(page) BTFIXUP_CALL(local_flush_page_for_dma)(page) +#endif + +char *srmmu_name; + +ctxd_t *srmmu_ctx_table_phys; +ctxd_t *srmmu_context_table; + +int viking_mxcc_present; +static DEFINE_SPINLOCK(srmmu_context_spinlock); + +int is_hypersparc; + +/* + * In general all page table modifications should use the V8 atomic + * swap instruction. This insures the mmu and the cpu are in sync + * with respect to ref/mod bits in the page tables. + */ +static inline unsigned long srmmu_swap(unsigned long *addr, unsigned long value) +{ + __asm__ __volatile__("swap [%2], %0" : "=&r" (value) : "0" (value), "r" (addr)); + return value; +} + +static inline void srmmu_set_pte(pte_t *ptep, pte_t pteval) +{ + srmmu_swap((unsigned long *)ptep, pte_val(pteval)); +} + +/* The very generic SRMMU page table operations. */ +static inline int srmmu_device_memory(unsigned long x) +{ + return ((x & 0xF0000000) != 0); +} + +int srmmu_cache_pagetables; + +/* these will be initialized in srmmu_nocache_calcsize() */ +unsigned long srmmu_nocache_size; +unsigned long srmmu_nocache_end; + +/* 1 bit <=> 256 bytes of nocache <=> 64 PTEs */ +#define SRMMU_NOCACHE_BITMAP_SHIFT (PAGE_SHIFT - 4) + +/* The context table is a nocache user with the biggest alignment needs. */ +#define SRMMU_NOCACHE_ALIGN_MAX (sizeof(ctxd_t)*SRMMU_MAX_CONTEXTS) + +void *srmmu_nocache_pool; +void *srmmu_nocache_bitmap; +static struct bit_map srmmu_nocache_map; + +static unsigned long srmmu_pte_pfn(pte_t pte) +{ + if (srmmu_device_memory(pte_val(pte))) { + /* Just return something that will cause + * pfn_valid() to return false. This makes + * copy_one_pte() to just directly copy to + * PTE over. + */ + return ~0UL; + } + return (pte_val(pte) & SRMMU_PTE_PMASK) >> (PAGE_SHIFT-4); +} + +static struct page *srmmu_pmd_page(pmd_t pmd) +{ + + if (srmmu_device_memory(pmd_val(pmd))) + BUG(); + return pfn_to_page((pmd_val(pmd) & SRMMU_PTD_PMASK) >> (PAGE_SHIFT-4)); +} + +static inline unsigned long srmmu_pgd_page(pgd_t pgd) +{ return srmmu_device_memory(pgd_val(pgd))?~0:(unsigned long)__nocache_va((pgd_val(pgd) & SRMMU_PTD_PMASK) << 4); } + + +static inline int srmmu_pte_none(pte_t pte) +{ return !(pte_val(pte) & 0xFFFFFFF); } + +static inline int srmmu_pte_present(pte_t pte) +{ return ((pte_val(pte) & SRMMU_ET_MASK) == SRMMU_ET_PTE); } + +static inline int srmmu_pte_read(pte_t pte) +{ return !(pte_val(pte) & SRMMU_NOREAD); } + +static inline void srmmu_pte_clear(pte_t *ptep) +{ srmmu_set_pte(ptep, __pte(0)); } + +static inline int srmmu_pmd_none(pmd_t pmd) +{ return !(pmd_val(pmd) & 0xFFFFFFF); } + +static inline int srmmu_pmd_bad(pmd_t pmd) +{ return (pmd_val(pmd) & SRMMU_ET_MASK) != SRMMU_ET_PTD; } + +static inline int srmmu_pmd_present(pmd_t pmd) +{ return ((pmd_val(pmd) & SRMMU_ET_MASK) == SRMMU_ET_PTD); } + +static inline void srmmu_pmd_clear(pmd_t *pmdp) { + int i; + for (i = 0; i < PTRS_PER_PTE/SRMMU_REAL_PTRS_PER_PTE; i++) + srmmu_set_pte((pte_t *)&pmdp->pmdv[i], __pte(0)); +} + +static inline int srmmu_pgd_none(pgd_t pgd) +{ return !(pgd_val(pgd) & 0xFFFFFFF); } + +static inline int srmmu_pgd_bad(pgd_t pgd) +{ return (pgd_val(pgd) & SRMMU_ET_MASK) != SRMMU_ET_PTD; } + +static inline int srmmu_pgd_present(pgd_t pgd) +{ return ((pgd_val(pgd) & SRMMU_ET_MASK) == SRMMU_ET_PTD); } + +static inline void srmmu_pgd_clear(pgd_t * pgdp) +{ srmmu_set_pte((pte_t *)pgdp, __pte(0)); } + +static inline pte_t srmmu_pte_wrprotect(pte_t pte) +{ return __pte(pte_val(pte) & ~SRMMU_WRITE);} + +static inline pte_t srmmu_pte_mkclean(pte_t pte) +{ return __pte(pte_val(pte) & ~SRMMU_DIRTY);} + +static inline pte_t srmmu_pte_mkold(pte_t pte) +{ return __pte(pte_val(pte) & ~SRMMU_REF);} + +static inline pte_t srmmu_pte_mkwrite(pte_t pte) +{ return __pte(pte_val(pte) | SRMMU_WRITE);} + +static inline pte_t srmmu_pte_mkdirty(pte_t pte) +{ return __pte(pte_val(pte) | SRMMU_DIRTY);} + +static inline pte_t srmmu_pte_mkyoung(pte_t pte) +{ return __pte(pte_val(pte) | SRMMU_REF);} + +/* + * Conversion functions: convert a page and protection to a page entry, + * and a page entry and page directory to the page they refer to. + */ +static pte_t srmmu_mk_pte(struct page *page, pgprot_t pgprot) +{ return __pte((page_to_pfn(page) << (PAGE_SHIFT-4)) | pgprot_val(pgprot)); } + +static pte_t srmmu_mk_pte_phys(unsigned long page, pgprot_t pgprot) +{ return __pte(((page) >> 4) | pgprot_val(pgprot)); } + +static pte_t srmmu_mk_pte_io(unsigned long page, pgprot_t pgprot, int space) +{ return __pte(((page) >> 4) | (space << 28) | pgprot_val(pgprot)); } + +/* XXX should we hyper_flush_whole_icache here - Anton */ +static inline void srmmu_ctxd_set(ctxd_t *ctxp, pgd_t *pgdp) +{ srmmu_set_pte((pte_t *)ctxp, (SRMMU_ET_PTD | (__nocache_pa((unsigned long) pgdp) >> 4))); } + +static inline void srmmu_pgd_set(pgd_t * pgdp, pmd_t * pmdp) +{ srmmu_set_pte((pte_t *)pgdp, (SRMMU_ET_PTD | (__nocache_pa((unsigned long) pmdp) >> 4))); } + +static void srmmu_pmd_set(pmd_t *pmdp, pte_t *ptep) +{ + unsigned long ptp; /* Physical address, shifted right by 4 */ + int i; + + ptp = __nocache_pa((unsigned long) ptep) >> 4; + for (i = 0; i < PTRS_PER_PTE/SRMMU_REAL_PTRS_PER_PTE; i++) { + srmmu_set_pte((pte_t *)&pmdp->pmdv[i], SRMMU_ET_PTD | ptp); + ptp += (SRMMU_REAL_PTRS_PER_PTE*sizeof(pte_t) >> 4); + } +} + +static void srmmu_pmd_populate(pmd_t *pmdp, struct page *ptep) +{ + unsigned long ptp; /* Physical address, shifted right by 4 */ + int i; + + ptp = page_to_pfn(ptep) << (PAGE_SHIFT-4); /* watch for overflow */ + for (i = 0; i < PTRS_PER_PTE/SRMMU_REAL_PTRS_PER_PTE; i++) { + srmmu_set_pte((pte_t *)&pmdp->pmdv[i], SRMMU_ET_PTD | ptp); + ptp += (SRMMU_REAL_PTRS_PER_PTE*sizeof(pte_t) >> 4); + } +} + +static inline pte_t srmmu_pte_modify(pte_t pte, pgprot_t newprot) +{ return __pte((pte_val(pte) & SRMMU_CHG_MASK) | pgprot_val(newprot)); } + +/* to find an entry in a top-level page table... */ +extern inline pgd_t *srmmu_pgd_offset(struct mm_struct * mm, unsigned long address) +{ return mm->pgd + (address >> SRMMU_PGDIR_SHIFT); } + +/* Find an entry in the second-level page table.. */ +static inline pmd_t *srmmu_pmd_offset(pgd_t * dir, unsigned long address) +{ + return (pmd_t *) srmmu_pgd_page(*dir) + + ((address >> PMD_SHIFT) & (PTRS_PER_PMD - 1)); +} + +/* Find an entry in the third-level page table.. */ +static inline pte_t *srmmu_pte_offset(pmd_t * dir, unsigned long address) +{ + void *pte; + + pte = __nocache_va((dir->pmdv[0] & SRMMU_PTD_PMASK) << 4); + return (pte_t *) pte + + ((address >> PAGE_SHIFT) & (PTRS_PER_PTE - 1)); +} + +static unsigned long srmmu_swp_type(swp_entry_t entry) +{ + return (entry.val >> SRMMU_SWP_TYPE_SHIFT) & SRMMU_SWP_TYPE_MASK; +} + +static unsigned long srmmu_swp_offset(swp_entry_t entry) +{ + return (entry.val >> SRMMU_SWP_OFF_SHIFT) & SRMMU_SWP_OFF_MASK; +} + +static swp_entry_t srmmu_swp_entry(unsigned long type, unsigned long offset) +{ + return (swp_entry_t) { + (type & SRMMU_SWP_TYPE_MASK) << SRMMU_SWP_TYPE_SHIFT + | (offset & SRMMU_SWP_OFF_MASK) << SRMMU_SWP_OFF_SHIFT }; +} + +/* + * size: bytes to allocate in the nocache area. + * align: bytes, number to align at. + * Returns the virtual address of the allocated area. + */ +static unsigned long __srmmu_get_nocache(int size, int align) +{ + int offset; + + if (size < SRMMU_NOCACHE_BITMAP_SHIFT) { + printk("Size 0x%x too small for nocache request\n", size); + size = SRMMU_NOCACHE_BITMAP_SHIFT; + } + if (size & (SRMMU_NOCACHE_BITMAP_SHIFT-1)) { + printk("Size 0x%x unaligned int nocache request\n", size); + size += SRMMU_NOCACHE_BITMAP_SHIFT-1; + } + BUG_ON(align > SRMMU_NOCACHE_ALIGN_MAX); + + offset = bit_map_string_get(&srmmu_nocache_map, + size >> SRMMU_NOCACHE_BITMAP_SHIFT, + align >> SRMMU_NOCACHE_BITMAP_SHIFT); + if (offset == -1) { + printk("srmmu: out of nocache %d: %d/%d\n", + size, (int) srmmu_nocache_size, + srmmu_nocache_map.used << SRMMU_NOCACHE_BITMAP_SHIFT); + return 0; + } + + return (SRMMU_NOCACHE_VADDR + (offset << SRMMU_NOCACHE_BITMAP_SHIFT)); +} + +unsigned inline long srmmu_get_nocache(int size, int align) +{ + unsigned long tmp; + + tmp = __srmmu_get_nocache(size, align); + + if (tmp) + memset((void *)tmp, 0, size); + + return tmp; +} + +void srmmu_free_nocache(unsigned long vaddr, int size) +{ + int offset; + + if (vaddr < SRMMU_NOCACHE_VADDR) { + printk("Vaddr %lx is smaller than nocache base 0x%lx\n", + vaddr, (unsigned long)SRMMU_NOCACHE_VADDR); + BUG(); + } + if (vaddr+size > srmmu_nocache_end) { + printk("Vaddr %lx is bigger than nocache end 0x%lx\n", + vaddr, srmmu_nocache_end); + BUG(); + } + if (size & (size-1)) { + printk("Size 0x%x is not a power of 2\n", size); + BUG(); + } + if (size < SRMMU_NOCACHE_BITMAP_SHIFT) { + printk("Size 0x%x is too small\n", size); + BUG(); + } + if (vaddr & (size-1)) { + printk("Vaddr %lx is not aligned to size 0x%x\n", vaddr, size); + BUG(); + } + + offset = (vaddr - SRMMU_NOCACHE_VADDR) >> SRMMU_NOCACHE_BITMAP_SHIFT; + size = size >> SRMMU_NOCACHE_BITMAP_SHIFT; + + bit_map_clear(&srmmu_nocache_map, offset, size); +} + +void srmmu_early_allocate_ptable_skeleton(unsigned long start, unsigned long end); + +extern unsigned long probe_memory(void); /* in fault.c */ + +/* + * Reserve nocache dynamically proportionally to the amount of + * system RAM. -- Tomas Szepe <szepe@pinerecords.com>, June 2002 + */ +void srmmu_nocache_calcsize(void) +{ + unsigned long sysmemavail = probe_memory() / 1024; + int srmmu_nocache_npages; + + srmmu_nocache_npages = + sysmemavail / SRMMU_NOCACHE_ALCRATIO / 1024 * 256; + + /* P3 XXX The 4x overuse: corroborated by /proc/meminfo. */ + // if (srmmu_nocache_npages < 256) srmmu_nocache_npages = 256; + if (srmmu_nocache_npages < SRMMU_MIN_NOCACHE_PAGES) + srmmu_nocache_npages = SRMMU_MIN_NOCACHE_PAGES; + + /* anything above 1280 blows up */ + if (srmmu_nocache_npages > SRMMU_MAX_NOCACHE_PAGES) + srmmu_nocache_npages = SRMMU_MAX_NOCACHE_PAGES; + + srmmu_nocache_size = srmmu_nocache_npages * PAGE_SIZE; + srmmu_nocache_end = SRMMU_NOCACHE_VADDR + srmmu_nocache_size; +} + +void srmmu_nocache_init(void) +{ + unsigned int bitmap_bits; + pgd_t *pgd; + pmd_t *pmd; + pte_t *pte; + unsigned long paddr, vaddr; + unsigned long pteval; + + bitmap_bits = srmmu_nocache_size >> SRMMU_NOCACHE_BITMAP_SHIFT; + + srmmu_nocache_pool = __alloc_bootmem(srmmu_nocache_size, + SRMMU_NOCACHE_ALIGN_MAX, 0UL); + memset(srmmu_nocache_pool, 0, srmmu_nocache_size); + + srmmu_nocache_bitmap = __alloc_bootmem(bitmap_bits >> 3, SMP_CACHE_BYTES, 0UL); + bit_map_init(&srmmu_nocache_map, srmmu_nocache_bitmap, bitmap_bits); + + srmmu_swapper_pg_dir = (pgd_t *)__srmmu_get_nocache(SRMMU_PGD_TABLE_SIZE, SRMMU_PGD_TABLE_SIZE); + memset(__nocache_fix(srmmu_swapper_pg_dir), 0, SRMMU_PGD_TABLE_SIZE); + init_mm.pgd = srmmu_swapper_pg_dir; + + srmmu_early_allocate_ptable_skeleton(SRMMU_NOCACHE_VADDR, srmmu_nocache_end); + + paddr = __pa((unsigned long)srmmu_nocache_pool); + vaddr = SRMMU_NOCACHE_VADDR; + + while (vaddr < srmmu_nocache_end) { + pgd = pgd_offset_k(vaddr); + pmd = srmmu_pmd_offset(__nocache_fix(pgd), vaddr); + pte = srmmu_pte_offset(__nocache_fix(pmd), vaddr); + + pteval = ((paddr >> 4) | SRMMU_ET_PTE | SRMMU_PRIV); + + if (srmmu_cache_pagetables) + pteval |= SRMMU_CACHE; + + srmmu_set_pte(__nocache_fix(pte), __pte(pteval)); + + vaddr += PAGE_SIZE; + paddr += PAGE_SIZE; + } + + flush_cache_all(); + flush_tlb_all(); +} + +static inline pgd_t *srmmu_get_pgd_fast(void) +{ + pgd_t *pgd = NULL; + + pgd = (pgd_t *)__srmmu_get_nocache(SRMMU_PGD_TABLE_SIZE, SRMMU_PGD_TABLE_SIZE); + if (pgd) { + pgd_t *init = pgd_offset_k(0); + memset(pgd, 0, USER_PTRS_PER_PGD * sizeof(pgd_t)); + memcpy(pgd + USER_PTRS_PER_PGD, init + USER_PTRS_PER_PGD, + (PTRS_PER_PGD - USER_PTRS_PER_PGD) * sizeof(pgd_t)); + } + + return pgd; +} + +static void srmmu_free_pgd_fast(pgd_t *pgd) +{ + srmmu_free_nocache((unsigned long)pgd, SRMMU_PGD_TABLE_SIZE); +} + +static pmd_t *srmmu_pmd_alloc_one(struct mm_struct *mm, unsigned long address) +{ + return (pmd_t *)srmmu_get_nocache(SRMMU_PMD_TABLE_SIZE, SRMMU_PMD_TABLE_SIZE); +} + +static void srmmu_pmd_free(pmd_t * pmd) +{ + srmmu_free_nocache((unsigned long)pmd, SRMMU_PMD_TABLE_SIZE); +} + +/* + * Hardware needs alignment to 256 only, but we align to whole page size + * to reduce fragmentation problems due to the buddy principle. + * XXX Provide actual fragmentation statistics in /proc. + * + * Alignments up to the page size are the same for physical and virtual + * addresses of the nocache area. + */ +static pte_t * +srmmu_pte_alloc_one_kernel(struct mm_struct *mm, unsigned long address) +{ + return (pte_t *)srmmu_get_nocache(PTE_SIZE, PTE_SIZE); +} + +static struct page * +srmmu_pte_alloc_one(struct mm_struct *mm, unsigned long address) +{ + unsigned long pte; + + if ((pte = (unsigned long)srmmu_pte_alloc_one_kernel(mm, address)) == 0) + return NULL; + return pfn_to_page( __nocache_pa(pte) >> PAGE_SHIFT ); +} + +static void srmmu_free_pte_fast(pte_t *pte) +{ + srmmu_free_nocache((unsigned long)pte, PTE_SIZE); +} + +static void srmmu_pte_free(struct page *pte) +{ + unsigned long p; + + p = (unsigned long)page_address(pte); /* Cached address (for test) */ + if (p == 0) + BUG(); + p = page_to_pfn(pte) << PAGE_SHIFT; /* Physical address */ + p = (unsigned long) __nocache_va(p); /* Nocached virtual */ + srmmu_free_nocache(p, PTE_SIZE); +} + +/* + */ +static inline void alloc_context(struct mm_struct *old_mm, struct mm_struct *mm) +{ + struct ctx_list *ctxp; + + ctxp = ctx_free.next; + if(ctxp != &ctx_free) { + remove_from_ctx_list(ctxp); + add_to_used_ctxlist(ctxp); + mm->context = ctxp->ctx_number; + ctxp->ctx_mm = mm; + return; + } + ctxp = ctx_used.next; + if(ctxp->ctx_mm == old_mm) + ctxp = ctxp->next; + if(ctxp == &ctx_used) + panic("out of mmu contexts"); + flush_cache_mm(ctxp->ctx_mm); + flush_tlb_mm(ctxp->ctx_mm); + remove_from_ctx_list(ctxp); + add_to_used_ctxlist(ctxp); + ctxp->ctx_mm->context = NO_CONTEXT; + ctxp->ctx_mm = mm; + mm->context = ctxp->ctx_number; +} + +static inline void free_context(int context) +{ + struct ctx_list *ctx_old; + + ctx_old = ctx_list_pool + context; + remove_from_ctx_list(ctx_old); + add_to_free_ctxlist(ctx_old); +} + + +static void srmmu_switch_mm(struct mm_struct *old_mm, struct mm_struct *mm, + struct task_struct *tsk, int cpu) +{ + if(mm->context == NO_CONTEXT) { + spin_lock(&srmmu_context_spinlock); + alloc_context(old_mm, mm); + spin_unlock(&srmmu_context_spinlock); + srmmu_ctxd_set(&srmmu_context_table[mm->context], mm->pgd); + } + + if (is_hypersparc) + hyper_flush_whole_icache(); + + srmmu_set_context(mm->context); +} + +/* Low level IO area allocation on the SRMMU. */ +static inline void srmmu_mapioaddr(unsigned long physaddr, + unsigned long virt_addr, int bus_type) +{ + pgd_t *pgdp; + pmd_t *pmdp; + pte_t *ptep; + unsigned long tmp; + + physaddr &= PAGE_MASK; + pgdp = pgd_offset_k(virt_addr); + pmdp = srmmu_pmd_offset(pgdp, virt_addr); + ptep = srmmu_pte_offset(pmdp, virt_addr); + tmp = (physaddr >> 4) | SRMMU_ET_PTE; + + /* + * I need to test whether this is consistent over all + * sun4m's. The bus_type represents the upper 4 bits of + * 36-bit physical address on the I/O space lines... + */ + tmp |= (bus_type << 28); + tmp |= SRMMU_PRIV; + __flush_page_to_ram(virt_addr); + srmmu_set_pte(ptep, __pte(tmp)); +} + +static void srmmu_mapiorange(unsigned int bus, unsigned long xpa, + unsigned long xva, unsigned int len) +{ + while (len != 0) { + len -= PAGE_SIZE; + srmmu_mapioaddr(xpa, xva, bus); + xva += PAGE_SIZE; + xpa += PAGE_SIZE; + } + flush_tlb_all(); +} + +static inline void srmmu_unmapioaddr(unsigned long virt_addr) +{ + pgd_t *pgdp; + pmd_t *pmdp; + pte_t *ptep; + + pgdp = pgd_offset_k(virt_addr); + pmdp = srmmu_pmd_offset(pgdp, virt_addr); + ptep = srmmu_pte_offset(pmdp, virt_addr); + + /* No need to flush uncacheable page. */ + srmmu_pte_clear(ptep); +} + +static void srmmu_unmapiorange(unsigned long virt_addr, unsigned int len) +{ + while (len != 0) { + len -= PAGE_SIZE; + srmmu_unmapioaddr(virt_addr); + virt_addr += PAGE_SIZE; + } + flush_tlb_all(); +} + +/* + * On the SRMMU we do not have the problems with limited tlb entries + * for mapping kernel pages, so we just take things from the free page + * pool. As a side effect we are putting a little too much pressure + * on the gfp() subsystem. This setup also makes the logic of the + * iommu mapping code a lot easier as we can transparently handle + * mappings on the kernel stack without any special code as we did + * need on the sun4c. + */ +struct thread_info *srmmu_alloc_thread_info(void) +{ + struct thread_info *ret; + + ret = (struct thread_info *)__get_free_pages(GFP_KERNEL, + THREAD_INFO_ORDER); +#ifdef CONFIG_DEBUG_STACK_USAGE + if (ret) + memset(ret, 0, PAGE_SIZE << THREAD_INFO_ORDER); +#endif /* DEBUG_STACK_USAGE */ + + return ret; +} + +static void srmmu_free_thread_info(struct thread_info *ti) +{ + free_pages((unsigned long)ti, THREAD_INFO_ORDER); +} + +/* tsunami.S */ +extern void tsunami_flush_cache_all(void); +extern void tsunami_flush_cache_mm(struct mm_struct *mm); +extern void tsunami_flush_cache_range(struct vm_area_struct *vma, unsigned long start, unsigned long end); +extern void tsunami_flush_cache_page(struct vm_area_struct *vma, unsigned long page); +extern void tsunami_flush_page_to_ram(unsigned long page); +extern void tsunami_flush_page_for_dma(unsigned long page); +extern void tsunami_flush_sig_insns(struct mm_struct *mm, unsigned long insn_addr); +extern void tsunami_flush_tlb_all(void); +extern void tsunami_flush_tlb_mm(struct mm_struct *mm); +extern void tsunami_flush_tlb_range(struct vm_area_struct *vma, unsigned long start, unsigned long end); +extern void tsunami_flush_tlb_page(struct vm_area_struct *vma, unsigned long page); +extern void tsunami_setup_blockops(void); + +/* + * Workaround, until we find what's going on with Swift. When low on memory, + * it sometimes loops in fault/handle_mm_fault incl. flush_tlb_page to find + * out it is already in page tables/ fault again on the same instruction. + * I really don't understand it, have checked it and contexts + * are right, flush_tlb_all is done as well, and it faults again... + * Strange. -jj + * + * The following code is a deadwood that may be necessary when + * we start to make precise page flushes again. --zaitcev + */ +static void swift_update_mmu_cache(struct vm_area_struct * vma, unsigned long address, pte_t pte) +{ +#if 0 + static unsigned long last; + unsigned int val; + /* unsigned int n; */ + + if (address == last) { + val = srmmu_hwprobe(address); + if (val != 0 && pte_val(pte) != val) { + printk("swift_update_mmu_cache: " + "addr %lx put %08x probed %08x from %p\n", + address, pte_val(pte), val, + __builtin_return_address(0)); + srmmu_flush_whole_tlb(); + } + } + last = address; +#endif +} + +/* swift.S */ +extern void swift_flush_cache_all(void); +extern void swift_flush_cache_mm(struct mm_struct *mm); +extern void swift_flush_cache_range(struct vm_area_struct *vma, + unsigned long start, unsigned long end); +extern void swift_flush_cache_page(struct vm_area_struct *vma, unsigned long page); +extern void swift_flush_page_to_ram(unsigned long page); +extern void swift_flush_page_for_dma(unsigned long page); +extern void swift_flush_sig_insns(struct mm_struct *mm, unsigned long insn_addr); +extern void swift_flush_tlb_all(void); +extern void swift_flush_tlb_mm(struct mm_struct *mm); +extern void swift_flush_tlb_range(struct vm_area_struct *vma, + unsigned long start, unsigned long end); +extern void swift_flush_tlb_page(struct vm_area_struct *vma, unsigned long page); + +#if 0 /* P3: deadwood to debug precise flushes on Swift. */ +void swift_flush_tlb_page(struct vm_area_struct *vma, unsigned long page) +{ + int cctx, ctx1; + + page &= PAGE_MASK; + if ((ctx1 = vma->vm_mm->context) != -1) { + cctx = srmmu_get_context(); +/* Is context # ever different from current context? P3 */ + if (cctx != ctx1) { + printk("flush ctx %02x curr %02x\n", ctx1, cctx); + srmmu_set_context(ctx1); + swift_flush_page(page); + __asm__ __volatile__("sta %%g0, [%0] %1\n\t" : : + "r" (page), "i" (ASI_M_FLUSH_PROBE)); + srmmu_set_context(cctx); + } else { + /* Rm. prot. bits from virt. c. */ + /* swift_flush_cache_all(); */ + /* swift_flush_cache_page(vma, page); */ + swift_flush_page(page); + + __asm__ __volatile__("sta %%g0, [%0] %1\n\t" : : + "r" (page), "i" (ASI_M_FLUSH_PROBE)); + /* same as above: srmmu_flush_tlb_page() */ + } + } +} +#endif + +/* + * The following are all MBUS based SRMMU modules, and therefore could + * be found in a multiprocessor configuration. On the whole, these + * chips seems to be much more touchy about DVMA and page tables + * with respect to cache coherency. + */ + +/* Cypress flushes. */ +static void cypress_flush_cache_all(void) +{ + volatile unsigned long cypress_sucks; + unsigned long faddr, tagval; + + flush_user_windows(); + for(faddr = 0; faddr < 0x10000; faddr += 0x20) { + __asm__ __volatile__("lda [%1 + %2] %3, %0\n\t" : + "=r" (tagval) : + "r" (faddr), "r" (0x40000), + "i" (ASI_M_DATAC_TAG)); + + /* If modified and valid, kick it. */ + if((tagval & 0x60) == 0x60) + cypress_sucks = *(unsigned long *)(0xf0020000 + faddr); + } +} + +static void cypress_flush_cache_mm(struct mm_struct *mm) +{ + register unsigned long a, b, c, d, e, f, g; + unsigned long flags, faddr; + int octx; + + FLUSH_BEGIN(mm) + flush_user_windows(); + local_irq_save(flags); + octx = srmmu_get_context(); + srmmu_set_context(mm->context); + a = 0x20; b = 0x40; c = 0x60; + d = 0x80; e = 0xa0; f = 0xc0; g = 0xe0; + + faddr = (0x10000 - 0x100); + goto inside; + do { + faddr -= 0x100; + inside: + __asm__ __volatile__("sta %%g0, [%0] %1\n\t" + "sta %%g0, [%0 + %2] %1\n\t" + "sta %%g0, [%0 + %3] %1\n\t" + "sta %%g0, [%0 + %4] %1\n\t" + "sta %%g0, [%0 + %5] %1\n\t" + "sta %%g0, [%0 + %6] %1\n\t" + "sta %%g0, [%0 + %7] %1\n\t" + "sta %%g0, [%0 + %8] %1\n\t" : : + "r" (faddr), "i" (ASI_M_FLUSH_CTX), + "r" (a), "r" (b), "r" (c), "r" (d), + "r" (e), "r" (f), "r" (g)); + } while(faddr); + srmmu_set_context(octx); + local_irq_restore(flags); + FLUSH_END +} + +static void cypress_flush_cache_range(struct vm_area_struct *vma, unsigned long start, unsigned long end) +{ + struct mm_struct *mm = vma->vm_mm; + register unsigned long a, b, c, d, e, f, g; + unsigned long flags, faddr; + int octx; + + FLUSH_BEGIN(mm) + flush_user_windows(); + local_irq_save(flags); + octx = srmmu_get_context(); + srmmu_set_context(mm->context); + a = 0x20; b = 0x40; c = 0x60; + d = 0x80; e = 0xa0; f = 0xc0; g = 0xe0; + + start &= SRMMU_REAL_PMD_MASK; + while(start < end) { + faddr = (start + (0x10000 - 0x100)); + goto inside; + do { + faddr -= 0x100; + inside: + __asm__ __volatile__("sta %%g0, [%0] %1\n\t" + "sta %%g0, [%0 + %2] %1\n\t" + "sta %%g0, [%0 + %3] %1\n\t" + "sta %%g0, [%0 + %4] %1\n\t" + "sta %%g0, [%0 + %5] %1\n\t" + "sta %%g0, [%0 + %6] %1\n\t" + "sta %%g0, [%0 + %7] %1\n\t" + "sta %%g0, [%0 + %8] %1\n\t" : : + "r" (faddr), + "i" (ASI_M_FLUSH_SEG), + "r" (a), "r" (b), "r" (c), "r" (d), + "r" (e), "r" (f), "r" (g)); + } while (faddr != start); + start += SRMMU_REAL_PMD_SIZE; + } + srmmu_set_context(octx); + local_irq_restore(flags); + FLUSH_END +} + +static void cypress_flush_cache_page(struct vm_area_struct *vma, unsigned long page) +{ + register unsigned long a, b, c, d, e, f, g; + struct mm_struct *mm = vma->vm_mm; + unsigned long flags, line; + int octx; + + FLUSH_BEGIN(mm) + flush_user_windows(); + local_irq_save(flags); + octx = srmmu_get_context(); + srmmu_set_context(mm->context); + a = 0x20; b = 0x40; c = 0x60; + d = 0x80; e = 0xa0; f = 0xc0; g = 0xe0; + + page &= PAGE_MASK; + line = (page + PAGE_SIZE) - 0x100; + goto inside; + do { + line -= 0x100; + inside: + __asm__ __volatile__("sta %%g0, [%0] %1\n\t" + "sta %%g0, [%0 + %2] %1\n\t" + "sta %%g0, [%0 + %3] %1\n\t" + "sta %%g0, [%0 + %4] %1\n\t" + "sta %%g0, [%0 + %5] %1\n\t" + "sta %%g0, [%0 + %6] %1\n\t" + "sta %%g0, [%0 + %7] %1\n\t" + "sta %%g0, [%0 + %8] %1\n\t" : : + "r" (line), + "i" (ASI_M_FLUSH_PAGE), + "r" (a), "r" (b), "r" (c), "r" (d), + "r" (e), "r" (f), "r" (g)); + } while(line != page); + srmmu_set_context(octx); + local_irq_restore(flags); + FLUSH_END +} + +/* Cypress is copy-back, at least that is how we configure it. */ +static void cypress_flush_page_to_ram(unsigned long page) +{ + register unsigned long a, b, c, d, e, f, g; + unsigned long line; + + a = 0x20; b = 0x40; c = 0x60; d = 0x80; e = 0xa0; f = 0xc0; g = 0xe0; + page &= PAGE_MASK; + line = (page + PAGE_SIZE) - 0x100; + goto inside; + do { + line -= 0x100; + inside: + __asm__ __volatile__("sta %%g0, [%0] %1\n\t" + "sta %%g0, [%0 + %2] %1\n\t" + "sta %%g0, [%0 + %3] %1\n\t" + "sta %%g0, [%0 + %4] %1\n\t" + "sta %%g0, [%0 + %5] %1\n\t" + "sta %%g0, [%0 + %6] %1\n\t" + "sta %%g0, [%0 + %7] %1\n\t" + "sta %%g0, [%0 + %8] %1\n\t" : : + "r" (line), + "i" (ASI_M_FLUSH_PAGE), + "r" (a), "r" (b), "r" (c), "r" (d), + "r" (e), "r" (f), "r" (g)); + } while(line != page); +} + +/* Cypress is also IO cache coherent. */ +static void cypress_flush_page_for_dma(unsigned long page) +{ +} + +/* Cypress has unified L2 VIPT, from which both instructions and data + * are stored. It does not have an onboard icache of any sort, therefore + * no flush is necessary. + */ +static void cypress_flush_sig_insns(struct mm_struct *mm, unsigned long insn_addr) +{ +} + +static void cypress_flush_tlb_all(void) +{ + srmmu_flush_whole_tlb(); +} + +static void cypress_flush_tlb_mm(struct mm_struct *mm) +{ + FLUSH_BEGIN(mm) + __asm__ __volatile__( + "lda [%0] %3, %%g5\n\t" + "sta %2, [%0] %3\n\t" + "sta %%g0, [%1] %4\n\t" + "sta %%g5, [%0] %3\n" + : /* no outputs */ + : "r" (SRMMU_CTX_REG), "r" (0x300), "r" (mm->context), + "i" (ASI_M_MMUREGS), "i" (ASI_M_FLUSH_PROBE) + : "g5"); + FLUSH_END +} + +static void cypress_flush_tlb_range(struct vm_area_struct *vma, unsigned long start, unsigned long end) +{ + struct mm_struct *mm = vma->vm_mm; + unsigned long size; + + FLUSH_BEGIN(mm) + start &= SRMMU_PGDIR_MASK; + size = SRMMU_PGDIR_ALIGN(end) - start; + __asm__ __volatile__( + "lda [%0] %5, %%g5\n\t" + "sta %1, [%0] %5\n" + "1:\n\t" + "subcc %3, %4, %3\n\t" + "bne 1b\n\t" + " sta %%g0, [%2 + %3] %6\n\t" + "sta %%g5, [%0] %5\n" + : /* no outputs */ + : "r" (SRMMU_CTX_REG), "r" (mm->context), "r" (start | 0x200), + "r" (size), "r" (SRMMU_PGDIR_SIZE), "i" (ASI_M_MMUREGS), + "i" (ASI_M_FLUSH_PROBE) + : "g5", "cc"); + FLUSH_END +} + +static void cypress_flush_tlb_page(struct vm_area_struct *vma, unsigned long page) +{ + struct mm_struct *mm = vma->vm_mm; + + FLUSH_BEGIN(mm) + __asm__ __volatile__( + "lda [%0] %3, %%g5\n\t" + "sta %1, [%0] %3\n\t" + "sta %%g0, [%2] %4\n\t" + "sta %%g5, [%0] %3\n" + : /* no outputs */ + : "r" (SRMMU_CTX_REG), "r" (mm->context), "r" (page & PAGE_MASK), + "i" (ASI_M_MMUREGS), "i" (ASI_M_FLUSH_PROBE) + : "g5"); + FLUSH_END +} + +/* viking.S */ +extern void viking_flush_cache_all(void); +extern void viking_flush_cache_mm(struct mm_struct *mm); +extern void viking_flush_cache_range(struct vm_area_struct *vma, unsigned long start, + unsigned long end); +extern void viking_flush_cache_page(struct vm_area_struct *vma, unsigned long page); +extern void viking_flush_page_to_ram(unsigned long page); +extern void viking_flush_page_for_dma(unsigned long page); +extern void viking_flush_sig_insns(struct mm_struct *mm, unsigned long addr); +extern void viking_flush_page(unsigned long page); +extern void viking_mxcc_flush_page(unsigned long page); +extern void viking_flush_tlb_all(void); +extern void viking_flush_tlb_mm(struct mm_struct *mm); +extern void viking_flush_tlb_range(struct vm_area_struct *vma, unsigned long start, + unsigned long end); +extern void viking_flush_tlb_page(struct vm_area_struct *vma, + unsigned long page); +extern void sun4dsmp_flush_tlb_all(void); +extern void sun4dsmp_flush_tlb_mm(struct mm_struct *mm); +extern void sun4dsmp_flush_tlb_range(struct vm_area_struct *vma, unsigned long start, + unsigned long end); +extern void sun4dsmp_flush_tlb_page(struct vm_area_struct *vma, + unsigned long page); + +/* hypersparc.S */ +extern void hypersparc_flush_cache_all(void); +extern void hypersparc_flush_cache_mm(struct mm_struct *mm); +extern void hypersparc_flush_cache_range(struct vm_area_struct *vma, unsigned long start, unsigned long end); +extern void hypersparc_flush_cache_page(struct vm_area_struct *vma, unsigned long page); +extern void hypersparc_flush_page_to_ram(unsigned long page); +extern void hypersparc_flush_page_for_dma(unsigned long page); +extern void hypersparc_flush_sig_insns(struct mm_struct *mm, unsigned long insn_addr); +extern void hypersparc_flush_tlb_all(void); +extern void hypersparc_flush_tlb_mm(struct mm_struct *mm); +extern void hypersparc_flush_tlb_range(struct vm_area_struct *vma, unsigned long start, unsigned long end); +extern void hypersparc_flush_tlb_page(struct vm_area_struct *vma, unsigned long page); +extern void hypersparc_setup_blockops(void); + +/* + * NOTE: All of this startup code assumes the low 16mb (approx.) of + * kernel mappings are done with one single contiguous chunk of + * ram. On small ram machines (classics mainly) we only get + * around 8mb mapped for us. + */ + +void __init early_pgtable_allocfail(char *type) +{ + prom_printf("inherit_prom_mappings: Cannot alloc kernel %s.\n", type); + prom_halt(); +} + +void __init srmmu_early_allocate_ptable_skeleton(unsigned long start, unsigned long end) +{ + pgd_t *pgdp; + pmd_t *pmdp; + pte_t *ptep; + + while(start < end) { + pgdp = pgd_offset_k(start); + if(srmmu_pgd_none(*(pgd_t *)__nocache_fix(pgdp))) { + pmdp = (pmd_t *) __srmmu_get_nocache( + SRMMU_PMD_TABLE_SIZE, SRMMU_PMD_TABLE_SIZE); + if (pmdp == NULL) + early_pgtable_allocfail("pmd"); + memset(__nocache_fix(pmdp), 0, SRMMU_PMD_TABLE_SIZE); + srmmu_pgd_set(__nocache_fix(pgdp), pmdp); + } + pmdp = srmmu_pmd_offset(__nocache_fix(pgdp), start); + if(srmmu_pmd_none(*(pmd_t *)__nocache_fix(pmdp))) { + ptep = (pte_t *)__srmmu_get_nocache(PTE_SIZE, PTE_SIZE); + if (ptep == NULL) + early_pgtable_allocfail("pte"); + memset(__nocache_fix(ptep), 0, PTE_SIZE); + srmmu_pmd_set(__nocache_fix(pmdp), ptep); + } + if (start > (0xffffffffUL - PMD_SIZE)) + break; + start = (start + PMD_SIZE) & PMD_MASK; + } +} + +void __init srmmu_allocate_ptable_skeleton(unsigned long start, unsigned long end) +{ + pgd_t *pgdp; + pmd_t *pmdp; + pte_t *ptep; + + while(start < end) { + pgdp = pgd_offset_k(start); + if(srmmu_pgd_none(*pgdp)) { + pmdp = (pmd_t *)__srmmu_get_nocache(SRMMU_PMD_TABLE_SIZE, SRMMU_PMD_TABLE_SIZE); + if (pmdp == NULL) + early_pgtable_allocfail("pmd"); + memset(pmdp, 0, SRMMU_PMD_TABLE_SIZE); + srmmu_pgd_set(pgdp, pmdp); + } + pmdp = srmmu_pmd_offset(pgdp, start); + if(srmmu_pmd_none(*pmdp)) { + ptep = (pte_t *) __srmmu_get_nocache(PTE_SIZE, + PTE_SIZE); + if (ptep == NULL) + early_pgtable_allocfail("pte"); + memset(ptep, 0, PTE_SIZE); + srmmu_pmd_set(pmdp, ptep); + } + if (start > (0xffffffffUL - PMD_SIZE)) + break; + start = (start + PMD_SIZE) & PMD_MASK; + } +} + +/* + * This is much cleaner than poking around physical address space + * looking at the prom's page table directly which is what most + * other OS's do. Yuck... this is much better. + */ +void __init srmmu_inherit_prom_mappings(unsigned long start,unsigned long end) +{ + pgd_t *pgdp; + pmd_t *pmdp; + pte_t *ptep; + int what = 0; /* 0 = normal-pte, 1 = pmd-level pte, 2 = pgd-level pte */ + unsigned long prompte; + + while(start <= end) { + if (start == 0) + break; /* probably wrap around */ + if(start == 0xfef00000) + start = KADB_DEBUGGER_BEGVM; + if(!(prompte = srmmu_hwprobe(start))) { + start += PAGE_SIZE; + continue; + } + + /* A red snapper, see what it really is. */ + what = 0; + + if(!(start & ~(SRMMU_REAL_PMD_MASK))) { + if(srmmu_hwprobe((start-PAGE_SIZE) + SRMMU_REAL_PMD_SIZE) == prompte) + what = 1; + } + + if(!(start & ~(SRMMU_PGDIR_MASK))) { + if(srmmu_hwprobe((start-PAGE_SIZE) + SRMMU_PGDIR_SIZE) == + prompte) + what = 2; + } + + pgdp = pgd_offset_k(start); + if(what == 2) { + *(pgd_t *)__nocache_fix(pgdp) = __pgd(prompte); + start += SRMMU_PGDIR_SIZE; + continue; + } + if(srmmu_pgd_none(*(pgd_t *)__nocache_fix(pgdp))) { + pmdp = (pmd_t *)__srmmu_get_nocache(SRMMU_PMD_TABLE_SIZE, SRMMU_PMD_TABLE_SIZE); + if (pmdp == NULL) + early_pgtable_allocfail("pmd"); + memset(__nocache_fix(pmdp), 0, SRMMU_PMD_TABLE_SIZE); + srmmu_pgd_set(__nocache_fix(pgdp), pmdp); + } + pmdp = srmmu_pmd_offset(__nocache_fix(pgdp), start); + if(srmmu_pmd_none(*(pmd_t *)__nocache_fix(pmdp))) { + ptep = (pte_t *) __srmmu_get_nocache(PTE_SIZE, + PTE_SIZE); + if (ptep == NULL) + early_pgtable_allocfail("pte"); + memset(__nocache_fix(ptep), 0, PTE_SIZE); + srmmu_pmd_set(__nocache_fix(pmdp), ptep); + } + if(what == 1) { + /* + * We bend the rule where all 16 PTPs in a pmd_t point + * inside the same PTE page, and we leak a perfectly + * good hardware PTE piece. Alternatives seem worse. + */ + unsigned int x; /* Index of HW PMD in soft cluster */ + x = (start >> PMD_SHIFT) & 15; + *(unsigned long *)__nocache_fix(&pmdp->pmdv[x]) = prompte; + start += SRMMU_REAL_PMD_SIZE; + continue; + } + ptep = srmmu_pte_offset(__nocache_fix(pmdp), start); + *(pte_t *)__nocache_fix(ptep) = __pte(prompte); + start += PAGE_SIZE; + } +} + +#define KERNEL_PTE(page_shifted) ((page_shifted)|SRMMU_CACHE|SRMMU_PRIV|SRMMU_VALID) + +/* Create a third-level SRMMU 16MB page mapping. */ +static void __init do_large_mapping(unsigned long vaddr, unsigned long phys_base) +{ + pgd_t *pgdp = pgd_offset_k(vaddr); + unsigned long big_pte; + + big_pte = KERNEL_PTE(phys_base >> 4); + *(pgd_t *)__nocache_fix(pgdp) = __pgd(big_pte); +} + +/* Map sp_bank entry SP_ENTRY, starting at virtual address VBASE. */ +static unsigned long __init map_spbank(unsigned long vbase, int sp_entry) +{ + unsigned long pstart = (sp_banks[sp_entry].base_addr & SRMMU_PGDIR_MASK); + unsigned long vstart = (vbase & SRMMU_PGDIR_MASK); + unsigned long vend = SRMMU_PGDIR_ALIGN(vbase + sp_banks[sp_entry].num_bytes); + /* Map "low" memory only */ + const unsigned long min_vaddr = PAGE_OFFSET; + const unsigned long max_vaddr = PAGE_OFFSET + SRMMU_MAXMEM; + + if (vstart < min_vaddr || vstart >= max_vaddr) + return vstart; + + if (vend > max_vaddr || vend < min_vaddr) + vend = max_vaddr; + + while(vstart < vend) { + do_large_mapping(vstart, pstart); + vstart += SRMMU_PGDIR_SIZE; pstart += SRMMU_PGDIR_SIZE; + } + return vstart; +} + +static inline void memprobe_error(char *msg) +{ + prom_printf(msg); + prom_printf("Halting now...\n"); + prom_halt(); +} + +static inline void map_kernel(void) +{ + int i; + + if (phys_base > 0) { + do_large_mapping(PAGE_OFFSET, phys_base); + } + + for (i = 0; sp_banks[i].num_bytes != 0; i++) { + map_spbank((unsigned long)__va(sp_banks[i].base_addr), i); + } + + BTFIXUPSET_SIMM13(user_ptrs_per_pgd, PAGE_OFFSET / SRMMU_PGDIR_SIZE); +} + +/* Paging initialization on the Sparc Reference MMU. */ +extern void sparc_context_init(int); + +void (*poke_srmmu)(void) __initdata = NULL; + +extern unsigned long bootmem_init(unsigned long *pages_avail); + +void __init srmmu_paging_init(void) +{ + int i, cpunode; + char node_str[128]; + pgd_t *pgd; + pmd_t *pmd; + pte_t *pte; + unsigned long pages_avail; + + sparc_iomap.start = SUN4M_IOBASE_VADDR; /* 16MB of IOSPACE on all sun4m's. */ + + if (sparc_cpu_model == sun4d) + num_contexts = 65536; /* We know it is Viking */ + else { + /* Find the number of contexts on the srmmu. */ + cpunode = prom_getchild(prom_root_node); + num_contexts = 0; + while(cpunode != 0) { + prom_getstring(cpunode, "device_type", node_str, sizeof(node_str)); + if(!strcmp(node_str, "cpu")) { + num_contexts = prom_getintdefault(cpunode, "mmu-nctx", 0x8); + break; + } + cpunode = prom_getsibling(cpunode); + } + } + + if(!num_contexts) { + prom_printf("Something wrong, can't find cpu node in paging_init.\n"); + prom_halt(); + } + + pages_avail = 0; + last_valid_pfn = bootmem_init(&pages_avail); + + srmmu_nocache_calcsize(); + srmmu_nocache_init(); + srmmu_inherit_prom_mappings(0xfe400000,(LINUX_OPPROM_ENDVM-PAGE_SIZE)); + map_kernel(); + + /* ctx table has to be physically aligned to its size */ + srmmu_context_table = (ctxd_t *)__srmmu_get_nocache(num_contexts*sizeof(ctxd_t), num_contexts*sizeof(ctxd_t)); + srmmu_ctx_table_phys = (ctxd_t *)__nocache_pa((unsigned long)srmmu_context_table); + + for(i = 0; i < num_contexts; i++) + srmmu_ctxd_set((ctxd_t *)__nocache_fix(&srmmu_context_table[i]), srmmu_swapper_pg_dir); + + flush_cache_all(); + srmmu_set_ctable_ptr((unsigned long)srmmu_ctx_table_phys); + flush_tlb_all(); + poke_srmmu(); + +#ifdef CONFIG_SUN_IO + srmmu_allocate_ptable_skeleton(sparc_iomap.start, IOBASE_END); + srmmu_allocate_ptable_skeleton(DVMA_VADDR, DVMA_END); +#endif + + srmmu_allocate_ptable_skeleton( + __fix_to_virt(__end_of_fixed_addresses - 1), FIXADDR_TOP); + srmmu_allocate_ptable_skeleton(PKMAP_BASE, PKMAP_END); + + pgd = pgd_offset_k(PKMAP_BASE); + pmd = srmmu_pmd_offset(pgd, PKMAP_BASE); + pte = srmmu_pte_offset(pmd, PKMAP_BASE); + pkmap_page_table = pte; + + flush_cache_all(); + flush_tlb_all(); + + sparc_context_init(num_contexts); + + kmap_init(); + + { + unsigned long zones_size[MAX_NR_ZONES]; + unsigned long zholes_size[MAX_NR_ZONES]; + unsigned long npages; + int znum; + + for (znum = 0; znum < MAX_NR_ZONES; znum++) + zones_size[znum] = zholes_size[znum] = 0; + + npages = max_low_pfn - pfn_base; + + zones_size[ZONE_DMA] = npages; + zholes_size[ZONE_DMA] = npages - pages_avail; + + npages = highend_pfn - max_low_pfn; + zones_size[ZONE_HIGHMEM] = npages; + zholes_size[ZONE_HIGHMEM] = npages - calc_highpages(); + + free_area_init_node(0, &contig_page_data, zones_size, + pfn_base, zholes_size); + } +} + +static void srmmu_mmu_info(struct seq_file *m) +{ + seq_printf(m, + "MMU type\t: %s\n" + "contexts\t: %d\n" + "nocache total\t: %ld\n" + "nocache used\t: %d\n", + srmmu_name, + num_contexts, + srmmu_nocache_size, + srmmu_nocache_map.used << SRMMU_NOCACHE_BITMAP_SHIFT); +} + +static void srmmu_update_mmu_cache(struct vm_area_struct * vma, unsigned long address, pte_t pte) +{ +} + +static void srmmu_destroy_context(struct mm_struct *mm) +{ + + if(mm->context != NO_CONTEXT) { + flush_cache_mm(mm); + srmmu_ctxd_set(&srmmu_context_table[mm->context], srmmu_swapper_pg_dir); + flush_tlb_mm(mm); + spin_lock(&srmmu_context_spinlock); + free_context(mm->context); + spin_unlock(&srmmu_context_spinlock); + mm->context = NO_CONTEXT; + } +} + +/* Init various srmmu chip types. */ +static void __init srmmu_is_bad(void) +{ + prom_printf("Could not determine SRMMU chip type.\n"); + prom_halt(); +} + +static void __init init_vac_layout(void) +{ + int nd, cache_lines; + char node_str[128]; +#ifdef CONFIG_SMP + int cpu = 0; + unsigned long max_size = 0; + unsigned long min_line_size = 0x10000000; +#endif + + nd = prom_getchild(prom_root_node); + while((nd = prom_getsibling(nd)) != 0) { + prom_getstring(nd, "device_type", node_str, sizeof(node_str)); + if(!strcmp(node_str, "cpu")) { + vac_line_size = prom_getint(nd, "cache-line-size"); + if (vac_line_size == -1) { + prom_printf("can't determine cache-line-size, " + "halting.\n"); + prom_halt(); + } + cache_lines = prom_getint(nd, "cache-nlines"); + if (cache_lines == -1) { + prom_printf("can't determine cache-nlines, halting.\n"); + prom_halt(); + } + + vac_cache_size = cache_lines * vac_line_size; +#ifdef CONFIG_SMP + if(vac_cache_size > max_size) + max_size = vac_cache_size; + if(vac_line_size < min_line_size) + min_line_size = vac_line_size; + cpu++; + if (cpu >= NR_CPUS || !cpu_online(cpu)) + break; +#else + break; +#endif + } + } + if(nd == 0) { + prom_printf("No CPU nodes found, halting.\n"); + prom_halt(); + } +#ifdef CONFIG_SMP + vac_cache_size = max_size; + vac_line_size = min_line_size; +#endif + printk("SRMMU: Using VAC size of %d bytes, line size %d bytes.\n", + (int)vac_cache_size, (int)vac_line_size); +} + +static void __init poke_hypersparc(void) +{ + volatile unsigned long clear; + unsigned long mreg = srmmu_get_mmureg(); + + hyper_flush_unconditional_combined(); + + mreg &= ~(HYPERSPARC_CWENABLE); + mreg |= (HYPERSPARC_CENABLE | HYPERSPARC_WBENABLE); + mreg |= (HYPERSPARC_CMODE); + + srmmu_set_mmureg(mreg); + +#if 0 /* XXX I think this is bad news... -DaveM */ + hyper_clear_all_tags(); +#endif + + put_ross_icr(HYPERSPARC_ICCR_FTD | HYPERSPARC_ICCR_ICE); + hyper_flush_whole_icache(); + clear = srmmu_get_faddr(); + clear = srmmu_get_fstatus(); +} + +static void __init init_hypersparc(void) +{ + srmmu_name = "ROSS HyperSparc"; + srmmu_modtype = HyperSparc; + + init_vac_layout(); + + is_hypersparc = 1; + + BTFIXUPSET_CALL(pte_clear, srmmu_pte_clear, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pmd_clear, srmmu_pmd_clear, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pgd_clear, srmmu_pgd_clear, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_all, hypersparc_flush_cache_all, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_mm, hypersparc_flush_cache_mm, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_range, hypersparc_flush_cache_range, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_page, hypersparc_flush_cache_page, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(flush_tlb_all, hypersparc_flush_tlb_all, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_mm, hypersparc_flush_tlb_mm, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_range, hypersparc_flush_tlb_range, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_page, hypersparc_flush_tlb_page, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(__flush_page_to_ram, hypersparc_flush_page_to_ram, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_sig_insns, hypersparc_flush_sig_insns, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_page_for_dma, hypersparc_flush_page_for_dma, BTFIXUPCALL_NOP); + + + poke_srmmu = poke_hypersparc; + + hypersparc_setup_blockops(); +} + +static void __init poke_cypress(void) +{ + unsigned long mreg = srmmu_get_mmureg(); + unsigned long faddr, tagval; + volatile unsigned long cypress_sucks; + volatile unsigned long clear; + + clear = srmmu_get_faddr(); + clear = srmmu_get_fstatus(); + + if (!(mreg & CYPRESS_CENABLE)) { + for(faddr = 0x0; faddr < 0x10000; faddr += 20) { + __asm__ __volatile__("sta %%g0, [%0 + %1] %2\n\t" + "sta %%g0, [%0] %2\n\t" : : + "r" (faddr), "r" (0x40000), + "i" (ASI_M_DATAC_TAG)); + } + } else { + for(faddr = 0; faddr < 0x10000; faddr += 0x20) { + __asm__ __volatile__("lda [%1 + %2] %3, %0\n\t" : + "=r" (tagval) : + "r" (faddr), "r" (0x40000), + "i" (ASI_M_DATAC_TAG)); + + /* If modified and valid, kick it. */ + if((tagval & 0x60) == 0x60) + cypress_sucks = *(unsigned long *) + (0xf0020000 + faddr); + } + } + + /* And one more, for our good neighbor, Mr. Broken Cypress. */ + clear = srmmu_get_faddr(); + clear = srmmu_get_fstatus(); + + mreg |= (CYPRESS_CENABLE | CYPRESS_CMODE); + srmmu_set_mmureg(mreg); +} + +static void __init init_cypress_common(void) +{ + init_vac_layout(); + + BTFIXUPSET_CALL(pte_clear, srmmu_pte_clear, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pmd_clear, srmmu_pmd_clear, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pgd_clear, srmmu_pgd_clear, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_all, cypress_flush_cache_all, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_mm, cypress_flush_cache_mm, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_range, cypress_flush_cache_range, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_page, cypress_flush_cache_page, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(flush_tlb_all, cypress_flush_tlb_all, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_mm, cypress_flush_tlb_mm, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_page, cypress_flush_tlb_page, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_range, cypress_flush_tlb_range, BTFIXUPCALL_NORM); + + + BTFIXUPSET_CALL(__flush_page_to_ram, cypress_flush_page_to_ram, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_sig_insns, cypress_flush_sig_insns, BTFIXUPCALL_NOP); + BTFIXUPSET_CALL(flush_page_for_dma, cypress_flush_page_for_dma, BTFIXUPCALL_NOP); + + poke_srmmu = poke_cypress; +} + +static void __init init_cypress_604(void) +{ + srmmu_name = "ROSS Cypress-604(UP)"; + srmmu_modtype = Cypress; + init_cypress_common(); +} + +static void __init init_cypress_605(unsigned long mrev) +{ + srmmu_name = "ROSS Cypress-605(MP)"; + if(mrev == 0xe) { + srmmu_modtype = Cypress_vE; + hwbug_bitmask |= HWBUG_COPYBACK_BROKEN; + } else { + if(mrev == 0xd) { + srmmu_modtype = Cypress_vD; + hwbug_bitmask |= HWBUG_ASIFLUSH_BROKEN; + } else { + srmmu_modtype = Cypress; + } + } + init_cypress_common(); +} + +static void __init poke_swift(void) +{ + unsigned long mreg; + + /* Clear any crap from the cache or else... */ + swift_flush_cache_all(); + + /* Enable I & D caches */ + mreg = srmmu_get_mmureg(); + mreg |= (SWIFT_IE | SWIFT_DE); + /* + * The Swift branch folding logic is completely broken. At + * trap time, if things are just right, if can mistakenly + * think that a trap is coming from kernel mode when in fact + * it is coming from user mode (it mis-executes the branch in + * the trap code). So you see things like crashme completely + * hosing your machine which is completely unacceptable. Turn + * this shit off... nice job Fujitsu. + */ + mreg &= ~(SWIFT_BF); + srmmu_set_mmureg(mreg); +} + +#define SWIFT_MASKID_ADDR 0x10003018 +static void __init init_swift(void) +{ + unsigned long swift_rev; + + __asm__ __volatile__("lda [%1] %2, %0\n\t" + "srl %0, 0x18, %0\n\t" : + "=r" (swift_rev) : + "r" (SWIFT_MASKID_ADDR), "i" (ASI_M_BYPASS)); + srmmu_name = "Fujitsu Swift"; + switch(swift_rev) { + case 0x11: + case 0x20: + case 0x23: + case 0x30: + srmmu_modtype = Swift_lots_o_bugs; + hwbug_bitmask |= (HWBUG_KERN_ACCBROKEN | HWBUG_KERN_CBITBROKEN); + /* + * Gee george, I wonder why Sun is so hush hush about + * this hardware bug... really braindamage stuff going + * on here. However I think we can find a way to avoid + * all of the workaround overhead under Linux. Basically, + * any page fault can cause kernel pages to become user + * accessible (the mmu gets confused and clears some of + * the ACC bits in kernel ptes). Aha, sounds pretty + * horrible eh? But wait, after extensive testing it appears + * that if you use pgd_t level large kernel pte's (like the + * 4MB pages on the Pentium) the bug does not get tripped + * at all. This avoids almost all of the major overhead. + * Welcome to a world where your vendor tells you to, + * "apply this kernel patch" instead of "sorry for the + * broken hardware, send it back and we'll give you + * properly functioning parts" + */ + break; + case 0x25: + case 0x31: + srmmu_modtype = Swift_bad_c; + hwbug_bitmask |= HWBUG_KERN_CBITBROKEN; + /* + * You see Sun allude to this hardware bug but never + * admit things directly, they'll say things like, + * "the Swift chip cache problems" or similar. + */ + break; + default: + srmmu_modtype = Swift_ok; + break; + }; + + BTFIXUPSET_CALL(flush_cache_all, swift_flush_cache_all, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_mm, swift_flush_cache_mm, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_page, swift_flush_cache_page, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_range, swift_flush_cache_range, BTFIXUPCALL_NORM); + + + BTFIXUPSET_CALL(flush_tlb_all, swift_flush_tlb_all, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_mm, swift_flush_tlb_mm, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_page, swift_flush_tlb_page, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_range, swift_flush_tlb_range, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(__flush_page_to_ram, swift_flush_page_to_ram, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_sig_insns, swift_flush_sig_insns, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_page_for_dma, swift_flush_page_for_dma, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(update_mmu_cache, swift_update_mmu_cache, BTFIXUPCALL_NORM); + + flush_page_for_dma_global = 0; + + /* + * Are you now convinced that the Swift is one of the + * biggest VLSI abortions of all time? Bravo Fujitsu! + * Fujitsu, the !#?!%$'d up processor people. I bet if + * you examined the microcode of the Swift you'd find + * XXX's all over the place. + */ + poke_srmmu = poke_swift; +} + +static void turbosparc_flush_cache_all(void) +{ + flush_user_windows(); + turbosparc_idflash_clear(); +} + +static void turbosparc_flush_cache_mm(struct mm_struct *mm) +{ + FLUSH_BEGIN(mm) + flush_user_windows(); + turbosparc_idflash_clear(); + FLUSH_END +} + +static void turbosparc_flush_cache_range(struct vm_area_struct *vma, unsigned long start, unsigned long end) +{ + FLUSH_BEGIN(vma->vm_mm) + flush_user_windows(); + turbosparc_idflash_clear(); + FLUSH_END +} + +static void turbosparc_flush_cache_page(struct vm_area_struct *vma, unsigned long page) +{ + FLUSH_BEGIN(vma->vm_mm) + flush_user_windows(); + if (vma->vm_flags & VM_EXEC) + turbosparc_flush_icache(); + turbosparc_flush_dcache(); + FLUSH_END +} + +/* TurboSparc is copy-back, if we turn it on, but this does not work. */ +static void turbosparc_flush_page_to_ram(unsigned long page) +{ +#ifdef TURBOSPARC_WRITEBACK + volatile unsigned long clear; + + if (srmmu_hwprobe(page)) + turbosparc_flush_page_cache(page); + clear = srmmu_get_fstatus(); +#endif +} + +static void turbosparc_flush_sig_insns(struct mm_struct *mm, unsigned long insn_addr) +{ +} + +static void turbosparc_flush_page_for_dma(unsigned long page) +{ + turbosparc_flush_dcache(); +} + +static void turbosparc_flush_tlb_all(void) +{ + srmmu_flush_whole_tlb(); +} + +static void turbosparc_flush_tlb_mm(struct mm_struct *mm) +{ + FLUSH_BEGIN(mm) + srmmu_flush_whole_tlb(); + FLUSH_END +} + +static void turbosparc_flush_tlb_range(struct vm_area_struct *vma, unsigned long start, unsigned long end) +{ + FLUSH_BEGIN(vma->vm_mm) + srmmu_flush_whole_tlb(); + FLUSH_END +} + +static void turbosparc_flush_tlb_page(struct vm_area_struct *vma, unsigned long page) +{ + FLUSH_BEGIN(vma->vm_mm) + srmmu_flush_whole_tlb(); + FLUSH_END +} + + +static void __init poke_turbosparc(void) +{ + unsigned long mreg = srmmu_get_mmureg(); + unsigned long ccreg; + + /* Clear any crap from the cache or else... */ + turbosparc_flush_cache_all(); + mreg &= ~(TURBOSPARC_ICENABLE | TURBOSPARC_DCENABLE); /* Temporarily disable I & D caches */ + mreg &= ~(TURBOSPARC_PCENABLE); /* Don't check parity */ + srmmu_set_mmureg(mreg); + + ccreg = turbosparc_get_ccreg(); + +#ifdef TURBOSPARC_WRITEBACK + ccreg |= (TURBOSPARC_SNENABLE); /* Do DVMA snooping in Dcache */ + ccreg &= ~(TURBOSPARC_uS2 | TURBOSPARC_WTENABLE); + /* Write-back D-cache, emulate VLSI + * abortion number three, not number one */ +#else + /* For now let's play safe, optimize later */ + ccreg |= (TURBOSPARC_SNENABLE | TURBOSPARC_WTENABLE); + /* Do DVMA snooping in Dcache, Write-thru D-cache */ + ccreg &= ~(TURBOSPARC_uS2); + /* Emulate VLSI abortion number three, not number one */ +#endif + + switch (ccreg & 7) { + case 0: /* No SE cache */ + case 7: /* Test mode */ + break; + default: + ccreg |= (TURBOSPARC_SCENABLE); + } + turbosparc_set_ccreg (ccreg); + + mreg |= (TURBOSPARC_ICENABLE | TURBOSPARC_DCENABLE); /* I & D caches on */ + mreg |= (TURBOSPARC_ICSNOOP); /* Icache snooping on */ + srmmu_set_mmureg(mreg); +} + +static void __init init_turbosparc(void) +{ + srmmu_name = "Fujitsu TurboSparc"; + srmmu_modtype = TurboSparc; + + BTFIXUPSET_CALL(flush_cache_all, turbosparc_flush_cache_all, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_mm, turbosparc_flush_cache_mm, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_page, turbosparc_flush_cache_page, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_range, turbosparc_flush_cache_range, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(flush_tlb_all, turbosparc_flush_tlb_all, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_mm, turbosparc_flush_tlb_mm, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_page, turbosparc_flush_tlb_page, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_range, turbosparc_flush_tlb_range, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(__flush_page_to_ram, turbosparc_flush_page_to_ram, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(flush_sig_insns, turbosparc_flush_sig_insns, BTFIXUPCALL_NOP); + BTFIXUPSET_CALL(flush_page_for_dma, turbosparc_flush_page_for_dma, BTFIXUPCALL_NORM); + + poke_srmmu = poke_turbosparc; +} + +static void __init poke_tsunami(void) +{ + unsigned long mreg = srmmu_get_mmureg(); + + tsunami_flush_icache(); + tsunami_flush_dcache(); + mreg &= ~TSUNAMI_ITD; + mreg |= (TSUNAMI_IENAB | TSUNAMI_DENAB); + srmmu_set_mmureg(mreg); +} + +static void __init init_tsunami(void) +{ + /* + * Tsunami's pretty sane, Sun and TI actually got it + * somewhat right this time. Fujitsu should have + * taken some lessons from them. + */ + + srmmu_name = "TI Tsunami"; + srmmu_modtype = Tsunami; + + BTFIXUPSET_CALL(flush_cache_all, tsunami_flush_cache_all, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_mm, tsunami_flush_cache_mm, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_page, tsunami_flush_cache_page, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_range, tsunami_flush_cache_range, BTFIXUPCALL_NORM); + + + BTFIXUPSET_CALL(flush_tlb_all, tsunami_flush_tlb_all, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_mm, tsunami_flush_tlb_mm, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_page, tsunami_flush_tlb_page, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_range, tsunami_flush_tlb_range, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(__flush_page_to_ram, tsunami_flush_page_to_ram, BTFIXUPCALL_NOP); + BTFIXUPSET_CALL(flush_sig_insns, tsunami_flush_sig_insns, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_page_for_dma, tsunami_flush_page_for_dma, BTFIXUPCALL_NORM); + + poke_srmmu = poke_tsunami; + + tsunami_setup_blockops(); +} + +static void __init poke_viking(void) +{ + unsigned long mreg = srmmu_get_mmureg(); + static int smp_catch; + + if(viking_mxcc_present) { + unsigned long mxcc_control = mxcc_get_creg(); + + mxcc_control |= (MXCC_CTL_ECE | MXCC_CTL_PRE | MXCC_CTL_MCE); + mxcc_control &= ~(MXCC_CTL_RRC); + mxcc_set_creg(mxcc_control); + + /* + * We don't need memory parity checks. + * XXX This is a mess, have to dig out later. ecd. + viking_mxcc_turn_off_parity(&mreg, &mxcc_control); + */ + + /* We do cache ptables on MXCC. */ + mreg |= VIKING_TCENABLE; + } else { + unsigned long bpreg; + + mreg &= ~(VIKING_TCENABLE); + if(smp_catch++) { + /* Must disable mixed-cmd mode here for other cpu's. */ + bpreg = viking_get_bpreg(); + bpreg &= ~(VIKING_ACTION_MIX); + viking_set_bpreg(bpreg); + + /* Just in case PROM does something funny. */ + msi_set_sync(); + } + } + + mreg |= VIKING_SPENABLE; + mreg |= (VIKING_ICENABLE | VIKING_DCENABLE); + mreg |= VIKING_SBENABLE; + mreg &= ~(VIKING_ACENABLE); + srmmu_set_mmureg(mreg); + +#ifdef CONFIG_SMP + /* Avoid unnecessary cross calls. */ + BTFIXUPCOPY_CALL(flush_cache_all, local_flush_cache_all); + BTFIXUPCOPY_CALL(flush_cache_mm, local_flush_cache_mm); + BTFIXUPCOPY_CALL(flush_cache_range, local_flush_cache_range); + BTFIXUPCOPY_CALL(flush_cache_page, local_flush_cache_page); + BTFIXUPCOPY_CALL(__flush_page_to_ram, local_flush_page_to_ram); + BTFIXUPCOPY_CALL(flush_sig_insns, local_flush_sig_insns); + BTFIXUPCOPY_CALL(flush_page_for_dma, local_flush_page_for_dma); + btfixup(); +#endif +} + +static void __init init_viking(void) +{ + unsigned long mreg = srmmu_get_mmureg(); + + /* Ahhh, the viking. SRMMU VLSI abortion number two... */ + if(mreg & VIKING_MMODE) { + srmmu_name = "TI Viking"; + viking_mxcc_present = 0; + msi_set_sync(); + + BTFIXUPSET_CALL(pte_clear, srmmu_pte_clear, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pmd_clear, srmmu_pmd_clear, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pgd_clear, srmmu_pgd_clear, BTFIXUPCALL_NORM); + + /* + * We need this to make sure old viking takes no hits + * on it's cache for dma snoops to workaround the + * "load from non-cacheable memory" interrupt bug. + * This is only necessary because of the new way in + * which we use the IOMMU. + */ + BTFIXUPSET_CALL(flush_page_for_dma, viking_flush_page, BTFIXUPCALL_NORM); + + flush_page_for_dma_global = 0; + } else { + srmmu_name = "TI Viking/MXCC"; + viking_mxcc_present = 1; + + srmmu_cache_pagetables = 1; + + /* MXCC vikings lack the DMA snooping bug. */ + BTFIXUPSET_CALL(flush_page_for_dma, viking_flush_page_for_dma, BTFIXUPCALL_NOP); + } + + BTFIXUPSET_CALL(flush_cache_all, viking_flush_cache_all, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_mm, viking_flush_cache_mm, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_page, viking_flush_cache_page, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_range, viking_flush_cache_range, BTFIXUPCALL_NORM); + +#ifdef CONFIG_SMP + if (sparc_cpu_model == sun4d) { + BTFIXUPSET_CALL(flush_tlb_all, sun4dsmp_flush_tlb_all, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_mm, sun4dsmp_flush_tlb_mm, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_page, sun4dsmp_flush_tlb_page, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_range, sun4dsmp_flush_tlb_range, BTFIXUPCALL_NORM); + } else +#endif + { + BTFIXUPSET_CALL(flush_tlb_all, viking_flush_tlb_all, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_mm, viking_flush_tlb_mm, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_page, viking_flush_tlb_page, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_range, viking_flush_tlb_range, BTFIXUPCALL_NORM); + } + + BTFIXUPSET_CALL(__flush_page_to_ram, viking_flush_page_to_ram, BTFIXUPCALL_NOP); + BTFIXUPSET_CALL(flush_sig_insns, viking_flush_sig_insns, BTFIXUPCALL_NOP); + + poke_srmmu = poke_viking; +} + +/* Probe for the srmmu chip version. */ +static void __init get_srmmu_type(void) +{ + unsigned long mreg, psr; + unsigned long mod_typ, mod_rev, psr_typ, psr_vers; + + srmmu_modtype = SRMMU_INVAL_MOD; + hwbug_bitmask = 0; + + mreg = srmmu_get_mmureg(); psr = get_psr(); + mod_typ = (mreg & 0xf0000000) >> 28; + mod_rev = (mreg & 0x0f000000) >> 24; + psr_typ = (psr >> 28) & 0xf; + psr_vers = (psr >> 24) & 0xf; + + /* First, check for HyperSparc or Cypress. */ + if(mod_typ == 1) { + switch(mod_rev) { + case 7: + /* UP or MP Hypersparc */ + init_hypersparc(); + break; + case 0: + case 2: + /* Uniprocessor Cypress */ + init_cypress_604(); + break; + case 10: + case 11: + case 12: + /* _REALLY OLD_ Cypress MP chips... */ + case 13: + case 14: + case 15: + /* MP Cypress mmu/cache-controller */ + init_cypress_605(mod_rev); + break; + default: + /* Some other Cypress revision, assume a 605. */ + init_cypress_605(mod_rev); + break; + }; + return; + } + + /* + * Now Fujitsu TurboSparc. It might happen that it is + * in Swift emulation mode, so we will check later... + */ + if (psr_typ == 0 && psr_vers == 5) { + init_turbosparc(); + return; + } + + /* Next check for Fujitsu Swift. */ + if(psr_typ == 0 && psr_vers == 4) { + int cpunode; + char node_str[128]; + + /* Look if it is not a TurboSparc emulating Swift... */ + cpunode = prom_getchild(prom_root_node); + while((cpunode = prom_getsibling(cpunode)) != 0) { + prom_getstring(cpunode, "device_type", node_str, sizeof(node_str)); + if(!strcmp(node_str, "cpu")) { + if (!prom_getintdefault(cpunode, "psr-implementation", 1) && + prom_getintdefault(cpunode, "psr-version", 1) == 5) { + init_turbosparc(); + return; + } + break; + } + } + + init_swift(); + return; + } + + /* Now the Viking family of srmmu. */ + if(psr_typ == 4 && + ((psr_vers == 0) || + ((psr_vers == 1) && (mod_typ == 0) && (mod_rev == 0)))) { + init_viking(); + return; + } + + /* Finally the Tsunami. */ + if(psr_typ == 4 && psr_vers == 1 && (mod_typ || mod_rev)) { + init_tsunami(); + return; + } + + /* Oh well */ + srmmu_is_bad(); +} + +/* don't laugh, static pagetables */ +static void srmmu_check_pgt_cache(int low, int high) +{ +} + +extern unsigned long spwin_mmu_patchme, fwin_mmu_patchme, + tsetup_mmu_patchme, rtrap_mmu_patchme; + +extern unsigned long spwin_srmmu_stackchk, srmmu_fwin_stackchk, + tsetup_srmmu_stackchk, srmmu_rett_stackchk; + +extern unsigned long srmmu_fault; + +#define PATCH_BRANCH(insn, dest) do { \ + iaddr = &(insn); \ + daddr = &(dest); \ + *iaddr = SPARC_BRANCH((unsigned long) daddr, (unsigned long) iaddr); \ + } while(0) + +static void __init patch_window_trap_handlers(void) +{ + unsigned long *iaddr, *daddr; + + PATCH_BRANCH(spwin_mmu_patchme, spwin_srmmu_stackchk); + PATCH_BRANCH(fwin_mmu_patchme, srmmu_fwin_stackchk); + PATCH_BRANCH(tsetup_mmu_patchme, tsetup_srmmu_stackchk); + PATCH_BRANCH(rtrap_mmu_patchme, srmmu_rett_stackchk); + PATCH_BRANCH(sparc_ttable[SP_TRAP_TFLT].inst_three, srmmu_fault); + PATCH_BRANCH(sparc_ttable[SP_TRAP_DFLT].inst_three, srmmu_fault); + PATCH_BRANCH(sparc_ttable[SP_TRAP_DACC].inst_three, srmmu_fault); +} + +#ifdef CONFIG_SMP +/* Local cross-calls. */ +static void smp_flush_page_for_dma(unsigned long page) +{ + xc1((smpfunc_t) BTFIXUP_CALL(local_flush_page_for_dma), page); + local_flush_page_for_dma(page); +} + +#endif + +static pte_t srmmu_pgoff_to_pte(unsigned long pgoff) +{ + return __pte((pgoff << SRMMU_PTE_FILE_SHIFT) | SRMMU_FILE); +} + +static unsigned long srmmu_pte_to_pgoff(pte_t pte) +{ + return pte_val(pte) >> SRMMU_PTE_FILE_SHIFT; +} + +/* Load up routines and constants for sun4m and sun4d mmu */ +void __init ld_mmu_srmmu(void) +{ + extern void ld_mmu_iommu(void); + extern void ld_mmu_iounit(void); + extern void ___xchg32_sun4md(void); + + BTFIXUPSET_SIMM13(pgdir_shift, SRMMU_PGDIR_SHIFT); + BTFIXUPSET_SETHI(pgdir_size, SRMMU_PGDIR_SIZE); + BTFIXUPSET_SETHI(pgdir_mask, SRMMU_PGDIR_MASK); + + BTFIXUPSET_SIMM13(ptrs_per_pmd, SRMMU_PTRS_PER_PMD); + BTFIXUPSET_SIMM13(ptrs_per_pgd, SRMMU_PTRS_PER_PGD); + + BTFIXUPSET_INT(page_none, pgprot_val(SRMMU_PAGE_NONE)); + BTFIXUPSET_INT(page_shared, pgprot_val(SRMMU_PAGE_SHARED)); + BTFIXUPSET_INT(page_copy, pgprot_val(SRMMU_PAGE_COPY)); + BTFIXUPSET_INT(page_readonly, pgprot_val(SRMMU_PAGE_RDONLY)); + BTFIXUPSET_INT(page_kernel, pgprot_val(SRMMU_PAGE_KERNEL)); + page_kernel = pgprot_val(SRMMU_PAGE_KERNEL); + pg_iobits = SRMMU_VALID | SRMMU_WRITE | SRMMU_REF; + + /* Functions */ +#ifndef CONFIG_SMP + BTFIXUPSET_CALL(___xchg32, ___xchg32_sun4md, BTFIXUPCALL_SWAPG1G2); +#endif + BTFIXUPSET_CALL(do_check_pgt_cache, srmmu_check_pgt_cache, BTFIXUPCALL_NOP); + + BTFIXUPSET_CALL(set_pte, srmmu_set_pte, BTFIXUPCALL_SWAPO0O1); + BTFIXUPSET_CALL(switch_mm, srmmu_switch_mm, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(pte_pfn, srmmu_pte_pfn, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pmd_page, srmmu_pmd_page, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pgd_page, srmmu_pgd_page, BTFIXUPCALL_NORM); + + BTFIXUPSET_SETHI(none_mask, 0xF0000000); + + BTFIXUPSET_CALL(pte_present, srmmu_pte_present, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pte_clear, srmmu_pte_clear, BTFIXUPCALL_SWAPO0G0); + BTFIXUPSET_CALL(pte_read, srmmu_pte_read, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(pmd_bad, srmmu_pmd_bad, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pmd_present, srmmu_pmd_present, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pmd_clear, srmmu_pmd_clear, BTFIXUPCALL_SWAPO0G0); + + BTFIXUPSET_CALL(pgd_none, srmmu_pgd_none, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pgd_bad, srmmu_pgd_bad, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pgd_present, srmmu_pgd_present, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pgd_clear, srmmu_pgd_clear, BTFIXUPCALL_SWAPO0G0); + + BTFIXUPSET_CALL(mk_pte, srmmu_mk_pte, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(mk_pte_phys, srmmu_mk_pte_phys, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(mk_pte_io, srmmu_mk_pte_io, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pgd_set, srmmu_pgd_set, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pmd_set, srmmu_pmd_set, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pmd_populate, srmmu_pmd_populate, BTFIXUPCALL_NORM); + + BTFIXUPSET_INT(pte_modify_mask, SRMMU_CHG_MASK); + BTFIXUPSET_CALL(pmd_offset, srmmu_pmd_offset, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pte_offset_kernel, srmmu_pte_offset, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(free_pte_fast, srmmu_free_pte_fast, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pte_free, srmmu_pte_free, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pte_alloc_one_kernel, srmmu_pte_alloc_one_kernel, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pte_alloc_one, srmmu_pte_alloc_one, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(free_pmd_fast, srmmu_pmd_free, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pmd_alloc_one, srmmu_pmd_alloc_one, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(free_pgd_fast, srmmu_free_pgd_fast, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(get_pgd_fast, srmmu_get_pgd_fast, BTFIXUPCALL_NORM); + + BTFIXUPSET_HALF(pte_writei, SRMMU_WRITE); + BTFIXUPSET_HALF(pte_dirtyi, SRMMU_DIRTY); + BTFIXUPSET_HALF(pte_youngi, SRMMU_REF); + BTFIXUPSET_HALF(pte_filei, SRMMU_FILE); + BTFIXUPSET_HALF(pte_wrprotecti, SRMMU_WRITE); + BTFIXUPSET_HALF(pte_mkcleani, SRMMU_DIRTY); + BTFIXUPSET_HALF(pte_mkoldi, SRMMU_REF); + BTFIXUPSET_CALL(pte_mkwrite, srmmu_pte_mkwrite, BTFIXUPCALL_ORINT(SRMMU_WRITE)); + BTFIXUPSET_CALL(pte_mkdirty, srmmu_pte_mkdirty, BTFIXUPCALL_ORINT(SRMMU_DIRTY)); + BTFIXUPSET_CALL(pte_mkyoung, srmmu_pte_mkyoung, BTFIXUPCALL_ORINT(SRMMU_REF)); + BTFIXUPSET_CALL(update_mmu_cache, srmmu_update_mmu_cache, BTFIXUPCALL_NOP); + BTFIXUPSET_CALL(destroy_context, srmmu_destroy_context, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(sparc_mapiorange, srmmu_mapiorange, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(sparc_unmapiorange, srmmu_unmapiorange, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(__swp_type, srmmu_swp_type, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(__swp_offset, srmmu_swp_offset, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(__swp_entry, srmmu_swp_entry, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(mmu_info, srmmu_mmu_info, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(alloc_thread_info, srmmu_alloc_thread_info, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(free_thread_info, srmmu_free_thread_info, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(pte_to_pgoff, srmmu_pte_to_pgoff, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pgoff_to_pte, srmmu_pgoff_to_pte, BTFIXUPCALL_NORM); + + get_srmmu_type(); + patch_window_trap_handlers(); + +#ifdef CONFIG_SMP + /* El switcheroo... */ + + BTFIXUPCOPY_CALL(local_flush_cache_all, flush_cache_all); + BTFIXUPCOPY_CALL(local_flush_cache_mm, flush_cache_mm); + BTFIXUPCOPY_CALL(local_flush_cache_range, flush_cache_range); + BTFIXUPCOPY_CALL(local_flush_cache_page, flush_cache_page); + BTFIXUPCOPY_CALL(local_flush_tlb_all, flush_tlb_all); + BTFIXUPCOPY_CALL(local_flush_tlb_mm, flush_tlb_mm); + BTFIXUPCOPY_CALL(local_flush_tlb_range, flush_tlb_range); + BTFIXUPCOPY_CALL(local_flush_tlb_page, flush_tlb_page); + BTFIXUPCOPY_CALL(local_flush_page_to_ram, __flush_page_to_ram); + BTFIXUPCOPY_CALL(local_flush_sig_insns, flush_sig_insns); + BTFIXUPCOPY_CALL(local_flush_page_for_dma, flush_page_for_dma); + + BTFIXUPSET_CALL(flush_cache_all, smp_flush_cache_all, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_mm, smp_flush_cache_mm, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_range, smp_flush_cache_range, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_page, smp_flush_cache_page, BTFIXUPCALL_NORM); + if (sparc_cpu_model != sun4d) { + BTFIXUPSET_CALL(flush_tlb_all, smp_flush_tlb_all, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_mm, smp_flush_tlb_mm, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_range, smp_flush_tlb_range, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_page, smp_flush_tlb_page, BTFIXUPCALL_NORM); + } + BTFIXUPSET_CALL(__flush_page_to_ram, smp_flush_page_to_ram, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_sig_insns, smp_flush_sig_insns, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_page_for_dma, smp_flush_page_for_dma, BTFIXUPCALL_NORM); +#endif + + if (sparc_cpu_model == sun4d) + ld_mmu_iounit(); + else + ld_mmu_iommu(); +#ifdef CONFIG_SMP + if (sparc_cpu_model == sun4d) + sun4d_init_smp(); + else + sun4m_init_smp(); +#endif +} diff --git a/arch/sparc/mm/sun4c.c b/arch/sparc/mm/sun4c.c new file mode 100644 index 000000000000..1d560390e282 --- /dev/null +++ b/arch/sparc/mm/sun4c.c @@ -0,0 +1,2276 @@ +/* $Id: sun4c.c,v 1.212 2001/12/21 04:56:15 davem Exp $ + * sun4c.c: Doing in software what should be done in hardware. + * + * Copyright (C) 1996 David S. Miller (davem@caip.rutgers.edu) + * Copyright (C) 1996 Eddie C. Dost (ecd@skynet.be) + * Copyright (C) 1996 Andrew Tridgell (Andrew.Tridgell@anu.edu.au) + * Copyright (C) 1997-2000 Anton Blanchard (anton@samba.org) + * Copyright (C) 1998 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + */ + +#define NR_TASK_BUCKETS 512 + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/init.h> +#include <linux/bootmem.h> +#include <linux/highmem.h> +#include <linux/fs.h> +#include <linux/seq_file.h> + +#include <asm/scatterlist.h> +#include <asm/page.h> +#include <asm/pgalloc.h> +#include <asm/pgtable.h> +#include <asm/vaddrs.h> +#include <asm/idprom.h> +#include <asm/machines.h> +#include <asm/memreg.h> +#include <asm/processor.h> +#include <asm/auxio.h> +#include <asm/io.h> +#include <asm/oplib.h> +#include <asm/openprom.h> +#include <asm/mmu_context.h> +#include <asm/sun4paddr.h> +#include <asm/highmem.h> +#include <asm/btfixup.h> +#include <asm/cacheflush.h> +#include <asm/tlbflush.h> + +/* Because of our dynamic kernel TLB miss strategy, and how + * our DVMA mapping allocation works, you _MUST_: + * + * 1) Disable interrupts _and_ not touch any dynamic kernel + * memory while messing with kernel MMU state. By + * dynamic memory I mean any object which is not in + * the kernel image itself or a thread_union (both of + * which are locked into the MMU). + * 2) Disable interrupts while messing with user MMU state. + */ + +extern int num_segmaps, num_contexts; + +extern unsigned long page_kernel; + +#ifdef CONFIG_SUN4 +#define SUN4C_VAC_SIZE sun4c_vacinfo.num_bytes +#else +/* That's it, we prom_halt() on sun4c if the cache size is something other than 65536. + * So let's save some cycles and just use that everywhere except for that bootup + * sanity check. + */ +#define SUN4C_VAC_SIZE 65536 +#endif + +#define SUN4C_KERNEL_BUCKETS 32 + +/* Flushing the cache. */ +struct sun4c_vac_props sun4c_vacinfo; +unsigned long sun4c_kernel_faults; + +/* Invalidate every sun4c cache line tag. */ +static void __init sun4c_flush_all(void) +{ + unsigned long begin, end; + + if (sun4c_vacinfo.on) + panic("SUN4C: AIEEE, trying to invalidate vac while it is on."); + + /* Clear 'valid' bit in all cache line tags */ + begin = AC_CACHETAGS; + end = (AC_CACHETAGS + SUN4C_VAC_SIZE); + while (begin < end) { + __asm__ __volatile__("sta %%g0, [%0] %1\n\t" : : + "r" (begin), "i" (ASI_CONTROL)); + begin += sun4c_vacinfo.linesize; + } +} + +static void sun4c_flush_context_hw(void) +{ + unsigned long end = SUN4C_VAC_SIZE; + + __asm__ __volatile__( + "1: addcc %0, -4096, %0\n\t" + " bne 1b\n\t" + " sta %%g0, [%0] %2" + : "=&r" (end) + : "0" (end), "i" (ASI_HWFLUSHCONTEXT) + : "cc"); +} + +/* Must be called minimally with IRQs disabled. */ +static void sun4c_flush_segment_hw(unsigned long addr) +{ + if (sun4c_get_segmap(addr) != invalid_segment) { + unsigned long vac_size = SUN4C_VAC_SIZE; + + __asm__ __volatile__( + "1: addcc %0, -4096, %0\n\t" + " bne 1b\n\t" + " sta %%g0, [%2 + %0] %3" + : "=&r" (vac_size) + : "0" (vac_size), "r" (addr), "i" (ASI_HWFLUSHSEG) + : "cc"); + } +} + +/* File local boot time fixups. */ +BTFIXUPDEF_CALL(void, sun4c_flush_page, unsigned long) +BTFIXUPDEF_CALL(void, sun4c_flush_segment, unsigned long) +BTFIXUPDEF_CALL(void, sun4c_flush_context, void) + +#define sun4c_flush_page(addr) BTFIXUP_CALL(sun4c_flush_page)(addr) +#define sun4c_flush_segment(addr) BTFIXUP_CALL(sun4c_flush_segment)(addr) +#define sun4c_flush_context() BTFIXUP_CALL(sun4c_flush_context)() + +/* Must be called minimally with interrupts disabled. */ +static void sun4c_flush_page_hw(unsigned long addr) +{ + addr &= PAGE_MASK; + if ((int)sun4c_get_pte(addr) < 0) + __asm__ __volatile__("sta %%g0, [%0] %1" + : : "r" (addr), "i" (ASI_HWFLUSHPAGE)); +} + +/* Don't inline the software version as it eats too many cache lines if expanded. */ +static void sun4c_flush_context_sw(void) +{ + unsigned long nbytes = SUN4C_VAC_SIZE; + unsigned long lsize = sun4c_vacinfo.linesize; + + __asm__ __volatile__( + "add %2, %2, %%g1\n\t" + "add %2, %%g1, %%g2\n\t" + "add %2, %%g2, %%g3\n\t" + "add %2, %%g3, %%g4\n\t" + "add %2, %%g4, %%g5\n\t" + "add %2, %%g5, %%o4\n\t" + "add %2, %%o4, %%o5\n" + "1:\n\t" + "subcc %0, %%o5, %0\n\t" + "sta %%g0, [%0] %3\n\t" + "sta %%g0, [%0 + %2] %3\n\t" + "sta %%g0, [%0 + %%g1] %3\n\t" + "sta %%g0, [%0 + %%g2] %3\n\t" + "sta %%g0, [%0 + %%g3] %3\n\t" + "sta %%g0, [%0 + %%g4] %3\n\t" + "sta %%g0, [%0 + %%g5] %3\n\t" + "bg 1b\n\t" + " sta %%g0, [%1 + %%o4] %3\n" + : "=&r" (nbytes) + : "0" (nbytes), "r" (lsize), "i" (ASI_FLUSHCTX) + : "g1", "g2", "g3", "g4", "g5", "o4", "o5", "cc"); +} + +/* Don't inline the software version as it eats too many cache lines if expanded. */ +static void sun4c_flush_segment_sw(unsigned long addr) +{ + if (sun4c_get_segmap(addr) != invalid_segment) { + unsigned long nbytes = SUN4C_VAC_SIZE; + unsigned long lsize = sun4c_vacinfo.linesize; + + __asm__ __volatile__( + "add %2, %2, %%g1\n\t" + "add %2, %%g1, %%g2\n\t" + "add %2, %%g2, %%g3\n\t" + "add %2, %%g3, %%g4\n\t" + "add %2, %%g4, %%g5\n\t" + "add %2, %%g5, %%o4\n\t" + "add %2, %%o4, %%o5\n" + "1:\n\t" + "subcc %1, %%o5, %1\n\t" + "sta %%g0, [%0] %6\n\t" + "sta %%g0, [%0 + %2] %6\n\t" + "sta %%g0, [%0 + %%g1] %6\n\t" + "sta %%g0, [%0 + %%g2] %6\n\t" + "sta %%g0, [%0 + %%g3] %6\n\t" + "sta %%g0, [%0 + %%g4] %6\n\t" + "sta %%g0, [%0 + %%g5] %6\n\t" + "sta %%g0, [%0 + %%o4] %6\n\t" + "bg 1b\n\t" + " add %0, %%o5, %0\n" + : "=&r" (addr), "=&r" (nbytes), "=&r" (lsize) + : "0" (addr), "1" (nbytes), "2" (lsize), + "i" (ASI_FLUSHSEG) + : "g1", "g2", "g3", "g4", "g5", "o4", "o5", "cc"); + } +} + +/* Don't inline the software version as it eats too many cache lines if expanded. */ +static void sun4c_flush_page_sw(unsigned long addr) +{ + addr &= PAGE_MASK; + if ((sun4c_get_pte(addr) & (_SUN4C_PAGE_NOCACHE | _SUN4C_PAGE_VALID)) == + _SUN4C_PAGE_VALID) { + unsigned long left = PAGE_SIZE; + unsigned long lsize = sun4c_vacinfo.linesize; + + __asm__ __volatile__( + "add %2, %2, %%g1\n\t" + "add %2, %%g1, %%g2\n\t" + "add %2, %%g2, %%g3\n\t" + "add %2, %%g3, %%g4\n\t" + "add %2, %%g4, %%g5\n\t" + "add %2, %%g5, %%o4\n\t" + "add %2, %%o4, %%o5\n" + "1:\n\t" + "subcc %1, %%o5, %1\n\t" + "sta %%g0, [%0] %6\n\t" + "sta %%g0, [%0 + %2] %6\n\t" + "sta %%g0, [%0 + %%g1] %6\n\t" + "sta %%g0, [%0 + %%g2] %6\n\t" + "sta %%g0, [%0 + %%g3] %6\n\t" + "sta %%g0, [%0 + %%g4] %6\n\t" + "sta %%g0, [%0 + %%g5] %6\n\t" + "sta %%g0, [%0 + %%o4] %6\n\t" + "bg 1b\n\t" + " add %0, %%o5, %0\n" + : "=&r" (addr), "=&r" (left), "=&r" (lsize) + : "0" (addr), "1" (left), "2" (lsize), + "i" (ASI_FLUSHPG) + : "g1", "g2", "g3", "g4", "g5", "o4", "o5", "cc"); + } +} + +/* The sun4c's do have an on chip store buffer. And the way you + * clear them out isn't so obvious. The only way I can think of + * to accomplish this is to read the current context register, + * store the same value there, then read an external hardware + * register. + */ +void sun4c_complete_all_stores(void) +{ + volatile int _unused; + + _unused = sun4c_get_context(); + sun4c_set_context(_unused); +#ifdef CONFIG_SUN_AUXIO + _unused = get_auxio(); +#endif +} + +/* Bootup utility functions. */ +static inline void sun4c_init_clean_segmap(unsigned char pseg) +{ + unsigned long vaddr; + + sun4c_put_segmap(0, pseg); + for (vaddr = 0; vaddr < SUN4C_REAL_PGDIR_SIZE; vaddr += PAGE_SIZE) + sun4c_put_pte(vaddr, 0); + sun4c_put_segmap(0, invalid_segment); +} + +static inline void sun4c_init_clean_mmu(unsigned long kernel_end) +{ + unsigned long vaddr; + unsigned char savectx, ctx; + + savectx = sun4c_get_context(); + kernel_end = SUN4C_REAL_PGDIR_ALIGN(kernel_end); + for (ctx = 0; ctx < num_contexts; ctx++) { + sun4c_set_context(ctx); + for (vaddr = 0; vaddr < 0x20000000; vaddr += SUN4C_REAL_PGDIR_SIZE) + sun4c_put_segmap(vaddr, invalid_segment); + for (vaddr = 0xe0000000; vaddr < KERNBASE; vaddr += SUN4C_REAL_PGDIR_SIZE) + sun4c_put_segmap(vaddr, invalid_segment); + for (vaddr = kernel_end; vaddr < KADB_DEBUGGER_BEGVM; vaddr += SUN4C_REAL_PGDIR_SIZE) + sun4c_put_segmap(vaddr, invalid_segment); + for (vaddr = LINUX_OPPROM_ENDVM; vaddr; vaddr += SUN4C_REAL_PGDIR_SIZE) + sun4c_put_segmap(vaddr, invalid_segment); + } + sun4c_set_context(savectx); +} + +void __init sun4c_probe_vac(void) +{ + sun4c_disable_vac(); + + if (ARCH_SUN4) { + switch (idprom->id_machtype) { + + case (SM_SUN4|SM_4_110): + sun4c_vacinfo.type = VAC_NONE; + sun4c_vacinfo.num_bytes = 0; + sun4c_vacinfo.linesize = 0; + sun4c_vacinfo.do_hwflushes = 0; + prom_printf("No VAC. Get some bucks and buy a real computer."); + prom_halt(); + break; + + case (SM_SUN4|SM_4_260): + sun4c_vacinfo.type = VAC_WRITE_BACK; + sun4c_vacinfo.num_bytes = 128 * 1024; + sun4c_vacinfo.linesize = 16; + sun4c_vacinfo.do_hwflushes = 0; + break; + + case (SM_SUN4|SM_4_330): + sun4c_vacinfo.type = VAC_WRITE_THROUGH; + sun4c_vacinfo.num_bytes = 128 * 1024; + sun4c_vacinfo.linesize = 16; + sun4c_vacinfo.do_hwflushes = 0; + break; + + case (SM_SUN4|SM_4_470): + sun4c_vacinfo.type = VAC_WRITE_BACK; + sun4c_vacinfo.num_bytes = 128 * 1024; + sun4c_vacinfo.linesize = 32; + sun4c_vacinfo.do_hwflushes = 0; + break; + + default: + prom_printf("Cannot initialize VAC - weird sun4 model idprom->id_machtype = %d", idprom->id_machtype); + prom_halt(); + }; + } else { + sun4c_vacinfo.type = VAC_WRITE_THROUGH; + + if ((idprom->id_machtype == (SM_SUN4C | SM_4C_SS1)) || + (idprom->id_machtype == (SM_SUN4C | SM_4C_SS1PLUS))) { + /* PROM on SS1 lacks this info, to be super safe we + * hard code it here since this arch is cast in stone. + */ + sun4c_vacinfo.num_bytes = 65536; + sun4c_vacinfo.linesize = 16; + } else { + sun4c_vacinfo.num_bytes = + prom_getintdefault(prom_root_node, "vac-size", 65536); + sun4c_vacinfo.linesize = + prom_getintdefault(prom_root_node, "vac-linesize", 16); + } + sun4c_vacinfo.do_hwflushes = + prom_getintdefault(prom_root_node, "vac-hwflush", 0); + + if (sun4c_vacinfo.do_hwflushes == 0) + sun4c_vacinfo.do_hwflushes = + prom_getintdefault(prom_root_node, "vac_hwflush", 0); + + if (sun4c_vacinfo.num_bytes != 65536) { + prom_printf("WEIRD Sun4C VAC cache size, " + "tell sparclinux@vger.kernel.org"); + prom_halt(); + } + } + + sun4c_vacinfo.num_lines = + (sun4c_vacinfo.num_bytes / sun4c_vacinfo.linesize); + switch (sun4c_vacinfo.linesize) { + case 16: + sun4c_vacinfo.log2lsize = 4; + break; + case 32: + sun4c_vacinfo.log2lsize = 5; + break; + default: + prom_printf("probe_vac: Didn't expect vac-linesize of %d, halting\n", + sun4c_vacinfo.linesize); + prom_halt(); + }; + + sun4c_flush_all(); + sun4c_enable_vac(); +} + +/* Patch instructions for the low level kernel fault handler. */ +extern unsigned long invalid_segment_patch1, invalid_segment_patch1_ff; +extern unsigned long invalid_segment_patch2, invalid_segment_patch2_ff; +extern unsigned long invalid_segment_patch1_1ff, invalid_segment_patch2_1ff; +extern unsigned long num_context_patch1, num_context_patch1_16; +extern unsigned long num_context_patch2_16; +extern unsigned long vac_linesize_patch, vac_linesize_patch_32; +extern unsigned long vac_hwflush_patch1, vac_hwflush_patch1_on; +extern unsigned long vac_hwflush_patch2, vac_hwflush_patch2_on; + +#define PATCH_INSN(src, dst) do { \ + daddr = &(dst); \ + iaddr = &(src); \ + *daddr = *iaddr; \ + } while (0) + +static void __init patch_kernel_fault_handler(void) +{ + unsigned long *iaddr, *daddr; + + switch (num_segmaps) { + case 128: + /* Default, nothing to do. */ + break; + case 256: + PATCH_INSN(invalid_segment_patch1_ff, + invalid_segment_patch1); + PATCH_INSN(invalid_segment_patch2_ff, + invalid_segment_patch2); + break; + case 512: + PATCH_INSN(invalid_segment_patch1_1ff, + invalid_segment_patch1); + PATCH_INSN(invalid_segment_patch2_1ff, + invalid_segment_patch2); + break; + default: + prom_printf("Unhandled number of segmaps: %d\n", + num_segmaps); + prom_halt(); + }; + switch (num_contexts) { + case 8: + /* Default, nothing to do. */ + break; + case 16: + PATCH_INSN(num_context_patch1_16, + num_context_patch1); + break; + default: + prom_printf("Unhandled number of contexts: %d\n", + num_contexts); + prom_halt(); + }; + + if (sun4c_vacinfo.do_hwflushes != 0) { + PATCH_INSN(vac_hwflush_patch1_on, vac_hwflush_patch1); + PATCH_INSN(vac_hwflush_patch2_on, vac_hwflush_patch2); + } else { + switch (sun4c_vacinfo.linesize) { + case 16: + /* Default, nothing to do. */ + break; + case 32: + PATCH_INSN(vac_linesize_patch_32, vac_linesize_patch); + break; + default: + prom_printf("Impossible VAC linesize %d, halting...\n", + sun4c_vacinfo.linesize); + prom_halt(); + }; + } +} + +static void __init sun4c_probe_mmu(void) +{ + if (ARCH_SUN4) { + switch (idprom->id_machtype) { + case (SM_SUN4|SM_4_110): + prom_printf("No support for 4100 yet\n"); + prom_halt(); + num_segmaps = 256; + num_contexts = 8; + break; + + case (SM_SUN4|SM_4_260): + /* should be 512 segmaps. when it get fixed */ + num_segmaps = 256; + num_contexts = 16; + break; + + case (SM_SUN4|SM_4_330): + num_segmaps = 256; + num_contexts = 16; + break; + + case (SM_SUN4|SM_4_470): + /* should be 1024 segmaps. when it get fixed */ + num_segmaps = 256; + num_contexts = 64; + break; + default: + prom_printf("Invalid SUN4 model\n"); + prom_halt(); + }; + } else { + if ((idprom->id_machtype == (SM_SUN4C | SM_4C_SS1)) || + (idprom->id_machtype == (SM_SUN4C | SM_4C_SS1PLUS))) { + /* Hardcode these just to be safe, PROM on SS1 does + * not have this info available in the root node. + */ + num_segmaps = 128; + num_contexts = 8; + } else { + num_segmaps = + prom_getintdefault(prom_root_node, "mmu-npmg", 128); + num_contexts = + prom_getintdefault(prom_root_node, "mmu-nctx", 0x8); + } + } + patch_kernel_fault_handler(); +} + +volatile unsigned long *sun4c_memerr_reg = NULL; + +void __init sun4c_probe_memerr_reg(void) +{ + int node; + struct linux_prom_registers regs[1]; + + if (ARCH_SUN4) { + sun4c_memerr_reg = ioremap(sun4_memreg_physaddr, PAGE_SIZE); + } else { + node = prom_getchild(prom_root_node); + node = prom_searchsiblings(prom_root_node, "memory-error"); + if (!node) + return; + if (prom_getproperty(node, "reg", (char *)regs, sizeof(regs)) <= 0) + return; + /* hmm I think regs[0].which_io is zero here anyways */ + sun4c_memerr_reg = ioremap(regs[0].phys_addr, regs[0].reg_size); + } +} + +static inline void sun4c_init_ss2_cache_bug(void) +{ + extern unsigned long start; + + if ((idprom->id_machtype == (SM_SUN4C | SM_4C_SS2)) || + (idprom->id_machtype == (SM_SUN4C | SM_4C_IPX)) || + (idprom->id_machtype == (SM_SUN4 | SM_4_330)) || + (idprom->id_machtype == (SM_SUN4C | SM_4C_ELC))) { + /* Whee.. */ + printk("SS2 cache bug detected, uncaching trap table page\n"); + sun4c_flush_page((unsigned int) &start); + sun4c_put_pte(((unsigned long) &start), + (sun4c_get_pte((unsigned long) &start) | _SUN4C_PAGE_NOCACHE)); + } +} + +/* Addr is always aligned on a page boundary for us already. */ +static int sun4c_map_dma_area(dma_addr_t *pba, unsigned long va, + unsigned long addr, int len) +{ + unsigned long page, end; + + *pba = addr; + + end = PAGE_ALIGN((addr + len)); + while (addr < end) { + page = va; + sun4c_flush_page(page); + page -= PAGE_OFFSET; + page >>= PAGE_SHIFT; + page |= (_SUN4C_PAGE_VALID | _SUN4C_PAGE_DIRTY | + _SUN4C_PAGE_NOCACHE | _SUN4C_PAGE_PRIV); + sun4c_put_pte(addr, page); + addr += PAGE_SIZE; + va += PAGE_SIZE; + } + + return 0; +} + +static struct page *sun4c_translate_dvma(unsigned long busa) +{ + /* Fortunately for us, bus_addr == uncached_virt in sun4c. */ + unsigned long pte = sun4c_get_pte(busa); + return pfn_to_page(pte & SUN4C_PFN_MASK); +} + +static void sun4c_unmap_dma_area(unsigned long busa, int len) +{ + /* Fortunately for us, bus_addr == uncached_virt in sun4c. */ + /* XXX Implement this */ +} + +/* TLB management. */ + +/* Don't change this struct without changing entry.S. This is used + * in the in-window kernel fault handler, and you don't want to mess + * with that. (See sun4c_fault in entry.S). + */ +struct sun4c_mmu_entry { + struct sun4c_mmu_entry *next; + struct sun4c_mmu_entry *prev; + unsigned long vaddr; + unsigned char pseg; + unsigned char locked; + + /* For user mappings only, and completely hidden from kernel + * TLB miss code. + */ + unsigned char ctx; + struct sun4c_mmu_entry *lru_next; + struct sun4c_mmu_entry *lru_prev; +}; + +static struct sun4c_mmu_entry mmu_entry_pool[SUN4C_MAX_SEGMAPS]; + +static void __init sun4c_init_mmu_entry_pool(void) +{ + int i; + + for (i=0; i < SUN4C_MAX_SEGMAPS; i++) { + mmu_entry_pool[i].pseg = i; + mmu_entry_pool[i].next = NULL; + mmu_entry_pool[i].prev = NULL; + mmu_entry_pool[i].vaddr = 0; + mmu_entry_pool[i].locked = 0; + mmu_entry_pool[i].ctx = 0; + mmu_entry_pool[i].lru_next = NULL; + mmu_entry_pool[i].lru_prev = NULL; + } + mmu_entry_pool[invalid_segment].locked = 1; +} + +static inline void fix_permissions(unsigned long vaddr, unsigned long bits_on, + unsigned long bits_off) +{ + unsigned long start, end; + + end = vaddr + SUN4C_REAL_PGDIR_SIZE; + for (start = vaddr; start < end; start += PAGE_SIZE) + if (sun4c_get_pte(start) & _SUN4C_PAGE_VALID) + sun4c_put_pte(start, (sun4c_get_pte(start) | bits_on) & + ~bits_off); +} + +static inline void sun4c_init_map_kernelprom(unsigned long kernel_end) +{ + unsigned long vaddr; + unsigned char pseg, ctx; +#ifdef CONFIG_SUN4 + /* sun4/110 and 260 have no kadb. */ + if ((idprom->id_machtype != (SM_SUN4 | SM_4_260)) && + (idprom->id_machtype != (SM_SUN4 | SM_4_110))) { +#endif + for (vaddr = KADB_DEBUGGER_BEGVM; + vaddr < LINUX_OPPROM_ENDVM; + vaddr += SUN4C_REAL_PGDIR_SIZE) { + pseg = sun4c_get_segmap(vaddr); + if (pseg != invalid_segment) { + mmu_entry_pool[pseg].locked = 1; + for (ctx = 0; ctx < num_contexts; ctx++) + prom_putsegment(ctx, vaddr, pseg); + fix_permissions(vaddr, _SUN4C_PAGE_PRIV, 0); + } + } +#ifdef CONFIG_SUN4 + } +#endif + for (vaddr = KERNBASE; vaddr < kernel_end; vaddr += SUN4C_REAL_PGDIR_SIZE) { + pseg = sun4c_get_segmap(vaddr); + mmu_entry_pool[pseg].locked = 1; + for (ctx = 0; ctx < num_contexts; ctx++) + prom_putsegment(ctx, vaddr, pseg); + fix_permissions(vaddr, _SUN4C_PAGE_PRIV, _SUN4C_PAGE_NOCACHE); + } +} + +static void __init sun4c_init_lock_area(unsigned long start, unsigned long end) +{ + int i, ctx; + + while (start < end) { + for (i = 0; i < invalid_segment; i++) + if (!mmu_entry_pool[i].locked) + break; + mmu_entry_pool[i].locked = 1; + sun4c_init_clean_segmap(i); + for (ctx = 0; ctx < num_contexts; ctx++) + prom_putsegment(ctx, start, mmu_entry_pool[i].pseg); + start += SUN4C_REAL_PGDIR_SIZE; + } +} + +/* Don't change this struct without changing entry.S. This is used + * in the in-window kernel fault handler, and you don't want to mess + * with that. (See sun4c_fault in entry.S). + */ +struct sun4c_mmu_ring { + struct sun4c_mmu_entry ringhd; + int num_entries; +}; + +static struct sun4c_mmu_ring sun4c_context_ring[SUN4C_MAX_CONTEXTS]; /* used user entries */ +static struct sun4c_mmu_ring sun4c_ufree_ring; /* free user entries */ +static struct sun4c_mmu_ring sun4c_ulru_ring; /* LRU user entries */ +struct sun4c_mmu_ring sun4c_kernel_ring; /* used kernel entries */ +struct sun4c_mmu_ring sun4c_kfree_ring; /* free kernel entries */ + +static inline void sun4c_init_rings(void) +{ + int i; + + for (i = 0; i < SUN4C_MAX_CONTEXTS; i++) { + sun4c_context_ring[i].ringhd.next = + sun4c_context_ring[i].ringhd.prev = + &sun4c_context_ring[i].ringhd; + sun4c_context_ring[i].num_entries = 0; + } + sun4c_ufree_ring.ringhd.next = sun4c_ufree_ring.ringhd.prev = + &sun4c_ufree_ring.ringhd; + sun4c_ufree_ring.num_entries = 0; + sun4c_ulru_ring.ringhd.lru_next = sun4c_ulru_ring.ringhd.lru_prev = + &sun4c_ulru_ring.ringhd; + sun4c_ulru_ring.num_entries = 0; + sun4c_kernel_ring.ringhd.next = sun4c_kernel_ring.ringhd.prev = + &sun4c_kernel_ring.ringhd; + sun4c_kernel_ring.num_entries = 0; + sun4c_kfree_ring.ringhd.next = sun4c_kfree_ring.ringhd.prev = + &sun4c_kfree_ring.ringhd; + sun4c_kfree_ring.num_entries = 0; +} + +static void add_ring(struct sun4c_mmu_ring *ring, + struct sun4c_mmu_entry *entry) +{ + struct sun4c_mmu_entry *head = &ring->ringhd; + + entry->prev = head; + (entry->next = head->next)->prev = entry; + head->next = entry; + ring->num_entries++; +} + +static __inline__ void add_lru(struct sun4c_mmu_entry *entry) +{ + struct sun4c_mmu_ring *ring = &sun4c_ulru_ring; + struct sun4c_mmu_entry *head = &ring->ringhd; + + entry->lru_next = head; + (entry->lru_prev = head->lru_prev)->lru_next = entry; + head->lru_prev = entry; +} + +static void add_ring_ordered(struct sun4c_mmu_ring *ring, + struct sun4c_mmu_entry *entry) +{ + struct sun4c_mmu_entry *head = &ring->ringhd; + unsigned long addr = entry->vaddr; + + while ((head->next != &ring->ringhd) && (head->next->vaddr < addr)) + head = head->next; + + entry->prev = head; + (entry->next = head->next)->prev = entry; + head->next = entry; + ring->num_entries++; + + add_lru(entry); +} + +static __inline__ void remove_ring(struct sun4c_mmu_ring *ring, + struct sun4c_mmu_entry *entry) +{ + struct sun4c_mmu_entry *next = entry->next; + + (next->prev = entry->prev)->next = next; + ring->num_entries--; +} + +static void remove_lru(struct sun4c_mmu_entry *entry) +{ + struct sun4c_mmu_entry *next = entry->lru_next; + + (next->lru_prev = entry->lru_prev)->lru_next = next; +} + +static void free_user_entry(int ctx, struct sun4c_mmu_entry *entry) +{ + remove_ring(sun4c_context_ring+ctx, entry); + remove_lru(entry); + add_ring(&sun4c_ufree_ring, entry); +} + +static void free_kernel_entry(struct sun4c_mmu_entry *entry, + struct sun4c_mmu_ring *ring) +{ + remove_ring(ring, entry); + add_ring(&sun4c_kfree_ring, entry); +} + +static void __init sun4c_init_fill_kernel_ring(int howmany) +{ + int i; + + while (howmany) { + for (i = 0; i < invalid_segment; i++) + if (!mmu_entry_pool[i].locked) + break; + mmu_entry_pool[i].locked = 1; + sun4c_init_clean_segmap(i); + add_ring(&sun4c_kfree_ring, &mmu_entry_pool[i]); + howmany--; + } +} + +static void __init sun4c_init_fill_user_ring(void) +{ + int i; + + for (i = 0; i < invalid_segment; i++) { + if (mmu_entry_pool[i].locked) + continue; + sun4c_init_clean_segmap(i); + add_ring(&sun4c_ufree_ring, &mmu_entry_pool[i]); + } +} + +static void sun4c_kernel_unmap(struct sun4c_mmu_entry *kentry) +{ + int savectx, ctx; + + savectx = sun4c_get_context(); + for (ctx = 0; ctx < num_contexts; ctx++) { + sun4c_set_context(ctx); + sun4c_put_segmap(kentry->vaddr, invalid_segment); + } + sun4c_set_context(savectx); +} + +static void sun4c_kernel_map(struct sun4c_mmu_entry *kentry) +{ + int savectx, ctx; + + savectx = sun4c_get_context(); + for (ctx = 0; ctx < num_contexts; ctx++) { + sun4c_set_context(ctx); + sun4c_put_segmap(kentry->vaddr, kentry->pseg); + } + sun4c_set_context(savectx); +} + +#define sun4c_user_unmap(__entry) \ + sun4c_put_segmap((__entry)->vaddr, invalid_segment) + +static void sun4c_demap_context(struct sun4c_mmu_ring *crp, unsigned char ctx) +{ + struct sun4c_mmu_entry *head = &crp->ringhd; + unsigned long flags; + + local_irq_save(flags); + if (head->next != head) { + struct sun4c_mmu_entry *entry = head->next; + int savectx = sun4c_get_context(); + + flush_user_windows(); + sun4c_set_context(ctx); + sun4c_flush_context(); + do { + struct sun4c_mmu_entry *next = entry->next; + + sun4c_user_unmap(entry); + free_user_entry(ctx, entry); + + entry = next; + } while (entry != head); + sun4c_set_context(savectx); + } + local_irq_restore(flags); +} + +static int sun4c_user_taken_entries; /* This is how much we have. */ +static int max_user_taken_entries; /* This limits us and prevents deadlock. */ + +static struct sun4c_mmu_entry *sun4c_kernel_strategy(void) +{ + struct sun4c_mmu_entry *this_entry; + + /* If some are free, return first one. */ + if (sun4c_kfree_ring.num_entries) { + this_entry = sun4c_kfree_ring.ringhd.next; + return this_entry; + } + + /* Else free one up. */ + this_entry = sun4c_kernel_ring.ringhd.prev; + sun4c_flush_segment(this_entry->vaddr); + sun4c_kernel_unmap(this_entry); + free_kernel_entry(this_entry, &sun4c_kernel_ring); + this_entry = sun4c_kfree_ring.ringhd.next; + + return this_entry; +} + +/* Using this method to free up mmu entries eliminates a lot of + * potential races since we have a kernel that incurs tlb + * replacement faults. There may be performance penalties. + * + * NOTE: Must be called with interrupts disabled. + */ +static struct sun4c_mmu_entry *sun4c_user_strategy(void) +{ + struct sun4c_mmu_entry *entry; + unsigned char ctx; + int savectx; + + /* If some are free, return first one. */ + if (sun4c_ufree_ring.num_entries) { + entry = sun4c_ufree_ring.ringhd.next; + goto unlink_out; + } + + if (sun4c_user_taken_entries) { + entry = sun4c_kernel_strategy(); + sun4c_user_taken_entries--; + goto kunlink_out; + } + + /* Grab from the beginning of the LRU list. */ + entry = sun4c_ulru_ring.ringhd.lru_next; + ctx = entry->ctx; + + savectx = sun4c_get_context(); + flush_user_windows(); + sun4c_set_context(ctx); + sun4c_flush_segment(entry->vaddr); + sun4c_user_unmap(entry); + remove_ring(sun4c_context_ring + ctx, entry); + remove_lru(entry); + sun4c_set_context(savectx); + + return entry; + +unlink_out: + remove_ring(&sun4c_ufree_ring, entry); + return entry; +kunlink_out: + remove_ring(&sun4c_kfree_ring, entry); + return entry; +} + +/* NOTE: Must be called with interrupts disabled. */ +void sun4c_grow_kernel_ring(void) +{ + struct sun4c_mmu_entry *entry; + + /* Prevent deadlock condition. */ + if (sun4c_user_taken_entries >= max_user_taken_entries) + return; + + if (sun4c_ufree_ring.num_entries) { + entry = sun4c_ufree_ring.ringhd.next; + remove_ring(&sun4c_ufree_ring, entry); + add_ring(&sun4c_kfree_ring, entry); + sun4c_user_taken_entries++; + } +} + +/* 2 page buckets for task struct and kernel stack allocation. + * + * TASK_STACK_BEGIN + * bucket[0] + * bucket[1] + * [ ... ] + * bucket[NR_TASK_BUCKETS-1] + * TASK_STACK_BEGIN + (sizeof(struct task_bucket) * NR_TASK_BUCKETS) + * + * Each slot looks like: + * + * page 1 -- task struct + beginning of kernel stack + * page 2 -- rest of kernel stack + */ + +union task_union *sun4c_bucket[NR_TASK_BUCKETS]; + +static int sun4c_lowbucket_avail; + +#define BUCKET_EMPTY ((union task_union *) 0) +#define BUCKET_SHIFT (PAGE_SHIFT + 1) /* log2(sizeof(struct task_bucket)) */ +#define BUCKET_SIZE (1 << BUCKET_SHIFT) +#define BUCKET_NUM(addr) ((((addr) - SUN4C_LOCK_VADDR) >> BUCKET_SHIFT)) +#define BUCKET_ADDR(num) (((num) << BUCKET_SHIFT) + SUN4C_LOCK_VADDR) +#define BUCKET_PTE(page) \ + ((((page) - PAGE_OFFSET) >> PAGE_SHIFT) | pgprot_val(SUN4C_PAGE_KERNEL)) +#define BUCKET_PTE_PAGE(pte) \ + (PAGE_OFFSET + (((pte) & SUN4C_PFN_MASK) << PAGE_SHIFT)) + +static void get_locked_segment(unsigned long addr) +{ + struct sun4c_mmu_entry *stolen; + unsigned long flags; + + local_irq_save(flags); + addr &= SUN4C_REAL_PGDIR_MASK; + stolen = sun4c_user_strategy(); + max_user_taken_entries--; + stolen->vaddr = addr; + flush_user_windows(); + sun4c_kernel_map(stolen); + local_irq_restore(flags); +} + +static void free_locked_segment(unsigned long addr) +{ + struct sun4c_mmu_entry *entry; + unsigned long flags; + unsigned char pseg; + + local_irq_save(flags); + addr &= SUN4C_REAL_PGDIR_MASK; + pseg = sun4c_get_segmap(addr); + entry = &mmu_entry_pool[pseg]; + + flush_user_windows(); + sun4c_flush_segment(addr); + sun4c_kernel_unmap(entry); + add_ring(&sun4c_ufree_ring, entry); + max_user_taken_entries++; + local_irq_restore(flags); +} + +static inline void garbage_collect(int entry) +{ + int start, end; + + /* 32 buckets per segment... */ + entry &= ~31; + start = entry; + for (end = (start + 32); start < end; start++) + if (sun4c_bucket[start] != BUCKET_EMPTY) + return; + + /* Entire segment empty, release it. */ + free_locked_segment(BUCKET_ADDR(entry)); +} + +static struct thread_info *sun4c_alloc_thread_info(void) +{ + unsigned long addr, pages; + int entry; + + pages = __get_free_pages(GFP_KERNEL, THREAD_INFO_ORDER); + if (!pages) + return NULL; + + for (entry = sun4c_lowbucket_avail; entry < NR_TASK_BUCKETS; entry++) + if (sun4c_bucket[entry] == BUCKET_EMPTY) + break; + if (entry == NR_TASK_BUCKETS) { + free_pages(pages, THREAD_INFO_ORDER); + return NULL; + } + if (entry >= sun4c_lowbucket_avail) + sun4c_lowbucket_avail = entry + 1; + + addr = BUCKET_ADDR(entry); + sun4c_bucket[entry] = (union task_union *) addr; + if(sun4c_get_segmap(addr) == invalid_segment) + get_locked_segment(addr); + + /* We are changing the virtual color of the page(s) + * so we must flush the cache to guarantee consistency. + */ + sun4c_flush_page(pages); +#ifndef CONFIG_SUN4 + sun4c_flush_page(pages + PAGE_SIZE); +#endif + + sun4c_put_pte(addr, BUCKET_PTE(pages)); +#ifndef CONFIG_SUN4 + sun4c_put_pte(addr + PAGE_SIZE, BUCKET_PTE(pages + PAGE_SIZE)); +#endif + +#ifdef CONFIG_DEBUG_STACK_USAGE + memset((void *)addr, 0, PAGE_SIZE << THREAD_INFO_ORDER); +#endif /* DEBUG_STACK_USAGE */ + + return (struct thread_info *) addr; +} + +static void sun4c_free_thread_info(struct thread_info *ti) +{ + unsigned long tiaddr = (unsigned long) ti; + unsigned long pages = BUCKET_PTE_PAGE(sun4c_get_pte(tiaddr)); + int entry = BUCKET_NUM(tiaddr); + + /* We are deleting a mapping, so the flush here is mandatory. */ + sun4c_flush_page(tiaddr); +#ifndef CONFIG_SUN4 + sun4c_flush_page(tiaddr + PAGE_SIZE); +#endif + sun4c_put_pte(tiaddr, 0); +#ifndef CONFIG_SUN4 + sun4c_put_pte(tiaddr + PAGE_SIZE, 0); +#endif + sun4c_bucket[entry] = BUCKET_EMPTY; + if (entry < sun4c_lowbucket_avail) + sun4c_lowbucket_avail = entry; + + free_pages(pages, THREAD_INFO_ORDER); + garbage_collect(entry); +} + +static void __init sun4c_init_buckets(void) +{ + int entry; + + if (sizeof(union thread_union) != (PAGE_SIZE << THREAD_INFO_ORDER)) { + extern void thread_info_size_is_bolixed_pete(void); + thread_info_size_is_bolixed_pete(); + } + + for (entry = 0; entry < NR_TASK_BUCKETS; entry++) + sun4c_bucket[entry] = BUCKET_EMPTY; + sun4c_lowbucket_avail = 0; +} + +static unsigned long sun4c_iobuffer_start; +static unsigned long sun4c_iobuffer_end; +static unsigned long sun4c_iobuffer_high; +static unsigned long *sun4c_iobuffer_map; +static int iobuffer_map_size; + +/* + * Alias our pages so they do not cause a trap. + * Also one page may be aliased into several I/O areas and we may + * finish these I/O separately. + */ +static char *sun4c_lockarea(char *vaddr, unsigned long size) +{ + unsigned long base, scan; + unsigned long npages; + unsigned long vpage; + unsigned long pte; + unsigned long apage; + unsigned long high; + unsigned long flags; + + npages = (((unsigned long)vaddr & ~PAGE_MASK) + + size + (PAGE_SIZE-1)) >> PAGE_SHIFT; + + scan = 0; + local_irq_save(flags); + for (;;) { + scan = find_next_zero_bit(sun4c_iobuffer_map, + iobuffer_map_size, scan); + if ((base = scan) + npages > iobuffer_map_size) goto abend; + for (;;) { + if (scan >= base + npages) goto found; + if (test_bit(scan, sun4c_iobuffer_map)) break; + scan++; + } + } + +found: + high = ((base + npages) << PAGE_SHIFT) + sun4c_iobuffer_start; + high = SUN4C_REAL_PGDIR_ALIGN(high); + while (high > sun4c_iobuffer_high) { + get_locked_segment(sun4c_iobuffer_high); + sun4c_iobuffer_high += SUN4C_REAL_PGDIR_SIZE; + } + + vpage = ((unsigned long) vaddr) & PAGE_MASK; + for (scan = base; scan < base+npages; scan++) { + pte = ((vpage-PAGE_OFFSET) >> PAGE_SHIFT); + pte |= pgprot_val(SUN4C_PAGE_KERNEL); + pte |= _SUN4C_PAGE_NOCACHE; + set_bit(scan, sun4c_iobuffer_map); + apage = (scan << PAGE_SHIFT) + sun4c_iobuffer_start; + + /* Flush original mapping so we see the right things later. */ + sun4c_flush_page(vpage); + + sun4c_put_pte(apage, pte); + vpage += PAGE_SIZE; + } + local_irq_restore(flags); + return (char *) ((base << PAGE_SHIFT) + sun4c_iobuffer_start + + (((unsigned long) vaddr) & ~PAGE_MASK)); + +abend: + local_irq_restore(flags); + printk("DMA vaddr=0x%p size=%08lx\n", vaddr, size); + panic("Out of iobuffer table"); + return NULL; +} + +static void sun4c_unlockarea(char *vaddr, unsigned long size) +{ + unsigned long vpage, npages; + unsigned long flags; + int scan, high; + + vpage = (unsigned long)vaddr & PAGE_MASK; + npages = (((unsigned long)vaddr & ~PAGE_MASK) + + size + (PAGE_SIZE-1)) >> PAGE_SHIFT; + + local_irq_save(flags); + while (npages != 0) { + --npages; + + /* This mapping is marked non-cachable, no flush necessary. */ + sun4c_put_pte(vpage, 0); + clear_bit((vpage - sun4c_iobuffer_start) >> PAGE_SHIFT, + sun4c_iobuffer_map); + vpage += PAGE_SIZE; + } + + /* garbage collect */ + scan = (sun4c_iobuffer_high - sun4c_iobuffer_start) >> PAGE_SHIFT; + while (scan >= 0 && !sun4c_iobuffer_map[scan >> 5]) + scan -= 32; + scan += 32; + high = sun4c_iobuffer_start + (scan << PAGE_SHIFT); + high = SUN4C_REAL_PGDIR_ALIGN(high) + SUN4C_REAL_PGDIR_SIZE; + while (high < sun4c_iobuffer_high) { + sun4c_iobuffer_high -= SUN4C_REAL_PGDIR_SIZE; + free_locked_segment(sun4c_iobuffer_high); + } + local_irq_restore(flags); +} + +/* Note the scsi code at init time passes to here buffers + * which sit on the kernel stack, those are already locked + * by implication and fool the page locking code above + * if passed to by mistake. + */ +static __u32 sun4c_get_scsi_one(char *bufptr, unsigned long len, struct sbus_bus *sbus) +{ + unsigned long page; + + page = ((unsigned long)bufptr) & PAGE_MASK; + if (!virt_addr_valid(page)) { + sun4c_flush_page(page); + return (__u32)bufptr; /* already locked */ + } + return (__u32)sun4c_lockarea(bufptr, len); +} + +static void sun4c_get_scsi_sgl(struct scatterlist *sg, int sz, struct sbus_bus *sbus) +{ + while (sz != 0) { + --sz; + sg[sz].dvma_address = (__u32)sun4c_lockarea(page_address(sg[sz].page) + sg[sz].offset, sg[sz].length); + sg[sz].dvma_length = sg[sz].length; + } +} + +static void sun4c_release_scsi_one(__u32 bufptr, unsigned long len, struct sbus_bus *sbus) +{ + if (bufptr < sun4c_iobuffer_start) + return; /* On kernel stack or similar, see above */ + sun4c_unlockarea((char *)bufptr, len); +} + +static void sun4c_release_scsi_sgl(struct scatterlist *sg, int sz, struct sbus_bus *sbus) +{ + while (sz != 0) { + --sz; + sun4c_unlockarea((char *)sg[sz].dvma_address, sg[sz].length); + } +} + +#define TASK_ENTRY_SIZE BUCKET_SIZE /* see above */ +#define LONG_ALIGN(x) (((x)+(sizeof(long))-1)&~((sizeof(long))-1)) + +struct vm_area_struct sun4c_kstack_vma; + +static void __init sun4c_init_lock_areas(void) +{ + unsigned long sun4c_taskstack_start; + unsigned long sun4c_taskstack_end; + int bitmap_size; + + sun4c_init_buckets(); + sun4c_taskstack_start = SUN4C_LOCK_VADDR; + sun4c_taskstack_end = (sun4c_taskstack_start + + (TASK_ENTRY_SIZE * NR_TASK_BUCKETS)); + if (sun4c_taskstack_end >= SUN4C_LOCK_END) { + prom_printf("Too many tasks, decrease NR_TASK_BUCKETS please.\n"); + prom_halt(); + } + + sun4c_iobuffer_start = sun4c_iobuffer_high = + SUN4C_REAL_PGDIR_ALIGN(sun4c_taskstack_end); + sun4c_iobuffer_end = SUN4C_LOCK_END; + bitmap_size = (sun4c_iobuffer_end - sun4c_iobuffer_start) >> PAGE_SHIFT; + bitmap_size = (bitmap_size + 7) >> 3; + bitmap_size = LONG_ALIGN(bitmap_size); + iobuffer_map_size = bitmap_size << 3; + sun4c_iobuffer_map = __alloc_bootmem(bitmap_size, SMP_CACHE_BYTES, 0UL); + memset((void *) sun4c_iobuffer_map, 0, bitmap_size); + + sun4c_kstack_vma.vm_mm = &init_mm; + sun4c_kstack_vma.vm_start = sun4c_taskstack_start; + sun4c_kstack_vma.vm_end = sun4c_taskstack_end; + sun4c_kstack_vma.vm_page_prot = PAGE_SHARED; + sun4c_kstack_vma.vm_flags = VM_READ | VM_WRITE | VM_EXEC; + insert_vm_struct(&init_mm, &sun4c_kstack_vma); +} + +/* Cache flushing on the sun4c. */ +static void sun4c_flush_cache_all(void) +{ + unsigned long begin, end; + + flush_user_windows(); + begin = (KERNBASE + SUN4C_REAL_PGDIR_SIZE); + end = (begin + SUN4C_VAC_SIZE); + + if (sun4c_vacinfo.linesize == 32) { + while (begin < end) { + __asm__ __volatile__( + "ld [%0 + 0x00], %%g0\n\t" + "ld [%0 + 0x20], %%g0\n\t" + "ld [%0 + 0x40], %%g0\n\t" + "ld [%0 + 0x60], %%g0\n\t" + "ld [%0 + 0x80], %%g0\n\t" + "ld [%0 + 0xa0], %%g0\n\t" + "ld [%0 + 0xc0], %%g0\n\t" + "ld [%0 + 0xe0], %%g0\n\t" + "ld [%0 + 0x100], %%g0\n\t" + "ld [%0 + 0x120], %%g0\n\t" + "ld [%0 + 0x140], %%g0\n\t" + "ld [%0 + 0x160], %%g0\n\t" + "ld [%0 + 0x180], %%g0\n\t" + "ld [%0 + 0x1a0], %%g0\n\t" + "ld [%0 + 0x1c0], %%g0\n\t" + "ld [%0 + 0x1e0], %%g0\n" + : : "r" (begin)); + begin += 512; + } + } else { + while (begin < end) { + __asm__ __volatile__( + "ld [%0 + 0x00], %%g0\n\t" + "ld [%0 + 0x10], %%g0\n\t" + "ld [%0 + 0x20], %%g0\n\t" + "ld [%0 + 0x30], %%g0\n\t" + "ld [%0 + 0x40], %%g0\n\t" + "ld [%0 + 0x50], %%g0\n\t" + "ld [%0 + 0x60], %%g0\n\t" + "ld [%0 + 0x70], %%g0\n\t" + "ld [%0 + 0x80], %%g0\n\t" + "ld [%0 + 0x90], %%g0\n\t" + "ld [%0 + 0xa0], %%g0\n\t" + "ld [%0 + 0xb0], %%g0\n\t" + "ld [%0 + 0xc0], %%g0\n\t" + "ld [%0 + 0xd0], %%g0\n\t" + "ld [%0 + 0xe0], %%g0\n\t" + "ld [%0 + 0xf0], %%g0\n" + : : "r" (begin)); + begin += 256; + } + } +} + +static void sun4c_flush_cache_mm(struct mm_struct *mm) +{ + int new_ctx = mm->context; + + if (new_ctx != NO_CONTEXT) { + flush_user_windows(); + + if (sun4c_context_ring[new_ctx].num_entries) { + struct sun4c_mmu_entry *head = &sun4c_context_ring[new_ctx].ringhd; + unsigned long flags; + + local_irq_save(flags); + if (head->next != head) { + struct sun4c_mmu_entry *entry = head->next; + int savectx = sun4c_get_context(); + + sun4c_set_context(new_ctx); + sun4c_flush_context(); + do { + struct sun4c_mmu_entry *next = entry->next; + + sun4c_user_unmap(entry); + free_user_entry(new_ctx, entry); + + entry = next; + } while (entry != head); + sun4c_set_context(savectx); + } + local_irq_restore(flags); + } + } +} + +static void sun4c_flush_cache_range(struct vm_area_struct *vma, unsigned long start, unsigned long end) +{ + struct mm_struct *mm = vma->vm_mm; + int new_ctx = mm->context; + + if (new_ctx != NO_CONTEXT) { + struct sun4c_mmu_entry *head = &sun4c_context_ring[new_ctx].ringhd; + struct sun4c_mmu_entry *entry; + unsigned long flags; + + flush_user_windows(); + + local_irq_save(flags); + /* All user segmap chains are ordered on entry->vaddr. */ + for (entry = head->next; + (entry != head) && ((entry->vaddr+SUN4C_REAL_PGDIR_SIZE) < start); + entry = entry->next) + ; + + /* Tracing various job mixtures showed that this conditional + * only passes ~35% of the time for most worse case situations, + * therefore we avoid all of this gross overhead ~65% of the time. + */ + if ((entry != head) && (entry->vaddr < end)) { + int octx = sun4c_get_context(); + sun4c_set_context(new_ctx); + + /* At this point, always, (start >= entry->vaddr) and + * (entry->vaddr < end), once the latter condition + * ceases to hold, or we hit the end of the list, we + * exit the loop. The ordering of all user allocated + * segmaps makes this all work out so beautifully. + */ + do { + struct sun4c_mmu_entry *next = entry->next; + unsigned long realend; + + /* "realstart" is always >= entry->vaddr */ + realend = entry->vaddr + SUN4C_REAL_PGDIR_SIZE; + if (end < realend) + realend = end; + if ((realend - entry->vaddr) <= (PAGE_SIZE << 3)) { + unsigned long page = entry->vaddr; + while (page < realend) { + sun4c_flush_page(page); + page += PAGE_SIZE; + } + } else { + sun4c_flush_segment(entry->vaddr); + sun4c_user_unmap(entry); + free_user_entry(new_ctx, entry); + } + entry = next; + } while ((entry != head) && (entry->vaddr < end)); + sun4c_set_context(octx); + } + local_irq_restore(flags); + } +} + +static void sun4c_flush_cache_page(struct vm_area_struct *vma, unsigned long page) +{ + struct mm_struct *mm = vma->vm_mm; + int new_ctx = mm->context; + + /* Sun4c has no separate I/D caches so cannot optimize for non + * text page flushes. + */ + if (new_ctx != NO_CONTEXT) { + int octx = sun4c_get_context(); + unsigned long flags; + + flush_user_windows(); + local_irq_save(flags); + sun4c_set_context(new_ctx); + sun4c_flush_page(page); + sun4c_set_context(octx); + local_irq_restore(flags); + } +} + +static void sun4c_flush_page_to_ram(unsigned long page) +{ + unsigned long flags; + + local_irq_save(flags); + sun4c_flush_page(page); + local_irq_restore(flags); +} + +/* Sun4c cache is unified, both instructions and data live there, so + * no need to flush the on-stack instructions for new signal handlers. + */ +static void sun4c_flush_sig_insns(struct mm_struct *mm, unsigned long insn_addr) +{ +} + +/* TLB flushing on the sun4c. These routines count on the cache + * flushing code to flush the user register windows so that we need + * not do so when we get here. + */ + +static void sun4c_flush_tlb_all(void) +{ + struct sun4c_mmu_entry *this_entry, *next_entry; + unsigned long flags; + int savectx, ctx; + + local_irq_save(flags); + this_entry = sun4c_kernel_ring.ringhd.next; + savectx = sun4c_get_context(); + flush_user_windows(); + while (sun4c_kernel_ring.num_entries) { + next_entry = this_entry->next; + sun4c_flush_segment(this_entry->vaddr); + for (ctx = 0; ctx < num_contexts; ctx++) { + sun4c_set_context(ctx); + sun4c_put_segmap(this_entry->vaddr, invalid_segment); + } + free_kernel_entry(this_entry, &sun4c_kernel_ring); + this_entry = next_entry; + } + sun4c_set_context(savectx); + local_irq_restore(flags); +} + +static void sun4c_flush_tlb_mm(struct mm_struct *mm) +{ + int new_ctx = mm->context; + + if (new_ctx != NO_CONTEXT) { + struct sun4c_mmu_entry *head = &sun4c_context_ring[new_ctx].ringhd; + unsigned long flags; + + local_irq_save(flags); + if (head->next != head) { + struct sun4c_mmu_entry *entry = head->next; + int savectx = sun4c_get_context(); + + sun4c_set_context(new_ctx); + sun4c_flush_context(); + do { + struct sun4c_mmu_entry *next = entry->next; + + sun4c_user_unmap(entry); + free_user_entry(new_ctx, entry); + + entry = next; + } while (entry != head); + sun4c_set_context(savectx); + } + local_irq_restore(flags); + } +} + +static void sun4c_flush_tlb_range(struct vm_area_struct *vma, unsigned long start, unsigned long end) +{ + struct mm_struct *mm = vma->vm_mm; + int new_ctx = mm->context; + + if (new_ctx != NO_CONTEXT) { + struct sun4c_mmu_entry *head = &sun4c_context_ring[new_ctx].ringhd; + struct sun4c_mmu_entry *entry; + unsigned long flags; + + local_irq_save(flags); + /* See commentary in sun4c_flush_cache_range(). */ + for (entry = head->next; + (entry != head) && ((entry->vaddr+SUN4C_REAL_PGDIR_SIZE) < start); + entry = entry->next) + ; + + if ((entry != head) && (entry->vaddr < end)) { + int octx = sun4c_get_context(); + + sun4c_set_context(new_ctx); + do { + struct sun4c_mmu_entry *next = entry->next; + + sun4c_flush_segment(entry->vaddr); + sun4c_user_unmap(entry); + free_user_entry(new_ctx, entry); + + entry = next; + } while ((entry != head) && (entry->vaddr < end)); + sun4c_set_context(octx); + } + local_irq_restore(flags); + } +} + +static void sun4c_flush_tlb_page(struct vm_area_struct *vma, unsigned long page) +{ + struct mm_struct *mm = vma->vm_mm; + int new_ctx = mm->context; + + if (new_ctx != NO_CONTEXT) { + int savectx = sun4c_get_context(); + unsigned long flags; + + local_irq_save(flags); + sun4c_set_context(new_ctx); + page &= PAGE_MASK; + sun4c_flush_page(page); + sun4c_put_pte(page, 0); + sun4c_set_context(savectx); + local_irq_restore(flags); + } +} + +static inline void sun4c_mapioaddr(unsigned long physaddr, unsigned long virt_addr) +{ + unsigned long page_entry; + + page_entry = ((physaddr >> PAGE_SHIFT) & SUN4C_PFN_MASK); + page_entry |= ((pg_iobits | _SUN4C_PAGE_PRIV) & ~(_SUN4C_PAGE_PRESENT)); + sun4c_put_pte(virt_addr, page_entry); +} + +static void sun4c_mapiorange(unsigned int bus, unsigned long xpa, + unsigned long xva, unsigned int len) +{ + while (len != 0) { + len -= PAGE_SIZE; + sun4c_mapioaddr(xpa, xva); + xva += PAGE_SIZE; + xpa += PAGE_SIZE; + } +} + +static void sun4c_unmapiorange(unsigned long virt_addr, unsigned int len) +{ + while (len != 0) { + len -= PAGE_SIZE; + sun4c_put_pte(virt_addr, 0); + virt_addr += PAGE_SIZE; + } +} + +static void sun4c_alloc_context(struct mm_struct *old_mm, struct mm_struct *mm) +{ + struct ctx_list *ctxp; + + ctxp = ctx_free.next; + if (ctxp != &ctx_free) { + remove_from_ctx_list(ctxp); + add_to_used_ctxlist(ctxp); + mm->context = ctxp->ctx_number; + ctxp->ctx_mm = mm; + return; + } + ctxp = ctx_used.next; + if (ctxp->ctx_mm == old_mm) + ctxp = ctxp->next; + remove_from_ctx_list(ctxp); + add_to_used_ctxlist(ctxp); + ctxp->ctx_mm->context = NO_CONTEXT; + ctxp->ctx_mm = mm; + mm->context = ctxp->ctx_number; + sun4c_demap_context(&sun4c_context_ring[ctxp->ctx_number], + ctxp->ctx_number); +} + +/* Switch the current MM context. */ +static void sun4c_switch_mm(struct mm_struct *old_mm, struct mm_struct *mm, struct task_struct *tsk, int cpu) +{ + struct ctx_list *ctx; + int dirty = 0; + + if (mm->context == NO_CONTEXT) { + dirty = 1; + sun4c_alloc_context(old_mm, mm); + } else { + /* Update the LRU ring of contexts. */ + ctx = ctx_list_pool + mm->context; + remove_from_ctx_list(ctx); + add_to_used_ctxlist(ctx); + } + if (dirty || old_mm != mm) + sun4c_set_context(mm->context); +} + +static void sun4c_destroy_context(struct mm_struct *mm) +{ + struct ctx_list *ctx_old; + + if (mm->context != NO_CONTEXT) { + sun4c_demap_context(&sun4c_context_ring[mm->context], mm->context); + ctx_old = ctx_list_pool + mm->context; + remove_from_ctx_list(ctx_old); + add_to_free_ctxlist(ctx_old); + mm->context = NO_CONTEXT; + } +} + +static void sun4c_mmu_info(struct seq_file *m) +{ + int used_user_entries, i; + + used_user_entries = 0; + for (i = 0; i < num_contexts; i++) + used_user_entries += sun4c_context_ring[i].num_entries; + + seq_printf(m, + "vacsize\t\t: %d bytes\n" + "vachwflush\t: %s\n" + "vaclinesize\t: %d bytes\n" + "mmuctxs\t\t: %d\n" + "mmupsegs\t: %d\n" + "kernelpsegs\t: %d\n" + "kfreepsegs\t: %d\n" + "usedpsegs\t: %d\n" + "ufreepsegs\t: %d\n" + "user_taken\t: %d\n" + "max_taken\t: %d\n", + sun4c_vacinfo.num_bytes, + (sun4c_vacinfo.do_hwflushes ? "yes" : "no"), + sun4c_vacinfo.linesize, + num_contexts, + (invalid_segment + 1), + sun4c_kernel_ring.num_entries, + sun4c_kfree_ring.num_entries, + used_user_entries, + sun4c_ufree_ring.num_entries, + sun4c_user_taken_entries, + max_user_taken_entries); +} + +/* Nothing below here should touch the mmu hardware nor the mmu_entry + * data structures. + */ + +/* First the functions which the mid-level code uses to directly + * manipulate the software page tables. Some defines since we are + * emulating the i386 page directory layout. + */ +#define PGD_PRESENT 0x001 +#define PGD_RW 0x002 +#define PGD_USER 0x004 +#define PGD_ACCESSED 0x020 +#define PGD_DIRTY 0x040 +#define PGD_TABLE (PGD_PRESENT | PGD_RW | PGD_USER | PGD_ACCESSED | PGD_DIRTY) + +static void sun4c_set_pte(pte_t *ptep, pte_t pte) +{ + *ptep = pte; +} + +static void sun4c_pgd_set(pgd_t * pgdp, pmd_t * pmdp) +{ +} + +static void sun4c_pmd_set(pmd_t * pmdp, pte_t * ptep) +{ + pmdp->pmdv[0] = PGD_TABLE | (unsigned long) ptep; +} + +static void sun4c_pmd_populate(pmd_t * pmdp, struct page * ptep) +{ + if (page_address(ptep) == NULL) BUG(); /* No highmem on sun4c */ + pmdp->pmdv[0] = PGD_TABLE | (unsigned long) page_address(ptep); +} + +static int sun4c_pte_present(pte_t pte) +{ + return ((pte_val(pte) & (_SUN4C_PAGE_PRESENT | _SUN4C_PAGE_PRIV)) != 0); +} +static void sun4c_pte_clear(pte_t *ptep) { *ptep = __pte(0); } + +static int sun4c_pte_read(pte_t pte) +{ + return (pte_val(pte) & _SUN4C_PAGE_READ); +} + +static int sun4c_pmd_bad(pmd_t pmd) +{ + return (((pmd_val(pmd) & ~PAGE_MASK) != PGD_TABLE) || + (!virt_addr_valid(pmd_val(pmd)))); +} + +static int sun4c_pmd_present(pmd_t pmd) +{ + return ((pmd_val(pmd) & PGD_PRESENT) != 0); +} + +#if 0 /* if PMD takes one word */ +static void sun4c_pmd_clear(pmd_t *pmdp) { *pmdp = __pmd(0); } +#else /* if pmd_t is a longish aggregate */ +static void sun4c_pmd_clear(pmd_t *pmdp) { + memset((void *)pmdp, 0, sizeof(pmd_t)); +} +#endif + +static int sun4c_pgd_none(pgd_t pgd) { return 0; } +static int sun4c_pgd_bad(pgd_t pgd) { return 0; } +static int sun4c_pgd_present(pgd_t pgd) { return 1; } +static void sun4c_pgd_clear(pgd_t * pgdp) { } + +/* + * The following only work if pte_present() is true. + * Undefined behaviour if not.. + */ +static pte_t sun4c_pte_mkwrite(pte_t pte) +{ + pte = __pte(pte_val(pte) | _SUN4C_PAGE_WRITE); + if (pte_val(pte) & _SUN4C_PAGE_MODIFIED) + pte = __pte(pte_val(pte) | _SUN4C_PAGE_SILENT_WRITE); + return pte; +} + +static pte_t sun4c_pte_mkdirty(pte_t pte) +{ + pte = __pte(pte_val(pte) | _SUN4C_PAGE_MODIFIED); + if (pte_val(pte) & _SUN4C_PAGE_WRITE) + pte = __pte(pte_val(pte) | _SUN4C_PAGE_SILENT_WRITE); + return pte; +} + +static pte_t sun4c_pte_mkyoung(pte_t pte) +{ + pte = __pte(pte_val(pte) | _SUN4C_PAGE_ACCESSED); + if (pte_val(pte) & _SUN4C_PAGE_READ) + pte = __pte(pte_val(pte) | _SUN4C_PAGE_SILENT_READ); + return pte; +} + +/* + * Conversion functions: convert a page and protection to a page entry, + * and a page entry and page directory to the page they refer to. + */ +static pte_t sun4c_mk_pte(struct page *page, pgprot_t pgprot) +{ + return __pte(page_to_pfn(page) | pgprot_val(pgprot)); +} + +static pte_t sun4c_mk_pte_phys(unsigned long phys_page, pgprot_t pgprot) +{ + return __pte((phys_page >> PAGE_SHIFT) | pgprot_val(pgprot)); +} + +static pte_t sun4c_mk_pte_io(unsigned long page, pgprot_t pgprot, int space) +{ + return __pte(((page - PAGE_OFFSET) >> PAGE_SHIFT) | pgprot_val(pgprot)); +} + +static unsigned long sun4c_pte_pfn(pte_t pte) +{ + return pte_val(pte) & SUN4C_PFN_MASK; +} + +static pte_t sun4c_pgoff_to_pte(unsigned long pgoff) +{ + return __pte(pgoff | _SUN4C_PAGE_FILE); +} + +static unsigned long sun4c_pte_to_pgoff(pte_t pte) +{ + return pte_val(pte) & ((1UL << PTE_FILE_MAX_BITS) - 1); +} + + +static __inline__ unsigned long sun4c_pmd_page_v(pmd_t pmd) +{ + return (pmd_val(pmd) & PAGE_MASK); +} + +static struct page *sun4c_pmd_page(pmd_t pmd) +{ + return virt_to_page(sun4c_pmd_page_v(pmd)); +} + +static unsigned long sun4c_pgd_page(pgd_t pgd) { return 0; } + +/* to find an entry in a page-table-directory */ +static inline pgd_t *sun4c_pgd_offset(struct mm_struct * mm, unsigned long address) +{ + return mm->pgd + (address >> SUN4C_PGDIR_SHIFT); +} + +/* Find an entry in the second-level page table.. */ +static pmd_t *sun4c_pmd_offset(pgd_t * dir, unsigned long address) +{ + return (pmd_t *) dir; +} + +/* Find an entry in the third-level page table.. */ +pte_t *sun4c_pte_offset_kernel(pmd_t * dir, unsigned long address) +{ + return (pte_t *) sun4c_pmd_page_v(*dir) + + ((address >> PAGE_SHIFT) & (SUN4C_PTRS_PER_PTE - 1)); +} + +static unsigned long sun4c_swp_type(swp_entry_t entry) +{ + return (entry.val & SUN4C_SWP_TYPE_MASK); +} + +static unsigned long sun4c_swp_offset(swp_entry_t entry) +{ + return (entry.val >> SUN4C_SWP_OFF_SHIFT) & SUN4C_SWP_OFF_MASK; +} + +static swp_entry_t sun4c_swp_entry(unsigned long type, unsigned long offset) +{ + return (swp_entry_t) { + (offset & SUN4C_SWP_OFF_MASK) << SUN4C_SWP_OFF_SHIFT + | (type & SUN4C_SWP_TYPE_MASK) }; +} + +static void sun4c_free_pte_slow(pte_t *pte) +{ + free_page((unsigned long)pte); +} + +static void sun4c_free_pgd_slow(pgd_t *pgd) +{ + free_page((unsigned long)pgd); +} + +static pgd_t *sun4c_get_pgd_fast(void) +{ + unsigned long *ret; + + if ((ret = pgd_quicklist) != NULL) { + pgd_quicklist = (unsigned long *)(*ret); + ret[0] = ret[1]; + pgtable_cache_size--; + } else { + pgd_t *init; + + ret = (unsigned long *)__get_free_page(GFP_KERNEL); + memset (ret, 0, (KERNBASE / SUN4C_PGDIR_SIZE) * sizeof(pgd_t)); + init = sun4c_pgd_offset(&init_mm, 0); + memcpy (((pgd_t *)ret) + USER_PTRS_PER_PGD, init + USER_PTRS_PER_PGD, + (PTRS_PER_PGD - USER_PTRS_PER_PGD) * sizeof(pgd_t)); + } + return (pgd_t *)ret; +} + +static void sun4c_free_pgd_fast(pgd_t *pgd) +{ + *(unsigned long *)pgd = (unsigned long) pgd_quicklist; + pgd_quicklist = (unsigned long *) pgd; + pgtable_cache_size++; +} + + +static __inline__ pte_t * +sun4c_pte_alloc_one_fast(struct mm_struct *mm, unsigned long address) +{ + unsigned long *ret; + + if ((ret = (unsigned long *)pte_quicklist) != NULL) { + pte_quicklist = (unsigned long *)(*ret); + ret[0] = ret[1]; + pgtable_cache_size--; + } + return (pte_t *)ret; +} + +static pte_t *sun4c_pte_alloc_one_kernel(struct mm_struct *mm, unsigned long address) +{ + pte_t *pte; + + if ((pte = sun4c_pte_alloc_one_fast(mm, address)) != NULL) + return pte; + + pte = (pte_t *)__get_free_page(GFP_KERNEL|__GFP_REPEAT); + if (pte) + memset(pte, 0, PAGE_SIZE); + return pte; +} + +static struct page *sun4c_pte_alloc_one(struct mm_struct *mm, unsigned long address) +{ + pte_t *pte = sun4c_pte_alloc_one_kernel(mm, address); + if (pte == NULL) + return NULL; + return virt_to_page(pte); +} + +static __inline__ void sun4c_free_pte_fast(pte_t *pte) +{ + *(unsigned long *)pte = (unsigned long) pte_quicklist; + pte_quicklist = (unsigned long *) pte; + pgtable_cache_size++; +} + +static void sun4c_pte_free(struct page *pte) +{ + sun4c_free_pte_fast(page_address(pte)); +} + +/* + * allocating and freeing a pmd is trivial: the 1-entry pmd is + * inside the pgd, so has no extra memory associated with it. + */ +static pmd_t *sun4c_pmd_alloc_one(struct mm_struct *mm, unsigned long address) +{ + BUG(); + return NULL; +} + +static void sun4c_free_pmd_fast(pmd_t * pmd) { } + +static void sun4c_check_pgt_cache(int low, int high) +{ + if (pgtable_cache_size > high) { + do { + if (pgd_quicklist) + sun4c_free_pgd_slow(sun4c_get_pgd_fast()); + if (pte_quicklist) + sun4c_free_pte_slow(sun4c_pte_alloc_one_fast(NULL, 0)); + } while (pgtable_cache_size > low); + } +} + +/* An experiment, turn off by default for now... -DaveM */ +#define SUN4C_PRELOAD_PSEG + +void sun4c_update_mmu_cache(struct vm_area_struct *vma, unsigned long address, pte_t pte) +{ + unsigned long flags; + int pseg; + + local_irq_save(flags); + address &= PAGE_MASK; + if ((pseg = sun4c_get_segmap(address)) == invalid_segment) { + struct sun4c_mmu_entry *entry = sun4c_user_strategy(); + struct mm_struct *mm = vma->vm_mm; + unsigned long start, end; + + entry->vaddr = start = (address & SUN4C_REAL_PGDIR_MASK); + entry->ctx = mm->context; + add_ring_ordered(sun4c_context_ring + mm->context, entry); + sun4c_put_segmap(entry->vaddr, entry->pseg); + end = start + SUN4C_REAL_PGDIR_SIZE; + while (start < end) { +#ifdef SUN4C_PRELOAD_PSEG + pgd_t *pgdp = sun4c_pgd_offset(mm, start); + pte_t *ptep; + + if (!pgdp) + goto no_mapping; + ptep = sun4c_pte_offset_kernel((pmd_t *) pgdp, start); + if (!ptep || !(pte_val(*ptep) & _SUN4C_PAGE_PRESENT)) + goto no_mapping; + sun4c_put_pte(start, pte_val(*ptep)); + goto next; + + no_mapping: +#endif + sun4c_put_pte(start, 0); +#ifdef SUN4C_PRELOAD_PSEG + next: +#endif + start += PAGE_SIZE; + } +#ifndef SUN4C_PRELOAD_PSEG + sun4c_put_pte(address, pte_val(pte)); +#endif + local_irq_restore(flags); + return; + } else { + struct sun4c_mmu_entry *entry = &mmu_entry_pool[pseg]; + + remove_lru(entry); + add_lru(entry); + } + + sun4c_put_pte(address, pte_val(pte)); + local_irq_restore(flags); +} + +extern void sparc_context_init(int); +extern unsigned long end; +extern unsigned long bootmem_init(unsigned long *pages_avail); +extern unsigned long last_valid_pfn; + +void __init sun4c_paging_init(void) +{ + int i, cnt; + unsigned long kernel_end, vaddr; + extern struct resource sparc_iomap; + unsigned long end_pfn, pages_avail; + + kernel_end = (unsigned long) &end; + kernel_end += (SUN4C_REAL_PGDIR_SIZE * 4); + kernel_end = SUN4C_REAL_PGDIR_ALIGN(kernel_end); + + pages_avail = 0; + last_valid_pfn = bootmem_init(&pages_avail); + end_pfn = last_valid_pfn; + + sun4c_probe_mmu(); + invalid_segment = (num_segmaps - 1); + sun4c_init_mmu_entry_pool(); + sun4c_init_rings(); + sun4c_init_map_kernelprom(kernel_end); + sun4c_init_clean_mmu(kernel_end); + sun4c_init_fill_kernel_ring(SUN4C_KERNEL_BUCKETS); + sun4c_init_lock_area(sparc_iomap.start, IOBASE_END); + sun4c_init_lock_area(DVMA_VADDR, DVMA_END); + sun4c_init_lock_areas(); + sun4c_init_fill_user_ring(); + + sun4c_set_context(0); + memset(swapper_pg_dir, 0, PAGE_SIZE); + memset(pg0, 0, PAGE_SIZE); + memset(pg1, 0, PAGE_SIZE); + memset(pg2, 0, PAGE_SIZE); + memset(pg3, 0, PAGE_SIZE); + + /* Save work later. */ + vaddr = VMALLOC_START; + swapper_pg_dir[vaddr>>SUN4C_PGDIR_SHIFT] = __pgd(PGD_TABLE | (unsigned long) pg0); + vaddr += SUN4C_PGDIR_SIZE; + swapper_pg_dir[vaddr>>SUN4C_PGDIR_SHIFT] = __pgd(PGD_TABLE | (unsigned long) pg1); + vaddr += SUN4C_PGDIR_SIZE; + swapper_pg_dir[vaddr>>SUN4C_PGDIR_SHIFT] = __pgd(PGD_TABLE | (unsigned long) pg2); + vaddr += SUN4C_PGDIR_SIZE; + swapper_pg_dir[vaddr>>SUN4C_PGDIR_SHIFT] = __pgd(PGD_TABLE | (unsigned long) pg3); + sun4c_init_ss2_cache_bug(); + sparc_context_init(num_contexts); + + { + unsigned long zones_size[MAX_NR_ZONES]; + unsigned long zholes_size[MAX_NR_ZONES]; + unsigned long npages; + int znum; + + for (znum = 0; znum < MAX_NR_ZONES; znum++) + zones_size[znum] = zholes_size[znum] = 0; + + npages = max_low_pfn - pfn_base; + + zones_size[ZONE_DMA] = npages; + zholes_size[ZONE_DMA] = npages - pages_avail; + + npages = highend_pfn - max_low_pfn; + zones_size[ZONE_HIGHMEM] = npages; + zholes_size[ZONE_HIGHMEM] = npages - calc_highpages(); + + free_area_init_node(0, &contig_page_data, zones_size, + pfn_base, zholes_size); + } + + cnt = 0; + for (i = 0; i < num_segmaps; i++) + if (mmu_entry_pool[i].locked) + cnt++; + + max_user_taken_entries = num_segmaps - cnt - 40 - 1; + + printk("SUN4C: %d mmu entries for the kernel\n", cnt); +} + +/* Load up routines and constants for sun4c mmu */ +void __init ld_mmu_sun4c(void) +{ + extern void ___xchg32_sun4c(void); + + printk("Loading sun4c MMU routines\n"); + + /* First the constants */ + BTFIXUPSET_SIMM13(pgdir_shift, SUN4C_PGDIR_SHIFT); + BTFIXUPSET_SETHI(pgdir_size, SUN4C_PGDIR_SIZE); + BTFIXUPSET_SETHI(pgdir_mask, SUN4C_PGDIR_MASK); + + BTFIXUPSET_SIMM13(ptrs_per_pmd, SUN4C_PTRS_PER_PMD); + BTFIXUPSET_SIMM13(ptrs_per_pgd, SUN4C_PTRS_PER_PGD); + BTFIXUPSET_SIMM13(user_ptrs_per_pgd, KERNBASE / SUN4C_PGDIR_SIZE); + + BTFIXUPSET_INT(page_none, pgprot_val(SUN4C_PAGE_NONE)); + BTFIXUPSET_INT(page_shared, pgprot_val(SUN4C_PAGE_SHARED)); + BTFIXUPSET_INT(page_copy, pgprot_val(SUN4C_PAGE_COPY)); + BTFIXUPSET_INT(page_readonly, pgprot_val(SUN4C_PAGE_READONLY)); + BTFIXUPSET_INT(page_kernel, pgprot_val(SUN4C_PAGE_KERNEL)); + page_kernel = pgprot_val(SUN4C_PAGE_KERNEL); + pg_iobits = _SUN4C_PAGE_PRESENT | _SUN4C_READABLE | _SUN4C_WRITEABLE | + _SUN4C_PAGE_IO | _SUN4C_PAGE_NOCACHE; + + /* Functions */ + BTFIXUPSET_CALL(___xchg32, ___xchg32_sun4c, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(do_check_pgt_cache, sun4c_check_pgt_cache, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(flush_cache_all, sun4c_flush_cache_all, BTFIXUPCALL_NORM); + + if (sun4c_vacinfo.do_hwflushes) { + BTFIXUPSET_CALL(sun4c_flush_page, sun4c_flush_page_hw, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(sun4c_flush_segment, sun4c_flush_segment_hw, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(sun4c_flush_context, sun4c_flush_context_hw, BTFIXUPCALL_NORM); + } else { + BTFIXUPSET_CALL(sun4c_flush_page, sun4c_flush_page_sw, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(sun4c_flush_segment, sun4c_flush_segment_sw, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(sun4c_flush_context, sun4c_flush_context_sw, BTFIXUPCALL_NORM); + } + + BTFIXUPSET_CALL(flush_tlb_mm, sun4c_flush_tlb_mm, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_mm, sun4c_flush_cache_mm, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(destroy_context, sun4c_destroy_context, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(switch_mm, sun4c_switch_mm, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_page, sun4c_flush_cache_page, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_page, sun4c_flush_tlb_page, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_range, sun4c_flush_tlb_range, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_range, sun4c_flush_cache_range, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(__flush_page_to_ram, sun4c_flush_page_to_ram, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_all, sun4c_flush_tlb_all, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(flush_sig_insns, sun4c_flush_sig_insns, BTFIXUPCALL_NOP); + + BTFIXUPSET_CALL(set_pte, sun4c_set_pte, BTFIXUPCALL_STO1O0); + + /* The 2.4.18 code does not set this on sun4c, how does it work? XXX */ + /* BTFIXUPSET_SETHI(none_mask, 0x00000000); */ /* Defaults to zero? */ + + BTFIXUPSET_CALL(pte_pfn, sun4c_pte_pfn, BTFIXUPCALL_NORM); +#if 0 /* PAGE_SHIFT <= 12 */ /* Eek. Investigate. XXX */ + BTFIXUPSET_CALL(pmd_page, sun4c_pmd_page, BTFIXUPCALL_ANDNINT(PAGE_SIZE - 1)); +#else + BTFIXUPSET_CALL(pmd_page, sun4c_pmd_page, BTFIXUPCALL_NORM); +#endif + BTFIXUPSET_CALL(pmd_set, sun4c_pmd_set, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pmd_populate, sun4c_pmd_populate, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(pte_present, sun4c_pte_present, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pte_clear, sun4c_pte_clear, BTFIXUPCALL_STG0O0); + BTFIXUPSET_CALL(pte_read, sun4c_pte_read, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(pmd_bad, sun4c_pmd_bad, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pmd_present, sun4c_pmd_present, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pmd_clear, sun4c_pmd_clear, BTFIXUPCALL_STG0O0); + + BTFIXUPSET_CALL(pgd_none, sun4c_pgd_none, BTFIXUPCALL_RETINT(0)); + BTFIXUPSET_CALL(pgd_bad, sun4c_pgd_bad, BTFIXUPCALL_RETINT(0)); + BTFIXUPSET_CALL(pgd_present, sun4c_pgd_present, BTFIXUPCALL_RETINT(1)); + BTFIXUPSET_CALL(pgd_clear, sun4c_pgd_clear, BTFIXUPCALL_NOP); + + BTFIXUPSET_CALL(mk_pte, sun4c_mk_pte, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(mk_pte_phys, sun4c_mk_pte_phys, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(mk_pte_io, sun4c_mk_pte_io, BTFIXUPCALL_NORM); + + BTFIXUPSET_INT(pte_modify_mask, _SUN4C_PAGE_CHG_MASK); + BTFIXUPSET_CALL(pmd_offset, sun4c_pmd_offset, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pte_offset_kernel, sun4c_pte_offset_kernel, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(free_pte_fast, sun4c_free_pte_fast, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pte_free, sun4c_pte_free, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pte_alloc_one_kernel, sun4c_pte_alloc_one_kernel, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pte_alloc_one, sun4c_pte_alloc_one, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(free_pmd_fast, sun4c_free_pmd_fast, BTFIXUPCALL_NOP); + BTFIXUPSET_CALL(pmd_alloc_one, sun4c_pmd_alloc_one, BTFIXUPCALL_RETO0); + BTFIXUPSET_CALL(free_pgd_fast, sun4c_free_pgd_fast, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(get_pgd_fast, sun4c_get_pgd_fast, BTFIXUPCALL_NORM); + + BTFIXUPSET_HALF(pte_writei, _SUN4C_PAGE_WRITE); + BTFIXUPSET_HALF(pte_dirtyi, _SUN4C_PAGE_MODIFIED); + BTFIXUPSET_HALF(pte_youngi, _SUN4C_PAGE_ACCESSED); + BTFIXUPSET_HALF(pte_filei, _SUN4C_PAGE_FILE); + BTFIXUPSET_HALF(pte_wrprotecti, _SUN4C_PAGE_WRITE|_SUN4C_PAGE_SILENT_WRITE); + BTFIXUPSET_HALF(pte_mkcleani, _SUN4C_PAGE_MODIFIED|_SUN4C_PAGE_SILENT_WRITE); + BTFIXUPSET_HALF(pte_mkoldi, _SUN4C_PAGE_ACCESSED|_SUN4C_PAGE_SILENT_READ); + BTFIXUPSET_CALL(pte_mkwrite, sun4c_pte_mkwrite, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pte_mkdirty, sun4c_pte_mkdirty, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pte_mkyoung, sun4c_pte_mkyoung, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(update_mmu_cache, sun4c_update_mmu_cache, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(pte_to_pgoff, sun4c_pte_to_pgoff, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pgoff_to_pte, sun4c_pgoff_to_pte, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(mmu_lockarea, sun4c_lockarea, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(mmu_unlockarea, sun4c_unlockarea, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(mmu_get_scsi_one, sun4c_get_scsi_one, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(mmu_get_scsi_sgl, sun4c_get_scsi_sgl, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(mmu_release_scsi_one, sun4c_release_scsi_one, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(mmu_release_scsi_sgl, sun4c_release_scsi_sgl, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(mmu_map_dma_area, sun4c_map_dma_area, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(mmu_unmap_dma_area, sun4c_unmap_dma_area, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(mmu_translate_dvma, sun4c_translate_dvma, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(sparc_mapiorange, sun4c_mapiorange, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(sparc_unmapiorange, sun4c_unmapiorange, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(__swp_type, sun4c_swp_type, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(__swp_offset, sun4c_swp_offset, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(__swp_entry, sun4c_swp_entry, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(alloc_thread_info, sun4c_alloc_thread_info, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(free_thread_info, sun4c_free_thread_info, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(mmu_info, sun4c_mmu_info, BTFIXUPCALL_NORM); + + /* These should _never_ get called with two level tables. */ + BTFIXUPSET_CALL(pgd_set, sun4c_pgd_set, BTFIXUPCALL_NOP); + BTFIXUPSET_CALL(pgd_page, sun4c_pgd_page, BTFIXUPCALL_RETO0); +} diff --git a/arch/sparc/mm/swift.S b/arch/sparc/mm/swift.S new file mode 100644 index 000000000000..2dcaa5ac1a38 --- /dev/null +++ b/arch/sparc/mm/swift.S @@ -0,0 +1,256 @@ +/* $Id: swift.S,v 1.9 2002/01/08 11:11:59 davem Exp $ + * swift.S: MicroSparc-II mmu/cache operations. + * + * Copyright (C) 1999 David S. Miller (davem@redhat.com) + */ + +#include <linux/config.h> +#include <asm/psr.h> +#include <asm/asi.h> +#include <asm/page.h> +#include <asm/pgtsrmmu.h> +#include <asm/asm_offsets.h> + + .text + .align 4 + +#if 1 /* XXX screw this, I can't get the VAC flushes working + * XXX reliably... -DaveM + */ + .globl swift_flush_cache_all, swift_flush_cache_mm + .globl swift_flush_cache_range, swift_flush_cache_page + .globl swift_flush_page_for_dma + .globl swift_flush_page_to_ram + +swift_flush_cache_all: +swift_flush_cache_mm: +swift_flush_cache_range: +swift_flush_cache_page: +swift_flush_page_for_dma: +swift_flush_page_to_ram: + sethi %hi(0x2000), %o0 +1: subcc %o0, 0x10, %o0 + add %o0, %o0, %o1 + sta %g0, [%o0] ASI_M_DATAC_TAG + bne 1b + sta %g0, [%o1] ASI_M_TXTC_TAG + retl + nop +#else + + .globl swift_flush_cache_all +swift_flush_cache_all: + WINDOW_FLUSH(%g4, %g5) + + /* Just clear out all the tags. */ + sethi %hi(16 * 1024), %o0 +1: subcc %o0, 16, %o0 + sta %g0, [%o0] ASI_M_TXTC_TAG + bne 1b + sta %g0, [%o0] ASI_M_DATAC_TAG + retl + nop + + .globl swift_flush_cache_mm +swift_flush_cache_mm: + ld [%o0 + AOFF_mm_context], %g2 + cmp %g2, -1 + be swift_flush_cache_mm_out + WINDOW_FLUSH(%g4, %g5) + rd %psr, %g1 + andn %g1, PSR_ET, %g3 + wr %g3, 0x0, %psr + nop + nop + mov SRMMU_CTX_REG, %g7 + lda [%g7] ASI_M_MMUREGS, %g5 + sta %g2, [%g7] ASI_M_MMUREGS + +#if 1 + sethi %hi(0x2000), %o0 +1: subcc %o0, 0x10, %o0 + sta %g0, [%o0] ASI_M_FLUSH_CTX + bne 1b + nop +#else + clr %o0 + or %g0, 2048, %g7 + or %g0, 2048, %o1 + add %o1, 2048, %o2 + add %o2, 2048, %o3 + mov 16, %o4 + add %o4, 2048, %o5 + add %o5, 2048, %g2 + add %g2, 2048, %g3 +1: sta %g0, [%o0 ] ASI_M_FLUSH_CTX + sta %g0, [%o0 + %o1] ASI_M_FLUSH_CTX + sta %g0, [%o0 + %o2] ASI_M_FLUSH_CTX + sta %g0, [%o0 + %o3] ASI_M_FLUSH_CTX + sta %g0, [%o0 + %o4] ASI_M_FLUSH_CTX + sta %g0, [%o0 + %o5] ASI_M_FLUSH_CTX + sta %g0, [%o0 + %g2] ASI_M_FLUSH_CTX + sta %g0, [%o0 + %g3] ASI_M_FLUSH_CTX + subcc %g7, 32, %g7 + bne 1b + add %o0, 32, %o0 +#endif + + mov SRMMU_CTX_REG, %g7 + sta %g5, [%g7] ASI_M_MMUREGS + wr %g1, 0x0, %psr + nop + nop +swift_flush_cache_mm_out: + retl + nop + + .globl swift_flush_cache_range +swift_flush_cache_range: + ld [%o0 + 0x0], %o0 /* XXX vma->vm_mm, GROSS XXX */ + sub %o2, %o1, %o2 + sethi %hi(4096), %o3 + cmp %o2, %o3 + bgu swift_flush_cache_mm + nop + b 70f + nop + + .globl swift_flush_cache_page +swift_flush_cache_page: + ld [%o0 + 0x0], %o0 /* XXX vma->vm_mm, GROSS XXX */ +70: + ld [%o0 + AOFF_mm_context], %g2 + cmp %g2, -1 + be swift_flush_cache_page_out + WINDOW_FLUSH(%g4, %g5) + rd %psr, %g1 + andn %g1, PSR_ET, %g3 + wr %g3, 0x0, %psr + nop + nop + mov SRMMU_CTX_REG, %g7 + lda [%g7] ASI_M_MMUREGS, %g5 + sta %g2, [%g7] ASI_M_MMUREGS + + andn %o1, (PAGE_SIZE - 1), %o1 +#if 1 + sethi %hi(0x1000), %o0 +1: subcc %o0, 0x10, %o0 + sta %g0, [%o1 + %o0] ASI_M_FLUSH_PAGE + bne 1b + nop +#else + or %g0, 512, %g7 + or %g0, 512, %o0 + add %o0, 512, %o2 + add %o2, 512, %o3 + add %o3, 512, %o4 + add %o4, 512, %o5 + add %o5, 512, %g3 + add %g3, 512, %g4 +1: sta %g0, [%o1 ] ASI_M_FLUSH_PAGE + sta %g0, [%o1 + %o0] ASI_M_FLUSH_PAGE + sta %g0, [%o1 + %o2] ASI_M_FLUSH_PAGE + sta %g0, [%o1 + %o3] ASI_M_FLUSH_PAGE + sta %g0, [%o1 + %o4] ASI_M_FLUSH_PAGE + sta %g0, [%o1 + %o5] ASI_M_FLUSH_PAGE + sta %g0, [%o1 + %g3] ASI_M_FLUSH_PAGE + sta %g0, [%o1 + %g4] ASI_M_FLUSH_PAGE + subcc %g7, 16, %g7 + bne 1b + add %o1, 16, %o1 +#endif + + mov SRMMU_CTX_REG, %g7 + sta %g5, [%g7] ASI_M_MMUREGS + wr %g1, 0x0, %psr + nop + nop +swift_flush_cache_page_out: + retl + nop + + /* Swift is write-thru, however it is not + * I/O nor TLB-walk coherent. Also it has + * caches which are virtually indexed and tagged. + */ + .globl swift_flush_page_for_dma + .globl swift_flush_page_to_ram +swift_flush_page_for_dma: +swift_flush_page_to_ram: + andn %o0, (PAGE_SIZE - 1), %o1 +#if 1 + sethi %hi(0x1000), %o0 +1: subcc %o0, 0x10, %o0 + sta %g0, [%o1 + %o0] ASI_M_FLUSH_PAGE + bne 1b + nop +#else + or %g0, 512, %g7 + or %g0, 512, %o0 + add %o0, 512, %o2 + add %o2, 512, %o3 + add %o3, 512, %o4 + add %o4, 512, %o5 + add %o5, 512, %g3 + add %g3, 512, %g4 +1: sta %g0, [%o1 ] ASI_M_FLUSH_PAGE + sta %g0, [%o1 + %o0] ASI_M_FLUSH_PAGE + sta %g0, [%o1 + %o2] ASI_M_FLUSH_PAGE + sta %g0, [%o1 + %o3] ASI_M_FLUSH_PAGE + sta %g0, [%o1 + %o4] ASI_M_FLUSH_PAGE + sta %g0, [%o1 + %o5] ASI_M_FLUSH_PAGE + sta %g0, [%o1 + %g3] ASI_M_FLUSH_PAGE + sta %g0, [%o1 + %g4] ASI_M_FLUSH_PAGE + subcc %g7, 16, %g7 + bne 1b + add %o1, 16, %o1 +#endif + retl + nop +#endif + + .globl swift_flush_sig_insns +swift_flush_sig_insns: + flush %o1 + retl + flush %o1 + 4 + + .globl swift_flush_tlb_mm + .globl swift_flush_tlb_range + .globl swift_flush_tlb_all +swift_flush_tlb_range: + ld [%o0 + 0x00], %o0 /* XXX vma->vm_mm GROSS XXX */ +swift_flush_tlb_mm: + ld [%o0 + AOFF_mm_context], %g2 + cmp %g2, -1 + be swift_flush_tlb_all_out +swift_flush_tlb_all: + mov 0x400, %o1 + sta %g0, [%o1] ASI_M_FLUSH_PROBE +swift_flush_tlb_all_out: + retl + nop + + .globl swift_flush_tlb_page +swift_flush_tlb_page: + ld [%o0 + 0x00], %o0 /* XXX vma->vm_mm GROSS XXX */ + mov SRMMU_CTX_REG, %g1 + ld [%o0 + AOFF_mm_context], %o3 + andn %o1, (PAGE_SIZE - 1), %o1 + cmp %o3, -1 + be swift_flush_tlb_page_out + nop +#if 1 + mov 0x400, %o1 + sta %g0, [%o1] ASI_M_FLUSH_PROBE +#else + lda [%g1] ASI_M_MMUREGS, %g5 + sta %o3, [%g1] ASI_M_MMUREGS + sta %g0, [%o1] ASI_M_FLUSH_PAGE /* rem. virt. cache. prot. */ + sta %g0, [%o1] ASI_M_FLUSH_PROBE + sta %g5, [%g1] ASI_M_MMUREGS +#endif +swift_flush_tlb_page_out: + retl + nop diff --git a/arch/sparc/mm/tsunami.S b/arch/sparc/mm/tsunami.S new file mode 100644 index 000000000000..8acd1787fde2 --- /dev/null +++ b/arch/sparc/mm/tsunami.S @@ -0,0 +1,133 @@ +/* $Id: tsunami.S,v 1.7 2001/12/21 04:56:15 davem Exp $ + * tsunami.S: High speed MicroSparc-I mmu/cache operations. + * + * Copyright (C) 1997 David S. Miller (davem@caip.rutgers.edu) + */ + +#include <linux/config.h> +#include <asm/ptrace.h> +#include <asm/asm_offsets.h> +#include <asm/psr.h> +#include <asm/asi.h> +#include <asm/page.h> +#include <asm/pgtsrmmu.h> + + .text + .align 4 + + .globl tsunami_flush_cache_all, tsunami_flush_cache_mm + .globl tsunami_flush_cache_range, tsunami_flush_cache_page + .globl tsunami_flush_page_to_ram, tsunami_flush_page_for_dma + .globl tsunami_flush_sig_insns + .globl tsunami_flush_tlb_all, tsunami_flush_tlb_mm + .globl tsunami_flush_tlb_range, tsunami_flush_tlb_page + + /* Sliiick... */ +tsunami_flush_cache_page: +tsunami_flush_cache_range: + ld [%o0 + 0x0], %o0 /* XXX vma->vm_mm, GROSS XXX */ +tsunami_flush_cache_mm: + ld [%o0 + AOFF_mm_context], %g2 + cmp %g2, -1 + be tsunami_flush_cache_out +tsunami_flush_cache_all: + WINDOW_FLUSH(%g4, %g5) +tsunami_flush_page_for_dma: + sta %g0, [%g0] ASI_M_IC_FLCLEAR + sta %g0, [%g0] ASI_M_DC_FLCLEAR +tsunami_flush_cache_out: +tsunami_flush_page_to_ram: + retl + nop + +tsunami_flush_sig_insns: + flush %o1 + retl + flush %o1 + 4 + + /* More slick stuff... */ +tsunami_flush_tlb_range: + ld [%o0 + 0x00], %o0 /* XXX vma->vm_mm GROSS XXX */ +tsunami_flush_tlb_mm: + ld [%o0 + AOFF_mm_context], %g2 + cmp %g2, -1 + be tsunami_flush_tlb_out +tsunami_flush_tlb_all: + mov 0x400, %o1 + sta %g0, [%o1] ASI_M_FLUSH_PROBE + nop + nop + nop + nop + nop +tsunami_flush_tlb_out: + retl + nop + + /* This one can be done in a fine grained manner... */ +tsunami_flush_tlb_page: + ld [%o0 + 0x00], %o0 /* XXX vma->vm_mm GROSS XXX */ + mov SRMMU_CTX_REG, %g1 + ld [%o0 + AOFF_mm_context], %o3 + andn %o1, (PAGE_SIZE - 1), %o1 + cmp %o3, -1 + be tsunami_flush_tlb_page_out + lda [%g1] ASI_M_MMUREGS, %g5 + sta %o3, [%g1] ASI_M_MMUREGS + sta %g0, [%o1] ASI_M_FLUSH_PROBE + nop + nop + nop + nop + nop +tsunami_flush_tlb_page_out: + retl + sta %g5, [%g1] ASI_M_MMUREGS + +#define MIRROR_BLOCK(dst, src, offset, t0, t1, t2, t3) \ + ldd [src + offset + 0x18], t0; \ + std t0, [dst + offset + 0x18]; \ + ldd [src + offset + 0x10], t2; \ + std t2, [dst + offset + 0x10]; \ + ldd [src + offset + 0x08], t0; \ + std t0, [dst + offset + 0x08]; \ + ldd [src + offset + 0x00], t2; \ + std t2, [dst + offset + 0x00]; + + .globl tsunami_copy_1page +tsunami_copy_1page: +/* NOTE: This routine has to be shorter than 70insns --jj */ + or %g0, (PAGE_SIZE >> 8), %g1 +1: + MIRROR_BLOCK(%o0, %o1, 0x00, %o2, %o3, %o4, %o5) + MIRROR_BLOCK(%o0, %o1, 0x20, %o2, %o3, %o4, %o5) + MIRROR_BLOCK(%o0, %o1, 0x40, %o2, %o3, %o4, %o5) + MIRROR_BLOCK(%o0, %o1, 0x60, %o2, %o3, %o4, %o5) + MIRROR_BLOCK(%o0, %o1, 0x80, %o2, %o3, %o4, %o5) + MIRROR_BLOCK(%o0, %o1, 0xa0, %o2, %o3, %o4, %o5) + MIRROR_BLOCK(%o0, %o1, 0xc0, %o2, %o3, %o4, %o5) + MIRROR_BLOCK(%o0, %o1, 0xe0, %o2, %o3, %o4, %o5) + subcc %g1, 1, %g1 + add %o0, 0x100, %o0 + bne 1b + add %o1, 0x100, %o1 + + .globl tsunami_setup_blockops +tsunami_setup_blockops: + sethi %hi(__copy_1page), %o0 + or %o0, %lo(__copy_1page), %o0 + sethi %hi(tsunami_copy_1page), %o1 + or %o1, %lo(tsunami_copy_1page), %o1 + sethi %hi(tsunami_setup_blockops), %o2 + or %o2, %lo(tsunami_setup_blockops), %o2 + ld [%o1], %o4 +1: add %o1, 4, %o1 + st %o4, [%o0] + add %o0, 4, %o0 + cmp %o1, %o2 + bne 1b + ld [%o1], %o4 + sta %g0, [%g0] ASI_M_IC_FLCLEAR + sta %g0, [%g0] ASI_M_DC_FLCLEAR + retl + nop diff --git a/arch/sparc/mm/viking.S b/arch/sparc/mm/viking.S new file mode 100644 index 000000000000..f58712d26bf5 --- /dev/null +++ b/arch/sparc/mm/viking.S @@ -0,0 +1,284 @@ +/* $Id: viking.S,v 1.19 2001/12/21 04:56:15 davem Exp $ + * viking.S: High speed Viking cache/mmu operations + * + * Copyright (C) 1997 Eddie C. Dost (ecd@skynet.be) + * Copyright (C) 1997,1998,1999 Jakub Jelinek (jj@ultra.linux.cz) + * Copyright (C) 1999 Pavel Semerad (semerad@ss1000.ms.mff.cuni.cz) + */ + +#include <linux/config.h> +#include <asm/ptrace.h> +#include <asm/psr.h> +#include <asm/asm_offsets.h> +#include <asm/asi.h> +#include <asm/mxcc.h> +#include <asm/page.h> +#include <asm/pgtsrmmu.h> +#include <asm/viking.h> +#include <asm/btfixup.h> + +#ifdef CONFIG_SMP + .data + .align 4 +sun4dsmp_flush_tlb_spin: + .word 0 +#endif + + .text + .align 4 + + .globl viking_flush_cache_all, viking_flush_cache_mm + .globl viking_flush_cache_range, viking_flush_cache_page + .globl viking_flush_page, viking_mxcc_flush_page + .globl viking_flush_page_for_dma, viking_flush_page_to_ram + .globl viking_flush_sig_insns + .globl viking_flush_tlb_all, viking_flush_tlb_mm + .globl viking_flush_tlb_range, viking_flush_tlb_page + +viking_flush_page: + sethi %hi(PAGE_OFFSET), %g2 + sub %o0, %g2, %g3 + srl %g3, 12, %g1 ! ppage >> 12 + + clr %o1 ! set counter, 0 - 127 + sethi %hi(PAGE_OFFSET + PAGE_SIZE - 0x80000000), %o3 + sethi %hi(0x80000000), %o4 + sethi %hi(VIKING_PTAG_VALID), %o5 + sethi %hi(2*PAGE_SIZE), %o0 + sethi %hi(PAGE_SIZE), %g7 + clr %o2 ! block counter, 0 - 3 +5: + sll %o1, 5, %g4 + or %g4, %o4, %g4 ! 0x80000000 | (set << 5) + + sll %o2, 26, %g5 ! block << 26 +6: + or %g5, %g4, %g5 + ldda [%g5] ASI_M_DATAC_TAG, %g2 + cmp %g3, %g1 ! ptag == ppage? + bne 7f + inc %o2 + + andcc %g2, %o5, %g0 ! ptag VALID? + be 7f + add %g4, %o3, %g2 ! (PAGE_OFFSET + PAGE_SIZE) | (set << 5) + ld [%g2], %g3 + ld [%g2 + %g7], %g3 + add %g2, %o0, %g2 + ld [%g2], %g3 + ld [%g2 + %g7], %g3 + add %g2, %o0, %g2 + ld [%g2], %g3 + ld [%g2 + %g7], %g3 + add %g2, %o0, %g2 + ld [%g2], %g3 + b 8f + ld [%g2 + %g7], %g3 + +7: + cmp %o2, 3 + ble 6b + sll %o2, 26, %g5 ! block << 26 + +8: inc %o1 + cmp %o1, 0x7f + ble 5b + clr %o2 + +9: retl + nop + +viking_mxcc_flush_page: + sethi %hi(PAGE_OFFSET), %g2 + sub %o0, %g2, %g3 + sub %g3, -PAGE_SIZE, %g3 ! ppage + PAGE_SIZE + sethi %hi(MXCC_SRCSTREAM), %o3 ! assume %hi(MXCC_SRCSTREAM) == %hi(MXCC_DESTSTREAM) + mov 0x10, %g2 ! set cacheable bit + or %o3, %lo(MXCC_SRCSTREAM), %o2 + or %o3, %lo(MXCC_DESSTREAM), %o3 + sub %g3, MXCC_STREAM_SIZE, %g3 +6: + stda %g2, [%o2] ASI_M_MXCC + stda %g2, [%o3] ASI_M_MXCC + andncc %g3, PAGE_MASK, %g0 + bne 6b + sub %g3, MXCC_STREAM_SIZE, %g3 + +9: retl + nop + +viking_flush_cache_page: +viking_flush_cache_range: +#ifndef CONFIG_SMP + ld [%o0 + 0x0], %o0 /* XXX vma->vm_mm, GROSS XXX */ +#endif +viking_flush_cache_mm: +#ifndef CONFIG_SMP + ld [%o0 + AOFF_mm_context], %g1 + cmp %g1, -1 + bne viking_flush_cache_all + nop + b,a viking_flush_cache_out +#endif +viking_flush_cache_all: + WINDOW_FLUSH(%g4, %g5) +viking_flush_cache_out: + retl + nop + +viking_flush_tlb_all: + mov 0x400, %g1 + retl + sta %g0, [%g1] ASI_M_FLUSH_PROBE + +viking_flush_tlb_mm: + mov SRMMU_CTX_REG, %g1 + ld [%o0 + AOFF_mm_context], %o1 + lda [%g1] ASI_M_MMUREGS, %g5 +#ifndef CONFIG_SMP + cmp %o1, -1 + be 1f +#endif + mov 0x300, %g2 + sta %o1, [%g1] ASI_M_MMUREGS + sta %g0, [%g2] ASI_M_FLUSH_PROBE + retl + sta %g5, [%g1] ASI_M_MMUREGS +#ifndef CONFIG_SMP +1: retl + nop +#endif + +viking_flush_tlb_range: + ld [%o0 + 0x00], %o0 /* XXX vma->vm_mm GROSS XXX */ + mov SRMMU_CTX_REG, %g1 + ld [%o0 + AOFF_mm_context], %o3 + lda [%g1] ASI_M_MMUREGS, %g5 +#ifndef CONFIG_SMP + cmp %o3, -1 + be 2f +#endif + sethi %hi(~((1 << SRMMU_PGDIR_SHIFT) - 1)), %o4 + sta %o3, [%g1] ASI_M_MMUREGS + and %o1, %o4, %o1 + add %o1, 0x200, %o1 + sta %g0, [%o1] ASI_M_FLUSH_PROBE +1: sub %o1, %o4, %o1 + cmp %o1, %o2 + blu,a 1b + sta %g0, [%o1] ASI_M_FLUSH_PROBE + retl + sta %g5, [%g1] ASI_M_MMUREGS +#ifndef CONFIG_SMP +2: retl + nop +#endif + +viking_flush_tlb_page: + ld [%o0 + 0x00], %o0 /* XXX vma->vm_mm GROSS XXX */ + mov SRMMU_CTX_REG, %g1 + ld [%o0 + AOFF_mm_context], %o3 + lda [%g1] ASI_M_MMUREGS, %g5 +#ifndef CONFIG_SMP + cmp %o3, -1 + be 1f +#endif + and %o1, PAGE_MASK, %o1 + sta %o3, [%g1] ASI_M_MMUREGS + sta %g0, [%o1] ASI_M_FLUSH_PROBE + retl + sta %g5, [%g1] ASI_M_MMUREGS +#ifndef CONFIG_SMP +1: retl + nop +#endif + +viking_flush_page_to_ram: +viking_flush_page_for_dma: +viking_flush_sig_insns: + retl + nop + +#ifdef CONFIG_SMP + .globl sun4dsmp_flush_tlb_all, sun4dsmp_flush_tlb_mm + .globl sun4dsmp_flush_tlb_range, sun4dsmp_flush_tlb_page +sun4dsmp_flush_tlb_all: + sethi %hi(sun4dsmp_flush_tlb_spin), %g3 +1: ldstub [%g3 + %lo(sun4dsmp_flush_tlb_spin)], %g5 + tst %g5 + bne 2f + mov 0x400, %g1 + sta %g0, [%g1] ASI_M_FLUSH_PROBE + retl + stb %g0, [%g3 + %lo(sun4dsmp_flush_tlb_spin)] +2: tst %g5 + bne,a 2b + ldub [%g3 + %lo(sun4dsmp_flush_tlb_spin)], %g5 + b,a 1b + +sun4dsmp_flush_tlb_mm: + sethi %hi(sun4dsmp_flush_tlb_spin), %g3 +1: ldstub [%g3 + %lo(sun4dsmp_flush_tlb_spin)], %g5 + tst %g5 + bne 2f + mov SRMMU_CTX_REG, %g1 + ld [%o0 + AOFF_mm_context], %o1 + lda [%g1] ASI_M_MMUREGS, %g5 + mov 0x300, %g2 + sta %o1, [%g1] ASI_M_MMUREGS + sta %g0, [%g2] ASI_M_FLUSH_PROBE + sta %g5, [%g1] ASI_M_MMUREGS + retl + stb %g0, [%g3 + %lo(sun4dsmp_flush_tlb_spin)] +2: tst %g5 + bne,a 2b + ldub [%g3 + %lo(sun4dsmp_flush_tlb_spin)], %g5 + b,a 1b + +sun4dsmp_flush_tlb_range: + sethi %hi(sun4dsmp_flush_tlb_spin), %g3 +1: ldstub [%g3 + %lo(sun4dsmp_flush_tlb_spin)], %g5 + tst %g5 + bne 3f + mov SRMMU_CTX_REG, %g1 + ld [%o0 + 0x00], %o0 /* XXX vma->vm_mm GROSS XXX */ + ld [%o0 + AOFF_mm_context], %o3 + lda [%g1] ASI_M_MMUREGS, %g5 + sethi %hi(~((1 << SRMMU_PGDIR_SHIFT) - 1)), %o4 + sta %o3, [%g1] ASI_M_MMUREGS + and %o1, %o4, %o1 + add %o1, 0x200, %o1 + sta %g0, [%o1] ASI_M_FLUSH_PROBE +2: sub %o1, %o4, %o1 + cmp %o1, %o2 + blu,a 2b + sta %g0, [%o1] ASI_M_FLUSH_PROBE + sta %g5, [%g1] ASI_M_MMUREGS + retl + stb %g0, [%g3 + %lo(sun4dsmp_flush_tlb_spin)] +3: tst %g5 + bne,a 3b + ldub [%g3 + %lo(sun4dsmp_flush_tlb_spin)], %g5 + b,a 1b + +sun4dsmp_flush_tlb_page: + sethi %hi(sun4dsmp_flush_tlb_spin), %g3 +1: ldstub [%g3 + %lo(sun4dsmp_flush_tlb_spin)], %g5 + tst %g5 + bne 2f + mov SRMMU_CTX_REG, %g1 + ld [%o0 + 0x00], %o0 /* XXX vma->vm_mm GROSS XXX */ + ld [%o0 + AOFF_mm_context], %o3 + lda [%g1] ASI_M_MMUREGS, %g5 + and %o1, PAGE_MASK, %o1 + sta %o3, [%g1] ASI_M_MMUREGS + sta %g0, [%o1] ASI_M_FLUSH_PROBE + sta %g5, [%g1] ASI_M_MMUREGS + retl + stb %g0, [%g3 + %lo(sun4dsmp_flush_tlb_spin)] +2: tst %g5 + bne,a 2b + ldub [%g3 + %lo(sun4dsmp_flush_tlb_spin)], %g5 + b,a 1b + nop +#endif |