2842 lines
106 KiB
C#
2842 lines
106 KiB
C#
using BizHawk.Common.NumberExtensions;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
|
|
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|
{
|
|
/// <summary>
|
|
/// FDC State and Methods
|
|
/// </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
|
|
{
|
|
#region Controller State
|
|
|
|
/// <summary>
|
|
/// Signs whether the drive is active
|
|
/// </summary>
|
|
public bool DriveLight;
|
|
|
|
/// <summary>
|
|
/// Collection of possible commands
|
|
/// </summary>
|
|
private List<Command> CommandList;
|
|
|
|
/// <summary>
|
|
/// State parameters relating to the Active command
|
|
/// </summary>
|
|
public CommandParameters ActiveCommandParams = new CommandParameters();
|
|
|
|
/// <summary>
|
|
/// The current active phase of the controller
|
|
/// </summary>
|
|
private Phase ActivePhase = Phase.Command;
|
|
|
|
/// <summary>
|
|
/// The currently active interrupt
|
|
/// </summary>
|
|
private InterruptState ActiveInterrupt = InterruptState.None;
|
|
/// <summary>
|
|
/// Command buffer
|
|
/// This does not contain the initial command byte (only parameter bytes)
|
|
/// </summary>
|
|
private byte[] CommBuffer = new byte[9];
|
|
|
|
/// <summary>
|
|
/// Current index within the command buffer
|
|
/// </summary>
|
|
private int CommCounter = 0;
|
|
|
|
/// <summary>
|
|
/// Initial command byte flag
|
|
/// Bit7 Multi Track (continue multi-sector-function on other head)
|
|
/// </summary>
|
|
private bool CMD_FLAG_MT;
|
|
|
|
/// <summary>
|
|
/// Initial command byte flag
|
|
/// Bit6 MFM-Mode-Bit (Default 1=Double Density)
|
|
/// </summary>
|
|
private bool CMD_FLAG_MF;
|
|
|
|
/// <summary>
|
|
/// Initial command byte flag
|
|
/// Bit5 Skip-Bit (set if secs with deleted DAM shall be skipped)
|
|
/// </summary>
|
|
private bool CMD_FLAG_SK;
|
|
|
|
/// <summary>
|
|
/// Step Rate Time (supplied via the specify command)
|
|
/// SRT stands for the steooino rate for the FDD ( 1 to 16 ms in 1 ms increments).
|
|
/// Stepping rate applies to all drives(FH= 1ms, EH= 2ms, etc.).
|
|
/// </summary>
|
|
private int SRT;
|
|
|
|
/// <summary>
|
|
/// Keeps track of the current SRT state
|
|
/// </summary>
|
|
private int SRT_Counter;
|
|
|
|
/// <summary>
|
|
/// Head Unload Time (supplied via the specify command)
|
|
/// HUT stands for the head unload time after a Read or Write operation has occurred
|
|
/// (16 to 240 ms in 16 ms Increments)
|
|
/// </summary>
|
|
private int HUT;
|
|
|
|
/// <summary>
|
|
/// Keeps track of the current HUT state
|
|
/// </summary>
|
|
private int HUT_Counter;
|
|
|
|
/// <summary>
|
|
/// Head load Time (supplied via the specify command)
|
|
/// HLT stands for the head load time in the FDD (2 to 254 ms in 2 ms Increments)
|
|
/// </summary>
|
|
private int HLT;
|
|
|
|
/// <summary>
|
|
/// Keeps track of the current HLT state
|
|
/// </summary>
|
|
private int HLT_Counter;
|
|
|
|
/// <summary>
|
|
/// Non-DMA Mode (supplied via the specify command)
|
|
/// ND stands for operation in the non-DMA mode
|
|
/// </summary>
|
|
private bool ND;
|
|
|
|
/// <summary>
|
|
/// In lieu of actual timing, this will count status reads in execution phase
|
|
/// where the CPU hasnt actually read any bytes
|
|
/// </summary>
|
|
private int OverrunCounter;
|
|
|
|
/// <summary>
|
|
/// Contains result bytes in result phase
|
|
/// </summary>
|
|
private byte[] ResBuffer = new byte[7];
|
|
|
|
/// <summary>
|
|
/// Contains sector data to be written/read in execution phase
|
|
/// </summary>
|
|
private byte[] ExecBuffer = new byte[0x8000];
|
|
|
|
/// <summary>
|
|
/// Interrupt result buffer
|
|
/// Persists (and returns when needed) the last result data when a sense interrupt status command happens
|
|
/// </summary>
|
|
private byte[] InterruptResultBuffer = new byte[2];
|
|
|
|
/// <summary>
|
|
/// Current index within the result buffer
|
|
/// </summary>
|
|
private int ResCounter = 0;
|
|
|
|
/// <summary>
|
|
/// The byte length of the currently active command
|
|
/// This may or may not be the same as the actual command resultbytes value
|
|
/// </summary>
|
|
private int ResLength = 0;
|
|
|
|
/// <summary>
|
|
/// Index for sector data within the result buffer
|
|
/// </summary>
|
|
private int ExecCounter = 0;
|
|
|
|
/// <summary>
|
|
/// The length of the current exec command
|
|
/// </summary>
|
|
private int ExecLength = 0;
|
|
|
|
/// <summary>
|
|
/// The last write byte that was received during execution phase
|
|
/// </summary>
|
|
private byte LastSectorDataWriteByte = 0;
|
|
|
|
/// <summary>
|
|
/// The last read byte to be sent during execution phase
|
|
/// </summary>
|
|
private byte LastSectorDataReadByte = 0;
|
|
|
|
/// <summary>
|
|
/// The last parameter byte that was written to the FDC
|
|
/// </summary>
|
|
private byte LastByteReceived = 0;
|
|
|
|
/// <summary>
|
|
/// Delay for reading sector
|
|
/// </summary>
|
|
private int SectorDelayCounter = 0;
|
|
|
|
/// <summary>
|
|
/// The phyical sector ID
|
|
/// </summary>
|
|
private int SectorID = 0;
|
|
|
|
/// <summary>
|
|
/// Counter for index pulses
|
|
/// </summary>
|
|
private int IndexPulseCounter;
|
|
|
|
/// <summary>
|
|
/// Specifies the index of the currently selected command (in the CommandList)
|
|
/// </summary>
|
|
public int CMDIndex
|
|
{
|
|
get { return _cmdIndex; }
|
|
set
|
|
{
|
|
_cmdIndex = value;
|
|
ActiveCommand = CommandList[_cmdIndex];
|
|
}
|
|
}
|
|
private int _cmdIndex;
|
|
|
|
/// <summary>
|
|
/// The currently active command
|
|
/// </summary>
|
|
private Command ActiveCommand;
|
|
|
|
/// <summary>
|
|
/// Main status register (accessed via reads to port 0x2ffd)
|
|
/// </summary>
|
|
/*
|
|
b0..3 DB FDD0..3 Busy (seek/recalib active, until succesful sense intstat)
|
|
b4 CB FDC Busy (still in command-, execution- or result-phase)
|
|
b5 EXM Execution Mode (still in execution-phase, non_DMA_only)
|
|
b6 DIO Data Input/Output (0=CPU->FDC, 1=FDC->CPU) (see b7)
|
|
b7 RQM Request For Master (1=ready for next byte) (see b6 for direction)
|
|
*/
|
|
private byte StatusMain;
|
|
|
|
/// <summary>
|
|
/// Status Register 0
|
|
/// </summary>
|
|
/*
|
|
b0,1 US Unit Select (driveno during interrupt)
|
|
b2 HD Head Address (head during interrupt)
|
|
b3 NR Not Ready (drive not ready or non-existing 2nd head selected)
|
|
b4 EC Equipment Check (drive failure or recalibrate failed (retry))
|
|
b5 SE Seek End (Set if seek-command completed)
|
|
b6,7 IC Interrupt Code (0=OK, 1=aborted:readfail/OK if EN, 2=unknown cmd
|
|
or senseint with no int occured, 3=aborted:disc removed etc.)
|
|
*/
|
|
private byte Status0;
|
|
|
|
/// <summary>
|
|
/// Status Register 1
|
|
/// </summary>
|
|
/*
|
|
b0 MA Missing Address Mark (Sector_ID or DAM not found)
|
|
b1 NW Not Writeable (tried to write/format disc with wprot_tab=on)
|
|
b2 ND No Data (Sector_ID not found, CRC fail in ID_field)
|
|
b3,6 0 Not used
|
|
b4 OR Over Run (CPU too slow in execution-phase (ca. 26us/Byte))
|
|
b5 DE Data Error (CRC-fail in ID- or Data-Field)
|
|
b7 EN End of Track (set past most read/write commands) (see IC)
|
|
*/
|
|
private byte Status1;
|
|
|
|
/// <summary>
|
|
/// Status Register 2
|
|
/// </summary>
|
|
/*
|
|
b0 MD Missing Address Mark in Data Field (DAM not found)
|
|
b1 BC Bad Cylinder (read/programmed track-ID different and read-ID = FF)
|
|
b2 SN Scan Not Satisfied (no fitting sector found)
|
|
b3 SH Scan Equal Hit (equal)
|
|
b4 WC Wrong Cylinder (read/programmed track-ID different) (see b1)
|
|
b5 DD Data Error in Data Field (CRC-fail in data-field)
|
|
b6 CM Control Mark (read/scan command found sector with deleted DAM)
|
|
b7 0 Not Used
|
|
*/
|
|
private byte Status2;
|
|
|
|
/// <summary>
|
|
/// Status Register 3
|
|
/// </summary>
|
|
/*
|
|
b0,1 US Unit Select (pin 28,29 of FDC)
|
|
b2 HD Head Address (pin 27 of FDC)
|
|
b3 TS Two Side (0=yes, 1=no (!))
|
|
b4 T0 Track 0 (on track 0 we are)
|
|
b5 RY Ready (drive ready signal)
|
|
b6 WP Write Protected (write protected)
|
|
b7 FT Fault (if supported: 1=Drive failure)
|
|
*/
|
|
private byte Status3;
|
|
|
|
#endregion
|
|
|
|
#region UPD Internal Functions
|
|
|
|
#region READ Commands
|
|
|
|
/// <summary>
|
|
/// Read Data
|
|
/// COMMAND: 8 parameter bytes
|
|
/// EXECUTION: Data transfer between FDD and FDC
|
|
/// RESULT: 7 result bytes
|
|
/// </summary>
|
|
private void UPD_ReadData()
|
|
{
|
|
switch (ActivePhase)
|
|
{
|
|
//----------------------------------------
|
|
// FDC is waiting for a command byte
|
|
//----------------------------------------
|
|
case Phase.Idle:
|
|
break;
|
|
|
|
//----------------------------------------
|
|
// Receiving command parameter bytes
|
|
//----------------------------------------
|
|
case Phase.Command:
|
|
|
|
// store the parameter in the command buffer
|
|
CommBuffer[CommCounter] = LastByteReceived;
|
|
|
|
// process parameter byte
|
|
ParseParamByteStandard(CommCounter);
|
|
|
|
// increment command parameter counter
|
|
CommCounter++;
|
|
|
|
// was that the last parameter byte?
|
|
if (CommCounter == ActiveCommand.ParameterByteCount)
|
|
{
|
|
// all parameter bytes received - setup for execution phase
|
|
|
|
// clear exec buffer and status registers
|
|
ClearExecBuffer();
|
|
Status0 = 0;
|
|
Status1 = 0;
|
|
Status2 = 0;
|
|
Status3 = 0;
|
|
|
|
// temp sector index
|
|
byte secIdx = ActiveCommandParams.Sector;
|
|
|
|
// hack for when another drive (non-existent) is being called
|
|
if (ActiveDrive.ID != 0)
|
|
DiskDriveIndex = 0;
|
|
|
|
// do we have a valid disk inserted?
|
|
if (!ActiveDrive.FLAG_READY)
|
|
{
|
|
// no disk, no tracks or motor is not on
|
|
SetBit(SR0_IC0, ref Status0);
|
|
SetBit(SR0_NR, ref Status0);
|
|
|
|
CommitResultCHRN();
|
|
CommitResultStatus();
|
|
//ResBuffer[RS_ST0] = Status0;
|
|
|
|
// move to result phase
|
|
ActivePhase = Phase.Result;
|
|
break;
|
|
}
|
|
|
|
int buffPos = 0;
|
|
int sectorSize = 0;
|
|
int maxTransferCap = 0;
|
|
|
|
// calculate requested size of data required
|
|
if (ActiveCommandParams.SectorSize == 0)
|
|
{
|
|
// When N=0, then DTL defines the data length which the FDC must treat as a sector. If DTL is smaller than the actual
|
|
// data length in a sector, the data beyond DTL in the sector is not sent to the Data Bus. The FDC reads (internally)
|
|
// the complete sector performing the CRC check and, depending upon the manner of command termination, may perform
|
|
// a Multi-Sector Read Operation.
|
|
sectorSize = ActiveCommandParams.DTL;
|
|
|
|
// calculate maximum transfer capacity
|
|
if (!CMD_FLAG_MF)
|
|
maxTransferCap = 3328;
|
|
}
|
|
else
|
|
{
|
|
// When N is non - zero, then DTL has no meaning and should be set to ffh
|
|
ActiveCommandParams.DTL = 0xFF;
|
|
|
|
// calculate maximum transfer capacity
|
|
switch (ActiveCommandParams.SectorSize)
|
|
{
|
|
case 1:
|
|
if (CMD_FLAG_MF)
|
|
maxTransferCap = 6656;
|
|
else
|
|
maxTransferCap = 3840;
|
|
break;
|
|
case 2:
|
|
if (CMD_FLAG_MF)
|
|
maxTransferCap = 7680;
|
|
else
|
|
maxTransferCap = 4096;
|
|
break;
|
|
case 3:
|
|
if (CMD_FLAG_MF)
|
|
maxTransferCap = 8192;
|
|
else
|
|
maxTransferCap = 4096;
|
|
break;
|
|
}
|
|
|
|
sectorSize = 0x80 << ActiveCommandParams.SectorSize;
|
|
}
|
|
|
|
// get the current track
|
|
var track = ActiveDrive.Disk.DiskTracks.Where(a => a.TrackNumber == ActiveDrive.CurrentTrackID).FirstOrDefault();
|
|
|
|
if (track == null || track.NumberOfSectors <= 0)
|
|
{
|
|
// track could not be found
|
|
SetBit(SR0_IC0, ref Status0);
|
|
SetBit(SR0_NR, ref Status0);
|
|
|
|
CommitResultCHRN();
|
|
CommitResultStatus();
|
|
|
|
//ResBuffer[RS_ST0] = Status0;
|
|
|
|
// move to result phase
|
|
ActivePhase = Phase.Result;
|
|
break;
|
|
}
|
|
|
|
FloppyDisk.Sector sector = null;
|
|
|
|
// sector read loop
|
|
for (;;)
|
|
{
|
|
bool terminate = false;
|
|
|
|
// lookup the sector
|
|
sector = GetSector();
|
|
|
|
if (sector == null)
|
|
{
|
|
// sector was not found after two passes of the disk index hole
|
|
SetBit(SR1_ND, ref Status1);
|
|
SetBit(SR0_IC0, ref Status0);
|
|
UnSetBit(SR0_IC1, ref Status0);
|
|
|
|
// result requires the actual track id, rather than the sector track id
|
|
ActiveCommandParams.Cylinder = track.TrackNumber;
|
|
|
|
CommitResultCHRN();
|
|
CommitResultStatus();
|
|
ActivePhase = Phase.Result;
|
|
break;
|
|
}
|
|
|
|
// sector ID was found on this track
|
|
|
|
// get status regs from sector
|
|
Status1 = sector.Status1;
|
|
Status2 = sector.Status2;
|
|
|
|
// we dont need EN
|
|
UnSetBit(SR1_EN, ref Status1);
|
|
|
|
// If SK=1, the FDC skips the sector with the Deleted Data Address Mark and reads the next sector.
|
|
// The CRC bits in the deleted data field are not checked when SK=1
|
|
if (CMD_FLAG_SK && Status2.Bit(SR2_CM))
|
|
{
|
|
if (ActiveCommandParams.Sector != ActiveCommandParams.EOT)
|
|
{
|
|
// increment the sector ID and search again
|
|
ActiveCommandParams.Sector++;
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
// no execution phase
|
|
SetBit(SR0_IC0, ref Status0);
|
|
UnSetBit(SR0_IC1, ref Status0);
|
|
|
|
// result requires the actual track id, rather than the sector track id
|
|
ActiveCommandParams.Cylinder = track.TrackNumber;
|
|
|
|
CommitResultCHRN();
|
|
CommitResultStatus();
|
|
ActivePhase = Phase.Result;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// read the sector
|
|
for (int i = 0; i < sector.DataLen; i++)
|
|
{
|
|
ExecBuffer[buffPos++] = sector.ActualData[i];
|
|
}
|
|
|
|
// mark the sector read
|
|
sector.SectorReadCompleted();
|
|
|
|
// any CRC errors?
|
|
if (Status1.Bit(SR1_DE) || Status2.Bit(SR2_DD))
|
|
{
|
|
SetBit(SR0_IC0, ref Status0);
|
|
UnSetBit(SR0_IC1, ref Status0);
|
|
terminate = true;
|
|
}
|
|
|
|
if (!CMD_FLAG_SK && Status2.Bit(SR2_CM))
|
|
{
|
|
// deleted address mark was detected with NO skip flag set
|
|
ActiveCommandParams.EOT = ActiveCommandParams.Sector;
|
|
SetBit(SR2_CM, ref Status2);
|
|
SetBit(SR0_IC0, ref Status0);
|
|
UnSetBit(SR0_IC1, ref Status0);
|
|
terminate = true;
|
|
}
|
|
|
|
if (sector.SectorID == ActiveCommandParams.EOT || terminate)
|
|
{
|
|
// this was the last sector to read
|
|
// or termination requested
|
|
|
|
SetBit(SR1_EN, ref Status1);
|
|
|
|
int keyIndex = 0;
|
|
for (int i = 0; i < track.Sectors.Length; i++)
|
|
{
|
|
if (track.Sectors[i].SectorID == sector.SectorID)
|
|
{
|
|
keyIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (keyIndex == track.Sectors.Length - 1)
|
|
{
|
|
// last sector on the cylinder, set EN
|
|
SetBit(SR1_EN, ref Status1);
|
|
|
|
// increment cylinder
|
|
ActiveCommandParams.Cylinder++;
|
|
|
|
// reset sector
|
|
ActiveCommandParams.Sector = sector.SectorID; // 1;
|
|
ActiveDrive.SectorIndex = 0;
|
|
}
|
|
else
|
|
{
|
|
ActiveDrive.SectorIndex++;
|
|
}
|
|
|
|
UnSetBit(SR0_IC1, ref Status0);
|
|
if (terminate)
|
|
SetBit(SR0_IC0, ref Status0);
|
|
else
|
|
UnSetBit(SR0_IC0, ref Status0);
|
|
|
|
SetBit(SR0_IC0, ref Status0);
|
|
|
|
// result requires the actual track id, rather than the sector track id
|
|
ActiveCommandParams.Cylinder = track.TrackNumber;
|
|
|
|
CommitResultCHRN();
|
|
CommitResultStatus();
|
|
ActivePhase = Phase.Execution;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
// continue with multi-sector read operation
|
|
ActiveCommandParams.Sector++;
|
|
//ActiveDrive.SectorIndex++;
|
|
}
|
|
}
|
|
|
|
if (ActivePhase == Phase.Execution)
|
|
{
|
|
ExecLength = buffPos;
|
|
ExecCounter = buffPos;
|
|
|
|
DriveLight = true;
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
//----------------------------------------
|
|
// FDC in execution phase reading/writing bytes
|
|
//----------------------------------------
|
|
case Phase.Execution:
|
|
|
|
var index = ExecLength - ExecCounter;
|
|
|
|
LastSectorDataReadByte = ExecBuffer[index];
|
|
|
|
OverrunCounter--;
|
|
ExecCounter--;
|
|
|
|
break;
|
|
|
|
//----------------------------------------
|
|
// Result bytes being sent to CPU
|
|
//----------------------------------------
|
|
case Phase.Result:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read Deleted Data
|
|
/// COMMAND: 8 parameter bytes
|
|
/// EXECUTION: Data transfer between the FDD and FDC
|
|
/// RESULT: 7 result bytes
|
|
/// </summary>
|
|
private void UPD_ReadDeletedData()
|
|
{
|
|
switch (ActivePhase)
|
|
{
|
|
//----------------------------------------
|
|
// FDC is waiting for a command byte
|
|
//----------------------------------------
|
|
case Phase.Idle:
|
|
break;
|
|
|
|
//----------------------------------------
|
|
// Receiving command parameter bytes
|
|
//----------------------------------------
|
|
case Phase.Command:
|
|
// store the parameter in the command buffer
|
|
CommBuffer[CommCounter] = LastByteReceived;
|
|
|
|
// process parameter byte
|
|
ParseParamByteStandard(CommCounter);
|
|
|
|
// increment command parameter counter
|
|
CommCounter++;
|
|
|
|
// was that the last parameter byte?
|
|
if (CommCounter == ActiveCommand.ParameterByteCount)
|
|
{
|
|
// all parameter bytes received - setup for execution phase
|
|
|
|
// clear exec buffer and status registers
|
|
ClearExecBuffer();
|
|
Status0 = 0;
|
|
Status1 = 0;
|
|
Status2 = 0;
|
|
Status3 = 0;
|
|
|
|
// temp sector index
|
|
byte secIdx = ActiveCommandParams.Sector;
|
|
|
|
// do we have a valid disk inserted?
|
|
if (!ActiveDrive.FLAG_READY)
|
|
{
|
|
// no disk, no tracks or motor is not on
|
|
SetBit(SR0_IC0, ref Status0);
|
|
SetBit(SR0_NR, ref Status0);
|
|
|
|
CommitResultCHRN();
|
|
CommitResultStatus();
|
|
//ResBuffer[RS_ST0] = Status0;
|
|
|
|
// move to result phase
|
|
ActivePhase = Phase.Result;
|
|
break;
|
|
}
|
|
|
|
int buffPos = 0;
|
|
int sectorSize = 0;
|
|
int maxTransferCap = 0;
|
|
|
|
// calculate requested size of data required
|
|
if (ActiveCommandParams.SectorSize == 0)
|
|
{
|
|
// When N=0, then DTL defines the data length which the FDC must treat as a sector. If DTL is smaller than the actual
|
|
// data length in a sector, the data beyond DTL in the sector is not sent to the Data Bus. The FDC reads (internally)
|
|
// the complete sector performing the CRC check and, depending upon the manner of command termination, may perform
|
|
// a Multi-Sector Read Operation.
|
|
sectorSize = ActiveCommandParams.DTL;
|
|
|
|
// calculate maximum transfer capacity
|
|
if (!CMD_FLAG_MF)
|
|
maxTransferCap = 3328;
|
|
}
|
|
else
|
|
{
|
|
// When N is non - zero, then DTL has no meaning and should be set to ffh
|
|
ActiveCommandParams.DTL = 0xFF;
|
|
|
|
// calculate maximum transfer capacity
|
|
switch (ActiveCommandParams.SectorSize)
|
|
{
|
|
case 1:
|
|
if (CMD_FLAG_MF)
|
|
maxTransferCap = 6656;
|
|
else
|
|
maxTransferCap = 3840;
|
|
break;
|
|
case 2:
|
|
if (CMD_FLAG_MF)
|
|
maxTransferCap = 7680;
|
|
else
|
|
maxTransferCap = 4096;
|
|
break;
|
|
case 3:
|
|
if (CMD_FLAG_MF)
|
|
maxTransferCap = 8192;
|
|
else
|
|
maxTransferCap = 4096;
|
|
break;
|
|
}
|
|
|
|
sectorSize = 0x80 << ActiveCommandParams.SectorSize;
|
|
}
|
|
|
|
// get the current track
|
|
var track = ActiveDrive.Disk.DiskTracks.Where(a => a.TrackNumber == ActiveDrive.CurrentTrackID).FirstOrDefault();
|
|
|
|
if (track == null || track.NumberOfSectors <= 0)
|
|
{
|
|
// track could not be found
|
|
SetBit(SR0_IC0, ref Status0);
|
|
SetBit(SR0_NR, ref Status0);
|
|
|
|
CommitResultCHRN();
|
|
CommitResultStatus();
|
|
|
|
//ResBuffer[RS_ST0] = Status0;
|
|
|
|
// move to result phase
|
|
ActivePhase = Phase.Result;
|
|
break;
|
|
}
|
|
|
|
FloppyDisk.Sector sector = null;
|
|
|
|
// sector read loop
|
|
for (;;)
|
|
{
|
|
bool terminate = false;
|
|
|
|
// lookup the sector
|
|
sector = GetSector();
|
|
|
|
if (sector == null)
|
|
{
|
|
// sector was not found after two passes of the disk index hole
|
|
SetBit(SR1_ND, ref Status1);
|
|
SetBit(SR0_IC0, ref Status0);
|
|
UnSetBit(SR0_IC1, ref Status0);
|
|
|
|
// result requires the actual track id, rather than the sector track id
|
|
ActiveCommandParams.Cylinder = track.TrackNumber;
|
|
|
|
CommitResultCHRN();
|
|
CommitResultStatus();
|
|
ActivePhase = Phase.Result;
|
|
break;
|
|
}
|
|
|
|
// sector ID was found on this track
|
|
|
|
// get status regs from sector
|
|
Status1 = sector.Status1;
|
|
Status2 = sector.Status2;
|
|
|
|
// we dont need EN
|
|
UnSetBit(SR1_EN, ref Status1);
|
|
|
|
// invert CM for read deleted data command
|
|
if (Status2.Bit(SR2_CM))
|
|
UnSetBit(SR2_CM, ref Status2);
|
|
else
|
|
SetBit(SR2_CM, ref Status2);
|
|
|
|
// skip flag is set and no DAM found
|
|
if (CMD_FLAG_SK && Status2.Bit(SR2_CM))
|
|
{
|
|
if (ActiveCommandParams.Sector != ActiveCommandParams.EOT)
|
|
{
|
|
// increment the sector ID and search again
|
|
ActiveCommandParams.Sector++;
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
// no execution phase
|
|
SetBit(SR0_IC0, ref Status0);
|
|
UnSetBit(SR0_IC1, ref Status0);
|
|
|
|
// result requires the actual track id, rather than the sector track id
|
|
ActiveCommandParams.Cylinder = track.TrackNumber;
|
|
|
|
CommitResultCHRN();
|
|
CommitResultStatus();
|
|
ActivePhase = Phase.Result;
|
|
break;
|
|
}
|
|
}
|
|
// we can read this sector
|
|
else
|
|
{
|
|
// if DAM is not set this will be the last sector to read
|
|
if (Status2.Bit(SR2_CM))
|
|
{
|
|
ActiveCommandParams.EOT = ActiveCommandParams.Sector;
|
|
}
|
|
|
|
if (!CMD_FLAG_SK && !Status2.Bit(SR2_CM) &&
|
|
ActiveDrive.Disk.Protection == ProtectionType.PaulOwens)
|
|
{
|
|
ActiveCommandParams.EOT = ActiveCommandParams.Sector;
|
|
SetBit(SR2_CM, ref Status2);
|
|
SetBit(SR0_IC0, ref Status0);
|
|
UnSetBit(SR0_IC1, ref Status0);
|
|
terminate = true;
|
|
}
|
|
|
|
// read the sector
|
|
for (int i = 0; i < sectorSize; i++)
|
|
{
|
|
ExecBuffer[buffPos++] = sector.ActualData[i];
|
|
}
|
|
|
|
// mark the sector read
|
|
sector.SectorReadCompleted();
|
|
|
|
if (sector.SectorID == ActiveCommandParams.EOT)
|
|
{
|
|
// this was the last sector to read
|
|
|
|
SetBit(SR1_EN, ref Status1);
|
|
|
|
int keyIndex = 0;
|
|
for (int i = 0; i < track.Sectors.Length; i++)
|
|
{
|
|
if (track.Sectors[i].SectorID == sector.SectorID)
|
|
{
|
|
keyIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (keyIndex == track.Sectors.Length - 1)
|
|
{
|
|
// last sector on the cylinder, set EN
|
|
SetBit(SR1_EN, ref Status1);
|
|
|
|
// increment cylinder
|
|
ActiveCommandParams.Cylinder++;
|
|
|
|
// reset sector
|
|
ActiveCommandParams.Sector = 1;
|
|
ActiveDrive.SectorIndex = 0;
|
|
}
|
|
else
|
|
{
|
|
ActiveDrive.SectorIndex++;
|
|
}
|
|
|
|
UnSetBit(SR0_IC1, ref Status0);
|
|
if (terminate)
|
|
SetBit(SR0_IC0, ref Status0);
|
|
else
|
|
UnSetBit(SR0_IC0, ref Status0);
|
|
|
|
SetBit(SR0_IC0, ref Status0);
|
|
|
|
// result requires the actual track id, rather than the sector track id
|
|
ActiveCommandParams.Cylinder = track.TrackNumber;
|
|
|
|
// remove CM (appears to be required to defeat Alkatraz copy protection)
|
|
UnSetBit(SR2_CM, ref Status2);
|
|
|
|
CommitResultCHRN();
|
|
CommitResultStatus();
|
|
ActivePhase = Phase.Execution;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
// continue with multi-sector read operation
|
|
ActiveCommandParams.Sector++;
|
|
//ActiveDrive.SectorIndex++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ActivePhase == Phase.Execution)
|
|
{
|
|
ExecLength = buffPos;
|
|
ExecCounter = buffPos;
|
|
DriveLight = true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
//----------------------------------------
|
|
// FDC in execution phase reading/writing bytes
|
|
//----------------------------------------
|
|
case Phase.Execution:
|
|
var index = ExecLength - ExecCounter;
|
|
|
|
LastSectorDataReadByte = ExecBuffer[index];
|
|
|
|
OverrunCounter--;
|
|
ExecCounter--;
|
|
|
|
break;
|
|
|
|
//----------------------------------------
|
|
// Result bytes being sent to CPU
|
|
//----------------------------------------
|
|
case Phase.Result:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read Diagnostic (read track)
|
|
/// COMMAND: 8 parameter bytes
|
|
/// EXECUTION: Data transfer between FDD and FDC. FDC reads all data fields from index hole to EDT
|
|
/// RESULT: 7 result bytes
|
|
/// </summary>
|
|
private void UPD_ReadDiagnostic()
|
|
{
|
|
switch (ActivePhase)
|
|
{
|
|
//----------------------------------------
|
|
// FDC is waiting for a command byte
|
|
//----------------------------------------
|
|
case Phase.Idle:
|
|
break;
|
|
|
|
//----------------------------------------
|
|
// Receiving command parameter bytes
|
|
//----------------------------------------
|
|
case Phase.Command:
|
|
|
|
// store the parameter in the command buffer
|
|
CommBuffer[CommCounter] = LastByteReceived;
|
|
|
|
// process parameter byte
|
|
ParseParamByteStandard(CommCounter);
|
|
|
|
// increment command parameter counter
|
|
CommCounter++;
|
|
|
|
// was that the last parameter byte?
|
|
if (CommCounter == ActiveCommand.ParameterByteCount)
|
|
{
|
|
// all parameter bytes received - setup for execution phase
|
|
|
|
// clear exec buffer and status registers
|
|
ClearExecBuffer();
|
|
Status0 = 0;
|
|
Status1 = 0;
|
|
Status2 = 0;
|
|
Status3 = 0;
|
|
|
|
// temp sector index
|
|
byte secIdx = ActiveCommandParams.Sector;
|
|
|
|
// do we have a valid disk inserted?
|
|
if (!ActiveDrive.FLAG_READY)
|
|
{
|
|
// no disk, no tracks or motor is not on
|
|
SetBit(SR0_IC0, ref Status0);
|
|
SetBit(SR0_NR, ref Status0);
|
|
|
|
CommitResultCHRN();
|
|
CommitResultStatus();
|
|
//ResBuffer[RS_ST0] = Status0;
|
|
|
|
// move to result phase
|
|
ActivePhase = Phase.Result;
|
|
break;
|
|
}
|
|
|
|
int buffPos = 0;
|
|
int sectorSize = 0;
|
|
int maxTransferCap = 0;
|
|
|
|
// calculate requested size of data required
|
|
if (ActiveCommandParams.SectorSize == 0)
|
|
{
|
|
// When N=0, then DTL defines the data length which the FDC must treat as a sector. If DTL is smaller than the actual
|
|
// data length in a sector, the data beyond DTL in the sector is not sent to the Data Bus. The FDC reads (internally)
|
|
// the complete sector performing the CRC check and, depending upon the manner of command termination, may perform
|
|
// a Multi-Sector Read Operation.
|
|
sectorSize = ActiveCommandParams.DTL;
|
|
|
|
// calculate maximum transfer capacity
|
|
if (!CMD_FLAG_MF)
|
|
maxTransferCap = 3328;
|
|
}
|
|
else
|
|
{
|
|
// When N is non - zero, then DTL has no meaning and should be set to ffh
|
|
ActiveCommandParams.DTL = 0xFF;
|
|
|
|
// calculate maximum transfer capacity
|
|
switch (ActiveCommandParams.SectorSize)
|
|
{
|
|
case 1:
|
|
if (CMD_FLAG_MF)
|
|
maxTransferCap = 6656;
|
|
else
|
|
maxTransferCap = 3840;
|
|
break;
|
|
case 2:
|
|
if (CMD_FLAG_MF)
|
|
maxTransferCap = 7680;
|
|
else
|
|
maxTransferCap = 4096;
|
|
break;
|
|
case 3:
|
|
if (CMD_FLAG_MF)
|
|
maxTransferCap = 8192;
|
|
else
|
|
maxTransferCap = 4096;
|
|
break;
|
|
}
|
|
|
|
sectorSize = 0x80 << ActiveCommandParams.SectorSize;
|
|
}
|
|
|
|
// get the current track
|
|
var track = ActiveDrive.Disk.DiskTracks.Where(a => a.TrackNumber == ActiveDrive.CurrentTrackID).FirstOrDefault();
|
|
|
|
if (track == null || track.NumberOfSectors <= 0)
|
|
{
|
|
// track could not be found
|
|
SetBit(SR0_IC0, ref Status0);
|
|
SetBit(SR0_NR, ref Status0);
|
|
|
|
CommitResultCHRN();
|
|
CommitResultStatus();
|
|
|
|
//ResBuffer[RS_ST0] = Status0;
|
|
|
|
// move to result phase
|
|
ActivePhase = Phase.Result;
|
|
break;
|
|
}
|
|
|
|
FloppyDisk.Sector sector = null;
|
|
ActiveDrive.SectorIndex = 0;
|
|
|
|
int secCount = 0;
|
|
|
|
// read the whole track
|
|
for (int i = 0; i < track.Sectors.Length; i++)
|
|
{
|
|
if (secCount >= ActiveCommandParams.EOT)
|
|
{
|
|
break;
|
|
}
|
|
|
|
var sec = track.Sectors[i];
|
|
for (int b = 0; b < sec.ActualData.Length; b++)
|
|
{
|
|
ExecBuffer[buffPos++] = sec.ActualData[b];
|
|
}
|
|
|
|
// mark the sector read
|
|
sec.SectorReadCompleted();
|
|
|
|
// end of sector - compare IDs
|
|
if (sec.TrackNumber != ActiveCommandParams.Cylinder ||
|
|
sec.SideNumber != ActiveCommandParams.Head ||
|
|
sec.SectorID != ActiveCommandParams.Sector ||
|
|
sec.SectorSize != ActiveCommandParams.SectorSize)
|
|
{
|
|
SetBit(SR1_ND, ref Status1);
|
|
}
|
|
|
|
secCount++;
|
|
ActiveDrive.SectorIndex = i;
|
|
}
|
|
|
|
if (secCount == ActiveCommandParams.EOT)
|
|
{
|
|
// this was the last sector to read
|
|
// or termination requested
|
|
|
|
int keyIndex = 0;
|
|
for (int i = 0; i < track.Sectors.Length; i++)
|
|
{
|
|
if (track.Sectors[i].SectorID == track.Sectors[ActiveDrive.SectorIndex].SectorID)
|
|
{
|
|
keyIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (keyIndex == track.Sectors.Length - 1)
|
|
{
|
|
// last sector on the cylinder, set EN
|
|
SetBit(SR1_EN, ref Status1);
|
|
|
|
// increment cylinder
|
|
ActiveCommandParams.Cylinder++;
|
|
|
|
// reset sector
|
|
ActiveCommandParams.Sector = 1;
|
|
ActiveDrive.SectorIndex = 0;
|
|
}
|
|
else
|
|
{
|
|
ActiveDrive.SectorIndex++;
|
|
}
|
|
|
|
UnSetBit(SR0_IC1, ref Status0);
|
|
UnSetBit(SR0_IC0, ref Status0);
|
|
|
|
CommitResultCHRN();
|
|
CommitResultStatus();
|
|
ActivePhase = Phase.Execution;
|
|
}
|
|
|
|
if (ActivePhase == Phase.Execution)
|
|
{
|
|
ExecLength = buffPos;
|
|
ExecCounter = buffPos;
|
|
|
|
DriveLight = true;
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
//----------------------------------------
|
|
// FDC in execution phase reading/writing bytes
|
|
//----------------------------------------
|
|
case Phase.Execution:
|
|
|
|
var index = ExecLength - ExecCounter;
|
|
|
|
LastSectorDataReadByte = ExecBuffer[index];
|
|
|
|
OverrunCounter--;
|
|
ExecCounter--;
|
|
|
|
break;
|
|
|
|
//----------------------------------------
|
|
// Result bytes being sent to CPU
|
|
//----------------------------------------
|
|
case Phase.Result:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read ID
|
|
/// COMMAND: 1 parameter byte
|
|
/// EXECUTION: The first correct ID information on the cylinder is stored in the data register
|
|
/// RESULT: 7 result bytes
|
|
/// </summary>
|
|
private void UPD_ReadID()
|
|
{
|
|
switch (ActivePhase)
|
|
{
|
|
//----------------------------------------
|
|
// FDC is waiting for a command byte
|
|
//----------------------------------------
|
|
case Phase.Idle:
|
|
break;
|
|
|
|
//----------------------------------------
|
|
// Receiving command parameter bytes
|
|
//----------------------------------------
|
|
case Phase.Command:
|
|
|
|
// store the parameter in the command buffer
|
|
CommBuffer[CommCounter] = LastByteReceived;
|
|
|
|
// process parameter byte
|
|
ParseParamByteStandard(CommCounter);
|
|
|
|
// increment command parameter counter
|
|
CommCounter++;
|
|
|
|
// was that the last parameter byte?
|
|
if (CommCounter == ActiveCommand.ParameterByteCount)
|
|
{
|
|
DriveLight = true;
|
|
|
|
// all parameter bytes received
|
|
ClearResultBuffer();
|
|
Status0 = 0;
|
|
Status1 = 0;
|
|
Status2 = 0;
|
|
Status3 = 0;
|
|
|
|
// set unit select
|
|
//SetUnitSelect(ActiveDrive.ID, ref Status0);
|
|
|
|
// HD should always be 0
|
|
UnSetBit(SR0_HD, ref Status0);
|
|
|
|
if (!ActiveDrive.FLAG_READY)
|
|
{
|
|
// no disk, no tracks or motor is not on
|
|
// it is at this point the +3 detects whether a disk is present
|
|
// if not (and after another readid and SIS) it will eventually proceed to loading from tape
|
|
SetBit(SR0_IC0, ref Status0);
|
|
SetBit(SR0_NR, ref Status0);
|
|
|
|
// setup the result buffer
|
|
ResBuffer[RS_ST0] = Status0;
|
|
for (int i = 1; i < 7; i++)
|
|
ResBuffer[i] = 0;
|
|
|
|
// move to result phase
|
|
ActivePhase = Phase.Result;
|
|
break;
|
|
}
|
|
|
|
var track = ActiveDrive.Disk.DiskTracks.Where(a => a.TrackNumber == ActiveDrive.CurrentTrackID).FirstOrDefault();
|
|
|
|
if (track != null && track.NumberOfSectors > 0 && track.TrackNumber != 0xff)
|
|
{
|
|
// formatted track
|
|
|
|
// is the index out of bounds?
|
|
if (ActiveDrive.SectorIndex >= track.NumberOfSectors)
|
|
{
|
|
// reset the index
|
|
ActiveDrive.SectorIndex = 0;
|
|
}
|
|
|
|
if (ActiveDrive.SectorIndex == 0 && ActiveDrive.Disk.DiskTracks[ActiveDrive.CurrentTrackID].Sectors.Length > 1)
|
|
{
|
|
// looks like readid always skips the first sector on a track
|
|
ActiveDrive.SectorIndex++;
|
|
}
|
|
|
|
// read the sector data
|
|
var data = track.Sectors[ActiveDrive.SectorIndex]; //.GetCHRN();
|
|
ResBuffer[RS_C] = data.TrackNumber;
|
|
ResBuffer[RS_H] = data.SideNumber;
|
|
ResBuffer[RS_R] = data.SectorID;
|
|
ResBuffer[RS_N] = data.SectorSize;
|
|
|
|
ResBuffer[RS_ST0] = Status0;
|
|
|
|
// check for DAM & CRC
|
|
//if (data.Status2.Bit(SR2_CM))
|
|
//SetBit(SR2_CM, ref ResBuffer[RS_ST2]);
|
|
|
|
|
|
// increment the current sector
|
|
ActiveDrive.SectorIndex++;
|
|
|
|
// is the index out of bounds?
|
|
if (ActiveDrive.SectorIndex >= track.NumberOfSectors)
|
|
{
|
|
// reset the index
|
|
ActiveDrive.SectorIndex = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// unformatted track?
|
|
CommitResultCHRN();
|
|
|
|
SetBit(SR0_IC0, ref Status0);
|
|
ResBuffer[RS_ST0] = Status0;
|
|
ResBuffer[RS_ST1] = 0x01;
|
|
}
|
|
|
|
ActivePhase = Phase.Result;
|
|
}
|
|
|
|
break;
|
|
|
|
//----------------------------------------
|
|
// FDC in execution phase reading/writing bytes
|
|
//----------------------------------------
|
|
case Phase.Execution:
|
|
break;
|
|
|
|
//----------------------------------------
|
|
// Result bytes being sent to CPU
|
|
//----------------------------------------
|
|
case Phase.Result:
|
|
break;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region WRITE Commands
|
|
|
|
/// <summary>
|
|
/// Write Data
|
|
/// COMMAND: 8 parameter bytes
|
|
/// EXECUTION: Data transfer between FDC and FDD
|
|
/// RESULT: 7 result bytes
|
|
/// </summary>
|
|
private void UPD_WriteData()
|
|
{
|
|
switch (ActivePhase)
|
|
{
|
|
//----------------------------------------
|
|
// FDC is waiting for a command byte
|
|
//----------------------------------------
|
|
case Phase.Idle:
|
|
break;
|
|
|
|
//----------------------------------------
|
|
// Receiving command parameter bytes
|
|
//----------------------------------------
|
|
case Phase.Command:
|
|
|
|
// store the parameter in the command buffer
|
|
CommBuffer[CommCounter] = LastByteReceived;
|
|
|
|
// process parameter byte
|
|
ParseParamByteStandard(CommCounter);
|
|
|
|
// increment command parameter counter
|
|
CommCounter++;
|
|
|
|
// was that the last parameter byte?
|
|
if (CommCounter == ActiveCommand.ParameterByteCount)
|
|
{
|
|
// all parameter bytes received - setup for execution phase
|
|
|
|
// clear exec buffer and status registers
|
|
ClearExecBuffer();
|
|
Status0 = 0;
|
|
Status1 = 0;
|
|
Status2 = 0;
|
|
Status3 = 0;
|
|
|
|
// temp sector index
|
|
byte secIdx = ActiveCommandParams.Sector;
|
|
|
|
// hack for when another drive (non-existent) is being called
|
|
if (ActiveDrive.ID != 0)
|
|
DiskDriveIndex = 0;
|
|
|
|
// do we have a valid disk inserted?
|
|
if (!ActiveDrive.FLAG_READY)
|
|
{
|
|
// no disk, no tracks or motor is not on
|
|
SetBit(SR0_IC0, ref Status0);
|
|
SetBit(SR0_NR, ref Status0);
|
|
|
|
CommitResultCHRN();
|
|
CommitResultStatus();
|
|
//ResBuffer[RS_ST0] = Status0;
|
|
|
|
// move to result phase
|
|
ActivePhase = Phase.Result;
|
|
break;
|
|
}
|
|
|
|
// check write protect tab
|
|
if (ActiveDrive.FLAG_WRITEPROTECT)
|
|
{
|
|
SetBit(SR0_IC0, ref Status0);
|
|
SetBit(SR1_NW, ref Status1);
|
|
|
|
CommitResultCHRN();
|
|
CommitResultStatus();
|
|
//ResBuffer[RS_ST0] = Status0;
|
|
|
|
// move to result phase
|
|
ActivePhase = Phase.Result;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
|
|
// calculate the number of bytes to write
|
|
int byteCounter = 0;
|
|
byte startSecID = ActiveCommandParams.Sector;
|
|
byte endSecID = ActiveCommandParams.EOT;
|
|
bool lastSec = false;
|
|
|
|
// get the first sector
|
|
var track = ActiveDrive.Disk.DiskTracks[ActiveCommandParams.Cylinder];
|
|
int secIndex = 0;
|
|
for (int s = 0; s < track.Sectors.Length; s++)
|
|
{
|
|
if (track.Sectors[s].SectorID == endSecID)
|
|
lastSec = true;
|
|
|
|
for (int i = 0; i < 0x80 << ActiveCommandParams.SectorSize; i++)
|
|
{
|
|
byteCounter++;
|
|
|
|
if (i == (0x80 << ActiveCommandParams.SectorSize) - 1 && lastSec)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (lastSec)
|
|
break;
|
|
}
|
|
|
|
ExecCounter = byteCounter;
|
|
ExecLength = byteCounter;
|
|
ActivePhase = Phase.Execution;
|
|
DriveLight = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
//----------------------------------------
|
|
// FDC in execution phase reading/writing bytes
|
|
//----------------------------------------
|
|
case Phase.Execution:
|
|
|
|
var index = ExecLength - ExecCounter;
|
|
|
|
ExecBuffer[index] = LastSectorDataWriteByte;
|
|
|
|
OverrunCounter--;
|
|
ExecCounter--;
|
|
|
|
if (ExecCounter <= 0)
|
|
{
|
|
int cnt = 0;
|
|
|
|
// all data received
|
|
byte startSecID = ActiveCommandParams.Sector;
|
|
byte endSecID = ActiveCommandParams.EOT;
|
|
bool lastSec = false;
|
|
var track = ActiveDrive.Disk.DiskTracks[ActiveCommandParams.Cylinder];
|
|
int secIndex = 0;
|
|
|
|
for (int s = 0; s < track.Sectors.Length; s++)
|
|
{
|
|
if (cnt == ExecLength)
|
|
break;
|
|
|
|
ActiveCommandParams.Sector = track.Sectors[s].SectorID;
|
|
|
|
if (track.Sectors[s].SectorID == endSecID)
|
|
lastSec = true;
|
|
|
|
int size = 0x80 << track.Sectors[s].SectorSize;
|
|
|
|
for (int d = 0; d < size; d++)
|
|
{
|
|
track.Sectors[s].SectorData[d] = ExecBuffer[cnt++];
|
|
}
|
|
|
|
if (lastSec)
|
|
break;
|
|
}
|
|
|
|
SetBit(SR0_IC0, ref Status0);
|
|
SetBit(SR1_EN, ref Status1);
|
|
|
|
CommitResultCHRN();
|
|
CommitResultStatus();
|
|
}
|
|
|
|
break;
|
|
|
|
//----------------------------------------
|
|
// Result bytes being sent to CPU
|
|
//----------------------------------------
|
|
case Phase.Result:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write ID (format write)
|
|
/// COMMAND: 5 parameter bytes
|
|
/// EXECUTION: Entire track is formatted
|
|
/// RESULT: 7 result bytes
|
|
/// </summary>
|
|
private void UPD_WriteID()
|
|
{
|
|
switch (ActivePhase)
|
|
{
|
|
//----------------------------------------
|
|
// FDC is waiting for a command byte
|
|
//----------------------------------------
|
|
case Phase.Idle:
|
|
break;
|
|
|
|
//----------------------------------------
|
|
// Receiving command parameter bytes
|
|
//----------------------------------------
|
|
case Phase.Command:
|
|
|
|
// store the parameter in the command buffer
|
|
CommBuffer[CommCounter] = LastByteReceived;
|
|
|
|
// process parameter byte
|
|
ParseParamByteStandard(CommCounter);
|
|
|
|
// increment command parameter counter
|
|
CommCounter++;
|
|
|
|
// was that the last parameter byte?
|
|
if (CommCounter == ActiveCommand.ParameterByteCount)
|
|
{
|
|
// all parameter bytes received - setup for execution phase
|
|
DriveLight = true;
|
|
|
|
// clear exec buffer and status registers
|
|
ClearExecBuffer();
|
|
Status0 = 0;
|
|
Status1 = 0;
|
|
Status2 = 0;
|
|
Status3 = 0;
|
|
|
|
// temp sector index
|
|
byte secIdx = ActiveCommandParams.Sector;
|
|
|
|
// hack for when another drive (non-existent) is being called
|
|
if (ActiveDrive.ID != 0)
|
|
DiskDriveIndex = 0;
|
|
|
|
// do we have a valid disk inserted?
|
|
if (!ActiveDrive.FLAG_READY)
|
|
{
|
|
// no disk, no tracks or motor is not on
|
|
SetBit(SR0_IC0, ref Status0);
|
|
SetBit(SR0_NR, ref Status0);
|
|
|
|
CommitResultCHRN();
|
|
CommitResultStatus();
|
|
//ResBuffer[RS_ST0] = Status0;
|
|
|
|
// move to result phase
|
|
ActivePhase = Phase.Result;
|
|
break;
|
|
}
|
|
|
|
// check write protect tab
|
|
if (ActiveDrive.FLAG_WRITEPROTECT)
|
|
{
|
|
SetBit(SR0_IC0, ref Status0);
|
|
SetBit(SR1_NW, ref Status1);
|
|
|
|
CommitResultCHRN();
|
|
CommitResultStatus();
|
|
//ResBuffer[RS_ST0] = Status0;
|
|
|
|
// move to result phase
|
|
ActivePhase = Phase.Result;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
// not implemented yet
|
|
SetBit(SR0_IC0, ref Status0);
|
|
SetBit(SR1_NW, ref Status1);
|
|
|
|
CommitResultCHRN();
|
|
CommitResultStatus();
|
|
//ResBuffer[RS_ST0] = Status0;
|
|
|
|
// move to result phase
|
|
ActivePhase = Phase.Result;
|
|
break;
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
//----------------------------------------
|
|
// FDC in execution phase reading/writing bytes
|
|
//----------------------------------------
|
|
case Phase.Execution:
|
|
break;
|
|
|
|
//----------------------------------------
|
|
// Result bytes being sent to CPU
|
|
//----------------------------------------
|
|
case Phase.Result:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write Deleted Data
|
|
/// COMMAND: 8 parameter bytes
|
|
/// EXECUTION: Data transfer between FDC and FDD
|
|
/// RESULT: 7 result bytes
|
|
/// </summary>
|
|
private void UPD_WriteDeletedData()
|
|
{
|
|
switch (ActivePhase)
|
|
{
|
|
//----------------------------------------
|
|
// FDC is waiting for a command byte
|
|
//----------------------------------------
|
|
case Phase.Idle:
|
|
break;
|
|
|
|
//----------------------------------------
|
|
// Receiving command parameter bytes
|
|
//----------------------------------------
|
|
case Phase.Command:
|
|
|
|
// store the parameter in the command buffer
|
|
CommBuffer[CommCounter] = LastByteReceived;
|
|
|
|
// process parameter byte
|
|
ParseParamByteStandard(CommCounter);
|
|
|
|
// increment command parameter counter
|
|
CommCounter++;
|
|
|
|
// was that the last parameter byte?
|
|
if (CommCounter == ActiveCommand.ParameterByteCount)
|
|
{
|
|
// all parameter bytes received - setup for execution phase
|
|
|
|
// clear exec buffer and status registers
|
|
ClearExecBuffer();
|
|
Status0 = 0;
|
|
Status1 = 0;
|
|
Status2 = 0;
|
|
Status3 = 0;
|
|
|
|
// temp sector index
|
|
byte secIdx = ActiveCommandParams.Sector;
|
|
|
|
// hack for when another drive (non-existent) is being called
|
|
if (ActiveDrive.ID != 0)
|
|
DiskDriveIndex = 0;
|
|
|
|
// do we have a valid disk inserted?
|
|
if (!ActiveDrive.FLAG_READY)
|
|
{
|
|
// no disk, no tracks or motor is not on
|
|
SetBit(SR0_IC0, ref Status0);
|
|
SetBit(SR0_NR, ref Status0);
|
|
|
|
CommitResultCHRN();
|
|
CommitResultStatus();
|
|
//ResBuffer[RS_ST0] = Status0;
|
|
|
|
// move to result phase
|
|
ActivePhase = Phase.Result;
|
|
break;
|
|
}
|
|
|
|
// check write protect tab
|
|
if (ActiveDrive.FLAG_WRITEPROTECT)
|
|
{
|
|
SetBit(SR0_IC0, ref Status0);
|
|
SetBit(SR1_NW, ref Status1);
|
|
|
|
CommitResultCHRN();
|
|
CommitResultStatus();
|
|
//ResBuffer[RS_ST0] = Status0;
|
|
|
|
// move to result phase
|
|
ActivePhase = Phase.Result;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
|
|
// calculate the number of bytes to write
|
|
int byteCounter = 0;
|
|
byte startSecID = ActiveCommandParams.Sector;
|
|
byte endSecID = ActiveCommandParams.EOT;
|
|
bool lastSec = false;
|
|
|
|
// get the first sector
|
|
var track = ActiveDrive.Disk.DiskTracks[ActiveCommandParams.Cylinder];
|
|
int secIndex = 0;
|
|
for (int s = 0; s < track.Sectors.Length; s++)
|
|
{
|
|
if (track.Sectors[s].SectorID == endSecID)
|
|
lastSec = true;
|
|
|
|
for (int i = 0; i < 0x80 << ActiveCommandParams.SectorSize; i++)
|
|
{
|
|
byteCounter++;
|
|
|
|
if (i == (0x80 << ActiveCommandParams.SectorSize) - 1 && lastSec)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (lastSec)
|
|
break;
|
|
}
|
|
|
|
ExecCounter = byteCounter;
|
|
ExecLength = byteCounter;
|
|
ActivePhase = Phase.Execution;
|
|
DriveLight = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
//----------------------------------------
|
|
// FDC in execution phase reading/writing bytes
|
|
//----------------------------------------
|
|
case Phase.Execution:
|
|
|
|
var index = ExecLength - ExecCounter;
|
|
|
|
ExecBuffer[index] = LastSectorDataWriteByte;
|
|
|
|
OverrunCounter--;
|
|
ExecCounter--;
|
|
|
|
if (ExecCounter <= 0)
|
|
{
|
|
int cnt = 0;
|
|
|
|
// all data received
|
|
byte startSecID = ActiveCommandParams.Sector;
|
|
byte endSecID = ActiveCommandParams.EOT;
|
|
bool lastSec = false;
|
|
var track = ActiveDrive.Disk.DiskTracks[ActiveCommandParams.Cylinder];
|
|
int secIndex = 0;
|
|
|
|
for (int s = 0; s < track.Sectors.Length; s++)
|
|
{
|
|
if (cnt == ExecLength)
|
|
break;
|
|
|
|
ActiveCommandParams.Sector = track.Sectors[s].SectorID;
|
|
|
|
if (track.Sectors[s].SectorID == endSecID)
|
|
lastSec = true;
|
|
|
|
int size = 0x80 << track.Sectors[s].SectorSize;
|
|
|
|
for (int d = 0; d < size; d++)
|
|
{
|
|
track.Sectors[s].SectorData[d] = ExecBuffer[cnt++];
|
|
}
|
|
|
|
if (lastSec)
|
|
break;
|
|
}
|
|
|
|
SetBit(SR0_IC0, ref Status0);
|
|
SetBit(SR1_EN, ref Status1);
|
|
|
|
CommitResultCHRN();
|
|
CommitResultStatus();
|
|
}
|
|
|
|
break;
|
|
|
|
//----------------------------------------
|
|
// Result bytes being sent to CPU
|
|
//----------------------------------------
|
|
case Phase.Result:
|
|
break;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region SCAN Commands
|
|
|
|
/// <summary>
|
|
/// Scan Equal
|
|
/// COMMAND: 8 parameter bytes
|
|
/// EXECUTION: Data compared between the FDD and FDC
|
|
/// RESULT: 7 result bytes
|
|
/// </summary>
|
|
private void UPD_ScanEqual()
|
|
{
|
|
switch (ActivePhase)
|
|
{
|
|
//----------------------------------------
|
|
// FDC is waiting for a command byte
|
|
//----------------------------------------
|
|
case Phase.Idle:
|
|
break;
|
|
|
|
//----------------------------------------
|
|
// Receiving command parameter bytes
|
|
//----------------------------------------
|
|
case Phase.Command:
|
|
break;
|
|
|
|
//----------------------------------------
|
|
// FDC in execution phase reading/writing bytes
|
|
//----------------------------------------
|
|
case Phase.Execution:
|
|
break;
|
|
|
|
//----------------------------------------
|
|
// Result bytes being sent to CPU
|
|
//----------------------------------------
|
|
case Phase.Result:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Scan Low or Equal
|
|
/// COMMAND: 8 parameter bytes
|
|
/// EXECUTION: Data compared between the FDD and FDC
|
|
/// RESULT: 7 result bytes
|
|
/// </summary>
|
|
private void UPD_ScanLowOrEqual()
|
|
{
|
|
switch (ActivePhase)
|
|
{
|
|
//----------------------------------------
|
|
// FDC is waiting for a command byte
|
|
//----------------------------------------
|
|
case Phase.Idle:
|
|
break;
|
|
|
|
//----------------------------------------
|
|
// Receiving command parameter bytes
|
|
//----------------------------------------
|
|
case Phase.Command:
|
|
break;
|
|
|
|
//----------------------------------------
|
|
// FDC in execution phase reading/writing bytes
|
|
//----------------------------------------
|
|
case Phase.Execution:
|
|
break;
|
|
|
|
//----------------------------------------
|
|
// Result bytes being sent to CPU
|
|
//----------------------------------------
|
|
case Phase.Result:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Scan High or Equal
|
|
/// COMMAND: 8 parameter bytes
|
|
/// EXECUTION: Data compared between the FDD and FDC
|
|
/// RESULT: 7 result bytes
|
|
/// </summary>
|
|
private void UPD_ScanHighOrEqual()
|
|
{
|
|
switch (ActivePhase)
|
|
{
|
|
//----------------------------------------
|
|
// FDC is waiting for a command byte
|
|
//----------------------------------------
|
|
case Phase.Idle:
|
|
break;
|
|
|
|
//----------------------------------------
|
|
// Receiving command parameter bytes
|
|
//----------------------------------------
|
|
case Phase.Command:
|
|
break;
|
|
|
|
//----------------------------------------
|
|
// FDC in execution phase reading/writing bytes
|
|
//----------------------------------------
|
|
case Phase.Execution:
|
|
break;
|
|
|
|
//----------------------------------------
|
|
// Result bytes being sent to CPU
|
|
//----------------------------------------
|
|
case Phase.Result:
|
|
break;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region OTHER Commands
|
|
|
|
/// <summary>
|
|
/// Specify
|
|
/// COMMAND: 2 parameter bytes
|
|
/// EXECUTION: NO execution phase
|
|
/// RESULT: NO result phase
|
|
///
|
|
/// Looks like specify command returns status 0x80 throughout its lifecycle
|
|
/// so CB is NOT set
|
|
/// </summary>
|
|
private void UPD_Specify()
|
|
{
|
|
switch (ActivePhase)
|
|
{
|
|
//----------------------------------------
|
|
// FDC is waiting for a command byte
|
|
//----------------------------------------
|
|
case Phase.Idle:
|
|
break;
|
|
|
|
//----------------------------------------
|
|
// Receiving command parameter bytes
|
|
//----------------------------------------
|
|
case Phase.Command:
|
|
|
|
// store the parameter in the command buffer
|
|
CommBuffer[CommCounter] = LastByteReceived;
|
|
|
|
// process parameter byte
|
|
byte currByte = CommBuffer[CommCounter];
|
|
BitArray bi = new BitArray(new byte[] { currByte });
|
|
|
|
switch (CommCounter)
|
|
{
|
|
// SRT & HUT
|
|
case 0:
|
|
SRT = 16 - (currByte >> 4) & 0x0f;
|
|
HUT = (currByte & 0x0f) << 4;
|
|
if (HUT == 0)
|
|
{
|
|
HUT = 255;
|
|
}
|
|
break;
|
|
// HLT & ND
|
|
case 1:
|
|
if (bi[0])
|
|
ND = true;
|
|
else
|
|
ND = false;
|
|
|
|
HLT = currByte & 0xfe;
|
|
if (HLT == 0)
|
|
{
|
|
HLT = 255;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// increment command parameter counter
|
|
CommCounter++;
|
|
|
|
// was that the last parameter byte?
|
|
if (CommCounter == ActiveCommand.ParameterByteCount)
|
|
{
|
|
// all parameter bytes received
|
|
ActivePhase = Phase.Idle;
|
|
}
|
|
|
|
break;
|
|
|
|
//----------------------------------------
|
|
// FDC in execution phase reading/writing bytes
|
|
//----------------------------------------
|
|
case Phase.Execution:
|
|
break;
|
|
|
|
//----------------------------------------
|
|
// Result bytes being sent to CPU
|
|
//----------------------------------------
|
|
case Phase.Result:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Seek
|
|
/// COMMAND: 2 parameter bytes
|
|
/// EXECUTION: Head is positioned over proper cylinder on disk
|
|
/// RESULT: NO result phase
|
|
/// </summary>
|
|
private void UPD_Seek()
|
|
{
|
|
switch (ActivePhase)
|
|
{
|
|
//----------------------------------------
|
|
// FDC is waiting for a command byte
|
|
//----------------------------------------
|
|
case Phase.Idle:
|
|
break;
|
|
|
|
//----------------------------------------
|
|
// Receiving command parameter bytes
|
|
//----------------------------------------
|
|
case Phase.Command:
|
|
// store the parameter in the command buffer
|
|
CommBuffer[CommCounter] = LastByteReceived;
|
|
|
|
// process parameter byte
|
|
byte currByte = CommBuffer[CommCounter];
|
|
switch (CommCounter)
|
|
{
|
|
case 0:
|
|
ParseParamByteStandard(CommCounter);
|
|
break;
|
|
case 1:
|
|
ActiveDrive.SeekingTrack = currByte;
|
|
break;
|
|
}
|
|
|
|
// increment command parameter counter
|
|
CommCounter++;
|
|
|
|
// was that the last parameter byte?
|
|
if (CommCounter == ActiveCommand.ParameterByteCount)
|
|
{
|
|
// all parameter bytes received
|
|
DriveLight = true;
|
|
ActivePhase = Phase.Execution;
|
|
ActiveCommand.CommandDelegate();
|
|
}
|
|
break;
|
|
|
|
//----------------------------------------
|
|
// FDC in execution phase reading/writing bytes
|
|
//----------------------------------------
|
|
case Phase.Execution:
|
|
// set seek flag
|
|
ActiveDrive.SeekStatus = SEEK_SEEK;
|
|
|
|
if (ActiveDrive.CurrentTrackID == CommBuffer[CM_C])
|
|
{
|
|
// we are already on the correct track
|
|
ActiveDrive.SectorIndex = 0;
|
|
}
|
|
else
|
|
{
|
|
// immediate seek
|
|
ActiveDrive.CurrentTrackID = CommBuffer[CM_C];
|
|
|
|
ActiveDrive.SectorIndex = 0;
|
|
|
|
if (ActiveDrive.Disk.DiskTracks[ActiveDrive.CurrentTrackID].Sectors.Length > 1)
|
|
{
|
|
// always read the first sector
|
|
//ActiveDrive.SectorIndex++;
|
|
}
|
|
}
|
|
|
|
// skip execution mode and go directly to idle
|
|
// result is determined by SIS command
|
|
ActivePhase = Phase.Idle;
|
|
break;
|
|
|
|
//----------------------------------------
|
|
// Result bytes being sent to CPU
|
|
//----------------------------------------
|
|
case Phase.Result:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Recalibrate (seek track 0)
|
|
/// COMMAND: 1 parameter byte
|
|
/// EXECUTION: Head retracted to track 0
|
|
/// RESULT: NO result phase
|
|
/// </summary>
|
|
private void UPD_Recalibrate()
|
|
{
|
|
switch (ActivePhase)
|
|
{
|
|
//----------------------------------------
|
|
// FDC is waiting for a command byte
|
|
//----------------------------------------
|
|
case Phase.Idle:
|
|
break;
|
|
|
|
//----------------------------------------
|
|
// Receiving command parameter bytes
|
|
//----------------------------------------
|
|
case Phase.Command:
|
|
// store the parameter in the command buffer
|
|
CommBuffer[CommCounter] = LastByteReceived;
|
|
|
|
// process parameter byte
|
|
ParseParamByteStandard(CommCounter);
|
|
|
|
// increment command parameter counter
|
|
CommCounter++;
|
|
|
|
// was that the last parameter byte?
|
|
if (CommCounter == ActiveCommand.ParameterByteCount)
|
|
{
|
|
// all parameter bytes received
|
|
DriveLight = true;
|
|
ActivePhase = Phase.Execution;
|
|
ActiveCommand.CommandDelegate();
|
|
}
|
|
break;
|
|
|
|
//----------------------------------------
|
|
// FDC in execution phase reading/writing bytes
|
|
//----------------------------------------
|
|
case Phase.Execution:
|
|
|
|
// immediate recalibration
|
|
ActiveDrive.TrackIndex = 0;
|
|
ActiveDrive.SectorIndex = 0;
|
|
|
|
// recalibrate appears to always skip the first sector
|
|
//if (ActiveDrive.Disk.DiskTracks[ActiveDrive.TrackIndex].Sectors.Length > 1)
|
|
//ActiveDrive.SectorIndex++;
|
|
|
|
// set seek flag
|
|
ActiveDrive.SeekStatus = SEEK_RECALIBRATE;
|
|
|
|
// skip execution mode and go directly to idle
|
|
// result is determined by SIS command
|
|
ActivePhase = Phase.Idle;
|
|
break;
|
|
|
|
//----------------------------------------
|
|
// Result bytes being sent to CPU
|
|
//----------------------------------------
|
|
case Phase.Result:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sense Interrupt Status
|
|
/// COMMAND: NO parameter bytes
|
|
/// EXECUTION: NO execution phase
|
|
/// RESULT: 2 result bytes
|
|
/// </summary>
|
|
private void UPD_SenseInterruptStatus()
|
|
{
|
|
switch (ActivePhase)
|
|
{
|
|
//----------------------------------------
|
|
// FDC is waiting for a command byte
|
|
//----------------------------------------
|
|
case Phase.Idle:
|
|
break;
|
|
|
|
//----------------------------------------
|
|
// Receiving command parameter bytes
|
|
//----------------------------------------
|
|
case Phase.Command:
|
|
break;
|
|
|
|
//----------------------------------------
|
|
// FDC in execution phase reading/writing bytes
|
|
//----------------------------------------
|
|
case Phase.Execution:
|
|
// SIS should return 2 bytes if sucessfully sensed an interrupt
|
|
// 1 byte otherwise
|
|
|
|
// it seems like the +3 ROM makes 3 SIS calls for each seek/recalibrate call for some reason
|
|
// possibly one for each drive???
|
|
// 1 - the interrupt is acknowleged with ST0 = 32 and track number
|
|
// 2 - second sis returns 1 ST0 byte with 192
|
|
// 3 - third SIS call returns standard 1 byte 0x80 (unknown cmd or SIS with no interrupt occured)
|
|
// for now I will assume that the first call is aimed at DriveA, the second at DriveB (which we are NOT implementing)
|
|
|
|
// check active drive first
|
|
if (ActiveDrive.SeekStatus == SEEK_RECALIBRATE ||
|
|
ActiveDrive.SeekStatus == SEEK_SEEK)
|
|
{
|
|
// interrupt has been raised for this drive
|
|
// acknowledge
|
|
ActiveDrive.SeekStatus = SEEK_IDLE;// SEEK_INTACKNOWLEDGED;
|
|
|
|
// result length 2
|
|
ResLength = 2;
|
|
|
|
// first byte ST0 0x20
|
|
Status0 = 0x20;
|
|
ResBuffer[0] = Status0;
|
|
// second byte is the current track id
|
|
ResBuffer[1] = ActiveDrive.CurrentTrackID;
|
|
}
|
|
/*
|
|
else if (ActiveDrive.SeekStatus == SEEK_INTACKNOWLEDGED)
|
|
{
|
|
// DriveA interrupt has already been acknowledged
|
|
ActiveDrive.SeekStatus = SEEK_IDLE;
|
|
|
|
ResLength = 1;
|
|
Status0 = 192;
|
|
ResBuffer[0] = Status0;
|
|
}
|
|
*/
|
|
else if (ActiveDrive.SeekStatus == SEEK_IDLE)
|
|
{
|
|
// SIS with no interrupt
|
|
ResLength = 1;
|
|
Status0 = 0x80;
|
|
ResBuffer[0] = Status0;
|
|
}
|
|
|
|
ActivePhase = Phase.Result;
|
|
|
|
break;
|
|
|
|
//----------------------------------------
|
|
// Result bytes being sent to CPU
|
|
//----------------------------------------
|
|
case Phase.Result:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sense Drive Status
|
|
/// COMMAND: 1 parameter byte
|
|
/// EXECUTION: NO execution phase
|
|
/// RESULT: 1 result byte
|
|
///
|
|
/// The ZX spectrum appears to only specify drive 1 as the parameter byte, NOT drive 0
|
|
/// After the final param byte is received main status changes to 0xd0
|
|
/// Data register (ST3) result is 0x51 if drive/disk not available
|
|
/// 0x71 if disk is present in 2nd drive
|
|
/// </summary>
|
|
private void UPD_SenseDriveStatus()
|
|
{
|
|
switch (ActivePhase)
|
|
{
|
|
//----------------------------------------
|
|
// FDC is waiting for a command byte
|
|
//----------------------------------------
|
|
case Phase.Idle:
|
|
break;
|
|
|
|
//----------------------------------------
|
|
// Receiving command parameter bytes
|
|
//----------------------------------------
|
|
case Phase.Command:
|
|
// store the parameter in the command buffer
|
|
CommBuffer[CommCounter] = LastByteReceived;
|
|
|
|
// process parameter byte
|
|
ParseParamByteStandard(CommCounter);
|
|
|
|
// increment command parameter counter
|
|
CommCounter++;
|
|
|
|
// was that the last parameter byte?
|
|
if (CommCounter == ActiveCommand.ParameterByteCount)
|
|
{
|
|
// all parameter bytes received
|
|
ActivePhase = Phase.Execution;
|
|
UPD_SenseDriveStatus();
|
|
}
|
|
break;
|
|
|
|
//----------------------------------------
|
|
// FDC in execution phase reading/writing bytes
|
|
//----------------------------------------
|
|
case Phase.Execution:
|
|
// one ST3 byte required
|
|
|
|
// set US
|
|
Status3 = (byte)ActiveDrive.ID;
|
|
|
|
if (Status3 != 0)
|
|
{
|
|
// we only support 1 drive
|
|
SetBit(SR3_FT, ref Status3);
|
|
}
|
|
else
|
|
{
|
|
// HD - only one side
|
|
UnSetBit(SR3_HD, ref Status3);
|
|
|
|
// write protect
|
|
if (ActiveDrive.FLAG_WRITEPROTECT)
|
|
SetBit(SR3_WP, ref Status3);
|
|
|
|
// track 0
|
|
if (ActiveDrive.FLAG_TRACK0)
|
|
SetBit(SR3_T0, ref Status3);
|
|
|
|
// rdy
|
|
if (ActiveDrive.Disk != null)
|
|
SetBit(SR3_RY, ref Status3);
|
|
}
|
|
|
|
ResBuffer[0] = Status3;
|
|
ActivePhase = Phase.Result;
|
|
|
|
break;
|
|
|
|
//----------------------------------------
|
|
// Result bytes being sent to CPU
|
|
//----------------------------------------
|
|
case Phase.Result:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Version
|
|
/// COMMAND: NO parameter bytes
|
|
/// EXECUTION: NO execution phase
|
|
/// RESULT: 1 result byte
|
|
/// </summary>
|
|
private void UPD_Version()
|
|
{
|
|
switch (ActivePhase)
|
|
{
|
|
case Phase.Idle:
|
|
case Phase.Command:
|
|
case Phase.Execution:
|
|
case Phase.Result:
|
|
UPD_Invalid();
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Invalid
|
|
/// COMMAND: NO parameter bytes
|
|
/// EXECUTION: NO execution phase
|
|
/// RESULT: 1 result byte
|
|
/// </summary>
|
|
private void UPD_Invalid()
|
|
{
|
|
switch (ActivePhase)
|
|
{
|
|
//----------------------------------------
|
|
// FDC is waiting for a command byte
|
|
//----------------------------------------
|
|
case Phase.Idle:
|
|
break;
|
|
|
|
//----------------------------------------
|
|
// Receiving command parameter bytes
|
|
//----------------------------------------
|
|
case Phase.Command:
|
|
break;
|
|
|
|
//----------------------------------------
|
|
// FDC in execution phase reading/writing bytes
|
|
//----------------------------------------
|
|
case Phase.Execution:
|
|
// no execution phase
|
|
ActivePhase = Phase.Result;
|
|
UPD_Invalid();
|
|
break;
|
|
|
|
//----------------------------------------
|
|
// Result bytes being sent to CPU
|
|
//----------------------------------------
|
|
case Phase.Result:
|
|
ResBuffer[0] = 0x80;
|
|
break;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#endregion
|
|
|
|
#region Controller Methods
|
|
|
|
/// <summary>
|
|
/// Called when a status register read is required
|
|
/// This can be called at any time
|
|
/// The main status register appears to be queried nearly all the time
|
|
/// so needs to be kept updated. It keeps the CPU informed of the current state
|
|
/// </summary>
|
|
private byte ReadMainStatus()
|
|
{
|
|
SetBit(MSR_RQM, ref StatusMain);
|
|
|
|
switch (ActivePhase)
|
|
{
|
|
case Phase.Idle:
|
|
UnSetBit(MSR_DIO, ref StatusMain);
|
|
UnSetBit(MSR_CB, ref StatusMain);
|
|
UnSetBit(MSR_EXM, ref StatusMain);
|
|
break;
|
|
case Phase.Command:
|
|
UnSetBit(MSR_DIO, ref StatusMain);
|
|
SetBit(MSR_CB, ref StatusMain);
|
|
UnSetBit(MSR_EXM, ref StatusMain);
|
|
break;
|
|
case Phase.Execution:
|
|
if (ActiveCommand.Direction == CommandDirection.OUT)
|
|
SetBit(MSR_DIO, ref StatusMain);
|
|
else
|
|
UnSetBit(MSR_DIO, ref StatusMain);
|
|
|
|
SetBit(MSR_EXM, ref StatusMain);
|
|
SetBit(MSR_CB, ref StatusMain);
|
|
|
|
// overrun detection
|
|
OverrunCounter++;
|
|
if (OverrunCounter >= 64)
|
|
{
|
|
// CPU has read the status register 64 times without reading the data register
|
|
// switch the current command into result phase
|
|
ActivePhase = Phase.Result;
|
|
|
|
// reset the overun counter
|
|
OverrunCounter = 0;
|
|
}
|
|
|
|
break;
|
|
case Phase.Result:
|
|
SetBit(MSR_DIO, ref StatusMain);
|
|
SetBit(MSR_CB, ref StatusMain);
|
|
UnSetBit(MSR_EXM, ref StatusMain);
|
|
break;
|
|
}
|
|
|
|
//if (!CheckTiming())
|
|
//{
|
|
// UnSetBit(MSR_EXM, ref StatusMain);
|
|
//}
|
|
|
|
return StatusMain;
|
|
}
|
|
private int testCount = 0;
|
|
/// <summary>
|
|
/// Handles CPU reading from the data register
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
private byte ReadDataRegister()
|
|
{
|
|
// default return value
|
|
byte res = 0xff;
|
|
|
|
// check RQM flag status
|
|
if (!GetBit(MSR_RQM, StatusMain))
|
|
{
|
|
// FDC is not ready to return data
|
|
return res;
|
|
}
|
|
|
|
// check active direction
|
|
if (!GetBit(MSR_DIO, StatusMain))
|
|
{
|
|
// FDC is expecting to receive, not send data
|
|
return res;
|
|
}
|
|
|
|
switch (ActivePhase)
|
|
{
|
|
case Phase.Execution:
|
|
// reset overrun counter
|
|
OverrunCounter = 0;
|
|
|
|
// execute read
|
|
ActiveCommand.CommandDelegate();
|
|
|
|
res = LastSectorDataReadByte;
|
|
|
|
if (ExecCounter <= 0)
|
|
{
|
|
// end of execution phase
|
|
ActivePhase = Phase.Result;
|
|
}
|
|
|
|
return res;
|
|
|
|
case Phase.Result:
|
|
|
|
DriveLight = false;
|
|
|
|
ActiveCommand.CommandDelegate();
|
|
|
|
// result byte reading
|
|
res = ResBuffer[ResCounter];
|
|
|
|
// increment result counter
|
|
ResCounter++;
|
|
|
|
if (ResCounter >= ResLength)
|
|
{
|
|
ActivePhase = Phase.Idle;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handles CPU writing to the data register
|
|
/// </summary>
|
|
/// <param name="data"></param>
|
|
private void WriteDataRegister(byte data)
|
|
{
|
|
if (!GetBit(MSR_RQM, StatusMain) || GetBit(MSR_DIO, StatusMain))
|
|
{
|
|
// FDC will not receive and process any bytes
|
|
return;
|
|
}
|
|
|
|
// store the incoming byte
|
|
LastByteReceived = data;
|
|
|
|
// process incoming bytes
|
|
switch (ActivePhase)
|
|
{
|
|
//// controller is idle awaiting the first command byte of a new instruction
|
|
case Phase.Idle:
|
|
ParseCommandByte(data);
|
|
break;
|
|
//// we are in command phase
|
|
case Phase.Command:
|
|
// attempt to process this parameter byte
|
|
//ProcessCommand(data);
|
|
ActiveCommand.CommandDelegate();
|
|
break;
|
|
//// we are in execution phase
|
|
case Phase.Execution:
|
|
// CPU is going to be sending data bytes to the FDC to be written to disk
|
|
|
|
// store the byte
|
|
LastSectorDataWriteByte = data;
|
|
ActiveCommand.CommandDelegate();
|
|
|
|
if (ExecCounter <= 0)
|
|
{
|
|
// end of execution phase
|
|
ActivePhase = Phase.Result;
|
|
}
|
|
|
|
break;
|
|
//// result phase
|
|
case Phase.Result:
|
|
// data register will not receive bytes during result phase
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Processes the first command byte (within a command instruction)
|
|
/// Returns TRUE if successful. FALSE if otherwise
|
|
/// Called only in idle phase
|
|
/// </summary>
|
|
/// <param name="cmdByte"></param>
|
|
/// <param name="direction"></param>
|
|
/// <returns></returns>
|
|
private bool ParseCommandByte(byte cmdByte)
|
|
{
|
|
// clear counters
|
|
CommCounter = 0;
|
|
ResCounter = 0;
|
|
|
|
// get the first 4 bytes
|
|
byte cByte = (byte)(cmdByte & 0x0f);
|
|
|
|
// get MT, MD and SK states
|
|
CMD_FLAG_MT = cmdByte.Bit(7);
|
|
CMD_FLAG_MF = cmdByte.Bit(6);
|
|
CMD_FLAG_SK = cmdByte.Bit(5);
|
|
|
|
cmdByte = cByte;
|
|
|
|
// lookup the command
|
|
var cmd = CommandList.Where(a => a.CommandCode == cmdByte).FirstOrDefault();
|
|
|
|
if (cmd == null)
|
|
{
|
|
// no command found - use invalid
|
|
CMDIndex = CommandList.Count() - 1;
|
|
}
|
|
else
|
|
{
|
|
// valid command found
|
|
CMDIndex = CommandList.FindIndex(a => a.CommandCode == cmdByte);
|
|
|
|
// check validity of command byte flags
|
|
// if a flag is set but not valid for this command then it is invalid
|
|
bool invalid = false;
|
|
|
|
if (!ActiveCommand.MT)
|
|
if (CMD_FLAG_MT)
|
|
invalid = true;
|
|
if (!ActiveCommand.MF)
|
|
if (CMD_FLAG_MF)
|
|
invalid = true;
|
|
if (!ActiveCommand.SK)
|
|
if (CMD_FLAG_SK)
|
|
invalid = true;
|
|
|
|
if (invalid)
|
|
{
|
|
// command byte included spurious bit 5,6 or 7 flags
|
|
CMDIndex = CommandList.Count() - 1;
|
|
}
|
|
|
|
/*
|
|
if ((CMD_FLAG_MF && !ActiveCommand.MF) ||
|
|
(CMD_FLAG_MT && !ActiveCommand.MT) ||
|
|
(CMD_FLAG_SK && !ActiveCommand.SK))
|
|
{
|
|
// command byte included spurious bit 5,6 or 7 flags
|
|
CMDIndex = CommandList.Count() - 1;
|
|
}
|
|
*/
|
|
}
|
|
|
|
CommCounter = 0;
|
|
ResCounter = 0;
|
|
|
|
// there will now be an active command set
|
|
// move to command phase
|
|
ActivePhase = Phase.Command;
|
|
|
|
/*
|
|
// check for invalid SIS
|
|
if (ActiveInterrupt == InterruptState.None && CMDIndex == CC_SENSE_INTSTATUS)
|
|
{
|
|
CMDIndex = CC_INVALID;
|
|
//ActiveCommand.CommandDelegate(InstructionState.StartResult);
|
|
}
|
|
*/
|
|
|
|
// set reslength
|
|
ResLength = ActiveCommand.ResultByteCount;
|
|
|
|
// if there are no expected param bytes to receive - go ahead and run the command
|
|
if (ActiveCommand.ParameterByteCount == 0)
|
|
{
|
|
ActivePhase = Phase.Execution;
|
|
ActiveCommand.CommandDelegate();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parses the first 5 command argument bytes that are of the standard format
|
|
/// </summary>
|
|
/// <param name="paramIndex"></param>
|
|
private void ParseParamByteStandard(int index)
|
|
{
|
|
byte currByte = CommBuffer[index];
|
|
BitArray bi = new BitArray(new byte[] { currByte });
|
|
|
|
switch (index)
|
|
{
|
|
// HD & US
|
|
case CM_HEAD:
|
|
if (bi[2])
|
|
ActiveCommandParams.Side = 1;
|
|
else
|
|
ActiveCommandParams.Side = 0;
|
|
|
|
ActiveCommandParams.UnitSelect = (byte)(GetUnitSelect(currByte));
|
|
DiskDriveIndex = ActiveCommandParams.UnitSelect;
|
|
break;
|
|
|
|
// C
|
|
case CM_C:
|
|
ActiveCommandParams.Cylinder = currByte;
|
|
break;
|
|
|
|
// H
|
|
case CM_H:
|
|
ActiveCommandParams.Head = currByte;
|
|
break;
|
|
|
|
// R
|
|
case CM_R:
|
|
ActiveCommandParams.Sector = currByte;
|
|
break;
|
|
|
|
// N
|
|
case CM_N:
|
|
ActiveCommandParams.SectorSize = currByte;
|
|
break;
|
|
|
|
// EOT
|
|
case CM_EOT:
|
|
ActiveCommandParams.EOT = currByte;
|
|
break;
|
|
|
|
// GPL
|
|
case CM_GPL:
|
|
ActiveCommandParams.Gap3Length = currByte;
|
|
break;
|
|
|
|
// DTL
|
|
case CM_DTL:
|
|
ActiveCommandParams.DTL = currByte;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clears the result buffer
|
|
/// </summary>
|
|
public void ClearResultBuffer()
|
|
{
|
|
for (int i = 0; i < ResBuffer.Length; i++)
|
|
{
|
|
ResBuffer[i] = 0;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clears the result buffer
|
|
/// </summary>
|
|
public void ClearExecBuffer()
|
|
{
|
|
for (int i = 0; i < ExecBuffer.Length; i++)
|
|
{
|
|
ExecBuffer[i] = 0;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Populates the result status registers
|
|
/// </summary>
|
|
private void CommitResultStatus()
|
|
{
|
|
// check for read diag
|
|
if (ActiveCommand.CommandCode == 0x02)
|
|
{
|
|
// commit to result buffer
|
|
ResBuffer[RS_ST0] = Status0;
|
|
ResBuffer[RS_ST1] = Status1;
|
|
return;
|
|
}
|
|
|
|
// check for error bits
|
|
if (GetBit(SR1_DE, Status1) ||
|
|
GetBit(SR1_MA, Status1) ||
|
|
GetBit(SR1_ND, Status1) ||
|
|
GetBit(SR1_NW, Status1) ||
|
|
GetBit(SR1_OR, Status1) ||
|
|
GetBit(SR2_BC, Status2) ||
|
|
GetBit(SR2_CM, Status2) ||
|
|
GetBit(SR2_DD, Status2) ||
|
|
GetBit(SR2_MD, Status2) ||
|
|
GetBit(SR2_SN, Status2) ||
|
|
GetBit(SR2_WC, Status2))
|
|
{
|
|
// error bits set - unset end of track
|
|
UnSetBit(SR1_EN, ref Status1);
|
|
}
|
|
|
|
// check for data errors
|
|
if (GetBit(SR1_DE, Status1) ||
|
|
GetBit(SR2_DD, Status2))
|
|
{
|
|
// unset control mark
|
|
UnSetBit(SR2_CM, ref Status2);
|
|
}
|
|
else if (GetBit(SR2_CM, Status2))
|
|
{
|
|
// DAM found - unset IC and US0
|
|
UnSetBit(SR0_IC0, ref Status0);
|
|
UnSetBit(SR0_US0, ref Status0);
|
|
}
|
|
|
|
// commit to result buffer
|
|
ResBuffer[RS_ST0] = Status0;
|
|
ResBuffer[RS_ST1] = Status1;
|
|
ResBuffer[RS_ST2] = Status2;
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Populates the result CHRN values
|
|
/// </summary>
|
|
private void CommitResultCHRN()
|
|
{
|
|
ResBuffer[RS_C] = ActiveCommandParams.Cylinder;
|
|
ResBuffer[RS_H] = ActiveCommandParams.Head;
|
|
ResBuffer[RS_R] = ActiveCommandParams.Sector;
|
|
ResBuffer[RS_N] = ActiveCommandParams.SectorSize;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Moves active phase into idle
|
|
/// </summary>
|
|
public void SetPhase_Idle()
|
|
{
|
|
ActivePhase = Phase.Idle;
|
|
|
|
// active direction
|
|
UnSetBit(MSR_DIO, ref StatusMain);
|
|
// CB
|
|
UnSetBit(MSR_CB, ref StatusMain);
|
|
// RQM
|
|
SetBit(MSR_RQM, ref StatusMain);
|
|
|
|
CommCounter = 0;
|
|
ResCounter = 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Moves to result phase
|
|
/// </summary>
|
|
public void SetPhase_Result()
|
|
{
|
|
ActivePhase = Phase.Result;
|
|
|
|
// active direction
|
|
SetBit(MSR_DIO, ref StatusMain);
|
|
// CB
|
|
SetBit(MSR_CB, ref StatusMain);
|
|
// RQM
|
|
SetBit(MSR_RQM, ref StatusMain);
|
|
// EXM
|
|
UnSetBit(MSR_EXM, ref StatusMain);
|
|
|
|
CommCounter = 0;
|
|
ResCounter = 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Moves to command phase
|
|
/// </summary>
|
|
public void SetPhase_Command()
|
|
{
|
|
ActivePhase = Phase.Command;
|
|
|
|
// default 0x80 - just RQM
|
|
SetBit(MSR_RQM, ref StatusMain);
|
|
UnSetBit(MSR_DIO, ref StatusMain);
|
|
UnSetBit(MSR_CB, ref StatusMain);
|
|
UnSetBit(MSR_EXM, ref StatusMain);
|
|
CommCounter = 0;
|
|
ResCounter = 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Moves to execution phase
|
|
/// </summary>
|
|
public void SetPhase_Execution()
|
|
{
|
|
ActivePhase = Phase.Execution;
|
|
|
|
// EXM
|
|
SetBit(MSR_EXM, ref StatusMain);
|
|
// CB
|
|
SetBit(MSR_CB, ref StatusMain);
|
|
// RQM
|
|
UnSetBit(MSR_RQM, ref StatusMain);
|
|
|
|
CommCounter = 0;
|
|
ResCounter = 0;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|