/*
*
*     SYS.C.SYS - system-wide general functions header
*
*     21-12-91  INH  Original
*     14-02-92       Event logic changed
*     13-04-92       Timer added
*     14-04-92       Rewritten with R12 values
*     24-11-93       Messages look-up
*     16-05-95       Add time to trace function
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include "wimp.h"
#include "werr.h"
#include "msgs.h"
#include "kernel.h"
#include "os.h"
#include "swis.h"

#include "sys.h.stdtypes"
#include "sys.h.sys"
#include "sys.h.config"
#include "sys.h.hrdstate"
#include "sys.h.FEstate"
#include "sys.h.syss"
#include "sys.h.cpu"
#include "sys.h.devices"
#include "module.h.pcsupport"
#include "module.h.pcdevhelp"

#define new(Type)   (Type *)malloc(sizeof(Type))

#define DEBUG 0

/* Globals *********************** */

struct HardwareState *SYS_State_Ptr;
struct FrontEndState *SYS_FEState_Ptr;

BYTE SYS_TempBuf [SYS_BufSize];


/* Trace and debug routines ******************************* */

static char stringbuf[256];


/* **************** */

void SYS_trace   ( char *s, ... )
{
  va_list x;
  va_start (x, s);

  if ( !CFG.Trace ) return;

  vsprintf(stringbuf, s, x);

  printf("Trace: %d: %s\n", SYS_GetTime(), stringbuf);
}

/* *************** */

static char *quickhex = "0123456789ABCDEF";

void SYS_tracedata ( BYTE *s, int len )
{
  int i, d;

  if ( !CFG.Trace ) return;

  while (len > 0)
  {
    memset ( stringbuf, ' ', 64 );
    for ( i=0; i<16; i++ )
    {
      d = *s++;
      stringbuf[i*3] = quickhex[d>>4];
      stringbuf[i*3+1] = quickhex[d&15];
      stringbuf[48+i] = (d < 32 || d > 127) ? '.' : d;
      if ( --len <= 0 )
        break;
    }

    stringbuf[64] = 0;
    printf("Tracedata[%s]\n", stringbuf);
  }

}


/* **************** */

void SYS_error   ( bool fatal, char *s, ... )
{
  char *msg;
  va_list x;

  va_start (x, s);

  /* The 'tidy up' bit is done here so that trace msgs from shutdown & other
      event routines don't trample on stringbuf.
  */

  if ( fatal )
  {
    if ( SYS_FEState.HasInputFocus )
    {
       SYS_callEvent (SYS_LoseFocus);
       SYS_FEState.HasInputFocus = false;
    }

    if ( SYS_FEState.FullFErunning)
    {
       SYS_callEvent (SYS_StopFE);
       SYS_FEState.FullFErunning = false;
       wimp_setmode(SYS_FEState.WimpMode.ModeNum);
    }

    if ( SYS_FEState.WinFErunning)
    {
       SYS_callEvent (SYS_StopWinFE);
       SYS_FEState.WinFErunning = false;
    }

    SYS_callEvent (SYS_Shutdown);
  }

  /* Lookup message and exit */

  msg = msgs_lookup(s);
  if ( msg==NULL || msg[0]==0 )
    sprintf(stringbuf, "Can't lookup error '%s'", s );
  else
    vsprintf(stringbuf, msg, x);

  if ( fatal )
  {
    if ( CFG.Trace ) printf("Fatal: %s\n", stringbuf );
    werr (true, "%s", stringbuf );
    exit(1);
  }

  if ( SYS_FEState.FullFErunning )
    printf("WARNING: %s\n", stringbuf);
  else
  {
    if ( CFG.Trace ) printf("Warning: %s\n", stringbuf);
    werr ( false, "Warning - %s", stringbuf );
  }
}

/* Miscellaneous routines ******************************** */

int SYS_GetTime()
{
  return * (int *)0x10C;
}

bool SYS_IntAvailable ( int level )
{
  if ( level< 0 || level >= 32 )
    return false;

  return (SYS_State.IntsAvailable & (1 << level) );
}

/* Routines for use by devices ******************************** */

void SYS_registerIO   ( int first, int last,
      RdFnPtr Rd8, RdFnPtr Rd16, WrFnPtr Wr8, WrFnPtr Wr16, int R12val )
{
  int i, firstslot, lastslot;

  firstslot = first >> 2; /* Assume I/O slots at 4-byte intervals */
  lastslot  = last  >> 2;

  if ( firstslot>lastslot || firstslot<0 || lastslot>=SYS_nIOSlots )
  {
    SYS_error(true, "ieparms2", "SYS_registerIO", first, last);
    return;
  }

  for (i=firstslot; i<=lastslot; i++)
  {
    SYS_State.IOhandlers[i].Read8   = Rd8;
    SYS_State.IOhandlers[i].Read16  = Rd16;
    SYS_State.IOhandlers[i].Write8  = Wr8;
    SYS_State.IOhandlers[i].Write16 = Wr16;
    SYS_State.IOhandlers[i].R12value= R12val;

  }
  #if DEBUG
    SYS_trace ("SYS_registerIO from %x to %x",(firstslot<<2),(lastslot<<2));
  #endif

}

