/*
 *  arch/ppc/mm/fault.c
 *
 *  Teleworld Customer Device version
 *    Copyright (C) 1998 All Rights Reserved
 *
 *  PowerPC version 
 *    Copyright (C) 1995-1996 Gary Thomas (gdt@linuxppc.org)
 *
 *  Derived from "arch/i386/mm/fault.c"
 *    Copyright (C) 1991, 1992, 1993, 1994  Linus Torvalds
 *
 *  Modified by Cort Dougan and Paul Mackerras.
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License
 *  as published by the Free Software Foundation; either version
 *  2 of the License, or (at your option) any later version.
 * 
 */

#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 <linux/ptrace.h>
#include <linux/mman.h>
#include <linux/mm.h>
#include <linux/config.h>
#include <linux/wait.h>

#include <asm/page.h>
#include <asm/pgtable.h>
#include <asm/uaccess.h>
#include <asm/mmu.h>
#include <asm/mmu_context.h>
#include <asm/system.h>
#include <asm/ppcops.h>
#include <asm/io.h>

#define CONFIG_GUARDS

#undef TLBMISS_DEBUG

#ifdef CONFIG_XMON
#if 1
#define BUG_CORRUPT_DEBUG
#endif
#endif

extern void debug(char *, struct pt_regs *);
extern void bad_page_fault(struct pt_regs *, unsigned long);
extern pte_t *findPTE(struct mm_struct *, unsigned long);

/*
 * This routine handles page faults.  It determines the address,
 * and the problem, and then passes it off to one of the appropriate
 * routines.
 *
 * The error_code parameter is DSISR for a data fault, SRR1 for
 * an instruction fault.
 */
