/* vax4xx_rz80.c: NCR 5380 SCSI controller

   Copyright (c) 2019, Matt Burke

   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
   THE AUTHOR 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 the author 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 author.

   rz           SCSI controller
*/

#include "vax_defs.h"
#include "sim_scsi.h"
#include "vax_rzdev.h"

#if defined(VAX_420)
#include "vax_ka420_rzrz_bin.h"
#else
#define BOOT_CODE_ARRAY NULL
#define BOOT_CODE_SIZE  0
#endif

#define RZ_NUMCT        2
#define RZ_NUMDR        8
#define RZ_CTLR         (RZ_NUMDR)

#define iflgs           u3
#define cnum            u4

/* Mode Register */

#define MODE_BLOCK      0x80                            /* DMA block mode */
#define MODE_TARG       0x40                            /* Target role */
#define MODE_PARCK      0x20                            /* Parity check enable */
#define MODE_INTPAR     0x10                            /* Interrupt on parity error */
#define MODE_INTEOP     0x08                            /* Interrupt on DMA end */
#define MODE_MONBSY     0x04                            /* Monitor BSY */
#define MODE_DMA        0x02                            /* Enable DMA transfer */
#define MODE_ARB        0x01                            /* Start arbitration */

/* Initiator Command Register */

#define ICMD_RST        0x80                            /* Assert RST */
#define ICMD_AIP        0x40                            /* Arbitration in progress */
#define ICMD_TEST       0x40                            /* Test mode */
#define ICMD_LA         0x20                            /* Lost arbitration */
#define ICMD_DIFF       0x20                            /* Differential enable */
#define ICMD_ACK        0x10                            /* Assert ACK */
#define ICMD_BSY        0x08                            /* Assert BSY */
#define ICMD_SEL        0x04                            /* Assert SEL */
#define ICMD_ATN        0x02                            /* Assert ATN */
#define ICMD_ENOUT      0x01                            /* Enable output */

/* Target Command Register */

#define TCMD_REQ        0x08                            /* Assert REQ */
#define TCMD_MSG        0x04                            /* Assert MSG */
#define TCMD_CD         0x02                            /* Assert C/D */
#define TCMD_IO         0x01                            /* Assert I/O */
#define TCMD_PHASE      0x07                            /* Phase bits */

/* Bus and Status Register */

#define STS_DMAEND      0x80                            /* DMA end */
#define STS_DMAREQ      0x40                            /* DMA request */
#define STS_PARERR      0x20                            /* Parity error */
#define STS_INTREQ      0x10                            /* Interrupt request */
#define STS_MATCH       0x08                            /* Phase match */
#define STS_BSYERR      0x04                            /* Busy error */
#define STS_ATN         0x02                            /* ATN asserted */
#define STS_ACK         0x01                            /* ACK asserted */

/* Current Bus Status Register */

#define CSTAT_RST       0x80                            /* RST asserted */
#define CSTAT_BSY       0x40                            /* BSY asserted */
#define CSTAT_REQ       0x20                            /* REQ asserted */
#define CSTAT_MSG       0x10                            /* MSG asserted */
#define CSTAT_CD        0x08                            /* C/D asserted */
#define CSTAT_IO        0x04                            /* I/O asserted */
#define CSTAT_SEL       0x02                            /* SEL asserted */
#define CSTAT_DBP       0x01                            /* Databus parity */
#define CSTAT_V_PHASE   2
#define CSTAT_M_PHASE   0x07                            /* Phase bits */

#define DBG_REG         0x0001                          /* registers */
#define DBG_CMD         0x0002                          /* display commands */
#define DBG_INT         0x0004                          /* display transfer requests */
#define DBG_DSK         0x0008                          /* display sim_disk activities */

#define PH_DATA_OUT     0
#define PH_DATA_IN      1
#define PH_COMMAND      2
#define PH_STATUS       3
#define PH_MSG_OUT      6
#define PH_MSG_IN       7

#define UA_SELECT       0

#define UNIT_V_DTYPE    (SCSI_V_UF + 0)                 /* drive type */
#define UNIT_M_DTYPE    0x1F
#define UNIT_DTYPE      (UNIT_M_DTYPE << UNIT_V_DTYPE)
#define GET_DTYPE(x)    (((x) >> UNIT_V_DTYPE) & UNIT_M_DTYPE)

