/* sigma_rad.c: Sigma 7211/7212 or 7231/7232 fixed head disk simulator

   Copyright (c) 2007-2024, Robert M Supnik

   Permission is hereby granted, free of charge, to any person obtaining a
   copy of this software and associated documentation files (the "Software"),
   to deal in the Software without restriction, including without limitation
   the rights to use, copy, modify, merge, publish, distribute, sublicense,
   and/or sell copies of the Software, and to permit persons to whom the
   Software is furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included in
   all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
   ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
   IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

   Except as contained in this notice, the name of Robert M Supnik shall not be
   used in advertising or otherwise to promote the sale, use or other dealings
   in this Software without prior written authorization from Robert M Supnik.

   rad          7211/7212 or 7231/7232 fixed head disk

   17-Feb-24    RMS     Zero delay from SIO to INIT state (Ken Rector)
   01-Feb-24    RMS     Fixed nx unit test (Ken Rector)
   22-Apr-23    RMS     Fixed write protect test (Ken Rector)
   02-Jul-22    RMS     Fixed bugs in multi-unit operation

   The RAD is a head-per-track disk.  To minimize overhead, the entire RAD
   is buffered in memory.

   Transfers are always done a sector at a time.
*/

#include "sigma_io_defs.h"
#include <math.h>

/* Constants */

#define RAD_7212        0                               /* ctlr type */
#define RAD_7232        1
#define RAD_NUMDR       4                               /* drives/ctlr */
#define RAD_WDSC        256                             /* words/sector */
#define RAD_WDMASK      (RAD_WDSC - 1)
#define RAD_SCTK1       82                              /* sectors/track */
#define RAD_SCTK3       12
#define RAD_TKUN1       64                              /* tracks/unit */
#define RAD_TKUN3       512
#define RAD_WDUNDF      (RAD_WDSC*RAD_SCTK1*RAD_TKUN1)  /* dflt words/unit */
#define RAD_WDUN        (RAD_WDSC*rad_tab[rad_model].sctk*rad_tab[rad_model].tkun)
#define RAD_N_WLK       16                              /* num wlk switches */

/* Address bytes */

#define RADA_V_TK1      7                               /* track offset */
#define RADA_M_TK1      0xFF
#define RADA_V_SC1      0                               /* sector offset */
#define RADA_M_SC1      0x7F
#define RADA_V_TK3      4
#define RADA_M_TK3      0x3FF
#define RADA_V_SC3      0
#define RADA_M_SC3      0xF
#define RADA_GETTK(x)   (((x) >> rad_tab[rad_model].tk_v) & rad_tab[rad_model].tk_m)
#define RADA_GETSC(x)   (((x) >> rad_tab[rad_model].sc_v) & rad_tab[rad_model].sc_m)

/* Write protect flag */

#define RADA_WP         0x80

/* Status byte 3 is current sector */
/* Status byte 4 (7212 only) is failing sector */

#define RADS_NBY1       4                               /* num status bytes */
#define RADS_NBY3       3

/* Device state */

#define RADS_INIT       0x101
#define RADS_END        0x102
#define RADS_WRITE      0x01
#define RADS_READ       0x02
#define RADS_SEEK       0x03
#define RADS_SENSE      0x04
#define RADS_CHECK      0x05
#define RADS_RDEES      0x12

/* Device status */

#define RADV_OVR        0x80                            /* overrun - NI */
#define RADV_BADS       0x20                            /* bad sector */
#define RADV_WPE        0x10

#define GET_PSC(x)      ((int32) fmod (sim_gtime() / ((double) (x * RAD_WDSC)), \
                        ((double) rad_tab[rad_model].sctk)))

/* Model table */

typedef struct {
    uint32        tk_v;                                 /* track extract */
    uint32        tk_m;
    uint32        sc_v;                                 /* sector extract */
    uint32        sc_m;
    uint32        sctk;                                 /* sectors/track */
    uint32        tkun;                                 /* tracks/unit */
    uint32        nbys;                                 /* bytes of status */
    } rad_t;

static rad_t rad_tab[] = {
    { RADA_V_TK1, RADA_M_TK1, RADA_V_SC1, RADA_M_SC1, RAD_SCTK1, RAD_TKUN1, RADS_NBY1 },
    { RADA_V_TK3, RADA_M_TK3, RADA_V_SC3, RADA_M_SC3, RAD_SCTK3, RAD_TKUN3, RADS_NBY3 }
    };

