rcos.cc

//*************************************************************************
//  MODULE : RCOS main module - control loop for the supervisor.          *
//  AUTHOR : Ron Chernich                                                 *
//  PURPOSE: Create, run and destroy the RCOS operating system under a    *
//           host operating system.                                       *
//  HISTORY:                                                              *
//   04-JUN-93 Dignified with a title block from the "T8" candidate code. *
//   29-OCT-93 Individual pop-ups changed to a GP Message Box function.   *
//   01-FEB-94 Dependency on integer size removed by typedef'ing bit lens *
//   02-FEB-94 Anti-fascist mode window re-paint added for Xlib support   *
//   25-AUG-94 All animation window responsibility given to Animator.     *
//   15-MAR-95 User terminals managed through a class for beter config.   *
//   22-MAR-95 Configurable options added via  object        *
//*************************************************************************


#include "rcos.hh"
#include "message.hh"
#include "exec.hh"
#include "kernel.hh"
#include "app.hh"
#include "tty.hh"
#include "mmu.hh"
#include "anim.hh"
#include "timer.hh"
#include "display.h"
#include "dev-disp.hh"
#include "cpmfs.hh"
#include "prefs.hh"


///////////////
// Button style for pop-up message box
//
enum mbStyle { MB_OK, MB_OKCANCEL, MB_YESNO, MB_YESCANCEL };
enum mbRetrn { ID_OK = 200, ID_CANCEL, ID_YES, ID_NO };

///////////////
// Globals.. the simple App instantiation makes it all start happening,
//
char *pszInitl =  { "RCOS - Press F1 for help" };
char *pszTitle =  { "RCOS - Ron's OS Simulator Mk IIb" };
App  Rcos(pszInitl);

//////////////////////
// this struct combines the physical attributes of an IBM 3740, 8"
// single side, single density disk and a Schugart Associates 880 drive.
//
static DPB _IBM3740 = {
        26,     // sectors (number from one)
        77,     // tracks (number from zero)
        1,      // sides
        128,    // bytes per sector
        2,      // mS per rev (~ 360 RPM)
        25,     // mS track to track
        10,     // mS settle
        1000    // soft (recoverable) error rate
};
                
/////////////////
// This struct says how to partition a 3740 diskette into the CP/M
// standard Single Side, Single Density (SSSD) - which was the only
// guaranteed interchange format in the late 70's.
//
static DPARAM _SSSD = {
        0,      // Unit number
        1,      // first sec number
        26,     // no of sectors
        6,      // sector skew factor
        1024,   // diak allocation block size (bytes)
        234,    // total blocks on disk
        64,     // no of directory entries
        0,      // none checked (treat as fixed disk)
        2       // reserved (system) tracks
};
       

///////////////////////////////////////////////////////////////////////
// local protos in order of appearance..
//
static UINT16 MessageBox (char*, char**, UINT16);
static void   PaintCtrls (INT16);
static void   PaintFixed (void);
static void   DestroyCtrls (INT16);

#ifdef DOS_ENV
       void   main (void);		// g++ doesn't like it
#endif