typedef struct {
    uint32 cnum;                                        /* ctrl number */
    uint8 odata;                                        /* output data */
    uint8 cdata;                                        /* current data */
    uint32 mode;                                        /* mode reg */
    uint32 icmd;                                        /* initiator cmd reg */
    uint32 tcmd;                                        /* target cmd reg */
    uint32 status;                                      /* status reg */
    uint32 cstat;
    uint32 selen;                                       /* select enable reg */
    uint32 dcount;                                      /* DMA count reg */
    uint32 daddr;                                       /* DMA addr reg */
    t_bool daddr_low;                                   /* DMA addr flag */
    uint32 ddir;                                        /* DMA dir */
    uint8 *buf;                                         /* unit buffer */
    int32 buf_ptr;                                      /* current buffer pointer */
    int32 buf_len;                                      /* current buffer length */
    SCSI_BUS bus;                                       /* SCSI bus state */
    } CTLR;

t_stat rz_svc (UNIT *uptr);
t_stat rz_isvc (UNIT *uptr);
t_stat rz_reset (DEVICE *dptr);
t_stat rz_attach (UNIT *uptr, CONST char *cptr);
t_stat rz_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr);
t_stat rz_set_type (UNIT *uptr, int32 val, CONST char *cptr, void *desc);
t_stat rz_show_type (FILE *st, UNIT *uptr, int32 val, CONST void *desc);
void rz_update_status (CTLR *rz);
void rz_setint (CTLR *rz, uint32 flags);
void rz_clrint (CTLR *rz);
void rz_sw_reset (CTLR *rz);
void rz_ack (CTLR *rz);
int32 rz_parity (int32 val, int32 odd);
const char *rz_description (DEVICE *dptr);

/* RZ data structures

   rz_dev      RZ device descriptor
   rz_unit     RZ unit descriptor
   rz_reg      RZ register list
*/

CTLR rz_ctx = { 0 };

UNIT rz_unit[] = {
    { UDATA (&rz_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_ROABLE+
            (RZ23_DTYPE << UNIT_V_DTYPE), RZ_SIZE (RZ23)) },
    { UDATA (&rz_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_ROABLE+
            (RZ23_DTYPE << UNIT_V_DTYPE), RZ_SIZE (RZ23)) },
    { UDATA (&rz_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_ROABLE+
            (RZ23_DTYPE << UNIT_V_DTYPE), RZ_SIZE (RZ23)) },
    { UDATA (&rz_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_ROABLE+
            (RZ23_DTYPE << UNIT_V_DTYPE), RZ_SIZE (RZ23)) },
    { UDATA (&rz_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_ROABLE+
            (RZ23_DTYPE << UNIT_V_DTYPE), RZ_SIZE (RZ23)) },
    { UDATA (&rz_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_ROABLE+
            (RZ23_DTYPE << UNIT_V_DTYPE), RZ_SIZE (RZ23)) },
    { UDATA (&rz_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_ROABLE+
            (RZ23_DTYPE << UNIT_V_DTYPE), RZ_SIZE (RZ23)) },
    { UDATA (&rz_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_ROABLE+
            (RZ23_DTYPE << UNIT_V_DTYPE), RZ_SIZE (RZ23)) },
    { UDATA (&rz_isvc, UNIT_DIS, 0) }
    };

REG rz_reg[] = {
    { FLDATAD ( INT,    int_req[IPL_SCA], INT_V_SCA, "interrupt pending flag") },
    { NULL }
    };

DEBTAB rz_debug[] = {
    { "REG",  DBG_REG,      "Register activity" },
    { "CMD",  DBG_CMD,      "Chip commands" },
    { "INT",  DBG_INT,      "Interrupts" },
    { "SCMD", SCSI_DBG_CMD, "SCSI commands" },
    { "SMSG", SCSI_DBG_MSG, "SCSI messages" },
    { "SBUS", SCSI_DBG_BUS, "SCSI bus activity" },
    { "SDSK", SCSI_DBG_DSK, "SCSI disk activity" },
    { "STAP", SCSI_DBG_TAP, "SCSI tape activity" },
    { 0 }
};

