/*************************************************************************
 *                                                                       *
 * $Id: s100_mdsad.c 1995 2008-07-15 03:59:13Z hharte $                  *
 *                                                                       *
 * Copyright (c) 2007-2008 Howard M. Harte.                              *
 * http://www.hartetec.com                                               *
 *                                                                       *
 * 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 HOWARD M. HARTE 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 name of Howard M. Harte shall *
 * not be used in advertising or otherwise to promote the sale, use or   *
 * other dealings in this Software without prior written authorization   *
 * Howard M. Harte.                                                      *
 *                                                                       *
 * SIMH Interface based on altairz80_hdsk.c, by Peter Schorn.            *
 *                                                                       *
 * Module Description:                                                   *
 *     Northstar MDS-AD Disk Controller module for SIMH                  *
 * Only Double-Density is supported for now.                             *
 *                                                                       *
 * Environment:                                                          *
 *     User mode only                                                    *
 *                                                                       *
 *************************************************************************/

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

#if defined (_WIN32)
#include <windows.h>
#endif

#ifdef DBG_MSG
#define DBG_PRINT(args) 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 VERBOSE_MSG (1 << 7)
#define RD_DATA_DETAIL_MSG  (1 << 8)
#define WR_DATA_DETAIL_MSG  (1 << 9)

extern uint32 PCX;
extern t_stat set_membase(UNIT *uptr, int32 val, char *cptr, void *desc);
extern t_stat show_membase(FILE *st, UNIT *uptr, int32 val, void *desc);
extern uint32 sim_map_resource(uint32 baseaddr, uint32 size, uint32 resource_type,
        int32 (*routine)(const int32, const int32, const int32), uint8 unmap);
extern REG *sim_PC;

#define MDSAD_MAX_DRIVES        4
#define MDSAD_SECTOR_LEN        512
#define MDSAD_SECTORS_PER_TRACK 10
#define MDSAD_TRACKS            35
#define MDSAD_RAW_LEN           (32 + 2 + MDSAD_SECTOR_LEN + 1)

typedef union {
    struct {
        uint8 zeros[32];
        uint8 sync[2];
        uint8 data[MDSAD_SECTOR_LEN];
        uint8 checksum;
    } u;
    uint8 raw[MDSAD_RAW_LEN];

} SECTOR_FORMAT;

typedef struct {
    UNIT *uptr;
    uint8 track;
    uint8 wp;       /* Disk write protected */
    uint8 sector;   /* Current Sector number */
    uint32 sector_wait_count;
} 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 procompensation is invoked if and only if DP=1. */
    uint8 st;       /* controls the level of the head step signal to the disk drives. */
    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_WLK        (UNIT_V_UF + 0) /* write locked                                 */
#define UNIT_MDSAD_WLK          (1 << UNIT_V_MDSAD_WLK)
#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)    /* Default North Star Disk Capacity */
#define IMAGE_TYPE_DSK          1               /* Flat binary "DSK" image file.                */
#define IMAGE_TYPE_CPT          3               /* CP/M Transfer "CPT" image file.              */

/* 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, 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 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 }
};

static MTAB mdsad_mod[] = {
    { MTAB_XTD|MTAB_VDV,    0,                  "MEMBASE",  "MEMBASE",  &set_membase, &show_membase, NULL },
    { UNIT_MDSAD_WLK,       0,                  "WRTENB",   "WRTENB",   NULL },
    { UNIT_MDSAD_WLK,       UNIT_MDSAD_WLK,     "WRTLCK",   "WRTLCK",   NULL },
    /* quiet, no warning messages       */
    { UNIT_MDSAD_VERBOSE,   0,                  "QUIET",    "QUIET",    NULL },
    /* verbose, show warning messages   */
    { UNIT_MDSAD_VERBOSE,   UNIT_MDSAD_VERBOSE, "VERBOSE",  "VERBOSE",  NULL },
    { 0 }
};

#define TRACE_PRINT(level, args)    if(mdsad_dev.dctrl & level) {   \
                                       printf args;                 \
                                    }

/* Debug Flags */
static DEBTAB mdsad_dt[] = {
    { "ERROR",  ERROR_MSG },
    { "SEEK",   SEEK_MSG },
    { "CMD",    CMD_MSG },
    { "RDDATA", RD_DATA_MSG },
    { "WRDATA", WR_DATA_MSG },
    { "STATUS", STATUS_MSG },
    { "ORDERS", ORDERS_MSG },
    { "RDDETAIL", RD_DATA_DETAIL_MSG },
    { "WRDETAIL", WR_DATA_DETAIL_MSG },
    { "VERBOSE",VERBOSE_MSG },
    { 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, "North Star Floppy Controller MDSAD"
};

/* Reset routine */
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, TRUE);
    } else {
        /* Connect MDSAD at base address */
        if(sim_map_resource(pnp->mem_base, pnp->mem_size,
            RESOURCE_TYPE_MEMORY, &mdsaddev, FALSE) != 0) {
            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 */
t_stat mdsad_attach(UNIT *uptr, 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) {
            break;
        }
    }

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

    if(uptr->capac > 0) {
        fgets(header, 4, uptr->fileref);
        if(!strcmp(header, "CPT")) {
            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)
        printf("MDSAD%d, attached to '%s', type=%s, len=%d\n", i, cptr,
            uptr->u3 == uptr->u3 == IMAGE_TYPE_CPT ? "CPT" : "DSK",
            uptr->capac);

    return SCPE_OK;
}


/* Detach routine */
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" NLP,
        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." NLP, 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;
    printf("MDSAD: " ADDRESS_FORMAT " %s Sector =" NLP "\t", PCX, isRead ? "Read" : "Write");
    for(i=0; i < MDSAD_SECTOR_LEN; i++) {
        printf("%02X ", sdata.u.data[i]);
        if(((i+1) & 0xf) == 0)
            printf(NLP "\t");
    }
    printf(NLP);
}

