/* sigma_cr.c: Sigma 7120/7122/7140 card reader
 
 Copyright (c) 2024, Ken Rector
 
 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
 KEN RECTOR 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 Ken Rector shall not be
 used in advertising or otherwise to promote the sale, use or other dealings
 in this Software without prior written authorization from Ken Rector.
 
 cr      7120 card reader
 
 27-Feb-2024       kenr      Initial version
 
    The 7120, 7122 and 7140 card readers are described in the SDS
    reference manual, 900970C.
 
    The simulator expects input data to be a file of 120 byte records with no control
    or other extraneous data, to simulate a punched card deck.  Each 120 byte record
    is translated to 80 16 bit columns with data in the loworder 12 bits.  In
    automatic mode each column (1-1/2 bytes) is translated from a hollerith code
    to an ebcdic character (1 byte).  In binary mode each pair of columns is
    translated to 3 data bytes.
 
    A length error in the input data will not be detected until the end of file
    and results in an Invalid Length and Unusual End status.  CPV sets the ignore
    incorrect length flag so this can cause trouble in the symbiont input process.
 
    Card reader speed for the 7120, 7122 and 7140 machines was 400, 400 and 1500
    cards per minute respectively, or 150, 150 and 40 msec per card.  The simulator
    runs much faster than this, transmitting 80 columns in ~400 instruction cycles,
    or 5 cycles per column.
 
    The cr device capacity indicates the number of cards in the hopper and stacker.
    There is no limit on the number of records in the hopper or stacker.  The
    stacker is never emptied and the count can overflow.  The hopper counter is
    set when a file is attached and reduced as each card is read.
 
    The cardreader is detached from the input file when the hopper count
    reaches zero.
 
    The card reader reports a Data Transmission Error if an incorrect EBCDIC character
    is detected, (more than 1 punch in rows 1-7).
 
 */

#include <sys/stat.h>
#include "sigma_io_defs.h"
#include "sim_card.h"


/* Unit status */

#define CDR_DTE        0x08                            /* data error */

/* Device States */

#define CRS_INIT        0x101                           /* feed card */
#define CRS_END         0x102                           /* end card */

#define UST             u3                              /* unit status */
#define UCMD            u4                              /* unit command */

uint32 cr_bptr;                                         /* buffer index */
uint32 cr_blnt;                                         /* buffer length */
uint32 cr_col;                                          /* current column */
uint32 cr_hopper;                                       /* hopper count */
uint32 *cr_stkptr;                                      /* selected stacker */
uint32 cr_stacker = 0;                                  /* stacker count */
uint32 cr_stacker1 = 0;                                 /* stacker 1 count */
uint32 cr_stacker2 = 0;                                 /* stacker 2 count */
uint16 cr_buffer[80];                                   /* 80 column data */
uint32 cr_ebcdic_init = 0;                              /* translate initial flag */
uint16 hol_to_ebcdic[4096];                             /* translation table */

uint8 cr_ord[] = {                                       /* valid order codes*/
    0, 0, 1, 0, 0, 0, 1, 0,
    0, 0, 1, 0, 0, 0, 1, 0,
    0, 0, 1, 0, 0, 0, 1, 0,
    0, 0, 1, 0, 0, 0, 1, 0,
    0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 1, 0, 0, 0, 1, 0,
    0, 0, 1, 0, 0, 0, 1, 0
};

extern uint32 chan_ctl_time;
extern uint16 ebcdic_to_hol[];


uint32 cr_disp (uint32 op, uint32 dva, uint32 *dvst);
t_stat cr_readrec (UNIT *uptr);
uint32 cr_tio_status (void);
uint32 cr_tdv_status (void);
t_stat cr_chan_err (uint32 st);
t_stat cr_svc (UNIT *uptr);
t_stat cr_reset (DEVICE *dptr);
t_stat cr_attach (UNIT *uptr, CONST char *cptr);
t_stat cr_detach (UNIT *uptr);
t_stat cr_show_cap (FILE *st, UNIT *uptr, int32 val, CONST void *desc);


dib_t   cr_dib = { DVA_CR, cr_disp, 0, NULL };

UNIT    cr_unit = {
    UDATA(&cr_svc, UNIT_ATTABLE | UNIT_RO , 0),
    60
};

REG     cr_reg[] = {
    { DRDATA (BPTR, cr_bptr, 17), PV_LEFT },
    { DRDATA (BLNT, cr_blnt, 17), PV_LEFT },
    { NULL }
};

MTAB    cr_mod[] = {
    {MTAB_XTD | MTAB_VDV, 0, "CHANNEL", "CHANNEL",
        &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, 0, "CAPACITY", NULL,
        NULL, &cr_show_cap, NULL, "Card hopper size" },
    {0}
};

DEVICE  cr_dev = {
    "CR", &cr_unit, cr_reg, cr_mod,
    1, 10, 31, 1, 16, 8,
    NULL, NULL, &cr_reset, &io_boot,&cr_attach,&cr_detach,
    &cr_dib, 0
};


/* Card Reader : IO dispatch routine  */

