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 ////////////////////////////////////