/* 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)
   30-Mar-15    RMS     Backported from GitHub master; removed extended
                        help and Qbus support
   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"
extern int32 int_req;
#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"
extern int32 int_req[IPL_HLVL];
#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"
extern int32 int_req[IPL_HLVL];
#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

#include <assert.h>
#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 *, char *);
t_stat cr_detach (UNIT *);
t_stat cr_set_type (UNIT *, int32, char *, void *);
t_stat cr_set_aieco (UNIT *, int32, char *, void *);
t_stat cr_show_format (FILE *, UNIT *, int32, void *);
t_stat cr_set_rate (UNIT *, int32, char *, void *);
t_stat cr_show_rate (FILE *, UNIT *, int32, void *);
t_stat cr_set_reset (UNIT *, int32, char *, void *);
t_stat cr_set_stop (UNIT *, int32, char *, void *);
t_stat cr_set_eof (UNIT *, int32, char *, void *);
t_stat cr_show_eof (FILE *, UNIT *, int32, void *);
t_stat cr_set_trans (UNIT *, int32, char*, void *);
t_stat cr_show_trans (FILE *, UNIT *, int32, 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
*/

static DIB cr_dib = { IOBA_CR, IOLN_CR, &cr_rd, &cr_wr,
        1, DFLT_IVCL, VEC_CR, { 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 REG cr_reg[] = {
    { GRDATA (BUF,    cr_unit.buf, DEV_RDX,  8, 0) },
#if defined (CR11_OK) || defined (CR11_ONLY)
    { GRDATA (CRS,            crs, DEV_RDX, 16, 0) },
    { GRDATA (CRB1,          crb1, DEV_RDX, 16, 0) },
    { GRDATA (CRB2,          crb2, DEV_RDX, 16, 0) },
    { GRDATA (CRM,            crm, DEV_RDX, 16, 0) },
    { FLDATA (INTCR,    IREQ (CR), INT_V_CR) },
#endif
#if defined (CD11_OK) || defined (CD11_ONLY) || defined (CD20_OK) || defined (CD20_ONLY)
    { GRDATA (CDST,          cdst, DEV_RDX, 16, 0) },
    { GRDATA (CDCC,          cdcc, DEV_RDX, 16, 0) },
    { GRDATA (CDBA,          cdba, DEV_RDX, 16, 0) },
    { GRDATA (CDDB,          cddb, DEV_RDX, 16, 0) },
    { FLDATA (INTCD,    IREQ (CD), INT_V_CD) },
#endif
    { GRDATA (BLOWER, blowerState, DEV_RDX,  2, 0) },
    { FLDATA (ERR,            crs, CSR_V_ERR) },
    { FLDATA (IE,             crs, CSR_V_IE) },
    { DRDATA (POS,    cr_unit.pos, T_ADDR_W), PV_LEFT },
    { DRDATA (TIME,  cr_unit.wait, 24), 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 },
#endif
#if defined (CD11_OK)
    { UNIT_TYPE,         0, "CD11", "CD11", 
        &cr_set_type, NULL, NULL },
#endif
#if defined (CD20_OK)
    { UNIT_TYPE, UNIT_CD20, "CD20", "CD20", 
        &cr_set_type, NULL, NULL },
#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 },
    { (UNIT_TYPE|UNIT_AIECO), (UNIT_CD20|0),          "standard", "NOAIECO",
        &cr_set_aieco, NULL, NULL },
#endif
    { UNIT_AUTOEOF, UNIT_AUTOEOF, "auto EOF", "AUTOEOF", 
        NULL, NULL, NULL },
    { UNIT_AUTOEOF,            0, "no auto EOF", "NOAUTOEOF", 
        NULL, NULL, NULL },
#if !defined (CR11_ONLY)
    { UNIT_RDCHECK, UNIT_RDCHECK, "read check", "RDCHECK",
      NULL, NULL, NULL },
    { UNIT_RDCHECK,            0, "no read check", "NORDCHECK",
      NULL, NULL, NULL },
#endif
      /* card reader STOP switch */
    { MTAB_XTD|MTAB_VDV, 0, NULL, "STOP",
        &cr_set_stop, NULL, NULL },
    /* card reader RESET switch */
    { MTAB_XTD|MTAB_VDV, 0, NULL, "RESET",
        &cr_set_reset, NULL, NULL },
#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 },
#endif
    { MTAB_XTD|MTAB_VUN, 0, "FORMAT", NULL,
        NULL, &cr_show_format, NULL },
    { MTAB_XTD|MTAB_VDV, 006, "ADDRESS", "ADDRESS",
        &set_addr, &show_addr, NULL },
    { MTAB_XTD|MTAB_VDV, 0, "VECTOR", "VECTOR",
        &set_vec, &show_vec, NULL },
    { MTAB_XTD|MTAB_VDV, 0, "RATE", "RATE={DEFAULT|200..1200}",
        &cr_set_rate, &cr_show_rate, NULL },
    { MTAB_XTD|MTAB_VDV, 0, "TRANSLATION",
        NULL,
        &cr_set_trans, &cr_show_trans, NULL },
    { 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
    };

/* 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 */

        assert (colStart < colEnd);
        assert (colStart >= 0);
        assert (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;

    assert (colStart < colEnd);
    assert (colStart >= 1);
    assert (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 (&cr_unit, spinUp);
                } else {
                    sim_activate (&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 (&cr_unit, spinUp);
                    } else {
                        sim_activate (&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 (&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 (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 (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) +1;

        for ( i = 0; i < NTRANS; i++ )
            size += strlen (transcodes[i].name)+1;
        translation_help = (char *)malloc (size );
        strcpy(translation_help, trans_hlp);
        for (i = 0; i < NTRANS; i++) {
            strcat(translation_help, transcodes[i].name);
            strcat(translation_help,"|");
        }
        strcpy(translation_help+strlen(translation_help)-1, "}");
        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 (&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,
                    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 (uptr, spinDown);
    }
    return (detach_unit (uptr));
}

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

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


#if defined (CR11_OK) || defined (CD11_OK) || defined (CD20_OK)
t_stat cr_set_type (    UNIT    *uptr,
                        int32   val,
                        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);
    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,
                        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,
                        void    *desc    )
{
    fprintf (st, "%s format", cardFormat);
    return (SCPE_OK);
}

t_stat cr_set_rate (    UNIT    *uptr,
                        int32   val,
                        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,
                        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,
                        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 (uptr, spinUp);
    }
    */
    return (SCPE_OK);
}

/* simulate pressing the card reader STOP button */

t_stat cr_set_stop (    UNIT    *uptr,
                        int32   val,
                        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,
                        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,
                     void    *desc    )
{
    fprintf (st, (eofPending? "EOF pending": "no EOF pending"));
    return (SCPE_OK);
}

t_stat cr_set_trans (   UNIT    *uptr,
                        int32   val,
                        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,
                        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);
}