uint32 rad_model = RAD_7212;                            /* model */
uint32 rad_cmd = 0;                                     /* state */
uint32 rad_flags = 0;                                   /* status flags */
uint32 rad_ad = 0;                                      /* rad address */
uint32 rad_wlk = 0;                                     /* write lock */
uint32 rad_time = 2;                                    /* inter-word time */

extern uint32 chan_ctl_time;

uint32 rad_disp (uint32 op, uint32 dva, uint32 *dvst);
uint32 rad_tio_status (uint32 un);
uint32 rad_tdv_status (uint32 un);
t_stat rad_chan_err (uint32 dva, uint32 st);
t_stat rad_svc (UNIT *uptr);
t_stat rad_reset (DEVICE *dptr);
t_stat rad_settype (UNIT *uptr, int32 val, CONST char *cptr, void *desc);
t_stat rad_showtype (FILE *st, UNIT *uptr, int32 val, CONST void *desc);
t_bool rad_inv_ad (uint32 *da);
t_bool rad_inc_ad (void);
t_bool rad_end_sec (UNIT *uptr, uint32 lnt, uint32 exp, uint32 st);
t_bool rad_wp_trk (uint32 adr);

/* RAD data structures

   rad_dev       RAD device descriptor
   rad_unit      RAD unit descriptor
   rad_reg       RAD register list
*/

dib_t rad_dib = { DVA_RAD, &rad_disp };

UNIT rad_unit[] = {
    { UDATA (&rad_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_BUFABLE+
             UNIT_MUSTBUF+UNIT_DISABLE, RAD_WDUNDF) },
    { UDATA (&rad_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_BUFABLE+
             UNIT_MUSTBUF+UNIT_DISABLE+UNIT_DIS, RAD_WDUNDF) },
    { UDATA (&rad_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_BUFABLE+
             UNIT_MUSTBUF+UNIT_DISABLE+UNIT_DIS, RAD_WDUNDF) },
    { UDATA (&rad_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_BUFABLE+
             UNIT_MUSTBUF+UNIT_DISABLE+UNIT_DIS, RAD_WDUNDF) }
    };

REG rad_reg[] = {
    { HRDATA (CMD, rad_cmd, 9) },
    { HRDATA (FLAGS, rad_flags, 8) },
    { HRDATA (ADDR, rad_ad, 15) },
    { HRDATA (WLK, rad_wlk, RAD_N_WLK) },
    { DRDATA (TIME, rad_time, 24), PV_LEFT },
    { FLDATA (MODEL, rad_model, 0), REG_HRO },
    { HRDATA (DEVNO, rad_dib.dva, 12), REG_HRO },
    { NULL }
    };

MTAB rad_mod[] = {
    { MTAB_XTD | MTAB_VDV, RAD_7212, NULL, "7211",
      &rad_settype, NULL, NULL },
    { MTAB_XTD | MTAB_VDV, RAD_7212, NULL, "7212",
      &rad_settype, NULL, NULL },
    { MTAB_XTD | MTAB_VDV, RAD_7232, NULL, "7231",
      &rad_settype, NULL, NULL },
    { MTAB_XTD | MTAB_VDV, RAD_7232, NULL, "7232",
      &rad_settype, NULL, NULL },
    { MTAB_XTD | MTAB_VDV, 0, "TYPE", NULL,
      NULL, &rad_showtype, NULL },
    { MTAB_XTD|MTAB_VDV, 0, "CHAN", "CHAN",
      &io_set_dvc, &io_show_dvc, NULL },
    { MTAB_XTD|MTAB_VDV, 0, "DVA", "DVA",
      &io_set_dva, &io_show_dva, NULL },
    { MTAB_XTD|MTAB_VDV|MTAB_NMO, 0, "CSTATE", NULL,
      NULL, &io_show_cst, NULL },
    { 0 }
    };

DEVICE rad_dev = {
    "RAD", rad_unit, rad_reg, rad_mod,
    RAD_NUMDR, 16, 21, 1, 16, 32,
    NULL, NULL, &rad_reset,
    &io_boot, NULL, NULL,
    &rad_dib, DEV_DISABLE
    };

/* RAD: IO dispatch routine

   For all calls except AIO, dva is the full channel/device/unit address
   For AIO, the handler must return the unit number
*/

