1 /* h316_lp.c: Honeywell 316/516 line printer
2
3 Copyright (c) 1999-2008, Robert M. Supnik
4
5 Permission is hereby granted, free of charge, to any person obtaining a
6 copy of this software and associated documentation files (the "Software"),
7 to deal in the Software without restriction, including without limitation
8 the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 and/or sell copies of the Software, and to permit persons to whom the
10 Software is furnished to do so, subject to the following conditions:
11
12 The above copyright notice and this permission notice shall be included in
13 all copies or substantial portions of the Software.
14
15 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22 Except as contained in this notice, the name of Robert M Supnik shall not be
23 used in advertising or otherwise to promote the sale, use or other dealings
24 in this Software without prior written authorization from Robert M Supnik.
25
26 lpt line printer
27
28 09-Jun-07 RMS Fixed lost last print line (Theo Engel)
29 19-Jan-06 RMS Added UNIT_TEXT flag
30 03-Apr-06 RMS Fixed bug in blanks backscanning (Theo Engel)
31 01-Dec-04 RMS Fixed bug in DMA/DMC support
32 24-Oct-03 RMS Added DMA/DMC support
33 25-Apr-03 RMS Revised for extended file support
34 30-May-02 RMS Widened POS to 32b
35
36 The Series 16 line printer is an unbuffered Analex shuttle printer.
37 Because it was unbuffered, the CPU had to scan out an entire line's
38 worth of characters (60 words) for every character on the print drum
39 (64 characters). Because it was a shuttle printer, the entire
40 process must be repeated first for the odd columns and then for the
41 even columns. After scanning the odd columns, the printer carriage
42 shuttled right by one column; after scanning the even columns, the
43 carriage shuttled left. This halved the number of hammers required,
44 reducing cost but increasing mechanical complexity.
45
46 The real printer is very timing dependent. If the CPU misses a
47 scan, then the wrong characters are printed. If the printer protocol
48 is violated, then results are unpredictable. The simulated printer
49 is much more forgiving. Rather than simulating the fixed drum and
50 hammer timing of the real printer, the simulator is driven by the
51 program's OTA instructions. If the program misses a time slot, the
52 simulator will still print the "correct" result. A timing based
53 simulation would be very hard to do in the absense of accurate
54 instruction timing.
55
56 Printer state is maintained in a set of position and state variables:
57
58 lpt_wdpos word count within a line scan (0-59)
59 lpt_drpos drum position (0-63)
60 lpt_crpos carriage position (0-1)
61 lpt_svcst service state (shuttle, paper advance)
62 lpt_svcch channel for paper advance (0 = no adv)
63 lpt_rdy transfer ready flag
64 lpt_prdn printing done flag
65 lpt_dma use DMA/DMC
66 lpt_eor DMA/DMC end of range
67 */
68
69 #include "h316_defs.h"
70
71 #define LPT_WIDTH 120 /* width */
72 #define LPT_SCAN (LPT_WIDTH / 2) /* words/scan */
73 #define LPT_DRUM 64 /* drum rows */
74 #define LPT_SVCSH 01 /* shuttle */
75 #define LPT_SVCPA 02 /* paper advance */
76
77 extern int32 dev_int, dev_enb;
78 extern int32 stop_inst;
79 extern uint32 chan_req;
80
81 int32 lpt_wdpos = 0; /* word position */
82 int32 lpt_drpos = 0; /* drum position */
83 int32 lpt_crpos = 0; /* carriage position */
84 int32 lpt_svcst = 0; /* service state */
85 int32 lpt_svcch = 0; /* service channel */
86 int32 lpt_rdy = 0; /* transfer flag */
87 int32 lpt_prdn = 1; /* printing done */
88 int32 lpt_dma = 0; /* use DMA/DMC */
89 int32 lpt_eor = 0; /* DMA/DMC end range */
90 char lpt_buf[LPT_WIDTH + 1] = { 0 }; /* line buffer */
91 int32 lpt_xtime = 5; /* transfer time */
92 int32 lpt_etime = 50; /* end of scan time */
93 int32 lpt_ptime = 5000; /* paper adv time */
94 int32 lpt_stopioe = 0; /* stop on error */
95
96 int32 lptio (int32 inst, int32 fnc, int32 dat, int32 dev);
97 t_stat lpt_svc (UNIT *uptr);
98 t_stat lpt_reset (DEVICE *dptr);
99
100 /* LPT data structures
101
102 lpt_dev LPT device descriptor
103 lpt_unit LPT unit descriptor
104 lpt_mod LPT modifiers
105 lpt_reg LPT register list
106 */
107
108 DIB lpt_dib = { LPT, IOBUS, 1, &lptio };
109
110 UNIT lpt_unit = { UDATA (&lpt_svc, UNIT_SEQ+UNIT_ATTABLE+UNIT_TEXT, 0) };
111
112 REG lpt_reg[] = {
113 { DRDATA (WDPOS, lpt_wdpos, 6) },
114 { DRDATA (DRPOS, lpt_drpos, 6) },
115 { FLDATA (CRPOS, lpt_crpos, 0) },
116 { FLDATA (RDY, lpt_rdy, 0) },
117 { FLDATA (EOR, lpt_eor, 0) },
118 { FLDATA (DMA, lpt_dma, 0) },
119 { FLDATA (PRDN, lpt_prdn, 0) },
120 { FLDATA (INTREQ, dev_int, INT_V_LPT) },
121 { FLDATA (ENABLE, dev_enb, INT_V_LPT) },
122 { ORDATA (SVCST, lpt_svcst, 2) },
123 { ORDATA (SVCCH, lpt_svcch, 2) },
124 { BRDATA (BUF, lpt_buf, 8, 8, 120) },
125 { DRDATA (POS, lpt_unit.pos, T_ADDR_W), PV_LEFT },
126 { DRDATA (XTIME, lpt_xtime, 24), PV_LEFT },
127 { DRDATA (ETIME, lpt_etime, 24), PV_LEFT },
128 { DRDATA (PTIME, lpt_ptime, 24), PV_LEFT },
129 { FLDATA (STOP_IOE, lpt_stopioe, 0) },
130 { NULL }
131 };
132
133 DEVICE lpt_dev = {
134 "LPT", &lpt_unit, lpt_reg, NULL,
135 1, 10, 31, 1, 8, 8,
136 NULL, NULL, &lpt_reset,
137 NULL, NULL, NULL,
138 &lpt_dib, DEV_DISABLE
139 };
140
141 /* IO routine */
142
lptio(int32 inst,int32 fnc,int32 dat,int32 dev)143 int32 lptio (int32 inst, int32 fnc, int32 dat, int32 dev)
144 {
145 int32 ch = lpt_dib.chan - 1; /* DMA/DMC chan */
146 int32 chr;
147
148 switch (inst) { /* case on opcode */
149
150 case ioOCP: /* OCP */
151 switch (fnc) { /* case on fnc */
152
153 case 000: case 002: case 004: /* paper adv */
154 lpt_svcst = lpt_svcst | LPT_SVCPA; /* set state */
155 lpt_svcch = fnc >> 1; /* save channel */
156 sim_activate (&lpt_unit, lpt_ptime);
157 CLR_INT (INT_LPT); /* clear int */
158 break;
159
160 case 003: /* init scan DMA/DMC */
161 lpt_prdn = 0; /* clear pr done */
162 lpt_wdpos = 0; /* init scan pos */
163 lpt_eor = 0;
164 if (ch >= 0) /* try for DMA/DMC */
165 lpt_dma = 1;
166 else lpt_dma = 0;
167 if (!sim_is_active (&lpt_unit)) {
168 lpt_rdy = 1;
169 if (lpt_dma)
170 SET_CH_REQ (ch);
171 }
172 CLR_INT (INT_LPT); /* clear int */
173 break;
174
175 case 007: /* init scan IO bus */
176 lpt_prdn = 0; /* clear pr done */
177 lpt_wdpos = 0; /* init scan pos */
178 lpt_eor = 0;
179 lpt_dma = 0; /* IO bus */
180 if (!sim_is_active (&lpt_unit))
181 lpt_rdy = 1;
182 CLR_INT (INT_LPT); /* clear int */
183 break;
184
185 default:
186 return IOBADFNC (dat);
187 }
188 break;
189
190 case ioSKS: /* SKS */
191 switch (fnc) { /* case on fnc */
192
193 case 000: /* if xfer rdy */
194 if (lpt_rdy)
195 return IOSKIP (dat);
196 break;
197
198 case 002: /* if !alarm */
199 if (lpt_unit.flags & UNIT_ATT)
200 return IOSKIP (dat);
201 break;
202
203 case 003: /* if odd col */
204 if (lpt_crpos)
205 return IOSKIP (dat);
206 break;
207
208 case 004: /* if !interrupt */
209 if (!TST_INTREQ (INT_LPT))
210 return IOSKIP (dat);
211 break;
212
213 case 011: /* if line printed */
214 if (lpt_prdn)
215 return IOSKIP (dat);
216 break;
217
218 case 012: /* if !shuttling */
219 if (!(lpt_svcst & LPT_SVCSH))
220 return IOSKIP (dat);
221 break;
222
223 case 013:
224 if (lpt_prdn && !(lpt_svcst & LPT_SVCSH))
225 return IOSKIP (dat);
226 break;
227
228 case 014: /* if !advancing */
229 if (!(lpt_svcst & LPT_SVCPA))
230 return IOSKIP (dat);
231 break;
232
233 case 015:
234 if (lpt_prdn && !(lpt_svcst & LPT_SVCPA))
235 return IOSKIP (dat);
236 break;
237
238 case 016:
239 if (!(lpt_svcst & (LPT_SVCSH | LPT_SVCPA)))
240 return IOSKIP (dat);
241 break;
242
243 case 017:
244 if (lpt_prdn && !(lpt_svcst & (LPT_SVCSH | LPT_SVCPA)))
245 return IOSKIP (dat);
246 break;
247
248 default:
249 return IOBADFNC (dat);
250 }
251 break;
252
253 case ioOTA: /* OTA */
254 if (fnc) /* only fnc 0 */
255 return IOBADFNC (dat);
256 if (lpt_rdy) { /* xfer ready? */
257 lpt_rdy = 0; /* clear xfer */
258 chr = (dat >> (lpt_crpos? 0: 8)) & 077; /* get 6b char */
259 if (chr == lpt_drpos) { /* match drum pos? */
260 if (chr < 040)
261 chr = chr | 0100;
262 lpt_buf[2 * lpt_wdpos + lpt_crpos] = chr;
263 }
264 lpt_wdpos++; /* adv scan pos */
265 if (lpt_wdpos >= LPT_SCAN) { /* end of scan? */
266 lpt_wdpos = 0; /* reset scan pos */
267 lpt_drpos++; /* adv drum pos */
268 if (lpt_drpos >= LPT_DRUM) { /* end of drum? */
269 lpt_drpos = 0; /* reset drum pos */
270 lpt_crpos = lpt_crpos ^ 1; /* shuttle */
271 lpt_svcst = lpt_svcst | LPT_SVCSH;
272 sim_activate (&lpt_unit, lpt_ptime);
273 } /* end if shuttle */
274 else sim_activate (&lpt_unit, lpt_etime);
275 } /* end if endscan */
276 else sim_activate (&lpt_unit, lpt_xtime);
277 return IOSKIP (dat); /* skip return */
278 }
279 break;
280
281 case ioEND: /* end DMA/DMC */
282 lpt_eor = 1; /* set end range */
283 break;
284 } /* end case op */
285
286 return dat;
287 }
288
289 /* Unit service */
290
lpt_svc(UNIT * uptr)291 t_stat lpt_svc (UNIT *uptr)
292 {
293 int32 i;
294 int32 ch = lpt_dib.chan - 1; /* DMA/DMC chan */
295 static const char *lpt_cc[] = {
296 "\r",
297 "\n",
298 "\n\f",
299 "\n"
300 };
301
302 if ((lpt_unit.flags & UNIT_ATT) == 0) /* attached? */
303 return IORETURN (lpt_stopioe, SCPE_UNATT);
304 if (lpt_dma) { /* DMA/DMC? */
305 if (lpt_eor) /* end range? intr */
306 SET_INT (INT_LPT);
307 else {
308 lpt_rdy = 1; /* set ready */
309 SET_CH_REQ (ch); /* get more data */
310 }
311 }
312 else lpt_rdy = 1; /* IO, continue scan */
313 if (lpt_svcst & LPT_SVCSH) { /* shuttling? */
314 SET_INT (INT_LPT); /* interrupt */
315 if (lpt_crpos == 0) { /* done shuttling? */
316 for (i = LPT_WIDTH - 1; i >= 0; i--) { /* backscan for blanks */
317 if (lpt_buf[i] != ' ')
318 break;
319 }
320 lpt_buf[i + 1] = 0;
321 fputs (lpt_buf, uptr->fileref); /* output buf */
322 uptr->pos = ftell (uptr->fileref); /* update pos */
323 for (i = 0; i < LPT_WIDTH; i++) /* clear buf */
324 lpt_buf[i] = ' ';
325 lpt_prdn = 1; /* print done */
326 }
327 }
328 if (lpt_svcst & LPT_SVCPA) { /* paper advance */
329 SET_INT (INT_LPT); /* interrupt */
330 fputs (lpt_cc[lpt_svcch & 03], uptr->fileref); /* output eol */
331 uptr->pos = ftell (uptr->fileref); /* update pos */
332 }
333 lpt_svcst = 0;
334 return SCPE_OK;
335 }
336
337 /* Reset routine */
338
lpt_reset(DEVICE * dptr)339 t_stat lpt_reset (DEVICE *dptr)
340 {
341 int32 i;
342
343 lpt_wdpos = lpt_drpos = lpt_crpos = 0; /* clear positions */
344 lpt_svcst = lpt_svcch = 0; /* idle state */
345 lpt_rdy = 0; /* not rdy to xfer */
346 lpt_prdn = 1; /* printing done */
347 lpt_eor = 0;
348 lpt_dma = 0;
349 for (i = 0; i < LPT_WIDTH; i++) /* clear buffer */
350 lpt_buf[i] = ' ';
351 lpt_buf[LPT_WIDTH] = 0;
352 CLR_INT (INT_LPT); /* clear int, enb */
353 CLR_ENB (INT_LPT);
354 sim_cancel (&lpt_unit); /* deactivate unit */
355 return SCPE_OK;
356 }
357