/*
    Copyright  (c) by Sebi Ryffel,
    Swiss Federal Institute of Technology,
    Computer Engineering and Networks Laboratory.
 
    TOPSY -- A Teachable Operating System.
             Implementation of a tiny and simple
             micro kernel for teaching purposes.
 
    For further information, please visit http://www.tik.ee.ethz.ch/~topsy
 
    This software is provided under the terms of the GNU General Public Licence.
 
    A full copy of the GNU GPL is provided in the file COPYING found in the 
    development root of Topsy.
*/
/*
	
	File:                  $Source: /home/ruf/cvs_Topsy/cvs/Topsy/VMemory/mips/Attic/vm_pmap_exc.c,v $
 	Author(s):             
 	Affiliation:           ETH Zuerich, TIK
 	Version:               $Revision: 2922 $
 	Creation Date:         
 	Last Date of Change:   $Date: 2007-03-19 17:48:52 +0100 (Mon, 19 Mär 2007) $      by: $Author: baderma $
		
*/

#include "Threads.h"
#include "Messages.h"
#include "TMHal.h"
#include "MMHal.h"
#include "TMIPC.h"
#include "MemoryLayout.h"
#include "TMScheduler.h"
#include "TMProtectionDomain.h"
#include "Assert.h"
#include "Support.h"
#include "Exception.h"

#include "vm.h"

#include "vm_pmap_asm.h"

#define PF_ERR_PRESENT      0x01
#define PF_ERR_WRITE        0x02
#define PF_ERR_USER         0x04
#define PF_ERR_RESERVED     0x08
#define PF_ERR_INSTRUCTION  0x10

void vm_page_fault_exeption_handler(ThreadId currentThread);
void vm_bound_exceeded_exeption_handler(ThreadId currentThread);
void vm_coproc_seg_overrun_exeption_handler(ThreadId currentThread);
void vm_invalid_tss_exeption_handler(ThreadId currentThread);
void vm_seg_not_present_exeption_handler(ThreadId currentThread);
void vm_stack_fault_exeption_handler(ThreadId currentThread);
void vm_alignment_fault_exeption_handler(ThreadId currentThread);

void vm_exception_handler(ThreadId currentThread, char* error_string);

void vm_install_exception_handlers(void)
{
  tmSetExceptionHandler(T_EX_05, vm_bound_exceeded_exeption_handler);
  tmSetExceptionHandler(T_EX_09, vm_coproc_seg_overrun_exeption_handler);
  tmSetExceptionHandler(T_EX_0A, vm_invalid_tss_exeption_handler);
  tmSetExceptionHandler(T_EX_0B, vm_seg_not_present_exeption_handler);
  tmSetExceptionHandler(T_EX_0C, vm_stack_fault_exeption_handler);
  tmSetExceptionHandler(T_EX_0E, vm_page_fault_exeption_handler);
  tmSetExceptionHandler(T_EX_11, vm_alignment_fault_exeption_handler);
}


void vm_exception_lookup(ThreadPtr threadPtr, address_t bad_addr, int type)
{
  Message msg;
  ThreadId currentThread = threadPtr->id;
  int eflags;

  /* the bootup thread has id 0 as soon the scheduler is running this
  	 thread is no longer existant and there is will be no thread with id
  	 0 again.
   */
  if ( currentThread == 0 )
    PANIC("vm_pmap_lookup failed and there is no VM Manager running");
  if ( currentThread == MMTHREADID )
  {
    DisplayContext("",threadPtr->contextPtr);
    PANIC("VM Manager caused a fault and vm_pmap_lookup failed");
  }

  /* Set us asleep. Until MMTHRED has done the lookup */
  LOCK_THREAD(threadPtr, eflags);
  {
    tmHalThreadSetBlocked(threadPtr, PAGE_FAULT); 
  }
  UNLOCK_THREAD(threadPtr, eflags);

  /* message to MMTHREAD */
  msg.id = VM_LOOKUP;
  msg.from = currentThread;
  msg.enveloppe = NULL;
  msg.msg.vm_lookup.pdId = threadPtr->pdPtr->pdId;
  msg.msg.vm_lookup.badVAddr = bad_addr;
  msg.msg.vm_lookup.type = type;
  // printf("vm_exeption_lookup: send msg to %i:mmThread from %i  pdId %i\n", MMTHREADID, currentThread, threadPtr->pdPtr->pdId);
  kSend(MMTHREADID, &msg);
  /* OK, now it's MMTHREADs turn to find the address.
  	 If it was a bad address MMTHREAD will kill us.
  	 If all was OK MMTHREAD will use schedulerSetReady() to wake us up
  	 then we will run just from where we did the TLB miss.
   */

  return;

}

