/* This file contains essentially all of the process and message handling.
 * It has two main entry points from the outside:
 *
 *   sys_call:   called when a process or task does SEND, RECEIVE or SENDREC
 *   interrupt:	called by interrupt routines to send a message to task
 *
 * It also has five minor entry points:
 *
 *   ready:	put a process on one of the ready queues so it can be run
 *   unready:	remove a process from the ready queues
 *   sched:	a process has run too long; schedule another one
 *   mini_send:	send a message (used by interrupt signals, etc.)
 *   pick_proc:	pick a process to run (used by system initialization)
 */

#include "../h/const.h"
#include "../h/type.h"
#include "../h/callnr.h"
#include "../h/com.h"
#include "../h/error.h"
#include "const.h"
#include "type.h"
#include "glo.h"
#include "proc.h"

/*===========================================================================*
 *				interrupt				     * 
 *===========================================================================*/
PUBLIC interrupt(task, m_ptr)
int task;			/* number of task to be started */
message *m_ptr;			/* interrupt message to send to the task */
{
/* An interrupt has occurred.  Schedule the task that handles it. */

  int i, n, old_map, this_bit;

#ifdef i8088
  /* Re-enable the 8259A interrupt controller. */
  port_out(INT_CTL, ENABLE);	/* this re-enables the 8259A controller chip */
  if (pc_at && task == WINCHESTER)
	  /* this re-enables the second controller chip */
	  port_out(INT2_CTL, ENABLE);
#endif

  /* Try to send the interrupt message to the indicated task. */
  this_bit = 1 << (-task);
  if (mini_send(HARDWARE, task, m_ptr) != OK) {
	/* The message could not be sent to the task; it was not waiting. */
	old_map = busy_map;	/* save original map of busy tasks */
	if (task == CLOCK) {
		lost_ticks++;
	} else {
		busy_map |= this_bit;		/* mark task as busy */
		task_mess[-task] = m_ptr;	/* record message pointer */
	}
  } else {
	/* Hardware interrupt was successfully sent as a message. */
	busy_map &= ~this_bit;	/* turn off the bit in case it was on */
	old_map = busy_map;
  }

  /* See if any tasks that were previously busy are now listening for msgs. */
  if (old_map != 0) {
	for (i = 2; i <= NR_TASKS; i++) {
		/* Check each task looking for one with a pending interrupt. */
		if ( (old_map>>i) & 1) {
			/* Task 'i' has a pending interrupt. */
			n = mini_send(HARDWARE, -i, task_mess[i]);
			if (n == OK) busy_map &= ~(1 << i);
		}
	}
  }

  /* If a task has just been readied and a user is running, run the task. */
  if (rdy_head[TASK_Q] != NIL_PROC && (cur_proc >= 0 || cur_proc == IDLE))
	pick_proc();
}


/*===========================================================================*
 *				sys_call				     * 
 *===========================================================================*/
PUBLIC sys_call(function, caller, src_dest, m_ptr)
int function;			/* SEND, RECEIVE, or BOTH */
int caller;			/* who is making this call */
int src_dest;			/* source to receive from or dest to send to */
message *m_ptr;			/* pointer to message */
{
/* The only system calls that exist in MINIX are sending and receiving
 * messages.  These 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).
 */

  register struct proc *rp;
  int n;

  /* Check for bad system call parameters. */
  rp = proc_addr(caller);
  if (src_dest < -NR_TASKS || (src_dest >= NR_PROCS && src_dest != ANY) ) {
	rp->p_reg[RET_REG] = E_BAD_SRC;
	return;
  }
  if (function != BOTH && caller >= LOW_USER) {
	rp->p_reg[RET_REG] = E_NO_PERM;	/* users only do BOTH */
	return;
  }

  /* The parameters are ok. Do the call. */
  if (function & SEND) {
	n = mini_send(caller, src_dest, m_ptr);	/* func = SEND or BOTH */
	if (function == SEND || n != OK) rp->p_reg[RET_REG] = n;
	if (n != OK) return;	/* SEND failed */
  }

  if (function & RECEIVE) {
	n = mini_rec(caller, src_dest, m_ptr);      /* func = RECEIVE or BOTH */
	rp->p_reg[RET_REG] = n;
  }
}

/*===========================================================================*
 *				mini_send				     * 
 *===========================================================================*/
