/* Copyright distributed.net 1997-1999 - All Rights Reserved
 * For use in distributed.net projects only.
 * Any other distribution or use of this source violates copyright.
 *
 * Emulation functions/stubs for portability across NetWare versions.
 * All functions here are CLIB safe (don't require context)
 *
 * Written by Cyrus Patel <cyp@fb14.uni-mainz.de>
 *
*/

#if (!defined(lint) && defined(__showids__))
const char *nwemu_cpp(void) {
return "@(#)$Id: nwlemu.cpp,v 1.1.2.1 1999/11/14 20:42:57 cyp Exp $"; }
#endif

#include <process.h>   /* ThreadSwitch(), FindNLMHandle() */
#include "nwlemu.h" /* ourselves */

#ifdef __cplusplus
extern "C" {
#endif
extern void *ImportPublicSymbol( int nlmHandle, const char *lname ); /* krnl/a3112 */
extern int UnImportPublicSymbol( int nlmHandle, const char *lname ); /* krnl/a3112 */
extern unsigned int GetNLMHandleFromPrelude(void);                   /* nwpre3x.c */
extern int GetFileServerName( int conn, const char *buffer /* min49 chars */ );
extern void OutputToScreen( int scrhandle, const char *fmt, ... );
extern void ConsolePrintf( /* const */ char *fmt, ... ); /* avoid nw*.h */
extern unsigned long GetCurrentTicks(void);
#ifdef __cplusplus
}
#endif

/* ===================================================================== */

unsigned int GetNLMHandle(void)      
{
  /* CLIB doesn't read the NLM handle from the PCB (thread structure),
     but instead, uses RunningProcess->threadGroupID->NLMID->nlmHandle.
     So, to stay clean of context issues, we get it from our custom prelude.
     (bug alert: CLIB 3x does not check SCGT (threadgroup) or SCN (NLMID) 
     validity when retrieving the NLM handle.)
  */
  return GetNLMHandleFromPrelude();
}                           

/* ===================================================================== */

static void *__ImUnimportSymbol(int NLMHandle, char *cname,register doimport)
{
  if (cname)
  {
    char lname[64]; 
    unsigned int cnamelen = 0;
    //if (NLMHandle == 0)
    //  NLMHandle = GetNLMHandle();
    while (cname[cnamelen] && (cnamelen+1) < sizeof(lname))
    {
      register char c = cname[cnamelen];
      lname[++cnamelen] = c;
    }
    if (cnamelen > 0 && (cnamelen+1) < sizeof(lname))
    {
      lname[0] = (char)cnamelen;
      if (doimport)
        return ImportPublicSymbol( NLMHandle, lname );
      return (void *)UnImportPublicSymbol( NLMHandle, lname );
    }
  }
  return (void *)0;    
}

void *ImportSymbol(int NLMHandle, char *cname) /* all important. */
{                                              /* CLIB.NLM >=4.x */
  return __ImUnimportSymbol( NLMHandle, cname, 1 );
}  

int UnimportSymbol(int NLMHandle, char *cname) /* CLIB.NLM >=4.x */
{
  return (int)__ImUnimportSymbol( NLMHandle, cname, 0 );
}  

/* ===================================================================== */

unsigned long GetCurrentTime(void)
{
  static unsigned long (*_GetCurrentTime)(void) = 
                                      ((unsigned long (*)(void))0x01);
  static unsigned long (*_currentTime);

  if (_GetCurrentTime == ((unsigned long (*)(void))0x01))
  {
    unsigned int nlmHandle = GetNLMHandle();
    void *lvect = ImportSymbol( nlmHandle, "currentTime" );
    void *fvect = ImportSymbol( nlmHandle, "GetCurrentTime" );
    _currentTime = (unsigned long (*))lvect;
    _GetCurrentTime = (unsigned long (*)(void))fvect;
  }
  if (_currentTime)
    return (*_currentTime);
  if (_GetCurrentTime)
    return (*_GetCurrentTime)();
  #if 0
  if (GetThreadGroupID())
  {
    ConsolePrintf("Unable to get GetCurrentTime() vector\r\n");
    ExitThread( 1 /* EXIT_NLM */, 3 /*return code*/ );
  }
  #endif  
  return GetCurrentTicks();
}  

/* ===================================================================== */

