//+++2004-08-31
//    Copyright (C) 2004  Mike Rieker, Beverly, MA USA
//
//    This program 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; version 2 of the License.
//
//    This program 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 this program; if not, write to the Free Software
//    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//---2004-08-31

/************************************************************************/
/*									*/
/*  This module contains all the routines for manipulating ast blocks	*/
/*									*/
/*  AST's get queued to a thread to cause it to interrupt what it is 	*/
/*  currently doing and call an arbitrary ast routine.  When the ast 	*/
/*  routine returns, the interrupted thread is resumed exactly where 	*/
/*  it left off.							*/
/*									*/
/*  AST's can be queued to execute in whatever processor mode (USER, 	*/
/*  KERNEL) that is desired.						*/
/*									*/
/************************************************************************/

#define _OZ_KNL_AST_C
#include "ozone.h"

#include "oz_knl_ast.h"
#include "oz_knl_kmalloc.h"
#include "oz_knl_objtype.h"
#include "oz_knl_printk.h"
#include "oz_knl_sdata.h"
#include "oz_knl_status.h"
#include "oz_knl_thread.h"

#define MAXRTNADRS 16

/* Ast block */

struct OZ_Ast { OZ_Objtype objtype;					/* OZ_OBJTYPE_AST */
                OZ_Ast *next;						/* next in list */
                OZ_Ast **prev;						/* previous in list */
                OZ_Thread *thread;					/* target thread */
                OZ_Procmode procmode;					/* target processor mode */
                OZ_Astentry astentry;					/* target routine entrypoint */
                void *astparam;						/* target routine parameter */
                int express;						/* express delivery flag */
                uLong aststs;						/* target routine status */

#if 000
						/* Used for debugging */

                OZ_Thread_state thread_state;		/* threads state when ast queued */
                OZ_Astmode astmode;			/* ast mode when ast was queued */
                int q_wasempty;				/* set if the thread's ast queue was empty */
                Long thcpuidx;				/* threads cpu index when ast queued */
                Long mycpuidx;				/* my cpu index when ast queued */
                void *exesp;				/* if thcpuidx < 0, its exec stack pointer */
                char *rtnadrs[MAXRTNADRS];		/* return addresses when ast queued */
#endif
              };

/************************************************************************/
/*									*/
/*  Create an ast block							*/
/*									*/
/*    Input:								*/
/*									*/
/*	thread   = pointer to thread block the ast will be queued to	*/
/*	procmode = processor mode to deliver at				*/
/*	astentry = entrypoint of ast routine				*/
/*	astparam = parameter to pass to ast routine			*/
/*	express  = 0 : deliver only if ast delivery enabled		*/
/*	           1 : deliver even if ast delivery inhibited		*/
/*	               (for knl mode, delivered with softints inhibited)
/*	smplock <= np							*/
/*									*/
/*    Output:								*/
/*									*/
/*	oz_knl_ast_create = OZ_SUCCESS : successful			*/
/*	                 OZ_EXQUOTANPP : caller exceeded npp quota	*/
/*	thread ref count incremented					*/
/*									*/
/*    Note:								*/
/*									*/
/*	Callers should always attempt to deliver the ast to the target 	*/
/*	thread (via oz_knl_thread_queueast), ie, do not delete the ast 	*/
/*	just because the event being ast'd did not happen but was 	*/
/*	aborted instead - deliver the ast then leave it to the caller 	*/
/*	to determine if the event happened or not.			*/
/*									*/
/************************************************************************/

uLong oz_knl_ast_create (OZ_Thread *thread, OZ_Procmode procmode, OZ_Astentry astentry, void *astparam, int express, OZ_Ast **ast_r)

{
  OZ_Ast *ast;

  ast = OZ_KNL_NPPMALLOQ (sizeof *ast);
  if (ast == NULL) return (OZ_EXQUOTANPP);
  ast -> objtype    = OZ_OBJTYPE_AST;
  ast -> next       = NULL;
  ast -> prev       = NULL;
  ast -> thread     = thread;
  ast -> procmode   = procmode;
  ast -> astentry   = astentry;
  ast -> astparam   = astparam;
  ast -> express    = express;

  oz_knl_thread_increfc (thread, 1);

  *ast_r = ast;

  return (OZ_SUCCESS);
}

/************************************************************************/
/*									*/
/*  Insert ast block into list						*/
/*									*/
/*    Input:								*/
/*									*/
/*	ast  = pointer to ast block					*/
/*	list = pointer to listhead or to predecessors next field	*/
/*									*/
/*    Output:								*/
/*									*/
/*	oz_knl_ast_insert = where to link next one to follow this one	*/
/*	*list modified to include ast block				*/
/*									*/
/************************************************************************/

OZ_Ast **oz_knl_ast_insert (OZ_Ast *ast, OZ_Ast **list)

