📄 cpmbdos.cpp
字号:
//*************************************************************************// MODULE : CpmBdos - The Basic Disk Operating System object members *// AUTHOR : Ron Chernich *// PURPOSE: This is the actual CP/M-like BDOS (Basic Disk Operating *// System), providing file operations and low level block *// access. Normally, char at a time buffering would be provided *// by the compiler - but RCOS builds this into the Exec/PCI. *// HISTORY: *// 13-AUG-94 First version *//*************************************************************************#include "cpmfs.hpp" //////////////////////////////////////////////////////////////////////////// The BDOS .. Instantiate one of these per disk drive.//------------------------------------------------------------------------// Each file consists of 0..31 "extents" where an extent is a physical// entry in the directory. Each extent can contain either 8 or 16// disk allocation blocks, depending on the total blocks on the media.// This is because 16 bytes of each dir entry are reserved for allocation// block numbers. IBM 3740 floppies have 243 blocks total, hence a block// ID fits in one byte. Bigger disks need two bytes per block number.// (Aside: MS-DOS compromised, using 12 bits for a group number)// A block is a power of two multiple of 1024 bytes - this is the smallest// piece of disk that can be allocated. When (say) 16 blocks have been// allocated, a new "extent" is opened, duplicating the file name in the// first free directory slot. Blocks are allocated on a first free basis// from the "Allocation Vector" for the disk.//// CP/M supported only block at a time transfers (it was up to compilers to// implement byte at a time access). Entry <nRecCnt> contains the "current"// 128 byte record of the last allocation block that is in use. If you write// beyond the last 128 byte record of the last block, a new block is // allocated, opening extents as required. Finding out where you are in// the current rec of the last block required an EOF mark (usually 0x1a).// When introduced, MS-DOS kept an actual byte count, making the EOF mark// redundant. But, to this day, some old ported proggies still write 0x1a// EOF marks in text files!//// For an IBM 3740 8" diskette with 241 available 1K blocks, each extent// can reference 16K and the maximum 32 extents for one file references// more than the disk can hold. Larger disks required larger block sizes.////////////////////////////////////////////////////////////////////////////// Class constructor sets params for the unit, allocates space for the// checksum vector (if we have any checked Dir entries), creates the// allocation vector (which will be initialised when the disk is "logged")// If the sector skew factor is not zero, a translate table is built.//CpmBdos::CpmBdos (DPB& dpb, DPARAM& mac, UINT16 nChanId) : pDpb(&dpb), nFsc(mac.nFsc), nLsc(mac.nLsc), nSkf(mac.nSkf), nBls(mac.nBls), nDks(mac.nDks), nDir(mac.nDir), nCks(mac.nCks), nOfs(mac.nOfs), nChan(nChanId){ nAlb = (nDks > 255) ? 8 : 16; if (0 == nCks) pCsv = NULL; else { pCsv = new BYTE[nCks]; if (NULL == pCsv) return; memset(pCsv, 0xff, nCks); } if (0 == nSkf) pXlt = NULL; else { int nSecs = nLsc + (nFsc ? 0 : 1); if (NULL == (pXlt = new BYTE[nSecs])) return; int nSec = nFsc; for (int i = 0; i < nSecs; i++) { *(pXlt + i) = nSec; nSec += nSkf; if (nSec > nLsc) { nSec -= nLsc; if (nSec == nFsc) ++nSec; } } } pAlv = new BYTE[(nDks + 7) / 8];}////////////////// shut down//CpmBdos::~CpmBdos (void){ if (pAlv) DELETE_ARRAY pAlv; if (pXlt) DELETE_ARRAY pXlt; if (pCsv) DELETE_ARRAY pCsv;}/////////////////////// The public interface used to simulate interrupt service vectoring.// The disk controller channel device calls her when a disk operation// has finished, supplying the packet of info (a XfReq object) that// encapsulates the transfer. The member who launched the packet knew// where to go next and placed an appropriate constant in the packet,// so at the mere expense of a few (!) nested stack frames..//void CpmBdos::NxtFunc (XfReq *pX){ switch (pX->ivec) { case LOG1 : LogDisk1(pX); break; case CLOSE1 : Close1(pX); break; case CLOSE2 : Close2(pX); break; case OPEN1 : Open1(pX); break; case CREAT1 : Creat1(pX); break; case CREAT2 : Creat2(pX); break; case CREAT3 : Creat3(pX); break; case READ0 : Read(pX, 0); break; case READ1 : Read1(pX); break; case WRITE1 : Write1(pX); break; case REMOV1 : Remov1(pX); break; case REMOV2 : Remov2(pX); break; case WRITE2 : break; }}/////////////////////////////////////////////////////////////////////////// These Members are PRIVATE//----------------------------------------------------------------------// The BDOS re-enterent mechanism runs by re-posting messages to itself.// Since the Kernel::Run message dispatcher destroys messages after// they have been dispatched, we must copy the message to be re-posted// otherwise we end up multiply free-ing mamory (which is very bad).//void CpmBdos::ReGenerate (XfReq* pOld, UINT16 nTag){ XfReq *pNew = new XfReq; *pNew = *pOld; message msg(nId, 0, nTag, (void*)pNew); pTx->PostMsg(nChan, &msg);}//////////////// Set/reset the passed bit of the disk allocation vector//void CpmBdos::AllocBlk (UINT16 nBit, BYTE nState){ UINT16 nByte = nBit / 8; BYTE nMask = 0x80 >> (nBit % 8); if (nState) *(pAlv + nByte) |= nMask; else *(pAlv + nByte) &= ~nMask;}//////////// Scan the Disk Alocation vector for the first free block// number (ie first zero bit, working left to right)// RETURNS: block number, or zero if disk full!//UINT16 CpmBdos::GetFreeBlk (void){ UINT16 nByte = 0; BYTE nMask = 0x80; while (nByte < nBls) { for (int i = 0; i < 8; i++) if (0x00 == (*(pAlv + nByte) & nMask >> i)) return (UINT16)((nByte * 8) + i); ++nByte; } return 0x00;}///////////// Set sector for transfer -// using sector skew translate table if not null//UINT16 CpmBdos::SecXlt (UINT16 nLogSec){ return (pXlt) ? *(pXlt + nLogSec - nFsc) : (nLogSec);}/////////////// Calculate track and sector numbers of passed record within block..//void CpmBdos::BlkDecode (UINT16 nBlk, UINT16 nRec, UINT16 *pnTrk, UINT16 *pnSec){ if (nBlk < nDks) { UINT16 nSecs = (nBlk * nBls / pDpb->nSecSize) + nRec; *pnTrk = nOfs + (UINT16)(nSecs / pDpb->nSecs); *pnSec = SecXlt(nFsc + (UINT16)(nSecs % pDpb->nSecs)); }}/////////////// A read/write has failed. Decrement the retry counter and if not zero,// post to the channel controller to try it again. If the error persists,// see your doctor (ghod, i hope this never gets called to the end).//void CpmBdos::DoRetry (XfReq *pX){ if (--(pX->nRetry)) { message msg(nId, 16, 16, (void*)pX); pTx->PostMsg(nChan, &msg); } else { if (pX->pNxt) { XfReq *pInner = pX->pNxt; pInner->bRes = FALSE; delete pX; NxtFunc(pInner); return; } if (LOG1 == pX->ivec) delete pX->pFcb; else { message msg(nId, FS_Ok); pTx->PostMsg(pX->uProc, &msg); } }}/////////////////// Several (lots) of members need to prepare for a full directory scan,// so this little 'fella sets up the passed FCB for the common stuff..//void CpmBdos::DirScanPrep (FCB *pFcb, UINT16 *pnTrk, UINT16 *pnSec){ pFcb->de.nRes1 = 0; pFcb->de.nRes2 = 0; pFcb->de.nExtent = 0; pFcb->nCurRec = nDir / 4; BlkDecode(0, 0, pnTrk, pnSec);}///////////////////// Begin the process of Logging the Disk by creating an XfReq for the// first record of the directory and queueing it for execution. Function// <LogDisk1> will receive the service "interrupt" and issue more// requests until logging is complete. Here I make an improvement on// CP/M - A blank disk is all 0xE5's - valid Directory entries have// a first byte of 0..15 and the erase process just rewrote this with// 0xE5 (note that the name remained in tact - unlike MS-DOS). We want// to scan all valid entries to build the allocation vector. Since// entries are allocated by using the first available, sequentially,// if we use 0xF7 to erase a file, the first time we strike an 0xE5// flag, we know that no more valid files exist after this point -// resulting in a considerably shortened log process.// void CpmBdos::LogDisk (void){ XfReq *pX; if (pCsv) memset(pCsv, 0xff, nCks); memset(pAlv, 0, (nDks + 7) / 8); UINT16 nDirBls = ((nDir * 32) + nBls - 1) / nBls; UINT16 nBlkVal = 0; while (nDirBls--) AllocBlk(nBlkVal++); pX = new XfReq; if (pX) { pX->pFnCb = this; pX->ivec = LOG1; pX->cmnd = DD_READ; pX->pDma = (char*)DirBuf; pX->pFcb = new FCB; if (NULL == pX->pFcb) delete pX; else { memset((char*)pX->pFcb, 0, sizeof(FCB)); pX->pFcb->nCurRec = nDir / 4; DirScanPrep(pX->pFcb, &pX->nTrk, &pX->nSec); message msg(nId, 0, 0, (void*)pX); pTx->PostMsg(nChan, &msg); } }}///////////// This is the directory scan log-in loop. We arrive here each time// another DIR block has been read to scan for current files, setting// the Allocation vectors from the individual entry maps and// calculating the checksums for all (checked) dir entries until// no more files CAN be found, or all DIR blocks have been scanned.//void CpmBdos::LogDisk1 (XfReq *pX){ if (FALSE == pX->bRes) DoRetry(pX); else { int idx; for (idx = 0; idx < 4; idx++) { DIRENT *pDir = (DIRENT*)(pX->pDma + (idx * 32)); if (0xE5 == pDir->nUsrNmbr) break; if (0xF7 != pDir->nUsrNmbr) for (int jdx = 0; jdx < 16; jdx++) { if (0 == pDir->nAllocBlk[jdx]) break; AllocBlk((UINT16)pDir->nAllocBlk[jdx]); } } if ((idx < 4) || (--(pX->pFcb->nCurRec) <= 0)) { delete pX->pFcb; pX->pFcb = NULL; } else { ++(pX->pFcb->de.nRes2); if (pX->pFcb->de.nRes2 >= (nBls / 128)) { ++(pX->pFcb->de.nRes1); pX->pFcb->de.nRes2 = 0; } BlkDecode(pX->pFcb->de.nRes1, pX->pFcb->de.nRes2, &(pX->nTrk), &(pX->nSec)); ReGenerate(pX, 1); } }}//////////////////// This is the loop point for "Open" operations. If the read was Ok, we// scan the Dir record, terminating "not found" if we encounter an 0xE5// flag. Valid names are matched against the required name (Wild cards// ARE permitted) and the actual disk dir entry copied over the user's// FCB on first match (the extent is also matched).//void CpmBdos::Open1 (XfReq *pX){ if (FALSE == pX->bRes) DoRetry(pX); else { int idx; for (idx = 0; idx < 4; idx++) { DIRENT *pDir = (DIRENT*)(pX->pDma + (idx * 32)); if (0xE5 == pDir->nUsrNmbr) { Open2(pX, FALSE); return; } if ((0xF7 != pDir->nUsrNmbr) && (pDir->nExtent == pX->pFcb->de.nExtent)) { int jdx; for (jdx = 0; jdx < FILENAME_LEN; jdx++) if ((pX->pFcb->de.fname[jdx] != pDir->fname[jdx]) && (pX->pFcb->de.fname[jdx] != '?')) break; if (jdx == FILENAME_LEN) { pX->pFcb->de.nRes1 = idx * 32; pX->pFcb->de.nRes2 = 0; pX->pFcb->nCurRec = 0; Open2(pX, TRUE); return; } } } if (--pX->pFcb->nCurRec <= 0) Open2(pX, FALSE); else { ++(pX->pFcb->de.nRes2); if (pX->pFcb->de.nRes2 >= (nBls / 128)) { ++(pX->pFcb->de.nRes1); pX->pFcb->de.nRes2 = 0; } BlkDecode(pX->pFcb->de.nRes1, pX->pFcb->de.nRes2, &(pX->nTrk), &(pX->nSec)); ReGenerate(pX, 2); //message msg(nId, 2, 2, (void*)pX); //pTx->PostMsg(nChan, &msg); } }}//////////////// This is the end of the "Open" process and we've either found the file// or we've not - so it's time to clean up and reply to the request.//// We must check for chained XfReq blocks, because we may have been// called to open the next extent of a file during a Read or Write, or// scan for the file before doing a Creat. In such cases, control gets// passed via the usual <NxtFunc> member of the attached request. If// there is no attached request, we message back to the original request// process ID via Kernel post.//void CpmBdos::Open2 (XfReq *pX, BOOL bFound){ if (NULL == pX->pNxt) { char *cp1 = (char*)&pX->pFcb->de; char *cp2 = (char*)pX->pDma+pX->pFcb->de.nRes1; memcpy(cp1, cp2, sizeof(DIRENT)); pX->pFcb->nCurRec = 0; message msg(nId, (bFound ? FS_Ok : FS_NotFound)); pTx->PostMsg(pX->uProc, &msg); } else { XfReq *pInner = pX->pNxt; pInner->bRes = bFound; switch (pInner->ivec) { case CLOSE1: case REMOV1: pInner->nTrk = pX->nTrk; pInner->nSec = pX->nSec; pInner->nSid = pX->nSid; pInner->pDma = pX->pDma; pInner->pFcb->de.nRes1 = pX->pFcb->de.nRes1; break; default: memcpy((char*)&pInner->pFcb->de, (char*)&pX->pFcb->de, sizeof(DIRENT)); pInner->pFcb->nCurRec = 0; } delete pX->pFcb; NxtFunc(pInner); }}///////////////// Arriving here, we have scanned the directory for the file and the// result sits in bRes: FALSE means the file does not exists, so we // need to scan for the first free directory entry of the disk.//void CpmBdos::Creat1 (XfReq *pX){ if (pX->bRes) { message msg(nId, FS_Exists); pTx->PostMsg(pX->uProc, &msg);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -