/* This file contains essentially all of the process and message handling.
 * It has two main entry points from the outside:
 *
 *   sys_call:  a system call, that is, the kernel is trapped with an INT 
 *   notify:    notify process of a system event (notifications aren't queued)
 *
 * It also has several minor entry points:
 *
 *   lock_ready:      put a process on one of the ready queues so it can be run
 *   lock_unready:    remove a process from the ready queues
 *   lock_sched:      a process has run too long; schedule another one
 *   lock_pick_proc:  pick a process to run (used by system initialization)
 *   unhold:          repeat all held-up notifications
 *
 * Changes:
 *   Nov 05, 2004     removed lock_mini_send()  (Jorrit N. Herder)
 *   Oct 28, 2004     non-blocking SEND and RECEIVE  (Jorrit N. Herder)
 *   Oct 28, 2004     rewrite of sys_call()  (Jorrit N. Herder)
 *   Oct 10, 2004     require BOTH for kernel sys_call()  (Jorrit N. Herder)
 *		      (to protect kernel tasks from being blocked)
 *   Sep 25, 2004     generalized notify() function  (Jorrit N. Herder)
 *   Sep 23, 2004     removed PM sig check in mini_rec()  (Jorrit N. Herder)
 *   Aug 19, 2004     generalized ready()/unready()  (Jorrit N. Herder)
 *   Aug 18, 2004     added notify() function  (Jorrit N. Herder) 
 *   May 01, 2004     check p_sendmask in mini_send()  (Jorrit N. Herder) 
 */

#include "kernel.h"
#include <minix/callnr.h>
#include <minix/com.h>
#include "proc.h"
#include "sendmask.h"

PRIVATE unsigned char switching;	/* nonzero to inhibit notify() */

FORWARD _PROTOTYPE( int mini_send, (struct proc *caller_ptr, int dest,
		message *m_ptr, int may_block) );
FORWARD _PROTOTYPE( int mini_rec, (struct proc *caller_ptr, int src,
		message *m_ptr, int may_block) );
FORWARD _PROTOTYPE( void ready, (struct proc *rp) );
FORWARD _PROTOTYPE( void sched, (void) );
FORWARD _PROTOTYPE( void unready, (struct proc *rp) );
FORWARD _PROTOTYPE( void pick_proc, (void) );

#if (CHIP == M68000)
FORWARD _PROTOTYPE( void cp_mess, (int src, struct proc *src_p, message *src_m,
		struct proc *dst_p, message *dst_m) );
#endif

#if (CHIP == INTEL)
#define CopyMess(s,sp,sm,dp,dm) \
	cp_mess(s, (sp)->p_memmap[D].mem_phys, (vir_bytes)sm, (dp)->p_memmap[D].mem_phys, (vir_bytes)dm)
#endif /* (CHIP == INTEL) */

#if (CHIP == M68000)
/* M68000 does not have cp_mess() in assembly like INTEL. Declare prototype
 * for cp_mess() here and define the function below. Also define CopyMess. 
 */
#endif /* (CHIP == M68000) */


/* Bit mask operations used to bits of the notification mask. */
#define set_bit(mask, n)	((mask) |= (1 << (n)))
#define clear_bit(mask, n)	((mask) &= ~(1 << (n)))
#define isset_bit(mask, n)	((mask) & (1 << (n)))


/*===========================================================================*
 *				    notify				     * 
 *===========================================================================*/
