/*
 * Copyright (c) 2007 Pavel Jancik, Michal Kebrt
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 * - The name of the author may not be used to endorse or promote products
 *   derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/** @addtogroup arm32mm
 * @{
 */
/** @file
 */
#include <panic.h>
#include <arch/exception.h>
#include <arch/debug_print/print.h>
#include <arch/mm/page_fault.h>
#include <mm/as.h>
#include <genarch/mm/page_pt.h>
#include <arch.h>
#include <interrupt.h>


//TODO: remove in final version
static void print_istate(istate_t* istate);
static void print_istate(istate_t* istate) {
	dprintf("\nIstate dump:\n");
	dprintf("    r0:%X    r1:%X    r2:%X    r3:%X\n", istate->r0,  istate->r1, istate->r2,  istate->r3);
	dprintf("    r4:%X    r5:%X    r6:%X    r7:%X\n", istate->r4,  istate->r5, istate->r6,  istate->r7);
	dprintf("    r8:%X    r8:%X   r10:%X   r11:%X\n", istate->r8,  istate->r9, istate->r10, istate->r11);
	dprintf("   r12:%X    sp:%X    lr:%X  spsr:%X\n", istate->r12, istate->sp, istate->lr,  istate->spsr);
	dprintf("   pc:%X\n", istate->pc);
 
}

/**
 * \return Value stored in fault status register
 */
static inline fault_status_t read_fault_status_register() {
        fault_status_union_t tmp;
        asm volatile (
		"mrc p15, 0, %0, c5, c0, 0"
        	: "=r"(tmp.dummy)
	);
	return tmp.fsr;
}

/**
 * \return Virtual adress. Access on this addres caused exception
 */
static inline uintptr_t read_fault_address_register() {
        uintptr_t tmp;
	// Fault adress is stored in coprocessor15, register 6
	asm volatile (
		"mrc p15, 0, %0, c6, c0, 0"
		: "=r"(tmp)
	);
	return tmp;
};

/** Check type of instruction
 * \param i_code Instruction op code
 * \return true if instruction is load or store, false otherwise
 */
static inline bool load_store_instruction(instruction_t i_code) {

	 // load store immediate offset
	if (i_code.instr_type == 0x2) {
		return true;
	};

        // load store register offset
        if (i_code.instr_type == 0x3 && i_code.bit4 == 0) {
		return true;
	};

        // load store multiple
        if (i_code.instr_type == 0x4) {
		return true;
	};

        // coprocessor load / strore
	if (i_code.instr_type == 0x6) {
		return true;
	};

	return false;
}

/** Check type of instruction
 * \param i_code Instruction op code
 * \return true if instruction is swap, false otherwise
 */
static inline bool swap_instruction(instruction_t i_code) {

	// swap, swapb instruction
	if (i_code.instr_type == 0x0 &&
	    (i_code.opcode == 0x8 || i_code.opcode == 0xA) &&
	    i_code.access == 0x0 && i_code.bits567 == 0x4 &&
	    i_code.bit4 == 1) {
		return true;
	};

	return false;
}


/**
 * Decode instruction and decide if try to read or write into memmory.
 *
 * \param instr_addr address of instruction which attempts access into memmory
 * \param badvaddr Virtual address on which instruction tries to access
 * \return type of access into memmory
 *  Note: return PF_ACESS_EXEC if no memmory acess
 */
