#include "ibm1130_defs.h"
#include "ibm1130_fmt.h"
#include <ctype.h>
#include <sys/stat.h>

#ifdef _WIN32
#  include <io.h>       /* Microsoft puts definition of mktemp into io.h rather than stdlib.h */
#endif

/* ibm1130_cr.c: IBM 1130 1442 Card Reader simulator

   Based on the SIMH package written by Robert M Supnik

 * (C) Copyright 2002, Brian Knittel.
 * You may freely use this program, but: it offered strictly on an AS-IS, AT YOUR OWN
 * RISK basis, there is no warranty of fitness for any purpose, and the rest of the
 * usual yada-yada. Please keep this notice and the copyright in any distributions
 * or modifications.
 *
 * This is not a supported product, but I welcome bug reports and fixes.
 * Mail to simh@ibm1130.org

 *  Update 2012-10-12  Added ability to specify tab expansion width in deck files

 *  Update 2008-11-24  Made card reader attach always use read-only mode, so if file does not exist
                       it will not be created as an empty file. Fixed bug in BOOT CR (cold start from card)
                       that resulted in seeing cold card data again when next card was read. (This caused
                       the DMS load deck to fail, for instance).

 *  Update 2007-05-01  Changed name of function xio_1142_card to xio_1442_card.
                       Took six years to notice the mistake.

 *  Update 2006-01-23  More fixes, in call to mktemp and in 2501 support, also thanks
                       to Carl Claunch.

 *  Update 2006-01-03  Fixed bug found by Carl Claunch: feed function does not
                       cause an operation complete interrupt. Standard DMS routines were not
                       sensitive to this but DUP uses its own interrupt handler, and this
                       is why DUP would hang at end of deck.
 
 *  Update 2005-05-19  Added support for 2501 reader

 *  Update 2004-11-08  Merged in correct physical card reader code

 *  Update 2004-11-02: Added -s to boot command: don't touch console switches.

 *  Update 2004-06-05: Removed "feedcycle" from cr_reset. Reset should not touch the card reader.

 *  Update 2004-04-12: Changed ascii field of CPCODE to unsigned char, caught a couple
                       other potential problems with signed characters used as subscript indexes.

 *  Update 2003-11-25: Physical card reader support working, may not be perfect.
                       Changed magic filename for stdin to "(stdin)".

 *  Update 2003-07-23: Added autodetect for card decks (029 vs binary),
    made this the default.
 
 *  Update 2003-06-21: Fixed bug in XIO_SENSE: op_complete and response
    bits were being cleared before the DSW was saved in ACC. Somehow DMS
    worked with this, but APL didn't.

 *  Update 2002-02-29: Added deck-list option.

 *  Update 2003-02-08: Fixed error in declaration of array list_save, pointed
    out by Ray Comas.

* -----------------------------------------------------------------------
* USAGE NOTES
* -----------------------------------------------------------------------

* Attach switches:

   The ATTACH CR command accepts several command-line switches

     -q quiet mode, the simulator will not print the name of each file it opens
        while processing deck files (which are discussed below). For example,
      
        ATTACH CR -q @deckfile

     -l makes the simulator convert lower case letters in text decks
        to the IBM lower-case Hollerith character codes. Normally, the simulator
        converts lower case input to the uppercase Hollerith character codes.
        (Lowercase codes are used in APL\1130 save decks).

     -d prints a lot of simulator debugging information

     -f converts tabs in an ascii file to spaces according to Fortran column conventions
     -a converts tabs in an ascii file to spaces according to 1130 Assembler column conventions
     -t converts tabs in an ascii file to spaces, with tab settings every 8 columns
     -# converts tabs in an ascii file to spaces, with tab settings every # columns
        (See below for a discussion of tab formatting)

     -p means that filename is a COM port connected to a physical card reader using
        the CARDREAD interface (see http://ibm1130.org/sim/downloads)

   NOTE: for the Card Reader (CR), the -r (readonly) switch is implied. If the file does
   not exist, it will NOT be created.

   The ATTACH CP command accepts the -d switch.

* Deck lists
   If you issue an attach command and specify the filename as
   "@filename", the file is interpreted as a list of filenames to
   be read in sequence; the effect is that the reader sees the concatenation
   of all of the files listed. The simulator "reset" does NOT rewind the deck list.

   Filenames may be quoted if they contain spaces.

   The strings %1, %2, etc, if they appear, are replaced with arguments passed
   on the attach command line after the name of the deckfile. These can be the
   arguments to ibm1130, or to the "do" command if a "do" script is executing, if the
   attach command is constructed this way:

       attach CR @deckfile %1 %2 %3
    
   This will pass the ibm1130 or do script arguments to attach, which will make
   them available in the deckfile. Then, for instance the line

       %1.for

   would be substituted accordingly.

   Blank lines and lines starting with ; # or * are ignored as comments.

   Filenames may be followed by whitespace and one or more mode options:
   The mode options are:
    
            b       forces interpration as raw binary
            a       forces conversion from ascii to 029 coding, tabs are left alone
            af      forces 029 ascii conversion, and interprets tabs in Fortran mode
            aa      forces 029 ascii conversion, and interprets tabs in 1130 Assembler mode
            at      forces 029 ascii conversion, and interprets tabs with settings every 8 spaces
            a#      forces 029 ascii conversion, and interprets tabs with settings every # spaces

   If "a" or "b" mode is not specified, the device mode setting is used.  In this case,
   if the mode is "auto", the simulator will select binary or 029 by inspecting each
   file in turn.

   If a tab mode is not specified, tabs are left unmolested (and are treated as invalid characters)

   Example:
   
       attach cr @decklist

   reads filenames from file "decklist," which might contain:
  
       file01.for xf
       file02.dat a
       file03 bin b
       file04 bin

   ('a' means 029, so, if you need 026 coding, specify the
   device default as the correct 026 code and omit the 'a' on the text files lines).

   Literal text cards can be entered in deck files by preceding an input
   line with an exclamation point. For example,

   !// JOB
   !// FOR
   program.for
   !// XEQ
   program.dat
   
   looks like two literal supervisor control cards, followed by the contents
   of file program.for, followed by an // XEQ card, followed by the contents
   of file program.dat.

   %n tokens are not replaced in literal cards.

   The line

   !BREAK
   
   has a special meaning: when read from a deck file, it stops the
   emulator as if "IMMEDIATE STOP" was pressed. This returns control to
   the command interpreter or to the current DO command script.
   
*  Card image format.
   Card files can be ascii text or binary.  There are several ASCII modes:
   CODE_029, CODE_26F, etc, corresponding to different code sets.
   Punch and reader modes can be set independently using

        set cr binary       set cp binary *
        set cr 029          set cp 029
        set cr 026f         set cp 026f
        set cr 026c         set cp 026c
        set cr auto *

   (* = default mode)

   In "auto" mode, the card reader will examine the first 160 bytes of
   the deck and guess whether the card is binary or 029 text encoded.
   When a deck file is used with auto mode, the simulator guesses for
   each file named in the deck file.

*  Tab formatting. The attach command and deckfile entries can indicate
   that tabs are to be converted to spaces, to help let you write free-form
   source files.  There are three tab conversion modes, which are set
   with the attach command or in a decklist, as discussed earlier

   Fortran mode:
        Input lines of the form

            [label]<tab>statement

        or

            [label]<tab>+continuation

        (where + is any nonalphabetic character) are rearranged in the
        appropriate manner:

                     1         2
            12345678901234567890...
            ------------------------
            label statement
            label+continuation

        However, you must take care that you don't end up with statement text after column 72.

        Input lines with * or C in column 1 (comments and directives) and lines without tabs
        are left alone.

        (The ! escape is not used before Fortran directives as before Assembler directives)

   Assembler mode:
        Input lines of the form

            [label]<whitespace>[opcode]<tab>[tag][L]<tab>[argument]

        are rearranged so that the input fields are placed in the appropriate columns

        The label must start on the first character of the line. If there is no label, 
        the first character(s) before the opcode must be whitespace. Following the opcode, there
        MUST be a tab character, followed by the format and tag. Following the format and tag 
        may be exactly one whitespace character, and then starts the argument.

        Input lines with * in column 1 and blank lines are turned into Assembler comments,
        with the * in the Opcode field.

        Assembler directive lines at the beginning of the deck must be preceded by
        ! to indicate that they are not comments. For example,

        !*LIST
        * This is a comment

    Plain Tab mode:
        Tabs are replaced with spaces. Tab settings are assumed to be eight characters wide,
        as is standard for vi, notepad, etc.

* CGI mode note: The command

     attach cr (stdin)

  will attach the card reader to stdin. However, this is not compatible
  with the default encoding autodetect feature, so the command must be
  preceded with

      set cr 029

* -----------------------------------------------------------------------
* PROGRAMMING NOTES
* -----------------------------------------------------------------------

NOTE - there is a problem with this code. The Device Status Word (DSW) is
computed from current conditions when requested by an XIO load status
command; the value of DSW available to the simulator's examine & save
commands may NOT be accurate. This should probably be fixed. (I think there's
a way to have the expression evaluator call a routine? That would be one
way to solve the problem, the other is to keep DSW up-to-date all the time).

   The 1442 card read/punch has several cycles:

   feed cycle:  moves card from hopper to read station
                      card from read station to punch station
                      card from punch station to stacker

   read or punch: operates on card at read or punch station (but not both).

   The simulator requires input cards to be read from the file attached
   to the card reader unit. A feed cycle reads one line (text mode) or
   160 bytes (binary mode) from the input file to the read station buffer,
   copies the read station buffer to the punch station buffer, and if
   the punch unit is attached to a file, writes the punch station buffer to
   the output file.
   
   The read and punch cycles operate on the appropriate card buffer.

   Detaching the card punch flushes the punch station buffer if necessary.

   As does the 1442, a read or punch cycle w/o a feed cycle causes a
   feed cycle first.

   A feed cycle on an empty deck (reader unattaced or at EOF) clears
   the appropriate buffer, so you can punch w/o attaching a deck to
   the card reader.

   (Note: Carl Claunch determined by examining DUP code that a feed cycle
   does not cause an operation complete interrupt).

-- -- this may need changing depending on how things work in hardware. TBD.
||  A read cycle on an empty deck causes an error.
||  Hmmm -- what takes the place of the Start button on
-- the card reader?

  Binary format is stored using fxwrite of short ints, in this format:

     1 1
     2 2 0 1 2 3 4 5 6 7 8 9
     * * * * * * * * * * * * 0 0 0 0

            MSB                                LSB
   byte 0   [ 6] [ 7] [ 8] [ 9]   0    0    0    0
   byte 1   [12] [11] [ 0] [ 1] [ 2] [ 3] [ 4] [ 5] 

   This means we can read words (little endian) and get this in memory:

       12 11 0 1 2 3 4 5 6 7 8 9 - - - -

   which is what the 1130 sees.

   ASCII can be read in blocks of 80 characters but can be terminated by newline prematurely.

   Booting: card reader IPL loads 80 columns (1 card) into memory starting
   at location 0 in a split fashion:

        ________________ _ _ _
       /
   12 |
   11 |
    0 |
    1 |
    2 |
    3 | Punched card
    4 |
    5 |
    6 |
    7 |
    8 |
    9 |
      +------------------ - - -

       12 11  0  1  2            3   4  5  6  7  8  9   <- columns of cold start card
        |  |  |  |  |  0  0  0  / \  |  |  |  |  |  |
      +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
      | 0| 1| 2| 3| 4| 5| 6| 7| 8| 9|10|11|12|13|14|15|
      +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
      |  OPCODE      | F| Tag |  DISPLACEMENT         |
      +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

   The zeros mean that all IPL instructions are short form,
   nonindexed. The 3 column is repeated in bits 8 and 9 so
   it's a sign bit.

   Boot command on a binary deck does this. Boot on an unattached
   reader loads one of the built-in boot card images. Boot with an ASCII
   deck isn't allowed.
*/

#define READ_DELAY       35         /* see how small a number we can get away with */
#define PUNCH_DELAY      35
#define FEED_DELAY       25
#define READ_2501_DELAY  500

/* umm, this is a weird little future project of mine. */

#define ENABLE_PHYSICAL_CARD_READER_SUPPORT

extern UNIT cpu_unit;

static t_stat cr_svc      (UNIT *uptr);
static t_stat cr_reset    (DEVICE *dptr);
static t_stat cr_set_code (UNIT *uptr, int32 match, CONST char *cptr, void *desc);
static t_stat cr_attach   (UNIT *uptr, CONST char *cptr);
static int32  guess_cr_code (void);
static void   feedcycle   (t_bool load, t_bool punching);

static t_stat cp_reset    (DEVICE *dptr);
static t_stat cp_set_code (UNIT *uptr, int32 match, CONST char *cptr, void *desc);
static t_stat cp_attach   (UNIT *uptr, CONST char *cptr);
static t_stat cp_detach   (UNIT *uptr);