uint32 cr_disp (uint32 op, uint32 dva, uint32 *dvst)
{
    switch (op) {                                       /* case on op */
            
        case OP_SIO:                                    /* start I/O */
            *dvst = cr_tio_status ();                   /* get status */
            if ((*dvst & DVS_AUTO) && !sim_is_active(&cr_unit))  {
                cr_unit.UCMD = CRS_INIT;                /* start dev thread */
                cr_stkptr = &cr_stacker;
                sim_activate (&cr_unit, 0);
            }
            break;
            
        case OP_TIO:                                    /* test status */
            *dvst = cr_tio_status ();                   /* return status */
            break;
            
        case OP_TDV:                                    /* test status */
            *dvst = cr_tdv_status ();                   /* return status */
            break;
            
        case OP_HIO:                                    /* halt I/O */
            chan_clr_chi (cr_dib.dva);                  /* clear int */
            *dvst = cr_tio_status ();
            if ((*dvst & DVS_DST) != 0) {               /* busy? */
                sim_cancel (&cr_unit);                  /* stop dev thread */
                chan_uen (cr_dib.dva);                  /* uend */
            }
            break;
            
        case OP_AIO:                                    /* acknowledge int */
            chan_clr_chi (cr_dib.dva);                  /* clr int*/
            *dvst = 0;                                  /* no status */
            break;
            
        default:
            *dvst = 0;
            return SCPE_IERR;
    }
    
    return 0;
}

/* Service routine */

t_stat cr_svc (UNIT *uptr)
{
    uint32 cmd = uptr->UCMD;
    t_stat st;
    char   c;
    
    if (cmd == CRS_INIT) {                          /* init state */
        st = chan_get_cmd (cr_dib.dva, &cmd);       /* get order */
        if (CHS_IFERR (st))                         /* bad device id, inactive state */
            return cr_chan_err (st);
        if ((cmd > 0x3e) ||                         /* invalid order? */
            (cr_ord[cmd] == 0)) {
            chan_uen (cr_dib.dva);
            return SCPE_OK;
        }
        uptr->UCMD = cmd;                           /* save order */
        cr_blnt = 0;                                /* empty buffer */
        cr_col = 0;                                 /* initial column */
        switch (cmd & 0x30) {                       /* note stacker */
            case 0x10:
                cr_stkptr = &cr_stacker1;
                break;
            case 0x30:
                cr_stkptr = &cr_stacker2;
                break;
            default:
                cr_stkptr = &cr_stacker;
                break;
        }
        sim_activate (uptr, chan_ctl_time);
        return SCPE_OK;
    }
    if (cmd == CRS_END) {                           /* end of card */
        st = chan_end (cr_dib.dva);                 /* set channel end, inactive */
        if (CHS_IFERR (st))                         /* bad dev/inactive? */
            return cr_chan_err (st);
        ++*cr_stkptr;                               /* add to stacker */
        if (--cr_hopper == 0)                       /* remove from hopper */
            cr_detach(uptr);                        /* end input deck */
        if (st == CHS_CCH) {                        /* command chain? */
            uptr->UCMD = CRS_INIT;                  /* restart thread */
            sim_activate (uptr, chan_ctl_time);
        }
        return SCPE_OK;
    }
    
    if (cr_blnt == 0) {                             /* card arriving? */
        if (cr_readrec (uptr) == 0) {               /* unexpected EOF, inv reclnt? */
            uptr->UCMD = CRS_END;                   /* end state */
            sim_activate (uptr, chan_ctl_time);     /* sched ctlr */
            return SCPE_OK;
        }
        if ((cmd & 0x04) &&                         /* mode change? */
            ((cr_buffer[0] & 0x180) == 0x180)) {
            cmd &= ~0x04;                           /* automatic(EBCDIC) and row 1 2 */
            uptr->UCMD = cmd;                       /* switch to binary mode */
        }
    }
    if (cmd & 0x04) {                               /* mode?   */
        int i = 0;                                  /* invalid punches? */
        int n = cr_buffer[cr_bptr++] & 0x1fc;
        c = (char) hol_to_ebcdic[cr_buffer[cr_bptr-1]]; /* automatic  */
        while (n) {                                 /* Kerninghams bit count alg */
            n &= (n-1);                             /* count bits in row 1-7 */
            i++;
        }
        if (i > 1) {                                /* >2 punches? */
            c = 0x00;                               /* return 0x00 */
            uptr->UST |= CDR_DTE;                   /* Transmission Data Error*/
            chan_set_chf (cr_dib.dva, CHF_XMDE);    /* operational status byte */
        }
    }
    else {
        switch (cr_col % 3) {                      /* binary */
            case 0:
                c = ((cr_buffer[cr_bptr] >> 4) & 0xff);
                break;
            case 1:
                c = ((cr_buffer[cr_bptr] & 0x0f) << 4);
                cr_bptr++;
                c |= ((cr_buffer[cr_bptr] & 0xf00) >> 8);
                break;
            case 2:
                c = (cr_buffer[cr_bptr++] & 0xff);
                break;
        }
    }
    cr_col++;
    st = chan_WrMemB (cr_dib.dva, c);               /* write to memory */
    if (CHS_IFERR (st))                             /* channel error? */
        return cr_chan_err (st);
    if ((st != CHS_ZBC) && (cr_bptr != cr_blnt)) {  /* not done? */
        sim_activate (uptr, chan_ctl_time);         /* continue */
        return SCPE_OK;
    }
    if (((st == CHS_ZBC) ^ (cr_bptr == cr_blnt)) && /* length err? */
        chan_set_chf (cr_dib.dva, CHF_LNTE))        /* Incorrect Length */
        return SCPE_OK;                             /* to operational status byte */
    
    uptr->UCMD = CRS_END;                           /* end state */
    sim_activate (uptr, chan_ctl_time);             /* sched ctlr */
    return SCPE_OK;
}