//TODO: remove debug print in final version ... instead panic return PF_ACESS_EXEC
static pf_access_t get_memmory_access_type(uint32_t instr_addr, uintptr_t badvaddr) {
        instruction_union_t tmp;
        tmp.ip = instr_addr;
	// get instruction op code
	instruction_t i_code = *(tmp.instr);

//         dprintf("get_instruction_memmory_access\n");
// 	dprintf(" instr_addr:%X\n",instr_addr);
// 	dprintf(" i_code:%X\n",i_code);
// 	dprintf(" i_code.condition:%d\n", i_code.condition);
// 	dprintf(" i_code.instr_type:%d\n",i_code.instr_type);
// 	dprintf(" i_code.opcode:%d\n",i_code.opcode);
// 	dprintf(" i_code.acess:%d\n", i_code.access);
// 	dprintf(" i_code.dummy:%d\n", i_code.dummy);
// 	dprintf(" i_code.bits567%d\n", i_code.bits567);
// 	dprintf(" i_code.bit4:%d\n", i_code.bit4);
// 	dprintf(" i_code.dummy1:%d\n", i_code.dummy1);


        // undefined instructions ... (or special instructions)
	if (i_code.condition == 0xf) {
		panic("page_fault - on instruction not acessing to memmory (instr_code:%X, badvaddr:%X)",i_code, badvaddr);
		return PF_ACCESS_EXEC;
	};

	// load store instructions
        if (load_store_instruction(i_code)) {
		if ( i_code.access == 1) {
			return PF_ACCESS_READ;
		} else {
			return PF_ACCESS_WRITE;
		}
	};

	// swap, swpb instruction
	if (swap_instruction(i_code))
	 {
		/* Swap instructions make read and write in one step.
		 * Type of access that caused exception have to page tables
		 *  and access rights.
		 */
//TODO: ALF!!!!! cann't use AS as is define as THE->as and THE structure is sored after stack_base of current thread
//      but now ... in exception we have separate stacks <==> different stack_pointer ... so AS contains nonsence data
//  same case as_page_fault .... it's nessesary to solve "stack" problem
                pte_level1_t* pte = (pte_level1_t*)
			pt_mapping_operations.mapping_find(AS, badvaddr);

		ASSERT(pte);

                /* check if read possible
                 * Note: Don't check PTE_READABLE because it returns 1 everytimes */
		if ( !PTE_PRESENT(pte) ) {
		        return PF_ACCESS_READ;
		}
		if ( !PTE_WRITABLE(pte) ) {
			return PF_ACCESS_WRITE;
		}
		else
			// badvaddr is present readable and writeable but error occured ... why?
			panic("page_fault - swap instruction, but address readable and writeable (instr_code:%X, badvaddr:%X)",i_code, badvaddr);
	}
	panic("page_fault - on instruction not acessing to memmory (instr_code:%X, badvaddr:%X)",i_code, badvaddr);
	return PF_ACCESS_EXEC;
}

/**
 * Routine that solves exception data_abourt
 *  ... you try to load or store value into invalid memmory address
 * \param istate State of CPU when data abourt occured
 * \param n number of exception
 */
//TODO: remove debug prints in final tested version
void data_abort(int n, istate_t *istate) {
        fault_status_t fsr = read_fault_status_register();
        uintptr_t  page = read_fault_address_register();

	pf_access_t access = get_memmory_access_type( istate->pc, page);

// 	print_istate(istate);
	dprintf(" page fault : ip:%X, va:%X, status:%x(%x), access:%d\n", istate->pc, page, fsr.status,fsr, access);

/* Alf: Will be commented until stack problem will be solved ...
	as_page_fault make consequent page faults*/

        int ret = as_page_fault(page, access, istate);
	dprintf(" as_page_fault ret:%d\n", ret);
        if (ret == AS_PF_FAULT) {
		fault_if_from_uspace(istate, "Page fault: %#x", page);
		panic("page fault\n");
        }

	// TODO: Remove this ... now for testing purposes ... it's bad to test page faults in kernel, where no page faults should occures
// 	panic("page fault ... solved\n");

}

/**
 * Routine that solves exception prefetch_about
 *  ... you try to execute instruction on invalid address
 * \param istate State of CPU when prefetch abourt occured
 * \param n number of exception
 */
void prefetch_abort(int n, istate_t *istate) {
	print_istate(istate);
	dprintf(" prefetch_abourt ... instruction on adress:%x can't be fetched\n", istate->pc);

/* Alf: Will be commented until stack problem will be solved ...
	as_page_fault make consequent page faults*/

	int ret = as_page_fault(istate->pc, PF_ACCESS_EXEC, istate);
	dprintf(" as_page_fault ret:%d\n", ret);
        if (ret == AS_PF_FAULT) {
                panic("page fault - instruction fetch at addr:%X\n", istate->pc);
        }


// 	panic("Prefetch abourt ... solved");
}

/** @}
 */
 
