cpmfs.hh

//*************************************************************************
//  MODULE : CpmFs - A file system based on the old CP/M design           *
//  AUTHOR : Ron Chernich                                                 *
//  PURPOSE: Provides reudimentary (nonhierarachial) file system for RCOS *
//  HISTORY:                                                              *
//    13-AUG-94 First version                                             *
//    17-AUG-84 DskDrv header file merged                                 *
//*************************************************************************

#ifndef __CPMFS__
  #define __CPMFS__

  #include "rcos.hh"
  #include "kernel.hh"
  #include "message.hh"

  #define MAX_DRIVES    3       // No real limit, but A: B: C: will do..

  class CpmFs;                  // forward reference to File System
  class CpmBdos;                // forward reference for Dos array


  /////////////////////////////////////////////////////////////////////////
  // structure describes salient physical characteristics of the
  // disk disk drive being modelled.
  //
  typedef struct DskParmsTag {
    UINT16 nSecs;               // sectors per track
    UINT16 nTrks;               // total cylinders on disk
    UINT16 nSides;              // no of tracks per cylinder
    UINT16 nSecSize;            // bytes per sector
    BYTE   nAngleInc;           // rotate speed (degrees per mS)
    BYTE   nTrkInc;             // track-to-track access time (mS)
    BYTE   nSettle;             // head settle adelay (mS)
    UINT32 nErrRate;            // soft error rate (one every...)
  } DPB, *PDPB;         


  /////////////////////
  // This parameter list is equivalent to the Digital Research "MAC"
  // macro that was used to build the required logical disk parameters..
  //
  typedef struct macTag {
    BYTE   nLid;        // Logical unit ID (0 = "A:" ...)
    BYTE   nFsc;        // first physical sector number (0 or 1)
    BYTE   nLsc;        // last sector number
    BYTE   nSkf;        // optional sector skew factor
    UINT16 nBls;        // data allocation block size
    UINT16 nDks;        // number of blocks on disk
    UINT16 nDir;        // number of directory entreies
    UINT16 nCks;        // number of "checked" directory entries (for exchangable media)
    BYTE   nOfs;        // track offset of logical track 00
  } DPARAM, *PDPARAM;

  
  //////////////////
  // As I remember it, the CP/M directory entries went something like this.
  // The m.s.b. of the name extension holds file attributes:
  //   fextn[0] set = read only
  //         1      = system (hidden)
  //
  #define PNAME_LEN     8
  #define ENAME_LEN     3
  #define BLOCK_LEN	128
  #define FILENAME_LEN  (PNAME_LEN + ENAME_LEN)
  //
  typedef struct deTag {
    BYTE nUsrNmbr;              // 0xe5 = erased, else user number (zero)
    char fname[PNAME_LEN];      // File name
    char fextn[ENAME_LEN];       // File extension
    BYTE nExtent;               // logical extent of this entry
    BYTE nRes1;                 // internal usage
    BYTE nRes2;                 // internal usage
    BYTE nRcnt;                 // BLOCK_LEN byte record num in use in last block
    BYTE nAllocBlk[16];         // Disk blocks allocated to this extent
  } DIRENT, *PDIRENT;
 
  
  ///////////////
  // constants define all intermediate operations in the CpmBdos (below)
  // that are called via the CpmBdos::NxtFnc public member.
  //
  enum NXTFNC { LOG1,  STAT1, CLOSE1, CLOSE2,
                OPEN1, OPEN2, CREAT1, CREAT2, CREAT3,
                READ0, READ1, WRITE0, WRITE1, WRITE2,
                FIND1, FIND2, RENAM1, RENAM2, REMOV1, REMOV2
              };

  /////////////
  // Permissable high level commands for the Disk Units via their
  // "channel" controller.
  //
  enum CHCMD { DD_CALIBRATE, DD_READ, DD_WRITE, DD_FORMAT };


  ///////////////
  // a CP/M File Control Block. While it "uses" the Directory entry struct,
  // certain bytes have different meanings from those held on the disk.
  // Note that the target buffer for the transfer is an extension of the
  // original - added to permit totally encapsulated operation requests.
  //
  typedef struct fcbTag {
    DIRENT de;                  // A.A. but nUsrNmbr = drive ID
    BYTE   nCurRec;             // current record for sequential read/write
    BYTE   nRandom[3];          // record number (0..65535)
    char  *pBuf;                // address for transfer
  } FCB, *PFCB;

  //////////////////
  // We plan to pass references to FCB's around using the asynchronous
  // "post" mechanism, but this ends up "deleteing" the passed object
  // if passed by reference, so this little double indirection thingy
  // preserves the FCB (as far as TxPost is concerned) but allows the
  // class  to make changes back to the referenced item..
  //
  typedef struct fcbRefTag {
    PFCB pFcbRef;
  } FCBREF, *PFCBREF;
  
  
  ///////////////
  // A "Transfer Request" class; being an object created for each quantum
  // of disk activity.  It gets posted to the appropriate drive unit and
  // enters its FIFO queue.  When actioned, the CpmBdos pointer is used
  // to call the member function that knows (via a constant placed in the
  // object by the previous member function) which member function to pass
  // the object along to next.
  //
  // Normally, the  pointer will be a copy of the buffer address in
  // the FCB, but making it separate allows the BDOS to present the disk
  // driver with the address of its internal buffers without danger of the
  // user code accidentally gaining access to them.
  //
  // Note: stuff which class  is not concerned with
  //       is hidden away - including C::tor and D::tor.
  //
  class XfReq {
    friend CpmFs;
    friend CpmBdos;
  protected:
    PFCB   pFcb;                // instance of an FCB in the PCB or BDOS
    NXTFNC ivec;                // what to do next (internal to CpmBdos)
    BYTE   nRetry;              // error retry count
    XfReq *pNxt;                // any "postponed" assosociated Xfer
   ~XfReq (void) { }            // only friends can destroy
    XfReq (UINT16 = 0);         //    or create one of these..
    XfReq (UINT16, PFCB);
  public:
    UINT16   uProc;             // transfer initiator PID (or BDOS ID)
    CHCMD    cmnd;              // What we want the disk channel to do
    UINT16   nTrk, nSec, nSid;  // Cylinder, sector, head
    char    *pDma;              // transfer buffer pointer
    BOOL     bRes;              // Outcome of channel operation
    CpmBdos *pFnCb;             // class that handeles completed xfers
  };


  /////////////////////////////////////////////////////////////////////
  // Interface to the file system based on our old friend, the "port".
  // Note how each file system operation takes a single XfReq pointer as
  // input.  The Single instance of class CpmFs receives requests for
  // FS operations from user processes as messages.  It merely creates
  // a new XfReq object, filling in the originating PID and FCB pointer,
  // determines which disk "unit" the request is for, checks that that
  // unit is "on-line" and then calls the appropriate (protected) member
  // function.
  // 
  // ALL calls to the File System result in the caller process
  // becomming blocked.  When the FS service is deemed to be complete,
  // a result message will be posted to the PID (unblocking it) and the
  // XfReq object killed off.  The last member function of CpmBdos to
  // be passed the XfReq pointer does this.
  //
  // Note: it's each user process's responsibility to ensure the FCB
  // is correctly initialised (by a successful call to FsOpen or FsCreat).
  // The FS uses these FCB's to retain working data during file I/O
  // operations.  Looks to me now (in hindsight) that if you diddle with
  // these, and you can trash the file system, big time. Funny, I don't
  // recall that ever happening though (?)
  //
  class CpmFs : public port {
  private:
    CpmBdos *pUnit[MAX_DRIVES];         // Disk File System instances
  public:
    CpmFs (UINT16, UINT16, Knl*);       // register with kernel
   ~CpmFs (void);                       // murder all file systems
    void RxPort (PMSG);                 // supply virtual member of "port"
    void MountDrive (UINT16, char, DPB&, DPARAM&); // Instatiate new Bdos
  };


  ///////////////////////////////////////////////////////////////////////
  // Here is the actual, re-entrant File System class.  In olden days
  // (BC++), we would write this as a set of subroutines with lists and
  // arrays to hold dynamic, unit variable data.  To-day, we make it a
  // class and instantiate it once for each physical disk configured in.
  // A more flexible scheme would have a list of devices rather than a
  // simple array (as used in class CpmFs) permitting unlimited, dynamic
  // mount and un-mount of devices at run time.
  //
  // Once a physical disk is determined to be "on-line" (ie, the host file
  // emulating a disk unit, or a real disk unit, even, has started up ok),
  // an instance of this class is created giving it the disk characteristics
  // and some required file system parameters (number of directory entries,
  // block size, etc..).  It is also given the Kernel message dispatcher
  // address and the ID of the disk unit it will be responsible for.
  //
  // The sequence of events then goes like this:
  // 1. A process invokes an FS operation and is blocked.  This results in
  //    a message to the CpmFs instance.
  // 2. The  creates an XfReq instance, fills in the FCB and PID
  //    data from the message, identifies the target unit from the FCB
  //    (placing a pointer to it in the XfReq), destroys the message and
  //    calls the appropriate member of the array of the  instances
  //    with the XfReq pointer.  The  takes no further part in the
  //    operation.
  // 3. The  instance formulates an appropriate disk transfer
  //    (track, sector etc) inserting this into the  object along
  //    with a constant identifying which member function will deal with
  //    the completed transfer.  It then posts the  to "its" disk
  //    channel handler.
  // 4. The  arrives at the channel handler and is placed in a FIFO
  //    queue.  When it finally gets actioned, a result code is placed in
  //    the  object and the  member of the  instance
  //    called with the  as parameter via the object pointer.
  // 5. Using the constant placed in the XfReq by the previous member func,
  //    CpmBdos::NxtFunc passes the XfReq to an internal, private function
  //    which will do one off:
  //      . Build a reply message and post it to the PID, then destroy the
  //        XfReq object.  This completes the cycle, un-blocking the Proc.
  //      . Alter the XfReq params (eg when a directory block just read
  //        needs to be re-written) and post to the channel handler again.
  //      . Spawn another XfReq (as in the case of a read/write operation
  //        that needs to re-access the directory to fetch the next extent),
  //        "attaching" the XfReq pointer to the new one, so that operations
  //        on it can continue after the intermediate operation has finished.
  //        This allows infinite (!?) nesting of channel calls. 
  // 6. A message arrives back at the originating process with the result
  //    of the requested operation, effectively un-blocking it to continue
  //    processing as dictated by internal logic.  The FCB it passed in the
  //    first place has been updated and data transfered to/from the buffer
  //    set in the FCB.  Simple, right?
  //   
  class CpmBdos {
    friend class CpmFs;
  private:
    Knl   *pTx;         // copy of CpmFs Message dispatcher
    UINT16 nId;         // copy of CpmFs unique identifier
    UINT16 nChan;       // Channel ID for XfReq messages
    BYTE   nFsc;        // first physical sector number (0 or 1)
    BYTE   nLsc;        // last sector number
    BYTE   nSkf;        // optional sector skew factor
    UINT16 nBls;        // data allocation block size
    UINT16 nDks;        // number of blocks on disk
    UINT16 nDir;        // number of directory entreies
    UINT16 nCks;        // number of "checked" directory entries (for exchangable media)
    BYTE   nOfs;        // track offset of logical track 00
    BYTE   nAlb;        // No of allocation blkd/extent (8 or 16)
    BYTE   DirBuf[BLOCK_LEN]; // buffer for directory operations
    BYTE  *pAlv;        // Allocation Vector (dynamically allocated)
    BYTE  *pCsv;        // Checksum vector (for exchangable disks)
    BYTE  *pXlt;        // Sector skew table (NULL if no skew)
    DPB   *pDpb;        // physical characteristics for this unit
    void   Open1  (XfReq*);                     // looping step in Open
    void   Open2  (XfReq*, BOOL);               // termination for Open
    void   Close1 (XfReq*);                     // found file dir block
    void   Close2 (XfReq*);                     // check update of dir blk
    void   Creat1 (XfReq*);                     // end exist scan check
    void   Creat2 (XfReq*);                     // looping step in Creat
    void   Creat3 (XfReq*);                     // Success step in Creat
    void   Stat1  (XfReq*);                     // looping step in Stat
    void   Renam1 (XfReq*);                     // looping step in Rename
    void   Renam2 (XfReq*);                     // success step in Rename
    void   Remov1 (XfReq*);                     // looping step in Delete
    void   Remov2 (XfReq*);                     // success step in Delete
    void   Find1  (XfReq*);                     // first find success/fail
    void   Find2  (XfReq*);                     // find next looping step
    void   Read1  (XfReq*);                     // read complete step
    void   Write1 (XfReq*);                     // write complete step
    void   Write2 (XfReq*);                     // write open new extent step
    void   Write3 (XfReq*);                     // write new extent intermediate
    void   DoRetry(XfReq*);                     // disk error recovery
    void   LogDisk  (void);                     // build Allocation and Checksum vectors
    void   LogDisk1 (XfReq*);                   // loop building Allocation vector
    UINT16 SecXlt (UINT16);                     // perform skew translation
    UINT16 GetFreeBlk (void);                   // locate first unused allocation block of unit
    void   AllocBlk (UINT16, BYTE = 1);         // set/reset numbered bit of disk allocation vector 
    void   ReGenerate (XfReq*, UINT16 = 0);	// copy passed request and post the copy
    void   DeAllocBlk (UINT16 n) { AllocBlk(n, 0); }
    void   DirScanPrep (FCB*, UINT16*, UINT16*);
    void   BlkDecode (UINT16, UINT16, UINT16*, UINT16*);
  protected:
    void   Open  (XfReq*);                      // locate by name if exists (wildcard ok)
    void   Creat (XfReq*);                      // Create if name unique and entry available
    void   Close (XfReq*);                      // Close file, update Dir.
    void   Renam (XfReq*);                      // name change (not open!)
    void   Remov (XfReq*);                      // remove file from system
    void   Stat  (XfReq*);                      // file size and protection
    void   Find  (XfReq*, UINT16);              // directory name scanner
    void   Read  (XfReq*, UINT16);              // Seq/Random read op.
    void   Write (XfReq*, UINT16);              // Seq/Random write op.
    CpmBdos (DPB&, DPARAM&, UINT16);            // only friends can create
  public:
   ~CpmBdos(void);
    void   NxtFunc (XfReq*);                    // the only public entry point
  };
  
  
  /////////////////
  // Disk transfer requests are held in a FIFO queue based on (guess what?)
  // We will use what Taligent term "implemenmtation inheritance", ie a
  // private base class.  Thie code rests with the Disk driver stuff..
  //
  class TreqstQ : private DblList {
    UINT16 nInq;
  public:
    TreqstQ (void) : nInq(0) { }
   ~TreqstQ (void) { }
    XfReq   *Get   (UINT16 = 0);
    XfReq   *Peek  (UINT16 = 0);
    void     Purge (UINT16 = 0);
    void     PurgeAll (void);
    UINT16   GetLen (void)  { return nInq; }
    void     Put (XfReq *p) { DblAppend((void*)p, sizeof(XfReq)); ++nInq; }
  };
  
  ////////////////
  // for safety, we sub-class off the disk device transfer FIFO queue
  // preventing the animator form doing anthing bad with queue data..
  //
  class TransQ {
    TreqstQ *pTQ;
    XfReq   *pX;
  public:
    TransQ (TreqstQ *p) : pTQ(p) { }
   ~TransQ (void) { }
    void   Peek (UINT16 n) { pX = pTQ->Peek(n);  }
    UINT16 GetLen (void) { return pTQ->GetLen(); }
    UINT16 GetPid (void) { return pX->uProc;     }
    UINT16 GetTrk (void) { return pX->nTrk;      }
    UINT16 GetSec (void) { return pX->nSec;      }
    UINT16 GetSid (void) { return pX->nSid;      }
    BOOL   IsRead (void) { return (pX->cmnd == DD_READ) ? TRUE : FALSE; }
  };

  
  ////////////
  // This is the sequence of events in performing a disk transfer. Normally,
  // we must perform all 4, in sequence unless the heads already happen to
  // be on track, in which case we can go direct from IDEL to TRANSFER..
  //
  enum ddState { DD_IDLE, DD_SEEK, DD_SETTLE, DD_WAIT, DD_TRANSFER };
  
  /////////////////
  // This class simulates a disk drive using preset constants, based on the
  // mS granularity system clock ticker, in terms of physical attributes of:
  //
  //    * Track to track seek time  
  //    * Head settle time
  //    * Rotational Latency
  //    * Soft error rate
  // 
  // Channel requests arrive via the usual message service.  The model
  // actions these in FIFO, moving the "head" missile to the specified
  // "track", delaying for the head settle period, waiting until the
  // required "sector" "arrives" under the "head", then calling the Read/
  // Write routine to do the transfer to the specific address.  Finally,
  // the CpmBdos::NxtFunc member is called (again via the Object pointer
  // in the message), passing it the transfer request object pointer.  A
  // parameter in the object indicates what routine is to precess the
  // completed transfer.  If this is the end of the chain for the transfer,
  // the XfReq object is destroyed and the blocked process sent a message
  // to resume, otherwise the params are set for another transfer and the
  // message re-posted to join the FIFO queue again.
  //
  // This sounds convoluted, but it enables us to implement a BDOS which
  // is re-enterant, since all transfers are encapsulated totally in a
  // XfReq object which rattles around independant of, and inter-leaved
  // with, all other such objects without multi-threading or interrupts.
  //
  // One of these objects should be instantiated for each drive unit to be
  // modelled, but NOTE that it is important to use the same parameter
  // struct when initialising the corresponding File System object so that
  // it "knows" the size of its "drive".
  //
  class DskModel : public port {
  private:
    ddState stage;              // stage of transfer (see enum type)
    BYTE    nLid;               // logical unit ID (0 = "A:" etc)
    BYTE    nAngleInc;          // rotate degrees per mS
    BYTE    nTrkInc;            // mS per head step
    BYTE    nSettle;            // mS to settle after step
    BYTE    nSecAngle;          // degrees per sector
    UINT16  nCurTrk;            // current head location
    UINT16  nCurAngle;          // current angle of sector index "hole"
    UINT32  nErrRate;           // soft error rate (one every...)
    UINT32  nOpCnt;             // count of operations for error emulation
    UINT32  lLastTime;          // gated clock state memory
    UINT32  lHoldTime;          // state memory for delayed events
    int     fd;                 // handle for host "disk" data file
    BOOL    bOk;                // TRUE if disk is on-line and operational
    UINT16  nBps;               // bytes per sector
    UINT32  nBpt;               // bytes per cylinder
    UINT32  nBpd;               // total bytes on "disk"
    TreqstQ ChanQ;              // FIFO queue of requests for this unit
    BOOL    Read (void);        // read to buffer BLOCK_LEN bytes
    BOOL    Write (void);       // write from buffer BLOCK_LEN bytes
    long    CalcOffset (void);  // get offset for host file transfer
  public: 
    DskModel (UINT16, UINT16, Knl*, char*, DPB&, BYTE);
   ~DskModel (void);
    void Scheduler (void);               // called in "main loop"
    void RxPort (PMSG);                  // incomming message handler
    BOOL IsOnLine (void) { return bOk; } // check on c::tor success
  };


#endif

/////////////////////////////////// eof ////////////////////////////////////