static int16 cr_dsw  = 0;                                   /* device status word */
static int32 cr_wait = READ_DELAY;                          /* read per-column wait */
static int32 cr_wait2501 = READ_2501_DELAY;                 /* read card wait for 2501 reader */
static int32 cf_wait = PUNCH_DELAY;                         /* punch per-column wait */
static int32 cp_wait = FEED_DELAY;                          /* feed op wait */
static int32 cr_count= 0;                                   /* read and punch card count */
static int32 cp_count= 0;
static int32 cr_addr = 0;                                   /* 2501 reader transfer address */
static int32 cr_cols = 0;                                   /* 2501 reader column count */

#define UNIT_V_OPERATION   (UNIT_V_UF + 0)                  /* operation in progress */
#define UNIT_V_CODE        (UNIT_V_UF + 2)                  /* three bits */
#define UNIT_V_CR_EMPTY    (UNIT_V_UF + 5)          /* NOTE: THIS MUST BE SET IN ibm1130_gui.c too */
#define UNIT_V_SCRATCH     (UNIT_V_UF + 6)
#define UNIT_V_QUIET       (UNIT_V_UF + 7)
#define UNIT_V_DEBUG       (UNIT_V_UF + 8)
#define UNIT_V_PHYSICAL    (UNIT_V_UF + 9)          /* NOTE: THIS MUST BE SET IN ibm1130_gui.c too */
#define UNIT_V_LASTPUNCH   (UNIT_V_UF + 10)                 /* used in unit_cp only */
#define UNIT_V_LOWERCASE   (UNIT_V_UF + 10)                 /* used in unit_cr only */
#define UNIT_V_ACTCODE     (UNIT_V_UF + 11)                 /* used in unit_cr only, 3 bits */
#define UNIT_V_2501        (UNIT_V_UF + 14)

#define UNIT_OP          (3u << UNIT_V_OPERATION)           /* two bits */
#define UNIT_CODE        (7u << UNIT_V_CODE)                /* three bits */
#define UNIT_CR_EMPTY    (1u << UNIT_V_CR_EMPTY)                
#define UNIT_SCRATCH     (1u << UNIT_V_SCRATCH)             /* temp file */
#define UNIT_QUIET       (1u << UNIT_V_QUIET)
#define UNIT_DEBUG       (1u << UNIT_V_DEBUG)
#define UNIT_PHYSICAL    (1u << UNIT_V_PHYSICAL)
#define UNIT_LASTPUNCH   (1u << UNIT_V_LASTPUNCH)
#define UNIT_LOWERCASE   (1u << UNIT_V_LOWERCASE)           /* permit lowercase input (needed for APL) */
#define UNIT_ACTCODE     (7u << UNIT_V_ACTCODE)
#define UNIT_2501        (1u << UNIT_V_2501)

#define OP_IDLE          (0u << UNIT_V_OPERATION)
#define OP_READING       (1u << UNIT_V_OPERATION)
#define OP_PUNCHING      (2u << UNIT_V_OPERATION)
#define OP_FEEDING       (3u << UNIT_V_OPERATION)

#define SET_OP(op)       {cr_unit.flags &= ~UNIT_OP; cr_unit.flags |= (op);}
#define CURRENT_OP       (cr_unit.flags & UNIT_OP)

#define CODE_AUTO        (0u << UNIT_V_CODE)
#define CODE_029         (1u << UNIT_V_CODE)
#define CODE_026F        (2u << UNIT_V_CODE)
#define CODE_026C        (3u << UNIT_V_CODE)
#define CODE_BINARY      (4u << UNIT_V_CODE)

#define GET_CODE(un)     (un.flags & UNIT_CODE)
#define SET_CODE(un,cd)  {un.flags &= ~UNIT_CODE; un.flags |= (cd);}

#define ACTCODE_029      (CODE_029    << (UNIT_V_ACTCODE-UNIT_V_CODE))  /* these are used ONLY in MTAB. Elsewhere */
#define ACTCODE_026F     (CODE_026F   << (UNIT_V_ACTCODE-UNIT_V_CODE))  /* we use values CODE_xxx with macros */
#define ACTCODE_026C     (CODE_026C   << (UNIT_V_ACTCODE-UNIT_V_CODE))  /* GET_ACTCODE and SET_ACTCODE. */
#define ACTCODE_BINARY   (CODE_BINARY << (UNIT_V_ACTCODE-UNIT_V_CODE))

        /* get/set macros for actual-code field, these use values like CODE_029 meant for the UNIT_CODE field */
#define GET_ACTCODE(un)    ((un.flags & UNIT_ACTCODE) >> (UNIT_V_ACTCODE-UNIT_V_CODE))
#define SET_ACTCODE(un,cd) {un.flags &= ~UNIT_ACTCODE; un.flags |= (cd) << (UNIT_V_ACTCODE-UNIT_V_CODE);}

#define COLUMN      u4                                      /* column field in unit record */

UNIT cr_unit = { UDATA (&cr_svc, UNIT_ATTABLE|UNIT_ROABLE|UNIT_CR_EMPTY, 0) };
UNIT cp_unit = { UDATA (NULL,    UNIT_ATTABLE, 0) };

MTAB cr_mod[] = {
    { UNIT_CODE,    CODE_029,       "029",      "029",      &cr_set_code},
    { UNIT_CODE,    CODE_026F,      "026F",     "026F",     &cr_set_code},
    { UNIT_CODE,    CODE_026C,      "026C",     "026C",     &cr_set_code},
    { UNIT_CODE,    CODE_BINARY,    "BINARY",   "BINARY",   &cr_set_code},
    { UNIT_CODE,    CODE_AUTO,      "AUTO",     "AUTO",     &cr_set_code},
    { UNIT_ACTCODE, ACTCODE_029,    "(029)",    NULL,       NULL},      /* display-only, shows current mode */
    { UNIT_ACTCODE, ACTCODE_026F,   "(026F)",   NULL,       NULL},
    { UNIT_ACTCODE, ACTCODE_026C,   "(026C)",   NULL,       NULL},
    { UNIT_ACTCODE, ACTCODE_BINARY, "(BINARY)", NULL,       NULL},
    { UNIT_2501,    0,              "1442",    "1442",      NULL},
    { UNIT_2501,    UNIT_2501,      "2501",    "2501",      NULL},
    { 0 }  };

MTAB cp_mod[] = {
    { UNIT_CODE, CODE_029,      "029",      "029",      &cp_set_code},
    { UNIT_CODE, CODE_026F,     "026F",     "026F",     &cp_set_code},
    { UNIT_CODE, CODE_026C,     "026C",     "026C",     &cp_set_code},
    { UNIT_CODE, CODE_BINARY,   "BINARY",   "BINARY",   &cp_set_code},
    { 0 }  };

REG cr_reg[] = {
    { HRDATA (CRDSW,    cr_dsw,  16) },                 /* device status word */
    { DRDATA (CRTIME,   cr_wait, 24), PV_LEFT },        /* operation wait for 1442 column read*/
    { DRDATA (2501TIME, cr_wait2501, 24), PV_LEFT },    /* operation wait for 2501 whole card read*/
    { DRDATA (CFTIME,   cf_wait, 24), PV_LEFT },        /* operation wait */
    { DRDATA (CRCOUNT,  cr_count, 32),PV_LEFT },        /* number of cards read since last attach cmd */
    { HRDATA (CRADDR,   cr_addr,  32) },                /* 2501 reader transfer address */
    { HRDATA (CRCOLS,   cr_cols,  32) },                /* 2501 reader column count */
    { NULL }  };

REG cp_reg[] = {
    { DRDATA (CPTIME,  cp_wait, 24), PV_LEFT },         /* operation wait */
    { DRDATA (CPCOUNT, cp_count, 32),PV_LEFT },         /* number of cards punched since last attach cmd */
    { NULL }  };

DEVICE cr_dev = {
    "CR", &cr_unit, cr_reg, cr_mod,
    1, 16, 16, 1, 16, 16,
    NULL, NULL, cr_reset,
    cr_boot, cr_attach, cr_detach};

DEVICE cp_dev = {
    "CP", &cp_unit, cp_reg, cp_mod,
    1, 16, 16, 1, 16, 16,
    NULL, NULL, cp_reset,
    NULL, cp_attach, cp_detach};

#define CR_DSW_1442_READ_RESPONSE       0x8000      /* device status word bits */
#define CR_DSW_1442_PUNCH_RESPONSE      0x4000
#define CR_DSW_1442_ERROR_CHECK         0x2000
#define CR_DSW_1442_LAST_CARD           0x1000
#define CR_DSW_1442_OP_COMPLETE         0x0800
#define CR_DSW_1442_FEED_CHECK          0x0100
#define CR_DSW_1442_BUSY                0x0002
#define CR_DSW_1442_NOT_READY           0x0001

#define CR_DSW_2501_ERROR_CHECK         0x2000      /* DSW for 2501 reader */
#define CR_DSW_2501_LAST_CARD           0x1000
#define CR_DSW_2501_OP_COMPLETE         0x0800
#define CR_DSW_2501_BUSY                0x0002
#define CR_DSW_2501_NOT_READY           0x0001

typedef struct {
    uint16 hollerith;
    unsigned char ascii;
} CPCODE;

static CPCODE cardcode_029[] =
{
    {0x0000,        ' '},
    {0x8000,        '&'},           /* + in 026 Fortran */
    {0x4000,        '-'},
    {0x2000,        '0'},
    {0x1000,        '1'},
    {0x0800,        '2'},
    {0x0400,        '3'},
    {0x0200,        '4'},
    {0x0100,        '5'},
    {0x0080,        '6'},
    {0x0040,        '7'},
    {0x0020,        '8'},
    {0x0010,        '9'},
    {0x9000,        'A'},
    {0x8800,        'B'},
    {0x8400,        'C'},
    {0x8200,        'D'},
    {0x8100,        'E'},
    {0x8080,        'F'},
    {0x8040,        'G'},
    {0x8020,        'H'},
    {0x8010,        'I'},
    {0x5000,        'J'},
    {0x4800,        'K'},
    {0x4400,        'L'},
    {0x4200,        'M'},
    {0x4100,        'N'},
    {0x4080,        'O'},
    {0x4040,        'P'},
    {0x4020,        'Q'},
    {0x4010,        'R'},
    {0x3000,        '/'},
    {0x2800,        'S'},
    {0x2400,        'T'},
    {0x2200,        'U'},
    {0x2100,        'V'},
    {0x2080,        'W'},
    {0x2040,        'X'},
    {0x2020,        'Y'},
    {0x2010,        'Z'},
    {0x0820,        ':'},
    {0x0420,        '#'},       /* = in 026 Fortran */
    {0x0220,        '@'},       /* ' in 026 Fortran */
    {0x0120,        '\''},
    {0x00A0,        '='},
    {0x0060,        '"'},
    {0x8820,        (unsigned char) '\xA2'},    /* cent, in MS-DOS encoding (this is in guess_cr_code as well) */
    {0x8420,        '.'},
    {0x8220,        '<'},       /* ) in 026 Fortran */
    {0x8120,        '('},
    {0x80A0,        '+'},
    {0x8060,        '|'},
    {0x4820,        '!'},
    {0x4420,        '$'},
    {0x4220,        '*'},
    {0x4120,        ')'},
    {0x40A0,        ';'},
    {0x4060,        (unsigned char) '\xAC'},    /* not, in MS-DOS encoding  (this is in guess_cr_code as well) */
    {0x2420,        ','},
    {0x2220,        '%'},       /* ( in 026 Fortran */
    {0x2120,        '_'},
    {0x20A0,        '>'},
    {0xB000,        'a'},
    {0xA800,        'b'},
    {0xA400,        'c'},
    {0xA200,        'd'},
    {0xA100,        'e'},
    {0xA080,        'f'},
    {0xA040,        'g'},
    {0xA020,        'h'},
    {0xA010,        'i'},
    {0xD000,        'j'},
    {0xC800,        'k'},
    {0xC400,        'l'},
    {0xC200,        'm'},
    {0xC100,        'n'},
    {0xC080,        'o'},
    {0xC040,        'p'},
    {0xC020,        'q'},
    {0xC010,        'r'},
    {0x6800,        's'},
    {0x6400,        't'},
    {0x6200,        'u'},
    {0x6100,        'v'},
    {0x6080,        'w'},
    {0x6040,        'x'},
    {0x6020,        'y'},
    {0x6010,        'z'},               /* these odd punch codes are used by APL: */
    {0x1010,        '\001'},                /* no corresponding ASCII   using ^A */
    {0x0810,        '\002'},                /* SYN                      using ^B */
    {0x0410,        '\003'},                /* no corresponding ASCII   using ^C */
    {0x0210,        '\004'},                /* PUNCH ON                 using ^D */
    {0x0110,        '\005'},                /* READER STOP              using ^E */
    {0x0090,        '\006'},                /* UPPER CASE               using ^F */
    {0x0050,        '\013'},                /* EOT                      using ^K */
    {0x0030,        '\016'},                /* no corresponding ASCII   using ^N */
    {0x1030,        '\017'},                /* no corresponding ASCII   using ^O */
    {0x0830,        '\020'},                /* no corresponding ASCII   using ^P */
};

