/*************************************************************************
 *                                                                       *
 * Copyright (c) 2007-2022 Howard M. Harte.                              *
 * https://github.com/hharte                                             *
 *                                                                       *
 * 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 NON-            *
 * INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 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 The Authors shall    *
 * not be used in advertising or otherwise to promote the sale, use or   *
 * other dealings in this Software without prior written authorization   *
 * from the Authors.                                                     *
 *                                                                       *
 * SIMH Interface based on altairz80_hdsk.c, by Peter Schorn.            *
 *                                                                       *
 * Module Description:                                                   *
 *     CompuPro DISK2 Hard Disk Controller module for SIMH.              *
 * This module must be used in conjunction with the CompuPro Selector    *
 * Channel Module for proper operation.                                  *
 *                                                                       *
 *************************************************************************/

#include "altairz80_defs.h"
#include "sim_imd.h"

/* Debug flags */
#define ERROR_MSG   (1 << 0)
#define SEEK_MSG    (1 << 1)
#define CMD_MSG     (1 << 2)
#define RD_DATA_MSG (1 << 3)
#define WR_DATA_MSG (1 << 4)
#define STATUS_MSG  (1 << 5)
#define IRQ_MSG     (1 << 6)
#define VERBOSE_MSG (1 << 7)

#define DISK2_MAX_DRIVES    4

typedef union {
    uint8 raw[2051];
    struct {
        uint8 header[3];
        uint8 data[2048];
    } u;
} SECTOR_FORMAT;

static SECTOR_FORMAT sdata;

typedef struct {
    UNIT *uptr;
    DISK_INFO *imd;
    uint16 ntracks;  /* number of tracks */
    uint8 nheads;    /* number of heads */
    uint8 nsectors;  /* number of sectors/track */
    uint32 sectsize; /* sector size, not including pre/postamble */
    uint16 track;    /* Current Track */
    uint8 ready;     /* Is drive ready? */
} DISK2_DRIVE_INFO;

typedef struct {
    PNP_INFO    pnp;    /* Plug and Play */
    uint8   sel_drive;  /* Currently selected drive */
    uint8   head_sel;   /* Head select (signals to drive itself) */
    uint8   head;       /* Head set by write to the HEAD register */
    uint8   cyl;        /* Cyl that the current operation is targeting */
    uint8   sector;     /* Sector the current READ/WRITE operation is targeting */
    uint8   hdr_sector; /* Current sector for WRITE_HEADER */
    uint8   ctl_attn;
    uint8   ctl_run;
    uint8   ctl_op;
    uint8   ctl_fault_clr;
    uint8   ctl_us;
    uint8   timeout;
    uint8   crc_error;
    uint8   overrun;
    uint8   seek_complete;
    uint8   write_fault;
    DISK2_DRIVE_INFO drive[DISK2_MAX_DRIVES];
} DISK2_INFO;

static DISK2_INFO disk2_info_data = { { 0x0, 0, 0xC8, 2 } };
static DISK2_INFO *disk2_info = &disk2_info_data;

/* Default geometry for a 20MB hard disk. */
#define C20MB_NTRACKS   243
#define C20MB_NHEADS    8
#define C20MB_NSECTORS  11
#define C20MB_SECTSIZE  1024

static int32 ntracks      = C20MB_NTRACKS;
static int32 nheads       = C20MB_NHEADS;
static int32 nsectors     = C20MB_NSECTORS;
static int32 sectsize     = C20MB_SECTSIZE;

extern uint32 PCX;
extern t_stat set_iobase(UNIT *uptr, int32 val, CONST char *cptr, void *desc);
extern t_stat show_iobase(FILE *st, UNIT *uptr, int32 val, CONST void *desc);
extern uint32 sim_map_resource(uint32 baseaddr, uint32 size, uint32 resource_type,
                               int32 (*routine)(const int32, const int32, const int32), const char* name, uint8 unmap);
extern int32 selchan_dma(uint8 *buf, uint32 len);
extern int32 find_unit_index(UNIT *uptr);
extern void raise_ss1_interrupt(uint8 intnum);

