/*
 *  linux/mm/memory.c
 *
 *  Copyright (C) 1991, 1992  Linus Torvalds
 *
 * This file is subject to the terms and conditions of the GNU General Public
 * License.  See the file README.legal in the main directory of this archive
 * for more details.
 */

/*
 *  Heavily modified 1993 by Hamish Macdonald for MC680X0 support
 */

/*
 * demand-loading started 01.12.91 - seems it is high on the list of
 * things wanted, and it should be easy to implement. - Linus
 */

/*
 * Ok, demand-loading was easy, shared pages a little bit tricker. Shared
 * pages started 02.12.91, seems to work. - Linus.
 *
 * Tested sharing by executing about 30 /bin/sh: under the old kernel it
 * would have taken more than the 6M I have free, but it worked well as
 * far as I could see.
 *
 * Also corrected some "invalidate()"s - I wasn't doing enough of them.
 */

/*
 * Real VM (paging to/from disk) started 18.12.91. Much more work and
 * thought has to go into this. Oh, well..
 * 19.12.91  -	works, somewhat. Sometimes I get faults, don't know why.
 *		Found it. Everything seems to work now.
 * 20.12.91  -	Ok, making the swap-device changeable like the root.
 */

#include <asm/system.h>
#include <asm/segment.h>

#include <linux/signal.h>
#include <linux/sched.h>
#include <linux/head.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/types.h>

#include <machine/ktypes.h>
#include <machine/sys_info.h>

#include <linux/mm.h>

ulong high_memory = 0;

/* offset from virtual to physical memory */
ulong vpoffset;

int nr_free_pages = 0;
ulong free_page_list = 0;
/*
 * The secondary free_page_list is used for malloc() etc things that
 * may need pages during interrupts etc. Normal get_free_page() operations
 * don't touch it, so it stays as a kind of "panic-list", that can be
 * accessed when all other mm tricks have failed.
 */
int nr_secondary_pages = 0;
ulong secondary_page_list = 0;

#define copy_page(from,to) (memcpy((void*)to,(const void *)from,PAGE_SIZE))

unsigned short * mem_map = NULL;

#define CODE_SPACE(addr,p) ((addr) < (p)->end_code)

/*
 * oom() prints a message (so that the user knows why the process died),
 * and gives the process an untrappable SIGSEGV.
 */
void oom(struct task_struct * task)
{
    printk("\nout of memory\n");
    task->sigaction[SIGKILL-1].sa_handler = NULL;
    task->blocked &= ~(1<<(SIGKILL-1));
    send_sig(SIGKILL,task,1);
}

static void free_one_table(ulong *page_dir)
{
    int j;
    ulong pg_table = *page_dir;
    ulong *page_table, pg_table_v;

    if (!pg_table)
	return;
    *page_dir = 0;
    pg_table_v = PTOV(pg_table & 0xfffff000);
    if (pg_table_v >= high_memory || !(pg_table & PAGE_TABLE)) {
	printk("Bad page table: [%08x]=%08x\n",page_dir,pg_table);
	return;
    }
    if (mem_map[MAP_NR(pg_table_v)] & MAP_PAGE_RESERVED)
	return;
    page_table = (ulong *) pg_table_v;

    for (j = 0 ; j < NUM_L2_ENTRIES ; j++, page_table++) {
	ulong pg = *page_table;

	if (!pg)
	    continue;
	*page_table = 0;
	if (pg & PAGE_PRESENT)
	    free_page(PTOV(0xfffff000 & pg));
	else
	    swap_free(pg);
    }
    free_page(pg_table_v);
}

/*
 * This function clears all user-level page tables of a process - this
 * is needed by execve(), so that old pages aren't in the way. Note that
 * unlike 'free_page_tables()', this function still leaves a valid
 * page-table-tree in memory: it just removes the user pages. The two
 * functions are similar, but there is a fundamental difference.
 */
void clear_page_tables(struct task_struct * tsk)
{
    int i;
    ulong *page_dir;

    if (!tsk)
	return;
    if (tsk == task[0])
	panic("task[0] (swapper) doesn't support exec()\n");

    page_dir = tsk->pmap.pagedir_v;
    if (!page_dir || page_dir == task[0]->pmap.pagedir_v) {
	printk("Trying to clear kernel page-directory: not good\n");
	return;
    }
    for (i = 0 ; i < NUM_L2_ENTRIES; i++, page_dir++)
	free_one_table(page_dir);
    invalidate();
    return;
}

/*
 * This function frees up all page tables of a process when it exits.
 */
void free_page_tables(struct task_struct * tsk)
{
    int i;
    ulong *page_dir;

    if (!tsk)
	return;
    if (tsk == task[0]) {
	printk("task[0] (swapper) killed: unable to recover\n");
	panic("Trying to free up swapper memory space");
    }
    page_dir = tsk->pmap.pagedir_v;
    if (!page_dir || page_dir == task[0]->pmap.pagedir_v) {
	printk("Trying to free kernel page-directory: not good\n");
	return;
    }

    for (i = 0 ; i < NUM_L2_ENTRIES; i++, page_dir++)
	free_one_table(page_dir);

    free_page((ulong)tsk->pmap.pagedir_v);
    tsk->pmap.pagedir_v = 0;
    tsk->pmap.pagedir_p = 0;
    invalidate();
}

/*
 * copy_page_tables() just copies the whole process memory range:
 * note the special handling of RESERVED (ie kernel) pages, which
 * means that they are always shared by all processes.
 */