void vm_page_fault_exeption_handler(ThreadId currentThread)
{
  struct vm_pmap *pmap;
  ThreadPtr thread;
  address_t page_fault_addr;
  address_t page_fault_eip;
  int pf_err, tf_err;

  /* then get the faulting thread */
  thread = __get_thread_from_esp();
  ASSERT(thread != NULL && thread->pdPtr != NULL);

  tf_err = thread->contextPtr->sf->tf_err;

  pmap = &thread->pdPtr->vm_space.vm_pmap;
  page_fault_addr = __get_page_fault_addr();
  page_fault_eip = (address_t)thread->contextPtr->sf->tf_eip; //__get_page_fault_eip();

  // printf("    in Thread %i:\"%s\" of PD %i:\"%s\"\n", thread->id, thread->name, thread->pdPtr->pdId, thread->pdPtr->name);
  // printf("    addr=0x%08x in instruction at 0x%08x err=0x%x\n", page_fault_addr, page_fault_eip, tf_err);

  // Find out Error 
  if (tf_err & PF_ERR_INSTRUCTION)  // Instruction fetch error
    pf_err = PMAP_EXEC;
  else if (tf_err & PF_ERR_WRITE)   // Write error
    pf_err = PMAP_WRITE;
  else  
    pf_err = PMAP_READ;

  // let the MMTHREAD do the lookup
  vm_exception_lookup(thread, (address_t)page_fault_addr, pf_err);

  return;
}


void vm_stack_fault_exeption_handler(ThreadId currentThread) 
{
  struct vm_pmap *pmap;
  ThreadPtr threadPtr;
  address_t page_fault_addr;
  address_t page_fault_eip;
  int pf_err, tf_err;

  /* then get the faulting thread */
  threadPtr = __get_thread_from_esp();
  ASSERT(threadPtr != NULL && threadPtr->pdPtr != NULL);

  tf_err = threadPtr->contextPtr->sf->tf_err;

  if (tf_err == 0)
  {
    // limit violation => terminate faulting thread
    vm_exception_handler(currentThread, "Stack Fault: Limit violation");
    // vm_exception_handler never returns
  }
  else
  {
    unsigned short ss = tf_err;   // segment selector
    GDTEPtr gdtePtr;
    Get_GDTE(ss, gdtePtr);        // get GDT-Entry of stack segment
    

    if (~gdtePtr->Byte5 & MM_B5_P)
    {
      // stack segment not present
      vm_exception_handler(currentThread, "Stack Fault: Stack Segment not present");
    } 
    else
    {
      // unknown error => terminate thread
      vm_exception_handler(currentThread, "Stack Fault: unknown error");
    }

  }
}



/* Define Exception Handlers */
#define VM_EXCEPTION_HANDLER(handler, string)   \
  void (handler)(ThreadId currentThread) \
  { \
    vm_exception_handler(currentThread, string); \
  }

/* Generic Exception Handler */
void vm_exception_handler(ThreadId currentThread, char* error_string)
{
  int eflags;
  Message msg;
  Thread* threadPtr = getTHbyId(currentThread);

  msg.id = TM_KILL;
  msg.from = MMTHREADID;
  msg.msg.tmKill.id = currentThread;

  WARNING(error_string);
  
  if (currentThread == TMTHREADID)
    PANIC("error in the Thread Management thread");

  // asm("hlt");

  /* Set us asleep */
  LOCK_THREAD(threadPtr, eflags);
  {
    tmHalThreadSetBlocked(threadPtr, ACTIVE); 
  }
  UNLOCK_THREAD(threadPtr, eflags);

  /* immediate in-kernel delivery without syscall
   * kSend does a schedule after the message arrived
   */

  kSend(TMTHREADID, &msg);
}

VM_EXCEPTION_HANDLER(vm_bound_exceeded_exeption_handler, "BOUND Range Exceeded Exception");
VM_EXCEPTION_HANDLER(vm_coproc_seg_overrun_exeption_handler, "Coprocessor Segment Overrun");
VM_EXCEPTION_HANDLER(vm_invalid_tss_exeption_handler, "Invalid TSS Exception");
VM_EXCEPTION_HANDLER(vm_seg_not_present_exeption_handler, "Segment Not Present");
VM_EXCEPTION_HANDLER(vm_alignment_fault_exeption_handler, "Alignment Check Exception");