static int checksum;
static uint32 sec_offset;

static uint32 calculate_mdsad_sec_offset(uint8 track, uint8 head, uint8 sector)
{
    if(mdsad_info->orders.ss == 0) {
        return ((track * (MDSAD_SECTOR_LEN * MDSAD_SECTORS_PER_TRACK)) + (sector * MDSAD_SECTOR_LEN));
    } else {
        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;

    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:
        {
            if(mdsad_info->datacount == 0) {
                TRACE_PRINT(WR_DATA_MSG, ("MDSAD: " ADDRESS_FORMAT
                    " WRITE Start:  Drive: %d, Track=%d, Head=%d, Sector=%d" NLP,
                    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" NLP,
                PCX, sec_offset, mdsad_info->datacount, Addr & 0xFF));
            mdsad_info->datacount++;
            if(mdsad_info->datacount < MDSAD_RAW_LEN)
                sdata.raw[mdsad_info->datacount] = Addr & 0xFF;

            if(mdsad_info->datacount == (MDSAD_RAW_LEN - 1)) {
                TRACE_PRINT(WR_DATA_MSG, ("MDSAD: " ADDRESS_FORMAT
                    " Write Complete" NLP, PCX));

                if ((pDrive->uptr == NULL) || (pDrive->uptr->fileref == NULL)) {
                    TRACE_PRINT(WR_DATA_MSG, ("MDSAD: " ADDRESS_FORMAT
                        " Drive: %d not attached - write ignored." NLP,
                        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) {
                            printf(".fileref is NULL!" NLP);
                        } else {
                            sim_fseek((pDrive->uptr)->fileref, sec_offset, SEEK_SET);
                            fwrite(sdata.u.data, MDSAD_SECTOR_LEN, 1,
                                (pDrive->uptr)->fileref);
                        }
                        break;
                    case IMAGE_TYPE_CPT:
                        printf("%s: CPT Format not supported" NLP, __FUNCTION__);
                        break;
                    default:
                        printf("%s: Unknown image Format" NLP, __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.st = (Addr & 0x10) >> 4;
            mdsad_info->orders.ds = (Addr & 0x0F);

            ds = mdsad_info->orders.ds;
            switch(mdsad_info->orders.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)) {
                TRACE_PRINT(ERROR_MSG, ("MDSAD: " ADDRESS_FORMAT
                    " Controller Orders update drive %x" NLP, PCX, mdsad_info->orders.ds));
                mdsad_info->orders.ds &= 0x03;
            }
            TRACE_PRINT(ORDERS_MSG, ("MDSAD: " ADDRESS_FORMAT
                " Controller Orders: Drive=%x[%x], DD=%d, SS=%d, DP=%d, ST=%d" NLP,
                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];

            if(mdsad_info->orders.st == 1) {
                if(mdsad_info->orders.dp == 0) {
                    TRACE_PRINT(SEEK_MSG, ("MDSAD: " ADDRESS_FORMAT
                        " Step out: Track=%d%s" NLP, PCX, pDrive->track,
                        pDrive->track == 0 ? "[Warn: already at 0]" : ""));
                    if(pDrive->track > 0) /* anything to do? */
                        pDrive->track--;
                } else {
                    TRACE_PRINT(SEEK_MSG, ("MDSAD: " ADDRESS_FORMAT
                        " Step  in: Track=%d%s" NLP, 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:
/*          TRACE_PRINT(CMD_MSG, ("MDSAD: " ADDRESS_FORMAT " DM=%x" NLP, PCX, (Addr & 0xF0) >> 4)); */
            switch(Addr & 0x0F) {
                case MDSAD_CMD_MOTORS_ON:
                    TRACE_PRINT(CMD_MSG, ("MDSAD: " ADDRESS_FORMAT
                        " CMD=Motors On" NLP, 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.wi = 0;
                            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.re = 1;
                            mdsad_info->a_status.bd = 1;
                            break;
                        default:
                            break;
                    }
                    break;
                case MDSAD_CMD_RESET_SF:
                    TRACE_PRINT(CMD_MSG, ("MDSAD: " ADDRESS_FORMAT
                        " CMD=Reset Sector Flag" NLP, PCX));
                    mdsad_info->com_status.sf = 0;
                    mdsad_info->datacount = 0;
                    break;
                case MDSAD_CMD_INTR_DIS:
                    TRACE_PRINT(CMD_MSG, ("MDSAD: " ADDRESS_FORMAT
                        " CMD=Disarm Interrupt" NLP, PCX));
                    mdsad_info->int_enable = 0;
                    break;
                case MDSAD_CMD_INTR_ARM:
                    TRACE_PRINT(CMD_MSG, ("MDSAD: " ADDRESS_FORMAT
                        " CMD=Arm Interrupt" NLP, PCX));
                    mdsad_info->int_enable = 1;
                    break;
                case MDSAD_CMD_SET_BODY:
                    TRACE_PRINT(CMD_MSG, ("MDSAD: " ADDRESS_FORMAT
                        " CMD=Set Body (Diagnostic)" NLP, PCX));
                    break;
                case MDSAD_CMD_BEGIN_WR:
                    TRACE_PRINT(CMD_MSG, ("MDSAD: " ADDRESS_FORMAT
                        " CMD=Begin Write" NLP, PCX));
                    break;
                case MDSAD_CMD_RESET:
                    TRACE_PRINT(CMD_MSG, ("MDSAD: " ADDRESS_FORMAT
                        " CMD=Reset Controller" NLP, PCX));
                    mdsad_info->com_status.mo = 0;  /* Turn motors off */
                    break;
                default:
                    TRACE_PRINT(CMD_MSG, ("MDSAD: " ADDRESS_FORMAT
                        " Unsupported CMD=0x%x" NLP, PCX, Addr & 0x0F));
                    break;
            }

            /* Always Double-Density for now... */
            mdsad_info->com_status.dd = 1;

            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);
                    TRACE_PRINT(STATUS_MSG, ("MDSAD: " ADDRESS_FORMAT
                        " A-Status = <%s %s %s %s %s %s %s %s>" NLP, PCX,
                        cData & MDSAD_A_SF ? "SF" : "  ",
                        cData & MDSAD_A_IX ? "IX" : "  ",
                        cData & MDSAD_A_DD ? "DD" : "  ",
                        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);
                    TRACE_PRINT(STATUS_MSG, ("MDSAD: " ADDRESS_FORMAT
                        " B-Status = <%s %s %s %s %s %s %s %s>" NLP, PCX,
                        cData & MDSAD_B_SF ? "SF" : "  ",
                        cData & MDSAD_B_IX ? "IX" : "  ",
                        cData & MDSAD_B_DD ? "DD" : "  ",
                        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);
                    TRACE_PRINT(STATUS_MSG, ("MDSAD: " ADDRESS_FORMAT
                        " C-Status = <%s %s %s %s %i>" NLP, PCX,
                        cData & MDSAD_C_SF ? "SF" : "  ",
                        cData & MDSAD_C_IX ? "IX" : "  ",
                        cData & MDSAD_C_DD ? "DD" : "  ",
                        cData & MDSAD_C_MO ? "MO" : "  ", cData & MDSAD_C_SC));
                    break;
                case MDSAD_READ_DATA:   /* READ DATA */
                {
                    if(mdsad_info->datacount == 0) {
                        TRACE_PRINT(RD_DATA_MSG, ("MDSAD: " ADDRESS_FORMAT
                            " READ Start:  Drive: %d, Track=%d, Head=%d, Sector=%d" NLP,
                            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)) {
                                TRACE_PRINT(RD_DATA_MSG, ("MDSAD: " ADDRESS_FORMAT
                                    " Drive: %d not attached - read ignored." NLP,
                                    PCX, mdsad_info->orders.ds));
                                return 0xe5;
                            }

                            switch((pDrive->uptr)->u3)
                            {
                                case IMAGE_TYPE_DSK:
                                    if(pDrive->uptr->fileref == NULL) {
                                        printf(".fileref is NULL!" NLP);
                                    } else {
                                        sim_fseek((pDrive->uptr)->fileref,
                                            sec_offset, SEEK_SET);
                                        fread(&sdata.u.data[0], MDSAD_SECTOR_LEN,
                                            1, (pDrive->uptr)->fileref);
                                    }
                                    break;
                                case IMAGE_TYPE_CPT:
                                    printf("%s: CPT Format not supported"
                                        NLP, __FUNCTION__);
                                    break;
                                default:
                                    printf("%s: Unknown image Format"
                                        NLP, __FUNCTION__);
                                    break;
                            }
                            if(mdsad_dev.dctrl & RD_DATA_DETAIL_MSG)
                                showdata(TRUE);
                    }

                    if(mdsad_info->datacount < MDSAD_SECTOR_LEN) {
                        cData = sdata.u.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" NLP,
                            PCX, sec_offset, mdsad_info->datacount, cData));
                    } else { /* checksum */
                        cData = checksum;
                        TRACE_PRINT(RD_DATA_MSG, ("MDSAD: " ADDRESS_FORMAT
                            " READ-DATA: Checksum is: 0x%02x" NLP,
                            PCX, cData));
                    }

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

            break;
    }
    return (cData);
}
