/* i1620_dp.c: IBM 1311 disk simulator

   Copyright (c) 2002-2017, 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.

   dp           1311 disk pack

   The 1311 disk pack has 100 cylinders, 10 tracks/cylinder, 20 sectors/track.
   Each sector contains 105 characters of information:

   5c           sector address
   100c         sector data

   By default, a sector's address field will be '00000', which is interpreted
   to mean the implied sector number that would be in place if the disk pack
   had been formatted with sequential sector numbers.

   13-Mar-17    RMS     Fixed bug in write check function test (COVERITY)
   18-Oct-02    RMS     Fixed bug in error testing (Hans Pufal)
*/

#include "i1620_defs.h"

#define DP_NUMDR        4                               /* #drives */
#define UNIT_V_WAE      (UNIT_V_UF + 0)                 /* write addr enab */
#define UNIT_WAE        (1 << UNIT_V_WAE)

/* Disk format */

#define DP_ADDR         5                               /* address */
#define DP_DATA         100                             /* data */
#define DP_NUMCH        (DP_ADDR + DP_DATA)

#define DP_NUMSC        20                              /* #sectors */
#define DP_NUMSF        10                              /* #surfaces */
#define DP_NUMCY        100                             /* #cylinders */
#define DP_TOTSC        (DP_NUMCY * DP_NUMSF * DP_NUMSC)
#define DP_SIZE         (DP_TOTSC * DP_NUMCH)

/* Disk control field */

#define DCF_DRV         0                               /* drive select */
#define DCF_SEC         1                               /* sector addr */
#define DCF_SEC_LEN     5
#define DCF_CNT         (DCF_SEC + DCF_SEC_LEN)         /* sector count */
#define DCF_CNT_LEN     3
#define DCF_ADR         (DCF_CNT + DCF_CNT_LEN)         /* buffer address */
#define DCF_ADR_LEN     5
#define DCF_LEN         (DCF_ADR + DCF_ADR_LEN)

/* Functions */

#define FNC_SEEK        1                               /* seek */
#define FNC_SEC         0                               /* sectors */
#define FNC_WCH         1                               /* write check */
#define FNC_NRL         2                               /* no rec lnt chk */
#define FNC_TRK         4                               /* tracks */
#define FNC_WRI         8                               /* write offset */

#define CYL             u3                              /* current cylinder */

extern uint8 M[MAXMEMSIZE];                             /* memory */
extern uint8 ind[NUM_IND];
extern UNIT cpu_unit;

int32 dp_stop = 1;                                      /* disk err stop */
uint32 dp_ba = 0;                                       /* buffer addr */

t_stat dp_reset (DEVICE *dptr);
t_stat dp_rdadr (UNIT *uptr, int32 sec, int32 qnr, int32 qwc);
t_stat dp_rdsec (UNIT *uptr, int32 sec, int32 qnr, int32 qwc);
t_stat dp_wradr (UNIT *uptr, int32 sec, int32 qnr);
t_stat dp_wrsec (UNIT *uptr, int32 sec, int32 qnr);
int32 dp_fndsec (UNIT *uptr, int32 sec, t_bool rd);
t_stat dp_nexsec (UNIT *uptr, int32 sec, int32 psec, t_bool rd);
t_bool dp_zeroad (uint8 *ap);
int32 dp_cvt_ad (uint8 *ap);
int32 dp_trkop (int32 drv, int32 sec);
int32 dp_cvt_bcd (uint32 ad, int32 len);
void dp_fill (UNIT *uptr, uint32 da, int32 cnt);
t_stat dp_tstgm (uint32 c, int32 qnr);

/* DP data structures

   dp_dev       DP device descriptor
   dp_unit      DP unit list
   dp_reg       DP register list
   dp_mod       DP modifier list
*/

UNIT dp_unit[] = {
    { UDATA (NULL, UNIT_FIX + UNIT_DISABLE + UNIT_ATTABLE +
             UNIT_BUFABLE + UNIT_MUSTBUF + UNIT_BCD, DP_SIZE) },
    { UDATA (NULL, UNIT_FIX + UNIT_DISABLE + UNIT_ATTABLE +
             UNIT_BUFABLE + UNIT_MUSTBUF + UNIT_BCD, DP_SIZE) },
    { UDATA (NULL, UNIT_FIX + UNIT_DISABLE + UNIT_ATTABLE +
             UNIT_BUFABLE + UNIT_MUSTBUF + UNIT_BCD, DP_SIZE) },
    { UDATA (NULL, UNIT_FIX + UNIT_DISABLE + UNIT_ATTABLE +
             UNIT_BUFABLE + UNIT_MUSTBUF + UNIT_BCD, DP_SIZE) }
    };

