/* hp2100_lpt.c: HP 2100 12845B Line Printer Interface simulator

   Copyright (c) 1993-2016, Robert M. Supnik
   Copyright (c) 2017-2018, J. David Bryan

   Permission is hereby granted, free of charge, to any person obtaining a copy
   of this software and associated documentation files (the "Software"), to deal
   in the Software without restriction, including without limitation the rights
   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   copies of the Software, and to permit persons to whom the Software is
   furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included in
   all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
   AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
   ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
   WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

   Except as contained in this notice, the names of the authors shall not be
   used in advertising or otherwise to promote the sale, use or other dealings
   in this Software without prior written authorization from the authors.

   LPT          HP 12845B Line Printer Interface

   26-Jun-18    JDB     Revised I/O model
   16-May-17    JDB     Changed REG_A to REG_X
   07-Feb-17    JDB     Passes the 2613/17/18 diagnostic (DSN 145103)
   04-Feb-17    JDB     Rewrote to add the HP 2613/17/18 line printers
   13-May-16    JDB     Modified for revised SCP API function parameter types
   10-Feb-12    JDB     Deprecated DEVNO in favor of SC
   28-Mar-11    JDB     Tidied up signal handling
   26-Oct-10    JDB     Changed I/O signal handler for revised signal model
   26-Jun-08    JDB     Rewrote device I/O to model backplane signals
                        Changed CTIME register width to match documentation
   22-Jan-07    RMS     Added UNIT_TEXT flag
   28-Dec-06    JDB     Added ioCRS state to I/O decoders
   19-Nov-04    JDB     Added restart when set online, etc.
   29-Sep-04    JDB     Added SET OFFLINE/ONLINE, POWEROFF/POWERON
                        Fixed status returns for error conditions
                        Fixed TOF handling so form remains on line 0
   03-Jun-04    RMS     Fixed timing (found by Dave Bryan)
   26-Apr-04    RMS     Fixed SFS x,C and SFC x,C
                        Implemented DMA SRQ (follows FLG)
   25-Apr-03    RMS     Revised for extended file support
   24-Oct-02    RMS     Cloned from 12653A

   References:
     - 12845B Line Printer Interface Kit Installation and Service Manual
         (12845-90011, January 1982)
     - HP 2607 Line Printer Diagnostic
         (12987-90004, September 1974)
     - 2613A/2617A/2618A Line Printer Diagnostic Operator's Manual
         (02618-90006, November 1977)
     - RTE Line Printer Driver (DVA12) Reference Manual
         (92001-90010, October 1980)


   The HP 12845B Line Printer Interface Kit connects the 2607A, 2613A, 2617A,
   and 2618A printers to the HP 1000 family.  Each subsystem consists of an
   interface card employing differential line drivers and receivers, an
   interconnecting cable, and an HP 2607A (200 lines per minute), HP 2613 (300
   lpm), HP 2617 (600 lpm), or HP 2618 (1250 lpm) line printer.  The interface
   is supported by RTE driver DVA12 and DOS driver DVR12.  The interface
   supports DMA transfers, but the OS drivers do not use them.

   Two versions of this interface were produced.  The 12845A interface is
   designed to connect the HP 2610 and HP 2614 line printers.  The 12845B
   interface supports these two printers, plus the 2607, 2613, 2617, 2618, 2619,
   and 2631.  The "A" card does not permit DMA transfers, whereas the "B" card
   does.  The latter provides a configuration jumper, "STR", that selectively
   delays the strobe to the printer.  This is needed because DMA output cycles
   assert IOO and STC in same cycle, i.e., data is coincident with strobe, and
   this is insufficient for the 2607, 2610 and 2614.  Placing "STR" in position
   2 delays the strobe for one cycle.  DMA with other printers and all non-DMA
   operation uses "STR" in position 1.

   The 12845B outputs seven data bits to the printer plus these control signals:

     - Master Clear
     - Paper Instruction
     - Strobe

   Master Clear is asserted by CRS and clears the line printer buffer and aborts
   a paper advance in progress (except on the 2613).  It does not abort a print
   cycle in progress.  Paper Instruction asserts to indicate that the data lines
   contain a format word that commands a print-and-slew operation.  Strobe
   asserts to indicate that the signals on the data lines are valid.

   The interface receives these status signals from the printer:

     - Demand
     - Buffer Ready
     - Printer Ready
     - Printer Online
     - VFU Channel 9
     - VFU Channel 12

   Demand denies when the printer is busy and asserts to indicate that the
   printer has completed the requested operation.  Denial causes Strobe to
   deny.  Assertion sets the flag buffer flip-flop if Buffer Ready is also
   asserted.

   Buffer Ready denies when the printer is printing and asserts when printing
   completes and the print buffer may be loaded.  Buffer Ready and Demand are
   reflected in bit 0 of the status word provided to the CPU.  The 2607 has
   Buffer Ready permanently asserted (it is double-buffered and can accept
   characters while printing).

   Printer Ready asserts when power is applied and no fault condition exists.
   It is inverted (i.e., to indicate Not Ready) and presented as bit 14 of the
   status word.

   Printer Online asserts while the printer is online.  It is reflected in bit
   15 of the status word.

   VFU Channel 9 and VFU channel 12 assert when the paper is positioned at the
   print line corresponding to holes punched in the associated VFU tape
   locations.  They are presented as status word bits 13 and 12, respectively.

   The interface status word contains these values for the 2613/17/18 printers:

     Value   Meaning
     ------  -------------------------------------
     140001  Power off or cable disconnected
     100001  Power on, paper loaded, printer ready
     100000  Power on, paper loaded, printer busy
     000000  Power on, print button up
     040000  Power on, paper out or drum gate open

   The 2607 status word values are:

     Value   Meaning
     ------  -----------------------------------------------------
     140001  Power off or cable disconnected
     100001  Power on, paper loaded, printer ready
     100000  Power on, paper loaded, printer busy
     000000  Power on, paper out or print button up or platen open

   The simulator supports a single line printer.  The supported printers are
   configured with Option 001, which provides a 128 (2607) or 96 (2613/17/18)
   character set.  Two output modes are provided: an expanded mode that is
   suitable for retaining printer output as a text file, and a compact mode that
   is suitable for sending the printer output to a host-connected physical
   printer.  An 8-channel (2607) or 12-channel (2613/17/18) Vertical Format Unit
   is supported, and custom VFU tape images may be loaded from properly
   formatted host-system text files.

   The printer supports realistic and optimized (fast) timing modes.  Realistic
   timing attempts to model the print buffer load and print-and-space operation
   delays inherent in the physical hardware.  For example, in REALTIME mode,
   output of longer lines takes more time than output of shorter lines, and
   spacing six lines takes approximately six times longer than spacing one line.
   In FASTTIME mode, all timings are reduced to be "just long enough" to satisfy
   the software driver requirements.

   Attaching the LPT device simulates loading paper into the printer.  Detaching
   simulates removing or running out of paper.

   The printer may be set offline or online.  It may also be powered off or on
   to accommodate the interface diagnostic.  A diagnostic mode is provided to
   install the 02613-80002 or 02618-80002 VFU tape.  This is the standard VFU
   tape with channel 10 punched for line 59 (BOF - 1), channel 11 punched for
   line 66 (TOF - 1), and channel 12 punched for line 1 (TOF).

   When the ON/OFFLINE button on the printer is pressed, the printer will not go
   offline (i.e., deny the ONLINE signal) if there are characters in the print
   buffer.  Instead, the offline condition is held off until an internal "allow
   offline" signal asserts.  This occurs when the print buffer is empty and the
   print cycle is inactive.

   When a "paper out" condition occurs, the 2613/17/18 printers will go offline
   at the end of the current line.  The 2607 printer waits until the top-of-form
   is seen before going offline.  This has implications for the SET OFFLINE and
   DETACH commands if they are issued while the print buffer contains data or
   the printer unit is busy executing a print action.

   The SET LPT OFFLINE and DETACH LPT commands check for data in the print
   buffer or a print operation in progress.  If either condition is true, they
   set their respective deferred-action flags and display "Command not
   completed."  A SHOW LPT will show that the device is still online and
   attached.  Once simulation is resumed and the print operation completes, the
   printer is set offline or detached as requested.  No console message reports
   this, as it is assumed that the executing program will detect the condition
   and report accordingly.  A subsequent SHOW LPT will indicate the new status.

   A SET LPT ONLINE command when a deferred-action flag is set simply clears the
   flag, which cancels the pending offline or detach condition.

   A RESET LPT command also clears the deferred-action flags and so clears any
   pending offline or detach.  However, it also clears the print buffer and
   terminates any print action in progress, so a SET LPT OFFLINE or DETACH LPT
   will succeed if issued subsequently.

   An immediate detach may be forced by the DETACH -F LPT command.  This
   simulates physically removing the paper from the printer and succeeds
   regardless of the current printer state.


   The interface responds to I/O instructions as follows:

   Output Data Word format (OTA and OTB):

      15  14  13  12  11  10   9   8   7   6   5   4   3   2   1   0
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 0 | -   -   -   -   -   -   -   - |      ASCII character      | character
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 1 | -   -   -   -   -   -   -   - |        format word        | format
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   The printers use only seven data bits, so the MSB of each byte is ignored.
   If the printer's line length is exceeded during write operations, the
   buffered line will be printed, the paper will be advanced one line, and the
   buffer will be cleared to accept the character causing the overflow.

   The format commands recognized by the printers are:

     0 0 0 0 0 0 0 -- slew 0 lines (suppress spacing) after printing *
          ...
     0 0 0 1 1 1 1 -- slew 15 lines after printing

     0 0 1 0 0 0 0 -- slew 16 lines after printing **
          ...
     0 1 1 1 1 1 1 -- slew 63 lines after printing **

      * slew 1 line on the 2607A, which cannot suppress printing.
     ** available only on the 2610A and 2614A

   and:

     1 x x 0 0 0 0 -- slew to VFU channel 1 after printing
          ...
     1 x x 0 1 1 1 -- slew to VFU channel 8 after printing

     1 x x 1 0 0 0 -- slew to VFU channel 9 after printing *
          ...
     1 x x 1 0 1 1 -- slew to VFU channel 12 after printing *

     * available only on the 2613A, 2617A, and 2618A


   Input Data Word format (LIA, LIB, MIA, and MIB):

      15  14  13  12  11  10   9   8   7   6   5   4   3   2   1   0
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | L | R | V | U | -   -   -   -   -   -   -   -   -   -   - | D |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   Where:

     L = Online
     R = Not ready
     V = VFU channel 9
     U = VFU channel 12
     D = Demand


   Implementation notes:

     1. The hardware STR jumper that delays the strobe for one cycle for DMA
        compatibility with the 2607, 2610, and 2614 printers is not implemented,
        as it is irrelevant for simulation.

     2. The hardware switch S1 (the NORMAL/TEST switch) that allows printer
        testing without a diagnostic program is not implemented.
*/



#include "hp2100_defs.h"
#include "hp2100_io.h"



/* Printer program constants */

#define CR                  '\r'                /* carriage return */
#define LF                  '\n'                /* line feed */
#define FF                  '\f'                /* form feed */
#define DEL                 '\177'              /* delete */

#define DATA_MASK           0177u               /* printer uses only 7 bits for data */
#define FORMAT_VFU          0100u               /* printer VFU selector */
#define FORMAT_MASK         0117u               /* printer format command mask for 12-channel VFU */
#define FORMAT_VFU_8_MASK   0107u               /* printer format command mask for 8-channel VFU */