void do_page_fault(struct pt_regs *regs, unsigned long address, int wasWrite)
{
	struct vm_area_struct * vma;
	extern unsigned int ppc_local_irq_count[];
	if (ppc_local_irq_count[0]) {
                debug("Page fault in interrupt handler", regs);
	}

	if (current == NULL) {
		bad_page_fault(regs, address);
		return;
	}
	down(&current->mm->mmap_sem);
	vma = find_vma(current->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:
	/*
	 * was it a write?
	 */
	if (wasWrite) {
		if (!(vma->vm_flags & VM_WRITE))
			goto bad_area;
	} else {
		if (!(vma->vm_flags & (VM_READ | VM_EXEC)))
			goto bad_area;
	}
	handle_mm_fault(current, vma, address, wasWrite);
	up(&current->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(&current->mm->mmap_sem);
	bad_page_fault(regs, address);
}

void
bad_page_fault(struct pt_regs *regs, unsigned long address)
{
	unsigned long fixup;
	unsigned long page;

	if (user_mode(regs)) {
		force_sig(SIGSEGV, current);
		return;
	}

	if (regs->nip >= KERNELBASE
	    && (fixup = search_exception_table(regs->nip)) != 0) {
		regs->nip = fixup;
		return;
	}
        debug("Kernel access to invalid page", regs);
	if (current != 0) {
            page = (unsigned long) current->thread.pgdir;
            printk(KERN_ALERT "current->tss.pg_tables = %08lx\n", page);
            page = ((unsigned long *) page)[address >> PGDIR_SHIFT];
            printk(KERN_ALERT "*pde = %08lx\n", page);
            if (page & PAGE_MASK) {
                    page &= PAGE_MASK;
                    address &= 0x003ff000;
                    page = ((unsigned long *) page)[address >> PAGE_SHIFT];
                    printk(KERN_ALERT "*pte = %08lx\n", page);
            }
        }
	/* die_if_kernel("Oops", regs, error_code); */
	do_exit(SIGSEGV);
	do_exit(SIGKILL);
}

#if defined(CONFIG_GUARDS)

//
// Implementation of "guarded regions". The idea here is to set up a mechanism
// where ranges of addresses can be checked for different threads touching
// those addresses simultaneously. For instance, if reading data from the disk
// overlaps with another process reading data from the same range.
//
// To do this, we set up a table which is updated when crticial regions
// are entered or left. Other accesses are blocked until the critical
// region is free.
//
// If the "fromfs" field is false, then this guard is from a readsectors()
// call which is going to DMA into main memory.  Otherwise, this guard is from
// the file system who is reading or writing to memory via the CPU.
//

#define MAXTOUCH	150

struct notouch {
    	int pid;
        char fromfs;
	unsigned long addr;
	unsigned int len;
	struct task_struct *task;
        wait_queue_head_t wait;
} donttouch[MAXTOUCH] = { { 0 } };
int dontmax = 0;

void
__noaccess(unsigned long addr, int len, int onoff, int fromfs, 
        struct task_struct *task) {
    if (onoff) { 
    recheck:
        {
            struct notouch *np = donttouch, *ep = &donttouch[MAXTOUCH];
            struct notouch *slot;
            struct notouch *end = &donttouch[dontmax];
            slot = 0;
            for (; np < end; np++) {
                if (np->addr) {
                    unsigned long saddr = np->addr;
                    unsigned long eaddr = np->addr + np->len;
                    unsigned long laddr = addr + len;
                    if ((addr <= saddr && laddr > saddr) ||
                        (addr < eaddr && addr >= saddr)) {

                        // We are only concerned about overlaps here if the
                        // previous one was from readsectors() and this one is
                        // from the filesystem.  In this case, we believe that
                        // the swapper is trying to throw out pages that we are
                        // doing a readsectors() to.  This is illegal.
                        //
                        // Otherwise, we assume that the stored guard is from
                        // the swapper trying to throw out some of our pages.
                        // The tasks may match, but only because the swapper
                        // sends down the task whose VM is being swapped.
                        // Since we won't be waiting on ourselves, there is no
                        // deadlock here.
                        if (np->task == task && fromfs && !np->fromfs) {
                            if (np->addr != addr || np->len != len) {
                                printk("Guard overlap: <0x%lx,%d> over <0x%lx,%d> fromfs %d\n",
                                        addr, len, np->addr, np->len, fromfs);
                                debug("Overlapping noaccess calls; process will hang", 0);
                            }
                            continue;
                        }

                        if (fromfs && np->fromfs) {
				/*
				 * Buffer cache copies are safe, since the IO
				 * only occurs from the buffer, not the source
				 * location.
				 */
				continue;
			}

			{
				/*
				 * Validate that these regions actually overlap.
				 * If not, nothing to do.
				 */
				unsigned long faddr = (addr < saddr ? saddr : addr);
				unsigned long laddr2 = (laddr < eaddr ? laddr : eaddr);
				for (; faddr < laddr2; faddr += PAGE_SIZE) {
					struct vm_area_struct *vm1;
					struct vm_area_struct *vm2;

					/*
					 * Check the possibility they refer to the
					 * same inode, and thus shared data.
					 */
					vm1 = find_vma(np->task->mm, faddr);
					if (!vm1)
						continue;
					vm2 = find_vma(task->mm, faddr);
					if (!vm2)
						continue;
					if (vm1->vm_file->f_dentry->d_inode !=
							vm2->vm_file->f_dentry->d_inode)
						continue;
					if ((vm1->vm_flags & VM_MAYSHARE) ||
					    (vm2->vm_flags & VM_MAYSHARE))
						break;
				}
				if (faddr >= laddr2)
					continue;
			}

#ifdef CONFIG_XMON
                        if (!fromfs && !np->fromfs) {
                            /*
                             * readsectors overlapping in the same shared spot.
                             * Application bug that we are currently polite
                             * enough to point out.
                             */
                            printk("Readsectors overlap: <0x%lx,%d> over <0x%lx,%d>\n",
                                    addr, len, np->addr, np->len);
                            debug("Invalid concurrent access", 0);
                        }
#endif

                        sleep_on(&np->wait);
                        goto recheck;
                    }
                }
                else if (!slot) {
                    slot = np;
                }
            }
            if (!slot) {
                if (np == ep) {
                    printk("No more guards available\n");
                    return;
                }
                slot = &donttouch[dontmax];
                dontmax++;
            }
            slot->addr = addr;
            slot->len = len;
            slot->pid = task->pid;
            slot->task = task;
            slot->fromfs = fromfs;
            init_waitqueue_head(&slot->wait);
        }
    }
    else {
        struct notouch *np = donttouch;
        struct notouch *end = &donttouch[dontmax];
        for (; np < end; np++) {
            if ((np->addr == addr) && (np->task == task)) {
                wake_up(&np->wait);
                np->addr = 0;
                if ((np + 1) == end) {
                    while (np >= donttouch) {
                        if (np->addr)
                            break;
                        dontmax--;
                        np--;
                    }
                }
                break;
            }
        }
    }
}

void
noaccess(unsigned long addr, int len, int onoff) {
    __noaccess(addr, len, onoff, 0, current);
}

void
fnoaccess(unsigned long addr, int len, int onoff, struct task_struct *task) {
    __noaccess(addr, len, onoff, 1, task);
}

void
dumpguards(void) {
    struct notouch *np = donttouch, *ep = &donttouch[MAXTOUCH];
    for (; np < ep; np++) {
        if (np->addr) {
            printk("%2d [0x%08lx,%7d) pid %5d fromfs %d wait %p <",
                np - donttouch, np->addr, np->len, np->pid, np->fromfs, &np->wait);
            if (waitqueue_active(&np->wait)) {
                struct list_head *next = np->wait.task_list.next;
                while (next != &np->wait.task_list) {
                    wait_queue_t *wq = list_entry(next, wait_queue_t, task_list);
					struct task_struct *task = wq->task;
                    printk("%d,", task->pid);
                    next = next->next;
                }
            }
            printk(">\n");
        }
    }
}

#endif

int
guard_if_unguarded(unsigned long addr, int len, struct task_struct *task) {
    struct notouch *np = donttouch;
    struct notouch *end = &donttouch[dontmax];
    for (; np < end; np++) {
        if (np->addr) {
            unsigned long saddr = np->addr;
            unsigned long eaddr = np->addr + np->len;
            unsigned long laddr = addr + len;
            if ((addr <= saddr && laddr > saddr) ||
                (addr < eaddr && addr >= saddr)) {

                return 1;

            }
        }
    }
    fnoaccess(addr, len, 1, task);
    return 0;
}

#ifdef BUG_CORRUPT_DEBUG
void tlbFlush(void);

/*
 * Turn this on to check for VM access to a direct IO region */
void
prohibit_if_guarded(unsigned long addr, int len) {
    struct notouch *np = donttouch;
    struct notouch *end = &donttouch[dontmax];

    for (; np < end; np++) {
        // Don't guard against current task.  The current task is in the 
        // syscall handler which will touch the page (and take tlb faults) in
        // order to insure the page is mapped.
        //
        // Also, we are only worried about guarded regions that are guarded by
        // readsectors.  If the guard is from the file system (swapper) then it
        // is ok.
        if (np->addr && np->task != current && !np->fromfs) {
            unsigned long saddr = np->addr;
            unsigned long eaddr = np->addr + np->len;
            unsigned long laddr = addr + len;
            if ((addr <= saddr && laddr > saddr) ||
                (addr < eaddr && addr >= saddr)) {
                    /*
                     * Validate that these regions actually overlap.
                     * If not, nothing to do.
                     */
                    unsigned long faddr = (addr < saddr ? saddr : addr);
                    unsigned long laddr2 = (laddr < eaddr ? laddr : eaddr);
                    for (; faddr < laddr2; faddr += PAGE_SIZE) {
                        struct vm_area_struct *vm1;
                        struct vm_area_struct *vm2;

                        /*
                         * Check the possibility they refer to the
                         * same inode, and thus shared data.
                         */
                        vm1 = find_vma(np->task->mm, faddr);
                        if (!vm1)
                                continue;
                        vm2 = find_vma(current->mm, faddr);
                        if (!vm2)
                                continue;
                        if (vm1->vm_file->f_dentry->d_inode != vm2->vm_file->f_dentry->d_inode)
                                continue;
                        if ((vm1->vm_flags & VM_MAYSHARE) ||
                            (vm2->vm_flags & VM_MAYSHARE))
                                break;
                    }
                    if (faddr >= laddr2)
                            continue;
                    printk("Guarded: 0x%08lx\n", addr);
                    debug("Prohibited access to guarded page", 0);
            }
        }
    }
}

void
pretlbFlush(void) {
    tlbFlush();
}
#else
#define prohibit_if_guarded(x,y)

void
pretlbFlush(void) {}

#endif

/*
 * Walk the tables to find the appropriate PTE for the given address.
 */
pte_t *
findPTE(struct mm_struct *mm, unsigned long badaddr) {
	pgd_t *pgd;
	pmd_t *pmd;
	pte_t *pte;

	struct vm_area_struct *vma;
	vma = find_vma(mm, badaddr);
	if (!vma)
		return 0;
	pgd = pgd_offset(mm, badaddr);
	if (pgd_none(*pgd))
		return 0;
	pmd = pmd_offset(pgd, badaddr);
	if (pmd_none(*pmd))
		return 0;
	pte = pte_offset(pmd, badaddr);
	if (pte_none(*pte))
		return 0;
	if (!pte_present(*pte))
		return 0;
	return pte;
}

/*
 * Wire down an entry in the TLB. Called rarely, so little reason to optimize.
 */
void tlbWirekernel(unsigned long va, unsigned long pa, int pgsz, int entry, int cache) {
	unsigned long tlblo;
	unsigned long tlbhi;
	unsigned long opid = mfspr(SPRN_PID);

	mtspr(SPRN_PID, 0);
	tlblo = (pa & TLBLO_RPN) | TLBLO_WR;
	if (!cache)
		tlblo |= TLBLO_G | TLBLO_I;
	else
		tlblo |= TLBLO_EX;
	tlbhi = (va & TLBHI_EPN) | pgsz | TLBHI_VALID;

	asm("tlbwe %0,%1,1" : : "r" (tlblo), "r" (entry));
	asm("tlbwe %0,%1,0" : : "r" (tlbhi), "r" (entry));
	asm("isync");

	mtspr(SPRN_PID, opid);
}

/*
 * Flush the entire TLB (except wired entries).
 */
void tlbFlush(void) {
	int i;
	unsigned long oflags;
	int opid;

	save_flags(oflags);
	cli();

	opid = mfspr(SPRN_PID);
	mtspr(SPRN_PID, 0);

	for (i = NTLB_WIRED; i < NTLB; i++) {
		unsigned long ov = 0;
		asm("tlbwe %0,%1,0" : : "r" (ov), "r" (i));
		asm("tlbwe %0,%1,1" : : "r" (ov), "r" (i));
	}
	asm("sync;isync");

	mtspr(SPRN_PID, opid);
	restore_flags(oflags);
}

/*
 * Flush one entry from the TLB.
 */
void tlbFlush1(unsigned long va, int pid) {
	unsigned long idx;
	unsigned long tlbhi;
	unsigned int found = 1;
	int opid;
	unsigned long oflags;

	save_flags(oflags);
	cli();

	opid = mfspr(SPRN_PID);
	mtspr(SPRN_PID, pid);
	asm("tlbsx. %0,0,%2;beq 1f;li %1,0;1:" : "=r" (idx), "=r" (found) : "r" (va));
	if (found) {
		asm("tlbre %0,%1,0" : "=r" (tlbhi) : "r" (idx));
		tlbhi &= ~TLBHI_VALID;
		asm("tlbwe %0,%1,0" : : "r" (tlbhi), "r" (idx));
	}
	mtspr(SPRN_PID, opid);

	restore_flags(oflags);
}

/* ---------- */
/*
 * TLB miss handling code.
 */

/*
 * Handle TLB faults. We should push this back to assembly code eventually.
 * Caller is responsible for turning off interrupts ...
 */
static inline void
tlbDropin(unsigned long tlbhi, unsigned long tlblo) {
	/*
	 * Avoid the divide at the slight cost of a little too
	 * much emphasis on the last few entries.
	 */
	unsigned long rand = mfspr(SPRN_TBLO);
	rand &= 0x3f;
        rand += NTLB_WIRED;
        if (rand >= NTLB)
		rand -= NTLB_WIRED;

	asm("tlbwe %0,%1,1" : : "r" (tlblo), "r" (rand));
	asm("tlbwe %0,%1,0" : : "r" (tlbhi), "r" (rand));
	asm("isync;sync");
}

static inline void
mkTlbEntry(unsigned long addr, pte_t *pte) {
	unsigned long tlbhi;
	unsigned long tlblo;
	int found = 1;
	int idx;

	/*
	 * Construct the TLB entry.
	 */
	tlbhi = addr & ~(PAGE_SIZE-1);
	tlblo = virt_to_phys(pte_page(*pte)) & TLBLO_RPN;
	if (pte_val(*pte) & _PAGE_HWWRITE)
		tlblo |= TLBLO_WR;
	if (pte_val(*pte) & _PAGE_NO_CACHE)
		tlblo |= TLBLO_I;
	tlblo |= TLBLO_EX;
	if (addr < KERNELBASE)
		tlblo |= TLBLO_Z_USER;
	tlbhi |= TLBHI_PGSZ_4K;
	tlbhi |= TLBHI_VALID;

	/*
	 * See if a match already exists in the TLB.
	 */
	asm("tlbsx. %0,0,%2;beq 1f;li %1,0;1:" : "=r" (idx), "=r" (found) : "r" (tlbhi));
	if (found) {
		/*
		 * Found an existing entry. Just reuse the index.
		 */
		asm("tlbwe %0,%1,0" : : "r" (tlbhi), "r" (idx));
		asm("tlbwe %0,%1,1" : : "r" (tlblo), "r" (idx));
	}
	else {
		/*
		 * Do the more expensive operation
		 */
		tlbDropin(tlbhi, tlblo);
	}
}

#if 0
/*
 * Mainline of the TLB miss handler. The above inline routines should fold into
 * this one, eliminating most function call overhead.
 */
#ifdef TLBMISS_DEBUG
volatile unsigned long miss_start;
volatile unsigned long miss_end;
#endif

static inline int tlbMiss(struct pt_regs *regs, unsigned long badaddr, int wasWrite)
{
	int spid, ospid;
	struct mm_struct *mm;
	pgd_t *pgd;
	pmd_t *pmd;
	pte_t *pte;

	if (!user_mode(regs) && (badaddr >= KERNELBASE)) {
		mm = &init_mm;
		spid = 0;
#ifdef TLBMISS_DEBUG
                miss_start = 0;
#endif
	}
	else {
		mm = current->mm;
		spid = mfspr(SPRN_PID);
#ifdef TLBMISS_DEBUG
                miss_start = 1;
#endif
	}
#ifdef TLBMISS_DEBUG
        store_cache_range((unsigned long)&miss_start, sizeof(miss_start));
#endif

	pgd = pgd_offset(mm, badaddr);
	if (pgd_none(*pgd))
		goto NOGOOD;

	pmd = pmd_offset(pgd, badaddr);
	if (pmd_none(*pmd))
		goto NOGOOD;

	pte = pte_offset(pmd, badaddr);
	if (pte_none(*pte))
		goto NOGOOD;
	if (!pte_present(*pte))
		goto NOGOOD;
#ifdef CONFIG_XMON
        prohibit_if_guarded(badaddr, sizeof(int));
#endif
	if (wasWrite) {
		if (!pte_write(*pte)) {
			goto NOGOOD;
		}
		set_pte(pte, pte_mkdirty(*pte));
	}
	set_pte(pte, pte_mkyoung(*pte));

	ospid = mfspr(SPRN_PID);
	mtspr(SPRN_PID, spid);
	mkTlbEntry(badaddr, pte);
	mtspr(SPRN_PID, ospid);

#ifdef TLBMISS_DEBUG
        miss_end = 0;
        store_cache_range((unsigned long)&miss_end, sizeof(miss_end));
#endif
	return 0;

NOGOOD:
#ifdef TLBMISS_DEBUG
        miss_end = 1;
        store_cache_range((unsigned long)&miss_end, sizeof(miss_end));
#endif
        return 1;
}
#endif

/*
 * End TLB miss handling code.
 */
/* ---------- */

/*
 * Used to flush the TLB if the page fault handler decides to change
 * something.
 */
void update_mmu_cache(struct vm_area_struct *vma, unsigned long addr, pte_t pte) {
	unsigned long flags;

	save_and_cli(flags);

	if (addr >= KERNELBASE) {
		int ospid = get_context();
		tlbFlush1(addr, 0);
		set_context(0);
		mkTlbEntry(addr, &pte);
		set_context(ospid);
	}
	else {
		if (current->mm->context != NO_CONTEXT)
			tlbFlush1(addr, current->mm->context);
		else {
			get_mmu_context(current->mm);
			set_context(current->mm->context);
		}
#ifdef CONFIG_XMON
		if (current->mm->context != get_context())
			debug("Inconsistent memory context", 0);
#endif
		mkTlbEntry(addr, &pte);
	}

	restore_flags(flags);
}

/*
 * Given a virtual address in the current address space, make
 * sure the associated physical page is present in memory,
 * and if the data is to be modified, that any copy-on-write
 * actions have taken place.
 */
unsigned long make_page_present(unsigned long p, int rw) {
        pte_t *pte;
        char c;

        get_user(c, (char *) p);

        pte = findPTE(current->mm, p);
        if (pte_none(*pte) || !pte_present(*pte))
            debug("make_page_present didn't load page", 0);

        if (rw && !pte_dirty(*pte)) {
            /*
            * You have to write-touch the page, so that
            * zero-filled pages are forced to be copied
            * rather than still pointing at the zero
            * page.
            */
            extern void tlbFlush1(unsigned long, int);
            tlbFlush1(p, get_context());
            put_user(c, (char *) p);
            if (!pte_write(*pte))
                debug("make_page_present didn't make page writable", 0);

            tlbFlush1(p, get_context());
        }
        return pte_page(*pte);
}

void DataTLBMissException(struct pt_regs *regs)
{
	unsigned long badaddr = mfspr(SPRN_DEAR);
	int wasWrite = mfspr(SPRN_ESR) & 0x800000;
	unsigned long flags;
	if (!current || !current->mm)
		debug("Early address fault", regs);
	save_flags(flags);
	sti();
	do_page_fault(regs, badaddr, wasWrite);
	restore_flags(flags);
}

void InstructionTLBMissException(struct pt_regs *regs)
{
	unsigned long flags;
	save_flags(flags);
	if (!current || !current->mm)
		debug("Early instruction fault", regs);
	sti();
	do_page_fault(regs, regs->nip, 0);
	restore_flags(flags);
}

void DataPageFault(struct pt_regs *regs)
{
	unsigned long badaddr = mfspr(SPRN_DEAR);
	int wasWrite = mfspr(SPRN_ESR) & 0x800000;
	unsigned long flags;
	save_flags(flags);
	sti();
	do_page_fault(regs, badaddr, wasWrite);
	restore_flags(flags);
}

void InstructionPageFault(struct pt_regs *regs)
{
	unsigned long flags;
	save_flags(flags);
	if (!current) {
		debug("ITLB fault with no current task", regs);
		sti();
		bad_page_fault(regs, regs->nip);
	}
	else {
		sti();
		do_page_fault(regs, regs->nip, 0);
	}
	restore_flags(flags);
}

#ifdef CONFIG_XMON
int XmonTLBMiss(struct pt_regs *regs) {
	return 1;
}
#endif