/* *************** */

void SYS_registerMem  ( int first, int last,
      RdFnPtr Rd8, RdFnPtr Rd16, WrFnPtr Wr8, WrFnPtr Wr16, int R12val )
{
  int i, firstslot, lastslot;

  firstslot = (first-SYS_MemBase) >> 14; /* Assumes 16Kbyte memory chunks */
  lastslot  = (last -SYS_MemBase) >> 14;

  if ( firstslot>lastslot || firstslot<0 || lastslot>=SYS_nMemSlots )
  {
    SYS_error(true, "ieparms2", "SYS_registerMem", first, last);
    return;
  }

  for (i=firstslot; i<=lastslot; i++)
  {
    SYS_State.Memhandlers[i].Read8   = Rd8;
    SYS_State.Memhandlers[i].Read16  = Rd16;
    SYS_State.Memhandlers[i].Write8  = Wr8;
    SYS_State.Memhandlers[i].Write16 = Wr16;
    SYS_State.Memhandlers[i].R12value= R12val;
  }

}

/* **************** */

void SYS_removeIO     ( int first, int last)
{
  SYS_registerIO (first, last,
         SYS_State.NullHandlers.Read8,
         SYS_State.NullHandlers.Read16,
         SYS_State.NullHandlers.Write8,
         SYS_State.NullHandlers.Write16, 0 );


}

/* **************** */

void SYS_removeMem    ( int first, int last)
{
  SYS_registerMem (first, last,
         SYS_State.NullHandlers.Read8,
         SYS_State.NullHandlers.Read16,
         SYS_State.NullHandlers.Write8,
         SYS_State.NullHandlers.Write16, 0 );

}

/* **************** */

/* Each new event is added to the end of the current list of event
   handlers. Therefore, they will be called in the order of
   installation of the device modules
*/

void SYS_registerEvent ( int evtno,  EvtFnPtr newfn, int R12val )
{
  struct CallList *newnode, *p;

  newnode = new( struct CallList );
  if ( newnode == NULL )
    SYS_error( true, "ienomem" );

  newnode->fn.Evt = newfn;
  newnode->R12value = R12val;
  newnode->next = NULL;

  p = SYS_State.eventlist[evtno];
  if ( p == NULL )
     SYS_State.eventlist[evtno] = newnode;
  else
  {
     while ( p->next != NULL ) p = p->next;

     p->next = newnode;
  }
}

/* **************** */

void SYS_registerHPC ( int HPC_id,  HPCFnPtr newfn, int flg, int R12val )
{
  HPC_handler *newnode;
  int listno;

  newnode = new( HPC_handler );
  if ( newnode == NULL )
    SYS_error( true, "ienomem" );

  newnode->DispatchFn = newfn;
  newnode->ServiceID  = HPC_id;
  newnode->flags      = flg;
  newnode->requesting = false;
  newnode->R12_val    = R12val;

  listno = ServiceIDHashFn(HPC_id);

  newnode->next     = SYS_State.HPCHandlerLists[listno];
  SYS_State.HPCHandlerLists[listno] = newnode;
}

/* ******************** */

void SYS_registerCfg ( CfgFnPtr newfn, int R12val )
{
  struct CallList *newnode;

  newnode = new( struct CallList );
  if ( newnode == NULL )
    SYS_error( true, "ienomem" );
  newnode->fn.Cfg   = newfn;
  newnode->next     = SYS_State.Config_list;
  newnode->R12value = R12val;

  SYS_State.Config_list = newnode;
}

/* ----------------------- */

void SYS_SetCallback ( Callback *pCB, OneIntFnPtr fnptr, int param )
{
  pCB->fn     = fnptr;
  pCB->R0val  = param;
  pCB->R12val = 0;
  os_swi1 ( PCSupport_SetCallback, (int) pCB );
}

/* ***************************************************** */

void  SYS_Interrupt (int intno)
{
  SYS_State.CPU_Interrupt (intno);
}

/* **************** */

void  SYS_Activity ( int count )
{
  if ( count > SYS_FEState.Activity )
    SYS_FEState.Activity = count;
}

/* Routines for use by CPU module and front end ************************* */

void SYS_callEvent ( int evtno )
{
  struct CallList *p;

  for ( p = SYS_State.eventlist[evtno]; p != NULL; p = p->next )
  {
    if ( p->R12value == 0 )
    {
      p->fn.Evt();
    }
    else
    {
      _kernel_swi_regs r;
      SYSs_CallViaR12 ( &r, (int) p->fn.Evt, p->R12value );
    }
  }
}

