1319 lines
52 KiB
C
1319 lines
52 KiB
C
/* pdp10_lp20.c: PDP-10 LP20 line printer simulator
|
|
|
|
Copyright (c) 1993-2009, Robert M Supnik
|
|
|
|
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
|
|
ROBERT M SUPNIK 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 Robert M Supnik shall not be
|
|
used in advertising or otherwise to promote the sale, use or other dealings
|
|
in this Software without prior written authorization from Robert M Supnik.
|
|
|
|
lp20 line printer
|
|
|
|
23-Jun-13 TL Add optical VFU support and fix some inconsistencies
|
|
with the hardware. Add documentation.
|
|
29-May-13 TL Force append when an existing file is attached.
|
|
Previously over-wrote file from the top.
|
|
19-Jan-07 RMS Added UNIT_TEXT flag
|
|
04-Sep-05 RMS Fixed missing return (found by Peter Schorn)
|
|
07-Jul-05 RMS Removed extraneous externs
|
|
18-Mar-05 RMS Added attached test to detach routine
|
|
29-Dec-03 RMS Fixed bug in scheduling
|
|
25-Apr-03 RMS Revised for extended file support
|
|
29-Sep-02 RMS Added variable vector support
|
|
Modified to use common Unibus routines
|
|
New data structures
|
|
30-May-02 RMS Widened POS to 32b
|
|
06-Jan-02 RMS Added enable/disable support
|
|
30-Nov-01 RMS Added extended SET/SHOW support
|
|
|
|
References:
|
|
EK-LP20-TM-004 LP20 LINE PRINTER SYSTEM MANUAL
|
|
B-TC-LP20-0-1 MP0006 LP20 Field Maintenance Print Set
|
|
DpC255137D Dataproducts Corp Maintenance Guide Vol. I
|
|
300LPM/600 LPM Line Printers.
|
|
LP2SER.MAC TOPS-10 Device driver
|
|
LPKSDV.MAC TOPS-20 Device driver
|
|
LP20.MAC TOPS-10/20 VFU/RAM utility
|
|
LPTSPL/LPTSUB.MAC TOPS-10/20 GALAXY spooler
|
|
*/
|
|
|
|
#include "pdp10_defs.h"
|
|
#include <ctype.h>
|
|
|
|
/* Time (seconds) of idleness before data flushed to attached file. */
|
|
#ifndef LP20_IDLE_TIME
|
|
#define LP20_IDLE_TIME (10)
|
|
#endif
|
|
|
|
/* The LP20 has the following CSR assignments:
|
|
* Unit No. 1: 775400, Vector: 754
|
|
* Unit No. 2: 775420, Vector: 750
|
|
*
|
|
* Note that the KS only supported one LP20.
|
|
* Note also that the vector assigned to unit 2 is lower than unit 1's.
|
|
*/
|
|
|
|
#define UNIT_DUMMY (1 << UNIT_V_UF)
|
|
#define LP_WIDTH 132 /* printer width */
|
|
#define DEFAULT_LPI 6 /* default lines-per-inch of LPT */
|
|
/* DAVFU RAM */
|
|
|
|
#define DV_SIZE 143 /* DAVFU size */
|
|
#define DV_DMASK 077 /* data mask per byte */
|
|
#define DV_TOF 0 /* top of form channel */
|
|
#define DV_BOF 11 /* bottom of form channel */
|
|
#define DV_MAX 11 /* max channel number */
|
|
#define MIN_VFU_LEN 2 /* minimum VFU length (in inches) */
|
|
#define VFU_LEN_VALID(lines, lpi) ((lines) >= (lpi * MIN_VFU_LEN))
|
|
|
|
/* Translation RAM */
|
|
|
|
#define TX_SIZE 256 /* translation RAM */
|
|
#define TX_AMASK (TX_SIZE - 1)
|
|
#define TX_DMASK 007777
|
|
#define TX_PARITY 010000 /* Parity bit (emulated: 'valid'; unwritten has bad 'parity') */
|
|
#define TX_V_FL 8 /* flags */
|
|
#define TX_M_FL 017
|
|
/* define TX_INTR 04000 *//* interrupt */
|
|
#define TX_DELH 02000 /* delimiter */
|
|
/* define TX_XLAT 01000 *//* translate */
|
|
/* define TX_DVFU 00400 *//* DAVFU */
|
|
#define TX_SLEW 00020 /* chan vs slew */
|
|
#define TX_VMASK 00017 /* spacing mask */
|
|
#define TX_CHR 0 /* states: pr char */
|
|
#define TX_RAM 1 /* pr translation */
|
|
#define TX_DVU 2 /* DAVFU action */
|
|
#define TX_INT 3 /* interrupt */
|
|
#define TX_GETFL(x) (((x) >> TX_V_FL) & TX_M_FL)
|
|
|
|
/* LPCSRA (765400) */
|
|
|
|
#define CSA_GO 0000001 /* go */
|
|
#define CSA_PAR 0000002 /* parity enable NI */
|
|
#define CSA_V_FNC 2 /* function */
|
|
#define CSA_M_FNC 03
|
|
#define FNC_PR 0 /* print */
|
|
#define FNC_TST 1 /* test */
|
|
#define FNC_DVU 2 /* load DAVFU */
|
|
#define FNC_RAM 3 /* load translation RAM */
|
|
#define FNC_INTERNAL 1 /* internal function */
|
|
#define CSA_FNC (CSA_M_FNC << CSA_V_FNC)
|
|
#define CSA_V_UAE 4 /* Unibus addr extension */
|
|
#define CSA_UAE (03 << CSA_V_UAE)
|
|
#define CSA_IE 0000100 /* interrupt enable */
|
|
#define CSA_DONE 0000200 /* done */
|
|
#define CSA_INIT 0000400 /* init */
|
|
#define CSA_ECLR 0001000 /* clear errors */
|
|
#define CSA_DELH 0002000 /* delimiter hold */
|
|
#define CSA_ONL 0004000 /* online */
|
|
#define CSA_DVON 0010000 /* DAVFU online */
|
|
#define CSA_UNDF 0020000 /* undefined char */
|
|
#define CSA_PZRO 0040000 /* page counter zero */
|
|
#define CSA_ERR 0100000 /* error */
|
|
#define CSA_RW (CSA_DELH | CSA_IE | CSA_UAE | CSA_FNC | CSA_PAR | CSA_GO)
|
|
#define CSA_MBZ (CSA_ECLR | CSA_INIT)
|
|
#define CSA_GETUAE(x) (((x) & CSA_UAE) << (16 - CSA_V_UAE))
|
|
#define CSA_GETFNC(x) (((x) >> CSA_V_FNC) & CSA_M_FNC)
|
|
|
|
/* LPCSRB (765402) */
|
|
|
|
#define CSB_GOE 0000001 /* go error */
|
|
#define CSB_DTE 0000002 /* DEM timing error NI */
|
|
#define CSB_MTE 0000004 /* MSYN error (Ubus timeout) */
|
|
#define CSB_RPE 0000010 /* RAM parity error */
|
|
#define CSB_MPE 0000020 /* MEM parity error NI */
|
|
#define CSB_LPE 0000040 /* LPT parity error NI */
|
|
#define CSB_DVOF 0000100 /* DAVFU not ready */
|
|
#define CSB_OFFL 0000200 /* offline */
|
|
#define CSB_TEST 0003400 /* test mode */
|
|
#define CSB_OVFU 0004000 /* optical VFU */
|
|
#define CSB_PBIT 0010000 /* data parity bit NI */
|
|
#define CSB_NRDY 0020000 /* printer error NI */
|
|
#define CSB_LA180 0040000 /* LA180 printer NI */
|
|
#define CSB_VLD 0100000 /* valid data NI */
|
|
#define CSB_ECLR (CSB_GOE | CSB_DTE | CSB_MTE | CSB_RPE | CSB_MPE | CSB_LPE)
|
|
#define CSB_ERR (CSB_ECLR | CSB_DVOF | CSB_OFFL)
|
|
#define CSB_RW CSB_TEST
|
|
#define CSB_MBZ (CSB_DTE | CSB_RPE | CSB_MPE | CSB_LPE | \
|
|
CSB_PBIT | CSB_NRDY | CSB_LA180 | CSB_VLD)
|
|
|
|
/* LPBA (765404) */
|
|
|
|
/* LPBC (765506) */
|
|
|
|
#define BC_MASK 0007777 /* <15:12> MBZ */
|
|
|
|
/* LPPAGC (765510) */
|
|
|
|
#define PAGC_MASK 0007777 /* <15:12> MBZ */
|
|
|
|
/* LPRDAT (765512) */
|
|
|
|
#define RDAT_MASK 0007777 /* <15:12> MBZ */
|
|
|
|
/* LPCOLC/LPCBUF (765514) */
|
|
|
|
/* LPCSUM/LPPDAT (765516) */
|
|
|
|
static int32 lpcsa = 0; /* control/status A */
|
|
static int32 lpcsb = CSB_DVOF; /* control/status B */
|
|
static int32 lpba = 0; /* bus address */
|
|
static int32 lpbc = 0; /* byte count */
|
|
static int32 lppagc = 0; /* page count */
|
|
static int32 lprdat = 0; /* RAM data */
|
|
static int32 lpcbuf = 0; /* character buffer */
|
|
static int32 lpcolc = 0; /* column count */
|
|
static int32 lppdat = 0; /* printer data */
|
|
static int32 lpcsum = 0; /* checksum */
|
|
static int32 dvptr = 0; /* davfu pointer */
|
|
static int32 dvlnt = 0; /* davfu length */
|
|
static int32 lp20_irq = 0; /* int request */
|
|
static int32 lp20_stopioe = 0; /* stop on error */
|
|
static int32 dvld = 0;
|
|
static int32 dvld_hold = 0;
|
|
static int32 lpi = DEFAULT_LPI; /* Printer's LPI. */
|
|
static int16 txram[TX_SIZE] = { 0 }; /* translation RAM */
|
|
static int16 davfu[DV_SIZE] = { 0 }; /* DAVFU */
|
|
|
|
static t_stat lp20_rd (int32 *data, int32 pa, int32 access);
|
|
static t_stat lp20_wr (int32 data, int32 pa, int32 access);
|
|
static int32 lp20_inta (void);
|
|
static t_stat lp20_svc (UNIT *uptr);
|
|
static t_stat idle_svc (UNIT *uptr);
|
|
static void set_flush_timer (UNIT *uptr);
|
|
static t_stat lp20_reset (DEVICE *dptr);
|
|
static t_stat lp20_init (DEVICE *dptr);
|
|
static t_stat lp20_attach (UNIT *uptr, CONST char *ptr);
|
|
static t_stat lp20_detach (UNIT *uptr);
|
|
static t_stat lp20_set_lpi (UNIT *uptr, int32 val, CONST char *cptr, void *desc);
|
|
static t_stat lp20_show_lpi (FILE *st, UNIT *up, int32 v, CONST void *dp);
|
|
static t_stat lp20_set_vfu_type (UNIT *uptr, int32 val, CONST char *cptr, void *desc);
|
|
static t_stat lp20_show_vfu_type (FILE *st, UNIT *up, int32 v, CONST void *dp);
|
|
static t_stat lp20_show_vfu (FILE *st, UNIT *up, int32 v, CONST void *dp);
|
|
static t_stat lp20_set_tof (UNIT *uptr, int32 val, CONST char *cptr, void *desc);
|
|
static t_stat lp20_clear_vfu (UNIT *uptr, int32 val, CONST char *cptr, void *desc);
|
|
static t_bool lp20_print (int32 c);
|
|
static t_bool lp20_adv (int32 c, t_bool advdvu);
|
|
static t_bool lp20_davfu (int32 c);
|
|
static void update_lpcs (int32 flg);
|
|
static void change_rdy (int32 setrdy, int32 clrrdy);
|
|
static int16 evenbits (int16 value);
|
|
static t_stat lp20_help (FILE *st, DEVICE *dptr,
|
|
UNIT *uptr, int32 flag, const char *cptr);
|
|
static const char *lp20_description (DEVICE *dptr);
|
|
|
|
/* DEC standard VFU tape for 'optical' VFU default.
|
|
* Note that this must be <= DV_SIZE as we copy it into the DAVFU.
|
|
*/
|
|
static const int16 defaultvfu[] = { /* Generated by vfu.pl per DEC HRM */
|
|
/* 66 line page with 6 line margin */
|
|
00377, /* Line 0 8 7 6 5 4 3 2 1 */
|
|
00220, /* Line 1 8 5 */
|
|
00224, /* Line 2 8 5 3 */
|
|
00230, /* Line 3 8 5 4 */
|
|
00224, /* Line 4 8 5 3 */
|
|
00220, /* Line 5 8 5 */
|
|
00234, /* Line 6 8 5 4 3 */
|
|
00220, /* Line 7 8 5 */
|
|
00224, /* Line 8 8 5 3 */
|
|
00230, /* Line 9 8 5 4 */
|
|
00264, /* Line 10 8 6 5 3 */
|
|
00220, /* Line 11 8 5 */
|
|
00234, /* Line 12 8 5 4 3 */
|
|
00220, /* Line 13 8 5 */
|
|
00224, /* Line 14 8 5 3 */
|
|
00230, /* Line 15 8 5 4 */
|
|
00224, /* Line 16 8 5 3 */
|
|
00220, /* Line 17 8 5 */
|
|
00234, /* Line 18 8 5 4 3 */
|
|
00220, /* Line 19 8 5 */
|
|
00364, /* Line 20 8 7 6 5 3 */
|
|
00230, /* Line 21 8 5 4 */
|
|
00224, /* Line 22 8 5 3 */
|
|
00220, /* Line 23 8 5 */
|
|
00234, /* Line 24 8 5 4 3 */
|
|
00220, /* Line 25 8 5 */
|
|
00224, /* Line 26 8 5 3 */
|
|
00230, /* Line 27 8 5 4 */
|
|
00224, /* Line 28 8 5 3 */
|
|
00220, /* Line 29 8 5 */
|
|
00276, /* Line 30 8 6 5 4 3 2 */
|
|
00220, /* Line 31 8 5 */
|
|
00224, /* Line 32 8 5 3 */
|
|
00230, /* Line 33 8 5 4 */
|
|
00224, /* Line 34 8 5 3 */
|
|
00220, /* Line 35 8 5 */
|
|
00234, /* Line 36 8 5 4 3 */
|
|
00220, /* Line 37 8 5 */
|
|
00224, /* Line 38 8 5 3 */
|
|
00230, /* Line 39 8 5 4 */
|
|
00364, /* Line 40 8 7 6 5 3 */
|
|
00220, /* Line 41 8 5 */
|
|
00234, /* Line 42 8 5 4 3 */
|
|
00220, /* Line 43 8 5 */
|
|
00224, /* Line 44 8 5 3 */
|
|
00230, /* Line 45 8 5 4 */
|
|
00224, /* Line 46 8 5 3 */
|
|
00220, /* Line 47 8 5 */
|
|
00234, /* Line 48 8 5 4 3 */
|
|
00220, /* Line 49 8 5 */
|
|
00264, /* Line 50 8 6 5 3 */
|
|
00230, /* Line 51 8 5 4 */
|
|
00224, /* Line 52 8 5 3 */
|
|
00220, /* Line 53 8 5 */
|
|
00234, /* Line 54 8 5 4 3 */
|
|
00220, /* Line 55 8 5 */
|
|
00224, /* Line 56 8 5 3 */
|
|
00230, /* Line 57 8 5 4 */
|
|
00224, /* Line 58 8 5 3 */
|
|
00220, /* Line 59 8 5 */
|
|
00020, /* Line 60 5 */
|
|
00020, /* Line 61 5 */
|
|
00020, /* Line 62 5 */
|
|
00020, /* Line 63 5 */
|
|
00020, /* Line 64 5 */
|
|
04020, /* Line 65 12 5 */
|
|
};
|
|
|
|
/* LP data structures
|
|
|
|
lp20_dev LPT device descriptor
|
|
lp20_unit LPT unit descriptor
|
|
lp20_reg LPT register list
|
|
*/
|
|
|
|
static DIB lp20_dib = {
|
|
IOBA_LP20, IOLN_LP20, &lp20_rd, &lp20_wr,
|
|
1, IVCL (LP20), VEC_LP20, { &lp20_inta }
|
|
};
|
|
|
|
/* Actual device timing varies depending on the printer.
|
|
* Printers used with the LP20 include both drum and band printers.
|
|
* Nominal speeds ranged from 200 LPM to 1250 LPM. Besides speed,
|
|
* the major variants were: Optical vs DAVFU, 64 vs. 96 character
|
|
* band/drum, and scientific vs. EDP fonts. Scientific used slashed
|
|
* Z and 0; EDP did not. All supported 132 colum output at a pitch
|
|
* of 10 CPI. Some had operator switch-selectable vertical pitches
|
|
* for either 6 or 8 LPI. Paper and ribbon are hit by a hammer onto
|
|
* the rotating drum when the desired character is in front of the
|
|
* hammer. Thus, a line that contains all the characters on a drum
|
|
* would take one full revolution to print, plus paper motion time.
|
|
* (Assuming no overstrikes.) At 100 RPM, this translates to 16.7 ms
|
|
* printing + 41 ms motion for the LP05. The math works out to 1,040
|
|
* LPM, but the rated speeds account for slew in the margins and some
|
|
* overstrikes (most commonly underline.) One could construct data
|
|
* patterns that overlapped some paper motion with unused character
|
|
* time on the drum. So the LP10, with 14 ms line advance could
|
|
* print the alphabet using 1/2 a rotation and move the paper in the
|
|
* other half - about 50% faster than rated speed. Bands move the
|
|
* characters horizontally (similar to chain/train printers), but
|
|
* the basic timing constraints are similar.
|
|
*
|
|
* Timing for several printers: a/b is 64/96 character set value.
|
|
* LP05 LP07 LP10 LP14
|
|
* Line advance: 41 ms 12.5 ms 14 ms 20 ms
|
|
* Slew: 20 ips 60 ips 35 ips 22.5 @8LPi/30 @6
|
|
* Drum Rotation: 1000/600 RPM band 1800/1200 1280/857
|
|
* Rated LPM: 230/300 1220/905 1250/925 890/650
|
|
* Weight lb/kg 340/154 800/363 800/363 420/191
|
|
*
|
|
* There is a variant that was designed to drive an LA180 with either
|
|
* a 7 or 8 bit parallel interface. The prints label it 'not a standard
|
|
* product'. It's not implemented in this emulation.
|
|
*/
|
|
|
|
static UNIT lp20_unit[] = {
|
|
{UDATA (&lp20_svc, UNIT_SEQ+UNIT_ATTABLE+UNIT_TEXT, 0), SERIAL_OUT_WAIT},
|
|
{UDATA (&idle_svc, UNIT_DIS, 0), 1000000, LP20_IDLE_TIME}
|
|
};
|
|
|
|
static REG lp20_reg[] = {
|
|
{ ORDATAD (LPCSA, lpcsa, 16, "control/status register A") },
|
|
{ ORDATAD (LPCSB, lpcsb, 16, "control/status register B") },
|
|
{ ORDATAD (LPBA, lpba, 16, "bus address register") },
|
|
{ ORDATAD (LPBC, lpbc, 12, "byte count register") },
|
|
{ ORDATAD (LPPAGC, lppagc, 12, "page count register") },
|
|
{ ORDATAD (LPRDAT, lprdat, 13, "RAM data register") },
|
|
{ ORDATAD (LPCBUF, lpcbuf, 8, "character buffer register") },
|
|
{ ORDATAD (LPCOLC, lpcolc, 8, "column counter register") },
|
|
{ ORDATAD (LPPDAT, lppdat, 8, "printer data register") },
|
|
{ ORDATAD (LPCSUM, lpcsum, 8, "checksum register") },
|
|
{ ORDATAD (DVPTR, dvptr, 7, "vertical forms unit pointer") },
|
|
{ ORDATAD (DVLNT, dvlnt, 7, "vertical forms unit length"), REG_RO + REG_NZ },
|
|
{ ORDATA (DVLD, dvld, 2), REG_RO | REG_HIDDEN },
|
|
{ ORDATA (DVLDH, dvld_hold, 6), REG_RO | REG_HIDDEN },
|
|
{ FLDATAD (INT, int_req, INT_V_LP20, "interrupt request") },
|
|
{ FLDATAD (IRQ, lp20_irq, 0, "clear interrupt request") },
|
|
{ FLDATAD (ERR, lpcsa, CSR_V_ERR, "error flag") },
|
|
{ FLDATAD (DONE, lpcsa, CSR_V_DONE, "done flag") },
|
|
{ FLDATAD (IE, lpcsa, CSR_V_IE, "interrupt enable flag") },
|
|
{ DRDATAD (POS, lp20_unit[0].pos, T_ADDR_W, "position in output file"), PV_LEFT },
|
|
{ DRDATAD (TIME, lp20_unit[0].wait, 24, "response time"), PV_LEFT },
|
|
{ FLDATAD (STOP_IOE, lp20_stopioe, 0, "stop on I/O error") },
|
|
{ BRDATAD (TXRAM, txram, 8, 13, TX_SIZE, "translation RAM") },
|
|
{ BRDATAD (DAVFU, davfu, 8, 12, DV_SIZE, "vertical forms unit array") },
|
|
{ DRDATA (LPI, lpi, 8), REG_RO | REG_HIDDEN },
|
|
{ ORDATA (DEVADDR, lp20_dib.ba, 32), REG_HRO },
|
|
{ ORDATA (DEVVEC, lp20_dib.vec, 16), REG_HRO },
|
|
{ NULL }
|
|
};
|
|
|
|
static MTAB lp20_mod[] = {
|
|
{ MTAB_XTD|MTAB_VDV, 004, "ADDRESS", "ADDRESS",
|
|
&set_addr, &show_addr, NULL },
|
|
{ MTAB_XTD|MTAB_VDV, 0, "VECTOR", "VECTOR",
|
|
&set_vec, &show_vec, NULL },
|
|
{ MTAB_XTD|MTAB_VDV|MTAB_NMO, 0, "VFU", NULL, NULL, &lp20_show_vfu,
|
|
NULL, "Display VFU tape/contents" },
|
|
{ MTAB_XTD|MTAB_VDV|MTAB_VALR|MTAB_NC, 0, "VFUTYPE", "VFUTYPE={DAVFU|OPTICAL{=tapefile}}",
|
|
&lp20_set_vfu_type, &lp20_show_vfu_type, NULL, NULL },
|
|
{ MTAB_XTD|MTAB_VDV|MTAB_VALO, 0, "LPI", "LPI={6-LPI|8-LPI}", &lp20_set_lpi, &lp20_show_lpi,
|
|
NULL, "Printer vertical lines per inch" },
|
|
{ UNIT_DUMMY, 0, NULL, "TOPOFFORM", &lp20_set_tof, NULL,
|
|
NULL, "Advance to top-of-form" },
|
|
{ UNIT_DUMMY, 0, NULL, "VFUCLEAR", &lp20_clear_vfu, NULL,
|
|
NULL, "Clear the VFU & Translation RAM" },
|
|
{ 0 }
|
|
};
|
|
|
|
DEVICE lp20_dev = {
|
|
"LP20", lp20_unit, lp20_reg, lp20_mod,
|
|
2, 10, 31, 1, 8, 8,
|
|
NULL, NULL, &lp20_reset,
|
|
NULL, &lp20_attach, &lp20_detach,
|
|
&lp20_dib, DEV_DISABLE | DEV_UBUS, 0,
|
|
NULL, NULL, NULL, &lp20_help, NULL, NULL, &lp20_description,
|
|
};
|
|
|
|
/* Line printer routines
|
|
|
|
lp20_rd I/O page read
|
|
lp20_wr I/O page write
|
|
lp20_svc process event (printer ready)
|
|
lp20_reset process reset
|
|
lp20_attach process attach
|
|
lp20_detach process detach
|
|
*/
|
|
|
|
static t_stat lp20_rd (int32 *data, int32 pa, int32 access)
|
|
{
|
|
update_lpcs (0); /* update csr's */
|
|
switch ((pa >> 1) & 07) { /* case on PA<3:1> */
|
|
|
|
case 00: /* LPCSA */
|
|
*data = lpcsa = lpcsa & ~CSA_MBZ;
|
|
if (lpcsb & CSB_OVFU) /* Optical: no DAVFU present */
|
|
*data &= ~CSA_DVON;
|
|
break;
|
|
|
|
case 01: /* LPCSB */
|
|
*data = lpcsb = lpcsb & ~CSB_MBZ;
|
|
if (lpcsb & CSB_OVFU)
|
|
*data &= ~CSB_DVOF;
|
|
break;
|
|
|
|
case 02: /* LPBA */
|
|
*data = lpba;
|
|
break;
|
|
|
|
case 03: /* LPBC */
|
|
*data = lpbc = lpbc & BC_MASK;
|
|
break;
|
|
|
|
case 04: /* LPPAGC */
|
|
*data = lppagc = lppagc & PAGC_MASK;
|
|
break;
|
|
|
|
case 05: /* LPRDAT */
|
|
*data = lprdat & RDAT_MASK;
|
|
if (evenbits((int16)*data))
|
|
*data |= TX_PARITY;
|
|
if (((lprdat & TX_PARITY) == 0) && (lpcsa & CSA_PAR)) /* Data invalid & parity checked? */
|
|
*data ^= TX_PARITY; /* Invalid: Provide bad parity */
|
|
break;
|
|
|
|
case 06: /* LPCOLC/LPCBUF */
|
|
*data = (lpcolc << 8) | lpcbuf;
|
|
break;
|
|
|
|
case 07: /* LPCSUM/LPPDAT */
|
|
*data = (lpcsum << 8) | lppdat;
|
|
break;
|
|
} /* end case PA */
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
static t_stat lp20_wr (int32 data, int32 pa, int32 access)
|
|
{
|
|
update_lpcs (0); /* update csr's */
|
|
switch ((pa >> 1) & 07) { /* case on PA<3:1> */
|
|
|
|
case 00: /* LPCSA */
|
|
if (access == WRITEB)
|
|
data = (pa & 1)? (lpcsa & 0377) | (data << 8): (lpcsa & ~0377) | data;
|
|
/* In hardware, a write that sets GO must not change any other
|
|
* bits in CSRA due to timing restrictions. Modifying any bits in
|
|
* CSRA while GO is set "may destroy the contents of the checksum register
|
|
* and produce other undesirable effects."
|
|
*/
|
|
if (data & CSA_ECLR) { /* error clear? */
|
|
lpcsa = (lpcsa | CSA_DONE) & ~CSA_GO; /* set done, clr go */
|
|
lpcsb = lpcsb & ~CSB_ECLR; /* clear err */
|
|
sim_cancel (lp20_unit); /* cancel I/O */
|
|
}
|
|
if (data & CSA_INIT) /* init? */
|
|
lp20_init (&lp20_dev);
|
|
if (data & CSA_GO) { /* go set? */
|
|
if ((lpcsa & CSA_GO) == 0) { /* not set before? */
|
|
if (lpcsb & CSB_ERR)
|
|
lpcsb = lpcsb | CSB_GOE;
|
|
lpcsum = 0; /* clear checksum */
|
|
sim_activate (lp20_unit, lp20_unit->wait);
|
|
}
|
|
}
|
|
else sim_cancel (lp20_unit); /* go clr, stop DMA */
|
|
lpcsa = (lpcsa & ~CSA_RW) | (data & CSA_RW);
|
|
if (dvld && (CSA_GETFNC (lpcsa) != FNC_DVU)) { /* DVU load aborted */
|
|
change_rdy (0, CSA_DVON); /* Mark DVU off-line and empty */
|
|
dvlnt = 0;
|
|
}
|
|
break;
|
|
|
|
case 01: /* LPCSB */
|
|
break; /* ignore writes to TEST */
|
|
|
|
case 02: /* LPBA */
|
|
if (access == WRITEB)
|
|
data = (pa & 1)? (lpba & 0377) | (data << 8): (lpba & ~0377) | data;
|
|
lpba = data;
|
|
break;
|
|
|
|
case 03: /* LPBC */
|
|
if (access == WRITEB)
|
|
data = (pa & 1)? (lpbc & 0377) | (data << 8): (lpbc & ~0377) | data;
|
|
lpbc = data & BC_MASK;
|
|
lpcsa = lpcsa & ~CSA_DONE;
|
|
break;
|
|
|
|
case 04: /* LPPAGC */
|
|
if (access == WRITEB)
|
|
data = (pa & 1)? (lppagc & 0377) | (data << 8): (lppagc & ~0377) | data;
|
|
lppagc = data & PAGC_MASK;
|
|
lpcsa &= ~CSA_PZRO; /* Note that even if at TOF, PZRO does not set */
|
|
break;
|
|
|
|
case 05: /* LPRDAT */
|
|
if (access == WRITEB)
|
|
data = (pa & 1)? (lprdat & 0377) | (data << 8): (lprdat & ~0377) | data;
|
|
lprdat = data & RDAT_MASK;
|
|
txram[lpcbuf & TX_AMASK] = (int16)(lprdat | TX_PARITY);/* load RAM and mark valid */
|
|
break;
|
|
|
|
case 06: /* LPCOLC/LPCBUF */
|
|
if ((access == WRITEB) && (pa & 1)) /* odd byte */
|
|
lpcolc = data & 0377;
|
|
else {
|
|
lpcbuf = data & 0377; /* even byte, word */
|
|
if (access == WRITE)
|
|
lpcolc = (data >> 8) & 0377;
|
|
}
|
|
break;
|
|
|
|
case 07: /* LPCSUM/LPPDAT */
|
|
break; /* read only */
|
|
} /* end case PA */
|
|
|
|
update_lpcs (0);
|
|
return SCPE_OK;
|
|
}
|
|
|
|
/* Line printer service
|
|
|
|
The translation RAM case table is derived from the LP20 spec and
|
|
verified against the LP20 RAM simulator in TOPS10 7.04 LPTSPL.
|
|
The equations are:
|
|
|
|
flags := inter, delim, xlate, paper, delim_hold (from CSRA)
|
|
actions : = print_input, print_xlate, davfu_action, interrupt
|
|
|
|
if (inter) {
|
|
if (!xlate || delim || delim_hold)
|
|
interrupt;
|
|
else if (paper)
|
|
davfu_action;
|
|
else print_xlate;
|
|
}
|
|
else if (paper) {
|
|
if (xlate || delim || delim_hold)
|
|
davfu_action;
|
|
else print_input;
|
|
}
|
|
else {
|
|
if (xlate || delim || delim_hold)
|
|
print_xlate;
|
|
else print_input;
|
|
}
|
|
*/
|
|
|
|
static t_stat lp20_svc (UNIT *uptr)
|
|
{
|
|
int32 fnc, i, tbc, txst;
|
|
uint16 wd10;
|
|
t_bool cont;
|
|
a10 ba;
|
|
|
|
static const uint32 txcase[32] = {
|
|
TX_CHR, TX_RAM, TX_CHR, TX_DVU, TX_RAM, TX_RAM, TX_DVU, TX_DVU,
|
|
TX_RAM, TX_RAM, TX_DVU, TX_DVU, TX_RAM, TX_RAM, TX_DVU, TX_DVU,
|
|
TX_INT, TX_INT, TX_INT, TX_INT, TX_RAM, TX_INT, TX_DVU, TX_INT,
|
|
TX_INT, TX_INT, TX_INT, TX_INT, TX_INT, TX_INT, TX_INT, TX_INT
|
|
};
|
|
|
|
lpcsa = lpcsa & ~CSA_GO;
|
|
ba = CSA_GETUAE (lpcsa) | lpba;
|
|
fnc = CSA_GETFNC (lpcsa);
|
|
tbc = 010000 - lpbc;
|
|
if (((fnc & FNC_INTERNAL) == 0) && ((uptr->flags & UNIT_ATT) == 0)) {
|
|
update_lpcs (CSA_ERR);
|
|
return IORETURN (lp20_stopioe, SCPE_UNATT);
|
|
}
|
|
if ((fnc == FNC_PR) && (lpcsb & CSB_DVOF)) {
|
|
update_lpcs (CSA_ERR);
|
|
return SCPE_OK;
|
|
}
|
|
|
|
for (i = 0, cont = TRUE; (i < tbc) && cont; ba++, i++) {
|
|
if (Map_ReadW (ba, 2, &wd10)) { /* get word, err? */
|
|
lpcsb = lpcsb | CSB_MTE; /* set NXM error */
|
|
update_lpcs (CSA_ERR); /* set done */
|
|
break;
|
|
}
|
|
lpcbuf = (wd10 >> ((ba & 1)? 8: 0)) & 0377; /* get character */
|
|
lpcsum = (lpcsum + lpcbuf) & 0377; /* add into checksum */
|
|
switch (fnc) { /* switch on function */
|
|
|
|
/* Translation RAM load */
|
|
|
|
case FNC_RAM: /* RAM load */
|
|
txram[(i >> 1) & TX_AMASK] = (wd10 & TX_DMASK) | TX_PARITY;
|
|
break;
|
|
|
|
/* DAVFU RAM load. The DAVFU RAM is actually loaded in bytes, delimited by
|
|
a start (354 to 356) and stop (357) byte pair. If the number of bytes
|
|
loaded is odd, or no bytes are loaded, the DAVFU is invalid.
|
|
Thus, with DVU load mode set in CSRA, there are three states:
|
|
0) Inactive 2) Start code seen,even byte 3) Start code seen, odd byte.
|
|
Normally, only a start or a stop code should be seen in (0), but any other
|
|
code that is received is ignored. A stop without a corresponding start is
|
|
legal, and specified to reset the current line pointer to 0 without
|
|
modifying the content of the RAM.
|
|
The DAVFU is physically in the printer, so printers with an optical
|
|
VFU see load data as normal data to be printed. The LP20 logic inhibits
|
|
the translation RAM in this mode, so any translation will not occur.
|
|
This is an unexpected condition; the OS/User should check the optical
|
|
VFU bit before attempting to load a DAVFU.
|
|
*/
|
|
|
|
case FNC_DVU: /* DVU load */
|
|
if (lpcsb & CSB_OVFU) {
|
|
/* OS should not attempt to load VFU if printer has Optical VFU.
|
|
* The DAVFU is in the printer, so it will see the attempted load
|
|
* as print data. The LP20 inhibits translation.
|
|
*/
|
|
cont = lp20_print (lpcbuf);
|
|
break;
|
|
}
|
|
if ((lpcbuf >= 0354) && (lpcbuf <= 0356)) { /* start DVU load? */
|
|
dvlnt = 0; /* reset lnt */
|
|
dvld = 2; /* Load is active, even */
|
|
if (lpcbuf == 0354)
|
|
lpi = 6;
|
|
else if (lpcbuf == 0355)
|
|
lpi = 8;
|
|
}
|
|
else if (lpcbuf == 0357) { /* stop DVU load? */
|
|
dvptr = 0; /* reset ptr */
|
|
dvld = 0;
|
|
if ((dvld & 1) || !VFU_LEN_VALID(dvlnt, lpi)) { /* if odd or invalid length */
|
|
dvlnt = 0;
|
|
change_rdy (0, CSA_DVON);
|
|
}
|
|
else change_rdy(CSA_DVON, 0);
|
|
}
|
|
else if (dvld == 2) { /* even state? */
|
|
dvld_hold = lpcbuf & DV_DMASK;
|
|
dvld = 3;
|
|
}
|
|
else if (dvld == 3) { /* odd state? */
|
|
if (dvlnt < DV_SIZE) {
|
|
davfu[dvlnt++] = (int16)(dvld_hold | ((lpcbuf & DV_DMASK) << 6));
|
|
dvld = 2;
|
|
}
|
|
else {
|
|
change_rdy (0, CSA_DVON);
|
|
dvlnt = dvld = 0;
|
|
}
|
|
}
|
|
break;
|
|
|
|
/* Print characters through the translation RAM */
|
|
|
|
case FNC_PR: /* print */
|
|
lprdat = txram[lpcbuf]; /* get RAM char */
|
|
if (((lprdat & TX_PARITY) == 0) && (lpcsa & CSA_PAR)) { /* Check for valid */
|
|
lpcsb |= CSB_RPE; /* Declare RAM parity error */
|
|
cont = FALSE;
|
|
break;
|
|
}
|
|
txst = (TX_GETFL (lprdat) << 1) | /* get state */
|
|
((lpcsa & CSA_DELH)? 1: 0); /* plus delim hold */
|
|
if (lprdat & TX_DELH)
|
|
lpcsa = lpcsa | CSA_DELH;
|
|
else lpcsa = lpcsa & ~CSA_DELH;
|
|
lpcsa = lpcsa & ~CSA_UNDF; /* assume char ok */
|
|
switch (txcase[txst]) { /* case on state */
|
|
|
|
case TX_CHR: /* take char */
|
|
cont = lp20_print (lpcbuf);
|
|
break;
|
|
|
|
case TX_RAM: /* take translation */
|
|
cont = lp20_print (lprdat);
|
|
break;
|
|
|
|
case TX_DVU: /* DAVFU action */
|
|
if (lprdat & TX_SLEW)
|
|
cont = lp20_adv (lprdat & TX_VMASK, TRUE);
|
|
else cont = lp20_davfu (lprdat & TX_VMASK);
|
|
break;
|
|
|
|
case TX_INT: /* interrupt */
|
|
lpcsa = lpcsa | CSA_UNDF; /* set flag */
|
|
cont = FALSE; /* force stop */
|
|
break;
|
|
} /* end case char state */
|
|
break;
|
|
|
|
case FNC_TST: /* test */
|
|
break;
|
|
} /* end case function */
|
|
} /* end for */
|
|
if (lpcolc == 0)
|
|
set_flush_timer (uptr);
|
|
else
|
|
sim_cancel (uptr+1);
|
|
lpba = ba & 0177777;
|
|
lpcsa = (lpcsa & ~CSA_UAE) | ((ba >> (16 - CSA_V_UAE)) & CSA_UAE);
|
|
lpbc = (lpbc + i) & BC_MASK;
|
|
if (lpbc) /* intr, but not done */
|
|
update_lpcs (CSA_MBZ);
|
|
else update_lpcs (CSA_DONE); /* intr and done */
|
|
if ((fnc == FNC_PR) && ferror (uptr->fileref)) {
|
|
sim_perror ("LP I/O error");
|
|
clearerr (uptr->fileref);
|
|
return SCPE_IOERR;
|
|
}
|
|
return SCPE_OK;
|
|
}
|
|
|
|
/* Print routines
|
|
|
|
lp20_print print a character
|
|
lp20_adv advance n lines
|
|
lp20_davfu advance to channel on VFU
|
|
|
|
Return TRUE to continue printing, FALSE to stop
|
|
*/
|
|
|
|
static t_bool lp20_print (int32 c)
|
|
{
|
|
t_bool r = TRUE;
|
|
int32 i, rpt = 1;
|
|
|
|
lppdat = c & 0177; /* mask char to 7b */
|
|
if (lppdat == 000) /* NUL? no op */
|
|
return TRUE;
|
|
if (lppdat == 012) /* LF? adv carriage */
|
|
return lp20_adv (1, TRUE);
|
|
if (lppdat == 014) /* FF? top of form */
|
|
return lp20_davfu (DV_TOF);
|
|
if (lppdat == 015) /* CR? reset col cntr */
|
|
lpcolc = -1;
|
|
else if (lppdat == 011) { /* TAB? simulate */
|
|
lppdat = ' '; /* with spaces */
|
|
if (lpcolc >= 128) {
|
|
r = lp20_adv (1, TRUE); /* eol? adv carriage */
|
|
rpt = 8; /* adv to col 9 */
|
|
}
|
|
else rpt = 8 - (lpcolc & 07); /* else adv 1 to 8 */
|
|
}
|
|
else {
|
|
if (lppdat < 040) /* cvt non-prnt to spc */
|
|
lppdat = ' ';
|
|
if (lpcolc >= LP_WIDTH) /* line full? */
|
|
r = lp20_adv (1, TRUE); /* adv carriage */
|
|
}
|
|
for (i = 0; i < rpt; i++)
|
|
fputc (lppdat, lp20_unit->fileref);
|
|
lp20_unit->pos = (t_addr)sim_ftell (lp20_unit->fileref);
|
|
lpcolc = lpcolc + rpt;
|
|
return r;
|
|
}
|
|
|
|
static t_bool lp20_adv (int32 cnt, t_bool dvuadv)
|
|
{
|
|
int32 i;
|
|
int stoppc = FALSE;
|
|
|
|
if (cnt == 0)
|
|
return TRUE;
|
|
|
|
if (lpcsb & CSB_DVOF)
|
|
return FALSE;
|
|
|
|
/* This logic has changed because it did not account for the case of more than one TOF
|
|
* occuring in the advance. Consider a tape with odd/even pages, and a slew channel that
|
|
* stops on the even. If we slew from the bottom of the even, we will pass the TOF of the
|
|
* odd page and stop on the odd; seeing a second TOF.
|
|
*/
|
|
|
|
lpcolc = 0; /* reset col cntr */
|
|
for (i = 0; i < cnt; i++) { /* print 'n' newlines; each can complete a page */
|
|
fputc ('\n', lp20_unit->fileref);
|
|
if (dvuadv) { /* update DAVFU ptr */
|
|
dvptr = (dvptr + cnt) % dvlnt;
|
|
if (davfu[dvptr] & (1 << DV_TOF)) { /* at top of form? */
|
|
lppagc = (lppagc - 1) & PAGC_MASK; /* decr page cntr */
|
|
if (lppagc == 0) {
|
|
lpcsa = lpcsa | CSA_PZRO; /* stop if zero */
|
|
stoppc = TRUE;
|
|
}
|
|
} /* At TOF */
|
|
} /* update pointer */
|
|
}
|
|
lp20_unit->pos = (t_addr)sim_ftell (lp20_unit->fileref);
|
|
if (stoppc) /* crossed one or more TOFs? */
|
|
return FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
static t_bool lp20_davfu (int32 cnt)
|
|
{
|
|
int i;
|
|
|
|
if (lpcsb & CSB_DVOF)
|
|
return FALSE;
|
|
if (cnt > DV_MAX) /* inval chan? */
|
|
cnt = 7;
|
|
for (i = 0; i < dvlnt; i++) { /* search DAVFU */
|
|
dvptr = dvptr + 1; /* adv DAVFU ptr */
|
|
if (dvptr >= dvlnt) /* wrap at end */
|
|
dvptr = 0;
|
|
if (davfu[dvptr] & (1 << cnt)) { /* channel stop set? */
|
|
if (cnt) /* ~TOF, adv */
|
|
return lp20_adv (i + 1, FALSE);
|
|
if (lpcolc) /* TOF, need newline? */
|
|
lp20_adv (1, FALSE);
|
|
fputc ('\f', lp20_unit->fileref); /* print form feed */
|
|
lp20_unit->pos = (t_addr)sim_ftell (lp20_unit->fileref);
|
|
lppagc = (lppagc - 1) & PAGC_MASK; /* decr page cntr */
|
|
if (lppagc != 0)
|
|
return TRUE;
|
|
else {
|
|
lpcsa = lpcsa | CSA_PZRO; /* stop if zero */
|
|
return FALSE;
|
|
}
|
|
}
|
|
} /* end for */
|
|
change_rdy (0,CSA_DVON); /* Code to channel with no channel stop */
|
|
return FALSE;
|
|
}
|
|
|
|
/* Update LPCSA, optionally request interrupt */
|
|
|
|
static void update_lpcs (int32 flg)
|
|
{
|
|
if (flg) /* set int req */
|
|
lp20_irq = 1;
|
|
lpcsa = (lpcsa | flg) & ~(CSA_MBZ | CSA_ERR | CSA_ONL);
|
|
lpcsb = (lpcsb | CSB_OFFL) & ~CSB_MBZ;
|
|
if (lp20_unit->flags & UNIT_ATT) {
|
|
lpcsa = lpcsa | CSA_ONL;
|
|
lpcsb = lpcsb & ~CSB_OFFL;
|
|
}
|
|
else lpcsa = lpcsa & ~CSA_DONE;
|
|
if (lpcsb & CSB_ERR)
|
|
lpcsa = lpcsa | CSA_ERR;
|
|
if ((lpcsa & CSA_IE) && lp20_irq)
|
|
SET_INT (LP20);
|
|
else CLR_INT (LP20);
|
|
return;
|
|
}
|
|
|
|
/* Set and clear READY bits in csa.
|
|
* used for bits where a transition should cause an interrupt.
|
|
* also updates corresponding bits in csb.
|
|
*/
|
|
static void change_rdy (int32 setrdy, int32 clrrdy)
|
|
{
|
|
int32 newcsa = (lpcsa | setrdy) & ~clrrdy;
|
|
|
|
if ((newcsa ^ lpcsa) & (CSA_ONL | CSA_DVON) && !sim_is_active (lp20_unit)) {
|
|
lp20_irq |= 1;
|
|
if (newcsa & CSA_IE)
|
|
SET_INT(LP20);
|
|
}
|
|
/* CSA_ERR is handled in update_csa */
|
|
|
|
if (newcsa & CSA_DVON)
|
|
lpcsb &= ~CSB_DVOF;
|
|
else
|
|
lpcsb |= CSB_DVOF;
|
|
if (newcsa & CSA_ONL)
|
|
lpcsb &= ~CSB_OFFL;
|
|
else
|
|
lpcsb |= CSB_OFFL;
|
|
|
|
lpcsa = newcsa;
|
|
}
|
|
|
|
/* Acknowledge interrupt (clear internal request) */
|
|
|
|
static int32 lp20_inta (void)
|
|
{
|
|
lp20_irq = 0; /* clear int req */
|
|
return lp20_dib.vec;
|
|
}
|
|
|
|
|
|
/* Simulator RESET
|
|
* Note that this does not reset the printer's DAVFU or the
|
|
* translation RAM, which survive system bootstraps.
|
|
* (SET VFUCLEAR will do that.)
|
|
*
|
|
*/
|
|
|
|
t_stat lp20_reset (DEVICE *dptr)
|
|
{
|
|
/* On power-up reset, clear DAVFU & RAM. Set DAVFU off-line. */
|
|
if (sim_switches & SWMASK ('P')) {
|
|
memset (davfu, 0, sizeof(davfu));
|
|
memset (txram, 0, sizeof(txram));
|
|
dvlnt = dvptr = dvld= 0;
|
|
lpcsa &= ~CSA_DVON;
|
|
lpcsb |= CSB_DVOF;
|
|
lpi = DEFAULT_LPI;
|
|
}
|
|
|
|
return lp20_init (dptr);
|
|
}
|
|
|
|
/* Local init does NOT initialize the DAVFU/TRANSLATION RAMs.
|
|
* They are in the printer, which reset does not reach.
|
|
*/
|
|
|
|
t_stat lp20_init (DEVICE *dptr)
|
|
{
|
|
lpcsa = (lpcsa & CSA_DVON) | CSA_DONE;
|
|
lpcsb = lpcsb & (CSB_OVFU | CSB_DVOF);
|
|
lpba = lpbc = lppagc = lpcolc = 0; /* clear registers */
|
|
lprdat = lppdat = lpcbuf = lpcsum = 0;
|
|
lp20_irq = 0; /* clear int req */
|
|
sim_cancel (lp20_unit); /* deactivate unit */
|
|
if (sim_is_active (lp20_unit+1)) {
|
|
fflush (lp20_unit->fileref);
|
|
sim_cancel (lp20_unit+1);
|
|
}
|
|
update_lpcs (0); /* update status */
|
|
return SCPE_OK;
|
|
}
|
|
|
|
static t_stat lp20_attach (UNIT *uptr, CONST char *cptr)
|
|
{
|
|
t_stat reason;
|
|
|
|
sim_switches |= SWMASK ('A'); /* position to EOF */
|
|
reason = attach_unit (uptr, cptr); /* attach file */
|
|
if (lpcsa & CSA_DVON) {
|
|
int i;
|
|
for (i = 0; i < dvlnt; i++) { /* Align VFU with new file */
|
|
if (davfu[dvptr] & (1 << DV_TOF))
|
|
break;
|
|
dvptr = (dvptr +1) % dvlnt;
|
|
}
|
|
if (!(davfu[dvptr] & (1 << DV_TOF))) /* No TOP channel -> bad VFU */
|
|
change_rdy (0, CSA_DVON);
|
|
}
|
|
if (lpcsa & CSA_ONL) /* just file chg? */
|
|
return reason;
|
|
if (sim_is_active (lp20_unit)) /* busy? no int */
|
|
update_lpcs (0);
|
|
else update_lpcs (CSA_MBZ); /* interrupt */
|
|
return reason;
|
|
}
|
|
|
|
static t_stat lp20_detach (UNIT *uptr)
|
|
{
|
|
t_stat reason;
|
|
|
|
if (!(uptr->flags & UNIT_ATT)) /* attached? */
|
|
return SCPE_OK;
|
|
if (sim_is_active (lp20_unit+1)) {
|
|
fflush (lp20_unit->fileref);
|
|
sim_cancel (lp20_unit+1);
|
|
}
|
|
reason = detach_unit (uptr);
|
|
sim_cancel (lp20_unit);
|
|
lpcsa = lpcsa & ~CSA_GO;
|
|
update_lpcs (CSA_MBZ);
|
|
return reason;
|
|
}
|
|
|
|
/* Set a timer after which, if idle, the IO buffer will be flushed.
|
|
* This allows simulator users to see their output.
|
|
* The timeout is in seconds; this is problematic with sim_activate timers.
|
|
* So u3 contains the number of seconds desired, and u4 the number to go.
|
|
*/
|
|
static void set_flush_timer (UNIT *uptr) {
|
|
uptr = lp20_unit+1;
|
|
uptr->u4 = uptr->u3;
|
|
uptr->u5 = (int32)time(NULL);
|
|
sim_cancel(uptr);
|
|
sim_activate_after (uptr, uptr->wait);
|
|
}
|
|
|
|
static t_stat idle_svc (UNIT *uptr)
|
|
{
|
|
if (--uptr->u4 > 0) {
|
|
sim_activate_after (uptr, uptr->wait);
|
|
return SCPE_OK;
|
|
}
|
|
uptr = lp20_unit+1;
|
|
fflush (uptr->fileref);
|
|
return SCPE_OK;
|
|
}
|
|
|
|
static t_stat lp20_set_vfu_type (UNIT *uptr, int32 val, CONST char *gptr, void *desc)
|
|
{
|
|
char gbuf[CBUFSIZE], *cptr;
|
|
char *fname, *cp;
|
|
FILE *vfile;
|
|
int sum = 0;
|
|
|
|
if (!gptr || !*gptr)
|
|
return SCPE_ARG;
|
|
|
|
gbuf[sizeof(gbuf)-1] = '\0';
|
|
strncpy (gbuf, gptr, sizeof(gbuf)-1);
|
|
cptr = gbuf;
|
|
|
|
fname = strchr (cptr, '=');
|
|
if (fname)
|
|
*fname++ = '\0';
|
|
|
|
for (cp = cptr; *cp; cp++)
|
|
*cp = (char)toupper (*cp);
|
|
|
|
if (strncmp (cptr, "DAVFU", strlen(cptr)) == 0) { /* set lp20 vfutype=davfu: Switch to DAVFU, empty */
|
|
if (fname && *fname)
|
|
return SCPE_ARG;
|
|
if (!(lpcsb & CSB_OVFU)) /* No change */
|
|
return SCPE_OK;
|
|
if (uptr->flags & UNIT_ATT)
|
|
return SCPE_NOATT;
|
|
|
|
lpcsb &= ~CSB_OVFU;
|
|
change_rdy (0, CSA_DVON);
|
|
dvptr = 0;
|
|
dvlnt = 0;
|
|
return SCPE_OK;
|
|
}
|
|
|
|
if (strncmp (cptr, "OPTICAL", strlen(cptr)) != 0)
|
|
return SCPE_ARG;
|
|
|
|
if (!fname || !*fname) { /* set lp20 vfutype=optical */
|
|
if ((uptr->flags & UNIT_ATT) && !(lpcsb & CSB_OVFU))
|
|
return SCPE_NOATT;
|
|
|
|
lpcsb |= CSB_OVFU;
|
|
change_rdy (CSA_DVON, 0);
|
|
memcpy (davfu, defaultvfu, sizeof defaultvfu);
|
|
dvlnt = sizeof (defaultvfu) / sizeof (defaultvfu[0]);
|
|
dvptr = 0;
|
|
return SCPE_OK;
|
|
}
|
|
|
|
/* set lp20 vfutype=optical=file
|
|
* This is OK when attached, so long as not changing from DAVFU.
|
|
* Read an optical tape file. These are line-oriented ASCII files:
|
|
* # ! ; comment
|
|
* lno: [ch [ch]...] Define line lno (0-length-1 with punches in
|
|
* channel(s) ch (1-12)
|
|
* Not required to be in order, if a lno appears more than once, the entries
|
|
* are ORed. (You can't unpunch a tape.)
|
|
* The highest lno defines the VFU length. Note that there is confusion about
|
|
* whether line numbers start at one or at zero. The HRM uses 0. Some of the
|
|
* utilitites use 1. We stick with 0 here..
|
|
*/
|
|
|
|
if (!(lpcsb & CSB_OVFU) && (uptr->flags & UNIT_ATT)) /* Changing device out from under OS */
|
|
return SCPE_NOATT;
|
|
|
|
vfile = sim_fopen( fname, "r" );
|
|
if (vfile == NULL) {
|
|
return SCPE_OPENERR;
|
|
}
|
|
memset (davfu, 0, sizeof davfu);
|
|
dvptr = dvlnt = 0;
|
|
|
|
while (!feof(vfile)) {
|
|
int32 line, hole;
|
|
int c;
|
|
|
|
/* Discard comments */
|
|
c = fgetc(vfile);
|
|
if (c == EOF)
|
|
break;
|
|
if ((c == '#') || (c == ';') || (c == '!')) {
|
|
while (!feof(vfile) && (c != '\n'))
|
|
c = fgetc(vfile);
|
|
continue;
|
|
}
|
|
ungetc(c, vfile);
|
|
|
|
/* Read a line number */
|
|
c = fscanf (vfile, " %u:", &line);
|
|
if (c == EOF)
|
|
break;
|
|
if ((c < 1) || (line < 0) || (((size_t)line) >= (sizeof (davfu)/sizeof davfu[0])))
|
|
goto fmt_err;
|
|
if (line+1 > dvlnt)
|
|
dvlnt = line+1;
|
|
|
|
/* Read channel numbers for current line */
|
|
while (!feof(vfile)) {
|
|
do {
|
|
c = fgetc (vfile);
|
|
} while (isspace(c) && (c != '\n'));
|
|
if ((c == '\n') || (c == EOF))
|
|
break;
|
|
ungetc(c, vfile);
|
|
c = fscanf (vfile, "%u", &hole);
|
|
if ((c == EOF) || (c < 1) || (c > 12))
|
|
goto fmt_err;
|
|
sum |= (davfu[line] |= 1 << (hole -1));
|
|
} /* End of line */
|
|
} /* EOF */
|
|
|
|
/* Validate VFU content */
|
|
if (!(sum & (1 << DV_TOF))) /* Verify that at least one punch is in the TOF channel. */
|
|
goto fmt_err;
|
|
if (!VFU_LEN_VALID(dvlnt, lpi)) /* Verify VFU has minimum number of lines */
|
|
goto fmt_err;
|
|
|
|
fclose(vfile);
|
|
lpcsb |= CSB_OVFU;
|
|
change_rdy (CSA_DVON, 0);
|
|
return SCPE_OK;
|
|
|
|
fmt_err:
|
|
dvlnt = 0;
|
|
change_rdy (0, CSA_DVON);
|
|
fclose(vfile);
|
|
return SCPE_FMT;
|
|
}
|
|
|
|
static t_stat lp20_show_vfu_type (FILE *st, UNIT *up, int32 v, CONST void *dp)
|
|
{
|
|
if (lpcsb & CSB_OVFU)
|
|
fprintf (st, "optical VFU");
|
|
else
|
|
fprintf (st, "DAVFU");
|
|
|
|
if (lpcsa & CSA_DVON)
|
|
fprintf (st, " loaded: %u lines, %.1f in", dvlnt, (((double)dvlnt)/lpi));
|
|
else
|
|
fprintf (st, " not ready");
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
static t_stat lp20_set_lpi (UNIT *uptr, int32 val, CONST char *cptr, void *desc)
|
|
{
|
|
int32 newlpi;
|
|
|
|
if (uptr->flags & UNIT_ATT)
|
|
return SCPE_NOATT;
|
|
|
|
if (!cptr || !*cptr)
|
|
newlpi = DEFAULT_LPI;
|
|
else if (!strcmp (cptr, "6") || !strcmp (cptr, "6-LPI"))
|
|
newlpi = 6;
|
|
else if (!strcmp (cptr, "8") || !strcmp (cptr, "8-LPI"))
|
|
newlpi = 8;
|
|
else
|
|
return SCPE_ARG;
|
|
|
|
if ((lpcsa & CSA_DVON) && !VFU_LEN_VALID(dvlnt, newlpi))
|
|
return SCPE_ARG;
|
|
|
|
lpi = newlpi;
|
|
return SCPE_OK;
|
|
}
|
|
|
|
static t_stat lp20_show_lpi (FILE *st, UNIT *up, int32 v, CONST void *dp)
|
|
{
|
|
fprintf (st, "%u LPI", lpi);
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
static t_stat lp20_show_vfu (FILE *st, UNIT *up, int32 v, CONST void *dp)
|
|
{
|
|
int l, c, sum;
|
|
|
|
if (lpcsb & CSB_OVFU)
|
|
fprintf (st, "Tape");
|
|
else
|
|
fprintf (st, "DAFVU");
|
|
|
|
if (lpcsb & CSB_DVOF) {
|
|
fprintf (st, " is not loaded\n");
|
|
return SCPE_OK;
|
|
}
|
|
|
|
fprintf (st, " contains:\n"
|
|
" 1 1 1\n"
|
|
"line 2 1 0 9 8 7 6 5 4 3 2 1\n"
|
|
"---- - - - - - - - - - - - -\n");
|
|
sum = 0;
|
|
for (l = 0; l < dvlnt; l++) {
|
|
if ( l && !(l % 5) )
|
|
fputc ('\n', st);
|
|
fprintf (st, "%4u", l);
|
|
for (c = DV_MAX; c >= 0; c--)
|
|
fprintf (st, " %c", (davfu[l] & (1 << c))? ((c >= 9)? 'X': '1'+c) : ' ');
|
|
fputc ('\n', st);
|
|
sum |= davfu[l];
|
|
}
|
|
|
|
if (!(sum & (1 << DV_TOF))) {
|
|
fprintf (st, "? No stop in channel %u (Top-of-Form)\n", DV_TOF+1);
|
|
}
|
|
if (!(sum & (1 << DV_BOF))) {
|
|
fprintf (st, "%% No stop in channel %u (Bottom-of-Form)\n", DV_BOF+1);
|
|
}
|
|
|
|
return SCPE_OK;
|
|
}
|
|
static t_stat lp20_set_tof (UNIT *uptr, int32 val, CONST char *cptr, void *desc)
|
|
{
|
|
int32 s_lpcsa = lpcsa;
|
|
int32 s_lppagc = lppagc;
|
|
|
|
if (cptr && *cptr)
|
|
return SCPE_ARG;
|
|
|
|
if (!(uptr->flags & UNIT_ATT))
|
|
return SCPE_NOATT;
|
|
|
|
if (lpcsb & CSB_DVOF)
|
|
return SCPE_INCOMP;
|
|
|
|
lp20_davfu (DV_TOF);
|
|
lppagc = s_lppagc;
|
|
lpcsa = s_lpcsa;
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
static int16 evenbits (int16 value)
|
|
{
|
|
int16 even = 1;
|
|
while (value) {
|
|
even ^= 1;
|
|
value &= value-1;
|
|
}
|
|
return even;
|
|
}
|
|
|
|
static t_stat lp20_clear_vfu (UNIT *uptr, int32 val, CONST char *cptr, void *desc)
|
|
{
|
|
int i;
|
|
|
|
if (!get_yn ("Clear DAVFU & RAM? [N]", FALSE))
|
|
return SCPE_OK;
|
|
for (i = 0; i < DV_SIZE; i++)
|
|
davfu[i] = 0;
|
|
for (i = 0; i < TX_SIZE; i++)
|
|
txram[i] = 0;
|
|
dvlnt = dvptr = dvld= 0;
|
|
change_rdy (0, CSA_DVON);
|
|
update_lpcs (0);
|
|
return SCPE_OK;
|
|
}
|
|
|
|
static t_stat lp20_help (FILE *st, DEVICE *dptr,
|
|
UNIT *uptr, int32 flag, const char *cptr)
|
|
{
|
|
fprintf (st,
|
|
"The LP20 DMA line printer controller is a UNIBUS device developed by the 36-bit product line.\n"
|
|
"The controller is used in the KS10, in PDP-11 based remote stations and in the KL10 console.\n"
|
|
"Several models of line printer can be (and were) attached to the LP20; with the long lines\n"
|
|
"option, up to 100 feet from the controller. Each LP20 controls one line printer.\n\n"
|
|
"Besides DMA, the LP20 incorporates a translation RAM that can handle case translation and more\n"
|
|
"sophisticated processing, such as representing control characters as ^x and FORTRAN carriage\n"
|
|
"control. In the former case, special characters are flagged for OS attention with an interrupt.\n"
|
|
"But FORTRAN carriage control can be handled entirely by the hardware. The RAM is loaded by\n"
|
|
"the host procesor. The LP20 supports printers with the traditional paper tape VFU, which is \n"
|
|
"used to define vertical motion. It also suports the DAVFU, (direct access) which is the electronic\n"
|
|
"equivalent of the optical tape. The DAVFU is more convenient for operators, as the print spooler\n"
|
|
"changes it automatically to match the forms. The traditional paper tape was read for every line\n"
|
|
"printed. Later printers read the tape at power-up (or change) and cached the data in on-board RAM.\n"
|
|
"This reduced wear on the tape (and the operator), and provides an example of hardware emulating hardware.\n\n"
|
|
"Emulator specifics:\n"
|
|
"The emulator can be configured with either a optical or a DAVFU. When the optical VFU is configured\n"
|
|
"a default tape is provided, which is setup for a 66 line page with a 60 line printable area, and\n"
|
|
"the DEC standard channels punched. (These correspond to FORTRAN carriage control.)\n\n"
|
|
"A custom optical tape can be configured with the SET lp20 VFUTYPE=OPTICAL=filename command. This will\n"
|
|
"load an ASCII file into the VFU, which has the following format:\n"
|
|
" #. ; ! at the beginning of a line are comments, and ignored.\n"
|
|
" line: n,m,o\n"
|
|
" These lines indicate the channels to be punched in each line. That is, that the paper\n"
|
|
" motion will stop when a \"move to channel n\" command is received.\n"
|
|
" The line number is decimal, between 0 and the page length -1.\n"
|
|
" The channel number can be between 1 and 12 - although most printers supported only 1-8\n"
|
|
" Channel 0 is the TOP OF FORM channel, and must be punched in at least one line.\n\n"
|
|
" Channel 12 is the BOTTOM OF FORM channel. Some printers use this to allow completion of the\n"
|
|
" last page when PAPER OUT is detected. Paper out sensors tended to alert early.\n"
|
|
" SET LP20 VFUTYPE=DAVFU (the default) configures the LP20 for a printer with a DAVFU.\n"
|
|
"\n"
|
|
"SET LP20 TOPOFFORM advances the paper to the top of the next page by slewing to VFU channel 0, as\n"
|
|
"does the TOP OF FORM button on a physical printer.\n"
|
|
"The DAVFU and translation RAMs survive RESET unless RESET -P is used. SET LP20 CLEARVFU is\n"
|
|
"provided to clear them independently.\n");
|
|
|
|
return SCPE_OK;
|
|
}
|
|
static const char *lp20_description (DEVICE *dptr)
|
|
{
|
|
return "DMA Line Printer controller";
|
|
}
|