blob: 2a0d3ce2b6b9b69ec170b482563c8332992a8734 [file] [log] [blame] [raw]
/* hp_disclib.c: HP MAC/ICD disc controller simulator library
Copyright (c) 2011-2012, 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.
20-Dec-12 JDB sim_is_active() now returns t_bool
24-Oct-12 JDB Changed CNTLR_OPCODE to title case to avoid name clash
07-May-12 JDB Corrected end-of-track delay time logic
02-May-12 JDB First release
09-Nov-11 JDB Created disc controller common library from DS simulator
References:
- 13037 Disc Controller Technical Information Package (13037-90902, Aug-1980)
- HP 13365 Integrated Controller Programming Guide (13365-90901, Feb-1980)
- HP 1000 ICD/MAC Disc Diagnostic Reference Manual (5955-4355, Jun-1984)
- RTE-IVB System Manager's Manual (92068-90006, Jan-1983)
- DVR32 RTE Moving Head Driver source (92084-18711, Revision 5000)
This library provides common functions required by HP disc controllers. It
implements the 13037 MAC and 13365 ICD controller command sets used with the
7905/06/20/25 and 7906H/20H/25H disc drives.
The library is an adaptation of the code originally written by Bob Supnik
for the DS simulator. DS simulates a 13037 controller connected via a 13175
disc interface to 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 CPU interfaces, such as the 12821A
HP-IB disc interface, 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.
As a result of this separation, the library does not handle the data transfer
between the controller and the interface directly. Instead, data is moved
between the interface and a sector buffer by the interface simulator, and
then the buffer is passed to the disc library for reading or writing. This
buffer is also used to pass disc commands and parameters to the controller,
and to receive status information from the controller. Only one buffer is
needed per interface, regardless of the number of controllers or units
handled, as a single interface cannot perform data transfers concurrently
with controller commands.
The library provides routines to prepare, start, and end commands, service
units, and poll drives for Attention status. In addition, routines are
provided to attach and detach disc images from drive units, load and unload
disc heads, classify commands, and provide opcode and phase name strings for
debugging.
Autosizing is supported when attaching a disc image. If enabled, the model
of the drive is set to match the disc image size. For example, if a 50 MB
disc image is attached to a unit set for autosizing, the unit's model will be
set to a 7920(H).
The interface simulator declares a structure that contains the state
variables for a controller. A MAC controller may handle multiple disc units.
An ICD controller handles only a single disc unit, but multiple controllers
may be employed to support several drives on a given interface. The type of
the controller (MAC or ICD) is contained in the structure, which is passed to
the disc library routines. The minor differences in controller action
between the two are handled internally. A macro (CNTLR_INIT) is provided to
initialize the structure.
The interface simulator also declares the sector buffer. The buffer is an
array containing DL_BUFSIZE 16-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. Other than setting the length when the controller places data into
the buffer and resetting the index at the start of a sector read or write,
the interface simulator is free to manipulate these values as desired.
In general, a user of the library is free to read any of the controller state
variable structure fields. Writing to the fields generally will interfere
with controller operations, with these exceptions:
Field Name Description
=========== ============================
status controller status
eod end of data flag
index data buffer index
length data buffer length
seek_time per-cylinder seek delay time
sector_time intersector delay time
cmd_time command response time
data_time data transfer response time
wait_time command wait time
In hardware, the controller executes in three basic states:
1. In the Poll Loop, which looks for commands and drive attention requests.
In each pass of the loop, the next CPU interface in turn is checked for a
command; if present, it is executed. If none are pending, all drives are
checked in turn until one is found with Attention status; if none are
found, the loop continues. If a drive is requesting attention, the
associated CPU interface is connected to check for a command; if present,
it is executed. If not, and the interface allows interrupts, an
interrupt request is made and the Command Wait Loop is entered. If
interrupts are not allowed, the Poll Loop continues.
2. In the Command Wait Loop, which looks for commands.
In each pass of the loop, the current CPU interface is checked for a
command; if present, it is executed. If not, the Command Wait Loop
continues. While in the loop, a 1.8 second timer is running. If it
expires before a command is received, the file mask is reset, and the
Poll Loop is entered.
3. In command execution, which processes the current command.
During command execution, the waits for input parameters, seek
completion, data transfers, and output status words are handled
internally. Each wait is governed by the 1.8 second timer; if it
expires, the command is aborted.
In simulation, these states are represented by the values cntlr_idle,
cntlr_wait, and cntlr_busy, respectively.
A MAC controller operates from one to eight drives, represented by an array
of one to eight units. When operating multiple units, a pointer to the first
unit of a contiguous array is passed, and the unit number present in the
command is used to index to the target unit.
A MAC controller emulation also requires an array of two contiguous auxiliary
units containing a controller unit and a command wait timeout unit. Commands
that do not access the drive, such as Address Record, are scheduled on the
controller unit to allow controller commands to execute while drive units are
seeking. The command wait timer limits the amount of time the controller
will wait for the interface to supply a command or parameter. A pointer to
the auxiliary unit array is set up during controller state variable
initialization. The auxiliary array may be separate or an extension of the
drive unit array.
An ICD controller manages a single unit corresponding to the drive in which
the controller is integrated. An interface declares a unit array
corresponding to the number of drives supported and passes the unit number to
use to the command preparation and start routines. Auxiliary units are not
used, and all commands are scheduled on the drive unit associated with a
given controller.
The library provides a unit service routine to handle all of the disc
commands. The routine is called from the interface service routine to handle
the common disc actions, while the interface routine handles actions specific
to the operation of the interface (such as data transfer).
The service routine schedules the unit to continue command execution under
these conditions:
1. A Seek or Recalibrate command is waiting for the seek completion.
2. A read or write command is waiting for the first data transfer of a
sector to start.
3. A read or write command is waiting for the next sector to start after
the final data transfer of the preceding sector.
4. A Verify command is waiting for the end of the current sector.
The library also provides controller and timer service routines for MAC
emulations. All three (unit, controller, and timer) must be called from
their respective interface service routines before any interface-specific
actions, if any, are taken.
On return from the library unit or controller service routines, the "wait"
field of the UNIT structure will be set to the activation time if the unit
is to be scheduled. The caller is responsible for activating the unit. If
the caller uses this feature, the field should be reset to zero before the
next service call.
The MAC timer unit is activated by the library, and its "wait" field is not
used. The timer starts when a command other than End, Seek, or Recalibrate
completes, or when the controller is waiting for the interface to supply or
accept a parameter during command execution. It stops when an End, Seek, or
Recalibrate command completes, a command is prepared for execution, or the
final parameter has been supplied or accepted by the interface during command
execution.
The controller maintains six variables in each drive's unit structure:
wait -- the current service activation time
pos -- the current byte offset into the disc image file
u3 (CYL) -- the current drive cylinder
u4 (STAT) -- the drive status (Status-2)
u5 (OP) -- the drive operation in process
u6 (PHASE) -- the current operation phase
These and other definitions are in the file hp_disclib.h, which must be
included in the interface simulator.
The controller library supports up to eight drives per MAC controller and one
drive per ICD controller. Unit numbers 0-7 represent valid drive addresses
for a MAC controller. The unit number field is ignored for an ICD
controller, and unit 0 is always implied. In simulation, MAC unit numbers
correspond one-for-one with device units, whereas one ICD controller is
associated with each of the several device units that are independently
addressed as unit 0.
The MAC controller firmware allows access to unit numbers 8-10 without
causing a Unit Unavailable error. Instead, the controller reports these
legal-but-invalid units as permanently offline.
Implementation notes:
1. The library does not simulate sector headers and trailers. Initialize
and Write Full Sector commands ignore the SPD bits and the supplied
header and trailer words. Read Full Sector fills in the header with the
current CHS address and sets the SPD bits to zero. The CRC and ECC words
in the trailer are returned as zeros. Programs that depend on drives
retaining the set values will fail.
2. The library does not simulate drive hold bits or support multiple CPU
interfaces connected to the same controller. CPU access to a valid drive
always succeeds.
3. The library does not simulate interface signals or function bus orders,
except for EOD (End of Data) and BUSY. The interface simulators must
decide for themselves what actions to take (e.g., interrupting the CPU)
on the basis of the controller state.
4. The command/sector buffer is an array of 16-bit elements. Byte-oriented
interface simulators, such as the 12821A HP-IB Disc Interface, must do
their own byte packing and unpacking.
5. 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 hidden, read-only registers.
*/
#include <math.h>
#include "hp_disclib.h"
/* Command accessors */
#define DL_V_OPCODE 8 /* bits 12- 8: general opcode */
#define DL_V_HOLD 7 /* bits 7- 7: general hold flag */
#define DL_V_UNIT 0 /* bits 3- 0: general unit number */
#define DL_V_SPD 13 /* bits 15-13: Initialize S/P/D flags */
#define DL_V_CHEAD 6 /* bits 7- 6: Cold Load Read head number */
#define DL_V_CSECT 0 /* bits 5- 0: Cold Load Read sector number */
#define DL_V_FRETRY 4 /* bits 7- 4: Set File Mask retry count */
#define DL_V_FDECR 3 /* bits 3- 3: Set File Mask seek decrement */
#define DL_V_FSPEN 2 /* bits 2- 2: Set File Mask sparing enable */
#define DL_V_FCYLM 1 /* bits 1- 1: Set File Mask cylinder mode */
#define DL_V_FAUTSK 0 /* bits 0- 0: Set File Mask auto seek */
#define DL_V_FMASK 0 /* bits 3- 0: Set File Mask (flags combined) */
#define DL_M_OPCODE 037 /* opcode mask */
#define DL_M_UNIT 017 /* unit mask */
#define DL_M_SPD 007 /* S/P/D flags mask */
#define DL_M_CHEAD 003 /* Cold Load Read head number mask */
#define DL_M_CSECT 077 /* Cold Load Read sector number mask */
#define DL_M_FRETRY 017 /* Set File Mask retry count mask */
#define DL_M_FMASK 017 /* Set File Mask flags mask */
#define GET_OPCODE(c) (CNTLR_OPCODE) (((c) >> DL_V_OPCODE) & DL_M_OPCODE)
#define GET_UNIT(c) (((c) >> DL_V_UNIT) & DL_M_UNIT)
#define GET_SPD(c) (((c) >> DL_V_SPD) & DL_M_SPD)
#define GET_CHEAD(c) (((c) >> DL_V_CHEAD) & DL_M_CHEAD)
#define GET_CSECT(c) (((c) >> DL_V_CSECT) & DL_M_CSECT)
#define GET_FRETRY(c) (((c) >> DL_V_FRETRY) & DL_M_FRETRY)
#define GET_FMASK(c) (((c) >> DL_V_FMASK) & DL_M_FMASK)
#define DL_FDECR (1 << DL_V_FDECR)
#define DL_FSPEN (1 << DL_V_FSPEN)
#define DL_FCYLM (1 << DL_V_FCYLM)
#define DL_FAUTSK (1 << DL_V_FAUTSK)
/* Parameter accessors */
#define DL_V_HEAD 8 /* bits 12- 8: head number */
#define DL_V_SECTOR 0 /* bits 7- 0: sector number */
#define DL_M_HEAD 0017 /* head number mask */
#define DL_M_SECTOR 0377 /* sector number mask */
#define GET_HEAD(p) (((p) >> DL_V_HEAD) & DL_M_HEAD)
#define GET_SECTOR(p) (((p) >> DL_V_SECTOR) & DL_M_SECTOR)
#define SET_HEAD(c) (((c)->head & DL_M_HEAD) << DL_V_HEAD)
#define SET_SECTOR(c) (((c)->sector & DL_M_SECTOR) << DL_V_SECTOR)
/* Drive properties table.
In hardware, drives report their Drive Type numbers to the controller upon
receipt of a Request Status tag bus command. The drive type is used to
determine the legal range of head and sector addresses (the drive itself will
validate the cylinder address during seeks).
In simulation, we set up a table of drive properties and use the model ID as
an index into the table. The table is used to validate seek parameters and
to provide the mapping between CHS addresses and the linear byte addresses
required by the host file access routines.
The 7905/06(H) drives consist of removable and fixed platters, whereas the
7920(H)/25(H) drives have only removable multi-platter packs. As a result,
7905/06 drives are almost always accessed in platter mode, i.e., a given
logical disc area is fully contained on either the removable or fixed
platter, whereas the 7920/25 drives are almost always accessed in cylinder
mode with logical disc areas spanning some or all of the platters.
Disc image files are arranged as a linear set of tracks. To improve
locality of access, tracks in the 7905/06 images are grouped per-platter,
whereas tracks on the 7920 and 7925 are sequential by cylinder and head
number.
The simulator maps the tracks on the 7905/06 removable platter (heads 0 and
1) to the first half of the disc image, and the tracks on the fixed platter
(heads 2 and, for the 7906 only, 3) to the second half of the image. For the
7906(H), the cylinder-head order of the tracks is 0-0, 0-1, 1-0, 1-1, ...,
410-0, 410-1, 0-2, 0-3, 1-2, 1-3, ..., 410-2, 410-3. The 7905 order is the
same, except that head 3 tracks are omitted.
For the 7920(H)/25(H), all tracks appear in cylinder-head order, e.g., 0-0,
0-1, 0-2, 0-3, 0-4, 1-0, 1-1, ..., 822-2, 822-3, 822-4 for the 7920(H).
This variable-access geometry is accomplished by defining additional "heads
per cylinder" values for the fixed and removable sections of each drive that
indicates the number of heads that should be grouped for locality. The
removable values are set to 2 on the 7905 and 7906, indicating that those
drives typically use cylinders consisting of two heads. They are set to the
number of heads per drive for the 7920 and 7925, as those typically use
cylinders encompassing the entire pack.
*/
#define D7905_RH 2
#define D7905_FH (D7905_HEADS - D7905_RH)
#define D7906_RH 2
#define D7906_FH (D7906_HEADS - D7906_RH)
#define D7920_RH D7920_HEADS
#define D7920_FH (D7920_HEADS - D7920_RH)
#define D7925_RH D7925_HEADS
#define D7925_FH (D7925_HEADS - D7925_RH)
typedef struct {
uint32 sectors; /* sectors per head */
uint32 heads; /* heads per cylinder*/
uint32 cylinders; /* cylinders per drive */
uint32 words; /* words per drive */
uint32 type; /* drive type */
uint32 remov_heads; /* number of removable-platter heads */
uint32 fixed_heads; /* number of fixed-platter heads */
} DRIVE_PROPERTIES;
static const DRIVE_PROPERTIES drive_props [] = {
{ D7905_SECTS, D7905_HEADS, D7905_CYLS, D7905_WORDS, D7905_TYPE, D7905_RH, D7905_FH },
{ D7906_SECTS, D7906_HEADS, D7906_CYLS, D7906_WORDS, D7906_TYPE, D7906_RH, D7906_FH },
{ D7920_SECTS, D7920_HEADS, D7920_CYLS, D7920_WORDS, D7920_TYPE, D7920_RH, D7920_FH },
{ D7925_SECTS, D7925_HEADS, D7925_CYLS, D7925_WORDS, D7925_TYPE, D7925_RH, D7925_FH }
};
#define PROPS_COUNT (sizeof (drive_props) / sizeof (drive_props [0]))
/* Convert a CHS address to a block offset.
A cylinder/head/sector address is converted into a linear block address that
may be used to calculate a byte offset to pass to the file access routines.
The conversion logic is:
if Head < removable_heads_per_cylinder then
tracks := Cylinder * removable_heads_per_cylinder + Head;
else
tracks := cylinders_per_drive * removable_heads_per_cylinder +
Cylinder * fixed_heads_per_cylinder + (Head - removable_heads_per_cylinder);
block := tracks * sectors_per_track + Sector;
byte_offset := block * words_per_sector * bytes_per_word;
The byte offset is calculated in two steps to allow for future controller
enhancements to support the CS/80 command set and its associated linear block
addressing mode.
*/
#define TO_BLOCK(cylinder,head,sector,model) \
(((head) < drive_props [model].remov_heads \
? (cylinder) * drive_props [model].remov_heads + (head) \
: drive_props [model].cylinders * drive_props [model].remov_heads \
+ ((cylinder) * drive_props [model].fixed_heads + (head) - drive_props [model].remov_heads)) \
* drive_props [model].sectors + (sector))
#define TO_OFFSET(block) ((block) * DL_WPSEC * sizeof (uint16))
/* Estimate the current sector.
The sector currently passing under the disc heads is estimated from the
current simulator time (i.e., the count of instructions since startup) and
the simulated disc rotation time. The computation logic is:
per_sector_time := word_transfer_time * words_per_sector + intersector_time;
current_sector := (current_time / per_sector_time) MOD sectors_per_track;
*/
#define GET_CURSEC(cvptr,uptr) \
((uint16) fmod (sim_gtime() / (double) ((cvptr->data_time * DL_WPSEC + cvptr->sector_time)), \
(double) drive_props [GET_MODEL (uptr->flags)].sectors))
/* Command properties table.
The validity of each command for a specified controller type is checked
against the command properties table when it is prepared. The table also
includes the count of inbound and outbound properties, the class of the
command, and flags to indicate certain common actions that should be taken.
*/
typedef struct {
uint32 params_in; /* count of input parameters */
uint32 params_out; /* count of output parameters */
CNTLR_CLASS classification; /* command classification */
t_bool valid [type_count]; /* per-type command validity */
t_bool clear_status; /* command clears the controller status */
t_bool unit_field; /* command has a unit field */
t_bool unit_check; /* command checks the unit number validity */
t_bool unit_access; /* command accesses the drive unit */
t_bool seek_wait; /* command waits for seek completion */
} DS_PROPS;
typedef const DS_PROPS *PRPTR;
#define T TRUE
#define F FALSE
static const DS_PROPS cmd_props [] = {
/* par par opcode valid for clear unit unit unit seek */
/* in out classification MAC ICD stat field check acces wait */
{ 0, 0, class_read, { T, T }, T, F, T, T, F }, /* 00 = cold load read */
{ 0, 0, class_control, { T, T }, T, T, T, T, T }, /* 01 = recalibrate */
{ 2, 0, class_control, { T, T }, T, T, T, T, F }, /* 02 = seek */
{ 0, 2, class_status, { T, T }, F, T, F, F, F }, /* 03 = request status */
{ 0, 1, class_status, { T, T }, T, T, T, F, F }, /* 04 = request sector address */
{ 0, 0, class_read, { T, T }, T, T, T, T, T }, /* 05 = read */
{ 0, 0, class_read, { T, T }, T, T, T, T, T }, /* 06 = read full sector */
{ 1, 0, class_read, { T, T }, T, T, T, T, T }, /* 07 = verify */
{ 0, 0, class_write, { T, T }, T, T, T, T, T }, /* 10 = write */
{ 0, 0, class_write, { T, T }, T, T, T, T, T }, /* 11 = write full sector */
{ 0, 0, class_control, { T, T }, T, F, F, F, F }, /* 12 = clear */
{ 0, 0, class_write, { T, T }, T, T, T, T, T }, /* 13 = initialize */
{ 2, 0, class_control, { T, T }, T, F, F, F, F }, /* 14 = address record */
{ 0, 7, class_status, { T, F }, F, F, F, F, F }, /* 15 = request syndrome */
{ 1, 0, class_read, { T, T }, T, T, T, T, T }, /* 16 = read with offset */
{ 0, 0, class_control, { T, T }, T, F, F, F, F }, /* 17 = set file mask */
{ 0, 0, class_invalid, { F, F }, T, F, F, F, F }, /* 20 = invalid */
{ 0, 0, class_invalid, { F, F }, T, F, F, F, F }, /* 21 = invalid */
{ 0, 0, class_read, { T, T }, T, T, T, T, T }, /* 22 = read without verify */
{ 1, 0, class_status, { T, F }, T, F, F, F, F }, /* 23 = load TIO register */
{ 0, 2, class_status, { T, T }, F, F, F, F, F }, /* 24 = request disc address */
{ 0, 0, class_control, { T, T }, T, F, F, F, F }, /* 25 = end */
{ 0, 0, class_control, { T, F }, T, T, T, F, F } /* 26 = wakeup */
};
/* Auxiliary unit indices */
typedef enum {
controller = 0, /* controller unit index */
timer /* command wait timer index */
} AUX_INDEX;
/* Controller opcode names */
static const char invalid_name [] = "invalid";
static const char *opcode_name [] = {
"cold load read", /* 00 */
"recalibrate", /* 01 */
"seek", /* 02 */
"request status", /* 03 */
"request sector address", /* 04 */
"read", /* 05 */
"read full sector", /* 06 */
"verify", /* 07 */
"write", /* 10 */
"write full sector", /* 11 */
"clear", /* 12 */
"initialize", /* 13 */
"address record", /* 14 */
"request syndrome", /* 15 */
"read with offset", /* 16 */
"set file mask", /* 17 */
invalid_name, /* 20 = invalid */
invalid_name, /* 21 = invalid */
"read without verify", /* 22 */
"load TIO register", /* 23 */
"request disc address", /* 24 */
"end", /* 25 */
"wakeup" /* 26 */
};
/* Controller phase names */
static const char *phase_name [] = {
"start",
"data",
"end"
};
/* Disc library local controller routines */
static t_bool start_seek (CVPTR cvptr, UNIT *uptr, CNTLR_OPCODE next_opcode, CNTLR_PHASE next_phase);
static t_stat start_read (CVPTR cvptr, UNIT *uptr);
static void end_read (CVPTR cvptr, UNIT *uptr);
static void start_write (CVPTR cvptr, UNIT *uptr);
static t_stat end_write (CVPTR cvptr, UNIT *uptr);
static t_bool position_sector (CVPTR cvptr, UNIT *uptr, t_bool verify);
static void next_sector (CVPTR cvptr, UNIT *uptr);
static t_stat io_error (CVPTR cvptr, UNIT *uptr);
/* Disc library local utility routines */
static void set_address (CVPTR cvptr, uint32 index);
static void set_timer (CVPTR cvptr, FLIP_FLOP action);
static uint16 drive_status (UNIT *uptr);
/* Disc library global controller routines */
/* Prepare a command for execution.
On entry, the first word of the controller buffer contains the command to
prepare, the "cvptr" parameter points at the controller state variable
structure, and the "units" parameter points at the first unit of the unit
array. For a MAC controller, the "unit limit" parameter indicates the last
valid unit number, and the unit to use is taken from the unit field of the
command word. For an ICD controller, the parameter indicates the number
of the unit to use directly.
If a valid command was prepared for execution, the routine returns TRUE and
sets the controller state to "busy." If the command is illegal, the routine
returns FALSE and sets the controller state to "waiting." In the latter
case, the controller status will indicate the reason for the rejection.
The opcode and unit number (for MAC controllers) are obtained from the buffer
and checked for legality. If either is illegal, the controller status is set
appropriately, and the routine returns FALSE.
For a valid command and an available unit, the controller's opcode field is
set from the buffer, the length field is set to the number of inbound
parameter words expected, and the index field is set to 1 to point at the
first parameter entry in the buffer.
*/
t_bool dl_prepare_command (CVPTR cvptr, UNIT *units, uint32 unit_limit)
{
uint32 unit;
PRPTR props;
CNTLR_OPCODE opcode;
set_timer (cvptr, CLEAR); /* stop the command wait timer */
opcode = GET_OPCODE (cvptr->buffer [0]); /* get the opcode from the command */
if (opcode > Last_Opcode) /* is the opcode invalid? */
props = &cmd_props [Invalid_Opcode]; /* undefined commands clear prior status */
else /* the opcode is potentially valid */
props = &cmd_props [opcode]; /* get the command properties */
if (cvptr->type == MAC) /* is this a MAC controller? */
if (props->unit_field) /* is the unit field defined for this command? */
unit = GET_UNIT (cvptr->buffer [0]); /* get the unit from the command */
else /* no unit specified in the command */
unit = 0; /* so the unit is always unit 0 */
else /* an ICD controller */
unit = unit_limit; /* uses the supplied unit number */
if (props->clear_status) { /* clear the prior controller status */
cvptr->status = normal_completion; /* if indicated for this command */
cvptr->spd_unit = SET_S1UNIT (unit); /* save the unit number for status requests */
}
if (cvptr->type <= last_type /* is the controller type legal, */
&& props->valid [cvptr->type]) /* and the opcode defined for this controller? */
if (props->unit_check && unit > DL_MAXUNIT) /* if the unit number is checked and is illegal, */
dl_end_command (cvptr, unit_unavailable); /* end with a unit unavailable error */
else {
cvptr->state = cntlr_busy; /* legal unit, so controller is now busy */
cvptr->opcode = opcode; /* save the controller opcode */
cvptr->length = props->params_in; /* set the inbound parameter count */
cvptr->index = 1; /* point at the first parameter element (if any) */
if (cvptr->type == MAC && cvptr->length) { /* is this a MAC controller with inbound parameters? */
cvptr->aux [controller].OP = opcode; /* save the opcode */
cvptr->aux [controller].PHASE = data_phase; /* and set the phase for parameter pickup */
set_timer (cvptr, SET); /* start the timer to wait for the first parameter */
}
return TRUE; /* the command is now prepared for execution */
}
else /* the opcode is undefined */
dl_end_command (cvptr, illegal_opcode); /* so set bad opcode status */
return FALSE; /* the preparation has failed */
}
/* Start a command.
On entry, the controller's opcode field contains the command to start, and
the buffer contains the command word in element 0 and the parameters required
by the command, if any, beginning in element 1. The call parameters are the
same as those supplied to the "prepare command" routine.
If the command was started successfully, the routine returns a pointer to the
unit to be activated and sets that unit's "wait" field to the activation
time. The caller should activate the unit upon return to complete or
continue command processing. If the command did not start, the routine
returns NULL.
If a seek is in progress on a drive when a command accessing that drive is
started, the unit pointer is returned but the unit's "wait" field is set to
zero. In this case, the unit must not be activated (as it already is).
Instead, the unit's opcode and phase fields will have been set to start the
command automatically when the seek completes.
For commands that return status from the controller, the buffer will contain
the returned value(s), the buffer index will be zero, and the buffer length
will be set to the number of words returned in the buffer. These words must
be returned to the CPU via the interface.
Implementation notes:
1. A command must have been prepared by calling dl_prepare_command first.
After preparation, the controller's opcode will be valid, and the unit
number field will be legal (but not necessarily valid) for those commands
that check the unit.
Unit numbers 0-7 represent valid drive addresses. However, the MAC
controller firmware allows access to unit numbers 8-10 without causing a
Unit Unavailable error. Instead, the controller reports these units as
permanently offline.
2. Commands that check for a valid unit do some processing before failing
with a Status-2 (not ready) error if the unit is invalid. For example,
the Seek command accepts its parameters from the CPU and sets the CHS
values into the controller before failing.
3. In hardware, read, write, and recalibrate commands wait in an internal
loop for a pending seek completion and clear the resulting Attention
status before executing. In simulation, we change a seeking drive unit's
opcode and phase fields from seek completion to the start of the next
command. This eliminates the setting of the Attention status and begins
command execution automatically when the seek completes.
If the seek completed between the command preparation and start,
Attention will have been set. If the unit is idle on entry, we clear the
Attention status unilaterally (it doesn't matter whether or not it was
set; Attention always is clear when commands start).
4. The Seek and Cold Load Read commands do not check for a seek or
recalibrate in progress. If the heads are moving, the drive will reject
a seek command with a Seek Check error. The firmware does not test
explicitly for Access Not Ready before executing the command, so the
parameters (e.g., controller CHS addresses) are still set as though the
command had succeeded.
A Seek command will return to the Poll Loop with Seek Check status set.
When the seek in progress completes, the controller will interrupt with
Drive Attention status. The controller address will differ from the
drive address, so it's incumbent upon the caller to issue a Request
Status command after the seek, which will return Status-2 Error status.
A Cold Load Read command issues a seek to cylinder 0 and then begins a
read, which first waits for seek completion. The Seek Check error will
abort the command at this point with Status-2 Error status.
In simulation, a Seek command allows the seek in progress to complete
normally, whereas a Cold Load Read command modifies the unit command
and phase from the end phase of Seek or Recalibrate to the start
phase of Read, which will catch the Seek Check error as in hardware.
5. The Cold Load Read command checks if the drive is ready before setting
the file mask. Therefore, we normally defer setting the file mask until
the unit service is called. However, if a seek is in progress, then the
drive must be ready, so we set the file mask here.
6. ECC is not simulated, so the Request Syndrome command always returns zero
values for the displacement and patterns.
7. The Request Status, Request Sector Address, and Wakeup commands reference
drive units but are scheduled on the controller unit because they may be
issued while a drive is processing a seek.
8. The activation time is set to the intersector time (latency) for read and
write commands, and to the controller processing time for all others.
The read/write start time cannot be shorter than 20 instructions, or
DVR32 will be unable to start DCPC in time to avoid an over/underrun.
*/
UNIT *dl_start_command (CVPTR cvptr, UNIT *units, uint32 unit_limit)
{
UNIT *uptr, *rptr;
uint32 unit;
PRPTR props;
t_bool is_seeking = FALSE;
props = &cmd_props [cvptr->opcode]; /* get the command properties */
if (cvptr->type == MAC) { /* is this a MAC controller? */
if (props->unit_field) /* is the unit field defined for this command? */
unit = GET_UNIT (cvptr->buffer [0]); /* get the unit number from the command */
else /* no unit is specified in the command */
unit = 0; /* so the unit number defaults to 0 */
if (unit > unit_limit) /* if the unit number is invalid, */
uptr = NULL; /* it does not correspond to a unit */
else if (props->unit_access) /* if the command accesses a drive, */
uptr = units + unit; /* get the address of the unit */
else /* the command accesses the controller only */
uptr = cvptr->aux + controller; /* so use the controller unit */
}
else { /* for an ICD controller, */
unit = 0; /* the unit value is ignored */
uptr = units + unit_limit; /* and we use the indicated unit */
}
if (props->unit_check && !uptr /* if the unit number is checked and is invalid */
|| props->seek_wait && (drive_status (uptr) & DL_S2STOPS)) { /* or if we're waiting for an offline drive */
dl_end_command (cvptr, status_2_error); /* then the command ends with a Status-2 error */
uptr = NULL; /* prevent the command from starting */
}
else if (uptr) { /* otherwise, we have a valid unit */
uptr->wait = cvptr->cmd_time; /* most commands use the command delay */
if (props->unit_access) { /* does the command access the unit? */
is_seeking = sim_is_active (uptr); /* see if the unit is busy */
if (is_seeking) /* if a seek is in progress, */
uptr->wait = 0; /* set for no unit activation */
else { /* otherwise, the unit is idle */
uptr->STAT &= ~DL_S2ATN; /* clear the drive Attention status */
if (props->classification == class_read /* if a read command */
|| props->classification == class_write) /* or a write command */
uptr->wait = cvptr->sector_time; /* schedule the sector start latency */
}
}
}
cvptr->index = 0; /* reset the buffer index */
cvptr->length = props->params_out; /* set the count of outbound parameters */
cvptr->eod = CLEAR; /* clear the end of data flag */
switch (cvptr->opcode) { /* dispatch the command */
case Cold_Load_Read:
cvptr->cylinder = 0; /* set the cylinder address to 0 */
cvptr->head = GET_CHEAD (cvptr->buffer [0]); /* set the head */
cvptr->sector = GET_CSECT (cvptr->buffer [0]); /* and sector from the command */
if (is_seeking) { /* if a seek is in progress, */
uptr->STAT |= DL_S2SC; /* a Seek Check occurs */
cvptr->file_mask = DL_FSPEN; /* enable sparing */
uptr->OP = Read; /* start the read on the seek completion */
uptr->PHASE = start_phase; /* and reset the command phase */
return uptr; /* to allow the seek to complete normally */
}
else /* the drive is not seeking */
uptr->wait = cvptr->cmd_time; /* the command starts with a seek, not a read */
break;
case Seek:
cvptr->cylinder = cvptr->buffer [1]; /* get the supplied cylinder */
cvptr->head = GET_HEAD (cvptr->buffer [2]); /* and head */
cvptr->sector = GET_SECTOR (cvptr->buffer [2]); /* and sector addresses */
if (is_seeking) { /* if a seek is in progress, */
uptr->STAT |= DL_S2SC; /* a Seek Check occurs */
dl_idle_controller (cvptr); /* return the controller to the idle condition */
return uptr; /* to allow the seek to complete normally */
}
break;
case Request_Status:
cvptr->buffer [0] = /* set the Status-1 value */
cvptr->spd_unit | SET_S1STAT (cvptr->status); /* into the buffer */
if (cvptr->type == MAC) /* is this a MAC controller? */
if (unit > unit_limit) /* if the unit number is invalid */
rptr = NULL; /* it does not correspond to a unit */
else /* otherwise, the unit is valid */
rptr = &units [unit]; /* so get the address of the referenced unit */
else /* if not a MAC controller */
rptr = uptr; /* then the referenced unit is the current unit */
cvptr->buffer [1] = drive_status (rptr); /* set the Status-2 value */
if (rptr) /* if the unit is valid */
rptr->STAT &= ~DL_S2FS; /* clear the First Status bit */
cvptr->spd_unit = SET_S1UNIT (unit); /* save the unit number */
if (unit > DL_MAXUNIT) /* if the unit number is illegal, */
cvptr->status = unit_unavailable; /* the next status will be Unit Unavailable */
else /* a legal unit */
cvptr->status = normal_completion; /* clears the controller status */
break;
case Request_Disc_Address:
set_address (cvptr, 0); /* return the CHS values in buffer 0-1 */
break;
case Request_Sector_Address:
if (unit > unit_limit) /* if the unit number is invalid */
rptr = NULL; /* it does not correspond to a unit */
else /* otherwise, the unit is valid */
rptr = &units [unit]; /* so get the address of the referenced unit */
if (drive_status (rptr) & DL_S2NR) /* if the drive is not ready, */
dl_end_command (cvptr, status_2_error); /* terminate with not ready status */
else /* otherwise, the drive is ready */
cvptr->buffer [0] = GET_CURSEC (cvptr, rptr); /* so calculate the current sector address */
break;
case Request_Syndrome:
cvptr->buffer [0] = /* return the Status-1 value in buffer 0 */
cvptr->spd_unit | SET_S1STAT (cvptr->status);
set_address (cvptr, 1); /* return the CHS values in buffer 1-2 */
cvptr->buffer [3] = 0; /* the displacement is always zero */
cvptr->buffer [4] = 0; /* the syndrome is always zero */
cvptr->buffer [5] = 0;
cvptr->buffer [6] = 0;
break;
case Address_Record:
cvptr->cylinder = cvptr->buffer [1]; /* get the supplied cylinder */
cvptr->head = GET_HEAD (cvptr->buffer [2]); /* and head */
cvptr->sector = GET_SECTOR (cvptr->buffer [2]); /* and sector addresses */
cvptr->eoc = CLEAR; /* clear the end-of-cylinder flag */
break;
case Set_File_Mask:
cvptr->file_mask = GET_FMASK (cvptr->buffer [0]); /* get the supplied file mask */
if (cvptr->type == MAC) /* if this is a MAC controller, */
cvptr->retry = GET_FRETRY (cvptr->buffer [0]); /* the retry count is supplied too */
break;
case Initialize:
if (uptr) /* if the unit is valid, */
cvptr->spd_unit |= /* merge the SPD flags */
SET_S1SPD (GET_SPD (cvptr->buffer [0])); /* from the command word */
break;
case Verify:
cvptr->verify_count = cvptr->buffer [1]; /* get the supplied sector count */
break;
default: /* the remaining commands */
break; /* are handled by the service routines */
}
if (uptr) { /* if the command accesses a valid unit */
uptr->OP = cvptr->opcode; /* save the opcode in the unit */
if (cvptr->length) /* if the command has outbound parameters, */
uptr->PHASE = data_phase; /* set up the data phase for the transfer */
else /* if there are no parameters, */
uptr->PHASE = start_phase; /* set up the command phase for execution */
return uptr; /* return a pointer to the scheduled unit */
}
else
return NULL; /* the command did not start */
}
/* Complete a command.
The current command is completed with the indicated status. The command
result status is set, the controller enters the command wait state, and the
CPU timer is restarted.
*/
void dl_end_command (CVPTR cvptr, CNTLR_STATUS status)
{
cvptr->status = status; /* set the command result status */
cvptr->state = cntlr_wait; /* set the controller state to waiting */
set_timer (cvptr, SET); /* start the command wait timer */
return;
}
/* Poll the drives for Attention status.
If interrupts are enabled on the interface, this routine is called to check
if any drive is requesting attention. The routine returns TRUE if a drive is
requesting attention and FALSE if not.
Starting with the last unit requesting attention, each drive is checked in
sequence. If a drive has its Attention status set, the controller saves its
unit number, sets the result status to Drive Attention, and enters the
command wait state. The routine returns TRUE to indicate that an interrupt
should be generated. The next time the routine is called, the poll begins
with the last unit that requested attention, so that each unit is given an
equal chance to respond.
If no unit is requesting attention, the routine returns FALSE to indicate
that no interrupt should be generated.
*/
t_bool dl_poll_drives (CVPTR cvptr, UNIT *units, uint32 unit_limit)
{
uint32 unit;
for (unit = 0; unit <= unit_limit; unit++) { /* check each unit in turn */
cvptr->poll_unit = /* start with the last unit checked */
(cvptr->poll_unit + 1) % (unit_limit + 1); /* and cycle back to unit 0 */
if (units [cvptr->poll_unit].STAT & DL_S2ATN) { /* if the unit is requesting attention, */
units [cvptr->poll_unit].STAT &= ~DL_S2ATN; /* clear the Attention status */
cvptr->spd_unit = SET_S1UNIT (cvptr->poll_unit); /* set the controller's unit number */
cvptr->status = drive_attention; /* and status */
cvptr->state = cntlr_wait; /* and wait for a command */
return TRUE; /* tell the caller to interrupt */
}
}
return FALSE; /* no requests, so do not generate an interrupt */
}
/* Service the disc drive unit.
The unit service routine is called to execute scheduled controller commands
for the specified unit. The actions to be taken depend on the current state
of the controller and the unit.
In addition to the controller state variables supplied in the call, the
service routine accesses these six variables in the UNIT structure:
wait -- the current service activation time
pos -- the current byte offset into the disc image file
u3 (CYL) -- the current drive cylinder
u4 (STAT) -- the drive status (Status-2)
u5 (OP) -- the drive operation in process
u6 (PHASE) -- the current operation phase
The activation time is set non-zero if the service should be rescheduled.
The caller is responsible upon return for activating the unit. The file
offset indicates the byte position in the disc image file for the next read
or write operation.
The drive cylinder gives the current location of the head positioner. This
may differ from the cylinder value in the controller if the Address Record
command has been used. The drive status maintains various per-drive
conditions (e.g., the state of the read-only and format switches, drive
ready, first status). The operation in process and operation phase define
the action to be taken by this service routine.
Initially, the operation in process is set to the opcode field of the command
when it is started. However, the operation in process may change during
execution (the controller opcode never does). This is to aid code reuse in
the service routine. For example, a Cold Load Read command is changed to a
Read command once the seek portion is complete, and a Read Without Verify
command is changed to a normal Read command after a track boundary is
crossed.
The operation phase provides different substates for those commands that
transfer data or that have different starting and ending actions. Three
phases are defined: start, data, and end. Commands that do not transfer data
to or from the CPU interface do not have data phases, and commands that
complete upon first service do not have end phases. The service routine
validates phase assignments and returns SCPE_IERR (Internal Error) if entry
is made with an illegal operation phase or a phase that is not valid for a
given operation.
An operation in the data phase is in the process of transferring data between
the CPU and sector buffer. Because this process is interface-specific, the
service routine does nothing (other than validate) in this phase. It is up
to the caller to transition from the data phase to the end phase when the
transfer is complete.
If an operation is completed, or an error has occurred, the controller state
on return will be either idle or waiting, instead of busy. The caller should
check the controller status to determine if normal completion or error
recovery is appropriate.
If the command is continuing, the service activation time will be set
appropriately. The caller should then call sim_activate to schedule the next
service and clear the "wait" field in preparation for the next service call.
Implementation notes:
1. The Cold Load Read and Seek commands check only the drive's Not Ready
status because seeking clears a Seek Check. The other commands that
access the unit (e.g., Read and Write) have already checked in the
command start routine for Not Ready, Seek Check, or Fault status and
terminated with a Status-2 error.
2. Several commands (e.g., Set File Mask, Address Record) are executed
completely within the dl_start_command routine, so all we do here is
finish the command with the expected status. The service routine is
called only to provide the proper command execution delay.
3. If a host file system error occurs, the service routine returns SCPE_IERR
to stop simulation. If simulation is resumed, the controller will behave
as though an uncorrectable data error had occurred.
*/
t_stat dl_service_drive (CVPTR cvptr, UNIT *uptr)
{
t_stat result = SCPE_OK;
const CNTLR_OPCODE opcode = (CNTLR_OPCODE) uptr->OP;
switch ((CNTLR_PHASE) uptr->PHASE) { /* dispatch the phase */
case start_phase:
switch (opcode) { /* dispatch the current operation */
case Recalibrate:
case Seek:
if (start_seek (cvptr, uptr, opcode, end_phase) /* start the seek; if it succeeded, */
&& (cvptr->type == MAC)) /* and this a MAC controller, */
dl_idle_controller (cvptr); /* then go idle until it completes */
break;
case Cold_Load_Read:
if (start_seek (cvptr, uptr, Read, start_phase)) /* start the seek; did it succeed? */
cvptr->file_mask = DL_FSPEN; /* set sparing enabled now */
break;
case Read:
case Read_With_Offset:
case Read_Without_Verify:
cvptr->length = DL_WPSEC; /* transfer just the data */
result = start_read (cvptr, uptr); /* start the sector read */
break;
case Read_Full_Sector:
cvptr->length = DL_WPFSEC; /* transfer the header/data/trailer */
result = start_read (cvptr, uptr); /* start the sector read */
break;
case Verify:
cvptr->length = 0; /* no data transfer needed */
result = start_read (cvptr, uptr); /* start the sector read */
if (uptr->PHASE == data_phase) { /* did the read start successfully? */
uptr->PHASE = end_phase; /* skip the data phase */
uptr->wait = cvptr->sector_time /* reschedule for the intersector time */
+ cvptr->data_time * DL_WPSEC; /* plus the data read time */
}
break;
case Write:
case Initialize:
cvptr->length = DL_WPSEC; /* transfer just the data */
start_write (cvptr, uptr); /* start the sector write */
break;
case Write_Full_Sector:
cvptr->length = DL_WPFSEC; /* transfer the header/data/trailer */
start_write (cvptr, uptr); /* start the sector write */
break;
case Request_Status:
case Request_Sector_Address:
case Clear:
case Address_Record:
case Request_Syndrome:
case Set_File_Mask:
case Load_TIO_Register:
case Request_Disc_Address:
case End:
case Wakeup:
dl_service_controller (cvptr, uptr); /* the controller service handles these */
break;
default: /* we were entered with an invalid state */
result = SCPE_IERR; /* return an internal (programming) error */
break;
} /* end of operation dispatch */
break; /* end of start phase handlers */
case data_phase:
switch (opcode) { /* dispatch the current operation */
case Read:
case Read_Full_Sector:
case Read_With_Offset:
case Read_Without_Verify:
case Write:
case Write_Full_Sector:
case Initialize:
break; /* data transfers are handled by the caller */
default: /* entered with an invalid state */
result = SCPE_IERR; /* return an internal (programming) error */
break;
} /* end of operation dispatch */
break; /* end of data phase handlers */
case end_phase:
switch (opcode) { /* dispatch the operation command */
case Recalibrate:
case Seek:
if (cvptr->type == ICD) /* is this an ICD controller? */
dl_end_command (cvptr, drive_attention); /* seeks end with Drive Attention status */
else /* if not an ICD controller, */
uptr->STAT |= DL_S2ATN; /* set Attention in the unit status */
break;
case Read:
case Read_Full_Sector:
case Read_With_Offset:
end_read (cvptr, uptr); /* end the sector read */
break;
case Read_Without_Verify:
if (cvptr->sector == 0) /* have we reached the end of the track? */
uptr->OP = Read; /* begin verifying the next time */
end_read (cvptr, uptr); /* end the sector read */
break;
case Verify:
cvptr->verify_count = /* decrement the count */
(cvptr->verify_count - 1) & DMASK; /* modulo 65536 */
if (cvptr->verify_count == 0) /* are there more sectors to verify? */
cvptr->eod = SET; /* no, so terminate the command cleanly */
end_read (cvptr, uptr); /* end the sector read */
break;
case Write:
case Write_Full_Sector:
case Initialize:
result = end_write (cvptr, uptr); /* end the sector write */
break;
case Request_Status:
case Request_Sector_Address:
case Request_Disc_Address:
dl_service_controller (cvptr, uptr); /* the controller service handles these */
break;
default: /* we were entered with an invalid state */
result = SCPE_IERR; /* return an internal (programming) error */
break;
} /* end of operation dispatch */
break; /* end of end phase handlers */
} /* end of phase dispatch */
return result; /* return the result of the service */
}
/* Service the controller unit.
The controller service routine is called to execute scheduled controller
commands that do not access drive units. It is also called to obtain command
parameters from the interface and to return command result values to the
interface. The actions to be taken depend on the current state of the
controller.
Controller commands are scheduled on a separate unit to allow concurrent
processing while seeks are in progress. For example, a seek may be started
on unit 0. While the seek is in progress, the CPU may request status from
the controller. In between returning the first and second status words to
the CPU, the seek may complete. Separating the controller unit allows seek
completion to be handled while the controller is "busy" waiting for the CPU
to indicate that it is ready for the second word.
For ICD controllers, the controller unit is not used, and all commands are
scheduled on the drive unit. This is possible because ICD controllers always
wait for seeks to complete before executing additional commands. To reduce
code duplication, however, the drive unit service calls the controller
service directly to handle controller commands.
The service routine validates phase assignments and returns SCPE_IERR
(Internal Error) if entry is made with an illegal operation phase or a phase
that is not valid for a given operation.
Implementation notes:
1. While the interface simulator is responsible for data phase transfers,
the controller service routine is responsible for (re)starting and
stopping the command wait timer for each parameter sent to and received
from the interface.
*/
t_stat dl_service_controller (CVPTR cvptr, UNIT *uptr)
{
t_stat result = SCPE_OK;
const CNTLR_OPCODE opcode = (CNTLR_OPCODE) uptr->OP;
switch ((CNTLR_PHASE) uptr->PHASE) { /* dispatch the phase */
case start_phase:
case end_phase:
switch (opcode) { /* dispatch the current operation */
case Request_Status:
dl_end_command (cvptr, cvptr->status); /* the command completes with no status change */
break;
case Clear:
dl_clear_controller (cvptr, uptr, soft_clear); /* clear the controller */
dl_end_command (cvptr, normal_completion); /* the command is complete */
break;
case Request_Sector_Address:
case Address_Record:
case Request_Syndrome:
case Set_File_Mask:
case Load_TIO_Register:
case Request_Disc_Address:
dl_end_command (cvptr, normal_completion); /* the command is complete */
break;
case End:
dl_idle_controller (cvptr); /* the command completes with the controller idle */
break;
case Wakeup:
dl_end_command (cvptr, unit_available); /* the command completes with Unit Available status */
break;
default: /* we were entered with an invalid state */
result = SCPE_IERR; /* return an internal (programming) error */
break;
} /* end of operation dispatch */
break; /* end of start and end phase handlers */
case data_phase:
switch (opcode) { /* dispatch the current operation */
case Seek:
case Verify:
case Address_Record:
case Read_With_Offset:
case Load_TIO_Register:
if (cvptr->length > 1) /* at least one more parameter to input? */
set_timer (cvptr, SET); /* restart the timer for the next parameter */
else /* this is the last one */
set_timer (cvptr, CLEAR); /* so stop the command wait timer */
break;
case Request_Status:
case Request_Sector_Address:
case Request_Syndrome:
case Request_Disc_Address:
if (cvptr->length > 0) /* at least one more to parameter output? */
set_timer (cvptr, SET); /* restart the timer for the next parameter */
else /* this is the last one */
set_timer (cvptr, CLEAR); /* so stop the command wait timer */
break;
default: /* we were entered with an invalid state */
result = SCPE_IERR; /* return an internal (programming) error */
break;
} /* end of operation dispatch */
break; /* end of data phase handlers */
} /* end of phase dispatch */
return result; /* return the result of the service */
}
/* Service the command wait timer unit.
The command wait timer service routine is called if the command wait timer
expires. This indicates that the CPU did not respond to a parameter transfer
or did not issue a new command within the ~1.8 second timeout period. The
timer is used with the MAC controller to ensure that a hung CPU does not tie
up the controller, preventing it from servicing other CPUs or drives. ICD
controllers do not use the command wait timer; they will wait forever, as
each controller is dedicated to a single interface.
When a timeout occurs, the controller unit is cancelled in case the cause was
a parameter timeout. Then the file mask is reset, and the controller is
idled.
The interface is responsible for polling for a new command and for drive
attention when a timeout occurs.
Implementation notes:
1. Only the controller unit may be active when the command wait timer
expires. A unit is never active because the timer is cancelled when
commands are executing and is restarted after the command completes.
*/
t_stat dl_service_timer (CVPTR cvptr, UNIT *uptr)
{
sim_cancel (cvptr->aux); /* cancel any controller activation */
dl_idle_controller (cvptr); /* idle the controller */
cvptr->file_mask = 0; /* clear the file mask */
return SCPE_OK;
}
/* Clear the controller.
The controller connected to the specified unit is cleared as directed. A MAC
controller is connected to several units, so the unit is used to find the
associated device and thereby the unit array. An ICD controller is connected
only to the specified unit.
In hardware, four conditions clear the 13037 controller:
- an initial application of power
- an assertion of the CLEAR signal by the CPU interface
- a timeout of the command wait timer
- a programmed Clear command
The first two conditions, called "hard clears," are equivalent and cause a
firmware restart with the PWRON flag set. The 13175 interface for the HP
1000 asserts the CLEAR signal in response to the backplane CRS signal if the
PRESET ENABLE jumper is not installed (which is the usual case). The third
condition also causes a firmware restart but with the PWRON flag clear. The
last condition is executed in the command handler and therefore returns to
the Command Wait Loop instead of the Poll Loop.
For a hard clear, the 13037 controller will:
- disconnect the CPU interface
- zero the controller RAM (no drives held, last polled unit number reset)
- issue a Controller Preset to clear all connected drives
- clear the clock offset
- clear the file mask
- enter the Poll Loop (which clears the controller status)
For a timeout clear, the 13037 controller will:
- disconnect the CPU interface
- clear the hold bits of any drives held by the interface that timed out
- clear the clock offset
- clear the file mask
- enter the Poll Loop (which clears the controller status)
For a programmed "soft" clear, the 13037 controller will:
- clear the controller status
- issue a Controller Preset to clear all connected drives
- enter the Command Wait Loop
Controller Preset is a tag bus command that is sent to all drives connected
to the controller. Each drive will:
- disconnect from the controller
- clear its internal drive faults
- clear its head and sector registers
- clear its illegal head and sector flip-flops
- reset its seek check, first status, drive fault, and attention status
In simulation, a hard clear occurs when a RESET -P or RESET command is
issued, or a programmed CLC 0 instruction is executed. A soft clear occurs
when a programmed Clear command is started. A timeout clear occurs when the
command wait timer unit is serviced, but this action is handled in the timer
unit service.
Because the controller execution state is implemented by scheduling command
phases for the target or controller unit, a simulated firmware restart must
abort any in-process activation. However, a firmware restart does not affect
seeks in progress, so these must be allowed to continue to completion so that
their Attention requests will be honored.
Implementation notes:
1. The specific 13365 controller actions on hard or soft clears are not
documented. Therefore, an ICD controller clear is handled as a MAC
controller clear, except that only the current drive is preset (as an ICD
controller manages only a single drive).
2. Neither hard nor soft clears affect the controller flags (e.g., EOC) or
registers (e.g., cylinder address).
3. In simulation, an internal seek, such as an auto-seek during a Read
command or the initial seek during a Cold Load Read command, will be
aborted for a hard or timeout clear, whereas in hardware it would
complete normally. This is OK, however, because an internal seek always
clears the drive's Attention status on completion, so aborting the
simulated seek is equivalent to an immediate seek completion.
4. In simulation, a Controller Preset only resets the specified status bits,
as the remainder of the hardware actions are not implemented.
*/
t_stat dl_clear_controller (CVPTR cvptr, UNIT *uptr, CNTLR_CLEAR clear_type)
{
uint32 unit, unit_count;
DEVICE *dptr = NULL;
if (clear_type == hard_clear) { /* is this a hard clear? */
dl_idle_controller (cvptr); /* idle the controller */
cvptr->file_mask = 0; /* clear the file mask */
cvptr->poll_unit = 0; /* clear the last unit polled */
}
if (cvptr->type == ICD) /* is this an ICD controller? */
unit_count = 1; /* there is only one unit per controller */
else { /* a MAC controller clears all units */
dptr = find_dev_from_unit (uptr); /* find the associated device */
if (dptr == NULL) /* the device doesn't exist?!? */
return SCPE_IERR; /* this is an impossible condition! */
else /* the device was found */
unit_count = dptr->numunits; /* so get the number of units */
}
for (unit = 0; unit < unit_count; unit++) { /* loop through the unit(s) */
if (dptr) /* pick up the unit from the device? */
uptr = dptr->units + unit; /* yes, so get the next unit */
if (!(uptr->flags & UNIT_DIS)) { /* is the unit enabled? */
if (clear_type == hard_clear /* a hard clear cancels */
&& uptr->OP != Seek /* only if not seeking */
&& uptr->OP != Recalibrate) /* or recalibrating */
sim_cancel (uptr); /* cancel the service */
uptr->STAT &= ~DL_S2CPS; /* do "Controller Preset" for the unit */
}
}
return SCPE_OK;
}
/* Idle the controller.
The command wait timer is turned off, the status is reset, and the controller
is returned to the idle state (Poll Loop).
*/
void dl_idle_controller (CVPTR cvptr)
{
cvptr->state = cntlr_idle; /* idle the controller */
cvptr->status = normal_completion; /* the Poll Loop clears the status */
set_timer (cvptr, CLEAR); /* stop the command wait timer */
return;
}
/* Load or unload the drive heads.
In hardware, a drive's heads are loaded when a disc pack is installed and the
RUN/STOP switch is set to RUN. The drive reports First Status when the heads
load to indicate that the pack has potentially changed. Setting the switch
to STOP unloads the heads. When the heads are unloaded, the drive reports
Not Ready and Drive Busy status.
In simulation, the unit must be attached before the heads may be unloaded or
loaded. As the heads should be automatically loaded when a unit is attached
and unloaded when a unit is detached, this routine must be called after
attaching and before detaching.
Implementation notes:
1. The drive sets its Attention status bit when the heads load or unload.
However, the ICD controller reports Attention only for head unloading.
2. Loading or unloading the heads clears Fault and Seek Check status.
3. 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 dl_load_unload (CVPTR cvptr, UNIT *uptr, t_bool load)
{
if ((uptr->flags & UNIT_ATT) == 0) /* the unit must be attached to [un]load */
return SCPE_UNATT; /* return "Unit not attached" if not */
else if (!(sim_switches & SIM_SW_REST)) /* modify the flags only if not restoring */
if (load) { /* are we loading the heads? */
uptr->flags = uptr->flags & ~UNIT_UNLOAD; /* clear the unload flag */
uptr->STAT = DL_S2FS; /* and set First Status */
if (cvptr->type != ICD) /* if this is not an ICD controller */
uptr->STAT |= DL_S2ATN; /* set Attention status also */
}
else { /* we are unloading the heads */
uptr->flags = uptr->flags | UNIT_UNLOAD; /* set the unload flag */
uptr->STAT = DL_S2ATN; /* and Attention status */
}
return SCPE_OK;
}
/* Disc library global utility routines */
/* Classify the current controller opcode.
The controller opcode is classified as a read, write, control, or status
command, and the classification is returned to the caller. If the opcode is
illegal or undefined for the indicated controller, the classification is
marked as invalid.
*/
CNTLR_CLASS dl_classify (CNTLR_VARS cntlr)
{
if (cntlr.type <= last_type /* if the controller type is legal */
&& cntlr.opcode <= Last_Opcode /* and the opcode is legal */
&& cmd_props [cntlr.opcode].valid [cntlr.type]) /* and is defined for this controller, */
return cmd_props [cntlr.opcode].classification; /* then return the command classification */
else /* the type or opcode is illegal */
return class_invalid; /* so return an invalid classification */
}
/* Return the name of an opcode.
A string representing the supplied controller opcode is returned to the
caller. If the opcode is illegal or undefined for the indicated controller,
the string "invalid" is returned.
*/
const char *dl_opcode_name (CNTLR_TYPE controller, CNTLR_OPCODE opcode)
{
if (controller <= last_type /* if the controller type is legal */
&& opcode <= Last_Opcode /* and the opcode is legal */
&& cmd_props [opcode].valid [controller]) /* and is defined for this controller, */
return opcode_name [opcode]; /* then return the opcode name */
else /* the type or opcode is illegal, */
return invalid_name; /* so return an error indication */
}
/* Return the name of a command phase.
A string representing the supplied phase is returned to the caller. If the
phase is illegal, the string "invalid" is returned.
*/
const char *dl_phase_name (CNTLR_PHASE phase)
{
if (phase <= last_phase) /* if the phase is legal, */
return phase_name [phase]; /* return the phase name */
else /* the phase is illegal, */
return invalid_name; /* so return an error indication */
}
/* Disc library global VM routines */
/* Attach a disc image file to a unit.
The file specified by the supplied filename is attached to the indicated
unit. If the attach was successful, the heads are loaded on the drive.
If the drive is set to autosize, the size of the image file is compared to
the table of drive capacities to determine which model of drive was used to
create it. If the image file is new, then the previous drive model is
retained.
*/
t_stat dl_attach (CVPTR cvptr, UNIT *uptr, char *cptr)
{
uint32 id, size;
t_stat result;
result = attach_unit (uptr, cptr); /* attach the unit */
if (result != SCPE_OK) /* did the attach fail? */
return result; /* yes, so return the error status */
dl_load_unload (cvptr, uptr, TRUE); /* if the attach succeeded, load the heads */
if (uptr->flags & UNIT_AUTO) { /* is autosizing enabled? */
size = sim_fsize (uptr->fileref) / sizeof (uint16); /* get the file size in words */
if (size > 0) /* a new file retains the current drive model */
for (id = 0; id < PROPS_COUNT; id++) /* find the best fit to the drive models */
if (size <= drive_props [id].words /* if the file size fits the drive capacity */
|| id == PROPS_COUNT - 1) { /* or this is the largest available drive */
uptr->capac = drive_props [id].words; /* then set the capacity */
uptr->flags = (uptr->flags & ~UNIT_MODEL) /* and the model */
| SET_MODEL (id);
break;
}
}
return SCPE_OK; /* the unit was successfully attached */
}
/* Detach a disc image file from a unit.
The heads are unloaded on the drive, and the attached file, if any, is
detached.
*/
t_stat dl_detach (CVPTR cvptr, UNIT *uptr)
{
dl_load_unload (cvptr, uptr, FALSE); /* unload the heads if attached */
return detach_unit (uptr); /* and detach the unit */
}
/* Set the drive model.
This validation routine is called to set the model of disc drive associated
with the specified unit. The "value" parameter indicates the model ID, and
the unit capacity is set to the size indicated.
*/
t_stat dl_set_model (UNIT *uptr, int32 value, char *cptr, void *desc)
{
if (uptr->flags & UNIT_ATT) /* we cannot alter the disc model */
return SCPE_ALATT; /* if the unit is attached */
if (value != UNIT_AUTO) /* if we are not autosizing */
uptr->capac = drive_props [GET_MODEL (value)].words; /* set the capacity to the new value */
return SCPE_OK;
}
/* Disc library local controller routines */
/* Start a read operation on the current sector.
The current sector indicated by the controller address is read from the disc
image file into the sector buffer in preparation for data transfer to the
CPU. If the end of the track had been reached, and the file mask permits,
an auto-seek is scheduled instead to allow the read to continue.
On entry, the end-of-data flag is checked. If it is set, the current read is
completed. Otherwise, the buffer data offset and verify options are set up.
For a Read Full Sector, the sync word is set from the controller type, and
dummy cylinder and head-sector words are generated from the current location
(as would be the case in the absence of track sparing).
The image file is positioned to the correct sector in preparation for
reading. If the positioning requires a permitted seek, it is scheduled, and
the routine returns with the operation phase unchanged to wait for seek
completion before resuming the read (when the seek completes, the service
routine will be entered, and we will be called again; this time, the
end-of-cylinder flag will be clear and positioning will succeed). If
positioning resulted in an error, the current read is terminated with the
error status set.
If positioning succeeded within the same cylinder, the sector image is read
into the buffer at an offset determined by the operation (Read Full Sector
leaves room at the start of the buffer for the sector header). If the image
file read did not return a full sector, the remainder of the buffer is padded
with zeros. If the image read failed with a file system error, SCPE_IOERR is
returned from the service routine to cause a simulation stop; resumption is
handled as an Uncorrectable Data Error.
If the image was read correctly, the next sector address is updated, the
operation phase is set for the data transfer, and the index of the first word
to transfer is set.
Implementation notes:
1. The length of the transfer required (cvptr->length) must be set before
entry.
2. Entry while executing a Read Without Verify or Read Full Sector command
inhibits address verification. The unit opcode is tested instead of the
controller opcode because a Read Without Verify is changed to a Read to
begin verifying after a track switch occurs.
*/
static t_stat start_read (CVPTR cvptr, UNIT *uptr)
{
uint32 count, offset;
t_bool verify;
const CNTLR_OPCODE opcode = (CNTLR_OPCODE) uptr->OP;
if (cvptr->eod == SET) { /* is the end of data indicated? */
dl_end_command (cvptr, normal_completion); /* complete the command */
return SCPE_OK;
}
if (opcode == Read_Full_Sector) { /* are we starting a Read Full Sector command? */
if (cvptr->type == ICD) /* is this an ICD controller? */
cvptr->buffer [0] = 0100377; /* ICD does not support ECC */
else
cvptr->buffer [0] = 0100376; /* MAC does support ECC */
set_address (cvptr, 1); /* set the current address into buffer 1-2 */
offset = 3; /* start the data after the header */
verify = FALSE; /* set for no address verification */
}
else { /* it's another read command */
offset = 0; /* data starts at the beginning */
verify = (opcode != Read_Without_Verify); /* set for address verification unless it's a RWV */
}
if (! position_sector (cvptr, uptr, verify)) /* position the sector */
return SCPE_OK; /* a seek is in progress or an error occurred */
count = sim_fread (cvptr->buffer + offset, /* read the sector from the image */
sizeof (uint16), DL_WPSEC, /* into the sector buffer */
uptr->fileref);
for (count = count + offset; count < cvptr->length; count++) /* pad the sector as needed */
cvptr->buffer [count] = 0; /* e.g., if reading from a new file */
if (ferror (uptr->fileref)) /* did a host file system error occur? */
return io_error (cvptr, uptr); /* set up the data error status and stop the simulation */
next_sector (cvptr, uptr); /* address the next sector */
uptr->PHASE = data_phase; /* set up the data transfer phase */
cvptr->index = 0; /* reset the data index */
return SCPE_OK; /* the read was successfully started */
}
/* Finish a read operation on the current sector.
On entry, the end-of-data flag is checked. If it is set, the current read is
completed. Otherwise, the command phase is reset to start the next sector,
and the disc service is set to allow for the intersector delay.
Implementation notes:
1. The CPU indicates the end of a read data transfer to an ICD controller by
untalking the drive. The untalk is done by the driver as soon as the
DCPC completion interrupt is processed. However, the time from the final
DCPC transfer through driver entry to the point where the untalk is
asserted on the bus varies from 80 instructions (RTE-6/VM with OS
microcode and the buffer in the system map) to 152 instructions (RTE-IVB
with the buffer in the user map). The untalk must occur before the start
of the next sector, or the drive will begin the data transfer.
Normally, this is not a problem, as the driver clears the FIFO of any
received data after DCPC completion. However, if the read terminates
after the last sector of a track, and accessing the next sector would
require an intervening seek, and the file mask disables auto-seeking or
an enabled seek would move the positioner beyond the drive limits, then
the controller will indicate an End of Cylinder error if the untalk does
not arrive before the seek is initiated.
The RTE driver (DVA32) and various utilities that manage the disc
directly (e.g., SWTCH) do not appear to account for these bogus errors,
so the ICD controller hardware must avoid them in some unknown manner.
We work around the issue by extending the intersector delay to allow time
for a potential untalk whenever the next access would otherwise fail.
Note that this issue does not occur with writes because DCPC completion
asserts EOI concurrently with the final data byte to terminate the
command.
*/
static void end_read (CVPTR cvptr, UNIT *uptr)
{
uint32 limit;
if (cvptr->eod == SET) /* is the end of data indicated? */
dl_end_command (cvptr, normal_completion); /* complete the command */
else { /* reading continues */
uptr->PHASE = start_phase; /* reset to the start phase */
uptr->wait = cvptr->sector_time; /* delay for the intersector time */
if (cvptr->eoc == SET && cvptr->type == ICD) { /* seek will be required and controller is ICD? */
if (!(cvptr->file_mask & DL_FAUTSK)) /* if auto-seek is disabled */
limit = cvptr->cylinder; /* then the limit is the current cylinder */
else if (cvptr->file_mask & DL_FDECR) /* else if enabled and decremental seek */
limit = 0; /* then the limit is cylinder 0 */
else /* else the enabled limit is the last cylinder */
limit = drive_props [GET_MODEL (uptr->flags)].cylinders;
if (cvptr->cylinder == limit) /* is positioner at the limit? */
uptr->wait = cvptr->eot_time; /* seek will fail; delay to allow CPU to untalk */
}
}
return;
}
/* Start a write operation on the current sector.
The current sector indicated by the controller address is positioned for
writing from the sector buffer to the disc image file after data transfer
from the CPU. If the end of the track had been reached, and the file mask
permits, an auto-seek is scheduled instead to allow the write to continue.
On entry, if writing is not permitted, or formatting is required but not
enabled, the command is terminated with an error. Otherwise, the disc image
file is positioned to the correct sector in preparation for writing.
If the positioning requires a permitted seek, it is scheduled, and the
routine returns with the operation phase unchanged to wait for seek
completion before resuming the write (when the seek completes, the service
routine will be entered, and we will be called again; this time, the
end-of-cylinder flag will be clear and positioning will succeed). If
positioning resulted in an error, the current write is terminated with the
error status set.
If positioning succeeded within the same cylinder, the operation phase is set
for the data transfer, and the index of the first word to transfer is set.
Implementation notes:
1. Entry while executing a Write Full Sector or Initialize command inhibits
address verification. In addition, the drive's FORMAT switch must be set
to the enabled position for these commands to succeed.
*/
static void start_write (CVPTR cvptr, UNIT *uptr)
{
const t_bool verify = (CNTLR_OPCODE) uptr->OP == Write; /* only Write verifies the sector address */
if ((uptr->flags & UNIT_WPROT) /* is the unit write protected, */
|| !verify && !(uptr->flags & UNIT_FMT)) /* or is formatting required but not enabled? */
dl_end_command (cvptr, status_2_error); /* terminate the write with an error */
else if (position_sector (cvptr, uptr, verify)) { /* writing is permitted; position the sector */
uptr->PHASE = data_phase; /* positioning succeeded; set up data transfer phase */
cvptr->index = 0; /* reset the data index */
}
return;
}
/* Finish a write operation on the current sector.
The current sector is written from the sector buffer to the disc image file
at the current file position. The next sector address is then updated to
allow writing to continue.
On entry, the drive is checked to ensure that it is ready for the write.
Then the sector buffer is padded appropriately if a full sector of data was
not transferred. The buffer is written to the disc image file at the
position corresponding to the controller address as set when the sector was
started. The write begins at a buffer offset determined by the command (a
Write Full Sector has header words at the start of the buffer that are not
written to the disc image).
If the image write failed with a file system error, SCPE_IOERR is returned
from the service routine to cause a simulation stop; resumption is handled as
an Uncorrectable Data Error. If the image was written correctly, the next
sector address is updated. If the end-of-data flag is set, the current write
is completed. Otherwise, the command phase is reset to start the next
sector, and the disc service is scheduled to allow for the intersector delay.
Implementation notes:
1. A partial sector is filled with 177777B words (ICD) or copies of the last
word (MAC) per page 7-10 of the ICD/MAC Disc Diagnostic manual.
*/
static t_stat end_write (CVPTR cvptr, UNIT *uptr)
{
uint32 count;
uint16 pad;
const CNTLR_OPCODE opcode = (CNTLR_OPCODE) uptr->OP;
const uint32 offset = (opcode == Write_Full_Sector ? 3 : 0);
if (uptr->flags & UNIT_UNLOAD) { /* if the drive is not ready, */
dl_end_command (cvptr, access_not_ready); /* terminate the command with an error */
return SCPE_OK;
}
if (cvptr->index < DL_WPSEC + offset) { /* was a partial sector transferred? */
if (cvptr->type == ICD) /* an ICD controller */
pad = DMASK; /* pads the sector with -1 */
else /* a MAC controller */
pad = cvptr->buffer [cvptr->index - 1]; /* pads with the last word written */
for (count = cvptr->index; count < DL_WPSEC + offset; count++)
cvptr->buffer [count] = pad; /* pad the sector buffer as needed */
}
sim_fwrite (cvptr->buffer + offset, sizeof (uint16), /* write the sector to the file */
DL_WPSEC, uptr->fileref);
if (ferror (uptr->fileref)) /* did a host file system error occur? */
return io_error (cvptr, uptr); /* set up the data error status and stop the simulation */
next_sector (cvptr, uptr); /* address the next sector */
if (cvptr->eod == SET) /* is the end of data indicated? */
dl_end_command (cvptr, normal_completion); /* complete the command */
else { /* writing continues */
uptr->PHASE = start_phase; /* reset to the start phase */
uptr->wait = cvptr->sector_time; /* delay for the intersector time */
}
return SCPE_OK;
}
/* Position the disc image file at the current sector.
The image file is positioned at the byte address corresponding to the drive's
current cylinder and the controller's current head and sector addresses.
Positioning may involve an auto-seek if a prior read or write addressed the
final sector of a cylinder. If a seek is initiated or an error is detected,
the routine returns FALSE to indicate that the positioning was not performed.
If the file was positioned, the routine returns TRUE.
On entry, if the controller's end-of-cylinder flag is set, a prior read or
write addressed the final sector in the current cylinder. If the file mask
does not permit auto-seeking, the current command is terminated with an End
of Cylinder error. Otherwise, the cylinder is incremented or decremented as
directed by the file mask, and a seek to the new cylinder is started.
If the increment or decrement resulted in an out-of-bounds value, the seek
will return Seek Check status, and the command is terminated with an error.
If the seek is legal, the routine returns with the disc service scheduled for
seek completion and the command state unchanged. When the service is
reentered, the read or write will continue on the new cylinder.
If the EOC flag was not set, the drive's position is checked against the
controller's position if address verification is requested. If they are
different (as may occur with an Address Record command that specified a
different location than the last Seek command), a seek is started to the
correct cylinder, and the routine returns with the disc service scheduled for
seek completion as above.
If the drive and controller positions agree or verification is not requested,
the CHS addresses are validated against the drive limits. If they are
invalid, Seek Check status is set, and the command is terminated with an
error.
If the addresses are valid, the drive is checked to ensure that it is ready
for positioning. If it is, the the byte offset in the image file is
calculated from the CHS address, and the file is positioned. The disc
service is scheduled to begin the data transfer, and the routine returns TRUE
to indicate that the file position was set.
Implementation notes:
1. The ICD controller returns an End of Cylinder error if an auto-seek
results in a position beyond the drive limits. The MAC controller
returns a Status-2 error. Both controllers set the Seek Check bit in the
drive status word.
*/
static t_bool position_sector (CVPTR cvptr, UNIT *uptr, t_bool verify)
{
uint32 block;
uint32 model = GET_MODEL (uptr->flags);
if (cvptr->eoc == SET) /* are we at the end of a cylinder? */
if (cvptr->file_mask & DL_FAUTSK) { /* is an auto-seek allowed? */
if (cvptr->file_mask & DL_FDECR) /* is a decremental seek requested? */
cvptr->cylinder = (cvptr->cylinder - 1) & DMASK; /* decrease the cylinder address with wraparound */
else /* an incremental seek is requested */
cvptr->cylinder = (cvptr->cylinder + 1) & DMASK; /* increase the cylinder address with wraparound */
start_seek (cvptr, uptr, /* start the auto-seek */
(CNTLR_OPCODE) uptr->OP, /* with the current operation */
(CNTLR_PHASE) uptr->PHASE); /* and phase unchanged */
if (uptr->STAT & DL_S2SC) /* did a seek check occur? */
if (cvptr->type == ICD) /* is this ICD controller? */
dl_end_command (cvptr, end_of_cylinder); /* report it as an End of Cylinder error */
else /* it is a MAC controller */
dl_end_command (cvptr, status_2_error); /* report it as a Status-2 error */
}
else /* the file mask does not permit an auto-seek */
dl_end_command (cvptr, end_of_cylinder); /* so terminate with an EOC error */
else if (verify && (uint32) uptr->CYL != cvptr->cylinder) { /* is the positioner on the wrong cylinder? */
start_seek (cvptr, uptr, /* start a seek to the correct cylinder */
(CNTLR_OPCODE) uptr->OP, /* with the current operation */
(CNTLR_PHASE) uptr->PHASE); /* and phase unchanged */
if (uptr->STAT & DL_S2SC) /* did a seek check occur? */
dl_end_command (cvptr, status_2_error); /* report a Status-2 error */
}
else if (((uint32) uptr->CYL >= drive_props [model].cylinders) /* is the cylinder out of bounds? */
|| (cvptr->head >= drive_props [model].heads) /* or the head? */
|| (cvptr->sector >= drive_props [model].sectors)) { /* or the sector? */
uptr->STAT = uptr->STAT | DL_S2SC; /* set Seek Check status */
dl_end_command (cvptr, status_2_error); /* and terminate with an error */
}
else if (uptr->flags & UNIT_UNLOAD) /* is the drive ready for positioning? */
dl_end_command (cvptr, access_not_ready); /* terminate the command with an access error */
else { /* we are ready to position the image file */
block = TO_BLOCK (uptr->CYL, cvptr->head, /* calculate the new block position */
cvptr->sector, model); /* (for inspection only) */
uptr->pos = TO_OFFSET (block); /* and then convert to a byte offset */
sim_fseek (uptr->fileref, uptr->pos, SEEK_SET); /* set the image file position */
uptr->wait = cvptr->data_time; /* delay for the data access time */
return TRUE; /* and report that positioning was accomplished */
}
return FALSE; /* report that positioning failed or was deferred */
}
/* Address the next sector.
The controller's CHS address is incremented to point at the next sector. If
the next sector number is valid, the routine returns. Otherwise, the sector
number is reset to sector 0. If the file mask is set for cylinder mode, the
head is incremented, and if the new head number is valid, the routine
returns. If the head number is invalid, it is reset to head 0, and the
end-of-cylinder flag is set. The EOC flag is also set if the file mask is
set for surface mode.
The new cylinder address is not set here, because cylinder validation must
only occur when the next sector is actually accessed. Otherwise, reading or
writing the last sector on a track or cylinder with auto-seek disabled would
cause an End of Cylinder error, even if the transfer ended with that sector.
Instead, we set the EOC flag to indicate that a cylinder update is pending.
As a result of this deferred update method, the state of the EOC flag must be
considered when returning the disc address to the CPU.
*/
static void next_sector (CVPTR cvptr, UNIT *uptr)
{
const uint32 model = GET_MODEL (uptr->flags); /* get the disc model */
cvptr->sector = cvptr->sector + 1; /* increment the sector number */
if (cvptr->sector < drive_props [model].sectors) /* are we at the end of the track? */
return; /* no, so the next sector value is OK */
cvptr->sector = 0; /* wrap the sector number */
if (cvptr->file_mask & DL_FCYLM) { /* are we in cylinder mode? */
cvptr->head = cvptr->head + 1; /* yes, so increment the head */
if (cvptr->head < drive_props [model].heads) /* are we at the end of the cylinder? */
return; /* no, so the next head value is OK */
cvptr->head = 0; /* wrap the head number */
}
cvptr->eoc = SET; /* set the end-of-cylinder flag to */
return; /* indicate that an update is required */
}
/* Start a seek.
A seek is initiated on the indicated unit if the drive is ready and the
cylinder, head, and sector values in the controller are valid for the current
drive model. If the current operation is a recalibrate, a seek is initiated
to cylinder 0 instead of the cylinder value stored in the controller. The
routine returns TRUE if the drive was ready for the seek and FALSE if it was
not.
If the controller cylinder is beyond the drive's limit, Seek Check status is
set in the unit, and the heads are not moved. Otherwise, the relative
cylinder position change is calculated, and the heads are moved to the new
position.
If the controller head or sector is beyond the drive's limit, Seek Check
status is set in the unit. Otherwise, Seek Check status is cleared, and the
new file offset is calculated.
A seek check terminates the current command for an ICD controller. For a MAC
controller, the seek check is noted in the drive status, but processing will
continue until the drive sets Attention status.
Finally, the drive operation and phase are set to the supplied values before
returning.
Implementation notes:
1. EOC is not reset for recalibrate so that a reseek will return to the same
location as was current when the recalibrate was done.
2. Calculation of the file offset is performed here simply to keep the unit
position register available for inspection. The actual file positioning
is done in position_sector.
3. In hardware, a seek to the current location will set Drive Busy status
for 1.3 milliseconds (the head settling time). In simulation, disc
service is scheduled as though a one-cylinder seek was requested.
*/
static t_bool start_seek (CVPTR cvptr, UNIT *uptr, CNTLR_OPCODE next_opcode, CNTLR_PHASE next_phase)
{
int32 delta;
uint32 block, target_cylinder;
const uint32 model = GET_MODEL (uptr->flags); /* get the drive model */
if (uptr->flags & UNIT_UNLOAD) { /* are the heads unloaded? */
dl_end_command (cvptr, status_2_error); /* the seek ends with Status-2 error */
return FALSE; /* as the drive was not ready */
}
if ((CNTLR_OPCODE) uptr->OP == Recalibrate) /* is the unit recalibrating? */
target_cylinder = 0; /* seek to cylinder 0 and don't reset the EOC flag */
else { /* it's a Seek command or an auto-seek request */
target_cylinder = cvptr->cylinder; /* seek to the controller cylinder */
cvptr->eoc = CLEAR; /* clear the end-of-cylinder flag */
}
if (target_cylinder >= drive_props [model].cylinders) { /* is the cylinder out of bounds? */
delta = 0; /* don't change the positioner */
uptr->STAT = uptr->STAT | DL_S2SC; /* and set Seek Check status */
}
else { /* the cylinder value is OK */
delta = abs (uptr->CYL - target_cylinder); /* calculate the relative movement */
uptr->CYL = target_cylinder; /* and move the positioner */
if ((cvptr->head >= drive_props [model].heads) /* if the head */
|| (cvptr->sector >= drive_props [model].sectors)) /* or the sector is out of bounds, */
uptr->STAT = uptr->STAT | DL_S2SC; /* set Seek Check status */
else { /* the head and sector are OK */
uptr->STAT = uptr->STAT & ~DL_S2SC; /* clear Seek Check status */
block = TO_BLOCK (uptr->CYL, cvptr->head, /* set up the new block position */
cvptr->sector, model); /* (for inspection only) */
uptr->pos = TO_OFFSET (block); /* and then convert to a byte offset */
}
}
if ((uptr->STAT & DL_S2SC) && cvptr->type == ICD) /* did a Seek Check occur for an ICD controller? */
dl_end_command (cvptr, status_2_error); /* the command ends with a Status-2 error */
else { /* the seek was OK or this is a MAC controller */
if (delta == 0) /* if the seek is to the same cylinder, */
delta = 1; /* then schedule as a one-cylinder seek */
uptr->wait = cvptr->seek_time * delta; /* the seek delay is based on the relative movement */
}
uptr->OP = next_opcode; /* set the next operation */
uptr->PHASE = next_phase; /* and command phase */
return TRUE; /* and report that the drive was ready */
}
/* Report an I/O error.
Errors indicated by the host file system are reported to the console, and
simulation is stopped with an "I/O error" message. If the simulation is
continued, the CPU will receive an Uncorrectable Data Error indication from
the controller.
*/
static t_stat io_error (CVPTR cvptr, UNIT *uptr)
{
dl_end_command (cvptr, uncorrectable_data_error); /* terminate the command with an error */
perror ("DiscLib I/O error"); /* report the error to the console */
clearerr (uptr->fileref); /* and clear the error in case we resume */
return SCPE_IOERR; /* return an I/O error to stop the simulator */
}
/* Disc library local utility routines */
/* Set the current controller address into the buffer.
The controller's current cylinder, head, and sector are packed into two words
and stored in the sector buffer, starting at the index specified. If the
end-of-cylinder flag is set, the cylinder is incremented to reflect the
auto-seek that will be attempted when the next sequential access is made.
Implementation notes:
1. The 13037 firmware always increments the cylinder number if the EOC flag
is set, rather than checking cylinder increment/decrement bit in the file
mask.
*/
static void set_address (CVPTR cvptr, uint32 index)
{
cvptr->buffer [index] = cvptr->cylinder + (cvptr->eoc == SET ? 1 : 0); /* update the cylinder if EOC is set */
cvptr->buffer [index + 1] = SET_HEAD (cvptr) | SET_SECTOR (cvptr); /* merge the head and sector */
return;
}
/* Start or stop the command wait timer.
A MAC controller uses a 1.8 second timer to ensure that it does not wait
forever for a non-responding disc drive or CPU interface. In simulation, MAC
interfaces supply an auxiliary timer unit that is activated when the command
wait timer is started and cancelled when the timer is stopped.
ICD interfaces do not use the command wait timer or supply an auxiliary unit.
Implementation notes:
1. Absolute activation is used because the timer is restarted between
parameter word transfers.
*/
static void set_timer (CVPTR cvptr, FLIP_FLOP action)
{
if (cvptr->type == MAC) /* is this a MAC controller? */
if (action == SET) /* should we start the timer? */
sim_activate_abs (cvptr->aux + timer, /* activate the auxiliary unit */
cvptr->wait_time);
else /* we stop the timer */
sim_cancel (cvptr->aux + timer); /* by canceling the unit */
return;
}
/* Return the drive status (status word 2).
In hardware, the controller outputs the Address Unit command on the drive tag
bus and the unit number on the drive control bus. The addressed drive then
responds by setting its internal "selected" flag. The controller then
outputs the Request Status command on the tag bug, and the selected drive
returns its status on the control bus. If a drive is selected but the heads
are unloaded, the drive returns Not Ready and Busy status. If no drive is
selected, the control bus floats inactive. This is interpreted by the
controller as Not Ready status (because the drive returns an inactive Ready
status).
In simulation, an enabled but detached unit corresponds to "selected but
heads unloaded," and a disabled unit corresponds to a non-existent unit.
Implementation notes:
1. The Attention, Drive Fault, First Status, and Seek Check bits are stored
in the unit status word. The other status bits are determined
dynamically.
2. The Drive Busy bit is set if the unit service is scheduled. In hardware,
this bit indicates that the heads are not positioned over a track, i.e.,
that a seek is in progress. In simulation, the only time a Request
Status command is allowed is either when the controller is waiting for
seek completion or for a new command. In the latter case, unit service
will not be scheduled, so activation can only be for seek completion.
*/
static uint16 drive_status (UNIT *uptr)
{
uint16 status;
uint32 model;
if (uptr == NULL) /* if the unit is invalid */
return DL_S2ERR | DL_S2NR; /* then it does not respond */
model = GET_MODEL (uptr->flags); /* get the drive model */
status = drive_props [model].type | uptr->STAT; /* start with the drive type and unit status */
if (uptr->flags & UNIT_WPROT) /* is the write protect switch set? */
status |= DL_S2RO; /* set the Protected status bit */
if (uptr->flags & UNIT_FMT) /* is the format switch enabled? */
status |= DL_S2FMT; /* set the Format status bit */
if (uptr->flags & UNIT_DIS) /* is the unit non-existent? */
status |= DL_S2NR; /* set the Not Ready bit */
else if (uptr->flags & UNIT_UNLOAD) /* are the heads unloaded? */
status |= DL_S2NR | DL_S2BUSY; /* set the Not Ready and Drive Busy bits */
if (sim_is_active (uptr)) /* is the drive positioner moving? */
status |= DL_S2BUSY; /* set the Drive Busy bit */
if (status & DL_S2ERRORS) /* are there any Status-2 errors? */
status |= DL_S2ERR; /* set the Error bit */
return status; /* return the unit status */
}