PUBLIC void notify(proc_nr, notify_type)
int proc_nr;			/* number of process to be started */
int notify_type;		/* notification to be sent */
{
/* A system event has occurred. Send a notification with source HARDWARE to
 * the given process. The notify() function was carefully designed so that it
 * (1) can be used safely from both interrupt handlers and the task level, and
 * (2) realizes asynchronous message passing with at least once semantics, 
 * that is, the notifications are not queued. If a race condition occurs, the
 * notification is queued and repeated later by unhold(). If the receiver is
 * not ready, the notification is blocked and checked later in receive().   
 */
  register struct proc *rp;	/* pointer to task's proc entry */
  message m;			/* message to send the notification */
  unsigned int notify_bit;	/* bit for this notification */

  /* Get notify bit and process pointer. */
  notify_bit = (unsigned int) (notify_type - NOTIFICATION);
  rp = proc_addr(proc_nr);

  /* If this call would compete with other process-switching functions, put
   * it on the 'held' queue to be flushed at the next non-competing restart().
   * The competing conditions are:
   * (1) k_reenter == (typeof k_reenter) -1:
   *     Call from the task level, typically from an output interrupt 
   *     routine. An interrupt handler might reenter notify(). Rare,
   *     so not worth special treatment.
   * (2) k_reenter > 0:
   *     Call from a nested interrupt handler. A previous interrupt 
   *     handler might be inside notify() or sys_call().
   * (3) switching != 0:
   *     A process-switching function other than notify() is being called 
   *     from the task level, typically sched() from CLOCK. An interrupt
   *	 handler might call notify() and pass the 'k_reenter' test.
   */
  if (k_reenter != 0 || switching) {
	lock();
	if (! rp->p_ntf_held) {			/* already on held queue? */
		if (held_head != NIL_PROC)
			held_tail->p_ntf_nextheld = rp;
		else
			held_head = rp;
		held_tail = rp;
		rp->p_ntf_nextheld = NIL_PROC;
	}
	set_bit(rp->p_ntf_held, notify_bit);	/* add bit to held mask */
 	unlock();
	return;
  }
  switching = TRUE;

  /* If process is not waiting for a notification, record the blockage. */
  if ( (rp->p_flags & (RECEIVING | SENDING)) != RECEIVING ||
      !isrxhardware(rp->p_getfrom)) {
	set_bit(rp->p_ntf_blocked, notify_bit);	/* add bit to blocked mask */
	switching = FALSE;
	return;
  }

  /* Destination is waiting for a notification. Send it a message with source
   * HARDWARE and type 'notify_type'. No more information can be reliably 
   * provided since notifications are not queued.
   */
  m.m_source = HARDWARE;     	/* direct copy does not work for servers */
  m.m_type = notify_type;
  CopyMess(HARDWARE, proc_addr(HARDWARE), &m, rp, rp->p_messbuf);
  rp->p_flags &= ~RECEIVING;
  clear_bit(rp->p_ntf_blocked, notify_bit);

  /* Announce the process ready and select a fresh process to run. */
  ready(rp);			
  pick_proc();
  switching = FALSE;
}

/*===========================================================================*
 *				sys_call				     * 
 *===========================================================================*/
PUBLIC int sys_call(call_nr, src_dst, m_ptr)
int call_nr;			/* (NB_)SEND, (NB_)RECEIVE, BOTH */
int src_dst;			/* source to receive from or dest to send to */
message *m_ptr;			/* pointer to message in the caller's space */
{
/* System calls are done by trapping to the kernel with an INT instruction.
 * The trap is caught and sys_call() is called to send or receive a message
 * (or both). The caller is always given by 'proc_ptr'.
 */
  register struct proc *caller_ptr = proc_ptr;	/* get pointer to caller */
  int function = call_nr & SYSCALL_FUNC;	/* get system call function */
  int may_block = ! (call_nr & NON_BLOCKING);	/* (dis)allow blocking? */
  int mask_entry;				/* bit to check in send mask */
  int result;					/* the system call's result */

  /* Calls directed to the kernel may only be sendrec(), because tasks always
   * reply and may not block if the caller doesn't do receive(). Users also
   * may only use sendrec() to protect the process manager and file system.  
   */
  if ((iskernel(src_dst) || isuserp(caller_ptr)) && function != BOTH) {
      result = ECALLDENIED;			/* BOTH was required */
  }
  
  /* Verify that requested source and/ or destination is a valid process. */
  else if (! isoksrc_dst(src_dst)) {
      result = EBADSRCDST;			/* invalid process number */
  }

  /* Now check if the call is known and try to perform the request. The only
   * system calls that exist in MINIX are sending and receiving messages.
   * Receiving is straightforward. Sending requires checks to see if sending
   * is allowed by the caller's send mask and to see if the destination is
   * alive.  
   */
  else {
      switch(function) {
      case SEND:		
          /* fall through, SEND is done in BOTH */
      case BOTH:			
          if (! isalive(src_dst)) { 			
              result = EDEADDST;		/* cannot send to the dead */
              break;
          }
          mask_entry = isuser(src_dst) ? USER_PROC_NR : src_dst;
          if (! isallowed(caller_ptr->p_sendmask, mask_entry)) {
              kprintf("WARNING: sys_call denied %d ", caller_ptr->p_nr);
              kprintf("sending to %d\n", proc_addr(src_dst)->p_nr);
              result = ECALLDENIED;		/* call denied by send mask */
              break;
          } 
          result = mini_send(caller_ptr, src_dst, m_ptr, may_block);
          if (function == SEND || result != OK) {	
              break;				/* done, or SEND failed */
          }					/* fall through for BOTH */
      case RECEIVE:			
          result = mini_rec(caller_ptr, src_dst, m_ptr, may_block);
          break;
      default:
          result = EBADCALL;			/* illegal system call */
      }
  }

  /* Now, return the result of the system call to the caller. */
  return(result);
}


