/* pdp11_cr.c: CR/CM/CD-11/CD20 card reader simulator

   Copyright (c) 2005-2017, John A. Dundas III
   Portions derived from work by Douglas W. Jones, jones@cs.uiowa.edu
   Portions derived from work by 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
   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.

  ------------------------------------------------------------------------------

   cr        CR11/CD11/CD20 punched and mark sense card reader for SIMH
   The CR11 controller is also compatible with the CM11-F, CME11, and CMS11.

  Information necessary to create this simulation was gathered from
  a number of sources including:

    CR11 Card Reader System Manual, DEC-11-HCRB-D
      http://www.bitsavers.org/pdf/dec/unibus/DEC-11-HCRB-D_CR11_Mar72.pdf
    Various editions of the Peripherals Handbook
    OpenVMS VAX Card Reader, Line Printer, and LPA11-K I/O User's
      Reference Manual, AA-PVXGA-TE
    http://h71000.www7.hp.com/DOC/73final/documentation/pdf/OVMS_VAX_CARD_LP_REF.pdf
    OpenVMS System Manager's Manual, Volume 1: Essentials
      http://h71000.www7.hp.com/DOC/732FINAL/aa-pv5mh-tk/aa-pv5mh-tk.PDF
    CRDRIVER.LIS - CR11 Card Reader Driver, X-9, graciously made available
      by HP
    Various RSTS manuals
    RT-11 Software Support Manual
    RT-11 System Reference Manual, DEC-11-ORUGA-C-D
    Professor Douglas W. Jones's web site:
      http://www.cs.uiowa.edu/~jones/cards/
    Paul Mattes' x026 keypunch simulator
      http://x3270.bgp.nu/x026.html
    CD2SER.MAC - TOPS-10 card reader driver source
      http://pdp-10.trailing-edge.com/custsupcuspmar86_bb-x130b-sb/02/cd2ser.mac
    CDRIVE.MAC - TOPS GALAXY card reader spooler
        http://pdp-10.trailing-edge.com/BB-BT99U-BB_1990/03/10,7/galaxy/cdrive/cdrive.mac
    SPRINT.MAC - TOPS GALAXY control card interpreter
        http://pdp-10.trailing-edge.com/BB-H138C-BM/01/galaxy-sources/sprint.mac
    CDKSDV.MAC - TOPS-20 card reader driver source
        http://pdp-10.trailing-edge.com/BB-Y393K-SM/01/monitor-sources/cdksdv.mac
    PROKS.MAC - TOPS-20 bit definitions
        http://pdp-10.trailing-edge.com/BB-Y393K-SM/01/monitor-sources/proks.mac

  The Card Image format code and documentation is adapted from Prof.
  Jones's site, with his permission.  Please see his site for additional
  documentation as well as the card image utilities referenced in
  his documentation (cardmake, cardlist, etc.).
    http://www.cs.uiowa.edu/~jones/cards/format.html

  Known limitations:
    1. Need a copy of the CR bootstrap (and some way to test it)
    2. Need a copy of the XXDP+ test deck
    3. No testing under RSX; volunteers needed
    4. No testing under Ultrix or Unix for PDP-11; volunteers needed
    5. No testing under Ultrix or Unix for VAX; volunteers needed
    6. The simulator implements a single controller/reader combination

  Operating System Notes

    RT-11 (and CTS-300) support one CR11 or CM11, but no CD11.

    VMS supports multiple CR11 controllers, but no CD11.

    RSTS/E supports either the CR11/CM11 or CD11 but not both in
    the same SIL.  It appears to support only one unit.

    For RSX there exists a CR/CM task handler.  Is there a CD
    handler?

    To-do (RSX): The CR11 unit works as a regular device (ie, 
    you can PIP from it) but it does not work well as a job
    input device (it works just once, somwhow the CRP processor
    gets stuck).
 
    Don't have any information about Unix or Ultrix-11 yet.  Same
    for VAX Unices.

    TOPS: only the CD20 variant of the CD11 is supported.  CD20 implies
    ECOs (at least) for Data Buffer status and augmented image mode.

  Revision History:
   19-Jan-17    RMS     CR11 is BR6, CD11 is BR4
   14-Mar-16    RMS     Added UC15 support (CR11 only)
   23-Feb-13    JGP     Added DEC version of the 026 codepage
                        Fixed the handling of the CR11 error bits after
                        a control register write.
                        Added logic reset after RESET button press
                        Commented and reestructured code (to supress
                        dangling elses)
   03-Jan-10    JAD     Eliminate gcc warnings
   01-Feb-07    RMS     Added PDP-10 support
   12-May-06    JAD     Modify the DEBUG code to use the SIMH DEBUG_x
                        macros.  Modify the UNIT structure to include
                        the DEBUG bit.
                        Mark the trans[] array contents constant.
                        Make device data structures static and constant
                        as appropriate.
   18-Mar-05    JAD     Slight optimization for blank punches recognizing
                        that blank is 0 in all character encodings.
   17-Mar-05    JAD     Completely initialize ascii_code correctly.
                        Define the end of deck punch code separately from
                        the cardcode.i file.
                        Make initTranslation() set a pointer to the correct
                        punch code table to use.  Modify card read functions
                        to use this table pointer.
   16-Mar-05    JAD     Make certain switches passed to the ATTACH command
                        are valid; return error on any others.
                        Make default unit wait time compatible with default
                        device specification.
                        Implement SET TRANSLATION=value.  Still need to
                        modify the H2ASCII table used for text files;
                        currently hard-coded to 029.
   24-Feb-05    JAD     Allow the maintenance bits in CRM to clear as
                        well as set status bits.  Not sure this is the
                        correct behavior, though, without more documentation.
                        Catch three more places to spin down the blower
                        correctly.
                        Zero the CDDB and CRM at INIT.
   17-Feb-05    JAD     When the hopper empties, a pick check should
                        be generated 300ms later.  They are simultaneous
                        for now.
                        Make sure readColumnBinary() generates a complete
                        EOF card.
   08-Feb-05    JAD     Replace blowerWait with different times for blower
                        spin up and down.
   06-Feb-05    JAD     After DETACH: mark CD offline, set appropriate
                        blower state.
                        Make sure unit wait time is recalculated every
                        time cpm is set.
   04-Feb-05    JAD     Better tracking of blower state throughout driver.
                        Make sure IE gets cleared for CR at INIT.
                        Normalize error response in read routines.
                        Finish condition handling for column binary.
   02-Feb-05    JAD     Remove Qbus support; Unibus only.
                        Support ATTACH switches:
                        A - ASCII, B - column binary, I - Card Image
                        If none given, check for .TXT or .CBN; if none,
                        examine file for magic header.
                        Finer granularity to blower state.  Expose this
                        variable to examine/deposit from SIMH.
                        Preliminary implementation of support for
                        column binary format.
   24-Jan-05    JAD     Make AUTOEOF work as a surrogate for the EOF
                        button of a CD11 reader.  May need to separate
                        this later, though.
                        Partial implementation of DATAERR for CD11.
                        Implement the Rev. J mods (as best I understand
                        them) to the CD11 affecting the CDDB used as a
                        second status register.
   23-Jan-05    JAD     Preliminary clean-up of CD state transitions.
                        Tested with RSTS/E (V9.1-05).
   22-Jan-05    JAD     Finish CR state transitions; should be close now.
                        Tested with RSTS/E (V9.1-05), RT-11 (V5.3), and
                        VAX/VMS (V7.2).
   19-Jan-05    JAD     Add bounds to the RATE command; also default and
                        help a la the XQ driver.
                        Improved handling of empty files.
   17-Jan-05    JAD     Add the CR maintenance register.
   16-Jan-05    JAD     Add preliminary CD11 support.
                        Simulate the STOP and RESET switches.
   14-Jan-05    JAD     Add the ability to automatically generate an 'EOF'
                        card recognized by DEC operating systems when
                        reading ASCII files.
   08-Jan-05    JAD     Original creation and testing
*/

/* Configuration notes:
 * Keep VM_arch symbols here and use them only to select features.  
 * CR attributes use generic symbols so device support is easy to change,
 * e.g. if software is discovered that uses a previously unsupported option. 
 * Conventions:
 * *_ONLY (AND *_req) means feature * is unconditionally present/required.
 * *_OK means feature * is selectable at runtime.
 * neither means feature is not present.
 * To support only one controller model, define <model>_ONLY.
 * To support more than one, define them all as <model>_OK.
 * Don't mix "_ONLY" and "_OK" for the same feature.  You won't like it.
 *
 * The CD/CR will work on any UNIBUS, and the CR will also work on a QBUS.
 * The configuration options used here are more restrictive to reflect 
 * known software support, as this reduces user configuration errors/confusion.
 */


#if defined (VM_PDP10)                                  /* PDP10 version */
#include "pdp10_defs.h"
#define IPL_CD          (IPL_CR)                        /* use same for CD */
#define INT_V_CD        (INT_V_CR)
#define INT_CD          (INT_CR)
#define DFLT_QB         (0)
#define DFLT_TYPE       (UNIT_CD20)                     /* CD20 (CD11) only */
#define DFLT_IVCL       (IVCL(CD))
#define DFLT_CPM        1200
#define CD20_ONLY       (1)
#define AIECO_REQ       (1)                             /* Requires Augmented Image ECO */
#elif defined (VM_VAX)                                  /* VAX version */
#include "vax_defs.h"
#define IPL_CD          (IPL_CR)                        /* use same for CD */
#define INT_V_CD        (INT_V_CR)
#define INT_CD          (INT_CR)
#define DFLT_QB         (DEV_QBUS)                      /* CR11 is programmed I/O only, Qbus OK */
#define DFLT_TYPE       (UNIT_CR11)                     /* CR11 only */
#define DFLT_IVCL       (IVCL(CR))
#define DFLT_CPM        285
#define CR11_ONLY       (1)
#else                                                   /* PDP-11 version */
#include "pdp11_defs.h"
#define DFLT_QB         (DEV_QBUS)                      /* CR11 is programmed I/O only, Qbus OK */
#define DFLT_TYPE       (UNIT_CR11)                     /* Default, but changable */
#define DFLT_IVCL       (IVCL(CR))
#define DFLT_CPM        285
#if defined (UC15)
#define CR11_ONLY       (1)
#else
#define CR11_OK         (1)                             /* only on real PDP-11 */
#define CD11_OK         (1)
#define CD20_OK         (1)
#define AIECO_OK        (1)                             /* Augmented Image ECO optional */
#endif
#endif

/* **** No VM_xxx macros should be referenced after this line **** */

/* create a int32 constant from four characters */
#define I4C(a,b,c,d)    (((a) << 24) | ((b) << 16) | ((c) << 8) | (d))
#define I4C_CBN         I4C ('C','B','N',' ')
#define I4C_H80         I4C ('H','8','0',' ')
#define I4C_H82         I4C ('H','8','2',' ')
#define I4C_H40         I4C ('H','4','0',' ')

