/* hp3000_lp.c: HP 3000 30209A Line Printer Interface simulator

   Copyright (c) 2016-2021, 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
   AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
   ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
   WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

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

   LP           HP 30209A Line Printer Interface

   01-Apr-21    JDB     Changed VFU type from "uint16" to "uint32"
   25-Mar-21    JDB     Converted ftell to position increment for pipes
   09-Dec-19    JDB     Replaced debugging macros with tracing macros
   01-Oct-19    JDB     DETACH -F now flushes the print buffer if not empty
   27-Dec-18    JDB     Revised fall through comments to comply with gcc 7
   07-Sep-17    JDB     Changed PCHR and UPCHR registers to PUNCHR and UNPCHR
                        Changed PRTBUF, OVPCHR, PUNCHR, and UNPCHR to REG_A
   05-Sep-17    JDB     Changed REG_A (permit any symbolic override) to REG_X
   20-Jul-17    JDB     Added a forced detach option (-F switch) to "lp_detach"
   24-Jun-17    JDB     Added "report_error", fixed "lp_set_model" times bug
   22-Jun-17    JDB     Moved deferred offline/detach cancel to SET ONLINE
   26-Apr-17    JDB     Fixed "lp_service" return for VFU channel not punched
                        Restricted auto-print on buffer full to the 2607
                        Paper fault is now delayed until the TOF for the 2607
                        Changed "activate_unit" to schedule zero-length delays
   12-Sep-16    JDB     Changed DIB register macro usage from SRDATA to DIB_REG
   03-Sep-16    JDB     Added power-fail detection
   08-Jul-16    JDB     Added REG entry to save the transfer unit wait field
                        Extended "lp_show_vfu" to show the VFU channel definitions
   01-Jul-16    JDB     First release version
   27-Apr-16    JDB     Passes the On-Line HP Line Printers Verification (D466A)
   19-Apr-16    JDB     Passes the universal interface diagnostic (D435A)
   24-Mar-16    JDB     Created

   References:
     - 30051A Universal Interface (Differential) Maintenance Manual
         (30051-90001, May 1976)
     - Installation and Service Manual for Line Printer Subsystems
         (30209-90006, May 1976)
     - Line Printer Operating and Programming Manual
         (30209-90008, June 1976)
     - HP 3000 Series III Engineering Diagrams Set
         (30000-90141, April 1980)


   The HP 30118A, 30127A, 30128A, and 30133A Line Printer Subsystems connect the
   2607A, 2613A, 2618A, and 2617A printers, respectively, to the HP 3000.  Each
   subsystem consists of a 30209A Line Printer Controller, employing a 30051A
   Universal Interface (Differential) and 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.  These subsystems employ the Multiplexer
   Channel to achieve a 360 KB/second transfer rate from the CPU.

   This module simulates three hardware devices:

     - the HP 30051A Universal Interface (Differential)
     - the HP 30049C Diagnostic Hardware Assembly
     - the HP 2607A/13A/17A/18A line printer and 30209-60004 printer cable

   Available with either differential or TTL I/O logic levels, the Universal
   Interface (UI) provides a 16-bit bidirectional parallel connection between a
   device and the HP 3000 system.  Both direct and programmed I/O via the
   Multiplexer Channel are supported, word or byte transfers may be selected,
   and byte packing and unpacking is available.  In addition to the 16-bit data
   path, a five-bit control word is supplied to the device, and an eight-bit
   status word is returned.  Flexible configuration of interface operation is
   provided via ten jumpers, and eight different interrupt sources are
   available.  The Universal Interface is also used to connect the paper tape
   reader, punch, and card reader to the HP 3000.

   The Diagnostic Hardware Assembly (DHA) connects to the UI device connectors
   and provides a programmable loopback and configuration capability.  Five LEDs
   continuously display the device control word, and test points are provided to
   monitor the state of the 16-bit data path, the ten programmable jumper
   settings, and the Device Command, Device Flag, and Device End signals.
   Enabling the diagnostic mode simulates the installation of the DHA in place
   of the printer device cable.

   The interface 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
   MPE software driver requirements.


   In hardware, the ten UI configuration jumpers perform these functions:

     Jumper  Interpretation when removed      Interpretation when installed
     ------  -------------------------------  ------------------------------
       W1    SR set by PCONTSTB               SR set by Device Status bit 11

       W2    Flag asserts on leading edge     Flag asserts on trailing edge

       W3    Command uses response mode       Command uses pulse mode

       W4    inhibit IRQ on Device Status     enable IRQ on Device Status
               bit 8 leading edge               bit 8 leading edge

       W5    DATA IN latched on Flag          DATA IN always transparent

       W6    Flag denies on trailing edge     Flag denies on leading edge

       W7    normal byte-mode write transfer  test byte-mode write transfer

       W8    inhibit IRQ on Device Status     enable IRQ on Device Status
               bit 9 leading edge               bit 9 leading edge

       W9    inhibit IRQ on Device Status     enable IRQ on Device Status
               bit 10 trailing edge             bit 10 trailing edge

       W10   DEV CMD polarity is normal       DEV CMD polarity is inverted

   The line printer cable is wired with this configuration:

     Interface Connection                      Printer Connection
     ----------------------------------------  ------------------
     Data Out bit 15                           DATA 1
     Data Out bit 14                           DATA 2
     Data Out bit 13                           DATA 3
     Data Out bit 12                           DATA 4
     Data Out bit 11                           DATA 5
     Data Out bit 10                           DATA 6
     Data Out bit  9                           DATA 7
     Device Command                            STROBE
     Device Flag                               ~DEMAND
     Control Word bit 10                       PAPER INSTRUCTION
     Device Status bit 9                       ONLINE
     Device Status bit 10                      ONLINE
     Device Status bit 11                      ~READY
     Device Status bit 12                      VFU CHANNEL 12
     Device Status bit 13                      VFU CHANNEL 9
     Device End                                ~ONLINE
     Set Transfer Error Flip-Flop              (no connection)
     Master Clear                              MASTER CLEAR

     Internal Connection                       Action
     ----------------------------------------  --------------------------------
     300 pF across the Write Delay One-Shot    sets 1.2 uS pulse width
     1500 pF across the Master Clear One-Shot  sets 5.1 uS pulse width
     jumper W4 shorted                         none (Status 8 is not connected)
     jumper W8 shorted                         enables IRQ when ONLINE asserts
     jumper W9 shorted                         enables IRQ when ONLINE denies

   DEMAND is wired inversely to Device Flag, so DEMAND assertion is Device Flag
   denial and vice versa.  DEMAND dropping after STROBE assertion corresponds
   with Device Flag asserting after Device Command asserts, and DEMAND asserting
   after the printer is ready corresponds to Device Flag denying.

   Similarly, ONLINE is wired inversely to Device End, so the printer going
   offline asserts Device End, and READY is wired inversely to Device Status bit
   11, so bit 11 is asserted when the printer is not ready (either powered off
   or out of paper).

   The READY and ONLINE signals indicate the current state of the printer.
   READY asserts when printer power is on, no alarm condition (paper out, tape
   format error) exits, and the VFU has been initialized.  ONLINE asserts when
   READY is asserted and the Online button is pressed.  Therefore:

     ~ONLINE * ~READY = paper out or VFU error
     ~ONLINE *  READY = paper loaded and offline
      ONLINE * ~READY = (prohibited)
      ONLINE *  READY = paper loaded and online

   The printer DEMAND signal asserts when the printer is ready for data and
   denies when it is printing or slewing.  It also denies when the printer goes
   offline.  DEMAND is cross-connected to the Device Flag differential input,
   so that DEV FLAG is the complement of DEMAND, i.e., it asserts when the
   printer is busy and denies when the printer is available.

   The normal sequence starts with DEMAND asserted (i.e., DEV FLAG denied).  The
   interface asserts STROBE (DEV CMD), the printer denies DEMAND (asserts DEV
   FLAG), the interface denies STROBE (DEV CMD), and the printer then asserts
   DEMAND (denies DEV FLAG) when the character data is accepted or the print
   operation is complete.

   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 ONLINE denies, DEMAND is inhibited, so the
   interface waits at the end of the handshake sequence for DEV FLAG to deny.
   Note that this holds off SR to the Multiplexer Channel, so the channel
   program waits.  When the printer is put back online, DEMAND asserts, so DEV
   FLAG denies, the handshake completes, SR asserts, and the interface returns
   to the idle condition to await the next command.

   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 LP OFFLINE and DETACH LP 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 LP 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 LP will indicate the new status.

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

   A RESET LP 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 LP OFFLINE or DETACH LP
   will succeed if issued subsequently.


   The Universal Interface responds to both direct I/O and programmed I/O from
   the Multiplexer Channel, as follows:

   Control Word Format (CIO and SIO Control word 2):

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | M | R | irq reset | A |  device control   | X | S | B | I | T | device
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | M | R | irq reset | A |   function    | E | X | S | B | I | T | DHA
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | M | R | irq reset | A | -   -   -   - | F | X | S | B | I | T | printer
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   Where:

     M = programmed master clear
     R = reset interrupts
     A = acquire data from device
     E = enable diagnostic hardware assembly function
     F = printer output character/format (0/1) code
     X = enable data transfer interrupt
     S = interrupt/device (0/1) status
     B = word/byte (0/1) transfer
     I = enable interrupts
     T = enable transfer timer

   IRQ Reset:

     000 = none
     001 = transfer timer and transfer error
     010 = I/O system
     011 = clear interface
     100 = data transfer completion
     101 = line ready (device status bit 8)
     110 = ready (device status bit 9)
     111 = not ready (device status bit 10)

   DHA Function:

     0000 = clear configuration registers (installs jumpers)
     0001 = remove jumper J2W2
     0010 = assert DEV END
     0011 = remove jumper J2W8
     0100 = set Transfer Error flip-flop
     0101 = remove jumper J2W4
     0110 = remove jumper J2W10
     0111 = remove jumper J2W6
     1000 = DEV FLAG follows DEV CMD or Control 6 (0/1)
     1001 = remove jumper J2W5
     1010 = assert CLEAR INTERFACE
     1011 = remove jumper J2W9
     1100 = Status 8-10 follow Control 6-8
              or master clear, power on, and power fail (0/1)
     1101 = remove jumper J2W1
     1110 = remove jumper J2W3
     1111 = remove jumper J2W7

   Bits 6-10 are the device control bits.  For the DHA, control bit 10 enables
   the function decoder.  The decoder is combinatorial and the registers are
   "ones-catching," so the function field must be set and then maintained while
   bit 10 is asserted and then denied.  For the line printer, control bit 10
   indicates whether character data (0) or format commands (1) will be output.
   Programmed control word 1 (IOCW) is not used.

   Setting control bit 15 starts (or restarts) the five-second transfer timer.
   Issuing a Reset Transfer Timer and Transfer Error Interrupts, a Master Reset,
   or a Reset Interrupts command stops the timer.  If the timer expires, a
   Transfer Timer interrupt occurs.


   Status Word Format (TIO and SIO Status):

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | S | D | I | seqct | F | 0 | 0 |  dev irq  | X | C | Y | E | T | interrupt
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | S | D | I | seqct | F | 1 | 0 |         device status         | device
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | S | D | I | seqct | F | 1 | 0 | - | L | L | N | V | U | - | - | printer
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   Where:

     S = SIO OK
     D = direct I/O OK
     I = interrupt pending
     F = device flag
     X = data transfer interrupt
     C = clear interface interrupt
     Y = I/O system interrupt
     E = transfer error interrupt
     T = transfer timer interrupt
     L = online
     N = not ready
     V = VFU channel 12
     U = VFU channel 9

   Sequence Counter:

     00 = idle
     10 = request to device issued for word or 1st byte
     11 = device operation started
     01 = request to device issued for 2nd byte

   Device Interrupt Request Bits:

      8 = device status bit 8 interrupt (not used by the printer)
      9 = device status bit 9 interrupt (printer went online)
     10 = device status bit 10 interrupt (printer went offline)

   Control word bit 12 determines whether the interrupt status word (0) or the
   device status word (1) is returned.

   A transfer error occurs when the channel asserts XFERERROR to abort a
   transfer for a parity error or memory address out of bounds.  Device status
   bits assume the logic 1 state with the inputs disconnected (e.g., power off).


   Output Data Word Format (WIO and SIO Write):

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

   The printer only uses 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.


   Implementation notes:

    1. The Clear Interface Logic (CLRIL) signal inhibits SIO OK status.  The
       Card Reader/Punch Interface version of the UI does not assert the CLRIL
       signal in response to external CLEAR INTERFACE assertion; only a transfer
       error does.  Therefore, the SIO OK signal is not inhibited while the
       clear interface interrupt is present.  For this version, the external
       Clear Interface signal sets the CLR INF flip-flop, which sets the (C) bit
       in the status register and generates an interrupt, but otherwise has no
       effect on the interface logic.

       The standard version of the UI asserts CLRIL, and therefore inhibits SIO
       OK, for both the clear interface and transfer error conditions.

    2. Because the interface uses differential interface logic, the external
       sense of a signal may be inverted by exchanging the + and - connections.
       To accommodate this in simulation, separate variables are used for the
       internal and external states.  For example, "device_command" represents
       the internal state, while "device_command_out" represents the external
       state (which may be inverted from the internal state if jumper J2W10 is
       installed).

    3. The Universal Interface supports terminating channel transfers by
       asserting DEVEND, and the line printer cable connects the ONLINE output
       inversely to the Device End input, so that it is asserted when the
       printer is offline.  However, when the printer goes offline, it holds its
       DEMAND line denied, which keeps Device Flag asserted.  This hangs the
       transfer handshake in the Device_Flag_1/2 state until the printer goes
       online again.  As the interface recognizes Device End only in the
       Device_Command_1/2 state, DEVEND will never be asserted to terminate a
       channel transfer.

    4. In hardware, a paper-out condition is noted, but the line printer does
       not go offline until the top of the next form is reached.  This ensures
       that the current page is completed first.  By contrast, a torn-paper
       condition causes the printer to go offline at the completion of the
       current line.  In simulation, a DETACH is handled as a torn-paper
       condition.

    5. Slewing in expanded mode is performed by appending CR LF pairs to the
       character buffer and then writing the combined buffer to the printer
       output file.  The size of the buffer must accommodate the largest print
       line (136 characters) plus the largest possible slew (144 lines * 2
       characters per line).
*/