unsigned long GetSuperHighResolutionTimer(void)
{
  /* http://developer.novell.com/ndk/doc/storarch/nwpa_enu/data/hct8zfuy.htm
     SDK: "This is a high resolution timer that combines the lowest 16bits 
     of Current Time with the timer register to give a timer resolution 
     of approximately 838 nanoseconds per count. This call does not allow 
     for possible tick count rollover, so the programmer must take into 
     consideration a "negative" time count."
     (838ns == 1000000000/1193181.666...)
     
     THIS IS WRONG INFORMATION! GetSuperHighResolutionTimer() returns 
     quite something else (IMO, it too returns 100usec time), but that
     is not the stub's problem to deal with.
  */
  static unsigned long (*_GetSuperHighResolutionTimer)(void) = 
                                            ((unsigned long (*)(void))0x01);
  unsigned long tb, te;

  if (_GetSuperHighResolutionTimer == ((unsigned long (*)(void))0x01))
  {
    unsigned long (*gshrt)(void) = (unsigned long (*)(void))0;
    long ver = (GetFileServerMajorVersionNumber()*100) +
                GetFileServerMinorVersionNumber();

    /* the others emulate it in A3112 which does not compensate for edge */
    /* (ie, the high and low words can be of _different_ ticks). Also, */
    /* NW311's loader does not ensure rategen mode */
    if (ver >= 410 || (ver >= 320 && ver < 400))
    {            
      gshrt = (unsigned long (*)(void))
            ImportSymbol( GetNLMHandle(), "GetSuperHighResolutionTimer" );
    }
    if (gshrt == ((unsigned long (*)(void))0)) /* NetWare 3.x */
    {
      /* make sure that the timer is mode 2 (rate generator) */
      /* rather than mode 3 (square wave), which doesn't count linearly. */
      /* it _should_ already be mode 2, but make sure anyway */
      te = tb = GetCurrentTime();
      while (tb == te) /* wait for tick change */
        tb = GetCurrentTime();
      _asm 
      {
        mov  dx, 43h 
        mov  al, 34h /* mode control, counter 0, set mode 2 */
        out  dx, al  /* outportb(0x43, 0x34); */
        mov  dx, 40h
        xor  al, al  /* default is 65536 (0x0000) */
        out  dx, al  /* outportb(0x40, 0xff); */
        out  dx, al  /* outportb(0x40, 0xff); */
      }
    }
    _GetSuperHighResolutionTimer = gshrt;
  }
  if ( _GetSuperHighResolutionTimer == ((unsigned long (*)(void))0) )
  {
    /*
    outportb(0x43, 0x00);
    lsb = (unsigned char)inportb(0x40);
    msb = (unsigned char)inportb(0x40);
    return ((GetCurrentTime() << 16)|(0xffff - ((msb<<8)|lsb)));
    */
    te = tb = GetCurrentTime();
    for (;;)
    {
      unsigned long t;
      te = tb;
      _asm xor  eax, eax
      _asm mov  dx, 43h 
      _asm out  dx, al  /* mode control, counter 0, read */
      _asm mov  dx, 40h
      _asm in   al, dx  /* read lsb */
      _asm xchg ah, al
      _asm in   al, dx  /* read msb */
      _asm xchg ah, al
      _asm not  ax
      _asm mov  t, eax
      tb = GetCurrentTime();
      if (tb == te)
      {
        te = t;
        break;
      }
    }
    return ((tb<<16)|te);
  }
  return (*_GetSuperHighResolutionTimer)();
}

/* ===================================================================== */