MTAB rz_mod[] = {
    { MTAB_XTD|MTAB_VUN, 0, "write enabled", "WRITEENABLED", 
        &scsi_set_wlk, &scsi_show_wlk,   NULL, "Write enable drive" },
    { MTAB_XTD|MTAB_VUN, 1, NULL, "LOCKED", 
        &scsi_set_wlk, NULL,   NULL, "Write lock drive" },
    { MTAB_XTD|MTAB_VUN, RZ23_DTYPE, NULL, "RZ23",
      &rz_set_type, NULL, NULL, "Set RZ23 Disk Type" },
    { MTAB_XTD|MTAB_VUN, RZ23L_DTYPE, NULL, "RZ23L",
      &rz_set_type, NULL, NULL, "Set RZ23L Disk Type" },
    { MTAB_XTD|MTAB_VUN, RZ24_DTYPE, NULL, "RZ24",
      &rz_set_type, NULL, NULL, "Set RZ24 Disk Type" },
    { MTAB_XTD|MTAB_VUN, RZ24L_DTYPE, NULL, "RZ24L",
      &rz_set_type, NULL, NULL, "Set RZ24L Disk Type" },
    { MTAB_XTD|MTAB_VUN, RZ25_DTYPE, NULL, "RZ25",
      &rz_set_type, NULL, NULL, "Set RZ25 Disk Type" },
    { MTAB_XTD|MTAB_VUN, RZ25L_DTYPE, NULL, "RZ25L",
      &rz_set_type, NULL, NULL, "Set RZ25L Disk Type" },
    { MTAB_XTD|MTAB_VUN, RZ26_DTYPE, NULL, "RZ26",
      &rz_set_type, NULL, NULL, "Set RZ26 Disk Type" },
    { MTAB_XTD|MTAB_VUN, RZ26L_DTYPE, NULL, "RZ26L",
      &rz_set_type, NULL, NULL, "Set RZ26L Disk Type" },
    { MTAB_XTD|MTAB_VUN, RZ55_DTYPE, NULL, "RZ55",
      &rz_set_type, NULL, NULL, "Set RZ55 Disk Type" },
    { MTAB_XTD|MTAB_VUN, RRD40_DTYPE, NULL, "CDROM",
      &rz_set_type, NULL, NULL, "Set RRD40 Disk Type" },
    { MTAB_XTD|MTAB_VUN, RRD40_DTYPE, NULL, "RRD40",
      &rz_set_type, NULL, NULL, "Set RRD40 Disk Type" },
    { MTAB_XTD|MTAB_VUN, RRD42_DTYPE, NULL, "RRD42",
      &rz_set_type, NULL, NULL, "Set RRD42 Disk Type" },
    { MTAB_XTD|MTAB_VUN, RRW11_DTYPE, NULL, "RRW11",
      &rz_set_type, NULL, NULL, "Set RRW11 Disk Type" },
    { MTAB_XTD|MTAB_VUN, CDW900_DTYPE, NULL, "CDW900",
      &rz_set_type, NULL, NULL, "Set SONY CDW-900E Disk Type" },
    { MTAB_XTD|MTAB_VUN, XR1001_DTYPE, NULL, "XR1001",
      &rz_set_type, NULL, NULL, "Set JVC XR-W1001 Disk Type" },
    { MTAB_XTD|MTAB_VUN, TZK50_DTYPE, NULL, "TZK50",
      &rz_set_type, NULL, NULL, "Set DEC TZK50 Tape Type" },
    { MTAB_XTD|MTAB_VUN, TZ30_DTYPE, NULL, "TZ30",
      &rz_set_type, NULL, NULL, "Set DEC TZ30 Tape Type" },
    { MTAB_XTD|MTAB_VUN|MTAB_VALR, RZU_DTYPE, NULL, "RZUSER",
      &rz_set_type, NULL, NULL, "Set RZUSER=size Disk Type" },
    { MTAB_XTD|MTAB_VUN, 0, "TYPE", NULL,
      NULL, &rz_show_type, NULL, "Display device type" },
    { SCSI_NOAUTO, SCSI_NOAUTO, "noautosize", "NOAUTOSIZE", NULL, NULL, NULL, "Disables disk autosize on attach" },
    { SCSI_NOAUTO,           0, "autosize",   "AUTOSIZE",   NULL, NULL, NULL, "Enables disk autosize on attach" },
    { MTAB_XTD|MTAB_VUN, 0, "FORMAT", "FORMAT",
      &scsi_set_fmt, &scsi_show_fmt, NULL, "Set/Display unit format" },
    { 0 }
    };

static const char *drv_types[] = {
    "RZ23",
    "RZ23L",
    "RZ24",
    "RZ24L",
    "RZ25",
    "RZ25L",
    "RZ26",
    "RZ26L",
    "RZ55",
    "CDROM",
    "RRD40",
    "RRD42",
    "RRW11",
    "CDW900",
    "XR1001",
    "TZK50",
    "TZ30",
    "RZUSER"
    };

DEVICE rz_dev = {
    "RZ", rz_unit, rz_reg, rz_mod,
    RZ_NUMDR + 1, DEV_RDX, 31, 1, DEV_RDX, 8,
    NULL, NULL, &rz_reset,
    NULL, &rz_attach, &scsi_detach,
    NULL, DEV_DEBUG | DEV_DISK | DEV_SECTORS | RZ_FLAGS,
    0, rz_debug, NULL, NULL, &rz_help, NULL, NULL,
    &rz_description
    };