#define    UNIT_V_TYPE      (UNIT_V_UF + 0)             /* Bit-encoded 2-bit field */
#define    UNIT_TYPE        (3u << UNIT_V_TYPE)
#define    UNIT_CR11        (1u << UNIT_V_TYPE)
#define    UNIT_CD20        (2u << UNIT_V_TYPE)

#define    UNIT_V_AUTOEOF   (UNIT_V_UF + 2)
#define    UNIT_AUTOEOF     (1u << UNIT_V_AUTOEOF)
#define    UNIT_V_RDCHECK   (UNIT_V_UF + 3)
#define    UNIT_RDCHECK     (1u << UNIT_V_RDCHECK)
#define    UNIT_V_AIECO     (UNIT_V_UF + 4)
#define    UNIT_AIECO       (1u << UNIT_V_AIECO)

/* Tests for which device is being emulated.
 * Note that CD20 is a CD11 + mandatory ECOs.  CD11_CTL will be true for both.
 */
#if defined (CD11_ONLY) || defined (CD20_ONLY)
#define CR11_CTL(up) (0)
#define CD11_CTL(up) (1)
#elif defined (CR11_ONLY)
#define CR11_CTL(up) (1)
#define CD11_CTL(up) (0)
#else
#define CR11_CTL(up) ((up)->flags & UNIT_CR11)
#define CD11_CTL(up) (!CR11_CTL(up))
#endif

#if defined (CD20_ONLY)
#define CD20_CTL(up) (1)
#elif defined (CD20_OK)
#define CD20_CTL(up) ((up)->flags & UNIT_CD20)
#else
#define CD20_CTL(up) (0)
#endif

/* Configuration */
#if defined (AIECO_REQ)
#define DFLT_AIECO   (UNIT_AIECO)
#else
#define DFLT_AIECO   (0)
#endif

#define    CR_ER            (00404)
#include "pdp11_cr_dat.h"
#define    PUNCH_EOD        (07417)
#define    PUNCH_SPACE      (0)                          /* same for all encodings */

/* CR */
/* also use CSR_ERR, CSR_IE, and CSR_GO */
#define    CRCSR_V_CRDDONE  14                          /* card done */
#define    CRCSR_V_SUPPLY   13                          /* supply error */
#define    CRCSR_V_RDCHK    12                          /* reader check */
#define    CRCSR_V_TIMERR   11                          /* timing error */
#define    CRCSR_V_ONLINE   10                          /* on line */
#define    CRCSR_V_BUSY     9                           /* busy reading */
#define    CRCSR_V_OFFLINE  8                           /* off line AKA READY? */
#define    CRCSR_V_COLRDY   7                           /* column ready */
#define    CRCSR_V_EJECT    1                           /* ignore card */

#define    CRCSR_CRDDONE    (1u << CRCSR_V_CRDDONE)
#define    CRCSR_SUPPLY     (1u << CRCSR_V_SUPPLY)
#define    CRCSR_RDCHK      (1u << CRCSR_V_RDCHK)
#define    CRCSR_TIMERR     (1u << CRCSR_V_TIMERR)
#define    CRCSR_ONLINE     (1u << CRCSR_V_ONLINE)
#define    CRCSR_BUSY       (1u << CRCSR_V_BUSY)
#define    CRCSR_OFFLINE    (1u << CRCSR_V_OFFLINE)
#define    CRCSR_COLRDY     (1u << CRCSR_V_COLRDY)
#define    CRCSR_EJECT      (1u << CRCSR_V_EJECT)

#define CRCSR_IMP           (CSR_ERR | CRCSR_CRDDONE | CRCSR_SUPPLY |    \
                             CRCSR_RDCHK | CRCSR_TIMERR | CRCSR_ONLINE | \
                             CRCSR_BUSY | CRCSR_OFFLINE | CRCSR_COLRDY | \
                             CSR_IE | CRCSR_EJECT)
#define CRCSR_RW            (CSR_IE | CRCSR_EJECT | CSR_GO)    /* read/write */

#define    CRM_V_MAINT      15                          /* enable maint funct */
#define    CRM_V_BUSY       14
#define    CRM_V_READY      13
#define    CRM_V_HOPPER     12

#define    CRM_MAINT        (1u << CRM_V_MAINT)
#define    CRM_BUSY         (1u << CRM_V_BUSY)
#define    CRM_READY        (1u << CRM_V_READY)
#define    CRM_HOPPER       (1u << CRM_V_HOPPER)

/* CD */
/* also use CSR_ERR, CSR_IE, and CSR_GO */
/* ERR */
#define    CDCSR_V_RDRCHK   14                          /* reader check: HOPPER,STACK,PICK,READ */
#define    CDCSR_V_EOF      13                          /* CD11-E EOF button */
#define    CDCSR_V_OFFLINE  12                          /* off line */
#define    CDCSR_V_DATAERR  11                          /* data packing error */
#define    CDCSR_V_LATE     10                          /* data late */
#define    CDCSR_V_NXM      9                           /* non-existent memory */
#define    CDCSR_V_PWRCLR   8                           /* power clear */
#define    CDCSR_V_RDY      7                           /* ready */
/* IE */
#define    CDCSR_V_XBA17    5                           /* NPR bus address bits<17:16> */
#define    CDCSR_V_XBA16    4
#define    CDCSR_V_ONLINE   3                           /* on line transition */
#define    CDCSR_V_HOPPER   2                           /* hopper check */
#define    CDCSR_V_PACK     1                           /* data packing */
/* GO */

#define    CDCSR_RDRCHK     (1u << CDCSR_V_RDRCHK)
#define    CDCSR_EOF        (1u << CDCSR_V_EOF)
#define    CDCSR_OFFLINE    (1u << CDCSR_V_OFFLINE)
#define    CDCSR_DATAERR    (1u << CDCSR_V_DATAERR)
#define    CDCSR_LATE       (1u << CDCSR_V_LATE)
#define    CDCSR_NXM        (1u << CDCSR_V_NXM)
#define    CDCSR_PWRCLR     (1u << CDCSR_V_PWRCLR)
#define    CDCSR_RDY        (1u << CDCSR_V_RDY)
#define    CDCSR_XBA17      (1u << CDCSR_V_XBA17)
#define    CDCSR_XBA16      (1u << CDCSR_V_XBA16)
#define    CDCSR_ONLINE     (1u << CDCSR_V_ONLINE)
#define    CDCSR_HOPPER     (1u << CDCSR_V_HOPPER)
#define    CDCSR_PACK       (1u << CDCSR_V_PACK)

#define    CDCSR_ANYERR     (CDCSR_RDRCHK | CDCSR_EOF | CDCSR_OFFLINE | CDCSR_DATAERR | CDCSR_LATE | CDCSR_NXM)

#define    CDCSR_IMP        (CSR_ERR | CDCSR_RDRCHK | CDCSR_EOF | CDCSR_OFFLINE | \
                             CDCSR_DATAERR | CDCSR_LATE | CDCSR_NXM | \
                             CDCSR_PWRCLR | CDCSR_RDY | CSR_IE | \
                             CDCSR_XBA17 | CDCSR_XBA16 | CDCSR_ONLINE | \
                             CDCSR_HOPPER | CDCSR_PACK | CSR_GO)

#define    CDCSR_RW         (CDCSR_PWRCLR | CSR_IE | CDCSR_XBA17 | CDCSR_XBA16 | \
                             CDCSR_PACK | CSR_GO)

/* CD11 second status register bits.  Valid only when not busy.  All
 * also set CDCSR_RDRCK (and CSR_ERR)
 */

#define CDDB_V_READ        14 /* Read check (extra punches, not readER check) */
#define CDDB_V_PICK        13 /* Pick check (card present, not grabbed) */
#define CDDB_V_STACK       12 /* Card did not arrive in stacker */

/* N.B. Per TOPS-20 driver, which references CD11 manual and printset: 
 * Stacker full is indicated by:
 * CDCSR_RDRCHK && !(CDDB_READ|CDDB_PICK|CDDB_STACK)
 */
#define CDDB_READ       (1U << CDDB_V_READ)
#define CDDB_PICK       (1u << CDDB_V_PICK)
#define CDDB_STACK      (1u << CDDB_V_STACK)


/* Blower state values */
#define    BLOW_OFF         (0)                         /* steady state off */
#define    BLOW_START       (1)                         /* starting up */
#define    BLOW_ON          (2)                         /* steady state on */
#define    BLOW_STOP        (3)                         /* shutting down */

/* Card Reader state */
static const char *cardFormat = "unknown";
static t_bool   (*readRtn)(UNIT *, int16 *, char *, char *);
static char     ascii_code[4096];                       /* 2^12 possible values */
static int      currCol;                                /* current column when reading */
static int      colStart;                               /* starting column */
static int      colEnd;                                 /* ending column */
static const int *codeTbl =                            /* punch translation table */
#if defined(CD20_ONLY) || (defined(DFLT_TYPE) && (DFLT_TYPE == UNIT_CD20))
    o29_decascii_code;
#else
    o29_code;
#endif
static  struct trans {
    const char *const name;
    const int  *table;
} transcodes[] = {
    { "DEFAULT", o29_code, },
    { "026", o26_dec_code, },
    { "026FTN", o26_ftn_code, },
    { "026DECASCII", o26_decascii_code, },
    { "029", o29_code, },
    { "EBCDIC", EBCDIC_code, },
    { "026DEC", o26_dec_code, },
    { "029DECASCII", o29_decascii_code },
};
#define NTRANS (sizeof transcodes /sizeof transcodes[0])

static int32    blowerState = BLOW_OFF;                 /* reader vacuum/blower motor */
static int32    spinUp = 3000000;                       /* blower spin-up time: 3 seconds (usec) */
static int32    spinDown = 2000000;                     /* blower spin-down time: 2 seconds (usec) */
static int      EOFcard = 0;                            /* played special card yet? */
static t_bool   eofPending = FALSE;                     /* Manual EOF switch pressed */
static int32    cpm = DFLT_CPM;                         /* reader rate: cards per minute */
static int      schedule_svc=0;                         /* Re-schedule service if true */
/* card image in various formats */
static int16    hcard[82];                              /* Hollerith format */
static char     ccard[82];                              /* DEC compressed format */
static char     acard[82];                              /* ASCII format */
/* CR/CM registers */
static int32    crs = CSR_ERR | CRCSR_OFFLINE | CRCSR_SUPPLY; /* control/status */
static int32    crb1 = 0;                               /* 12-bit Hollerith characters */
static int32    crb2 = 0;                               /* 8-bit compressed characters */
static int32    crm = 0;                                /* CMS maintenance register */
/* CD registers */
static int32    cdst = CSR_ERR | CDCSR_OFFLINE | CDCSR_HOPPER; /* Control/status - off-line until attached */
static int32    cdcc = 0;                               /* column count */
static int32    cdba = 0;                               /* current address, low 16 bits */
static int32    cddb = 0;                               /* data, 2nd status */
static int32    cddbs = 0;                              /* second status bits (or with cddb) */