#define FORMAT_SUPPRESS     0000u               /* format code to slew 0 lines */
#define FORMAT_VFU_CHAN_1   0100u               /* format code to slew to VFU channel 1 */
#define FORMAT_VFU_BIAS     0077u               /* bias converting from format code to channel number */

#define VFU_MAX             144                 /* maximum number of VFU form lines */
#define VFU_SIZE            (VFU_MAX + 1)       /* size of the VFU array */
#define LINE_SIZE           256                 /* size of the character array used to read the VFU file */

#define VFU_WIDTH           12                  /* maximum number of VFU channels */

#define VFU_CHANNEL_1       04000u              /* top of form */
#define VFU_CHANNEL_2       02000u              /* bottom of form */
#define VFU_CHANNEL_3       01000u              /* single space */
#define VFU_CHANNEL_4       00400u              /* double space */
#define VFU_CHANNEL_5       00200u              /* triple space */
#define VFU_CHANNEL_6       00100u              /* half page */
#define VFU_CHANNEL_7       00040u              /* quarter page */
#define VFU_CHANNEL_8       00020u              /* sixth page */
#define VFU_CHANNEL_9       00010u              /* bottom of form */
#define VFU_CHANNEL_10      00004u              /* (unassigned) */
#define VFU_CHANNEL_11      00002u              /* (unassigned) */
#define VFU_CHANNEL_12      00001u              /* (unassigned) */

#define CHARS_MAX           136                 /* maximum number of characters buffered by the printers */

#define BUFFER_SIZE         (CHARS_MAX + VFU_MAX * 2)   /* max chars + max VFU * 2 (for CR LF) */


/* Device flags */

#define DEV_DIAG_SHIFT      (DEV_V_UF + 0)              /* diagnostic VFU tape is installed */
#define DEV_REALTIME_SHIFT  (DEV_V_UF + 1)              /* timing mode is realistic */

#define DEV_DIAG            (1u << DEV_DIAG_SHIFT)      /* diagnostic mode flag */
#define DEV_REALTIME        (1u << DEV_REALTIME_SHIFT)  /* realistic timing flag */


/* Printer unit flags.

   UNIT_V_UF +  7   6   5   4   3   2   1   0
              +---+---+---+---+---+---+---+---+
              | - | - | P | O | E |   model   |
              +---+---+---+---+---+---+---+---+

   Where:

     P = power is off
     O = offline
     E = expanded output
*/

#define UNIT_MODEL_SHIFT    (UNIT_V_UF + 0)     /* printer model ID */
#define UNIT_EXPAND_SHIFT   (UNIT_V_UF + 3)     /* printer uses expanded output */
#define UNIT_OFFLINE_SHIFT  (UNIT_V_UF + 4)     /* printer is offline */
#define UNIT_POWEROFF_SHIFT (UNIT_V_UF + 5)     /* printer power is off */

#define UNIT_MODEL_MASK     0000007u            /* model ID mask */

#define UNIT_MODEL          (UNIT_MODEL_MASK << UNIT_MODEL_SHIFT)
#define UNIT_EXPAND         (1u << UNIT_EXPAND_SHIFT)
#define UNIT_OFFLINE        (1u << UNIT_OFFLINE_SHIFT)
#define UNIT_ONLINE         0
#define UNIT_POWEROFF       (1u << UNIT_POWEROFF_SHIFT)

#define UNIT_2607           (HP_2607 << UNIT_MODEL_SHIFT)
#define UNIT_2613           (HP_2613 << UNIT_MODEL_SHIFT)
#define UNIT_2617           (HP_2617 << UNIT_MODEL_SHIFT)
#define UNIT_2618           (HP_2618 << UNIT_MODEL_SHIFT)


/* Unit flags accessor */

#define GET_MODEL(f)        ((PRINTER_TYPE) (((f) >> UNIT_MODEL_SHIFT) & UNIT_MODEL_MASK))


/* Printer types */

typedef enum {
    HP_2607,                                    /* HP 2607A */
    HP_2613,                                    /* HP 2613A */
    HP_2617,                                    /* HP 2617A */
    HP_2618                                     /* HP 2618A */
    } PRINTER_TYPE;


/* Printer locality states */

typedef enum {
    Offline,                                    /* printer is going offline */
    Online                                      /* printer is going online */
    } LOCALITY;


/* Printer properties table.

   This table contains the characteristics that vary between printer models.
   The "char_set" field values reflect printer Option 001, 96/128-character set.
   The "not_ready" field indicates whether a paper fault sets a separate
   not-ready status or simply takes the printer offline.  The "fault_at_eol"
   field indicates whether a paper fault is reported at the end of any line or
   only at the top of the next form.
*/

typedef struct {
    uint32  line_length;                        /* the maximum number of print positions */
    uint32  char_set;                           /* the size of the character set */
    uint32  vfu_channels;                       /* the number of VFU channels */
    t_bool  not_ready;                          /* TRUE if the printer reports a separate not ready status */
    t_bool  overprints;                         /* TRUE if the printer supports overprinting */
    t_bool  autoprints;                         /* TRUE if the printer automatically prints on buffer overflow */
    t_bool  fault_at_eol;                       /* TRUE if a paper fault is reported at the end of any line */
    } PRINTER_PROPS;

static const PRINTER_PROPS print_props [] = {   /* printer properties, indexed by PRINTER_TYPE */
/*     line    char    VFU      not     over    auto   fault  */
/*    length   set   channels  ready   prints  prints  at EOL */
/*    ------  -----  --------  ------  ------  ------  ------ */
    {  132,   128,      8,     FALSE,  FALSE,  TRUE,   FALSE  },    /* HP_2607 */
    {  136,    96,     12,     TRUE,   TRUE,   FALSE,  TRUE   },    /* HP_2613 */
    {  136,    96,     12,     TRUE,   TRUE,   FALSE,  TRUE   },    /* HP_2617 */
    {  132,    96,     12,     TRUE,   TRUE,   FALSE,  TRUE   }     /* HP_2618 */
    };


/* Delay properties table.

   To support the realistic timing mode, the delay properties table contains
   timing specifications for the supported printers.  The times represent the
   delays for mechanical and electronic operations.  Delay values are in event
   tick counts; macros are used to convert from times to ticks.


   Implementation notes:

    1. Although all of the printers operate more slowly with a 96/128-character
       set installed than with a 64-character set, the times reflect the smaller
       set size.  Also, some models provide different print rates, depending on
       how many and/or which characters are printed.  These variations are not
       simulated.
*/

typedef struct {
    int32  buffer_load;                         /* per-character transfer time */
    int32  print;                               /* print time */
    int32  advance;                             /* paper advance time per line */
    } DELAY_PROPS;

static const DELAY_PROPS real_times [] = {      /* real-time delays, indexed by PRINTER_TYPE */
   /*  buffer                paper   */
   /*   load      print     advance  */
   /* ---------  --------  --------- */
    { uS (12.6), mS (260), mS (40.1) },         /* HP_2607  200 lines per minute */
    { uS (1.75), mS (183), mS (8.33) },         /* HP_2613  300 lines per minute */
    { uS (1.75), mS ( 86), mS (6.67) },         /* HP_2617  600 lines per minute */
    { uS (1.75), mS ( 38), mS (4.76) }          /* HP_2618 1250 lines per minute */
    };

#define LP_BUFFER_LOAD      uS (1)              /* fast per-character transfer time */
#define LP_PRINT            mS (1)              /* fast print time */
#define LP_ADVANCE          uS (50)             /* fast paper advance time per line */

static DELAY_PROPS fast_times =                 /* FASTTIME delays */
    { LP_BUFFER_LOAD,
      LP_PRINT,
      LP_ADVANCE
    };


/* Printer control word.

      15  14  13  12  11  10   9   8   7   6   5   4   3   2   1   0
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 0 | -   -   -   -   -   -   -   - |      ASCII character      | character
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 1 | -   -   -   -   -   -   -   - |        format word        | format
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
*/

#define CN_FORMAT           0100000u            /* printer output character/format (0/1) code */

static const BITSET_NAME prt_control_names [] = {       /* Printer control word names */
    "\1format\0character"                               /*   bit 15 */
    };

static const BITSET_FORMAT prt_control_format =         /* names, offset, direction, alternates, bar */
    { FMT_INIT (prt_control_names, 15, msb_first, has_alt, no_bar) };


/* Printer status word.

      15  14  13  12  11  10   9   8   7   6   5   4   3   2   1   0
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | L | R | V | U | -   -   -   -   -   -   -   -   -   -   - | D |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
*/

#define ST_ONLINE           0100000u            /* online */
#define ST_NOT_READY        0040000u            /* not ready */
#define ST_VFU_9            0020000u            /* VFU channel 9 */
#define ST_VFU_12           0010000u            /* VFU channel 12 */
#define ST_DEMAND           0000001u            /* demand */

#define ST_POWEROFF         (ST_ONLINE | ST_NOT_READY | ST_DEMAND)

static const BITSET_NAME prt_status_names [] = {        /* Printer status word names */
    "\1online\0offline",                                /*   bit 15 */
    "\1not ready\0ready",                               /*   bit 14 */
    "VFU 9",                                            /*   bit 13 */
    "VFU 12",                                           /*   bit 12 */
    NULL,                                               /*   bit 11 */
    NULL,                                               /*   bit 10 */
    NULL,                                               /*   bit  9 */
    NULL,                                               /*   bit  8 */
    NULL,                                               /*   bit  7 */
    NULL,                                               /*   bit  6 */
    NULL,                                               /*   bit  5 */
    NULL,                                               /*   bit  4 */
    NULL,                                               /*   bit  3 */
    NULL,                                               /*   bit  2 */
    NULL,                                               /*   bit  1 */
    "idle"                                              /*   bit  0 */
    };

static const BITSET_FORMAT prt_status_format =          /* names, offset, direction, alternates, bar */
    { FMT_INIT (prt_status_names, 0, msb_first, has_alt, no_bar) };


/* Interface state */

typedef struct {
    HP_WORD      output_word;                   /* output data register */
    HP_WORD      status_word;                   /* input data register */
    FLIP_FLOP    command;                       /* command flip-flop */
    FLIP_FLOP    control;                       /* control flip-flop */
    FLIP_FLOP    flag;                          /* flag flip-flop */
    FLIP_FLOP    flag_buffer;                   /* flag buffer flip-flop */
    t_bool       strobe;                        /* STROBE signal to the printer */
    t_bool       demand;                        /* DEMAND signal from the printer */
    } CARD_STATE;

static CARD_STATE lpt;                          /* per-card state */


/* Printer state */

static t_bool paper_fault     = TRUE;           /* TRUE if the printer is out of paper */
static t_bool tape_fault      = FALSE;          /* TRUE if there is no punch in a commanded VFU channel */
static t_bool offline_pending = FALSE;          /* TRUE if an offline request is waiting for the printer to finish */
static uint32 overprint_char  = DEL;            /* character to use if overprinted */
static uint32 current_line    = 1;              /* current form line */
static uint32 buffer_index    = 0;              /* current index into the print buffer */

static uint32 form_length;                      /* form length in lines */
static uint8  buffer [BUFFER_SIZE];             /* character and paper advance buffer */
static uint16 VFU [VFU_SIZE];                   /* vertical format unit tape */
static char   vfu_title [LINE_SIZE];            /* descriptive title of the tape currently in the VFU */

static int32  punched_char   = 'O';             /* character to display if VFU channel is punched */
static int32  unpunched_char = '.';             /* character to display if VFU channel is not punched */