/* **************** */

bool SYS_callCfg  ( char *text )
{
  struct CallList *p;
  bool res = false;

  for ( p = SYS_State.Config_list; p != NULL; p = p->next )
  {
    if ( p->R12value == 0 )
    {
      res = p->fn.Cfg(text);
    }
    else
    {
      _kernel_swi_regs r;
      r.r[0] = (int) text;
      res = SYSs_CallViaR12 ( &r, (int) p->fn.Cfg, p->R12value );
    }

    if ( res )
      return true;
  }

  return false;
}

/* **************** */

void SYS_DispatchHPC ( BYTE *buf )
{
  HPC_handler *p;
  int id;
  id =  buf[0] + (buf[1]<<8);

  for ( p = SYS_State.HPCHandlerLists[ServiceIDHashFn(id)];
        p != NULL;
        p = p->next )
  {
    if ( p->ServiceID == id )  /* Found it */
    {
      if ( p->R12_val == 0 )
        p->DispatchFn ( buf );
      else
      {
        _kernel_swi_regs r;
        r.r[0] = (int) buf;
        SYSs_CallViaR12 ( &r, (int) p->DispatchFn, p->R12_val );
      }

      return;
    }
  }

  SYS_trace("Unknown HPC Dispatch:");
  SYS_tracedata(buf, 16 );
  buf[0] = buf[1] = buf[2] = buf[3] = 0xFF;
}

/* **************** */

/* SYS_DoCallbacks ()

   Will process the list of all callback routines which
   have been registered with the 386PC_SetCallback SWI.
   This will traditionally be called as soon as the CPU
   emulation has stopped for any reason.
*/

void SYS_DoCallbacks ( void )
{
  Callback *pCB;

  while ( os_swi1r ( PCSupport_GetCallback, 0, (int *) &pCB ) == NULL &&
          pCB != NULL )
  {
    if ( pCB->R12val == 0 )
      pCB->fn ( pCB->R0val );
    else
    {
      _kernel_swi_regs r;
      r.r[0] = pCB->R0val;
      SYSs_CallViaR12 ( &r, (int) pCB->fn, pCB->R12val );
    }
  }
}

/* --------------------- */

static bool ModuleShutdown(void)
{
  _kernel_swi_regs R;
  /* Tell support module we're going */
  R.r[0] = 0;
  R.r[1] = 0;
  _kernel_swi ( PCDevHelp_Init, &R, &R );

  /* Release card */
  R.r[0] = 0;
  _kernel_swi ( PCSupport_LockCard, &R, &R );

  return false;
}

/* Init routine *************************************** */

bool SYS_Init (void)
{
  _kernel_swi_regs R;
  float version ;

  /* Get details from PCSupport modules */

  if ( _kernel_swi ( PCSupport_ModuleInfo, &R, &R ) != NULL )
  {
    printf("PCSupport module is not loaded!\n");
    return false;
  }

  /* Returns R0 = module version * 100, R1 -> static data area,
       R2 = size of static data area */

  if ( R.r[0] != 204)
  {
    version =  R.r[0]/100;
    printf("PCSupport module is the wrong version!\nFound version: %f.2, should be 2.04\n",version);
    return false;
  }

  if ( R.r[2] < (sizeof(struct HardwareState)+sizeof(struct FrontEndState)) )
  {
    printf("Eek! Harvey's got some structure sizes wrong!\n");
    return false;
  }

  SYS_State_Ptr   = (struct HardwareState *) R.r[1];
  SYS_FEState_Ptr = (struct FrontEndState *)
                           (R.r[1] + sizeof(struct HardwareState));

  /* Try to lock card */
  R.r[0] = 1;
  if ( _kernel_swi ( PCSupport_LockCard, &R, &R ) != NULL || R.r[0] != 0 )
  {
    printf("PC Card is already in use\n");
    return false;
  }

  /* Zero out structures */
  memset ( (BYTE *) SYS_State_Ptr, 0, sizeof(struct HardwareState) );
  memset ( (BYTE *) SYS_FEState_Ptr, 0, sizeof(struct FrontEndState) );

  SYS_State.current_ver = HRDSTATE_CURR_VER;
  SYS_State.min_compat_ver = HRDSTATE_MIN_VER;
  SYS_FEState.current_ver = FESTATE_CURR_VER;
  SYS_FEState.min_compat_ver = FESTATE_MIN_VER;

  R.r[0] = (int) SYS_State_Ptr;
  R.r[1] = (int) SYS_FEState_Ptr;
  _kernel_swi ( PCDevHelp_Init, &R, &R );

  /* Assume trace is on until we've finished processing Config */

  CFG.Trace = true;

  SYS_registerEvent ( SYS_Shutdown, ModuleShutdown, 0 );
  return true;
}

/* **************** */


