785 lines
22 KiB
C
785 lines
22 KiB
C
/*
|
|
Hatari - floppy_ipf.c
|
|
|
|
This file is distributed under the GNU General Public License, version 2 or at
|
|
your option any later version. Read the file gpl.txt for details.
|
|
|
|
IPF disk image support.
|
|
|
|
IPF files are handled using the capsimage library, which emulates the FDC
|
|
at low level and allows to read complex protections.
|
|
|
|
RAW files made with KryoFlux board or CT RAW dumped with an Amiga are also handled
|
|
by the capsimage library.
|
|
*/
|
|
const char floppy_ipf_fileid[] = "Hatari floppy_ipf.c : " __DATE__ " " __TIME__;
|
|
|
|
#include "main.h"
|
|
#include "file.h"
|
|
#include "floppy.h"
|
|
#include "floppy_ipf.h"
|
|
#include "fdc.h"
|
|
#include "log.h"
|
|
#include "memorySnapShot.h"
|
|
#include "screen.h"
|
|
#include "video.h"
|
|
#include "cycles.h"
|
|
|
|
#ifdef HAVE_CAPSIMAGE
|
|
#if CAPSIMAGE_VERSION == 5
|
|
#include <caps5/CapsLibAll.h>
|
|
#else
|
|
#include <caps/fdc.h>
|
|
#define CAPS_LIB_RELEASE 4
|
|
#define CAPS_LIB_REVISION 2
|
|
#endif
|
|
/* Macro to check release and revision */
|
|
#define CAPS_LIB_REL_REV ( CAPS_LIB_RELEASE * 100 + CAPS_LIB_REVISION )
|
|
#endif
|
|
|
|
|
|
typedef struct
|
|
{
|
|
#ifdef HAVE_CAPSIMAGE
|
|
Uint32 CapsLibRelease;
|
|
Uint32 CapsLibRevision;
|
|
|
|
struct CapsFdc Fdc; /* Fdc state */
|
|
struct CapsDrive Drive[ MAX_FLOPPYDRIVES ]; /* Physical drives */
|
|
CapsLong CapsImage[ MAX_FLOPPYDRIVES ]; /* For the IPF disk images */
|
|
|
|
int Rev_Track[ MAX_FLOPPYDRIVES ]; /* Needed to handle CAPSSetRevolution for type II/III commands */
|
|
int Rev_Side[ MAX_FLOPPYDRIVES ];
|
|
|
|
bool DriveEnabled[ MAX_FLOPPYDRIVES ];/* Is drive ON or OFF */
|
|
bool DoubleSided[ MAX_FLOPPYDRIVES ];/* Is drive double sided or not */
|
|
|
|
Sint64 FdcClock; /* Current value of CyclesGlobalClockCounter */
|
|
#endif
|
|
} IPF_STRUCT;
|
|
|
|
|
|
static IPF_STRUCT IPF_State; /* All variables related to the IPF support */
|
|
|
|
|
|
#ifdef HAVE_CAPSIMAGE
|
|
static void IPF_CallBack_Trk ( struct CapsFdc *pc , CapsULong State );
|
|
static void IPF_CallBack_Irq ( struct CapsFdc *pc , CapsULong State );
|
|
static void IPF_CallBack_Drq ( struct CapsFdc *pc , CapsULong State );
|
|
static void IPF_Drive_Update_Enable_Side ( void );
|
|
#endif
|
|
|
|
|
|
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
/**
|
|
* Save/Restore snapshot of local variables('MemorySnapShot_Store' handles type)
|
|
* We must take care of whether Hatari was compiled with IPF support of not
|
|
* when saving/restoring snapshots to avoid incompatibilies.
|
|
*/
|
|
void IPF_MemorySnapShot_Capture(bool bSave)
|
|
{
|
|
int StructSize;
|
|
int Drive;
|
|
|
|
if ( bSave ) /* Saving snapshot */
|
|
{
|
|
StructSize = sizeof ( IPF_State ); /* 0 if HAVE_CAPSIMAGE is not defined */
|
|
MemorySnapShot_Store(&StructSize, sizeof(StructSize));
|
|
fprintf ( stderr , "ipf save %d\n" , StructSize );
|
|
if ( StructSize > 0 )
|
|
MemorySnapShot_Store(&IPF_State, sizeof(IPF_State));
|
|
}
|
|
|
|
else /* Restoring snapshot */
|
|
{
|
|
MemorySnapShot_Store(&StructSize, sizeof(StructSize));
|
|
fprintf ( stderr , "ipf load %d\n" , StructSize );
|
|
if ( ( StructSize == 0 ) && ( sizeof ( IPF_State ) > 0 ) )
|
|
{
|
|
Log_AlertDlg(LOG_ERROR, "This memory snapshot doesn't include IPF data but this version of Hatari was built with IPF support");
|
|
return; /* Continue restoring the rest of the memory snapshot */
|
|
}
|
|
else if ( ( StructSize > 0 ) && ( sizeof ( IPF_State ) == 0 ) )
|
|
{
|
|
Log_AlertDlg(LOG_ERROR, "This memory snapshot includes IPF data but this version of Hatari was not built with IPF support");
|
|
MemorySnapShot_Skip( StructSize ); /* Ignore the IPF data */
|
|
return; /* Continue restoring the rest of the memory snapshot */
|
|
}
|
|
else if ( ( StructSize > 0 ) && ( StructSize != sizeof ( IPF_State ) ) )
|
|
{
|
|
Log_AlertDlg(LOG_ERROR, "This memory snapshot includes IPF data different from the ones handled in this version of Hatari");
|
|
MemorySnapShot_Skip( StructSize ); /* Ignore the IPF data */
|
|
return; /* Continue restoring the rest of the memory snapshot */
|
|
}
|
|
|
|
if ( StructSize > 0 )
|
|
{
|
|
MemorySnapShot_Store(&IPF_State, sizeof(IPF_State));
|
|
|
|
#ifdef HAVE_CAPSIMAGE
|
|
/* For IPF structures, we need to update some pointers in Fdc/Drive/CapsImage */
|
|
/* drive : PUBYTE trackbuf, PUDWORD timebuf */
|
|
/* fdc : PCAPSDRIVE driveprc, PCAPSDRIVE drive, CAPSFDCHOOK callback functions */
|
|
IPF_State.Fdc.drive = IPF_State.Drive; /* Connect drives array to the FDC */
|
|
if ( IPF_State.Fdc.driveprc != NULL ) /* Recompute active drive's pointer */
|
|
IPF_State.Fdc.driveprc = IPF_State.Fdc.drive + IPF_State.Fdc.driveact;
|
|
|
|
CAPSFdcInvalidateTrack ( &IPF_State.Fdc , 0 ); /* Invalidate buffered track data for drive 0 */
|
|
CAPSFdcInvalidateTrack ( &IPF_State.Fdc , 1 ); /* Invalidate buffered track data for drive 1 */
|
|
|
|
/* Set callback functions */
|
|
IPF_State.Fdc.cbirq = IPF_CallBack_Irq;
|
|
IPF_State.Fdc.cbdrq = IPF_CallBack_Drq;
|
|
IPF_State.Fdc.cbtrk = IPF_CallBack_Trk;
|
|
#endif
|
|
|
|
/* Call IPF_Insert to recompute IPF_State.CapsImage[ Drive ] */
|
|
for ( Drive=0 ; Drive < MAX_FLOPPYDRIVES ; Drive++ )
|
|
if ( EmulationDrives[Drive].ImageType == FLOPPY_IMAGE_TYPE_IPF )
|
|
if ( IPF_Insert ( Drive , EmulationDrives[Drive].pBuffer , EmulationDrives[Drive].nImageBytes ) == false )
|
|
{
|
|
Log_AlertDlg(LOG_ERROR, "Error restoring IPF image %s in drive %d" ,
|
|
EmulationDrives[Drive].sFileName , Drive );
|
|
return;
|
|
}
|
|
|
|
fprintf ( stderr , "ipf load ok\n" );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
/**
|
|
* Does filename end with a .IPF or .RAW or .CTR extension ? If so, return true.
|
|
* .RAW and .CTR support requires caps lib >= 5.1
|
|
*/
|
|
bool IPF_FileNameIsIPF(const char *pszFileName, bool bAllowGZ)
|
|
{
|
|
return ( File_DoesFileExtensionMatch(pszFileName,".ipf" )
|
|
|| ( bAllowGZ && File_DoesFileExtensionMatch(pszFileName,".ipf.gz") )
|
|
#if CAPS_LIB_REL_REV >= 501
|
|
|| File_DoesFileExtensionMatch(pszFileName,".raw" )
|
|
|| ( bAllowGZ && File_DoesFileExtensionMatch(pszFileName,".raw.gz") )
|
|
|| File_DoesFileExtensionMatch(pszFileName,".ctr" )
|
|
|| ( bAllowGZ && File_DoesFileExtensionMatch(pszFileName,".ctr.gz") )
|
|
#endif
|
|
);
|
|
}
|
|
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
/**
|
|
* Load .IPF file into memory, set number of bytes loaded and return a pointer
|
|
* to the buffer.
|
|
*/
|
|
Uint8 *IPF_ReadDisk(int Drive, const char *pszFileName, long *pImageSize, int *pImageType)
|
|
{
|
|
#ifndef HAVE_CAPSIMAGE
|
|
Log_AlertDlg(LOG_ERROR, "This version of Hatari was not built with IPF support, this disk image can't be handled.");
|
|
return NULL;
|
|
|
|
#else
|
|
Uint8 *pIPFFile;
|
|
|
|
*pImageSize = 0;
|
|
|
|
/* Just load directly a buffer, and set ImageSize accordingly */
|
|
pIPFFile = File_Read(pszFileName, pImageSize, NULL);
|
|
if (!pIPFFile)
|
|
{
|
|
*pImageSize = 0;
|
|
return NULL;
|
|
}
|
|
|
|
*pImageType = FLOPPY_IMAGE_TYPE_IPF;
|
|
return pIPFFile;
|
|
#endif
|
|
}
|
|
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
/**
|
|
* Save .IPF file from memory buffer. Returns true is all OK.
|
|
*/
|
|
bool IPF_WriteDisk(int Drive, const char *pszFileName, Uint8 *pBuffer, int ImageSize)
|
|
{
|
|
/* saving is not supported for IPF files */
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
* Init the FDC and the drives used to handle IPF images
|
|
*/
|
|
bool IPF_Init ( void )
|
|
{
|
|
#ifndef HAVE_CAPSIMAGE
|
|
return true;
|
|
|
|
#else
|
|
int i;
|
|
struct CapsVersionInfo caps_vi;
|
|
|
|
fprintf ( stderr , "IPF : IPF_Init\n" );
|
|
|
|
if ( CAPSInit() != imgeOk )
|
|
{
|
|
fprintf ( stderr , "IPF : Could not initialize the capsimage library\n" );
|
|
return false;
|
|
}
|
|
|
|
if ( CAPSGetVersionInfo ( &caps_vi , 0 ) != imgeOk )
|
|
{
|
|
fprintf ( stderr , "IPF : CAPSVersionInfo failed\n" );
|
|
return false;
|
|
}
|
|
fprintf ( stderr , "IPF : capsimage library version release=%d revision=%d\n" , (int)caps_vi.release , (int)caps_vi.revision );
|
|
IPF_State.CapsLibRelease = caps_vi.release;
|
|
IPF_State.CapsLibRevision = caps_vi.revision;
|
|
|
|
/* Default values for each physical drive */
|
|
memset ( IPF_State.Drive , 0 , sizeof ( IPF_State.Drive ) );
|
|
for ( i=0 ; i < MAX_FLOPPYDRIVES ; i++ )
|
|
{
|
|
IPF_State.Drive[ i ].type = sizeof ( struct CapsDrive );
|
|
IPF_State.Drive[ i ].rpm = CAPSDRIVE_35DD_RPM;
|
|
IPF_State.Drive[ i ].maxtrack = CAPSDRIVE_35DD_HST;
|
|
|
|
IPF_State.Rev_Track[ i ] = -1;
|
|
IPF_State.Rev_Side[ i ] = -1;
|
|
|
|
IPF_State.DriveEnabled[ i ] = true;
|
|
IPF_State.DoubleSided[ i ] = true;
|
|
}
|
|
|
|
/* Init FDC with 2 physical drives */
|
|
memset ( &IPF_State.Fdc , 0 , sizeof ( IPF_State.Fdc ) );
|
|
IPF_State.Fdc.type = sizeof( struct CapsFdc );
|
|
IPF_State.Fdc.model = cfdcmWD1772;
|
|
IPF_State.Fdc.drive = IPF_State.Drive;
|
|
IPF_State.Fdc.drivecnt = MAX_FLOPPYDRIVES;
|
|
|
|
if ( CAPSFdcInit ( &IPF_State.Fdc ) != imgeOk)
|
|
{
|
|
fprintf ( stderr , "IPF : CAPSFdcInit failed\n" );
|
|
return false;
|
|
}
|
|
|
|
/* 2 drives by default */
|
|
IPF_State.Fdc.drivemax = MAX_FLOPPYDRIVES;
|
|
/* Update drives' state in case we have some drives ON or OFF in config or parameters */
|
|
IPF_Drive_Update_Enable_Side ();
|
|
|
|
/* FDC clock */
|
|
IPF_State.Fdc.clockfrq = 8000000;
|
|
|
|
/* Set callback functions */
|
|
IPF_State.Fdc.cbirq = IPF_CallBack_Irq;
|
|
IPF_State.Fdc.cbdrq = IPF_CallBack_Drq;
|
|
IPF_State.Fdc.cbtrk = IPF_CallBack_Trk;
|
|
|
|
CAPSFdcReset ( &IPF_State.Fdc );
|
|
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
* Exit
|
|
*/
|
|
void IPF_Exit ( void )
|
|
{
|
|
#ifndef HAVE_CAPSIMAGE
|
|
#else
|
|
CAPSExit();
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
* Init the ressources to handle the IPF image inserted into a drive (0=A: 1=B:)
|
|
*/
|
|
bool IPF_Insert ( int Drive , Uint8 *pImageBuffer , long ImageSize )
|
|
{
|
|
#ifndef HAVE_CAPSIMAGE
|
|
return false;
|
|
|
|
#else
|
|
CapsLong ImageId;
|
|
#if CAPS_LIB_REL_REV >= 501
|
|
CapsLong ImageType;
|
|
#endif
|
|
|
|
ImageId = CAPSAddImage();
|
|
if ( ImageId < 0 )
|
|
{
|
|
fprintf ( stderr , "IPF : error CAPSAddImage\n" );
|
|
return false;
|
|
}
|
|
|
|
#if CAPS_LIB_REL_REV >= 501
|
|
ImageType = CAPSGetImageTypeMemory ( pImageBuffer , ImageSize );
|
|
if ( ImageType == citError )
|
|
{
|
|
fprintf ( stderr , "IPF : error CAPSGetImageTypeMemory\n" );
|
|
return false;
|
|
}
|
|
else if ( ImageType == citUnknown )
|
|
{
|
|
fprintf ( stderr , "IPF : unknown image type\n" );
|
|
return false;
|
|
}
|
|
|
|
fprintf ( stderr , "IPF : IPF_Insert drive=%d buf=%p size=%ld imageid=%d type=" , Drive , pImageBuffer , ImageSize , ImageId );
|
|
switch ( ImageType ) {
|
|
case citIPF: fprintf ( stderr , "IPF\n" ); break;
|
|
case citCTRaw: fprintf ( stderr , "CT RAW\n" ); break;
|
|
case citKFStream: fprintf ( stderr , "KF STREAM\n" ) ; break;
|
|
case citDraft: fprintf ( stderr , "DRAFT\n" ) ; break;
|
|
default : fprintf ( stderr , "NOT SUPPORTED\n" );
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
if ( CAPSLockImageMemory ( ImageId , pImageBuffer , (CapsULong)ImageSize , DI_LOCK_MEMREF ) == imgeOk )
|
|
{
|
|
struct CapsImageInfo cii;
|
|
int i;
|
|
|
|
/* Print some debug infos */
|
|
if ( CAPSGetImageInfo ( &cii , ImageId ) == imgeOk )
|
|
{
|
|
printf("Type: %d\n", (int)cii.type);
|
|
printf("Release: %d\n", (int)cii.release);
|
|
printf("Revision: %d\n", (int)cii.revision);
|
|
printf("Min Cylinder: %d\n", (int)cii.mincylinder);
|
|
printf("Max Cylinder: %d\n", (int)cii.maxcylinder);
|
|
printf("Min Head: %d\n", (int)cii.minhead);
|
|
printf("Max Head: %d\n", (int)cii.maxhead);
|
|
printf("Creation Date: %04d/%02d/%02d %02d:%02d:%02d.%03d\n", (int)cii.crdt.year, (int)cii.crdt.month, (int)cii.crdt.day, (int)cii.crdt.hour, (int)cii.crdt.min, (int)cii.crdt.sec, (int)cii.crdt.tick);
|
|
printf("Platforms:");
|
|
for (i = 0; i < CAPS_MAXPLATFORM; i++)
|
|
if (cii.platform[i] != ciipNA)
|
|
printf ( " %s" , CAPSGetPlatformName(cii.platform[i]) );
|
|
printf("\n");
|
|
|
|
/* Some IPF disks are not correctly supported yet : display a warning */
|
|
if ( (int)cii.release == 3222 ) /* Sundog */
|
|
Log_AlertDlg ( LOG_INFO , "'Sundog' is not correctly supported yet, it requires write access." );
|
|
else if ( (int)cii.release == 3058 ) /* Lethal Xcess */
|
|
Log_AlertDlg ( LOG_INFO , "'Lethal Xcess' is not correctly supported yet, protection will fail" );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CAPSRemImage ( ImageId ) ;
|
|
return false;
|
|
}
|
|
|
|
if ( CAPSLoadImage ( ImageId , DI_LOCK_DENALT | DI_LOCK_DENVAR | DI_LOCK_UPDATEFD ) != imgeOk )
|
|
{
|
|
fprintf ( stderr , "IPF : error CAPSLoadImage\n" );
|
|
CAPSUnlockImage ( ImageId );
|
|
CAPSRemImage ( ImageId ) ;
|
|
return false;
|
|
}
|
|
|
|
|
|
IPF_State.CapsImage[ Drive ] = ImageId;
|
|
|
|
IPF_State.Drive[ Drive ].diskattr |= CAPSDRIVE_DA_IN; /* Disk inserted, keep the value for "write protect" */
|
|
|
|
CAPSFdcInvalidateTrack ( &IPF_State.Fdc , Drive ); /* Invalidate previous buffered track data for drive, if any */
|
|
|
|
IPF_State.Rev_Track[ Drive ] = -1; /* Invalidate previous track/side to handle revolution's count */
|
|
IPF_State.Rev_Side[ Drive ] = -1;
|
|
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
* When ejecting a disk, free the ressources associated with an IPF image
|
|
*/
|
|
bool IPF_Eject ( int Drive )
|
|
{
|
|
#ifndef HAVE_CAPSIMAGE
|
|
return false;
|
|
|
|
#else
|
|
fprintf ( stderr , "IPF : IPF_Eject drive=%d imageid=%d\n" , Drive , IPF_State.CapsImage[ Drive ] );
|
|
|
|
CAPSFdcInvalidateTrack ( &IPF_State.Fdc , Drive ); /* Invalidate previous buffered track data for drive, if any */
|
|
|
|
if ( CAPSUnlockImage ( IPF_State.CapsImage[ Drive ] ) < 0 )
|
|
{
|
|
fprintf ( stderr , "IPF : error CAPSUnlockImage\n" );
|
|
return false;
|
|
}
|
|
|
|
if ( CAPSRemImage ( IPF_State.CapsImage[ Drive ] ) < 0 )
|
|
{
|
|
fprintf ( stderr , "IPF : error CAPSRemImage\n" );
|
|
return false;
|
|
}
|
|
|
|
IPF_State.CapsImage[ Drive ] = -1;
|
|
|
|
IPF_State.Drive[ Drive ].diskattr &= ~CAPSDRIVE_DA_IN;
|
|
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
* Reset FDC state when a reset signal was received
|
|
*/
|
|
void IPF_Reset ( void )
|
|
{
|
|
#ifdef HAVE_CAPSIMAGE
|
|
CAPSFdcReset ( &IPF_State.Fdc );
|
|
|
|
IPF_State.FdcClock = CyclesGlobalClockCounter;
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
* Callback function used when track is changed.
|
|
* We need to update the track data by calling CAPSLockTrack
|
|
*/
|
|
#ifdef HAVE_CAPSIMAGE
|
|
static void IPF_CallBack_Trk ( struct CapsFdc *pc , CapsULong State )
|
|
{
|
|
int Drive = State; /* State is the drive number in that case */
|
|
struct CapsDrive *pd = pc->drive+Drive; /* Current drive where the track change occurred */
|
|
struct CapsTrackInfoT1 cti;
|
|
|
|
cti.type=1;
|
|
if ( CAPSLockTrack ( &cti , IPF_State.CapsImage[ Drive ] , pd->buftrack , pd->bufside ,
|
|
DI_LOCK_DENALT|DI_LOCK_DENVAR|DI_LOCK_UPDATEFD|DI_LOCK_TYPE ) != imgeOk )
|
|
return;
|
|
|
|
LOG_TRACE(TRACE_FDC, "fdc ipf callback trk drive=%d buftrack=%d bufside=%d VBL=%d HBL=%d\n" , Drive ,
|
|
(int)pd->buftrack , (int)pd->bufside , nVBLs , nHBL );
|
|
|
|
pd->ttype = cti.type;
|
|
pd->trackbuf = cti.trackbuf;
|
|
pd->timebuf = cti.timebuf;
|
|
pd->tracklen = cti.tracklen;
|
|
pd->overlap = cti.overlap;
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
|
|
/*
|
|
* Callback function used when the FDC change the IRQ signal
|
|
*/
|
|
#ifdef HAVE_CAPSIMAGE
|
|
static void IPF_CallBack_Irq ( struct CapsFdc *pc , CapsULong State )
|
|
{
|
|
LOG_TRACE(TRACE_FDC, "fdc ipf callback irq state=0x%x VBL=%d HBL=%d\n" , (int)State , nVBLs , nHBL );
|
|
|
|
if ( State )
|
|
FDC_SetIRQ ( FDC_IRQ_SOURCE_OTHER ); /* IRQ bit was set */
|
|
else
|
|
FDC_ClearIRQ (); /* IRQ bit was reset */
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
|
|
/*
|
|
* Callback function used when the FDC change the DRQ signal
|
|
* -> copy the byte to/from the DMA's FIFO if it's a read or a write to the disk
|
|
*/
|
|
#ifdef HAVE_CAPSIMAGE
|
|
static void IPF_CallBack_Drq ( struct CapsFdc *pc , CapsULong State )
|
|
{
|
|
Uint8 Byte;
|
|
|
|
if ( State == 0 )
|
|
return; /* DRQ bit was reset, do nothing */
|
|
|
|
if ( FDC_DMA_GetModeControl_R_WR () != 0 ) /* DMA write mode */
|
|
{
|
|
Byte = FDC_DMA_FIFO_Pull (); /* Get a byte from the DMA FIFO */
|
|
CAPSFdcWrite ( &IPF_State.Fdc , 3 , Byte ); /* Write to FDC's reg 3 */
|
|
|
|
LOG_TRACE(TRACE_FDC, "fdc ipf callback drq state=0x%x write byte 0x%02x VBL=%d HBL=%d\n" , (int)State , Byte , nVBLs , nHBL );
|
|
}
|
|
|
|
else /* DMA read mode */
|
|
{
|
|
Byte = CAPSFdcRead ( &IPF_State.Fdc , 3 ); /* Read from FDC's reg 3 */
|
|
FDC_DMA_FIFO_Push ( Byte ); /* Add byte to the DMA FIFO */
|
|
|
|
LOG_TRACE(TRACE_FDC, "fdc ipf callback drq state=0x%x read byte 0x%02x VBL=%d HBL=%d\n" , (int)State , Byte , nVBLs , nHBL );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
/*
|
|
* This function is used to enable/disable a drive when
|
|
* using the UI or command line parameters
|
|
*
|
|
* NOTE : for now, IPF only supports changing drive 1, drive 0
|
|
* is always ON.
|
|
*/
|
|
void IPF_Drive_Set_Enable ( int Drive , bool value )
|
|
{
|
|
#ifndef HAVE_CAPSIMAGE
|
|
return;
|
|
|
|
#else
|
|
IPF_State.DriveEnabled[ Drive ] = value; /* Store the new state */
|
|
|
|
IPF_Drive_Update_Enable_Side (); /* Update IPF's internal state */
|
|
#endif
|
|
}
|
|
|
|
|
|
/*
|
|
* This function is used to configure a drive as single sided
|
|
* or double sided when using the UI or command line parameters
|
|
*/
|
|
void IPF_Drive_Set_DoubleSided ( int Drive , bool value )
|
|
{
|
|
#ifndef HAVE_CAPSIMAGE
|
|
return;
|
|
|
|
#else
|
|
IPF_State.DoubleSided[ Drive ] = value; /* Store the new state */
|
|
|
|
IPF_Drive_Update_Enable_Side (); /* Update IPF's internal state */
|
|
#endif
|
|
}
|
|
|
|
|
|
/*
|
|
* Update IPF's internal state depending on which drives are ON or OFF
|
|
* and if the drive is single or double sided (for capslib >= 5.1)
|
|
*/
|
|
#ifdef HAVE_CAPSIMAGE
|
|
static void IPF_Drive_Update_Enable_Side ( void )
|
|
{
|
|
#if CAPS_LIB_REL_REV >= 501
|
|
int i;
|
|
#endif
|
|
|
|
if ( IPF_State.DriveEnabled[ 1 ] )
|
|
IPF_State.Fdc.drivemax = MAX_FLOPPYDRIVES; /* Should be 2 */
|
|
else
|
|
IPF_State.Fdc.drivemax = MAX_FLOPPYDRIVES - 1; /* Should be 1 */
|
|
|
|
#if CAPS_LIB_REL_REV >= 501
|
|
for ( i=0 ; i < MAX_FLOPPYDRIVES ; i++ )
|
|
{
|
|
if ( IPF_State.DoubleSided[ i ] )
|
|
IPF_State.Drive[ i ].diskattr &= ~CAPSDRIVE_DA_SS; /* Double sided */
|
|
else
|
|
IPF_State.Drive[ i ].diskattr |= CAPSDRIVE_DA_SS; /* Single sided */
|
|
}
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
|
|
/*
|
|
* Set the drive and the side to be used for the next FDC commands
|
|
* io_porta_old is the previous value, io_porta_new is the new value
|
|
* to take into account.
|
|
* We report a side change only when a drive is selected.
|
|
*/
|
|
void IPF_SetDriveSide ( Uint8 io_porta_old , Uint8 io_porta_new )
|
|
{
|
|
#ifndef HAVE_CAPSIMAGE
|
|
return;
|
|
|
|
#else
|
|
int Side;
|
|
|
|
LOG_TRACE(TRACE_FDC, "fdc ipf change drive/side io_porta_old=0x%x io_porta_new=0x%x VBL=%d HBL=%d\n" , io_porta_old , io_porta_new , nVBLs , nHBL );
|
|
|
|
Side = ( (~io_porta_new) & 0x01 ); /* Side 0 or 1 */
|
|
|
|
IPF_State.Fdc.drivenew = -1; /* By default, don't select any drive */
|
|
|
|
/* Check drive 1 first */
|
|
if ( ( io_porta_new & 0x04 ) == 0 )
|
|
{
|
|
IPF_State.Drive[ 1 ].newside = Side;
|
|
IPF_State.Fdc.drivenew = 1; /* Select drive 1 */
|
|
}
|
|
|
|
/* If both drive 0 and drive 1 are enabled, we keep only drive 0 as newdrive */
|
|
if ( ( io_porta_new & 0x02 ) == 0 )
|
|
{
|
|
IPF_State.Drive[ 0 ].newside = Side;
|
|
IPF_State.Fdc.drivenew = 0; /* Select drive 0 (and un-select drive 1 if set above) */
|
|
}
|
|
|
|
IPF_Emulate(); /* Update emulation's state up to this point, then set new drive/side */
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
* Write a byte into one of the FDC registers
|
|
* 0=command 1=track 2=sector 3=data
|
|
*/
|
|
void IPF_FDC_WriteReg ( Uint8 Reg , Uint8 Byte )
|
|
{
|
|
#ifndef HAVE_CAPSIMAGE
|
|
return; /* This should not be reached (an IPF image can't be inserted without capsimage) */
|
|
|
|
#else
|
|
LOG_TRACE(TRACE_FDC, "fdc ipf write reg=%d data=0x%x VBL=%d HBL=%d\n" , Reg , Byte , nVBLs , nHBL );
|
|
|
|
|
|
#if CAPS_LIB_REL_REV >= 501
|
|
/* In the case of CTR images, we must reset the revolution counter */
|
|
/* when a command access data on disk and track/side changed since last access */
|
|
if ( Reg == 0 )
|
|
{
|
|
int Type;
|
|
int Drive;
|
|
|
|
Type = FDC_GetCmdType ( Byte );
|
|
if ( ( Type == 2 ) || ( Type == 3 ) )
|
|
{
|
|
Drive = IPF_State.Fdc.driveact;
|
|
if ( ( Drive >= 0 )
|
|
&& ( ( IPF_State.Drive[ Drive ].side != IPF_State.Rev_Side[ Drive ] ) || ( IPF_State.Drive[ Drive ].track != IPF_State.Rev_Track[ Drive ] ) ) )
|
|
{
|
|
IPF_State.Rev_Side[ Drive ] = IPF_State.Drive[ Drive ].side;
|
|
IPF_State.Rev_Track[ Drive ] = IPF_State.Drive[ Drive ].track;
|
|
CAPSSetRevolution ( IPF_State.CapsImage[ Drive ] , 0 );
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
IPF_Emulate(); /* Update emulation's state up to this point */
|
|
|
|
CAPSFdcWrite ( &IPF_State.Fdc , Reg , Byte );
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
* Read the content of one of the FDC registers
|
|
* 0=status 1=track 2=sector 3=data
|
|
*/
|
|
Uint8 IPF_FDC_ReadReg ( Uint8 Reg )
|
|
{
|
|
#ifndef HAVE_CAPSIMAGE
|
|
return 0; /* This should not be reached (an IPF image can't be inserted without capsimage) */
|
|
#else
|
|
Uint8 Byte;
|
|
|
|
IPF_Emulate(); /* Update emulation's state up to this point */
|
|
|
|
Byte = CAPSFdcRead ( &IPF_State.Fdc , Reg );
|
|
LOG_TRACE(TRACE_FDC, "fdc ipf read reg=%d data=0x%x VBL=%d HBL=%d\n" , Reg , Byte , nVBLs , nHBL );
|
|
|
|
return Byte;
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
* Return the content of some registers to display them in the statusbar
|
|
* We should not call IPF_Emulate() or similar, reading should not change emulation's state
|
|
*/
|
|
void IPF_FDC_StatusBar ( Uint8 *pCommand , Uint8 *pHead , Uint8 *pTrack , Uint8 *pSector , Uint8 *pSide )
|
|
{
|
|
#ifndef HAVE_CAPSIMAGE
|
|
return; /* This should not be reached (an IPF image can't be inserted without capsimage) */
|
|
#else
|
|
int Drive;
|
|
|
|
Drive = IPF_State.Fdc.driveact;
|
|
if ( Drive < 0 ) /* If no drive enabled, use drive O for Head/Side */
|
|
Drive = 0;
|
|
|
|
/* We read directly in the structures, to be sure we don't change emulation's state */
|
|
*pCommand = IPF_State.Fdc.r_command;
|
|
*pHead = IPF_State.Drive[ Drive ].track;
|
|
*pTrack = IPF_State.Fdc.r_track;
|
|
*pSector = IPF_State.Fdc.r_sector;
|
|
*pSide = IPF_State.Drive[ Drive ].side;
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
* Run the FDC emulation during NbCycles cycles (relative to the 8MHz FDC's clock)
|
|
*/
|
|
void IPF_Emulate ( void )
|
|
{
|
|
#ifndef HAVE_CAPSIMAGE
|
|
return;
|
|
|
|
#else
|
|
int NbCycles;
|
|
int Drive;
|
|
|
|
NbCycles = CyclesGlobalClockCounter - IPF_State.FdcClock; /* Number of cycles since last emulation */
|
|
if ( NbCycles < 0 )
|
|
NbCycles = 0; /* We should call CAPSFdcEmulate even when NbCycles=0 */
|
|
|
|
// LOG_TRACE(TRACE_FDC, "fdc ipf emulate cycles=%d VBL=%d HBL=%d clock=%lld\n" , NbCycles , nVBLs , nHBL , CyclesGlobalClockCounter );
|
|
|
|
/* Update Write Protect status for each drive */
|
|
for ( Drive=0 ; Drive < MAX_FLOPPYDRIVES ; Drive++ )
|
|
if ( Floppy_IsWriteProtected ( Drive ) )
|
|
IPF_State.Drive[ Drive ].diskattr |= CAPSDRIVE_DA_WP; /* Disk write protected */
|
|
else
|
|
IPF_State.Drive[ Drive ].diskattr &= ~CAPSDRIVE_DA_WP; /* Disk is not write protected */
|
|
|
|
|
|
CAPSFdcEmulate ( &IPF_State.Fdc , NbCycles ); /* Process at max NbCycles */
|
|
IPF_State.FdcClock += IPF_State.Fdc.clockact; /* clockact can be < NbCycle in some cases */
|
|
|
|
/* Update UI's LEDs depending on Status Register */
|
|
FDC_Drive_Set_BusyLed ( (IPF_State.Fdc.r_st0 & ~IPF_State.Fdc.r_stm) | (IPF_State.Fdc.r_st1 & IPF_State.Fdc.r_stm) );
|
|
#endif
|
|
}
|
|
|
|
|
|
|