#define UNIT_V_DISK2_VERBOSE    (UNIT_V_UF + 1) /* verbose mode, i.e. show error messages   */
#define UNIT_DISK2_VERBOSE      (1 << UNIT_V_DISK2_VERBOSE)
#define DISK2_CAPACITY          (C20MB_NTRACKS*C20MB_NHEADS*C20MB_NSECTORS*C20MB_SECTSIZE)   /* Default Disk Capacity */

static t_stat disk2_reset(DEVICE *disk2_dev);
static t_stat disk2_attach(UNIT *uptr, CONST char *cptr);
static t_stat disk2_detach(UNIT *uptr);
static const char* disk2_description(DEVICE *dptr);

static void raise_disk2_interrupt(void);

static int32 disk2dev(const int32 port, const int32 io, const int32 data);

static uint8 DISK2_Read(const uint32 Addr);
static uint8 DISK2_Write(const uint32 Addr, uint8 cData);

static UNIT disk2_unit[] = {
    { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, DISK2_CAPACITY) },
    { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, DISK2_CAPACITY) },
    { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, DISK2_CAPACITY) },
    { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, DISK2_CAPACITY) }
};

static REG disk2_reg[] = {
    { DRDATAD (NTRACKS,    ntracks,             10,
               "Number of tracks"),                                     },
    { DRDATAD (NHEADS,     nheads,              8,
               "Number of heads"),                                      },
    { DRDATAD (NSECTORS,   nsectors,            8,
               "Number of sectors per track"),                          },
    { DRDATAD (SECTSIZE,   sectsize,            11,
               "Sector size not including pre/postamble"),              },
    { HRDATAD (SEL_DRIVE,  disk2_info_data.sel_drive, 3,
               "Currently selected drive"),                             },
    { HRDATAD (CYL,        disk2_info_data.cyl,       8,
               "Cylinder that the current operation is targeting"),     },
    { HRDATAD (HEAD,       disk2_info_data.head,      8,
               "Head that the current operation is targeting"),         },
    { HRDATAD (SECTOR,     disk2_info_data.sector,    8,
               "Sector that the current operation is targeting"),       },

    { NULL }
};

#define DISK2_NAME  "Compupro Hard Disk Controller"

static const char* disk2_description(DEVICE *dptr) {
    return DISK2_NAME;
}

static MTAB disk2_mod[] = {
    { MTAB_XTD|MTAB_VDV,    0,                  "IOBASE",   "IOBASE",
        &set_iobase, &show_iobase, NULL, "Sets disk controller I/O base address"    },
    /* quiet, no warning messages       */
    { UNIT_DISK2_VERBOSE,   0,                  "QUIET",    "QUIET",
        NULL, NULL, NULL, "No verbose messages for unit " DISK2_NAME "n"            },
    /* verbose, show warning messages   */
    { UNIT_DISK2_VERBOSE,   UNIT_DISK2_VERBOSE, "VERBOSE",  "VERBOSE",
        NULL, NULL, NULL, "Verbose messages for unit " DISK2_NAME "n"               },
    { 0 }
};

/* Debug Flags */
static DEBTAB disk2_dt[] = {
    { "ERROR",      ERROR_MSG,      "Error messages"    },
    { "SEEK",       SEEK_MSG,       "Seek messages"     },
    { "CMD",        CMD_MSG,        "Command messages"  },
    { "READ",       RD_DATA_MSG,    "Read messages"     },
    { "WRITE",      WR_DATA_MSG,    "Write messages"    },
    { "STATUS",     STATUS_MSG,     "Status messages"   },
    { "IRQ",        IRQ_MSG,        "IRQ messages"      },
    { "VERBOSE",    VERBOSE_MSG,    "Verbose messages"  },
    { NULL,         0                                   }
};

DEVICE disk2_dev = {
    "DISK2", disk2_unit, disk2_reg, disk2_mod,
    DISK2_MAX_DRIVES, 10, 31, 1, DISK2_MAX_DRIVES, DISK2_MAX_DRIVES,
    NULL, NULL, &disk2_reset,
    NULL, &disk2_attach, &disk2_detach,
    &disk2_info_data, (DEV_DISABLE | DEV_DIS | DEV_DEBUG), ERROR_MSG,
    disk2_dt, NULL, NULL, NULL, NULL, NULL, &disk2_description
};

