/*************************************************************************
 *                                                                       *
 * 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:                                                   *
 *     Northstar MDS-AD Disk Controller module for SIMH                  *
 * Single- and Double-density disks are supported, but the entire disk   *
 * must be the same density.                                             *
 *                                                                       *
 *************************************************************************/

/*#define DBG_MSG*/
#include "altairz80_defs.h"
#include "sim_imd.h"

#ifdef DBG_MSG
#define DBG_PRINT(args) sim_printf args
#else
#define DBG_PRINT(args)
#endif

/* 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 ORDERS_MSG          (1 << 6)
#define RD_DATA_DETAIL_MSG  (1 << 7)
#define WR_DATA_DETAIL_MSG  (1 << 8)

extern uint32 PCX;
extern t_stat set_membase(UNIT *uptr, int32 val, CONST char *cptr, void *desc);
extern t_stat show_membase(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);

#define MDSAD_MAX_DRIVES        4
#define MDSAD_SECTOR_LEN_DD     512
#define MDSAD_SECTOR_LEN_SD     256
#define MDSAD_SECTORS_PER_TRACK 10
#define MDSAD_TRACKS            35
#define MDSAD_ZEROS_LEN_DD      32
#define MDSAD_ZEROS_LEN_SD      16
#define MDSAD_SYNC_LEN_DD       2
#define MDSAD_SYNC_LEN_SD       1
#define MDSAD_CHECKSUM_LEN      1
#define MDSAD_RAW_LEN_DD        (MDSAD_ZEROS_LEN_DD + MDSAD_SYNC_LEN_DD + MDSAD_SECTOR_LEN_DD + MDSAD_CHECKSUM_LEN)
#define MDSAD_RAW_LEN_SD        (MDSAD_ZEROS_LEN_SD + MDSAD_SYNC_LEN_SD + MDSAD_SECTOR_LEN_SD + MDSAD_CHECKSUM_LEN)

#define IS_DOUBLE_DENSITY       (pDrive->sector_len == MDSAD_SECTOR_LEN_DD)
#define MDSAD_SECTOR_DATA       (IS_DOUBLE_DENSITY ? sdata.u_dd.data : sdata.u_sd.data)
#define MDSAD_RAW_LEN           (IS_DOUBLE_DENSITY ? MDSAD_RAW_LEN_DD : MDSAD_RAW_LEN_SD)
#define MDSAD_SECTOR_LEN        (pDrive->sector_len)

typedef union {
    struct {
        uint8 zeros[MDSAD_ZEROS_LEN_DD];
        uint8 sync[MDSAD_SYNC_LEN_DD];
        uint8 data[MDSAD_SECTOR_LEN_DD];
        uint8 checksum;
    } u_dd;
    struct {
        uint8 zeros[MDSAD_ZEROS_LEN_SD];
        uint8 sync[MDSAD_SYNC_LEN_SD];
        uint8 data[MDSAD_SECTOR_LEN_SD];
        uint8 checksum;
    } u_sd;
    uint8 raw[MDSAD_RAW_LEN_DD];

} SECTOR_FORMAT;

typedef struct {
    UNIT *uptr;
    uint8 track;
    uint8 wp;       /* Disk write protected */
    uint8 sector;   /* Current Sector number */
    uint32 sector_wait_count;
    uint16 sector_len;
} MDSAD_DRIVE_INFO;

typedef struct {
    uint8 dd;       /* Controls density on write DD=1 for double density and DD=0 for single density. */
    uint8 ss;       /* Specifies the side of a double-sided diskette. The bottom side (and only side of a single-sided diskette) is selected when SS=0. The second (top) side is selected when SS=1. */
    uint8 dp;       /* has shared use. During stepping operations, DP=O specifies a step out and DP=1 specifies a step in. During write operations, write precompensation is invoked if and only if DP=1. */
    uint8 st;       /* controls the level of the head step signal to the disk drives. */
    uint8 pst;      /* value of step signal (st) on previous order */
    uint8 ds;       /* is the drive select field, encoded as follows: */
                    /* 0=no drive selected
                     * 1=drive 1 selected
                     * 2=drive 2 selected
                     * 4=drive 3 selected
                     * 8=drive 4 selected
                     */
} ORDERS;

typedef struct {
    uint8 sf;       /* Sector Flag: set when sector hole detected, reset by software. */
    uint8 ix;       /* Index Detect: true if index hole detected during previous sector. */
    uint8 dd;       /* Double Density Indicator: true if data being read is encoded in double density. */
    uint8 mo;       /* Motor On: true while motor(s) are on. */
} COM_STATUS;