///////////////////////////////////////////////////////////////////////
// popup a Windows-like message box with supplied heading and text,
// with appropriate controls.  The size of the box depends on the
// passed array of string pointers.
// RETURNS: ID of button pressed to close the window
//
UINT16 MessageBox (char *szTitle, char **pszText, UINT16 uStyle)
{
  UINT32 lParam;
  UINT16 msg, wParam;
  INT16  n, x, y, x1, y1, x2, y2;
  static char *pLab[] = { "&Ok", "&Cancel", "&Yes", "&No" };

  for (x = 0, y = 0; pszText[y]; y++)
    x = MAX(x, (INT16)GfxTextExtent(pszText[y]));
  y2 = SYS_Height + PB_HT + (GfxTextHeight() * ++y) + 16;
  x2 = 16 + ((uStyle > MB_OK) ? (PB_WID << 1) + 8 : PB_WID);
  x2 = MAX(x2, x+16);
  y1 = (WIN_Y2 - WIN_Y1 - y2) >> 1;
  x1 = (WIN_X2 - WIN_X1 - x2) >> 1;
  if (uStyle == MB_OK)
    Rcos.AppChildWin(RC_Modal, szTitle, x1, y1, x1+x2, y1+y2);
  else
    Rcos.AppChildWin(RC_Modal, szTitle, x1, y1, x1+x2, y1+y2, _Red);
  Mickey.AutoPointer(x1, y1, x1+x2, y1+y2);
  switch (uStyle) {
    case MB_OK:
      Rcos.AppCreate(RC_PushBtn, ID_OK, pLab[0],
        x1 + ((x2 - PB_WID) >> 1), y1 + y2 - PB_HT - 8, PB_WID, PB_HT);
      break;
    case MB_OKCANCEL:
      Rcos.AppCreate(RC_PushBtn, ID_CANCEL, pLab[1],
        x1 + 8, y1 + y2 - PB_HT - 8, PB_WID, PB_HT);
      Rcos.AppCreate(RC_PushBtn, ID_OK, pLab[0],
        x1 + x2 - PB_WID - 8, y1 + y2 - PB_HT - 8, PB_WID, PB_HT);
      break;
    case MB_YESCANCEL:
      Rcos.AppCreate(RC_PushBtn, ID_CANCEL, pLab[1],
        x1 + 8, y1 + y2 - PB_HT - 8, PB_WID, PB_HT);
      Rcos.AppCreate(RC_PushBtn, ID_YES, pLab[2],
        x1 + x2 - PB_WID - 8, y1 + y2 - PB_HT - 8, PB_WID, PB_HT);
      break;
    case MB_YESNO:
      Rcos.AppCreate(RC_PushBtn, ID_NO, pLab[3],
        x1 + 8, y1 + y2 - PB_HT - 8, PB_WID, PB_HT);
      Rcos.AppCreate(RC_PushBtn, ID_YES, pLab[2],
        x1 + x2 - PB_WID - 8, y1 + y2 - PB_HT - 8, PB_WID, PB_HT);
      break;
  }
  GfxSetClip(x1, y1, x1+x2, y1+y2);
  y = y1 + SYS_Height + 8;
  n = 0;
  while (pszText[n]) {
    x = x1 + ((x2 - GfxTextExtent(pszText[n])) >> 1);
    GfxText(x, y, pszText[n], GFX_Transparent);
    y += GfxTextHeight();
    ++n;
  }
  GfxClrClip();
  Mickey.ShowPointer();
  while (Rcos.AppGetMsg(msg, wParam, lParam) == FALSE)
    ;
  Rcos.AppDestroy(RC_PushBtn, ID_OK);
  Rcos.AppDestroy(RC_PushBtn, ID_CANCEL);
  Rcos.AppDestroy(RC_PushBtn, ID_NO);
  Rcos.AppDestroy(RC_PushBtn, ID_YES);
  Rcos.AppCloseWin();
  KbdIn.KeyFlush(_ALT);
  KbdIn.KeyFlush(_ASC);
  return wParam;
}

//////////////////
// Paint fixed supervisor objects.
//
static void PaintFixed (void)
{

  GfxRect(STRIP_X1, STRIP_Y1, STRIP_X2, STRIP_Y2, GFX_Frame, _Black);
  GfxRect(STRIP_X1, STRIP_Y1+1, STRIP_X2, STRIP_Y2-1, GFX_Fill, _White);
  GfxTextColorBg(_Yellow);  
  GfxPattern(GFX_HalfTone);
  GfxRect(STRIP_X1, STRIP_Y2+1, STRIP_X2, GFX_Ymin-1, GFX_Fill, _White);
  GfxPattern(GFX_Solid);
  GfxRect(CON_X1-1, CON_Y1-1, CON_X2+1, CON_Y2+1, GFX_Frame, _Black);
}        
////////////////////
// Initialize Control panel gadgets. Ideally, these should live in a
// resource segment of the EXE file as binary data which is disgarded
// after use.  Oh well..
//
static void PaintCtrls (INT16 n)
{
  BOOL bMx[4];
 
  PaintFixed();
  bMx[0] = bMx[1] = bMx[2] = bMx[3] = FALSE;
  bMx[n] = TRUE;
  Rcos.AppCreate(RC_PushBtn, PB_RUN, "&Run", PB_X1,PB_Y1,PB_WID,PB_HT);
  Rcos.AppCreate(RC_PushBtn, PB_PAUSE, "&Pause", PB_X1,PB_Y1+PB_DY, PB_WID, PB_HT);
  Rcos.AppCreate(RC_PushBtn, PB_STEP, "&Step",  PB_X1,PB_Y1+(2*PB_DY),PB_WID,PB_HT);
  Rcos.AppCreate(RC_PushBtn, PB_ABOUT, "&About", PB_X1,PB_Y1+(3*PB_DY),PB_WID,PB_HT);
  Rcos.AppCreate(RC_RadioBtn, RB_PM, "&CPU Scheduler", 20, STRIP_Y0, bMx[0]);
  Rcos.AppCreate(RC_RadioBtn, RB_MM, "&Memory Manager", 150, STRIP_Y0, bMx[1]);
  Rcos.AppCreate(RC_RadioBtn, RB_RM, "R&esource Manager", 300, STRIP_Y0, bMx[2]);
  Rcos.AppCreate(RC_RadioBtn, RB_DM, "&Devices", 460, STRIP_Y0, bMx[3]);
  Rcos.AppCreate(RC_CheckBox, CB_CONF, "Co&nfirm", 560, STRIP_Y0, TRUE);
}