/* RZB data structures

   rzb_dev      RZB device descriptor
   rzb_unit     RZB unit descriptor
   rzb_reg      RZB register list
*/

CTLR rzb_ctx = { 1 };

DIB rzb_dib = {
    RZ_ROM_INDEX, BOOT_CODE_ARRAY, BOOT_CODE_SIZE
    };

UNIT rzb_unit[] = {
    { UDATA (&rz_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_ROABLE+
            (RZ23_DTYPE << UNIT_V_DTYPE), RZ_SIZE (RZ23)) },
    { UDATA (&rz_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_ROABLE+
            (RZ23_DTYPE << UNIT_V_DTYPE), RZ_SIZE (RZ23)) },
    { UDATA (&rz_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_ROABLE+
            (RZ23_DTYPE << UNIT_V_DTYPE), RZ_SIZE (RZ23)) },
    { UDATA (&rz_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_ROABLE+
            (RZ23_DTYPE << UNIT_V_DTYPE), RZ_SIZE (RZ23)) },
    { UDATA (&rz_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_ROABLE+
            (RZ23_DTYPE << UNIT_V_DTYPE), RZ_SIZE (RZ23)) },
    { UDATA (&rz_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_ROABLE+
            (RZ23_DTYPE << UNIT_V_DTYPE), RZ_SIZE (RZ23)) },
    { UDATA (&rz_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_ROABLE+
            (RZ23_DTYPE << UNIT_V_DTYPE), RZ_SIZE (RZ23)) },
    { UDATA (&rz_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_ROABLE+
            (RZ23_DTYPE << UNIT_V_DTYPE), RZ_SIZE (RZ23)) },
    { UDATA (&rz_isvc, UNIT_DIS, 0) }
    };

REG rzb_reg[] = {
    { FLDATAD ( INT,    int_req[IPL_SCB], INT_V_SCB, "interrupt pending flag") },
    { NULL }
    };

DEVICE rzb_dev = {
    "RZB", rzb_unit, rzb_reg, rz_mod,
    RZ_NUMDR + 1, DEV_RDX, 31, 1, DEV_RDX, 8,
    NULL, NULL, &rz_reset,
    NULL, &rz_attach, &scsi_detach,
    &rzb_dib, DEV_DEBUG | DEV_DISK | DEV_SECTORS | RZB_FLAGS,
    0, rz_debug, NULL, NULL,
    &rz_help, NULL                                      /* help and attach_help routines */
    };

static DEVICE *rz_devmap[RZ_NUMCT] = {
    &rz_dev, &rzb_dev
    };

static CTLR *rz_ctxmap[RZ_NUMCT] = {
    &rz_ctx, &rzb_ctx
    };

/* RZ routines

   rz_rd       I/O page read
   rz_wr       I/O page write
   rz_svc      process event
   rz_reset    process reset
*/

