// XtratuM
// version: 0.1
//
// (c) 2004, Miguel Masmano <mimastel@doctor.upv.es>
// Released under the terms of GPL Licence v2

#include <XtratuM.h>
#include <XtratuM_API.h>
#include <arch/XtratuM_asm.h>

struct xm_domain_struct *xm_domain_list = NULL;
struct xm_domain_struct *root_domain = NULL;
struct xm_domain_struct *current_domain = NULL;

bitmap_t global_pending_events;

static inline struct xm_domain_struct *
__create_domain_node (char *name, unsigned int priority) {
  struct xm_domain_struct *domain;

  if (!(domain = (struct xm_domain_struct *)
	vmalloc (sizeof (struct xm_domain_struct)))) {
    printk ("__create_new_domain_node(): Error calling vmalloc\n");
    printk ("(There is not enough memory?)\n");
    return NULL;
  }

  if (!(domain -> name = (char *) vmalloc 
	(sizeof (char) * (strlen (name) + 1)))) {
    printk ("__create_new_domain_node(): Error calling vmalloc\n");
    printk ("(There is not enough memory?)\n");
    vfree (domain);
    return NULL;
  }

  strcpy (domain -> name, name);

  domain -> flags = 0;
  clear_bitmap (domain -> pending_events);
  clear_bitmap (domain -> blocked_events);
  clear_bitmap (domain -> intercepted_events);
  set_bitmap (domain -> masked_events);

  domain -> priority = priority;
  domain -> next = domain -> prev = NULL;
  memset ((char *) &domain -> trap_handler[0], 0, MAX_TRAPS 
	  * sizeof (handler_t));

  return domain;
}

static int xm_init (void) {
  unsigned int flags, event;

  hw_save_flags_and_cli (flags);
  
  clear_bitmap (global_pending_events);
  
  root_domain = __create_domain_node ("Linux", 
				      xm_min_priority());
  
  if (!root_domain) return -1;
  
  xm_set_domain_state(root_domain, xm_DOMAIN_ACTIVE);

  for (event = 0; event < 16; event ++) {
    set_bit (root_domain -> intercepted_events, event);
    clear_bit (root_domain -> masked_events, event);
    root_domain -> event_handler [event] = linux_irq_handler;
  }
  current_domain = xm_domain_list = root_domain;

  xm_arch_takeover ();

  // We are able now to receive interrupts
  xm_disable_events_flag (root_domain);

  hw_restore_flags (flags);

  printk ("XtratuM initialised successfully\n");
  
  return 0;
}

static void xm_finish (void) {
  struct xm_domain_struct *domain, *aux;
  unsigned int hw_flags;
  
  hw_save_flags_and_cli (hw_flags);
  
  xm_arch_giveup ();
  // Finishing off all existing domains
  domain =  xm_domain_list;
  while (domain) {
    aux = domain;
    domain = domain -> next;
    
    if (aux -> name)
      vfree (aux -> name);
    if (aux)
      vfree (aux);
  }

  hw_restore_flags (hw_flags);

  printk ("XtratuM finished successfully\n");
}

static int wrapper_init_domain (int (*mf) (void)) {
  unsigned int flags;
  
  // Events' flag is disabled by default
  xm_enable_events_flag (current_domain);
  mf ();
  // All events are disabled
  set_bitmap (current_domain -> masked_events);
  xm_disable_events_flag (current_domain);
  hw_save_flags_and_cli (flags);
  xm_set_domain_state (current_domain, xm_DOMAIN_FINISHED);
  hw_restore_flags (flags);
  xm_sched();
  // this point never can be executed
  return 0;
}

static  void generic_trap_handler (int trap, struct pt_regs *regs) {
  printk ("<Domain \"%s\" killed> trap %d errorcode %d not handled\n",
	  current_domain -> name, trap, (int) regs -> orig_eax);
  xm_set_domain_state (current_domain, xm_DOMAIN_FINISHED);
  xm_sched();
}


///////////// XtratuM functions