static CPCODE cardcode_026F[] =     /* 026 fortran */
{
    {0x0000,        ' '},
    {0x8000,        '+'},
    {0x4000,        '-'},
    {0x2000,        '0'},
    {0x1000,        '1'},
    {0x0800,        '2'},
    {0x0400,        '3'},
    {0x0200,        '4'},
    {0x0100,        '5'},
    {0x0080,        '6'},
    {0x0040,        '7'},
    {0x0020,        '8'},
    {0x0010,        '9'},
    {0x9000,        'A'},
    {0x8800,        'B'},
    {0x8400,        'C'},
    {0x8200,        'D'},
    {0x8100,        'E'},
    {0x8080,        'F'},
    {0x8040,        'G'},
    {0x8020,        'H'},
    {0x8010,        'I'},
    {0x5000,        'J'},
    {0x4800,        'K'},
    {0x4400,        'L'},
    {0x4200,        'M'},
    {0x4100,        'N'},
    {0x4080,        'O'},
    {0x4040,        'P'},
    {0x4020,        'Q'},
    {0x4010,        'R'},
    {0x3000,        '/'},
    {0x2800,        'S'},
    {0x2400,        'T'},
    {0x2200,        'U'},
    {0x2100,        'V'},
    {0x2080,        'W'},
    {0x2040,        'X'},
    {0x2020,        'Y'},
    {0x2010,        'Z'},
    {0x0420,        '='},
    {0x0220,        '\''},
    {0x8420,        '.'},
    {0x8220,        ')'},
    {0x8220,        '<'},               /* if ASCII has <, treat like ) */
    {0x4420,        '$'},
    {0x4220,        '*'},
    {0x2420,        ','},
    {0x2220,        '('},
    {0x2220,        '%'},               /* if ASCII has %, treat like ) */
};

static CPCODE cardcode_026C[] =     /* 026 commercial */
{
    {0x0000,        ' '},
    {0x8000,        '+'},
    {0x4000,        '-'},
    {0x2000,        '0'},
    {0x1000,        '1'},
    {0x0800,        '2'},
    {0x0400,        '3'},
    {0x0200,        '4'},
    {0x0100,        '5'},
    {0x0080,        '6'},
    {0x0040,        '7'},
    {0x0020,        '8'},
    {0x0010,        '9'},
    {0x9000,        'A'},
    {0x8800,        'B'},
    {0x8400,        'C'},
    {0x8200,        'D'},
    {0x8100,        'E'},
    {0x8080,        'F'},
    {0x8040,        'G'},
    {0x8020,        'H'},
    {0x8010,        'I'},
    {0x5000,        'J'},
    {0x4800,        'K'},
    {0x4400,        'L'},
    {0x4200,        'M'},
    {0x4100,        'N'},
    {0x4080,        'O'},
    {0x4040,        'P'},
    {0x4020,        'Q'},
    {0x4010,        'R'},
    {0x3000,        '/'},
    {0x2800,        'S'},
    {0x2400,        'T'},
    {0x2200,        'U'},
    {0x2100,        'V'},
    {0x2080,        'W'},
    {0x2040,        'X'},
    {0x2020,        'Y'},
    {0x2010,        'Z'},
    {0x0420,        '='},
    {0x0220,        '\''},
    {0x8420,        '.'},
    {0x8220,        '<'},
    {0x8220,        ')'},           /* if ASCII has ), treat like < */
    {0x4420,        '$'},
    {0x4220,        '*'},
    {0x2420,        ','},
    {0x2220,        '%'},
    {0x2220,        '('},           /* if ASCII has (, treat like % */
};

extern int cgi;

static int16 ascii_to_card[256];

static CPCODE *cardcode;
static size_t ncardcode;
static FILE *deckfile = NULL;
static char tempfile[128];
static int any_punched = 0;

#define MAXARGLEN 80                    /* max length of a saved attach command argument */
#define MAXARGS   10                    /* max number of arguments to save */
static char list_save[MAXARGS][MAXARGLEN+1], *list_arg[MAXARGLEN+1];
static int list_nargs = 0;
static const char* (*tab_proc)(char* str, int width) = NULL;        /* tab reformatting routine */
static int tab_width = 8;

static uint16 punchstation[80];
static uint16 readstation[80];
static enum {STATION_EMPTY, STATION_LOADED, STATION_READ, STATION_PUNCHED} punchstate = STATION_EMPTY, readstate = STATION_EMPTY;

static t_bool nextdeck (void);
static void checkdeck (void);

static t_stat pcr_attach(UNIT *uptr, CONST char *devname);
static t_stat pcr_detach(UNIT *uptr);
static t_stat pcr_svc(UNIT *uptr);
static void   pcr_xio_sense(int modify);
static void   pcr_xio_feedcycle(void);
static void   pcr_xio_startread(void);
static void   pcr_reset(void);

int boot_drive = -1;
t_bool program_is_loaded = FALSE;

/* lookup_codetable - use code flag setting to get code table pointer and length */

static t_bool lookup_codetable (int32 match, CPCODE **pcode, size_t *pncode)
{
    switch (match) {
        case CODE_029:
            *pcode  = cardcode_029;
            *pncode = sizeof(cardcode_029) / sizeof(CPCODE);
            break;

        case CODE_026F:
            *pcode  = cardcode_026F;
            *pncode = sizeof(cardcode_026F) / sizeof(CPCODE);
            break;

        case CODE_026C:
            *pcode  = cardcode_026C;
            *pncode = sizeof(cardcode_026C) / sizeof(CPCODE);
            break;

        case CODE_BINARY:
            *pcode  = NULL;
            *pncode = 0;
            break;

        default:
            printf("Eek! Undefined code table index");
            return FALSE;
    }
    return TRUE;
}

t_stat set_active_cr_code (int match)
{
    CPCODE *code;
    size_t ncode;

    SET_ACTCODE(cr_unit, match);

    if (! lookup_codetable(match, &code, &ncode))
        return SCPE_ARG;

    if (code != NULL) {                 /* if an ASCII mode was selected */
        size_t i;

        memset(ascii_to_card, 0, sizeof(ascii_to_card));

        for (i = 0; i < ncode; i++)     /* set ascii to card code table */
            ascii_to_card[code[i].ascii] = code[i].hollerith;
    }

    return SCPE_OK;
}

static t_stat cr_set_code (UNIT *uptr, int32 match, CONST char *cptr, void *desc)
{
    if (match == CODE_AUTO)
        match = guess_cr_code();

    return set_active_cr_code(match);
}

static int32 guess_cr_code (void)
{
    int i;
    long filepos;
    int32 guess;
    union {
        uint16 w[80];               /* one card image, viewed as 80 short words */
        char   c[160];              /* same, viewed as 160 characters */
    } line;

    /* here, we can see if the attached file is binary or ascii and auto-set the
     * mode. If we the file is a binary deck, we should be able to read a record of 80 short
     * words, and the low 4 bits of each word must be zero. If the file was an ascii deck,
     * then these low 4 bits are the low 4 bits of every other character in the first 160
     * chararacters of the file. They would all only be 0 if all of these characters were
     * in the following set: {NUL ^P space 0 @ P ` p} . It seems very unlikely that
     * this would happen, as even if the deck consisted of untrimmed card images and
     * the first two lines were blank, the 81'st character would be a newline, and it would
     * appear at one of the every-other characters seen on little-endian machines, anyway.
     * So: if the code mode is AUTO, we can use this test and select either BINARY or 029.
     * Might as well also check for the all-blanks and newlines case in case this is a
     * big-endian machine.
     */


    guess = CODE_029;                                   /* assume ASCII, 029 */

    if ((cr_unit.flags & UNIT_ATT) && (cr_unit.fileref != NULL)) {
        filepos = ftell(cr_unit.fileref);               /* remember current position in file */
        fseek(cr_unit.fileref, 0, SEEK_SET);            /* go to first record of file */
                                                        /* read card image; if file too short, leave guess set to 029 */
        if (fxread(line.w, sizeof(line.w[0]), 80, cr_unit.fileref) == 80) {
            guess = CODE_BINARY;                        /* we got a card image, assume binary */

            for (i = 0; i < 80; i++) {                  /* make sure low bits are zeroes, which our binary card format promises */
                if (line.w[i] & 0x000F) {
                    guess = CODE_029;                   /* low bits set, must be ascii text */
                    break;
                }
            }

            if (guess == CODE_BINARY) {                 /* if we saw no low bits, it could have been all spaces. */
                guess = CODE_029;                       /* so now assume file is text */
                for (i = 0; i < 160; i++) {             /* ensure all 160 characters are 7-bit ASCII (or not or cent) */
                                                        /* 3.0-3, changed test for > 0x7f to & 0x80 */
                    if ((strchr("\r\n\t\xA2\xAC", line.c[i]) == NULL) && ((line.c[i] < ' ') || (line.c[i] & 0x80))) {
                        guess = CODE_BINARY;            /* oops, null or weird character, it's binary after all */
                        break;
                    }
                }
            }
        }

        if (filepos != -1)
            fseek(cr_unit.fileref, filepos, SEEK_SET);  /* return to original position */
    }

    return guess;
}

static t_stat cp_set_code (UNIT *uptr, int32 match, CONST char *cptr, void *desc)
{
    CPCODE *code;
    size_t ncode;

    if (! lookup_codetable(match, &code, &ncode))
        return SCPE_ARG;

    cardcode  = code;               /* save code table for punch output */
    ncardcode = ncode;

    return SCPE_OK;
}

t_stat load_cr_boot (int32 drvno, int switches)
{
    int i;
    const char *name;
    char msg[80];
    t_bool expand;
    uint16 word, *boot;
    static uint16 dms_boot_data[] = {               /* DMSV2M12, already expanded to 16 bits */
        0xc80a, 0x18c2, 0xd008, 0xc019, 0x8007, 0xd017, 0xc033, 0x100a,
        0xd031, 0x7015, 0x000c, 0xe800, 0x0020, 0x08f8, 0x4828, 0x7035,
        0x70fa, 0x4814, 0xf026, 0x2000, 0x8800, 0x9000, 0x9800, 0xa000,
        0xb000, 0xb800, 0xb810, 0xb820, 0xb830, 0xb820, 0x3000, 0x08ea,
        0xc0eb, 0x4828, 0x70fb, 0x9027, 0x4830, 0x70f8, 0x8001, 0xd000,
        0xc0f4, 0xd0d9, 0xc01d, 0x1804, 0xe8d6, 0xd0d9, 0xc8e3, 0x18d3,
        0xd017, 0x18c4, 0xd0d8, 0x9016, 0xd815, 0x90db, 0xe8cc, 0xd0ef,
        0xc016, 0x1807, 0x0035, 0x00d0, 0xc008, 0x1803, 0xe8c4, 0xd00f,
        0x080d, 0x08c4, 0x1003, 0x4810, 0x70d9, 0x3000, 0x08df, 0x3000,
        0x7010, 0x00d1, 0x0028, 0x000a, 0x70f3, 0x0000, 0x00d0, 0xa0c0
    };
    static uint16 apl_boot_data[] = {               /* APLIPL, already expanded */
        0x7021, 0x3000, 0x7038, 0xa0c0, 0x0002, 0x4808, 0x0003, 0x0026,
        0x0001, 0x0001, 0x000c, 0x0000, 0x0000, 0x0800, 0x48f8, 0x0027,
        0x7002, 0x08f2, 0x3800, 0xe0fe, 0x18cc, 0x100e, 0x10c1, 0x4802,
        0x7007, 0x4828, 0x7005, 0x4804, 0x7001, 0x70f3, 0x08e7, 0x70e1,
        0x08ed, 0x70f1, 0xc0e0, 0x1807, 0xd0de, 0xc0df, 0x1801, 0xd0dd,
        0x800d, 0xd00c, 0xc0e3, 0x1005, 0xe80a, 0xd009, 0xc0d8, 0x1008,
        0xd0d6, 0xc0dd, 0x1008, 0x80d4, 0xd0da, 0x1000, 0xb000, 0x00f6,
        0x70e7, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
        0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
        0x9000, 0x4004, 0x40c0, 0x8001, 0x4004, 0x40c0, 0x0000, 0x0000  };
    static uint16 aplp_boot_data[] = {              /* APLIPL Privileged, already expanded */
        0x7021, 0x3000, 0x7038, 0xa0c0, 0x0002, 0x4808, 0x0003, 0x0026,
        0x0001, 0x0001, 0x000c, 0x0000, 0x0000, 0x0800, 0x48f8, 0x0027,
        0x7002, 0x08f2, 0x3800, 0xe0fe, 0x18cc, 0x100e, 0x10c1, 0x4802,
        0x7007, 0x4828, 0x7005, 0x4804, 0x7001, 0x70f3, 0x08e7, 0x70e1,
        0x08ed, 0x70f1, 0xc0e0, 0x1807, 0xd0de, 0xc0df, 0x1801, 0xd0dd,
        0x800d, 0xd00c, 0xc0e3, 0x1005, 0xe80a, 0xd009, 0xc0d8, 0x1008,
        0xd0d6, 0xc0dd, 0x1008, 0x80d4, 0xd0da, 0x1002, 0xb000, 0x00f6,
        0x70e7, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
        0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
        0x9000, 0x4004, 0x40c0, 0x8001, 0x4004, 0x40c0, 0x4004, 0x4001
    };

    if ((switches & SWMASK('A')) && (switches & SWMASK('P'))) {
        boot   = aplp_boot_data;
        name   = "APL\\1130 Privileged";
        expand = FALSE;
    }
    else if (switches & SWMASK('A')) {
        boot   = apl_boot_data;
        name   = "APL\\1130";
        expand = FALSE;
    }
    else {
        boot   = dms_boot_data;
        name   = "DMS V2M12";
        expand = FALSE;
    }

    if (drvno >= 0 && ! (switches & SWMASK('S')))               /* if specified, set toggle switches to disk drive no */
        CES = drvno;                /* so BOOT DSK1 will work correctly (DMS boot uses this) */
                                    /* but do not touch switches if -S was specified */

    IAR = 0;                        /* clear IAR */

    for (i = 0; i < 80; i++) {      /* store the boot image to core words 0..79 */
        word = boot[i];             /* expanding the 12-bit card data to 16 bits if not already expanded */
        if (expand)
            word = (word & 0xF800) | ((word & 0x0400) ? 0x00C0 : 0x0000) | ((word & 0x03F0) >> 4);

        WriteW(i, word);
    }
                                    /* quiet switch or CGI mode inhibit the boot remark */
    if (((switches & SWMASK('Q')) == 0) && ! cgi) {                 /* 3.0-3, parenthesized & operation, per lint check */
        sprintf(msg, "Loaded %s cold start card", name);

        remark_cmd(msg);
    }

    boot_drive = drvno;
    program_is_loaded = TRUE;

    return SCPE_OK;
}

