From afb5d6277d8d1ab4052543e1963bb8a052b1f800 Mon Sep 17 00:00:00 2001 From: Mark Pizzolato Date: Mon, 22 Dec 2014 05:48:13 -0800 Subject: [PATCH] TAPE: Extensive cleanup of tape operations (gap, end of medium, format change, etc.) from Dave Bryan 256. ENHANCEMENT: Add tape runaway support to the simulator tape library. OBSERVATION: The ANSI specifications for NRZI, PE, and GCR tape recording mandate a maximum length of 25 feet for erase gaps. Currently, an erase gap of any length is ignored when reading or spacing. To allow detection of non-compliant tape images, the simulator tape library is enhanced to halt positioning and return tape runaway status if a gap of 25 feet or more is encountered. Runaway detection is enabled by calling the tape library to set the tape density in bits per inch. If this call is not made, erase gaps present in a tape image are effectively ignored. Also, with the addition of a separate "set density" call, it is no longer necessary to supply the density when writing erase gaps. RESOLUTION: Modify "sim_tape_rdlntf" and "sim_tape_rdlntr" (sim_tape.c) to detect tape runaway, and add a new MTSE_RUNAWAY status to sim_tape.h. Add new "sim_tape_set_dens" and "sim_tape_show_dens" functions to set and show the bits per inch for a unit, respectively, and eliminate the "bpi" parameter to "sim_tape_wrgap" in preference to using the density established by a previous "sim_tape_set_dens" call. Add named constants to "sim_tape.h" that specify the density. 257. ENHANCEMENT: Improve performance when reading or spacing over erase gaps. OBSERVATION: Performance when reading or spacing over erase gaps is poor, especially in the reverse direction. Currently, each 4-byte gap marker is read individually, and in the reverse direction, each read is preceded by a seek to move the file pointer backward. This combination causes stream cache invalidation and a physical disc access for each gap marker. As a single gap consists of over 1000 markers, performance is far worse than if a gap was read as a block. RESOLUTION: Modify "sim_tape_rdlntf" and "sim_tape_rdlntr" (sim_tape.c) to buffer reads of gap markers. Using a 128-element buffer, performance improves about thirty-fold. 258. PROBLEM: Writing an end-of-medium positions the tape image after the mark. OBSERVATION: The "sim_tape_wreom" simulator tape library function writes an end-of-medium marker on the tape image. The intent is to erase the remainder of the tape. The "SIMH Magtape Representation and Handling" document states that the tape position is not updated by this function. However, the function leaves the tape positioned after the marker. A subsequent read would stop at the EOM marker. However, writing a new marker over that one would then allow reading of the data following the EOM that supposedly had been erased by the original "sim_tape_wreom" call. CAUSE: The tape position is updated by the internal "sim_tape_wrdata" call that is used to write the EOM marker, but it is not reset afterward by the function. RESOLUTION: Modify "sim_tape_wreom" (sim_tape.c) to reset the tape position to point at the EOM marker before returning. This prevents reading past an EOM marker, and a subsequent write will overwrite the marker rather than embed it between data records. 259. PROBLEM: Reading through an erase gap in reverse may return EOM status. OBSERVATION: A reverse read or spacing operation through an erase gap may return end-of-medium status. Reading or spacing forward through the same gap works properly. CAUSE: Writing an erase gap over existing records may produce a gap that is longer than requested. This occurs when truncating the last record to be overlaid by the gap would leave a record that is shorter than the minimum size allowed (eight bytes for the length words plus two bytes for the data). In this case, the gap is lengthened to overlay the entire record. If the new gap size is not evenly divisible by four, a half-gap is metadata marker of value 0xFFFF added to the beginning of the gap. If a gap that begins with a half-gap marker is written immediately after a previous gap, the "seam" between gaps will contain the bytes FE FF FF FF ( FF FF ) FE FF FF FF.... Reading forward across this seam will yield a metadata value of 0xFFFEFFFF, which is recognized and handled by seeking two bytes back to resynchronize reading. However, reading in reverse will yield the value 0xFFFFFFFF, which is interpreted as end-of-medium. RESOLUTION: Modify "sim_tape_rdlntr" (sim_tape.c) to recognize 0xFFFFFFFF as a half-gap marker and resynchronize in response. End of medium cannot occur when reading in reverse, as it is impossible to position the tape image beyond an EOM marker. Therefore, any 0xFFFFFFFF value encountered must be a half-gap "seam" originating as above. 260. PROBLEM: sim_tape_wrgap fails when format is changed from SIMH format. OBSERVATION: The HP 2100 magnetic tape simulator supports erase gaps and calls sim_tape_wrgap when commanded to write a gap. However, if a tape format other than SIMH format is selected, the call fails with MTSE_FMT. CAUSE: Erase gaps are not supported in formats other than SIMH, but the call should not fail. Instead, the call should be a "no-operation" if the underlying format does not support gaps. RESOLUTION: Modify "sim_tape_wrgap" (sim_tape.c) to return MTSE_OK with no action performed if a tape format other than SIMH is selected. 261. PROBLEM: The magnetic tape format of an attached unit may be changed. OBSERVATION: The magnetic tape library supports several tape image formats. The format to use may be specified either by an "ATTACH -F" command or by a "SET FORMAT" command. The latter calls the "sim_tape_set_fmt" function, which allows the format of a file currently attached to be changed. However, the format is an intrinsic property of the tape image file, so changing it once the file has been attached makes no sense. CAUSE: Oversight. RESOLUTION: Modify "sim_tape_set_fmt" (sim_tape.c) to return an error (SCPE_ALATT, "Unit already attached") if the unit is attached. --- sim_defs.h | 2 + sim_tape.c | 510 ++++++++++++++++++++++++++++++++++++++++++----------- sim_tape.h | 27 ++- 3 files changed, 435 insertions(+), 104 deletions(-) diff --git a/sim_defs.h b/sim_defs.h index 7cd4c51a..9a8597e2 100644 --- a/sim_defs.h +++ b/sim_defs.h @@ -530,6 +530,8 @@ struct sim_unit { #define UNIT_ATTMULT 0000001 /* Allow multiple attach commands */ #define UNIT_TM_POLL 0000002 /* TMXR Polling unit */ #define UNIT_NO_FIO 0000004 /* fileref is NOT a FILE * */ +#define UNIT_V_DF_TAPE 3 /* Bit offset for Tape Density reservation */ +#define UNIT_S_DF_TAPE 3 /* Bits Reserved for Tape Density */ struct sim_bitfield { char *name; /* field name */ diff --git a/sim_tape.c b/sim_tape.c index 3cb018e1..c495d27b 100644 --- a/sim_tape.c +++ b/sim_tape.c @@ -82,6 +82,8 @@ sim_tape_show_fmt show tape format sim_tape_set_capac set tape capacity sim_tape_show_capac show tape capacity + sim_tape_set_dens set tape density + sim_tape_show_dens show tape density sim_tape_set_async enable asynchronous operation sim_tape_clr_async disable asynchronous operation */ @@ -109,6 +111,17 @@ static struct sim_tape_fmt fmts[MTUF_N_FMT] = { { NULL, 0, 0 } }; +static const uint32 bpi [] = { /* tape density table, indexed by MT_DENS constants */ + 0, /* 0 = MT_DENS_NONE -- density not set */ + 200, /* 1 = MT_DENS_200 -- 200 bpi NRZI */ + 556, /* 2 = MT_DENS_556 -- 556 bpi NRZI */ + 800, /* 3 = MT_DENS_800 -- 800 bpi NRZI */ + 1600, /* 4 = MT_DENS_1600 -- 1600 bpi PE */ + 6250 /* 5 = MT_DENS_6250 -- 6250 bpi GCR */ + }; + +#define BPI_COUNT (sizeof (bpi) / sizeof (bpi [0])) /* count of density table entries */ + 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); @@ -238,7 +251,7 @@ struct tape_context *ctx = (struct tape_context *)uptr->tape_ctx; ctx->io_status = sim_tape_wreomrw (uptr); break; case TOP_WGAP: - ctx->io_status = sim_tape_wrgap (uptr, ctx->gaplen, ctx->bpi); + ctx->io_status = sim_tape_wrgap (uptr, ctx->gaplen); break; case TOP_SPRF: ctx->io_status = sim_tape_sprecf (uptr, ctx->bc); @@ -561,15 +574,73 @@ if (sim_deb && (ctx->dptr->dctrl & reason)) Outputs: status = operation status - exit condition position - + exit condition tape position + ------------------ ------------------------------------------- unit unattached unchanged read error unchanged, PNU set end of file/medium unchanged, PNU set tape mark updated + tape runaway updated data record updated, sim_fread will read record forward - See notes at "sim_tape_wrgap" regarding erase gap implementation. + This routine is called to set up a record read or spacing in the forward + direction. On return, status is MTSE_OK and the tape is positioned at the + first data byte if a record was encountered, or status is an MTSE error code + giving the reason that the operation did not succeed and the tape position is + as indicated above. + + The ANSI standards for magnetic tape recording (X3.32, X3.39, and X3.54) and + the equivalent ECMA standard (ECMA-62) specify a maximum erase gap length of + 25 feet (7.6 meters). While gaps of any length may be written, gaps longer + than this are non-standard and may indicate that an unrecorded or erased tape + is being read. + + If the tape density has been set via a previous "sim_tape_set_dens" call, + then the length is monitored when skipping over erase gaps. If the length + reaches 25 feet, motion is terminated, and MTSE_RUNAWAY status is returned. + Runaway status is also returned if an end-of-medium marker or the physical + end of file is encountered while spacing over a gap. + + If the density has not been set, then a gap of any length is skipped, and + MTSE_RUNAWAY status is never returned. In effect, erase gaps present in the + tape image file will be transparent to the caller. + + Erase gaps are currently supported only in SIMH (MTUF_F_STD) tape format. + Because gaps may be partially overwritten with data records, gap metadata + must be examined marker-by-marker. To reduce the number of file read calls, + a buffer of metadata elements is used. The buffer size is initially + established at 256 elements but may be set to any size desired. To avoid a + large read for the typical case where an erase gap is not present, the first + read is of a single metadatum marker. If that is a gap marker, then + additional buffered reads are performed. + + See the notes at "sim_tape_wrgap" regarding the erase gap implementation. + + Implementation notes: + + 1. For programming convenience, erase gap processing is performed for both + SIMH standard and E11 tape formats, although the latter will never + contain erase gaps, as the "sim_tape_wrgap" call takes no action for the + E11 format. + + 2. The "feof" call cannot return a non-zero value on the first pass through + the loop, because the "sim_fseek" call resets the internal end-of-file + indicator. Subsequent passes only occur if an erase gap is present, so + a non-zero return indicates an EOF was seen while reading through a gap. + + 3. The dynamic start/stop test of the HP 3000 magnetic tape diagnostic + heavily exercises the erase gap scanning code. Sample test execution + times for various buffer sizes on a 2 GHz host platform are: + + buffer size execution time + (elements) (CPU seconds) + ----------- -------------- + 1 7200 + 32 783 + 128 237 + 256 203 + 512 186 + 1024 171 */ t_stat sim_tape_rdlntf (UNIT *uptr, t_mtrlnt *bc) @@ -580,42 +651,114 @@ t_bool all_eof; uint32 f = MT_GET_FMT (uptr); t_mtrlnt sbc; t_tpclnt tpcbc; +t_mtrlnt buffer [256]; /* local tape buffer */ +uint32 bufcntr, bufcap; /* buffer counter and capacity */ +int32 runaway_counter, sizeof_gap; /* bytes remaining before runaway and bytes per gap */ t_stat r = MTSE_OK; -MT_CLR_PNU (uptr); -if ((uptr->flags & UNIT_ATT) == 0) /* not attached? */ - return MTSE_UNATT; -sim_fseek (uptr->fileref, uptr->pos, SEEK_SET); /* set tape pos */ -switch (f) { /* switch on fmt */ +MT_CLR_PNU (uptr); /* clear the position-not-updated flag */ - case MTUF_F_STD: case MTUF_F_E11: - do { - 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 */ - r = MTSE_EOM; - break; - } - uptr->pos = uptr->pos + sizeof (t_mtrlnt); /* spc over rec lnt */ - if (*bc == MTR_TMK) { /* tape mark? */ - r = MTSE_TMK; - break; - } - if (*bc == MTR_FHGAP) { /* half gap? */ - uptr->pos = uptr->pos + sizeof (t_mtrlnt) / 2; /* half space fwd */ - sim_fseek (uptr->fileref, uptr->pos, SEEK_SET); /* resync */ - } - else if (*bc != MTR_GAP) - uptr->pos = uptr->pos + sizeof (t_mtrlnt) + /* spc over record */ - ((f == MTUF_F_STD)? ((sbc + 1) & ~1): sbc); +if ((uptr->flags & UNIT_ATT) == 0) /* if the unit is not attached */ + return MTSE_UNATT; /* then quit with an error */ + +sim_fseek (uptr->fileref, uptr->pos, SEEK_SET); /* set the initial tape position */ + +switch (f) { /* the read method depends on the tape format */ + + case MTUF_F_STD: + case MTUF_F_E11: + runaway_counter = 25 * 12 * bpi [MT_DENS (uptr->dynflags)]; /* set the largest legal gap size in bytes */ + + if (runaway_counter == 0) { /* if tape density has not been not set */ + sizeof_gap = 0; /* then disable runaway detection */ + runaway_counter = INT_MAX; /* to allow gaps of any size */ } - while ((*bc == MTR_GAP) || (*bc == MTR_FHGAP)); - break; + else /* otherwise */ + sizeof_gap = sizeof (t_mtrlnt); /* set the size of the gap */ + + bufcntr = 0; /* force an initial read */ + bufcap = 0; /* but of just one metadata marker */ + + do { /* loop until a record, gap, or error seen */ + if (bufcntr == bufcap) { /* if the buffer is empty then refill it */ + if (feof (uptr->fileref)) { /* if we hit the EOF while reading gaps */ + if (sizeof_gap > 0) /* then if detection is enabled */ + r = MTSE_RUNAWAY; /* then report a tape runaway */ + else /* otherwise report the physical EOF */ + r = MTSE_EOM; /* as the end-of-medium */ + break; + } + + else if (bufcap == 0) /* otherwise if this is the initial read */ + bufcap = 1; /* then start with just one marker */ + + else /* otherwise reset the capacity */ + bufcap = sizeof (buffer) /* to the full size of the buffer */ + / sizeof (buffer [0]); + + bufcap = sim_fread (buffer, /* fill the buffer */ + sizeof (t_mtrlnt), /* with tape metadata */ + bufcap, + uptr->fileref); + + if (ferror (uptr->fileref)) { /* if a file I/O error occurred */ + MT_SET_PNU (uptr); /* then set position not updated */ + r = sim_tape_ioerr (uptr); /* report the error and quit */ + break; + } + + else if (bufcap == 0) { /* otherwise if nothing was read */ + MT_SET_PNU (uptr); /* then set position not updated */ + r = MTSE_EOM; /* report the end of medium and quit */ + break; + } + + else /* otherwise reset the index */ + bufcntr = 0; /* to the start of the buffer */ + } + + *bc = buffer [bufcntr++]; /* store the metadata marker value */ + + if (*bc == MTR_EOM) { /* if an end-of-medium marker is seen */ + MT_SET_PNU (uptr); /* then set position not updated */ + r = MTSE_EOM; /* report the end of medium and quit */ + break; + } + + uptr->pos = uptr->pos + sizeof (t_mtrlnt); /* space over the marker */ + + if (*bc == MTR_TMK) { /* if the value is a tape mark */ + r = MTSE_TMK; /* then quit with tape mark status */ + break; + } + + else if (*bc == MTR_GAP) /* otherwise if the value is a full gap */ + runaway_counter -= sizeof_gap; /* then decrement the gap counter */ + + else if (*bc == MTR_FHGAP) { /* otherwise if the value if a half gap */ + uptr->pos = uptr->pos - sizeof (t_mtrlnt) / 2; /* then back up */ + sim_fseek (uptr->fileref, uptr->pos, SEEK_SET); /* to resync */ + bufcntr = bufcap; /* mark the buffer as invalid to force a read */ + + *bc = MTR_GAP; /* reset the marker */ + runaway_counter -= sizeof_gap / 2; /* and decrement the gap counter */ + } + + else { /* otherwise it's a record marker */ + if (bufcntr < bufcap) /* if the position is within the buffer */ + sim_fseek (uptr->fileref, uptr->pos, SEEK_SET); /* then seek to the data area */ + + sbc = MTR_L (*bc); /* extract the record length */ + uptr->pos = uptr->pos + sizeof (t_mtrlnt) /* position to the start */ + + (f == MTUF_F_STD ? (sbc + 1) & ~1 : sbc); /* of the record */ + } + } + while (*bc == MTR_GAP && runaway_counter > 0); /* continue until data or runaway occurs */ + + if (r == MTSE_OK && runaway_counter <= 0) /* if a tape runaway occurred */ + r = MTSE_RUNAWAY; /* then report it */ + + break; /* otherwise the operation succeeded */ case MTUF_F_TPC: sim_fread (&tpcbc, sizeof (t_tpclnt), 1, uptr->fileref); @@ -674,17 +817,25 @@ return r; Outputs: status = operation status - exit condition position - + exit condition tape position + ------------------ ------------------------------------------- unit unattached unchanged beginning of tape unchanged read error unchanged end of file unchanged end of medium updated tape mark updated + tape runaway updated data record updated, sim_fread will read record forward - See notes at "sim_tape_wrgap" regarding erase gap implementation. + This routine is called to set up a record read or spacing in the reverse + direction. On return, status is MTSE_OK and the tape is positioned at the + first data byte if a record was encountered, or status is an MTSE error code + giving the reason that the operation did not succeed and the tape position is + as indicated above. + + See the notes at "sim_tape_rdlntf" and "sim_tape_wrgap" regarding tape + runaway and the erase gap implementation, respectively. */ t_stat sim_tape_rdlntr (UNIT *uptr, t_mtrlnt *bc) @@ -696,51 +847,99 @@ uint32 f = MT_GET_FMT (uptr); t_addr ppos; t_mtrlnt sbc; t_tpclnt tpcbc; +t_mtrlnt buffer [256]; /* local tape buffer */ +uint32 bufcntr, bufcap; /* buffer counter and capacity */ +int32 runaway_counter, sizeof_gap; /* bytes remaining before runaway and bytes per gap */ t_stat r = MTSE_OK; -MT_CLR_PNU (uptr); -if ((uptr->flags & UNIT_ATT) == 0) /* not attached? */ - return MTSE_UNATT; -if (sim_tape_bot (uptr)) /* at BOT? */ - return MTSE_BOT; -switch (f) { /* switch on fmt */ +MT_CLR_PNU (uptr); /* clear the position-not-updated flag */ - case MTUF_F_STD: case MTUF_F_E11: - do { - 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)) { /* eof? */ - r = MTSE_EOM; +if ((uptr->flags & UNIT_ATT) == 0) /* if the unit is not attached */ + return MTSE_UNATT; /* then quit with an error */ + +if (sim_tape_bot (uptr)) /* if the unit is positioned at the BOT */ + return MTSE_BOT; /* then reading backward is not possible */ + +switch (f) { /* the read method depends on the tape format */ + + case MTUF_F_STD: + case MTUF_F_E11: + runaway_counter = 25 * 12 * bpi [MT_DENS (uptr->dynflags)]; /* set largest legal gap size in bytes */ + + if (runaway_counter == 0) { /* if tape density has not been not set */ + sizeof_gap = 0; /* then disable runaway detection */ + runaway_counter = INT_MAX; /* to allow gaps of any size */ + } + + else /* otherwise */ + sizeof_gap = sizeof (t_mtrlnt); /* set the size of the gap */ + + bufcntr = 0; /* force an initial read */ + bufcap = 1; /* but of just one metadata marker */ + + do { /* loop until a record, gap, or error seen */ + if (bufcntr == 0) { /* if the buffer is empty then refill it */ + if (sim_tape_bot (uptr)) { /* if the search has backed into the BOT */ + r = MTSE_BOT; /* then quit with an error */ + break; + } + + else if (uptr->pos < bufcap * sizeof (t_mtrlnt)) /* if less than a full buffer remains */ + bufcap = (uint32)(uptr->pos / sizeof (t_mtrlnt));/* then reduce the capacity accordingly */ + + sim_fseek (uptr->fileref, /* seek back to the location */ + uptr->pos - bufcap * sizeof (t_mtrlnt), /* corresponding to the start */ + SEEK_SET); /* of the buffer */ + + bufcntr = sim_fread (buffer, sizeof (t_mtrlnt), /* fill the buffer */ + bufcap, uptr->fileref); /* with tape metadata */ + + if (ferror (uptr->fileref)) { /* if a file I/O error occurred */ + MT_SET_PNU (uptr); /* then set position not updated */ + r = sim_tape_ioerr (uptr); /* report the error and quit */ + break; + } + + else /* otherwise reset the capacity */ + bufcap = sizeof (buffer) /* to the full size of the buffer */ + / sizeof (buffer [0]); + } + + *bc = buffer [--bufcntr]; /* store the metadata marker value */ + + uptr->pos = uptr->pos - sizeof (t_mtrlnt); /* backspace over the marker */ + + if (*bc == MTR_TMK) { /* if the marker is a tape mark */ + r = MTSE_TMK; /* then quit with tape mark status */ break; } - uptr->pos = uptr->pos - sizeof (t_mtrlnt); /* spc over rec lnt */ - if (*bc == MTR_EOM) { /* eom? */ - r = MTSE_EOM; - break; + + else if (*bc == MTR_GAP) /* otherwise if the marker is a full gap */ + runaway_counter -= sizeof_gap; /* then decrement the gap counter */ + + else if ((*bc & MTR_M_RHGAP) == MTR_RHGAP /* otherwise if the marker */ + || *bc == MTR_RRGAP) { /* is a half gap */ + uptr->pos = uptr->pos + sizeof (t_mtrlnt) / 2; /* then position forward to resync */ + bufcntr = 0; /* mark the buffer as invalid to force a read */ + + *bc = MTR_GAP; /* reset the marker */ + runaway_counter -= sizeof_gap / 2; /* and decrement the gap counter */ } - if (*bc == MTR_TMK) { /* tape mark? */ - return MTSE_TMK; - break; + + else { /* otherwise it's a record marker */ + sbc = MTR_L (*bc); /* extract the record length */ + uptr->pos = uptr->pos - sizeof (t_mtrlnt) /* position to the start */ + - (f == MTUF_F_STD ? (sbc + 1) & ~1 : sbc); /* of the record */ + sim_fseek (uptr->fileref, /* seek to the data area */ + uptr->pos + sizeof (t_mtrlnt), SEEK_SET); } - if ((*bc & MTR_M_RHGAP) == MTR_RHGAP) { /* half gap? */ - uptr->pos = uptr->pos + sizeof (t_mtrlnt) / 2; /* half space rev */ - sim_fseek (uptr->fileref, uptr->pos, SEEK_SET); /* resync */ - } - else if (*bc != MTR_GAP) { - 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); - } - else if (sim_tape_bot (uptr)) { /* backed into BOT? */ - r = MTSE_BOT; - break; - } - } - while ((*bc == MTR_GAP) || (*bc == MTR_RHGAP)); - break; + } + while (*bc == MTR_GAP && runaway_counter > 0); /* continue until data or runaway occurs */ + + if (r == MTSE_OK && runaway_counter <= 0) /* if a tape runaway occurred */ + r = MTSE_RUNAWAY; /* then report it */ + + break; /* otherwise the operation succeeded */ case MTUF_F_TPC: ppos = sim_tape_tpc_fnd (uptr, (t_addr *) uptr->filebuf); /* find prev rec */ @@ -1033,12 +1232,19 @@ return r; t_stat sim_tape_wreom (UNIT *uptr) { +t_stat result; struct tape_context *ctx = (struct tape_context *)uptr->tape_ctx; sim_debug (ctx->dbit, ctx->dptr, "sim_tape_wreom(unit=%d)\n", uptr-ctx->dptr->units); if (MT_GET_FMT (uptr) == MTUF_F_P7B) /* cant do P7B */ return MTSE_FMT; -return sim_tape_wrdata (uptr, MTR_EOM); + +result = sim_tape_wrdata (uptr, MTR_EOM); /* write the EOM marker */ + +uptr->pos = uptr->pos - sizeof (t_mtrlnt); /* restore original tape position */ +MT_SET_PNU (uptr); /* indicate that position was not updated */ + +return result; } t_stat sim_tape_wreom_a (UNIT *uptr, TAPE_PCALLBACK callback) @@ -1080,7 +1286,6 @@ return r; Inputs: uptr = pointer to tape unit gaplen = length of gap in tenths of an inch - bpi = current recording density in bytes per inch Outputs: status = operation status @@ -1099,8 +1304,8 @@ return r; value. This value is chosen so that it is still recognizable even if it has been "cut in half" by a subsequent data overwrite that does not end on a metadatum-sized boundary. In addition, a range of metadata values are - reserved for detection in the reverse direction. Erase gaps are supported - only in SIMH tape format. + reserved for detection in the reverse direction. Erase gaps are currently + supported only in SIMH (MTUF_F_STD) tape format. This implementation supports erasing gaps in the middle of a populated tape image and will always produce a valid image. It also produces valid images @@ -1115,8 +1320,8 @@ return r; problematic if the density setting is not the same as that used during recording. There is no way to establish a gap of a certain length unequivocally in an image file, so this implementation establishes a gap of a - certain number of bytes that reflect the desired gap length at the bpi used - during writing. + certain number of bytes that reflect the desired gap length at the tape + density in bits per inch used during writing. To write an erase gap, the implementation uses one of two approaches, depending on whether or not the current tape position is at EOM. Erasing at @@ -1131,12 +1336,14 @@ return r; Because the smallest legal tape record requires space for two metadata markers plus two data bytes, an erasure that would leave less than that is increased to consume the entire record. Otherwise, the final record is - truncated appropriately. + truncated appropriately by rewriting the leading and trailing length words + appropriately. When reading in either direction, gap metadata markers are ignored (skipped) until a record length header, EOF marker, EOM marker, or physical EOF is encountered. Thus, tape images containing gap metadata are transparent to - the calling simulator. + the calling simulator (unless tape runaway support is enabled -- see the + notes at "sim_tape_rdlntf" for details). The permissibility of data record lengths that are not multiples of the metadatum size presents a difficulty when reading. If such an "odd length" @@ -1162,31 +1369,50 @@ return r; 0xFFFEFFFF - reserved (indicates half-gap in forward reads) 0xFFFF0000:0xFFFF00FF - reserved (indicates half-gap in reverse reads) 0xFFFF8000:0xFFFF80FF - reserved (indicates half-gap in reverse reads) - */ -t_stat sim_tape_wrgap (UNIT *uptr, uint32 gaplen, uint32 bpi) + If the tape density has been set via a previous sim_tape_set_dens call, and + the tape format is set to SIMH format, then this routine will write a gap of + the appropriate size. If the density has not been set, then no action will + be taken, and either MTSE_IOERR or MTSE_OK status will be returned, depending + on whether SIMH or another format is selected, respectively. A simulator + that calls this routine must set the density beforehand; failure to do so is + an error. However, calling while another format is enabled is OK and is + treated as a no-operation. This allows a device simulator that supports + writing erase gaps to use the same code without worrying about the tape + format currently selected by the user. +*/ + +t_stat sim_tape_wrgap (UNIT *uptr, uint32 gaplen) { struct tape_context *ctx = (struct tape_context *)uptr->tape_ctx; t_stat st; t_mtrlnt meta, sbc, new_len, rec_size; t_addr gap_pos = uptr->pos; -uint32 file_size, marker_count; -uint32 format = MT_GET_FMT (uptr); -uint32 gap_alloc = 0; /* gap allocated from tape */ -int32 gap_needed = (gaplen * bpi) / 10; /* gap remainder still needed */ +uint32 file_size, marker_count, tape_density; +int32 gap_needed; +uint32 gap_alloc = 0; /* gap currently allocated from the tape */ +const uint32 format = MT_GET_FMT (uptr); /* tape format */ const uint32 meta_size = sizeof (t_mtrlnt); /* bytes per metadatum */ const uint32 min_rec_size = 2 + sizeof (t_mtrlnt) * 2; /* smallest data record size */ -sim_debug (ctx->dbit, ctx->dptr, "sim_tape_wrgap(unit=%d, gaplen=%p, bpi=%d)\n", uptr-ctx->dptr->units, gaplen, bpi); +sim_debug (ctx->dbit, ctx->dptr, "sim_tape_wrgap(unit=%d, gaplen=%p)\n", uptr-ctx->dptr->units, gaplen); MT_CLR_PNU (uptr); -if ((uptr->flags & UNIT_ATT) == 0) /* not attached? */ - return MTSE_UNATT; -if (format != MTUF_F_STD) /* not SIMH fmt? */ - return MTSE_FMT; -if (sim_tape_wrp (uptr)) /* write protected? */ - return MTSE_WRP; +if ((uptr->flags & UNIT_ATT) == 0) /* if the unit is not attached */ + return MTSE_UNATT; /* then we cannot proceed */ + +else if (sim_tape_wrp (uptr)) /* otherwise if the unit is write protected */ + return MTSE_WRP; /* then we cannot write */ + +tape_density = bpi [MT_DENS (uptr->dynflags)]; /* get the density of the tape */ + +if (format != MTUF_F_STD) /* if erase gaps aren't supported by the format */ + return MTSE_OK; /* then take no action */ +else if (tape_density == 0) /* otherwise if the density is not set */ + return MTSE_IOERR; /* then report an I/O error */ +else /* otherwise */ + gap_needed = (gaplen * tape_density) / 10; /* determine the gap size needed in bytes */ file_size = sim_fsize (uptr->fileref); /* get file size */ sim_fseek (uptr->fileref, uptr->pos, SEEK_SET); /* position tape */ @@ -1230,7 +1456,7 @@ do { gap_needed = gap_needed - meta_size / 2; /* reduce requirement */ } - else if (uptr->pos + + else if (uptr->pos + MTR_L (meta) + meta_size > file_size) { /* rec len out of range? */ gap_alloc = gap_alloc + gap_needed; /* presume overwritten tape */ gap_needed = 0; /* allocate remainder */ @@ -1304,12 +1530,12 @@ while (--marker_count > 0); return MTSE_OK; } -t_stat sim_tape_wrgap_a (UNIT *uptr, uint32 gaplen, uint32 bpi, TAPE_PCALLBACK callback) +t_stat sim_tape_wrgap_a (UNIT *uptr, uint32 gaplen, TAPE_PCALLBACK callback) { t_stat r = MTSE_OK; AIO_CALLSETUP - r = sim_tape_wrgap (uptr, gaplen, bpi); -AIO_CALL(TOP_RDRR, NULL, NULL, NULL, 0, 0, gaplen, bpi, NULL, callback); + r = sim_tape_wrgap (uptr, gaplen); +AIO_CALL(TOP_RDRR, NULL, NULL, NULL, 0, 0, gaplen, 0, NULL, callback); return r; } @@ -1834,6 +2060,8 @@ t_stat sim_tape_set_fmt (UNIT *uptr, int32 val, char *cptr, void *desc) { uint32 f; +if (uptr->flags & UNIT_ATT) + return SCPE_ALATT; if (uptr == NULL) return SCPE_IERR; if (cptr == NULL) @@ -1944,3 +2172,81 @@ else fprintf (st, "unlimited capacity"); return SCPE_OK; } + +/* Set the tape density. + + Set the density of the specified tape unit either to the value supplied or to + the value represented by the supplied character string. + + If "desc" is NULL, then "val" must be set to one of the MT_DENS_* constants + in sim_tape.h other than MT_DENS_NONE; the supplied value is used as the tape + density, and the character string is ignored. Otherwise, "desc" must point + at an int32 value containing a set of allowed densities constructed as a + bitwise OR of the appropriate MT_*_VALID values. In this case, the string + pointed to by "cptr" will be parsed for a decimal value corresponding to the + desired density in bits per inch and validated against the set of allowed + values. + + In either case, SCPE_ARG is returned if the density setting is not valid or + allowed. If the setting is OK, the new density is set into the unit + structure, and SCPE_OK is returned. +*/ + +t_stat sim_tape_set_dens (UNIT *uptr, int32 val, char *cptr, void *desc) +{ +uint32 density, new_bpi; +t_stat result = SCPE_OK; + +if (uptr == NULL) /* if the unit pointer is null */ + return SCPE_IERR; /* then the caller has screwed up */ + +else if (desc == NULL) /* otherwise if a validation set was not supplied */ + if (val > 0 && val < (int32) BPI_COUNT) /* then if a valid density code was supplied */ + uptr->dynflags = (uptr->dynflags & ~MTVF_DENS_MASK) /* then insert the code */ + | (val << UNIT_V_DF_TAPE); /* in the unit flags */ + else /* otherwise the code is invalid */ + return SCPE_ARG; /* so report a bad argument */ + +else { /* otherwise a validation set was supplied */ + if (cptr == NULL || *cptr == 0) /* but if no value is present */ + return SCPE_MISVAL; /* then report a missing value */ + + new_bpi = (uint32) get_uint (cptr, 10, UINT_MAX, &result); /* convert the string value */ + + if (result != SCPE_OK) /* if the conversion failed */ + result = SCPE_ARG; /* then report a bad argument */ + + else for (density = 0; density < BPI_COUNT; density++) /* otherwise validate the density */ + if (new_bpi == bpi [density] /* if it matches a value in the list */ + && ((1 << density) & *(int32 *) desc)) { /* and it's an allowed value */ + uptr->dynflags = (uptr->dynflags & ~MTVF_DENS_MASK) /* then store the index of the value */ + | density << UNIT_V_DF_TAPE; /* in the unit flags */ + return SCPE_OK; /* and return success */ + } + + result = SCPE_ARG; /* if no match, then report a bad argument */ + } + +return result; /* return the result of the operation */ +} + +/* Show the tape density */ + +t_stat sim_tape_show_dens (FILE *st, UNIT *uptr, int32 val, void *desc) +{ +uint32 tape_density; + +if (uptr == NULL) /* if the unit pointer is null */ + return SCPE_IERR; /* then the caller has screwed up */ + +else { /* otherwise get the density */ + tape_density = bpi [MT_DENS (uptr->dynflags)]; /* of the tape from the unit flags */ + + if (tape_density) /* if it's set */ + fprintf (st, "density=%d bpi", tape_density); /* then report it */ + else /* otherwise */ + fprintf (st, "density not set"); /* it was never set by the caller */ + } + +return SCPE_OK; +} diff --git a/sim_tape.h b/sim_tape.h index bd4bb607..920feda8 100644 --- a/sim_tape.h +++ b/sim_tape.h @@ -41,6 +41,7 @@ typedef uint32 t_mtrlnt; /* magtape rec lnt */ #define MTR_TMK 0x00000000 /* tape mark */ #define MTR_EOM 0xFFFFFFFF /* end of medium */ #define MTR_GAP 0xFFFFFFFE /* primary gap */ +#define MTR_RRGAP 0xFFFFFFFF /* reverse read half gap */ #define MTR_FHGAP 0xFFFEFFFF /* fwd half gap (overwrite) */ #define MTR_RHGAP 0xFFFF0000 /* rev half gap (overwrite) */ #define MTR_M_RHGAP (~0x000080FF) /* range mask for rev gap */ @@ -103,6 +104,25 @@ typedef uint16 t_tpclnt; /* magtape rec lnt */ #define MTPOS_V_DLE 4 #define MTPOS_M_DLE (1u << MTPOS_V_DLE) /* Detect LEOT */ +/* Tape density values */ + +#define MT_DENS_NONE 0 /* density not set */ +#define MT_DENS_200 1 /* 200 bpi NRZI */ +#define MT_DENS_556 2 /* 556 bpi NRZI */ +#define MT_DENS_800 3 /* 800 bpi NRZI */ +#define MT_DENS_1600 4 /* 1600 bpi PE */ +#define MT_DENS_6250 5 /* 6250 bpi GCR */ + +#define MTVF_DENS_MASK (((1u << UNIT_S_DF_TAPE) - 1) << UNIT_V_DF_TAPE) +#define MT_DENS(f) (((f) & MTVF_DENS_MASK) >> UNIT_V_DF_TAPE) + +#define MT_NONE_VALID (1u << MT_DENS_NONE) /* density not set is valid */ +#define MT_200_VALID (1u << MT_DENS_200) /* 200 bpi is valid */ +#define MT_556_VALID (1u << MT_DENS_556) /* 556 bpi is valid */ +#define MT_800_VALID (1u << MT_DENS_800) /* 800 bpi is valid */ +#define MT_1600_VALID (1u << MT_DENS_1600) /* 1600 bpi is valid */ +#define MT_6250_VALID (1u << MT_DENS_6250) /* 6250 bpi is valid */ + /* Return status codes */ #define MTSE_OK 0 /* no error */ @@ -116,6 +136,7 @@ typedef uint16 t_tpclnt; /* magtape rec lnt */ #define MTSE_RECE 8 /* error in record */ #define MTSE_WRP 9 /* write protected */ #define MTSE_LEOT 10 /* Logical End Of Tape */ +#define MTSE_RUNAWAY 11 /* tape runaway */ typedef void (*TAPE_PCALLBACK)(UNIT *unit, t_stat status); @@ -143,8 +164,8 @@ t_stat sim_tape_wreom (UNIT *uptr); t_stat sim_tape_wreom_a (UNIT *uptr, TAPE_PCALLBACK callback); t_stat sim_tape_wreomrw (UNIT *uptr); t_stat sim_tape_wreomrw_a (UNIT *uptr, TAPE_PCALLBACK callback); -t_stat sim_tape_wrgap (UNIT *uptr, uint32 gaplen, uint32 bpi); -t_stat sim_tape_wrgap_a (UNIT *uptr, uint32 gaplen, uint32 bpi, TAPE_PCALLBACK callback); +t_stat sim_tape_wrgap (UNIT *uptr, uint32 gaplen); +t_stat sim_tape_wrgap_a (UNIT *uptr, uint32 gaplen, TAPE_PCALLBACK callback); t_stat sim_tape_sprecf (UNIT *uptr, t_mtrlnt *bc); t_stat sim_tape_sprecf_a (UNIT *uptr, t_mtrlnt *bc, TAPE_PCALLBACK callback); t_stat sim_tape_sprecsf (UNIT *uptr, uint32 count, uint32 *skipped); @@ -173,6 +194,8 @@ t_stat sim_tape_set_fmt (UNIT *uptr, int32 val, char *cptr, void *desc); t_stat sim_tape_show_fmt (FILE *st, UNIT *uptr, int32 val, void *desc); t_stat sim_tape_set_capac (UNIT *uptr, int32 val, char *cptr, void *desc); t_stat sim_tape_show_capac (FILE *st, UNIT *uptr, int32 val, void *desc); +t_stat sim_tape_set_dens (UNIT *uptr, int32 val, char *cptr, void *desc); +t_stat sim_tape_show_dens (FILE *st, UNIT *uptr, int32 val, void *desc); t_stat sim_tape_set_asynch (UNIT *uptr, int latency); t_stat sim_tape_clr_asynch (UNIT *uptr); void sim_tape_data_trace (UNIT *uptr, const uint8 *data, size_t len, const char* txt, int detail, uint32 reason);