int copy_page_tables(struct task_struct * tsk)
{
    int i;
    ulong *old_page_dir;
    ulong *new_page_dir;

    old_page_dir = current->pmap.pagedir_v;
    new_page_dir = (ulong *)get_free_page(GFP_KERNEL);
    if (!new_page_dir)
	return -ENOMEM;
    tsk->pmap.pagedir_v = new_page_dir;
    tsk->pmap.pagedir_p = VTOP(new_page_dir);
    tsk->pmap.crp[0] = 0x80000000 | SHORT_DESC;
    tsk->pmap.crp[1] = tsk->pmap.pagedir_p;

    /* set up new kernel stack */
    tsk->pmap.kstack_v = (ulong *)tsk->kernel_stack_page;
    tsk->pmap.kstack_p = VTOP(tsk->kernel_stack_page);

    /* if initial fork (from task 0 (swapper), don't copy page tables */
    if (current == task[0])
	goto end;

    for (i = 0 ; i < 1024 ; i++, old_page_dir++,new_page_dir++) {
	int j;
	ulong old_pg_table, *old_page_table;
	ulong new_pg_table, *new_page_table;

	/* read descriptor for page table */
	old_pg_table = *old_page_dir;

	/* if it is an invalid descriptor, continue to the next */
	if (!old_pg_table)
	    continue;

	/*
	 * if the address is too high, or it is not a short
	 * descriptor, things are screwy.
	 */

	if (PTOV(old_pg_table) >= high_memory || !(old_pg_table & PAGE_TABLE)) {
	    printk("copy_page_tables: bad page table: "
		   "probable memory corruption\n");
	    *old_page_dir = 0;
	    continue;
	}

	/*
	 * if it is a reserved entry (won't be, with the separate kernel
	 * and user virtual address spaces for the M68K port.
	 */
	if (mem_map[MAP_NR(PTOV(old_pg_table))] & MAP_PAGE_RESERVED) {
	    *new_page_dir = old_pg_table;
	    continue;
	}

	/* get a page for the new page table, can't allocate, no fork */
	new_pg_table = get_free_page(GFP_KERNEL);
	if (!new_pg_table) {
	    free_page_tables(tsk);
	    return -ENOMEM;
	}

	/* write the new descriptor to the new page directory */
	*new_page_dir = VTOP(new_pg_table) | PAGE_TABLE;

	/* process each page in the page table */
	old_page_table = (ulong *)PTOV(old_pg_table & 0xfffff000);
	new_page_table = (ulong *)new_pg_table;
	for (j = 0 ; j < 1024 ; j++,old_page_table++,new_page_table++) {
	    ulong pg;

	    /* read page descriptor from table */
	    pg = *old_page_table;

	    /* if invalid page, continue */
	    if (!pg)
		continue;

	    /* check for swapped out page (invalid desc, nonzero) */
	    if (!(pg & PAGE_PRESENT)) {
		*new_page_table = swap_duplicate(pg);
		continue;
	    }

	    /*
	     * share the page in both process maps, but write protect
	     * it in both, to catch attempts to write (copy on write)
	     */
	    if ((pg & (PAGE_RONLY | PAGE_COW)) == PAGE_COW)
		pg |= PAGE_RONLY;
	    *new_page_table = pg;
	    if (mem_map[MAP_NR(PTOV(pg & 0xfffff000))] & MAP_PAGE_RESERVED)
		continue;
	    *old_page_table = pg;
	    mem_map[MAP_NR(PTOV(pg & 0xfffff000))]++;
	}
    }

  end:
    invalidate();
    return 0;
}

/*
 * a more complete version of free_page_tables which performs with page
 * granularity.
 */
int unmap_page_range(ulong from, ulong size)
{
    ulong page, page_dir;
    ulong *page_table, *dir;
    ulong poff, pcnt, pc;

    if (from & 0xfff) {
	printk ("unmap_page_range called with wrong alignment\n");
	return -EINVAL;
    }
    size = (size + 0xfff) >> PAGE_SHIFT;
    dir = &current->pmap.pagedir_v[L1_INDEX(from)];
    poff = L2_INDEX(from);
    if ((pcnt = 1024 - poff) > size)
	pcnt = size;

    for ( ; size > 0; ++dir, size -= pcnt,
	 pcnt = (size > 1024 ? 1024 : size)) {
	if (!(page_dir = *dir)) {
	    poff = 0;
	    continue;
	}
	if (!(page_dir & PAGE_TABLE)) {
	    printk("unmap_page_range: bad page directory.");
	    continue;
	}
	page_table = (ulong *)(PTOV(page_dir & 0xfffff000));
	if (poff) {
	    page_table += poff;
	    poff = 0;
	}
	for (pc = pcnt; pc--; page_table++) {
	    if ((page = *page_table) != 0) {
		*page_table = 0;
		if (page & PAGE_PRESENT) {
		    --current->rss;
		    free_page(PTOV(page & 0xfffff000));
		} else
		    swap_free(page);
	    }
	}
	if (pcnt == 1024) {
	    free_page(PTOV(page_dir & 0xfffff000));
	    *dir = 0;
	}
    }
    invalidate();
    return 0;
}