/*===========================================================================*
 *				mini_send				     * 
 *===========================================================================*/
PRIVATE int mini_send(caller_ptr, dest, m_ptr, may_block)
register struct proc *caller_ptr;	/* who is trying to send a message? */
int dest;				/* to whom is message being sent? */
message *m_ptr;				/* pointer to message buffer */
int may_block;				/* (dis)allow blocking */
{
/* Send a message from 'caller_ptr' to 'dest'. If 'dest' is blocked waiting
 * for this message, copy the message to it and unblock 'dest'. If 'dest' is
 * not waiting at all, or is waiting for another source, queue 'caller_ptr'.
 */
  register struct proc *dest_ptr, *next_ptr;
  vir_bytes vb;			/* message buffer pointer as vir_bytes */
  vir_clicks vlo, vhi;		/* virtual clicks containing message to send */

  dest_ptr = proc_addr(dest);	/* pointer to destination's proc entry */

#if ALLOW_GAP_MESSAGES
  /* This check allows a message to be anywhere in data or stack or gap. 
   * It will have to be made more elaborate later for machines which
   * don't have the gap mapped.
   */
  vb = (vir_bytes) m_ptr;
  vlo = vb >> CLICK_SHIFT;	/* vir click for bottom of message */
  vhi = (vb + MESS_SIZE - 1) >> CLICK_SHIFT;	/* vir click for top of msg */
  if (vlo < caller_ptr->p_memmap[D].mem_vir || vlo > vhi ||
      vhi >= caller_ptr->p_memmap[S].mem_vir + caller_ptr->p_memmap[S].mem_len)
        return(EFAULT); 
#else
  /* Check for messages wrapping around top of memory or outside data seg. */
  vb = (vir_bytes) m_ptr;
  vlo = vb >> CLICK_SHIFT;	/* vir click for bottom of message */
  vhi = (vb + MESS_SIZE - 1) >> CLICK_SHIFT;	/* vir click for top of msg */
  if (vhi < vlo ||
      vhi - caller_ptr->p_memmap[D].mem_vir >= caller_ptr->p_memmap[D].mem_len)
	return(EFAULT);
#endif

  /* Check for deadlock by 'caller_ptr' and 'dest' sending to each other. */
  if (dest_ptr->p_flags & SENDING) {
	next_ptr = proc_addr(dest_ptr->p_sendto);
	while (TRUE) {
		if (next_ptr == caller_ptr) return(ELOCKED);
		if (next_ptr->p_flags & SENDING)
			next_ptr = proc_addr(next_ptr->p_sendto);
		else
			break;
	}
  }

  /* Check to see if 'dest' is blocked waiting for this message. */
  if ( (dest_ptr->p_flags & (RECEIVING | SENDING)) == RECEIVING &&
       (dest_ptr->p_getfrom == ANY ||
        dest_ptr->p_getfrom == proc_number(caller_ptr))) {
	/* Destination is indeed waiting for this message. */
	CopyMess(proc_number(caller_ptr), caller_ptr, m_ptr, dest_ptr,
		 dest_ptr->p_messbuf);
	dest_ptr->p_flags &= ~RECEIVING;	/* deblock destination */
	if (dest_ptr->p_flags == 0) ready(dest_ptr);
  } else if (may_block) {
	/* Destination is not waiting.  Block and queue caller. */
	caller_ptr->p_messbuf = m_ptr;
	if (caller_ptr->p_flags == 0) unready(caller_ptr);
	caller_ptr->p_flags |= SENDING;
	caller_ptr->p_sendto= dest;

	/* Process is now blocked.  Put in on the destination's queue. */
	if ( (next_ptr = dest_ptr->p_callerq) == NIL_PROC)
		dest_ptr->p_callerq = caller_ptr;
	else {
		while (next_ptr->p_sendlink != NIL_PROC)
			next_ptr = next_ptr->p_sendlink;
		next_ptr->p_sendlink = caller_ptr;
	}
	caller_ptr->p_sendlink = NIL_PROC;
  } else {
	return(ENOTREADY);
  }
  return(OK);
}

/*===========================================================================*
 *				mini_rec				     * 
 *===========================================================================*/