/* forward references */
static void setupCardFile (UNIT *, int32);
t_stat cr_rd (int32 *, int32, int32);
t_stat cr_wr (int32, int32, int32);
int32  cr_intac(void);
t_stat cr_svc (UNIT *);
t_stat cr_reset (DEVICE *);
t_stat cr_attach (UNIT *, CONST char *);
t_stat cr_detach (UNIT *);
t_stat cr_set_type (UNIT *, int32, CONST char *, void *);
t_stat cr_set_aieco (UNIT *, int32, CONST char *, void *);
t_stat cr_show_format (FILE *, UNIT *, int32, CONST void *);
t_stat cr_set_rate (UNIT *, int32, CONST char *, void *);
t_stat cr_show_rate (FILE *, UNIT *, int32, CONST void *);
t_stat cr_set_reset (UNIT *, int32, CONST char *, void *);
t_stat cr_set_stop (UNIT *, int32, CONST char *, void *);
t_stat cr_set_eof (UNIT *, int32, CONST char *, void *);
t_stat cr_show_eof (FILE *, UNIT *, int32, CONST void *);
t_stat cr_set_trans (UNIT *, int32, CONST char*, void *);
t_stat cr_show_trans (FILE *, UNIT *, int32, CONST void *);
static t_stat cr_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr);
const char *cr_description (DEVICE *dptr);
void cr_set_int (void);
void cr_clr_int (void);

/* CR data structures

   cr_dib   CR device information block
   cr_unit  CR unit descriptor
   cr_reg   CR register list
   cr_mod   CR modifier table
   cr_dev   CR device descriptor
*/

#define IOLN_CR         010

static DIB cr_dib = { IOBA_AUTO, IOLN_CR, &cr_rd, &cr_wr,
        1, DFLT_IVCL, VEC_AUTO, { cr_intac } };

static UNIT cr_unit = {
    UDATA (&cr_svc,
      UNIT_ATTABLE+UNIT_SEQ+UNIT_ROABLE+UNIT_DISABLE+
      DFLT_TYPE+UNIT_AUTOEOF+UNIT_RDCHECK+DFLT_AIECO, 0),
        (60 * 1000000) / (DFLT_CPM * 80) };

static const REG cr_reg[] = {
    { GRDATAD (BUF,    cr_unit.buf, DEV_RDX,  8, 0, "ASCII value of last column processed") },
#if defined (CR11_OK) || defined (CR11_ONLY)
    { GRDATAD (CRS,            crs, DEV_RDX, 16, 0, "CR11 status register") },
    { GRDATAD (CRB1,          crb1, DEV_RDX, 16, 0, "CR11 12-bit Hollerith character") },
    { GRDATAD (CRB2,          crb2, DEV_RDX, 16, 0, "CR11 8-bit compressed character") },
    { GRDATAD (CRM,            crm, DEV_RDX, 16, 0, "CR11 maintenance register") },
    { FLDATA (INTCR,    IREQ (CR), INT_V_CR) },
#endif
#if defined (CD11_OK) || defined (CD11_ONLY) || defined (CD20_OK) || defined (CD20_ONLY)
    { GRDATAD (CDST,          cdst, DEV_RDX, 16, 0, "CD11 control/status register") },
    { GRDATAD (CDCC,          cdcc, DEV_RDX, 16, 0, "CD11 column count") },
    { GRDATAD (CDBA,          cdba, DEV_RDX, 16, 0, "CD11 current bus address") },
    { GRDATAD (CDDB,          cddb, DEV_RDX, 16, 0, "CD11 data buffer, 2nd status") },
    { FLDATA (INTCD,    IREQ (CD), INT_V_CD) },
#endif
    { GRDATAD (BLOWER, blowerState, DEV_RDX,  2, 0, "blower state value") },
    { FLDATAD (ERR,            crs, CSR_V_ERR,      "error flag (CRS<15>)") },
    { FLDATAD (IE,             crs, CSR_V_IE,       "interrupt enable flag (CRS<6>)") },
    { DRDATAD (POS,    cr_unit.pos, T_ADDR_W,       "file position - do not alter"), PV_LEFT },
    { DRDATAD (TIME,  cr_unit.wait, 24,             "delay time between columns"), PV_LEFT },
    { GRDATA (DEVADDR,   cr_dib.ba, DEV_RDX, 32, 0), REG_HRO },
    { GRDATA (DEVVEC,   cr_dib.vec, DEV_RDX, 16, 0), REG_HRO },
    { GRDATA (DEVVLOC, cr_dib.vloc, DEV_RDX, 16, 0), REG_HRO },
    { NULL }  };

static char *translation_help = NULL;
static MTAB cr_mod[] = {
#if defined (CR11_OK)
    { UNIT_TYPE, UNIT_CR11, "CR11", "CR11", 
        &cr_set_type, NULL, NULL, "Set device type to CR11" },
#endif
#if defined (CD11_OK)
    { UNIT_TYPE,         0, "CD11", "CD11", 
        &cr_set_type, NULL, NULL, "Set device type to CD11" },
#endif
#if defined (CD20_OK)
    { UNIT_TYPE, UNIT_CD20, "CD20", "CD20", 
        &cr_set_type, NULL, NULL, "Set device type to CD20" },
#endif
#if defined (CR11_ONLY) || defined (CD11_ONLY) || defined (CD20_ONLY)
    { UNIT_TYPE, UNIT_CR11, "CR11", NULL, },
    { UNIT_TYPE,         0, "CD11", NULL, },
    { UNIT_TYPE, UNIT_CD20, "CD20", NULL, },
#endif
#if defined (AIECO_OK)
    { (UNIT_TYPE|UNIT_AIECO), (UNIT_CD20|UNIT_AIECO), "augmented image ECO", "AIECO",
        &cr_set_aieco, NULL, NULL, "Enable CD20 augmented image ECO" },
    { (UNIT_TYPE|UNIT_AIECO), (UNIT_CD20|0),          "standard", "NOAIECO",
        &cr_set_aieco, NULL, NULL, "Disable CD20 augmented image ECO" },
#endif
    { UNIT_AUTOEOF, UNIT_AUTOEOF, "auto EOF", "AUTOEOF", 
        NULL, NULL, NULL, "Enable auto EOF mode" },
    { UNIT_AUTOEOF,            0, "no auto EOF", "NOAUTOEOF", 
        NULL, NULL, NULL, "Disable auto EOF mode" },
#if !defined (CR11_ONLY)
    { UNIT_RDCHECK, UNIT_RDCHECK, "read check", "RDCHECK",
      NULL, NULL, NULL, "Enable read check errors" },
    { UNIT_RDCHECK,            0, "no read check", "NORDCHECK",
      NULL, NULL, NULL, "Disable read check errors" },
#endif
      /* card reader STOP switch */
    { MTAB_XTD|MTAB_VDV, 0, NULL, "STOP",
        &cr_set_stop, NULL, NULL, "Pulse reader Stop button" },
    /* card reader RESET switch */
    { MTAB_XTD|MTAB_VDV, 0, NULL, "RESET",
        &cr_set_reset, NULL, NULL, "Pulse reader reset button" },
#if !defined (CR11_ONLY)
    /* card reader EOF switch */
    { MTAB_XTD|MTAB_VDV, MTAB_XTD|MTAB_VDV, "EOF pending", "EOF",
        &cr_set_eof, &cr_show_eof, NULL, "Pulse reader EOF button" },
#endif
    { MTAB_XTD|MTAB_VUN, 0, "FORMAT", NULL,
        NULL, &cr_show_format, NULL, "Set reader input format" },
    { MTAB_XTD|MTAB_VDV|MTAB_VALR, 006, "ADDRESS", "ADDRESS",
        &set_addr, &show_addr, NULL, "Bus address" },
    { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "VECTOR", "VECTOR",
        &set_vec, &show_vec, NULL, "Interrupt vector" },
    { MTAB_XTD|MTAB_VDV, 0, "RATE", "RATE={DEFAULT|200..1200}",
        &cr_set_rate, &cr_show_rate, NULL, "Display input rate" },
    { MTAB_XTD|MTAB_VDV, 0, "TRANSLATION",
        NULL,           /* Populated by cr_reset */
        &cr_set_trans, &cr_show_trans, NULL, "Display translation mode" },
    { 0 }  };

DEVICE cr_dev = {
    "CR", &cr_unit, (REG *)&cr_reg, (MTAB *)&cr_mod,
    1, 10, 31, 1, DEV_RDX, 8,
    NULL, NULL, &cr_reset,
    NULL, &cr_attach, &cr_detach,
    &cr_dib, DEV_DISABLE | DEV_DIS | DFLT_QB | DEV_UBUS | DEV_DEBUG, 0,
    NULL, NULL, NULL, &cr_help, NULL, NULL,
    &cr_description
    };

/* Utility routines */

/*
These functions read a "card" from a virtual deck file (passed in
fp) and fill in three arrays.  The first array 'hcard' contains the
12-bit binary image of the punch in each column; the second array
'ccard' contains the 8-bit DEC encoded representation of the
corresponding column; the third array 'acard' contains the ASCII
representation (if possible) of the character.  The routines return
TRUE if a card was read (possibly with errors) and FALSE if the
"hopper is empty" (EOF) or fatal file errors prevented any portion
of a card from being read.

Note that the hopper becomes empty when the last card moves to the
read station.  Thus hopper empty without an error means that data
from that card is valid.  Thus hopper empty is first signalled when
the NEXT card read would return EOF.  Reads after that will return
some error bit.

Errors other than EOF are signaled out of band in the controller
state variables.  Possible errors are data in columns 0 or 81
(signalled as read check; currently these columns are ignored), or
any file errors (signalled as motion check).

Might rethink this.  Should probably treat file errors as "pick
check".  Retry 3 times.  After that, give up with error.

*/

/* Common handling for end of file and errors on input */

static t_bool fileEOF ( UNIT  *uptr,
                        int16 *hcard,
                        char  *ccard,
                        char  *acard,
                        int32  cddbsBits )
{
    int col;

    if (DEBUG_PRS (cr_dev))
        fprintf (sim_deb, "hopper empty-eof\n");

    if (!EOFcard && (uptr->flags & UNIT_AUTOEOF) && !ferror(uptr->fileref)) {
        EOFcard = -1;
        /* Generate EOD card, which empties the hopper */
        for (col = 1; col <= 8; col++) {
            hcard[col] = PUNCH_EOD;
            ccard[col] = (char)h2c_code[PUNCH_EOD];
            acard[col] = ' ';
        }
        while (col <= colEnd) {
            hcard[col] = PUNCH_SPACE;
            ccard[col] = PUNCH_SPACE;
            acard[col] = ' ';
            col++;
        }
        /* The CR11 doesn't set SUPPPLY at this time, but waits until the EOF card is done. */
        cdst |= CDCSR_HOPPER;
        return (TRUE);
    }
    
    /* Not auto EOF, or EOF already handled. This is an attempt to read
     * with an empty hopper.  Report a pick, read or stacker check as well
     * as hopper empty to indicate that no data was transfered.  One might
     * think that cdcc unchanged would be sufficient, but that's not what
     * the OSs check.
     */

    crs |= CSR_ERR | CRCSR_SUPPLY | CRCSR_OFFLINE;
    crs &= ~(CRCSR_COLRDY | CRCSR_ONLINE);

    cdst |= CSR_ERR | CDCSR_RDRCHK | CDCSR_HOPPER;
    cddbs |= cddbsBits;

    if (((uptr->flags & UNIT_AUTOEOF) || eofPending) && !ferror(uptr->fileref)) {
        cdst |= CDCSR_EOF;
        eofPending = FALSE;
    }
    return (FALSE);
}