uint32 rad_disp (uint32 op, uint32 dva, uint32 *dvst)
{
uint32 i;
uint32 un = DVA_GETUNIT (dva);
int32 iu;
UNIT *uptr;

if ((un >= RAD_NUMDR) ||                                /* inv unit num? */
    (rad_unit[un].flags & UNIT_DIS)) {                  /* disabled unit? */
    *dvst = DVT_NODEV;
    return 0;
    }
switch (op) {                                           /* case on op */

    case OP_SIO:                                        /* start I/O */
        *dvst = rad_tio_status (un);                    /* get status */
        if (chan_chk_dvi (dva))                         /* int pending? */
            *dvst |= (CC2 << DVT_V_CC);                 /* SIO fails */
        else if ((*dvst & (DVS_CST|DVS_DST)) == 0) {    /* ctrl + dev idle? */
            rad_cmd = RADS_INIT;                        /* start dev thread */
            sim_activate (&rad_unit[un], 0);
            }
        break;

    case OP_TIO:                                        /* test status */
        *dvst = rad_tio_status (un);                    /* return status */
        break;

    case OP_TDV:                                        /* test status */
        *dvst = rad_tdv_status (un);                    /* return status */
        break;

    case OP_HIO:                                        /* halt I/O */
        chan_clr_chi (dva);                             /* clr int */
        *dvst = rad_tio_status (un);                    /* get status */
        if ((*dvst & DVS_CST) != 0) {                   /* ctrl busy? */
            for (i = 0; i < RAD_NUMDR; i++) {           /* find busy unit */
                uptr = &rad_unit[i];
                if (sim_is_active (uptr)) {             /* active? */
                    sim_cancel (uptr);                  /* stop */
                    chan_uen (rad_dib.dva | i);         /* uend on drive */
                    }                                   /* end if active */
                }                                       /* end for */
            }
        break;

    case OP_AIO:                                        /* acknowledge int */
        iu = chan_clr_chi (rad_dib.dva);                /* clr int */
        if (iu < 0) {                                   /* no int? */
            *dvst = 0;
            return SCPE_IERR;
            }
        *dvst = rad_tdv_status (iu) |                   /* status like TDV */
            (iu << DVT_V_UN);                           /* or in unit */
        break;

    default:
        *dvst = 0;
        return SCPE_IERR;
        }

return 0;
}

/* Unit service - this code assumes the entire disk is buffered */

