/*
 * hal_do_exception.c - implementation of exception handler for TSAR-MIPS32
 * 
 * Authors  Ghassan Almaless (2008,2009,2010,2011,2012)
 *          Alain Greiner (2016)
 *
 * Copyright (c) UPMC Sorbonne Universites
 *
 * This file is part of ALMOS-MKH.
 *
 * ALMOS-MKH.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; version 2.0 of the License.
 *
 * ALMOS-MKH.is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with ALMOS-MKH.; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include <types.h>
#include <task.h>
#include <thread.h>
#include <kdmsg.h>
#include <pmm.h>
#include <vmm.h>
#include <errno.h>
#include <scheduler.h>
#include <cpu.h>
#include <spinlock.h>
#include <distlock.h>
#include <cpu-trace.h>
#include <cpu-regs.h>

//////////////////////////////////////////////////////////////////////////////////////////
// This enum defines the relevant values for the XCODE field from CP0_CR register.
//////////////////////////////////////////////////////////////////////////////////////////

typedef enum
{
    XCODE_ADEL = 4,        // Illegal address for data load 
    XCODE_ADES = 5,        // Illegal address for data store
    XCODE_IBE  = 6,        // Instruction MMU exception
    XCODE_DBE  = 7,        // Data MMU exception
    XCODE_RI   = 10,       // Reserved instruction exception
    XCODE_FPU  = 11,       // FPU coprocessor exception
    XCODE_OVR  = 12        // Arithmetic Overflow exception
}
xcode_values_t;

//////////////////////////////////////////////////////////////////////////////////////////
// This defines the masks used to analyse the TSAR MMU exception code
//////////////////////////////////////////////////////////////////////////////////////////

#define    TSAR_MMU_PAGE_UNMAPPED   0x0003    // page fault (PTE unmapped)
#define    TSAR_MMU_USER_PRIVILEGE  0x0004    // user access to a kernel segment
#define    TSAR_MMU_USER_WRITE      0x0008    // user access to non writable segment
#define    TSAR_MMU_USER_EXEC       0x0010    // user access to non executable segment
#define    TSAR_MMU_KERNEL_XTN      0x0020    // kernel illegal external access   
#define    TSAR_MMU_KERNEL_PT1      0x0040    // kernel illegal PT1 access   
#define    TSAR_MMU_KERNEL_PT2      0x0080    // kernel illegal PT2 access   
#define    TSAR_MMU_KERNEL_DATA     0x0100    // kernel illegal data access

//////////////////////////////////////////////////////////////////////////////////////////
// This defines the masks used to get the TSAR MMU PTE attributes
//////////////////////////////////////////////////////////////////////////////////////////

#define    TSAR_MMU_PTE_V           0x80000000  // Valid
#define    TSAR_MMU_PTE_T           0x40000000  // Small Page
#define    TSAR_MMU_PTE_C           0x08000000  // Cachable  
#define    TSAR_MMU_PTE_W           0x04000000  // Writable  
#define    TSAR_MMU_PTE_X           0x02000000  // eXecutable
#define    TSAR_MMU_PTE_U           0x01000000  // User accessible

//////////////////////////////////////////////////////////////////////////////////////////
// This enum defines the various types of error code returned to the hal_do_exception()
// function by the mmu_exception_handler() and fpu_exception_handler().
//////////////////////////////////////////////////////////////////////////////////////////

typedef enum
{
	EXCP_SOLVED        = 0,   // No error => the unmapped PTE has been mapped
	EXCP_USER_ERROR    = 1,   // User error => user process will receive a SIGSEGV
    EXCP_KERNEL_PANIC  = 2,   // Kernel error => kernel panic
}
mmu_excp_t;

/////////////////////////////////////////////////////////////////////////////////////////
// This remote_spinlock is a global variable defined in all clusters, 
// but only the spinlock implemented in the boot cluster is used.
/////////////////////////////////////////////////////////////////////////////////////////

__attribute__((section(".kdata")))
remote_spinlock_t  exception_lock              CONFIG_CACHE_LINE_ALIGNED;

/////////////////////////////////////////////////////////////////////////////////////////
// This function is called by the hal_do_exception() function when a "FPU unusable"
// exception has been detected by the calling thread.
// This function check in CP0_CR register that the unavailable CPU is actually CP1,
// it saves the FPU context in the owner thread descriptor, and restore the FPU context
// from the calling thread descriptor.
/////////////////////////////////////////////////////////////////////////////////////////
static error_t fpu_exception_handler( reg_t    * regs_tbl )
{
    thread_t * this = CURRENT_THREAD;    // calling thread
	core_t   * core = this->core;        // associated core

    // check coprocessor index
	if( ((regs_tbl[CR] >> 28) & 0x3) != 1 )
    {
		printk(WARNING, "%s for thread %x : bad coprocessor indexn",
		       __FUNCTION__ , this->trdid );
		return EXCP_KERNEL_PANIC;
    }
  
	hal_fpu_enable();

	if( (core->fpu_owner != NULL) && (core->fpu_owner != this) )
	{
		hal_fpu_context_save ( &core->fpu_owner->uzone );
	}

	hal_fpu_context_restore( &this->uzone );
	cpu->fpu_owner = this;

	return EXCP_SOLVED;
}


///////////////////////////////////////////////////////////////////////////////////
// This function is called by the hal_do_exception() function when a TSAR-MMU
// exception has been detected. There is three possible actions :
// 1) simple page fault => page table is updated and thread resume.
// 2) user error        => user process is killed.
// 3) kernel error      => system crash.
///////////////////////////////////////////////////////////////////////////////////
// @ excp_code  : generic exception code returned by TSAR-MMU
// @ bad_vaddr  : faulty virtual address 
// @ return EXCP_RESOLVED / MMU_EXCP_USER_ERROR / MMU_EXCP_KERNEL_PANIC
///////////////////////////////////////////////////////////////////////////////////
static error_t mmu_exception_handler( uint32_t   excp_code, 
                                      uint32_t   bad_vaddr )
{
	thread_t       * this;        // calling thread pointer
	vseg_t         * vseg;        // vseg containing the bad_vaddr
	process_t      * process;     // local process descriptor
	vmm_t          * vmm;         // VMM for calling thread
    vpn_t            vpn;         // VPN for bad_vaddr
    uint32_t         flags;       // vseg flags 
    error_t          error;       // return value

	this        = CURRENT_THREAD;
	process     = this->process;
	vmm         = &process->vmm;
    vpn         = bad_vaddr>>CONFIG_PPM_PAGE_SHIFT;

	vmm_dmsg(2, "%s enters for thread %x in process %x / bad_vaddr = %x / excep_code = %x\n", 
		 __FUNCTION__, this->trdid , process->pid , bad_vaddr , excep_code );

    // a kernel thread should not rise an MMU exception
	if( this->type != T_USER )
	{
		printk(WARNING, "%s for thread %x : it's a kernel thread / vaddr = %x\n",
		       __FUNCTION__ , this->trdid , bad_vaddr );
		return EXCP_KERNEL_PANIC;
	}
  
    // enable IRQs 
	hal_enable_irq( NULL );

    // update user_time 
	tm_usr_compute( this );

    // vaddr must be contained in a registered vseg
    vseg = vmm_get_vseg( process , bad_vaddr );

    if( vseg == NULL )   // vseg not found 
	{
        if( cxy != cxy_ref )  // try to get vseg from reference VMM
        {
            rpc_vmm_get_ref_vseg_client( cxy_ref , process_ref , bad_vaddr , &vseg );
        }

        if( vseg == NULL )    // illegal user vaddr => return user error
        {
            printk(WARNING, "%s for thread %x : no vseg for vaddr = %x\n",
                   __FUNCTION__ , this->trdid , bad_vaddr ); 
            hal_disable_irq( NULL );
		    return EXCP_USER_ERROR;
        }
        else                  // legal vaddr => get vseg flags
        {
            flags = vseg->flags;
        }
    }

	vmm_dmsg(2, "%s found vseg for thread %x / vseg_base = %x / vseg_flags = %x\n", 
			 __FUNCTION__ , this->trdid , vseg->begin , vseg->flags );

    // analyse TSAR MMU exception code
    if( excp_code & TSAR_MMU_UNMAPPED )
    {
        // try to map the unmapped PTE
        error = vmm_handle_page_fault( process , vseg , vpn );
        if( error )
        {
            printk(WARNING, "%s for thread %x : cannot allocate memory for new PTE\n",
                   __FUNCTION__ , this->trdid , bad_vaddr ); 
            hal_disable_irq( NULL );
		    return EXCP_KERNEL_PANIC;
        }
        else
        {
            vmm_dmsg(2, "%s page fault succesfully handled for vaddr = %x in thread %x\n",
		             __FUNCTION__ , bad_vaddr , this->trdid );
  
            // page fault successfully handled
            hal_disable_irq( NULL );

            hal_yield();  // TODO Pourquoi ce yield ?

            // update kernel_time
            tm_sys_compute(this);

            return EXCP_SOLVED;
        }
    }
    else if( excp_code & TSAR_MMU_USER_PRIVILEGE )
    {
        printk(WARNING,"%s for thread %x : user access to kernel vseg at vaddr = %x\n",
               __FUNCTION__ , thread->trdid , bad_vaddr );
        return EXCP_USER_ERROR;
    }
    else if( excp_code & TSAR_MMU_USER_EXEC )
    {
        printk(WARNING,"%s for thread %x : access to non-exec vseg at vaddr = %x\n"
               __FUNCTION__ , thread->trdid , bad_vaddr );
        return EXCP_USER_ERROR;
    }
    else if( excp_code & TSAR_MMU_USER_WRITE )
    {
        printk(WARNING,"%s for thread %x : write to non-writable vseg at vaddr = %x\n"
               __FUNCTION__ , thread->trdid , bad_vaddr );
        return EXCP_USER_ERROR;
    }
    else if( excp_code & TSAR_MMU_KERNEL_XTN )
    {
        printk(WARNING,"%s for thread %x : kernel illegal access to external address = %x\n"
               __FUNCTION__ , thread->trdid , bad_vaddr );
        return EXCP_KERNEL_PANIC;
    }
    else if( excp_code & TSAR_MMU_KERNEL_PT1 )
    {
        printk(WARNING,"%s for thread %x : kernel bus error accessing PT1 / vaddr = %x\n"
               __FUNCTION__ , thread->trdid , bad_vaddr );
        return EXCP_KERNEL_PANIC;
    }
    else if( excp_code & TSAR_MMU_KERNEL_PT2 )
    {
        printk(WARNING,"%s for thread %x : kernel bus error accessing PT2 / vaddr = %x\n"
               __FUNCTION__ , thread->trdid , bad_vaddr );
        return EXCP_KERNEL_PANIC;
    }
    else if( excp_code & TSAR_MMU_KERNEL_DATA )
    {
        printk(WARNING,"%s for thread %x : kernel bus error accessing DATA / vaddr = %x\n"
               __FUNCTION__ , thread->trdid , bad_vaddr );
        return EXCP_KERNEL_PANIC;
    }
    else
    {
        printk(WARNING,"%s for thread %x : undefined MMUexception code ??? / vaddr = %x\n"
               __FUNCTION__ , thread->trdid , bad_vaddr );
        return EXCP_KERNEL_PANIC;
    }
  
} // end mmu_exception_handler()


///////////////////////////////////////
void hal_do_exception( thread_t * this, 
                       gid_t      gid, 
                       reg_t    * regs_tbl )
{
	error_t             error;
	uint32_t            excCode;               // XCODE from CP0_CR
	uint32_t            mmu_iexcp_code;        // MMU IEXCP_CODE from CP2
	uint32_t            mmu_ibad_vaddr;        // MMU IBAD_VADDR from CP2
	uint32_t            mmu_dexcp_code;        // MMU DEXCP_CODE from CP2
	uint32_t            mmu_dbad_vaddr;        // MMU BDAD_VADDR from CP2
	bool_t              isInKernelMode;
	mmu_except_info_t * entry;
	hal_except_info_t * execErr;

    // get XCODE from CP0_CR register
	excCode        = (regs_tbl[CR] >> 2) & 0x1F;

    // get relevant values from CP2 registers
	mmu_iexcp_code = mips_get_cp2(MMU_IETR, 0);
	mmu_ibad_vaddr = mips_get_cp2(MMU_IBVAR, 0);
	mmu_dexcp_code = mips_get_cp2(MMU_DETR, 0);
	mmu_dbad_vaddr = mips_get_cp2(MMU_DBVAR, 0);

	switch(excCode)
	{
	case XCODE_IBE:
		error = mmu_exception_handler( mmu_iexcp_code , mmu_ibad_vaddr );
		break;
	case XCODE_DBE:
		error = mmu_exception_handler( mmu_dexcp_code , mmu_dbad_vaddr );
		break;
	case XCODE_CPU:
		error = fpu_exception_handler( regs_tbl );
		break;
	default:
		error = EXCP_KERNEL_PANIC;
		break;
	}

    // analyse error code
	if( error == EXCP_SOLVED )          // page fault successfully handled => just return
    { 
       return;
    }

	else if( error == EXCP_USER_ERROR ) //  user error => kill the user process and return
	{
//      TODO [AG]
//		uspace_start = (uint32_t) &__uspace_start;
//		uspace_end   = (uint32_t) &__uspace_end;
//
//		if((regs_tbl[EPC] >= uspace_start) && (regs_tbl[EPC] <= uspace_end))
//		{
//			regs_tbl[EPC] = (reg_t) &hal_uspace_error;
//			regs_tbl[MMU_MD] = (reg_t) 0x3;//MMU_MODE OFF
//			return;
//		}
	}
    else                                // kernel error => kernel panic
    {
        // take the exception_lock located in boot_cluster 
        cxy_t  boot_cxy = LOCAL_CLUSTER->boot_cxy;
	    remote_spinlock_lock( XPTR( boot_cxy , &exception_lock ) );

        thread_t  * this    = CURRENT_THREAD;
        process_t * process = this->process;

        // dump registers values

	except_dmsg("====================================================================\n");
	except_dmsg("Kernel Panic: thread %x in process %x on core %x at cycle %d\n",
                this->trdid , process->pid , gid , hal_time_stamp() );

	except_dmsg("Processor State:\n");
  
	except_dmsg("CR:   %x\tEPC:  %x\tSR:   %x\tSP:    %x\tUSR SP %x\n",
		    regs_tbl[CR],regs_tbl[EPC],regs_tbl[SR],regs_tbl[SP],this->uzone.regs[SP]);

	except_dmsg("at_1  %x\tv0_2  %x\t\tv1_3  %x\ta0_4   %x\ta1_5   %x\n",
		    regs_tbl[AT],regs_tbl[V0],regs_tbl[V1],regs_tbl[A0],regs_tbl[A1]);

	except_dmsg("a2_6  %x\t\ta3_7  %x\tt0_8  %x\tt1_9   %x\tt2_10  %x\n",
		    regs_tbl[A2],regs_tbl[A3],regs_tbl[T0],regs_tbl[T1],regs_tbl[T2]);
  
	except_dmsg("t3_11 %x\tt4_12 %x\t\tt5_13 %x\tt6_14  %x\tt7_15  %x\n",
		    regs_tbl[T3],regs_tbl[T4],regs_tbl[T5],regs_tbl[T6],regs_tbl[T7]);

	except_dmsg("t8_24 %x\t\tt9_25 %x\tgp_28 %x\tc0_hi  %x\tc0_lo  %x\n",
		    regs_tbl[T8],regs_tbl[T9],regs_tbl[GP],regs_tbl[HI],regs_tbl[LO]);

	except_dmsg("s0_16 %x\ts1_17 %x\ts2_18 %x\ts3_19  %x\ts4_20  %x\n",
		    regs_tbl[S0],regs_tbl[S1],regs_tbl[S2],regs_tbl[S3],regs_tbl[S4]);
  
	except_dmsg("s5_21 %x\ts6_22 %x\t\ts7_23 %x\ts8_30  %x\tra_31  %x\n\n",
		    regs_tbl[S5],regs_tbl[S6],regs_tbl[S7],regs_tbl[S8],regs_tbl[RA]);

	except_dmsg("Thread State %x\n"
                "\tsys_stack_top = %x\n"
                "tusr_stack      = %x\n"
                "\tutls          = %x\n"
                "\tstate         = %s\n"
                "\tlocks         = %d\n",
	            this->trdid,
		        this->uzone.regs[KSP], 
                this->uzone.regs[SP], 
                this->uzone.regs[TLS_K1],
                thread_get_state_name( this->state ), 
                this->locks_count);

	isInKernelMode = (regs_tbl[SR] & 0x10) ? false : true;

	except_dmsg("\nIs in kernel: %s\n", (isInKernelMode) ? "YES" : "NO");

	if(isInKernelMode)
	{
		execErr = hal_except_get_entry(excCode);
		if(regs_tbl[EPC] >= __ktext_start && regs_tbl[EPC] <= __ktext_end)
			instContent = *((uint32_t*) regs_tbl[EPC]);
		else
			instContent = 0;

		except_dmsg("Pid %d, Cpu %d, Inst. %x, Exception : code %d, name %s, description %s, bad address %x\n",
			    this->task->pid,
			    gid,
			    instContent,
			    excCode, 
			    execErr->name, 
			    execErr->desc, 
			    hal_get_bad_vaddr()
				);
	
	}


	except_dmsg("====================================================================\n");

    // release exception lock
	remote_spinlock_unlock( XPTR( boot_cxy , &exception_lock ) );

	sched_exit(this);        // TODO ??? [AG]
	while(entry != NULL);
}
