dskdrv.cc
//*************************************************************************
// MODULE : SimDsk - Conceptual Disk Driver for all types of drives. *
// AUTHOR : Ron Chernich *
// PURPOSE: This object Provides the Intermediate level IO routines *
// (select, seek, etc) to interface between the File System and *
// multiple instances of all types of physical disk units. *
// HISTORY: *
// 13-AUG-94 First version *
// 17-AUG-94 Animation features added *
//*************************************************************************
#include "cpmfs.hh"
#include
#ifdef _DOS_ENV
#include
#include
#include
#endif
#ifdef UNIX
#include
#include
#include
#endif
#include
///////////////////////////////////////////////////////////////////////////
// First we have the im plemenation of a FIFO queue that holds pending
// disk transfer requests..
//
XfReq* TreqstQ::Get (UINT16 n)
{
XfReq *p = Peek(n);
if (p) {
DblDelete();
--nInq;
}
return p;
}
XfReq* TreqstQ::Peek (UINT16 n)
{
XfReq *p = (XfReq*)DblGetHead();
if (n)
while (--n)
if (NULL == (p = (XfReq*)DblGetNext()))
break;
return p;
}
void TreqstQ::Purge (UINT16 n)
{
XfReq *p = Get(n);
}
void TreqstQ::PurgeAll (void)
{
while (nInq)
Purge();
}
///////////////////////////////////////////////////////////////////////////
// The disk device is simulated through a host OS sequential file of the
// passed mount name. If the file (created if necessary) length does not
// match the "disk" parameters, it is "initialized" by writing all "0xE5"
// records out to the required length. Info conveyed in the DPB are stored.
//
DskModel::DskModel (UINT16 id, UINT16 cls, Knl *pK, char *pSt, DPB& dsk, BYTE n) :
port(id, cls, pK), nCurTrk(0), nCurAngle(0), nOpCnt(0), lHoldTime(0),
nLid(n), bOk(FALSE), stage(DD_IDLE)
{
char stName[128], *cp;
strcpy(stName, pSt);
cp = strrchr(stName, '.');
if ((NULL == cp) || (strlen(stName) - (cp - stName) > 4))
strcat(stName, ".dsk");
#ifdef UNIX
fd = open(stName, O_CREAT | O_RDWR, S_IREAD | S_IWRITE );
#endif
#if defined(BC20) || defined(BC31)
fd = open(stName, O_CREAT | O_BINARY | O_RDWR, S_IREAD | S_IWRITE);
#endif
#if !defined(BC31) && !defined(BC20) && !defined(UNIX)
fd = open(stName, _O_CREAT | _O_BINARY | _O_RDWR, _S_IREAD | _S_IWRITE);
#endif
if (fd > 0) {
struct stat info;
message Msg(uID, ANI_DCREAT, nLid, (void*)&dsk.nTrks);
stat(stName, &info);
long nBlks = dsk.nSecs * dsk.nTrks * dsk.nSides;
if (info.st_size != (long)(nBlks * dsk.nSecSize)) {
char *pBuf = new char[dsk.nSecSize];
if (NULL == pBuf)
return;
memset(pBuf, 0xe5, dsk.nSecSize);
for (int i = 0; i < nBlks; i++)
if ((int)dsk.nSecSize != write(fd, pBuf, dsk.nSecSize))
return;
//_commit(fd);
DELETE_ARRAY pBuf;
}
nBps = dsk.nSecSize;
nBpt = nBps * dsk.nSecs * dsk.nSides;
nBpd = nBpt * dsk.nTrks;
nSecAngle = (BYTE)(360 / (dsk.nSecs + 1));
nAngleInc = dsk.nAngleInc;
nTrkInc = dsk.nTrkInc;
nSettle = dsk.nSettle;
nErrRate = dsk.nErrRate;
lLastTime = Clock.GetTicks();
pTx->SendMsg(ID_ANIM, &Msg);
bOk = TRUE;
}
}
//////////////
// Close our associated disk file.
//
DskModel::~DskModel (void)
{
if (fd > 0)
close(fd);
}
/////////////
// Calculate offset for a transfer, doing rudimentary validation
// RETURNS -1 .. record out of range, else record number.
//
long DskModel::CalcOffset (void)
{
XfReq *p = ChanQ.Peek();
long nOfs = (p->nTrk * p->nSid * nBpt) + ((p->nSec - 1) * nBps);
return (nOfs <= (long)(nBpd - nBps)) ? nOfs : -1L;
}
/////////////////////
// Internal read simulates a "soft" (recoverable) error every
// operations. On others, it calcs the location of the sector sized
// file sector to transfer and does so the address at the head of Q.
// RETURNS: TRUE .. transfer Ok
// FALSE .. bad params or soft (or even a host hard) error
//
BOOL DskModel::Read (void)
{
if (++nOpCnt >= nErrRate)
nOpCnt = 0;
else {
XfReq *p = ChanQ.Peek();
long nOfs = CalcOffset();
if ((nOfs > 0L) &&
(nOfs == lseek(fd, nOfs, SEEK_SET)) &&
((int)nBps == read(fd, p->pDma, nBps)))
return TRUE;
}
return FALSE;
}
/////////////////////
// Same sort of thing as for read operation..
// RETURNS: TRUE .. transfer Ok
// FALSE .. bad params or soft (or even a host hard) error
//
BOOL DskModel::Write (void)
{
if (++nOpCnt >= nErrRate)
nOpCnt = 0;
else {
XfReq *p = ChanQ.Peek();
long nOfs = CalcOffset();
if ((nOfs > 0L) &&
(nOfs == lseek(fd, nOfs, SEEK_SET)) &&
((int)nBps == write(fd, p->pDma, nBps)))
return TRUE;
}
return FALSE;
}
///////////////
// Port receive member simply adds passed Transfer Request to the queue
// of transfer rwquests (FIFO). A message is sent to the animator in case
// the disk model is being displayed, which will update the queue contents.
//
void DskModel::RxPort (PMSG pMsg)
{
if (pMsg->pBody) {
ChanQ.Put((XfReq*)pMsg->pBody);
TransQ Tq(&ChanQ);
message msg(uID, ANI_CHAN_ENQ, nLid, &Tq);
pTx->SendMsg(ID_ANIM, &msg);
}
}
//////////////////
// This member is called on every pass around the RCOS loop in ,
// effectively acting as the disk sub-system's time slice. If there
// are no transfers in the queue, we just update the angular disk
// position and exit. If there is something at the head of queue, we:
//
// 1. Step the heads until nCurTrk = required track (with animation),
// 2. Delay for the head settle period,
// 3. After the settle, calculate the delay until the disk rotation
// (based on the current angular position) will bring the required
// sector "under" the heads,
// 4. Perform the read or write operation, then
// 5. Remove the head of queue object, call the animator, then
// call the CpmDsk::NxtFunc function (using the pointer in the XfReq)
// which is analogous to issuing a "disk operation" complete interrupt.
//
void DskModel::Scheduler (void)
{
UINT32 lTm = Clock.GetTicks();
UINT16 nMs = (UINT16)(lTm - lLastTime);
UINT16 ndTheta = nMs / nAngleInc;
if (ndTheta) {
if (361 < (nCurAngle += ndTheta))
nCurAngle %= 360;
message msg(uID, ANI_SPIN, nLid, (void*)&nCurAngle);
pTx->SendMsg(ID_ANIM, &msg);
}
lLastTime = lTm;
//
// If there is a transfer pending, do (in order):
// 1. Seek to track
// 2. Wait head settling time
// 3. Wait out rotational latency
// 4. Perform transfer.
// if (by luck) we are already on track, skip steps 1 and 2
//
if (ChanQ.GetLen()) {
XfReq *p = ChanQ.Peek();
switch (stage) {
//
// The disk drive has nothing to do and a transfer is waiting. If we
// are on-cylinder, we can immediatly begin scanning for the required
// sector. Otherwise we'll have to go the whole hog - seek, settle etc..
//
case DD_IDLE:
if (nCurTrk != p->nTrk) {
UINT16 nTargTrk = p->nTrk;
if (nCurTrk < nTargTrk)
nTargTrk |= 0x8000;
message msg(uID, ANI_BEGIN_SEEK, nLid, (void*)&nTargTrk);
pTx->SendMsg(ID_ANIM, &msg);
stage = DD_SEEK;
}
else {
message msg(uID, ANI_ONTRACK, p->nSec);
pTx->SendMsg(ID_ANIM, &msg);
UINT16 nSecPos = (p->nSec - 1) * nSecAngle;
if (nSecPos < nCurAngle)
lHoldTime = lLastTime + ((nCurAngle - nSecPos) / nAngleInc);
else
lHoldTime = lLastTime + ((360 - nCurAngle + nSecPos) / nAngleInc);
stage = DD_TRANSFER;
}
break;
//
// We are seeking. Move the heads at the best rate the mechanics will
// accomodate until we arrive on cylinder, then set in the anticipated
// time after which the heads will be stable enough to start reading..
//
case DD_SEEK: {
INT16 nInc = nMs / nTrkInc;
if (nInc) {
INT16 nGap = abs(nCurTrk - p->nTrk);
if (nInc > nGap)
nInc = nGap;
if (nCurTrk > p->nTrk)
nInc = -nInc;
message msg(uID, ANI_SEEK, 0,(void*)&nInc);
pTx->SendMsg(ID_ANIM, &msg);
nCurTrk += nInc;
if (nCurTrk == p->nTrk) {
lHoldTime = lLastTime;
stage = DD_SETTLE;
}
}
}
break;
//
// We are on track and the head settling time has been set. If it has
// expired, calculate rotational time delay until the desired sector
// arrives..
//
case DD_SETTLE:
if (nSettle <= lLastTime - lHoldTime) {
UINT16 nSecPos = (p->nSec - 1) * nSecAngle;
if (nSecPos < nCurAngle)
lHoldTime = lLastTime + ((nCurAngle - nSecPos) / nAngleInc);
else
lHoldTime = lLastTime + ((360 - nCurAngle + nSecPos) / nAngleInc);
message msg(uID, ANI_ONTRACK, p->nSec);
pTx->SendMsg(ID_ANIM, &msg);
stage = DD_WAIT;
}
break;
//
// When we reach here, we see if the rotational delay has expired
//
case DD_WAIT:
if (lLastTime >= lHoldTime) {
lHoldTime = 0L;
stage = DD_TRANSFER;
}
break;
//
// Ready to perform the read/write operation, de-queue the transfer
// request, run the display animation, then callback (interrupt) the
// file system to do the next stage of the BDOS operation..
//
case DD_TRANSFER: {
TransQ Tq(&ChanQ);
message msg(uID, ANI_RWOP, nLid, &Tq);
pTx->SendMsg(ID_ANIM, &msg);
switch (p->cmnd) {
case DD_READ : p->bRes = Read(); break;
case DD_WRITE: p->bRes = Write(); break;
}
ChanQ.Purge();
stage = DD_IDLE;
p->pFnCb->NxtFunc(p);
}
break;
}
}
}
/////////////////////////////////// eof ///////////////////////////////////