int32 rz_rd (int32 pa)
{
int32 ctlr = (pa >> 8) & 1;
CTLR *rz = rz_ctxmap[ctlr];
DEVICE *dptr = rz_devmap[ctlr];
int32 rg = (pa >> 2) & 0x1F;
int32 data = 0;
int32 len;

if (dptr->flags & DEV_DIS)                              /* disabled? */
    return 0;

switch (rg) {

    case 0:                                             /* SCS_CUR_DATA */
        if ((rz->icmd & ICMD_ENOUT) || (rz->icmd & ICMD_AIP)) /* initiator controlling bus */
            data = rz->odata;
        else if (rz->bus.target >= 0) {
            len = scsi_read (&rz->bus, &rz->cdata, 0);  /* receive current byte */
            data = rz->cdata;
            }
        else {                                          /* bus idle */
            data = 0;
            if (rz->mode & MODE_PARCK) {                /* parity checking enabled? */
                if (rz->mode & MODE_INTPAR) {           /* interrupt too? */
                    sim_debug (DBG_INT, dptr, "Delayed: Parity Error\n");
                    rz_setint (rz, STS_PARERR);
                    }
                else
                    rz->status = rz->status | STS_PARERR; /* signal parity error */
                }
            }
        break;

    case 1:                                             /* SCS_INI_CMD */
        data = rz->icmd;
        break;

    case 2:                                             /* SCS_MODE */
        data = rz->mode;
        break;

    case 3:                                             /* SCS_TAR_CMD */
        data = rz->tcmd;
        break;

    case 4:                                             /* SCS_CUR_STAT */
        if (rz->icmd & ICMD_RST)
            data = CSTAT_RST;
        else {
            if (rz->mode & MODE_TARG)                   /* target mode? */
                data = ((rz->tcmd & 0xF) << CSTAT_V_PHASE);
            else {                                      /* initiator mode? */
                data = (rz->bus.phase << CSTAT_V_PHASE);
                if (rz->icmd & ICMD_SEL)
                    data = data | CSTAT_SEL;
                if (rz->bus.target >= 0)                /* target selected? */
                    data = data | CSTAT_BSY;
                if ((rz->bus.req) && ((rz->icmd & ICMD_ACK) == 0))
                    data = data | CSTAT_REQ;
                }
            if (rz->icmd & ICMD_ENOUT)
                data = data | (rz_parity (rz->odata, 1));
            else if (rz->bus.target >= 0) {
                len = scsi_read (&rz->bus, &rz->cdata, 0);  /* receive current byte */
                data = data | (rz_parity (rz->cdata, 1));
                }
            }
        break;

    case 5:                                             /* SCS_STATUS */
        rz->status = rz->status & ~(STS_ACK | STS_ATN | STS_MATCH);
        if ((rz->icmd & ICMD_RST) == 0) {
            if ((rz->mode & MODE_TARG) == 0) {          /* initiator mode */
                if (rz->icmd & ICMD_ACK)
                    rz->status = rz->status | STS_ACK;
                if (rz->bus.atn)
                    rz->status = rz->status | STS_ATN;
                if ((rz->tcmd & TCMD_PHASE) == rz->bus.phase) /* bus phase match? */
                    rz->status = rz->status | STS_MATCH;
                }
            }
        data = rz->status;
        break;

    case 6:                                             /* SCS_IN_DATA */
        if (rz->bus.target >= 0) {                      /* target selected? */
            len = scsi_read (&rz->bus, &rz->cdata, 0);  /* receive current byte */
            data = rz->cdata;
            }
        else data = 0;
        break;

    case 7:                                             /* SCS_RESET */
        rz->status = rz->status & ~(STS_INTREQ | STS_PARERR | STS_BSYERR);
        rz_clrint (rz);
        data = 0;
        break;

    case 8:                                             /* SCD_ADR */
        data = rz->daddr & DCNT_MASK;
        break;

    case 16:                                            /* SCD_CNT */
        data = rz->dcount & DCNT_MASK;
        break;

    case 17:                                            /* SCD_DIR */
        data = rz->ddir;
        break;

    default:
        data = 0;
        break;
        }

sim_debug (DBG_REG, dptr, "reg %d read, value = %X, PC = %08X\n", rg, data, fault_PC);
return data;
}

