From 6243f9314f54e82b4af0656dc4db178198e14a73 Mon Sep 17 00:00:00 2001 From: Timothe Litt Date: Fri, 5 Jul 2013 00:01:24 -0400 Subject: [PATCH] PDP-10 update: LP20 Add Optical VFU support Correct some inconsistencies with the hardware. Add documentation and help. Flush output file after 10 seconds of idleness to allow external watchers/processors to access entire job without forcing a simulator halt or detach/attach. Make CR return to column 0, not column 1. Make DAVFU contents accessible via SHOW. Initialize RAM structures on power-up reset, but not reboot. --- PDP10/pdp10_lp20.c | 839 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 733 insertions(+), 106 deletions(-) diff --git a/PDP10/pdp10_lp20.c b/PDP10/pdp10_lp20.c index 9d6e2a96..9a8366c4 100644 --- a/PDP10/pdp10_lp20.c +++ b/PDP10/pdp10_lp20.c @@ -25,6 +25,8 @@ 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 @@ -39,25 +41,52 @@ 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" +/* 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 07777 +#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 */ @@ -106,13 +135,13 @@ #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 NI */ +#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 NI */ +#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 */ @@ -120,7 +149,7 @@ #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_OVFU |\ +#define CSB_MBZ (CSB_DTE | CSB_RPE | CSB_MPE | CSB_LPE | \ CSB_PBIT | CSB_NRDY | CSB_LA180 | CSB_VLD) /* LPBA (765404) */ @@ -141,39 +170,128 @@ /* LPCSUM/LPPDAT (765516) */ -extern d10 *M; /* main memory */ extern int32 int_req; -int32 lpcsa = 0; /* control/status A */ -int32 lpcsb = 0; /* control/status B */ -int32 lpba = 0; /* bus address */ -int32 lpbc = 0; /* byte count */ -int32 lppagc = 0; /* page count */ -int32 lprdat = 0; /* RAM data */ -int32 lpcbuf = 0; /* character buffer */ -int32 lpcolc = 0; /* column count */ -int32 lppdat = 0; /* printer data */ -int32 lpcsum = 0; /* checksum */ -int32 dvptr = 0; /* davfu pointer */ -int32 dvlnt = 0; /* davfu length */ -int32 lp20_irq = 0; /* int request */ -int32 lp20_stopioe = 0; /* stop on error */ -int16 txram[TX_SIZE] = { 0 }; /* translation RAM */ -int16 davfu[DV_SIZE] = { 0 }; /* DAVFU */ +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 */ DEVICE lp20_dev; -t_stat lp20_rd (int32 *data, int32 pa, int32 access); -t_stat lp20_wr (int32 data, int32 pa, int32 access); -int32 lp20_inta (void); -t_stat lp20_svc (UNIT *uptr); -t_stat lp20_reset (DEVICE *dptr); -t_stat lp20_attach (UNIT *uptr, char *ptr); -t_stat lp20_detach (UNIT *uptr); -t_stat lp20_clear_vfu (UNIT *uptr, int32 val, char *cptr, void *desc); -t_bool lp20_print (int32 c); -t_bool lp20_adv (int32 c, t_bool advdvu); -t_bool lp20_davfu (int32 c); -void update_lpcs (int32 flg); +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, char *ptr); +static t_stat lp20_detach (UNIT *uptr); +static t_stat lp20_set_lpi (UNIT *uptr, int32 val, char *cptr, void *desc); +static t_stat lp20_show_lpi (FILE *st, UNIT *up, int32 v, void *dp); +static t_stat lp20_set_vfu_type (UNIT *uptr, int32 val, char *cptr, void *desc); +static t_stat lp20_show_vfu_type (FILE *st, UNIT *up, int32 v, void *dp); +static t_stat lp20_show_vfu (FILE *st, UNIT *up, int32 v, void *dp); +static t_stat lp20_set_tof (UNIT *uptr, int32 val, char *cptr, void *desc); +static t_stat lp20_clear_vfu (UNIT *uptr, int32 val, 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, struct sim_device *dptr, + struct sim_unit *uptr, int32 flag, char *cptr); +static 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 @@ -182,28 +300,71 @@ void update_lpcs (int32 flg); lp20_reg LPT register list */ -DIB lp20_dib = { +static DIB lp20_dib = { IOBA_LP20, IOLN_LP20, &lp20_rd, &lp20_wr, 1, IVCL (LP20), VEC_LP20, { &lp20_inta } }; -UNIT lp20_unit = { - UDATA (&lp20_svc, UNIT_SEQ+UNIT_ATTABLE+UNIT_TEXT, 0), SERIAL_OUT_WAIT +/* 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_idle; +static UNIT lp20_unit = { + UDATA (&lp20_svc, UNIT_SEQ+UNIT_ATTABLE+UNIT_TEXT, 0), SERIAL_OUT_WAIT, + 0, 0, 0, 0, &lp20_idle, + }; +static UNIT lp20_idle = { + UDATA (&idle_svc, UNIT_DIS, 0), 1000000, LP20_IDLE_TIME, + 0, 0, 0, &lp20_unit, }; -REG lp20_reg[] = { +static REG lp20_reg[] = { { ORDATA (LPCSA, lpcsa, 16) }, { ORDATA (LPCSB, lpcsb, 16) }, { ORDATA (LPBA, lpba, 16) }, { ORDATA (LPBC, lpbc, 12) }, { ORDATA (LPPAGC, lppagc, 12) }, - { ORDATA (LPRDAT, lprdat, 12) }, + { ORDATA (LPRDAT, lprdat, 13) }, { ORDATA (LPCBUF, lpcbuf, 8) }, { ORDATA (LPCOLC, lpcolc, 8) }, { ORDATA (LPPDAT, lppdat, 8) }, { ORDATA (LPCSUM, lpcsum, 8) }, { ORDATA (DVPTR, dvptr, 7) }, { ORDATA (DVLNT, dvlnt, 7), REG_RO + REG_NZ }, + { ORDATA (DVLD, dvld, 2), REG_RO | REG_HIDDEN }, + { ORDATA (DVLDH, dvld_hold, 6), REG_RO | REG_HIDDEN }, { FLDATA (INT, int_req, INT_V_LP20) }, { FLDATA (IRQ, lp20_irq, 0) }, { FLDATA (ERR, lpcsa, CSR_V_ERR) }, @@ -212,19 +373,29 @@ REG lp20_reg[] = { { DRDATA (POS, lp20_unit.pos, T_ADDR_W), PV_LEFT }, { DRDATA (TIME, lp20_unit.wait, 24), PV_LEFT }, { FLDATA (STOP_IOE, lp20_stopioe, 0) }, - { BRDATA (TXRAM, txram, 8, 12, TX_SIZE) }, + { BRDATA (TXRAM, txram, 8, 13, TX_SIZE) }, { BRDATA (DAVFU, davfu, 8, 12, DV_SIZE) }, + { 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 } }; -MTAB lp20_mod[] = { - { UNIT_DUMMY, 0, NULL, "VFUCLEAR", &lp20_clear_vfu }, +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 } }; @@ -233,7 +404,8 @@ DEVICE lp20_dev = { 1, 10, 31, 1, 8, 8, NULL, NULL, &lp20_reset, NULL, &lp20_attach, &lp20_detach, - &lp20_dib, DEV_DISABLE | DEV_UBUS + &lp20_dib, DEV_DISABLE | DEV_UBUS, 0, + NULL, NULL, NULL, &lp20_help, NULL, NULL, &lp20_description, }; /* Line printer routines @@ -246,17 +418,21 @@ DEVICE lp20_dev = { lp20_detach process detach */ -t_stat lp20_rd (int32 *data, int32 pa, int32 access) +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 */ @@ -272,7 +448,11 @@ switch ((pa >> 1) & 07) { /* case on PA<3:1> */ break; case 05: /* LPRDAT */ - *data = lprdat = lprdat & RDAT_MASK; + *data = lprdat & RDAT_MASK; + if (evenbits(*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 */ @@ -287,7 +467,7 @@ switch ((pa >> 1) & 07) { /* case on PA<3:1> */ return SCPE_OK; } -t_stat lp20_wr (int32 data, int32 pa, int32 access) +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> */ @@ -295,13 +475,18 @@ 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_reset (&lp20_dev); + lp20_init (&lp20_dev); if (data & CSA_GO) { /* go set? */ if ((lpcsa & CSA_GO) == 0) { /* not set before? */ if (lpcsb & CSB_ERR) @@ -312,6 +497,10 @@ switch ((pa >> 1) & 07) { /* case on PA<3:1> */ } 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 */ @@ -334,13 +523,14 @@ switch ((pa >> 1) & 07) { /* case on PA<3:1> */ 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] = lprdat; /* load RAM */ + txram[lpcbuf & TX_AMASK] = lprdat | TX_PARITY; /* load RAM and mark valid */ break; case 06: /* LPCOLC/LPCBUF */ @@ -389,10 +579,9 @@ return SCPE_OK; } */ -t_stat lp20_svc (UNIT *uptr) +static t_stat lp20_svc (UNIT *uptr) { -int32 fnc, i, tbc, temp, txst; -int32 dvld = -2; /* must be even */ +int32 fnc, i, tbc, txst; uint16 wd10; t_bool cont; a10 ba; @@ -412,7 +601,7 @@ if (((fnc & FNC_INTERNAL) == 0) && ((lp20_unit.flags & UNIT_ATT) == 0)) { update_lpcs (CSA_ERR); return IORETURN (lp20_stopioe, SCPE_UNATT); } -if ((fnc == FNC_PR) && (dvlnt == 0)) { +if ((fnc == FNC_PR) && (lpcsb & CSB_DVOF)) { update_lpcs (CSA_ERR); return SCPE_OK; } @@ -430,37 +619,76 @@ for (i = 0, cont = TRUE; (i < tbc) && cont; ba++, i++) { /* Translation RAM load */ case FNC_RAM: /* RAM load */ - txram[(i >> 1) & TX_AMASK] = wd10 & TX_DMASK; + 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 ((lpcbuf >= 0354) && (lpcbuf <= 0356)) /* start DVU load? */ - dvld = dvlnt = 0; /* reset lnt */ - else if (lpcbuf == 0357) { /* stop DVU load? */ - dvptr = 0; /* reset ptr */ - if (dvld & 1) /* if odd, invalid */ - dvlnt = 0; + 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; } - else if (dvld == 0) { /* even state? */ - temp = lpcbuf & DV_DMASK; - dvld = 1; + 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 (dvld == 1) { /* odd state? */ - if (dvlnt < DV_SIZE) - davfu[dvlnt++] = temp | ((lpcbuf & DV_DMASK) << 6); + 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++] = dvld_hold | ((lpcbuf & DV_DMASK) << 6); + dvld = 2; + } + else { + change_rdy (0, CSA_DVON); + dvlnt = dvld = 0; + } } break; -/* Print characters */ +/* 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) @@ -494,6 +722,10 @@ for (i = 0, cont = TRUE; (i < tbc) && cont; ba++, i++) { break; } /* end case function */ } /* end for */ +if (lpcolc == 0) + set_flush_timer (&lp20_unit); +else + sim_cancel ((UNIT *)lp20_unit.up7); lpba = ba & 0177777; lpcsa = (lpcsa & ~CSA_UAE) | ((ba >> (16 - CSA_V_UAE)) & CSA_UAE); lpbc = (lpbc + i) & BC_MASK; @@ -517,7 +749,7 @@ return SCPE_OK; Return TRUE to continue printing, FALSE to stop */ -t_bool lp20_print (int32 c) +static t_bool lp20_print (int32 c) { t_bool r = TRUE; int32 i, rpt = 1; @@ -530,7 +762,7 @@ if (lppdat == 012) /* LF? adv carriage */ if (lppdat == 014) /* FF? top of form */ return lp20_davfu (DV_TOF); if (lppdat == 015) /* CR? reset col cntr */ - lpcolc = 0; + lpcolc = -1; else if (lppdat == 011) { /* TAB? simulate */ lppdat = ' '; /* with spaces */ if (lpcolc >= 128) { @@ -552,35 +784,50 @@ lpcolc = lpcolc + rpt; return r; } -t_bool lp20_adv (int32 cnt, t_bool dvuadv) +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++) +for (i = 0; i < cnt; i++) { /* print 'n' newlines; each can complete a page */ fputc ('\n', lp20_unit.fileref); -lp20_unit.pos = ftell (lp20_unit.fileref); /* print 'n' newlines */ -if (dvuadv) /* update DAVFU ptr */ - dvptr = (dvptr + cnt) % dvlnt; -if (davfu[dvptr] & (1 << DV_TOF)) { /* at top of form? */ - if ((lppagc = (lppagc - 1) & PAGC_MASK)) { /* decr page cntr */ - lpcsa = lpcsa & ~CSA_PZRO; /* update status */ - return TRUE; - } - else { - lpcsa = lpcsa | CSA_PZRO; /* stop if zero */ - return FALSE; - } + 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 = ftell (lp20_unit.fileref); +if (stoppc) /* Crossed one or more TOFs? */ + return FALSE; + return TRUE; } -t_bool lp20_davfu (int32 cnt) +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 */ @@ -593,68 +840,120 @@ for (i = 0; i < dvlnt; i++) { /* search DAVFU */ if (lpcolc) /* TOF, need newline? */ lp20_adv (1, FALSE); fputc ('\f', lp20_unit.fileref); /* print form feed */ - lp20_unit.pos = ftell (lp20_unit.fileref); - if ((lppagc = (lppagc - 1) & PAGC_MASK)) { /* decr page cntr */ - lpcsa = lpcsa & ~CSA_PZRO; /* update status */ + lp20_unit.pos = 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 */ -dvlnt = 0; /* DAVFU error */ +change_rdy (0,CSA_DVON); /* Code to channel with no channel stop */ return FALSE; } /* Update LPCSA, optionally request interrupt */ -void update_lpcs (int32 flg) +static void update_lpcs (int32 flg) { if (flg) /* set int req */ lp20_irq = 1; -lpcsa = (lpcsa | flg) & ~(CSA_MBZ | CSA_ERR | CSA_ONL | CSA_DVON); -lpcsb = (lpcsb | CSB_OFFL | CSB_DVOF) & ~CSB_MBZ; +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 (dvlnt) { - lpcsa = lpcsa | CSA_DVON; - lpcsb = lpcsb & ~CSB_DVOF; - } if (lpcsb & CSB_ERR) lpcsa = lpcsa | CSA_ERR; if ((lpcsa & CSA_IE) && lp20_irq) - int_req = int_req | INT_LP20; -else int_req = int_req & ~INT_LP20; + 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) */ -int32 lp20_inta (void) +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) { -lpcsa = CSA_DONE; -lpcsb = 0; + /* 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 */ -dvptr = 0; /* reset davfu ptr */ sim_cancel (&lp20_unit); /* deactivate unit */ +if (sim_is_active ((UNIT *)lp20_unit.up7)) { + fflush (lp20_unit.fileref); + sim_cancel ((UNIT *)lp20_unit.up7); + } update_lpcs (0); /* update status */ return SCPE_OK; } -t_stat lp20_attach (UNIT *uptr, char *cptr) +static t_stat lp20_attach (UNIT *uptr, char *cptr) { t_stat reason; @@ -663,6 +962,16 @@ if (reason == SCPE_OK) { sim_fseek (uptr->fileref, 0, SEEK_END); uptr->pos = (t_addr)sim_ftell (uptr->fileref); } +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 */ @@ -671,12 +980,16 @@ else update_lpcs (CSA_MBZ); /* interrupt */ return reason; } -t_stat lp20_detach (UNIT *uptr) +static t_stat lp20_detach (UNIT *uptr) { t_stat reason; if (!(uptr->flags & UNIT_ATT)) /* attached? */ return SCPE_OK; +if (sim_is_active ((UNIT *)lp20_unit.up7)) { + fflush (lp20_unit.fileref); + sim_cancel ((UNIT *)lp20_unit.up7); +} reason = detach_unit (uptr); sim_cancel (&lp20_unit); lpcsa = lpcsa & ~CSA_GO; @@ -684,15 +997,329 @@ update_lpcs (CSA_MBZ); return reason; } -t_stat lp20_clear_vfu (UNIT *uptr, int32 val, char *cptr, void *desc) +/* 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 = (UNIT *)uptr->up7; +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 = (UNIT *)uptr->up7; +fflush (uptr->fileref); +return SCPE_OK; +} + +static t_stat lp20_set_vfu_type (UNIT *uptr, int32 val, char *cptr, void *desc) +{ +char *fname, *cp; +FILE *vfile; +int sum = 0; + +if (!cptr || !*cptr) + return SCPE_ARG; + +fname = strchr (cptr, '='); +if (fname) + *fname++ = '\0'; + +for (cp = cptr; *cp; cp++) + *cp = 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) || (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, 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, 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, void *dp) +{ +fprintf (st, "%u LPI", lpi); + +return SCPE_OK; +} + +static t_stat lp20_show_vfu (FILE *st, UNIT *up, int32 v, 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, 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, char *cptr, void *desc) { int i; -if (!get_yn ("Clear DAVFU? [N]", FALSE)) +if (!get_yn ("Clear DAVFU & RAM? [N]", FALSE)) return SCPE_OK; for (i = 0; i < DV_SIZE; i++) davfu[i] = 0; -dvlnt = dvptr = 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, struct sim_device *dptr, + struct sim_unit *uptr, int32 flag, 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 char *lp20_description (DEVICE *dptr) +{ + return "DMA Line Printer controller"; +}