| /* sim_tape.c: simulator tape support library | |
| Copyright (c) 1993-2004, Robert M Supnik | |
| Permission is hereby granted, free of charge, to any person obtaining a | |
| copy of this software and associated documentation files (the "Software"), | |
| to deal in the Software without restriction, including without limitation | |
| the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
| and/or sell copies of the Software, and to permit persons to whom the | |
| Software is furnished to do so, subject to the following conditions: | |
| The above copyright notice and this permission notice shall be included in | |
| all copies or substantial portions of the Software. | |
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
| ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER | |
| IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | |
| CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| Except as contained in this notice, the name of Robert M Supnik shall not | |
| be used in advertising or otherwise to promote the sale, use or other dealings | |
| in this Software without prior written authorization from Robert M Supnik. | |
| Ultimately, this will be a place to hide processing of various tape formats, | |
| as well as OS-specific direct hardware access. | |
| 05-Jan-04 RMS Revised for file I/O library | |
| 25-Apr-03 RMS Added extended file support | |
| 28-Mar-03 RMS Added E11 and TPC format support | |
| Public routines: | |
| sim_tape_attach attach tape unit | |
| sim_tape_detach detach tape unit | |
| sim_tape_rdrecf read tape record forward | |
| sim_tape_rdrecr read tape record reverse | |
| sim_tape_wrrecf write tape record forward | |
| sim_tape_sprecf space tape record forward | |
| sim_tape_sprecr space tape record reverse | |
| sim_tape_wrtmk write tape mark | |
| sim_tape_wreom erase remainder of tape | |
| sim_tape_rewind rewind | |
| sim_tape_reset reset unit | |
| sim_tape_bot TRUE if at beginning of tape | |
| sim_tape_eot TRUE if at or beyond end of tape | |
| sim_tape_wrp TRUE if write protected | |
| sim_tape_set_fmt set tape format | |
| sim_tape_show_fmt show tape format | |
| */ | |
| #include "sim_defs.h" | |
| #include "sim_tape.h" | |
| struct sim_tape_fmt { | |
| char *name; /* name */ | |
| int32 uflags; /* unit flags */ | |
| t_addr bot; /* bot test */ | |
| }; | |
| static struct sim_tape_fmt fmts[MTUF_N_FMT] = { | |
| { "SIMH", 0, sizeof (t_mtrlnt) - 1 }, | |
| { "E11", 0, sizeof (t_mtrlnt) - 1 }, | |
| { "TPC", UNIT_RO, sizeof (t_tpclnt) - 1 }, | |
| /* { "TPF", UNIT_RO, 0 }, */ | |
| { NULL, 0, 0 } | |
| }; | |
| extern int32 sim_switches; | |
| t_stat sim_tape_ioerr (UNIT *uptr); | |
| t_stat sim_tape_wrdata (UNIT *uptr, uint32 dat); | |
| uint32 sim_tape_tpc_map (UNIT *uptr, t_addr *map); | |
| t_addr sim_tape_tpc_fnd (UNIT *uptr, t_addr *map); | |
| /* Attach tape unit */ | |
| t_stat sim_tape_attach (UNIT *uptr, char *cptr) | |
| { | |
| uint32 objc; | |
| char gbuf[CBUFSIZE]; | |
| t_stat r; | |
| if (sim_switches & SWMASK ('F')) { /* format spec? */ | |
| cptr = get_glyph (cptr, gbuf, 0); /* get spec */ | |
| if (*cptr == 0) return SCPE_2FARG; /* must be more */ | |
| if (sim_tape_set_fmt (uptr, 0, gbuf, NULL) != SCPE_OK) | |
| return SCPE_ARG; } | |
| r = attach_unit (uptr, cptr); /* attach unit */ | |
| if (r != SCPE_OK) return r; /* error? */ | |
| switch (MT_GET_FMT (uptr)) { /* case on format */ | |
| case MTUF_F_TPC: /* TPC */ | |
| objc = sim_tape_tpc_map (uptr, NULL); /* get # objects */ | |
| if (objc == 0) { /* tape empty? */ | |
| sim_tape_detach (uptr); | |
| return SCPE_FMT; } /* yes, complain */ | |
| uptr->filebuf = calloc (objc + 1, sizeof (t_mtrlnt)); | |
| if (uptr->filebuf == NULL) { /* map allocated? */ | |
| sim_tape_detach (uptr); | |
| return SCPE_MEM; } /* no, complain */ | |
| uptr->hwmark = objc + 1; /* save map size */ | |
| sim_tape_tpc_map (uptr, uptr->filebuf); /* fill map */ | |
| break; | |
| default: | |
| break; } | |
| sim_tape_rewind (uptr); | |
| return SCPE_OK; | |
| } | |
| /* Detach tape unit */ | |
| t_stat sim_tape_detach (UNIT *uptr) | |
| { | |
| uint32 f = MT_GET_FMT (uptr); | |
| t_stat r; | |
| r = detach_unit (uptr); /* detach unit */ | |
| if (r != SCPE_OK) return r; | |
| switch (f) { /* case on format */ | |
| case MTUF_F_TPC: /* TPC */ | |
| if (uptr->filebuf) free (uptr->filebuf); /* free map */ | |
| uptr->filebuf = NULL; | |
| uptr->hwmark = 0; | |
| break; | |
| default: | |
| break; } | |
| sim_tape_rewind (uptr); | |
| return SCPE_OK; | |
| } | |
| /* Read record length forward (internal routine) | |
| Inputs: | |
| uptr = pointer to tape unit | |
| bc = pointer to returned record length | |
| Outputs: | |
| status = operation status | |
| exit condition position | |
| unit unattached unchanged | |
| read error unchanged, PNU set | |
| end of file/medium unchanged, PNU set | |
| tape mark updated | |
| data record updated, sim_fread will read record forward | |
| */ | |
| t_stat sim_tape_rdlntf (UNIT *uptr, t_mtrlnt *bc) | |
| { | |
| uint32 f = MT_GET_FMT (uptr); | |
| t_mtrlnt sbc; | |
| t_tpclnt tpcbc; | |
| MT_CLR_PNU (uptr); | |
| if ((uptr->flags & UNIT_ATT) == 0) return MTSE_UNATT; /* not attached? */ | |
| sim_fseek (uptr->fileref, uptr->pos, SEEK_SET); /* set tape pos */ | |
| switch (f) { /* switch on fmt */ | |
| case MTUF_F_STD: case MTUF_F_E11: | |
| sim_fread (bc, sizeof (t_mtrlnt), 1, uptr->fileref); /* read rec lnt */ | |
| sbc = MTR_L (*bc); /* save rec lnt */ | |
| if (ferror (uptr->fileref)) { /* error? */ | |
| MT_SET_PNU (uptr); /* pos not upd */ | |
| return sim_tape_ioerr (uptr); } | |
| if (feof (uptr->fileref) || (*bc == MTR_EOM)) { /* eof or eom? */ | |
| MT_SET_PNU (uptr); /* pos not upd */ | |
| return MTSE_EOM; } | |
| uptr->pos = uptr->pos + sizeof (t_mtrlnt); /* spc over rec lnt */ | |
| if (*bc == MTR_TMK) return MTSE_TMK; /* tape mark? */ | |
| uptr->pos = uptr->pos + sizeof (t_mtrlnt) + /* spc over record */ | |
| ((f == MTUF_F_STD)? ((sbc + 1) & ~1): sbc); | |
| break; | |
| case MTUF_F_TPC: | |
| sim_fread (&tpcbc, sizeof (t_tpclnt), 1, uptr->fileref); | |
| *bc = tpcbc; /* save rec lnt */ | |
| if (ferror (uptr->fileref)) { /* error? */ | |
| MT_SET_PNU (uptr); /* pos not upd */ | |
| return sim_tape_ioerr (uptr); } | |
| if (feof (uptr->fileref)) { /* eof? */ | |
| MT_SET_PNU (uptr); /* pos not upd */ | |
| return MTSE_EOM; } | |
| uptr->pos = uptr->pos + sizeof (t_tpclnt); /* spc over reclnt */ | |
| if (tpcbc == TPC_TMK) return MTSE_TMK; /* tape mark? */ | |
| uptr->pos = uptr->pos + ((tpcbc + 1) & ~1); /* spc over record */ | |
| break; | |
| default: | |
| return MTSE_FMT; } | |
| return MTSE_OK; | |
| } | |
| /* Read record length reverse (internal routine) | |
| Inputs: | |
| uptr = pointer to tape unit | |
| bc = pointer to returned record length | |
| Outputs: | |
| status = operation status | |
| exit condition position | |
| unit unattached unchanged | |
| beginning of tape unchanged | |
| read error unchanged | |
| end of file unchanged | |
| end of medium updated | |
| tape mark updated | |
| data record updated, sim_fread will read record forward | |
| */ | |
| t_stat sim_tape_rdlntr (UNIT *uptr, t_mtrlnt *bc) | |
| { | |
| uint32 f = MT_GET_FMT (uptr); | |
| t_addr ppos; | |
| t_mtrlnt sbc; | |
| t_tpclnt tpcbc; | |
| MT_CLR_PNU (uptr); | |
| if ((uptr->flags & UNIT_ATT) == 0) return MTSE_UNATT; /* not attached? */ | |
| if (sim_tape_bot (uptr)) return MTSE_BOT; /* at BOT? */ | |
| switch (f) { /* switch on fmt */ | |
| case MTUF_F_STD: case MTUF_F_E11: | |
| sim_fseek (uptr->fileref, uptr->pos - sizeof (t_mtrlnt), SEEK_SET); | |
| sim_fread (bc, sizeof (t_mtrlnt), 1, uptr->fileref); /* read rec lnt */ | |
| sbc = MTR_L (*bc); | |
| if (ferror (uptr->fileref)) /* error? */ | |
| return sim_tape_ioerr (uptr); | |
| if (feof (uptr->fileref)) return MTSE_EOM; /* eof? */ | |
| uptr->pos = uptr->pos - sizeof (t_mtrlnt); /* spc over rec lnt */ | |
| if (*bc == MTR_EOM) return MTSE_EOM; /* eom? */ | |
| if (*bc == MTR_TMK) return MTSE_TMK; /* tape mark? */ | |
| uptr->pos = uptr->pos - sizeof (t_mtrlnt) - /* spc over record */ | |
| ((f == MTUF_F_STD)? ((sbc + 1) & ~1): sbc); | |
| sim_fseek (uptr->fileref, uptr->pos + sizeof (t_mtrlnt), SEEK_SET); | |
| break; | |
| case MTUF_F_TPC: | |
| ppos = sim_tape_tpc_fnd (uptr, uptr->filebuf); /* find prev rec */ | |
| sim_fseek (uptr->fileref, ppos, SEEK_SET); /* position */ | |
| sim_fread (&tpcbc, sizeof (t_tpclnt), 1, uptr->fileref); | |
| *bc = tpcbc; /* save rec lnt */ | |
| if (ferror (uptr->fileref)) /* error? */ | |
| return sim_tape_ioerr (uptr); | |
| if (feof (uptr->fileref)) return MTSE_EOM; /* eof? */ | |
| uptr->pos = ppos; /* spc over record */ | |
| if (*bc == MTR_TMK) return MTSE_TMK; /* tape mark? */ | |
| sim_fseek (uptr->fileref, uptr->pos + sizeof (t_tpclnt), SEEK_SET); | |
| break; | |
| default: | |
| return MTSE_FMT; } | |
| return MTSE_OK; | |
| } | |
| /* Read record forward | |
| Inputs: | |
| uptr = pointer to tape unit | |
| buf = pointer to buffer | |
| bc = pointer to returned record length | |
| max = maximum record size | |
| Outputs: | |
| status = operation status | |
| exit condition position | |
| unit unattached unchanged | |
| read error unchanged, PNU set | |
| end of file/medium unchanged, PNU set | |
| invalid record unchanged, PNU set | |
| tape mark updated | |
| data record updated | |
| data record error updated | |
| */ | |
| t_stat sim_tape_rdrecf (UNIT *uptr, uint8 *buf, t_mtrlnt *bc, t_mtrlnt max) | |
| { | |
| t_mtrlnt i, tbc, rbc; | |
| t_addr opos; | |
| t_stat st; | |
| opos = uptr->pos; /* old position */ | |
| if (st = sim_tape_rdlntf (uptr, &tbc)) return st; /* read rec lnt */ | |
| *bc = rbc = MTR_L (tbc); /* strip error flag */ | |
| if (rbc > max) { /* rec out of range? */ | |
| MT_SET_PNU (uptr); | |
| uptr->pos = opos; | |
| return MTSE_INVRL; } | |
| i = sim_fread (buf, sizeof (uint8), rbc, uptr->fileref); /* read record */ | |
| if (ferror (uptr->fileref)) { /* error? */ | |
| MT_SET_PNU (uptr); | |
| uptr->pos = opos; | |
| return sim_tape_ioerr (uptr); } | |
| for ( ; i < rbc; i++) buf[i] = 0; /* fill with 0's */ | |
| return (MTR_F (tbc)? MTSE_RECE: MTSE_OK); | |
| } | |
| /* Read record reverse | |
| Inputs: | |
| uptr = pointer to tape unit | |
| buf = pointer to buffer | |
| bc = pointer to returned record length | |
| max = maximum record size | |
| Outputs: | |
| status = operation status | |
| exit condition position | |
| unit unattached unchanged | |
| read error unchanged | |
| end of file unchanged | |
| end of medium updated | |
| invalid record unchanged | |
| tape mark updated | |
| data record updated | |
| data record error updated | |
| */ | |
| t_stat sim_tape_rdrecr (UNIT *uptr, uint8 *buf, t_mtrlnt *bc, t_mtrlnt max) | |
| { | |
| t_mtrlnt i, rbc, tbc; | |
| t_stat st; | |
| if (st = sim_tape_rdlntr (uptr, &tbc)) return st; /* read rec lnt */ | |
| *bc = rbc = MTR_L (tbc); /* strip error flag */ | |
| if (rbc > max) return MTSE_INVRL; /* rec out of range? */ | |
| i = sim_fread (buf, sizeof (uint8), rbc, uptr->fileref); /* read record */ | |
| if (ferror (uptr->fileref)) /* error? */ | |
| return sim_tape_ioerr (uptr); | |
| for ( ; i < rbc; i++) buf[i] = 0; /* fill with 0's */ | |
| return (MTR_F (tbc)? MTSE_RECE: MTSE_OK); | |
| } | |
| /* Write record forward | |
| Inputs: | |
| uptr = pointer to tape unit | |
| buf = pointer to buffer | |
| bc = record length | |
| Outputs: | |
| status = operation status | |
| exit condition position | |
| unit unattached unchanged | |
| write protect unchanged | |
| write error unchanged, PNU set | |
| data record updated | |
| */ | |
| t_stat sim_tape_wrrecf (UNIT *uptr, uint8 *buf, t_mtrlnt bc) | |
| { | |
| uint32 f = MT_GET_FMT (uptr); | |
| t_mtrlnt sbc; | |
| MT_CLR_PNU (uptr); | |
| if ((uptr->flags & UNIT_ATT) == 0) return MTSE_UNATT; /* not attached? */ | |
| if (sim_tape_wrp (uptr)) return MTSE_WRP; /* write prot? */ | |
| if (f == MTUF_F_STD) sbc = (bc + 1) & ~1; | |
| else sbc = bc; | |
| sim_fseek (uptr->fileref, uptr->pos, SEEK_SET); /* set pos */ | |
| sim_fwrite (&bc, sizeof (t_mtrlnt), 1, uptr->fileref); | |
| sim_fwrite (buf, sizeof (uint8), sbc, uptr->fileref); | |
| sim_fwrite (&bc, sizeof (t_mtrlnt), 1, uptr->fileref); | |
| if (ferror (uptr->fileref)) { /* error? */ | |
| MT_SET_PNU (uptr); | |
| return sim_tape_ioerr (uptr); } | |
| uptr->pos = uptr->pos + sbc + (2 * sizeof (t_mtrlnt)); /* move tape */ | |
| return MTSE_OK; | |
| } | |
| /* Write metadata forward (internal routine) */ | |
| t_stat sim_tape_wrdata (UNIT *uptr, uint32 dat) | |
| { | |
| MT_CLR_PNU (uptr); | |
| if ((uptr->flags & UNIT_ATT) == 0) return MTSE_UNATT; /* not attached? */ | |
| if (sim_tape_wrp (uptr)) return MTSE_WRP; /* write prot? */ | |
| sim_fseek (uptr->fileref, uptr->pos, SEEK_SET); /* set pos */ | |
| sim_fwrite (&dat, sizeof (t_mtrlnt), 1, uptr->fileref); | |
| if (ferror (uptr->fileref)) { /* error? */ | |
| MT_SET_PNU (uptr); | |
| return sim_tape_ioerr (uptr); } | |
| uptr->pos = uptr->pos + sizeof (t_mtrlnt); /* move tape */ | |
| return MTSE_OK; | |
| } | |
| /* Write tape mark */ | |
| t_stat sim_tape_wrtmk (UNIT *uptr) | |
| { | |
| return sim_tape_wrdata (uptr, MTR_TMK); | |
| } | |
| /* Write end of medium */ | |
| t_stat sim_tape_wreom (UNIT *uptr) | |
| { | |
| return sim_tape_wrdata (uptr, MTR_EOM); | |
| } | |
| /* Space record forward */ | |
| t_stat sim_tape_sprecf (UNIT *uptr, t_mtrlnt *bc) | |
| { | |
| t_stat st; | |
| st = sim_tape_rdlntf (uptr, bc); /* get record length */ | |
| *bc = MTR_L (*bc); | |
| return st; | |
| } | |
| /* Space record reverse */ | |
| t_stat sim_tape_sprecr (UNIT *uptr, t_mtrlnt *bc) | |
| { | |
| t_stat st; | |
| if (MT_TST_PNU (uptr)) { | |
| MT_CLR_PNU (uptr); | |
| *bc = 0; | |
| return SCPE_OK; } | |
| st = sim_tape_rdlntr (uptr, bc); /* get record length */ | |
| *bc = MTR_L (*bc); | |
| return st; | |
| } | |
| /* Rewind tape */ | |
| t_stat sim_tape_rewind (UNIT *uptr) | |
| { | |
| uptr->pos = 0; | |
| MT_CLR_PNU (uptr); | |
| return MTSE_OK; | |
| } | |
| /* Reset tape */ | |
| t_stat sim_tape_reset (UNIT *uptr) | |
| { | |
| MT_CLR_PNU (uptr); | |
| return SCPE_OK; | |
| } | |
| /* Test for BOT */ | |
| t_bool sim_tape_bot (UNIT *uptr) | |
| { | |
| uint32 f = MT_GET_FMT (uptr); | |
| return (uptr->pos <= fmts[f].bot)? TRUE: FALSE; | |
| } | |
| /* Test for end of tape */ | |
| t_bool sim_tape_eot (UNIT *uptr, t_addr cap) | |
| { | |
| return (uptr->pos > cap)? TRUE: FALSE; | |
| } | |
| /* Test for write protect */ | |
| t_bool sim_tape_wrp (UNIT *uptr) | |
| { | |
| return (uptr->flags & MTUF_WRP)? TRUE: FALSE; | |
| } | |
| /* Process I/O error */ | |
| t_stat sim_tape_ioerr (UNIT *uptr) | |
| { | |
| perror ("Magtape library I/O error"); | |
| clearerr (uptr->fileref); | |
| return SCPE_IOERR; | |
| } | |
| /* Set tape format */ | |
| t_stat sim_tape_set_fmt (UNIT *uptr, int32 val, char *cptr, void *desc) | |
| { | |
| int32 f; | |
| if (uptr == NULL) return SCPE_IERR; | |
| if (cptr == NULL) return SCPE_ARG; | |
| for (f = 0; f < MTUF_N_FMT; f++) { | |
| if (fmts[f].name && (strcmp (cptr, fmts[f].name) == 0)) { | |
| uptr->flags = (uptr->flags & ~MTUF_FMT) | | |
| (f << MTUF_V_FMT) | fmts[f].uflags; | |
| return SCPE_OK; } } | |
| return SCPE_ARG; | |
| } | |
| /* Show tape format */ | |
| t_stat sim_tape_show_fmt (FILE *st, UNIT *uptr, int32 val, void *desc) | |
| { | |
| int32 f = MT_GET_FMT (uptr); | |
| if (fmts[f].name) fprintf (st, "%s format", fmts[f].name); | |
| else fprintf (st, "invalid format"); | |
| return SCPE_OK; | |
| } | |
| /* Map a TPC format tape image */ | |
| uint32 sim_tape_tpc_map (UNIT *uptr, t_addr *map) | |
| { | |
| t_addr tpos; | |
| t_tpclnt bc; | |
| uint32 i, objc; | |
| if ((uptr == NULL) || (uptr->fileref == NULL)) return 0; | |
| for (objc = 0, tpos = 0;; ) { | |
| sim_fseek (uptr->fileref, tpos, SEEK_SET); | |
| i = sim_fread (&bc, sizeof (t_tpclnt), 1, uptr->fileref); | |
| if (i == 0) break; | |
| if (map) map[objc] = tpos; | |
| objc++; | |
| tpos = tpos + ((bc + 1) & ~1) + sizeof (t_tpclnt); } | |
| if (map) map[objc] = tpos; | |
| return objc; | |
| } | |
| /* Find the preceding record in a TPC file */ | |
| t_addr sim_tape_tpc_fnd (UNIT *uptr, t_addr *map) | |
| { | |
| uint32 lo, hi, p; | |
| if (map == NULL) return 0; | |
| lo = 0; | |
| hi = uptr->hwmark - 1; | |
| do { p = (lo + hi) >> 1; | |
| if (uptr->pos == map[p]) | |
| return ((p == 0)? map[p]: map[p - 1]); | |
| else if (uptr->pos < map[p]) hi = p - 1; | |
| else lo = p + 1; } | |
| while (lo <= hi); | |
| return ((p == 0)? map[p]: map[p - 1]); | |
| } |