unsigned long GetHighResolutionTimer(void)
{
  /* SDK: "This timer combines the Current Time with the timer register 
     to create a return value that has a resolution of approximately 
     100 microseconds per count."
  */
  static unsigned long (*_GetHighResolutionTimer)(void) = 
                                         ((unsigned long (*)(void))0x01);
  if (_GetHighResolutionTimer == ((unsigned long (*)(void))0x01))
  {
    _GetHighResolutionTimer = (unsigned long (*)(void))
            ImportSymbol( GetNLMHandle(), "GetHighResolutionTimer" );
  }
  
  if (!_GetHighResolutionTimer)
  {
    static int supertimerisok = -1;
    unsigned long lo32, hi32;
    if (supertimerisok == -1)
    {
      lo32 = GetCurrentTime();
      hi32 = GetSuperHighResolutionTimer();
      lo32 = (lo32 & 0xffff);
      hi32 = (hi32 >> 16) & 0xffff;
      supertimerisok = ((hi32 == lo32 || hi32 == (lo32+1)) ? (1) : (0));
    }
    /* the two overlap. the high 16 bits of lo32 == low 16 bits of hi32 */
    lo32 = 0; hi32 = GetCurrentTime();
    for (;;)
    {
      unsigned long tmp = hi32;
      if (supertimerisok)
        lo32 = GetSuperHighResolutionTimer();
      else
      {
        _asm xor  eax, eax
        _asm mov  dx, 43h 
        _asm out  dx, al  /* mode control, counter 0, read */
        _asm mov  dx, 40h
        _asm in   al, dx  /* read lsb */
        _asm xchg ah, al
        _asm in   al, dx  /* read msb */
        _asm xchg ah, al
        _asm not  ax
        _asm mov  dx, ax
        _asm mov  eax, hi32
        _asm shl  eax, 16
        _asm mov  ax, dx
        _asm mov  lo32, eax
      }
      hi32 = GetCurrentTime();
      if (hi32 == tmp)
        break;
    }
    #define TIMER_FREQUENCY 1193182 /* 1193181.666... */
    _asm mov eax, lo32
    _asm mov edx, hi32
    _asm shr edx, 16
    _asm mov ecx, TIMER_FREQUENCY
    _asm div ecx 
    _asm mov hi32, eax /* secs */ 
    _asm mov eax, edx /*remainder */ 
    _asm xor edx, edx
    _asm mov ecx, 1000000
    _asm mul ecx
    _asm mov ecx, TIMER_FREQUENCY
    _asm div ecx
    _asm mov lo32, eax /* usecs */
    #undef TIMER_FREQUENCY
    return ((hi32 * 10000UL) + ((((lo32+5)/10) + 5)/10));
  }
  return (*_GetHighResolutionTimer)();
}

/* ===================================================================== */

unsigned long GetSystemConsoleScreen(void)
{
  static unsigned long (*_systemConsoleScreen);
  static unsigned long (*_GetSystemConsoleScreen)(void) = 
                                            ((unsigned long (*)(void))0x01);
  if (_GetSystemConsoleScreen == ((unsigned long (*)(void))0x01))
  {
    _systemConsoleScreen = (unsigned long (*))
                       ImportSymbol(GetNLMHandle(),"systemConsoleScreen");
    _GetSystemConsoleScreen = (unsigned long (*)(void))
                       ImportSymbol(GetNLMHandle(),"GetSystemConsoleScreen");
  }
  if (_GetSystemConsoleScreen)
    return (*_GetSystemConsoleScreen)();
  if (_systemConsoleScreen)
    return (*_systemConsoleScreen);
  return 0;
}

/* ===================================================================== */

void RingTheBell(void)  /* >=312 */
{
  static void (*_RingTheBell)(void) = ((void (*)(void))0x01);
  /* static void (*_StartAndStopBell)(void); static void (*_StopBell)(void); */
  
  if (_RingTheBell == ((void (*)(void))0x01))
    _RingTheBell = (void (*)(void))ImportSymbol(GetNLMHandle(),"RingTheBell");
  if (_RingTheBell)
    (*_RingTheBell)();
  else
  {
    unsigned long sysScreen = GetSystemConsoleScreen();
    if (sysScreen) OutputToScreen( sysScreen, "\a" );
  }
  return;
}

/* ===================================================================== */

int ReturnFileServerName(char *buffer) /* min 64+1 */
{                                      /* to get len call with buffer==NULL */
  static char fsname[64+1];
  static int fsnamelen = -1;
  int pos;
  
  if (fsnamelen < 0)
  {
    char *lname;
    unsigned int nlmHandle = GetNLMHandle();
    char *symname = "ReturnFileServerName";
    void *vect = ImportSymbol( nlmHandle, symname );
    fsnamelen = 0;
    if (vect)
    {
      char scratch[128+1];
      fsnamelen = ((int (*)(char *))vect)(scratch);
      if (fsnamelen)
      {
        pos = 0;
        lname = scratch;
        while (pos < fsnamelen && pos < (sizeof(fsname)-1))
          fsname[pos++] = *lname++;
        fsnamelen = pos;
      }
      fsname[fsnamelen] = '\0';
    }
    else
    {
      vect = ImportSymbol( nlmHandle, symname+6 );
      lname = (vect) ? (*((char **)vect)) : ((char *)0);
      if (lname)
      {
        fsnamelen = *((unsigned char *)lname); 
        lname++;
      }
      if (fsnamelen)
      {
        pos = 0;
        while (pos < fsnamelen && pos < (sizeof(fsname)-1))
          fsname[pos++] = *lname++;
        fsnamelen = pos;
      }
      fsname[fsnamelen] = '\0';
      UnimportSymbol( nlmHandle, symname+6 );
    }
  }
  if (buffer)
  {
    int i;
    for (i=0; i<fsnamelen; i++)
      buffer[i] = fsname[i];
    buffer[fsnamelen]='\0';
  }
  return fsnamelen;
}