PUBLIC int mini_send(caller, dest, m_ptr)
int caller;			/* who is trying to send a message? */
int dest;			/* to whom is message being sent? */
message *m_ptr;			/* pointer to message buffer */
{
/* Send a message from 'caller' 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'.
 */

  register struct proc *caller_ptr, *dest_ptr, *next_ptr;
  vir_bytes vb;			/* message buffer pointer as vir_bytes */
  vir_clicks vlo, vhi;		/* virtual clicks containing message to send */
  vir_clicks len;		/* length of data segment in clicks */

  /* User processes are only allowed to send to FS and MM.  Check for this. */
  if (caller >= LOW_USER && (dest != FS_PROC_NR && dest != MM_PROC_NR))
	return(E_BAD_DEST);
  caller_ptr = proc_addr(caller);	/* pointer to source's proc entry */
  dest_ptr = proc_addr(dest);	/* pointer to destination's proc entry */
  if (dest_ptr->p_flags & P_SLOT_FREE) return(E_BAD_DEST);	/* dead dest */

  /* Check for messages wrapping around top of memory or outside data seg. */
  len = caller_ptr->p_map[D].mem_len;
  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 message */
  if (vhi < vlo || vhi - caller_ptr->p_map[D].mem_vir >= len)return(E_BAD_ADDR);

  /* Check to see if 'dest' is blocked waiting for this message. */
  if ( (dest_ptr->p_flags & RECEIVING) &&
		(dest_ptr->p_getfrom == ANY || dest_ptr->p_getfrom == caller) ) {
	/* Destination is indeed waiting for this message. */
	cp_mess(caller, caller_ptr->p_map[D].mem_phys, m_ptr, 
				dest_ptr->p_map[D].mem_phys, dest_ptr->p_messbuf);
	dest_ptr->p_flags &= ~RECEIVING;	/* deblock destination */
	if (dest_ptr->p_flags == 0) ready(dest_ptr);
  } else {
	/* Destination is not waiting.  Block and queue caller. */
	if (caller == HARDWARE) return(E_OVERRUN);
	caller_ptr->p_messbuf = m_ptr;
	caller_ptr->p_flags |= SENDING;
	unready(caller_ptr);

	/* 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;
  }
  return(OK);
}


/*===========================================================================*
 *				mini_rec				     * 
 *===========================================================================*/
PRIVATE int mini_rec(caller, src, m_ptr)
int caller;			/* process trying to get message */
int src;			/* which message source is wanted (or ANY) */
message *m_ptr;			/* pointer to message buffer */
{
/* 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.  No need to check parameters for validity.
 * Users calls are always sendrec(), and mini_send() has checked already.  
 * Calls from the tasks, MM, and FS are trusted.
 */

  register struct proc *caller_ptr, *sender_ptr, *prev_ptr;
  int sender;

  caller_ptr = proc_addr(caller);	/* pointer to caller's proc structure */

  /* Check to see if a message from desired source is already available. */
  sender_ptr = caller_ptr->p_callerq;
  while (sender_ptr != NIL_PROC) {
	sender = sender_ptr - proc - NR_TASKS;
	if (src == ANY || src == sender) {
		/* An acceptable message has been found. */
		cp_mess(sender, sender_ptr->p_map[D].mem_phys, sender_ptr->p_messbuf,
					caller_ptr->p_map[D].mem_phys, m_ptr);
		sender_ptr->p_flags &= ~SENDING;	/* deblock sender */
		if (sender_ptr->p_flags == 0) ready(sender_ptr);
		if (sender_ptr == caller_ptr->p_callerq)
			caller_ptr->p_callerq = sender_ptr->p_sendlink;
		else
			prev_ptr->p_sendlink = sender_ptr->p_sendlink;
		return(OK);
	}
	prev_ptr = sender_ptr;
	sender_ptr = sender_ptr->p_sendlink;
  }

  /* No suitable message is available.  Block the process trying to receive. */
  caller_ptr->p_getfrom = src;
  caller_ptr->p_messbuf = m_ptr;
  caller_ptr->p_flags |= RECEIVING;
  unready(caller_ptr);

  /* If MM has just blocked and there are kernel signals pending, now is the
   * time to tell MM about them, since it will be able to accept the message.
   */
  if (sig_procs > 0 && caller == MM_PROC_NR && src == ANY) inform(MM_PROC_NR);
  return(OK);
}


/*===========================================================================*
 *				pick_proc				     * 
 *===========================================================================*/
PUBLIC pick_proc()
{
/* Decide who to run now. */

  register int q;		/* which queue to use */

  if (rdy_head[TASK_Q] != NIL_PROC) q = TASK_Q;
  else if (rdy_head[SERVER_Q] != NIL_PROC) q = SERVER_Q;
  else q = USER_Q;

  /* Set 'cur_proc' and 'proc_ptr'. If system is idle, set 'cur_proc' to a
   * special value (IDLE), and set 'proc_ptr' to point to an unused proc table
   * slot, namely, that of task -1 (HARDWARE), so save() will have somewhere to
   * deposit the registers when a interrupt occurs on an idle machine.
   * Record previous process so that when clock tick happens, the clock task
   * can find out who was running just before it began to run.  (While the
   * clock task is running, 'cur_proc' = CLOCKTASK. In addition, set 'bill_ptr'
   * to always point to the process to be billed for CPU time.
   */
  prev_proc = cur_proc;
  if (rdy_head[q] != NIL_PROC) {
	/* Someone is runnable. */
	cur_proc = rdy_head[q] - proc - NR_TASKS;
	proc_ptr = rdy_head[q];
	if (cur_proc >= LOW_USER) bill_ptr = proc_ptr;
  } else {
	/* No one is runnable. */
	cur_proc = IDLE;
	proc_ptr = proc_addr(HARDWARE);
	bill_ptr = proc_ptr;
  }
}

/*===========================================================================*
 *				ready					     * 
 *===========================================================================*/
PUBLIC ready(rp)
register struct proc *rp;	/* this process is now runnable */
{
/* Add 'rp' to the end of one of the queues of runnable processes. Three
 * queues are maintained:
 *   TASK_Q   - (highest priority) for runnable tasks
 *   SERVER_Q - (middle priority) for MM and FS only
 *   USER_Q   - (lowest priority) for user processes
 */

  register int q;		/* TASK_Q, SERVER_Q, or USER_Q */
  int r;

  lock();			/* disable interrupts */
  r = (rp - proc) - NR_TASKS;	/* task or proc number */
  q = (r < 0 ? TASK_Q : r < LOW_USER ? SERVER_Q : USER_Q);

  /* See if the relevant queue is empty. */
  if (rdy_head[q] == NIL_PROC)
	rdy_head[q] = rp;	/* add to empty queue */
  else
	rdy_tail[q]->p_nextready = rp;	/* add to tail of nonempty queue */
  rdy_tail[q] = rp;		/* new entry has no successor */
  rp->p_nextready = NIL_PROC;
  restore();			/* restore interrupts to previous state */
}


/*===========================================================================*
 *				unready					     * 
 *===========================================================================*/
PUBLIC unready(rp)
register struct proc *rp;	/* this process is no longer runnable */
{
/* A process has blocked. */

  register struct proc *xp;
  int r, q;

  lock();			/* disable interrupts */
  r = rp - proc - NR_TASKS;
  q = (r < 0 ? TASK_Q : r < LOW_USER ? SERVER_Q : USER_Q);
  if ( (xp = rdy_head[q]) == NIL_PROC) return;
  if (xp == rp) {
	/* Remove head of queue */
	rdy_head[q] = xp->p_nextready;
	pick_proc();
  } else {
	/* 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;
	while (xp->p_nextready != NIL_PROC) xp = xp->p_nextready;
	rdy_tail[q] = xp;
  }
  restore();			/* restore interrupts to previous state */
}


/*===========================================================================*
 *				sched					     * 
 *===========================================================================*/
PUBLIC 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.
 */

  lock();			/* disable interrupts */
  if (rdy_head[USER_Q] == NIL_PROC) {
	restore();		/* restore interrupts to previous state */
	return;
  }

  /* One or more user processes queued. */
  rdy_tail[USER_Q]->p_nextready = rdy_head[USER_Q];
  rdy_tail[USER_Q] = rdy_head[USER_Q];
  rdy_head[USER_Q] = rdy_head[USER_Q]->p_nextready;
  rdy_tail[USER_Q]->p_nextready = NIL_PROC;
  pick_proc();
  restore();			/* restore interrupts to previous state */
}
