These changes facilitate more robust parameter type checking and helps to identify unexpected coding errors. Most simulators can now also be compiled with a C++ compiler without warnings. Additionally, these changes have also been configured to facilitate easier backporting of simulator and device simulation modules to run under the simh v3.9+ SCP framework.
3184 lines
163 KiB
C
3184 lines
163 KiB
C
/* hp_tapelib.c: HP magnetic tape controller simulator library
|
|
|
|
Copyright (c) 2013-2016, J. David Bryan
|
|
Copyright (c) 2004-2011, 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 THE
|
|
AUTHORS 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 names of the authors shall not be
|
|
used in advertising or otherwise to promote the sale, use or other dealings
|
|
in this Software without prior written authorization from the authors.
|
|
|
|
13-May-16 JDB Modified for revised SCP API function parameter types
|
|
20-Nov-15 JDB First release version
|
|
24-Mar-13 JDB Created tape controller common library from MS simulator
|
|
|
|
References:
|
|
- 13181B Digital Magnetic Tape Unit Interface Kit Operating and Service Manual
|
|
(13181-90901, November 1982)
|
|
- 13183B Digital Magnetic Tape Unit Interface Kit Operating and Service Manual
|
|
(13183-90901, November 1983)
|
|
- 30115A Nine-Track (NRZI-PE) Magnetic Tape Subsystem Maintenance Manual
|
|
(30115-90001, June 1976)
|
|
- 30115A Nine-Track (NRZI-PE) Magnetic Tape Subsystem Microprogram Listing
|
|
(30115-90005, January 1974)
|
|
- Standard ECMA-12 for Data Interchange on 9-Track Magnetic Tape
|
|
(June 1970)
|
|
|
|
|
|
This library provides the common functions required by the device controllers
|
|
for the HP 7970B and 7970E tape drives. It implements the command sets of
|
|
the 13181 and 13183 controllers for the HP 1000, and the 30215 controller for
|
|
the HP 3000.
|
|
|
|
The library is an adaptation of the code originally written by Bob Supnik for
|
|
the HP2100 MS simulator. MS simulates an HP 13181 or 13183 controller
|
|
interface for an HP 1000 computer. To create the library, the functions of
|
|
the controller were separated from the functions of the interface. This
|
|
allows the library to work with other CPUs, such as the 30215 interface for
|
|
the HP 3000, that use substantially different communication protocols. The
|
|
library functions implement the controller command set for the drive units.
|
|
The interface functions handle the transfer of commands and data to and from
|
|
the CPU.
|
|
|
|
In contrast with the 13037 MAC disc controller, HP had no standard tape
|
|
controller that was shared between computer families. Instead, there were
|
|
several similar but incompatible controller implementations. Command sets,
|
|
command encoding, status encoding, status responses, and controller hardware
|
|
design varied from implementation to implementation, with each tied tightly
|
|
to the specific machine for which it was created.
|
|
|
|
Therefore, to provide a "universal" tape controller that may be shared
|
|
between the 1000 and 3000 simulators, this library implements an abstract
|
|
controller modeled on the interface design of the 13037. A tape interface
|
|
interacts with the controller over 16-bit data, flag, and function buses.
|
|
Commands, status, and data are exchanged across the data bus, with the flag
|
|
bus providing indications of the state of the interface and the function bus
|
|
indicating what actions the interface must take in response to command
|
|
processing by the controller.
|
|
|
|
A device interface simulator interacts with the tape controller simulator via
|
|
the "tl_controller" routine, which simulates the signal interconnections
|
|
between the interface and controller. Utility routines are also provided to
|
|
attach and detach tape image files from drive units, set units offline or
|
|
online, set drive model and protection status, and select the controller
|
|
timing mode (real or fast). The controller routine is called by the
|
|
interface whenever the state of the flag bus changes, either due to a request
|
|
from the CPU or a service event, and the controller responds by potentially
|
|
changing the data and function buses. The interface merely responds to those
|
|
changes without requiring any other knowledge of the internal state of the
|
|
controller.
|
|
|
|
The interface initiates controller action by placing data on the data bus and
|
|
then changing the state of the flag bus, and the controller responds by
|
|
asserting one or more functions on the function bus. For example, the
|
|
interface starts a "Write Record" tape command by placing the command on the
|
|
data bus and asserting CMRDY (Command Ready) and CMXEQ (Command Execute) on
|
|
the flag bus. The controller responds by decoding the command, initiating
|
|
processing, and then placing the IFGTC (Interface Get Command) and RQSRV
|
|
(Request Service) functions on the function bus. The interface responds by
|
|
clearing the transfer flags in preparation for the data transfer and
|
|
requesting service from the DMA channel. Once the controller determines that
|
|
the tape is moving and is positioned correctly for the write, an event
|
|
service entry calls the controller with the DTRDY (Data Ready) flag and the
|
|
first word of data to write. The controller responds by saving the word to
|
|
the record buffer and asserting the IFOUT (Interface Out) and RQSRV
|
|
functions. The interface responds by clearing the DTRDY flag and requesting
|
|
the next word from the DMA channel.
|
|
|
|
Hardware wait loops in the abstract controller that wait for tape motion are
|
|
simulated by timed events. The tl_controller routine activates the selected
|
|
unit and returns to the caller until the expected external event occurs. In
|
|
the Write Record example, the controller will wait for the simulated tape to
|
|
start and position the record for writing and then will wait for each data
|
|
word to be supplied by the interface.
|
|
|
|
The controller supports realistic and optimized (fast) timing modes.
|
|
Realistic timing attempts to model the actual controller and tape unit motion
|
|
delays inherent in tape operations. For example, in REALTIME mode, reads of
|
|
longer records take more time than reads of shorter records, rewinds take
|
|
times proportional to the distance from the load point, and an erase gap is
|
|
written before the first record after the load point. In FASTTIME mode, all
|
|
timings are reduced to be "just long enough" to satisfy software
|
|
requirements, and movement across erase gaps takes no additional time.
|
|
|
|
Typically, a total operation time consists of a controller overhead delay, a
|
|
tape transport start time, an optional gap traverse time, a data record
|
|
traverse time, and a transport stop time. These times are modeled by the
|
|
service activation times for the various command phases.
|
|
|
|
A controller instance is represented by a CNTLR_VARS structure, which
|
|
maintains the controller's internal state. Each 13181/3 and 30215 interface
|
|
has a single controller instance that controls up to four drive units,
|
|
whereas an HP-IB interface will have one controller instance per drive unit.
|
|
The minor differences in controller action between the two are handled
|
|
internally.
|
|
|
|
The 1000/3000 interface simulators must declare one unit for each tape drive
|
|
to be controlled by the library, plus one additional unit for the controller
|
|
itself. For an HP-IB controller, only one unit is required.
|
|
|
|
The controller maintains five values in each drive's unit structure:
|
|
|
|
u3 (PROP) -- the current drive properties
|
|
u4 (STATUS) -- the current drive status
|
|
u5 (OPCODE) -- the current drive operation in progress
|
|
u6 (PHASE) -- the current drive operation phase
|
|
pos -- the current byte offset into the tape image file
|
|
|
|
These and other definitions are in the file hp_tapelib.h, which must be
|
|
included in the interface simulator.
|
|
|
|
The drive status field contains only a subset of the status maintained by
|
|
drives in hardware. Specifically, the Write Protected, Write Status, and
|
|
1600-bpi Density bits are stored in the status field. The other bits (End of
|
|
Tape, Unit Busy, Unit Ready, Load Point, Rewinding, and Unit Offline) are set
|
|
dynamically whenever status is requested.
|
|
|
|
Per-drive opcode and phase values allow rewinds to be overlapped with
|
|
operations on other drives. For example, a Rewind issued to unit 0 may be
|
|
followed by a Read Record issued to unit 1. When the rewind completes on
|
|
unit 0, its opcode and phase values will let the controller set the
|
|
appropriate completion status without disturbing the values currently in use
|
|
by unit 1.
|
|
|
|
The simulation defines these command phases:
|
|
|
|
Idle -- waiting for the next command to be issued
|
|
Wait -- waiting for the channel data transfer
|
|
Error -- waiting to interrupt for a command abort
|
|
Start -- waiting for the drive to come up to speed after starting
|
|
Traverse -- waiting for the drive to traverse an erase gap
|
|
Data -- waiting for the drive to traverse a data record
|
|
Stop -- waiting for the drive to slow to a stop
|
|
|
|
A value represents the current state of the unit. If a unit is active, the
|
|
phase will end when the unit is serviced.
|
|
|
|
During reads and forward/backward spacing commands, each data record may be
|
|
optionally preceded in the direction of motion by an erase gap. If no gap is
|
|
present, the associated traversal phase is skipped, and service proceeds
|
|
directly from the start phase to the data phase.
|
|
|
|
In-progress operations may be cancelled by issuing a controller clear. In
|
|
hardware, this causes tape motion to cease immediately, except for rewind
|
|
operations, which continue to the load point. Under simulation, a write-in-
|
|
progress is terminated with the data received so far written as a truncated
|
|
record. Additionally, for a NRZI drive, the record is marked as "bad" due to
|
|
the lack of a CRC character. A read- or spacing-in-progress is abandoned if
|
|
the clear occurs during the controller overhead or transport start period or
|
|
is terminated with partial movement if it occurs during the gap traverse
|
|
period. A read- or spacing-in-progress is completed normally if the clear
|
|
occurs during the data record traverse, as the SIMH magnetic tape format has
|
|
no provision for recovery when positioned within a record.
|
|
|
|
In addition to the controller structure(s), an interface declares a data
|
|
buffer to be used for record transfers. The buffer is an array containing
|
|
TL_BUFSIZE 8-bit elements; the address of the buffer is stored in the
|
|
controller state structure. The controller maintains the current index into
|
|
the buffer, as well as the length of valid data stored there. Only one
|
|
buffer is needed per interface, regardless of the number of controllers or
|
|
units handled, as a single interface cannot perform data transfers on one
|
|
drive concurrently with a command directed to another drive.
|
|
|
|
An interface is also responsible for declaring a structure of type
|
|
DELAY_PROPS that contains the timing values for the controller when in
|
|
FASTTIME mode. The values are event counts for these actions:
|
|
|
|
- the time from rewind initiation to controller idle
|
|
- the travel time per inch during rewinding
|
|
- the time from BOT detection to load point search completion
|
|
- the time starting from the BOT marker to the data block
|
|
- the time starting from the IR gap to the data block
|
|
- the travel time from one data byte to the next
|
|
- the controller execution overhead time from command to first motion
|
|
|
|
These values are typically exposed via the interface's register set and so
|
|
may be altered by the user. A macro, DELAY_INIT, is provided to initialize
|
|
the structure.
|
|
|
|
In addition, certain actions are enabled only when the controller is in
|
|
realistic or optimized timing mode. Specifically, when in REALTIME mode, the
|
|
controller will:
|
|
|
|
- stop at a proportional position if it is cleared during a gap traversal
|
|
- calculate and add the CRCC and LRCC to the record buffer for 13181 reads
|
|
|
|
In FASTTIME mode, the controller will:
|
|
|
|
- omit writing the gap normally required at the beginning of the tape
|
|
- omit writing the gap for the "Write Gap and File Mark" command
|
|
- omit the gap traversal time for reads and spacings
|
|
|
|
The controller library provides a macro, TL_MODS, that initializes MTAB
|
|
entries. A macro, CNTLR_INIT, is provided to initialize the controller
|
|
structure from the following parameters:
|
|
|
|
- the type of the controller
|
|
- the simulation DEVICE structure on which the controller operates
|
|
- the data buffer array
|
|
- the structure containing the FASTTIME values
|
|
|
|
A macro, TL_REGS, is also provided that initializes a set of REG structures
|
|
to provide a user interface to the controller structure.
|
|
|
|
In hardware, the 3000 controller detects a tape unit offline-to-online
|
|
transition and responds by requesting a CPU interrupt. In simulation, SET
|
|
<dev> OFFLINE and SET <dev> ONLINE commands represent these actions. The
|
|
controller must be notified by calling the tl_onoffline routine in response
|
|
to changes in a tape drive's ONLINE switch.
|
|
|
|
Finally, the controller library provides extensive tracing of its internal
|
|
operations via debug logging. Six debug flags are declared for use by the
|
|
interface simulator. When enabled, these report controller actions at
|
|
various levels of detail.
|
|
|
|
The simulator tape support library (sim_tape) routines are used to implement
|
|
the low-level tape image file handling. Fatal errors from these routines are
|
|
reported to the simulation console and cause a simulation stop. If
|
|
simulation is resumed, the controller command in progress is aborted with an
|
|
uncorrectable data error indication. Typically, the program executing under
|
|
simulation reacts to this by retrying the operation.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The library does not simulate the optional OFF-0-1-2-3 Unit Select
|
|
buttons of the 7970-series drives. Instead, each drive adopts the unit
|
|
address corresponding to the simulator UNIT number associated with the
|
|
drive. For example, unit MS2 responds to unit address 2.
|
|
|
|
A set of bits to represent the Unit Select switch setting has been
|
|
allocated in the PROP field of the UNIT structure, but it is currently
|
|
unused. This feature could be added by reading the unit number from
|
|
there, rather than deriving it from the position in the device's UNIT
|
|
array, and adding SET <unit> UNIT=OFF|0|1|2|3 and SHOW <unit> UNIT
|
|
commands and associated validation and print routines. However, the
|
|
operational benefit of this, compared to the current approach, is nil, as
|
|
the only utility derived would be a somewhat cleaner set of test commands
|
|
for the magnetic tape diagnostic.
|
|
|
|
The current unit select diagnostic approach moves the scratch tape
|
|
attachment from drive to drive in lieu of changing the Unit Select switch
|
|
of a single drive. This passes both the 1000 and 3000 diagnostics,
|
|
although reattaching does reset the tape position to the load point.
|
|
Fortunately, continuance of tape position isn't tested.
|
|
|
|
2. The library does not properly simulate the HP 30215 tape controller's
|
|
Write_Record_without_Parity (WRZ) and Read_Record_with_CRCC (RDC)
|
|
commands, which are used only with NRZI 7970B drives. In hardware, WRZ
|
|
forces the parity of data bytes written to the tape to zero, allowing the
|
|
creation of records containing dropouts, bad parity, and spacing gaps,
|
|
and RDC returns two extra bytes containing the CRCC and its parity bit
|
|
read from the tape.
|
|
|
|
Because the tape image format contains only the data content of records,
|
|
under simulation WRZ writes all received bytes to a data record, except
|
|
that a parity error is indicated if a byte has even parity, and zero
|
|
bytes (dropouts) are omitted. RDC is simulated by returning the
|
|
calculated CRC of the record.
|
|
|
|
It would be possible to simulate these functions correctly if the tape
|
|
image format is extended to allow private data. For example, the WRZ
|
|
command would write a private data record containing all bytes, and RDC
|
|
would read the record and interpret the data accordingly. Reading a
|
|
standard record with RDC would return a calculated CRCC. However, the
|
|
operational benefit of this is nil, as only the diagnostic uses these
|
|
commands, and then only to verify that the CRCC, LRCC, and dropout
|
|
detections are working properly. These functions are irrelevant in a
|
|
simulation environment.
|
|
|
|
3. In hardware, the transport stop time will be eliminated if another
|
|
command of the proper type is received in time. If a new command is in
|
|
the same command group (read forward, read reverse, or write forward),
|
|
and it follows within 103 microseconds of the previous command, then tape
|
|
motion will continue. Otherwise, it will stop and restart.
|
|
|
|
In simulation, all tape commands complete the stop phase before the next
|
|
command is started.
|
|
|
|
4. The SAVE command does not save the "wait" and "pos" fields of the UNIT
|
|
structure automatically. To ensure that they are saved, they are
|
|
referenced by read-only registers.
|
|
*/
|
|
|
|
|
|
|
|
#include "hp_tapelib.h"
|
|
|
|
|
|
|
|
/* Program constants */
|
|
|
|
#define NO_EVENT -1 /* do not schedule an event */
|
|
|
|
#define NO_ACTION (CNTLR_IFN_IBUS) (NO_FUNCTIONS | NO_DATA)
|
|
#define SCP_STATUS(w) (CNTLR_IFN_IBUS) (SCPE | (w))
|
|
|
|
|
|
/* Controller unit pointer */
|
|
|
|
#define CNTLR_UPTR (cvptr->device->units + TL_CNTLR_UNIT)
|
|
|
|
|
|
/* Unit flags accessor */
|
|
|
|
#define GET_MODEL(f) ((f) >> UNIT_MODEL_SHIFT & UNIT_MODEL_MASK)
|
|
|
|
|
|
/* Per-unit property flags and accessors.
|
|
|
|
The property value (PROP) contains several fields that describe the drive and
|
|
its currently mounted tape reel:
|
|
|
|
15| 14 13 12| 11 10 9 | 8 7 6 | 5 4 3 | 2 1 0
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
[...] - | model | unit num | reel | property array index |
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
The property array index is the element number of the drive_props array that
|
|
describes the drive associated with the unit number. The reel size value is
|
|
one of the REEL_SIZE enumeration constants that reflects the specified length
|
|
of the tape reel mounted to the unit.
|
|
|
|
The unit number and model are not currently used but are reserved for future
|
|
expansion.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. Unit attention could be kept in the property value, rather than as a flag
|
|
in the bitmap controller field "unit_attention". However, keeping the
|
|
attention flags together means that they can be checked very quickly,
|
|
rather than having to isolate and check a bit in each UNIT structure.
|
|
*/
|
|
|
|
#define PROP_INDEX_WIDTH 8 /* tape drive property array index */
|
|
#define PROP_REEL_WIDTH 2 /* mounted tape reel size */
|
|
#define PROP_UNIT_WIDTH 3 /* tape drive unit number */
|
|
#define PROP_MODEL_WIDTH 3 /* tape drive model */
|
|
|
|
#define PROP_INDEX_SHIFT 0 /* bits 7-0 */
|
|
#define PROP_REEL_SHIFT 8 /* bits 9-8 */
|
|
#define PROP_UNIT_SHIFT 10 /* bits 12-10 */
|
|
#define PROP_MODEL_SHIFT 13 /* bits 15-13 */
|
|
|
|
#define PROP_INDEX_MASK ((1 << PROP_INDEX_WIDTH) - 1 << PROP_INDEX_SHIFT)
|
|
#define PROP_REEL_MASK ((1 << PROP_REEL_WIDTH) - 1 << PROP_REEL_SHIFT)
|
|
#define PROP_UNIT_MASK ((1 << PROP_UNIT_WIDTH) - 1 << PROP_UNIT_SHIFT)
|
|
#define PROP_MODEL_MASK ((1 << PROP_MODEL_WIDTH) - 1 << PROP_MODEL_SHIFT)
|
|
|
|
#define PROP_INDEX(u) (((u)->PROP & PROP_INDEX_MASK) >> PROP_INDEX_SHIFT)
|
|
#define PROP_REEL(u) (((u)->PROP & PROP_REEL_MASK) >> PROP_REEL_SHIFT)
|
|
#define PROP_UNIT(u) (((u)->PROP & PROP_UNIT_MASK) >> PROP_UNIT_SHIFT)
|
|
#define PROP_MODEL(u) (((u)->PROP & PROP_MODEL_MASK) >> PROP_MODEL_SHIFT)
|
|
|
|
#define PROP_REEL_UNLIM (Reel_Unlimited << PROP_REEL_SHIFT)
|
|
#define PROP_REEL_600 (Reel_600_Foot << PROP_REEL_SHIFT)
|
|
#define PROP_REEL_1200 (Reel_1200_Foot << PROP_REEL_SHIFT)
|
|
#define PROP_REEL_2400 (Reel_2400_Foot << PROP_REEL_SHIFT)
|
|
|
|
|
|
/* Magnetic tape reel sizes */
|
|
|
|
typedef enum {
|
|
Reel_Unlimited = 0,
|
|
Reel_600_Foot,
|
|
Reel_1200_Foot,
|
|
Reel_2400_Foot
|
|
} REEL_SIZE;
|
|
|
|
|
|
/* Unit command phases.
|
|
|
|
Command phases indicate the state of the unit. If an event is currently
|
|
scheduled, the phase indicates the unit state as it will be on entry to the
|
|
event service routine. For example, upon event expiration, a scheduled unit
|
|
in the start phase will have started, will be up to speed, and will have
|
|
passed through the interrecord gap and be ready to transfer data. The start
|
|
phase activation delay represents the time required for these actions to
|
|
occur.
|
|
*/
|
|
|
|
typedef enum {
|
|
Idle_Phase = 0, /* waiting for the next command to be issued */
|
|
Wait_Phase, /* waiting for the channel data transfer */
|
|
Start_Phase, /* waiting for the drive to come up to speed after starting */
|
|
Traverse_Phase, /* waiting for the drive to traverse an erase gap */
|
|
Data_Phase, /* waiting for the drive to traverse a data record */
|
|
Stop_Phase, /* waiting for the drive to slow to a stop */
|
|
Error_Phase /* waiting to interrupt for a command abort */
|
|
} CNTLR_PHASE;
|
|
|
|
|
|
/* Drive properties table.
|
|
|
|
The drive properties table is used to validate drive type and density changes
|
|
within the subset of drives supported by a given controller. It also
|
|
contains the erase gap size, which is controller-specific.
|
|
*/
|
|
|
|
typedef struct {
|
|
CNTLR_TYPE controller; /* the controller model */
|
|
DRIVE_TYPE drive; /* a supported tape drive model */
|
|
uint32 density; /* a supported tape drive density code */
|
|
uint32 bpi; /* the recording density in bits per inch */
|
|
uint32 gap_size; /* the erase gap size in tenths of an inch */
|
|
} DRIVE_PROPS;
|
|
|
|
static const DRIVE_PROPS drive_props [] = {
|
|
/* gap */
|
|
/* cntrlr drive density code bpi size */
|
|
/* --------- --------- ------------ ----- ---- */
|
|
{ HP_13181, HP_7970B, MT_DENS_800, 800, 48 },
|
|
{ HP_13183, HP_7970E, MT_DENS_1600, 1600, 30 },
|
|
{ HP_30215, HP_7970B, MT_DENS_800, 800, 38 },
|
|
{ HP_30215, HP_7970E, MT_DENS_1600, 1600, 38 },
|
|
{ HP_IB, HP_7974, MT_DENS_800, 800, 0 },
|
|
{ HP_IB, HP_7974, MT_DENS_1600, 1600, 0 }
|
|
};
|
|
|
|
#define PROPS_COUNT (sizeof drive_props / sizeof drive_props [0])
|
|
|
|
|
|
/* Delay properties table.
|
|
|
|
To support the realistic timing mode, the delay properties table contains
|
|
timing specifications for the supported tape drives. Table entries
|
|
correspond one-for-one with drive properties table entries.
|
|
|
|
The times represent the delays for mechanical and electronic operations.
|
|
Delay values are stored in event tick counts; macros are used to convert from
|
|
times to ticks.
|
|
|
|
The rewind start time is the delay from rewind command initiation until the
|
|
controller is released. The rewind stop time is the time to stop the reverse
|
|
motion plus the time to search forward to the load point.
|
|
|
|
The controller overhead values are estimates; they do not appear to be
|
|
documented for any of the controllers.
|
|
*/
|
|
|
|
static const DELAY_PROPS real_times [] = {
|
|
/* rewind rewind rewind BOT IR per-byte cntlr */
|
|
/* start time/inch stop time time data xfer overhead */
|
|
/* -------- --------- -------- ----------- --------- ---------- -------- */
|
|
{ uS (556), mS (6.25), S (2.25), mS (102.22), mS (7.88), uS (27.78), uS ( 5) }, /* HP_13181, HP_7970B, MT_DENS_800, */
|
|
{ uS (556), mS (6.25), S (2.25), mS (160.00), mS (8.67), uS (13.89), uS ( 5) }, /* HP_13183, HP_7970E, MT_DENS_1600 */
|
|
{ mS (2.2), mS (6.25), S (2.25), mS ( 9.73), mS (9.73), uS (27.78), uS ( 20) }, /* HP_30215, HP_7970B, MT_DENS_800 */
|
|
{ mS (2.2), mS (6.25), S (2.25), mS ( 12.24), mS (9.73), uS (13.89), uS ( 20) }, /* HP_30215, HP_7970E, MT_DENS_1600 */
|
|
{ mS (0.0), mS (0.00), S (0.00), mS ( 00.00), mS (0.00), uS (00.00), uS ( 00) }, /* HP_IB, HP_7974, MT_DENS_800 */
|
|
{ mS (0.0), mS (0.00), S (0.00), mS ( 00.00), mS (0.00), uS (00.00), uS ( 00) } /* HP_IB, HP_7974, MT_DENS_1600 */
|
|
};
|
|
|
|
|
|
/* Command properties table.
|
|
|
|
The validity of each command for the controller type is checked against the
|
|
command properties table when it is prepared for execution. The table also
|
|
includes the class of the command, whether the drive must be ready before
|
|
execution is permitted, and whether the command requires a data transfer
|
|
phase.
|
|
*/
|
|
|
|
typedef struct {
|
|
CNTLR_CLASS class; /* command class */
|
|
t_bool valid [CNTLR_COUNT]; /* command validity, indexed by CNTLR_TYPE */
|
|
t_bool ready; /* command requires a ready drive */
|
|
t_bool transfer; /* command requires a transfer of data */
|
|
} COMMAND_PROPERTIES;
|
|
|
|
#define T TRUE
|
|
#define F FALSE
|
|
|
|
static const COMMAND_PROPERTIES cmd_props [] = { /* command properties, in CNTLR_OPCODE order */
|
|
/* opcode valid for drive data */
|
|
/* class 181 183 215 HPIB ready xfer */
|
|
/* ------------- --- --- --- ---- ----- ----- */
|
|
{ Class_Control, { T, T, T, T }, F, F }, /* 00 = Select_Unit_0 */
|
|
{ Class_Control, { T, T, T, T }, F, F }, /* 01 = Select_Unit_1 */
|
|
{ Class_Control, { T, T, T, T }, F, F }, /* 02 = Select_Unit_2 */
|
|
{ Class_Control, { T, T, T, T }, F, F }, /* 03 = Select_Unit_3 */
|
|
{ Class_Control, { T, T, F, F }, F, F }, /* 04 = Clear_Controller */
|
|
{ Class_Read, { T, T, T, T }, T, T }, /* 05 = Read_Record */
|
|
{ Class_Read, { F, F, T, F }, T, T }, /* 06 = Read_Record_with_CRCC */
|
|
{ Class_Read, { T, T, F, F }, T, T }, /* 07 = Read_Record_Backward */
|
|
{ Class_Read, { T, T, F, F }, T, T }, /* 08 = Read_File_Forward */
|
|
{ Class_Write, { T, T, T, T }, T, T }, /* 09 = Write_Record */
|
|
{ Class_Write, { F, F, T, F }, T, T }, /* 10 = Write_Record_without_Parity */
|
|
{ Class_Write, { T, T, T, T }, T, F }, /* 11 = Write_File_Mark */
|
|
{ Class_Write, { T, T, T, T }, T, F }, /* 12 = Write_Gap */
|
|
{ Class_Write, { T, T, F, F }, T, F }, /* 13 = Write_Gap_and_File_Mark */
|
|
{ Class_Control, { T, T, T, T }, T, F }, /* 14 = Forward_Space_Record */
|
|
{ Class_Control, { T, T, T, T }, T, F }, /* 15 = Forward_Space_File */
|
|
{ Class_Control, { T, T, T, T }, T, F }, /* 16 = Backspace_Record */
|
|
{ Class_Control, { T, T, T, T }, T, F }, /* 17 = Backspace_File */
|
|
{ Class_Rewind, { T, T, T, T }, T, F }, /* 18 = Rewind */
|
|
{ Class_Rewind, { T, T, T, T }, T, F }, /* 19 = Rewind_Offline */
|
|
{ Class_Invalid, { F, F, F, F }, F, F } /* 20 = Invalid_Opcode */
|
|
};
|
|
|
|
|
|
/* Status mapping table.
|
|
|
|
The various HP tape controllers report tape status conditions in different
|
|
locations in the status word. To accommodate this, a lookup table is used to
|
|
map specific conditions to the controller-specific bit(s). An enumeration is
|
|
used to provide symbolic table index constants.
|
|
|
|
A given controller may not report all status conditions. For conditions not
|
|
reported, the table entries are zero, so that ORing the values into the
|
|
status word has no effect.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. Unit Selected status for units 0-3 must have values 0-3, so that the
|
|
selected unit field of the controller state structure may be used
|
|
directly as an index into the table.
|
|
|
|
2. Single Track Error cannot occur under simulation. In hardware, this
|
|
error occurs on phase-encoded drives when a data error is detected and
|
|
corrected.
|
|
|
|
3. Multiple Track Error status for PE drives is the same as Parity Error
|
|
status for NRZI drives. Both are reported as Data_Error status.
|
|
|
|
4. The status bits for the 30215 controller's Data_Error, Timing_Error,
|
|
Tape_Runaway, and Command_Rejected errors are complemented from their
|
|
actual values. This allows an all-zeros value to represent No Error,
|
|
which is consistent with the values used by the other controllers. The
|
|
30215 simulator complements these encoded error bits before reporting the
|
|
controller status.
|
|
|
|
5. Of the 30215 controller's encoded error status values, only Data_Error
|
|
(value 4) and Timing_Error (value 6) can occur in the same transfer. The
|
|
controller hardware uses a priority encoder to give Timing_Error reporting
|
|
priority. Because of the values employed, we can simply OR the two
|
|
errors as they occur, and Timing_Error will result.
|
|
*/
|
|
|
|
typedef enum {
|
|
Unit_0_Selected = 0,
|
|
Unit_1_Selected = 1,
|
|
Unit_2_Selected = 2,
|
|
Unit_3_Selected = 3,
|
|
Command_Rejected,
|
|
Data_Error,
|
|
Density_1600,
|
|
End_of_File,
|
|
End_of_Tape,
|
|
Interface_Busy,
|
|
Load_Point,
|
|
Odd_Length,
|
|
Protected,
|
|
Rewinding,
|
|
Tape_Runaway,
|
|
Timing_Error,
|
|
Unit_Busy,
|
|
Unit_Offline,
|
|
Unit_Ready,
|
|
Write_Status
|
|
} STATUS_CONDITION;
|
|
|
|
static const uint32 status_bits [] [CNTLR_COUNT] = { /* indexed by STATUS_CONDITION and CNTLR_TYPE */
|
|
|
|
/* HP_13181 HP_13183 HP_30215 */
|
|
/* -------- -------- -------- */
|
|
{ 0000000, 0000000, 0000000 }, /* Unit_0_Selected */
|
|
{ 0000000, 0020000, 0004000 }, /* Unit_1_Selected */
|
|
{ 0000000, 0040000, 0010000 }, /* Unit_2_Selected */
|
|
{ 0000000, 0060000, 0014000 }, /* Unit_3_Selected */
|
|
{ 0000010, 0000010, 0000012 }, /* Command_Rejected */
|
|
{ 0000002, 0000002, 0000004 }, /* Data_Error */
|
|
{ 0000000, 0100000, 0000100 }, /* Density_1600 */
|
|
{ 0000200, 0000200, 0000020 }, /* End_of_File */
|
|
{ 0000040, 0000040, 0002000 }, /* End_of_Tape */
|
|
{ 0000400, 0000400, 0000000 }, /* Interface_Busy */
|
|
{ 0000100, 0000100, 0000200 }, /* Load_Point */
|
|
{ 0004000, 0004000, 0040000 }, /* Odd_Length */
|
|
{ 0000004, 0000004, 0001000 }, /* Protected */
|
|
{ 0002000, 0002000, 0000000 }, /* Rewinding */
|
|
{ 0000000, 0000000, 0000010 }, /* Tape_Runaway */
|
|
{ 0000020, 0000020, 0000006 }, /* Timing_Error */
|
|
{ 0001000, 0001000, 0000000 }, /* Unit_Busy */
|
|
{ 0000001, 0000001, 0000000 }, /* Unit_Offline */
|
|
{ 0000000, 0000000, 0000400 }, /* Unit_Ready */
|
|
{ 0000000, 0000000, 0000040 } /* Write_Status */
|
|
};
|
|
|
|
/* Controller status flags */
|
|
|
|
#define CST_UNITSEL (status_bits [cvptr->unit_selected] [cvptr->type])
|
|
#define CST_REJECT (status_bits [Command_Rejected] [cvptr->type])
|
|
#define CST_RUNAWAY (status_bits [Tape_Runaway] [cvptr->type])
|
|
#define CST_DATAERR (status_bits [Data_Error] [cvptr->type])
|
|
#define CST_EOF (status_bits [End_of_File] [cvptr->type])
|
|
#define CST_TIMERR (status_bits [Timing_Error] [cvptr->type])
|
|
#define CST_IFBUSY (status_bits [Interface_Busy] [cvptr->type])
|
|
#define CST_ODDLEN (status_bits [Odd_Length] [cvptr->type])
|
|
|
|
/* Unit status flags */
|
|
|
|
#define UST_WRPROT (status_bits [Protected] [cvptr->type])
|
|
#define UST_WRSTAT (status_bits [Write_Status] [cvptr->type])
|
|
#define UST_DEN1600 (status_bits [Density_1600] [cvptr->type])
|
|
|
|
/* Dynamic status flags */
|
|
|
|
#define DST_EOT (status_bits [End_of_Tape] [cvptr->type])
|
|
#define DST_UNITBUSY (status_bits [Unit_Busy] [cvptr->type])
|
|
#define DST_UNITRDY (status_bits [Unit_Ready] [cvptr->type])
|
|
#define DST_LOADPT (status_bits [Load_Point] [cvptr->type])
|
|
#define DST_REWIND (status_bits [Rewinding] [cvptr->type])
|
|
#define DST_UNITLOCL (status_bits [Unit_Offline] [cvptr->type])
|
|
|
|
|
|
/* Controller operation names */
|
|
|
|
static const BITSET_NAME flag_names [] = { /* controller flag names, in CNTLR_FLAG order */
|
|
"CMRDY", /* 000001 */
|
|
"CMXEQ", /* 000002 */
|
|
"DTRDY", /* 000004 */
|
|
"EOD", /* 000010 */
|
|
"INTOK", /* 000020 */
|
|
"OVRUN", /* 000040 */
|
|
"XFRNG" /* 000100 */
|
|
};
|
|
|
|
static const BITSET_FORMAT flag_format = /* names, offset, direction, alternates, bar */
|
|
{ FMT_INIT (flag_names, 0, lsb_first, no_alt, no_bar) };
|
|
|
|
|
|
static const BITSET_NAME function_names [] = { /* interface function names, in CNTLR_IFN order */
|
|
"IFIN", /* 000000200000 */
|
|
"IFOUT", /* 000000400000 */
|
|
"IFGTC", /* 000001000000 */
|
|
"SCPE", /* 000002000000 */
|
|
"RQSRV", /* 000004000000 */
|
|
"DVEND", /* 000010000000 */
|
|
"STCFL", /* 000020000000 */
|
|
"STDFL", /* 000040000000 */
|
|
"STINT", /* 000100000000 */
|
|
"DATTN" /* 000200000000 */
|
|
};
|
|
|
|
static const BITSET_FORMAT function_format = /* names, offset, direction, alternates, bar */
|
|
{ FMT_INIT (function_names, 16, lsb_first, no_alt, no_bar) };
|
|
|
|
|
|
static const char *opcode_names [] = { /* command opcode names, in CNTLR_OPCODE order */
|
|
"Select Unit 0",
|
|
"Select Unit 1",
|
|
"Select Unit 2",
|
|
"Select Unit 3",
|
|
"Clear Controller",
|
|
"Read Record",
|
|
"Read Record with CRCC",
|
|
"Read Record Backward",
|
|
"Read File Forward",
|
|
"Write Record",
|
|
"Write Record without Parity",
|
|
"Write File Mark",
|
|
"Write Gap",
|
|
"Write Gap and File Mark",
|
|
"Forward Space Record",
|
|
"Forward Space File",
|
|
"Backspace Record",
|
|
"Backspace File",
|
|
"Rewind",
|
|
"Rewind Offline",
|
|
"Invalid Command"
|
|
};
|
|
|
|
|
|
static const char *phase_names [] = { /* unit state names, in CNTLR_PHASE order */
|
|
"idle",
|
|
"wait",
|
|
"start",
|
|
"traverse",
|
|
"data",
|
|
"stop",
|
|
"error"
|
|
};
|
|
|
|
|
|
static const char *state_names [] = { /* controller state names, in CNTLR_STATE order */
|
|
"idle",
|
|
"busy",
|
|
"end",
|
|
"error"
|
|
};
|
|
|
|
|
|
static const char *const unit_names [] = { /* unit names, in unit number order */
|
|
"Unit 0",
|
|
"Unit 1",
|
|
"Unit 2",
|
|
"Unit 3",
|
|
"Controller unit"
|
|
};
|
|
|
|
|
|
/* Simulator tape support library call properties table.
|
|
|
|
The support library (sim_tape) call properties table is used to determine
|
|
whether the call may have read or written an erase gap in the tape image
|
|
file and whether the call returned a valid data length value. These are used
|
|
to interpret the tape positional change as a result of the call to separate
|
|
the gap length from the data record length. The table also contains strings
|
|
describing the call actions that are used when tracing library calls.
|
|
*/
|
|
|
|
typedef enum {
|
|
lib_space_fwd, /* call to sim_tape_sprecf */
|
|
lib_space_rev, /* call to sim_tape_sprecr */
|
|
lib_read_fwd, /* call to sim_tape_rdrecf */
|
|
lib_read_rev, /* call to sim_tape_rdrecr */
|
|
lib_write, /* call to sim_tape_wrrecf */
|
|
lib_write_gap, /* call to sim_tape_wrgap */
|
|
lib_write_tmk, /* call to sim_tape_wrtmk */
|
|
lib_rewind /* call to sim_tape_rewind */
|
|
} TAPELIB_CALL;
|
|
|
|
|
|
typedef struct {
|
|
t_bool gap_is_valid; /* call may involve an erase gap */
|
|
t_bool data_is_valid; /* call may involve a data record */
|
|
char *action; /* string describing the call action */
|
|
} TAPELIB_PROPERTIES;
|
|
|
|
|
|
static const TAPELIB_PROPERTIES lib_props [] = { /* indexed by TAPELIB_CALL */
|
|
/* gap data */
|
|
/* valid valid action */
|
|
/* ----- ----- ----------------- */
|
|
{ T, T, "forward space" }, /* lib_space_fwd */
|
|
{ T, T, "backspace" }, /* lib_space_rev */
|
|
{ T, T, "read" }, /* lib_read_fwd */
|
|
{ T, T, "reverse read" }, /* lib_read_rev */
|
|
{ F, T, "write" }, /* lib_write */
|
|
{ T, F, "write gap" }, /* lib_write_gap */
|
|
{ F, F, "write tape mark" }, /* lib_write_tmk */
|
|
{ T, F, "rewind" }, /* lib_rewind */
|
|
};
|
|
|
|
|
|
/* Simulator tape support library status values */
|
|
|
|
static const char *status_name [] = { /* indexed by MTSE value */
|
|
"succeeded", /* MTSE_OK */
|
|
"terminated with tape mark seen", /* MTSE_TMK */
|
|
"failed with unit not attached", /* MTSE_UNATT */
|
|
"failed with I/O error", /* MTSE_IOERR */
|
|
"failed with invalid record length", /* MTSE_INVRL */
|
|
"failed with invalid tape format", /* MTSE_FMT */
|
|
"terminated with beginning of tape seen", /* MTSE_BOT */
|
|
"terminated with end of medium seen", /* MTSE_EOM */
|
|
"succeeded with data error", /* MTSE_RECE */
|
|
"failed with no write ring", /* MTSE_WRP */
|
|
"failed with tape runaway" /* MTSE_RUNAWAY */
|
|
};
|
|
|
|
|
|
/* Tape library local controller routines */
|
|
|
|
static CNTLR_IFN_IBUS start_command (CVPTR cvptr, CNTLR_FLAG_SET flags, CNTLR_OPCODE opcode);
|
|
static CNTLR_IFN_IBUS continue_command (CVPTR cvptr, UNIT *uptr, CNTLR_FLAG_SET flags, CNTLR_IBUS data);
|
|
static CNTLR_IFN_IBUS end_command (CVPTR cvptr, UNIT *uptr);
|
|
static CNTLR_IFN_IBUS poll_drives (CVPTR cvptr);
|
|
static CNTLR_IFN_IBUS call_tapelib (CVPTR cvptr, UNIT *uptr, TAPELIB_CALL lib_call, t_mtrlnt parameter);
|
|
static CNTLR_IFN_IBUS abort_command (CVPTR cvptr, UNIT *uptr, t_stat status);
|
|
static void reject_command (CVPTR cvptr, UNIT *uptr);
|
|
static void add_crcc_lrcc (CVPTR cvptr, CNTLR_OPCODE opcode);
|
|
|
|
/* Tape library local utility routines */
|
|
|
|
static void activate_unit (CVPTR cvptr, UNIT *uptr);
|
|
static t_stat validate_drive (CVPTR cvptr, UNIT *uptr, DRIVE_TYPE new_drive, uint32 new_bpi);
|
|
|
|
|
|
|
|
/* Tape library global controller routines */
|
|
|
|
|
|
/* Tape controller interface.
|
|
|
|
This routine simulates the hardware interconnection between the abstract tape
|
|
controller and the CPU interface. This routine is called whenever the flag
|
|
state changes. This would be when a new command is to be started, when a
|
|
channel begins a read or write operation, when a channel program terminates,
|
|
or when a channel program error occurs. It must also be called when the unit
|
|
service routine is entered. The caller passes in the set of interface flags
|
|
and the contents of the data buffer. The routine returns a set of functions;
|
|
this is accompanied by a data value in these cases:
|
|
|
|
Function Data Value
|
|
-------- --------------------------------
|
|
IFGTC command classification code
|
|
SCPE SCPE status code
|
|
IFIN data record word
|
|
IFOUT data record CRCC and LRCC word
|
|
DATTN unit number requesting attention
|
|
|
|
On entry, the flags and controller state are examined to determine if a new
|
|
controller command should be initiated or the current command should be
|
|
continued. If this routine is being called as a result of an event service
|
|
or by channel initialization, "uptr" will point at the unit being serviced.
|
|
Otherwise, "uptr" will be NULL (for example, when starting a new controller
|
|
command).
|
|
|
|
On entry, if a 3000 channel error has occurred, then return with no event
|
|
scheduled; the CPU interface will recover by clearing the controller.
|
|
Otherwise, if this is an event service entry or channel initialization, then
|
|
process the next step of the command. Otherwise, if the CMRDY or CMXEQ flag
|
|
is asserted, then validate or start a new command. Finally, if the
|
|
controller is idle, then poll the drives for attention.
|
|
|
|
In all cases, return a combined function set and outbound data word to the
|
|
caller.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. To accommodate the HP 1000's 1318x controllers that separate command
|
|
validation from command execution, the "start_command" routine is entered
|
|
if either the CMRDY or CMXEQ flag is set, but the command will be started
|
|
only if the latter is set.
|
|
*/
|
|
|
|
CNTLR_IFN_IBUS tl_controller (CVPTR cvptr, UNIT *uptr, CNTLR_FLAG_SET flags, CNTLR_IBUS data)
|
|
{
|
|
CNTLR_IFN_IBUS outbound;
|
|
|
|
dpprintf (cvptr->device, TL_DEB_IOB, "Controller (%s) received data %06o with flags %s\n",
|
|
state_names [cvptr->state], data, fmt_bitset (flags, flag_format));
|
|
|
|
if (flags & XFRNG) /* if a channel error has occurred */
|
|
outbound = NO_ACTION; /* then the controller hangs until it's cleared */
|
|
|
|
else if (uptr) /* otherwise if an event is being serviced */
|
|
outbound = continue_command (cvptr, uptr, flags, data); /* then continue with command processing */
|
|
|
|
else if (flags & (CMRDY | CMXEQ)) /* otherwise if a new command is ready */
|
|
outbound = start_command (cvptr, flags, (CNTLR_OPCODE) data); /* then validate or execute it */
|
|
|
|
else /* otherwise there's nothing to do */
|
|
outbound = NO_ACTION; /* except possibly poll for attention */
|
|
|
|
if (cvptr->state == Idle_State /* if the controller is idle */
|
|
&& cvptr->type == HP_30215 /* and it's the 3000 controller */
|
|
&& flags & INTOK) /* and interrupts are allowed */
|
|
outbound = poll_drives (cvptr); /* then poll the drives for attention */
|
|
|
|
dpprintf (cvptr->device, TL_DEB_IOB, "Controller (%s) returned data %06o with functions %s\n",
|
|
state_names [cvptr->state], TLIBUS (outbound),
|
|
fmt_bitset (TLIFN (outbound), function_format));
|
|
|
|
return outbound;
|
|
}
|
|
|
|
|
|
/* Set a unit online or offline.
|
|
|
|
If the unit indicated by the "uptr" parameter is currently attached, it will
|
|
be set online if the "online" parameter is true or offline if the parameter
|
|
is false. If the drive is not rewinding, and it was offline and is being set
|
|
online, then the unit attention flag is set, and, if the controller is idle,
|
|
the routine returns SCPE_INCOMP to indicate that the caller must then call
|
|
the controller to poll for unit attention to complete the command.
|
|
Otherwise, it returns SCPE_OK, and the drives will be polled automatically
|
|
when the current command completes and the controller is idled.
|
|
|
|
If the drive is rewinding when it is set online, attention is deferred until
|
|
the rewind completes (because the unit is not ready until then).
|
|
*/
|
|
|
|
t_stat tl_onoffline (CVPTR cvptr, UNIT *uptr, t_bool online)
|
|
{
|
|
const uint32 unit = uptr - cvptr->device->units; /* the unit number */
|
|
t_stat status = SCPE_OK;
|
|
|
|
if (uptr->flags & UNIT_ATT) { /* if the unit is attached */
|
|
if (!(uptr->flags & UNIT_REWINDING) /* then if the tape is not rewinding */
|
|
&& uptr->flags & UNIT_OFFLINE /* and it is currently offline */
|
|
&& online) { /* and is going online */
|
|
cvptr->unit_attention |= 1 << unit; /* then the unit needs attention */
|
|
|
|
if (cvptr->state == Idle_State) /* if the controller is idle */
|
|
status = SCPE_INCOMP; /* then it must be called to poll the drives */
|
|
}
|
|
|
|
dpprintf (cvptr->device, TL_DEB_INCO, "Unit %d set %s\n",
|
|
unit, (online ? "online" : "offline"));
|
|
}
|
|
|
|
else /* otherwise the unit is detached and offline */
|
|
status = SCPE_UNATT; /* so it cannot be set online */
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
/* Return the current controller and unit status.
|
|
|
|
This routine returns the combined formatted controller and unit status for
|
|
the currently selected drive unit.
|
|
|
|
In hardware, the selected tape unit presents its status information
|
|
continuously to the controller. The 7970B and 7970E drives provide these
|
|
common status signals:
|
|
|
|
- SL = status online (selected * online)
|
|
- SLP = status load point (selected * online * at-load-point)
|
|
- SRW = status rewind (selected * online * rewinding)
|
|
- SET = status end-of-tape (selected * online * EOT-during-motion-forward)
|
|
- SR = status ready (selected * online * ~loading * ~rewinding)
|
|
- SFP = status file protect (selected * online * ~write-ring)
|
|
- SW = status write (selected * online * cmd-set-write * motion-forward)
|
|
|
|
Unique status signals for the 7970B are:
|
|
|
|
- S7 = status 7-track drive
|
|
|
|
For the 7970E, they are:
|
|
|
|
- SD2 = status density 200 bpi
|
|
- SD5 = status density 556 bpi
|
|
- SD8 = status density 800 bpi
|
|
- SD16 = status density 1600 bpi
|
|
- MTE = multiple-track-error
|
|
- STE = single-track-error
|
|
- TM = tape-mark
|
|
- IDB = ID burst
|
|
- EOB = end-of-block
|
|
|
|
In simulation, returned status is a combination of static controller status,
|
|
static unit status, and dynamic unit status. Static status is set during
|
|
command execution and persists until the next command begins. An example is
|
|
a data error or write protected status. Dynamic status is determined at the
|
|
point of the status call. An example is end-of-tape status.
|
|
|
|
The bits representing the returned status are controller-specific, so macros
|
|
are used to index into the status mapping table.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The HP 3000 controller presents the SR signal as "unit ready" status.
|
|
The 1000 controllers present the complement of the SR signal as "unit
|
|
busy" status and the complement of the SL signal as "unit local" status.
|
|
*/
|
|
|
|
uint16 tl_status (CVPTR cvptr)
|
|
{
|
|
UNIT *const uptr = cvptr->device->units + cvptr->unit_selected; /* a pointer to the selected unit */
|
|
uint32 status;
|
|
|
|
status = cvptr->status | CST_UNITSEL; /* merge the controller status and the selected unit number */
|
|
|
|
if (uptr->flags & UNIT_OFFLINE) /* if the unit is offline */
|
|
status |= DST_UNITBUSY | DST_UNITLOCL; /* then set not-ready and not-online status */
|
|
|
|
else { /* otherwise the unit is online */
|
|
status |= uptr->STATUS; /* so add the unit status */
|
|
|
|
if (uptr->flags & UNIT_REWINDING) /* if the tape is rewinding */
|
|
status |= DST_REWIND | DST_UNITBUSY; /* then set rewind and busy status */
|
|
|
|
else { /* otherwise */
|
|
status |= DST_UNITRDY; /* the unit is ready */
|
|
|
|
if (sim_tape_bot (uptr)) /* if the tape is positioned at the beginning */
|
|
status |= DST_LOADPT; /* then set load-point status */
|
|
|
|
else if (sim_tape_eot (uptr)) /* otherwise if the tape positioned after EOT */
|
|
status |= DST_EOT; /* then set end-of-tape status */
|
|
}
|
|
}
|
|
|
|
return LOWER_WORD (status); /* return the 16-bit combined controller and unit status */
|
|
}
|
|
|
|
|
|
/* Reset the controller.
|
|
|
|
This routine is called to perform a hard clear on the tape controller. It is
|
|
a harder clear than the clear performed by the "tl_clear" routine in
|
|
response to a programmed master reset or clear. This clear aborts any I/O in
|
|
progress, including rewinds, and resets the controller and all units to the
|
|
idle state. It is typically called by the interface in response to a RESET
|
|
command.
|
|
|
|
In addition, if this is a power-on reset, it sets up the property entry index
|
|
value in each unit.
|
|
*/
|
|
|
|
t_stat tl_reset (CVPTR cvptr)
|
|
{
|
|
uint32 unit;
|
|
UNIT *uptr;
|
|
DRIVE_TYPE drive;
|
|
|
|
tl_clear (cvptr); /* clear any in-progress writes */
|
|
|
|
if (sim_switches & SWMASK ('P')) /* if this is a power-on reset */
|
|
for (unit = 0; unit < cvptr->device->numunits; unit++) { /* then set up the unit property indices */
|
|
uptr = cvptr->device->units + unit; /* get a pointer to the unit */
|
|
drive = GET_MODEL (uptr->flags); /* and the current drive model ID */
|
|
|
|
if (validate_drive (cvptr, uptr, drive, 0) != SCPE_OK) /* if the drive's property index cannot be set */
|
|
return SCPE_IERR; /* then return internal error status */
|
|
}
|
|
|
|
for (unit = 0; unit < cvptr->device->numunits; unit++) { /* reset all of the drives */
|
|
uptr = cvptr->device->units + unit; /* get a pointer to the unit */
|
|
|
|
sim_tape_reset (uptr); /* reset the tape support library status */
|
|
sim_cancel (uptr); /* and cancel any in-process operation */
|
|
uptr->wait = NO_EVENT; /* and any scheduled operation */
|
|
|
|
uptr->PHASE = Idle_Phase; /* idle the unit */
|
|
uptr->OPCODE = Invalid_Opcode; /* and clear the opcode */
|
|
|
|
uptr->STATUS &= ~UST_WRSTAT; /* clear any write status */
|
|
uptr->flags &= ~UNIT_REWINDING; /* the unit is no longer rewinding */
|
|
}
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
|
|
/* Clear the controller.
|
|
|
|
This routine performs a hardware clear on the controller. It is equivalent
|
|
to asserting the CLEAR signal to the 3000 controller, which restarts the
|
|
controller microprogram, or executing the CLR command on the 1000
|
|
controllers. It clears any controller operation in progress and stops all
|
|
tape motion, except that drives with rewinds in progress are allowed to
|
|
complete the rewind. For the 3000 controller only, unit 0 is selected.
|
|
|
|
In simulation, if a clear is done before a write starts, the write is
|
|
abandoned. If it's done while a write is in progress, the record is
|
|
truncated at the current point and marked as bad.
|
|
|
|
In REALTIME mode only, if an in-progress read or spacing operation involves
|
|
an erase gap (indicated by the position change being greater than the record
|
|
length traversed), the simulated position is calculated. If it is within the
|
|
gap, the tape is repositioned and then aligned to a gap marker. Otherwise,
|
|
the position is unchanged, as repositioning within a record is not supported.
|
|
|
|
In FASTTIME mode, calculating tape position from the remaining traversal time
|
|
is unreliable, so it is not attempted.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The traverse phase is entered either when traversing an erase gap or when
|
|
rewinding. Once rewinding is excluded, a unit in the traverse phase must
|
|
be moving over a gap.
|
|
|
|
2. The REALTIME gap traversal position at the time of the controller clear
|
|
is calculated by determining how much of the gap had been traversed and
|
|
then adding or subtracting that from the initial position, depending on
|
|
the direction of travel. The gap traversal amount in bytes is the
|
|
original gap length minus the amount not yet traversed, which is
|
|
determined by dividing the remaining event delay time by the delay time
|
|
per byte. If the current (final) position is less than the initial
|
|
position, then the tape was moving backward, so the traversal amount is
|
|
subtracted from the initial position. Otherwise, the tape is moving
|
|
forward, and the amount is added.
|
|
|
|
3. In hardware, a clear asynchronously resets the state machine (1000
|
|
controllers) or restarts the microprogram (3000 controller). Therefore,
|
|
an aborted write will leave a bad record on the tape, as the normal write
|
|
sequence will not complete -- an NRZI drive will not have the trailing
|
|
CRCC and LRCC bytes, and a PE drive will not have the postamble and IRG.
|
|
In simulation, the record is written with the "bad data" indicator.
|
|
|
|
4. The only unit that may be in a non-idle phase without being active is the
|
|
controller unit.
|
|
*/
|
|
|
|
void tl_clear (CVPTR cvptr)
|
|
{
|
|
uint32 unit;
|
|
int32 remaining_time;
|
|
UNIT *uptr;
|
|
t_addr reset_position, relative_position;
|
|
t_mtrlnt marker;
|
|
|
|
for (unit = 0; unit < cvptr->device->numunits; unit++) { /* look for a write or gap traverse in progress */
|
|
uptr = cvptr->device->units + unit; /* get a pointer to the unit */
|
|
|
|
remaining_time = sim_activate_time (uptr); /* get the remaining unit delay time, if any */
|
|
|
|
if (remaining_time) { /* if the unit is currently active */
|
|
if (uptr->flags & UNIT_REWINDING) /* then a clear does not affect a rewind in progress */
|
|
dpprintf (cvptr->device, TL_DEB_INCO,
|
|
"Unit %d controller clear allowed %s to continue\n",
|
|
unit, opcode_names [uptr->OPCODE]);
|
|
|
|
else { /* but all other commands are aborted */
|
|
sim_cancel (uptr); /* so cancel any scheduled event */
|
|
|
|
if ((cvptr->device->flags & DEV_REALTIME) /* if REALTIME mode is selected */
|
|
&& uptr->PHASE == Traverse_Phase) { /* and a gap is being traversed */
|
|
relative_position = cvptr->gaplen /* then calculate the relative progress */
|
|
- remaining_time / cvptr->dlyptr->data_xfer;
|
|
|
|
if (uptr->pos < cvptr->initial_position) /* if the motion is backward */
|
|
reset_position = cvptr->initial_position - relative_position; /* then move toward the BOT */
|
|
else /* otherwise */
|
|
reset_position = cvptr->initial_position + relative_position; /* move toward the EOT */
|
|
|
|
cvptr->gaplen -= relative_position; /* reduce the gap length by the amount not traversed */
|
|
|
|
while (cvptr->gaplen > sizeof (t_mtrlnt)) { /* align the reset position to a gap marker */
|
|
if (sim_fseek (uptr->fileref, /* seek to the reset position */
|
|
reset_position, SEEK_SET) /* and if the seek succeeds */
|
|
|| sim_fread (&marker, sizeof (t_mtrlnt), /* then read the marker */
|
|
1, uptr->fileref) == 0) { /* if either call fails */
|
|
cprintf ("%s simulator tape library I/O error: %s\n", /* then report the error */
|
|
sim_name, strerror (errno)); /* to the console */
|
|
|
|
clearerr (uptr->fileref); /* clear the error and leave the file */
|
|
break; /* at the original position */
|
|
}
|
|
|
|
else if (marker == MTR_GAP) { /* otherwise if a gap marker was read */
|
|
uptr->pos = reset_position; /* then set the new position */
|
|
break; /* and repositioning is complete */
|
|
}
|
|
|
|
else { /* otherwise */
|
|
reset_position--; /* back up a byte and try again */
|
|
cvptr->gaplen--; /* until the gap is exhausted */
|
|
}
|
|
};
|
|
|
|
dpprintf (cvptr->device, TL_DEB_INCO,
|
|
"Unit %d controller clear stopped tape motion at position %d\n",
|
|
unit, uptr->pos);
|
|
}
|
|
|
|
else /* otherwise FASTTIME mode is selected */
|
|
dpprintf (cvptr->device, TL_DEB_INCO,
|
|
"Unit %d controller clear aborted %s after partial completion\n",
|
|
unit, opcode_names [uptr->OPCODE]);
|
|
|
|
if (cmd_props [uptr->OPCODE].class == Class_Write /* if the last command was a write */
|
|
&& cmd_props [uptr->OPCODE].transfer == TRUE /* that involves a data transfer */
|
|
&& uptr->PHASE == Data_Phase) /* that was not completed */
|
|
cvptr->call_status = MTSE_RECE; /* then the record will be bad */
|
|
|
|
uptr->PHASE = Stop_Phase; /* execute the stop phase of the command */
|
|
continue_command (cvptr, uptr, NO_FLAGS, NO_DATA); /* to ensure a partial record is written */
|
|
|
|
sim_tape_reset (uptr); /* reset the tape support library status */
|
|
}
|
|
}
|
|
|
|
else if (uptr->PHASE != Idle_Phase) { /* otherwise if the controller unit is executing */
|
|
uptr->PHASE = Idle_Phase; /* then idle it */
|
|
|
|
if (uptr->OPCODE != Clear_Controller) /* report the abort only if this isn't a clear command */
|
|
dpprintf (cvptr->device, TL_DEB_INCO,
|
|
"Unit %d controller clear aborted %s after partial completion\n",
|
|
unit, opcode_names [uptr->OPCODE]);
|
|
}
|
|
}
|
|
|
|
cvptr->status = 0; /* clear the controller status */
|
|
cvptr->state = Idle_State; /* and idle it */
|
|
cvptr->unit_attention = 0; /* clear any pending unit attention */
|
|
|
|
if (cvptr->type == HP_30215) /* if this is the 3000 controller */
|
|
cvptr->unit_selected = 0; /* then a clear selects unit 0 */
|
|
|
|
dpprintf (cvptr->device, TL_DEB_INCO, "Controller cleared\n");
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
/* Tape library global utility routines */
|
|
|
|
|
|
/* Return the name of an opcode.
|
|
|
|
A string representing the supplied controller opcode is returned to the
|
|
caller. If the opcode is invalid, an error string is returned.
|
|
*/
|
|
|
|
const char *tl_opcode_name (CNTLR_OPCODE opcode)
|
|
{
|
|
if (opcode < Invalid_Opcode) /* if the opcode is legal */
|
|
return opcode_names [opcode]; /* then return the opcode name */
|
|
else /* otherwise */
|
|
return opcode_names [Invalid_Opcode]; /* return an error indication */
|
|
}
|
|
|
|
|
|
/* Return the name of a unit.
|
|
|
|
A string representing the supplied unit is returned to the caller. If the
|
|
unit is invalid, an error string is returned.
|
|
*/
|
|
|
|
const char *tl_unit_name (uint32 unit)
|
|
{
|
|
if (unit <= TL_CNTLR_UNIT) /* if the unit number is valid */
|
|
return unit_names [unit]; /* then return the unit designator */
|
|
else /* otherwise */
|
|
return "Unit invalid"; /* return an error indication */
|
|
}
|
|
|
|
|
|
/* Tape library global SCP support routines */
|
|
|
|
|
|
/* Attach a tape image file to a unit.
|
|
|
|
The file specified by the supplied filename is attached to the indicated
|
|
unit. If the attach was successful, the drive is set online, and unit
|
|
attention is set.
|
|
|
|
If the controller is idle, the routine returns SCPE_INCOMP to indicate that
|
|
the caller must then call the controller to poll for drive attention to
|
|
complete the command. Otherwise, it returns SCPE_OK, and the drives will be
|
|
polled automatically when the current command completes and the controller is
|
|
idled.
|
|
|
|
Implementation notes:
|
|
|
|
1. The support library LOCKED and WRITEENABLED modifiers are not used to
|
|
specify a tape as read-only. All HP drives use a write ring on the back
|
|
of the tape reel, which must be dismounted to remove or add the ring.
|
|
Therefore, changing read-only status results in a remount, leaving the
|
|
tape at the load point. LOCKED and WRITEENABLED do not provide these
|
|
semantics. Instead, we require the -R option to the ATTACH command to
|
|
specify that a tape does not have a write ring.
|
|
|
|
2. If we are called during a RESTORE command, the unit's flags are not
|
|
changed to avoid upsetting the state that was SAVEd.
|
|
*/
|
|
|
|
t_stat tl_attach (CVPTR cvptr, UNIT *uptr, CONST char *cptr)
|
|
{
|
|
const uint32 unit = uptr - cvptr->device->units; /* the unit number */
|
|
t_stat result;
|
|
|
|
result = sim_tape_attach (uptr, cptr); /* attach the tape image file to the unit */
|
|
|
|
if (result == SCPE_OK /* if the attach was successful */
|
|
&& (sim_switches & SIM_SW_REST) == 0) { /* and we are not being called during a RESTORE command */
|
|
uptr->flags = uptr->flags & ~UNIT_OFFLINE; /* then set the unit online */
|
|
|
|
if (uptr->flags & UNIT_RO) /* if the attached file is read-only */
|
|
uptr->STATUS |= UST_WRPROT; /* then set write-protected status */
|
|
else /* otherwise */
|
|
uptr->STATUS &= ~UST_WRPROT; /* clear the status */
|
|
|
|
cvptr->unit_attention |= 1 << unit; /* drive attention sets on tape load */
|
|
|
|
dpprintf (cvptr->device, TL_DEB_INCO, "Unit %d tape loaded and set online\n",
|
|
unit);
|
|
|
|
if (cvptr->state == Idle_State) /* if the controller is idle */
|
|
result = SCPE_INCOMP; /* then it must be called to poll the drives */
|
|
}
|
|
|
|
return result; /* return the result of the attach */
|
|
}
|
|
|
|
|
|
/* Detach a tape image file from a unit.
|
|
|
|
The tape is unloaded from the drive, and the attached file, if any, is
|
|
detached. Unloading a tape leaves the drive offline. A command in progress
|
|
is allowed to continue to completion, unless it attempts to access the file.
|
|
If it does, the command will abort and simulation will stop with a "Unit not
|
|
attached" error message.
|
|
*/
|
|
|
|
t_stat tl_detach (UNIT *uptr)
|
|
{
|
|
uptr->flags &= ~UNIT_OFFLINE; /* set the unit offline */
|
|
|
|
return sim_tape_detach (uptr); /* detach the tape image file from the unit */
|
|
}
|
|
|
|
|
|
/* Set the controller timing mode.
|
|
|
|
This validation routine is called to set the timing mode for the tape
|
|
controller. The "value" parameter is set to 1 to enable realistic timing and
|
|
0 to enable optimized timing. The "desc" parameter is a pointer to the
|
|
controller.
|
|
|
|
When realistic timing is specified, unit activation delays are a function of
|
|
both the controller type and the tape drive model. As some controllers,
|
|
e.g., the HP 3000 controller, support several drive models, the delays must
|
|
be determined dynamically when a command is started on a given unit.
|
|
Therefore, this routine simply sets or clears the "real time" flag, which is
|
|
tested in the "start_command" routine.
|
|
*/
|
|
|
|
t_stat tl_set_timing (UNIT *uptr, int32 value, CONST char *cptr, void *desc)
|
|
{
|
|
DEVICE *const dptr = ((CVPTR) desc)->device; /* a pointer to the controlling device */
|
|
|
|
if (value) /* if realistic timing is requested */
|
|
dptr->flags |= DEV_REALTIME; /* then set the real time flag */
|
|
else /* otherwise */
|
|
dptr->flags &= ~DEV_REALTIME; /* clear it for fast timing */
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
|
|
/* Set the tape drive model.
|
|
|
|
This validation routine is called to set the model of the tape drive
|
|
associated with the specified unit. The "value" parameter indicates the
|
|
model ID. Support for the drive model with the specified controller is
|
|
verified before permitting the change.
|
|
*/
|
|
|
|
t_stat tl_set_model (UNIT *uptr, int32 value, CONST char *cptr, void *desc)
|
|
{
|
|
const CVPTR cvptr = (CVPTR) desc; /* the controller state structure pointer */
|
|
const DRIVE_TYPE new_drive = GET_MODEL (value); /* the new model ID */
|
|
|
|
return validate_drive (cvptr, uptr, new_drive, 0); /* verify the model change and set the property index */
|
|
}
|
|
|
|
|
|
/* Set the tape drive density.
|
|
|
|
This validation routine is called to set the density of the tape drive
|
|
associated with the specified unit via a "SET <unit> DENSITY=<bpi>" command.
|
|
The "cptr" parameter points to the <bpi> string, which must be a value
|
|
supported by the controller and tape drive model. Support for the new
|
|
density setting is verified before permitting the change.
|
|
*/
|
|
|
|
t_stat tl_set_density (UNIT *uptr, int32 value, CONST char *cptr, void *desc)
|
|
{
|
|
const CVPTR cvptr = (CVPTR) desc; /* the controller state structure pointer */
|
|
const DRIVE_TYPE model = GET_MODEL (uptr->flags); /* the current drive model ID */
|
|
uint32 new_bpi;
|
|
t_stat status;
|
|
|
|
if (cptr == NULL || *cptr == '\0') /* if no density value is present */
|
|
return SCPE_MISVAL; /* then report a missing value error */
|
|
|
|
new_bpi = (uint32) get_uint (cptr, 10, UINT_MAX, &status); /* parse the supplied value */
|
|
|
|
if (status != SCPE_OK) /* if a parsing failure occurred */
|
|
return status; /* then report the problem */
|
|
|
|
else if (new_bpi == 0) /* otherwise if the new density is zero */
|
|
return SCPE_ARG; /* then disallow it */
|
|
|
|
else /* otherwise a numeric value is present */
|
|
return validate_drive (cvptr, uptr, model, new_bpi); /* so validate the density change */
|
|
}
|
|
|
|
|
|
/* Set the tape drive reel capacity.
|
|
|
|
This validation routine is called to set the tape reel capacity of the tape
|
|
drive associated with the specified unit. The "value" parameter indicates
|
|
whether the capacity in megabytes (0) or the tape length in feet (1) was
|
|
specified.
|
|
|
|
If the capacity was specified with a "SET <unit> CAPACITY=<n>" command, the
|
|
simulator tape support library routine "sim_tape_set_capac" is called to
|
|
parse the size in megabytes and set the unit capacity value. If the routine
|
|
succeeded, any existing reel size is cleared.
|
|
|
|
If the capacity was specified with a "SET <unit> REEL=<length>" command, the
|
|
"cptr" parameter points to the <length> string, which must be one of the
|
|
standard reel sizes (600, 1200, or 2400-foot). If the reel size is 0, the
|
|
capacity is reset to the unlimited size. Otherwise, the reel size ID is set
|
|
in the unit property value, and the unit capacity in bytes is calculated and
|
|
set from the reel size and tape drive density. If the reel size is not a
|
|
valid value, the routine returns SCPE_ARG to indicate an invalid argument was
|
|
supplied.
|
|
*/
|
|
|
|
t_stat tl_set_reelsize (UNIT *uptr, int32 value, CONST char *cptr, void *desc)
|
|
{
|
|
const uint32 tape_bpi = drive_props [PROP_INDEX (uptr)].bpi; /* the tape unit density */
|
|
int32 reel;
|
|
t_stat status;
|
|
|
|
if (value == 0) { /* if the capacity is being specified directly */
|
|
status = sim_tape_set_capac (uptr, value, cptr, desc); /* then set it using the supplied size in megabytes */
|
|
|
|
if (status == SCPE_OK) /* if the supplied capacity is OK */
|
|
uptr->PROP = uptr->PROP & ~PROP_REEL_MASK /* then clear any prior reel size setting */
|
|
| PROP_REEL_UNLIM;
|
|
return status;
|
|
}
|
|
|
|
else if (cptr == NULL) /* otherwise if no reel size is present */
|
|
return SCPE_ARG; /* then return an invalid argument error */
|
|
|
|
else { /* otherwise a size is specified */
|
|
reel = (int32) get_uint (cptr, 10, 2400, &status); /* so parse the tape length in feet */
|
|
|
|
if (status != SCPE_OK) /* if the parse failed */
|
|
return status; /* then return the failure status */
|
|
|
|
else { /* otherwise */
|
|
switch (reel) { /* validate the reel size */
|
|
|
|
case 0: /* an unlimited-length reel */
|
|
uptr->capac = 0; /* so set the capacity */
|
|
uptr->PROP = uptr->PROP & ~PROP_REEL_MASK /* and reel property to "unlimited" */
|
|
| PROP_REEL_UNLIM;
|
|
break;
|
|
|
|
case 600: /* a 600 foot reel */
|
|
uptr->capac = 600 * 12 * tape_bpi; /* so set the capacity in bytes */
|
|
uptr->PROP = uptr->PROP & ~PROP_REEL_MASK /* and set the reel property ID */
|
|
| PROP_REEL_600;
|
|
break;
|
|
|
|
case 1200: /* a 1200 foot reel */
|
|
uptr->capac = 1200 * 12 * tape_bpi;
|
|
uptr->PROP = uptr->PROP & ~PROP_REEL_MASK
|
|
| PROP_REEL_1200;
|
|
break;
|
|
|
|
case 2400: /* a 2400 foot reel */
|
|
uptr->capac = 2400 * 12 * tape_bpi;
|
|
uptr->PROP = uptr->PROP & ~PROP_REEL_MASK
|
|
| PROP_REEL_2400;
|
|
break;
|
|
|
|
default: /* other length values */
|
|
return SCPE_ARG; /* are invalid */
|
|
}
|
|
|
|
return SCPE_OK; /* a valid reel size is accepted */
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* Show the controller timing mode.
|
|
|
|
This display routine is called to show the timing mode for the tape
|
|
controller. The "desc" parameter is a pointer to the controller.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The explicit use of "const CNTLR_VARS *" is necessary to declare a
|
|
pointer to a constant structure. Using "const CVPTR" declares a constant
|
|
pointer instead.
|
|
*/
|
|
|
|
t_stat tl_show_timing (FILE *st, UNIT *uptr, int32 value, CONST void *desc)
|
|
{
|
|
DEVICE *const dptr = ((const CNTLR_VARS *) desc)->device; /* a pointer to the controlling device */
|
|
|
|
if (dptr->flags & DEV_REALTIME) /* if the real time flag is set */
|
|
fputs ("realistic timing", st); /* then we're using realistic timing */
|
|
else /* otherwise */
|
|
fputs ("fast timing", st); /* we're using optimized timing */
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
|
|
/* Show the tape drive density.
|
|
|
|
This display routine is called to show the density in bits per inch of the
|
|
tape drive associated with the specified unit. The "uptr" parameter points
|
|
to the unit to be queried.
|
|
*/
|
|
|
|
t_stat tl_show_density (FILE *st, UNIT *uptr, int32 value, CONST void *desc)
|
|
{
|
|
fprintf (st, "%d bpi", drive_props [PROP_INDEX (uptr)].bpi);
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
|
|
/* Show the tape drive reel capacity.
|
|
|
|
This display routine is called to show the tape reel capacity of the tape
|
|
drive associated with the unit pointed to by the "uptr" parameter. The
|
|
"value" parameter indicates whether the reel size was requested explicitly
|
|
via a "SHOW <unit> REEL" command (1), or whether the capacity was requested
|
|
explicitly via a "SHOW <unit> CAPACITY" command or implicitly via "SHOW
|
|
<dev>" or "SHOW <unit>" commands (0). The "desc" parameter is a pointer to
|
|
the controller.
|
|
|
|
If the reel capacity is unlimited or is a specified size in megabytes, the
|
|
simulator tape support library routine "sim_tape_show_capac" is called to
|
|
report the unlimited size or the size in megabytes. Otherwise, the specified
|
|
length in feet is reported.
|
|
|
|
The reel size format modifier is a "named SHOW only" entry, so the caller
|
|
does not add a trailing newline after the routine returns. Therefore, we
|
|
have to add it here.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. Reel size IDs for 600, 1200, and 2400 foot reels must be 1, 2, and 3,
|
|
respectively, to provide multiplication by 2 ** <reel ID>.
|
|
*/
|
|
|
|
t_stat tl_show_reelsize (FILE *st, UNIT *uptr, int32 value, CONST void *desc)
|
|
{
|
|
t_stat status = SCPE_OK;
|
|
|
|
if (PROP_REEL (uptr) == Reel_Unlimited) /* if the unit has unlimited or custom size */
|
|
status = sim_tape_show_capac (st, uptr, value, desc); /* then display the capacity in bytes */
|
|
|
|
else /* otherwise */
|
|
fprintf (st, "%4d foot reel", 300 << PROP_REEL (uptr)); /* display the reel size in feet */
|
|
|
|
if (value == 1) /* if we're called to display the reel size */
|
|
fputc ('\n', st); /* then add a newline, as the caller will omit it */
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
|
|
/* Tape library local controller routines */
|
|
|
|
|
|
/* Start a new command.
|
|
|
|
This routine is called to validate and optionally begin execution of a new
|
|
command. It is called when the controller is waiting for a command and the
|
|
interface asserts CMRDY and/or CMXEQ to indicate that a new command is
|
|
available. It returns a set of action functions and a data word to the
|
|
caller. For a good command with CMXEQ asserted, it also sets up the next
|
|
phase of operation on the controller and/or drive unit and schedules the
|
|
unit(s) as appropriate.
|
|
|
|
On entry, the command opcode is supplied in the "inbound_data" parameter and
|
|
applies to the currently selected unit. The current delay properties pointer
|
|
is set up, depending on the timing mode, and the opcode is checked for
|
|
validity. If it fails the check, the command is rejected. If it's OK and is
|
|
a Unit Select command, it is executed immediately. Otherwise, if the CMXEQ
|
|
flag is set, command execution is scheduled.
|
|
|
|
The routine asserts the IFGTC function to inform the interface that the
|
|
command was executed. If the command transfers data, the class of the
|
|
command (read or write) is returned, so that the interface may ensure that
|
|
the correct type of channel transfer is set up. All other commands,
|
|
including write commands that do not transfer data (e.g., Write File Mark),
|
|
are classified as control commands to indicate that no channel transfer is
|
|
required.
|
|
|
|
A command rejection occurs on the 1000 controllers for these reasons:
|
|
|
|
- the unit is not ready for any command requiring tape motion
|
|
- the tape has no write ring and a write command is issued
|
|
- a Select Unit command is issued while the controller is busy
|
|
- a Backspace Record or Backspace File command is issued with the tape
|
|
positioned at the load point
|
|
|
|
A command rejection occurs on the 3000 controller for these reasons:
|
|
|
|
- the unit is not ready for any command requiring tape motion
|
|
- the tape has no write ring and a write command is issued
|
|
- an illegal command opcode is issued
|
|
- illegal bits are set in the control word
|
|
- a command is issued while the controller is busy
|
|
- a TOGGLEOUTXFER signal asserts without a write data command in process
|
|
- a TOGGLEINXFER signal asserts without a read data command in process
|
|
- a PCONTSTB signal asserts with the input or output transfer flip-flops set
|
|
|
|
Examples of the last three rejection reasons are:
|
|
|
|
- a Write File Mark control order is followed by a write channel order
|
|
- a Write Record control order is followed by a read channel order
|
|
- a write channel order is followed by a Write Record control order
|
|
|
|
An interface may force a command reject by passing the Invalid_Opcode value
|
|
as the command to validate.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The 1000 controllers separate validity checking from execution. The
|
|
former occurs when an OTx instruction is issued; the latter occurs when
|
|
an STC instruction is issued. The flags that accompany instruction
|
|
execution are CMRDY for OTx and CMXEQ for STC.
|
|
|
|
The 3000 controller combines these two functions, and the flags
|
|
accompanying the single call are CMRDY and CMXEQ.
|
|
|
|
2. The Select_Unit_n command opcode values are contiguous, so that the
|
|
unit selected may be set directly from the opcode value.
|
|
|
|
3. The 3000 controller hardware executes a Select Unit command in about 10
|
|
microcode instructions from PCONTSTB to drive select output and service
|
|
request assertion, which is about 4.3 microseconds (the microinstruction
|
|
time is 434 nanoseconds). The 1000 controllers clock the Select Unit
|
|
command directly into a register with the IOO signal; this effectively
|
|
selects the unit "immediately." In simulation, the Select Unit command
|
|
completes without scheduling an event delay.
|
|
|
|
4. For the 3000 controller, command rejection requires a delay between
|
|
detection and interrupt assertion, which must be scheduled on the
|
|
controller unit (all drive units may be busy, e.g., rewinding, so we
|
|
can't schedule on them).
|
|
|
|
5. All tape drives ignore a rewind command if the tape is positioned at the
|
|
load point. The 3000 controller will complete the command immediately,
|
|
whereas the 1000 controllers still will delay for the rewind start time
|
|
before completing.
|
|
|
|
6. The "drive ready" condition exists if the unit is online and not
|
|
rewinding.
|
|
*/
|
|
|
|
static CNTLR_IFN_IBUS start_command (CVPTR cvptr, CNTLR_FLAG_SET flags, CNTLR_OPCODE opcode)
|
|
{
|
|
UNIT *const uptr = cvptr->device->units + cvptr->unit_selected; /* a pointer to the currently selected unit */
|
|
CNTLR_IFN_IBUS outbound;
|
|
|
|
if (cvptr->device->flags & DEV_REALTIME) /* if realistic timing is selected */
|
|
cvptr->dlyptr = &real_times [PROP_INDEX (uptr)]; /* then get the real times pointer for this drive */
|
|
else /* otherwise optimized timing is selected */
|
|
cvptr->dlyptr = cvptr->fastptr; /* so use the fast times pointer */
|
|
|
|
if ((flags & CMRDY) /* if command validity is to be checked */
|
|
&& (opcode >= Invalid_Opcode /* and the opcode is invalid */
|
|
|| cvptr->type > LAST_CNTLR /* or the controller type is undefined */
|
|
|| cmd_props [opcode].valid [cvptr->type] == FALSE /* or the opcode is not valid for this controller */
|
|
|| cmd_props [opcode].ready /* or it requires a ready drive */
|
|
&& uptr->flags & (UNIT_OFFLINE | UNIT_REWINDING) /* but it's offline or rewinding */
|
|
|| cmd_props [opcode].class == Class_Write /* or this is a write command */
|
|
&& sim_tape_wrp (uptr) /* but the drive is write-protected */
|
|
|| opcode != Clear_Controller /* or it's not a controller clear */
|
|
&& cvptr->state != Idle_State)) { /* and the controller is busy */
|
|
CNTLR_UPTR->OPCODE = opcode; /* then save the rejected code */
|
|
reject_command (cvptr, NULL); /* and reject the command */
|
|
outbound = NO_ACTION; /* with no further action */
|
|
}
|
|
|
|
else { /* otherwise the command is (assumed to be) OK */
|
|
if (cmd_props [opcode].class == Class_Write) /* if this is a write command */
|
|
uptr->STATUS |= UST_WRSTAT; /* then set write status */
|
|
else /* otherwise */
|
|
uptr->STATUS &= ~UST_WRSTAT; /* clear it */
|
|
|
|
cvptr->status = 0; /* clear the controller status for the new command */
|
|
|
|
cvptr->call_status = MTSE_OK; /* clear the last library call status code */
|
|
cvptr->index = 0; /* set up the */
|
|
cvptr->length = 0; /* buffer access */
|
|
cvptr->gaplen = 0; /* and clear the gap length */
|
|
|
|
if (opcode >= Select_Unit_0 && opcode <= Select_Unit_3) { /* if the opcode is a Select Unit command */
|
|
cvptr->unit_selected = (uint32) (opcode - Select_Unit_0); /* then select the indicated unit */
|
|
|
|
dpprintf (cvptr->device, TL_DEB_INCO, "%s completed\n",
|
|
opcode_names [opcode]);
|
|
|
|
dpprintf (cvptr->device, TL_DEB_CMD, "%s succeeded\n",
|
|
opcode_names [opcode]);
|
|
|
|
outbound = IFGTC | RQSRV | Class_Control; /* indicate that a control command was executed */
|
|
}
|
|
|
|
else if (flags & CMXEQ) { /* otherwise if the command is to be executed */
|
|
cvptr->state = Busy_State; /* then set the controller */
|
|
cvptr->status = CST_IFBUSY; /* and the interface to the busy state */
|
|
|
|
uptr->OPCODE = opcode; /* save the opcode in the selected unit */
|
|
|
|
if (cmd_props [opcode].transfer) { /* if this command transfers data */
|
|
CNTLR_UPTR->PHASE = Wait_Phase; /* then set up the wait phase */
|
|
CNTLR_UPTR->OPCODE = opcode; /* and command opcode on the controller unit */
|
|
|
|
outbound = IFGTC | RQSRV | cmd_props [opcode].class; /* return the transfer class (read or write) */
|
|
}
|
|
|
|
else { /* otherwise it's a control command */
|
|
uptr->PHASE = Start_Phase; /* so set up the start phase */
|
|
uptr->wait = cvptr->dlyptr->overhead; /* and the initial controller delay */
|
|
|
|
if (cmd_props [opcode].ready) /* if the command accesses the drive */
|
|
if (cmd_props [opcode].class != Class_Rewind) /* then if this is not a rewind command */
|
|
if (sim_tape_bot (uptr)) /* then if the tape is positioned at the BOT */
|
|
uptr->wait += cvptr->dlyptr->bot_start; /* then the start delay is longer */
|
|
else /* than it would be if the position */
|
|
uptr->wait += cvptr->dlyptr->ir_start; /* is between two records */
|
|
|
|
else if (! sim_tape_bot (uptr) /* otherwise if the rewind is not from the load point */
|
|
|| cvptr->type != HP_30215) /* or this is not the 3000 controller */
|
|
uptr->wait += cvptr->dlyptr->rewind_start; /* then add the rewind start delay */
|
|
|
|
activate_unit (cvptr, uptr); /* schedule the start phase */
|
|
|
|
outbound = IFGTC | Class_Control; /* indicate a control command is executing */
|
|
}
|
|
}
|
|
|
|
else /* otherwise */
|
|
outbound = NO_ACTION; /* command execution was not requested */
|
|
}
|
|
|
|
return outbound; /* return the functions and data word */
|
|
}
|
|
|
|
|
|
/* Continue the current command.
|
|
|
|
This routine simulates continuing execution of the controller microcode or
|
|
state machine for the current command. It is called whenever the controller
|
|
has had to wait for action from the CPU interface or the drive unit, and that
|
|
action has now occurred. Typically, this would be whenever the interface
|
|
flag status changes, or a unit's event service has been entered. It returns
|
|
a set of action functions and a data word to the caller. It also sets up the
|
|
next phase of operation on the controller and/or drive unit and schedules the
|
|
unit(s) as appropriate.
|
|
|
|
On entry, the "uptr" parameter points to the unit whose event is to be
|
|
serviced; this may be either the controller unit or a drive unit. The
|
|
"inbound_flags" and "inbound_data" parameters contain the CPU interface flags
|
|
and data buffer values.
|
|
|
|
While a unit is activated, the current phase indicates the reason for the
|
|
activation, i.e., what the simulated drive is "doing" at the moment, as
|
|
follows:
|
|
|
|
Idle_Phase -- Waiting for the next command to be issued (note that
|
|
this phase, which indicates that the unit is not
|
|
scheduled, is distinct from the controller Idle_State,
|
|
which indicates that the controller itself is idle).
|
|
|
|
Wait_Phase -- Waiting for the channel data transfer program to start.
|
|
|
|
Start_Phase -- Waiting for the drive to come up to speed after
|
|
starting.
|
|
|
|
Traverse_Phase -- Waiting for the drive to traverse an erase gap preceding
|
|
a data record.
|
|
|
|
Data_Phase -- Waiting for the drive to traverse a data record or
|
|
accept or return the next data bytes to be transferred
|
|
to or from the tape.
|
|
|
|
Stop_Phase -- Waiting for the drive to slow to a stop.
|
|
|
|
Error_Phase -- Waiting to interrupt for a command abort.
|
|
|
|
Depending on the current command opcode and phase, a number of actions may be
|
|
taken:
|
|
|
|
Idle_Phase -- Attempting to continue execution of an idle unit causes
|
|
a command rejection error. For the 3000, this implies a
|
|
TOGGLEINXFER or TOGGLEOUTXFER or READNEXTWD signal was
|
|
asserted with no command in progress.
|
|
|
|
Wait_Phase -- If the EOD flag is set, then reject the command.
|
|
Otherwise, the correct type of channel transfer has
|
|
started, so start the tape motion and set up the start
|
|
phase.
|
|
|
|
Start_Phase -- If a write data command is executing, request the first
|
|
data word from the interface. A Write File Mark command
|
|
sets up the traverse phase to write an erase gap if the
|
|
tape is at BOT and the mode is REALTIME; otherwise, the
|
|
data phase is set up. A read data command reads the
|
|
record from the tape image file and sets up the traverse
|
|
or data phases, depending on whether or not an erase gap
|
|
was present. A file or record positioning command
|
|
spaces the file in the indicated direction and sets up
|
|
the traverse, start, or stop phases, depending on
|
|
whether a gap was present and the command continues or
|
|
is complete. A Write Gap command sets up the traverse
|
|
phase to write the gap to the file. A rewind command
|
|
completes immediately or sets up the stop phase if the
|
|
tape is positioned at BOT, or sets up the traverse phase
|
|
to rewind the file.
|
|
|
|
Traverse_Phase -- A Write Gap or rewind command sets up the stop phase. A
|
|
write, read, or spacing command sets up the data phase.
|
|
A gap traversal that ended with an error, e.g., runaway
|
|
or a tape mark, sets up the stop phase.
|
|
|
|
Data_Phase -- For read transfers, return the next word from the record
|
|
buffer, or for write transfers, store the next word into
|
|
the record buffer, and schedule the next data phase
|
|
transfer if the CPU has not indicated an end-of-data
|
|
condition. If it has, or if the last word of the buffer
|
|
has been transmitted, schedule the stop phase. For a
|
|
Write File Mark command, write the tape mark to the
|
|
file and set up the stop phase.
|
|
|
|
Stop_Phase -- A write data command writes the completed record to the
|
|
file. A Rewind command sets unit attention and then
|
|
rewinds the image file. In all cases, idle the unit and
|
|
controller and mark the command as complete.
|
|
|
|
Error_Phase -- A command abort has delayed until the tape motion has
|
|
stopped, so idle the unit and assert the STINT function
|
|
to request an interrupt.
|
|
|
|
Phase assignments are validated on entry, and SCPE_IERR (Internal Error) is
|
|
returned if entry is made with a phase that is not valid for a given
|
|
operation.
|
|
|
|
At the completion of the current phase, the next phase is scheduled, if
|
|
required, before returning the appropriate function set and data word to the
|
|
caller. If the current phase is continuing, the service activation time will
|
|
be set appropriately.
|
|
|
|
The commands employ the various phases as follows:
|
|
|
|
Command Wait Start Trav Data Stop
|
|
+-----------------------------+-------+-------+-------+-------+-------+
|
|
| Select_Unit_0 | - | - | - | - | - |
|
|
| Select_Unit_1 | - | - | - | - | - |
|
|
| Select_Unit_2 | - | - | - | - | - |
|
|
| Select_Unit_3 | - | - | - | - | - |
|
|
| Clear_Controller | - | C | - | - | N |
|
|
| Read_Record | X | R | t | D | N |
|
|
| Read_Record_with_CRCC | X | R | t | D | N |
|
|
| Read_Record_Backward | X | R | t | D | N |
|
|
| Read_File_Forward | X | R | t | D | N |
|
|
| Write_Record | X | g | t | D | W |
|
|
| Write_Record_without_Parity | X | g | t | D | W |
|
|
| Write_File_Mark | - | g | t | F | N |
|
|
| Write_Gap | - | G | T | - | N |
|
|
| Write_Gap_and_File_Mark | - | G | T | F | N |
|
|
| Forward_Space_Record | - | S | t | d | N |
|
|
| Forward_Space_File | - | S | t | d | N |
|
|
| Backspace_Record | - | S | t | d | N |
|
|
| Backspace_File | - | S | t | d | N |
|
|
| Rewind | - | N | B | - | E |
|
|
| Rewind_Offline | - | N | B | - | E |
|
|
+-----------------------------+-------+-------+-------+-------+-------+
|
|
|
|
Key:
|
|
|
|
B = traverse the tape while rewinding to BOT
|
|
C = clear the controller
|
|
D = traverse the record while transferring data via the record buffer
|
|
d = traverse the record without transferring data
|
|
E = rewind the tape file
|
|
F = write a file mark to the tape file
|
|
G = write a gap to the tape file
|
|
g = write a gap to the tape file only if REALTIME and BOT
|
|
N = no action other than setting up the next phase
|
|
R = read a record from the tape file
|
|
S = space over a record in the tape file
|
|
T = traverse an erase gap
|
|
t = traverse an erase gap only if present
|
|
W = write a record to the tape file
|
|
X = wait for a channel transfer to start
|
|
- = skip the phase
|
|
|
|
For the 3000 controller, a unit preparing to read or write data must wait for
|
|
the channel transfer to start. The wait phase is set up on the controller
|
|
unit, and the operation pauses until a read or write channel order is issued.
|
|
The EOD flag is used to ensure that the correct type of order is issued. The
|
|
interface sets the EOD flag as part of IFGTC processing and clears it if the
|
|
type of channel order matches the type of tape command, e.g., a read order is
|
|
issued for a Read Record command. If the wrong type of order is issued,
|
|
e.g., a write order for a Read Record command, EOD will be set on entry in
|
|
the wait state; this will cause a command reject.
|
|
|
|
Within the data phase of a write command, the interface sets EOD to indicate
|
|
the end of the data record. For a read command, the interface generally
|
|
doesn't know the length of the next record. If the programmed transfer
|
|
length is shorter than the record length, the interface will set EOD to
|
|
indicate that the remainder of the data record is not wanted. If the record
|
|
length is shorter than the transfer length, the controller will assert the
|
|
DVEND function to indicate that the device has ended the transfer
|
|
prematurely.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The Forward Space Record, Forward Space File, Backspace Record, and
|
|
Backspace File commands schedule data phases to traverse the data record,
|
|
but not data is transferred to the interface.
|
|
|
|
2. The 7970 ignores a rewind command if the tape is positioned at the load
|
|
point. The 3000 controller completes such a command immediately; as the
|
|
Unit Ready signal from the drive never denies, drive attention is never
|
|
set, i.e., the rewind completes without the usual unit interrupt. The
|
|
1000 controllers set Interface Busy status for 0.56 millisecond,
|
|
regardless of the tape position, and complete with the Command Flag set.
|
|
|
|
3. The 30215 controller does not detect tape runaway when spacing in the
|
|
reverse direction. If the support library "space record reverse" routine
|
|
returns MTSE_RUNAWAY status, the error status is cleared, but the buffer
|
|
length is set to 0 to indicate that the spacing did not encompass a data
|
|
record. After traversing the gap, the spacing will be retried.
|
|
|
|
4. Support library errors that occur after traversing an erase gap, e.g.,
|
|
encountering a tape mark, schedule the traversal phase for the gap but
|
|
set the controller into the error state to terminate the command after
|
|
traversal.
|
|
|
|
5. If the EOD flag is set by the interface before the entire data record is
|
|
transferred, the stop phase delay for the interrecord gap is lengthened
|
|
by an amount corresponding to the length of the data record remaining.
|
|
|
|
6. Data record read error status (MTSE_RECE) is initially treated as
|
|
successful to allow the transfer of the record data to the interface.
|
|
After the stop phase has been processed, the error status is restored to
|
|
abort the tape command.
|
|
|
|
7. The Clear Controller command must be scheduled on the controller unit, as
|
|
all four tape units may be executing rewinds in progress, which must be
|
|
allowed to complete normally.
|
|
|
|
8. A write of a null record is reported as a tape error. This may occur if
|
|
a Write_Record_without_Parity (WRZ) writes an all-zeros buffer (a byte
|
|
with zero parity won't be seen, as it has no clocking information).
|
|
|
|
9. The 13181 controller places the CRCC and LRCC into the interface output
|
|
data register after completion of a read or write record command. This
|
|
behavior is undocumented but is used by the HP 1000 tape diagnostic. To
|
|
provide this functionality in simulation, the controller calculates the
|
|
CRCC and LRCC from the current record buffer if it is configured as a
|
|
13181 and is in REALTIME mode and returns the CRCC/LRCC in an outbound
|
|
data word at the end of a read or write transfer. The 13181 interface
|
|
simulator must store this word in its output data register if IFOUT is
|
|
asserted with EOD set; the IFIN handler stores the outbound value in the
|
|
data register normally, so no special handling is needed for this case.
|
|
This "extra" word is not accompanied by a channel service request, so it
|
|
does not form part of the data record transferred.
|
|
|
|
10. The 13181 and 13183 controllers check and report odd-length records while
|
|
spacing. The 30215 controller checks and reports only for reads.
|
|
|
|
11. The 13181 and 13183 controllers terminate the Forward Space File command
|
|
at the end of the first record past the end of tape marker.
|
|
|
|
12. The 30215 controller always indicates an even number of bytes for a partial
|
|
transfer, even if the record itself is an odd length. Odd-length status
|
|
for the other controllers always reflects the last record read or spaced
|
|
over.
|
|
|
|
13. Erase gaps must be written to the image file during the start phase so
|
|
that a controller clear during the traverse phase can reposition into the
|
|
gap appropriately.
|
|
|
|
14. Data phase trace statements number data bytes or words transferred from 1
|
|
to the record length, rather than from 0 to the length - 1.
|
|
|
|
15. The data record buffer size is limited. If an attempt is made to write a
|
|
record that is larger than the buffer, the record is truncated at the
|
|
buffer size and marked as "bad", and a Transfer Error interrupt occurs.
|
|
|
|
Tape diagnostics usually attempt to write a single record that
|
|
encompasses the entire tape reel by supplying data until "end of tape"
|
|
status is seen. Continuing to accept and discard data after the buffer
|
|
size is exceeded isn't practicable, as the unit position isn't updated
|
|
until the record is written, and therefore the caller would never see the
|
|
EOT status that is used to terminate the write.
|
|
|
|
16. The tape drive is a synchronous device, so overrun or underrun can occur
|
|
if the interface is not ready when the controller must transfer data. An
|
|
overrun occurs when the controller is ready with a tape read word (IFIN),
|
|
but the interface buffer is full (DTRDY). An underrun occurs when the
|
|
controller needs a tape write word (IFOUT), but the interface buffer is
|
|
empty (~DTRDY). These conditions are detected by the interface and
|
|
communicated to the controller by the OVRUN flag.
|
|
|
|
17. Unit attention is set at the completion of a Rewind or Rewind_Offline if
|
|
the unit is online. The status cannot be inferred from the command, as
|
|
the user may have set the unit offline or online explicitly before the
|
|
rewind completed.
|
|
*/
|
|
|
|
static CNTLR_IFN_IBUS continue_command (CVPTR cvptr, UNIT *uptr, CNTLR_FLAG_SET inbound_flags, CNTLR_IBUS inbound_data)
|
|
{
|
|
const CNTLR_OPCODE opcode = (CNTLR_OPCODE) uptr->OPCODE; /* the current command opcode */
|
|
const CNTLR_PHASE phase = (CNTLR_PHASE) uptr->PHASE; /* the current command phase */
|
|
const t_bool service_entry = (phase > Wait_Phase); /* TRUE if entered via unit service */
|
|
uint32 unit;
|
|
uint8 data_byte;
|
|
t_mtrlnt error_flag;
|
|
BYTE_SELECTOR selector;
|
|
DRIVE_PROPS const *pptr;
|
|
CNTLR_IFN_IBUS outbound = NO_ACTION;
|
|
t_bool complete = FALSE;
|
|
|
|
unit = uptr - cvptr->device->units; /* get the unit number */
|
|
|
|
dpprintf (cvptr->device, TL_DEB_STATE, "%s %s %s phase entered from %s\n",
|
|
unit_names [unit], opcode_names [opcode], phase_names [phase],
|
|
(service_entry ? "service" : "interface"));
|
|
|
|
|
|
switch (phase) { /* dispatch the phase */
|
|
|
|
case Idle_Phase:
|
|
reject_command (cvptr, uptr); /* reject if no command is in progress */
|
|
break;
|
|
|
|
|
|
case Wait_Phase:
|
|
if (inbound_flags & EOD) /* if the EOD flag is set */
|
|
reject_command (cvptr, uptr); /* then the wrong channel order was issued */
|
|
|
|
else { /* otherwise the channel is ready to transfer */
|
|
uptr->OPCODE = Invalid_Opcode; /* so clear the controller command */
|
|
uptr->PHASE = Idle_Phase; /* and idle the unit */
|
|
|
|
unit = cvptr->unit_selected; /* get the selected unit number */
|
|
uptr = cvptr->device->units + unit; /* and unit pointer */
|
|
|
|
uptr->PHASE = Start_Phase; /* set up the start phase */
|
|
uptr->wait = cvptr->dlyptr->overhead; /* and the initial controller delay */
|
|
|
|
if (sim_tape_bot (uptr)) /* if the tape is positioned at the BOT */
|
|
uptr->wait += cvptr->dlyptr->bot_start; /* then the start delay is longer */
|
|
else /* than it would if the position */
|
|
uptr->wait += cvptr->dlyptr->ir_start; /* is between records */
|
|
}
|
|
break;
|
|
|
|
|
|
case Start_Phase:
|
|
dpprintf (cvptr->device, TL_DEB_INCO, "Unit %d %s started at position %d\n",
|
|
unit, opcode_names [opcode], uptr->pos);
|
|
|
|
pptr = &drive_props [PROP_INDEX (uptr)]; /* get the drive property pointer */
|
|
|
|
cvptr->initial_position = uptr->pos; /* save the initial tape position */
|
|
|
|
switch (opcode) { /* dispatch the current operation */
|
|
|
|
case Clear_Controller:
|
|
tl_clear (cvptr); /* clear the controller */
|
|
|
|
uptr->PHASE = Stop_Phase; /* schedule the completion */
|
|
uptr->wait = cvptr->dlyptr->ir_start; /* after motion ceases */
|
|
break;
|
|
|
|
|
|
case Read_Record:
|
|
case Read_Record_with_CRCC:
|
|
outbound = /* read the next record from the file */
|
|
call_tapelib (cvptr, uptr, lib_read_fwd, TL_MAXREC);
|
|
|
|
if ((outbound & SCPE) == NO_FLAGS) { /* if the read succeeded */
|
|
if (cvptr->length & 1) /* then if the record length was odd */
|
|
cvptr->status |= CST_ODDLEN; /* then set the corresponding status */
|
|
|
|
if (cvptr->gaplen) { /* if a gap was present */
|
|
uptr->PHASE = Traverse_Phase; /* then schedule the gap traversal phase */
|
|
uptr->wait = /* and the traversal time */
|
|
cvptr->gaplen * cvptr->dlyptr->data_xfer;
|
|
}
|
|
|
|
else { /* otherwise there was no gap */
|
|
uptr->PHASE = Data_Phase; /* so proceed to the data phase */
|
|
uptr->wait = /* with the data delay time */
|
|
2 * cvptr->dlyptr->data_xfer;
|
|
}
|
|
|
|
if (pptr->bpi <= 800 /* if this is an NRZI drive */
|
|
&& opcode == Read_Record_with_CRCC /* and the CRCC was requested */
|
|
|| cvptr->device->flags & DEV_REALTIME /* or the mode is REALTIME */
|
|
&& cvptr->type == HP_13181) /* for the 13181 controller */
|
|
add_crcc_lrcc (cvptr, opcode); /* then add the CRCC/LRCC to the buffer */
|
|
}
|
|
break;
|
|
|
|
|
|
case Write_Record:
|
|
case Write_Record_without_Parity:
|
|
outbound = RQSRV; /* request the first data word from the channel */
|
|
|
|
/* fall into the Write_File_Mark case */
|
|
|
|
case Write_File_Mark:
|
|
if ((cvptr->device->flags & DEV_REALTIME) == 0 /* if fast timing is enabled */
|
|
|| sim_tape_bot (uptr) == FALSE) { /* or the tape is not positioned at the BOT */
|
|
uptr->PHASE = Data_Phase; /* then proceed to the data phase */
|
|
uptr->wait = 2 * cvptr->dlyptr->data_xfer; /* with the data delay time */
|
|
break;
|
|
} /* otherwise an initial gap is needed */
|
|
|
|
/* fall into the Write_Gap case */
|
|
|
|
case Write_Gap:
|
|
outbound |= /* erase the gap */
|
|
call_tapelib (cvptr, uptr, lib_write_gap, pptr->gap_size);
|
|
|
|
if ((outbound & SCPE) == NO_FLAGS) { /* if the erase succeeded */
|
|
uptr->PHASE = Traverse_Phase; /* then set up to traverse the it */
|
|
|
|
cvptr->gaplen = (pptr->gap_size * pptr->bpi) / 10; /* set the length of the gap */
|
|
uptr->wait = cvptr->gaplen * cvptr->dlyptr->data_xfer; /* and the traversal time */
|
|
}
|
|
break;
|
|
|
|
|
|
case Forward_Space_Record:
|
|
case Forward_Space_File:
|
|
outbound = /* space forward over the next record in the file */
|
|
call_tapelib (cvptr, uptr, lib_space_fwd, 0);
|
|
|
|
if ((outbound & SCPE) == NO_FLAGS) /* if the spacing succeeded */
|
|
if (cvptr->gaplen > 0) { /* then if a gap is present */
|
|
uptr->PHASE = Traverse_Phase; /* then schedule the traversal phase */
|
|
uptr->wait = /* for the gap */
|
|
cvptr->gaplen * cvptr->dlyptr->data_xfer;
|
|
}
|
|
|
|
else { /* otherwise no gap intervenes */
|
|
uptr->PHASE = Data_Phase; /* so schedule the data phase */
|
|
uptr->wait = /* to space over the record */
|
|
cvptr->length * cvptr->dlyptr->data_xfer;
|
|
}
|
|
break;
|
|
|
|
|
|
case Backspace_Record:
|
|
case Backspace_File:
|
|
outbound = /* space in reverse over the previous record in the file */
|
|
call_tapelib (cvptr, uptr, lib_space_rev, 0);
|
|
|
|
if ((outbound & SCPE) == NO_FLAGS) /* if the spacing succeeded */
|
|
if (cvptr->gaplen > 0) { /* then if a gap is present */
|
|
uptr->PHASE = Traverse_Phase; /* then schedule the traversal phase */
|
|
uptr->wait = /* for the gap */
|
|
cvptr->gaplen * cvptr->dlyptr->data_xfer;
|
|
}
|
|
|
|
else { /* otherwise no gap intervenes */
|
|
uptr->PHASE = Data_Phase; /* so schedule the data phase */
|
|
uptr->wait = /* to space over the record */
|
|
cvptr->length * cvptr->dlyptr->data_xfer;
|
|
}
|
|
break;
|
|
|
|
|
|
case Rewind_Offline:
|
|
uptr->flags |= UNIT_OFFLINE; /* set the unit offline immediately */
|
|
|
|
/* fall into the Rewind case */
|
|
|
|
case Rewind:
|
|
outbound = end_command (cvptr, uptr); /* release the controller */
|
|
|
|
if (sim_tape_bot (uptr)) /* if the tape is positioned at the load point */
|
|
complete = TRUE; /* then the command is complete */
|
|
|
|
else { /* otherwise the tape must be rewound */
|
|
uptr->flags |= UNIT_REWINDING; /* so set rewinding status */
|
|
|
|
uptr->OPCODE = opcode; /* restore the opcode cleared by the end routine */
|
|
uptr->PHASE = Traverse_Phase; /* and proceed to the rewinding phase */
|
|
|
|
uptr->wait = /* base the traversal time on the current tape position */
|
|
(uptr->pos * cvptr->dlyptr->rewind_rate) / pptr->bpi;
|
|
}
|
|
break;
|
|
|
|
|
|
case Select_Unit_0: /* the command is invalid for this state */
|
|
case Select_Unit_1: /* the command is invalid for this state */
|
|
case Select_Unit_2: /* the command is invalid for this state */
|
|
case Select_Unit_3: /* the command is invalid for this state */
|
|
case Read_Record_Backward: /* the command is invalid for this state */
|
|
case Read_File_Forward: /* the command is invalid for this state */
|
|
case Write_Gap_and_File_Mark: /* the command is invalid for this state */
|
|
case Invalid_Opcode: /* the command is invalid for this state */
|
|
outbound = /* abort the command and stop the simulation */
|
|
abort_command (cvptr, uptr, SCPE_IERR);
|
|
break;
|
|
} /* end of the start phase operation dispatch */
|
|
|
|
break; /* end of the start phase handlers */
|
|
|
|
|
|
case Traverse_Phase:
|
|
switch (opcode) { /* dispatch the current operation */
|
|
|
|
case Read_Record:
|
|
case Read_Record_with_CRCC:
|
|
case Write_Record:
|
|
case Write_Record_without_Parity:
|
|
case Write_File_Mark:
|
|
case Forward_Space_Record:
|
|
case Forward_Space_File:
|
|
uptr->PHASE = Data_Phase; /* proceed to the data phase */
|
|
uptr->wait = 2 * cvptr->dlyptr->data_xfer; /* for a data transfer as needed */
|
|
break;
|
|
|
|
|
|
case Write_Gap:
|
|
uptr->PHASE = Stop_Phase; /* proceed to the completion phase */
|
|
uptr->wait = cvptr->dlyptr->ir_start; /* after the interrecord stop delay */
|
|
break;
|
|
|
|
|
|
case Backspace_Record:
|
|
case Backspace_File:
|
|
if (cvptr->length == 0) /* if a tape runaway occurred but was ignored */
|
|
uptr->PHASE = Start_Phase; /* then try the spacing operation again */
|
|
else /* otherwise */
|
|
uptr->PHASE = Data_Phase; /* proceed to the data phase to skip the record */
|
|
|
|
uptr->wait = 2 * cvptr->dlyptr->data_xfer;
|
|
break;
|
|
|
|
|
|
case Rewind:
|
|
case Rewind_Offline:
|
|
uptr->PHASE = Stop_Phase; /* motion is complete, so stop the drive */
|
|
uptr->wait = cvptr->dlyptr->rewind_stop; /* after the load point search finishes */
|
|
break;
|
|
|
|
|
|
case Select_Unit_0: /* the command is invalid for this state */
|
|
case Select_Unit_1: /* the command is invalid for this state */
|
|
case Select_Unit_2: /* the command is invalid for this state */
|
|
case Select_Unit_3: /* the command is invalid for this state */
|
|
case Clear_Controller: /* the command is invalid for this state */
|
|
case Read_Record_Backward: /* the command is invalid for this state */
|
|
case Read_File_Forward: /* the command is invalid for this state */
|
|
case Write_Gap_and_File_Mark: /* the command is invalid for this state */
|
|
case Invalid_Opcode: /* the command is invalid for this state */
|
|
outbound = /* abort the command and stop the simulation */
|
|
abort_command (cvptr, uptr, SCPE_IERR);
|
|
break;
|
|
} /* end of the traverse phase operation dispatch */
|
|
|
|
|
|
if (cvptr->state > Busy_State) { /* if an error condition exists */
|
|
uptr->PHASE = Stop_Phase; /* then terminate the command */
|
|
uptr->wait = cvptr->dlyptr->ir_start; /* after tape motion stops */
|
|
}
|
|
|
|
break; /* end of the traverse phase handlers */
|
|
|
|
|
|
case Data_Phase:
|
|
switch (opcode) { /* dispatch the current operation */
|
|
|
|
case Read_Record:
|
|
case Read_Record_with_CRCC:
|
|
if (cvptr->index == cvptr->length /* if record data is exhausted */
|
|
|| inbound_flags & EOD) { /* or the channel transfer is complete */
|
|
uptr->PHASE = Stop_Phase; /* then set up the stop phase processing */
|
|
uptr->wait = (cvptr->length - cvptr->index)
|
|
* cvptr->dlyptr->data_xfer
|
|
+ cvptr->dlyptr->ir_start;
|
|
|
|
if ((inbound_flags & EOD) == NO_FLAGS) /* if the record ended before than the channel finished */
|
|
cvptr->state = End_State; /* then note that the device ended the transfer */
|
|
|
|
if (cvptr->device->flags & DEV_REALTIME /* if the mode is REALTIME */
|
|
&& cvptr->type == HP_13181) /* for the 13181 controller */
|
|
outbound = /* then return the CRCC and LRCC */
|
|
IFIN | TO_WORD (cvptr->buffer [cvptr->length + 0],
|
|
cvptr->buffer [cvptr->length + 1]);
|
|
}
|
|
|
|
else { /* otherwise there is more data to transfer */
|
|
if (cvptr->type == HP_IB) { /* if the controller uses HP-IB */
|
|
outbound = /* then transfer one byte at a time */
|
|
cvptr->buffer [cvptr->index++]; /* to the data register */
|
|
|
|
dpprintf (cvptr->device, TL_DEB_XFER, "Unit %d %s byte %d is %03o\n",
|
|
unit, opcode_names [opcode],
|
|
cvptr->index, outbound);
|
|
|
|
uptr->wait = cvptr->dlyptr->data_xfer; /* schedule the next byte transfer */
|
|
}
|
|
|
|
else { /* otherwise we transfer full words */
|
|
outbound = /* move data to the high byte */
|
|
TO_WORD (cvptr->buffer [cvptr->index++], 0);
|
|
|
|
if (cvptr->index < cvptr->length) /* if there is more to transfer */
|
|
outbound |= /* then merge in the low byte */
|
|
cvptr->buffer [cvptr->index++];
|
|
|
|
dpprintf (cvptr->device, TL_DEB_XFER, "Unit %d %s word %d is %06o\n",
|
|
unit, opcode_names [opcode],
|
|
(cvptr->index + 1) / 2, outbound);
|
|
|
|
uptr->wait = 2 * cvptr->dlyptr->data_xfer; /* schedule the next word transfer */
|
|
}
|
|
|
|
outbound |= IFIN | RQSRV; /* tell the interface that more data is available */
|
|
}
|
|
break;
|
|
|
|
|
|
case Write_Record:
|
|
case Write_Record_without_Parity:
|
|
if (cvptr->index == TL_MAXREC) { /* if the buffer is full */
|
|
uptr->PHASE = Stop_Phase; /* then set up the stop phase processing */
|
|
uptr->wait = cvptr->dlyptr->ir_start; /* after the motion stops */
|
|
|
|
cvptr->call_status = MTSE_RECE; /* the record will be truncated, so mark it as bad */
|
|
|
|
outbound = IFOUT; /* tell the interface that we have the data */
|
|
}
|
|
|
|
else { /* otherwise the buffer still has space */
|
|
if (cvptr->type == HP_IB) { /* if the controller uses HP-IB */
|
|
cvptr->buffer [cvptr->index++] = /* then transfer the lower byte */
|
|
LOWER_BYTE (inbound_data); /* to the buffer */
|
|
|
|
cvptr->length = cvptr->length + 1; /* update the buffer length */
|
|
|
|
uptr->wait = cvptr->dlyptr->data_xfer; /* schedule the next byte transfer */
|
|
|
|
dpprintf (cvptr->device, TL_DEB_XFER, "Unit %d %s byte %d is %06o\n",
|
|
unit, opcode_names [opcode],
|
|
cvptr->index, inbound_data);
|
|
}
|
|
|
|
else { /* otherwise we transfer full words */
|
|
for (selector = upper; selector <= lower; selector++) { /* so unpack the data bytes */
|
|
if (selector == upper) /* if the upper byte is selected */
|
|
data_byte = UPPER_BYTE (inbound_data); /* then get it */
|
|
else /* otherwise */
|
|
data_byte = LOWER_BYTE (inbound_data); /* get the lower byte */
|
|
|
|
if (opcode == Write_Record_without_Parity /* if writing without parity */
|
|
&& drive_props [PROP_INDEX (uptr)].bpi <= 800) { /* and this is an NRZI drive */
|
|
if (odd_parity [data_byte]) /* then if the parity is wrong */
|
|
cvptr->call_status = MTSE_RECE; /* then the CRCC will be wrong */
|
|
|
|
if (data_byte == 0) /* a zero byte without parity */
|
|
continue; /* will be read as a dropout, not data */
|
|
} /* which may generate an odd record length */
|
|
|
|
cvptr->buffer [cvptr->index++] = data_byte; /* transfer the byte */
|
|
cvptr->length = cvptr->length + 1; /* and update the buffer length */
|
|
}
|
|
|
|
uptr->wait = 2 * cvptr->dlyptr->data_xfer; /* schedule the next word transfer */
|
|
|
|
dpprintf (cvptr->device, TL_DEB_XFER, "Unit %d %s word %d is %06o\n",
|
|
unit, opcode_names [opcode],
|
|
(cvptr->index + 1) / 2, inbound_data);
|
|
}
|
|
|
|
if (inbound_flags & EOD) { /* if the transfer is now complete */
|
|
uptr->PHASE = Stop_Phase; /* then set up the stop phase processing */
|
|
uptr->wait = cvptr->dlyptr->ir_start; /* after the motion stops */
|
|
|
|
if (cvptr->device->flags & DEV_REALTIME /* if the mode is REALTIME */
|
|
&& cvptr->type == HP_13181) { /* for the 13181 controller */
|
|
add_crcc_lrcc (cvptr, opcode); /* then add the CRCC/LRCC to the buffer */
|
|
|
|
outbound = /* return the CRCC and LRCC */
|
|
TO_WORD (cvptr->buffer [cvptr->length + 0],
|
|
cvptr->buffer [cvptr->length + 1]);
|
|
}
|
|
|
|
outbound |= IFOUT; /* tell the interface that we have the data */
|
|
}
|
|
|
|
else /* otherwise tell the interface that */
|
|
outbound = IFOUT | RQSRV; /* we have the data and need more */
|
|
}
|
|
break;
|
|
|
|
|
|
case Write_File_Mark:
|
|
case Write_Gap_and_File_Mark:
|
|
outbound = /* write the file (tape) mark */
|
|
call_tapelib (cvptr, uptr, lib_write_tmk, 0);
|
|
|
|
if ((outbound & SCPE) == NO_FLAGS) { /* if the write succeeded */
|
|
cvptr->status |= CST_EOF; /* then set EOF status */
|
|
uptr->PHASE = Stop_Phase; /* and set up the stop phase processing */
|
|
uptr->wait = cvptr->dlyptr->ir_start; /* after the motion stops */
|
|
}
|
|
break;
|
|
|
|
|
|
case Forward_Space_Record:
|
|
case Backspace_Record:
|
|
uptr->PHASE = Stop_Phase; /* set up the stop phase processing */
|
|
uptr->wait = cvptr->dlyptr->ir_start; /* after the motion stops */
|
|
break;
|
|
|
|
|
|
case Forward_Space_File:
|
|
if (sim_tape_eot (uptr) /* if the EOT was seen */
|
|
&& (cvptr->type == HP_13181 /* and this is */
|
|
|| cvptr->type == HP_13183)) { /* an HP 1000 controller */
|
|
uptr->PHASE = Stop_Phase; /* then the command ends */
|
|
uptr->wait = cvptr->dlyptr->ir_start; /* at this record */
|
|
break; /* after the motion stops */
|
|
}
|
|
|
|
/* otherwise fall into the Backspace_File case */
|
|
|
|
case Backspace_File:
|
|
uptr->PHASE = Start_Phase; /* set up to space over the next record */
|
|
uptr->wait = 2 * cvptr->dlyptr->ir_start; /* after spacing over the interrecord gap */
|
|
break;
|
|
|
|
|
|
case Select_Unit_0: /* the command is invalid for this state */
|
|
case Select_Unit_1: /* the command is invalid for this state */
|
|
case Select_Unit_2: /* the command is invalid for this state */
|
|
case Select_Unit_3: /* the command is invalid for this state */
|
|
case Clear_Controller: /* the command is invalid for this state */
|
|
case Read_Record_Backward: /* the command is invalid for this state */
|
|
case Read_File_Forward: /* the command is invalid for this state */
|
|
case Write_Gap: /* the command is invalid for this state */
|
|
case Rewind: /* the command is invalid for this state */
|
|
case Rewind_Offline: /* the command is invalid for this state */
|
|
case Invalid_Opcode: /* the command is invalid for this state */
|
|
outbound = /* abort the command and stop the simulation */
|
|
abort_command (cvptr, uptr, SCPE_IERR);
|
|
break;
|
|
} /* end of the data phase operation dispatch */
|
|
|
|
break; /* end of the data phase handlers */
|
|
|
|
|
|
case Stop_Phase:
|
|
switch (opcode) { /* dispatch the operation command */
|
|
|
|
case Clear_Controller: /* no additional completion action is required */
|
|
break;
|
|
|
|
|
|
case Read_Record:
|
|
case Read_Record_with_CRCC:
|
|
if (inbound_flags & OVRUN) { /* if a data overrun occurred */
|
|
cvptr->status |= CST_TIMERR; /* then indicate a timing error */
|
|
cvptr->state = Error_State;
|
|
}
|
|
|
|
if (cvptr->type == HP_30215 /* if this is the 3000 controller */
|
|
&& cvptr->index < cvptr->length) /* and a partial record was transferred */
|
|
cvptr->status &= ~CST_ODDLEN; /* then the byte count is always even */
|
|
|
|
cvptr->length = cvptr->index; /* set the length to report the amount actually transferred */
|
|
break;
|
|
|
|
|
|
case Write_Record:
|
|
case Write_Record_without_Parity:
|
|
if (cvptr->length > 0) { /* if there is data in the buffer */
|
|
if (cvptr->call_status == MTSE_RECE) /* then if the data is in error */
|
|
error_flag = MTR_ERF; /* then mark the record as bad */
|
|
else /* otherwise */
|
|
error_flag = 0; /* mark the record as good */
|
|
|
|
outbound = /* write the record */
|
|
call_tapelib (cvptr, uptr, lib_write, error_flag);
|
|
|
|
if (inbound_flags & OVRUN) { /* if a data underrun occurred */
|
|
cvptr->status |= CST_TIMERR; /* then indicate a timing error */
|
|
cvptr->state = Error_State;
|
|
}
|
|
}
|
|
|
|
else { /* otherwise the buffer is empty */
|
|
cvptr->status |= CST_DATAERR; /* which reports as a data error */
|
|
cvptr->state = Error_State; /* as it cannot be written */
|
|
}
|
|
break;
|
|
|
|
|
|
case Forward_Space_Record:
|
|
case Forward_Space_File:
|
|
case Backspace_Record:
|
|
case Backspace_File:
|
|
if (cvptr->type != HP_30215 /* if this is not the 3000 controller */
|
|
&& cvptr->length & 1) /* and the record length is odd */
|
|
cvptr->status |= CST_ODDLEN; /* then set the corresponding status */
|
|
|
|
/* fall into the Write_File_Mark case */
|
|
|
|
case Write_File_Mark:
|
|
case Write_Gap:
|
|
if (cvptr->state == End_State) /* a BOT or EOF indication */
|
|
cvptr->state = Busy_State; /* is the normal completion for these commands */
|
|
break; /* no additional completion action required */
|
|
|
|
|
|
case Rewind:
|
|
case Rewind_Offline:
|
|
if ((uptr->flags & UNIT_OFFLINE) == 0) /* if the unit is online */
|
|
cvptr->unit_attention |= 1 << unit; /* then attention sets on rewind completion */
|
|
|
|
uptr->flags &= ~UNIT_REWINDING; /* clear rewinding status */
|
|
|
|
outbound = /* rewind the tape (which always succeeds) */
|
|
call_tapelib (cvptr, uptr, lib_rewind, 0);
|
|
|
|
complete = TRUE; /* mark the command as complete */
|
|
uptr->PHASE = Idle_Phase; /* but skip the normal completion action */
|
|
break;
|
|
|
|
|
|
case Select_Unit_0: /* the command is invalid for this state */
|
|
case Select_Unit_1: /* the command is invalid for this state */
|
|
case Select_Unit_2: /* the command is invalid for this state */
|
|
case Select_Unit_3: /* the command is invalid for this state */
|
|
case Read_Record_Backward: /* the command is invalid for this state */
|
|
case Read_File_Forward: /* the command is invalid for this state */
|
|
case Write_Gap_and_File_Mark: /* the command is invalid for this state */
|
|
case Invalid_Opcode: /* the command is invalid for this state */
|
|
outbound = /* abort the command and stop the simulation */
|
|
abort_command (cvptr, uptr, SCPE_IERR);
|
|
break;
|
|
} /* end of the stop phase operation dispatch */
|
|
|
|
|
|
if (cvptr->call_status == MTSE_RECE) /* a bad data record */
|
|
cvptr->state = Error_State; /* is now treated as an error */
|
|
|
|
if (uptr->PHASE == Stop_Phase) { /* if the stop completed normally */
|
|
outbound |= end_command (cvptr, uptr); /* then terminate the command */
|
|
complete = TRUE; /* and mark it as complete */
|
|
}
|
|
|
|
break; /* end of the stop phase handlers */
|
|
|
|
|
|
case Error_Phase:
|
|
outbound = end_command (cvptr, uptr); /* end the command with an interrupt */
|
|
break;
|
|
|
|
} /* end of phase dispatching */
|
|
|
|
|
|
if (uptr->wait != NO_EVENT) /* if the unit has been scheduled */
|
|
activate_unit (cvptr, uptr); /* then activate it */
|
|
|
|
if (complete) { /* if the command is complete */
|
|
dpprintf (cvptr->device, TL_DEB_INCO, /* then report the final tape position */
|
|
"Unit %d %s completed at position %d\n",
|
|
unit, opcode_names [opcode], uptr->pos);
|
|
|
|
dpprintf (cvptr->device, TL_DEB_CMD, (cvptr->length > 0
|
|
? "Unit %d %s of %d-byte record %s\n"
|
|
: "Unit %d %s %.0d%s\n"),
|
|
unit, opcode_names [opcode], cvptr->length,
|
|
status_name [cvptr->call_status]);
|
|
}
|
|
|
|
return outbound; /* return the data word and function set */
|
|
}
|
|
|
|
|
|
/* End the current command.
|
|
|
|
The command executing on the specified unit is ended, and the unit and
|
|
controller are idled. The set of functions appropriate to the controller
|
|
state is returned to the caller.
|
|
*/
|
|
|
|
static CNTLR_IFN_IBUS end_command (CVPTR cvptr, UNIT *uptr)
|
|
{
|
|
static const CNTLR_IFN_SET end_functions [] = { /* indexed by CNTLR_STATE */
|
|
NO_FUNCTIONS, /* Idle_State */
|
|
RQSRV | STCFL, /* Busy_State */
|
|
DVEND | RQSRV | STCFL, /* End_State */
|
|
STINT | STCFL /* Error_State */
|
|
};
|
|
CNTLR_IFN_SET outbound;
|
|
|
|
outbound = end_functions [cvptr->state]; /* get the state-specific end functions */
|
|
|
|
uptr->OPCODE = Invalid_Opcode; /* clear the command */
|
|
uptr->PHASE = Idle_Phase; /* and idle the unit */
|
|
|
|
cvptr->state = Idle_State; /* idle the controller */
|
|
cvptr->status &= ~CST_IFBUSY; /* and the interface */
|
|
|
|
return (CNTLR_IFN_IBUS) outbound; /* return the function set */
|
|
}
|
|
|
|
|
|
/* Poll the tape drives for drive attention status.
|
|
|
|
The controller's drive attention bitmap is checked to determine if any tape
|
|
drive unit is requesting attention. If so, then the units are scanned in
|
|
ascending order to determine the first unit with a request pending. That
|
|
unit's flag is cleared, and a Set Drive Attention interface function is
|
|
returned, along with the unit number on the interface data bus. If no unit
|
|
is requesting attention, the routine returns an empty function set.
|
|
|
|
The HP 3000 controller sets drive attention when a Rewind command completes
|
|
or a unit is set online from an offline condition. The controller must poll
|
|
the drives for attention requests when it is idle and interrupts are allowed
|
|
by the CPU interface.
|
|
|
|
HP 1000 controllers do not call this routine. Rewind completion and
|
|
offline-to-online transitions must be determined by requesting controller
|
|
status.
|
|
*/
|
|
|
|
static CNTLR_IFN_IBUS poll_drives (CVPTR cvptr)
|
|
{
|
|
int32 unit;
|
|
|
|
dpprintf (cvptr->device, TL_DEB_INCO, "Controller polled drives for attention\n");
|
|
|
|
unit = 0; /* scan the units in numerical order */
|
|
|
|
while (cvptr->unit_attention) /* loop through the attention bits */
|
|
if (cvptr->unit_attention & 1 << unit) { /* looking for the first one set */
|
|
cvptr->unit_attention &= ~(1 << unit); /* and then clear it */
|
|
|
|
dpprintf (cvptr->device, TL_DEB_INCO, "Unit %d requested attention\n",
|
|
unit);
|
|
|
|
cvptr->unit_selected = unit; /* select the unit requesting attention */
|
|
|
|
return (CNTLR_IFN_IBUS) (DATTN | unit); /* return drive attention and the unit number */
|
|
}
|
|
|
|
else /* if this unit isn't requesting attention */
|
|
unit = unit + 1; /* then look at the next one */
|
|
|
|
return NO_ACTION; /* no drives are requesting attention */
|
|
}
|
|
|
|
|
|
/* Call a simulator tape support library routine.
|
|
|
|
The simulator tape support library routine specified by the "lib_call"
|
|
parameter is called for the controller specified by "cvptr" and the unit
|
|
specified by "uptr". For the lib_read_fwd and lib_read_rev calls, the
|
|
"parameter" value is the available buffer size in bytes. For the lib_write
|
|
call, the parameter is 0 to mark the record as good or MTR_ERF to mark it as
|
|
bad. For the lib_write_gap call, the parameter is the gap length in tenths
|
|
of an inch. The parameter value is ignored for the other calls.
|
|
|
|
After calling the specified routine, the returned status is examined and
|
|
translated into the appropriate controller status values. Any status other
|
|
than "call succeeded" or "call succeeded but with bad record data" asserts
|
|
the SCPE function and returns the corresponding SCPE status code.
|
|
Recoverable errors, e.g., encountering a tape mark, return SCPE_OK to the
|
|
event service routine to allow simulation to continue. Fatal errors, e.g., a
|
|
corrupt tape image file, cause simulation stops. If simulation is resumed by
|
|
the user, the current tape command fails and sets tape error status.
|
|
|
|
The caller must test the returned bus value for the SCPE function before
|
|
continuing with the current phase processing. If it is present, an abort has
|
|
occurred, and the rest of the phase processing should be bypassed, as the
|
|
phase and timing have been set appropriately by this routine.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. If the parameter accompanying a lib_write call is MTR_ERF, and the write
|
|
succeeded, then report a read-after-write failure by setting the call
|
|
status explicitly to indicate a bad record.
|
|
|
|
2. The simulator tape support library routines skip over erase gaps when
|
|
reading or spacing. To recover the gap size for use in calculating the
|
|
motion delay, the difference between the initial and final tape
|
|
positions, less the difference corresponding to the returned data record,
|
|
determines the gap size.
|
|
|
|
3. The position delta cannot be calculated by taking the absolute value of
|
|
the difference because both position values are of type t_addr, which is
|
|
unsigned and therefore yields an unsigned result. Moreover, the
|
|
positions cannot be cast to signed values because they may be either 32-
|
|
or 64-bit values, depending on USE_ADDR64.
|
|
|
|
4. The calculated "gap length" is actually the rewind distance for the
|
|
Rewind and Rewind_Offline commands.
|
|
|
|
5. When a command is started, the current record length is initialized to 0.
|
|
Support library routines that return MTSE_OK or MTSE_RECE may (e.g.,
|
|
sim_tape_rdrecf) or may not (e.g., sim_tape_wrtmk) set the record length.
|
|
Therefore, when processing these two status returns, cvptr->length > 0
|
|
indicates that the command has associated data, whereas cvptr->length = 0
|
|
indicates that no data are present.
|
|
|
|
6. The record length value returned by support library routines is not valid
|
|
unless the call succeeds. Therefore, we set the length to zero
|
|
explicitly for all status returns other than MTSE_OK and MTSE_RECE.
|
|
|
|
7. The CNTLR_IFN_IBUS returned value depends on the SCPE error codes fitting
|
|
into the lower 16 bits.
|
|
|
|
8. The 30215 controller does not detect tape runaway when spacing in the
|
|
reverse direction. If the support library "space record reverse" routine
|
|
returns MTSE_RUNAWAY status, the error status is cleared, but the buffer
|
|
length is set to 0 to indicate that the spacing did not encompass a data
|
|
record. After traversing the gap, the spacing will be retried.
|
|
|
|
9. The MTSE_WRP status return shouldn't occur, as write-protection status is
|
|
checked before initiating a write command. However, a user might detach
|
|
and then reattach a file with read-only status in the middle of the
|
|
command, so we detect and handle this condition.
|
|
|
|
10. In hardware, if a tape unit is taken offline while a transfer is in
|
|
progress, the 1000 and 3000 controllers will hang. They check for Unit
|
|
Ready status at the start of the transfer but then wait in a loop for
|
|
Read Clock or End of Block. Neither signal will occur if the drive has
|
|
been taken offline.
|
|
|
|
In simulation, taking a unit offline with the SET <unit> OFFLINE command
|
|
allows the current command to complete normally before Unit Ready is
|
|
denied. Taking a unit offline with the DETACH <unit> command causes a
|
|
"Unit not attached" simulator stop.
|
|
*/
|
|
|
|
static CNTLR_IFN_IBUS call_tapelib (CVPTR cvptr, UNIT *uptr, TAPELIB_CALL lib_call, t_mtrlnt parameter)
|
|
{
|
|
t_bool do_gap, do_data;
|
|
uint32 unit, gap_inches, gap_tenths;
|
|
CNTLR_IFN_IBUS result = (CNTLR_IFN_IBUS) NO_FUNCTIONS; /* the expected case */
|
|
|
|
switch (lib_call) { /* dispatch to the selected routine */
|
|
|
|
case lib_space_fwd: /* space record forward */
|
|
cvptr->call_status = sim_tape_sprecf (uptr, &cvptr->length);
|
|
break;
|
|
|
|
case lib_space_rev: /* space record reverse */
|
|
cvptr->call_status = sim_tape_sprecr (uptr, &cvptr->length);
|
|
break;
|
|
|
|
case lib_read_fwd: /* read record forward */
|
|
cvptr->call_status = sim_tape_rdrecf (uptr, cvptr->buffer,
|
|
&cvptr->length, parameter);
|
|
break;
|
|
|
|
case lib_read_rev: /* read record reverse */
|
|
cvptr->call_status = sim_tape_rdrecr (uptr, cvptr->buffer,
|
|
&cvptr->length, parameter);
|
|
break;
|
|
|
|
case lib_write: /* write record forward */
|
|
cvptr->call_status = sim_tape_wrrecf (uptr, cvptr->buffer,
|
|
parameter | cvptr->length);
|
|
|
|
if (parameter && cvptr->call_status == MTSE_OK) /* if the record is bad and the write succeeded */
|
|
cvptr->call_status = MTSE_RECE; /* then report a read-after-write failure */
|
|
break;
|
|
|
|
case lib_write_gap: /* write erase gap */
|
|
cvptr->call_status = sim_tape_wrgap (uptr, (uint32) parameter);
|
|
break;
|
|
|
|
case lib_write_tmk: /* write tape mark */
|
|
cvptr->call_status = sim_tape_wrtmk (uptr);
|
|
break;
|
|
|
|
case lib_rewind: /* rewind tape */
|
|
cvptr->call_status = sim_tape_rewind (uptr);
|
|
break;
|
|
}
|
|
|
|
|
|
if (cvptr->initial_position < uptr->pos) /* calculate the preliminary gap size */
|
|
cvptr->gaplen = uptr->pos - cvptr->initial_position; /* for either forward motion */
|
|
else /* or */
|
|
cvptr->gaplen = cvptr->initial_position - uptr->pos; /* for reverse motion */
|
|
|
|
switch (cvptr->call_status) { /* dispatch on the call status */
|
|
|
|
case MTSE_RECE: /* record data in error */
|
|
cvptr->status |= CST_DATAERR; /* report as a data error */
|
|
|
|
/* fall into the MTSE_OK case */
|
|
|
|
case MTSE_OK: /* operation succeeded */
|
|
if (cvptr->length > 0) /* if data is present */
|
|
cvptr->gaplen -= (cvptr->length + 1 & ~1) /* then reduce the calculated gap length */
|
|
+ 2 * sizeof (t_mtrlnt); /* by the rounded record length and marker sizes */
|
|
break;
|
|
|
|
|
|
case MTSE_TMK: /* tape mark encountered */
|
|
cvptr->gaplen -= sizeof (t_mtrlnt); /* reduce the gap length by the metadata marker size */
|
|
|
|
/* fall into the MTSE_EOM case */
|
|
|
|
case MTSE_EOM: /* end of medium encountered */
|
|
cvptr->status |= CST_EOF; /* set the EOF status */
|
|
|
|
if (cvptr->type == HP_13181) /* the HP 1000 NRZI controller */
|
|
cvptr->status |= CST_ODDLEN; /* also sets odd length status for a tape mark */
|
|
|
|
/* fall into the MTSE_BOT case */
|
|
|
|
case MTSE_BOT: /* beginning of tape encountered */
|
|
cvptr->state = End_State; /* indicate a device end condition */
|
|
|
|
if (cvptr->gaplen > 0) { /* if a gap is present */
|
|
uptr->PHASE = Traverse_Phase; /* then traverse it first */
|
|
uptr->wait = /* before stopping */
|
|
cvptr->gaplen * cvptr->dlyptr->data_xfer;
|
|
}
|
|
|
|
else { /* otherwise */
|
|
uptr->PHASE = Stop_Phase; /* set up the stop phase processing */
|
|
uptr->wait = cvptr->dlyptr->ir_start; /* after motion stops */
|
|
}
|
|
|
|
cvptr->length = 0; /* clear the record length */
|
|
result = SCP_STATUS (SCPE_OK); /* and indicate a recoverable error */
|
|
break;
|
|
|
|
|
|
case MTSE_RUNAWAY: /* tape runaway */
|
|
if (lib_call == lib_space_rev /* the HP 3000 controller does not recognize */
|
|
&& cvptr->type == HP_30215) /* tape runaway during reverse motion */
|
|
cvptr->call_status = MTSE_OK; /* so ignore it if it's encountered */
|
|
|
|
else { /* otherwise */
|
|
cvptr->state = Error_State; /* indicate a terminal error */
|
|
cvptr->status |= CST_RUNAWAY; /* and report the cause */
|
|
|
|
uptr->PHASE = Traverse_Phase; /* traverse the gap */
|
|
uptr->wait = /* before stopping */
|
|
cvptr->gaplen * cvptr->dlyptr->data_xfer;
|
|
|
|
result = SCP_STATUS (SCPE_OK); /* indicate a recoverable error */
|
|
}
|
|
|
|
cvptr->length = 0; /* clear the record length */
|
|
break;
|
|
|
|
|
|
case MTSE_FMT: /* illegal tape image format */
|
|
result = abort_command (cvptr, uptr, SCPE_FMT); /* abort the command and stop the simulation */
|
|
break;
|
|
|
|
|
|
case MTSE_UNATT: /* unit unattached */
|
|
tl_detach (uptr); /* resyncronize the attachment status */
|
|
result = abort_command (cvptr, uptr, SCPE_UNATT); /* abort the command and stop the simulation */
|
|
break;
|
|
|
|
|
|
case MTSE_INVRL: /* invalid record length */
|
|
result = abort_command (cvptr, uptr, SCPE_MTRLNT); /* abort the command and stop the simulation */
|
|
break;
|
|
|
|
|
|
case MTSE_IOERR: /* host system I/O error */
|
|
result = abort_command (cvptr, uptr, SCPE_IOERR); /* abort the command and stop the simulation */
|
|
break;
|
|
|
|
|
|
case MTSE_WRP: /* write protected */
|
|
uptr->STATUS |= UST_WRPROT; /* resynchronize the write protection status */
|
|
result = abort_command (cvptr, uptr, SCPE_NORO); /* abort the command and stop the simulation */
|
|
break;
|
|
|
|
|
|
default: /* unanticipated error */
|
|
result = abort_command (cvptr, uptr, SCPE_IERR); /* abort the command and stop the simulation */
|
|
break;
|
|
} /* end of the call status dispatching */
|
|
|
|
|
|
if (DPPRINTING (cvptr->device, TL_DEB_INCO)) { /* if tracing is enabled */
|
|
unit = uptr - cvptr->device->units; /* then get the unit number */
|
|
|
|
do_data = /* TRUE if the data record length is valid and present */
|
|
lib_props [lib_call].data_is_valid && cvptr->length > 0;
|
|
|
|
do_gap = /* TRUE if the erase gap length is valid and present */
|
|
lib_props [lib_call].gap_is_valid && cvptr->gaplen > 0;
|
|
|
|
if (cvptr->gaplen > 0) { /* if a gap or rewind spacing exists */
|
|
gap_inches = /* then calculate the movement in inches */
|
|
cvptr->gaplen / drive_props [PROP_INDEX (uptr)].bpi;
|
|
gap_tenths = /* and tenths of an inch */
|
|
((10 * cvptr->gaplen) / drive_props [PROP_INDEX (uptr)].bpi) % 10;
|
|
}
|
|
|
|
if (do_gap && do_data) /* if both gap and data are present */
|
|
hp_debug (cvptr->device, TL_DEB_INCO, /* then report both objects */
|
|
"Unit %d %s call of %d.%d-inch erase gap and %d-word record %s\n",
|
|
unit, lib_props [lib_call].action,
|
|
gap_inches, gap_tenths, /* using the movement calculated above */
|
|
(cvptr->length + 1) / 2,
|
|
status_name [cvptr->call_status]);
|
|
|
|
else if (do_data) /* otherwise if data only are present */
|
|
hp_debug (cvptr->device, TL_DEB_INCO, /* then report the record length */
|
|
"Unit %d %s call of %d-word record %s\n",
|
|
unit, lib_props [lib_call].action,
|
|
(cvptr->length + 1) / 2,
|
|
status_name [cvptr->call_status]);
|
|
|
|
else if (do_gap) /* otherwise if motion only is present */
|
|
hp_debug (cvptr->device, TL_DEB_INCO, /* then report it */
|
|
"Unit %d %s call of %d.%d%s %s\n",
|
|
unit, lib_props [lib_call].action,
|
|
gap_inches, gap_tenths,
|
|
(lib_call == lib_rewind ? " inches" : "-inch erase gap"),
|
|
status_name [cvptr->call_status]);
|
|
|
|
else /* otherwise no data was involved */
|
|
hp_debug (cvptr->device, TL_DEB_INCO, /* so just report the event */
|
|
"Unit %d %s call %s\n",
|
|
unit, lib_props [lib_call].action,
|
|
status_name [cvptr->call_status]);
|
|
}
|
|
|
|
if ((cvptr->device->flags & DEV_REALTIME) == 0) /* if optimized timing is selected */
|
|
cvptr->gaplen = 0; /* then the gap traversal phase is omitted */
|
|
|
|
return result; /* return the function set and data */
|
|
}
|
|
|
|
|
|
/* Abort the command.
|
|
|
|
The current command is aborted due to a fatal error return from the simulator
|
|
tape support library, e.g., if the tape image file format is corrupt. As
|
|
this routine is invoked when there is no sensible way of recovering, it sets
|
|
the controller to the error state, sets the controller status to reflect an
|
|
uncorrectable data error, schedules the error phase after a nominal delay,
|
|
and returns the associated SCPE error code to the calling service event
|
|
routine to stop the simulation. If the user resumes, the error phase will be
|
|
entered, and the program executing under simulation (typically, an operating
|
|
system driver) will receive the data error. The operation may be retried,
|
|
leading to the same failure, but eventually the simulated program will give
|
|
up and handle the fatal error in an appropriate fashion.
|
|
*/
|
|
|
|
static CNTLR_IFN_IBUS abort_command (CVPTR cvptr, UNIT *uptr, t_stat status)
|
|
{
|
|
cvptr->state = Error_State; /* indicate a fatal error */
|
|
cvptr->status |= CST_DATAERR; /* and report the cause as a data error */
|
|
|
|
uptr->PHASE = Error_Phase; /* terminate the command */
|
|
uptr->wait = cvptr->dlyptr->overhead; /* after a nominal delay */
|
|
|
|
cvptr->length = 0; /* clear the record length and stop the simulation */
|
|
return SCP_STATUS (status); /* with the appropriate console message */
|
|
}
|
|
|
|
|
|
/* Reject the command.
|
|
|
|
The command attempting to start (if "uptr" is NULL) is rejected, or the
|
|
command currently executing (if "uptr" is not NULL) is aborted. A busy unit
|
|
is idled, Command Reject status is set, and, if the controller is the HP 3000
|
|
controller, an interrupt request is scheduled after the tape stop delay
|
|
expires.
|
|
*/
|
|
|
|
static void reject_command (CVPTR cvptr, UNIT *uptr)
|
|
{
|
|
uint32 unit;
|
|
|
|
if (uptr) /* if a command is currently executing */
|
|
uptr->PHASE = Idle_Phase; /* then idle the tape drive */
|
|
else /* otherwise a command is attempting to start */
|
|
uptr = CNTLR_UPTR; /* so set up action on the controller unit */
|
|
|
|
unit = uptr - cvptr->device->units; /* determine the unit number for tracing */
|
|
|
|
dpprintf (cvptr->device, TL_DEB_CMD, "%s %s command rejected\n",
|
|
unit_names [unit], opcode_names [uptr->OPCODE]);
|
|
|
|
cvptr->status = CST_REJECT; /* set the Command Reject status */
|
|
cvptr->state = Error_State;
|
|
|
|
if (cvptr->type == HP_30215) { /* if this is the 3000 controller */
|
|
uptr = CNTLR_UPTR; /* then schedule the delay on the controller unit */
|
|
|
|
uptr->PHASE = Error_Phase; /* set up the error phase */
|
|
uptr->wait = cvptr->dlyptr->ir_start; /* and schedule the tape stop delay */
|
|
|
|
activate_unit (cvptr, uptr); /* start the controller delay */
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* Add the calculated CRC and LRC characters to the tape record buffer.
|
|
|
|
The cyclic redundancy check and longitudinal redundancy check characters
|
|
specified by ANSI X3.22 and ECMA-12 are calculated for the data record
|
|
currently in the buffer and then appended to the end of the buffer after
|
|
padding to an even record length if necessary.
|
|
|
|
The CRCC is generated by performing half-adds (XORs) of each record byte plus
|
|
odd parity with an initially-zero accumulator, followed by a 9-bit circular
|
|
right-shift. For each add, if the LSB of the accumulator is 1, bits 2 to 5
|
|
include a forced-carry (an XOR with the binary constant 000 011 100). After
|
|
all data bytes have been added, all bits except bits 3 and 5 are inverted by
|
|
a half-add with the binary constant 111 010 111.
|
|
|
|
The LRCC is generated by performing half-adds (XORs) of each record byte plus
|
|
odd parity with an initially-zero accumulator, followed by a half-add of the
|
|
calculated CRCC byte.
|
|
|
|
If the routine is called on behalf of the Read_Record_with_CRCC command, the
|
|
9-bit CRCC is split and appended to the buffer, and the record length is
|
|
increased appropriately. Otherwise, the CRCC and LRCC, without their MSBs,
|
|
are stored in the buffer, and the record length is not altered.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The 9-bit circular right-shift is implemented by testing the LSB. If
|
|
it is zero, then a 16-bit logical right-shift is performed; this is
|
|
equivalent, as the rotated-in MSB would be zero. If the LSB is one, the
|
|
same 16-bit logical right-shift is performed, but the rotated-in MSB is
|
|
restored by ORing in a preshifted one-bit (this latter action is folded
|
|
into the XOR required to complement the bits 5-2 and relies on the MSB
|
|
being zero after shifting).
|
|
*/
|
|
|
|
static void add_crcc_lrcc (CVPTR cvptr, CNTLR_OPCODE opcode)
|
|
{
|
|
uint32 index;
|
|
uint16 byte, crc, lrc;
|
|
|
|
crc = 0; /* initialize the CRC */
|
|
lrc = 0; /* and LRC accumulators */
|
|
|
|
for (index = 0; index < cvptr->length; index++) { /* for each byte in the data record */
|
|
byte = odd_parity [cvptr->buffer [index]] /* merge the calculated odd parity bit */
|
|
| cvptr->buffer [index]; /* to reconstruct the 9-bit tape data */
|
|
|
|
crc = crc ^ byte; /* add the byte to the accumulators */
|
|
lrc = lrc ^ byte; /* without carries */
|
|
|
|
if (crc & 1) /* perform a 9-bit circular right shift */
|
|
crc = crc >> 1 ^ 0474; /* on the CRC accumulator */
|
|
else /* while inverting bits 2 through 5 */
|
|
crc = crc >> 1; /* if the resulting LSB is a one */
|
|
|
|
dpprintf (cvptr->device, TL_DEB_XFER,
|
|
"CRCC/LRCC index = %2d, buffer = %03o, byte = %06o, crc = %06o, lrc = %06o\n",
|
|
index, cvptr->buffer [index], byte, crc, lrc);
|
|
}
|
|
|
|
crc = crc ^ 0727; /* invert all bits except bits 3 and 5 */
|
|
lrc = lrc ^ crc; /* include the CRCC in the LRCC calculation */
|
|
|
|
index = cvptr->length; /* get the current record length */
|
|
|
|
if (index & 1) /* if the record length was odd */
|
|
cvptr->buffer [index++] = 0; /* then pad the buffer with a zero byte */
|
|
|
|
if (opcode == Read_Record_with_CRCC) { /* if the CRCC was requested with the record */
|
|
cvptr->buffer [index++] = LOWER_BYTE (crc); /* then store the CRCC in the upper byte */
|
|
cvptr->buffer [index++] = crc >> 1 & D8_SIGN; /* and the parity bit in the MSB of the lower byte */
|
|
|
|
cvptr->length = index; /* count the CRCC and LRCC as part of the record */
|
|
}
|
|
|
|
else {
|
|
cvptr->buffer [index++] = LOWER_BYTE (crc); /* store the CRCC and LRCC into the buffer */
|
|
cvptr->buffer [index++] = LOWER_BYTE (lrc); /* without altering the valid length */
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
/* Tape library local utility routines */
|
|
|
|
|
|
/* Activate the unit.
|
|
|
|
The specified unit is activated using the unit's "wait" time. If tracing
|
|
is enabled, the activation is logged to the debug file.
|
|
*/
|
|
|
|
static void activate_unit (CVPTR cvptr, UNIT *uptr)
|
|
{
|
|
const uint32 unit = uptr - cvptr->device->units; /* the unit number */
|
|
|
|
dpprintf (cvptr->device, TL_DEB_STATE, "%s %s %s phase delay %d service scheduled\n",
|
|
unit_names [unit], opcode_names [uptr->OPCODE], phase_names [uptr->PHASE],
|
|
uptr->wait);
|
|
|
|
sim_activate (uptr, uptr->wait); /* activate the unit */
|
|
uptr->wait = NO_EVENT; /* and clear the activation time */
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* Validate a drive model or density change.
|
|
|
|
The tape drive model "new_drive" and density setting "new_bpi" of the unit
|
|
specified by "uptr" are validated against the drives and densities supported
|
|
by the controller type indicated by "cvptr". If the controller supports the
|
|
model at the density specified, the unit's property index field is set to
|
|
reference the corresponding entry in the drive properties table, the
|
|
simulator tape support library is notified of the density change, and the
|
|
routine returns SCPE_OK to indicate success. If the controller does not
|
|
support the model, SCPE_ARG is returned to indicate failure.
|
|
|
|
If "new_bpi" is zero, then validation does not consider the drive density,
|
|
and the first matching property entry, which should specify the preferred
|
|
default for selectable-density drives, is used.
|
|
*/
|
|
|
|
static t_stat validate_drive (CVPTR cvptr, UNIT *uptr, DRIVE_TYPE new_drive, uint32 new_bpi)
|
|
{
|
|
const CNTLR_TYPE ctype = cvptr->type; /* the controller type */
|
|
uint32 entry;
|
|
|
|
for (entry = 0; entry < PROPS_COUNT; entry++) /* check each property table entry for a match */
|
|
if (drive_props [entry].controller == ctype /* if this is our controller */
|
|
&& drive_props [entry].drive == new_drive /* and the model is supported */
|
|
&& (new_bpi == 0 /* and the density is not specified */
|
|
|| drive_props [entry].bpi == new_bpi)) { /* or the density is supported */
|
|
uptr->PROP = uptr->PROP & ~PROP_INDEX_MASK /* then set the unit's property table index */
|
|
| entry << PROP_INDEX_SHIFT;
|
|
|
|
sim_tape_set_dens (uptr, /* tell the tape library the density in use */
|
|
drive_props [entry].density,
|
|
NULL, NULL);
|
|
|
|
if (drive_props [entry].bpi == 1600) /* if the drive is a 1600-bpi unit */
|
|
uptr->STATUS |= UST_DEN1600; /* then set the density bit in the status */
|
|
else /* otherwise */
|
|
uptr->STATUS &= ~UST_DEN1600; /* clear it */
|
|
|
|
return SCPE_OK; /* allow the change */
|
|
}
|
|
|
|
return SCPE_ARG; /* if validation fails then reject the change */
|
|
}
|