static const DELAY_PROPS *dlyptr = &fast_times; /* pointer to the event delay times to use */


/* Interface local SCP support routines */

static INTERFACE lp_interface;

static t_stat lp_service (UNIT   *uptr);
static t_stat lp_reset   (DEVICE *dptr);


/* Interface local utility routines */

static void activate_unit (UNIT *uptr);
static void report_error  (FILE *stream);


/* Printer local SCP support routines */

static t_stat lp_attach         (UNIT *uptr, CONST char *cptr);
static t_stat lp_detach         (UNIT *uptr);

static t_stat lp_set_mode       (UNIT *uptr, int32 value, CONST char *cptr, void *desc);
static t_stat lp_set_model      (UNIT *uptr, int32 value, CONST char *cptr, void *desc);
static t_stat lp_set_on_offline (UNIT *uptr, int32 value, CONST char *cptr, void *desc);
static t_stat lp_set_vfu        (UNIT *uptr, int32 value, CONST char *cptr, void *desc);
static t_stat lp_show_mode      (FILE *st,   UNIT *uptr,  int32 value,      CONST void *desc);
static t_stat lp_show_vfu       (FILE *st,   UNIT *uptr,  int32 value,      CONST void *desc);


/* Printer local utility routines */

static void   lp_master_clear (UNIT *uptr);
static t_bool lp_set_alarm    (UNIT *uptr);
static t_bool lp_set_locality (UNIT *uptr, LOCALITY printer_state);
static t_stat lp_load_vfu     (UNIT *uptr, FILE *vf);
static int32  lp_read_line    (FILE *vf,   char *line, uint32 size);


/* Interface SCP data structures */


/* Unit list */

#define UNIT_FLAGS          (UNIT_ATTABLE | UNIT_SEQ | UNIT_EXPAND | UNIT_OFFLINE)

static UNIT lpt_unit [] = {
    { UDATA (&lp_service, UNIT_FLAGS | UNIT_2607, 0), 0 }
    };


/* Device information block */

static DIB lpt_dib = {
    &lp_interface,                              /* the device's I/O interface function pointer */
    LPT,                                        /* the device's select code (02-77) */
    0,                                          /* the card index */
    "12845B Line Printer Interface",            /* the card description */
    NULL                                        /* the ROM description */
    };


/* Register list */

static REG lpt_reg [] = {
/*    Macro   Name    Location                  Radix     Width      Offset      Depth            Flags       */
/*    ------  ------  ------------------------  -----  ------------  ------  -------------  ----------------- */
    { FLDATA (DEVCTL, lpt.control,                                     0)                                     },
    { FLDATA (DEVFLG, lpt.flag,                                        0)                                     },
    { FLDATA (DEVFBF, lpt.flag_buffer,                                 0)                                     },

    { FLDATA (STROBE, lpt.strobe,                                      0)                                     },
    { FLDATA (DEMAND, lpt.demand,                                      0)                                     },

    { ORDATA (OUTPUT, lpt.output_word,                     16),                             PV_RZRO | REG_X   },
    { ORDATA (STATUS, lpt.status_word,                     16),                             PV_RZRO           },

    { FLDATA (PFAULT, paper_fault,                                     0)                                     },
    { FLDATA (TFAULT, tape_fault,                                      0)                                     },
    { FLDATA (OLPEND, offline_pending,                                 0)                                     },

    { DRDATA (PRLINE, current_line,                         8),                             PV_LEFT           },
    { DRDATA (BUFIDX, buffer_index,                         8),                             PV_LEFT           },
    { BRDATA (PRTBUF, buffer,                     8,        8,               BUFFER_SIZE),  PV_RZRO | REG_A   },
    { ORDATA (OVPCHR, overprint_char,                       8),                             PV_RZRO | REG_A   },

    { DRDATA (FORMLN, form_length,                          8),                             PV_LEFT | REG_RO  },
    { BRDATA (TITLE,  vfu_title,                  8,        8,                LINE_SIZE),             REG_HRO },
    { BRDATA (VFU,    VFU,                        2,    VFU_WIDTH,            VFU_SIZE),    PV_RZRO | REG_RO  },
    { ORDATA (PUNCHR, punched_char,                         8),                             PV_RZRO | REG_A   },
    { ORDATA (UNPCHR, unpunched_char,                       8),                             PV_RZRO | REG_A   },

    { DRDATA (BTIME,  fast_times.buffer_load,              24),                             PV_LEFT | REG_NZ  },
    { DRDATA (PTIME,  fast_times.print,                    24),                             PV_LEFT | REG_NZ  },
    { DRDATA (STIME,  fast_times.advance,                  24),                             PV_LEFT | REG_NZ  },
    { DRDATA (POS,    lpt_unit [0].pos,                 T_ADDR_W),                          PV_LEFT           },
    { DRDATA (UWAIT,  lpt_unit [0].wait,                   32),                             PV_LEFT | REG_HRO },

      DIB_REGS (lpt_dib),

    { NULL }
    };


/* Modifier list */

typedef enum {                                  /* Device modes */
    Fast_Time,                                  /*   use optimized timing */
    Real_Time,                                  /*   use realistic timing */
    Printer,                                    /*   use the printer VFU tape */
    Diagnostic                                  /*   use the diagnostic VFU tape */
    } DEVICE_MODES;

static MTAB lpt_mod [] = {
/*    Mask Value     Match Value    Print String       Match String  Validation          Display  Descriptor */
/*    -------------  -------------  -----------------  ------------  ------------------  -------  ---------- */
    { UNIT_MODEL,    UNIT_2607,     "2607",            "2607",       &lp_set_model,      NULL,    NULL       },
    { UNIT_MODEL,    UNIT_2613,     "2613",            "2613",       &lp_set_model,      NULL,    NULL       },
    { UNIT_MODEL,    UNIT_2617,     "2617",            "2617",       &lp_set_model,      NULL,    NULL       },
    { UNIT_MODEL,    UNIT_2618,     "2618",            "2618",       &lp_set_model,      NULL,    NULL       },

    { UNIT_OFFLINE,  UNIT_OFFLINE,  "offline",         "OFFLINE",    &lp_set_on_offline, NULL,    NULL       },
    { UNIT_OFFLINE,  0,             "online",          "ONLINE",     &lp_set_on_offline, NULL,    NULL,      },

    { UNIT_EXPAND,   UNIT_EXPAND,   "expanded output", "EXPAND",     NULL,               NULL,    NULL       },
    { UNIT_EXPAND,   0,             "compact output",  "COMPACT",    NULL,               NULL,    NULL,      },

    { UNIT_POWEROFF, UNIT_POWEROFF, "power off",       "POWEROFF",   NULL,               NULL,    NULL       },
    { UNIT_POWEROFF, 0,             "power on",        "POWERON",    NULL,               NULL,    NULL,      },

/*    Entry Flags          Value        Print String  Match String  Validation    Display        Descriptor        */
/*    -------------------  -----------  ------------  ------------  ------------  -------------  ----------------- */
    { MTAB_XDV,            Fast_Time,   NULL,         "FASTTIME",   &lp_set_mode, NULL,          NULL              },
    { MTAB_XDV,            Real_Time,   NULL,         "REALTIME",   &lp_set_mode, NULL,          NULL              },
    { MTAB_XDV,            Printer,     NULL,         "PRINTER",    &lp_set_mode, NULL,          NULL              },
    { MTAB_XDV,            Diagnostic,  NULL,         "DIAGNOSTIC", &lp_set_mode, NULL,          NULL              },
    { MTAB_XDV,            0,           "MODES",      NULL,         NULL,         &lp_show_mode, NULL              },

    { MTAB_XDV,             1u,         "SC",         "SC",         &hp_set_dib,  &hp_show_dib,  (void *) &lpt_dib },
    { MTAB_XDV | MTAB_NMO, ~1u,         "DEVNO",      "DEVNO",      &hp_set_dib,  &hp_show_dib,  (void *) &lpt_dib },

    { MTAB_XDV | MTAB_NMO, 1,           "VFU",        NULL,         NULL,         &lp_show_vfu,  NULL              },
    { MTAB_XDV | MTAB_NC,  0,           "VFU",        "VFU",        &lp_set_vfu,  &lp_show_vfu,  NULL              },

    { 0 }
    };


/* Debugging trace list */

static DEBTAB lpt_deb [] = {
    { "CMD",   TRACE_CMD   },                   /* trace interface or controller commands */
    { "CSRW",  TRACE_CSRW  },                   /* trace interface control, status, read, and write actions */
    { "SERV",  TRACE_SERV  },                   /* trace unit service scheduling calls and entries */
    { "XFER",  TRACE_XFER  },                   /* trace data transmissions */
    { "IOBUS", TRACE_IOBUS },                   /* trace I/O bus signals and data words received and returned */
    { NULL,    0           }
    };


/* Device descriptor */

DEVICE lpt_dev = {
    "LPT",                                      /* device name */
    lpt_unit,                                   /* unit array */
    lpt_reg,                                    /* register array */
    lpt_mod,                                    /* modifier array */
    1,                                          /* number of units */
    10,                                         /* address radix */
    32,                                         /* address width = 4 GB */
    1,                                          /* address increment */
    8,                                          /* data radix */
    8,                                          /* data width */
    NULL,                                       /* examine routine */
    NULL,                                       /* deposit routine */
    &lp_reset,                                  /* reset routine */
    NULL,                                       /* boot routine */
    &lp_attach,                                 /* attach routine */
    &lp_detach,                                 /* detach routine */
    &lpt_dib,                                   /* device information block pointer */
    DEV_DISABLE | DEV_DEBUG,                    /* device flags */
    0,                                          /* debug control flags */
    lpt_deb,                                    /* debug flag name array */
    NULL,                                       /* memory size change routine */
    NULL                                        /* logical device name */
    };



/* Interface local SCP support routines */



/* Line printer interface.

   The line printer interface is installed on the I/O bus and receives I/O
   commands from the CPU and DMA/DCPC channels.  In simulation, the asserted
   signals on the bus are represented as bits in the inbound signal_set.  Each
   signal is processed sequentially in numerical order.


   Implementation notes:

    1. The simulation implements the 12845B Series 1506 interface that inhibits
       STROBE if the printer is offline.

    2. In hardware, an STC signal sets both the Control flip-flop and the
       Information Ready flip-flop.  The latter asserts STROBE to the printer,
       which denies DEMAND in response.  The trailing edge of DEMAND clears the
       Information Ready flip-flop and denies STROBE.  In simulation, STC sets
       the Control flip-flop and calls the device service routine to begin the
       print operation.  This is equivalent to STROBE asserting, then DEMAND
       denying, then STROBE denying.  The Information Ready flip-flop is not
       simulated, as it effectively sets and clears within one CPU instruction.
*/

