/* hp3000_lp.c: HP 3000 30209A Line Printer Interface simulator | |
Copyright (c) 2016, J. David Bryan | |
Permission is hereby granted, free of charge, to any person obtaining a copy | |
of this software and associated documentation files (the "Software"), to deal | |
in the Software without restriction, including without limitation the rights | |
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
copies of the Software, and to permit persons to whom the Software is | |
furnished to do so, subject to the following conditions: | |
The above copyright notice and this permission notice shall be included in | |
all copies or substantial portions of the Software. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN | |
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
Except as contained in this notice, the name of the author shall not be used | |
in advertising or otherwise to promote the sale, use or other dealings in | |
this Software without prior written authorization from the author. | |
LP HP 30209A Line Printer Interface | |
12-Sep-16 JDB Changed DIB register macro usage from SRDATA to DIB_REG | |
03-Sep-16 JDB Added power-fail detection | |
08-Jul-16 JDB Added REG entry to save the transfer unit wait field | |
Extended "lp_show_vfu" to show the VFU channel definitions | |
01-Jul-16 JDB First release version | |
27-Apr-16 JDB Passes the On-Line HP Line Printers Verification (D466A) | |
19-Apr-16 JDB Passes the universal interface diagnostic (D435A) | |
24-Mar-16 JDB Created | |
References: | |
- 30051A Universal Interface (Differential) Maintenance Manual | |
(30051-90001, May 1976) | |
- Installation and Service Manual for Line Printer Subsystems | |
(30209-90006, May 1976) | |
- Line Printer Operating and Programming Manual | |
(30209-90008, June 1976) | |
- HP 3000 Series III Engineering Diagrams Set | |
(30000-90141, April 1980) | |
The HP 30118A, 30127A, 30128A, and 30133A Line Printer Subsystems connect the | |
2607A, 2613A, 2618A, and 2617A printers, respectively, to the HP 3000. Each | |
subsystem consists of a 30209A Line Printer Controller, employing a 30051A | |
Universal Interface (Differential) and interconnecting cable, and an HP | |
2607A (200 lines per minute), HP 2613 (300 lpm), HP 2617 (600 lpm), or HP | |
2618 (1250 lpm) line printer. These subsystems employ the Multiplexer | |
Channel to achieve a 360 KB/second transfer rate from the CPU. | |
This module simulates three hardware devices: | |
- the HP 30051A Universal Interface (Differential) | |
- the HP 30049C Diagnostic Hardware Assembly | |
- the HP 2607A/13A/17A/18A line printer and 30209-60004 printer cable | |
Available with either differential or TTL I/O logic levels, the Universal | |
Interface (UI) provides a 16-bit bidirectional parallel connection between a | |
device and the HP 3000 system. Both direct and programmed I/O via the | |
Multiplexer Channel are supported, word or byte transfers may be selected, | |
and byte packing and unpacking is available. In addition to the 16-bit data | |
path, a five-bit control word is supplied to the device, and an eight-bit | |
status word is returned. Flexible configuration of interface operation is | |
provided via ten jumpers, and eight different interrupt sources are | |
available. The Universal Interface is also used to connect the paper tape | |
reader, punch, and card reader to the HP 3000. | |
The Diagnostic Hardware Assembly (DHA) connects to the UI device connectors | |
and provides a programmable loopback and configuration capability. Five LEDs | |
continuously display the device control word, and test points are provided to | |
monitor the state of the 16-bit data path, the ten programmable jumper | |
settings, and the Device Command, Device Flag, and Device End signals. | |
Enabling the diagnostic mode simulates the installation of the DHA in place | |
of the printer device cable. | |
The interface supports a single line printer. The supported printers are | |
configured with Option 001, which provides a 128 (2607) or 96 (2613/17/18) | |
character set. Two output modes are provided: an expanded mode that is | |
suitable for retaining printer output as a text file, and a compact mode that | |
is suitable for sending the printer output to a host-connected physical | |
printer. An 8-channel (2607) or 12-channel (2613/17/18) Vertical Format Unit | |
is supported, and custom VFU tape images may be loaded from properly | |
formatted host-system text files. | |
The printer supports realistic and optimized (fast) timing modes. Realistic | |
timing attempts to model the print buffer load and print-and-space operation | |
delays inherent in the physical hardware. For example, in REALTIME mode, | |
output of longer lines takes more time than output of shorter lines, and | |
spacing six lines takes approximately six times longer than spacing one line. | |
In FASTTIME mode, all timings are reduced to be "just long enough" to satisfy | |
MPE software driver requirements. | |
In hardware, the ten UI configuration jumpers perform these functions: | |
Jumper Interpretation when removed Interpretation when installed | |
------ ------------------------------- ------------------------------ | |
W1 SR set by PCONTSTB SR set by Device Status bit 11 | |
W2 Flag asserts on leading edge Flag asserts on trailing edge | |
W3 Command uses response mode Command uses pulse mode | |
W4 inhibit IRQ on Device Status enable IRQ on Device Status | |
bit 8 leading edge bit 8 leading edge | |
W5 DATA IN latched on Flag DATA IN always transparent | |
W6 Flag denies on trailing edge Flag denies on leading edge | |
W7 normal byte-mode write transfer test byte-mode write transfer | |
W8 inhibit IRQ on Device Status enable IRQ on Device Status | |
bit 9 leading edge bit 9 leading edge | |
W9 inhibit IRQ on Device Status enable IRQ on Device Status | |
bit 10 trailing edge bit 10 trailing edge | |
W10 DEV CMD polarity is normal DEV CMD polarity is inverted | |
The line printer cable is wired with this configuration: | |
Interface Connection Printer Connection | |
---------------------------------------- ------------------ | |
Data Out bit 15 DATA 1 | |
Data Out bit 14 DATA 2 | |
Data Out bit 13 DATA 3 | |
Data Out bit 12 DATA 4 | |
Data Out bit 11 DATA 5 | |
Data Out bit 10 DATA 6 | |
Data Out bit 9 DATA 7 | |
Device Command STROBE | |
Device Flag ~DEMAND | |
Control Word bit 10 PAPER INSTRUCTION | |
Device Status bit 9 ONLINE | |
Device Status bit 10 ONLINE | |
Device Status bit 11 ~READY | |
Device Status bit 12 VFU CHANNEL 12 | |
Device Status bit 13 VFU CHANNEL 9 | |
Device End ~ONLINE | |
Set Transfer Error Flip-Flop (no connection) | |
Master Clear MASTER CLEAR | |
Internal Connection Action | |
---------------------------------------- -------------------------------- | |
300 pF across the Write Delay One-Shot sets 1.2 uS pulse width | |
1500 pF across the Master Clear One-Shot sets 5.1 uS pulse width | |
jumper W4 shorted none (Status 8 is not connected) | |
jumper W8 shorted enables IRQ when ONLINE asserts | |
jumper W9 shorted enables IRQ when ONLINE denies | |
DEMAND is wired inversely to Device Flag, so DEMAND assertion is Device Flag | |
denial and vice versa. DEMAND dropping after STROBE assertion corresponds | |
with Device Flag asserting after Device Command asserts, and DEMAND asserting | |
after the printer is ready corresponds to Device Flag denying. | |
Similarly, ONLINE is wired inversely to Device End, so the printer going | |
offline asserts Device End, and READY is wired inversely to Device Status bit | |
11, so bit 11 is asserted when the printer is not ready (either powered off | |
or out of paper). | |
The READY and ONLINE signals indicate the current state of the printer. | |
READY asserts when printer power is on, no alarm condition (paper out, tape | |
format error) exits, and the VFU has been initialized. ONLINE asserts when | |
READY is asserted and the Online button is pressed. Therefore: | |
~ONLINE * ~READY = paper out or VFU error | |
~ONLINE * READY = paper loaded and offline | |
ONLINE * ~READY = (prohibited) | |
ONLINE * READY = paper loaded and online | |
The printer DEMAND signal asserts when the printer is ready for data and | |
denies when it is printing or slewing. It also denies when the printer goes | |
offline. DEMAND is cross-connected to the Device Flag differential input, | |
so that DEV FLAG is the complement of DEMAND, i.e., it asserts when the | |
printer is busy and denies when the printer is available. | |
The normal sequence starts with DEMAND asserted (i.e., DEV FLAG denied). The | |
interface asserts STROBE (DEV CMD), the printer denies DEMAND (asserts DEV | |
FLAG), the interface denies STROBE (DEV CMD), and the printer then asserts | |
DEMAND (denies DEV FLAG) when the character data is accepted or the print | |
operation is complete. | |
When the ON/OFFLINE button on the printer is pressed, the printer will not go | |
offline (i.e., deny the ONLINE signal) if there are characters in the print | |
buffer. Instead, the offline condition is held off until an internal "allow | |
offline" signal asserts. This occurs when the print buffer is empty and the | |
print cycle is inactive. When ONLINE denies, DEMAND is inhibited, so the | |
interface waits at the end of the handshake sequence for DEV FLAG to deny. | |
Note that this holds off SR to the Multiplexer Channel, so the channel | |
program waits. When the printer is put back online, DEMAND asserts, so DEV | |
FLAG denies, the handshake completes, SR asserts, and the interface returns | |
to the idle condition to await the next command. | |
This has implications for the SET OFFLINE and DETACH commands if they are | |
issued while the print buffer contains data or the printer unit is busy | |
executing a print action. | |
The SET LP OFFLINE and SET LP DETACH 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 deferred." A | |
SHOW LP will show that the device is still online and attached. Once | |
simulation is resumed and the print operation completes, the printer is set | |
offline or detached as requested. No console message reports this, as it is | |
assumed that the executing program will detect the condition and report | |
accordingly. A subsequent SHOW LP will indicate the new status. | |
A SET LP ONLINE or ATTACH LP command when the corresponding deferred-action | |
flag is set simply clears the flag. In particular, an ATTACH LP command must | |
not specify a new image filename; if one is specified while a deferred detach | |
is in progress, the routine returns SCPE_NOFNC ("Command not allowed"). | |
A RESET LP command also clears the deferred-action flags and so clears any | |
pending offline or detach. However, it also clears the print buffer and | |
terminates any print action in progress, so a SET LP OFFLINE or DETACH LP | |
will succeed if issued subsequently. | |
The Universal Interface responds to both direct I/O and programmed I/O from | |
the Multiplexer Channel, as follows: | |
Control Word Format (CIO and SIO Control word 2): | |
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| M | R | irq reset | A | device control | X | S | B | I | T | device | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| M | R | irq reset | A | function | E | X | S | B | I | T | DHA | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| M | R | irq reset | A | - - - - | F | X | S | B | I | T | printer | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
Where: | |
M = programmed master clear | |
R = reset interrupts | |
A = acquire data from device | |
E = enable diagnostic hardware assembly function | |
F = printer output character/format (0/1) code | |
X = enable data transfer interrupt | |
S = interrupt/device (0/1) status | |
B = word/byte (0/1) transfer | |
I = enable interrupts | |
T = enable transfer timer | |
IRQ Reset: | |
000 = none | |
001 = transfer timer and transfer error | |
010 = I/O system | |
011 = clear interface | |
100 = data transfer completion | |
101 = line ready (device status bit 8) | |
110 = ready (device status bit 9) | |
111 = not ready (device status bit 10) | |
DHA Function: | |
0000 = clear configuration registers (installs jumpers) | |
0001 = remove jumper J2W2 | |
0010 = assert DEV END | |
0011 = remove jumper J2W8 | |
0100 = set Transfer Error flip-flop | |
0101 = remove jumper J2W4 | |
0110 = remove jumper J2W10 | |
0111 = remove jumper J2W6 | |
1000 = DEV FLAG follows DEV CMD or Control 6 (0/1) | |
1001 = remove jumper J2W5 | |
1010 = assert CLEAR INTERFACE | |
1011 = remove jumper J2W9 | |
1100 = Status 8-10 follow Control 6-8 | |
or master clear, power on, and power fail (0/1) | |
1101 = remove jumper J2W1 | |
1110 = remove jumper J2W3 | |
1111 = remove jumper J2W7 | |
Bits 6-10 are the device control bits. For the DHA, control bit 10 enables | |
the function decoder. The decoder is combinatorial and the registers are | |
"ones-catching," so the function field must be set and then maintained while | |
bit 10 is asserted and then denied. For the line printer, control bit 10 | |
indicates whether character data (0) or format commands (1) will be output. | |
Programmed control word 1 (IOCW) is not used. | |
Setting control bit 15 starts (or restarts) the five-second transfer timer. | |
Issuing a Reset Transfer Timer and Transfer Error Interrupts, a Master Reset, | |
or a Reset Interrupts command stops the timer. If the timer expires, a | |
Transfer Timer interrupt occurs. | |
Status Word Format (TIO and SIO Status): | |
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| S | D | I | seqct | F | 0 | 0 | dev irq | X | C | Y | E | T | interrupt | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| S | D | I | seqct | F | 1 | 0 | device status | device | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| S | D | I | seqct | F | 1 | 0 | - | L | L | N | V | U | - | - | printer | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
Where: | |
S = SIO OK | |
D = direct I/O OK | |
I = interrupt pending | |
F = device flag | |
X = data transfer interrupt | |
C = clear interface interrupt | |
Y = I/O system interrupt | |
E = transfer error interrupt | |
T = transfer timer interrupt | |
L = online | |
N = not ready | |
V = VFU channel 12 | |
U = VFU channel 9 | |
Sequence Counter: | |
00 = idle | |
10 = request to device issued for word or 1st byte | |
11 = device operation started | |
01 = request to device issued for 2nd byte | |
Device Interrupt Request Bits: | |
8 = device status bit 8 interrupt (not used by the printer) | |
9 = device status bit 9 interrupt (printer went online) | |
10 = device status bit 10 interrupt (printer went offline) | |
Control word bit 12 determines whether the interrupt status word (0) or the | |
device status word (1) is returned. | |
A transfer error occurs when the channel asserts XFERERROR to abort a | |
transfer for a parity error or memory address out of bounds. Device status | |
bits assume the logic 1 state with the inputs disconnected (e.g., power off). | |
Output Data Word Format (WIO and SIO Write): | |
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| - | 1st ASCII character | - | 2nd ASCII character | byte mode | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| - - - - - - - - | - | ASCII character | word mode | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| - - - - - - - - | - | format word | format | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
The printer only uses seven data bits, so the MSB of each byte is ignored. | |
If the printer's line length is exceeded during write operations, the | |
buffered line will be printed, the paper will be advanced one line, and the | |
buffer will be cleared to accept the character causing the overflow. | |
Implementation notes: | |
1. The Clear Interface Logic (CLRIL) signal inhibits SIO OK status. The | |
Card Reader/Punch Interface version of the UI does not assert the CLRIL | |
signal in response to external CLEAR INTERFACE assertion; only a transfer | |
error does. Therefore, the SIO OK signal is not inhibited while the | |
clear interface interrupt is present. For this version, the external | |
Clear Interface signal sets the CLR INF flip-flop, which sets the (C) bit | |
in the status register and generates an interrupt, but otherwise has no | |
effect on the interface logic. | |
The standard version of the UI asserts CLRIL, and therefore inhibits SIO | |
OK, for both the clear interface and transfer error conditions. | |
2. Because the interface uses differential interface logic, the external | |
sense of a signal may be inverted by exchanging the + and - connections. | |
To accommodate this in simulation, separate variables are used for the | |
internal and external states. For example, "device_command" represents | |
the internal state, while "device_command_out" represents the external | |
state (which may be inverted from the internal state if jumper J2W10 is | |
installed). | |
3. The Universal Interface supports terminating channel transfers by | |
asserting DEVEND, and the line printer cable connects the ONLINE output | |
inversely to the Device End input, so that it is asserted when the | |
printer is offline. However, when the printer goes offline, it holds its | |
DEMAND line denied, which keeps Device Flag asserted. This hangs the | |
transfer handshake in the Device_Flag_1/2 state until the printer goes | |
online again. As the interface recognizes Device End only in the | |
Device_Command_1/2 state, DEVEND will never be asserted to terminate a | |
channel transfer. | |
4. In hardware, a paper-out condition is noted, but the line printer does | |
not go offline until the top of the next form is reached. This ensures | |
that the current page is completed first. By contrast, a torn-paper | |
condition causes the printer to go offline at the completion of the | |
current line. In simulation, a DETACH is handled as a torn-paper | |
condition. | |
5. Slewing in expanded mode is performed by appending CR LF pairs to the | |
character buffer and then writing the combined buffer to the printer | |
output file. The size of the buffer must accommodate the largest print | |
line (136 characters) plus the largest possible slew (144 lines * 2 | |
characters per line). | |
*/ | |
#include "hp3000_defs.h" | |
#include "hp3000_io.h" | |
/* Interface program constants */ | |
#define PULSE_TIME uS (8) /* device command pulse = 8 microseconds */ | |
#define XFER_TIME S (5) /* transfer timeout = 5 seconds */ | |
/* Printer program constants */ | |
#define CR '\r' /* carriage return */ | |
#define LF '\n' /* line feed */ | |
#define FF '\f' /* form feed */ | |
#define DEL '\177' /* delete */ | |
#define DATA_MASK 0177u /* printer uses only 7 bits for data */ | |
#define FORMAT_VFU 0100u /* printer VFU selector */ | |
#define FORMAT_MASK 0117u /* printer format command mask for 12-channel VFU */ | |
#define FORMAT_VFU_8_MASK 0107u /* printer format command mask for 8-channel VFU */ | |
#define FORMAT_SUPPRESS 0000u /* format code to slew 0 lines */ | |
#define FORMAT_VFU_CHAN_1 0100u /* format code to slew to VFU channel 1 */ | |
#define FORMAT_VFU_BIAS 0077u /* bias converting from format code to channel number */ | |
#define VFU_MAX 144 /* maximum number of VFU form lines */ | |
#define VFU_SIZE (VFU_MAX + 1) /* size of the VFU array */ | |
#define LINE_SIZE 256 /* size of the character array used to read the VFU file */ | |
#define VFU_WIDTH 12 /* maximum number of VFU channels */ | |
#define VFU_CHANNEL_1 04000u /* top of form */ | |
#define VFU_CHANNEL_2 02000u /* bottom of form */ | |
#define VFU_CHANNEL_3 01000u /* single space */ | |
#define VFU_CHANNEL_4 00400u /* double space */ | |
#define VFU_CHANNEL_5 00200u /* triple space */ | |
#define VFU_CHANNEL_6 00100u /* half page */ | |
#define VFU_CHANNEL_7 00040u /* quarter page */ | |
#define VFU_CHANNEL_8 00020u /* sixth page */ | |
#define VFU_CHANNEL_9 00010u /* bottom of form */ | |
#define VFU_CHANNEL_10 00004u /* (unassigned) */ | |
#define VFU_CHANNEL_11 00002u /* (unassigned) */ | |
#define VFU_CHANNEL_12 00001u /* (unassigned) */ | |
#define CHARS_MAX 136 /* maximum number of characters buffered by the printers */ | |
#define BUFFER_SIZE (CHARS_MAX + VFU_MAX * 2) /* max chars + max VFU * 2 (CR LF) */ | |
#define PRINTER_JUMPERS (W4 | W8 | W9) /* jumpers J2W4, J2W8, and J2W9 are installed */ | |
/* Debug flags */ | |
#define DEB_CMD (1u << 0) /* trace controller commands */ | |
#define DEB_CSRW (1u << 1) /* trace command initiations and completions */ | |
#define DEB_STATE (1u << 2) /* trace device handshake state changes */ | |
#define DEB_SERV (1u << 3) /* trace channel service scheduling calls */ | |
#define DEB_XFER (1u << 4) /* trace data transmissions */ | |
#define DEB_IOB (1u << 5) /* trace I/O bus signals and data words */ | |
/* Device flags */ | |
#define DEV_DIAG_SHIFT (DEV_V_UF + 0) /* Diagnostic Hardware Assembly is installed */ | |
#define DEV_REALTIME_SHIFT (DEV_V_UF + 1) /* timing mode is realistic */ | |
#define DEV_DIAG (1u << DEV_DIAG_SHIFT) /* diagnostic mode flag */ | |
#define DEV_REALTIME (1u << DEV_REALTIME_SHIFT) /* realistic timing flag */ | |
/* Printer unit flags */ | |
#define UNIT_MODEL_SHIFT (UNIT_V_UF + 0) /* bits 0-2: printer model ID */ | |
#define UNIT_EXPAND_SHIFT (UNIT_V_UF + 3) /* bits 3-3: printer uses expanded output */ | |
#define UNIT_OFFLINE_SHIFT (UNIT_V_UF + 4) /* bits 4-4: printer is offline */ | |
#define UNIT_MODEL_MASK 0000007u /* model ID mask */ | |
#define UNIT_MODEL (UNIT_MODEL_MASK << UNIT_MODEL_SHIFT) | |
#define UNIT_EXPAND (1u << UNIT_EXPAND_SHIFT) | |
#define UNIT_OFFLINE (1u << UNIT_OFFLINE_SHIFT) | |
#define UNIT_ONLINE 0 | |
#define UNIT_2607 (HP_2607 << UNIT_MODEL_SHIFT) | |
#define UNIT_2613 (HP_2613 << UNIT_MODEL_SHIFT) | |
#define UNIT_2617 (HP_2617 << UNIT_MODEL_SHIFT) | |
#define UNIT_2618 (HP_2618 << UNIT_MODEL_SHIFT) | |
/* Unit flags accessor */ | |
#define GET_MODEL(f) ((PRINTER_TYPE) (((f) >> UNIT_MODEL_SHIFT) & UNIT_MODEL_MASK)) | |
/* Unit references */ | |
#define xfer_unit lp_unit [0] /* transfer handshake unit */ | |
#define xfer_uptr (&lp_unit [0]) /* transfer handshake unit pointer */ | |
#define pulse_uptr (&lp_unit [1]) /* pulse timer unit pointer */ | |
#define timer_uptr (&lp_unit [2]) /* transfer timer unit pointer */ | |
static const char *const unit_name [] = { /* unit names, indexed by unit number */ | |
"Transfer", | |
"Pulse", | |
"Watchdog" | |
}; | |
/* Printer types */ | |
typedef enum { | |
HP_2607, /* HP 2607A */ | |
HP_2613, /* HP 2613A */ | |
HP_2617, /* HP 2617A */ | |
HP_2618 /* HP 2618A */ | |
} PRINTER_TYPE; | |
/* Printer locality states */ | |
typedef enum { | |
Offline, /* printer is going offline */ | |
Online /* printer is going online */ | |
} LOCALITY; | |
/* Printer properties table. | |
This table contains the characteristics that vary between printer models. | |
The "char_set" field values reflect printer Option 001, 96/128-character set. | |
*/ | |
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 overprints; /* TRUE if the printer supports overprinting */ | |
} PRINTER_PROPS; | |
static const PRINTER_PROPS print_props [] = { /* printer properties, indexed by PRINTER_TYPE */ | |
/* line char VFU over */ | |
/* length set channels prints */ | |
/* ------ ----- -------- ------ */ | |
{ 132, 128, 8, FALSE }, /* HP_2607 */ | |
{ 136, 96, 12, TRUE }, /* HP_2613 */ | |
{ 136, 96, 12, TRUE }, /* HP_2617 */ | |
{ 132, 96, 12, 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 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 variances are not simulated. | |
*/ | |
typedef struct { | |
int32 buffer_load; /* per-character transfer time */ | |
int32 print; /* print time */ | |
int32 advance; /* paper advance time per line */ | |
} DELAY_PROPS; | |
static const DELAY_PROPS real_times [] = { /* real-time delays, indexed by PRINTER_TYPE */ | |
/* buffer paper */ | |
/* load print advance */ | |
/* --------- -------- --------- */ | |
{ uS (12.6), mS (260), mS (40.1) }, /* HP_2607 200 lines per minute */ | |
{ uS (1.75), mS (183), mS (8.33) }, /* HP_2613 300 lines per minute */ | |
{ uS (1.75), mS ( 86), mS (6.67) }, /* HP_2617 600 lines per minute */ | |
{ uS (1.75), mS ( 38), mS (4.76) } /* HP_2618 1250 lines per minute */ | |
}; | |
#define LP_BUFFER_LOAD uS (1) /* fast per-character transfer time */ | |
#define LP_PRINT mS (1) /* fast print time */ | |
#define LP_ADVANCE uS (50) /* fast paper advance time per line */ | |
static DELAY_PROPS fast_times = /* FASTTIME delays */ | |
{ LP_BUFFER_LOAD, | |
LP_PRINT, | |
LP_ADVANCE | |
}; | |
/* Data transfer handshake sequencer. | |
The sequencer controls the handshake that transfers data between the | |
interface and the device. | |
*/ | |
typedef enum { | |
Idle, /* the device is idle */ | |
Device_Command_1, /* device command is asserted for a word or first byte */ | |
Device_Flag_1, /* device flag is asserted for a word or first byte */ | |
Device_Command_2, /* device command is asserted for the second byte */ | |
Device_Flag_2 /* device flag is asserted for the second byte */ | |
} SEQ_STATE; | |
static const char *const state_name [] = { /* sequencer state names, indexed by SEQ_STATE */ | |
"Idle", | |
"Device Command 1", | |
"Device Flag 1", | |
"Device Command 2", | |
"Device Flag 2" | |
}; | |
/* Configuration jumpers. | |
Various aspects of interface operation are configured by installing or | |
removing jumpers contained within the connector hood of the device | |
interconnection cable. Jumpers are simulated by bits in the "jumper_set" | |
word, with a 1 value representing "installed" and a 0 value representing | |
"removed" (although in hardware installing a jumper pulls the corresponding | |
signal down to 0). | |
The Diagnostic Hardware Assembly provides programmatic configuration of the | |
jumpers. All jumpers are installed by executing a "clear registers" command, | |
and then individual jumpers may be removed by executing the corresponding | |
"remove jumper J2Wn" commands. This is simulated by setting all of the bits | |
in the "jumper_set" word and then selectively ANDing the word with | |
complemented constants from the "jumper_map" table, thereby clearing | |
individual bits. | |
Implementation notes: | |
1. In simulation, jumper W5 is not used. The DATA IN signals are always | |
latched when DEV FLAG asserts. Always-transparent operation is not | |
provided. | |
2. In hardware, DHA control word bits 6-9 are wired to decoder input bits | |
0-3. As the 3000 uses decreasing bit-number significance, while the | |
decoder chip uses increasing bit-number significance, the order of the | |
functions in the "jumper_map" table reflect the reversed bit order of the | |
index. For example, index 0001 contains the function for decoder output | |
8 (1000). | |
*/ | |
#define W1 (1u << 0) /* SR set by PCONTSTB/Device Status bit 11 */ | |
#define W2 (1u << 1) /* +/- edge of Device Flag advances sequence counter from 1 to 2 */ | |
#define W3 (1u << 2) /* Device Command operates in response/pulse mode */ | |
#define W4 (1u << 3) /* inhibit/enable interrupt on STAT8 + edge */ | |
#define W5 (1u << 4) /* Data In latched on sequence count 1 and 3/always transparent */ | |
#define W6 (1u << 5) /* -/+ edge of Device Flag advances sequence counter from 2 to 3 */ | |
#define W7 (1u << 6) /* normal/test write transfer */ | |
#define W8 (1u << 7) /* inhibit/enable interrupt on STAT9 + edge */ | |
#define W9 (1u << 8) /* inhibit/enable interrupt on STAT10 - edge */ | |
#define W10 (1u << 9) /* Device Command same/inverted polarity as Data Out */ | |
#define J2W1_INSTALLED ((jumper_set & W1) != 0) | |
#define J2W2_INSTALLED ((jumper_set & W2) != 0) | |
#define J2W3_INSTALLED ((jumper_set & W3) != 0) | |
#define J2W4_INSTALLED ((jumper_set & W4) != 0) | |
#define J2W5_INSTALLED ((jumper_set & W5) != 0) | |
#define J2W6_INSTALLED ((jumper_set & W6) != 0) | |
#define J2W7_INSTALLED ((jumper_set & W7) != 0) | |
#define J2W8_INSTALLED ((jumper_set & W8) != 0) | |
#define J2W9_INSTALLED ((jumper_set & W9) != 0) | |
#define J2W10_INSTALLED ((jumper_set & W10) != 0) | |
static const uint32 jumper_map [16] = { /* jumper removal map, indexed by CN_DHA_FN */ | |
0, /* 0000 = (unaffected) */ | |
~W2, /* 0001 = remove jumper J2W2 */ | |
0, /* 0010 = (unaffected) */ | |
~W8, /* 0011 = remove jumper J2W8 */ | |
0, /* 0100 = (unaffected) */ | |
~W4, /* 0101 = remove jumper J2W4 */ | |
~W10, /* 0110 = remove jumper J2W10 */ | |
~W6, /* 0111 = remove jumper J2W6 */ | |
0, /* 1000 = (unaffected) */ | |
~W5, /* 1001 = remove jumper J2W5 */ | |
0, /* 1010 = (unaffected) */ | |
~W9, /* 1011 = remove jumper J2W9 */ | |
0, /* 1100 = (unaffected) */ | |
~W1, /* 1101 = remove jumper J2W1 */ | |
~W3, /* 1110 = remove jumper J2W3 */ | |
~W7 /* 1111 = remove jumper J2W7 */ | |
}; | |
/* Diagnostic Hardware Assembly control register. | |
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| M | F | S | - - - | jumpers J2W10-J2W1 | | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
Where: | |
M = master reset has occurred | |
F = device flag follows device command/control 6 (0/1) | |
S = status 8-10 follow control 6-8/master clear-power on-power fail (0/1) | |
Implementation notes: | |
1. Jumper bits are defined as 0 = removed and 1 = installed. This is | |
the opposite of the DHA hardware, for which a zero output "installs" a | |
jumper. | |
2. Jumpers J2W10-J2W1, which are stored in the "jumpers" array, are mirrored | |
in the jumper control register to allow the diagnostic to test the full | |
set of jumpers with single assertions. Otherwise, ten assertions would | |
be necessary for each test. | |
*/ | |
#define DHA_MR 0100000u /* (M) a master reset has occurred */ | |
#define DHA_FLAG_SEL 0040000u /* (F) device flag follows control 6 */ | |
#define DHA_STAT_SEL 0020000u /* (S) status 8-10 follow master clear-power on-power fail */ | |
#define DHA_JUMPER_MASK 0001777u /* J2Wx jumpers mask */ | |
#define DHA_CLEAR 0001777u /* control register clear value (all jumpers installed) */ | |
/* Interface control word. | |
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| M | R | irq reset | A | device control | X | S | B | I | T | device | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| M | R | irq reset | A | function | E | X | S | B | I | T | DHA | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
*/ | |
#define CN_MR 0100000u /* (M) master reset */ | |
#define CN_RIN 0040000u /* (R) reset interrupt */ | |
#define CN_RIN_MASK 0034000u /* reset interrupt request selector mask */ | |
#define CN_RIN_XFR_TMR 0004000u /* reset watchdog timer and transfer error interrupts */ | |
#define CN_ACQUIRE 0002000u /* (A) acquire data from device */ | |
#define CN_DHA_FN_MASK 0001700u /* diagnostic hardware assembly function mask */ | |
#define CN_DHA_ST_MASK 0001600u /* diagnostic hardware assembly status mask */ | |
#define CN_DHA_FLAG 0001000u /* diagnostic hardware assembly device flag value */ | |
#define CN_DHA_FN_ENABLE 0000040u /* (E) enable diagnostic hardware assembly function */ | |
#define CN_XFR_IRQ_ENABLE 0000020u /* (X) enable data transfer interrupt */ | |
#define CN_DEVSTAT 0000010u /* (S) interrupt/device (0/1) status */ | |
#define CN_BYTE_XFER 0000004u /* (B) word/byte (0/1) transfer */ | |
#define CN_IRQ_ENABLE 0000002u /* (I) enable interrupts */ | |
#define CN_XFR_TMR_ENABLE 0000001u /* (T) enable data transfer timer */ | |
#define CN_RIN_SHIFT 11 /* reset interrupt request alignment shift */ | |
#define CN_DHA_ST_SHIFT 7 /* diagnostic hardware assembly status alignment shift */ | |
#define CN_DHA_FN_SHIFT 6 /* diagnostic hardware assembly function alignment shift */ | |
#define CN_RESET(c) (((c) & CN_RIN_MASK) >> CN_RIN_SHIFT) | |
#define CN_DHA_ST(c) (((c) & CN_DHA_ST_MASK) >> CN_DHA_ST_SHIFT) | |
#define CN_DHA_FN(c) (((c) & CN_DHA_FN_MASK) >> CN_DHA_FN_SHIFT) | |
static const char *const dha_fn_name [16] = { /* DHA function names, indexed by CN_DHA_FN */ | |
"clear registers", /* 0000 = clear registers (installs jumpers) */ | |
"remove J2W2", /* 0001 = remove jumper J2W2 */ | |
"assert DEVEND", /* 0010 = assert Device End */ | |
"remove J2W8", /* 0011 = remove jumper J2W8 */ | |
"set transfer error", /* 0100 = set Transfer Error flip-flop */ | |
"remove J2W4", /* 0101 = remove jumper J2W4 */ | |
"remove J2W10", /* 0110 = remove jumper J2W10 */ | |
"remove J2W6", /* 0111 = remove jumper J2W6 */ | |
"control 6 drives device flag", /* 1000 = connect device flag to control bit 6 */ | |
"remove J2W5", /* 1001 = remove jumper J2W5 */ | |
"assert CLRIF", /* 1010 = assert Clear Interface */ | |
"remove J2W9", /* 1011 = remove jumper J2W9 */ | |
"CLR/PON/PF drive status 8-10", /* 1100 = connect status 8-10 to master clear/power on/power fail */ | |
"remove J2W1", /* 1101 = remove jumper J2W1 */ | |
"remove J2W3", /* 1110 = remove jumper J2W3 */ | |
"remove J2W7" /* 1111 = remove jumper J2W7 */ | |
}; | |
static const char *const reset_irq_name [8] = { /* reset interrupt request names, indexed by CN_RESET */ | |
"", /* 000 = none */ | |
" | reset timer/xfer error irq", /* 001 = watchdog timer and transfer error */ | |
" | reset I/O system irq", /* 010 = I/O system */ | |
" | reset clear interface irq", /* 011 = clear interface */ | |
" | reset data xfer irq", /* 100 = data transfer completion */ | |
" | reset status 8 irq", /* 101 = device status 8 */ | |
" | reset status 9 irq", /* 110 = device status 9 */ | |
" | reset status 10 irq" /* 111 = device status 10 */ | |
}; | |
static const BITSET_NAME dha_control_names [] = { /* DHA control word names */ | |
"master clear", /* bit 0 */ | |
"clear interrupts", /* bit 1 */ | |
NULL, /* bit 2 */ | |
NULL, /* bit 3 */ | |
NULL, /* bit 4 */ | |
"acquire data", /* bit 5 */ | |
"DC6", /* bit 6 */ | |
"DC7", /* bit 7 */ | |
"DC8", /* bit 8 */ | |
"DC9", /* bit 9 */ | |
"enable function", /* bit 10 */ | |
"enable data xfer interrupt", /* bit 11 */ | |
"\1device status\0interrupt status", /* bit 12 */ | |
"\1byte xfer\0word xfer", /* bit 13 */ | |
"enable interrupts", /* bit 14 */ | |
"enable transfer timer" /* bit 15 */ | |
}; | |
static const BITSET_FORMAT dha_control_format = /* names, offset, direction, alternates, bar */ | |
{ FMT_INIT (dha_control_names, 0, msb_first, has_alt, no_bar) }; | |
/* Printer control word. | |
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| M | R | irq reset | - - - - - | F | X | S | B | I | T | printer | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
*/ | |
#define CN_FORMAT 0000040u /* printer output character/format (0/1) code */ | |
static const BITSET_NAME prt_control_names [] = { /* Printer control word names */ | |
"master clear", /* bit 0 */ | |
"clear interrupts", /* bit 1 */ | |
NULL, /* bit 2 */ | |
NULL, /* bit 3 */ | |
NULL, /* bit 4 */ | |
"acquire data", /* bit 5 */ | |
NULL, /* bit 6 */ | |
NULL, /* bit 7 */ | |
NULL, /* bit 8 */ | |
NULL, /* bit 9 */ | |
"\1format\0character", /* bit 10 */ | |
"enable data xfer interrupt", /* bit 11 */ | |
"\1device status\0interrupt status", /* bit 12 */ | |
"\1byte xfer\0word xfer", /* bit 13 */ | |
"enable interrupts", /* bit 14 */ | |
"enable transfer timer" /* bit 15 */ | |
}; | |
static const BITSET_FORMAT prt_control_format = /* names, offset, direction, alternates, bar */ | |
{ FMT_INIT (prt_control_names, 0, msb_first, has_alt, no_bar) }; | |
/* Interface status word. | |
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| S | D | I | seqct | F | 0 | 0 | dev irq | X | C | Y | E | T | interrupt | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| S | D | I | seqct | F | 1 | 0 | device status | device | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
Implementation notes: | |
1. The entry for bit 6 of the interrupt status names formatting array is | |
given in the alternate form to print the "interrupt status" string when | |
the bit is zero. | |
*/ | |
#define ST_SIO_OK 0100000u /* (S) SIO OK to use */ | |
#define ST_DIO_OK 0040000u /* (D) direct I/O OK to use */ | |
#define ST_IRQ_PENDING 0020000u /* (I) interrupt pending */ | |
#define ST_SEQ_COUNT_0 0000000u /* sequence count 0 (00) */ | |
#define ST_SEQ_COUNT_1 0010000u /* sequence count 1 (10) */ | |
#define ST_SEQ_COUNT_2 0014000u /* sequence count 2 (11) */ | |
#define ST_SEQ_COUNT_3 0004000u /* sequence count 3 (01) */ | |
#define ST_DEVFLAG 0002000u /* (F) device flag */ | |
#define ST_DEVSTAT 0001000u /* interrupt/device (0/1) status */ | |
#define ST_DEVIRQ_MASK 0000340u /* device interrupt request mask */ | |
#define ST_ST8_IRQ 0000200u /* device status 8 interrupt */ | |
#define ST_DHA_MR 0000200u /* diagnostic hardware assembly status 8 (master clear) */ | |
#define ST_ST9_IRQ 0000100u /* device status 9 interrupt */ | |
#define ST_DHA_PON 0000100u /* diagnostic hardware assembly status 9 (power on) */ | |
#define ST_ST10_IRQ 0000040u /* device status 10 interrupt */ | |
#define ST_DHA_NOT_PF 0000040u /* diagnostic hardware assembly status 10 (~power fail) */ | |
#define ST_DHA_DEVSTAT_MASK 0000037u /* diagnostic hardware assembly status 11-15 mask */ | |
#define ST_XFR_IRQ 0000020u /* (X) data transfer interrupt */ | |
#define ST_ST11_SR 0000020u /* device status 11 service request */ | |
#define ST_CLRIF_IRQ 0000010u /* (C) clear interface interrupt */ | |
#define ST_IOSYS_IRQ 0000004u /* (Y) I/O system interrupt */ | |
#define ST_XFERERR_IRQ 0000002u /* (E) transfer error interrupt */ | |
#define ST_XFR_TMR_IRQ 0000001u /* (T) transfer timer interrupt */ | |
#define ST_DEVIRQ_SHIFT 5 /* device status 8-10 interrupt request alignment shift */ | |
#define ST_DEVIRQ(n) ((n) << ST_DEVIRQ_SHIFT & ST_DEVIRQ_MASK) | |
#define ST_CLRIL (ST_CLRIF_IRQ | ST_XFERERR_IRQ) /* conditions that assert the CLRIL signal */ | |
static const uint32 sequence_counter [] = { /* externally visible sequencer values, indexed by SEQ_STATE */ | |
ST_SEQ_COUNT_0, /* 00 = Idle */ | |
ST_SEQ_COUNT_1, /* 10 = Device_Command_1 */ | |
ST_SEQ_COUNT_2, /* 11 = Device_Flag_1 */ | |
ST_SEQ_COUNT_3, /* 01 = Device_Command_2 */ | |
ST_SEQ_COUNT_0 /* 00 = Device_Flag_2 */ | |
}; | |
static const uint32 reset_irq [8] = { /* selective reset irq mask values, indexed by CN_RESET */ | |
~0u, /* 000 = none */ | |
~(ST_XFR_TMR_IRQ | ST_XFERERR_IRQ), /* 001 = watchdog timer and transfer error */ | |
~ST_IOSYS_IRQ, /* 010 = I/O system */ | |
~ST_CLRIF_IRQ, /* 011 = clear interface */ | |
~ST_XFR_IRQ, /* 100 = data transfer completion */ | |
~ST_ST8_IRQ, /* 101 = device status 8 */ | |
~ST_ST9_IRQ, /* 110 = device status 9 */ | |
~ST_ST10_IRQ /* 111 = device status 10 */ | |
}; | |
static const BITSET_NAME int_status_names [] = { /* Interrupt status word names */ | |
"SIO OK", /* bit 0 */ | |
"DIO OK", /* bit 1 */ | |
"interrupt", /* bit 2 */ | |
"SEQ 1", /* bit 3 */ | |
"SEQ 2", /* bit 4 */ | |
"device flag", /* bit 5 */ | |
"\1\0interrupt status", /* bit 6 */ | |
NULL, /* bit 7 */ | |
"status 8", /* bit 8 */ | |
"status 9", /* bit 9 */ | |
"status 10", /* bit 10 */ | |
"data xfer", /* bit 11 */ | |
"clear interface", /* bit 12 */ | |
"system", /* bit 13 */ | |
"transfer error", /* bit 14 */ | |
"transfer timeout" /* bit 15 */ | |
}; | |
static const BITSET_NAME dev_status_names [] = { /* Device status word names */ | |
"SIO OK", /* bit 0 */ | |
"DIO OK", /* bit 1 */ | |
"interrupt", /* bit 2 */ | |
"SEQ 1", /* bit 3 */ | |
"SEQ 2", /* bit 4 */ | |
"device flag", /* bit 5 */ | |
"device status", /* bit 6 */ | |
NULL, /* bit 7 */ | |
"DS8", /* bit 8 */ | |
"DS9", /* bit 9 */ | |
"DS10", /* bit 10 */ | |
"DS11", /* bit 11 */ | |
"DS12", /* bit 12 */ | |
"DS13", /* bit 13 */ | |
"DS14", /* bit 14 */ | |
"DS15" /* bit 15 */ | |
}; | |
static const BITSET_FORMAT int_status_format = /* names, offset, direction, alternates, bar */ | |
{ FMT_INIT (int_status_names, 0, msb_first, has_alt, no_bar) }; | |
static const BITSET_FORMAT dev_status_format = /* names, offset, direction, alternates, bar */ | |
{ FMT_INIT (dev_status_names, 0, msb_first, no_alt, no_bar) }; | |
/* Printer status word. | |
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| S | D | I | seqct | F | 1 | 0 | - | L | L | N | V | U | - | - | printer | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
*/ | |
#define ST_ONLINE 0000140u /* online */ | |
#define ST_NOT_READY 0000020u /* not ready */ | |
#define ST_VFU_12 0000010u /* VFU channel 12 */ | |
#define ST_VFU_9 0000004u /* VFU channel 9 */ | |
static const BITSET_NAME prt_status_names [] = { /* Printer status word names */ | |
"SIO OK", /* bit 0 */ | |
"DIO OK", /* bit 1 */ | |
"interrupt", /* bit 2 */ | |
"SEQ 1", /* bit 3 */ | |
"SEQ 2", /* bit 4 */ | |
"device flag", /* bit 5 */ | |
"device status", /* bit 6 */ | |
NULL, /* bit 7 */ | |
NULL, /* bit 8 */ | |
"\1online\0offline", /* bit 9 */ | |
NULL, /* bit 10 */ | |
"\1not ready\0ready", /* bit 11 */ | |
"VFU 12", /* bit 12 */ | |
"VFU 9" /* bit 13 */ | |
}; | |
static const BITSET_FORMAT prt_status_format = /* names, offset, direction, alternates, bar */ | |
{ FMT_INIT (prt_status_names, 2, msb_first, has_alt, no_bar) }; | |
/* Interface state */ | |
static HP_WORD control_word = 0; /* control word */ | |
static HP_WORD int_status_word = 0; /* interrupt status word (bits 8-15) */ | |
static HP_WORD dev_status_word = 0; /* device status word (bits 8-15) */ | |
static HP_WORD read_word = 0; /* read word */ | |
static HP_WORD write_word = 0; /* write word */ | |
static SEQ_STATE sequencer = Idle; /* data transfer handshake sequencer */ | |
static uint32 jumper_set = PRINTER_JUMPERS; /* set of configuration jumpers */ | |
static FLIP_FLOP sio_busy = CLEAR; /* SIO busy flip-flop */ | |
static FLIP_FLOP channel_sr = CLEAR; /* channel service request flip-flop */ | |
static FLIP_FLOP device_sr = CLEAR; /* device service request flip-flop */ | |
static FLIP_FLOP input_xfer = CLEAR; /* input transfer flip-flop */ | |
static FLIP_FLOP output_xfer = CLEAR; /* output transfer flip-flop */ | |
static FLIP_FLOP read_xfer = CLEAR; /* read transfer flip-flop */ | |
static FLIP_FLOP write_xfer = CLEAR; /* write transfer flip-flop */ | |
static FLIP_FLOP interrupt_mask = SET; /* interrupt mask flip-flop */ | |
static FLIP_FLOP device_command = CLEAR; /* device command flip-flop */ | |
static FLIP_FLOP device_flag = CLEAR; /* device flag flip-flop */ | |
static FLIP_FLOP device_end = CLEAR; /* device end flip-flop */ | |
static HP_WORD data_out = 0; /* external DATA OUT signal bus */ | |
static t_bool device_command_out = FALSE; /* external DEV CMD signal state */ | |
static HP_WORD data_in = 0; /* external DATA IN signal bus */ | |
static t_bool device_flag_in = FALSE; /* external DEV FLAG signal state */ | |
static t_bool device_end_in = FALSE; /* external DEV END signal state */ | |
/* Diagnostic Hardware Assembly state */ | |
static HP_WORD dha_control_word = 0; /* Diagnostic Hardware Assembly control word */ | |
static t_bool power_warning = FALSE; /* PFWARN is not asserted to the DHA */ | |
/* Printer state */ | |
static t_bool paper_fault = TRUE; /* TRUE if the printer is out of paper */ | |
static t_bool tape_fault = FALSE; /* TRUE if there is no punch in a VFU channel command */ | |
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 CNTLR_INTRF ui_interface; | |
static t_stat xfer_service (UNIT *uptr); | |
static t_stat pulse_service (UNIT *uptr); | |
static t_stat timer_service (UNIT *uptr); | |
static t_stat ui_reset (DEVICE *dptr); | |
/* Interface local utility routines */ | |
static t_stat master_reset (t_bool programmed_clear); | |
static void clear_interface_logic (void); | |
static void activate_unit (UNIT *uptr); | |
static OUTBOUND_SET set_interrupt (uint32 interrupt); | |
static OUTBOUND_SET set_device_status (uint32 status_mask, uint32 new_status_word); | |
static OUTBOUND_SET handshake_xfer (void); | |
/* Diagnostic Hardware Assembly local SCP support routines */ | |
static t_stat diag_service (UNIT *uptr); | |
/* Diagnostic Hardware Assembly local utility routines */ | |
static t_stat diag_reset (t_bool programmed_clear); | |
static OUTBOUND_SET diag_control (uint32 control_word); | |
/* Printer local SCP support routines */ | |
static t_stat lp_service (UNIT *uptr); | |
static t_stat lp_attach (UNIT *uptr, 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 t_stat lp_reset (t_bool programmed_clear); | |
static OUTBOUND_SET lp_control (uint32 control_word); | |
static t_bool lp_set_alarm (UNIT *uptr); | |
static t_bool lp_set_locality (UNIT *uptr, LOCALITY printer_state); | |
static t_stat lp_load_vfu (UNIT *uptr, FILE *vf); | |
static int32 lp_read_line (FILE *vf, char *line, uint32 size); | |
/* Interface SCP data structures */ | |
/* Device information block */ | |
static DIB lp_dib = { | |
&ui_interface, /* device interface */ | |
14, /* device number */ | |
11, /* service request number */ | |
18, /* interrupt priority */ | |
INTMASK_E /* interrupt mask */ | |
}; | |
/* Unit list */ | |
#define UNIT_FLAGS (UNIT_ATTABLE | UNIT_SEQ | UNIT_EXPAND | UNIT_OFFLINE) | |
static UNIT lp_unit [] = { | |
{ UDATA (&xfer_service, UNIT_FLAGS | UNIT_2617, 0), 0 }, | |
{ UDATA (&pulse_service, UNIT_DIS, 0), PULSE_TIME }, | |
{ UDATA (&timer_service, UNIT_DIS, 0), XFER_TIME } | |
}; | |
/* Register list. | |
The list consists of the interface registers followed by the Diagnostic | |
Hardware Assembly registers and then the printer registers. | |
Implementation notes: | |
1. The DHA hardware buffers control word bits 6-10 to LEDs. Inspection and | |
user confirmation of the control word state is required by the interface | |
diagnostic. In simulation, bits 6-10 of the control word are presented | |
as the CNLED register to allow an ASSERT command to test this subrange of | |
bits with single commands. | |
*/ | |
static REG lp_reg [] = { | |
/* Macro Name Location Radix Width Offset Depth Flags */ | |
/* ------ ------ ------------------------ ----- ------------ ------ ------------- ------------------ */ | |
{ FLDATA (SIOBSY, sio_busy, 0) }, | |
{ FLDATA (CHANSR, channel_sr, 0) }, | |
{ FLDATA (DEVSR, device_sr, 0) }, | |
{ FLDATA (INXFR, input_xfer, 0) }, | |
{ FLDATA (OUTXFR, output_xfer, 0) }, | |
{ FLDATA (RDXFR, read_xfer, 0) }, | |
{ FLDATA (WRXFR, write_xfer, 0) }, | |
{ FLDATA (INTMSK, interrupt_mask, 0) }, | |
{ FLDATA (DEVCMD, device_command, 0) }, | |
{ FLDATA (DEVFLG, device_flag, 0) }, | |
{ FLDATA (DEVEND, device_end, 0) }, | |
{ DRDATA (SEQSTA, sequencer, 8), PV_LEFT }, | |
{ ORDATA (CNTL, control_word, 16), PV_RZRO }, | |
{ ORDATA (ISTAT, int_status_word, 16), PV_RZRO }, | |
{ ORDATA (DSTAT, dev_status_word, 16), PV_RZRO }, | |
{ ORDATA (READ, read_word, 16), PV_RZRO | REG_A }, | |
{ ORDATA (WRITE, write_word, 16), PV_RZRO | REG_A }, | |
{ YRDATA (J2WX, jumper_set, 10, PV_RZRO) }, | |
{ ORDATA (DATOUT, data_out, 16), PV_RZRO | REG_A }, | |
{ ORDATA (DATIN, data_in, 16), PV_RZRO | REG_A }, | |
{ FLDATA (DCOUT, device_command_out, 0) }, | |
{ FLDATA (DFIN, device_flag_in, 0) }, | |
{ FLDATA (DENDIN, device_end_in, 0) }, | |
DIB_REGS (lp_dib), | |
{ ORDATA (DIAGCN, dha_control_word, 16), PV_RZRO }, | |
{ GRDATA (CNLED, control_word, 2, 5, 5), PV_RZRO }, | |
{ FLDATA (PFWARN, power_warning, 0) }, | |
{ FLDATA (PFAULT, paper_fault, 0) }, | |
{ FLDATA (TFAULT, tape_fault, 0) }, | |
{ FLDATA (OLPEND, offline_pending, 0) }, | |
{ DRDATA (PRLINE, current_line, 8), PV_LEFT }, | |
{ DRDATA (BUFIDX, buffer_index, 8), PV_LEFT }, | |
{ BRDATA (PRTBUF, buffer, 8, 8, BUFFER_SIZE), PV_RZRO | REG_A }, | |
{ ORDATA (OVPCHR, overprint_char, 8), PV_RZRO | REG_A }, | |
{ DRDATA (FORMLN, form_length, 8), PV_LEFT | REG_RO }, | |
{ BRDATA (TITLE, vfu_title, 8, 8, LINE_SIZE), REG_HRO }, | |
{ BRDATA (VFU, VFU, 2, VFU_WIDTH, VFU_SIZE), PV_RZRO | REG_RO }, | |
{ ORDATA (PCHR, punched_char, 8), PV_RZRO | REG_A }, | |
{ ORDATA (UPCHR, unpunched_char, 8), PV_RZRO | REG_A }, | |
{ DRDATA (BTIME, fast_times.buffer_load, 24), PV_LEFT | REG_NZ }, | |
{ DRDATA (PTIME, fast_times.print, 24), PV_LEFT | REG_NZ }, | |
{ DRDATA (STIME, fast_times.advance, 24), PV_LEFT | REG_NZ }, | |
{ DRDATA (POS, lp_unit [0].pos, T_ADDR_W), PV_LEFT }, | |
{ DRDATA (UWAIT, lp_unit [0].wait, 32), PV_LEFT | REG_HRO }, | |
{ NULL } | |
}; | |
/* Modifier list */ | |
typedef enum { /* Device modes */ | |
Fast_Time, /* use optimized timing */ | |
Real_Time, /* use realistic timing */ | |
Printer, /* connect to the printer */ | |
Diagnostic /* connect to the DHA */ | |
} DEVICE_MODES; | |
static MTAB lp_mod [] = { | |
/* Mask Value Match Value Print String Match String Validation Display Descriptor */ | |
/* ------------ ------------ ----------------- ------------ ------------------ ------- ---------- */ | |
{ UNIT_MODEL, UNIT_2607, "2607", "2607", &lp_set_model, NULL, NULL }, | |
{ UNIT_MODEL, UNIT_2613, "2613", "2613", &lp_set_model, NULL, NULL }, | |
{ UNIT_MODEL, UNIT_2617, "2617", "2617", &lp_set_model, NULL, NULL }, | |
{ UNIT_MODEL, UNIT_2618, "2618", "2618", &lp_set_model, NULL, NULL }, | |
{ UNIT_OFFLINE, UNIT_OFFLINE, "offline", "OFFLINE", &lp_set_on_offline, NULL, NULL }, | |
{ UNIT_OFFLINE, 0, "online", "ONLINE", &lp_set_on_offline, NULL, NULL, }, | |
{ UNIT_EXPAND, UNIT_EXPAND, "expanded output", "EXPAND", NULL, NULL, NULL }, | |
{ UNIT_EXPAND, 0, "compact output", "COMPACT", NULL, NULL, NULL, }, | |
/* Entry Flags Value Print String Match String Validation Display Descriptor */ | |
/* ------------------- ----------- ------------ ------------ ------------ ------------- ---------------- */ | |
{ MTAB_XDV, Fast_Time, NULL, "FASTTIME", &lp_set_mode, NULL, NULL }, | |
{ MTAB_XDV, Real_Time, NULL, "REALTIME", &lp_set_mode, NULL, NULL }, | |
{ MTAB_XDV, Printer, NULL, "PRINTER", &lp_set_mode, NULL, NULL }, | |
{ MTAB_XDV, Diagnostic, NULL, "DIAGNOSTIC", &lp_set_mode, NULL, NULL }, | |
{ MTAB_XDV, 0, "MODES", NULL, NULL, &lp_show_mode, NULL }, | |
{ MTAB_XDV, VAL_DEVNO, "DEVNO", "DEVNO", &hp_set_dib, &hp_show_dib, (void *) &lp_dib }, | |
{ MTAB_XDV, VAL_INTMASK, "INTMASK", "INTMASK", &hp_set_dib, &hp_show_dib, (void *) &lp_dib }, | |
{ MTAB_XDV, VAL_INTPRI, "INTPRI", "INTPRI", &hp_set_dib, &hp_show_dib, (void *) &lp_dib }, | |
{ MTAB_XDV, VAL_SRNO, "SRNO", "SRNO", &hp_set_dib, &hp_show_dib, (void *) &lp_dib }, | |
{ MTAB_XDV | MTAB_NMO, 1, "VFU", NULL, NULL, &lp_show_vfu, NULL }, | |
{ MTAB_XDV | MTAB_NC, 0, "VFU", "VFU", &lp_set_vfu, &lp_show_vfu, NULL }, | |
{ 0 } | |
}; | |
/* Debugging trace list */ | |
static DEBTAB lp_deb [] = { | |
{ "CMD", DEB_CMD }, /* controller commands */ | |
{ "CSRW", DEB_CSRW }, /* interface control, status, read, and write actions */ | |
{ "SERV", DEB_SERV }, /* controller unit service scheduling calls */ | |
{ "XFER", DEB_XFER }, /* controller data reads and writes */ | |
{ "STATE", DEB_STATE }, /* handshake execution state changes */ | |
{ "IOBUS", DEB_IOB }, /* interface I/O bus signals and data words */ | |
{ NULL, 0 } | |
}; | |
/* Device descriptor */ | |
DEVICE lp_dev = { | |
"LP", /* device name */ | |
lp_unit, /* unit array */ | |
lp_reg, /* register array */ | |
lp_mod, /* modifier array */ | |
3, /* number of units */ | |
10, /* address radix */ | |
32, /* address width = 4 GB */ | |
1, /* address increment */ | |
8, /* data radix */ | |
8, /* data width */ | |
NULL, /* examine routine */ | |
NULL, /* deposit routine */ | |
&ui_reset, /* reset routine */ | |
NULL, /* boot routine */ | |
&lp_attach, /* attach routine */ | |
&lp_detach, /* detach routine */ | |
&lp_dib, /* device information block pointer */ | |
DEV_DISABLE | DEV_DEBUG, /* device flags */ | |
0, /* debug control flags */ | |
lp_deb, /* debug flag name array */ | |
NULL, /* memory size change routine */ | |
NULL /* logical device name */ | |
}; | |
/* Interface local SCP support routines */ | |
/* Universal interface. | |
The universal interface is installed on the IOP and Multiplexer Channel buses | |
and receives direct and programmed I/O commands from the IOP and Multiplexer | |
Channel, respectively. In simulation, the asserted signals on the buses are | |
represented as bits in the inbound_signals set. Each signal is processed | |
sequentially in numerical order, and a set of similar outbound_signals is | |
assembled and returned to the caller, simulating assertion of the | |
corresponding backplane signals. | |
After setting the control mode to establish word or byte mode, SIO data | |
transfer between the interface and the connected device is initiated by a | |
PWRITESTB or READNEXTWD order. For direct I/O, a DWRITESTB or a DCONTSTB | |
with the "acquire" bit set initiates a transfer. | |
A sequencer governs the generation of the device handshake signals. The | |
handshake begins with the assertion of the Device Control signal. In | |
response, the device asserts the Device Flag signal. The interface then | |
denies Device Control, and the device denies Device Flag. For a byte | |
transfer, this sequence repeats automatically for the second byte. Byte | |
packing and unpacking is provided by the interface. | |
Eight interrupt sources are provided and may be individually set by their | |
associated conditions. A master interrupt enable is provided by setting the | |
appropriate control word bit, and the requesting sources may be cleared | |
independently. An interrupt acknowledgement from the IOP clears the master | |
interrupt enable to prevent multiple sources from interrupting | |
simultaneously. | |
The status word returned by a DSTATSTB or PSTATSTB signal consists of | |
interface status in the upper byte and either interrupt or device status in | |
the lower byte, as selected by a control word bit. | |
Implementation notes: | |
1. In a hardware transfer abort, READNEXTWD or PWRITESTB causes the | |
sequencer to transition to the Device_Command_1 state and set the Device | |
End flip-flop, which asserts DEVEND to the multiplexer channel, and then | |
the Device End flip-flop is cleared by ACKSR. In simulation, ACKSR | |
occurs before the PREADSTB or PWRITESTB that asserts DEVEND, so the state | |
of the Device End flip-flop is saved in the ACKSR handler and is then | |
checked in a subsequent PREADSTB or PWRITESTB to assert DEVEND. | |
2. In hardware, the SETJMP signal is ignored, and the JMPMET signal is | |
asserted continuously when enabled by CHANSO. | |
3. In hardware, a power fail warning (PFWARN) is asserted continuously from | |
detection until power is lost. In simulation, the "power_warning" flag | |
is set by a PFWARN assertion and is cleared by a power-on reset. PFWARN | |
is used only by the DHA. | |
*/ | |
static SIGNALS_DATA ui_interface (DIB *dibptr, INBOUND_SET inbound_signals, HP_WORD inbound_value) | |
{ | |
INBOUND_SIGNAL signal; | |
INBOUND_SET working_set = inbound_signals; | |
HP_WORD outbound_value = 0; | |
OUTBOUND_SET outbound_signals = NO_SIGNALS; | |
t_bool abort_transfer = FALSE; | |
dprintf (lp_dev, DEB_IOB, "Received data %06o with signals %s\n", | |
inbound_value, fmt_bitset (inbound_signals, inbound_format)); | |
while (working_set) { | |
signal = IONEXTSIG (working_set); /* isolate the next signal */ | |
switch (signal) { /* dispatch an I/O signal */ | |
case INTPOLLIN: | |
if (dibptr->interrupt_request) { /* if a request is pending */ | |
dibptr->interrupt_request = CLEAR; /* then clear it */ | |
dibptr->interrupt_active = SET; /* and mark it now active */ | |
outbound_signals |= INTACK; /* acknowledge the interrupt */ | |
outbound_value = dibptr->device_number; /* and return our device number */ | |
control_word &= ~(CN_DEVSTAT | CN_IRQ_ENABLE); /* clear the device status and IRQ enable flip-flops */ | |
} | |
else /* otherwise the request has been reset */ | |
outbound_signals |= INTPOLLOUT; /* so let the IOP know to cancel it */ | |
break; | |
case SETINT: | |
case DSETINT: | |
outbound_signals |= set_interrupt (ST_IOSYS_IRQ); /* set the I/O system interrupt flip-flop */ | |
break; | |
case DRESETINT: | |
dibptr->interrupt_active = CLEAR; /* reset the interrupt active flip-flop */ | |
outbound_signals |= set_interrupt (0); /* and check whether another IRQ is pending */ | |
break; | |
case DSETMASK: | |
if (dibptr->interrupt_mask == INTMASK_E) /* if the mask is always enabled */ | |
interrupt_mask = SET; /* then set the mask flip-flop */ | |
else /* otherwise */ | |
interrupt_mask = D_FF (dibptr->interrupt_mask /* set the mask flip-flop if the mask bit */ | |
& inbound_value); /* is present in the mask value */ | |
outbound_signals |= set_interrupt (0); /* check whether an IRQ is pending */ | |
break; | |
case PCONTSTB: | |
if (! J2W1_INSTALLED) /* if W1 (SR set by Device Status) is not installed */ | |
device_sr = SET; /* then set the device service request flip-flop */ | |
/* fall into the DCONTSTB case */ | |
case DCONTSTB: | |
dprintf (lp_dev, DEB_CSRW, | |
(lp_dev.flags & DEV_DIAG && inbound_value & CN_DHA_FN_ENABLE | |
? "Control is %s%s | %s\n" | |
: "Control is %s%s\n"), | |
fmt_bitset (inbound_value, | |
(lp_dev.flags & DEV_DIAG | |
? dha_control_format | |
: prt_control_format)), | |
reset_irq_name [CN_RESET (inbound_value)], | |
dha_fn_name [CN_DHA_FN (inbound_value)]); | |
if (inbound_value & CN_MR) /* if the programmed master reset bit is set */ | |
master_reset (TRUE); /* then reset the interface and the control word */ | |
else if (inbound_value & CN_RIN) { /* otherwise if the reset interrupt bit is set */ | |
dibptr->interrupt_request = CLEAR; /* then clear the interrupt request */ | |
int_status_word = 0; /* and all interrupt sources */ | |
sim_cancel (timer_uptr); /* cancel the transfer timer */ | |
control_word = inbound_value; /* and set the control word */ | |
} | |
else { /* otherwise */ | |
int_status_word &= reset_irq [CN_RESET (inbound_value)]; /* clear the specified IRQ source */ | |
if ((inbound_value & CN_RIN_MASK) == CN_RIN_XFR_TMR) /* if the timer interrupt was cleared */ | |
sim_cancel (timer_uptr); /* then stop the timer too */ | |
else if (CN_XFR_TMR_ENABLE /* otherwise if the transfer timer */ | |
& ~control_word & inbound_value) { /* is enabled with a 0-to-1 transition */ | |
sim_cancel (timer_uptr); /* then retrigger */ | |
activate_unit (timer_uptr); /* the timer */ | |
} | |
control_word = inbound_value; /* set the control word */ | |
} | |
if (control_word & CN_ACQUIRE) { /* if the next word is requested */ | |
device_command = SET; /* then set the device command flip-flop */ | |
read_xfer = SET; /* and the read transfer flip-flop */ | |
outbound_signals |= handshake_xfer (); /* and start the device handshake */ | |
} | |
if (lp_dev.flags & DEV_DIAG) /* if the DHA is installed */ | |
outbound_signals |= diag_control (control_word); /* then process the DHA-specific controls */ | |
else /* otherwise */ | |
outbound_signals |= lp_control (control_word); /* process the device-specific controls */ | |
break; | |
case PSTATSTB: | |
case DSTATSTB: | |
outbound_value = sequence_counter [sequencer]; /* start with the sequence counter value */ | |
if (sio_busy == CLEAR /* if the interface is inactive */ | |
&& (int_status_word & ST_CLRIL) == 0) /* and the clear interface logic IRQ is denied */ | |
outbound_value |= ST_SIO_OK; /* then programmed I/O is enabled */ | |
if (sequencer == Idle) /* if the device is inactive */ | |
outbound_value |= ST_DIO_OK; /* then direct I/O is enabled */ | |
if (int_status_word) /* if any interrupt requests are pending */ | |
outbound_value |= ST_IRQ_PENDING; /* then set the status bit */ | |
if (device_flag_in) /* if the device flag is asserted */ | |
outbound_value |= ST_DEVFLAG; /* then set the status bit */ | |
if (control_word & CN_DEVSTAT) /* if the device status flip-flop is set */ | |
outbound_value |= ST_DEVSTAT | dev_status_word; /* then return the device status */ | |
else /* otherwise */ | |
outbound_value |= int_status_word; /* return the interrupt status */ | |
dprintf (lp_dev, DEB_CSRW, "Status is %s\n", | |
fmt_bitset (outbound_value, | |
(control_word & CN_DEVSTAT | |
? (lp_dev.flags & DEV_DIAG | |
? dev_status_format | |
: prt_status_format) | |
: int_status_format))); | |
break; | |
case DREADSTB: | |
outbound_value = (HP_WORD) read_word; /* return the data input register value */ | |
break; | |
case DWRITESTB: | |
write_word = (uint32) inbound_value; /* store the value in the data output register */ | |
device_command = SET; /* set the device command flip-flop */ | |
write_xfer = SET; /* and the write transfer flip-flop */ | |
outbound_signals |= handshake_xfer (); /* and start the device handshake */ | |
break; | |
case DSTARTIO: | |
dprintf (lp_dev, DEB_CSRW, "Channel program started\n"); | |
sio_busy = SET; /* set the SIO busy flip-flop */ | |
mpx_assert_REQ (dibptr); /* request the channel */ | |
channel_sr = SET; /* set the service request flip-flop */ | |
outbound_signals |= SRn; /* and assert a service request */ | |
break; | |
case ACKSR: | |
device_sr = CLEAR; /* acknowledge the service request */ | |
abort_transfer = (t_bool) device_end; /* TRUE if the transfer is to be aborted */ | |
device_end = CLEAR; /* clear the device end flip-flop */ | |
break; | |
case TOGGLESR: | |
TOGGLE (channel_sr); /* set or clear the channel service request flip-flop */ | |
break; | |
case TOGGLESIOOK: | |
TOGGLE (sio_busy); /* set or clear the SIO busy flip-flop */ | |
if (sio_busy == CLEAR) | |
dprintf (lp_dev, DEB_CSRW, "Channel program ended\n"); | |
break; | |
case TOGGLEINXFER: | |
TOGGLE (input_xfer); /* set or clear the input transfer flip-flop */ | |
device_end_in = FALSE; /* clear the external device end condition */ | |
break; | |
case TOGGLEOUTXFER: | |
TOGGLE (output_xfer); /* set or clear the output transfer flip-flop */ | |
if (output_xfer == SET) /* if starting an output transfer */ | |
device_sr = SET; /* request the first word to write */ | |
device_end_in = FALSE; /* clear the external device end condition */ | |
break; | |
case PCMD1: | |
device_sr = SET; /* request the second control word */ | |
break; | |
case READNEXTWD: | |
device_command = SET; /* set the device command flip-flop */ | |
read_xfer = SET; /* and the read transfer flip-flop */ | |
outbound_signals |= handshake_xfer (); /* and start the device handshake */ | |
break; | |
case PREADSTB: | |
if (abort_transfer) { /* if the transfer has been aborted */ | |
outbound_value = dibptr->device_number * 4; /* then return the DRT address */ | |
outbound_signals |= DEVEND; /* and indicate a device abort */ | |
} | |
else /* otherwise the transfer continues */ | |
outbound_value = (HP_WORD) read_word; /* so return the data input register value */ | |
break; | |
case PWRITESTB: | |
if (abort_transfer) { /* if the transfer has been aborted */ | |
outbound_value = dibptr->device_number * 4; /* then return the DRT address */ | |
outbound_signals |= DEVEND; /* and indicate a device abort */ | |
} | |
else { /* otherwise the transfer continues */ | |
write_word = (uint32) inbound_value; /* so store the value in the data output register */ | |
device_command = SET; /* set the device command flip-flop */ | |
write_xfer = SET; /* and the write transfer flip-flop */ | |
outbound_signals |= handshake_xfer (); /* and start the device handshake */ | |
} | |
break; | |
case DEVNODB: | |
outbound_value = dibptr->device_number * 4; /* return the DRT address */ | |
break; | |
case XFERERROR: | |
dprintf (lp_dev, DEB_CSRW, "Channel program aborted\n"); | |
clear_interface_logic (); /* clear the interface to abort the transfer */ | |
outbound_signals |= set_interrupt (ST_XFERERR_IRQ); /* set the transfer error interrupt flip-flop */ | |
break; | |
case CHANSO: | |
if (channel_sr | device_sr) /* if the interface has requested service */ | |
outbound_signals |= SRn; /* then assert SRn to the channel */ | |
outbound_signals |= JMPMET; /* JMPMET is tied active on this interface */ | |
break; | |
case EOT: | |
if (inbound_signals & PREADSTB) /* if this is the end of a read transfer */ | |
device_sr = SET; /* then request channel service */ | |
break; | |
case PFWARN: | |
power_warning = TRUE; /* system power is in the process of failing */ | |
break; | |
case SETJMP: /* not used by this interface */ | |
break; | |
} | |
IOCLEARSIG (working_set, signal); /* remove the current signal from the set */ | |
} | |
dprintf (lp_dev, DEB_IOB, "Returned data %06o with signals %s\n", | |
outbound_value, fmt_bitset (outbound_signals, outbound_format)); | |
return IORETURN (outbound_signals, outbound_value); /* return the outbound signals and value */ | |
} | |
/* Service the transfer handshake. | |
This service routine is called once for each state of the device transfer | |
handshake. The handshake sequencer schedules the transfer events with the | |
appropriate delays. If no delay is required, the service routine is entered | |
directly from the sequencer. Otherwise, an event is scheduled, and the | |
routine is entered when the event time expires. | |
Jumper W10 determines the output polarity of the DEV CMD signal to the | |
device, and jumpers W2 and W6 determine the input edges of the DEV FLAG | |
signal from the device used to assert and deny the Device Flag, as follows: | |
Jumper Interpretation when removed Interpretation when installed | |
------ ----------------------------- ----------------------------- | |
W10 DEV CMD polarity is normal DEV CMD polarity is inverted | |
W2 Flag asserts on leading edge Flag asserts on trailing edge | |
W6 Flag denies on trailing edge Flag denies on leading edge | |
Note that if jumpers W2 and W6 are not installed or removed in pairs, the | |
Device Flag asserts and denies on the same edge of the DEV FLAG signal. In | |
this case, the service routine sets the flag on the first call and clears the | |
flag on the second call without requiring a change in the incoming signal. | |
Implementation notes: | |
1. The "device_command_out" and "device_flag_in" variables represent the | |
states of the DEV CMD and DEV FLAG signal lines. Edge detection for the | |
Device Flag is accomplished by comparing the current state to the prior | |
state. | |
2. If the routine was entered by event timer expiration, the handshake | |
sequencer must be called explicitly, and any returned backplane signals | |
must be asserted explicitly. If the routine was called directly, the | |
sequencer is responsible for asserting backplane signals as required. | |
3. This routine may be called with a NULL "uptr" parameter to update the | |
saved last state of the "device_flag_in" variable. The NULL value | |
indicates that this is not part of the normal handshake sequence. | |
*/ | |
static t_stat xfer_service (UNIT *uptr) | |
{ | |
static t_bool device_flag_last = FALSE; | |
const t_bool timed_entry = (uptr != NULL && uptr->wait > 0); /* TRUE if entry was by event timer */ | |
t_stat result; | |
OUTBOUND_SET signals; | |
device_command_out = device_command ^ J2W10_INSTALLED; /* set device command out; invert if W10 is installed */ | |
if (lp_dev.flags & DEV_DIAG) /* if the DHA is connected */ | |
result = diag_service (uptr); /* then service the diagnostic hardware */ | |
else /* otherwise */ | |
result = lp_service (uptr); /* service the connected device */ | |
if (sequencer == Device_Command_1 /* if Device Command */ | |
|| sequencer == Device_Command_2) { /* is asserted */ | |
if (device_flag_last != device_flag_in /* then if the flag input has changed */ | |
&& J2W2_INSTALLED ^ device_flag_in) /* and jumper W2 is in and 1 -> 0 or W2 is out and 0 -> 1 */ | |
device_flag = SET; /* then Device Flag sets */ | |
} | |
else /* otherwise Device Command is denied */ | |
if (J2W2_INSTALLED != J2W6_INSTALLED /* so if W2 installation differs from W6 installation */ | |
|| (device_flag_last != device_flag_in /* or if the flag input has changed */ | |
&& J2W6_INSTALLED ^ device_flag_last)) /* and jumper W6 is in and 0 -> 1 or W6 is out and 1 -> 0 */ | |
device_flag = CLEAR; /* then Device Flag clears */ | |
device_flag_last = device_flag_in; /* save the current state of the flag */ | |
if (timed_entry) { /* if this is a timed entry */ | |
signals = handshake_xfer (); /* then continue the handshake */ | |
if (signals & INTREQ) /* if an interrupt request was generated */ | |
iop_assert_INTREQ (&lp_dib); /* then assert the INTREQ signal */ | |
if (signals & SRn) /* if a service request was generated */ | |
mpx_assert_SRn (&lp_dib); /* then assert the SRn signal */ | |
} | |
return result; /* return the result of the service call */ | |
} | |
/* Service the device command pulse timer. | |
In pulse mode, the DEV CMD signal asserts for 8 microseconds. This service | |
routine is entered to deny DEV CMD. The transfer service is called directly | |
to notify it of Device Command clearing, and the handshake sequencer is then | |
called in case the transfer service altered the Device Flag in response. | |
*/ | |
static t_stat pulse_service (UNIT *uptr) | |
{ | |
dprintf (lp_dev, DEB_SERV, "Pulse service entered\n"); | |
device_command = CLEAR; /* clear the device command flip-flop */ | |
activate_unit (xfer_uptr); /* let the device know that command has denied */ | |
handshake_xfer (); /* and continue the handshake */ | |
return SCPE_OK; | |
} | |
/* Service the transfer timer. | |
Setting the appropriate bit in the control word starts the five-second | |
transfer timer. If it expires, this routine is entered. The transfer timer | |
interrupt is set, and if interrupts are enabled, INTREQ is asserted to the | |
IOP. As a convenience to the user, the file attached to the device unit is | |
flushed. | |
*/ | |
static t_stat timer_service (UNIT *uptr) | |
{ | |
dprintf (lp_dev, DEB_SERV, "Watchdog service entered\n"); | |
if (set_interrupt (ST_XFR_TMR_IRQ) == INTREQ) /* set the transfer timer interrupt flip-flop */ | |
iop_assert_INTREQ (&lp_dib); /* and assert the INTREQ signal if enabled */ | |
if (xfer_unit.flags & UNIT_ATT) /* if the transfer unit is attached */ | |
fflush (xfer_unit.fileref); /* then flush any partial output */ | |
return SCPE_OK; /* return success */ | |
} | |
/* Device reset routine. | |
This routine is called for a RESET or RESET LP command. It is the simulation | |
equivalent of the IORESET signal, which is asserted by the front panel LOAD | |
and DUMP switches. | |
For this interface, IORESET is identical to the Programmed Master Clear | |
invoked by setting bit 0 of the control word. | |
Implementation notes: | |
1. Calling "master_clear" with a FALSE parameter indicates that this is a | |
commanded reset. This allows the connected device-specific reset | |
routines to distinguish from a Programmed Master Clear. | |
*/ | |
static t_stat ui_reset (DEVICE *dptr) | |
{ | |
return master_reset (FALSE); /* perform a non-programmed master reset */ | |
} | |
/* Interface local utility routines */ | |
/* Master reset. | |
A master reset is generated either by an I/O Reset signal or a Programmed | |
Master Clear (CIO bit 0). It sets the interrupt mask, clears any pending or | |
active interrupt, clears all interrupt sources, clears the control word, | |
clears the read and write registers, resets the handshake sequencer to its | |
idle state, clears the interface logic flip-flops, and cancels all active | |
event timers. It also calls pulses the MASTER CLEAR signal line to the | |
device for a preset time. | |
Implementation notes: | |
1. Calling the reset routine for the connected device simulates asserting | |
the MASTER CLEAR signal. | |
*/ | |
static t_stat master_reset (t_bool programmed_clear) | |
{ | |
interrupt_mask = SET; /* set the interrupt mask flip-flop */ | |
lp_dib.interrupt_request = CLEAR; /* clear any current */ | |
lp_dib.interrupt_active = CLEAR; /* interrupt request */ | |
int_status_word = 0; /* clear all interrupt request sources */ | |
control_word = 0; /* clear the control word */ | |
write_word = 0; /* and the output data register */ | |
read_word = 0; /* and the input data register */ | |
sequencer = Idle; /* clear the handshake sequencer to the idle state */ | |
read_xfer = CLEAR; /* clear the read transfer */ | |
write_xfer = CLEAR; /* and write transfer flip-flops */ | |
device_command = CLEAR; /* clear the device command */ | |
device_flag = CLEAR; /* and device flag flip-flops */ | |
data_out = 0; /* clear the external state */ | |
data_in = 0; /* of the I/O lines */ | |
device_end_in = FALSE; /* and the external device end line */ | |
clear_interface_logic (); /* clear the interface to abort any transfer in progress */ | |
sim_cancel (xfer_uptr); /* cancel */ | |
sim_cancel (pulse_uptr); /* any pending */ | |
sim_cancel (timer_uptr); /* event timers */ | |
if (lp_dev.flags & DEV_DIAG) /* if the DHA is installed */ | |
return diag_reset (programmed_clear); /* then reset the diagnostic hardware */ | |
else /* otherwise */ | |
return lp_reset (programmed_clear); /* reset the device */ | |
} | |
/* Clear the interface logic. | |
The clear interface logic signal is asserted when the channel indicates a | |
transfer failure by asserting XFERERROR, or when the device asserts the CLEAR | |
INTERFACE signal. It clears the SIO Busy, Channel and Device Service | |
Request, Input Transfer, Output Transfer, and Device End flip-flops. | |
*/ | |
static void clear_interface_logic (void) | |
{ | |
sio_busy = CLEAR; /* clear the SIO busy flip-flop */ | |
channel_sr = CLEAR; /* and the channel service request flip-flop */ | |
device_sr = CLEAR; /* and the device service request flip-flop */ | |
input_xfer = CLEAR; /* and the input transfer flip-flop */ | |
output_xfer = CLEAR; /* and the output transfer flip-flop */ | |
device_end = CLEAR; /* and the device end flip-flop */ | |
return; | |
} | |
/* Activate a unit. | |
The specified unit is added to the event queue if a non-zero event time has | |
been given. Otherwise, the event handler is called directly. | |
*/ | |
static void activate_unit (UNIT *uptr) | |
{ | |
if (uptr->wait > 0) { /* if the event time is set */ | |
dprintf (lp_dev, DEB_SERV, "%s delay %u service scheduled\n", | |
unit_name [uptr - lp_unit], uptr->wait); | |
sim_activate (uptr, uptr->wait); /* then activate the unit */ | |
} | |
else /* otherwise */ | |
uptr->action (uptr); /* call the event handler directly */ | |
return; | |
} | |
/* Set an interrupt. | |
The interrupt bit specified is set in the interrupt status word. If enabled, | |
INTREQ is returned to request an interrupt. | |
The routine is also called with a zero "interrupt" parameter value to check | |
whether an interrupt should be requested. | |
*/ | |
static OUTBOUND_SET set_interrupt (uint32 interrupt) | |
{ | |
int_status_word |= interrupt; /* set the specified interrupt flip-flop */ | |
if (int_status_word /* if an interrupt request is present */ | |
&& control_word & CN_IRQ_ENABLE /* and the IRQ enable flip-flop is set */ | |
&& lp_dib.interrupt_active == CLEAR /* and no interrupt is currently active */ | |
&& interrupt_mask == SET) { /* and the interrupt mask is satisfied */ | |
lp_dib.interrupt_request = SET; /* then request an interrupt */ | |
return INTREQ; /* and assert the INTREQ signal */ | |
} | |
else /* otherwise an interrupt request */ | |
return NO_SIGNALS; /* cannot be made at this time */ | |
} | |
/* Set the device status. | |
The device status word is masked with the supplied "status_mask" and then the | |
corresponding bits of the "new_status_word" are merged in. If enabled by the | |
associated jumpers and the required edge transitions, interrupts for status | |
bits 8-10 may be generated. | |
*/ | |
static OUTBOUND_SET set_device_status (uint32 status_mask, uint32 new_status_word) | |
{ | |
OUTBOUND_SET outbound_signals = NO_SIGNALS; | |
if (status_mask & ST_DEVIRQ_MASK) { /* if a status interrupt is possible */ | |
if (J2W4_INSTALLED /* then if jumper J4 is installed to enable */ | |
&& ~dev_status_word & new_status_word & ST_ST8_IRQ) /* and a 0 -> 1 transition occurred on status 8 */ | |
outbound_signals |= set_interrupt (ST_ST8_IRQ); /* then set the status 8 interrupt flip-flop */ | |
if (J2W8_INSTALLED /* if jumper J8 is installed to enable */ | |
&& ~dev_status_word & new_status_word & ST_ST9_IRQ) /* and a 0 -> 1 transition occurred on status 9 */ | |
outbound_signals |= set_interrupt (ST_ST9_IRQ); /* then set the status 9 interrupt flip-flop */ | |
if (J2W9_INSTALLED /* if jumper J9 is installed to enable */ | |
&& dev_status_word & ~new_status_word & ST_ST10_IRQ) /* and a 1 -> 0 transition occurred on status 10 */ | |
outbound_signals |= set_interrupt (ST_ST10_IRQ); /* then set the status 10 interrupt flip-flop */ | |
} | |
dev_status_word = dev_status_word & ~status_mask /* clear the old device status */ | |
| new_status_word & status_mask; /* and set the new status */ | |
return outbound_signals; /* return INTREQ if any interrupts were requested */ | |
} | |
/* Start or continue the data transfer handshake. | |
This routine implements the two-wire data transfer handshake with the device. | |
For each word or byte transferred, the Device Command signal from the | |
interface and the Device Flag signal from the device assume these states: | |
Command Flag State Next State | |
State State Action Transition | |
-------- -------- ---------------- -------------- | |
denied denied device idle Command sets | |
asserted denied device started Flag sets | |
asserted asserted device completed Command clears | |
denied asserted interface idle Flag clears | |
In hardware, a two-bit gray counter implements a four-state sequencer, with | |
three states assigned as follows for a word transfer: | |
Command Flag | |
State State Action State State Next State Transition | |
----- ------------------ -------- -------- --------------------- | |
0 0 idle denied denied read or write command | |
1 0 word requested asserted denied Flag sets | |
1 1 word started denied asserted Flag clears | |
0 0 word completed denied denied --- | |
For a two-byte transfer, the states are: | |
Command Flag | |
State State Action State State Next State Transition | |
----- ------------------ -------- -------- --------------------- | |
0 0 idle denied denied read or write command | |
1 0 1st byte requested asserted denied Flag sets | |
1 1 1st byte started denied asserted Flag clears | |
1 0 1st byte completed asserted denied Flag sets | |
2nd byte requested | |
0 0 2nd byte started denied asserted Flag clears | |
0 0 2nd byte completed denied denied --- | |
The presence of the asserted Device Flag when the count is 00 differentiates | |
between the "2nd byte started" and "operation completed" conditions. | |
In simulation, these last two conditions are assigned to separate states, as | |
follows: | |
Hdwe Simulation Command Flag | |
State State State State | |
----- ---------------- -------- -------- | |
0 0 Idle denied denied | |
1 0 Device_Command_1 asserted denied | |
1 1 Device_Flag_1 denied asserted | |
1 0 Device_Command_2 asserted denied | |
0 0 Device_Flag_2 denied asserted | |
0 0 Idle denied denied | |
To provide the proper values to appear in the Sequence Counter field of the | |
status word, a mapping array is used to supply the value 00 for the | |
Device_Flag_2 state. | |
The device service is scheduled after each state transition, except the | |
return to the idle state, to detect the change in the Device Command signal | |
or to schedule the change in the Device Flag. The device determines whether | |
the service is entered immediately or after an event time expires. | |
For the diagnostic device, the service routine is entered immediately for all | |
transitions. If the DHA is configured for flag-follows-command mode, the | |
full handshake cycle executes before returning from the routine. If it is | |
configured for flag-follows-control-6, the routine returns after the Idle-to- | |
Device_Command_1 state transition to wait for control word bit 6 to be set. | |
When it is, the routine is reentered, transitions from Device_Command_1 to | |
Device_Flag_1, and then returns to wait for control word bit 6 to be cleared. | |
When it is, the routine is entered and completes the transition from | |
Device_Flag_1 to the Idle state (assuming a word transfer). | |
For the printer device, the service routine is entered immediately for Device | |
Flag assertions, but flag denials are scheduled with a delay corresponding to | |
the printer operation time. The initial handshake entry transitions from | |
Idle to Device_Command_1, calls the service routine, which sets Device Flag, | |
transitions to Device_Flag_1, schedules the service routine, and returns to | |
wait for the event timer to expire. When it does, the event service routine | |
is entered, which clears Device Flag and calls this routine to continue the | |
handshake. The routine transitions from Device_Flag_1 to Idle to complete | |
the handshake sequence. | |
Summarizing: | |
Diagnostic Service Diagnostic Service | |
State Printer Service Flag follows Cmd Flag follows cont.6 | |
---------------- ---------------- ------------------ ------------------- | |
Device_Command_1 set Flag set Flag wait for Control.6 | |
Device_Flag_1 wait for service clear Flag wait for Control.6 | |
Device_Command_2 set Flag set Flag wait for Control.6 | |
Device_Flag_2 wait for service clear Flag wait for Control.6 | |
If the device asserts the DEV END signal in response to Device Command, the | |
Device End flip-flop is set, and the sequencer is reset back to the Idle | |
state to abort the transfer. DEV END assertion in any other state is ignored | |
until Device Command is set. | |
If jumper W3 is installed, DEV CMD is pulsed for 8 microseconds by asserting | |
Device Command and scheduling the pulse timer to deny it when the event timer | |
expires. | |
A DWRITESTB or PWRITESTB signal stores a 16-bit value in the data output | |
register. In word mode, the value is presented continuously on the 16 DATA | |
OUT lines. In byte mode, the upper byte in the data output register is | |
presented on both bytes of the DATA OUT lines until the Device Flag sets to | |
indicate that the device has accepted the first byte, whereupon the full | |
16-bit value is presented on the DATA OUT lines. The result is that the | |
upper byte and then the lower byte appears on the lower byte of the DATA OUT | |
lines. | |
During byte-mode read cycles, the previously stored full 16-bit output value | |
is presented on the DATA OUT lines if J2W7 is removed. If J2W7 is installed, | |
the upper byte and then the lower byte appears on the lower byte. In other | |
words, a byte read with J2W7 installed causes the DATA OUT lines to assume | |
the same values in sequence that occur during a byte write. This is used by | |
the diagnostic to test the DATA OUT multiplexer. | |
A read is initiated by the READNEXTWD signal or by setting the Acquire bit in | |
the control word. Device Command sets in response. While Device Command is | |
set, the data input register is transparent and passes the value on the Data | |
In lines through. When Device Flag sets, the value on the DATA IN lines is | |
latched in the register. A DREADSTB or PREADSTB signal then enables the | |
register onto the IOP Data bus. With J2W5 installed, the data in register is | |
always transparent, and a DREADSTB or PREADSTB signal presents the current | |
value on the DATA IN lines to the IOP Data bus. | |
In word mode with J2W5 removed, 16-bit data presented at the DATA IN lines is | |
passed through the data input register while Device Command is set and is | |
latched when the Device Flag sets. In byte mode with J2W5 removed, the value | |
presented on the lower byte of the DATA IN lines is presented to both bytes | |
of the data input register, passed through while Device Command is set, and | |
latched into both bytes of the register when the Device Flag sets to indicate | |
that the device has supplied the first byte. When Device Command sets for | |
the second byte, the value presented on the lower byte of the DATA IN lines | |
is presented to the lower byte of the data input register, passed through | |
while Device Command is set, and latched into the lower byte when the Device | |
Flag sets to indicate that the device has supplied the second byte. The | |
result is that the data input register presents the first byte in both bytes | |
of the register and then the second byte presents as the lower byte of the | |
register, resulting in a packed 16-bit value. | |
Implementation notes: | |
1. In hardware, the sequencer moves from state 2 through state 3 to state 0 | |
when the device flag denies at the end of a word transfer. For a packed | |
byte transfer, the sequencer moves from state 3 to state 0 when the | |
device flag asserts for the second byte, with logic holding off the | |
"operation done" signal until the flag denies. | |
In simulation, the sequencer moves on flag denial directly from | |
Device_Flag_1 to Idle for a word transfer and on flag assertion from | |
Device_Command_2 to Device_Flag_2 and then on flag denial to Idle for a | |
second byte transfer. The sequence count reported in a status return is | |
0 for Device_Flag_2, preserving the appearance of returning to state 0 | |
while the internal Device_Flag_2 state holds off the "operation done" | |
signal. | |
2. In hardware, a DEV END signal asserts the Q2 and Q3 qualifiers, enabling | |
the sequence counter to proceed through the state sequence back to the | |
idle state. In simulation, the sequencer state is set directly back to | |
Idle. | |
3. In hardware, with jumper W5 out, the DATA IN latches are transparent in | |
the Device_Command_1 and Device_Command_2 states and are latched | |
otherwise, i.e., when Device Flag asserts. With jumper W5 in, the | |
latches are transparent always, and a read gets the real-time state of | |
the DATA IN lines. In simulation, the read register is set when Device | |
Flag asserts; transparency is not simulated. | |
4. The diagnostic tests the byte unpacking and packing multiplexers on the | |
DATA OUT and DATA IN lines, so we must simulate the multiplexing | |
accurately with respect to the intermediate values before the handshake | |
is complete. | |
*/ | |
static OUTBOUND_SET handshake_xfer (void) | |
{ | |
const SEQ_STATE entry_state = sequencer; /* the state of the sequencer at entry */ | |
t_bool started = FALSE; /* TRUE if the sequencer started */ | |
OUTBOUND_SET outbound_signals = NO_SIGNALS; | |
SEQ_STATE last_state; | |
do { /* run the sequencer as long as it advances */ | |
last_state = sequencer; /* save the last state to see if it changes */ | |
if (sequencer < Device_Command_2 /* if this is the first byte */ | |
&& control_word & CN_BYTE_XFER /* of a byte transfer */ | |
&& (J2W7_INSTALLED || write_xfer)) /* and W7 is installed or it's a write transfer */ | |
data_out = write_word & ~D8_MASK /* then the upper 8 bits appear */ | |
| UPPER_BYTE (write_word); /* in both bytes */ | |
else /* otherwise */ | |
data_out = write_word; /* the full 16 bits appear */ | |
switch (sequencer) { /* dispatch the current state */ | |
case Idle: | |
if (device_command == SET) { /* if device command has been set */ | |
sequencer = Device_Command_1; /* then proceed to the next state */ | |
if (device_end_in /* if external device end asserts */ | |
&& (read_xfer || write_xfer)) /* during a transfer */ | |
device_command = CLEAR; /* then device command is inhibited */ | |
else { /* otherwise */ | |
if (J2W3_INSTALLED) /* if jumper W3 (pulse mode) is installed */ | |
activate_unit (pulse_uptr); /* then schedule device command denial */ | |
activate_unit (xfer_uptr); /* schedule device flag assertion */ | |
} | |
started = TRUE; /* indicate that the sequencer was started */ | |
} | |
break; | |
case Device_Command_1: | |
if (device_end_in) /* if external device end asserts */ | |
if (read_xfer || write_xfer) { /* then if a transfer is in progress */ | |
device_end = SET; /* then set the Device End flip-flop to abort */ | |
device_command = CLEAR; /* clear the device command */ | |
read_xfer = CLEAR; /* and read transfer */ | |
write_xfer = CLEAR; /* and write transfer flip-flops */ | |
sequencer = Idle; /* idle the sequencer */ | |
device_sr = SET; /* and request channel service */ | |
break; | |
} | |
else /* otherwise no transfer is in progress */ | |
device_end_in = FALSE; /* so clear the signal */ | |
if (device_flag == SET) { /* if the device flag has been set */ | |
sequencer = Device_Flag_1; /* then proceed to the next state */ | |
device_command = CLEAR; /* and deny device command */ | |
activate_unit (xfer_uptr); /* schedule device flag denial */ | |
if (control_word & CN_BYTE_XFER) /* if this is a byte transfer */ | |
read_word = TO_WORD (data_in, data_in); /* then the lower 8 bits appear in both bytes */ | |
else /* otherwise */ | |
read_word = data_in; /* the full 16 bits appear */ | |
if (J2W1_INSTALLED && sio_busy /* if jumper W1 (status drives SR) is installed */ | |
&& dev_status_word & ST_ST11_SR) /* and a transfer is in progress with status 11 set */ | |
device_sr = SET; /* then request channel service */ | |
} | |
break; | |
case Device_Flag_1: | |
if (device_flag == CLEAR) /* if the device flag has been cleared */ | |
if (control_word & CN_BYTE_XFER) { /* then if this is a byte transfer */ | |
sequencer = Device_Command_2; /* then proceed to the next state */ | |
device_command = SET; /* and assert device command for the second byte */ | |
data_out = write_word; /* latch the output word */ | |
activate_unit (xfer_uptr); /* schedule device flag assertion */ | |
} | |
else { /* otherwise the transfer is complete */ | |
read_xfer = CLEAR; /* so clear the read transfer */ | |
write_xfer = CLEAR; /* and write transfer flip-flops */ | |
sequencer = Idle; /* idle the sequencer */ | |
device_sr = SET; /* and request channel service */ | |
if (control_word & CN_XFR_IRQ_ENABLE) /* if a transfer interrupt is requested */ | |
outbound_signals |= set_interrupt (ST_XFR_IRQ); /* then set the transfer interrupt flip-flop */ | |
} | |
break; | |
case Device_Command_2: | |
if (device_flag == SET || device_end_in) { /* if the device flag or external device end has been set */ | |
sequencer = Device_Flag_2; /* then proceed to the next state */ | |
device_command = CLEAR; | |
activate_unit (xfer_uptr); /* schedule device flag denial */ | |
read_word &= ~D8_MASK; /* clear the lower byte */ | |
if (device_end_in == FALSE) /* if the transfer succeeded */ | |
read_word |= LOWER_BYTE (data_in); /* then merge the received lower byte */ | |
} | |
break; | |
case Device_Flag_2: | |
if (device_flag == CLEAR) { /* if the device flag was cleared */ | |
read_xfer = CLEAR; /* then clear the read transfer */ | |
write_xfer = CLEAR; /* and write transfer flip-flops */ | |
sequencer = Idle; /* idle the sequencer */ | |
device_sr = SET; /* and request channel service */ | |
if (control_word & CN_XFR_IRQ_ENABLE) /* if a transfer interrupt is requested */ | |
outbound_signals |= set_interrupt (ST_XFR_IRQ); /* then set the transfer interrupt flip-flop */ | |
} | |
break; | |
} /* end of state dispatching */ | |
} | |
while (sequencer != last_state); /* continue as long as the sequence is progressing */ | |
if (DPRINTING (lp_dev, DEB_STATE)) | |
if (sequencer != entry_state) | |
hp_debug (&lp_dev, DEB_STATE, "Sequencer transitioned from the %s state to the %s state\n", | |
state_name [entry_state], state_name [sequencer]); | |
else if (started) | |
if (device_end) | |
hp_debug (&lp_dev, DEB_STATE, "Sequencer reset by device end\n"); | |
else | |
hp_debug (&lp_dev, DEB_STATE, "Sequencer executed a full %s-transfer cycle\n", | |
(control_word & CN_BYTE_XFER ? "byte" : "word")); | |
if (device_sr && sio_busy) /* if the interface has requested service */ | |
outbound_signals |= SRn; /* then assert SRn to the channel */ | |
return outbound_signals; /* return the accumulated signals */ | |
} | |
/* Diagnostic Hardware Assembly local SCP support routines */ | |
/* Service the transfer handshake for the Diagnostic Hardware Assembly. | |
The DHA loops the data out lines back to the data in lines, with bits 11-15 | |
also connecting to bits 11-15 of the status in lines. The DHA also may be | |
configured to connect either the DEV CMD output or the CONT 6 output to the | |
DEV FLAG input. | |
Implementation notes: | |
1. The DHA transfer service is called with a null pointer to update the | |
potential change in the flag state. | |
*/ | |
static t_stat diag_service (UNIT *uptr) | |
{ | |
if (uptr) /* trace only if this is a handshake entry */ | |
dprintf (lp_dev, DEB_SERV, "%s state transfer service entered\n", | |
state_name [sequencer]); | |
if (dha_control_word & DHA_FLAG_SEL) /* if in "flag follows control 6" mode */ | |
device_flag_in = (control_word & CN_DHA_FLAG) != 0; /* then set the flag from control word bit 6 */ | |
else /* otherwise */ | |
device_flag_in = device_command_out; /* device flag is connected to device command */ | |
data_in = data_out; /* data in is connected to data out */ | |
set_device_status (ST_DHA_DEVSTAT_MASK, data_out); /* status bits 11-15 are connected to data out */ | |
return SCPE_OK; | |
} | |
/* Diagnostic Hardware Assembly local utility routines */ | |
/* Diagnostic hardware assembly reset. | |
When the MASTER CLEAR signal is asserted to the DHA, the master reset bit in | |
the DHA control word is set. In addition, the status bits connected to the | |
DATA OUT lines from the interface are cleared, as the interface has cleared | |
its output register. | |
If this reset was caused by a RESET or RESET LP command, the set of installed | |
jumpers in the DHA control word is updated. This picks up any jumper changes | |
made at the user interface. | |
Implementation notes: | |
1. The DHA transfer service is called with a null pointer to update the | |
potential change in the DEV FLAG state that may have occurred by a change | |
to the DEV CMD state if the lines are connected. | |
*/ | |
static t_stat diag_reset (t_bool programmed_clear) | |
{ | |
if (programmed_clear) { /* if this is a programmed master clear */ | |
dha_control_word |= DHA_MR; /* then record the master reset */ | |
set_device_status (ST_DHA_DEVSTAT_MASK, data_out); /* clear the status bits connected to data out */ | |
xfer_service (NULL); /* update the current device flag state */ | |
} | |
else /* otherwise this is a commanded reset */ | |
dha_control_word = dha_control_word & DHA_JUMPER_MASK /* so refresh the DHA control word */ | |
| jumper_set; /* from the jumpers */ | |
return SCPE_OK; | |
} | |
/* Process the diagnostic hardware assembly control word. | |
This routine is called when a DCONTSTB or PCONTSTB assertion indicates that | |
the control word is to be set. If bit 10 is set, then bits 6-9 represent an | |
encoded action to be taken by the DHA. Two of the actions potentially change | |
the state of the device status lines, which may also generate an interrupt if | |
properly configured and enabled. In addition, the DEV FLAG signal may | |
change, depending on the state of the "flag follows control bit 6" action, | |
which may cause the handshake sequencer to change states. | |
Implementation notes: | |
1. The jumpers part of the DHA control word is "cleared" to all ones, which | |
corresponds to installing all of the jumpers. | |
2. The DHA transfer service is called with a null pointer to update the | |
potential change in the flag state. | |
3. Setting bit 2 of the DHA control word reflects the current state of the | |
PON and ~PFWARN signals in status bits 9 and 10, respectively. Status 9 | |
is always set, as PON is always active while the machine is operating. | |
Status 10 is normally set to indicate that PFWARN is denied. However, if | |
the system power is failing, PFWARN is asserted from detection until | |
power is lost. | |
*/ | |
static OUTBOUND_SET diag_control (uint32 control_word) | |
{ | |
uint32 new_status; | |
OUTBOUND_SET outbound_signals = NO_SIGNALS; | |
if (control_word & CN_DHA_FN_ENABLE) /* if the decoder is enabled */ | |
switch (CN_DHA_FN (control_word)) { /* then decode the DHA command */ | |
case 0: /* clear the registers */ | |
dha_control_word = DHA_CLEAR; /* initialize the DHA control word */ | |
jumper_set = DHA_CLEAR & DHA_JUMPER_MASK; /* and install all of the jumpers */ | |
break; | |
case 2: /* assert the Device End signal */ | |
device_end_in = TRUE; /* set the external device end line */ | |
break; | |
case 4: /* set the Transfer Error flip-flop */ | |
outbound_signals = set_interrupt (ST_XFERERR_IRQ); /* set the transfer error interrupt flip-flop */ | |
break; | |
case 8: /* connect the device flag to control bit 6 */ | |
dha_control_word |= DHA_FLAG_SEL; /* set the "flag follows control 6" bit */ | |
break; | |
case 10: /* assert the Clear Interface signal */ | |
clear_interface_logic (); /* clear the interface logic */ | |
outbound_signals = set_interrupt (ST_CLRIF_IRQ); /* and set the clear interface interrupt flip-flop */ | |
break; | |
case 12: /* connect status 8-10 to master clear/power on/power fail */ | |
dha_control_word |= DHA_STAT_SEL; /* set the "status follows master clear-power on-power fail" bit */ | |
break; | |
default: /* remove a jumper */ | |
dha_control_word &= jumper_map [CN_DHA_FN (control_word)]; /* clear the specified control register bit */ | |
jumper_set = dha_control_word & DHA_JUMPER_MASK; /* and remove the indicated jumper */ | |
break; | |
} | |
if (dha_control_word & DHA_STAT_SEL) { /* if status follows master clear/power on/power fail */ | |
new_status = ST_DHA_PON; /* then indicate that power is on */ | |
if (power_warning == FALSE) /* if we have seen a PFWARN signal */ | |
new_status |= ST_DHA_NOT_PF; /* then indicate that power has not failed */ | |
if (dha_control_word & DHA_MR) /* if a master reset is requested */ | |
new_status |= ST_DHA_MR; /* then indicate a master clear */ | |
} | |
else /* otherwise set the device status */ | |
new_status = ST_DEVIRQ (CN_DHA_ST (control_word)); /* from the connected DHA control bits */ | |
outbound_signals |= set_device_status (ST_DEVIRQ_MASK, new_status); /* set the status and test for IRQs */ | |
xfer_service (NULL); /* record the current device flag state */ | |
outbound_signals |= handshake_xfer (); /* check for a device handshake transition */ | |
return outbound_signals; /* return INTREQ if any interrupts were requested */ | |
} | |
/* Printer local SCP support routines */ | |
/* Service the transfer handshake for the printer. | |
The printer transfer service is called to output a character to the printer | |
buffer or to output a format command that causes the buffered line to be | |
printed with specified paper movement. | |
In hardware, the interface places a character or format code on the lower | |
seven data out lines and asserts STROBE (DEV CMD) to the printer. The | |
printer responds by denying DEMAND (asserting DEV FLAG). The interface then | |
denies STROBE and waits for the printer to reassert DEMAND (deny DEV FLAG) to | |
indicate that the buffer load or print operation is complete. | |
In simulation, this service routine is called twice for each transfer. It is | |
called directly with Device Command set and then after a variable delay with | |
Device Command clear. In response to the former call, the routine sets the | |
Device Flag, loads the character buffer or prints the buffered line, and then | |
sets up an event delay corresponding to the operation performed. In response | |
to the latter call, the routine clears the Device Flag and then clears the | |
event delay time, so that the routine will be reentered directly when Device | |
Command sets again. | |
If a SET LP OFFLINE command or a DETACH LP command simulating an out-of-paper | |
condition is given, the printer will not honor the command immediately if | |
data exists in the print buffer or the printer is currently printing a line. | |
In this case, the action is deferred until the service routine is entered to | |
complete a print operation. At that point, the printer goes offline with | |
DEMAND denied. This leaves the transfer handshake incomplete. When the | |
printer is placed back online, this routine is called to assert DEMAND and | |
conclude the handshake. | |
Control word bit 10 determines whether the code on the data out lines is | |
interpreted as a character (0) or a format command (1). Character data is | |
loaded into the buffer; if the line length is exceeded, the printer | |
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. 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. | |
2. 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. | |
3. 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. | |
4. As a convenience to the user, the printer output file is flushed when a | |
TOF operation is performed. | |
5. The user may examine the TFAULT and PFAULT registers to determine why the | |
printer went offline. | |
6. The transfer service may be called with a null pointer to update the | |
potential change in the flag state. | |
7. If printing is attempted with the printer offline, this routine will be | |
called with STROBE asserted (device_command_in TRUE) and DEMAND denied | |
(device_flag_in TRUE). The printer ignores STROBE if DEMAND is not | |
asserted, so we simply return in this case. This will hang the handshake | |
until the printer is set online, and we are reentered with DEMAND | |
asserted. | |
*/ | |
static t_stat lp_service (UNIT *uptr) | |
{ | |
const t_bool printing = ((control_word & CN_FORMAT) != 0); /* TRUE if a print command was received */ | |
static uint32 overprint_index = 0; | |
PRINTER_TYPE model; | |
uint8 data_byte, format_byte; | |
uint16 channel; | |
uint32 line_count, slew_count, vfu_status; | |
if (uptr == NULL) /* if we're called for a state update */ | |
return SCPE_OK; /* then return with no other action */ | |
dprintf (lp_dev, DEB_SERV, "%s state printer service entered\n", | |
state_name [sequencer]); | |
if (device_command_out == FALSE) { /* if STROBE has denied */ | |
if (printing) { /* then if printing occurred */ | |
buffer_index = 0; /* then clear the buffer */ | |
if (paper_fault) /* if an out-of-paper condition is pending */ | |
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 */ | |
dprintf (lp_dev, DEB_CMD, "Commanded VFU channel is not punched\n"); | |
return lp_set_alarm (uptr); /* then take the printer offline now */ | |
} | |
else if (offline_pending) { /* otherwise if a non-alarm offline request is pending */ | |
lp_set_locality (uptr, Offline); /* then take the printer offline now */ | |
return SCPE_OK; | |
} | |
} | |
device_flag_in = FALSE; /* assert DEMAND to complete the handshake */ | |
uptr->wait = 0; /* and request direct entry when STROBE next asserts */ | |
} | |
else if (device_flag_in == FALSE) { /* otherwise if STROBE has asserted while DEMAND is asserted */ | |
device_flag_in = TRUE; /* then deny DEMAND */ | |
model = GET_MODEL (uptr->flags); /* get the printer type */ | |
data_byte = (uint8) (data_out & DATA_MASK); /* only the lower 7 bits are connected */ | |
if (printing == FALSE) { /* if loading the print buffer */ | |
if (data_byte > '_' /* then if the character is "lowercase" */ | |
&& print_props [model].char_set == 64) /* but the printer doesn't support it */ | |
data_byte = data_byte - 040; /* then shift it to "uppercase" */ | |
if ((data_byte < ' ' || data_byte == DEL) /* if the character is a control character */ | |
&& print_props [model].char_set != 128) /* but the printer doesn't support it */ | |
data_byte = ' '; /* then substitute a space */ | |
if (buffer_index < print_props [model].line_length) { /* if there is room in the buffer */ | |
if (overprint_index == 0 /* then if not overprinting */ | |
|| buffer_index >= overprint_index /* or past the current buffer limit */ | |
|| buffer [buffer_index] == ' ') /* or overprinting a blank */ | |
buffer [buffer_index] = data_byte; /* then store the character */ | |
else if (data_byte != ' ' /* otherwise if we're overprinting a character */ | |
&& data_byte != buffer [buffer_index]) /* with a different character */ | |
buffer [buffer_index] = (uint8) overprint_char; /* then substitute the overprint character */ | |
buffer_index++; /* increment the buffer index */ | |
uptr->wait = dlyptr->buffer_load; /* schedule the buffer load delay */ | |
} | |
else { /* otherwise the buffer is full */ | |
dprintf (lp_dev, DEB_CMD, "Buffer overflow printed %u characters on line %u\n", | |
buffer_index, current_line); | |
buffer [buffer_index++] = CR; /* tie off */ | |
buffer [buffer_index++] = LF; /* the current buffer */ | |
fwrite (buffer, sizeof buffer [0], /* write the buffer to the printer file */ | |
buffer_index, uptr->fileref); | |
uptr->pos = 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 */ | |
dprintf (lp_dev, DEB_CMD, "Printer advanced 1 line to line %u\n", | |
current_line); | |
overprint_index = 0; /* clear any accumulated overprint index */ | |
buffer [0] = data_byte; /* store the character */ | |
buffer_index = 1; /* in the empty buffer */ | |
uptr->wait = dlyptr->print /* schedule the print delay */ | |
+ dlyptr->advance /* plus the paper advance delay */ | |
+ dlyptr->buffer_load; /* plus the buffer load delay */ | |
} | |
dprintf (lp_dev, DEB_XFER, "Character %s sent to printer\n", | |
fmt_char (data_byte)); | |
} | |
else { /* otherwise this is a print format command */ | |
dprintf (lp_dev, DEB_XFER, "Format code %03o sent to printer\n", | |
data_byte); | |
format_byte = data_byte & FORMAT_MASK; /* format commands ignore bits 10-11 */ | |
if (overprint_index > buffer_index) /* if the overprinted line is longer than the current line */ | |
buffer_index = overprint_index; /* then extend the current buffer index */ | |
if (buffer_index > 0 && format_byte != FORMAT_SUPPRESS) /* if printing will occur, then trace it */ | |
dprintf (lp_dev, DEB_CMD, "Printed %u character%s on line %u\n", | |
buffer_index, (buffer_index == 1 ? "" : "s"), current_line); | |
if (format_byte == FORMAT_SUPPRESS /* if this is a "suppress space" request */ | |
&& print_props [model].overprints) { /* and the printer is capable of overprinting */ | |
slew_count = 0; /* then do not slew after printing */ | |
if (uptr->flags & UNIT_EXPAND) { /* if the printer is in expanded mode */ | |
if (buffer_index > overprint_index) /* then if the current line is longer than the overprinted line */ | |
overprint_index = buffer_index; /* then extend the overprinted line */ | |
buffer_index = 0; /* reset the buffer index to overprint the next line */ | |
} | |
else /* otherwise the printer is in compact mode */ | |
buffer [buffer_index++] = CR; /* so overprint by emitting a CR without a LF */ | |
dprintf (lp_dev, DEB_CMD, "Printer commanded to suppress spacing on line %u\n", | |
current_line); | |
} | |
else if (format_byte & FORMAT_VFU) { /* otherwise if this is a VFU command */ | |
if (print_props [model].vfu_channels == 8) /* then if it's an 8-channel VFU */ | |
format_byte &= FORMAT_VFU_8_MASK; /* then only three bits are significant */ | |
channel = VFU_CHANNEL_1 >> (format_byte - FORMAT_VFU_BIAS - 1); /* set the requested channel */ | |
dprintf (lp_dev, DEB_CMD, "Printer commanded to slew to VFU channel %u from line %u\n", | |
format_byte - FORMAT_VFU_BIAS, current_line); | |
tape_fault = (channel & VFU [0]) == 0; /* a tape fault occurs if there is no punch in this channel */ | |
slew_count = 0; /* initialize the slew counter */ | |
do { /* the VFU always slews at least one line */ | |
slew_count++; /* increment the slew counter */ | |
current_line++; /* and the line counter */ | |
if (current_line > form_length) /* if the current line is beyond the end of the form */ | |
current_line = 1; /* then reset to the top of the next form */ | |
} | |
while (!tape_fault && (channel & VFU [current_line]) == 0); /* continue until a punch is seen */ | |
} | |
else { /* otherwise it must be a slew command */ | |
slew_count = format_byte; /* get the number of lines to slew */ | |
if (format_byte == FORMAT_SUPPRESS) /* if the printer cannot overprint */ | |
slew_count = 1; /* then the paper advances after printing */ | |
dprintf (lp_dev, DEB_CMD, "Printer commanded to slew %u line%s from line %u\n", | |
slew_count, (slew_count == 1 ? "" : "s"), current_line); | |
current_line = current_line + slew_count; /* move the current line */ | |
if (current_line > form_length) /* if the current line is beyond the end of the form */ | |
current_line = current_line - form_length; /* then it extends onto the next form */ | |
} | |
if (format_byte == FORMAT_VFU_CHAN_1 /* if a TOF was requested */ | |
&& !(uptr->flags & UNIT_EXPAND) /* and the printer is in compact mode */ | |
&& slew_count > 1) { /* and more than one line is needed to reach the TOF */ | |
if (buffer_index > 0) { /* then if the buffer not empty */ | |
buffer [buffer_index++] = CR; /* then print */ | |
buffer [buffer_index++] = LF; /* the current line */ | |
} | |
buffer [buffer_index++] = FF; /* emit a FF to move to the TOF */ | |
} | |
else if (slew_count > 0) { /* otherwise a slew is needed */ | |
buffer [buffer_index++] = CR; /* then emit a CR LF */ | |
buffer [buffer_index++] = LF; /* to print the current line */ | |
line_count = slew_count; /* get the number of lines to slew */ | |
while (--line_count > 0) { /* while movement is needed */ | |
if (uptr->flags & UNIT_EXPAND) /* if the printer is in expanded mode */ | |
buffer [buffer_index++] = CR; /* then blank lines are CR LF pairs */ | |
buffer [buffer_index++] = LF; /* otherwise just LFs are used */ | |
} | |
} | |
if (buffer_index > 0) { /* if the buffer is not empty */ | |
fwrite (buffer, sizeof buffer [0], /* then write it to the printer file */ | |
buffer_index, uptr->fileref); | |
overprint_index = 0; /* clear any existing overprint index */ | |
} | |
vfu_status = 0; /* assume no punches for channels 9 and 12 */ | |
if (print_props [model].vfu_channels > 8) { /* if the printer VFU has more than 8 channels */ | |
if (VFU [current_line] & VFU_CHANNEL_9) /* then if channel 9 is punched for this line */ | |
vfu_status |= ST_VFU_9; /* then report it in the device status */ | |
if (VFU [current_line] & VFU_CHANNEL_12) /* if channel 12 is punched for this line */ | |
vfu_status |= ST_VFU_12; /* then report it in the device status */ | |
} | |
set_device_status (ST_VFU_9 | ST_VFU_12, vfu_status); /* set the VFU status */ | |
if (format_byte == FORMAT_VFU_CHAN_1) /* if a TOF request was performed */ | |
fflush (uptr->fileref); /* then flush the file buffer for inspection */ | |
uptr->wait = dlyptr->print /* schedule the print delay */ | |
+ slew_count * dlyptr->advance; /* plus the paper advance delay */ | |
uptr->pos = ftell (uptr->fileref); /* update the file position */ | |
if (slew_count > 0) | |
dprintf (lp_dev, DEB_CMD, "Printer advanced %u line%s to line %u\n", | |
slew_count, (slew_count == 1 ? "" : "s"), current_line); | |
} | |
if (ferror (uptr->fileref)) { /* if a host file system error occurred */ | |
cprintf ("%s simulator printer I/O error: %s\n", /* then report the error to the console */ | |
sim_name, strerror (errno)); | |
clearerr (uptr->fileref); /* clear the error */ | |
lp_set_alarm (uptr); /* set an alarm condition */ | |
return SCPE_IOERR; /* and stop the simulator */ | |
} | |
} | |
return SCPE_OK; /* return event service success */ | |
} | |
/* Attach the printer image file. | |
The specified file is attached to the indicated unit. This is the simulation | |
equivalent of loading paper into the printer and pressing the ONLINE button. | |
The transition from offline to online causes an interrupt. | |
A new image file may be requested by giving the "-N" switch to the attach | |
command. If an existing file is specified with "-N", it will be cleared; if | |
specified without "-P", 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. | |
As a special case, a detach (out-of-paper condition) that has been deferred | |
until printing completes may be cancelled by giving an attach command without | |
a filename, i.e., ATTACH LP. If a filename is given, but the detach is still | |
pending, the routine returns a "Command not allowed" error. | |
Implementation notes: | |
1. If we are called during a RESTORE command to reattach a file previously | |
attached when the simulation was SAVEd, the device status and file | |
position are not altered. | |
2. The pointer to the appropriate event delay times is set in case we are | |
being called during a RESTORE command (the assignment is redundant | |
otherwise). | |
*/ | |
static t_stat lp_attach (UNIT *uptr, CONST char *cptr) | |
{ | |
t_stat result = SCPE_OK; | |
if (paper_fault && offline_pending) /* if an out-of-paper condition is deferred */ | |
if (cptr == NULL) /* then if an attach command is given without a filename */ | |
offline_pending = FALSE; /* then cancel the request, leaving the file attached */ | |
else /* otherwise a filename was specified */ | |
return SCPE_NOFNC; /* but we can't attach until the previous file detaches */ | |
else { /* otherwise no deferral is active */ | |
result = attach_unit (uptr, cptr); /* so attach the specified printer image file */ | |
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 */ | |
set_device_status (ST_NOT_READY, 0); /* then clear not-ready status */ | |
current_line = 1; /* reset the line counter to the top of the form */ | |
if (sim_switches & SWMASK ('N')) /* if a new (empty) file was requested */ | |
uptr->pos = 0; /* then position at the start of the file */ | |
else if (fseek (uptr->fileref, 0, SEEK_END) == 0) /* otherwise append by seeking to the end of the file */ | |
uptr->pos = (t_addr) ftell (uptr->fileref); /* and repositioning if the seek succeeded */ | |
dprintf (lp_dev, DEB_CMD, "Printer paper loaded\n"); | |
lp_set_locality (uptr, Online); /* set the printer online */ | |
} | |
} | |
paper_fault = FALSE; /* clear any existing paper fault */ | |
if (lp_dev.flags & DEV_REALTIME) /* if the printer is in real-time mode */ | |
dlyptr = &real_times [GET_MODEL (uptr->flags)]; /* then point at the times for the current model */ | |
else /* otherwise */ | |
dlyptr = &fast_times; /* point at the fast times */ | |
return result; /* return the result of the attach */ | |
} | |
/* Detach the printer image file. | |
The specified file is detached from the indicated unit. This is the | |
simulation equivalent to unloading the paper from the printer or the printer | |
running out of paper. The out-of-paper condition cause a paper fault alarm, | |
and the printer goes offline. The transition from online to offline causes | |
an interrupt. | |
When the printer runs out of paper, it will not go offline until characters | |
present in the buffer are printed and paper motion stops. In simulation, | |
entering a DETACH LP command while the printer is busy will defer the file | |
detach until the current print operation completes. | |
Implementation notes: | |
1. During simulator shutdown, this routine is called for all three units, | |
not just the printer unit. The printer must be detached, even if a | |
detach has been deferred, to ensure that the file is closed properly. We | |
do this in response to a detach request with the SIM_SW_SHUT switch | |
present. | |
2. The DETACH ALL command will fail if any detach routine returns a status | |
other than SCPE_OK. Because a deferred detach is not fatal, we must | |
return SCPE_OK, but we still want to print a warning to the user. | |
*/ | |
static t_stat lp_detach (UNIT *uptr) | |
{ | |
if (uptr->flags & UNIT_ATTABLE) /* if we're being called for the printer unit */ | |
if ((uptr->flags & UNIT_ATT) == 0) /* then if the unit is not currently attached */ | |
return SCPE_UNATT; /* then report it */ | |
else if (lp_set_alarm (uptr) /* otherwise if a paper alarm is accepted */ | |
|| (sim_switches & SIM_SW_SHUT)) { /* or this is a shutdown call */ | |
paper_fault = TRUE; /* then set the out-of-paper condition */ | |
dprintf (lp_dev, DEB_CMD, "Printer is out of paper\n"); | |
return detach_unit (uptr); /* and detach the unit */ | |
} | |
else { /* otherwise the alarm was rejected at this time */ | |
paper_fault = TRUE; /* so set the out-of-paper condition now */ | |
dprintf (lp_dev, DEB_CMD, "Paper out request deferred until print completes\n"); | |
cputs ("Command deferred\n"); /* but the actual detach must be deferred */ | |
return SCPE_OK; /* until the buffer prints */ | |
} | |
else /* otherwise */ | |
return SCPE_UNATT; /* we've been called for the wrong unit */ | |
} | |
/* Set the device modes. | |
This validation routine is entered with the "value" parameter set to one of | |
the DEVICE_MODES values. The device flag implied by the value is set or | |
cleared. The unit, character, and descriptor pointers are not used. | |
Implementation notes: | |
1. Switching between printer and diagnostic mode sets the configuration | |
jumpers accordingly. | |
2. Switching between printer and diagnostic mode clears the event delay. | |
This is necessary in case the command was entered while an event was | |
queued. | |
*/ | |
static t_stat lp_set_mode (UNIT *uptr, int32 value, CONST char *cptr, void *desc) | |
{ | |
switch ((DEVICE_MODES) value) { /* dispatch the mode to set */ | |
case Fast_Time: /* entering optimized timing mode */ | |
lp_dev.flags &= ~DEV_REALTIME; /* so clear the real-time flag */ | |
dlyptr = &fast_times; /* and point at the fast times */ | |
break; | |
case Real_Time: /* entering realistic timing mode */ | |
lp_dev.flags |= DEV_REALTIME; /* so set the real-time flag */ | |
dlyptr = &real_times [GET_MODEL (uptr->flags)]; /* and point at the times for the current model */ | |
break; | |
case Printer: /* entering printer mode */ | |
lp_dev.flags &= ~DEV_DIAG; /* so clear the diagnostic flag */ | |
xfer_unit.wait = 0; /* and clear any event delay that had been set */ | |
jumper_set = PRINTER_JUMPERS; /* set the jumpers for printer operation */ | |
break; | |
case Diagnostic: /* entering diagnostic mode */ | |
lp_dev.flags |= DEV_DIAG; /* so set the diagnostic flag */ | |
xfer_unit.wait = 0; /* and clear any event delay that had been set */ | |
jumper_set = dha_control_word & DHA_JUMPER_MASK; /* set the jumpers for DHA operation */ | |
break; | |
} | |
return SCPE_OK; /* the mode change succeeds */ | |
} | |
/* 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 (lp_dev.flags & DEV_REALTIME) /* if the printer is in real-time mode */ | |
dlyptr = &real_times [GET_MODEL (uptr->flags)]; /* 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. | |
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 prints "Command deferred" | |
to inform the user. Otherwise, the unit is set offline, DEMAND is denied, | |
and DEV END is asserted to indicate that the printer is not ready. | |
If the printer is being put online, the unit must be attached (i.e., paper | |
must be loaded), or the command is rejected. If paper is present, the unit | |
is set online, and any tape fault present is cleared. If the sequencer | |
indicates an incomplete handshake, as would occur if paper ran out while | |
printing, the transfer service is called to complete the handshake by | |
asserting DEMAND. Otherwise, DEMAND is asserted explicitly, and DEV END is | |
denied. | |
Transitions between the offline and online state cause interrupts, and INTREQ | |
is asserted to the IOP if a transition occurred (but not, e.g., for a SET LP | |
OFFLINE command where the printer is already offline). | |
Implementation notes: | |
1. Because a deferred offline request is not fatal, we return SCPE_OK to | |
allow command files to continue to execute, but we print a warning to the | |
user. | |
*/ | |
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 */ | |
lp_set_locality (uptr, Online); /* then set the printer online */ | |
else if (lp_set_locality (uptr, Offline) == FALSE) { /* otherwise if it cannot be set offline now */ | |
cputs ("Command deferred\n"); /* then let the user know */ | |
dprintf (lp_dev, DEB_CMD, "Offline request deferred until print completes\n"); | |
} | |
return SCPE_OK; /* return operation success */ | |
} | |
/* Set the VFU tape. | |
This validation routine is entered to set up the VFU on the printer. It is | |
invoked by one of two commands: | |
SET LP VFU | |
SET LP VFU=<filename> | |
The first form loads the standard 66-line tape into the VFU. The second form | |
loads the VFU with the tape image specified by the filename. The format of | |
the tape image is described in the comments for the "lp_load_vfu" routine. | |
On entry, "uptr" points at the printer unit, "cptr" points to the first | |
character after the "VFU" keyword, and the "value" and "desc" parameters are | |
unused. If "cptr" is NULL, then the first command form was given, and the | |
"lp_load_vfu" routine is called with a NULL file stream pointer to indicate | |
that the standard VFU tape should be used. Otherwise, the second command | |
form was given, and "cptr" points to the supplied filename. The file is | |
opened, and the "lp_load_vfu" routine is called with the stream pointer to | |
load the VFU tape image contained therein. | |
*/ | |
static t_stat lp_set_vfu (UNIT *uptr, int32 value, 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 */ | |
(lp_dev.flags & DEV_REALTIME ? "realistic" : "fast"), | |
(lp_dev.flags & DEV_DIAG ? "diagnostic" : "printer")); | |
return SCPE_OK; | |
} | |
/* Show the VFU tape. | |
This display routine is called to show the content of the tape currently | |
loaded in the printer's VFU. The "value" parameter indicates how the routine | |
was called. It is 0 if a SHOW LP command was given and 1 if a SHOW LP VFU | |
command was issued. For the former, only the VFU title is displayed. The | |
latter displays the VFU title, followed by a header labelling each of the | |
channel columns and then one line for each line of the form consisting of | |
punch and no-punch characters, according to the VFU definition. | |
The output stream is passed in the "st" parameter, and the "uptr" and "desc" | |
parameters are ignored. | |
Implementation notes: | |
1. Setting the string precision for the header lines trims them to the | |
appropriate number of channels. | |
*/ | |
static t_stat lp_show_vfu (FILE *st, UNIT *uptr, int32 value, 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); | |
const uint32 channel_count = print_props [model].vfu_channels; | |
uint32 chan, line, current_channel; | |
if (value == 0) /* if we're called for a summary display */ | |
fputs (vfu_title, st); /* then output only the VFU title */ | |
else { /* otherwise the full VFU definition is requested */ | |
fprintf (st, "\n%s tape is loaded.\n\n", vfu_title); /* so start by displaying the VFU title */ | |
fprintf (st, "Line %.*s\n", channel_count * 5, header_1); /* display the */ | |
fprintf (st, "---- %.*s\n", channel_count * 5, header_2); /* channel headers */ | |
for (line = 1; line <= form_length; line++) { /* loop through the VFU array */ | |
fprintf (st, "%3d ", line); /* display the current form line number */ | |
current_channel = VFU_CHANNEL_1; /* start with channel 1 */ | |
for (chan = 1; chan <= channel_count; chan++) { /* loop through the defined channels */ | |
fputs (" ", st); /* add some space */ | |
if (VFU [line] & current_channel) /* if the current channel is punched for this line */ | |
fputc (punched_char, st); /* then display a punched location */ | |
else /* otherwise */ | |
fputc (unpunched_char, st); /* display an unpunched location */ | |
current_channel = current_channel >> 1; /* move to the next channel */ | |
} | |
fputc ('\n', st); /* end the line */ | |
} | |
} | |
return SCPE_OK; | |
} | |
/* Printer local utility routines */ | |
/* Printer reset. | |
This routine is called when the MASTER CLEAR signal is asserted to the | |
printer. The "programmed_clear" parameter is TRUE if the routine is called | |
for a Programmed Master Clear or IORESET assertion, and FALSE if the routine | |
is called for a RESET or RESET LP command. In the latter case, the presence | |
of the "-P" switch indicates that this is a power-on reset. In either case, | |
the interface reset has already been performed; this routine is responsible | |
for resetting the printer only. | |
In hardware, asserting MASTER CLEAR: | |
- clears the input buffer without printing | |
- finishes printing the current line and performs any stored VFU action | |
- inhibits DEMAND for approximately three milliseconds | |
In simulation, the buffer index is reset, a tape fault is cleared, a paper | |
fault is determined by the paper status, and any deferred offline command is | |
cleared. Printing is always "complete" at the point of entry, as the actual | |
file write is instantaneous from the simulation perspective. DEMAND is not | |
altered, as the printer is "ready" as soon as the command completes. DEV END | |
is reset to the offline status (asserted if the printer is offline, denied if | |
online). | |
In addition, if a power-on reset (RESET -P) is done, the original FASTTIME | |
settings are restored, the standard VFU tape is loaded, and the power failure | |
warning is cleared. | |
*/ | |
static t_stat lp_reset (t_bool programmed_clear) | |
{ | |
OUTBOUND_SET signals; | |
uint32 new_status = 0; | |
t_stat result = SCPE_OK; | |
if (! programmed_clear && (sim_switches & SWMASK ('P'))) { /* if this is a commanded power-on reset */ | |
fast_times.buffer_load = LP_BUFFER_LOAD; /* then reset the per-character transfer time, */ | |
fast_times.print = LP_PRINT; /* the print and advance-one-line time, */ | |
fast_times.advance = LP_ADVANCE; /* and the slew additional lines time */ | |
result = lp_load_vfu (xfer_uptr, NULL); /* load the standard VFU tape */ | |
power_warning = FALSE; /* clear the power failure warning */ | |
} | |
buffer_index = 0; /* clear the buffer without printing */ | |
offline_pending = FALSE; /* cancel any pending offline request */ | |
tape_fault = FALSE; /* clear any tape fault */ | |
paper_fault = ! (xfer_unit.flags & UNIT_ATT); /* and set paper fault if out of paper */ | |
if (paper_fault) /* if a paper fault exists */ | |
new_status |= ST_NOT_READY; /* then set not-ready status */ | |
if (xfer_unit.flags & UNIT_OFFLINE) { /* if the printer is offline */ | |
device_flag_in = TRUE; /* then DEMAND denies while the printer is not ready */ | |
device_end_in = TRUE; /* and DEV END asserts while the printer is offline */ | |
} | |
else { /* otherwise the printer is online */ | |
new_status |= ST_ONLINE; /* so set online status */ | |
device_flag_in = FALSE; /* DEMAND asserts when the printer is ready */ | |
device_end_in = FALSE; /* and DEV END denies when the printer is online */ | |
} | |
xfer_service (NULL); /* tell the data transfer service that signals have changed */ | |
signals = set_device_status (ST_ONLINE | ST_NOT_READY, /* set the new device status */ | |
new_status); | |
if (signals & INTREQ) /* if the status change caused an interrupt */ | |
iop_assert_INTREQ (&lp_dib); /* then assert the INTREQ signal */ | |
return result; /* return the result of the reset */ | |
} | |
/* Process the printer control word. | |
This routine is called when a DCONTSTB or PCONTSTB assertion indicates that | |
the control word is to be set. No direct action is taken by the printer in | |
response, so the routine simply returns. | |
*/ | |
static OUTBOUND_SET lp_control (uint32 control_word) | |
{ | |
return NO_SIGNALS; /* no special control action is needed */ | |
} | |
/* Set an alarm condition. | |
This routine is called when an alarm condition exists. An alarm occurs when | |
paper is out (paper fault) or a VFU command addresses a channel that does not | |
contain a punch (tape fault). In response, the printer goes not ready and | |
offline. | |
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) | |
{ | |
if (lp_set_locality (uptr, Offline)) { /* if the printer went offline */ | |
set_device_status (ST_NOT_READY, ST_NOT_READY); /* then set the printer not-ready */ | |
return TRUE; /* and return completion success */ | |
} | |
else /* otherwise the offline request is pending */ | |
return FALSE; /* so return deferral status */ | |
} | |
/* Set the printer locality. | |
This routine is called to set the printer online or offline and returns TRUE | |
if the request succeeded or FALSE if it was deferred. An online request | |
always succeeds, so it is up to the caller to ensure that going online is | |
permissible (e.g., that paper is loaded into the printer). An offline | |
request succeeds only if the printer is idle. If characters are present in | |
the print buffer, or if the printer is printing or slewing, then the request | |
is deferred until the current line is complete. | |
The printer cable inversely connects DEMAND to the Device Flag input and | |
ONLINE to the Device End input. As both deny when the printer goes offline | |
and assert when the printer goes online, Device Flag and Device End assert | |
and deny, respectively. | |
If the printer goes offline with an operation in progress, Device Flag will | |
remain asserted, and the handshake sequencer will remain in the Device_Flag_1 | |
or Device_Flag_2 state until the printer is set online again. The transfer | |
service routine is informed of these state changes, so that the handshake can | |
complete when the printer is again set online. | |
Implementation notes: | |
1. When called with a NULL parameter, the transfer service routine will | |
update its internal device flag state but will take no other action. | |
When called with a unit pointer, the routine continues the handshake | |
sequence. | |
*/ | |
static t_bool lp_set_locality (UNIT *uptr, LOCALITY printer_state) | |
{ | |
OUTBOUND_SET signals; | |
if (printer_state == Offline) { /* if the printer is going offline */ | |
if (buffer_index == 0 /* then if the buffer is empty */ | |
&& sim_is_active (uptr) == FALSE) { /* and the printer is idle */ | |
uptr->flags |= UNIT_OFFLINE; /* then set the printer offline now */ | |
signals = set_device_status (ST_ONLINE, 0); /* then set the printer offline now */ | |
device_flag_in = TRUE; /* DEMAND denies while the printer is offline */ | |
device_end_in = TRUE; /* DEV END asserts while the printer is offline */ | |
xfer_service (NULL); /* inform the service routine of the signal changes */ | |
} | |
else { /* otherwise the request must wait */ | |
offline_pending = TRUE; /* until the line is printed */ | |
return FALSE; /* 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 */ | |
signals = set_device_status (ST_ONLINE | ST_NOT_READY, /* set online status */ | |
ST_ONLINE); /* and clear not ready status */ | |
device_flag_in = FALSE; /* DEMAND asserts when the printer is online */ | |
device_end_in = FALSE; /* and DEV END denies when the printer is online */ | |
if (sequencer != Idle) /* if the transfer handshake is in progress */ | |
xfer_service (uptr); /* then complete the suspended operation */ | |
else /* otherwise */ | |
xfer_service (NULL); /* inform the service routine of the signal changes */ | |
} | |
dprintf (lp_dev, DEB_CMD, "Printer set %s\n", | |
(printer_state == Offline ? "offline" : "online")); | |
if (signals & INTREQ) /* if the transition caused an interrupt */ | |
iop_assert_INTREQ (&lp_dib); /* then assert the INTREQ signal */ | |
offline_pending = FALSE; | |
return TRUE; | |
} | |
/* 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 (1535-2655 for the 8-channel HP 2607 and 2613-80001 for | |
the 12-channel HP 2613, 2617, and 2618) defines the channels as: | |
Chan Description | |
---- -------------- | |
1 Top of form | |
2 Bottom of form | |
3 Single space | |
4 Double space | |
5 Triple space | |
6 Half page | |
7 Quarter page | |
8 Sixth page | |
9 Bottom of form | |
...with channels 10-12 uncommitted. A custom tape must dedicate channel 1 to | |
the top-of-form, but the other channels may be defined as desired. | |
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. | |
A semicolon appearing anywhere on a line begins a comment, and the semicolon | |
and all following characters are ignored. Zero-length lines, including lines | |
beginning with a semicolon, are ignored. | |
Note that a line containing one or more blanks is not a zero-length line, so, | |
for example, the line " ; a comment starting in column 2" is not ignored. | |
The first (non-ignored) line in the file is a VFU definition line of this | |
exact form: | |
VFU=<punch characters>,<no-punch character>{,<title>} | |
...where: | |
Parameter Description | |
------------------ ------------------------------------------------------- | |
punch characters a string of one or more characters used interchangeably | |
to represent a punched location | |
no-punch character a single character representing a non-punched location | |
title an optional descriptive string printed by the SHOW LP | |
VFU command ("Custom VFU" is used by default) | |
If the "VFU" line is missing or not of the correct form, then "Format error" | |
status is returned, and the VFU tape is not changed. | |
The remaining (non-ignored) lines define the channels punched for each line | |
of the printed form. The line format consists of a sequence of punch, | |
no-punch, and "other" characters in channel order. Each punch or no-punch | |
character defines a channel state, starting with channel 1 and proceeding | |
left-to-right until all channels for the VFU are defined; if the line | |
terminates before all channels are defined, the remaining channels are set to | |
the no-punch state. Any "other" characters (i.e., neither a punch character | |
nor a no-punch character) are ignored and may be used freely to delineate the | |
tape channels. | |
Examples using the standard 66-line tape definition for an 8-channel VFU: | |
; the VFU definition | VFU=1234578, ; no-punch is a ' ' | |
VFU=1,0,a binary tape image | | |
| 1 ; top of form | |
; the channel definitions | 345 ; form line 1 | |
| 3 ; form line 2 | |
10111111 ; top of form | 34 ; form line 3 | |
00100000 ; single space | 3 5 ; form line 4 | |
0011 ; channels 5-8 no-punch | 34 ; form line 5 | |
| | |
-------------------------------------+------------------------------------- | |
| | |
VFU=X,-,blanks are "others" | VFU=TO,.,brackets are "others" | |
| ; 1 2 3 4 5 6 7 8 | |
X - X X X X X X ; line 1 | ;--- --- --- --- --- --- --- --- | |
- - X - - - - - ; line 2 | [T] . [O] [O] [O] [O] [O] [O] | |
- - X X - - - - ; line 3 | . . [O] . . . . . | |
| . . [O] [O] . . . . | |
On entry, the "vf" parameter determines whether the standard tape or a custom | |
tape is to be loaded. If "vf" is NULL, a standard 66-line tape is generated | |
and stored in the tape buffer. Otherwise, a custom tape file is read, | |
parsed, and assembled VFU entries are stored in the tape buffer. After | |
generation or a successful tape load, the buffer is copied to the VFU array, | |
the form length is set, the current line is reset to the top-of-form, and the | |
state of VFU channels 9 and 12 are set into the device status. | |
Implementation notes: | |
1. VFU array entries 1-n correspond to form print lines 1-n. Entry 0 is the | |
logical OR of all of the other entries and is used during VFU format | |
command processing to determine if a punch is present somewhere in a | |
given channel. | |
*/ | |
static t_stat lp_load_vfu (UNIT *uptr, FILE *vf) | |
{ | |
const PRINTER_TYPE model = GET_MODEL (uptr->flags); /* get the printer type */ | |
uint32 line, channel, vfu_status; | |
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 */ | |
strcpy (vfu_title, "Standard VFU"); /* then set the title */ | |
tape [ 1] = VFU_CHANNEL_1; /* punch channel 1 for the top of form */ | |
tape [60] = VFU_CHANNEL_2 | VFU_CHANNEL_9; /* punch channels 2 and 9 for the bottom of form */ | |
for (line = 1; line <= 60; line++) { /* load each of the 60 printable lines */ | |
tape [line] |= VFU_CHANNEL_3 /* punch channel 3 for single space */ | |
| (line % 2 == 1 ? VFU_CHANNEL_4 : 0) /* punch channel 4 for double space */ | |
| (line % 3 == 1 ? VFU_CHANNEL_5 : 0) /* punch channel 5 for triple space */ | |
| (line % 30 == 1 ? VFU_CHANNEL_6 : 0) /* punch channel 6 for next half page */ | |
| (line % 15 == 1 ? VFU_CHANNEL_7 : 0) /* punch channel 7 for next quarter page */ | |
| (line % 10 == 1 ? VFU_CHANNEL_8 : 0); /* punch channel 8 for next sixth page */ | |
tape [0] |= tape [line]; /* accumulate the channel punches */ | |
} | |
form_length = 66; /* set the form length */ | |
} | |
else { /* otherwise load a custom VFU from the file */ | |
len = lp_read_line (vf, buffer, sizeof buffer); /* read the first line */ | |
if (len <= 0 /* if there isn't one */ | |
|| strncmp (buffer, "VFU=", strlen ("VFU=")) != 0) { /* or it's not a VFU definition statement */ | |
cputs ("Missing VFU definition line\n"); /* then complain to the console */ | |
return SCPE_FMT; /* and fail with a format error */ | |
} | |
bptr = buffer + strlen ("VFU="); /* point at the first control argument */ | |
tptr = strtok (bptr, ","); /* parse the punch token */ | |
if (tptr == NULL) { /* if it's missing */ | |
cputs ("Missing punch field in the VFU control line\n"); /* then complain to the console */ | |
return SCPE_FMT; /* and fail with a format error */ | |
} | |
strcpy (punch, tptr); /* save the set of punch characters */ | |
tptr = strtok (NULL, ","); /* parse the no-punch token */ | |
if (tptr == NULL){ /* if it's missing */ | |
cputs ("Missing no-punch field in the VFU control line\n"); /* then complain to the console */ | |
return SCPE_FMT; /* and fail with a format error */ | |
} | |
no_punch = *tptr; /* save the no-punch character */ | |
tptr = strtok (NULL, ","); /* parse the optional title */ | |
if (tptr != NULL) /* if it's present */ | |
strcpy (vfu_title, tptr); /* then save the user's title */ | |
else /* otherwise */ | |
strcpy (vfu_title, "Custom VFU"); /* use a generic title */ | |
for (line = 1; line <= VFU_MAX; line++) { /* load up to the maximum VFU tape length */ | |
len = lp_read_line (vf, buffer, sizeof buffer); /* read a tape definition line */ | |
if (len <= 0) /* if at the EOF or an error occurred */ | |
break; /* then the load is complete */ | |
tptr = buffer; /* point at the first character of the line */ | |
channel = VFU_CHANNEL_1; /* set the channel 1 indicator */ | |
while (channel != 0 && *tptr != '\0') { /* loop until the channel or definition is exhausted */ | |
if (strchr (punch, *tptr) != NULL) { /* if the character is in the punch set */ | |
tape [line] |= channel; /* then punch the current channel */ | |
channel = channel >> 1; /* and move to the next one */ | |
} | |
else if (*tptr == no_punch) /* otherwise if the character is the no-punch character */ | |
channel = channel >> 1; /* then move to the next channel */ | |
tptr++; /* otherwise the character is neither, so ignore it */ | |
} | |
tape [0] |= tape [line]; /* accumulate the channel punches */ | |
} | |
if ((tape [1] & VFU_CHANNEL_1) == 0) { /* if there is no channel 1 punch in the first line */ | |
cputs ("Missing punch in channel 1 of line 1\n"); /* then complain to the console */ | |
return SCPE_FMT; /* and fail with a format error */ | |
} | |
form_length = line - 1; /* set the form length */ | |
} | |
memcpy (VFU, tape, sizeof VFU); /* copy the tape buffer to the VFU array */ | |
current_line = 1; /* reset the line counter to the top of the form */ | |
vfu_status = 0; /* assume no punches for channels 9 and 12 */ | |
if (print_props [model].vfu_channels > 8) { /* if the printer VFU has more than 8 channels */ | |
if (VFU [1] & VFU_CHANNEL_9) /* then if channel 9 is punched for this line */ | |
vfu_status |= ST_VFU_9; /* then report it in the device status */ | |
if (VFU [1] & VFU_CHANNEL_12) /* if channel 12 is punched for this line */ | |
vfu_status |= ST_VFU_12; /* then report it in the device status */ | |
} | |
set_device_status (ST_VFU_9 | ST_VFU_12, vfu_status); /* set the VFU status */ | |
return SCPE_OK; /* the VFU was successfully loaded */ | |
} | |
/* Read a line from the VFU file. | |
This routine reads a line from the VFU tape image file designated by the file | |
stream parameter "vf", stores the data in the string buffer pointed to by | |
"line" and whose size is given by "size", and returns the length of that | |
string. Comments are stripped from lines that are read, and the routine | |
continues to read until a non-zero-length line is found. If the end of the | |
file was reached, the return value is 0. If a file error occurred, the | |
return value is -1. | |
Implementation notes: | |
1. The routine assumes that the file was opened in text mode, so that | |
automatic CRLF-to-LF conversion is done if needed. This simplifies | |
the end-of-line removal. | |
*/ | |
static int32 lp_read_line (FILE *vf, char *line, uint32 size) | |
{ | |
char *result; | |
int32 len = 0; | |
while (len == 0) { | |
result = fgets (line, size, vf); /* get the next line from the file */ | |
if (result == NULL) /* if an error occurred */ | |
if (feof (vf)) /* then if the end of file was seen */ | |
return 0; /* then return an EOF indication */ | |
else { /* otherwise report the error to the console */ | |
cprintf ("%s simulator line printer I/O error: %s\n", | |
sim_name, strerror (errno)); | |
clearerr (vf); /* clear the error */ | |
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; | |
} |