diff options
author | Ralf Baechle <ralf@linux-mips.org> | 1997-01-07 02:33:00 +0000 |
---|---|---|
committer | <ralf@linux-mips.org> | 1997-01-07 02:33:00 +0000 |
commit | beb116954b9b7f3bb56412b2494b562f02b864b1 (patch) | |
tree | 120e997879884e1b9d93b265221b939d2ef1ade1 /arch/sparc/mm | |
parent | 908d4681a1dc3792ecafbe64265783a86c4cccb6 (diff) |
Import of Linux/MIPS 2.1.14
Diffstat (limited to 'arch/sparc/mm')
-rw-r--r-- | arch/sparc/mm/Makefile | 27 | ||||
-rw-r--r-- | arch/sparc/mm/asyncd.c | 189 | ||||
-rw-r--r-- | arch/sparc/mm/fault.c | 398 | ||||
-rw-r--r-- | arch/sparc/mm/generic.c | 124 | ||||
-rw-r--r-- | arch/sparc/mm/init.c | 414 | ||||
-rw-r--r-- | arch/sparc/mm/loadmmu.c | 165 | ||||
-rw-r--r-- | arch/sparc/mm/srmmu.c | 3477 | ||||
-rw-r--r-- | arch/sparc/mm/sun4c.c | 1965 | ||||
-rw-r--r-- | arch/sparc/mm/vac-flush.c | 94 |
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; - } - } -} - |