REG dp_reg[] = {
    { FLDATAD (ADCHK, ind[IN_DACH], 0, "address check (compare error) indicator") },
    { FLDATAD (WLRC, ind[IN_DWLR], 0, "wrong length record check indicator") },
    { FLDATAD (CYLO, ind[IN_DCYO], 0, "cylinder overflow check indicator") },
    { FLDATAD (ERR, ind[IN_DERR], 0, "disk error indicator") },
    { FLDATAD (DPSTOP, dp_stop, 0, "disk check stop") },
    { URDATAD (CYL, dp_unit[0].CYL, 10, 8, 0,
              DP_NUMDR, PV_LEFT + REG_RO, "Cylinder") },
    { NULL }
    };

MTAB dp_mod[] = {
    { UNIT_WAE, 0, "write address disabled", "ADDROFF", NULL, NULL, NULL, "set unit n address enable off" },
    { UNIT_WAE, UNIT_WAE, "write address enabled", "ADDRON", NULL, NULL, NULL, "set unit n address enable on" }, 
    { 0 }
    };

DEVICE dp_dev = {
    "DP", dp_unit, dp_reg, dp_mod,
    DP_NUMDR, 10, 21, 1, 16, 5,
    NULL, NULL, &dp_reset,
    NULL, NULL, NULL
    };

/* Disk IO routine */