t_stat cr_boot (int32 unitno, DEVICE *dptr)
{
    t_stat rval;
    int i;

    if ((rval = reset_all(0)) != SCPE_OK)
        return rval;

    if (! (cr_unit.flags & UNIT_ATT))           /* no deck; load standard boot anyway */
        return load_cr_boot(-1, 0);

    if (GET_ACTCODE(cr_unit) != CODE_BINARY) {
        printf("Can only boot from card reader when set to BINARY mode\n");
        return SCPE_IOERR;
    }

    if (cr_unit.fileref == NULL)                /* this will happen if no file in deck file can be opened */
        return SCPE_IOERR;

    feedcycle(TRUE, FALSE);

    if (readstate != STATION_LOADED) {
        printf("No cards in reader\n");
        return SCPE_IOERR;
    }

/*  if (fxread(buf, sizeof(buf[0]), 80, cr_unit.fileref) != 80) */
/*      return SCPE_IOERR; */

    IAR = 0;                                    /* Program Load sets IAR = 0 */

    for (i = 0; i < 80; i++)                    /* shift 12 bits into 16 */
        WriteW(i, (readstation[i] & 0xF800) | ((readstation[i] & 0x0400) ? 0x00C0 : 0x0000) | ((readstation[i] & 0x03F0) >> 4));

    readstate = STATION_READ;                   /* the current card has been consumed */

    program_is_loaded = TRUE;

    return SCPE_OK;
}

char card_to_ascii (uint16 hol)
{
    size_t i;

    for (i = 0; i < ncardcode; i++)
        if (cardcode[i].hollerith == hol)
            return (char) cardcode[i].ascii;

    return '?';
}

/* hollerith_to_ascii - provide a generic conversion for simulator debugging  */

char hollerith_to_ascii (uint16 hol)
{
    size_t i;

    for (i = 0; i < ncardcode; i++)
        if (cardcode_029[i].hollerith == hol)
            return (char) cardcode[i].ascii;

    return ' ';
}

/* feedcycle - move cards to next station */

static void feedcycle (t_bool load, t_bool punching)
{
    char buf[84], *x;
    const char *result;
    int i, nread, nwrite, ch;

    /* write punched card if punch is attached to a file */
    if (cp_unit.flags & UNIT_ATT) {
        if (any_punched && punchstate != STATION_EMPTY) {
            if (GET_CODE(cp_unit) == CODE_BINARY) {
                fxwrite(punchstation, sizeof(punchstation[0]), 80, cp_unit.fileref);
            }
            else {
                for (i = 80; --i >= 0; ) {      /* find last nonblank column */
                    if (punchstation[i] != 0)
                        break;
                }

                /* i is now index of last character to output or -1 if all blank */

                for (nwrite = 0; nwrite <= i; nwrite++) {           /* convert characters */
                    buf[nwrite] = card_to_ascii(punchstation[nwrite]);
                }

                /* nwrite is now number of characters to output */

#ifdef WIN32
                buf[nwrite++] = '\r';               /* add CR before NL for microsoft */
#endif
                buf[nwrite++] = '\n';               /* append newline */
                fxwrite(buf, sizeof(char), nwrite, cp_unit.fileref);
            }
        }

        cp_count++;
    }

    if (! load)                                     /* all we wanted to do was flush the punch */
        return;

    /* slide cards from reader to punch. If we know we're punching,
     * generate a blank card in any case. Otherwise, it should take two feed
     * cycles to get a read card from the hopper to punch station. Also when
     * the reader is a 2501, we assume the 1442 is a punch only */

    if (readstate == STATION_EMPTY || (cr_unit.flags & UNIT_2501)) {
        if (punching) {
            memset(punchstation, 0, sizeof(punchstation));
            punchstate = STATION_LOADED;
        }
        else
            punchstate = STATION_EMPTY;
    }
    else {
        memcpy(punchstation, readstation, sizeof(punchstation));
        punchstate = STATION_LOADED;
    }

    /* load card into read station */

again:      /* jump here if we've loaded a new deck after emptying the previous one */

    if (cr_unit.flags & UNIT_ATT) {

        memset(readstation, 0, sizeof(readstation));        /* blank out the card image */

        if (cr_unit.fileref == NULL)
            nread = 0;

        else if (GET_ACTCODE(cr_unit) == CODE_BINARY)       /* binary read is straightforward */
            nread = fxread(readstation, sizeof(readstation[0]), 80, cr_unit.fileref);

        else if (fgets(buf, sizeof(buf), cr_unit.fileref) == NULL)  /* read up to 80 chars */
            nread = 0;                                      /* hmm, end of file */

        else {                                              /* check for CRLF or newline */
            if ((x = strchr(buf, '\r')) == NULL)
                x = strchr(buf, '\n');

            if (x == NULL) {                                /* there were no delimiters, burn rest of line */
                while ((ch = getc(cr_unit.fileref)) != EOF) {   /* get character */
                    if (ch == '\n')                             /* newline, done */
                        break;

                    if (ch == '\r') {                           /* CR, try to take newline too */
                        ch = getc(cr_unit.fileref);
                        if (ch != EOF && ch != '\n')            /* hmm, put it back */
                            ungetc(ch, cr_unit.fileref);

                        break;
                    }
                }
                if ((nread = strlen(buf)) > 80)         /* use the line as read, at most 80 characters */
                    nread = 80;
            }
            else
                nread = x-buf;                          /* reduce length of string */

            if (! (cr_unit.flags & UNIT_LOWERCASE))
                upcase(buf);                            /* force uppercase */

            if (tab_proc != NULL) {                     /* apply tab editing, if specified */
                buf[nread] = '\0';                      /* .. be sure string is terminated  */
                result = (*tab_proc)(buf, tab_width);   /* .. convert tabs  spaces          */
                nread  = strlen(result);                /* .. set new read length           */
            }
            else
                result = buf;

            for (i = 0; i < nread; i++)                 /* convert ascii to punch code */
                readstation[i] = ascii_to_card[(unsigned char) result[i]];

            nread = 80;                                 /* even if line was blank consider it present */
        }

        if (nread <= 0) {                               /* set hopper flag accordingly */
            if (deckfile != NULL && nextdeck())
                goto again;

            if (punching)                               /* pretend we loaded a blank card */
                nread = 80;
        }

        if (nread == 0) {
            SETBIT(cr_unit.flags, UNIT_CR_EMPTY);
            readstate = STATION_EMPTY;
            cr_count = -1;                              /* nix the card counter */
        }
        else {
            CLRBIT(cr_unit.flags, UNIT_CR_EMPTY);
            readstate = STATION_LOADED;
            cr_count++;
            cr_unit.pos++;
        }
    }
/*  else */
/*      readstate = STATION_EMPTY; */

    cr_unit.COLUMN = -1;                                /* neither device is currently cycling */
    cp_unit.COLUMN = -1;
}

#ifdef NO_USE_FOR_THIS_CURRENTLY

/* this routine should probably be hooked up to the GUI somehow */

/* NPRO - nonprocess runout, flushes out the reader/punch */

static void npro (void)
{
    if (cr_unit.flags & UNIT_ATT)
        fseek(cr_unit.fileref, 0, SEEK_END);        /* push reader to EOF */
    if (deckfile != NULL)
        fseek(deckfile, 0, SEEK_END);               /* skip to end of deck list */

    cr_count = -1;                                  /* nix the card counter */

    if (punchstate == STATION_PUNCHED)
        feedcycle(FALSE, FALSE);                    /* flush out card just punched */

    readstate = punchstate = STATION_EMPTY;
    cr_unit.COLUMN = -1;                            /* neither device is currently cycling */
    cp_unit.COLUMN = -1;
    SETBIT(cr_unit.flags, UNIT_CR_EMPTY);           /* set hopper empty */
}

#endif

/* skipbl - skip leading whitespace in a string */

static char * skipbl (char *str)
{
    while (*str && *str <= ' ')
        str++;

    return str;
}

#if THIS_IS_EVER_NEEDED
/* This function is never called. Leaving it in if the IBM-1130 author
 * deems it significant. */
static char * trim (char *str)
{
    char *s, *lastnb;

    for (lastnb = str-1, s = str; *s; s++)  /* point to last nonblank characteter in string */
        if (*s > ' ')
            lastnb = s;

    lastnb[1] = '\0';                       /* clip just after it */

    return str;
}
#endif

/* alltrim - remove all leading and trailing whitespace from a string */

static char * alltrim (char *str)
{
    char *s, *lastnb;

    if ((s = skipbl(str)) != str)           /* slide down over leading whitespace */
        strcpy(str, s);

    for (lastnb = str-1, s = str; *s; s++)  /* point to last nonblank characteter in string */
        if (*s > ' ')
            lastnb = s;

    lastnb[1] = '\0';                       /* clip just after it */

    return str;
}

/* checkdeck - set hopper empty status based on condition of current reader file */

static void checkdeck (void)
{
    t_bool empty;

    if (cr_unit.fileref == NULL) {                  /* there is no open file */
        empty = TRUE;
    }
    else {
        fseek(cr_unit.fileref, 0, SEEK_END);        /* seek to end of file */
        empty = ftell(cr_unit.fileref) <= 0;        /* file is empty if there was nothing in it*/
        cr_count = 0;                               /* reset card counter */
        cr_unit.pos = 0;
        fseek(cr_unit.fileref, 0, SEEK_SET);        /* rewind deck */
    }

    if (empty) {
        SETBIT(cr_unit.flags, UNIT_CR_EMPTY);
        if (cr_unit.fileref != NULL)                /* real file but it's empty, hmmm, try another */
            nextdeck();
    }
    else {
        CLRBIT(cr_unit.flags, UNIT_CR_EMPTY);
    }
}

/* nextdeck - attempt to load a new file from the deck list into the hopper */

