blob: 1420a331f7b18f1814fb64983d16c7fa50bd3571 [file] [log] [blame] [raw]
/* PDQ3_fdc.c: PDQ3 simulator Floppy disk device
Work derived from Copyright (c) 2004-2012, Robert M. Supnik
Copyright (c) 2013 Holger Veit
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Except as contained in this notice, the names of Robert M Supnik and Holger Veit
shall not be used in advertising or otherwise to promote the sale, use or other dealings
in this Software without prior written authorization from Robert M Supnik and Holger Veit.
2013xxxx hv initial version
20141003 hv compiler suggested warnings (vc++2013, gcc)
*/
#include "pdq3_defs.h"
#include "sim_imd.h"
#include <ctype.h>
/* FDC/DMA bit definitions */
/* declarations of FDC and DMA chip */
/* drive select register */
#define FDC_SEL_SIDE 0x80 /* 0=side0, 1=side1 */
#define FDC_SEL_SDEN 0x40 /* 0=DDEN, 1=SDEN */
#define FDC_SEL_UNIT3 0x08 /* 1=select */
#define FDC_SEL_UNIT2 0x04 /* 1=select */
#define FDC_SEL_UNIT1 0x02 /* 1=select */
#define FDC_SEL_UNIT0 0x01 /* 1=select */
/* command register */
#define FDC_BIT_HEADLOAD 0x08
#define FDC_BIT_VERIFY 0x04
#define FDC_BIT_STEP3 0x00
#define FDC_BIT_STEP6 0x01
#define FDC_BIT_STEP10 0x02
#define FDC_BIT_STEP15 0x03
#define FDC_BIT_UPDATE 0x10
#define FDC_BIT_MULTI 0x10
#define FDC_BIT_SIDESEL 0x08
#define FDC_BIT_SIDECMP 0x02
#define FDC_BIT_DATAMARK 0x01
#define FDC_BIT_INTIMM 0x08
#define FDC_BIT_INTIDX 0x04
#define FDC_BIT_INTN2R 0x02
#define FDC_BIT_INTR2N 0x01
#define FDC_RESTORE 0x00
#define FDC_SEEK 0x10
#define FDC_STEP 0x20
#define FDC_STEP_U 0x30
#define FDC_STEPIN 0x40
#define FDC_STEPIN_U 0x50
#define FDC_STEPOUT 0x60
#define FDC_STEPOUT_U 0x70
#define FDC_READSEC 0x80
#define FDC_READSEC_M 0x90
#define FDC_WRITESEC 0xa0
#define FDC_WRITESEC_M 0xb0
#define FDC_READADDR 0xc4
#define FDC_READTRK 0xe4
#define FDC_WRITETRK 0xf4
#define FDC_FORCEINT 0xd0
#define FDC_IDLECMD 0xff
#define FDC_CMDMASK 0xf0
/* status register */
#define FDC_ST1_NOTREADY 0x80
#define FDC_ST1_WRTPROT 0x40
#define FDC_ST1_HEADLOAD 0x20
#define FDC_ST1_SEEKERROR 0x10
#define FDC_ST1_CRCERROR 0x08
#define FDC_ST1_TRACK0 0x04
#define FDC_ST1_IDXPULSE 0x02
#define FDC_ST1_BUSY 0x01
#define FDC_ST2_NOTREADY FDC_ST1_NOTREADY
#define FDC_ST2_WRTPROT FDC_ST1_WRTPROT
#define FDC_ST2_TYPEWFLT 0x20
#define FDC_ST2_RECNOTFND 0x10
#define FDC_ST2_CRCERROR FDC_ST1_CRCERROR
#define FDC_ST2_LOSTDATA 0x04
#define FDC_ST2_DRQ 0x02
#define FDC_ST2_BUSY FDC_ST1_BUSY
/* DMA ctrl reg */
#define DMA_CTRL_AECE 0x40
#define DMA_CTRL_HBUS 0x20
#define DMA_CTRL_IOM 0x10
#define DMA_CTRL_TCIE 0x08
#define DMA_CTRL_TOIE 0x04
#define DMA_CTRL_DIE 0x02
#define DMA_CTRL_RUN 0x01
/* DMA status reg */
#define DMA_ST_BUSY 0x80
#define DMA_ST_AECE DMA_CTRL_AECE
#define DMA_ST_HBUS DMA_CTRL_HBUS
#define DMA_ST_IOM DMA_CTRL_IOM
#define DMA_ST_TCZI 0x08
#define DMA_ST_TOI 0x04
#define DMA_ST_DINT 0x02
#define DMA_ST_BOW 0x01
/* FDC unit flags */
#define UNIT_V_FDC_WLK (UNIT_V_UF + 0) /* write locked */
#define UNIT_FDC_WLK (1 << UNIT_V_FDC_WLK)
#define UNIT_V_FDC_VERBOSE (UNIT_V_UF + 1) /* verbose mode, i.e. show error messages */
#define UNIT_FDC_VERBOSE (1 << UNIT_V_FDC_VERBOSE)
/* FDC timing */
#define FDC_WAIT_STEP 3000
#define FDC_WAIT_READ 8000
#define FDC_WAIT_READNEXT 800
#define FDC_WAIT_WRITE 8000
#define FDC_WAIT_WRITENEXT 800
#define FDC_WAIT_FORCEINT 100
#define FDC_WAIT_IDXPULSE 16000
uint8 reg_fdc_cmd; /* FC30 write */
uint8 reg_fdc_status; /* FC30 read */
int8 reg_fdc_track; /* FC31 */
int8 reg_fdc_sector; /* FC32 */
int8 reg_fdc_data; /* FC33 */
uint8 reg_fdc_drvsel; /* only combined */
uint8 reg_dma_ctrl; /* FC38 writeonly */
uint8 reg_dma_status; /* FC39 */
uint8 reg_dma_cntl; /* FC3A */
uint8 reg_dma_cnth; /* FC3B */
uint8 reg_dma_addrl; /* FC3C */
uint8 reg_dma_addrh; /* FC3D */
uint8 reg_dma_addre; /* FC3E */
uint8 reg_dma_id; /* FC3F - unusable */
uint16 _reg_dma_cnt; /* combined reg */
uint32 _reg_dma_addr; /* combined reg */
int8 fdc_selected; /* currently selected drive, -1=none */
uint8 fdc_intpending; /* currently executing force interrupt command, 0=none */
uint8 fdc_recbuf[1024];
uint32 fdc_recsize;
t_bool dma_isautoload;
/* externals */
extern UNIT cpu_unit;
/* forwards */
t_stat fdc_svc (UNIT *uptr);
t_stat fdc_reset (DEVICE *uptr);
t_stat fdc_attach(UNIT *uptr, CONST char *cptr);
t_stat fdc_detach(UNIT *uptr);
t_stat pdq3_diskCreate(FILE *fileref, const char *ctlr_comment);
t_stat pdq3_diskFormat(DISK_INFO *myDisk);
static void dma_reqinterrupt();
/* data structures */
typedef struct _drvdata {
UNIT *dr_unit;
DISK_INFO *dr_imd;
uint8 dr_ready;
uint8 dr_head;
uint8 dr_trk;
uint8 dr_sec;
uint8 dr_stepdir; /* 0=in, 1=out */
} DRVDATA;
DRVDATA fdc_drv[] = {
{ NULL, NULL, 0, },
{ NULL, NULL, 0, },
{ NULL, NULL, 0, },
{ NULL, NULL, 0, }
};
/* FDC data structures
fdc_dev FDC device descriptor
fdc_unit FDC unit descriptor
fdc_mod FDC modifier list
fdc_reg FDC register list
*/
IOINFO fdc_ioinfo = { NULL, FDC_IOBASE, 16, FDC_VEC, 2, fdc_read, fdc_write };
IOINFO fdc_ctxt = { &fdc_ioinfo };
UNIT fdc_unit[] = {
{ UDATA (&fdc_svc, UNIT_ATTABLE|UNIT_FIX|UNIT_BINK|UNIT_ROABLE|UNIT_DISABLE, 0), 0, },
{ UDATA (&fdc_svc, UNIT_ATTABLE|UNIT_FIX|UNIT_BINK|UNIT_ROABLE|UNIT_DISABLE, 0), 1, },
};
REG fdc_reg[] = {
{ HRDATA (FCMD, reg_fdc_cmd, 8) },
{ HRDATA (FSTAT, reg_fdc_status, 8) },
{ HRDATA (FTRK, reg_fdc_track, 8) },
{ HRDATA (FSEC, reg_fdc_sector, 8) },
{ HRDATA (FDATA, reg_fdc_data, 8) },
{ HRDATA (FSEL, reg_fdc_drvsel, 8) },
{ HRDATA (DCMD, reg_dma_ctrl, 8) },
{ HRDATA (DSTAT, reg_dma_status, 8) },
{ HRDATA (DCNTH, reg_dma_cnth, 8) },
{ HRDATA (DCNTL, reg_dma_cntl, 8) },
{ HRDATA (_DCNT, _reg_dma_cnt, 16), REG_RO|REG_HIDDEN },
{ HRDATA (DADDRE, reg_dma_addre, 8) },
{ HRDATA (DADDRH, reg_dma_addrh, 8) },
{ HRDATA (DADDRL, reg_dma_addrl, 8) },
{ HRDATA (_DADDR, _reg_dma_addr, 18), REG_RO|REG_HIDDEN },
{ NULL }
};
MTAB fdc_mod[] = {
{ MTAB_XTD|MTAB_VDV, 0, "IOBASE", "IOBASE", NULL, &show_iobase },
{ MTAB_XTD|MTAB_VDV, 0, "VECTOR", "VECTOR", NULL, &show_iovec },
{ MTAB_XTD|MTAB_VDV, 0, "PRIO", "PRIO", NULL, &show_ioprio },
{ UNIT_FDC_WLK, 0, "WRTENB", "WRTENB", NULL },
{ UNIT_FDC_WLK, UNIT_FDC_WLK, "WRTLCK", "WRTLCK", NULL },
{ 0 }
};
DEBTAB fdc_dflags[] = {
{ "CMD", DBG_FD_CMD },
{ "READ", DBG_FD_READ },
{ "WRITE", DBG_FD_WRITE },
{ "SVC", DBG_FD_SVC },
{ "IMD", DBG_FD_IMD },
{ "IMD2", DBG_FD_IMD2 }, /* deep inspection */
{ "DMA", DBG_FD_DMA },
{ "DMA2", DBG_FD_DMA2 }, /* deep inspection */
{ 0, 0 }
};
DEVICE fdc_dev = {
"FDC", /*name*/
fdc_unit, /*units*/
fdc_reg, /*registers*/
fdc_mod, /*modifiers*/
2, /*numunits*/
16, /*aradix*/
16, /*awidth*/
1, /*aincr*/
8, /*dradix*/
8, /*dwidth*/
NULL, /*examine*/
NULL, /*deposit*/
&fdc_reset, /*reset*/
NULL, /*boot. Note this is hidden, use BOOT CPU. */
&fdc_attach,/*attach*/
&fdc_detach,/*detach*/
&fdc_ctxt, /*ctxt*/
DEV_DEBUG, /*flags*/
0, /*dctrl*/
fdc_dflags, /*debflags*/
NULL, /*msize*/
NULL /*lname*/
};
/* boot unit - not available through BOOT FDC cmd, use BOOT CPU instead */
t_stat fdc_boot(int32 unitnum, DEVICE *dptr) {
if (unitnum < 0 || (uint32)unitnum > dptr->numunits)
return SCPE_NXUN;
// sim_printf("BOOT FDC%d\n",unitnum);
return fdc_autoload(unitnum);
}
t_stat fdc_attach(UNIT *uptr, CONST char *cptr) {
t_stat rc;
int i = uptr->u_unitno;
char header[4];
sim_debug(DBG_FD_IMD, &fdc_dev, DBG_PCFORMAT1 "Attach FDC drive %d\n", DBG_PC, i);
sim_cancel(uptr);
if ((rc=attach_unit(uptr,cptr)) != SCPE_OK) return rc;
fdc_drv[i].dr_unit = uptr;
uptr->capac = sim_fsize(uptr->fileref);
fdc_drv[i].dr_ready = 0;
if (uptr->capac > 0) {
fgets(header, 4, uptr->fileref);
if (strncmp(header, "IMD", 3) != 0) {
sim_printf("FDC: Only IMD disk images are supported\n");
fdc_drv[i].dr_unit = NULL;
return SCPE_OPENERR;
}
} else {
/* create a disk image file in IMD format. */
if (pdq3_diskCreate(uptr->fileref, "SIMH pdq3_fdc created") != SCPE_OK) {
sim_printf("FDC: Failed to create IMD disk.\n");
fdc_drv[i].dr_unit = NULL;
return SCPE_OPENERR;
}
uptr->capac = sim_fsize(uptr->fileref);
}
sim_debug(DBG_FD_IMD, &fdc_dev, DBG_PCFORMAT2 "Attached to '%s', type=IMD, len=%d\n",
DBG_PC, cptr, uptr->capac);
fdc_drv[i].dr_imd = diskOpenEx(uptr->fileref, isbitset(uptr->flags,UNIT_FDC_VERBOSE), &fdc_dev, DBG_FD_IMD, DBG_FD_IMD2);
if (fdc_drv[i].dr_imd == NULL) {
sim_printf("FDC: IMD disk corrupt.\n");
fdc_drv[i].dr_unit = NULL;
return SCPE_OPENERR;
}
fdc_drv[i].dr_ready = 1;
/* handle force interrupt to wait for disk change */
if (isbitset(fdc_intpending,0x01)) {
dma_reqinterrupt();
clrbit(reg_fdc_status,FDC_ST1_BUSY);
clrbit(fdc_intpending,0x01);
}
return SCPE_OK;
}
t_stat fdc_detach(UNIT *uptr) {
t_stat rc;
int i = uptr->u_unitno;
sim_debug(DBG_FD_IMD, &fdc_dev, DBG_PCFORMAT1 "Detach FDC drive %d\n", DBG_PC, i);
sim_cancel(uptr);
rc = diskClose(&fdc_drv[i].dr_imd);
fdc_drv[i].dr_ready = 0;
/* handle force interrupt to wait for disk change */
if (isbitset(fdc_intpending,0x02)) {
cpu_raiseInt(INT_DMAFD);
clrbit(reg_fdc_status,FDC_ST1_BUSY);
clrbit(fdc_intpending,0x02);
}
if (rc != SCPE_OK) return rc;
return detach_unit(uptr); /* detach unit */
}
static t_stat fdc_start(UNIT *uptr,int time) {
/* request service */
sim_debug(DBG_FD_SVC, &fdc_dev, DBG_PCFORMAT2 "Start Service after %d ticks\n", DBG_PC, time);
return sim_activate(uptr, time);
}
static t_stat fdc_stop(UNIT *uptr) {
/* request service */
sim_debug(DBG_FD_SVC, &fdc_dev, DBG_PCFORMAT2 "Cancel Service\n", DBG_PC);
return sim_cancel(uptr);
}
static void fdc_update_rdonly(DRVDATA *curdrv) {
/* read only drive? */
if (isbitset(curdrv->dr_unit->flags,UNIT_RO))
setbit(reg_fdc_status,FDC_ST1_WRTPROT);
else
clrbit(reg_fdc_status,FDC_ST1_WRTPROT);
}
t_bool fdc_driveready(DRVDATA *curdrv) {
/* some drive selected, and disk in drive? */
if (curdrv==NULL || curdrv->dr_ready == 0) {
setbit(reg_fdc_status,FDC_ST1_NOTREADY);
clrbit(reg_fdc_status,FDC_ST1_BUSY);
reg_fdc_cmd = FDC_IDLECMD;
return FALSE;
}
/* drive is ready */
clrbit(reg_fdc_status,FDC_ST1_NOTREADY);
/* read only drive? */
fdc_update_rdonly(curdrv);
return TRUE;
}
static t_bool fdc_istrk0(DRVDATA *curdrv,int8 trk) {
curdrv->dr_trk = trk;
if (trk <= 0) {
setbit(reg_fdc_status,FDC_ST1_TRACK0);
reg_fdc_track = 0;
return TRUE;
}
return FALSE;
}
/* return true if invalid track (CRC error) */
static t_bool fdc_stepin(DRVDATA *curdrv, t_bool upd) {
curdrv->dr_stepdir = FDC_STEPIN;
curdrv->dr_trk++;
if (upd) reg_fdc_track = curdrv->dr_trk;
if (curdrv->dr_trk > FDC_MAX_TRACKS) {
setbit(reg_fdc_status,FDC_ST1_CRCERROR);
return TRUE;
}
return FALSE;
}
/* return true if trk0 reached */
static t_bool fdc_stepout(DRVDATA *curdrv, t_bool upd) {
curdrv->dr_stepdir = FDC_STEPOUT;
curdrv->dr_trk--;
if (upd) reg_fdc_track = curdrv->dr_trk;
return fdc_istrk0(curdrv, reg_fdc_track);
}
static void fdc_clr_st1_error() {
clrbit(reg_fdc_status,FDC_ST1_NOTREADY|FDC_ST1_SEEKERROR|FDC_ST1_CRCERROR);
}
static void dma_interrupt(int bit) {
if (isbitset(reg_dma_ctrl,bit)) {
sim_debug(DBG_FD_DMA, & fdc_dev, DBG_PCFORMAT2 "Raise DMA/FDC interrupt\n", DBG_PC);
cpu_raiseInt(INT_DMAFD);
}
}
static t_bool dma_abort(t_bool fromfinish) {
clrbit(reg_dma_status,DMA_ST_BUSY);
clrbit(reg_dma_ctrl,DMA_CTRL_RUN);
/* if autoload was finished, finally start the CPU.
* note: autoload will read the first track, and then fail at end of track with an error */
if (dma_isautoload) {
sim_debug(DBG_FD_DMA, & fdc_dev, DBG_PCFORMAT2 "AUTOLOAD finished by end-of-track (DMA aborted)\n", DBG_PC);
cpu_finishAutoload();
dma_isautoload = FALSE;
} else if (!fromfinish) {
sim_debug(DBG_FD_DMA, & fdc_dev, DBG_PCFORMAT2 "Aborted transfer\n", DBG_PC);
}
return FALSE;
}
/* all data transferred */
static void dma_finish() {
setbit(reg_dma_status,DMA_ST_TCZI);
dma_abort(TRUE);
dma_interrupt(DMA_CTRL_TCIE);
sim_debug(DBG_FD_DMA, & fdc_dev, DBG_PCFORMAT2 "Finished transfer\n", DBG_PC);
}
/* request interrupt from FDC */
static void dma_reqinterrupt() {
setbit(reg_dma_status,DMA_ST_DINT);
dma_interrupt(DMA_CTRL_DIE);
}
static void dma_fix_regs() {
reg_dma_cntl = _reg_dma_cnt & 0xff;
reg_dma_cnth = _reg_dma_cnt>>8;
reg_dma_addre = (_reg_dma_addr>>16) & 0x03;
reg_dma_addrh = (_reg_dma_addr>>8) & 0xff;
reg_dma_addrl = _reg_dma_addr & 0xff;
}
/* return true if successfully transferred */
static t_bool dma_transfer_to_ram(uint8 *buf, int bufsize) {
t_bool rc = TRUE;
int i;
uint16 data;
t_addr tstart = _reg_dma_addr/2;
int cnt = _reg_dma_cnt ^ 0xffff;
int xfersz = bufsize > cnt ? cnt : bufsize;
sim_debug(DBG_FD_DMA, &fdc_dev, DBG_PCFORMAT2 "Transfer to RAM $%x...$%x\n",
DBG_PC, _reg_dma_addr/2,(_reg_dma_addr + xfersz - 1)/2);
for (i=0; i<xfersz; i += 16) {
sim_debug(DBG_FD_DMA2, &fdc_dev, DBG_PCFORMAT1 "$%04x: %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x\n",
DBG_PC, tstart, buf[i+0],buf[i+1], buf[i+2],buf[i+3], buf[i+4],buf[i+5], buf[i+6],buf[i+7],
buf[i+8],buf[i+9], buf[i+10],buf[i+11], buf[i+12],buf[i+13], buf[i+14],buf[i+15]);
tstart += 8;
}
if (isbitclr(reg_dma_ctrl,DMA_CTRL_IOM))
sim_printf("Warning: wrong IOM direction for DMA transfer to RAM\n");
for (i=0; i<bufsize; i++) {
data = buf[i];
// sim_printf("addr=%04x data=%02x\n",_reg_dma_addr, data);
if (WriteB(0, _reg_dma_addr++, data, FALSE) != SCPE_OK) {
(void)dma_abort(FALSE);
setbit(reg_dma_status,DMA_ST_TOI);
dma_interrupt(DMA_CTRL_TOIE);
return FALSE; /* write fault */
}
_reg_dma_cnt++;
if (_reg_dma_cnt == 0) /* all data done? */
break;
}
if (_reg_dma_cnt == 0) { /* all data done? */
dma_finish();
rc = FALSE;
}
dma_fix_regs();
return rc;
}
/* return true if successfully transferred */
static t_bool dma_transfer_from_ram(uint8 *buf, int bufsize) {
t_bool rc = TRUE;
int i;
uint16 data;
uint32 tstart = _reg_dma_addr/2;
int cnt = _reg_dma_cnt ^ 0xffff;
int xfersz = bufsize > cnt ? cnt : bufsize;
sim_debug(DBG_FD_DMA, &fdc_dev, DBG_PCFORMAT2 "Transfer from RAM $%x...$%x\n",
DBG_PC, _reg_dma_addr/2, (_reg_dma_addr + xfersz - 1)/2);
if (isbitset(reg_dma_ctrl,DMA_CTRL_IOM))
sim_printf("Warning: wrong IOM direction for DMA transfer from RAM\n");
for (i=0; i<bufsize; i++) {
if (ReadB(0, _reg_dma_addr++, &data, FALSE)) {
(void)dma_abort(FALSE);
setbit(reg_dma_status,DMA_ST_TOI);
dma_interrupt(DMA_CTRL_TOIE);
return FALSE; /* write fault */
}
buf[i] = data & 0xff;
_reg_dma_cnt++;
if (_reg_dma_cnt == 0) /* all data done? */
break;
}
for (i=0; i<xfersz; i += 16) {
sim_debug(DBG_FD_DMA2, &fdc_dev, DBG_PCFORMAT1 "$%04x: %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x\n",
DBG_PC, tstart, buf[i+0],buf[i+1], buf[i+2],buf[i+3], buf[i+4],buf[i+5], buf[i+6],buf[i+7],
buf[i+8],buf[i+9], buf[i+10],buf[i+11], buf[i+12],buf[i+13], buf[i+14],buf[i+15]);
tstart += 8;
}
if (_reg_dma_cnt == 0) { /* all data done? */
dma_finish();
rc = FALSE;
}
dma_fix_regs();
return rc;
}
/* return true if read satisfied, false if error */
static t_bool fdc_readsec(DRVDATA *curdrv) {
uint32 flags;
/* does sector exist? */
if (sectSeek(curdrv->dr_imd, curdrv->dr_trk, curdrv->dr_head)) {
setbit(reg_fdc_status,FDC_ST2_RECNOTFND);
return FALSE;
}
/* get sector size available */
fdc_recsize = curdrv->dr_imd->track[curdrv->dr_trk][curdrv->dr_head].sectsize;
/* clear errors. Also clear LOSTDATA bit due to aliasing to TRACK00 bit from previous seek operation */
clrbit(reg_fdc_status,FDC_ST2_NOTREADY|FDC_ST2_LOSTDATA|FDC_ST2_WRTPROT);
if (sectRead(curdrv->dr_imd, curdrv->dr_trk, curdrv->dr_head, curdrv->dr_sec,
fdc_recbuf, fdc_recsize, &flags, &fdc_recsize)) {
setbit(reg_fdc_status,FDC_ST2_RECNOTFND);
return FALSE;
}
if (isbitset(flags,IMD_DISK_IO_ERROR_CRC)) {
setbit(reg_fdc_status,FDC_ST2_CRCERROR);
return FALSE; /* terminate read */
}
if (isbitset(flags,IMD_DISK_IO_DELETED_ADDR_MARK))
setbit(reg_fdc_status,FDC_ST2_TYPEWFLT);
/* trigger DMA transfer */
if (!dma_transfer_to_ram(fdc_recbuf, fdc_recsize))
return FALSE;
/* now finished */
return TRUE;
}
static t_bool fdc_writesec(DRVDATA *curdrv) {
uint32 flags;
/* write protect? */
if (imdIsWriteLocked(curdrv->dr_imd)) {
dma_abort(FALSE);
setbit(reg_fdc_status,FDC_ST2_WRTPROT);
return FALSE;
}
/* is sector available? */
if (sectSeek(curdrv->dr_imd, curdrv->dr_trk, curdrv->dr_head)) {
setbit(reg_fdc_status,FDC_ST2_RECNOTFND);
return FALSE;
}
/* clear errors. Also clear LOSTDATA bit due to aliasing to TRACK00 bit from previous seek operation */
clrbit(reg_fdc_status,FDC_ST2_NOTREADY|FDC_ST2_LOSTDATA|FDC_ST2_WRTPROT);
/* get sector size */
fdc_recsize = curdrv->dr_imd->track[curdrv->dr_trk][curdrv->dr_head].sectsize;
/* get from ram into write buffer */
if (!dma_transfer_from_ram(fdc_recbuf, fdc_recsize))
return FALSE;
if (sectWrite(curdrv->dr_imd, curdrv->dr_trk, curdrv->dr_head, curdrv->dr_sec,
fdc_recbuf, fdc_recsize, &flags, &fdc_recsize)) {
setbit(reg_fdc_status,FDC_ST2_RECNOTFND);
return FALSE;
}
if (isbitset(flags,IMD_DISK_IO_ERROR_GENERAL)) {
setbit(reg_fdc_status,FDC_ST2_TYPEWFLT);
return FALSE;
}
if (isbitset(flags,IMD_DISK_IO_ERROR_WPROT)) {
setbit(reg_fdc_status,FDC_ST2_WRTPROT);
return FALSE;
}
/* advance to next sector for multiwrite */
if (isbitset(reg_fdc_cmd,FDC_BIT_MULTI)) { /* multi bit */
curdrv->dr_sec++;
reg_fdc_sector++;
}
/* now finished */
return TRUE;
}
static t_bool fdc_rwerror() {
/* note: LOSTDATA cannot occur */
return isbitset(reg_fdc_status,FDC_ST2_TYPEWFLT|FDC_ST2_RECNOTFND|FDC_ST2_CRCERROR /*|FDC_ST2_LOSTDATA*/);
}
static t_stat fdc_set_notready(uint8 cmd)
{
switch (cmd & FDC_CMDMASK) {
default:
case FDC_RESTORE:
case FDC_SEEK:
case FDC_STEP: /* single step in current direction, no update */
case FDC_STEPIN: /* single step towards center */
case FDC_STEPOUT: /* single step towards edge of disk */
case FDC_STEP_U:
case FDC_STEPIN_U:
case FDC_STEPOUT_U:
setbit(reg_fdc_status,FDC_ST1_SEEKERROR);
break;
case FDC_READSEC_M:
case FDC_READSEC: /* type II: read a sector via DMA */
setbit(reg_fdc_status,FDC_ST2_CRCERROR);
break;
case FDC_WRITESEC_M:
case FDC_WRITESEC: /* type II: read a sector via DMA */
setbit(reg_fdc_status,FDC_ST2_TYPEWFLT);
break;
}
return SCPE_OK;
}
static t_stat fdc_restartmulti(DRVDATA *curdrv,int wait) {
/* advance to next sector for multiread */
sim_debug(DBG_FD_SVC, &fdc_dev, " Restarting FDC_SVC for multiple R/W\n");
curdrv->dr_sec++;
reg_fdc_sector++;
/* restart service for multi-sector */
return fdc_start(curdrv->dr_unit, wait);
}
/* process the FDC commands, and restart, if necessary */
t_stat fdc_svc(UNIT *uptr) {
DRVDATA *curdrv = fdc_selected==-1 ? NULL : &fdc_drv[fdc_selected];
t_bool rdy = fdc_driveready(curdrv);
t_bool um_flg; /* update or multi bit */
sim_debug(DBG_FD_SVC,&fdc_dev, DBG_PCFORMAT2 "Calling FDC_SVC for unit=%x cmd=%x\n",
DBG_PC, fdc_selected, reg_fdc_cmd);
if (reg_fdc_cmd == FDC_IDLECMD) return SCPE_OK;
if (!rdy) return fdc_set_notready(reg_fdc_cmd & FDC_CMDMASK);
um_flg = isbitset(reg_fdc_cmd,FDC_BIT_UPDATE);
switch (reg_fdc_cmd & FDC_CMDMASK) {
case FDC_RESTORE:
fdc_istrk0(curdrv,0);
curdrv->dr_stepdir = FDC_STEPOUT;
break;
case FDC_SEEK:
if (reg_fdc_track > reg_fdc_data) {
if (fdc_stepout(curdrv, TRUE)) break;
return fdc_start(curdrv->dr_unit,FDC_WAIT_STEP);
} else if (reg_fdc_track < reg_fdc_data) {
if (fdc_stepin(curdrv, TRUE)) break;
return fdc_start(curdrv->dr_unit,FDC_WAIT_STEP);
}
/* found position */
fdc_clr_st1_error();
break;
case FDC_STEP: /* single step in current direction, no update */
case FDC_STEP_U:
if (curdrv->dr_stepdir == FDC_STEPIN) {
if (fdc_stepin(curdrv, um_flg)) break;
} else
fdc_stepout(curdrv, um_flg);
fdc_clr_st1_error();
break;
case FDC_STEPIN: /* single step towards center */
case FDC_STEPIN_U:
if (fdc_stepin(curdrv, um_flg)) break;
fdc_clr_st1_error();
break;
case FDC_STEPOUT: /* single step towards edge of disk */
case FDC_STEPOUT_U:
if (fdc_stepin(curdrv, um_flg)) break;
fdc_clr_st1_error();
break;
case FDC_READSEC_M:
case FDC_READSEC: /* type II: read a sector via DMA */
if (!fdc_readsec(curdrv) || fdc_rwerror()) {
dma_abort(TRUE);
break;
}
if (isbitset(reg_dma_status,DMA_ST_BUSY) && um_flg)
return fdc_restartmulti(curdrv,FDC_WAIT_READNEXT);
break;
case FDC_WRITESEC_M:
case FDC_WRITESEC: /* type II: read a sector via DMA */
if (!fdc_writesec(curdrv) || fdc_rwerror()) {
dma_abort(TRUE);
break;
}
if (isbitset(reg_dma_status,DMA_ST_BUSY) && um_flg)
return fdc_restartmulti(curdrv,FDC_WAIT_WRITENEXT);
break;
default:
sim_printf("fdc_svc: Fix me - command not yet implemented: cmd=0x%x\n", reg_fdc_cmd);
}
clrbit(reg_fdc_status,FDC_ST1_BUSY);
reg_fdc_cmd = FDC_IDLECMD;
return SCPE_OK;
}
t_stat fdc_binit() {
fdc_selected = -1;
fdc_intpending = 0;
/* reset FDC registers */
reg_fdc_cmd = FDC_IDLECMD; /* invalid command, used as idle marker */
reg_fdc_status = 0;
reg_fdc_track = 0;
reg_fdc_sector = 1;
reg_fdc_data = 1;
reg_fdc_drvsel = 0;
/* reset DMA registers */
reg_dma_ctrl = DMA_CTRL_AECE | DMA_CTRL_HBUS | DMA_CTRL_IOM;
reg_dma_status = DMA_ST_AECE | DMA_ST_HBUS | DMA_ST_IOM;
_reg_dma_cnt = 0x0001;
/* hack: initialize boot code to load ad 0x2000(word address).
* However, DMA is based on byte addresses, so multiply with 2 */
_reg_dma_addr = reg_dmabase*2;
reg_dma_id = 0;
dma_fix_regs();
return SCPE_OK;
}
t_stat fdc_reset (DEVICE *dptr) {
int i;
DEVCTXT* ctxt = (DEVCTXT*)dptr->ctxt;
// sim_printf("RESET FDC\n");
if (dptr->flags & DEV_DIS)
del_ioh(ctxt->ioi);
else
add_ioh(ctxt->ioi);
/* allow for 2 drives */
for (i=0; i<2; i++) {
DRVDATA *cur = &fdc_drv[i];
cur->dr_unit = &fdc_unit[i];
cur->dr_trk = 0;
cur->dr_sec = 1;
cur->dr_head = 0;
cur->dr_stepdir = 0;
}
return fdc_binit();
}
/* select drive, according to select register */
static DRVDATA *fdc_select() {
DRVDATA *curdrv = NULL;
if (isbitset(reg_fdc_drvsel,FDC_SEL_UNIT0)) fdc_selected = 0;
else if (isbitset(reg_fdc_drvsel,FDC_SEL_UNIT1)) fdc_selected = 1;
else if (isbitset(reg_fdc_drvsel,FDC_SEL_UNIT2)) fdc_selected = 0;
else if (isbitset(reg_fdc_drvsel,FDC_SEL_UNIT3)) fdc_selected = 1;
else fdc_selected = -1;
if (fdc_selected >= 0) {
curdrv = &fdc_drv[fdc_selected];
fdc_update_rdonly(curdrv); /* update R/O flag */
curdrv->dr_head = isbitset(reg_fdc_drvsel,FDC_SEL_SIDE) ? 1 : 0;
curdrv->dr_unit = &fdc_unit[fdc_selected];
}
return curdrv;
}
static const char *cmdlist[] = {
"Restore","Seek","Step","Step+Upd","StepIn","StepIn+Upd",
"StepOut","StepOut+Upd","Read","Read+Multi","Write","WriteMulti",
"ReadAddr","ForceInt","ReadTrack","WriteTrack"
};
static void debug_fdccmd(uint16 cmd) {
char buf[200];
uint16 dsel = cmd >> 8, cr = (cmd >> 4) & 0x0f;
buf[0] = 0;
if (cmd & 0xff00) {
strcat(buf,"DSR=[");
strcat(buf,dsel & FDC_SEL_SIDE ? "SIDE1" : "SIDE0");
if (dsel & FDC_SEL_SDEN) strcat(buf,",SDEN");
strcat(buf,",UNIT");
if (dsel & FDC_SEL_UNIT3) strcat(buf,"3");
else if (dsel & FDC_SEL_UNIT2) strcat(buf,"2");
else if (dsel & FDC_SEL_UNIT1) strcat(buf,"1");
else if (dsel & FDC_SEL_UNIT0) strcat(buf,"0");
strcat(buf,"] ");
}
strcat(buf,"CR=[");
strcat(buf,cmdlist[cr]);
if (cr < 8) {
if (cmd & FDC_BIT_HEADLOAD) strcat(buf,"+Load");
if (cmd & FDC_BIT_VERIFY) strcat(buf,"+Vrfy");
cmd &= FDC_BIT_STEP15;
if (cmd == FDC_BIT_STEP3) strcat(buf,"+Step3");
else if (cmd == FDC_BIT_STEP6) strcat(buf,"+Step6");
else if (cmd == FDC_BIT_STEP10) strcat(buf,"+Step10");
else if (cmd == FDC_BIT_STEP15) strcat(buf,"+Step15");
} else
switch (cr) {
case 8: case 9:
case 0xa: case 0xb:
strcat(buf, cmd & FDC_BIT_SIDESEL ? "+SideSel1" : "+SideSel0");
strcat(buf, cmd & FDC_BIT_SIDECMP ? "+SideCmp1" : "+SideCmp0");
if (cr > 9)
strcat(buf, cmd & FDC_BIT_DATAMARK ? "+DelMark" : "+DataMark");
default:
break;
case 0x0f:
if (cmd & FDC_BIT_INTIMM) strcat(buf,"+IMM");
if (cmd & FDC_BIT_INTIDX) strcat(buf,"+IDX");
if (cmd & FDC_BIT_INTN2R) strcat(buf,"+N2R");
if (cmd & FDC_BIT_INTR2N) strcat(buf,"+R2N");
}
strcat(buf,"]");
sim_debug(DBG_FD_CMD, &fdc_dev, DBG_PCFORMAT2 "Command: %s\n", DBG_PC,buf);
}
static t_stat fdc_docmd(uint16 data) {
UNIT *uptr;
DRVDATA *curdrv = fdc_select();
if (curdrv== NULL) return SCPE_IOERR;
debug_fdccmd(data);
uptr = curdrv->dr_unit;
if (!fdc_driveready(curdrv)) {
sim_debug(DBG_FD_CMD,&fdc_dev, DBG_PCFORMAT2 "fdc_docmd: drive not ready\n", DBG_PC);
return SCPE_OK;
}
reg_fdc_cmd = data & 0xff;
switch (data & FDC_CMDMASK) {
/* type I commands */
case FDC_RESTORE:
case FDC_SEEK:
case FDC_STEP:
case FDC_STEP_U:
case FDC_STEPIN:
case FDC_STEPIN_U:
case FDC_STEPOUT:
case FDC_STEPOUT_U:
setbit(reg_fdc_status, FDC_ST1_BUSY);
return fdc_start(uptr,FDC_WAIT_STEP);
/* type II commands */
case FDC_READSEC:
case FDC_READSEC_M:
curdrv->dr_sec = reg_fdc_sector; /* sector to start */
setbit(reg_fdc_status, FDC_ST2_BUSY);
return fdc_start(uptr,FDC_WAIT_READ);
case FDC_WRITESEC:
case FDC_WRITESEC_M:
curdrv->dr_sec = reg_fdc_sector; /* sector to start */
setbit(reg_fdc_status, FDC_ST2_BUSY);
return fdc_start(uptr,FDC_WAIT_WRITE);
/* type III commands */
default:
sim_printf("fdc_docmd: Fix me - command not yet implemented: cmd=0x%x\n", reg_fdc_cmd);
setbit(reg_fdc_status, FDC_ST2_BUSY);
return SCPE_NOFNC;
/* type IV command */
case FDC_FORCEINT:
if (isbitset(data,0x01)) { /* int on transition from not-ready to ready */
fdc_stop(uptr);
} else if (isbitset(data,0x06)) { /* int on transition from ready to not-ready, or vice versa */
/* handle in fdc_detach */
fdc_intpending |= reg_fdc_cmd;
return SCPE_OK;
} else if (isbitset(data,0x08)) { /* immediate int */
dma_reqinterrupt();
return SCPE_OK; /* don't reset BUSY */
} else { /* terminate */
fdc_stop(uptr);
/* successful cmd clears errors */
clrbit(reg_fdc_status,FDC_ST2_TYPEWFLT|FDC_ST2_RECNOTFND|FDC_ST2_CRCERROR|FDC_ST2_LOSTDATA);
}
/* reset busy bit */
clrbit(reg_fdc_status,FDC_ST1_BUSY);
}
return SCPE_OK;
}
void dma_docmd(uint16 data) {
reg_dma_ctrl = data & 0xff;
reg_dma_status &= 0x8f;
reg_dma_status |= (reg_dma_ctrl & 0x70);
if (isbitset(reg_dma_ctrl,DMA_CTRL_RUN))
setbit(reg_dma_status,DMA_ST_BUSY);
}
/* setup FDC/DMA to read first track into low memory */
t_stat fdc_autoload(int unitnum) {
int unitbit = 1 << unitnum;
sim_debug(DBG_FD_CMD, &fdc_dev, DBG_PCFORMAT2 "Autoload Unit=%d\n", DBG_PC, unitnum);
dma_isautoload = TRUE;
/* note: this is partly in microcode/ROM. The DMA cntrlr itself does not set the
* FDC register for multi_read */
fdc_reset(&fdc_dev);
dma_docmd(DMA_CTRL_RUN|DMA_CTRL_DIE|DMA_CTRL_TCIE|DMA_CTRL_IOM|DMA_CTRL_HBUS|DMA_CTRL_AECE);
reg_fdc_drvsel = FDC_SEL_SDEN | unitbit;
return fdc_docmd(FDC_READSEC_M);
}
static t_bool fd_reg16bit[] = {
FALSE,FALSE,FALSE,FALSE,
TRUE, TRUE, TRUE, TRUE,
FALSE,FALSE,FALSE,FALSE,
FALSE,FALSE,FALSE,FALSE
};
t_stat fdc_write(t_addr ioaddr, uint16 data) {
int io = ioaddr & 15;
sim_debug(DBG_FD_WRITE, &fdc_dev, DBG_PCFORMAT0 "%s write %04x to IO=$%04x\n",
DBG_PC, fd_reg16bit[io] ? "Byte":"Word", data, ioaddr);
switch (io) {
case 4: /* cmd + drvsel */
reg_fdc_drvsel = (data >> 8) & 0xff;
fdc_docmd(data);
break;
case 0: /* cmd writeonly */
fdc_docmd(data);
break;
case 5: /* track + drvsel */
reg_fdc_drvsel = (data >> 8) & 0xff;
reg_fdc_track = data & 0xff;
break;
case 1: /* track */
reg_fdc_track = data & 0xff;
break;
case 6: /* sector + drvsel */
reg_fdc_drvsel = (data >> 8) & 0xff;
reg_fdc_sector = data & 0xff;
break;
case 2: /* sector */
reg_fdc_sector = data & 0xff;
break;
case 7: /* data + drvsel */
reg_fdc_drvsel = (data >> 8) & 0xff;
reg_fdc_data = data & 0xff;
break;
case 3: /* data */
reg_fdc_data = data & 0xff;
break;
case 8: /* dma ctrl */
dma_docmd(data);
break;
case 9: /* dma status */
if (isbitset(reg_dma_status,DMA_ST_BUSY))
sim_printf("Warning: DMA: write status while BUSY\n");
reg_dma_status = data & 0x8f;
break;
case 0x0a: /* count low */
reg_dma_cntl = data & 0xff;
break;
case 0x0b: /* count high */
reg_dma_cnth = data & 0xff;
break;
case 0x0c: /* addr low */
reg_dma_addrl = data & 0xff;
break;
case 0x0d: /* addr high */
reg_dma_addrh = data & 0xff;
break;
case 0x0e: /* addr ext */
reg_dma_addre = data & 0x03;
break;
case 0x0f: /* ID register */
reg_dma_id = data & 0xff;
break;
}
_reg_dma_cnt = (reg_dma_cnth << 8) | reg_dma_cntl;
if (_reg_dma_cnt) clrbit(reg_dma_status,DMA_ST_TCZI);
_reg_dma_addr = (((uint32)reg_dma_addre)<<16) | (((uint32)reg_dma_addrh)<<8) | reg_dma_addrl;
(void)fdc_select();
return SCPE_OK;
}
t_stat fdc_read(t_addr ioaddr, uint16 *data) {
switch (ioaddr & 15) {
case 0: /* status readonly */
case 4:
*data = reg_fdc_status;
break;
case 1: /* track */
case 5:
*data = reg_fdc_track;
break;
case 2: /* sector */
case 6:
*data = reg_fdc_sector;
break;
case 3: /* data */
case 7:
*data = reg_fdc_data;
break;
case 8: /* read nothing */
*data = 0;
break;
case 9:
*data = reg_dma_status;
break;
case 0x0a: /* byte low */
*data = reg_dma_cntl;
break;
case 0x0b:
*data = reg_dma_cnth;
break;
case 0x0c:
*data = reg_dma_addrl;
break;
case 0x0d:
*data = reg_dma_addrh;
break;
case 0x0e:
*data = reg_dma_addre;
break;
default: /* note: ID register 0xfc3f is unusable because RE is tied to VCC */
*data = reg_dma_id;
break;
}
sim_debug(DBG_FD_READ, &fdc_dev, DBG_PCFORMAT1 "Byte read %02x from IO=$%04x\n",
DBG_PC, *data, ioaddr);
return SCPE_OK;
}
/*
* Create an ImageDisk (IMD) file. This function just creates the comment header, and allows
* the user to enter a comment. After the IMD is created, it must be formatted with a format
* program on the simulated operating system, ie CP/M, CDOS, 86-DOS.
*
* If the IMD file already exists, the user will be given the option of overwriting it.
*/
t_stat pdq3_diskCreate(FILE *fileref, const char *ctlr_comment) {
DISK_INFO *myDisk = NULL;
char *comment;
char *curptr;
int answer;
int32 len, remaining;
long fsize;
if(fileref == NULL) {
return (SCPE_OPENERR);
}
if(sim_fsize(fileref) != 0) {
sim_printf("PDQ3_IMD: Disk image already has data, do you want to overwrite it? ");
answer = getchar();
if((answer != 'y') && (answer != 'Y')) {
return (SCPE_OPENERR);
}
}
if((curptr = comment = (char *)calloc(1, MAX_COMMENT_LEN)) == 0) {
sim_printf("PDQ3_IMD: Memory allocation failure.\n");
return (SCPE_MEM);
}
sim_printf("PDQ3_IMD: Enter a comment for this disk.\n"
"PDQ3_IMD: Terminate with a '.' on an otherwise blank line.\n");
remaining = MAX_COMMENT_LEN;
do {
sim_printf("IMD> ");
fgets(curptr, remaining - 3, stdin);
if (strcmp(curptr, ".\n") == 0) {
remaining = 0;
} else {
len = strlen(curptr) - 1;
if (curptr[len] != '\n')
len++;
remaining -= len;
curptr += len;
*curptr++ = 0x0d;
*curptr++ = 0x0a;
}
} while (remaining > 4);
*curptr = 0x00;
/* rewind to the beginning of the file. */
rewind(fileref);
/* Erase the contents of the IMD file in case we are overwriting an existing image. */
fsize = ftell(fileref);
sim_set_fsize(fileref, fsize<0 ? 0 : fsize);
fprintf(fileref, "IMD SIMH %s %s\n", __DATE__, __TIME__);
fputs(comment, fileref);
free(comment);
fprintf(fileref, "%s\n", ctlr_comment);
fputc(0x1A, fileref); /* EOF marker for IMD comment. */
fflush(fileref);
if((myDisk = diskOpen(fileref, 0)) == NULL) {
sim_printf("PDQ3_IMD: Error opening disk for format.\n");
return(SCPE_OPENERR);
}
if(pdq3_diskFormat(myDisk) != SCPE_OK) {
sim_printf("PDQ3_IMD: error formatting disk.\n");
}
return diskClose(&myDisk);
}
t_stat pdq3_diskFormat(DISK_INFO *myDisk) {
uint8 i = 0;
uint8 sector_map[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26};
uint32 flags;
sim_printf("PDQ3_IMD: Formatting disk in PDQ3 format.\n");
/* format first track as 26 sectors with 128 bytes */
if((trackWrite(myDisk, 0, 0, 26, 128, sector_map, IMD_MODE_500K_FM, 0xE5, &flags)) != 0) {
sim_printf("PDQ3_IMD: Error formatting track %d\n", i);
return SCPE_IOERR;
}
sim_printf(".");
/* format the remaining tracks as 26 sectors with 256 bytes */
for(i=1;i<77;i++) {
if((trackWrite(myDisk, i, 0, 26, 256, sector_map, IMD_MODE_500K_MFM, 0xE5, &flags)) != 0) {
sim_printf("PDQ3_IMD: Error formatting track %d\n", i);
return SCPE_IOERR;
} else {
putchar('.');
}
}
sim_printf("\nPDQ3_IMD: Format Complete.\n");
return SCPE_OK;
}