t_stat rad_svc (UNIT *uptr)
{
uint32 i, sc, da, cmd, wd, wd1, c[4];
uint32 *fbuf = (uint32 *) uptr->filebuf;
uint32 un = uptr - rad_unit;
uint32 dva = rad_dib.dva | un;
uint32 st;
int32 t;

switch (rad_cmd) {

    case RADS_INIT:                                     /* init state */
        st = chan_get_cmd (dva, &cmd);                  /* get command */
        if (CHS_IFERR (st))                             /* channel error? */
            return rad_chan_err (dva, st);
        if ((cmd == 0) ||                               /* invalid cmd? */
            ((cmd > RADS_CHECK) && (cmd != RADS_RDEES))) {
            chan_uen (dva);                             /* uend */
            return SCPE_OK;
            }
        rad_flags = 0;                                  /* clear status */
        rad_cmd = cmd & 0x7;                            /* next state */
        if ((cmd == RADS_SEEK) || (cmd == RADS_SENSE))  /* seek or sense? */
            sim_activate (uptr, chan_ctl_time);         /* schedule soon */
        else {                                          /* data transfer */
            sc = RADA_GETSC (rad_ad);                   /* new sector */
            t = sc - GET_PSC (rad_time);                /* delta to new */
            if (t < 0)                                  /* wrap around? */
                t = t + rad_tab[rad_model].sctk;
            sim_activate (uptr, t * rad_time * RAD_WDSC); /* schedule op */
            }
        return SCPE_OK;

    case RADS_END:                                      /* end state */
        st = chan_end (dva);                            /* set channel end */
        if (CHS_IFERR (st))                             /* channel error? */
            return rad_chan_err (dva, st);
        if (st == CHS_CCH) {                            /* command chain? */
            rad_cmd = RADS_INIT;                        /* restart thread */
            sim_activate (uptr, chan_ctl_time);
            }
        return SCPE_OK;                                 /* done */

    case RADS_SEEK:                                     /* seek */
        c[0] = c[1] = 0;
        for (i = 0, st = 0; (i < 2) && (st != CHS_ZBC); i++) {
            st = chan_RdMemB (dva, &c[i]);              /* get byte */
            if (CHS_IFERR (st))                         /* channel error? */
                return rad_chan_err (dva, st);
            }
        rad_ad = ((c[0] & 0x7F) << 8) | c[1];           /* new address */
        if (((i != 2) || (st != CHS_ZBC)) &&            /* length error? */
            chan_set_chf (dva, CHF_LNTE))               /* care? */
            return SCPE_OK;
        break;

    case RADS_SENSE:                                    /* sense */
        c[0] = (rad_ad >> 8) & 0x7F;                    /* addr hi */
        if (rad_wp_trk (rad_ad))                        /* addr wr prot? */
            c[0] |= RADA_WP;                            /* set flag */
        c[1] = rad_ad & 0xFF;                           /* addr lo */
        c[2] = GET_PSC (rad_time);                      /* curr sector */
        c[3] = 0;
        for (i = 0, st = 0; (i < rad_tab[rad_model].nbys) && (st != CHS_ZBC); i++) {
            st = chan_WrMemB (dva, c[i]);               /* store char */
            if (CHS_IFERR (st))                         /* channel error? */
                return rad_chan_err (dva, st);
            }
        if (((i != rad_tab[rad_model].nbys) || (st != CHS_ZBC)) &&
            chan_set_chf (dva, CHF_LNTE))               /* length error? */
            return SCPE_OK;
        break;

    case RADS_WRITE:                                    /* write */
        if (rad_wp_trk (rad_ad)) {                      /* write protected? */
            rad_flags |= RADV_WPE;                      /* set status */
            chan_uen (dva);                             /* uend */
            return SCPE_OK;
            }
        if (rad_inv_ad (&da)) {                         /* invalid addr? */
            chan_uen (dva);                             /* uend */
            return SCPE_OK;
            }
        for (i = 0, st = 0; i < RAD_WDSC; da++, i++) {  /* write */
            if (st != CHS_ZBC) {                        /* chan active? */
                st = chan_RdMemW (dva, &wd);            /* get data */
                if (CHS_IFERR (st)) {                   /* channel error? */
                    rad_inc_ad ();                      /* da increments */
                    return rad_chan_err (dva, st);
                    }
                }
            else wd = 0;
            fbuf[da] = wd;                              /* store in buffer */
            if (da >= uptr->hwmark)                     /* update length */
                uptr->hwmark = da + 1;
            }
       if (rad_end_sec (uptr, i, RAD_WDSC, st))         /* transfer done? */
            return SCPE_OK;
        break;

/* Must be done by bytes to get precise miscompare */

    case RADS_CHECK:                                    /* write check */
        if (rad_inv_ad (&da)) {                         /* invalid addr? */
            chan_uen (dva);                             /* uend */
            return SCPE_OK;
            }
        for (i = 0, st = 0; (i < (RAD_WDSC * 4)) && (st != CHS_ZBC); ) {
            st = chan_RdMemB (dva, &wd);                /* read sector */
            if (CHS_IFERR (st)) {                       /* channel error? */
                rad_inc_ad ();                          /* da increments */
                return rad_chan_err (dva, st);
                }
            wd1 = (fbuf[da] >> (24 - ((i % 4) * 8))) & 0xFF; /* byte */
            if (wd != wd1) {                            /* check error? */
                rad_inc_ad ();                          /* da increments */
                chan_set_chf (dva, CHF_XMDE);           /* set xmt err flag */
                chan_uen (dva);                         /* force uend */
                return SCPE_OK;
                }
            da = da + ((++i % 4) == 0);                 /* every 4th byte */
            }
        if (rad_end_sec (uptr, i, RAD_WDSC * 4, st))    /* transfer done? */
            return SCPE_OK;
        break;

    case RADS_READ:                                     /* read */
        if (rad_inv_ad (&da)) {                         /* invalid addr? */
            chan_uen (dva);                             /* uend */
            return SCPE_OK;
            }
        for (i = 0, st = 0; (i < RAD_WDSC) && (st != CHS_ZBC); da++, i++) {
            st = chan_WrMemW (dva, fbuf[da]);           /* store in mem */
            if (CHS_IFERR (st)) {                       /* channel error? */
                rad_inc_ad ();                          /* da increments */
                return rad_chan_err (dva, st);
                }
            }
        if (rad_end_sec (uptr, i, RAD_WDSC, st))        /* transfer done? */
            return SCPE_OK;
        break;
        }

rad_cmd = RADS_END;                                     /* op done, next state */
sim_activate (uptr, chan_ctl_time);
return SCPE_OK;
}

/* Common read/write sector end routine 

   case 1 - more to transfer, not end disk - reschedule, return TRUE
   case 2 - more to transfer, end disk - uend, return TRUE
   case 3 - transfer done, length error - uend, return TRUE
   case 4 - transfer done, no length error - return FALSE (sched end state)
*/