static SIGNALS_VALUE lp_interface (const DIB *dibptr, INBOUND_SET inbound_signals, HP_WORD inbound_value)
{
INBOUND_SIGNAL signal;
INBOUND_SET    working_set = inbound_signals;
SIGNALS_VALUE  outbound    = { ioNONE, 0 };
t_bool         irq_enabled = FALSE;

while (working_set) {                                   /* while signals remain */
    signal = IONEXTSIG (working_set);                   /*   isolate the next signal */

    switch (signal) {                                   /* dispatch the I/O signal */

        case ioCLF:                                     /* Clear Flag flip-flop */
            lpt.flag_buffer = CLEAR;                    /* reset the flag buffer */
            lpt.flag        = CLEAR;                    /*   and flag flip-flops */
            break;


        case ioSTF:                                     /* Set Flag flip-flop */
            lpt.flag_buffer = SET;                      /* set the flag buffer flip-flop */
            break;


        case ioENF:                                     /* Enable Flag */
            if (lpt.flag_buffer == SET)                 /* if the flag buffer flip-flop is set */
                lpt.flag = SET;                         /*   then set the flag flip-flop */
            break;


        case ioSFC:                                     /* Skip if Flag is Clear */
            if (lpt.flag == CLEAR)                      /* if the flag flip-flop is clear */
                outbound.signals |= ioSKF;              /*   then assert the Skip on Flag signal */
            break;


        case ioSFS:                                     /* Skip if Flag is Set */
            if (lpt.flag == SET)                        /* if the flag flip-flop is set */
                outbound.signals |= ioSKF;              /*   then assert the Skip on Flag signal */
            break;


        case ioIOI:
            if (lpt_unit [0].flags & UNIT_POWEROFF)     /* if the printer power is off */
                outbound.value = ST_POWEROFF;           /*   then return the power-off status */

            else if (lpt.demand)                                /* otherwise if DEMAND is asserted */
                outbound.value = lpt.status_word | ST_DEMAND;   /*   then reflect it in the status word */

            else                                        /* otherwise return */
                outbound.value = lpt.status_word;       /*   the (static) status */

            tprintf (lpt_dev, TRACE_CSRW, "Status is %s\n",
                     fmt_bitset (outbound.value, prt_status_format));
            break;


        case ioIOO:
            tprintf (lpt_dev, TRACE_CSRW, "Control is %s | %s\n",
                     fmt_bitset (inbound_value, prt_control_format),
                     fmt_char (inbound_value));

            lpt.output_word = inbound_value;             /* save the character or format word */
            break;


        case ioPOPIO:                                   /* Power-On Preset to I/O */
            lpt.flag_buffer = SET;                      /* set the flag buffer flip-flop */
            lpt.output_word = 0;                        /*   and clear the output register */
            break;


        case ioCRS:                                     /* Control Reset */
            lp_master_clear (lpt_unit);                 /* CRS asserts MASTER CLEAR to the printer */

            lpt.control = CLEAR;                        /* clear the control flip-flop */
            lpt.command = CLEAR;                        /*   and the command flip-flop */
            lpt.strobe = FALSE;                         /*     and deny STROBE to the printer */

            sim_cancel (lpt_unit);                      /* cancel any operation in progress */
            break;


        case ioCLC:                                     /* Clear Control flip-flop */
            lpt.control = CLEAR;                        /* clear the control flip-flop */
            lpt.command = CLEAR;                        /*   and the command flip-flop */
            lpt.strobe = FALSE;                         /*     and deny STROBE to the printer */
            break;


        case ioSTC:                                     /* Set Control flip-flop */
            lpt.control = SET;                          /* set the control flip-flop */
            lpt.command = SET;                          /*   and the command flip-flop */

            if (lpt.status_word & ST_ONLINE) {          /* if the printer is online */
                lpt.strobe = TRUE;                      /*   then assert STROBE to the printer */

                lpt_unit [0].wait = 0;                  /* set for immediate service entry */
                activate_unit (lpt_unit);               /*   and call the service routine */
                }
            break;


        case ioSIR:                                     /* Set Interrupt Request */
            if (lpt.control & lpt.flag)                 /* if the control and flag flip-flops are set */
                outbound.signals |= cnVALID;            /*   then deny PRL */
            else                                        /* otherwise */
                outbound.signals |= cnPRL | cnVALID;    /*   conditionally assert PRL */

            if (lpt.control & lpt.flag & lpt.flag_buffer)   /* if the control, flag, and flag buffer flip-flops are set */
                outbound.signals |= cnIRQ | cnVALID;        /*   then conditionally assert IRQ */

            if (lpt.flag == SET)                        /* if the flag flip-flop is set */
                outbound.signals |= ioSRQ;              /*   then assert SRQ */
            break;


        case ioIAK:                                     /* Interrupt Acknowledge */
            lpt.flag_buffer = CLEAR;                     /* clear the flag buffer flip-flop */
            break;


        case ioIEN:                                     /* Interrupt Enable */
            irq_enabled = TRUE;                         /* permit IRQ to be asserted */
            break;


        case ioPRH:                                         /* Priority High */
            if (irq_enabled && outbound.signals & cnIRQ)    /* if IRQ is enabled and conditionally asserted */
                outbound.signals |= ioIRQ | ioFLG;          /*   then assert IRQ and FLG */

            if (!irq_enabled || outbound.signals & cnPRL)   /* if IRQ is disabled or PRL is conditionally asserted */
                outbound.signals |= ioPRL;                  /*   then assert it unconditionally */
            break;


        case ioEDT:                                     /* not used by this interface */
        case ioPON:                                     /* not used by this interface */
            break;
        }

    IOCLEARSIG (working_set, signal);                   /* remove the current signal from the set */
    }                                                   /*   and continue until all signals are processed */

return outbound;                                        /* return the outbound signals and value */
}


/* Service the printer.

   The printer transfer service is called to output a character to the printer
   buffer or to output a format command that causes the buffered line to be
   printed with specified paper movement.

   In hardware, the interface places a character or format code on the lower
   seven data out lines and asserts STROBE to the printer.  The printer responds
   by denying DEMAND.  The interface then denies STROBE and waits for the
   printer to reassert DEMAND to indicate that the buffer load or print
   operation is complete.

   In simulation, this service routine is called twice for each transfer.  It is
   called immediately when STROBE is asserted with DEMAND asserted and then
   after a variable delay with STROBE denied.  In response to the former call,
   the routine denies DEMAND and STROBE, loads the character buffer or prints
   the buffered line, and then sets up an event delay corresponding to the
   operation performed.  In response to the latter call, the routine asserts
   DEMAND and then clears the event delay time, so that the routine will be
   reentered immediately when STROBE is asserted again.  DEMAND assertion also
   sets the flag buffer flip-flop.

   If a SET LPT OFFLINE or DETACH LPT command simulating an out-of-paper
   condition is given, the printer will not honor the command immediately if
   data exists in the print buffer or the printer is currently printing a line.
   In this case, the action is deferred until this service routine is entered to
   complete a print operation.  At that point, the printer goes offline with
   DEMAND denied.  This leaves the transfer handshake incomplete.  When the
   printer is placed back online, DEMAND is asserted to conclude the handshake.

   Control word bit 15 determines whether the code on the data out lines is
   interpreted as a character (0) or a format command (1).  If there is room in
   the print buffer, the character is loaded.  If not, then depending on the
   model, the printer either discards the character or automatically prints the
   buffer contents, advances the paper one line, and stores the new character in
   the empty buffer.  If a control character is sent but the printer cannot
   print it, a space is loaded in its place.

   A format command causes the current buffer to be printed, and then the paper
   is advanced by a prescribed amount.  Two output modes are provided: compact
   and expanded.

   In compact mode, a printed line is terminated by a CR LF pair, but subsequent
   line spacing is performed by LFs alone.  Also, a top-of-form request will
   emit a FF character instead of the number of LFs required to reach the top of
   the next form, and overprinting is handled by emitting a lone CR at the end
   of the line.  This mode is used when the printer output file will be sent to
   a physical printer connected to the host.

   In expanded mode, paper advance is handled solely by emitting CR LF pairs.
   Overprinting is handled by merging characters in the buffer.  This mode is
   used where the printer output file will be saved or manipulated as a text
   file.

   The format commands recognized by the printer are:

     0 x x 0 0 0 0 -- slew 0 lines (suppress spacing) after printing
          ...
     0 x x 1 1 1 1 -- slew 15 lines after printing

   and:

     1 x x 0 0 0 0 -- slew to VFU channel 1 after printing
          ...
     1 x x 1 0 1 1 -- slew to VFU channel 12 after printing

   A command to slew to a VFU channel that is not punched or to a VFU channel
   other than those defined for the printer will cause a tape fault, and the
   printer will go offline; setting the printer back online will clear the
   fault.  Otherwise, LFs or a FF (compact mode) or CR LF pairs (expanded mode)
   will be added to the buffer to advance the paper the required number of
   lines.

   Not all printers can overprint.  A request to suppress spacing on a printer
   that cannot (e.g., the HP 2607) is treated as a request for single spacing.

   If the stream write fails, an error message is displayed on the simulation
   console, a printer alarm condition is set (which takes the printer offline),
   and SCPE_IOERR is returned to cause a simulation stop to give the user the
   opportunity to fix the problem.  Simulation may then be resumed, either with
   the printer set back online if the problem is fixed, or with the printer
   remaining offline if the problem is uncorrectable.


   Implementation notes:

    1. When a paper-out condition is detected, the 2607 printer goes offline
       only when the next top-of-form is reached.  The 2613/17/18 printers go
       offline as soon as the current line completes.

    2. A printer going offline, either via a user action or by a paper-out
       condition, does not complete the last handshake.  Instead, DEMAND remains
       denied, so the interface flag never sets.  Typically, this causes an I/O
       timeout in the OS driver, which issues a CLC and downs the device.  When
       the printer is set back online, DEMAND asserts, which sets the flag.
       However, the CLC has cleared the control flip-flop, so no interrupt is
       generated.  When the device is upped, the incomplete request is reissued;
       this results in either a duplicate printed line or a duplicate paper
       movement.

    3. Because attached files are opened in binary mode, newline translation
       (i.e., from LF to CR LF) is not performed by the host system.  Therefore,
       we write explicit CR LF pairs to end lines, even in compact mode, as
       required for fidelity to HP peripherals.  If bare LFs are used by the
       host system, the printer output file must be postprocessed to remove the
       CRs.

    4. Overprinting in expanded mode is simulated by merging the lines in the
       buffer.  A format command to suppress spacing resets the buffer index but
       saves the previous buffer length as a "high water mark" that will be
       extended if the overlaying line is longer.  This process may be repeated
       as many times as desired before issuing a format command that prints the
       buffer.

       When overlaying characters, if a space overlays a printing character, a
       printing character overlays a space, or a printing character overlays
       itself, then the printing character is retained.  Otherwise, an
       "overprint character" (which defaults to DEL, but can be changed by the
       user) replaces the character in the buffer.

    5. Printers that support 12-channel VFUs treat the VFU format command as
       modulo 16.  Printers that support 8-channel VFUs treat the command as
       modulo 8.

    6. As a convenience to the user, the printer output stream is flushed when a
       TOF operation is performed.  This permits inspection of the output file
       from the SCP command prompt while output is ongoing.

    7. The user may examine the TFAULT and PFAULT registers to determine why the
       printer went offline.

    8. Explicit tests for lowercase and control characters are much faster and
       are used rather than calls to "islower" and "iscntrl", which must
       consider the current locale.

    9. This routine will not be entered with the printer unit unattached.  The
       printer must be online (and therefore attached) before an STC will
       schedule the service, and "lp_detach" will not detach the unit if it is
       busy unless it is forced (and, in the latter case, "lp_detach" will
       cancel the service event).  So protection against "uptr->fileref" being
       NULL is not required.
*/