#include "hp3000_defs.h"
#include "hp3000_io.h"



/* Interface program constants */

#define PULSE_TIME          uS (8)              /* device command pulse = 8 microseconds */
#define XFER_TIME           S (5)               /* transfer timeout = 5 seconds */

/* 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 (CR LF) */

#define PRINTER_JUMPERS     (W4 | W8 | W9)      /* jumpers J2W4, J2W8, and J2W9 are installed */


/* Debug flags */

#define DEB_CMD             (1u << 0)           /* trace controller commands */
#define DEB_CSRW            (1u << 1)           /* trace command initiations and completions */
#define DEB_STATE           (1u << 2)           /* trace device handshake state changes */
#define DEB_SERV            (1u << 3)           /* trace channel service scheduling calls */
#define DEB_XFER            (1u << 4)           /* trace data transmissions */
#define DEB_IOB             (1u << 5)           /* trace I/O bus signals and data words */


/* Device flags */

#define DEV_DIAG_SHIFT      (DEV_V_UF + 0)              /* Diagnostic Hardware Assembly 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
              +---+---+---+---+---+---+---+---+
              | - | - | - | O | E |   model   |
              +---+---+---+---+---+---+---+---+

   Where:

     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_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_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))


/* Unit references */

#define xfer_unit           lp_unit [0]         /* transfer handshake unit */

#define xfer_uptr           (&lp_unit [0])      /* transfer handshake unit pointer */
#define pulse_uptr          (&lp_unit [1])      /* pulse timer unit pointer */
#define timer_uptr          (&lp_unit [2])      /* transfer timer unit pointer */

static const char *const unit_name [] = {       /* unit names, indexed by unit number */
    "Transfer",
    "Pulse",
    "Watchdog"
    };


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


/* Data transfer handshake sequencer.

   The sequencer controls the handshake that transfers data between the
   interface and the device.
*/

typedef enum {
    Idle,                                       /* the device is idle */
    Device_Command_1,                           /* device command is asserted for a word or first byte */
    Device_Flag_1,                              /* device flag is asserted for a word or first byte */
    Device_Command_2,                           /* device command is asserted for the second byte */
    Device_Flag_2                               /* device flag is asserted for the second byte */
    } SEQ_STATE;

static const char *const state_name [] = {      /* sequencer state names, indexed by SEQ_STATE */
    "Idle",
    "Device Command 1",
    "Device Flag 1",
    "Device Command 2",
    "Device Flag 2"
    };


/* Configuration jumpers.

   Various aspects of interface operation are configured by installing or
   removing jumpers contained within the connector hood of the device
   interconnection cable.  Jumpers are simulated by bits in the "jumper_set"
   word, with a 1 value representing "installed" and a 0 value representing
   "removed" (although in hardware installing a jumper pulls the corresponding
   signal down to 0).

   The Diagnostic Hardware Assembly provides programmatic configuration of the
   jumpers.  All jumpers are installed by executing a "clear registers" command,
   and then individual jumpers may be removed by executing the corresponding
   "remove jumper J2Wn" commands.  This is simulated by setting all of the bits
   in the "jumper_set" word and then selectively ANDing the word with
   complemented constants from the "jumper_map" table, thereby clearing
   individual bits.


   Implementation notes:

    1. In simulation, jumper W5 is not used.  The DATA IN signals are always
       latched when DEV FLAG asserts.  Always-transparent operation is not
       provided.

    2. In hardware, DHA control word bits 6-9 are wired to decoder input bits
       0-3.  As the 3000 uses decreasing bit-number significance, while the
       decoder chip uses increasing bit-number significance, the order of the
       functions in the "jumper_map" table reflect the reversed bit order of the
       index.  For example, index 0001 contains the function for decoder output
       8 (1000).
*/

#define W1                  (1u << 0)           /* SR set by PCONTSTB/Device Status bit 11 */
#define W2                  (1u << 1)           /* +/- edge of Device Flag advances sequence counter from 1 to 2 */
#define W3                  (1u << 2)           /* Device Command operates in response/pulse mode */
#define W4                  (1u << 3)           /* inhibit/enable interrupt on STAT8 + edge */
#define W5                  (1u << 4)           /* Data In latched on sequence count 1 and 3/always transparent */
#define W6                  (1u << 5)           /* -/+ edge of Device Flag advances sequence counter from 2 to 3 */
#define W7                  (1u << 6)           /* normal/test write transfer */
#define W8                  (1u << 7)           /* inhibit/enable interrupt on STAT9 + edge */
#define W9                  (1u << 8)           /* inhibit/enable interrupt on STAT10 - edge */
#define W10                 (1u << 9)           /* Device Command same/inverted polarity as Data Out */

#define J2W1_INSTALLED      ((jumper_set & W1)  != 0)
#define J2W2_INSTALLED      ((jumper_set & W2)  != 0)
#define J2W3_INSTALLED      ((jumper_set & W3)  != 0)
#define J2W4_INSTALLED      ((jumper_set & W4)  != 0)
#define J2W5_INSTALLED      ((jumper_set & W5)  != 0)
#define J2W6_INSTALLED      ((jumper_set & W6)  != 0)
#define J2W7_INSTALLED      ((jumper_set & W7)  != 0)
#define J2W8_INSTALLED      ((jumper_set & W8)  != 0)
#define J2W9_INSTALLED      ((jumper_set & W9)  != 0)
#define J2W10_INSTALLED     ((jumper_set & W10) != 0)

static const uint32 jumper_map [16] = {         /* jumper removal map, indexed by CN_DHA_FN */
     0,                                         /*   0000 = (unaffected) */
    ~W2,                                        /*   0001 = remove jumper J2W2 */
     0,                                         /*   0010 = (unaffected) */
    ~W8,                                        /*   0011 = remove jumper J2W8 */
     0,                                         /*   0100 = (unaffected) */
    ~W4,                                        /*   0101 = remove jumper J2W4 */
    ~W10,                                       /*   0110 = remove jumper J2W10 */
    ~W6,                                        /*   0111 = remove jumper J2W6 */
     0,                                         /*   1000 = (unaffected) */
    ~W5,                                        /*   1001 = remove jumper J2W5 */
     0,                                         /*   1010 = (unaffected) */
    ~W9,                                        /*   1011 = remove jumper J2W9 */
     0,                                         /*   1100 = (unaffected) */
    ~W1,                                        /*   1101 = remove jumper J2W1 */
    ~W3,                                        /*   1110 = remove jumper J2W3 */
    ~W7                                         /*   1111 = remove jumper J2W7 */
    };


/* Diagnostic Hardware Assembly control register.

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | M | F | S | -   -   - |          jumpers J2W10-J2W1           |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   Where:

     M = master reset has occurred
     F = device flag follows device command/control 6 (0/1)
     S = status 8-10 follow control 6-8/master clear-power on-power fail (0/1)


   Implementation notes:

    1. Jumper bits are defined as 0 = removed and 1 = installed.  This is
       the opposite of the DHA hardware, for which a zero output "installs" a
       jumper.

    2. Jumpers J2W10-J2W1, which are stored in the "jumpers" array, are mirrored
       in the jumper control register to allow the diagnostic to test the full
       set of jumpers with single assertions.  Otherwise, ten assertions would
       be necessary for each test.
*/

#define DHA_MR              0100000u            /* (M) a master reset has occurred */
#define DHA_FLAG_SEL        0040000u            /* (F) device flag follows control 6 */
#define DHA_STAT_SEL        0020000u            /* (S) status 8-10 follow master clear-power on-power fail */
#define DHA_JUMPER_MASK     0001777u            /* J2Wx jumpers mask */
#define DHA_CLEAR           0001777u            /* control register clear value (all jumpers installed) */


/* Interface control word.

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | M | R | irq reset | A |  device control   | X | S | B | I | T | device
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | M | R | irq reset | A |   function    | E | X | S | B | I | T | DHA
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
*/

#define CN_MR               0100000u            /* (M) master reset */
#define CN_RIN              0040000u            /* (R) reset interrupt */
#define CN_RIN_MASK         0034000u            /* reset interrupt request selector mask */
#define CN_RIN_XFR_TMR      0004000u            /* reset watchdog timer and transfer error interrupts */
#define CN_ACQUIRE          0002000u            /* (A) acquire data from device */
#define CN_DHA_FN_MASK      0001700u            /* diagnostic hardware assembly function mask */
#define CN_DHA_ST_MASK      0001600u            /* diagnostic hardware assembly status mask */
#define CN_DHA_FLAG         0001000u            /* diagnostic hardware assembly device flag value */
#define CN_DHA_FN_ENABLE    0000040u            /* (E) enable diagnostic hardware assembly function */
#define CN_XFR_IRQ_ENABLE   0000020u            /* (X) enable data transfer interrupt */
#define CN_DEVSTAT          0000010u            /* (S) interrupt/device (0/1) status */
#define CN_BYTE_XFER        0000004u            /* (B) word/byte (0/1) transfer */
#define CN_IRQ_ENABLE       0000002u            /* (I) enable interrupts */
#define CN_XFR_TMR_ENABLE   0000001u            /* (T) enable data transfer timer */

#define CN_RIN_SHIFT        11                  /* reset interrupt request alignment shift */
#define CN_DHA_ST_SHIFT      7                  /* diagnostic hardware assembly status alignment shift */
#define CN_DHA_FN_SHIFT      6                  /* diagnostic hardware assembly function alignment shift */

#define CN_RESET(c)         (((c) & CN_RIN_MASK)    >> CN_RIN_SHIFT)
#define CN_DHA_ST(c)        (((c) & CN_DHA_ST_MASK) >> CN_DHA_ST_SHIFT)
#define CN_DHA_FN(c)        (((c) & CN_DHA_FN_MASK) >> CN_DHA_FN_SHIFT)

static const char *const dha_fn_name [16] = {   /* DHA function names, indexed by CN_DHA_FN */
    "clear registers",                          /*   0000 = clear registers (installs jumpers) */
    "remove J2W2",                              /*   0001 = remove jumper J2W2 */
    "assert DEVEND",                            /*   0010 = assert Device End */
    "remove J2W8",                              /*   0011 = remove jumper J2W8 */
    "set transfer error",                       /*   0100 = set Transfer Error flip-flop */
    "remove J2W4",                              /*   0101 = remove jumper J2W4 */
    "remove J2W10",                             /*   0110 = remove jumper J2W10 */
    "remove J2W6",                              /*   0111 = remove jumper J2W6 */
    "control 6 drives device flag",             /*   1000 = connect device flag to control bit 6 */
    "remove J2W5",                              /*   1001 = remove jumper J2W5 */
    "assert CLRIF",                             /*   1010 = assert Clear Interface */
    "remove J2W9",                              /*   1011 = remove jumper J2W9 */
    "CLR/PON/PF drive status 8-10",             /*   1100 = connect status 8-10 to master clear/power on/power fail */
    "remove J2W1",                              /*   1101 = remove jumper J2W1 */
    "remove J2W3",                              /*   1110 = remove jumper J2W3 */
    "remove J2W7"                               /*   1111 = remove jumper J2W7 */
    };