PRIVATE int mini_rec(caller_ptr, src, m_ptr, may_block)
register struct proc *caller_ptr;	/* process trying to get message */
int src;				/* which message source is wanted */
message *m_ptr;				/* pointer to message buffer */
int may_block;				/* (dis)allow blocking */
{
/* A process or task wants to get a message.  If one is already queued,
 * acquire it and deblock the sender.  If no message from the desired source
 * is available, block the caller.  
 */
  register struct proc *sender_ptr;
  register struct proc *previous_ptr;
  message m;
  int i;

  /* Check to see if a message from desired source is already available. */
  if (!(caller_ptr->p_flags & SENDING)) {

    /* Check caller queue. */
    for (sender_ptr = caller_ptr->p_callerq; sender_ptr != NIL_PROC;
	 previous_ptr = sender_ptr, sender_ptr = sender_ptr->p_sendlink) {
	if (src == ANY || src == proc_number(sender_ptr)) {
		/* An acceptable message has been found. */
		CopyMess(proc_number(sender_ptr), sender_ptr,
			 sender_ptr->p_messbuf, caller_ptr, m_ptr);
		if (sender_ptr == caller_ptr->p_callerq)
			caller_ptr->p_callerq = sender_ptr->p_sendlink;
		else
			previous_ptr->p_sendlink = sender_ptr->p_sendlink;
		if ((sender_ptr->p_flags &= ~SENDING) == 0)
			ready(sender_ptr);	/* deblock sender */
		return(OK);
	}
    }

    /* Check bit mask for blocked notifications. If multiple bits are set, 
     * send the first notification encountered; the rest is handled later.
     * This effectively prioritizes notifications. Notification also have
     * priority of other messages. 
     */
    if (caller_ptr->p_ntf_blocked && isrxhardware(src)) {
        for (i=0; i<NR_NOTIFICATIONS; i++) {
            if (isset_bit(caller_ptr->p_ntf_blocked, i)) {
                m.m_source = HARDWARE;  
                m.m_type = NOTIFICATION + i;
                CopyMess(HARDWARE, proc_addr(HARDWARE), &m, caller_ptr, m_ptr);
	        clear_bit(caller_ptr->p_ntf_blocked, i);
	        return(OK);
	    }
	}
    }
  }

  /* No suitable message is available.  Block the process trying to receive,
   * unless this is not allowed by the system call.
   */
  if (may_block) {
      caller_ptr->p_getfrom = src;
      caller_ptr->p_messbuf = m_ptr;
      if (caller_ptr->p_flags == 0) unready(caller_ptr);
      caller_ptr->p_flags |= RECEIVING;
      return(OK);
  } else {
      return(ENOTREADY);
  }
}

/*===========================================================================*
 *				pick_proc				     * 
 *===========================================================================*/
PRIVATE void pick_proc()
{
/* Decide who to run now.  A new process is selected by setting 'proc_ptr'.
 * When a fresh user (or idle) process is selected, record it in 'bill_ptr',
 * so the clock task can tell who to bill for system time.
 */
  register struct proc *rp;			/* process to run */
  int q;					/* iterate over queues */

  /* Check each of the scheduling queues for ready processes. The number of
   * queues is defined in proc.h, and priorities are set in the task table.
   * The lowest queue contains IDLE, which is always ready.
   */
  for (q=0; q < NR_SCHED_QUEUES; q++) {	
    if ( (rp = rdy_head[q]) != NIL_PROC) {
	proc_ptr = rp;				/* run process 'rp' next */
	if (isuserp(rp) || isidlep(rp)) 	/* possible bill 'rp' */
		bill_ptr = rp;
	return;
    }
  }
}

/*===========================================================================*
 *				ready					     * 
 *===========================================================================*/
PRIVATE void ready(rp)
register struct proc *rp;	/* this process is now runnable */
{
/* Add 'rp' to one of the queues of runnable processes.  */
  int q = rp->p_priority;	/* scheduling queue to use */

  /* Processes, in principle, are added to the end of the queue. However, 
   * user processes are added in front of the queue, because this is a bit 
   * fairer to I/O bound processes. 
   */
  if (isuserp(rp)) {	/* add to front of queue */
  	if (rdy_head[q] == NIL_PROC)
		rdy_tail[q] = rp;
  	rp->p_nextready = rdy_head[q];		/* add to front of queue */
  	rdy_head[q] = rp;
  } 
  else {
      if (rdy_head[q] != NIL_PROC)
		rdy_tail[q]->p_nextready = rp;	/* add to end of queue */
      else 
		rdy_head[q] = rp;		/* add to empty queue */
      rdy_tail[q] = rp;
      rp->p_nextready = NIL_PROC;
  }

  /* Run 'rp' next if it has a higher priority than 'proc_ptr'. This actually
   * should be done via pick_proc(), but mini_send() and mini_rec() rely
   * on this side-effect.
   */
  if (rp->p_priority < proc_ptr->p_priority) proc_ptr = rp;
}