int __xm_create_domain (xm_domain_t *domain, char *name, 
			 unsigned int priority, 
			 int (*main_func) (void), unsigned long pd) {
  struct xm_domain_struct *ptr, *ptr2;
  unsigned int trap;

  // A new domain can be only created from the root domain
  if (current_domain != root_domain) return -1;

  *domain = __create_domain_node (name, priority);
  if (!*domain) return -1;
 
  xm_set_domain_state ((*domain), xm_DOMAIN_ACTIVE);
  if (!((*domain) -> stack = 
	xm_create_stack (wrapper_init_domain, main_func))) {
    vfree ((*domain) -> name);
    vfree (*domain);
    return -1;
  }
  (*domain) -> begining_stack = xm_get_begining_stack ((*domain) -> stack);
  
  for (trap = 0; trap < MAX_TRAPS; trap ++)
    (*domain) -> trap_handler [trap] = generic_trap_handler;

  ptr = ptr2 = xm_domain_list; 
  
  // xm_domain_list never can be NULL at this point
  while (ptr != NULL) {
    if ((*domain) -> priority < ptr -> priority) break;
    ptr2 = ptr;
    ptr = ptr -> next;
  }

  // The new domain must be inserted on the head of the list
  if (xm_domain_list == ptr) {
    xm_domain_list = *domain;
    ptr -> prev = *domain;
    (*domain) -> next = ptr;
  } else {
    // The new domain must be inserted on the tail of the list
    if (!ptr) {
      ptr2 -> next = *domain;
      (*domain) -> prev = ptr2;
    } else {
      // The new domain must be  inserted on the middle (it means, not
      // on the head and not on the tail) of the list
      if (ptr -> prev) ptr -> prev -> next = *domain;
      (*domain) -> next = ptr;
      (*domain) -> prev = ptr -> prev;
      ptr -> prev = *domain;
    }
  }
  
  xm_sched();

  return 0;
}

void xm_destroy_domain (xm_domain_t domain) {
  unsigned int flags;

  // A domain can be only destroyed from the root domain
  if (!domain || current_domain != root_domain) return;
  
  hw_save_flags_and_cli (flags);
  if (xm_domain_list == domain) xm_domain_list = domain -> next;
  
  if (domain -> next) domain -> next -> prev = domain -> prev;
  if (domain -> prev) domain -> prev -> next = domain -> next;

  vfree (domain -> begining_stack);
  vfree (domain -> name);
  vfree (domain);
  hw_restore_flags (flags);
}

void __xm_suspend_domain (xm_domain_t domain) {
  // This  domain must  be set  as suspended  and it  only  will be
  // available again if an event for it arrives
  if (!domain)
    xm_set_domain_state (current_domain, xm_DOMAIN_SUSPENDED);
  else 
    xm_set_domain_state (domain, xm_DOMAIN_SUSPENDED);
  xm_sched();
}

void __xm_set_domain_priority (xm_domain_t domain, unsigned int priority) {
  xm_domain_t sel_domain = domain, ptr, ptr2;
  unsigned int flags;

  if (!domain) sel_domain = current_domain;
  
  hw_save_flags_and_cli (flags);
  
  if (xm_domain_list == sel_domain) 
    xm_domain_list = sel_domain -> next;

  if (sel_domain -> prev) sel_domain -> prev -> next = sel_domain -> next;
  if (sel_domain -> next) sel_domain -> next -> prev = sel_domain -> prev;
  sel_domain -> next = sel_domain -> prev = NULL;

  sel_domain -> priority = priority;

  if (!xm_domain_list) 
    xm_domain_list = sel_domain;
  else {
    ptr = ptr2 = xm_domain_list; 
    // xm_domain_list never can be NULL at this point

    while (ptr != NULL) {
      if (sel_domain -> priority < ptr -> priority) break;
      ptr2 = ptr;
      ptr = ptr -> next;
    }
    // The new domain must be inserted on the head of the list
    if (xm_domain_list == ptr) {
      xm_domain_list = sel_domain;
      ptr -> prev = sel_domain;
      sel_domain -> next = ptr;
    } else {
      // The new domain must be inserted on the tail of the list
      if (!ptr) {
	ptr2 -> next = sel_domain;
	sel_domain -> prev = ptr2;
      } else {
	// The new domain must be  inserted on the middle (it means, not
	// on the head and not on the tail) of the list
	if (ptr -> prev) ptr -> prev -> next = sel_domain;
	sel_domain -> next = ptr;
	sel_domain -> prev = ptr -> prev;
	ptr -> prev = sel_domain;
      }
    }
  }

  hw_restore_flags (flags);
}