static const char *const reset_irq_name [8] = { /* reset interrupt request names, indexed by CN_RESET */
    "",                                         /*   000 = none */
    " | reset timer/xfer error irq",            /*   001 = watchdog timer and transfer error */
    " | reset I/O system irq",                  /*   010 = I/O system */
    " | reset clear interface irq",             /*   011 = clear interface */
    " | reset data xfer irq",                   /*   100 = data transfer completion */
    " | reset status 8 irq",                    /*   101 = device status 8 */
    " | reset status 9 irq",                    /*   110 = device status 9 */
    " | reset status 10 irq"                    /*   111 = device status 10 */
    };

static const BITSET_NAME dha_control_names [] = {       /* DHA control word names */
    "master clear",                                     /*   bit  0 */
    "clear interrupts",                                 /*   bit  1 */
    NULL,                                               /*   bit  2 */
    NULL,                                               /*   bit  3 */
    NULL,                                               /*   bit  4 */
    "acquire data",                                     /*   bit  5 */
    "DC6",                                              /*   bit  6 */
    "DC7",                                              /*   bit  7 */
    "DC8",                                              /*   bit  8 */
    "DC9",                                              /*   bit  9 */
    "enable function",                                  /*   bit 10 */
    "enable data xfer interrupt",                       /*   bit 11 */
    "\1device status\0interrupt status",                /*   bit 12 */
    "\1byte xfer\0word xfer",                           /*   bit 13 */
    "enable interrupts",                                /*   bit 14 */
    "enable transfer timer"                             /*   bit 15 */
    };

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


/* Printer control word.

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | M | R | irq reset | -   -   -   -   - | F | X | S | B | I | T | printer
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
*/

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

static const BITSET_NAME prt_control_names [] = {       /* Printer control word names */
    "master clear",                                     /*   bit  0 */
    "clear interrupts",                                 /*   bit  1 */
    NULL,                                               /*   bit  2 */
    NULL,                                               /*   bit  3 */
    NULL,                                               /*   bit  4 */
    "acquire data",                                     /*   bit  5 */
    NULL,                                               /*   bit  6 */
    NULL,                                               /*   bit  7 */
    NULL,                                               /*   bit  8 */
    NULL,                                               /*   bit  9 */
    "\1format\0character",                              /*   bit 10 */
    "enable data xfer interrupt",                       /*   bit 11 */
    "\1device status\0interrupt status",                /*   bit 12 */
    "\1byte xfer\0word xfer",                           /*   bit 13 */
    "enable interrupts",                                /*   bit 14 */
    "enable transfer timer"                             /*   bit 15 */
    };

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


/* Interface status word.

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | S | D | I | seqct | F | 0 | 0 |  dev irq  | X | C | Y | E | T | interrupt
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | S | D | I | seqct | F | 1 | 0 |         device status         | device
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+


   Implementation notes:

    1. The entry for bit 6 of the interrupt status names formatting array is
       given in the alternate form to print the "interrupt status" string when
       the bit is zero.
*/

#define ST_SIO_OK           0100000u            /* (S) SIO OK to use */
#define ST_DIO_OK           0040000u            /* (D) direct I/O OK to use */
#define ST_IRQ_PENDING      0020000u            /* (I) interrupt pending */
#define ST_SEQ_COUNT_0      0000000u            /* sequence count 0 (00) */
#define ST_SEQ_COUNT_1      0010000u            /* sequence count 1 (10) */
#define ST_SEQ_COUNT_2      0014000u            /* sequence count 2 (11) */
#define ST_SEQ_COUNT_3      0004000u            /* sequence count 3 (01) */
#define ST_DEVFLAG          0002000u            /* (F) device flag */
#define ST_DEVSTAT          0001000u            /* interrupt/device (0/1) status */
#define ST_DEVIRQ_MASK      0000340u            /* device interrupt request mask */
#define ST_ST8_IRQ          0000200u            /* device status 8 interrupt */
#define ST_DHA_MR           0000200u            /* diagnostic hardware assembly status 8 (master clear) */
#define ST_ST9_IRQ          0000100u            /* device status 9 interrupt */
#define ST_DHA_PON          0000100u            /* diagnostic hardware assembly status 9 (power on) */
#define ST_ST10_IRQ         0000040u            /* device status 10 interrupt */
#define ST_DHA_NOT_PF       0000040u            /* diagnostic hardware assembly status 10 (~power fail) */
#define ST_DHA_DEVSTAT_MASK 0000037u            /* diagnostic hardware assembly status 11-15 mask */
#define ST_XFR_IRQ          0000020u            /* (X) data transfer interrupt */
#define ST_ST11_SR          0000020u            /* device status 11 service request */
#define ST_CLRIF_IRQ        0000010u            /* (C) clear interface interrupt */
#define ST_IOSYS_IRQ        0000004u            /* (Y) I/O system interrupt */
#define ST_XFERERR_IRQ      0000002u            /* (E) transfer error interrupt */
#define ST_XFR_TMR_IRQ      0000001u            /* (T) transfer timer interrupt */

#define ST_DEVIRQ_SHIFT     5                   /* device status 8-10 interrupt request alignment shift */

#define ST_DEVIRQ(n)        ((n) << ST_DEVIRQ_SHIFT & ST_DEVIRQ_MASK)

#define ST_CLRIL            (ST_CLRIF_IRQ | ST_XFERERR_IRQ) /* conditions that assert the CLRIL signal */

static const uint32 sequence_counter [] = {     /* externally visible sequencer values, indexed by SEQ_STATE */
    ST_SEQ_COUNT_0,                             /*   00 = Idle */
    ST_SEQ_COUNT_1,                             /*   10 = Device_Command_1 */
    ST_SEQ_COUNT_2,                             /*   11 = Device_Flag_1 */
    ST_SEQ_COUNT_3,                             /*   01 = Device_Command_2 */
    ST_SEQ_COUNT_0                              /*   00 = Device_Flag_2 */
    };

static const uint32 reset_irq [8] = {           /* selective reset irq mask values, indexed by CN_RESET */
    ~0u,                                        /*   000 = none */
    ~(ST_XFR_TMR_IRQ | ST_XFERERR_IRQ),         /*   001 = watchdog timer and transfer error */
    ~ST_IOSYS_IRQ,                              /*   010 = I/O system */
    ~ST_CLRIF_IRQ,                              /*   011 = clear interface */
    ~ST_XFR_IRQ,                                /*   100 = data transfer completion */
    ~ST_ST8_IRQ,                                /*   101 = device status 8 */
    ~ST_ST9_IRQ,                                /*   110 = device status 9 */
    ~ST_ST10_IRQ                                /*   111 = device status 10 */
    };

static const BITSET_NAME int_status_names [] = {        /* Interrupt status word names */
    "SIO OK",                                           /*   bit  0 */
    "DIO OK",                                           /*   bit  1 */
    "interrupt",                                        /*   bit  2 */
    "SEQ 1",                                            /*   bit  3 */
    "SEQ 2",                                            /*   bit  4 */
    "device flag",                                      /*   bit  5 */
    "\1\0interrupt status",                             /*   bit  6 */
    NULL,                                               /*   bit  7 */
    "status 8",                                         /*   bit  8 */
    "status 9",                                         /*   bit  9 */
    "status 10",                                        /*   bit 10 */
    "data xfer",                                        /*   bit 11 */
    "clear interface",                                  /*   bit 12 */
    "system",                                           /*   bit 13 */
    "transfer error",                                   /*   bit 14 */
    "transfer timeout"                                  /*   bit 15 */
    };

static const BITSET_NAME dev_status_names [] = {        /* Device status word names */
    "SIO OK",                                           /*   bit  0 */
    "DIO OK",                                           /*   bit  1 */
    "interrupt",                                        /*   bit  2 */
    "SEQ 1",                                            /*   bit  3 */
    "SEQ 2",                                            /*   bit  4 */
    "device flag",                                      /*   bit  5 */
    "device status",                                    /*   bit  6 */
    NULL,                                               /*   bit  7 */
    "DS8",                                              /*   bit  8 */
    "DS9",                                              /*   bit  9 */
    "DS10",                                             /*   bit 10 */
    "DS11",                                             /*   bit 11 */
    "DS12",                                             /*   bit 12 */
    "DS13",                                             /*   bit 13 */
    "DS14",                                             /*   bit 14 */
    "DS15"                                              /*   bit 15 */
    };

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

static const BITSET_FORMAT dev_status_format =          /* names, offset, direction, alternates, bar */
    { FMT_INIT (dev_status_names, 0, msb_first, no_alt, no_bar) };


/* Printer status word.

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | S | D | I | seqct | F | 1 | 0 | - | L | L | N | V | U | - | - | printer
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
*/

#define ST_ONLINE           0000140u            /* online */
#define ST_NOT_READY        0000020u            /* not ready */
#define ST_VFU_12           0000010u            /* VFU channel 12 */
#define ST_VFU_9            0000004u            /* VFU channel 9 */

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

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


/* Interface state */

static HP_WORD control_word     = 0;            /* control word */
static HP_WORD int_status_word  = 0;            /* interrupt status word (bits 8-15) */
static HP_WORD dev_status_word  = 0;            /* device status word (bits 8-15) */
static HP_WORD read_word        = 0;            /* read word */
static HP_WORD write_word       = 0;            /* write word */

static SEQ_STATE sequencer  = Idle;             /* data transfer handshake sequencer */
static uint32    jumper_set = PRINTER_JUMPERS;  /* set of configuration jumpers */

static FLIP_FLOP sio_busy       = CLEAR;        /* SIO busy flip-flop */
static FLIP_FLOP channel_sr     = CLEAR;        /* channel service request flip-flop */
static FLIP_FLOP device_sr      = CLEAR;        /* device service request flip-flop */
static FLIP_FLOP input_xfer     = CLEAR;        /* input transfer flip-flop */
static FLIP_FLOP output_xfer    = CLEAR;        /* output transfer flip-flop */
static FLIP_FLOP read_xfer      = CLEAR;        /* read transfer flip-flop */
static FLIP_FLOP write_xfer     = CLEAR;        /* write transfer flip-flop */
static FLIP_FLOP interrupt_mask = SET;          /* interrupt mask flip-flop */

static FLIP_FLOP device_command = CLEAR;        /* device command flip-flop */
static FLIP_FLOP device_flag    = CLEAR;        /* device flag flip-flop */
static FLIP_FLOP device_end     = CLEAR;        /* device end flip-flop */

static HP_WORD data_out           = 0;          /* external DATA OUT signal bus */
static t_bool  device_command_out = FALSE;      /* external DEV CMD signal state */

static HP_WORD data_in            = 0;          /* external DATA IN signal bus */
static t_bool  device_flag_in     = FALSE;      /* external DEV FLAG signal state */
static t_bool  device_end_in      = FALSE;      /* external DEV END signal state */


/* Diagnostic Hardware Assembly state */

static HP_WORD dha_control_word = 0;            /* Diagnostic Hardware Assembly control word */
static t_bool  power_warning    = FALSE;        /* PFWARN is not asserted to the DHA */


/* 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 uint32 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 CNTLR_INTRF ui_interface;
static t_stat      xfer_service  (UNIT   *uptr);
static t_stat      pulse_service (UNIT   *uptr);
static t_stat      timer_service (UNIT   *uptr);
static t_stat      ui_reset      (DEVICE *dptr);


/* Interface local utility routines */

static t_stat       master_reset          (t_bool programmed_clear);
static void         clear_interface_logic (void);
static void         activate_unit         (UNIT   *uptr);
static void         report_error          (FILE   *stream);
static OUTBOUND_SET set_interrupt         (uint32 interrupt);
static OUTBOUND_SET set_device_status     (uint32 status_mask, uint32 new_status_word);
static OUTBOUND_SET handshake_xfer        (void);


/* Diagnostic Hardware Assembly local SCP support routines */

static t_stat diag_service (UNIT *uptr);


/* Diagnostic Hardware Assembly local utility routines */

static t_stat       diag_reset   (t_bool programmed_clear);
static OUTBOUND_SET diag_control (uint32 control_word);


/* Printer local SCP support routines */

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

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


/* Printer local utility routines */

static t_stat       lp_reset        (t_bool programmed_clear);
static OUTBOUND_SET lp_control      (uint32 control_word);
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 */


/* Device information block */

static DIB lp_dib = {
    &ui_interface,                              /* device interface */
    14,                                         /* device number */
    11,                                         /* service request number */
    18,                                         /* interrupt priority */
    INTMASK_E                                   /* interrupt mask */
    };


/* Unit list */

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

static UNIT lp_unit [] = {
    { UDATA (&xfer_service,  UNIT_FLAGS | UNIT_2617, 0), 0          },
    { UDATA (&pulse_service, UNIT_DIS,               0), PULSE_TIME },
    { UDATA (&timer_service, UNIT_DIS,               0), XFER_TIME  }
    };