typedef struct {
    uint8 wi;       /* Window: true during 96-microsecond window at beginning of sector. */
    uint8 re;       /* Read Enable: true while phase-locked loop is enabled. */
    uint8 sp;       /* Spare: reserved for future use. */
    uint8 bd;       /* Body: set when sync character is detected. */
} A_STATUS;

typedef struct {
    uint8 wr;       /* Write: true during valid write operation. */
    uint8 sp;       /* Spare: reserved for future use. */
    uint8 wp;       /* Write Protect: true while the diskette installed in the selected drive is write protected. */
    uint8 t0;       /* Track 0: true if selected drive is at track zero. */
} B_STATUS;

typedef struct {
    uint8 sc;       /* Sector Counter: indicates the current sector position. */
} C_STATUS;

typedef struct {
    PNP_INFO    pnp;    /* Plug and Play */

    ORDERS      orders;
    COM_STATUS  com_status;
    A_STATUS    a_status;
    B_STATUS    b_status;
    C_STATUS    c_status;

    uint8 int_enable;   /* Interrupt Enable */
    uint32 datacount;   /* Number of data bytes transferred from controller for current sector */
    MDSAD_DRIVE_INFO drive[MDSAD_MAX_DRIVES];
} MDSAD_INFO;

static MDSAD_INFO mdsad_info_data = { { 0xE800, 1024, 0, 0 } };
static MDSAD_INFO *mdsad_info = &mdsad_info_data;

static SECTOR_FORMAT sdata;

#define UNIT_V_MDSAD_VERBOSE    (UNIT_V_UF + 1) /* verbose mode, i.e. show error messages       */
#define UNIT_MDSAD_VERBOSE      (1 << UNIT_V_MDSAD_VERBOSE)
#define MDSAD_CAPACITY          (70*10*MDSAD_SECTOR_LEN_DD)    /* Default North Star Disk Capacity */

/* MDS-AD Controller Subcases */
#define MDSAD_READ_ROM      0
#define MDSAD_WRITE_DATA    1
#define MDSAD_CTLR_ORDERS   2
#define MDSAD_CTLR_COMMAND  3

/* MDS-AD Controller Commands */
#define MDSAD_CMD_NOP       0
#define MDSAD_CMD_RESET_SF  1
#define MDSAD_CMD_INTR_DIS  2
#define MDSAD_CMD_INTR_ARM  3
#define MDSAD_CMD_SET_BODY  4
#define MDSAD_CMD_MOTORS_ON 5
#define MDSAD_CMD_BEGIN_WR  6
#define MDSAD_CMD_RESET     7

/* MDS-AD Data returned on DI bus */
#define MDSAD_A_STATUS      1
#define MDSAD_B_STATUS      2
#define MDSAD_C_STATUS      3
#define MDSAD_READ_DATA     4

/* MDS-AD status byte masks */
/* A-Status */
#define MDSAD_A_SF          0x80
#define MDSAD_A_IX          0x40
#define MDSAD_A_DD          0x20
#define MDSAD_A_MO          0x10
#define MDSAD_A_WI          0x08
#define MDSAD_A_RE          0x04
#define MDSAD_A_SP          0x02
#define MDSAD_A_BD          0x01

/* B-Status */
#define MDSAD_B_SF          0x80
#define MDSAD_B_IX          0x40
#define MDSAD_B_DD          0x20
#define MDSAD_B_MO          0x10
#define MDSAD_B_WR          0x08
#define MDSAD_B_SP          0x04
#define MDSAD_B_WP          0x02
#define MDSAD_B_T0          0x01

/* C-Status */
#define MDSAD_C_SF          0x80
#define MDSAD_C_IX          0x40
#define MDSAD_C_DD          0x20
#define MDSAD_C_MO          0x10
#define MDSAD_C_SC          0x0f

/* Local function prototypes */
static t_stat mdsad_reset(DEVICE *mdsad_dev);
static t_stat mdsad_attach(UNIT *uptr, CONST char *cptr);
static t_stat mdsad_detach(UNIT *uptr);
static t_stat mdsad_boot(int32 unitno, DEVICE *dptr);
static uint8 MDSAD_Read(const uint32 Addr);
static const char* mdsad_description(DEVICE *dptr);

static int32 mdsaddev(const int32 Addr, const int32 rw, const int32 data);

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

static REG mdsad_reg[] = {
    { NULL }
};

#define MDSAD_NAME  "North Star Floppy Controller"

static const char* mdsad_description(DEVICE *dptr) {
    return MDSAD_NAME;
}