/* ===================================================================== */

static int __GetSetSetableParameterValue( register int doGet, 
       unsigned long connum, unsigned char *setableParamName, void *val )
{
  static int (*_GetSetableParameterValue)( unsigned long, 
       unsigned char *, void * ) = 
       (int (*)(unsigned long, unsigned char *, void *))(0x01);
  static int (*_SetSetableParameterValue)( unsigned long, 
       unsigned char *, void * ) = 
       (int (*)(unsigned long, unsigned char *, void *))(0x01);

  if (_GetSetableParameterValue == ((int (*)(unsigned long, 
                                     unsigned char *, void *))(0x01)) )
  {
    _GetSetableParameterValue = 
        (int (*)(unsigned long, unsigned char *, void *))
        ImportSymbol( GetNLMHandle(), "GetSetableParameterValue" );
    _SetSetableParameterValue = 
        (int (*)(unsigned long, unsigned char *, void *))
        ImportSymbol( GetNLMHandle(), "SetSetableParameterValue" );
  }
  if (_GetSetableParameterValue && _SetSetableParameterValue)
  {
    if (doGet)
      return (*_GetSetableParameterValue)(connum,setableParamName, val);
    return (*_SetSetableParameterValue)(connum,setableParamName, val);
  } 
  return -1;    
}

unsigned long GetSetableParameterValue( unsigned long connum,
                             unsigned char *setableParamName, void *val )
{                                                  /* CLIB.NLM>=4.x */
  return __GetSetSetableParameterValue( 1, connum, setableParamName, val );
}  
               
unsigned long SetSetableParameterValue( unsigned long connum, 
                             unsigned char *setableParamName, void *val )
{                                                  /* CLIB.NLM>=4.x */
  return __GetSetSetableParameterValue( 0, connum, setableParamName, val );
}  

/* ===================================================================== */
               
static long __GetFileServerMajorMinorVersionNumber(void)
{
  /* although A3112 exports this on 3.x, it always returns 3.11 */
  static int minor, major = -1;
  if (major == -1)
  {
    unsigned int thisthat, nlmHandle = GetNLMHandle();
    struct { char *symname; int what; } minmaj[2] = {
           { "GetFileServerMajorVersionNumber", 3 },
           { "GetFileServerMinorVersionNumber", 0 } };
    for (thisthat = 0; thisthat < 2; thisthat++)
    {
      void *vect; char *symname = minmaj[thisthat].symname;
      if ((vect = ImportSymbol( nlmHandle, symname+3 ))!=((void *)0))
      {
        minmaj[thisthat].what = (*((unsigned char *)(vect)));
        UnimportSymbol( nlmHandle, symname+3 );
      }
      else if ((vect = ImportSymbol( nlmHandle, symname ))!=((void *)0))
      {
        minmaj[thisthat].what = (*((int (*)(void))vect))(); 
        UnimportSymbol( nlmHandle, symname );
      }
    }
    minor = minmaj[1].what;
    major = minmaj[0].what;
  }
  return ((major & 0xff)+(minor << 8));
}  
  
unsigned int GetFileServerMajorVersionNumber(void) /* A3112/SERVER.NLM >=4.x */
{
  return (__GetFileServerMajorMinorVersionNumber() & 0xff);
}

unsigned int GetFileServerMinorVersionNumber(void) /* A3112/SERVER.NLM >=4.x */
{
  return ((__GetFileServerMajorMinorVersionNumber() >> 8) & 0xff);
}

/* ===================================================================== */