/* Register list.

   The list consists of the interface registers followed by the Diagnostic
   Hardware Assembly registers and then the printer registers.


   Implementation notes:

    1. The DHA hardware buffers control word bits 6-10 to LEDs.  Inspection and
       user confirmation of the control word state is required by the interface
       diagnostic.  In simulation, bits 6-10 of the control word are presented
       as the CNLED register to allow an ASSERT command to test this subrange of
       bits with single commands.
*/

static REG lp_reg [] = {
/*    Macro   Name    Location                  Radix     Width      Offset      Depth            Flags        */
/*    ------  ------  ------------------------  -----  ------------  ------  -------------  ------------------ */
    { FLDATA (SIOBSY, sio_busy,                                        0)                                      },
    { FLDATA (CHANSR, channel_sr,                                      0)                                      },
    { FLDATA (DEVSR,  device_sr,                                       0)                                      },
    { FLDATA (INXFR,  input_xfer,                                      0)                                      },
    { FLDATA (OUTXFR, output_xfer,                                     0)                                      },
    { FLDATA (RDXFR,  read_xfer,                                       0)                                      },
    { FLDATA (WRXFR,  write_xfer,                                      0)                                      },
    { FLDATA (INTMSK, interrupt_mask,                                  0)                                      },

    { FLDATA (DEVCMD, device_command,                                  0)                                      },
    { FLDATA (DEVFLG, device_flag,                                     0)                                      },
    { FLDATA (DEVEND, device_end,                                      0)                                      },

    { DRDATA (SEQSTA, sequencer,                            8),                             PV_LEFT            },
    { ORDATA (CNTL,   control_word,                        16),                             PV_RZRO            },
    { ORDATA (ISTAT,  int_status_word,                     16),                             PV_RZRO            },
    { ORDATA (DSTAT,  dev_status_word,                     16),                             PV_RZRO            },
    { ORDATA (READ,   read_word,                           16),                             PV_RZRO | REG_X    },
    { ORDATA (WRITE,  write_word,                          16),                             PV_RZRO | REG_X    },
    { YRDATA (J2WX,   jumper_set,                          10),                             PV_RZRO            },

    { ORDATA (DATOUT, data_out,                            16),                             PV_RZRO | REG_X    },
    { ORDATA (DATIN,  data_in,                             16),                             PV_RZRO | REG_X    },

    { FLDATA (DCOUT,  device_command_out,                              0)                                      },
    { FLDATA (DFIN,   device_flag_in,                                  0)                                      },
    { FLDATA (DENDIN, device_end_in,                                   0)                                      },

      DIB_REGS (lp_dib),

    { ORDATA (DIAGCN, dha_control_word,                    16),                             PV_RZRO            },
    { GRDATA (CNLED,  control_word,               2,        5,         5),                  PV_RZRO            },
    { FLDATA (PFWARN, power_warning,                                   0)                                      },

    { 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,    lp_unit [0].pos,                  T_ADDR_W),                          PV_LEFT            },
    { DRDATA (UWAIT,  lp_unit [0].wait,                    32),                             PV_LEFT | REG_HRO  },

    { NULL }
    };


/* Modifier list */

typedef enum {                                  /* Device modes */
    Fast_Time,                                  /*   use optimized timing */
    Real_Time,                                  /*   use realistic timing */
    Printer,                                    /*   connect to the printer */
    Diagnostic                                  /*   connect to the DHA */
    } DEVICE_MODES;

static MTAB lp_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,      },

/*    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,            VAL_DEVNO,   "DEVNO",      "DEVNO",      &hp_set_dib,  &hp_show_dib,  (void *) &lp_dib },
    { MTAB_XDV,            VAL_INTMASK, "INTMASK",    "INTMASK",    &hp_set_dib,  &hp_show_dib,  (void *) &lp_dib },
    { MTAB_XDV,            VAL_INTPRI,  "INTPRI",     "INTPRI",     &hp_set_dib,  &hp_show_dib,  (void *) &lp_dib },
    { MTAB_XDV,            VAL_SRNO,    "SRNO",       "SRNO",       &hp_set_dib,  &hp_show_dib,  (void *) &lp_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 lp_deb [] = {
    { "CMD",   DEB_CMD   },                     /* controller commands */
    { "CSRW",  DEB_CSRW  },                     /* interface control, status, read, and write actions */
    { "SERV",  DEB_SERV  },                     /* controller unit service scheduling calls */
    { "XFER",  DEB_XFER  },                     /* controller data reads and writes */
    { "STATE", DEB_STATE },                     /* handshake execution state changes */
    { "IOBUS", DEB_IOB   },                     /* interface I/O bus signals and data words */
    { NULL,    0         }
    };


/* Device descriptor */

DEVICE lp_dev = {
    "LP",                                       /* device name */
    lp_unit,                                    /* unit array */
    lp_reg,                                     /* register array */
    lp_mod,                                     /* modifier array */
    3,                                          /* 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 */
    &ui_reset,                                  /* reset routine */
    NULL,                                       /* boot routine */
    &lp_attach,                                 /* attach routine */
    &lp_detach,                                 /* detach routine */
    &lp_dib,                                    /* device information block pointer */
    DEV_DISABLE | DEV_DEBUG,                    /* device flags */
    0,                                          /* debug control flags */
    lp_deb,                                     /* debug flag name array */
    NULL,                                       /* memory size change routine */
    NULL                                        /* logical device name */
    };



/* Interface local SCP support routines */



/* Universal interface.

   The universal interface is installed on the IOP and Multiplexer Channel buses
   and receives direct and programmed I/O commands from the IOP and Multiplexer
   Channel, respectively.  In simulation, the asserted signals on the buses are
   represented as bits in the inbound_signals set.  Each signal is processed
   sequentially in numerical order, and a set of similar outbound_signals is
   assembled and returned to the caller, simulating assertion of the
   corresponding backplane signals.

   After setting the control mode to establish word or byte mode, SIO data
   transfer between the interface and the connected device is initiated by a
   PWRITESTB or READNEXTWD order.  For direct I/O, a DWRITESTB or a DCONTSTB
   with the "acquire" bit set initiates a transfer.

   A sequencer governs the generation of the device handshake signals.  The
   handshake begins with the assertion of the Device Control signal.  In
   response, the device asserts the Device Flag signal.  The interface then
   denies Device Control, and the device denies Device Flag.  For a byte
   transfer, this sequence repeats automatically for the second byte.  Byte
   packing and unpacking is provided by the interface.

   Eight interrupt sources are provided and may be individually set by their
   associated conditions.  A master interrupt enable is provided by setting the
   appropriate control word bit, and the requesting sources may be cleared
   independently.  An interrupt acknowledgement from the IOP clears the master
   interrupt enable to prevent multiple sources from interrupting
   simultaneously.

   The status word returned by a DSTATSTB or PSTATSTB signal consists of
   interface status in the upper byte and either interrupt or device status in
   the lower byte, as selected by a control word bit.


   Implementation notes:

    1. In a hardware transfer abort, READNEXTWD or PWRITESTB causes the
       sequencer to transition to the Device_Command_1 state and set the Device
       End flip-flop, which asserts DEVEND to the multiplexer channel, and then
       the Device End flip-flop is cleared by ACKSR.  In simulation, ACKSR
       occurs before the PREADSTB or PWRITESTB that asserts DEVEND, so the state
       of the Device End flip-flop is saved in the ACKSR handler and is then
       checked in a subsequent PREADSTB or PWRITESTB to assert DEVEND.

    2. In hardware, the SETJMP signal is ignored, and the JMPMET signal is
       asserted continuously when enabled by CHANSO.

    3. In hardware, a power fail warning (PFWARN) is asserted continuously from
       detection until power is lost.  In simulation, the "power_warning" flag
       is set by a PFWARN assertion and is cleared by a power-on reset.  PFWARN
       is used only by the DHA.
*/

