summaryrefslogtreecommitdiffstats
path: root/arch/sparc/mm
diff options
context:
space:
mode:
authorRalf Baechle <ralf@linux-mips.org>1997-01-07 02:33:00 +0000
committer <ralf@linux-mips.org>1997-01-07 02:33:00 +0000
commitbeb116954b9b7f3bb56412b2494b562f02b864b1 (patch)
tree120e997879884e1b9d93b265221b939d2ef1ade1 /arch/sparc/mm
parent908d4681a1dc3792ecafbe64265783a86c4cccb6 (diff)
Import of Linux/MIPS 2.1.14
Diffstat (limited to 'arch/sparc/mm')
-rw-r--r--arch/sparc/mm/Makefile27
-rw-r--r--arch/sparc/mm/asyncd.c189
-rw-r--r--arch/sparc/mm/fault.c398
-rw-r--r--arch/sparc/mm/generic.c124
-rw-r--r--arch/sparc/mm/init.c414
-rw-r--r--arch/sparc/mm/loadmmu.c165
-rw-r--r--arch/sparc/mm/srmmu.c3477
-rw-r--r--arch/sparc/mm/sun4c.c1965
-rw-r--r--arch/sparc/mm/vac-flush.c94
9 files changed, 6371 insertions, 482 deletions
diff --git a/arch/sparc/mm/Makefile b/arch/sparc/mm/Makefile
index a4148d013..13652e467 100644
--- a/arch/sparc/mm/Makefile
+++ b/arch/sparc/mm/Makefile
@@ -1,4 +1,4 @@
-#
+# $Id: Makefile,v 1.21 1996/04/26 10:45:53 tridge Exp $
# Makefile for the linux Sparc-specific parts of the memory manager.
#
# Note! Dependencies are done automagically by 'make dep', which also
@@ -7,26 +7,7 @@
#
# Note 2! The CFLAGS definition is now in the main makefile...
-.c.o:
- $(CC) $(CFLAGS) -c $<
-.s.o:
- $(AS) -o $*.o $<
-.c.s:
- $(CC) $(CFLAGS) -S $<
-
-OBJS = fault.o vac-flush.o init.o
-
-mm.o: $(OBJS)
- $(LD) -r -o mm.o $(OBJS)
-
-modules:
+O_TARGET := mm.o
+O_OBJS := fault.o init.o sun4c.o srmmu.o loadmmu.o generic.o asyncd.o
-dep:
- $(CPP) -M *.c > .depend
-
-#
-# include a dependency file if one exists
-#
-ifeq (.depend,$(wildcard .depend))
-include .depend
-endif
+include $(TOPDIR)/Rules.make
diff --git a/arch/sparc/mm/asyncd.c b/arch/sparc/mm/asyncd.c
new file mode 100644
index 000000000..d6ed42252
--- /dev/null
+++ b/arch/sparc/mm/asyncd.c
@@ -0,0 +1,189 @@
+/* $Id: asyncd.c,v 1.8 1996/09/21 04:30:12 davem Exp $
+ * The asyncd kernel daemon. This handles paging on behalf of
+ * processes that receive page faults due to remote (async) memory
+ * accesses.
+ *
+ * Idea and skeleton code courtesy of David Miller (bless his cotton socks)
+ *
+ * Implemented by tridge
+ */
+
+#include <linux/mm.h>
+#include <linux/malloc.h>
+#include <linux/sched.h>
+#include <linux/head.h>
+#include <linux/kernel.h>
+#include <linux/kernel_stat.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/stat.h>
+#include <linux/swap.h>
+#include <linux/fs.h>
+
+#include <asm/dma.h>
+#include <asm/system.h> /* for cli()/sti() */
+#include <asm/segment.h> /* for memcpy_to/fromfs */
+#include <asm/bitops.h>
+#include <asm/pgtable.h>
+
+/*
+ * The wait queue for waking up the async daemon:
+ */
+static struct wait_queue * asyncd_wait = NULL;
+
+struct async_job {
+ volatile struct async_job *next;
+ int taskid;
+ struct mm_struct *mm;
+ unsigned long address;
+ int write;
+ void (*callback)(int,unsigned long,int,int);
+};
+
+static volatile struct async_job *async_queue = NULL;
+static volatile struct async_job *async_queue_end = NULL;
+
+static void add_to_async_queue(int taskid,
+ struct mm_struct *mm,
+ unsigned long address,
+ int write,
+ void (*callback)(int,unsigned long,int,int))
+{
+ struct async_job *a = kmalloc(sizeof(*a),GFP_ATOMIC);
+
+ if (!a)
+ panic("out of memory in asyncd\n");
+
+ a->next = NULL;
+ a->taskid = taskid;
+ a->mm = mm;
+ a->address = address;
+ a->write = write;
+ a->callback = callback;
+
+ if (!async_queue) {
+ async_queue = a;
+ } else {
+ async_queue_end->next = a;
+ }
+ async_queue_end = a;
+}
+
+
+void async_fault(unsigned long address, int write, int taskid,
+ void (*callback)(int,unsigned long,int,int))
+{
+ struct task_struct *tsk = task[taskid];
+ struct mm_struct *mm = tsk->mm;
+
+#if 0
+ printk("paging in %x for task=%d\n",address,taskid);
+#endif
+ add_to_async_queue(taskid, mm, address, write, callback);
+ wake_up(&asyncd_wait);
+}
+
+static int fault_in_page(int taskid,
+ struct vm_area_struct *vma,
+ unsigned address,int write)
+{
+ struct task_struct *tsk = task[taskid];
+ pgd_t *pgd;
+ pmd_t *pmd;
+ pte_t *pte;
+
+ if (!tsk || !tsk->mm)
+ return 1;
+
+ if (!vma || (write && !(vma->vm_flags & VM_WRITE)))
+ goto bad_area;
+ if (vma->vm_start > address)
+ goto bad_area;
+
+ pgd = pgd_offset(vma->vm_mm, address);
+ pmd = pmd_alloc(pgd,address);
+ if(!pmd)
+ goto no_memory;
+ pte = pte_alloc(pmd, address);
+ if(!pte)
+ goto no_memory;
+ if(!pte_present(*pte)) {
+ do_no_page(tsk, vma, address, write);
+ goto finish_up;
+ }
+ set_pte(pte, pte_mkyoung(*pte));
+ flush_tlb_page(vma, address);
+ if(!write)
+ goto finish_up;
+ if(pte_write(*pte)) {
+ set_pte(pte, pte_mkdirty(*pte));
+ flush_tlb_page(vma, address);
+ goto finish_up;
+ }
+ do_wp_page(tsk, vma, address, write);
+
+ /* Fall through for do_wp_page */
+finish_up:
+ update_mmu_cache(vma, address, *pte);
+ return 0;
+
+no_memory:
+ oom(tsk);
+ return 1;
+
+bad_area:
+ tsk->tss.sig_address = address;
+ tsk->tss.sig_desc = SUBSIG_NOMAPPING;
+ send_sig(SIGSEGV, tsk, 1);
+ return 1;
+}
+
+/* Note the semaphore operations must be done here, and _not_
+ * in async_fault().
+ */
+static void run_async_queue(void)
+{
+ int ret;
+ while (async_queue) {
+ volatile struct async_job *a = async_queue;
+ struct mm_struct *mm = a->mm;
+ struct vm_area_struct *vma;
+ async_queue = async_queue->next;
+ down(&mm->mmap_sem);
+ vma = find_vma(mm, a->address);
+ ret = fault_in_page(a->taskid,vma,a->address,a->write);
+ a->callback(a->taskid,a->address,a->write,ret);
+ up(&mm->mmap_sem);
+ kfree_s((void *)a,sizeof(*a));
+ }
+}
+
+
+
+
+/*
+ * The background async daemon.
+ * Started as a kernel thread from the init process.
+ */
+int asyncd(void *unused)
+{
+ current->session = 1;
+ current->pgrp = 1;
+ sprintf(current->comm, "asyncd");
+ current->blocked = ~0UL; /* block all signals */
+
+ /* Give kswapd a realtime priority. */
+ current->policy = SCHED_FIFO;
+ current->priority = 32; /* Fixme --- we need to standardise our
+ namings for POSIX.4 realtime scheduling
+ priorities. */
+
+ printk("Started asyncd\n");
+
+ while (1) {
+ current->signal = 0;
+ interruptible_sleep_on(&asyncd_wait);
+ run_async_queue();
+ }
+}
+
diff --git a/arch/sparc/mm/fault.c b/arch/sparc/mm/fault.c
index 4c5fd0bc3..8c8755ce5 100644
--- a/arch/sparc/mm/fault.c
+++ b/arch/sparc/mm/fault.c
@@ -1,26 +1,40 @@
+/* $Id: fault.c,v 1.77 1996/10/28 00:56:02 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)
+ */
+
+#include <asm/head.h>
+
#include <linux/string.h>
#include <linux/types.h>
#include <linux/ptrace.h>
#include <linux/mman.h>
+#include <linux/tasks.h>
+#include <linux/smp.h>
#include <linux/signal.h>
#include <linux/mm.h>
#include <asm/system.h>
#include <asm/segment.h>
-#include <asm/openprom.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>
-extern unsigned long pg0[1024]; /* page table for 0-4MB for everybody */
-extern struct sparc_phys_banks sp_banks[14];
-
-extern void die_if_kernel(char *,struct pt_regs *,long);
+#define ELEMENTS(arr) (sizeof (arr)/sizeof (arr[0]))
-struct linux_romvec *romvec;
+extern struct sparc_phys_banks sp_banks[SPARC_PHYS_BANKS];
+extern int prom_node_root;
-/* foo */
+extern void die_if_kernel(char *,struct pt_regs *);
-int tbase_needs_unmapping;
+struct linux_romvec *romvec;
/* At boot time we determine these two values necessary for setting
* up the segment maps and page table entries (pte's).
@@ -35,139 +49,297 @@ int vac_size, vac_linesize, vac_do_hw_vac_flushes;
int vac_entries_per_context, vac_entries_per_segment;
int vac_entries_per_page;
-/*
- * Define this if things work differently on a i386 and a i486:
- * it will (on a i486) warn about kernel memory accesses that are
- * done without a 'verify_area(VERIFY_WRITE,..)'
- */
-#undef CONFIG_TEST_VERIFY_AREA
+/* 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 that 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)
{
- register struct linux_romvec *lprom;
- register struct linux_mlist_v0 *mlist;
- register unsigned long bytes, base_paddr, tally;
- register int i;
-
- bytes = tally = 0;
- base_paddr = 0;
- i=0;
- lprom = romvec;
- switch(lprom->pv_romvers)
- {
- case 0:
- mlist=(*(lprom->pv_v0mem.v0_totphys));
- 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;
-
- if(mlist->theres_more != (void *)0) {
- i++;
- mlist=mlist->theres_more;
- bytes=mlist->num_bytes;
- tally += bytes;
- sp_banks[i].base_addr = (unsigned long) mlist->start_adr;
- sp_banks[i].num_bytes = mlist->num_bytes;
- }
- break;
- case 2:
- printk("no v2 memory probe support yet.\n");
- (*(lprom->pv_halt))();
- break;
- }
-
- i++;
- sp_banks[i].base_addr = 0xdeadbeef;
- sp_banks[i].num_bytes = 0;
-
- return tally;
-}
+ int total;
-/* Sparc routine to reserve the mapping of the open boot prom */
+ total = prom_probe_memory();
-/* uncomment this for FAME and FORTUNE! */
-/* #define DEBUG_MAP_PROM */
+ /* Oh man, much nicer, keep the dirt in promlib. */
+ return total;
+}
-int
-map_the_prom(int curr_num_segs)
-{
- register unsigned long prom_va_begin;
- register unsigned long prom_va_end;
- register int segmap_entry, i;
+extern void sun4c_complete_all_stores(void);
- prom_va_begin = LINUX_OPPROM_BEGVM;
- prom_va_end = LINUX_OPPROM_ENDVM;
+/* 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();
+}
-#ifdef DEBUG_MAP_PROM
- printk("\ncurr_num_segs = 0x%x\n", curr_num_segs);
+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;
+ int from_user = !(regs->psr & PSR_PS);
+#if 0
+ static unsigned long last_one;
#endif
- while( prom_va_begin < prom_va_end)
- {
- segmap_entry=get_segmap(prom_va_begin);
-
- curr_num_segs = ((segmap_entry<curr_num_segs)
- ? segmap_entry : curr_num_segs);
-
- for(i = num_contexts; --i > 0;)
- (*romvec->pv_setctxt)(i, (char *) prom_va_begin,
- segmap_entry);
-
- if(segmap_entry == invalid_segment)
- {
-
-#ifdef DEBUG_MAP_PROM
- printk("invalid_segments, virt_addr 0x%x\n", prom_va_begin);
+ down(&mm->mmap_sem);
+ if(text_fault)
+ address = regs->pc;
+
+#if 0
+ if(current->tss.ex.count) {
+ printk("f<pid=%d,tf=%d,wr=%d,addr=%08lx,pc=%08lx>\n",
+ tsk->pid, text_fault, write, address, regs->pc);
+ printk("EX: count<%d> pc<%08lx> expc<%08lx> address<%08lx>\n",
+ (int) current->tss.ex.count, current->tss.ex.pc,
+ current->tss.ex.expc, current->tss.ex.address);
+#if 0
+ if(last_one == address) {
+ printk("Twice in a row, AIEEE. Spinning so you can see the dump.\n");
+ show_regs(regs);
+ sti();
+ while(1)
+ barrier();
+ }
+ last_one = address;
#endif
-
- prom_va_begin += 0x40000; /* num bytes per segment entry */
- continue;
}
-
- /* DUH, prom maps itself so that users can access it. This is
- * broken.
- */
-
-#ifdef DEBUG_MAP_PROM
- printk("making segmap for prom privileged, va = 0x%x\n",
- prom_va_begin);
#endif
+ /* Now actually handle the fault. Do kernel faults special,
+ * because on the sun4c we could have faulted trying to read
+ * the vma area of the task and without the following code
+ * we'd fault recursively until all our stack is gone. ;-(
+ */
+ if(!from_user && address >= PAGE_OFFSET) {
+ quick_kernel_fault(address);
+ return;
+ }
- for(i = 0x40; --i >= 0; prom_va_begin+=4096)
- {
- put_pte(prom_va_begin, get_pte(prom_va_begin) | 0x20000000);
+ 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:
+ 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;
+ }
+ handle_mm_fault(vma, address, write);
+ up(&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(&mm->mmap_sem);
+ /* Did we have an exception handler installed? */
+ if(current->tss.ex.count == 1) {
+ if(from_user) {
+ printk("Yieee, exception signalled from user mode.\n");
+ } else {
+ /* Set pc to %g1, set %g1 to -EFAULT and %g2 to
+ * the faulting address so we can cleanup.
+ */
+ printk("Exception: PC<%08lx> faddr<%08lx>\n", regs->pc, address);
+ printk("EX: count<%d> pc<%08lx> expc<%08lx> address<%08lx>\n",
+ (int) current->tss.ex.count, current->tss.ex.pc,
+ current->tss.ex.expc, current->tss.ex.address);
+ current->tss.ex.count = 0;
+ regs->pc = current->tss.ex.expc;
+ regs->npc = regs->pc + 4;
+ regs->u_regs[UREG_G1] = -EFAULT;
+ regs->u_regs[UREG_G2] = address - current->tss.ex.address;
+ regs->u_regs[UREG_G3] = current->tss.ex.pc;
+ return;
+ }
}
+ if(from_user) {
+#if 0
+ printk("Fault whee %s [%d]: segfaults at %08lx pc=%08lx\n",
+ tsk->comm, tsk->pid, address, regs->pc);
+#endif
+ tsk->tss.sig_address = address;
+ tsk->tss.sig_desc = SUBSIG_NOMAPPING;
+ send_sig(SIGSEGV, tsk, 1);
+ return;
+ }
+ if((unsigned long) address < PAGE_SIZE) {
+ printk(KERN_ALERT "Unable to handle kernel NULL pointer dereference");
+ } else
+ printk(KERN_ALERT "Unable to handle kernel paging request");
+ printk(KERN_ALERT " at virtual address %08lx\n",address);
+ printk(KERN_ALERT "tsk->mm->context = %08lx\n",
+ (unsigned long) tsk->mm->context);
+ printk(KERN_ALERT "tsk->mm->pgd = %08lx\n",
+ (unsigned long) tsk->mm->pgd);
+ die_if_kernel("Oops", regs);
+}
- }
+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 pgd_t *sun4c_pgd_offset(struct mm_struct *,unsigned long);
+ extern pte_t *sun4c_pte_offset(pmd_t *,unsigned long);
+ struct task_struct *tsk = current;
+ struct mm_struct *mm = tsk->mm;
+ pgd_t *pgd;
+ pte_t *pte;
+
+ if(text_fault)
+ address = regs->pc;
+
+ pgd = sun4c_pgd_offset(mm, address);
+ pte = sun4c_pte_offset((pmd_t *) pgd, address);
+
+ /* This conditional is 'interesting'. */
+ if(pgd_val(*pgd) && !(write && !(pte_val(*pte) & _SUN4C_PAGE_WRITE))
+ && (pte_val(*pte) & _SUN4C_PAGE_VALID))
+ /* XXX Very bad, can't do this optimization when VMA arg is actually
+ * XXX used by update_mmu_cache()!
+ */
+ sun4c_update_mmu_cache((struct vm_area_struct *) 0, address, *pte);
+ else
+ do_sparc_fault(regs, text_fault, write, address);
+}
- printk("Mapped the PROM in all contexts...\n");
+/* 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;
-#ifdef DEBUG_MAP_PROM
- printk("curr_num_segs = 0x%x\n", curr_num_segs);
+#if 0
+ printk("wf<pid=%d,wr=%d,addr=%08lx>\n",
+ tsk->pid, write, address);
#endif
+ down(&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:
+ if(write)
+ if(!(vma->vm_flags & VM_WRITE))
+ goto bad_area;
+ else
+ if(!(vma->vm_flags & (VM_READ | VM_EXEC)))
+ goto bad_area;
+ handle_mm_fault(vma, address, write);
+ up(&mm->mmap_sem);
+ return;
+bad_area:
+ up(&mm->mmap_sem);
+#if 0
+ printk("Window whee %s [%d]: segfaults at %08lx\n",
+ tsk->comm, tsk->pid, address);
+#endif
+ tsk->tss.sig_address = address;
+ tsk->tss.sig_desc = SUBSIG_NOMAPPING;
+ send_sig(SIGSEGV, tsk, 1);
+ return;
+}
- return curr_num_segs;
+void window_overflow_fault(void)
+{
+ unsigned long sp;
+ sp = current->tss.rwbuf_stkptrs[0];
+ if(((sp + 0x38) & PAGE_MASK) != (sp & PAGE_MASK))
+ force_user_fault(sp + 0x38, 1);
+ force_user_fault(sp, 1);
}
-/*
- * This routine handles page faults. It determines the address,
- * and the problem, and then passes it off to one of the appropriate
- * routines.
- */
-asmlinkage void do_page_fault(struct pt_regs *regs, unsigned long error_code)
+void window_underflow_fault(unsigned long sp)
{
- die_if_kernel("Oops", regs, error_code);
- do_exit(SIGKILL);
+ 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 000000000..0c202fdeb
--- /dev/null
+++ b/arch/sparc/mm/generic.c
@@ -0,0 +1,124 @@
+/* $Id: generic.c,v 1.4 1996/10/27 08:36:41 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 <asm/pgtable.h>
+#include <asm/page.h>
+
+
+/* Allocate a block of RAM which is aligned to its size.
+ * This procedure can be used until the call to mem_init().
+ */
+void *sparc_init_alloc(unsigned long *kbrk, unsigned long size)
+{
+ unsigned long mask = size - 1;
+ unsigned long ret;
+
+ if(!size)
+ return 0x0;
+ if(size & mask) {
+ prom_printf("panic: sparc_init_alloc botch\n");
+ prom_halt();
+ }
+ ret = (*kbrk + mask) & ~mask;
+ *kbrk = ret + size;
+ memset((void*) ret, 0, size);
+ return (void*) ret;
+}
+
+static inline void forget_pte(pte_t page)
+{
+ if (pte_none(page))
+ return;
+ if (pte_present(page)) {
+ unsigned long addr = pte_page(page);
+ if (MAP_NR(addr) >= max_mapnr || PageReserved(mem_map+MAP_NR(addr)))
+ return;
+ free_page(addr);
+ if (current->mm->rss <= 0)
+ return;
+ current->mm->rss--;
+ return;
+ }
+ swap_free(pte_val(page));
+}
+
+/* Remap IO memory, the same way as remap_page_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(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(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(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(pmd, address);
+ if (!pte)
+ return -ENOMEM;
+ io_remap_pte_range(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(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;
+
+ pgprot_val(prot) = pg_iobits;
+ offset -= from;
+ dir = pgd_offset(current->mm, from);
+ flush_cache_range(current->mm, beg, end);
+ while (from < end) {
+ pmd_t *pmd = pmd_alloc(dir, from);
+ error = -ENOMEM;
+ if (!pmd)
+ break;
+ error = io_remap_pmd_range(pmd, from, end - from, offset + from, prot, space);
+ if (error)
+ break;
+ from = (from + PGDIR_SIZE) & PGDIR_MASK;
+ dir++;
+ }
+ flush_tlb_range(current->mm, beg, end);
+ return error;
+}
diff --git a/arch/sparc/mm/init.c b/arch/sparc/mm/init.c
index a65e9e094..41ac6c194 100644
--- a/arch/sparc/mm/init.c
+++ b/arch/sparc/mm/init.c
@@ -1,7 +1,8 @@
-/*
+/* $Id: init.c,v 1.42 1996/10/27 08:36:44 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)
*/
#include <linux/config.h>
@@ -15,29 +16,28 @@
#include <linux/ptrace.h>
#include <linux/mman.h>
#include <linux/mm.h>
+#include <linux/swap.h>
+#ifdef CONFIG_BLK_DEV_INITRD
+#include <linux/blk.h>
+#endif
#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>
-extern void scsi_mem_init(unsigned long);
-extern void sound_mem_init(void);
-extern void die_if_kernel(char *,struct pt_regs *,long);
extern void show_net_buffers(void);
-extern int map_the_prom(int);
-
-struct sparc_phys_banks sp_banks[14];
-unsigned long *sun4c_mmu_table;
-extern int invalid_segment, num_segmaps, num_contexts;
+struct sparc_phys_banks sp_banks[SPARC_PHYS_BANKS];
+unsigned long sparc_unmapped_base;
/*
* BAD_PAGE is the page that is used for page faults when linux
* is out-of-memory. Older versions of linux just did a
* do_exit(), but using this instead means there is less risk
- * for a process dying in kernel mode, possibly leaving a inode
+ * for a process dying in kernel mode, possibly leaving an inode
* unused etc..
*
* BAD_PAGETABLE is the accompanying page-table: it is initialized
@@ -58,29 +58,23 @@ pte_t __bad_page(void)
return pte_mkdirty(mk_pte((unsigned long) EMPTY_PGE, PAGE_SHARED));
}
-unsigned long __zero_page(void)
-{
- memset((void *) ZERO_PGE, 0, PAGE_SIZE);
- return ZERO_PGE;
-}
-
void show_mem(void)
{
int i,free = 0,total = 0,reserved = 0;
int shared = 0;
- printk("Mem-info:\n");
+ printk("\nMem-info:\n");
show_free_areas();
printk("Free swap: %6dkB\n",nr_swap_pages<<(PAGE_SHIFT-10));
- i = high_memory >> PAGE_SHIFT;
+ i = max_mapnr;
while (i-- > 0) {
total++;
- if (mem_map[i] & MAP_PAGE_RESERVED)
+ if (PageReserved(mem_map + i))
reserved++;
- else if (!mem_map[i])
+ else if (!mem_map[i].count)
free++;
else
- shared += mem_map[i]-1;
+ shared += mem_map[i].count-1;
}
printk("%d pages of RAM\n",total);
printk("%d free pages\n",free);
@@ -92,273 +86,189 @@ void show_mem(void)
#endif
}
-extern unsigned long free_area_init(unsigned long, unsigned long);
+extern pgprot_t protection_map[16];
+
+unsigned long sparc_context_init(unsigned long start_mem, int numctx)
+{
+ int ctx;
+
+ ctx_list_pool = (struct ctx_list *) start_mem;
+ start_mem += (numctx * sizeof(struct ctx_list));
+ for(ctx = 0; ctx < numctx; ctx++) {
+ struct ctx_list *clist;
+
+ clist = (ctx_list_pool + ctx);
+ clist->ctx_number = ctx;
+ clist->ctx_mm = 0;
+ }
+ 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);
+ return start_mem;
+}
/*
- * paging_init() sets up the page tables: in the alpha version this actually
- * unmaps the bootup page table (as we're now in KSEG, so we don't need it).
+ * paging_init() sets up the page tables: We call the MMU specific
+ * init routine based upon the Sun model type on the Sparc.
*
- * The bootup sequence put the virtual page table into high memory: that
- * means that we can change the L1 page table by just using VL1p below.
*/
+extern unsigned long sun4c_paging_init(unsigned long, unsigned long);
+extern unsigned long srmmu_paging_init(unsigned long, unsigned long);
+extern unsigned long device_scan(unsigned long);
unsigned long paging_init(unsigned long start_mem, unsigned long end_mem)
{
- unsigned long i, a, b, mask=0;
- unsigned long curseg, curpte, num_inval;
- unsigned long address;
- pte_t *pg_table;
+ switch(sparc_cpu_model) {
+ case sun4c:
+ case sun4e:
+ start_mem = sun4c_paging_init(start_mem, end_mem);
+ sparc_unmapped_base = 0xe0000000;
+ break;
+ case sun4m:
+ case sun4d:
+ start_mem = srmmu_paging_init(start_mem, end_mem);
+ 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;
+ return device_scan(start_mem);
+}
- register int num_segs, num_ctx;
- register char * c;
+struct cache_palias *sparc_aliases;
- num_segs = num_segmaps;
- num_ctx = num_contexts;
+extern int min_free_pages;
+extern int free_pages_low;
+extern int free_pages_high;
+extern void srmmu_frob_mem_map(unsigned long);
- num_segs -= 1;
- invalid_segment = num_segs;
+int physmem_mapped_contig = 1;
- start_mem = free_area_init(start_mem, end_mem);
+static void taint_real_pages(unsigned long start_mem, unsigned long end_mem)
+{
+ unsigned long addr, tmp2 = 0;
+
+ if(physmem_mapped_contig) {
+ for(addr = PAGE_OFFSET; addr < end_mem; addr += PAGE_SIZE) {
+ if(addr >= KERNBASE && addr < start_mem)
+ addr = start_mem;
+ for(tmp2=0; sp_banks[tmp2].num_bytes != 0; tmp2++) {
+ unsigned long phys_addr = (addr - PAGE_OFFSET);
+ unsigned long base = sp_banks[tmp2].base_addr;
+ unsigned long limit = base + sp_banks[tmp2].num_bytes;
+
+ if((phys_addr >= base) && (phys_addr < limit) &&
+ ((phys_addr + PAGE_SIZE) < limit))
+ mem_map[MAP_NR(addr)].flags &= ~(1<<PG_reserved);
+ }
+ }
+ } else {
+ if((sparc_cpu_model == sun4m) || (sparc_cpu_model == sun4d)) {
+ srmmu_frob_mem_map(start_mem);
+ } else {
+ for(addr = start_mem; addr < end_mem; addr += PAGE_SIZE)
+ mem_map[MAP_NR(addr)].flags &= ~(1<<PG_reserved);
+ }
+ }
+}
-/* On the sparc we first need to allocate the segmaps for the
- * PROM's virtual space, and make those segmaps unusable. We
- * map the PROM in ALL contexts therefore the break key and the
- * sync command work no matter what state you took the machine
- * out of
- */
+void mem_init(unsigned long start_mem, unsigned long end_mem)
+{
+ int codepages = 0;
+ int datapages = 0;
+ unsigned long tmp2, addr;
+ extern char etext;
+
+ /* Saves us work later. */
+ memset((void *) ZERO_PAGE, 0, PAGE_SIZE);
- printk("mapping the prom...\n");
- num_segs = map_the_prom(num_segs);
+ end_mem &= PAGE_MASK;
+ max_mapnr = MAP_NR(end_mem);
+ high_memory = (void *) end_mem;
start_mem = PAGE_ALIGN(start_mem);
- /* Set up static page tables in kernel space, this will be used
- * so that the low-level page fault handler can fill in missing
- * TLB entries since all mmu entries cannot be loaded at once
- * on the sun4c.
- */
-
-#if 0
- /* ugly debugging code */
- for(i=0; i<40960; i+=PAGE_SIZE)
- printk("address=0x%x vseg=%d pte=0x%x\n", (unsigned int) i,
- (int) get_segmap(i), (unsigned int) get_pte(i));
-#endif
+ addr = KERNBASE;
+ while(addr < start_mem) {
+#ifdef CONFIG_BLK_DEV_INITRD
+ if (initrd_below_start_ok && addr >= initrd_start && addr < initrd_end)
+ mem_map[MAP_NR(addr)].flags &= ~(1<<PG_reserved);
+ else
+#endif
+ mem_map[MAP_NR(addr)].flags |= (1<<PG_reserved);
+ addr += PAGE_SIZE;
+ }
- printk("Setting up kernel static mmu table... bounce bounce\n");
-
- address = 0; /* ((unsigned long) &end) + 524288; */
- sun4c_mmu_table = (unsigned long *) start_mem;
- pg_table = (pte_t *) start_mem;
- curseg = curpte = num_inval = 0;
- while(address < end_mem) {
- if(curpte == 0)
- put_segmap((address&PGDIR_MASK), curseg);
- for(i=0; sp_banks[i].num_bytes != 0; i++)
- if((address >= sp_banks[i].base_addr) &&
- (address <= (sp_banks[i].base_addr + sp_banks[i].num_bytes)))
- goto good_address;
- /* No physical memory here, so set the virtual segment to
- * the invalid one, and put an invalid pte in the static
- * kernel table.
- */
- *pg_table = mk_pte((address >> PAGE_SHIFT), PAGE_INVALID);
- pg_table++; curpte++; num_inval++;
- if(curpte > 63) {
- if(curpte == num_inval) {
- put_segmap((address&PGDIR_MASK), invalid_segment);
- } else {
- put_segmap((address&PGDIR_MASK), curseg);
- curseg++;
- }
- curpte = num_inval = 0;
- }
- address += PAGE_SIZE;
- continue;
-
- good_address:
- /* create pte entry */
- if(address < (((unsigned long) &end) + 524288)) {
- pte_val(*pg_table) = get_pte(address);
- } else {
- *pg_table = mk_pte((address >> PAGE_SHIFT), PAGE_KERNEL);
- put_pte(address, pte_val(*pg_table));
- }
-
- pg_table++; curpte++;
- if(curpte > 63) {
- put_segmap((address&PGDIR_MASK), curseg);
- curpte = num_inval = 0;
- curseg++;
- }
- address += PAGE_SIZE;
- }
-
- start_mem = (unsigned long) pg_table;
- /* ok, allocate the kernel pages, map them in all contexts
- * (with help from the prom), and lock them. Isn't the sparc
- * fun kiddies? TODO
- */
-
-#if 0
- /* ugly debugging code */
- for(i=0x1a3000; i<(0x1a3000+40960); i+=PAGE_SIZE)
- printk("address=0x%x vseg=%d pte=0x%x\n", (unsigned int) i,
- (int) get_segmap(i), (unsigned int) get_pte(i));
- halt();
+ taint_real_pages(start_mem, end_mem);
+ for (addr = PAGE_OFFSET; addr < end_mem; addr += PAGE_SIZE) {
+ if(PageReserved(mem_map + MAP_NR(addr))) {
+ if ((addr < (unsigned long) &etext) && (addr >= KERNBASE))
+ codepages++;
+ else if((addr < start_mem) && (addr >= KERNBASE))
+ datapages++;
+ continue;
+ }
+ mem_map[MAP_NR(addr)].count = 1;
+#ifdef CONFIG_BLK_DEV_INITRD
+ if (!initrd_start ||
+ (addr < initrd_start || addr >= initrd_end))
#endif
+ free_page(addr);
+ }
- b=PGDIR_ALIGN(start_mem)>>18;
- c= (char *)0x0;
-
- printk("mapping kernel in all contexts...\n");
-
- for(a=0; a<b; a++)
- {
- for(i=0; i<num_contexts; i++)
- {
- /* map the kernel virt_addrs */
- (*(romvec->pv_setctxt))(i, (char *) c, a);
- }
- c += 0x40000;
- }
-
- /* Ok, since now mapped in all contexts, we can free up
- * context zero to be used amongst user processes.
- */
-
- /* free context 0 here TODO */
-
- /* invalidate all user pages and initialize the pte struct
- * for userland. TODO
- */
-
- /* Make the kernel text unwritable and cacheable, the prom
- * loaded our text as writable, only sneaky sunos kernels need
- * self-modifying code.
- */
-
- a= (unsigned long) &etext;
- mask=~(PTE_NC|PTE_W); /* make cacheable + not writable */
-
- /* must do for every segment since kernel uses all contexts
- * and unlike some sun kernels I know of, we can't hard wire
- * context 0 just for the kernel, that is unnecessary.
- */
-
- for(i=0; i<8; i++)
- {
- b=PAGE_ALIGN((unsigned long) &trapbase);
-
- switch_to_context(i);
-
- for(;b<a; b+=4096)
- {
- put_pte(b, (get_pte(b) & mask));
- }
- }
-
- invalidate(); /* flush the virtual address cache */
-
- printk("\nCurrently in context - ");
- for(i=0; i<num_contexts; i++)
- {
- switch_to_context(i);
- printk("%d ", (int) i);
- }
- printk("\n");
-
- switch_to_context(0);
+ tmp2 = nr_free_pages << PAGE_SHIFT;
- invalidate();
- return start_mem;
-}
-
-void mem_init(unsigned long start_mem, unsigned long end_mem)
-{
- unsigned long start_low_mem = PAGE_SIZE;
- int codepages = 0;
- int reservedpages = 0;
- int datapages = 0;
- int i = 0;
- unsigned long tmp, limit, tmp2, addr;
- extern char etext;
-
- end_mem &= PAGE_MASK;
- high_memory = end_mem;
-
- start_low_mem = PAGE_ALIGN(start_low_mem);
- start_mem = PAGE_ALIGN(start_mem);
-
- for(i = 0; sp_banks[i].num_bytes != 0; i++) {
- tmp = sp_banks[i].base_addr;
- limit = (sp_banks[i].base_addr + sp_banks[i].num_bytes);
- if(tmp<start_mem) {
- if(limit>start_mem)
- tmp = start_mem;
- else continue;
- }
-
- while(tmp<limit) {
- mem_map[MAP_NR(tmp)] = 0;
- tmp += PAGE_SIZE;
- }
- if(sp_banks[i+1].num_bytes != 0)
- while(tmp < sp_banks[i+1].base_addr) {
- mem_map[MAP_NR(tmp)] = MAP_PAGE_RESERVED;
- tmp += PAGE_SIZE;
- }
- }
-
-#ifdef CONFIG_SCSI
- scsi_mem_init(high_memory);
-#endif
+ printk("Memory: %luk available (%dk kernel code, %dk data) [%08lx,%08lx]\n",
+ tmp2 >> 10,
+ codepages << (PAGE_SHIFT-10),
+ datapages << (PAGE_SHIFT-10), PAGE_OFFSET, end_mem);
- for (addr = 0; addr < high_memory; addr += PAGE_SIZE) {
- if(mem_map[MAP_NR(addr)]) {
- if (addr < (unsigned long) &etext)
- codepages++;
- else if(addr < start_mem)
- datapages++;
- else
- reservedpages++;
- continue;
- }
- mem_map[MAP_NR(addr)] = 1;
- free_page(addr);
- }
-
- tmp2 = nr_free_pages << PAGE_SHIFT;
-
- printk("Memory: %luk/%luk available (%dk kernel code, %dk reserved, %dk data)\n",
- tmp2 >> 10,
- high_memory >> 10,
- codepages << (PAGE_SHIFT-10),
- reservedpages << (PAGE_SHIFT-10),
- datapages << (PAGE_SHIFT-10));
-
- invalidate();
- return;
+ min_free_pages = nr_free_pages >> 7;
+ if(min_free_pages < 16)
+ min_free_pages = 16;
+ free_pages_low = min_free_pages + (min_free_pages >> 1);
+ free_pages_high = min_free_pages + min_free_pages;
}
void si_meminfo(struct sysinfo *val)
{
int i;
- i = high_memory >> PAGE_SHIFT;
+ i = MAP_NR(high_memory);
val->totalram = 0;
val->sharedram = 0;
val->freeram = nr_free_pages << PAGE_SHIFT;
val->bufferram = buffermem;
while (i-- > 0) {
- if (mem_map[i] & MAP_PAGE_RESERVED)
+ if (PageReserved(mem_map + i))
continue;
val->totalram++;
- if (!mem_map[i])
+ if (!mem_map[i].count)
continue;
- val->sharedram += mem_map[i]-1;
+ val->sharedram += mem_map[i].count-1;
}
val->totalram <<= PAGE_SHIFT;
val->sharedram <<= PAGE_SHIFT;
- return;
}
diff --git a/arch/sparc/mm/loadmmu.c b/arch/sparc/mm/loadmmu.c
new file mode 100644
index 000000000..ac1ecd790
--- /dev/null
+++ b/arch/sparc/mm/loadmmu.c
@@ -0,0 +1,165 @@
+/* $Id: loadmmu.c,v 1.36 1996/10/27 08:36:46 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)
+ */
+
+#include <linux/kernel.h>
+#include <linux/mm.h>
+
+#include <asm/system.h>
+#include <asm/page.h>
+#include <asm/pgtable.h>
+
+unsigned long page_offset = 0xf0000000;
+
+struct ctx_list *ctx_list_pool;
+struct ctx_list ctx_free;
+struct ctx_list ctx_used;
+
+unsigned long (*alloc_kernel_stack)(struct task_struct *tsk);
+void (*free_kernel_stack)(unsigned long stack);
+struct task_struct *(*alloc_task_struct)(void);
+void (*free_task_struct)(struct task_struct *tsk);
+
+void (*quick_kernel_fault)(unsigned long);
+
+void (*mmu_exit_hook)(void);
+void (*mmu_flush_hook)(void);
+
+/* translate between physical and virtual addresses */
+unsigned long (*mmu_v2p)(unsigned long);
+unsigned long (*mmu_p2v)(unsigned long);
+
+char *(*mmu_lockarea)(char *, unsigned long);
+void (*mmu_unlockarea)(char *, unsigned long);
+
+char *(*mmu_get_scsi_one)(char *, unsigned long, struct linux_sbus *sbus);
+void (*mmu_get_scsi_sgl)(struct mmu_sglist *, int, struct linux_sbus *sbus);
+void (*mmu_release_scsi_one)(char *, unsigned long, struct linux_sbus *sbus);
+void (*mmu_release_scsi_sgl)(struct mmu_sglist *, int, struct linux_sbus *sbus);
+
+void (*mmu_map_dma_area)(unsigned long addr, int len);
+
+void (*update_mmu_cache)(struct vm_area_struct *vma, unsigned long address, pte_t pte);
+
+#ifdef __SMP__
+void (*local_flush_cache_all)(void);
+void (*local_flush_cache_mm)(struct mm_struct *);
+void (*local_flush_cache_range)(struct mm_struct *, unsigned long start,
+ unsigned long end);
+void (*local_flush_cache_page)(struct vm_area_struct *, unsigned long address);
+
+void (*local_flush_tlb_all)(void);
+void (*local_flush_tlb_mm)(struct mm_struct *);
+void (*local_flush_tlb_range)(struct mm_struct *, unsigned long start,
+ unsigned long end);
+void (*local_flush_tlb_page)(struct vm_area_struct *, unsigned long address);
+void (*local_flush_page_to_ram)(unsigned long address);
+#endif
+
+void (*flush_cache_all)(void);
+void (*flush_cache_mm)(struct mm_struct *);
+void (*flush_cache_range)(struct mm_struct *, unsigned long start,
+ unsigned long end);
+void (*flush_cache_page)(struct vm_area_struct *, unsigned long address);
+
+void (*flush_tlb_all)(void);
+void (*flush_tlb_mm)(struct mm_struct *);
+void (*flush_tlb_range)(struct mm_struct *, unsigned long start,
+ unsigned long end);
+void (*flush_tlb_page)(struct vm_area_struct *, unsigned long address);
+
+void (*flush_page_to_ram)(unsigned long page);
+
+void (*set_pte)(pte_t *pteptr, pte_t pteval);
+
+unsigned int pmd_shift, pmd_size, pmd_mask;
+unsigned int (*pmd_align)(unsigned int);
+unsigned int pgdir_shift, pgdir_size, pgdir_mask;
+unsigned int (*pgdir_align)(unsigned int);
+unsigned int ptrs_per_pte, ptrs_per_pmd, ptrs_per_pgd;
+unsigned int pg_iobits;
+
+pgprot_t page_none, page_shared, page_copy, page_readonly, page_kernel;
+
+unsigned long (*pte_page)(pte_t);
+unsigned long (*pmd_page)(pmd_t);
+unsigned long (*pgd_page)(pgd_t);
+
+void (*sparc_update_rootmmu_dir)(struct task_struct *, pgd_t *pgdir);
+unsigned long (*(vmalloc_start))(void);
+void (*switch_to_context)(struct task_struct *tsk);
+
+int (*pte_none)(pte_t);
+int (*pte_present)(pte_t);
+void (*pte_clear)(pte_t *);
+
+int (*pmd_none)(pmd_t);
+int (*pmd_bad)(pmd_t);
+int (*pmd_present)(pmd_t);
+void (*pmd_clear)(pmd_t *);
+
+int (*pgd_none)(pgd_t);
+int (*pgd_bad)(pgd_t);
+int (*pgd_present)(pgd_t);
+void (*pgd_clear)(pgd_t *);
+
+pte_t (*mk_pte)(unsigned long, pgprot_t);
+pte_t (*mk_pte_phys)(unsigned long, pgprot_t);
+pte_t (*mk_pte_io)(unsigned long, pgprot_t, int);
+void (*pgd_set)(pgd_t *, pmd_t *);
+pte_t (*pte_modify)(pte_t, pgprot_t);
+pgd_t * (*pgd_offset)(struct mm_struct *, unsigned long);
+pmd_t * (*pmd_offset)(pgd_t *, unsigned long);
+pte_t * (*pte_offset)(pmd_t *, unsigned long);
+void (*pte_free_kernel)(pte_t *);
+pte_t * (*pte_alloc_kernel)(pmd_t *, unsigned long);
+
+void (*pmd_free_kernel)(pmd_t *);
+pmd_t * (*pmd_alloc_kernel)(pgd_t *, unsigned long);
+void (*pte_free)(pte_t *);
+pte_t * (*pte_alloc)(pmd_t *, unsigned long);
+
+void (*pmd_free)(pmd_t *);
+pmd_t * (*pmd_alloc)(pgd_t *, unsigned long);
+void (*pgd_free)(pgd_t *);
+
+pgd_t * (*pgd_alloc)(void);
+
+int (*pte_write)(pte_t);
+int (*pte_dirty)(pte_t);
+int (*pte_young)(pte_t);
+
+pte_t (*pte_wrprotect)(pte_t);
+pte_t (*pte_mkclean)(pte_t);
+pte_t (*pte_mkold)(pte_t);
+pte_t (*pte_mkwrite)(pte_t);
+pte_t (*pte_mkdirty)(pte_t);
+pte_t (*pte_mkyoung)(pte_t);
+
+char *(*mmu_info)(void);
+
+extern void ld_mmu_sun4c(void);
+extern void ld_mmu_srmmu(void);
+
+void
+load_mmu(void)
+{
+ switch(sparc_cpu_model) {
+ case sun4c:
+ ld_mmu_sun4c();
+ break;
+ case sun4m:
+ case sun4d:
+ ld_mmu_srmmu();
+ break;
+ default:
+ printk("load_mmu:MMU support not available for this architecture\n");
+ printk("load_mmu:sparc_cpu_model = %d\n", (int) sparc_cpu_model);
+ printk("load_mmu:Halting...\n");
+ panic("load_mmu()");
+ }
+}
diff --git a/arch/sparc/mm/srmmu.c b/arch/sparc/mm/srmmu.c
new file mode 100644
index 000000000..7d9b653df
--- /dev/null
+++ b/arch/sparc/mm/srmmu.c
@@ -0,0 +1,3477 @@
+/* $Id: srmmu.c,v 1.103 1996/10/31 06:28:35 davem Exp $
+ * srmmu.c: SRMMU specific routines for memory management.
+ *
+ * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu)
+ * Copyright (C) 1995 Peter A. Zaitcev (zaitcev@ithil.mcst.ru)
+ * Copyright (C) 1996 Eddie C. Dost (ecd@skynet.be)
+ */
+
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/malloc.h>
+
+#include <asm/page.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/iommu.h>
+#include <asm/asi.h>
+#include <asm/msi.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>
+
+enum mbus_module srmmu_modtype;
+unsigned int hwbug_bitmask;
+int vac_cache_size;
+int vac_line_size;
+int vac_badbits;
+
+extern unsigned long sparc_iobase_vaddr;
+
+#ifdef __SMP__
+extern void smp_capture(void);
+extern void smp_release(void);
+#else
+#define smp_capture()
+#define smp_release()
+#endif /* !(__SMP__) */
+
+/* #define USE_CHUNK_ALLOC 1 */
+
+static void (*ctxd_set)(ctxd_t *ctxp, pgd_t *pgdp);
+static void (*pmd_set)(pmd_t *pmdp, pte_t *ptep);
+
+static void (*flush_page_for_dma)(unsigned long page);
+static void (*flush_cache_page_to_uncache)(unsigned long page);
+static void (*flush_tlb_page_for_cbit)(unsigned long page);
+#ifdef __SMP__
+static void (*local_flush_page_for_dma)(unsigned long page);
+static void (*local_flush_cache_page_to_uncache)(unsigned long page);
+static void (*local_flush_tlb_page_for_cbit)(unsigned long page);
+#endif
+
+static struct srmmu_stats {
+ int invall;
+ int invpg;
+ int invrnge;
+ int invmm;
+} module_stats;
+
+static char *srmmu_name;
+
+ctxd_t *srmmu_ctx_table_phys;
+ctxd_t *srmmu_context_table;
+
+static struct srmmu_trans {
+ unsigned long vbase;
+ unsigned long pbase;
+ unsigned long size;
+} srmmu_map[SPARC_PHYS_BANKS];
+
+static int viking_mxcc_present = 0;
+
+void srmmu_frob_mem_map(unsigned long start_mem)
+{
+ unsigned long bank_start, bank_end;
+ unsigned long addr;
+ int i;
+
+ /* First, mark all pages as invalid. */
+ for(addr = PAGE_OFFSET; MAP_NR(addr) < max_mapnr; addr += PAGE_SIZE)
+ mem_map[MAP_NR(addr)].flags |= (1<<PG_reserved);
+
+ start_mem = PAGE_ALIGN(start_mem);
+ for(i = 0; srmmu_map[i].size; i++) {
+ bank_start = srmmu_map[i].vbase;
+ bank_end = bank_start + srmmu_map[i].size;
+ while(bank_start < bank_end) {
+ if((bank_start >= KERNBASE) &&
+ (bank_start < start_mem)) {
+ bank_start += PAGE_SIZE;
+ continue;
+ }
+ mem_map[MAP_NR(bank_start)].flags &= ~(1<<PG_reserved);
+ bank_start += PAGE_SIZE;
+ }
+ }
+}
+
+/* Physical memory can be _very_ non-contiguous on the sun4m, especially
+ * the SS10/20 class machines and with the latest openprom revisions.
+ * So we have to crunch the free page pool.
+ */
+static inline unsigned long srmmu_v2p(unsigned long vaddr)
+{
+ int i;
+
+ for(i=0; srmmu_map[i].size != 0; i++) {
+ if(srmmu_map[i].vbase <= vaddr &&
+ (srmmu_map[i].vbase + srmmu_map[i].size > vaddr)) {
+ return (vaddr - srmmu_map[i].vbase) + srmmu_map[i].pbase;
+ }
+ }
+ return 0xffffffffUL;
+}
+
+static inline unsigned long srmmu_p2v(unsigned long paddr)
+{
+ int i;
+
+ for(i=0; srmmu_map[i].size != 0; i++) {
+ if(srmmu_map[i].pbase <= paddr &&
+ (srmmu_map[i].pbase + srmmu_map[i].size > paddr))
+ return (paddr - srmmu_map[i].pbase) + srmmu_map[i].vbase;
+ }
+ return 0xffffffffUL;
+}
+
+/* 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)
+{
+#if MEM_BUS_SPACE
+ /* the AP1000 has its memory on bus 8, not 0 like suns do */
+ if (!(value&KERNBASE))
+ value |= MEM_BUS_SPACE<<28;
+ if (value == MEM_BUS_SPACE<<28) value = 0;
+#endif
+ __asm__ __volatile__("swap [%2], %0\n\t" :
+ "=&r" (value) :
+ "0" (value), "r" (addr));
+ return value;
+}
+
+/* Functions really use this, not srmmu_swap directly. */
+#define srmmu_set_entry(ptr, newentry) \
+ srmmu_swap((unsigned long *) (ptr), (newentry))
+
+
+/* The very generic SRMMU page table operations. */
+static unsigned int srmmu_pmd_align(unsigned int addr) { return SRMMU_PMD_ALIGN(addr); }
+static unsigned int srmmu_pgdir_align(unsigned int addr) { return SRMMU_PGDIR_ALIGN(addr); }
+
+static unsigned long srmmu_vmalloc_start(void)
+{
+ return SRMMU_VMALLOC_START;
+}
+
+static unsigned long srmmu_pgd_page(pgd_t pgd)
+{ return srmmu_p2v((pgd_val(pgd) & SRMMU_PTD_PMASK) << 4); }
+
+static unsigned long srmmu_pmd_page(pmd_t pmd)
+{ return srmmu_p2v((pmd_val(pmd) & SRMMU_PTD_PMASK) << 4); }
+
+static inline int srmmu_device_memory(pte_t pte)
+{
+ return (pte_val(pte)>>28) != MEM_BUS_SPACE;
+}
+
+static unsigned long srmmu_pte_page(pte_t pte)
+{ return srmmu_device_memory(pte)?~0:srmmu_p2v((pte_val(pte) & SRMMU_PTE_PMASK) << 4); }
+
+static int srmmu_pte_none(pte_t pte) { return !pte_val(pte); }
+static int srmmu_pte_present(pte_t pte)
+{ return ((pte_val(pte) & SRMMU_ET_MASK) == SRMMU_ET_PTE); }
+
+static void srmmu_pte_clear(pte_t *ptep) { set_pte(ptep, __pte(0)); }
+
+static int srmmu_pmd_none(pmd_t pmd) { return !pmd_val(pmd); }
+static int srmmu_pmd_bad(pmd_t pmd)
+{ return (pmd_val(pmd) & SRMMU_ET_MASK) != SRMMU_ET_PTD; }
+
+static int srmmu_pmd_present(pmd_t pmd)
+{ return ((pmd_val(pmd) & SRMMU_ET_MASK) == SRMMU_ET_PTD); }
+
+static void srmmu_pmd_clear(pmd_t *pmdp) { set_pte((pte_t *)pmdp, __pte(0)); }
+
+static int srmmu_pgd_none(pgd_t pgd) { return !pgd_val(pgd); }
+static int srmmu_pgd_bad(pgd_t pgd)
+{ return (pgd_val(pgd) & SRMMU_ET_MASK) != SRMMU_ET_PTD; }
+
+static int srmmu_pgd_present(pgd_t pgd)
+{ return ((pgd_val(pgd) & SRMMU_ET_MASK) == SRMMU_ET_PTD); }
+
+static void srmmu_pgd_clear(pgd_t * pgdp) { set_pte((pte_t *)pgdp, __pte(0)); }
+
+static int srmmu_pte_write(pte_t pte) { return pte_val(pte) & SRMMU_WRITE; }
+static int srmmu_pte_dirty(pte_t pte) { return pte_val(pte) & SRMMU_DIRTY; }
+static int srmmu_pte_young(pte_t pte) { return pte_val(pte) & SRMMU_REF; }
+
+static pte_t srmmu_pte_wrprotect(pte_t pte) { pte_val(pte) &= ~SRMMU_WRITE; return pte;}
+static pte_t srmmu_pte_mkclean(pte_t pte) { pte_val(pte) &= ~SRMMU_DIRTY; return pte; }
+static pte_t srmmu_pte_mkold(pte_t pte) { pte_val(pte) &= ~SRMMU_REF; return pte; }
+static pte_t srmmu_pte_mkwrite(pte_t pte) { pte_val(pte) |= SRMMU_WRITE; return pte; }
+static pte_t srmmu_pte_mkdirty(pte_t pte) { pte_val(pte) |= SRMMU_DIRTY; return pte; }
+static pte_t srmmu_pte_mkyoung(pte_t pte) { pte_val(pte) |= SRMMU_REF; 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 srmmu_mk_pte(unsigned long page, pgprot_t pgprot)
+{ pte_t pte; pte_val(pte) = ((srmmu_v2p(page)) >> 4) | pgprot_val(pgprot); return pte; }
+
+static pte_t srmmu_mk_pte_phys(unsigned long page, pgprot_t pgprot)
+{ pte_t pte; pte_val(pte) = ((page) >> 4) | pgprot_val(pgprot); return pte; }
+
+static pte_t srmmu_mk_pte_io(unsigned long page, pgprot_t pgprot, int space)
+{
+ pte_t pte;
+ pte_val(pte) = ((page) >> 4) | (space << 28) | pgprot_val(pgprot);
+ return pte;
+}
+
+static void srmmu_ctxd_set(ctxd_t *ctxp, pgd_t *pgdp)
+{
+ set_pte((pte_t *)ctxp, (SRMMU_ET_PTD | (srmmu_v2p((unsigned long) pgdp) >> 4)));
+}
+
+static void srmmu_pgd_set(pgd_t * pgdp, pmd_t * pmdp)
+{
+ set_pte((pte_t *)pgdp, (SRMMU_ET_PTD | (srmmu_v2p((unsigned long) pmdp) >> 4)));
+}
+
+static void srmmu_pmd_set(pmd_t * pmdp, pte_t * ptep)
+{
+ set_pte((pte_t *)pmdp, (SRMMU_ET_PTD | (srmmu_v2p((unsigned long) ptep) >> 4)));
+}
+
+static pte_t srmmu_pte_modify(pte_t pte, pgprot_t newprot)
+{
+ pte_val(pte) = (pte_val(pte) & SRMMU_CHG_MASK) | pgprot_val(newprot);
+ return pte;
+}
+
+/* to find an entry in a top-level page table... */
+static pgd_t *srmmu_pgd_offset(struct mm_struct * mm, unsigned long address)
+{
+ return mm->pgd + ((address >> SRMMU_PGDIR_SHIFT) & (SRMMU_PTRS_PER_PGD - 1));
+}
+
+/* Find an entry in the second-level page table.. */
+static pmd_t *srmmu_pmd_offset(pgd_t * dir, unsigned long address)
+{
+ return (pmd_t *) srmmu_pgd_page(*dir) + ((address >> SRMMU_PMD_SHIFT) & (SRMMU_PTRS_PER_PMD - 1));
+}
+
+/* Find an entry in the third-level page table.. */
+static pte_t *srmmu_pte_offset(pmd_t * dir, unsigned long address)
+{
+ return (pte_t *) srmmu_pmd_page(*dir) + ((address >> PAGE_SHIFT) & (SRMMU_PTRS_PER_PTE - 1));
+}
+
+/* This must update the context table entry for this process. */
+static void srmmu_update_rootmmu_dir(struct task_struct *tsk, pgd_t *pgdp)
+{
+ if(tsk->mm->context != NO_CONTEXT) {
+ flush_cache_mm(current->mm);
+ ctxd_set(&srmmu_context_table[tsk->mm->context], pgdp);
+ flush_tlb_mm(current->mm);
+ }
+}
+
+static inline void srmmu_uncache_page(unsigned long addr)
+{
+ pgd_t *pgdp = srmmu_pgd_offset(init_task.mm, addr);
+ pmd_t *pmdp;
+ pte_t *ptep;
+
+ if((pgd_val(*pgdp) & SRMMU_ET_MASK) == SRMMU_ET_PTE) {
+ ptep = (pte_t *) pgdp;
+ } else {
+ pmdp = srmmu_pmd_offset(pgdp, addr);
+ if((pmd_val(*pmdp) & SRMMU_ET_MASK) == SRMMU_ET_PTE) {
+ ptep = (pte_t *) pmdp;
+ } else {
+ ptep = srmmu_pte_offset(pmdp, addr);
+ }
+ }
+
+ flush_cache_page_to_uncache(addr);
+ set_pte(ptep, __pte((pte_val(*ptep) & ~SRMMU_CACHE)));
+ flush_tlb_page_for_cbit(addr);
+}
+
+static inline void srmmu_recache_page(unsigned long addr)
+{
+ pgd_t *pgdp = srmmu_pgd_offset(init_task.mm, addr);
+ pmd_t *pmdp;
+ pte_t *ptep;
+
+ if((pgd_val(*pgdp) & SRMMU_ET_MASK) == SRMMU_ET_PTE) {
+ ptep = (pte_t *) pgdp;
+ } else {
+ pmdp = srmmu_pmd_offset(pgdp, addr);
+ if((pmd_val(*pmdp) & SRMMU_ET_MASK) == SRMMU_ET_PTE) {
+ ptep = (pte_t *) pmdp;
+ } else {
+ ptep = srmmu_pte_offset(pmdp, addr);
+ }
+ }
+ set_pte(ptep, __pte((pte_val(*ptep) | SRMMU_CACHE)));
+ flush_tlb_page_for_cbit(addr);
+}
+
+static inline unsigned long srmmu_getpage(void)
+{
+ unsigned long page = get_free_page(GFP_KERNEL);
+
+ return page;
+}
+
+static inline void srmmu_putpage(unsigned long page)
+{
+ free_page(page);
+}
+
+#ifdef USE_CHUNK_ALLOC
+
+#define LC_HIGH_WATER 128
+#define BC_HIGH_WATER 32
+
+static unsigned long *lcnks = 0;
+static unsigned long *bcnks = 0;
+static int lcwater = 0;
+static int bcwater = 0;
+static int chunk_pages = 0;
+static int clct_pages = 0;
+
+#define RELAX_JIFFIES 16
+
+static int lcjiffies;
+static int bcjiffies;
+
+struct chunk {
+ struct chunk *next;
+ struct chunk *prev;
+ struct chunk *npage;
+ struct chunk *ppage;
+ int count;
+};
+
+static int garbage_calls = 0;
+
+#define OTHER_PAGE(p,q) (((unsigned long)(p) ^ (unsigned long)(q)) & PAGE_MASK)
+
+static inline int garbage_collect(unsigned long **cnks, int n, int cpp)
+{
+ struct chunk *root = (struct chunk *)*cnks;
+ struct chunk *p, *q, *curr, *next;
+ int water = n;
+
+ next = root->next;
+ curr = root->prev = root->next = root->npage = root->ppage = root;
+ root->count = 1;
+
+ garbage_calls++;
+
+ while (--n) {
+ p = next;
+ next = next->next;
+
+ if (OTHER_PAGE(p, curr)) {
+
+ q = curr->npage;
+ while (q != curr) {
+ if (!OTHER_PAGE(p, q))
+ break;
+ q = q->npage;
+ }
+
+ if (q == curr) {
+
+ (p->npage = curr->npage)->ppage = p;
+ curr->npage = p;
+ p->ppage = curr;
+
+ p->next = p->prev = p;
+ p->count = 1;
+
+ curr = p;
+
+ continue;
+ }
+ curr = q;
+ }
+
+ (p->next = curr->next)->prev = p;
+ curr->next = p;
+ p->prev = curr;
+
+ if (++curr->count == cpp) {
+
+ q = curr->npage;
+ if (curr == q) {
+
+ srmmu_putpage((unsigned long)curr & PAGE_MASK);
+ water -= cpp;
+
+ clct_pages++;
+ chunk_pages--;
+
+ if (--n) {
+ p = next;
+ next = next->next;
+
+ curr = root->prev =
+ root->next = root->npage =
+ root->ppage = root = p;
+ root->count = 1;
+
+ continue;
+ }
+ return 0;
+ }
+
+ if (curr == root)
+ root = q;
+
+ curr->ppage->npage = q;
+ q->ppage = curr->ppage;
+
+ srmmu_putpage((unsigned long)curr & PAGE_MASK);
+ water -= cpp;
+
+ clct_pages++;
+ chunk_pages--;
+
+ curr = q;
+ }
+ }
+
+ p = root;
+ while (p->npage != root) {
+ p->prev->next = p->npage;
+ p = p->npage;
+ }
+
+ *cnks = (unsigned long *)root;
+ return water;
+}
+
+
+static inline unsigned long *get_small_chunk(void)
+{
+ unsigned long *rval;
+ unsigned long flags;
+
+ save_and_cli(flags);
+ if(lcwater) {
+ lcwater--;
+ rval = lcnks;
+ lcnks = (unsigned long *) *rval;
+ } else {
+ rval = (unsigned long *) __get_free_page(GFP_KERNEL);
+
+ if(!rval) {
+ restore_flags(flags);
+ return 0;
+ }
+ chunk_pages++;
+
+ lcnks = (rval + 64);
+
+ /* Cache stomping, I know... */
+ *(rval + 64) = (unsigned long) (rval + 128);
+ *(rval + 128) = (unsigned long) (rval + 192);
+ *(rval + 192) = (unsigned long) (rval + 256);
+ *(rval + 256) = (unsigned long) (rval + 320);
+ *(rval + 320) = (unsigned long) (rval + 384);
+ *(rval + 384) = (unsigned long) (rval + 448);
+ *(rval + 448) = (unsigned long) (rval + 512);
+ *(rval + 512) = (unsigned long) (rval + 576);
+ *(rval + 576) = (unsigned long) (rval + 640);
+ *(rval + 640) = (unsigned long) (rval + 704);
+ *(rval + 704) = (unsigned long) (rval + 768);
+ *(rval + 768) = (unsigned long) (rval + 832);
+ *(rval + 832) = (unsigned long) (rval + 896);
+ *(rval + 896) = (unsigned long) (rval + 960);
+ *(rval + 960) = 0;
+ lcwater = 15;
+ }
+ lcjiffies = jiffies;
+ restore_flags(flags);
+ memset(rval, 0, 256);
+ return rval;
+}
+
+static inline void free_small_chunk(unsigned long *it)
+{
+ unsigned long flags;
+
+ save_and_cli(flags);
+ *it = (unsigned long) lcnks;
+ lcnks = it;
+ lcwater++;
+
+ if ((lcwater > LC_HIGH_WATER) &&
+ (jiffies > lcjiffies + RELAX_JIFFIES))
+ lcwater = garbage_collect(&lcnks, lcwater, 16);
+
+ restore_flags(flags);
+}
+
+static inline unsigned long *get_big_chunk(void)
+{
+ unsigned long *rval;
+ unsigned long flags;
+
+ save_and_cli(flags);
+ if(bcwater) {
+ bcwater--;
+ rval = bcnks;
+ bcnks = (unsigned long *) *rval;
+ } else {
+ rval = (unsigned long *) __get_free_page(GFP_KERNEL);
+
+ if(!rval) {
+ restore_flags(flags);
+ return 0;
+ }
+ chunk_pages++;
+
+ bcnks = (rval + 256);
+
+ /* Cache stomping, I know... */
+ *(rval + 256) = (unsigned long) (rval + 512);
+ *(rval + 512) = (unsigned long) (rval + 768);
+ *(rval + 768) = 0;
+ bcwater = 3;
+ }
+ bcjiffies = jiffies;
+ restore_flags(flags);
+ memset(rval, 0, 1024);
+ return rval;
+}
+
+static inline void free_big_chunk(unsigned long *it)
+{
+ unsigned long flags;
+
+ save_and_cli(flags);
+ *it = (unsigned long) bcnks;
+ bcnks = it;
+ bcwater++;
+
+ if ((bcwater > BC_HIGH_WATER) &&
+ (jiffies > bcjiffies + RELAX_JIFFIES))
+ bcwater = garbage_collect(&bcnks, bcwater, 4);
+
+ restore_flags(flags);
+}
+
+#define NEW_PGD() (pgd_t *) get_big_chunk()
+#define NEW_PMD() (pmd_t *) get_small_chunk()
+#define NEW_PTE() (pte_t *) get_small_chunk()
+#define FREE_PGD(chunk) free_big_chunk((unsigned long *)(chunk))
+#define FREE_PMD(chunk) free_small_chunk((unsigned long *)(chunk))
+#define FREE_PTE(chunk) free_small_chunk((unsigned long *)(chunk))
+
+#else
+
+/* The easy versions. */
+#define NEW_PGD() (pgd_t *) srmmu_getpage()
+#define NEW_PMD() (pmd_t *) srmmu_getpage()
+#define NEW_PTE() (pte_t *) srmmu_getpage()
+#define FREE_PGD(chunk) srmmu_putpage((unsigned long)(chunk))
+#define FREE_PMD(chunk) srmmu_putpage((unsigned long)(chunk))
+#define FREE_PTE(chunk) srmmu_putpage((unsigned long)(chunk))
+
+#endif
+
+/*
+ * Allocate and free page tables. The xxx_kernel() versions are
+ * used to allocate a kernel page table - this turns on ASN bits
+ * if any, and marks the page tables reserved.
+ */
+static void srmmu_pte_free_kernel(pte_t *pte)
+{
+ FREE_PTE(pte);
+}
+
+static pte_t *srmmu_pte_alloc_kernel(pmd_t *pmd, unsigned long address)
+{
+ address = (address >> PAGE_SHIFT) & (SRMMU_PTRS_PER_PTE - 1);
+ if(srmmu_pmd_none(*pmd)) {
+ pte_t *page = NEW_PTE();
+ if(srmmu_pmd_none(*pmd)) {
+ if(page) {
+ pmd_set(pmd, page);
+ return page + address;
+ }
+ pmd_set(pmd, BAD_PAGETABLE);
+ return NULL;
+ }
+ FREE_PTE(page);
+ }
+ if(srmmu_pmd_bad(*pmd)) {
+ printk("Bad pmd in pte_alloc: %08lx\n", pmd_val(*pmd));
+ pmd_set(pmd, BAD_PAGETABLE);
+ return NULL;
+ }
+ return (pte_t *) srmmu_pmd_page(*pmd) + address;
+}
+
+static void srmmu_pmd_free_kernel(pmd_t *pmd)
+{
+ FREE_PMD(pmd);
+}
+
+static pmd_t *srmmu_pmd_alloc_kernel(pgd_t *pgd, unsigned long address)
+{
+ address = (address >> SRMMU_PMD_SHIFT) & (SRMMU_PTRS_PER_PMD - 1);
+ if(srmmu_pgd_none(*pgd)) {
+ pmd_t *page;
+ page = NEW_PMD();
+ if(srmmu_pgd_none(*pgd)) {
+ if(page) {
+ pgd_set(pgd, page);
+ return page + address;
+ }
+ pgd_set(pgd, (pmd_t *) BAD_PAGETABLE);
+ return NULL;
+ }
+ FREE_PMD(page);
+ }
+ if(srmmu_pgd_bad(*pgd)) {
+ printk("Bad pgd in pmd_alloc: %08lx\n", pgd_val(*pgd));
+ pgd_set(pgd, (pmd_t *) BAD_PAGETABLE);
+ return NULL;
+ }
+ return (pmd_t *) pgd_page(*pgd) + address;
+}
+
+static void srmmu_pte_free(pte_t *pte)
+{
+ FREE_PTE(pte);
+}
+
+static pte_t *srmmu_pte_alloc(pmd_t * pmd, unsigned long address)
+{
+ address = (address >> PAGE_SHIFT) & (SRMMU_PTRS_PER_PTE - 1);
+ if(srmmu_pmd_none(*pmd)) {
+ pte_t *page = NEW_PTE();
+ if(srmmu_pmd_none(*pmd)) {
+ if(page) {
+ pmd_set(pmd, page);
+ return page + address;
+ }
+ pmd_set(pmd, BAD_PAGETABLE);
+ return NULL;
+ }
+ FREE_PTE(page);
+ }
+ if(srmmu_pmd_bad(*pmd)) {
+ printk("Bad pmd in pte_alloc: %08lx\n", pmd_val(*pmd));
+ pmd_set(pmd, BAD_PAGETABLE);
+ return NULL;
+ }
+ return ((pte_t *) srmmu_pmd_page(*pmd)) + address;
+}
+
+/* Real three-level page tables on SRMMU. */
+static void srmmu_pmd_free(pmd_t * pmd)
+{
+ FREE_PMD(pmd);
+}
+
+static pmd_t *srmmu_pmd_alloc(pgd_t * pgd, unsigned long address)
+{
+ address = (address >> SRMMU_PMD_SHIFT) & (SRMMU_PTRS_PER_PMD - 1);
+ if(srmmu_pgd_none(*pgd)) {
+ pmd_t *page = NEW_PMD();
+ if(srmmu_pgd_none(*pgd)) {
+ if(page) {
+ pgd_set(pgd, page);
+ return page + address;
+ }
+ pgd_set(pgd, (pmd_t *) BAD_PAGETABLE);
+ return NULL;
+ }
+ FREE_PMD(page);
+ }
+ if(srmmu_pgd_bad(*pgd)) {
+ printk("Bad pgd in pmd_alloc: %08lx\n", pgd_val(*pgd));
+ pgd_set(pgd, (pmd_t *) BAD_PAGETABLE);
+ return NULL;
+ }
+ return (pmd_t *) srmmu_pgd_page(*pgd) + address;
+}
+
+static void srmmu_pgd_free(pgd_t *pgd)
+{
+ FREE_PGD(pgd);
+}
+
+static pgd_t *srmmu_pgd_alloc(void)
+{
+ return NEW_PGD();
+}
+
+static void srmmu_set_pte_cacheable(pte_t *ptep, pte_t pteval)
+{
+ srmmu_set_entry(ptep, pte_val(pteval));
+}
+
+static void srmmu_set_pte_nocache_hyper(pte_t *ptep, pte_t pteval)
+{
+ unsigned long flags;
+
+ save_and_cli(flags);
+ srmmu_set_entry(ptep, pte_val(pteval));
+ hyper_flush_cache_page(((unsigned long)ptep) & PAGE_MASK);
+ restore_flags(flags);
+}
+
+static void srmmu_set_pte_nocache_cypress(pte_t *ptep, pte_t pteval)
+{
+ register unsigned long a, b, c, d, e, f, g;
+ unsigned long line, page;
+
+ srmmu_set_entry(ptep, pte_val(pteval));
+ page = ((unsigned long)ptep) & PAGE_MASK;
+ line = (page + PAGE_SIZE) - 0x100;
+ a = 0x20; b = 0x40; c = 0x60; d = 0x80; e = 0xa0; f = 0xc0; g = 0xe0;
+ 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);
+}
+
+static void srmmu_set_pte_nocache_nomxccvik(pte_t *ptep, pte_t pteval)
+{
+ unsigned long paddr = srmmu_v2p(((unsigned long)ptep));
+ unsigned long vaddr;
+ int set;
+ int i;
+
+ set = (paddr >> 5) & 0x7f;
+ vaddr = (KERNBASE + PAGE_SIZE) | (set << 5);
+ srmmu_set_entry(ptep, pteval);
+ for (i = 0; i < 8; i++) {
+ __asm__ __volatile__ ("ld [%0], %%g0" : : "r" (vaddr));
+ vaddr += PAGE_SIZE;
+ }
+}
+
+static void srmmu_quick_kernel_fault(unsigned long address)
+{
+#ifdef __SMP__
+ printk("CPU[%d]: Kernel faults at addr=0x%08lx\n",
+ smp_processor_id(), address);
+ while (1) ;
+#else
+ printk("Kernel faults at addr=0x%08lx\n", address);
+ printk("PTE=%08lx\n", srmmu_hwprobe((address & PAGE_MASK)));
+ die_if_kernel("SRMMU bolixed...", current->tss.kregs);
+#endif
+}
+
+static inline void alloc_context(struct task_struct *tsk)
+{
+ struct mm_struct *mm = tsk->mm;
+ struct ctx_list *ctxp;
+
+#if CONFIG_AP1000
+ if (tsk->taskid >= MPP_TASK_BASE) {
+ mm->context = MPP_CONTEXT_BASE + (tsk->taskid - MPP_TASK_BASE);
+ return;
+ }
+#endif
+
+ 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 == current->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;
+
+#if CONFIG_AP1000
+ if (context >= MPP_CONTEXT_BASE)
+ return; /* nothing to do! */
+#endif
+
+ ctx_old = ctx_list_pool + context;
+ remove_from_ctx_list(ctx_old);
+ add_to_free_ctxlist(ctx_old);
+}
+
+
+static void srmmu_switch_to_context(struct task_struct *tsk)
+{
+ if(tsk->mm->context == NO_CONTEXT) {
+ alloc_context(tsk);
+ flush_cache_mm(current->mm);
+ ctxd_set(&srmmu_context_table[tsk->mm->context], tsk->mm->pgd);
+ flush_tlb_mm(current->mm);
+ }
+ srmmu_set_context(tsk->mm->context);
+}
+
+/* Low level IO area allocation on the SRMMU. */
+void srmmu_mapioaddr(unsigned long physaddr, unsigned long virt_addr, int bus_type, int rdonly)
+{
+ pgd_t *pgdp;
+ pmd_t *pmdp;
+ pte_t *ptep;
+ unsigned long tmp;
+
+ physaddr &= PAGE_MASK;
+ pgdp = srmmu_pgd_offset(init_task.mm, 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);
+ if(rdonly)
+ tmp |= SRMMU_PRIV_RDONLY;
+ else
+ tmp |= SRMMU_PRIV;
+ flush_page_to_ram(virt_addr);
+ set_pte(ptep, tmp);
+ flush_tlb_all();
+}
+
+void srmmu_unmapioaddr(unsigned long virt_addr)
+{
+ pgd_t *pgdp;
+ pmd_t *pmdp;
+ pte_t *ptep;
+
+ pgdp = srmmu_pgd_offset(init_task.mm, virt_addr);
+ pmdp = srmmu_pmd_offset(pgdp, virt_addr);
+ ptep = srmmu_pte_offset(pmdp, virt_addr);
+
+ /* No need to flush uncacheable page. */
+ set_pte(ptep, pte_val(srmmu_mk_pte((unsigned long) EMPTY_PGE, PAGE_SHARED)));
+ flush_tlb_all();
+}
+
+static char *srmmu_lockarea(char *vaddr, unsigned long len)
+{
+ return vaddr;
+}
+
+static void srmmu_unlockarea(char *vaddr, unsigned long len)
+{
+}
+
+/* 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 task_struct *srmmu_alloc_task_struct(void)
+{
+ return (struct task_struct *) kmalloc(sizeof(struct task_struct), GFP_KERNEL);
+}
+
+unsigned long srmmu_alloc_kernel_stack(struct task_struct *tsk)
+{
+ return __get_free_pages(GFP_KERNEL, 1, 0);
+}
+
+static void srmmu_free_task_struct(struct task_struct *tsk)
+{
+ kfree(tsk);
+}
+
+static void srmmu_free_kernel_stack(unsigned long stack)
+{
+ free_pages(stack, 1);
+}
+
+/* Tsunami flushes. It's page level tlb invalidation is not very
+ * useful at all, you must be in the context that page exists in to
+ * get a match.
+ */
+static void tsunami_flush_cache_all(void)
+{
+ flush_user_windows();
+ tsunami_flush_icache();
+ tsunami_flush_dcache();
+}
+
+static void tsunami_flush_cache_mm(struct mm_struct *mm)
+{
+#ifndef __SMP__
+ if(mm->context != NO_CONTEXT) {
+#endif
+ flush_user_windows();
+ tsunami_flush_icache();
+ tsunami_flush_dcache();
+#ifndef __SMP__
+ }
+#endif
+}
+
+static void tsunami_flush_cache_range(struct mm_struct *mm, unsigned long start, unsigned long end)
+{
+#ifndef __SMP__
+ if(mm->context != NO_CONTEXT) {
+#endif
+ flush_user_windows();
+ tsunami_flush_icache();
+ tsunami_flush_dcache();
+#ifndef __SMP__
+ }
+#endif
+}
+
+static void tsunami_flush_cache_page(struct vm_area_struct *vma, unsigned long page)
+{
+#ifndef __SMP__
+ struct mm_struct *mm = vma->vm_mm;
+ if(mm->context != NO_CONTEXT) {
+#endif
+ flush_user_windows();
+ tsunami_flush_icache();
+ tsunami_flush_dcache();
+#ifndef __SMP__
+ }
+#endif
+}
+
+static void tsunami_flush_cache_page_to_uncache(unsigned long page)
+{
+ tsunami_flush_dcache();
+}
+
+/* Tsunami does not have a Copy-back style virtual cache. */
+static void tsunami_flush_page_to_ram(unsigned long page)
+{
+}
+
+/* However, Tsunami is not IO coherent. */
+static void tsunami_flush_page_for_dma(unsigned long page)
+{
+ tsunami_flush_icache();
+ tsunami_flush_dcache();
+}
+
+static void tsunami_flush_tlb_all(void)
+{
+ module_stats.invall++;
+ srmmu_flush_whole_tlb();
+}
+
+static void tsunami_flush_tlb_mm(struct mm_struct *mm)
+{
+ module_stats.invmm++;
+#ifndef __SMP__
+ if(mm->context != NO_CONTEXT) {
+#endif
+ srmmu_flush_whole_tlb();
+#ifndef __SMP__
+ }
+#endif
+}
+
+static void tsunami_flush_tlb_range(struct mm_struct *mm, unsigned long start, unsigned long end)
+{
+ module_stats.invrnge++;
+#ifndef __SMP__
+ if(mm->context != NO_CONTEXT) {
+#endif
+ srmmu_flush_whole_tlb();
+#ifndef __SMP__
+ }
+#endif
+}
+
+static void tsunami_flush_tlb_page(struct vm_area_struct *vma, unsigned long page)
+{
+ int octx;
+ struct mm_struct *mm = vma->vm_mm;
+
+#ifndef __SMP__
+ if(mm->context != NO_CONTEXT) {
+#endif
+ unsigned long flags;
+
+ save_and_cli(flags);
+ octx = srmmu_get_context();
+
+ srmmu_set_context(mm->context);
+ srmmu_flush_tlb_page(page);
+ srmmu_set_context(octx);
+ restore_flags(flags);
+#ifndef __SMP__
+ }
+#endif
+ module_stats.invpg++;
+}
+
+static void tsunami_flush_tlb_page_for_cbit(unsigned long page)
+{
+ srmmu_flush_tlb_page(page);
+}
+
+/* Swift flushes. It has the recommended SRMMU specification flushing
+ * facilities, so we can do things in a more fine grained fashion than we
+ * could on the tsunami. Let's watch out for HARDWARE BUGS...
+ */
+
+static void swift_flush_cache_all(void)
+{
+ flush_user_windows();
+ swift_idflash_clear();
+}
+
+static void swift_flush_cache_mm(struct mm_struct *mm)
+{
+#ifndef __SMP__
+ if(mm->context != NO_CONTEXT) {
+#endif
+ flush_user_windows();
+ swift_idflash_clear();
+#ifndef __SMP__
+ }
+#endif
+}
+
+static void swift_flush_cache_range(struct mm_struct *mm, unsigned long start, unsigned long end)
+{
+#ifndef __SMP__
+ if(mm->context != NO_CONTEXT) {
+#endif
+ flush_user_windows();
+ swift_idflash_clear();
+#ifndef __SMP__
+ }
+#endif
+}
+
+static void swift_flush_cache_page(struct vm_area_struct *vma, unsigned long page)
+{
+#ifndef __SMP__
+ struct mm_struct *mm = vma->vm_mm;
+ if(mm->context != NO_CONTEXT) {
+#endif
+ flush_user_windows();
+ if(vma->vm_flags & VM_EXEC)
+ swift_flush_icache();
+ swift_flush_dcache();
+#ifndef __SMP__
+ }
+#endif
+}
+
+/* Not copy-back on swift. */
+static void swift_flush_page_to_ram(unsigned long page)
+{
+}
+
+/* But not IO coherent either. */
+static void swift_flush_page_for_dma(unsigned long page)
+{
+ swift_flush_dcache();
+}
+
+static void swift_flush_cache_page_to_uncache(unsigned long page)
+{
+ swift_flush_dcache();
+}
+
+static void swift_flush_tlb_all(void)
+{
+ module_stats.invall++;
+ srmmu_flush_whole_tlb();
+}
+
+static void swift_flush_tlb_mm(struct mm_struct *mm)
+{
+ module_stats.invmm++;
+#ifndef __SMP__
+ if(mm->context != NO_CONTEXT)
+#endif
+ srmmu_flush_whole_tlb();
+}
+
+static void swift_flush_tlb_range(struct mm_struct *mm, unsigned long start, unsigned long end)
+{
+ module_stats.invrnge++;
+#ifndef __SMP__
+ if(mm->context != NO_CONTEXT)
+#endif
+ srmmu_flush_whole_tlb();
+}
+
+static void swift_flush_tlb_page(struct vm_area_struct *vma, unsigned long page)
+{
+#ifndef __SMP__
+ struct mm_struct *mm = vma->vm_mm;
+ if(mm->context != NO_CONTEXT)
+#endif
+ srmmu_flush_whole_tlb();
+ module_stats.invpg++;
+}
+
+static void swift_flush_tlb_page_for_cbit(unsigned long page)
+{
+ srmmu_flush_whole_tlb();
+}
+
+/* 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.
+ */
+
+/* Viking flushes. For Sun's mainline MBUS processor it is pretty much
+ * a crappy mmu. The on-chip I&D caches only have full flushes, no fine
+ * grained cache invalidations. It only has these "flash clear" things
+ * just like the MicroSparcI. Added to this many revs of the chip are
+ * teaming with hardware buggery. Someday maybe we'll do direct
+ * diagnostic tag accesses for page level flushes as those should
+ * be painless and will increase performance due to the frequency of
+ * page level flushes. This is a must to _really_ flush the caches,
+ * crazy hardware ;-)
+ */
+
+static void viking_flush_cache_all(void)
+{
+}
+
+static void viking_flush_cache_mm(struct mm_struct *mm)
+{
+}
+
+static void viking_flush_cache_range(struct mm_struct *mm, unsigned long start, unsigned long end)
+{
+}
+
+static void viking_flush_cache_page(struct vm_area_struct *vma, unsigned long page)
+{
+}
+
+/* Non-mxcc vikings are copy-back but are pure-physical so no flushing. */
+static void viking_flush_page_to_ram(unsigned long page)
+{
+}
+
+static void viking_mxcc_flush_page(unsigned long page)
+{
+ unsigned long ppage = srmmu_v2p(page & PAGE_MASK);
+ unsigned long paddr0, paddr1;
+
+ if (ppage == 0xffffffffUL)
+ return;
+
+ paddr0 = 0x10; /* Set cacheable bit. */
+ paddr1 = ppage;
+
+ /* Read the page's data through the stream registers,
+ * and write it back to memory. This will issue
+ * coherent write invalidates to all other caches, thus
+ * should also be sufficient in an MP system.
+ */
+ __asm__ __volatile__ ("or %%g0, %0, %%g2\n\t"
+ "or %%g0, %1, %%g3\n"
+ "1:\n\t"
+ "stda %%g2, [%2] %5\n\t"
+ "stda %%g2, [%3] %5\n\t"
+ "add %%g3, %4, %%g3\n\t"
+ "btst 0xfff, %%g3\n\t"
+ "bne 1b\n\t"
+ "nop\n\t" : :
+ "r" (paddr0), "r" (paddr1),
+ "r" (MXCC_SRCSTREAM),
+ "r" (MXCC_DESSTREAM),
+ "r" (MXCC_STREAM_SIZE),
+ "i" (ASI_M_MXCC) : "g2", "g3");
+
+ /* This was handcoded after a look at the gcc output from
+ *
+ * do {
+ * mxcc_set_stream_src(paddr);
+ * mxcc_set_stream_dst(paddr);
+ * paddr[1] += MXCC_STREAM_SIZE;
+ * } while (paddr[1] & ~PAGE_MASK);
+ */
+}
+
+static void viking_no_mxcc_flush_page(unsigned long page)
+{
+ unsigned long ppage = srmmu_v2p(page & PAGE_MASK);
+ int set, block;
+ unsigned long ptag[2];
+ unsigned long vaddr;
+ int i;
+
+ if (ppage == 0xffffffffUL)
+ return;
+ ppage >>= 12;
+
+ for (set = 0; set < 128; set++) {
+ for (block = 0; block < 4; block++) {
+
+ viking_get_dcache_ptag(set, block, ptag);
+
+ if (ptag[1] != ppage)
+ continue;
+ if (!(ptag[0] & VIKING_PTAG_VALID))
+ continue;
+ if (!(ptag[0] & VIKING_PTAG_DIRTY))
+ continue;
+
+ /* There was a great cache from TI
+ * with comfort as much as vi,
+ * 4 pages to flush,
+ * 4 pages, no rush,
+ * since anything else makes him die.
+ */
+ vaddr = (KERNBASE + PAGE_SIZE) | (set << 5);
+ for (i = 0; i < 8; i++) {
+ __asm__ __volatile__ ("ld [%0], %%g2\n\t" : :
+ "r" (vaddr) : "g2");
+ vaddr += PAGE_SIZE;
+ }
+
+ /* Continue with next set. */
+ break;
+ }
+ }
+}
+
+/* Viking is IO cache coherent, but really only on MXCC. */
+static void viking_flush_page_for_dma(unsigned long page)
+{
+}
+
+static void viking_flush_tlb_all(void)
+{
+ module_stats.invall++;
+ flush_user_windows();
+ srmmu_flush_whole_tlb();
+}
+
+static void viking_flush_tlb_mm(struct mm_struct *mm)
+{
+ int octx;
+ module_stats.invmm++;
+
+#ifndef __SMP__
+ if(mm->context != NO_CONTEXT) {
+#endif
+ flush_user_windows();
+ octx = srmmu_get_context();
+ srmmu_set_context(mm->context);
+ srmmu_flush_tlb_ctx();
+ srmmu_set_context(octx);
+#ifndef __SMP__
+ }
+#endif
+}
+
+static void viking_flush_tlb_range(struct mm_struct *mm, unsigned long start, unsigned long end)
+{
+ int octx;
+ module_stats.invrnge++;
+
+#ifndef __SMP__
+ if(mm->context != NO_CONTEXT) {
+#endif
+ flush_user_windows();
+ octx = srmmu_get_context();
+ srmmu_set_context(mm->context);
+ if((start - end) < SRMMU_PMD_SIZE) {
+ start &= PAGE_MASK;
+ while(start < end) {
+ srmmu_flush_tlb_page(start);
+ start += PAGE_SIZE;
+ }
+ } else if((start - end) < SRMMU_PGDIR_SIZE) {
+ start &= SRMMU_PMD_MASK;
+ while(start < end) {
+ srmmu_flush_tlb_segment(start);
+ start += SRMMU_PMD_SIZE;
+ }
+ } else {
+ start &= SRMMU_PGDIR_MASK;
+ while(start < end) {
+ srmmu_flush_tlb_region(start);
+ start += SRMMU_PGDIR_SIZE;
+ }
+ }
+ srmmu_set_context(octx);
+#ifndef __SMP__
+ }
+#endif
+}
+
+static void viking_flush_tlb_page(struct vm_area_struct *vma, unsigned long page)
+{
+ int octx;
+ struct mm_struct *mm = vma->vm_mm;
+
+ module_stats.invpg++;
+#ifndef __SMP__
+ if(mm->context != NO_CONTEXT) {
+#endif
+ flush_user_windows();
+ octx = srmmu_get_context();
+ srmmu_set_context(mm->context);
+ srmmu_flush_tlb_page(page);
+ srmmu_set_context(octx);
+#ifndef __SMP__
+ }
+#endif
+}
+
+static void viking_flush_tlb_page_for_cbit(unsigned long page)
+{
+ srmmu_flush_tlb_page(page);
+}
+
+/* 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)
+{
+ unsigned long flags, faddr;
+ int octx;
+
+#ifndef __SMP__
+ if(mm->context != NO_CONTEXT) {
+#endif
+ register unsigned long a, b, c, d, e, f, g;
+ flush_user_windows();
+ save_and_cli(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);
+ restore_flags(flags);
+#ifndef __SMP__
+ }
+#endif
+}
+
+static void cypress_flush_cache_range(struct mm_struct *mm, unsigned long start, unsigned long end)
+{
+ unsigned long flags, faddr;
+ int octx;
+
+#ifndef __SMP__
+ if(mm->context != NO_CONTEXT) {
+#endif
+ register unsigned long a, b, c, d, e, f, g;
+ flush_user_windows();
+ save_and_cli(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_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_PMD_SIZE;
+ }
+ srmmu_set_context(octx);
+ restore_flags(flags);
+#ifndef __SMP__
+ }
+#endif
+}
+
+static void cypress_flush_cache_page(struct vm_area_struct *vma, unsigned long page)
+{
+ struct mm_struct *mm = vma->vm_mm;
+ unsigned long flags, line;
+ int octx;
+
+#ifndef __SMP__
+ if(mm->context != NO_CONTEXT) {
+#endif
+ register unsigned long a, b, c, d, e, f, g;
+ flush_user_windows();
+ save_and_cli(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);
+ restore_flags(flags);
+#ifndef __SMP__
+ }
+#endif
+}
+
+/* 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)
+{
+}
+
+static void cypress_flush_page_to_uncache(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);
+}
+
+static void cypress_flush_tlb_all(void)
+{
+ module_stats.invall++;
+ srmmu_flush_whole_tlb();
+}
+
+static void cypress_flush_tlb_mm(struct mm_struct *mm)
+{
+ int octx;
+
+ module_stats.invmm++;
+#ifndef __SMP__
+ if(mm->context != NO_CONTEXT) {
+#endif
+ octx = srmmu_get_context();
+ srmmu_set_context(mm->context);
+ srmmu_flush_tlb_ctx();
+ srmmu_set_context(octx);
+#ifndef __SMP__
+ }
+#endif
+}
+
+static void cypress_flush_tlb_range(struct mm_struct *mm, unsigned long start, unsigned long end)
+{
+ int octx;
+ module_stats.invrnge++;
+
+#ifndef __SMP__
+ if(mm->context != NO_CONTEXT) {
+#endif
+ flush_user_windows();
+ octx = srmmu_get_context();
+ srmmu_set_context(mm->context);
+ if((start - end) < SRMMU_PMD_SIZE) {
+ start &= PAGE_MASK;
+ while(start < end) {
+ srmmu_flush_tlb_page(start);
+ start += PAGE_SIZE;
+ }
+ } else if((start - end) < SRMMU_PGDIR_SIZE) {
+ start &= SRMMU_PMD_MASK;
+ while(start < end) {
+ srmmu_flush_tlb_segment(start);
+ start += SRMMU_PMD_SIZE;
+ }
+ } else {
+ start &= SRMMU_PGDIR_MASK;
+ while(start < end) {
+ srmmu_flush_tlb_region(start);
+ start += SRMMU_PGDIR_SIZE;
+ }
+ }
+ srmmu_set_context(octx);
+#ifndef __SMP__
+ }
+#endif
+}
+
+static void cypress_flush_tlb_page(struct vm_area_struct *vma, unsigned long page)
+{
+ int octx;
+ struct mm_struct *mm = vma->vm_mm;
+
+ module_stats.invpg++;
+#ifndef __SMP__
+ if(mm->context != NO_CONTEXT) {
+#endif
+ flush_user_windows();
+ octx = srmmu_get_context();
+ srmmu_set_context(mm->context);
+ srmmu_flush_tlb_page(page);
+ srmmu_set_context(octx);
+#ifndef __SMP__
+ }
+#endif
+}
+
+static void cypress_flush_tlb_page_for_cbit(unsigned long page)
+{
+ srmmu_flush_tlb_page(page);
+}
+
+/* Hypersparc flushes. Very nice chip... */
+static void hypersparc_flush_cache_all(void)
+{
+ flush_user_windows();
+ hyper_flush_unconditional_combined();
+ hyper_flush_whole_icache();
+}
+
+static void hypersparc_flush_cache_mm(struct mm_struct *mm)
+{
+#ifndef __SMP__
+ if(mm->context != NO_CONTEXT) {
+#endif
+ flush_user_windows();
+ hyper_flush_cache_user();
+ hyper_flush_whole_icache();
+#ifndef __SMP__
+ }
+#endif
+}
+
+/* Boy was my older implementation inefficient... */
+static void hypersparc_flush_cache_range(struct mm_struct *mm, unsigned long start, unsigned long end)
+{
+ volatile unsigned long clear;
+ int octx;
+
+#ifndef __SMP__
+ if(mm->context != NO_CONTEXT) {
+#endif
+ flush_user_windows();
+ octx = srmmu_get_context();
+ start &= PAGE_MASK;
+ srmmu_set_context(mm->context);
+ while(start < end) {
+ if(srmmu_hwprobe(start))
+ hyper_flush_cache_page(start);
+ start += PAGE_SIZE;
+ }
+ clear = srmmu_get_fstatus();
+ srmmu_set_context(octx);
+ hyper_flush_whole_icache();
+#ifndef __SMP__
+ }
+#endif
+}
+
+/* HyperSparc requires a valid mapping where we are about to flush
+ * in order to check for a physical tag match during the flush.
+ */
+static void hypersparc_flush_cache_page(struct vm_area_struct *vma, unsigned long page)
+{
+ struct mm_struct *mm = vma->vm_mm;
+ volatile unsigned long clear;
+ int octx;
+
+#ifndef __SMP__
+ if(mm->context != NO_CONTEXT) {
+#endif
+ octx = srmmu_get_context();
+ flush_user_windows();
+ srmmu_set_context(mm->context);
+ hyper_flush_whole_icache();
+ if(!srmmu_hwprobe(page))
+ goto no_mapping;
+ hyper_flush_cache_page(page);
+ no_mapping:
+ clear = srmmu_get_fstatus();
+ srmmu_set_context(octx);
+#ifndef __SMP__
+ }
+#endif
+}
+
+/* HyperSparc is copy-back. */
+static void hypersparc_flush_page_to_ram(unsigned long page)
+{
+ volatile unsigned long clear;
+
+ if(srmmu_hwprobe(page))
+ hyper_flush_cache_page(page);
+ clear = srmmu_get_fstatus();
+}
+
+/* HyperSparc is IO cache coherent. */
+static void hypersparc_flush_page_for_dma(unsigned long page)
+{
+}
+
+static void hypersparc_flush_cache_page_to_uncache(unsigned long page)
+{
+ volatile unsigned long clear;
+
+ if(srmmu_hwprobe(page))
+ hyper_flush_cache_page(page);
+ clear = srmmu_get_fstatus();
+}
+
+static void hypersparc_flush_tlb_all(void)
+{
+ module_stats.invall++;
+ srmmu_flush_whole_tlb();
+}
+
+static void hypersparc_flush_tlb_mm(struct mm_struct *mm)
+{
+ int octx;
+
+ module_stats.invmm++;
+#ifndef __SMP__
+ if(mm->context != NO_CONTEXT) {
+#endif
+
+ octx = srmmu_get_context();
+ srmmu_set_context(mm->context);
+ srmmu_flush_tlb_ctx();
+ srmmu_set_context(octx);
+
+#ifndef __SMP__
+ }
+#endif
+}
+
+static void hypersparc_flush_tlb_range(struct mm_struct *mm, unsigned long start, unsigned long end)
+{
+ int octx;
+
+ module_stats.invrnge++;
+#ifndef __SMP__
+ if(mm->context != NO_CONTEXT) {
+#endif
+
+ octx = srmmu_get_context();
+ srmmu_set_context(mm->context);
+ if((start - end) < SRMMU_PMD_SIZE) {
+ start &= PAGE_MASK;
+ while(start < end) {
+ srmmu_flush_tlb_page(start);
+ start += PAGE_SIZE;
+ }
+ } else if((start - end) < SRMMU_PGDIR_SIZE) {
+ start &= SRMMU_PMD_MASK;
+ while(start < end) {
+ srmmu_flush_tlb_segment(start);
+ start += SRMMU_PMD_SIZE;
+ }
+ } else {
+ start &= SRMMU_PGDIR_MASK;
+ while(start < end) {
+ srmmu_flush_tlb_region(start);
+ start += SRMMU_PGDIR_SIZE;
+ }
+ }
+ srmmu_set_context(octx);
+
+#ifndef __SMP__
+ }
+#endif
+}
+
+static void hypersparc_flush_tlb_page(struct vm_area_struct *vma, unsigned long page)
+{
+ struct mm_struct *mm = vma->vm_mm;
+ int octx;
+
+ module_stats.invpg++;
+#ifndef __SMP__
+ if(mm->context != NO_CONTEXT) {
+#endif
+
+ octx = srmmu_get_context();
+ srmmu_set_context(mm->context);
+ srmmu_flush_tlb_page(page);
+ srmmu_set_context(octx);
+
+#ifndef __SMP__
+ }
+#endif
+}
+
+static void hypersparc_flush_tlb_page_for_cbit(unsigned long page)
+{
+ srmmu_flush_tlb_page(page);
+}
+
+static void hypersparc_ctxd_set(ctxd_t *ctxp, pgd_t *pgdp)
+{
+ hyper_flush_whole_icache();
+ set_pte((pte_t *)ctxp, (SRMMU_ET_PTD | (srmmu_v2p((unsigned long) pgdp) >> 4)));
+}
+
+static void hypersparc_update_rootmmu_dir(struct task_struct *tsk, pgd_t *pgdp)
+{
+ if(tsk->mm->context != NO_CONTEXT) {
+ flush_cache_mm(current->mm);
+ ctxd_set(&srmmu_context_table[tsk->mm->context], pgdp);
+ flush_tlb_mm(current->mm);
+ }
+}
+
+static void hypersparc_switch_to_context(struct task_struct *tsk)
+{
+ hyper_flush_whole_icache();
+ if(tsk->mm->context == NO_CONTEXT) {
+ alloc_context(tsk);
+ flush_cache_mm(current->mm);
+ ctxd_set(&srmmu_context_table[tsk->mm->context], tsk->mm->pgd);
+ flush_tlb_mm(current->mm);
+ }
+ srmmu_set_context(tsk->mm->context);
+}
+
+/* IOMMU things go here. */
+
+#define LONG_ALIGN(x) (((x)+(sizeof(long))-1)&~((sizeof(long))-1))
+
+#define IOPERM (IOPTE_CACHE | IOPTE_WRITE | IOPTE_VALID)
+#define MKIOPTE(phys) (((((phys)>>4) & IOPTE_PAGE) | IOPERM) & ~IOPTE_WAZ)
+
+static inline void srmmu_map_dvma_pages_for_iommu(struct iommu_struct *iommu,
+ unsigned long kern_end)
+{
+ unsigned long first = page_offset;
+ unsigned long last = kern_end;
+ iopte_t *iopte = iommu->page_table;
+
+ iopte += ((first - iommu->start) >> PAGE_SHIFT);
+ while(first <= last) {
+ iopte_val(*iopte++) = MKIOPTE(srmmu_v2p(first));
+ first += PAGE_SIZE;
+ }
+}
+
+unsigned long iommu_init(int iommund, unsigned long memory_start,
+ unsigned long memory_end, struct linux_sbus *sbus)
+{
+ unsigned int impl, vers, ptsize;
+ unsigned long tmp;
+ struct iommu_struct *iommu;
+ struct linux_prom_registers iommu_promregs[PROMREG_MAX];
+
+ memory_start = LONG_ALIGN(memory_start);
+ iommu = (struct iommu_struct *) memory_start;
+ memory_start += sizeof(struct iommu_struct);
+ prom_getproperty(iommund, "reg", (void *) iommu_promregs,
+ sizeof(iommu_promregs));
+ iommu->regs = (struct iommu_regs *)
+ sparc_alloc_io(iommu_promregs[0].phys_addr, 0, (PAGE_SIZE * 3),
+ "IOMMU registers", iommu_promregs[0].which_io, 0x0);
+ if(!iommu->regs)
+ panic("Cannot map IOMMU registers.");
+ impl = (iommu->regs->control & IOMMU_CTRL_IMPL) >> 28;
+ vers = (iommu->regs->control & IOMMU_CTRL_VERS) >> 24;
+ tmp = iommu->regs->control;
+ tmp &= ~(IOMMU_CTRL_RNGE);
+ switch(page_offset & 0xf0000000) {
+ case 0xf0000000:
+ tmp |= (IOMMU_RNGE_256MB | IOMMU_CTRL_ENAB);
+ iommu->plow = iommu->start = 0xf0000000;
+ break;
+ case 0xe0000000:
+ tmp |= (IOMMU_RNGE_512MB | IOMMU_CTRL_ENAB);
+ iommu->plow = iommu->start = 0xe0000000;
+ break;
+ case 0xd0000000:
+ case 0xc0000000:
+ tmp |= (IOMMU_RNGE_1GB | IOMMU_CTRL_ENAB);
+ iommu->plow = iommu->start = 0xc0000000;
+ break;
+ case 0xb0000000:
+ case 0xa0000000:
+ case 0x90000000:
+ case 0x80000000:
+ tmp |= (IOMMU_RNGE_2GB | IOMMU_CTRL_ENAB);
+ iommu->plow = iommu->start = 0x80000000;
+ break;
+ }
+ iommu->regs->control = tmp;
+ iommu_invalidate(iommu->regs);
+ iommu->end = 0xffffffff;
+
+ /* Allocate IOMMU page table */
+ ptsize = iommu->end - iommu->start + 1;
+ ptsize = (ptsize >> PAGE_SHIFT) * sizeof(iopte_t);
+
+ /* Stupid alignment constraints give me a headache. */
+ memory_start = PAGE_ALIGN(memory_start);
+ memory_start = (((memory_start) + (ptsize - 1)) & ~(ptsize - 1));
+ iommu->lowest = iommu->page_table = (iopte_t *) memory_start;
+ memory_start += ptsize;
+
+ /* Initialize new table. */
+ flush_cache_all();
+ if(viking_mxcc_present) {
+ unsigned long start = (unsigned long) iommu->page_table;
+ unsigned long end = (start + ptsize);
+ while(start < end) {
+ viking_mxcc_flush_page(start);
+ start += PAGE_SIZE;
+ }
+ } else if(flush_page_for_dma == viking_no_mxcc_flush_page) {
+ unsigned long start = (unsigned long) iommu->page_table;
+ unsigned long end = (start + ptsize);
+ while(start < end) {
+ viking_no_mxcc_flush_page(start);
+ start += PAGE_SIZE;
+ }
+ }
+ memset(iommu->page_table, 0, ptsize);
+ srmmu_map_dvma_pages_for_iommu(iommu, memory_end);
+ flush_tlb_all();
+ iommu->regs->base = srmmu_v2p((unsigned long) iommu->page_table) >> 4;
+ iommu_invalidate(iommu->regs);
+
+ sbus->iommu = iommu;
+ printk("IOMMU: impl %d vers %d page table at %p of size %d bytes\n",
+ impl, vers, iommu->page_table, ptsize);
+ return memory_start;
+}
+
+void iommu_sun4d_init(int sbi_node, struct linux_sbus *sbus)
+{
+ u32 *iommu;
+ struct linux_prom_registers iommu_promregs[PROMREG_MAX];
+
+ prom_getproperty(sbi_node, "reg", (void *) iommu_promregs,
+ sizeof(iommu_promregs));
+ iommu = (u32 *)
+ sparc_alloc_io(iommu_promregs[2].phys_addr, 0, (PAGE_SIZE * 16),
+ "XPT", iommu_promregs[2].which_io, 0x0);
+ if(!iommu)
+ panic("Cannot map External Page Table.");
+
+ /* Initialize new table. */
+ flush_cache_all();
+ if(viking_mxcc_present) {
+ unsigned long start = (unsigned long) iommu;
+ unsigned long end = (start + 16 * PAGE_SIZE);
+ while(start < end) {
+ viking_mxcc_flush_page(start);
+ start += PAGE_SIZE;
+ }
+ } else if(flush_page_for_dma == viking_no_mxcc_flush_page) {
+ unsigned long start = (unsigned long) iommu;
+ unsigned long end = (start + 16 * PAGE_SIZE);
+ while(start < end) {
+ viking_no_mxcc_flush_page(start);
+ start += PAGE_SIZE;
+ }
+ }
+ memset(iommu, 0, 16 * PAGE_SIZE);
+ flush_tlb_all();
+
+ sbus->iommu = (struct iommu_struct *)iommu;
+}
+
+static char *srmmu_get_scsi_one(char *vaddr, unsigned long len, struct linux_sbus *sbus)
+{
+ unsigned long page = ((unsigned long) vaddr) & PAGE_MASK;
+
+ while(page < ((unsigned long)(vaddr + len))) {
+ flush_page_for_dma(page);
+ page += PAGE_SIZE;
+ }
+ return vaddr;
+}
+
+static void srmmu_get_scsi_sgl(struct mmu_sglist *sg, int sz, struct linux_sbus *sbus)
+{
+ unsigned long page;
+
+ while(sz >= 0) {
+ page = ((unsigned long) sg[sz].addr) & PAGE_MASK;
+ while(page < (unsigned long)(sg[sz].addr + sg[sz].len)) {
+ flush_page_for_dma(page);
+ page += PAGE_SIZE;
+ }
+ sg[sz].dvma_addr = (char *) (sg[sz].addr);
+ sz--;
+ }
+}
+
+static void srmmu_release_scsi_one(char *vaddr, unsigned long len, struct linux_sbus *sbus)
+{
+}
+
+static void srmmu_release_scsi_sgl(struct mmu_sglist *sg, int sz, struct linux_sbus *sbus)
+{
+}
+
+static unsigned long mempool;
+
+/* 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.
+ */
+
+static unsigned long kbpage;
+
+/* Some dirty hacks to abstract away the painful boot up init. */
+static inline unsigned long srmmu_early_paddr(unsigned long vaddr)
+{
+ return ((vaddr - KERNBASE) + kbpage);
+}
+
+static inline void srmmu_early_pgd_set(pgd_t *pgdp, pmd_t *pmdp)
+{
+ set_pte((pte_t *)pgdp, (SRMMU_ET_PTD | (srmmu_early_paddr((unsigned long) pmdp) >> 4)));
+}
+
+static inline void srmmu_early_pmd_set(pmd_t *pmdp, pte_t *ptep)
+{
+ set_pte((pte_t *)pmdp, (SRMMU_ET_PTD | (srmmu_early_paddr((unsigned long) ptep) >> 4)));
+}
+
+static inline unsigned long srmmu_early_pgd_page(pgd_t pgd)
+{
+ return (((pgd_val(pgd) & SRMMU_PTD_PMASK) << 4) - kbpage) + KERNBASE;
+}
+
+static inline unsigned long srmmu_early_pmd_page(pmd_t pmd)
+{
+ return (((pmd_val(pmd) & SRMMU_PTD_PMASK) << 4) - kbpage) + KERNBASE;
+}
+
+static inline pmd_t *srmmu_early_pmd_offset(pgd_t *dir, unsigned long address)
+{
+ return (pmd_t *) srmmu_early_pgd_page(*dir) + ((address >> SRMMU_PMD_SHIFT) & (SRMMU_PTRS_PER_PMD - 1));
+}
+
+static inline pte_t *srmmu_early_pte_offset(pmd_t *dir, unsigned long address)
+{
+ return (pte_t *) srmmu_early_pmd_page(*dir) + ((address >> PAGE_SHIFT) & (SRMMU_PTRS_PER_PTE - 1));
+}
+
+static inline void srmmu_allocate_ptable_skeleton(unsigned long start, unsigned long end)
+{
+ pgd_t *pgdp;
+ pmd_t *pmdp;
+ pte_t *ptep;
+
+ while(start < end) {
+ pgdp = srmmu_pgd_offset(init_task.mm, start);
+ if(srmmu_pgd_none(*pgdp)) {
+ pmdp = sparc_init_alloc(&mempool, SRMMU_PMD_TABLE_SIZE);
+ srmmu_early_pgd_set(pgdp, pmdp);
+ }
+ pmdp = srmmu_early_pmd_offset(pgdp, start);
+ if(srmmu_pmd_none(*pmdp)) {
+ ptep = sparc_init_alloc(&mempool, SRMMU_PTE_TABLE_SIZE);
+ srmmu_early_pmd_set(pmdp, ptep);
+ }
+ start = (start + SRMMU_PMD_SIZE) & SRMMU_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 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_PMD_MASK))) {
+ if(srmmu_hwprobe((start-PAGE_SIZE) + SRMMU_PMD_SIZE) == prompte)
+ what = 1;
+ }
+
+ if(!(start & ~(SRMMU_PGDIR_MASK))) {
+ if(srmmu_hwprobe((start-PAGE_SIZE) + SRMMU_PGDIR_SIZE) ==
+ prompte)
+ what = 2;
+ }
+
+ pgdp = srmmu_pgd_offset(init_task.mm, start);
+ if(what == 2) {
+ pgd_val(*pgdp) = prompte;
+ start += SRMMU_PGDIR_SIZE;
+ continue;
+ }
+ if(srmmu_pgd_none(*pgdp)) {
+ pmdp = sparc_init_alloc(&mempool, SRMMU_PMD_TABLE_SIZE);
+ srmmu_early_pgd_set(pgdp, pmdp);
+ }
+ pmdp = srmmu_early_pmd_offset(pgdp, start);
+ if(what == 1) {
+ pmd_val(*pmdp) = prompte;
+ start += SRMMU_PMD_SIZE;
+ continue;
+ }
+ if(srmmu_pmd_none(*pmdp)) {
+ ptep = sparc_init_alloc(&mempool, SRMMU_PTE_TABLE_SIZE);
+ srmmu_early_pmd_set(pmdp, ptep);
+ }
+ ptep = srmmu_early_pte_offset(pmdp, start);
+ pte_val(*ptep) = prompte;
+ start += PAGE_SIZE;
+ }
+}
+
+static void srmmu_map_dma_area(unsigned long addr, int len)
+{
+ unsigned long page, end;
+ pgprot_t dvma_prot;
+ struct iommu_struct *iommu = SBus_chain->iommu;
+ iopte_t *iopte = iommu->page_table;
+ iopte_t *iopte_first = iopte;
+
+ if(viking_mxcc_present)
+ dvma_prot = __pgprot(SRMMU_CACHE | SRMMU_ET_PTE | SRMMU_PRIV);
+ else
+ dvma_prot = __pgprot(SRMMU_ET_PTE | SRMMU_PRIV);
+
+ iopte += ((addr - iommu->start) >> PAGE_SHIFT);
+ end = PAGE_ALIGN((addr + len));
+ while(addr < end) {
+ page = get_free_page(GFP_KERNEL);
+ if(!page) {
+ prom_printf("alloc_dvma: Cannot get a dvma page\n");
+ prom_halt();
+ } else {
+ pgd_t *pgdp;
+ pmd_t *pmdp;
+ pte_t *ptep;
+
+ pgdp = srmmu_pgd_offset(init_task.mm, addr);
+ pmdp = srmmu_pmd_offset(pgdp, addr);
+ ptep = srmmu_pte_offset(pmdp, addr);
+
+ set_pte(ptep, pte_val(srmmu_mk_pte(page, dvma_prot)));
+
+ iopte_val(*iopte++) = MKIOPTE(srmmu_v2p(page));
+ }
+ addr += PAGE_SIZE;
+ }
+ flush_cache_all();
+ if(viking_mxcc_present) {
+ unsigned long start = ((unsigned long) iopte_first) & PAGE_MASK;
+ unsigned long end = PAGE_ALIGN(((unsigned long) iopte));
+ while(start < end) {
+ viking_mxcc_flush_page(start);
+ start += PAGE_SIZE;
+ }
+ } else if(flush_page_for_dma == viking_no_mxcc_flush_page) {
+ unsigned long start = ((unsigned long) iopte_first) & PAGE_MASK;
+ unsigned long end = PAGE_ALIGN(((unsigned long) iopte));
+ while(start < end) {
+ viking_no_mxcc_flush_page(start);
+ start += PAGE_SIZE;
+ }
+ }
+ flush_tlb_all();
+ iommu_invalidate(iommu->regs);
+}
+
+/* #define DEBUG_MAP_KERNEL */
+
+#ifdef DEBUG_MAP_KERNEL
+#define MKTRACE(foo) prom_printf foo
+#else
+#define MKTRACE(foo)
+#endif
+
+static int lots_of_ram = 0;
+static int large_pte_optimize = 1;
+
+#define KERNEL_PTE(page_shifted) ((page_shifted)|SRMMU_CACHE|SRMMU_PRIV|SRMMU_VALID)
+
+/* Create a third-level SRMMU 16MB page mapping. */
+static inline void do_large_mapping(unsigned long vaddr, unsigned long phys_base)
+{
+ pgd_t *pgdp = srmmu_pgd_offset(init_task.mm, vaddr);
+ unsigned long big_pte;
+
+ MKTRACE(("dlm[v<%08lx>-->p<%08lx>]", vaddr, phys_base));
+ big_pte = KERNEL_PTE(phys_base >> 4);
+ pgd_val(*pgdp) = big_pte;
+}
+
+/* Create second-level SRMMU 256K medium sized page mappings. */
+static inline void do_medium_mapping(unsigned long vaddr, unsigned long vend,
+ unsigned long phys_base)
+{
+ pgd_t *pgdp;
+ pmd_t *pmdp;
+ unsigned long medium_pte;
+
+ MKTRACE(("dmm[v<%08lx,%08lx>-->p<%08lx>]", vaddr, vend, phys_base));
+ while(vaddr < vend) {
+ pgdp = srmmu_pgd_offset(init_task.mm, vaddr);
+ pmdp = srmmu_early_pmd_offset(pgdp, vaddr);
+ medium_pte = KERNEL_PTE(phys_base >> 4);
+ pmd_val(*pmdp) = medium_pte;
+ phys_base += SRMMU_PMD_SIZE;
+ vaddr += SRMMU_PMD_SIZE;
+ }
+}
+
+/* Create a normal set of SRMMU page mappings for the virtual range
+ * START to END, using physical pages beginning at PHYS_BASE.
+ */
+static inline void do_small_mapping(unsigned long start, unsigned long end,
+ unsigned long phys_base)
+{
+ pgd_t *pgdp;
+ pmd_t *pmdp;
+ pte_t *ptep;
+
+ MKTRACE(("dsm[v<%08lx,%08lx>-->p<%08lx>]", start, end, phys_base));
+ while(start < end) {
+ pgdp = srmmu_pgd_offset(init_task.mm, start);
+ pmdp = srmmu_early_pmd_offset(pgdp, start);
+ ptep = srmmu_early_pte_offset(pmdp, start);
+
+ pte_val(*ptep) = KERNEL_PTE(phys_base >> 4);
+ phys_base += PAGE_SIZE;
+ start += PAGE_SIZE;
+ }
+}
+
+/* Look in the sp_bank for the given physical page, return the
+ * index number the entry was found in, or -1 for not found.
+ */
+static inline int find_in_spbanks(unsigned long phys_page)
+{
+ int entry;
+
+ for(entry = 0; sp_banks[entry].num_bytes; entry++) {
+ unsigned long start = sp_banks[entry].base_addr;
+ unsigned long end = start + sp_banks[entry].num_bytes;
+
+ if((start <= phys_page) && (phys_page < end))
+ return entry;
+ }
+ return -1;
+}
+
+/* Find an spbank entry not mapped as of yet, TAKEN_VECTOR is an
+ * array of char's, each member indicating if that spbank is mapped
+ * yet or not.
+ */
+static inline int find_free_spbank(char *taken_vector)
+{
+ int entry;
+
+ for(entry = 0; sp_banks[entry].num_bytes; entry++)
+ if(!taken_vector[entry])
+ break;
+ return entry;
+}
+
+/* Same as above, but with a given bank size limit BLIMIT. */
+static inline int find_free_spbank_limited(char *taken_vector, unsigned long limit)
+{
+ int entry;
+
+ for(entry = 0; sp_banks[entry].num_bytes; entry++)
+ if(!taken_vector[entry] &&
+ (sp_banks[entry].num_bytes < limit))
+ break;
+ return entry;
+}
+
+/* Map sp_bank entry SP_ENTRY, starting at virtual address VBASE.
+ * This routine is expected to update the srmmu_map and try as
+ * hard as possible to use 16MB level-one SRMMU pte's when at all
+ * possible to get short termination and faster translations.
+ */
+static inline unsigned long map_spbank(unsigned long vbase, int sp_entry)
+{
+ unsigned long pstart = sp_banks[sp_entry].base_addr;
+ unsigned long vstart = vbase;
+ unsigned long vend = vbase + sp_banks[sp_entry].num_bytes;
+ static int srmmu_bank = 0;
+
+ /* If physically not aligned on 16MB boundry, just shortcut
+ * right here by mapping them with 4k normal pages, and bumping
+ * the next virtual address to the next 16MB boundry. You can
+ * get this with various RAM configurations due to the way in
+ * which the PROM carves out it's own chunks of memory.
+ */
+ if(pstart & ~SRMMU_PGDIR_MASK) {
+ do_small_mapping(vstart, vend, pstart);
+ vstart = SRMMU_PGDIR_ALIGN(vend);
+ goto finish_up;
+ }
+ while(vstart < vend) {
+ unsigned long coverage, next_aligned;
+ if(vstart & ~SRMMU_PMD_MASK) {
+ next_aligned = SRMMU_PMD_ALIGN(vstart);
+ if(next_aligned <= vend) {
+ coverage = (next_aligned - vstart);
+ do_small_mapping(vstart, next_aligned, pstart);
+ } else {
+ coverage = (vend - vstart);
+ do_small_mapping(vstart, vend, pstart);
+ }
+ } else if(vstart & ~SRMMU_PGDIR_MASK) {
+ next_aligned = SRMMU_PGDIR_ALIGN(vstart);
+ if(next_aligned <= vend) {
+ coverage = (next_aligned - vstart);
+ do_medium_mapping(vstart, next_aligned, pstart);
+ } else {
+ coverage = (vend - vstart);
+ do_small_mapping(vstart, vend, pstart);
+ }
+ } else {
+ coverage = SRMMU_PGDIR_SIZE;
+ if(large_pte_optimize || ((vstart+coverage)<=vend)) {
+ do_large_mapping(vstart, pstart);
+ } else {
+ coverage = (vend - vstart);
+ do_small_mapping(vstart, vend, pstart);
+ }
+ }
+ vstart += coverage; pstart += coverage;
+ }
+finish_up:
+ srmmu_map[srmmu_bank].vbase = vbase;
+ srmmu_map[srmmu_bank].pbase = sp_banks[sp_entry].base_addr;
+ srmmu_map[srmmu_bank].size = sp_banks[sp_entry].num_bytes;
+ MKTRACE(("SRMMUBANK[v<%08lx>p<%08lx>s<%08lx>]", vbase, sp_banks[sp_entry].base_addr, sp_banks[sp_entry].num_bytes));
+ srmmu_bank++;
+ return vstart;
+}
+
+static inline void memprobe_error(char *msg)
+{
+ prom_printf(msg);
+ prom_printf("Halting now...\n");
+ prom_halt();
+}
+
+/* Assumptions: The bank given to the kernel from the prom/bootloader
+ * is part of a full bank which is at least 4MB in size and begins at
+ * 0xf0000000 (ie. KERNBASE).
+ */
+static void map_kernel(void)
+{
+ unsigned long raw_pte, physpage;
+ unsigned long vaddr, tally, low_base;
+ char etaken[SPARC_PHYS_BANKS];
+ int entry;
+
+ /* Step 1: Clear out sp_banks taken map. */
+ MKTRACE(("map_kernel: clearing etaken vector... "));
+ for(entry = 0; entry < SPARC_PHYS_BANKS; entry++)
+ etaken[entry] = 0;
+
+ low_base = KERNBASE;
+
+ /* Step 2: Calculate 'lots_of_ram'. */
+ tally = 0;
+ for(entry = 0; sp_banks[entry].num_bytes; entry++)
+ tally += sp_banks[entry].num_bytes;
+ if(tally >= (0xfd000000 - KERNBASE))
+ lots_of_ram = 1;
+ else
+ lots_of_ram = 0;
+ MKTRACE(("tally=%08lx lots_of_ram<%d>\n", tally, lots_of_ram));
+
+ /* Step 3: Fill in KERNBASE base pgd. Lots of sanity checking here. */
+ raw_pte = srmmu_hwprobe(KERNBASE + PAGE_SIZE);
+ if((raw_pte & SRMMU_ET_MASK) != SRMMU_ET_PTE)
+ memprobe_error("Wheee, kernel not mapped at all by boot loader.\n");
+ physpage = (raw_pte & SRMMU_PTE_PMASK) << 4;
+ physpage -= PAGE_SIZE;
+ if(physpage & ~(SRMMU_PGDIR_MASK))
+ memprobe_error("Wheee, kernel not mapped on 16MB physical boundry.\n");
+ entry = find_in_spbanks(physpage);
+ if(entry == -1 || (sp_banks[entry].base_addr != physpage))
+ memprobe_error("Kernel mapped in non-existant memory.\n");
+ MKTRACE(("map_kernel: map_spbank(vbase=%08x, entry<%d>)[%08lx,%08lx]\n", KERNBASE, entry, sp_banks[entry].base_addr, sp_banks[entry].num_bytes));
+ if(((KERNBASE + (sp_banks[entry].num_bytes)) > 0xfd000000) ||
+ ((KERNBASE + (sp_banks[entry].num_bytes)) < KERNBASE)) {
+ unsigned long orig_base = sp_banks[entry].base_addr;
+ unsigned long orig_len = sp_banks[entry].num_bytes;
+ unsigned long can_map = (0xfd000000 - KERNBASE);
+
+ /* Map a partial bank in this case, adjust the base
+ * and the length, but don't mark it used.
+ */
+ sp_banks[entry].num_bytes = can_map;
+ MKTRACE(("wheee really big mapping [%08lx,%08lx]", orig_base, can_map));
+ vaddr = map_spbank(KERNBASE, entry);
+ MKTRACE(("vaddr now %08lx ", vaddr));
+ sp_banks[entry].base_addr = orig_base + can_map;
+ sp_banks[entry].num_bytes = orig_len - can_map;
+ MKTRACE(("adjust[%08lx,%08lx]\n", (orig_base + can_map), (orig_len - can_map)));
+ MKTRACE(("map_kernel: skipping first loop\n"));
+ goto loop_skip;
+ }
+ vaddr = map_spbank(KERNBASE, entry);
+ etaken[entry] = 1;
+
+ /* Step 4: Map what we can above KERNBASE. */
+ MKTRACE(("map_kernel: vaddr=%08lx, entering first loop\n", vaddr));
+ for(;;) {
+ unsigned long bank_size;
+
+ MKTRACE(("map_kernel: ffsp()"));
+ entry = find_free_spbank(&etaken[0]);
+ bank_size = sp_banks[entry].num_bytes;
+ MKTRACE(("<%d> base=%08lx bs=%08lx ", entry, sp_banks[entry].base_addr, bank_size));
+ if(!bank_size)
+ break;
+ if(((vaddr + bank_size) >= 0xfd000000) ||
+ ((vaddr + bank_size) < KERNBASE)) {
+ unsigned long orig_base = sp_banks[entry].base_addr;
+ unsigned long orig_len = sp_banks[entry].num_bytes;
+ unsigned long can_map = (0xfd000000 - vaddr);
+
+ /* Map a partial bank in this case, adjust the base
+ * and the length, but don't mark it used.
+ */
+ sp_banks[entry].num_bytes = can_map;
+ MKTRACE(("wheee really big mapping [%08lx,%08lx]", orig_base, can_map));
+ vaddr = map_spbank(vaddr, entry);
+ MKTRACE(("vaddr now %08lx ", vaddr));
+ sp_banks[entry].base_addr = orig_base + can_map;
+ sp_banks[entry].num_bytes = orig_len - can_map;
+ MKTRACE(("adjust[%08lx,%08lx]\n", (orig_base + can_map), (orig_len - can_map)));
+ break;
+ }
+ if(!bank_size)
+ break;
+
+ /* Ok, we can map this one, do it. */
+ MKTRACE(("map_spbank(%08lx,entry<%d>) ", vaddr, entry));
+ vaddr = map_spbank(vaddr, entry);
+ etaken[entry] = 1;
+ MKTRACE(("vaddr now %08lx\n", vaddr));
+ }
+ MKTRACE(("\n"));
+ /* If not lots_of_ram, assume we did indeed map it all above. */
+loop_skip:
+ if(!lots_of_ram)
+ goto check_and_return;
+
+ /* Step 5: Map the rest (if any) right below KERNBASE. */
+ MKTRACE(("map_kernel: doing low mappings... "));
+ tally = 0;
+ for(entry = 0; sp_banks[entry].num_bytes; entry++) {
+ if(!etaken[entry])
+ tally += SRMMU_PGDIR_ALIGN(sp_banks[entry].num_bytes);
+ }
+ if(!tally)
+ memprobe_error("Whee, lots_of_ram yet no low pages to map.\n");
+ low_base = (KERNBASE - tally);
+ MKTRACE(("tally=%08lx low_base=%08lx\n", tally, low_base));
+
+ /* Ok, now map 'em. */
+ MKTRACE(("map_kernel: Allocate pt skeleton (%08lx, %08x)\n",low_base,KERNBASE));
+ srmmu_allocate_ptable_skeleton(low_base, KERNBASE);
+ vaddr = low_base;
+ MKTRACE(("map_kernel: vaddr=%08lx Entering second loop for low maps.\n", vaddr));
+ for(;;) {
+ unsigned long bank_size;
+
+ entry = find_free_spbank(&etaken[0]);
+ bank_size = sp_banks[entry].num_bytes;
+ MKTRACE(("map_kernel: e<%d> base=%08lx bs=%08lx ", entry, sp_banks[entry].base_addr, bank_size));
+ if(!bank_size)
+ break;
+ if((vaddr + bank_size) > KERNBASE)
+ memprobe_error("Wheee, kernel low mapping overflow.\n");
+ MKTRACE(("map_spbank(%08lx, %d) ", vaddr, entry));
+ vaddr = map_spbank(vaddr, entry);
+ etaken[entry] = 1;
+ tally -= SRMMU_PGDIR_ALIGN(bank_size);
+ MKTRACE(("Now, vaddr=%08lx tally=%08lx\n", vaddr, tally));
+ }
+ MKTRACE(("\n"));
+ if(tally)
+ memprobe_error("Wheee, did not map all of low mappings.\n");
+check_and_return:
+ /* Step 6: Sanity check, make sure we did it all. */
+ MKTRACE(("check_and_return: "));
+ for(entry = 0; sp_banks[entry].num_bytes; entry++) {
+ MKTRACE(("e[%d]=%d ", entry, etaken[entry]));
+ if(!etaken[entry]) {
+ MKTRACE(("oops\n"));
+ memprobe_error("Some bank did not get mapped.\n");
+ }
+ }
+ MKTRACE(("success\n"));
+ init_task.mm->mmap->vm_start = page_offset = low_base;
+ return; /* SUCCESS! */
+}
+
+unsigned long srmmu_endmem_fixup(unsigned long mem_end_now)
+{
+ unsigned long tally = 0;
+ int i;
+
+ for(i = 0; sp_banks[i].num_bytes; i++)
+ tally += SRMMU_PGDIR_ALIGN(sp_banks[i].num_bytes);
+ if(tally < (0x0d000000UL)) {
+ return KERNBASE + tally;
+ } else {
+ return 0xfd000000UL;
+ }
+}
+
+/* Paging initialization on the Sparc Reference MMU. */
+extern unsigned long free_area_init(unsigned long, unsigned long);
+extern unsigned long sparc_context_init(unsigned long, int);
+
+extern int physmem_mapped_contig;
+extern int linux_num_cpus;
+
+void (*poke_srmmu)(void);
+
+unsigned long srmmu_paging_init(unsigned long start_mem, unsigned long end_mem)
+{
+ unsigned long ptables_start;
+ int i, cpunode;
+ char node_str[128];
+
+ sparc_iobase_vaddr = 0xfd000000; /* 16MB of IOSPACE on all sun4m's. */
+ physmem_mapped_contig = 0; /* for init.c:taint_real_pages() */
+
+#if CONFIG_AP1000
+ num_contexts = AP_NUM_CONTEXTS;
+#else
+ /* Find the number of contexts on the srmmu. */
+ cpunode = prom_getchild(prom_root_node);
+ num_contexts = 0;
+ while((cpunode = prom_getsibling(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;
+ }
+ }
+#endif
+ if(!num_contexts) {
+ prom_printf("Something wrong, can't find cpu node in paging_init.\n");
+ prom_halt();
+ }
+
+ ptables_start = mempool = PAGE_ALIGN(start_mem);
+ memset(swapper_pg_dir, 0, PAGE_SIZE);
+ kbpage = srmmu_hwprobe(KERNBASE + PAGE_SIZE);
+ kbpage = (kbpage & SRMMU_PTE_PMASK) << 4;
+ kbpage -= PAGE_SIZE;
+
+ srmmu_allocate_ptable_skeleton(KERNBASE, end_mem);
+#if CONFIG_SUN_IO
+ srmmu_allocate_ptable_skeleton(sparc_iobase_vaddr, IOBASE_END);
+ srmmu_allocate_ptable_skeleton(DVMA_VADDR, DVMA_END);
+#endif
+
+ mempool = PAGE_ALIGN(mempool);
+#if CONFIG_AP1000
+ ap_inherit_mappings();
+#else
+ srmmu_inherit_prom_mappings(0xfe400000,(LINUX_OPPROM_ENDVM-PAGE_SIZE));
+#endif
+ map_kernel();
+#if CONFIG_AP1000
+ /* the MSC wants this aligned on a 16k boundary */
+ srmmu_context_table =
+ sparc_init_alloc(&mempool,
+ num_contexts*sizeof(ctxd_t)<0x4000?
+ 0x4000:
+ num_contexts*sizeof(ctxd_t));
+#else
+ srmmu_context_table = sparc_init_alloc(&mempool, num_contexts*sizeof(ctxd_t));
+#endif
+ srmmu_ctx_table_phys = (ctxd_t *) srmmu_v2p((unsigned long) srmmu_context_table);
+ for(i = 0; i < num_contexts; i++)
+ ctxd_set(&srmmu_context_table[i], swapper_pg_dir);
+
+ start_mem = PAGE_ALIGN(mempool);
+
+ flush_cache_all();
+ if(flush_page_for_dma == viking_no_mxcc_flush_page) {
+ unsigned long start = ptables_start;
+ unsigned long end = start_mem;
+
+ while(start < end) {
+ viking_no_mxcc_flush_page(start);
+ start += PAGE_SIZE;
+ }
+ }
+ srmmu_set_ctable_ptr((unsigned long) srmmu_ctx_table_phys);
+ flush_tlb_all();
+ poke_srmmu();
+
+#if CONFIG_AP1000
+ /* on the AP we don't put the top few contexts into the free
+ context list as these are reserved for parallel tasks */
+ start_mem = sparc_context_init(start_mem, MPP_CONTEXT_BASE);
+#else
+ start_mem = sparc_context_init(start_mem, num_contexts);
+#endif
+ start_mem = free_area_init(start_mem, end_mem);
+
+ return PAGE_ALIGN(start_mem);
+}
+
+static char srmmuinfo[512];
+
+static char *srmmu_mmu_info(void)
+{
+ sprintf(srmmuinfo, "MMU type\t: %s\n"
+ "invall\t\t: %d\n"
+ "invmm\t\t: %d\n"
+ "invrnge\t\t: %d\n"
+ "invpg\t\t: %d\n"
+ "contexts\t: %d\n"
+#ifdef USE_CHUNK_ALLOC
+ "big chunks\t: %d\n"
+ "little chunks\t: %d\n"
+ "chunk pages\t: %d\n"
+ "garbage\t\t: %d\n"
+ "garbage hits\t: %d\n"
+#endif
+ , srmmu_name,
+ module_stats.invall,
+ module_stats.invmm,
+ module_stats.invrnge,
+ module_stats.invpg,
+ num_contexts
+#ifdef USE_CHUNK_ALLOC
+ , bcwater, lcwater,
+ chunk_pages,
+ garbage_calls,
+ clct_pages
+#endif
+ );
+ return srmmuinfo;
+}
+
+static void srmmu_update_mmu_cache(struct vm_area_struct * vma, unsigned long address, pte_t pte)
+{
+}
+
+static void srmmu_exit_hook(void)
+{
+ struct mm_struct *mm = current->mm;
+
+ if(mm->context != NO_CONTEXT && mm->count == 1) {
+ flush_cache_mm(mm);
+ ctxd_set(&srmmu_context_table[mm->context], swapper_pg_dir);
+ flush_tlb_mm(mm);
+ free_context(mm->context);
+ mm->context = NO_CONTEXT;
+ }
+}
+
+static void srmmu_flush_hook(void)
+{
+ if(current->tss.flags & SPARC_FLAG_KTHREAD) {
+ alloc_context(current);
+ flush_cache_mm(current->mm);
+ ctxd_set(&srmmu_context_table[current->mm->context], current->mm->pgd);
+ flush_tlb_mm(current->mm);
+ srmmu_set_context(current->mm->context);
+ }
+}
+
+static void srmmu_vac_update_mmu_cache(struct vm_area_struct * vma,
+ unsigned long address, pte_t pte)
+{
+#if 0
+ struct inode *inode;
+ struct vm_area_struct *vmaring;
+ unsigned long offset, vaddr;
+ unsigned long start;
+ pgd_t *pgdp;
+ pmd_t *pmdp;
+ pte_t *ptep;
+
+ if (!(vma->vm_flags & VM_WRITE) ||
+ !(vma->vm_flags & VM_SHARED))
+ return;
+
+ inode = vma->vm_inode;
+ if (!inode)
+ return;
+
+ offset = (address & PAGE_MASK) - vma->vm_start;
+ vmaring = inode->i_mmap;
+ do {
+ vaddr = vmaring->vm_start + offset;
+
+ if ((vaddr ^ address) & vac_badbits) {
+ start = vma->vm_start;
+ while (start < vma->vm_end) {
+ pgdp = srmmu_pgd_offset(vma->vm_mm, start);
+ pmdp = srmmu_pmd_offset(pgdp, start);
+ ptep = srmmu_pte_offset(pmdp, start);
+
+ flush_cache_page_to_uncache(start);
+ set_pte(ptep, __pte((pte_val(*ptep) &
+ ~SRMMU_CACHE)));
+ flush_tlb_page_for_cbit(start);
+
+ start += PAGE_SIZE;
+ }
+ return;
+ }
+ } while ((vmaring = vmaring->vm_next_share) != inode->i_mmap);
+#endif
+}
+
+static void hypersparc_exit_hook(void)
+{
+ struct mm_struct *mm = current->mm;
+
+ if(mm->context != NO_CONTEXT && mm->count == 1) {
+ /* HyperSparc is copy-back, any data for this
+ * process in a modified cache line is stale
+ * and must be written back to main memory now
+ * else we eat shit later big time.
+ */
+ flush_cache_mm(mm);
+ ctxd_set(&srmmu_context_table[mm->context], swapper_pg_dir);
+ flush_tlb_mm(mm);
+ free_context(mm->context);
+ mm->context = NO_CONTEXT;
+ }
+}
+
+static void hypersparc_flush_hook(void)
+{
+ if(current->tss.flags & SPARC_FLAG_KTHREAD) {
+ alloc_context(current);
+ flush_cache_mm(current->mm);
+ ctxd_set(&srmmu_context_table[current->mm->context], current->mm->pgd);
+ flush_tlb_mm(current->mm);
+ srmmu_set_context(current->mm->context);
+ }
+}
+
+/* Init various srmmu chip types. */
+static void srmmu_is_bad(void)
+{
+ prom_printf("Could not determine SRMMU chip type.\n");
+ prom_halt();
+}
+
+static void init_vac_layout(void)
+{
+ int nd, cache_lines;
+ char node_str[128];
+
+ 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"))
+ break;
+ }
+ if(nd == 0) {
+ prom_printf("No CPU nodes found, halting.\n");
+ prom_halt();
+ }
+
+ 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;
+ vac_badbits = (vac_cache_size - 1) & PAGE_MASK;
+}
+
+static void 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);
+ hyper_clear_all_tags();
+
+ put_ross_icr(HYPERSPARC_ICCR_FTD | HYPERSPARC_ICCR_ICE);
+ hyper_flush_whole_icache();
+ clear = srmmu_get_faddr();
+ clear = srmmu_get_fstatus();
+}
+
+static void init_hypersparc(void)
+{
+ srmmu_name = "ROSS HyperSparc";
+
+ init_vac_layout();
+
+ set_pte = srmmu_set_pte_nocache_hyper;
+ flush_cache_all = hypersparc_flush_cache_all;
+ flush_cache_mm = hypersparc_flush_cache_mm;
+ flush_cache_range = hypersparc_flush_cache_range;
+ flush_cache_page = hypersparc_flush_cache_page;
+
+ flush_tlb_all = hypersparc_flush_tlb_all;
+ flush_tlb_mm = hypersparc_flush_tlb_mm;
+ flush_tlb_range = hypersparc_flush_tlb_range;
+ flush_tlb_page = hypersparc_flush_tlb_page;
+
+ flush_page_to_ram = hypersparc_flush_page_to_ram;
+ flush_page_for_dma = hypersparc_flush_page_for_dma;
+ flush_cache_page_to_uncache = hypersparc_flush_cache_page_to_uncache;
+ flush_tlb_page_for_cbit = hypersparc_flush_tlb_page_for_cbit;
+
+ ctxd_set = hypersparc_ctxd_set;
+ switch_to_context = hypersparc_switch_to_context;
+ mmu_exit_hook = hypersparc_exit_hook;
+ mmu_flush_hook = hypersparc_flush_hook;
+ update_mmu_cache = srmmu_vac_update_mmu_cache;
+ sparc_update_rootmmu_dir = hypersparc_update_rootmmu_dir;
+ poke_srmmu = poke_hypersparc;
+}
+
+static void poke_cypress(void)
+{
+ unsigned long mreg = srmmu_get_mmureg();
+ unsigned long faddr;
+ volatile unsigned long clear;
+
+ clear = srmmu_get_faddr();
+ clear = srmmu_get_fstatus();
+
+ 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));
+ }
+
+ /* 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_cypress_common(void)
+{
+ init_vac_layout();
+
+ set_pte = srmmu_set_pte_nocache_cypress;
+ flush_cache_all = cypress_flush_cache_all;
+ flush_cache_mm = cypress_flush_cache_mm;
+ flush_cache_range = cypress_flush_cache_range;
+ flush_cache_page = cypress_flush_cache_page;
+
+ flush_tlb_all = cypress_flush_tlb_all;
+ flush_tlb_mm = cypress_flush_tlb_mm;
+ flush_tlb_page = cypress_flush_tlb_page;
+ flush_tlb_range = cypress_flush_tlb_range;
+
+ flush_page_to_ram = cypress_flush_page_to_ram;
+ flush_page_for_dma = cypress_flush_page_for_dma;
+ flush_cache_page_to_uncache = cypress_flush_page_to_uncache;
+ flush_tlb_page_for_cbit = cypress_flush_tlb_page_for_cbit;
+
+ update_mmu_cache = srmmu_vac_update_mmu_cache;
+ poke_srmmu = poke_cypress;
+}
+
+static void init_cypress_604(void)
+{
+ srmmu_name = "ROSS Cypress-604(UP)";
+ srmmu_modtype = Cypress;
+ init_cypress_common();
+}
+
+static void 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 poke_swift(void)
+{
+ unsigned long mreg = srmmu_get_mmureg();
+
+ /* Clear any crap from the cache or else... */
+ swift_idflash_clear();
+ mreg |= (SWIFT_IE | SWIFT_DE); /* I & D caches on */
+
+ /* 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_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;
+ };
+
+ flush_cache_all = swift_flush_cache_all;
+ flush_cache_mm = swift_flush_cache_mm;
+ flush_cache_page = swift_flush_cache_page;
+ flush_cache_range = swift_flush_cache_range;
+
+ flush_tlb_all = swift_flush_tlb_all;
+ flush_tlb_mm = swift_flush_tlb_mm;
+ flush_tlb_page = swift_flush_tlb_page;
+ flush_tlb_range = swift_flush_tlb_range;
+
+ flush_page_to_ram = swift_flush_page_to_ram;
+ flush_page_for_dma = swift_flush_page_for_dma;
+ flush_cache_page_to_uncache = swift_flush_cache_page_to_uncache;
+ flush_tlb_page_for_cbit = swift_flush_tlb_page_for_cbit;
+
+ /* 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 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_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;
+
+ flush_cache_all = tsunami_flush_cache_all;
+ flush_cache_mm = tsunami_flush_cache_mm;
+ flush_cache_page = tsunami_flush_cache_page;
+ flush_cache_range = tsunami_flush_cache_range;
+
+ flush_tlb_all = tsunami_flush_tlb_all;
+ flush_tlb_mm = tsunami_flush_tlb_mm;
+ flush_tlb_page = tsunami_flush_tlb_page;
+ flush_tlb_range = tsunami_flush_tlb_range;
+
+ flush_page_to_ram = tsunami_flush_page_to_ram;
+ flush_page_for_dma = tsunami_flush_page_for_dma;
+ flush_cache_page_to_uncache = tsunami_flush_cache_page_to_uncache;
+ flush_tlb_page_for_cbit = tsunami_flush_tlb_page_for_cbit;
+
+ poke_srmmu = poke_tsunami;
+}
+
+static void poke_viking(void)
+{
+ unsigned long mreg = srmmu_get_mmureg();
+ static int smp_catch = 0;
+
+ 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);
+#if CONFIG_AP1000
+ mreg &= ~(VIKING_SBENABLE);
+#endif
+ srmmu_set_mmureg(mreg);
+
+
+#ifdef __SMP__
+ /* Avoid unnecessary cross calls. */
+ flush_cache_all = local_flush_cache_all;
+ flush_page_to_ram = local_flush_page_to_ram;
+ flush_page_for_dma = local_flush_page_for_dma;
+ if (viking_mxcc_present) {
+ flush_cache_page_to_uncache = local_flush_cache_page_to_uncache;
+ }
+#endif
+}
+
+static void init_viking(void)
+{
+ unsigned long mreg = srmmu_get_mmureg();
+
+ /* Ahhh, the viking. SRMMU VLSI abortion number two... */
+
+ if(mreg & VIKING_MMODE) {
+ unsigned long bpreg;
+
+ srmmu_name = "TI Viking";
+ viking_mxcc_present = 0;
+ set_pte = srmmu_set_pte_nocache_nomxccvik;
+
+ bpreg = viking_get_bpreg();
+ bpreg &= ~(VIKING_ACTION_MIX);
+ viking_set_bpreg(bpreg);
+
+ msi_set_sync();
+
+ flush_cache_page_to_uncache = viking_no_mxcc_flush_page;
+
+ /* 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.
+ */
+ flush_page_for_dma = viking_no_mxcc_flush_page;
+ } else {
+ srmmu_name = "TI Viking/MXCC";
+ viking_mxcc_present = 1;
+ flush_cache_page_to_uncache = viking_mxcc_flush_page;
+
+ /* MXCC vikings lack the DMA snooping bug. */
+ flush_page_for_dma = viking_flush_page_for_dma;
+ }
+
+ flush_cache_all = viking_flush_cache_all;
+ flush_cache_mm = viking_flush_cache_mm;
+ flush_cache_page = viking_flush_cache_page;
+ flush_cache_range = viking_flush_cache_range;
+
+ flush_tlb_all = viking_flush_tlb_all;
+ flush_tlb_mm = viking_flush_tlb_mm;
+ flush_tlb_page = viking_flush_tlb_page;
+ flush_tlb_range = viking_flush_tlb_range;
+
+ flush_page_to_ram = viking_flush_page_to_ram;
+ flush_tlb_page_for_cbit = viking_flush_tlb_page_for_cbit;
+
+ poke_srmmu = poke_viking;
+}
+
+/* Probe for the srmmu chip version. */
+static void 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:
+ /* Uniprocessor Cypress */
+ init_cypress_604();
+ break;
+ 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:
+ srmmu_is_bad();
+ break;
+ };
+ return;
+ }
+
+ /* Next check for Fujitsu Swift. */
+ if(psr_typ == 0 && psr_vers == 4) {
+ 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();
+}
+
+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;
+
+#ifdef __SMP__
+extern unsigned long rirq_mmu_patchme, srmmu_reti_stackchk;
+#endif
+
+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 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);
+#ifdef __SMP__
+ PATCH_BRANCH(rirq_mmu_patchme, srmmu_reti_stackchk);
+#endif
+ 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 __SMP__
+/* Local cross-calls. */
+static void smp_flush_page_for_dma(unsigned long page)
+{
+ xc1((smpfunc_t) local_flush_page_for_dma, page);
+}
+
+static void smp_flush_cache_page_to_uncache(unsigned long page)
+{
+ xc1((smpfunc_t) local_flush_cache_page_to_uncache, page);
+}
+
+static void smp_flush_tlb_page_for_cbit(unsigned long page)
+{
+ xc1((smpfunc_t) local_flush_tlb_page_for_cbit, page);
+}
+#endif
+
+/* Load up routines and constants for sun4m mmu */
+void ld_mmu_srmmu(void)
+{
+ /* First the constants */
+ pmd_shift = SRMMU_PMD_SHIFT;
+ pmd_size = SRMMU_PMD_SIZE;
+ pmd_mask = SRMMU_PMD_MASK;
+ pgdir_shift = SRMMU_PGDIR_SHIFT;
+ pgdir_size = SRMMU_PGDIR_SIZE;
+ pgdir_mask = SRMMU_PGDIR_MASK;
+
+ ptrs_per_pte = SRMMU_PTRS_PER_PTE;
+ ptrs_per_pmd = SRMMU_PTRS_PER_PMD;
+ ptrs_per_pgd = SRMMU_PTRS_PER_PGD;
+
+ page_none = SRMMU_PAGE_NONE;
+ page_shared = SRMMU_PAGE_SHARED;
+ page_copy = SRMMU_PAGE_COPY;
+ page_readonly = SRMMU_PAGE_RDONLY;
+ page_kernel = SRMMU_PAGE_KERNEL;
+ pg_iobits = SRMMU_VALID | SRMMU_WRITE | SRMMU_REF;
+
+ /* Functions */
+ set_pte = srmmu_set_pte_cacheable;
+ switch_to_context = srmmu_switch_to_context;
+ pmd_align = srmmu_pmd_align;
+ pgdir_align = srmmu_pgdir_align;
+ vmalloc_start = srmmu_vmalloc_start;
+
+ pte_page = srmmu_pte_page;
+ pmd_page = srmmu_pmd_page;
+ pgd_page = srmmu_pgd_page;
+
+ sparc_update_rootmmu_dir = srmmu_update_rootmmu_dir;
+
+ pte_none = srmmu_pte_none;
+ pte_present = srmmu_pte_present;
+ pte_clear = srmmu_pte_clear;
+
+ pmd_none = srmmu_pmd_none;
+ pmd_bad = srmmu_pmd_bad;
+ pmd_present = srmmu_pmd_present;
+ pmd_clear = srmmu_pmd_clear;
+
+ pgd_none = srmmu_pgd_none;
+ pgd_bad = srmmu_pgd_bad;
+ pgd_present = srmmu_pgd_present;
+ pgd_clear = srmmu_pgd_clear;
+
+ mk_pte = srmmu_mk_pte;
+ mk_pte_phys = srmmu_mk_pte_phys;
+ pgd_set = srmmu_pgd_set;
+ mk_pte_io = srmmu_mk_pte_io;
+ pte_modify = srmmu_pte_modify;
+ pgd_offset = srmmu_pgd_offset;
+ pmd_offset = srmmu_pmd_offset;
+ pte_offset = srmmu_pte_offset;
+ pte_free_kernel = srmmu_pte_free_kernel;
+ pmd_free_kernel = srmmu_pmd_free_kernel;
+ pte_alloc_kernel = srmmu_pte_alloc_kernel;
+ pmd_alloc_kernel = srmmu_pmd_alloc_kernel;
+ pte_free = srmmu_pte_free;
+ pte_alloc = srmmu_pte_alloc;
+ pmd_free = srmmu_pmd_free;
+ pmd_alloc = srmmu_pmd_alloc;
+ pgd_free = srmmu_pgd_free;
+ pgd_alloc = srmmu_pgd_alloc;
+
+ pte_write = srmmu_pte_write;
+ pte_dirty = srmmu_pte_dirty;
+ pte_young = srmmu_pte_young;
+ pte_wrprotect = srmmu_pte_wrprotect;
+ pte_mkclean = srmmu_pte_mkclean;
+ pte_mkold = srmmu_pte_mkold;
+ pte_mkwrite = srmmu_pte_mkwrite;
+ pte_mkdirty = srmmu_pte_mkdirty;
+ pte_mkyoung = srmmu_pte_mkyoung;
+ update_mmu_cache = srmmu_update_mmu_cache;
+ mmu_exit_hook = srmmu_exit_hook;
+ mmu_flush_hook = srmmu_flush_hook;
+ mmu_lockarea = srmmu_lockarea;
+ mmu_unlockarea = srmmu_unlockarea;
+
+ mmu_get_scsi_one = srmmu_get_scsi_one;
+ mmu_get_scsi_sgl = srmmu_get_scsi_sgl;
+ mmu_release_scsi_one = srmmu_release_scsi_one;
+ mmu_release_scsi_sgl = srmmu_release_scsi_sgl;
+
+ mmu_map_dma_area = srmmu_map_dma_area;
+
+ mmu_info = srmmu_mmu_info;
+ mmu_v2p = srmmu_v2p;
+ mmu_p2v = srmmu_p2v;
+
+ /* Task struct and kernel stack allocating/freeing. */
+ alloc_kernel_stack = srmmu_alloc_kernel_stack;
+ alloc_task_struct = srmmu_alloc_task_struct;
+ free_kernel_stack = srmmu_free_kernel_stack;
+ free_task_struct = srmmu_free_task_struct;
+
+ quick_kernel_fault = srmmu_quick_kernel_fault;
+
+ /* SRMMU specific. */
+ ctxd_set = srmmu_ctxd_set;
+ pmd_set = srmmu_pmd_set;
+
+ get_srmmu_type();
+ patch_window_trap_handlers();
+
+#ifdef __SMP__
+ /* El switcheroo... */
+
+ local_flush_cache_all = flush_cache_all;
+ local_flush_cache_mm = flush_cache_mm;
+ local_flush_cache_range = flush_cache_range;
+ local_flush_cache_page = flush_cache_page;
+ local_flush_tlb_all = flush_tlb_all;
+ local_flush_tlb_mm = flush_tlb_mm;
+ local_flush_tlb_range = flush_tlb_range;
+ local_flush_tlb_page = flush_tlb_page;
+ local_flush_page_to_ram = flush_page_to_ram;
+ local_flush_page_for_dma = flush_page_for_dma;
+ local_flush_cache_page_to_uncache = flush_cache_page_to_uncache;
+ local_flush_tlb_page_for_cbit = flush_tlb_page_for_cbit;
+
+ flush_cache_all = smp_flush_cache_all;
+ flush_cache_mm = smp_flush_cache_mm;
+ flush_cache_range = smp_flush_cache_range;
+ flush_cache_page = smp_flush_cache_page;
+ flush_tlb_all = smp_flush_tlb_all;
+ flush_tlb_mm = smp_flush_tlb_mm;
+ flush_tlb_range = smp_flush_tlb_range;
+ flush_tlb_page = smp_flush_tlb_page;
+ flush_page_to_ram = smp_flush_page_to_ram;
+ flush_page_for_dma = smp_flush_page_for_dma;
+ flush_cache_page_to_uncache = smp_flush_cache_page_to_uncache;
+ flush_tlb_page_for_cbit = smp_flush_tlb_page_for_cbit;
+#endif
+}
diff --git a/arch/sparc/mm/sun4c.c b/arch/sparc/mm/sun4c.c
new file mode 100644
index 000000000..fdc74d3d7
--- /dev/null
+++ b/arch/sparc/mm/sun4c.c
@@ -0,0 +1,1965 @@
+/* $Id: sun4c.c,v 1.121 1996/11/01 20:36:27 ecd 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)
+ */
+
+#include <linux/kernel.h>
+#include <linux/mm.h>
+
+#include <asm/page.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>
+
+extern int num_segmaps, num_contexts;
+
+/* Small structure for ease of handling in the low level kernel fault
+ * handler. This holds all information necessary, like the sun4c_ufree_ring
+ * for user segments.
+ */
+struct sun4c_segment_info {
+ unsigned long vaddr;
+ unsigned char pseg;
+};
+struct sun4c_segment_info *sun4c_kernel_next;
+
+#define SUN4C_KERNEL_BUCKETS 32
+#define SUN4C_KERNEL_BSIZE (sizeof(struct sun4c_segment_info) \
+ * SUN4C_KERNEL_BUCKETS)
+
+#ifndef MAX
+#define MAX(a,b) ((a)<(b)?(b):(a))
+#endif
+#ifndef MIN
+#define MIN(a,b) ((a)<(b)?(a):(b))
+#endif
+
+
+
+#define KGPROF_PROFILING 0
+#if KGPROF_PROFILING
+#define KGPROF_DEPTH 3 /* this needs to match the code below */
+#define KGPROF_SIZE 100
+static struct {
+ unsigned addr[KGPROF_DEPTH];
+ unsigned count;
+} kgprof_counters[KGPROF_SIZE];
+
+/* just call this function from whatever function you think needs it then
+ look at /proc/cpuinfo to see where the function is being called from
+ and how often. This gives a type of "kernel gprof" */
+#define NEXT_PROF(prev,lvl) (prev>PAGE_OFFSET?__builtin_return_address(lvl):0)
+static inline void kgprof_profile(void)
+{
+ unsigned ret[KGPROF_DEPTH];
+ int i,j;
+ /* you can't use a variable argument to __builtin_return_address() */
+ ret[0] = (unsigned)__builtin_return_address(0);
+ ret[1] = (unsigned)NEXT_PROF(ret[0],1);
+ ret[2] = (unsigned)NEXT_PROF(ret[1],2);
+
+ for (i=0;i<KGPROF_SIZE && kgprof_counters[i].addr[0];i++) {
+ for (j=0;j<KGPROF_DEPTH;j++)
+ if (ret[j] != kgprof_counters[i].addr[j]) break;
+ if (j==KGPROF_DEPTH) break;
+ }
+ if (i<KGPROF_SIZE) {
+ for (j=0;j<KGPROF_DEPTH;j++)
+ kgprof_counters[i].addr[j] = ret[j];
+ kgprof_counters[i].count++;
+ }
+}
+#endif
+
+
+/* Flushing the cache. */
+struct sun4c_vac_props sun4c_vacinfo;
+static int ctxflushes, segflushes, pageflushes;
+
+/* convert a virtual address to a physical address and vice
+ versa. Easy on the 4c */
+static unsigned long sun4c_v2p(unsigned long vaddr)
+{
+ return(vaddr - PAGE_OFFSET);
+}
+
+static unsigned long sun4c_p2v(unsigned long vaddr)
+{
+ return(vaddr + PAGE_OFFSET);
+}
+
+
+/* Invalidate every sun4c cache line tag. */
+void 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_vacinfo.num_bytes);
+ while(begin < end) {
+ __asm__ __volatile__("sta %%g0, [%0] %1\n\t" : :
+ "r" (begin), "i" (ASI_CONTROL));
+ begin += sun4c_vacinfo.linesize;
+ }
+}
+
+/* Blow the entire current context out of the virtual cache. */
+static inline void sun4c_flush_context(void)
+{
+ unsigned long vaddr;
+
+ ctxflushes++;
+ if(sun4c_vacinfo.do_hwflushes) {
+ for(vaddr=0; vaddr < sun4c_vacinfo.num_bytes; vaddr+=PAGE_SIZE)
+ __asm__ __volatile__("sta %%g0, [%0] %1\n\t" : :
+ "r" (vaddr), "i" (ASI_HWFLUSHCONTEXT));
+ } else {
+ /* AJT: possibly read the tags and avoid flushing the ones that
+ are above 0xf0000000 so the kernel isn't flushed all the time */
+ __asm__ __volatile__("add %1, %1, %%g1\n\t"
+ "add %1, %%g1, %%g2\n\t"
+ "add %1, %%g2, %%g3\n\t"
+ "add %1, %%g3, %%g4\n\t"
+ "add %1, %%g4, %%g5\n\t"
+ "add %1, %%g5, %%o4\n\t"
+ "add %1, %%o4, %%o5\n"
+ "1:\n\t"
+ "subcc %0, %%o5, %0\n\t"
+ "sta %%g0, [%0] %2\n\t"
+ "sta %%g0, [%0 + %1] %2\n\t"
+ "sta %%g0, [%0 + %%g1] %2\n\t"
+ "sta %%g0, [%0 + %%g2] %2\n\t"
+ "sta %%g0, [%0 + %%g3] %2\n\t"
+ "sta %%g0, [%0 + %%g4] %2\n\t"
+ "sta %%g0, [%0 + %%g5] %2\n\t"
+ "bg 1b\n\t"
+ " sta %%g0, [%0 + %%o4] %2\n\t" : :
+ "r" (sun4c_vacinfo.num_bytes),
+ "r" (sun4c_vacinfo.linesize),
+ "i" (ASI_FLUSHCTX) :
+ "g1", "g2", "g3", "g4", "g5", "o4", "o5");
+ }
+}
+
+/* Scrape the segment starting at ADDR from the virtual cache. */
+static inline void sun4c_flush_segment(unsigned long addr)
+{
+ segflushes++;
+ addr &= SUN4C_REAL_PGDIR_MASK;
+ if(sun4c_vacinfo.do_hwflushes) {
+ unsigned long end = (addr + sun4c_vacinfo.num_bytes);
+
+ for( ; addr < end; addr += PAGE_SIZE)
+ __asm__ __volatile__("sta %%g0, [%0] %1\n\t" : :
+ "r" (addr), "i" (ASI_HWFLUSHSEG));
+ } else {
+ __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] %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"
+ "sta %%g0, [%0 + %%o4] %3\n\t"
+ "bg 1b\n\t"
+ " add %0, %%o5, %0\n\t" : :
+ "r" (addr), "r" (sun4c_vacinfo.num_bytes),
+ "r" (sun4c_vacinfo.linesize),
+ "i" (ASI_FLUSHSEG) :
+ "g1", "g2", "g3", "g4", "g5", "o4", "o5");
+ }
+}
+
+/* Bolix one page from the virtual cache. */
+static inline void sun4c_flush_page(unsigned long addr)
+{
+ addr &= PAGE_MASK;
+
+ pageflushes++;
+ if(sun4c_vacinfo.do_hwflushes) {
+ __asm__ __volatile__("sta %%g0, [%0] %1\n\t" : :
+ "r" (addr), "i" (ASI_HWFLUSHPAGE));
+ } else {
+ __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] %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"
+ "sta %%g0, [%0 + %%o4] %3\n\t"
+ "bg 1b\n\t"
+ " add %0, %%o5, %0\n\t" : :
+ "r" (addr), "r" (PAGE_SIZE),
+ "r" (sun4c_vacinfo.linesize),
+ "i" (ASI_FLUSHPG) :
+ "g1", "g2", "g3", "g4", "g5", "o4", "o5");
+ }
+}
+
+/* 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);
+ _unused = *AUXREG;
+}
+
+/* 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 sun4c_probe_vac(void)
+{
+ sun4c_disable_vac();
+ 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.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();
+ };
+
+ /* Only vac-hwflush (with a dash) is reliable, weitek
+ * power-up processor claims vac_hwflush (underscore)
+ * yet crashes if you try to use hardware based flushes.
+ */
+ 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 davem");
+ 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 num_context_patch1, num_context_patch1_16;
+extern unsigned long num_context_patch2, num_context_patch2_16;
+extern unsigned long sun4c_kernel_buckets_patch;
+extern unsigned long sun4c_kernel_buckets_patch_32;
+
+#define PATCH_INSN(src, dst) do { \
+ daddr = &(dst); \
+ iaddr = &(src); \
+ *daddr = *iaddr; \
+ } while (0);
+
+static void 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;
+ 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);
+ PATCH_INSN(num_context_patch2_16,
+ num_context_patch2);
+ break;
+ default:
+ prom_printf("Unhandled number of contexts: %d\n",
+ num_contexts);
+ prom_halt();
+ }
+ switch (SUN4C_KERNEL_BUCKETS) {
+ case 16:
+ /* Default, nothing to do. */
+ break;
+ case 32:
+ PATCH_INSN(sun4c_kernel_buckets_patch_32,
+ sun4c_kernel_buckets_patch);
+ break;
+ default:
+ prom_printf("Unhandled number of kernel buckets: %d\n",
+ SUN4C_KERNEL_BUCKETS);
+ prom_halt();
+ }
+}
+
+static void sun4c_probe_mmu(void)
+{
+ 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 = 0;
+
+void sun4c_probe_memerr_reg(void)
+{
+ int node;
+ struct linux_prom_registers regs[1];
+
+ node = prom_getchild(prom_root_node);
+ node = prom_searchsiblings(prom_root_node, "memory-error");
+ if (!node)
+ return;
+ prom_getproperty(node, "reg", (char *)regs, sizeof(regs));
+ sun4c_memerr_reg = sparc_alloc_io(regs[0].phys_addr, 0,
+ regs[0].reg_size,
+ "memory parity error",
+ regs[0].which_io, 0);
+}
+
+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))) {
+ /* 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 boundry for us already. */
+static void sun4c_map_dma_area(unsigned long addr, int len)
+{
+ unsigned long page, end;
+
+ end = PAGE_ALIGN((addr + len));
+ while(addr < end) {
+ page = get_free_page(GFP_KERNEL);
+ if(!page) {
+ prom_printf("alloc_dvma: Cannot get a dvma page\n");
+ prom_halt();
+ }
+ sun4c_flush_page(page);
+ page -= PAGE_OFFSET;
+ page >>= PAGE_SHIFT;
+ page |= (_SUN4C_PAGE_VALID | _SUN4C_PAGE_WRITE | _SUN4C_PAGE_NOCACHE);
+ sun4c_put_pte(addr, page);
+ addr += PAGE_SIZE;
+ }
+}
+
+
+/* TLB management. */
+struct sun4c_mmu_entry {
+ struct sun4c_mmu_entry *next;
+ struct sun4c_mmu_entry *prev;
+ unsigned long vaddr;
+ unsigned char pseg;
+ unsigned char locked;
+};
+static struct sun4c_mmu_entry mmu_entry_pool[256];
+
+static void sun4c_init_mmu_entry_pool(void)
+{
+ int i;
+
+ for(i=0; i < 256; i++) {
+ mmu_entry_pool[i].pseg = i;
+ mmu_entry_pool[i].next = 0;
+ mmu_entry_pool[i].prev = 0;
+ mmu_entry_pool[i].vaddr = 0;
+ mmu_entry_pool[i].locked = 0;
+ }
+ 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;
+
+ 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);
+ }
+ }
+ 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 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;
+ }
+}
+
+struct sun4c_mmu_ring {
+ struct sun4c_mmu_entry ringhd;
+ int num_entries;
+};
+static struct sun4c_mmu_ring sun4c_context_ring[16]; /* used user entries */
+static struct sun4c_mmu_ring sun4c_ufree_ring; /* free user entries */
+
+static inline void sun4c_next_kernel_bucket(struct sun4c_segment_info **next)
+{
+ (*next)++;
+ *next = (struct sun4c_segment_info *)
+ ((unsigned long)*next & ~SUN4C_KERNEL_BSIZE);
+}
+
+static inline void sun4c_init_rings(unsigned long *mempool)
+{
+ int i;
+ for(i=0; i<16; 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;
+ /* This needs to be aligned to twice it's size for speed. */
+ sun4c_kernel_next = sparc_init_alloc(mempool, 2 * SUN4C_KERNEL_BSIZE);
+}
+
+static inline 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 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 inline void recycle_ring(struct sun4c_mmu_ring *ring, struct sun4c_mmu_entry *entry)
+{
+ struct sun4c_mmu_entry *head = &ring->ringhd;
+ struct sun4c_mmu_entry *next = entry->next;
+
+ (next->prev = entry->prev)->next = next;
+ entry->prev = head; (entry->next = head->next)->prev = entry;
+ head->next = entry;
+ /* num_entries stays the same */
+}
+
+static inline void free_user_entry(int ctx, struct sun4c_mmu_entry *entry)
+{
+ remove_ring(sun4c_context_ring+ctx, entry);
+ add_ring(&sun4c_ufree_ring, entry);
+}
+
+static inline void assign_user_entry(int ctx, struct sun4c_mmu_entry *entry)
+{
+ remove_ring(&sun4c_ufree_ring, entry);
+ add_ring(sun4c_context_ring+ctx, entry);
+}
+
+static void 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);
+ sun4c_kernel_next->vaddr = 0;
+ sun4c_kernel_next->pseg = mmu_entry_pool[i].pseg;
+ sun4c_next_kernel_bucket(&sun4c_kernel_next);
+ howmany--;
+ }
+}
+
+static void 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 inline 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 inline 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);
+}
+
+static inline void sun4c_user_unmap(struct sun4c_mmu_entry *uentry)
+{
+ /* PM: need flush_user_windows() ?? */
+ sun4c_put_segmap(uentry->vaddr, invalid_segment);
+}
+
+static inline void sun4c_user_map(struct sun4c_mmu_entry *uentry)
+{
+ unsigned long start = uentry->vaddr;
+ unsigned long end = start + SUN4C_REAL_PGDIR_SIZE;
+
+ sun4c_put_segmap(uentry->vaddr, uentry->pseg);
+ while(start < end) {
+ sun4c_put_pte(start, 0);
+ start += PAGE_SIZE;
+ }
+}
+
+static inline void sun4c_demap_context(struct sun4c_mmu_ring *crp, unsigned char ctx)
+{
+ struct sun4c_mmu_entry *this_entry, *next_entry;
+ int savectx = sun4c_get_context();
+
+ this_entry = crp->ringhd.next;
+ flush_user_windows();
+ sun4c_set_context(ctx);
+ sun4c_flush_context();
+ while(crp->num_entries) {
+ next_entry = this_entry->next;
+ sun4c_user_unmap(this_entry);
+ free_user_entry(ctx, this_entry);
+ this_entry = next_entry;
+ }
+ sun4c_set_context(savectx);
+}
+
+static inline void sun4c_demap_one(struct sun4c_mmu_ring *crp,unsigned char ctx)
+{
+ /* by using .prev we get a kind of "lru" algorithm */
+ struct sun4c_mmu_entry *entry = crp->ringhd.prev;
+ int savectx = sun4c_get_context();
+
+ flush_user_windows();
+ sun4c_set_context(ctx);
+ sun4c_flush_segment(entry->vaddr);
+ sun4c_user_unmap(entry);
+ free_user_entry(ctx, entry);
+ sun4c_set_context(savectx);
+}
+
+/* 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.
+ */
+static inline struct sun4c_mmu_entry *sun4c_user_strategy(void)
+{
+ struct ctx_list *next_one;
+ struct sun4c_mmu_ring *rp = 0;
+ unsigned char ctx;
+
+ /* If some are free, return first one. */
+ if(sun4c_ufree_ring.num_entries)
+ return sun4c_ufree_ring.ringhd.next;
+
+ /* Grab one from the LRU context. */
+ next_one = ctx_used.next;
+ while (sun4c_context_ring[next_one->ctx_number].num_entries == 0)
+ next_one = next_one->next;
+
+ ctx = next_one->ctx_number;
+ rp = &sun4c_context_ring[ctx];
+
+ sun4c_demap_one(rp,ctx);
+ return sun4c_ufree_ring.ringhd.next;
+}
+
+static inline void alloc_user_segment(unsigned long address, unsigned char ctx)
+{
+ struct sun4c_mmu_entry *entry;
+
+ address &= SUN4C_REAL_PGDIR_MASK;
+ entry = sun4c_user_strategy();
+ assign_user_entry(ctx, entry);
+ entry->vaddr = address;
+ sun4c_user_map(entry);
+}
+
+/* XXX Just like kernel tlb replacement we'd like to have a low level
+ * XXX equivalent for user faults which need not go through the mm
+ * XXX subsystem just to load a mmu entry. But this might not be as
+ * XXX feasible since we need to go through the kernel page tables
+ * XXX for this process, which we currently don't lock into the mmu
+ * XXX so we would fault with traps off... must think about this...
+ */
+void sun4c_update_mmu_cache(struct vm_area_struct *vma, unsigned long address, pte_t pte)
+{
+ unsigned long flags;
+#if 0
+ struct inode *inode;
+ struct vm_area_struct *vmaring;
+ unsigned long offset, vaddr;
+ unsigned long start;
+ pgd_t *pgdp;
+ pmd_t *pmdp;
+ pte_t *ptep;
+#endif
+
+ save_and_cli(flags);
+ address &= PAGE_MASK;
+ if(sun4c_get_segmap(address) == invalid_segment)
+ alloc_user_segment(address, sun4c_get_context());
+ sun4c_put_pte(address, pte_val(pte));
+
+#if 0
+ if (!(vma->vm_flags & VM_WRITE) ||
+ !(vma->vm_flags & VM_SHARED))
+ goto done;
+
+ inode = vma->vm_inode;
+ if (!inode)
+ goto done;
+
+ offset = (address & PAGE_MASK) - vma->vm_start;
+ vmaring = inode->i_mmap;
+ do {
+ vaddr = vmaring->vm_start + offset;
+
+ if (S4CVAC_BADALIAS(vaddr, address)) {
+ start = vma->vm_start;
+ while (start < vma->vm_end) {
+ pgdp = pgd_offset(vma->vm_mm, start);
+ pmdp = pmd_offset(pgdp, start);
+ ptep = pte_offset(pmdp, start);
+
+ if (sun4c_get_pte(start) & _SUN4C_PAGE_VALID)
+ sun4c_put_pte(start, sun4c_get_pte(start) |
+ _SUN4C_PAGE_NOCACHE);
+
+ start += PAGE_SIZE;
+ }
+ goto done;
+ }
+ } while ((vmaring = vmaring->vm_next_share) != inode->i_mmap);
+
+done:
+#endif
+ restore_flags(flags);
+}
+
+/* This is now a fast in-window trap handler to avoid any and all races. */
+static void sun4c_quick_kernel_fault(unsigned long address)
+{
+ printk("Kernel faults at addr=0x%08lx\n", address);
+ panic("sun4c fault handler bolixed...");
+}
+
+/*
+ * 4 page buckets for task struct and kernel stack allocation.
+ *
+ * TASK_STACK_BEGIN
+ * bucket[0]
+ * bucket[1]
+ * [ ... ]
+ * bucket[NR_TASKS-1]
+ * TASK_STACK_BEGIN + (sizeof(struct task_bucket) * NR_TASKS)
+ *
+ * Each slot looks like:
+ *
+ * page 1 -- task struct
+ * page 2 -- unmapped, for stack redzone (maybe use for pgd)
+ * page 3/4 -- kernel stack
+ */
+
+struct task_bucket {
+ struct task_struct task;
+ char _unused1[PAGE_SIZE - sizeof(struct task_struct)];
+ char kstack[(PAGE_SIZE*3)];
+};
+
+struct task_bucket *sun4c_bucket[NR_TASKS];
+
+#define BUCKET_EMPTY ((struct task_bucket *) 0)
+#define BUCKET_SIZE (PAGE_SIZE << 2)
+#define BUCKET_SHIFT 14 /* log2(sizeof(struct task_bucket)) */
+#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) & 0xffff) << PAGE_SHIFT))
+
+static inline void get_locked_segment(unsigned long addr)
+{
+ struct sun4c_mmu_entry *stolen;
+ unsigned long flags;
+
+ save_and_cli(flags);
+ addr &= SUN4C_REAL_PGDIR_MASK;
+ stolen = sun4c_user_strategy();
+ remove_ring(&sun4c_ufree_ring, stolen);
+ stolen->vaddr = addr;
+ flush_user_windows();
+ sun4c_kernel_map(stolen);
+ restore_flags(flags);
+}
+
+static inline void free_locked_segment(unsigned long addr)
+{
+ struct sun4c_mmu_entry *entry;
+ unsigned long flags;
+ unsigned char pseg;
+
+ save_and_cli(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);
+ restore_flags(flags);
+}
+
+static inline void garbage_collect(int entry)
+{
+ int start, end;
+
+ /* 16 buckets per segment... */
+ entry &= ~15;
+ start = entry;
+ for(end = (start + 16); start < end; start++)
+ if(sun4c_bucket[start] != BUCKET_EMPTY)
+ return;
+ /* Entire segment empty, release it. */
+ free_locked_segment(BUCKET_ADDR(entry));
+}
+
+static struct task_struct *sun4c_alloc_task_struct(void)
+{
+ unsigned long addr, page;
+ int entry;
+
+ page = get_free_page(GFP_KERNEL);
+ if(!page)
+ return (struct task_struct *) 0;
+ /* XXX Bahh, linear search too slow, use hash
+ * XXX table in final implementation. Or
+ * XXX keep track of first free when we free
+ * XXX a bucket... anything but this.
+ */
+ for(entry = 0; entry < NR_TASKS; entry++)
+ if(sun4c_bucket[entry] == BUCKET_EMPTY)
+ break;
+ if(entry == NR_TASKS) {
+ free_page(page);
+ return (struct task_struct *) 0;
+ }
+ addr = BUCKET_ADDR(entry);
+ sun4c_bucket[entry] = (struct task_bucket *) addr;
+ if(sun4c_get_segmap(addr) == invalid_segment)
+ get_locked_segment(addr);
+ sun4c_put_pte(addr, BUCKET_PTE(page));
+ return (struct task_struct *) addr;
+}
+
+static unsigned long sun4c_alloc_kernel_stack(struct task_struct *tsk)
+{
+ unsigned long saddr = (unsigned long) tsk;
+ unsigned long page[2];
+
+ if(!saddr)
+ return 0;
+ page[0] = __get_free_page(GFP_KERNEL);
+ if(!page[0])
+ return 0;
+ page[1] = __get_free_page(GFP_KERNEL);
+ if(!page[1]) {
+ free_page(page[0]);
+ return 0;
+ }
+ saddr += (PAGE_SIZE << 1);
+ sun4c_put_pte(saddr, BUCKET_PTE(page[0]));
+ sun4c_put_pte(saddr + PAGE_SIZE, BUCKET_PTE(page[1]));
+ return saddr;
+}
+
+static void sun4c_free_kernel_stack(unsigned long stack)
+{
+ unsigned long page[2];
+
+ page[0] = BUCKET_PTE_PAGE(sun4c_get_pte(stack));
+ page[1] = BUCKET_PTE_PAGE(sun4c_get_pte(stack+PAGE_SIZE));
+ sun4c_flush_page(stack);
+ sun4c_flush_page(stack + PAGE_SIZE);
+ sun4c_put_pte(stack, 0);
+ sun4c_put_pte(stack + PAGE_SIZE, 0);
+ free_page(page[0]);
+ free_page(page[1]);
+}
+
+static void sun4c_free_task_struct(struct task_struct *tsk)
+{
+ unsigned long tsaddr = (unsigned long) tsk;
+ unsigned long page = BUCKET_PTE_PAGE(sun4c_get_pte(tsaddr));
+ int entry = BUCKET_NUM(tsaddr);
+
+ sun4c_flush_page(tsaddr);
+ sun4c_put_pte(tsaddr, 0);
+ sun4c_bucket[entry] = BUCKET_EMPTY;
+ free_page(page);
+ garbage_collect(entry);
+}
+
+static void sun4c_init_buckets(void)
+{
+ int entry;
+
+ if(sizeof(struct task_bucket) != (PAGE_SIZE << 2)) {
+ prom_printf("task bucket not 4 pages!\n");
+ prom_halt();
+ }
+ for(entry = 0; entry < NR_TASKS; entry++)
+ sun4c_bucket[entry] = BUCKET_EMPTY;
+}
+
+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;
+ save_and_cli(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;
+ sun4c_flush_page(vpage);
+ sun4c_put_pte(apage, pte);
+ vpage += PAGE_SIZE;
+ }
+ restore_flags(flags);
+ return (char *) ((base << PAGE_SHIFT) + sun4c_iobuffer_start +
+ (((unsigned long) vaddr) & ~PAGE_MASK));
+
+abend:
+ restore_flags(flags);
+ printk("DMA vaddr=0x%p size=%08lx\n", vaddr, size);
+ panic("Out of iobuffer table");
+ return 0;
+}
+
+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;
+ while (npages != 0) {
+ --npages;
+ sun4c_put_pte(vpage, 0);
+ clear_bit((vpage - sun4c_iobuffer_start) >> PAGE_SHIFT,
+ sun4c_iobuffer_map);
+ vpage += PAGE_SIZE;
+ }
+
+ /* garbage collect */
+ save_and_cli(flags);
+ 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);
+ }
+ restore_flags(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 char *sun4c_get_scsi_one(char *bufptr, unsigned long len, struct linux_sbus *sbus)
+{
+ unsigned long page;
+
+ page = ((unsigned long) bufptr) & PAGE_MASK;
+ if(MAP_NR(page) > max_mapnr)
+ return bufptr; /* already locked */
+ return sun4c_lockarea(bufptr, len);
+}
+
+static void sun4c_get_scsi_sgl(struct mmu_sglist *sg, int sz, struct linux_sbus *sbus)
+{
+ while(sz >= 0) {
+ sg[sz].dvma_addr = sun4c_lockarea(sg[sz].addr, sg[sz].len);
+ sz--;
+ }
+}
+
+static void sun4c_release_scsi_one(char *bufptr, unsigned long len, struct linux_sbus *sbus)
+{
+ unsigned long page = (unsigned long) bufptr;
+
+ if(page < sun4c_iobuffer_start)
+ return; /* On kernel stack or similar, see above */
+ sun4c_unlockarea(bufptr, len);
+}
+
+static void sun4c_release_scsi_sgl(struct mmu_sglist *sg, int sz, struct linux_sbus *sbus)
+{
+ while(sz >= 0) {
+ sun4c_unlockarea(sg[sz].dvma_addr, sg[sz].len);
+ sz--;
+ }
+}
+
+#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 unsigned long sun4c_init_lock_areas(unsigned long start_mem)
+{
+ 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_TASKS));
+ if(sun4c_taskstack_end >= SUN4C_LOCK_END) {
+ prom_printf("Too many tasks, decrease NR_TASKS 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 = (unsigned long *) start_mem;
+ memset((void *) start_mem, 0, bitmap_size);
+ start_mem += bitmap_size;
+
+ /* Now get us some mmu entries for I/O maps. */
+ /* sun4c_init_lock_area(sun4c_iobuffer_start, sun4c_iobuffer_end); */
+ sun4c_kstack_vma.vm_mm = init_task.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);
+ return start_mem;
+}
+
+/* Cache flushing on the sun4c. */
+static void sun4c_flush_cache_all(void)
+{
+ /* Clear all tags in the sun4c cache.
+ * The cache is write through so this is safe.
+ */
+ flush_user_windows();
+ __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] %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"
+ "sta %%g0, [%0 + %%o4] %3\n\t"
+ "bg 1b\n\t"
+ " add %0, %%o5, %0\n\t" : :
+ "r" (AC_CACHETAGS),
+ "r" (sun4c_vacinfo.num_bytes),
+ "r" (sun4c_vacinfo.linesize),
+ "i" (ASI_CONTROL) :
+ "g1", "g2", "g3", "g4", "g5", "o4", "o5");
+}
+
+static void sun4c_flush_cache_mm(struct mm_struct *mm)
+{
+ int octx;
+
+#ifndef __SMP__
+ if(mm->context != NO_CONTEXT) {
+#endif
+ octx = sun4c_get_context();
+ flush_user_windows();
+ sun4c_set_context(mm->context);
+ sun4c_flush_context();
+ sun4c_set_context(octx);
+#ifndef __SMP__
+ }
+#endif
+}
+
+
+static void sun4c_flush_cache_range(struct mm_struct *mm, unsigned long start, unsigned long end)
+{
+ int size, size2, octx, i;
+ unsigned long start2,end2;
+ struct sun4c_mmu_entry *entry,*entry2;
+
+ /* don't flush kernel memory as its always valid in
+ all contexts */
+ if (start >= PAGE_OFFSET)
+ return;
+
+#if KGPROF_PROFILING
+ kgprof_profile();
+#endif
+
+#ifndef __SMP__
+ if(mm->context != NO_CONTEXT) {
+#endif
+ size = end - start;
+
+ octx = sun4c_get_context();
+ flush_user_windows();
+ sun4c_set_context(mm->context);
+
+ entry = sun4c_context_ring[mm->context].ringhd.next;
+ i = sun4c_context_ring[mm->context].num_entries;
+ while (i--) {
+ entry2 = entry->next;
+ if (entry->vaddr < start || entry->vaddr >= end)
+ goto next_entry;
+
+ start2 = MAX(start,entry->vaddr);
+ end2 = MIN(end,entry->vaddr+SUN4C_REAL_PGDIR_SIZE);
+ size2 = end2 - start2;
+
+ if (size2 <= (PAGE_SIZE << 3)) {
+ start2 &= PAGE_MASK;
+ while(start2 < end2) {
+ sun4c_flush_page(start2);
+ start2 += PAGE_SIZE;
+ }
+ } else {
+ start2 &= SUN4C_REAL_PGDIR_MASK;
+ sun4c_flush_segment(start2);
+ /* we are betting that the entry will not be
+ needed for a while */
+ sun4c_user_unmap(entry);
+ free_user_entry(mm->context, entry);
+ }
+
+ next_entry:
+ entry = entry2;
+ }
+ sun4c_set_context(octx);
+#ifndef __SMP__
+ }
+#endif
+}
+
+static void sun4c_flush_cache_page(struct vm_area_struct *vma, unsigned long page)
+{
+ int octx;
+ struct mm_struct *mm = vma->vm_mm;
+
+ /* don't flush kernel memory as its always valid in
+ all contexts */
+ if (page >= PAGE_OFFSET)
+ return;
+
+ /* Sun4c has no separate I/D caches so cannot optimize for non
+ * text page flushes.
+ */
+#ifndef __SMP__
+ if(mm->context != NO_CONTEXT) {
+#endif
+ octx = sun4c_get_context();
+ flush_user_windows();
+ sun4c_set_context(mm->context);
+ sun4c_flush_page(page);
+ sun4c_set_context(octx);
+#ifndef __SMP__
+ }
+#endif
+}
+
+/* Sun4c cache is write-through, so no need to validate main memory
+ * during a page copy in kernel space.
+ */
+static void sun4c_flush_page_to_ram(unsigned long page)
+{
+}
+
+/* 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)
+{
+ unsigned long flags;
+ int savectx, ctx, entry;
+
+ save_and_cli(flags);
+ savectx = sun4c_get_context();
+ for (entry = 0; entry < SUN4C_KERNEL_BUCKETS; entry++) {
+ if (sun4c_kernel_next->vaddr) {
+ for(ctx = 0; ctx < num_contexts; ctx++) {
+ sun4c_set_context(ctx);
+ sun4c_put_segmap(sun4c_kernel_next->vaddr,
+ invalid_segment);
+ }
+ sun4c_kernel_next->vaddr = 0;
+ }
+ sun4c_next_kernel_bucket(&sun4c_kernel_next);
+ }
+ sun4c_set_context(savectx);
+ restore_flags(flags);
+}
+
+static void sun4c_flush_tlb_mm(struct mm_struct *mm)
+{
+ struct sun4c_mmu_entry *this_entry, *next_entry;
+ struct sun4c_mmu_ring *crp;
+ int savectx, ctx;
+
+#ifndef __SMP__
+ if(mm->context != NO_CONTEXT) {
+#endif
+ crp = &sun4c_context_ring[mm->context];
+ savectx = sun4c_get_context();
+ ctx = mm->context;
+ this_entry = crp->ringhd.next;
+ flush_user_windows();
+ sun4c_set_context(mm->context);
+ sun4c_flush_context();
+ while(crp->num_entries) {
+ next_entry = this_entry->next;
+ sun4c_user_unmap(this_entry);
+ free_user_entry(ctx, this_entry);
+ this_entry = next_entry;
+ }
+ sun4c_set_context(savectx);
+#ifndef __SMP__
+ }
+#endif
+}
+
+static void sun4c_flush_tlb_range(struct mm_struct *mm, unsigned long start, unsigned long end)
+{
+ struct sun4c_mmu_entry *entry,*entry2;
+ unsigned char savectx;
+ int i;
+
+#ifndef __SMP__
+ if(mm->context == NO_CONTEXT)
+ return;
+#endif
+
+#if KGPROF_PROFILING
+ kgprof_profile();
+#endif
+
+ savectx = sun4c_get_context();
+ sun4c_set_context(mm->context);
+ start &= SUN4C_REAL_PGDIR_MASK;
+
+ entry = sun4c_context_ring[mm->context].ringhd.next;
+ i = sun4c_context_ring[mm->context].num_entries;
+ while (i--) {
+ entry2 = entry->next;
+ if (entry->vaddr >= start && entry->vaddr < end) {
+ sun4c_flush_segment(entry->vaddr);
+ sun4c_user_unmap(entry);
+ free_user_entry(mm->context, entry);
+ }
+ entry = entry2;
+ }
+ sun4c_set_context(savectx);
+}
+
+
+static void sun4c_flush_tlb_page(struct vm_area_struct *vma, unsigned long page)
+{
+ struct mm_struct *mm = vma->vm_mm;
+ int savectx;
+
+#ifndef __SMP__
+ if(mm->context != NO_CONTEXT) {
+#endif
+ savectx = sun4c_get_context();
+ sun4c_set_context(mm->context);
+ page &= PAGE_MASK;
+ if(sun4c_get_pte(page) & _SUN4C_PAGE_VALID)
+ sun4c_put_pte(page, 0);
+ sun4c_set_context(savectx);
+#ifndef __SMP__
+ }
+#endif
+}
+
+/* Sun4c mmu hardware doesn't update the dirty bit in the pte's
+ * for us, so we do it in software.
+ */
+static void sun4c_set_pte(pte_t *ptep, pte_t pte)
+{
+
+ if((pte_val(pte) & (_SUN4C_PAGE_WRITE|_SUN4C_PAGE_DIRTY)) ==
+ _SUN4C_PAGE_WRITE)
+ pte_val(pte) |= _SUN4C_PAGE_DIRTY;
+
+ *ptep = pte;
+}
+
+void sun4c_mapioaddr(unsigned long physaddr, unsigned long virt_addr,
+ int bus_type, int rdonly)
+{
+ unsigned long page_entry;
+
+ page_entry = ((physaddr >> PAGE_SHIFT) & 0xffff);
+ page_entry |= (_SUN4C_PAGE_VALID | _SUN4C_PAGE_WRITE |
+ _SUN4C_PAGE_NOCACHE | _SUN4C_PAGE_IO);
+ if(rdonly)
+ page_entry &= (~_SUN4C_PAGE_WRITE);
+ sun4c_flush_page(virt_addr);
+ sun4c_put_pte(virt_addr, page_entry);
+}
+
+void sun4c_unmapioaddr(unsigned long virt_addr)
+{
+ sun4c_flush_page(virt_addr); /* XXX P3: Is it necessary for I/O page? */
+ sun4c_put_pte(virt_addr, 0);
+}
+
+static inline void sun4c_alloc_context(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 == current->mm)
+ ctxp = ctxp->next;
+ if(ctxp == &ctx_used)
+ panic("out of mmu contexts");
+ 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);
+}
+
+#if some_day_soon /* We need some tweaking to start using this */
+extern void force_user_fault(unsigned long, int);
+
+void sun4c_switch_heuristic(struct pt_regs *regs)
+{
+ unsigned long sp = regs->u_regs[UREG_FP];
+ unsigned long sp2 = sp + REGWIN_SZ - 0x8;
+
+ force_user_fault(regs->pc, 0);
+ force_user_fault(sp, 0);
+ if((sp&PAGE_MASK) != (sp2&PAGE_MASK))
+ force_user_fault(sp2, 0);
+}
+#endif
+
+static void sun4c_switch_to_context(struct task_struct *tsk)
+{
+ struct ctx_list *ctx;
+
+ if(tsk->mm->context == NO_CONTEXT) {
+ sun4c_alloc_context(tsk->mm);
+ goto set_context;
+ }
+
+ /* Update the LRU ring of contexts. */
+ ctx = ctx_list_pool + tsk->mm->context;
+ remove_from_ctx_list(ctx);
+ add_to_used_ctxlist(ctx);
+
+set_context:
+ sun4c_set_context(tsk->mm->context);
+}
+
+static void sun4c_flush_hook(void)
+{
+ if(current->tss.flags & SPARC_FLAG_KTHREAD) {
+ sun4c_alloc_context(current->mm);
+ sun4c_set_context(current->mm->context);
+ }
+}
+
+static void sun4c_exit_hook(void)
+{
+ struct ctx_list *ctx_old;
+ struct mm_struct *mm = current->mm;
+
+ if(mm->context != NO_CONTEXT && mm->count == 1) {
+ 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;
+ }
+}
+
+#if KGPROF_PROFILING
+static char s4cinfo[10240];
+#else
+static char s4cinfo[512];
+#endif
+
+static char *sun4c_mmu_info(void)
+{
+ 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;
+
+ sprintf(s4cinfo, "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"
+ "usedpsegs\t: %d\n"
+ "ufreepsegs\t: %d\n"
+ "context\t\t: %d flushes\n"
+ "segment\t\t: %d flushes\n"
+ "page\t\t: %d flushes\n",
+ sun4c_vacinfo.num_bytes,
+ (sun4c_vacinfo.do_hwflushes ? "yes" : "no"),
+ sun4c_vacinfo.linesize,
+ num_contexts,
+ (invalid_segment + 1),
+ invalid_segment - used_user_entries -
+ sun4c_ufree_ring.num_entries + 1,
+ used_user_entries,
+ sun4c_ufree_ring.num_entries,
+ ctxflushes, segflushes, pageflushes);
+
+#if KGPROF_PROFILING
+ {
+ char *p = s4cinfo + strlen(s4cinfo);
+ int i,j;
+ sprintf(p,"kgprof profiling:\n"); p += strlen(p);
+ for (i=0;i<KGPROF_SIZE && kgprof_counters[i].addr[0];i++) {
+ sprintf(p,"%5d ",kgprof_counters[i].count); p += strlen(p);
+ for (j=0;j<KGPROF_DEPTH;j++) {
+ sprintf(p,"%08x ",kgprof_counters[i].addr[j]);
+ p += strlen(p);
+ }
+ sprintf(p,"\n"); p += strlen(p);
+ }
+ }
+#endif
+
+ return s4cinfo;
+}
+
+/* Nothing below here should touch the mmu hardware nor the mmu_entry
+ * data structures.
+ */
+
+static unsigned int sun4c_pmd_align(unsigned int addr) { return SUN4C_PMD_ALIGN(addr); }
+static unsigned int sun4c_pgdir_align(unsigned int addr) { return SUN4C_PGDIR_ALIGN(addr); }
+
+/* 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 unsigned long sun4c_vmalloc_start(void)
+{
+ return SUN4C_VMALLOC_START;
+}
+
+static int sun4c_pte_none(pte_t pte) { return !pte_val(pte); }
+static int sun4c_pte_present(pte_t pte) { return pte_val(pte) & _SUN4C_PAGE_VALID; }
+static void sun4c_pte_clear(pte_t *ptep) { pte_val(*ptep) = 0; }
+
+static int sun4c_pmd_none(pmd_t pmd) { return !pmd_val(pmd); }
+static int sun4c_pmd_bad(pmd_t pmd)
+{
+ return (pmd_val(pmd) & ~PAGE_MASK) != PGD_TABLE ||
+ MAP_NR(pmd_val(pmd)) > max_mapnr;
+}
+
+static int sun4c_pmd_present(pmd_t pmd) { return pmd_val(pmd) & PGD_PRESENT; }
+static void sun4c_pmd_clear(pmd_t *pmdp) { pmd_val(*pmdp) = 0; }
+
+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 int sun4c_pte_write(pte_t pte) { return pte_val(pte) & _SUN4C_PAGE_WRITE; }
+static int sun4c_pte_dirty(pte_t pte) { return pte_val(pte) & _SUN4C_PAGE_DIRTY; }
+static int sun4c_pte_young(pte_t pte) { return pte_val(pte) & _SUN4C_PAGE_REF; }
+
+static pte_t sun4c_pte_wrprotect(pte_t pte) { pte_val(pte) &= ~_SUN4C_PAGE_WRITE; return pte; }
+static pte_t sun4c_pte_mkclean(pte_t pte) { pte_val(pte) &= ~_SUN4C_PAGE_DIRTY; return pte; }
+static pte_t sun4c_pte_mkold(pte_t pte) { pte_val(pte) &= ~_SUN4C_PAGE_REF; return pte; }
+static pte_t sun4c_pte_mkwrite(pte_t pte) { pte_val(pte) |= _SUN4C_PAGE_WRITE; return pte; }
+static pte_t sun4c_pte_mkdirty(pte_t pte) { pte_val(pte) |= _SUN4C_PAGE_DIRTY; return pte; }
+static pte_t sun4c_pte_mkyoung(pte_t pte) { pte_val(pte) |= _SUN4C_PAGE_REF; 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(unsigned long page, pgprot_t pgprot)
+{
+ return __pte(((page - PAGE_OFFSET) >> PAGE_SHIFT) | 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 pte_t sun4c_pte_modify(pte_t pte, pgprot_t newprot)
+{
+ return __pte((pte_val(pte) & _SUN4C_PAGE_CHG_MASK) | pgprot_val(newprot));
+}
+
+static unsigned long sun4c_pte_page(pte_t pte)
+{
+ return (PAGE_OFFSET + ((pte_val(pte) & 0xffff) << (PAGE_SHIFT)));
+}
+
+static unsigned long sun4c_pmd_page(pmd_t pmd)
+{
+ return (pmd_val(pmd) & PAGE_MASK);
+}
+
+/* to find an entry in a page-table-directory */
+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(pmd_t * dir, unsigned long address)
+{
+ return (pte_t *) sun4c_pmd_page(*dir) + ((address >> PAGE_SHIFT) & (SUN4C_PTRS_PER_PTE - 1));
+}
+
+/* Update the root mmu directory. */
+static void sun4c_update_rootmmu_dir(struct task_struct *tsk, pgd_t *pgdir)
+{
+}
+
+/* Allocate and free page tables. The xxx_kernel() versions are
+ * used to allocate a kernel page table - this turns on ASN bits
+ * if any, and marks the page tables reserved.
+ */
+static void sun4c_pte_free_kernel(pte_t *pte)
+{
+ free_page((unsigned long) pte);
+}
+
+static pte_t *sun4c_pte_alloc_kernel(pmd_t *pmd, unsigned long address)
+{
+ address = (address >> PAGE_SHIFT) & (SUN4C_PTRS_PER_PTE - 1);
+ if (sun4c_pmd_none(*pmd)) {
+ pte_t *page = (pte_t *) get_free_page(GFP_KERNEL);
+ if (sun4c_pmd_none(*pmd)) {
+ if (page) {
+ pmd_val(*pmd) = PGD_TABLE | (unsigned long) page;
+ return page + address;
+ }
+ pmd_val(*pmd) = PGD_TABLE | (unsigned long) BAD_PAGETABLE;
+ return NULL;
+ }
+ free_page((unsigned long) page);
+ }
+ if (sun4c_pmd_bad(*pmd)) {
+ printk("Bad pmd in pte_alloc_kernel: %08lx\n", pmd_val(*pmd));
+ pmd_val(*pmd) = PGD_TABLE | (unsigned long) BAD_PAGETABLE;
+ return NULL;
+ }
+ return (pte_t *) sun4c_pmd_page(*pmd) + address;
+}
+
+/*
+ * allocating and freeing a pmd is trivial: the 1-entry pmd is
+ * inside the pgd, so has no extra memory associated with it.
+ */
+static void sun4c_pmd_free_kernel(pmd_t *pmd)
+{
+ pmd_val(*pmd) = 0;
+}
+
+static pmd_t *sun4c_pmd_alloc_kernel(pgd_t *pgd, unsigned long address)
+{
+ return (pmd_t *) pgd;
+}
+
+static void sun4c_pte_free(pte_t *pte)
+{
+ free_page((unsigned long) pte);
+}
+
+static pte_t *sun4c_pte_alloc(pmd_t * pmd, unsigned long address)
+{
+ address = (address >> PAGE_SHIFT) & (SUN4C_PTRS_PER_PTE - 1);
+ if (sun4c_pmd_none(*pmd)) {
+ pte_t *page = (pte_t *) get_free_page(GFP_KERNEL);
+ if (sun4c_pmd_none(*pmd)) {
+ if (page) {
+ pmd_val(*pmd) = PGD_TABLE | (unsigned long) page;
+ return page + address;
+ }
+ pmd_val(*pmd) = PGD_TABLE | (unsigned long) BAD_PAGETABLE;
+ return NULL;
+ }
+ free_page((unsigned long) page);
+ }
+ if (sun4c_pmd_bad(*pmd)) {
+ printk("Bad pmd in pte_alloc: %08lx\n", pmd_val(*pmd));
+ pmd_val(*pmd) = PGD_TABLE | (unsigned long) BAD_PAGETABLE;
+ return NULL;
+ }
+ return (pte_t *) sun4c_pmd_page(*pmd) + address;
+}
+
+/*
+ * allocating and freeing a pmd is trivial: the 1-entry pmd is
+ * inside the pgd, so has no extra memory associated with it.
+ */
+static void sun4c_pmd_free(pmd_t * pmd)
+{
+ pmd_val(*pmd) = 0;
+}
+
+static pmd_t *sun4c_pmd_alloc(pgd_t * pgd, unsigned long address)
+{
+ return (pmd_t *) pgd;
+}
+
+static void sun4c_pgd_free(pgd_t *pgd)
+{
+ free_page((unsigned long) pgd);
+}
+
+static pgd_t *sun4c_pgd_alloc(void)
+{
+ return (pgd_t *) get_free_page(GFP_KERNEL);
+}
+
+extern unsigned long free_area_init(unsigned long, unsigned long);
+extern unsigned long sparc_context_init(unsigned long, int);
+extern unsigned long end;
+
+unsigned long sun4c_paging_init(unsigned long start_mem, unsigned long end_mem)
+{
+ int i, cnt;
+ unsigned long kernel_end;
+ extern unsigned long sparc_iobase_vaddr;
+
+ kernel_end = (unsigned long) &end;
+ kernel_end += (SUN4C_REAL_PGDIR_SIZE * 3);
+ kernel_end = SUN4C_REAL_PGDIR_ALIGN(kernel_end);
+ sun4c_probe_mmu();
+ invalid_segment = (num_segmaps - 1);
+ sun4c_init_mmu_entry_pool();
+ sun4c_init_rings(&start_mem);
+ 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_iobase_vaddr, IOBASE_END);
+ sun4c_init_lock_area(DVMA_VADDR, DVMA_END);
+ start_mem = sun4c_init_lock_areas(start_mem);
+ sun4c_init_fill_user_ring();
+
+ sun4c_set_context(0);
+ memset(swapper_pg_dir, 0, PAGE_SIZE);
+ memset(pg0, 0, PAGE_SIZE);
+ /* Save work later. */
+ pgd_val(swapper_pg_dir[SUN4C_VMALLOC_START>>SUN4C_PGDIR_SHIFT]) =
+ PGD_TABLE | (unsigned long) pg0;
+ sun4c_init_ss2_cache_bug();
+ start_mem = PAGE_ALIGN(start_mem);
+ /* start_mem = sun4c_init_alloc_dvma_pages(start_mem); */
+ start_mem = sparc_context_init(start_mem, num_contexts);
+ start_mem = free_area_init(start_mem, end_mem);
+ cnt = 0;
+ for(i = 0; i < num_segmaps; i++)
+ if(mmu_entry_pool[i].locked)
+ cnt++;
+ printk("SUN4C: %d mmu entries for the kernel\n", cnt);
+ return start_mem;
+}
+
+/* Load up routines and constants for sun4c mmu */
+void ld_mmu_sun4c(void)
+{
+ printk("Loading sun4c MMU routines\n");
+
+ /* First the constants */
+ pmd_shift = SUN4C_PMD_SHIFT;
+ pmd_size = SUN4C_PMD_SIZE;
+ pmd_mask = SUN4C_PMD_MASK;
+ pgdir_shift = SUN4C_PGDIR_SHIFT;
+ pgdir_size = SUN4C_PGDIR_SIZE;
+ pgdir_mask = SUN4C_PGDIR_MASK;
+
+ ptrs_per_pte = SUN4C_PTRS_PER_PTE;
+ ptrs_per_pmd = SUN4C_PTRS_PER_PMD;
+ ptrs_per_pgd = SUN4C_PTRS_PER_PGD;
+
+ page_none = SUN4C_PAGE_NONE;
+ page_shared = SUN4C_PAGE_SHARED;
+ page_copy = SUN4C_PAGE_COPY;
+ page_readonly = SUN4C_PAGE_READONLY;
+ page_kernel = SUN4C_PAGE_KERNEL;
+ pg_iobits = _SUN4C_PAGE_NOCACHE | _SUN4C_PAGE_IO | _SUN4C_PAGE_VALID
+ | _SUN4C_PAGE_WRITE | _SUN4C_PAGE_DIRTY;
+
+ /* Functions */
+#ifndef __SMP__
+ flush_cache_all = sun4c_flush_cache_all;
+ flush_cache_mm = sun4c_flush_cache_mm;
+ flush_cache_range = sun4c_flush_cache_range;
+ flush_cache_page = sun4c_flush_cache_page;
+
+ flush_tlb_all = sun4c_flush_tlb_all;
+ flush_tlb_mm = sun4c_flush_tlb_mm;
+ flush_tlb_range = sun4c_flush_tlb_range;
+ flush_tlb_page = sun4c_flush_tlb_page;
+#else
+ local_flush_cache_all = sun4c_flush_cache_all;
+ local_flush_cache_mm = sun4c_flush_cache_mm;
+ local_flush_cache_range = sun4c_flush_cache_range;
+ local_flush_cache_page = sun4c_flush_cache_page;
+
+ local_flush_tlb_all = sun4c_flush_tlb_all;
+ local_flush_tlb_mm = sun4c_flush_tlb_mm;
+ local_flush_tlb_range = sun4c_flush_tlb_range;
+ local_flush_tlb_page = sun4c_flush_tlb_page;
+
+ flush_cache_all = smp_flush_cache_all;
+ flush_cache_mm = smp_flush_cache_mm;
+ flush_cache_range = smp_flush_cache_range;
+ flush_cache_page = smp_flush_cache_page;
+
+ flush_tlb_all = smp_flush_tlb_all;
+ flush_tlb_mm = smp_flush_tlb_mm;
+ flush_tlb_range = smp_flush_tlb_range;
+ flush_tlb_page = smp_flush_tlb_page;
+#endif
+
+ flush_page_to_ram = sun4c_flush_page_to_ram;
+
+ set_pte = sun4c_set_pte;
+ switch_to_context = sun4c_switch_to_context;
+ pmd_align = sun4c_pmd_align;
+ pgdir_align = sun4c_pgdir_align;
+ vmalloc_start = sun4c_vmalloc_start;
+
+ pte_page = sun4c_pte_page;
+ pmd_page = sun4c_pmd_page;
+
+ sparc_update_rootmmu_dir = sun4c_update_rootmmu_dir;
+
+ pte_none = sun4c_pte_none;
+ pte_present = sun4c_pte_present;
+ pte_clear = sun4c_pte_clear;
+
+ pmd_none = sun4c_pmd_none;
+ pmd_bad = sun4c_pmd_bad;
+ pmd_present = sun4c_pmd_present;
+ pmd_clear = sun4c_pmd_clear;
+
+ pgd_none = sun4c_pgd_none;
+ pgd_bad = sun4c_pgd_bad;
+ pgd_present = sun4c_pgd_present;
+ pgd_clear = sun4c_pgd_clear;
+
+ mk_pte = sun4c_mk_pte;
+ mk_pte_phys = sun4c_mk_pte_phys;
+ mk_pte_io = sun4c_mk_pte_io;
+ pte_modify = sun4c_pte_modify;
+ pgd_offset = sun4c_pgd_offset;
+ pmd_offset = sun4c_pmd_offset;
+ pte_offset = sun4c_pte_offset;
+ pte_free_kernel = sun4c_pte_free_kernel;
+ pmd_free_kernel = sun4c_pmd_free_kernel;
+ pte_alloc_kernel = sun4c_pte_alloc_kernel;
+ pmd_alloc_kernel = sun4c_pmd_alloc_kernel;
+ pte_free = sun4c_pte_free;
+ pte_alloc = sun4c_pte_alloc;
+ pmd_free = sun4c_pmd_free;
+ pmd_alloc = sun4c_pmd_alloc;
+ pgd_free = sun4c_pgd_free;
+ pgd_alloc = sun4c_pgd_alloc;
+
+ pte_write = sun4c_pte_write;
+ pte_dirty = sun4c_pte_dirty;
+ pte_young = sun4c_pte_young;
+ pte_wrprotect = sun4c_pte_wrprotect;
+ pte_mkclean = sun4c_pte_mkclean;
+ pte_mkold = sun4c_pte_mkold;
+ pte_mkwrite = sun4c_pte_mkwrite;
+ pte_mkdirty = sun4c_pte_mkdirty;
+ pte_mkyoung = sun4c_pte_mkyoung;
+ update_mmu_cache = sun4c_update_mmu_cache;
+ mmu_exit_hook = sun4c_exit_hook;
+ mmu_flush_hook = sun4c_flush_hook;
+ mmu_lockarea = sun4c_lockarea;
+ mmu_unlockarea = sun4c_unlockarea;
+
+ mmu_get_scsi_one = sun4c_get_scsi_one;
+ mmu_get_scsi_sgl = sun4c_get_scsi_sgl;
+ mmu_release_scsi_one = sun4c_release_scsi_one;
+ mmu_release_scsi_sgl = sun4c_release_scsi_sgl;
+
+ mmu_map_dma_area = sun4c_map_dma_area;
+
+ mmu_v2p = sun4c_v2p;
+ mmu_p2v = sun4c_p2v;
+
+ /* Task struct and kernel stack allocating/freeing. */
+ alloc_kernel_stack = sun4c_alloc_kernel_stack;
+ alloc_task_struct = sun4c_alloc_task_struct;
+ free_kernel_stack = sun4c_free_kernel_stack;
+ free_task_struct = sun4c_free_task_struct;
+
+ quick_kernel_fault = sun4c_quick_kernel_fault;
+ mmu_info = sun4c_mmu_info;
+
+ /* These should _never_ get called with two level tables. */
+ pgd_set = 0;
+ pgd_page = 0;
+}
diff --git a/arch/sparc/mm/vac-flush.c b/arch/sparc/mm/vac-flush.c
deleted file mode 100644
index 796366b53..000000000
--- a/arch/sparc/mm/vac-flush.c
+++ /dev/null
@@ -1,94 +0,0 @@
-/* vac.c: Routines for flushing various amount of the Sparc VAC
- (virtual address cache).
-
- Copyright (C) 1994 David S. Miller (davem@caip.rutgers.edu)
-*/
-
-#include <asm/vac-ops.h>
-#include <asm/page.h>
-
-/* Flush all VAC entries for the current context */
-
-extern int vac_do_hw_vac_flushes, vac_size, vac_linesize;
-extern int vac_entries_per_context, vac_entries_per_segment;
-extern int vac_entries_per_page;
-
-void
-flush_vac_context()
-{
- register int entries_left, offset;
- register char* address;
-
- entries_left = vac_entries_per_context;
- address = (char *) 0;
-
- if(vac_do_hw_vac_flushes)
- {
- while(entries_left-- >=0)
- {
- hw_flush_vac_context_entry(address);
- address += PAGE_SIZE;
- }
- }
- else
- {
- offset = vac_linesize;
- while(entries_left-- >=0)
- {
- sw_flush_vac_context_entry(address);
- address += offset;
- }
- }
-}
-
-void
-flush_vac_segment(register unsigned int segment)
-{
- register int entries_left, offset;
- register char* address = (char *) 0;
-
- entries_left = vac_entries_per_segment;
- __asm__ __volatile__("sll %0, 18, %1\n\t"
- "sra %1, 0x2, %1\n\t"
- : "=r" (segment) : "0" (address));
-
- if(vac_do_hw_vac_flushes)
- {
- while(entries_left-- >=0)
- {
- hw_flush_vac_segment_entry(address);
- address += PAGE_SIZE;
- }
- }
- else
- {
- offset = vac_linesize;
- while(entries_left-- >=0)
- {
- sw_flush_vac_segment_entry(address);
- address += offset;
- }
- }
-}
-
-void
-flush_vac_page(register unsigned int addr)
-{
- register int entries_left, offset;
-
- if(vac_do_hw_vac_flushes)
- {
- hw_flush_vac_page_entry((unsigned long *) addr);
- }
- else
- {
- entries_left = vac_entries_per_page;
- offset = vac_linesize;
- while(entries_left-- >=0)
- {
- sw_flush_vac_page_entry((unsigned long *) addr);
- addr += offset;
- }
- }
-}
-