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