t_stat dp (uint32 op, uint32 pa, uint32 f0, uint32 f1)
{
int32 drv, sa, sec, psec, cnt, qwc, qnr, t;
UNIT *uptr;
t_stat r;

if (pa & 1)                                             /* dcf must be even */
    return STOP_INVDCF;
ind[IN_DACH] = ind[IN_DWLR] = 0;                        /* clr indicators */
ind[IN_DERR] = ind[IN_DCYO] = 0;
sa = ADDR_A (pa, DCF_SEC);                              /* ptr to sector */
if (((dp_unit[0].flags & UNIT_DIS) == 0) &&             /* only drive 0? */
     (dp_unit[1].flags & UNIT_DIS) &&
     (dp_unit[2].flags & UNIT_DIS) &&
     (dp_unit[3].flags & UNIT_DIS)) drv = 0;            /* ignore drv select */
else drv = (((M[pa] & 1)? M[pa]: M[sa]) & 0xE) >> 1;    /* drive # */
if (drv >= DP_NUMDR)                                    /* invalid? */
    return STOP_INVDRV;
uptr = dp_dev.units + drv;                              /* get unit ptr */
if ((uptr->flags & UNIT_ATT) == 0) {                    /* attached? */
    ind[IN_DERR] = 1;                                   /* no, error */
    CRETIOE (dp_stop, SCPE_UNATT);
    }

sec = dp_cvt_bcd (sa, DCF_SEC_LEN);                     /* cvt sector */
if ((sec < 0) || (sec >= (DP_NUMDR * DP_TOTSC)))        /* bad sector? */
    return STOP_INVDSC;
if (op == OP_K) {                                       /* seek? */
    if (f1 != FNC_SEEK)                                 /* really? */
        return STOP_INVFNC;
    uptr->CYL = (sec / (DP_NUMSF * DP_NUMSC)) %         /* set cyl # */
        DP_NUMCY;
    return SCPE_OK;                                     /* done! */
    }

cnt = dp_cvt_bcd (ADDR_A (pa, DCF_CNT), DCF_CNT_LEN);   /* get count */
t = dp_cvt_bcd (ADDR_A (pa, DCF_ADR), DCF_ADR_LEN);     /* get address */
if ((t < 0) || (t & 1))                                 /* bad address? */
    return STOP_INVDBA;
dp_ba = t;                                              /* save addr */

if (f1 >= FNC_WRI)                                      /* invalid func? */
    return STOP_INVFNC;
if (op == OP_RN)                                        /* read? set wch */
    qwc = f1 & FNC_WCH;
else if (op == OP_WN) {                                 /* write? */
    if (f1 & FNC_WCH)                                   /* cant check */
        return STOP_INVFNC;
    f1 = f1 + FNC_WRI;                                  /* offset fnc */
    }
else return STOP_INVFNC;                                /* not R or W */
qnr = f1 & FNC_NRL;                                     /* no rec check? */

switch (f1 & ~(FNC_WCH | FNC_NRL)) {                    /* case on function */

    case FNC_SEC:                                       /* read sectors */
        if (cnt <= 0)                                   /* bad count? */
            return STOP_INVDCN;
        psec = dp_fndsec (uptr, sec, TRUE);             /* find sector */
        if (psec < 0)                                   /* error? */
            CRETIOE (dp_stop, STOP_DACERR);
        do {                                            /* loop on count */
            if ((r = dp_rdsec (uptr, psec, qnr, qwc)))  /* read sector */
                break;
            sec++; psec++;                              /* next sector */
            } while ((--cnt > 0) &&
              ((r = dp_nexsec (uptr, sec, psec, TRUE)) == SCPE_OK));
        break;                                          /* done, clean up */

    case FNC_TRK:                                       /* read track */
        psec = dp_trkop (drv, sec);                     /* start of track */
        for (cnt = 0; cnt < DP_NUMSC; cnt++) {          /* full track */
            if ((r = dp_rdadr (uptr, psec, qnr, qwc)))  /* read addr */
                break;                                  /* error? */
            if ((r = dp_rdsec (uptr, psec, qnr, qwc)))  /* read data */
                break;                                  /* error? */
            psec = dp_trkop (drv, sec) + ((psec + 1) % DP_NUMSC);
            }
        break;                                          /* done, clean up */        

    case FNC_SEC + FNC_WRI:                             /* write sectors */
        if (cnt <= 0)                                   /* bad count? */
            return STOP_INVDCN;
        psec = dp_fndsec (uptr, sec, FALSE);            /* find sector */
        if (psec < 0)                                   /* error? */
            CRETIOE (dp_stop, STOP_DACERR);
        do {                                            /* loop on count */
            if ((r = dp_tstgm (M[dp_ba], qnr)))         /* start with gm? */
                break;
            if ((r = dp_wrsec (uptr, psec, qnr)))       /* write data */
                break;
            sec++; psec++;                              /* next sector */
            } while ((--cnt > 0) &&
              ((r = dp_nexsec (uptr, sec, psec, FALSE)) == SCPE_OK));
        break;                                          /* done, clean up */

    case FNC_TRK + FNC_WRI:                             /* write track */
        if ((uptr->flags & UNIT_WAE) == 0)              /* enabled? */
                return STOP_WRADIS;
        psec = dp_trkop (drv, sec);                     /* start of track */
        for (cnt = 0; cnt < DP_NUMSC; cnt++) {          /* full track */
            if ((r = dp_tstgm (M[dp_ba], qnr)))         /* start with gm? */
                break;
            if ((r = dp_wradr (uptr, psec, qnr)))       /* write addr */
                break;
            if ((r = dp_wrsec (uptr, psec, qnr)))       /* write data */
                break;
            psec = dp_trkop (drv, sec) + ((psec + 1) % DP_NUMSC);
            }
        break;                                          /* done, clean up */

    default:                                            /* unknown */
        return STOP_INVFNC;
        }

if ((r == SCPE_OK) && !qnr) {                           /* eor check? */
    if ((M[dp_ba] & DIGIT) != GRP_MARK) {               /* GM at end? */
        ind[IN_DWLR] = ind[IN_DERR] = 1;                /* no, error */
        r = STOP_WRLERR;
        }
    }
if ((r != SCPE_OK) &&                                   /* error? */
    (dp_stop || !ind[IN_DERR]))                         /* iochk or stop? */
    return r;
return SCPE_OK;                                         /* continue */
}

/* Read or compare address with memory */

t_stat dp_rdadr (UNIT *uptr, int32 sec, int32 qnr, int32 qwc)
{
int32 i;
uint8 ad;
int32 da = (sec % DP_TOTSC) * DP_NUMCH;                 /* char number */
uint8 *ap = ((uint8 *) uptr->filebuf) + da;             /* buf ptr */
t_bool zad = dp_zeroad (ap);                            /* zero address */
static const int32 dec_tab[DP_ADDR] = {                 /* powers of 10 */
    10000, 1000, 100, 10, 1
    } ;

for (i = 0; i < DP_ADDR; i++) {                         /* copy/check addr */
    if (zad) {                                          /* addr zero? */
        ad = sec / dec_tab[i];                          /* get addr digit */
        sec = sec % dec_tab[i];                         /* get remainder */
        }
    else ad = *ap;                                      /* addr digit */
    if (qwc) {                                          /* write check? */
        if (dp_tstgm (M[dp_ba], qnr))                   /* grp mrk in mem? */
            return STOP_WRLERR;                         /* yes, error */
        if (!zad && (M[dp_ba] != ad)) {                 /* digits equal? */
            ind[IN_DACH] = ind[IN_DERR] = 1;            /* no, error */
            return STOP_DWCERR;
            }
        }
    else M[dp_ba] = ad & (FLAG | DIGIT);                /* store digit */
    if (dp_tstgm (*ap, qnr))                            /* grp mrk on disk? */
        return STOP_WRLERR;
    ap++; PP (dp_ba);                                   /* adv ptrs */
    }
return SCPE_OK;
}