/*===========================================================================*
 *				unready					     * 
 *===========================================================================*/
PRIVATE void unready(rp)
register struct proc *rp;	/* this process is no longer runnable */
{
/* A process has blocked. See ready for a description of the queues. */

  register struct proc *xp;
  register struct proc **qtail; /* queue's rdy_tail */
  int q = rp->p_priority;	/* queue to use */

  /* Side-effect for tasks: check if the task's stack still is ok? */
  if (istaskp(rp)) { 				
	if (*rp->p_stguard != STACK_GUARD)
		panic("stack overrun by task", proc_number(rp));
  }

  /* Now make sure that the process is not in its ready queue. Remove the 
   * process if it is found. The easy part is to check the front of the queue. 
   */
  if ( (xp = rdy_head[q]) == NIL_PROC) return;
  if (xp == rp) {
	rdy_head[q] = xp->p_nextready;		/* remove head of queue */
	if (rp == proc_ptr) 			/* current process removed */
		pick_proc();			/* pick new process to run */
	return;
  }

  /* No match yet. Search body of queue. A process can be made unready even 
   * if it is not running by being sent a signal that kills it.
   */
  while (xp->p_nextready != rp)
	if ( (xp = xp->p_nextready) == NIL_PROC) return;
  xp->p_nextready = xp->p_nextready->p_nextready;
  qtail = &rdy_tail[q];
  if (*qtail == rp) *qtail = xp;
}

/*===========================================================================*
 *				sched					     * 
 *===========================================================================*/
PRIVATE void sched()
{
/* The current process has run too long. If another low priority (user)
 * process is runnable, put the current process on the end of the user queue,
 * possibly promoting another user to head of the queue.
 */
  if (rdy_head[PPRI_USER] == NIL_PROC) return;

  /* One or more user processes queued. */
  rdy_tail[PPRI_USER]->p_nextready = rdy_head[PPRI_USER];
  rdy_tail[PPRI_USER] = rdy_head[PPRI_USER];
  rdy_head[PPRI_USER] = rdy_head[PPRI_USER]->p_nextready;
  rdy_tail[PPRI_USER]->p_nextready = NIL_PROC;
  pick_proc();
}

/*==========================================================================*
 *				lock_pick_proc				    *
 *==========================================================================*/
PUBLIC void lock_pick_proc()
{
/* Safe gateway to pick_proc() for tasks. */
  switching = TRUE;
  pick_proc();
  switching = FALSE;
}

/*==========================================================================*
 *				lock_ready				    *
 *==========================================================================*/
PUBLIC void lock_ready(rp)
struct proc *rp;		/* this process is now runnable */
{
/* Safe gateway to ready() for tasks. */
  switching = TRUE;
  ready(rp);
  switching = FALSE;
}

/*==========================================================================*
 *				lock_unready				    *
 *==========================================================================*/
PUBLIC void lock_unready(rp)
struct proc *rp;		/* this process is no longer runnable */
{
/* Safe gateway to unready() for tasks. */
  switching = TRUE;
  unready(rp);
  switching = FALSE;
}

/*==========================================================================*
 *				lock_sched				    *
 *==========================================================================*/
PUBLIC void lock_sched()
{
/* Safe gateway to sched() for tasks. */
  switching = TRUE;
  sched();
  switching = FALSE;
}

/*==========================================================================*
 *				unhold					    *
 *==========================================================================*/
PUBLIC void unhold()
{
/* Flush any held-up notifications. 'k_reenter' must be 0. 'held_head' must 
 * not be NIL_PROC.  Interrupts must be disabled.  They will be enabled but 
 * will be disabled when this returns.
 */
  register struct proc *rp;	/* current head of held queue */
  int i;

  if (switching) return;
  rp = held_head;
  do {
      for (i=0; i<NR_NOTIFICATIONS; i++) {
          if (isset_bit(rp->p_ntf_held,i)) {
              clear_bit(rp->p_ntf_held,i);
              if (! rp->p_ntf_held)	/* proceed to next in queue? */
                  if ( (held_head = rp->p_ntf_nextheld) == NIL_PROC)
                      held_tail = NIL_PROC;
              unlock();		/* reduce latency; held queue may change! */
              notify(proc_number(rp), NOTIFICATION + i);
              lock();		/* protect the held queue again */
          }
      }
  }
  while ( (rp = held_head) != NIL_PROC);
}