int xm_raise_event_all_domains (int event, int flags) {
  unsigned int hw_flags;
  struct xm_domain_struct *sel_domain;

  if (event < 0 || event >= MAX_EVENTS) return -1;

  hw_save_flags_and_cli(hw_flags);
  
  for (sel_domain = xm_domain_list; sel_domain; 
       sel_domain = sel_domain -> next) {
    if (is_bit_set (sel_domain -> intercepted_events, event)) {
      set_bit (sel_domain -> pending_events, event);
    }

    if (is_bit_set (sel_domain -> blocked_events, event)) break;
  }
					  
  hw_restore_flags (hw_flags);
  
  if ((flags & xm_RAISE_EVENT_NOW) == xm_RAISE_EVENT_NOW)
    xm_sched();
  
  return 0;
}

int xm_raise_event (xm_domain_t domain, int event, int flags) {
  unsigned int hw_flags;

  if (event < 0 || event >= MAX_EVENTS) return -1;

  hw_save_flags_and_cli(hw_flags);
  
  if (is_bit_set (domain -> intercepted_events, event))
    set_bit (domain -> pending_events, event);
  
  hw_restore_flags (hw_flags);
  
  if ((flags & xm_RAISE_EVENT_NOW) == xm_RAISE_EVENT_NOW)
    xm_sched();
  
  return 0;
}

int __xm_install_event_handler (xm_domain_t domain,
				int event, handler_t handler, int flags) {
  unsigned int hw_flags;
  xm_domain_t sel_domain;

  if (event < 0 || event >= MAX_EVENTS) return -1;
  if (!domain) sel_domain = current_domain;
  else sel_domain = domain;

  hw_save_flags_and_cli (hw_flags);
  sel_domain -> event_handler [event] = handler;
  if (handler) {
    set_bit (sel_domain -> intercepted_events, event);
    set_bit (sel_domain -> masked_events, event);
    // The event can not received until the domain unmasks it
    if ((flags & xm_BLOCK_EVENT) == xm_BLOCK_EVENT)
      set_bit (sel_domain -> blocked_events, event);
  } else {
    clear_bit (sel_domain -> intercepted_events, event);
    set_bit (sel_domain -> masked_events, event);
    clear_bit (sel_domain -> blocked_events, event);
  }
  hw_restore_flags (hw_flags);

  return 0;
}

int __xm_install_trap_handler (xm_domain_t domain, 
			       int trap, handler_t handler) {
  unsigned int hw_flags;
  xm_domain_t sel_domain;

  if (trap < 0 || trap >= MAX_TRAPS ) return -1;
  if (!domain) sel_domain = current_domain;
  else sel_domain = domain;
  
  hw_save_flags_and_cli (hw_flags);
  if (handler)
    sel_domain -> trap_handler [trap] = handler;
  else 
    sel_domain -> trap_handler [trap] = generic_trap_handler;
  hw_restore_flags (hw_flags);

  return 0;
}

void xm_disable_bus_event (int event) {
  unsigned int hw_flags;
  hw_save_flags_and_cli (hw_flags);
  hw_irq_disable (event);
  hw_restore_flags (hw_flags);
}

void xm_enable_bus_event (int event) {
  unsigned int hw_flags;
  hw_save_flags_and_cli (hw_flags);
  if (is_bit_set (global_pending_events, event))
    clear_bit (global_pending_events, event);
  hw_irq_enable (event);
  hw_restore_flags (hw_flags);
}

void __xm_enable_events (xm_domain_t domain) {
  unsigned int hw_flags;
  hw_save_flags_and_cli (hw_flags);
  if (!domain)
    xm_enable_events_flag (current_domain);
  else
    xm_enable_events_flag (domain);
  hw_restore_flags (hw_flags);
}

void __xm_disable_events (xm_domain_t domain) {
  unsigned int hw_flags;
  hw_save_flags_and_cli (hw_flags);
  if (!domain)
    xm_disable_events_flag (current_domain);
  else
    xm_disable_events_flag (domain);
  hw_restore_flags (hw_flags);
}

// Following two functions only mask the event for the current domain
void __xm_mask_event (xm_domain_t domain, int event) {
  unsigned int hw_flags;
  hw_save_flags_and_cli (hw_flags);
  if (!domain)
    set_bit (current_domain -> masked_events, event);
  else
    set_bit (domain -> masked_events, event);
  hw_restore_flags (hw_flags);
}

void __xm_unmask_event (xm_domain_t domain, int event) {
  unsigned int hw_flags;
  hw_save_flags_and_cli (hw_flags);
  if (!domain)
    clear_bit (current_domain -> masked_events, event);
  else
    clear_bit (domain -> masked_events, event);
  hw_restore_flags (hw_flags);
}