static t_bool nextdeck (void)
{
    char buf[200], *fname, *c, quote;
    int code;
    long fpos;

    cr_count = 0;                           /* clear read count */
    cr_unit.pos = 0;

    if (deckfile == NULL)                   /* we can't help */
        return FALSE;

    code = GET_CODE(cr_unit);               /* default code as set */

    if (cr_unit.fileref != NULL) {          /* this pulls the rug out from under scp */
        fclose(cr_unit.fileref);            /* since the attach flag is still set. be careful! */
        cr_unit.fileref = NULL;

        if (cr_unit.flags & UNIT_SCRATCH) {
            remove(tempfile);
            CLRBIT(cr_unit.flags, UNIT_SCRATCH);
        }
    }

    for (;;) {                              /* get a filename */
        tab_proc = NULL;                    /* default: no tab editing */
        tab_width = 8;

        if (fgets(buf, sizeof(buf), deckfile) == NULL)
            break;                          /* oops, no more names */

        alltrim(buf);                       /* remove leading and trailing spaces */

        if (! *buf)
            continue;                       /* empty line */

        if (*buf == '#' || *buf == '*' || *buf == ';')
            continue;                       /* comment */

        if (strnicmp(buf, "!BREAK", 6) == 0) {  /* stop the simulation */
            break_simulation(STOP_DECK_BREAK);
            continue;
        }

        if (buf[0] == '!') {                /* literal text line, make a temporary file */

#if defined  (__GNUC__) && !defined (_WIN32)                /* GCC complains about mktemp & always provides mkstemp */

            if (*tempfile == '\0') {                        /* first time, open guaranteed-unique file */
                int fh;
                mode_t prev_mode = umask(0177);

                strcpy(tempfile, "tempXXXXXX");             /* get modifiable copy of name template */

                if ((fh = mkstemp(tempfile)) == -1) {       /* open file. Actual name is set by side effect */
                    printf("Cannot create temporary deck file\n");
                    break_simulation(STOP_DECK_BREAK);
                    umask(prev_mode);
                    return 0;
                }
                                                            /* get FILE * from the file handle */
                if ((cr_unit.fileref = fdopen(fh, "w+b")) == NULL) {
                    printf("Cannot use temporary deck file %s\n", tempfile);
                    break_simulation(STOP_DECK_BREAK);
                    umask(prev_mode);
                    return 0;
                }
                umask(prev_mode);
            }
            else {                                          /* on later opens, just reuse the old name */
                mode_t prev_mode = umask(0177);

                if ((cr_unit.fileref = fopen(tempfile, "w+b")) == NULL) {
                    printf("Cannot create temporary file %s\n", tempfile);
                    break_simulation(STOP_DECK_BREAK);
                    umask(prev_mode);
                    return 0;
                }
                umask(prev_mode);
            }
#else                   /* ANSI standard C always provides mktemp */

            if (*tempfile == '\0') {                        /* first time, construct unique name */
                strcpy(tempfile, "tempXXXXXX");             /* make a modifiable copy of the template */
                if (mktemp(tempfile) == NULL) {
                    printf("Cannot create temporary card file name\n");
                    break_simulation(STOP_DECK_BREAK);
                    return 0;
                }
            }
                                                            /* (re)create file */
            if ((cr_unit.fileref = fopen(tempfile, "w+b")) == NULL) {
                printf("Cannot create temporary file %s\n", tempfile);
                break_simulation(STOP_DECK_BREAK);
                return 0;
            }
#endif

            SETBIT(cr_unit.flags, UNIT_SCRATCH);

            for (;;) {                                      /* store literal cards into temporary file */
                upcase(buf+1);
                fputs(buf+1, cr_unit.fileref);
                putc('\n', cr_unit.fileref);

                if (cpu_unit.flags & UNIT_ATT)
                    trace_io("(Literal card %s\n)", buf+1);
                if (! (cr_unit.flags & UNIT_QUIET))
                    printf(  "(Literal card %s)\n", buf+1);

                fpos = ftell(deckfile);
                if (fgets(buf, sizeof(buf), deckfile) == NULL)
                    break;                          /* oops, end of file */
                if (buf[0] != '!' || strnicmp(buf, "!BREAK", 6) == 0)
                    break;
                alltrim(buf);
            }
            if (fpos != -1)
                fseek(deckfile, fpos, SEEK_SET);    /* restore deck file to just before non-literal card */

            fseek(cr_unit.fileref, 0, SEEK_SET);    /* rewind scratch file for reading */
            code = CODE_029;                        /* assume literal cards use keycode 029 */
            break;
        }

        sim_sub_args(buf, sizeof(buf), list_arg);   /* substitute in stuff from the attach command line */

        c = buf;                                    /* pick filename from string */

        while (*c && *c <= ' ')                     /* skip leading blanks (there could be some now after subsitution) */
            c++;

        fname = c;                                  /* remember start */

        if (*c == '\'' || *c == '"') {              /* quoted string */
            quote = *c++;                           /* remember the quote type */
            fname++;                                /* skip the quote */
            while (*c && (*c != quote))
                c++;                                /* skip to end of quote */
        }
        else {                                      /* not quoted; look for terminating whitespace */
            while (*c && (*c > ' '))
                c++;
        }

        if (*c)
            *c++ = 0;                               /* term arg at space or closing quote & move to next character */

        if (! *fname)                               /* blank line, no filename */
            continue;

        if ((cr_unit.fileref = fopen(fname, "rb")) == NULL) {
            printf("File '%s' specified in deck file '%s' cannot be opened\n", fname, cr_unit.filename+1);
            continue;
        }

        c = skipbl(c);                      /* skip to next token, which would be mode, if present */

        switch (*c) {
            case 'b':
            case 'B':
                code = CODE_BINARY;                 /* force code */
                c++;                                /* accept mode character by moving past it */
                break;

            case 'a':
            case 'A':
                code = CODE_029;
                c++;

                switch (*c) {                       /* is ascii mode followed by another character? */
                    case 'F':
                    case 'f':
                        tab_proc = EditToFortran;
                        c++;
                        break;

                    case 'A':
                    case 'a':
                        tab_proc = EditToAsm;
                        c++;
                        break;

                    case 't':
                    case 'T':
                        tab_proc = EditToWhitespace;
                        c++;
                        tab_width = 0;              /* see if there is a digit after the 4 -- if so use it as tab expansion width */
                        while (isdigit(*c))
                            tab_width = tab_width*10 + *c++ - '0';

                        if (tab_width == 0)
                            tab_width = 8;

                        break;
                }
        }

        if (code == CODE_AUTO)                      /* otherwise if mode is auto, guess it, otherwise use default */
            code = guess_cr_code();

        if (cpu_unit.flags & UNIT_ATT)
            trace_io("(Opened %s deck %s%s)\n", (code == CODE_BINARY) ? "binary" : "text", fname, tab_proc ? (*tab_proc)(NULL, tab_width) : "");

        if (! (cr_unit.flags & UNIT_QUIET))
            printf(  "(Opened %s deck %s%s)\n", (code == CODE_BINARY) ? "binary" : "text", fname, tab_proc ? (*tab_proc)(NULL, tab_width) : "");

        break;
    }

    checkdeck();

    if (code != CODE_AUTO)                      /* if code was determined, set it */
        set_active_cr_code(code);               /* (it may be left at CODE_AUTO when deckfile is exhausted */

    return (cr_unit.flags & UNIT_CR_EMPTY) == 0;/* return TRUE if a deck has been loaded */
}

static t_stat cr_reset (DEVICE *dptr)
{
    if (GET_ACTCODE(cr_unit) == CODE_AUTO)
        SET_ACTCODE(cr_unit, CODE_029);         /* if actual code is not yet set, select 029 for now*/

    cr_set_code(&cr_unit, GET_ACTCODE(cr_unit), NULL, NULL);    /* reset to specified code table */

    readstate = STATION_EMPTY;

    cr_dsw = 0;
    sim_cancel(&cr_unit);                           /* cancel any pending ops */
    calc_ints();

    SET_OP(OP_IDLE);

    cr_unit.COLUMN = -1;                            /* neither device is currently cycling */

    if (cr_unit.flags & UNIT_PHYSICAL) {
        pcr_reset();
        return SCPE_OK;
    }

    return SCPE_OK;
}

static t_stat cp_reset (DEVICE *dptr)
{
    if (GET_CODE(cp_unit) == CODE_AUTO)
        SET_CODE(cp_unit, CODE_BINARY);             /* punch is never in auto mode; turn it to binary on startup */

    cp_set_code(&cp_unit, GET_CODE(cp_unit), NULL, NULL);
    punchstate = STATION_EMPTY;

    cp_unit.COLUMN = -1;
    return SCPE_OK;
}

t_stat cr_rewind (void)
{
    if ((cr_unit.flags & UNIT_ATT) == 0)
        return SCPE_UNATT;

    if (deckfile) {
        fseek(deckfile, 0, SEEK_SET);
        nextdeck();
    }
    else {
        fseek(cr_unit.fileref, 0, SEEK_SET);
        checkdeck();
        cr_set_code(&cr_unit, GET_CODE(cr_unit), NULL, NULL);
    }

    cr_unit.pos = 0;

    /* there is a read pending. Pull the card in to make it go */
    if (CURRENT_OP == OP_READING || CURRENT_OP == OP_PUNCHING || CURRENT_OP == OP_FEEDING)
        feedcycle(TRUE, (cp_unit.flags & UNIT_ATT) != 0);

    return SCPE_OK;
}

static t_stat cr_attach (UNIT *uptr, CONST char *iptr)
{
    t_stat rval;
    t_bool use_decklist, old_quiet;
    char gbuf[4*CBUFSIZE], *cptr = gbuf;
    char *c, *arg, quote;

    gbuf[sizeof(gbuf)-1] = '\0';
    strlcpy(gbuf, iptr, sizeof gbuf);
    cr_detach(uptr);                                /* detach file and possibly deck file */

    CLRBIT(uptr->flags, UNIT_SCRATCH|UNIT_QUIET|UNIT_DEBUG|UNIT_PHYSICAL|UNIT_LOWERCASE);   /* set options */

    tab_proc = NULL;
    tab_width = 8;
    use_decklist = FALSE;

    sim_switches |= SWMASK('R');    // the card reader is readonly. Don't create an empty file if file does not exist

    if (sim_switches & SWMASK('D')) SETBIT(uptr->flags, UNIT_DEBUG);
    if (sim_switches & SWMASK('Q')) SETBIT(uptr->flags, UNIT_QUIET);
    if (sim_switches & SWMASK('L')) SETBIT(uptr->flags, UNIT_LOWERCASE);

    if (sim_switches & SWMASK('F')) tab_proc = EditToFortran;
    if (sim_switches & SWMASK('A')) tab_proc = EditToAsm;
    if (sim_switches & SWMASK('T')) tab_proc = EditToWhitespace;

    /* user can specify multiple names on the CR attach command if using a deck file. The deck file 
     * can contain %n tokens to pickup the additional name(s). */

    c = cptr;                                       /* extract arguments */
    for (list_nargs = 0; list_nargs < MAXARGS; list_nargs++) {
        while (*c && (*c <= ' '))                   /* skip blanks */
            c++;

        if (! *c)
            break;                                  /* all done */

        if (list_nargs == 0 && *c == '@') {         /* @ might occur before a quoted name; check first */
            c++;
            use_decklist = TRUE;
        }

        if (*c == '\'' || *c == '"') {              /* quoted string */
            quote = *c++;
            arg = c;                                /* save start */
            while (*c && (*c != quote))
                c++;
        }
        else {
            arg = c;                                /* save start */
            while (*c && (*c > ' '))
                c++;
        }

        if (*c)
            *c++ = 0;                               /* term arg at space or closing quote */

        list_arg[list_nargs] = list_save[list_nargs];   /* set pointer to permanent storage location */
        strlcpy(list_arg[list_nargs], arg, sizeof list_save[list_nargs]);  /* store copy */
    }
    list_arg[list_nargs] = NULL;                    /* NULL terminate the end of the argument list */


    if (list_nargs <= 0)                            /* need at least 1 */
        return SCPE_2FARG;

    cr_count = 0;                                   /* reset card counter */

    cptr = list_arg[0];                             /* filename is first argument */
    if (*cptr == '@') {                             /* @ might also occur inside a quoted name; check afterwards too */
        use_decklist = TRUE;
        cptr++;
    }

    else if (sim_switches & SWMASK('P')) {          /* open physical card reader device */
        return pcr_attach(uptr, cptr);
    }

    if (list_nargs > 1 && ! use_decklist)           /* if not using deck file, there should have been only one name */
        return SCPE_2MARG;

    if (strcmp(cptr, "(stdin)") == 0 && ! use_decklist) {   /* standard input */
        if (uptr->flags & UNIT_DIS) return SCPE_UDIS;       /* disabled? */
        uptr->filename = (char *)calloc(CBUFSIZE, sizeof(char));
        strcpy(uptr->filename, "(stdin)");
        uptr->fileref = stdin;
        SETBIT(uptr->flags, UNIT_ATT);
        uptr->pos = 0;
    }
    else {
        old_quiet = sim_quiet;                      /* attach the file, but set sim_quiet so we don't get the "CR is read-only" message */
        sim_quiet = TRUE;
        rval = attach_unit(uptr, cptr);
        sim_quiet = old_quiet;

        if (rval != SCPE_OK)                        /* file did not exist */
            return rval;
    }

    if (use_decklist) {                             /* if we skipped the '@', store the actually-specified name */
        uptr->filename[0] = '@';
        strncpy(uptr->filename+1, cptr, CBUFSIZE-1);

        deckfile = cr_unit.fileref;                 /* save the deck file stream in our local variable */
        cr_unit.fileref  = NULL;
        nextdeck();
    }
    else {
        checkdeck();
        cr_set_code(&cr_unit, GET_CODE(cr_unit), NULL, NULL);
    }

    /* there is a read pending. Pull the card in to make it go */
    if (CURRENT_OP == OP_READING || CURRENT_OP == OP_PUNCHING || CURRENT_OP == OP_FEEDING)
        feedcycle(TRUE, (cp_unit.flags & UNIT_ATT) != 0);

    return SCPE_OK;
}