static t_bool readCardImage (   UNIT    *uptr,
                                int16   *hcard,
                                char    *ccard,
                                char    *acard    )
{
    int    c1, c2, c3, col;
    FILE   *fp = uptr->fileref;

    if (DEBUG_PRS (cr_dev))
        fprintf (sim_deb, "readCardImage pos %d\n", (int) ftell (fp));
    do {
        /* get card header bytes */
        c1 = fgetc (fp);
        c2 = fgetc (fp);
        c3 = fgetc (fp);
        uptr->pos = ftell (fp);
        /* check for EOF */
        if (c1 == EOF)
            return fileEOF (uptr, hcard, ccard, acard, CDDB_PICK);

        /* check for valid card header */
        if ((c2 == EOF) || (c3 == EOF) || ((c1 & c2 & c3 & 0x80) == 0) ) {
            if (DEBUG_PRS (cr_dev))
                fprintf (sim_deb, "header error\n");
            /* unexpected EOF or format problems */
            return fileEOF (uptr, hcard, ccard, acard, CDDB_READ);
        }

        /* Read card image into internal buffer */

        ASSURE (colStart < colEnd);
        ASSURE (colStart >= 0);
        ASSURE (colEnd <= 81);
        for (col = colStart; col < colEnd; ) {
            int16    i;
            int    c1, c2, c3;
            /* get 3 bytes */
            c1 = fgetc (fp);
            c2 = fgetc (fp);
            c3 = fgetc (fp);
            uptr->pos = ftell (fp);
            if (ferror (fp) || (c1 == EOF) || (c2 == EOF) || (c3 == EOF)) {
                if (DEBUG_PRS (cr_dev))
                    fprintf (sim_deb, "file error\n");
                    /* signal error; unexpected EOF, format problems, or file error(s) */
                return  fileEOF (uptr, hcard, ccard, acard, ferror(fp)? CDDB_READ: CDDB_PICK);
            }
            /* convert to 2 columns */
            i = ((c1 << 4) | ( c2 >> 4)) & 0xFFF;
            hcard[col] = i;
            ccard[col] = (char)h2c_code[i];
            acard[col] = ascii_code[i];
            col++;

            i = (((c2 & 017) << 8) | c3) & 0xFFF;
            hcard[col] = i;
            ccard[col] = (char)h2c_code[i];
            acard[col] = ascii_code[i];
            col++;
        }
    } while ((c3 & 0x3f) == 0x3f); /* Skip metacards (Revised Jones spec) */

    if (DEBUG_PRS (cr_dev))
        fprintf (sim_deb, "successfully loaded card\n");
    return (TRUE);
}

static t_bool readColumnBinary (    UNIT    *uptr,
                                    int16   *hcard,
                                    char    *ccard,
                                    char    *acard    )
{
    int    col;
    FILE   *fp = uptr->fileref;

    for (col = colStart; col <= colEnd; col++) {
        int c1, c2;
        uint16 i;
        c1 = fgetc (fp);
        c2 = fgetc (fp);
        uptr->pos = ftell (fp);
        if (c1 == EOF)
            return fileEOF (uptr, hcard, ccard, acard, CDDB_PICK);
        if ((c2 == EOF) || ferror(fp))
            return fileEOF (uptr, hcard, ccard, acard, CDDB_READ);
        i = (c1 & 077) | ((c2 & 077) << 6);
        hcard[col] = i;
        ccard[col] = (char)h2c_code[i];
        acard[col] = ascii_code[i];
    }
    return (TRUE);
}

/*

Should this routine perform special handling of non-printable,
(e.g., control) characters or characters that have no encoded
representation? (In DEC026/DEC029 they all do...)

*/

static t_bool readCardASCII (   UNIT    *uptr,
                                int16   *hcard,
                                char    *ccard,
                                char    *acard    )
{
    int    c = 0, col, peek;
    FILE   *fp = uptr->fileref;

    ASSURE (colStart < colEnd);
    ASSURE (colStart >= 1);
    ASSURE (colEnd <= 80);

    if (DEBUG_PRS (cr_dev))
        fprintf (sim_deb, "readCardASCII\n");
    for (col = colStart; col <= colEnd; ) {
        switch (c = fgetc (fp)) {
        case EOF:
            if (ferror (fp)) {
                uptr->pos = ftell (fp);
                return fileEOF (uptr, hcard, ccard, acard, CDDB_READ);
            }
            if (col == colStart) {
                if (DEBUG_PRS (cr_dev))
                    fprintf (sim_deb, "hopper empty\n");
                uptr->pos = ftell (fp);
                return fileEOF (uptr, hcard, ccard, acard, CDDB_PICK);
            }
            /* fall through */
        case '\r':
            peek = fgetc (uptr->fileref);
            if ((peek != EOF) && (peek != '\n'))
                ungetc (peek, uptr->fileref);
            goto fill;
        case '\n':
            peek = fgetc (uptr->fileref);
            if ((peek != EOF) && (peek != '\r'))
                ungetc (peek, uptr->fileref);
          fill:
            while (col <= colEnd) {
                hcard[col] = PUNCH_SPACE;
                ccard[col] = PUNCH_SPACE;
                acard[col] = ' ';
                col++;
            }
            break;
        case '\t':
            do {
                hcard[col] = PUNCH_SPACE;
                ccard[col] = PUNCH_SPACE;
                acard[col] = ' ';
                col++;
            } while (((col & 07) != 1) && (col <= colEnd));
            break;
        default:
            hcard[col] = (uint16)codeTbl[c & 0177];
            /* check for unrepresentable ASCII characters */
            if (hcard[col] == CR_ER) {
                cdst |= CDCSR_DATAERR;
                if (DEBUG_PRS (cr_dev))
                    fprintf (sim_deb,
                        "error character at column %d (%c)\n",
                            col, c & 0177);
            }
            ccard[col] = (char)h2c_code[hcard[col]];
            acard[col] = (char)c;
            col++;
            break;
        }
    }
    /* silently truncate/flush long lines, or flag over-length card? */
    if (c != '\n' && c != '\r') {
        if (DEBUG_PRS (cr_dev))
            fprintf (sim_deb, "truncating card\n");
        c = fgetc (fp);
        while (c != EOF) {
            if ((c == '\n') || (c == '\r')) {
                peek = fgetc (uptr->fileref);
                if (peek == EOF)
                    break;
                if (((c == '\n') && (peek != '\r')) || ((c == '\r') && (peek != '\n')))
                    ungetc (peek, uptr->fileref);
                break;
            }
            c = fgetc (fp);
        }
    }
    if (DEBUG_PRS (cr_dev))
        fprintf (sim_deb, "successfully loaded card\n");
    uptr->pos = ftell (fp);
    return (TRUE);
}

/*

Initialize the binary translation table.  Generally called when a
new deck is attached but could be set manually as well.

*/

static void initTranslation (void)
{
    int32    i;

    memset (ascii_code, '~', sizeof (ascii_code));
    for (i = 0; i < 0177; i++)
        ascii_code[codeTbl[i]] = (char)i;
}

/*

Examine the command switches, file extension, and virtual card deck
file to determine the format.  Set up the global variables
appropriately.  Rewind ASCII files to the beginning

*/

static void setupCardFile ( UNIT    *uptr,
                            int32   switches    )
{
    int32    i;

    if (switches & SWMASK ('A'))
        i = 0;
    else if (switches & SWMASK ('B'))
        i = I4C_CBN;
    else if (switches & SWMASK ('I'))
        goto read_header;
    else if (match_ext (uptr->filename, "TXT"))
        i = 0;
    else if (match_ext (uptr->filename, "CBN"))
        i = I4C_CBN;
    else {
read_header:
        /* look for card image magic file number */
        i = fgetc (uptr->fileref);
        i = (i << 8) | fgetc (uptr->fileref);
        i = (i << 8) | fgetc (uptr->fileref);
        i = (i << 8) | ' ';
    }
    switch (i) {
    case I4C_H80:
        colStart = 1;
        colEnd = 80;
        cardFormat = "card image";
        readRtn = readCardImage;
        break;
    case I4C_H82:
        colStart = 0;
        colEnd = 81;
        cardFormat = "card image";
        readRtn = readCardImage;
        break;
    case I4C_H40:
        colStart = 1;
        colEnd = 40;
        cardFormat = "card image";
        readRtn = readCardImage;
        break;
    case I4C_CBN:
        colStart = 1;
        colEnd = 80;
        cardFormat = "column binary";
        readRtn = readColumnBinary;
        break;
    default:
        colStart = 1;
        colEnd = 80;
        cardFormat = "ASCII";
        readRtn = readCardASCII;
        fseek (uptr->fileref, 0L, SEEK_SET);
        break;
    }
    initTranslation ();
    if (DEBUG_PRS (cr_dev))
        fprintf (sim_deb, "colStart = %d, colEnd = %d\n",
            colStart, colEnd);
    cr_unit.pos = ftell (uptr->fileref);
}

/* Card reader routines

   cr_rd        I/O page read
   cr_wr        I/O page write
   cr_svc       process event (reader ready)
   cr_reset     process reset
   cr_attach    process attach
   cr_detach    process detach
*/

