/****************************************************************************/ /* Beebem - (c) David Alan Gilbert 1994 */ /* ------------------------------------ */ /* This program may be distributed freely within the following restrictions:*/ /* */ /* 1) You may not charge for this program or for any part of it. */ /* 2) This copyright message must be distributed with all copies. */ /* 3) This program must be distributed complete with source code. Binary */ /* only distribution is not permitted. */ /* 4) The author offers no warrenties, or guarentees etc. - you use it at */ /* your own risk. If it messes something up or destroys your computer */ /* thats YOUR problem. */ /* 5) You may use small sections of code from this program in your own */ /* applications - but you must acknowledge its use. If you plan to use */ /* large sections then please ask the author. */ /* */ /* If you do not agree with any of the above then please do not use this */ /* program. */ /* Please report any problems to the author at gilbertd@cs.man.ac.uk */ /****************************************************************************/ /*--------------------------------------------------------------------------*/ /* 8271 disc emulation - David Alan Gilbert 4/12/94 */ // 27 Jan 97 MHG changed loadiscimage to totaL LOAD and 1/2Mb limit // 4 FEB 97 mhg removed all couts #include #include #include #include #include "6502core.h" #include "disc8271.h" int load_file(char *name,char *mem,int max); void error(char *t1,char *t2); #define MAX_DISC_SIZE (256*10*80*2) char discbuffer[MAX_DISC_SIZE]; int Disc8271Trigger; /* Cycle based time Disc8271Trigger */ static unsigned char ResultReg; static unsigned char StatusReg; static unsigned char DataReadReg; static unsigned char Internal_Scan_SectorNum; static unsigned int Internal_Scan_Count; /* Read as two bytes */ static unsigned char Internal_ModeReg; static unsigned char Internal_CurrentTrack[2]; /* 0/1 for surface number */ static unsigned char Internal_DriveControlOutputPort; static unsigned char Internal_DriveControlInputPort; static unsigned char Internal_BadTracks[2][2]; /* 1st subscript is surface 0/1 and second subscript is badtrack 0/1 */ static int ThisCommand; static int NParamsInThisCommand; static int PresentParam; /* From 0 */ static unsigned char Params[16]; /* Wildly more than we need */ static int Selects[2]; /* Drive selects */ static int Writeable[2]={1,1}; /* True if the drives are writeable - for the moment let them be */ static int NextInterruptIsErr; /* none 0 causes error and drops this value into result reg */ #define TRACKSPERDRIVE 80 /* Note Head select is done from bit 5 of the drive output register */ #define CURRENTHEAD ((Internal_DriveControlOutputPort>>5) & 1) /* Note: reads one byte every 80us */ #define TIMEBETWEENBYTES (160) typedef struct { struct { unsigned int CylinderNum:7; unsigned int RecordNum:5; unsigned int HeadNum:1; unsigned int PhysRecLength; } IDField; unsigned int Deleted:1; /* If non-zero the sector is deleted */ char *Data; } SectorType; typedef struct { int LogicalSectors; /* Number of sectors stated in format command */ int NSectors; /* i.e. the number of records we have - not anything physical */ SectorType *Sectors; int Gap1Size,Gap3Size,Gap5Size; /* From format command */ } TrackType; /* All data on the disc - first param is drive number, then head. then physical track id */ TrackType DiscStore[4][2][TRACKSPERDRIVE]; SectorType idstore[10*80*2]; //-------------------------------------- char *mptr0,*mptr1; void mmreset(void) { mptr0=(char *)&discbuffer[0]; mptr1=(char *)&idstore[0]; } char *mmcalloc(int num,int size) { // memset(mptr1,0,num*size); //clear memory mptr1+=num*size; return(mptr1-(num*size)); } char *mmmalloc(int num) { mptr0+=num; return(mptr0-num); } //-------------------------------------- typedef void (*CommandFunc)(void); #define UPDATENMISTATUS if (StatusReg & 8) NMIStatus |=1<=TRACKSPERDRIVE) LogicalTrackID=TRACKSPERDRIVE-1; return(&(DiscStore[UnitID][CURRENTHEAD][LogicalTrackID])); }; /* GetTrackPtr */ /*--------------------------------------------------------------------------*/ /* Returns a pointer to the data structure for a particular sector. Returns */ /* NULL for Sector not found. Doesn't check cylinder/head ID */ static SectorType *GetSectorPtr(TrackType *Track, int LogicalSectorID, int FindDeleted) { int CurrentSector; if (Track->Sectors==NULL) return(NULL); for(CurrentSector=0;CurrentSectorNSectors;CurrentSector++) if ((Track->Sectors[CurrentSector].IDField.RecordNum==LogicalSectorID) && ((!Track->Sectors[CurrentSector].Deleted) || (!FindDeleted))) return(&(Track->Sectors[CurrentSector])); return(NULL); }; /* GetSectorPtr */ /*--------------------------------------------------------------------------*/ /* Returns true if the drive signified by the current selects is ready */ static int CheckReady(void) { if (Selects[0]) return(1); if (Selects[1]) return(1); return(0); }; /* CheckReady */ /*--------------------------------------------------------------------------*/ /* Cause an error - pass err num */ static void DoErr(int ErrNum) { SetTrigger(50,Disc8271Trigger); /* Give it a bit of time */ NextInterruptIsErr=ErrNum; StatusReg=0x80; /* Command is busy - come back when I have an interrupt */ UPDATENMISTATUS; }; /* DoErr */ /*--------------------------------------------------------------------------*/ /* Checks a few things in the sector - returns true if OK */ static int ValidateSector(SectorType *SecToVal,int Track,int SecLength) { if (SecToVal->IDField.CylinderNum!=Track) return(0); if (SecToVal->IDField.PhysRecLength!=SecLength) return(0); return(1); }; /* ValidateSector */ /*--------------------------------------------------------------------------*/ static void DoVarLength_ScanDataCommand(void) { DoSelects(); NotImp("DoVarLength_ScanDataCommand"); }; /*--------------------------------------------------------------------------*/ static void DoVarLength_ScanDataAndDeldCommand(void) { DoSelects(); NotImp("DoVarLength_ScanDataAndDeldCommand"); }; /*--------------------------------------------------------------------------*/ static void Do128ByteSR_WriteDataCommand(void) { DoSelects(); NotImp("Do128ByteSR_WriteDataCommand"); }; /*--------------------------------------------------------------------------*/ static void DoVarLength_WriteDataCommand(void) { DoSelects(); NotImp("DoVarLength_WriteDataCommand"); }; /*--------------------------------------------------------------------------*/ static void Do128ByteSR_WriteDeletedDataCommand(void) { DoSelects(); NotImp("Do128ByteSR_WriteDeletedDataCommand"); }; /*--------------------------------------------------------------------------*/ static void DoVarLength_WriteDeletedDataCommand(void) { DoSelects(); NotImp("DoVarLength_WriteDeletedDataCommand"); }; /*--------------------------------------------------------------------------*/ static void Do128ByteSR_ReadDataCommand(void) { DoSelects(); NotImp("Do128ByteSR_ReadDataCommand"); }; /*--------------------------------------------------------------------------*/ static void DoVarLength_ReadDataCommand(void) { int Drive=-1; DoSelects(); DoLoadHead(); if (!CheckReady()) { DoErr(0x10); /* Drive not ready */ return; }; if (Selects[0]) Drive=0; if (Selects[1]) Drive=1; Internal_CurrentTrack[Drive]=Params[0]; if (ReadCommandStatus.CurrentTrackPtr=GetTrackPtr(Params[0]),ReadCommandStatus.CurrentTrackPtr==NULL) { DoErr(0x10); return; }; if (ReadCommandStatus.CurrentSectorPtr=GetSectorPtr(ReadCommandStatus.CurrentTrackPtr,Params[1],0),ReadCommandStatus.CurrentSectorPtr==NULL) { DoErr(0x1e); /* Sector not found */ return; }; ReadCommandStatus.TrackAddr=Params[0]; ReadCommandStatus.CurrentSector=Params[1]; ReadCommandStatus.SectorsToGo=Params[2] & 31; ReadCommandStatus.SectorLength=1<<(7+((Params[2] >> 5) & 7)); if (ValidateSector(ReadCommandStatus.CurrentSectorPtr,ReadCommandStatus.TrackAddr,ReadCommandStatus.SectorLength)) { ReadCommandStatus.ByteWithinSector=0; SetTrigger(TIMEBETWEENBYTES,Disc8271Trigger); StatusReg=0x80; /* Command busy */ UPDATENMISTATUS; ReadCommandStatus.ByteWithinSector=0; } else { DoErr(0x1e); /* Sector not found */ }; }; /*--------------------------------------------------------------------------*/ static void ReadInterrupt(void) { extern int DumpAfterEach; int LastByte=0; if (ReadCommandStatus.SectorsToGo<0) { StatusReg=0x18; /* Result and interrupt */ UPDATENMISTATUS; return; }; DataReadReg=ReadCommandStatus.CurrentSectorPtr->Data[ReadCommandStatus.ByteWithinSector++]; /*cout << "ReadInterrupt called - DataReadReg=0x" << hex << int(DataReadReg) << dec << "ByteWithinSector=" << ReadCommandStatus.ByteWithinSector << "\n"; */ /* DumpAfterEach=1; */ ResultReg=0; if (ReadCommandStatus.ByteWithinSector>=ReadCommandStatus.SectorLength) { ReadCommandStatus.ByteWithinSector=0; /* I don't know if this can cause the thing to step - I presume not for the moment */ if (--ReadCommandStatus.SectorsToGo) { ReadCommandStatus.CurrentSector++; if (ReadCommandStatus.CurrentSectorPtr=GetSectorPtr(ReadCommandStatus.CurrentTrackPtr,ReadCommandStatus.CurrentSector,0), ReadCommandStatus.CurrentSectorPtr==NULL) { DoErr(0x1e); /* Sector not found */ return; }/* else cout << "all ptr for sector " << ReadCommandStatus.CurrentSector << "\n"*/; } else { /* Last sector done */ StatusReg=0x9c; UPDATENMISTATUS; LastByte=1; ReadCommandStatus.SectorsToGo=-1; /* To let us bail out */ SetTrigger(TIMEBETWEENBYTES,Disc8271Trigger); /* To pick up result */ }; }; if (!LastByte) { StatusReg=0x8c; /* Command busy, */ UPDATENMISTATUS; SetTrigger(TIMEBETWEENBYTES,Disc8271Trigger); }; }; /* ReadInterrupt */ /*--------------------------------------------------------------------------*/ static void Do128ByteSR_ReadDataAndDeldCommand(void) { DoSelects(); NotImp("Do128ByteSR_ReadDataAndDeldCommand"); }; /*--------------------------------------------------------------------------*/ static void DoVarLength_ReadDataAndDeldCommand(void) { DoSelects(); NotImp("DoVarLength_ReadDataAndDeldCommand"); }; /*--------------------------------------------------------------------------*/ static void DoReadIDCommand(void) { DoSelects(); NotImp("DoReadIDCommand"); }; /*--------------------------------------------------------------------------*/ static void Do128ByteSR_VerifyDataAndDeldCommand(void) { DoSelects(); NotImp("Do128ByteSR_VerifyDataAndDeldCommand"); }; /*--------------------------------------------------------------------------*/ static void DoVarLength_VerifyDataAndDeldCommand(void) { DoSelects(); NotImp("DoVarLength_VerifyDataAndDeldCommand"); }; /*--------------------------------------------------------------------------*/ static void DoFormatCommand(void) { DoSelects(); NotImp("DoFormatCommand"); }; /*--------------------------------------------------------------------------*/ static void DoSeekInt(void) { StatusReg=0x18; /* Result with interrupt */ UPDATENMISTATUS; ResultReg=0; /* All OK */ }; /*--------------------------------------------------------------------------*/ static void DoSeekCommand(void) { int Drive=-1; DoSelects(); DoLoadHead(); if (Selects[0]) Drive=0; if (Selects[1]) Drive=1; if (Drive<0) { DoErr(0x10); return; }; Internal_CurrentTrack[Drive]=Params[0]; StatusReg=0x80; /* Command busy */ UPDATENMISTATUS; SetTrigger(100,Disc8271Trigger); /* A short delay to causing an interrupt */ }; /*--------------------------------------------------------------------------*/ static void DoReadDriveStatusCommand(void) { int Track0,WriteProt; DoSelects(); if (Selects[0]) { Track0=(Internal_CurrentTrack[0]==0); WriteProt=(!Writeable[0]); }; if (Selects[1]) { Track0=(Internal_CurrentTrack[1]==0); WriteProt=(!Writeable[1]); }; ResultReg=0x81; /* For the moment!!! */ ResultReg=0x80 | (Selects[1]?0x40:0) | (Selects[0]?0x4:0) | (Track0?2:0) | (WriteProt?8:0); StatusReg|=0x10; /* Result */ UPDATENMISTATUS; }; /*--------------------------------------------------------------------------*/ static void DoSpecifyCommand(void) { /* Should set stuff up here */ }; /*--------------------------------------------------------------------------*/ static void DoWriteSpecialCommand(void) { DoSelects(); switch(Params[0]) { case 0x06: Internal_Scan_SectorNum=Params[1]; break; case 0x14: Internal_Scan_Count&=0xff; Internal_Scan_Count|=Params[1]<<8; break; case 0x13: Internal_Scan_Count&=0xff00; Internal_Scan_Count|=Params[1]; break; case 0x12: Internal_CurrentTrack[0]=Params[1]; break; case 0x1a: Internal_CurrentTrack[1]=Params[1]; break; case 0x17: Internal_ModeReg=Params[1]; break; case 0x23: Internal_DriveControlOutputPort=Params[1]; Selects[0]=(Params[1] & 0x40)!=0; Selects[1]=(Params[1] & 0x80)!=0; break; case 0x22: Internal_DriveControlInputPort=Params[1]; break; case 0x10: Internal_BadTracks[0][0]=Params[1]; break; case 0x11: Internal_BadTracks[0][1]=Params[1]; break; case 0x18: Internal_BadTracks[1][0]=Params[1]; break; case 0x19: Internal_BadTracks[1][1]=Params[1]; break; default: //cout << "Write to bad special register\n"; return; break; }; /* Special register number switch */ }; /* DoWriteSpecialCommand */ /*--------------------------------------------------------------------------*/ static void DoReadSpecialCommand(void) { DoSelects(); switch(Params[0]) { case 0x06: ResultReg=Internal_Scan_SectorNum; break; case 0x14: ResultReg=(Internal_Scan_Count>>8) & 255; break; case 0x13: ResultReg=Internal_Scan_Count & 255; break; case 0x12: ResultReg=Internal_CurrentTrack[0]; break; case 0x1a: ResultReg=Internal_CurrentTrack[1]; break; case 0x17: ResultReg=Internal_ModeReg; break; case 0x23: ResultReg=Internal_DriveControlOutputPort; break; case 0x22: ResultReg=Internal_DriveControlInputPort; break; case 0x10: ResultReg=Internal_BadTracks[0][0]; break; case 0x11: ResultReg=Internal_BadTracks[0][1]; break; case 0x18: ResultReg=Internal_BadTracks[1][0]; break; case 0x19: ResultReg=Internal_BadTracks[1][1]; break; default: //cout << "Read of bad special register\n"; return; break; }; /* Special register number switch */ StatusReg |= 16; /* Result reg full */ UPDATENMISTATUS; }; /*--------------------------------------------------------------------------*/ static void DoBadCommand(void) { }; /*--------------------------------------------------------------------------*/ /* The following table is used to parse commands from the command number written into the command register - it can't distinguish between subcommands selected from the first parameter */ static PrimaryCommandLookupType PrimaryCommandLookup[]={ {0x00, 0x3f, 3, DoVarLength_ScanDataCommand, NULL, "Scan Data (Variable Length/Multi-Record)"}, {0x04, 0x3f, 3, DoVarLength_ScanDataAndDeldCommand, NULL, "Scan Data & deleted data (Variable Length/Multi-Record)"}, {0x0a, 0x3f, 2, Do128ByteSR_WriteDataCommand, NULL, "Write Data (128 byte/single record)"}, {0x0b, 0x3f, 3, DoVarLength_WriteDataCommand, NULL, "Write Data (Variable Length/Multi-Record)"}, {0x0e, 0x3f, 2, Do128ByteSR_WriteDeletedDataCommand, NULL, "Write Deleted Data (128 byte/single record)"}, {0x0f, 0x3f, 3, DoVarLength_WriteDeletedDataCommand, NULL, "Write Deleted Data (Variable Length/Multi-Record)"}, {0x12, 0x3f, 2, Do128ByteSR_ReadDataCommand, NULL, "Read Data (128 byte/single record)"}, {0x13, 0x3f, 3, DoVarLength_ReadDataCommand, ReadInterrupt, "Read Data (Variable Length/Multi-Record)"}, {0x16, 0x3f, 2, Do128ByteSR_ReadDataAndDeldCommand, NULL, "Read Data & deleted data (128 byte/single record)"}, {0x17, 0x3f, 3, DoVarLength_ReadDataAndDeldCommand, NULL, "Read Data & deleted data (Variable Length/Multi-Record)"}, {0x1b, 0x3f, 3, DoReadIDCommand, NULL, "ReadID" }, {0x1e, 0x3f, 2, Do128ByteSR_VerifyDataAndDeldCommand, NULL, "Verify Data and Deleted Data (128 byte/single record)"}, {0x1f, 0x3f, 3, DoVarLength_VerifyDataAndDeldCommand, NULL, "Verify Data and Deleted Data (Variable Length/Multi-Record)"}, {0x23, 0x3f, 5, DoFormatCommand, NULL, "Format"}, {0x29, 0x3f, 1, DoSeekCommand, DoSeekInt, "Seek"}, {0x2c, 0x3f, 0, DoReadDriveStatusCommand, NULL, "Read drive status"}, {0x35, 0xff, 4, DoSpecifyCommand, NULL, "Specify" }, {0x3a, 0x3f, 2, DoWriteSpecialCommand, NULL, "Write special registers" }, {0x3d, 0x3f, 1, DoReadSpecialCommand, NULL, "Read special registers" }, {0, 0, 0, DoBadCommand, NULL, "Unknown command"} /* Terminator due to 0 mask matching all */ }; /* PrimaryCommandLookup */ /*--------------------------------------------------------------------------*/ /* returns a pointer to the data structure for the given command */ /* If no matching command is given, the pointer points to an entry with a 0 */ /* mask, with a sensible function to call. */ static PrimaryCommandLookupType *CommandPtrFromNumber(int CommandNumber) { PrimaryCommandLookupType *presptr=PrimaryCommandLookup; for(;presptr->CommandNum!=(presptr->Mask & CommandNumber);presptr++); return(presptr); }; /* CommandPtrFromNumber */ /*--------------------------------------------------------------------------*/ /* Address is in the range 0-7 - with the fe80 etc stripped out */ int Disc8271_read(int Address) { switch (Address) { case 0: /*cout << "8271 Status register read (0x" << hex << int(StatusReg) << dec << ")\n"; */ return(StatusReg); break; case 1: /*cout << "8271 Result register read (0x" << hex << int(ResultReg) << dec << ")\n"; */ StatusReg &=~18; /* Clear interrupt request and result reg full flag*/ UPDATENMISTATUS; return(ResultReg); break; case 4: /*cout << "8271 data register read\n"; */ StatusReg &= ~0xc; /* Clear interrupt and non-dma request - not stated but DFS never looks at result reg!*/ UPDATENMISTATUS; return(DataReadReg); break; default: // cout << "8271: Read to unknown register address=" << Address << "\n"; break; }; /* Address switch */ return(0); }; /* Disc8271_read */ /*--------------------------------------------------------------------------*/ static void CommandRegWrite(int Value) { PrimaryCommandLookupType *ptr=CommandPtrFromNumber(Value); /*cout << "8271: Command register write value=0x" << hex << Value << dec << "(Name=" << ptr->Ident << ")\n"; */ ThisCommand=Value; NParamsInThisCommand=ptr->NParams; PresentParam=0; StatusReg|=0x90; /* Observed on beeb for read special */ UPDATENMISTATUS; /* No parameters then call routine immediatly */ if (NParamsInThisCommand==0) { StatusReg&=0x7e; UPDATENMISTATUS; ptr->ToCall(); }; }; /* CommandRegWrite */ /*--------------------------------------------------------------------------*/ static void ParamRegWrite(int Value) { if (PresentParam>=NParamsInThisCommand) { //cout << "8271: Unwanted parameter register write value=0x" << hex << Value << dec << "\n"; } else { Params[PresentParam++]=Value; StatusReg&=0xfe; /* Observed on beeb */ UPDATENMISTATUS; if (PresentParam>=NParamsInThisCommand) { int tmp; StatusReg&=0x7e; /* Observed on beeb */ UPDATENMISTATUS; PrimaryCommandLookupType *ptr=CommandPtrFromNumber(ThisCommand); // cout << ""; /* cout << "8271: All parameters arrived for '" << ptr->Ident; for(tmp=0;tmpToCall(); }; /* Got all params yet? */ } /* Parameter wanted ? */ }; /* ParamRegWrite */ /*--------------------------------------------------------------------------*/ /* Address is in the range 0-7 - with the fe80 etc stripped out */ void Disc8271_write(int Address, int Value) { switch (Address) { case 0: CommandRegWrite(Value); break; case 1: ParamRegWrite(Value); break; case 2: // cout << "8271: Reset register write, value=0x" << hex << Value << dec << "\n"; /* The caller should write a 1 and then >11 cycles later a 0 - but I'm just going to reset on both edges */ Disc8271_reset(); break; case 4: // cout << "8271: data register write, value=0x" << hex << Value << dec << "\n"; break; default: // cout << "8271: Write to unknown register address=" << Address << ", value=0x" << hex << Value << dec << "\n"; break; }; /* Address switch */ }; /* Disc8271_write */ /*--------------------------------------------------------------------------*/ void Disc8271_poll_real(void) { ClearTrigger(Disc8271Trigger); PrimaryCommandLookupType *comptr; /* Set the interrupt flag in the status register */ StatusReg|=8; UPDATENMISTATUS; if (NextInterruptIsErr) { ResultReg=NextInterruptIsErr; StatusReg=0x18; /* ResultReg full and interrupt */ UPDATENMISTATUS; NextInterruptIsErr=0; } else { /* Should only happen while a command is still active */ comptr=CommandPtrFromNumber(ThisCommand); if (comptr->IntHandler!=NULL) comptr->IntHandler(); }; }; /* Disc8271_poll */ /*--------------------------------------------------------------------------*/ // // single sided 40 or 80 track disk // directory at 0 & 1 // /*--------------------------------------------------------------------------*/ static void LoadSimpleDiscImage(char *FileName, int DriveNum,int HeadNum, int Tracks,int offset) { // FILE *infile=fopen(FileName,"rb"); // fseek(infile,offset,SEEK_SET); int CurrentTrack,CurrentSector; SectorType *SecPtr; int size; // if (!infile) // { // cout << "Could not open disc file " << FileName << "\n"; // return; // }; size=load_file(FileName,discbuffer,MAX_DISC_SIZE); // Tracks=size/(256*10); mmreset(); for(CurrentTrack=0;CurrentTrackmax) len=max; fseek(f,0,SEEK_SET); fread(mem,1,len,f); fclose(f); return(len); }