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