t_bool rad_end_sec (UNIT *uptr, uint32 lnt, uint32 exp, uint32 st)
{
uint32 un = uptr - rad_unit;
uint32 dva = rad_dib.dva | un;

if (st != CHS_ZBC) {                                    /* end record? */
    if (rad_inc_ad ())                                  /* inc addr, ovf? */
        chan_uen (dva);                                 /* uend */
    else sim_activate (uptr, rad_time * 16);            /* no, next sector */
    return TRUE;
    }
rad_inc_ad ();                                          /* just incr addr */
if ((lnt != exp) &&                                     /* length error? */
    chan_set_chf (dva, CHF_LNTE))                       /* do we care? */
    return TRUE;
return FALSE;                                           /* cmd done */
}

/* RAD status routine */

uint32 rad_tio_status (uint32 un)
{
uint32 i, st;

st = DVS_AUTO;                                          /* flags */
if (sim_is_active (&rad_unit[un]))                      /* active => busy */
    st |= DVS_DBUSY;
else if ((rad_unit[un].flags & UNIT_ATT) == 0)          /* not att => offl */
    st |= DVS_DOFFL;                                 
for (i = 0; i < RAD_NUMDR; i++) {                       /* loop thru units */
    if (sim_is_active (&rad_unit[i])) {                 /* active? */
        st |= (DVS_CBUSY | (CC2 << DVT_V_CC));          /* ctrl is busy */
        return st;
        }
    }
return st;
}

uint32 rad_tdv_status (uint32 un)
{
uint32 st;

st = rad_flags;
if (rad_inv_ad (NULL))                                  /* bad address? */
    st |= RADV_BADS;
return st;
}

/* Validate disk address */

t_bool rad_inv_ad (uint32 *da)
{
uint32 tk = RADA_GETTK (rad_ad);
uint32 sc = RADA_GETSC (rad_ad);

if ((tk >= rad_tab[rad_model].tkun) ||                  /* bad sec or trk? */
    (sc >= rad_tab[rad_model].sctk)) {
    return TRUE;
    }
if (da)                                                 /* return word addr */
    *da = ((tk * rad_tab[rad_model].sctk) + sc) * RAD_WDSC;
return FALSE;
}

/* Increment disk address */

t_bool rad_inc_ad (void)
{
uint32 tk = RADA_GETTK (rad_ad);
uint32 sc = RADA_GETSC (rad_ad);

sc = sc + 1;                                            /* sector++ */
if (sc >= rad_tab[rad_model].sctk) {                    /* overflow? */
    sc = 0;                                             /* wrap sector */
    tk = tk + 1;                                        /* track++ */
    }
rad_ad = ((tk << rad_tab[rad_model].tk_v) |             /* rebuild rad_ad */
          (sc << rad_tab[rad_model].sc_v));
if (tk >= rad_tab[rad_model].tkun)                      /* overflow? */
    return TRUE;
return FALSE;
}

/* Test disk addr for protected tracks */

t_bool rad_wp_trk (uint32 adr)
{
uint32 trk = RADA_GETTK (adr);                          /* track */
uint32 sw = (trk * RAD_N_WLK) / rad_tab[rad_model].tkun; /* switch num */

if ((rad_wlk >> sw) & 1)                                /* switch set? */
    return TRUE;
return FALSE;
}

/* Channel error */

t_stat rad_chan_err (uint32 dva, uint32 st)
{
chan_uen (dva);                                         /* uend */
if (st < CHS_ERR)
    return st;
return SCPE_OK;
}

/* Reset routine */

t_stat rad_reset (DEVICE *dptr)
{
uint32 i;

for (i = 0; i < RAD_NUMDR; i++)
    sim_cancel (&rad_unit[i]);                          /* stop dev thread */
rad_cmd = 0;
rad_flags = 0;
rad_ad = 0;
chan_reset_dev (rad_dib.dva);                           /* clr int, active */
return SCPE_OK;
}

/* Set controller type */

t_stat rad_settype (UNIT *uptr, int32 val, CONST char *cptr, void *desc)
{
uint32 i;

for (i = 0; i < RAD_NUMDR; i++) {                       /* all units unatt? */
    if (rad_unit[i].flags & UNIT_ATT)
        return SCPE_ALATT;
    }
rad_model = val;                                        /* update model */
rad_reset (&rad_dev);                                   /* reset */
for (i = 0; i < RAD_NUMDR; i++)                         /* update capacity */
    rad_unit[i].capac = RAD_WDUN;
return SCPE_OK;
}

/* Show controller type */

t_stat rad_showtype (FILE *st, UNIT *uptr, int32 val, CONST void *desc)
{
fprintf (st, (rad_model == RAD_7212)? "7211/7212": "7231/7232");
return SCPE_OK;
}