int zeromap_page_range(ulong from, ulong size, int mask)
{
    ulong *page_table, *dir;
    ulong poff, pcnt;
    ulong page;

    if (mask) {
	if ((mask & 0xfffff001) != PAGE_PRESENT) {
	    printk("zeromap_page_range: mask = %08x\n",mask);
	    return -EINVAL;
	}
	mask |= VTOP(ZERO_PAGE);
    }
    if (from & 0xfff) {
	printk("zeromap_page_range: from = %08x\n",from);
	return -EINVAL;
    }
    dir = &current->pmap.pagedir_v[L1_INDEX(from)];
    size = (size + 0xfff) >> PAGE_SHIFT;
    poff = L2_INDEX(from);
    if ((pcnt = 1024 - poff) > size)
	pcnt = size;

    while (size > 0) {
	if (!(PAGE_TABLE & *dir)) {
	    if (!(page_table = (ulong *)get_free_page(GFP_KERNEL))) {
		invalidate();
		return -ENOMEM;
	    }
	    if (PAGE_TABLE & *dir) {
		free_page((ulong) page_table);
		page_table = (ulong *)PTOV(*dir & 0xfffff000);
		dir++;
	    } else
		*dir++ = VTOP(page_table) | PAGE_TABLE;
	} else {
	    page_table = (ulong *)PTOV(*dir & 0xfffff000);
	    dir++;
	}
	page_table += poff;
	poff = 0;
	for (size -= pcnt; pcnt-- ;) {
	    if ((page = *page_table) != 0) {
		*page_table = 0;
		if (page & PAGE_PRESENT) {
		    --current->rss;
		    free_page(PTOV(page & 0xfffff000));
		} else
		    swap_free(page);
	    }
	    if (mask)
		++current->rss;
	    *page_table++ = mask;
	}
	pcnt = (size > 1024 ? 1024 : size);
    }
    invalidate();
    return 0;
}

/*
 * maps a range of physical memory into the requested pages. the old
 * mappings are removed. any references to nonexistent pages results
 * in null mappings (currently treated as "copy-on-access")
 */
int remap_page_range(ulong from, ulong to, ulong size, int mask)
{
    ulong *page_table, *dir;
    ulong poff, pcnt;
    ulong page;

    if (mask) {
	if ((mask & 0xfffff001) != PAGE_PRESENT) {
	    printk("remap_page_range: mask = %08x\n",mask);
	    return -EINVAL;
	}
    }
    if ((from & 0xfff) || (to & 0xfff)) {
	printk("remap_page_range: from = %08x, to=%08x\n",from,to);
	return -EINVAL;
    }
    dir = &current->pmap.pagedir_v[L1_INDEX(from)];
    size = (size + 0xfff) >> PAGE_SHIFT;
    poff = L2_INDEX(from);
    if ((pcnt = 1024 - poff) > size)
	pcnt = size;

    while (size > 0) {
	if (!(PAGE_TABLE & *dir)) {
	    if (!(page_table = (ulong *)get_free_page(GFP_KERNEL))) {
		invalidate();
		return -1;
	    }
	    *dir++ = VTOP(page_table) | PAGE_TABLE;
	}
	else
	    page_table = (ulong *)(0xfffff000 & *dir++);
	if (poff) {
	    page_table += poff;
	    poff = 0;
	}

	for (size -= pcnt; pcnt-- ;) {
	    if ((page = *page_table) != 0) {
		*page_table = 0;
		if (PAGE_PRESENT & page) {
		    --current->rss;
		    free_page(0xfffff000 & page);
		} else
		    swap_free(page);
	    }

	    /*
	     * i'm not sure of the second cond here. should we
	     * report failure?
	     * the first condition should return an invalid access
	     * when the page is referenced. current assumptions
	     * cause it to be treated as demand allocation.
	     */
	    if (!mask || to >= high_memory || !mem_map[MAP_NR(to)])
		*page_table++ = 0;	/* not present */
	    else {
		++current->rss;
		*page_table++ = VTOP(to) | mask;
		if (!(mem_map[MAP_NR(to)] & MAP_PAGE_RESERVED))
		    mem_map[MAP_NR(to)]++;
	    }
	    to += PAGE_SIZE;
	}
	pcnt = (size > 1024 ? 1024 : size);
    }
    invalidate();
    return 0;
}

/*
 * This function puts a page in memory at the wanted address.
 * It returns the physical address of the page gotten, 0 if
 * out of memory (either when trying to access page-table or
 * page.)
 * The "prot" argument controls how the page can be accessed.
 */
static ulong put_page(struct task_struct * tsk,ulong page, ulong address,
		      int prot)
{
    ulong tmp, *page_table;

    if ((prot & 0xfffff001) != PAGE_PRESENT)
	printk("put_page: prot = %08x\n",prot);

    if (page >= high_memory) {
	printk("put_page: trying to put page %p at %p\n",page,address);
	return 0;
    }
    tmp = mem_map[MAP_NR(page)];
    if (!(tmp & MAP_PAGE_RESERVED) && (tmp != 1)) {
	printk("put_page: mem_map disagrees with %p at %p\n",page,address);
	return 0;
    }

    page_table = &tsk->pmap.pagedir_v[L1_INDEX(address)];
    if (*page_table & PAGE_TABLE)
	page_table = (ulong *) PTOV(*page_table & 0xfffff000);
    else {
	printk("put_page: bad page directory entry\n");
	oom(tsk);
	*page_table = BAD_PAGETABLE | PAGE_TABLE;
	return 0;
    }
    page_table += L2_INDEX(address);
    if (*page_table) {
	printk("put_page: page already exists\n");
	*page_table = 0;
	invalidate();
    }
    *page_table = VTOP(page) | prot;
    /* no need for invalidate */
    return page;
}

/*
 * The previous function doesn't work very well if you also want to mark
 * the page dirty: exec.c wants this, as it has earlier changed the page,
 * and we want the dirty-status to be correct (for VM). Thus the same
 * routine, but this time we mark it dirty too.
 */