t_stat cr_rd (  int32   *data,
                int32   PA,
                int32   access    )
{
    switch ((PA >> 1) & 03) {
    case 0:        /* CSR */
        if (cdst & (CDCSR_ANYERR))
            cdst |= CSR_ERR;
        else
            cdst &= ~CSR_ERR;
        *data = (CR11_CTL(&cr_unit)) ?
            crs & CRCSR_IMP : cdst & CDCSR_IMP;
        /* CR: if error removed, clear 15, 14, 11, 10 */
        if (DEBUG_PRS (cr_dev))
            fprintf (sim_deb, "cr_rd crs %06o cdst %06o\n",
                crs, cdst);
        break;
    case 1:
        /* Get word of data from crb1 (Hollerith code) or CD11 CC */
        *data = (CR11_CTL(&cr_unit)) ? crb1 : cdcc;
        crs &= ~CRCSR_COLRDY;
        if (DEBUG_PRS (cr_dev)) {
        if (CR11_CTL(&cr_unit))
            fprintf (sim_deb, "cr_rd crb1 %06o '%c' %d\n",
                     crb1, cr_unit.buf, cr_unit.buf);
        else
            fprintf (sim_deb, "cr_rd cdcc %06o\n", cdcc);
        }
        /* Does crb1 clear after read? Implied by VMS driver. */
        crb1 = 0;
        break;
    case 2:
        /* Get word of data from crb2 (DEC Compressed) or CD11 BA */
        *data = (CR11_CTL(&cr_unit)) ? crb2 : cdba;
        crs &= ~CRCSR_COLRDY;
        if (DEBUG_PRS (cr_dev)) {
            if (CR11_CTL(&cr_unit))
                fprintf (sim_deb, "cr_rd crb2 %06o\n", crb2);
            else
                fprintf (sim_deb, "\r\ncr_rd cdba %06o\n", cdba);
        }
       crb2 = 0;    /* see note for crb1 */ 
       break;
    case 3:
    default:
        if (CR11_CTL(&cr_unit)) /* CR11 maintenance */
            *data = crm;
        else /* CD11 data buffer/status.  Note this implementation returns extended 
              * status even while busy (rather than the zone).  Might be wrong.
              */
            *data = 0100000 | (cddbs & (CDDB_READ|CDDB_PICK|CDDB_STACK)) |
                 ((crs & CRCSR_BUSY) ?
                cddb & 0777 : 0777);
        if (DEBUG_PRS (cr_dev))
            fprintf (sim_deb, "cr_rd crm %06o cddb %06o data %06o\n",
                crm, cddb, *data);
        break;
    }
    return (SCPE_OK);
}

t_stat cr_wr (  int32   data,
                int32   PA,
                int32   access    )
{
    int curr_crs = crs;     /* Save current crs to recover status */

    switch ((PA >> 1) & 03) {
    case 0:
        if (CR11_CTL(&cr_unit)) {
            /* ignore high-byte writes */
            if (PA & 1)
                break;
            /* fixup data for low byte write */
            if (access == WRITEB)
                data = (crs & ~0377) | (data & 0377); 
            if (!(data & CSR_IE))
                cr_clr_int ();
            crs = (crs & ~CRCSR_RW) | (data & CRCSR_RW);
            /* Clear status bits after CSR load */
            crs &= ~(CSR_ERR | CRCSR_ONLINE | CRCSR_CRDDONE | CRCSR_TIMERR);
            if (crs & CRCSR_OFFLINE)
                crs |= CSR_ERR;
            /* 
             * Read card requested:
             * Check if there was any error which required an operator
             * intervention, and if so, reassert the corresponding
             * error bits and assert interrupt.
             * (Expected by the VMS CRDRIVER)
             */
            if (data & CSR_GO) {
                if (curr_crs & (CRCSR_SUPPLY | CRCSR_RDCHK | CRCSR_OFFLINE)) {
                    crs |= CSR_ERR | (curr_crs & (CRCSR_SUPPLY | CRCSR_RDCHK |
                                                  CRCSR_OFFLINE));
                    if (crs & CSR_IE) cr_set_int ();
                }
                if (blowerState != BLOW_ON) {
                    blowerState = BLOW_START;
                    sim_activate_after (&cr_unit, spinUp);
                } else {
                    sim_activate_after (&cr_unit, cr_unit.wait);
                }
            }
            if (DEBUG_PRS (cr_dev))
                fprintf (sim_deb, "cr_wr data %06o crs %06o\n",
                         data, crs);
        } else { /* CD11 */
            if (access == WRITEB)
                data = (PA & 1)? (((data & 0xff)<<8) | (cdst & 0x00ff)):
                                  ((data & 0x00ff) | (cdst & 0xFF00));

            if (data & CDCSR_PWRCLR) {
                cr_clr_int ();
                sim_cancel (&cr_unit);
                cdcc = 0;
                cdba = 0;
                cddb = 0;
                cddbs = 0;
                if (!(cr_unit.flags & UNIT_ATT)) { /* Clear troublesome bits, but leave error/offline */
                    cdst &= ~(CSR_IE | CDCSR_DATAERR | CDCSR_LATE | CDCSR_NXM | CDCSR_RDY |
                              CDCSR_XBA17 | CDCSR_XBA16 | CDCSR_ONLINE | CDCSR_PACK);
                    cdst |= CSR_ERR | CDCSR_OFFLINE | CDCSR_RDRCHK;
                    cddbs |= CDDB_STACK;
                    break;
                }

                crs &= ~CRCSR_BUSY;
                cdst &= (CDCSR_OFFLINE | CDCSR_RDY | CDCSR_HOPPER);
                if( (cr_unit.flags & UNIT_ATT) && !feof(cr_unit.fileref) && !ferror(cr_unit.fileref) )
                    cdst &= ~(CDCSR_HOPPER);
                if (cdst & (CDCSR_ANYERR))
                    cdst |= CSR_ERR;
                cdst |= CDCSR_RDY;
                break;
            }

            if (data & CSR_GO) {
                /* To simplify the service code, don't start if CDCC == 0.
                 * In the hardware, it's not sensible...
                 */
                if ((crs & CRCSR_BUSY) || (cdcc == 0)) {
                    cdst |= (CDCSR_RDRCHK | CDCSR_HOPPER | CSR_ERR);
                } else {
                    cdst &= ~(CDCSR_RDRCHK | CDCSR_DATAERR | CDCSR_LATE | CDCSR_NXM | CDCSR_RDY | CDCSR_ONLINE);
                    cdst = (cdst & ~(CDCSR_EOF | CSR_IE | CDCSR_XBA17 | CDCSR_XBA16 | CDCSR_PACK | CDCSR_HOPPER))
                         | (data &  (CDCSR_EOF | CSR_IE | CDCSR_XBA17 | CDCSR_XBA16 | CDCSR_PACK));
                    cddbs &= ~(CDDB_READ|CDDB_PICK|CDDB_STACK);

                    /* Always attempt to start.  If not attached, errors will set after delay */
                    if (!(cdst & CDCSR_HOPPER) )
                        cdst &= ~(CSR_ERR);
                    if (blowerState != BLOW_ON) {
                        blowerState = BLOW_START;
                        sim_activate_after (&cr_unit, spinUp);
                    } else {
                        sim_activate_after (&cr_unit, cr_unit.wait);
                    }
                }
            } else {
                cdst = (cdst & ~(CSR_ERR | CDCSR_RDRCHK | CDCSR_EOF | CDCSR_DATAERR | CDCSR_LATE | 
                                 CDCSR_NXM | CSR_IE | CDCSR_XBA17 | CDCSR_XBA16 | CDCSR_ONLINE | CDCSR_PACK))
                      |(data &  (CSR_ERR | CDCSR_RDRCHK | CDCSR_EOF | CDCSR_DATAERR | CDCSR_LATE | 
                                 CDCSR_NXM | CSR_IE | CDCSR_XBA17 | CDCSR_XBA16 | CDCSR_ONLINE | CDCSR_PACK));
            }
            /* Apparently the hardware does not set int if ready/online are already set.  If it did, TOPS-10's driver wouldn't work */
            if (!(cdst & CSR_IE))
                cr_clr_int ();

            if (DEBUG_PRS (cr_dev))
                fprintf (sim_deb, "cr_wr data %06o cdst %06o\n",
                    data, cdst);
        }
        break;
    case 1:
        if (DEBUG_PRS (cr_dev))
            fprintf (sim_deb, "cr_wr cdcc %06o\n", data);
        if (CD11_CTL(&cr_unit))
            cdcc = data & 0177777;
        break;
    case 2:
        if (DEBUG_PRS (cr_dev))
            fprintf (sim_deb, "cr_wr crba %06o\n", data);
        if (CD11_CTL(&cr_unit))
            cdba = data & 0177777;
        break;
    case 3:
        if (DEBUG_PRS (cr_dev))
            fprintf (sim_deb, "cr_wr cddb/crm %06o\n", data);
        /* ignore writes to cddb */
        if (CD11_CTL(&cr_unit))
            break;

        /* fixup data for byte writes and read-modify-write */
        if (access == WRITEB)
            data = (PA & 1) ?
                (crm & 0377) | (data << 8) :
                (crm & ~0377) | (data & 0377);
        crm = data & 0177777;
        /* not 100% certain how these work */
        if (!(crm & CRM_MAINT))
            break;
        crs = (crm & CRM_BUSY) ?
            (crs | CRCSR_BUSY) : (crs & ~CRCSR_BUSY);
        crs = (crm & CRM_READY) ?
            (crs | CRCSR_OFFLINE) : (crs & ~CRCSR_OFFLINE);
        crs = (crm & CRM_HOPPER) ?
            (crs | CRCSR_SUPPLY | CRCSR_RDCHK) :
            (crs & ~(CRCSR_SUPPLY | CRCSR_RDCHK));
        crb1 = crm & 07777;    /* load low 12 bits */
        break;
    default:
        /* can't happen */
        break;
    }
    return (SCPE_OK);
}

/*
 * Interrupt acknowledge routine
 * Reschedule service routine if needed (based on 
 * schedule_svc flag).
 * Do the actual scheduling just for the CR11 (VAX/PDP11). The PDP10 does
 * not seem to call this entry point.
 */

int32 cr_intac() {
    if CR11_CTL(&cr_unit) {
        if (schedule_svc) {
            sim_activate_after (&cr_unit, cr_unit.wait);
            schedule_svc = 0;
        }
    }
    return cr_dib.vec;      /* Constant interrupt vector */
}

