diff options
author | Ralf Baechle <ralf@linux-mips.org> | 1997-04-29 21:13:14 +0000 |
---|---|---|
committer | <ralf@linux-mips.org> | 1997-04-29 21:13:14 +0000 |
commit | 19c9bba94152148523ba0f7ef7cffe3d45656b11 (patch) | |
tree | 40b1cb534496a7f1ca0f5c314a523c69f1fee464 /arch/sparc64/mm | |
parent | 7206675c40394c78a90e74812bbdbf8cf3cca1be (diff) |
Import of Linux/MIPS 2.1.36
Diffstat (limited to 'arch/sparc64/mm')
-rw-r--r-- | arch/sparc64/mm/Makefile | 13 | ||||
-rw-r--r-- | arch/sparc64/mm/asyncd.c | 272 | ||||
-rw-r--r-- | arch/sparc64/mm/extable.c | 69 | ||||
-rw-r--r-- | arch/sparc64/mm/fault.c | 201 | ||||
-rw-r--r-- | arch/sparc64/mm/generic.c | 124 | ||||
-rw-r--r-- | arch/sparc64/mm/init.c | 730 |
6 files changed, 1409 insertions, 0 deletions
diff --git a/arch/sparc64/mm/Makefile b/arch/sparc64/mm/Makefile new file mode 100644 index 000000000..c41c7a938 --- /dev/null +++ b/arch/sparc64/mm/Makefile @@ -0,0 +1,13 @@ +# $Id: Makefile,v 1.1 1996/12/26 10:24:22 davem Exp $ +# Makefile for the linux Sparc64-specific parts of the memory manager. +# +# Note! Dependencies are done automagically by 'make dep', which also +# removes any old dependencies. DON'T put your own dependencies here +# unless it's something special (ie not a .c file). +# +# Note 2! The CFLAGS definition is now in the main makefile... + +O_TARGET := mm.o +O_OBJS := fault.o init.o generic.o asyncd.o extable.o + +include $(TOPDIR)/Rules.make diff --git a/arch/sparc64/mm/asyncd.c b/arch/sparc64/mm/asyncd.c new file mode 100644 index 000000000..4e7de16fb --- /dev/null +++ b/arch/sparc64/mm/asyncd.c @@ -0,0 +1,272 @@ +/* $Id: asyncd.c,v 1.1 1996/12/26 10:24:24 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 <linux/config.h> +#include <linux/interrupt.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> + +#define DEBUG 0 + +#define WRITE_LIMIT 100 +#define LOOP_LIMIT 200 + +static struct { + int faults, read, write, success, failure, errors; +} stats; + +/* + * 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) { + printk("ERROR: out of memory in asyncd\n"); + a->callback(taskid,address,write,1); + return; + } + + if (write) + stats.write++; + else + stats.read++; + + 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; + + stats.faults++; + +#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); + mark_bh(TQUEUE_BH); +} + +static int fault_in_page(int taskid, + struct vm_area_struct *vma, + unsigned address,int write) +{ + static unsigned last_address; + static int last_task, loop_counter; + 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; + + if (address == last_address && taskid == last_task) { + loop_counter++; + } else { + loop_counter = 0; + last_address = address; + last_task = taskid; + } + + if (loop_counter == WRITE_LIMIT && !write) { + printk("MSC bug? setting write request\n"); + stats.errors++; + write = 1; + } + + if (loop_counter == LOOP_LIMIT) { + printk("MSC bug? failing request\n"); + stats.errors++; + return 1; + } + + 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: + stats.success++; + update_mmu_cache(vma, address, *pte); + return 0; + +no_memory: + stats.failure++; + oom(tsk); + return 1; + +bad_area: + stats.failure++; + 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; + unsigned flags; + + while (async_queue) { + volatile struct async_job *a; + struct mm_struct *mm; + struct vm_area_struct *vma; + + save_flags(flags); cli(); + a = async_queue; + async_queue = async_queue->next; + restore_flags(flags); + + mm = a->mm; + + down(&mm->mmap_sem); + vma = find_vma(mm, a->address); + ret = fault_in_page(a->taskid,vma,a->address,a->write); +#if DEBUG + printk("fault_in_page(task=%d addr=%x write=%d) = %d\n", + a->taskid,a->address,a->write,ret); +#endif + a->callback(a->taskid,a->address,a->write,ret); + up(&mm->mmap_sem); + kfree_s((void *)a,sizeof(*a)); + } +} + + +#if CONFIG_AP1000 +static void asyncd_info(void) +{ + printk("CID(%d) faults: total=%d read=%d write=%d success=%d fail=%d err=%d\n", + mpp_cid(),stats.faults, stats.read, stats.write, stats.success, + stats.failure, stats.errors); +} +#endif + + +/* + * 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 asyncd 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"); + +#if CONFIG_AP1000 + bif_add_debug_key('a',asyncd_info,"stats on asyncd"); +#endif + + while (1) { + unsigned flags; + + save_flags(flags); cli(); + + while (!async_queue) { + current->signal = 0; + interruptible_sleep_on(&asyncd_wait); + } + + restore_flags(flags); + + run_async_queue(); + } +} + diff --git a/arch/sparc64/mm/extable.c b/arch/sparc64/mm/extable.c new file mode 100644 index 000000000..b2df0e169 --- /dev/null +++ b/arch/sparc64/mm/extable.c @@ -0,0 +1,69 @@ +/* + * linux/arch/sparc64/mm/extable.c + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <asm/uaccess.h> + +extern const struct exception_table_entry __start___ex_table[]; +extern const struct exception_table_entry __stop___ex_table[]; + +static unsigned long +search_one_table(const struct exception_table_entry *start, + const struct exception_table_entry *last, + unsigned long value, unsigned long *g2) +{ + const struct exception_table_entry *first = start; + const struct exception_table_entry *mid; + long diff = 0; + while (first <= last) { + mid = (last - first) / 2 + first; + diff = mid->insn - value; + if (diff == 0) { + if (!mid->fixup) { + *g2 = 0; + return (mid + 1)->fixup; + } else + return mid->fixup; + } else if (diff < 0) + first = mid+1; + else + last = mid-1; + } + if (last->insn < value && !last->fixup && last[1].insn > value) { + *g2 = (value - last->insn)/4; + return last[1].fixup; + } + if (first > start && first[-1].insn < value + && !first[-1].fixup && first->insn < value) { + *g2 = (value - first[-1].insn)/4; + return first->fixup; + } + return 0; +} + +unsigned long +search_exception_table(unsigned long addr, unsigned long *g2) +{ + unsigned long ret; + +#ifndef CONFIG_MODULES + /* There is only the kernel to search. */ + ret = search_one_table(__start___ex_table, + __stop___ex_table-1, addr, g2); + if (ret) return ret; +#else + /* The kernel is the last "module" -- no need to treat it special. */ + struct module *mp; + for (mp = module_list; mp != NULL; mp = mp->next) { + if (mp->ex_table_start == NULL) + continue; + ret = search_one_table(mp->ex_table_start, + mp->ex_table_end-1, addr, g2); + if (ret) return ret; + } +#endif + + return 0; +} diff --git a/arch/sparc64/mm/fault.c b/arch/sparc64/mm/fault.c new file mode 100644 index 000000000..0dd118c8e --- /dev/null +++ b/arch/sparc64/mm/fault.c @@ -0,0 +1,201 @@ +/* $Id: fault.c,v 1.4 1997/03/11 17:37:07 jj Exp $ + * arch/sparc64/mm/fault.c: Page fault handlers for the 64-bit Sparc. + * + * Copyright (C) 1996 David S. Miller (davem@caip.rutgers.edu) + * Copyright (C) 1997 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + */ + +#include <asm/head.h> + +#include <linux/string.h> +#include <linux/types.h> +#include <linux/ptrace.h> +#include <linux/mman.h> +#include <linux/signal.h> +#include <linux/mm.h> +#include <linux/smp_lock.h> + +#include <asm/page.h> +#include <asm/pgtable.h> +#include <asm/openprom.h> +#include <asm/oplib.h> +#include <asm/uaccess.h> + +#define ELEMENTS(arr) (sizeof (arr)/sizeof (arr[0])) + +extern struct sparc_phys_banks sp_banks[SPARC_PHYS_BANKS]; + +/* Nice, simple, prom library does all the sweating for us. ;) */ +unsigned long prom_probe_memory (void) +{ + register struct linux_mlist_p1275 *mlist; + register unsigned long bytes, base_paddr, tally; + register int i; + + i = 0; + mlist = *prom_meminfo()->p1275_available; + bytes = tally = mlist->num_bytes; + base_paddr = (unsigned int) mlist->start_adr; + + sp_banks[0].base_addr = base_paddr; + sp_banks[0].num_bytes = bytes; + + while (mlist->theres_more != (void *) 0){ + i++; + mlist = mlist->theres_more; + bytes = mlist->num_bytes; + tally += bytes; + if (i >= SPARC_PHYS_BANKS-1) { + printk ("The machine has more banks than " + "this kernel can support\n" + "Increase the SPARC_PHYS_BANKS " + "setting (currently %d)\n", + SPARC_PHYS_BANKS); + i = SPARC_PHYS_BANKS-1; + break; + } + + sp_banks[i].base_addr = (unsigned long) mlist->start_adr; + sp_banks[i].num_bytes = mlist->num_bytes; + } + + i++; + sp_banks[i].base_addr = 0xdeadbeef; + sp_banks[i].num_bytes = 0; + + /* Now mask all bank sizes on a page boundary, it is all we can + * use anyways. + */ + for(i=0; sp_banks[i].num_bytes != 0; i++) + sp_banks[i].num_bytes &= PAGE_MASK; + + return tally; +} + +/* Traverse the memory lists in the prom to see how much physical we + * have. + */ +unsigned long +probe_memory(void) +{ + unsigned long total; + + total = prom_probe_memory(); + + /* Oh man, much nicer, keep the dirt in promlib. */ + return total; +} + +void unhandled_fault(unsigned long address, struct task_struct *tsk, + struct pt_regs *regs) +{ + if((unsigned long) address < PAGE_SIZE) { + printk(KERN_ALERT "Unable to handle kernel NULL " + "pointer dereference"); + } else { + printk(KERN_ALERT "Unable to handle kernel paging request " + "at virtual address %016lx\n", (unsigned long)address); + } + printk(KERN_ALERT "tsk->mm->context = %016lx\n", + (unsigned long) tsk->mm->context); + printk(KERN_ALERT "tsk->mm->pgd = %016lx\n", + (unsigned long) tsk->mm->pgd); + die_if_kernel("Oops", regs); +} + +asmlinkage int lookup_fault(unsigned long pc, unsigned long ret_pc, + unsigned long address) +{ + unsigned long g2; + int i; + unsigned insn; + struct pt_regs regs; + + i = search_exception_table (ret_pc, &g2); + switch (i) { + /* load & store will be handled by fixup */ + case 3: return 3; + /* store will be handled by fixup, load will bump out */ + /* for _to_ macros */ + case 1: insn = *(unsigned *)pc; if ((insn >> 21) & 1) return 1; break; + /* load will be handled by fixup, store will bump out */ + /* for _from_ macros */ + case 2: insn = *(unsigned *)pc; + if (!((insn >> 21) & 1) || ((insn>>19)&0x3f) == 15) return 2; + break; + default: break; + } + memset (®s, 0, sizeof (regs)); + regs.tpc = pc; + regs.tnpc = pc + 4; + /* FIXME: Should set up regs->tstate? */ + unhandled_fault (address, current, ®s); + /* Not reached */ + return 0; +} + +asmlinkage void do_sparc64_fault(struct pt_regs *regs, int text_fault, int write, + unsigned long address) +{ + struct vm_area_struct *vma; + struct task_struct *tsk = current; + struct mm_struct *mm = tsk->mm; + unsigned long fixup; + unsigned long g2; + int from_user = !(regs->tstate & TSTATE_PRIV); + + lock_kernel (); + 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; + /* + * 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); + goto out; + /* + * 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); + /* Is this in ex_table? */ + + g2 = regs->u_regs[UREG_G2]; + if (!from_user && (fixup = search_exception_table (regs->tpc, &g2))) { + printk("Exception: PC<%016lx> faddr<%016lx>\n", regs->tpc, address); + printk("EX_TABLE: insn<%016lx> fixup<%016lx> g2<%016lx>\n", + regs->tpc, fixup, g2); + regs->tpc = fixup; + regs->tnpc = regs->tpc + 4; + regs->u_regs[UREG_G2] = g2; + goto out; + } + if(from_user) { + tsk->tss.sig_address = address; + tsk->tss.sig_desc = SUBSIG_NOMAPPING; + send_sig(SIGSEGV, tsk, 1); + goto out; + } + unhandled_fault (address, tsk, regs); +out: + unlock_kernel(); +} diff --git a/arch/sparc64/mm/generic.c b/arch/sparc64/mm/generic.c new file mode 100644 index 000000000..289ddd411 --- /dev/null +++ b/arch/sparc64/mm/generic.c @@ -0,0 +1,124 @@ +/* $Id: generic.c,v 1.1 1996/12/26 10:24:23 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; + + prot = __pgprot(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/sparc64/mm/init.c b/arch/sparc64/mm/init.c new file mode 100644 index 000000000..57ca5eb92 --- /dev/null +++ b/arch/sparc64/mm/init.c @@ -0,0 +1,730 @@ +/* $Id: init.c,v 1.24 1997/04/17 21:49:41 jj Exp $ + * arch/sparc64/mm/init.c + * + * Copyright (C) 1996,1997 David S. Miller (davem@caip.rutgers.edu) + * Copyright (C) 1997 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + */ + +#include <linux/string.h> +#include <linux/init.h> +#include <linux/blk.h> +#include <linux/swap.h> + +#include <asm/system.h> +#include <asm/page.h> +#include <asm/pgtable.h> +#include <asm/oplib.h> +#include <asm/iommu.h> +#include <asm/io.h> +#include <asm/mmu_context.h> +#include <asm/vaddrs.h> + +extern void show_net_buffers(void); +extern unsigned long device_scan(unsigned long); + +struct sparc_phys_banks sp_banks[SPARC_PHYS_BANKS]; + +/* Ugly, but necessary... -DaveM */ +unsigned long phys_base, null_pmd_table, null_pte_table; + +extern unsigned long empty_null_pmd_table; +extern unsigned long empty_null_pte_table; + +unsigned long tlb_context_cache = CTX_FIRST_VERSION; + +/* References to section boundaries */ +extern char __init_begin, __init_end, etext, __p1275_loc, __bss_start; + +/* + * 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 an inode + * unused etc.. + * + * BAD_PAGETABLE is the accompanying page-table: it is initialized + * to point to BAD_PAGE entries. + * + * ZERO_PAGE is a special page that is used for zero-initialized + * data and COW. + */ +pmd_t *__bad_pmd(void) +{ + pmd_t *pmdp = (pmd_t *) &empty_bad_pmd_table; + + __init_pmd(pmdp); + return pmdp; +} + +pte_t *__bad_pte(void) +{ + memset((void *) &empty_bad_pte_table, 0, PAGE_SIZE); + return (pte_t *) (((unsigned long)&empty_bad_pte_table) + phys_base); +} + +pte_t __bad_page(void) +{ + memset((void *) &empty_bad_page, 0, PAGE_SIZE); + return pte_mkdirty(mk_pte((((unsigned long) &empty_bad_page)+phys_base), + PAGE_SHARED)); +} + +void show_mem(void) +{ + int i,free = 0,total = 0,reserved = 0; + int shared = 0; + + printk("\nMem-info:\n"); + show_free_areas(); + printk("Free swap: %6dkB\n",nr_swap_pages<<(PAGE_SHIFT-10)); + i = max_mapnr; + while (i-- > 0) { + total++; + if (PageReserved(mem_map + i)) + reserved++; + else if (!atomic_read(&mem_map[i].count)) + free++; + else + shared += atomic_read(&mem_map[i].count) - 1; + } + printk("%d pages of RAM\n",total); + printk("%d free pages\n",free); + printk("%d reserved pages\n",reserved); + printk("%d pages shared\n",shared); + show_buffers(); +#ifdef CONFIG_NET + show_net_buffers(); +#endif +} + +/* IOMMU support, the ideas are right, the code should be cleaned a bit still... */ + +/* XXX Also, play with the streaming buffers at some point, both + * XXX Fusion and Sunfire both have them aparently... -DaveM + */ + +/* This keeps track of pages used in sparc_alloc_dvma() invocations. */ +static unsigned long dvma_map_pages[0x10000000 >> 16] = { 0, }; +static unsigned long dvma_pages_current_offset = 0; +static int dvma_pages_current_index = 0; + +__initfunc(unsigned long iommu_init(int iommu_node, unsigned long memory_start, + unsigned long memory_end, struct linux_sbus *sbus)) +{ + struct iommu_struct *iommu; + struct sysio_regs *sregs; + struct linux_prom_registers rprop[2]; + unsigned long impl, vers; + unsigned long control, tsbbase; + unsigned long *iopte; + int err, i; + + err = prom_getproperty(iommu_node, "reg", (char *)rprop, + sizeof(rprop)); + if(err == -1) { + prom_printf("iommu_init: Cannot map SYSIO control registers.\n"); + prom_halt(); + } + sregs = (struct sysio_regs *) sparc_alloc_io(rprop[0].phys_addr, + (void *)0, + sizeof(struct sysio_regs), + "SYSIO Regs", + rprop[0].which_io, 0x0); + + memory_start = (memory_start + 7) & ~7; + iommu = (struct iommu_struct *) memory_start; + memory_start += sizeof(struct iommu_struct); + iommu->sysio_regs = sregs; + sbus->iommu = iommu; + + control = sregs->iommu_control; + impl = (control & IOMMU_CTRL_IMPL) >> 60; + vers = (control & IOMMU_CTRL_VERS) >> 56; + printk("IOMMU: IMPL[%x] VERS[%x] SYSIO mapped at %016lx\n", + (unsigned int) impl, (unsigned int)vers, (unsigned long) sregs); + + control &= ~(IOMMU_CTRL_TSBSZ); + control |= (IOMMU_TSBSZ_64K | IOMMU_CTRL_TBWSZ | IOMMU_CTRL_ENAB); + + /* Use only 64k pages, things are layed out in the 32-bit SBUS + * address space like this: + * + * 0x00000000 ---------------------------------------- + * | Direct physical mappings for most | + * | DVMA to paddr's within this range | + * 0xf0000000 ---------------------------------------- + * | For mappings requested via | + * | sparc_alloc_dvma() | + * 0xffffffff ---------------------------------------- + */ + tsbbase = PAGE_ALIGN(memory_start); + memory_start = (tsbbase + ((64 * 1024) * 8)); + iommu->page_table = (iopte_t *) tsbbase; + iopte = (unsigned long *) tsbbase; + + /* Setup aliased mappings... */ + for(i = 0; i < (65536 - 4096); i++) { + *iopte = (IOPTE_VALID | IOPTE_64K | IOPTE_CACHE | IOPTE_WRITE); + *iopte |= (i << 16); + iopte++; + } + + /* Clear all sparc_alloc_dvma() maps. */ + for( ; i < 65536; i++) + *iopte++ = 0; + + sregs->iommu_tsbbase = __pa(tsbbase); + sregs->iommu_control = control; + + return memory_start; +} + +void mmu_map_dma_area(unsigned long addr, int len, __u32 *dvma_addr) +{ + struct iommu_struct *iommu = SBus_chain->iommu; /* GROSS ME OUT! */ + pgd_t *pgdp; + pmd_t *pmdp; + pte_t *ptep; + + /* Find out if we need to grab some pages. */ + if(!dvma_map_pages[dvma_pages_current_index] || + ((dvma_pages_current_offset + len) > (1 << 16))) { + unsigned long *iopte; + unsigned long newpages = __get_free_pages(GFP_KERNEL, 3, 0); + int i; + + if(!newpages) + panic("AIEEE cannot get DVMA pages."); + + memset((char *)newpages, 0, (1 << 16)); + + if(!dvma_map_pages[dvma_pages_current_index]) { + dvma_map_pages[dvma_pages_current_index] = newpages; + i = dvma_pages_current_index; + } else { + dvma_map_pages[dvma_pages_current_index + 1] = newpages; + i = dvma_pages_current_index + 1; + } + + /* Stick it in the IOMMU. */ + i = (65536 - 4096) + i; + iopte = (unsigned long *)(iommu->page_table + i); + *iopte = (IOPTE_VALID | IOPTE_64K | IOPTE_CACHE | IOPTE_WRITE); + *iopte |= __pa(newpages); + } + + /* Get this out of the way. */ + *dvma_addr = (__u32) ((0xf0000000) + + (dvma_pages_current_index << 16) + + (dvma_pages_current_offset)); + + while(len > 0) { + while((len > 0) && (dvma_pages_current_offset < (1 << 16))) { + pte_t pte; + unsigned long the_page = + dvma_map_pages[dvma_pages_current_index] + + dvma_pages_current_offset; + + /* Map the CPU's view. */ + pgdp = pgd_offset(init_task.mm, addr); + pmdp = pmd_alloc_kernel(pgdp, addr); + ptep = pte_alloc_kernel(pmdp, addr); + pte = mk_pte(the_page, PAGE_KERNEL); + set_pte(ptep, pte); + + dvma_pages_current_offset += PAGE_SIZE; + addr += PAGE_SIZE; + len -= PAGE_SIZE; + } + dvma_pages_current_index++; + dvma_pages_current_offset = 0; + } +} + +__u32 mmu_get_scsi_one(char *vaddr, unsigned long len, struct linux_sbus *sbus) +{ + __u32 sbus_addr = (__u32) __pa(vaddr); + + if((sbus_addr < 0xf0000000) && + ((sbus_addr + len) < 0xf0000000)) + return sbus_addr; + + /* "can't happen"... GFP_DMA assures this. */ + panic("Very high scsi_one mappings should never happen."); + return (__u32)0; +} + +void mmu_get_scsi_sgl(struct mmu_sglist *sg, int sz, struct linux_sbus *sbus) +{ + while(sz >= 0) { + __u32 page = (__u32) __pa(((unsigned long) sg[sz].addr)); + if((page < 0xf0000000) && + (page + sg[sz].len) < 0xf0000000) { + sg[sz].dvma_addr = page; + } else { + /* "can't happen"... GFP_DMA assures this. */ + panic("scsi_sgl high mappings should never happen."); + } + sz--; + } +} + +char *mmu_info(void) +{ + /* XXX */ + return "MMU Type: Spitfire\n\tFIXME: Write this\n"; +} + +static unsigned long mempool; + +struct linux_prom_translation { + unsigned long virt; + unsigned long size; + unsigned long data; +}; + +#define MAX_TRANSLATIONS 64 +static void inherit_prom_mappings(void) +{ + struct linux_prom_translation transl[MAX_TRANSLATIONS]; + pgd_t *pgdp; + pmd_t *pmdp; + pte_t *ptep; + int node, n, i; + + node = prom_finddevice("/virtual-memory"); + if ((n = prom_getproperty(node, "translations", (char *) transl, + sizeof(transl))) == -1) { + prom_printf("Couldn't get translation property\n"); + prom_halt(); + } + n = n / sizeof(transl[0]); + + for (i = 0; i < n; i++) { + unsigned long vaddr; + + if (transl[i].virt >= 0xf0000000 && transl[i].virt < 0x100000000) { + for (vaddr = transl[i].virt; + vaddr < transl[i].virt + transl[i].size; + vaddr += PAGE_SIZE) { + pgdp = pgd_offset(init_task.mm, vaddr); + if (pgd_none(*pgdp)) { + pmdp = sparc_init_alloc(&mempool, + PMD_TABLE_SIZE); + __init_pmd(pmdp); + pgd_set(pgdp, pmdp); + } + pmdp = pmd_offset(pgdp, vaddr); + if (pmd_none(*pmdp)) { + ptep = sparc_init_alloc(&mempool, + PTE_TABLE_SIZE); + pmd_set(pmdp, ptep); + } + ptep = pte_offset(pmdp, vaddr); + set_pte (ptep, __pte(transl[i].data | _PAGE_MODIFIED)); + transl[i].data += PAGE_SIZE; + } + } + } +} + +static void inherit_locked_prom_mappings(void) +{ + int i; + int dtlb_seen = 0; + int itlb_seen = 0; + + /* Fucking losing PROM has more mappings in the TLB, but + * it (conveniently) fails to mention any of these in the + * translations property. The only ones that matter are + * the locked PROM tlb entries, so we impose the following + * irrecovable rule on the PROM, it is allowed 1 locked + * entry in the ITLB and 1 in the DTLB. We move those + * (if necessary) up into tlb entry 62. + * + * Supposedly the upper 16GB of the address space is + * reserved for OBP, BUT I WISH THIS WAS DOCUMENTED + * SOMEWHERE!!!!!!!!!!!!!!!!! Furthermore the entire interface + * used between the client program and the firmware on sun5 + * systems to coordinate mmu mappings is also COMPLETELY + * UNDOCUMENTED!!!!!! Thanks S(t)un! + */ + for(i = 0; i < 62; i++) { + unsigned long data; + + data = spitfire_get_dtlb_data(i); + if(!dtlb_seen && (data & _PAGE_L)) { + unsigned long tag = spitfire_get_dtlb_tag(i); + __asm__ __volatile__("stxa %%g0, [%0] %1" + : : "r" (TLB_TAG_ACCESS), "i" (ASI_DMMU)); + membar("#Sync"); + spitfire_put_dtlb_data(i, 0x0UL); + membar("#Sync"); + + /* Re-install it. */ + __asm__ __volatile__("stxa %0, [%1] %2" + : : "r" (tag), "r" (TLB_TAG_ACCESS), + "i" (ASI_DMMU)); + membar("#Sync"); + spitfire_put_dtlb_data(62, data); + membar("#Sync"); + dtlb_seen = 1; + if(itlb_seen) + break; + } + data = spitfire_get_itlb_data(i); + if(!itlb_seen && (data & _PAGE_L)) { + unsigned long tag = spitfire_get_itlb_tag(i); + __asm__ __volatile__("stxa %%g0, [%0] %1" + : : "r" (TLB_TAG_ACCESS), "i" (ASI_IMMU)); + membar("#Sync"); + spitfire_put_itlb_data(i, 0x0UL); + membar("#Sync"); + + /* Re-install it. */ + __asm__ __volatile__("stxa %0, [%1] %2" + : : "r" (tag), "r" (TLB_TAG_ACCESS), + "i" (ASI_IMMU)); + membar("#Sync"); + spitfire_put_itlb_data(62, data); + membar("#Sync"); + itlb_seen = 1; + if(dtlb_seen) + break; + } + } +} + +__initfunc(static void +allocate_ptable_skeleton(unsigned long start, unsigned long end)) +{ + pgd_t *pgdp; + pmd_t *pmdp; + pte_t *ptep; + + while (start < end) { + pgdp = pgd_offset(init_task.mm, start); + if (pgd_none(*pgdp)) { + pmdp = sparc_init_alloc(&mempool, + PMD_TABLE_SIZE); + __init_pmd(pmdp); + pgd_set(pgdp, pmdp); + } + pmdp = pmd_offset(pgdp, start); + if (pmd_none(*pmdp)) { + ptep = sparc_init_alloc(&mempool, + PTE_TABLE_SIZE); + pmd_set(pmdp, ptep); + } + start = (start + PMD_SIZE) & PMD_MASK; + } +} + +/* + * Create a mapping for an I/O register. Have to make sure the side-effect + * bit is set. + */ + +void sparc_ultra_mapioaddr(unsigned long physaddr, unsigned long virt_addr, + int bus, int rdonly) +{ + pgd_t *pgdp = pgd_offset(init_task.mm, virt_addr); + pmd_t *pmdp = pmd_offset(pgdp, virt_addr); + pte_t *ptep = pte_offset(pmdp, virt_addr); + pte_t pte; + + physaddr &= PAGE_MASK; + + if(rdonly) + pte = mk_pte_phys(physaddr, __pgprot(pg_iobits)); + else + pte = mk_pte_phys(physaddr, __pgprot(pg_iobits | __DIRTY_BITS)); + + set_pte(ptep, pte); +} + +void sparc_ultra_unmapioaddr(unsigned long virt_addr) +{ + pgd_t *pgdp; + pmd_t *pmdp; + pte_t *ptep; + + pgdp = pgd_offset(init_task.mm, virt_addr); + pmdp = pmd_offset(pgdp, virt_addr); + ptep = pte_offset(pmdp, virt_addr); + + /* No need to flush uncacheable page. */ + pte_clear(ptep); +} + +#ifdef DEBUG_MMU +void sparc_ultra_dump_itlb(void) +{ + int slot; + + prom_printf ("Contents of itlb:\n"); + for (slot = 0; slot < 64; slot+=2) { + prom_printf ("%2x:%016lx,%016lx %2x:%016lx,%016lx\n", + slot, spitfire_get_itlb_tag(slot), spitfire_get_itlb_data(slot), + slot+1, spitfire_get_itlb_tag(slot+1), spitfire_get_itlb_data(slot+1)); + } +} + +void sparc_ultra_dump_dtlb(void) +{ + int slot; + + prom_printf ("Contents of dtlb:\n"); + for (slot = 0; slot < 64; slot+=2) { + prom_printf ("%2x:%016lx,%016lx %2x:%016lx,%016lx\n", + slot, spitfire_get_dtlb_tag(slot), spitfire_get_dtlb_data(slot), + slot+1, spitfire_get_dtlb_tag(slot+1), spitfire_get_dtlb_data(slot+1)); + } +} +#endif + +/* paging_init() sets up the page tables */ + +extern unsigned long free_area_init(unsigned long, unsigned long); + +__initfunc(unsigned long +paging_init(unsigned long start_mem, unsigned long end_mem)) +{ + extern unsigned long phys_base; + extern void setup_tba(unsigned long kpgdir); + extern void __bfill64(void *, unsigned long); + pgd_t *pgdp; + pmd_t *pmdp; + pte_t *ptep, pte; + int i; + + /* Must create 2nd locked DTLB entry if physical ram starts at + * 4MB absolute or higher, kernel image has been placed in the + * right place at PAGE_OFFSET but references to start_mem and pages + * will be to the perfect alias mapping, so set it up now. + */ + if(phys_base >= (4 * 1024 * 1024)) { + unsigned long alias_base = phys_base + PAGE_OFFSET; + unsigned long pte; + unsigned long flags; + + /* We assume physical memory starts at some 4mb multiple, + * if this were not true we wouldn't boot up to this point + * anyways. + */ + pte = phys_base | _PAGE_VALID | _PAGE_SZ4MB; + pte |= _PAGE_CP | _PAGE_CV | _PAGE_P | _PAGE_L | _PAGE_W; + save_flags(flags); cli(); + __asm__ __volatile__(" + stxa %1, [%0] %3 + stxa %2, [%5] %4 + membar #Sync + flush %%g4 + nop + nop + nop" + : /* No outputs */ + : "r" (TLB_TAG_ACCESS), "r" (alias_base), "r" (pte), + "i" (ASI_DMMU), "i" (ASI_DTLB_DATA_ACCESS), "r" (61 << 3) + : "memory"); + restore_flags(flags); + + /* Now set kernel pgd to upper alias so physical page computations + * work. + */ + init_mm.pgd += (phys_base / (sizeof(pgd_t *))); + } + + null_pmd_table = __pa(((unsigned long)&empty_null_pmd_table) + phys_base); + null_pte_table = __pa(((unsigned long)&empty_null_pte_table) + phys_base); + + pmdp = (pmd_t *) &empty_null_pmd_table; + for(i = 0; i < 1024; i++) + pmd_val(pmdp[i]) = null_pte_table; + + memset((void *) &empty_null_pte_table, 0, PAGE_SIZE); + + /* Now can init the kernel/bad page tables. */ + __bfill64((void *)swapper_pg_dir, null_pmd_table); + __bfill64((void *)&empty_bad_pmd_table, null_pte_table); + + /* We use mempool to create page tables, therefore adjust it up + * such that __pa() macros etc. work. + */ + mempool = PAGE_ALIGN(start_mem) + phys_base; + + /* FIXME: This should be done much nicer. + * Just now we allocate 64M for each. + */ + allocate_ptable_skeleton(IOBASE_VADDR, IOBASE_VADDR + 0x4000000); + allocate_ptable_skeleton(DVMA_VADDR, DVMA_VADDR + 0x4000000); + inherit_prom_mappings(); + allocate_ptable_skeleton(0, 0x8000 + PAGE_SIZE); + + /* Map prom interface page. */ + pgdp = pgd_offset(init_task.mm, 0x8000); + pmdp = pmd_offset(pgdp, 0x8000); + ptep = pte_offset(pmdp, 0x8000); + pte = mk_pte(((unsigned long)&__p1275_loc)+phys_base, PAGE_KERNEL); + set_pte(ptep, pte); + + /* Ok, we can use our TLB miss and window trap handlers safely. */ + setup_tba((unsigned long)init_mm.pgd); + + /* Kill locked PROM interface page mapping, the mapping will + * re-enter on the next PROM interface call via our TLB miss + * handlers. + */ + spitfire_flush_dtlb_primary_page(0x8000); + membar("#Sync"); + spitfire_flush_itlb_primary_page(0x8000); + membar("#Sync"); + + /* Really paranoid. */ + flushi(PAGE_OFFSET); + membar("#Sync"); + + /* Cleanup the extra locked TLB entry we created since we have the + * nice TLB miss handlers of ours installed now. + */ + if(phys_base >= (4 * 1024 * 1024)) { + /* We only created DTLB mapping of this stuff. */ + spitfire_flush_dtlb_nucleus_page(phys_base + PAGE_OFFSET); + membar("#Sync"); + + /* Paranoid */ + flushi(PAGE_OFFSET); + membar("#Sync"); + } + + inherit_locked_prom_mappings(); + + flush_tlb_all(); + + start_mem = free_area_init(PAGE_ALIGN(mempool), end_mem); + + return device_scan (PAGE_ALIGN (start_mem)); +} + +extern int min_free_pages; +extern int free_pages_low; +extern int free_pages_high; + +__initfunc(static void taint_real_pages(unsigned long start_mem, unsigned long end_mem)) +{ + unsigned long addr, tmp2 = 0; + + for(addr = PAGE_OFFSET; addr < end_mem; addr += PAGE_SIZE) { + if(addr >= PAGE_OFFSET && addr < start_mem) + addr = start_mem; + for(tmp2=0; sp_banks[tmp2].num_bytes != 0; tmp2++) { + unsigned long phys_addr = __pa(addr); + 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); + } + } +} + +__initfunc(void mem_init(unsigned long start_mem, unsigned long end_mem)) +{ + int codepages = 0; + int datapages = 0; + int initpages = 0; + int prompages = 0; + unsigned long tmp2, addr; + unsigned long data_end; + + end_mem &= PAGE_MASK; + max_mapnr = MAP_NR(end_mem); + high_memory = (void *) end_mem; + + start_mem = PAGE_ALIGN(start_mem); + num_physpages = (start_mem - phys_base - PAGE_OFFSET) >> PAGE_SHIFT; + + addr = PAGE_OFFSET; + 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; + } + + taint_real_pages(start_mem, end_mem); + data_end = start_mem - phys_base; + for (addr = PAGE_OFFSET; addr < end_mem; addr += PAGE_SIZE) { + if(PageReserved(mem_map + MAP_NR(addr))) { + if ((addr < (unsigned long) &etext) && (addr >= PAGE_OFFSET)) + codepages++; + else if((addr >= (unsigned long)&__init_begin && addr < (unsigned long)&__init_end)) + initpages++; + else if((addr >= (unsigned long)&__p1275_loc && addr < (unsigned long)&__bss_start)) + prompages++; + else if((addr < data_end) && (addr >= PAGE_OFFSET)) + datapages++; + continue; + } + atomic_set(&mem_map[MAP_NR(addr)].count, 1); + num_physpages++; +#ifdef CONFIG_BLK_DEV_INITRD + if (!initrd_start || + (addr < initrd_start || addr >= initrd_end)) +#endif + free_page(addr); + } + + tmp2 = nr_free_pages << PAGE_SHIFT; + + printk("Memory: %luk available (%dk kernel code, %dk data, %dk init, %dk prom) [%016lx,%016lx]\n", + tmp2 >> 10, + codepages << (PAGE_SHIFT-10), + datapages << (PAGE_SHIFT-10), + initpages << (PAGE_SHIFT-10), + prompages << (PAGE_SHIFT-10), + PAGE_OFFSET, end_mem); + + 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 free_initmem (void) +{ + unsigned long addr; + + addr = (unsigned long)(&__init_begin); + for (; addr < (unsigned long)(&__init_end); addr += PAGE_SIZE) { + mem_map[MAP_NR(addr)].flags &= ~(1 << PG_reserved); + atomic_set(&mem_map[MAP_NR(addr)].count, 1); + free_page(addr); + } +} + +void si_meminfo(struct sysinfo *val) +{ + int i; + + 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 (PageReserved(mem_map + i)) + continue; + val->totalram++; + if (!atomic_read(&mem_map[i].count)) + continue; + val->sharedram += atomic_read(&mem_map[i].count) - 1; + } + val->totalram <<= PAGE_SHIFT; + val->sharedram <<= PAGE_SHIFT; +} |