static SIGNALS_DATA ui_interface (DIB *dibptr, INBOUND_SET inbound_signals, HP_WORD inbound_value)
{
INBOUND_SIGNAL signal;
INBOUND_SET    working_set      = inbound_signals;
HP_WORD        outbound_value   = 0;
OUTBOUND_SET   outbound_signals = NO_SIGNALS;
t_bool         abort_transfer   = FALSE;

tprintf (lp_dev, DEB_IOB, "Received data %06o with signals %s\n",
         inbound_value, fmt_bitset (inbound_signals, inbound_format));

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

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

        case INTPOLLIN:
            if (dibptr->interrupt_request) {            /* if a request is pending */
                dibptr->interrupt_request = CLEAR;      /*   then clear it */
                dibptr->interrupt_active  = SET;        /*     and mark it now active */

                outbound_signals |= INTACK;             /* acknowledge the interrupt */
                outbound_value = dibptr->device_number; /*   and return our device number */

                control_word &= ~(CN_DEVSTAT | CN_IRQ_ENABLE);  /* clear the device status and IRQ enable flip-flops */
                }

            else                                        /* otherwise the request has been reset */
                outbound_signals |= INTPOLLOUT;         /*   so let the IOP know to cancel it */
            break;


        case SETINT:
        case DSETINT:
            outbound_signals |= set_interrupt (ST_IOSYS_IRQ);   /* set the I/O system interrupt flip-flop */
            break;


        case DRESETINT:
            dibptr->interrupt_active = CLEAR;           /* reset the interrupt active flip-flop */
            outbound_signals |= set_interrupt (0);      /*   and check whether another IRQ is pending */
            break;


        case DSETMASK:
            if (dibptr->interrupt_mask == INTMASK_E)            /* if the mask is always enabled */
                interrupt_mask = SET;                           /*   then set the mask flip-flop */
            else                                                /* otherwise */
                interrupt_mask = D_FF (dibptr->interrupt_mask   /*   set the mask flip-flop if the mask bit */
                                       & inbound_value);        /*     is present in the mask value */

            outbound_signals |= set_interrupt (0);              /* check whether an IRQ is pending */
            break;


        case PCONTSTB:
            if (! J2W1_INSTALLED)                       /* if W1 (SR set by Device Status) is not installed */
                device_sr = SET;                        /*   then set the device service request flip-flop */

        /* fall through into the DCONTSTB case */

        case DCONTSTB:
            tprintf (lp_dev, DEB_CSRW,
                     (lp_dev.flags & DEV_DIAG && inbound_value & CN_DHA_FN_ENABLE
                       ? "Control is %s%s | %s\n"
                       : "Control is %s%s\n"),
                     fmt_bitset (inbound_value,
                                 (lp_dev.flags & DEV_DIAG
                                   ? dha_control_format
                                   : prt_control_format)),
                     reset_irq_name [CN_RESET (inbound_value)],
                     dha_fn_name [CN_DHA_FN (inbound_value)]);

            if (inbound_value & CN_MR)                  /* if the programmed master reset bit is set */
                master_reset (TRUE);                    /*   then reset the interface and the control word */

            else if (inbound_value & CN_RIN) {          /* otherwise if the reset interrupt bit is set */
                dibptr->interrupt_request = CLEAR;      /*   then clear the interrupt request */
                int_status_word = 0;                    /*     and all interrupt sources */

                sim_cancel (timer_uptr);                /* cancel the transfer timer */
                control_word = inbound_value;           /*   and set the control word */
                }

            else {                                                          /* otherwise */
                int_status_word &= reset_irq [CN_RESET (inbound_value)];    /*   clear the specified IRQ source */

                if ((inbound_value & CN_RIN_MASK) == CN_RIN_XFR_TMR)    /* if the timer interrupt was cleared */
                    sim_cancel (timer_uptr);                            /*   then stop the timer too */

                else if (CN_XFR_TMR_ENABLE                      /* otherwise if the transfer timer */
                           & ~control_word & inbound_value) {   /*   is enabled with a 0-to-1 transition */
                    sim_cancel (timer_uptr);                    /*     then retrigger */
                    activate_unit (timer_uptr);                 /*       the timer */
                    }

                control_word = inbound_value;                   /* set the control word */
                }

            if (control_word & CN_ACQUIRE) {            /* if the next word is requested */
                device_command = SET;                   /*   then set the device command flip-flop */
                read_xfer      = SET;                   /*     and the read transfer flip-flop */
                outbound_signals |= handshake_xfer ();  /*       and start the device handshake */
                }

            if (lp_dev.flags & DEV_DIAG)                            /* if the DHA is installed */
                outbound_signals |= diag_control (control_word);    /*   then process the DHA-specific controls */
            else                                                    /* otherwise */
                outbound_signals |= lp_control (control_word);      /*   process the device-specific controls */

            break;


        case PSTATSTB:
        case DSTATSTB:
            outbound_value = sequence_counter [sequencer];  /* start with the sequence counter value */

            if (sio_busy == CLEAR                       /* if the interface is inactive */
              && (int_status_word & ST_CLRIL) == 0)     /*   and the clear interface logic IRQ is denied */
                outbound_value |= ST_SIO_OK;            /*     then programmed I/O is enabled */

            if (sequencer == Idle)                      /* if the device is inactive */
                outbound_value |= ST_DIO_OK;            /*   then direct I/O is enabled */

            if (int_status_word)                        /* if any interrupt requests are pending */
                outbound_value |= ST_IRQ_PENDING;       /*   then set the status bit */

            if (device_flag_in)                         /* if the device flag is asserted */
                outbound_value |= ST_DEVFLAG;           /*   then set the status bit */

            if (control_word & CN_DEVSTAT)                      /* if the device status flip-flop is set */
                outbound_value |= ST_DEVSTAT | dev_status_word; /*   then return the device status */
            else                                                /* otherwise */
                outbound_value |= int_status_word;              /*   return the interrupt status */

            tprintf (lp_dev, DEB_CSRW, "Status is %s\n",
                     fmt_bitset (outbound_value,
                                 (control_word & CN_DEVSTAT
                                   ? (lp_dev.flags & DEV_DIAG
                                       ? dev_status_format
                                       : prt_status_format)
                                   : int_status_format)));
            break;


        case DREADSTB:
            outbound_value = (HP_WORD) read_word;       /* return the data input register value */
            break;


        case DWRITESTB:
            write_word = (uint32) inbound_value;        /* store the value in the data output register */

            device_command = SET;                       /* set the device command flip-flop */
            write_xfer     = SET;                       /*   and the write transfer flip-flop */
            outbound_signals |= handshake_xfer ();      /*     and start the device handshake */
            break;


        case DSTARTIO:
            tprintf (lp_dev, DEB_CSRW, "Channel program started\n");

            sio_busy = SET;                             /* set the SIO busy flip-flop */

            mpx_assert_REQ (dibptr);                    /* request the channel */

            channel_sr = SET;                           /* set the service request flip-flop */
            outbound_signals |= SRn;                    /*   and assert a service request */
            break;


        case ACKSR:
            device_sr = CLEAR;                          /* acknowledge the service request */

            abort_transfer = (t_bool) device_end;       /* TRUE if the transfer is to be aborted */
            device_end = CLEAR;                         /* clear the device end flip-flop */
            break;


        case TOGGLESR:
            TOGGLE (channel_sr);                        /* set or clear the channel service request flip-flop */
            break;


        case TOGGLESIOOK:
            TOGGLE (sio_busy);                          /* set or clear the SIO busy flip-flop */

            if (sio_busy == CLEAR)
                tprintf (lp_dev, DEB_CSRW, "Channel program ended\n");
            break;


        case TOGGLEINXFER:
            TOGGLE (input_xfer);                        /* set or clear the input transfer flip-flop */

            device_end_in = FALSE;                      /* clear the external device end condition */
            break;


        case TOGGLEOUTXFER:
            TOGGLE (output_xfer);                       /* set or clear the output transfer flip-flop */

            if (output_xfer == SET)                     /* if starting an output transfer */
                device_sr = SET;                        /*   request the first word to write */

            device_end_in = FALSE;                      /* clear the external device end condition */
            break;


        case PCMD1:
            device_sr = SET;                            /* request the second control word */
            break;


        case READNEXTWD:
            device_command = SET;                       /* set the device command flip-flop */
            read_xfer      = SET;                       /*   and the read transfer flip-flop */
            outbound_signals |= handshake_xfer ();      /*     and start the device handshake */
            break;


        case PREADSTB:
            if (abort_transfer) {                           /* if the transfer has been aborted */
                outbound_value = dibptr->device_number * 4; /*   then return the DRT address */
                outbound_signals |= DEVEND;                 /*     and indicate a device abort */
                }

            else                                            /* otherwise the transfer continues */
                outbound_value = (HP_WORD) read_word;       /*   so return the data input register value */
            break;


        case PWRITESTB:
            if (abort_transfer) {                           /* if the transfer has been aborted */
                outbound_value = dibptr->device_number * 4; /*   then return the DRT address */
                outbound_signals |= DEVEND;                 /*     and indicate a device abort */
                }

            else {                                          /* otherwise the transfer continues */
                write_word = (uint32) inbound_value;        /*   so store the value in the data output register */

                device_command = SET;                       /* set the device command flip-flop */
                write_xfer     = SET;                       /*   and the write transfer flip-flop */
                outbound_signals |= handshake_xfer ();      /*     and start the device handshake */
                }
            break;


        case DEVNODB:
            outbound_value = dibptr->device_number * 4;     /* return the DRT address */
            break;


        case XFERERROR:
            tprintf (lp_dev, DEB_CSRW, "Channel program aborted\n");

            clear_interface_logic ();                   /* clear the interface to abort the transfer */

            outbound_signals |= set_interrupt (ST_XFERERR_IRQ); /* set the transfer error interrupt flip-flop */
            break;


        case CHANSO:
            if (channel_sr | device_sr)                 /* if the interface has requested service */
                outbound_signals |= SRn;                /*   then assert SRn to the channel */

            outbound_signals |= JMPMET;                 /* JMPMET is tied active on this interface */
            break;


        case EOT:
            if (inbound_signals & PREADSTB)             /* if this is the end of a read transfer */
                device_sr = SET;                        /*   then request channel service */
            break;


        case PFWARN:
            power_warning = TRUE;                       /* system power is in the process of failing */
            break;


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

    IOCLEARSIG (working_set, signal);                   /* remove the current signal from the set */
    }


tprintf (lp_dev, DEB_IOB, "Returned data %06o with signals %s\n",
         outbound_value, fmt_bitset (outbound_signals, outbound_format));

return IORETURN (outbound_signals, outbound_value);     /* return the outbound signals and value */
}


/* Service the transfer handshake.

   This service routine is called once for each state of the device transfer
   handshake.  The handshake sequencer schedules the transfer events with the
   appropriate delays.

   Jumper W10 determines the output polarity of the DEV CMD signal to the
   device, and jumpers W2 and W6 determine the input edges of the DEV FLAG
   signal from the device used to assert and deny the Device Flag, as follows:

     Jumper  Interpretation when removed    Interpretation when installed
     ------  -----------------------------  -----------------------------
       W10   DEV CMD polarity is normal     DEV CMD polarity is inverted

       W2    Flag asserts on leading edge   Flag asserts on trailing edge

       W6    Flag denies on trailing edge   Flag denies on leading edge

   Note that if jumpers W2 and W6 are not installed or removed in pairs, the
   Device Flag asserts and denies on the same edge of the DEV FLAG signal.  In
   this case, the service routine sets the flag on the first call and clears the
   flag on the second call without requiring a change in the incoming signal.


   Implementation notes:

    1. The "device_command_out" and "device_flag_in" variables represent the
       states of the DEV CMD and DEV FLAG signal lines.  Edge detection for the
       Device Flag is accomplished by comparing the current state to the prior
       state.

    2. As the routine was entered by an event timer expiration, the handshake
       sequencer must be called explicitly, and any returned backplane signals
       must be asserted explicitly.

    3. This routine may be called with a NULL "uptr" parameter to update the
       saved last state of the "device_flag_in" variable.  The NULL value
       indicates that this is not part of the normal handshake sequence.
*/

static t_stat xfer_service (UNIT *uptr)
{
static t_bool device_flag_last = FALSE;
t_stat        result;
OUTBOUND_SET  signals;

device_command_out = device_command ^ J2W10_INSTALLED;  /* set device command out; invert if W10 is installed */

if (lp_dev.flags & DEV_DIAG)                            /* if the DHA is connected */
    result = diag_service (uptr);                       /*   then service the diagnostic hardware */
else                                                    /* otherwise */
    result = lp_service (uptr);                         /*   service the connected device */

if (sequencer == Device_Command_1                       /* if Device Command */
  || sequencer == Device_Command_2) {                   /*   is asserted */
    if (device_flag_last != device_flag_in              /*     then if the flag input has changed */
      && J2W2_INSTALLED ^ device_flag_in)               /*     and jumper W2 is in and 1 -> 0 or W2 is out and 0 -> 1 */
        device_flag = SET;                              /*       then Device Flag sets */
    }

else                                                    /* otherwise Device Command is denied */
    if (J2W2_INSTALLED != J2W6_INSTALLED                /*   so if W2 installation differs from W6 installation */
      || (device_flag_last != device_flag_in            /*     or if the flag input has changed */
      && J2W6_INSTALLED ^ device_flag_last))            /*     and jumper W6 is in and 0 -> 1 or W6 is out and 1 -> 0 */
        device_flag = CLEAR;                            /*       then Device Flag clears */

device_flag_last = device_flag_in;                      /* save the current state of the flag */

signals = handshake_xfer ();                            /* continue the handshake */

if (signals & INTREQ)                                   /* if an interrupt request was generated */
    iop_assert_INTREQ (&lp_dib);                        /*   then assert the INTREQ signal */

if (signals & SRn)                                      /* if a service request was generated */
    mpx_assert_SRn (&lp_dib);                           /*   then assert the SRn signal */

return result;                                          /* return the result of the service call */
}


/* Service the device command pulse timer.

   In pulse mode, the DEV CMD signal asserts for 8 microseconds.  This service
   routine is entered to deny DEV CMD.  The transfer service is called directly
   to notify it of Device Command clearing, and the handshake sequencer is then
   called in case the transfer service altered the Device Flag in response.
*/

static t_stat pulse_service (UNIT *uptr)
{
t_stat status;

tprintf (lp_dev, DEB_SERV, "Pulse service entered\n");

device_command = CLEAR;                                 /* clear the device command flip-flop */

status = xfer_service (xfer_uptr);                      /* let the device know that command has denied */
handshake_xfer ();                                      /*   and continue the handshake */

return status;
}


/* Service the transfer timer.

   Setting the appropriate bit in the control word starts the five-second
   transfer timer.  If it expires, this routine is entered.  The transfer timer
   interrupt is set, and if interrupts are enabled, INTREQ is asserted to the
   IOP.  As a convenience to the user, the file attached to the device unit is
   flushed.
*/

static t_stat timer_service (UNIT *uptr)
{
tprintf (lp_dev, DEB_SERV, "Watchdog service entered\n");

if (set_interrupt (ST_XFR_TMR_IRQ) == INTREQ)           /* set the transfer timer interrupt flip-flop */
    iop_assert_INTREQ (&lp_dib);                        /*   and assert the INTREQ signal if enabled */

if (xfer_unit.flags & UNIT_ATT)                         /* if the transfer unit is attached */
    fflush (xfer_unit.fileref);                         /*   then flush any partial output */

return SCPE_OK;                                         /* return success */
}


/* Device reset routine.

   This routine is called for a RESET or RESET LP command.  It is the simulation
   equivalent of the IORESET signal, which is asserted by the front panel LOAD
   and DUMP switches.

   For this interface, IORESET is identical to the Programmed Master Clear
   invoked by setting bit 0 of the control word.


   Implementation notes:

    1. Calling "master_reset" with a FALSE parameter indicates that this is a
       commanded reset.  This allows the connected device-specific reset
       routines to distinguish from a Programmed Master Clear.
*/

static t_stat ui_reset (DEVICE *dptr)
{
return master_reset (FALSE);                            /* perform a non-programmed master reset */
}



/* Interface local utility routines */



/* Master reset.

   A master reset is generated either by an I/O Reset signal or a Programmed
   Master Clear (CIO bit 0).  It sets the interrupt mask, clears any pending or
   active interrupt, clears all interrupt sources, clears the control word,
   clears the read and write registers, resets the handshake sequencer to its
   idle state, clears the interface logic flip-flops, and cancels all active
   event timers.  It also calls pulses the MASTER CLEAR signal line to the
   device for a preset time.


   Implementation notes:

    1. Calling the reset routine for the connected device simulates asserting
       the MASTER CLEAR signal.
*/