void __xm_save_mask_events (xm_domain_t domain, bitmap_t *old_mask) {
  unsigned int hw_flags;
  hw_save_flags_and_cli (hw_flags);
  if (!domain)
    *old_mask = current_domain -> masked_events;
  else
    *old_mask = domain -> masked_events;

  hw_restore_flags (hw_flags);
}

void __xm_mask_all_events (xm_domain_t domain) {
  unsigned int hw_flags;
  hw_save_flags_and_cli (hw_flags);
  if (!domain)
    set_bitmap (current_domain -> masked_events);
  else
    set_bitmap (domain -> masked_events);

  hw_restore_flags (hw_flags);
}

void __xm_unmask_all_events (xm_domain_t domain) {
  unsigned int hw_flags;
  hw_save_flags_and_cli (hw_flags);
  if (!domain)
    clear_bitmap (current_domain -> masked_events);
  else
    clear_bitmap (domain -> masked_events);
  hw_restore_flags (hw_flags);
}


void __xm_restore_mask_events (xm_domain_t domain, bitmap_t mask) {
  unsigned int hw_flags;
  hw_save_flags_and_cli (hw_flags);
  if (!domain)
    current_domain -> masked_events = mask;
  else
    domain -> masked_events = mask;
  hw_restore_flags (hw_flags);
}

// dummy_f is an useless function,
// but we need a valid address to create
// a fake stack
static void dummy_f (void) {
  xm_sync_events ();
  xm_enable_events_flag (root_domain);
}

void xm_sync_events (void) {
  unsigned int hw_flags;
  struct pt_regs dummy_regs = FAKE_REGS;
  
  hw_save_flags_and_cli(hw_flags);

  if (is_some_bit_set (current_domain -> pending_events)) {
      unsigned long event;
      
      while ((event = get_next_set_bit_and_clear 
	      (current_domain -> pending_events,
	       current_domain -> masked_events)) != -1) {
	
	// An event  pending when there  is not any  handler installed
	// catching it??? Must be an error
	assert (current_domain -> event_handler [event]);
	if (!current_domain -> event_handler [event]) continue;
	// Before executing an event handler, events' flag is disabled
	// and the event is masked
	set_bit (current_domain -> masked_events, event);
	xm_disable_events_flag (current_domain);
	
	dummy_regs.orig_eax = event;
	hw_enable_interrupts();
	// Here the event handler is executed always
	// with the same conditions, events flag disabled
	// and the executed event masked as well
	(*current_domain -> event_handler[event]) 
	  (event, &dummy_regs);
	hw_disable_interrupts();
      }
  }

  // Returning from an interrupt (event in this case) means
  // to enable events flag (like in the actual case)
  if (current_domain != root_domain)
    xm_enable_events_flag (current_domain);
  
  hw_restore_flags (hw_flags);
}

// This a  quite simple scheduler  which only select and  executes the
// most and not inactive domain
int xm_sched (void) {
  struct xm_domain_struct *sel_domain;
  unsigned int hw_flags;

  //assert (!hw_are_interrupts_enabled ());
  hw_save_flags_and_cli (hw_flags);

  for (sel_domain = xm_domain_list; sel_domain; 
       sel_domain = sel_domain -> next) {
    if (xm_get_domain_state(sel_domain) == xm_DOMAIN_ACTIVE || 
	(xm_get_domain_state(sel_domain) == xm_DOMAIN_SUSPENDED && 
	 xm_is_events_flag_enabled (sel_domain) &&
	 get_domain_pending_events(sel_domain -> pending_events,
				   sel_domain -> masked_events)))
      break;
  }

  // no selected  domain?, well it doesn't the  matter, XtratuM can
  // always try to execute the root domain, even if the root domain
  // would be suspended
  if (!sel_domain) sel_domain = root_domain;
  
  if (sel_domain != current_domain) {
    // Caution,  recall that  after the  domain_context_switch, the
    // selected variable will be incorrect since the stack will
    // change
    domain_context_switch (sel_domain, &current_domain);
  }

  xm_set_domain_state(current_domain, xm_DOMAIN_ACTIVE);

  if (!xm_is_events_flag_enabled (current_domain)) {
    hw_restore_flags (hw_flags);
    return 0;
  }

  xm_sync_events();

  hw_restore_flags (hw_flags);
  return 1;
}

int init_module (void) {
  return xm_init ();
}

void cleanup_module (void) {
  xm_finish ();
}

MODULE_AUTHOR("Miguel Masmano <mimastel@doctor.upv.es>");
MODULE_DESCRIPTION("XtratuM");
MODULE_LICENSE("GPL");