/* Read or compare data with memory */

t_stat dp_rdsec (UNIT *uptr, int32 sec, int32 qnr, int32 qwc)
{
int32 i;
int32 da = (sec % DP_TOTSC) * DP_NUMCH;                 /* char number */
uint8 *ap = ((uint8 *) uptr->filebuf) + da + DP_ADDR;   /* buf ptr */

for (i = 0; i < DP_DATA; i++) {                         /* copy data */
    if (qwc) {                                          /* write check? */
        if (dp_tstgm (M[dp_ba], qnr))                   /* grp mrk in mem? */
            return STOP_WRLERR;                         /* yes, error */
        if (M[dp_ba] != *ap) {                          /* dig+flags equal? */
            ind[IN_DACH] = ind[IN_DERR] = 1;            /* no, error */
            return STOP_DWCERR;
            }
        }
    else M[dp_ba] = *ap & (FLAG | DIGIT);               /* flag + digit */
    if (dp_tstgm (*ap, qnr))                            /* grp mrk on disk? */
        return STOP_WRLERR;
    ap++; PP (dp_ba);                                   /* adv ptrs */
    }
return SCPE_OK;
}

/* Write address to disk */

t_stat dp_wradr (UNIT *uptr, int32 sec, int32 qnr)
{
int32 i;
uint32 da = (sec % DP_TOTSC) * DP_NUMCH;                /* char number */
uint8 *ap = ((uint8 *) uptr->filebuf) + da;             /* buf ptr */

for (i = 0; i < DP_ADDR; i++) {                         /* copy address */
    *ap = M[dp_ba] & (FLAG | DIGIT);                    /* flag + digit */
    if (da >= uptr->hwmark)
        uptr->hwmark = da + 1;
    if (dp_tstgm (*ap, qnr)) {                          /* grp mrk fm mem? */
        dp_fill (uptr, da + 1, DP_NUMCH - i - 1);       /* fill addr+data */
        return STOP_WRLERR;                             /* error */
        }
    da++; ap++; PP (dp_ba);                             /* adv ptrs */
    }
return SCPE_OK;
}

/* Write data to disk */

t_stat dp_wrsec (UNIT *uptr, int32 sec, int32 qnr)
{
int32 i;
uint32 da = ((sec % DP_TOTSC) * DP_NUMCH) + DP_ADDR;    /* char number */
uint8 *ap = ((uint8 *) uptr->filebuf) + da;             /* buf ptr */

for (i = 0; i < DP_DATA; i++) {                         /* copy data */
    *ap = M[dp_ba] & (FLAG | DIGIT);                    /* get character */
    if (da >= uptr->hwmark)
        uptr->hwmark = da + 1;
    if (dp_tstgm (*ap, qnr)) {                          /* grp mrk fm mem? */
        dp_fill (uptr, da + 1, DP_DATA - i - 1);        /* fill data */ 
        return STOP_WRLERR;                             /* error */
        }
    da++; ap++; PP (dp_ba);                             /* adv ptrs */
    }
return SCPE_OK;
}

/* Find sector */

int32 dp_fndsec (UNIT *uptr, int32 sec, t_bool rd)
{
int32 ctrk = sec % (DP_NUMSF * DP_NUMSC);               /* curr trk-sec */
int32 psec = ((uptr->CYL) * (DP_NUMSF * DP_NUMSC)) + ctrk;
int32 da = psec * DP_NUMCH;                             /* char number */
uint8 *ap = ((uint8 *) uptr->filebuf) + da;             /* buf ptr */
int32 dskad, i;

if (dp_zeroad (ap))                                     /* addr zero? ok */
    return psec;
dskad = dp_cvt_ad (ap);                                 /* cvt addr */
if (dskad == sec) {                                     /* match? */
    if (rd || ((*ap & FLAG) == 0))                      /* read or !wprot? */
        return psec;
    ind[IN_DACH] = ind[IN_DERR] = 1;                    /* no match */
    return -1;
    }           
psec = psec - (psec % DP_NUMSC);                        /* sector 0 */
for (i = 0; i < DP_NUMSC; i++, psec++) {                /* check track */
    da = psec * DP_NUMCH;                               /* char number */
    ap = ((uint8 *) uptr->filebuf) + da;                /* word pointer */
    if (dp_zeroad (ap))                                 /* no implicit match */
        continue;
    dskad = dp_cvt_ad (ap);                             /* cvt addr */
    if (dskad == sec) {                                 /* match? */
        if (rd || ((*ap & FLAG) == 0))                  /* read or !wprot? */
            return psec;
        ind[IN_DACH] = ind[IN_DERR] = 1;                /* no match */
        return -1;
        }
    }           
ind[IN_DACH] = ind[IN_DERR] = 1;                        /* no match */
return -1;
}