ulong put_dirty_page (struct task_struct * tsk,
			      ulong page,
			      ulong address)
{
    ulong tmp, *page_table;

    if (page >= high_memory)
	printk("put_dirty_page: trying to put page %p at %p\n",page,address);
    if (mem_map[MAP_NR(page)] != 1)
	printk("mem_map disagrees with %p at %p\n",page,address);
    page_table = &tsk->pmap.pagedir_v[L1_INDEX(address)];
    if (*page_table & PAGE_TABLE)
	page_table = (ulong *)PTOV(*page_table & 0xfffff000);
    else {
	if (!(tmp=get_free_page(GFP_KERNEL)))
	    return 0;
	*page_table = VTOP(tmp) | PAGE_TABLE;
	page_table = (ulong *)tmp;
    }
    page_table += L2_INDEX(address);
    if (*page_table) {
	printk("put_dirty_page: page already exists\n");
	*page_table = 0;
	invalidate();
    }
    *page_table = VTOP(page) | (PAGE_DIRTY | PAGE_PRIVATE);
    /* no need for invalidate */
    return page;
}

/*
 * This routine handles present pages, when users try to write
 * to a shared page. It is done by copying the page to a new address
 * and decrementing the shared-page counter for the old page.
 */
void do_wp_page(ulong writecycle, ulong address,
		struct task_struct * tsk,
		ulong usp)
{
    unsigned long *pdep, pde, *ptep, old_page, prot;
    unsigned long new_page;

#ifdef DEBUG
    if (usp)
	printk ("write protected page fault at addr %#lx in task %#lx\n",
		address, tsk);
#endif
    new_page = __get_free_page(GFP_KERNEL);
    pdep = &tsk->pmap.pagedir_v[L1_INDEX(address)];
    pde = *pdep;

    /* check page directory entry */
    if (!(pde & PAGE_TABLE)) {
	if (new_page)
	    free_page(new_page);
	return;
    }
    if ((pde & PAGE_TABLE) != PAGE_TABLE
	|| PTOV(pde & 0xfffff000) >= high_memory) {
	printk ("do_wp_page: bogus page-table at address %08x (%08x)\n",
	       address, pde);
	*pdep = VTOP(BAD_PAGETABLE) | PAGE_TABLE;
	send_sig(SIGKILL, tsk, 1);
	if (new_page)
	    free_page(new_page);
	return;
    }
    ptep = (ulong *)PTOV(pde & 0xfffff000);
    ptep += L2_INDEX(address);
    old_page = *(ulong *)ptep;
    if (!(old_page & PAGE_PRESENT)) {
	if (new_page)
	    free_page(new_page);
	return;
    }

    /* check limit */
    if (PTOV(old_page) >= high_memory) {
	printk("do_wp_page: bogus page at address %08x (%08x)\n",
	       address,old_page);
	*ptep = VTOP(BAD_PAGE) | PAGE_SHARED;
	send_sig(SIGKILL, tsk, 1);
	if (new_page)
	    free_page(new_page);
	return;
    }

    /* check if page is now read/write */
    if (!(old_page & PAGE_RONLY)) {
	if (new_page)
	    free_page(new_page);
	return;
    }

    if (!(old_page & PAGE_COW)) {
	if (usp && tsk == current) {
#ifdef DEBUG
	    printk ("wp page, not COW, seg fault\n");
#endif
	    send_sig (SIGSEGV, tsk, 1);
	}
    }

    /* minor page fault (copy on write) */
    tsk->min_flt++;

    prot = ((old_page & 0x00000fff) & ~PAGE_RONLY) | PAGE_RW;
    old_page = PTOV(old_page & 0xfffff000);

    /* if page was used only once (no longer shared) enable read write */
    if (mem_map[MAP_NR(old_page)]==1) {
	*ptep &= ~PAGE_RONLY;
	invalidate();
	if (new_page)
	    free_page(new_page);
	return;
    }

    if (new_page)
	copy_page(old_page,new_page);
    else {
	new_page = BAD_PAGE;
	oom(tsk);
    }

    *ptep = VTOP(new_page) | prot;
    if (old_page)
	free_page(old_page);
    invalidate();
}

int verify_area(int type, void * addr, unsigned long size)
{
    unsigned long start;

    start = (unsigned long) addr;
    if (start >= TASK_SIZE)
	return -EFAULT;
    if (size > TASK_SIZE - start)
	return -EFAULT;
    if (type == VERIFY_READ || !size)
	return 0;
    if (!size)
	return 0;
    size--;
    size += start & 0xfff;
    size >>= 12;
    start &= 0xfffff000;
    do {
	do_wp_page(1,start,current,0);
	start += 4096;
    } while (size--);
    return 0;
}

static void get_empty_page(struct task_struct * tsk, ulong address)
{
    ulong tmp;

    tmp = get_free_page(GFP_KERNEL);
    if (!tmp) {
	oom(tsk);
	tmp = BAD_PAGE;
    }
    if (!put_page(tsk,tmp,address,PAGE_PRIVATE))
	free_page(tmp);
}

/*
 * try_to_share() checks the page at address "address" in the task "p",
 * to see if it exists, and if it is clean. If so, share it with the current
 * task.
 *
 * NOTE! This assumes we have checked that p != current, and that they
 * share the same executable or library.
 */