static unsigned int __GetCurrMaxNumberOfPollingLoops(int getMax)
{
  static int (*_MaximumNumberOfPollingLoops) = ((int *)0x01);
  static int (*_NumberOfPollingLoops);
  if (_MaximumNumberOfPollingLoops == ((int *)0x01))
  {
    _MaximumNumberOfPollingLoops = 
       (int *)ImportSymbol(GetNLMHandle(), "MaximumNumberOfPollingLoops" );
    _NumberOfPollingLoops = 
       (int *)ImportSymbol(GetNLMHandle(), "NumberOfPollingLoops" );
  }
  if (_MaximumNumberOfPollingLoops && _NumberOfPollingLoops)
  {
    if (getMax)
      return (unsigned int)(*_MaximumNumberOfPollingLoops);
    return (unsigned int)(*_NumberOfPollingLoops);
  }
  return ((getMax)?(+1):(0));
}

unsigned int GetMaximumNumberOfPollingLoops(void) /* SERVER.NLM >=4.x */
{
  return __GetCurrMaxNumberOfPollingLoops(1);
}

unsigned int GetNumberOfPollingLoops(void)  /* SERVER.NLM >=4.x */
{
  return __GetCurrMaxNumberOfPollingLoops(0);
}

/* ===================================================================== */

#define __yield_ThreadSwitch                 1
#define __yield_ThreadSwitchWithDelay        2
#define __yield_ThreadSwitchLowPriority      3
#define __yield_CYieldIfNeeded            0x11
#define __yield_CYieldWithDelay           0x12
#define __yield_CYieldUntilIdle           0x13

static void __ThreadSwitchXXX(int mode)
{
  /* ThreadSwitch/thr_yield/CRescheduleMyself/CYieldIfNeeded */
  static void (*_ThreadSwitch)(void) = (void (*)(void))0x01; /* control */
  static void (*_ThreadSwitchWithDelay)(void);
  static void (*_ThreadSwitchLowPriority)(void);
  static void (*_CYieldIfNeeded)(void);
  static void (*_CYieldWithDelay)(void);
  static void (*_CYieldUntilIdle)(void);

  if (_ThreadSwitch == (void (*)(void))0x01)
  {
    void (*proc)(void);
    unsigned int nlmHandle = GetNLMHandle();
    
    _CYieldIfNeeded = _ThreadSwitch = ThreadSwitch;
    _CYieldWithDelay = _ThreadSwitchWithDelay = (void (*)(void))0;
    _CYieldUntilIdle = _ThreadSwitchLowPriority = (void (*)(void))0;

    proc = (void (*)(void))ImportSymbol(nlmHandle, "CYieldIfNeeded"); /* 4.11 */
    if (!proc)
      proc = (void (*)(void))ImportSymbol(nlmHandle, "CRescheduleMyself");
    if (!proc) 
      proc = (void (*)(void))ImportSymbol(nlmHandle, "CRescheduleLast"); /*3.11*/
    if (proc)               
      _CYieldIfNeeded = proc; /* phew! */

    proc = (void (*)(void))ImportSymbol(nlmHandle, "CYieldUntilIdle");
    if (!proc)
      proc = (void (*)(void))ImportSymbol(nlmHandle, "CRescheduleLastLowPriority");
    if (proc)
    {
      _ThreadSwitchLowPriority = _CYieldUntilIdle = proc;
      proc = (void (*)(void))ImportSymbol(nlmHandle, "ThreadSwitchLowPriority");
      if (proc)
        _ThreadSwitchLowPriority = proc;
    }
    
    proc = (void (*)(void))ImportSymbol(nlmHandle, "CYieldWithDelay");
    if (!proc)
      proc = (void (*)(void))ImportSymbol(nlmHandle, "CRescheduleLastWithDelay");
    if (proc)
    {
      _ThreadSwitchWithDelay = _CYieldWithDelay = proc;
      proc = (void (*)(void))ImportSymbol(nlmHandle, "ThreadSwitchWithDelay");
      if (proc)
        _ThreadSwitchWithDelay = proc;
    }
  }    
  if (mode == __yield_CYieldUntilIdle)
  {
    if (_CYieldUntilIdle)
      (*_CYieldUntilIdle)();
    else
      (*_CYieldIfNeeded)();
  }
  else if (mode == __yield_ThreadSwitchLowPriority)
  {
    if (_ThreadSwitchLowPriority)
      (*_ThreadSwitchLowPriority)();
    //else if (_ThreadSwitchWithDelay)
    //  (*_ThreadSwitchWithDelay)();
    else
      ThreadSwitch();
  }
  else if (mode == __yield_CYieldWithDelay)
  {
    if (_CYieldWithDelay)
      (*_CYieldWithDelay)();
    else
    { /* should we do a CSchedule|CancelInterruptTimeCallBack thingie here? */
      int i=0; 
      while ((i++)<=10)
        (*_CYieldIfNeeded)();
    }
  }
  else if (mode == __yield_ThreadSwitchWithDelay)
  {
    if (_ThreadSwitchWithDelay)
      (*_ThreadSwitchWithDelay)();
    else
    {
      delay(0);
      /* while (GetDiskIOsPending() || GetNestedInterruptLevel()) 
        ThreadSwitch();
      */
    }
  }   
  else /* if (mode == __yield_CYieldIfNeeded) */
  {
    (*_CYieldIfNeeded)();
  }
  return;
}