static t_stat lp_service (UNIT *uptr)
{
const PRINTER_TYPE model = GET_MODEL (uptr->flags);                 /* the printer model number */
const t_bool       printing = ((lpt.output_word & CN_FORMAT) != 0); /* TRUE if a print command was received */
static uint32      overprint_index = 0;                             /* the "high-water" mark while overprinting */
uint8              data_byte, format_byte;
uint16             channel;
uint32             line_count, slew_count;

tprintf (lpt_dev, TRACE_SERV, "Printer service entered\n");

if (uptr->flags & UNIT_POWEROFF)                        /* if the printer power is off */
    return SCPE_OK;                                     /*   then no action is taken */

else if (lpt.strobe == FALSE) {                         /* otherwise if STROBE has denied */
    if (printing) {                                     /*   then if printing occurred */
        buffer_index = 0;                               /*     then clear the buffer */

        if (paper_fault) {                              /* if an out-of-paper condition is pending */
            if (print_props [model].fault_at_eol        /*   then if the printer faults at the end of a line */
              || current_line == 1)                     /*     or the printer is at the top of the form */
                return lp_detach (uptr);                /*       then complete it now with the printer offline */
            }

        else if (tape_fault) {                          /* otherwise if a referenced VFU channel was not punched */
            tprintf (lpt_dev, TRACE_CMD, "Commanded VFU channel is not punched\n");
            lp_set_alarm (uptr);                        /*   then set an alarm condition that takes the printer offline */
            return SCPE_OK;
            }

        else if (offline_pending) {                     /* otherwise if a non-alarm offline request is pending */
            lp_set_locality (uptr, Offline);            /*   then take the printer offline now */
            return SCPE_OK;
            }
        }

    lpt.demand = TRUE;                                  /* assert DEMAND to complete the handshake */
    uptr->wait = 0;                                     /*   and request immediate entry when STROBE next asserts */

    lpt.flag_buffer = SET;                              /* set the flag buffer */
    io_assert (&lpt_dev, ioa_ENF);                      /*   and flag flip-flops on DEMAND assertion */
    }

else if (lpt.demand == TRUE) {                          /* otherwise if STROBE has asserted while DEMAND is asserted */
    lpt.demand = FALSE;                                 /*   then deny DEMAND */
    lpt.strobe = FALSE;                                 /*     which resets STROBE */

    data_byte = (uint8) (lpt.output_word & DATA_MASK);  /* only the lower 7 bits are sent to the printer */

    if (printing == FALSE) {                            /* if loading the print buffer */
        if (data_byte > '_'                             /*   then if the character is "lowercase" */
          && print_props [model].char_set == 64)        /*     but the printer doesn't support it */
            data_byte = data_byte - 040;                /*       then shift it to "uppercase" */

        if ((data_byte < ' ' || data_byte == DEL)       /* if the character is a control character */
          && print_props [model].char_set != 128)       /*   but the printer doesn't support it */
            data_byte = ' ';                            /*     then substitute a space */

        if (buffer_index < print_props [model].line_length) {   /* if there is room in the buffer */
            if (overprint_index == 0                            /*   then if not overprinting */
              || buffer_index >= overprint_index                /*     or past the current buffer limit */
              || buffer [buffer_index] == ' ')                  /*     or overprinting a blank */
                buffer [buffer_index] = data_byte;              /*       then store the character */

            else if (data_byte != ' '                           /* otherwise if we're overprinting a character */
              && data_byte != buffer [buffer_index])            /*   with a different character */
                buffer [buffer_index] = (uint8) overprint_char; /*     then substitute the overprint character */

            buffer_index++;                             /* increment the buffer index */

            uptr->wait = dlyptr->buffer_load;           /* schedule the buffer load delay */

            tprintf (lpt_dev, TRACE_XFER, "Character %s sent to printer\n",
                     fmt_char (data_byte));
            }

        else if (print_props [model].autoprints) {      /* otherwise if a buffer overflow auto-prints */
            tprintf (lpt_dev, TRACE_CMD, "Buffer overflow printed %u characters on line %u\n",
                     buffer_index, current_line);

            buffer [buffer_index++] = CR;               /*   then tie off */
            buffer [buffer_index++] = LF;               /*     the current buffer */

            fwrite (buffer, sizeof buffer [0],          /* write the buffer to the printer file */
                    buffer_index, uptr->fileref);

            uptr->pos = (t_addr) ftell (uptr->fileref); /* update the file position */

            current_line = current_line + 1;            /* move the paper one line */

            if (current_line > form_length)             /* if the current line is beyond the end of the form */
                current_line = 1;                       /*   then reset to the top of the next form */

            tprintf (lpt_dev, TRACE_CMD, "Printer advanced 1 line to line %u\n",
                     current_line);

            overprint_index = 0;                        /* clear any accumulated overprint index */

            buffer [0] = data_byte;                     /* store the character */
            buffer_index = 1;                           /*   in the empty buffer */

            uptr->wait = dlyptr->print                  /* schedule the print delay */
                           + dlyptr->advance            /*   plus the paper advance delay */
                           + dlyptr->buffer_load;       /*   plus the buffer load delay */

            tprintf (lpt_dev, TRACE_XFER, "Character %s sent to printer\n",
                     fmt_char (data_byte));
            }

        else {                                          /* otherwise the printer discards excess characters */
            uptr->wait = dlyptr->buffer_load;           /*   so just schedule the load delay */

            tprintf (lpt_dev, TRACE_CMD, "Buffer overflow discards character %s\n",
                     fmt_char (data_byte));
            }
        }

    else {                                              /* otherwise this is a print format command */
        tprintf (lpt_dev, TRACE_XFER, "Format code %03o sent to printer\n",
                 data_byte);

        format_byte = data_byte & FORMAT_MASK;          /* format commands ignore bits 5-4 */

        if (overprint_index > buffer_index)             /* if the overprinted line is longer than the current line */
            buffer_index = overprint_index;             /*   then extend the current buffer index */

        if (buffer_index > 0 && format_byte != FORMAT_SUPPRESS) /* if printing will occur, then trace it */
            tprintf (lpt_dev, TRACE_CMD, "Printed %u character%s on line %u\n",
                     buffer_index, (buffer_index == 1 ? "" : "s"), current_line);

        if (format_byte == FORMAT_SUPPRESS              /* if this is a "suppress space" request */
          && print_props [model].overprints) {          /*   and the printer is capable of overprinting */
            slew_count = 0;                             /*     then do not slew after printing */

            if (uptr->flags & UNIT_EXPAND) {            /* if the printer is in expanded mode */
                if (buffer_index > overprint_index)     /*   then if the current length exceeds the overprinted length */
                    overprint_index = buffer_index;     /*     then extend the overprinted line */

                buffer_index = 0;                       /* reset the buffer index to overprint the next line */
                }

            else                                        /* otherwise the printer is in compact mode */
                buffer [buffer_index++] = CR;           /*   so overprint by emitting a CR without a LF */

            tprintf (lpt_dev, TRACE_CMD, "Printer commanded to suppress spacing on line %u\n",
                     current_line);
            }

        else if (format_byte & FORMAT_VFU) {            /* otherwise if this is a VFU command */
            if (print_props [model].vfu_channels == 8)  /*   then if it's an 8-channel VFU */
                format_byte &= FORMAT_VFU_8_MASK;       /*     then only three bits are significant */

            channel = VFU_CHANNEL_1 >> (format_byte - FORMAT_VFU_BIAS - 1); /* set the requested channel */

            tprintf (lpt_dev, TRACE_CMD, "Printer commanded to slew to VFU channel %u from line %u\n",
                     format_byte - FORMAT_VFU_BIAS, current_line);

            tape_fault = (channel & VFU [0]) == 0;      /* a tape fault occurs if there is no punch in this channel */

            slew_count = 0;                             /* initialize the slew counter */

            do {                                        /* the VFU always slews at least one line */
                slew_count++;                           /* increment the slew counter */
                current_line++;                         /*   and the line counter */

                if (current_line > form_length)         /* if the current line is beyond the end of the form */
                    current_line = 1;                   /*   then reset to the top of the next form */
                }
            while (!tape_fault && (channel & VFU [current_line]) == 0); /* continue until a punch is seen */
            }

        else {                                          /* otherwise it must be a slew command */
            slew_count = format_byte;                   /*   so get the number of lines to slew */

            if (format_byte == FORMAT_SUPPRESS)         /* if the printer cannot overprint */
                slew_count = 1;                         /*   then the paper advances after printing */

            tprintf (lpt_dev, TRACE_CMD, "Printer commanded to slew %u line%s from line %u\n",
                     slew_count, (slew_count == 1 ? "" : "s"), current_line);

            current_line = current_line + slew_count;   /* move the current line */

            if (current_line > form_length)                 /* if the current line is beyond the end of the form */
                current_line = current_line - form_length;  /*   then it extends onto the next form */
            }

        if (format_byte == FORMAT_VFU_CHAN_1            /* if a TOF was requested */
          && !(uptr->flags & UNIT_EXPAND)               /*   and the printer is in compact mode */
          && slew_count > 1) {                          /*     and more than one line is needed to reach the TOF */
            if (buffer_index > 0) {                     /*       then if the buffer not empty */
                buffer [buffer_index++] = CR;           /*         then print */
                buffer [buffer_index++] = LF;           /*           the current line */
                }

            buffer [buffer_index++] = FF;               /* emit a FF to move to the TOF */
            }

        else if (slew_count > 0) {                      /* otherwise a slew is needed */
            buffer [buffer_index++] = CR;               /*   then emit a CR LF */
            buffer [buffer_index++] = LF;               /*     to print the current line */

            line_count = slew_count;                    /* get the number of lines to slew */

            while (--line_count > 0) {                  /* while movement is needed */
                if (uptr->flags & UNIT_EXPAND)          /* if the printer is in expanded mode */
                    buffer [buffer_index++] = CR;       /*   then blank lines are CR LF pairs */

                buffer [buffer_index++] = LF;           /* otherwise just LFs are used */
                }
            }

        if (buffer_index > 0) {                         /* if the buffer is not empty */
            fwrite (buffer, sizeof buffer [0],          /*   then write it to the printer file */
                    buffer_index, uptr->fileref);

            overprint_index = 0;                        /* clear any existing overprint index */
            }

        lpt.status_word &= ~(ST_VFU_9 | ST_VFU_12);     /* assume no punches for channels 9 and 12 */

        if (print_props [model].vfu_channels > 8) {     /* if the printer VFU has more than 8 channels */
            if (VFU [current_line] & VFU_CHANNEL_9)     /*   then if channel 9 is punched for this line */
                lpt.status_word |= ST_VFU_9;            /*     then report it in the device status */

            if (VFU [current_line] & VFU_CHANNEL_12)    /* if channel 12 is punched for this line */
                lpt.status_word |= ST_VFU_12;           /*   then report it in the device status */
            }

        if (format_byte == FORMAT_VFU_CHAN_1)           /* if a TOF request was performed */
            fflush (uptr->fileref);                     /*   then flush the file buffer for inspection */

        uptr->wait = dlyptr->print                      /* schedule the print delay */
                       + slew_count * dlyptr->advance;  /*   plus the paper advance delay */

        uptr->pos = (t_addr) ftell (uptr->fileref);     /* update the file position */

        if (slew_count > 0)
            tprintf (lpt_dev, TRACE_CMD, "Printer advanced %u line%s to line %u\n",
                     slew_count, (slew_count == 1 ? "" : "s"), current_line);
        }

    if (ferror (uptr->fileref)) {                       /* if a host file system error occurred */
        report_error (uptr->fileref);                   /*   then report the error to the console */

        lp_set_alarm (uptr);                            /* set an alarm condition */
        return SCPE_IOERR;                              /*   and stop the simulator */
        }

    else                                                /* otherwise the write succeeded */
        activate_unit (lpt_unit);                       /*   so schedule the DEMAND reassertion */
    }

return SCPE_OK;                                         /* return event service success */
}