static int try_to_share(ulong address, struct task_struct * tsk,
			struct task_struct * p, ulong writecycle,
			ulong newpage)
{
    ulong from;
    ulong to;
    ulong *from_page;
    ulong *to_page;
    ulong virt_addr;

    from_page = &p->pmap.pagedir_v[L1_INDEX(address)];
    to_page   = &tsk->pmap.pagedir_v[L1_INDEX(address)];

    /* is there a page table in from? */
    from = *from_page;
    if (!(from & PAGE_TABLE))
	return 0;
    from_page = (ulong *)PTOV(from & 0xfffff000);
    from_page += L2_INDEX(address);
    from = *from_page;
    /* is the page clean and present? */
    if ((from & (PAGE_PRESENT | PAGE_DIRTY)) != PAGE_PRESENT)
	return 0;
    virt_addr = PTOV(from & 0xfffff000);
    if (virt_addr >= high_memory)
	return 0;
    if (mem_map[MAP_NR(virt_addr)] & MAP_PAGE_RESERVED)
	return 0;
    /* is the destination ok? */
    to = *to_page;
    if (!(to & PAGE_TABLE))
	return 0;
    to_page = (ulong *)PTOV(to & 0xfffff000);
    to_page += L2_INDEX(address);
    if (*to_page)
	return 0;
    /* share them if read - do COW immediately otherwise */
    if (writecycle) {
	copy_page(virt_addr,newpage);
	to = VTOP(newpage) | PAGE_PRIVATE;
    } else {
	mem_map[MAP_NR(virt_addr)]++;
	from |= PAGE_RONLY;
	to = from;
	if (newpage)
	    free_page(newpage);
    }
    *from_page = from;
    *to_page = to;
    invalidate();
    return 1;
}

/*
 * share_page() tries to find a process that could share a page with
 * the current one. Address is the address of the wanted page relative
 * to the current data space.
 *
 * We first check if it is at all feasible by checking executable->i_count.
 * It should be >1 if there are other tasks sharing this inode.
 */
static int share_page(struct task_struct * tsk, struct inode * inode,
	ulong address, ulong writecycle, ulong newpage)
{
    struct task_struct ** p;
    int i;

    if (!inode || inode->i_count < 2)
	return 0;
    for (p = &LAST_TASK ; p > &FIRST_TASK ; --p) {
	if (!*p)
	    continue;
	if (tsk == *p)
	    continue;
	if (inode != (*p)->executable) {
	    for (i=0; i < (*p)->numlibraries; i++)
		if (inode == (*p)->libraries[i].library)
		    break;
	    if (i >= (*p)->numlibraries)
		continue;
	}
	if (try_to_share(address,tsk,*p,writecycle,newpage))
	    return 1;
    }
    return 0;
}

/*
 * fill in an empty page-table if none exists
 */
static ulong get_empty_pgtable(struct task_struct * tsk,ulong address)
{
    ulong page = 0;
    ulong *p;

repeat:
    p = &tsk->pmap.pagedir_v[L1_INDEX(address)];

    if (*p & PAGE_TABLE) {
	if (page)
	    free_page(page);
	return *p;
    }
    if (*p) {
	printk("get_empty_pgtable: bad page-directory entry \n");
	*p = 0;
    }
    if (page) {
	*p = VTOP(page) | PAGE_TABLE;
	return *p;
    }
    if ((page = get_free_page(GFP_KERNEL)) != 0)
	goto repeat;
    oom(current);
    *p = VTOP(BAD_PAGETABLE) | PAGE_TABLE;
    return 0;
}

void do_no_page(ulong writecycle, ulong address,
		struct task_struct *tsk, ulong usp)
{
    int nr[8], prot;
    ulong tmp;
    ulong page, *p;
    unsigned int block,i;
    struct inode * inode;

    page = get_empty_pgtable(tsk,address);
    if (!page)
	return;
    p = (ulong *)PTOV(page & 0xfffff000);
    tmp = p[L2_INDEX(address)];
    if (tmp & PAGE_PRESENT)
	return;

    /* resident set size has increased */
    ++tsk->rss;

    /* page was swapped out, flag major fault and swap it in */
    if (tmp) {
	++tsk->maj_flt;
	swap_in((ulong *) page);
	return;
    }

    /* determine if address is in current executable or a library */
    address &= 0xfffff000;
    inode = NULL;
    block = 0;
    if (address < tsk->end_data) {
	inode = tsk->executable;
	block = 1 + address / BLOCK_SIZE;
    } else {
	i = tsk->numlibraries;
	while (i-- > 0) {
	    if (address < tsk->libraries[i].start)
		continue;
	    block = address - tsk->libraries[i].start;
	    if (block >= tsk->libraries[i].length + tsk->libraries[i].bss)
		continue;
	    inode = tsk->libraries[i].library;
	    if (block < tsk->libraries[i].length)
		block = 1 + block / BLOCK_SIZE;
	    else
		block = 0;
	    break;
	}
    }

#ifdef DEBUG
    if (usp && address < 0x2000)
	printk ("address < 0x2000, inode=%#lx, block=%ld\n", inode, block);
#endif

    /* no inode, minor fault, allocate zero fill page */
    if (!inode) {
	++tsk->min_flt;
	get_empty_page(tsk,address);
	if (tsk != current)
	    return;
	if (address < tsk->brk)
	    return;
	if (address+8192 >= (usp & 0xfffff000))
	    return;
#ifdef DEBUG
	printk ("address out of range for minor page fault\n");
#endif
	send_sig(SIGSEGV,tsk,1);
	return;
    }

    /* if we can get the page from another task, minor fault */
    page = get_free_page(GFP_KERNEL);
    if (share_page(tsk,inode,address,writecycle,page)) {
	++tsk->min_flt;
	return;
    }

#ifdef DEBUG
    printk ("reading page from device\n");
#endif