/*  get next record */

t_stat cr_readrec (UNIT *uptr) {
    
    int    col;
    FILE   *fp = uptr->fileref;
    
    for (col = 0; col < 80; ) {
        int16    i;
        int    c1, c2, c3;
        
        c1 = fgetc (fp);                            /* read 3 bytes */
        c2 = fgetc (fp);
        c3 = fgetc (fp);
        if (feof(fp) || (c1 == EOF) || (c2 == EOF) || (c3 == EOF)) {
            cr_blnt = cr_bptr = 0;
            chan_set_chf (cr_dib.dva, CHF_LNTE);
            return 0;
        }
        i = ((c1 << 4) | ( c2 >> 4)) & 0xFFF;       /* pack 1-1/2 bytes per column */
        cr_buffer[col] = i;
        col++;
        i = (((c2 & 017) << 8) | c3) & 0xFFF;
        cr_buffer[col] = i;
        col++;
    }
    cr_bptr = 0;
    cr_blnt = 80;
    return 80;
}


/* CR status routine */

uint32 cr_tio_status (void)
{
    uint32 st;
    
    st = (cr_unit.flags & UNIT_ATT) ? DVS_AUTO: 0;  /* AUTO : MANUAL */
    if (sim_is_active (&cr_unit))                   /* dev busy? */
        st |= ( DVS_CBUSY | DVS_DBUSY | (CC2 << DVT_V_CC));
    return st;
}

uint32 cr_tdv_status (void)
{
    uint32 st;
    
    if (cr_unit.flags & UNIT_ATT &&
        (cr_hopper > 0))                            /* rdr att? */
        st = cr_unit.UST;
    else
        st = (CC2 << DVT_V_CC);
    return st;
}

/* Channel error */

t_stat cr_chan_err (uint32 st)
{
    chan_uen (cr_dib.dva);
    if (st < CHS_ERR)
        return st;
    return SCPE_OK;
}

/* Reset routine */

t_stat cr_reset (DEVICE *dptr)
{
    int i;
    if (!cr_ebcdic_init) {                          /* initialize translate table */
        for (i = 0; i < 4096; i++)
            hol_to_ebcdic[i] = 0x100;               /* a la sim_card */
        for (i = 0; i < 256; i++) {
            uint16     temp = ebcdic_to_hol[i];
            if (hol_to_ebcdic[temp] != 0x100) {
                fprintf(stderr, "Translation error %02x is %03x and %03x\n",
                    i, temp, hol_to_ebcdic[temp]);
            } else {
                hol_to_ebcdic[temp] = i;
            }
        }
        cr_ebcdic_init = 1;
    }
    sim_cancel (&cr_unit);                          /* stop dev thread */
    chan_reset_dev (cr_dib.dva);                    /* clr int, active */
    return SCPE_OK;
}

/* Attach routine */

t_stat cr_attach (UNIT *uptr, CONST char *cptr)
{
    char *saved_filename;
    int r;

    saved_filename = uptr->filename;
    uptr->filename = NULL;
    if ((r = attach_unit(uptr, cptr)) != SCPE_OK) {
        uptr->filename = saved_filename;
        return r;
        }
    r = sim_fsize(uptr->fileref);
    if ((r % 120) != 0) {                           /* multiple of 120 byte cards? */
        detach_unit(uptr);
        fprintf(stderr,"CR file size error\n");
        return SCPE_IOERR;
    }
    cr_hopper = r / 120;
    return SCPE_OK;
}

/* Detach routine */

t_stat cr_detach (UNIT *uptr)
{
    cr_hopper = 0;
    return  detach_unit(uptr);
}

t_stat cr_show_cap (FILE *st, UNIT *uptr, int32 val, CONST void *desc) {
    
    if (cr_hopper == 0)
        fprintf(st,"hopper empty");
    else {
        if (cr_hopper == 1)
            fprintf(st,"1 card");
        else
            fprintf(st,"%d cards",cr_hopper);
        fprintf(st," in hopper");
    }
    fprintf(st,"\nNormal Stacker %d\n",cr_stacker);
    fprintf(st,"Alt Stacker 1 %d\n",cr_stacker1);
    fprintf(st,"Alt Stacker 2 %d",cr_stacker2);
    return SCPE_OK;
}
