First Commit of my working state
[simh.git] / PDP18B / pdp18b_mt.c
1 /* pdp18b_mt.c: 18b PDP magnetic tape simulator
2
3 Copyright (c) 1993-2006, 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 mt (PDP-9) TC59 magtape
27 (PDP-15) TC59D magtape
28
29 16-Feb-06 RMS Added tape capacity checking
30 16-Aug-05 RMS Fixed C++ declaration and cast problems
31 18-Mar-05 RMS Added attached test to detach routine
32 14-Jan-04 RMS Revised IO device call interface
33 25-Apr-03 RMS Revised for extended file support
34 28-Mar-03 RMS Added multiformat support
35 04-Mar-03 RMS Fixed bug in MTTR
36 01-Mar-03 RMS Fixed bug in interrupt handling
37 Revised for magtape library
38 02-Feb-03 RMS Revised IOT decoding
39 30-Oct-02 RMS Revised BOT handling, added error record handling
40 05-Oct-02 RMS Added DIB, device number support
41 Revamped error recovery
42 28-Aug-02 RMS Added end of medium support
43 30-May-02 RMS Widened POS to 32b
44 22-Apr-02 RMS Added maximum record length test
45 06-Jan-02 RMS Revised enabled/disable support
46 29-Nov-01 RMS Added read only unit support
47 25-Nov-01 RMS Revised interrupt structure
48 Changed UST, POS, FLG to arrays
49 26-Apr-01 RMS Added device enable/disable support
50 15-Feb-01 RMS Fixed 3-cycle data break sequence
51 04-Oct-98 RMS V2.4 magtape format
52 22-Jan-97 RMS V2.3 magtape format
53 29-Jun-96 RMS Added unit enable/disable support
54
55 Magnetic tapes are represented as a series of variable records
56 of the form:
57
58 32b byte count
59 byte 0
60 byte 1
61 :
62 byte n-2
63 byte n-1
64 32 byte count
65
66 If the byte count is odd, the record is padded with an extra byte
67 of junk. File marks are represented by a byte count of 0.
68 */
69
70 #include "pdp18b_defs.h"
71 #include "sim_tape.h"
72
73 #define MT_NUMDR 8 /* #drives */
74 #define USTAT u3 /* unit status */
75 #define MT_MAXFR (1 << 16) /* max record length */
76 #define MT_WC 032 /* word count */
77 #define MT_CA 033 /* current addr */
78 #define WC_SIZE (1 << 12) /* max word count */
79 #define WC_MASK (WC_SIZE - 1)
80
81 /* Command/unit - mt_cu */
82
83 #define CU_V_UNIT 15 /* unit */
84 #define CU_M_UNIT 07
85 #define CU_PARITY 0040000 /* parity select */
86 #define CU_DUMP 0020000 /* dump mode */
87 #define CU_ERASE 0010000 /* ext rec gap */
88 #define CU_V_CMD 9 /* command */
89 #define CU_M_CMD 07
90 #define FN_NOP 00
91 #define FN_REWIND 01
92 #define FN_READ 02
93 #define FN_CMPARE 03
94 #define FN_WRITE 04
95 #define FN_WREOF 05
96 #define FN_SPACEF 06
97 #define FN_SPACER 07
98 #define CU_IE 0000400 /* interrupt enable */
99 #define CU_V_TYPE 6 /* drive type */
100 #define CU_M_TYPE 03
101 #define TY_9TK 3
102 #define GET_UNIT(x) (((x) >> CU_V_UNIT) & CU_M_UNIT)
103 #define GET_CMD(x) (((x) >> CU_V_CMD) & CU_M_CMD)
104 #define GET_TYPE(x) (((x) >> CU_V_TYPE) & CU_M_TYPE)
105 #define PACKED(x) (((x) & CU_DUMP) || (GET_TYPE (x) != TY_9TK))
106
107 /* Status - stored in mt_sta or (*) uptr->USTAT */
108
109 #define STA_ERR 0400000 /* error */
110 #define STA_REW 0200000 /* *rewinding */
111 #define STA_BOT 0100000 /* *start of tape */
112 #define STA_ILL 0040000 /* illegal cmd */
113 #define STA_PAR 0020000 /* parity error */
114 #define STA_EOF 0010000 /* *end of file */
115 #define STA_EOT 0004000 /* *end of tape */
116 #define STA_CPE 0002000 /* compare error */
117 #define STA_RLE 0001000 /* rec lnt error */
118 #define STA_DLT 0000400 /* data late */
119 #define STA_BAD 0000200 /* bad tape */
120 #define STA_DON 0000100 /* done */
121
122 #define STA_CLR 0000077 /* always clear */
123 #define STA_DYN (STA_REW | STA_BOT | STA_EOF | STA_EOT)
124 /* kept in USTAT */
125
126 extern int32 M[];
127 extern int32 int_hwre[API_HLVL+1];
128 extern UNIT cpu_unit;
129
130 int32 mt_cu = 0; /* command/unit */
131 int32 mt_sta = 0; /* status register */
132 int32 mt_time = 10; /* record latency */
133 int32 mt_stopioe = 1; /* stop on error */
134 int32 mt_log = 0;
135 uint8 *mtxb = NULL; /* transfer buffer */
136
137 DEVICE mt_dev;
138 int32 mt (int32 dev, int32 pulse, int32 dat);
139 int32 mt_iors (void);
140 t_stat mt_svc (UNIT *uptr);
141 t_stat mt_reset (DEVICE *dptr);
142 t_stat mt_attach (UNIT *uptr, char *cptr);
143 t_stat mt_detach (UNIT *uptr);
144 int32 mt_updcsta (UNIT *uptr, int32 val);
145 t_stat mt_map_err (UNIT *uptr, t_stat st);
146 UNIT *mt_busy (void);
147
148 /* MT data structures
149
150 mt_dev MT device descriptor
151 mt_unit MT unit list
152 mt_reg MT register list
153 mt_mod MT modifier list
154 */
155
156 DIB mt_dib = { DEV_MT, 1, &mt_iors, { &mt } };
157
158 UNIT mt_unit[] = {
159 { UDATA (&mt_svc, UNIT_ATTABLE+UNIT_DISABLE+UNIT_ROABLE, 0) },
160 { UDATA (&mt_svc, UNIT_ATTABLE+UNIT_DISABLE+UNIT_ROABLE, 0) },
161 { UDATA (&mt_svc, UNIT_ATTABLE+UNIT_DISABLE+UNIT_ROABLE, 0) },
162 { UDATA (&mt_svc, UNIT_ATTABLE+UNIT_DISABLE+UNIT_ROABLE, 0) },
163 { UDATA (&mt_svc, UNIT_ATTABLE+UNIT_DISABLE+UNIT_ROABLE, 0) },
164 { UDATA (&mt_svc, UNIT_ATTABLE+UNIT_DISABLE+UNIT_ROABLE, 0) },
165 { UDATA (&mt_svc, UNIT_ATTABLE+UNIT_DISABLE+UNIT_ROABLE, 0) },
166 { UDATA (&mt_svc, UNIT_ATTABLE+UNIT_DISABLE+UNIT_ROABLE, 0) }
167 };
168
169 REG mt_reg[] = {
170 { ORDATA (STA, mt_sta, 18) },
171 { ORDATA (CMD, mt_cu, 18) },
172 { ORDATA (WC, M[MT_WC], 18) },
173 { ORDATA (CA, M[MT_CA], 18) },
174 { FLDATA (INT, int_hwre[API_MTA], INT_V_MTA) },
175 { FLDATA (STOP_IOE, mt_stopioe, 0) },
176 { DRDATA (TIME, mt_time, 24), PV_LEFT },
177 { URDATA (UST, mt_unit[0].USTAT, 8, 16, 0, MT_NUMDR, 0) },
178 { URDATA (POS, mt_unit[0].pos, 10, T_ADDR_W, 0,
179 MT_NUMDR, PV_LEFT | REG_RO) },
180 { FLDATA (LOG, mt_log, 0), REG_HIDDEN },
181 { ORDATA (DEVNO, mt_dib.dev, 6), REG_HRO },
182 { NULL }
183 };
184
185 MTAB mt_mod[] = {
186 { MTUF_WLK, 0, "write enabled", "WRITEENABLED", NULL },
187 { MTUF_WLK, MTUF_WLK, "write locked", "LOCKED", NULL },
188 { MTAB_XTD|MTAB_VUN, 0, "FORMAT", "FORMAT",
189 &sim_tape_set_fmt, &sim_tape_show_fmt, NULL },
190 { MTAB_XTD|MTAB_VUN, 0, "CAPACITY", "CAPACITY",
191 &sim_tape_set_capac, &sim_tape_show_capac, NULL },
192 { MTAB_XTD|MTAB_VDV, 0, "DEVNO", "DEVNO",
193 &set_devno, &show_devno, NULL },
194 { 0 }
195 };
196
197 DEVICE mt_dev = {
198 "MT", mt_unit, mt_reg, mt_mod,
199 MT_NUMDR, 10, 31, 1, 8, 8,
200 NULL, NULL, &mt_reset,
201 NULL, &mt_attach, &mt_detach,
202 &mt_dib, DEV_DISABLE
203 };
204
205 /* IOT routine */
206
207 int32 mt (int32 dev, int32 pulse, int32 dat)
208 {
209 int32 f, sb;
210 UNIT *uptr;
211
212 uptr = mt_dev.units + GET_UNIT (mt_cu); /* get unit */
213 mt_updcsta (uptr, 0); /* update status */
214 sb = pulse & 060; /* subop */
215 if (pulse & 01) {
216 if ((sb == 000) && (uptr->flags & UNIT_ATT) && /* MTTR */
217 !sim_is_active (uptr))
218 dat = IOT_SKP | dat;
219 else if ((sb == 020) && !mt_busy ()) /* MTCR */
220 dat = IOT_SKP | dat;
221 else if ((sb == 040) && (mt_sta & (STA_ERR | STA_DON))) /* MTSF */
222 dat = IOT_SKP | dat;
223 }
224 if ((pulse & 06) && mt_log)
225 printf ("[MT%d: IOT=%o, AC=%o, sta=%o]\n",
226 GET_UNIT (mt_cu), 0707300 + pulse, dat, mt_sta);
227 if (pulse & 02) {
228 if (sb == 000) dat = dat | (mt_cu & 0777700); /* MTRC */
229 else if (sb == 020) { /* MTAF, MTLC */
230 if (!mt_busy ()) mt_cu = mt_sta = 0; /* if not busy, clr */
231 mt_sta = mt_sta & ~(STA_ERR | STA_DON); /* clear flags */
232 }
233 else if (sb == 040) dat = dat | mt_sta; /* MTRS */
234 }
235 if (pulse & 04) {
236 if (sb == 000) { /* MTGO */
237 f = GET_CMD (mt_cu); /* get function */
238 if (mt_busy () ||
239 sim_is_active (uptr) ||
240 (f == FN_NOP) ||
241 (((f == FN_SPACER) || (f == FN_REWIND)) && (uptr->USTAT & STA_BOT)) ||
242 (((f == FN_WRITE) || (f == FN_WREOF)) && sim_tape_wrp (uptr)) ||
243 ((uptr->flags & UNIT_ATT) == 0))
244 mt_sta = mt_sta | STA_ILL | STA_ERR; /* set illegal op */
245 else {
246 if (f == FN_REWIND) uptr->USTAT = STA_REW; /* rewind? */
247 else mt_sta = uptr->USTAT = 0; /* no, clear status */
248 sim_activate (uptr, mt_time); /* start io */
249 }
250 }
251 if (sb == 020) /* MTCM, MTLC */
252 mt_cu = (mt_cu & 0770700) | (dat & 0777700); /* load status */
253 }
254 mt_updcsta (mt_dev.units + GET_UNIT (mt_cu), 0); /* update status */
255 return dat;
256 }
257
258 /* Unit service
259
260 If rewind done, reposition to start of tape, set status
261 else, do operation, set done, interrupt
262 */
263
264 t_stat mt_svc (UNIT *uptr)
265 {
266 int32 c, c1, c2, c3, f, i, p, u;
267 int32 wc, xma;
268 t_mtrlnt tbc, cbc;
269 t_bool passed_eot;
270 t_stat st, r = SCPE_OK;
271
272 u = (int32) (uptr - mt_dev.units); /* get unit number */
273 f = GET_CMD (mt_cu); /* get command */
274 wc = WC_SIZE - (M[MT_WC] & WC_MASK); /* word count is 12b */
275
276 if (uptr->USTAT & STA_REW) { /* rewind? */
277 sim_tape_rewind (uptr); /* rewind tape */
278 if (uptr->flags & UNIT_ATT) uptr->USTAT = STA_BOT;
279 else uptr->USTAT = 0;
280 if (u == GET_UNIT (mt_cu)) mt_updcsta (uptr, STA_DON);
281 if (mt_log) printf ("[MT%d: rewind complete, sta=%o]\n", u, mt_sta);
282 return SCPE_OK;
283 }
284
285 if ((uptr->flags & UNIT_ATT) == 0) { /* if not attached */
286 mt_updcsta (uptr, STA_ILL); /* illegal operation */
287 return IORETURN (mt_stopioe, SCPE_UNATT);
288 }
289
290 passed_eot = sim_tape_eot (uptr); /* passed EOT? */
291 switch (f) { /* case on function */
292
293 case FN_READ: /* read */
294 case FN_CMPARE: /* read/compare */
295 st = sim_tape_rdrecf (uptr, mtxb, &tbc, MT_MAXFR); /* read rec */
296 if (st == MTSE_RECE) mt_sta = mt_sta | STA_PAR | STA_ERR; /* rec in err? */
297 else if (st != MTSE_OK) { /* other error? */
298 mt_sta = mt_sta | STA_RLE | STA_ERR; /* set RLE flag */
299 r = mt_map_err (uptr, st); /* map error */
300 break;
301 }
302 cbc = PACKED (mt_cu)? wc * 3: wc * 2; /* expected bc */
303 if (tbc != cbc) mt_sta = mt_sta | STA_RLE | STA_ERR; /* wrong size? */
304 if (tbc < cbc) { /* record small? */
305 cbc = tbc; /* use smaller */
306 wc = PACKED (mt_cu)? ((tbc + 2) / 3): ((tbc + 1) / 2);
307 }
308 for (i = p = 0; i < wc; i++) { /* copy buffer */
309 M[MT_WC] = (M[MT_WC] + 1) & DMASK; /* inc WC, CA */
310 M[MT_CA] = (M[MT_CA] + 1) & DMASK;
311 xma = M[MT_CA] & AMASK;
312 if (PACKED (mt_cu)) { /* packed? */
313 c1 = mtxb[p++] & 077;
314 c2 = mtxb[p++] & 077;
315 c3 = mtxb[p++] & 077;
316 c = (c1 << 12) | (c2 << 6) | c3;
317 }
318 else {
319 c1 = mtxb[p++];
320 c2 = mtxb[p++];
321 c = (c1 << 8) | c2;
322 }
323 if ((f == FN_READ) && MEM_ADDR_OK (xma)) M[xma] = c;
324 else if ((f == FN_CMPARE) && (c != (M[xma] &
325 (PACKED (mt_cu)? DMASK: 0177777)))) {
326 mt_updcsta (uptr, STA_CPE);
327 break;
328 }
329 } /* end for */
330 break;
331
332 case FN_WRITE: /* write */
333 tbc = PACKED (mt_cu)? wc * 3: wc * 2;
334 xma = M[MT_CA] & AMASK; /* get mem addr */
335 for (i = p = 0; i < wc; i++) { /* copy buf to tape */
336 xma = (xma + 1) & AMASK; /* incr mem addr */
337 if (PACKED (mt_cu)) { /* packed? */
338 mtxb[p++] = (M[xma] >> 12) & 077;
339 mtxb[p++] = (M[xma] >> 6) & 077;
340 mtxb[p++] = M[xma] & 077;
341 }
342 else {
343 mtxb[p++] = (M[xma] >> 8) & 0377;
344 mtxb[p++] = M[xma] & 0377;
345 }
346 } /* end for */
347 if (st = sim_tape_wrrecf (uptr, mtxb, tbc)) /* write rec, err? */
348 r = mt_map_err (uptr, st); /* map error */
349 else {
350 M[MT_CA] = (M[MT_CA] + wc) & DMASK; /* advance mem addr */
351 M[MT_WC] = 0; /* clear word cnt */
352 }
353 mt_cu = mt_cu & ~CU_ERASE; /* clear erase flag */
354 break;
355
356 case FN_WREOF:
357 if (st = sim_tape_wrtmk (uptr)) /* write tmk, err? */
358 r = mt_map_err (uptr, st); /* map error */
359 else uptr->USTAT = STA_EOF;
360 mt_cu = mt_cu & ~CU_ERASE; /* clear erase flag */
361 break;
362
363 case FN_SPACEF: /* space forward */
364 do {
365 M[MT_WC] = (M[MT_WC] + 1) & DMASK; /* inc WC */
366 if (st = sim_tape_sprecf (uptr, &tbc)) { /* space rec fwd, err? */
367 r = mt_map_err (uptr, st); /* map error */
368 break;
369 }
370 } while ((M[MT_WC] != 0) && (passed_eot || !sim_tape_eot (uptr)));
371 break;
372
373 case FN_SPACER: /* space reverse */
374 do {
375 M[MT_WC] = (M[MT_WC] + 1) & DMASK; /* inc WC */
376 if (st = sim_tape_sprecr (uptr, &tbc)) { /* space rec rev, err? */
377 r = mt_map_err (uptr, st); /* map error */
378 break;
379 }
380 } while (M[MT_WC] != 0);
381 break;
382 } /* end case */
383
384 if (!passed_eot && sim_tape_eot (uptr)) /* just passed EOT? */
385 uptr->USTAT = uptr->USTAT | STA_EOT;
386 mt_updcsta (uptr, STA_DON); /* set done */
387 if (mt_log) printf ("MT%d: fnc=%d done, ma=%o, wc=%o, sta=%o]\n",
388 u, f, M[MT_CA], M[MT_WC], mt_sta);
389 return r;
390 }
391
392 /* Update controller status */
393
394 int32 mt_updcsta (UNIT *uptr, int32 news)
395 {
396 mt_sta = (mt_sta & ~(STA_DYN | STA_CLR)) |
397 (uptr->USTAT & STA_DYN) | news;
398 if ((mt_sta & (STA_ERR | STA_DON)) && (mt_cu & CU_IE))
399 SET_INT (MTA);
400 else CLR_INT (MTA); /* int request */
401 return mt_sta;
402 }
403
404 /* Test if controller busy */
405
406 UNIT *mt_busy (void)
407 {
408 int32 u;
409 UNIT *uptr;
410
411 for (u = 0; u < MT_NUMDR; u++) { /* loop thru units */
412 uptr = mt_dev.units + u;
413 if (sim_is_active (uptr) && ((uptr->USTAT & STA_REW) == 0))
414 return uptr;
415 }
416 return NULL;
417 }
418
419 /* Map tape error status */
420
421 t_stat mt_map_err (UNIT *uptr, t_stat st)
422 {
423 switch (st) {
424
425 case MTSE_FMT: /* illegal fmt */
426 case MTSE_UNATT: /* not attached */
427 mt_sta = mt_sta | STA_ILL | STA_ERR;
428 case MTSE_OK: /* no error */
429 return SCPE_IERR;
430
431 case MTSE_TMK: /* end of file */
432 uptr->USTAT = uptr->USTAT | STA_EOF; /* set EOF */
433 mt_sta = mt_sta | STA_ERR;
434 break;
435
436 case MTSE_IOERR: /* IO error */
437 mt_sta = mt_sta | STA_PAR | STA_ERR; /* set par err */
438 if (mt_stopioe) return SCPE_IOERR;
439 break;
440
441 case MTSE_INVRL: /* invalid rec lnt */
442 mt_sta = mt_sta | STA_PAR | STA_ERR; /* set par err */
443 return SCPE_MTRLNT;
444
445 case MTSE_RECE: /* record in error */
446 mt_sta = mt_sta | STA_PAR | STA_ERR; /* set par err */
447 break;
448
449 case MTSE_EOM: /* end of medium */
450 mt_sta = mt_sta | STA_BAD | STA_ERR; /* set end tape */
451 break;
452
453 case MTSE_BOT: /* reverse into BOT */
454 uptr->USTAT = uptr->USTAT | STA_BOT; /* set status */
455 mt_sta = mt_sta | STA_ERR;
456 break;
457
458 case MTSE_WRP: /* write protect */
459 mt_sta = mt_sta | STA_ILL | STA_ERR; /* illegal operation */
460 break;
461 }
462
463 return SCPE_OK;
464 }
465
466 /* Reset routine */
467
468 t_stat mt_reset (DEVICE *dptr)
469 {
470 int32 u;
471 UNIT *uptr;
472
473 mt_cu = mt_sta = 0;
474 for (u = 0; u < MT_NUMDR; u++) { /* loop thru units */
475 uptr = mt_dev.units + u;
476 sim_tape_reset (uptr); /* reset tape */
477 sim_cancel (uptr); /* cancel activity */
478 if (uptr->flags & UNIT_ATT) uptr->USTAT = STA_BOT;
479 else uptr->USTAT = 0;
480 }
481 mt_updcsta (&mt_unit[0], 0); /* update status */
482 if (mtxb == NULL) mtxb = (uint8 *) calloc (MT_MAXFR, sizeof (uint8));
483 if (mtxb == NULL) return SCPE_MEM;
484 return SCPE_OK;
485 }
486
487 /* IORS routine */
488
489 int32 mt_iors (void)
490 {
491 return (mt_sta & (STA_ERR | STA_DON))? IOS_MTA: 0;
492 }
493
494 /* Attach routine */
495
496 t_stat mt_attach (UNIT *uptr, char *cptr)
497 {
498 t_stat r;
499
500 r = sim_tape_attach (uptr, cptr);
501 if (r != SCPE_OK) return r;
502 uptr->USTAT = STA_BOT;
503 mt_updcsta (mt_dev.units + GET_UNIT (mt_cu), 0); /* update status */
504 return r;
505 }
506
507 /* Detach routine */
508
509 t_stat mt_detach (UNIT* uptr)
510 {
511 if (!(uptr->flags & UNIT_ATT)) return SCPE_OK; /* attached? */
512 if (!sim_is_active (uptr)) uptr->USTAT = 0;
513 mt_updcsta (mt_dev.units + GET_UNIT (mt_cu), 0); /* update status */
514 return sim_tape_detach (uptr);
515 }