void rz_wr (int32 pa, int32 data, int32 access)
{
int32 ctlr = (pa >> 8) & 1;
CTLR *rz = rz_ctxmap[ctlr];
DEVICE *dptr = rz_devmap[ctlr];
UNIT *uptr = dptr->units + RZ_CTLR;
int32 rg = (pa >> 2) & 0x1F;
int32 i;

if (dptr->flags & DEV_DIS)                              /* disabled? */
    return;

switch (rg) {

    case 0:                                             /* SCS_OUT_DATA */
        rz->odata = data;
        break;

    case 1:                                             /* SCS_INI_CMD */
        if ((rz->mode & MODE_TARG) == 0) {              /* initiator mode */
            if ((data ^ rz->icmd) & ICMD_ATN) {
                if (data & ICMD_ATN)                    /* setting ATN */
                    scsi_set_atn (&rz->bus);
                else                                    /* clearing ATN */
                    scsi_release_atn (&rz->bus);
                }
            if ((data ^ rz->icmd) & ICMD_ACK) {
                if (data & ICMD_ACK) {                  /* setting ACK */
                    if (rz->bus.target >= 0)
                        rz_ack (rz);
                    }
                }
            }
        if (data & ICMD_ENOUT) {
            if ((data & ICMD_SEL) && (rz->bus.target < 0)) { /* setting SEL */
                if ((rz->odata == rz->selen) && (rz->selen != 0)) {
                    rz_setint (rz, 0);              /* selecting ourselves */
                    break;
                    }
                for (i = 0; i < RZ_NUMDR; i++) {
                    if ((rz->odata & (1u << i)) && (i != RZ_SCSI_ID)) {
                        scsi_select (&rz->bus, i);
                        break;
                        }
                    }
                }
            }
#if 0
        if ((data ^ rz->icmd) & ICMD_SEL) {
            if ((data & ICMD_SEL) == 0) {               /* clearing SEL */
                if (rz->bus.target < 0)
                    scsi_release (&rz->bus);
                }
            }
#endif
        if ((data ^ rz->icmd) & ICMD_RST) {
            if (data & ICMD_RST) {                      /* setting RST */
                rz_sw_reset (rz);
                rz->icmd = ICMD_RST;
                rz->status = STS_INTREQ;
                sim_debug (DBG_INT, dptr, "Delayed: Bus reset asserted\n");
                rz_setint (rz, 0);
                }
            else {                                      /* clearing RST */
                rz->icmd = data;
                sim_debug (DBG_INT, dptr, "Delayed: Bus reset cleared\n");
                rz_setint (rz, 0);
                }
            }
        else rz->icmd = data;
        break;

    case 2:                                             /* SCS_MODE */
        if (data & MODE_ARB) {                          /* start arbitration */
            rz->status = rz->status & ~(STS_INTREQ | STS_PARERR | STS_BSYERR);
            rz_clrint (rz);
            if (scsi_arbitrate (&rz->bus, RZ_SCSI_ID)) {
                rz->icmd = rz->icmd | ICMD_AIP;
                rz->icmd = rz->icmd & ~ICMD_LA;
                }
            else {
                rz->icmd = rz->icmd & ~ICMD_AIP;
                rz->icmd = rz->icmd | ICMD_LA;
                }
            }
        if (data & MODE_MONBSY) {                       /* enable BSY monitor */
            if (rz->bus.target < 0) {                   /* BSY set? */
                sim_debug (DBG_INT, dptr, "Delayed: Busy error\n");
                rz_setint (rz, STS_BSYERR);             /* no, error */
                }
            }
        if ((data & MODE_DMA) == 0)                     /* clearing DMA */
            rz->status = rz->status & ~STS_DMAEND;
        rz->mode = data;
        if (((rz->icmd & ICMD_BSY) == 0) && (rz->bus.target < 0))
            rz->mode = rz->mode & ~MODE_DMA;            /* DMA can only be set when BSY is set */
        rz_update_status (rz);
        break;

    case 3:                                             /* SCS_TAR_CMD */
        rz->tcmd = data & 0xF;
        if ((rz->mode & MODE_TARG) == 0)                /* not target mode */
            rz_update_status (rz);
        break;

    case 4:                                             /* SCS_SEL_ENA */
        rz->selen = data;
        break;

    case 5:                                             /* SCS_DMA_SEND */
        uptr = dptr->units + rz->bus.target;
        sim_activate (uptr, 50);
        break;

    case 6:                                             /* SCS_DMA_TRCV */
        break;

    case 7:                                             /* SCS_DMA_IRCV */
        uptr = dptr->units + rz->bus.target;
        sim_activate (uptr, 50);
        break;

    case 8:                                             /* SCD_ADR */
        if (access == L_BYTE) {
            if (rz->daddr_low) {
                rz->daddr = rz->daddr | (data & BMASK);
                rz->daddr_low = FALSE;
                }
            else {
                rz->daddr = ((data & 0x3F) << 8);
                rz->daddr_low = TRUE;
                }
            }
        else rz->daddr = data & DCNT_MASK;
        break;

    case 16:                                            /* SCD_CNT */
        rz->dcount = data & DCNT_MASK;
        break;

    case 17:                                            /* SCD_DIR */
        rz->ddir = data;
        break;
        }

sim_debug (DBG_REG, dptr, "reg %d write, value = %X, PC = %08X\n", rg, data, fault_PC);
SET_IRQL;
}

int32 rz_parity (int32 val, int32 odd)
{
for ( ; val != 0; val = val >> 1) {
    if (val & 1)
        odd = odd ^ 1;
    }
return odd;
}

void rz_ack (CTLR *rz)
{
uint32 len;
uint32 old_phase;

old_phase = rz->bus.phase;
switch (rz->bus.phase) {

    case PH_MSG_OUT:
    case PH_COMMAND:
    case PH_DATA_OUT:
        if (rz->bus.phase == PH_DATA_OUT)
            rz->buf_ptr = 0;                            /* TODO: fix this in sim_scsi.c */
        rz->buf[rz->buf_ptr++] = rz->odata;
        scsi_write (&rz->bus, &rz->buf[0], rz->buf_ptr);  /* send next byte */
        break;

    case PH_DATA_IN:
    case PH_STATUS:
    case PH_MSG_IN:
        len = scsi_read (&rz->bus, &rz->cdata, 1);      /* receive next byte */
        break;
        }
if (old_phase != rz->bus.phase)                         /* new phase? */
    rz->buf_ptr = 0;                                    /* reset buffer */
if (old_phase == PH_MSG_IN)                             /* message in just processed? */
    scsi_release (&rz->bus);                            /* accept message */
rz_update_status (rz);
}