static t_stat master_reset (t_bool programmed_clear)
{
interrupt_mask = SET;                                   /* set the interrupt mask flip-flop */

lp_dib.interrupt_request = CLEAR;                       /* clear any current */
lp_dib.interrupt_active  = CLEAR;                       /*   interrupt request */

int_status_word = 0;                                    /* clear all interrupt request sources */

control_word = 0;                                       /* clear the control word */
write_word   = 0;                                       /*   and the output data register */
read_word    = 0;                                       /*     and the input data register */

sequencer = Idle;                                       /* clear the handshake sequencer to the idle state */

read_xfer  = CLEAR;                                     /* clear the read transfer */
write_xfer = CLEAR;                                     /*   and write transfer flip-flops */

device_command = CLEAR;                                 /* clear the device command */
device_flag    = CLEAR;                                 /*   and device flag flip-flops */

data_out      = 0;                                      /* clear the external state */
data_in       = 0;                                      /*   of the I/O lines */
device_end_in = FALSE;                                  /*     and the external device end line */

clear_interface_logic ();                               /* clear the interface to abort any transfer in progress */

sim_cancel (xfer_uptr);                                 /* cancel */
sim_cancel (pulse_uptr);                                /*   any pending */
sim_cancel (timer_uptr);                                /*     event timers */

if (lp_dev.flags & DEV_DIAG)                            /* if the DHA is installed */
    return diag_reset (programmed_clear);               /*   then reset the diagnostic hardware */
else                                                    /* otherwise */
    return lp_reset (programmed_clear);                 /*   reset the device */
}


/* Clear the interface logic.

   The clear interface logic signal is asserted when the channel indicates a
   transfer failure by asserting XFERERROR, or when the device asserts the CLEAR
   INTERFACE signal.  It clears the SIO Busy, Channel and Device Service
   Request, Input Transfer, Output Transfer, and Device End flip-flops.
*/

static void clear_interface_logic (void)
{
sio_busy    = CLEAR;                                    /* clear the SIO busy flip-flop */
channel_sr  = CLEAR;                                    /*   and the channel service request flip-flop */
device_sr   = CLEAR;                                    /*   and the device service request flip-flop */
input_xfer  = CLEAR;                                    /*   and the input transfer flip-flop */
output_xfer = CLEAR;                                    /*   and the output transfer flip-flop */
device_end  = CLEAR;                                    /*   and the device end flip-flop */

return;
}


/* Activate the unit.

   The specified unit is activated using the unit's "wait" time.  If tracing
   is enabled, the activation is logged to the debug file.


   Implementation notes:

    1. A zero-length delay is scheduled, rather than calling the service routine
       directly, so that the status return value from the event service routine
       is correctly passed back to SCP.
*/

static void activate_unit (UNIT *uptr)
{
tprintf (lp_dev, DEB_SERV, "%s delay %u service scheduled\n",
         unit_name [uptr - lp_unit], uptr->wait);

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

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;
}


/* Set an interrupt.

   The interrupt bit specified is set in the interrupt status word.  If enabled,
   INTREQ is returned to request an interrupt.

   The routine is also called with a zero "interrupt" parameter value to check
   whether an interrupt should be requested.
*/

static OUTBOUND_SET set_interrupt (uint32 interrupt)
{
int_status_word |= interrupt;                           /* set the specified interrupt flip-flop */

if (int_status_word                                     /* if an interrupt request is present */
  && control_word & CN_IRQ_ENABLE                       /*   and the IRQ enable flip-flop is set */
  && lp_dib.interrupt_active == CLEAR                   /*   and no interrupt is currently active */
  && interrupt_mask == SET) {                           /*   and the interrupt mask is satisfied */
    lp_dib.interrupt_request = SET;                     /*     then request an interrupt */
    return INTREQ;                                      /*       and assert the INTREQ signal */
    }

else                                                    /* otherwise an interrupt request */
    return NO_SIGNALS;                                  /*   cannot be made at this time */
}


/* Set the device status.

   The device status word is masked with the supplied "status_mask" and then the
   corresponding bits of the "new_status_word" are merged in.  If enabled by the
   associated jumpers and the required edge transitions, interrupts for status
   bits 8-10 may be generated.
*/

static OUTBOUND_SET set_device_status (uint32 status_mask, uint32 new_status_word)
{
OUTBOUND_SET outbound_signals = NO_SIGNALS;

if (status_mask & ST_DEVIRQ_MASK) {                         /* if a status interrupt is possible */
    if (J2W4_INSTALLED                                      /*   then if jumper J4 is installed to enable */
      && ~dev_status_word & new_status_word & ST_ST8_IRQ)   /*     and a 0 -> 1 transition occurred on status 8 */
        outbound_signals |= set_interrupt (ST_ST8_IRQ);     /*       then set the status 8 interrupt flip-flop */

    if (J2W8_INSTALLED                                      /* if jumper J8 is installed to enable */
      && ~dev_status_word & new_status_word & ST_ST9_IRQ)   /*   and a 0 -> 1 transition occurred on status 9 */
        outbound_signals |= set_interrupt (ST_ST9_IRQ);     /*     then set the status 9 interrupt flip-flop */

    if (J2W9_INSTALLED                                      /* if jumper J9 is installed to enable */
      && dev_status_word & ~new_status_word & ST_ST10_IRQ)  /*   and a 1 -> 0 transition occurred on status 10 */
        outbound_signals |= set_interrupt (ST_ST10_IRQ);    /*     then set the status 10 interrupt flip-flop */
    }

dev_status_word = dev_status_word & ~status_mask            /* clear the old device status */
                    | new_status_word & status_mask;        /*   and set the new status */

return outbound_signals;                                    /* return INTREQ if any interrupts were requested */
}


/* Start or continue the data transfer handshake.

   This routine implements the two-wire data transfer handshake with the device.
   For each word or byte transferred, the Device Command signal from the
   interface and the Device Flag signal from the device assume these states:

     Command     Flag         State          Next State
      State      State        Action         Transition
     --------  --------  ----------------  --------------
     denied    denied    device idle       Command sets
     asserted  denied    device started    Flag sets
     asserted  asserted  device completed  Command clears
     denied    asserted  interface idle    Flag clears

   In hardware, a two-bit gray counter implements a four-state sequencer, with
   three states assigned as follows for a word transfer:

                                Command     Flag
     State  State Action         State      State   Next State Transition
     -----  ------------------  --------  --------  ---------------------
      0 0   idle                denied    denied    read or write command
      1 0   word requested      asserted  denied    Flag sets
      1 1   word started        denied    asserted  Flag clears
      0 0   word completed      denied    denied    ---

   For a two-byte transfer, the states are:

                                Command     Flag
     State  State Action         State      State   Next State Transition
     -----  ------------------  --------  --------  ---------------------
      0 0   idle                denied    denied    read or write command
      1 0   1st byte requested  asserted  denied    Flag sets
      1 1   1st byte started    denied    asserted  Flag clears
      1 0   1st byte completed  asserted  denied    Flag sets
            2nd byte requested
      0 0   2nd byte started    denied    asserted  Flag clears
      0 0   2nd byte completed  denied    denied    ---

   The presence of the asserted Device Flag when the count is 00 differentiates
   between the "2nd byte started" and "operation completed" conditions.

   In simulation, these last two conditions are assigned to separate states, as
   follows:

     Hdwe      Simulation     Command     Flag
     State       State         State      State
     -----  ----------------  --------  --------
      0 0   Idle              denied    denied
      1 0   Device_Command_1  asserted  denied
      1 1   Device_Flag_1     denied    asserted
      1 0   Device_Command_2  asserted  denied
      0 0   Device_Flag_2     denied    asserted
      0 0   Idle              denied    denied

   To provide the proper values to appear in the Sequence Counter field of the
   status word, a mapping array is used to supply the value 00 for the
   Device_Flag_2 state.

   The device service is scheduled after each state transition, except the
   return to the idle state, to detect the change in the Device Command signal
   or to schedule the change in the Device Flag.  The device determines whether
   the service will be entered immediately (at the next poll) or after a delay
   time expires.

   For the diagnostic device, the service routine is entered immediately for all
   transitions.  For the printer device, the service routine is entered
   immediately for Device Flag assertions, but flag denials are scheduled with a
   delay corresponding to the printer operation time.  The operations are as
   follows:

                                         Diagnostic Service  Diagnostic Service
     State             Printer Service    Flag follows Cmd   Flag follows cont.6
     ----------------  ----------------  ------------------  -------------------
     Device_Command_1  set Flag          set Flag            wait for Control.6
     Device_Flag_1     wait for service  clear Flag          wait for Control.6
     Device_Command_2  set Flag          set Flag            wait for Control.6
     Device_Flag_2     wait for service  clear Flag          wait for Control.6

   If the device asserts the DEV END signal in response to Device Command, the
   Device End flip-flop is set, and the sequencer is reset back to the Idle
   state to abort the transfer.  DEV END assertion in any other state is ignored
   until Device Command is set.

   If jumper W3 is installed, DEV CMD is pulsed for 8 microseconds by asserting
   Device Command and scheduling the pulse timer to deny it when the event timer
   expires.

   A DWRITESTB or PWRITESTB signal stores a 16-bit value in the data output
   register.  In word mode, the value is presented continuously on the 16 DATA
   OUT lines.  In byte mode, the upper byte in the data output register is
   presented on both bytes of the DATA OUT lines until the Device Flag sets to
   indicate that the device has accepted the first byte, whereupon the full
   16-bit value is presented on the DATA OUT lines.  The result is that the
   upper byte and then the lower byte appears on the lower byte of the DATA OUT
   lines.

   During byte-mode read cycles, the previously stored full 16-bit output value
   is presented on the DATA OUT lines if J2W7 is removed.  If J2W7 is installed,
   the upper byte and then the lower byte appears on the lower byte.  In other
   words, a byte read with J2W7 installed causes the DATA OUT lines to assume
   the same values in sequence that occur during a byte write.  This is used by
   the diagnostic to test the DATA OUT multiplexer.

   A read is initiated by the READNEXTWD signal or by setting the Acquire bit in
   the control word.  Device Command sets in response.  While Device Command is
   set, the data input register is transparent and passes the value on the Data
   In lines through.  When Device Flag sets, the value on the DATA IN lines is
   latched in the register.  A DREADSTB or PREADSTB signal then enables the
   register onto the IOP Data bus.  With J2W5 installed, the data in register is
   always transparent, and a DREADSTB or PREADSTB signal presents the current
   value on the DATA IN lines to the IOP Data bus.

   In word mode with J2W5 removed, 16-bit data presented at the DATA IN lines is
   passed through the data input register while Device Command is set and is
   latched when the Device Flag sets.  In byte mode with J2W5 removed, the value
   presented on the lower byte of the DATA IN lines is presented to both bytes
   of the data input register, passed through while Device Command is set, and
   latched into both bytes of the register when the Device Flag sets to indicate
   that the device has supplied the first byte.  When Device Command sets for
   the second byte, the value presented on the lower byte of the DATA IN lines
   is presented to the lower byte of the data input register, passed through
   while Device Command is set, and latched into the lower byte when the Device
   Flag sets to indicate that the device has supplied the second byte.  The
   result is that the data input register presents the first byte in both bytes
   of the register and then the second byte presents as the lower byte of the
   register, resulting in a packed 16-bit value.


   Implementation notes:

    1. In hardware, the sequencer moves from state 2 through state 3 to state 0
       when the device flag denies at the end of a word transfer.  For a packed
       byte transfer, the sequencer moves from state 3 to state 0 when the
       device flag asserts for the second byte, with logic holding off the
       "operation done" signal until the flag denies.

       In simulation, the sequencer moves on flag denial directly from
       Device_Flag_1 to Idle for a word transfer and on flag assertion from
       Device_Command_2 to Device_Flag_2 and then on flag denial to Idle for a
       second byte transfer.  The sequence count reported in a status return is
       0 for Device_Flag_2, preserving the appearance of returning to state 0
       while the internal Device_Flag_2 state holds off the "operation done"
       signal.

    2. In hardware, a DEV END signal asserts the Q2 and Q3 qualifiers, enabling
       the sequence counter to proceed through the state sequence back to the
       idle state.  In simulation, the sequencer state is set directly back to
       Idle.

    3. In hardware, with jumper W5 out, the DATA IN latches are transparent in
       the Device_Command_1 and Device_Command_2 states and are latched
       otherwise, i.e., when Device Flag asserts.  With jumper W5 in, the
       latches are transparent always, and a read gets the real-time state of
       the DATA IN lines.  In simulation, the read register is set when Device
       Flag asserts; transparency is not simulated.

    4. The diagnostic tests the byte unpacking and packing multiplexers on the
       DATA OUT and DATA IN lines, so we must simulate the multiplexing
       accurately with respect to the intermediate values before the handshake
       is complete.

    5. The sequencer loop is used only during a device end assertion to move
       from Idle to Device_Command_1 and back to Idle.  All other transitions
       involve unit activation and so exit this routine after the sequence state
       is changed.
*/