t_stat cr_detach (UNIT *uptr)
{
    t_stat rval;

    cr_count = 0;                                   /* clear read count */

    if (cr_unit.flags & UNIT_PHYSICAL)
        return pcr_detach(uptr);

    if (cr_unit.flags & UNIT_ATT && deckfile != NULL) {
        if (cr_unit.fileref != NULL)            /* close the active card deck */
            fclose(cr_unit.fileref);

        if (cr_unit.flags & UNIT_SCRATCH) {
            remove(tempfile);
            CLRBIT(cr_unit.flags, UNIT_SCRATCH);
        }

        cr_unit.fileref = deckfile;             /* give scp a file to close */
    }

    if (uptr->fileref == stdin) {
        CLRBIT(uptr->flags, UNIT_ATT);
        free(uptr->filename);
        uptr->filename = NULL;
        uptr->fileref  = NULL;
        rval = SCPE_OK;
    }
    else
        rval = detach_unit(uptr);

    return rval;
}

static t_stat cp_attach (UNIT *uptr, CONST char *cptr)
{
    char gbuf[2*CBUFSIZE];
                                                /* if -d is specified turn on debugging (bit is in card reader UNIT) */
    if (sim_switches & SWMASK('D')) SETBIT(cr_unit.flags, UNIT_DEBUG);

    return attach_unit(uptr, quotefix(cptr, gbuf)); /* fix quotes in filenames & attach */
}

static t_stat cp_detach   (UNIT *uptr)
{
    if (cp_unit.flags & UNIT_ATT)
        if (punchstate == STATION_PUNCHED)
            feedcycle(FALSE, FALSE);            /* flush out card just punched */

    any_punched = 0;                            /* reset punch detected */
    cp_count = 0;                               /* clear punch count */

    return detach_unit(uptr);
}

static void op_done (UNIT *u, const char *opname, t_bool issue_intr)
{
    if (u->flags & UNIT_DEBUG)
        DEBUG_PRINT("!CR %s Op Complete, card %d%s", opname, cr_count, issue_intr ? ", interrupt" : "");

    SET_OP(OP_IDLE);

    if (u->flags & UNIT_2501)                   /* we use u-> not cr_unit. because PUNCH is always a 1442 */
        CLRBIT(cr_dsw,  CR_DSW_2501_BUSY);
    else
        CLRBIT(cr_dsw,  CR_DSW_1442_BUSY);      /* this is trickier. 1442 cr and cp share a dsw */

    if (issue_intr) {                           /* issue op-complete interrupt for read and punch ops but not feed */
        if (u->flags & UNIT_2501) {
            SETBIT(cr_dsw,  CR_DSW_2501_OP_COMPLETE);
            SETBIT(ILSW[4], ILSW_4_2501_CARD);
        }
        else {
            SETBIT(cr_dsw,  CR_DSW_1442_OP_COMPLETE);
            SETBIT(ILSW[4], ILSW_4_1442_CARD);
        }
        calc_ints();
    }
}

static t_stat cr_svc (UNIT *uptr)
{
    int i;

    if (uptr->flags & UNIT_PHYSICAL)
        return pcr_svc(uptr);

    switch (CURRENT_OP) {
        case OP_IDLE:
            break;

        case OP_FEEDING:
            op_done(&cr_unit, "feed", FALSE);
            break;

        case OP_READING:
            if (readstate == STATION_EMPTY) {           /* read active but no cards? hang */
                sim_activate(&cr_unit, cf_wait);
                break;
            }

            if (cr_unit.flags & UNIT_2501) {            /* 2501 transfers entire card then interrupts */
                for (i = 0; i < cr_cols; i++)           /* (we wait until end of delay time before transferring data) */
                    M[(cr_addr + i) & mem_mask] = readstation[i];

                readstate = STATION_READ;
                op_done(&cr_unit, "read", TRUE);
            }
            else if (++cr_unit.COLUMN < 80) {           /* 1442 interrupts on each column... */
                SETBIT(cr_dsw,  CR_DSW_1442_READ_RESPONSE);
                SETBIT(ILSW[0], ILSW_0_1442_CARD);
                calc_ints();
                sim_activate(&cr_unit, cr_wait);
                if (cr_unit.flags & UNIT_DEBUG)
                    DEBUG_PRINT("!CR Read Response %d : %d", cr_count, cr_unit.COLUMN+1);
            }
            else {                                      /* ... then issues op-complete */
                readstate = STATION_READ;
                op_done(&cr_unit, "read", TRUE);
            }
            break;

        case OP_PUNCHING:
            if (punchstate == STATION_EMPTY) {          /* punch active but no cards? hang */
                sim_activate(&cr_unit, cf_wait);
                break;
            }

            if (cp_unit.flags & UNIT_LASTPUNCH) {
                punchstate = STATION_PUNCHED;
                op_done(&cp_unit, "punch", TRUE);
            }
            else if (++cp_unit.COLUMN < 80) {
                SETBIT(cr_dsw,  CR_DSW_1442_PUNCH_RESPONSE);
                SETBIT(ILSW[0], ILSW_0_1442_CARD);
                calc_ints();
                sim_activate(&cr_unit, cp_wait);
                if (cr_unit.flags & UNIT_DEBUG)
                    DEBUG_PRINT("!CR Punch Response");
            }
            else {
                punchstate = STATION_PUNCHED;
                op_done(&cp_unit, "punch", TRUE);
            }
            break;
    }

    return SCPE_OK;
}

void xio_2501_card (int32 addr, int32 func, int32 modify)
{
    char msg[80];
    int ch;
    t_bool lastcard;

/* it would be nice for simulated reader to be able to use 2501 mode -- much more
 * efficient. Using the 1403 printer and 2501 reader speeds things up quite considerably. */

    switch (func) {
        case XIO_SENSE_DEV:
            if (cr_unit.flags & UNIT_PHYSICAL) {
                pcr_xio_sense(modify);
                break;
            }

// the following part is questionable -- the 2501 might need to be more picky about setting
// the LAST_CARD bit...

            if ((cr_unit.flags & UNIT_ATT) == 0)
                lastcard = TRUE;                    /* if nothing to read, hopper's empty */
            else if (readstate == STATION_LOADED)
                lastcard = FALSE;
            else if (cr_unit.fileref == NULL)
                lastcard = TRUE;
            else if ((ch = getc(cr_unit.fileref)) != EOF) {
                ungetc(ch, cr_unit.fileref);        /* put character back; hopper's not empty */
                lastcard = FALSE;
            }
            else if (deckfile != NULL && nextdeck())
                lastcard = FALSE;
            else
                lastcard = TRUE;                    /* there is nothing left to read for a next card */

            CLRBIT(cr_dsw, CR_DSW_2501_LAST_CARD|CR_DSW_2501_BUSY|CR_DSW_2501_NOT_READY);

            if (lastcard)
                SETBIT(cr_dsw, CR_DSW_2501_LAST_CARD|CR_DSW_2501_NOT_READY);
            // don't clear it here -- modify bit must be set before last card can be cleared

            if (CURRENT_OP != OP_IDLE)
                SETBIT(cr_dsw, CR_DSW_2501_BUSY|CR_DSW_2501_NOT_READY);

            ACC = cr_dsw;                           /* return the DSW */

            if (cr_unit.flags & UNIT_DEBUG)
                DEBUG_PRINT("#CR Sense %04x%s", cr_dsw & 0xFFFF, (modify & 1) ? " RESET" : "");

            if (modify & 0x01) {                    /* reset interrupts */
//              if (! lastcard)                     /* (lastcard is reset only when modify bit is set)  */
                    CLRBIT(cr_dsw, CR_DSW_2501_LAST_CARD);
                CLRBIT(cr_dsw, CR_DSW_2501_OP_COMPLETE);
                CLRBIT(ILSW[4], ILSW_4_2501_CARD);
            }
            break;

        case XIO_INITR:
            if (cr_unit.flags & UNIT_DEBUG)
                DEBUG_PRINT("#CR Start read");

            cr_unit.COLUMN = -1;

            cr_cols = M[addr & mem_mask];               /* save column count and transfer address */
            cr_addr = addr+1;

            if ((cr_cols < 0) || (cr_cols > 80))        /* this is questionable -- what would hardware do? */
                cr_cols = 80;

            if (cr_unit.flags & UNIT_PHYSICAL) {
                pcr_xio_startread();
                break;
            }

            if (readstate != STATION_LOADED)
                feedcycle(TRUE, (cp_unit.flags & UNIT_ATT) != 0);

            SET_OP(OP_READING);
            sim_cancel(&cr_unit);
            sim_activate(&cr_unit, cr_wait2501);
            break;

        default:
            sprintf(msg, "Invalid 2501 XIO function %x", func);
            xio_error(msg);
            break;
    }
}

void xio_1442_card (int32 addr, int32 func, int32 modify)
{
    char msg[80];
    int ch;
    uint16 wd;
    t_bool lastcard;

    switch (func) {
        case XIO_SENSE_DEV:
            if (cr_unit.flags & UNIT_PHYSICAL) {
                pcr_xio_sense(modify);
                break;
            }

/* glunk
 * have to separate out what status is 1442 is punch only and 2501 is the reader */

            if (cp_unit.flags & UNIT_ATT)
                lastcard = FALSE;                   /* if punch file is open, assume infinite blank cards in reader */
            else if ((cr_unit.flags & UNIT_ATT) == 0)
                lastcard = TRUE;                    /* if nothing to read, hopper's empty */
            else if (readstate == STATION_LOADED)
                lastcard = FALSE;
            else if (cr_unit.fileref == NULL)
                lastcard = TRUE;
            else if ((ch = getc(cr_unit.fileref)) != EOF) {
                ungetc(ch, cr_unit.fileref);        /* put character back; hopper's not empty */
                lastcard = FALSE;
            }
            else if (deckfile != NULL && nextdeck())
                lastcard = FALSE;
            else
                lastcard = TRUE;                    /* there is nothing left to read for a next card */

            CLRBIT(cr_dsw, CR_DSW_1442_LAST_CARD | CR_DSW_1442_BUSY | CR_DSW_1442_NOT_READY);

            if (lastcard)
                SETBIT(cr_dsw, CR_DSW_1442_LAST_CARD);

            if (CURRENT_OP != OP_IDLE)
                SETBIT(cr_dsw, CR_DSW_1442_BUSY | CR_DSW_1442_NOT_READY);
            else if (readstate == STATION_EMPTY && punchstate == STATION_EMPTY && lastcard)
                SETBIT(cr_dsw, CR_DSW_1442_NOT_READY);

            ACC = cr_dsw;                           /* return the DSW */

            if (cr_unit.flags & UNIT_DEBUG)
                DEBUG_PRINT("#CR Sense %04x%s%s", cr_dsw & 0xFFFF, (modify & 1) ? " RESET0" : "", (modify & 2) ? " RESET4" : "");

            if (modify & 0x01) {                    /* reset interrupts */
                CLRBIT(cr_dsw,  CR_DSW_1442_READ_RESPONSE | CR_DSW_1442_PUNCH_RESPONSE);
                CLRBIT(ILSW[0], ILSW_0_1442_CARD);
            }

            if (modify & 0x02) {
                CLRBIT(cr_dsw,  CR_DSW_1442_OP_COMPLETE);
                CLRBIT(ILSW[4], ILSW_4_1442_CARD);
            }
            break;

        case XIO_READ:                              /* get card data into word pointed to in IOCC packet */
            if (cr_unit.flags & OP_READING) {
                if (cr_unit.COLUMN < 0) {
                    xio_error("1442: Premature read!");
                }
                else if (cr_unit.COLUMN < 80) {
                    WriteW(addr, readstation[cr_unit.COLUMN]);
                    if (cr_unit.flags & UNIT_DEBUG)
                        DEBUG_PRINT("#CR Read %03x", (readstation[cr_unit.COLUMN] >> 4));
                }
                else if (cr_unit.COLUMN == 80) {
                    xio_error("1442: Read past column 80!");
                    cr_unit.COLUMN++;       /* don't report it again */
                }
            }
            else {
/* don't complain: APL\1130 issues both reads and writes on every interrupt
 * (probably to keep the code small). Apparently it's just ignored if corresponding
 *  control didn't initiate a read cycle.
 *              xio_error("1442: Read when not in a read cycle!"); */
            }
            break;

        case XIO_WRITE:
            if (cr_unit.flags & OP_PUNCHING) {
                if (cp_unit.COLUMN < 0) {
                    xio_error("1442: Premature write!");
                }
                else if (cp_unit.flags & UNIT_LASTPUNCH) {
                    xio_error("1442: Punch past last-punch column!");
                    cp_unit.COLUMN = 81;
                }
                else if (cp_unit.COLUMN < 80) {
                    wd = (uint16) ReadW(addr);          /* store one word to punch buffer */
                    punchstation[cp_unit.COLUMN] = wd & 0xFFF0;
                    if (wd & 0x0008)            /* mark this as last column to be punched */
                        SETBIT(cp_unit.flags, UNIT_LASTPUNCH);
                    if (cr_unit.flags & UNIT_DEBUG)
                        DEBUG_PRINT("#CR Punch %03x%s", (wd >> 4) & 0xFFF, (wd & 8) ? " LAST" : "");
                }
                else if (cp_unit.COLUMN == 80) {
                    xio_error("1442: Punch past column 80!");
                    cp_unit.COLUMN++;           /* don't report it again */
                }
            }
            else {
/* don't complain: APL\1130 issues both reads and writes on every interrupt
 * (probably to keep the code small). Apparently it's just ignored if corresponding
 *  control didn't initiate a punch cycle.
 *              xio_error("1442: Write when not in a punch cycle!"); */
            }
            break;

        case XIO_CONTROL:
            switch (modify & 7) {
                case 1:                             /* start punch */
                    if (cr_unit.flags & UNIT_DEBUG)
                        DEBUG_PRINT("#CR Start Punch");
                    if (punchstate != STATION_LOADED)
                        feedcycle(TRUE, TRUE);

                    SET_OP(OP_PUNCHING);
                    cp_unit.COLUMN = -1;

                    CLRBIT(cp_unit.flags, UNIT_LASTPUNCH);

                    any_punched = 1;                /* we've started punching, so enable writing to output deck file */

                    sim_cancel(&cr_unit);
                    sim_activate(&cr_unit, cp_wait);
                    break;

                case 2:                             /* feed cycle */
                    if (cr_unit.flags & UNIT_DEBUG)
                        DEBUG_PRINT("#CR Feed");

                    if (cr_unit.flags & UNIT_PHYSICAL) {
                        pcr_xio_feedcycle();
                        break;
                    }

                    feedcycle(TRUE, (cp_unit.flags & UNIT_ATT) != 0);

                    SET_OP(OP_FEEDING);
                    sim_cancel(&cr_unit);
                    sim_activate(&cr_unit, cf_wait);
                    break;

                case 4:                             /* start read */
                    if (cr_unit.flags & UNIT_DEBUG)
                        DEBUG_PRINT("#CR Start read");

                    cr_unit.COLUMN = -1;

                    if (cr_unit.flags & UNIT_PHYSICAL) {
                        pcr_xio_startread();
                        break;
                    }

                    if (readstate != STATION_LOADED)
                        feedcycle(TRUE, (cp_unit.flags & UNIT_ATT) != 0);

                    SET_OP(OP_READING);
                    sim_cancel(&cr_unit);
                    sim_activate(&cr_unit, cr_wait);
                    break;

                case 0:
                    if (cr_unit.flags & UNIT_DEBUG)
                        DEBUG_PRINT("#CR NOP");
                    break;

                default:
                    sprintf(msg, "1442: Multiple operations in XIO_CONTROL: %x", modify);
                    xio_error(msg);
                    return;
            }

            break;

        default:
            sprintf(msg, "Invalid 1442 XIO function %x", func);
            xio_error(msg);
            break;
    }
}