/* Device reset routine.

   This routine is called for a RESET or RESET LPT command.  It is the
   simulation equivalent of the POPIO signal, which is asserted by the front
   panel PRESET switch.
*/

static t_stat lp_reset (DEVICE *dptr)
{
UNIT * const uptr = dptr->units;                        /* a pointer to the printer unit */

io_assert (dptr, ioa_POPIO);                            /* PRESET the device (asserts MASTER CLEAR) */

if (sim_switches & SWMASK ('P')) {                      /* if this is a power-on reset */
    fast_times.buffer_load = LP_BUFFER_LOAD;            /*   then reset the per-character transfer time, */
    fast_times.print       = LP_PRINT;                  /*     the print and advance-one-line time, */
    fast_times.advance     = LP_ADVANCE;                /*       and the slew additional lines time */

    return lp_load_vfu (uptr, NULL);                    /* load the standard VFU tape */
    }

else                                                    /* otherwise this is a normal reset */
    return SCPE_OK;                                     /*   so just return */
}



/* Interface local utility routines */


/* Activate a unit.

   The specified unit is added to the event queue with the delay specified by
   the unit wait field.


   Implementation notes:

    1. This routine may be called with wait = 0, which will expire immediately
       and enter the service routine with the next sim_process_event call.
       Activation is required in this case to allow the service routine to
       return an error code to stop the simulation.  If the service routine was
       called directly, any returned error would be lost.
*/

static void activate_unit (UNIT *uptr)
{
tprintf (lpt_dev, TRACE_SERV, "Unit delay %u service scheduled\n",
         uptr->wait);

sim_activate (uptr, uptr->wait);                        /* activate the unit with the specified wait */

return;
}


/* Report a stream I/O error to the console.

   If a stream I/O error has been detected, this routine will print an error
   message to the simulation console and clear the stream's error indicator.
*/

static void report_error (FILE *stream)
{
cprintf ("%s simulator printer I/O error: %s\n",        /* report the error to the console */
         sim_name, strerror (errno));

clearerr (stream);                                      /* clear the error */

return;
}



/* Printer local SCP support routines */



/* Attach the printer image file.

   The specified file is attached to the indicated unit.  This is the simulation
   equivalent of loading paper into the printer and pressing the ONLINE button.
   The transition from offline to online typically generates an interrupt.

   A new image file may be requested by giving the "-N" switch to the ATTACH
   command.  If an existing file is specified with "-N", it will be cleared; if
   specified without "-N", printer output will be appended to the end of the
   existing file content.  In all cases, the paper is positioned at the top of
   the form.


   Implementation notes:

    1. If we are called during a RESTORE command to reattach a file previously
       attached when the simulation was SAVEd, the device status and file
       position are not altered.  This is because SIMH 4.x restores the register
       contents before reattaching, while 3.x reattaches before restoring the
       registers.

    2. The pointer to the appropriate event delay times is set in case we are
       being called during a RESTORE command (the assignment is redundant
       otherwise).
*/

static t_stat lp_attach (UNIT *uptr, CONST char *cptr)
{
t_stat result;

result = hp_attach (uptr, cptr);                        /* attach the specified printer image file for appending */

if (result == SCPE_OK                                   /* if the attach was successful */
  && (sim_switches & SIM_SW_REST) == 0) {               /*   and we are not being called during a RESTORE command */
    lpt.status_word &= ~ST_NOT_READY;                   /*     then clear not-ready status */
    current_line = 1;                                   /*       and reset the line counter to the top of the form */

    tprintf (lpt_dev, TRACE_CMD, "Printer paper loaded\n");

    lp_set_locality (uptr, Online);                     /* set the printer online */
    }

if (lpt_dev.flags & DEV_REALTIME)                       /* if the printer is in real-time mode */
    dlyptr = &real_times [GET_MODEL (uptr->flags)];     /*   then point at the times for the current model */
else                                                    /* otherwise */
    dlyptr = &fast_times;                               /*   point at the fast times */

return result;                                          /* return the result of the attach */
}


/* Detach the printer image file.

   The specified file is detached from the indicated unit.  This is the
   simulation equivalent of running out of paper or unloading the paper from the
   printer.  The out-of-paper condition cause a paper fault alarm, and the
   printer goes offline.

   When the printer runs out of paper, it will not go offline until characters
   present in the buffer are printed and paper motion stops.  In addition, the
   2607 printer waits until the paper reaches the top-of-form position before
   going offline.

   In simulation, entering a DETACH LPT command while the printer is busy will
   defer the file detach until print operations reach the top of the next form
   (2607) or until the current print operation completes (2613/17/18).  An
   immediate detach may be forced by adding the -F switch to the DETACH command.
   This simulates physically removing the paper from the printer and succeeds
   regardless of the current printer state.


   Implementation notes:

    1. During simulator shutdown, this routine is called for the printer unit.
       The printer must be detached, even if a detach has been deferred, to
       ensure that the file is closed properly.  We do this in response to a
       detach request with the SIM_SW_SHUT switch present.

    2. The DETACH ALL command will fail if any detach routine returns a status
       other than SCPE_OK.  Because a deferred detach is not fatal, we must
       return SCPE_OK, but we still want to print a warning to the user.

    3. Because the 2607 only paper faults at TOF, we must explicitly set the
       offline_pending flag, as lp_set_alarm may not have been called.
*/

static t_stat lp_detach (UNIT *uptr)
{
const PRINTER_TYPE model = GET_MODEL (uptr->flags);     /* the printer model number */

if ((uptr->flags & UNIT_ATT) == 0)                      /* if the unit is not currently attached */
    return SCPE_UNATT;                                  /*   then report it */

else {
    if (sim_switches & (SWMASK ('F') | SIM_SW_SHUT)) {  /* if this is a forced detach or shut down request */
        current_line = 1;                               /*   then reset the printer to TOF to enable the detach */
        sim_cancel (uptr);                              /*     and terminate */
        lpt.strobe = FALSE;                             /*       any print action in progress */
        }

    if ((print_props [model].fault_at_eol               /* otherwise if the printer faults at the end of any line */
      || current_line == 1)                             /*   or the printer is at the top of the form */
      && lp_set_alarm (uptr)) {                         /*   and a paper alarm is accepted */
        paper_fault = TRUE;                             /*     then set the out-of-paper condition */

        tprintf (lpt_dev, TRACE_CMD, "Printer is out of paper\n");

        return detach_unit (uptr);                      /* detach the unit */
        }

    else {                                              /* otherwise the alarm was rejected at this time */
        paper_fault = TRUE;                             /*   so set the out-of-paper condition now */
        offline_pending = TRUE;                         /*     but defer the detach */

        tprintf (lpt_dev, TRACE_CMD, "Paper out request deferred until print completes\n");

        cprintf ("%s\n", sim_error_text (SCPE_INCOMP)); /* report that the actual detach must be deferred */
        return SCPE_OK;                                 /*   until the buffer has been printed */
        }
    }
}


/* Set the device modes.

   This validation routine is entered with the "value" parameter set to one of
   the DEVICE_MODES values.  The device flag implied by the value is set or
   cleared.  The unit, character, and descriptor pointers are not used.
*/

static t_stat lp_set_mode (UNIT *uptr, int32 value, CONST char *cptr, void *desc)
{
switch ((DEVICE_MODES) value) {                         /* dispatch the mode to set */

    case Fast_Time:                                     /* entering optimized timing mode */
        lpt_dev.flags &= ~DEV_REALTIME;                 /*   so clear the real-time flag */
        dlyptr = &fast_times;                           /*     and point at the fast times */
        break;


    case Real_Time:                                     /* entering realistic timing mode */
        lpt_dev.flags |= DEV_REALTIME;                  /*   so set the real-time flag */
        dlyptr = &real_times [GET_MODEL (uptr->flags)]; /*     and point at the times for the current model */
        break;


    case Printer:                                       /* entering printer mode */
        lpt_dev.flags &= ~DEV_DIAG;                     /*   so clear the diagnostic flag */

        lp_load_vfu (uptr, NULL);                       /* reload the standard VFU tape */
        break;


    case Diagnostic:                                    /* entering diagnostic mode */
        lpt_dev.flags |= DEV_DIAG;                      /*   so set the diagnostic flag */

        lp_load_vfu (uptr, NULL);                       /* load the diagnostic VFU tape */
        break;
    }

return SCPE_OK;                                         /* mode changes always succeed */
}


/* Set the printer model.

   This validation routine is called to set the model of the printer.  The
   "value" parameter is one of the UNIT_26nn constants that indicates the new
   model.  Validation isn't necessary, except to detect a model change and alter
   the real-time delays accordingly.
*/

static t_stat lp_set_model (UNIT *uptr, int32 value, CONST char *cptr, void *desc)
{
if (lpt_dev.flags & DEV_REALTIME)                       /* if the printer is in real-time mode */
    dlyptr = &real_times [GET_MODEL (value)];           /*   then use the times for the new model */

return SCPE_OK;                                         /* allow the reassignment to proceed */
}


/* Set the printer online or offline.

   This validation routine is called to set the printer online or offline.  The
   "value" parameter is UNIT_OFFLINE if the printer is going offline and is zero
   if the printer is going online.  This simulates pressing the ON/OFFLINE
   button on the printer.  The unit must be attached (i.e., paper must be
   loaded), before the printer may be set online or offline.

   If the printer is being taken offline, the buffer is checked to see if any
   characters are present.  If they are, or if the printer unit is currently
   scheduled (i.e., executing a print operation), the offline request is
   deferred until printing completes, and the routine returns "Command not
   complete" status to inform the user.  Otherwise, the unit is set offline and
   DEMAND is denied to indicate that the printer is not ready.

   If the printer is being put online and paper is present, the unit is set
   online, and any paper or tape fault present is cleared.  DEMAND is asserted
   to indicate that the printer is ready to accept characters.  If the flag
   flip-flop is clear, then DEMAND assertion sets the flag buffer flip-flop.

   As a special case, a detach (out-of-paper condition) or offline request that
   has been deferred until printing completes may be cancelled by setting the
   printer online.  No other action is taken, because the printer has never
   transitioned to the offline state.


   Implementation notes:

    1. Although a deferred offline request is not fatal, we return SCPE_INCOMP
       to prevent "set_cmd" from setting the UNIT_OFFLINE bit in the unit flags
       before the printer actually goes offline.
*/

static t_stat lp_set_on_offline (UNIT *uptr, int32 value, CONST char *cptr, void *desc)
{
if ((uptr->flags & UNIT_ATT) == 0)                      /* if the printer is detached */
    return SCPE_UNATT;                                  /*   then it can't be set online or offline */

else if (value == UNIT_ONLINE)                          /* otherwise if this is an online request */
    if (paper_fault && offline_pending) {               /*   then if an out-of-paper condition is deferred */
        paper_fault = FALSE;                            /*     then cancel the request */
        offline_pending = FALSE;                        /*       leaving the file attached */
        }

    else                                                /*   otherwise it's a normal online request */
        lp_set_locality (uptr, Online);                 /*     so set the printer online */

else if (lp_set_locality (uptr, Offline) == FALSE) {    /* otherwise if it cannot be set offline now */
    tprintf (lpt_dev, TRACE_CMD, "Offline request deferred until print completes\n");
    return SCPE_INCOMP;                                 /*   then let the user know */
    }

return SCPE_OK;                                         /* return operation success */
}