/* Find next sector - must be sequential, cannot cross cylinder boundary */

t_stat dp_nexsec (UNIT *uptr, int32 sec, int32 psec, t_bool rd)
{
int32 ctrk = psec % (DP_NUMSF * DP_NUMSC);              /* curr trk-sec */
int32 da = psec * DP_NUMCH;                             /* word number */
uint8 *ap = ((uint8 *) uptr->filebuf) + da;             /* buf ptr */
int32 dskad;

if (ctrk) {                                             /* not trk zero? */
    if (dp_zeroad (ap))                                 /* addr zero? ok */
        return SCPE_OK;
    dskad = dp_cvt_ad (ap);                             /* cvt addr */
    if ((dskad == sec) &&                               /* match? */
       (rd || ((*ap & FLAG) == 0)))                     /* read or !wprot? */
       return SCPE_OK;
    ind[IN_DACH] = ind[IN_DERR] = 1;                    /* no, error */
    return STOP_DACERR;
    }
ind[IN_DCYO] = ind[IN_DERR] = 1;                        /* cyl overflow */
return STOP_CYOERR;
}

/* Test for zero address */

t_bool dp_zeroad (uint8 *ap)
{
int32 i;

for (i = 0; i < DP_ADDR; i++, ap++) {                   /* loop thru addr */
    if (*ap & DIGIT)                                    /* nonzero? lose */
        return FALSE;
    }
return TRUE;                                            /* all zeroes */
}

/* Test for group mark when enabled */

t_stat dp_tstgm (uint32 c, int32 qnr)
{
if (!qnr && ((c & DIGIT) == GRP_MARK)) {                /* premature GM? */
    ind[IN_DWLR] = ind[IN_DERR] = 1;                    /* error */
    return STOP_WRLERR;
    }
return SCPE_OK;
}

/* Convert disk address to binary - invalid char force bad address */

int32 dp_cvt_ad (uint8 *ap)
{
int32 i, r;
uint8 c;

for (i = r = 0; i < DP_ADDR; i++, ap++) {               /* loop thru addr */
    c = *ap & DIGIT;                                    /* get digit */
    if (BAD_DIGIT (c))                                  /* bad digit? */
        return -1;
    r = (r * 10) + c;                                   /* bcd to binary */
    }
return r;
}

/* Track operation setup */

int32 dp_trkop (int32 drv, int32 sec)
{
int32 ctrk = (sec / DP_NUMSC) % DP_NUMSF;

return ((drv * DP_TOTSC) + (dp_unit[drv].CYL * DP_NUMSF * DP_NUMSC) +
    (ctrk * DP_NUMSC));
}

/* Convert DCF BCD field to binary */

int32 dp_cvt_bcd (uint32 ad, int32 len)
{
uint8 c;
int32 r;

for (r = 0; len > 0; len--) {                           /* loop thru char */
    c = M[ad] & DIGIT;                                  /* get digit */
    if (BAD_DIGIT (c))                                  /* invalid? */
        return -1;
    r = (r * 10) + c;                                   /* cvt to bin */
    PP (ad);                                            /* next digit */
    }
return r;
}

/* Fill sector buffer with zero */

void dp_fill (UNIT *uptr, uint32 da, int32 cnt)
{
while (cnt-- > 0) {                                     /* fill with zeroes*/
    *(((uint8 *) uptr->filebuf) + da) = 0;
    if (da >= uptr->hwmark)
        uptr->hwmark = da + 1;
    da++;
    }
return;
}

/* Reset routine */

t_stat dp_reset (DEVICE *dptr)
{
int32 i;

for (i = 0; i < DP_NUMDR; i++)                          /* reset cylinder */
    dp_unit[i].CYL = 0;
ind[IN_DACH] = ind[IN_DWLR] = 0;                        /* clr indicators */
ind[IN_DERR] = ind[IN_DCYO] = 0;
return SCPE_OK;
}