/* Reset routine */
static t_stat disk2_reset(DEVICE *dptr)
{
    PNP_INFO *pnp = (PNP_INFO *)dptr->ctxt;

    if(dptr->flags & DEV_DIS) { /* Disconnect I/O Ports */
        sim_map_resource(pnp->io_base, pnp->io_size, RESOURCE_TYPE_IO, &disk2dev, "disk2dev", TRUE);
    } else {
        /* Connect DISK2 at base address */
        if(sim_map_resource(pnp->io_base, pnp->io_size, RESOURCE_TYPE_IO, &disk2dev, "disk2dev", FALSE) != 0) {
            sim_printf("%s: error mapping I/O resource at 0x%04x\n", __FUNCTION__, pnp->io_base);
            return SCPE_ARG;
        }
    }
    return SCPE_OK;
}


/* Attach routine */
static t_stat disk2_attach(UNIT *uptr, CONST char *cptr)
{
    t_stat r = SCPE_OK;
    DISK2_DRIVE_INFO *pDrive;
    int i = 0;

    i = find_unit_index(uptr);
    if (i == -1) {
        return (SCPE_IERR);
    }
    pDrive = &disk2_info->drive[i];

    pDrive->ready = 1;
    disk2_info->write_fault = 1;
    pDrive->track = 5;
    pDrive->ntracks = ntracks;
    pDrive->nheads = nheads;
    pDrive->nsectors = nsectors;
    pDrive->sectsize = sectsize;

    r = attach_unit(uptr, cptr);    /* attach unit  */
    if ( r != SCPE_OK)              /* error?       */
        return r;

    /* Determine length of this disk */
    if(sim_fsize(uptr->fileref) != 0) {
        uptr->capac = sim_fsize(uptr->fileref);
    } else {
        uptr->capac = (pDrive->ntracks * pDrive->nsectors * pDrive->nheads * pDrive->sectsize);
    }

    pDrive->uptr = uptr;

    /* Default for new file is DSK */
    uptr->u3 = IMAGE_TYPE_DSK;

    if(uptr->capac > 0) {
        r = assignDiskType(uptr);
        if (r != SCPE_OK) {
            disk2_detach(uptr);
            return r;
        }
    }

    if (uptr->flags & UNIT_DISK2_VERBOSE)
        sim_printf("DISK2%d, attached to '%s', type=%s, len=%d\n", i, cptr,
            uptr->u3 == IMAGE_TYPE_IMD ? "IMD" : uptr->u3 == IMAGE_TYPE_CPT ? "CPT" : "DSK",
            uptr->capac);

    if(uptr->u3 == IMAGE_TYPE_IMD) {
        if(uptr->capac < 318000) {
            sim_printf("Cannot create IMD files with SIMH.\nCopy an existing file and format it with CP/M.\n");
            disk2_detach(uptr);
            return SCPE_OPENERR;
        }

        if (uptr->flags & UNIT_DISK2_VERBOSE)
            sim_printf("--------------------------------------------------------\n");
        disk2_info->drive[i].imd = diskOpenEx((uptr->fileref), (uptr->flags & UNIT_DISK2_VERBOSE),
                                              &disk2_dev, VERBOSE_MSG, VERBOSE_MSG);
        if (uptr->flags & UNIT_DISK2_VERBOSE)
            sim_printf("\n");
    } else {
        disk2_info->drive[i].imd = NULL;
    }

    return SCPE_OK;
}


/* Detach routine */
static t_stat disk2_detach(UNIT *uptr)
{
    t_stat r;
    int8 i;

    i = find_unit_index(uptr);

    if (i == -1) {
        return (SCPE_IERR);
    }

    if (uptr->flags & UNIT_DISK2_VERBOSE)
        sim_printf("Detach DISK2%d\n", i);

    r = detach_unit(uptr);  /* detach unit */
    if ( r != SCPE_OK)
        return r;

    return SCPE_OK;
}


static int32 disk2dev(const int32 port, const int32 io, const int32 data)
{
/*  sim_debug(VERBOSE_MSG, &disk2_dev, "DISK2: " ADDRESS_FORMAT " IO %s, Port %02x\n", PCX, io ? "WR" : "RD", port); */
    if(io) {
        DISK2_Write(port, data);
        return 0;
    } else {
        return(DISK2_Read(port));
    }
}