    /* major fault, get a page and read in the contents */
    ++tsk->maj_flt;
    if (!page) {
	oom(current);
	put_page(tsk,BAD_PAGE,address,PAGE_PRIVATE);
	return;
    }
    prot = PAGE_PRIVATE;
    if (CODE_SPACE(address,tsk))
	prot = PAGE_READONLY;
    if (block) {
	for (i=0 ; i<4 ; block++,i++) {
	    nr[i] = bmap(inode,block);
#ifdef DEBUG
	    printk ("reading block %d\n", nr[i]);
#endif
	}
	bread_page(page,inode->i_dev,nr,1024,prot);

#ifdef DEBUG
	if (address == 0 && *(ulong *)page == 0)
	    panic ("first word of new page is zero (after read)\n");
#endif
    }

    /* attempt to share the page with other tasks */
    if (!writecycle && share_page(tsk,inode,address,writecycle,page)) {
	return;
    }

    i = address + PAGE_SIZE - tsk->end_data;
    if (i > PAGE_SIZE-1)
	i = 0;
    tmp = page + PAGE_SIZE;
    while (i--) {
	tmp--;
	*(char *)tmp = 0;
    }
#ifdef DEBUG
	if (address == 0 && *(ulong *)page == 0)
	    panic ("first word of new page is zero (after wierd stuff)\n");
#endif

    if (put_page(tsk,page,address,prot))
	return;
    if (page)
	free_page(page);
    oom(current);
}

void decode_bus_fault (struct frame *fp, unsigned short mmusr,
		       ulong addr)
{
    printk ("bad bus fault.  MMUSR=%#06x, ADDR=%#010x, PC=%#010x\n",
	     mmusr, addr, fp->pc);
    for (;;)
	*(unsigned short *)0xdff180 = 0xf00;
}

#define INV_BIT 	(1)
#define WP_BIT		(2)
#define BUSERR_BIT	(3)

/*
 * This routine handles page faults.  It determines the problem, and
 * then passes it off to one of the appropriate routines.
 */
void do_page_fault(struct frame *fp, ulong address, ulong writecycle)
{
    volatile unsigned short mmusr;
    ulong desc;
    ulong usp = 0;
    ulong mmusrbits = 0;
    ulong stack_limit;
    extern void die_if_kernel(char *,long,long);

    if (!(fp->sr & PS_S))
	/* if !supervisor mode, set user stack pointer */
	usp = fp->usp;

    /* get level 7 scan */
    __asm__ __volatile__ ("ptestr #1,%2@,#7,%0\n\t"
			  "pmove mmusr,%1@"
			  : "=a&" (desc)
			  : "a" (&mmusr), "a" (address) : "memory");

#ifdef DEBUG
    printk ("mmusr is %#x for addr %#x in task %#x\n", mmusr, address,
	    current);
    printk ("descriptor address is %#x, contents %#x\n",
	    PTOV(desc), *(ulong *)PTOV(desc));
#endif

    /* decode mmusr bits into a short form */
    mmusrbits = ((mmusr & MMU_I) ? INV_BIT : 0) |
	((mmusr & MMU_WP) ? WP_BIT : 0) |
	    ((mmusr & MMU_B) ? BUSERR_BIT : 0);

    /* take action based on the above bits */
    switch (mmusrbits) {
      case INV_BIT:
	if (address < TASK_SIZE)
	    do_no_page (writecycle, address, current, usp);
	else {
	    printk ("Unable to handle kernel bus fault at address %#lx\n"
		    "from %#lx\n", address, fp->pc);
	    die_if_kernel("Oops",usp,mmusr);
	    do_exit(SIGKILL);
	}
#ifdef DEBUG
	if (address == 0x2000)
	    printk ("access of address 0x2000 from pc=%#lx\n", fp->pc);
#endif
	break;
      case WP_BIT:
	if (!(fp->un.fmtb.ssw & RW) && address < TASK_SIZE)
	    do_wp_page (writecycle, address, current, usp);
	 else {
	    printk ("Unable to handle kernel bus fault at address %08x\n",
		    address);
	    die_if_kernel("Oops",usp,mmusr);
	    do_exit(SIGKILL);
	}
	break;
      default:
	decode_bus_fault (fp, mmusr, address);
    }

    /*
     * load the ATC entry for the access, marking it modified if the
     * bus cycle was a write cycle
     */
    if (writecycle)
	__asm__ __volatile__ ("ploadw #1,%0@" :: "a" (address));
    else
	__asm__ __volatile__ ("ploadr #1,%0@" :: "a" (address));


    if (!usp)
	return;
    stack_limit = current->rlim[RLIMIT_STACK].rlim_cur;
    if (stack_limit >= RLIM_INFINITY)
	return;
    if (stack_limit >= current->start_stack)
	return;
    stack_limit = current->start_stack - stack_limit;
    if (usp < stack_limit) {
#ifdef DEBUG
	printk ("user stack exceeds limit, segfault\n");
#endif
	send_sig(SIGSEGV, current, 1);
    }

}

/*
 * 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
 * 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.
 */
static ulong empty_bad_page_table;

ulong __bad_pagetable(void)
{
    int i;

    for( i = 0; i < 1024; i++ )
	((ulong *)empty_bad_page_table)[i] = VTOP(BAD_PAGE) + PAGE_DESC;

    return empty_bad_page_table;
}

static ulong empty_bad_page;

ulong __bad_page(void)
{
    memset ((void *)empty_bad_page, 0, PAGE_SIZE);

    return empty_bad_page;
}

static ulong empty_zero_page;