/*
Enter the service routine once for each column read from the card.
CR state bits drive this primarily (see _BUSY and _CRDDONE).  However,
when in CD mode, also execute one column of DMA input.
*/
t_stat cr_svc ( UNIT    *uptr    )
{
    uint32    pa;
    uint8    c;
    uint16   w;
    int      n;

    /* Blower stopping: set it to OFF and do nothing */
    if (blowerState == BLOW_STOP) {
        blowerState = BLOW_OFF;
        return (SCPE_OK);
    }
    /* Blower starting: set it to ON and do regular service */
    if (blowerState == BLOW_START)
        blowerState = BLOW_ON;

    /* (almost) anything we do now will cause a CR (But not a CD) interrupt */
    if ((CR11_CTL(uptr)) && (crs & CSR_IE))
       cr_set_int ();
    
    /* Unit not attached, or error status while idle */
    if (!(uptr->flags & UNIT_ATT) || (!(crs & CRCSR_BUSY) && ((CR11_CTL(uptr)?crs : cdst) & CSR_ERR))) {
        if (CD11_CTL(uptr)) {
            if (!(uptr->flags & UNIT_ATT)){
                cdst |= (CDCSR_HOPPER | CDCSR_RDRCHK | CDCSR_OFFLINE | CSR_ERR);
                cddbs |= CDDB_STACK;
            }
            if (cdst & CSR_IE)
                cr_set_int ();
        }
        return (SCPE_OK);
    }

    /* End of card: unit busy and column past end column */
    if ((crs & CRCSR_BUSY) && (currCol > colEnd)) {
        /* clear busy state and set card done bit */
        crs &= ~(CRCSR_BUSY | CRCSR_COLRDY);
        crs |= CRCSR_CRDDONE;

         if (DEBUG_PRS (cr_dev))
            fprintf (sim_deb, "cr_svc card done\n");
         
         /* Check CD11 error status that stops transfers */
        if (CD11_CTL(uptr) && (cdst & (CDCSR_LATE | CDCSR_NXM))) {
            cdst |= CSR_ERR | CDCSR_OFFLINE | CDCSR_RDY | CDCSR_RDRCHK;
            cr_set_int ();
            return (SCPE_OK);
        }

        if (CR11_CTL(uptr))
            return (SCPE_OK);

        /* If a CD11 gets this far, an interrupt is required.  If CDCC != 0,
         * continue reading the next card.
         */
        cr_set_int ();
        if (cdcc == 0)
            return (SCPE_OK);
    }

    /* If unit is not busy: try to read a card */ 
    if (!(crs & CRCSR_BUSY)) {
        crs &= ~CRCSR_CRDDONE; /* This line WAS commented out - JGP 2013.02.05 */

        /* Call the appropriate read card routine.
         * If no card is read (FALSE return), we tried to read with an empty hopper.
         * The card read routine set the appropriate error bits.  Shutdown. 
         */
        if (!readRtn (uptr, hcard, ccard, acard)) {
            blowerState = BLOW_STOP;
            if (CD11_CTL(uptr)) {
readFault:
                cdst |= CDCSR_RDY;
                if (cdst & (CDCSR_RDRCHK | CDCSR_HOPPER))
                    cdst |= CSR_ERR | CDCSR_OFFLINE;
                if (cdst & CSR_IE)
                    cr_set_int ();

            } else {
                /*
                 * CR11 handling: assert SUPPLY and ERROR bits,
                 * put de device offline and DO NOT TRIGGER AN INTERRUPT
                 * (if the interrupt is asserted RSX and VMS will get 80
                 * bytes of garbage, and RSX could crash).
                 */
                if (crs & (CRCSR_RDCHK | CRCSR_SUPPLY)) {
                    crs |= CSR_ERR | CRCSR_OFFLINE;
                    crs &= ~(CRCSR_ONLINE | CRCSR_BUSY | CRCSR_CRDDONE);
                    cr_clr_int ();
                }
            }
            sim_activate_after (uptr, spinDown);
            return (SCPE_OK);
        }
        
        /* Card read: reset column counter and assert BUSY */
        currCol = colStart;
        crs |= CRCSR_BUSY;

        /* Update status if this read emptied hopper.
         * The CR11 doesn't set SUPPLY until after the last card is read.
         */
       
        /* I/O error status bits have been set during read.
         * Look ahead to see if another card is in file.
         */
        n = feof (uptr->fileref);
        if (n)
            n = EOF;
        else {
            n = fgetc (uptr->fileref);
            if (n != EOF)
                ungetc (n, uptr->fileref);
        }

        if ((n == EOF) && ((EOFcard > 0) || !(uptr->flags & UNIT_AUTOEOF))) {
            /* EOF and generated EOFcard sent or not an autoEOF unit.
             * Set status to reflect last card taken.
             */
            cdst |= (CDCSR_RDRCHK | CSR_ERR | CDCSR_OFFLINE | CDCSR_HOPPER);
            if (eofPending) {
                cdst |= CDCSR_EOF;
                eofPending = FALSE;
            }
        }
        
        if (EOFcard)
            EOFcard = 1;
        
        
        if (CD11_CTL(uptr)) {
             /* Handle read check: punches in col 0 or 81/last (DEC only did 80 cols, but...) */
            if ((uptr->flags & UNIT_RDCHECK) && 
                (((colStart == 0) && (hcard[0] != 0)) || ((colEnd & 1) && (hcard[colEnd] != 0)))) {
                cdst |= (CDCSR_RDRCHK | CSR_ERR);
                cddbs |= CDDB_READ;
                if (1) /* 0 if read check should transfer card */
                    goto readFault;
            }
            /* CDDB_PICK, CDDB_STACK, <stacker full) */
        }
    }

    /* check for overrun (timing error) */
    if (CR11_CTL(uptr) && (crs & CRCSR_COLRDY))
        crs |= CSR_ERR | CRCSR_TIMERR;

    /* Update the "buffer" registers with current column */
    crb1 = hcard[currCol] & 07777;      /* Hollerith value */
    crb2 = ccard[currCol] & 0377;       /* DEC compressed hollerith value */
    uptr->buf = acard[currCol] & 0377;  /* Helpful for debug: ASCII value */

    /* CD11 specific code follows */
    if (CD11_CTL(uptr)) {
        pa = cdba | ((cdst & 060) << 12);
/*
The implementation of _NXM here is not quite the same as I interpret
the (limited) documentaiton I have to indicate.  However the effect
should be similar.  Documentation indicates that once _NXM is set,
further NPR requests are inhibited though the card is allowed to
read until completion.  This implies that CDBA and the XBA bits are
incremented accordingly, even though no data transfer occurs.  This
code detects and flags the NXM condition but allows attempts at
subsequent memory writes, thus insuring the address registers are
incremented properly.  If this causes problems, I'll fix it.
*/
        if (cdst & CDCSR_PACK)
            cddb = c = ccard[currCol] & 0377;
        else
            cddb = w = hcard[currCol] & 07777;  /* Punched zones: <12><11><0><1><2><3><4><5><6><7><8><9> */

        if (cdcc == 0) /* Transfer requires CC non-zero */
             cdst |= CDCSR_LATE;
        else {
            if (cdst & CDCSR_PACK) {
                if (Map_WriteB (pa, 1, &c))
                    cdst |= CDCSR_NXM;
                pa = (pa + 1) & 0777777;
            } else {
                /* "Augmented Image" - provides full column binary and packed encoding in 15 bits.
                 * Bits <14:12> encode which zone, if any, of 1-7 is punched.  0 => none, otherwise zone #.
                 * Bit 15 set indicates that more than one punch occured in zones 1-7; in this case the packed
                 * encoding is not valid. (Card may be binary data.)  
                 * This was probably an ECO to the CD11.  TOPS-10/20 depend on it, so it's definitely in the CD20.
                 */
                if (uptr->flags & UNIT_AIECO) {
                    uint16 z;
                    w |= ((ccard[currCol] & 07) << 12);     /* Encode zones 1..7 - same as 'packed' format */
                    z = w & 0774;
                    if ((z & -z) != z)                      /* More than one punch in 1..7 */
                        w |= 0100000;                       /* sets Hollerith (encoding) failure (not an error) */
                }
                if (Map_WriteW (pa, 2, &w))
                    cdst |= CDCSR_NXM;
                pa = (pa + 2) & 0777777;
            }
            cdba = pa & 0177777;
            cdst = (cdst & ~(CDCSR_XBA17|CDCSR_XBA16)) |
                ((pa & 0600000) >> 12);
            cdcc = (cdcc + 1) & 0177777;
            /* Interrupt at end of buffer; read continues to end of card.
             * If this is the last column, defer interrupt so end doesn't interrupt again.
             */
            if ((cdcc == 0) && (cdst & CSR_IE) && (currCol < colEnd))
                cr_set_int ();
        }
    } else { /* CR11 */
        /* Handle EJECT bit: if set DO NOT assert COLRDY */
        /* nor interrupt                                 */
        if ((crs & CRCSR_EJECT)) {
            cr_clr_int ();
        } else {
            crs |= CRCSR_COLRDY;
        }
    }

    /* CD11 and CR11 */
    currCol++;    /* advance the column counter */

    /* Schedule next service cycle */
    /* CR11 (VAX/PDP11): just raise the schedule_svc flag; the intack 
     * routine will do the actual rescheduling.
     * CD11/20 (PDP10): Do the rescheduling (the intack seems to do nothing)
     */
    if (CD11_CTL(uptr)) {
        sim_activate_after(uptr, uptr->wait);
    } else {
        schedule_svc = 1;
    }
    return (SCPE_OK);
}

t_stat cr_reset (   DEVICE  *dptr    )
{
    if (DEBUG_PRS (cr_dev))
        fprintf (sim_deb, "cr_reset\n");

    if (!translation_help) {
        size_t i;
        const char trans_hlp[] = "TRANSLATION={";
        size_t size = sizeof(trans_hlp) + 2;

        for ( i = 0; i < NTRANS; i++ )
            size += strlen (transcodes[i].name)+1;
        translation_help = (char *)malloc (size);
        strlcpy (translation_help, trans_hlp, size);
        for (i = 0; i < NTRANS; i++) {
            strlcat (translation_help, transcodes[i].name, size);
            strlcat (translation_help,"|", size);
        }
        translation_help[strlen (translation_help) - 1] = '\0';
        strlcat (translation_help, "}", size);
        for (i = 0; i < (sizeof cr_mod / sizeof cr_mod[0]); i++ ) 
            if (cr_mod[i].pstring && !strcmp(cr_mod[i].pstring, "TRANSLATION")) {
                cr_mod[i].mstring = translation_help;
                break;
            }
    }
    cr_unit.buf = 0;
    currCol = 1;
    crs &= ~(CSR_ERR|CRCSR_CRDDONE|CRCSR_TIMERR|CRCSR_ONLINE|CRCSR_BUSY|
         CRCSR_COLRDY|CSR_IE|CRCSR_EJECT|CSR_GO);
    if (crs & (CRCSR_OFFLINE))
        crs |= CSR_ERR;
    crb1 = 0;
    crb2 = 0;
    crm = 0;
    cdst &= ~(CSR_ERR|CDCSR_RDRCHK|CDCSR_EOF|CDCSR_DATAERR|CDCSR_LATE|
          CDCSR_NXM|CSR_IE|CDCSR_XBA17|CDCSR_XBA16|CDCSR_ONLINE|
          CDCSR_PACK|CSR_GO);
    cdst |= CDCSR_RDY;
    if (cdst & CDCSR_ANYERR)
        cdst |= CSR_ERR;
    cdcc = 0;
    cdba = 0;
    cddb = 0;
    /* ATTACHed doesn't mean ONLINE; set CR reset (pushing the reset switch)
     * is what puts the reader on-line.  Reset doesn't control fingers.
     */
    if ((cr_unit.flags & UNIT_ATT) && !feof (cr_unit.fileref)) {
        if (!(crs & CRCSR_OFFLINE))
            crs |= CRCSR_ONLINE;    /* non-standard */
        crs &= ~(CRCSR_RDCHK | CRCSR_SUPPLY );
        cdst &= ~(CDCSR_RDRCHK | CDCSR_HOPPER);
        cddbs = 0;
    } else {
        cdst |= CSR_ERR | CDCSR_RDRCHK | CDCSR_HOPPER;
        cddbs |= CDDB_STACK;
        crs |= CSR_ERR | CRCSR_RDCHK | CRCSR_SUPPLY;
    }
    sim_cancel (&cr_unit);        /* deactivate unit */
    if (blowerState != BLOW_OFF) {
        blowerState = BLOW_STOP;
        sim_activate_after (&cr_unit, spinDown);
    }
    EOFcard = 0;
    CLR_INT (CR);
    CLR_INT (CD);
    /* TBD: flush current card */
    /* init uptr->wait ? */
    return auto_config (dptr->name, 1);
}