static MTAB mdsad_mod[] = {
    { MTAB_XTD|MTAB_VDV,    0,                  "MEMBASE",  "MEMBASE",
        &set_membase, &show_membase, NULL, "Sets disk controller memory base address"   },
    /* quiet, no warning messages       */
    { UNIT_MDSAD_VERBOSE,   0,                  "QUIET",    "QUIET",
        NULL, NULL, NULL, "No verbose messages for unit " MDSAD_NAME "n"                },
    /* verbose, show warning messages   */
    { UNIT_MDSAD_VERBOSE,   UNIT_MDSAD_VERBOSE, "VERBOSE",  "VERBOSE",
        NULL, NULL, NULL, "Verbose messages for unit " MDSAD_NAME "n"                   },
    { 0 }
};

/* Debug Flags */
static DEBTAB mdsad_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"       },
    { "ORDERS",     ORDERS_MSG,         "Orders messages"       },
    { "RDDETAIL",   RD_DATA_DETAIL_MSG, "Read detail messages"  },
    { "WRDETAIL",   WR_DATA_DETAIL_MSG, "Write detail messages" },
    { NULL,         0                                           }
};

DEVICE mdsad_dev = {
    "MDSAD", mdsad_unit, mdsad_reg, mdsad_mod,
    MDSAD_MAX_DRIVES, 10, 31, 1, MDSAD_MAX_DRIVES, MDSAD_MAX_DRIVES,
    NULL, NULL, &mdsad_reset,
    &mdsad_boot, &mdsad_attach, &mdsad_detach,
    &mdsad_info_data, (DEV_DISABLE | DEV_DIS | DEV_DEBUG), ERROR_MSG,
    mdsad_dt, NULL, NULL, NULL, NULL, NULL, &mdsad_description
};

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

    if(dptr->flags & DEV_DIS) {
        sim_map_resource(pnp->mem_base, pnp->mem_size,
            RESOURCE_TYPE_MEMORY, &mdsaddev, "mdsaddev", TRUE);
    } else {
        /* Connect MDSAD at base address */
        if(sim_map_resource(pnp->mem_base, pnp->mem_size,
            RESOURCE_TYPE_MEMORY, &mdsaddev, "mdsaddev", FALSE) != 0) {
            sim_printf("%s: error mapping resource at 0x%04x\n",
                __FUNCTION__, pnp->mem_base);
            dptr->flags |= DEV_DIS;
            return SCPE_ARG;
        }
    }
    return SCPE_OK;
}

/* Attach routine */
static t_stat mdsad_attach(UNIT *uptr, CONST char *cptr)
{
    char header[4];
    t_stat r;
    unsigned int i = 0;

    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 = MDSAD_CAPACITY;
    }

    for(i = 0; i < MDSAD_MAX_DRIVES; i++) {
        mdsad_info->drive[i].uptr = &mdsad_dev.units[i];
    }

    for(i = 0; i < MDSAD_MAX_DRIVES; i++) {
        if(mdsad_dev.units[i].fileref == uptr->fileref) {
            mdsad_info->orders.st = 0;      /* ensure valid state */

            if (uptr->capac > 89600) {      /* Double-density */
                mdsad_info->drive[i].sector_len = MDSAD_SECTOR_LEN_DD;
            } else {      /* Single-density */
                mdsad_info->drive[i].sector_len = MDSAD_SECTOR_LEN_SD;
            }
            break;
        }
    }

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

    if(uptr->capac > 0) {
        char *rtn = fgets(header, 4, uptr->fileref);
        if((rtn != NULL) && (strncmp(header, "CPT", 3) == 0)) {
            sim_printf("CPT images not yet supported\n");
            uptr->u3 = IMAGE_TYPE_CPT;
            mdsad_detach(uptr);
            return SCPE_OPENERR;
        } else {
            uptr->u3 = IMAGE_TYPE_DSK;
        }
    }

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

    return SCPE_OK;
}


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

    for(i = 0; i < MDSAD_MAX_DRIVES; i++) {
        if(mdsad_dev.units[i].fileref == uptr->fileref) {
            break;
        }
    }

    if (i >= MDSAD_MAX_DRIVES)
        return SCPE_ARG;

    DBG_PRINT(("Detach MDSAD%d\n", i));

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

    mdsad_dev.units[i].fileref = NULL;
    return SCPE_OK;
}

static t_stat mdsad_boot(int32 unitno, DEVICE *dptr)
{

    PNP_INFO *pnp = (PNP_INFO *)dptr->ctxt;

    DBG_PRINT(("Booting MDSAD Controller at 0x%04x, unit %d\n",
        pnp->mem_base+1+(unitno&3), unitno & 3));

    /* Unit 3 can't be booted yet.  This involves modifying the A register. */
    *((int32 *) sim_PC->loc) = pnp->mem_base+1+(unitno&3);
    return SCPE_OK;
}