unsigned long __zero_page(void)
{
    memset ((void *)empty_zero_page, 0, PAGE_SIZE);

    return empty_zero_page;
}

/* kernel code start address */
extern void start();
/* "end" marks the end of the kernel text/data/bss */
extern int end;

/* The pointer to the page descriptor for the kernel stack */
ulong *kstack_descp;

/*
 * mm_init() sets up the page tables for the kernel and task 0 (swapper)
 * it is called from init/head.s
 */
volatile void mm_init(void)
{
    int i, num_pt;
    ulong address, physmem_start = 0, physmem_end = 0;
    ulong virtual_start, virtual_end, interrupt_stack;
    ulong *kpage_dir;
    ulong *kstack_pt, tmp;
    ulong srp[2]; /* supervisor root pointer */

    /*
     * Only the highest address chunk of memory is currently
     * used.
     * Hopefully at some point the memory manager will support
     * the use of multiple non-contiguous chunks of memory.
     */

    /*
     * determine the memory chunk being used (highest address)
     * record the physical addresses of the boundaries
     */
    for (i = 0; i < sys_info.num_memory; i++) {
	/* record chip memory size */
	if ((ulong)sys_info.memory[i].addr > physmem_start) {
	    physmem_start = (ulong)sys_info.memory[i].addr;
	    physmem_end = (ulong)sys_info.memory[i].addr +
		sys_info.memory[i].size;
	}
    }

#ifdef DEBUG
    printk ("physmem_start is %#lx\nphysmem_end is %#lx\n",
	    physmem_start, physmem_end);
#endif

    /* virtual address after end of kernel */
    virtual_start = ((ulong)&end + (PAGE_SIZE-1)) & ~(PAGE_SIZE-1);

    /* virtual address of end of available memory */
    virtual_end = (ulong)start + (physmem_end - physmem_start);

#ifdef DEBUG
    printk ("virtual_start is %#lx\nvirtual_end is %#lx\n",
	    virtual_start, virtual_end);
#endif

    /* calculate the virtual->physical offset */
    vpoffset = physmem_start - (ulong)start;

#ifdef DEBUG
    printk ("virtual->physical offset is %#lx\n", vpoffset);
#endif

    /*
     * calculate how many page tables are needed for mapping kernel
     * virtual memory
     */
    num_pt = (physmem_end - physmem_start + (NBP_L1E-1)) / NBP_L1E;

#ifdef DEBUG
    printk ("Using %d page table(s) to map memory\n", num_pt);
#endif

    /* allocate space for kernel page directory */
    kpage_dir = (ulong *)virtual_start;
    virtual_start += PAGE_SIZE;

    /* task[0] (swapper) pmap records the kernel vmem resources */
    task[0]->pmap.pagedir_v = kpage_dir;
    task[0]->pmap.pagedir_p = VTOP (kpage_dir);

    /*
     * initialize the bad page table and bad page to point
     * to a couple of allocated pages
     */
    empty_bad_page_table = virtual_start;
    virtual_start += PAGE_SIZE;
    empty_bad_page = virtual_start;
    virtual_start += PAGE_SIZE;
    empty_zero_page = virtual_start;
    virtual_start += PAGE_SIZE;

    /* initialize the kernel page directory */
    if (sys_info.cputype & AFF_68040) {
	/* we can't use early termination page descriptors with 68040 */

#if 0
#error	 none of this works yet
	l2_table = (ulong *)virtual_start;

	virtual_start += num_l3 * PAGE_SIZE;

	sd.desc.used = 0;
	sd.desc.wp = PTE_READ_WRITE;
	sd.desc.dt = SHORT_DESC;

	for (i = 0; i < num_l3; i++)
	{
	    sd.desc.addr = VTOP((ulong)l3_table + PAGE_SIZE * i) >> 4;
	    l2_table[i] = sd;
	}

	/* Create page descriptors in the level 3 tables */

	for (i = 0, address = physmem_start;
	     i < NUM_L3_ENTRIES * num_l3;
	     i++, address += PAGE_SIZE)
	    l3_table[i] = address | PAGE_DESC;
#endif
    } else {
	/* we have 68030 or 68851, use early termination page descriptors */

#ifdef DEBUG
	printk ("68030 or 68851, using early termination page descriptors\n");
#endif

	/* map the first 16M using early termination page descriptors */
	for (i = 0, address = 0;
	     i < 4;
	     i++, address += NBP_L1E)
	{
	    tmp = address | PAGE_PRIVATE;
#ifdef DEBUG
	    printk ("kpage_dir[%d] = %#lx\n", i, tmp);
#endif
	    kpage_dir[i] = tmp;
	}

	/* map the kernel physical virtual using
	 * early termination page descriptors */
	for (i = KERN_L1_ENTRY, address = physmem_start;
	     i < KERN_L1_ENTRY + num_pt;
	     i++, address += NBP_L1E)
	{
	    tmp = address | PAGE_PRIVATE;
#ifdef DEBUG
	    printk ("kpage_dir[%d] = %#lx\n", i, tmp);
#endif
	    kpage_dir[i] = tmp;
	}
    } /* 68040? */

    /* allocate page table for kernel stack, clear it, and map it in kernel
     * page directory */
    kstack_pt = (ulong *)virtual_start;
    virtual_start += PAGE_SIZE;
    memset (kstack_pt, 0, PAGE_SIZE);
    kpage_dir[KSTACK_ENTRY] = VTOP(kstack_pt) | PAGE_TABLE;

    /* initialize pointer to kernel stack page descriptor */
    kstack_descp = &kstack_pt[1];

    /* allocate space for (per process) kernel stack and map it */
    task[0]->kernel_stack_page = virtual_start;
    memset((void *)task[0]->kernel_stack_page, 0, PAGE_SIZE);
    virtual_start += PAGE_SIZE;

#ifdef DEBUG
    printk ("swapper kernel stack at %#x\n", task[0]->kernel_stack_page);
#endif

    /* record in task 0 (swapper) pmap */
    task[0]->pmap.kstack_v = (ulong *)task[0]->kernel_stack_page;
    task[0]->pmap.kstack_p = VTOP (task[0]->pmap.kstack_v);

    *kstack_descp = task[0]->pmap.kstack_p | PAGE_PRIVATE;

    /*
     * allocate interrupt stack page and map it
     */
    interrupt_stack = virtual_start;
    virtual_start += PAGE_SIZE;
    kstack_pt[3] = VTOP(interrupt_stack) | PAGE_PRIVATE;

    /* setup new Supervisor root pointer */
    srp[0] = 0x80000000 | SHORT_DESC;
    srp[1] = task[0]->pmap.pagedir_p;

    /*
     * move the new srp value to the SRP register
     * This switches the kernel page tables from the temporary ones
     * in chip memory to the permanent ones
     */
    __asm__ __volatile__ ("pmove %0@,srp" :: "a" (&srp[0]));

    /* setup CPU root pointer for swapper (nil) */
    task[0]->pmap.crp[0] = 0x80000000 | SHORT_DESC;
    task[0]->pmap.crp[1] = task[0]->pmap.pagedir_p;
    __asm__ __volatile__ ("pmove %0@,crp" :: "a" (&task[0]->pmap.crp[0]));

    /*
     * Set up SFC/DFC registers (user data space)
     */
    set_fs (0x1);

    /*
     * move from executing on the interrupt stack to executing on the
     * master stack, set up master stack pointer, interrupt stack pointer
     * and call start_kernel, pushing arguments.
     */
    __asm__ __volatile__ ("oriw  #0x1000,sr\n\t"
			  "movel %0,sp\n\t"
			  "movec %1,isp\n\t"
			  "movel %2,sp@-\n\t"
			  "movel %3,sp@-\n\t"
			  "bsr   _start_kernel\n\t"
			  "1:    bra 1b"
			  :: "d" (KSTACK_ADDR + PAGE_SIZE),
			  "d" (ISTACK_ADDR + PAGE_SIZE),
			  "d" (virtual_end),
			  "d" (virtual_start));
    /* fake out volatile function */
fake_volatile:
    goto fake_volatile;
}