static OUTBOUND_SET handshake_xfer (void)
{
const SEQ_STATE entry_state      = sequencer;           /* the state of the sequencer at entry */
t_bool          reset            = FALSE;               /* TRUE if the sequencer is reset */
OUTBOUND_SET    outbound_signals = NO_SIGNALS;
SEQ_STATE       last_state;

do {                                                    /* run the sequencer as long as it advances */
    last_state = sequencer;                             /* save the last state to see if it changes */

    if (sequencer < Device_Command_2                    /* if this is the first byte */
      && control_word & CN_BYTE_XFER                    /*   of a byte transfer */
      && (J2W7_INSTALLED || write_xfer))                /*     and W7 is installed or it's a write transfer */
        data_out = write_word & ~D8_MASK                /*       then the upper 8 bits appear */
                      | UPPER_BYTE (write_word);        /*         in both bytes */
    else                                                /* otherwise */
        data_out = write_word;                          /*   the full 16 bits appear */


    switch (sequencer) {                                /* dispatch the current state */

        case Idle:
            if (device_command == SET) {                /* if device command has been set */
                sequencer = Device_Command_1;           /*   then proceed to the next state */

                if (device_end_in                       /* if external device end asserts */
                  && (read_xfer || write_xfer))         /*   during a transfer */
                    device_command = CLEAR;             /*     then device command is inhibited */

                else {                                  /* otherwise */
                    if (J2W3_INSTALLED)                 /*   if jumper W3 (pulse mode) is installed */
                        activate_unit (pulse_uptr);     /*     then schedule device command denial */

                    activate_unit (xfer_uptr);          /* schedule device flag assertion */
                    }
                }
            break;


        case Device_Command_1:
            if (device_end_in)                          /* if external device end asserts */
                if (read_xfer || write_xfer) {          /*   then if a transfer is in progress */
                    device_end = SET;                   /*     then set the Device End flip-flop to abort */

                    device_command = CLEAR;             /* clear the device command */
                    read_xfer      = CLEAR;             /*   and read transfer  */
                    write_xfer     = CLEAR;             /*     and write transfer flip-flops */

                    sequencer = Idle;                   /* idle the sequencer */
                    reset     = TRUE;                   /*   and indicate that it was reset */

                    device_sr = SET;                    /* request channel service */
                    break;
                    }

                else                                    /* otherwise no transfer is in progress */
                    device_end_in = FALSE;              /*   so clear the signal */

            if (device_flag == SET) {                   /* if the device flag has been set */
                sequencer = Device_Flag_1;              /*   then proceed to the next state */
                device_command = CLEAR;                 /*     and deny device command */

                activate_unit (xfer_uptr);              /* schedule device flag denial */

                if (control_word & CN_BYTE_XFER)            /* if this is a byte transfer */
                    read_word = TO_WORD (data_in, data_in); /*   then the lower 8 bits appear in both bytes */
                else                                        /* otherwise */
                    read_word = data_in;                    /*   the full 16 bits appear */

                if (J2W1_INSTALLED && sio_busy          /* if jumper W1 (status drives SR) is installed */
                  && dev_status_word & ST_ST11_SR)      /*   and a transfer is in progress with status 11 set */
                    device_sr = SET;                    /*     then request channel service */
                }
            break;


        case Device_Flag_1:
            if (device_flag == CLEAR)                   /* if the device flag has been cleared */
                if (control_word & CN_BYTE_XFER) {      /*   then if this is a byte transfer */
                    sequencer = Device_Command_2;       /*     then proceed to the next state */
                    device_command = SET;               /*       and assert device command for the second byte */

                    data_out = write_word;              /* latch the output word */

                    activate_unit (xfer_uptr);          /* schedule device flag assertion */
                    }

                else {                                  /* otherwise the transfer is complete */
                    read_xfer  = CLEAR;                 /*   so clear the read transfer  */
                    write_xfer = CLEAR;                 /*     and write transfer flip-flops */

                    sequencer = Idle;                   /* idle the sequencer */
                    device_sr = SET;                    /*   and request channel service */

                    if (control_word & CN_XFR_IRQ_ENABLE)               /* if a transfer interrupt is requested */
                        outbound_signals |= set_interrupt (ST_XFR_IRQ); /*   then set the transfer interrupt flip-flop */
                    }
            break;


        case Device_Command_2:
            if (device_flag == SET || device_end_in) {  /* if the device flag or external device end has been set */
                sequencer = Device_Flag_2;              /*   then proceed to the next state */
                device_command = CLEAR;

                activate_unit (xfer_uptr);              /* schedule device flag denial */

                read_word &= ~D8_MASK;                  /* clear the lower byte */

                if (device_end_in == FALSE)             /* if the transfer succeeded */
                    read_word |= LOWER_BYTE (data_in);  /*   then merge the received lower byte */
                }
            break;


        case Device_Flag_2:
            if (device_flag == CLEAR) {                 /* if the device flag was cleared */
                read_xfer  = CLEAR;                     /*   then clear the read transfer  */
                write_xfer = CLEAR;                     /*     and write transfer flip-flops */

                sequencer = Idle;                       /* idle the sequencer */
                device_sr = SET;                        /*   and request channel service */

                if (control_word & CN_XFR_IRQ_ENABLE)               /* if a transfer interrupt is requested */
                    outbound_signals |= set_interrupt (ST_XFR_IRQ); /*   then set the transfer interrupt flip-flop */
                }
            break;
        }                                               /* end of state dispatching */
    }
while (sequencer != last_state);                        /* continue as long as the sequence is progressing */


if (TRACING (lp_dev, DEB_STATE))
    if (sequencer != entry_state)
        hp_trace (&lp_dev, DEB_STATE, "Sequencer transitioned from the %s state to the %s state\n",
                  state_name [entry_state], state_name [sequencer]);

    else if (reset && device_end)
        hp_trace (&lp_dev, DEB_STATE, "Sequencer reset by device end\n");

if (device_sr && sio_busy)                              /* if the interface has requested service */
    outbound_signals |= SRn;                            /*   then assert SRn to the channel */

return outbound_signals;                                /* return the accumulated signals */
}



/* Diagnostic Hardware Assembly local SCP support routines */



/* Service the transfer handshake for the Diagnostic Hardware Assembly.

   The DHA loops the data out lines back to the data in lines, with bits 11-15
   also connecting to bits 11-15 of the status in lines.  The DHA also may be
   configured to connect either the DEV CMD output or the CONT 6 output to the
   DEV FLAG input.


   Implementation notes:

    1. The DHA transfer service is called with a null pointer to update the
       potential change in the flag state.
*/

static t_stat diag_service (UNIT *uptr)
{
if (uptr)                                               /* trace only if this is a handshake entry */
    tprintf (lp_dev, DEB_SERV, "%s state transfer service entered\n",
             state_name [sequencer]);

if (dha_control_word & DHA_FLAG_SEL)                    /* if in "flag follows control 6" mode */
    device_flag_in = (control_word & CN_DHA_FLAG) != 0; /*   then set the flag from control word bit 6 */
else                                                    /* otherwise */
    device_flag_in = device_command_out;                /*   device flag is connected to device command */

data_in = data_out;                                     /* data in is connected to data out */

set_device_status (ST_DHA_DEVSTAT_MASK, data_out);      /* status bits 11-15 are connected to data out */

return SCPE_OK;
}



/* Diagnostic Hardware Assembly local utility routines */



/* Diagnostic hardware assembly reset.

   When the MASTER CLEAR signal is asserted to the DHA, the master reset bit in
   the DHA control word is set.  In addition, the status bits connected to the
   DATA OUT lines from the interface are cleared, as the interface has cleared
   its output register.

   If this reset was caused by a RESET or RESET LP command, the set of installed
   jumpers in the DHA control word is updated.  This picks up any jumper changes
   made at the user interface.


   Implementation notes:

    1. The DHA transfer service is called with a null pointer to update the
       potential change in the DEV FLAG state that may have occurred by a change
       to the DEV CMD state if the lines are connected.
*/

static t_stat diag_reset (t_bool programmed_clear)
{
if (programmed_clear) {                                 /* if this is a programmed master clear */
    dha_control_word |= DHA_MR;                         /*   then record the master reset */

    set_device_status (ST_DHA_DEVSTAT_MASK, data_out);  /* clear the status bits connected to data out */

    xfer_service (NULL);                                /* update the current device flag state */
    }

else                                                        /* otherwise this is a commanded reset */
    dha_control_word = dha_control_word & DHA_JUMPER_MASK   /*   so refresh the DHA control word */
                         | jumper_set;                      /*     from the jumpers */

return SCPE_OK;
}


/* Process the diagnostic hardware assembly control word.

   This routine is called when a DCONTSTB or PCONTSTB assertion indicates that
   the control word is to be set.  If bit 10 is set, then bits 6-9 represent an
   encoded action to be taken by the DHA.  Two of the actions potentially change
   the state of the device status lines, which may also generate an interrupt if
   properly configured and enabled.  In addition, the DEV FLAG signal may
   change, depending on the state of the "flag follows control bit 6" action,
   which may cause the handshake sequencer to change states.


   Implementation notes:

    1. The jumpers part of the DHA control word is "cleared" to all ones, which
       corresponds to installing all of the jumpers.

    2. The DHA transfer service is called with a null pointer to update the
       potential change in the flag state.

    3. Setting bit 2 of the DHA control word reflects the current state of the
       PON and ~PFWARN signals in status bits 9 and 10, respectively.  Status 9
       is always set, as PON is always active while the machine is operating.
       Status 10 is normally set to indicate that PFWARN is denied.  However, if
       the system power is failing, PFWARN is asserted from detection until
       power is lost.
*/

static OUTBOUND_SET diag_control (uint32 control_word)
{
uint32       new_status;
OUTBOUND_SET outbound_signals = NO_SIGNALS;

if (control_word & CN_DHA_FN_ENABLE)                    /* if the decoder is enabled */
    switch (CN_DHA_FN (control_word)) {                 /*   then decode the DHA command */

        case 0:                                         /* clear the registers */
            dha_control_word = DHA_CLEAR;               /* initialize the DHA control word */
            jumper_set = DHA_CLEAR & DHA_JUMPER_MASK;   /*   and install all of the jumpers */
            break;

        case 2:                                         /* assert the Device End signal */
            device_end_in = TRUE;                       /* set the external device end line */
            break;

        case 4:                                                 /* set the Transfer Error flip-flop */
            outbound_signals = set_interrupt (ST_XFERERR_IRQ);  /* set the transfer error interrupt flip-flop */
            break;

        case 8:                                         /* connect the device flag to control bit 6 */
            dha_control_word |= DHA_FLAG_SEL;           /* set the "flag follows control 6" bit */
            break;

        case 10:                                                /* assert the Clear Interface signal */
            clear_interface_logic ();                           /* clear the interface logic */
            outbound_signals = set_interrupt (ST_CLRIF_IRQ);    /*   and set the clear interface interrupt flip-flop */
            break;

        case 12:                                        /* connect status 8-10 to master clear/power on/power fail */
            dha_control_word |= DHA_STAT_SEL;           /* set the "status follows master clear-power on-power fail" bit */
            break;

        default:                                                        /* remove a jumper */
            dha_control_word &= jumper_map [CN_DHA_FN (control_word)];  /* clear the specified control register bit */
            jumper_set = dha_control_word & DHA_JUMPER_MASK;            /*   and remove the indicated jumper */
            break;
        }


if (dha_control_word & DHA_STAT_SEL) {                  /* if status follows master clear/power on/power fail */
    new_status = ST_DHA_PON;                            /*   then indicate that power is on */

    if (power_warning == FALSE)                         /* if we have seen a PFWARN signal */
        new_status |= ST_DHA_NOT_PF;                    /*   then indicate that power has not failed */

    if (dha_control_word & DHA_MR)                      /* if a master reset is requested */
        new_status |= ST_DHA_MR;                        /*   then indicate a master clear */
    }

else                                                    /* otherwise set the device status */
    new_status = ST_DEVIRQ (CN_DHA_ST (control_word));  /*   from the connected DHA control bits */

outbound_signals |= set_device_status (ST_DEVIRQ_MASK, new_status); /* set the status and test for IRQs */

xfer_service (NULL);                                    /* record the current device flag state */

outbound_signals |= handshake_xfer ();                  /* check for a device handshake transition */

return outbound_signals;                                /* return INTREQ if any interrupts were requested */
}



/* Printer local SCP support routines */



/* Service the transfer handshake for 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 (DEV CMD) to the printer.  The
   printer responds by denying DEMAND (asserting DEV FLAG).  The interface then
   denies STROBE and waits for the printer to reassert DEMAND (deny DEV FLAG) 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 with Device Command set and then after a variable delay
   with Device Command clear.  In response to the former call, the routine sets
   the Device Flag, 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 clears the Device Flag and then
   clears the event delay time, so that the routine will be reentered
   immediately when Device Command sets again.

   If a SET LP OFFLINE command or a DETACH LP 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 the 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, this routine is called to assert DEMAND and
   conclude the handshake.

   Control word bit 10 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. 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.

    3. 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.

    4. 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.

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

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

    7. The transfer service may be called with a null pointer to update the
       potential change in the flag state.

    8. If printing is attempted with the printer offline, this routine will be
       called with STROBE asserted (device_command_in TRUE) and DEMAND denied
       (device_flag_in TRUE).  The printer ignores STROBE if DEMAND is not
       asserted, so we simply return in this case.  This will hang the handshake
       until the printer is set online, and we are reentered with DEMAND
       asserted.  As a consequence, explicit protection against "uptr->fileref"
       being NULL is not required.

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

static t_stat lp_service (UNIT *uptr)
{
const t_bool  printing = ((control_word & CN_FORMAT) != 0);    /* TRUE if a print command was received */
static uint32 overprint_index = 0;
PRINTER_TYPE  model;
uint8         data_byte, format_byte;
uint32        channel, line_count, slew_count, vfu_status;