/* Set the VFU tape.

   This validation routine is entered to set up the VFU on the printer.  It is
   invoked by one of two commands:

     SET LPT VFU
     SET LPT VFU=<filename>

   The first form loads the standard 66-line tape into the VFU.  The second form
   loads the VFU with the tape image specified by the filename.  The format of
   the tape image is described in the comments for the "lp_load_vfu" routine.

   On entry, "uptr" points at the printer unit, "cptr" points to the first
   character after the "VFU" keyword, and the "value" and "desc" parameters are
   unused.  If "cptr" is NULL, then the first command form was given, and the
   "lp_load_vfu" routine is called with a NULL file stream pointer to indicate
   that the standard VFU tape should be used.  Otherwise, the second command
   form was given, and "cptr" points to the supplied filename.  The file is
   opened, and the "lp_load_vfu" routine is called with the stream pointer to
   load the VFU tape image contained therein.
*/

static t_stat lp_set_vfu (UNIT *uptr, int32 value, CONST char *cptr, void *desc)
{
FILE   *vfu_stream;
t_stat result;

if (cptr == NULL)                                       /* if a VFU reset is requested */
    result = lp_load_vfu (uptr, NULL);                  /*   then reload the standard VFU tape */

else if (*cptr == '\0')                                 /* otherwise if the filename was omitted */
    return SCPE_MISVAL;                                 /*   then report the missing argument */

else {                                                  /* otherwise the filename was specified */
    vfu_stream = fopen (cptr, "r");                     /*   so attempt to open it */

    if (vfu_stream == NULL)                             /* if the open failed */
        return SCPE_OPENERR;                            /*   then report the error */

    result = lp_load_vfu (uptr, vfu_stream);            /* load the VFU tape from the file */

    fclose (vfu_stream);                                /* close the file */
    }

return result;                                          /* return the result of the load */
}


/* Show the device modes.

   This display routine is called to show the device modes for the printer.  The
   output stream is passed in the "st" parameter, and the other parameters are
   ignored.  The timing mode and connection mode are printed.
*/

static t_stat lp_show_mode (FILE *st, UNIT *uptr, int32 value, CONST void *desc)
{
fprintf (st, "%s timing, %s mode",                      /* print the timing and connection modes */
         (lpt_dev.flags & DEV_REALTIME ? "realistic" : "fast"),
         (lpt_dev.flags & DEV_DIAG ? "diagnostic" : "printer"));

return SCPE_OK;
}


/* Show the VFU tape.

   This display routine is called to show the content of the tape currently
   loaded in the printer's VFU.  The "value" parameter indicates how the routine
   was called.  It is 0 if a SHOW LPT command was given and 1 if a SHOW LPT VFU
   command was issued.  For the former, only the VFU title is displayed.  The
   latter displays the VFU title, followed by a header labelling each of the
   channel columns  and then one line for each line of the form consisting of
   punch and no-punch characters, according to the VFU definition.

   The output stream is passed in the "st" parameter, and the "uptr" and "desc"
   parameters are ignored.


   Implementation notes:

    1. Setting the string precision for the header lines trims them to the
       appropriate number of channels.
*/

static t_stat lp_show_vfu (FILE *st, UNIT *uptr, int32 value, CONST void *desc)
{
static const char header_1 [] = " Ch 1 Ch 2 Ch 3 Ch 4 Ch 5 Ch 6 Ch 7 Ch 8 Ch 9 Ch10 Ch11 Ch12";
static const char header_2 [] = " ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----";

const PRINTER_TYPE model = GET_MODEL (uptr->flags);             /* the printer model number */
const uint32 channel_count = print_props [model].vfu_channels;  /* the count of VFU channels */
uint32 chan, line, current_channel;

if (value == 0)                                         /* if we're called for a summary display */
    fputs (vfu_title, st);                              /*   then output only the VFU title */

else {                                                      /* otherwise the full VFU definition is requested */
    fprintf (st, "\n%s tape is loaded.\n\n", vfu_title);    /*   so start by displaying the VFU title */

    fprintf (st, "Line %.*s\n", channel_count * 5, header_1);   /* display the */
    fprintf (st, "---- %.*s\n", channel_count * 5, header_2);   /*   channel headers */

    for (line = 1; line <= form_length; line++) {       /* loop through the VFU array */
        fprintf (st, "%3d ", line);                     /* display the current form line number */

        current_channel = VFU_CHANNEL_1;                /* start with channel 1 */

        for (chan = 1; chan <= channel_count; chan++) { /* loop through the defined channels */
            fputs ("    ", st);                         /* add some space */

            if (VFU [line] & current_channel)           /* if the current channel is punched for this line */
                fputc (punched_char, st);               /*   then display a punched location */
            else                                        /* otherwise */
                fputc (unpunched_char, st);             /*   display an unpunched location */

            current_channel = current_channel >> 1;     /* move to the next channel */
            }

        fputc ('\n', st);                               /* end the line */
        }
    }

return SCPE_OK;
}



/* Printer local utility routines */


/* Clear the printer.

   This routine simulates the assertion of the MASTER CLEAR signal to the
   printer.  In response, the printer clears the line buffer and aborts a paper
   advance in progress (except on the 2613).  It does not abort a print cycle in
   progress.


   Implementation notes:

    1. In simulation, printing and slewing occurs when the event service routine
       is entered on DEMAND assertion.  Service is then scheduled for a delay
       corresponding to the print action.  On service entry with DEMAND denied,
       the buffer is cleared and DEMAND is asserted.  Therefore, cancelling the
       event service does not abort a print cycle or paper advance, as they have
       already occurred when the event service is queued.
*/

static void lp_master_clear (UNIT *uptr)
{
const PRINTER_TYPE model = GET_MODEL (uptr->flags);     /* the printer model number */

sim_cancel (uptr);                                      /* deactivate the unit */

lpt.strobe = FALSE;                                     /* deny STROBE to the printer */
lpt.demand = FALSE;                                     /*   and assume that DEMAND is also denied */

buffer_index = 0;                                       /* clear the buffer without printing */

offline_pending = FALSE;                                /* cancel any pending offline request */

tape_fault  = FALSE;                                    /* clear any tape fault */
paper_fault = ! (uptr->flags & UNIT_ATT);               /* set a paper fault if the printer is out of paper */

if (sim_is_active (uptr)) {                             /* if the event service is scheduled */
    sim_cancel (uptr);                                  /*   then cancel it */
    lp_service (uptr);                                  /*     and call the service routine now to clean up */
    }

if (! (uptr->flags & UNIT_POWEROFF)) {                  /* if the printer power is on */
    lpt.status_word = 0;                                /*   then prepare to set the printer status */

    if (paper_fault && print_props [model].not_ready)   /* if paper is out and the printer reports it separately */
        lpt.status_word |= ST_NOT_READY;                /*   then add not-ready status */

    if (! (uptr->flags & UNIT_OFFLINE)) {               /* if the printer is online */
        lpt.demand = TRUE;                              /*   then DEMAND is asserted */

        lpt.status_word |= ST_ONLINE;                   /* set ONLINE status */
        }
    }

tprintf (lpt_dev, TRACE_CMD, "Master clear asserted to the printer\n");

return;
}


/* Set an alarm condition.

   This routine is called when an alarm condition exists.  An alarm occurs when
   paper is out (paper fault) or a VFU command addresses a channel that does not
   contain a punch (tape fault).  In response, the printer goes offline and,
   for all models except the 2607, becomes not-ready.

   On entry, the routine attempts to set the printer offline.  If this succeeds,
   the printer is set not-ready.  If it fails (for reasons explained in the
   comments for the "lp_set_on_offline" routine), it will be set offline and
   not-ready when printing completes.
*/

static t_bool lp_set_alarm (UNIT *uptr)
{
const PRINTER_TYPE model = GET_MODEL (uptr->flags);     /* the printer model number */

if (lp_set_locality (uptr, Offline)) {                  /* if the printer went offline */
    if (print_props [model].not_ready)                  /*   then if the printer reports ready status separately */
        lpt.status_word |= ST_NOT_READY;                /*     then add not-ready status */

    return TRUE;                                        /* return completion success */
    }

else                                                    /* otherwise the offline request is pending */
    return FALSE;                                       /*   so return deferral status */
}


/* Set the printer locality.

   This routine is called to set the printer online or offline and returns TRUE
   if the request succeeded or FALSE if it was deferred.  An online request
   always succeeds, so it is up to the caller to ensure that going online is
   permissible (e.g., that paper is loaded into the printer).  An offline
   request succeeds only if the printer is idle.  If characters are present in
   the print buffer, or if the printer is printing or slewing, then the request
   is deferred until the current line is complete.

   If the printer goes offline with an operation in progress, DEMAND will remain
   denied when the operation completes, so the interface flag will not set.
   DEMAND reasserts when the printer is set back online; if the flag flip-flop
   is clear, then the flag buffer flip-flop is set.
*/

static t_bool lp_set_locality (UNIT *uptr, LOCALITY printer_state)
{
if (printer_state == Offline) {                         /* if the printer is going offline */
    if (buffer_index == 0                               /*   then if the buffer is empty */
      && sim_is_active (uptr) == FALSE) {               /*     and the printer is idle */
        uptr->flags |= UNIT_OFFLINE;                    /*       then set the printer offline now */

        lpt.status_word &= ~ST_ONLINE;                  /* update the printer status */

        lpt.demand = FALSE;                             /* DEMAND denies while the printer is not ready */
        }

    else {                                              /*   otherwise the request must wait */
        offline_pending = TRUE;                         /*     until the line is printed */
        return FALSE;                                   /* report that the command is not complete */
        }
    }

else {                                                  /* otherwise the printer is going online */
    uptr->flags &= ~UNIT_OFFLINE;                       /*   so clear the unit flag */

    paper_fault = FALSE;                                /* clear any paper fault */
    tape_fault  = FALSE;                                /*   and any tape fault */

    lpt.status_word = lpt.status_word & ~ST_NOT_READY | ST_ONLINE;  /* update the printer status */

    lpt.demand = TRUE;                                  /* DEMAND asserts when the printer is online */

    if (lpt.flag == CLEAR) {                            /* if the flag flip-flop is clear */
        lpt.flag_buffer = SET;                          /* then DEMAND assertion sets the flag buffer */
        io_assert (&lpt_dev, ioa_ENF);                  /*   and flag flip-flops */
        }
    }

tprintf (lpt_dev, TRACE_CMD, "Printer set %s\n",
         (printer_state == Offline ? "offline" : "online"));

offline_pending = FALSE;                                /* the operation completed */
return TRUE;                                            /*   successfully */
}