{
  OZ_Ast *next;

  OZ_KNL_CHKOBJTYPE (ast, OZ_OBJTYPE_AST);

  if (ast -> prev != NULL) oz_crash ("oz_knl_ast_insert: ast %p already in a list", ast);
  ast -> prev = list;
  ast -> next = next = *list;
  *list = ast;
  if (next != NULL) next -> prev = &(ast -> next);

  return (&(ast -> next));
}

/************************************************************************/
/*									*/
/*  Remove ast block from list						*/
/*									*/
/*    Input:								*/
/*									*/
/*	ast  = pointer to ast block					*/
/*									*/
/*    Output:								*/
/*									*/
/*	ast block removed from list					*/
/*									*/
/************************************************************************/

void oz_knl_ast_remove (OZ_Ast *ast)

{
  OZ_Ast *next;

  OZ_KNL_CHKOBJTYPE (ast, OZ_OBJTYPE_AST);

  if (ast -> prev == NULL) oz_crash ("oz_knl_ast_remove: ast %p not in a list", ast);

  /* Remove from list */

  *(ast -> prev) = next = ast -> next;
  if (next != NULL) next -> prev = ast -> prev;

  /* Make its pointers null to indicate it's no longer in a list */

  ast -> next = ast;
  ast -> prev = NULL;
}

/************************************************************************/
/*									*/
/*  Delete an ast block							*/
/*									*/
/*    Input:								*/
/*									*/
/*	ast = ast block to delete					*/
/*	smplock = as required by oz_knl_thread_increfc			*/
/*									*/
/*    Output:								*/
/*									*/
/*	ast block freed off						*/
/*	*astentry = pointer to ast routine				*/
/*	*astparam = parameter for ast routine				*/
/*	*aststs   = status for ast routine				*/
/*	thread ref count decremented					*/
/*									*/
/*    Note:								*/
/*									*/
/*	This routine should only be called by the thread routines, 	*/
/*	as creators of ast's should always try to queue the ast to 	*/
/*	the target thread (via oz_thread_queueast) under all 		*/
/*	circumstances.							*/
/*									*/
/************************************************************************/

void oz_knl_ast_delete (OZ_Ast *ast, OZ_Astentry *astentry, void **astparam, uLong *aststs)

{
  OZ_KNL_CHKOBJTYPE (ast, OZ_OBJTYPE_AST);

  if (ast -> prev != NULL) oz_crash ("oz_knl_ast_delete: deleting ast block that's in a list");
  oz_knl_thread_increfc (ast -> thread, -1);
  if (astentry != NULL) *astentry = ast -> astentry;
  if (astparam != NULL) *astparam = ast -> astparam;
  if (aststs   != NULL) *aststs   = ast -> aststs;
  OZ_KNL_NPPFREE (ast);
}

/************************************************************************/
/*									*/
/*  Get and Set various fields in the Ast block				*/
/*									*/
/************************************************************************/

OZ_Ast *oz_knl_ast_getnext (OZ_Ast *ast)

{
  OZ_KNL_CHKOBJTYPE (ast, OZ_OBJTYPE_AST);
  if (ast -> prev == NULL) oz_crash ("oz_knl_ast_getnext: ast %p is not in a list", ast);
  return (ast -> next);
}

OZ_Procmode oz_knl_ast_getprocmode (OZ_Ast *ast)

{
  OZ_KNL_CHKOBJTYPE (ast, OZ_OBJTYPE_AST);
  return (ast -> procmode);
}

OZ_Thread *oz_knl_ast_getthread (OZ_Ast *ast)

{
  OZ_KNL_CHKOBJTYPE (ast, OZ_OBJTYPE_AST);
  return (ast -> thread);
}

int oz_knl_ast_getexpress (OZ_Ast *ast)

{
  OZ_KNL_CHKOBJTYPE (ast, OZ_OBJTYPE_AST);
  return (ast -> express);
}

void oz_knl_ast_setstatus (OZ_Ast *ast, uLong aststs)

{
  OZ_KNL_CHKOBJTYPE (ast, OZ_OBJTYPE_AST);
  ast -> aststs = aststs;
}

/************************************************************************/
/*									*/
/*  Validate an ast queue and count the elements in it			*/
/*									*/
/*    Input:								*/
/*									*/
/*	queueh = pointer to pointer to first ast in queue		*/
/*	queuet = NULL : this list doesn't keep track of last in queue	*/
/*	         else : pointer to last ast's next field		*/
/*	                (or same as 'queueh' if queue is supposedly empty)
/*									*/
/*    Output:								*/
/*									*/
/*	oz_knl_ast_validateq = number of ast's in the queue		*/
/*									*/
/************************************************************************/

int oz_knl_ast_validateq (OZ_Ast **queueh, OZ_Ast **queuet)

{
  int count;
  OZ_Ast *ast, **last;

  count = 0;
  for (last = queueh; (ast = *last) != NULL; last = &(ast -> next)) {
    if (ast -> prev != last) oz_crash ("oz_knl_ast_validateq: ast -> prev %p, last %p", ast -> prev, last);
    count ++;
  }
  if ((queuet != NULL) && (last != queuet)) oz_crash ("oz_knl_ast_validateq: queuet %p, last %p", queuet, last);
  return (count);
}

