/*
 *  arch/s390/mm/fault.c
 *
 *  S390 version
 *    Copyright (C) 1999 IBM Deutschland Entwicklung GmbH, IBM Corporation
 *    Author(s): Hartmut Penner (hp@de.ibm.com)
 *
 *  Derived from "arch/i386/mm/fault.c"
 *    Copyright (C) 1995  Linus Torvalds
 */

#include <linux/signal.h>
#include <linux/sched.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/smp.h>
#include <linux/smp_lock.h>

#include <asm/system.h>
#include <asm/uaccess.h>
#include <asm/pgtable.h>
#include <asm/hardirq.h>

extern void die(const char *,struct pt_regs *,long);

/*
 * This routine handles page faults.  It determines the address,
 * and the problem, and then passes it off to one of the appropriate
 * routines.
 *
 * error_code:
 *             ****0004       Protection           ->  Write-Protection  (suprression)
 *             ****0010       Segment translation  ->  Not present       (nullification)
 *             ****0011       Page translation     ->  Not present       (nullification)
 */
asmlinkage void do_page_fault(struct pt_regs *regs, unsigned long error_code)
{
        struct task_struct *tsk;
        struct mm_struct *mm;
        struct vm_area_struct * vma;
        unsigned long address;
        unsigned long fixup;
        int write;
        unsigned long psw_mask;
        unsigned long psw_addr;

        /*
         *  get psw mask of Program old psw to find out,
         *  if user or kernel mode
         */

        psw_mask = S390_lowcore.program_old_psw.mask;
        psw_addr = S390_lowcore.program_old_psw.addr;

        /* 
         * get the failing address 
         * more specific the segment and page table portion of 
         * the address 
         */

        address = S390_lowcore.trans_exc_code&0x7ffff000;

        tsk = current;
        mm = tsk->mm;

        if (atomic_read(&S390_lowcore.local_irq_count))
                die("page fault from irq handler",regs,error_code);

        down(&mm->mmap_sem);

        vma = find_vma(mm, address);
        if (!vma) {
	        printk("no vma for address %lX\n",address);
                goto bad_area;
        }
        if (vma->vm_start <= address) 
                goto good_area;
        if (!(vma->vm_flags & VM_GROWSDOWN)) {
                printk("VM_GROWSDOWN not set, but address %lX \n",address);
                printk("not in vma %p (start %lX end %lX)\n",vma,
                       vma->vm_start,vma->vm_end);
                goto bad_area;
        }
        if (expand_stack(vma, address)) {
                printk("expand of vma failed address %lX\n",address);
                printk("vma %p (start %lX end %lX)\n",vma,
                       vma->vm_start,vma->vm_end);
                goto bad_area;
        }
/*
 * Ok, we have a good vm_area for this memory access, so
 * we can handle it..
 */
good_area:
        write = 0;
        switch (error_code & 0xFF) {
                case 0x04:                                /* write, present*/
                        write = 1;
                        break;
                case 0x10:                                   /* not present*/
                case 0x11:                                   /* not present*/
                        if (!(vma->vm_flags & (VM_READ | VM_EXEC | VM_WRITE))) {
                                printk("flags %X of vma for address %lX wrong \n",
                                       vma->vm_flags,address);
                                printk("vma %p (start %lX end %lX)\n",vma,
                                       vma->vm_start,vma->vm_end);
                                goto bad_area;
                        }
                        break;
                default:
                       printk("code should be 4, 10 or 11 (%lX) \n",error_code&0xFF);  
                       goto bad_area;
        }

	/*
	 * If for any reason at all we couldn't handle the fault,
	 * make sure we exit gracefully rather than endlessly redo
	 * the fault.
	 */
survive:
	{
	  int fault = handle_mm_fault(tsk, vma, address, write);
	  if (!fault)
		  goto do_sigbus;
	  if (fault < 0)
		  goto out_of_memory;
	}

        up(&mm->mmap_sem);
        return;

/*
 * Something tried to access memory that isn't in our memory map..
 * Fix it, but check if it's kernel or user first..
 */
bad_area:
        up(&mm->mmap_sem);

        /* User mode accesses just cause a SIGSEGV */
        if (psw_mask & PSW_PROBLEM_STATE) {
                tsk->tss.prot_addr = address;
                tsk->tss.error_code = error_code;
                tsk->tss.trap_no = 14;

                printk("User process fault: interruption code 0x%lX\n",error_code);
                printk("failing address: %lX\n",address);
		show_crashed_task_info();
                force_sig(SIGSEGV, tsk);
                return;
	}

 no_context:
        /* Are we prepared to handle this kernel fault?  */
        if ((fixup = search_exception_table(regs->psw.addr)) != 0) {
                regs->psw.addr = fixup;
                return;
        }

/*
 * Oops. The kernel tried to access some bad page. We'll have to
 * terminate things with extreme prejudice.
 *
 * First we check if it was the bootup rw-test, though..
 */
        if (address < PAGE_SIZE)
                printk(KERN_ALERT "Unable to handle kernel NULL pointer dereference");
        else
                printk(KERN_ALERT "Unable to handle kernel paging request");
        printk(" at virtual address %08lx\n",address);
/*
 * need to define, which information is useful here
 */

        die("Oops", regs, error_code);
        do_exit(SIGKILL);


/*
 * We ran out of memory, or some other thing happened to us that made
 * us unable to handle the page fault gracefully.
 */
out_of_memory:
	if (tsk->pid == 1)
                {
		tsk->policy |= SCHED_YIELD;
		schedule();
		goto survive;
		    }
	up(&mm->mmap_sem);
	if (psw_mask & PSW_PROBLEM_STATE)
	{
		printk("VM: killing process %s\n", tsk->comm);
		do_exit(SIGKILL);
                }
	goto no_context;

do_sigbus:
	up(&mm->mmap_sem);

	/*
	 * Send a sigbus, regardless of whether we were in kernel
	 * or user mode.
	 */
        tsk->tss.prot_addr = address;
        tsk->tss.error_code = error_code;
        tsk->tss.trap_no = 14;
	force_sig(SIGBUS, tsk);

	/* Kernel mode? Handle exceptions or die */
	if (!(psw_mask & PSW_PROBLEM_STATE))
		goto no_context;
}