static int32 mdsaddev(const int32 Addr, const int32 rw, const int32 data)
{
    if(rw == 0) { /* Read */
        return(MDSAD_Read(Addr));
    } else {    /* Write */
        DBG_PRINT(("MDSAD: write attempt at 0x%04x ignored.\n", Addr));
        return (-1);
    }
}

/* This ROM image is taken from the Solace Emulator, which uses         */
/* a ROM from a "Micro Complex Phase Lock II" dual-                     */
/* density controller card.  It is supposedly compatible with the       */
/* Northstar-designed dual density floppy controller.  It has the       */
/* interesting property that by jumping to base_addr+0 (or +1) and      */
/* it boots from floppy 0; jump to base_addr+2 you boot from floppy 1;  */
/* jump to base_addr+3 and you boot from floppy 2.  You can boot from   */
/* floppy 3 by loading A with 08H and jumping to base_addr+7.           */
static uint8 mdsad_rom[] = {
    0x44, 0x01, 0x01, 0x01, 0x82, 0x84, 0x78, 0xE6, 0x07, 0x4F, 0x00, 0x31, 0x30, 0x00, 0x21, 0x29, /* 0x00 */
    0x00, 0xE5, 0x21, 0x2C, 0xC2, 0xE5, 0x21, 0x77, 0x13, 0xE5, 0x21, 0xC9, 0x1A, 0xE5, 0xCD, 0x28, /* 0x10 */
    0x00, 0x21, 0x30, 0x00, 0x5B, 0x52, 0x44, 0x54, 0x5D, 0x3A, 0x27, 0x00, 0x57, 0xC3, 0x29, 0x00, /* 0x20 */
    0x14, 0x14, 0x1E, 0x15, 0x1A, 0x26, 0x30, 0xCD, 0xD9, 0x00, 0x42, 0x05, 0x0A, 0xCD, 0xD4, 0x00, /* 0x30 */
    0x2E, 0x0D, 0x2D, 0xCA, 0x43, 0x00, 0xCD, 0xD7, 0x00, 0x1A, 0xE6, 0x40, 0xCA, 0x42, 0x00, 0x3E, /* 0x40 */
    0x0A, 0xF5, 0xCD, 0xC1, 0x00, 0x1E, 0x20, 0x1A, 0xE6, 0x01, 0xC2, 0x63, 0x00, 0xCD, 0xC5, 0x00, /* 0x50 */
    0xC3, 0x55, 0x00, 0x2E, 0x04, 0xCD, 0xE7, 0x00, 0x1E, 0x10, 0x1A, 0xE6, 0x04, 0xCA, 0x68, 0x00, /* 0x60 */
    0x3E, 0x09, 0x3D, 0xC2, 0x72, 0x00, 0x1A, 0xE6, 0x20, 0xC2, 0x84, 0x00, 0xCD, 0xC1, 0x00, 0x2E, /* 0x70 */
    0x08, 0xCD, 0xE7, 0x00, 0x06, 0xA3, 0x1E, 0x10, 0x05, 0xCA, 0xF4, 0x00, 0x1A, 0x0F, 0xD2, 0x88, /* 0x80 */
    0x00, 0x1E, 0x40, 0x1A, 0x67, 0x2E, 0x00, 0x36, 0x59, 0x07, 0x47, 0x23, 0x1A, 0x77, 0xA8, 0x07, /* 0x90 */
    0x47, 0x2C, 0xC2, 0x9C, 0x00, 0x24, 0x1A, 0x77, 0xA8, 0x07, 0x47, 0x2C, 0xC2, 0xA6, 0x00, 0x1A, /* 0xA0 */
    0xA8, 0xC2, 0xF4, 0x00, 0x25, 0x2E, 0x03, 0x71, 0x2D, 0x36, 0x59, 0xC2, 0xB8, 0x00, 0x2E, 0x0A, /* 0xB0 */
    0xE9, 0x3E, 0x20, 0x81, 0x4F, 0x0A, 0x3E, 0x10, 0x81, 0x4F, 0x0A, 0x3E, 0xF0, 0x81, 0x4F, 0x0A, /* 0xC0 */
    0x79, 0xE6, 0x0F, 0x4F, 0xCD, 0xD7, 0x00, 0x26, 0x01, 0x1E, 0x11, 0x1A, 0x1D, 0x1A, 0xB7, 0xF2, /* 0xD0 */
    0xDD, 0x00, 0x25, 0xC2, 0xD9, 0x00, 0xC9, 0xCD, 0xD7, 0x00, 0x1E, 0x35, 0x1A, 0xE6, 0x0F, 0xBD, /* 0xE0 */
    0xC2, 0xE7, 0x00, 0xC9, 0xF1, 0x3D, 0xF5, 0xC2, 0x55, 0x00, 0xC3, 0xFA, 0x00, 0x52, 0x44, 0x54  /* 0xF0 */
};