/************************************************************************/
/*									*/
/*  Dump an ast list to console						*/
/*									*/
/*    Input:								*/
/*									*/
/*	indent = number of spaces to indent				*/
/*	astl   = first ast in list					*/
/*									*/
/************************************************************************/

void oz_knl_ast_dumplist (int indent, OZ_Ast *astl)

{
  OZ_Ast *ast;

  OZ_KNL_CHKOBJTYPE (astl, OZ_OBJTYPE_AST);

  for (ast = astl; ast != NULL; ast = ast -> next) oz_knl_ast_dumpone (indent, ast);
}

/************************************************************************/
/*									*/
/*  Dump a single ast to console					*/
/*									*/
/*    Input:								*/
/*									*/
/*	indent = number of spaces to indent				*/
/*	ast    = pointer to ast to dump					*/
/*									*/
/************************************************************************/

void oz_knl_ast_dumpone (int indent, OZ_Ast *ast)

{
  OZ_KNL_CHKOBJTYPE (ast, OZ_OBJTYPE_AST);

  oz_knl_printk ("%*sAst %p: next=%p, prev=%p, thread=%p, procmode=%d, astentry=%p, astparam=%p, aststs=%u\n", indent, "", 
                 ast, ast -> next, ast -> prev, ast -> thread, ast -> procmode, ast -> astentry, ast -> astparam, ast -> aststs);
}

/************************************************************************/
/*									*/
/*  This routine records the state of the target thread when the ast 	*/
/*  is being queued.  Then, if the thread should hang with deliverable 	*/
/*  ast's stuck in its queue, we can see what state it was in when the 	*/
/*  ast was queued.							*/
/*									*/
/************************************************************************/

#include "oz_knl_hw.h"

#ifdef OZ_HW_TYPE_486
typedef struct { void *es0;
                 uLong ebx;
                 void *esi;
                 void *edi;
                 void *ebp;
                 void *eip;
               } Hwthsav;

typedef struct { uLong ustksiz;
                 uLong ustkvpg;
                 void *estackva;
                 Hwthsav *exesp;
                 uLong fpused;
                 uByte fpsave[120];
               } Hwthctx;
#else
typedef void Hwthctx;
#endif

void oz_knl_ast_setqinfo (OZ_Ast *ast, OZ_Thread_state thread_state, OZ_Astmode astmode, Long thcpuidx, Hwthctx *hwthctx, int wasempty)

{
#if 000
  Hwthsav *exesp;
  int i;
  void **ebp;

  extern char trace_begaddr[1], trace_endaddr[1];
  extern OZ_Thread *trace_thread;
  extern void trace_start (int itscurrent);

  ast -> thread_state = thread_state;			/* save thread's state just before queuing ast */
  ast -> astmode      = astmode;
  ast -> q_wasempty   = wasempty;
  ast -> thcpuidx     = thcpuidx;			/* save the cpu that the thread's context is loaded in (-1 if not) */
  ast -> mycpuidx     = oz_hw_cpu_getcur ();		/* get my current cpu index */
  if (thcpuidx == ast -> mycpuidx) {			/* see if thread's context is loaded on the current cpu */
    for (i = 0; i < MAXRTNADRS; i ++) {			/* if so, save some return addresses */
      ast -> rtnadrs[i] = oz_hw_getrtnadr (i + 1);
      if (wasempty && (ast -> thread == trace_thread) && (ast -> rtnadrs[i] >= trace_begaddr) && (ast -> rtnadrs[i] < trace_endaddr)) trace_start (1);
      if (ast -> rtnadrs[i] == NULL) break;
    }
  }
  else if (thcpuidx < 0) {
    exesp = hwthctx -> exesp;						// grab its saved stack pointer
    ast -> exesp = exesp;						// save it in the ast
    ast -> rtnadrs[0] = (void *)0x87654321;				// in case exec stack not writable
    if (OZ_HW_WRITABLE (sizeof *exesp, exesp, OZ_PROCMODE_KNL)) {	// make sure exec stack accessible
      ebp = exesp -> ebp;						// ok, get its frame pointer
      for (i = 0; i < MAXRTNADRS; i ++) {				// save this many frames at most
        if (!OZ_HW_WRITABLE (8, ebp, OZ_PROCMODE_KNL)) break;		// break it off if not accessible
        ast -> rtnadrs[i] = ebp[1];					// ok, save the return address
        if (wasempty && (ast -> thread == trace_thread) && (ast -> rtnadrs[i] >= trace_begaddr) && (ast -> rtnadrs[i] < trace_endaddr)) trace_start (0);
        ebp = ebp[0];							// pop out a level
      }
      if (i < MAXRTNADRS) ast -> rtnadrs[i] = ebp;			// if room, save the ebp that ended it all
    }
  }
#endif
}