BizHawk/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Disk/NECUPD765.FDD.cs

894 lines
29 KiB
C#

using BizHawk.Common;
using BizHawk.Common.NumberExtensions;
using System;
using System.Linq;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>
/// Floppy drive related stuff
/// </summary>
#region Attribution
/*
Implementation based on the information contained here:
http://www.cpcwiki.eu/index.php/765_FDC
and here:
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
*/
#endregion
public partial class NECUPD765 : IFDDHost
{
#region Drive State
/// <summary>
/// FDD Flag - motor on/off
/// </summary>
public bool FDD_FLAG_MOTOR;
/// <summary>
/// The index of the currently active disk drive
/// </summary>
public int DiskDriveIndex
{
get { return _diskDriveIndex; }
set
{
// when index is changed update the ActiveDrive
_diskDriveIndex = value;
ActiveDrive = DriveStates[_diskDriveIndex];
}
}
private int _diskDriveIndex = 0;
/// <summary>
/// The currently active drive
/// </summary>
private DriveState ActiveDrive;
/// <summary>
/// Array that holds state information for each possible drive
/// </summary>
private DriveState[] DriveStates = new DriveState[4];
#endregion
#region FDD Methods
/// <summary>
/// Initialization / reset of the floppy drive subsystem
/// </summary>
private void FDD_Init()
{
for (int i = 0; i < 4; i++)
{
DriveState ds = new DriveState(i, this);
DriveStates[i] = ds;
}
}
/// <summary>
/// Searches for the requested sector
/// </summary>
/// <returns></returns>
private FloppyDisk.Sector GetSector()
{
FloppyDisk.Sector sector = null;
// get the current track
var trk = ActiveDrive.Disk.DiskTracks[ActiveDrive.TrackIndex];
// get the current sector index
int index = ActiveDrive.SectorIndex;
// make sure this index exists
if (index > trk.Sectors.Length)
{
index = 0;
}
// index hole count
int iHole = 0;
// loop through the sectors in a track
// the loop ends with either the sector being found
// or the index hole being passed twice
while (iHole <= 2)
{
// does the requested sector match the current sector
if (trk.Sectors[index].SectorIDInfo.C == ActiveCommandParams.Cylinder &&
trk.Sectors[index].SectorIDInfo.H == ActiveCommandParams.Head &&
trk.Sectors[index].SectorIDInfo.R == ActiveCommandParams.Sector &&
trk.Sectors[index].SectorIDInfo.N == ActiveCommandParams.SectorSize)
{
// sector has been found
sector = trk.Sectors[index];
UnSetBit(SR2_BC, ref Status2);
UnSetBit(SR2_WC, ref Status2);
break;
}
// check for bad cylinder
if (trk.Sectors[index].SectorIDInfo.C == 255)
{
SetBit(SR2_BC, ref Status2);
}
// check for no cylinder
else if (trk.Sectors[index].SectorIDInfo.C != ActiveCommandParams.Cylinder)
{
SetBit(SR2_WC, ref Status2);
}
// incrememnt sector index
index++;
// have we reached the index hole?
if (trk.Sectors.Length <= index)
{
// wrap around
index = 0;
iHole++;
}
}
// search loop has completed and the sector may or may not have been found
// bad cylinder detected?
if (Status2.Bit(SR2_BC))
{
// remove WC
UnSetBit(SR2_WC, ref Status2);
}
// update sectorindex on drive
ActiveDrive.SectorIndex = index;
return sector;
}
#endregion
#region IFDDHost
// IFDDHost methods that fall through to the currently active drive
/// <summary>
/// Parses a new disk image and loads it into this floppy drive
/// </summary>
/// <param name="tapeData"></param>
public void FDD_LoadDisk(byte[] diskData)
{
// we are only going to load into the first drive
DriveStates[0].FDD_LoadDisk(diskData);
}
/// <summary>
/// Ejects the current disk
/// </summary>
public void FDD_EjectDisk()
{
DriveStates[0].FDD_EjectDisk();
}
/// <summary>
/// Signs whether the current active drive has a disk inserted
/// </summary>
public bool FDD_IsDiskLoaded
{
get { return DriveStates[DiskDriveIndex].FDD_IsDiskLoaded; }
}
/// <summary>
/// Returns the disk object from drive 0
/// </summary>
public FloppyDisk DiskPointer
{
get { return DriveStates[0].Disk; }
}
public FloppyDisk Disk { get; set; }
#endregion
#region Drive Status Class
/// <summary>
/// Holds specfic state information about a drive
/// </summary>
private class DriveState : IFDDHost
{
#region State
/// <summary>
/// The drive ID from an FDC perspective
/// </summary>
public int ID;
/// <summary>
/// Signs whether this drive ready
/// TRUE if both drive exists and has a disk inserted
/// </summary>
public bool FLAG_READY
{
get
{
if (!FDD_IsDiskLoaded || Disk.GetTrackCount() == 0 || !FDC.FDD_FLAG_MOTOR)
return false;
else
return true;
}
}
/// <summary>
/// Disk is write protected (TRUE BY DEFAULT)
/// </summary>
public bool FLAG_WRITEPROTECT = false;
/// <summary>
/// Storage for seek steps
/// One step for each indexpulse (track index) until seeked track
/// </summary>
public int SeekCounter;
/// <summary>
/// Seek status
/// </summary>
public int SeekStatus;
/// <summary>
/// Age counter
/// </summary>
public int SeekAge;
/// <summary>
/// The current side
/// </summary>
public byte CurrentSide;
/// <summary>
/// The current track index in the DiskTracks array
/// </summary>
public byte TrackIndex;
/// <summary>
/// The track ID of the current cylinder
/// </summary>
public byte CurrentTrackID
{
get
{
// default invalid track
int id = 0xff;
if (Disk == null)
return (byte)id;
if (Disk.DiskTracks.Count() == 0)
return (byte)id;
if (TrackIndex >= Disk.GetTrackCount())
TrackIndex = 0;
else if (TrackIndex < 0)
TrackIndex = 0;
var track = Disk.DiskTracks[TrackIndex];
id = track.TrackNumber;
return (byte)id;
}
set
{
for (int i = 0; i < Disk.GetTrackCount(); i++)
{
if (Disk.DiskTracks[i].TrackNumber == value)
{
TrackIndex = (byte)i;
break;
}
}
}
}
/// <summary>
/// The new track that the drive is seeking to
/// (used in seek operations)
/// </summary>
public int SeekingTrack;
/// <summary>
/// The current sector index in the Sectors array
/// </summary>
public int SectorIndex;
/// <summary>
/// The currently loaded floppy disk
/// </summary>
public FloppyDisk Disk { get; set; }
/// <summary>
/// The parent controller
/// </summary>
private NECUPD765 FDC;
#endregion
#region Lookups
/// <summary>
/// TRUE if we are on track 0
/// </summary>
public bool FLAG_TRACK0
{
get
{
if (TrackIndex == 0) { return true; }
else { return false; }
}
}
#endregion
#region Public Methods
/*
/// <summary>
/// Moves the head across the disk cylinders
/// </summary>
/// <param name="direction"></param>
/// <param name="cylinderCount"></param>
public void MoveHead(SkipDirection direction, int cylinderCount)
{
// get total tracks
int trackCount = Disk.DiskTracks.Count();
int trk = 0;
switch (direction)
{
case SkipDirection.Increment:
trk = (int)CurrentTrack + cylinderCount;
if (trk >= trackCount)
{
// past the last track
trk = trackCount - 1;
}
else if (trk < 0)
trk = 0;
break;
case SkipDirection.Decrement:
trk = (int)CurrentTrack - cylinderCount;
if (trk < 0)
{
// before the first track
trk = 0;
}
else if (trk >= trackCount)
trk = trackCount - 1;
break;
}
// move the head
CurrentTrack = (byte)trk;
}
*/
/*
/// <summary>
/// Finds a supplied sector
/// </summary>
/// <param name="resBuffer"></param>
/// <param name=""></param>
/// <param name=""></param>
/// <returns></returns>
public FloppyDisk.Sector FindSector(ref byte[] resBuffer, CommandParameters prms)
{
int index =CurrentSector;
int lc = 0;
FloppyDisk.Sector sector = null;
bool found = false;
do
{
sector = Disk.DiskTracks[CurrentTrack].Sectors[index];
if (sector != null && sector.SectorID == prms.Sector)
{
// sector found
// check for data errors
if ((sector.Status1 & 0x20) != 0 || (sector.Status2 & 0x20) != 0)
{
// data errors found
}
found = true;
break;
}
// sector doesnt match
var c = Disk.DiskTracks[CurrentTrack].Sectors[index].TrackNumber;
if (c == 255)
{
// bad cylinder
resBuffer[RS_ST2] |= 0x02;
}
else if (prms.Cylinder != c)
{
// cylinder mismatch
resBuffer[RS_ST2] |= 0x10;
}
// increment index
index++;
if (index >= Disk.DiskTracks[CurrentTrack].NumberOfSectors)
{
// out of bounds
index = 0;
lc++;
}
} while (lc < 2);
if ((resBuffer[RS_ST2] & 0x02) != 0)
{
// bad cylinder set - remove no cylinder
UnSetBit(SR2_WC, ref resBuffer[RS_ST2]);
}
// update current sector
CurrentSector = index;
if (found)
return sector;
else
return null;
}
/// <summary>
/// Populates a result buffer
/// </summary>
/// <param name="resBuffer"></param>
/// <param name="chrn"></param>
public void FillResult(ref byte[] resBuffer, CHRN chrn)
{
// clear results
resBuffer[RS_ST0] = 0;
resBuffer[RS_ST1] = 0;
resBuffer[RS_ST2] = 0;
resBuffer[RS_C] = 0;
resBuffer[RS_H] = 0;
resBuffer[RS_R] = 0;
resBuffer[RS_N] = 0;
if (chrn == null)
{
// no chrn supplied
resBuffer[RS_ST0] = ST0;
resBuffer[RS_ST1] = 0;
resBuffer[RS_ST2] = 0;
resBuffer[RS_C] = 0;
resBuffer[RS_H] = 0;
resBuffer[RS_R] = 0;
resBuffer[RS_N] = 0;
}
}
/// <summary>
/// Populates the result buffer with ReadID data
/// </summary>
/// <returns></returns>
public void ReadID(ref byte[] resBuffer)
{
if (CheckDriveStatus() == false)
{
// drive not ready
resBuffer[RS_ST0] = ST0;
return;
}
var track = Disk.DiskTracks.Where(a => a.TrackNumber == CurrentTrack).FirstOrDefault();
if (track != null && track.NumberOfSectors > 0)
{
// formatted track
// get the current sector
int index = CurrentSector;
// is the index out of bounds?
if (index >= track.NumberOfSectors)
{
// reset the index
index = 0;
}
// read the sector data
var data = track.Sectors[index];
resBuffer[RS_C] = data.TrackNumber;
resBuffer[RS_H] = data.SideNumber;
resBuffer[RS_R] = data.SectorID;
resBuffer[RS_N] = data.SectorSize;
resBuffer[RS_ST0] = ST0;
// increment the current sector
CurrentSector = index + 1;
return;
}
else
{
// unformatted track?
resBuffer[RS_C] = FDC.CommBuffer[CM_C];
resBuffer[RS_H] = FDC.CommBuffer[CM_H];
resBuffer[RS_R] = FDC.CommBuffer[CM_R];
resBuffer[RS_N] = FDC.CommBuffer[CM_N];
SetBit(SR0_IC0, ref ST0);
resBuffer[RS_ST0] = ST0;
resBuffer[RS_ST1] = 0x01;
return;
}
}
*/
/*
/// <summary>
/// The drive performs a seek operation if necessary
/// Return value TRUE indicates seek complete
/// </summary>
public void DoSeek()
{
if (CurrentState != DriveMainState.Recalibrate &&
CurrentState != DriveMainState.Seek)
{
// no seek/recalibrate has been asked for
return;
}
if (GetBit(ID, FDC.StatusMain))
{
// drive is already seeking
return;
}
RunSeekCycle();
}
/// <summary>
/// Runs a seek cycle
/// </summary>
public void RunSeekCycle()
{
for (;;)
{
switch (SeekState)
{
// seek or recalibrate has been requested
case SeekSubState.Idle:
if (CurrentState == DriveMainState.Recalibrate)
{
// recalibrate always seeks to track 0
SeekingTrack = 0;
}
SeekState = SeekSubState.MoveInit;
// mark drive as busy
// this should be cleared by SIS command
SetBit(ID, ref FDC.StatusMain);
break;
// setup for the head move
case SeekSubState.MoveInit:
if (CurrentTrack == SeekingTrack)
{
// we are already at the required track
if (CurrentState == DriveMainState.Recalibrate &&
!FLAG_TRACK0)
{
// recalibration fail
SeekIntState = SeekIntStatus.Abnormal;
// raise seek interrupt
FDC.ActiveInterrupt = InterruptState.Seek;
// unset DB bit
UnSetBit(ID, ref FDC.StatusMain);
// equipment check
SetBit(SR0_EC, ref FDC.Status0);
SeekState = SeekSubState.PerformCompletion;
break;
}
if (CurrentState == DriveMainState.Recalibrate &&
FLAG_TRACK0)
{
// recalibration success
SeekIntState = SeekIntStatus.Normal;
// raise seek interrupt
FDC.ActiveInterrupt = InterruptState.Seek;
// unset DB bit
UnSetBit(ID, ref FDC.StatusMain);
SeekState = SeekSubState.PerformCompletion;
break;
}
}
// check for error
if (IntStatus >= IC_ABORTED_DISCREMOVED || Disk == null)
{
// drive not ready
FLAG_READY = false;
// drive not ready
SeekIntState = SeekIntStatus.DriveNotReady;
// cancel any interrupt
FDC.ActiveInterrupt = InterruptState.None;
// unset DB bit
UnSetBit(ID, ref FDC.StatusMain);
SeekState = SeekSubState.PerformCompletion;
break;
}
if (SeekCounter > 1)
{
// not ready to seek yet
SeekCounter--;
return;
}
if (FDC.SRT < 1 && CurrentTrack != SeekingTrack)
{
SeekState = SeekSubState.MoveImmediate;
break;
}
// head move
SeekState = SeekSubState.HeadMove;
break;
case SeekSubState.HeadMove:
// do the seek
SeekCounter = FDC.SRT;
if (CurrentTrack < SeekingTrack)
{
// we are seeking forward
var delta = SeekingTrack - CurrentTrack;
MoveHead(SkipDirection.Increment, 1);
}
else if (CurrentTrack > SeekingTrack)
{
// we are seeking backward
var delta = CurrentTrack - SeekingTrack;
MoveHead(SkipDirection.Decrement, 1);
}
// should the seek be completed now?
if (CurrentTrack == SeekingTrack)
{
SeekState = SeekSubState.PerformCompletion;
break;
}
// seek not finished yet
return;
// seek emulation processed immediately
case SeekSubState.MoveImmediate:
if (CurrentTrack < SeekingTrack)
{
// we are seeking forward
var delta = SeekingTrack - CurrentTrack;
MoveHead(SkipDirection.Increment, delta);
}
else if (CurrentTrack > SeekingTrack)
{
// we are seeking backward
var delta = CurrentTrack - SeekingTrack;
MoveHead(SkipDirection.Decrement, delta);
}
SeekState = SeekSubState.PerformCompletion;
break;
case SeekSubState.PerformCompletion:
SeekDone();
SeekState = SeekSubState.SeekCompleted;
break;
case SeekSubState.SeekCompleted:
// seek has already completed
return;
}
}
}
/// <summary>
/// Called when a seek operation has completed
/// </summary>
public void SeekDone()
{
SeekCounter = 0;
SeekingTrack = CurrentTrack;
// generate ST0 register data
// get only the IC bits
IntStatus &= IC_ABORTED_DISCREMOVED;
// drive ready?
if (!FLAG_READY)
{
SetBit(SR0_NR, ref IntStatus);
SetBit(SR0_EC, ref IntStatus);
// are we recalibrating?
if (CurrentState == DriveMainState.Recalibrate)
{
SetBit(SR0_EC, ref IntStatus);
}
}
// set seek end
SetBit(SR0_SE, ref IntStatus);
/*
// head address
if (CurrentSide > 0)
{
SetBit(SR0_HD, ref IntStatus);
// drive only supports 1 head
// set the EC bit
SetBit(SR0_EC, ref IntStatus);
}
*/
/*
// UnitSelect
SetUnitSelect(ID, ref IntStatus);
// move to none state
//CurrentState = DriveMainState.None;
//SeekState = SeekSubState.SeekCompleted;
// set the seek interrupt flag for this drive
// this will be cleared at the next successful senseint
FLAG_SEEK_INTERRUPT = true;
//CurrentState = DriveMainState.None;
}
*/
#endregion
#region Construction
public DriveState(int driveID, NECUPD765 fdc)
{
ID = driveID;
FDC = fdc;
}
#endregion
#region IFDDHost
/// <summary>
/// Parses a new disk image and loads it into this floppy drive
/// </summary>
/// <param name="tapeData"></param>
public void FDD_LoadDisk(byte[] diskData)
{
// try dsk first
FloppyDisk fdd = null;
bool found = false;
foreach (DiskType type in Enum.GetValues(typeof(DiskType)))
{
switch (type)
{
case DiskType.CPCExtended:
fdd = new CPCExtendedFloppyDisk();
found = fdd.ParseDisk(diskData);
break;
case DiskType.CPC:
fdd = new CPCFloppyDisk();
found = fdd.ParseDisk(diskData);
break;
}
if (found)
{
Disk = fdd;
break;
}
}
if (!found)
{
throw new Exception(this.GetType().ToString() +
"\n\nDisk image file could not be parsed. Potentially an unknown format.");
}
}
/// <summary>
/// Ejects the current disk
/// </summary>
public void FDD_EjectDisk()
{
Disk = null;
//FLAG_READY = false;
}
/// <summary>
/// Signs whether the current active drive has a disk inserted
/// </summary>
public bool FDD_IsDiskLoaded
{
get
{
if (Disk != null)
return true;
else
return false;
}
}
#endregion
#region StateSerialization
public void SyncState(Serializer ser)
{
ser.Sync("ID", ref ID);
ser.Sync("FLAG_WRITEPROTECT", ref FLAG_WRITEPROTECT);
//ser.Sync("FLAG_DISKCHANGED", ref FLAG_DISKCHANGED);
//ser.Sync("FLAG_RECALIBRATING", ref FLAG_RECALIBRATING);
//ser.Sync("FLAG_SEEK_INTERRUPT", ref FLAG_SEEK_INTERRUPT);
//ser.Sync("IntStatus", ref IntStatus);
//ser.Sync("ST0", ref ST0);
//ser.Sync("RecalibrationCounter", ref RecalibrationCounter);
ser.Sync("SeekCounter", ref SeekCounter);
ser.Sync("SeekStatus", ref SeekStatus);
ser.Sync("SeekAge", ref SeekAge);
ser.Sync("CurrentSide", ref CurrentSide);
//ser.Sync("CurrentTrack", ref CurrentTrack);
ser.Sync("TrackIndex", ref TrackIndex);
ser.Sync("SeekingTrack", ref SeekingTrack);
//ser.Sync("CurrentSector", ref CurrentSector);
ser.Sync("SectorIndex", ref SectorIndex);
//ser.Sync("RAngles", ref RAngles);
//ser.Sync("DataPointer", ref DataPointer);
//ser.SyncEnum("CurrentState", ref CurrentState);
//ser.SyncEnum("SeekState", ref SeekState);
//ser.SyncEnum("SeekIntState", ref SeekIntState);
}
#endregion
}
#endregion
}
}