static void showdata(int32 isRead) {
    int32 i;
    MDSAD_DRIVE_INFO* pDrive;

    pDrive = &mdsad_info->drive[mdsad_info->orders.ds];
    sim_printf("MDSAD: " ADDRESS_FORMAT " %s Sector =\n\t", PCX, isRead ? "Read" : "Write");
    for(i=0; i < MDSAD_SECTOR_LEN; i++) {
        sim_printf("%02X ", MDSAD_SECTOR_DATA[i]);
        if(((i+1) & 0xf) == 0)
            sim_printf("\n\t");
    }
    sim_printf("\n");
}

static int checksum;
static uint32 sec_offset;

static uint32 calculate_mdsad_sec_offset(uint8 track, uint8 head, uint8 sector)
{
    MDSAD_DRIVE_INFO* pDrive;

    pDrive = &mdsad_info->drive[mdsad_info->orders.ds];

    if(mdsad_info->orders.ss == 0) { /* Side 0 */
        return ((track * (MDSAD_SECTOR_LEN * MDSAD_SECTORS_PER_TRACK)) + (sector * MDSAD_SECTOR_LEN));
    } else { /* Side 1 */
        return ((((MDSAD_TRACKS-1) - track) * (MDSAD_SECTOR_LEN * MDSAD_SECTORS_PER_TRACK)) +
                 ((MDSAD_SECTOR_LEN * MDSAD_SECTORS_PER_TRACK) * MDSAD_TRACKS) + /* Skip over side 0 */
                 (sector * MDSAD_SECTOR_LEN)); /* Sector offset from beginning of track. */
    }
}