void rz_update_status (CTLR *rz)
{
DEVICE *dptr = rz_devmap[rz->cnum];

if ((rz->tcmd & TCMD_PHASE) == rz->bus.phase)
    rz->status = rz->status | STS_MATCH;
else {
    rz->status = rz->status & ~STS_MATCH;
    if ((rz->mode & MODE_DMA) && (rz->bus.req)) {
        sim_debug (DBG_INT, dptr, "Immediate: Phase mismatch\n");
        if (rz->cnum == 0)
            SET_INT (SCB);
        else
            SET_INT (SCA);
        rz->status = rz->status | STS_INTREQ;
        }
    }
if ((rz->mode & MODE_MONBSY) && (rz->bus.target < 0)) {  /* monitoring BSY? */
    sim_debug (DBG_INT, dptr, "Delayed: Busy error\n");
    rz_setint (rz, STS_BSYERR);
    }
}

t_stat rz_svc (UNIT *uptr)
{
CTLR *rz = rz_ctxmap[uptr->cnum];
DEVICE *dptr = rz_devmap[uptr->cnum];
int32 dma_len;
uint32 old_phase;

old_phase = rz->bus.phase;
if (rz->dcount == 0)
    dma_len = DMA_SIZE;                                 /* full buffer */
else
    dma_len = ((rz->dcount ^ DCNT_MASK) + 1) & DCNT_MASK; /* 2's complement */
if (rz->ddir == 1) {                                    /* DMA in */
    dma_len = scsi_read (&rz->bus, &rz->buf[0], dma_len);
    ddb_WriteB (rz->daddr, dma_len, &rz->buf[0]);
    }
else {                                                  /* DMA out */
    ddb_ReadB (rz->daddr, dma_len, &rz->buf[0]);
    dma_len = scsi_write (&rz->bus, &rz->buf[0], dma_len);
    }
rz->buf_len = 0;
rz->dcount = (rz->dcount + dma_len) & DCNT_MASK;        /* increment toward zero */
dma_len = ((rz->dcount ^ DCNT_MASK) + 1) & DCNT_MASK;   /* 2's complement */
if (rz->ddir == 1) {                                    /* DMA in */
    if (old_phase == PH_MSG_IN)                         /* message in just processed? */
        scsi_release (&rz->bus);                        /* accept message */
    }
else {
    if ((rz->bus.phase == SCSI_STS) && (dma_len == 2)) { /* VMS driver expects this */
        rz->dcount = (rz->dcount + 1) & DCNT_MASK;      /* increment toward zero */
        dma_len--;                                      /* decrement remaining xfer */
        }
    }
if (dma_len == 0) {
    sim_debug (DBG_INT, dptr, "Service: DMA done\n");
    if (rz->cnum == 0)
        SET_INT (SCB);
    else
        SET_INT (SCA);
    rz->status = rz->status | STS_INTREQ | STS_DMAEND;
    }
rz_update_status (rz);
return SCPE_OK;
}

t_stat rz_isvc (UNIT *uptr)
{
CTLR *rz = rz_ctxmap[uptr->cnum];
DEVICE *dptr = rz_devmap[uptr->cnum];

sim_debug (DBG_INT, dptr, "Service: flags = %X\n", uptr->iflgs);
if (rz->cnum == 0)
    SET_INT (SCB);
else
    SET_INT (SCA);
rz->status = rz->status | STS_INTREQ | uptr->iflgs;
uptr->iflgs = 0;
return SCPE_OK;
}

void rz_setint (CTLR *rz, uint32 flags)
{
DEVICE *dptr = rz_devmap[rz->cnum];
UNIT *uptr = dptr->units + RZ_CTLR;

uptr->iflgs |= flags;
if (!sim_is_active (uptr))
    sim_activate (uptr, 50);
}

void rz_clrint (CTLR *rz)
{
DEVICE *dptr = rz_devmap[rz->cnum];

sim_debug (DBG_INT, dptr, "Immediate: Clear int\n");
if (rz->cnum == 0)
    CLR_INT (SCB);
else
    CLR_INT (SCA);
rz->status = rz->status & ~STS_INTREQ;
}

void rz_sw_reset (CTLR *rz)
{
DEVICE *dptr;
UNIT *uptr;
uint32 i;

dptr = rz_devmap[rz->cnum];
for (i = 0; i < (RZ_NUMDR + 1); i++) {
    uptr = dptr->units + i;
    sim_cancel (uptr);
    uptr->iflgs = 0;
    }
rz_clrint (rz);
rz->cdata = 0;
rz->mode = 0;
rz->icmd = 0;
rz->tcmd = 0;
rz->status = 0;
rz->cstat = 0;
rz->selen = 0;
rz->dcount = 0;
rz->daddr = 0;
rz->daddr_low = FALSE;
rz->ddir = 0;
rz->buf_ptr = 0;
scsi_reset (&rz->bus);
}