if (uptr == NULL)                                       /* if we're called for a state update */
    return SCPE_OK;                                     /*   then return with no other action */
else                                                    /* otherwise */
    model = GET_MODEL (uptr->flags);                    /*   get the printer type */

tprintf (lp_dev, DEB_SERV, "%s state printer service entered\n",
         state_name [sequencer]);

if (device_command_out == FALSE) {                      /* 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 any 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 (lp_dev, DEB_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;
            }
        }

    device_flag_in = FALSE;                             /* assert DEMAND to complete the handshake */
    uptr->wait = 0;                                     /*   and request direct entry when STROBE next asserts */
    }

else if (device_flag_in == FALSE) {                     /* otherwise if STROBE has asserted while DEMAND is asserted */
    device_flag_in = TRUE;                              /*   then deny DEMAND */

    data_byte = (uint8) (data_out & DATA_MASK);         /* only the lower 7 bits are connected */

    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 (lp_dev, DEB_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 (lp_dev, DEB_CMD, "Buffer overflow printed %u characters on line %u\n",
                     buffer_index, current_line);

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

            uptr->pos += sizeof buffer [0]                  /* write the buffer to the printer file */
              * (t_addr) fwrite (buffer, sizeof buffer [0], /*   and update the file position */
                                 buffer_index, uptr->fileref);

            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 (lp_dev, DEB_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 (lp_dev, DEB_XFER, "Character %s sent to printer\n",
                     fmt_char (data_byte));
            }

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

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

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

        format_byte = data_byte & FORMAT_MASK;          /* format commands ignore bits 10-11 */

        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 (lp_dev, DEB_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 line is longer than the overprinted line */
                    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 (lp_dev, DEB_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 (lp_dev, DEB_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;                   /* 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 (lp_dev, DEB_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 */
            uptr->pos += sizeof buffer [0]                  /*   then write the buffer to the printer file */
              * (t_addr) fwrite (buffer, sizeof buffer [0], /*     and update the file position */
                                 buffer_index, uptr->fileref);

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

        vfu_status = 0;                                 /* 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 */
                vfu_status |= 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 */
                vfu_status |= ST_VFU_12;                /*   then report it in the device status */
            }

        set_device_status (ST_VFU_9 | ST_VFU_12, vfu_status);   /* set the VFU 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 */

        if (slew_count > 0)
            tprintf (lp_dev, DEB_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 */
        }
    }

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


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

    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, char *cptr)
{
t_stat result;

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

if (result == SCPE_OK                                   /* if the attach was successful */
  && not (sim_switches & SIM_SW_REST)) {                /*   and we are not being called during a RESTORE command */
    set_device_status (ST_NOT_READY, 0);                /*     then clear not-ready status */

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

    tprintf (lp_dev, DEB_CMD, "Printer paper loaded\n");

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

paper_fault = FALSE;                                    /* clear any existing paper fault */

if (lp_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.  The transition from online to offline causes an
   interrupt.

   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 LP 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 all three units,
       not just 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_ATTABLE)                         /* if we're being called for the printer unit */
    if ((uptr->flags & UNIT_ATT) == 0)                  /*   then if the unit is not currently attached */
        return SCPE_UNATT;                              /*     then report it */

    else {                                                  /* otherwise */
        if (sim_switches & (SWMASK ('F') | SIM_SW_SHUT)) {  /*   if this is a forced detach or shut down request */
            if (buffer_index != 0) {                        /*     then if the buffer is not empty */
                if (buffer [buffer_index - 1] != LF         /*       then if the last buffer character */
                  && buffer [buffer_index - 1] != FF) {     /*         is not a line feed or form feed */
                    buffer [buffer_index++] = CR;           /*           then tie off */
                    buffer [buffer_index++] = LF;           /*             the current buffer */
                    }

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

                buffer_index = 0;                           /* clear the buffer index to permit detaching */
                }

            current_line = 1;                               /* reset the printer to TOF to enable detaching */
            sim_cancel (uptr);                              /*   and terminate */
            device_command_out = FALSE;                     /*     any print action in progress */
            }

        if ((print_props [model].fault_at_eol           /* 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 (lp_dev, DEB_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 */
            offline_pending = TRUE;                     /*     but defer the detach */

            tprintf (lp_dev, DEB_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 */
            }
        }

else                                                    /* otherwise */
    return SCPE_UNATT;                                  /*   we've been called for the wrong unit */
}


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


   Implementation notes:

    1. Switching between printer and diagnostic mode sets the configuration
       jumpers accordingly.

    2. Switching between printer and diagnostic mode clears the event delay.
       This is necessary in case the command was entered while an event was
       queued.
*/

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

    case Fast_Time:                                     /* entering optimized timing mode */
        lp_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 */
        lp_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 */
        lp_dev.flags &= ~DEV_DIAG;                      /*   so clear the diagnostic flag */
        xfer_unit.wait = 0;                             /*     and clear any event delay that had been set */

        jumper_set = PRINTER_JUMPERS;                   /* set the jumpers for printer operation */
        break;


    case Diagnostic:                                    /* entering diagnostic mode */
        lp_dev.flags |= DEV_DIAG;                       /*   so set the diagnostic flag */
        xfer_unit.wait = 0;                             /*     and clear any event delay that had been set */

        jumper_set = dha_control_word & DHA_JUMPER_MASK;    /* set the jumpers for DHA operation */
        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, char *cptr, void *desc)
{
if (lp_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,
   DEMAND is denied, and DEV END is asserted 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.  If the sequencer
   indicates an incomplete handshake, as would occur if paper ran out while
   printing, the transfer service is called to complete the handshake by
   asserting DEMAND.  Otherwise, DEMAND is asserted explicitly, and DEV END is
   denied.

   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.

   Transitions between the offline and online state cause interrupts, and INTREQ
   is asserted to the IOP if a transition occurred (but not, e.g., for a SET LP
   OFFLINE command where the printer is already offline).


   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, 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 (lp_dev, DEB_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 LP VFU
     SET LP 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, 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, void *desc)
{
fprintf (st, "%s timing, %s mode",                      /* print the timing and connection modes */
         (lp_dev.flags & DEV_REALTIME ? "realistic" : "fast"),
         (lp_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 LP command was given and 1 if a SHOW LP 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, 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 */



/* Printer reset.

   This routine is called when the MASTER CLEAR signal is asserted to the
   printer.  The "programmed_clear" parameter is TRUE if the routine is called
   for a Programmed Master Clear or IORESET assertion, and FALSE if the routine
   is called for a RESET or RESET LP command.  In the latter case, the presence
   of the "-P" switch indicates that this is a power-on reset.  In either case,
   the interface reset has already been performed; this routine is responsible
   for resetting the printer only.

   In hardware, asserting MASTER CLEAR:

     - clears the input buffer without printing
     - finishes printing the current line and performs any stored VFU action
     - inhibits DEMAND for approximately three milliseconds

   In simulation, the buffer index is reset, a tape fault is cleared, a paper
   fault is determined by the paper status, and any deferred offline command is
   cleared.  Printing is always "complete" at the point of entry, as the actual
   file write is instantaneous from the simulation perspective.  DEMAND is not
   altered, as the printer is "ready" as soon as the command completes.  DEV END
   is reset to the offline status (asserted if the printer is offline, denied if
   online).

   In addition, if a power-on reset (RESET -P) is done, the original FASTTIME
   settings are restored, the standard VFU tape is loaded, and the power failure
   warning is cleared.
*/

static t_stat lp_reset (t_bool programmed_clear)
{
const PRINTER_TYPE model = GET_MODEL (xfer_unit.flags); /* the printer model number */
OUTBOUND_SET signals;
uint32       new_status = 0;
t_stat       result     = SCPE_OK;

if (! programmed_clear && (sim_switches & SWMASK ('P'))) {  /* if this is a commanded 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 */

    result = lp_load_vfu (xfer_uptr, NULL);                 /* load the standard VFU tape */

    power_warning = FALSE;                                  /* clear the power failure warning */
    }

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 = ! (xfer_unit.flags & UNIT_ATT);           /*   and set paper fault if out of paper */

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

if (xfer_unit.flags & UNIT_OFFLINE) {                   /* if the printer is offline */
    device_flag_in = TRUE;                              /*   then DEMAND denies while the printer is not ready */
    device_end_in  = TRUE;                              /*     and DEV END asserts while the printer is offline */
    }

else {                                                  /* otherwise the printer is online */
    new_status |= ST_ONLINE;                            /*   so set online status */

    device_flag_in = FALSE;                             /* DEMAND asserts when the printer is ready */
    device_end_in  = FALSE;                             /*   and DEV END denies when the printer is online */
    }

xfer_service (NULL);                                    /* tell the data transfer service that signals have changed */

signals = set_device_status (ST_ONLINE | ST_NOT_READY,  /* set the new device status */
                             new_status);

if (signals & INTREQ)                                   /* if the status change caused an interrupt */
    iop_assert_INTREQ (&lp_dib);                        /*   then assert the INTREQ signal */

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


/* Process the printer control word.

   This routine is called when a DCONTSTB or PCONTSTB assertion indicates that
   the control word is to be set.  No direct action is taken by the printer in
   response, so the routine simply returns.
*/

static OUTBOUND_SET lp_control (uint32 control_word)
{
return NO_SIGNALS;                                      /* no special control action is needed */
}


/* 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 */
        set_device_status (ST_NOT_READY, ST_NOT_READY); /*     then set the printer not-ready */

    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.

   The printer cable inversely connects DEMAND to the Device Flag input and
   ONLINE to the Device End input.  As both deny when the printer goes offline
   and assert when the printer goes online, Device Flag and Device End assert
   and deny, respectively.

   If the printer goes offline with an operation in progress, Device Flag will
   remain asserted, and the handshake sequencer will remain in the Device_Flag_1
   or Device_Flag_2 state until the printer is set online again.  The transfer
   service routine is informed of these state changes, so that the handshake can
   complete when the printer is again set online.


   Implementation notes:

    1. When called with a NULL parameter, the transfer service routine will
       update its internal device flag state but will take no other action.
       When called with a unit pointer, the routine continues the handshake
       sequence.
*/

static t_bool lp_set_locality (UNIT *uptr, LOCALITY printer_state)
{
OUTBOUND_SET signals;

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

        signals = set_device_status (ST_ONLINE, 0);     /* update the printer status */

        device_flag_in = TRUE;                          /* DEMAND denies while the printer is offline */
        device_end_in  = TRUE;                          /* DEV END asserts while the printer is offline */

        xfer_service (NULL);                            /* inform the service routine of the signal changes */
        }

    else {                                              /*   otherwise the request must wait */
        offline_pending = TRUE;                         /*     until the line is printed */
        return FALSE;                                   /*       and 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 */

    signals = set_device_status (ST_ONLINE | ST_NOT_READY,  /* set online status */
                                 ST_ONLINE);                /*   and clear not ready status */

    device_flag_in = FALSE;                             /* DEMAND asserts when the printer is online */
    device_end_in  = FALSE;                             /*   and DEV END denies when the printer is online */

    if (sequencer != Idle)                              /* if the transfer handshake is in progress */
        xfer_service (uptr);                            /*   then complete the suspended operation */
    else                                                /* otherwise */
        xfer_service (NULL);                            /*   inform the service routine of the signal changes */
    }

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

if (signals & INTREQ)                                   /* if the transition caused an interrupt */
    iop_assert_INTREQ (&lp_dib);                        /*   then assert the INTREQ signal */

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.

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

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, vfu_status;
int32              len;
char               buffer [LINE_SIZE], punch [LINE_SIZE], no_punch;
char               *bptr, *tptr;
uint32             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 */
        }

    form_length = 66;                                   /* set the form length */
    strcpy (vfu_title, "Standard VFU");                 /*   and set the title */
    }

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

vfu_status = 0;                                         /* 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 */
        vfu_status |= ST_VFU_9;                         /*     then report it in the device status */

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

set_device_status (ST_VFU_9 | ST_VFU_12, vfu_status);   /* set the VFU 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;
}