/*
Handle the interface status and SIMH portion of the ATTACH.  Another
routine is used to evaluate the file and initialize other state
globals correctly.
*/

#define    MASK    (SWMASK('A')|SWMASK('B')|SWMASK('I')|SWMASK('R'))

/* Attach unit                                                              */
/* This should simulate physically putting a stack of cards into the hopper */
/* No bits should change, nor an interrupt should be asserted               */
/* This is a change of behaviour respect to the previous code               */
t_stat cr_attach (  UNIT    *uptr,
                    CONST char    *cptr    )
{
    t_stat        reason;

    if (sim_switches & ~MASK)
        return (SCPE_INVSW);
    /* file must previously exist; kludge */
    sim_switches |= SWMASK ('R');
    reason = attach_unit (uptr, cptr);
    if(uptr->flags & UNIT_ATT) {
        setupCardFile(uptr, sim_switches);
    }
    
    return (reason);
}

/* Detach unit: assert SUPPLY and OFFLINE bits (and ERR) */
t_stat cr_detach (  UNIT    *uptr    )
{
    crs |= CSR_ERR | CRCSR_SUPPLY | CRCSR_OFFLINE;
    /* interrupt? */
    crs &= ~CRCSR_ONLINE;
    cdst |= CSR_ERR | CDCSR_HOPPER | CDCSR_OFFLINE;
    cardFormat = "unknown";
    if (blowerState != BLOW_OFF) {
        blowerState = BLOW_STOP;
        sim_activate_after (uptr, spinDown);
    }
    return (detach_unit (uptr));
}

void cr_set_int (void)
{
if (CR11_CTL (&cr_unit)) {
    SET_INT (CR);
    }
else {
    SET_INT (CD);
    }
}

void cr_clr_int (void)
{
if (CR11_CTL (&cr_unit)) {
    CLR_INT (CR);
    }
else {
    CLR_INT (CD);
    }
}


#if defined (CR11_OK) || defined (CD11_OK) || defined (CD20_OK)
t_stat cr_set_type (    UNIT    *uptr,
                        int32   val,
                        CONST char    *cptr,
                        void    *desc    )
{
    DEVICE *dptr = find_dev_from_unit (uptr);
    DIB *dibp;

    /* disallow type change if currently attached */

    if (uptr->flags & UNIT_ATT)
        return (SCPE_NOFNC);
    if (dptr == NULL)
        return SCPE_IERR;
    if ((dibp = (DIB *) dptr->ctxt) == NULL)
        return SCPE_IERR;
    if (val == UNIT_CR11) {
        dptr->flags |= DEV_QBUS;                    /* Can be a Qbus device - programmed I/O only */
    } else {                                        /* CD11/CD20 are 18bit DMA devices */
        if (!UNIBUS)
            return SCPE_NOFNC;
        dptr->flags &= ~DEV_QBUS;                   /* Not on a Qbus (22bit) */
    }
    cpm = (val & UNIT_CR11) ? 285 : ((val & UNIT_CD20)? 1200 :1000);
    uptr->wait = (60 * 1000000) / (cpm * 80);       /* Time between columns in usec.
                                                     * Readers are rated in card/min for 80 column cards */
    transcodes[0].table = (val & UNIT_CD20)? o29_decascii_code : o29_code;
    dibp->vloc = (val & UNIT_CR11)? IVCL (CR): IVCL (CD);

    return (SCPE_OK);
}
#endif

#if defined (AIECO_OK)
t_stat cr_set_aieco (    UNIT    *uptr,
                        int32   val,
                        CONST char    *cptr,
                        void    *desc    )
{
    /* disallow eco change if currently attached or not CD20 */

    if (uptr->flags & UNIT_ATT || !CD20_CTL(uptr))
        return (SCPE_NOFNC);

    uptr->flags = (uptr->flags & ~UNIT_AIECO) | (val & UNIT_AIECO);
    return (SCPE_OK);
}
#endif

t_stat cr_show_format ( FILE    *st,
                        UNIT    *uptr,
                        int32   val,
                        CONST void    *desc    )
{
    fprintf (st, "%s format", cardFormat);
    return (SCPE_OK);
}

t_stat cr_set_rate (    UNIT    *uptr,
                        int32   val,
                        CONST char    *cptr,
                        void    *desc    )
{
    t_stat    status = SCPE_OK;
    int32    i;

    if (!cptr)
        return (SCPE_MISVAL);
    if (strcmp (cptr, "DEFAULT") == 0)
        i = CR11_CTL(uptr) ? 285 : (CD20_CTL(uptr)? 1200 :1000);
    else
        i = (int32) get_uint (cptr, 10, 0xFFFFFFFF, &status);
    if (status == SCPE_OK) {
        if (i < 200 || i > 1200)
            status = SCPE_ARG;
        else {
            cpm = i;
            uptr->wait = (60 * 1000000) / (cpm * 80);   /* Time between columns in usec.
                                                         * Readers are rated in card/min for 80 column cards */
        }
    }
    return (status);
}

t_stat cr_show_rate (   FILE    *st,
                        UNIT    *uptr,
                        int32   val,
                        CONST void    *desc    )
{
    fprintf (st, "%d cards per minute", cpm);
    return (SCPE_OK);
}

/* simulate pressing the card reader RESET button  */
/* Per CR11 docs, transition to ONLINE, reset card */
/* reader logic.                                   */
/* RESET is somewhat of a misnomer; START is the function */

t_stat cr_set_reset (   UNIT    *uptr,
                        int32   val,
                        CONST char    *cptr,
                        void    *desc    )
{
    if (DEBUG_PRS (cr_dev))
        fprintf (sim_deb, "cr_set_reset\n");
/*
 Ignore the RESET switch while a read cycle is in progress or the
 unit simply is not attached.
 */
    if ((crs & CRCSR_BUSY) || !(uptr->flags & UNIT_ATT))
        return (SCPE_OK);
 
    /* if no errors, signal transition to on line */
    crs |= CRCSR_ONLINE;
    /* Clear error bits                           */
    crs &= ~(CSR_ERR|CRCSR_CRDDONE|CRCSR_SUPPLY|CRCSR_RDCHK|CRCSR_TIMERR|
                     CRCSR_OFFLINE|CRCSR_BUSY|CRCSR_COLRDY|CRCSR_EJECT|CSR_GO);
    cdst |= CDCSR_ONLINE;
    cdst &= ~(CSR_ERR | CDCSR_OFFLINE | CDCSR_RDRCHK | CDCSR_HOPPER |
          CDCSR_EOF);
    /* I don't think the hardware clears these errors, but TOPS-10 seems to expect it.
     * Since we know the reader is idle, and this is OPR intervention, it seems safe.
    */
    cdst &= ~(CDCSR_LATE | CDCSR_NXM);

    /* Assert interrupt if interrupts enabled     */
    if ((CR11_CTL(uptr)?crs : cdst) & CSR_IE) {
        cr_set_int ();
        if (DEBUG_PRS (cr_dev))
            fprintf (sim_deb, "cr_set_reset setting interrupt\n");
    }

    /* Reset controller status */
    cr_unit.buf = 0;
    currCol = 1;
    crb1 = 0;
    crb2 = 0;
    cdcc = 0;
    cdba = 0;
    cddb = 0;
    cddbs = 0;
    EOFcard = 0;
    
    /* start up the blower if the hopper is not empty 
    if (blowerState != BLOW_ON) {
        blowerState = BLOW_START;
        sim_activate_after(uptr, spinUp);
    }
    */
    return (SCPE_OK);
}

/* simulate pressing the card reader STOP button */

t_stat cr_set_stop (    UNIT    *uptr,
                        int32   val,
                        CONST char    *cptr,
                        void    *desc    )
{
    if (DEBUG_PRS (cr_dev))
        fprintf (sim_deb, "set_stop\n");
    crs &= ~CRCSR_ONLINE;
    crs |= CSR_ERR | CRCSR_OFFLINE;
    cdst |= CSR_ERR | CDCSR_OFFLINE;
    /* CD11 does not appear to interrupt on STOP. */
    if (CR11_CTL(uptr) && (crs & CSR_IE))
        cr_set_int ();
    if (blowerState != BLOW_OFF) {
        blowerState = BLOW_STOP;
    }
    return (SCPE_OK);
}

/* simulate pressing the card reader EOF button */

t_stat cr_set_eof (    UNIT    *uptr,
                        int32   val,
                        CONST char    *cptr,
                        void    *desc    )
{
    if (DEBUG_PRS (cr_dev))
        fprintf (sim_deb, "set_eof\n");
    eofPending = 1;

    return (SCPE_OK);
}

t_stat cr_show_eof ( FILE    *st,
                     UNIT    *uptr,
                     int32   val,
                     CONST void    *desc    )
{
    fprintf (st, (eofPending? "EOF pending": "no EOF pending"));
    return (SCPE_OK);
}

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

    if (!cptr)
        return (SCPE_MISVAL);

    for (i = 0; i < NTRANS; i++) {
        if (strcmp (cptr, transcodes[i].name) == 0)
            break;
    }
    if (i >= NTRANS)
        return (SCPE_ARG);
    codeTbl = transcodes[i].table;
    initTranslation ();    /* reinitialize tables */
    return (SCPE_OK);
}

t_stat cr_show_trans (  FILE    *st,
                        UNIT    *uptr,
                        int32   val,
                        CONST void    *desc    )
{
    size_t i;

    for (i = 1; i < NTRANS; i++ )
        if (transcodes[i].table == codeTbl) {
            fprintf (st, "translation=%s", transcodes[i].name);
            return SCPE_OK;
        }
    fprintf (st, "translation=%s", transcodes[0].name);
    return (SCPE_OK);
}

/* Only used from here to EOF, so not passing size of string.
 * This ugliness is more maintainable than a preprocessor mess.
 */