////////////////////
// Kill off all controls
//
static void DestroyCtrls (INT16 nWin)
{
  if (nWin == 3) {
    Rcos.AppDestroy(RC_RadioBtn, RB_TTY1);
    Rcos.AppDestroy(RC_RadioBtn, RB_TTY2);
  }
  Rcos.AppDestroy(RC_PushBtn, PB_RUN);
  Rcos.AppDestroy(RC_PushBtn, PB_PAUSE);
  Rcos.AppDestroy(RC_PushBtn, PB_STEP);
  Rcos.AppDestroy(RC_PushBtn, PB_ABOUT);
  Rcos.AppDestroy(RC_RadioBtn, RB_PM);
  Rcos.AppDestroy(RC_RadioBtn, RB_MM);
  Rcos.AppDestroy(RC_RadioBtn, RB_RM);
  Rcos.AppDestroy(RC_RadioBtn, RB_DM);
  Rcos.AppDestroy(RC_CheckBox, CB_CONF);
}

////////////////////////////////////////////////////////////////////////////
//                                                                        //
//  RCOS is controlled from here. To give the appearence of concurrency   //
//  without pre-emptive interrupts, the three operations in the main      //
//  program loop should do as little as possible before yielding to each  //
//  other.  Actual control (supervision) of the OS is ALL performed here. //
//  Supervisor gadgets set booleans which control running and stepping.   //
//  The Kernel runs on Messages.  These are posted into its queue by the  //
//  Operator's Console Command parser, Device Drivers and Kernel itself   //
//  (it's usefull to post messages to youself!)  On each pass through the //
//  loop, Kernel extracts and actions one message - if there are any.  If //
//  there are no messages in the queue, the Kernel scheduler gets control //
//  and runs the P-code interpreter to execute a single p-code.           //
//                                                                        //
////////////////////////////////////////////////////////////////////////////
//
void main (void)
{
//  rect   r;
  UINT32 lParam;
  UINT16 msg, wParam, nRbLast;
  BOOL   bState,
         bRun = FALSE,
         bStep = FALSE,
         bFirst = TRUE,
         bActive = TRUE,
         bReTitle = FALSE;
  static char *pszDisp[] = {"cpu scheduler", "memory manager",
                            "resource manager", "devices", NULL };
  static char *pszExit[] = {"RCOS is about to terminate.", " ",
                            "Are you sure you want to exit now?",
                             NULL };
  static char *pszHelp[] = {"This program is CUA complient.", " ",
                            "Use the mouse, or press ALT+Underlined char to",
                            "activate a control.  Press ALT+F4 to terminate.",
                             NULL };
  static char *pszRcos[] = {"RCOS - A multi-tasking OS simulator.",
                            "Written by Ronald A Chernich (1993)",
                            "for the Department of Maths and Computing",
                            "of the University of Central Queensland.",
                             NULL };


  if (Rcos.AppRunning()) {
    //
    // Since the application framework seems to be running, we can
    // instantiate the Kernel, followed by all the device drivers,
    // passing their constructors a pointer to the Kernel.  They will
    // notify it of their existance by message passing.  This greatly
    // simplifies design changes.
    //
    Preferences Prefs;
    Knl  Kernel(Prefs.GetPrefInt(PREFS_QUAN, PCD_QUANTUM));
    tty  OpCon(ID_TTY0, CLS_VDU, &Kernel, 
         rect(CON_X1, CON_Y1, CON_X2-CON_X1, CON_Y2-CON_Y1),
         _Black, _BrightWhite);
    UINT16 nTerms = Prefs.GetPrefInt(PREFS_TTYS, NUM_TTY);

    if ( nTerms == 0 )
      nTerms=2;

    Mmu      MemMan(ID_MMU, CLS_SysRes, &Kernel);
    CpmFs    FileSys(ID_FS, CLS_SysRes, &Kernel);
    Animate  Ani(ID_ANIM,   CLS_SysRes, &Kernel, nTerms);
    DskModel ADrive(ID_DISK_A, CLS_FSF, &Kernel, "CPMA", _IBM3740, _SSSD.nLid);
    if (ADrive.IsOnLine())
      FileSys.MountDrive(ID_DISK_A, 'A', _IBM3740, _SSSD);
      
    //_SSSD.nLid = 1;
    //DskModel BDrive(ID_DISK_B, CLS_FSF, &Kernel, "CPMB", _IBM3740, _SSSD.nLid);
    //if (BDrive.IsOnLine())
    //  FileSys.MountDrive(ID_DISK_B, 'B', _IBM3740, IBM_SSSD);

    Mickey.HidePointer();
    {
      char st[32];
      Prefs.GetPrefStr(PREFS_VIEW, pszDisp[0], st, sizeof(st));
      UINT16 nInit = 0;
      while (pszDisp[nInit])
        if (strcmp(pszDisp[nInit], st))
          ++nInit;
        else
          break;
      if (NULL == pszDisp[nInit])
        nInit = 0;
      PaintCtrls(nInit);
      Ani.SetCurWin(nInit, bReTitle);
      nRbLast = RB_PM + nInit;
    }
    if (Ani.GetCurWin() == CPU_DISP) {
      MSG mess = message(ID_NULL, ANI_REFRESH, Ani.GetCurWin());
      Kernel.SendMsg(ID_ANIM, &mess);
    }
    OpCon.SetMode(TTY_Active | TTY_UConly | TTY_UseANSI | TTY_IBar);
    OpCon.ReFresh();
    Mickey.ShowPointer();
    while (bActive) {
      //
      // RCOS called here - each call to Kernel will do one of:
      //  * Dispatch a message (thus running a device driver).
      //  * Perform task switch if quantum expired.
      //  * Execute a pcode, task switching if pcode calls "slow" operation.
      //
      // We start the clock before the kernel code runs and stops it
      // again after the Kernel has had its time slice.  This allows
      // the Kernel to perform reasonably accurate time accounting.
      // Each emulated disk drive also get some time to spin it wheels
      // and see if any pending operation has completed...
      //
      if (bRun || bStep) {
        Clock.Start();
        bStep = Kernel.Run();
        Clock.Stop();
        ADrive.Scheduler();
        //BDrive.Scheduler();
      }
      if (Rcos.AppGetMsg(msg, wParam, lParam)) {
        switch (msg) {
          case RC_Close:
            if (Rcos.AppCBstate(CB_CONF, bState) == FALSE)
              bActive = FALSE;
            else
              bActive = (bState ? (BOOL)(ID_CANCEL ==
                MessageBox("Information!", pszExit, MB_YESCANCEL)) : FALSE);
            break;
          case RC_Click:
            switch (wParam) {
              case PB_RUN:
                bRun = TRUE;
                Rcos.AppTitle("Running..");
                if (bFirst) {
                  Kernel.Startup();
                  bFirst = FALSE;
                }
                break;
              case PB_PAUSE:
                bRun = FALSE;
                bStep = FALSE;
                Rcos.AppTitle("Paused..");
                break;
              case PB_STEP:
                bStep= TRUE;
                bRun = FALSE;
                Rcos.AppTitle("Step Mode Active.");
                break;
              case PB_ABOUT:
                MessageBox("About the Author", pszRcos, MB_OK);
                break;
            default:
              if ((wParam >= RB_PM) && (wParam <= RB_TTY_BASE + nTerms - 1))
                nRbLast = wParam;
              if ( ((wParam - RB_PM) != (UINT16)Ani.GetCurWin()) &&
                   ((wParam >= RB_PM) && (wParam <= RB_DM)) ) {
                Ani.SetCurWin((wParam - RB_PM), bReTitle);
                MSG mess = message(ID_NULL, ANI_REFRESH, Ani.GetCurWin());
                Kernel.SendMsg(ID_ANIM, &mess);
              }
            }
            bReTitle = ((wParam <= PB_STEP) ? TRUE : FALSE);
            break;
          case RC_Paint:
            PaintFixed();
            Ani.SetCurWin(Ani.GetCurWin(), bReTitle);
            OpCon.ReFresh();
            Rcos.pMain->Refresh();
            break;
          case RC_HelpKey:
            Rcos.AppTitle(pszTitle);
            MessageBox("Help", pszHelp, MB_OK);
            break;
        }
      }
      //
      // Dispatch any user keystrokes to one of the tty devices based on
      // the current radio button setting.  Note that unless the TTY's
      // are visible, they can't get input (reasonable).  Changing the
      // display from the "Devices" automatically resets input to the
      // operator's console, as does the "Devices" button at all times..
      //
      if (KbdIn.KeyHit(_ASC)) {
        MSG mess(ID_Kernel, KM_KeyPress, (UINT16)KbdIn.KeyGet(_ASC));
        UINT16 wDest = (nRbLast >= RB_TTY_BASE) ?
                       ID_TTY_BASE + (nRbLast - RB_TTY_BASE) : ID_TTY0;
        Kernel.PostMsg(wDest, &mess);
      }
    }
    DestroyCtrls((UINT16)Ani.GetCurWin());
  }
}

/////////////////////////////////// EOF ////////////////////////////////////