#define DISK2_CSR   0   /* R=DISK2 Status / W=DISK2 Control Register */
#define DISK2_DATA  1   /* R=Step Pulse / W=Write Data Register */

static uint8 DISK2_Read(const uint32 Addr)
{
    uint8 cData;
    DISK2_DRIVE_INFO *pDrive;

    pDrive = &disk2_info->drive[disk2_info->sel_drive];
    cData = 0x00;

    switch(Addr & 0x1) {
        case DISK2_CSR:
            cData  = (disk2_info->ctl_attn) << 7;
            cData |= (disk2_info->timeout) << 6;
            cData |= (disk2_info->crc_error) << 5;
            cData |= (disk2_info->overrun) << 4;
            cData |= (pDrive->ready == 0) ? 0x08 : 0x00;
            cData |= (disk2_info->seek_complete == 0) ? 0x04 : 0x00;
            cData |= (disk2_info->write_fault) << 1;
            cData |= ((pDrive->track != 0) || (disk2_info->seek_complete == 0)) ? 0x01 : 0x00;
            sim_debug(STATUS_MSG, &disk2_dev, "DISK2: " ADDRESS_FORMAT
                      " RD STATUS = 0x%02x\n", PCX, cData);

            disk2_info->seek_complete = 1;
            break;
        case DISK2_DATA:
            if(disk2_info->ctl_op & 0x04) {
                if(pDrive->track < pDrive->ntracks) {
                    pDrive->track ++;
                }
            } else {
                if(pDrive->track > 0) {
                    pDrive->track --;
                }
            }
            sim_debug(SEEK_MSG, &disk2_dev, "DISK2: " ADDRESS_FORMAT
                      " Step %s, Track=%d\n", PCX,
                      disk2_info->ctl_op & 0x04 ? "IN" : "OUT", pDrive->track);
            disk2_info->seek_complete = 0;
            cData = 0xFF;           /* Return High-Z data */
            break;
    }

    return (cData);
}

#define DISK2_OP_DRIVE  0x00
#define DISK2_OP_CYL    0x01
#define DISK2_OP_HEAD   0x02
#define DISK2_OP_SECTOR 0x03

#define DISK2_CMD_NULL          0x00
#define DISK2_CMD_READ_DATA     0x01
#define DISK2_CMD_WRITE_DATA    0x02
#define DISK2_CMD_WRITE_HEADER  0x03
#define DISK2_CMD_READ_HEADER   0x04