/* Load the VFU.

   The printer VFU is loaded either with a custom tape image stored in the file
   associated with the stream "vf" or with the standard 66-line tape if the
   stream is NULL.  The "uptr" parameter points to the printer unit.

   The standard VFU tape (02607-80024 for the 8-channel HP 2607 and 02613-80001
   for the 12-channel HP 2613, 2617, and 2618) defines the channels as:

     Chan  Description
     ----  --------------
       1   Top of form
       2   Bottom of form
       3   Single space
       4   Double space
       5   Triple space
       6   Half page
       7   Quarter page
       8   Sixth page
       9   Bottom of form

   ...with channels 10-12 uncommitted.

   If the DIAGNOSTIC mode is set, the standard tape is replaced with the
   diagnostic tape (02613-80002 for the 2613 and 2617 or 02618-80002 for the
   2618).  This tape extends the standard VFU tape above as follows:

     Chan  Description
     ----  ----------------------------------------
      10   Line before the bottom of form (line 59)
      11   Line before the top of form (line 66)
      12   Top of form (line 1)

   The 2607 diagnostic mode uses the standard 8-channel tape.

   A custom tape file starts with a VFU definition line and then contains one
   channel-definition line for each line of the form.  The number of lines
   establishes the form length.  Channel 1 must be dedicated to the top-of-form,
   but the other channels may be defined as desired.

   A semicolon appearing anywhere on a line begins a comment, and the semicolon
   and all following characters are ignored.  Zero-length lines, including lines
   beginning with a semicolon, are ignored.

   Note that a line containing one or more blanks is not a zero-length line, so,
   for example, the line " ; a comment starting in column 2" is not ignored.

   The first (non-ignored) line in the file is a VFU definition line of this
   exact form:

     VFU=<punch characters>,<no-punch character>{,<title>}

   ...where:

     Parameter           Description
     ------------------  -------------------------------------------------------
     punch characters    a string of one or more characters used interchangeably
                         to represent a punched location

     no-punch character  a single character representing a non-punched location

     title               an optional descriptive string printed by the SHOW LPT
                         VFU command ("Custom VFU" is used by default)

   If the "VFU" line is missing or not of the correct form, then "Format error"
   status is returned, and the VFU tape is not changed.

   The remaining (non-ignored) lines define the channels punched for each line
   of the printed form.  The line format consists of a sequence of punch,
   no-punch, and "other" characters in channel order.  Each punch or no-punch
   character defines a channel state, starting with channel 1 and proceeding
   left-to-right until all channels for the VFU are defined; if the line
   terminates before all channels are defined, the remaining channels are set to
   the no-punch state.  Any "other" characters (i.e., neither a punch character
   nor a no-punch character) are ignored and may be used freely to delineate the
   tape channels.

   Examples using the standard 66-line tape definition for an 8-channel VFU:


     ; the VFU definition                 |   VFU=1234578,  ; no-punch is a ' '
     VFU=1,0,a binary tape image          |
                                          |   1             ; top of form
     ; the channel definitions            |     345         ; form line 1
                                          |     3           ; form line 2
     10111111   ; top of form             |     34          ; form line 3
     00100000   ; single space            |     3 5         ; form line 4
     0011       ; channels 5-8 no-punch   |     34          ; form line 5
                                          |
     -------------------------------------+-------------------------------------
                                          |
     VFU=X,-,blanks are "others"          |   VFU=TO,.,brackets are "others"
                                          |   ; 1   2   3   4   5   6   7   8
     X  -  X  X  X  X  X  X   ; line 1    |   ;--- --- --- --- --- --- --- ---
     -  -  X  -  -  -  -  -   ; line 2    |    [T]  .  [O] [O] [O] [O] [O] [O]
     -  -  X  X  -  -  -  -   ; line 3    |     .   .  [O]  .   .   .   .   .
                                          |     .   .  [O] [O]  .   .   .   .


   On entry, the "vf" parameter determines whether the standard tape or a custom
   tape is to be loaded.  If "vf" is NULL, a standard 66-line tape is generated
   and stored in the tape buffer.  Otherwise, a custom tape file is read,
   parsed, and assembled VFU entries are stored in the tape buffer.  After
   generation or a successful tape load, the buffer is copied to the VFU array,
   the form length is set, the current line is reset to the top-of-form, and the
   state of VFU channels 9 and 12 are set into the device status.


   Implementation notes:

    1. VFU array entries 1-n correspond to form print lines 1-n.  Entry 0 is the
       logical OR of all of the other entries and is used during VFU format
       command processing to determine if a punch is present somewhere in a
       given channel.

    2. In DIAGNOSTIC mode, the 2613/17/18 diagnostic tape is generated
       unconditionally, as the 2607 will use only the eight standard channels.
*/

static t_stat lp_load_vfu (UNIT *uptr, FILE *vf)
{
const PRINTER_TYPE model = GET_MODEL (uptr->flags);     /* the printer model number */
uint32             line, channel;
int32              len;
char               buffer [LINE_SIZE], punch [LINE_SIZE], no_punch;
char               *bptr, *tptr;
uint16             tape [VFU_SIZE] = { 0 };

if (vf == NULL) {                                       /* if the standard VFU is requested */
    tape [ 1] = VFU_CHANNEL_1;                          /*   then punch channel 1 for the top of form */
    tape [60] = VFU_CHANNEL_2 | VFU_CHANNEL_9;          /*     and channels 2 and 9 for the bottom of form */

    for (line = 1; line <= 60; line++) {                            /* load each of the 60 printable lines */
        tape [line] |= VFU_CHANNEL_3                                /* punch channel 3 for single space */
                         | (line %  2 ==  1 ? VFU_CHANNEL_4 : 0)    /* punch channel 4 for double space */
                         | (line %  3 ==  1 ? VFU_CHANNEL_5 : 0)    /* punch channel 5 for triple space */
                         | (line % 30 ==  1 ? VFU_CHANNEL_6 : 0)    /* punch channel 6 for next half page */
                         | (line % 15 ==  1 ? VFU_CHANNEL_7 : 0)    /* punch channel 7 for next quarter page */
                         | (line % 10 ==  1 ? VFU_CHANNEL_8 : 0);   /* punch channel 8 for next sixth page */

        tape [0] |= tape [line];                        /* accumulate the channel punches */
        }

    if (lpt_dev.flags & DEV_DIAG) {                     /* if the device is in diagnostic mode */
        strcpy (vfu_title, "Diagnostic VFU");           /*   then install the diagnostic VFU tape */

        tape [59] |= VFU_CHANNEL_10;                    /* punch channel 10 on line 59 */
        tape [66] |= VFU_CHANNEL_11;                    /* punch channel 11 on line 66 */
        tape [ 1] |= VFU_CHANNEL_12;                    /* punch channel 12 on line  1 */

        tape [0] |= VFU_CHANNEL_10                      /* add the additional channel punches */
                      | VFU_CHANNEL_11                  /*   to the accumulator */
                      | VFU_CHANNEL_12;
        }

    else                                                /* otherwise the device is in standard mode */
        strcpy (vfu_title, "Standard VFU");             /*   so install the standard VFU tape */

    form_length = 66;                                   /* set the form length */
    }

else {                                                  /* otherwise load a custom VFU from the file */
    len = lp_read_line (vf, buffer, sizeof buffer);     /* read the first line */

    if (len <= 0                                            /* if there isn't one */
      || strncmp (buffer, "VFU=", strlen ("VFU=")) != 0) {  /*   or it's not a VFU definition statement */
        cputs ("Missing VFU definition line\n");            /*     then complain to the console */
        return SCPE_FMT;                                    /*       and fail with a format error */
        }

    bptr = buffer + strlen ("VFU=");                    /* point at the first control argument */

    tptr = strtok (bptr, ",");                          /* parse the punch token */

    if (tptr == NULL) {                                             /* if it's missing */
        cputs ("Missing punch field in the VFU control line\n");    /*   then complain to the console */
        return SCPE_FMT;                                            /*     and fail with a format error */
        }

    strcpy (punch, tptr);                               /* save the set of punch characters */

    tptr = strtok (NULL, ",");                          /* parse the no-punch token */

    if (tptr == NULL){                                              /* if it's missing */
        cputs ("Missing no-punch field in the VFU control line\n"); /*   then complain to the console */
        return SCPE_FMT;                                            /*     and fail with a format error */
        }

    no_punch = *tptr;                                   /* save the no-punch character */

    tptr = strtok (NULL, ",");                          /* parse the optional title */

    if (tptr != NULL)                                   /* if it's present */
        strcpy (vfu_title, tptr);                       /*   then save the user's title */
    else                                                /* otherwise */
        strcpy (vfu_title, "Custom VFU");               /*   use a generic title */


    for (line = 1; line <= VFU_MAX; line++) {           /* load up to the maximum VFU tape length */
        len = lp_read_line (vf, buffer, sizeof buffer); /* read a tape definition line */

        if (len <= 0)                                   /* if at the EOF or an error occurred */
            break;                                      /*   then the load is complete */

        tptr = buffer;                                  /* point at the first character of the line */

        channel = VFU_CHANNEL_1;                        /* set the channel 1 indicator */

        while (channel != 0 && *tptr != '\0') {         /* loop until the channel or definition is exhausted */
            if (strchr (punch, *tptr) != NULL) {        /* if the character is in the punch set */
                tape [line] |= channel;                 /*   then punch the current channel */
                channel = channel >> 1;                 /*     and move to the next one */
                }

            else if (*tptr == no_punch)                 /* otherwise if the character is the no-punch character */
                channel = channel >> 1;                 /*   then move to the next channel */

            tptr++;                                     /* otherwise the character is neither, so ignore it */
            }

        tape [0] |= tape [line];                        /* accumulate the channel punches */
        }


    if ((tape [1] & VFU_CHANNEL_1) == 0) {                  /* if there is no channel 1 punch in the first line */
        cputs ("Missing punch in channel 1 of line 1\n");   /*   then complain to the console */
        return SCPE_FMT;                                    /*     and fail with a format error */
        }

    form_length = line - 1;                             /* set the form length */
    }

memcpy (VFU, tape, sizeof VFU);                         /* copy the tape buffer to the VFU array */

current_line = 1;                                       /* reset the line counter to the top of the form */

lpt.status_word &= ~(ST_VFU_9 | ST_VFU_12);             /* assume no punches for channels 9 and 12 */

if (print_props [model].vfu_channels > 8) {             /* if the printer VFU has more than 8 channels */
    if (VFU [1] & VFU_CHANNEL_9)                        /*   then if channel 9 is punched for this line */
        lpt.status_word |= ST_VFU_9;                    /*     then report it in the device status */

    if (VFU [1] & VFU_CHANNEL_12)                       /* if channel 12 is punched for this line */
        lpt.status_word |= ST_VFU_12;                   /*   then report it in the device status */
    }

return SCPE_OK;                                         /* the VFU was successfully loaded */
}


/* Read a line from the VFU file.

   This routine reads a line from the VFU tape image file designated by the file
   stream parameter "vf", stores the data in the string buffer pointed to by
   "line" and whose size is given by "size", and returns the length of that
   string.  Comments are stripped from lines that are read, and the routine
   continues to read until a non-zero-length line is found.  If the end of the
   file was reached, the return value is 0.  If a file error occurred, the
   return value is -1.


   Implementation notes:

    1. The routine assumes that the file was opened in text mode, so that
       automatic CRLF-to-LF conversion is done if needed.  This simplifies
       the end-of-line removal.
*/

static int32 lp_read_line (FILE *vf, char *line, uint32 size)
{
char  *result;
int32 len = 0;

while (len == 0) {
    result = fgets (line, size, vf);                    /* get the next line from the file */

    if (result == NULL)                                 /* if an error occurred */
        if (feof (vf))                                  /*   then if the end of file was seen */
            return 0;                                   /*     then return an EOF indication */

        else {                                          /*   otherwise */
            report_error (vf);                          /*     report the error to the console */
            return -1;                                  /*       and return an error indication */
            }

    len = strlen (line);                                /* get the current line length */

    if (len > 0 && line [len - 1] == '\n')              /* if the last character is a newline */
        line [--len] = '\0';                            /*   then remove it and decrease the length */

    result = strchr (line, ';');                        /* search for a comment indicator */

    if (result != NULL) {                               /* if one was found */
        *result = '\0';                                 /*   then truncate the line at that point */
        len = (int32) (result - line);                  /*     and recalculate the line length */
        }
    }

return len;
}
