1 /* pdp11_ta.c: PDP-11 cassette tape simulator
3 Copyright (c) 2007, Robert M Supnik
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:
12 The above copyright notice and this permission notice shall be included in
13 all copies or substantial portions of the Software.
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 ATAION OF CONTRATA, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 CONNETAION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
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.
26 ta TA11/TU60 cassette tape
28 06-Aug-07 RMS Foward op at BOT skips initial file gap
30 Magnetic tapes are represented as a series of variable records
41 If the byte count is odd, the record is padded with an extra byte
42 of junk. File marks are represented by a byte count of 0.
44 Cassette format differs in one very significant way: it has file gaps
45 rather than file marks. If the controller spaces or reads into a file
46 gap and then reverses direction, the file gap is not seen again. This
47 is in contrast to magnetic tapes, where the file mark is a character
48 sequence and is seen again if direction is reversed. In addition,
49 cassettes have an initial file gap which is automatically skipped on
50 forward operations from beginning of tape.
53 #include "pdp11_defs.h"
56 #define TA_NUMDR 2 /* #drives */
57 #define FNC u3 /* unit function */
58 #define UST u4 /* unit status */
59 #define TA_SIZE 93000 /* chars/tape */
60 #define TA_MAXFR (TA_SIZE) /* max record lnt */
62 /* Control/status - TACS */
64 #define TACS_ERR (1 << CSR_V_ERR) /* error */
65 #define TACS_CRC 0040000 /* CRC */
66 #define TACS_BEOT 0020000 /* BOT/EOT */
67 #define TACS_WLK 0010000 /* write lock */
68 #define TACS_EOF 0004000 /* end file */
69 #define TACS_TIM 0002000 /* timing */
70 #define TACS_EMP 0001000 /* empty */
71 #define TACS_V_UNIT 8 /* unit */
72 #define TACS_M_UNIT (TA_NUMDR - 1)
73 #define TACS_UNIT (TACS_M_UNIT << TACS_V_UNIT)
74 #define TACS_TR (1 << CSR_V_DONE) /* transfer req */
75 #define TACS_IE (1 << CSR_V_IE) /* interrupt enable */
76 #define TACS_RDY 0000040 /* ready */
77 #define TACS_ILBS 0000020 /* start CRC */
78 #define TACS_V_FNC 1 /* function */
90 #define TACS_FNC (TACS_M_FNC << TACS_V_FNC)
91 #define TACS_GO (1 << CSR_V_GO) /* go */
92 #define TACS_W (TACS_UNIT|TACS_IE|TACS_ILBS|TACS_FNC)
93 #define TACS_XFRERR (TACS_ERR|TACS_CRC|TACS_WLK|TACS_EOF|TACS_TIM)
94 #define GET_UNIT(x) (((x) >> TACS_V_UNIT) & TACS_M_UNIT)
95 #define GET_FNC(x) (((x) >> TACS_V_FNC) & TACS_M_FNC)
97 /* Function code flags */
99 #define OP_WRI 01 /* op is a write */
100 #define OP_REV 02 /* op is rev motion */
101 #define OP_FWD 04 /* op is fwd motion */
103 /* Unit status flags */
105 #define UST_REV (OP_REV) /* last op was rev */
106 #define UST_GAP 01 /* last op hit gap */
108 extern int32 int_req
[IPL_HLVL
];
109 extern FILE *sim_deb
;
111 uint32 ta_cs
= 0; /* control/status */
112 uint32 ta_idb
= 0; /* input data buf */
113 uint32 ta_odb
= 0; /* output data buf */
114 uint32 ta_write
= 0; /* TU60 write flag */
115 uint32 ta_bptr
= 0; /* buf ptr */
116 uint32 ta_blnt
= 0; /* buf length */
117 int32 ta_stime
= 1000; /* start time */
118 int32 ta_ctime
= 100; /* char latency */
119 uint32 ta_stopioe
= 1; /* stop on error */
120 uint8
*ta_xb
= NULL
; /* transfer buffer */
121 static uint8 ta_fnc_tab
[TACS_M_FNC
+ 1] = {
122 OP_WRI
|OP_FWD
, OP_WRI
|OP_FWD
, OP_FWD
, OP_REV
,
123 OP_REV
, OP_FWD
, OP_FWD
, 0
127 t_stat
ta_rd (int32
*data
, int32 PA
, int32 access
);
128 t_stat
ta_wr (int32 data
, int32 PA
, int32 access
);
129 t_stat
ta_svc (UNIT
*uptr
);
130 t_stat
ta_reset (DEVICE
*dptr
);
131 t_stat
ta_attach (UNIT
*uptr
, char *cptr
);
132 t_stat
ta_detach (UNIT
*uptr
);
134 t_stat
ta_map_err (UNIT
*uptr
, t_stat st
);
135 UNIT
*ta_busy (void);
136 void ta_set_tr (void);
137 uint32
ta_updsta (UNIT
*uptr
);
138 uint32
ta_crc (uint8
*buf
, uint32 cnt
);
140 /* TA data structures
142 ta_dev TA device descriptor
144 ta_reg TA register list
145 ta_mod TA modifier list
149 IOBA_TA
, IOLN_TA
, &ta_rd
, &ta_wr
,
150 1, IVCL (TA
), VEC_TA
, { NULL
}
154 { UDATA (&ta_svc
, UNIT_ATTABLE
+UNIT_ROABLE
, TA_SIZE
) },
155 { UDATA (&ta_svc
, UNIT_ATTABLE
+UNIT_ROABLE
, TA_SIZE
) },
159 { ORDATA (TACS
, ta_cs
, 16) },
160 { ORDATA (TAIDB
, ta_idb
, 8) },
161 { ORDATA (TAODB
, ta_odb
, 8) },
162 { FLDATA (WRITE
, ta_write
, 0) },
163 { FLDATA (INT
, IREQ (TA
), INT_V_TA
) },
164 { FLDATA (ERR
, ta_cs
, CSR_V_ERR
) },
165 { FLDATA (TR
, ta_cs
, CSR_V_DONE
) },
166 { FLDATA (IE
, ta_cs
, CSR_V_IE
) },
167 { DRDATA (BPTR
, ta_bptr
, 17) },
168 { DRDATA (BLNT
, ta_blnt
, 17) },
169 { DRDATA (STIME
, ta_stime
, 24), PV_LEFT
+ REG_NZ
},
170 { DRDATA (CTIME
, ta_ctime
, 24), PV_LEFT
+ REG_NZ
},
171 { FLDATA (STOP_IOE
, ta_stopioe
, 0) },
172 { URDATA (UFNC
, ta_unit
[0].FNC
, 8, 5, 0, TA_NUMDR
, 0), REG_HRO
},
173 { URDATA (UST
, ta_unit
[0].UST
, 8, 2, 0, TA_NUMDR
, 0), REG_HRO
},
174 { URDATA (POS
, ta_unit
[0].pos
, 10, T_ADDR_W
, 0,
175 TA_NUMDR
, PV_LEFT
| REG_RO
) },
176 { ORDATA (DEVADDR
, ta_dib
.ba
, 32), REG_HRO
},
177 { ORDATA (DEVVEC
, ta_dib
.vec
, 16), REG_HRO
},
182 { MTUF_WLK
, 0, "write enabled", "WRITEENABLED", NULL
},
183 { MTUF_WLK
, MTUF_WLK
, "write locked", "LOCKED", NULL
},
184 // { MTAB_XTD|MTAB_VUN, 0, "FORMAT", "FORMAT",
185 // &sim_tape_set_fmt, &sim_tape_show_fmt, NULL },
186 { MTAB_XTD
|MTAB_VUN
, 0, "CAPACITY", NULL
,
187 NULL
, &sim_tape_show_capac
, NULL
},
188 { MTAB_XTD
|MTAB_VDV
, IOLN_TA
, "ADDRESS", "ADDRESS",
189 &set_addr
, &show_addr
, NULL
},
190 { MTAB_XTD
|MTAB_VDV
, 0, "VECTOR", "VECTOR",
191 &set_vec
, &show_vec
, NULL
},
196 "TA", ta_unit
, ta_reg
, ta_mod
,
197 TA_NUMDR
, 10, 31, 1, 8, 8,
198 NULL
, NULL
, &ta_reset
,
199 NULL
, &ta_attach
, &ta_detach
,
200 &ta_dib
, DEV_DISABLE
| DEV_DIS
| DEV_DEBUG
203 /* I/O dispatch routines, I/O addresses 17777500 - 17777503
205 17777500 TACS read/write
206 17777502 TADB read/write
209 t_stat
ta_rd (int32
*data
, int32 PA
, int32 access
)
211 switch ((PA
>> 1) & 01) { /* decode PA<1> */
214 *data
= ta_updsta (NULL
); /* update status */
218 *data
= ta_idb
; /* return byte */
219 ta_cs
&= ~TACS_TR
; /* clear tra req */
227 t_stat
ta_wr (int32 data
, int32 PA
, int32 access
)
229 switch ((PA
>> 1) & 01) { /* decode PA<1> */
232 if (access
== WRITEB
) data
= (PA
& 1)? /* byte write? */
233 (ta_cs
& 0377) | (data
<< 8): /* merge old */
234 (ta_cs
& ~0377) | data
;
235 ta_cs
= (ta_cs
& ~TACS_W
) | (data
& TACS_W
); /* merge new */
236 if ((data
& CSR_GO
) && !ta_busy ()) /* go, not busy? */
237 ta_go (); /* start operation */
238 if (ta_cs
& TACS_ILBS
) ta_cs
&= ~TACS_TR
; /* ILBS inhibits TR */
242 if (PA
& 1) break; /* ignore odd byte */
243 ta_odb
= data
; /* return byte */
244 ta_cs
&= ~TACS_TR
; /* clear tra req */
248 ta_updsta (NULL
); /* update status */
252 /* Start a new operation - cassette is not busy */
256 UNIT
*uptr
= ta_dev
.units
+ GET_UNIT (ta_cs
);
257 uint32 fnc
= GET_FNC (ta_cs
);
258 uint32 flg
= ta_fnc_tab
[fnc
];
259 uint32 old_ust
= uptr
->UST
;
261 if (DEBUG_PRS (ta_dev
)) fprintf (sim_deb
,
262 ">>TA start: op=%o, old_sta = %o, pos=%d\n",
263 fnc
, uptr
->UST
, uptr
->pos
);
264 ta_cs
&= ~(TACS_XFRERR
|TACS_EMP
|TACS_TR
|TACS_RDY
); /* clr err, tr, rdy */
265 ta_bptr
= 0; /* init buffer */
267 if ((uptr
->flags
& UNIT_ATT
) == 0) {
268 ta_cs
|= TACS_ERR
|TACS_EMP
|TACS_RDY
;
271 if (flg
& OP_WRI
) { /* write op? */
272 if (sim_tape_wrp (uptr
)) { /* locked? */
273 ta_cs
|= TACS_ERR
|TACS_WLK
|TACS_RDY
; /* don't start */
283 ta_cs
&= ~TACS_BEOT
; /* tape in motion */
284 uptr
->FNC
= fnc
; /* save function */
285 if ((fnc
!= TACS_REW
) && !(flg
& OP_WRI
)) { /* spc/read cmd? */
288 uptr
->UST
= flg
& UST_REV
; /* save direction */
289 if (sim_tape_bot (uptr
) && (flg
& OP_FWD
)) { /* spc/read fwd bot? */
290 st
= sim_tape_rdrecf (uptr
, ta_xb
, &t
, TA_MAXFR
); /* skip file gap */
291 if (st
!= MTSE_TMK
) /* not there? */
292 sim_tape_rewind (uptr
); /* restore tap pos */
293 else old_ust
= 0; /* defang next */
295 if ((old_ust
^ uptr
->UST
) == (UST_REV
|UST_GAP
)) { /* reverse in gap? */
296 if (uptr
->UST
) /* skip file gap */
297 sim_tape_rdrecr (uptr
, ta_xb
, &t
, TA_MAXFR
);
298 else sim_tape_rdrecf (uptr
, ta_xb
, &t
, TA_MAXFR
);
299 if (DEBUG_PRS (ta_dev
)) fprintf (sim_deb
,
300 ">>TA skip gap: op=%o, old_sta = %o, pos=%d\n",
301 fnc
, uptr
->UST
, uptr
->pos
);
305 sim_activate (uptr
, ta_stime
); /* schedule op */
311 t_stat
ta_svc (UNIT
*uptr
)
314 uint32 flg
= ta_fnc_tab
[uptr
->FNC
& TACS_M_FNC
];
318 if ((uptr
->flags
& UNIT_ATT
) == 0) { /* not attached? */
319 ta_cs
|= TACS_ERR
|TACS_EMP
|TACS_RDY
;
320 ta_updsta (uptr
); /* update status */
321 return (ta_stopioe
? SCPE_UNATT
: SCPE_OK
);
323 if (((flg
& OP_FWD
) && sim_tape_eot (uptr
)) || /* illegal motion? */
324 ((flg
& OP_REV
) && sim_tape_bot (uptr
))) {
325 ta_cs
|= TACS_ERR
|TACS_BEOT
|TACS_RDY
; /* error */
331 switch (uptr
->FNC
) { /* case on function */
333 case TACS_READ
: /* read start */
334 st
= sim_tape_rdrecf (uptr
, ta_xb
, &ta_blnt
, TA_MAXFR
); /* get rec */
335 if (st
== MTSE_RECE
) ta_cs
|= TACS_ERR
|TACS_CRC
; /* rec in err? */
336 else if (st
!= MTSE_OK
) { /* other error? */
337 r
= ta_map_err (uptr
, st
); /* map error */
340 crc
= ta_crc (ta_xb
, ta_blnt
); /* calculate CRC */
341 ta_xb
[ta_blnt
++] = (crc
>> 8) & 0377; /* append to buffer */
342 ta_xb
[ta_blnt
++] = crc
& 0377;
343 uptr
->FNC
|= TACS_2ND
; /* next state */
344 sim_activate (uptr
, ta_ctime
); /* sched next char */
347 case TACS_READ
|TACS_2ND
: /* read char */
348 if (ta_bptr
< ta_blnt
) /* more chars? */
349 ta_idb
= ta_xb
[ta_bptr
++];
352 ta_cs
|= TACS_ERR
|TACS_CRC
; /* overrun */
353 break; /* tape stops */
355 if (ta_cs
& TACS_ILBS
) { /* CRC seq? */
356 uptr
->FNC
|= TACS_3RD
; /* next state */
357 sim_activate (uptr
, ta_stime
); /* sched CRC chk */
360 ta_set_tr (); /* set tra req */
361 sim_activate (uptr
, ta_ctime
); /* sched next char */
365 case TACS_READ
|TACS_3RD
: /* second read CRC */
366 if (ta_bptr
!= ta_blnt
) { /* partial read? */
367 crc
= ta_crc (ta_xb
, ta_bptr
+ 2); /* actual CRC */
368 if (crc
!= 0) ta_cs
|= TACS_ERR
|TACS_CRC
; /* must be zero */
370 break; /* read done */
372 case TACS_WRITE
: /* write start */
373 for (i
= 0; i
< TA_MAXFR
; i
++) ta_xb
[i
] = 0; /* clear buffer */
374 ta_set_tr (); /* set tra req */
375 uptr
->FNC
|= TACS_2ND
; /* next state */
376 sim_activate (uptr
, ta_ctime
); /* sched next char */
379 case TACS_WRITE
|TACS_2ND
: /* write char */
380 if (ta_cs
& TACS_ILBS
) { /* CRC seq? */
381 uptr
->FNC
|= TACS_3RD
; /* next state */
382 sim_activate (uptr
, ta_stime
); /* sched wri done */
385 if ((ta_bptr
< TA_MAXFR
) && /* room in buf? */
386 ((uptr
->pos
+ ta_bptr
) < uptr
->capac
)) /* room on tape? */
387 ta_xb
[ta_bptr
++] = ta_odb
; /* store char */
388 ta_set_tr (); /* set tra req */
389 sim_activate (uptr
, ta_ctime
); /* sched next char */
393 case TACS_WRITE
|TACS_3RD
: /* write CRC */
394 if (ta_bptr
) { /* anything to write? */
395 if (st
= sim_tape_wrrecf (uptr
, ta_xb
, ta_bptr
)) /* write, err? */
396 r
= ta_map_err (uptr
, st
); /* map error */
400 case TACS_WFG
: /* write file gap */
401 if (st
= sim_tape_wrtmk (uptr
)) /* write tmk, err? */
402 r
= ta_map_err (uptr
, st
); /* map error */
405 case TACS_REW
: /* rewind */
406 sim_tape_rewind (uptr
);
407 ta_cs
|= TACS_BEOT
; /* bot, no error */
410 case TACS_SRB
: /* space rev blk */
411 if (st
= sim_tape_sprecr (uptr
, &tbc
)) /* space rev, err? */
412 r
= ta_map_err (uptr
, st
); /* map error */
415 case TACS_SRF
: /* space rev file */
416 while ((st
= sim_tape_sprecr (uptr
, &tbc
)) == MTSE_OK
) ;
417 if (st
== MTSE_TMK
) /* if tape mark, */
418 ta_cs
|= TACS_EOF
; /* set EOF, no err */
419 else r
= ta_map_err (uptr
, st
); /* else map error */
422 case TACS_SFB
: /* space fwd blk */
423 if (st
= sim_tape_sprecf (uptr
, &tbc
)) /* space rev, err? */
424 r
= ta_map_err (uptr
, st
); /* map error */
425 ta_cs
|= TACS_CRC
; /* CRC sets, no err */
428 case TACS_SFF
: /* space fwd file */
429 while ((st
= sim_tape_sprecf (uptr
, &tbc
)) == MTSE_OK
) ;
430 if (st
== MTSE_TMK
) /* if tape mark, */
431 ta_cs
|= TACS_EOF
; /* set EOF, no err */
432 else r
= ta_map_err (uptr
, st
); /* else map error */
435 default: /* never get here! */
439 ta_cs
|= TACS_RDY
; /* set ready */
440 ta_updsta (uptr
); /* update status */
441 if (DEBUG_PRS (ta_dev
)) fprintf (sim_deb
,
442 ">>TA done: op=%o, status = %o, pos=%d\n",
443 uptr
->FNC
, ta_cs
, uptr
->pos
);
447 /* Update controller status */
449 uint32
ta_updsta (UNIT
*uptr
)
451 if (uptr
== NULL
) { /* unit specified? */
452 if ((uptr
= ta_busy ()) == NULL
) /* use busy */
453 uptr
= ta_dev
.units
+ GET_UNIT (ta_cs
); /* use sel unit */
455 else if (ta_cs
& TACS_EOF
) uptr
->UST
|= UST_GAP
; /* save EOF */
456 if (uptr
->flags
& UNIT_ATT
) ta_cs
&= ~TACS_EMP
; /* attached? */
457 else ta_cs
|= TACS_EMP
|TACS_RDY
; /* no, empty, ready */
458 if ((ta_cs
& TACS_IE
) && /* int enabled? */
459 (ta_cs
& (TACS_TR
|TACS_RDY
))) /* req or ready? */
460 SET_INT (TA
); /* set int req */
461 else CLR_INT (TA
); /* no, clr int req */
465 /* Set transfer request */
467 void ta_set_tr (void)
469 if (ta_cs
& TACS_TR
) ta_cs
|= (TACS_ERR
|TACS_TIM
); /* flag still set? */
470 else ta_cs
|= TACS_TR
; /* set xfr req */
471 if (ta_cs
& TACS_IE
) SET_INT (TA
); /* if ie, int req */
475 /* Test if controller busy */
482 for (u
= 0; u
< TA_NUMDR
; u
++) { /* loop thru units */
483 uptr
= ta_dev
.units
+ u
;
484 if (sim_is_active (uptr
)) return uptr
;
489 /* Calculate CRC on buffer */
491 uint32
ta_crc (uint8
*buf
, uint32 cnt
)
496 for (i
= 0; i
< cnt
; i
++) {
497 crc
= crc
^ (((uint32
) buf
[i
]) << 8);
498 for (j
= 0; j
< 8; j
++) {
499 if (crc
& 1) crc
= (crc
>> 1) ^ 0xA001;
506 /* Map error status */
508 t_stat
ta_map_err (UNIT
*uptr
, t_stat st
)
512 case MTSE_FMT
: /* illegal fmt */
513 case MTSE_UNATT
: /* unattached */
514 ta_cs
|= TACS_ERR
|TACS_CRC
;
515 case MTSE_OK
: /* no error */
516 return SCPE_IERR
; /* never get here! */
518 case MTSE_TMK
: /* end of file */
519 ta_cs
|= TACS_ERR
|TACS_EOF
;
522 case MTSE_IOERR
: /* IO error */
523 ta_cs
|= TACS_ERR
|TACS_CRC
; /* set crc err */
524 if (ta_stopioe
) return SCPE_IOERR
;
527 case MTSE_INVRL
: /* invalid rec lnt */
528 ta_cs
|= TACS_ERR
|TACS_CRC
; /* set crc err */
531 case MTSE_RECE
: /* record in error */
532 case MTSE_EOM
: /* end of medium */
533 ta_cs
|= TACS_ERR
|TACS_CRC
; /* set crc err */
536 case MTSE_BOT
: /* reverse into BOT */
537 ta_cs
|= TACS_ERR
|TACS_BEOT
; /* set bot */
540 case MTSE_WRP
: /* write protect */
541 ta_cs
|= TACS_ERR
|TACS_WLK
; /* set wlk err */
550 t_stat
ta_reset (DEVICE
*dptr
)
561 CLR_INT (TA
); /* clear interrupt */
562 for (u
= 0; u
< TA_NUMDR
; u
++) { /* loop thru units */
563 uptr
= ta_dev
.units
+ u
;
564 sim_cancel (uptr
); /* cancel activity */
565 sim_tape_reset (uptr
); /* reset tape */
567 if (ta_xb
== NULL
) ta_xb
= (uint8
*) calloc (TA_MAXFR
+ 2, sizeof (uint8
));
568 if (ta_xb
== NULL
) return SCPE_MEM
;
574 t_stat
ta_attach (UNIT
*uptr
, char *cptr
)
578 r
= sim_tape_attach (uptr
, cptr
);
579 if (r
!= SCPE_OK
) return r
;
587 t_stat
ta_detach (UNIT
* uptr
)
591 if (!(uptr
->flags
& UNIT_ATT
)) return SCPE_OK
; /* check attached */
592 r
= sim_tape_detach (uptr
);