void ThreadSwitchWithDelay(void) /* CLIB >=3.12 */
{  __ThreadSwitchXXX(__yield_ThreadSwitchWithDelay); }
void ThreadSwitchLowPriority(void) /* CLIB >=4.x */
{  __ThreadSwitchXXX(__yield_ThreadSwitchLowPriority); }

void CRescheduleLastWithDelay(void) /* kernel >=4.x, 3.12 */
{  __ThreadSwitchXXX(__yield_CYieldWithDelay); }
void CRescheduleWithDelay(void) /* kernel >=4.x, 3.12 */
{  __ThreadSwitchXXX(__yield_CYieldWithDelay); }
void CYieldWithDelay(void) /* kernel >=4.x, 3.12 */
{  __ThreadSwitchXXX(__yield_CYieldWithDelay); }

void CRescheduleLastLowPriority(void) /* kernel >=4.x */
{  __ThreadSwitchXXX(__yield_CYieldUntilIdle); }
void CYieldUntilIdle(void) /* kernel >=4.x */
{  __ThreadSwitchXXX(__yield_CYieldUntilIdle); }

void CRescheduleMyself(void) /* kernet >=3.0? */
{  __ThreadSwitchXXX(__yield_CYieldIfNeeded); }
void CRescheduleLast(void) /* kernet >=3.0? */
{  __ThreadSwitchXXX(__yield_CYieldIfNeeded); }
void CYieldIfNeeded(void) 
{  __ThreadSwitchXXX(__yield_CYieldIfNeeded); }

/* ===================================================================== */

/* what is this function called? its not CalculateProcessorUtilization either*/

unsigned int GetProcessorUtilization(void) /* SMP.NLM or kernel */
{
  static int (*_CPU_Combined) = ((int *)0x01);
  unsigned int m, n;
  if (_CPU_Combined == ((int *)0x01))
    _CPU_Combined = (int *)ImportSymbol( GetNLMHandle(), "CPU_Combined" );
  if (_CPU_Combined)
    return (unsigned int)(*_CPU_Combined);
  m = GetMaximumNumberOfPollingLoops();
  n = GetNumberOfPollingLoops();
  if (n > ((0xFFFFFFFFul)/150))
  {
    n>>=8;
    m>>=8;
  }
  if ((m == 0) || (n > m))
    return 0;
  return (100-(((n*100)+(m>>1))/m));
}  

/* ===================================================================== */

unsigned int GetNumberOfRegisteredProcessors(void) /* Kernel >= 4.x */
{
  int count = -1;
  unsigned int nlmHandle = GetNLMHandle();
  char *fname = "GetNumberOfRegisteredProcessors";
  int (*proc)(void) = (int (*)(void))ImportSymbol( nlmHandle, fname );
  if (proc)
  {
    count = ((*proc)());
    UnimportSymbol( nlmHandle, fname );
  }
  if (count < 1)
    count = 1;
  return count;
}

