blob: a09e1460a7271faa1ebcebb39e622aba040c09ca [file] [log] [blame] [raw]
/* hp2100_lpt.c: HP 2100 12845B Line Printer Interface simulator
Copyright (c) 1993-2016, Robert M. Supnik
Copyright (c) 2017, J. David Bryan
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Except as contained in this notice, the names of the authors shall not be
used in advertising or otherwise to promote the sale, use or other dealings
in this Software without prior written authorization from the authors.
LPT HP 12845B Line Printer Interface
16-May-17 JDB Changed REG_A to REG_X
07-Feb-17 JDB Passes the 2613/17/18 diagnostic (DSN 145103)
04-Feb-17 JDB Rewrote to add the HP 2613/17/18 line printers
13-May-16 JDB Modified for revised SCP API function parameter types
10-Feb-12 JDB Deprecated DEVNO in favor of SC
28-Mar-11 JDB Tidied up signal handling
26-Oct-10 JDB Changed I/O signal handler for revised signal model
26-Jun-08 JDB Rewrote device I/O to model backplane signals
Changed CTIME register width to match documentation
22-Jan-07 RMS Added UNIT_TEXT flag
28-Dec-06 JDB Added ioCRS state to I/O decoders
19-Nov-04 JDB Added restart when set online, etc.
29-Sep-04 JDB Added SET OFFLINE/ONLINE, POWEROFF/POWERON
Fixed status returns for error conditions
Fixed TOF handling so form remains on line 0
03-Jun-04 RMS Fixed timing (found by Dave Bryan)
26-Apr-04 RMS Fixed SFS x,C and SFC x,C
Implemented DMA SRQ (follows FLG)
25-Apr-03 RMS Revised for extended file support
24-Oct-02 RMS Cloned from 12653A
References:
- 12845B Line Printer Interface Kit Installation and Service Manual
(12845-90011, January 1982)
- HP 2607 Line Printer Diagnostic
(12987-90004, September 1974)
- 2613A/2617A/2618A Line Printer Diagnostic Operator's Manual
(02618-90006, November 1977)
- RTE Line Printer Driver (DVA12) Reference Manual
(92001-90010, October 1980)
The HP 12845B Line Printer Interface Kit connects the 2607A, 2613A, 2617A,
and 2618A printers to the HP 1000 family. Each subsystem consists of an
interface card employing differential line drivers and receivers, an
interconnecting cable, and an HP 2607A (200 lines per minute), HP 2613 (300
lpm), HP 2617 (600 lpm), or HP 2618 (1250 lpm) line printer. The interface
is supported by RTE driver DVA12 and DOS driver DVR12. The interface
supports DMA transfers, but the OS drivers do not use them.
Two versions of this interface were produced. The 12845A interface is
designed to connect the HP 2610 and HP 2614 line printers. The 12845B
interface supports these two printers, plus the 2607, 2613, 2617, 2618, 2619,
and 2631. The "A" card does not permit DMA transfers, whereas the "B" card
does. The latter provides a configuration jumper, "STR", that selectively
delays the strobe to the printer. This is needed because DMA output cycles
assert IOO and STC in same cycle, i.e., data is coincident with strobe, and
this is insufficient for the 2607, 2610 and 2614. Placing "STR" in position
2 delays the strobe for one cycle. DMA with other printers and all non-DMA
operation uses "STR" in position 1.
The 12845B outputs seven data bits to the printer plus these control signals:
- Master Clear
- Paper Instruction
- Strobe
Master Clear is asserted by CRS and clears the line printer buffer and aborts
a paper advance in progress (except on the 2613). It does not abort a print
cycle in progress. Paper Instruction asserts to indicate that the data lines
contain a format word that commands a print-and-slew operation. Strobe
asserts to indicate that the signals on the data lines are valid.
The interface receives these status signals from the printer:
- Demand
- Buffer Ready
- Printer Ready
- Printer Online
- VFU Channel 9
- VFU Channel 12
Demand denies when the printer is busy and asserts to indicate that the
printer has completed the requested operation. Denial causes Strobe to
deny. Assertion sets the flag buffer flip-flop if Buffer Ready is also
asserted.
Buffer Ready denies when the printer is printing and asserts when printing
completes and the print buffer may be loaded. Buffer Ready and Demand are
reflected in bit 0 of the status word provided to the CPU. The 2607 has
Buffer Ready permanently asserted (it is double-buffered and can accept
characters while printing).
Printer Ready asserts when power is applied and no fault condition exists.
It is inverted (i.e., to indicate Not Ready) and presented as bit 14 of the
status word.
Printer Online asserts while the printer is online. It is reflected in bit
15 of the status word.
VFU Channel 9 and VFU channel 12 assert when the paper is positioned at the
print line corresponding to holes punched in the associated VFU tape
locations. They are presented as status word bits 13 and 12, respectively.
The interface status word contains these values for the 2613/17/18 printers:
Value Meaning
------ -------------------------------------
140001 Power off or cable disconnected
100001 Power on, paper loaded, printer ready
100000 Power on, paper loaded, printer busy
000000 Power on, print button up
040000 Power on, paper out or drum gate open
The 2607 status word values are:
Value Meaning
------ -----------------------------------------------------
140001 Power off or cable disconnected
100001 Power on, paper loaded, printer ready
100000 Power on, paper loaded, printer busy
000000 Power on, paper out or print button up or platen open
The simulator supports a single line printer. The supported printers are
configured with Option 001, which provides a 128 (2607) or 96 (2613/17/18)
character set. Two output modes are provided: an expanded mode that is
suitable for retaining printer output as a text file, and a compact mode that
is suitable for sending the printer output to a host-connected physical
printer. An 8-channel (2607) or 12-channel (2613/17/18) Vertical Format Unit
is supported, and custom VFU tape images may be loaded from properly
formatted host-system text files.
The printer supports realistic and optimized (fast) timing modes. Realistic
timing attempts to model the print buffer load and print-and-space operation
delays inherent in the physical hardware. For example, in REALTIME mode,
output of longer lines takes more time than output of shorter lines, and
spacing six lines takes approximately six times longer than spacing one line.
In FASTTIME mode, all timings are reduced to be "just long enough" to satisfy
the software driver requirements.
Attaching the LPT device simulates loading paper into the printer. Detaching
simulates removing or running out of paper.
The printer may be set offline or online. It may also be powered off or on
to accommodate the interface diagnostic. A diagnostic mode is provided to
install the 02613-80002 or 02618-80002 VFU tape. This is the standard VFU
tape with channel 10 punched for line 59 (BOF - 1), channel 11 punched for
line 66 (TOF - 1), and channel 12 punched for line 1 (TOF).
When the ON/OFFLINE button on the printer is pressed, the printer will not go
offline (i.e., deny the ONLINE signal) if there are characters in the print
buffer. Instead, the offline condition is held off until an internal "allow
offline" signal asserts. This occurs when the print buffer is empty and the
print cycle is inactive.
When a "paper out" condition occurs, the 2613/17/18 printers will go offline
at the end of the current line. The 2607 printer waits until the top-of-form
is seen before going offline. This has implications for the SET OFFLINE and
DETACH commands if they are issued while the print buffer contains data or
the printer unit is busy executing a print action.
The SET LPT OFFLINE and DETACH LPT commands check for data in the print
buffer or a print operation in progress. If either condition is true, they
set their respective deferred-action flags and display "Command not
completed." A SHOW LPT will show that the device is still online and
attached. Once simulation is resumed and the print operation completes, the
printer is set offline or detached as requested. No console message reports
this, as it is assumed that the executing program will detect the condition
and report accordingly. A subsequent SHOW LPT will indicate the new status.
A SET LPT ONLINE command when a deferred-action flag is set simply clears the
flag, which cancels the pending offline or detach condition.
A RESET LPT command also clears the deferred-action flags and so clears any
pending offline or detach. However, it also clears the print buffer and
terminates any print action in progress, so a SET LPT OFFLINE or DETACH LPT
will succeed if issued subsequently.
An immediate detach may be forced by the DETACH -F LPT command. This
simulates physically removing the paper from the printer and succeeds
regardless of the current printer state.
The interface responds to I/O instructions as follows:
Output Data Word format (OTA and OTB):
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 | - - - - - - - - | ASCII character | character
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 1 | - - - - - - - - | format word | format
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
The printers use only seven data bits, so the MSB of each byte is ignored.
If the printer's line length is exceeded during write operations, the
buffered line will be printed, the paper will be advanced one line, and the
buffer will be cleared to accept the character causing the overflow.
The format commands recognized by the printers are:
0 0 0 0 0 0 0 -- slew 0 lines (suppress spacing) after printing *
...
0 0 0 1 1 1 1 -- slew 15 lines after printing
0 0 1 0 0 0 0 -- slew 16 lines after printing **
...
0 1 1 1 1 1 1 -- slew 63 lines after printing **
* slew 1 line on the 2607A, which cannot suppress printing.
** available only on the 2610A and 2614A
and:
1 x x 0 0 0 0 -- slew to VFU channel 1 after printing
...
1 x x 0 1 1 1 -- slew to VFU channel 8 after printing
1 x x 1 0 0 0 -- slew to VFU channel 9 after printing *
...
1 x x 1 0 1 1 -- slew to VFU channel 12 after printing *
* available only on the 2613A, 2617A, and 2618A
Input Data Word format (LIA, LIB, MIA, and MIB):
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| L | R | V | U | - - - - - - - - - - - | D |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
Where:
L = Online
R = Not ready
V = VFU channel 9
U = VFU channel 12
D = Demand
Implementation notes:
1. The hardware STR jumper that delays the strobe for one cycle for DMA
compatibility with the 2607, 2610, and 2614 printers is not implemented,
as it is irrelevant for simulation.
2. The hardware switch S1 (the NORMAL/TEST switch) that allows printer
testing without a diagnostic program is not implemented.
*/
#include "hp2100_defs.h"
/* Printer program constants */
#define CR '\r' /* carriage return */
#define LF '\n' /* line feed */
#define FF '\f' /* form feed */
#define DEL '\177' /* delete */
#define DATA_MASK 0177u /* printer uses only 7 bits for data */
#define FORMAT_VFU 0100u /* printer VFU selector */
#define FORMAT_MASK 0117u /* printer format command mask for 12-channel VFU */
#define FORMAT_VFU_8_MASK 0107u /* printer format command mask for 8-channel VFU */
#define FORMAT_SUPPRESS 0000u /* format code to slew 0 lines */
#define FORMAT_VFU_CHAN_1 0100u /* format code to slew to VFU channel 1 */
#define FORMAT_VFU_BIAS 0077u /* bias converting from format code to channel number */
#define VFU_MAX 144 /* maximum number of VFU form lines */
#define VFU_SIZE (VFU_MAX + 1) /* size of the VFU array */
#define LINE_SIZE 256 /* size of the character array used to read the VFU file */
#define VFU_WIDTH 12 /* maximum number of VFU channels */
#define VFU_CHANNEL_1 04000u /* top of form */
#define VFU_CHANNEL_2 02000u /* bottom of form */
#define VFU_CHANNEL_3 01000u /* single space */
#define VFU_CHANNEL_4 00400u /* double space */
#define VFU_CHANNEL_5 00200u /* triple space */
#define VFU_CHANNEL_6 00100u /* half page */
#define VFU_CHANNEL_7 00040u /* quarter page */
#define VFU_CHANNEL_8 00020u /* sixth page */
#define VFU_CHANNEL_9 00010u /* bottom of form */
#define VFU_CHANNEL_10 00004u /* (unassigned) */
#define VFU_CHANNEL_11 00002u /* (unassigned) */
#define VFU_CHANNEL_12 00001u /* (unassigned) */
#define CHARS_MAX 136 /* maximum number of characters buffered by the printers */
#define BUFFER_SIZE (CHARS_MAX + VFU_MAX * 2) /* max chars + max VFU * 2 (for CR LF) */
/* Device flags */
#define DEV_DIAG_SHIFT (DEV_V_UF + 0) /* diagnostic VFU tape is installed */
#define DEV_REALTIME_SHIFT (DEV_V_UF + 1) /* timing mode is realistic */
#define DEV_DIAG (1u << DEV_DIAG_SHIFT) /* diagnostic mode flag */
#define DEV_REALTIME (1u << DEV_REALTIME_SHIFT) /* realistic timing flag */
/* Printer unit flags.
UNIT_V_UF + 7 6 5 4 3 2 1 0
+---+---+---+---+---+---+---+---+
| - | - | P | O | E | model |
+---+---+---+---+---+---+---+---+
Where:
P = power is off
O = offline
E = expanded output
*/
#define UNIT_MODEL_SHIFT (UNIT_V_UF + 0) /* printer model ID */
#define UNIT_EXPAND_SHIFT (UNIT_V_UF + 3) /* printer uses expanded output */
#define UNIT_OFFLINE_SHIFT (UNIT_V_UF + 4) /* printer is offline */
#define UNIT_POWEROFF_SHIFT (UNIT_V_UF + 5) /* printer power is off */
#define UNIT_MODEL_MASK 0000007u /* model ID mask */
#define UNIT_MODEL (UNIT_MODEL_MASK << UNIT_MODEL_SHIFT)
#define UNIT_EXPAND (1u << UNIT_EXPAND_SHIFT)
#define UNIT_OFFLINE (1u << UNIT_OFFLINE_SHIFT)
#define UNIT_ONLINE 0
#define UNIT_POWEROFF (1u << UNIT_POWEROFF_SHIFT)
#define UNIT_2607 (HP_2607 << UNIT_MODEL_SHIFT)
#define UNIT_2613 (HP_2613 << UNIT_MODEL_SHIFT)
#define UNIT_2617 (HP_2617 << UNIT_MODEL_SHIFT)
#define UNIT_2618 (HP_2618 << UNIT_MODEL_SHIFT)
/* Unit flags accessor */
#define GET_MODEL(f) ((PRINTER_TYPE) (((f) >> UNIT_MODEL_SHIFT) & UNIT_MODEL_MASK))
/* Printer types */
typedef enum {
HP_2607, /* HP 2607A */
HP_2613, /* HP 2613A */
HP_2617, /* HP 2617A */
HP_2618 /* HP 2618A */
} PRINTER_TYPE;
/* Printer locality states */
typedef enum {
Offline, /* printer is going offline */
Online /* printer is going online */
} LOCALITY;
/* Printer properties table.
This table contains the characteristics that vary between printer models.
The "char_set" field values reflect printer Option 001, 96/128-character set.
The "not_ready" field indicates whether a paper fault sets a separate
not-ready status or simply takes the printer offline. The "fault_at_eol"
field indicates whether a paper fault is reported at the end of any line or
only at the top of the next form.
*/
typedef struct {
uint32 line_length; /* the maximum number of print positions */
uint32 char_set; /* the size of the character set */
uint32 vfu_channels; /* the number of VFU channels */
t_bool not_ready; /* TRUE if the printer reports a separate not ready status */
t_bool overprints; /* TRUE if the printer supports overprinting */
t_bool autoprints; /* TRUE if the printer automatically prints on buffer overflow */
t_bool fault_at_eol; /* TRUE if a paper fault is reported at the end of any line */
} PRINTER_PROPS;
static const PRINTER_PROPS print_props [] = { /* printer properties, indexed by PRINTER_TYPE */
/* line char VFU not over auto fault */
/* length set channels ready prints prints at EOL */
/* ------ ----- -------- ------ ------ ------ ------ */
{ 132, 128, 8, FALSE, FALSE, TRUE, FALSE }, /* HP_2607 */
{ 136, 96, 12, TRUE, TRUE, FALSE, TRUE }, /* HP_2613 */
{ 136, 96, 12, TRUE, TRUE, FALSE, TRUE }, /* HP_2617 */
{ 132, 96, 12, TRUE, TRUE, FALSE, TRUE } /* HP_2618 */
};
/* Delay properties table.
To support the realistic timing mode, the delay properties table contains
timing specifications for the supported printers. The times represent the
delays for mechanical and electronic operations. Delay values are in event
tick counts; macros are used to convert from times to ticks.
Implementation notes:
1. Although all of the printers operate more slowly with a 96/128-character
set installed than with a 64-character set, the times reflect the smaller
set size. Also, some models provide different print rates, depending on
how many and/or which characters are printed. These variations are not
simulated.
*/
typedef struct {
int32 buffer_load; /* per-character transfer time */
int32 print; /* print time */
int32 advance; /* paper advance time per line */
} DELAY_PROPS;
static const DELAY_PROPS real_times [] = { /* real-time delays, indexed by PRINTER_TYPE */
/* buffer paper */
/* load print advance */
/* --------- -------- --------- */
{ uS (12.6), mS (260), mS (40.1) }, /* HP_2607 200 lines per minute */
{ uS (1.75), mS (183), mS (8.33) }, /* HP_2613 300 lines per minute */
{ uS (1.75), mS ( 86), mS (6.67) }, /* HP_2617 600 lines per minute */
{ uS (1.75), mS ( 38), mS (4.76) } /* HP_2618 1250 lines per minute */
};
#define LP_BUFFER_LOAD uS (1) /* fast per-character transfer time */
#define LP_PRINT mS (1) /* fast print time */
#define LP_ADVANCE uS (50) /* fast paper advance time per line */
static DELAY_PROPS fast_times = /* FASTTIME delays */
{ LP_BUFFER_LOAD,
LP_PRINT,
LP_ADVANCE
};
/* Printer control word.
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 | - - - - - - - - | ASCII character | character
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 1 | - - - - - - - - | format word | format
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
*/
#define CN_FORMAT 0100000u /* printer output character/format (0/1) code */
static const BITSET_NAME prt_control_names [] = { /* Printer control word names */
"\1format\0character" /* bit 15 */
};
static const BITSET_FORMAT prt_control_format = /* names, offset, direction, alternates, bar */
{ FMT_INIT (prt_control_names, 15, msb_first, has_alt, no_bar) };
/* Printer status word.
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| L | R | V | U | - - - - - - - - - - - | D |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
*/
#define ST_ONLINE 0100000u /* online */
#define ST_NOT_READY 0040000u /* not ready */
#define ST_VFU_9 0020000u /* VFU channel 9 */
#define ST_VFU_12 0010000u /* VFU channel 12 */
#define ST_DEMAND 0000001u /* demand */
#define ST_POWEROFF (ST_ONLINE | ST_NOT_READY | ST_DEMAND)
static const BITSET_NAME prt_status_names [] = { /* Printer status word names */
"\1online\0offline", /* bit 15 */
"\1not ready\0ready", /* bit 14 */
"VFU 9", /* bit 13 */
"VFU 12", /* bit 12 */
NULL, /* bit 11 */
NULL, /* bit 10 */
NULL, /* bit 9 */
NULL, /* bit 8 */
NULL, /* bit 7 */
NULL, /* bit 6 */
NULL, /* bit 5 */
NULL, /* bit 4 */
NULL, /* bit 3 */
NULL, /* bit 2 */
NULL, /* bit 1 */
"idle" /* bit 0 */
};
static const BITSET_FORMAT prt_status_format = /* names, offset, direction, alternates, bar */
{ FMT_INIT (prt_status_names, 0, msb_first, has_alt, no_bar) };
/* Interface state */
static HP_WORD output_word = 0; /* output data word */
static HP_WORD status_word = 0; /* input status word */
static t_bool strobe = FALSE; /* STROBE signal to the printer */
static t_bool demand = FALSE; /* DEMAND signal from the printer */
static struct {
FLIP_FLOP control; /* control flip-flop */
FLIP_FLOP flag; /* flag flip-flop */
FLIP_FLOP flagbuf; /* flag buffer flip-flop */
} lpt = { CLEAR, CLEAR, CLEAR };
/* Printer state */
static t_bool paper_fault = TRUE; /* TRUE if the printer is out of paper */
static t_bool tape_fault = FALSE; /* TRUE if there is no punch in a commanded VFU channel */
static t_bool offline_pending = FALSE; /* TRUE if an offline request is waiting for the printer to finish */
static uint32 overprint_char = DEL; /* character to use if overprinted */
static uint32 current_line = 1; /* current form line */
static uint32 buffer_index = 0; /* current index into the print buffer */
static uint32 form_length; /* form length in lines */
static uint8 buffer [BUFFER_SIZE]; /* character and paper advance buffer */
static uint16 VFU [VFU_SIZE]; /* vertical format unit tape */
static char vfu_title [LINE_SIZE]; /* descriptive title of the tape currently in the VFU */
static int32 punched_char = 'O'; /* character to display if VFU channel is punched */
static int32 unpunched_char = '.'; /* character to display if VFU channel is not punched */
static const DELAY_PROPS *dlyptr = &fast_times; /* pointer to the event delay times to use */
/* Interface local SCP support routines */
static IOHANDLER lp_interface;
static t_stat lp_service (UNIT *uptr);
static t_stat lp_reset (DEVICE *dptr);
/* Interface local utility routines */
static void activate_unit (UNIT *uptr);
static void report_error (FILE *stream);
/* Printer local SCP support routines */
static t_stat lp_attach (UNIT *uptr, CONST char *cptr);
static t_stat lp_detach (UNIT *uptr);
static t_stat lp_set_mode (UNIT *uptr, int32 value, CONST char *cptr, void *desc);
static t_stat lp_set_model (UNIT *uptr, int32 value, CONST char *cptr, void *desc);
static t_stat lp_set_on_offline (UNIT *uptr, int32 value, CONST char *cptr, void *desc);
static t_stat lp_set_vfu (UNIT *uptr, int32 value, CONST char *cptr, void *desc);
static t_stat lp_show_mode (FILE *st, UNIT *uptr, int32 value, CONST void *desc);
static t_stat lp_show_vfu (FILE *st, UNIT *uptr, int32 value, CONST void *desc);
/* Printer local utility routines */
static void lp_master_clear (UNIT *uptr);
static t_bool lp_set_alarm (UNIT *uptr);
static t_bool lp_set_locality (UNIT *uptr, LOCALITY printer_state);
static t_stat lp_load_vfu (UNIT *uptr, FILE *vf);
static int32 lp_read_line (FILE *vf, char *line, uint32 size);
/* Interface SCP data structures */
/* Device information block */
static DIB lpt_dib = {
&lp_interface, /* device interface */
LPT, /* select code */
0 /* card index */
};
/* Unit list */
#define UNIT_FLAGS (UNIT_ATTABLE | UNIT_SEQ | UNIT_EXPAND | UNIT_OFFLINE)
static UNIT lpt_unit [] = {
{ UDATA (&lp_service, UNIT_FLAGS | UNIT_2607, 0), 0 }
};
/* Register list */
static REG lpt_reg [] = {
/* Macro Name Location Radix Width Offset Depth Flags */
/* ------ ------ ------------------------ ----- ------------ ------ ------------- ----------------- */
{ FLDATA (DEVCTL, lpt.control, 0) },
{ FLDATA (DEVFLG, lpt.flag, 0) },
{ FLDATA (DEVFBF, lpt.flagbuf, 0) },
{ FLDATA (STROBE, strobe, 0) },
{ FLDATA (DEMAND, demand, 0) },
{ ORDATA (OUTPUT, output_word, 16), PV_RZRO | REG_X },
{ ORDATA (STATUS, status_word, 16), PV_RZRO },
{ FLDATA (PFAULT, paper_fault, 0) },
{ FLDATA (TFAULT, tape_fault, 0) },
{ FLDATA (OLPEND, offline_pending, 0) },
{ DRDATA (PRLINE, current_line, 8), PV_LEFT },
{ DRDATA (BUFIDX, buffer_index, 8), PV_LEFT },
{ BRDATA (PRTBUF, buffer, 8, 8, BUFFER_SIZE), PV_RZRO | REG_A },
{ ORDATA (OVPCHR, overprint_char, 8), PV_RZRO | REG_A },
{ DRDATA (FORMLN, form_length, 8), PV_LEFT | REG_RO },
{ BRDATA (TITLE, vfu_title, 8, 8, LINE_SIZE), REG_HRO },
{ BRDATA (VFU, VFU, 2, VFU_WIDTH, VFU_SIZE), PV_RZRO | REG_RO },
{ ORDATA (PUNCHR, punched_char, 8), PV_RZRO | REG_A },
{ ORDATA (UNPCHR, unpunched_char, 8), PV_RZRO | REG_A },
{ DRDATA (BTIME, fast_times.buffer_load, 24), PV_LEFT | REG_NZ },
{ DRDATA (PTIME, fast_times.print, 24), PV_LEFT | REG_NZ },
{ DRDATA (STIME, fast_times.advance, 24), PV_LEFT | REG_NZ },
{ DRDATA (POS, lpt_unit [0].pos, T_ADDR_W), PV_LEFT },
{ DRDATA (UWAIT, lpt_unit [0].wait, 32), PV_LEFT | REG_HRO },
DIB_REGS (lpt_dib),
{ NULL }
};
/* Modifier list */
typedef enum { /* Device modes */
Fast_Time, /* use optimized timing */
Real_Time, /* use realistic timing */
Printer, /* use the printer VFU tape */
Diagnostic /* use the diagnostic VFU tape */
} DEVICE_MODES;
static MTAB lpt_mod [] = {
/* Mask Value Match Value Print String Match String Validation Display Descriptor */
/* ------------- ------------- ----------------- ------------ ------------------ ------- ---------- */
{ UNIT_MODEL, UNIT_2607, "2607", "2607", &lp_set_model, NULL, NULL },
{ UNIT_MODEL, UNIT_2613, "2613", "2613", &lp_set_model, NULL, NULL },
{ UNIT_MODEL, UNIT_2617, "2617", "2617", &lp_set_model, NULL, NULL },
{ UNIT_MODEL, UNIT_2618, "2618", "2618", &lp_set_model, NULL, NULL },
{ UNIT_OFFLINE, UNIT_OFFLINE, "offline", "OFFLINE", &lp_set_on_offline, NULL, NULL },
{ UNIT_OFFLINE, 0, "online", "ONLINE", &lp_set_on_offline, NULL, NULL, },
{ UNIT_EXPAND, UNIT_EXPAND, "expanded output", "EXPAND", NULL, NULL, NULL },
{ UNIT_EXPAND, 0, "compact output", "COMPACT", NULL, NULL, NULL, },
{ UNIT_POWEROFF, UNIT_POWEROFF, "power off", "POWEROFF", NULL, NULL, NULL },
{ UNIT_POWEROFF, 0, "power on", "POWERON", NULL, NULL, NULL, },
/* Entry Flags Value Print String Match String Validation Display Descriptor */
/* ------------------- ----------- ------------ ------------ ------------ ------------- ----------------- */
{ MTAB_XDV, Fast_Time, NULL, "FASTTIME", &lp_set_mode, NULL, NULL },
{ MTAB_XDV, Real_Time, NULL, "REALTIME", &lp_set_mode, NULL, NULL },
{ MTAB_XDV, Printer, NULL, "PRINTER", &lp_set_mode, NULL, NULL },
{ MTAB_XDV, Diagnostic, NULL, "DIAGNOSTIC", &lp_set_mode, NULL, NULL },
{ MTAB_XDV, 0, "MODES", NULL, NULL, &lp_show_mode, NULL },
{ MTAB_XDV, 1u, "SC", "SC", &hp_set_dib, &hp_show_dib, (void *) &lpt_dib },
{ MTAB_XDV | MTAB_NMO, ~1u, "DEVNO", "DEVNO", &hp_set_dib, &hp_show_dib, (void *) &lpt_dib },
{ MTAB_XDV | MTAB_NMO, 1, "VFU", NULL, NULL, &lp_show_vfu, NULL },
{ MTAB_XDV | MTAB_NC, 0, "VFU", "VFU", &lp_set_vfu, &lp_show_vfu, NULL },
{ 0 }
};
/* Debugging trace list */
static DEBTAB lpt_deb [] = {
{ "CMD", TRACE_CMD }, /* trace interface or controller commands */
{ "CSRW", TRACE_CSRW }, /* trace interface control, status, read, and write actions */
{ "SERV", TRACE_SERV }, /* trace unit service scheduling calls and entries */
{ "XFER", TRACE_XFER }, /* trace data transmissions */
{ "IOBUS", TRACE_IOBUS }, /* trace I/O bus signals and data words received and returned */
{ NULL, 0 }
};
/* Device descriptor */
DEVICE lpt_dev = {
"LPT", /* device name */
lpt_unit, /* unit array */
lpt_reg, /* register array */
lpt_mod, /* modifier array */
1, /* number of units */
10, /* address radix */
32, /* address width = 4 GB */
1, /* address increment */
8, /* data radix */
8, /* data width */
NULL, /* examine routine */
NULL, /* deposit routine */
&lp_reset, /* reset routine */
NULL, /* boot routine */
&lp_attach, /* attach routine */
&lp_detach, /* detach routine */
&lpt_dib, /* device information block pointer */
DEV_DISABLE | DEV_DEBUG, /* device flags */
0, /* debug control flags */
lpt_deb, /* debug flag name array */
NULL, /* memory size change routine */
NULL /* logical device name */
};
/* Interface local SCP support routines */
/* Line printer interface.
The line printer interface is installed on the I/O bus and receives I/O
commands from the CPU and DMA/DCPC channels. In simulation, the asserted
signals on the bus are represented as bits in the inbound signal_set. Each
signal is processed sequentially in numerical order.
Implementation notes:
1. The simulation implements the 12845B Series 1506 interface that inhibits
STROBE if the printer is offline.
2. In hardware, an STC signal sets both the Control flip-flop and the
Information Ready flip-flop. The latter asserts STROBE to the printer,
which denies DEMAND in response. The trailing edge of DEMAND clears the
Information Ready flip-flop and denies STROBE. In simulation, STC sets
the Control flip-flop and calls the device service routine to begin the
print operation. This is equivalent to STROBE asserting, then DEMAND
denying, then STROBE denying. The Information Ready flip-flop is not
simulated, as it effectively sets and clears within one CPU instruction.
*/
static uint32 lp_interface (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data)
{
HP_WORD data;
IOSIGNAL signal;
IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */
while (working_set) {
signal = IONEXT (working_set); /* isolate the next signal */
switch (signal) { /* dispatch an I/O signal */
case ioCLF:
lpt.flag = CLEAR; /* clear the flag */
lpt.flagbuf = CLEAR; /* and flag buffer flip-flops */
break;
case ioSTF:
case ioENF:
lpt.flag = SET; /* set the flag */
lpt.flagbuf = SET; /* and flag buffer flip-flops */
break;
case ioSFC:
setstdSKF (lpt); /* skip if the flag is clear */
break;
case ioSFS:
setstdSKF (lpt); /* skip if the flag is set */
break;
case ioIOI:
if (lpt_unit [0].flags & UNIT_POWEROFF) /* if the printer power is off */
data = ST_POWEROFF; /* then return the power-off status */
else if (demand) /* otherwise if DEMAND is asserted */
data = status_word | ST_DEMAND; /* then reflect it in the status word */
else /* otherwise return */
data = status_word; /* the (static) status */
stat_data = IORETURN (SCPE_OK, data); /* merge in the return status */
tprintf (lpt_dev, TRACE_CSRW, "Status is %s\n",
fmt_bitset (data, prt_status_format));
break;
case ioIOO:
tprintf (lpt_dev, TRACE_CSRW, "Control is %s | %s\n",
fmt_bitset (stat_data, prt_control_format),
fmt_char (IODATA (stat_data)));
output_word = IODATA (stat_data); /* save the character or format word */
break;
case ioPOPIO:
lpt.flag = SET; /* set the flag */
lpt.flagbuf = SET; /* and flag buffer flip-flops */
output_word = 0; /* and clear the output buffer */
break;
case ioCRS:
lp_master_clear (lpt_unit); /* CRS asserts MASTER CLEAR to the printer */
/* fall into the ioCLC case */
case ioCLC:
lpt.control = CLEAR; /* clear the control flip-flop */
strobe = FALSE; /* and deny STROBE to the printer */
break;
case ioSTC:
lpt.control = SET; /* set the control flip-flop */
if (status_word & ST_ONLINE) { /* if the printer is online */
strobe = TRUE; /* then assert STROBE to the printer */
lpt_unit [0].wait = 0; /* set for immediate service entry */
activate_unit (lpt_unit); /* and call the service routine */
}
break;
case ioSIR:
setstdPRL (lpt); /* set the standard PRL signal */
setstdIRQ (lpt); /* set the standard IRQ signal */
setstdSRQ (lpt); /* set the standard SRQ signal */
break;
case ioIAK:
lpt.flagbuf = CLEAR; /* clear the flag buffer flip-flop */
break;
default: /* all other signals */
break; /* are ignored */
}
working_set = working_set & ~signal; /* remove the current signal from the set */
}
return stat_data; /* return the outbound status and data */
}
/* Service the printer.
The printer transfer service is called to output a character to the printer
buffer or to output a format command that causes the buffered line to be
printed with specified paper movement.
In hardware, the interface places a character or format code on the lower
seven data out lines and asserts STROBE to the printer. The printer responds
by denying DEMAND. The interface then denies STROBE and waits for the
printer to reassert DEMAND to indicate that the buffer load or print
operation is complete.
In simulation, this service routine is called twice for each transfer. It is
called immediately when STROBE is asserted with DEMAND asserted and then
after a variable delay with STROBE denied. In response to the former call,
the routine denies DEMAND and STROBE, loads the character buffer or prints
the buffered line, and then sets up an event delay corresponding to the
operation performed. In response to the latter call, the routine asserts
DEMAND and then clears the event delay time, so that the routine will be
reentered immediately when STROBE is asserted again. DEMAND assertion also
sets the flag buffer flip-flop.
If a SET LPT OFFLINE or DETACH LPT command simulating an out-of-paper
condition is given, the printer will not honor the command immediately if
data exists in the print buffer or the printer is currently printing a line.
In this case, the action is deferred until this service routine is entered to
complete a print operation. At that point, the printer goes offline with
DEMAND denied. This leaves the transfer handshake incomplete. When the
printer is placed back online, DEMAND is asserted to conclude the handshake.
Control word bit 15 determines whether the code on the data out lines is
interpreted as a character (0) or a format command (1). If there is room in
the print buffer, the character is loaded. If not, then depending on the
model, the printer either discards the character or automatically prints the
buffer contents, advances the paper one line, and stores the new character in
the empty buffer. If a control character is sent but the printer cannot
print it, a space is loaded in its place.
A format command causes the current buffer to be printed, and then the paper
is advanced by a prescribed amount. Two output modes are provided: compact
and expanded.
In compact mode, a printed line is terminated by a CR LF pair, but subsequent
line spacing is performed by LFs alone. Also, a top-of-form request will
emit a FF character instead of the number of LFs required to reach the top of
the next form, and overprinting is handled by emitting a lone CR at the end
of the line. This mode is used when the printer output file will be sent to
a physical printer connected to the host.
In expanded mode, paper advance is handled solely by emitting CR LF pairs.
Overprinting is handled by merging characters in the buffer. This mode is
used where the printer output file will be saved or manipulated as a text
file.
The format commands recognized by the printer are:
0 x x 0 0 0 0 -- slew 0 lines (suppress spacing) after printing
...
0 x x 1 1 1 1 -- slew 15 lines after printing
and:
1 x x 0 0 0 0 -- slew to VFU channel 1 after printing
...
1 x x 1 0 1 1 -- slew to VFU channel 12 after printing
A command to slew to a VFU channel that is not punched or to a VFU channel
other than those defined for the printer will cause a tape fault, and the
printer will go offline; setting the printer back online will clear the
fault. Otherwise, LFs or a FF (compact mode) or CR LF pairs (expanded mode)
will be added to the buffer to advance the paper the required number of
lines.
Not all printers can overprint. A request to suppress spacing on a printer
that cannot (e.g., the HP 2607) is treated as a request for single spacing.
If the stream write fails, an error message is displayed on the simulation
console, a printer alarm condition is set (which takes the printer offline),
and SCPE_IOERR is returned to cause a simulation stop to give the user the
opportunity to fix the problem. Simulation may then be resumed, either with
the printer set back online if the problem is fixed, or with the printer
remaining offline if the problem is uncorrectable.
Implementation notes:
1. When a paper-out condition is detected, the 2607 printer goes offline
only when the next top-of-form is reached. The 2613/17/18 printers go
offline as soon as the current line completes.
2. A printer going offline, either via a user action or by a paper-out
condition, does not complete the last handshake. Instead, DEMAND remains
denied, so the interface flag never sets. Typically, this causes an I/O
timeout in the OS driver, which issues a CLC and downs the device. When
the printer is set back online, DEMAND asserts, which sets the flag.
However, the CLC has cleared the control flip-flop, so no interrupt is
generated. When the device is upped, the incomplete request is reissued;
this results in either a duplicate printed line or a duplicate paper
movement.
3. Because attached files are opened in binary mode, newline translation
(i.e., from LF to CR LF) is not performed by the host system. Therefore,
we write explicit CR LF pairs to end lines, even in compact mode, as
required for fidelity to HP peripherals. If bare LFs are used by the
host system, the printer output file must be postprocessed to remove the
CRs.
4. Overprinting in expanded mode is simulated by merging the lines in the
buffer. A format command to suppress spacing resets the buffer index but
saves the previous buffer length as a "high water mark" that will be
extended if the overlaying line is longer. This process may be repeated
as many times as desired before issuing a format command that prints the
buffer.
When overlaying characters, if a space overlays a printing character, a
printing character overlays a space, or a printing character overlays
itself, then the printing character is retained. Otherwise, an
"overprint character" (which defaults to DEL, but can be changed by the
user) replaces the character in the buffer.
5. Printers that support 12-channel VFUs treat the VFU format command as
modulo 16. Printers that support 8-channel VFUs treat the command as
modulo 8.
6. As a convenience to the user, the printer output stream is flushed when a
TOF operation is performed. This permits inspection of the output file
from the SCP command prompt while output is ongoing.
7. The user may examine the TFAULT and PFAULT registers to determine why the
printer went offline.
8. Explicit tests for lowercase and control characters are much faster and
are used rather than calls to "islower" and "iscntrl", which must
consider the current locale.
9. This routine will not be entered with the printer unit unattached. The
printer must be online (and therefore attached) before an STC will
schedule the service, and "lp_detach" will not detach the unit if it is
busy unless it is forced (and, in the latter case, "lp_detach" will
cancel the service event). So protection against "uptr->fileref" being
NULL is not required.
*/
static t_stat lp_service (UNIT *uptr)
{
const PRINTER_TYPE model = GET_MODEL (uptr->flags); /* the printer model number */
const t_bool printing = ((output_word & CN_FORMAT) != 0); /* TRUE if a print command was received */
static uint32 overprint_index = 0; /* the "high-water" mark while overprinting */
uint8 data_byte, format_byte;
uint16 channel;
uint32 line_count, slew_count;
tprintf (lpt_dev, TRACE_SERV, "Printer service entered\n");
if (uptr->flags & UNIT_POWEROFF) /* if the printer power is off */
return SCPE_OK; /* then no action is taken */
else if (strobe == FALSE) { /* otherwise if STROBE has denied */
if (printing) { /* then if printing occurred */
buffer_index = 0; /* then clear the buffer */
if (paper_fault) { /* if an out-of-paper condition is pending */
if (print_props [model].fault_at_eol /* then if the printer faults at the end of a line */
|| current_line == 1) /* or the printer is at the top of the form */
return lp_detach (uptr); /* then complete it now with the printer offline */
}
else if (tape_fault) { /* otherwise if a referenced VFU channel was not punched */
tprintf (lpt_dev, TRACE_CMD, "Commanded VFU channel is not punched\n");
lp_set_alarm (uptr); /* then set an alarm condition that takes the printer offline */
return SCPE_OK;
}
else if (offline_pending) { /* otherwise if a non-alarm offline request is pending */
lp_set_locality (uptr, Offline); /* then take the printer offline now */
return SCPE_OK;
}
}
demand = TRUE; /* assert DEMAND to complete the handshake */
uptr->wait = 0; /* and request immediate entry when STROBE next asserts */
lp_interface (&lpt_dib, ioENF, 0); /* the flag buffer flip-flop sets on DEMAND assertion */
}
else if (demand == TRUE) { /* otherwise if STROBE has asserted while DEMAND is asserted */
demand = FALSE; /* then deny DEMAND */
strobe = FALSE; /* which resets STROBE */
data_byte = (uint8) (output_word & DATA_MASK); /* only the lower 7 bits are sent to the printer */
if (printing == FALSE) { /* if loading the print buffer */
if (data_byte > '_' /* then if the character is "lowercase" */
&& print_props [model].char_set == 64) /* but the printer doesn't support it */
data_byte = data_byte - 040; /* then shift it to "uppercase" */
if ((data_byte < ' ' || data_byte == DEL) /* if the character is a control character */
&& print_props [model].char_set != 128) /* but the printer doesn't support it */
data_byte = ' '; /* then substitute a space */
if (buffer_index < print_props [model].line_length) { /* if there is room in the buffer */
if (overprint_index == 0 /* then if not overprinting */
|| buffer_index >= overprint_index /* or past the current buffer limit */
|| buffer [buffer_index] == ' ') /* or overprinting a blank */
buffer [buffer_index] = data_byte; /* then store the character */
else if (data_byte != ' ' /* otherwise if we're overprinting a character */
&& data_byte != buffer [buffer_index]) /* with a different character */
buffer [buffer_index] = (uint8) overprint_char; /* then substitute the overprint character */
buffer_index++; /* increment the buffer index */
uptr->wait = dlyptr->buffer_load; /* schedule the buffer load delay */
tprintf (lpt_dev, TRACE_XFER, "Character %s sent to printer\n",
fmt_char (data_byte));
}
else if (print_props [model].autoprints) { /* otherwise if a buffer overflow auto-prints */
tprintf (lpt_dev, TRACE_CMD, "Buffer overflow printed %u characters on line %u\n",
buffer_index, current_line);
buffer [buffer_index++] = CR; /* then tie off */
buffer [buffer_index++] = LF; /* the current buffer */
fwrite (buffer, sizeof buffer [0], /* write the buffer to the printer file */
buffer_index, uptr->fileref);
uptr->pos = (t_addr) ftell (uptr->fileref); /* update the file position */
current_line = current_line + 1; /* move the paper one line */
if (current_line > form_length) /* if the current line is beyond the end of the form */
current_line = 1; /* then reset to the top of the next form */
tprintf (lpt_dev, TRACE_CMD, "Printer advanced 1 line to line %u\n",
current_line);
overprint_index = 0; /* clear any accumulated overprint index */
buffer [0] = data_byte; /* store the character */
buffer_index = 1; /* in the empty buffer */
uptr->wait = dlyptr->print /* schedule the print delay */
+ dlyptr->advance /* plus the paper advance delay */
+ dlyptr->buffer_load; /* plus the buffer load delay */
tprintf (lpt_dev, TRACE_XFER, "Character %s sent to printer\n",
fmt_char (data_byte));
}
else { /* otherwise the printer discards excess characters */
uptr->wait = dlyptr->buffer_load; /* so just schedule the load delay */
tprintf (lpt_dev, TRACE_CMD, "Buffer overflow discards character %s\n",
fmt_char (data_byte));
}
}
else { /* otherwise this is a print format command */
tprintf (lpt_dev, TRACE_XFER, "Format code %03o sent to printer\n",
data_byte);
format_byte = data_byte & FORMAT_MASK; /* format commands ignore bits 5-4 */
if (overprint_index > buffer_index) /* if the overprinted line is longer than the current line */
buffer_index = overprint_index; /* then extend the current buffer index */
if (buffer_index > 0 && format_byte != FORMAT_SUPPRESS) /* if printing will occur, then trace it */
tprintf (lpt_dev, TRACE_CMD, "Printed %u character%s on line %u\n",
buffer_index, (buffer_index == 1 ? "" : "s"), current_line);
if (format_byte == FORMAT_SUPPRESS /* if this is a "suppress space" request */
&& print_props [model].overprints) { /* and the printer is capable of overprinting */
slew_count = 0; /* then do not slew after printing */
if (uptr->flags & UNIT_EXPAND) { /* if the printer is in expanded mode */
if (buffer_index > overprint_index) /* then if the current length exceeds the overprinted length */
overprint_index = buffer_index; /* then extend the overprinted line */
buffer_index = 0; /* reset the buffer index to overprint the next line */
}
else /* otherwise the printer is in compact mode */
buffer [buffer_index++] = CR; /* so overprint by emitting a CR without a LF */
tprintf (lpt_dev, TRACE_CMD, "Printer commanded to suppress spacing on line %u\n",
current_line);
}
else if (format_byte & FORMAT_VFU) { /* otherwise if this is a VFU command */
if (print_props [model].vfu_channels == 8) /* then if it's an 8-channel VFU */
format_byte &= FORMAT_VFU_8_MASK; /* then only three bits are significant */
channel = VFU_CHANNEL_1 >> (format_byte - FORMAT_VFU_BIAS - 1); /* set the requested channel */
tprintf (lpt_dev, TRACE_CMD, "Printer commanded to slew to VFU channel %u from line %u\n",
format_byte - FORMAT_VFU_BIAS, current_line);
tape_fault = (channel & VFU [0]) == 0; /* a tape fault occurs if there is no punch in this channel */
slew_count = 0; /* initialize the slew counter */
do { /* the VFU always slews at least one line */
slew_count++; /* increment the slew counter */
current_line++; /* and the line counter */
if (current_line > form_length) /* if the current line is beyond the end of the form */
current_line = 1; /* then reset to the top of the next form */
}
while (!tape_fault && (channel & VFU [current_line]) == 0); /* continue until a punch is seen */
}
else { /* otherwise it must be a slew command */
slew_count = format_byte; /* so get the number of lines to slew */
if (format_byte == FORMAT_SUPPRESS) /* if the printer cannot overprint */
slew_count = 1; /* then the paper advances after printing */
tprintf (lpt_dev, TRACE_CMD, "Printer commanded to slew %u line%s from line %u\n",
slew_count, (slew_count == 1 ? "" : "s"), current_line);
current_line = current_line + slew_count; /* move the current line */
if (current_line > form_length) /* if the current line is beyond the end of the form */
current_line = current_line - form_length; /* then it extends onto the next form */
}
if (format_byte == FORMAT_VFU_CHAN_1 /* if a TOF was requested */
&& !(uptr->flags & UNIT_EXPAND) /* and the printer is in compact mode */
&& slew_count > 1) { /* and more than one line is needed to reach the TOF */
if (buffer_index > 0) { /* then if the buffer not empty */
buffer [buffer_index++] = CR; /* then print */
buffer [buffer_index++] = LF; /* the current line */
}
buffer [buffer_index++] = FF; /* emit a FF to move to the TOF */
}
else if (slew_count > 0) { /* otherwise a slew is needed */
buffer [buffer_index++] = CR; /* then emit a CR LF */
buffer [buffer_index++] = LF; /* to print the current line */
line_count = slew_count; /* get the number of lines to slew */
while (--line_count > 0) { /* while movement is needed */
if (uptr->flags & UNIT_EXPAND) /* if the printer is in expanded mode */
buffer [buffer_index++] = CR; /* then blank lines are CR LF pairs */
buffer [buffer_index++] = LF; /* otherwise just LFs are used */
}
}
if (buffer_index > 0) { /* if the buffer is not empty */
fwrite (buffer, sizeof buffer [0], /* then write it to the printer file */
buffer_index, uptr->fileref);
overprint_index = 0; /* clear any existing overprint index */
}
status_word &= ~(ST_VFU_9 | ST_VFU_12); /* assume no punches for channels 9 and 12 */
if (print_props [model].vfu_channels > 8) { /* if the printer VFU has more than 8 channels */
if (VFU [current_line] & VFU_CHANNEL_9) /* then if channel 9 is punched for this line */
status_word |= ST_VFU_9; /* then report it in the device status */
if (VFU [current_line] & VFU_CHANNEL_12) /* if channel 12 is punched for this line */
status_word |= ST_VFU_12; /* then report it in the device status */
}
if (format_byte == FORMAT_VFU_CHAN_1) /* if a TOF request was performed */
fflush (uptr->fileref); /* then flush the file buffer for inspection */
uptr->wait = dlyptr->print /* schedule the print delay */
+ slew_count * dlyptr->advance; /* plus the paper advance delay */
uptr->pos = (t_addr) ftell (uptr->fileref); /* update the file position */
if (slew_count > 0)
tprintf (lpt_dev, TRACE_CMD, "Printer advanced %u line%s to line %u\n",
slew_count, (slew_count == 1 ? "" : "s"), current_line);
}
if (ferror (uptr->fileref)) { /* if a host file system error occurred */
report_error (uptr->fileref); /* then report the error to the console */
lp_set_alarm (uptr); /* set an alarm condition */
return SCPE_IOERR; /* and stop the simulator */
}
else /* otherwise the write succeeded */
activate_unit (lpt_unit); /* so schedule the DEMAND reassertion */
}
return SCPE_OK; /* return event service success */
}
/* Device reset routine.
This routine is called for a RESET or RESET LPT command. It is the
simulation equivalent of the POPIO signal, which is asserted by the front
panel PRESET switch.
*/
static t_stat lp_reset (DEVICE *dptr)
{
UNIT *const uptr = dptr->units; /* a pointer to the printer unit */
IOPRESET (&lpt_dib); /* PRESET the device (asserts MASTER CLEAR) */
if (sim_switches & SWMASK ('P')) { /* if this is a power-on reset */
fast_times.buffer_load = LP_BUFFER_LOAD; /* then reset the per-character transfer time, */
fast_times.print = LP_PRINT; /* the print and advance-one-line time, */
fast_times.advance = LP_ADVANCE; /* and the slew additional lines time */
return lp_load_vfu (uptr, NULL); /* load the standard VFU tape */
}
else /* otherwise this is a normal reset */
return SCPE_OK; /* so just return */
}
/* Interface local utility routines */
/* Activate a unit.
The specified unit is added to the event queue with the delay specified by
the unit wait field.
Implementation notes:
1. This routine may be called with wait = 0, which will expire immediately
and enter the service routine with the next sim_process_event call.
Activation is required in this case to allow the service routine to
return an error code to stop the simulation. If the service routine was
called directly, any returned error would be lost.
*/
static void activate_unit (UNIT *uptr)
{
tprintf (lpt_dev, TRACE_SERV, "Unit delay %u service scheduled\n",
uptr->wait);
sim_activate (uptr, uptr->wait); /* activate the unit with the specified wait */
return;
}
/* Report a stream I/O error to the console.
If a stream I/O error has been detected, this routine will print an error
message to the simulation console and clear the stream's error indicator.
*/
static void report_error (FILE *stream)
{
cprintf ("%s simulator printer I/O error: %s\n", /* report the error to the console */
sim_name, strerror (errno));
clearerr (stream); /* clear the error */
return;
}
/* Printer local SCP support routines */
/* Attach the printer image file.
The specified file is attached to the indicated unit. This is the simulation
equivalent of loading paper into the printer and pressing the ONLINE button.
The transition from offline to online typically generates an interrupt.
A new image file may be requested by giving the "-N" switch to the ATTACH
command. If an existing file is specified with "-N", it will be cleared; if
specified without "-N", printer output will be appended to the end of the
existing file content. In all cases, the paper is positioned at the top of
the form.
Implementation notes:
1. If we are called during a RESTORE command to reattach a file previously
attached when the simulation was SAVEd, the device status and file
position are not altered. This is because SIMH 4.x restores the register
contents before reattaching, while 3.x reattaches before restoring the
registers.
2. The pointer to the appropriate event delay times is set in case we are
being called during a RESTORE command (the assignment is redundant
otherwise).
*/
static t_stat lp_attach (UNIT *uptr, CONST char *cptr)
{
t_stat result;
result = hp_attach (uptr, cptr); /* attach the specified printer image file for appending */
if (result == SCPE_OK /* if the attach was successful */
&& (sim_switches & SIM_SW_REST) == 0) { /* and we are not being called during a RESTORE command */
status_word &= ~ST_NOT_READY; /* then clear not-ready status */
current_line = 1; /* and reset the line counter to the top of the form */
tprintf (lpt_dev, TRACE_CMD, "Printer paper loaded\n");
lp_set_locality (uptr, Online); /* set the printer online */
}
if (lpt_dev.flags & DEV_REALTIME) /* if the printer is in real-time mode */
dlyptr = &real_times [GET_MODEL (uptr->flags)]; /* then point at the times for the current model */
else /* otherwise */
dlyptr = &fast_times; /* point at the fast times */
return result; /* return the result of the attach */
}
/* Detach the printer image file.
The specified file is detached from the indicated unit. This is the
simulation equivalent of running out of paper or unloading the paper from the
printer. The out-of-paper condition cause a paper fault alarm, and the
printer goes offline.
When the printer runs out of paper, it will not go offline until characters
present in the buffer are printed and paper motion stops. In addition, the
2607 printer waits until the paper reaches the top-of-form position before
going offline.
In simulation, entering a DETACH LPT command while the printer is busy will
defer the file detach until print operations reach the top of the next form
(2607) or until the current print operation completes (2613/17/18). An
immediate detach may be forced by adding the -F switch to the DETACH command.
This simulates physically removing the paper from the printer and succeeds
regardless of the current printer state.
Implementation notes:
1. During simulator shutdown, this routine is called for the printer unit.
The printer must be detached, even if a detach has been deferred, to
ensure that the file is closed properly. We do this in response to a
detach request with the SIM_SW_SHUT switch present.
2. The DETACH ALL command will fail if any detach routine returns a status
other than SCPE_OK. Because a deferred detach is not fatal, we must
return SCPE_OK, but we still want to print a warning to the user.
3. Because the 2607 only paper faults at TOF, we must explicitly set the
offline_pending flag, as lp_set_alarm may not have been called.
*/
static t_stat lp_detach (UNIT *uptr)
{
const PRINTER_TYPE model = GET_MODEL (uptr->flags); /* the printer model number */
if ((uptr->flags & UNIT_ATT) == 0) /* if the unit is not currently attached */
return SCPE_UNATT; /* then report it */
else {
if (sim_switches & (SWMASK ('F') | SIM_SW_SHUT)) { /* if this is a forced detach or shut down request */
current_line = 1; /* then reset the printer to TOF to enable the detach */
sim_cancel (uptr); /* and terminate */
strobe = FALSE; /* any print action in progress */
}
if ((print_props [model].fault_at_eol /* otherwise if the printer faults at the end of any line */
|| current_line == 1) /* or the printer is at the top of the form */
&& lp_set_alarm (uptr)) { /* and a paper alarm is accepted */
paper_fault = TRUE; /* then set the out-of-paper condition */
tprintf (lpt_dev, TRACE_CMD, "Printer is out of paper\n");
return detach_unit (uptr); /* detach the unit */
}
else { /* otherwise the alarm was rejected at this time */
paper_fault = TRUE; /* so set the out-of-paper condition now */
offline_pending = TRUE; /* but defer the detach */
tprintf (lpt_dev, TRACE_CMD, "Paper out request deferred until print completes\n");
cprintf ("%s\n", sim_error_text (SCPE_INCOMP)); /* report that the actual detach must be deferred */
return SCPE_OK; /* until the buffer has been printed */
}
}
}
/* Set the device modes.
This validation routine is entered with the "value" parameter set to one of
the DEVICE_MODES values. The device flag implied by the value is set or
cleared. The unit, character, and descriptor pointers are not used.
*/
static t_stat lp_set_mode (UNIT *uptr, int32 value, CONST char *cptr, void *desc)
{
switch ((DEVICE_MODES) value) { /* dispatch the mode to set */
case Fast_Time: /* entering optimized timing mode */
lpt_dev.flags &= ~DEV_REALTIME; /* so clear the real-time flag */
dlyptr = &fast_times; /* and point at the fast times */
break;
case Real_Time: /* entering realistic timing mode */
lpt_dev.flags |= DEV_REALTIME; /* so set the real-time flag */
dlyptr = &real_times [GET_MODEL (uptr->flags)]; /* and point at the times for the current model */
break;
case Printer: /* entering printer mode */
lpt_dev.flags &= ~DEV_DIAG; /* so clear the diagnostic flag */
lp_load_vfu (uptr, NULL); /* reload the standard VFU tape */
break;
case Diagnostic: /* entering diagnostic mode */
lpt_dev.flags |= DEV_DIAG; /* so set the diagnostic flag */
lp_load_vfu (uptr, NULL); /* load the diagnostic VFU tape */
break;
}
return SCPE_OK; /* mode changes always succeed */
}
/* Set the printer model.
This validation routine is called to set the model of the printer. The
"value" parameter is one of the UNIT_26nn constants that indicates the new
model. Validation isn't necessary, except to detect a model change and alter
the real-time delays accordingly.
*/
static t_stat lp_set_model (UNIT *uptr, int32 value, CONST char *cptr, void *desc)
{
if (lpt_dev.flags & DEV_REALTIME) /* if the printer is in real-time mode */
dlyptr = &real_times [GET_MODEL (value)]; /* then use the times for the new model */
return SCPE_OK; /* allow the reassignment to proceed */
}
/* Set the printer online or offline.
This validation routine is called to set the printer online or offline. The
"value" parameter is UNIT_OFFLINE if the printer is going offline and is zero
if the printer is going online. This simulates pressing the ON/OFFLINE
button on the printer. The unit must be attached (i.e., paper must be
loaded), before the printer may be set online or offline.
If the printer is being taken offline, the buffer is checked to see if any
characters are present. If they are, or if the printer unit is currently
scheduled (i.e., executing a print operation), the offline request is
deferred until printing completes, and the routine returns "Command not
complete" status to inform the user. Otherwise, the unit is set offline and
DEMAND is denied to indicate that the printer is not ready.
If the printer is being put online and paper is present, the unit is set
online, and any paper or tape fault present is cleared. DEMAND is asserted
to indicate that the printer is ready to accept characters. If the flag
flip-flop is clear, then DEMAND assertion sets the flag buffer flip-flop.
As a special case, a detach (out-of-paper condition) or offline request that
has been deferred until printing completes may be cancelled by setting the
printer online. No other action is taken, because the printer has never
transitioned to the offline state.
Implementation notes:
1. Although a deferred offline request is not fatal, we return SCPE_INCOMP
to prevent "set_cmd" from setting the UNIT_OFFLINE bit in the unit flags
before the printer actually goes offline.
*/
static t_stat lp_set_on_offline (UNIT *uptr, int32 value, CONST char *cptr, void *desc)
{
if ((uptr->flags & UNIT_ATT) == 0) /* if the printer is detached */
return SCPE_UNATT; /* then it can't be set online or offline */
else if (value == UNIT_ONLINE) /* otherwise if this is an online request */
if (paper_fault && offline_pending) { /* then if an out-of-paper condition is deferred */
paper_fault = FALSE; /* then cancel the request */
offline_pending = FALSE; /* leaving the file attached */
}
else /* otherwise it's a normal online request */
lp_set_locality (uptr, Online); /* so set the printer online */
else if (lp_set_locality (uptr, Offline) == FALSE) { /* otherwise if it cannot be set offline now */
tprintf (lpt_dev, TRACE_CMD, "Offline request deferred until print completes\n");
return SCPE_INCOMP; /* then let the user know */
}
return SCPE_OK; /* return operation success */
}
/* Set the VFU tape.
This validation routine is entered to set up the VFU on the printer. It is
invoked by one of two commands:
SET LPT VFU
SET LPT VFU=<filename>
The first form loads the standard 66-line tape into the VFU. The second form
loads the VFU with the tape image specified by the filename. The format of
the tape image is described in the comments for the "lp_load_vfu" routine.
On entry, "uptr" points at the printer unit, "cptr" points to the first
character after the "VFU" keyword, and the "value" and "desc" parameters are
unused. If "cptr" is NULL, then the first command form was given, and the
"lp_load_vfu" routine is called with a NULL file stream pointer to indicate
that the standard VFU tape should be used. Otherwise, the second command
form was given, and "cptr" points to the supplied filename. The file is
opened, and the "lp_load_vfu" routine is called with the stream pointer to
load the VFU tape image contained therein.
*/
static t_stat lp_set_vfu (UNIT *uptr, int32 value, CONST char *cptr, void *desc)
{
FILE *vfu_stream;
t_stat result;
if (cptr == NULL) /* if a VFU reset is requested */
result = lp_load_vfu (uptr, NULL); /* then reload the standard VFU tape */
else if (*cptr == '\0') /* otherwise if the filename was omitted */
return SCPE_MISVAL; /* then report the missing argument */
else { /* otherwise the filename was specified */
vfu_stream = fopen (cptr, "r"); /* so attempt to open it */
if (vfu_stream == NULL) /* if the open failed */
return SCPE_OPENERR; /* then report the error */
result = lp_load_vfu (uptr, vfu_stream); /* load the VFU tape from the file */
fclose (vfu_stream); /* close the file */
}
return result; /* return the result of the load */
}
/* Show the device modes.
This display routine is called to show the device modes for the printer. The
output stream is passed in the "st" parameter, and the other parameters are
ignored. The timing mode and connection mode are printed.
*/
static t_stat lp_show_mode (FILE *st, UNIT *uptr, int32 value, CONST void *desc)
{
fprintf (st, "%s timing, %s mode", /* print the timing and connection modes */
(lpt_dev.flags & DEV_REALTIME ? "realistic" : "fast"),
(lpt_dev.flags & DEV_DIAG ? "diagnostic" : "printer"));
return SCPE_OK;
}
/* Show the VFU tape.
This display routine is called to show the content of the tape currently
loaded in the printer's VFU. The "value" parameter indicates how the routine
was called. It is 0 if a SHOW LPT command was given and 1 if a SHOW LPT VFU
command was issued. For the former, only the VFU title is displayed. The
latter displays the VFU title, followed by a header labelling each of the
channel columns and then one line for each line of the form consisting of
punch and no-punch characters, according to the VFU definition.
The output stream is passed in the "st" parameter, and the "uptr" and "desc"
parameters are ignored.
Implementation notes:
1. Setting the string precision for the header lines trims them to the
appropriate number of channels.
*/
static t_stat lp_show_vfu (FILE *st, UNIT *uptr, int32 value, CONST void *desc)
{
static const char header_1 [] = " Ch 1 Ch 2 Ch 3 Ch 4 Ch 5 Ch 6 Ch 7 Ch 8 Ch 9 Ch10 Ch11 Ch12";
static const char header_2 [] = " ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----";
const PRINTER_TYPE model = GET_MODEL (uptr->flags); /* the printer model number */
const uint32 channel_count = print_props [model].vfu_channels; /* the count of VFU channels */
uint32 chan, line, current_channel;
if (value == 0) /* if we're called for a summary display */
fputs (vfu_title, st); /* then output only the VFU title */
else { /* otherwise the full VFU definition is requested */
fprintf (st, "\n%s tape is loaded.\n\n", vfu_title); /* so start by displaying the VFU title */
fprintf (st, "Line %.*s\n", channel_count * 5, header_1); /* display the */
fprintf (st, "---- %.*s\n", channel_count * 5, header_2); /* channel headers */
for (line = 1; line <= form_length; line++) { /* loop through the VFU array */
fprintf (st, "%3d ", line); /* display the current form line number */
current_channel = VFU_CHANNEL_1; /* start with channel 1 */
for (chan = 1; chan <= channel_count; chan++) { /* loop through the defined channels */
fputs (" ", st); /* add some space */
if (VFU [line] & current_channel) /* if the current channel is punched for this line */
fputc (punched_char, st); /* then display a punched location */
else /* otherwise */
fputc (unpunched_char, st); /* display an unpunched location */
current_channel = current_channel >> 1; /* move to the next channel */
}
fputc ('\n', st); /* end the line */
}
}
return SCPE_OK;
}
/* Printer local utility routines */
/* Clear the printer.
This routine simulates the assertion of the MASTER CLEAR signal to the
printer. In response, the printer clears the line buffer and aborts a paper
advance in progress (except on the 2613). It does not abort a print cycle in
progress.
Implementation notes:
1. In simulation, printing and slewing occurs when the event service routine
is entered on DEMAND assertion. Service is then scheduled for a delay
corresponding to the print action. On service entry with DEMAND denied,
the buffer is cleared and DEMAND is asserted. Therefore, cancelling the
event service does not abort a print cycle or paper advance, as they have
already occurred when the event service is queued.
*/
static void lp_master_clear (UNIT *uptr)
{
const PRINTER_TYPE model = GET_MODEL (uptr->flags); /* the printer model number */
sim_cancel (uptr); /* deactivate the unit */
strobe = FALSE; /* deny STROBE to the printer */
demand = FALSE; /* and assume that DEMAND is also denied */
buffer_index = 0; /* clear the buffer without printing */
offline_pending = FALSE; /* cancel any pending offline request */
tape_fault = FALSE; /* clear any tape fault */
paper_fault = ! (uptr->flags & UNIT_ATT); /* set a paper fault if the printer is out of paper */
if (sim_is_active (uptr)) { /* if the event service is scheduled */
sim_cancel (uptr); /* then cancel it */
lp_service (uptr); /* and call the service routine now to clean up */
}
if (! (uptr->flags & UNIT_POWEROFF)) { /* if the printer power is on */
status_word = 0; /* then prepare to set the printer status */
if (paper_fault && print_props [model].not_ready) /* if paper is out and the printer reports it separately */
status_word |= ST_NOT_READY; /* then add not-ready status */
if (! (uptr->flags & UNIT_OFFLINE)) { /* if the printer is online */
demand = TRUE; /* then DEMAND is asserted */
status_word |= ST_ONLINE; /* set ONLINE status */
}
}
tprintf (lpt_dev, TRACE_CMD, "Master clear asserted to the printer\n");
return;
}
/* Set an alarm condition.
This routine is called when an alarm condition exists. An alarm occurs when
paper is out (paper fault) or a VFU command addresses a channel that does not
contain a punch (tape fault). In response, the printer goes offline and,
for all models except the 2607, becomes not-ready.
On entry, the routine attempts to set the printer offline. If this succeeds,
the printer is set not-ready. If it fails (for reasons explained in the
comments for the "lp_set_on_offline" routine), it will be set offline and
not-ready when printing completes.
*/
static t_bool lp_set_alarm (UNIT *uptr)
{
const PRINTER_TYPE model = GET_MODEL (uptr->flags); /* the printer model number */
if (lp_set_locality (uptr, Offline)) { /* if the printer went offline */
if (print_props [model].not_ready) /* then if the printer reports ready status separately */
status_word |= ST_NOT_READY; /* then add not-ready status */
return TRUE; /* return completion success */
}
else /* otherwise the offline request is pending */
return FALSE; /* so return deferral status */
}
/* Set the printer locality.
This routine is called to set the printer online or offline and returns TRUE
if the request succeeded or FALSE if it was deferred. An online request
always succeeds, so it is up to the caller to ensure that going online is
permissible (e.g., that paper is loaded into the printer). An offline
request succeeds only if the printer is idle. If characters are present in
the print buffer, or if the printer is printing or slewing, then the request
is deferred until the current line is complete.
If the printer goes offline with an operation in progress, DEMAND will remain
denied when the operation completes, so the interface flag will not set.
DEMAND reasserts when the printer is set back online; if the flag flip-flop
is clear, then the flag buffer flip-flop is set.
*/
static t_bool lp_set_locality (UNIT *uptr, LOCALITY printer_state)
{
if (printer_state == Offline) { /* if the printer is going offline */
if (buffer_index == 0 /* then if the buffer is empty */
&& sim_is_active (uptr) == FALSE) { /* and the printer is idle */
uptr->flags |= UNIT_OFFLINE; /* then set the printer offline now */
status_word &= ~ST_ONLINE; /* update the printer status */
demand = FALSE; /* DEMAND denies while the printer is not ready */
}
else { /* otherwise the request must wait */
offline_pending = TRUE; /* until the line is printed */
return FALSE; /* report that the command is not complete */
}
}
else { /* otherwise the printer is going online */
uptr->flags &= ~UNIT_OFFLINE; /* so clear the unit flag */
paper_fault = FALSE; /* clear any paper fault */
tape_fault = FALSE; /* and any tape fault */
status_word = status_word & ~ST_NOT_READY | ST_ONLINE; /* update the printer status */
demand = TRUE; /* DEMAND asserts when the printer is online */
if (lpt.flag == CLEAR) /* if the flag flip-flop is clear */
lp_interface (&lpt_dib, ioENF, 0); /* then DEMAND assertion sets the flag buffer flip-flop */
}
tprintf (lpt_dev, TRACE_CMD, "Printer set %s\n",
(printer_state == Offline ? "offline" : "online"));
offline_pending = FALSE; /* the operation completed */
return TRUE; /* successfully */
}
/* Load the VFU.
The printer VFU is loaded either with a custom tape image stored in the file
associated with the stream "vf" or with the standard 66-line tape if the
stream is NULL. The "uptr" parameter points to the printer unit.
The standard VFU tape (02607-80024 for the 8-channel HP 2607 and 02613-80001
for the 12-channel HP 2613, 2617, and 2618) defines the channels as:
Chan Description
---- --------------
1 Top of form
2 Bottom of form
3 Single space
4 Double space
5 Triple space
6 Half page
7 Quarter page
8 Sixth page
9 Bottom of form
...with channels 10-12 uncommitted.
If the DIAGNOSTIC mode is set, the standard tape is replaced with the
diagnostic tape (02613-80002 for the 2613 and 2617 or 02618-80002 for the
2618). This tape extends the standard VFU tape above as follows:
Chan Description
---- ----------------------------------------
10 Line before the bottom of form (line 59)
11 Line before the top of form (line 66)
12 Top of form (line 1)
The 2607 diagnostic mode uses the standard 8-channel tape.
A custom tape file starts with a VFU definition line and then contains one
channel-definition line for each line of the form. The number of lines
establishes the form length. Channel 1 must be dedicated to the top-of-form,
but the other channels may be defined as desired.
A semicolon appearing anywhere on a line begins a comment, and the semicolon
and all following characters are ignored. Zero-length lines, including lines
beginning with a semicolon, are ignored.
Note that a line containing one or more blanks is not a zero-length line, so,
for example, the line " ; a comment starting in column 2" is not ignored.
The first (non-ignored) line in the file is a VFU definition line of this
exact form:
VFU=<punch characters>,<no-punch character>{,<title>}
...where:
Parameter Description
------------------ -------------------------------------------------------
punch characters a string of one or more characters used interchangeably
to represent a punched location
no-punch character a single character representing a non-punched location
title an optional descriptive string printed by the SHOW LPT
VFU command ("Custom VFU" is used by default)
If the "VFU" line is missing or not of the correct form, then "Format error"
status is returned, and the VFU tape is not changed.
The remaining (non-ignored) lines define the channels punched for each line
of the printed form. The line format consists of a sequence of punch,
no-punch, and "other" characters in channel order. Each punch or no-punch
character defines a channel state, starting with channel 1 and proceeding
left-to-right until all channels for the VFU are defined; if the line
terminates before all channels are defined, the remaining channels are set to
the no-punch state. Any "other" characters (i.e., neither a punch character
nor a no-punch character) are ignored and may be used freely to delineate the
tape channels.
Examples using the standard 66-line tape definition for an 8-channel VFU:
; the VFU definition | VFU=1234578, ; no-punch is a ' '
VFU=1,0,a binary tape image |
| 1 ; top of form
; the channel definitions | 345 ; form line 1
| 3 ; form line 2
10111111 ; top of form | 34 ; form line 3
00100000 ; single space | 3 5 ; form line 4
0011 ; channels 5-8 no-punch | 34 ; form line 5
|
-------------------------------------+-------------------------------------
|
VFU=X,-,blanks are "others" | VFU=TO,.,brackets are "others"
| ; 1 2 3 4 5 6 7 8
X - X X X X X X ; line 1 | ;--- --- --- --- --- --- --- ---
- - X - - - - - ; line 2 | [T] . [O] [O] [O] [O] [O] [O]
- - X X - - - - ; line 3 | . . [O] . . . . .
| . . [O] [O] . . . .
On entry, the "vf" parameter determines whether the standard tape or a custom
tape is to be loaded. If "vf" is NULL, a standard 66-line tape is generated
and stored in the tape buffer. Otherwise, a custom tape file is read,
parsed, and assembled VFU entries are stored in the tape buffer. After
generation or a successful tape load, the buffer is copied to the VFU array,
the form length is set, the current line is reset to the top-of-form, and the
state of VFU channels 9 and 12 are set into the device status.
Implementation notes:
1. VFU array entries 1-n correspond to form print lines 1-n. Entry 0 is the
logical OR of all of the other entries and is used during VFU format
command processing to determine if a punch is present somewhere in a
given channel.
2. In DIAGNOSTIC mode, the 2613/17/18 diagnostic tape is generated
unconditionally, as the 2607 will use only the eight standard channels.
*/
static t_stat lp_load_vfu (UNIT *uptr, FILE *vf)
{
const PRINTER_TYPE model = GET_MODEL (uptr->flags); /* the printer model number */
uint32 line, channel;
int32 len;
char buffer [LINE_SIZE], punch [LINE_SIZE], no_punch;
char *bptr, *tptr;
uint16 tape [VFU_SIZE] = { 0 };
if (vf == NULL) { /* if the standard VFU is requested */
tape [ 1] = VFU_CHANNEL_1; /* then punch channel 1 for the top of form */
tape [60] = VFU_CHANNEL_2 | VFU_CHANNEL_9; /* and channels 2 and 9 for the bottom of form */
for (line = 1; line <= 60; line++) { /* load each of the 60 printable lines */
tape [line] |= VFU_CHANNEL_3 /* punch channel 3 for single space */
| (line % 2 == 1 ? VFU_CHANNEL_4 : 0) /* punch channel 4 for double space */
| (line % 3 == 1 ? VFU_CHANNEL_5 : 0) /* punch channel 5 for triple space */
| (line % 30 == 1 ? VFU_CHANNEL_6 : 0) /* punch channel 6 for next half page */
| (line % 15 == 1 ? VFU_CHANNEL_7 : 0) /* punch channel 7 for next quarter page */
| (line % 10 == 1 ? VFU_CHANNEL_8 : 0); /* punch channel 8 for next sixth page */
tape [0] |= tape [line]; /* accumulate the channel punches */
}
if (lpt_dev.flags & DEV_DIAG) { /* if the device is in diagnostic mode */
strcpy (vfu_title, "Diagnostic VFU"); /* then install the diagnostic VFU tape */
tape [59] |= VFU_CHANNEL_10; /* punch channel 10 on line 59 */
tape [66] |= VFU_CHANNEL_11; /* punch channel 11 on line 66 */
tape [ 1] |= VFU_CHANNEL_12; /* punch channel 12 on line 1 */
tape [0] |= VFU_CHANNEL_10 /* add the additional channel punches */
| VFU_CHANNEL_11 /* to the accumulator */
| VFU_CHANNEL_12;
}
else /* otherwise the device is in standard mode */
strcpy (vfu_title, "Standard VFU"); /* so install the standard VFU tape */
form_length = 66; /* set the form length */
}
else { /* otherwise load a custom VFU from the file */
len = lp_read_line (vf, buffer, sizeof buffer); /* read the first line */
if (len <= 0 /* if there isn't one */
|| strncmp (buffer, "VFU=", strlen ("VFU=")) != 0) { /* or it's not a VFU definition statement */
cputs ("Missing VFU definition line\n"); /* then complain to the console */
return SCPE_FMT; /* and fail with a format error */
}
bptr = buffer + strlen ("VFU="); /* point at the first control argument */
tptr = strtok (bptr, ","); /* parse the punch token */
if (tptr == NULL) { /* if it's missing */
cputs ("Missing punch field in the VFU control line\n"); /* then complain to the console */
return SCPE_FMT; /* and fail with a format error */
}
strcpy (punch, tptr); /* save the set of punch characters */
tptr = strtok (NULL, ","); /* parse the no-punch token */
if (tptr == NULL){ /* if it's missing */
cputs ("Missing no-punch field in the VFU control line\n"); /* then complain to the console */
return SCPE_FMT; /* and fail with a format error */
}
no_punch = *tptr; /* save the no-punch character */
tptr = strtok (NULL, ","); /* parse the optional title */
if (tptr != NULL) /* if it's present */
strcpy (vfu_title, tptr); /* then save the user's title */
else /* otherwise */
strcpy (vfu_title, "Custom VFU"); /* use a generic title */
for (line = 1; line <= VFU_MAX; line++) { /* load up to the maximum VFU tape length */
len = lp_read_line (vf, buffer, sizeof buffer); /* read a tape definition line */
if (len <= 0) /* if at the EOF or an error occurred */
break; /* then the load is complete */
tptr = buffer; /* point at the first character of the line */
channel = VFU_CHANNEL_1; /* set the channel 1 indicator */
while (channel != 0 && *tptr != '\0') { /* loop until the channel or definition is exhausted */
if (strchr (punch, *tptr) != NULL) { /* if the character is in the punch set */
tape [line] |= channel; /* then punch the current channel */
channel = channel >> 1; /* and move to the next one */
}
else if (*tptr == no_punch) /* otherwise if the character is the no-punch character */
channel = channel >> 1; /* then move to the next channel */
tptr++; /* otherwise the character is neither, so ignore it */
}
tape [0] |= tape [line]; /* accumulate the channel punches */
}
if ((tape [1] & VFU_CHANNEL_1) == 0) { /* if there is no channel 1 punch in the first line */
cputs ("Missing punch in channel 1 of line 1\n"); /* then complain to the console */
return SCPE_FMT; /* and fail with a format error */
}
form_length = line - 1; /* set the form length */
}
memcpy (VFU, tape, sizeof VFU); /* copy the tape buffer to the VFU array */
current_line = 1; /* reset the line counter to the top of the form */
status_word &= ~(ST_VFU_9 | ST_VFU_12); /* assume no punches for channels 9 and 12 */
if (print_props [model].vfu_channels > 8) { /* if the printer VFU has more than 8 channels */
if (VFU [1] & VFU_CHANNEL_9) /* then if channel 9 is punched for this line */
status_word |= ST_VFU_9; /* then report it in the device status */
if (VFU [1] & VFU_CHANNEL_12) /* if channel 12 is punched for this line */
status_word |= ST_VFU_12; /* then report it in the device status */
}
return SCPE_OK; /* the VFU was successfully loaded */
}
/* Read a line from the VFU file.
This routine reads a line from the VFU tape image file designated by the file
stream parameter "vf", stores the data in the string buffer pointed to by
"line" and whose size is given by "size", and returns the length of that
string. Comments are stripped from lines that are read, and the routine
continues to read until a non-zero-length line is found. If the end of the
file was reached, the return value is 0. If a file error occurred, the
return value is -1.
Implementation notes:
1. The routine assumes that the file was opened in text mode, so that
automatic CRLF-to-LF conversion is done if needed. This simplifies
the end-of-line removal.
*/
static int32 lp_read_line (FILE *vf, char *line, uint32 size)
{
char *result;
int32 len = 0;
while (len == 0) {
result = fgets (line, size, vf); /* get the next line from the file */
if (result == NULL) /* if an error occurred */
if (feof (vf)) /* then if the end of file was seen */
return 0; /* then return an EOF indication */
else { /* otherwise */
report_error (vf); /* report the error to the console */
return -1; /* and return an error indication */
}
len = strlen (line); /* get the current line length */
if (len > 0 && line [len - 1] == '\n') /* if the last character is a newline */
line [--len] = '\0'; /* then remove it and decrease the length */
result = strchr (line, ';'); /* search for a comment indicator */
if (result != NULL) { /* if one was found */
*result = '\0'; /* then truncate the line at that point */
len = (int32) (result - line); /* and recalculate the line length */
}
}
return len;
}