static void cr_supported ( char *string, int32 *bits, size_t string_aize )
{
int32 crtypes = 0;
#define MAXDESCRIP sizeof ("CR11/CD11/CD20/") /* sizeof includes \0 */
char devtype[MAXDESCRIP] = "";

#if defined (CR11_ONLY) || defined (CR11_OK)
    crtypes |= 1;
#endif
#if defined (CD11_ONLY) || defined (CD11_OK)
    crtypes |= 2;
#endif
#if defined (CD20_ONLY) || defined (CD20_OK)
    crtypes |= 4;
#endif

if (string) {
    if (crtypes & 1)
        strlcat (devtype, "CR11/", sizeof (devtype));
    if (crtypes & 2)
        strlcat (devtype, "CD11/", sizeof (devtype));
    if (crtypes & 4)
        strlcat (devtype, "CD20/", sizeof (devtype));
    strlcpy (string, devtype, string_aize);
    if (string[strlen (string) - 1] == '/')
        string[strlen (string) - 1] = '\0';
}
if (bits)
    *bits = crtypes;
}

static t_stat cr_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr)
{
char devtype[MAXDESCRIP];
int32 crtypes;

cr_supported (devtype, &crtypes, sizeof (devtype));
fprintf (st, "%s Card Reader (CR)\n\n", devtype);
fprintf (st, "The card reader (CR) implements a single controller (the model(s) shown\n");
fprintf (st, "above) and a card reader (e.g., Documation M200, GDI Model 100) by reading\n");
fprintf (st, "a file and presenting lines or cards to the simulator.  Card decks may be\n");
fprintf (st, "represented by plain text ASCII files, card image files, or column binary\n");
fprintf (st, "files.\n\n");

fprintf (st, "The controller is also compatible with the CM11-F, CME11, and CMS11.\n\n");

fprintf (st, "Card image files are a file format designed by Douglas W. Jones at the\n");
fprintf (st, "University of Iowa to support the interchange of card deck data.  These\n");
fprintf (st, "files have a much richer information carrying capacity than plain ASCII\n");
fprintf (st, "files.  Card Image files can contain such interchange information as\n");
fprintf (st, "card-stock color, corner cuts, special artwork, as well as the binary\n");
fprintf (st, "punch data representing all 12 columns.  Complete details on the format,\n");
fprintf (st, "as well as sample code, are available at Prof. Jones's site:\n");
fprintf (st, "       http://www.cs.uiowa.edu/~jones/cards/.\n\n");

if ((crtypes & -crtypes) != crtypes) {
    fprintf (st, "The card reader device an be configured to emulate the following\n");
    fprintf (st, "controller models with these commands:\n\n");
    if (crtypes & 1)
        fprintf (st, "    SET CR CR11       set controller type to CR11\n");
    if (crtypes & 2)
        fprintf (st, "    SET CR CD11       set controller type to CD11\n");
    if (crtypes & 4) {
        fprintf (st, "    SET CR CD20       set controller type to CD20\n");
#if defined (AIECO_OK)
        fprintf (st, "    SET CR AIECO      emulate the CD20 \"augmented image\" ECO\n");
        fprintf (st, "                      default is %semulated.\n", (DFLT_AIECO? "":"not "));
#endif
}
    fprintf (st, "\nThe controller type must be set before attaching a virtual card deck to\n");
    fprintf (st, "the device.  You may NOT change controller type once a file is attached.\n\n");
    fprintf (st, "The primary differences between the controllers are summarized in the\n");
    fprintf (st, "table below.  By default, %s simulation is selected.\n\n", 
                    (DFLT_TYPE & UNIT_CD20)? "CD20": ((DFLT_TYPE & UNIT_CR11)? "CR11" : "CD11"));
    fprintf (st, "                    CR11                CD11/CD20\n");
    fprintf (st, "    BR              6                   4\n");
    fprintf (st, "    registers       4                   3\n");
    fprintf (st, "    data transfer   BR                  DMA\n");
    fprintf (st, "    card rate       200-600         1000-1200\n");
    fprintf (st, "    hopper cap.     <= 1000         1000-2250\n");
    fprintf (st, "    cards           Mark-sense & punched only\n");
    fprintf (st, "                    punched\n\n");
    fprintf (st, "The CD11 simulation includes the Rev. J modification to make the CDDB act\n");
    fprintf (st, "as a second status register during non-data transfer periods.\n\n");
}
if (crtypes & 1) {
    fprintf (st, "Examples of the CR11 include the M8290 and M8291 (CMS11).  All card readers\n");
    fprintf (st, "use a common vector at 0230 and CSR at 177160.  Even though the CR11 is\n");
    fprintf (st, "normally configured as a BR6 device, it is configured for BR4 in this\n");
    fprintf (st, "simulation.\n\n");
}
fprintf (st, "The card reader supports ASCII, card image, and column binary format card\n");
fprintf (st, "\"decks.\"  When reading plain ASCII files, lines longer than 80 characters\n");
fprintf (st, "are silently truncated.  Card image support is included for 80 column\n");
fprintf (st, "Hollerith, 82 column Hollerith, and 40 column Hollerith (mark-sense) cards.\n");
fprintf (st, "Column binary supports 80 column card images only.\n");
if (crtypes & 6) {
    fprintf (st, "The CD11/CD20 optionally check columns 0/81/41 for punches, which produce\n");
    fprintf( st, "read check errors.  As verifiers may produce these, this can be controlled:\n\n");
    fprintf( st, "    SET CR RDCHECK   - Enable read check errors (default)\n");
    fprintf( st, "    SET CR NORDCHECK - Disable read check errors\n\n");
}
fprintf (st, "All files are attached read-only (as if the -R switch were given).\n");
fprintf (st, "    ATTACH -A CR <file>           file is ASCII text\n");
fprintf (st, "    ATTACH -B CR <file>           file is column binary\n");
fprintf (st, "    ATTACH -I CR <file>           file is card image format\n\n");

fprintf (st, "If no flags are given, the file extension is evaluated.  If the filename\n");
fprintf (st, "ends in .TXT, the file is treated as ASCII text.  If the filename ends in\n");
fprintf (st, ".CBN, the file is treated as column binary.  Otherwise, the CR driver looks\n");
fprintf (st, "for a card image header.  If a correct header is found the file is treated\n");
fprintf (st, "as card image format, otherwise it is treated as ASCII text.\n\n");

fprintf (st, "The correct character translation MUST be set if a plain text file is to\n");
fprintf (st, "be used for card deck input.  The correct translation SHOULD be set to\n");
fprintf (st, "allow correct ASCII debugging of a card image or column binary input deck.\n");
fprintf (st, "Depending upon the operating system in use, how it was generated, and how\n");
fprintf (st, "the card data will be read and used, the translation must be set correctly\n");
fprintf (st, "so that the proper character set is used by the driver.  Use the following\n");
fprintf (st, "command to explicitly set the correct translation:\n\n");
fprintf (st, "    SET TRANSLATION={DEFAULT|026|026FTN|026DEC|026DECASCII|029|029DECASCII|EBCDIC}\n\n");
fprintf (st, "This command should be given after a deck is attached to the simulator.\n");
fprintf (st, "The mappings above are completely described at\n");
fprintf (st, "    http://www.cs.uiowa.edu/~jones/cards/codes.html.\n");
fprintf (st, "Note that early DEC software typically used 029 or 026FTN mappings.\n");
fprintf (st, "Later systems used the 026DECASCII and/or 029DECASCII mappings, which\n");
fprintf (st, "include all 7-bit ASCII characters.\n\n");
fprintf (st, "DEC operating systems used a variety of methods to determine the end of\n");
fprintf (st, "a deck (recognizing that 'hopper empty' does not necessarily mean the\n");
fprintf (st, "end of a deck.  Below is a summary of the various operating system\n");
fprintf (st, "conventions for signaling end of deck (or end of file with multi-file\n");
fprintf (st, "batch systems):\n\n");
fprintf (st, "    RT-11:    12-11-0-1-6-7-8-9 punch in column 1\n");
fprintf (st, "    RSTS/E:   12-11-0-1 or 12-11-0-1-6-7-8-9 punch in column 1\n");
fprintf (st, "    RSX:      12-11-0-1-6-7-8-9 punch in first 8 columns\n");
fprintf (st, "    VMS:      12-11-0-1-6-7-8-9 punch in first 8 columns\n");
fprintf (st, "    TOPS:     12-11-0-1 or 12-11-0-1-6-7-8-9 punch in column 1\n\n");
fprintf (st, "Using the AUTOEOF setting, the card reader can be set to automatically\n");
fprintf (st, "generate an EOF card consisting of the 12-11-0-1-6-7-8-9 punch in columns\n");
fprintf (st, "1-8.\n\n");
if (crtypes & 6) {
    fprintf (st, "When set,\nThe %s ", ((crtypes & 6) == 2)? "CD11": ((crtypes & 6) == 4)? "CD20": "CD11/CD20");

    fprintf (st,                    "will automatically set the EOF bit in the\n");
    fprintf (st, "controller after the EOF card has been processed.  By default AUTOEOF\n");
    fprintf (st, "is enabled.  The controller also supports an EOF switch that will set\n");
    fprintf (st, "the EOF bit when the hopper empties.  The switch resets each time the\n");
    fprintf (st, "hopper empties.  The SET CR EOF command emulates this.\n");
    if (crtypes &1)
        fprintf (st, "The CR11 does not support the EOF switch/bit.\n\n");
    else
        fprintf (st, "\n");
}
fprintf (st, "The default card reader input rate for the ");
if (crtypes & 4) {
    fprintf (st,                                         "CD20 is 1200 cpm");
    if (crtypes != 4)
        fprintf (st, ", and for the\n");
    else
        fprintf (st, ".\n");
}
if (crtypes & 3)
    fprintf (st,                                         "CR/CD11 is 285 cpm.\n");
fprintf (st, "The reader rate can be set to its default value or to anywhere in the\n");
fprintf (st, "range of 200 to 1200 cpm.This rate may be changed while the unit is\n");
fprintf (st, "attached.\n\n");
fprintf (st, "It is standard operating procedure for operators to load a card deck and\n");
fprintf (st, "press the momentary action RESET button to clear any error conditions and\n");
fprintf (st, "alert the processor that a deck is available to read.  Use the:\n");
fprintf (st, "    SET CR RESET\n");
fprintf (st, "command to simulate pressing the card reader RESET button.\n\n");
fprintf (st, "Another common control of physical card readers is the STOP button.  An\n");
fprintf (st, "operator could use this button to finish the read operation for the\n");
fprintf (st, "current card and terminate reading a deck early.  Use the SET CR STOP\n");
fprintf (st, "command to simulate pressing the card reader STOP button.\n\n");
fprintf (st, "The simulator does not support the BOOT command.  The simulator does not\n");
fprintf (st, "stop on file I/O errors.  Instead the controller signals a reader check\n");
fprintf (st, "to the CPU.\n");

fprint_reg_help (st, dptr);
return SCPE_OK;
}

const char *cr_description (DEVICE *dptr)
{
  /* Not thread-safe, but malloc() would be leak. */
  static char desc[MAXDESCRIP+sizeof(" card reader")-1] = "";

  if (desc[0] == '\0') {
      cr_supported (desc, NULL, sizeof (desc));
      strlcat (desc, " card reader", sizeof (desc));
  }
  return desc;
}