#if ! (defined(ENABLE_PHYSICAL_CARD_READER_SUPPORT) && defined(WIN32))

    /* stub out the physical card reader routines */

    static t_stat pcr_attach        (UNIT *uptr, CONST char *devname) {return SCPE_ARG;}
    static t_stat pcr_detach        (UNIT *uptr)                {return detach_unit(uptr);}
    static t_stat pcr_svc           (UNIT *uptr)                {return SCPE_OK;}
    static void   pcr_xio_sense     (int modify) {}
    static void   pcr_xio_feedcycle (void) {}
    static void   pcr_xio_startread (void) {}
    static void   pcr_reset         (void) {}

#else

/*
 * This code supports a physical card reader interface I built. Interface schematic
 * and documentation can be downloaded from http://ibm1130.org/sim/downloads/cardread.zip
 */

#include <windows.h>

#define PCR_STATUS_READY         1                  /* bits in interface reply byte */
#define PCR_STATUS_ERROR         2
#define PCR_STATUS_HEMPTY        4
#define PCR_STATUS_EOF           8
#define PCR_STATUS_PICKING      16

#define PCR_STATUS_MSEC         150                 /* when idle, get status every 150 msec */

typedef enum {
    PCR_STATE_IDLE,                                 /* nothing expected from the interface */
    PCR_STATE_WAIT_CMD_RESPONSE,                    /* waiting for response from any command other than P */
    PCR_STATE_WAIT_PICK_CMD_RESPONSE,               /* waiting for response from P command */
    PCR_STATE_WAIT_DATA_START,                      /* waiting for introduction to data from P command */
    PCR_STATE_WAIT_DATA,                            /* waiting for data from P command */
    PCR_STATE_WAIT_PICK_FINAL_RESPONSE,             /* waiting for status byte after last of the card data */
    PCR_STATE_CLOSED
} PCR_STATE;

static void  pcr_cmd (char cmd);
static DWORD CALLBACK pcr_thread (LPVOID arg);
static BOOL  pcr_handle_status_byte (int nrcvd);
static void  pcr_trigger_interrupt_0(void);
static void  begin_pcr_critical_section (void);
static void  end_pcr_critical_section (void);
static void  pcr_set_dsw_from_status (BOOL post_pick);
static t_stat pcr_open_controller (const char *devname);

static PCR_STATE pcr_state  = PCR_STATE_CLOSED;     /* current state of connection to physical card reader interface */
static char      pcr_status = 0;                    /* last status byte received from the interface */
static int       pcr_nleft;                         /* number of bytes still expected from pick command */
static int       pcr_nready;                        /* number of bytes waiting in the input buffer for simulator to read */
static BOOL      pcr_done;
static HANDLE    hpcr        = INVALID_HANDLE_VALUE;
static HANDLE    hPickEvent  = INVALID_HANDLE_VALUE;
static HANDLE    hResetEvent = INVALID_HANDLE_VALUE;
static OVERLAPPED ovRd, ovWr;                       /* overlapped IO structures for reading from, writing to device */
static int       nwaits;                            /* number of timeouts waiting for response from interface */
static char      response_byte;                     /* buffer to receive command/status response byte from overlapped read */
static char      lastcmd = '?';

/* pcr_attach - perform attach function to physical card reader */

static t_stat pcr_attach (UNIT *uptr, CONST char *devname)
{
    DWORD thread_id;
    t_stat rval;

    pcr_state = PCR_STATE_CLOSED;
    sim_cancel(uptr);
    cr_unit.COLUMN = -1;                            /* device is not currently cycling */

    if ((rval = pcr_open_controller(devname)) != SCPE_OK)
        return rval;

    if (hPickEvent == INVALID_HANDLE_VALUE)
        hPickEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

    if (hResetEvent == INVALID_HANDLE_VALUE)
        hResetEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

    pcr_status = PCR_STATUS_HEMPTY;                 /* set default status: offline, no cards */
    pcr_state  = PCR_STATE_IDLE;
    pcr_done   = FALSE;
    cr_dsw     = CR_DSW_1442_LAST_CARD | CR_DSW_1442_NOT_READY;

    set_active_cr_code(CODE_BINARY);                /* force binary mode */

    if (CreateThread(NULL, 0, pcr_thread, NULL, 0, &thread_id) == NULL) {
        pcr_state = PCR_STATE_CLOSED;
        CloseHandle(hpcr);
        hpcr = INVALID_HANDLE_VALUE;
        printf("Error creating card reader thread\n");
        return SCPE_IERR;
    }

    SETBIT(uptr->flags, UNIT_PHYSICAL|UNIT_ATT);    /* mark device as attached */
    uptr->filename = (char *)malloc(strlen(devname)+1);
    strcpy(uptr->filename, devname);

    return SCPE_OK;
}

/* pcr_open_controller - open the USB device's virtual COM port and configure the interface */

static t_stat pcr_open_controller (const char *devname)
{
    DCB dcb;
    COMMTIMEOUTS cto;
    DWORD nerr;

    if (hpcr != INVALID_HANDLE_VALUE)
        return SCPE_OK;
                                                    /* open the COM port */
    hpcr = CreateFile(devname, GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
    if (hpcr == INVALID_HANDLE_VALUE)
        return SCPE_OPENERR;

    memset(&dcb, 0, sizeof(dcb));                   /* set communications parameters */

    dcb.DCBlength       = sizeof(DCB);
    dcb.BaudRate        = CBR_115200;               /* for the USB virtual com port, baud rate is irrelevant */
    dcb.fBinary         = 1;
    dcb.fParity         = 0;
    dcb.fOutxCtsFlow    = 0;
    dcb.fOutxDsrFlow    = 0;
    dcb.fDtrControl     = DTR_CONTROL_ENABLE;
    dcb.fDsrSensitivity = FALSE;
    dcb.fTXContinueOnXoff = 0;
    dcb.fOutX           = 0;
    dcb.fInX            = 0;
    dcb.fErrorChar      = 0;
    dcb.fNull           = 0;
    dcb.fRtsControl     = RTS_CONTROL_ENABLE;                
    dcb.fAbortOnError   = 0;
    dcb.XonLim          = 0;
    dcb.XoffLim         = 0;
    dcb.ByteSize        = 8;
    dcb.Parity          = NOPARITY;
    dcb.StopBits        = ONESTOPBIT;
    dcb.XonChar         = 0;
    dcb.XoffChar        = 0;
    dcb.ErrorChar       = 0;
    dcb.EofChar         = 0;
    dcb.EvtChar         = 0;

    if (! SetCommState(hpcr, &dcb)) {
        CloseHandle(hpcr);
        hpcr = INVALID_HANDLE_VALUE;
        printf("Call to SetCommState failed\n");
        return SCPE_OPENERR;
    }

    cto.ReadIntervalTimeout         = 100;          /* stop if 100 msec elapses between two received bytes */
    cto.ReadTotalTimeoutMultiplier  = 0;            /* no length sensitivity */
    cto.ReadTotalTimeoutConstant    = 400;          /* allow 400 msec for a read (reset command can take a while) */

    cto.WriteTotalTimeoutMultiplier = 0;
    cto.WriteTotalTimeoutConstant   = 200;          /* allow 200 msec for a write */

    if (! SetCommTimeouts(hpcr, &cto)) {
        CloseHandle(hpcr);
        hpcr = INVALID_HANDLE_VALUE;
        printf("Call to SetCommTimeouts failed\n");
        return SCPE_OPENERR;
    }

    PurgeComm(hpcr, PURGE_TXABORT|PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR);
    ClearCommError(hpcr, &nerr, NULL);

    return SCPE_OK;
}

/* pcr_detach - detach physical reader from CR device */

static t_stat pcr_detach (UNIT *uptr)
{
    if (cr_unit.flags & UNIT_ATT) {
        CloseHandle(hpcr);                          /* close the COM port (this will lead to the thread closing) */
        hpcr = INVALID_HANDLE_VALUE;
        pcr_state = PCR_STATE_CLOSED;

        free(uptr->filename);                       /* release the name copy */
        uptr->filename = NULL;
    }

    CLRBIT(cr_unit.flags, UNIT_PHYSICAL|UNIT_ATT);  /* drop the attach and physical bits */
    return SCPE_OK;
}

/* pcr_xio_sense - perform XIO sense function on physical card reader */

static void pcr_xio_sense (int modify)
{
    if (modify & 0x01) {                            /* reset simulated interrupts */
        CLRBIT(cr_dsw,  CR_DSW_1442_READ_RESPONSE | CR_DSW_1442_PUNCH_RESPONSE);
        CLRBIT(ILSW[0], ILSW_0_1442_CARD);
    }

    if (modify & 0x02) {
        CLRBIT(cr_dsw,  CR_DSW_1442_OP_COMPLETE);
        CLRBIT(ILSW[4], ILSW_4_1442_CARD);
    }

    ACC = cr_dsw;                                   /* DSW was set in real-time, just return the DSW */

    if (cr_unit.flags & UNIT_DEBUG)
        DEBUG_PRINT("#CR Sense %04x%s%s", cr_dsw, (modify & 1) ? " RESET0" : "", (modify & 2) ? " RESET4" : "");
}

/* report_error - issue detailed report of Windows IO error */

static void report_error (char *msg, DWORD err)
{
    char *lpMessageBuffer = NULL;
        
    FormatMessage(
      FORMAT_MESSAGE_ALLOCATE_BUFFER |
      FORMAT_MESSAGE_FROM_SYSTEM,
      NULL,
      GetLastError(),
      MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), /* The user default language */
      (LPTSTR) &lpMessageBuffer,
      0,
      NULL );

    printf("GetOverlappedResult failed, %s, %s\n",
        msg, lpMessageBuffer);

    LocalFree(lpMessageBuffer);
}

/* pcr_thread - thread to handle card reader interface communications */