/* ===================================================================== */
/* 
   NWSMPIsLoaded() and NWSMPIsAvailable() have to be the stupidest, most 
   misleading pieces of code ever to cross the CLIB arena.
*/
static int __NWSMPIsXXX(int which)
{
  static int _isavail, _isloaded = -1;
  if (_isloaded == -1)
  {
    int isloaded = 0, isavail = 0;
    int ver = (GetFileServerMajorVersionNumber() * 100) + 
               GetFileServerMinorVersionNumber();
    if ( ver >= 411 )
    {
      unsigned int nlmHandle = GetNLMHandle();
      char *fname; int (*proc)(void); 

      fname = "NWSMPIsAvailable";
      proc = (int (*)(void))ImportSymbol( nlmHandle, fname );
      if (proc)
      {
        if ((*proc)())
          isavail++;
        UnimportSymbol( nlmHandle, fname );
      }
//ConsolePrintf("%s() [%8.8x] => %s\r\n", fname, proc,((isavail)?("yes"):("no")));
      isloaded = isavail; /* must be loaded if available */
      if (!isloaded)
      {
        fname = "NWSMPIsLoaded";
        proc = (int (*)(void))ImportSymbol( nlmHandle, fname );
        if (proc)
        {
          if ((*proc)())
            isloaded++;
          UnimportSymbol( nlmHandle, fname );
        }
      }
//ConsolePrintf("%s() [%8.8x] => %s\r\n", fname, proc,((isloaded)?("yes"):("no")));

      if (!isavail && !isloaded) /* cannot trust the test just done */
      {
        char scratch[64];
        fname = "SMP NetWare Kernel Mode";
        if ( GetSetableParameterValue( 0, (unsigned char *)fname, 
                                            (void *)&scratch[0] ) == 0 )
        {
          isloaded = 1;
          if ( ver >= 500 )
            isavail = 1;
          else 
          {
            unsigned int (*_spin_alloc)(const char *name);
            _spin_alloc = (unsigned int (*)(const char *name))
                      ImportSymbol( nlmHandle, "NWSMPSpinAlloc" );
            if (_spin_alloc)
            {
              int (*_spin_destroy)( unsigned int ) = (int (*)(unsigned int))
                   ImportSymbol( nlmHandle, "NWSMPSpinDestroy" );
              if (_spin_destroy)
              {
                unsigned int spin = (*_spin_alloc)("distributed.net");
                if (spin)
                {
                  isavail = 1;
                  (*_spin_destroy)(spin);
                }
                UnimportSymbol( nlmHandle, "NWSMPSpinDestroy" );
              }
              UnimportSymbol( nlmHandle, "NWSMPSpinAlloc" );
            }
          } /* NW411 */
        } /* GetSetableParameterValue */
//ConsolePrintf("Alternate check: SMP IsLoaded: %d IsAvail: %d\r\n", isloaded, isavail );
      } /* (!isavail && !isloaded) */
    } /* Ver >= 411 */
    _isavail = isavail;
    _isloaded = isloaded;
  }
  if (_isavail)
    return 1;
  if (which == 'l')
    return _isloaded;
  return _isavail;
}      
 
int NWSMPIsLoaded(void) /* "has CLIB CLIB'ified SMP.NLM exports?" */
{ return /* __NWSMPIsXXX('l'); */ (FindNLMHandle( "SMP.NLM" ) != 0); }
int NWSMPIsAvailable(void) /* test of SMP.NLM's "extern int *mdisable;" */
{ return __NWSMPIsXXX('a'); }

/* ===================================================================== */

void NWThreadToMP(void)
{
  static void (*_NWThreadToMP)(void) = (void (*)(void))0x01;
  if (_NWThreadToMP == ((void (*)(void))0x01))
    _NWThreadToMP = (void (*)(void))ImportSymbol( GetNLMHandle(), "NWThreadToMP" );
  if (_NWThreadToMP)
    (*_NWThreadToMP)();
  return;
}

/* ===================================================================== */

void NWThreadToNetWare(void)
{
  static void (*_NWThreadToNW)(void) = (void (*)(void))0x01;
  if (_NWThreadToNW == ((void (*)(void))0x01))
    _NWThreadToNW = (void (*)(void))ImportSymbol( GetNLMHandle(), "NWThreadToNetWare" );
  if (_NWThreadToNW)
    (*_NWThreadToNW)();
  return;
}

/* ===================================================================== */

