First Commit of my working state
[simh.git] / Interdata / id_mt.c
1 /* id_mt.c: Interdata magnetic tape simulator
2
3 Copyright (c) 2001-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 M46-494 dual density 9-track magtape controller
27
28 16-Feb-06 RMS Added tape capacity checking
29 18-Mar-05 RMS Added attached test to detach routine
30 07-Dec-04 RMS Added read-only file support
31 25-Apr-03 RMS Revised for extended file support
32 28-Mar-03 RMS Added multiformat support
33 28-Feb-03 RMS Revised for magtape library
34 20-Feb-03 RMS Fixed read to stop selch on error
35
36 Magnetic tapes are represented as a series of variable 8b records
37 of the form:
38
39 32b record length in bytes - exact number
40 byte 0
41 byte 1
42 :
43 byte n-2
44 byte n-1
45 32b record length in bytes - exact number
46
47 If the byte count is odd, the record is padded with an extra byte
48 of junk. File marks are represented by a single record length of 0.
49 End of tape is two consecutive end of file marks.
50 */
51
52 #include "id_defs.h"
53 #include "sim_tape.h"
54
55 #define UST u3 /* unit status */
56 #define UCMD u4 /* unit command */
57 #define MT_MAXFR (1 << 24) /* max transfer */
58
59 /* Command - in UCMD */
60
61 #define MTC_SPCR 0x11 /* backspace */
62 #define MTC_SKFR 0x13 /* space file rev */
63 #define MTC_CLR 0x20 /* clear */
64 #define MTC_RD 0x21 /* read */
65 #define MTC_WR 0x22 /* write */
66 #define MTC_SKFF 0x23 /* space file fwd */
67 #define MTC_WEOF 0x30 /* write eof */
68 #define MTC_REW 0x38 /* rewind */
69 #define MTC_MASK 0x3F
70 #define MTC_STOP1 0x40 /* stop, set EOM */
71 #define MTC_STOP2 0x80 /* stop, set NMTN */
72
73 /* Status byte, * = in UST */
74
75 #define STA_ERR 0x80 /* error */
76 #define STA_EOF 0x40 /* end of file */
77 #define STA_EOT 0x20 /* *end of tape */
78 #define STA_NMTN 0x10 /* *no motion */
79 #define STA_UFLGS (STA_EOT|STA_NMTN) /* unit flags */
80 #define STA_MASK (STA_ERR|STA_EOF|STA_BSY|STA_EOM)
81 #define SET_EX (STA_ERR|STA_EOF|STA_NMTN)
82
83 extern uint32 int_req[INTSZ], int_enb[INTSZ];
84
85 uint8 mtxb[MT_MAXFR]; /* xfer buffer */
86 uint32 mt_bptr = 0; /* pointer */
87 uint32 mt_blnt = 0; /* length */
88 uint32 mt_sta = 0; /* status byte */
89 uint32 mt_db = 0; /* data buffer */
90 uint32 mt_xfr = 0; /* data xfr in prog */
91 uint32 mt_arm[MT_NUMDR] = { 0 }; /* intr armed */
92 int32 mt_wtime = 10; /* byte latency */
93 int32 mt_rtime = 1000; /* record latency */
94 int32 mt_stopioe = 1; /* stop on error */
95 uint8 mt_tplte[] = { 0, o_MT0, o_MT0*2, o_MT0*3, TPL_END };
96
97 static const uint8 bad_cmd[64] = {
98 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
99 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
100 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
101 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1
102 };
103
104 DEVICE mt_dev;
105 uint32 mt (uint32 dev, uint32 op, uint32 dat);
106 t_stat mt_svc (UNIT *uptr);
107 t_stat mt_reset (DEVICE *dptr);
108 t_stat mt_attach (UNIT *uptr, char *cptr);
109 t_stat mt_detach (UNIT *uptr);
110 t_stat mt_boot (int32 unitno, DEVICE *dptr);
111 t_stat mt_map_err (UNIT *uptr, t_stat st);
112
113 /* MT data structures
114
115 mt_dev MT device descriptor
116 mt_unit MT unit list
117 mt_reg MT register list
118 mt_mod MT modifier list
119 */
120
121 DIB mt_dib = { d_MT, 0, v_MT, mt_tplte, &mt, NULL };
122
123 UNIT mt_unit[] = {
124 { UDATA (&mt_svc, UNIT_ATTABLE + UNIT_ROABLE + UNIT_DISABLE, 0) },
125 { UDATA (&mt_svc, UNIT_ATTABLE + UNIT_ROABLE + UNIT_DISABLE, 0) },
126 { UDATA (&mt_svc, UNIT_ATTABLE + UNIT_ROABLE + UNIT_DISABLE, 0) },
127 { UDATA (&mt_svc, UNIT_ATTABLE + UNIT_ROABLE + UNIT_DISABLE, 0) }
128 };
129
130 REG mt_reg[] = {
131 { HRDATA (STA, mt_sta, 8) },
132 { HRDATA (BUF, mt_db, 8) },
133 { BRDATA (DBUF, mtxb, 16, 8, MT_MAXFR) },
134 { HRDATA (DBPTR, mt_bptr, 16) },
135 { HRDATA (DBLNT, mt_blnt, 17), REG_RO },
136 { FLDATA (XFR, mt_xfr, 0) },
137 { GRDATA (IREQ, int_req[l_MT], 16, MT_NUMDR, i_MT) },
138 { GRDATA (IENB, int_enb[l_MT], 16, MT_NUMDR, i_MT) },
139 { BRDATA (IARM, mt_arm, 16, 1, MT_NUMDR) },
140 { FLDATA (STOP_IOE, mt_stopioe, 0) },
141 { DRDATA (WTIME, mt_wtime, 24), PV_LEFT + REG_NZ },
142 { DRDATA (RTIME, mt_rtime, 24), PV_LEFT + REG_NZ },
143 { URDATA (UST, mt_unit[0].UST, 16, 8, 0, MT_NUMDR, 0) },
144 { URDATA (CMD, mt_unit[0].UCMD, 16, 8, 0, MT_NUMDR, 0) },
145 { URDATA (POS, mt_unit[0].pos, 10, T_ADDR_W, 0,
146 MT_NUMDR, PV_LEFT | REG_RO) },
147 { HRDATA (DEVNO, mt_dib.dno, 8), REG_HRO },
148 { HRDATA (SELCH, mt_dib.sch, 1), REG_HRO },
149 { NULL }
150 };
151
152 MTAB mt_mod[] = {
153 { MTUF_WLK, 0, "write enabled", "WRITEENABLED", NULL },
154 { MTUF_WLK, MTUF_WLK, "write locked", "LOCKED", NULL },
155 { MTAB_XTD|MTAB_VUN, 0, "FORMAT", "FORMAT",
156 &sim_tape_set_fmt, &sim_tape_show_fmt, NULL },
157 { MTAB_XTD|MTAB_VUN, 0, "CAPACITY", "CAPACITY",
158 &sim_tape_set_capac, &sim_tape_show_capac, NULL },
159 { MTAB_XTD|MTAB_VDV, 0, "DEVNO", "DEVNO",
160 &set_dev, &show_dev, NULL },
161 { MTAB_XTD|MTAB_VDV, 0, "SELCH", "SELCH",
162 &set_sch, &show_sch, NULL },
163 { 0 }
164 };
165
166 DEVICE mt_dev = {
167 "MT", mt_unit, mt_reg, mt_mod,
168 MT_NUMDR, 10, 31, 1, 16, 8,
169 NULL, NULL, &mt_reset,
170 &mt_boot, &mt_attach, &mt_detach,
171 &mt_dib, DEV_DISABLE
172 };
173
174 /* Magtape: IO routine */
175
176 uint32 mt (uint32 dev, uint32 op, uint32 dat)
177 {
178 uint32 i, f, t;
179 uint32 u = (dev - mt_dib.dno) / o_MT0;
180 UNIT *uptr = mt_dev.units + u;
181
182 switch (op) { /* case IO op */
183
184 case IO_ADR: /* select */
185 sch_adr (mt_dib.sch, dev); /* inform sel ch */
186 return BY; /* byte only */
187
188 case IO_RD: /* read data */
189 if (mt_xfr) mt_sta = mt_sta | STA_BSY; /* xfr? set busy */
190 return mt_db; /* return data */
191
192 case IO_WD: /* write data */
193 if (mt_xfr) { /* transfer? */
194 mt_sta = mt_sta | STA_BSY; /* set busy */
195 if ((uptr->UCMD & (MTC_STOP1 | MTC_STOP2)) &&
196 ((uptr->UCMD & MTC_MASK) == MTC_WR)) /* while stopping? */
197 mt_sta = mt_sta | STA_ERR; /* write overrun */
198 }
199 mt_db = dat & DMASK8; /* store data */
200 break;
201
202 case IO_SS: /* status */
203 mt_sta = mt_sta & STA_MASK; /* ctrl status */
204 if (uptr->flags & UNIT_ATT) /* attached? */
205 t = mt_sta | (uptr->UST & STA_UFLGS); /* yes, unit status */
206 else t = mt_sta | STA_DU; /* no, dev unavail */
207 if (t & SET_EX) t = t | STA_EX; /* test for ex */
208 return t;
209
210 case IO_OC: /* command */
211 mt_arm[u] = int_chg (v_MT + u, dat, mt_arm[u]);
212 f = dat & MTC_MASK; /* get cmd */
213 if (f == MTC_CLR) { /* clear? */
214 mt_reset (&mt_dev); /* reset world */
215 break;
216 }
217 if (((uptr->flags & UNIT_ATT) == 0) || /* ignore if unatt */
218 bad_cmd[f] || /* or bad cmd */
219 (((f == MTC_WR) || (f == MTC_WEOF)) && /* or write */
220 sim_tape_wrp (uptr))) break; /* and protected */
221 for (i = 0; i < MT_NUMDR; i++) { /* check other drvs */
222 if (sim_is_active (&mt_unit[i]) && /* active? */
223 (mt_unit[i].UCMD != MTC_REW)) { /* not rewind? */
224 sim_cancel (&mt_unit[i]); /* stop */
225 mt_unit[i].UCMD = 0;
226 }
227 }
228 if (sim_is_active (uptr) && /* unit active? */
229 !(uptr->UCMD & (MTC_STOP1 | MTC_STOP2))) /* not stopping? */
230 break; /* ignore */
231 if ((f == MTC_WR) || (f == MTC_REW)) mt_sta = 0;/* write, rew: bsy=0 */
232 else mt_sta = STA_BSY; /* bsy=1,nmtn,eom,err=0 */
233 mt_bptr = mt_blnt = 0; /* not yet started */
234 if ((f == MTC_RD) || (f == MTC_WR)) /* data xfr? */
235 mt_xfr = 1; /* set xfr flag */
236 else mt_xfr = 0;
237 uptr->UCMD = f; /* save cmd */
238 uptr->UST = 0; /* clr tape stat */
239 sim_activate (uptr, mt_rtime); /* start op */
240 break;
241 }
242
243 return 0;
244 }
245
246 /* Unit service
247
248 A given operation can generate up to three interrupts
249
250 - EOF generates an interrupt when set (read, space, wreof)
251 BUSY will still be set, EOM and NMTN will be clear
252 - After operation complete + delay, EOM generates an interrupt
253 BUSY will be clear, EOM will be set, NMTN will be clear
254 - After a further delay, NMTN generates an interrupt
255 BUSY will be clear, EOM and NMTN will be set
256
257 Rewind generates an interrupt when NMTN sets
258 */
259
260 t_stat mt_svc (UNIT *uptr)
261 {
262 uint32 i;
263 int32 u = uptr - mt_dev.units;
264 uint32 dev = mt_dib.dno + (u * o_MT0);
265 t_mtrlnt tbc;
266 t_bool passed_eot;
267 t_stat st, r = SCPE_OK;
268
269 if ((uptr->flags & UNIT_ATT) == 0) { /* not attached? */
270 uptr->UCMD = 0; /* clr cmd */
271 uptr->UST = 0; /* set status */
272 mt_xfr = 0; /* clr op flags */
273 mt_sta = STA_ERR | STA_EOM; /* set status */
274 if (mt_arm[u]) SET_INT (v_MT + u); /* interrupt */
275 return IORETURN (mt_stopioe, SCPE_UNATT);
276 }
277
278 if (uptr->UCMD & MTC_STOP2) { /* stop, gen NMTN? */
279 uptr->UCMD = 0; /* clr cmd */
280 uptr->UST = uptr->UST | STA_NMTN; /* set nmtn */
281 mt_xfr = 0; /* clr xfr */
282 if (mt_arm[u]) SET_INT (v_MT + u); /* set intr */
283 return SCPE_OK;
284 }
285
286 if (uptr->UCMD & MTC_STOP1) { /* stop, gen EOM? */
287 uptr->UCMD = uptr->UCMD | MTC_STOP2; /* clr cmd */
288 mt_sta = (mt_sta & ~STA_BSY) | STA_EOM; /* clr busy, set eom */
289 if (mt_arm[u]) SET_INT (v_MT + u); /* set intr */
290 sim_activate (uptr, mt_rtime); /* schedule */
291 return SCPE_OK;
292 }
293
294 passed_eot = sim_tape_eot (uptr); /* passed EOT? */
295 switch (uptr->UCMD) { /* case on function */
296
297 case MTC_REW: /* rewind */
298 sim_tape_rewind (uptr); /* reposition */
299 uptr->UCMD = 0; /* clr cmd */
300 uptr->UST = STA_NMTN | STA_EOT; /* update status */
301 mt_sta = mt_sta & ~STA_BSY; /* don't set EOM */
302 if (mt_arm[u]) SET_INT (v_MT + u); /* interrupt */
303 return SCPE_OK;
304
305 /* For read, busy = 1 => buffer empty
306 For write, busy = 1 => buffer full
307 For read, data transfers continue for the full length of the
308 record, or the maximum size of the transfer buffer
309 For write, data transfers continue until a write is attempted
310 and the buffer is empty
311 */
312
313 case MTC_RD: /* read */
314 if (mt_blnt == 0) { /* first time? */
315 st = sim_tape_rdrecf (uptr, mtxb, &tbc, MT_MAXFR); /* read rec */
316 if (st == MTSE_RECE) mt_sta = mt_sta | STA_ERR; /* rec in err? */
317 else if (st != SCPE_OK) { /* other error? */
318 r = mt_map_err (uptr, st); /* map error */
319 if (sch_actv (mt_dib.sch, dev)) /* if sch, stop */
320 sch_stop (mt_dib.sch);
321 break;
322 }
323 mt_blnt = tbc; /* set buf lnt */
324 }
325
326 if (sch_actv (mt_dib.sch, dev)) { /* sch active? */
327 i = sch_wrmem (mt_dib.sch, mtxb, mt_blnt); /* store rec in mem */
328 if (sch_actv (mt_dib.sch, dev)) /* sch still active? */
329 sch_stop (mt_dib.sch); /* stop chan, long rd */
330 else if (i < mt_blnt) /* process entire rec? */
331 mt_sta = mt_sta | STA_ERR; /* no, overrun error */
332 }
333 else if (mt_bptr < mt_blnt) { /* no, if !eor */
334 if (!(mt_sta & STA_BSY)) /* busy still clr? */
335 mt_sta = mt_sta | STA_ERR; /* read overrun */
336 mt_db = mtxb[mt_bptr++]; /* get next byte */
337 mt_sta = mt_sta & ~STA_BSY; /* !busy = buf full */
338 if (mt_arm[u]) SET_INT (v_MT + u); /* set intr */
339 sim_activate (uptr, mt_wtime); /* reschedule */
340 return SCPE_OK;
341 }
342 break; /* record done */
343
344 case MTC_WR: /* write */
345 if (sch_actv (mt_dib.sch, dev)) { /* sch active? */
346 mt_bptr = sch_rdmem (mt_dib.sch, mtxb, MT_MAXFR); /* get rec */
347 if (sch_actv (mt_dib.sch, dev)) /* not done? */
348 sch_stop (mt_dib.sch); /* stop chan */
349 }
350 else if (mt_sta & STA_BSY) { /* no, if !eor */
351 if (mt_bptr < MT_MAXFR) /* if room */
352 mtxb[mt_bptr++] = mt_db; /* store in buf */
353 mt_sta = mt_sta & ~STA_BSY; /* !busy = buf emp */
354 if (mt_arm[u]) SET_INT (v_MT + u); /* set intr */
355 sim_activate (uptr, mt_wtime); /* reschedule */
356 return SCPE_OK;
357 }
358
359 if (mt_bptr) { /* any chars? */
360 if (st = sim_tape_wrrecf (uptr, mtxb, mt_bptr)) /* write, err? */
361 r = mt_map_err (uptr, st); /* map error */
362 }
363 break; /* record done */
364
365 case MTC_WEOF: /* write eof */
366 if (st = sim_tape_wrtmk (uptr)) /* write tmk, err? */
367 r = mt_map_err (uptr, st); /* map error */
368 mt_sta = mt_sta | STA_EOF; /* set eof */
369 if (mt_arm[u]) SET_INT (v_MT + u); /* interrupt */
370 break;
371
372 case MTC_SKFF: /* skip file fwd */
373 while ((st = sim_tape_sprecf (uptr, &tbc)) == MTSE_OK) ;
374 if (st == MTSE_TMK) { /* stopped by tmk? */
375 mt_sta = mt_sta | STA_EOF; /* set eof */
376 if (mt_arm[u]) SET_INT (v_MT + u); /* set intr */
377 }
378 else r = mt_map_err (uptr, st); /* map error */
379 break;
380
381 case MTC_SKFR: /* skip file rev */
382 while ((st = sim_tape_sprecr (uptr, &tbc)) == MTSE_OK) ;
383 if (st == MTSE_TMK) { /* stopped by tmk? */
384 mt_sta = mt_sta | STA_EOF; /* set eof */
385 if (mt_arm[u]) SET_INT (v_MT + u); /* set intr */
386 }
387 else r = mt_map_err (uptr, st); /* map error */
388 break;
389
390 case MTC_SPCR: /* backspace */
391 if (st = sim_tape_sprecr (uptr, &tbc)) /* skip rec rev, err? */
392 r = mt_map_err (uptr, st); /* map error */
393 break;
394 } /* end case */
395
396 if (!passed_eot && sim_tape_eot (uptr)) /* just passed EOT? */
397 uptr->UST = uptr->UST | STA_EOT;
398 uptr->UCMD = uptr->UCMD | MTC_STOP1; /* set stop stage 1 */
399 sim_activate (uptr, mt_rtime); /* schedule */
400 return r;
401 }
402
403 /* Map tape error status */
404
405 t_stat mt_map_err (UNIT *uptr, t_stat st)
406 {
407 int32 u = uptr - mt_dev.units;
408
409 switch (st) {
410
411 case MTSE_FMT: /* illegal fmt */
412 case MTSE_UNATT: /* not attached */
413 mt_sta = mt_sta | STA_ERR;
414 case MTSE_OK: /* no error */
415 return SCPE_IERR;
416
417 case MTSE_TMK: /* end of file */
418 mt_sta = mt_sta | STA_EOF; /* set eof */
419 if (mt_arm[u]) SET_INT (v_MT + u); /* set intr */
420 break;
421
422 case MTSE_IOERR: /* IO error */
423 mt_sta = mt_sta | STA_ERR; /* set err */
424 if (mt_stopioe) return SCPE_IOERR;
425 break;
426
427 case MTSE_INVRL: /* invalid rec lnt */
428 mt_sta = mt_sta | STA_ERR;
429 return SCPE_MTRLNT;
430
431 case MTSE_WRP: /* write protect */
432 case MTSE_RECE: /* record in error */
433 case MTSE_EOM: /* end of medium */
434 mt_sta = mt_sta | STA_ERR; /* set err */
435 break;
436
437 case MTSE_BOT: /* reverse into BOT */
438 uptr->UST = uptr->UST | STA_EOT; /* set err */
439 break;
440 } /* end switch */
441
442 return SCPE_OK;
443 }
444
445 /* Reset routine */
446
447 t_stat mt_reset (DEVICE *dptr)
448 {
449 uint32 u;
450 UNIT *uptr;
451
452 mt_bptr = mt_blnt = 0; /* clr buf */
453 mt_sta = STA_BSY; /* clr flags */
454 mt_xfr = 0; /* clr controls */
455 for (u = 0; u < MT_NUMDR; u++) { /* loop thru units */
456 CLR_INT (v_MT + u); /* clear int */
457 CLR_ENB (v_MT + u); /* disable int */
458 mt_arm[u] = 0; /* disarm int */
459 uptr = mt_dev.units + u;
460 sim_tape_reset (uptr); /* clear pos flag */
461 sim_cancel (uptr); /* cancel activity */
462 uptr->UST = (uptr->UST & STA_UFLGS) | STA_NMTN; /* init status */
463 uptr->UCMD = 0; /* init cmd */
464 }
465 return SCPE_OK;
466 }
467
468 /* Attach routine */
469
470 t_stat mt_attach (UNIT *uptr, char *cptr)
471 {
472 int32 u = uptr - mt_dev.units;
473 t_stat r;
474
475 r = sim_tape_attach (uptr, cptr);
476 if (r != SCPE_OK) return r;
477 uptr->UST = STA_EOT;
478 if (mt_arm[u]) SET_INT (v_MT + u);
479 return r;
480 }
481
482 /* Detach routine */
483
484 t_stat mt_detach (UNIT* uptr)
485 {
486 int32 u = uptr - mt_dev.units;
487 t_stat r;
488
489 if (!(uptr->flags & UNIT_ATT)) return SCPE_OK;
490 r = sim_tape_detach (uptr);
491 if (r != SCPE_OK) return r;
492 if (mt_arm[u]) SET_INT (v_MT + u);
493 uptr->UST = 0;
494 return SCPE_OK;
495 }
496
497 /* Bootstrap routine */
498
499 #define BOOT_START 0x50
500 #define BOOT_LEN (sizeof (boot_rom) / sizeof (uint8))
501
502 static uint8 boot_rom[] = {
503 0xD5, 0x00, 0x00, 0xCF, /* ST: AL CF */
504 0x43, 0x00, 0x00, 0x80 /* BR 80 */
505 };
506
507 t_stat mt_boot (int32 unitno, DEVICE *dptr)
508 {
509 extern uint32 PC, dec_flgs;
510 extern uint16 decrom[];
511 extern DIB sch_dib;
512 uint32 sch_dev;
513
514 if (decrom[0xD5] & dec_flgs) return SCPE_NOFNC; /* AL defined? */
515 sim_tape_rewind (&mt_unit[unitno]); /* rewind */
516 sch_dev = sch_dib.dno + mt_dib.sch; /* sch dev # */
517 IOWriteBlk (BOOT_START, BOOT_LEN, boot_rom); /* copy boot */
518 IOWriteB (AL_DEV, mt_dib.dno + (unitno * o_MT0)); /* set dev no for unit */
519 IOWriteB (AL_IOC, 0xA1); /* set dev cmd */
520 IOWriteB (AL_SCH, sch_dev); /* set dev no for chan */
521 PC = BOOT_START;
522 return SCPE_OK;
523 }