static DWORD CALLBACK pcr_thread (LPVOID arg)
{
    DWORD event;
    long nrcvd, nread, nwritten;
    HANDLE objs[4];
    BOOL pick_queued = FALSE, reset_queued = FALSE;

    nwaits = 0;

    ZeroMemory(&ovRd,  sizeof(ovRd));
    ZeroMemory(&ovWr,  sizeof(ovWr));
    ovRd.hEvent = CreateEvent(NULL, TRUE,  FALSE, NULL);    /* create an event for async IO reads */
    ovWr.hEvent = CreateEvent(NULL, TRUE,  FALSE, NULL);    /* create an event for async IO writes */

    objs[0] = ovRd.hEvent;
    objs[1] = ovWr.hEvent;
    objs[2] = hResetEvent;
    objs[3] = hPickEvent;

    while (hpcr != INVALID_HANDLE_VALUE) {
        if (pcr_state == PCR_STATE_IDLE) {
            if (pick_queued) {
                pcr_cmd('P');
                pick_queued = FALSE;
                pcr_done    = FALSE;
                pcr_state   = PCR_STATE_WAIT_PICK_CMD_RESPONSE;
            }
            else if (reset_queued) {
                pcr_cmd('X');
                reset_queued = FALSE;
                pcr_state = PCR_STATE_WAIT_CMD_RESPONSE;
            }
        }

        event = WaitForMultipleObjects(4, objs, FALSE, PCR_STATUS_MSEC);

        switch (event) {
            case WAIT_OBJECT_0+0:                       /* read complete */
                ResetEvent(ovRd.hEvent);
                if (! GetOverlappedResult(hpcr, &ovRd, &nrcvd, TRUE))
                    report_error("PCR_Read", GetLastError());
                else if (cr_unit.flags & UNIT_DEBUG)
                    printf("PCR_Read: event, %ld rcvd\n", nrcvd);
                break;

            case WAIT_OBJECT_0+1:                       /* write complete */
                nwritten = 0;
                ResetEvent(ovWr.hEvent);
                if (! GetOverlappedResult(hpcr, &ovWr, &nwritten, TRUE))
                    report_error("PCR_Write", GetLastError());
                else if (cr_unit.flags & UNIT_DEBUG)
                    printf("PCR_Write: event, %ld sent\n", nwritten);
                continue;

            case WAIT_OBJECT_0+2:                       /* reset request from simulator */
                reset_queued = TRUE;
                pick_queued  = FALSE;
                continue;

            case WAIT_OBJECT_0+3:                       /* pick request from simulator */
                pick_queued = TRUE;
                continue;

            case WAIT_TIMEOUT:
                if (pcr_state == PCR_STATE_IDLE) {
                    pcr_state = PCR_STATE_WAIT_CMD_RESPONSE;
                    ovRd.Offset = ovRd.OffsetHigh = 0;
                    pcr_cmd('S');
                }
                else if (pcr_state == PCR_STATE_WAIT_CMD_RESPONSE && ++nwaits >= 6) {
                    printf("Requesting status again!\n");
                    ovRd.Offset = ovRd.OffsetHigh = 0;
                    pcr_cmd('S');
                }
                continue;

            default:
                printf("Unexpected pcr_wait result %08lx", event);
                continue;
        }

        /* We only get here if read event occurred */

        switch (pcr_state) {
            case PCR_STATE_IDLE:                        /* nothing expected from the interface */
                PurgeComm(hpcr, PURGE_RXCLEAR|PURGE_RXABORT);
                break;

            case PCR_STATE_WAIT_CMD_RESPONSE:           /* waiting for response from any command other than P */
                if (pcr_handle_status_byte(nrcvd))
                    pcr_state = PCR_STATE_IDLE;
                break;

            case PCR_STATE_WAIT_PICK_CMD_RESPONSE:      /* waiting for response from P command */
                if (pcr_handle_status_byte(nrcvd)) {
                    pcr_cmd('\0');                      /* queue a response read */
                    pcr_state = PCR_STATE_WAIT_DATA_START;
                }
                break;

            case PCR_STATE_WAIT_DATA_START:             /* waiting for leadin character from P command (= or !) */
                if (nrcvd <= 0) {                       /* (this could take an indefinite amount of time) */
                    if (cr_unit.flags & UNIT_DEBUG) 
                        printf("PCR: NO RESP YET\n");

                    continue;                           /* reader is not ready */
                }

                if (cr_unit.flags & UNIT_DEBUG)         /* (this could take an indefinite amount of time) */
                    printf("PCR: GOT %c\n", response_byte);

                switch (response_byte) {
                    case '=':                           /* = means pick in progress, 160 bytes of data will be coming */
                        pcr_state  = PCR_STATE_WAIT_DATA;
                        ovRd.Offset = ovRd.OffsetHigh = 0;
                        nread = 20;                     /* initiate a read */
                        ReadFile(hpcr, ((char *) readstation), nread, &nrcvd, &ovRd);
                        break;

                    case '!':                           /* ! means pick has been canceled, status will be coming next */
                        pcr_state = PCR_STATE_WAIT_CMD_RESPONSE;
                        pcr_cmd('\0');                  /* initiate read */
                        break;

                    default:                            /* anything else is a datacomm error, or something */
                        /* indicate read check or something */
/*                      pcr_state = PCR_STATE_IDLE; */
                        break;
                }
                break;

            case PCR_STATE_WAIT_DATA:                   /* waiting for data from P command */
                if (cr_unit.flags & UNIT_DEBUG)
                    printf((nrcvd <= 0) ? "PCR: NO RESP!\n" : "PCR: GOT %d BYTES\n", nrcvd);

                if (nrcvd > 0) {
                    pcr_nleft -= nrcvd;

                    begin_pcr_critical_section();
                    pcr_nready += nrcvd;
                    end_pcr_critical_section();
                }

                if (pcr_nleft > 0) {
                    ovRd.Offset = ovRd.OffsetHigh = 0;
                    nread = min(pcr_nleft, 20);
                    ReadFile(hpcr, ((char *) readstation)+160-pcr_nleft, nread, &nrcvd, &ovRd);
                }
                else {
                    pcr_state = PCR_STATE_WAIT_PICK_FINAL_RESPONSE;
                    pcr_cmd('\0');                          /* queue read */
                }
                break;

            case PCR_STATE_WAIT_PICK_FINAL_RESPONSE:        /* waiting for status byte after last of the card data */
                if (pcr_handle_status_byte(nrcvd)) {
                    readstate = STATION_READ;
                    pcr_state = PCR_STATE_IDLE;
                    pcr_done  = TRUE;
                }
                break;
        }
    }

    CloseHandle(ovRd.hEvent);
    CloseHandle(ovWr.hEvent);

    return 0;
}

/* pcr_cmd - issue command byte to interface. Read of response byte is queued */

static void pcr_cmd (char cmd)
{
    long nwritten, nrcvd;
    int status;

    if (cmd != '\0') {
        if (cr_unit.flags & UNIT_DEBUG /* && (cmd != 'S' || cmd != lastcmd) */)
            printf("PCR: SENT %c\n", cmd);

        lastcmd = cmd;

        ResetEvent(ovWr.hEvent);
        ovWr.Offset = ovWr.OffsetHigh = 0;
        status = WriteFile(hpcr, &cmd, 1, &nwritten, &ovWr);
        if (status == 0 && GetLastError() != ERROR_IO_PENDING)
            printf("Error initiating write in pcr_cmd\n");
    }

    ovRd.Offset = ovRd.OffsetHigh = 0;
    status = ReadFile(hpcr, &response_byte, 1, &nrcvd, &ovRd);      /* if no bytes ready, just return -- a later wait-event will catch it */
    if (status == 0 && GetLastError() != ERROR_IO_PENDING)
        printf("Error initiating read in pcr_cmd\n");

/*  if (cr_unit.flags & UNIT_DEBUG)
 *      if (nrcvd == 0)
 *          printf("PCR: NO RESPONSE\n");
 *      else
 *          printf("PCR: RESPONSE %c\n", response_byte); */

    nwaits = 0;
}

/* pcr_handle_status_byte - handle completion of read of response byte */

static BOOL pcr_handle_status_byte (int nrcvd)
{
    static char prev_status = '?';
    BOOL show;

    if (nrcvd <= 0)
        return FALSE;

    pcr_status = response_byte;                     /* save new status */

    show = lastcmd != 'S' || pcr_status != prev_status;

    if ((cr_unit.flags & UNIT_DEBUG) && show) {
        printf("PCR: status %c\n", pcr_status);
        prev_status = pcr_status;
    }

    pcr_set_dsw_from_status(FALSE);

    return TRUE;
}

/* pcr_set_dsw_from_status - construct device status word from current physical reader status */

static void pcr_set_dsw_from_status (BOOL post_pick)
{
                                                    /* set 1130 status word bits */
    CLRBIT(cr_dsw, CR_DSW_1442_LAST_CARD | CR_DSW_1442_BUSY | CR_DSW_1442_NOT_READY | CR_DSW_1442_ERROR_CHECK);

    if (pcr_status & PCR_STATUS_HEMPTY)             
        SETBIT(cr_dsw, CR_DSW_1442_LAST_CARD | CR_DSW_1442_NOT_READY);

    if (pcr_status & PCR_STATUS_ERROR)
        SETBIT(cr_dsw, CR_DSW_1442_ERROR_CHECK);

    /* we have a problem -- ready doesn't come back up right away after a pick. */
    /* I think I'll fudge this and not set NOT_READY immediately after a pick   */

    if ((! post_pick) && ! (pcr_status & PCR_STATUS_READY))
        SETBIT(cr_dsw, CR_DSW_1442_NOT_READY);

    if (CURRENT_OP != OP_IDLE)
        SETBIT(cr_dsw, CR_DSW_1442_BUSY | CR_DSW_1442_NOT_READY);
}

static void pcr_xio_feedcycle (void)
{
    SET_OP(OP_FEEDING);
    cr_unit.COLUMN = -1;
    SetEvent(hPickEvent);
    sim_activate(&cr_unit, cr_wait);            /* keep checking frequently */
}

static void pcr_xio_startread (void)
{
    SET_OP(OP_READING);
    cr_unit.COLUMN = -1;
    pcr_nleft  = 160;
    pcr_nready = 0;
    SetEvent(hPickEvent);
    sim_activate(&cr_unit, cr_wait);            /* keep checking frequently */
}

static void pcr_reset (void)
{
    pcr_status = PCR_STATUS_HEMPTY;             /* set default status: offline, no cards */
    pcr_state  = PCR_STATE_IDLE;
    cr_dsw     = CR_DSW_1442_LAST_CARD | CR_DSW_1442_NOT_READY;

    sim_cancel(&cr_unit);

    SetEvent(hResetEvent);
}

/* pcr_trigger_interrupt_0 - simulate a read response interrupt so OS will read queued column data */

static void pcr_trigger_interrupt_0 (void)
{
    if (++cr_unit.COLUMN < 80) {
        SETBIT(cr_dsw,  CR_DSW_1442_READ_RESPONSE);
        SETBIT(ILSW[0], ILSW_0_1442_CARD);
        calc_ints();

        begin_pcr_critical_section();
        pcr_nready -= 2;
        end_pcr_critical_section();

        if (cr_unit.flags & UNIT_DEBUG)
            printf("SET IRQ0 col %d\n", cr_unit.COLUMN+1);
    }
}

static t_stat pcr_svc (UNIT *uptr)
{
    switch (CURRENT_OP) {
        case OP_IDLE:
            break;

        case OP_READING:
            if (pcr_nready >= 2) {                          /* if there is a whole column buffered, simulate column interrupt*/
                                                            /* pcr_trigger_interrupt_0 - simulate a read response interrupt so OS will read queued column data */

                pcr_trigger_interrupt_0();
                sim_activate(&cr_unit, cr_wait);            /* keep checking frequently */
            }
            else if (pcr_done) {
                pcr_done = FALSE;
                cr_count++;
                op_done(&cr_unit, "pcr read", TRUE);
                pcr_set_dsw_from_status(TRUE);
            }
            else
                sim_activate(&cr_unit, cr_wait);            /* keep checking frequently */
            break;

        case OP_FEEDING:
            if (pcr_done) {
                cr_count++;
                op_done(&cr_unit, "pcr feed", FALSE);
                pcr_set_dsw_from_status(TRUE);
            }
            else
                sim_activate(&cr_unit, cr_wait);            /* keep checking frequently */

            break;

        case OP_PUNCHING:
            return cr_svc(uptr);
    }

    return SCPE_OK;
}

static CRITICAL_SECTION pcr_critsect;

static void begin_pcr_critical_section (void)
{
    static BOOL mustinit = TRUE;

    if (mustinit) {
        InitializeCriticalSection(&pcr_critsect);
        mustinit = FALSE;
    }

    EnterCriticalSection(&pcr_critsect);
}

static void end_pcr_critical_section (void)
{
    LeaveCriticalSection(&pcr_critsect);
}

#endif