static uint8 DISK2_Write(const uint32 Addr, uint8 cData)
{
    uint32 track_offset;
    uint8 result = 0;
    uint8 i;
    long file_offset;
    DISK2_DRIVE_INFO *pDrive;
    size_t rtn;

    pDrive = &disk2_info->drive[disk2_info->sel_drive];

    switch(Addr & 0x1) {
        case DISK2_CSR:     /* Write CTL register */
            disk2_info->ctl_attn = (cData & 0x80) >> 7;
            disk2_info->ctl_run = (cData & 0x40) >> 6;
            disk2_info->ctl_op = (cData & 0x38) >> 3;
            disk2_info->ctl_fault_clr = (cData & 0x04) >> 2;
            if(disk2_info->ctl_fault_clr == 1) {
                disk2_info->timeout = 0;
            }
            disk2_info->ctl_us = (cData & 0x03);
            sim_debug(VERBOSE_MSG, &disk2_dev, "DISK2: " ADDRESS_FORMAT
                      " ATTN*=%d, RUN=%d, OP=%d, FAULT_CLR=%d, US=%d\n",
                      PCX, disk2_info->ctl_attn, disk2_info->ctl_run,
                      disk2_info->ctl_op, disk2_info->ctl_fault_clr,
                      disk2_info->ctl_us);

            /* FIXME: seek_complete = 1 is needed by CP/M, but why? Also, maybe related,
             *        there appears to be a bug in the seeking logic.  For some reason, the
             *        pDrive->track does not equal the disk2_info->cyl, when doing READ_DATA and
             *        WRITE_DATA commands.  For this reason, disk2_info->cyl is used instead of
             *        pDrive->track for these commands.  For READ_HEADER and WRITE_HEADER,
             *        pDrive->track is used, because the DISK2 format program (DISK2.COM) does not
             *        issue DISK2_OP_CYL.  The root cause of this anomaly needs to be determined,
             *        because it is surely a bug in the logic somewhere.
             */
             /* pDrive->track may be different from disk2_info->cyl when a program such as DISK2.COM
                moves the position of the track without informing the CP/M BIOS which stores the
                current track for each drive. This appears to be an application program bug.
            */
            disk2_info->seek_complete = 1;

            if(disk2_info->ctl_run == 1) {
                disk2_info->timeout = 0;
                track_offset = disk2_info->cyl * pDrive->nheads * pDrive->nsectors * (pDrive->sectsize + 3);

                switch(disk2_info->ctl_op) {
                    case DISK2_CMD_NULL:
                        sim_debug(CMD_MSG, &disk2_dev, "DISK2: " ADDRESS_FORMAT
                                  " NULL Command\n", PCX);
                        break;
                    case DISK2_CMD_READ_DATA:
                        sim_debug(RD_DATA_MSG, &disk2_dev, "DISK2: " ADDRESS_FORMAT
                                  " READ_DATA: (C:%d/H:%d/S:%d)\n", PCX, disk2_info->cyl, disk2_info->head, disk2_info->sector);
                        if(disk2_info->head_sel != disk2_info->head) {
                            sim_printf("DISK2: " ADDRESS_FORMAT
                                   " READ_DATA: head_sel != head\n", PCX);
                        }
                        /* See FIXME above... that might be why this does not work properly... */
                        if(disk2_info->cyl != pDrive->track) { /* problem, should not happen, see above */
                            sim_debug(ERROR_MSG, &disk2_dev, "DISK2: " ADDRESS_FORMAT
                                      " READ_DATA: cyl=%d, track=%d\n", PCX, disk2_info->cyl, pDrive->track);
                            pDrive->track = disk2_info->cyl; /* update track */
                        }
                        if(sim_fseek((pDrive->uptr)->fileref, track_offset + (disk2_info->head_sel * pDrive->nsectors * (pDrive->sectsize + 3)), SEEK_SET) == 0) {
                            for(i=0;i<pDrive->nsectors;i++) {
                                /* Read sector */
                                rtn = sim_fread(sdata.raw, 1, (pDrive->sectsize + 3), (pDrive->uptr)->fileref);
                                if (rtn != (size_t)(pDrive->sectsize + 3)) {
                                    sim_debug(ERROR_MSG, &disk2_dev, "DISK2: " ADDRESS_FORMAT
                                              " READ_DATA: sim_fread error.\n", PCX);
                                }
                                if(sdata.u.header[2] == disk2_info->sector) {
                                    if(sdata.u.header[0] != disk2_info->cyl) { /*pDrive->track) { */
                                        sim_printf("DISK2: " ADDRESS_FORMAT
                                                   " READ_DATA Incorrect header: track\n", PCX);
                                        disk2_info->timeout = 1;
                                    }
                                    if(sdata.u.header[1] != disk2_info->head) {
                                        sim_printf("DISK2: " ADDRESS_FORMAT
                                                   " READ_DATA Incorrect header: head\n", PCX);
                                        disk2_info->timeout = 1;
                                    }

                                    selchan_dma(sdata.u.data, pDrive->sectsize);
                                    break;
                                }
                                if(i == pDrive->nsectors) {
                                    sim_printf("DISK2: " ADDRESS_FORMAT
                                               " Sector not found\n", PCX);
                                    disk2_info->timeout = 1;
                                }
                            }
                        } else {
                            sim_debug(ERROR_MSG, &disk2_dev, "DISK2: " ADDRESS_FORMAT
                                      " READ_DATA: sim_fseek error.\n", PCX);
                        }

                        break;
                    case DISK2_CMD_WRITE_DATA:
                        sim_debug(WR_DATA_MSG, &disk2_dev, "DISK2: " ADDRESS_FORMAT
                                  " WRITE_DATA: (C:%d/H:%d/S:%d)\n", PCX, disk2_info->cyl, disk2_info->head, disk2_info->sector);
                        if(disk2_info->head_sel != disk2_info->head) {
                            sim_printf("DISK2: " ADDRESS_FORMAT " WRITE_DATA: head_sel != head\n", PCX);
                        }
                        if(disk2_info->cyl != pDrive->track) { /* problem, should not happen, see above */
                            sim_debug(ERROR_MSG, &disk2_dev, "DISK2: " ADDRESS_FORMAT
                                      " WRITE_DATA = 0x%02x, cyl=%d, track=%d\n", PCX, cData, disk2_info->cyl, pDrive->track);
                            pDrive->track = disk2_info->cyl; /* update track */
                       }

                        if(sim_fseek((pDrive->uptr)->fileref, track_offset + (disk2_info->head_sel * pDrive->nsectors * (pDrive->sectsize + 3)), SEEK_SET) == 0) {
                            for(i=0;i<pDrive->nsectors;i++) {
                                /* Read sector */
                                file_offset = ftell((pDrive->uptr)->fileref);
                                rtn = sim_fread(sdata.raw, 1, 3, (pDrive->uptr)->fileref);
                                if (rtn != 3) {
                                    sim_debug(ERROR_MSG, &disk2_dev, "DISK2: " ADDRESS_FORMAT
                                              " WRITE_DATA: sim_fread error.\n", PCX);
                                }
                                if(sdata.u.header[2] == disk2_info->sector) {
                                    if(sdata.u.header[0] != disk2_info->cyl) {
                                        sim_printf("DISK2: " ADDRESS_FORMAT
                                                   " WRITE_DATA Incorrect header: track\n", PCX);
                                        disk2_info->timeout = 1;
                                    }
                                    if(sdata.u.header[1] != disk2_info->head) {
                                        sim_printf("DISK2: " ADDRESS_FORMAT
                                                   " WRITE_DATA Incorrect header: head\n", PCX);
                                        disk2_info->timeout = 1;
                                    }

                                    selchan_dma(sdata.u.data, pDrive->sectsize);
                                    if(sim_fseek((pDrive->uptr)->fileref, file_offset+3, SEEK_SET) == 0) {
                                        sim_fwrite(sdata.u.data, 1, (pDrive->sectsize), (pDrive->uptr)->fileref);
                                    } else {
                                        sim_debug(ERROR_MSG, &disk2_dev, "DISK2: " ADDRESS_FORMAT
                                                  " WRITE_DATA: sim_fseek error.\n", PCX);
                                    }
                                    break;
                                }
                                rtn = sim_fread(sdata.raw, 1, pDrive->sectsize, (pDrive->uptr)->fileref);
                                if (rtn != (size_t)(pDrive->sectsize)) {
                                    sim_debug(ERROR_MSG, &disk2_dev, "DISK2: " ADDRESS_FORMAT
                                              " WRITE_DATA: sim_fread error.\n", PCX);
                                }
                                if(i == pDrive->nsectors) {
                                    sim_printf("DISK2: " ADDRESS_FORMAT " Sector not found\n", PCX);
                                    disk2_info->timeout = 1;
                                }
                            }
                        } else {
                            sim_debug(ERROR_MSG, &disk2_dev, "DISK2: " ADDRESS_FORMAT
                                      " WRITE_DATA: sim_fseek error.\n", PCX);
                        }
                        break;
                    case DISK2_CMD_WRITE_HEADER:
                        track_offset = pDrive->track * pDrive->nheads * pDrive->nsectors * (pDrive->sectsize + 3);
                        sim_debug(CMD_MSG, &disk2_dev, "DISK2: " ADDRESS_FORMAT
                                  " WRITE_HEADER Command: track=%d (%d), Head=%d, Sector=%d\n",
                                  PCX, pDrive->track, disk2_info->cyl,
                                  disk2_info->head_sel, disk2_info->hdr_sector);

                        i = disk2_info->hdr_sector;
                        selchan_dma(sdata.raw, 3);
                        if(sim_fseek((pDrive->uptr)->fileref, track_offset + (disk2_info->head_sel * (pDrive->sectsize + 3) * pDrive->nsectors) + (i * (pDrive->sectsize + 3)), SEEK_SET) == 0) {
                            sim_fwrite(sdata.raw, 1, 3, (pDrive->uptr)->fileref);
                        } else {
                            sim_debug(ERROR_MSG, &disk2_dev, "DISK2: " ADDRESS_FORMAT
                                      " WRITE_HEADER Command: sim_fseek error.\n", PCX);
                        }

                        disk2_info->hdr_sector++;
                        if(disk2_info->hdr_sector >= pDrive->nsectors) {
                            disk2_info->hdr_sector = 0;
                            disk2_info->timeout = 1;
                        }
                        break;
                    case DISK2_CMD_READ_HEADER:
                        track_offset = pDrive->track * pDrive->nheads * pDrive->nsectors * (pDrive->sectsize + 3);
                        sim_debug(CMD_MSG, &disk2_dev, "DISK2: " ADDRESS_FORMAT
                                  " READ_HEADER Command\n", PCX);
                        if(sim_fseek((pDrive->uptr)->fileref, track_offset + (disk2_info->head_sel * pDrive->nsectors * (pDrive->sectsize + 3)), SEEK_SET) == 0) {
                            rtn = sim_fread(sdata.raw, 1, 3, (pDrive->uptr)->fileref);
                            if (rtn != 3) {
                                sim_debug(ERROR_MSG, &disk2_dev, "DISK2: " ADDRESS_FORMAT
                                          " READ_HEADER: sim_fread error.\n", PCX);
                            }
                        } else {
                            sim_debug(ERROR_MSG, &disk2_dev, "DISK2: " ADDRESS_FORMAT
                                      " READ_HEADER: sim_fseek error.\n", PCX);
                        }
                        selchan_dma(sdata.raw, 3);

                        break;
                    default:
                        sim_printf("DISK2: " ADDRESS_FORMAT " Unknown CMD=%d\n", PCX, disk2_info->ctl_op);
                        break;
                }

                raise_disk2_interrupt();
                disk2_info->ctl_attn = 0;
            }

            break;
        case DISK2_DATA:
            switch(disk2_info->ctl_op) {
                case DISK2_OP_DRIVE:
                    switch(cData >> 4) {
                        case 0x01:
                            disk2_info->sel_drive = 0;
                            break;
                        case 0x02:
                            disk2_info->sel_drive = 1;
                            break;
                        case 0x04:
                            disk2_info->sel_drive = 2;
                            break;
                        case 0x08:
                            disk2_info->sel_drive = 3;
                            break;
                        default:
                            sim_printf("DISK2: " ADDRESS_FORMAT
                                   " Error, invalid drive select=0x%x\n", PCX, cData >> 4);
                            break;
                    }

                    disk2_info->head_sel = cData & 0x0F;

                    sim_debug(VERBOSE_MSG, &disk2_dev, "DISK2: " ADDRESS_FORMAT
                              " Write DATA [DRIVE]=%d, Head=%d\n",
                              PCX, disk2_info->sel_drive, disk2_info->head);
                    break;
                case DISK2_OP_CYL:
                    disk2_info->cyl = cData;
                    sim_debug(VERBOSE_MSG, &disk2_dev, "DISK2: " ADDRESS_FORMAT
                              " Write DATA [CYL] = %02x\n", PCX, cData);
                    break;
                case DISK2_OP_HEAD:
                    disk2_info->head = cData;
                    sim_debug(VERBOSE_MSG, &disk2_dev, "DISK2: " ADDRESS_FORMAT
                              " Write DATA [HEAD] = %02x\n", PCX, cData);
                    break;
                case DISK2_OP_SECTOR:
                    disk2_info->sector = cData;
                    sim_debug(VERBOSE_MSG, &disk2_dev, "DISK2: " ADDRESS_FORMAT
                              " Write Register [SECTOR] = %02x\n", PCX, cData);
                    break;
                default:
                    sim_debug(VERBOSE_MSG, &disk2_dev, "DISK2: " ADDRESS_FORMAT
                              " Write Register unknown op [%d] = %02x\n",
                              PCX, disk2_info->ctl_op, cData);
                    break;
            }
    }

    return (result);
}

#define SS1_VI1_INT         1   /* DISK2/DISK3 interrupts tied to VI1 */

static void raise_disk2_interrupt(void)
{
    sim_debug(IRQ_MSG, &disk2_dev, "DISK2: " ADDRESS_FORMAT
              " Interrupt\n", PCX);

    raise_ss1_interrupt(SS1_VI1_INT);

}