static uint8 MDSAD_Read(const uint32 Addr)
{
    uint8 cData;
    uint8 ds;
    MDSAD_DRIVE_INFO *pDrive;
    int32 rtn;

    cData = 0x00;

    pDrive = &mdsad_info->drive[mdsad_info->orders.ds];

    switch( (Addr & 0x300) >> 8 ) {
        case MDSAD_READ_ROM:
            cData = mdsad_rom[Addr & 0xFF];
            break;
        case MDSAD_WRITE_DATA:
        {
            pDrive->sector_len = (mdsad_info->orders.dd == 1) ? MDSAD_SECTOR_LEN_DD : MDSAD_SECTOR_LEN_SD;
            if(mdsad_info->datacount == 0) {
                sim_debug(WR_DATA_MSG, &mdsad_dev, "MDSAD: " ADDRESS_FORMAT
                          " WRITE Start:  Drive: %d, Track=%d, Head=%d, Sector=%d\n",
                          PCX, mdsad_info->orders.ds, pDrive->track,
                          mdsad_info->orders.ss, pDrive->sector);

                sec_offset = calculate_mdsad_sec_offset(pDrive->track,
                    mdsad_info->orders.ss,
                    pDrive->sector);

            }

            DBG_PRINT(("MDSAD: " ADDRESS_FORMAT
                " WRITE-DATA[offset:%06x+%03x]=%02x\n",
                PCX, sec_offset, mdsad_info->datacount, Addr & 0xFF));
            mdsad_info->datacount++;
            if(mdsad_info->datacount < (uint32)MDSAD_RAW_LEN)
                sdata.raw[mdsad_info->datacount] = Addr & 0xFF;

            if(mdsad_info->datacount == (MDSAD_RAW_LEN - 1)) {
                sim_debug(WR_DATA_DETAIL_MSG, &mdsad_dev, "MDSAD: " ADDRESS_FORMAT
                          " Write Complete\n", PCX);

                if ((pDrive->uptr == NULL) || (pDrive->uptr->fileref == NULL)) {
                    sim_debug(WR_DATA_MSG, &mdsad_dev, "MDSAD: " ADDRESS_FORMAT
                              " Drive: %d not attached - write ignored.\n", PCX, mdsad_info->orders.ds);
                    return 0x00;
                }
                if(mdsad_dev.dctrl & WR_DATA_DETAIL_MSG)
                    showdata(FALSE);
                switch((pDrive->uptr)->u3)
                {
                    case IMAGE_TYPE_DSK:
                        if(pDrive->uptr->fileref == NULL) {
                            sim_printf(".fileref is NULL!\n");
                        } else {
                            if (sim_fseek((pDrive->uptr)->fileref, sec_offset, SEEK_SET) == 0) {
                                sim_fwrite(MDSAD_SECTOR_DATA, 1, MDSAD_SECTOR_LEN,
                                           (pDrive->uptr)->fileref);
                            } else {
                                sim_printf("%s: sim_fseek error\n", __FUNCTION__);
                            }
                        }
                        break;
                    case IMAGE_TYPE_CPT:
                        sim_printf("%s: CPT Format not supported\n", __FUNCTION__);
                        break;
                    default:
                        sim_printf("%s: Unknown image Format\n", __FUNCTION__);
                        break;
                }
            }
            break;
        }
        case MDSAD_CTLR_ORDERS:
            mdsad_info->orders.dd = (Addr & 0x80) >> 7;
            mdsad_info->orders.ss = (Addr & 0x40) >> 6;
            mdsad_info->orders.dp = (Addr & 0x20) >> 5;
            mdsad_info->orders.pst = mdsad_info->orders.st;     /* save previous state of st */
            mdsad_info->orders.st = (Addr & 0x10) >> 4;
            ds = Addr & 0x0F;

            switch(ds) {
                case 0:
                case 1:
                    mdsad_info->orders.ds = 0;
                    break;
                case 2:
                    mdsad_info->orders.ds = 1;
                    break;
                case 4:
                    mdsad_info->orders.ds = 2;
                    break;
                case 8:
                    mdsad_info->orders.ds = 3;
                    break;
            }

            if(mdsad_info->orders.ds != (mdsad_info->orders.ds & 0x03)) {
                sim_debug(ERROR_MSG, &mdsad_dev, "MDSAD: " ADDRESS_FORMAT
                          " Controller Orders update drive %x\n", PCX, mdsad_info->orders.ds);
                mdsad_info->orders.ds &= 0x03;
            }
            sim_debug(ORDERS_MSG, &mdsad_dev, "MDSAD: " ADDRESS_FORMAT
                      " Controller Orders: Drive=%x[%x], DD=%d, SS=%d, DP=%d, ST=%d\n",
                      PCX, mdsad_info->orders.ds, ds, mdsad_info->orders.dd,
                      mdsad_info->orders.ss, mdsad_info->orders.dp, mdsad_info->orders.st);

            /* use latest selected drive */
            pDrive = &mdsad_info->drive[mdsad_info->orders.ds];

            /* step only on transition of the step bit from 1 to 0. This duplicates the
               way the hardware functions and is required by some North Star code */
            if((mdsad_info->orders.st == 0) && (mdsad_info->orders.pst != 0)) {
                if(mdsad_info->orders.dp == 0) {
                    sim_debug(SEEK_MSG, &mdsad_dev, "MDSAD: " ADDRESS_FORMAT
                              " Step out: Track=%d%s\n", PCX, pDrive->track,
                              pDrive->track == 0 ? "[Warn: already at 0]" : "");
                    if(pDrive->track > 0) /* anything to do? */
                        pDrive->track--;
                } else {
                    sim_debug(SEEK_MSG, &mdsad_dev, "MDSAD: " ADDRESS_FORMAT
                              " Step  in: Track=%d%s\n", PCX, pDrive->track,
                              pDrive->track == (MDSAD_TRACKS - 1) ? "[Warn: already at highest track]" : "");
                    if(pDrive->track < (MDSAD_TRACKS - 1)) /* anything to do? */
                        pDrive->track++;
                }
            }
            /* always update t0 */
            mdsad_info->b_status.t0 = (pDrive->track == 0);
            break;
        case MDSAD_CTLR_COMMAND:
/*          sim_debug(CMD_MSG, &mdsad_dev, "MDSAD: " ADDRESS_FORMAT " DM=%x\n", PCX, (Addr & 0xF0) >> 4); */
            switch(Addr & 0x0F) {
                case MDSAD_CMD_MOTORS_ON:
                    sim_debug(CMD_MSG, &mdsad_dev, "MDSAD: " ADDRESS_FORMAT
                              " CMD=Motors On\n", PCX);
                    mdsad_info->com_status.mo = 1;      /* Turn motors on */
                    break;

                case MDSAD_CMD_NOP:
                    pDrive->sector_wait_count++;
                    switch(pDrive->sector_wait_count) {
                        case 10:
                        {
                            mdsad_info->com_status.sf = 1;
                            mdsad_info->a_status.re = 0;
                            mdsad_info->a_status.bd = 0;
                            pDrive->sector_wait_count = 0;
                            pDrive->sector++;
                            if(pDrive->sector >= MDSAD_SECTORS_PER_TRACK) {
                                pDrive->sector = 0;
                                mdsad_info->com_status.ix = 1;
                            } else {
                                mdsad_info->com_status.ix = 0;
                            }
                            break;
                        }
                        case 2:
                            mdsad_info->a_status.wi = 1;
                            break;
                        case 3:
                            mdsad_info->a_status.wi = 0;
                            mdsad_info->a_status.re = 1;
                            mdsad_info->a_status.bd = 1;
                            break;
                        default:
                            break;
                    }
                    break;
                case MDSAD_CMD_RESET_SF:
                    sim_debug(CMD_MSG, &mdsad_dev, "MDSAD: " ADDRESS_FORMAT
                              " CMD=Reset Sector Flag\n", PCX);
                    mdsad_info->com_status.sf = 0;
                    mdsad_info->datacount = 0;
                    break;
                case MDSAD_CMD_INTR_DIS:
                    sim_debug(CMD_MSG, &mdsad_dev, "MDSAD: " ADDRESS_FORMAT
                              " CMD=Disarm Interrupt\n", PCX);
                    mdsad_info->int_enable = 0;
                    break;
                case MDSAD_CMD_INTR_ARM:
                    sim_debug(CMD_MSG, &mdsad_dev, "MDSAD: " ADDRESS_FORMAT
                              " CMD=Arm Interrupt\n", PCX);
                    mdsad_info->int_enable = 1;
                    break;
                case MDSAD_CMD_SET_BODY:
                    sim_debug(CMD_MSG, &mdsad_dev, "MDSAD: " ADDRESS_FORMAT
                              " CMD=Set Body (Diagnostic)\n", PCX);
                    break;
                case MDSAD_CMD_BEGIN_WR:
                    sim_debug(CMD_MSG, &mdsad_dev, "MDSAD: " ADDRESS_FORMAT
                              " CMD=Begin Write\n", PCX);
                    break;
                case MDSAD_CMD_RESET:
                    sim_debug(CMD_MSG, &mdsad_dev, "MDSAD: " ADDRESS_FORMAT
                              " CMD=Reset Controller\n", PCX);
                    mdsad_info->com_status.mo = 0;  /* Turn motors off */
                    break;
                default:
                    sim_debug(CMD_MSG, &mdsad_dev, "MDSAD: " ADDRESS_FORMAT
                              " Unsupported CMD=0x%x\n", PCX, Addr & 0x0F);
                    break;
            }

            /* Set density based on sector length. */
            mdsad_info->com_status.dd = (MDSAD_SECTOR_LEN == MDSAD_SECTOR_LEN_DD) ? 1 : 0;

            cData  = (mdsad_info->com_status.sf & 1) << 7;
            cData |= (mdsad_info->com_status.ix & 1) << 6;
            cData |= (mdsad_info->com_status.dd & 1) << 5;
            cData |= (mdsad_info->com_status.mo & 1) << 4;

            mdsad_info->c_status.sc = pDrive->sector;

            switch( (Addr & 0xF0) >> 4) {
                case MDSAD_A_STATUS:    /* A-STATUS */
                    cData |= (mdsad_info->a_status.wi & 1) << 3;
                    cData |= (mdsad_info->a_status.re & 1) << 2;
                    cData |= (mdsad_info->a_status.sp & 1) << 1;
                    cData |= (mdsad_info->a_status.bd & 1);
                    sim_debug(STATUS_MSG, &mdsad_dev, "MDSAD: " ADDRESS_FORMAT
                              " A-Status = <%s %s %s %s %s %s %s %s>\n",
                              PCX,
                              cData & MDSAD_A_SF ? "SF" : "  ",
                              cData & MDSAD_A_IX ? "IX" : "  ",
                              cData & MDSAD_A_DD ? "DD" : "SD",
                              cData & MDSAD_A_MO ? "MO" : "  ",
                              cData & MDSAD_A_WI ? "WI" : "  ",
                              cData & MDSAD_A_RE ? "RE" : "  ",
                              cData & MDSAD_A_SP ? "SP" : "  ",
                              cData & MDSAD_A_BD ? "BD" : "  ");
                    break;
                case MDSAD_B_STATUS:    /* B-STATUS */
                    cData |= (mdsad_info->b_status.wr & 1) << 3;
                    cData |= (mdsad_info->b_status.sp & 1) << 2;
                    cData |= (mdsad_info->b_status.wp & 1) << 1;
                    cData |= (mdsad_info->b_status.t0 & 1);
                    sim_debug(STATUS_MSG, &mdsad_dev, "MDSAD: " ADDRESS_FORMAT
                              " B-Status = <%s %s %s %s %s %s %s %s>\n",
                              PCX,
                              cData & MDSAD_B_SF ? "SF" : "  ",
                              cData & MDSAD_B_IX ? "IX" : "  ",
                              cData & MDSAD_B_DD ? "DD" : "SD",
                              cData & MDSAD_B_MO ? "MO" : "  ",
                              cData & MDSAD_B_WR ? "WR" : "  ",
                              cData & MDSAD_B_SP ? "SP" : "  ",
                              cData & MDSAD_B_WP ? "WP" : "  ",
                              cData & MDSAD_B_T0 ? "T0" : "  ");
                    break;
                case MDSAD_C_STATUS:    /* C-STATUS */
                    cData |= (mdsad_info->c_status.sc & 0xF);
                    sim_debug(STATUS_MSG, &mdsad_dev, "MDSAD: " ADDRESS_FORMAT
                              " C-Status = <%s %s %s %s %i>\n",
                              PCX,
                              cData & MDSAD_C_SF ? "SF" : "  ",
                              cData & MDSAD_C_IX ? "IX" : "  ",
                              cData & MDSAD_C_DD ? "DD" : "SD",
                              cData & MDSAD_C_MO ? "MO" : "  ",
                              cData & MDSAD_C_SC);
                    break;
                case MDSAD_READ_DATA:   /* READ DATA */
                {
                    if(mdsad_info->datacount == 0) {
                        sim_debug(RD_DATA_MSG, &mdsad_dev, "MDSAD: " ADDRESS_FORMAT
                                  " READ Start:  Drive: %d, Track=%d, Head=%d, Sector=%d\n",
                                  PCX,
                                  mdsad_info->orders.ds,
                                  pDrive->track,
                                  mdsad_info->orders.ss,
                                  pDrive->sector);

                            checksum = 0;

                            sec_offset = calculate_mdsad_sec_offset(pDrive->track,
                                mdsad_info->orders.ss,
                                pDrive->sector);

                            if ((pDrive->uptr == NULL) ||
                                    (pDrive->uptr->fileref == NULL)) {
                                sim_debug(RD_DATA_MSG, &mdsad_dev, "MDSAD: " ADDRESS_FORMAT
                                          " Drive: %d not attached - read ignored.\n",
                                          PCX, mdsad_info->orders.ds);
                                return 0xe5;
                            }

                            switch((pDrive->uptr)->u3)
                            {
                                case IMAGE_TYPE_DSK:
                                    if(pDrive->uptr->fileref == NULL) {
                                        sim_printf(".fileref is NULL!\n");
                                    } else {
                                        if (sim_fseek((pDrive->uptr)->fileref,
                                                      sec_offset, SEEK_SET) == 0) {
                                            rtn = sim_fread(MDSAD_SECTOR_DATA, 1, MDSAD_SECTOR_LEN,
                                                            (pDrive->uptr)->fileref);
                                            if (rtn != MDSAD_SECTOR_LEN) {
                                                sim_debug(ERROR_MSG, &mdsad_dev, "MDSAD: " ADDRESS_FORMAT
                                                          " READ: sim_fread error.\n", PCX);
                                            }
                                        } else {
                                            sim_debug(ERROR_MSG, &mdsad_dev, "MDSAD: " ADDRESS_FORMAT
                                                      " READ: sim_fseek error.\n", PCX);
                                        }
                                    }
                                    break;
                                case IMAGE_TYPE_CPT:
                                    sim_printf("%s: CPT Format not supported\n", __FUNCTION__);
                                    break;
                                default:
                                    sim_printf("%s: Unknown image Format\n", __FUNCTION__);
                                    break;
                            }
                            if(mdsad_dev.dctrl & RD_DATA_DETAIL_MSG)
                                showdata(TRUE);
                    }

                    if(mdsad_info->datacount < MDSAD_SECTOR_LEN) {
                        cData = MDSAD_SECTOR_DATA[mdsad_info->datacount];

                        /* Exclusive OR */
                        checksum ^= cData;
                        /* Rotate Left Circular */
                        checksum = ((checksum << 1) | ((checksum & 0x80) != 0)) & 0xff;

                        DBG_PRINT(("MDSAD: " ADDRESS_FORMAT
                            " READ-DATA[offset:%06x+%03x]=%02x\n",
                            PCX, sec_offset, mdsad_info->datacount, cData));
                    } else { /* checksum */
                        cData = checksum;
                        sim_debug(RD_DATA_DETAIL_MSG, &mdsad_dev, "MDSAD: " ADDRESS_FORMAT
                                  " READ-DATA: Checksum is: 0x%02x\n",
                                  PCX, cData);
                    }

                    mdsad_info->datacount++;
                    break;
                }
                default:
                    DBG_PRINT(("MDSAD: " ADDRESS_FORMAT
                        " Invalid DM=%x\n", PCX, Addr & 0xF));
                    break;
            }

            break;
    }
    return (cData);
}
