| 1 | /* i7094_lp.c: IBM 716 line printer simulator\r |
| 2 | \r |
| 3 | Copyright (c) 2003-2007, Robert M. Supnik\r |
| 4 | \r |
| 5 | Permission is hereby granted, free of charge, to any person obtaining a\r |
| 6 | copy of this software and associated documentation files (the "Software"),\r |
| 7 | to deal in the Software without restriction, including without limitation\r |
| 8 | the rights to use, copy, modify, merge, publish, distribute, sublicense,\r |
| 9 | and/or sell copies of the Software, and to permit persons to whom the\r |
| 10 | Software is furnished to do so, subject to the following conditions:\r |
| 11 | \r |
| 12 | The above copyright notice and this permission notice shall be included in\r |
| 13 | all copies or substantial portions of the Software.\r |
| 14 | \r |
| 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r |
| 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r |
| 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\r |
| 18 | ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\r |
| 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\r |
| 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\r |
| 21 | \r |
| 22 | Except as contained in this notice, the name of Robert M Supnik shall not be\r |
| 23 | used in advertising or otherwise to promote the sale, use or other dealings\r |
| 24 | in this Software without prior written authorization from Robert M Supnik.\r |
| 25 | \r |
| 26 | lpt 716 line printer\r |
| 27 | \r |
| 28 | 19-Jan-07 RMS Added UNIT_TEXT flag\r |
| 29 | \r |
| 30 | Internally, the 7094 works only with column binary and is limited to\r |
| 31 | 72 columns of data. Each row of the printed line is represented by\r |
| 32 | 72b of data (two 36b words). A complete print line consists of 12 rows\r |
| 33 | (24 36b words).\r |
| 34 | \r |
| 35 | The printer can also echo part of what it prints, namely, the digit rows\r |
| 36 | plus the 8+3 and 8+4 combinations. This was intended for verification of\r |
| 37 | check printing. Echoed data is interspersed with output data in the\r |
| 38 | following order:\r |
| 39 | \r |
| 40 | output row 9 to row 1\r |
| 41 | echo row "8+4"\r |
| 42 | output row 0\r |
| 43 | echo row "8+3"\r |
| 44 | output row 11\r |
| 45 | echo row 9\r |
| 46 | output row 12\r |
| 47 | echo row 8 to row 1\r |
| 48 | */\r |
| 49 | \r |
| 50 | #include "i7094_defs.h"\r |
| 51 | \r |
| 52 | #define UNIT_V_CONS (UNIT_V_UF + 0) /* print to console */\r |
| 53 | #define UNIT_CONS (1u << UNIT_V_CONS)\r |
| 54 | #define UNIT_V_BZ (UNIT_V_UF + 1)\r |
| 55 | #define UNIT_V_48 (UNIT_V_UF + 2)\r |
| 56 | #define UNIT_BZ (1 << UNIT_V_BZ)\r |
| 57 | #define UNIT_48 (1 << UNIT_V_48)\r |
| 58 | #define GET_PCHAIN(x) (((x) >> UNIT_V_BZ) & (UNIT_BZ|UNIT_48))\r |
| 59 | \r |
| 60 | #define LPT_BINLNT 24 /* bin buffer length */\r |
| 61 | #define LPT_ECHLNT 22 /* echo buffer length */\r |
| 62 | #define LPT_CHRLNT 80 /* char buffer length */\r |
| 63 | \r |
| 64 | #define LPS_INIT 0 /* init state */\r |
| 65 | #define LPS_DATA 1 /* print data state */\r |
| 66 | #define ECS_DATA 2 /* echo data state */\r |
| 67 | #define LPS_END 3 /* end state */\r |
| 68 | \r |
| 69 | #define LPB_9ROW 0 /* bin buf: 9 row */\r |
| 70 | #define LPB_8ROW 2 /* 8 row */\r |
| 71 | #define LPB_4ROW 10 /* 4 row */\r |
| 72 | #define LPB_3ROW 12 /* 3 row */\r |
| 73 | #define LPB_1ROW 16 /* 1 row */\r |
| 74 | #define LPB_12ROW 22 /* 12 row */\r |
| 75 | \r |
| 76 | #define ECB_84ROW 0 /* echo buf: 8-4 row */\r |
| 77 | #define ECB_83ROW 2 /* 8-3 row */\r |
| 78 | #define ECB_9ROW 4 /* 9 row */\r |
| 79 | \r |
| 80 | #define ECHO_F 0100 /* echo map: flag */\r |
| 81 | #define ECHO_MASK 0037 /* mask */\r |
| 82 | \r |
| 83 | #define CMD_BIN 1 /* cmd: bcd/bin */\r |
| 84 | #define CMD_ECHO 2 /* cmd: wrs/rds */\r |
| 85 | \r |
| 86 | uint32 lpt_sta = 0; /* state */\r |
| 87 | uint32 lpt_bptr = 0; /* buffer ptr */\r |
| 88 | uint32 lpt_cmd = 0; /* modes */\r |
| 89 | uint32 lpt_tstart = 27500; /* timing */\r |
| 90 | uint32 lpt_tstop = 27500;\r |
| 91 | uint32 lpt_tleft = 150;\r |
| 92 | uint32 lpt_tright = 4000;\r |
| 93 | t_uint64 lpt_chob = 0;\r |
| 94 | uint32 lpt_chob_v = 0;\r |
| 95 | t_uint64 lpt_bbuf[LPT_BINLNT]; /* binary buffer */\r |
| 96 | t_uint64 lpt_ebuf[LPT_ECHLNT]; /* echo buffer */\r |
| 97 | \r |
| 98 | \r |
| 99 | /* Echo ordering map */\r |
| 100 | \r |
| 101 | static const uint8 echo_map[LPT_BINLNT + LPT_ECHLNT] = {\r |
| 102 | 0, 1, 2, 3, 4, 5, 6, 7, /* write 9 to 1 */\r |
| 103 | 8, 9, 10, 11, 12, 13, 14, 15,\r |
| 104 | 16, 17,\r |
| 105 | 0+ECHO_F, 1+ECHO_F, /* echo 8+4 */\r |
| 106 | 18, 19, /* write 0 */\r |
| 107 | 2+ECHO_F, 3+ECHO_F, /* echo 8+3 */\r |
| 108 | 20, 21, /* write 11 */\r |
| 109 | 4+ECHO_F, 5+ECHO_F, /* echo 9 */\r |
| 110 | 22, 23, /* write 12 */\r |
| 111 | 6+ECHO_F, 7+ECHO_F, 8+ECHO_F, 9+ECHO_F, /* echo 8 to 1 */\r |
| 112 | 10+ECHO_F, 11+ECHO_F, 12+ECHO_F, 13+ECHO_F,\r |
| 113 | 14+ECHO_F, 15+ECHO_F, 16+ECHO_F, 17+ECHO_F,\r |
| 114 | 18+ECHO_F, 19+ECHO_F, 20+ECHO_F, 21+ECHO_F\r |
| 115 | };\r |
| 116 | \r |
| 117 | extern uint32 ind_ioc;\r |
| 118 | extern t_uint64 bit_masks[36];\r |
| 119 | extern uint32 col_masks[12];\r |
| 120 | extern char bcd_to_ascii_a[64];\r |
| 121 | extern char bcd_to_ascii_h[64];\r |
| 122 | extern char bcd_to_pca[64];\r |
| 123 | extern char bcd_to_pch[64];\r |
| 124 | \r |
| 125 | char *pch_table[4] = {\r |
| 126 | bcd_to_ascii_h, bcd_to_ascii_a, bcd_to_pch, bcd_to_pca,\r |
| 127 | };\r |
| 128 | \r |
| 129 | t_stat lpt_reset (DEVICE *dptr);\r |
| 130 | t_stat lpt_svc (UNIT *uptr);\r |
| 131 | t_stat lpt_chsel (uint32 ch, uint32 sel, uint32 unit);\r |
| 132 | t_stat lpt_chwr (uint32 ch, t_uint64 val, uint32 flags);\r |
| 133 | t_stat lpt_end_line (UNIT *uptr);\r |
| 134 | \r |
| 135 | extern char colbin_to_bcd (uint32 colbin);\r |
| 136 | \r |
| 137 | /* LPT data structures\r |
| 138 | \r |
| 139 | lpt_dev LPT device descriptor\r |
| 140 | lpt_unit LPT unit descriptor\r |
| 141 | lpt_reg LPT register list\r |
| 142 | */\r |
| 143 | \r |
| 144 | DIB lpt_dib = { &lpt_chsel, &lpt_chwr };\r |
| 145 | \r |
| 146 | UNIT lpt_unit = {\r |
| 147 | UDATA (&lpt_svc, UNIT_SEQ+UNIT_ATTABLE+UNIT_CONS+UNIT_TEXT, 0)\r |
| 148 | };\r |
| 149 | \r |
| 150 | REG lpt_reg[] = {\r |
| 151 | { ORDATA (STATE, lpt_sta, 2) },\r |
| 152 | { ORDATA (CMD, lpt_cmd, 2) },\r |
| 153 | { ORDATA (CHOB, lpt_chob, 36) },\r |
| 154 | { FLDATA (CHOBV, lpt_chob_v, 0) },\r |
| 155 | { DRDATA (BPTR, lpt_bptr, 6), PV_LEFT },\r |
| 156 | { BRDATA (BUF, lpt_bbuf, 8, 36, LPT_BINLNT) },\r |
| 157 | { BRDATA (EBUF, lpt_ebuf, 8, 36, LPT_ECHLNT) },\r |
| 158 | { DRDATA (POS, lpt_unit.pos, T_ADDR_W), PV_LEFT },\r |
| 159 | { DRDATA (TSTART, lpt_tstart, 24), PV_LEFT + REG_NZ },\r |
| 160 | { DRDATA (TSTOP, lpt_tstop, 24), PV_LEFT + REG_NZ },\r |
| 161 | { DRDATA (TLEFT, lpt_tleft, 24), PV_LEFT + REG_NZ },\r |
| 162 | { DRDATA (TRIGHT, lpt_tright, 24), PV_LEFT + REG_NZ },\r |
| 163 | { NULL }\r |
| 164 | };\r |
| 165 | \r |
| 166 | MTAB lpt_mod[] = {\r |
| 167 | { UNIT_CONS, UNIT_CONS, "default to console", "DEFAULT" },\r |
| 168 | { UNIT_CONS, 0 , "no default device", "NODEFAULT" },\r |
| 169 | { UNIT_48, UNIT_48, "48 character chain", "48" },\r |
| 170 | { UNIT_48, 0, "64 character chain", "64" },\r |
| 171 | { UNIT_BZ, UNIT_BZ, "business set", "BUSINESS" },\r |
| 172 | { UNIT_BZ, 0, "Fortran set", "FORTRAN" },\r |
| 173 | { 0 }\r |
| 174 | };\r |
| 175 | \r |
| 176 | DEVICE lpt_dev = {\r |
| 177 | "LPT", &lpt_unit, lpt_reg, lpt_mod,\r |
| 178 | 1, 10, 31, 1, 8, 7,\r |
| 179 | NULL, NULL, &lpt_reset,\r |
| 180 | NULL, NULL, NULL,\r |
| 181 | &lpt_dib, DEV_DISABLE\r |
| 182 | };\r |
| 183 | \r |
| 184 | /* Channel select routine */\r |
| 185 | \r |
| 186 | t_stat lpt_chsel (uint32 ch, uint32 sel, uint32 unit)\r |
| 187 | {\r |
| 188 | if (sel & CHSL_NDS) return ch6_end_nds (ch); /* nds? nop */\r |
| 189 | \r |
| 190 | switch (sel) { /* case on cmd */\r |
| 191 | \r |
| 192 | case CHSL_RDS: /* read */\r |
| 193 | case CHSL_WRS: /* write */\r |
| 194 | if (!(lpt_unit.flags & (UNIT_ATT|UNIT_CONS))) /* not attached? */\r |
| 195 | return SCPE_UNATT;\r |
| 196 | if (sim_is_active (&lpt_unit)) /* busy? */\r |
| 197 | return ERR_STALL;\r |
| 198 | lpt_cmd = ((unit & 02)? CMD_BIN: 0) | /* save modes */\r |
| 199 | ((sel == CHSL_RDS)? CMD_ECHO: 0);\r |
| 200 | lpt_sta = LPS_INIT; /* initial state */\r |
| 201 | sim_activate (&lpt_unit, lpt_tstart); /* start reader */\r |
| 202 | break;\r |
| 203 | \r |
| 204 | default: /* other */\r |
| 205 | return STOP_ILLIOP;\r |
| 206 | }\r |
| 207 | \r |
| 208 | return SCPE_OK;\r |
| 209 | }\r |
| 210 | \r |
| 211 | /* Channel write routine\r |
| 212 | \r |
| 213 | - Normal mode is processed here\r |
| 214 | - Echo mode is processed in the service routine (like a read) */\r |
| 215 | \r |
| 216 | t_stat lpt_chwr (uint32 ch, t_uint64 val, uint32 eorfl)\r |
| 217 | {\r |
| 218 | uint32 u = (lpt_cmd & CMD_BIN)? U_LPBIN: U_LPBCD; /* reconstruct unit */\r |
| 219 | \r |
| 220 | lpt_chob = val & DMASK; /* store data */\r |
| 221 | lpt_chob_v = 1; /* set valid */\r |
| 222 | if (lpt_sta == ECS_DATA) return SCPE_OK;\r |
| 223 | if (lpt_sta == LPS_DATA) {\r |
| 224 | lpt_bbuf[lpt_bptr++] = lpt_chob; /* store data */\r |
| 225 | if (eorfl || /* end record, or */\r |
| 226 | ((lpt_cmd & CMD_BIN)? /* last word in buffer? */\r |
| 227 | (lpt_bptr > (LPB_1ROW + 1)): /* (binary mode) */\r |
| 228 | (lpt_bptr > (LPB_12ROW + 1)))) { /* (normal mode) */\r |
| 229 | ch6_set_flags (CH_A, u, CHF_EOR); /* set eor */\r |
| 230 | return lpt_end_line (&lpt_unit);\r |
| 231 | }\r |
| 232 | return SCPE_OK;\r |
| 233 | }\r |
| 234 | return SCPE_IERR;\r |
| 235 | }\r |
| 236 | \r |
| 237 | /* Unit timeout */\r |
| 238 | \r |
| 239 | t_stat lpt_svc (UNIT *uptr)\r |
| 240 | {\r |
| 241 | uint32 u = (lpt_cmd & CMD_BIN)? U_LPBIN: U_LPBCD; /* reconstruct unit */\r |
| 242 | uint32 i, map;\r |
| 243 | \r |
| 244 | switch (lpt_sta) { /* case on state */\r |
| 245 | \r |
| 246 | case LPS_INIT: /* initial state */\r |
| 247 | for (i = 0; i < LPT_BINLNT; i++) /* clear data buffer */\r |
| 248 | lpt_bbuf[i] = 0;\r |
| 249 | for (i = 0; i < LPT_ECHLNT; i++) /* clear echo buffer */\r |
| 250 | lpt_ebuf[i] = 0;\r |
| 251 | if (lpt_cmd & CMD_BIN) lpt_bptr = LPB_1ROW; /* set buffer ptr */\r |
| 252 | else lpt_bptr = LPB_9ROW;\r |
| 253 | if (lpt_cmd & CMD_ECHO) lpt_sta = ECS_DATA; /* set data state */\r |
| 254 | else lpt_sta = LPS_DATA;\r |
| 255 | ch6_req_wr (CH_A, u); /* request channel */\r |
| 256 | lpt_chob = 0; /* clr, inval buffer */\r |
| 257 | lpt_chob_v = 0;\r |
| 258 | sim_activate (uptr, lpt_tleft); /* go again */\r |
| 259 | break;\r |
| 260 | \r |
| 261 | case LPS_DATA: /* print data state */\r |
| 262 | if (!ch6_qconn (CH_A, u)) /* disconnect? */\r |
| 263 | return lpt_end_line (uptr); /* line is done */\r |
| 264 | if (lpt_chob_v) lpt_chob_v = 0; /* valid? clear */\r |
| 265 | else ind_ioc = 1; /* no, io check */\r |
| 266 | ch6_req_wr (CH_A, u); /* request chan again */\r |
| 267 | sim_activate (uptr, (lpt_bptr & 1)? lpt_tleft: lpt_tright);\r |
| 268 | break;\r |
| 269 | \r |
| 270 | case ECS_DATA: /* echo data state */\r |
| 271 | map = echo_map[lpt_bptr++]; /* map column */\r |
| 272 | if (map == ECHO_F) { /* first echo? */\r |
| 273 | lpt_ebuf[ECB_84ROW] = lpt_bbuf[LPB_8ROW] & lpt_bbuf[LPB_4ROW];\r |
| 274 | lpt_ebuf[ECB_84ROW + 1] = lpt_bbuf[LPB_8ROW + 1] & lpt_bbuf[LPB_4ROW + 1];\r |
| 275 | lpt_ebuf[ECB_83ROW] = lpt_bbuf[LPB_8ROW] & lpt_bbuf[LPB_3ROW];\r |
| 276 | lpt_ebuf[ECB_83ROW + 1] = lpt_bbuf[LPB_8ROW + 1] & lpt_bbuf[LPB_3ROW + 1];\r |
| 277 | for (i = 0; i < 18; i++) /* copy rows 9.. 1 */\r |
| 278 | lpt_ebuf[ECB_9ROW + i] = lpt_bbuf[LPB_9ROW + i];\r |
| 279 | }\r |
| 280 | if (map & ECHO_F) { /* echo cycle */\r |
| 281 | ch6_req_rd (CH_A, u, lpt_ebuf[map & ECHO_MASK], 0);\r |
| 282 | if (lpt_bptr >= (LPT_BINLNT + LPT_ECHLNT))\r |
| 283 | return lpt_end_line (uptr); /* done? */\r |
| 284 | sim_activate (uptr, lpt_tleft); /* short timer */\r |
| 285 | }\r |
| 286 | else { /* print cycle */\r |
| 287 | if (lpt_chob_v) lpt_chob_v = 0; /* valid? clear */\r |
| 288 | else ind_ioc = 1; /* no, io check */\r |
| 289 | lpt_bbuf[map] = lpt_chob; /* store in buffer */\r |
| 290 | sim_activate (uptr, (lpt_bptr & 1)? lpt_tleft: lpt_tright);\r |
| 291 | }\r |
| 292 | if (!(echo_map[lpt_bptr] & ECHO_F)) /* print word next? */\r |
| 293 | ch6_req_wr (CH_A, u); /* req channel */\r |
| 294 | break;\r |
| 295 | \r |
| 296 | case LPS_END: /* end state */\r |
| 297 | if (ch6_qconn (CH_A, u)) { /* lpt still conn? */\r |
| 298 | lpt_sta = LPS_INIT; /* initial state */\r |
| 299 | sim_activate (uptr, 1); /* next line */\r |
| 300 | }\r |
| 301 | break;\r |
| 302 | }\r |
| 303 | \r |
| 304 | return SCPE_OK;\r |
| 305 | }\r |
| 306 | \r |
| 307 | /* End line routine */\r |
| 308 | \r |
| 309 | t_stat lpt_end_line (UNIT *uptr)\r |
| 310 | {\r |
| 311 | uint32 i, col, row, bufw, colbin;\r |
| 312 | char *pch, bcd, lpt_cbuf[LPT_CHRLNT + 1];\r |
| 313 | t_uint64 dat;\r |
| 314 | \r |
| 315 | pch = pch_table[GET_PCHAIN (lpt_unit.flags)]; /* get print chain */\r |
| 316 | for (col = 0; col < (LPT_CHRLNT + 1); col++) /* clear ascii buf */\r |
| 317 | lpt_cbuf[col] = ' '; \r |
| 318 | for (col = 0; col < 72; col++) { /* proc 72 columns */\r |
| 319 | colbin = 0;\r |
| 320 | dat = bit_masks[35 - (col % 36)]; /* mask for column */\r |
| 321 | for (row = 0; row < 12; row++) { /* proc 12 rows */\r |
| 322 | bufw = (row * 2) + (col / 36); /* index to buffer */\r |
| 323 | if (lpt_bbuf[bufw] & dat) colbin |= col_masks[row];\r |
| 324 | }\r |
| 325 | bcd = colbin_to_bcd (colbin); /* column bin -> BCD */\r |
| 326 | lpt_cbuf[col] = pch[bcd]; /* -> ASCII */\r |
| 327 | }\r |
| 328 | for (i = LPT_CHRLNT; (i > 0) &&\r |
| 329 | (lpt_cbuf[i - 1] == ' '); --i) ; /* trim spaces */\r |
| 330 | lpt_cbuf[i] = 0; /* append nul */\r |
| 331 | if (uptr->flags & UNIT_ATT) { /* file? */\r |
| 332 | fputs (lpt_cbuf, uptr->fileref); /* write line */\r |
| 333 | fputc ('\n', uptr->fileref); /* append nl */\r |
| 334 | uptr->pos = ftell (uptr->fileref); /* update position */\r |
| 335 | if (ferror (uptr->fileref)) { /* error? */\r |
| 336 | perror ("LPT I/O error");\r |
| 337 | clearerr (uptr->fileref);\r |
| 338 | return SCPE_IOERR;\r |
| 339 | }\r |
| 340 | }\r |
| 341 | else if (uptr->flags & UNIT_CONS) { /* print to console? */\r |
| 342 | for (i = 0; lpt_cbuf[i] != 0; i++) sim_putchar (lpt_cbuf[i]);\r |
| 343 | sim_putchar ('\r');\r |
| 344 | sim_putchar ('\n');\r |
| 345 | }\r |
| 346 | else return SCPE_UNATT; /* otherwise error */\r |
| 347 | lpt_sta = LPS_END; /* end line state */\r |
| 348 | sim_cancel (uptr); /* cancel current */\r |
| 349 | sim_activate (uptr, lpt_tstop); /* long timer */\r |
| 350 | return SCPE_OK;\r |
| 351 | }\r |
| 352 | \r |
| 353 | /* Reset routine */\r |
| 354 | \r |
| 355 | t_stat lpt_reset (DEVICE *dptr)\r |
| 356 | {\r |
| 357 | uint32 i;\r |
| 358 | \r |
| 359 | for (i = 0; i < LPT_BINLNT; i++) lpt_bbuf[i] = 0; /* clear bin buf */\r |
| 360 | for (i = 0; i < LPT_ECHLNT; i++) lpt_ebuf[i] = 0; /* clear echo buf */\r |
| 361 | lpt_sta = 0; /* clear state */\r |
| 362 | lpt_cmd = 0; /* clear modes */\r |
| 363 | lpt_bptr = 0; /* clear buf ptr */\r |
| 364 | lpt_chob = 0;\r |
| 365 | lpt_chob_v = 0;\r |
| 366 | sim_cancel (&lpt_unit); /* stop printer */\r |
| 367 | return SCPE_OK;\r |
| 368 | }\r |