/* 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) {
    if (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) {
    strlcat(buf,"DSR=[",sizeof(buf));
    strlcat(buf,dsel & FDC_SEL_SIDE ? "SIDE1" : "SIDE0",sizeof(buf));
    if (dsel & FDC_SEL_SDEN) strlcat(buf,",SDEN",sizeof(buf));
    strlcat(buf,",UNIT",sizeof(buf));
    if (dsel & FDC_SEL_UNIT3) strlcat(buf,"3",sizeof(buf));
    else if (dsel & FDC_SEL_UNIT2) strlcat(buf,"2",sizeof(buf));
    else if (dsel & FDC_SEL_UNIT1) strlcat(buf,"1",sizeof(buf));
    else if (dsel & FDC_SEL_UNIT0) strlcat(buf,"0",sizeof(buf));
    strlcat(buf,"] ",sizeof(buf));
  }
  strlcat(buf,"CR=[",sizeof(buf));
  strlcat(buf,cmdlist[cr],sizeof(buf));
  if (cr < 8) {
    if (cmd & FDC_BIT_HEADLOAD) strlcat(buf,"+Load",sizeof(buf));
    if (cmd & FDC_BIT_VERIFY) strlcat(buf,"+Vrfy",sizeof(buf));
    cmd &= FDC_BIT_STEP15;
    if (cmd == FDC_BIT_STEP3) strlcat(buf,"+Step3",sizeof(buf));
    else if (cmd == FDC_BIT_STEP6) strlcat(buf,"+Step6",sizeof(buf));
    else if (cmd == FDC_BIT_STEP10) strlcat(buf,"+Step10",sizeof(buf));
    else if (cmd == FDC_BIT_STEP15) strlcat(buf,"+Step15",sizeof(buf));
  } else
    switch (cr) {
    case 8: case 9:
    case 0xa: case 0xb:
      strlcat(buf, cmd & FDC_BIT_SIDESEL ? "+SideSel1" : "+SideSel0",sizeof(buf));
      strlcat(buf, cmd & FDC_BIT_SIDECMP ? "+SideCmp1" : "+SideCmp0",sizeof(buf));
      if (cr > 9)
        strlcat(buf, cmd & FDC_BIT_DATAMARK ? "+DelMark" : "+DataMark",sizeof(buf));
    default:
      break;
    case 0x0f:
      if (cmd & FDC_BIT_INTIMM) strlcat(buf,"+IMM",sizeof(buf));
      if (cmd & FDC_BIT_INTIDX) strlcat(buf,"+IDX",sizeof(buf));
      if (cmd & FDC_BIT_INTN2R) strlcat(buf,"+N2R",sizeof(buf));
      if (cmd & FDC_BIT_INTR2N) strlcat(buf,"+R2N",sizeof(buf));
    }
  strlcat(buf,"]",sizeof(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> ");
        if (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;
}