t_stat rz_reset (DEVICE *dptr)
{
int32 ctlr, i;
uint32 dtyp;
CTLR *rz;
UNIT *uptr;
t_stat r;

for (i = 0, ctlr = -1; i < RZ_NUMCT; i++) {             /* find ctrl num */
    if (rz_devmap[i] == dptr)
        ctlr = i;
    }
if (ctlr < 0)                                           /* not found??? */
    return SCPE_IERR;
rz = rz_ctxmap[ctlr];
if (rz->buf == NULL)
    rz->buf = (uint8 *)calloc (DMA_SIZE, sizeof(uint8));
if (rz->buf == NULL)
    return SCPE_MEM;
r = scsi_init (&rz->bus, DMA_SIZE);                     /* init SCSI bus */
if (r != SCPE_OK)
    return r;
rz->bus.dptr = dptr;                                    /* set bus device */
for (i = 0; i < (RZ_NUMDR + 1); i++) {                  /* init units */
    uptr = dptr->units + i;
    uptr->cnum = ctlr;                                  /* set ctrl index */
    if (i == RZ_SCSI_ID)                                /* initiator ID? */
        uptr->flags = UNIT_DIS;                         /* disable unit */
    if (i < RZ_NUMDR) {
        scsi_add_unit (&rz->bus, i, uptr);
        dtyp = GET_DTYPE (uptr->flags);
        scsi_set_unit (&rz->bus, uptr, &rzdev_tab[dtyp]);
        scsi_reset_unit (uptr);
        }
    }
rz_sw_reset (rz);
return SCPE_OK;
}

/* Set unit type (and capacity if user defined) */

t_stat rz_set_type (UNIT *uptr, int32 val, CONST char *cptr, void *desc)
{
CTLR *rz = rz_ctxmap[uptr->cnum];
uint32 cap;
uint32 max = sim_toffset_64? RZU_EMAXC: RZU_MAXC;
t_stat r;

if ((val < 0) || ((val != RZU_DTYPE) && cptr))
    return SCPE_ARG;
if (uptr->flags & UNIT_ATT)
    return SCPE_ALATT;
if (cptr) {
    cap = (uint32) get_uint (cptr, 10, 0xFFFFFFFF, &r);
    if ((sim_switches & SWMASK ('L')) == 0)
        cap = cap * 1954;
    if ((r != SCPE_OK) || (cap < RZU_MINC) || (cap > max))
        return SCPE_ARG;
    rzdev_tab[val].lbn = cap;
    }
uptr->flags = (uptr->flags & ~UNIT_DTYPE) | (val << UNIT_V_DTYPE);
uptr->capac = (t_addr)rzdev_tab[val].lbn;
scsi_set_unit (&rz->bus, uptr, &rzdev_tab[val]);
scsi_reset_unit (uptr);
return SCPE_OK;
}

/* Show unit type */

t_stat rz_show_type (FILE *st, UNIT *uptr, int32 val, CONST void *desc)
{
fprintf (st, "%s", rzdev_tab[GET_DTYPE (uptr->flags)].name);
return SCPE_OK;
}

t_stat rz_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr)
{
fprintf (st, "NCR 5380 SCSI Controller (%s)\n\n", dptr->name);
fprintf (st, "The %s controller simulates the NCR 5380 SCSI controller connected\n", dptr->name);
fprintf (st, "to a bus with up to 7 target devices.\n");
if (dptr->flags & DEV_DISABLE)
    fprintf (st, "Initially the %s controller is disabled.\n", dptr->name);
else
    fprintf (st, "The %s controller cannot be disabled.\n", dptr->name);
fprintf (st, "SCSI target device %s%d is reserved for the initiator and cannot\n", dptr->name, RZ_SCSI_ID);
fprintf (st, "be enabled\n");
fprintf (st, "Each target on the SCSI bus can be set to one of several types:\n");
fprint_set_help (st, dptr);
fprintf (st, "Configured options can be displayed with:\n\n");
fprint_show_help (st, dptr);
fprint_reg_help (st, dptr);
scsi_help (st, dptr, uptr, flag, cptr);
return SCPE_OK;
}

t_stat rz_attach (UNIT *uptr, CONST char *cptr)
{
return scsi_attach_ex (uptr, cptr, drv_types);
}

const char *rz_description (DEVICE *dptr)
{
return "NCR 5380 SCSI controller";
}