void mem_init(ulong start_mem, ulong end_mem)
{
    int codepages = 0;
    int datapages = 0;
    ulong tmp;
    unsigned short * p;
    extern int etext, end;

    end_mem &= 0xfffff000;
    high_memory = end_mem;
    start_mem += 0x0000000f;
    start_mem &= 0xfffffff0;

    tmp = MAP_NR(end_mem);
    mem_map = (unsigned short *) start_mem;

    p = mem_map + tmp;
    start_mem = (ulong) p;
    while (p > mem_map)
	*--p = MAP_PAGE_RESERVED;
    start_mem += 0x00000fff;
    start_mem &= 0xfffff000;
    while (start_mem < end_mem) {
	mem_map[MAP_NR(start_mem)] = 0;
	start_mem += 4096;
    }

    free_page_list = 0;
    nr_free_pages = 0;
    for (tmp = KSTART_ADDR ; tmp < end_mem ; tmp += 4096) {
	if (mem_map[MAP_NR(tmp)]) {
	    if (tmp < (ulong)&etext)
		codepages++;
	    else if (tmp < (ulong)&end)
		datapages++;
	    continue;
	}
	*(ulong *) tmp = free_page_list;
	free_page_list = tmp;
	nr_free_pages++;
    }
    tmp = nr_free_pages << PAGE_SHIFT;
    printk("Memory: %dk/%dk available (%dk kernel code, %dk kernel data)\n",
	   tmp >> 10,
	   (end_mem-KSTART_ADDR) >> 10,
	   codepages << 2,
	   datapages << 2);
}

ulong mm_phys_to_virt (ulong addr)
{
    return PTOV (addr);
}

void si_meminfo(struct sysinfo *val)
{
    int i;

    i = high_memory >> PAGE_SHIFT;
    val->totalram = 0;
    val->freeram = 0;
    val->sharedram = 0;
    val->bufferram = buffermem;
    while (i-- > (KSTART_ADDR >> PAGE_SHIFT))  {
	if (mem_map[i] & MAP_PAGE_RESERVED)
	    continue;
	val->totalram++;
	if (!mem_map[i]) {
	    val->freeram++;
	    continue;
	}
	val->sharedram += mem_map[i]-1;
    }
    val->totalram <<= PAGE_SHIFT;
    val->freeram <<= PAGE_SHIFT;
    val->sharedram <<= PAGE_SHIFT;
    return;
}

/*
 * abstract out the high memory variable
 */
int valid_addr(ulong addr, int count)
{
    if (sys_info.machtype == MACH_AMIGA && addr < sys_info.si_amiga.chip_size)
	/* check for chip memory */
	if (addr + count > sys_info.si_amiga.chip_size)
	    return sys_info.si_amiga.chip_size - addr;
	else
	    return count;

    /* otherwise, kernel virtual memory */
    if (addr >= KSTART_ADDR && addr < high_memory)
	if (addr + count > high_memory)
	    return high_memory - addr;
	else
	    return count;

    /* not valid memory */
    return 0;
}