int GetServerConfigurationInfo(int *servType, int *ldrType) /* >=3.12/4.1 */
{ 
  static int ldr = -1, serv = -1;
  if (serv == -1)
  {
    int nlmHandle = GetNLMHandle();
    char *symname = "GetServerConfigurationInfo";
    void *proc = ImportSymbol( nlmHandle, symname );
    if (proc)
    {
      (*((int (*)(int *,int *))proc))(&serv, &ldr);
      UnimportSymbol( nlmHandle, symname );
    }
    else
    {
      symname = "GetServerConfigurationType";
      proc = ImportSymbol( nlmHandle, symname );
      if (proc)
      {
        serv = (*((int (*)(void))proc))();
        UnimportSymbol( nlmHandle, symname );
      }
      else 
      {
        symname = "serverConfigurationType";
        proc = ImportSymbol( nlmHandle, symname );
        if (proc)
        {
          serv = (*((int *)proc));
          UnimportSymbol( nlmHandle, symname );
        }
      }
    }
    if (serv == -1)
      serv = 0;
  }
  if (servType)
    *servType = serv;
  if (ldrType)
    *ldrType = ((ldr == -1)?(1):(ldr));
  return 0;
}  

/* ===================================================================== */

static unsigned int __GetUnsignedXXX(int which)
{
  static unsigned int (*_GetNestedInterruptLevel)(void) = (unsigned int (*)(void))0x01;
  static unsigned int (*_GetDiskIOsPending)(void) = (unsigned int (*)(void))0x01;
  static unsigned int (*_NestedInterruptCount);
  static unsigned int (*_DiskIOsPending);

  if (_GetNestedInterruptLevel == ((unsigned int (*)(void))0x01))
  {
    unsigned int nlmHandle = GetNLMHandle();
    _DiskIOsPending = _NestedInterruptCount = (unsigned int (*))0;

    _GetDiskIOsPending = (unsigned int (*)(void))
                 ImportSymbol( nlmHandle, "GetDiskIOsPending" );
    if (!_GetDiskIOsPending)
      _DiskIOsPending = (unsigned int *)
                 ImportSymbol( nlmHandle, "DiskIOsPending" );

    _GetNestedInterruptLevel = (unsigned int (*)(void))
                 ImportSymbol( nlmHandle, "GetNestedInterruptLevel" );
    if (!_GetNestedInterruptLevel)
      _NestedInterruptCount = (unsigned int *)
                 ImportSymbol( nlmHandle, "NestedInterruptCount" );
  }
  if (which == 'q')
  {
    if (_GetNestedInterruptLevel)
      return (*_GetNestedInterruptLevel)();
    if (_NestedInterruptCount)
      return (*_NestedInterruptCount);
  }
  else if (which == 'd')
  {
    if (_GetDiskIOsPending)
      return (*_GetDiskIOsPending)();
    if (_DiskIOsPending)
      return (*_DiskIOsPending);
  }
  return 0;
}

int GetDiskIOsPending(void) { return __GetUnsignedXXX('q'); }
int GetNestedInterruptLevel(void) { return __GetUnsignedXXX('d'); }

/* ===================================================================== */

static int __addremovepollingproc( void (*proc)(void), unsigned long rTag )
{
  static void (*_RemovePollingProcedure)(void (*)(void))
               = (void (*)(void (*)(void)))0x01;
  static int (*_AddPollingProcedureRTag)(void (*)(void), unsigned long)
               = ((int (*)(void (*)(void),unsigned long ))0x01);
  if (_RemovePollingProcedure == ((void (*)(void))0x01))
  {
    unsigned int nlmHandle = GetNLMHandle();
    void *add = ImportSymbol( nlmHandle, "AddPollingProcedureRTag" );
    void *rem = ImportSymbol( nlmHandle, "RemovePollingProcedure" );
    if (!add || !rem)
    {
      if (add)
        UnimportSymbol( nlmHandle, "AddPollingProcedureRTag" );
      if (rem)
        UnimportSymbol( nlmHandle, "RemovePollingProcedure" );
      add = rem = (void *)0;
    }
    _AddPollingProcedureRTag = (int (*)(void (*)(void),unsigned long))add;
    _RemovePollingProcedure = (void (*)(void (*)(void)))rem;
  }
  if (_AddPollingProcedureRTag && _RemovePollingProcedure)
  {
    if (rTag)
      return (*_AddPollingProcedureRTag)( proc, rTag );
    (*_RemovePollingProcedure)( proc );
    return 0;
  }
  return -1;
}

int AddPollingProcedureRTag( void (*proc)(void), unsigned long rTag )
{
  return (!rTag) ? -1 : __addremovepollingproc( proc, rTag );
}

void RemovePollingProcedure( void (*proc)(void) )
{
  __addremovepollingproc( proc, 0 ); return;
}

/* ===================================================================== */
