/* Machine independent exception handling
   Copyright (C) 1992 Free Software Foundation

This file is part of the GNU Hurd.

The GNU Hurd 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, or (at your option)
any later version.

The GNU Hurd 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 the GNU Hurd; see the file COPYING.  If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */

/* Written by Michael I. Bushnell.  */

/* The basic idea here is to catch the exceptions we receive from the
   kernel.  Memory faults result in a longjmp to the thread faulting.  It
   will report an appropriate error to the user.  Other faults result in
   going away. */

#include "ufs.h"
#include <assert.h>
#include <stdio.h>
#include <mach/exception.h>
#include <mach/exc.h>

#define EXC_TABLE_SIZE 1024	/* Ack!XXX -- should be dynamically sized!  */
/* This is the table of addresses for which we should report faults
   back to the faulting thread. */
static struct 
{
  void *offset;
  long length;
} memory_fault_table[EXC_TABLE_SIZE];

static struct mutex memory_fault_lock;
static int check_fault_addr (int);
static mach_port_t excport;
static mach_port_t oldexcport;

/* Called by the microkernel (through a MiG server) when we take an 
   exception.  Generally we panic, but if the exception is a reference
   to some piece of disk, allow the faulting routine to handle it.  */
error_t
catch_exception_raise (mach_port_t exc_port,
		       thread_t thread,
		       task_t task,
		       int exc,
		       int code,
		       int subcode)
{
  char *name;
  
  assert (task == mach_task_self ());

  switch (exc)
    {
    case EXC_BAD_INSTRUCTION:
      name = "Illegal instruction";
      break;
    case EXC_ARITHMETIC:
      name = "Arithmetic violation (or panic)";
      break;
    case EXC_EMULATION:
      name = "Emulation fault";
      break;
    case EXC_SOFTWARE:
      name = "Software fault";
      break;
    case EXC_BREAKPOINT:
      name = "Breakpoint fault";
      break;
    case EXC_BAD_ACCESS:
      if (check_fault_addr (subcode))
	{
	  report_memory_fault (thread, code);
	  printf ("bad access exc 0x%x 0x%x PC: 0x%x\n", code, subcode,
		  thread_pc (thread));
	  return 0;
	}
      name = "Address fault";
      break;
    default:
      name = "Unknown fault";
      break;
    }
  
  if (oldexcport)
    return exception_raise (oldexcport, thread, task, exc, code, subcode);
  else
    {
      printf ("%s, code == 0x%x, subcode == 0x%x\n", name, code, subcode);
      printf ("PC=0x%x\n", thread_pc (thread));

      task_terminate (mach_task_self ());
      for (;;);			/* Prevent GCC complaints */
#if 0
      /* Suspension is helpful if there's a debugger waiting.  The startup
	 task also recognizes suspension as indicating a fatal error in the
	 task. */
      task_suspend (mach_task_self ());
      for (;;);
#endif
    }
}

/* Mark the memory at ADDR continuing for LEN bytes as mapped from the 
   disk.  Call when vm_map-ing part of the disk. 
   CAVEAT: addr must not be zero. */
void
register_memory_fault_area (void *addr, 
			    long len)
{
  int i;

  assert (addr);

  mutex_lock (&memory_fault_lock);
  
  for (i = 0; i < EXC_TABLE_SIZE; i++)
    if (!memory_fault_table[i].offset)
      {
	memory_fault_table[i].offset = addr;
	memory_fault_table[i].length = len;
	mutex_unlock (&memory_fault_lock);
	return;
      }

  panic ("out of memory fault slots");
}

/* Mark the memory at ADDR continuing for LEN bytes as no longer
   mapped from the disk.  Call when vm_unmap-ing part of the disk.  */
void
unregister_memory_fault_area (void *addr,
			      long len)
{
  int i;

  assert (addr);

  mutex_lock (&memory_fault_lock);
  for (i = 0; i < EXC_TABLE_SIZE; i++)
    if (memory_fault_table[i].offset == addr
	&& memory_fault_table[i].length == len)
      {
	memory_fault_table[i].offset = 0;
	mutex_unlock (&memory_fault_lock);
	return;
      }
  panic ("unregister_memory_fault_area");
}

/* Return true if IADDR lies within a section of memory mapped from
   the disk.  */
static int
check_fault_addr (iaddr)
     int iaddr;
{
  void *addr = (void *)iaddr;
  int i;
  
  mutex_lock (&memory_fault_lock);
  for (i = 0; i < EXC_TABLE_SIZE; i++)
    if (memory_fault_table[i].offset
	&& addr - memory_fault_table[i].offset < memory_fault_table[i].length
	&& addr - memory_fault_table[i].offset > 0)
      {
	mutex_unlock (&memory_fault_lock);
	return 1;
      }
  mutex_unlock (&memory_fault_lock);
  return 0;
}

/* Main loop for thread handling exceptions.  */
static void
exception_thread ()
{
  error_t error;
  error_t mach_msg_server (int (*)(mach_msg_header_t *, mach_msg_header_t *),
			   int, mach_port_t);
  error_t exc_server (mach_msg_header_t *, mach_msg_header_t *);

  error = mach_msg_server (exc_server, __vm_page_size, excport);
  panic_with_error ("mach_msg_server for exceptions", error);
}

/* Set up the exception handling system.  */
void
init_exceptions ()
{
  int i;

  for (i = 0; i < EXC_TABLE_SIZE; i++)
    memory_fault_table[i].offset = 0;
  mutex_init (&memory_fault_lock);

  mach_port_allocate (mach_task_self (), MACH_PORT_RIGHT_RECEIVE, &excport);
  mach_port_insert_right (mach_task_self (), excport, excport, 
			  MACH_MSG_TYPE_MAKE_SEND);
  task_get_special_port (mach_task_self (), TASK_EXCEPTION_PORT, &oldexcport);
  task_set_special_port (mach_task_self (), TASK_EXCEPTION_PORT, excport);
  mach_port_deallocate (mach_task_self (), excport);
  cthread_detach (cthread_fork ((cthread_fn_t) exception_thread, (any_t) 0));
}
